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
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:
@@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from scribe.services.design_systems import DesignSystemCycle
|
||||
from grimoire.services.design_systems import DesignSystemCycle
|
||||
from tests.helpers import design_token_stub, fake_record
|
||||
|
||||
|
||||
@@ -31,27 +31,27 @@ async def test_creating_without_a_parent_passes_none_not_zero():
|
||||
"""0 is the "omitted" sentinel, and it must not reach the service as a
|
||||
system id — there is no system 0, so the create would fail an ACL check for
|
||||
a record that cannot exist."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.create_design_system = AsyncMock(return_value=_fake_design_system())
|
||||
from scribe.mcp.tools.design_systems import create_design_system
|
||||
from grimoire.mcp.tools.design_systems import create_design_system
|
||||
await create_design_system(title="FabledSword")
|
||||
assert svc.create_design_system.await_args.kwargs["parent_id"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_creating_with_a_parent_passes_it_through():
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.create_design_system = AsyncMock(return_value=_fake_design_system())
|
||||
from scribe.mcp.tools.design_systems import create_design_system
|
||||
from grimoire.mcp.tools.design_systems import create_design_system
|
||||
await create_design_system(title="Grimoire", parent_id=4)
|
||||
assert svc.create_design_system.await_args.kwargs["parent_id"] == 4
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_raises_when_the_parent_is_not_writable():
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.create_design_system = AsyncMock(return_value=None)
|
||||
from scribe.mcp.tools.design_systems import create_design_system
|
||||
from grimoire.mcp.tools.design_systems import create_design_system
|
||||
with pytest.raises(ValueError):
|
||||
await create_design_system(title="Grimoire", parent_id=4)
|
||||
|
||||
@@ -61,9 +61,9 @@ async def test_create_raises_when_the_parent_is_not_writable():
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_with_parent_id_zero_leaves_the_parent_alone():
|
||||
"""The common case — renaming a system must not silently re-root it."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_design_system = AsyncMock(return_value=_fake_design_system())
|
||||
from scribe.mcp.tools.design_systems import update_design_system
|
||||
from grimoire.mcp.tools.design_systems import update_design_system
|
||||
await update_design_system(design_system_id=1, title="Renamed")
|
||||
fields = svc.update_design_system.await_args.kwargs
|
||||
assert "parent_id" not in fields
|
||||
@@ -75,18 +75,18 @@ async def test_update_with_parent_id_minus_one_clears_it():
|
||||
"""-1 means "make this a family system". It has to arrive at the service as
|
||||
None, which is the value the service reads as "become a root" — where
|
||||
omitting the key means "leave alone"."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_design_system = AsyncMock(return_value=_fake_design_system())
|
||||
from scribe.mcp.tools.design_systems import update_design_system
|
||||
from grimoire.mcp.tools.design_systems import update_design_system
|
||||
await update_design_system(design_system_id=1, parent_id=-1)
|
||||
assert svc.update_design_system.await_args.kwargs["parent_id"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_with_a_positive_parent_id_sets_it():
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_design_system = AsyncMock(return_value=_fake_design_system())
|
||||
from scribe.mcp.tools.design_systems import update_design_system
|
||||
from grimoire.mcp.tools.design_systems import update_design_system
|
||||
await update_design_system(design_system_id=1, parent_id=4)
|
||||
assert svc.update_design_system.await_args.kwargs["parent_id"] == 4
|
||||
|
||||
@@ -95,11 +95,11 @@ async def test_update_with_a_positive_parent_id_sets_it():
|
||||
async def test_a_cycle_surfaces_as_a_usable_error_not_a_not_found():
|
||||
"""The service raises so this layer can keep the two apart. An agent told
|
||||
"not found" would retry the same call; one told what the loop is can fix it."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_design_system = AsyncMock(
|
||||
side_effect=DesignSystemCycle("2 already inherits from 1")
|
||||
)
|
||||
from scribe.mcp.tools.design_systems import update_design_system
|
||||
from grimoire.mcp.tools.design_systems import update_design_system
|
||||
with pytest.raises(ValueError, match="already inherits"):
|
||||
await update_design_system(design_system_id=1, parent_id=2)
|
||||
|
||||
@@ -110,9 +110,9 @@ async def test_a_cycle_surfaces_as_a_usable_error_not_a_not_found():
|
||||
async def test_update_token_treats_order_index_minus_one_as_unchanged():
|
||||
"""0 is a VALID order_index, so it cannot double as the omitted sentinel —
|
||||
the same reason the systems tools use -1."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_token = AsyncMock(return_value=_fake_token())
|
||||
from scribe.mcp.tools.design_systems import update_design_token
|
||||
from grimoire.mcp.tools.design_systems import update_design_token
|
||||
await update_design_token(token_id=9, purpose="page bg")
|
||||
fields = svc.update_token.await_args.kwargs
|
||||
assert "order_index" not in fields
|
||||
@@ -121,9 +121,9 @@ async def test_update_token_treats_order_index_minus_one_as_unchanged():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_token_accepts_order_index_zero():
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_token = AsyncMock(return_value=_fake_token())
|
||||
from scribe.mcp.tools.design_systems import update_design_token
|
||||
from grimoire.mcp.tools.design_systems import update_design_token
|
||||
await update_design_token(token_id=9, order_index=0)
|
||||
assert svc.update_token.await_args.kwargs["order_index"] == 0
|
||||
|
||||
@@ -132,9 +132,9 @@ async def test_update_token_accepts_order_index_zero():
|
||||
async def test_update_token_can_set_an_empty_value_map():
|
||||
"""`value_by_mode={}` is meaningful — it strips every mode from a token. The
|
||||
guard is `is not None`, not truthiness, or that edit would be unreachable."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_token = AsyncMock(return_value=_fake_token())
|
||||
from scribe.mcp.tools.design_systems import update_design_token
|
||||
from grimoire.mcp.tools.design_systems import update_design_token
|
||||
await update_design_token(token_id=9, value_by_mode={})
|
||||
assert svc.update_token.await_args.kwargs["value_by_mode"] == {}
|
||||
|
||||
@@ -143,9 +143,9 @@ async def test_update_token_can_set_an_empty_value_map():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clearing_a_projects_design_system_passes_none():
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.set_project_design_system = AsyncMock(return_value=True)
|
||||
from scribe.mcp.tools.design_systems import set_project_design_system
|
||||
from grimoire.mcp.tools.design_systems import set_project_design_system
|
||||
result = await set_project_design_system(project_id=2, design_system_id=-1)
|
||||
assert svc.set_project_design_system.await_args.args[2] is None
|
||||
assert result["design_system_id"] is None
|
||||
@@ -156,7 +156,7 @@ async def test_resolve_returns_serialised_tokens_with_their_provenance():
|
||||
"""The payload has to carry the shadowed entries, not just the winner —
|
||||
dropping them at the serialisation boundary would discard the one thing
|
||||
resolution was built to preserve."""
|
||||
from scribe.services.design_cascade import resolve_tokens
|
||||
from grimoire.services.design_cascade import resolve_tokens
|
||||
|
||||
|
||||
resolved = resolve_tokens(
|
||||
@@ -164,9 +164,9 @@ async def test_resolve_returns_serialised_tokens_with_their_provenance():
|
||||
{1: [design_token_stub("--fs-accent", {"base": "#6b2118"})],
|
||||
2: [design_token_stub("--fs-accent", {"base": "#5b4a8a"})]},
|
||||
)
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.resolve_design_system = AsyncMock(return_value=resolved)
|
||||
from scribe.mcp.tools.design_systems import resolve_design_system
|
||||
from grimoire.mcp.tools.design_systems import resolve_design_system
|
||||
payload = await resolve_design_system(design_system_id=2)
|
||||
|
||||
token = payload["tokens"][0]
|
||||
@@ -183,18 +183,18 @@ async def test_update_token_can_clear_supersedes_with_an_empty_list():
|
||||
"""`[]` means "this token replaces nothing after all" — a real edit. Guarded
|
||||
on `is not None` so it isn't swallowed as "unchanged", the same trap
|
||||
update_snippet guards against."""
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_token = AsyncMock(return_value=_fake_token())
|
||||
from scribe.mcp.tools.design_systems import update_design_token
|
||||
from grimoire.mcp.tools.design_systems import update_design_token
|
||||
await update_design_token(token_id=9, supersedes=[])
|
||||
assert svc.update_token.await_args.kwargs["supersedes"] == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_token_leaves_supersedes_alone_when_omitted():
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc") as svc:
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc") as svc:
|
||||
svc.update_token = AsyncMock(return_value=_fake_token())
|
||||
from scribe.mcp.tools.design_systems import update_design_token
|
||||
from grimoire.mcp.tools.design_systems import update_design_token
|
||||
await update_design_token(token_id=9, purpose="text on action surfaces")
|
||||
assert "supersedes" not in svc.update_token.await_args.kwargs
|
||||
|
||||
@@ -206,15 +206,15 @@ async def test_get_design_system_carries_the_chain_merged_guidance():
|
||||
"""enter_project carries only a summary and points here. The
|
||||
system's own `guidance` field is only its departures; a session building
|
||||
UI needs the house style too, so the merged form rides alongside."""
|
||||
from scribe.mcp.tools.design_systems import get_design_system
|
||||
from grimoire.mcp.tools.design_systems import get_design_system
|
||||
|
||||
merged = [{"design_system_id": 1, "title": "House", "guidance": "house style"},
|
||||
{"design_system_id": 2, "title": "App", "guidance": "departures"}]
|
||||
with patch("scribe.mcp.tools.design_systems.ds_svc.get_design_system",
|
||||
with patch("grimoire.mcp.tools.design_systems.ds_svc.get_design_system",
|
||||
AsyncMock(return_value=_fake_design_system())), \
|
||||
patch("scribe.mcp.tools.design_systems.ds_svc.list_tokens",
|
||||
patch("grimoire.mcp.tools.design_systems.ds_svc.list_tokens",
|
||||
AsyncMock(return_value=[])), \
|
||||
patch("scribe.mcp.tools.design_systems.ds_svc.design_context",
|
||||
patch("grimoire.mcp.tools.design_systems.ds_svc.design_context",
|
||||
AsyncMock(return_value={"guidance": merged})):
|
||||
out = await get_design_system(2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user