refactor(runtime): persist durable Tool Result projections - #4287
Conversation
9d41dd3 to
4d7007a
Compare
484f4e9 to
b2b2ae8
Compare
jackwener
left a comment
There was a problem hiding this comment.
Reviewed the latest head b2b2ae86d. No P0 or P1 — approving. I found nothing worth filing at P2 or P3, so there are no inline comments. audit, ubuntu-latest, macos-latest, windows-latest and windows_recovery are green; package and test were still running.
This is 43 files and 2,779 additions, so rather than claim I read all of it evenly, here is what I went after and what I found.
The allocation bound is done properly. 3d423b8c3 checks byteLength before copying, and the test is the part worth praising: it subclasses ArrayBuffer to count slice() calls and asserts data.copies === 0, plus passes a planning callback that throws if reached. That proves rejection happens before allocation rather than merely that oversized input is rejected — two independent proofs of the property that actually matters.
The same discipline holds across the module. decodeBoundedImageData bounds the encoded base64 length before decoding, so an oversized string never allocates a decoded buffer, and it rejects non-canonical base64 — which also closes off two encodings of the same bytes. Structural limits on depth, node count and part count are in place, and unsafe media types fail closed in three separate paths.
The artifact protection is the right shape. The new tool_result_projection source carries userDeletable: false while tool_result is user-deletable. That matters because durable replay depends on these projections — a user-deletable projection would mean a tool result that cannot be reconstructed. sharedReadable: true keeps shared-session reads working. I also checked whether this new enum value crosses the Runtime Host protocol wire and would need an epoch bump: it does not appear in the protocol frames, so no bump is required.
The session-manager.ts removal is safe, and I checked the call chain rather than the symbol. Removing branchFromTurn, branchBeforeTurn and reviseBeforeTurn looks alarming at first, because the Desktop renderer, preload bridge and IPC main all still reference those names, and the implementation is gone from every package. It is fine: the live path is sessions:branchFromTurn → deps.client.copySession("branch", ...) → Host → conversation-copy.ts, which never went through those SessionManager methods. They were a superseded in-process path with no remaining callers, and the 995 removed test lines tested exactly them.
And the copy path is correctly updated for the new format, which is the detail that would have been easy to miss: conversation-copy.ts now rewrites artifact references inside modelProjection when copying. Without that, a branched or revised session would carry projection refs pointing at the source session's artifacts. It has 22 tests.
Scope of this review. I examined the allocation bounds, the artifact source policy and its wire exposure, the session-manager removal and its full call chain, and the conversation-copy projection rewrite. I did not line-by-line review tool-runtime.ts (+266), ai-sdk-backend.ts (+143), or all 478 lines of the new projection module — I scanned the latter for bounds and failure modes rather than reading every branch. Saying so plainly since "approved" on a change this size should not be read as "every line verified".
Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed the current head and found one P1 blocker in the durable tool-result settlement path. The change otherwise has substantial focused coverage, but a supported asynchronous model-output projector can now prevent T2 from ever being committed after the tool effect has already completed.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
M4n5ter
left a comment
There was a problem hiding this comment.
Review of exact head b2b2ae86dd2a00312e73dd1fb3090ff07365a4cc: changes are required. I independently reproduced the unresolved P1 on tool-runtime.ts:1610: an allowed asynchronous projector can leave a completed tool effect permanently at T1 with no T2 outcome, even after turn abort. Because that P1 blocks approval, I am not approving this head. I left the separate artifact-lifecycle P2 inline below. The closed schema, side-effect-free projection admission, atomic SQLite outcome/projection commit, live/next-turn/restart replay, and exact Session copy paths otherwise held in focused verification; I found no separate simplification issue.
Automated review notice: This comment was posted by an automated review agent operated by M4n5ter. It is not an independent human review and does not replace one.
zhiiw
left a comment
There was a problem hiding this comment.
Reviewed at exact head b2b2ae86 (verified unchanged at review time; all checks completed/success on this head, including test 25m50s and package 23m41s).
Durable Tool Result projection foundation, verified rather than assumed:
- The schema is closed, versioned, and bounded at the Core layer: 256 KiB / 64 parts / depth 32 / 20k nodes, exact-shape validation per kind, one frozen failure sentinel.
sanitizeJsonValuerejects non-finite numbers, non-plain objects, andtoJSONserializers, and writes keys viadefineProperty, which makes__proto__payload keys inert by construction. Encoding re-decodes its own output before returning, so the writer can never produce something the reader would reject — invalid output becomes the sentinel instead. - Artifact refs are session-scoped and image-only: parts must reference the owning session (
ref.sessionIdchecked at both the encode and thedecodeRuntimeEventboundary, the latter viahasOwnedModelProjection— no cross-session ref can ride a persisted event), carry a canonicalized raster MIME, and inline image bytes must round-trip as canonical base64 under the existingMAX_READ_IMAGE_BYTES. The planner validates and freezes without writing;persist()runs only after admission, so a rejected projection never leaves an orphan write on the artifact path. - Admission precedes publication: the call message and
tool_startevent now land only afterprepareDurableToolAttempt(T1) succeeds — a rejected claim publishes nothing. T2 adoption deep-compares the committed event includingmodelProjection, so a replayed outcome cannot silently carry a different projection than the one admitted. - Live, replay, and restart share one representation: the transient
settledModelOutputslane and the post-T2 model-output recomputation are gone; replay materializes from the durable projection, rehydrating image refs for vision-capable models with per-artifact budget charging (deterministic decision keys, so re-materialization does not double-charge) and degrading to explicit text notes otherwise. Legacy events flow through the one compatibility codec (decodeEffectiveToolResultProjection), including the retired-ExploreAgent and shell-result shapes; unprojectable legacy path images pass through as legacy output rather than failing. - Session copy does not open a second projection path: the copy collects
session_filerefs from exactly the sitesrewriteStorageRefreaches (including the new projection parts), remaps through the existing exact-copy owner, andrewriteProjectionArtifactRefthrows on an invalid rewrite instead of emitting a corrupt projection. - Source policy is now one Core table:
ARTIFACT_SOURCE_POLICIES(userDeletable / userVisible / sharedReadable) replaces the desktop's local visibility switch and the coordinator's local shared-source set;tool_result_projectionis not user-deletable and not user-visible, matching its role as ledger content.
Executed on a real Windows machine at this head: clean forced rebuild, then Core/Runtime projection suites 45/45, the four ToolRuntime boundary suites 76/77 (the one failure is the pre-existing symlink-EPERM environment case — this machine has no Developer Mode; the failing assertion creates a symlink fixture), storage artifact/runtime-store + ai-sdk-backend 274/274, and the runtime-host recovery pair 1 pass + 1 platform skip (UDS). One honest limitation: the rewritten execution-model-composition suite does not finish on this machine (pre-existing environmental slowness in the process-heavy composition fixtures); the hosted test job runs it green on this head.
Automated review notice: This comment was posted by an automated review agent operated by zhiiw. It is not an independent human review and does not replace one.
简体中文
durable Tool Result 投影地基,全部核实而非假设:Core 层 schema 封闭有界(256KiB/64 段/深度 32/2 万节点),sanitizeJson 拒绝非有限数/非纯对象/toJSON,defineProperty 逐键写使 proto 键天然失效;编码后自解码自检,非法输出收编到唯一 sentinel。artifact ref 限本 session(编码与 decodeRuntimeEvent 双边界都查 sessionId)、白名单 raster MIME、canonical base64 往返校验;plan 不落盘、persist 在准入之后。T1 先准入后发布(call 消息/事件只在 claim 成功后落地);T2 adopt 深比对含 modelProjection。实时/重放/重启共享同一表示:settledModelOutputs 瞬态车道删除,重放按 vision 能力+预算把 artifact ref 再水化成图片(确定性 decision key 不重复计费),失败降级为明确文本注记;legacy 事件走唯一兼容 codec。session copy 只 remap ref 不另开投影路径,非法 rewrite 直接抛。artifact 来源策略收成 Core 一张三轴表。本机真 Windows:投影套件 45/45、tool-runtime 边界 76/77(1 个既有 symlink EPERM 环境噪音)、storage+backend 274/274;重写的 composition 套件本机跑不完(既有环境性),hosted test 同 head 跑绿——如实声明。
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
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
Generated-by: Codex
Generated-by: Codex
b2b2ae8 to
6190657
Compare
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed exact head 619065706eaffb0d5ef2481058c88d12ac733fda. The previous P1 is fixed: MakaTool.toModelOutput is now a synchronous contract, and an untyped/plugin thenable fails closed immediately to the deterministic projection_failed sentinel, so a completed tool effect can no longer be stranded before T2. The new production-boundary regression verifies that a never-settling projector still commits the fallback outcome without waiting.
I found no new P0 or P1. The previously reported P2 around partially published, unreferenced projection artifacts remains an explicit known limitation scoped to the follow-up lifecycle work; it is not fixed by this head.
Validation included a clean install, build:test, full typecheck, Core 739/739, Runtime 3,081 pass with 13 skips, focused durable-boundary Runtime tests 305/305, renderer architecture 60/60 plus the production graph check, changed-file Biome, git diff --check, a clean merge tree against current main, and all current hosted checks green. Runtime Host completed 1,443 pass / 1 fail / 12 skip; the sole Bash sandbox-boundary failure reproduces on the prior head and the host denies both unshare and bwrap. Storage completed 1,039 pass / 1 warning-sensitive child-process failure / 10 skip; that crash-recovery file passed 5/5 under the intended Node 22 runtime with warnings suppressed. I could not independently exercise the unavailable Linux command sandbox.
Review notice: This review was prepared by an automated review agent operated by hqhq1025 and is published at the direction of AstroHan, who has read these findings and is the human accountable for them.
M4n5ter
left a comment
There was a problem hiding this comment.
Approved at exact head 619065706eaffb0d5ef2481058c88d12ac733fda. The asynchronous-projector blocker is closed: toModelOutput is now a synchronous contract, and an untyped thenable immediately becomes the deterministic projection_failed result instead of delaying T2. The new never-settling-projector regression and 69 other focused Core, Runtime, and Storage tests pass, as do the four dependency-ordered builds.
The existing P2 artifact-lifecycle issue remains real and is not claimed fixed here: a current-head probe still persists artifact 1 when artifact 2 fails, then returns the failure projection. That follow-up is already recorded for the staged publication/reachability work in #4283 and does not block approval under the requested P0/P1 gate.
All exact-head hosted checks are green, both review threads are resolved, and the PR is mergeable and clean against current main.
Automated review notice: This comment was posted by an automated review agent operated by M4n5ter. It is not an independent human review and does not replace one.
* refactor(runtime): budget and compact effective model history Context budgeting, summarization, provider-native compaction, and history compact checkpoint source digests each re-derived their own view of a Tool Result. All four read the raw `function_response.result` — the execution fact — rather than the durable model projection that #4287 commits atomically at T2 and that live continuation, next-Turn replay, and restart already replay. A bounded or redacted projection was therefore sized, summarized, and hashed at the size and content of the raw evidence it replaced. Move the sizing authority to the reducer: `estimateRuntimeEventChars`, `estimateRuntimeEventsTokens`, and `groupEventsByTurn` leave the dependency-free scalar helpers and live in `model-history.ts`, where a `function_response` is measured through `effectiveToolResultModelValue` — one decode of the effective projection shared by sizing and digests. The summary size floor rides the same estimator, so it is fixed by construction. Route the two compaction materializers through the same effective output as primary replay: `effectiveReplayToolResultOutput` prefers the durable projection and falls back to raw output only for a response that has none (legacy or provider-native). This removes the divergent `toolResultOutput` call in the LLM summarizer and the Codex compactor. Derive the checkpoint source digest from effective model history. Rewriting raw evidence under an unchanged projection no longer invalidates a valid checkpoint, while replacing the projection does — a checkpoint can never replay over content it did not cover. That is a source-policy change, so `HISTORY_COMPACT_SOURCE_POLICY_VERSION` becomes v2 and a v1 checkpoint fails the existing shape check and re-summarizes instead of claiming coverage this policy never computed. `HistoryCompactCheckpoint` stays a contiguous-prefix replacement; no sparse override is introduced. Covered by `effective-history-compaction.test.ts` (budget sizes the projection, summarization cannot read replaced raw output, the digest ignores raw evidence but tracks the projection, a v1-policy checkpoint no longer validates) and the runtime workspace suite. Part of #4283 (PR 2). Generated-by: Claude Code * refactor(runtime): anchor the checkpoint digest on persisted fields Review found that hashing the MATERIALIZED effective value bound a durable digest to in-memory representation: the discriminant labels of an internal union, the artifact placeholder wording in `durableProjectionToToolResultOutput`, and — for legacy responses re-derived through the compatibility codec — the Bash/File projectors and the encoder's size bounds. Any later refactor of those would silently invalidate every persisted checkpoint, with no reason for anyone to bump the source policy. Select the persisted field that carries the response's model-visible content instead: `providerOutput` for a provider-native result, `modelProjection` when the response has one, and otherwise the raw `result` its effective content is still derived from — which is exactly what the pre-projection digest hashed. This is a structural choice over durable data only, so the digest is stable under code evolution while keeping the property it was changed for: rewriting raw evidence under an unchanged projection does not invalidate a checkpoint, and replacing the projection does. Sizing follows the same simplification, since the digest was its only other consumer: `EffectiveToolResultModelValue` and `effectiveToolResultModelValue` are gone and `estimateEffectiveToolResultChars` switches on the reducer's own `decodeEffectiveToolResultProjection` result. That leaves `estimateToolResultOutputChars` with a single caller, so it moves in beside it and stops being exported — which also drops the import edge this PR had added from the projection codec back into the budgeting helpers. Measured while re-deriving a legacy response through the whole compatibility codec on every call (200 responses x 200 KB: 94 ms, against 4 ms for the raw length it replaced), and one request measures the same events several times — budget verdicts, the compactable-content filter, and checkpoint prefix matching each walk the history. Memoize the per-response count on the content object, which is immutable once committed. Refs #4283. Generated-by: Claude Code * fix(runtime): treat a superseded compaction checkpoint as absent, not fatal Bumping the source policy to v2 was described as a safe degradation because compaction fails open and re-summarizes. Review showed that is true of `history-compaction`, `ai-sdk-compaction`, and the backend, but NOT of conversation copy: `cloneAgentRunEvent` threw both when the shape check rejected a checkpoint and when its prefix no longer matched. A run ledger keeps every checkpoint it ever recorded, so a session that had compacted under the old policy would have failed to branch, edit-and-resend, or start a side conversation — permanently, since a newer checkpoint does not remove the older record. Nothing in the suite covered it, because every copy fixture built its checkpoint under the current policy. A copy carries the canonical raw RuntimeEvents and can compact again, so drop such a record instead, joining the `return null` that opaque provider state already used for the same reason. The same rejection made `execution-inspect` report every pre-upgrade session as `compaction_checkpoint_invalid` at severity `error`, which would bury the records that really are damaged. Distinguish a well-formed checkpoint minted under an older policy with `isSupersededHistoryCompactCheckpoint`, and report it as `compaction_checkpoint_superseded` at `info` with a matching `superseded` validation state. Covered by a copy regression test that records a v1-policy checkpoint and asserts the copy completes carrying none; it fails against the previous throwing branch. Refs #4283. Generated-by: Claude Code
…4350) Archiving a Tool Result removed content from the model's view without recording that it had: the active prune kept its replacement in a per-Turn map a restart discarded, and the stale prune carried archive refs on the compaction policy and re-derived them on every replay. Neither was durable, so there was no single answer to what the model sees for a given Tool Result, and a replay path could resurrect content a prune had already removed. This adds one append-only model-projection-transition record on the operational AgentRunEvent ledger and migrates both prune paths onto it. A Session-scoped reducer folds the ledger onto the canonical RuntimeEvent stream, and replay, budgeting, compaction, the current Turn and conversation copy all read that fold. Source-digest validation plus the previousTransitionId chain make the result independent of ledger order, arrival order and clocks; a content-derived id makes a duplicated concurrent append idempotent. A record this build cannot decode withholds its target's projection instead of replaying the raw body, and a record that names no target fails the read rather than guessing. The migrated structures are deleted rather than deferred: the per-Turn placeholder map, the archive-ref policy table and its replay path, the second placeholder kind, and the stale prune's own event rewriter. Projection artifact publication is now all-or-nothing and carries an ownership receipt, so a plan retracts only what it itself published — the partial-publication leak found in #4287's review. Compatibility: the RuntimeEvent ledger keeps its existing shape and is never rewritten; sessions with no transition records fold to their current history unchanged. HistoryCompactCheckpoint keeps its contiguous-prefix meaning. Refs #4283 Refs #4287 Generated-by: Claude Code
* feat(core): define durable tool result projections Generated-by: Codex * feat(runtime): add durable projection codec Generated-by: Codex * feat(runtime): commit projections atomically at T2 Generated-by: Codex * refactor(runtime): replay effective tool result projections Generated-by: Codex * feat(runtime-host): persist projected artifacts and recoveries Generated-by: Codex * refactor(runtime): remove transient Tool Result authority Generated-by: Codex * fix(storage): reuse projected image artifacts Generated-by: Codex * fix(runtime): remap projected artifacts on copy Generated-by: Codex * refactor(runtime): remove synthetic projection return Generated-by: Codex * refactor(runtime): privatize synthetic result writer Generated-by: Codex * fix(runtime): validate projected images before persistence Generated-by: Codex * fix(runtime): reject unowned projection copies Generated-by: Codex * refactor(runtime): remove retired conversation copy owner Generated-by: Codex * test(runtime): verify durable projection owner boundaries Generated-by: Codex * refactor(runtime): keep projection helper types private Generated-by: Codex * test(runtime): target durable projection T2 rollback Generated-by: Codex * refactor(runtime): trim projection artifact input Generated-by: Codex * test(runtime): produce restart projection through T2 Generated-by: Codex * style(runtime): format session manager tests Generated-by: Codex * fix(runtime): make projection admission side-effect free Generated-by: Codex * test(runtime): replay persisted image after restart Generated-by: Codex * test(runtime-host): cover projection artifact restart Generated-by: Codex * refactor(runtime): preserve projection content fidelity Generated-by: Codex * fix(storage): protect projection artifacts Generated-by: Codex * fix(runtime): admit tool calls before publication Generated-by: Codex * fix(runtime): bound projection copies before allocation Generated-by: Codex * test(runtime-host): consolidate projection lifecycle coverage Generated-by: Codex * test(runtime): lock T1 failure side effects Generated-by: Codex * refactor(core): unify artifact source policy Generated-by: Codex * fix(runtime): make tool result projectors total Generated-by: Codex
…#4348) * refactor(runtime): budget and compact effective model history Context budgeting, summarization, provider-native compaction, and history compact checkpoint source digests each re-derived their own view of a Tool Result. All four read the raw `function_response.result` — the execution fact — rather than the durable model projection that apache#4287 commits atomically at T2 and that live continuation, next-Turn replay, and restart already replay. A bounded or redacted projection was therefore sized, summarized, and hashed at the size and content of the raw evidence it replaced. Move the sizing authority to the reducer: `estimateRuntimeEventChars`, `estimateRuntimeEventsTokens`, and `groupEventsByTurn` leave the dependency-free scalar helpers and live in `model-history.ts`, where a `function_response` is measured through `effectiveToolResultModelValue` — one decode of the effective projection shared by sizing and digests. The summary size floor rides the same estimator, so it is fixed by construction. Route the two compaction materializers through the same effective output as primary replay: `effectiveReplayToolResultOutput` prefers the durable projection and falls back to raw output only for a response that has none (legacy or provider-native). This removes the divergent `toolResultOutput` call in the LLM summarizer and the Codex compactor. Derive the checkpoint source digest from effective model history. Rewriting raw evidence under an unchanged projection no longer invalidates a valid checkpoint, while replacing the projection does — a checkpoint can never replay over content it did not cover. That is a source-policy change, so `HISTORY_COMPACT_SOURCE_POLICY_VERSION` becomes v2 and a v1 checkpoint fails the existing shape check and re-summarizes instead of claiming coverage this policy never computed. `HistoryCompactCheckpoint` stays a contiguous-prefix replacement; no sparse override is introduced. Covered by `effective-history-compaction.test.ts` (budget sizes the projection, summarization cannot read replaced raw output, the digest ignores raw evidence but tracks the projection, a v1-policy checkpoint no longer validates) and the runtime workspace suite. Part of apache#4283 (PR 2). Generated-by: Claude Code * refactor(runtime): anchor the checkpoint digest on persisted fields Review found that hashing the MATERIALIZED effective value bound a durable digest to in-memory representation: the discriminant labels of an internal union, the artifact placeholder wording in `durableProjectionToToolResultOutput`, and — for legacy responses re-derived through the compatibility codec — the Bash/File projectors and the encoder's size bounds. Any later refactor of those would silently invalidate every persisted checkpoint, with no reason for anyone to bump the source policy. Select the persisted field that carries the response's model-visible content instead: `providerOutput` for a provider-native result, `modelProjection` when the response has one, and otherwise the raw `result` its effective content is still derived from — which is exactly what the pre-projection digest hashed. This is a structural choice over durable data only, so the digest is stable under code evolution while keeping the property it was changed for: rewriting raw evidence under an unchanged projection does not invalidate a checkpoint, and replacing the projection does. Sizing follows the same simplification, since the digest was its only other consumer: `EffectiveToolResultModelValue` and `effectiveToolResultModelValue` are gone and `estimateEffectiveToolResultChars` switches on the reducer's own `decodeEffectiveToolResultProjection` result. That leaves `estimateToolResultOutputChars` with a single caller, so it moves in beside it and stops being exported — which also drops the import edge this PR had added from the projection codec back into the budgeting helpers. Measured while re-deriving a legacy response through the whole compatibility codec on every call (200 responses x 200 KB: 94 ms, against 4 ms for the raw length it replaced), and one request measures the same events several times — budget verdicts, the compactable-content filter, and checkpoint prefix matching each walk the history. Memoize the per-response count on the content object, which is immutable once committed. Refs apache#4283. Generated-by: Claude Code * fix(runtime): treat a superseded compaction checkpoint as absent, not fatal Bumping the source policy to v2 was described as a safe degradation because compaction fails open and re-summarizes. Review showed that is true of `history-compaction`, `ai-sdk-compaction`, and the backend, but NOT of conversation copy: `cloneAgentRunEvent` threw both when the shape check rejected a checkpoint and when its prefix no longer matched. A run ledger keeps every checkpoint it ever recorded, so a session that had compacted under the old policy would have failed to branch, edit-and-resend, or start a side conversation — permanently, since a newer checkpoint does not remove the older record. Nothing in the suite covered it, because every copy fixture built its checkpoint under the current policy. A copy carries the canonical raw RuntimeEvents and can compact again, so drop such a record instead, joining the `return null` that opaque provider state already used for the same reason. The same rejection made `execution-inspect` report every pre-upgrade session as `compaction_checkpoint_invalid` at severity `error`, which would bury the records that really are damaged. Distinguish a well-formed checkpoint minted under an older policy with `isSupersededHistoryCompactCheckpoint`, and report it as `compaction_checkpoint_superseded` at `info` with a matching `superseded` validation state. Covered by a copy regression test that records a v1-policy checkpoint and asserts the copy completes carrying none; it fails against the previous throwing branch. Refs apache#4283. Generated-by: Claude Code
…pache#4350) Archiving a Tool Result removed content from the model's view without recording that it had: the active prune kept its replacement in a per-Turn map a restart discarded, and the stale prune carried archive refs on the compaction policy and re-derived them on every replay. Neither was durable, so there was no single answer to what the model sees for a given Tool Result, and a replay path could resurrect content a prune had already removed. This adds one append-only model-projection-transition record on the operational AgentRunEvent ledger and migrates both prune paths onto it. A Session-scoped reducer folds the ledger onto the canonical RuntimeEvent stream, and replay, budgeting, compaction, the current Turn and conversation copy all read that fold. Source-digest validation plus the previousTransitionId chain make the result independent of ledger order, arrival order and clocks; a content-derived id makes a duplicated concurrent append idempotent. A record this build cannot decode withholds its target's projection instead of replaying the raw body, and a record that names no target fails the read rather than guessing. The migrated structures are deleted rather than deferred: the per-Turn placeholder map, the archive-ref policy table and its replay path, the second placeholder kind, and the stale prune's own event rewriter. Projection artifact publication is now all-or-nothing and carries an ownership receipt, so a plan retracts only what it itself published — the partial-publication leak found in apache#4287's review. Compatibility: the RuntimeEvent ledger keeps its existing shape and is never rewritten; sessions with no transition records fold to their current history unchanged. HistoryCompactCheckpoint keeps its contiguous-prefix meaning. Refs apache#4283 Refs apache#4287 Generated-by: Claude Code
Summary
Tool Results previously had no single durable, provider-neutral model-visible representation. Live continuation, later replay, and restart recovery could therefore reconstruct different output, while a transient post-T2 model-output path competed with the RuntimeEvent ledger.
This PR adds the PR 1 foundation from #4283:
The final diff is 43 files (
+2779/-1578): production and other source is+1225/-463, while tests are+1554/-1115. The added volume defines and verifies the durable protocol; the deletions remove competing authorities and tests replaced by stronger production-composition coverage.Refs #4283
Lifecycle
flowchart TD A[Provider tool call] --> B{RuntimeStore T1 claim} B -- rejected --> R[Stop before call publication or execution] B -- accepted --> C[Runtime publishes the admitted call and executes once] C --> D{Storage plans exact artifact refs without writes} D -- planning fails --> F[Core-defined deterministic failure projection] D -- planned --> E{Core admits one closed and bounded projection} E -- rejected --> F E -- admitted --> G{Storage persists protected projection artifacts} G -- persistence fails --> F G -- persisted --> H[Canonical durable projection] F --> I{RuntimeStore T2 atomic commit} H --> I I -- fails --> J[Fail-stop; T1 prevents repeated effects] I -- committed --> K[Effective-history reducer] K --> L[Live continuation] K --> M[Next-Turn replay] K --> N[Cold restart]Core is the only compatibility/schema authority; Runtime does not recalculate projection semantics during replay. Session copy also does not create another projection path: the existing Runtime Host exact-copy owner only remaps persisted
session_filerefs.Projection bounding is a structural and resource contract, not arbitrary-content redaction. Tool-authored text and JSON remain faithful, consistent with Discussion #4119; credential stores and omission-based credential APIs remain separate security boundaries.
Verification
npm --workspace @maka/core run test:dist— 739 passednpm --workspace @maka/storage run test:dist— 1015 passed, 7 skippednpm --workspace @maka/runtime run test:dist— 3093 passed, 13 skippednpm --workspace @maka/runtime-host run test:dist— 1444 passed, 12 skippedgit diff --checkpassedReview focus
This remains the durable projection foundation. Budgeting/compaction policy, projection transitions/pruning/copy-artifact protocols, and overflow omission remain outside this PR.
Schema/admission failures are zero-write: all parts and exact artifact refs are planned and decoded before any artifact is published. Projection-owned artifacts use a protected lifetime, so ordinary user deletion cannot invalidate committed history. Once multi-artifact publication begins, a later artifact failure or T2 failure can still leave a hidden, content-addressed orphan without committing a dangling projection ref. Repeat settlement is rejected at T1 before tool execution, projection work, or call-side publication can run again. Making publication atomic across ArtifactStore and RuntimeStore requires a batch staging/publish or garbage-collection lifecycle protocol, so that bounded P2 remains explicit follow-up scope rather than a local PR 1 patch.
The main invariants are that admission precedes effects, projection is computed before T2, outcome and projection commit atomically, and one durable authority feeds live continuation, next-Turn replay, and restart.
AI use
Select exactly one:
Tool(s) and scope: Codex implemented the schema, codec, Runtime and Host integration, tests, validation, simplification, and review-driven corrections.
Checklist
Does this PR entail a change in behavior?