Files
FabledGrimoire/tests/test_forge_token_sealing.py
bvandeusenandClaude Opus 5.5 0f5ef0be63
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
rename(547): the server is grimoire — package, image, DB defaults, backups
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>
2026-10-22 10:50:45 -04:00

48 lines
1.8 KiB
Python

"""Forge-connection tokens are stored sealed.
Migration 0122 seals the rows that existed before, with its OWN frozen copy of
the v1 key derivation (so a later edit to services/secrets.py can never change
what an old migration writes). That copy is only right while it agrees with
what the app opens today — which is what this pins. If secrets.py ever moves
to a v2 key, it must still open v1 values, and this test says so.
"""
import importlib.util
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from grimoire.services import secrets
from grimoire.services.forge_connections import connection_token
_MIGRATION = Path(__file__).resolve().parents[1] / "alembic" / "versions" / "0122_seal_forge_tokens.py"
def _migration():
spec = importlib.util.spec_from_file_location("migration_0122", _MIGRATION)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_what_the_migration_seals_the_app_opens():
sealed = _migration().seal_v1("gitea-read-token")
assert "gitea-read-token" not in sealed
assert secrets.unseal(sealed) == "gitea-read-token"
def test_a_row_reads_through_connection_token():
row = SimpleNamespace(token_sealed=secrets.seal("ghp_abc"))
assert connection_token(row) == "ghp_abc"
def test_a_row_sealed_under_another_key_reads_as_none():
row = SimpleNamespace(token_sealed=secrets.seal("ghp_abc"))
with patch.object(secrets.Config, "SECRET_KEY", "a-different-instance"):
assert connection_token(row) is None
def test_a_left_over_plain_text_token_is_not_used_as_one():
"""A value that never went through seal is refused, not passed to the
forge as if it were the token: a reader sees None, never the raw bytes."""
assert connection_token(SimpleNamespace(token_sealed="plain-text-token")) is None