docs(6209): seventeen more test files — lesson shape and round trip, rule scope/moments/kind/surfacing, drift, deadlock race, backup, inception, backfill, claims, supersession, ledgers, family shapes
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m7s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 12s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m7s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 12s
Docstrings and comments state the property and its reason in the present tense. The session-context ledger test no longer describes the decision it "used to pin"; the supersession tests state demote-not-filter without the quote; one-row measurements become the general shape with their figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""A backfill repairs to current truth; it does not replay a snapshot (#4262).
|
||||
"""A backfill repairs to current truth; it does not replay a snapshot.
|
||||
|
||||
The bug these pin: every backfill used to SELECT the text alongside the id and
|
||||
then iterate that snapshot, sleeping between records. A run over a real corpus
|
||||
@@ -90,8 +90,8 @@ async def test_a_record_deleted_between_the_scan_and_the_loop_is_skipped():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_record_whose_text_outran_its_vectors_is_re_embedded():
|
||||
"""The repair half (#4202 — a guard that refuses to write a bad value does
|
||||
not undo the bad values already stored). Note 5 IS at the current version,
|
||||
"""The repair half: a guard that refuses to write a bad value does not
|
||||
undo the bad values already stored. Note 5 IS at the current version,
|
||||
so the version comparison alone would skip it forever; its text is newer
|
||||
than its vectors, which is the only signal that separates "embedded from
|
||||
stale text" from "embedded correctly"."""
|
||||
@@ -131,7 +131,7 @@ async def test_a_task_logged_since_its_vectors_is_re_embedded():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_corpus_that_is_genuinely_current_embeds_nothing():
|
||||
"""The guard must be able to stay quiet, or it is not a guard (#167) — and
|
||||
"""The guard must be able to stay quiet, or it is not a guard — and
|
||||
a backfill that re-embeds everything every boot is a different bug."""
|
||||
scan = _scan_session(all_ids=[1, 2, 3], current_ids=[1, 2, 3])
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"""The shape ledger judging against family ideas, without a database
|
||||
(milestone 463 step 5).
|
||||
"""The shape ledger judging against family ideas, without a database.
|
||||
|
||||
Pinned here: what a project's shapes say about an idea, when an answer
|
||||
contradicts them, which reference a shape in a given language is judged
|
||||
against, and the family arm's rule — measured on 2026-10-06 (#4991), and
|
||||
replayed below as the table it was chosen from. The two ledgers moving
|
||||
against, and the family arm's rule — measured, and replayed below as the
|
||||
table it was chosen from. The two ledgers moving
|
||||
together against Postgres are in tests/test_integration_family_adoption.py.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
@@ -95,7 +94,7 @@ OWN, REF, OTHER = 1, 2, 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name,hits,expected", [
|
||||
# #4991's pairs, the reference as REF and the best other hit as OTHER.
|
||||
# The measured pairs, the reference as REF and the best other hit as OTHER.
|
||||
("A py->go", [(0.728, REF), (0.703, OTHER)], "family"),
|
||||
("B py->go", [(0.715, REF), (0.675, OTHER)], "family"),
|
||||
("D kt->kt", [(0.816, REF), (0.734, OTHER)], "family"),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"""Project inception (milestone 297) — step 1: the record's shape.
|
||||
"""Project inception — the record's shape.
|
||||
|
||||
The WHY a project inherits what it does lives on projects.inception. Pure
|
||||
validation is pinned here; the effects are step 3's integration tests.
|
||||
validation is pinned here; the effects are the integration tests.
|
||||
|
||||
Rules left inception in two steps: the always-on opt-out with the tier
|
||||
(milestone 394), and the rulebook subscription with subscriptions (milestone
|
||||
414). A rule in a rulebook is global, so there is nothing to choose.
|
||||
Rules are not an inception choice: a rule in a rulebook is global, so there is
|
||||
nothing to choose.
|
||||
"""
|
||||
from scribe.models.project import Project
|
||||
from scribe.services.inception import (
|
||||
@@ -39,7 +38,7 @@ def test_validate_inception_pins_the_choice_vocabulary():
|
||||
# believe a rulebook had been declined.
|
||||
assert "unknown inception choice" in validate_inception(
|
||||
{"exclude_always_on_rulebooks": [1]})
|
||||
# Same for the subscription choice since milestone 414.
|
||||
# Same for a rulebook subscription choice.
|
||||
assert "unknown inception choice" in validate_inception({"subscribe_rulebooks": [2]})
|
||||
assert "positive id or null" in validate_inception({"design_system_id": 0})
|
||||
assert "positive id or null" in validate_inception({"design_system_id": True})
|
||||
@@ -71,8 +70,8 @@ def test_normalize_choices_is_canonical_and_complete():
|
||||
|
||||
|
||||
def test_standard_systems_vocabulary_reads_the_catalog_not_a_constant():
|
||||
"""The vocabulary moved from a module constant to the global catalog table
|
||||
(milestone 307): a constant cannot be a foreign key, so nothing outside a
|
||||
"""The vocabulary is the global catalog table, not a module constant: a
|
||||
constant cannot be a foreign key, so nothing outside a
|
||||
project could reference an area. The seed and the bootstrap ask must both
|
||||
read the table, or the list they show and the list they mint diverge."""
|
||||
import inspect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Real-Postgres round trip for the note fields #3182 restored.
|
||||
"""Real-Postgres round trip for the note fields a restore carries.
|
||||
|
||||
The unit lane can prove a serialiser EMITS a key. It cannot prove a restore
|
||||
puts the value back on the right row, and the note->note edges are exactly
|
||||
@@ -7,8 +7,6 @@ the SOURCE database, so a restore that writes them straight through succeeds,
|
||||
reports success, and silently points every edge at whatever record happens to
|
||||
hold that number here.
|
||||
|
||||
That is why #3182 was not fixed in passing.
|
||||
|
||||
These drive the REAL `restore_full_backup`, not a reimplementation of its
|
||||
loop. A test that re-derives the remap it is checking would agree with
|
||||
whatever the product does, including nothing.
|
||||
@@ -162,10 +160,10 @@ async def _no_leftover_restored_user():
|
||||
|
||||
|
||||
async def test_a_restored_record_keeps_what_it_IS(restored):
|
||||
"""#3182's headline. Without note_type and task_kind a restore reported
|
||||
success and handed back a corpus where every snippet and process was a
|
||||
plain note and every issue and spike was `work` — the whole vocabulary
|
||||
milestone 312 and #3128 were about, gone, with nothing to notice it by."""
|
||||
"""Without note_type and task_kind a restore reports success and hands
|
||||
back a corpus where every snippet and process is a plain note and every
|
||||
issue and spike is `work` — the record vocabulary gone, with nothing to
|
||||
notice it by."""
|
||||
by_title, _ = restored
|
||||
|
||||
assert by_title["debounce — rate-limit"].note_type == "snippet"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"""#3262 against a real Postgres: the embedder loses the race, it doesn't run it.
|
||||
"""Against a real Postgres: the embedder loses the race, it doesn't run it.
|
||||
|
||||
The reported failure was a deadlock — `DELETE FROM rulebooks` killed by the
|
||||
server while a detached `upsert_rule_embedding` held the other half of the
|
||||
cycle. It cannot be reproduced with mocks, because there is nothing to
|
||||
The failure is a deadlock — `DELETE FROM rulebooks` killed by the server
|
||||
while a detached `upsert_rule_embedding` holds the other half of the cycle.
|
||||
It cannot be reproduced with mocks, because there is nothing to
|
||||
deadlock: the whole bug lives in the ORDER two transactions take two row
|
||||
locks, which only a lock manager can adjudicate.
|
||||
|
||||
So each test here holds a real delete open in one transaction and calls the
|
||||
embedder in another. What is being pinned is that the embedder RETURNS —
|
||||
promptly, having written nothing. Before the fix it would sit on the chunk
|
||||
rows waiting for a delete that is itself waiting on the insert's foreign key,
|
||||
and the test would hang rather than fail, which is why every call carries a
|
||||
deadline (rule 156).
|
||||
promptly, having written nothing. An embedder that sits on the chunk rows
|
||||
waiting for a delete that is itself waiting on the insert's foreign key would
|
||||
hang the test rather than fail it, which is why every call carries a
|
||||
deadline.
|
||||
"""
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, patch
|
||||
@@ -53,8 +53,7 @@ SETTLE_DEADLINE_SECONDS = 30
|
||||
async def seeded():
|
||||
"""A rule and a note to race against.
|
||||
|
||||
CLEANED AT SETUP, NOT TEARDOWN — the same constraint #3241 hit and the
|
||||
reason this file exists. `create_rule` fires its own detached embedding
|
||||
CLEANED AT SETUP, NOT TEARDOWN. `create_rule` fires its own detached embedding
|
||||
task; a teardown that deleted the rulebook would be racing exactly the
|
||||
thing under test, on a loop that is closing.
|
||||
"""
|
||||
@@ -141,7 +140,7 @@ async def test_a_rule_refresh_yields_to_a_delete_cascading_from_its_rulebook(see
|
||||
|
||||
|
||||
async def test_a_note_refresh_yields_to_a_delete_of_the_note(seeded):
|
||||
"""The note twin, which #3262 recorded as unverified. Notes are soft-deleted
|
||||
"""The note twin. Notes are soft-deleted
|
||||
day to day, so the hard delete a trash purge issues is the one that can put
|
||||
a lock on the row while a refresh is in flight."""
|
||||
async with async_session() as blocker:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Real-Postgres tests for the drift surface (milestone 399 step 5, #3895).
|
||||
"""Real-Postgres tests for the drift surface.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
@@ -20,8 +20,8 @@ subquery picking the newest version per rule, an ownership clause spanning two
|
||||
paths, and a write that has to leave the history longer than it found it. A
|
||||
stand-in session returns whatever the test handed it, so it could confirm none
|
||||
of those. `test_the_restore_is_recorded_as_an_edit` is the one that matters
|
||||
most: it is the whole basis for allowing a one-click revert here when
|
||||
milestone 323 refused one for rules, and if it were ever to pass while the
|
||||
most: it is the whole basis for allowing a one-click revert here when rules
|
||||
have none, and if it were ever to pass while the
|
||||
restore silently overwrote history, the carve-out would be indefensible.
|
||||
"""
|
||||
import pytest
|
||||
@@ -228,8 +228,8 @@ async def test_restoring_puts_the_old_wording_back(world):
|
||||
async def test_the_restore_is_recorded_as_an_edit(world):
|
||||
"""THE ONE THAT MATTERS MOST — it is why this carve-out is defensible.
|
||||
|
||||
Milestone 323 refused a one-click restore for rules because "a silent
|
||||
revert would erase the only record of why the rewrite happened". The
|
||||
Rules have no one-click restore because a silent revert would erase the
|
||||
only record of why the rewrite happened. The
|
||||
preference carve-out survives that objection only while nothing is
|
||||
erased: the restore goes through `update_rule`, so it takes its own
|
||||
snapshot and the history GAINS an entry.
|
||||
@@ -262,7 +262,7 @@ async def test_the_restore_is_recorded_as_an_edit(world):
|
||||
|
||||
|
||||
async def test_a_rule_refuses_the_one_click_restore(world):
|
||||
"""Milestone 323 stands for rules, and the kind check is in the service
|
||||
"""Rules still have no restore, and the kind check is in the service
|
||||
rather than only in the route — so a caller reaching this directly cannot
|
||||
forget it."""
|
||||
await rulebooks_svc.update_rule(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Real-Postgres tests for `rules.kind` and its CHECK (0098, #3849 step 1).
|
||||
"""Real-Postgres tests for `rules.kind` and its CHECK (migration 0098).
|
||||
|
||||
Rule 36 exists because a value and its constraint drift apart: the code
|
||||
A value and its constraint drift apart: the code
|
||||
starts writing a new kind while the database still refuses it, and nothing
|
||||
catches it until a write fails in front of someone. A mock cannot show that —
|
||||
it has no CHECK — so the constraint gets a real-DB test, exactly as 0091's
|
||||
@@ -94,8 +94,7 @@ async def test_an_unknown_kind_is_refused(topic_id):
|
||||
"""The half that proves the constraint is there at all.
|
||||
|
||||
Without this, every other assertion in this file would pass against a
|
||||
table whose CHECK had been dropped — the exact failure rule 36 is written
|
||||
against.
|
||||
table whose CHECK had been dropped — the exact drift this guards.
|
||||
"""
|
||||
_uid, topic = topic_id
|
||||
with pytest.raises(IntegrityError):
|
||||
@@ -119,8 +118,8 @@ async def test_a_rule_can_become_a_preference_after_the_fact(topic_id):
|
||||
"""The read-back is the whole test.
|
||||
|
||||
A version asserting only that the update call succeeded would pass against
|
||||
code that accepted `kind` and dropped it — which is how the same bug
|
||||
survived on `task_kind` long enough to be found by hand (#3129).
|
||||
code that accepted `kind` and dropped it — a bug that otherwise surfaces
|
||||
only when someone finds it by hand.
|
||||
|
||||
This is also the migration path the always-on triage needs: the rules that
|
||||
turn out to be preferences change one column and keep their id, history
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Real-Postgres tests for rule mounts (milestone 458 step 3).
|
||||
"""Real-Postgres tests for rule mounts.
|
||||
|
||||
A mount is the claim "this rule arrives at this moment". These pin what that
|
||||
claim depends on:
|
||||
@@ -7,7 +7,7 @@ claim depends on:
|
||||
- the rule's detail reads its mounts back;
|
||||
- a backup carries them, attached to the RESTORED rule's new id rather than
|
||||
the old number;
|
||||
- the delivery lookup (step 4) answers by home: a project's mounted rule
|
||||
- the delivery lookup answers by home: a project's mounted rule
|
||||
reaches only work bound to that project, and nobody else's mount arrives.
|
||||
"""
|
||||
import pytest
|
||||
@@ -150,7 +150,7 @@ async def test_a_backup_carries_the_mounts_to_the_restored_rule(world):
|
||||
assert sorted(r["moment"] for r in payload["rule_moments"]) == [
|
||||
"reply.report", "work.finish",
|
||||
]
|
||||
# The mount was an edit, so it was also recorded as two judgments (step 7)
|
||||
# The mount was an edit, so it was also recorded as two judgments
|
||||
# — and those travel too, onto the restored rule.
|
||||
payload["rule_moment_judgments"] = [
|
||||
row for row in exported["rule_moment_judgments"] if row["rule_id"] == rule.id
|
||||
@@ -238,7 +238,7 @@ async def test_the_delivery_lookup_answers_by_home(world):
|
||||
|
||||
|
||||
async def test_moment_usage_counts_deliveries_and_the_agent_opens_that_followed(world):
|
||||
"""The per-moment readout (step 6), on real SQL: an EXISTS inside a
|
||||
"""The per-moment readout, on real SQL: an EXISTS inside a
|
||||
COUNT(CASE …) is a shape no mock can vouch for, and the block's own guard
|
||||
would turn a database refusal into a quiet `moment_usage_failed`."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Real-Postgres tests for WHERE a rule reaches (milestone 414, step 1).
|
||||
"""Real-Postgres tests for WHERE a rule reaches.
|
||||
|
||||
A rule lives in a rulebook topic (global) or on one project. Retrieval used to
|
||||
ignore that and search every rule the user owned, so each project's rules were
|
||||
@@ -78,7 +78,7 @@ async def _found(user_id: int, **scope) -> set[int]:
|
||||
"""The rule ids a search finds — and a failure, not an empty set, when the
|
||||
search never ran. `semantic_search_rules` fails open (an exception becomes
|
||||
[]), so without `report["searched"]` an error and a scoping miss read the
|
||||
same (#4958)."""
|
||||
same."""
|
||||
report: dict = {}
|
||||
raised: list[str] = []
|
||||
with patch("scribe.services.embeddings.get_embedding",
|
||||
@@ -109,7 +109,7 @@ async def test_retrieval_reaches_a_rule_only_from_its_home(homes):
|
||||
|
||||
|
||||
async def test_a_shared_project_brings_its_rules_to_a_collaborator(homes):
|
||||
"""Readability goes through access.can_read_project (rule 78): a viewer on
|
||||
"""Readability goes through access.can_read_project: a viewer on
|
||||
A gets A's rules. Not the owner's global rules — rulebooks are the
|
||||
owner's — and not B's, which is not shared."""
|
||||
collaborator = homes["collaborator"]
|
||||
@@ -118,9 +118,9 @@ async def test_a_shared_project_brings_its_rules_to_a_collaborator(homes):
|
||||
|
||||
|
||||
async def test_an_in_scope_rule_is_found_however_many_closer_rules_are_out_of_scope():
|
||||
"""#4958. Ordered through the HNSW index, the search took the ~40 nearest
|
||||
chunks from every owner and filtered them AFTERWARDS — so a reader's own
|
||||
rule ranked past the 40th chunk overall was never returned. Here another
|
||||
"""A search ordered through the HNSW index takes the ~40 nearest chunks
|
||||
from every owner, and filtering them AFTERWARDS loses a reader's own rule
|
||||
ranked past the 40th chunk overall. Here another
|
||||
user's 80 rules all sit nearer the query than the reader's one rule; the
|
||||
reader's search must still find it."""
|
||||
tag = uuid.uuid4().hex[:8]
|
||||
|
||||
@@ -10,12 +10,9 @@ planning read carries — and what mocks can't prove about it:
|
||||
comparison, never a similarity score.
|
||||
2. An UNTAGGED global rule is not listed: it applies everywhere and arrives by
|
||||
retrieval, and listing every one under every project would say nothing.
|
||||
3. A `co_surfaces` partner arrives with its other half — the failure that made
|
||||
merging rule 144 into rule 46 look like the only fix — unless the partner
|
||||
lives on a different project, because an edge is not a way in.
|
||||
|
||||
Milestone 414 dropped the suppression claim ("an explicit suppression outranks
|
||||
an edge") with suppressions themselves.
|
||||
3. A `co_surfaces` partner arrives with its other half — without it, merging
|
||||
two rules that must arrive together looks like the only fix — unless the
|
||||
partner lives on a different project, because an edge is not a way in.
|
||||
"""
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
@@ -102,9 +99,9 @@ async def test_a_tagged_global_rule_is_listed_for_a_project_that_works_in_its_ar
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_co_surfaces_drags_in_the_half_that_would_have_been_missed(world):
|
||||
"""Rule 144 was split off rule 46 and folded back the same day because
|
||||
"either rule could surface without the other". This is the edge that makes
|
||||
that unnecessary: the partner arrives even though nothing else selected
|
||||
"""Two rules that must arrive together get folded into one because either
|
||||
could surface without the other. This is the edge that makes that
|
||||
unnecessary: the partner arrives even though nothing else selected
|
||||
it, and says why it is here."""
|
||||
partner = await rulebooks_svc.create_rule(
|
||||
world["topic"], world["owner"], "Version names are labels",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The document a lesson is embedded as (milestone 385 step 3; milestone 427).
|
||||
"""The document a lesson is embedded as.
|
||||
|
||||
WHY THIS IS THE STEP THAT DECIDES THE MILESTONE
|
||||
|
||||
@@ -9,17 +9,16 @@ anywhere would report that.
|
||||
|
||||
WHERE THE SHARP SHAPE LIVES
|
||||
|
||||
A snippet, which note #2485 measured as the only sharp record in the corpus (a
|
||||
0.153 top-to-second gap against 0.010–0.023 for everything else), is sharp
|
||||
A snippet, measured as the sharpest record kind in a corpus (a 0.153
|
||||
top-to-second gap against 0.010–0.023 for everything else), is sharp
|
||||
because its document states its purpose twice: `name — when to use` as the
|
||||
title, and again as the body's first line. A lesson follows the snippet.
|
||||
|
||||
Until milestone 427 that `subject — trigger` title was also the STORED title,
|
||||
so every listing, menu and search row showed the trigger too — kilobytes of it.
|
||||
Now the stored title is the subject, and `embeddings.document_title` joins the
|
||||
trigger back from `data` at embed time. The embedded TEXT is what it always
|
||||
was, which is the property these guards pin: nothing re-embeds, and the floors
|
||||
tuned against these vectors stay calibrated.
|
||||
The STORED title is the subject alone, so listings, menus and search rows do
|
||||
not carry kilobytes of trigger; `embeddings.document_title` joins the trigger
|
||||
back from `data` at embed time. The embedded TEXT keeps the `subject —
|
||||
trigger` shape, which is the property these guards pin: the floors tuned
|
||||
against these vectors stay calibrated.
|
||||
|
||||
No similarity number is asserted anywhere: a threshold pins the embedder's
|
||||
behaviour rather than this code's, and breaks on a model change that is not a
|
||||
@@ -44,7 +43,7 @@ def _embedded(what: str, trigger: str, insight: str) -> tuple[str, str]:
|
||||
|
||||
|
||||
def test_the_stored_title_is_the_subject_alone():
|
||||
"""Milestone 427. The trigger lives in `data` and the body's first line; the
|
||||
"""The trigger lives in `data` and the body's first line; the
|
||||
title a listing shows is what the lesson is ABOUT."""
|
||||
title, _ = lessons_svc.lesson_document(SUBJECT, TRIGGER, INSIGHT)
|
||||
assert title == SUBJECT
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Reading a lesson's insight back and re-composing it returns the same body (#4272).
|
||||
"""Reading a lesson's insight back and re-composing it returns the same body.
|
||||
|
||||
THE PROPERTY. `update_lesson` is field-wise: an edit that touches only
|
||||
`system_ids`, or only `what`, passes `insight=None`, and the service then
|
||||
@@ -24,11 +24,9 @@ insight", the full trigger was prepended again, and the stored lesson gained
|
||||
(n-1) paragraphs per update — silently, compounding, and in the half of the
|
||||
document that does the retrieving.
|
||||
|
||||
Measured on the one real row this damaged (lesson #4207, a three-paragraph
|
||||
trigger): the body grew by exactly 607 characters per edit — the length of
|
||||
paragraphs 2 and 3 with their separators — taking it from 1291 characters to
|
||||
7361 over ten edits, at which point the trigger appears eleven times and the
|
||||
insight is 17% of the record.
|
||||
On a three-paragraph trigger the body grows by the length of paragraphs 2 and
|
||||
3 with their separators on every edit — after ten edits the trigger appears
|
||||
eleven times and the insight is a sixth of the record.
|
||||
|
||||
WHY A MARKER COULD NOT HAVE FIXED IT. There is no end-of-trigger marker in
|
||||
the body and adding one would not repair a row already written without it.
|
||||
@@ -99,7 +97,7 @@ def _stored(trigger: str, insight: str, sources: list[int]):
|
||||
MagicMock auto-creates truthy and coerces to `1`. A no-sources shape then
|
||||
recomposes with `**Learned from:** #1` and the round trip fails for a
|
||||
reason that has nothing to do with the trigger. `fake_lesson` pins that
|
||||
field to None — note #2109's point, which is why the fixture exists.
|
||||
field to None, which is why the fixture exists.
|
||||
"""
|
||||
title, body = lessons_svc.lesson_document(WHAT, trigger, insight, sources)
|
||||
data = lessons_svc.compose_data(WHAT, trigger, sources)
|
||||
@@ -137,7 +135,7 @@ def test_an_edit_that_changes_nothing_changes_nothing(_name, trigger, insight, s
|
||||
# Reported as a size delta rather than left to a diff of two multi-kilobyte
|
||||
# strings: the failure this catches is duplication, and the number of
|
||||
# characters gained says which paragraph came back far faster than an
|
||||
# equality dump does (rule 167 — a guard has to name what broke).
|
||||
# equality dump does (a guard has to name what broke).
|
||||
assert (title, body) == (note.title, note.body), (
|
||||
f"a no-op edit rewrote the lesson: title {len(title) - len(note.title):+d} "
|
||||
f"chars, body {len(body) - len(note.body):+d} chars"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""The supersession claim — who may make it, and what it refuses.
|
||||
|
||||
Step 2 of #278. Ranking behaviour is step 3; this covers only recording and
|
||||
reading the relation.
|
||||
Ranking behaviour is in test_supersession_ranking.py; this covers only
|
||||
recording and reading the relation.
|
||||
|
||||
The cycle tests are the ones worth reading. Under FLAT demotion a ring of
|
||||
records that supersede each other claims every member is obsolete, so all of
|
||||
@@ -53,7 +53,7 @@ async def test_superseding_a_note_you_can_only_READ_is_refused_not_dropped():
|
||||
|
||||
Demoting someone else's record out of their retrieval is damage that is
|
||||
invisible from the outside — the caller would believe it worked, and the
|
||||
owner would have no symptom to trace. Rule #47.
|
||||
owner would have no symptom to trace.
|
||||
"""
|
||||
# writable for the superseder (id 1), not for the target (id 2)
|
||||
writable = AsyncMock(side_effect=lambda uid, nid: nid == 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The SessionStart hook clears the rule ledger exactly when context dies (#3749).
|
||||
"""The SessionStart hook clears the rule ledger exactly when context dies.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
@@ -109,28 +109,16 @@ def test_the_rules_ledger_survives_exactly_when_the_context_does(tmp_path):
|
||||
|
||||
|
||||
def test_the_note_ledgers_are_cleared_too_now_that_a_repeat_is_shown(tmp_path):
|
||||
"""The decision this test used to pin, reversed — and why it could be.
|
||||
"""The note ledgers are cleared on a compaction too.
|
||||
|
||||
It read: the note arms were deliberately left out of #3749, whether a
|
||||
surfaced NOTE should come back after a compaction is a different question,
|
||||
and a glob over `<sid>.*` would decide it by accident. That was right when
|
||||
it was written, and the reason was the note arms' behaviour rather than the
|
||||
note arms' nature: a note repeat was WITHHELD, so clearing their ledger
|
||||
meant re-injecting whole menu lines the session had already been given.
|
||||
Against that, leaving them was the cheaper mistake.
|
||||
|
||||
#4101 removed the premise. A note repeat is now rendered again with a
|
||||
`seen` marker instead of dropped from the search, so the ledger no longer
|
||||
decides whether a record is reachable — only whether its line says the
|
||||
A note repeat is rendered again with a `seen` marker rather than dropped
|
||||
from the search, so the ledger does not decide whether a record is
|
||||
reachable — only whether its line says the
|
||||
session has met it before. Across a compaction that marker becomes a false
|
||||
statement: it tells a freshly-summarised session it has already seen a
|
||||
record that is nowhere in its context. Keeping the ledger buys nothing and
|
||||
asserts something untrue.
|
||||
|
||||
Kept as a named test rather than deleted, because the roster it guards
|
||||
moved to tests/test_session_ledger_clear.py and what is worth keeping HERE
|
||||
is the record that the answer changed, with the reason attached — a
|
||||
deleted test takes the old decision's reasoning with it.
|
||||
asserts something untrue. The full roster is guarded in
|
||||
tests/test_session_ledger_clear.py.
|
||||
"""
|
||||
env = _env(tmp_path)
|
||||
sid = "sess-scope"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Demotion: superseded records rank behind their equals, and are never hidden.
|
||||
|
||||
Step 3 of #278. The distinction these tests exist to protect is DEMOTE vs
|
||||
FILTER. The operator was explicit:
|
||||
|
||||
"the failure is pollution, not existence"
|
||||
The distinction these tests exist to protect is DEMOTE vs FILTER: the failure
|
||||
is pollution, not existence.
|
||||
|
||||
Hiding a superseded record would turn every one of them into something you must
|
||||
already know exists in order to find, and would destroy "what did we think
|
||||
@@ -131,11 +129,10 @@ async def test_an_empty_candidate_set_short_circuits():
|
||||
def test_the_penalty_is_sized_to_reorder_a_cluster_not_shuffle_within_it():
|
||||
"""Sized against a measurement, not by feel.
|
||||
|
||||
Measured 2026-08-07 (#2486): the notes competing on a dev-log's own title
|
||||
phrase sat within ~0.014 of each other, spanning 0.6506 down to 0.6120. A
|
||||
penalty smaller than that spread would move a record within a tie without
|
||||
changing which one wins — the failure #2486 already proved cannot be tuned
|
||||
away, because the neighbours are not barely passing, they are tied.
|
||||
Notes competing on a dev-log's own title phrase sit within ~0.014 of each
|
||||
other (0.6506 down to 0.6120 on one measured cluster). A penalty smaller
|
||||
than that spread moves a record within a tie without changing which one
|
||||
wins — a failure that cannot be tuned away, because the neighbours are not barely passing, they are tied.
|
||||
|
||||
The upper bound is the operator's constraint, not an optimisation: a penalty
|
||||
large enough to bury a superseded record outright is hiding by another name.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Rulings are counted where someone will read them (#4769).
|
||||
"""Rulings are counted where someone will read them.
|
||||
|
||||
`system_usage_events` was written from milestone 444 step 4 and read by
|
||||
nothing. These pin the two readouts:
|
||||
`system_usage_events` is read in two places, and these pin both readouts:
|
||||
|
||||
- PER SYSTEM: `usage_for_systems` — one GROUP BY, a zero shape for a System
|
||||
nothing touched — carried by the Systems list, the REST detail and MCP
|
||||
@@ -10,7 +9,7 @@ nothing. These pin the two readouts:
|
||||
the areas named, and deliberately NO pull-through ratio.
|
||||
|
||||
Integration for both aggregates, not mocked: a GROUP BY the database rejects
|
||||
is swallowed by the fail-open guard and reads as zero, which is #2663 exactly.
|
||||
is swallowed by the fail-open guard and reads as zero.
|
||||
"""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""A task's claim — which session is working it, and how it dies (milestone 381 step 2).
|
||||
"""A task's claim — which session is working it, and how it dies.
|
||||
|
||||
WHAT THIS PINS
|
||||
|
||||
1. **The write that is the work stamps it.** Reaching `in_progress` — by
|
||||
update OR by a create that names it (#3683) — claims the task for whoever
|
||||
update OR by a create that names it — claims the task for whoever
|
||||
made the change. Nothing asks the model to claim anything.
|
||||
2. **Ending or un-starting the work releases it**, and releasing is
|
||||
idempotent.
|
||||
@@ -287,8 +287,8 @@ async def test_ending_a_session_releases_only_that_sessions_claims(users):
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_a_collaborator_with_write_access_can_log_and_so_claims(users):
|
||||
"""create_log used to filter on the task's OWNER (a rule #78 violation), so
|
||||
a collaborator on a shared task could not log on it — nor, since logging
|
||||
"""create_log filters on who may write the TASK, not on its owner, so a
|
||||
collaborator on a shared task can log on it — nor, since logging
|
||||
stamps the claim, ever be seen working it."""
|
||||
from scribe.services import sharing, task_logs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user