Skip to content

feat(agent): a refused value stays out of the trace - #462

Merged
AminChirazi merged 1 commit into
mainfrom
claude/a-refused-value-stays-out-of-the-trace
Aug 8, 2026
Merged

feat(agent): a refused value stays out of the trace#462
AminChirazi merged 1 commit into
mainfrom
claude/a-refused-value-stays-out-of-the-trace

Conversation

@AminChirazi

Copy link
Copy Markdown
Contributor

A user's trace read:

s0007  type 1200      ← out of range
s0012  type 800       ← right value, appended by the old typing into 1200800
s0013  clear
s0014  type 500       ← accepted

Four steps memorialized where one is the truth — and every replay re-performed the whole fumble, driving the app through its error state on purpose, forever. Their words: "the worst is when executing again, it repeats all the steps instead of keeping the last thing it tried and worked."

The principle already existed — this closes its last gap

An occluded click is not recorded, because it records a success the page never saw. A walk-away from a rejected form is not recorded, because it records a success the page refused to give. But a value the page rejected — same verdict, from the same page — was recorded, and then replayed.

Now: when the rejected-form guard fires and the correction sticks, the writes that produced the red fields are dropped before the trace is minted, and the survivors renumbered. The trace reads as the one line a person would have left. The live run is unchanged — the fumble still happened against the app — only its memorial goes.

Three guards, each from adversarial review

The recording spans join before the prune. The join is by positional id; joining after the renumber handed every survivor the time window of a different executed action — and heal's before/after frames would have shown the wrong moment. This was a real bug in my first version, caught in review.

An observer between fumble and correction blocks the prune. A capture or assert there read the page with the fumble in it; a trace without the fumble would replay a different observation than the one recording verified. The journal is kept, refused value and all — there is a test where a capture sits mid-fumble and the refused value stays.

Positional tokens are never pruned. The prune keys on selector strings, and an nth-of-type path can name different elements before and after a DOM mutation — last-write-wins on the string could drop an accepted write to a different element. Stable tokens only; everything else fails open to the journal, which is the direction throughout.

Review findings deliberately NOT fixed here, so they're on the record

  • The dropped steps' frames stay in the recording bundle, referenced by nothing. Harmless orphans; cleaning them is bundle GC, not trace correctness.
  • Pages that count input events (debounce, dirty-flags) can distinguish fumble+correction from correction alone — the same residual class the fill-semantics change accepted, and much less likely.
  • A span-integrity test (each surviving step's recording span brackets its own action) needs the mock to grow frame-capture plumbing; the reordering fix is covered by review analysis and the join-before-prune ordering is now structural. Flagged rather than silently skipped.

Notes

  • ~310 lines, one file. Three tests, mutation-verified: disabling the prune fails the main test; removing the observer guard fails the observer test, by name.
  • Charter check from review: no invariant touched — this is minting, not healing; no trace-format change (replace and step ids already exist); the occluded-click precedent is the controlling one. The review's condition — the observer guard — is implemented.
  • cargo fmt, clippy -D warnings, full suite (918 tests) green.

🤖 Generated with Claude Code

A user's trace read: type 1200, type 800 - appended by the old typing
semantics into 1200800 - clear, type 500. Four steps memorialized where
one is the truth, and every replay re-performed the whole fumble, driving
the app through its error state on purpose, for ever.

The repo already had the principle, applied everywhere except here. An
occluded click is not recorded, because it records a success the page
never saw. A walk-away from a rejected form is not recorded, because it
records a success the page refused to give. But a VALUE the page rejected
- same verdict, from the same page - was recorded, and then replayed.

When the rejected-form guard fires and the correction sticks, the writes
that produced the red fields are now dropped before the trace is minted,
and the survivors renumbered. The trace reads as the one line a person
would have left: the value that stuck. The live run is unchanged - the
fumble still happened against the app - only its memorial goes.

Three guards keep the drop honest, each from adversarial review:

The recording spans join BEFORE the prune. The join is by positional id,
so joining after the renumber handed every survivor the time window of a
different executed action - and heal's before/after frames would show the
wrong moment.

An observer between the fumble and the correction blocks the prune. A
capture or assert there read the page WITH the fumble in it; a trace
without the fumble would replay a different observation than the one
recording verified. The journal is kept, refused value and all.

A positional token (`nth-of-type` paths) is never pruned. The prune keys
on selector strings, and a positional path can name different elements
before and after a DOM mutation - last-write-wins on the string could
drop an accepted write to a different element. Stable tokens only;
everything else fails open to the journal, which is the direction
throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AminChirazi
AminChirazi merged commit 1da830f into main Aug 8, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant