ADR-039 — Database-per-service ownership and the consolidated Vulnerabilities service

Status: Accepted (revision 12, 2026-07-23 — owner rulings on the round-20 architecture review: D13 amended — KEV returns to Tier 0 (owner: “don’t accept loss; we seek to be the best one” — exploit evidence becomes a first-class hub signal family served via hub API/events/artifact; program SPRINT_20260723_003); DC-06/07 amended (stream_epoch in the event envelope — producer restore/rebuild no longer silently freezes consumers); DC-36 replayability = derive- on-demand over the hub’s own raw documents + revision history (owner: replay reproduces via the API; no archival of replayed artifacts); DC-37 estate restore doctrine (restore classes + epoch-driven convergence + restore drills); DC-38 one consensus-evaluator library fed by the sectioned corpus artifact + local overlays; DC-39 contract-version discipline at the artifact and plugin seams; P17 Platform charter (domain-agnostic, no domain nouns); P18 service-birth criteria (the consolidation criteria apply in reverse to any new deployable). Rev-11 record — DC-33: BinaryIndex’s knowledge plane dissolves into the hub (owner-initiated; no stellaops_binaryindex; Symbols = hub-adjacent CAS); DC-34: SbomService = the SBOM system of record (absorbs vuln.sbom_registry + Scanner’s SBOM registry; blobs → object store). Rev-10 record — owner reconfirmed the migration hard rule (P4 sharpened: every microservice carries and applies its own migrations; no central mechanism of any kind); adds DC-29 one-outbox-lineage (evolve StellaOps.Eventing, which already ships an outbox — currently homed in the shared DB’s timeline schema, register X17) . Rev-9 record — adds P16 non-PostgreSQL state ownership (Valkey keyspaces/streams + object-store buckets follow the same owner/budget rules — DC-27/DC-28) and extends DC-26 to the central migrator’s runtime faces (Platform’s migration admin API + /api/v1/admin/seed-demo, the CLI’s manual migration applier). Rev-8 record — owner approved the OfflineKit unification (EVL-3 → program 025: ExportCenter + Mirror.Creator + AirGap Importer/Bundle/Sync → StellaOps.OfflineKit / stellaops_offlinekit; sealed-mode posture + time anchor → Platform; airgap-controller/airgap-time deployables retire; replay stays separate). Rev-7 record — adds P15 decision-time independence and binds the design-challenges register (DC-01…DC-20) as binding design input for all programs. Rev-6 record — owner correction: the platform database keeps its name stellaops_platform; the bare stellaops name applies at the container/instance level (executed in SPRINT_20260722_021 PLT-2). Owner approved the evaluation outcomes: Evidence merge (011 widened to Attestor+TSA+tileproxy+EvidenceLocker → stellaops_evidence; Signer stays out), Graph+ReachGraph merge (023 → stellaops_graph; signals separate), registry→Integrations (024, availability-isolated role), doctor-web retirement (009 DOC-5), remediation archive (dormant, evidence-backed), export→hub rejected. AirGap: investigation confirmed a posture/exchange mixture — split proposal (posture→Platform; Importer+Mirror.Creator+ExportCenter → StellaOps.OfflineKit) pending owner confirmation in 022 EVL-3; see service-consolidation-review.md §2.3. Rev-4 record: owner review round 3 approved D1–D14 with instructions: D1 hub database stellaops_vuln; D2 rebuild-from-scratch default with agent-judged mechanical shortcuts; D3 owner/refactor-source named explicitly; D6 “do it the right way”; D8 100 GB available + old plane deleted before re-ingest; D11 registry-based doctor architecture; D14 one program per merge. The hard rule is codified in CODE_OF_CONDUCT.md §8.2 and AGENTS.md §2.7. Runtime/destructive actions still require their named execution windows per sprint.) Deciders: Owner (V. Moushkov, reviews of 2026-07-22) + Architecture Evidence: vulnerability-data-plane-separation-investigation.md, database-ownership-matrix.md, vulnerability-data-plane-v2-design.md

Context

For the third time in three months a vulnerability corpus has expanded inside the physical stellaops_platform database (May: 306 GB, June: 420 GB, July: 91 GB and growing). Live measurements on 2026-07-22:

