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>
151 lines
6.2 KiB
Python
151 lines
6.2 KiB
Python
"""The document a lesson is embedded as.
|
||
|
||
WHY THIS IS THE STEP THAT DECIDES THE MILESTONE
|
||
|
||
Everything before this is storage. A lesson stored with a trigger but embedded
|
||
as ordinary prose is a note wearing a label: it would look right in every
|
||
listing and simply never be retrieved at the moment it applies, and nothing
|
||
anywhere would report that.
|
||
|
||
WHERE THE SHARP SHAPE LIVES
|
||
|
||
A snippet, measured as the sharpest record kind in a corpus (a 0.153
|
||
top-to-second gap against 0.010–0.023 for everything else), is sharp
|
||
because its document states its purpose twice: `name — when to use` as the
|
||
title, and again as the body's first line. A lesson follows the snippet.
|
||
|
||
The STORED title is the subject alone, so listings, menus and search rows do
|
||
not carry kilobytes of trigger; `embeddings.document_title` joins the trigger
|
||
back from `data` at embed time. The embedded TEXT keeps the `subject —
|
||
trigger` shape, which is the property these guards pin: the floors tuned
|
||
against these vectors stay calibrated.
|
||
|
||
No similarity number is asserted anywhere: a threshold pins the embedder's
|
||
behaviour rather than this code's, and breaks on a model change that is not a
|
||
regression.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from grimoire.services import lessons as lessons_svc
|
||
from grimoire.services.embeddings import chunk_document, document_title, embedding_text
|
||
|
||
TRIGGER = "a test fails on code you believe is correct"
|
||
SUBJECT = "Suspect the guard before the code"
|
||
INSIGHT = "Check whether the assertion still describes the property it was written for."
|
||
|
||
|
||
def _embedded(what: str, trigger: str, insight: str) -> tuple[str, str]:
|
||
"""The (title, body) the write path EMBEDS a lesson as — built the way
|
||
`notes.embed_note` builds it, from what `create_lesson` stores."""
|
||
title, body = lessons_svc.lesson_document(what, trigger, insight)
|
||
data = lessons_svc.compose_data(what, trigger)
|
||
return document_title(title, lessons_svc.LESSON_NOTE_TYPE, data, body), body
|
||
|
||
|
||
def test_the_stored_title_is_the_subject_alone():
|
||
"""The trigger lives in `data` and the body's first line; the
|
||
title a listing shows is what the lesson is ABOUT."""
|
||
title, _ = lessons_svc.lesson_document(SUBJECT, TRIGGER, INSIGHT)
|
||
assert title == SUBJECT
|
||
|
||
|
||
def test_the_embedded_document_is_the_one_it_always_was():
|
||
"""THE no-re-embed guard. The document text must be byte-identical to what
|
||
a lesson embedded as when its stored title carried the trigger — and an
|
||
un-migrated row, whose stored title still does, must come out the same
|
||
rather than with the trigger twice."""
|
||
legacy_title = f"{SUBJECT} — {TRIGGER}"
|
||
title, body = _embedded(SUBJECT, TRIGGER, INSIGHT)
|
||
assert title == legacy_title
|
||
|
||
data = lessons_svc.compose_data(SUBJECT, TRIGGER)
|
||
assert document_title(legacy_title, lessons_svc.LESSON_NOTE_TYPE, data, body) == legacy_title
|
||
|
||
|
||
def test_the_trigger_appears_twice_in_the_document():
|
||
"""Purpose stated twice in a short document is the entire measured cause of
|
||
a snippet's sharpness, and it is the one property that distinguishes a
|
||
lesson's vector from a plain note's."""
|
||
title, body = _embedded(SUBJECT, TRIGGER, INSIGHT)
|
||
document = embedding_text(title, body)
|
||
|
||
assert document.count(TRIGGER) == 2
|
||
# Once in each half, not twice in one of them.
|
||
assert TRIGGER in title
|
||
assert TRIGGER in body
|
||
|
||
|
||
def test_the_document_leads_with_when_it_applies():
|
||
"""The embedded title is `{what} — {when}` and the body's FIRST line
|
||
restates it, so the opening of the document is about the situation rather
|
||
than the topic."""
|
||
title, body = _embedded(SUBJECT, TRIGGER, INSIGHT)
|
||
|
||
assert title == f"{SUBJECT} — {TRIGGER}"
|
||
assert body.splitlines()[0] == f"**When to apply:** {TRIGGER}"
|
||
|
||
|
||
def test_the_trigger_reaches_the_document_even_without_the_mirror():
|
||
"""A row whose `data` lost its mirror still embeds sharply: the trigger is
|
||
read back from the body, the same fallback `lesson_trigger` has."""
|
||
title, body = lessons_svc.lesson_document(SUBJECT, TRIGGER, INSIGHT)
|
||
assert document_title(title, lessons_svc.LESSON_NOTE_TYPE, None, body) == (
|
||
f"{SUBJECT} — {TRIGGER}"
|
||
)
|
||
|
||
|
||
def test_a_short_lesson_is_exactly_one_chunk():
|
||
"""`chunk_document`'s first contract line: a record inside the window
|
||
yields one chunk identical to the historical `title\\nbody`."""
|
||
title, body = _embedded(SUBJECT, TRIGGER, INSIGHT)
|
||
chunks = chunk_document(title, body)
|
||
|
||
assert len(chunks) == 1
|
||
assert chunks[0].count(TRIGGER) == 2
|
||
|
||
|
||
def test_a_long_lesson_keeps_the_trigger_on_every_chunk():
|
||
"""Every chunk is prefixed with the embedded title, which carries the
|
||
trigger — so a long story occupies its own vectors instead of averaging
|
||
itself into the trigger's, and each is still anchored to when it applies.
|
||
"""
|
||
narrative = "\n\n".join(
|
||
f"## Section {i}\n" + ("An unrelated sentence about deployment. " * 40)
|
||
for i in range(6)
|
||
)
|
||
title, body = _embedded(SUBJECT, TRIGGER, narrative)
|
||
chunks = chunk_document(title, body)
|
||
|
||
assert len(chunks) > 1, "the fixture must actually exceed the chunk budget"
|
||
assert all(TRIGGER in chunk for chunk in chunks)
|
||
|
||
|
||
def test_a_lesson_with_no_trigger_still_embeds():
|
||
"""Degrades to title + insight, the way a rule with no trigger does — less
|
||
sharply, and still findable."""
|
||
title, body = _embedded(SUBJECT, "", INSIGHT)
|
||
|
||
assert title == SUBJECT
|
||
assert body == INSIGHT
|
||
assert chunk_document(title, body) == [f"{SUBJECT}\n{INSIGHT}"]
|
||
|
||
|
||
def test_the_composed_body_is_the_one_the_reader_is_parsed_back_from():
|
||
"""`compose_body` writes the trigger line and `lesson_trigger` reads it. A
|
||
lesson whose mirror in `data` is missing still answers correctly, so the
|
||
two must agree on the exact markdown."""
|
||
from types import SimpleNamespace
|
||
|
||
_, body = lessons_svc.lesson_document(SUBJECT, TRIGGER, INSIGHT)
|
||
no_mirror = SimpleNamespace(data=None, body=body)
|
||
|
||
assert lessons_svc.lesson_trigger(no_mirror) == TRIGGER
|
||
|
||
|
||
def test_the_title_and_body_are_composed_by_one_call():
|
||
"""`lesson_document` returns both halves so they cannot be built apart."""
|
||
assert lessons_svc.lesson_document(SUBJECT, TRIGGER, INSIGHT) == (
|
||
SUBJECT,
|
||
lessons_svc.compose_body(INSIGHT, TRIGGER),
|
||
)
|