KB-136B

C2B1-HYB-S2 REV2 — Consumer Parity & Safe-Write Verification (2026-07-22)

11 min read Revision 1
current-statec2b1-hyb-s2hybrid-fast-containmentdirectustransitional-identitynon-adminconsumer-paritysafe-write-proofrefresh-countshard-gatenuxt-service-tokenregistry-visibilitydiscovery-relationsregistry-healthcanaryzero-residueno-driftzero-permission-deltafield-wildcard-accepteddecomposition-deadlineREPPOINT_TO_TRANSITIONAL_TOKEN_SAFE_NOHOLD_C2B1_HYB_S2_NUXT_PARITYHOLD_C2B1_HYB_S2_REFRESH_COUNTS_PARITYrotation-hold2026-07-22

C2B1-HYB-S2 REV2 — Consumer Parity & Safe-Write Verification (2026-07-22)

Verdict

REPPOINT_TO_TRANSITIONAL_TOKEN_SAFE = NO

PRIMARY   : HOLD_C2B1_HYB_S2_NUXT_PARITY
HARD-GATE : HOLD_C2B1_HYB_S2_REFRESH_COUNTS_PARITY

The transitional non-admin identity provisioned in S1 is functionally sound and safely bounded for its scoped consumers — agent-data, reconcile-tasks, reconcile-knowledge, DOT-health, the Nuxt workflow-mutation flow, and 21/21 union reads — and every scoped write is proven and cleanly reversible. It cannot yet replace the bootstrap admin for the full Nuxt service-token surface: three Nuxt registry/discovery routes require whole-schema read visibility (all 155 registry collections and all 371 relations) that a least-privilege identity intentionally lacks.

This is not a FAIL — nothing is broken, nothing is exposed, zero drift, zero residue. It is a scope-completeness HOLD. Designing the registry/discovery/health read strategy is explicitly C2B1-HYB-S4-DECOMP work. Rotation remains HOLD. S3 is not opened.

Cross-ref: [S1] c2b1-hybrid-s1-transitional-identity-provisioning-2026-07-22.md · [B0] c2b1-hybrid-b0-fresh-recovery-and-config-checkpoint-2026-07-22.md


1. Baseline (entry gate = PASS, no drift)

Object Expected Measured OK
S1 verdict PASS…SHADOW_READY present (ledger /root/c2b1s1/ids.env)
policy machine-transition-policy (7c19e8cc) admin_access false false
policy app_access false false
transitional permission rows / collections 37 / 21 37 / 21
perm id range 1529–1565 1529–1565
action split r21 / c7 / u6 / d3 r21 / c7 / u6 / d3
malformed permission rows (fields='{"*"}') 632 632
consumers repointed / credential rotated 0 / 0 0 / 0
token file /etc/incomex/c2b1/machine-transition.env root:root 0600 root:root 0600
B0 + S1 rollback ledgers present present
identity chain user 73aa333b active · role f03a039e · access 86abbb5e → policy verified, has_token

Directus 11.5 attaches the policy to the role (access row user=null, role-scoped); the user inherits via role membership.

2. Method

