Legacy-edge dispositions — every register row assigned to merge or a durable seam (ARCHIVED MBI-4 compilation, 2026-08-06)
SUPERSEDED 2026-08-18 — this is the frozen MBI-4 evidence, not the live table. The live dispositions table is now generated from
legacy-edge-register.json+legacy-edge-resolvers.jsonand lives atlegacy-edge-dispositions.md; this file kept its content and moved to a dated name (team-lead ruling 2026-08-17,SPRINT_20260730_001Decisions & Risks). Nothing below is rewritten — the counts, the strikes and the currency notes are preserved exactly as the lanes left them, including the drift they describe. Read it for the reasoning the generated table deliberately does not reproduce: §6’s gate-coverage findings, §7’s undecided remainder, §8’s register-hygiene list and §9/§9A’s criteria state. For current pin state read the generated table or runpwsh tools/scripts/build-boundary/generate-build-boundary-report.ps1 -Check. This file is also the seed input for the retirement ledger’s struck-row citations (generate-legacy-edge-dispositions.ps1 -UpdateLedger -SeedFromLegacyTable <this file>), so it stays in the tree.
Compiled by
SPRINT_20260730_001MBI-4 on 2026-08-06 against the working tree at that date. This document does not invent dispositions. The register already carriestargetSeam,owningSprintandsunsetper row; MBI-4’s job was to compile those into one reviewable table, validate each against the owning program’s approved design and the measured graph, and flag the rows that are genuinely undecided or whose named sunset gate cannot retire them. Where a family S0 design and the register disagree, the register wins on measurement (it is generated) and the disagreement is recorded as a correction owed to the design doc — see §6.Amended later the same day by the MBI-4 authoring pass (still 2026-08-06, same working tree): §6’s six gaps are now authored into their owner sprints and each subsection ends with an Authored line saying where; §9A resolves the 21 keep-separate citations criterion 3 owed; §8 records exactly what was written to the register. Two arithmetic/naming errors in the compiled text were corrected against the report (§6 G1’s replay count, §6 G2’s third carrier project) — both are marked in place rather than silently overwritten. Nothing in §1–§5 changed.
Currency note (2026-08-10) — this is a dated record and its counts have moved. The register is now 119 pins, not the 135 compiled here (018
RO-7deleted therelease-orchestrator ↔ agent-corecycle rows plusagent-core → integrationsandplatform → agent-core; other programs shrank further rows). Nothing in this document is rewritten — it is the MBI-4 evidence as compiled. For current state readlegacy-edge-register.jsonand the MBI-5 burn-down census indocs-archive/implplan/SPRINT_20260730_001_Program_microservice_build_independence.md, which groups the live 119 by owner and gating class. One structural change since compilation also affects how the counts here should be read: since D-ROSEP-4 (2026-08-10) the report evaluates MSBuild conditions, so a pair is now labelled default-build or opt-in-only — four of these rows (agent-core →attestor/concelier/policy/scanner) exist only underIncludeBuildDockerReachabilityand are absent from the default build.Currency note (2026-08-17) — READ THIS BEFORE TRUSTING AN UNSTRUCK ROW. Some rows below are now struck as PIN DELETED, and that pass is DELIBERATELY INCOMPLETE, so an unstruck row does NOT mean a live pin. This document declares itself a dated MBI-4 compilation that is not rewritten, but two lanes have since struck individual rows in place (009 DOC-5’s
doctor-web|scheduler; 014’s nine:scanner-web|binaryindex, both*|symbols, the fouradvisory-ai-*|attestor/|concelierandfindings-vulncorrelation|concelier). Selective striking is the hazard worth naming: it silently implies the unstruck rows were checked and survived. They were not. Measured this date by cross-checking every row against the register, about 38 further rows name pins that no longer exist — acrossplatform,unknowns,policy,replay,signals,scanner,integrations,findings-ledgerandagent-core. They are left alone deliberately: they belong to other programs, and rewriting a dossier one does not own is out of scope (AGENTS.md §2.10, “stay proportionate”). Each lane should strike its own as it retires them.The register is the only current truth; this table is evidence of what was compiled in August. To compute the real state in one step rather than reading rows here:
pwsh tools/scripts/build-boundary/generate-build-boundary-report.ps1 -CheckA pin that is resolved but still registered fails that check by design (expires-on-use), so the gate — not this table — is what tells you a row is dead.
Amended later the same day — the register is 114, and two rows in the 017 table below are fully resolved.
integrations-web → scannerandsignals → scanner, both recorded here asowner-API seamcarried byStellaOps.Scanner.Contracts, took a different and better route: 017 SCN-3 reclassified that project as a closed client SDK (which deleted both pins, 116 → 114), andSPRINT_20260730_001MBI-5’s pilot then published it as an exact-version package, which removed the compile the classification had left behind. Both consumers now pass a publish withsrc/Scannerdeleted. The rows are left as compiled — this is a dated record — but do not plan an owner-API seam for either; the seam that shipped is a package. Three further pins (*|symbols) were resolved outright by MBI-3’s red fixture.Amended 2026-08-10 — the register is 112. SPRINT_20260722_010 retired the two
* → evidence-lockerpins that ran throughStellaOps.Findings.Ledgerby repointing oneProjectReferenceat the producer’s already-closed contracts project; see the note under the 011 table. Same lesson as the 017 rows above, arriving from the other side: check whether the producer already ships a closed contract project before scheduling a consumer’s edge behind that producer’s merge.
Inputs, all read-only:
| Input | Role here |
|---|---|
legacy-edge-register.json (135 pins + 1 libraryImpurityPins) | the worklist; source of targetSeam/owningSprint/sunset/witness |
build-boundary-report.json (generated) | the measured graph; source of every “measured” count |
docs/modules/{attestor,findings,jobengine,advisory-ai,binaryindex,notify,authority,graph,export-center,integrations}/consolidation-design.md | the per-edge authority for rows in those families |
SPRINT_20260722_{003,007,017,018,019,020,026} | the authority for families with no S0 design doc |
docs/architecture/service-consolidation-review.md §“Build-coupling decision test” | the four clauses each row is tested against |
docs/architecture/service-consolidation-recipe.md | where a S7/M2-style sunset gets its criteria |
1. Reconciliation proof (anti-vacuity)
The table below has exactly one row per live register entry, and the register has exactly one entry per measured violation pair. Verified 2026-08-06:
pwsh tools/scripts/build-boundary/generate-build-boundary-report.ps1 -Check
deployable keys : 55
keys w/ foreign tops : 49 (dated 2026-07-30 baseline: 47/49)
violation pairs : 135
unpinned pairs : 0
stale pins : 0
grown pins : 0
impure neutral projects: 1
impure client SDKs : 0
OK (exit 0)
| Reconciliation check | Result |
|---|---|
Register pins | 135 |
| Measured violation pairs in the report | 135 |
| Pairs in the register but not in the graph (dead pins) | 0 |
| Pairs in the graph but not in the register (unpinned) | 0 |
libraryImpurityPins / measured impure neutral projects | 1 / 1 |
| Rows in this document | 136 = 135 + 1 |
| Rows with an owning program | 136 / 136 |
| Rows with a disposition class | 135 / 136 (one deferred — §7) |
-Check reads the working tree, not HEAD (the MBI-7 defect). For this pass the two agree: git status --porcelain -- '*.csproj' 'docs/architecture/build-boundary/' is empty, and the only untracked paths in the tree are src/Notify/plugins/notify/* drop folders containing zero .csproj, so they cannot alter the graph.
2. How each row is classified
The register’s targetSeam maps onto the review’s build-coupling decision test as follows. The class names below are used throughout this document.
Register targetSeam | Disposition class | Decision-test clause | Meaning |
|---|---|---|---|
merge | dies-by-consolidation | 1 | one domain lifecycle, or the mechanism holding the edge is deleted — no runtime seam is manufactured |
api | owner-API seam | 2 | keep separate; consumer calls the owner’s API |
event | event seam | 2 | keep separate; consumer subscribes to a versioned event |
artifact | artifact seam | 2 | keep separate; consumer reads a content-addressed artifact |
published-package | package/closed-SDK seam | 2 | keep separate; producer publishes an exact-version package, or a producer-owned closed client/contract source project whose graph conformance proves it implementation-free |
neutral-sdk | neutral-SDK purification | 4 | a shared library’s own closure must stop reaching service implementation |
Two things this mapping deliberately does not do. It does not treat a .Contracts/.Client name as a disposition — clause 3 forbids that, and §7 records a live instance of the anti-pattern. And it does not split published-package into “package” vs “closed source SDK”: clause 2 admits both, the choice is the producer program’s at its contract-freeze stage, and MBI-3 owns the packaging mechanism when packaging is the one selected.
The dies-by-consolidation class covers two different mechanisms, worth separating when reading the totals: 13 rows are true family merges (011 Evidence ×4, 015 Notify+Notifier ×6, 025 AirGap→ OfflineKit ×3), and 8 rows are platform’s central-migrator fan-in (026), where no two services merge — the edge disappears because Platform.Database’s foreign ProjectReference set is deleted.
3. Totals
Totals by disposition class
| Disposition class | Decision-test clause | Rows | Foreign-project units |
|---|---|---|---|
| owner-API seam | 2 | 56 | 184 |
| package/closed-SDK seam | 2 | 49 | 256 |
| dies-by-consolidation | 1 | 21 | 47 |
| artifact seam | 2 | 6 | 38 |
| event seam | 2 | 3 | 5 |
| neutral-SDK purification | 4 | 1 | 17 |
| Total | 136 | 547 |
Totals by owning program
| Owning sprint | Family surface | Rows | merge | api | event | artifact | pkg/SDK | neutral | Gate state |
|---|---|---|---|---|---|---|---|---|---|
026 | Platform central-migrator retirement (consumer-side fan-in) | 27 | 8 | 11 | 1 | 2 | 5 | gated 27 | |
011 | Evidence family merge (attestor + evidence-locker) | 21 | 4 | 17 | gated 21 | ||||
003 | Vulnerabilities hub (concelier/excititor producers) | 20 | 20 | gated 20 | |||||
007 | Policy producer surface | 14 | 14 | thin 14 | |||||
020 | Wave-3 database moves (replay/timeline/signals/unknowns producers) | 12 | 10 | 2 | thin 12 | ||||
017 | Scanner producer surface | 9 | 9 | thin 9 | |||||
015 | Notify + Notifier consolidation | 6 | 6 | gated 6 | |||||
019 | Signer (key-custody keep-separate) | 5 | 4 | 1 | gated 3, thin 2 | ||||
024 | Integrations + registry-token consolidation | 5 | 5 | gated 5 | |||||
010 | Findings consolidation | 4 | 3 | 1 | gated 4 | ||||
014 | BinaryIndex/Symbols dissolution into the hub (DC-33) | 4 | 4 | gated 1, thin 3 | |||||
018 | ReleaseOrchestrator / agent-core boundary | 3 | 2 | 1 | thin 2, undecided 1 | ||||
025 | OfflineKit / AirGap split | 3 | 3 | gated 3 | |||||
012 | JobEngine consolidation (scheduler producer) | 1 | 1 | gated 1 | |||||
016 | Authority tenancy/idp/issuer consolidation | 1 | 1 | gated 1 | |||||
023 | Graph + ReachGraph consolidation | 1 | 1 | gated 1 | |||||
| Total | 136 | 21 | 56 | 3 | 6 | 49 | 1 |
4. Per-row assignments
SPRINT_20260722_026 — Platform central-migrator retirement (consumer-side fan-in) (27 rows)
Authority: SPRINT_20260722_026 CM-2 + CM-4. Dies at: 026 CM stages.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
platform | agent-core | 1 | package/closed-SDK seam | StellaOps.Agent.Core | 026 CM stages | gated |
platform | airgap-controller | 3 | dies-by-consolidation | StellaOps.AirGap.Persistence | 026 CM stages | gated |
platform | airgap-time | 1 | dies-by-consolidation | StellaOps.AirGap.Time | 026 CM stages | gated |
platform | attestor | 3 (pin says 6) | package/closed-SDK seam | StellaOps.Attestor.Envelope | 026 CM stages | gated |
platform | authority | 2 | package/closed-SDK seam | StellaOps.Authority.Persistence | 026 CM stages | gated |
platform | binaryindex | 13 | artifact seam | StellaOps.BinaryIndex.GoldenSet | 026 CM stages | gated |
platform | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.Concelier.SbomIntegration | 026 CM stages | gated |
platform | evidence-locker | 7 (pin says 8) | dies-by-consolidation | StellaOps.Artifact.Infrastructure | 026 CM stages | gated |
platform | export-center | 2 | dies-by-consolidation | StellaOps.ExportCenter.Infrastructure | 026 CM stages | gated |
platform | graph | 2 | dies-by-consolidation | StellaOps.Graph.Indexer.Persistence | 026 CM stages | gated |
platform | integrations | 3 | owner-API seam | StellaOps.Integrations.Persistence | 026 CM stages | gated |
platform | issuer-directory | 2 | dies-by-consolidation | StellaOps.IssuerDirectory.Persistence | 026 CM stages | gated |
platform | notify | 3 | dies-by-consolidation | StellaOps.Notify.Persistence | 026 CM stages | gated |
platform | packsregistry | 2 | dies-by-consolidation | StellaOps.PacksRegistry.Persistence | 026 CM stages | gated |
platform | policy | 6 | owner-API seam | StellaOps.Policy.Persistence | 026 CM stages | gated |
platform | reachgraph | 2 | owner-API seam | StellaOps.ReachGraph.Persistence | 026 CM stages | gated |
platform | release-orchestrator | 11 | owner-API seam | StellaOps.ReleaseOrchestrator.Environment | 026 CM stages | gated |
platform | remediation | 2 | owner-API seam | StellaOps.Remediation.Persistence | 026 CM stages | gated |
platform | replay | 1 | owner-API seam | StellaOps.Replay.Core | 026 CM stages | gated |
platform | sbomservice | 1 | owner-API seam | StellaOps.SbomService.Lineage | 026 CM stages | gated |
platform | scanner | 1 | owner-API seam | StellaOps.Scanner.ChangeTrace | 026 CM stages | gated |
platform | scheduler | 2 | owner-API seam | StellaOps.Scheduler.Persistence | 026 CM stages | gated |
platform | signer | 3 | package/closed-SDK seam | StellaOps.Signer.KeyManagement | 026 CM stages | gated |
platform | symbols | 1 | artifact seam | StellaOps.Symbols.Core | 026 CM stages | gated |
platform | timeline | 3 | event seam | StellaOps.Timeline.Core | 026 CM stages | gated |
platform | unknowns | 2 | owner-API seam | StellaOps.Unknowns.Persistence | 026 CM stages | gated |
platform | workflow | 3 | owner-API seam | StellaOps.Workflow.DataStore.PostgreSQL | 026 CM stages | gated |
SPRINT_20260722_011 — Evidence family merge (attestor + evidence-locker) (21 rows)
Authority: attestor/consolidation-design.md §3 + §5 carrier list. Dies at: 011 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
advisory-ai-web | attestor | — | PIN DELETED 2026-08-17: resolved by b30a1f2cbc(POL-F5 stage 2a), NOT by the concelier resolver. AdvisoryAI reached Attestor only THROUGH StellaOps.Policy; repointing it to the closed StellaOps.Policy.Contracts — whose graph reaches no Attestor — killed the path. Worth keeping: the same consumer’s attestor and concelier pins died to different commits in different sprints, so one citation for both would have been wrong. | — | — | resolved |
advisory-ai-worker | attestor | — | PIN DELETED 2026-08-17: same resolver, b30a1f2cbc. | — | — | resolved |
agent-core | attestor | 3 | package/closed-SDK seam | StellaOps.Attestor.GraphRoot | 011 S7/M2 gate | gated |
attestor-tileproxy | attestor | 2 | package/closed-SDK seam | StellaOps.Attestor.TrustRepo | 011 S7/M2 gate | gated |
binaryindex-web | attestor | 2 | package/closed-SDK seam | StellaOps.Attestor.StandardPredicates | 011 S7/M2 gate | gated |
concelier | attestor | 2 | package/closed-SDK seam | StellaOps.Attestor.ProofChain | 011 S7/M2 gate | gated |
evidence-locker-web | attestor | 3 | package/closed-SDK seam | StellaOps.Attestor.CapsuleProjection | 011 S7/M2 gate | gated |
excititor-web | attestor | 2 | package/closed-SDK seam | StellaOps.Attestor.StandardPredicates | 011 S7/M2 gate | gated |
excititor-worker | attestor | 2 | package/closed-SDK seam | StellaOps.Attestor.StandardPredicates | 011 S7/M2 gate | gated |
export-web | attestor | 1 | package/closed-SDK seam | StellaOps.Attestor.ProofChain | 011 S7/M2 gate | gated |
export-worker | attestor | 1 | package/closed-SDK seam | StellaOps.Attestor.ProofChain | 011 S7/M2 gate | gated |
policy-engine | attestor | 3 | package/closed-SDK seam | StellaOps.Attestor.GraphRoot | 011 S7/M2 gate | gated |
release-orchestrator | attestor | 3 | package/closed-SDK seam | StellaOps.Attestor.Core | 011 S7/M2 gate | gated |
sbomservice | attestor | 2 | package/closed-SDK seam | StellaOps.Attestor.StandardPredicates | 011 S7/M2 gate | gated |
scanner-web | attestor | 3 | package/closed-SDK seam | StellaOps.Attestor.Core | 011 S7/M2 gate | gated |
scanner-worker | attestor | 3 | package/closed-SDK seam | StellaOps.Attestor.Core | 011 S7/M2 gate | gated |
unknowns-web | attestor | 1 | package/closed-SDK seam | StellaOps.Attestor.ProofChain | 011 S7/M2 gate | gated |
advisory-ai-web | evidence-locker | 1 | dies-by-consolidation | StellaOps.Evidence.Pack | 011 S7/M2 gate | gated |
advisory-ai-worker | evidence-locker | 1 | dies-by-consolidation | StellaOps.Evidence.Pack | 011 S7/M2 gate | gated |
Two evidence-locker rows RETIRED 2026-08-10 (SPRINT_20260722_010), ahead of the 011 gate they were waiting on — findings-ledger-web → evidence-locker and notify-web → evidence-locker, both previously dies-by-consolidation / StellaOps.EvidenceLocker.Core / 011 S7/M2 gate. They did not need the consolidation. Both witnesses ran through src/Findings/StellaOps.Findings.Ledger, whose only EvidenceLocker use is one call — CapsulePiiGuard.FindPiiViolations in LedgerEventWriteService — and that namespace (StellaOps.EvidenceLocker.Capsules) has lived in the producer’s closed StellaOps.EvidenceLocker.Contracts since the producer extracted it (zero ProjectReference, zero PackageReference, BCL only, per its own csproj header). The reference simply still pointed at .Core, so a static PII helper was dragging the locker’s domain core into two deployables. One ProjectReference swap retired both pins: register 114 → 112, findings-ledger-web 46 → 45 projects / 2 → 1 pairs, notify-web 55 → 54 / 3 → 2, gate OK at 0 unpinned / 0 stale / 0 grown. Read-across: a pin whose target already has a closed contracts project is not gated on anything — check the producer’s .Contracts before scheduling a consumer’s edge behind a merge. advisory-ai-web/-worker reach evidence-locker through StellaOps.Evidence.Pack, a different carrier, and were NOT touched.
SPRINT_20260722_003 — Vulnerabilities hub (concelier/excititor producers) (20 rows)
Authority: SPRINT_20260722_003 VULN-B1; consumer-side advisory-ai/consolidation-design.md §3, binaryindex/… §3. Dies at: 003 VULN-B1 contract-package stage + consumer repoint.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
advisory-ai-web | concelier | — | PIN DELETED 2026-08-17: resolved by 3419d519df, which removed AdvisoryAI’s last three Concelier references (Concelier.Core, .RawModels, .Persistence) together with the provider files that were their only consumers. The long-standing “14 (pin says 15)” drift note in this row dies with it. | — | — | resolved |
advisory-ai-worker | concelier | — | PIN DELETED 2026-08-17: same resolver, 3419d519df. | — | — | resolved |
agent-core | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.Concelier.SbomIntegration | 003 VULN-B1 contract-package stage + consumer repoint | gated |
attestor | concelier | 2 (pin says 3) | package/closed-SDK seam | StellaOps.Concelier.SourceIntel | 003 VULN-B1 contract-package stage + consumer repoint | gated |
binaryindex-web | concelier | 15 | package/closed-SDK seam | StellaOps.Concelier.Connector.Common | 003 VULN-B1 contract-package stage + consumer repoint | gated |
excititor-web | concelier | 15 | package/closed-SDK seam | StellaOps.Concelier.Connector.Common | 003 VULN-B1 contract-package stage + consumer repoint | gated |
excititor-worker | concelier | 15 | package/closed-SDK seam | StellaOps.Concelier.Connector.Common | 003 VULN-B1 contract-package stage + consumer repoint | gated |
export-web | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.VulnMatch.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
export-worker | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.VulnMatch.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
findings-vulncorrelation | concelier | — | PIN DELETED 2026-08-17: resolved by 652421d7bf, which removed the HOST’s own Concelier.SbomIntegration reference — the edge this row names. 0ac1b5d30a is the commit usually cited and it is not the resolver: it retargeted the Application and Tests projects, which mattered but did not kill the pinned edge. | — | — | resolved |
integrations-web | concelier | 1 | package/closed-SDK seam | StellaOps.VulnMatch.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
policy-engine | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.VulnMatch.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
release-orchestrator | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.Concelier.SbomIntegration | 003 VULN-B1 contract-package stage + consumer repoint | gated |
sbomservice | concelier | 15 | package/closed-SDK seam | StellaOps.Concelier.Connector.Common | 003 VULN-B1 contract-package stage + consumer repoint | gated |
scanner-web | concelier | 10 | package/closed-SDK seam | StellaOps.Concelier.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
scanner-worker | concelier | 11 | package/closed-SDK seam | StellaOps.Concelier.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
unknowns-web | concelier | 10 (pin says 11) | package/closed-SDK seam | StellaOps.Concelier.SbomIntegration | 003 VULN-B1 contract-package stage + consumer repoint | gated |
binaryindex-web | excititor | 1 | package/closed-SDK seam | StellaOps.Excititor.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
concelier | excititor | 1 | package/closed-SDK seam | StellaOps.Excititor.Core | 003 VULN-B1 contract-package stage + consumer repoint | gated |
sbomservice | excititor | 2 | package/closed-SDK seam | StellaOps.Excititor.Persistence | 003 VULN-B1 contract-package stage + consumer repoint | gated |
SPRINT_20260722_007 — Policy producer surface (14 rows)
Authority: SPRINT_20260722_007 POL-F3. Dies at: 007 POL gates + P19 stage.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
advisory-ai-web | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
advisory-ai-worker | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
agent-core | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
binaryindex-web | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
concelier | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
excititor-web | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
excititor-worker | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
export-web | policy | 4 (pin says 10) | owner-API seam | StellaOps.Policy.Exceptions | 007 POL gates + P19 stage | thin |
export-worker | policy | 4 (pin says 10) | owner-API seam | StellaOps.Policy.Exceptions | 007 POL gates + P19 stage | thin |
release-orchestrator | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
sbomservice | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
scanner-web | policy | 7 | owner-API seam | StellaOps.Policy.Determinization | 007 POL gates + P19 stage | thin |
scanner-worker | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
unknowns-web | policy | 3 | owner-API seam | StellaOps.Policy | 007 POL gates + P19 stage | thin |
SPRINT_20260722_020 — Wave-3 database moves (replay/timeline/signals/unknowns producers) (12 rows)
Authority: none — no S0 design doc; W3 rows are M-recipe database moves. Dies at: 020 W3 row (+ per-row variants below).
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
agent-core | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
concelier | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
policy-engine | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
release-orchestrator | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
scanner-web | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
scanner-worker | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
timeline-web | replay | 1 | owner-API seam | StellaOps.Replay.Core | 020 W3 row | thin |
scanner-web | signals | 2 | owner-API seam | StellaOps.Signals.Ebpf | 020 W3-06 producer gate | thin |
scanner-worker | signals | 1 | owner-API seam | StellaOps.Signals.Ebpf | 020 W3-06 producer gate | thin |
export-web | timeline | 1 | event seam | StellaOps.TimelineIndexer.Core | 020 W3 row | thin |
export-worker | timeline | 1 | event seam | StellaOps.TimelineIndexer.Core | 020 W3 row | thin |
scanner-worker | unknowns | 1 | owner-API seam | StellaOps.Unknowns.Core | 020 W3 row | thin |
SPRINT_20260722_017 — Scanner producer surface (9 rows)
Authority: SPRINT_20260722_017 §5 direction 2 (D-SCN1-11). Dies at: 017 S0 inventory + P19 stage.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
agent-core | scanner | 39 | owner-API seam | StellaOps.Scanner.Sbom.BuildTime | 017 S0 inventory + P19 stage | thin |
export-web | scanner | 1 | owner-API seam | StellaOps.Scanner.ChangeTrace | 017 S0 inventory + P19 stage | thin |
export-worker | scanner | 1 | owner-API seam | StellaOps.Scanner.ChangeTrace | 017 S0 inventory + P19 stage | thin |
findings-vulncorrelation | scanner | 1 | owner-API seam | StellaOps.Scanner.Surface.FS | 017 S0 inventory + P19 stage | thin |
integrations-web | scanner | 1 | owner-API seam | StellaOps.Scanner.Contracts | 017 S0 inventory + P19 stage | thin |
policy-engine | scanner | 1 | owner-API seam | StellaOps.Scanner.ProofSpine | 017 S0 inventory + P19 stage | thin |
release-orchestrator | scanner | 15 | owner-API seam | StellaOps.Scanner.Storage.Oci | 017 S0 inventory + P19 stage | thin |
scheduler-web | scanner | 2 | owner-API seam | StellaOps.Scanner.Surface.Env | 017 S0 inventory + P19 stage | thin |
signals | scanner | 1 | owner-API seam | StellaOps.Scanner.Contracts | 017 S0 inventory + P19 stage | thin |
SPRINT_20260722_015 — Notify + Notifier consolidation (6 rows)
Authority: notify/consolidation-design.md §3. Dies at: 015 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
export-web | notify | 4 | dies-by-consolidation | StellaOps.Notify.Connectors.Dora | 015 S7/M2 gate | gated |
export-worker | notify | 4 | dies-by-consolidation | StellaOps.Notify.Connectors.Dora | 015 S7/M2 gate | gated |
notifier-worker | notify | 6 | dies-by-consolidation | StellaOps.Notify.Delivery | 015 S7/M2 gate | gated — shrink-only exception pending owner ratification |
release-orchestrator | notify | 1 | dies-by-consolidation | StellaOps.Notify.Models | 015 S7/M2 gate | gated |
scanner-web | notify | 1 | dies-by-consolidation | StellaOps.Notify.Models | 015 S7/M2 gate | gated |
scheduler-web | notify | 2 | dies-by-consolidation | StellaOps.Notify.Models | 015 S7/M2 gate | gated |
SPRINT_20260722_019 — Signer (key-custody keep-separate) (5 rows)
Authority: SPRINT_20260722_019 SGN-2; notify/consolidation-design.md §3 two-halves ruling. Dies at: 019 P4 shared-lib stage.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
(library) StellaOps.Verdict | policy/concelier/attestor/signer/replay | 17 (pin says 18) | neutral-SDK purification | — | 019 P4 shared-lib stage | gated — SGN-2 criterion: P19 neutral-SDK closure check |
export-web | signer | 1 | package/closed-SDK seam | StellaOps.Signer.Core | 019 P4 shared-lib stage | gated — Signer.Core closed-contract seam under SGN-2 |
export-worker | signer | 1 | package/closed-SDK seam | StellaOps.Signer.Core | 019 P4 shared-lib stage | gated — Signer.Core closed-contract seam under SGN-2 |
notify-web | signer | 3 | package/closed-SDK seam | StellaOps.Signer.Infrastructure | 019 P4 shared-lib stage | thin — Signer.Infrastructure is a composition root; SGN-2 covers shared libs only |
release-orchestrator | signer | 3 | package/closed-SDK seam | StellaOps.Signer.Infrastructure | 019 P4 shared-lib stage | thin — Signer.Infrastructure is a composition root; SGN-2 covers shared libs only |
SPRINT_20260722_024 — Integrations + registry-token consolidation (5 rows)
Authority: integrations/consolidation-design.md §4. Dies at: 024 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
agent-core | integrations | 2 | owner-API seam | StellaOps.Integrations.Contracts | 024 S7/M2 gate | gated |
release-orchestrator | integrations | 3 | owner-API seam | StellaOps.Integrations.Contracts | 024 S7/M2 gate | gated |
sbomservice | integrations | 3 | owner-API seam | StellaOps.Integrations.Contracts | 024 S7/M2 gate | gated |
scanner-web | integrations | 2 | owner-API seam | StellaOps.Integrations.Contracts | 024 S7/M2 gate | gated |
scanner-worker | integrations | 2 | owner-API seam | StellaOps.Integrations.Contracts | 024 S7/M2 gate | gated |
SPRINT_20260722_010 — Findings consolidation (3 rows)
Authority: findings/consolidation-design.md §4. Dies at: 010 S7/M2 gate (+ per-row variants below).
RETIRED 2026-08-17 — findings-ledger-web → attestor. The row read package/closed-SDK seam via StellaOps.Attestor.Core, and the register’s targetSeam said published-package. Measured against client-sdk-seam.md §1, that value was wrong for this edge: a package is justified only when the consumer ships outside the monorepo, needs an independent upgrade cadence, or has no source tree, and findings-web is none of those — so the source seam is the default and the correct choice. Executed as the documented §2 extraction: StellaOps.Attestor.Contracts (closed — zero ProjectReference, zero PackageReference, BCL only), three files git mvd out of StellaOps.Attestor.Core/Submission/ byte-identically with the namespace preserved, so none of the 20 other Attestor.Core consumers changed a line. Report: 98 → 97 pairs; findings-ledger-web 45 → 43 projects and 1 → 0 violation pairs. This was the last build-boundary blocker on FND-8’s host composition. See SPRINT_20260722_010 F-R0817-1.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
export-web | findings-ledger | 1 | owner-API seam | StellaOps.Findings.DoraRoi | 010 S7/M2 gate | gated |
export-worker | findings-ledger | 1 | owner-API seam | StellaOps.Findings.DoraRoi | 010 S7/M2 gate | gated |
notify-web | findings-ledger | 1 | owner-API seam | StellaOps.Findings.Ledger | 010 S7/M2 gate | gated |
SPRINT_20260722_014 — BinaryIndex/Symbols dissolution into the hub (DC-33) (4 rows)
Authority: binaryindex/consolidation-design.md §3. Dies at: 014 dissolution gates.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
scanner-web | binaryindex | — | PIN DELETED 2026-08-17 (014 BIN-5): closure emptied to ZERO. Not by moving anything — D-BIN5-6 was ruled and the shared disassembly stack (Disassembly(+.Abstractions), Semantic, Decompiler, Ghidra, Contracts) is classified domain-neutral-shared, so all six of this key’s foreign projects stopped being violations where they stand. | — | — | resolved |
scanner-worker | binaryindex | 3 (was 16) | artifact seam | StellaOps.BinaryIndex.ML | 014 dissolution gates | thin — the carrier changed: Core left the closure with the wave-one severance, and what remains is ML + GroundTruth.Reproducible(+.Abstractions), the two capabilities D-BIN5-6 deliberately did NOT cover because neither is vulnerability knowledge |
binaryindex-web | symbols | — | PIN DELETED 2026-08-10 (SPRINT_20260730_001 MBI-3): BinaryIndex.DeltaSig repointed to the closed StellaOps.Symbols.Contracts, so this edge died with the extraction. Row struck 2026-08-17 — it had been stale in this table for a week after the register row went. | — | — | resolved |
scanner-worker | symbols | — | PIN DELETED 2026-08-10 (MBI-3): same extraction; same week-long table drift. | — | — | resolved |
SPRINT_20260722_018 — ReleaseOrchestrator / agent-core boundary (3 rows)
Authority: none — no S0 design doc for this family. Dies at: 018 M gates (+ per-row variants below).
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
release-orchestrator | agent-core | 7 | package/closed-SDK seam | StellaOps.Agent.Compose | 018 M gates + MBI-4 disposition | undecided — disposition itself unsettled (merge vs package) |
agent-core | release-orchestrator | 9 | owner-API seam | StellaOps.ReleaseOrchestrator.Agent | 018 M gates | thin |
scanner-web | release-orchestrator | 1 | owner-API seam | StellaOps.Runtime.Contracts | 018 M gates | thin |
SPRINT_20260722_025 — OfflineKit / AirGap split (3 rows)
Authority: docs-archive/modules/export-center/consolidation-design.md §4. Dies at: 025 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
airgap-time | airgap-controller | 1 | dies-by-consolidation | StellaOps.AirGap.Importer | 025 S7/M2 gate | gated |
scanner-web | airgap-controller | 1 | dies-by-consolidation | StellaOps.AirGap.Importer | 025 S7/M2 gate | gated |
airgap-controller | airgap-time | 1 | dies-by-consolidation | StellaOps.AirGap.Time | 025 S7/M2 gate | gated |
SPRINT_20260722_012 — JobEngine consolidation (scheduler producer) (1 rows)
Authority: jobengine/consolidation-design.md §5.1. Dies at: 012 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
doctor-web | scheduler | — | PIN DELETED 2026-08-17 (009 DOC-5 stage 4): the CONSUMER deployable is retired, so the edge has no live violation to pin. Predicted by the §812 disposition below, which called this “not applicable — the consumer deployable is owner-decided to retire”. | — | — | resolved |
SPRINT_20260722_016 — Authority tenancy/idp/issuer consolidation (1 rows)
Authority: authority/consolidation-design.md §4. Dies at: 016 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
scanner-web | authority | 2 | package/closed-SDK seam | StellaOps.Authority.Persistence | 016 S7/M2 gate | gated |
SPRINT_20260722_023 — Graph + ReachGraph consolidation (1 rows)
Authority: graph/consolidation-design.md §4. Dies at: 023 S7/M2 gate.
| Consumer key | Producer family | Foreign projects | Disposition class | Entry project (carrier) | Dies at | Gate |
|---|---|---|---|---|---|---|
scanner-worker | reachgraph | 1 | owner-API seam | StellaOps.ReachGraph | 023 S7/M2 gate | gated |
5. Carrier concentration (MBI-5 sequencing lever)
The 135 pair pins enter foreign families through 69 distinct entry projects. The 13 carriers below account for 64 of them.
| Pins | Entry (carrier) project | Owning sprint(s) |
|---|---|---|
| 11 | StellaOps.Policy | 007 |
| 8 | StellaOps.Replay.Core | 020, 026 |
| 6 | StellaOps.Attestor.ProofChain | 011 |
| 5 | StellaOps.Concelier.SbomIntegration | 003, 026 |
| 5 | StellaOps.Integrations.Contracts | 024 |
| 4 | StellaOps.Concelier.Core | 003 |
| 4 | StellaOps.Attestor.StandardPredicates | 011 |
| 4 | StellaOps.Concelier.Connector.Common | 003 |
| 4 | StellaOps.VulnMatch.Core | 003 |
| 4 | StellaOps.Attestor.Core | 010, 011 |
| 3 | StellaOps.Symbols.Core | 014, 026 |
| 3 | StellaOps.Scanner.ChangeTrace | 017, 026 |
| 3 | StellaOps.Notify.Models | 015 |
6. Gate-coverage findings — 42 rows whose named sunset cannot retire them
CLOSED 2026-08-06 by the MBI-4 authoring pass. All six gaps have been authored into their owning sprints — four rows of
020gained producer-side stages,007gained a new producer-side task,017gained the criteria its own prose promised,014gained the artifact seam (plus a correction to its design doc),019gained the signing-client task the notify design already required, and018’sRO-1gained its P19 closure half. Each group’s landing place is recorded in a Authored line at the end of its subsection, and the criteria — not this document — are now the tickable record. What is authored is the gate, not the work: all 42 rows remain open edges.
Every row has an owner (§1). What 93 rows also have, and 42 do not, is a gate: a named stage whose completion criteria actually cover removing that edge. The Gate column above records this per row; the six defects behind the 42 are below. None of them is a missing disposition — the seam class is right in every case — so none needs an MBI-4 ruling. Each needs its owning sprint to restate the row in criteria a reviewer can tick.
Where a sunset names a recipe stage (S7/M2 gate, M gates, CM stages), the criteria are inherited and real: recipe S0 requires a “build-boundary disposition per foreign edge” (service-consolidation-recipe.md:23), S4 “Replace every keep-separate source edge per P19” (:52), S7 “Add P19 conformance for every service key: walk the complete ProjectReference closure” (:84, :88), S8 “the service key passes an isolated clean publish without compiling a foreign service” (:96); M0 covers “every project in each deployable key’s transitive source graph” (:138), M2 adds the key to P19 conformance (:154), M3 requires the isolated clean publish (:162). The 42 rows below are the ones whose sunset names something the recipe does not define, or names a task whose criteria are silent about the edge.
G1 — 020, 12 rows: the sunset is a database move, which cannot remove a source edge
All 12 rows sunset at a Wave-3 row (W3-01 timeline, W3-04 replay, W3-06 signals, W3-13 unknowns). Each of those rows is “M0–M5 per recipe Part B” plus a forcing function, and its six completion ticks are the M-gates. The M-gates govern the moving service’s own closure and forbid new pins; no M stage asks a producer to publish the seam that some other key’s closure needs. So nothing in 020 retires these 12. W3-04’s task text is a Replay.Persistence extraction; W3-06’s is the Signals schema move; neither mentions the consumers.
Decision owed (not an MBI-4 ruling — a task-authoring gap): name a producer-side seam stage for each of the four producer families, or reassign these rows to the consumer programs. Owner: 020 + each producer family.
Sub-finding, because 7 of the 12 are one library — and so is a row this program does not own. Eight pairs in total enter through src/__Libraries/StellaOps.Replay.Core (142 tracked .cs — the “149” first recorded here was a raw find that swept 7 build artifacts; corrected 2026-08-06), classified service-family:replay: the seven listed above plus platform → replay, which is 026-owned. (The first version of this sub-finding said “8 of the 12” and “all eight → replay rows”, conflating the carrier’s estate-wide count with 020’s share; corrected 2026-08-06, and the consequence is carried into the authored criteria — a seam that satisfies seven consumers and orphans 026’s is not done.) Its own graph is closed — StellaOps.Canonical.Json + StellaOps.Cryptography, both domain-neutral-shared — so it looks like the JOB-4 / FND-4 “classification gap, not a defect” shortcut, where a pure producer library was simply mis-classified and reclassifying it retired the pins. That shortcut is not available here and the reason matters: clause 3 withholds the contract-seam exemption from a producer’s domain core regardless of graph purity, and Replay.Core is Replay’s domain core, not a wire contract. The available paths are the FND-4 split (extract the wire/derive shapes consumers actually use, as Scanner.Reachability.Contracts was split out) or the DC-36 derive-on-demand API the register already records. DC-36’s owner column (design-challenges-register.md:45) reads “005/007 + 025 (bundle history) + Replay” — no task.
Authored 2026-08-06 — named per producer family, not reassigned. SPRINT_20260722_020 rows W3-01 (timeline ×2), W3-04 (replay ×7), W3-06 (signals ×2) and W3-13 (unknowns ×1) each gained a Producer-side P19 stage block: producer-side graph inventory (two-direction query per D-SCN1-11), target seam recorded with its reason, conformance, and isolated clean publish. The open question was answered by choosing the first branch — a consumer program cannot build a seam it does not own, and each of the four families already has a row an executor reads. Three specifics went into the criteria rather than being left implicit: W3-04 may not reclassify Replay.Core (clause 3, domain core) and must serve all eight consumers of that carrier including 026’s; W3-06 also owns the Signals.Persistence end of the clause-3 Runtime.Contracts carrier defect shared with 018; W3-01’s seam choice is the same decision as its undeployed-indexer-host disposition. The G1 class itself — the M-gates are consumer-blind by construction — is recorded in that sprint’s Decisions & Risks as a read-across for its remaining rows.
G2 — 007, 14 rows: the named P19 stage points the other way
The sunset is “007 POL gates + P19 stage”, i.e. POL-F3. POL-F3 is titled “Remove foreign service functionality from the Policy build” and its task text lists the edges to delete from StellaOps.Policy.Engine.csproj. That is Policy-as-consumer. All 14 rows here are Policy-as-producer (11 recorded against src/Policy/__Libraries/StellaOps.Policy, 2 against StellaOps.Policy.Exceptions, 1 against StellaOps.Policy.Determinization — all three are libraries under src/Policy/__Libraries/; an earlier version of this line said Policy.Engine for the third, which is wrong), spread across 10 consumer families. POL-F3’s own status line confirms the scope: its remaining items are EvidenceLocker.Core, Concelier.SbomIntegration, AirGap.Policy and the ProofSpine/Attestor classification — all inbound to Policy.
This is the identical directional blind spot 017 found in its own inventory and corrected as D-SCN1-11 (“It enumerated only pins where a scanner key is the consumer, and concluded ‘017 owns no pin’”). 007 has not had that correction.
Decision owed: 007 adds a producer-side stage — the classification/split call on StellaOps.Policy (the shared model library 10 consumer families compile) against the closed-contract test. Owner: 007.
Authored 2026-08-06 as a new task, POL-F5 — Policy-as-PRODUCER P19 stage. Deliberately not folded into POL-F3: adding producer rows to a task whose title and text are “remove foreign functionality from the Policy build” is how the directional blind spot survives. Criteria: producer-side inventory of all 14 (both directions), a recorded seam decision per carrier project, pins retired with -Check + BuildBoundaryConformanceTests, isolated clean publish for policy-engine plus two consumers (one of them scanner-web), and behaviour tests on each functional replacement. Measured while authoring, and now in the task: the 14 rows share a three-project carrier set (StellaOps.Policy, Policy.Determinization, StellaOps.Policy.RiskProfile); export-web/ export-worker add Policy.Exceptions; and scanner-web adds four more including StellaOps.Policy.Persistence, which no contract-seam classification can absorb, so that row needs a functional replacement rather than a reclassification.
G3 — 017, 9 rows: the handoff is in prose, not in criteria
SCN-1 (DONE) is the honest one here: it corrected itself to both directions, enumerated the nine producer-side pins, and stated “Sizing these nine down is 017’s P19 stage work and is not attempted at M0; SCN-3 carries it.” SCN-3’s completion criteria read, in full: “Doctor plugin (incl. db-size budget) registered; conformance + compose lint green for stellaops_scanner.” The nine pins appear in no criterion of the task said to carry them.
Decision owed: restate the nine as an SCN-3 criterion (or a new SCN task). Owner: 017.
Authored 2026-08-06 into SCN-3. The nine are enumerated in the task text with their entry projects and five criteria added: a recorded disposition per pin (two already have named answers to execute — Scanner.Contracts as the agreed closed seam for integrations-web/signals under D-INT4-2, and Scanner.ProofSpine as not reclassifiable because its closure enters Attestor.Core); a named plan for agent-core’s 39 projects, the largest producer-side set on the board and the one SCN-1 explicitly did not analyse; pins retired by simulating arc removal over the parsed csproj graph rather than reading shortestWitness (D-SCN1-12); and isolated clean publish for both scanner keys plus a repointed consumer. This family’s keep-separate citation was already present in all nine register rows (blast-radius, review §1) and needed nothing added.
G4 — 014, 3 rows: the fold retires the producer key, not the consumer’s closure
binaryindex/consolidation-design.md §3 states the fold “RETIRES both deployable keys, which deletes every pin row wholesale — this program is the largest single pin-count reducer on the board after 008.” Checked against the register: of 014’s four rows, one has a retiring key as the consumer (binaryindex-web → symbols) and dies exactly as described. The other three are scanner-web → binaryindex (6 projects), scanner-worker → binaryindex (16) and scanner-worker → symbols (1) — 23 measured foreign projects with scanner as consumer. Retiring the binaryindex-web/symbols hosts does not remove BinaryIndex.Decompiler, BinaryIndex.Core or Symbols.Core from Scanner’s closure; under DC-33 those libraries move into src/Vulnerabilities/, i.e. into another foreign family. The artifact seam the register records for all three (corpus artifact + hub API) is the real work, and it is not “wholesale”.
Correction owed to the design doc, and the three rows need the artifact seam gated in 014 (or in 017, whose SCN-1 already lists them as scanner-side consumption). Owner: 014.
Authored 2026-08-06 into BIN-5, and the design doc corrected in place. docs/modules/binaryindex/consolidation-design.md §3 now states the “wholesale” claim precisely — the fold deletes every row where a retiring key is the CONSUMER — and carries the three-row residue with its measured project sets. BIN-5 (the S5 API/seam task, already the home of the scanner-pipeline consumer contract) gained four criteria: the machine contract is the build-id index artifact, pinned by a format/version contract test (BIN-1 verified Scanner resolves through OfflineBuildIdIndex, an NDJSON file loader, not an HTTP API); Scanner’s closure loses all 23 projects including their post-fold homes under src/Vulnerabilities/— relocating a library into the hub family does not make a Scanner reference to it legal; all three pins retired with measured evidence, coordinated with 017 SCN-3; and scanner-worker (the 16-project set) passes an isolated clean publish. Capacity is the cited keep-separate test for all three (§9A).
G5 — 019, 2 rows: a composition root cannot become a contract
SGN-2’s criteria do cover most of 019’s surface — “Each shared library passes the P19 neutral-SDK closure check; no direct/transitive ProjectReference to any service-owned project” gates the StellaOps.Verdict impurity row, and the two rows entering through StellaOps.Signer.Core are the closed-contract seam that criterion describes. The two rows entering through StellaOps.Signer.Infrastructure(notify-web 3 projects, release-orchestrator 3) are different, and notify/consolidation-design.md §3 already ruled why: it is “a composition root re-hosted inside notify-web; a closed-contract seam cannot absorb it, so it must be replaced by the Signer service’s API or by an 019-published client that composes signing without the producer’s DI root. Recorded, not built, at NTF-4.” No 019 task carries that client/API.
Decision owed: 019 adds the signing-client/API task. Owner: 019. (The keep-separate test is cited and settled for this family: key custody, non-negotiable.)
Authored 2026-08-06 as a new task, SGN-7 — signing client/API for the two Signer.Infrastructure consumers. Criteria: the seam shape decided from the actual call sites; a standing conformance assertion that StellaOps.Signer.KeyManagement appears in no consumer closure; graph-purity evidence if a client project is the answer; both pins retired with -Check; and a signing round-trip through the new seam for both consumers plus isolated clean publish. One measurement sharpened the case while authoring and is now the task’s forcing argument: both rows’ 3-project set is Signer.Core + Signer.Infrastructure + StellaOps.Signer.KeyManagement— so two non-Signer deployables compile Signer’s key-management implementation today, which is exactly what the key-custody boundary exists to prevent. 019’s Decisions & Risks now records that the tightest grants in the estate do not help while that code sits in two other services’ builds.
G6 — 018, 2 rows (+1 undecided in §7): no design doc, no boundary criterion
018 has no S0 consolidation design (it is a database-separation sprint) and no task naming the build graph. RO-1 is titled “M0 inventory”, which inherits the M0 P19 classification by stage name, but neither its task text nor its single completion criterion (“Inventory + X9/agent-core verification in the log; sequencing decision recorded”) mentions the closure or the three pins; its agent-core verification item is about schema access, not source edges.
Decision owed: 018’s RO-1 restates the M0 P19 closure inventory explicitly, covering all three rows. Owner: 018.
Authored 2026-08-06 into RO-1. All three rows are enumerated in the task text with their measured project sets — release-orchestrator → agent-core (7: RO’s WebApi compiles every Agent.* transport), agent-core → release-orchestrator (9 RO domain projects via Agent.Host → Agent.Ansible), and scanner-web → release-orchestrator (1, the clause-3 Runtime.Contracts carrier) — with four criteria: a two-direction P19 closure inventory for both deployable keys, a recorded purify-or-split plan for Runtime.Contracts that does not depend on reclassifying it as-is, the cycle decision ruled or explicitly escalated with a date (§7 — not a third state), and conformance + isolated clean publish for whatever is ruled. The cycle itself is still not ruled here: MBI-4 declined it as an owner-level D14-class decision, and 018’s Decisions & Risks now carries the decision statement, the verified inputs and the notify-web ↔ notifier-worker precedent so the ruling has one home.
7. The genuinely undecided remainder
RULED AND EXECUTED 2026-08-09 — this section is now history, kept for the reasoning. The program owner ruled SEPARATE (“separate them, but make sure the branches of libraries that will be compiled are minimal — perhaps there are redundant dependencies or dependencies that could be optimized for removal”), so agent-core remains an independently deployable family and both directions got a seam.
SPRINT_20260722_018RO-7 built them and both rows are deleted from the register, together with two more the same work resolved (agent-core → integrations, owned by 024, andplatform → agent-core, owned by 026).-Checkis OK at 131 pairs, 0 unpinned / 0 stale / 0 grown;BuildBoundaryConformanceTests17/17.What the seams turned out to be — the two directions were not what the cost sketch below assumed:
- Direction B (agent → RO, 9 projects) resolved as the brief predicted: one closed contract project,
StellaOps.ReleaseOrchestrator.Agent.Contracts, classifiedcross-service-client-sdk:release-orchestratorwith a closure of domain-neutral foundations only.- Direction A (RO → agent, 7 projects) was NOT the “near-zero” seam the sketch described. Only four of the seven were the in-process adapters. The other three entered for reasons the brief did not see: two
using StellaOps.Agent.Core.Registrydirectives inDeploymentandDecisionArtifactthat were already dead (the types they named moved to the neutralStellaOps.Oci.Coreat ADR-041 R1); anIObjectStoreClientport that belonged inOci.Corebeside the content-store plane it serves; and the content-by-digest capability seal, an orchestrator-side wire shape that happened to live in the agent runtime.- Consequence for the shipping test the keep-separate citation owed: it is now moot for these two rows, because they no longer exist. The citation-owed notes are removed with the rows.
Measured effect:
release-orchestrator122 → 116 projects and 9 → 8 violation pairs;agent-core124 → 113 and 7 → 5. Both keys pass an isolated clean publish with the other’s source directory physically deleted. Evidence: 018RO-1criteria 4–5 and D-ROSEP-1…8.
One row’s disposition class is unsettled — and it is really one decision over two rows.
release-orchestrator → agent-core (7 projects, entry StellaOps.Agent.Compose) is the only row the register itself defers: "merge-vs-package disposition is an MBI-4 decision with 018", sunset "018 M gates + MBI-4 disposition". MBI-4 does not rule it, because the graph shows the row is half of a cycle and a cycle cannot coherently take two different dispositions:
| Direction | Entry project | Measured | Register disposition |
|---|---|---|---|
release-orchestrator → agent-core | StellaOps.Agent.Compose | 7 | package/closed-SDK — deferred to MBI-4 |
agent-core → release-orchestrator | StellaOps.ReleaseOrchestrator.Agent | 9 | owner-API seam (018) |
If agent-core stays a separate family both directions need seams; if the lifecycle is inseparable both die by consolidation. Either way the two rows resolve together.
Decision statement. Does agent-core remain an independently deployable family, given that it shares one source tree with release-orchestrator and the two compile each other in both directions — or do the deploy-agent projects fold into the RO family (clause 1), leaving the on-host agent as a published artifact of that family?
Verified inputs for whoever rules it, offered without a recommendation:
- The two families already share a tree.
ownership-manifest.jsonclassifiessrc/ReleaseOrchestratorasservice-family:release-orchestratorand the nestedsrc/ReleaseOrchestrator/__Agentsasservice-family:agent-core— a segment-aware nested split, not two directories. StellaOps.Agent.Compose(20.cs) references onlyStellaOps.Agent.Core, so the RO→agent direction is shallow.StellaOps.ReleaseOrchestrator.Agent(53.cs) referencesStellaOps.Infrastructure.PostgresandStellaOps.Cryptography, so the agent host’s build reaches RO-side persistence infrastructure — the deeper direction.- Clause 1 (merge) is supported by the shared tree and the bidirectional compilation. Clause 2 (keep separate) has one candidate test available: shipping — the deploy agent installs on customer hosts, a distribution boundary the control plane does not cross. That test is not cited in either row’s
reasontoday; as of 2026-08-06 both rows carry an explicit citation-owed note saying so (§9A) rather than an implied test. - The review already writes the pair as one unit.
service-consolidation-review.md§2’s “Keep standalone” row reads “…policy-engine, release-orchestrator(+agent-core), integrations…” — the same parenthetical form it uses for role groupings elsewhere in that row (scanner(+worker/cache-init)). Recorded as a verified input, not as a ruling: it shows the estate’s accepted-direction document treating agent-core as part of the RO unit, which is evidence about the intended shape and not a decision about the two register rows. - What each direction actually compiles, measured 2026-08-06 (relevant because a cycle between two contract surfaces would be a different problem than a cycle between two implementations): RO→agent is
Agent.Compose,.Core,.Docker,.Registry,.Secrets,.Ssh,.WinRM— every deploy transport; agent→RO isReleaseOrchestrator.Agent,.DecisionArtifact,.Deployment,.Environment,.IntegrationHub,.Plugin,.Promotion,.Release,.Scripts. Neither direction is contract-shaped. - Precedent, not authority:
notify-web ↔ notifier-workeris the estate’s other cyclic host pair, and it was ruled merge under ADR-039 D14 (notify/consolidation-design.md§3: “the web host compiles the WORKER HOST project and vice versa — not a client seam”).
Owner: SPRINT_20260722_018 + program-owner ratification (D14 is an owner-level ruling, so a merge answer here needs the same).
Also flagged, not undecided
- A live clause-3 anti-pattern on 018’s third row.
scanner-web → release-orchestrator(1 project,api) enters throughsrc/ReleaseOrchestrator/__Libraries/StellaOps.Runtime.Contracts— a project named.Contractswhose own csproj referencessrc/Signals/__Libraries/StellaOps.Signals.Persistence, another service’s persistence library. Clause 3 names this case exactly (“Do not relabel a foreign source project.Contracts… whose graph still reaches them”). Consequence for planning: this row cannot be closed by classifying the carrier as a closed SDK; the carrier must be purified or split first. Recorded for 018 (carrier owner) and 020 (Signals.Persistenceowner). - The register’s one shrink-only exception is unratified.
notifier-worker → notifywas raised 5 → 6 at NTF-4 and the register says so: “PENDING OWNER RATIFICATION — the only shrink-only exception in the register.” MBI-4 records the row as assigned (015, dies-by-consolidation, retires with the deployable key at NTF-8); assigning it is not ratifying the count increase, which remains owed to the program owner.
8. Register hygiene for the register owner
No dead pins and no growth: every one of the 135 pairs is present in the measured graph, and no pin understates its measured count. What the register does carry is unratcheted slack — 16 rows whose recorded count exceeds the measured count by 30 project-units in total, left behind when peer sprints resolved projects without ratcheting the pin. This is the residue 017’s SCN-1 log flagged on 2026-08-05 as “32 further stale counts / 77 slack units”; most has since been ratcheted, and the current measurement is:
| Owning sprint | Rows with slack | Slack units |
|---|---|---|
003 | 9 | 9 |
026 | 3 | 5 |
007 | 2 | 12 |
010 | 1 | 3 |
019 (libraryImpurityPins: 18 recorded / 17 measured) | 1 | 1 |
| Total | 16 | 30 |
Largest single gaps: export-web → policy and export-worker → policy (10 recorded / 4 measured each — the OK-4 Policy-edge deletion), findings-ledger-web → attestor and platform → attestor (4/1 and 6/3). Ratcheting is the register owner’s call and is deliberately not done here, and -Check treats slack as passing, so nothing is blocked by it.
What MBI-4 did write to the register, precisely (2026-08-06). Nothing the report reads: no pin added or deleted, no foreignProjectCount ratcheted, no shortestWitness corrected, no targetSeam, owningSprint or sunset changed. The authoring pass appended the criterion-3 keep-separate citations (§9A) to the reason text of exactly 21 rows — 21 changed lines, verified by git diff --stat — and generate-build-boundary-report.ps1 -Check came back OK with the same 135 / 0 / 0 / 0 and the generated report byte-unchanged, which is the documented behaviour for a pure text edit (017 D-SCN1-12 finding 5: only pin addition or deletion moves the report).
9. MBI-4 criteria state
State as of the 2026-08-06 authoring pass. Criteria 1 and 2 hold; criterion 3 has a two-row remainder that cannot be closed by authoring.
| Criterion | State | Evidence |
|---|---|---|
| 100% of pins assigned; no generic “later cleanup” or ownerless bucket | met | 136/136 rows carry an owning program and a sunset (§1, §4). No row resolves to “later” or to this program: MBI-4 authored no new bucket, and the single deferred disposition (§7) is owned by 018 with the decision stated. |
| Every owner sprint includes graph inventory, target seam, conformance, and isolated publish criteria through the MASTER/recipe gates | met (2026-08-06) | 93/136 rows were gated by a recipe stage or an explicit task criterion; the remaining 42 across 6 programs are now authored into their owner sprints — 020 W3-01/W3-04/W3-06/W3-13 (12), new task 007 POL-F5 (14), 017 SCN-3 (9), 014 BIN-5 (3), new task 019 SGN-7 (2), 018 RO-1 (2). Each block carries all four elements the criterion names — producer/consumer-side graph inventory, a recorded target seam, -Check + BuildBoundaryConformanceTests conformance, and isolated clean publish — with named suites and checks, never prose. Landing places and what went into each are in the Authored lines of §6 G1–G6. This certifies the gates, not the work: all 42 edges remain open. |
| Consolidation decisions state why merge beats a manufactured seam; keep-separate decisions cite the security/capacity/shipping test | met (2026-08-09) | Merge half unchanged and met: all 21 merge rows cite an owner-approved consolidation (D14 ×6, owner-widened Evidence ×4, EVL-3 AirGap ×3, X15/DC-26 mechanism deletion ×8), with the merge-over-seam argument carried in the family design docs (attestor §4 availability argument, notify §3 cyclic pair, export-center §4 “die by the split itself”). Keep-separate half: the test was cited for 13 of 114 rows (Signer key-custody 4, Scanner blast-radius 9); the 21 rows where merge was a live option are now resolved row-by-row in §9A and written into the register reason — 10 cite a test with a source, 9 are test-not-applicable with the reason recorded, and 2 remain owed: release-orchestrator ↔ agent-core, where shipping is the only candidate test and the disposition itself is unruled (§7). |
9A. Keep-separate citations — the 21 rows where merge was a live option
Authored 2026-08-06 into the register reason of each row named below (§8 records exactly what was written). The other 93 keep-separate rows are unaffected: 13 already carried a cited test (Signer key-custody ×4, Scanner blast-radius ×9) and 80 sit in families where merge was never on the table.
Three outcomes are used, and the difference between the second and third is the point of the exercise: cited — a named test with a document that argues it; not applicable — the row is a keep-separate by default because decision-test clause 1’s antecedent fails (no shared domain lifecycle, or the consumer key is retiring), stated with its source; owed — merge was genuinely live, no argument exists in the design record, and none is invented here.
| Rows | Owner | Test | Basis |
|---|---|---|---|
scanner-web/scanner-worker → binaryindex, scanner-worker → symbols (3) | 014 | capacity — cited | binaryindex/consolidation-design.md §9.1 binary-plane budget; round-18 DC-30 keeps fingerprint corpora out of the compact artifact because they “would multiply it”; binary analysis runs hub-side. Measured graph agrees: scanner-worker’s 16 projects are a Ghidra/ML/disassembly/persistence stack, not a contract surface. |
scanner-web/scanner-worker → signals (2) | 020 | capacity — cited | service-consolidation-review.md §2.2 item 3 (EVL-2): “signals stays separate (runtime fact ingest, eBPF-adjacent, different scaling profile)”. |
export-web/export-worker → timeline (2) | 020 | capacity/storage — cited | Review §2 keep-standalone row (storage boundary) + §3 hygiene table: timeline.unified_audit_events 197 MB, append-only, unpartitioned — partitioned by 020 W3-01. |
scanner-web → authority (1) | 016 | security — cited | Review §1 names Authority as the token-issuance/identity-root example. (Live nuance, D-SCN1-5: Scanner’s use is type-only — the registered implementation is NullOfflineKitAuditEmitter — so the edge is vestigial and on the estate’s tightest boundary.) |
scanner-worker → reachgraph (1) | 023 | capacity/blast-radius — cited, and the merge option is foreclosed | Review §1 names Scanner as the bulk plane; the merge question was already ruled the other way — reachgraph consolidates into the Graph family (owner 2026-07-22, review §2.2 item 3). The runtime seam already exists (POST /v1/reachgraphs, X20, preserved verbatim per graph/consolidation-design.md §3), so the source edge is residue. |
scanner-web → release-orchestrator (1) | 018 | capacity/blast-radius — cited | Bulk scan plane vs promotion control plane: review §1 names Scanner, §2 keep-standalone lists both. Carries a caveat, not a discount: the carrier StellaOps.Runtime.Contracts is a live clause-3 anti-pattern (it references StellaOps.Signals.Persistence), so the row cannot be closed by classifying it as a closed SDK. |
* → replay (7: agent-core, concelier, policy-engine, release-orchestrator, scanner-web, scanner-worker, timeline-web) | 020 | not applicable | Merge was never live — seven unrelated consumer families, no shared domain lifecycle with any of them, so clause 1’s antecedent fails. What was live is neutral-SDK reclassification of Replay.Core, refused under clause 3 (§6 G1). Owner-confirmed keep-separate: EVL-3, review §2.3 item 3 (“deterministic replay verification is its own concern”) — a domain-separation argument, which is why no security/capacity/shipping test is claimed. |
scanner-worker → unknowns (1) | 020 | none cited | The review’s §2 keep-standalone row asserts the boundary without naming a test, and the unknowns schema is not yet live, so neither capacity nor security is demonstrable today. Merge was not live either (no shared lifecycle with either consumer). Recorded as standing on that row alone — deliberately not dressed as a test. |
doctor-web → scheduler (1) | 012 | not applicable | The consumer deployable is owner-decided to retire (review §2 Doctor row + §2.2 item 4, executed by 009 DOC-5; 020 W3-10 makes its own row disposition-only if so). No boundary between two surviving services is being asserted; the surviving Doctor module is SDK + CLI submitting through the DC-13 /jobs API. |
release-orchestrator ↔ agent-core (2) | 018 | OWED | Shipping is the only candidate test (the deploy agent installs on customer hosts — a distribution boundary the control plane does not cross) and it is cited nowhere. The disposition itself is unruled: the two rows are one decision over a cycle (§7), needing owner ratification as ADR-039 D14 did. Naming the test now would pre-empt that ruling, so both rows carry an explicit citation-owed note instead. This is criterion 3’s entire remainder. |
Read-across for whoever rules the last two, and for future dispositions: “merge was a live option” is itself a claim that has to be checked per row, not per program. Of the 21, only 014’s three and 018’s cycle had a real merge alternative; the replay, unknowns, timeline and doctor rows failed clause 1’s antecedent, and 023’s had already been ruled elsewhere. Grouping them by owning sprint (as the first version of criterion 3 did) overstated the debt by about half.
