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: if stamp_shapes and project_id:
try: try:
divergence = await shape_ledger_svc.write_time_divergence( divergence = await shape_ledger_svc.write_time_divergence(
project_id, path, stamp_shapes, stamped project_id, path, stamp_shapes, stamped, code or "",
) )
except Exception: except Exception:
logger.warning("write-time divergence check failed", exc_info=True) 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. # precision comes from the in-play test above, not from this window.
PULL_WINDOW = timedelta(hours=6) 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: def snippet_kind(symbol: str, language: str) -> str:
"""The ledger kind a snippet's reference belongs to — "css" when its """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), 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 {} 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( async def stamp_write_path_instances(
user_id: int, user_id: int,
project_id: int, project_id: int,
@@ -961,7 +1138,15 @@ async def stamp_write_path_instances(
return [] return []
# Which pulled canons are in play for this payload, by kind, ranked. # 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(): for sid, pulled_at in pulled.items():
note = await snippets_svc.get_snippet(user_id, sid) note = await snippets_svc.get_snippet(user_id, sid)
if note is None: if note is None:
@@ -971,11 +1156,19 @@ async def stamp_write_path_instances(
kind = snippet_kind(symbol, fields.get("language") or "") kind = snippet_kind(symbol, fields.get("language") or "")
if references_symbol(code, symbol, kind): if references_symbol(code, symbol, kind):
rank, why = 2, f"hook: pulled #{sid}; payload references `{_norm_symbol(symbol)}`" 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})" rank, why = 1, f"hook: pulled #{sid}; payload resembles it ({resembles[sid]:.2f})"
else: 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 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: if not in_play:
return [] return []
for bucket in in_play.values(): for bucket in in_play.values():
@@ -998,8 +1191,31 @@ async def stamp_write_path_instances(
bucket = in_play.get(kind) bucket = in_play.get(kind)
if not bucket: if not bucket:
continue continue
_rank, _at, sid, why = bucket[0]
row = by_key.get((name, kind)) 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 row is None:
if not repo_key: if not repo_key:
continue continue
@@ -1671,6 +1887,24 @@ async def confirm_proposals(
# A canon dominates a directory+kind when at least this many siblings are # 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. # 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_MIN_JUDGED = 3
_DENSITY_SHARE = 0.6 _DENSITY_SHARE = 0.6
@@ -1713,11 +1947,20 @@ async def canon_density(project_id: int, path: str, kind: str) -> tuple[int, int
) )
).scalars().all() ).scalars().all()
siblings = [r for r in rows if _dir_of(r.path) == directory] 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( async def write_time_divergence(
project_id: int, path: str, shapes: list[tuple[str, str]], stamped: list[dict], project_id: int, path: str, shapes: list[tuple[str, str]], stamped: list[dict],
code: str = "",
) -> list[dict]: ) -> list[dict]:
"""The in-band check for the shapes the hook named at ``path``: for each """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 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) dom = density.get(kind)
if not dom: if not dom:
continue continue
sid, n, judged = dom sid, n, judged, cform = dom
if just_stamped.get((name, kind)) == sid: if just_stamped.get((name, kind)) == sid:
continue continue
row = by_key.get((name, kind)) row = by_key.get((name, kind))
@@ -1753,6 +1996,19 @@ async def write_time_divergence(
row.status != "unclassified" or row.proposed_snippet_id == sid row.status != "unclassified" or row.proposed_snippet_id == sid
): ):
continue 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, out.append({"symbol": name, "kind": kind, "canon_snippet_id": sid,
"instances": n, "judged": judged}) "instances": n, "judged": judged})
return out return out
@@ -1856,6 +2112,9 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
flagged = 0 flagged = 0
for siblings in by_dir.values(): for siblings in by_dir.values():
dom = dominant_canon(siblings) 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: for r in siblings:
if r.status not in _MECHANICAL_TODO: if r.status not in _MECHANICAL_TODO:
continue continue
@@ -1866,6 +2125,12 @@ async def flag_divergence(project_id: int, *, since: datetime | None) -> int:
continue continue
if r.proposed_snippet_id == dom[0]: if r.proposed_snippet_id == dom[0]:
continue # the proposer already says "instance of the canon" 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] r.diverges_from = dom[0]
flagged += 1 flagged += 1
await session.commit() await session.commit()
+265
View File
@@ -0,0 +1,265 @@
"""A canon may only be urged on a shape that could plausibly BE it (#4204).
WHAT WAS WRONG. `dominant_canon` is a base rate: it answers "what is most
common in this directory" and never "is this that". With `kind` carrying only
`css | sym`, a frozen dataclass, a module constant, a sync predicate, a class
and an async service function were all siblings, so the prior was the entire
argument — it told a registry module of pure helpers to build from the
`async_session` service canon. Upstream, the auto-stamp had no resemblance
floor at all (`elif sid in resembles`), so 0.69 asserted as confidently as
0.95, and one payload-level score was applied to every symbol in the file.
The two halves compound: loose stamping manufactures the density that the
divergence check then reads as authority. Both are fixed by the same
primitive, and it is tested here on REAL signatures taken from the ledger —
Python, Go, TypeScript and Vue — rather than invented ones, because a
classifier that only works on the examples its author imagined is the failure
this is meant to end.
The unknown case is tested hardest. `forms_agree` requires BOTH sides to be
known, so an unreadable signature makes the checks quieter rather than more
confident. A guard that treats "I cannot tell" as "match" is the shape of the
bug, not the fix.
"""
from __future__ import annotations
import pytest
from scribe.services.shape_ledger import (
FORM_UNKNOWN, canon_form, forms_agree, shape_form, signature_in,
)
class _Row:
"""A ledger row, as `canon_form` reads one."""
def __init__(self, signature, snippet_id=7, status="instance", kind="sym"):
self.signature, self.snippet_id = signature, snippet_id
self.status, self.kind = status, kind
# ── shape_form, on signatures actually in the ledger ──────────────────────
@pytest.mark.parametrize("signature,want", [
# Python
("class SessionAbsent(RuntimeError):", "type"),
("def build_channel() -> str:", "fn"),
("async def attach(self) -> None:", "async-fn"),
("async def _make_room(self, user_id: int, credential_id: int, opening) -> bool:",
"async-fn"),
("def test_version_regex_rejects_bad_formats():", "fn"),
# Go
("func NewService(pool *pgxpool.Pool, cfg *lidarrconfig.Service) *Service {", "fn"),
("type Store struct {", "type"),
# TypeScript / Vue SFC
("export function useBuild() {", "fn"),
("function offsetWords(minutes: number): string {", "fn"),
("export default class Thing {", "type"),
("interface Lesson {", "type"),
("const handler = async (req) => {", "async-fn"),
("const offset = (n) => n + 1", "fn"),
("const MAX_BUDGET = 10", "binding"),
# Unreadable
("", FORM_UNKNOWN),
(" ", FORM_UNKNOWN),
("# just a comment", FORM_UNKNOWN),
("}", FORM_UNKNOWN),
])
def test_shape_form_reads_real_signatures(signature, want) -> None:
assert shape_form(signature, "sym") == want
def test_css_is_its_own_form_regardless_of_signature() -> None:
assert shape_form(".pin {", "css") == "css"
assert shape_form("", "css") == "css"
def test_the_five_shapes_that_started_this_are_not_service_functions() -> None:
"""The concrete case. #2860 is an `async def` service unit; none of these
is one, and all five were told to build from it."""
canon = shape_form("async def create_note(user_id: int, ...):", "sym")
assert canon == "async-fn"
for sig in (
"class Point:",
"def _p(source, kind, what, **kw) -> tuple[str, Point]:",
"def get_point(source: str) -> Point | None:",
"def is_registered(source: str) -> bool:",
"def sources_expected_to_emit() -> list[str]:",
):
assert not forms_agree(shape_form(sig, "sym"), canon), sig
# ── forms_agree: unknown never matches ────────────────────────────────────
def test_two_known_equal_forms_agree() -> None:
assert forms_agree("fn", "fn")
def test_different_forms_do_not() -> None:
assert not forms_agree("fn", "async-fn")
assert not forms_agree("type", "fn")
@pytest.mark.parametrize("a,b", [
(FORM_UNKNOWN, "fn"), ("fn", FORM_UNKNOWN), (FORM_UNKNOWN, FORM_UNKNOWN),
])
def test_unknown_never_agrees_with_anything(a, b) -> None:
"""Including with itself — "I cannot tell" twice is not a match."""
assert not forms_agree(a, b)
# ── canon_form: a canon whose own rows disagree has no opinion ────────────
def test_a_canon_whose_instances_agree_asserts_that_form() -> None:
rows = [_Row("async def a():"), _Row("async def b():"), _Row("async def c():")]
assert canon_form(rows, 7) == "async-fn"
def test_a_canon_whose_instances_disagree_asserts_nothing() -> None:
"""Portal's #3283 exactly: one snippet recorded as canon for a class, a
plain def, an async def and a dozen tests. That spread is the SYMPTOM of
loose stamping, so reading it as "no opinion" makes the two fixes
cooperate — a poisoned canon falls silent instead of flagging others."""
rows = [_Row("class A:"), _Row("def b():"), _Row("async def c():")]
assert canon_form(rows, 7) == FORM_UNKNOWN
def test_a_clear_majority_still_asserts() -> None:
rows = [_Row("def a():"), _Row("def b():"), _Row("def c():"), _Row("class D:")]
assert canon_form(rows, 7) == "fn"
def test_rows_of_other_canons_and_unjudged_rows_do_not_vote() -> None:
rows = [
_Row("def a():"), _Row("def b():"), _Row("def c():"),
_Row("class X:", snippet_id=9), # another canon
_Row("class Y:", status="unclassified"), # not judged
_Row("class Z:", status="variant"), # a departure, not a vote
]
assert canon_form(rows, 7) == "fn"
def test_a_canon_with_no_readable_signatures_asserts_nothing() -> None:
assert canon_form([_Row(""), _Row(""), _Row("")], 7) == FORM_UNKNOWN
def test_a_canon_with_no_rows_at_all_asserts_nothing() -> None:
assert canon_form([], 7) == FORM_UNKNOWN
# ── signature_in: the payload is where a brand-new shape lives ────────────
CODE = '''
import re
MAX = 10
class Point:
"""A point."""
async def fetch(user_id: int) -> None:
...
def helper(x):
return x
export function useBuild() {
'''
@pytest.mark.parametrize("symbol,want_form", [
("Point", "type"),
("fetch", "async-fn"),
("helper", "fn"),
("useBuild", "fn"),
("MAX", "binding"),
])
def test_a_definition_in_the_payload_is_found(symbol, want_form) -> None:
assert shape_form(signature_in(CODE, symbol, "sym"), "sym") == want_form
def test_a_symbol_that_is_only_CALLED_is_not_a_definition() -> None:
"""The conflation worth keeping out: referencing a name is evidence of
USE, never of being that shape."""
assert signature_in("value = helper(3)\nreturn fetch(x)", "helper", "sym") == ""
def test_a_missing_symbol_yields_nothing_rather_than_a_guess() -> None:
assert signature_in(CODE, "nowhere", "sym") == ""
assert shape_form(signature_in(CODE, "nowhere", "sym"), "sym") == FORM_UNKNOWN
def test_an_empty_payload_yields_nothing() -> None:
assert signature_in("", "Point", "sym") == ""
def test_a_css_class_is_found_by_its_selector() -> None:
assert signature_in(".pin { color: red; }", "pin", "css").startswith(".pin")
assert signature_in(".pin { }", ".pin", "css").startswith(".pin")
def test_a_near_name_is_not_matched() -> None:
"""Word-bounded, so `helper` never claims `helper_two`."""
assert signature_in("def helper_two(x):\n ...", "helper", "sym") == ""
# ── The burden scales with the evidence ───────────────────────────────────
#
# Two kinds of evidence reach the stamp, and treating them alike was the
# first version of this fix and was wrong in both directions. An explicit
# by-name reference — the payload literally names the canon's symbol — is
# strong; demanding positive form agreement there silenced it whenever the
# 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 whole-file resemblance SCORE is weak: one number speaks for every symbol
# in the file, which is precisely how a class, an async method and a private
# helper were all recorded as instances of one snippet.
from scribe.services.shape_ledger import _stamp_allowed, forms_conflict # noqa: E402
@pytest.mark.parametrize("a,b,want", [
("fn", "type", True),
("async-fn", "fn", True),
("fn", "fn", False),
(FORM_UNKNOWN, "fn", False), # cannot contradict what you cannot read
("fn", FORM_UNKNOWN, False),
(FORM_UNKNOWN, FORM_UNKNOWN, False),
])
def test_forms_conflict_needs_both_sides_readable(a, b, want) -> None:
assert forms_conflict(a, b) is want
def test_a_named_reference_stands_unless_the_forms_contradict() -> None:
assert _stamp_allowed(2, "fn", "fn")
assert _stamp_allowed(2, FORM_UNKNOWN, "fn") # unreadable shape, strong evidence
assert _stamp_allowed(2, "fn", FORM_UNKNOWN)
assert not _stamp_allowed(2, "type", "fn") # a class is not that function
def test_a_resemblance_score_must_positively_agree() -> None:
"""The weak path, and the one that produced the mess being fixed."""
assert _stamp_allowed(1, "fn", "fn")
assert not _stamp_allowed(1, FORM_UNKNOWN, "fn")
assert not _stamp_allowed(1, "fn", FORM_UNKNOWN)
assert not _stamp_allowed(1, "type", "fn")
def test_the_portal_case_no_longer_stamps_one_canon_onto_a_whole_file() -> None:
"""A class, an async method and a sync helper in one file, against one
payload-level score. Previously all three were stamped; now the score
alone cannot carry any of them, and only a matching form could."""
canon = "async-fn"
written = [("class SessionAbsent(RuntimeError):", False),
("async def attach(self) -> None:", True),
("def _run_control_client(self, command: str) -> None:", False)]
for signature, should_stamp in written:
assert _stamp_allowed(1, shape_form(signature, "sym"), canon) is should_stamp
def test_the_resemblance_floor_is_above_the_retrieval_floors() -> None:
"""An unattended WRITE about the codebase should need more evidence than
a suggestion shown to a reader — the retrieval bars sit near 0.70."""
from scribe.services.shape_ledger import _RESEMBLE_MIN
assert _RESEMBLE_MIN >= 0.80
+4 -1
View File
@@ -1638,8 +1638,11 @@ async def test_the_write_time_divergence_check_is_named_in_band():
1, "frontend/src/components/Danger.vue", code=REAL_CODE, project_id=24, 1, "frontend/src/components/Danger.vue", code=REAL_CODE, project_id=24,
stamp_shapes=[("sym", "confirmDanger")], stamp_shapes=[("sym", "confirmDanger")],
) )
# The payload travels with the call now (#4204): the check compares the
# shape being written against the canon's form, and a shape written right
# now may exist nowhere but in this code.
check.assert_awaited_once_with(24, "frontend/src/components/Danger.vue", check.assert_awaited_once_with(24, "frontend/src/components/Danger.vue",
[("sym", "confirmDanger")], []) [("sym", "confirmDanger")], [], REAL_CODE)
assert out["divergence"] == div assert out["divergence"] == div
assert "Divergence check at `frontend/src/components/Danger.vue`" in out["context"] assert "Divergence check at `frontend/src/components/Danger.vue`" in out["context"]
assert "`confirmDanger` → #2761 (20 of 21 judged siblings are its instances)" in out["context"] assert "`confirmDanger` → #2761 (20 of 21 judged siblings are its instances)" in out["context"]