Microservice → database ownership matrix
Status: evidence inventory (current state verified 2026-07-22) + Accepted target (ADR-039 rev-4, owner-approved 2026-07-22). Sources: devops/compose/docker-compose.stella-services.yml, live stellaops-postgres catalog (read-only), AddStartupMigrations sweep over src/, docs/agent-playbooks/data-and-stores.md. src/ wins over this document; rows marked (verify) lack a confirmed code anchor.
1. Current physical reality (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).
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.
| Deployable(s) (compose) | Schemas written today | Migration assembly (AddStartupMigrations) | Category | Proposed database / role (stellaops_*) | Cluster | Wave | Risk |
|---|---|---|---|---|---|---|---|
| platform | platform, shared | Platform.Database | control plane | platform — DB name stays stellaops_platform(owner correction 2026-07-22; keeps shared.tenants while the resolver contract stands) | control | — (stays) | low |
| authority | authority (own DB already) | 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 (106 tables, 2.1 GB) | Scanner.Storage | data plane | scanner | control | 2 | medium |
| advisory-ai-web + advisory-ai-worker (+ data-init) | advisoryai | AdvisoryAI hosting | data plane | advisoryai | control | 2 | low |
| opsmemory-web | opsmemory | OpsMemory | control plane | opsmemory | control | 3 | low |
| timeline-web | timeline (+ timeline_app) | Timeline persistence | evidence/read-model | timeline | control | 2 | low |
| notify-web | notify (+ notify_app) | Notify.Persistence | control plane | notify | control | 2 | low |
| notifier-worker | notifier (verify: compose points notifier__storage__postgres at shared DB; live schema is empty) | — (verify) | control plane | notifier (or fold into notify if same service — decide in Wave 2) | control | 2 | low |
| 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) |
| scheduler-web | scheduler (+ scheduler_app) | Scheduler.Persistence (JobEngine) | control plane | scheduler | control | 2 | low |
| release-orchestrator (+ agent-ca-init) | release (83 tables), release_orchestrator, release_orchestrator_agent, scripts | ReleaseOrchestrator(+.Environment) | control plane | release_orchestrator (all four schemas) | control | 3 | medium (Platform/Policy seams) |
| agent-core | (verify — likely none / registry cache volumes) | — | control plane | — | — | 3 | low |
| attestor (+ tileproxy, tsa) | attestor, proofchain | Attestor.Persistence | evidence | attestor | control | 3 | low |
| signer | signer, crypto (CredentialStore shared lib) | Signer.KeyManagement + CredentialStore | control plane | signer (crypto schema instantiates per host DB per ADR-039 P4) | control | 3 | low |
| evidence-locker-web + worker | evidence_locker (+ _app) | EvidenceLocker persistence | evidence | evidence_locker | control | 3 | low |
| findings-ledger-web | findings | Findings ledger persistence | data plane | findings | control | 3 | low |
| riskengine-web + worker | riskengine | RiskEngine | data plane | riskengine | control | 3 | low |
| findings-security-web | findings_security | Findings.Security.Persistence | read-model | findings_security | control | 3 | low |
| findings-vulncorrelation | analytics | Findings.VulnCorrelation.Persistence | read-model | vulncorrelation | control | 3 | low |
| signals | signals | Signals.Persistence | data plane | signals | control | 3 | low |
| sbomservice | sbom | SbomService.Persistence | data plane | sbomservice | control | 3 | low |
| graph-api | graph | Graph.Indexer.Persistence | data plane | graph | control | 3 | low |
| reachgraph-web | reachgraph | ReachGraph.Persistence | data plane | reachgraph | control | 3 | low |
| binaryindex-web | binaries, binary_index, golden_sets(runtime — BinaryIndex.WebService/Program.cs:48 auto-migrates it via AddStartupMigrations; previously mislabeled bench, round-20 correction) | BinaryIndex persistence | data plane | binaryindex | control | 3 | low |
| symbols | symbols | Symbols runtime persistence | data plane | symbols | control | 3 | low |
| export-web + export-worker | export_center (+ _app) | ExportCenter persistence | control plane | export_center | control | 3 | low |
| replay-web | replay | Replay.WebService | evidence | replay | control | 3 | low |
| integrations-web | integrations | Integrations.Persistence | control plane | integrations | control | 3 | low |
| packsregistry-web + worker | packs | PacksRegistry.Persistence | control plane | packsregistry | control | 3 | low |
| doctor-web (+ evidence-init) | doctor | Doctor persistence | ops | doctor | control | 3 | low |
| issuer-directory | issuer | IssuerDirectory persistence | control plane | issuer_directory | control | 3 | low |
| vexlens-web | vexlens | VexLens persistence | read-model | vexlens | control | 3 | low |
| airgap-controller | airgap | AirGap persistence | control plane | airgap | control | 3 | low |
| registry-token | registry_token | Registry token persistence | control plane | registry_token | control | 3 | low |
| unknowns-web | unknowns (schema not yet present live) | Unknowns.Persistence | data plane | unknowns | control | 3 | low |
replay/tester, workflow host (verify — workflow schema, 10 tables, host unconfirmed) | workflow | Workflow.DataStore.PostgreSQL | control plane | with its host service | control | 3 | low |
| bench/tools (non-runtime) | groundtruth, public | — | fixtures/bench | out of runtime scope; must not block waves (golden_sets moved to the binaryindex-web row — it is runtime; round-20 correction) | — | 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)
| # | Consumer → target | Kind | Evidence | Disposition (target, ADR-039 rev-2) |
|---|---|---|---|---|
| X1 | Excititor worker → vuln.issue_observations/_evidence_refs/_linksets | DELETE (destructive, cross-owner) | src/Concelier/StellaOps.Excititor.Worker/Scheduling/VexRetentionSweepService.cs:111-130 | Freeze now (D6); disappears entirely with consolidation — v2 has no retention sweep at all |
| X2 | Excititor (web+worker) → vexhub.statements | write (cross-owner) | src/Concelier/__Libraries/StellaOps.Excititor.Persistence/Postgres/VexHub/PostgresVexHubProjectionSink.cs:461-487 | Becomes internal to StellaOps.Vulnerabilities (D2): a rebuildable dist projection driven by the hub outbox |
| X3 | Policy engine → vuln.issue_gate_decisions + vuln.advisory_affected(matcher rows via VulnMatch.Core/IAdvisoryRowSource) | read | src/Policy/StellaOps.Policy.Engine/Services/PostgresFindingsLookup.cs:1083,1135 (+ advisory_affected reads; src/__Libraries/StellaOps.VulnMatch.Core/IAdvisoryRowSource.cs) | Killed by D3 + DC-30 v2: gates → Policy-owned projection fed by hub events; matcher rows → the generation-stamped compact corpus artifact consumed via VulnDb.Compact (one artifact for every matcher host, online = offline); no cross-DB read remains (P15); no advisory-row tables outside the hub |
| X4 | CLI compact vuln-db export → vuln.issue_gate_decisions ⋈ vuln.issue_linksets | read (cross-DB JOIN) | src/Cli/StellaOps.Cli/Commands/VulnDb/PostgresCompactVulnDbCorpusSource.cs:126-164 | Killed by D3: CLI reads the hub’s global corpus-export API (the export is global data; reading tenant gates was a category error) |
| X5 | CLI mirror-seed / archive rebuild → concelier/vuln | write (ops tool as owner) | src/Cli/StellaOps.Cli/Commands/MirrorSeedCommandGroup.cs:472,547; src/Concelier/__Libraries/StellaOps.Concelier.Persistence/Postgres/MirrorSeed/* | Becomes the hub’s mirror import/export plugins (D2); the CLI drives the hub API, never SQL |
| X6 | Authority → shared.tenants (platform DB) | write | src/Authority/StellaOps.Authority/StellaOps.Authority/Tenants/SharedTenantsPropagation.cs:397 (falls back to STELLAOPS_POSTGRES_CONNECTION) | Killed by D10: Authority is sole tenancy owner in stellaops_authority; shared.tenants becomes an event-carried replica or is retired; Authority never touches the platform DB |
| X7 | Authority → platform DB (IdP provider import) | read | compose docker-compose.stella-services.yml:507 | Killed by D10: IdP configuration is Authority-owned (completes the SPRINT_20260718_006 direction) |
| X8 | Policy engine → scheduler schema | write via Scheduler.Persistence client | compose docker-compose.stella-services.yml:1751-1752 | Wave 2 decision: JobEngine API/queue, or the scheduler client persists into Policy’s own DB (P4) (verify current semantics first) |
| X9 | corrected (dig 2026-07-22): all projection code lives in StellaOps.Findings.Security.* (owns findings_security.security_finding_projection); the release.-schema copy (574 rows) has no code writer found — suspected legacy duplicate | — | grep sweep 2026-07-22; live row counts | Findings copy → owned by 010; release. copy → confirm + retire in SPRINT_20260722_018 RO-1 |
| X10 | Doctor DB checks → many schemas | read-only monitoring | src/__Libraries/StellaOps.Doctor.Plugins.Database/Checks/DatabaseCheckBase.cs:17 | Killed by D11/P11: doctor checks become plugins hosted inside each service; Doctor aggregates via APIs and holds no cross-DB connections. Doctor also reads shared.tenants directly (SharedTenantsDoctorTenantCatalog) — dies with the 009 retirement |
| X11 | Findings.Security → scanner.* | read (SQL joins) | src/Findings/__Libraries/StellaOps.Findings.Security.Persistence/Stores/SecurityFindingProjectionStore.cs (FROM scanner.) | Disposition in SPRINT_20260722_010 (owner API / event-carried projection) before Scanner’s grant revocation (017) |
| X12 | Integrations → scanner.* | read | src/Integrations/StellaOps.Integrations.WebService/PostgresRegistryImageSeverityCountProvider.cs | Disposition in SPRINT_20260722_024 before 017’s revocation |
| X13 | Policy → scanner.* | read | src/Policy/StellaOps.Policy.Engine/Services/PostgresFindingsLookup.cs | Disposition with the 007 lookup replacement (same file as X3) |
| X14 | Every tenant-resolving service → shared.tenants | read via shared library | src/Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/Tenancy/StellaOpsTenantResolver.cs (ADR-010); direct readers: IssuerDirectory, Doctor | The library mechanism is built once in SPRINT_20260722_027 (replica tables per host DB, dual-mode rollout; covers shared.actor_identity too); 016 lands the tenants producer |
| X15 | platform-web migrates ~23 foreign schemas (central migrator) | cross-service DDL/migrations | src/Platform/__Libraries/StellaOps.Platform.Database/MigrationModulePlugins.cs — 24 IMigrationModulePlugin entries (airgap, authority, binaries, evidence, evidence_locker, export_center, graph, integrations, issuer, notify, packs, policy, proofchain, reachgraph, release_orchestrator, remediation, sbom, scheduler, signer, timeline×2, unknowns, workflow, + platform’s own). Explains the 0-byte shell schemas and X9’s origin (archived mig061/Release/050_SecurityDispositionProjection.sql). Runtime faces: PlatformMigrationAdminService + /api/v1/admin/* (incl. seed-demo), CLI MigrationCommandService manual applier | DC-26: each program deletes its plugin in the same change as its DB move (recipe S3/M1); the mechanism + runtime faces retire early in SPRINT_20260722_026; 021 PLT-4 final sweep |
| X16 | ReleaseOrchestrator ⇄ platform-schema plugin-registry tables | read/write via shared library | src/Plugin/StellaOps.Plugin.Registry/PostgresPluginRegistry.cs; consumers: ReleaseOrchestrator.WebApi/Program.cs, ReleaseOrchestrator.Plugin/Registry/ConnectorRegistry.cs; migrated by Platform’s PluginRegistryMigrationModulePlugin | P4 pattern (019 SGN-2): Plugin.Registry instantiates its tables in the host’s own DB (RO’s registry → stellaops_release_orchestrator, in 018); Platform keeps its own instance; the central plugin entry dies with DC-26 |
| X17 | Eventing producers → timeline.outbox/timeline.events | write via shared library | src/__Libraries/StellaOps.Eventing (EventingDbContext.cs:35,69 — ToTable("events"/"outbox", schemaName)); hosts: VexLens, Findings-Ledger, Concelier (Feedser.Core), Timeline; schema centrally migrated (X15 EventingMigrationModulePlugin) | DC-29: P6 is delivered by evolving StellaOps.Eventing — per-host outbox tables (P4), DC-06 envelope, new inbox/lease/fencing; Timeline becomes a stream consumer via the existing ITimelineStreamBridge; shared-DB tables retire with the producers’ moves |
| X18 | Findings.Security → vuln.* (heavy) | read (SQL joins) | SecurityFindingProjectionStore.cs + AggregatedRiskStatusService.cs join vuln.sbom_canonical_match, vuln.sbom_registry, vuln.advisory_canonical, vuln.advisories, vuln.advisory_cvss, vuln.kev_flags(the exploited-flag reader) | Disposition (010 + DC-30): advisory/CVSS/exploited data → corpus artifact + hub events; sbom_registry + sbom_canonical_match are artifact-scoped match RESULTS, not global knowledge — they move to the Findings family’s ownership (control plane), never into the tenant-free hub |
| X19 | SbomService → vuln.sbom_canonical_match | write (BYOS upload “produces real vuln.sbom_canonical_match rows”, SbomUploadService.cs:179) | src/SbomService/StellaOps.SbomService/Services/SbomUploadService.cs | Dies with the X18 re-homing: SbomService publishes an sbom.uploaded event; match computation runs on the corpus artifact in the Findings family (010); Scanner’s historical involvement (Scanner.WebService/Program.cs:1279 comment “Scanner no longer…”) confirmed in 017 S0 |
| X20 | Scanner.Worker → ReachGraph | write (HTTP publisher) | Scanner.Worker/Processing/Reachability/Publishers/HttpReachGraphPublisher.cs posts ReachGraphMinimal to POST /v1/reachgraphs when ScannerWorkerOptions.PublishToReachGraph is true (base URL option, e.g. http://reachgraph:8080); stage: ReachabilityPublishStageExecutor | 023 merge preserves the POST /v1/reachgraphs contract + keeps the reachgraph network alias during transition; Scanner repoints its base-URL option at its next deploy (coordinate with 017); contract test pins the writer alongside the two pinned readers |
Approved read-contract register (ADR-039 P5): transitional only — X6/X7 until D10 lands, X10 until D11 lands, X8/X9 until their Wave-2 dispositions. The target register is empty. New entries require an ADR-039 amendment.
4. Shared-fallback burn-down
Code that resolves STELLAOPS_POSTGRES_CONNECTION outside Platform (must fail closed instead — ADR-039 P3): Authority SharedTenantsPropagation.cs:397; CLI MigrationCommandService.cs:408 and MirrorSeedCommandGroup.cs:547; every compose service env described in §1. The conformance test in ADR-039 P10 pins this list and fails on additions.
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 bare stellaops name applies at the container/instance level, aligned at endgame per SPRINT_20260722_021 PLT-2).
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) | src/Concelier/StellaOps.Concelier.WebService | StellaOps.Concelier.Persistence | shared DB (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 |
| vexlens-web | src/VexLens/ | VexLens persistence (verify) | shared DB (vexlens) | folds into Vulnerabilities (hub close-out evaluation) | 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 |
|---|---|---|---|---|---|---|---|
| findings-ledger-web | src/Findings/StellaOps.Findings.Ledger.WebService | StellaOps.Findings.Ledger (library w/ migrations) | shared DB (findings) | StellaOps.Findings (findings-web/-worker) | src/Findings/StellaOps.Findings.WebService/.Worker | StellaOps.Findings.Persistence | stellaops_findings |
| riskengine-web + riskengine-worker | src/Findings/StellaOps.RiskEngine.WebService/.Worker | StellaOps.RiskEngine.Core (store wired in WebService) | shared DB (riskengine) | merged ↑ (scoring = worker role) | ↑ | merged ↑ | stellaops_findings |
| findings-security-web | src/Findings/StellaOps.Findings.Security.WebService | StellaOps.Findings.Security.Persistence | shared DB (findings_security) | merged ↑ | ↑ | merged ↑ | stellaops_findings |
| findings-vulncorrelation | src/Findings/StellaOps.Findings.VulnCorrelation.WebService | StellaOps.Findings.VulnCorrelation.Persistence | shared DB (analytics) | merged ↑ | ↑ | merged ↑ | stellaops_findings |
| attestor | src/Attestor/StellaOps.Attestor.WebService | Attestor.Persistence (ProofChainDbContext) | shared DB (attestor, proofchain) | StellaOps.Evidence (owner-widened, 011) | src/Evidence/ | StellaOps.Evidence.Persistence | stellaops_evidence |
| attestor-tileproxy | src/Attestor/StellaOps.Attestor.TileProxy | — | — | role of StellaOps.Evidence | ↑ | — | — |
| tsa | infra container from devops/docker/tsa/ (openssl RFC3161 — no .NET host) | — | tsa-data volume | infra role of StellaOps.Evidence | devops/docker/tsa/ | — | — |
| evidence-locker-web + worker | src/EvidenceLocker/StellaOps.EvidenceLocker (one project; web+worker = role containers) | migrations embedded in StellaOps.EvidenceLocker(.Infrastructure) | shared DB (evidence_locker) | merged into StellaOps.Evidence (011) | src/Evidence/ | merged ↑ | stellaops_evidence |
| scheduler-web | src/JobEngine/StellaOps.Scheduler.WebService (+ Worker.Host) | StellaOps.Scheduler.Persistence | shared DB (scheduler) | StellaOps.JobEngine (jobengine-web/-worker) | src/JobEngine/StellaOps.JobEngine.WebService/.Worker | StellaOps.JobEngine.Persistence | stellaops_jobengine |
| packsregistry-web + packsregistry-worker | src/JobEngine/StellaOps.PacksRegistry | StellaOps.PacksRegistry.Persistence | shared DB (packs) | merged ↑ | ↑ | merged ↑ | stellaops_jobengine |
| advisory-ai-web + advisory-ai-worker | src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/.Worker | AdvisoryAI hosting migrations (verify) | shared DB (advisoryai) | StellaOps.AdvisoryAI (advisoryai-web/-worker) | same module, consolidated hosts | StellaOps.AdvisoryAI.Persistence | stellaops_advisoryai |
| opsmemory-web | src/AdvisoryAI/StellaOps.OpsMemory.WebService | StellaOps.OpsMemory | shared DB (opsmemory) | merged ↑ (module) | ↑ | merged ↑ | stellaops_advisoryai |
| binaryindex-web | src/BinaryIndex/StellaOps.BinaryIndex.WebService (+ .Worker) | StellaOps.BinaryIndex.Persistence | shared DB (binaries, binary_index) | dissolves into the hub (DC-33): connector family + binary fact class + artifact content + internal VEX deriver; deployable retires | src/Vulnerabilities/ | hub persistence | stellaops_vuln |
| symbols | src/BinaryIndex/StellaOps.Symbols.Server | symbols runtime persistence | shared DB (symbols) | hub-adjacent global CAS (catalog in hub; blobs → object store, P16); deployable retires | src/Vulnerabilities/ | hub persistence | stellaops_vuln |
| notify-web | src/Notify/StellaOps.Notify.WebService | StellaOps.Notify.Persistence | shared DB (notify) | StellaOps.Notify (notify-web/-worker) | src/Notify/ | StellaOps.Notify.Persistence (kept) | stellaops_notify |
| notifier-worker | src/Notifier/StellaOps.Notifier/StellaOps.Notifier.WebService + .Worker (only worker deployed) | own schema live-empty; hosts a crypto CredentialStore instance | shared DB (notifier, empty) | merged ↑ (worker role); src/Notifier/ deleted | src/Notify/ | merged ↑ | stellaops_notify |
| issuer-directory | src/Authority/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.WebService | IssuerDirectory.Infrastructure | shared DB (issuer) | role of StellaOps.Authority (S0 decision on container shape) | 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 |
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+registry-token → 024; 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.Database | shared DB (platform, shared) | unchanged | unchanged | StellaOps.Platform.Persistence (D12) | stellaops_platform(name kept — owner correction; container/instance naming aligned at PLT-2) |
| policy-engine | src/Policy/StellaOps.Policy.Engine | StellaOps.Policy.Persistence | shared DB (policy) | unchanged + gains vuln gate projection (D3, sprint 007) | unchanged | unchanged | stellaops_policy |
| scanner-web + scanner-worker (+ cache-init) | src/Scanner/ | StellaOps.Scanner.Storage | shared DB (scanner, minus EPSS) | unchanged | unchanged | StellaOps.Scanner.Persistence (D12) | stellaops_scanner |
| release-orchestrator (+ agent-ca-init) | src/ReleaseOrchestrator/ | StellaOps.ReleaseOrchestrator.Persistence (+ Environment data source in WebApi) | shared DB (release, release_orchestrator, release_orchestrator_agent, scripts) | unchanged | unchanged | consolidated StellaOps.ReleaseOrchestrator.Persistence | stellaops_release_orchestrator |
| signer | src/Attestor/StellaOps.Signer | Signer.KeyManagement + CredentialStore libs (crypto) | shared DB (signer, crypto) | unchanged — stays separate (key custody, D14) | unchanged | StellaOps.Signer.Persistence (D12; crypto schema instantiates per host, P4) | stellaops_signer |
| evidence-locker-web + worker | — row moved to §5.2 (Evidence merge, 011) — | ||||||
| timeline-web | src/Timeline/ | Timeline persistence (verify) | shared DB (timeline) | unchanged (+ audit-event partitioning, review §3) | unchanged | per D12 | stellaops_timeline |
| export-web + export-worker | src/ExportCenter/StellaOps.ExportCenter/* (WebService/Worker/Infrastructure + Cra/Nis2/StandardsMapping/RiskBundles adapters) | ExportCenter.Infrastructure | shared DB (export_center, 16 tables) | StellaOps.OfflineKit (owner-approved EVL-3, 025) | src/OfflineKit/ | StellaOps.OfflineKit.Persistence | stellaops_offlinekit |
| replay-web | src/Replay/ | in-WebService store (PostgresFeedSnapshotIndexStore) | shared DB (replay) | unchanged | unchanged | StellaOps.Replay.Persistence (D12: extract from WebService) | stellaops_replay |
| integrations-web | src/Integrations/ | Integrations.Persistence (IntegrationDbContext) | shared DB (integrations) | StellaOps.Integrations + registry-token role (owner-approved, 024) | src/Integrations/ | StellaOps.Integrations.Persistence | stellaops_integrations |
| signals | src/Signals/ | StellaOps.Signals.Persistence | shared DB (signals) | unchanged | unchanged | unchanged | stellaops_signals |
| sbomservice | src/SbomService/ | StellaOps.SbomService.Persistence | shared DB (sbom) | 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 |
| graph-api | src/Graph/ | StellaOps.Graph.Indexer.Persistence | shared DB (graph) | StellaOps.Graph (owner-approved merge, 023) | src/Graph/ | StellaOps.Graph.Persistence | stellaops_graph |
| reachgraph-web | 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/AirGap/ | 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-token | src/Registry/StellaOps.Registry.TokenService | in-service (RegistryTokenPersistenceExtensions.cs:52) | shared DB (registry_token) | role of StellaOps.Integrations (owner-approved, 024) | src/Integrations/ | merged | stellaops_integrations |
| unknowns-web | src/Unknowns/ | StellaOps.Unknowns.Persistence | shared DB (unknowns, not yet live) | unchanged | unchanged | unchanged | stellaops_unknowns |
| remediation | src/Remediation/ | RemediationDataSource assembly | none (dormant: no compose service/route/UI) | archived (owner-approved; AdvisoryAI autofix is in-module) | — | — | — |
| workflow host (verify which service hosts it) | src/Workflow/ | StellaOps.Workflow.DataStore.PostgreSQL | shared DB (workflow) | with its host service (P4) | unchanged | unchanged | host’s DB |
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 | stays; gains per-service DBs (platform DB name unchanged) |
| stellaops-postgres-vulndata | — | hosts stellaops_vuln | new (own volume, P8) |
| valkey / rustfs (seaweedfs) / registry (zot) | — | — | unchanged |
Bench/tools schemas (groundtruth, public; analytics is VulnCorrelation) are non-runtime; dispositioned in Wave 3 (must not block any program). golden_sets is not bench — binaryindex-web live-migrates and serves it (round-20 correction); its destination rides the 014 dissolution.
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) |
scheduler:runner, scheduler:planner | JobEngine (Scheduler) | run/planner queues | 2 streams | as above |
stream:orchestrator:events | JobEngine | orchestrator event stream (stream:{name} convention) | 39 entries | consumer-group trimming policy TBD with 012 |
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 |
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.
| 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 |
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 |
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 (8 source references, all in-family); size budget lands with NPS-3’s doctor check |
stella-exports/{tenant}/{run-id}/ | ExportCenter | export-run outputs, tenant-prefixed, immutable retention per profile | windowed (retention scheduler purges expired runs) | profile retention config |
oci-cache/ | Scanner (OCI layer cache) | pull-through layer cache | rebuildable cache | env …BUCKET:-oci-cache |
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) |
mirror/ | Mirror/ExportCenter | mirror bundle parts | per bundle contract | provisioned on first mirror export |
stellaops-rekor-dev | Attestor (dev rekor) | dev transparency-log state | dev-only | never in production estates |
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). |
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).