Owner review (2026-07-22) resolved the direction: these are one domain and should be one service with one database, focused on being the raw + normalized vulnerability knowledge hub; per-tenant decision projections belong to the control plane; existing corpus data is disposable because connectors and mirror bundles can restore it from sources.

Decision (policy)

P1 — One physical database and role per independently deployable microservice

Every independently deployable microservice owns its own physical PostgreSQL database and a dedicated role. Web/worker replicas (and init jobs) of the same microservice share that owner database. Independent microservices never share domain tables. Schema-per-service inside a shared database is explicitly not compliant. Where two “services” cannot avoid sharing domain tables, that is the signal they are one service and must be consolidated (as applied to Vulnerabilities in D2).

This is a HARD RULE, codified in docs/code-of-conduct/CODE_OF_CONDUCT.md §8.2 and AGENTS.md §2.7 (2026-07-22). Root cause of the original miss: those documents themselves said a service “owns a PostgreSQL schema” — the contract legitimized schema-level ownership, so every agent complied with the letter while ~46 schemas shared one database. The boundary is the database; the wording is fixed at the source.

P2 — Naming and connection contract

P3 — No shared-connection fallback (fail closed)

A service whose owner connection is missing MUST fail to start with an explicit error. Falling back to STELLAOPS_POSTGRES_CONNECTION is forbidden. After all waves complete the generic variable is retired and Platform uses STELLAOPS_POSTGRES_PLATFORM_CONNECTION.

P4 — Migration ownership (HARD RULE — owner reconfirmed 2026-07-23)

Every microservice carries its own migrations — embedded in its own (or its hosted libraries’) assemblies, applied by its own startup, against its own database, and nowhere else. No central mechanism of any kind is compliant: not a plugin file (X15), not an admin API, not a CLI applier — all three exist today and all three retire (SPRINT_20260722_026 + DC-26). Platform never migrates another service’s schema. Shared persistence libraries (Cryptography.CredentialStorecrypto, Verdict, Plugin.Registry, Eventing, Artifact.Infrastructure, scheduler client) ship their migrations inside the library, and the hosting service embeds and applies them into its own database — that is what “carries its own migrations” means for shared code.

P5 — Cross-service data access

Allowed mechanisms, in order of preference: (1) the owning service’s API; (2) transactional outbox → ordered events → idempotent inbox + durable checkpoint, applied by a single leased writer with a fencing token per stream; (3) a time-boxed entry on the approved read-contract register (matrix §3). Anything else — and any cross-service write or delete — is a defect. Cross-service SQL JOINs die by construction once databases separate.

P6 — Outbox/inbox/checkpoint/lease standard

One shared implementation (extending StellaOps.Infrastructure.Postgres) provides: outbox appended in the same source transaction as the fact write; inbox/checkpoint advanced in the same destination transaction as the applied change; renewable lease with fencing token; content-guarded (logically and physically idempotent) upserts; source generation + completion markers so absence is only tombstoned after a completed generation. Age-only retention of currently applicable facts is forbidden.

P7 — Capacity budgets and metrics on every write path

Every data plane declares per-database budgets (bytes, rows, WAL/batch) enforced by a shared capacity contract governing all of its write paths — not a guard on one writer. Alarms and writer-pause trip before the safety floor with an operator-visible reason.

P8 — Cluster topology

InstanceVolumeDatabases
stellaops-postgres (existing)existing volumestellaops_platform (Platform control plane — name kept), stellaops_authority, and all control-plane service databases as waves land
stellaops-postgres-vulndata (new)new dedicated volume (100 GB available per owner), preferably a separate physical diskstellaops_vuln (single hub database)

The harm vector in all three incidents was volume exhaustion; a dedicated cluster+volume is the only boundary in a Docker estate that caps blast radius. Tablespaces inside one containerized instance are rejected.

P9 — Backup/restore boundaries

Backups are per-database. The vulnerability hub’s primary recovery story is re-ingestion from sources and mirror bundles (D7); control-plane databases keep conventional backup/restore. Rollback remains forward-only per ADR-004.

P10 — Enforcement

Architecture conformance tests (src/__Tests/architecture/…): (a) every WebService resolves only its own connection key; (b) no non-Platform source references STELLAOPS_POSTGRES_CONNECTION beyond the pinned register; © compose lint maps each service to its own database; (d) persistence projects follow the P2 naming standard. Role grants make cross-owner writes fail in integration tests.

P11 — Doctor is a plugin model with a platform capability registry

