fix(ledger): a canon is only urged on a shape that could be it (#4204)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Failing after 51s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 25s

Two halves of one defect, found while writing #3431 and measured on a second
project.

THE DIVERGENCE CHECK WAS A BASE RATE. `dominant_canon` answers "what is most
common in this directory" and never "is this that" — the candidate's
signature was not examined at all. With `kind` carrying only `css | sym`, a
frozen dataclass, a module constant, a sync predicate, a class and an async
service function are all siblings, so the prior was not merely the best
signal, it was the only one. Writing a registry module of pure helpers
produced five prompts to build them from the `async_session` service canon.

THE AUTO-STAMP HAD NO FLOOR. `elif sid in resembles` took any score at all:
0.69 asserted as confidently as 0.95, and the number went into the reason
line without ever being compared to anything. Worse, the score is computed
against the WHOLE PAYLOAD, so one number spoke for every symbol in the file.
On Portal that recorded `class SessionAbsent`, `def build_channel`,
`async def attach` and a dozen test functions as instances of one snippet —
17 rows under #3283, which then made that directory "canon-dense" and started
instructing every later writer in it.

The two compound: loose stamping manufactures the density the divergence
check reads as authority. Both are fixed by one primitive.

`shape_form` derives a coarse form — css / type / async-fn / fn / binding —
from the signature, on READ. `kind` is part of the row identity, so widening
that column needs a migration and a re-extract (#4204 option 2, still the
principled fix); deriving costs nothing and is reversible. Every caller asks
`shape_form`, so the day the column carries the answer it returns that.

THE BURDEN SCALES WITH THE EVIDENCE, and getting this wrong was the first
version. A by-name reference — the payload names the canon's symbol — is
strong and needs only the absence of contradiction; demanding positive
agreement there silenced it whenever a shape's definition was not in the
payload (an Edit rather than a Write), turning strong evidence into none for
a reason unrelated to the code. A resemblance score is weak and must
positively agree.

`canon_form` reads the form a canon's own judged rows agree on, and returns
unknown when they disagree. That makes the halves cooperate: a canon already
poisoned by loose stamping — Portal's #3283 — falls silent instead of
flagging anyone else.

`forms_agree` requires BOTH sides known, so an unreadable signature makes the
checks quieter rather than more confident. `_RESEMBLE_MIN` is 0.80 rather
than the retrieval floors near 0.70: those decide whether to SHOW a record,
where being wrong costs a glance; this decides whether to RECORD a claim
unattended, where being wrong misinstructs everyone who writes there after.

Caught by the tests, not by review: a first pass required `const`/`let`/`var`
before a binding, so every Python module constant read as unreadable and a
whole form was silently excluded from both checks.

Not addressed: rows already carrying a wrong snippet_id are not undone by a
guard at the point of classification (lesson #4202). Portal's 17 keep
producing dominance until something re-judges them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-20 21:16:51 -04:00
co-authored by Claude Opus 5
parent e2c3a5c2b5
commit 947203fa44
4 changed files with 541 additions and 8 deletions
+1 -1
View File
@@ -2028,7 +2028,7 @@ async def build_write_path_hint(
if stamp_shapes and project_id:
try:
divergence = await shape_ledger_svc.write_time_divergence(
project_id, path, stamp_shapes, stamped
project_id, path, stamp_shapes, stamped, code or "",
)
except Exception:
logger.warning("write-time divergence check failed", exc_info=True)
+271 -6
View File
@@ -868,6 +868,170 @@ async def snippet_consumers(user_id: int, note_id: int) -> dict:
# precision comes from the in-play test above, not from this window.
PULL_WINDOW = timedelta(hours=6)
# ── WHAT KIND OF THING a shape is, past `css | sym` (#4204) ──────────────
#
# THE PROBLEM THIS EXISTS FOR. `kind` has exactly two values, so a frozen
# dataclass, a module constant, a sync predicate, a class and an async service
# function are all siblings of one another. Nothing downstream could possibly
# discriminate on a distinction the column does not carry — which is how a
# directory base rate ended up standing alone as the entire divergence
# argument. It was not chosen over a better signal; it was the only signal
# there was. Measured consequence: writing a registry module of pure helpers
# produced five prompts to build them from the `async_session` service canon,
# and on another project one snippet had been recorded as the canon for a
# `class`, a plain `def`, an `async def` and a dozen test functions at once.
#
# DERIVED FROM THE SIGNATURE ON READ, not stored. `kind` is part of the row
# identity — ("project_id", "repo_key", "path", "symbol", "kind") — so
# widening that column rewrites every row's key and needs a migration plus a
# re-extract. Reading the form off the signature costs nothing, needs no
# migration, and is reversible. Widening `kind` remains the principled fix and
# this does not foreclose it: every caller below asks `shape_form`, so the day
# the column carries the answer, this function returns it instead.
#
# DELIBERATELY COARSE, and it must stay that way. It answers "could these
# plausibly be the same kind of thing", never "what language construct is
# this". A finer taxonomy would need per-language parsing and would start
# disagreeing with itself across the four languages this ledger already holds
# (Python, Go, TypeScript, Vue SFC) — and a classifier that is wrong in a new
# way is worse than the coarse one it replaced.
FORM_UNKNOWN = ""
# How much a canon's own instances must agree before it may assert a form.
# A canon whose rows disagree about what they are cannot tell anyone else what
# to be — and that disagreement is the SYMPTOM of the bad stamping this same
# change fixes, so reading it as "no opinion" makes the two halves cooperate:
# a canon poisoned by loose stamping falls silent instead of flagging.
_FORM_SHARE = 0.6
# Leading words that say nothing about the form of the thing being declared.
_FORM_NOISE = ("export ", "default ", "public ", "private ", "static ", "final ")
# A declared TYPE, across the languages in play. Coarse on purpose: a Go
# struct, a TS interface and a Python class are one bucket because the
# question is only ever "is the other thing also a type".
_FORM_TYPE_WORDS = ("class ", "interface ", "type ", "struct ", "enum ")
_FORM_FN_WORDS = ("def ", "function ", "func ", "fn ", "sub ")
# THE DECLARING KEYWORD IS OPTIONAL, because Python has none. A module
# constant is written `MAX_BUDGET = 10` — no `const`, no `let` — and a
# pattern that required one classified every Python constant as unreadable,
# which then read downstream as "do not assert" and quietly excluded a whole
# form from both checks. Caught by the payload test, not by review.
_FORM_BINDING = re.compile(r"^(?:(?:const|let|var)\s+)?[\w$]+\s*(?::[^=]+)?=")
_FORM_ARROW = re.compile(
r"^(?:(?:const|let|var)\s+)?[\w$]+\s*(?::[^=]+)?=\s*(async\s*)?\("
)
def shape_form(signature: str, kind: str = "sym") -> str:
"""The structural form of a shape: css / type / async-fn / fn / binding.
Returns FORM_UNKNOWN when the signature does not say, and every caller
treats that as "do not assert", never as "no match". An unreadable
signature must make this quieter, not more confident.
"""
if kind == "css":
return "css"
sig = (signature or "").strip()
if not sig:
return FORM_UNKNOWN
changed = True
while changed:
changed = False
for lead in _FORM_NOISE:
if sig.startswith(lead):
sig, changed = sig[len(lead):].lstrip(), True
if sig.startswith(_FORM_TYPE_WORDS):
return "type"
if sig.startswith("async "):
return "async-fn"
if sig.startswith(_FORM_FN_WORDS):
return "fn"
m = _FORM_ARROW.match(sig)
if m:
return "async-fn" if m.group(1) else "fn"
if _FORM_BINDING.match(sig):
return "binding"
return FORM_UNKNOWN
def forms_agree(a: str, b: str) -> bool:
"""Do two forms match well enough to assert a relationship?
BOTH must be known. "Unknown equals unknown" would make two shapes nobody
can read into a confident pair, which is the failure this whole change is
about — a guess dressed as a finding.
"""
return bool(a) and bool(b) and a == b
def forms_conflict(a: str, b: str) -> bool:
"""Do two KNOWN forms rule each other out?
The weaker sibling of `forms_agree`, and the pair exists because the two
kinds of evidence this ledger acts on deserve different burdens.
An explicit by-name reference — the payload literally names the canon's
symbol — is strong, so it needs only the absence of a contradiction: stamp
unless the forms are both readable and different. A payload-level
RESEMBLANCE SCORE is weak, computed against the whole file, so one score
speaks for every symbol in it; that needs positive agreement before
asserting anything, which is `forms_agree`.
Demanding agreement everywhere was the first version of this and it was
wrong: it silenced the by-name path whenever a shape's definition was not
in the payload — an Edit rather than a Write — turning strong evidence
into no evidence for a reason that has nothing to do with the code.
"""
return bool(a) and bool(b) and a != b
def canon_form(rows: Iterable, snippet_id: int) -> str:
"""The form a canon's own judged rows agree on, or FORM_UNKNOWN."""
forms: dict[str, int] = {}
for r in rows:
if r.snippet_id != snippet_id or r.status not in ("canonical", "instance"):
continue
f = shape_form(getattr(r, "signature", "") or "", r.kind)
if f:
forms[f] = forms.get(f, 0) + 1
if not forms:
return FORM_UNKNOWN
top, n = max(forms.items(), key=lambda kv: kv[1])
return top if n / sum(forms.values()) >= _FORM_SHARE else FORM_UNKNOWN
def signature_in(code: str, symbol: str, kind: str) -> str:
"""The line in ``code`` that DEFINES ``symbol``, or "" if none does.
The write-time checks are asked about a shape that may have no ledger row
yet — it is being written right now — so the payload is the only place its
signature exists. Finding nothing returns "", which reads downstream as
FORM_UNKNOWN and therefore as silence.
"""
if not code or not symbol:
return ""
name = re.escape(symbol.lstrip("."))
if kind == "css":
pat = re.compile(r"^\s*\." + name + r"\b")
else:
pat = re.compile(
r"^\s*(?:(?:export|default|public|private|static|final)\s+)*"
r"(?:"
r"(?:(?:async\s+)?(?:def|function|func|fn|sub)|class|interface|type"
r"|struct|enum|const|let|var)\s+" + name + r"\b"
# A bare binding — `MAX = 10`, `Handler = ...` — which is how
# Python (and plain JS assignment) declares one.
r"|" + name + r"\s*(?::[^=\n]+)?=(?!=)"
r")"
)
for line in code.splitlines():
if pat.match(line):
return line.strip()
return ""
def snippet_kind(symbol: str, language: str) -> str:
"""The ledger kind a snippet's reference belongs to — "css" when its
symbol is a class selector (or it is a stylesheet with no symbol),
@@ -920,6 +1084,19 @@ async def recent_pulls(user_id: int, *, window: timedelta = PULL_WINDOW) -> dict
return {}
def _stamp_allowed(rank: int, mine: str, canon: str) -> bool:
"""May this evidence assert that a shape of form ``mine`` IS ``canon``?
Named rather than inlined because the asymmetry is the decision, not an
implementation detail: rank 2 (the payload names the canon's symbol) has
only to avoid contradicting, rank 1 (a whole-file similarity score) has to
positively agree. See `forms_conflict` for why both exist.
"""
if rank >= 2:
return not forms_conflict(mine, canon)
return forms_agree(mine, canon)
async def stamp_write_path_instances(
user_id: int,
project_id: int,
@@ -961,7 +1138,15 @@ async def stamp_write_path_instances(
return []
# Which pulled canons are in play for this payload, by kind, ranked.
in_play: dict[str, list[tuple[int, datetime, int, str]]] = {}
#
# THE FORM OF THE CANON TRAVELS WITH IT (#4204), because what gets written
# here is an ASSERTION — "this shape IS that canon" — and it is permanent
# until something re-judges it. The evidence below is payload-level: a
# resemblance score is computed against the whole file, so without a
# per-shape test every symbol in that file inherits one verdict. Measured
# on another project: a single write stamped `class SessionAbsent`,
# `async def attach` and `_run_control_client` as instances of one snippet.
in_play: dict[str, list[tuple[int, datetime, int, str, str]]] = {}
for sid, pulled_at in pulled.items():
note = await snippets_svc.get_snippet(user_id, sid)
if note is None:
@@ -971,11 +1156,19 @@ async def stamp_write_path_instances(
kind = snippet_kind(symbol, fields.get("language") or "")
if references_symbol(code, symbol, kind):
rank, why = 2, f"hook: pulled #{sid}; payload references `{_norm_symbol(symbol)}`"
elif sid in resembles:
elif resembles.get(sid, 0.0) >= _RESEMBLE_MIN:
rank, why = 1, f"hook: pulled #{sid}; payload resembles it ({resembles[sid]:.2f})"
else:
# A SCORE BELOW THE FLOOR IS NOT WEAK EVIDENCE, IT IS NONE. This
# branch used to be `elif sid in resembles`, which took any score
# at all — 0.69 counted exactly as much as 0.95, and the number was
# printed into the reason line while never being tested against
# anything. A row written on that basis is indistinguishable
# afterwards from one written on real evidence.
continue
in_play.setdefault(kind, []).append((rank, pulled_at, sid, why))
in_play.setdefault(kind, []).append(
(rank, pulled_at, sid, why, shape_form(fields.get("signature") or "", kind))
)
if not in_play:
return []
for bucket in in_play.values():
@@ -998,8 +1191,31 @@ async def stamp_write_path_instances(
bucket = in_play.get(kind)
if not bucket:
continue
_rank, _at, sid, why = bucket[0]
row = by_key.get((name, kind))
# PER SHAPE, NOT PER FILE (#4204). The best candidate whose FORM
# matches this shape's — not simply the best candidate. A class and
# a function in one file can no longer be handed the same canon
# because the file as a whole resembled it.
#
# The signature comes from the payload first: a shape being written
# right now may have no ledger row yet, and then the code is the
# only place it exists. No readable signature on either side means
# no stamp — `forms_agree` requires both to be known, so an
# unreadable shape falls silent instead of matching everything.
mine = shape_form(
signature_in(code, name, kind) or getattr(row, "signature", "") or "", kind
)
# THE BURDEN SCALES WITH THE EVIDENCE. Rank 2 is an explicit
# by-name reference to the canon in this very payload; it stands
# unless the forms actively contradict. Rank 1 is a similarity
# score over the whole file — one number that would otherwise
# speak for every symbol in it — so it must positively agree.
cand = next(
(t for t in bucket if _stamp_allowed(t[0], mine, t[4])), None
)
if cand is None:
continue
_rank, _at, sid, why, _cform = cand
if row is None:
if not repo_key:
continue
@@ -1671,6 +1887,24 @@ async def confirm_proposals(
# A canon dominates a directory+kind when at least this many siblings are
# judged (canonical/instance) and this share of them answer to one snippet.
# How much a payload must resemble a canon before the hook may assert, with
# nobody watching, that a shape IS an instance of it.
#
# WHY A FLOOR AT ALL. There was none: any score the semantic arm produced
# counted, so 0.69 asserted as confidently as 0.95, and the score went into
# the reason line without ever being compared to anything. Those rows are
# permanent — they feed `dominant_canon`, which then tells the next writer in
# that directory what to build from — so a thin stamp does not stay thin, it
# compounds.
#
# 0.80 rather than the retrieval floors near 0.70, deliberately. Those bars
# decide whether to SHOW someone a record, where being wrong costs a glance.
# This one decides whether to RECORD a claim about the codebase unattended,
# where being wrong costs a wrong instruction to everyone who writes in that
# directory afterwards. An unattended write should need more evidence than a
# suggestion, not the same.
_RESEMBLE_MIN = 0.80
_DENSITY_MIN_JUDGED = 3
_DENSITY_SHARE = 0.6
@@ -1713,11 +1947,20 @@ async def canon_density(project_id: int, path: str, kind: str) -> tuple[int, int
)
).scalars().all()
siblings = [r for r in rows if _dir_of(r.path) == directory]
return dominant_canon(siblings)
dom = dominant_canon(siblings)
if dom is None:
return None
# The canon's form travels with the count, read from its OWN judged rows.
# `dominant_canon` keeps its three-value shape: it answers "what dominates
# here", which is still a true and separately useful question, and its
# test pins that arithmetic. What changed is that nobody acts on the count
# alone any more.
return dom[0], dom[1], dom[2], canon_form(siblings, dom[0])
async def write_time_divergence(
project_id: int, path: str, shapes: list[tuple[str, str]], stamped: list[dict],
code: str = "",
) -> list[dict]:
"""The in-band check for the shapes the hook named at ``path``: for each
kind whose directory has a dominant canon, the named shapes that are
@@ -1745,7 +1988,7 @@ async def write_time_divergence(
dom = density.get(kind)
if not dom:
continue
sid, n, judged = dom
sid, n, judged, cform = dom
if just_stamped.get((name, kind)) == sid:
continue
row = by_key.get((name, kind))
@@ -1753,6 +1996,19 @@ async def write_time_divergence(
row.status != "unclassified" or row.proposed_snippet_id == sid
):
continue
# DOES THIS SHAPE EVEN RESEMBLE THE CANON? (#4204) Without this the
# line was a pure base rate: "most things here are X, so be X", with
# the candidate never examined. It told a frozen dataclass and three
# pure predicates to build from the `async_session` service canon.
#
# Signature from the payload first — a shape being written now may
# have no row yet, and `forms_agree` needs both sides known, so an
# unreadable one produces silence rather than a guess.
mine = shape_form(
signature_in(code, name, kind) or getattr(row, "signature", "") or "", kind
)
if not forms_agree(mine, cform):
continue
out.append({"symbol": name, "kind": kind, "canon_snippet_id": sid,
"instances": n, "judged": judged})
return out
@@ -1856,6 +2112,9 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
flagged = 0
for siblings in by_dir.values():
dom = dominant_canon(siblings)
# Computed once per directory rather than per row: the canon's
# form is a property of the canon, not of who is being judged.
cform = canon_form(siblings, dom[0]) if dom else FORM_UNKNOWN
for r in siblings:
if r.status not in _MECHANICAL_TODO:
continue
@@ -1866,6 +2125,12 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
continue
if r.proposed_snippet_id == dom[0]:
continue # the proposer already says "instance of the canon"
# The same structural test the write-time check applies
# (#4204). The sweep and the hook must agree about what counts
# as divergence, or an audit contradicts the line the writer
# was shown at the keyboard.
if not forms_agree(shape_form(r.signature or "", r.kind), cform):
continue
r.diverges_from = dom[0]
flagged += 1
await session.commit()