PR #3 still needs changes
3 open findings · 3 P1
311 baseline + 82 earlier probes pass · 3 new assertions fail
+239 / -29 since last review 180759cReviewed exact GitHub PR #3 head 180759c95783720cffdbf1a29e27e36fd93f2694 on 23 September 2026. All earlier reproductions pass. Three P1 findings remain, backed by three new failing assertions and two passing controls.
What improved
F48 and F50 are fixed. Ingest now durably reserves its original fingerprint under the exact interaction, and explicit null results survive recovery. Typed confirmation, cancellation and revision now record the target they selected, which fixes the previous retargeting examples. The live runtime and recovery call the same settlement decision.
These are useful changes with reasonable scope. The new code is not unnecessarily elaborate. The remaining concern is where those rules are applied: the semantic entry point still bypasses target recording, and recovery still interprets target selection as completed application.
Remaining failures
- F54, P1, partially addressed: replaying an ingested semantic yes can execute a newly presented transaction. Typed commands are protected, but the affirm branch directly calls commit without recording the observation's selected target. G1 fails; G4 passes with an intact journal.
- F55, P1, new: a crash after command-target but before cancellation consumes that unapplied cancellation. Replay returns a synthesized result without an error and leaves the transaction awaiting confirmation. G2 fails; G5 shows that the acceptance-only prefix remains safely runnable.
- F53, P1, partially addressed: recovery skips settlement when the transaction is paused, even after reconciliation durably records success. The live runtime settles that same transaction. G3 recovers a SUCCEEDED effect with an UNKNOWN owning transaction.
These are valid prefixes of real emitted journals. G3 pauses during an outstanding invoke, receives a lost-response error, then reconciles successfully. It does not fabricate a transaction state or alter an outcome record.
Verification
pnpm test: 311 tests pass on PR #3 itself.pnpm typecheck: passes.- All 82 earlier independent probes pass, including the eight cases from the previous round.
- Five new cases: three failed assertions, two passing controls.
- All three independent recovery test files pass strict TypeScript checking.
The first GitHub API request hit a transient connection failure. A subsequent request confirmed the exact head above before the review verdict. Testing used an isolated detached worktree. No Circe source changes, GitHub comments, approvals or merges were made.
Architecture judgment
Keep the shared settlement helper, but make its callers agree about when settled outcomes must update a transaction. Pause should govern dispatch, not prevent recognition of effects that have already completed. Moving only the status decision into a shared function leaves the recovery-only pause guard as a second lifecycle rule.
Command progress needs one contract across typed commands and semantic ingestion. Record the actual reservation key and selected transaction before consequences, then distinguish that selection from application and completion. Place the recording at a shared consequence boundary where possible; adding another isolated call only in the affirm branch would leave the same drift risk in other paths.
This calls for a small, explicit progress model and focused boundary tests. It does not justify a generic event-sourcing framework or a rewrite. Once fixed, retain a prefix-cut matrix for representative typed and semantic commands, including target selection, application, dispatch and settlement, with paused and unpaused controls. That is more useful than adding only the exact examples from each review.
This verdict covers PR #3 only. File-journal durability and later stack integrations remain separate reviews.
cd /tmp/circe-pr3-round3
pnpm exec vitest run --config /tmp/circe-pr3-round3-evidence/vitest.config.mjs recovery-round3.test.ts
At this head the command reports three failed and two passed. Downloaded configs contain local paths and need alias updates elsewhere. Original finding bodies below are historical; the latest verification note and current prompt describe the remaining work.
New probes Failures and controls 82 earlier probes Baseline Typecheck Config Round 1 archiveRound 2 archiveSidecar
Original walkthrough, preserved
PR #3 adds startup reconstruction to the journal port. It records full intents and transaction snapshots, discourse snapshots, accepted commands and final command results; recoverState reconstructs maps and marks unresolved dispatches UNKNOWN.
- createRuntime calls recoverState before exposing command and ingestion entry points.
- Mutation writers append intent, transaction, effect and outcome records at different points in each command.
- Recovery loads the latest snapshots, reattaches frozen effects and caches reconstructed command results.
- Reconciliation operates on recovered UNKNOWN effects without an automatic retry.
Original scope: +885 / -40 at 8d9cc15.
- packages/core/src/index.ts +1 / -0
- packages/core/src/journal-recovery.ts +271 / -0
- packages/core/src/runtime.ts +107 / -36
- packages/core/src/types.ts +91 / -4
- packages/evals/src/journal-recovery.test.ts +415 / -0
flowchart TD A[Accepted command] --> B[Intent and transaction snapshots] B --> C[Per-effect freeze records] C --> D[Dispatch request records] D --> E[External invoke] E --> F[Effect outcome records] F --> G[Transaction settlement snapshot] G --> H[Discourse snapshot and command outcome] J[Restart from any durable prefix] --> K[recoverState] K --> L[Restore runtime maps and accept new commands]
Round 3 changes
The fix adds scoped observation reservations, target-selection records for typed commands, a shared settlement decision and correct null-result recovery. All earlier reproductions pass. Semantic execution still bypasses target recording, target-only progress is mistaken for completion, and paused recovery still skips settlement.
- 311 repository tests and typecheck pass.
- All 82 earlier independent probes pass.
- Five new cases: three failures and two passing controls; all recovery probe files typecheck.
- F48 and F50 close. F53 and F54 remain partially addressed. F55 is new.
Current fix prompt · Ctrl+`
Rerun prompt · Ctrl+Space
Finding status
- FIXED · P1 · F46. Restart loses the presented gate and can redirect unaddressed actions
- FIXED · P1 · F47. Recovery is not fully scoped to an exact interaction identity
- FIXED · P1 · F48. Acceptance is treated as completion and reconstructed commands lose fingerprints
- FIXED · P1 · F49. Crash recovery invents batch receipt keys that were never dispatched
- FIXED · P2 · F50. Clean recovery drops completed effect results and failure details
- FIXED · P1 · F51. A partially recorded effect set can execute as if it were complete
- FIXED · P2 · F52. Recovery restores a validation reservation whose worker no longer exists
- OPEN · P1 · F53. Recovered effect outcomes do not settle the transaction before new commands
- OPEN · P1 · F54. Replayed confirm or cancel commands can act on a different transaction
- OPEN · P1 · F55. Target selection is cached as completion before the command applies
P1 F54. Replayed confirm or cancel commands can act on a different transaction openOriginal: packages/core/src/journal-recovery.ts:314-328 at a8099f1logiccorrectness
Typed-command E1 and E2 pass. G1 reproduces the same unintended execution through ingest: semantic affirm calls commit without recording a command target under observation:
Current source: packages/core/src/runtime.ts:2160-2163
Current fix prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/runtime.ts` around lines 2160-2163, Typed-command E1 and E2 pass. G1 reproduces the same unintended execution through ingest: semantic affirm calls commit without recording a command target under observation:<id>. After A completes and the final observation outcome is lost, presenting B then replaying the original yes invokes B. The durable fingerprint matches correctly, but no applied-target record prevents re-selection. G4 is the passing complete-journal control. Ensure all consequence-bearing entry points record application targets/progress using the actual reservation key, including observation:<id>. Semantic affirm/reject/request and adapted revise must not bypass the durable contract used by typed commands. Verify G1 and G4 plus E1/E2. Put the recording at a shared consequence boundary where practical, rather than patching only the affirm branch. Reproduce with `/tmp/circe-pr3-round3-evidence/recovery-round3.test.ts` at PR #3 head 180759c95783720cffdbf1a29e27e36fd93f2694. Keep all 82 earlier independent probes passing and run repository tests and typecheck.
The new recovery rule treats an accepted command as never started when synthesizeResult finds no transaction created by that command. That inference is false for commands that modify an existing transaction. Transaction.commandKey names the original proposal, so a completed confirm or cancel has no matching transaction under its own key.
E1 confirms A, then restarts from the valid prefix immediately before the final command-outcome. After presenting B, redelivery of the identical unaddressed confirmation invokes B. E2 repeats the sequence for cancellation and cancels B. The complete-journal confirmation control E4 returns the recorded result and leaves B untouched. This is a regression introduced by allowing unmatched accepted commands to run again.
Persist command application identity and its selected transaction/revision before applying consequences. On recovery, distinguish never-applied work from an applied command whose reply was lost. An incomplete command may return a conservative explicit result, but replay must never resolve its target again against a later gate.
Original fix direction. Use durable per-command application/progress information for commands that target existing transactions. Bind the selected transaction and revision before applying work; reconstruct a safe replay result or explicit incomplete state. Do not fix this by disabling redelivery of all accepted-only commands, which would regress D6.
Original probes: E1, E2; E4 control
Captured source
314: // Commands with recorded work are reconstructed so replay never re-executes
315: // them. A command accepted but never started has no work to reconstruct and
316: // stays runnable: its reservation is the restored fingerprint.
317: for (const tx of transactions.values()) {
318: if (tx.commandKey) acceptedKeys.set(tx.commandKey, tx.commandId ?? tx.commandKey);
319: }
320: for (const key of acceptedKeys.keys()) {
321: if (commandResults.has(key)) continue;
322: const result = synthesizeResult(key, transactions, intents, effects);
323: if (
324: result.intents.length === 0 &&
325: result.transactions.length === 0 &&
326: result.effects.length === 0
327: ) {
328: continue;Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 314-328, The new recovery rule treats an accepted command as never started when synthesizeResult finds no transaction created by that command. That inference is false for commands that modify an existing transaction. Transaction.commandKey names the original proposal, so a completed confirm or cancel has no matching transaction under its own key. Use durable per-command application/progress information for commands that target existing transactions. Bind the selected transaction and revision before applying work; reconstruct a safe replay result or explicit incomplete state. Do not fix this by disabling redelivery of all accepted-only commands, which would regress D6. Reproduce E1 and E2 in `/tmp/circe-pr3-round2-evidence/recovery-round2.test.ts`; retain E4 and D6 as controls. Check revise and resume under the same contract. Keep all 74 earlier probes passing and run the repository tests and typecheck.
P1 F55. Target selection is cached as completion before the command applies openOriginal: packages/core/src/journal-recovery.ts:333-337 at 180759clogicdata-integrity
command-target is emitted before applying a confirmation, cancellation, revision or resume. Recovery treats its presence as sufficient to install a completed commandResults entry, even when the journal contains no consequence of that command. Recording a selected target prevents retargeting, but does not prove application.
G2 creates a gated transaction, cancels it, then recovers the actual journal prefix ending immediately after the cancellation's command-target record. Replaying that same explicit cancellation returns a synthesized result with no error and leaves the transaction AWAITING_CONFIRMATION. That command ID is permanently consumed although its cancellation never ran. G5 cuts one record earlier, after acceptance, and correctly completes the cancellation on replay.
The new target record creates an additional crash boundary. Represent selected-but-unapplied work separately from completed work. Recovery may safely continue against the pinned target with the necessary revision checks, or expose an explicit recoverable incomplete outcome. It must not silently cache ordinary completion, and must not reselect a later gate.
Original fix direction. Distinguish target selection from application/completion in the durable command contract. Preserve selected identity and required revision evidence, recover safely before external dispatch or surface an explicit incomplete result, and keep UNKNOWN external effects non-retriable. Retain both G2 and G5, plus the typed and semantic no-retarget controls.
Original probes: G2; G5 control
Captured source
333: if (commandTargets.has(key)) {
334: // The command selected a transaction (or none) before applying
335: // consequences: replay must never resolve that target again.
336: const target = commandTargets.get(key);
337: transactionIds = new Set(target ? [target] : []);Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 333-337, command-target is emitted before applying a confirmation, cancellation, revision or resume. Recovery treats its presence as sufficient to install a completed commandResults entry, even when the journal contains no consequence of that command. Recording a selected target prevents retargeting, but does not prove application. Distinguish target selection from application/completion in the durable command contract. Preserve selected identity and required revision evidence, recover safely before external dispatch or surface an explicit incomplete result, and keep UNKNOWN external effects non-retriable. Retain both G2 and G5, plus the typed and semantic no-retarget controls. Reproduce G2 in `/tmp/circe-pr3-round3-evidence/recovery-round3.test.ts`. A target-only prefix currently consumes an unapplied cancellation with no error. Check confirm/revise/resume against the same progress contract. Keep all 82 earlier independent probes passing and run the repository tests and typecheck.
P1 F46. Restart loses the presented gate and can redirect unaddressed actions fixedOriginal: packages/core/src/types.ts:560-570 at 8d9cc15logiccorrectness
Remains fixed. All original recovery probes and the complete prior independent suite pass.
DiscourseSnapshot omits presentedGateId and the fact that a gate has already been presented. Neither finalize nor the startup restore preserves these fields. Transactions are reconstructed, but the runtime forgets which one the user saw and incorrectly treats the interaction as never having presented a gate.
After proposing A and then B, a clean restart makes an unaddressed confirmation do nothing, instead of confirming B. If B has already succeeded before restart, an unaddressed cancellation falls back to older A and cancels it. D1 and D2 reproduce both behaviors from complete, valid journals; no crash truncation is needed.
This reintroduces the transaction-targeting behavior that PR #2 corrected. Persist and restore the presented identity even after that transaction settles. Keep the no-fallback semantics; do not infer the anchor from whichever live transaction happens to be first.
Original fix direction. Extend the durable discourse contract and its writer/reader with the presented anchor and its lifetime semantics. Preserve the settled anchor tombstone or an equivalent durable fact. Verify both current-gate confirmation and no fallback to older work after settlement.
Original probes: D1, D2
Captured source
560: export interface DiscourseSnapshot {
561: scopes: Scope[];
562: scopeStack: ScopeId[];
563: retainedScopes: ScopeId[];
564: views: WorldView[];
565: referents: Referent[];
566: currentReferentId?: ReferentId;
567: recentReferentIds: ReferentId[];
568: pendingQuestion?: PendingQuestion;
569: pendingFrame?: { capability: CapabilityId };
570: metrics: MetricsBucket;Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/types.ts` around lines 560-570, DiscourseSnapshot omits presentedGateId and the fact that a gate has already been presented. Neither finalize nor the startup restore preserves these fields. Transactions are reconstructed, but the runtime forgets which one the user saw and incorrectly treats the interaction as never having presented a gate. Extend the durable discourse contract and its writer/reader with the presented anchor and its lifetime semantics. Preserve the settled anchor tombstone or an equivalent durable fact. Verify both current-gate confirmation and no fallback to older work after settlement. Reproduce D1, D2 in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
P1 F47. Recovery is not fully scoped to an exact interaction identity fixedOriginal: packages/core/src/journal-recovery.ts:200-203 at 8d9cc15logicdata-integrity
Remains fixed. All original recovery probes and the complete prior independent suite pass.
The command-accepted branch populates acceptedKeys without checking entry.interactionId, although the field is available. Later, recovery synthesizes cached results for these foreign keys. Effect records use startsWith(effectPrefix) instead of an exact interaction identity, so valid IDs A and A:child also overlap.
D3 writes command same in interaction A, then starts interaction B on the shared journal. B's own command same returns an empty reconstructed result and never invokes. D4 records an effect under A:child and recovers A; A imports the other interaction's effect despite having none of its intents or transactions. The existing isolation test uses different command IDs and does not assert effect isolation.
The journal is advertised as supporting multiple interactions, so every recovered namespace must apply the same exact boundary. The effect overlap can also collide with local transaction IDs such as tx:1 when those records exist in both interactions.
Original fix direction. Filter command acceptance by exact interactionId. Give effect, dispatch and outcome records an explicit interactionId or use a provably unambiguous identity encoding and exact ownership check. Test identical local command IDs across interactions and delimiter-containing interaction IDs.
Original probes: D3, D4
Captured source
200: case "command-accepted":
201: if (entry.commandId) {
202: acceptedKeys.set(`command:${entry.commandId}`, entry.commandId);
203: }Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 200-203, The command-accepted branch populates acceptedKeys without checking entry.interactionId, although the field is available. Later, recovery synthesizes cached results for these foreign keys. Effect records use startsWith(effectPrefix) instead of an exact interaction identity, so valid IDs A and A:child also overlap. Filter command acceptance by exact interactionId. Give effect, dispatch and outcome records an explicit interactionId or use a provably unambiguous identity encoding and exact ownership check. Test identical local command IDs across interactions and delimiter-containing interaction IDs. Reproduce D3, D4 in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
P1 F48. Acceptance is treated as completion and reconstructed commands lose fingerprints fixedOriginal: packages/core/src/journal-recovery.ts:242-252 at 8d9cc15logicdata-integrity
E6 and E7 now pass. observation-accepted durably records the interaction-scoped key and original fingerprint before ingest applies work. Prior typed-command cases also pass.
Recovery turns every accepted key without a command-outcome into a completed cached TurnResult, even if no intent or transaction was written. It also restores fingerprints only from command-outcome or command-failed, ignoring the accepted command payload. Acceptance, execution progress and completion are therefore conflated.
D6 keeps a real journal prefix ending immediately after command-accepted. On restart, redelivery returns an empty result with no errors, creates no intent and does no work; that key is consumed permanently. D5 cuts only the final command-outcome after A already succeeded. Redelivering the same ID with target B is accepted as a cached A result instead of raising the required payload-conflict error.
A conservative no-retry policy is appropriate after uncertain external execution, but acceptance alone is not evidence of execution. Recovery needs a visible incomplete state or a safe resumption path, plus the immutable accepted fingerprint. Silently synthesizing completion hides lost work.
Original fix direction. Persist a scoped reservation with the original fingerprint before applying the command, and represent accepted, partially applied and completed states separately. Recover never-started commands or surface an explicit recoverable failure; retain no-reinvoke behavior after dispatch. Restore and compare fingerprints before any cached result is returned.
Original probes: D5, D6
Captured source
242: // Accepted commands whose outcome was never recorded are reconstructed
243: // from the state they produced, so a replay cannot execute them again.
244: for (const tx of transactions.values()) {
245: if (tx.commandKey) acceptedKeys.set(tx.commandKey, tx.commandId ?? tx.commandKey);
246: }
247: for (const key of acceptedKeys.keys()) {
248: if (commandResults.has(key)) continue;
249: commandResults.set(
250: key,
251: synthesizeResult(key, transactions, intents, effects),
252: );Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 242-252, Recovery turns every accepted key without a command-outcome into a completed cached TurnResult, even if no intent or transaction was written. It also restores fingerprints only from command-outcome or command-failed, ignoring the accepted command payload. Acceptance, execution progress and completion are therefore conflated. Persist a scoped reservation with the original fingerprint before applying the command, and represent accepted, partially applied and completed states separately. Recover never-started commands or surface an explicit recoverable failure; retain no-reinvoke behavior after dispatch. Restore and compare fingerprints before any cached result is returned. Reproduce D5, D6 in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
P1 F49. Crash recovery invents batch receipt keys that were never dispatched fixedOriginal: packages/core/src/journal-recovery.ts:229-238 at 8d9cc15logicdata-integrity
Remains fixed. All original recovery probes and the complete prior independent suite pass.
dispatch-requested stores only effectId and a timestamp. When acknowledgement is absent, recoverState fabricates a receipt with idempotencyKey equal to each member effect ID and executor set to unknown. The runtime actually submits a batch under the first member's key, so the persisted record is insufficient to reconstruct what was sent.
D7 runs a two-target batch and restarts from the prefix before its first acknowledgement. Both effects correctly become UNKNOWN. Reconciling the second effect asks about its own ID, although only the first effect's ID was used for the external operation. The recorded executor is also lost. A real executor cannot reliably find the second operation because it never existed under that key.
This is precisely the lost-response boundary that the PR promises to handle without retry. The dispatch identity has to be durable before the external call; an acknowledgement cannot be the first place the true batch identity is recorded.
Original fix direction. Write the actual dispatch envelope before invoking: interaction, executor, operation/idempotency key, and full member effect IDs. Build synthetic lost-ack receipts from that record for every member. Verify all recovered receipts use the actual batch key and no recovery path reinvokes uncertain work.
Original probes: D7
Captured source
229: for (const effect of effects.values()) {
230: if (!dispatchedAt.has(effect.id) || terminal.has(effect.id)) continue;
231: if (effect.status !== "PENDING" && effect.status !== "RUNNING") continue;
232: effect.status = "UNKNOWN";
233: effect.failure = undefined;
234: effect.receipt ??= {
235: effectId: effect.id,
236: executor: "unknown",
237: idempotencyKey: effect.id,
238: dispatchedAt: dispatchedAt.get(effect.id)!,Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 229-238, dispatch-requested stores only effectId and a timestamp. When acknowledgement is absent, recoverState fabricates a receipt with idempotencyKey equal to each member effect ID and executor set to unknown. The runtime actually submits a batch under the first member's key, so the persisted record is insufficient to reconstruct what was sent. Write the actual dispatch envelope before invoking: interaction, executor, operation/idempotency key, and full member effect IDs. Build synthetic lost-ack receipts from that record for every member. Verify all recovered receipts use the actual batch key and no recovery path reinvokes uncertain work. Reproduce D7 in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
P1 F51. A partially recorded effect set can execute as if it were complete fixedOriginal: packages/core/src/journal-recovery.ts:210-215 at 8d9cc15logicdata-integrity
Remains fixed. All original recovery probes and the complete prior independent suite pass.
commit writes one effect-frozen record per target and only later writes confirmation and the transaction transition containing the full effect list. Recovery reattaches whichever effects survived, but there is no durable manifest or completion marker proving that the recovered list is the complete authorized set.
D9 proposes targets A and B and restarts from the actual journal prefix ending after the first effect-frozen record. Resume executes only A and settles the transaction as SUCCEEDED. B vanishes from the effect set even though the recovered intent still contains both targets. A complete-freeze control correctly resumes both targets.
Reattaching an orphan effect is useful, but it cannot establish that no sibling is missing. Before dispatch, recovery must know whether the freeze was complete. Otherwise it silently changes the operation the user requested.
Original fix direction. Record the complete frozen effect set and its transaction/revision association as one durable unit, or use a manifest plus completion record. On recovery, incomplete freezes must be completed deterministically with stable identities or remain explicitly blocked. Never mark a subset successful as the whole requested operation.
Original probes: D9
Captured source
210: // A crash between freezing and the next transaction transition must not
211: // orphan authorized effects or allow a second effect set for the revision.
212: for (const effect of effects.values()) {
213: const tx = transactions.get(effect.transactionId);
214: if (tx && !tx.effects.includes(effect.id)) tx.effects.push(effect.id);
215: }Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 210-215, commit writes one effect-frozen record per target and only later writes confirmation and the transaction transition containing the full effect list. Recovery reattaches whichever effects survived, but there is no durable manifest or completion marker proving that the recovered list is the complete authorized set. Record the complete frozen effect set and its transaction/revision association as one durable unit, or use a manifest plus completion record. On recovery, incomplete freezes must be completed deterministically with stable identities or remain explicitly blocked. Never mark a subset successful as the whole requested operation. Reproduce D9 in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
P2 F50. Clean recovery drops completed effect results and failure details fixedOriginal: packages/core/src/journal-recovery.ts:174-185 at 8d9cc15logicdata-integrity
E8 now passes. Recovery distinguishes undefined from an explicit null result; prior object-result and structured-failure cases still pass.
Recovery reconstructs effects from effect-frozen and status-only outcome records. invokeBatch updates effect.result or effect.failure in memory but emits outcome without those payloads; reconciliation also omits successful results and failure details. The full command-outcome is loaded into the result cache, not used to reconstruct current effects.
The two D8 cases complete successfully and fail with a structured executor error, then restart from an intact journal. The recovered effect retains its status but loses the success result or failure object. Cached replay can still contain data that runtime.state() no longer has, giving different answers for the same durable outcome.
This is a data-fidelity defect on ordinary clean restart, independent of disk corruption or torn writes. Outcome records should be sufficient to rebuild the effect itself rather than relying on an unrelated command response cache.
Original fix direction. Journal the complete outcome data needed by Effect, including structured result, failure and timestamps, for invoke, observation and reconciliation writers. Reconstruct those fields consistently and test success/failure state equality after clean recovery.
Original probes: D8 success and failure
Captured source
174: case "outcome":
175: case "reconciled": {
176: const effect = effects.get(entry.effectId);
177: if (!effect) break;
178: const status: EffectStatus = VALID_EFFECT_STATUSES.has(entry.status)
179: ? entry.status
180: : "UNKNOWN";
181: effect.status = status;
182: if (entry.detail !== undefined) effect.result = structuredClone(entry.detail);
183: if (TERMINAL_EFFECT_STATUSES.has(status)) {
184: effect.endedAt = entry.at;
185: terminal.add(entry.effectId);Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 174-185, Recovery reconstructs effects from effect-frozen and status-only outcome records. invokeBatch updates effect.result or effect.failure in memory but emits outcome without those payloads; reconciliation also omits successful results and failure details. The full command-outcome is loaded into the result cache, not used to reconstruct current effects. Journal the complete outcome data needed by Effect, including structured result, failure and timestamps, for invoke, observation and reconciliation writers. Reconstruct those fields consistently and test success/failure state equality after clean recovery. Reproduce D8 success and failure in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
P2 F52. Recovery restores a validation reservation whose worker no longer exists fixedOriginal: packages/core/src/journal-recovery.ts:138-142 at 8d9cc15logiccorrectness
Remains fixed. All original recovery probes and the complete prior independent suite pass.
Transaction snapshots include pendingCommitRevision and recoverState restores it verbatim. This field is an in-process compare-and-set reservation for an outstanding authority await, not a durable confirmation. No validation worker survives createRuntime after a restart.
D10 starts confirmation with validation still awaiting, then records an interrupt. touchedTx journals the paused transaction with pendingCommitRevision still set. After restart, even explicitly resuming and confirming that transaction cannot invoke: commit sees the stale reservation and returns. There are no frozen effects and no surviving operation that could clear the reservation.
The reservation was necessary for live concurrency safety, but restoring it as an active lock makes recoverable work permanently uncommittable. Durable authorization and ephemeral in-flight ownership need different recovery rules.
Original fix direction. Normalize in-flight validation reservations during recovery. Clear or recoverably abandon pendingCommitRevision when no durable committed effect set owns it, then require fresh validation before commit. Preserve duplicate-confirm protection within a live runtime and do not treat the stale lock as authorization.
Original probes: D10
Captured source
138: case "transaction-transition":
139: if (entry.interactionId === interactionId) {
140: transactions.set(entry.transaction.id, structuredClone(entry.transaction));
141: lastTransitionIndex.set(entry.transaction.id, index);
142: }Original finding prompt
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `packages/core/src/journal-recovery.ts` around lines 138-142, Transaction snapshots include pendingCommitRevision and recoverState restores it verbatim. This field is an in-process compare-and-set reservation for an outstanding authority await, not a durable confirmation. No validation worker survives createRuntime after a restart. Normalize in-flight validation reservations during recovery. Clear or recoverably abandon pendingCommitRevision when no durable committed effect set owns it, then require fresh validation before commit. Preserve duplicate-confirm protection within a live runtime and do not treat the stale lock as authorization. Reproduce D10 in `/tmp/circe-pr3-evidence/recovery.test.ts` against PR #3 head 8d9cc151f6f62fb825d041b3a2b546773a8a3fff. Keep the three recovery controls and all 59 earlier reviewer probes passing. Run the repo test suite and typecheck. Change the durable record and recovery rule together; do not patch only the test scenario.
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Sidecar: /home/abstergo/Documents/reviews/Circe-journal-recovery-abbyview.json HTML: /home/abstergo/Documents/reviews/Circe-journal-recovery-abbyview.html In `packages/core/src/journal-recovery.ts` around lines 302-314, E3 now passes for an unpaused UNKNOWN transaction. G3 still fails when an interrupt paused the transaction during invoke: after a lost response and successful reconciliation, live state is SUCCEEDED, but a crash prefix through reconciled recovers the effect as SUCCEEDED and the paused transaction as UNKNOWN. Recovery skips tx.paused before calling the shared helper. Pausing dispatch must not suppress settlement of already completed effects. Remove the recovery-only pause veto from terminal effect settlement while preserving the pause flag and preventing new dispatch. Keep complete-effect-set and cancellation checks. Compare live and recovered behavior for paused successful and failed outcomes as well as UNKNOWN reconciliation. Verify G3 and the earlier E3/D11 cases. Reproduce with `/tmp/circe-pr3-round3-evidence/recovery-round3.test.ts` at PR #3 head 180759c95783720cffdbf1a29e27e36fd93f2694. Keep all 82 earlier independent probes passing and run repository tests and typecheck. In `packages/core/src/runtime.ts` around lines 2160-2163, Typed-command E1 and E2 pass. G1 reproduces the same unintended execution through ingest: semantic affirm calls commit without recording a command target under observation:<id>. After A completes and the final observation outcome is lost, presenting B then replaying the original yes invokes B. The durable fingerprint matches correctly, but no applied-target record prevents re-selection. G4 is the passing complete-journal control. Ensure all consequence-bearing entry points record application targets/progress using the actual reservation key, including observation:<id>. Semantic affirm/reject/request and adapted revise must not bypass the durable contract used by typed commands. Verify G1 and G4 plus E1/E2. Put the recording at a shared consequence boundary where practical, rather than patching only the affirm branch. Reproduce with `/tmp/circe-pr3-round3-evidence/recovery-round3.test.ts` at PR #3 head 180759c95783720cffdbf1a29e27e36fd93f2694. Keep all 82 earlier independent probes passing and run repository tests and typecheck. In `packages/core/src/journal-recovery.ts` around lines 333-337, command-target is emitted before applying a confirmation, cancellation, revision or resume. Recovery treats its presence as sufficient to install a completed commandResults entry, even when the journal contains no consequence of that command. Recording a selected target prevents retargeting, but does not prove application. Distinguish target selection from application/completion in the durable command contract. Preserve selected identity and required revision evidence, recover safely before external dispatch or surface an explicit incomplete result, and keep UNKNOWN external effects non-retriable. Retain both G2 and G5, plus the typed and semantic no-retarget controls. Reproduce G2 in `/tmp/circe-pr3-round3-evidence/recovery-round3.test.ts`. A target-only prefix currently consumes an unapplied cancellation with no error. Check confirm/revise/resume against the same progress contract. Keep all 82 earlier independent probes passing and run the repository tests and typecheck.
/abbyview journal-recovery Round 4. Sidecar: /home/abstergo/Documents/reviews/Circe-journal-recovery-abbyview.json Incremental: preserve baseline and original finding bodies. Review changes since 180759c95783720cffdbf1a29e27e36fd93f2694 and affected paths. Verify F53, F54 and F55 with round-3 probes; retain all 82 earlier probes. Append only new findings after F55. This does not approve later stack PRs.
P1 F53. Recovered effect outcomes do not settle the transaction before new commands openOriginal: packages/core/src/journal-recovery.ts:174-185 at 8d9cc15logiccorrectness
E3 now passes for an unpaused UNKNOWN transaction. G3 still fails when an interrupt paused the transaction during invoke: after a lost response and successful reconciliation, live state is SUCCEEDED, but a crash prefix through reconciled recovers the effect as SUCCEEDED and the paused transaction as UNKNOWN. Recovery skips tx.paused before calling the shared helper. Pausing dispatch must not suppress settlement of already completed effects.
Current source: packages/core/src/journal-recovery.ts:302-314
Current fix prompt
The reducer updates effect status but leaves transaction state at the last separately written transaction-transition. After scanning the journal it reattaches effects, yet never derives the transaction lifecycle from those recovered outcomes. A valid crash prefix can therefore contain SUCCEEDED effects with a transaction still marked COMMITTED.
D11 keeps the prefix through a single effect's successful outcome, immediately before the final transaction transition. After restart, a named continuation of that completed intent passes the live-intent check and executes a second operation under a new key. The same continuation is correctly rejected once the uninterrupted runtime has settled; D14 is the positive control for that rejection.
Effect completion is already durable at this boundary. Recovery must reconcile the owning transaction before exposing state to command selection. It must also distinguish complete effect sets from partial freezes, which is the separate F51 requirement.
Original fix direction. After rebuilding verified complete effect sets, derive transaction settlement from recovered effect outcomes using the same lifecycle rules as the runtime. Preserve cancellations and superseding revisions. Expose a coherent state before accepting continuations or resumes; add an outcome-before-transition crash-prefix regression.
Original probes: D11
Captured source
174: case "outcome": 175: case "reconciled": { 176: const effect = effects.get(entry.effectId); 177: if (!effect) break; 178: const status: EffectStatus = VALID_EFFECT_STATUSES.has(entry.status) 179: ? entry.status 180: : "UNKNOWN"; 181: effect.status = status; 182: if (entry.detail !== undefined) effect.result = structuredClone(entry.detail); 183: if (TERMINAL_EFFECT_STATUSES.has(status)) { 184: effect.endedAt = entry.at; 185: terminal.add(entry.effectId);Original finding prompt