CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 44s
Step 2 of the Scribe -> Grimoire rename. - src/scribe -> src/grimoire, and every import, patch target, module path, hypercorn entry point and `python -m grimoire.restore` with it; pyproject, uv.lock and the frontend package are named grimoire. - Image git.fabledsword.com/bvandeusen/fabledgrimoire; CI's Postgres service runs as grimoire / grimoire_test. - Compose files default the DB user, database and (prod) volume to grimoire, each overridable from .env, so an install created as Scribe keeps its data by setting POSTGRES_USER, POSTGRES_DB and PGDATA_VOLUME. - Backups are written as grimoire-<ts>.tar with format grimoire-backup-tar. Archives named scribe-* with the scribe-backup-tar manifest still restore and are still counted at the destination (folder and S3), with tests. - Exports, the MCP auth realm, the ASGI scope key and the persistent logger carry the new name. Kept on purpose: the secret-sealing key label stays "scribe.secrets.v1", because every stored secret was derived under it. Plugin identity (plugin name, hooks, env vars, .scribe marker, skills) is step 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
441 lines
19 KiB
Python
441 lines
19 KiB
Python
"""A canon may only be urged on a shape that could plausibly BE it.
|
|
|
|
WHY. `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 are all siblings, so the prior is the entire argument — it
|
|
would tell a registry module of pure helpers to build from an
|
|
`async_session` service canon. An auto-stamp without a resemblance floor
|
|
asserts 0.69 as confidently as 0.95.
|
|
|
|
The two halves compound: loose stamping manufactures the density that the
|
|
divergence check then reads as authority. Both are held 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 grimoire.services.shape_ledger import (
|
|
FORM_UNKNOWN, canon_form, comparable_siblings, dominant_canon,
|
|
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_a_registry_modules_helpers_are_not_service_functions() -> None:
|
|
"""The concrete case. The canon is an `async def` service unit; none of
|
|
these is one."""
|
|
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_the_divergence_gate_silences_only_a_family_contradiction() -> 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 these five 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 the divergence
|
|
readout's acceptance case, where a sync `confirmDanger` beside an async
|
|
confirm helper SHOULD be flagged.
|
|
|
|
Widening `kind` does not separate them: it buys the silence by bucketing
|
|
`fn` apart from `async-fn`, which takes the async canon out of a sync
|
|
candidate's denominator and silences the acceptance case by the identical
|
|
mechanism, one layer down. Whatever separates these four
|
|
has to distinguish a helper that does the canon's JOB from one that does
|
|
not, and no signature carries that. A comparison of meaning is the only
|
|
lever, not one of two."""
|
|
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:
|
|
"""The divergence readout's acceptance case: gating divergence on
|
|
`forms_agree` would silence a hand-rolled sync confirm in a directory
|
|
where an async confirm helper is canon — the exact prompt the readout
|
|
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:
|
|
"""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 grimoire.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_one_canon_is_not_stamped_onto_a_whole_file() -> None:
|
|
"""A class, an async method and a sync helper in one file, against one
|
|
payload-level score. The score alone cannot carry any of them; only a
|
|
matching form can."""
|
|
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 grimoire.services.shape_ledger import _RESEMBLE_MIN
|
|
|
|
assert _RESEMBLE_MIN >= 0.80
|
|
|
|
|
|
# ── The denominator, not the gate ─────────────────────────────────────────
|
|
#
|
|
# `dominant_canon` is a base rate, and a base rate is only a statement about
|
|
# something if its denominator is a population somebody asked a question
|
|
# about. It counted every code symbol in a directory as one bucket: a frozen
|
|
# dataclass, a module constant and an async service unit were three
|
|
# comparable things, and "372 judged siblings" was the authority the
|
|
# divergence line spoke with.
|
|
#
|
|
# `comparable_siblings` narrows it using the SAME predicate as the gate, so
|
|
# the count and the verdict cannot drift into disagreeing about what
|
|
# comparable means.
|
|
|
|
|
|
# `_Row` above is reused rather than redefined. A second class of the same
|
|
# name here shadowed the first — same fields, different `snippet_id` default —
|
|
# and silently broke three `canon_form` tests that had been passing, which is
|
|
# a neater demonstration of this file's subject than anything it asserts.
|
|
SERVICE = "async def get_note(user_id: int, note_id: int) -> Note | None:"
|
|
HELPER = "def is_registered(source: str) -> bool:"
|
|
|
|
|
|
def test_a_type_is_not_counted_against_a_directory_of_callables() -> None:
|
|
rows = [_Row(SERVICE) for _ in range(6)] + [_Row("class Point:", snippet_id=9)]
|
|
assert len(comparable_siblings(rows, shape_form("class Point:"))) == 1
|
|
|
|
|
|
def test_a_callable_is_not_counted_against_a_dataclass() -> None:
|
|
"""The honest-denominator half, and the one that changes reported numbers:
|
|
`judged` stops overstating how much of the directory was ever comparable."""
|
|
rows = [_Row(SERVICE) for _ in range(6)] + [_Row("class Point:", snippet_id=9)]
|
|
assert len(comparable_siblings(rows, shape_form(HELPER))) == 6
|
|
|
|
|
|
def test_the_async_canon_stays_in_a_sync_candidates_denominator() -> None:
|
|
"""THE DIVERGENCE READOUT'S ACCEPTANCE CASE, and the reason this narrows by family
|
|
rather than by form.
|
|
|
|
A hand-rolled sync `confirmDanger` in a directory where an async confirm
|
|
helper is canon must still be flagged. Bucketing the denominator by exact
|
|
form — which is what widening `kind` to carry `fn` vs `async-fn` amounts
|
|
to — would take the canon out of this count and silence it.
|
|
"""
|
|
rows = [_Row("async def confirmDanger(message: str) -> bool:") for _ in range(5)]
|
|
kept = comparable_siblings(rows, shape_form("function confirmDanger(message) {"))
|
|
assert len(kept) == 5
|
|
assert dominant_canon(kept) is not None
|
|
|
|
|
|
def test_an_unreadable_sibling_stays_counted() -> None:
|
|
"""Quieter, never louder. Dropping unknown rows shrinks `judged`, raises
|
|
the dominant canon's share, and fires the check MORE on exactly the
|
|
directories it can read least."""
|
|
rows = [_Row(SERVICE) for _ in range(4)] + [_Row("")]
|
|
assert len(comparable_siblings(rows, shape_form(SERVICE))) == 5
|
|
|
|
|
|
def test_an_unreadable_candidate_narrows_nothing() -> None:
|
|
"""The other side of the same discipline: a candidate whose own signature
|
|
says nothing gets the full denominator, not a guessed one."""
|
|
rows = [_Row(SERVICE), _Row("class Point:")]
|
|
assert comparable_siblings(rows, FORM_UNKNOWN) == rows
|
|
|
|
|
|
def test_a_family_passed_where_a_form_belongs_would_be_a_silent_no_op() -> None:
|
|
"""A REGRESSION GUARD ON A BUG THIS CHANGE ACTUALLY HAD.
|
|
|
|
`families_conflict` coarsens both sides itself, so handing it a family
|
|
makes `shape_family("callable")` return "" and the whole narrowing becomes
|
|
a no-op — while every call site still reads as though it applied. Pinned
|
|
because the wrong value is the right TYPE and the failure is silent.
|
|
"""
|
|
# A CALLABLE candidate, deliberately: `fn`'s family is `callable`, a word
|
|
# that is not itself a form, so `shape_family("callable")` is "" and the
|
|
# exclusion never fires. Picking `type` here would prove nothing — `type`
|
|
# is both a form and a family name, so passing the family still narrows
|
|
# and the bug hides. That near-miss is why this test exists at all.
|
|
rows = [_Row(SERVICE) for _ in range(6)] + [_Row("class Point:", snippet_id=9)]
|
|
form = shape_form("def helper(x) -> bool:")
|
|
assert shape_family(form) != form, "this test needs a form whose family differs"
|
|
by_form = comparable_siblings(rows, form)
|
|
by_family = comparable_siblings(rows, shape_family(form))
|
|
assert len(by_form) == 6, "the dataclass is not comparable to a function"
|
|
assert len(by_family) == len(rows), "a family narrows nothing — that is the bug"
|
|
assert by_form != by_family
|
|
|
|
|
|
def test_callable_helpers_beside_a_callable_canon_still_prompt() -> None:
|
|
"""HONEST ACCOUNTING, matching the sibling test above.
|
|
|
|
The narrowed denominator does not silence the four `def` helpers, and
|
|
cannot: they are callables, the canon is a callable, so nothing is
|
|
excluded from their count. What it buys is that the count is over
|
|
comparable things. Asserted so the claim cannot quietly rot into
|
|
"this fixed it".
|
|
"""
|
|
rows = [_Row(SERVICE) for _ in range(6)] + [_Row("class Point:", snippet_id=9)]
|
|
for sig in (
|
|
"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]:",
|
|
):
|
|
kept = comparable_siblings(rows, shape_form(sig))
|
|
dom = dominant_canon(kept)
|
|
assert dom is not None, sig
|
|
assert not families_conflict(shape_form(sig), canon_form(kept, dom[0])), sig
|