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
+19 -19
View File
@@ -43,8 +43,8 @@ import pytest
def test_both_doors_share_one_serializer():
"""A payload shape spelled once per door answers the two of them
differently the first time a field is added."""
from scribe.mcp.tools import lessons as mcp_lessons
from scribe.services import lessons as lessons_svc
from grimoire.mcp.tools import lessons as mcp_lessons
from grimoire.services import lessons as lessons_svc
assert mcp_lessons._to_dict is lessons_svc.lesson_to_dict
@@ -53,7 +53,7 @@ def test_the_rest_door_composes_nothing_itself():
"""Asserted on structure. The document is what ranks, so a door
that built its own title would produce lessons that rank differently from
the ones the agent writes."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes)
# It may CALL the service's composer (the dedup gate needs the document),
@@ -67,15 +67,15 @@ def test_the_rest_door_composes_nothing_itself():
def test_the_door_is_registered():
"""A blueprint nobody registers is a file, not a door."""
from scribe import app as app_module
from grimoire import app as app_module
src = inspect.getsource(app_module)
assert "from scribe.routes.lessons import lessons_bp" in src
assert "from grimoire.routes.lessons import lessons_bp" in src
assert "app.register_blueprint(lessons_bp)" in src
def test_the_blueprint_is_mounted_where_the_client_looks():
from scribe.routes.lessons import lessons_bp
from grimoire.routes.lessons import lessons_bp
assert lessons_bp.url_prefix == "/api/lessons"
@@ -84,7 +84,7 @@ def test_the_reverse_lookup_is_registered_before_the_id_route():
"""Quart matches in registration order. The int converter protects
`taught-by` today, but the ordering is what keeps that true if the
converter is ever widened — the same care snippets' `/duplicates` takes."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes)
assert src.index('"/taught-by/<int:record_id>"') < src.index(
@@ -98,7 +98,7 @@ def test_the_reverse_lookup_is_registered_before_the_id_route():
def test_create_refuses_a_lesson_with_no_trigger():
"""The kind-specific guard. A triggerless lesson saves and never surfaces,
and nothing about the stored record shows it."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes.create_lesson_route)
assert "when_to_apply is required" in src
@@ -111,14 +111,14 @@ def test_create_refuses_a_lesson_with_no_trigger():
def test_update_refuses_to_clear_the_trigger():
"""The other half. Creating without one is refused; emptying one later
would reach the same broken state by a different path."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes.update_lesson_route)
assert "cannot be cleared" in src
def test_the_subject_is_required_too():
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes.create_lesson_route)
assert "what is required" in src
@@ -133,7 +133,7 @@ def test_the_subject_is_required_too():
def test_writes_check_permission_and_act_as_the_owner(handler):
"""Resolve share-aware, then write as the owner — otherwise a shared
editor is rejected by the owner-scoped service."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(getattr(routes, handler))
assert "can_write_note" in src, f"{handler} does not check write permission"
@@ -148,7 +148,7 @@ def test_no_handler_builds_its_own_owner_filter():
`Note.user_id == uid` clause instead of going through the service and the
access helpers. Passing `user_id=uid` INTO a service is the correct call
and is not what this looks for."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes)
assert "Note.user_id" not in src
@@ -159,7 +159,7 @@ def test_no_handler_builds_its_own_owner_filter():
def test_delete_trashes_recoverably():
"""Every kind's delete is a trash, and the batch id is what restores it."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes.delete_lesson_route)
assert "trash_svc.delete" in src
@@ -172,7 +172,7 @@ def test_delete_trashes_recoverably():
def test_the_reverse_lookup_reads_the_indexed_mirror():
"""Not by scanning bodies: `data[taught_by]` is JSONB with a GIN index
(0070), which is the whole reason step 4 put the list there."""
from scribe.services import lessons as lessons_svc
from grimoire.services import lessons as lessons_svc
src = inspect.getsource(lessons_svc.lessons_taught_by)
assert "path_exists" in src
@@ -182,7 +182,7 @@ def test_the_reverse_lookup_reads_the_indexed_mirror():
def test_the_reverse_lookup_is_share_aware():
"""It renders beside a record the caller can already see, so a lesson
someone shared with them belongs in the list exactly as their own does."""
from scribe.services import lessons as lessons_svc
from grimoire.services import lessons as lessons_svc
src = inspect.getsource(lessons_svc.lessons_taught_by)
assert "readable_notes_clause" in src
@@ -192,7 +192,7 @@ def test_the_reverse_lookup_is_share_aware():
def test_the_reverse_lookup_refuses_a_nonsense_id_rather_than_interpolating():
"""The jsonpath is built by formatting, so the id has to be an int before
it gets near the expression."""
from scribe.services import lessons as lessons_svc
from grimoire.services import lessons as lessons_svc
src = inspect.getsource(lessons_svc.lessons_taught_by)
assert "int(record_id)" in src
@@ -200,7 +200,7 @@ def test_the_reverse_lookup_refuses_a_nonsense_id_rather_than_interpolating():
@pytest.mark.asyncio
async def test_a_bad_id_returns_nothing_and_raises_nothing():
from scribe.services.lessons import lessons_taught_by
from grimoire.services.lessons import lessons_taught_by
assert await lessons_taught_by(1, 0) == []
assert await lessons_taught_by(1, -3) == []
@@ -214,7 +214,7 @@ def test_the_payload_reads_back_the_composed_fields():
"""A caller that wrote `when_to_apply` reads `when_to_apply` back, not a
body it has to parse."""
from tests.helpers import fake_lesson
from scribe.services.lessons import compose_body, lesson_to_dict
from grimoire.services.lessons import compose_body, lesson_to_dict
what = "Read the job log before waiting longer"
trigger = "a CI run has sat in_progress longer than its suite takes"
@@ -239,7 +239,7 @@ def test_the_payload_reads_back_the_composed_fields():
def test_the_rest_guards_can_fail():
"""Shown turning red once."""
from scribe.routes import lessons as routes
from grimoire.routes import lessons as routes
src = inspect.getsource(routes)
assert "a phrase that is definitely not in this module" not in src