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
+55 -55
View File
@@ -8,7 +8,7 @@ from types import SimpleNamespace
import pytest
from scribe.services.retrieval_telemetry import (
from grimoire.services.retrieval_telemetry import (
_build_payload,
record_retrieval,
)
@@ -100,7 +100,7 @@ def test_the_count_of_hits_the_reader_already_held_is_carried():
def test_the_readout_reports_unmeasured_suppression_as_none():
"""`_bucket` renders the aggregate. No row reporting it → null, never a
zeroed dict: a zeroed dict states a measurement nobody made."""
from scribe.services.retrieval_telemetry import _bucket
from grimoire.services.retrieval_telemetry import _bucket
# calls, zero, p10, p50, p90, min, max, avg_n, dur,
# measured, supp_calls, supp_zero, miss_calls, miss_p50, miss_p90, miss_max
@@ -138,9 +138,9 @@ def test_record_retrieval_without_event_loop_is_safe():
async def test_insert_retrieval_log_roundtrip(_dispose_engine):
from sqlalchemy import delete, select
from scribe.models import async_session
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import _insert_retrieval_log
from grimoire.models import async_session
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import _insert_retrieval_log
payload = _build_payload(
user_id=990001, source="mcp_search", query="pgvector tuning",
@@ -179,10 +179,10 @@ async def test_insert_retrieval_log_roundtrip(_dispose_engine):
async def test_retrieval_summary_reads_what_the_writer_wrote(_dispose_engine):
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.note_usage import NoteUsageEvent
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import (
from grimoire.models import async_session
from grimoire.models.note_usage import NoteUsageEvent
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import (
_insert_retrieval_log, retrieval_summary,
)
@@ -252,7 +252,7 @@ async def test_retrieval_summary_reads_what_the_writer_wrote(_dispose_engine):
async def test_retrieval_summary_is_empty_not_broken_for_a_fresh_install(_dispose_engine):
"""An install with no telemetry gets a coherent zero readout, and
`read_failed` stays False — "no rows" is not "the read broke"."""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
out = await retrieval_summary(990003, days=30)
assert out["read_failed"] is False
@@ -272,9 +272,9 @@ async def test_retrieval_summary_sees_only_its_own_users_telemetry(_dispose_engi
included. The owner filter is the access rule, so it gets a test."""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import (
from grimoire.models import async_session
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import (
_insert_retrieval_log, retrieval_summary,
)
@@ -310,9 +310,9 @@ async def test_by_source_separates_a_surface_that_earns_its_noise_from_one_that_
"""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.note_usage import NoteUsageEvent
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.models import async_session
from grimoire.models.note_usage import NoteUsageEvent
from grimoire.services.retrieval_telemetry import retrieval_summary
UID = 990010
async with async_session() as s:
@@ -367,9 +367,9 @@ async def test_an_ambient_surface_reports_its_counts_but_no_ratio(_dispose_engin
counts stay visible and the ratio that would be misread is null."""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.note_usage import NoteUsageEvent
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.models import async_session
from grimoire.models.note_usage import NoteUsageEvent
from grimoire.services.retrieval_telemetry import retrieval_summary
UID = 990011
async with async_session() as s:
@@ -401,9 +401,9 @@ async def test_the_agent_pull_filter_does_not_treat_its_underscore_as_a_wildcard
else in the payload would reveal the difference."""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.note_usage import NoteUsageEvent
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.models import async_session
from grimoire.models.note_usage import NoteUsageEvent
from grimoire.services.retrieval_telemetry import retrieval_summary
UID = 990012
async with async_session() as s:
@@ -434,8 +434,8 @@ async def _rule_events(uid, rows):
"""Write (event, source) pairs for one rule and hand back a cleanup."""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.rule_usage import RuleUsageEvent
from grimoire.models import async_session
from grimoire.models.rule_usage import RuleUsageEvent
async with async_session() as s:
s.add_all([
@@ -466,7 +466,7 @@ async def test_rule_usage_is_a_coherent_zero_on_a_fresh_install(_dispose_engine)
zero asserts "rules were shown and none opened", which with an empty
numerator AND denominator is a claim the data does not support.
"""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
out = await retrieval_summary(990010, days=30)
assert out["read_failed"] is False
@@ -482,7 +482,7 @@ async def test_rule_usage_is_a_coherent_zero_on_a_fresh_install(_dispose_engine)
async def test_an_agent_reading_a_surfaced_rule_is_what_moves_the_ratio(_dispose_engine):
"""The whole point of the milestone: the arm can now be told apart from a
bar it cannot fail to clear."""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990011, [
(5001, "surfaced", "write_path_rule"),
@@ -512,7 +512,7 @@ async def test_a_person_browsing_the_rule_list_does_not_move_the_ratio(_dispose_
list in the web UI says nothing about the hint. Both are still counted in
`pulled`, so "is this rule dead weight?" stays answerable.
"""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990012, [
(5003, "surfaced", "write_path_rule"),
@@ -539,7 +539,7 @@ async def test_rule_events_stay_out_of_the_note_block(_dispose_engine):
was told about — and the rule arm would still be invisible, because a few
dozen rules against thousands of notes is noise on the note ratio.
"""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990013, [
(5004, "surfaced", "write_path_rule"),
@@ -561,7 +561,7 @@ async def test_rule_events_stay_out_of_the_note_block(_dispose_engine):
async def test_rule_usage_sees_only_its_own_users_events(_dispose_engine):
"""Same access rule as the rest of the readout — the owner filter IS the
rule for telemetry, which is not a shared record kind."""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990014, [
(5005, "surfaced", "write_path_rule"),
@@ -586,7 +586,7 @@ async def test_the_preload_lands_in_ambient_and_never_in_the_ratio(_dispose_engi
with the arm. So the resident deliveries are counted, reported, and kept
out of the ratio.
"""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990012, [
# One rule the arm actually chose, and opened.
@@ -624,7 +624,7 @@ async def test_ambient_alone_reports_no_ratio(_dispose_engine):
"""A brand-new install loads rules every session and may never trigger the
arm. That must read as "no ranked surfacings yet", not as a precision of
zero — the reading that would make a working install look broken."""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990013, [
(5201, "surfaced", "session_start"),
@@ -652,7 +652,7 @@ async def test_the_outcome_counts_come_back_split(_dispose_engine):
recorded", which is true of both and useful about neither: a departure is
evidence about the rule, an application is evidence about the agent.
"""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990016, [
# Opened and followed.
@@ -689,7 +689,7 @@ async def test_an_outcome_is_never_split_by_ambient(_dispose_engine):
"""`source` on an outcome row names the door the outcome came through, not
a ranker, so the surfaced/ambient split has nothing to say about it. An
outcome recorded from an unranked source must still count."""
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.services.retrieval_telemetry import retrieval_summary
cleanup = await _rule_events(990017, [
(5401, "surfaced", "session_start"),
@@ -722,7 +722,7 @@ def test_coverage_says_nothing_rather_than_false_when_nothing_was_recorded():
"""
from datetime import datetime, timezone
from scribe.services.retrieval_telemetry import _coverage
from grimoire.services.retrieval_telemetry import _coverage
since = datetime(2026, 9, 1, tzinfo=timezone.utc)
assert _coverage(None, since) == {
@@ -733,7 +733,7 @@ def test_coverage_says_nothing_rather_than_false_when_nothing_was_recorded():
def test_coverage_reads_a_start_before_the_window_as_covered():
from datetime import datetime, timezone
from scribe.services.retrieval_telemetry import _coverage
from grimoire.services.retrieval_telemetry import _coverage
since = datetime(2026, 9, 1, tzinfo=timezone.utc)
older = datetime(2026, 8, 1, tzinfo=timezone.utc)
@@ -763,9 +763,9 @@ async def test_coverage_is_per_source_because_the_table_is_older_than_its_arms(
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.models import async_session
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import retrieval_summary
UID = 990077
now = datetime.now(timezone.utc)
@@ -821,9 +821,9 @@ async def test_a_surface_that_went_silent_is_not_the_same_as_one_that_never_ran(
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.models import async_session
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import retrieval_summary
UID = 990078
now = datetime.now(timezone.utc)
@@ -877,9 +877,9 @@ async def test_a_section_is_complete_only_from_its_latest_contributor(
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.rule_usage import RuleUsageEvent
from scribe.services.retrieval_telemetry import retrieval_summary
from grimoire.models import async_session
from grimoire.models.rule_usage import RuleUsageEvent
from grimoire.services.retrieval_telemetry import retrieval_summary
UID = 990078
now = datetime.now(timezone.utc)
@@ -962,7 +962,7 @@ def test_the_readout_reports_unmeasured_near_misses_as_none():
"""`_bucket`'s half of the same discipline, and the reason it is a block
rather than three loose keys: old rows predate the column, so a window can
legitimately contain declines nobody measured."""
from scribe.services.retrieval_telemetry import _bucket
from grimoire.services.retrieval_telemetry import _bucket
# calls, zero, p10, p50, p90, min, max, avg_n, dur,
# measured, supp_calls, supp_zero, miss_calls, miss_p50, miss_p90, miss_max
@@ -988,7 +988,7 @@ def test_the_readout_carries_no_field_derivable_from_its_neighbours():
field reintroduces `calls - zero_result_calls` under a new name, the sum
below is where it shows up.
"""
from scribe.services.retrieval_telemetry import _bucket
from grimoire.services.retrieval_telemetry import _bucket
b = _bucket([326, 114, 0.6, 0.68, 0.77, 0.55, 0.85, 1.7, 130.9,
0, 0, 0, 114, 0.61, 0.71, 0.72])
@@ -1025,9 +1025,9 @@ async def test_the_near_miss_distribution_is_a_query_postgres_accepts(_dispose_e
"""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import (
from grimoire.models import async_session
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import (
_insert_retrieval_log, retrieval_summary,
)
@@ -1122,7 +1122,7 @@ def test_a_search_that_never_ran_writes_no_row():
"""
from unittest.mock import patch
import scribe.services.retrieval_telemetry as rt
import grimoire.services.retrieval_telemetry as rt
with patch.object(rt, "_build_payload") as build:
rt.record_retrieval(
@@ -1147,7 +1147,7 @@ def test_a_search_that_ran_and_found_nothing_still_writes_its_row():
"""
from unittest.mock import patch
import scribe.services.retrieval_telemetry as rt
import grimoire.services.retrieval_telemetry as rt
with patch.object(rt, "_build_payload") as build:
rt.record_retrieval(
@@ -1168,7 +1168,7 @@ def test_a_caller_that_never_asked_is_assumed_to_have_searched():
"""
from unittest.mock import patch
import scribe.services.retrieval_telemetry as rt
import grimoire.services.retrieval_telemetry as rt
with patch.object(rt, "_build_payload") as build:
rt.record_retrieval(
@@ -1233,9 +1233,9 @@ async def test_the_listing_names_the_highest_declines_per_source(_dispose_engine
"""
from sqlalchemy import delete
from scribe.models import async_session
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_telemetry import (
from grimoire.models import async_session
from grimoire.models.retrieval_log import RetrievalLog
from grimoire.services.retrieval_telemetry import (
_insert_retrieval_log, retrieval_summary,
)