Stop false 'keccak/unstructured storage is unreachable' skips; gate skip justifications#39
Draft
shellygr wants to merge 7 commits into
Draft
Stop false 'keccak/unstructured storage is unreachable' skips; gate skip justifications#39shellygr wants to merge 7 commits into
shellygr wants to merge 7 commits into
Conversation
Generated specs skipped properties whose state lives in keccak-derived storage slots, citing CVL inexpressibility. Keccak slots are compile-time constants reachable via Sload/Sstore hooks, ghosts, or harness getters; five coordinated changes stop both the author and the judge from blessing such skips: - prop_inference.py: narrow backend-guidance item 2 to hash collisions/inversion; keccak-derived slot constants and unstructured storage are explicitly in scope. - property_generation_prompt.j2: new <storage_access> block (slot-constant definitions, Sload/Sstore hooks incl. KEY patterns, ghost mirroring, harness getters, KB search pointer). - property_judge_system_prompt.j2: "No Source Changes" becomes "No Protocol Source Changes" -- harnesses are verification infrastructure. The demand-a-harness wording is gated on harness_augmentation (default(false), a no-op until harness augmentation lands); the fallback labels such skips "missing harness support, not CVL inexpressibility". - property_judge_prompt.j2 Criteria 7: capability checklist (slot constant + hook, harness getter/direct storage, ghost + summary) the judge must run before accepting a "not expressible" skip. - cvl_generation.py: _RecordSkipSchema requires alternatives_considered; access-shaped skip reasons (keccak/storage slot/unstructured/no getter/cannot access|observe) are rejected until each triggered capability is addressed. Adds render smoke tests for the modified templates (with/without harness_augmentation) and skip-gate acceptance/rejection tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, collision carve-out Three review findings on the skip-gate change: - property_judge_prompt.j2: the Criteria 7 immutability carve-out still blessed any skip fixable "with Solidity code changes", contradicting the checklist item (b) six lines above and the protocol-vs-harness system prompt. It is now scoped to *protocol* code only, and explicitly states that needing a harness getter/wrapper does not validate a skip. - SkippedProperty carries alternatives_considered (defaulted for cached pre-field instances) and the property_feedback_judge input renders each alternative under the skip line, so the judge audits their substance instead of the gate keyword-matching them and dropping the text. The field stays out of _compute_digest (skip identity remains title+reason). - The bare "keccak" gate trigger fired on collision/inversion/preimage skips, which the guidance keeps legitimate; the trigger now requires keccak near "slot"/"storage" (the access-shaped usage the gate targets). Adds tests: collision reason passes the gate, alternatives land on the recorded skip, back-compat deserialization without the new field, and a render pin that the old blanket immutability wording is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review finding: the Criteria 7 directives ("if any of these mechanisms
applies, reject the skip"; "needing a harness getter or wrapper does NOT
make a skip valid") told the judge to reject harness-requiring skips even
in pipelines where the author has no tool to create harnesses, tensioning
with the system prompt's harness_augmentation=false fallback and burning
author/judge rounds until the rebuttal escape hatch fired.
Criteria 7 now branches on the same harness_augmentation variable as the
system prompt (default false): the fallback keeps rejection for the
always-available mechanisms (slot constant + hook, direct storage access,
ghost mirroring) but accepts harness-getter-only skips with the "missing
harness support, not CVL inexpressibility" label, matching the system
prompt wording. FeedbackInherentParams gains the NotRequired param so
PR 2 can bind both templates consistently; current call sites omit it.
Render tests pin both branches and omitted==explicit-false equivalence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…shaped skip triggers
The judge audits alternatives_considered (feedback.py surfaces them), so
mutating them after validation must stale the PROVER stamp; empty lists
contribute nothing, keeping cached pre-field digests valid. Also widen the
skip-gate triggers to `cannot (be )?(access|observ)` so passive phrasings
("cannot be accessed/observed") no longer bypass the gate. Tests pin both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In greenfield the author defines the contract API through the stub, so a missing getter is never a capability gap: getter-shaped skips stay rejectable there instead of being accepted with the fallback label. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shellygr
commented
Jul 3, 2026
| # about collisions/inversion/preimages of the hash function itself — so the keccak trigger | ||
| # requires the word near "slot"/"storage", the access-shaped usage this gate targets. | ||
| _KECCAK_SLOT = r"keccak\S*.{0,40}(?:slot|storage)|(?:slot|storage).{0,40}keccak" | ||
| _SKIP_GATE_CHECKS: list[tuple[re.Pattern[str], str, re.Pattern[str]]] = [ |
Contributor
Author
There was a problem hiding this comment.
claude, have you THOUGHT LONG AND HARD ABOUT THIS?
The record_skip gate no longer does NLP-in-regex over LLM prose (_SKIP_GATE_CHECKS/_KECCAK_SLOT are gone). The author instead self-classifies every skip via a required reason_category Literal on the tool schema; the gate is purely structural: a storage_access skip cannot be recorded until alternatives_considered is non-empty. The judge polices classification honesty — the category is surfaced in its input and Criteria 7 instructs it to audit access-shaped reasons categorized otherwise as storage_access. SkippedProperty defaults the category to "other" for cache back-compat, and only non-default categories join _compute_digest (the same empty-contributes-nothing scheme as alternatives_considered), so cached pre-field skips keep their legacy digests and validation stamps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mirrored NotRequired harness_augmentation fields on FeedbackInherentParams/JudgeSystemParams (aligned only by comments) are gone. property_feedback_judge now takes harness_augmentation: bool = False and injects it into BOTH the task-prompt and system-prompt binds via one helper, so the two judge templates cannot disagree; a pipeline that gains harness augmentation flips exactly one Python argument. The templates' default(false) remains as a render fail-safe only. Render tests assert the harness wording is present/absent in both templates together for flag true and false, including through the _bind_harness_augmentation injection path itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Reworked per the architecture review — delta since the previous version (aef046a → b7cf58e):
|
shellygr
commented
Jul 4, 2026
| key compromising, phishing, etc.) | ||
| 2. Reasoning about hash function behavior or hash collisions (e.g., | ||
| "invalid signatures should be rejected") | ||
| 2. Reasoning about hash function *collisions or inversion* (e.g., |
Contributor
Author
There was a problem hiding this comment.
We need to see how this updated section interacts with proper erc-7201 annotations. It may apply in cases where proper annotations do not exist or that we failed to parse them.
shellygr
commented
Jul 4, 2026
| If the property being skipped could be formalized with Solidity code changes, the skip is VALID. Do *NOT* reject | ||
| skips with advice to change the Solidity code. The Solidity code is *IMMUTABLE* and suggestions to change the Solidity | ||
| code are INVALID. | ||
| If the property being skipped could be formalized only with *protocol* Solidity code changes, the skip is VALID. |
Contributor
Author
There was a problem hiding this comment.
to adapt to John's edit agent
shellygr
commented
Jul 4, 2026
|
|
||
| {% if sort != "greenfield" %} | ||
| ## No Source Changes | ||
| ## No Protocol Source Changes |
Contributor
Author
There was a problem hiding this comment.
n.b. protocol source changes means we do not change the original files, but source changes involving harnesses are allowed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of 6 related spec-quality PRs derived from comparing generated specs against hand-written specs from a recent customer engagement (generated specs skipped properties claiming keccak-derived storage slots are unreachable in CVL — they are not).
What
prop_inference.pyCERTORA_BACKEND_GUIDANCE: hash collision/inversion reasoning remains out of scope, but keccak-derived storage slots are compile-time constants — hookable viaSload/Sstoreand exposable via harness getters.property_generation_prompt.j2: new<storage_access>block (slot-constant definitions, slot hooks, ghost mirroring, harness getters, KB pointers).property_judge_system_prompt.j2: "No Source Changes" → "No Protocol Source Changes"; harnesses are verification infrastructure. Harness-augmentation wording is injected byproperty_feedback_judge(harness_augmentation: bool = False)into both judge templates through one bind helper (no-op until the main-harness PR flips the argument).property_judge_prompt.j2Criteria 7: judge must check slot-hook / harness-getter / ghost+summary expressibility before accepting a "not expressible" skip, and audits access-shaped skip reasons whose declared category isn'tstorage_access; greenfield keeps unconditional rejection (author controls the stubs).cvl_generation.py_RecordSkipSchema: requiredreason_category: Literal["storage_access", "hash_collision", "prover_limitation", "environment", "other"]plus requiredalternatives_considered. The gate is purely structural: astorage_accessskip cannot be recorded untilalternatives_consideredis non-empty — no text matching over LLM prose. Category and alternatives are surfaced verbatim to the judge and covered by the validation digest (mutating them stales the stamp; default"other"category and empty alternatives hash like legacy for cache back-compat).Design revisions after architecture review
_SKIP_GATE_CHECKS/_KECCAK_SLOT) deleted — NLP-in-regex over LLM prose was brittle; the author now self-classifies via thereason_categoryenum and the gate checks structure only.storage_access(judge polices semantics, not a regex).reason_categoryjoins_compute_digestonly when non-default, so cached pre-field skips keep their legacy digests and stamps.NotRequiredharness_augmentationfields onFeedbackInherentParams/JudgeSystemParams(aligned only by comments) removed — the flag now threads once throughproperty_feedback_judgeinto both template binds, so the two judge prompts cannot disagree.Cross-PR interactions
harness_augmentationis flipped to true by the ap-main-harness PR (Main-contract augmentation harness: unstructured-storage getters, helper decomposition, verify-target threading #40) changing exactly one Python argument at theproperty_feedback_judgecall site; safe to merge in either order.reason_category+ structural gate + digest coverage + judge-audit design is the shared "structured exception ledger" pattern that Vacuity detection + repair ladder: fix setup root causes instead of filtering methods #41 (vacuity repair) reuses foracknowledge_vacuous_method; Vacuity detection + repair ladder: fix setup root causes instead of filtering methods #41 rebases on this PR's_compute_digestand judge-input plumbing.property_generation_prompt.j2/property_judge_prompt.j2in disjoint sections from ap-vacuity-repair and ap-cvlmath; later-merging PR rebases mechanically.Tests
uv run pytest tests/ -q -m "not expensive" --ignore=tests/test_rag_db.py --ignore=tests/test_token_usage.py→ 137 passed, 3 skipped, 3 deselected (ignored files fail identically on master: Postgres testcontainer / model-registry env).tests/test_cvl_skips.pyrewritten for the enum gate (structural gating, digest back-compat, judge surfacing);tests/test_template_render.pygained the cross-template agreement tests. Pyright on changed files: 0 errors.🤖 Generated with Claude Code