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.json and lives at legacy-edge-dispositions.md; this file kept its content and moved to a dated name (team-lead ruling 2026-08-17, SPRINT_20260730_001 Decisions & 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 run pwsh 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_001 MBI-4 on 2026-08-06 against the working tree at that date. This document does not invent dispositions. The register already carries targetSeam, owningSprint and sunset per 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-7 deleted the release-orchestrator ↔ agent-core cycle rows plus agent-core → integrations and platform → agent-core; other programs shrank further rows). Nothing in this document is rewritten — it is the MBI-4 evidence as compiled. For current state read legacy-edge-register.json and the MBI-5 burn-down census in docs-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 under IncludeBuildDockerReachability and 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 four advisory-ai-*|attestor/|concelier and findings-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 — across platform, unknowns, policy, replay, signals, scanner, integrations, findings-ledger and agent-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 -Check

A 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 → scanner and signals → scanner, both recorded here as owner-API seam carried by StellaOps.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), and SPRINT_20260730_001 MBI-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 with src/Scanner deleted. 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-locker pins that ran through StellaOps.Findings.Ledger by repointing one ProjectReference at 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:

InputRole 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.mdthe 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.mdwhere 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 checkResult
Register pins135
Measured violation pairs in the report135
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 projects1 / 1
Rows in this document136 = 135 + 1
Rows with an owning program136 / 136
Rows with a disposition class135 / 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 targetSeamDisposition classDecision-test clauseMeaning
mergedies-by-consolidation1one domain lifecycle, or the mechanism holding the edge is deleted — no runtime seam is manufactured
apiowner-API seam2keep separate; consumer calls the owner’s API
eventevent seam2keep separate; consumer subscribes to a versioned event
artifactartifact seam2keep separate; consumer reads a content-addressed artifact
published-packagepackage/closed-SDK seam2keep separate; producer publishes an exact-version package, or a producer-owned closed client/contract source project whose graph conformance proves it implementation-free
neutral-sdkneutral-SDK purification4a 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 classDecision-test clauseRowsForeign-project units
owner-API seam256184
package/closed-SDK seam249256
dies-by-consolidation12147
artifact seam2638
event seam235
neutral-SDK purification4117
Total136547

Totals by owning program

