Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions docs/qa-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ once and they hold for every run that follows in the same conversation.
[interaction-delivery.md](../skills/fomo-kernel/references/interaction-delivery.md)
(#293). On a review route it also proves the cash anchor was settled by the
user rather than by the agent's discretion: `cash_anchor_checked` carries
`found_in_source` before the first surface, or `provided`/`declined` after
the card the question was asked at, and no outcome exists for "not asked"
(#357). When an anchor arrives mid-run, `add-cash` mints a new session id —
keep the original one for the whole trace.
`found_in_source` before the first surface, `declined` after the settled
card the question was asked at, or `provided` *before* the recorded
preview pair — providing an anchor always mints a new session id via
`add-cash` and recomputes the review, so the settled card is the one
rendered afterward, and the exactly-once preview pair this trace may
record is reserved for it, never the pre-cash card that only asked (#663).
Keep the original session id for the whole trace regardless. No outcome
exists for "not asked" (#357).
4. **Verdict and verification** — the session ended with an `owner_verdict`
event and `tools/ux_receipt.py verify` passing. Human-graded runs use both
`--require-owner-verdict` and `--require-timing-integrity`; only
Expand Down
25 changes: 16 additions & 9 deletions qa/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,12 @@ python3 tools/ux_receipt.py event --session-id <ID> --event card_presented \

A candidate with no `grounding` omits the field entirely (like `candidate_1`) — **do not invent a sentence to fill it**. That is precisely the half #293 cannot catch and only a human can hold.

**The `weekly_review` route carries one extra opener, and `verify` enforces it** (the trace above is `first_review`; do not copy the opener into it). When `prepare` selects `weekly_review`, show the user the rule agreed last time **before the first question and the first card**. Two rows differ from the trace above — that opener, and the cash anchor. This trace deliberately shows the other cash shape: a source with no balance row, so the plan came back `absent`, the balance was asked for in the same message as the preview card, and the outcome row therefore sits **after** that card (`provided` when the user gave one, `declined` when they did not). Everything else — questions, answers received, both card stages, rule choice — is copied verbatim:
**The `weekly_review` route carries one extra opener, and `verify` enforces it** (the trace above is `first_review`; do not copy the opener into it). When `prepare` selects `weekly_review`, show the user the rule agreed last time **before the first question and the first card**. Two rows differ from the trace above — that opener, and the cash anchor. This trace deliberately shows the other cash shape: a source with no balance row, so the plan came back `absent`, and the balance was asked for in the same message as a preview card. Where the outcome row lands from there depends on the outcome (#663), because only one of the two recomputes anything:

- `declined` — nothing recomputes, so the card that asked the question is also the settled one, and the row sits **after** it, exactly as before.
- `provided` — always triggers `add-cash` and a recompute, so the card the user actually used to choose or skip the rule is the one rendered *afterward*, never the one that asked. The trace's one `stage=preview` artifact/`card_presented` pair is reserved for that later, settled card, so the row sits **before** it: record `cash_anchor_checked --cash-outcome provided` when the user answers, run `add-cash` and rerun `preview` on the session it returns — keeping THIS session id for the whole trace, since a receipt records one conversation, not one engine session — and only then record the deferred `artifact_generated`/`card_presented` pair for the card that recompute produced. The first, pre-cash card was real and the user did see it; it simply gets no artifact/presentation row of its own, because it is an intermediate interaction, not the accepted decision artifact. A pair recorded before this row would receipt that superseded card instead, which `verify` now refuses by name.

This trace shows `provided`. Everything else — questions, answers received, both card stages, rule choice — is copied verbatim, with the preview pair moved to follow the cash row instead of preceding it:

```bash
# qa-trace: weekly_review
Expand All @@ -234,19 +239,21 @@ python3 tools/ux_receipt.py event --session-id <ID> --event memory_presented \
# python3 tools/ux_receipt.py event --session-id <ID> --event memory_presented --memory-kind due_revisit
python3 tools/ux_receipt.py event --session-id <ID> --event question_presented --mode native_options
python3 tools/ux_receipt.py event --session-id <ID> --event answers_received
# The FIRST preview (holdings-only) renders and is shown here, in one message with the
# rule choice and the cash question — a real, user-visible step. #663: it is an
# intermediate interaction, not the accepted decision artifact, so it gets no
# artifact_generated/card_presented row of its own; only the cash answer is recorded now.
# The user gave a balance, so run `review.py add-cash --session-id <ID> --cash <json>` and
# rerun `preview` on the session it returns before recording the next two rows below.
python3 tools/ux_receipt.py event --session-id <ID> --event cash_anchor_checked \
--cash-outcome provided
# The settled card — the one add-cash's rerun preview rendered — is the pair this trace
# records, and the rule choice is shown alongside it.
python3 tools/ux_receipt.py event --session-id <ID> --event artifact_generated \
--stage preview --artifact-path <preview-card.html>
python3 tools/ux_receipt.py event --session-id <ID> --event card_presented --stage preview --mode widget
# The card, the rule choice and the cash question are ONE message, card on top. These
# three rows are the order the surfaces appeared in it.
python3 tools/ux_receipt.py event --session-id <ID> --event rule_choice_presented \
--mode native_options --grounding-check-file <grounding-check.json>
# The user answered, so `provided`. If they gave a balance, run
# `review.py add-cash --session-id <ID> --cash <json>` and rerun preview on the session
# it returns — but keep THIS session id for the whole trace: a receipt records one
# conversation, not one engine session.
python3 tools/ux_receipt.py event --session-id <ID> --event cash_anchor_checked \
--cash-outcome provided
python3 tools/ux_receipt.py event --session-id <ID> --event artifact_generated \
--stage final --artifact-path <final-card.html>
python3 tools/ux_receipt.py event --session-id <ID> --event card_presented --stage final --mode widget
Expand Down
2 changes: 1 addition & 1 deletion skills/fomo-kernel/references/data-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ python3 engine/review.py add-cash --session-id <ID> \

This re-enters the same review with the anchor added, and it reuses that session's frozen prices rather than resolving new ones — the user answered against the card those numbers rendered, so a second observation instant would be a different review wearing the same session (#665). Two outcomes, and the response names which: the anchor propagated into the account pillar (`recompute.outcome: "anchor_propagated"`), or the facts underneath it moved and the command refuses. What counts as moving is the source facts, not the anchor's own downstream effects: the input files, that frozen frame, the recorded book, the diagnosis, the rules offered, and the questions already answered. A transaction file that grew since the card was rendered is refused before anything is written at all. Answers, narrative, and frozen question surfaces carry over untouched; the returned session id supersedes the one you passed, and `card_plan.required_honesty_keys` gains the account-basis key, which needs one more sentence in `narrative.honesty`. Rerun `preview` on the returned session and show the recomputed card. If they skip, the card keeps its holdings pillar and its unlock invitation exactly as before, and the review finishes normally — skipping is a real answer, not a failure.

On `first_review` and full-tier `weekly_review`, record which of the three happened (`cash_anchor_checked`, `references/ux-receipt.md`): `found_in_source` before the first question or card, `provided` or `declined` after the card the question was attached to. There is no outcome meaning "the agent decided not to ask" — that is what made a run where the user never got the chance look identical to one where they declined (#357, fifth recurrence).
On `first_review` and full-tier `weekly_review`, record which of the three happened (`cash_anchor_checked`, `references/ux-receipt.md`): `found_in_source` before the first question or card; `declined` after the card the question was attached to, since nothing recomputes and that card stands as settled; `provided` *before* the recorded preview pair instead, since providing always recomputes and the settled card — the one this trace's exactly-once preview pair must record — is the one rendered after that recompute, not the one that asked (#663). There is no outcome meaning "the agent decided not to ask" — that is what made a run where the user never got the chance look identical to one where they declined (#357, fifth recurrence).

One anchor is enough: the engine reconstructs the account's historical cash balance by rolling that single point backward through each trade's cash footprint, so the user never has to supply a day-by-day record. Two things follow from that, worth knowing because neither is visible in the data:

Expand Down
9 changes: 6 additions & 3 deletions skills/fomo-kernel/references/ux-receipt.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ python3 tools/ux_receipt.py event --session-id <id> \
--event memory_presented --memory-kind prior_commitment

# cash anchor (first_review, full-tier weekly). found_in_source goes before the
# first question or card; provided/declined go after the card they were asked at
# first question or card; declined goes after the (settled) card it was asked
# at; provided goes *before* the settled preview pair below -- providing an
# anchor always recomputes the review, so that pair must record the card
# shown after the recompute, never the one that asked (#663)
python3 tools/ux_receipt.py event --session-id <id> \
--event cash_anchor_checked --cash-outcome found_in_source

Expand All @@ -66,9 +69,9 @@ python3 tools/ux_receipt.py event --session-id <id> --event card_presented --sta

`--cash-outcome` takes exactly one of `found_in_source` (the statement carried a balance row, so nothing was asked), `provided` (the user was asked at the card beat and gave one), or `declined` (the user was asked and did not). Every value states what the *user's* data or answer decided; there is no value for "the agent decided not to ask". The retired `skipped` was exactly that, and #357's fifth recurrence recorded it correctly and in order while the user was never offered the question — the gate passed and the experience was identical to forgetting. Now a run that never asked can record nothing, and `verify` refuses a trace with no `cash_anchor_checked` on a review whose declared plan says the check was owed, so "nobody was asked" and "they declined" are different traces.

Position follows the outcome, and `verify` enforces it both ways. `found_in_source` is read before `prepare` runs, so it must precede the first question or card — retrospective evidence rather than a self-report, the same anti-backfill rule as the weekly opener. `provided` and `declined` record a question asked in the same message as the preview card (`data-contract.md`), so they must come *after* the first `card_presented`; a `declined` recorded earlier is refused, because at that point there was no card the question could have been attached to.
Position follows the outcome, and `verify` enforces it both ways. `found_in_source` is read before `prepare` runs, so it must precede the first question or card — retrospective evidence rather than a self-report, the same anti-backfill rule as the weekly opener. `provided` and `declined` both record a question asked in the same message as a preview card (`data-contract.md`), but from there the two invert (#663), because only one of them recomputes anything. `declined` triggers no recompute, so the card that asked the question is also the settled one, and the row must come *after* the first `card_presented`; a `declined` recorded earlier is refused, because at that point there was no card the question could have been attached to.

When the user provides an anchor, `review.py add-cash` recomputes the review and returns a new session id. **Keep the original session id for the whole trace.** A receipt records one conversation with a user, not one engine session — the same reason a refresh trace is keyed by `refresh_id` and a `consider` trace by `evaluation_id`.
`provided` always triggers `review.py add-cash` and a recompute, so the card the user actually used to choose or skip the rule is never the one that prompted the question — it is the one `add-cash`'s rerun `preview` renders afterward, on the new session id `add-cash` returns. **Keep the original session id for the whole trace anyway** — a receipt records one conversation with a user, not one engine session, the same reason a refresh trace is keyed by `refresh_id` and a `consider` trace by `evaluation_id` — but the trace's one `stage=preview` artifact/`card_presented` pair (`verify` allows exactly one, on every route that renders a card) is reserved for that later, settled card. Do not call `artifact_generated`/`card_presented` for the first, pre-cash card at all: the user still sees it in real time and answers the cash question against it — that presentation is not deferred — but it is an intermediate interaction, not the accepted decision artifact, so only its *receipt event* is deferred, until the recompute settles and the settled card is ready to show. Concretely: record `cash_anchor_checked --cash-outcome provided` when the user answers (before `add-cash` runs), then record the deferred `artifact_generated`/`card_presented` pair once `preview` has rendered the settled card on the session `add-cash` returned — in that order, so the row comes *before* the pair rather than after it, the inverse of `declined`. A `provided` trace whose recorded pair sits at or before this row has kept the superseded, pre-cash card as its decision artifact instead, and `verify` refuses it, naming the row. If a future route needs to audit every intermediate render rather than just the settled one, that is a distinct, repeatable event kind to add — not a reason to overload this exactly-once pair.

`snapshot_review` states cash inline in its own envelope and `test_drive` persists no anchor, so neither carries this requirement at all — `ROUTE_CONTRACTS` says so structurally, independent of anything a trace declares. `first_review` and `weekly_review` do carry it structurally, but a *specific* review can still have no cash-anchor opportunity: a light-tier week's plan carries `input.cash_anchor.status == "not_applicable"` (reason `light_tier`) the same positive way a snapshot's or a test drive's does (`data-contract.md`). Transcribe that status with `--cash-anchor-status not_applicable` at `start` (above, #677), and `verify` requires **zero** `cash_anchor_checked` events instead of one for that trace — recording one anyway, of any outcome, fails as a named contradiction: the plan this trace declares it walked said no check was owed, so an event claiming one happened cannot also be true. A `declined` written to make an honest gap verify is exactly the fabrication this tool exists to catch, not a way around it. Omit the flag and `verify` keeps the original unconditional reading, so a receipt written before it existed still verifies exactly as it did.

Expand Down
Loading
Loading