CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Failing after 56s
CI & Build / Python tests (push) Successful in 1m29s
CI & Build / Build & push image (push) Successful in 28s
CI run 7090 caught this; the unit suite could not. Integration job 25763 — `test_a_second_confirm_dialog_is_detected_and_named`, the acceptance case of milestone #2793. WHAT I GOT WRONG. The previous commit gated BOTH halves of the ledger on `forms_agree`. That is right for stamping and backwards for divergence, because the two assert opposite things: STAMPING says "this IS that canon". Agreement in form is evidence FOR the claim, so demanding it is correct. DIVERGENCE says "this is NOT the canon that dominates here — did you mean to?" A form MISMATCH is the PREMISE of that prompt. Requiring the candidate to match the canon silences the check precisely where it belongs. So #2793's case stopped firing: a hand-rolled sync `confirmDanger` in a directory where an async confirm helper is canon read as `fn` against `async-fn`, disagreed, and was dropped. `flag_divergence` returned 0 where the test demands 1, and the write-time check returned nothing where it must name the canon. That is a real flag the milestone exists to produce, and my change removed it. THE FIX. Divergence now gates at FAMILY level — callable {fn, async-fn}, type, value, css — and only on contradiction. A sync function beside an async one is still a fair question. A frozen dataclass told to build from an async service function is not a question at all. WHAT THIS DOES NOT FIX, asserted rather than commented so it fails the day it changes (`test_how_many_of_the_five_the_divergence_gate_actually_silences`): of #4204's five false prompts this silences ONE. `Point` is a type against a callable canon. `_p`, `get_point`, `is_registered` and `sources_expected_to_emit` are callables like the canon and still ask — and at the signature level they are indistinguishable from the #2793 case above, so nothing readable here can separate them. That needs #4204 option 2 (widen `kind` past `css | sym`) or a comparison of meaning rather than form. The stamping half — `_RESEMBLE_MIN` 0.80 and the graded burden — is unchanged and unaffected by this failure. It is also the half that matters more: loose stamping is what manufactures the density the divergence check reads as authority. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
326 lines
13 KiB
Python
326 lines
13 KiB
Python
"""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, families_conflict, forms_agree, shape_family,
|
|
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
|
|
|
|
|
|
def test_how_many_of_the_five_the_divergence_gate_actually_silences() -> None:
|
|
"""HONEST ACCOUNTING, and it is deliberately an assertion rather than a
|
|
comment so it fails the day the answer changes.
|
|
|
|
Divergence gates at FAMILY level and only on contradiction, because a form
|
|
mismatch is the PREMISE of a divergence prompt, not an objection to it —
|
|
see `shape_family`. So of #4204's five false prompts this silences one:
|
|
`Point` is a type and the canon is a callable. The four `def` helpers are
|
|
callables like the canon and still ask.
|
|
|
|
That is not a shortcoming of the gate, it is the limit of the signature:
|
|
at this level those four are indistinguishable from #2793's acceptance
|
|
case, where a sync `confirmDanger` beside an async confirm helper SHOULD
|
|
be flagged. Separating them needs #4204 option 2 (widen `kind`) or a
|
|
comparison of meaning."""
|
|
canon = shape_form("async def create_note(user_id: int, ...):", "sym")
|
|
silenced = {
|
|
sig: families_conflict(shape_form(sig, "sym"), canon)
|
|
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 silenced["class Point:"] is True
|
|
assert sum(silenced.values()) == 1
|
|
|
|
|
|
def test_a_sync_function_beside_an_async_canon_is_still_a_fair_question() -> None:
|
|
"""#2793's acceptance case, which the first version of this change broke:
|
|
gating divergence on `forms_agree` silenced a hand-rolled sync confirm in
|
|
a directory where an async confirm helper is canon — the exact prompt the
|
|
milestone exists to produce."""
|
|
canon = shape_form("async function onTrash() {", "sym")
|
|
mine = shape_form("function confirmDanger() {", "sym")
|
|
assert canon == "async-fn" and mine == "fn"
|
|
assert not forms_agree(mine, canon) # the STAMP gate would refuse
|
|
assert not families_conflict(mine, canon) # the DIVERGENCE gate asks anyway
|
|
|
|
|
|
@pytest.mark.parametrize(("form", "want"), [
|
|
("fn", "callable"), ("async-fn", "callable"), ("type", "type"),
|
|
("binding", "value"), ("css", "css"), (FORM_UNKNOWN, ""),
|
|
])
|
|
def test_every_form_has_a_family_and_unknown_has_none(form, want) -> None:
|
|
assert shape_family(form) == want
|
|
|
|
|
|
@pytest.mark.parametrize(("a", "b"), [
|
|
(FORM_UNKNOWN, "fn"), ("fn", FORM_UNKNOWN), (FORM_UNKNOWN, FORM_UNKNOWN),
|
|
])
|
|
def test_an_unreadable_form_never_silences_a_divergence_prompt(a, b) -> None:
|
|
"""Same direction as everywhere else in this module: not knowing makes the
|
|
check quieter about ASSERTING and never quieter about ASKING."""
|
|
assert not families_conflict(a, b)
|
|
|
|
|
|
# ── 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
|