Health/diagnostic checks execute inside the service they check, via a doctor plugin hosted by that service (using the existing plugin SDK), surfaced over the service’s own API. Owner-directed architecture (D11, three phases): (1) doctor base library + plugin contract first; (2) a registration surface on Platform where every microservice with one or more doctor functions registers them (capability catalog: service, check id, category, endpoint), so discovery is dynamic, not config; (3) current consumers of the central Doctor (Console pages, ops runbooks, doctor CLI) are rerouted to the per-service endpoints via that registry. The Doctor service aggregates via registry + APIs only — no cross-database connections, no knowledge of foreign schemas; the Doctor.Plugins.Database probe-everything pattern is retired (matrix X10). Program: SPRINT_20260722_009.

P13 — Anti-bloat data-modeling rules (all new schemas, estate-wide)

Every table declares a retention class (source-of-truth / derived-rebuildable / append-audit partitioned with a window / cache-bounded); no second full representation of a semantic fact; verbose payloads separated from hot rows; every index justified by a named reader (index bytes > heap bytes needs a recorded reason); per-database budgets with a doctor-plugin size check; physically idempotent writers (content-guarded upserts, never unconditional DO UPDATE). Details and enforcement: service-consolidation-review.md §4.

P14 — Domain-homing rule

Data lives with its domain, not with its consumer. A service may hold another domain’s data only as a declared, rebuildable projection. (Live violation this rule retires: ~2.05 GB of EPSS — per-CVE vulnerability data — homed in the scanner schema because Scanner consumes it; it relocates to the Vulnerabilities hub.)

P16 — Non-PostgreSQL state follows the same ownership rules

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 original harm vector was disk exhaustion, and object stores/queues can reproduce it unpoliced. Every service’s keys/streams live under its own prefix ({service}: keys; the existing stream:{name} convention); every object-store bucket/prefix appears in the ownership matrix with its owner and budget; the doctor standard checks include cache/bucket size against budget. Cross-service consumption of another owner’s stream is a contract (DC-06/07 envelope), never ad-hoc key reads. Details: DC-27, DC-28.

P15 — Decision-time independence (release gates never wait on the data plane)

Release decisions read only control-plane state. Policy’s gate evaluation consumes its own projections (fed asynchronously by hub events); no gate-time code path performs a synchronous call or query into the vulndata cluster. A vulndata outage therefore degrades freshness (verdicts carry a staleness/corpus_state annotation per DC-09), never availability of release decisions. Enforced by conformance (Policy holds no vulndata-cluster connection) and a chaos test (stop the vulndata cluster → gates keep deciding). Full rationale: DC-11.

P12 — The vulnerability hub is tenant-free

stellaops_vulnerabilities holds global knowledge only: raw and normalized vulnerability/VEX/ distro data, provenance, and global consensus. Anything tenant-scoped (effective per-tenant decisions, overlays, acceptances, local VEX) lives in control-plane services. This keeps the hub rebuildable-from-sources by definition and removes tenancy/RLS complexity from the largest data plane.

P17 — Platform charter (rev-12)

Platform is the estate’s domain-agnostic control plane: estate settings, sealed-mode posture state, capability/actor catalogs, console bootstrap. It owns no domain nouns (vulnerabilities, findings, evidence, scans, releases, jobs, notifications, SBOMs…). Anything carrying a domain noun or a domain write-path belongs to a domain service or a shared library. Adding a Platform responsibility requires an ADR-039 amendment arguing why it is estate posture rather than domain logic. Enforcement: review gate + a conformance lint for domain nouns in Platform schemas/APIs. Rationale: posture (025), the capability registry (009), and the actor-identity catalog (027) are each individually justified — this charter keeps the sum bounded so Platform never regrows into the god-service whose database this program just emptied.

P18 — Service-birth criteria (rev-12)

The consolidation criteria (review §1) apply in reverse: a new independently deployable service (and database) requires the §1 walkthrough in writing (domain language, shared tables, scaling profile; the keep-separate tests: security boundary, blast radius, optional shipping) plus an ADR-039 amendment adding it to the topology and the ownership matrix. No deployable enters compose without its matrix row, owner database, and P4 migrations. Rationale: the estate consolidated once from ~44 deployables; without a birth gate the count silently creeps back over the decade.

Decisions (VDPS-D1 … VDPS-D14)

