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:
@@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from scribe.services.design_systems import DesignSystemCycle
|
||||
from grimoire.services.design_systems import DesignSystemCycle
|
||||
from tests.helpers import make_mock_session
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ async def test_create_with_a_parent_is_denied_without_write_on_that_parent():
|
||||
"""Parenting to someone else's system would let their delete or re-parent
|
||||
silently restyle your app, so the chain may only be built from systems you
|
||||
can write — which, per the ACL, means ones you own."""
|
||||
with patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_design_system = AsyncMock(return_value=False)
|
||||
from scribe.services.design_systems import create_design_system
|
||||
from grimoire.services.design_systems import create_design_system
|
||||
result = await create_design_system(user_id=1, title="Grimoire", parent_id=7)
|
||||
assert result is None
|
||||
|
||||
@@ -36,11 +36,11 @@ async def test_create_without_a_parent_never_consults_the_parent_acl():
|
||||
title=obj.title, parent_id=obj.parent_id
|
||||
)
|
||||
)
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_design_system = AsyncMock(return_value=False)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import create_design_system
|
||||
from grimoire.services.design_systems import create_design_system
|
||||
await create_design_system(user_id=1, title=" FabledSword ")
|
||||
assert captured["title"] == "FabledSword" # stripped
|
||||
assert captured["parent_id"] is None
|
||||
@@ -57,13 +57,13 @@ async def test_reparenting_into_a_loop_raises_rather_than_returning_none():
|
||||
mock_session = make_mock_session()
|
||||
mock_session.get = AsyncMock(return_value=MagicMock(deleted_at=None, parent_id=None))
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc, \
|
||||
patch("scribe.services.design_systems._parent_map",
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc, \
|
||||
patch("grimoire.services.design_systems._parent_map",
|
||||
AsyncMock(return_value={1: None, 2: 1})):
|
||||
acc.can_write_design_system = AsyncMock(return_value=True)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import update_design_system
|
||||
from grimoire.services.design_systems import update_design_system
|
||||
with pytest.raises(DesignSystemCycle):
|
||||
await update_design_system(user_id=1, design_system_id=1, parent_id=2)
|
||||
|
||||
@@ -79,13 +79,13 @@ async def test_clearing_the_parent_is_allowed_and_is_not_read_as_no_change():
|
||||
mock_session = make_mock_session()
|
||||
mock_session.get = AsyncMock(return_value=system)
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc, \
|
||||
patch("scribe.services.design_systems._parent_map",
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc, \
|
||||
patch("grimoire.services.design_systems._parent_map",
|
||||
AsyncMock(return_value={1: 5, 5: None})):
|
||||
acc.can_write_design_system = AsyncMock(return_value=True)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import update_design_system
|
||||
from grimoire.services.design_systems import update_design_system
|
||||
await update_design_system(user_id=1, design_system_id=1, parent_id=None)
|
||||
|
||||
assert system.parent_id is None
|
||||
@@ -95,9 +95,9 @@ async def test_clearing_the_parent_is_allowed_and_is_not_read_as_no_change():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_token_denied_without_write_on_the_system():
|
||||
with patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_design_system = AsyncMock(return_value=False)
|
||||
from scribe.services.design_systems import create_token
|
||||
from grimoire.services.design_systems import create_token
|
||||
result = await create_token(user_id=1, design_system_id=3, name="--fs-obsidian")
|
||||
assert result is None
|
||||
|
||||
@@ -113,11 +113,11 @@ async def test_token_values_default_to_an_empty_map_not_json_null():
|
||||
side_effect=lambda obj: captured.update(value_by_mode=obj.value_by_mode)
|
||||
)
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_design_system = AsyncMock(return_value=True)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import create_token
|
||||
from grimoire.services.design_systems import create_token
|
||||
await create_token(
|
||||
user_id=1, design_system_id=3, name="--fs-radius-md", value_by_mode=None
|
||||
)
|
||||
@@ -127,9 +127,9 @@ async def test_token_values_default_to_an_empty_map_not_json_null():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_tokens_denied_returns_empty():
|
||||
with patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_read_design_system = AsyncMock(return_value=False)
|
||||
from scribe.services.design_systems import list_tokens
|
||||
from grimoire.services.design_systems import list_tokens
|
||||
assert await list_tokens(user_id=1, design_system_id=3) == []
|
||||
|
||||
|
||||
@@ -137,10 +137,10 @@ async def test_list_tokens_denied_returns_empty():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pointing_a_project_at_a_system_needs_write_on_the_project():
|
||||
with patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_project = AsyncMock(return_value=False)
|
||||
acc.can_read_design_system = AsyncMock(return_value=True)
|
||||
from scribe.services.design_systems import set_project_design_system
|
||||
from grimoire.services.design_systems import set_project_design_system
|
||||
assert await set_project_design_system(1, project_id=5, design_system_id=3) is False
|
||||
|
||||
|
||||
@@ -153,13 +153,13 @@ async def test_pointing_a_project_needs_only_READ_on_the_system():
|
||||
mock_session = make_mock_session()
|
||||
mock_session.get = AsyncMock(return_value=project)
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_project = AsyncMock(return_value=True)
|
||||
acc.can_read_design_system = AsyncMock(return_value=True)
|
||||
acc.can_write_design_system = AsyncMock(return_value=False)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import set_project_design_system
|
||||
from grimoire.services.design_systems import set_project_design_system
|
||||
assert await set_project_design_system(1, project_id=5, design_system_id=3) is True
|
||||
|
||||
assert project.design_system_id == 3
|
||||
@@ -173,12 +173,12 @@ async def test_clearing_a_projects_design_system_skips_the_system_acl():
|
||||
mock_session = make_mock_session()
|
||||
mock_session.get = AsyncMock(return_value=project)
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_project = AsyncMock(return_value=True)
|
||||
acc.can_read_design_system = AsyncMock(return_value=False)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import set_project_design_system
|
||||
from grimoire.services.design_systems import set_project_design_system
|
||||
assert await set_project_design_system(1, project_id=5, design_system_id=None) is True
|
||||
|
||||
assert project.design_system_id is None
|
||||
@@ -192,9 +192,9 @@ async def test_resolve_denied_returns_none_not_an_empty_set():
|
||||
"""None and [] mean different things here: "you may not see this" versus
|
||||
"this chain genuinely holds no tokens". A caller that got [] for a denial
|
||||
would render an empty design system as though it were real."""
|
||||
with patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_read_design_system = AsyncMock(return_value=False)
|
||||
from scribe.services.design_systems import resolve_design_system
|
||||
from grimoire.services.design_systems import resolve_design_system
|
||||
assert await resolve_design_system(user_id=1, design_system_id=3) is None
|
||||
|
||||
|
||||
@@ -217,12 +217,12 @@ async def test_resolve_scopes_the_hierarchy_to_the_systems_OWNER_not_the_caller(
|
||||
)
|
||||
parent_map = AsyncMock(return_value={3: None})
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems._parent_map", parent_map), \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems._parent_map", parent_map), \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_read_design_system = AsyncMock(return_value=True)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import resolve_design_system
|
||||
from grimoire.services.design_systems import resolve_design_system
|
||||
result = await resolve_design_system(user_id=caller, design_system_id=3)
|
||||
|
||||
assert result == [] # empty chain, not None
|
||||
@@ -239,11 +239,11 @@ async def test_create_token_supersedes_defaults_to_an_empty_list_not_json_null()
|
||||
mock_session.add = MagicMock(
|
||||
side_effect=lambda obj: captured.update(supersedes=obj.supersedes)
|
||||
)
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_design_system = AsyncMock(return_value=True)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import create_token
|
||||
from grimoire.services.design_systems import create_token
|
||||
await create_token(user_id=1, design_system_id=3, name="--fs-x", supersedes=None)
|
||||
assert captured["supersedes"] == []
|
||||
|
||||
@@ -255,11 +255,11 @@ async def test_create_token_records_the_literals_it_replaces():
|
||||
mock_session.add = MagicMock(
|
||||
side_effect=lambda obj: captured.update(supersedes=obj.supersedes)
|
||||
)
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems.access") as acc:
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems.access") as acc:
|
||||
acc.can_write_design_system = AsyncMock(return_value=True)
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import create_token
|
||||
from grimoire.services.design_systems import create_token
|
||||
await create_token(
|
||||
user_id=1, design_system_id=3, name="--fs-text-on-action",
|
||||
value_by_mode={"base": "#e8e4d8"}, supersedes=["#fff", "#ffffff"],
|
||||
@@ -271,9 +271,9 @@ async def test_create_token_records_the_literals_it_replaces():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stylesheet_denied_returns_none():
|
||||
with patch("scribe.services.design_systems.resolve_design_system",
|
||||
with patch("grimoire.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=None)):
|
||||
from scribe.services.design_systems import stylesheet_for_system
|
||||
from grimoire.services.design_systems import stylesheet_for_system
|
||||
assert await stylesheet_for_system(1, 3) is None
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ async def test_stylesheet_reports_what_the_sheet_cannot_say_for_itself():
|
||||
"""The CSS alone hides two things a reviewer needs: which tokens are still
|
||||
valueless, and which values are declared twice. Both ride alongside rather
|
||||
than being left for the reader to derive from the text."""
|
||||
from scribe.services.design_cascade import Contribution, ResolvedToken
|
||||
from grimoire.services.design_cascade import Contribution, ResolvedToken
|
||||
|
||||
def _resolved(name, base=None):
|
||||
return ResolvedToken(
|
||||
@@ -296,15 +296,15 @@ async def test_stylesheet_reports_what_the_sheet_cannot_say_for_itself():
|
||||
|
||||
system = MagicMock()
|
||||
system.title = "FabledSword"
|
||||
with patch("scribe.services.design_systems.resolve_design_system",
|
||||
with patch("grimoire.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=[
|
||||
_resolved("--fs-moss", "#4a5d3f"),
|
||||
_resolved("--fs-success", "#4a5d3f"),
|
||||
_resolved("--fs-radius-sm"),
|
||||
])), \
|
||||
patch("scribe.services.design_systems.get_design_system",
|
||||
patch("grimoire.services.design_systems.get_design_system",
|
||||
AsyncMock(return_value=system)):
|
||||
from scribe.services.design_systems import stylesheet_for_system
|
||||
from grimoire.services.design_systems import stylesheet_for_system
|
||||
result = await stylesheet_for_system(1, 3)
|
||||
|
||||
assert result["token_count"] == 3
|
||||
@@ -317,7 +317,7 @@ async def test_stylesheet_reports_what_the_sheet_cannot_say_for_itself():
|
||||
# --- design_context: the delivery side --------------------------------------
|
||||
|
||||
def _ctx_token(name: str, group: str | None):
|
||||
from scribe.services.design_cascade import ResolvedToken
|
||||
from grimoire.services.design_cascade import ResolvedToken
|
||||
return ResolvedToken(
|
||||
name=name, contributions={}, group_name=group,
|
||||
purpose=None, rationale=None, supersedes=(), order_index=0,
|
||||
@@ -345,16 +345,16 @@ async def test_design_context_merges_guidance_ANCESTOR_FIRST():
|
||||
scalars=MagicMock(return_value=MagicMock(all=lambda: [app, family]))
|
||||
))
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems._parent_map",
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems._parent_map",
|
||||
AsyncMock(return_value={3: 1, 1: None})), \
|
||||
patch("scribe.services.design_systems.resolve_design_system",
|
||||
patch("grimoire.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=[
|
||||
_ctx_token("--a", "surface"), _ctx_token("--b", "type"),
|
||||
_ctx_token("--c", "surface"), _ctx_token("--d", None),
|
||||
])):
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import design_context
|
||||
from grimoire.services.design_systems import design_context
|
||||
out = await design_context(user_id=42, design_system_id=3)
|
||||
|
||||
assert [g["title"] for g in out["guidance"]] == ["House", "App"]
|
||||
@@ -368,9 +368,9 @@ async def test_design_context_merges_guidance_ANCESTOR_FIRST():
|
||||
async def test_design_context_denied_returns_none():
|
||||
"""It rides on resolve_design_system's ACL rather than re-deriving one —
|
||||
a second permission path is a second thing to get wrong."""
|
||||
with patch("scribe.services.design_systems.resolve_design_system",
|
||||
with patch("grimoire.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=None)):
|
||||
from scribe.services.design_systems import design_context
|
||||
from grimoire.services.design_systems import design_context
|
||||
assert await design_context(user_id=1, design_system_id=3) is None
|
||||
|
||||
|
||||
@@ -392,13 +392,13 @@ async def test_design_context_omits_systems_with_no_guidance():
|
||||
scalars=MagicMock(return_value=MagicMock(all=lambda: [app, family]))
|
||||
))
|
||||
|
||||
with patch("scribe.services.design_systems.async_session") as mock_cls, \
|
||||
patch("scribe.services.design_systems._parent_map",
|
||||
with patch("grimoire.services.design_systems.async_session") as mock_cls, \
|
||||
patch("grimoire.services.design_systems._parent_map",
|
||||
AsyncMock(return_value={3: 1, 1: None})), \
|
||||
patch("scribe.services.design_systems.resolve_design_system",
|
||||
patch("grimoire.services.design_systems.resolve_design_system",
|
||||
AsyncMock(return_value=[])):
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.design_systems import design_context
|
||||
from grimoire.services.design_systems import design_context
|
||||
out = await design_context(user_id=42, design_system_id=3)
|
||||
|
||||
assert [g["title"] for g in out["guidance"]] == ["House"]
|
||||
|
||||
Reference in New Issue
Block a user