feat(hash): member-access names verbatim + versioned stamps (#140)#141
Merged
Conversation
The canonical hash alpha-renamed every identifier, so re-pointing an anchored span at a different single-occurrence external symbol (PointsTier.TIER_1 -> TIER_2, b.Del -> b.Keep, ProbeColor.RED -> GREEN) produced a byte-identical hash: the gate stayed green while the claim's prose became false. This is the member-access slice of #77 — the shape that accounts for every reproduced miss. The v2 recipe keeps the property/field component of a member-access expression verbatim instead of alpha-renaming it, in TS/Go/Rust/Python. These positions name an external member, never a local binding, so emitting them verbatim distinguishes "re-pointed at a different symbol" (loud) from "renamed my own local" (still quiet) — rename tolerance is preserved. v1 == v2 minus that single rule, one mode flag, no frozen copy of the old algorithm. Stamps now carry their recipe (`2:` prefix; bare hex = implicit v1). check verifies each stamp under its own recipe, so existing v1 stamps keep passing (with a one-line nudge) until verify re-stamps them as v2; an unrecognized prefix fails closed. Forced re-verify is avoided deliberately — it would launder real v1-missed drift green. One `surf verify` per repo ends the transition. Golden fixtures pin both recipes per language; docs/reference/hash-recipes.md documents the rules and migration. The repo's own hubs are migrated to v2 and affected claim prose updated. Out of scope (stays in #77): the full bound/free split for bare free identifiers, the external-corpus validation harness, dogfood claims in hubs/hash.md, and the full version-table governance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
Closes the member-access blind spot in the canonical hash — the shape that accounts for every reproduced miss in #77 — and ships the versioned-stamp migration any hash-recipe change needs. This is the actionable slice of #77 carved out in #140; the principled full bound/free split and its governance stay in #77.
The problem
The hash alpha-renamed every identifier to a positional placeholder, so re-pointing an anchored span at a different single-occurrence external symbol produced a byte-identical hash.
checkstayed green while the claim's prose became false:PointsTier.TIER_1→TIER_2Tiers.getHighest→getLowestb.Del(n)→b.Keep(n)ProbeColor.RED→GREENWhat changed
property_identifier(member_expression), Go/Rustfield_identifier(selector/field expression), Python theattributename. These positions name an external member, never a local binding, so emitting them verbatim distinguishes "re-pointed at a different symbol" (loud) from "renamed my own local" (still quiet — rename tolerance preserved).v1 ≡ v2minus this one rule: a single mode flag, no frozen copy of the old algorithm.2:prefix; bare hex = implicit v1).checkverifies each stamp under its own recipe, so existing v1 stamps keep passing — with a one-line nudge — untilsurf verifyre-stamps them as v2. An unrecognized prefix fails closed. Forced re-verify is deliberately avoided: it would launder real v1-missed drift green. Onesurf verifyper repo ends the transition.docs/reference/hash-recipes.mddocuments the rules and migration.surf checkis green on v2 with no nudge.Out of scope — stays in #77
Full bound/free split for bare free identifiers; external-corpus validation harness; dogfood claims in
hubs/hash.md; full version-table governance.Verification
All gates run locally on this branch:
cargo fmt --all --check— clean (exit 0)cargo clippy --all-targets --all-features -- -D warnings— clean (exit 0)cargo test --all— 202 passed, 0 failed across all cratessurf check(repo dogfood) —all anchored spans match their stored hashesNew coverage: the five probes flip green→DIVERGED (v1-blind / v2-loud, all four families); rename tolerance preserved (consistent local + receiver renames stay quiet); clean-v1 nudge; changed-v1 still fails; v1→v2 verify upgrade is idempotent; unknown-version fails closed;
2:stamp YAML round-trip; member-access golden digests pinned per recipe.Closes #140
🤖 Generated with Claude Code