Skip to content

feat(hash): member-access names verbatim + versioned stamps (#140)#141

Merged
Connorrmcd6 merged 1 commit into
mainfrom
feat/140-member-access-hash
Jun 20, 2026
Merged

feat(hash): member-access names verbatim + versioned stamps (#140)#141
Connorrmcd6 merged 1 commit into
mainfrom
feat/140-member-access-hash

Conversation

@Connorrmcd6

Copy link
Copy Markdown
Owner

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. check stayed green while the claim's prose became false:

edit in the anchored span lang before
PointsTier.TIER_1TIER_2 TS green (missed)
Tiers.getHighestgetLowest TS green (missed)
b.Del(n)b.Keep(n) Go green (missed)
ProbeColor.REDGREEN Python green (missed)

What changed

  • v2 recipe keeps the property/field component of a member-access expression verbatim instead of alpha-renaming it — TS property_identifier (member_expression), Go/Rust field_identifier (selector/field expression), Python the attribute name. 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 ≡ v2 minus this one rule: a single mode flag, no frozen copy of the old algorithm.
  • Versioned stamps: stored hashes 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 surf verify re-stamps them as v2. An unrecognized prefix fails closed. Forced re-verify is deliberately avoided: it would launder real v1-missed drift green. One surf verify per repo ends the transition.
  • Golden fixtures pin both recipes per language (plus member-access cases); docs/reference/hash-recipes.md documents the rules and migration.
  • Dogfooded: the repo's own hubs are migrated to v2 and the affected claim prose updated; surf check is 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 --all202 passed, 0 failed across all crates
  • surf check (repo dogfood) — all anchored spans match their stored hashes

New 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

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>
@Connorrmcd6 Connorrmcd6 merged commit ff27763 into main Jun 20, 2026
4 checks passed
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.

check: member-access names verbatim in the hash (v2 recipe) — high-value slice of #77

1 participant