#Decision (proposed)Alternatives consideredConsequences
D1Vulnerability data physically leaves stellaops_platform. Schema-only isolation rejected.Quotas/tuning in place — rejected: third recurrence proves it.Platform DB shrinks to true control plane.
D2Consolidate Concelier + Excititor + VexHub into one StellaOps.Vulnerabilities microservice (deployables: vulnerabilities-web, vulnerabilities-worker) owning one database stellaops_vulnon the vulndata cluster. The service is the raw + normalized data hub: source-connector plugins (streaming ingest), mirror import/export plugins, and lookup/serving APIs (vuln, VEX, consensus, corpus export). Namespaces/projects/files are renamed StellaOps.Vulnerabilities.* with DAL as StellaOps.Vulnerabilities.Persistence (P2).(a) Keep 3 services + 4 DBs (rev-1 of this ADR) — rejected by owner review: the cross-writes (X1/X2) prove they are one domain; separate DBs would force outbox hops inside one logical service. (b) Merge DBs but keep 3 deployables — rejected: re-creates shared-domain-tables between “independent” services (P1 violation).Method (owner instruction): rebuild from scratch by default — the service is re-authored in its new home; code is carried over only where the transformation is purely mechanical (tool/script-based namespace + file renames), and the executing agents decide per component where mechanical transfer genuinely saves work. Never mechanically port the defective layers (storage/projection/retention). Known traps for the mechanical parts: cross-ALC plugin identity, embedded migration resource names, bundle prune lists. Internal schemas (ingest, facts, consensus, dist) are fine — one owner.
D3Per-tenant decision projections move to the control plane. The successor of vuln.issue_gate_decisions is a Policy-owned projection in stellaops_policy: the existing policy-engine microservice does this work — the derivation logic is refactored out of Concelier’s gate-projection code (PostgresIssueGateDecisionProjectionStore, GateDecisionStatusSemantics, dirty-queue) into Policy, fed by hub events + tenant overlays; tenant overlays (acceptance/exception/local VEX) are control-plane data. The hub keeps the global consensus/linkset layer and serves it via API. CLI compact vuln-db export reads the hub’s global corpus API (it is global data; today it incorrectly reads tenant-scoped gates).(a) Keep gates in the hub DB with Policy reading cross-DB — rejected: cross-service SQL, and tenant data would poison the tenant-free hub (P12).Kills register entries X3/X4 outright. Policy owns its own read-model lifecycle; UI “Sources & consensus” reads hub APIs.
D4Fact identity = (issuer/source, stable source_statement_id, canonical vulnerability, canonical product, normalized applicability hash); issue identity = (canonical vulnerability, canonical product) (tenant-free in the hub). A claim and its derived distribution statement are one fact with two provenance records; disjoint ranges are distinct facts; age/latest is never an applicability rule.Keep (provider, vuln, product) latest-wins — rejected: measurably destroyed disjoint ranges (corpus already flattened to exactly one claim per group).Retention becomes semantic reconciliation.
D5All propagation uses the P6 outbox/inbox/lease standard: internal hub projections (facts → consensus, facts → distribution view) and outbound hub→control-plane events (to Policy’s projection) alike. One leased+fenced writer per stream; single destination transaction; physically idempotent replay.Timer sweeps + process-local watermarks — rejected: measured write amplification and restart re-runs.Deterministic replay; restart resumes durable watermark.
D6Approved — “do it the right way”: disable the VexRetentionSweepService destructive sweeps as a proper, tested change (config default-off + code guard removing Excititor’s authority over foreign tables), not an ad-hoc toggle; runtime window still required. With D7 (fresh rebuild) the old plane is disposable, so no further v1 correctness investment — no alarms build-out, no repair tooling. Growth until decommission is accepted and monitored.Fix v1 retention identities first — rejected by D7: dead-end work on a store we discard.Cheapest possible safeguard; old plane serves as-is until cutover.
D7Fresh start; no data preservation. The hub is built empty and filled by full re-ingestion from source connectors and mirror seed bundles (accepted duration: days). No side-by-side backfill from v1, no row-level parity against v1 (it is measurably corrupted and pre-flattened). Cutover gates are coverage-based: per-source ingest completion vs. upstream inventories, sanity count comparison old-vs-new with a known-differences ledger, and reader smoke parity (Policy/CLI/UI forcing checks).Side-by-side backfill + deterministic parity (rev-1) — rejected by owner review: large temp capacity, weeks of machinery, and v1 is not a trustworthy baseline anyway.With D13’s connector pruning, every retained Tier-0 source is a credential-free public feed or the local mirror — re-ingest has no external credential dependency. History accumulated in v1 (withdrawn/old revisions) is lost — accepted.
D8Capacity: 100 GB is available now (owner statement 2026-07-22) for the vulndata cluster volume (hub steady state ≈ 25–35 GB measured basis + ingest/WAL headroom). Sequencing (owner instruction): the old vulnerability data in the platform DB is deleted before the new hub ingests — nothing is preserved (D7), so the old plane does not need to keep serving during the rebuild; the estate accepts a degraded vulnerability window (alpha) and frees ~89 GB up front.Keep old plane serving until cutover (rev-3 ordering) — declined by owner: dual-run machinery buys nothing when no data is preserved and the estate is in alpha.Simpler program: no dual-run, no alias-metric ceremony, no cutover-rollback theater; the decommission still runs under its own destructive approval (D9).
D9Decommission of the old plane (vuln/vex/vexhub/concelier schemas in stellaops_platform, ~89 GB) is a separate destructive approval after cutover + soak. No pre-drop snapshot (owner decision 2026-07-22): the tested recovery path is re-ingestion from sources/mirror — exactly what the architecture guarantees. Never encoded in startup migration.Pre-drop snapshot as insurance — offered by architecture, declined by owner: it would re-bless derived state as a recovery source, the habit being removed.Disk reclaimed last; recovery after the drop = re-ingest (days) by design.
D10Tenancy: Authority is the sole owner of tenants and IdP configuration in stellaops_authority (it already owns authority.tenants and tenant lifecycle; the IdP global-config-store work is already in flight). shared.tenants in the platform DB stops being written by Authority and becomes an event-carried replica (or is retired when consumers move to the Authority API). Kills X6/X7: Authority never reads or writes the platform DB.(a) New dedicated StellaOps.Tenancy microservice — viable if the tenant model grows (orgs, quotas, billing); not justified today for a catalog + IdP config, and it would add a bootstrap-order dependency below Authority. Revisit trigger: multi-org hierarchy or tenant self-service.Needs owner confirmation (owner floated both options). Either way the platform-DB coupling dies.
D11Doctor plugin model per P11: each service hosts its own doctor checks; Doctor aggregates via APIs; the cross-schema database checks retire.Central Doctor with a per-DB connection catalog (rev-1 X10 disposition) — rejected: reproduces the “one service knows all schemas” defect the estate is removing.Doctor work lands per service as its wave lands; X10 removed from the register.
D12Persistence naming standard per P2 (StellaOps.<Service>.Persistence), applied estate-wide with the waves; for Vulnerabilities it lands with the D2 rename.Leave mixed naming — rejected: the inconsistency hides ownership, which is this incident’s root theme.Mechanical renames; embedded-resource names change with them (rebuild-owning-lib trap noted in sprint).
D13Connector lifecycle policy (owner decision 2026-07-22; amended rev-12, 2026-07-23): sources are tiered — Tier 0 core (credential-free public feeds + mirror), Tier 1 regional/optional plugins (kept, off by default: RU-BDU/NKCKI, JVN, KISA, CERT-Bund/FR, CCCS, ICS, …), and removed: Cisco + MSRC (advisory and Excititor CSAF — proven working but bring no content beyond existing sources), GHSA (fully covered by OSV), ACSC / CERT-In (region-locked). KEV was initially in the removed list and RETURNS to Tier 0 at rev-12 — the round-20 review proved exploited-in-the-wild is load-bearing (RO’s BlockOnKnownExploited gate defaults true; Findings risk aggregation; compact export) and no Tier-0 source substitutes (NVD maps no cisaExploit* fields); the catalog is credential-free, so it satisfies the Tier-0 criterion; owner: loss not accepted. Exploit evidence becomes a first-class signal family (curated exploited + exploit availability + EPSS probability) served by the hub — program SPRINT_20260723_003. Removal = full code/config/fixture deletion; re-adding is a plugin drop-in because the hub core is source-agnostic. Signal types stay first-class in the fact model regardless of which source feeds them. Full policy: connectors-architecture.md.Disable-flags instead of deletion — rejected: dead code rots, and the plugin contract makes re-add cheap; keep-and-maintain — rejected: no content value.Tier 0 stays credential-free (strengthens D7); the removed connectors’ documented rationale prevents re-litigating.
D14Estate consolidation program: apply the consolidation criteria and anti-bloat rules (P13/P14) estate-wide before the broader rewrite — recommended merges: Findings family (5 deployables→1), Attestor+TSA+tileproxy (Signer stays separate: key-custody boundary), JobEngine, AdvisoryAI+OpsMemory, BinaryIndex+Symbols, Notify+Notifier, IssuerDirectory→Authority; EPSS relocates from Scanner to the hub (P14). Per-family decisions land at Wave-2 planning. Full review: service-consolidation-review.md.Leave topology as-is and only fix databases — rejected: the vulnerability incident came from boundaries drawn wrong, not just from a shared DB; fixing databases around wrong boundaries hard-codes them for another decade.~44 deployables → ~24 groups / ~20 services & databases; each merge is its own decision + sprint; none blocks the Vulnerabilities program.

