KB-7F09
C2B1-R0 — Option B: Direct Decomposition (least-privilege identity set) design [RECOMMENDED]
5 min read Revision 1
current-statec2b1-r0DIRECT_DECOMPOSITIONoption-brecommendedleast-privilegeidentity-setnuxt-serviceagent-data-syncreconcile-writerdot-observerSYNC_SECRETread-only2026-07-21
Option B — Direct Decomposition (least-privilege identity set)
Concept
Provision a small set of least-privilege machine identities, one per capability cluster, repoint each consumer to its own identity, verify (positive + negative), then rotate admin. No throwaway broad token; the end-state is the desired least-privilege posture with no second pass.
Identity set (provisioned in S1-APPLY — NOT created in R0)
Measured scope is small, so the nominal 5 groups collapse to 4 live + 1 deferred:
| # | Identity | Consumers covered | Capability (content only; admin_access=FALSE) | Arm |
|---|---|---|---|---|
| 1 | nuxt-service |
incomex-nuxt | READ registry/discovery/transitive collections; READ+WRITE workflows |
static token |
| 2 | agent-data-sync |
incomex-agent-data | READ sync-target collections; DELETE sync-target items | static token |
| 3 | reconcile-writer |
reconcile-tasks, reconcile-knowledge | READ+WRITE tasks, task_comments, knowledge_documents |
static token (replaces password login) |
| 4 | dot-observer |
dot-collection-health, dot-apr-health (+ dot-dot-coverage if API-confirmed) | READ+WRITE system_issues; READ health metadata |
static token (replaces password login) |
| 5 | dot-operator (DEFERRED) |
dot-apr-execute | — | — (job is BROKEN; provision only after it is fixed) |
Notes:
- The mission's "Nuxt read" and "Nuxt workflow writer" groups are merged into
nuxt-service: it is a single container with a single token var; splitting into two tokens in one process buys nothing. (Evidence: compose L174 single arm; all fallbacks UNSET.) - Converting the password-login consumers (reconcile, dot-*) to static tokens removes the 25-fail lockout vector entirely (token auth does not increment login attempts) and removes session churn.
- Scanners (
orphan,misclass) anddot-dot-healthneed no identity — they are PG-direct; just remove their dead credential exports (hygiene).
Exact permission rows per identity are designed in S1-DESIGN, not R0.
Migration sequence (blast-radius-first)
- Now / no gate: provision identities 1–4; repoint the 4 file consumers (reconcile×2, dot-collection-health, dot-apr-health) → edit protected files only, no restart. This removes the password blast-radius and the lockout risk immediately.
- Now / no gate:
docker saveagent-data image → repointagent-data-sync→ recreate agent-data. - Parallel: run NUXT-IMG-H1 (pin image to local digest
72715a...,docker savebackup, prove a no-pull recreate on a throwaway clone). - After NUXT-IMG-H1: repoint Nuxt →
nuxt-service→ recreate nuxt. - C2B1-S2 negative verification per identity (each identity is denied everything outside its envelope; admin-surface calls 403). C2B1-R1 proves machine-dependency-on-admin = 0.
- C2B1-X: rotate admin password + static token. C2B1-V: encrypted backup + restore proof.
Rollback
Per-identity and per-consumer. Each repoint reverts by restoring one file/compose line (+ recreate for containers). Admin credential remains valid until X, so fall-back is always available.
Trade-off
- + End-state is least-privilege with no throwaway broad token and no second pass.
- + Password→token conversion eliminates the lockout vector.
- − ~2h more design/verify than Option A (4 policies vs 1).
- = Same Nuxt-image gate and same 2 restarts as Option A.
Return values
DIRECT_DECOMPOSITION_ETA_OPERATOR_HOURS = ~8h (4 identities+policies 3h; file repoints 0.75h;
agent-data 0.5h; NUXT-IMG-H1 1.5h; nuxt 0.5h;
rotate 0.5h; per-identity verify 1.25h)
DIRECT_DECOMPOSITION_ETA_ELAPSED_HOURS = ~48-72h (staged verification per identity; cautious)
DIRECT_DECOMPOSITION_RESTART_COUNT = 2 (agent-data, nuxt) — Directus NOT restarted
DIRECT_DECOMPOSITION_BLOCKERS =
- NUXT_IMAGE_RECREATE_READY = NO -> NUXT-IMG-H1 mandatory before nuxt repoint (SHARED with Option A)
- agent-data sync-target collection set is dynamic -> enumerate exactly in S1 (coarse set known)
- dot-dot-coverage API-vs-PG usage low-confidence -> confirm in S1 (fold into dot-observer or drop)
- SYNC_SECRET == admin token -> must be decoupled before/with C2B1-X