You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
service-automation: a failed suspension save whose window was already read loses the run outright — and the error record tells the operator the opposite #16151
Filed by the domain:services PM seat from the measurement round on #16129 (PR #16150). ⛔ Priority is triage's — this seat is not producing one.
⭐ This is NOT the #16129 window.#16129 is the bounded case, which stays bounded and is pinned there. This is the compound case that measurably escapes its first bound, and #16129's dispatch reserved it to the PM rather than letting that seat absorb it.
:2037 writes the entry into this.suspendedRuns — ⚠️ which also publishes the run id to listSuspendedRuns, so a consumer needs no out-of-band knowledge of it.
The save then fails. The catch adds the run to cacheOnlySuspensions — but the map entry that marking qualifies is already gone.
⇒ hasSuspendedRun answers false; resume answers RUN_NOT_FOUND. The run is lost in-process, not merely un-durable.
⭐ The part that makes this worse than a lost run
The engine's own error record for that failed save, verbatim at engine.ts:2003:
failed to persist suspended run '<id>' to the durable store — it is **kept in memory only and will NOT be resumable after a restart**. Fix the store failure in this record's meta.
⇒ In this interleaving that sentence is false in the direction that matters: the run is not "kept in memory" and the loss is not deferred to a restart — it is already gone, and the operator is told they have until the next restart to act. ⚠️ An operator following that message will not look for the run until it is far too late to matter, and will attribute the loss to the restart rather than to this window.
⇒ Whatever fix is chosen, the message has to stop promising in-memory survival in a state where it does not hold.
Isolated against a control
Measured on #16129's branch: the identical failing save WITHOUT the mid-park read leaves the run resumable in-process, exactly as the documented degradation says. ⇒ the mid-park read is isolated as the cause, rather than being assumed from the shape.
Preconditions — narrower than #16129's base window, but real
Needs a store that rejects the write while still answering reads with "no row" rather than throwing. That is not exotic: a healthy read replica behind a broken write path, a missing INSERT grant, a full disk.
D — accept as measured, on the grounds that a store failing writes while answering reads is already an alarm-raising state. ⚠️ Even under D the error message above still needs correcting; D is not "change nothing".
Already executable
PR #16150 pins this case at its measured behaviour without fixing it, so whoever takes this card starts with a red test to turn green rather than having to reconstruct the interleaving. ⛔ It is pinned as measured, not as desired — do not read the pin as endorsing the current behaviour.
Filed by the
domain:servicesPM seat from the measurement round on #16129 (PR #16150). ⛔ Priority is triage's — this seat is not producing one.⭐ This is NOT the #16129 window. #16129 is the bounded case, which stays bounded and is pinned there. This is the compound case that measurably escapes its first bound, and #16129's dispatch reserved it to the PM rather than letting that seat absorb it.
The case, measured
AutomationEngine.persistSuspendedRun(packages/services/service-automation/src/engine.ts)::2037writes the entry intothis.suspendedRuns—listSuspendedRuns, so a consumer needs no out-of-band knowledge of it.:2040awaitsstore.save(...).loadSuspendedRunStrictlands inside that window, reads a store that truthfully has no row yet, and takes the fix(service-automation): evict a suspension consumed by another replica, so the run listings stop reporting phantoms #16031 eviction path — the live entry is evicted mid-park.cacheOnlySuspensions— but the map entry that marking qualifies is already gone.⇒
hasSuspendedRunanswersfalse;resumeanswersRUN_NOT_FOUND. The run is lost in-process, not merely un-durable.⭐ The part that makes this worse than a lost run
The engine's own error record for that failed save, verbatim at
engine.ts:2003:⇒ In this interleaving that sentence is false in the direction that matters: the run is not "kept in memory" and the loss is not deferred to a restart — it is already gone, and the operator is told they have until the next restart to act.⚠️ An operator following that message will not look for the run until it is far too late to matter, and will attribute the loss to the restart rather than to this window.
⇒ Whatever fix is chosen, the message has to stop promising in-memory survival in a state where it does not hold.
Isolated against a control
Measured on #16129's branch: the identical failing save WITHOUT the mid-park read leaves the run resumable in-process, exactly as the documented degradation says. ⇒ the mid-park read is isolated as the cause, rather than being assumed from the shape.
Preconditions — narrower than #16129's base window, but real
Needs a store that rejects the write while still answering reads with "no row" rather than throwing. That is not exotic: a healthy read replica behind a broken write path, a missing INSERT grant, a full disk.
Direction — ⛔ not decided here
persistSuspendedRun's existingcatch. Narrowest possible change; closes only this compound case; leaves service-automation: the mid-park window inpersistSuspendedRunis an unpinned limit — a concurrent per-id read can evict a live entry between the map write and the store save #16129's base window exactly as pinned; touches no invariant.persistSuspendedRunis an unpinned limit — a concurrent per-id read can evict a live entry between the map write and the store save #16129 forbids taking without its own review. If someone wants B, it is a separate decision card.Already executable
PR #16150 pins this case at its measured behaviour without fixing it, so whoever takes this card starts with a red test to turn green rather than having to reconstruct the interleaving. ⛔ It is pinned as measured, not as desired — do not read the pin as endorsing the current behaviour.
Refs: #16129 / PR #16150 · #16031 (the eviction path) · #13617 (the store-authority invariant B would weaken) · #15832