Files
FabledGrimoire/tests/test_settings_defaults_agree.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

133 lines
5.8 KiB
Python

"""The Settings UI shows the default the server actually uses.
WHY THIS EXISTS
A retrieval threshold lives in two places by necessity: a Python constant the
arm reads when no row is stored, and a Vue `ref` the Settings form shows when
the operator has never touched it. Neither can import the other.
So the form's initial value is a CLAIM about the server's behaviour, and it is
the kind of claim that rots quietly. Retune the Python constant and the input
keeps rendering the old number — the operator reads it as the bar in force,
sees no reason to change anything, and the form has misinformed them about the
one fact it exists to convey. Nothing errors, nothing looks wrong, and the
value they are shown is simply not the value being applied.
WHAT THIS PINS
The RELATIONSHIP, never the number: for each threshold, the Vue ref's initial
string equals the Python default. Retuning either stays free as long as both
move — which is the point, because these are tuning values and they move.
The Vue side is read from SOURCE, because that file cannot be imported at all.
The Python side is read from source for a loose constant and IMPORTED for a
registry surface — the registry is a plain table of frozen dataclasses with
nothing to trigger on import, and importing it means this guard checks the value
the server will actually resolve rather than a literal that happens to look
right.
It cannot check that the form WRITES the right key — that is behaviour, and
the keys are asserted where they are built. It catches the drift that has no
other alarm.
"""
from __future__ import annotations
import pathlib
import re
import pytest
ROOT = pathlib.Path(__file__).resolve().parents[1]
_SERVICES = ROOT / "src" / "grimoire" / "services"
_VUE = ROOT / "frontend" / "src" / "views" / "SettingsView.vue"
# THE SIX RETRIEVAL FLOORS live in one registry, so their side of
# the pairing is a SURFACE NAME rather than a module constant. That is strictly
# better for this guard: the surface name is also the telemetry source, so a row
# here names the same arm the readout does, and a floor that moved cannot be
# checked against a stale constant that happened to keep its old value.
_SURFACE_PAIRS = (
("auto_inject", "kbInjectThreshold"),
("write_path", "kbWritePathThreshold"),
("write_path_rule", "kbRuleHintThreshold"),
("pre_tool_rule", "kbToolRuleThreshold"),
("prompt_rule", "kbPromptRuleThreshold"),
("reply_rule", "kbReplyRuleThreshold"),
)
# (services module, python constant, vue ref) for the defaults that are NOT
# retrieval surfaces. Hand-written because the pairing is an editorial fact —
# the names share no convention either side could derive — and asserted to
# exist on both sides, so a rename fails loudly rather than silently dropping
# that threshold from the check.
_CONSTANT_PAIRS = (
# The plan gate: it blocks a create, so a form showing a
# looser bar than the one in force would be the more misleading drift. Not
# a push surface, so it has no registry entry and keeps the older shape.
("dedup.py", "PLAN_MATCH_DEFAULT_THRESHOLD", "kbPlanMatchThreshold"),
)
def _python_default(module: str, name: str) -> float:
m = re.search(rf"^{re.escape(name)}\s*=\s*([0-9.]+)\s*$",
(_SERVICES / module).read_text(), re.M)
assert m, (
f"{name} is no longer a bare module-level float in "
f"services/{module}. If it moved or was renamed, update "
f"_PAIRS; if it was retired, drop its row — leaving it here checks "
f"nothing while looking like coverage."
)
return float(m.group(1))
def _vue_default(ref_name: str) -> float:
m = re.search(rf"const {re.escape(ref_name)} = ref\(\"([0-9.]+)\"\)",
_VUE.read_text())
assert m, (
f"{ref_name} is no longer a `ref(\"<number>\")` in SettingsView.vue. "
f"If the control was renamed, update _PAIRS; if it was removed, the "
f"setting has lost its UI and that is the thing to fix."
)
return float(m.group(1))
def _assert_agrees(server: float, form: float, ref_name: str, what: str) -> None:
assert form == server, (
f"SettingsView shows {form} for {ref_name} while the server defaults "
f"to {server} ({what}). An operator who has never set this reads the "
f"form as the value in force, so the two must move together — retune "
f"both, or neither."
)
@pytest.mark.parametrize(("surface", "ref_name"), _SURFACE_PAIRS,
ids=[p[0] for p in _SURFACE_PAIRS])
def test_the_form_shows_the_floor_the_surface_uses(surface, ref_name):
"""An untouched control must render the bar actually in force."""
from grimoire.services.retrieval_surfaces import get_surface
_assert_agrees(get_surface(surface).floor_default, _vue_default(ref_name),
ref_name, f"{surface} floor")
def test_every_tunable_surface_has_a_control():
"""A number an operator may need different has a UI or it is
not shipped. Derived from the registry, so a seventh surface arrives here
as a failure rather than as a setting only the model can reach."""
from grimoire.services.retrieval_surfaces import surface_names
covered = {s for s, _ref in _SURFACE_PAIRS}
missing = [n for n in surface_names() if n not in covered]
assert not missing, (
f"these surfaces are tunable with no Settings control: {missing}. "
"Add the control and its row, or say in _SURFACE_PAIRS why it has none."
)
@pytest.mark.parametrize(("module", "constant", "ref_name"), _CONSTANT_PAIRS,
ids=[p[1] for p in _CONSTANT_PAIRS])
def test_the_form_shows_the_default_the_server_uses(module, constant, ref_name):
"""Same claim, for the defaults that are not retrieval surfaces."""
_assert_agrees(_python_default(module, constant), _vue_default(ref_name),
ref_name, constant)