Owning sprintFamily surfaceRowsmergeapieventartifactpkg/SDKneutralGate state
026Platform central-migrator retirement (consumer-side fan-in)27811125gated 27
011Evidence family merge (attestor + evidence-locker)21417gated 21
003Vulnerabilities hub (concelier/excititor producers)2020gated 20
007Policy producer surface1414thin 14
020Wave-3 database moves (replay/timeline/signals/unknowns producers)12102thin 12
017Scanner producer surface99thin 9
015Notify + Notifier consolidation66gated 6
019Signer (key-custody keep-separate)541gated 3, thin 2
024Integrations + registry-token consolidation55gated 5
010Findings consolidation431gated 4
014BinaryIndex/Symbols dissolution into the hub (DC-33)44gated 1, thin 3
018ReleaseOrchestrator / agent-core boundary321thin 2, undecided 1
025OfflineKit / AirGap split33gated 3
012JobEngine consolidation (scheduler producer)11gated 1
016Authority tenancy/idp/issuer consolidation11gated 1
023Graph + ReachGraph consolidation11gated 1
Total136215636491

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
platformagent-core1package/closed-SDK seamStellaOps.Agent.Core026 CM stagesgated
platformairgap-controller3dies-by-consolidationStellaOps.AirGap.Persistence026 CM stagesgated
platformairgap-time1dies-by-consolidationStellaOps.AirGap.Time026 CM stagesgated
platformattestor3 (pin says 6)package/closed-SDK seamStellaOps.Attestor.Envelope026 CM stagesgated
platformauthority2package/closed-SDK seamStellaOps.Authority.Persistence026 CM stagesgated
platformbinaryindex13artifact seamStellaOps.BinaryIndex.GoldenSet026 CM stagesgated
platformconcelier10 (pin says 11)package/closed-SDK seamStellaOps.Concelier.SbomIntegration026 CM stagesgated
platformevidence-locker7 (pin says 8)dies-by-consolidationStellaOps.Artifact.Infrastructure026 CM stagesgated
platformexport-center2dies-by-consolidationStellaOps.ExportCenter.Infrastructure026 CM stagesgated
platformgraph2dies-by-consolidationStellaOps.Graph.Indexer.Persistence026 CM stagesgated
platformintegrations3owner-API seamStellaOps.Integrations.Persistence026 CM stagesgated
platformissuer-directory2dies-by-consolidationStellaOps.IssuerDirectory.Persistence026 CM stagesgated
platformnotify3dies-by-consolidationStellaOps.Notify.Persistence026 CM stagesgated
platformpacksregistry2dies-by-consolidationStellaOps.PacksRegistry.Persistence026 CM stagesgated
platformpolicy6owner-API seamStellaOps.Policy.Persistence026 CM stagesgated
platformreachgraph2owner-API seamStellaOps.ReachGraph.Persistence026 CM stagesgated
platformrelease-orchestrator11owner-API seamStellaOps.ReleaseOrchestrator.Environment026 CM stagesgated
platformremediation2owner-API seamStellaOps.Remediation.Persistence026 CM stagesgated
platformreplay1owner-API seamStellaOps.Replay.Core026 CM stagesgated
platformsbomservice1owner-API seamStellaOps.SbomService.Lineage026 CM stagesgated
platformscanner1owner-API seamStellaOps.Scanner.ChangeTrace026 CM stagesgated
platformscheduler2owner-API seamStellaOps.Scheduler.Persistence026 CM stagesgated
platformsigner3package/closed-SDK seamStellaOps.Signer.KeyManagement026 CM stagesgated
platformsymbols1artifact seamStellaOps.Symbols.Core026 CM stagesgated
platformtimeline3event seamStellaOps.Timeline.Core026 CM stagesgated
platformunknowns2owner-API seamStellaOps.Unknowns.Persistence026 CM stagesgated
platformworkflow3owner-API seamStellaOps.Workflow.DataStore.PostgreSQL026 CM stagesgated

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
advisory-ai-webattestorPIN 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-workerattestorPIN DELETED 2026-08-17: same resolver, b30a1f2cbc.resolved
agent-coreattestor3package/closed-SDK seamStellaOps.Attestor.GraphRoot011 S7/M2 gategated
attestor-tileproxyattestor2package/closed-SDK seamStellaOps.Attestor.TrustRepo011 S7/M2 gategated
binaryindex-webattestor2package/closed-SDK seamStellaOps.Attestor.StandardPredicates011 S7/M2 gategated
concelierattestor2package/closed-SDK seamStellaOps.Attestor.ProofChain011 S7/M2 gategated
evidence-locker-webattestor3package/closed-SDK seamStellaOps.Attestor.CapsuleProjection011 S7/M2 gategated
excititor-webattestor2package/closed-SDK seamStellaOps.Attestor.StandardPredicates011 S7/M2 gategated
excititor-workerattestor2package/closed-SDK seamStellaOps.Attestor.StandardPredicates011 S7/M2 gategated
export-webattestor1package/closed-SDK seamStellaOps.Attestor.ProofChain011 S7/M2 gategated
export-workerattestor1package/closed-SDK seamStellaOps.Attestor.ProofChain011 S7/M2 gategated
policy-engineattestor3package/closed-SDK seamStellaOps.Attestor.GraphRoot011 S7/M2 gategated
release-orchestratorattestor3package/closed-SDK seamStellaOps.Attestor.Core011 S7/M2 gategated
sbomserviceattestor2package/closed-SDK seamStellaOps.Attestor.StandardPredicates011 S7/M2 gategated
scanner-webattestor3package/closed-SDK seamStellaOps.Attestor.Core011 S7/M2 gategated
scanner-workerattestor3package/closed-SDK seamStellaOps.Attestor.Core011 S7/M2 gategated
unknowns-webattestor1package/closed-SDK seamStellaOps.Attestor.ProofChain011 S7/M2 gategated
advisory-ai-webevidence-locker1dies-by-consolidationStellaOps.Evidence.Pack011 S7/M2 gategated
advisory-ai-workerevidence-locker1dies-by-consolidationStellaOps.Evidence.Pack011 S7/M2 gategated

