Files
FabledGrimoire/tests/test_settings_defaults_agree.py
bvandeusenandClaude Opus 5.5 c8ede427ca
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m7s
CI & Build / Python tests (push) Failing after 1m30s
CI & Build / Build & push image (push) Skipped
align(4909d): every rule arm that annotates reads one bar, kb_rule_threshold at 0.70
prompt_rule, pre_tool_rule and write_path_rule now share one settings key,
RULE_FLOOR_KEY = "kb_rule_threshold", default 0.70. The reply backstop keeps
its own 0.80, because there the floor is a stop bar. Moving the shared floor
through tune_retrieval or the Settings form records a tuning event on every
surface that reads it (set_dial, via retrieval_surfaces.sharing_floor), and
the result names the surfaces it carried as `moved_with`. Settings shows one
"Standing-rule confidence threshold" control where it showed three.

Operator ruling (2026-10-05): one shared bar for the rule arms, "especially
since we're only taking the first x number of them" -- the budget limits
what is shown; the bar only decides what is worth ranking. Kept a dial.

Why 0.70, from records read rather than percentiles:
- write_path_rule near-misses at 0.7199-0.72 were all false positives
  (rule 149 against a Lidarr comment, 157 against a retention Svelte file,
  121 against an Archives restore, 164 against a config test, 131 against a
  Go package), so the old 0.72 was not hiding real hits -- but the band does
  noise control on this arm, so dropping 0.02 costs little.
- prompt_rule: the operator set 0.70 by hand on 2026-09-18, after a 0.60
  experiment admitted junk and was reverted. 0.70 is the number with a human
  decision behind it.
- pre_tool_rule lines near 0.68 this session were mostly noise; rising to
  0.70 trims them, and the band still narrows what clears.

Retired keys: kb_promptrule_threshold, kb_toolrule_threshold and
kb_rulehint_threshold are no longer read. Rows an install stored under them
stay in the settings table, inert. On this instance the effective bars move
pre_tool_rule 0.68 -> 0.70, write_path_rule 0.72 -> 0.70, prompt_rule stays
0.70; record_release_defaults writes those per-surface release rows on boot,
so the telemetry shift has an event to explain it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:32:09 -04:00

134 lines
5.9 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 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"),
# The three rule hints read one bar, so one control serves them.
("write_path_rule", "kbRuleThreshold"),
("pre_tool_rule", "kbRuleThreshold"),
("prompt_rule", "kbRuleThreshold"),
("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)