rename(547): the server is grimoire — package, image, DB defaults, backups
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>
This commit is contained in:
2026-10-22 10:50:45 -04:00
co-authored by Claude Opus 5.5
parent e99f1169f7
commit 0f5ef0be63
447 changed files with 3611 additions and 3558 deletions
+16 -16
View File
@@ -116,9 +116,9 @@ async def test_a_title_change_reaches_the_mirror_too():
def test_every_kind_with_a_derived_mirror_is_registered():
"""The load-bearing one. A single `if` naming one kind leaves the next
kind uncovered."""
from scribe.services.lessons import LESSON_NOTE_TYPE
from scribe.services.notes import _mirror_recomposers
from scribe.services.snippets import SNIPPET_NOTE_TYPE
from grimoire.services.lessons import LESSON_NOTE_TYPE
from grimoire.services.notes import _mirror_recomposers
from grimoire.services.snippets import SNIPPET_NOTE_TYPE
table = _mirror_recomposers()
assert SNIPPET_NOTE_TYPE in table, "snippets lost their mirror recomposer"
@@ -132,7 +132,7 @@ def test_every_kind_with_a_derived_mirror_is_registered():
def test_the_dispatch_is_a_lookup_not_a_named_kind():
"""Asserted on structure. A lookup extends in one line in one
place; naming a kind inline is the shape that left lessons uncovered."""
from scribe.services import notes as notes_module
from grimoire.services import notes as notes_module
src = inspect.getsource(notes_module.update_note)
assert "_mirror_recomposers()" in src
@@ -144,7 +144,7 @@ def test_the_dispatch_is_a_lookup_not_a_named_kind():
def test_each_recomposer_takes_the_note_and_nothing_else():
"""A registry entry with the wrong signature fails inside a generic PATCH,
which is the one moment nobody is watching."""
from scribe.services.notes import _mirror_recomposers
from grimoire.services.notes import _mirror_recomposers
for kind, fn in _mirror_recomposers().items():
assert callable(fn), f"{kind} maps to something not callable"
@@ -158,13 +158,13 @@ NEW_TRIGGER = "a CI run has sat in_progress far longer than its suite takes"
def _lesson_body(trigger, insight="Read the job log.", sources=None):
from scribe.services.lessons import compose_body
from grimoire.services.lessons import compose_body
return compose_body(insight, trigger, sources)
@pytest.mark.asyncio
async def test_a_body_write_moves_a_lessons_trigger_with_it():
from scribe.services.lessons import TRIGGER_KEY
from grimoire.services.lessons import TRIGGER_KEY
what = "Read the job log before waiting longer"
note = fake_lesson(
@@ -186,8 +186,8 @@ async def test_a_subject_containing_an_em_dash_still_splits():
separator: a subject may legitimately contain one. The title here is an
UN-MIGRATED one, still carrying its trigger, because that
is the row the inverse still has to read correctly."""
from scribe.services.embeddings import trigger_title
from scribe.services.lessons import TRIGGER_KEY
from grimoire.services.embeddings import trigger_title
from grimoire.services.lessons import TRIGGER_KEY
what = "A wait with no deadline — the shape, not the symptom"
trigger = "you are about to await something crossing a process boundary"
@@ -205,7 +205,7 @@ async def test_dropping_the_provenance_line_drops_it_from_the_mirror():
the failure this recompose exists to prevent, not a courtesy — the
opposite call from a snippet's `verification`, which is carried because it
was never in the body to delete."""
from scribe.services.lessons import SOURCES_KEY
from grimoire.services.lessons import SOURCES_KEY
note = fake_lesson(
title="Something learned",
@@ -220,7 +220,7 @@ async def test_dropping_the_provenance_line_drops_it_from_the_mirror():
async def test_an_explicit_data_wins_for_a_lesson_too():
"""`update_lesson` composes the mirror from the merged field set and passes
it here; that caller knows things a re-read of the body cannot recover."""
from scribe.services.lessons import TRIGGER_KEY
from grimoire.services.lessons import TRIGGER_KEY
note = fake_lesson(data={TRIGGER_KEY: "old"}, project_id=None)
authoritative = {TRIGGER_KEY: "from the service", "what": "x"}
@@ -232,7 +232,7 @@ async def test_an_explicit_data_wins_for_a_lesson_too():
async def test_a_lesson_title_change_reaches_the_mirror():
"""A lesson's subject lives in its title, so a title edit is a trigger for
recomposition exactly as it is for a snippet's name."""
from scribe.services.lessons import TRIGGER_KEY
from grimoire.services.lessons import TRIGGER_KEY
trigger = "two absolute siblings overlap"
note = fake_lesson(
@@ -261,7 +261,7 @@ async def test_a_lesson_title_change_reaches_the_mirror():
def test_untrigger_title_inverts_trigger_title(subject, trigger):
"""The join's inverse lives beside it rather than in whichever caller
wanted it."""
from scribe.services.embeddings import trigger_title, untrigger_title
from grimoire.services.embeddings import trigger_title, untrigger_title
title = trigger_title(subject, trigger)
assert untrigger_title(title, trigger) == (subject or trigger).strip()
@@ -270,7 +270,7 @@ def test_untrigger_title_inverts_trigger_title(subject, trigger):
def test_untrigger_title_degrades_to_the_whole_title():
"""A record written before the join existed still answers with something a
human recognises rather than with ""."""
from scribe.services.embeddings import untrigger_title
from grimoire.services.embeddings import untrigger_title
assert untrigger_title("a plain old title", "") == "a plain old title"
assert untrigger_title("a plain old title", "a trigger it lacks") == (
@@ -282,7 +282,7 @@ def test_the_trigger_separator_is_spelled_in_exactly_one_place():
"""Two literals that must match are one edit away from not matching."""
import pathlib
root = pathlib.Path(__file__).resolve().parents[1] / "src" / "scribe"
root = pathlib.Path(__file__).resolve().parents[1] / "src" / "grimoire"
offenders = [
str(p.relative_to(root)) for p in root.rglob("*.py")
if '" \u2014 "' in p.read_text() and p.name != "embeddings.py"
@@ -295,7 +295,7 @@ def test_the_trigger_separator_is_spelled_in_exactly_one_place():
def test_the_mirror_guards_can_fail():
"""Shown turning red once."""
from scribe.services.lessons import TRIGGER_KEY, recompose_data
from grimoire.services.lessons import TRIGGER_KEY, recompose_data
bare = fake_lesson(title="just a title", body="no composed lines", data=None)
assert TRIGGER_KEY not in recompose_data(bare)