A canon is only urged on a shape that could be it, the ledger can say what looks wrong, and the agent is the judge #173
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
|
|||||||
Reference in New Issue
Block a user