Compare commits
2
Commits
17e1931c0b
...
b3e6a27dc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e6a27dc1 | ||
|
|
0e658bf275 |
@@ -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,11 +1,11 @@
|
||||
"""The REST door for lessons — the half the web UI can actually reach (#3734).
|
||||
"""The REST door for lessons — the half the web UI can actually reach.
|
||||
|
||||
WHY THIS EXISTS AT ALL
|
||||
|
||||
Milestone 385 built the lesson kind through the MCP tools, which is the surface
|
||||
an agent uses. The Vue app speaks REST, so until this blueprint existed a lesson
|
||||
was a record a person could not create, read, edit or retire from the UI — rule
|
||||
27's "no UI, no ship" failing at the door rather than in the view.
|
||||
The MCP tools are the surface an agent uses; the Vue app speaks REST. Without
|
||||
this blueprint a lesson is a record a person cannot create, read, edit or
|
||||
retire from the UI — a feature with no UI, failing at the door rather than in
|
||||
the view.
|
||||
|
||||
WHAT THESE PIN
|
||||
|
||||
@@ -17,7 +17,7 @@ kind:
|
||||
own title would produce lessons that rank differently from the agent's, and
|
||||
the document IS what ranks.
|
||||
|
||||
- ACL (rule 78). Share-aware resolve, write as the owner — the pattern
|
||||
- ACL. Share-aware resolve, write as the owner — the pattern
|
||||
routes/snippets.py sets — so a shared editor isn't rejected by the
|
||||
owner-scoped service.
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_both_doors_share_one_serializer():
|
||||
|
||||
|
||||
def test_the_rest_door_composes_nothing_itself():
|
||||
"""Asserted on structure (rule 167). The document is what ranks, so a door
|
||||
"""Asserted on structure. The document is what ranks, so a door
|
||||
that built its own title would produce lessons that rank differently from
|
||||
the ones the agent writes."""
|
||||
from scribe.routes import lessons as routes
|
||||
@@ -124,7 +124,7 @@ def test_the_subject_is_required_too():
|
||||
assert "what is required" in src
|
||||
|
||||
|
||||
# ── ACL: rule 78's pattern, not a bare owner filter ──────────────────────────
|
||||
# ── ACL: the shared-access pattern, not a bare owner filter ──────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -139,12 +139,12 @@ def test_writes_check_permission_and_act_as_the_owner(handler):
|
||||
assert "can_write_note" in src, f"{handler} does not check write permission"
|
||||
assert "note.user_id" in src, (
|
||||
f"{handler} writes as the caller rather than as the owner, which "
|
||||
f"rejects a legitimately shared editor (rule 78)"
|
||||
f"rejects a legitimately shared editor"
|
||||
)
|
||||
|
||||
|
||||
def test_no_handler_builds_its_own_owner_filter():
|
||||
"""Rule 78's actual failure mode: a route assembling its own
|
||||
"""The access layer's actual failure mode: a route assembling its own
|
||||
`Note.user_id == uid` clause instead of going through the service and the
|
||||
access helpers. Passing `user_id=uid` INTO a service is the correct call
|
||||
and is not what this looks for."""
|
||||
@@ -238,7 +238,7 @@ def test_the_payload_reads_back_the_composed_fields():
|
||||
|
||||
|
||||
def test_the_rest_guards_can_fail():
|
||||
"""Rule 167: shown turning red once."""
|
||||
"""Shown turning red once."""
|
||||
from scribe.routes import lessons as routes
|
||||
|
||||
src = inspect.getsource(routes)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""The notes corpus on the one retrieval pipeline (milestone 456 step 4).
|
||||
"""The notes corpus on the one retrieval pipeline.
|
||||
|
||||
`run_note_arm` is driven directly here, with the search and both recorders
|
||||
stubbed through `NoteIO`: the stage order, the invariants the rule arms
|
||||
already pin (#3497's call row before any return, #3752's fresh-only cut,
|
||||
#4101's ledger-renders-not-removes), the write path's withheld menu (#3739),
|
||||
already pin (the call row before any return, the fresh-only cut, the ledger
|
||||
renders rather than removes), the write path's withheld menu,
|
||||
and the two reserved slots in their load-bearing order. The builders that
|
||||
compose these arms keep their own tests; these pin the stages they share.
|
||||
"""
|
||||
@@ -53,7 +53,7 @@ async def _run(arm, route, *, budget=3, **moment):
|
||||
return result, io, calls
|
||||
|
||||
|
||||
# ── the call row (#3497, #3752) ───────────────────────────────────────────
|
||||
# ── the call row ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -75,7 +75,7 @@ async def test_repeats_and_named_records_are_counted_not_reported():
|
||||
(row,) = _rows(io.record_retrieval, "auto_inject")
|
||||
assert [int(n.id) for _s, n in row["results"]] == [22]
|
||||
assert row["suppressed"] == 2
|
||||
# The repeat stays on the menu (#4101); the named record does not — it is
|
||||
# The repeat stays on the menu; the named record does not — it is
|
||||
# shown by its own block.
|
||||
assert [int(n.id) for _s, n in result.menu] == [11, 22]
|
||||
(surfaced,) = _rows(io.record_surfaced, "auto_inject")
|
||||
@@ -105,7 +105,7 @@ def test_every_notes_stage_logs_before_it_can_return():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_band_narrows_the_menu_after_the_row_is_written():
|
||||
"""Notes log BEFORE the band (#2085) — the row is the candidate set a
|
||||
"""Notes log BEFORE the band — the row is the candidate set a
|
||||
floor is tuned against, the surfacing rows are what the reader saw."""
|
||||
hits = [_note(1, 0.90), _note(2, 0.70)]
|
||||
result, io, _ = await _run(rp.WRITE_PATH, lambda kw: hits)
|
||||
@@ -141,7 +141,7 @@ async def test_a_recorder_that_raises_costs_only_its_row():
|
||||
assert [int(n.id) for _s, n in result.menu] == [1]
|
||||
|
||||
|
||||
# ── the write path's withheld menu (#3739) ────────────────────────────────
|
||||
# ── the write path's withheld menu ────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -223,7 +223,7 @@ async def test_a_slot_stands_down_when_its_kind_won_on_score():
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_slot_spends_itself_on_a_repeat_without_booking_it_again():
|
||||
"""A record shown in an EARLIER call is exactly what a slot may spend
|
||||
itself on (#4101) — it is rendered, but it is not a new surfacing."""
|
||||
itself on — it is rendered, but it is not a new surfacing."""
|
||||
main = [_note(1, 0.70)]
|
||||
lesson = [_note(42, 0.58, note_type=LESSON_NOTE_TYPE)]
|
||||
result, io, _ = await _run(
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
"""A logged query never carries a credential (#3925).
|
||||
"""A logged query never carries a credential.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
@@ -9,8 +9,7 @@ that exported a token therefore stored the token.
|
||||
|
||||
And storing it was not the worst of it. `near_miss_samples` is the readout the
|
||||
threshold documentation tells you to open before moving a bar, so the value
|
||||
came back OUT into an agent's context on the next tuning pass — which is
|
||||
exactly how this was found, during #3853's threshold spike.
|
||||
would come back OUT into an agent's context on the next tuning pass.
|
||||
|
||||
WHAT THIS PINS, IN BOTH DIRECTIONS, AND WHY THE SECOND HALF IS THE HARD ONE
|
||||
|
||||
@@ -21,12 +20,11 @@ A scrubber has two ways to fail and only one of them is obvious.
|
||||
because it is silent: the whole worth of a near-miss sample is reading the
|
||||
query that was actually refused, and a scrubber that chewed up ordinary
|
||||
commands would turn the one instrument for tuning a bar into unreadable
|
||||
stubs while still looking like it worked. That is the #2663 shape — a
|
||||
surface that reads fine and has quietly stopped saying anything.
|
||||
stubs while still looking like it worked — a surface that reads fine and
|
||||
has quietly stopped saying anything.
|
||||
|
||||
So the second block is not padding. Its cases are REAL queries taken from this
|
||||
install's `near_miss_samples` during #3853, and they must survive byte for
|
||||
byte. If a future pattern is added and one of them changes, the pattern is too
|
||||
So the second block is not padding. Its cases are REAL near-miss queries, and
|
||||
they must survive byte for byte. If a future pattern is added and one of them changes, the pattern is too
|
||||
greedy — tighten it rather than editing the expectation.
|
||||
|
||||
The secret cases use FABRICATED values in the real formats. Nothing here is or
|
||||
@@ -59,7 +57,7 @@ _SECRETS = [
|
||||
"MIIEowIBAAKC"),
|
||||
]
|
||||
|
||||
# Real queries, from this install's near-miss samples during #3853.
|
||||
# Real queries, from near-miss samples.
|
||||
_EVIDENCE = [
|
||||
"git push origin dev",
|
||||
'git pull --rebase origin dev 2>&1 | tail -3; echo "=== HEAD ==="; git log --oneline -2',
|
||||
@@ -114,7 +112,7 @@ def test_empty_and_missing_queries_pass_through():
|
||||
|
||||
|
||||
def test_the_write_path_scrubs_rather_than_the_read_path():
|
||||
"""The payload built for storage carries the redacted text (#3925).
|
||||
"""The payload built for storage carries the redacted text.
|
||||
|
||||
Pinned on `_build_payload` because that is the single seam every source
|
||||
reaches the column through. A per-caller scrub would be three places for
|
||||
@@ -135,21 +133,20 @@ def test_the_write_path_scrubs_rather_than_the_read_path():
|
||||
assert "git push" in payload["query"]
|
||||
|
||||
|
||||
# ── the SQL twin has its own word-boundary spelling (#3925) ─────────────
|
||||
# ── the SQL twin has its own word-boundary spelling ─────────────────────
|
||||
#
|
||||
# Migration 0099 carries an inlined copy of these patterns, deliberately: a
|
||||
# migration is a frozen record of what already ran, and importing the live
|
||||
# ones would mean it quietly did something different next year.
|
||||
#
|
||||
# Frozen is not the same as correct, and the first cut was neither. The
|
||||
# boundary was dropped in the port, so `sk-` matched inside any word
|
||||
# containing it — `<task-notification>` became `<ta[redacted:token]>` across
|
||||
# thousands of rows on the one install that ran it. And writing `\b` would not
|
||||
# have saved it: in Postgres ARE `\b` is a BACKSPACE, not a word boundary.
|
||||
# Frozen is not the same as correct. Without the boundary, `sk-` matches
|
||||
# inside any word containing it — `<task-notification>` becomes
|
||||
# `<ta[redacted:token]>`. And writing `\b` does not help: in Postgres ARE `\b`
|
||||
# is a BACKSPACE, not a word boundary.
|
||||
# `\m` (start of word) is the spelling that means what Python's `\b` means.
|
||||
#
|
||||
# So this pins the property no reader can eyeball, and it is a PRESENCE check
|
||||
# on a token that must appear rather than an absence check on prose (#3352).
|
||||
# on a token that must appear rather than an absence check on prose.
|
||||
|
||||
def test_the_migrations_patterns_anchor_to_a_word_start_the_postgres_way():
|
||||
"""`\\m`, never `\\b` — the two are unrelated in Postgres."""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Rule usage telemetry — the parts that need no database (milestone 333 step 1).
|
||||
"""Rule usage telemetry — the parts that need no database.
|
||||
|
||||
The round trip lives in `test_integration_backup_rule_usage_roundtrip.py`.
|
||||
What is here is the payload building and the zero shape: cheap, and the half
|
||||
@@ -143,10 +143,10 @@ def test_the_model_serialises_the_fields_the_ratio_needs():
|
||||
assert row["created_at"] is None
|
||||
|
||||
|
||||
# ─── the readout (milestone 333 step 5) ──────────────────────────────────────
|
||||
# Integration: a real GROUP BY over a real table. Step 1 unit-tested the WRITE
|
||||
# path and the zero shape and left the aggregate uncovered, which only became
|
||||
# load-bearing when the rule list started rendering it.
|
||||
# ─── the readout ─────────────────────────────────────────────────────────────
|
||||
# Integration: a real GROUP BY over a real table. The unit tests above cover
|
||||
# the WRITE path and the zero shape; the aggregate the rule list renders needs
|
||||
# a real database.
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@@ -212,7 +212,7 @@ async def test_usage_for_rules_on_an_empty_id_list_asks_the_database_nothing(
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_preloaded_rule_does_not_read_as_a_ranked_surfacing(_dispose_engine):
|
||||
"""The split that makes the always-on set judgeable (#3473).
|
||||
"""The split that makes the always-on set judgeable.
|
||||
|
||||
A resident rule is delivered every session by a surface that chose
|
||||
nothing. Counting those as `surfaced_count` would rank the always-on set
|
||||
@@ -262,12 +262,11 @@ async def test_a_preloaded_rule_does_not_read_as_a_ranked_surfacing(_dispose_eng
|
||||
await s.commit()
|
||||
|
||||
|
||||
# ── the outcome stream (#4212, milestone 419) ─────────────────────────────
|
||||
# ── the outcome stream ────────────────────────────────────────────────────
|
||||
#
|
||||
# What these guard is a distinction, not a payload. Before this existed, a
|
||||
# rule read and obeyed and a rule read and ignored left byte-identical
|
||||
# telemetry, so the readout could not name the failure the whole milestone
|
||||
# was opened on. The tests that matter most below are the ones asserting
|
||||
# What these guard is a distinction, not a payload. Without outcomes, a rule
|
||||
# read and obeyed and a rule read and ignored leave byte-identical telemetry,
|
||||
# so the readout cannot name the failure. The tests that matter most below are the ones asserting
|
||||
# that a REASONLESS DEPARTURE IS NEVER WRITTEN, and that an unacted rule is
|
||||
# a state in its own right rather than the absence of one.
|
||||
|
||||
@@ -301,7 +300,7 @@ def test_a_departure_with_no_reason_is_never_written(captured, reason):
|
||||
miss, so writing one would collapse the two states this table exists to
|
||||
separate — silently, in the readout, where nobody would see it happen.
|
||||
Dropped and reported, never stored: telemetry that lies is worse than
|
||||
telemetry that is absent (#2663)."""
|
||||
telemetry that is absent."""
|
||||
rule_usage.record_rule_outcome(
|
||||
user_id=7, rule_id=156, outcome=DEPARTED,
|
||||
source="mcp_rule_outcome", detail=reason or "",
|
||||
@@ -357,10 +356,9 @@ def test_a_rule_opened_and_departed_from_is_departed():
|
||||
|
||||
|
||||
def test_a_rule_opened_and_never_acted_on_is_unacted() -> None:
|
||||
"""THE STATE THAT DID NOT EXIST, and the reason for the milestone. Not
|
||||
"no data": the rule was surfaced, deliberately opened, and then left no
|
||||
trace of having mattered. Until now that was arithmetically identical to
|
||||
compliance, which is why nothing could report it."""
|
||||
"""THE UNACTED STATE. Not "no data": the rule was surfaced, deliberately
|
||||
opened, and then left no trace of having mattered. Without this state it
|
||||
is arithmetically identical to compliance, and nothing can report it."""
|
||||
assert rule_usage.outcome_state(
|
||||
_usage(surfaced_count=4, pull_count=2)
|
||||
) == rule_usage.UNACTED
|
||||
@@ -389,5 +387,5 @@ def test_a_departure_outranks_an_application():
|
||||
def test_the_state_reads_the_aggregate_the_readout_already_returns():
|
||||
"""`outcome_state` takes `usage_for_rules`' own shape, so the badge, the
|
||||
readout and any later session summary cannot disagree about what
|
||||
"followed" means — the drift #3246 found across the rules system."""
|
||||
"followed" means."""
|
||||
assert rule_usage.outcome_state(rule_usage.empty_rule_usage()) == rule_usage.UNREAD
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
"""A compaction clears every session ledger, by convention not by list (#4101).
|
||||
"""A compaction clears every session ledger, by convention not by list.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
Milestone 386 established the claim: a ledger describes what a session HOLDS,
|
||||
so the events that destroy context must destroy it too, or the records it names
|
||||
become permanently unreachable mid-session. `scribe_session_context.sh`
|
||||
implemented that — for the rules ledger, by name.
|
||||
|
||||
Five ledgers live in that directory and two were on the list. The note arms'
|
||||
three (`.ids`, `.sync.ids`, `.derive.ids`) were left, under a comment asserting
|
||||
this was "a decision rather than an oversight". It was not a decision, and the
|
||||
arms left out are the ones where it costs most:
|
||||
A ledger describes what a session HOLDS, so the events that destroy context
|
||||
must destroy it too, or the records it names become permanently unreachable
|
||||
mid-session. A clear that names ledgers one by one misses the next one, and the
|
||||
note arms' three (`.ids`, `.sync.ids`, `.derive.ids`) are where a miss costs
|
||||
most:
|
||||
|
||||
- their exclusions are HARD — `exclude_ids` is passed into
|
||||
`semantic_search_notes` itself, so a surfaced note leaves the result set
|
||||
entirely, with no weaker rendering to fall back to the way #3750 gave a
|
||||
repeated rule one;
|
||||
- and they never AGE — #3751's TTL was added to the rules ledger only.
|
||||
entirely, with no weaker rendering to fall back to the way a repeated
|
||||
rule has one;
|
||||
- and they never AGE — the TTL is on the rules ledger only.
|
||||
|
||||
Hard, permanent and never cleared: a note surfaced in a session's first minute
|
||||
is unreachable for the rest of it, through any number of compactions.
|
||||
@@ -38,7 +34,7 @@ test. Both sides are asserted:
|
||||
|
||||
And the negative: `<sid>.unreached` is not a ledger of held context but a
|
||||
record that the instance could not be reached, and a glob that swept it away
|
||||
would make a hook forget an outage it is meant to report (#2932).
|
||||
would make a hook forget an outage it is meant to report.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -137,7 +133,7 @@ def test_a_source_that_kept_the_context_keeps_the_ledgers(source, tmp_path):
|
||||
def test_the_outage_marker_is_not_swept_with_them(tmp_path):
|
||||
"""`.unreached` records that the instance was down, not what was surfaced.
|
||||
|
||||
Different lifetime, different question. #2932's whole point is that "we
|
||||
Different lifetime, different question. The marker's whole point is that "we
|
||||
checked and found nothing" and "we never managed to check" must stay
|
||||
distinguishable, and a clear that took this file out would quietly answer
|
||||
the second with the first.
|
||||
@@ -235,7 +231,7 @@ def test_the_clear_is_derived_and_not_a_list_of_names():
|
||||
|
||||
|
||||
def test_the_evidence_twin_is_not_swept_with_them(tmp_path):
|
||||
"""The second thing that survives, and for the `.unreached` reason (#4217).
|
||||
"""The second thing that survives, and for the `.unreached` reason.
|
||||
|
||||
`scribe_rules_append` writes each ledger twice: `<sid>.<kind>.ids`, which
|
||||
says what this context HOLDS and must be forgotten here, and
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Pull-time freshness (#2690) — attach_live_body against a mocked forge.
|
||||
"""Pull-time freshness — attach_live_body against a mocked forge.
|
||||
|
||||
The properties that must hold, each with its own way of rotting:
|
||||
|
||||
- A no-forge instance's pull response is BYTE-IDENTICAL to today's (rule
|
||||
#115 — the baseline, not a degraded mode).
|
||||
- A no-forge instance's pull response is BYTE-IDENTICAL to a plain read
|
||||
(the baseline, not a degraded mode).
|
||||
- The probe never rewrites the body: "current" refreshes provenance,
|
||||
"diverged" reports, and neither clobbers the record mid-read.
|
||||
- A 404 stamps the mechanically-true 'missing' verdict — once, not on
|
||||
@@ -57,8 +57,8 @@ def _file_response(content: str, commit_sha: str = SHA) -> httpx.Response:
|
||||
|
||||
|
||||
def _patched(forge):
|
||||
"""Stub the owner-keyring lookup (#2778): None → an empty selector, i.e.
|
||||
the owner has no connections — the old 'no forge configured' state."""
|
||||
"""Stub the owner-keyring lookup: None → an empty selector, i.e. the
|
||||
owner has no connections — no forge configured."""
|
||||
from scribe.services.forge import ForgeSelector
|
||||
|
||||
selector = ForgeSelector(() if forge is None else (forge,))
|
||||
@@ -112,7 +112,7 @@ async def test_current_with_same_stored_sha_skips_the_write():
|
||||
|
||||
|
||||
async def test_stored_sha_short_circuit_skips_the_content_fetch():
|
||||
"""#2693: when provenance already names a commit and the forge reports no
|
||||
"""When provenance already names a commit and the forge reports no
|
||||
newer commit touching the path, the pull is confirmed current WITHOUT a
|
||||
content transfer — the economy that fits pull-time freshness inside
|
||||
GitHub's rate limits."""
|
||||
@@ -274,7 +274,7 @@ async def test_forge_failure_inside_lookup_never_breaks_the_pull():
|
||||
assert "body_source" not in data
|
||||
|
||||
|
||||
# --- #2782: an annotated record is not a diverged one ------------------------
|
||||
# --- an annotated record is not a diverged one ------------------------------
|
||||
# Containment is right for a verbatim record and wrong for a deliberately
|
||||
# annotated one: the commentary that makes the record worth reading is exactly
|
||||
# what makes `cached in fetched` false, forever. These pin the escape hatch —
|
||||
@@ -326,7 +326,7 @@ async def test_an_expired_verdict_does_not_vouch():
|
||||
|
||||
|
||||
async def test_a_push_invalidated_verdict_does_not_vouch():
|
||||
"""A push touched the recorded location since the check (#2691) — the repo
|
||||
"""A push touched the recorded location since the check — the repo
|
||||
moved under the verdict even though the record didn't."""
|
||||
data = _data(code=ANNOTATED, verification=_ok_verdict(invalidated_by="c" * 40))
|
||||
assert await _freshness(data) == ("cache", "diverged")
|
||||
@@ -349,7 +349,7 @@ async def test_a_verbatim_record_still_takes_the_containment_path():
|
||||
|
||||
|
||||
async def test_a_verdict_predating_commit_stamping_does_not_vouch():
|
||||
"""Verdicts recorded before `commit_sha` existed (#2688) carry no commit to
|
||||
"""Verdicts recorded before `commit_sha` existed carry no commit to
|
||||
compare, so they cannot tie the body to a known state of the source. They
|
||||
fall through to containment rather than vouching on age alone."""
|
||||
verdict = svc.compose_verification(
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"""task_document — the document a TASK is embedded as, work logs included (#4251).
|
||||
"""task_document — the document a TASK is embedded as, work logs included.
|
||||
|
||||
A work log is the richest record Scribe holds of *why* something is the way it
|
||||
is: prose written during the work, saying what was tried and ruled out. #4241
|
||||
made it readable. It was still not findable, so "has anyone tried this
|
||||
approach?" — the question a log answers — could not reach one, and #4208 was
|
||||
rebuilt in this very session because its logs were unreachable.
|
||||
is: prose written during the work, saying what was tried and ruled out. A log
|
||||
that is readable but not embedded is not findable, so "has anyone tried this
|
||||
approach?" — the question a log answers — cannot reach it.
|
||||
|
||||
These tests pin the shape and the two properties the decision rested on: a task
|
||||
without logs embeds EXACTLY as it always did, and a task with them stays as
|
||||
@@ -30,7 +29,7 @@ D2 = datetime.datetime(2026, 9, 21, 11, 0)
|
||||
def test_a_task_with_no_logs_embeds_exactly_as_it_did_before():
|
||||
"""Most notes are not tasks and most tasks carry no log. Their vectors are
|
||||
the corpus this instance's thresholds were measured against, and changing
|
||||
them for nothing would move every tuned number underneath itself (#4225)."""
|
||||
them for nothing would move every tuned number underneath itself."""
|
||||
assert task_document("A title", "A body") == ("A title", "A body")
|
||||
assert task_document("A title", "A body", []) == ("A title", "A body")
|
||||
assert task_document("T", None) == ("T", None)
|
||||
@@ -81,10 +80,9 @@ def test_an_entry_with_no_timestamp_still_gets_its_own_section():
|
||||
|
||||
def test_each_log_becomes_its_own_title_anchored_chunk():
|
||||
"""THE decision this build turns on. The objection to putting logs in the
|
||||
task's document is that a long log drowns a short title — true before #280,
|
||||
when one vector per record meant a 2,000-word log averaged the task's
|
||||
subject away and everything past ~400 words was truncated unread. Chunking
|
||||
answers both: separate vectors, each carrying the title as its anchor."""
|
||||
task's document is that a long log drowns a short title — true of one
|
||||
vector per record, where a 2,000-word log averages the task's subject away
|
||||
and everything past ~400 words is truncated unread. Chunking answers both: separate vectors, each carrying the title as its anchor."""
|
||||
def _entry(subject: str) -> str:
|
||||
para = " ".join([f"This log entry discusses the {subject} in detail."] * 8)
|
||||
return "\n\n".join(f"{para} (p{i})" for i in range(6))
|
||||
@@ -102,7 +100,7 @@ def test_each_log_becomes_its_own_title_anchored_chunk():
|
||||
# a log section and scored as part of it.
|
||||
assert any("Short body." in c and "alternative" not in c for c in chunks)
|
||||
|
||||
# And nothing is lost: this is what #280 exists for. Paragraph-shaped,
|
||||
# And nothing is lost: this is what chunking exists for. Paragraph-shaped,
|
||||
# because that is what a log is and because a single unbroken 3,000-char
|
||||
# line is hard-split mid-line by design — `test_chunking` owns that case.
|
||||
joined = "\n".join(chunks)
|
||||
@@ -113,7 +111,7 @@ def test_each_log_becomes_its_own_title_anchored_chunk():
|
||||
|
||||
def test_a_short_task_with_a_short_log_is_still_one_sharp_chunk():
|
||||
"""Over-splitting is not the goal either. A task and one short log stay a
|
||||
single document, the shape note #2485 measured as the sharpest."""
|
||||
single document, the shape that measures sharpest."""
|
||||
title, body = task_document("T", "body", [(D1, "a short note about it")])
|
||||
assert chunk_document(title, body) == [embedding_text(title, body)]
|
||||
|
||||
@@ -122,8 +120,8 @@ def test_a_short_task_with_a_short_log_is_still_one_sharp_chunk():
|
||||
#
|
||||
# The shaper above is pure. These pin that the writers actually call it — a
|
||||
# task whose logs are in the document but never re-indexed after one is written
|
||||
# is #4241's half-surface one layer down: the entry is readable, and the search
|
||||
# still answers as though it were never written.
|
||||
# is a half-surface one layer down: the entry is readable, and the search still
|
||||
# answers as though it were never written.
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch # noqa: E402
|
||||
|
||||
|
||||
Reference in New Issue
Block a user