diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue
index aa2c0e74..3a9a9b2a 100644
--- a/frontend/src/views/SettingsView.vue
+++ b/frontend/src/views/SettingsView.vue
@@ -159,16 +159,10 @@ const kbWritePathEnabled = ref(true);
// code embeddings sit on a much higher similarity floor than prose, so 0.55
// lets unrelated code through. Shares top-k, not the threshold.
const kbWritePathThreshold = ref("0.68");
-const kbRuleHintThreshold = ref("0.72");
+// One bar for every rule hint — prompt, command and edit alike. They search
+// the same rules, and what limits each is its budget, not the bar.
+const kbRuleThreshold = ref("0.70");
const kbCheckpointThreshold = ref("0.80");
-// The two ACT arms do not share a bar. A write-path query is a code payload; a
-// pre-tool query is a shell command, often under a dozen words — less text,
-// less signal, lower scores for the same relevance — so one shared bar makes
-// them behave like different subsystems.
-const kbToolRuleThreshold = ref("0.68");
-// And the prompt boundary is a third query shape again — the operator's own
-// prose rather than anything a tool produced.
-const kbPromptRuleThreshold = ref("0.72");
// The reply arm: its bar is the bar at which a finished reply
// is HELD for one read, so it sits with the checkpoint, not with the hints.
const kbReplyRuleThreshold = ref("0.8");
@@ -367,14 +361,10 @@ async function saveKbInject() {
// Same `|| default` reasoning: falling back to 0 would surface every
// snippet in the corpus on every edit, which is the failure this knob fixes.
const wpT = asBar(kbWritePathThreshold.value, 0.68);
- // Same `|| default` reasoning again, and it bites harder here: a rule hint
- // fires on every write, so a fallback of 0 would attach a standing rule to
- // every edit in the session.
- const rhT = asBar(kbRuleHintThreshold.value, 0.72);
- // Same `|| default` guard, and the same reason: this arm fires before every
- // Bash call, so a fallback of 0 would put a rule in front of every command.
- const trT = asBar(kbToolRuleThreshold.value, 0.68);
- const prT = asBar(kbPromptRuleThreshold.value, 0.72);
+ // Same `|| default` reasoning again, and it bites harder here: rule hints
+ // fire on every edit and before every command, so a fallback of 0 would put
+ // a standing rule beside each of them.
+ const ruleT = asBar(kbRuleThreshold.value, 0.7);
// The checkpoint bar, and the `|| default` guard matters most here of all:
// this is the only number that can STOP a call, so a fallback of 0 would
// hold the first command of every session behind whatever ranked first.
@@ -409,10 +399,8 @@ async function saveKbInject() {
kbGateNoteOverlapFloor.value = String(gateOverlap);
kbPlanMatchThreshold.value = String(planT);
kbWritePathThreshold.value = String(wpT);
- kbRuleHintThreshold.value = String(rhT);
+ kbRuleThreshold.value = String(ruleT);
kbCheckpointThreshold.value = String(cpT);
- kbToolRuleThreshold.value = String(trT);
- kbPromptRuleThreshold.value = String(prT);
kbReplyRuleThreshold.value = String(ryT);
savingKbInject.value = true;
kbInjectSaved.value = false;
@@ -426,20 +414,15 @@ async function saveKbInject() {
// measurements that split them.
kb_writepath_enabled: kbWritePathEnabled.value ? 'true' : 'false',
kb_writepath_threshold: String(wpT),
- // A THIRD corpus with a third bar — see RULEHINT_DEFAULT_THRESHOLD
- // in services/plugin_context.py for why rules cannot share the
- // code threshold any more than code could share the prose one.
- kb_rulehint_threshold: String(rhT),
+ // A third corpus with a third bar — see RULE_FLOOR_DEFAULT in
+ // services/retrieval_pipeline.py for why every rule hint shares it.
+ kb_rule_threshold: String(ruleT),
// The one bar that stops a call rather than annotating it. Its own key,
- // never derived from the two rule bars above: it answers a different
+ // never derived from the rule bar above: it answers a different
// question of the same scores — not "is this worth showing" but "is the
// corpus confident enough to be read first" — and a number that moves
// when another moves is a number nobody can reason about.
kb_checkpoint_threshold: String(cpT),
- // A FOURTH and FIFTH bar, and they are separate keys on purpose: one
- // number cannot serve arms whose queries are different shapes. Moving one must not move the others.
- kb_toolrule_threshold: String(trT),
- kb_promptrule_threshold: String(prT),
// The reply backstop's own bar: it HOLDS a reply, so like the
// checkpoint it is never derived from a hint bar.
kb_replyrule_threshold: String(ryT),
@@ -864,14 +847,8 @@ onMounted(async () => {
if (allSettings.kb_checkpoint_threshold !== undefined) {
kbCheckpointThreshold.value = allSettings.kb_checkpoint_threshold;
}
- if (allSettings.kb_rulehint_threshold !== undefined) {
- kbRuleHintThreshold.value = allSettings.kb_rulehint_threshold;
- }
- if (allSettings.kb_toolrule_threshold !== undefined) {
- kbToolRuleThreshold.value = allSettings.kb_toolrule_threshold;
- }
- if (allSettings.kb_promptrule_threshold !== undefined) {
- kbPromptRuleThreshold.value = allSettings.kb_promptrule_threshold;
+ if (allSettings.kb_rule_threshold !== undefined) {
+ kbRuleThreshold.value = allSettings.kb_rule_threshold;
}
if (allSettings.kb_replyrule_threshold !== undefined) {
kbReplyRuleThreshold.value = allSettings.kb_replyrule_threshold;
@@ -1772,10 +1749,10 @@ async function deleteUser(userId: number) {
How many snippets and issues one edit may be offered (1–10). Reach for this rather than the threshold when the hint feels long: lowering it keeps the best matches and drops the tail, while raising the bar above throws away good matches along with weak ones.
-
+
- The same hint can mention a standing rule whose trigger resembles the
- code being written. Every rule is eligible — nothing is
- preloaded any more, so this is the only way a rule reaches a write.
- Stricter than the threshold above, because there are far fewer rules
- than snippets: with a small set something always ranks first, so the
- bar has to carry more of the judgement. If rules arrive unread, or one
- you needed never showed up, that is Claude's to notice and correct
- from the telemetry — and the budget below is usually the better lever.
+ The bar for a standing rule or preference to be offered at all,
+ whether against the code being written, a command about to run, or
+ what you just said. Every rule is eligible — nothing is
+ preloaded, so this is how a rule reaches the work. One bar serves all
+ three: they search the same rules, and how many each may show is set
+ by its own budget below, which is usually the better lever. If rules
+ arrive unread, or one you needed never showed up, that is Claude's to
+ notice and correct from the telemetry.
@@ -1834,26 +1811,6 @@ async function deleteUser(userId: number) {
only — file edits are never held.
-
-
-
-
- The bar for a rule surfacing before a command runs, where the
- query is the command text rather than code. Lower than the one above
- on purpose: a shell command is short, so it scores lower for the same
- relevance — at a shared bar this arm spoke on 2% of calls against the
- write path's 37%.
-
-
How many standing rules one command may be shown (1–10). This is the busiest arm there is — it fires before every command — so its budget is the one most worth keeping small.
-
-
-
-
- The bar for a rule or preference surfacing against what you just
- said, before anything is done. It is the only moment that reaches
- a rule about how to answer rather than how to act, so it runs once a
- turn rather than once a tool call. A third query shape — your prose,
- not a command or a file — which is why it carries its own number.
-
-
str:
# The operator's message is the only query that exists before one is composed,
# and this arm is what runs against it.
PROMPTRULE_THRESHOLD_KEY = SURFACES["prompt_rule"].floor_key
-# INHERITED FROM THE ACT ARMS. 0.72 is tuned against
-# code and shell commands. An operator's prose is a different query shape
-# against the same documents, and nothing yet says the two distributions line
-# up — triggers are written in the vocabulary of the MOMENT, which for most
-# rules is act vocabulary, so prose may well score lower across the board.
-#
-# Starting at the act arms' number anyway is deliberate: it is the only value
-# with evidence behind it, and guessing lower would put an unmeasured bar in
-# front of a corpus that binds. Every call is logged under `prompt_rule`, so
-# real traffic settles it — read `near_miss_samples` before moving this, not
-# the percentile alone.
+# THE SAME BAR AS THE ACT ARMS. An operator's prose is a different query shape
+# against the same documents, and triggers are written in the vocabulary of
+# the moment, which for most rules is act vocabulary. Every call is logged
+# under `prompt_rule`, so read `near_miss_samples` before moving the shared
+# bar, not the percentile alone.
PROMPTRULE_DEFAULT_THRESHOLD = SURFACES["prompt_rule"].floor_default
# ANCHORED ON THIS HOOK'S BUDGET rather than the act arms'. This arm fires
@@ -1171,9 +1128,8 @@ async def get_writepath_config(user_id: int) -> dict:
independent pairs, resolved from the registry.
Each floor's reason is recorded where its default is: WRITEPATH (code
- embeddings sit on a much higher similarity floor than prose), RULEHINT (a
- third corpus again), TOOLRULE (a shell command is a different query shape
- from a code payload and scores lower for the same relevance).
+ embeddings sit on a much higher similarity floor than prose) and RULEHINT
+ (a third corpus again, whose one bar both rule arms read).
THE BUDGET IS SPLIT TOO. "How many titles at once" does not mean the same
thing on both surfaces: this arm fires before every Write and Edit while
diff --git a/src/grimoire/services/retrieval_pipeline.py b/src/grimoire/services/retrieval_pipeline.py
index 766f4b10..d37da308 100644
--- a/src/grimoire/services/retrieval_pipeline.py
+++ b/src/grimoire/services/retrieval_pipeline.py
@@ -460,6 +460,20 @@ class RuleArm:
declared: Declared | None = None
+# ONE BAR FOR EVERY RULE ARM THAT ANNOTATES. The prompt, command and write
+# arms search the same corpus, and what limits what they show is the budget
+# and the band, not the floor. The floor only answers "is this worth ranking
+# at all", and that question has one answer per corpus, not one per query
+# shape — so the three share one settings key, and moving it moves all three.
+# `set_dial` records the move on every surface that shares the key.
+#
+# Set low on purpose: a hit under it is never shown, so a bar set too high
+# costs a rule that binds, while one set a little low costs a line the band
+# usually drops. The reply backstop keeps its own number, because there the
+# floor is a stop bar and not a hint bar.
+RULE_FLOOR_KEY = "kb_rule_threshold"
+RULE_FLOOR_DEFAULT = 0.70
+
# The arms differ on purpose where these flags differ. Whether the prompt arm
# should band, and whether the act arms should reserve a preference, are open
# questions to settle on telemetry, not by symmetry.
@@ -468,8 +482,8 @@ PROMPT_RULE = RuleArm(
preference_slot=True,
tuning=Surface(
name="prompt_rule",
- floor_key="kb_promptrule_threshold",
- floor_default=0.72,
+ floor_key=RULE_FLOOR_KEY,
+ floor_default=RULE_FLOOR_DEFAULT,
budget_key="kb_promptrule_top_k",
budget_default=3,
asks="the operator's message, against rule triggers",
@@ -483,8 +497,8 @@ PRE_TOOL_RULE = RuleArm(
preference_slot=False,
tuning=Surface(
name="pre_tool_rule",
- floor_key="kb_toolrule_threshold",
- floor_default=0.68,
+ floor_key=RULE_FLOOR_KEY,
+ floor_default=RULE_FLOOR_DEFAULT,
budget_key="kb_toolrule_top_k",
budget_default=5,
asks="the command about to run, against rule triggers",
@@ -498,8 +512,8 @@ WRITE_PATH_RULE = RuleArm(
preference_slot=False,
tuning=Surface(
name="write_path_rule",
- floor_key="kb_rulehint_threshold",
- floor_default=0.72,
+ floor_key=RULE_FLOOR_KEY,
+ floor_default=RULE_FLOOR_DEFAULT,
budget_key="kb_rulehint_top_k",
budget_default=5,
asks="the code being written, against rule triggers",
diff --git a/src/grimoire/services/retrieval_surfaces.py b/src/grimoire/services/retrieval_surfaces.py
index 64b0f357..d0865bc6 100644
--- a/src/grimoire/services/retrieval_surfaces.py
+++ b/src/grimoire/services/retrieval_surfaces.py
@@ -110,6 +110,9 @@ def dial_for_key(key: str) -> tuple[str, str] | None:
Derived rather than listed so a seventh surface is covered the moment it is
added here, which is the only way this stays true.
+
+ A floor key several surfaces share resolves to the first of them;
+ `set_dial` records the move on every surface in `sharing_floor`.
"""
for surface in SURFACES.values():
if key == surface.floor_key:
@@ -119,6 +122,16 @@ def dial_for_key(key: str) -> tuple[str, str] | None:
return None
+def sharing_floor(name: str) -> list[str]:
+ """Every surface that reads the same floor key as `name`, itself included.
+
+ The rule arms share one bar, so moving one surface's floor moves its
+ siblings, and each of them needs the event that says so.
+ """
+ key = get_surface(name).floor_key
+ return [n for n, s in SURFACES.items() if s.floor_key == key]
+
+
async def floor_for(user_id: int, name: str) -> float:
"""This install's current floor for a surface, clamped to [0, 1]."""
s = get_surface(name)
diff --git a/src/grimoire/services/retrieval_tuning.py b/src/grimoire/services/retrieval_tuning.py
index ba8d3f7c..1ee25c0b 100644
--- a/src/grimoire/services/retrieval_tuning.py
+++ b/src/grimoire/services/retrieval_tuning.py
@@ -44,6 +44,7 @@ from grimoire.services.retrieval_surfaces import (
budget_for,
floor_for,
get_surface,
+ sharing_floor,
surface_names,
)
from grimoire.services.settings import set_setting
@@ -257,6 +258,10 @@ async def set_dial(
"reason": text, "actor": actor, "unchanged": True,
}
+ # A shared floor moves every surface that reads it, and each one's history
+ # has to show the move, or its telemetry changes with no event to explain it.
+ moved = sharing_floor(surface) if dial == "floor" else [surface]
+
await set_setting(user_id, key, stored)
async with async_session() as session:
# Stamped with the space this number was chosen in. Read at
@@ -264,12 +269,13 @@ async def set_dial(
# the caller recording it are the same call, so there is no window in
# which they could disagree.
stamp = calibration_stamp()
- session.add(RetrievalTuningEvent(
- user_id=user_id, surface=surface, dial=dial,
- old_value=old, new_value=applied, actor=actor, reason=text,
- embedding_model=stamp["embedding_model"],
- shape_version=stamp["shape_version"],
- ))
+ for name in moved:
+ session.add(RetrievalTuningEvent(
+ user_id=user_id, surface=name, dial=dial,
+ old_value=old, new_value=applied, actor=actor, reason=text,
+ embedding_model=stamp["embedding_model"],
+ shape_version=stamp["shape_version"],
+ ))
await session.commit()
return {
@@ -286,6 +292,9 @@ async def set_dial(
# Always present, both ways round: a caller that has to test for the
# key's absence to learn the answer will eventually forget to.
"unchanged": False,
+ # The other surfaces this move carried with it: a shared floor is one
+ # number, and a caller tuning one rule arm has tuned them all.
+ "moved_with": [n for n in moved if n != surface],
}
diff --git a/tests/test_retrieval_surfaces.py b/tests/test_retrieval_surfaces.py
index 8cf909a6..81b96c06 100644
--- a/tests/test_retrieval_surfaces.py
+++ b/tests/test_retrieval_surfaces.py
@@ -71,13 +71,29 @@ def test_every_surface_name_is_a_real_telemetry_source():
)
-def test_no_two_surfaces_share_a_settings_key():
- """One dial, one label."""
- floors = [s.floor_key for s in rs.SURFACES.values()]
+def test_a_shared_settings_key_is_one_bar_with_one_default():
+ """One dial, one label. Surfaces may share a floor key, as the rule arms
+ do, but then they must ship the same default: otherwise the one number
+ means something different on each surface until somebody sets it."""
+ by_key: dict[str, set[float]] = {}
+ for s in rs.SURFACES.values():
+ by_key.setdefault(s.floor_key, set()).add(s.floor_default)
+ split = {k: v for k, v in by_key.items() if len(v) > 1}
+ assert not split, f"a shared floor key ships two defaults: {split}"
+
budgets = [s.budget_key for s in rs.SURFACES.values()]
- assert len(set(floors)) == len(floors), f"duplicate floor key in {floors}"
assert len(set(budgets)) == len(budgets), f"duplicate budget key in {budgets}"
- assert not (set(floors) & set(budgets)), "a floor key doubles as a budget key"
+ assert not (set(by_key) & set(budgets)), "a floor key doubles as a budget key"
+
+
+def test_the_rule_arms_that_annotate_share_one_bar_and_the_reply_stop_does_not():
+ from grimoire.services.retrieval_pipeline import RULE_FLOOR_KEY
+
+ assert sorted(rs.sharing_floor("prompt_rule")) == [
+ "pre_tool_rule", "prompt_rule", "write_path_rule",
+ ]
+ assert rs.SURFACES["prompt_rule"].floor_key == RULE_FLOOR_KEY
+ assert rs.sharing_floor("reply_rule") == ["reply_rule"]
def test_every_surface_says_what_it_asks_over_what_and_how_often():
diff --git a/tests/test_retrieval_tuning.py b/tests/test_retrieval_tuning.py
index cc4eb481..dca92b96 100644
--- a/tests/test_retrieval_tuning.py
+++ b/tests/test_retrieval_tuning.py
@@ -86,9 +86,9 @@ async def test_a_good_change_writes_the_setting_and_the_event():
setter.assert_awaited_once()
_uid, key, stored = setter.await_args.args
- assert key == "kb_promptrule_threshold" and stored == "0.66"
+ assert key == "kb_rule_threshold" and stored == "0.66"
- event = session.add.call_args.args[0]
+ event = session.add.call_args_list[0].args[0]
assert event.surface == "prompt_rule" and event.dial == "floor"
# The before-value is what makes the history answerable.
assert event.old_value == 0.72 and event.new_value == 0.66
@@ -97,6 +97,37 @@ async def test_a_good_change_writes_the_setting_and_the_event():
assert out["clamped"] is False
+@pytest.mark.asyncio
+async def test_a_shared_floor_records_the_move_on_every_surface_that_reads_it():
+ """One key, one write, an event per surface. A rule arm whose bar moved
+ with no event of its own would show a telemetry shift nothing explains."""
+ session, ctx = _patches(floor=0.70)
+ with ctx[0], patch.object(rt, "set_setting", AsyncMock()) as setter, \
+ ctx[2], ctx[3]:
+ out = await rt.set_dial(
+ 1, "pre_tool_rule", "floor", 0.74,
+ reason="read the 10 highest declines: every one was a real miss",
+ )
+
+ setter.assert_awaited_once()
+ recorded = {c.args[0].surface for c in session.add.call_args_list}
+ assert recorded == {"pre_tool_rule", "prompt_rule", "write_path_rule"}
+ assert sorted(out["moved_with"]) == ["prompt_rule", "write_path_rule"]
+
+
+@pytest.mark.asyncio
+async def test_a_budget_moves_only_its_own_surface():
+ session, ctx = _patches(budget=3)
+ with ctx[0], patch.object(rt, "set_setting", AsyncMock()), ctx[2], ctx[3]:
+ out = await rt.set_dial(
+ 1, "prompt_rule", "budget", 4,
+ reason="on-point hits sit at ranks 4 and 5 as often as rank 1",
+ )
+
+ assert [c.args[0].surface for c in session.add.call_args_list] == ["prompt_rule"]
+ assert out["moved_with"] == []
+
+
@pytest.mark.asyncio
@pytest.mark.parametrize("dial, sent, applied", [
("floor", 1.4, 1.0),
diff --git a/tests/test_routes_retrieval_tuning.py b/tests/test_routes_retrieval_tuning.py
index 5a0b8836..8f2c8576 100644
--- a/tests/test_routes_retrieval_tuning.py
+++ b/tests/test_routes_retrieval_tuning.py
@@ -76,10 +76,15 @@ def test_the_route_does_not_take_the_actor_from_the_caller():
def test_every_registry_key_is_recognised_as_a_dial():
"""Derived from the registry, both directions. A seventh surface added
without a row here is a floor that can be written silently again."""
- from grimoire.services.retrieval_surfaces import SURFACES, dial_for_key
+ from grimoire.services.retrieval_surfaces import (
+ SURFACES, dial_for_key, sharing_floor,
+ )
for name, surface in SURFACES.items():
- assert dial_for_key(surface.floor_key) == (name, "floor")
+ # A shared floor resolves to one of the surfaces that read it, and
+ # `set_dial` records the move on all of them.
+ owner, dial = dial_for_key(surface.floor_key)
+ assert dial == "floor" and name in sharing_floor(owner)
assert dial_for_key(surface.budget_key) == (name, "budget")
diff --git a/tests/test_settings_defaults_agree.py b/tests/test_settings_defaults_agree.py
index 33109bd6..ecb2298f 100644
--- a/tests/test_settings_defaults_agree.py
+++ b/tests/test_settings_defaults_agree.py
@@ -41,7 +41,7 @@ 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 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
@@ -49,9 +49,10 @@ _VUE = ROOT / "frontend" / "src" / "views" / "SettingsView.vue"
_SURFACE_PAIRS = (
("auto_inject", "kbInjectThreshold"),
("write_path", "kbWritePathThreshold"),
- ("write_path_rule", "kbRuleHintThreshold"),
- ("pre_tool_rule", "kbToolRuleThreshold"),
- ("prompt_rule", "kbPromptRuleThreshold"),
+ # 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"),
)
diff --git a/tests/test_write_path_trigger.py b/tests/test_write_path_trigger.py
index e5b06dd7..3f57557d 100644
--- a/tests/test_write_path_trigger.py
+++ b/tests/test_write_path_trigger.py
@@ -485,13 +485,13 @@ async def test_writepath_threshold_is_operator_tunable_and_clamped():
@pytest.mark.asyncio
-async def test_the_rule_arm_has_its_own_tunable_bar():
+async def test_the_rule_arms_have_their_own_tunable_bar():
"""Settable and clamped again, for the THIRD corpus.
Separate from the code threshold above and separately settable, because the
- two are measured against different things: 0.68 was derived from code
- against note PROSE, and rules are short imperative technical
- English — a more homogeneous corpus whose noise floor sits higher.
+ two are measured against different things: the code bar against note
+ PROSE, the rule bar against short imperative technical English — a more
+ homogeneous corpus whose noise floor sits higher.
"""
from grimoire.services import plugin_context as pc
@@ -524,33 +524,28 @@ async def test_the_two_write_path_bars_are_independent():
@pytest.mark.asyncio
-async def test_the_two_act_arms_read_independent_rule_bars():
- """The command arm's bar moves without the write path's.
+async def test_both_act_arms_read_the_one_rule_bar():
+ """One stored value moves the command arm and the write arm together.
- The two act arms behave like different subsystems at the same number,
- because a code payload is long and rich where a shell command is short. A config assembler that
- reads one key into both fields would silently undo that, and the symptom
- would be invisible: both arms would simply agree again.
+ They search the same rules, and what limits each is its budget and band.
+ A config assembler that read a second key into either field would split
+ the bar again with nothing in the settings to show it.
"""
from grimoire.services import plugin_context as pc
- stored = {pc.RULEHINT_THRESHOLD_KEY: "0.75", pc.TOOLRULE_THRESHOLD_KEY: "0.61"}
+ assert pc.TOOLRULE_THRESHOLD_KEY == pc.RULEHINT_THRESHOLD_KEY
+ stored = {pc.RULEHINT_THRESHOLD_KEY: "0.61"}
with _stored_settings(stored):
cfg = await pc.get_writepath_config(1)
- assert cfg["rule_threshold"] == 0.75
+ assert cfg["rule_threshold"] == 0.61
assert cfg["tool_rule_threshold"] == 0.61
@pytest.mark.asyncio
-async def test_a_garbage_command_bar_falls_back_to_its_own_default():
- """Not to 0.0, and not to the write path's default.
-
- Falling back to 0.0 would attach a rule to every Bash call in the session;
- falling back to the sibling's default would quietly re-merge the two bars
- that are kept separate, which is the harder failure to see because the arm
- keeps working.
- """
+async def test_a_garbage_rule_bar_falls_back_to_its_default():
+ """Not to 0.0, which would attach a rule to every Bash call and every
+ write in the session."""
from grimoire.services import plugin_context as pc
stored = {pc.TOOLRULE_THRESHOLD_KEY: "banana"}
@@ -558,20 +553,7 @@ async def test_a_garbage_command_bar_falls_back_to_its_own_default():
cfg = await pc.get_writepath_config(1)
assert cfg["tool_rule_threshold"] == pc.TOOLRULE_DEFAULT_THRESHOLD
-
-
-def test_the_command_bar_defaults_below_the_write_path_bar():
- """A DIRECTION check, like its sibling above, and for the same reason: the
- value is measured against one corpus, the relationship is not.
-
- A shell command carries less text than a code payload and therefore scores
- lower for the same relevance; at the write path's bar, consequential
- commands retrieve nothing. Tuning either value stays free; inverting the
- relationship would mute the command arm.
- """
- from grimoire.services import plugin_context as pc
-
- assert pc.TOOLRULE_DEFAULT_THRESHOLD < pc.RULEHINT_DEFAULT_THRESHOLD
+ assert cfg["rule_threshold"] == pc.RULEHINT_DEFAULT_THRESHOLD
def test_the_rule_bar_defaults_above_the_code_bar():