[Body narrowed 2026-08-24 after current-source drift intake.] PR #17086 / #17065 delivered the restart deadline, post-dispatch uncertain classification, pre-POST durable interlock, cross-action redispatch guard, and positive StartedAt settlement. Do not reimplement that scope. The operative contract below owns only the planned-restart detector residual that remains after those changes.
Context
This is the surviving completeness half of the follow-up from PR #16974.
PR #16974 moved planned-restart subtraction onto the recovery-run ledger and made detector health visible. PR #17086 later added an append-only, retention-exempt restart interlock before Docker dispatch and reconciled uncertain outcomes before a second restart-bearing action can run.
Those changes improved the producer. The planned-restart consumer still treats two incomplete reads as a complete zero.
Exact current-source falsifiers at origin/dev af8294420ad139ea79a849e7d8b8aa2aaa979696:
{
"corrupt": {"count":0,"reason":null,"status":"available"},
"uncertain": {"count":0,"reason":null,"status":"available"}
}
corrupt: a retained .jsonl file contains no decodable row; readRecentRecoveryRunStates() silently skips it and collectPlannedRestarts() certifies an available zero.
uncertain: the read contains a within-window active-effect-interlock / effectDisposition: uncertain entry but no positive lifecycle proof; collectPlannedRestarts() filters it out and certifies an available zero.
Current Problem
1. The reader exposes entries, not read completeness
readRecentRecoveryRunStates() is intentionally tolerant for general diagnostic listing: it takes the latest valid row per file and skips files with no valid row. That is not enough for a consumer claiming the planned-restart window is complete. A corrupt or empty retained artifact may be the exact proof the detector needs to subtract.
A genuinely empty directory is different and must remain an available zero.
2. Active uncertainty is invisible to planned-restart health
PR #17086 correctly writes pending / reobserve-requested entries with effectDisposition: uncertain and retentionClass: active-effect-interlock. They prevent duplicate actuator dispatch. The bridge's planned-restart count reads the same store but looks only for a positive details.runtimeAccess restart proof, so an unresolved possibly-applied restart still becomes a certified zero.
The correct detector result is degraded/unavailable until positive settlement establishes whether the restart belongs in the subtraction window.
3. The producer→store→bridge composition is still reconstructed
The bridge specs drive the real low-level Docker restart proof and production DTO constructors, then inject an in-memory recovery-run reader. They do not execute:
RecoveryActuatorService
→ appendRecoveryRunState
→ readRecentRecoveryRunStates / completeness projection
→ DeploymentStateBridgeService default reader
That leaves runtime proof propagation, restart-bearing reconfigure, latest-row semantics, uncertainty, and store completeness unbound as one composition.
4. The subtraction window still uses a post-response proof timestamp
The bridge reads Docker RestartCount before diagnosis. PR #17086 now records a pre-dispatch interlock with the exact incarnation baseline and request time, but collectPlannedRestarts() counts only terminal runtimeAccess.observedAt, which is created after the Docker response succeeds.
The detector must use observation coordinates that honestly bound the restart-count sample, or degrade when no such coordinate exists. A restart beginning after inspect and completing before diagnosis must not fall below the next baseline.
Architectural Boundary
RecoveryActuatorService owns lifecycle execution and the durable run sequence.
recoveryRunStateStore.mjs owns tolerant audit reads plus any new completeness-aware projection.
DeploymentStateBridgeService.collectPlannedRestarts() owns the authoritative-count admission decision.
- ADR 0025 keeps restart churn non-authoritative and last in classification order.
- ADR 0026 owns uncertain effect and actuator semantics already delivered by PR #17086.
The existing tolerant reader may remain for best-effort listing and active-interlock recovery. The planned-restart path needs an explicit completeness-bearing surface; it must not silently change unrelated consumers from tolerant to strict.
Fix
- Add completeness semantics to the recovery-run read consumed by planned-restart subtraction. Distinguish a complete empty directory from retained files that are empty, corrupt, undecodable, truncated, or otherwise unclassified.
- Make
collectPlannedRestarts() degrade and suppress churn when the baseline window contains an unresolved pending / reobserve-requested restart effect without a positive settlement proof.
- Add one production-bound composition control that executes actual restart and restart-bearing reconfigure through
RecoveryActuatorService, writes the real temporary store, and calls the production completeness reader through the bridge's existing dependency seam—no hand-built DTO or entries array.
- Add actual-writer negative controls for
update-memory-limit, wrong service, definite not-applied / declined operations, and corrupt/empty retained artifacts.
- Bind the count window to the prior/current Docker observation and the pre-dispatch interlock coordinate. An interleaving restart after inspect but before diagnosis must either count or degrade, never disappear below a later baseline.
- Correct JSDoc that calls the terminal
runtimeAccess.observedAt a dispatch stamp. The pre-dispatch interlock is the dispatch-bound coordinate; the positive terminal proof is response-bound.
Contract Ledger
| Target surface |
Source of authority |
Behavior |
Fallback / error semantics |
Evidence |
| planned-restart recovery-run read |
retained JSONL source in recoveryRunStateStore |
entries plus explicit completeness for the requested window |
retained empty/corrupt/undecodable artifact makes completeness unavailable; genuinely empty directory is complete zero |
real-store controls |
| planned-restart health |
DeploymentStateBridgeService.collectPlannedRestarts |
available only when every effect in the baseline window is classified |
corrupt/truncated/active-uncertain/unbound window ⇒ degraded and suppress record-only churn |
exact status/reason matrix |
| actuator→bridge composition |
RecoveryActuatorService + recovery-run store |
actual restart and reconfigure receipts survive the real writer/reader chain and are subtracted once |
definite no-effect stays zero; uncertain never becomes certified zero |
production-bound temporary-store test + mutations |
| churn observation window |
Docker inspect proof + pre-dispatch restart interlock + terminal settlement |
baseline and planned effects share an honestly bounded interval |
interleaving or unbound coordinates degrade rather than misattribute |
first-observation/window-reanchor interleaving control |
Acceptance Criteria
Delivered Elsewhere — Do Not Reimplement
PR #17086 already owns:
- validated Docker grace/client deadline ordering;
- transport-phase
uncertain classification;
- durable pre-POST restart interlocks;
- authority/admission rechecks after awaited persistence;
- cross-action redispatch prevention for restart and reconfigure;
- positive
StartedAt settlement and retention-exempt active interlocks.
Decision Record Impact
aligned-with ADR 0025 and ADR 0026 — no authority, action class, privilege, or actuator widening.
Out of Scope
- Reopening #16482, #17065, PR #16974, or PR #17086.
- Changing restart-churn from non-authoritative record-only diagnosis.
- Combining the heal-event and recovery-run ledgers.
- Replacing the tolerant general-purpose reader for consumers that do not claim count completeness.
- General futility/age policy for an unresolved restart interlock; that remains outside this detector-completeness leaf.
Avoided Traps
- Treating skipped corruption as resilience. Best-effort listing and evidence-complete counting have different contracts.
- Counting uncertainty as applied or not-applied. Either guess can hide or manufacture churn; degraded is the honest detector state.
- Reimplementing PR #17086. The producer interlock is already correct; this leaf teaches the planned-restart consumer to understand it.
- Replacing focused predicate tests. Keep them; add the missing end-to-end composition proof.
Intake / Successor-Risk Receipt
- Ticket created
2026-08-11T14:58:06Z; current bot band pre-stale under the 90-day stale / 14-day close workflow.
- Earlier-session self-authored drift probe intersected all three owning surfaces, so the full intake ran.
- Newer authority: #17065 / PR #17086 narrows but does not duplicate this residual.
- Exact current-source falsifiers above reproduce at
af8294420a.
- Duplicate sweep: no other issue or PR owns
readRecentRecoveryRunStates completeness for planned-restart subtraction.
- ADR successor-risk:
adr-aligned with accepted/amended ADR 0025/0026; no amendment required.
- Classification after this amendment:
valid-as-written with positive ROI.
Related
#16482 · PR #16974 · #17065 · PR #17086 · #17072 · ADR 0025 · ADR 0026
Origin Session ID: 0dc1379e-5329-4fba-80ca-f6466822f7c9
Retrieval Hint: planned restart recovery-run completeness corrupt active-effect-interlock uncertain window bridge default reader
Context
This is the surviving completeness half of the follow-up from PR #16974.
PR #16974 moved planned-restart subtraction onto the recovery-run ledger and made detector health visible. PR #17086 later added an append-only, retention-exempt restart interlock before Docker dispatch and reconciled uncertain outcomes before a second restart-bearing action can run.
Those changes improved the producer. The planned-restart consumer still treats two incomplete reads as a complete zero.
Exact current-source falsifiers at
origin/devaf8294420ad139ea79a849e7d8b8aa2aaa979696:{ "corrupt": {"count":0,"reason":null,"status":"available"}, "uncertain": {"count":0,"reason":null,"status":"available"} }corrupt: a retained.jsonlfile contains no decodable row;readRecentRecoveryRunStates()silently skips it andcollectPlannedRestarts()certifies an available zero.uncertain: the read contains a within-windowactive-effect-interlock/effectDisposition: uncertainentry but no positive lifecycle proof;collectPlannedRestarts()filters it out and certifies an available zero.Current Problem
1. The reader exposes entries, not read completeness
readRecentRecoveryRunStates()is intentionally tolerant for general diagnostic listing: it takes the latest valid row per file and skips files with no valid row. That is not enough for a consumer claiming the planned-restart window is complete. A corrupt or empty retained artifact may be the exact proof the detector needs to subtract.A genuinely empty directory is different and must remain an available zero.
2. Active uncertainty is invisible to planned-restart health
PR #17086 correctly writes
pending/reobserve-requestedentries witheffectDisposition: uncertainandretentionClass: active-effect-interlock. They prevent duplicate actuator dispatch. The bridge's planned-restart count reads the same store but looks only for a positivedetails.runtimeAccessrestart proof, so an unresolved possibly-applied restart still becomes a certified zero.The correct detector result is degraded/unavailable until positive settlement establishes whether the restart belongs in the subtraction window.
3. The producer→store→bridge composition is still reconstructed
The bridge specs drive the real low-level Docker restart proof and production DTO constructors, then inject an in-memory recovery-run reader. They do not execute:
That leaves runtime proof propagation, restart-bearing
reconfigure, latest-row semantics, uncertainty, and store completeness unbound as one composition.4. The subtraction window still uses a post-response proof timestamp
The bridge reads Docker
RestartCountbefore diagnosis. PR #17086 now records a pre-dispatch interlock with the exact incarnation baseline and request time, butcollectPlannedRestarts()counts only terminalruntimeAccess.observedAt, which is created after the Docker response succeeds.The detector must use observation coordinates that honestly bound the restart-count sample, or degrade when no such coordinate exists. A restart beginning after inspect and completing before diagnosis must not fall below the next baseline.
Architectural Boundary
RecoveryActuatorServiceowns lifecycle execution and the durable run sequence.recoveryRunStateStore.mjsowns tolerant audit reads plus any new completeness-aware projection.DeploymentStateBridgeService.collectPlannedRestarts()owns the authoritative-count admission decision.The existing tolerant reader may remain for best-effort listing and active-interlock recovery. The planned-restart path needs an explicit completeness-bearing surface; it must not silently change unrelated consumers from tolerant to strict.
Fix
collectPlannedRestarts()degrade and suppress churn when the baseline window contains an unresolvedpending/reobserve-requestedrestart effect without a positive settlement proof.RecoveryActuatorService, writes the real temporary store, and calls the production completeness reader through the bridge's existing dependency seam—no hand-built DTO or entries array.update-memory-limit, wrong service, definite not-applied / declined operations, and corrupt/empty retained artifacts.runtimeAccess.observedAta dispatch stamp. The pre-dispatch interlock is the dispatch-bound coordinate; the positive terminal proof is response-bound.Contract Ledger
recoveryRunStateStoreDeploymentStateBridgeService.collectPlannedRestartsavailableonly when every effect in the baseline window is classifiedRecoveryActuatorService+ recovery-run storeAcceptance Criteria
plannedRestarts.status: available.RecoveryActuatorService, persists through the real store, and reaches the bridge through the production completeness reader; only the temporary directory is bound by the existing dependency seam.finishActionor reconfigure makes that composition test red.update-memory-limit, wrong-service, definite not-applied, and declined/no-action entries are not subtracted.pending/reobserve-requestedentry witheffectDisposition: uncertaindegrades planned-restart detection until positive settlement.authoritative: false,actionClass: record, and last in classification order.Delivered Elsewhere — Do Not Reimplement
PR #17086 already owns:
uncertainclassification;StartedAtsettlement and retention-exempt active interlocks.Decision Record Impact
aligned-with ADR 0025 and ADR 0026— no authority, action class, privilege, or actuator widening.Out of Scope
Avoided Traps
Intake / Successor-Risk Receipt
2026-08-11T14:58:06Z; current bot bandpre-staleunder the 90-day stale / 14-day close workflow.af8294420a.readRecentRecoveryRunStatescompleteness for planned-restart subtraction.adr-alignedwith accepted/amended ADR 0025/0026; no amendment required.valid-as-writtenwith positive ROI.Related
#16482 · PR #16974 · #17065 · PR #17086 · #17072 · ADR 0025 · ADR 0026
Origin Session ID:
0dc1379e-5329-4fba-80ca-f6466822f7c9Retrieval Hint:
planned restart recovery-run completeness corrupt active-effect-interlock uncertain window bridge default reader