feat: durable message lifecycle from admission to execution - #3721
feat: durable message lifecycle from admission to execution#3721Astro-Han wants to merge 24 commits into
Conversation
M4n5ter
left a comment
There was a problem hiding this comment.
English
I found two correctness issues on exact head 2ea21a68950502615f5e109ce914a7af94a97140: one makes durable follow-up reordering fail for every real permutation, and the other rejects otherwise valid 32–49 KiB messages as an internal failure. The exact-head hosted test check is green, but its queue test uses an in-memory lifecycle stub for reorder and does not exercise either storage boundary.
简体中文
我在 exact head 2ea21a68950502615f5e109ce914a7af94a97140 上确认了两个正确性问题:持久化 follow-up 队列的任何实际换序都会失败;另一个问题会把本来合法的 32–49 KiB 消息错误地变成内部错误。当前 head 的托管 test 检查是绿色,但队列测试对重排使用了内存 lifecycle stub,没有覆盖这两个真实存储边界。
Astro-Han
left a comment
There was a problem hiding this comment.
I reviewed this head and found blocking issues that need to be addressed before merge.
[P1] Recovery can replay raw /skill text instead of the prepared skill invocation
Idle submit persists raw modelContent first (message-coordinator.ts:906-918) and only later expands the skill in the Host (root-turn-coordinator.ts:1028-1077). If the Host exits between those steps, recovery replays the persisted raw content without re-running skill preparation, producing a root that the normal path would have wrapped.
[P1] Rejected idle submit leaves a phantom user message in the transcript
The admission and user transcript are written together, but later start/admission can still fail (skill blocked, oversized, binding failure). The cancellation only flips lifecycle state, not the transcript — a failed send remains visible and retries create duplicates.
[P2] Message reorder with identical content is rejected as a conflict
The metadata store compares target order byte-for-byte, so any non-trivial reorder is treated as a conflict. Existing tests pass only because reorders in those fixtures are no-ops.
[P2] Admission envelope can exceed storage limits undetected
Submitted and prepared payloads are checked separately, but the combined admission envelope (64 KiB limit) is not pre-validated. Inputs in the 32–49 KiB range pass early gates yet fail at admission, surfacing as an internal failure.
CI on 2ea21a689505 is test: success. These issues are independent of CI and require fixes before approval. Heads verified at time of review.
简体中文
存在恢复路径与 transcript 残留等阻断问题,需修复后重审。Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Preserve live Client capability bindings, make cancellation retries idempotent, and keep admission-backed transcripts out of compatibility Run synthesis until their root contract owns them. Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
2ea21a6 to
d691232
Compare
Generated-by: Codex
Astro-Han
left a comment
There was a problem hiding this comment.
I verified a blocking issue in this head.
[P2] Retracted messages that never reached the Host remain visible in the transcript
When a queued Accepted message is retracted during reload, the lifecycle row is moved to cancelled but the session_messages user row and catalog preview inserted at admission stay visible. TUI expects still-queued text to return to the editor and only consumed messages to stay in history — after reload the old row is still paged as consumed steering, and resubmission leaves a duplicate.
Repro: next_turn submit → queue.retract → reopen. Lifecycle cancelled but readMessages still returns the user row with steeringEventId.
Fix: for accepted -> cancelled (never handed off) hide/remove the transcript row and recompute the preview with a lifecycle-aware projection; keep the row only for handed_off -> cancelled where Runtime actually consumed it. Cover reload + edit/resubmit.
Previous skill-recovery P2 is closed in this head. Checks on 490ffb31a4 are test: success.
简体中文
撤回未送达消息仍留在历史视图,需区分未送达与已消费后取消。Generated-by: Codex
Astro-Han
left a comment
There was a problem hiding this comment.
I verified the previous blocking finding still applies in this head.
[P2] Retracted Accepted messages remain visible
sqlite-session-metadata-store.ts:1605-1648 inserts the transcript row on Accepted, but cancelMessageAdmissions at :1935-1963 only flips lifecycle to cancelled without hiding rows that never reached the Host. After reload the never-handed-off row is still rendered, and resubmission leaves a duplicate.
Head f01aa233cd vs previous 490ffb31. Checks on f01aa233cd are test: success dependent on exact-head run.
简体中文
该阻断在该头仍成立。Generated-by: Codex
Summary
This is the first end-to-end durable message lifecycle PR built from
main, replacing Draft PR #3633 without cherry-picking or continuing its patch series.The contract is simple: once
turn.message.submitaccepts a message, a Host crash must not lose it, revive it after settlement, or execute it twice.These are durable classifications, not an independent provider state machine.
queued,leased, andin_flightremain reconstructible Host-memory projections.First-principles ownership
message_admissionsis the sole durable authority for an accepted draft, including canonical content, edit, reorder, promotion, retract, and lifecycle classification.Cancelled; no append-only history must be deleted or repaired.RootAdmissionOwnerowns the durable Root execution contract and source-message proof.Accepted -> HandedOffin the same transaction.userMessageId = nulland keeps its individualsourceMessages; Runtime does not invent an aggregate transcript message.HostMessageCoordinatorowns only the reconstructible queue projection and the shared proof classifier/settlement path. Normal completion and restart recovery call the same owner.Executedrequires a provider-request proof downstream of the admitted Root contract.Crash-cut behavior
Accepteddraft exists and transcript has zero rows for it.HandedOffplus its transcript identity and never puts it back in the queue.Executedand never replays it.Cancelledwithout history. Terminal cancellation after handoff retains history.Scope
Included: atomic admission/root transcript composition, canonical Skill admission, edit/reorder/promotion/retract, ordered successor handoff, proof-driven settlement, restart recovery, size boundaries, durable capability derivation, and removal of old Runtime queue/transcript authority.
Excluded: CLI/Desktop projection refactors and Side Conversation UI/hooks.
Verification
Only affected tests were run locally; the full repository suite was not run locally.
48b162326.@maka/storageand@maka/runtime-host.Review follow-up
/skillreplay and rejected phantom-history paths.HandedOffinto one SQLite transaction owned by the proof-driven lifecycle settlement path.AI use
This PR was implemented with Codex assistance. The design, repository decisions, code changes, affected-test selection, review of Draft PR #3633 as evidence, adversarial review, simplification audit, and verification were directed and checked against the repository's durable authorities and production composition.