Skip to content

fix(qa): the preview receipt is the settled pre-commitment card, not an intermediate render (closes #663) - #696

Merged
atomchung merged 1 commit into
mainfrom
claude/663-single-preview-receipt
Jul 31, 2026
Merged

fix(qa): the preview receipt is the settled pre-commitment card, not an intermediate render (closes #663)#696
atomchung merged 1 commit into
mainfrom
claude/663-single-preview-receipt

Conversation

@atomchung

Copy link
Copy Markdown
Owner

Summary

When a first_review/weekly_review resolves an absent cash anchor at the card beat, add-cash recomputes the review and a second preview renders before the commitment is written (flows/first-review.md step 6). verify's exactly-once stage=preview pair had no rule distinguishing the pre-cash card that only prompted the question from the settled, post-recompute card the user actually used to choose or skip the rule — so the only way a provided trace could satisfy the old "cash_anchor_checked after the card" ordering was to receipt the superseded card as the decision artifact. That is issue #663.

Maintainer disposition (comment, 2026-07-31): a review trace has exactly one stage=preview artifact/presentation pair — the last pre-commitment card the user actually used. An earlier preview shown only to unlock a cash anchor is an intermediate interaction, not the accepted artifact. The receipt event is deferred until the recompute settles; the presentation itself is not deferred. Trace id stability across add-cash's engine session replacement was already required. Receipt semantics only — no change to card generation or add-cash.

What changed

verify_rows in skills/fomo-kernel/tools/ux_receipt.py now splits the two cash-anchor outcomes that recompute differently, where it used to treat them identically:

  • declined — unchanged: nothing recomputes, so the card that asked the question is also the settled one, and the row must come after it.
  • provided — inverted (new): providing an anchor always triggers add-cash and a recompute, so the exactly-once stage=preview artifact/card pair can only honestly be the card rendered afterward. The row must now come before that pair. A provided trace whose recorded pair sits at or before the row fails, naming the superseded-card problem.

Docs updated in the same change (mirrored surface, per docs/maintainer-guide.md's mirrored-surfaces table):

  • skills/fomo-kernel/references/ux-receipt.md — the primary contract (required).
  • docs/qa-runbook.md gate 3 — was stating the old unconditional "after the card" claim.
  • skills/fomo-kernel/references/data-contract.md — same stale claim, fixed to avoid a fresh cross-doc contradiction.
  • qa/SKILL.md — the replayed, mechanically-checked weekly_review # qa-trace: walkthrough was recording the pre-cash card; now shows the correct deferred order and is the reference example.

Checked for staleness, no change needed: references/card-delivery.md and references/interaction-delivery.md describe presentation mechanics (which channel, same-message timing) that stay true regardless of which card the receipt records; flows/first-review.md/flows/weekly-review.md cross-reference ux-receipt.md for the recording mechanic without asserting the ordering themselves.

Required proof (from the disposition)

  1. End-to-end trace prepare → first preview shown → cash answer → add-cash → second preview shown → commitment verifies, single preview artifact points at the second card. Built and driven with the actual ux_receipt.py CLI (subprocess) as a scratch probe, and as a production-shaped fixture in tests/test_interaction_trajectory.py::test_end_to_end_provided_trace_pointing_at_the_settled_card_verifies. Both pass ({"status":"pass",...} / verify_rows(rows) == []).
  2. A trace that receipts the superseded (first) card as the decision artifact fails, with an error naming the problem. Same CLI probe rebuilt with the old ordering: verify now exits 1 with FAIL: cash_anchor_checked recorded 'provided' but the recorded preview artifact/card precedes it; ... recording it before this event keeps the superseded pre-cash card as the decision artifact .... Unit-level: test_a_provided_cash_answer_after_the_settled_preview_fails and test_end_to_end_provided_trace_pointing_at_the_superseded_card_fails.
  3. Trace id stability across engine session replacement. Covered by the pre-existing "all events must use the declared session_id" invariant (test_session_id_must_be_consistent), which is unaffected by which engine session add-cash returns — the receipt's own session id is the conversation id declared once at start (references/ux-receipt.md). Added test_trace_id_stays_the_original_conversation_id_across_add_cash, which ties that existing coverage to [process·qa] Cash-anchor recompute mid-first_review has no documented receipt slot for its second preview card #663's scenario directly (and cross-references test_review_v2.py::test_add_cash_recomputes_the_same_review_without_re_asking_anything, which proves for real that add-cash returns a session id different from the one it was called with) rather than reimplementing anything.
  4. Mutation proof. Disabled the new ordering check (if stale:if False:) with a cp backup/restore (never git checkout), cleared __pycache__, import-checked the mutated tree. Both proof-2 tests reddened with a clean semantic failure (not a crash):
    RED  test_a_provided_cash_answer_after_the_settled_preview_fails: []
    RED  test_end_to_end_provided_trace_pointing_at_the_superseded_card_fails: []
    
    Restored via cp from the backup (byte-identical diff confirmed), cleared __pycache__ again, reran — both green.
  5. Full offline suite green. python3 tests/run_all.pyPASS: all 49 suites passed. (run once before the mutation, once after restore — both green; this also caught and fixed qa/tests/test_skill_commands.py's replay of the now-stale qa/SKILL.md walkthrough before this PR, which is the "adjacent finding" below).

Adjacent findings (not folded in)

  • qa/tests/test_skill_commands.py mechanically replays every # qa-trace: fence in qa/SKILL.md through the real CLI and verify — it caught the stale weekly_review walkthrough the moment the ordering check landed, which is exactly the drift-detection it exists for. No further action needed; noting it because it's a good example of the harness doing its job.
  • No other file in the repository encodes a provided cash-outcome receipt shape (checked via grep -rl "cash_outcome.*provided" and grep -rl "cash_anchor_checked" across .py/.md/.json) beyond the six files this PR touches, so there is no further mirrored-surface drift to fix.

Assumptions

  • The rule choice (rule_choice_presented) continues to be recorded in the same message as whichever card is recorded — now the settled one on a provided trace — consistent with flows/first-review.md step 6's "show that card before the commitment is written." This PR does not change when the commitment itself is written, only which card's presentation events the receipt records.

🤖 Generated with Claude Code

…an intermediate render (closes #663)

When a first_review/weekly_review resolves an absent cash anchor at the card
beat, `add-cash` recomputes the review and a second preview renders before
the commitment is written (flows/first-review.md step 6). `verify`'s
exactly-once stage=preview pair had no rule distinguishing the pre-cash card
that only asked the question from the settled, post-recompute card the user
actually used to choose or skip the rule -- so the only way a `provided`
trace could satisfy the old "cash_anchor_checked after the card" ordering was
to receipt the superseded card as the decision artifact.

Maintainer disposition (issue #663, comment 2026-07-31): a review trace has
exactly one stage=preview pair, the last pre-commitment card; an earlier
preview shown only to unlock a cash anchor is an intermediate interaction,
not the accepted artifact. The receipt EVENT is deferred until the recompute
settles; the presentation itself is not deferred.

`verify_rows` now splits the two outcomes that recompute differently:
`declined` keeps the existing "after the card" ordering, since nothing
recomputes and the card that asked stands as settled. `provided` inverts it --
the recorded stage=preview artifact/card pair must postdate
`cash_anchor_checked`, because providing an anchor always triggers `add-cash`
and a recompute, so the exactly-once pair can only honestly be the card
rendered afterward. A `provided` trace whose pair sits at or before that row
now fails, naming the superseded-card problem.

Trace id stability across add-cash's engine session replacement was already
covered by the pre-existing "all events must use the declared session_id"
check (references/ux-receipt.md's "keep the original session id for the
whole trace"); this adds a dedicated assertion tying that coverage to #663's
own scenario rather than reimplementing it.

Docs updated in the same change (mirrored surface): references/ux-receipt.md
(primary contract), docs/qa-runbook.md gate 3, references/data-contract.md,
and qa/SKILL.md's replayed `weekly_review` qa-trace walkthrough, which was
recording the pre-cash card and is now the reference example for the correct
deferred ordering. references/card-delivery.md and
references/interaction-delivery.md were checked and need no change: both
describe presentation mechanics that stay true regardless of which card the
receipt records.

Full offline suite green: `python3 tests/run_all.py` -> PASS: all 49 suites
passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@atomchung
atomchung marked this pull request as ready for review July 31, 2026 19:06
@atomchung
atomchung merged commit c217228 into main Jul 31, 2026
3 checks passed
@atomchung
atomchung deleted the claude/663-single-preview-receipt branch July 31, 2026 20:08
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