Two evidence-locker rows RETIRED 2026-08-10 (SPRINT_20260722_010), ahead of the 011 gate they were waiting onfindings-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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
advisory-ai-webconcelierPIN 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-workerconcelierPIN DELETED 2026-08-17: same resolver, 3419d519df.resolved
agent-coreconcelier10 (pin says 11)package/closed-SDK seamStellaOps.Concelier.SbomIntegration003 VULN-B1 contract-package stage + consumer repointgated
attestorconcelier2 (pin says 3)package/closed-SDK seamStellaOps.Concelier.SourceIntel003 VULN-B1 contract-package stage + consumer repointgated
binaryindex-webconcelier15package/closed-SDK seamStellaOps.Concelier.Connector.Common003 VULN-B1 contract-package stage + consumer repointgated
excititor-webconcelier15package/closed-SDK seamStellaOps.Concelier.Connector.Common003 VULN-B1 contract-package stage + consumer repointgated
excititor-workerconcelier15package/closed-SDK seamStellaOps.Concelier.Connector.Common003 VULN-B1 contract-package stage + consumer repointgated
export-webconcelier10 (pin says 11)package/closed-SDK seamStellaOps.VulnMatch.Core003 VULN-B1 contract-package stage + consumer repointgated
export-workerconcelier10 (pin says 11)package/closed-SDK seamStellaOps.VulnMatch.Core003 VULN-B1 contract-package stage + consumer repointgated
findings-vulncorrelationconcelierPIN 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-webconcelier1package/closed-SDK seamStellaOps.VulnMatch.Core003 VULN-B1 contract-package stage + consumer repointgated
policy-engineconcelier10 (pin says 11)package/closed-SDK seamStellaOps.VulnMatch.Core003 VULN-B1 contract-package stage + consumer repointgated
release-orchestratorconcelier10 (pin says 11)package/closed-SDK seamStellaOps.Concelier.SbomIntegration003 VULN-B1 contract-package stage + consumer repointgated
sbomserviceconcelier15package/closed-SDK seamStellaOps.Concelier.Connector.Common003 VULN-B1 contract-package stage + consumer repointgated
scanner-webconcelier10package/closed-SDK seamStellaOps.Concelier.Core003 VULN-B1 contract-package stage + consumer repointgated
scanner-workerconcelier11package/closed-SDK seamStellaOps.Concelier.Core003 VULN-B1 contract-package stage + consumer repointgated
unknowns-webconcelier10 (pin says 11)package/closed-SDK seamStellaOps.Concelier.SbomIntegration003 VULN-B1 contract-package stage + consumer repointgated
binaryindex-webexcititor1package/closed-SDK seamStellaOps.Excititor.Core003 VULN-B1 contract-package stage + consumer repointgated
concelierexcititor1package/closed-SDK seamStellaOps.Excititor.Core003 VULN-B1 contract-package stage + consumer repointgated
sbomserviceexcititor2package/closed-SDK seamStellaOps.Excititor.Persistence003 VULN-B1 contract-package stage + consumer repointgated

SPRINT_20260722_007 — Policy producer surface (14 rows)

Authority: SPRINT_20260722_007 POL-F3. Dies at: 007 POL gates + P19 stage.

Consumer keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
advisory-ai-webpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
advisory-ai-workerpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
agent-corepolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
binaryindex-webpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
concelierpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
excititor-webpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
excititor-workerpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
export-webpolicy4 (pin says 10)owner-API seamStellaOps.Policy.Exceptions007 POL gates + P19 stagethin
export-workerpolicy4 (pin says 10)owner-API seamStellaOps.Policy.Exceptions007 POL gates + P19 stagethin
release-orchestratorpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
sbomservicepolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
scanner-webpolicy7owner-API seamStellaOps.Policy.Determinization007 POL gates + P19 stagethin
scanner-workerpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin
unknowns-webpolicy3owner-API seamStellaOps.Policy007 POL gates + P19 stagethin

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
agent-corereplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
concelierreplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
policy-enginereplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
release-orchestratorreplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
scanner-webreplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
scanner-workerreplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
timeline-webreplay1owner-API seamStellaOps.Replay.Core020 W3 rowthin
scanner-websignals2owner-API seamStellaOps.Signals.Ebpf020 W3-06 producer gatethin
scanner-workersignals1owner-API seamStellaOps.Signals.Ebpf020 W3-06 producer gatethin
export-webtimeline1event seamStellaOps.TimelineIndexer.Core020 W3 rowthin
export-workertimeline1event seamStellaOps.TimelineIndexer.Core020 W3 rowthin
scanner-workerunknowns1owner-API seamStellaOps.Unknowns.Core020 W3 rowthin

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
agent-corescanner39owner-API seamStellaOps.Scanner.Sbom.BuildTime017 S0 inventory + P19 stagethin
export-webscanner1owner-API seamStellaOps.Scanner.ChangeTrace017 S0 inventory + P19 stagethin
export-workerscanner1owner-API seamStellaOps.Scanner.ChangeTrace017 S0 inventory + P19 stagethin
findings-vulncorrelationscanner1owner-API seamStellaOps.Scanner.Surface.FS017 S0 inventory + P19 stagethin
integrations-webscanner1owner-API seamStellaOps.Scanner.Contracts017 S0 inventory + P19 stagethin
policy-enginescanner1owner-API seamStellaOps.Scanner.ProofSpine017 S0 inventory + P19 stagethin
release-orchestratorscanner15owner-API seamStellaOps.Scanner.Storage.Oci017 S0 inventory + P19 stagethin
scheduler-webscanner2owner-API seamStellaOps.Scanner.Surface.Env017 S0 inventory + P19 stagethin
signalsscanner1owner-API seamStellaOps.Scanner.Contracts017 S0 inventory + P19 stagethin

SPRINT_20260722_015 — Notify + Notifier consolidation (6 rows)

Authority: notify/consolidation-design.md §3. Dies at: 015 S7/M2 gate.

Consumer keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
export-webnotify4dies-by-consolidationStellaOps.Notify.Connectors.Dora015 S7/M2 gategated
export-workernotify4dies-by-consolidationStellaOps.Notify.Connectors.Dora015 S7/M2 gategated
notifier-workernotify6dies-by-consolidationStellaOps.Notify.Delivery015 S7/M2 gategated — shrink-only exception pending owner ratification
release-orchestratornotify1dies-by-consolidationStellaOps.Notify.Models015 S7/M2 gategated
scanner-webnotify1dies-by-consolidationStellaOps.Notify.Models015 S7/M2 gategated
scheduler-webnotify2dies-by-consolidationStellaOps.Notify.Models015 S7/M2 gategated

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
(library) StellaOps.Verdictpolicy/concelier/attestor/signer/replay17 (pin says 18)neutral-SDK purification019 P4 shared-lib stagegated — SGN-2 criterion: P19 neutral-SDK closure check
export-websigner1package/closed-SDK seamStellaOps.Signer.Core019 P4 shared-lib stagegated — Signer.Core closed-contract seam under SGN-2
export-workersigner1package/closed-SDK seamStellaOps.Signer.Core019 P4 shared-lib stagegated — Signer.Core closed-contract seam under SGN-2
notify-websigner3package/closed-SDK seamStellaOps.Signer.Infrastructure019 P4 shared-lib stagethin — Signer.Infrastructure is a composition root; SGN-2 covers shared libs only
release-orchestratorsigner3package/closed-SDK seamStellaOps.Signer.Infrastructure019 P4 shared-lib stagethin — 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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
agent-coreintegrations2owner-API seamStellaOps.Integrations.Contracts024 S7/M2 gategated
release-orchestratorintegrations3owner-API seamStellaOps.Integrations.Contracts024 S7/M2 gategated
sbomserviceintegrations3owner-API seamStellaOps.Integrations.Contracts024 S7/M2 gategated
scanner-webintegrations2owner-API seamStellaOps.Integrations.Contracts024 S7/M2 gategated
scanner-workerintegrations2owner-API seamStellaOps.Integrations.Contracts024 S7/M2 gategated

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
export-webfindings-ledger1owner-API seamStellaOps.Findings.DoraRoi010 S7/M2 gategated
export-workerfindings-ledger1owner-API seamStellaOps.Findings.DoraRoi010 S7/M2 gategated
notify-webfindings-ledger1owner-API seamStellaOps.Findings.Ledger010 S7/M2 gategated

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
scanner-webbinaryindexPIN 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-workerbinaryindex3 (was 16)artifact seamStellaOps.BinaryIndex.ML014 dissolution gatesthin — 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-websymbolsPIN 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-workersymbolsPIN 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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
release-orchestratoragent-core7package/closed-SDK seamStellaOps.Agent.Compose018 M gates + MBI-4 dispositionundecided — disposition itself unsettled (merge vs package)
agent-corerelease-orchestrator9owner-API seamStellaOps.ReleaseOrchestrator.Agent018 M gatesthin
scanner-webrelease-orchestrator1owner-API seamStellaOps.Runtime.Contracts018 M gatesthin

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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
airgap-timeairgap-controller1dies-by-consolidationStellaOps.AirGap.Importer025 S7/M2 gategated
scanner-webairgap-controller1dies-by-consolidationStellaOps.AirGap.Importer025 S7/M2 gategated
airgap-controllerairgap-time1dies-by-consolidationStellaOps.AirGap.Time025 S7/M2 gategated

