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
+41 -41
View File
@@ -9,11 +9,11 @@ import pytest
import pytest_asyncio
from sqlalchemy import select
from scribe.models import async_session
from scribe.models.code_shape import CodeShape
from scribe.models.project import Project
from scribe.models.user import User
from scribe.services.shape_ledger import (
from grimoire.models import async_session
from grimoire.models.code_shape import CodeShape
from grimoire.models.project import Project
from grimoire.models.user import User
from grimoire.services.shape_ledger import (
classify_shapes,
classify_shapes_where,
list_project_shapes,
@@ -36,7 +36,7 @@ SHAPES = [
@pytest_asyncio.fixture
async def seeded():
"""Owner + outsider, a project with a synced 4-shape ledger, one snippet."""
from scribe.services import snippets as snippets_svc
from grimoire.services import snippets as snippets_svc
async with async_session() as s:
owner = await ensure_user(s, "classify_owner")
@@ -174,7 +174,7 @@ async def test_sync_stamps_scoped_rows_and_unstamps_when_they_become_reachable(s
"""By-construction one-offs arrive `scoped` (mechanical), count as
accounted, are reached by the sweep's default, and go back to the todo
if a later tree makes them ordinary. A judgment overrides the stamp."""
from scribe.services.coverage import ArchiveShape
from grimoire.services.coverage import ArchiveShape
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
scoped_shapes = [
ArchiveShape("web/Card.vue", "css", "card", ".card {", "s1", ".card { x: 1 }", True),
@@ -206,7 +206,7 @@ async def test_uses_edges_are_the_consumer_map(seeded):
"""A shape keeps ONE snippet_id (what it is) and any number of
uses edges (what it calls); the snippet's consumer map lists them,
list_shapes(uses=N) finds them, and a sweep can write them."""
from scribe.services import snippets as snippets_svc
from grimoire.services import snippets as snippets_svc
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
helper = await snippets_svc.create_snippet(
owner, name="cls_hash_helper", code="def hash_token(raw):\n return raw\n",
@@ -232,7 +232,7 @@ async def test_uses_edges_are_the_consumer_map(seeded):
assert (await list_project_shapes(owner, pid, uses=hid))[1] == 2
# The proposer writes uses edges for JUDGED rows too: Config (exempt)
# names hash_token in its body → an edge, no proposal.
from scribe.services.shape_ledger import propose_for_repo
from grimoire.services.shape_ledger import propose_for_repo
await classify_shapes(owner, pid, [
{"path": "src/app.py", "symbol": "Config", "status": "exempt", "reason": "settings"},
])
@@ -270,8 +270,8 @@ async def test_list_filters_compose(seeded):
@pytest.mark.integration
async def test_get_snippet_carries_the_structured_consumer_map(seeded):
from scribe.mcp._context import _user_id_ctx
from scribe.mcp.tools.snippets import get_snippet
from grimoire.mcp._context import _user_id_ctx
from grimoire.mcp.tools.snippets import get_snippet
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
await classify_shapes(owner, pid, [
@@ -303,7 +303,7 @@ async def test_sync_refiles_rows_whose_snippet_was_purged(seeded):
{"path": "src/app.py", "symbol": "make_app", "status": "instance",
"snippet_id": sid},
])
from scribe.models.note import Note
from grimoire.models.note import Note
async with async_session() as s:
note = await s.get(Note, sid)
@@ -331,7 +331,7 @@ async def test_write_path_evidence_is_a_proposal_never_a_verdict(seeded):
edge — a fact about the code — is still recorded."""
from datetime import datetime, timezone
from scribe.services.shape_ledger import suggest_write_path_instances
from grimoire.services.shape_ledger import suggest_write_path_instances
owner, other, pid, sid = (
seeded["owner"], seeded["other"], seeded["pid"], seeded["snippet"]
@@ -396,7 +396,7 @@ async def test_a_brand_new_shape_gets_a_provisional_row_carrying_the_suggestion(
carry a suggestion."""
from datetime import datetime, timezone
from scribe.services.shape_ledger import suggest_write_path_instances, unjudged_shapes
from grimoire.services.shape_ledger import suggest_write_path_instances, unjudged_shapes
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
pulled = {sid: datetime.now(timezone.utc)}
@@ -446,8 +446,8 @@ async def test_recent_pulls_reads_the_usage_stream(seeded):
window; a surfacing alone is not a pull."""
from datetime import datetime, timedelta, timezone
from scribe.models.note_usage import PULLED, SURFACED, NoteUsageEvent
from scribe.services.shape_ledger import recent_pulls
from grimoire.models.note_usage import PULLED, SURFACED, NoteUsageEvent
from grimoire.services.shape_ledger import recent_pulls
owner, sid = seeded["owner"], seeded["snippet"]
now = datetime.now(timezone.utc)
@@ -473,7 +473,7 @@ def _quiet_semantic():
bases and the bookkeeping, so it answers "nothing" here."""
from unittest.mock import AsyncMock, patch
from scribe.services import shape_ledger
from grimoire.services import shape_ledger
return patch.object(shape_ledger, "_semantic_canon", AsyncMock(return_value=None))
@@ -494,7 +494,7 @@ async def test_proposer_proposes_and_confirm_classifies(seeded):
nothing for the unrelated — skips rows whose content it already judged,
and a scoped confirm turns proposals into agent instances while a
classify on another retires its proposal."""
from scribe.services.shape_ledger import (
from grimoire.services.shape_ledger import (
confirm_proposals, propose_for_repo,
)
@@ -564,7 +564,7 @@ async def test_derive_groups_land_on_rows_and_in_the_summary(seeded):
"""Shapes with no canon hit that repeat — identical bodies in two files,
the same name in three — carry a derive proposal, and the readout ranks
the families. A canon proposal keeps a row out of any derive group."""
from scribe.services.shape_ledger import (
from grimoire.services.shape_ledger import (
apply_derive_groups, live_rows, propose_for_repo, proposal_summary,
)
@@ -627,7 +627,7 @@ async def test_write_time_derive_names_the_family_or_the_canon_for_a_name(seeded
count; for CSS a NAME family, never a body one); a
name whose canonical row lives elsewhere → that canon; a judged row at
the path, the canon's own file, or an unknown name → silence."""
from scribe.services.shape_ledger import apply_derive_groups, write_time_derive
from grimoire.services.shape_ledger import apply_derive_groups, write_time_derive
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
defs = _defs(
@@ -672,7 +672,7 @@ async def test_derive_new_names_the_copy_that_joined_a_family_since_the_stamp(se
newcomer — the drift since the last refresh, not the whole family."""
from datetime import datetime, timezone
from scribe.services.shape_ledger import (
from grimoire.services.shape_ledger import (
apply_derive_groups, derive_new_summary, live_rows,
)
@@ -704,8 +704,8 @@ async def test_transition_and_prefix_references_clear_the_unused_css_flag(seeded
a transition `name=` and a concatenated name — travel from the real
extractor through resolution into the flag, so rules they reach stop
being reported as unused. Only a rule nothing can reach stays listed."""
from scribe.services.coverage import class_references
from scribe.services.shape_ledger import sync_repo_consumers, used_by_map, live_rows
from grimoire.services.coverage import class_references
from grimoire.services.shape_ledger import sync_repo_consumers, used_by_map, live_rows
pid, owner = seeded["pid"], seeded["owner"]
defs = _defs(
@@ -756,7 +756,7 @@ async def test_consumer_map_syncs_edges_from_template_references(seeded):
resolution for a scoped class, fan-out to the shared sheet for a class a
template does not define, counts refreshed and stale edges removed on
the next sync, and a vanished row's edges gone with it."""
from scribe.services.shape_ledger import consumers_of, live_rows, sync_repo_consumers
from grimoire.services.shape_ledger import consumers_of, live_rows, sync_repo_consumers
pid = seeded["pid"]
defs = _defs(
@@ -791,7 +791,7 @@ async def test_consumer_map_syncs_edges_from_template_references(seeded):
# The readout side: used_by per css row, the unused-css flag, and the
# family's consumers on the write-path check.
from scribe.services.shape_ledger import (
from grimoire.services.shape_ledger import (
apply_derive_groups, used_by_map, write_time_derive,
)
owner = seeded["owner"]
@@ -825,12 +825,12 @@ async def test_a_second_confirm_dialog_is_detected_and_named(seeded):
judgment clears the flag; a shape proposed AS the canon is not flagged."""
from datetime import datetime, timedelta, timezone
from scribe.services.shape_ledger import (
from grimoire.services.shape_ledger import (
divergence_summary, flag_divergence, live_rows, propose_for_repo,
write_time_divergence,
)
from scribe.services import snippets as snippets_svc
from grimoire.services import snippets as snippets_svc
owner, pid = seeded["owner"], seeded["pid"]
# The confirm helper is TS canon: a sym basis only proposes
@@ -917,8 +917,8 @@ async def test_weak_stamps_elect_no_canon_and_their_flags_are_withdrawn(seeded):
from sqlalchemy import update
from scribe.services import snippets as snippets_svc
from scribe.services.shape_ledger import (
from grimoire.services import snippets as snippets_svc
from grimoire.services.shape_ledger import (
_RESEMBLE_REASON, flag_divergence, propose_for_repo, write_time_divergence,
)
@@ -999,9 +999,9 @@ async def test_a_semantic_miss_does_not_silence_the_prompt(seeded):
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, patch
from scribe.services import shape_ledger
from scribe.services import snippets as snippets_svc
from scribe.services.shape_ledger import flag_divergence, live_rows, propose_for_repo
from grimoire.services import shape_ledger
from grimoire.services import snippets as snippets_svc
from grimoire.services.shape_ledger import flag_divergence, live_rows, propose_for_repo
owner, pid = seeded["owner"], seeded["pid"]
canon = await snippets_svc.create_snippet(
@@ -1051,7 +1051,7 @@ async def _two_generations(seeded, tag: str):
"""
from datetime import datetime, timezone
from scribe.services import snippets as snippets_svc
from grimoire.services import snippets as snippets_svc
owner, pid = seeded["owner"], seeded["pid"]
canon = await snippets_svc.create_snippet(
@@ -1092,8 +1092,8 @@ async def test_the_capped_semantic_pass_reaches_the_new_shape_first(seeded):
ONE check, the new shape must be the one that gets it."""
from unittest.mock import AsyncMock, patch
from scribe.services import shape_ledger
from scribe.services.shape_ledger import live_rows, propose_for_repo
from grimoire.services import shape_ledger
from grimoire.services.shape_ledger import live_rows, propose_for_repo
owner, pid, later, _previous = await _two_generations(seeded, "cap")
with patch.object(shape_ledger, "_semantic_canon", AsyncMock(return_value=None)):
@@ -1108,7 +1108,7 @@ async def test_the_capped_semantic_pass_reaches_the_new_shape_first(seeded):
@pytest.mark.integration
async def test_history_records_what_was_used_when_and_drift_asks_for_a_recheck(seeded):
from scribe.services.shape_ledger import shape_history
from grimoire.services.shape_ledger import shape_history
owner, other, pid, sid = (
seeded["owner"], seeded["other"], seeded["pid"], seeded["snippet"]
@@ -1163,7 +1163,7 @@ async def test_a_shape_written_this_turn_is_judged_before_the_sync_sees_it(seede
then reads the shape keeps the judgment, and a sync that does NOT have it
yet (the code has not reached the bound ref) keeps it through the vanish
and the reappearance. Without `repo` the old contract holds."""
from scribe.services import repo_bindings as repo_bindings_svc
from grimoire.services import repo_bindings as repo_bindings_svc
owner, pid = seeded["owner"], seeded["pid"]
await repo_bindings_svc.set_binding(owner, REPO, pid)
@@ -1200,7 +1200,7 @@ async def test_unjudged_is_what_nobody_read(seeded):
audit's verdict is not. Evidence travels; a verdict is never inferred."""
from sqlalchemy import update
from scribe.services.shape_ledger import _RESEMBLE_REASON, unjudged_shapes
from grimoire.services.shape_ledger import _RESEMBLE_REASON, unjudged_shapes
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
await classify_shapes(owner, pid, [
@@ -1238,9 +1238,9 @@ async def test_a_whole_file_snippet_makes_its_file_row_canonical(seeded):
symbol, is the canon for that file row — the claim a whole-file record
makes and a definition row never could. It still covers no definition
inside the file."""
from scribe.services import snippets as snippets_svc
from scribe.services.coverage import _recorded_locations
from scribe.services.shape_ledger import mark_canonicals
from grimoire.services import snippets as snippets_svc
from grimoire.services.coverage import _recorded_locations
from grimoire.services.shape_ledger import mark_canonicals
owner, pid = seeded["owner"], seeded["pid"]
await sync_repo_shapes(pid, REPO, SHAPES + [