LearnNewsExamplesServices
Frontmatter
id16984
titlePlanned-restart completeness still skips corrupt and active-uncertain runs
stateClosed
labels
bugaitesting
assigneesneo-gpt-emmy
createdAtAug 11, 2026, 4:58 PM
updatedAtAug 24, 2026, 9:49 AM
githubUrlhttps://github.com/neomjs/neo/issues/16984
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 24, 2026, 9:49 AM

Planned-restart completeness still skips corrupt and active-uncertain runs

Closed Backlog/active-chunk-15 bugaitesting
neo-gpt-emmy
neo-gpt-emmy commented on Aug 11, 2026, 4:58 PM

[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

  1. 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.
  2. Make collectPlannedRestarts() degrade and suppress churn when the baseline window contains an unresolved pending / reobserve-requested restart effect without a positive settlement proof.
  3. 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.
  4. Add actual-writer negative controls for update-memory-limit, wrong service, definite not-applied / declined operations, and corrupt/empty retained artifacts.
  5. 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.
  6. 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

  • A retained corrupt, empty, or undecodable recovery-run artifact cannot yield plannedRestarts.status: available.
  • A complete empty recovery-run directory still yields an available count of zero.
  • A production-bound test executes restart and restart-bearing reconfigure through 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.
  • Removing restart proof propagation from finishAction or reconfigure makes that composition test red.
  • Actual-writer controls prove update-memory-limit, wrong-service, definite not-applied, and declined/no-action entries are not subtracted.
  • A within-window pending / reobserve-requested entry with effectDisposition: uncertain degrades planned-restart detection until positive settlement.
  • A restart beginning after inspect and completing before diagnosis cannot fall below the next baseline window.
  • The restart-churn fact remains authoritative: false, actionClass: record, and last in classification order.
  • JSDoc distinguishes the pre-dispatch interlock coordinate from the post-response positive proof timestamp.

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

tobiu referenced in commit 9bb2fbe - "fix(agentos): make planned restart counts fail closed (#16984) (#17677) on Aug 24, 2026, 9:49 AM
tobiu closed this issue on Aug 24, 2026, 9:49 AM