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
{{ message }}
Repository navigation
refactor(core): retired-value folding is re-implemented per decoder with nothing enforcing it #3413
Decoding a persisted record and validating fresh input are different jobs with opposite requirements. A persistence decoder must fold retired representations — rejecting one makes an old record unreadable. A wire or input validator must reject them — accepting one masks a handshake that should have been refused.
The codebase expresses both with the same isX() predicates, so which job a call site is doing is carried only by naming and comments. Every decoder therefore hand-writes its own fold, and nothing fails when one forgets. The pattern has now been independently re-implemented five times:
Site
Retired representation folded
agent-run.ts:583
automationId → legacyAutomationId field rename
agent-run.ts:589
status: waiting_permission → waiting_for_user
session-store.ts:1068
permissionMode: execute → ask
tool-result-record-schema.ts:209
same, subagent tool results
scheduled-task.ts
same, stored Automations
The last two were added in #3396, and one of them was missed on the first pass precisely because of this: hasValidSubagentResultFields reads as a validator but serves decodeCanonicalToolResultContent, a persistence decoder. The classification signal is in the function name; the authority is in the call chain; they disagreed.
This is not specific to PermissionMode. Any closed enum — status, provider, backend kind — pays the same cost on its next contraction, and pays it silently.
Proposed change
Make "this is a persistence read" checkable rather than conventional. The open question is how far to take it:
Minimal: one retired-value registry per enum plus a decodePersisted* naming contract, enforced by lint. Cheap, still relies on the author reaching for the right helper.
Structural: store read paths return a distinct type that cannot become the domain type without passing a decoder, so omitting the fold fails to compile. Correct, but touches every store read signature.
Pick one deliberately. The minimal option does not close the failure mode that produced this issue.
Why separate
This touches no permission behavior and spans several enums. Reverting it would leave #3396 correct, and reverting #3396 would leave this correct — different revert units, different reviewers.
AI use
Generative tooling made a substantive contribution: Claude Code identified this while implementing #3396, after review caught the missed fold. Evidence verified against current main by the human contributor before filing.
Hi, I'd like to take this.
I plan to pursue the structural option: durable store reads should produce an opaque persisted value that cannot flow into domain types until a decodePersisted* boundary canonicalizes retired representations. Wire and fresh-input validators will remain strict.
Since #3396 introduces three of the five cited sites and is still under review, I'll base the implementation on its final merged shape rather than stack it on a moving branch.
If this scope matches the intended direction, could a maintainer assign this issue to me?
AI use: Codex helped inspect the current code and PR state and draft this implementation direction; I reviewed it.
Activity
Sun-GLiang commentedon Aug 22, 2026
Hi, I'd like to take this.
I plan to pursue the structural option: durable store reads should produce an opaque persisted value that cannot flow into domain types until a
decodePersisted*boundary canonicalizes retired representations. Wire and fresh-input validators will remain strict.Since #3396 introduces three of the five cited sites and is still under review, I'll base the implementation on its final merged shape rather than stack it on a moving branch.
If this scope matches the intended direction, could a maintainer assign this issue to me?
AI use: Codex helped inspect the current code and PR state and draft this implementation direction; I reviewed it.