Program structure (many sprints, deliberately)

SprintScope
SPRINT_20260722_001Phase A: D1–D14 approvals, retention freeze (runtime approval), connector-removal sign-off, volume provisioning
SPRINT_20260722_003Consolidation & rename to StellaOps.Vulnerabilities + connector pruning (D13) + persistence baseline + outbox/lease library
SPRINT_20260722_004Hub ingestion: connector plugin host, normalization to facts, EPSS relocation (P14), mirror import
SPRINT_20260722_005Hub projections + serving/export APIs + mirror export
SPRINT_20260722_006Router/gateway route migration + Console UI migration
SPRINT_20260722_007Policy-owned tenant gate projection + reader repointing
SPRINT_20260722_008Early decommission of the old plane (destructive approval, D8/D9) → production re-ingest → reader switch (no snapshot; degraded vulnerability window accepted in alpha)
SPRINT_20260722_002Estate coordination board: sequencing map, register burn-down verification, conformance growth gate, wave scheduling
SPRINT_20260722_009Doctor program (D11): base library + plugin contract → Platform capability registry → consumer rerouting → retire central probing
SPRINT_20260722_010016One program per D14 family merge: Findings, Evidence (Attestor+TSA+tileproxy+EvidenceLocker — 011, owner-widened), JobEngine, AdvisoryAI(+OpsMemory), BinaryIndex → re-scoped to hub dissolution (DC-33, no standalone service), Notify(+Notifier), Authority(+IssuerDirectory). Each starts with an immersion/design phase before any code moves
SPRINT_20260722_023Graph consolidation: graph-api + reachgraph → StellaOps.Graph / stellaops_graph (owner-approved; signals stays separate)
SPRINT_20260722_024Integrations + registry-token fold (owner-approved; token issuance as availability-isolated role) → stellaops_integrations
SPRINT_20260722_025OfflineKit unification (owner-approved EVL-3): ExportCenter + Mirror.Creator + AirGap exchange → stellaops_offlinekit; posture → Platform; envelope realized via AirGap.Bundle (DC-18/21)
SPRINT_20260722_026Central-migrator retirement (X15 mechanism side): admin API + seed-demo + CLI applier retired early; shell-schema cleanup; per-program plugin deletions stay with programs
SPRINT_20260722_027Shared-catalog replication library (X14, both catalogs): replica tables per host DB + producers (Authority tenants via 016; Platform actor_identity) + dual-mode rollout
SPRINT_20260722_028Non-PostgreSQL state ownership (P16/DC-27/28): Valkey prefix + object-store bucket inventories, budgets, doctor checks
SPRINT_20260722_017019Dedicated same-service DB moves (recipe Part B): Scanner (post-EPSS), ReleaseOrchestrator (4 schemas + X9 sequencing), Signer (+ the P4 shared-persistence-library pattern)
SPRINT_20260722_020Wave-3 batch: the 15 remaining same-service moves, one evidence row each (no blanket ticks)
SPRINT_20260722_021Platform endgame: DAL rename, container/instance naming alignment (stellaops), generic-connection retirement, final estate audit (empty register)
SPRINT_20260722_022Evaluation families (S0+S1 only): VexLens fold, Reachability, Offline/exchange — outcomes spawn programs or record keep/defer

Consequences

References