docs(6209): forge, dedup, tuning, registry, reply shapes and discipline, checkpoint, lesson kind tests
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m24s
CI & Build / Python tests (push) Successful in 2m13s
CI & Build / Build & push image (push) Successful in 13s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m24s
CI & Build / Python tests (push) Successful in 2m13s
CI & Build / Build & push image (push) Successful in 13s
Eight more test files state their properties without issue, milestone, rule and decision numbers. The tuning and suspension notes describe the symptom a shipped default or a late ledger produces instead of one install's dated readings; the reply-discipline and checkpoint docstrings state the failure they prevent instead of the session that measured it. test_empty_selector_is_the_rule_115_baseline is renamed for what it checks: the no-forge baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
"""The `lesson` kind — its trigger, its title, and its place in the vocabulary.
|
||||
|
||||
WHY THIS EXISTS (milestone 385 step 2, #3729)
|
||||
WHY THIS EXISTS
|
||||
|
||||
A lesson is a note that must be findable by WHEN IT APPLIES rather than by what
|
||||
it is about. That is a document-shape fact: the trigger has to reach the
|
||||
embedded text, and decision #4157 put it in `notes.data` with a mirror in the
|
||||
title and the head of the body — the shape snippets already use.
|
||||
embedded text, so it lives in `notes.data` with a mirror in the title and the
|
||||
head of the body — the shape snippets use.
|
||||
|
||||
THE ONE THAT MATTERS MOST
|
||||
|
||||
`test_one_join_builds_every_trigger_title`. Three kinds now rank on a
|
||||
`{subject} — {trigger}` title: rules, snippets and lessons. That join had three
|
||||
implementations before this step and would have had four; #3207 records what
|
||||
that costs. The guard is behavioural rather than `assert a is b`, because the
|
||||
`{subject} — {trigger}` title: rules, snippets and lessons, through ONE join —
|
||||
a copy per kind is a fix that has to be found in three places. The guard is behavioural rather than `assert a is b`, because the
|
||||
three are reached through different public names and a test that compared
|
||||
identities would pass on a re-implementation that merely re-exported.
|
||||
|
||||
@@ -75,7 +74,7 @@ def test_one_join_builds_every_trigger_title():
|
||||
expected = f"{subject} — {trigger}"
|
||||
|
||||
assert trigger_title(subject, trigger) == expected
|
||||
# The two note kinds reach it through the EMBEDDED title (milestone 427),
|
||||
# The two note kinds reach it through the EMBEDDED title,
|
||||
# from their own mirror key — never a hand-rolled join.
|
||||
from scribe.services.embeddings import document_title
|
||||
assert document_title(subject, "lesson", {"when_to_apply": trigger}) == expected
|
||||
@@ -95,7 +94,7 @@ def test_the_kind_is_in_the_browse_vocabulary():
|
||||
|
||||
Asserted through the public table rather than a literal, because the door's
|
||||
validation, the counts and both dialects of the type filter are all
|
||||
generated from it (#3161) — so this is the one place that decides.
|
||||
generated from it — so this is the one place that decides.
|
||||
"""
|
||||
assert lessons_svc.LESSON_NOTE_TYPE in knowledge_svc.FACET_TYPES
|
||||
assert lessons_svc.LESSON_NOTE_TYPE in knowledge_svc.NON_TASK_FACETS
|
||||
@@ -104,7 +103,7 @@ def test_the_kind_is_in_the_browse_vocabulary():
|
||||
def test_the_global_kinds_list_names_the_lesson_and_nothing_else():
|
||||
"""`embeddings.GLOBAL_NOTE_TYPES` spells "lesson" as a literal because
|
||||
`services.lessons` imports `trigger_title` from that module, so importing
|
||||
it back would close a cycle (milestone 385 step 3). The copy is pinned
|
||||
it back would close a cycle. The copy is pinned
|
||||
here instead — the one thing a literal costs is that it can drift, and
|
||||
this is what stops it.
|
||||
"""
|
||||
@@ -114,7 +113,7 @@ def test_the_global_kinds_list_names_the_lesson_and_nothing_else():
|
||||
|
||||
|
||||
def test_a_lesson_is_not_a_task_on_either_arm_of_the_filter():
|
||||
"""The cell left empty on purpose (#3163).
|
||||
"""The cell left empty on purpose.
|
||||
|
||||
`is_task` IS `status is not None`, so a lesson that acquired a status would
|
||||
become a task and start appearing in open-work listings — the one way
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"""A rule put IN FRONT of an act, not beside its result (#4214, milestone 419).
|
||||
"""A rule put IN FRONT of an act, not beside its result.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
Every other rule surface in this plugin returns `additionalContext`, which
|
||||
Claude Code delivers alongside the tool RESULT. By the time the line is read
|
||||
the call is written, so the rule reads as commentary on a decision already
|
||||
made. Milestone 419 measured the cost over one session: seven misses, three
|
||||
caught by the operator and none by this system, five of them the same move —
|
||||
acting on the thing in hand without reading the contract around it.
|
||||
made — and the common miss is acting on the thing in hand without reading
|
||||
the contract around it.
|
||||
|
||||
A checkpoint spends the same retrieval differently. The hook returns a `deny`,
|
||||
the act does not run, and the rule's own text can be read before the call
|
||||
@@ -22,11 +21,10 @@ itself (a tuning value, and a test asserting 0.80 would fail on every retune
|
||||
while proving nothing) or the wording of the reason (prose, and it will be
|
||||
rewritten). The cases below express scores relative to the constant.
|
||||
|
||||
THE ONE BOUNDARY THAT IS A RECORDED DECISION, NOT A DESIGN CHOICE. Only the
|
||||
THE ONE BOUNDARY THAT IS A STANDING DECISION, NOT A DESIGN CHOICE. Only the
|
||||
ACTION arm can hold. `scribe_prior_art.sh` carries a tested property that it
|
||||
never returns a permissionDecision — the operator's decision that a recall aid
|
||||
may not stand in the way of a write — and this milestone does not get to
|
||||
quietly overturn it. The write-path arm computes the same block and returns
|
||||
never returns a permissionDecision — a recall aid may not stand in the way of
|
||||
a write — and a checkpoint does not get to quietly overturn that. The write-path arm computes the same block and returns
|
||||
it, so the decision can be revisited with evidence; the hook ignores it. The
|
||||
last test here asserts that boundary holds, because it is exactly the kind of
|
||||
property that erodes when someone extends the feature later.
|
||||
@@ -101,7 +99,7 @@ def test_a_preference_never_holds_an_act():
|
||||
|
||||
def test_a_rule_the_session_already_opened_never_holds_an_act():
|
||||
"""`held` is observable — a PostToolUse hook watches for the `get_rule`
|
||||
call (#4100) — so this is a recorded event, not a model's self-report
|
||||
call — so this is a recorded event, not a model's self-report
|
||||
about its own context. A session that read the rule has already had the
|
||||
thing the checkpoint exists to produce, and holding it again would punish
|
||||
the behaviour being asked for."""
|
||||
@@ -167,7 +165,7 @@ def test_a_rule_with_no_trigger_renders_without_an_empty_bracket():
|
||||
def test_every_held_act_carries_a_rendered_reason():
|
||||
"""Rendered at the one call site inside `checkpoint_for`, never by each
|
||||
arm: two arms that each remember to render it are two arms that can stop
|
||||
agreeing on what a stop says, which is #3497's history for this pair."""
|
||||
agreeing on what a stop says."""
|
||||
assert hold([hit(0.99, 1)])["reason"].strip()
|
||||
|
||||
|
||||
@@ -253,8 +251,8 @@ def test_only_the_action_hook_can_hold_an_act():
|
||||
than left to memory.
|
||||
|
||||
`scribe_prior_art.sh` runs before every Write and Edit and has never been
|
||||
able to stop one. That is the operator's decision — a recall aid may not
|
||||
stand in the way of the work — and `test_hook_never_returns_a_permission_
|
||||
able to stop one. That is a standing decision — a recall aid may not stand
|
||||
in the way of the work — and `test_hook_never_returns_a_permission_
|
||||
decision` in test_write_path_trigger.py holds the other half of it.
|
||||
|
||||
This milestone has a live argument for extending the checkpoint to writes:
|
||||
@@ -273,7 +271,7 @@ def test_only_the_action_hook_can_hold_an_act():
|
||||
|
||||
|
||||
def test_the_action_hook_caps_every_hold_it_emits():
|
||||
"""Structural, and able to fail (rule 167): the deny and the ledger call
|
||||
"""Structural, and able to fail: the deny and the ledger call
|
||||
must appear together. A deny emitted outside the guard is a session that
|
||||
can be held by the same rule on every command, which is the one outcome
|
||||
both guards exist to prevent."""
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
"""A reply's sections are chosen, not filled (#4153, milestone 409 step 7).
|
||||
"""A reply's sections are chosen, not filled.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
Step 6 measured the scaffold on live sessions and found the two halves
|
||||
disagreeing: **adherence passed and the read test failed.** Completion replies
|
||||
carried every section the table asks for — placement, what changed, what needs
|
||||
the operator, what comes next — and the operator still could not read them.
|
||||
|
||||
The cause was in the skill rather than in compliance with it. It said to pick a
|
||||
kind of reply "then fill its sections… keep them even when one is short", which
|
||||
is an instruction to complete a form. Nothing anywhere set a ceiling, and a
|
||||
section with a heading and nothing to say gets filled rather than dropped. So a
|
||||
faithful reply and an unreadable one were the same reply.
|
||||
A reply can carry every section the table asks for — placement, what changed,
|
||||
what needs the operator, what comes next — and still be unreadable: adherence
|
||||
passes and the read test fails. An instruction to pick a kind of reply "then
|
||||
fill its sections" is an instruction to complete a form, and with no ceiling a
|
||||
section with a heading and nothing to say gets filled rather than dropped. A
|
||||
faithful reply and an unreadable one become the same reply.
|
||||
|
||||
WHAT IS PINNED
|
||||
|
||||
@@ -25,18 +21,18 @@ unchanged and are not this file's subject:
|
||||
3. "Needs you" takes BOTH tests: theirs to decide, and blocking.
|
||||
4. A decision already made is acted on rather than re-argued.
|
||||
|
||||
RULE 167, AND WHY THERE IS NO ABSENCE CHECK HERE
|
||||
WHY THERE IS NO ABSENCE CHECK HERE
|
||||
|
||||
The obvious guard — assert the skill no longer tells anyone to "fill" a
|
||||
section — is the trap snippet #3352 names. This skill legitimately discusses
|
||||
The obvious guard — assert the skill never tells anyone to "fill" a section —
|
||||
is a trap. This skill legitimately discusses
|
||||
filling in order to warn against it ("not a form to complete", "a section
|
||||
filled because it was in the table"), so an absence check would fail on the
|
||||
corrected text: a false alarm about the very thing it protects.
|
||||
|
||||
So every assertion here is a PRESENCE check, and one is POSITIONAL —
|
||||
`test_the_needs_you_test_sits_with_the_needs_you_section` pins where the test
|
||||
lives, not merely that the words occur somewhere in the file. All four were
|
||||
falsified against the pre-#4153 text before being committed.
|
||||
lives, not merely that the words occur somewhere in the file. Each fails
|
||||
against a skill that says to fill its sections.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -111,7 +107,7 @@ def test_the_needs_you_test_sits_with_the_needs_you_section():
|
||||
def test_a_settled_decision_is_acted_on_rather_than_re_argued():
|
||||
"""Re-opening a decision reads as self-contradiction, not as diligence.
|
||||
|
||||
Phrased as a practice rather than a prohibition (rule 165): the instruction
|
||||
Phrased as a practice rather than a prohibition: the instruction
|
||||
is what to DO with a decision, with the failure named after it.
|
||||
"""
|
||||
text = _flat()
|
||||
@@ -126,14 +122,13 @@ def test_the_scaffold_itself_is_untouched():
|
||||
"""This step changed the discipline AROUND the sections, not the sections.
|
||||
|
||||
If a future edit deletes a category while tightening the prose, that is a
|
||||
change to #4009's subject and should not ride along silently here.
|
||||
change to the scaffold itself and should not ride along silently here.
|
||||
"""
|
||||
text = _flat()
|
||||
for section in ("where this sits", "what now works", "how / why",
|
||||
"needs you", "next"):
|
||||
assert section in text, f"completion-report section {section!r} is gone"
|
||||
# The kinds live in the delivered shapes since milestone 500 step 4; the
|
||||
# skill keeps the reasoning and the worked completion report.
|
||||
# The kinds live in the delivered shapes; the skill keeps the reasoning and the worked completion report.
|
||||
from scribe.services import reply_shapes
|
||||
|
||||
shapes = " ".join(s.title + " " + s.text for s in reply_shapes.SHAPES.values()).lower()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The default reply shapes and the moments they ride (milestone 500 step 1).
|
||||
"""The default reply shapes and the moments they ride.
|
||||
|
||||
What this pins is what delivery will depend on: every shape rides a moment that
|
||||
exists, the core is small enough to pay for on every turn, no slice grows back
|
||||
@@ -15,7 +15,7 @@ _REAL_RECORD = reply_shapes.record_delivery
|
||||
|
||||
|
||||
def test_there_is_a_core_and_at_least_one_slice():
|
||||
"""The sweeps below are vacuous over an empty catalog (rule 167)."""
|
||||
"""The sweeps below are vacuous over an empty catalog."""
|
||||
assert reply_shapes.CORE_KEY in reply_shapes.SHAPES
|
||||
assert len(reply_shapes.SHAPES) >= 2
|
||||
|
||||
@@ -74,16 +74,16 @@ def test_every_shape_says_what_it_is_and_when_it_arrives(key):
|
||||
|
||||
|
||||
def test_the_core_carries_the_length_discipline():
|
||||
"""Milestone 409's live read: replies that had every section were still
|
||||
too long. Delivery cannot fix that; the core has to say it."""
|
||||
"""A reply that has every section can still be too long. Delivery cannot
|
||||
fix that; the core has to say it."""
|
||||
text = reply_shapes.core().text.lower()
|
||||
assert "shortest reply that carries the answer" in text
|
||||
assert "what can go" in text
|
||||
|
||||
|
||||
def test_who_decides_what_rides_every_reply_and_every_ask():
|
||||
"""The operator's line (milestone 500 step 2): the agent settles what
|
||||
only it can see, the operator decides direction. A one-liner every turn,
|
||||
"""Who decides: the agent settles what only it can see, the operator
|
||||
decides direction. A one-liner every turn,
|
||||
in full where a question is about to be handed back."""
|
||||
core = reply_shapes.core().text.lower()
|
||||
asks = reply_shapes.SHAPES["asks"].text.lower()
|
||||
@@ -130,7 +130,7 @@ def test_the_tool_is_registered_as_a_read():
|
||||
|
||||
|
||||
def test_both_doors_read_one_catalog():
|
||||
"""Rule 33 parity: the session and the Settings view cannot show
|
||||
"""Parity: the session and the Settings view cannot show
|
||||
different defaults."""
|
||||
from scribe.mcp.tools import moments as tool
|
||||
from scribe.routes import retrieval as routes
|
||||
@@ -158,7 +158,7 @@ async def test_the_route_returns_the_operators_overview_with_a_bounded_window():
|
||||
assert view.await_args.kwargs == {"days": days}
|
||||
|
||||
|
||||
# ── the operator's view (milestone 500 step 5) ──────────────────────────
|
||||
# ── the Settings view ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def test_the_overview_lists_the_preferences_mounted_on_each_shapes_moment():
|
||||
@@ -226,7 +226,7 @@ async def test_delivery_counts_tally_each_shape_by_form_and_skip_what_does_not_p
|
||||
assert "bogus" not in counts
|
||||
|
||||
|
||||
# ── delivery (milestone 500 step 3) ─────────────────────────────────────
|
||||
# ── delivery ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", list(reply_shapes.SHAPES))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""Every retrieval point is declared, derived from the CALL SITES (#3431).
|
||||
"""Every retrieval point is declared, derived from the CALL SITES.
|
||||
|
||||
WHY AST AND NOT GREP, demonstrated rather than asserted. Two of the sources in
|
||||
this system reach their recorder as `source=SOURCE` through a module-level
|
||||
constant — `wide_net` and `preference_slot` — so a grep for `source="` finds
|
||||
neither. A registry test built on that grep would pass while being blind to
|
||||
two arms, which is the narrowing #3191 warns about: a check that looks
|
||||
two arms — a check that looks
|
||||
thorough and quietly covers less than it claims.
|
||||
|
||||
So the extractor below parses each module, resolves module-level string
|
||||
@@ -35,13 +35,13 @@ from scribe.services.retrieval_surfaces import SURFACES
|
||||
SRC = Path(__file__).resolve().parents[1] / "src"
|
||||
|
||||
# The recorders, plus the one FORWARDER. `rules_payload` takes a `source` and
|
||||
# passes it to `record_rule_surfaced` on its caller's behalf (snippet #2858),
|
||||
# passes it to `record_rule_surfaced` on its caller's behalf,
|
||||
# so its callers are the real declaration site and a scan of recorders alone
|
||||
# would miss every ambient rule surfacing.
|
||||
RECORDERS = {
|
||||
"record_retrieval", "record_surfaced", "record_pulled",
|
||||
"record_rule_surfaced", "record_rule_pulled", "rules_payload",
|
||||
# The rulings arm (#4769): its two recorders, and `rulings_for_paths`,
|
||||
# The rulings arm: its two recorders, and `rulings_for_paths`,
|
||||
# which forwards its caller's `source` the way `rules_payload` does.
|
||||
"record_system_surfaced", "record_system_pulled", "rulings_for_paths",
|
||||
}
|
||||
@@ -90,7 +90,7 @@ def call_sites() -> tuple[dict[str, set[str]], list[str]]:
|
||||
|
||||
|
||||
def test_the_extractor_finds_something() -> None:
|
||||
"""The guard has to be able to fail (rule 167).
|
||||
"""The guard has to be able to fail.
|
||||
|
||||
An extractor that silently matched nothing would make every assertion
|
||||
below vacuously true — a green suite proving the opposite of what it
|
||||
@@ -130,7 +130,7 @@ def test_every_unresolved_call_site_is_declared() -> None:
|
||||
# EQUALITY, not prefix. The first spelling of this compared against a
|
||||
# prefix that could never match — the paths begin `scribe/` — so the check
|
||||
# passed by matching nothing, which is the failure mode a guard is most
|
||||
# likely to have and least likely to show (rule 167).
|
||||
# likely to have and least likely to show.
|
||||
undeclared = sorted({u for u in unresolved if u not in FAN_OUT_SITES})
|
||||
assert not undeclared, (
|
||||
"these call sites pass `source` as a value this test cannot resolve, "
|
||||
@@ -157,7 +157,7 @@ def test_every_tunable_surface_is_also_a_registered_point() -> None:
|
||||
|
||||
|
||||
def test_a_quiet_point_says_why() -> None:
|
||||
"""A justified silence must carry its justification (#2475).
|
||||
"""A justified silence must carry its justification.
|
||||
|
||||
Without the reason the reader cannot tell a decision from an oversight,
|
||||
which is the whole difference this field exists to record.
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"""Moving a floor, and the reason that has to come with it (#4102).
|
||||
"""Moving a floor, and the reason that has to come with it.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
The operator handed the dial to the model: *"the user should be able to touch it
|
||||
but the model should be the thing handling it 9 times out of 10."* Everything
|
||||
here guards the half of that sentence people skip — the operator still has to be
|
||||
able to see what was done on their behalf and disagree with it.
|
||||
The model moves these dials most of the time, and a person can too. Everything
|
||||
here guards the half of that arrangement people skip — the person still has to
|
||||
be able to see what was done on their behalf and disagree with it.
|
||||
|
||||
WHAT THIS PINS
|
||||
|
||||
@@ -20,7 +19,7 @@ WHAT THIS PINS
|
||||
question anyone asks of a surface behaving oddly.
|
||||
3. **A clamp is reported, never swallowed.** A caller that believes it set 1.4
|
||||
will read the next telemetry as evidence about a bar that was never in
|
||||
force — the same class of error as #3739, one layer up.
|
||||
force.
|
||||
4. **An unknown surface is refused before anything is written.** Settings keys
|
||||
are free-form strings, so a typo'd surface would write a key nothing reads:
|
||||
a change that reports success and alters nothing.
|
||||
@@ -167,7 +166,7 @@ async def test_an_unknown_actor_is_refused():
|
||||
|
||||
|
||||
def test_the_tool_teaches_reading_the_records_not_the_percentile():
|
||||
"""The contract an agent actually reads (rule 167).
|
||||
"""The contract an agent actually reads.
|
||||
|
||||
The failure this prevents is a model moving a bar because `near_misses.p90`
|
||||
sat close to it. That has been measured pointing the wrong way — 69 declines
|
||||
@@ -198,20 +197,19 @@ def test_every_tool_in_the_module_is_registered():
|
||||
]
|
||||
|
||||
|
||||
# ── record_release_defaults / floor_moves_since (#4225) ───────────────────
|
||||
# ── record_release_defaults / floor_moves_since ───────────────────────────
|
||||
#
|
||||
# THE HOLE THESE FILL. This table records dial turns — a person or a model
|
||||
# choosing a number. It was silent about the other way a floor moves: somebody
|
||||
# edits `floor_default` in the registry and ships it. That change is invisible
|
||||
# to every consumer of this table, and one consumer is `band_hugs_floor`, which
|
||||
# compares a band against a floor and could not notice they came from different
|
||||
# regimes.
|
||||
# choosing a number. The other way a floor moves is somebody editing
|
||||
# `floor_default` in the registry and shipping it, and without a release row
|
||||
# that change is invisible to every consumer of this table — including
|
||||
# `band_hugs_floor`, which compares a band against a floor and cannot notice
|
||||
# they came from different regimes.
|
||||
#
|
||||
# Measured on the instance that found it: `write_path_rule` went 0.68 -> 0.72
|
||||
# on 2026-09-02 as a shipped default, so a 30-day window opening 2026-08-22
|
||||
# held six days of calls made under the old bar. The readout reported the band
|
||||
# sitting "-0.0216 above" its floor — which is what a two-population comparison
|
||||
# looks like when it finally says so out loud.
|
||||
# The symptom: a window that spans a shipped default change holds calls made
|
||||
# under the old bar, and the readout reports the band a negative distance
|
||||
# "above" its floor — what a two-population comparison looks like when it
|
||||
# finally says so out loud.
|
||||
|
||||
|
||||
def _release_rows(rows):
|
||||
@@ -350,12 +348,10 @@ async def test_a_baseline_is_not_reported_as_a_floor_move():
|
||||
# WATCHING. Before an arm's first row there is nothing to move, nothing to
|
||||
# report, and no way to tell a steady floor from an unrecorded change.
|
||||
#
|
||||
# Measured the day the ledger was seeded on this instance: its earliest event
|
||||
# of any kind is 2026-09-17, its `write_path_rule` floor rows begin at
|
||||
# 2026-09-21, and a 30-day window opening 2026-08-22 reported that arm's band
|
||||
# sitting "-0.0208 above" its floor. That negative distance is the documented
|
||||
# tell of a raise inside the window — announced by the check the suspension
|
||||
# was built to stop, because the suspension had nothing to read.
|
||||
# The symptom: a window that opens before an arm's first floor row reports the
|
||||
# band a negative distance "above" its floor. That is the documented tell of a
|
||||
# raise inside the window — announced by the check the suspension exists to
|
||||
# stop, because the suspension has nothing to read.
|
||||
|
||||
|
||||
def _gap_rows(rows):
|
||||
|
||||
@@ -43,7 +43,7 @@ async def test_short_body_skips_semantic_check():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_semantic_match_when_body_substantial():
|
||||
# A note blocks only in the copy band (#4306).
|
||||
# A note blocks only in the copy band.
|
||||
hit = fake_note(id=20, title="Existing", note_type="note")
|
||||
sem = AsyncMock(return_value=[(0.99, hit)])
|
||||
with patch("scribe.services.dedup.async_session",
|
||||
@@ -60,8 +60,8 @@ async def test_semantic_match_when_body_substantial():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_near_note_is_surfaced_not_blocked():
|
||||
"""#4306: sibling notes — the next dev-log, another part of one design —
|
||||
measured 0.90–0.98, so a match there is shown for the session to judge
|
||||
"""Sibling notes — the next dev-log, another part of one design — measure
|
||||
0.90–0.98, so a match there is shown for the session to judge
|
||||
instead of refusing the write."""
|
||||
from scribe.services.dedup import _NOTE_OVERLAP_FLOOR
|
||||
|
||||
@@ -133,9 +133,9 @@ def test_a_batch_overlap_names_its_record():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gate_catches_a_duplicate_hiding_in_a_later_chunk():
|
||||
"""The capability #280 adds to the gate: a long candidate that duplicates
|
||||
an existing record in ONE SECTION is caught, where the whole-document
|
||||
query this replaces diluted exactly the section that mattered. The gate
|
||||
"""A long candidate that duplicates an existing record in ONE SECTION is
|
||||
caught; a whole-document query would dilute exactly the section that
|
||||
matters. The gate
|
||||
queries once per chunk and any chunk's hit blocks."""
|
||||
para = ("This section restates an existing decision in enough words to be "
|
||||
"a real paragraph of content for the chunker to keep. ") * 4
|
||||
@@ -202,13 +202,13 @@ def test_duplicate_response_shape():
|
||||
assert "update_task" in r["message"]
|
||||
|
||||
|
||||
# --- snippet structural identity (#2518) -------------------------------------
|
||||
# --- snippet structural identity ---------------------------------------------
|
||||
#
|
||||
# The gate used to compare a snippet's rendered DOCUMENT, which is mostly prose
|
||||
# about the code. Measured on the button corpus, that failed in both directions
|
||||
# at once: two deliberately-parallel variants were refused at 0.92, while a
|
||||
# verbatim re-record of one snippet under a different name scored below 0.90 and
|
||||
# was created. These tests pin the structural signals that replaced it.
|
||||
# A snippet's rendered DOCUMENT is mostly prose about the code, and comparing
|
||||
# it fails in both directions at once: two deliberately-parallel variants
|
||||
# refused at 0.92, while a verbatim re-record under a different name scores
|
||||
# below 0.90 and is created. These tests pin the structural signals used
|
||||
# instead.
|
||||
|
||||
|
||||
def _session_sequence(results):
|
||||
@@ -371,7 +371,7 @@ def test_duplicate_response_names_what_matched_for_structural_hits():
|
||||
assert "identical code" in r["message"]
|
||||
|
||||
|
||||
# --- the generalised report (#2547) ------------------------------------------
|
||||
# --- the generalised report -------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -404,7 +404,7 @@ def test_every_kind_has_a_suggestion_and_none_proposes_merging_notes():
|
||||
"""The suggestion is the report's point: what to DO differs by what the
|
||||
records are, and 'merge' is only ever the answer for snippets — folding two
|
||||
notes destroys what each said, which is why consolidated_at was dropped
|
||||
rather than built (#2483)."""
|
||||
rather than built."""
|
||||
from scribe.services.dedup import _KIND_SUGGESTION, _REPORT_KINDS
|
||||
|
||||
for kind in _REPORT_KINDS:
|
||||
@@ -418,7 +418,7 @@ def test_every_kind_has_a_suggestion_and_none_proposes_merging_notes():
|
||||
assert "supersedes" in _KIND_SUGGESTION["note"]
|
||||
|
||||
|
||||
# ── the plan gate (milestone 415, step 4) ─────────────────────────────────────
|
||||
# ── the plan gate ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Forge adapter contract tests (#2689) — the Gitea implementation against a
|
||||
"""Forge adapter contract tests — the Gitea implementation against a
|
||||
mocked transport, plus the configuration gate.
|
||||
|
||||
httpx.MockTransport is the fake forge: the adapter takes an injectable
|
||||
@@ -9,7 +9,7 @@ reproduce.
|
||||
|
||||
The most load-bearing tests are the OFF ones: a user with no usable connection
|
||||
must get an empty selector, because every consumer treats that as "behave as
|
||||
if the module didn't exist" (rule #115 — the baseline user has no forge).
|
||||
if the module didn't exist" (the baseline user has no forge).
|
||||
DB-backed keyring behavior (get_forges: rows, the project pin, the admin-only
|
||||
env entry) lives in tests/test_integration_forge_keyring.py.
|
||||
"""
|
||||
@@ -175,9 +175,9 @@ def test_full_values_build_a_github_adapter():
|
||||
)
|
||||
|
||||
|
||||
# --- the selector (#2778): host-keyed resolution over a keyring ---------------
|
||||
# --- the selector: host-keyed resolution over a keyring ----------------------
|
||||
|
||||
def test_empty_selector_is_the_rule_115_baseline():
|
||||
def test_empty_selector_is_the_no_forge_baseline():
|
||||
selector = ForgeSelector()
|
||||
assert not selector.configured
|
||||
assert selector.resolve(f"{BASE}/alice/widget") is None
|
||||
@@ -204,7 +204,7 @@ def test_forge_error_taxonomy_is_catchable_as_one_family():
|
||||
|
||||
def test_adapter_contract_surface():
|
||||
"""Both adapters implement exactly this surface — the second
|
||||
implementation is what proves it's a contract (#2693)."""
|
||||
implementation is what proves it's a contract."""
|
||||
from scribe.services.forge import FORGE_KINDS, GitHubForge
|
||||
|
||||
for cls in (GiteaForge, GitHubForge):
|
||||
@@ -222,7 +222,7 @@ def test_forge_routes_registered_at_their_post_2778_homes():
|
||||
"""Connections are USER settings; only the webhook secret stays admin.
|
||||
The old instance-wide /api/admin/forge endpoints must be GONE, not
|
||||
coexisting — a legacy config surface that still wrote admin settings
|
||||
would silently configure nothing (rule #22: no dual path)."""
|
||||
would silently configure nothing (one path, not two)."""
|
||||
from scribe.app import create_app
|
||||
from scribe.routes import admin as admin_routes
|
||||
|
||||
@@ -239,11 +239,10 @@ def test_forge_routes_registered_at_their_post_2778_homes():
|
||||
|
||||
|
||||
def test_settings_kv_surface_masks_the_webhook_secret():
|
||||
"""The generic /api/settings dump masked nothing — the admin endpoints'
|
||||
masking was bypassable by reading the raw KV rows (found while wiring the
|
||||
forge token; smtp_password had the same exposure). forge_token left the
|
||||
KV with #2778 (keyring rows carry it now), so it is deliberately no
|
||||
longer a secret KEY here."""
|
||||
"""The generic /api/settings dump masks secrets too — otherwise the admin
|
||||
endpoints' masking is bypassable by reading the raw KV rows. The forge
|
||||
token lives on keyring rows, not in the KV, so it is deliberately not a
|
||||
secret KEY here."""
|
||||
from scribe.routes.settings import _SECRET_KEYS, _masked
|
||||
|
||||
out = _masked({"forge_webhook_secret": "sec", "smtp_password": "pw", "theme": "dark"})
|
||||
@@ -270,7 +269,7 @@ def test_config_has_the_docker_secret_channel():
|
||||
assert hasattr(Config, attr)
|
||||
|
||||
|
||||
# --- the GitHub adapter (#2693) ----------------------------------------------
|
||||
# --- the GitHub adapter -----------------------------------------------------
|
||||
# Same contract, second implementation. Where behavior below differs from the
|
||||
# Gitea tests above, that difference IS the adapter's job: API host mapping,
|
||||
# Bearer auth, the missing last_commit_sha, the codeload redirect.
|
||||
|
||||
Reference in New Issue
Block a user