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:
@@ -21,9 +21,9 @@ def _no_exclusions():
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_rulebook_stores_to_db():
|
||||
mock_session = make_mock_session()
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import create_rulebook
|
||||
from grimoire.services.rulebooks import create_rulebook
|
||||
await create_rulebook(
|
||||
user_id=7, title="FabledSword family", description="rules for the family",
|
||||
)
|
||||
@@ -38,9 +38,9 @@ async def test_list_rulebooks_returns_owned_only():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalars.return_value.all.return_value = [rb]
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import list_rulebooks
|
||||
from grimoire.services.rulebooks import list_rulebooks
|
||||
results = await list_rulebooks(user_id=7)
|
||||
assert len(results) == 1
|
||||
|
||||
@@ -52,9 +52,9 @@ async def test_get_rulebook_returns_none_when_not_owner():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalar_one_or_none.return_value = None
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_rulebook
|
||||
from grimoire.services.rulebooks import get_rulebook
|
||||
result = await get_rulebook(rulebook_id=1, user_id=99)
|
||||
assert result is None
|
||||
|
||||
@@ -66,9 +66,9 @@ async def test_update_rulebook_only_sets_provided_fields():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalar_one_or_none.return_value = rb
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import update_rulebook
|
||||
from grimoire.services.rulebooks import update_rulebook
|
||||
await update_rulebook(rulebook_id=1, user_id=7, title="new")
|
||||
assert rb.title == "new"
|
||||
|
||||
@@ -81,9 +81,9 @@ async def test_delete_rulebook_calls_delete():
|
||||
mock_result.scalar_one_or_none.return_value = rb
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
mock_session.delete = AsyncMock()
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import delete_rulebook
|
||||
from grimoire.services.rulebooks import delete_rulebook
|
||||
await delete_rulebook(rulebook_id=1, user_id=7)
|
||||
assert mock_session.delete.called
|
||||
|
||||
@@ -97,9 +97,9 @@ async def test_create_topic_requires_owned_rulebook():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalar_one_or_none.return_value = None
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import create_topic
|
||||
from grimoire.services.rulebooks import create_topic
|
||||
with pytest.raises(ValueError, match="not found"):
|
||||
await create_topic(
|
||||
rulebook_id=999, user_id=7, title="git-workflow",
|
||||
@@ -119,9 +119,9 @@ async def test_list_topics_returns_topics_for_owned_rulebook():
|
||||
topic_result.scalars.return_value.all.return_value = [topic]
|
||||
mock_session.execute = AsyncMock(side_effect=[rb_result, topic_result])
|
||||
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import list_topics
|
||||
from grimoire.services.rulebooks import list_topics
|
||||
results = await list_topics(rulebook_id=1, user_id=7)
|
||||
assert len(results) == 1
|
||||
assert results[0].title == "git-workflow"
|
||||
@@ -135,9 +135,9 @@ async def test_create_rule_requires_owned_topic():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalar_one_or_none.return_value = None # topic not found
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import create_rule
|
||||
from grimoire.services.rulebooks import create_rule
|
||||
with pytest.raises(ValueError, match="topic .* not found"):
|
||||
await create_rule(
|
||||
topic_id=999, user_id=7, title="x", statement="y",
|
||||
@@ -153,9 +153,9 @@ async def test_list_rules_filters_by_topic_id():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalars.return_value.all.return_value = [rule]
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import list_rules
|
||||
from grimoire.services.rulebooks import list_rules
|
||||
results = await list_rules(user_id=7, topic_id=10)
|
||||
assert len(results) == 1
|
||||
|
||||
@@ -166,9 +166,9 @@ async def test_get_rule_returns_none_when_not_owner():
|
||||
mock_result = MagicMock()
|
||||
mock_result.scalar_one_or_none.return_value = None
|
||||
mock_session.execute = AsyncMock(return_value=mock_result)
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_rule
|
||||
from grimoire.services.rulebooks import get_rule
|
||||
result = await get_rule(rule_id=1, user_id=99)
|
||||
assert result is None
|
||||
|
||||
@@ -193,11 +193,11 @@ def _no_edges():
|
||||
execute() calls happen to arrive in.
|
||||
"""
|
||||
return (
|
||||
patch("scribe.services.rulebooks.co_surfaced_partners",
|
||||
patch("grimoire.services.rulebooks.co_surfaced_partners",
|
||||
AsyncMock(return_value=[])),
|
||||
patch("scribe.services.rulebooks.list_rule_relations",
|
||||
patch("grimoire.services.rulebooks.list_rule_relations",
|
||||
AsyncMock(return_value={})),
|
||||
patch("scribe.services.rulebooks.list_rule_systems",
|
||||
patch("grimoire.services.rulebooks.list_rule_systems",
|
||||
AsyncMock(return_value={})),
|
||||
)
|
||||
|
||||
@@ -228,9 +228,9 @@ async def test_get_applicable_rules_returns_shape():
|
||||
])
|
||||
|
||||
_p1, _p2, _p3 = _no_edges()
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_applicable_rules
|
||||
from grimoire.services.rulebooks import get_applicable_rules
|
||||
result = await get_applicable_rules(project_id=3, user_id=7, limit=50)
|
||||
|
||||
assert set(result) == {"rules", "project_rules", "truncated"}
|
||||
@@ -254,9 +254,9 @@ async def test_get_applicable_rules_truncates_when_over_limit():
|
||||
])
|
||||
|
||||
_p1, _p2, _p3 = _no_edges()
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_applicable_rules
|
||||
from grimoire.services.rulebooks import get_applicable_rules
|
||||
result = await get_applicable_rules(project_id=3, user_id=7, limit=50)
|
||||
|
||||
assert result["truncated"] is True
|
||||
@@ -272,9 +272,9 @@ async def test_a_project_with_no_areas_lists_no_global_rules():
|
||||
mock_session.execute = AsyncMock(side_effect=[_areas(), _empty()])
|
||||
|
||||
_p1, _p2, _p3 = _no_edges()
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_applicable_rules
|
||||
from grimoire.services.rulebooks import get_applicable_rules
|
||||
result = await get_applicable_rules(project_id=3, user_id=7)
|
||||
|
||||
assert result["rules"] == []
|
||||
@@ -295,9 +295,9 @@ async def test_get_applicable_rules_includes_project_scoped_rules():
|
||||
mock_session.execute = AsyncMock(side_effect=[_areas(), proj_rules_result])
|
||||
|
||||
_p1, _p2, _p3 = _no_edges()
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls, _p1, _p2, _p3:
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_applicable_rules
|
||||
from grimoire.services.rulebooks import get_applicable_rules
|
||||
result = await get_applicable_rules(project_id=3, user_id=7)
|
||||
|
||||
assert len(result["project_rules"]) == 2
|
||||
@@ -319,12 +319,12 @@ async def test_a_co_surfaces_partner_on_another_project_is_not_dragged_in():
|
||||
fake_rule(id=202, topic_id=None, project_id=8, title="other-project partner"),
|
||||
]
|
||||
|
||||
with patch("scribe.services.rulebooks.async_session") as mock_cls, \
|
||||
patch("scribe.services.rulebooks.co_surfaced_partners", AsyncMock(return_value=partners)), \
|
||||
patch("scribe.services.rulebooks.list_rule_relations", AsyncMock(return_value={})), \
|
||||
patch("scribe.services.rulebooks.list_rule_systems", AsyncMock(return_value={})):
|
||||
with patch("grimoire.services.rulebooks.async_session") as mock_cls, \
|
||||
patch("grimoire.services.rulebooks.co_surfaced_partners", AsyncMock(return_value=partners)), \
|
||||
patch("grimoire.services.rulebooks.list_rule_relations", AsyncMock(return_value={})), \
|
||||
patch("grimoire.services.rulebooks.list_rule_systems", AsyncMock(return_value={})):
|
||||
mock_cls.return_value = mock_session
|
||||
from scribe.services.rulebooks import get_applicable_rules
|
||||
from grimoire.services.rulebooks import get_applicable_rules
|
||||
result = await get_applicable_rules(project_id=3, user_id=7)
|
||||
|
||||
assert [r["id"] for r in result["rules"]] == [200, 201]
|
||||
@@ -340,7 +340,7 @@ def test_rule_brief_carries_age_but_not_the_deep_fields():
|
||||
why a rule written before the capability it duplicates was
|
||||
indistinguishable at read time from one still doing work.
|
||||
"""
|
||||
from scribe.services.rulebooks import rule_brief
|
||||
from grimoire.services.rulebooks import rule_brief
|
||||
|
||||
out = rule_brief(fake_rule(
|
||||
when_to_apply="before any git push",
|
||||
@@ -358,7 +358,7 @@ def test_rule_brief_carries_age_but_not_the_deep_fields():
|
||||
def test_rule_brief_omits_keys_a_rule_has_no_value_for():
|
||||
"""A null key reads as a capability the record has and isn't using,
|
||||
which is a different claim from not having one."""
|
||||
from scribe.services.rulebooks import rule_brief
|
||||
from grimoire.services.rulebooks import rule_brief
|
||||
|
||||
out = rule_brief(fake_rule())
|
||||
assert "when_to_apply" not in out
|
||||
@@ -377,7 +377,7 @@ def test_a_rule_with_no_check_says_nothing_about_verification():
|
||||
would be worthless — every rule would look like something someone ought to
|
||||
be verifying, and the handful that genuinely rot would stop standing out.
|
||||
"""
|
||||
from scribe.services.rulebooks import last_verified_label, rule_brief
|
||||
from grimoire.services.rulebooks import last_verified_label, rule_brief
|
||||
|
||||
rule = fake_rule()
|
||||
assert last_verified_label(rule) is None
|
||||
@@ -388,7 +388,7 @@ def test_an_unverified_constraint_reads_never_rather_than_null():
|
||||
"""A null key reads as a capability going unused. "never" is
|
||||
a different and much stronger claim — this rule asserts a fact about
|
||||
someone else's software and nobody has ever confirmed it."""
|
||||
from scribe.services.rulebooks import last_verified_label, rule_brief
|
||||
from grimoire.services.rulebooks import last_verified_label, rule_brief
|
||||
|
||||
rule = fake_rule(verify_with="cat CI-runner/renovate/config.js")
|
||||
assert last_verified_label(rule) == "never"
|
||||
@@ -398,7 +398,7 @@ def test_an_unverified_constraint_reads_never_rather_than_null():
|
||||
def test_a_verified_constraint_reports_the_date_it_was_checked():
|
||||
"""A date, not a stamp — the question is "how old is this", the same call
|
||||
rule_brief makes for updated_at."""
|
||||
from scribe.services.rulebooks import last_verified_label
|
||||
from grimoire.services.rulebooks import last_verified_label
|
||||
|
||||
rule = fake_rule(
|
||||
verify_with="cat CI-runner/renovate/config.js",
|
||||
@@ -411,7 +411,7 @@ def test_the_check_text_itself_never_enters_a_listing():
|
||||
"""A listing says WHICH rules can rot, not how to test them. The check can
|
||||
be a long command; multiplied across an always-on set it is the same bloat
|
||||
`why` and `how_to_apply` are kept out of a brief to avoid."""
|
||||
from scribe.services.rulebooks import rule_brief
|
||||
from grimoire.services.rulebooks import rule_brief
|
||||
|
||||
out = rule_brief(fake_rule(
|
||||
verify_with="a very long command " * 20,
|
||||
@@ -430,7 +430,7 @@ def test_a_sweep_row_carries_the_check_in_full():
|
||||
rule. A sweep row exists to be acted on — the reader is about to go and
|
||||
run the check — so the text is the payload's point, not its bloat.
|
||||
"""
|
||||
from scribe.services.rulebooks import verification_row
|
||||
from grimoire.services.rulebooks import verification_row
|
||||
|
||||
row = verification_row(fake_rule(
|
||||
verify_with="cat CI-runner/renovate/config.js",
|
||||
@@ -448,7 +448,7 @@ def test_never_verified_reports_no_day_count_rather_than_zero():
|
||||
Getting this wrong would invert the row's meaning for exactly the rules
|
||||
that most need attention.
|
||||
"""
|
||||
from scribe.services.rulebooks import verification_row
|
||||
from grimoire.services.rulebooks import verification_row
|
||||
|
||||
row = verification_row(fake_rule(verify_with="read the workflow"))
|
||||
assert row["last_verified"] == "never"
|
||||
@@ -458,7 +458,7 @@ def test_never_verified_reports_no_day_count_rather_than_zero():
|
||||
def test_a_verified_row_counts_the_days():
|
||||
from datetime import timedelta
|
||||
|
||||
from scribe.services.rulebooks import verification_row
|
||||
from grimoire.services.rulebooks import verification_row
|
||||
|
||||
row = verification_row(fake_rule(
|
||||
verify_with="read the workflow",
|
||||
|
||||
Reference in New Issue
Block a user