The predecessor AirGap Controller, Time host, and shared DAL sources are withdrawn by OK-5 under the approved posture split. Platform owns generic environment-state custody; the surviving policy/replication and OfflineKit time-verification libraries own interpretation and verification. This does not retire the exchange libraries or claim the separate live stop/schema window has run. Earlier host instructions below describe the predecessor. See the current split in
docs-archive/modules/export-center/consolidation-design.md.
Microservice → database ownership matrix
Status: historical baseline with individually dated follow-up evidence + Accepted target (ADR-039 rev-13). Sources: devops/compose/docker-compose.stella-services.yml, live stellaops-postgres catalog (read-only), AddStartupMigrations sweep over src/, the data-and-stores role playbook (AGENTS.md — Memory). src/ wins over this document; rows marked (verify) lack a confirmed code anchor.
Source verification (2026-09-09)
Verified against 286391582b4d00782ae61002941079d40ef8c68e; current unresolved work is routed by the estate board. The older physical inventory and per-family rows below retain their own evidence dates. They are not a fresh census of the running estate.
| Signal | Verified result | Re-verify |
|---|---|---|
| Build-boundary exceptions | 31 pins across eight owning sprints, zero library impurities, no missing owner/seam/sunset fields | Parse build-boundary/legacy-edge-register.json |
| Generic base-compose connection | 0 environment assignments (2026-09-14, PLT-3); the only remaining textual hits are comments that say it is retired. Earlier: 32 textual lines; 25 environment assignments in 21 service definitions after YAML parsing | rtk rg -n STELLAOPS_POSTGRES_CONNECTION devops/compose/docker-compose.stella-services.yml; parse service environment maps |
stellaops-platform naming footprint | 299 tracked files at the verified commit, including 34 under src/devops/tools/.gitea after frozen roots are excluded. A text hit is not proof of runtime use | rtk git grep -l stellaops-platform 286391582b -- |
Central authority_app shell | The previously attributed Authority migration plugin was removed by 86056143f2; its dotted resource prefix never matched the migrations. This does not establish which historical process created the shell. 2026-09-14 (PLT-4): MigrationModulePlugins.cs is deleted; devops/compose/postgres-init/00-v1-baseline.sql now creates the authority / authority_app shells inside stellaops_authority and nothing else | rtk rg -n 'authority_app' devops/compose/postgres-init/00-v1-baseline.sql |
No deployed ownership or closure row was changed by this source verification.
1. Historical physical baseline (measured 2026-07-22)
- One shared instance
stellaops-postgres(postgres:18.1), one volume (99.25 GB), three databases:stellaops_platform91 GB,stellaops_authority252 MB,postgres7.7 MB. - Every service except Authority connects with the same
STELLAOPS_POSTGRES_CONNECTION(→stellaops_platform). ~46 service schemas share that database. - Vulnerability data plane inside
stellaops_platform:vuln71 GB,vex9,625 MB,vexhub5,908 MB,concelier2,322 MB ≈ 89 GB = 97.8%. Everything else totals < 3 GB (largest:scanner2,110 MB,advisoryai271 MB,timeline199 MB;platformitself 2,024 kB).
Currency note (re-measured 2026-08-03, read-only, same lab database). These sizes move a lot:
vuln24 GB,concelier1,551 MB,vex3,968 kB,vexhub680 kB ≈ 25.5 GB,scanner2,701 MB. The lab was reset between the two readings, so the 2026-07-22 figures above are a fuller-corpus snapshot and neither reading is the production estate. Do not use either as a reclamation or headroom target — theSPRINT_20260722_008VULN-G1 window measures the target database at step 0; see the G1 execution-window runbook §1–2.
Source boundary update (CM-6, 2026-08-26; verified against
d3358c84319b0da835e944d70f360f7df526d9c5). Platform no longer reads RO topology tables or carries the RO migration plugin. It reads active tenant UUIDs from its ownshared.tenants, then consumes the tenant-scoped RO owner API described in X22 below. Re-verify withrg -n "ReleaseOrchestratorMigrationModulePlugin|PostgresTopologySourceReader" src/Platform(the plugin name must return zero hits; the whole plugin mechanism was deleted 2026-09-14, PLT-4) andrg -n "topology-snapshot" src/Platform src/ReleaseOrchestrator. Platform’s own098_v1_release_topology_inventory_projection.sql(Startup band) creates therelease.topology_*projection tables instellaops_platform.
2. Deployable → schema matrix
Replica sets (web/worker/init of one service) are one row — they legitimately share the owner database. Wave = adoption wave from ADR-039 (1 = vulnerability plane now, 2 = large/high-churn next, 3 = small batch moves). Risk = migration risk of the move itself.
The
Clustercolumn is historical as of 2026-08-14 — read it as “which cluster this row was once planned for”, not as current topology.SPRINT_20260810_002(PTC-1…PTC-5) consolidated the estate onto ONE PostgreSQL server per the ADR-039 P1 topology clarification: “own physical database” means a logical database + its own role on the shared installation cluster, and a dedicated server is an optional scaling choice, not the default. So avulndatavalue below names a retired cluster (STOPPED 2026-08-14, volume kept as the repoint rollback), and everystellaops_*database in this table that exists today lives onstellaops-postgres/db.stella-ops.local. The boundary is the per-database role plusREVOKE CONNECT, not the container — see §5.4 for the measured census.
| Deployable(s) (compose) | Schemas written today | Migration assembly (AddStartupMigrations) | Category | Proposed database / role (stellaops_*) | Cluster | Wave | Risk |
|---|---|---|---|---|---|---|---|
| platform | platform, shared, release (19 live tables + the 9 topology_* projection tables 098 owns since 2026-09-14), catalog_replica, eventing (P6), crypto (P4) — the complete set; nothing else remains in stellaops_platform after 021 PLT-4’s four windows (981 MB, fresh-install-proven) | Platform.Persistence | control plane | platform — DB name stays stellaops_platform(owner correction 2026-07-22; keeps shared.tenants while the resolver contract stands). End-state 2026-09-14 (021 PLT-4): 985 MB, down from 91 GB; residue left = policy 11 tables/41 rows (Policy’s ruling) and symbols/golden_sets/groundtruth seed rows (Vulnerabilities’ ruling). | control | — (stays) | low |
| authority | authority, authority_app, issuer, catalog_replica (P4), eventing (P6) — the complete set since 2026-09-14 (SPRINT_20260914_005 PEF-3 dropped the ten foreign shells the pre-prune init file had created inside stellaops_authority: advisoryai analytics attestor evidence_locker platform policy proofchain release release_app shared) | Authority.Persistence | control plane | authority (done — the precedent) | control | done | — |
concelier (+ concelier-jobs-init) → merges into StellaOps.Vulnerabilities | concelier (sources/DTOs/jobs), vuln canonical advisory tables + issue_observations/_evidence_refs/_linksets (global consensus) | Concelier.Storage → Vulnerabilities.Persistence | data plane (hub) | vuln(single hub DB stellaops_vuln, ADR-039 D2; fresh build, no data moved — D7) | vulndata | 1 | rename + rebuild, no data migration |
concelier (today’s serving role) — vuln.issue_gate_decisions + rebuild queue | per-tenant gate projection | Concelier.Storage (today) | control plane (tenant decisions) | moves to policy— Policy-owned projection fed by hub events (ADR-039 D3) | control | 1 | rebuilt by Policy projector |
excititor-web + excititor-worker → merges into StellaOps.Vulnerabilities | vex (claims, raw blobs/documents, product dedup, checkpoints, quarantine) | Excititor.Persistence → Vulnerabilities.Persistence | data plane (hub) | vuln | vulndata | 1 | rename + rebuild |
vexhub-web → merges into StellaOps.Vulnerabilities | vexhub (statements, sources, provenance, conflicts) — rows currently written by Excititor’s sink, not by VexHub | VexHub.Persistence → Vulnerabilities.Persistence (dist schema) | data plane (distribution view, rebuildable) | vuln | vulndata | 1 | rename + rebuild |
| scanner-web + scanner-worker (+ cache-init) | scanner + family-owned eventing on stellaops_scanner; legacy stellaops_platform.scanner and the mishomed signals objects are retained offline pending SCN-6 | StellaOps.Scanner.Persistence (sole scanner migration authority) | data plane | scanner — live on stellaops_scanner since 2026-08-29 | control | 2 — DONE | old-source retirement/drop remains SCN-6 |
advisory-ai-web + advisory-ai-worker (+ data-init) → consolidated as advisoryai-web + advisoryai-worker | advisoryai | StellaOps.AdvisoryAI.Persistence (sole family migration authority) | data plane | advisoryai — live on stellaops_advisoryai since 2026-09-04 (AAI-9 window 20260904-aai9; 13,238 carried rows copied under a writer freeze, count + content-digest parity empty-diff on all 25 tables) | control | 2 — DONE | AAI-10 retired the predecessor path 2026-09-07 (compose keys deleted, host obsoleted). Legacy stellaops_platform.advisoryai is retained OFFLINE as the rollback input until its separately approved destructive drop |
opsmemory-web → dissolved into advisoryai-web (in-process module) | opsmemory | merged ↑ (StellaOps.AdvisoryAI.Persistence) | control plane | opsmemory — live on stellaops_advisoryai since 2026-09-04 (same AAI-9 window; decisions carried empty, write+recall proven on the consolidated host) | control | 3 — DONE | AAI-10 retired opsmemory-web 2026-09-07: compose key deleted, host frozen at src/__Obsoleted/AdvisoryAI/StellaOps.OpsMemory.WebService/, routes served in-process by advisoryai-web. Legacy stellaops_platform.opsmemory is retained OFFLINE as the rollback input until its separately approved destructive drop |
| timeline-web | timeline (+ timeline_app, + eventing) — MOVED: live on stellaops_timeline(role timeline; REVOKE CONNECT re-proven 2026-09-11 — CONNECT true for stellaops_timeline, false for all 21 sibling stellaops_* databases). The database was fresh-converged 2026-08-14 08:20 by PTC-4/Window B and the historical corpus was deliberately NOT carried (owner ruling C), so it holds events from 2026-08-14 onward — 452,007 at 2026-09-11. The legacy stellaops_platform.timeline schema was snapshotted and dropped 2026-08-24 under that ruling, and the 2026-08-24 partition bridge’s rollback heap was dropped 2026-09-11 under W3-01’s recorded approval. The vestigial timeline_app schema left on stellaops_platform (0 tables, 1 function, no policy callers — every platform RLS policy calls a schema-qualified require_current_tenant belonging to another *_app) is SPRINT_20260722_026’s to disposition, not Timeline’s. 2026-09-15 (SPRINT_20260911_001 TLC-1): a third schema, eventing, joins this database — the P4 reliability store (inbox, consumer_checkpoints, leases, outbox, stream_state, remote_stream_consumers) that StellaOps.Eventing.Reliability self-migrates under its own eventing.schema_migrations ledger, so the DC-29 findings.dispositions consumer can admit, insert into timeline.events and advance its checkpoint in ONE transaction. Per-host by rule, never shared; timeline.schema_migrations stays at the consolidated baseline’s four rows. | Timeline persistence | evidence/read-model | stellaops_timeline — live | control | 2 | low |
| notify-web + notify-worker | notify, notify_app, eventing, crypto, catalog_replica | Notify.Persistence.Consolidated + owner-scoped shared migrations; Notify.Persistence supplies repositories only | control plane | notify — live on stellaops_notify | control | 2 — database cutover complete | least-privilege owner; tenant-scoped operations |
notifier | — | control plane | folded into notify on stellaops_notify | control | DONE | sources frozen at src/__Obsoleted/Notifier/ on 2026-09-08 | |
| policy-engine | policy (+ policy_app); plus foreign writes: scheduler (via its scheduler connection), release.security_finding_projection (verify writer path); foreign read: vuln.issue_gate_decisions | Policy.Persistence | control plane | policy | control | 2 | medium (contracts, not size) |
| jobengine-web + jobengine-worker (+ jobengine-estate-worker) | scheduler (+ scheduler_app), packs, eventing — MOVED 2026-09-12 (012 JOB-9, ADR-039 D14): live on stellaops_jobengine(role jobengine). Succeeds scheduler-web, packsregistry-web and packsregistry-worker, whose compose keys, publish keys and containers were all removed in the same window. The copy was a column-listed --data-only merge of the only three tables holding rows — scheduler.runs 1634, scheduler.schedules 11, packs.audit_log 1 — with exact count parity, an identical run-state histogram (running 1, completed 1127, error 506) and the live estate-doctor schedule row SKIPPED rather than overwritten so its estate_constraints survived. No BYPASSRLS grant was needed: both databases sit on the shared server, so the merge ran as the superuser and the boundary role’s attributes were never touched. Legacy stellaops_platform.scheduler/scheduler_app/packs DROPPED 2026-09-14 (021 PLT-4, 94f86d6cec; evidence docs/implplan/_evidence/20260914-plt4-closeout/); their last creator, the scheduler block of devops/compose/postgres-init/00-v1-baseline.sql, was removed the same day (SPRINT_20260914_003 JEF-1). | StellaOps.JobEngine.Persistence (sole authority; both predecessor DALs absorbed byte-identically, and the two central-migrator plugins deleted) | control plane | stellaops_jobengine — DONE | control | DONE | — |
| release-orchestrator (+ agent-ca-init) | release, release_orchestrator, release_orchestrator_agent, scripts — scripts VERIFIED LIVE ON THE OWN DATABASE and its platform-side copy DROPPED 2026-09-13 (021 PLT-4): docker inspect stellaops-release-orchestrator shows BOTH the primary connection and the Scripts__Postgres__ConnectionString side channel resolving stellaops_release_orchestrator, so the 3-table stellaops_platform.scripts copy was residue (1 bookkeeping row, 0 domain rows), not a live read. SPRINT_20260913_001 ORP-4 had been filed claiming the opposite and was ABANDONED on this measurement. — MOVED 2026-08-22: live on stellaops_release_orchestrator(role release_orchestrator, REVOKE CONNECT proven by a 10×10 CONNECT probe: 10 own accepts, 90 sibling refusals; consolidated baseline converged 55 domain tables + one ledger; table-scoped data-only copy moved 2,953/2,953 rows across 54 source-present tables, per-table row/digest parity and 25 FK-orphan probes all green; tenant_deployment_settings is the one target-only table; doctor 11/11 post-cutover — D-RO5-9, SPRINT_20260722_018). Legacy stellaops_platform schemas retained for rollback; RO-6’s own destructive-drop approval has nothing to approve — the confirmed-DEAD drop set is measured EMPTY on this estate (RO-3 2026-08-06, re-corroborated 2026-08-17); DROP SCHEMA release stays forbidden (Platform owns 19 live tables in it) | ReleaseOrchestrator(+.Environment) | control plane | stellaops_release_orchestrator — DONE | control | 3 | medium (Platform/Policy seams) |
| agent-core | (verify — likely none / registry cache volumes) | — | control plane | — | — | 3 | low |
| evidence-web + evidence-worker | evidence (+ carried attestor, proofchain, evidence_locker, evidence_locker_app) — MOVED 2026-09-05 (011 EVD-9): live on stellaops_evidence(role evidence, owner-only CONNECT with REVOKE CONNECT … FROM PUBLIC, 13/13 forced RLS asserted, parity 31/31 tables at count and canonical-row SHA-256, fk_artifacts_bundle added, 0 family sessions left on stellaops_platform). Legacy stellaops_platform schemas retained: the drop is a SEPARATE destructive approval and has NOT been taken — see EVD-10b. | StellaOps.Evidence.Persistence.Consolidated | evidence | stellaops_evidence — DONE | control | 3 | low |
attestor, proofchain — MOVED 2026-09-05 with the family (row above). Compose key, container and image all gone; the host project has no Program.cs. The domain and application libraries are kept and live under src/Evidence/__Libraries/ | Attestor.Persistence (library only) | evidence | stellaops_evidence — DONE | control | 3 | low | |
| attestor-tileproxy | — (no database) | — | evidence | — | — | — | alive and unchanged; an Evidence-family member by ownership, keeping its own compose key |
| tsa | — (no database; compose_tsa-data volume) | — | evidence | — | — | — | alive and unchanged; an Evidence-family member by ownership, keeping its own compose key and volume |
| signer | signer — MOVED 2026-08-17 (019 SGN-5): now stellaops_signer, converged by StellaOps.Signer.Persistence. The former crypto entry here was WRONG and is deleted (D-SGN1-1, re-verified live 2026-08-18: stellaops_signer contains only public and signer; Signer’s 50-project closure holds no shared-persistence library, so it instantiates no crypto schema) | Signer.Persistence (was Signer.KeyManagement) | control plane | stellaops_signer — live | control | 3 | low |
evidence_locker (+ _app) — MOVED 2026-09-05 with the family. The object store was carried, not recreated: compose_evidence-data keeps its physical name at the same in-container path. Blue/green siblings retired in the same change | EvidenceLocker persistence (libraries only) | evidence | stellaops_evidence — DONE | control | 3 | low | |
| findings-ledger-web | findings — MOVED 2026-08-26 (010 FND-9): the consolidated findings-web/findings-worker family is live on stellaops_findings(role findings with BYPASSRLS+CREATEROLE; owner-only CONNECT; Findings 001–008 + Eventing 001–004 converged fresh at HEAD 3eeed9b925; table-scoped data-only copy 7/7 tables with row-count + content-hash parity — 533 security_finding_projection, 37 security_risk_snapshot, five singles incl. the byte-faithful seam cursor). The shared platform eventing schema was measured LIVE shared infrastructure and excluded from copy and revocation. Legacy stellaops_platform schemas retained: the drop is a SEPARATE destructive approval with its own window and has NOT been taken. Measured 2026-08-27 by exact count(*) (note pg_stat_user_tables reports 0 for all four and is stale): findings 35 tables / 2296 kB / 5 rows, findings_security 7 tables / 1408 kB / 575 rows (security_finding_projection 533, security_risk_snapshot 37, schema_migrations 5), analytics 12 tables / 1312 kB / 2 rows, riskengine 2 tables / 152 kB / 1 row. So the drop is not row-free — it discards 570 real projection rows. | Findings ledger persistence | data plane | stellaops_findings — DONE | control | 3 | low |
riskengine — MOVED 2026-08-26 with the family (row above); riskengine.risk_score_results measured 0 rows at source (the recorded worker-scoring fold gap), so the copy carried nothing. Both hosts and their source trees are now gone (owner ruling Q-2); the schema is kept and owned by the consolidated family | RiskEngine.Core + .Infrastructure (libraries only) | data plane | stellaops_findings — DONE | control | 3 | low | |
| findings-security-web | findings_security — MOVED 2026-08-26 with the family (row above); the consolidated security plane serves from Findings-owned local tables since the FND-X18-7 read cutover (2026-08-27); the typed 503 is now only the cold-start answer on an estate with no advisory generation — corrected 2026-09-16, SPRINT_20260914_001 VRP-6 | Findings.Security.Persistence | read-model | stellaops_findings — DONE | control | 3 | low |
analytics — MOVED 2026-08-26 with the family (row above); all source analytics tables were 0 rows at the move (2 bookkeeping rows at 2026-08-27). The host and its source tree are now gone; the Application/Persistence libraries are kept and remain unwired (the X18 re-home they were waiting on landed under FND-X18-5…7, so their disposition is now a separate call — noted 2026-09-16, SPRINT_20260914_001 VRP-6) | Findings.VulnCorrelation.Persistence | read-model | stellaops_findings — DONE | control | 3 | low | |
| signals | signals — MOVED: live on stellaops_signals(role signals, REVOKE CONNECT proven 2026-08-23; 23 tables + 2 matviews + 4 views, ledger = the two owned rows, zero rows in every table). Legacy stellaops_platform.signals retained for rollback and measured zero domain rows — a real count(*) sweep, not n_live_tup | Signals.Persistence | data plane | stellaops_signals — DONE | control | 3 | low |
| sbomservice | sbom — MOVED 2026-08-22: live on stellaops_sbomservice(role sbomservice, REVOKE CONNECT proven 2026-08-23; sbom 26 tables + an eventing schema of 7). Legacy stellaops_platform.sbom retained for rollback with its 6 non-ledger rows, all of which are present in the target | SbomService.Persistence | data plane | stellaops_sbomservice — DONE | control | 3 | low |
| graph-api | graph + reachgraph— measured LIVE 2026-09-07 (ROA-7, read-only): already on stellaops_graph. The container recreated 2026-09-07 carries STELLAOPS_POSTGRES_GRAPH_CONNECTION -> stellaops_graph as role graph, and that database holds BOTH schemas with exactly the two-row ledger SPRINT_20260722_023 GRA-9 specifies (001_graph_consolidated_baseline.sql, 002_force_reachgraph_tenant_rls.sql, applied 2026-09-02). All six CAS tables across both databases are 0 rows. This is currency, not a claim that GRA-9’s window closed — that row still owes its forcing functions and its own status. | Graph.Persistence (consolidated baseline) | data plane | stellaops_graph | control | 3 | low |
reachgraph — the schema lives on in stellaops_graph beside graph, converged by StellaOps.Graph.Persistence’s consolidated baseline; what was deleted is the second HOST, not the data. GRA-10 removed src/ReachGraph/, the compose service block, the publish key and the services-matrix row, and moved the direct reachgraph.stella-ops.local alias onto graph-api. The last live measurement before deletion (GRA-9 window close, 2026-09-08) found the host DORMANT: no database connection at all, Router publication off since ROA-7, X20’s publisher unarmed, all three CAS tables 0 rows. The running container is not removed by that change — stopping stellaops-reachgraph-web and re-rendering the 17 recorded chains that still name it is GRA-10’s live half. | — (was ReachGraph.Persistence) | data plane | stellaops_graph | control | 3 | low | |
| binaryindex-web (source retired, BIN-10) | Predecessor binaries, binary_index; golden_sets retires to fixtures/bench per D-BIN3-6 | No current source migration owner | retained predecessor data | binary knowledge → stellaops_vuln; no predecessor business payload to copy; DROP deferred | — | 3 | low |
| symbols (source retired, BIN-10) | Predecessor symbols; demo source/catalog seeds excluded from hub | No current source migration owner | retained predecessor data | metadata → stellaops_vuln, blobs → configured CAS (§6.2); no predecessor manifests/blob bytes; DROP deferred | — | 3 | low |
src/__Obsoleted/ExportCenter/ | export_center (+ _app) — MOVED 2026-09-13 to stellaops_offlinekit, where it sits beside the new offlinekit schema (measured 2026-09-14: that database holds exactly export_center, export_center_app, offlinekit). The predecessor database stellaops_exportcenter still exists and still holds its own export_center/export_center_app copy — it is the window’s rollback, and dropping it plus revoking role exportcenter is a SEPARATE destructive approval that has not been taken | StellaOps.OfflineKit.Persistence (was ExportCenter.Infrastructure) | control plane | stellaops_offlinekit — DONE | control | done | — |
| replay-web | replay | Replay.WebService | evidence | replay | control | 3 | low |
| integrations-web | integrations — MOVED 2026-08-23: live on stellaops_integrations; the legacy shared schema was DROPPED 2026-09-14 (021 PLT-4, only migrator bookkeeping remained) | StellaOps.Integrations.Persistence (single host-owned authority; Platform plugin retired 2026-08-24) | control plane | stellaops_integrations — DONE; Policy gate decisions read through Policy’s owner API since 2026-09-14 (X23, ORP-3) — no cross-database read remains | control | done | — |
packs — folded into the JobEngine family row above | merged into StellaOps.JobEngine.Persistence | control plane | stellaops_jobengine — DONE | control | DONE | hosts frozen at src/__Obsoleted/JobEngine/ | |
doctor SCHEMA survives pending the stage-5 destructive drop | doctor | Doctor persistence | ops | doctor | control | 3 | low |
| issuer-directory | issuer — MOVED 2026-09-08 (016 AUTH-9, fourth window): live on stellaops_authority, served by the FOLDED Authority host rather than by its own deployable (S0 container-shape decision). The standalone stellaops-issuer-directory container was stopped in that window and the gateway route retargeted to authority.stella-ops.local. The copy carried nothing — source stellaops_platform.issuer measured 0 rows in all four domain tables at the fence, so the parity gate read NOOP_ALREADY_EQUAL; the schema was already converged in the target by migration 024 (AUTH-3). Owned by role authority_admin, which holds no CONNECT on any sibling database. The legacy stellaops_platform.issuer schema was DROPPED 2026-09-13 (021 PLT-4), exactly as 016 AUTH-10 handed it over measured: 0 rows in all five domain tables, the only content being 4 retired-migrator schema_migrations rows. | IssuerDirectory persistence (folded into StellaOps.Authority) | control plane | stellaops_authority — DONE | control | done | — |
vexlensstellaops_platform 2026-09-13 (021 PLT-4 destructive window): 7 tables, every domain table 0 rows, only 4 retired-migrator schema_migrations rows. The module was deleted at VULN-B1 and src/ contains zero vexlens. SQL | VexLens persistence (retired) | read-model | — | control | 3 | low | |
src/__Obsoleted/AirGap/StellaOps.AirGap.Controller/ | airgapstellaops* database scanned for nspname='airgap' returned zero, and there is no stellaops_airgap database. It was never converged into a family database, so this row was allocating an owner to a schema with no rows and no home. Sealed-posture state lives in Platform’s ENVIRONMENT-scoped custodian platform.environment_state (present, 0 rows at the same measurement — no posture is declared on this estate) | — (was AirGap persistence) | control plane | none — posture is Platform’s environment_state; there is no AirGap-family database | control | n/a | — |
| registry-web + registry-token (the Registry family, ADR-041 OD-2) | registry (registry-web: repositories/manifests/blobs/tags + upload sessions), registry_token (registry-token: plan_rules/plan_audit) | StellaOps.Registry.Persistence (schema registry); registry-token migrates registry_token in-service (RegistryTokenPersistenceExtensions.cs:37) | control plane | stellaops_registry — DONE, both roles share the family database; REVOKE CONNECT applied, sibling roles refused | control | done | — |
| unknowns-web | unknowns + unknowns_app — MOVED 2026-08-22: live on stellaops_unknowns(role unknowns, REVOKE CONNECT proven 2026-08-23; RLS enabled AND FORCEd). There is no legacy schema to drop: unknowns/unknowns_app never existed in stellaops_platform (re-confirmed 2026-08-23) and the RC1 acceptance cluster that once held a converged empty copy is gone — no container, no volume | Unknowns.Persistence | data plane | stellaops_unknowns — DONE | control | 3 | low |
| workflow host (source retained/dormant; owner ruling 2026-08-19 keeps it for future use) | workflow (10-table central-migration residue; fresh platform databases no longer converge it) | Workflow.DataStore.PostgreSQL | dormant control plane | Workflow-family database + role on activation (P4); no move now | control | 3 | low |
| bench/tools (non-runtime) | groundtruth, public; retired golden_sets reference data | — | fixtures/bench | D-BIN3-6: golden sets never enter hub runtime migrations; predecessor sink seeds are preserved in committed source, no authored data to move | — | 3 | low |
Notes:
- “One microservice, several replicas” (scanner-web/worker, excititor-web/worker, export web/worker, evidence-locker web/worker, riskengine web/worker, packsregistry web/worker, advisory-ai web/worker) share one owner database by design. Concelier vs. Excititor vs. VexHub are independent deployables and get independent databases — this matrix does not reinterpret database-per-service as schema-per-service.
- The
vulnschema today mixes two ownership categories (Concelier canonical advisory source vs. rebuildable serving projection); in the target both live inside the single hub databasestellaops_vulnas internal schemas (ingest/factsvsconsensus/dist) per the v2 design — the rev-1 two-database split (stellaops_concelier+stellaops_vuln) was superseded by D2.
3. Cross-service SQL inventory (violations to burn down)
X1–X17 were re-verified by EST-2 on 2026-09-10 (Europe/Sofia): nine closures, one bar-dependent result and seven remaining source entries. The report carries current anchors, owning-row states, commit/test links and executed checks. It closes the verification task, not the estate-wide empty-register gate.
| # | Consumer → target | Kind | Evidence | Disposition (target, ADR-039 rev-2) |
|---|---|---|---|---|
| X1 | Excititor retention sweep → former vuln.* / vexhub.* targets | CLOSED: component retired with the plane, 2026-09-14 (SPRINT_20260914_001 VRP-4 chose the broader bar — component retirement — and it is met: VexRetentionSweepService and its options/metrics/tests have lived under src/__Obsoleted/Concelier/StellaOps.Excititor.Worker/Scheduling/ since VULN-B1 d80e7ce610 (2026-09-11, frozen per CoC 15.4); no live host composes it) | Prior: BAR-DEPENDENT — VexRetentionSweepService retains only owned sweep targets; default disabled; VexRetentionSweepAuthorityTests 2/2 at the 2026-09-10 audit | Foreign-target authority is gone and pinned (1526a7f169); the sweep class itself remains. EST-2 records both interpretations rather than choosing the broader component-retirement bar. OWNER RE-TAGGED 2026-09-14 → SPRINT_20260914_001 (VRP-4): the sprint this row named is ARCHIVED, so the entry had no owner. A register tag is only an owner while its sprint is ACTIVE. |
| X2 | Excititor → vexhub.sources/statements | CLOSED: writer retired with the plane, 2026-09-14 (SPRINT_20260914_001 VRP-1: PostgresVexHubProjectionSink.cs has lived under src/__Obsoleted/Concelier/__Libraries/StellaOps.Excititor.Persistence/Postgres/VexHub/ since VULN-B1 d80e7ce610 (2026-09-11, frozen per CoC 15.4); no live project compiles it and no host writes vexhub.*) | Prior: OPEN — PostgresVexHubProjectionSink.cs:166,336,366 still inserts into the old schema | The hub successor does not remove this source anchor. 003/008 retain the legacy-source/federation disposition; no closure or active-runtime-write claim is inferred. OWNER RE-TAGGED 2026-09-14 → SPRINT_20260914_001 (VRP-1): the sprint this row named is ARCHIVED, so the entry had no owner. A register tag is only an owner while its sprint is ACTIVE. |
| X3 | Policy → vuln.issue_gate_decisions / vuln.advisory_affected | CLOSED: reader deleted 2026-09-14 | PostgresFindingsLookup.cs reads only policy.vuln_gate_current; pinned by ReleaseComponentsLookupIntegrationTests.SourceContract_HasOwnerClientAndNoForeignSql (rejects FROM/JOIN on vuln.* and the three retired tokens) and FindingsAdvisorySourceOptionsTests (options expose only OnUnavailable; a leftover AdvisorySource/ShadowCompare key fails startup naming the key and the row) | SPRINT_20260914_001 VRP-2. Measured before deletion: the two tables existed in none of the 21 stellaops_* databases (dropped 2026-08-04, 008 G1), Policy connects only to stellaops_policy, the live host ran AdvisorySource=PolicyProjection, and selecting either retired source yielded 42P01 → deny. The six reads were dead rollback code with a dead target, so they were deleted rather than re-homed: LoadLegacyCvesForComponentsAsync, LoadGateDecisionCvesForComponentsAsync, RunAdvisoryQueryPassAsync, the parity classifier and the FindingsAdvisorySource enum are gone, and the compose / expectations / bundle keys with them. No DatabaseOwnershipConformanceTests or SharedCatalogReadConformanceTests pin ever named this entry (verified by grep 2026-09-14), so there is nothing to burn. |
| X4 | CLI compact export → vuln.issue_gate_decisions ⋈ vuln.issue_linksets | CLOSED: SQL branch retired 2026-09-14 (SPRINT_20260914_001 VRP-3, verify-and-close): PostgresCompactVulnDbCorpusSource.cs has lived under src/__Obsoleted/Cli/ since VULN-B1 5b4847c9a6 (2026-09-11); the live VulnDbCommandGroup.cs constructs only HubCompactVulnDbCorpusSource (:254) and names no Npgsql/connection string. Prior: OPEN — PostgresCompactVulnDbCorpusSource.cs:147-148; VulnDbCommandGroup.cs:289 still constructs it beside the Hub source | 008 VULN-G4 is DONE for signed Hub export/offline acceptance. X4 source retirement remains on the 003/008 and final PLT-4 register worklist; the completed API path is not an absence pin for SQL. OWNER RE-TAGGED 2026-09-14 → SPRINT_20260914_001 (VRP-3): the sprint this row named is ARCHIVED, so the entry had no owner. A register tag is only an owner while its sprint is ACTIVE. | |
| X5 | CLI mirror seed / rebuild → Concelier persistence | CLOSED: operator SQL retired and the guard exception burned 2026-09-14 (SPRINT_20260914_001 VRP-3, verify-and-close): the live MirrorSeedCommandGroup.cs is 112 lines with no SQL or connection string (mirror seeding moved to the owner’s API at VULN-B1 5b4847c9a6), and DatabaseOwnershipConformanceTests.CliGenericConnectionExceptions is an empty register pinned Assert.Empty (:1117,1146). Prior: OPEN — MirrorSeedCommandGroup.cs:472,547; the CLI generic-connection guard still carries this exact exception | 026 CM-4 records the owner-side mirror-seed/federation prerequisite. The passing exception register prevents growth; it does not prove closure. OWNER RE-TAGGED 2026-09-14 → SPRINT_20260914_001 (VRP-3): the sprint this row named is ARCHIVED, so the entry had no owner. A register tag is only an owner while its sprint is ACTIVE. | |
| X6 | Authority → Platform shared.tenants writer | CLOSED | Writer/dependency deleted in 1265d4d13f; AuthorityPlatformDatabaseIsolationConformanceTests 3/3 plus folded-host mutation tests 2/2 | Platform converges its own catalog over its own connection. AUTH-25 header reconciled to DONE against all eight already-ticked criteria and the executed AUTH-9d receipt; no new live PATCH was run by EST-2. |
| X7 | Authority → Platform IdP import | CLOSED | Importer/data-source removal 70fd9d4f7b; Authority own-connection and Platform-isolation conformance passed | 016 AUTH-4 DONE. The old generic/Platform connection cannot return through the guarded source/compose surfaces. |
| X8 | Policy → Scheduler persistence | CLOSED | PolicyOwnedGateEvaluationQueue (80756a1ef2); X8_PolicyCompilesNoJobEngineImplementation_AndTheRegisterAgrees passed | Queue state belongs to Policy; archived POL-F3 and the 012 X8 disposition retain the implementation evidence. No JobEngine/Scheduler implementation pair remains in Policy. |
| X9 | Historical writer-less release.* security projections | CLOSED: retired residue | 2026-09-10 read-only probe: all five tables absent in both Platform and RO databases; RO residue/baseline guards 3/3, including a present-with-rows control | RO-1 had already disproved the cited 574-row footprint for this estate. 865545d5da pins residue detection and excludes those tables from RO convergence. No new DROP or invented deletion commit. |
| X10 | Doctor central cross-schema migration checks / shared tenant catalog | CLOSED | 0daa7e086c deleted the five checks, schema enumerator, ledger seam and shared-tenant reader; check-ID retirement 5/5, per-schema integration retirement 1/1 | 009 DOC-4/DOC-5. Dedicated guards live under src/__Libraries/__Tests/StellaOps.Doctor*; the broader DOC-4b worker capability remains separate. |
| X11 | Findings.Security → Scanner SQL | CLOSED: source boundary | Old store frozen by 13b0a644d7; active-generation composition and compiled foreign-schema prohibition passed (2/2 dedicated checks) | Findings consumes owner facts locally. The SCN-7 seam pin b9a05a30df prevents returning Scanner SQL/DSN coupling; X18 activation/availability remains separate. |
| X23 | Integrations → Policy policy.gate_decisions | CLOSED: owner API (2026-09-14) | PostgresRegistryImagePolicyDecisionProvider deleted; HttpRegistryImagePolicyDecisionProvider calls POST /api/v1/policy/gate/decisions/by-digest. Absence pinned by CanonicalCompose_ReadsPolicyThroughItsOwnerApi_AndPinsScannerProjectionAuth and by the empty generic-connection register. | SPRINT_20260913_001 ORP-3. This entry is recorded CLOSED rather than omitted because the read was live and WRONG, not merely unlawful: it served stellaops_platform.policy, a copy frozen on 2026-08-15 (its vuln_gate_current sat at 10,503,548 rows for a month while Policy’s own database was at 11,144,755 and growing; the gate_decisions table the read actually hit holds 5 rows in the owner database), while Policy ran on stellaops_policy. Register ID corrected 2026-09-14 at landing: this row was filed as a second X21 while X21 (Findings.Security → sbom) already existed below; renumbered X23, the next free ID. The tenant half (a hand-rolled shared.tenants slug→UUID lookup, X14/ORF-2) was discharged by DELETION in the same change: Policy takes the tenant from the caller’s bearer and resolves it through its own replica, so the lookup did not move — it stopped existing. |
| X12 | Integrations → Scanner SQL | CLOSED: source boundary | ScannerProjectionService populates the local projection; ActiveConsumers_ReachScannerOnlyThroughOwnerSeams forbids Scanner SQL and DSN references | b9a05a30df, archived 024/017. Runtime transport/lag evidence is distinct from this source closure. |
| X13 | Policy → Scanner SQL | CLOSED: source boundary | IScannerDigestDetailClient is required and Scanner SQL/DSN references are forbidden by the same executed consumer-seam pin | b9a05a30df, archived 007/017. The separate retained vulnerability SQL is still X3, not silently closed here. |
| X14 | Legacy shared-catalog readers → shared.tenants / shared.actor_identity | PARTIAL (2026-09-12) | The library anchor this row named — PostgresStellaOpsTenantResolver in IStellaOpsTenantResolver.cs — is GONE, deleted by 027 SCR-3 after measuring ZERO call sites, with SharedCatalogReadConformanceTests preventing its return. KnownDirectReaders is 7 entries, down from 14 (Policy, ReleaseOrchestrator and IssuerDirectory legacy resolvers retired; Platform’s three re-classified to the producer register because 016 AUTH-25 makes Platform the owner-converger of shared.tenants). | 027 ARCHIVED 2026-09-12 with all five rows DONE; 016 AUTH-10 closed 2026-09-11. Residual readers have a live owner in SPRINT_20260912_001 (ORF-1/2/4) — two of them are live defects, since RO’s own shared.tenants converged EMPTY. One entry is PERMANENT: an applied migration (ADR-004) that can never expire, so the register’s floor is one, not zero. |
| X15 | Platform central migration mechanism | CLOSED: mechanism DELETED 2026-09-14 | IMigrationModulePlugin, MigrationModulePluginDiscovery, MigrationModulePlugins.cs, ReleaseMigrationRunner, the static registry and the CLI stella system migrations-* surface are deleted by 8c2ea1505c. Pinned by CentralMigratorMechanism_IsDeleted, CentralMigrator_HasNoCliFace and NoManualCategoryMigrationsTests. | 021 PLT-4 DC-26 closed the source mechanism. Per-service startup convergence and ADR-004 forward-only migrations remain required. Live schema retirement is separate from this source census. |
| X16 | RO Plugin Registry library instance | CLOSED: per-host homing | Parameterized schema repair cd25df804c; PluginRegistryPerHostSchemaTests 4/4 plus RO own-connection conformance | 019 SGN-2 and 018 RO-2/RO-5 DONE. A local platform schema in RO’s own database is a P4 library instance, not a connection to Platform. Platform’s remaining local migration plugin is X15. |
| X17 | Legacy Eventing producers/store → timeline.events/outbox | CLOSED: owner named, cross-service write severed 2026-09-14 (0e26c27d37, TLC-1 owner half: timeline-web self-migrates timeline.events + hlc_state via 003_v1_timeline_eventing_adoption.sql, its Eventing connection is derived from the owner connection and fails closed on any other database; findings-web appends to its own P6 eventing.outbox on findings.dispositions.tenant.<hex>; timeline.outbox never existed live and its code is deleted). The Timeline CONSUMER of that stream is SPRINT_20260911_001 TLC-1’s feature work, not a register entry — delivered 2026-09-15: StellaOps.Timeline.DispositionProjection folds findings.dispositions into timeline.events through IInboxConsumer + a fenced lease, with its checkpoint in the eventing schema of stellaops_timeline; off by default behind FindingDispositionProjection:Enabled, and proved end to end on the reference estate 2026-09-15 (outbox seq 2 -> inbox -> timeline.events -> checkpoint -> the public HLC route; docs/implplan/_evidence/20260915-tlc1-consumer-window/). The row STAYS CLOSED: it closed on the owner ruling and the severed cross-service write, and the consumer adds a reader, not a second lineage. Prior: OPEN — EventingDbContext still defaults to timeline and maps both tables; EventingMigrationModulePlugin remains | 003 VULN-B4 delivered the reliable per-host mechanism (f8039fe1d7); Timeline’s consumer exit moved out of 020 W3-01 on 2026-09-11 (that row closed on the database move) into SPRINT_20260911_001 TLC-1, BLOCKED on the owner’s bounded consumer contract. Mechanism delivery does not close every producer/store exit. | |
| X18 | vuln.* (heavy) | CLOSED (the cited joins are gone; what remains is one stale comment, now fixed) | SecurityFindingProjectionStore.cs and its foreign-schema tests were frozen to src/__Obsoleted/Findings/ at FND-26 (2026-08-29); AggregatedRiskStatusService.cs named vuln.* only in a COMMENT, corrected 2026-09-16 with its duplicate in AggregatedRiskStatusModels.cs. findings-web composes AddActiveGenerationSecurityReadModel() (StellaOps.Findings.WebService/Program.cs:255-258); the live pin is FindingsSecurityPersistenceForeignSchemaTests (no tolerated foreign schema at all) plus ScannerConsolidationConformanceTests’ X18-7 composition assertions. | SPRINT_20260914_001 VRP-6, verify-and-close. The prior body text was STALE, not merely unfinished: it said “FND-X18-5…7 remain, so current routes keep the typed 503”, but all three rows are Status: DONE in docs-archive/implplan/SPRINT_20260722_010_Findings_service_consolidation_program.md (:1071, :1094, :1129) and the runbook recorded the plane serving a live generation on 2026-08-27. The typed 503 is the COLD-START contract now, not the served state. Measured live 2026-09-16: findings_security.advisory_matcher_projection 40,253,283 rows, advisory_metadata_projection 1,365,172, advisory_exploit_projection 750,252, advisory_match_projection 3,700, advisory_corpus_section_import 9 over 3 generations — the consumer, import, matching and read cutover are all present and populated. Residual is ARMING, not this register’s: the shipped compose defaults for FINDINGS_ADVISORY_SBOM_PROJECTION_ENABLED / FINDINGS_SCANNER_SECURITY_PROJECTION_ENABLED are false while this estate runs them true, which is recorded in that sprint’s Decisions & Risks for a Findings owner. The FND-X18-7 STOP box is not open — an earlier draft of this row said it was, quoting the archived 010 rather than the live runbook; the warning was retired 2026-08-27 by running the full hold/rollback/restore/resume cycle, and the runbook’s own diagnostic on 2026-09-16 returns a single active generation with witness_current = t and no staging row. |
| X19 | vuln.sbom_canonical_match | CLOSED — was an API call, never a direct write (corrected 2026-08-09) | SbomLearnForwarder.cs is deleted with ConcelierLearnOptions, its DI, its HttpClient and its spec; SbomUploadService no longer forwards; every SbomService__Concelier__* key is gone from compose, the golden-path override and the regenerated bundle. Absence pinned by SbomServiceConsolidationConformanceTests.SbomService_ComposesNoConcelierLearnForwarder_AfterX19Retirement, which guards the code AND the config carriers (an unbound key binds silently). | SPRINT_20260914_001 VRP-7. The precondition this row itself set is MET AND LIVE. The row required “a closed contract and P6 sbom.versions catch-up with transactional sbom.uploaded/sbom.version.retired, epoch/head/horizon, remote-consumer retention, and bounded snapshot+head bootstrap” before the POST could go. All of it shipped under FND-X18-3 (StellaOps.SbomService.Contracts, PostgresSbomLedgerRepository outbox append inside the upload transaction, migration 008_ledger_version_stream_fence.sql). Measured live 2026-09-16: eventing.outbox holds 52 sbom.uploaded + 1 sbom.version.retired on stream sbom.versions, sbom.ledger_versions carries 51 of 54 rows with a version_stream_seq, and eventing.remote_stream_consumers shows findings-advisory-sbom-v1 at seq 53 reporting 2026-09-16T14:07:32Z. Meanwhile the dead forwarder was still ARMED (LearnOnUpload shipped :-true) against a host frozen on 2026-09-11, so every BYOS upload made a round trip that could only 404. The 2026-08-04 G1 window had already ruled /api/v1/learn/sbom dies with the Concelier host (docs/implplan/_evidence/2026-08-04-sprint-008-vuln-g1-window.md:76). Scanner.Worker’s identical forwarder was retired in the same change (VRP-8). |
| X21 | sbom.ledger_versions | read (raw SQL LEFT JOIN LATERAL) — removed | was SecurityFindingProjectionStore.cs, resolving artifact_ref inside already-dead vuln.*/scanner.* statements | Both joins remain deleted. FND-26 (2026-08-29) then froze the whole predecessor store to src/__Obsoleted/, and its SecurityFindingProjectionStoreForeignSchemaTests moved with it; the live pin is now FindingsSecurityPersistenceForeignSchemaTests, which asserts the SAME invariant over the live StellaOps.Findings.Security.Persistence assembly with no tolerated foreign schema at all. X21 does not reopen. Owner item 21(b)'s build-ready X18 design now supplies the deferred artifact identity through SbomService’s transactional version event + exact-version document seam into tenant-keyed Findings projections. FND-X18-3 and FND-X18-6 have since landed and the stream is live (see X19), so that identity now arrives through the owner seam. It was never permission for an HTTP lookup in a read/gate or a restored database join, and still is not. |
| X20 | Scanner.Worker → ReachGraph | CLOSED: publisher retired (2026-09-14) | HttpReachGraphPublisher, IReachGraphPublisher, ScannerWorkerOptions.Reachability.PublishToReachGraph / ReachGraphBaseUrl, the ReachGraphDigest analysis key and the Worker’s StellaOps.ReachGraph.Contracts reference are deleted; ReachabilityPublishStageExecutor keeps only the release-evidence publisher. Absence pinned by ScannerWorker_CompilesNoReachGraphProject_AfterX20Retirement. | SPRINT_20260914_001 VRP-5. Measured 2026-09-14 before retiring: the live scanner worker carried no Scanner__Worker__Reachability__PublishToReachGraph (default false), no reachgraph container existed, and the target host was deleted at source on 2026-09-08 (023 GRA-10) — unarmed everywhere, target gone. The reachgraph.stella-ops.local alias and the api://reachgraph Authority audience are NOT removed here (Platform URL pointer / S001 baseline grant); they are residue, not a writer. |
| X22 | release.regions/environments/targets/agents | former raw SQL read — removed | PostgresTopologySourceReader.cs was deleted. Platform now reads only active UUIDs from its own shared.tenants; HttpReleaseOrchestratorTopologySourceReader calls RO GET /internal/v1/release-orchestrator/topology-snapshot with a signed tenant-bound two-minute release:read envelope over the zero-reference producer contract. No Router route or SQL fallback exists. | Registered owner read contract: schema v1; exact-tenant and cross-row partition checks; deterministic region/environment/target/agent ordering; Platform projection remains rebuildable. RO returns the union of built-in + extended agent capabilities and maps canonical Stale to wire offline. |
Approved cross-service SQL exception register (ADR-039 P5): the previously named X6, X8 and X9 transitional approvals have reached their closure conditions and are removed from this approval list by the 2026-09-10 verification. This does not approve any remaining source violation. The target SQL-exception register is empty; new raw-SQL approvals require an ADR-039 amendment. X22 is the intended replacement pattern: an explicit, producer-owned HTTP read contract into a consumer-owned rebuildable projection, not permission to connect to the owner database.
X10 REMOVED from the approved list 2026-08-17 (SPRINT_20260722_009 DOC-4/DOC-5 stage 4). It was approved “until D11 lands”; D11 has landed and the reach is gone, not merely dispositioned — the five cross-schema checks, the
information_schema.tablesenumerator and the eleven-schema expected set are all deleted (0daa7e086c). Its ROW above stays, annotated CLOSED, per the convention X7 and X21 use: the table is the inventory and keeps its history, this sentence is the standing APPROVAL and must not name a contract nobody holds.X7 also removed here, because its own row already reads “CLOSED 2026-08-17 — the cited anchor no longer exists” with the compose evidence beside it; leaving it in the approved list contradicted the row. Flagged rather than silently taken: X7 belongs to the Authority/D10 lane, so if that closure is premature the sentence is where to put it back.
4. Shared-fallback burn-down — COMPLETE (2026-09-14)
The generic STELLAOPS_POSTGRES_CONNECTION has no live consumer anywhere in the estate, and the transitional pin register that tracked its retreat is EMPTY. ADR-039 P3 is discharged.
The burn-down ran 21 service definitions → 11 → 1 → 0. The last two steps are worth separating, because they were different defects wearing the same variable:
SPRINT_20260722_021PLT-3 (2026-09-13) cleared the last services that used it as their OWN storage pointer, ending with Platform itself moving toSTELLAOPS_POSTGRES_PLATFORM_CONNECTION.SPRINT_20260913_001ORP-3 (2026-09-14) cleared the survivor that used it to read SOMEBODY ELSE’s data:integrations-webheldPolicyStorage__Postgres__ConnectionStringpointed atstellaops_platform.policy, a schema frozen on 2026-08-15 when Policy moved tostellaops_policy. There was no lawful repoint —provision-service-database.shrevokes CONNECT from PUBLIC and grants it only to the owner role — so the read moved to Policy’s owner API (POST /api/v1/policy/gate/decisions/by-digest,policy:read, tenant from the bearer).
Two guards hold the floor, and they are different in kind:
Compose_PostgresConnections_AreOwnVariableOrPinnedis the shrink-only ratchet. It would have accepted a NEW service consuming the variable with a NEW pin, reading that as transitional debt.Compose_GenericConnectionVariable_HasNoLiveConsumerAndNoPinis the absence pin added with the final deletion. It fails on any live consumer AND on any pin, so reopening the programme requires deleting a named test and saying why.
One residue, out of scope and owned elsewhere: docker-compose.local-export-worker-rollback.yml still carries ConnectionStrings__Default: ${STELLAOPS_POSTGRES_CONNECTION}. It is the recorded rollback posture of the LIVE stellaops-export-worker container (RAR-7), so it is deliberately a frozen historical value rather than a live pointer — but it is a real estate-wide grep hit and it belongs to the OfflineKit consolidation lane, not here.
5. Full rename map — every container: current → target
Legend: unchanged = same name/src path. (verify) = project name not yet confirmed against a code anchor — the owning program’s S0 task confirms it. D12 DAL renames (…Storage/…Database → StellaOps.<Service>.Persistence) land with each service’s own wave, never out-of-band. “shared DB” = stellaops_platform (name kept — owner correction 2026-07-22). The container/instance half of that naming plan is WITHDRAWN: the owner DECLINED it on 2026-09-13 (SPRINT_20260722_021 PLT-2, ABANDONED). The bare stellaops name is NOT applied at the container level — the Platform service container stays stellaops-platform, which is what container_name: ${STELLAOPS_ESTATE_PREFIX:-stellaops}-platform already renders, in the same shape as every other service. Grounds are recorded in PLT-2’s status; the load-bearing one is that nothing in the estate resolves Platform by container name (every consumer uses the platform.stella-ops.local alias), so the rename bought no behaviour and would have made Platform the sole exception to the prefix convention.
5.1 Vulnerabilities program (sprints 003–008)
| Current microservice | Src path | DB project(s) used | DB used | New microservice | New src path | New DB project | New DB |
|---|---|---|---|---|---|---|---|
| concelier (+ concelier-jobs-init), retired VULN-G6 | src/__Obsoleted/Concelier/StellaOps.Concelier.WebService | StellaOps.Concelier.Persistence (historical) | retired schemas (concelier, vuln) | StellaOps.Vulnerabilities (vulnerabilities-web/-worker) | src/Vulnerabilities/ | StellaOps.Vulnerabilities.Persistence | stellaops_vuln |
| excititor-web + excititor-worker | src/Concelier/StellaOps.Excititor.WebService / .Worker | StellaOps.Excititor.Persistence | shared DB (vex) | merged ↑ | src/Vulnerabilities/ | merged ↑ | stellaops_vuln |
| vexhub-web | src/VexHub/ | StellaOps.VexHub.Persistence | shared DB (vexhub) | merged ↑ (dist schema) | src/Vulnerabilities/ | merged ↑ | stellaops_vuln |
src/VexLens/ | — | vexlens) | folded into Vulnerabilities | src/Vulnerabilities/ | merged ↑ | stellaops_vuln | |
| — (EPSS data only) | src/Scanner/ EPSS connector tables | StellaOps.Scanner.Storage | shared DB (scanner.epss_*, ~2.05 GB) | data relocates to the hub (P14); Scanner keeps no EPSS tables | — | StellaOps.Vulnerabilities.Persistence | stellaops_vuln |
5.2 Family merge programs (sprints 010–016)
| Current microservice | Src path | DB project(s) used | DB used | New microservice | New src path | New DB project | New DB |
|---|---|---|---|---|---|---|---|
src/__Obsoleted/) | src/Findings/StellaOps.Findings.Ledger.WebService | StellaOps.Findings.Ledger (library w/ migrations, KEPT) | shared DB (findings) | StellaOps.Findings (findings-web/-worker) | src/Findings/StellaOps.Findings.WebService/.Worker | StellaOps.Findings.Persistence | stellaops_findings |
src/Findings/StellaOps.RiskEngine.WebService/.Worker | StellaOps.RiskEngine.Core + .Infrastructure (kept as libraries) | shared DB (riskengine) | merged ↑ (scoring = worker role) | ↑ | merged ↑ | stellaops_findings | |
src/__Obsoleted/) | src/Findings/StellaOps.Findings.Security.WebService | StellaOps.Findings.Security.Persistence (KEPT) | shared DB (findings_security) | merged ↑ | ↑ | merged ↑ | stellaops_findings |
src/Findings/StellaOps.Findings.VulnCorrelation.WebService | StellaOps.Findings.VulnCorrelation.Application/.Persistence (kept as libraries) | shared DB (analytics) | merged ↑ | ↑ | merged ↑ | stellaops_findings | |
src/Attestor/StellaOps.Attestor.WebServiceProgram.cs; src/Attestor/ survives only for the StellaOps.Signer.* libraries | Attestor.Persistence (ProofChainDbContext), kept as a library under src/Evidence/__Libraries/ | shared DB (attestor, proofchain) | StellaOps.Evidence (evidence-web/evidence-worker) — DONE 2026-09-05 | src/Evidence/ | StellaOps.Evidence.Persistence.Consolidated (name corrected 2026-08-10 — see note) | stellaops_evidence | |
| attestor-tileproxy | src/Attestor/StellaOps.Attestor.TileProxy | — | — | Evidence-family member by ownership since 2026-09-05 (EVD-8); compose key and container deliberately unchanged | ↑ | — | — |
| tsa | infra container from devops/docker/tsa/ (openssl RFC3161 — no .NET host) | — | tsa-data volume, carried by staying attached to this unchanged key as compose_tsa-data | Evidence-family infra member by ownership since 2026-09-05 (EVD-8); not re-keyed | devops/docker/tsa/ | — | — |
src/EvidenceLocker/StellaOps.EvidenceLockersrc/Evidence/__Libraries/ | migrations superseded by the consolidated baseline | shared DB (evidence_locker) | merged into StellaOps.Evidence (011) — DONE 2026-09-05 | src/Evidence/ | merged ↑ | stellaops_evidence | |
src/__Obsoleted/JobEngine/StellaOps.Scheduler.WebService (frozen; Worker.Host retires with JOB-10) | StellaOps.Scheduler.Persistence (migrations frozen; the DAL was absorbed byte-identically) | scheduler)stellaops_platform 2026-09-14 (021 PLT-4) | StellaOps.JobEngine (jobengine-web/-worker) — DONE 2026-09-12 | src/JobEngine/StellaOps.JobEngine.WebService/.Worker | StellaOps.JobEngine.Persistence | stellaops_jobengine | |
src/__Obsoleted/JobEngine/StellaOps.PacksRegistry/StellaOps.PacksRegistry.WebService (frozen; the .Worker project retires with JOB-10) | StellaOps.PacksRegistry.Persistence (migrations frozen; the DAL was absorbed byte-identically) | packs)stellaops_platform 2026-09-14 (021 PLT-4) | merged ↑ — DONE 2026-09-12 | ↑ | merged ↑ | stellaops_jobengine | |
src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/.Worker — the same projects, renamed keys | StellaOps.AdvisoryAI.Persistence (sole authority) | shared DB (advisoryai) | StellaOps.AdvisoryAI (advisoryai-web/-worker) — DONE 2026-09-04 | same module, consolidated hosts | StellaOps.AdvisoryAI.Persistence | stellaops_advisoryai | |
src/__Obsoleted/) | src/AdvisoryAI/StellaOps.OpsMemory.WebService__Libraries/StellaOps.OpsMemory.Application and is mapped by advisoryai-web | migrations superseded by the consolidated baseline | shared DB (opsmemory) | merged ↑ (module) — DONE 2026-09-04 | ↑ | merged ↑ | stellaops_advisoryai |
| binaryindex-web (retired source) | src/__Obsoleted/BinaryIndex/ | frozen predecessor persistence | retained predecessor schemas; DROP deferred | Hub-native binary facts and generation; standing retirement completed 2026-09-12 | src/Vulnerabilities/ | hub persistence | stellaops_vuln |
| symbols (retired source) | src/__Obsoleted/BinaryIndex/StellaOps.Symbols.Server/ | frozen predecessor persistence | retained predecessor schema; DROP deferred | Hub-owned global manifest/source/catalog metadata and CAS content | src/Vulnerabilities/ | hub persistence and configured CAS | stellaops_vuln |
| notify-web | src/Notify/StellaOps.Notify.WebService | StellaOps.Notify.Persistence repositories + .Consolidated migrations | stellaops_notify (live) | StellaOps.Notify (notify-web/-worker) | src/Notify/ | StellaOps.Notify.Persistence (kept) + .Consolidated migration authority | stellaops_notify |
src/__Obsoleted/) | src/Notifier/StellaOps.Notifier/StellaOps.Notifier.WebService + .Workersrc/Notify/__Libraries/StellaOps.Notify.Delivery and is composed by notify-worker | own schema live-empty; hosted a crypto CredentialStore instance | shared DB (notifier, empty) | merged ↑ (worker role) — DONE 2026-09-04, deployable retired on a constructed consumer.role=notify-worker forcing function | src/Notify/ | merged ↑ | stellaops_notify |
src/__Obsoleted/Authority/) | src/Authority/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.WebServicesrc/Authority/__Libraries/StellaOps.IssuerDirectory.Api and is composed by authority | IssuerDirectory.Infrastructure | issuer schema moved INTO stellaops_authority at AUTH-9’s fold window (2026-09-08, D-AUTH3-1) | merged ↑ (role of StellaOps.Authority, S0 decision on container shape) — DONE: deployable retired once the fold was live, the container stopped and the gateway routed the prefix to authority | src/Authority/ | StellaOps.Authority.Persistence | stellaops_authority |
| authority | src/Authority/StellaOps.Authority | StellaOps.Authority.Persistence | stellaops_authority (already own DB) | unchanged + D10 (tenants/IdP sole owner; zero platform-DB access) | unchanged | unchanged | stellaops_authority |
D-EVD3-14 — the Evidence DAL name (corrected 2026-08-10, EVD-3 Stage B). The attestor row above originally read
StellaOps.Evidence.Persistence. That assembly name is already taken bysrc/__Libraries/StellaOps.Evidence.Persistence, an unrelated shared library owning a differentevidenceschema (generic evidence records, EF Core, once migrated centrally by platform-web’sEvidenceMigrationModulePlugin— deleted with the plugin mechanism 2026-09-14, PLT-4) and compiled byPlatform.Database. Two projects cannot share an assembly name, so the merged DAL isStellaOps.Evidence.Persistence.Consolidated, following the Notify / Timeline / ReleaseOrchestrator / Integrations / CredentialStore precedent. The twoevidenceschemas never collide — one lives in the shared platform database, the other instellaops_evidence, where it holds the migration ledger pair plus the Evidence-owned Timestamp Assurance state from migrations 002/003. Applies to theevidence-locker-web + workerrow too, which points at the attestor row via “merged ↑”.
5.3 Same service, new database (Waves 2–3; D12 DAL rename where deviating)
Owning programs: Scanner → SPRINT_20260722_017; ReleaseOrchestrator → 018; Signer → 019; Platform → 021 (endgame); Graph+ReachGraph → 023; Integrations → 024; the Registry family (registry-web + registry-token) → SPRINT_20260803_004 (ADR-041 OD-2 removed registry-token from 024’s scope); EvidenceLocker → 011; doctor-web → retirement via 009; remediation → archived; ExportCenter + AirGap (+ Mirror.Creator) → 025 (OfflineKit, owner-approved); every other row → SPRINT_20260722_020 (batch).
| Current microservice | Src path | DB project(s) used | DB used | New microservice | New src path | New DB project | New DB |
|---|---|---|---|---|---|---|---|
| platform | src/Platform/ | StellaOps.Platform.Persistence — D12 rename LANDED 2026-08-18 (021 PLT-1); the old StellaOps.Platform.Database name is gone from src/ | shared DB (platform, shared) | unchanged | unchanged | StellaOps.Platform.Persistence (D12) | stellaops_platform(name kept — owner correction). Container name also kept: PLT-2’s stellaops-platform → stellaops rename was DECLINED by the owner 2026-09-13 and the row is ABANDONED |
| policy-engine | src/Policy/StellaOps.Policy.Engine | StellaOps.Policy.Persistence | shared DB (policy) | unchanged + gains vuln gate projection (D3, sprint 007); publishes POST /api/v1/policy/gate/decisions/by-digest (policy:read, tenant from the bearer) for Integrations since 2026-09-14 (X23) | unchanged | unchanged | stellaops_policy; the platform copy of policy was dropped 2026-09-14 except 11 pre-move tables (41 rows) awaiting Policy’s carry-or-discard ruling |
| scanner-web + scanner-worker (+ cache-init) | src/Scanner/ | StellaOps.Scanner.Persistence is the sole migration authority; retained StellaOps.Scanner.Storage adapters run without migrations pending SCN-6 | scanner, minus EPSS)stellaops_scanner, MOVED 2026-08-29; source scanner/signals objects retained offline for rollback and a separately confirmed SCN-6 drop | unchanged | unchanged | StellaOps.Scanner.Persistence (D12) | stellaops_scanner — DONE |
| release-orchestrator (+ agent-ca-init) | src/ReleaseOrchestrator/ | StellaOps.ReleaseOrchestrator.Persistence (+ Environment data source in WebApi) | release, release_orchestrator, release_orchestrator_agent, scripts)stellaops_release_orchestrator, MOVED 2026-08-22 (D-RO5-9: 55-table consolidated baseline + one ledger; table-scoped data-only copy, 2,953/2,953 rows across 54 source-present tables; doctor 11/11); legacy stellaops_platform schemas retained for rollback — old-schema drop approval recorded as nothing-to-approve, the drop set is empty on this estate | unchanged | unchanged | consolidated StellaOps.ReleaseOrchestrator.Persistence | stellaops_release_orchestrator — DONE |
| signer | src/Attestor/StellaOps.Signer | Signer.Persistence (consolidated baseline is the live migration authority since 2026-08-17) | signer, crypto)stellaops_signer, MOVED 2026-08-17 by SGN-5 (no crypto schema — D-SGN1-1) | unchanged — stays separate (key custody, D14) | unchanged | StellaOps.Signer.Persistence (D12) | stellaops_signer — DONE; old stellaops_platform.signer deliberately retained, drop approval DEFERRED to owner (019 SGN-6) |
| evidence-locker-web + worker | — row moved to §5.2 (Evidence merge, 011) — | ||||||
| timeline-web | src/Timeline/ | StellaOps.Timeline.Persistence.Consolidated — the live startup authority since the 2026-08-24 window; ledger order 000 → 001_v1_timeline_consolidated_baseline → 002 all applied | timeline)stellaops_timeline, live (M4 2026-08-10 onto a dedicated cluster, then PTC-4/Window B moved it to the shared server 2026-08-14 and fresh-converged it; the corpus was deliberately not carried, owner ruling C). Legacy stellaops_platform.timeline snapshotted and dropped 2026-08-24 under that ruling | unchanged; audit-event partitioning (review §3) DELIVERED — timeline.unified_audit_events is relkind='p' with monthly children 2026_06…2026_12 + DEFAULT and 452,007 rows (measured 2026-09-11); retention is partition maintenance, and the bridge’s rollback heap was dropped 2026-09-11 | unchanged | per D12 — live | stellaops_timeline — DONE (W3-01 closed 2026-09-11; doctor /doctor/checks 6/6 healthy) |
src/ExportCenter/StellaOps.ExportCenter/*src/OfflineKit/ (the predecessor tree is frozen at src/__Obsoleted/ExportCenter/) | StellaOps.OfflineKit.Persistence | export_center, 15 domain tables + a private migration ledger export_schema_version, verified OK-3 2026-08-04)stellaops_offlinekit, MOVED 2026-09-13 (025 OK-10; six rows copied with parity, tenants replica converged at sequence 9). Source stellaops_exportcenter retained as the rollback — its drop and the exportcenter role revoke are a separate destructive window, not taken | StellaOps.OfflineKit (owner-approved EVL-3, 025) | src/OfflineKit/ | StellaOps.OfflineKit.Persistence | stellaops_offlinekit — DONE | |
| replay-web | src/Replay/ | StellaOps.Replay.Persistence(D12 extraction DONE 2026-08-09, W3-04) | shared DB (replay) — repoint staged, window-gated | unchanged | unchanged | StellaOps.Replay.Persistence | stellaops_replay |
| integrations-web | src/Integrations/ | StellaOps.Integrations.Persistence (IntegrationDbContext + canonical embedded baseline) | integrations)stellaops_integrations, MOVED 2026-08-23; legacy shared schema retained for rollback and no destructive drop approved | StellaOps.Integrations (owner-approved, 024) — | src/Integrations/ | StellaOps.Integrations.Persistence — single authority; Platform plugin/reference and staged .Consolidated project retired 2026-08-24 | stellaops_integrations — DONE |
| signals | src/Signals/ | StellaOps.Signals.Persistence | signals)stellaops_signals, MOVED 2026-08-10 (re-converged on the shared server 2026-08-14 by PTC-3/Window A; zero rows on both sides throughout, so nothing was carried and nothing was lost); legacy stellaops_platform.signals retained for rollback and measured zero domain rows | unchanged | unchanged | unchanged | stellaops_signals — DONE; the pre-window connection keys were retired from the resolver at M5 (2026-08-23), old schema drop DEFERRED to owner |
| sbomservice | src/SbomService/ | StellaOps.SbomService.Persistence | sbom)stellaops_sbomservice, MOVED 2026-08-22; legacy stellaops_platform.sbom retained for rollback pending M5 | SBOM system of record (DC-34): absorbs vuln.sbom_registry + Scanner’s SBOM registry; blobs → object store; producers publish, consumers subscribe | unchanged | unchanged | stellaops_sbomservice — DONE |
| graph-api | src/Graph/ | StellaOps.Graph.Indexer.Persistence | shared DB (graph) | StellaOps.Graph (owner-approved merge, 023) | src/Graph/ | StellaOps.Graph.Persistence | stellaops_graph |
src/ReachGraph/ | ReachGraph.Persistence | shared DB (reachgraph) | merged into StellaOps.Graph (023; CAS role) | src/Graph/ | merged ↑ | stellaops_graph | |
| doctor-web (+ evidence-init) | src/Doctor/ | Doctor persistence (verify) | shared DB (doctor) | retired (owner-approved, 009 DOC-5) — module survives as plugin SDK + CLI; registry lives in Platform | — | — | — (schema dispositioned at retirement) |
| airgap-controller (+ airgap-time) | src/__Obsoleted/AirGap/ (frozen OK-5; libraries relocated OK-14) | StellaOps.AirGap.Persistence (one 32 kB state table) | shared DB (airgap) | split decided (owner EVL-3, 025): posture → Platform (seal/status/time-anchor; both deployables retire); Importer/Bundle/Sync + Mirror.Creator → OfflineKit | posture src/Platform/; exchange src/OfflineKit/ | Platform.Persistence / OfflineKit.Persistence | posture → platform DB; exchange → stellaops_offlinekit |
| registry-web + registry-token | src/Registry/ (StellaOps.Registry.WebService + StellaOps.Registry.TokenService) | StellaOps.Registry.Persistence (schema registry); token service in-service (RegistryTokenPersistenceExtensions.cs:37, schema registry_token) | registry_token)stellaops_registry, MOVED — both roles live there | src/Registry/ | StellaOps.Registry.Persistence + in-service token ledger | stellaops_registry — DONE | |
| unknowns-web | src/Unknowns/ | StellaOps.Unknowns.Persistence | unknowns + unknowns_app)stellaops_unknowns, MOVED 2026-08-22 by fresh convergence — there was never a copy in stellaops_platform to move, and the RC1 acceptance cluster that held one is gone | unchanged | unchanged | unchanged | stellaops_unknowns — DONE; no drop approval is owed — no legacy schema, database, role or volume exists to drop |
| remediation | src/__Obsoleted/Remediation/ (archived 2026-08-19, 020 W3-14; central-migrator plugin deleted, so fresh DBs no longer receive the schema) | — (no live migrator) | none | archived — executed (owner re-confirmed 2026-08-19; AdvisoryAI autofix is in-module; the four Console /security/remediation* routes retired in the same commit) | — | — | — |
| workflow host (source retained/dormant; owner ruling 2026-08-19 keeps it for future use) | src/Workflow/ | StellaOps.Workflow.DataStore.PostgreSQL | shared-DB workflow residue; no deployed host | future Workflow-family service (new P4 activation sprint) | src/Workflow/ | StellaOps.Workflow.DataStore.PostgreSQL | Workflow-family database on the shared PostgreSQL installation |
Evaluation families (VexLens fold, Reachability, Offline/exchange): SPRINT_20260722_022.
5.4 No database / infrastructure (unchanged unless noted)
| Container | Src path | DB | Change |
|---|---|---|---|
| router-gateway | src/Router/StellaOps.Gateway.WebService | — (Valkey) | route table updates only (sprints 006 + per-program S5) |
| stellaops (console) + frontdoor + console-builder | src/Web/StellaOps.Web | — | API base-map repoints only (S6 stages) |
stellaops-cli (stella) | src/Cli/ | — (was direct SQL) | X4/X5 die: corpus/mirror via hub APIs |
| agent-core | RO agent host (verify) | — (verify) | unchanged |
| airgap-time | AirGap module | — | unchanged |
| init jobs (scanner-cache-init, doctor-evidence-init, advisory-ai-data-init, release-orchestrator-agent-ca-init, concelier-jobs-init) | with their services | — | follow their owning service’s program (concelier-jobs-init → Vulnerabilities) |
| stellaops-postgres (postgres:18.1) | — | hosts control-plane DBs and every consolidated per-service DB — measured 2026-08-14 at the PTC-6 close-out: 7 stellaops_* databases (stellaops_platform 11 GB, stellaops_vuln 1398 MB, stellaops_authority 189 MB, stellaops_policy 39 MB, stellaops_signals 9790 kB, stellaops_timeline 9750 kB, stellaops_replay 7854 kB) + the postgres maintenance DB. CURRENCY NOTE — the 7-database census is a frozen 2026-08-14 measurement and is left as recorded. Re-measured 2026-08-23: the live count is 12 — stellaops_authority, stellaops_platform, stellaops_policy, stellaops_registry, stellaops_release_orchestrator, stellaops_replay, stellaops_sbomservice, stellaops_signals, stellaops_signer, stellaops_timeline, stellaops_unknowns, stellaops_vuln. (The 2026-08-18 note said 9; stellaops_release_orchestrator, stellaops_sbomservice and stellaops_unknowns have landed since.) Do not quote any of these three numbers — re-measure with SELECT datname FROM pg_database WHERE datname LIKE 'stellaops%', which is why the query is here | stays; the only PostgreSQL server on the installation. Whole-estate sizing handed over from the retired vulndata cluster at PTC-5 (shared_buffers=4096MB, effective_cache_size=16GB, maintenance_work_mem=512MB, read back from pg_settings). |
| — | stellaops_vuln | RETIRED 2026-08-14 (SPRINT_20260810_002 PTC-5): stellaops_vuln moved onto the shared server per the ADR-039 P1 topology clarification. Container is STOPPED, not removed — its volume compose_vulndata-postgres-data (67.36 GB) is the repoint rollback. | |
| — | stellaops_signals | RETIRED 2026-08-14 (PTC-3, Window A). STOPPED exit 0; volume compose_signals-postgres-data (67.47 MB) kept as the repoint rollback. | |
| — | stellaops_replay | RETIRED 2026-08-14 (PTC-3, Window A). STOPPED exit 0; volume compose_replay-postgres-data (65.4 MB) kept as the repoint rollback. | |
| — | stellaops_timeline | RETIRED 2026-08-14 (PTC-4, Window B). STOPPED exit 0; volume compose_timeline-postgres-data (264.1 MB) kept as the repoint rollback — it still holds the 105,895 audit events ruling C deliberately did not carry. | |
| valkey / rustfs (seaweedfs) / registry (zot) | — | — | unchanged |
Bench/tools data (groundtruth, public; analytics is VulnCorrelation) is non-runtime. The predecessor BinaryIndex host did migrate/serve golden_sets, but its approved retirement disposition is fixtures/bench (014 D-BIN3-6/A6), never a hub runtime schema. BIN-9 verified that the only predecessor golden-set rows are exact committed reference seeds, with no authored definitions, targets or audit records. The original schema remains pending separately authorized DROP; retaining it does not require creating a replacement runtime database.
5.5 Measured role boundary (census 2026-08-14, SPRINT_20260810_002 PTC-6)
The ownership boundary on a shared server is the per-database role plus REVOKE CONNECT, so it has to be demonstrated, not asserted. Re-measure with bash tools/scripts/deploy/postgres/probe-database-isolation.sh (read-only; SELECT 1 per cell).
Six login roles on stellaops-postgres: stellaops (superuser), policy, replay, signals, timeline, vuln. Six further *_admin roles (authority_admin, findings_ledger_admin, notify_admin, policy_admin, scheduler_admin, vex_admin) are NOLOGIN and carry no session.
A *_admin role is provisioned by the install path, never by a migration — a migration runs as the service’s own NOCREATEROLE role and cannot create one. The requirement, the from-scratch failure it prevents (42501 permission denied to create role) and the exact statement to run on an operator-managed cluster are stated once, in INSTALL_GUIDE.md - PostgreSQL roles the install path must create. Currency, 2026-09-14 (SPRINT_20260914_003 JEF-1): 05-service-admin-roles.sql provisions authority_admin (AUTH-26, 2026-09-09) and scheduler_admin (moved there when the platform scheduler block left 00-v1-baseline.sql; the JobEngine 001 baseline guards on that role). The existing 00-v1-baseline.sql still provisions findings_ledger_admin. The remaining Notify, Policy and VEX admin-role requirements and the installer verification command are recorded in the install guide above.
Isolation matrix: 25/25 PASS (5 service roles × 5 service databases) — each role reaches exactly its own database; all 20 cross-database attempts refused.
Currency (re-measured 2026-08-23, read-only). The census above is the frozen PTC-6 reading. The live login-role set is now eleven:
stellaops(superuser) pluspolicy,registry,release_orchestrator,replay,sbomservice,signals,signer,timeline,unknowns,vuln. The four wave-3 roles this sprint owns were re-probed against all twelvestellaops_*databases withhas_database_privilege(<role>, <db>, 'CONNECT'):timeline,signals,sbomserviceandunknownseach return true for exactly their own database and false for the other eleven — a 4 × 12 slice that is exactly diagonal, 44 refusals and 4 grants. Noteplatformandauthoritystill connect as the superuser and therefore sit outside this boundary by design, as PTC-6 recorded.
Two honesty caveats a reader must not gloss:
platformandauthorityare outside the role boundary. Both still connect as thestellaopssuperuser, which bypassesREVOKE CONNECTby design, so they are excluded from the matrix rather than passing it. Their own separation programs close this.- A passing
policycell proves the boundary exists, not that policy is behind it. Measured 2026-08-14: the livepolicy-enginecontainer’sPostgres__Policy__ConnectionStringnamesDatabase=stellaops_platform;Username=stellaops, where the real projection lives (policy.vuln_gate_current= 10.5M rows). POL-F6 has not cut over; that isSPRINT_20260722_007’s work, not a topology-window regression. Updated later the same day:stellaops_policyis no longer empty — a cutover attempt converged its schema (59 base tables + 7 views inpolicy, 7 ineventing), created the first policy pack and seeded 906,000 gate rows before rolling back. Those rows carry no consumer checkpoint, so they are a resumable partial seed rather than a live projection; nothing reads that database today. The blocker the attempt found, because it changes what the next window must do first: Policy resolves tenant UUIDs from Platform’sshared.tenants, so onstellaops_policya gate evaluation decides and attests correctly and then 500s writing decision history. The exit is SCR-3 (SPRINT_20260722_027) — flipCatalog:Replication:Tenantsso Policy resolves tenants from its own replica of Authority’s catalog. Sequence: SCR-3 first, verified on the shared database; then POL-F6.
Two-consumer trap when re-running the probe: it derives pairs from STELLAOPS_POSTGRES_<SVC>_CONNECTION lines in devops/compose/.env, but the vulnerability hub’s connection is assembled in docker-compose.vulnerabilities.yml and has no .env presence at all since PTC-6 dropped VULN_POSTGRES_HOST. A plain run therefore silently reports a 4×4 matrix and omits vuln. Feed it an env file built from .env plus the value read from the live container (live truth, not a hand-copied duplicate that can drift), and delete that file afterwards:
cp devops/compose/.env /tmp/probe.env
docker inspect stellaops-vulnerabilities-web --format '{{range .Config.Env}}{{println .}}{{end}}' \
| grep '^STELLAOPS_POSTGRES_VULN_CONNECTION=' >> /tmp/probe.env
bash tools/scripts/deploy/postgres/probe-database-isolation.sh --env-file /tmp/probe.env && rm /tmp/probe.env
6. Non-database owned state (P16 / DC-27 / DC-28)
Databases are not the only shared state: Valkey keyspaces/streams and object-store buckets/prefixes have named owners, a prefix convention, and P7 budgets. The FULL inventory is SPRINT_20260722_028’s deliverable — the rows below are the ones their owning task has already landed, so nothing waits on that sweep to be recorded.
6.1 Valkey keyspaces / streams
Live inventory 2026-08-02 (SPRINT_20260722_028 NPS-1: read-only SCAN + MEMORY USAGE against the lab stack — 882 keys, 19.3 MB total; re-verify against a production-shaped estate before treating counts as budgets). Convention: keys under {service}:, streams under the existing stream:{name} form; cross-service consumption of another owner’s stream is a DC-06/DC-07 envelope contract, never an ad-hoc key read.
| Prefix / stream | Owner | Purpose | Measured (lab) | Budget / retention |
|---|---|---|---|---|
router:requests:{service}, router:responses, router:cancels:{service}:{id} | Router/gateway (messaging transport) | per-service request queues, shared response queue, cancellation markers | ~404 keys | transport-transient; leases + timeouts already bound growth (Router__Messaging__*) |
notify:events:idemp:{producer}|{tenant}|{kind}|{id} | Notify | event-delivery idempotency dedup keys | 471 keys, ~184 B each, TTL ≈ 95 min observed | TTL-bounded by construction; budget = rate × TTL |
scanner:jobs, scanner:jobs:dead | Scanner | scan job queue + dead letter | 2 streams | queue depth is the operational alarm; DLQ needs an explicit drain runbook (flagged). Disposition (P16, 2026-09-14): unbudgeted by design — a queue is bounded by its producers, not by a byte cap; the DLQ drain runbook is Scanner’s (owner: Scanner family; carrier: SPRINT_20260722_028 NPS-3’s measurer work names it). No measurer exists for Valkey anywhere in the estate; that is recorded here rather than silently missing. |
scheduler:runner, scheduler:planner | JobEngine (Scheduler) | run/planner queues | 2 streams | as above. Disposition (P16, 2026-09-14): unbudgeted by design (bounded by producers); owner JobEngine; the 012 programme that used to carry it is archived, so the carrier is SPRINT_20260722_028 NPS-3 (Valkey measurer) — nothing else waits on it. |
stream:orchestrator:events | Scanner (scanner-web) — owner corrected 2026-09-15 | orchestrator event stream (stream:{name} convention; the stream: prefix is applied centrally by ValkeyEventStream.cs:53, so configured names are bare) | 39 entries, all dead | Disposition (P16, 2026-09-15, NPS-3 — supersedes the 2026-09-14 “owner JobEngine” disposition, which was wrong on every reading. JobEngine does not write this stream: GraphJobEventPublisher.cs:78 defaults to stella.events, and no stream:stella.events key exists in the live estate. The only LIVE configured producer is scanner-web — SCANNER_SCANNER__EVENTS__STREAM: "${SCANNER_EVENTS_STREAM:-orchestrator:events}" (docker-compose.stella-services.yml:1050, docker-compose.scanner.yml:295) with a real publisher registered at Scanner.WebService/Program.cs:638. Ownership follows the writer, so the owner is Scanner. Live probe 2026-09-15: the key is the estate’s ONLY stream:* key; XLEN 39; XINFO GROUPS returns zero consumer groups. All 39 entries are kind=CONSENSUS, spanning 2026-07-08…2026-07-11, written by VexLens through Eventing__StreamBridge__StreamName: "orchestrator:events" (docker-compose.cons-c1a-enable.override.yml:44). VexLens was retired 2026-08-04 (SPRINT_20260722_008 VULN-G1) and its tree deleted (SPRINT_20260722_003 VULN-B1), and that override no longer renders. So the content is residue from a deleted service, read by nobody, while scanner-web has emitted nothing here in over two months. Two consequences, both un-owned and neither a trimming policy: (a) the residue is §2.11 dead state — dropping it is a destructive action needing its own window, NOT a startup action; (b) the Findings consumers (ConsensusChangeIngestionOptions.cs:25,29 and two siblings) are configured against a producer that no longer exists — FND-21 ruled exactly this inert on 2026-08-28. Trimming policy + measurer: SPRINT_20260915_001 OSM-1/OSM-2. |
timeline.events | Timeline | the estate event stream (consumer group timeline-indexer, 12 consumers) | XLEN 0 at scan (indexer keeps it drained) | convention outlier flagged: dot-form name predates the {service}: rule; renaming is a coordinated producer+consumer change — record as accepted legacy or fold into a 012/Timeline move, do NOT rename ad hoc Disposition (P16, 2026-09-14): owner Timeline; the rename and the trimming policy ride SPRINT_20260911_001 TLC-1 (Timeline becomes a DC-29 stream consumer) — the same owner decision DC-26’s last half waits on; “fold into a 012/Timeline move” is retired wording, 012 is archived. |
Cross-service ad-hoc key reads found: none. Every prefix has exactly one writing owner; the only cross-service touch points are the router transport queues (that is their job) and consumer-group reads on the streams, which is the sanctioned contract. Consumer groups in compose: 40 distinct Router__Messaging__ConsumerGroup values, one per service — matching the convention.
6.3 Named volumes (round-22 scope: ALL persistent non-Postgres state)
Live docker volume ls sweep 2026-08-02 (lab; compose project prefix stripped; scratch/QA projects excluded). Rule: every named volume has an owner and a class; *-plugin-scratch volumes (11 found: advisoryai, authority, concelier, doctor, excititor, integrations, notify, policy-messaging, scanner, scheduler, +) are ephemeral by contract (tmpfs-mode plugin staging) and carry a standing infra-exempt note as a family.
Method correction 2026-09-15 (NPS-3) — read this before trusting the section’s completeness. The inventory above was built from a live sweep of one lab, so it could only ever cover what that lab happened to be running on 2026-08-02. A declaration-side sweep — every top-level
volumes:entry acrossdevops/compose/*.yml, diffed against this table — had never been run. Running it found fourteen shipped states with no row at all, including two custody surfaces (stellaops-cryptopro-keys,openbao-data), three volumes live in the estate right now (findings-advisory-corpus,vulnerabilities-corpus-publications,packsregistry-upload-data), and one object-store bucket missing from §6.2 (registry-content) whose own compose comment calls it P16-owned. Every overlay that was switched off in the lab — openbao, cryptopro, rekor-tiles, tile-proxy, consul — was invisible to the original method by construction. Those rows are added below and in §6.2.The consequence for anyone auditing P16:
SPRINT_20260722_021PLT-4 ticked its P16 audit criterion on “All 15 §6.1/§6.2 rows name an owner”. That counted rows in this table, not stores in the estate, so it could not have detected an absent row. A future audit must diff declarations against this table, not read the table back to itself. The mechanical form of that check belongs intools/scripts/validate/with a--self-test(AGENTS.md §2.13), and is carried bySPRINT_20260915_001OSM-9.
| Volume | Owner | Class / note |
|---|---|---|
postgres-data | Platform cluster (shared DB — shrinks as ADR-039 lands) | source-of-truth; the 008 window drops ~91 GB of vuln-plane data from it |
valkey-data | Valkey (transport/queues above) | transient-rebuildable (AOF persistence for restart continuity, not durability) |
rustfs-data | object store (see §6.2) | source-of-truth for evidence/exports; per-prefix budgets in §6.2 |
evidence-data, attestor-proofchain-cas, scanner-poe-cas-data | EvidenceLocker / Attestor / Scanner | content-addressed evidence — custody surfaces, append-only |
doctor-evidence | Doctor (retires with 009 DOC-5 → JobEngine run records) | migrates, then dies |
tsa-data | TSA infra container (011 family) | infra; key custody noted |
stellaops-vault-data | Vault (KEK backend) | custody surface — never infra-exempt |
sm-remote-keys (compose file docker-compose.sm-remote.yml) | SmRemote | custody surface (regional key material) |
console-dist | Console builder → gateway | rebuildable (refreshed on console image up) |
scanner-cache-data, scanner-surface-cache, agent-core-registry-cache | Scanner / agent-core | caches, rebuildable, size-budget candidates for NPS-3’s doctor check |
offlinekit-data | OfflineKit | custody surface — carrier CAS (cas/), plus transient quarantine/ and multipart-buffer/. BUDGETED since SPRINT_20260722_025 OK-CAP (028 NPS-3): offlinekit-web declares owned state volume:offlinekit-import-cas measuring cas/ ONLY, and OfflineKitImportStorage.PromoteAsync carries the fail-closed P7 pre-write pause. Budget key Doctor:State:volume:offlinekit-import-cas:BudgetBytes, shell-safe alias OFFLINEKIT_IMPORT_CAS_BUDGET_BYTES. A missing or unreadable tree reports offlinekit-import-cas-measurement-unavailable rather than zero. Live red/green PASSED 2026-09-15 (028 NPS-3 window): Info/healthy unbudgeted at 918,047 bytes (independent du agreeing), a control carrier admitted 202 taking it to 975,834, then under a 1-byte budget Critical/unhealthy and a real import refused HTTP 507 CAS_WRITE_PAUSEDwith bytes unchanged 975,834 -> 975,834; budget removed, the same refused carrier then admitted to exactly 1,033,621 bytes — the figure the refusal projected, proving the guard’s arithmetic and not merely its verdict. Still open: the budget has no VALUE — OFFLINEKIT_IMPORT_CAS_BUDGET_BYTES is an empty-default pass-through in every compose file and in the live container, so the P7 guard ships OFF (SPRINT_20260915_001 OSM-6); and measure -> compare -> write is not an atomic cross-replica reservation (the same bound Scanner’s row records). Procedure: docs/modules/offlinekit/operations/import-cas-capacity-guard.md. |
exportcenter-object-store-data | OfflineKit | export-run artifacts, audit bundles, NIS2/assurance bundles and risk bundles under /var/lib/stella/exportcenter. Mounted by BOTH offlinekit roles since OK-12 — the worker had no mount at all before that, so its artifacts landed in the container layer. No budget or measurer yet: the stella-exports/ and mirror/ prefixes in §6.2 name it, and NPS-3’s check has not been extended to it. Disposition (P16, 2026-09-14): owner OfflineKit; no budget or measurer yet — extend the OK-CAP IOwnedStateMeasurer to this volume under SPRINT_20260722_028 NPS-3; recorded rather than silently missing. |
concelier-jobs, advisory-ai-{outputs,plans,queue}, agent-core-deployments, agent-ca-data, art-var, stellaops-registry-data, stellaops-tester-seed, regauth-basic | owning service per name (Concelier / AdvisoryAI / agent-core / registry / tester) | working state; owners recorded, budgets with NPS-3 |
telemetry overlay (prometheus/tempo/loki/grafana in docker-compose.telemetry.yml) | Telemetry overlay | infra-exempt (observability retention governs), noted per round-22 |
stellaops-cryptopro-keys | CryptoPro provider overlay (docker-compose.crypto-provider.cryptopro.yml:162) | ADDED 2026-09-15 (NPS-3) — CUSTODY SURFACE, never infra-exempt. Mounted at /var/opt/cprocsp/keys under the comment “Optional: Mount key containers” (:86). This is regional crypto key material and belongs in the same class as stellaops-vault-data and sm-remote-keys; it was missing for the reason given in the section note below. No budget, no measurer; the custody question (who may read it, how it is rotated) matters more here than the byte count. |
openbao-data | OpenBao / Vault secret backend (docker-compose.openbao.yml:55, mounted :82) | ADDED 2026-09-15 (NPS-3) — CUSTODY SURFACE, never infra-exempt. The KEK/secret backend’s storage. In the documented LOCAL dev/CI path the server runs ephemeral -dev mode (in-memory, auto-unsealed) and this volume stays empty, but the real-server path (:41) persists sealed material here. Distinct from the already-listed stellaops-vault-data (docker-compose.integrations.yml:58) — the matrix carried one and not the other. |
findings-advisory-corpus | findings-web (docker-compose.findings.yml:361) | ADDED 2026-09-15 (NPS-3). Advisory-corpus cache root plus two sub-roots the same volume carries — Findings__ScannerSecurityProjection__CacheRoot (:337) and Findings__Nis2IncidentLedgerConsumer__CacheRoot (:351). Rebuildable cache; unbudgeted, no measurer. Live in the estate today (compose_findings-advisory-corpus). |
vulnerabilities-corpus-publications | vulnerabilities-web (docker-compose.vulnerabilities.yml:289) | ADDED 2026-09-15 (NPS-3). Corpus publication artifacts; written by the publisher role (:257) and mounted read-only by the consumer (:191). Relates to the vuln/corpus/<generation>/ row in §6.2 and inherits its last-N windowing, but is a separate physical state with no budget or measurer. Live in the estate today. |
packsregistry-upload-data | JobEngine (docker-compose.stella-services.yml:172; mounted read-only at docker-compose.jobengine.yml:174) | ADDED 2026-09-15 (NPS-3). Packs-registry upload staging under /var/lib/stellaops/packsregistry/uploads. An append-side growth surface with no retention statement and no measurer — the shape P16 exists to police. Live in the estate today. Note JobEngine already returns 507 on a per-request artifact byte cap (PacksRegistryComposition.cs:642,662), which is a request-size limit, not a state budget. |
rekor-tiles-data | rekor-v2 transparency log (docker-compose.stella-infra.yml:46, mounted :201) | ADDED 2026-09-15 (NPS-3). Append-only transparency-log tiles; grows monotonically by design, so “unbudgeted” needs an explicit retention/rotation statement rather than silence. No measurer. |
stellaops-customer-registry-data | integrations overlay fixture (docker-compose.integrations.yml:62, mounted :326) | ADDED 2026-09-15 (NPS-3), then classed out of P16 scope in the same pass. Present in the live estate (container stellaops-customer-registry) and absent from this table. Traced to a stock OCI registry at /var/lib/registry — a customer registry under test, the same family as the gitea/gitlab/nexus fixtures below, not state a Stella Ops service owns. Distinct from stellaops-registry-data (the estate’s own registry). The compliance golden-path overlay re-declares it under a per-estate prefix (${CIL2_ESTATE_PREFIX}-customer-registry-data), which is a copy of the same fixture, not a second owner. |
consul-data (docker-compose.integrations.yml), tile-cache + tuf-cache (docker-compose.tile-proxy.yml), corpus-data (docker-compose.corpus.yml), stella-cli-state (docker-compose.cli.yml), otel-data + promtail-data (docker-compose.telemetry-offline.yml) | owning overlay per name | ADDED 2026-09-15 (NPS-3) as a group: each is a declared, shipped overlay volume with no row. otel-data/promtail-data extend the telemetry family exemption above (which named only prometheus/tempo/loki/grafana); the rest are working state, unbudgeted, no measurer. |
Third-party systems under test — gitea-*, gitlab-*, jenkins-data, nexus-data, minio-data (console-audit + integrations overlays), bsim-data + ghidra-* (docker-compose.bsim.yml) | integration-fixture overlays, not Stella Ops services | Dispositioned 2026-09-15 (NPS-3) as a family: out of P16 scope — these are external systems the estate integrates with or analyses under test, not state a Stella Ops service owns. Recorded so the sweep’s silence about them is a decision rather than an omission. |
6.2 Object-store buckets / prefixes
Live inventory 2026-08-02 (SPRINT_20260722_028 NPS-2: lab filer lists ONE provisioned bucket at 33.6 MB total store; the rest are configured-but-unprovisioned env keys that materialize on first write). Rule: cross-service object access goes through the owner’s API or the digest contract (ADR-033/035) — never raw paths. Sweep result: zero cross-owner raw-path consumers — every bucket’s S3 access lives in its owning family’s tree.
| Prefix | Owner | Contents | Retention class | Budget / knob |
|---|---|---|---|---|
scanner-artifacts/ (LIVE) | Scanner | scan artifacts/CAS payloads | working set | Scanner family only. SCN-CAS source partial (017 / 028 D-NPS3-1, 2026-08-23): S3 now measures real paginated bucket/prefix bytes; scanner-web declares doctor state bucket:scanner-artifacts; both Scanner roles receive the fail-closed P7 pre-write pause. Budget key Doctor:State:bucket:scanner-artifacts:BudgetBytes. Live red/green PASSED 2026-08-23 (corrected 2026-09-15 — this row said “has not run”, which was already false when written): the clean scanner-web image reported Info/healthy unbudgeted at 142,432,843 bytes, a 1-byte budget produced Critical/unhealthy, a tenant-valid scanner:write upload was refused with scanner-cas-write-paused leaving bytes 142,432,843 -> 142,432,843, and removing the override restored Info/healthy (SPRINT_20260722_017 SCN-CAS; SPRINT_20260722_028 Execution Log). Still open: the native RustFS API has no evidenced list/size route and fails explicitly rather than returning zero; measure -> compare -> write is not an atomic cross-replica quota reservation; and the budget has no VALUE — SCANNER_CAS_BUDGET_BYTES is an empty-default pass-through in every compose file and in the live container, so the P7 guard is off (CapacityGuardedArtifactObjectStore.cs:42-46). Procedure: docs/modules/scanner/operations/cas-capacity-guard.md. |
stella-exports/{tenant}/{run-id}/ | OfflineKit (was ExportCenter until SPRINT_20260722_025 OK-10, 2026-09-13) | export-run outputs, tenant-prefixed, immutable retention per profile | windowed (retention scheduler purges expired runs) | profile retention config. Materialised as a VOLUME, not a bucket (exportcenter-object-store-data, §6.3): OK-12’s export-run executor writes run artifacts to the filesystem under Export:Runs:StorageRoot and records absolute paths in the distribution ledger. No owned-state budget yet — OK-CAP budgeted the IMPORT CAS, which is a different volume. Disposition (P16, 2026-09-14): windowed retention bounds it in time, not in bytes; owner OfflineKit; the byte budget is the OK-CAP measurer extended to this prefix (SPRINT_20260722_028 NPS-3 extension, recorded 2026-09-13 as un-owned by OK-10 — now owned by OfflineKit on this row). |
oci-cache/ | ReleaseOrchestrator — owner corrected 2026-09-15 | orchestrator-held L2 content cache (digest-keyed, content-addressed) behind /api/v1/release-orchestrator/deployments/{id}/registry/content/{kind}/{digest} | rebuildable cache | env …BUCKET:-oci-cache. Disposition (P16, 2026-09-15, NPS-3 — supersedes the 2026-09-14 “owner Scanner” disposition. oci-cache has zero occurrences anywhere in src/Scanner. The constant is ReleaseOrchestrator’s: ContentByDigestServiceCollectionExtensions.cs:34 (L2Prefix = "oci-cache"), and compose configures it on that service alone — docker-compose.release-orchestrator.yml:437, docker-compose.stella-services.yml:1993, whose comment reads “bucket segregated to oci-cache. The orchestrator ALONE holds this key.” Root cause of the mis-attribution, stated in source: RustFsObjectStoreOptions.cs:11-16 records that RO “used to reach into StellaOps.Scanner.Storage … against its own separate oci-cache bucket — a build-time dependency on Scanner’s implementation for data Scanner never owned”, severed by SPRINT_20260722_017 SCN-6. The NPS-2 inventory recorded the borrowed driver as ownership of the data. Rebuildable, so unbudgeted remains acceptable; a measurer belongs to ReleaseOrchestrator (a doctor adopter — ReleaseOrchestrator.WebApi/Program.cs:1758), not to an extension of Scanner’s IOwnedStateMeasurer. Carrier: SPRINT_20260915_001 (OSM-7 for the doctor adoption this bucket needs first). |
surface-cache/ | Scanner (surface) | surface analysis cache | rebuildable cache | naming spread flagged: surface-cache / stellaops-surface / surface-artifacts / surface-bucket all appear across code+tests — converge on ONE name before provisioning production (17 in-family references) Disposition (P16, 2026-09-14): rebuildable cache, unbudgeted acceptable; owner Scanner; the name convergence is Scanner’s before any production provisioning (recorded, no carrier row yet — Scanner files it). |
mirror/ | OfflineKit (was Mirror/ExportCenter until SPRINT_20260722_025 OK-10, 2026-09-13) | mirror bundle parts | per bundle contract | provisioned on first mirror export Disposition (P16, 2026-09-14): per-bundle contract, materialises on first export; owner OfflineKit; budget rides OfflineKit’s existing measurer (OK-CAP) once the prefix is provisioned — carrier SPRINT_20260722_028 NPS-3 extension. |
stellaops-rekor-dev | Attestor (dev rekor) | dev transparency-log state | dev-only | never in production estates |
evidence/timestamp-assurance/objects/<sha256> | StellaOps.Evidence | Exact RFC 3161 tokens/responses, certificate chains, signed TL/LOTL/OCSP/CRL objects and the authentication/policy/agreement material actually relied upon. The database keeps immutable digest/pointer metadata, CHECK-caps each text-rich row at 64 KiB aggregate logical metadata, and refuses keys outside this content-addressed prefix. The successor-token writer composed by TA-7 is the first and so far only writer, and it writes only under evidence/timestamp-assurance/tokens/<sha256>. | source-of-truth, retained for R; shared-object deletion requires reference count and active-hold count both zero | Writer budget (TA-7, SPRINT_20260819_001): at most one object per gated remediation execution, and executions are themselves capped by the durable per-action rate window (Evidence:TimestampAssurance:Remediation:MaxExecutionsPerWindow, default 4/hour/tenant). Each object is CHECK-refused above 256 KiB before the first byte is written, and the write is refused outright when the configured capacity headroom is unavailable — fail-closed, discharging the obligation this cell previously recorded as owed; database cardinality is deduplicated by (tenant_id, sha256) and restricted to actual result dependencies, never every global snapshot. |
vuln/symbols/<sha256> | vulnerabilities-web (hub) | Symbol blob CAS — the debuginfo/PDB payloads behind symbols.manifest. Folded in by DC-33 (SPRINT_20260722_014 BIN-3): the retiring symbols deployable stored these bytes in the database as symbols.blobs.content_bytes; P16 moves them here and the hub keeps only the catalog (symbols.blob_catalog: sha256, size, media type, object key). Content-addressed and tenant-free — a debug-id resolves to the same bytes for every estate. | windowed online (LRU working set; a missing blob re-fetches from its configured symbol source) / source-of-truth in air-gap, where the pinned pack set has no upstream to re-fetch from | Working-set cap driven by symbols.blob_catalog.last_read_at; per-estate size budget lands with NPS-3’s doctor check. Air-gap estates size the prefix to their pinned packs and must exclude it from eviction. |
vuln/corpus/<generation>/ | vulnerabilities-web (hub) | Sectioned corpus artifact per COMPLETED generation (DC-38): one generation manifest plus its content-addressed parts — matcher-rows, consensus-inputs, exploit-evidence, reachability-sinks. Generation identity = the manifest digest, carried in corpus.generation.completed (DC-06). | windowed — producer keeps a last-N working set; a generation leaving the window is deleted, never rewritten (content-addressed parts are immutable while retained) | Vulnerabilities:Corpus:WorkingSetGenerations (producer) and the consumer-side CorpusGenerationStore working set, default 2 (active + previous, so an A/B swap can roll back and replay against the previous generation). Per-host disk is capped by section subsetting: consumers fetch only the sections they declare (CLI/Integrations skip consensus-inputs; Policy takes all). |
registry-content (bucket, prefix registry/) | Registry (stella-registry) | OCI registry content storage — the blobs and manifests the estate’s own registry serves | source-of-truth | ADDED 2026-09-15 (NPS-3). This bucket was absent from §6.2 entirely, although its own compose comment declares it in-scope: “the Registry-OWNED bucket (OD-3, P16). No other service reads or writes this bucket. registry-content is not a prefix inside someone else’s bucket on purpose: P16 ownership is about the bucket, and sharing one would make the claim unverifiable” (docker-compose.registry.yml:148-152; also docker-compose.stella-services.yml:3227). No budget and no measurer, and unlike every other §6.2 owner, Registry is not a doctor adopter at all — it has no AddServiceDoctorChecks call — so wiring a measurer here means adopting doctor first. Carrier: SPRINT_20260915_001 (OSM-7 for the doctor adoption this bucket needs first). |
Explicitly NOT decided here (owner R3): long-term generation retention for verdict replay — i.e. whether a verdict pins its corpus generation in the evidence plane so it stays replayable after the working set has rotated. The last-N working set above is a matcher-host guarantee, not an evidence guarantee; CorpusGenerationStore.ReadRetained says so in its failure message rather than implying replay works forever. Recorded by SPRINT_20260722_005 VULN-D4 (2026-08-02).