SPRINT_20260722_012 — JobEngine consolidation (scheduler producer) (1 rows)

Authority: jobengine/consolidation-design.md §5.1. Dies at: 012 S7/M2 gate.

Consumer keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
doctor-webschedulerPIN 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 keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
scanner-webauthority2package/closed-SDK seamStellaOps.Authority.Persistence016 S7/M2 gategated

SPRINT_20260722_023 — Graph + ReachGraph consolidation (1 rows)

Authority: graph/consolidation-design.md §4. Dies at: 023 S7/M2 gate.

Consumer keyProducer familyForeign projectsDisposition classEntry project (carrier)Dies atGate
scanner-workerreachgraph1owner-API seamStellaOps.ReachGraph023 S7/M2 gategated

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.

PinsEntry (carrier) projectOwning sprint(s)
11StellaOps.Policy007
8StellaOps.Replay.Core020, 026
6StellaOps.Attestor.ProofChain011
5StellaOps.Concelier.SbomIntegration003, 026
5StellaOps.Integrations.Contracts024
4StellaOps.Concelier.Core003
4StellaOps.Attestor.StandardPredicates011
4StellaOps.Concelier.Connector.Common003
4StellaOps.VulnMatch.Core003
4StellaOps.Attestor.Core010, 011
3StellaOps.Symbols.Core014, 026
3StellaOps.Scanner.ChangeTrace017, 026
3StellaOps.Notify.Models015

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 020 gained producer-side stages, 007 gained a new producer-side task, 017 gained the criteria its own prose promised, 014 gained the artifact seam (plus a correction to its design doc), 019 gained the signing-client task the notify design already required, and 018’s RO-1 gained 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_018 RO-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, and platform → agent-core, owned by 026). -Check is OK at 131 pairs, 0 unpinned / 0 stale / 0 grown; BuildBoundaryConformanceTests 17/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, classified cross-service-client-sdk:release-orchestrator with 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.Registry directives in Deployment and DecisionArtifact that were already dead (the types they named moved to the neutral StellaOps.Oci.Core at ADR-041 R1); an IObjectStoreClient port that belonged in Oci.Core beside 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-orchestrator 122 → 116 projects and 9 → 8 violation pairs; agent-core 124 → 113 and 7 → 5. Both keys pass an isolated clean publish with the other’s source directory physically deleted. Evidence: 018 RO-1 criteria 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:

DirectionEntry projectMeasuredRegister disposition
release-orchestrator → agent-coreStellaOps.Agent.Compose7package/closed-SDK — deferred to MBI-4
agent-core → release-orchestratorStellaOps.ReleaseOrchestrator.Agent9owner-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:

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

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 sprintRows with slackSlack units
00399
02635
007212
01013
019 (libraryImpurityPins: 18 recorded / 17 measured)11
Total1630

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.

CriterionStateEvidence
100% of pins assigned; no generic “later cleanup” or ownerless bucketmet136/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 gatesmet (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 testmet (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 reason10 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). Those two cannot be closed by authoring — inventing a citation is what this criterion exists to prevent — so they wait on 018’s ruling plus owner ratification. CLOSED 2026-08-09: the owner ruled the cycle SEPARATE and 018 RO-7 built both seams, so the two rows are deleted from the register rather than cited. A citation was never written for them, which is the right outcome — the criterion exists to stop an invented one, and the edges are gone.

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.

RowsOwnerTestBasis
scanner-web/scanner-worker → binaryindex, scanner-worker → symbols (3)014capacity — citedbinaryindex/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)020capacity — citedservice-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)020capacity/storage — citedReview §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)016security — citedReview §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)023capacity/blast-radius — cited, and the merge option is foreclosedReview §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)018capacity/blast-radius — citedBulk 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)020not applicableMerge 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)020none citedThe 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)012not applicableThe 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)018OWEDShipping 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.