Skip to content

Record what each published row was measured under - #65

Merged
leggetter merged 1 commit into
mainfrom
snapshot-provenance
Aug 28, 2026
Merged

Record what each published row was measured under#65
leggetter merged 1 commit into
mainfrom
snapshot-provenance

Conversation

@leggetter

Copy link
Copy Markdown
Collaborator

The stale-rows fix for #60.

The problem

export-results --merge carries forward every cell a run did not re-execute. That earns its place — the -no-skills twins refresh monthly, so without it a weekly snapshot would have holes. What it lacked was any notion of a row going out of date.

The 25 August snapshot holds 114 rows across six execution dates spanning thirteen days, 22 of them from 13 August — measured before the sandbox CLI moved to 2.5.0, before fixed sleeps became polling, before several scorer corrections, and before the base prompt gained "Do not ask clarifying questions" yesterday. Nothing in the file said so.

The ranAt field's own comment already described this ("the published grid is not one snapshot… two cells side by side can be a month apart"). It just never acted on it.

The change

Each row now carries two fingerprints, following vercel-labs/agent-eval:

  • harness — the base prompt, the Hookdeck CLI pin, the skills CLI pin, and the skills submodule commit. What every scenario shares.
  • scenario — a walk of the scenario's own files: prompt, scorer, seed, solution, fixtures.

Two rather than one because "this scenario was rewritten" and "the harness moved under everything at once" want different responses, and a single hash cannot distinguish them.

--merge now reports what it is carrying, by execution date, and which rows were measured under something else:

carrying 92 row(s) this run did not measure — by date: 2026-08-13 22, 2026-08-17 44, ...
  66 measured under something else — harness changed: 44, no provenance recorded: 22
    benchmark-dedupe-001-duplicate-events x claude-code-sonnet-5-no-skills  (no provenance recorded)
    ...
  publishing them anyway. Pass --drop-stale to leave the cells absent instead.

It still publishes them. --drop-stale is opt-in, because a snapshot with holes and a snapshot with silent thirteen-day-old rows are both wrong, and which is less wrong depends on what the snapshot is for. A release cut to report a measured change wants it on; a weekly refresh keeping the page populated probably does not. The defect in #60 was that nothing was ever said — not that stale rows were carried.

Rows predating the field report as "no provenance recorded" and are never counted as current. Not knowing what a row was measured under is the condition this exists to surface.

A gap found while doing it

apps/framework had test files that no command ran. It has no test script, so pnpm -r test skipped it entirely — including redact.test.ts, which guards the credential leak that reached a public artifact on the first real run. That test has never been executed by CI.

Added the script. pnpm -r test now covers 13 framework tests that previously ran nowhere, and framework check runs them too.

Verification

pnpm typecheck clean, pnpm -r test green (267 tests, up from 254), pnpm -r build clean. Five new tests cover the scenario fingerprint: stable for identical content, changes on scorer and prompt edits, covers files added later without being told about them, ignores dotfiles and node_modules.

Not in this PR

Re-scoring stored trajectories — τ³-bench's approach, and the clean fix — needs #21 (durable transcripts) first. This makes staleness visible; that would make it repairable without re-running an agent.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nt2Zgjw7STjrnFXYKRRVAA

`export-results --merge` carried forward every cell a run did not
re-execute, with no notion of a row going out of date. The 25 August
snapshot held 114 rows across six execution dates spanning thirteen days
— 22 of them from 13 August, measured before the CLI moved to 2.5.0,
before fixed sleeps became polling, before several scorer corrections and
before the base prompt gained "Do not ask clarifying questions". Nothing
in the file said so, and the `ranAt` comment already described the
problem without acting on it.

Each row now carries two fingerprints, following vercel-labs/agent-eval:
`harness` over the base prompt and the CLI and skills pins, `scenario`
over the scenario's own files. Two rather than one because "this scenario
was rewritten" and "the harness moved under everything at once" want
different responses and a single hash cannot tell them apart.

The merge reports what it is carrying, by date, and which rows were
measured under something else. It still publishes them: `--drop-stale` is
opt-in, because a snapshot with holes and a snapshot with silent
thirteen-day-old rows are both wrong and which is less wrong depends on
what the snapshot is for. The defect in #60 was that nothing was ever
*said*, not that stale rows were carried.

Also: apps/framework had test files that no command ran. `redact.test.ts`
guards the credential leak that reached a public artifact on the first
real run, and has never been executed by CI. Added a `test` script, so
`pnpm -r test` now covers it and the new provenance tests — 13 tests that
previously ran nowhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nt2Zgjw7STjrnFXYKRRVAA
@leggetter
leggetter merged commit eb4f430 into main Aug 28, 2026
2 checks passed
@leggetter
leggetter deleted the snapshot-provenance branch August 28, 2026 09:37
leggetter added a commit that referenced this pull request Aug 28, 2026
…picked

The ticket said "so they receive order events at their endpoint" and named no
endpoint, so any reachable URL satisfied it. Across the six stored cells of this
scenario the agents picked six different receivers: three Event Gateway sources
on hkdk.events, a webhook.site inbox, a mock.hookdeck.com path, and a
localtunnel inside the agent's own sandbox. Five passed.

The sixth is the one worth reading. It failed, and not because the customer was
not subscribed — the agent built the tenant, the destination and the topics, and
reported delivery confirmed. Its tunnel died with the container before scoring.
So the check named "an order event reaches the customer" was discriminating on
whether an improvised receiver outlived the run.

EVAL.ts already carried this as a known hole and deferred it, because fixing it
changes a published scenario. That is the right call to revisit now rather than
later: publishing is held under #66, the scenario fingerprint from #65 records
the change for anyone comparing rows across it, and the next matrix has not been
paid for yet. Doing it after that run would mean measuring this scenario twice.

The ticket now names mock.hookdeck.com/api/v1/acme/orders — the URL SOLUTION.ts
was already delivering to, which is how the mismatch surfaced — and the scorer
counts only destinations pointed there. A customer's endpoint is not the
integrator's to choose.

Verified with score-only rather than an agent run: the solution path passes 2/2,
the unsolved path fails, and a solution edited to deliver to a receiver of its
own choosing now fails where it would previously have passed.

Refs #47

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQzUoMAwEBJWpEGVvVzSjK
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