C2B1-HYB-S2 REV2 — Consumer Parity & Safe-Write Verification (2026-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 /presets → 403 · 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:
- 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. - 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.
- Refactor the three routes to PG-direct counting/relations (as
scanner-counts.shalready does viaverify_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).