Approval staleness is currently bound only to the PR head: state_of() in runner/verdict.py returns green iff cf.get("approved_sha") == head_sha, and update_case_file() in runner/casefile.py records only approved_sha. The rubrics_fingerprint() hash (runner/render.py) is recorded purely as provenance — as rubrics_version on run/round archive records, in run ids and dedupe keys, and in rendered meta blocks. Nothing compares it when deciding whether an approval carries forward.
Consequence: if a rubric (or, since #93, a vendored reference document) is edited between rounds, an approval already recorded at the current head stays green. The tightened rubric only takes effect once the PR pushes a new commit. Case files store no rubrics version, so even a manual sweep cannot tell which approvals predate a rubric edit.
Fix sketch: record the fingerprint alongside approved_sha at approval time, and have state_of() treat a mismatch as stale (re-run pending), exactly like a head move.
Design question to settle first — granularity:
- Global fingerprint (the existing
rubrics_fingerprint, which hashes every rubrics/*.md plus rubrics/references/*.md): trivial to wire, but any edit to _common.md, any rubric, or any reference invalidates ALL rubrics' carried-forward approvals on every open PR, forcing a full re-review sweep for a one-line wording tweak in a single rubric.
- Per-rubric prompt fingerprint (hash of
_common.md + <rubric>.md + that rubric's RUBRIC_REFERENCES documents): invalidates exactly the approvals whose prompt text actually changed. _common.md edits still invalidate everything (correctly — every prompt embeds it). This is the targeted option; the per-run prompt_sha256 already recorded is close but includes the PR context, so a dedicated prompt-template hash is needed.
Noticed while reviewing #93, whose comments originally claimed the fingerprint already invalidated carried-forward approvals; the language has been corrected there, and this issue tracks actually building the binding.
Approval staleness is currently bound only to the PR head:
state_of()inrunner/verdict.pyreturnsgreeniffcf.get("approved_sha") == head_sha, andupdate_case_file()inrunner/casefile.pyrecords onlyapproved_sha. Therubrics_fingerprint()hash (runner/render.py) is recorded purely as provenance — asrubrics_versionon run/round archive records, in run ids and dedupe keys, and in rendered meta blocks. Nothing compares it when deciding whether an approval carries forward.Consequence: if a rubric (or, since #93, a vendored reference document) is edited between rounds, an approval already recorded at the current head stays green. The tightened rubric only takes effect once the PR pushes a new commit. Case files store no rubrics version, so even a manual sweep cannot tell which approvals predate a rubric edit.
Fix sketch: record the fingerprint alongside
approved_shaat approval time, and havestate_of()treat a mismatch asstale(re-run pending), exactly like a head move.Design question to settle first — granularity:
rubrics_fingerprint, which hashes everyrubrics/*.mdplusrubrics/references/*.md): trivial to wire, but any edit to_common.md, any rubric, or any reference invalidates ALL rubrics' carried-forward approvals on every open PR, forcing a full re-review sweep for a one-line wording tweak in a single rubric._common.md+<rubric>.md+ that rubric'sRUBRIC_REFERENCESdocuments): invalidates exactly the approvals whose prompt text actually changed._common.mdedits still invalidate everything (correctly — every prompt embeds it). This is the targeted option; the per-runprompt_sha256already recorded is close but includes the PR context, so a dedicated prompt-template hash is needed.Noticed while reviewing #93, whose comments originally claimed the fingerprint already invalidated carried-forward approvals; the language has been corrected there, and this issue tracks actually building the binding.