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:
|
||||
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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
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",
|
||||
[("sym", "confirmDanger")], [])
|
||||
[("sym", "confirmDanger")], [], REAL_CODE)
|
||||
assert out["divergence"] == div
|
||||
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"]
|
||||
|
||||
Reference in New Issue
Block a user