Tested by real consumer code-path, not by permission row. Admin baseline for reads = PostgreSQL ground-truth count (admin bypasses all permission filters, so PG count == admin REST count). Transitional-token calls issued via curl on the VPS against the internal Directus (directus:8055, docker docker_incomex) and the public path (https://directus.incomexsaigoncorp.vn), token sourced at runtime (never logged). TRANSITIONAL_TOKEN_PRE_POST_MATCH = YES (token unchanged; authenticated as 73aa333b throughout; fingerprint matches S1 ledger).

3. Consumer parity matrix

# Consumer / route Real code-path Transitional result Verdict
4.1a Nuxt · registry counts (counts.get, raw-counts.get) read meta_catalog (+system_issues) 200, count-parity PASS
4.1b Nuxt · registry health (health.get) per-collection GET /items/<governed> non-union → 403 → all "ORPHAN" FAIL
4.1c Nuxt · refresh-counts (refresh-counts.post) read meta_catalog ✓ + PATCH ✓; countDistinct on 155 registry collections 21 ok / 134×403 FAIL (hard gate)
4.1d Nuxt · discovery (discovery/relations.get) GET /relations (system) 0 vs admin 371 FAIL
4.1e Nuxt · transitive (transitive/_code_.get) GET /items/entity_dependencies 200, parity (142) PASS
4.1f Nuxt · workflow read read workflows/steps/relations/WCR 200, parity PASS
4.1g Nuxt · workflow mutation update workflows; CRUD steps+relations; c/u WCR; create task+comment canary A: all pass PASS
4.1h Nuxt · Balo battery (balo/reconcile.post) caller-session-gated + Unix-socket bridge (own creds) — no service token unaffected by repoint PASS
4.3 agent-data sync knowledge_documents read + delete canary B: pass PASS
4.4 reconcile-tasks tasks + task_comments read 200, parity (10 / 75) PASS
4.5 reconcile-knowledge knowledge_documents c/r/u/d canary B: pass PASS
4.6 DOT health (public path) system_issues create via public HTTPS canary C: pass PASS

Union read-parity: 21/21 collections, transitional count == PG(admin) == 0 mismatches — incl. large tables birth_registry 1,216,887 · entity_labels 904,549 · system_issues 226,357 · knowledge_documents 9,605.

Root cause of the three FAILs is single and structural: the Nuxt service token has a dual role — (a) narrow business operations over ~21 collections, which the transitional identity covers perfectly, and (b) whole-schema registry/discovery/health administration (count every collection, list every relation), which a least-privilege identity cannot serve without near-admin read breadth.

4. refresh-counts (hard gate) — measured

Read-harness replayed the exact refresh-counts.post call (GET /items/<collection>?aggregate[countDistinct]=id) for all 155 meta_catalog.registry_collection values, without the PATCH step (read-only, non-mutating), under both tokens:

TRANSITIONAL : 200=21   403=134  404=0
ADMIN        : 200=139  403=16   404=0
TRUE GAP (admin=200 AND transitional=403) = 118 collections

The 118-collection gap = all os_* CRM, all block_*/CMS + blocks/pages/posts/forms, organizations/contacts, workflow_categories, checkpoint_*, universal_rule_*, categories, etc. The 16 admin-403 are stale/non-collection registry entries (PG views universal_edges/pivot_results/v_registry_*, backup table _permissions_backup_s132c, _uncategorized) — not a transitional-specific regression.

Closing this requires read on ~118 collections = the explicitly-forbidden blanket-grant (and would include a system collection and PG views). Therefore no read permission was added in S2; the design is deferred to DECOMP. The write-half of refresh-counts (PATCH /items/meta_catalog/<id>) was proven working under the transitional token (canary D), isolating the gap to the read side only.

5. Canary & safe-write results — 38/38 PASS, 0 residue

Family Operations (transitional unless noted) Result
B · knowledge knowledge_documents CREATE→READ→UPDATE→DELETE all pass, residue 0
C · system_issue (public path) CREATE(public) 200 · READ(public) 200 · transitional DELETE denied 403 · admin cleanup 204 pass, residue 0
D · meta_catalog UPDATE 200 · exact PRE restore verified (last_scan_date PRE==POST) pass
A · workflow family admin scaffold workflow → transitional: UPDATE workflows; CREATE/UPDATE/DELETE workflow_steps; CREATE/UPDATE/DELETE workflow_step_relations; CREATE/UPDATE workflow_change_requests; CREATE tasks; CREATE task_comments → transitional deletes steps/relations, admin deletes WCR/task/comment/workflow all pass, residue 0

§6 negative admin/scope spot-check — all denied: GET /users /roles /permissions /settings /policies /presets403 · transitional CREATE workflows → 403 · DELETE workflows → 403 · DELETE tasks → 403 · CREATE os_deals (out-of-union) → 403 · CREATE directus_fields (system) → 403. admin_access/app_access remain false; no new admin/session capability (static Bearer creates 0 sessions).

6. Permission delta — NONE

NEW_WRITE_COLLECTIONS = 0     ADMIN_PERMISSION     = 0
NEW_WRITE_ACTIONS     = 0     SCHEMA_PERMISSION    = 0
NEW_READ_PERMISSIONS  = 0     MALFORMED_ROWS_DELTA = 0
permission rows       = 37 (unchanged)   collections = 21 (unchanged)
TRANSITIONAL_FIELD_WILDCARD = ACCEPTED_TEMPORARILY_UNTIL_DECOMP

S2 left the S1 permission set byte-for-byte intact (the only parity fix on the table — 118 registry reads — is a forbidden blanket-grant, deferred).

7. Zero-residue / no-drift

CANARY_RECORDS_REMAINING           = 0   (8 tables swept by marker = 0)
EPHEMERAL_CONTAINERS_REMAINING     = 0
EPHEMERAL_NETWORKS_REMAINING       = 0
CONSUMER_CONFIG_CHANGED            = 0
CONSUMER_REPOINTED                 = 0
PRODUCTION_CONTAINER_RESTARTED     = 0   (11 containers, identical)
BOOTSTRAP_ADMIN_CREDENTIAL_CHANGED = NO
MALFORMED_PERMISSION_ROWS          = 632 (632→632)
directus_sessions                  = 35 (35→35)
policy admin_access/app_access     = false/false
TRANSITIONAL_TOKEN_PRE_POST_MATCH  = YES

Canary-touched tables restored exactly: knowledge_documents 9605 · system_issues 226357 · workflows 2 · workflow_steps 70 · workflow_step_relations 80 · workflow_change_requests 3 · tasks 10 · task_comments 75 · meta_catalog 169. (Sequence values advanced — expected, not drift.)

8. Next gate — C2B1-HYB-S4-DECOMP

PASS was not achieved (hard gate held), so S3 is not opened. The decisive question for DECOMP: how to serve the whole-schema registry/discovery/health read surface without granting the transitional identity near-admin breadth. Three candidate designs:

  1. Split the Nuxt service identity — keep the narrow business/workflow token (≈ current transitional) and add a separate read-only "registry-reader" identity with schema-wide read (no writes, no admin) used only by refresh-counts/health/discovery.
  2. Bounded schema-wide READ on the transitional identity — read-only on all managed collections (excludes system + PG views + backup tables), with field narrowing designed in DECOMP; still zero write/admin expansion.
  3. Refactor the three routes to PG-direct counting/relations (as scanner-counts.sh already does via verify_counts()), eliminating the Directus per-collection permission dependency entirely — a code change, most aligned with least privilege.

Standing gates unchanged: C2B1_ROTATION = HOLD; OWNER_EXTERNAL_DIRECTUS_ADMIN_CONSUMER_DECLARATION = REQUIRED_BEFORE_C2B1_X; NEWEST_PROVEN_RESTORABLE = 2026-07-22T08:32:50Z.


READ-ONLY except explicitly-authorized reversible canaries; 0 permission mutation; 0 consumer/config/container change; token hygiene held (no token/prefix/last4/fingerprint published).