docs(6209): forty test files — design cascade and stylesheet, embeddings, family canon, forge, backup round trips, inception, placement, platforms, moments, rule move and verification, search scope, facets

Module and test docstrings state what is pinned without the record number
or step that introduced it. Incident accounts become the failure shape in
the present tense: the pipefail head, the code_shapes import, the export
order, the facet dialects, the cross-kind repeat, the write-once kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 21:40:25 -04:00
co-authored by Claude Opus 5.5
parent 63e97a6f0f
commit 79de8938b9
40 changed files with 105 additions and 110 deletions
+6 -6
View File
@@ -1,8 +1,8 @@
"""The auto-inject query carries the conversation beside a thin prompt (#4364).
"""The auto-inject query carries the conversation beside a thin prompt.
The notes arm used to retrieve against the operator's typed words alone, and a
mid-session follow-up — "yes do that", "now fix the filter" — names nothing a
record can match. The hook now reads the tail of the last assistant reply out
The operator's typed words alone are a poor query: a mid-session follow-up —
"yes do that", "now fix the filter" — names nothing a record can match. The
hook reads the tail of the last assistant reply out
of the transcript and sends it as `ctx`; the server appends it to a SHORT
prompt only. Two halves, pinned separately: what the server builds, and what
the hook extracts.
@@ -103,10 +103,10 @@ def test_no_transcript_is_silence_not_failure(tmp_path):
assert recent(tmp_path, []) == ""
# --- what a menu line carries (#4364) ----------------------------------------
# --- what a menu line carries -----------------------------------------------
#
# The name, the kind and System, and the WHOLE matched passage — once. A repeat
# is a pointer. These pin the shape against the three ways it had gone wrong:
# is a pointer. These pin the shape against the three ways it goes wrong:
# a trigger-composed title rendered as the line (1,500+ chars), the passage cut
# to its head (which was the title again), and a `seen` repeat re-rendered whole.
+2 -2
View File
@@ -1,4 +1,4 @@
"""The CI-side token check (#2277).
"""The CI-side token check.
The checker is stdlib-only and lives in scripts/ so CI can run it without an
install, so these tests import it by path rather than as a package.
@@ -45,7 +45,7 @@ def test_declarations_after_a_comment_are_not_lost():
def test_the_supersedes_block_is_read_from_the_sheet_not_hardcoded():
"""Rule #115: the checker must know nothing about any install's palette.
"""The checker knows nothing about any install's palette.
Everything it enforces comes out of the stylesheet it is pointed at."""
assert check.superseded_literals(SHEET) == {
"#fff": "--fs-text-primary",
+2 -3
View File
@@ -1,12 +1,11 @@
"""Every create_* tool says what it is NOT for (#3123).
"""Every create_* tool says what it is NOT for.
WHY THIS EXISTS
Scribe's record kinds are reached for interchangeably — a note written where
a task was owed, a rule written where a snippet belonged — and the moment of
choice is the only moment a correction is cheap. The tool docstring IS the
agent-facing contract (rule 119 puts product guidance there and nowhere
else), so a docstring that only documents parameters answers "how do I call
agent-facing contract (product guidance lives there and nowhere else), so a docstring that only documents parameters answers "how do I call
this" while leaving "should I be calling this at all" unasked.
The gap was lopsided before this: `create_rule` and `start_planning` both
+3 -3
View File
@@ -1,4 +1,4 @@
"""The parent chain: walking it, and refusing to close it (milestone #254 step 1).
"""The parent chain: walking it, and refusing to close it.
Pure functions over a `{id: parent_id}` literal, so a whole hierarchy is one line
of setup and no database is involved. That is the reason the cascade lives in its
@@ -99,7 +99,7 @@ def test_the_guard_survives_a_hierarchy_that_is_already_corrupt():
# --- resolution -------------------------------------------------------------
#
# The cascade proper (milestone #254 step 2). Hierarchies are stated as literals
# The cascade proper. Hierarchies are stated as literals
# because resolve_tokens is pure and duck-typed — which is the whole reason it
# lives here rather than inside the service.
@@ -384,7 +384,7 @@ def test_rows_without_a_supersedes_attribute_at_all_still_resolve():
def test_rationale_cascades_like_purpose_and_is_a_different_question():
"""`purpose` is what the token is FOR; `rationale` is why it is this value.
Rules carry the second routinely ("Success = Moss, aligned by design") and a
token row had nowhere to put it until now."""
token row needs a place to put it."""
resolved = _by_name(resolve_tokens(
APP, PARENTS,
{
+3 -3
View File
@@ -120,8 +120,8 @@ def test_the_header_says_what_the_sheet_is_for():
# --- modes ------------------------------------------------------------------
def test_base_goes_on_the_root_selector_and_other_modes_layer_over_it():
"""Matches the convention already in the codebase, and the one-way scoping
#251 recorded: light on `:root`, dark layered on an attribute selector."""
"""Matches the convention already in the codebase, and its one-way
scoping: light on `:root`, dark layered on an attribute selector."""
css = render_stylesheet([
design_token_stub(name="--fs-bg", value_by_mode={"base": "#f5f1e8", "dark": "#14171a"}),
])
@@ -188,7 +188,7 @@ def test_a_declared_token_with_no_value_appears_as_a_comment_not_a_silence():
def test_an_empty_system_renders_a_sheet_with_no_blocks_rather_than_failing():
"""A system with no tokens is an ordinary state (rule #115), including one
"""A system with no tokens is an ordinary state, including one
that was just created."""
css = render_stylesheet([])
assert "{" not in css
+2 -2
View File
@@ -9,7 +9,7 @@ the dangerous one: a recurring task embedded to a different shape than the rest
of the corpus is ranked against documents it doesn't match, and nothing reports
it. A wrong vector returns results; it just returns the wrong ones.
It is also the precondition for #2486. A dev-log's vector separates from five
It is also the precondition for comparing document shapes. A dev-log's vector separates from five
unrelated dev-logs by 0.023 where a snippet separates by 0.153, and the leading
explanation is shape — a snippet states its purpose twice in a short document.
Testing an alternative against three copies would mean testing a shape that
@@ -77,5 +77,5 @@ def test_nothing_else_builds_the_embedding_document_itself():
f"these build the embedding document inline instead of calling "
f"embedding_text(): {offenders}. One definition — an inlined copy is "
f"ranked against a corpus it no longer matches the moment the shape "
f"changes, and nothing reports it (#2486)."
f"changes, and nothing reports it."
)
+3 -3
View File
@@ -1,4 +1,4 @@
"""The embedding refresh must LOSE to a delete, not race it (#3262).
"""The embedding refresh must LOSE to a delete, not race it.
Both upserts replace a record's vectors as delete-then-insert. That takes two
row locks — the chunk rows, then the parent row via the insert's foreign key —
@@ -32,7 +32,7 @@ def _mock_session(lock_result: object = 7, execute_side_effect=None):
session = MagicMock()
claimed = MagicMock()
claimed.scalar_one_or_none.return_value = lock_result
# A task's document carries its work logs (#4251), so the refresh reads
# A task's document carries its work logs, so the refresh reads
# task_logs before it builds chunks. Answered explicitly — left to
# autovivify, `list(result.all())` would raise and be swallowed, and these
# tests would be exercising the failure path without saying so.
@@ -72,7 +72,7 @@ async def test_a_note_refresh_claims_the_row_before_rewriting_its_vectors():
sqls = [compiled_sql(c.args[0], dialect=PG) for c in session.execute.call_args_list]
# Located by what they ARE rather than by position: the work-log read a
# task's document needs (#4251) runs before any of this, and an index is
# task's document needs runs before any of this, and an index is
# not what the claim is about.
claim = next(i for i, sql in enumerate(sqls) if "FOR KEY SHARE NOWAIT" in sql)
replace = next(
+1 -1
View File
@@ -74,7 +74,7 @@ async def test_get_embedding_propagates_model_load_failures():
await get_embedding("x")
# ─── query_embedding_memo (milestone 456 step 1) ─────────────────────────────
# ─── query_embedding_memo ────────────────────────────────────────────────────
def _counting_embeddings():
+2 -2
View File
@@ -1,4 +1,4 @@
"""The promotion engine without a database (milestone 463 step 3).
"""The promotion engine without a database.
The parts that decide — which citations claim lineage, which criteria veto —
are pure and pinned here, beside the doors' wiring: the triggers ride the
@@ -89,7 +89,7 @@ def test_proven_needs_named_evidence():
def test_the_criteria_the_agent_is_told_are_the_ones_enforced():
"""Rule 119: the criteria are product text. The promote tool's docstring
"""The criteria are product text. The promote tool's docstring
must name every criterion the service checks, by its parameter name."""
doc = family_tools.promote_family_idea.__doc__
for key in CRITERIA_KEYS:
+2 -2
View File
@@ -1,4 +1,4 @@
"""The adoption ledger without a database (milestone 463 step 4).
"""The adoption ledger without a database.
The parts that decide are pure and pinned here: what an assessment needs
before it can be recorded, what each ground of the conflict order needs —
@@ -166,7 +166,7 @@ def test_no_reference_at_all_points_at_the_idea():
# --- the product text and the doors ---------------------------------------------------
def test_the_outcomes_the_agent_is_told_are_the_ones_enforced():
"""Rule 119: the outcomes and the order are product text. The tool
"""The outcomes and the order are product text. The tool
docstrings must name every outcome and every ground the service checks,
in the service's order."""
assess_doc = tool_doc("scribe.mcp.tools.family", "assess_family_adoption")
+1 -1
View File
@@ -1,4 +1,4 @@
"""How family canon reaches a session (milestone 463 step 6), without a
"""How family canon reaches a session, without a
database: the line enter_project carries, the skill and its triggers, and the
pointers every other surface gives to it. Retrieval reach, the readout's
counts and the owed answers against Postgres are in
+1 -1
View File
@@ -1,4 +1,4 @@
"""Forge-connection tokens are stored sealed (milestone 523, #5877).
"""Forge-connection tokens are stored sealed.
Migration 0122 seals the rows that existed before, with its OWN frozen copy of
the v1 key derivation (so a later edit to services/secrets.py can never change
+2 -2
View File
@@ -1,4 +1,4 @@
"""Forge push webhook (#2691) — signature gate, payload parsing, and the
"""Forge push webhook — signature gate, payload parsing, and the
end-to-end drift flag on real Postgres.
The webhook is the seam that makes verification scale: a push names exactly
@@ -43,7 +43,7 @@ def test_signature_gate():
def test_delivered_signature_reads_both_forges_headers():
"""#2693: GitHub signs the same HMAC but ships it as
"""GitHub signs the same HMAC but ships it as
X-Hub-Signature-256: sha256=<hex> — the whole webhook payload mapping is
this header, so pin it."""
hexsig = _sign(b"{}")
+1 -1
View File
@@ -1,4 +1,4 @@
"""The create gate's similarity bars are settings, clamped and fail-open (#4385)."""
"""The create gate's similarity bars are settings, clamped and fail-open."""
from unittest.mock import AsyncMock, patch
import pytest
+5 -5
View File
@@ -1,14 +1,14 @@
"""Under `pipefail`, an early-exiting `head` must not void the output it kept (#4042).
"""Under `pipefail`, an early-exiting `head` must not void the output it kept.
The hooks run with `set -uo pipefail`. `hits=$(git grep -l … | head -4) ||
hits=""` lost its four hits whenever git grep was still writing when head
exited: SIGPIPE, a failed substitution, and the fallback wiped the result. It
only showed on big outputs, so the by-name duplicate arm went silent for
hits=""` loses its four hits whenever git grep is still writing when head
exits: SIGPIPE, a failed substitution, and the fallback wipes the result. It
only shows on big outputs, so the by-name duplicate arm goes silent for
exactly the most-duplicated names.
Two halves: the real function on a repo big enough to overflow the pipe, and
the check_plugin pattern that keeps the shape out of every hook (shown able to
fail, rule #167).
fail).
"""
from __future__ import annotations
@@ -1,11 +1,11 @@
"""Real-Postgres round trip for code_shapes (#4197).
"""Real-Postgres round trip for code_shapes.
**What this guards is a column that was simply not there.** `code_shapes`
exported `reason_code`, `recheck_at` and `diverges_from` and imported none of
them — a restore reported success and came back with every judgment's verdict
**What this guards is a column the import leaves out.** If `code_shapes`
exports `reason_code`, `recheck_at` and `diverges_from` and imports none of
them, a restore reports success and comes back with every judgment's verdict
and no code for why, every recheck flag cleared, and every divergence pointer
gone. The unit-level column guard in `test_services_backup.py` is what found
it and is what stops it recurring; this file is the behavioural half, because
gone. The unit-level column guard in `test_services_backup.py` catches a
column missing from either side; this file is the behavioural half, because
"the builder passes the kwarg" and "the value survives a real restore" are
different claims.
@@ -1,11 +1,11 @@
"""Real-Postgres check that the full export is ORDERED, so the backup file
deduplicates between runs (milestone 523).
deduplicates between runs.
Only Postgres shows this. An unordered select returns rows in heap order, and
an UPDATE writes the row's new version at the end of the heap — so before
export_full_backup ordered its queries, editing one old note moved it to the
bottom of `notes`. Every byte after its old place shifted, and a restic run
over the next backup stored most of the table again. A mocked session returns
an UPDATE writes the row's new version at the end of the heap — so without an
ORDER BY in export_full_backup, editing one old note moves it to the bottom of
`notes`. Every byte after its old place shifts, and a restic run over the next
backup stores most of the table again. A mocked session returns
whatever order it was handed and could never catch it.
"""
import io
@@ -101,7 +101,7 @@ async def test_the_tar_restores_with_every_id_remapped(written):
assert (note.project_id, note.milestone_id) == (project.id, milestone.id)
assert note.body == "From the tar, not the JSON."
assert note.status == "todo"
# The Profile page travels with its user (#5879); a field left blank stays
# The Profile page travels with its user; a field left blank stays
# blank rather than restoring as the page's default.
assert (profile.display_name, profile.tone) == ("Drill Owner", "technical")
assert profile.interests == ["backups"]
@@ -1,4 +1,4 @@
"""Real-Postgres round trip for rule_versions (milestone 323 step 1).
"""Real-Postgres round trip for rule_versions.
Two things here cannot be shown with mocks, and both are the kind that fail
QUIETLY — a restore reports success and hands back history that is wrong.
@@ -7,7 +7,7 @@ QUIETLY — a restore reports success and hands back history that is wrong.
A restore that writes it straight through succeeds and attaches every
snapshot to whatever rule happens to hold that number here — an edit
history filed against the wrong binding instruction, which is worse than
no history at all. This is #3182's `arose_from_id` trap on a new table.
no history at all. It is the same trap as a note's `arose_from_id`.
2. **A null actor does not drop the row.** `user_id` is SET NULL precisely so
history outlives the account that wrote it, so the restore deliberately
@@ -217,7 +217,7 @@ async def test_both_snapshots_come_back(restored):
async def test_the_history_attaches_to_the_RESTORED_rule(restored):
"""#3182's trap. The source rule still exists and holds a different id, so
"""The remap trap. The source rule still exists and holds a different id, so
a straight-through copy would file this history against it — or against
whatever unrelated rule owns that number."""
new_rule_id = restored["rule"].id
+3 -3
View File
@@ -1,4 +1,4 @@
"""The adoption ledger against real Postgres (milestone 463 step 4).
"""The adoption ledger against real Postgres.
What the unit lane can't show: an answer moves the row and logs exactly one
decision; an owed answer files its task in the OWING project, tagged to the
@@ -236,7 +236,7 @@ async def test_another_projects_answer_is_recorded_as_precedent(family):
assert first["decision"]["id"] in second["decision"]["precedent_ids"]
assert second["precedents"][0]["relation"] == "same idea, another project"
assert second["precedents"][0]["project_title"] == "android one"
# A precedent says its reason once: before/after are state only (#5131).
# A precedent says its reason once: before/after are state only.
assert second["precedents"][0]["reason"] == "the source app"
assert "reason" not in second["precedents"][0]["after"]
@@ -392,7 +392,7 @@ async def test_the_matrix_hides_what_the_caller_cannot_read(family):
assert all(i["note_id"] != family["note"] for i in matrix["ideas"])
# --- the shape ledger as evidence (step 5) -------------------------------------------
# --- the shape ledger as evidence ----------------------------------------------------
REPO = "git.example/android-two"
KOTLIN_BODY = (
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres checks for family canon's schema (milestone 463 step 1).
"""Real-Postgres checks for family canon's schema.
Two kinds of claim live here, and both need a database:
+4 -4
View File
@@ -1,4 +1,4 @@
"""The promotion engine against real Postgres (milestone 463 step 3).
"""The promotion engine against real Postgres.
What the unit lane can't show: a promotion opens the right ledger rows and
no others, a veto changes nothing but the log, an undo puts back exactly the
@@ -279,9 +279,9 @@ async def test_a_repeat_with_no_shared_platform_is_silent(family):
async def test_a_repeat_across_kinds_is_silent(family):
"""A snippet whose only match is a NOTE is not a repeat (#5130): a dated
release log scored 0.83 against a signed-APK CI snippet on shared
vocabulary and was opened as a candidate. The stub hands back the
"""A snippet whose only match is a NOTE is not a repeat: a dated
release log can score above the bar against a signed-APK CI snippet on
shared vocabulary alone, and would open as a candidate. The stub hands back the
cross-kind hit directly, so this pins the trigger's own check, not the
search filter in front of it."""
async with async_session() as s:
+2 -3
View File
@@ -1,7 +1,6 @@
"""A family idea reaches every project on its platform, and no other
(milestone 463 step 6).
"""A family idea reaches every project on its platform, and no other.
WHY THIS IS AN INTEGRATION TEST — the lesson-reach reasoning (#3730) holds
WHY THIS IS AN INTEGRATION TEST — the lesson-reach reasoning holds
here too: the reach is one `OR` inside the search's project filter, and only
the ROWS that come back prove it. Every note embeds identically to the query,
so scoping is the only thing that can separate them. The embedder is stubbed;
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres integration tests for the forge keyring (#2778).
"""Real-Postgres integration tests for the forge keyring.
Runs only in the CI integration lane (real Postgres, schema built by
`alembic upgrade head`, which includes migration 0078's forge_connections
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres tests for "recently touched" in the enter_project handshake (#4045).
"""Real-Postgres tests for "recently touched" in the enter_project handshake.
What a mock cannot show: that a work-log moves its task up the list even
though logging never bumps the task's updated_at, and that a milestone whose
+2 -2
View File
@@ -1,10 +1,10 @@
"""Real-Postgres integration tests for project inception (milestone 297).
"""Real-Postgres integration tests for project inception.
What mocks can't prove: a decision's effects land through the real services
(the design system points, the standard Systems seed once), the record is
written last, a bad target applies nothing.
Rules are not part of inception since milestone 414: a rule in a rulebook is
Rules are not part of inception: a rule in a rulebook is
global and applies to every project, so there is nothing to subscribe to.
"""
import pytest
+1 -1
View File
@@ -1,4 +1,4 @@
"""A lesson is reachable from a project it was not written on (step 3).
"""A lesson is reachable from a project it was not written on.
WHY THIS IS AN INTEGRATION TEST
+4 -5
View File
@@ -1,8 +1,7 @@
"""Real-Postgres tests for finding a plan by meaning (milestone 415, steps 3 and 4).
"""Real-Postgres tests for finding a plan by meaning.
A project's roadmap written as milestones was invisible to recall: `search`
covered notes, tasks and rules, so "is there already a plan for this?" had no
tool. What a mock cannot show is the join scoping the vectors to a project and
A project's roadmap is written as milestones, and `search` reaches them, so
"is there already a plan for this?" has a tool. What a mock cannot show is the join scoping the vectors to a project and
to what the caller may read, so these seed real milestones with hand-made
vectors and stub only the embedder.
"""
@@ -84,7 +83,7 @@ async def test_a_project_the_caller_cannot_read_returns_nothing(roadmap):
assert await _found(roadmap["stranger"]) == []
# ── the plan gate (step 4): start_planning finds the plan before making another ──
# ── the plan gate: start_planning finds the plan before making another ──
LONG = "Resolve works and editions against the metadata providers. " * 5
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres tests for an install's moment mappings (milestone 458 step 2).
"""Real-Postgres tests for an install's moment mappings.
What these pin is what the in-session correction promises the operator: a
mapping made is in force on the next call, a default switched off stays off,
+3 -3
View File
@@ -32,7 +32,7 @@ def _vec(*nonzero_first):
def _emb(note_id, user_id, chunk_index, vec):
"""A chunk row at the current chunker version (#280, migration 0077)."""
"""A chunk row at the current chunker version."""
from scribe.services.embeddings import CHUNKER_VERSION, EMBEDDING_MODEL
return NoteEmbedding(
@@ -63,7 +63,7 @@ async def seeded():
# query vector will be [1,0,0,...]; near ~ identical (sim≈1.0),
# far is orthogonal (sim≈0.0 -> filtered by the default threshold).
# near gets a SECOND, weaker chunk (sim≈0.6) — the collapse to
# best-chunk-per-note (#280) is under test: near must come back once,
# best-chunk-per-note is under test: near must come back once,
# at its best chunk's score, not twice.
s.add(_emb(near.id, user.id, 0, _vec(1.0)))
s.add(_emb(near.id, user.id, 1, _vec(0.6, 0.8)))
@@ -94,7 +94,7 @@ async def test_semantic_search_ranks_and_thresholds_via_pgvector(seeded):
assert near_id in ids
assert far_id not in ids
assert ids[0] == near_id
# Chunk collapse (#280): near has TWO chunk rows above the floor (sim≈1.0
# Chunk collapse: near has TWO chunk rows above the floor (sim≈1.0
# and ≈0.6) and must appear exactly once, at its best chunk's score.
assert ids.count(near_id) == 1
top_score = results[0][0]
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres tests for task placement (milestone 409 step 1).
"""Real-Postgres tests for task placement.
What a mock cannot show: that step order is creation order rather than update
order, that position and progress are computed over the steps the CALLER may
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres checks for platform membership (milestone 463 step 2).
"""Real-Postgres checks for platform membership.
The one invariant: detection only ever ADDS, and only where nobody has
answered. A refresh that overwrote a "no" would put a project back into a
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres proof that a batch create cannot be interfered with (#4016).
"""Real-Postgres proof that a batch create cannot be interfered with.
What mocks cannot show: that the ids a placeholder is rewritten to are the ids
the rows actually got, that two batches running at the same time each cite
@@ -1,6 +1,6 @@
"""Real-Postgres tests for moment proposals and judgments (milestone 458 step 7).
"""Real-Postgres tests for moment proposals and judgments.
What the step promises, against the real tables:
What the feature promises, against the real tables:
- the pass reads only rules nobody has answered for, and any answer — a
proposal, a mount, "no moment fits" — takes a rule off that list;
- a proposal mounts nothing; a confirm mounts, a reject is kept, and a judged
@@ -8,7 +8,7 @@ What the step promises, against the real tables:
- an edit that takes a moment off a rule is a rejection the signal respects;
- the signal counts SESSIONS, not opens, and asks once the bar is crossed;
- only the owner can propose, judge, or feed the signal;
- the misfire (step 7b): only a mount can misfire, reports count DAYS, the
- the misfire: only a mount can misfire, reports count DAYS, the
bar makes an unmount proposal, reject takes it off, confirm keeps it and
stops the asking, and a re-mount starts the count again.
"""
@@ -196,7 +196,7 @@ async def test_the_open_resolves_acts_through_the_installs_mappings(world):
assert (await _row(done.id, "work.deliver")).source == "signal"
# ── The misfire (step 7b) ────────────────────────────────────────────────────
# ── The misfire ──────────────────────────────────────────────────────────────
@pytest.fixture
+2 -2
View File
@@ -1,4 +1,4 @@
"""Real-Postgres tests for moving a rule between homes (milestone 414, step 3).
"""Real-Postgres tests for moving a rule between homes.
A rule's home is its reach: a rulebook topic makes it global, a project makes
it that project's. The alternative to a move — recreate the rule in the other
@@ -84,7 +84,7 @@ async def test_a_project_rule_becomes_global_and_keeps_everything(homes):
async def test_a_move_writes_no_version(homes):
"""A version records what a rule SAID (milestone 323, decision 4). A move
"""A version records what a rule SAID. A move
changes where it binds, not a word of it."""
before = len(await rv_svc.list_versions(homes["rule"]))
await rulebooks_svc.move_rule(homes["rule"], homes["owner"], topic_id=homes["topic"])
+2 -2
View File
@@ -1,4 +1,4 @@
"""Real-Postgres tests for a rule's CHECK — the write half (milestone 312).
"""Real-Postgres tests for a rule's CHECK — the write half.
What mocks cannot prove, and what the staleness sweep depends on:
@@ -132,7 +132,7 @@ async def test_editing_anything_else_leaves_the_stamp_alone(constraint):
assert row.why.startswith("act_runner picks the shell")
# ── the sweep itself (step 3) ──────────────────────────────────────────
# ── the sweep itself ───────────────────────────────────────────────────
@pytest_asyncio.fixture
async def rulebook_of_three():
+6 -6
View File
@@ -1,14 +1,14 @@
"""Real-Postgres crowd tests: an in-scope record is found however many nearer
records sit outside the scope (#4961; the rule search's is #4958's, in
records sit outside the scope (the rule search's crowd test is in
test_integration_rule_scope).
Ordered straight off a `*_embeddings` table, the planner can walk the HNSW
index, which takes ~`hnsw.ef_search` (40) nearest chunks from every owner and
project and only then applies the scope. Each test here puts 80 out-of-scope
records nearer the query than the reader's one in-scope record, so the old
shape could not reach it when the index served the order. What a test cannot
force is the plan — the shape guard in test_search_scope_shape is what fails
on the old query whatever Postgres picks.
records nearer the query than the reader's one in-scope record, so a query
that scopes after the index walk cannot reach it when the index serves the
order. What a test cannot force is the plan — the shape guard in
test_search_scope_shape is what fails on that query whatever Postgres picks.
Rows are inserted directly with hand-made vectors and the embedder is stubbed,
so the scope alone decides what comes back.
@@ -57,7 +57,7 @@ def _chunk(model, key: str, record_id: int, vec: list[float], **extra):
async def _found(search, user_id: int, **scope) -> set[int]:
"""The ids a search finds. Every search fails open, so a query that raised
reads as an empty result — the swallowed traceback is captured and named
in the failure instead (#4958)."""
in the failure instead."""
raised: list[str] = []
with patch("scribe.services.embeddings.get_embedding",
AsyncMock(return_value=QUERY_VEC)), \
+1 -1
View File
@@ -1,4 +1,4 @@
"""Real-Postgres integration test for the snippet location reverse lookup (#2083).
"""Real-Postgres integration test for the snippet location reverse lookup.
Runs only in the CI integration lane (real Postgres, schema built by
`alembic upgrade head`, which includes migration 0070's `notes.data` + GIN index).
+5 -6
View File
@@ -1,6 +1,6 @@
"""Real-Postgres test that the CHECK actually accepts 'spike' (0091).
Rule 36 exists because the value and the constraint can drift apart: the
The value and the constraint can 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 its own real-DB test,
@@ -68,19 +68,18 @@ async def test_an_unknown_kind_is_still_refused(owner_id):
Without this, every assertion above would pass just as happily against a
table whose CHECK had been dropped and never re-added — which is exactly
the failure rule 36 is written against.
the drift this file is written against.
"""
with pytest.raises(IntegrityError):
await _write(owner_id, "investigation")
async def test_a_kind_can_be_corrected_after_the_fact(owner_id):
"""The write-once bug, asserted against a real column (#3129).
"""A kind is not write-once, asserted against a real column.
The read-back is the whole test. A version that only asserted the update
call succeeded would have passed against the broken code — the route
returned 200 while dropping the field, which is exactly how this survived
long enough to be found by hand.
call succeeded would pass against a route that returns 200 while dropping
the field, which is a failure nothing else reports.
"""
from scribe.services import notes as notes_svc
+8 -9
View File
@@ -3,14 +3,14 @@
The facet predicate is written twice by necessity — as SQL for rows the
database hands back, and as Python for candidates the vector search has
already fetched — plus a third time as the `is_task` pre-filter that narrows
the candidate set before it exists. Those three used to be hand-written and
agreed only by luck: adding `issue` to the SQL arm alone would have set the
pre-filter to is_task=False, handed the Python arm a candidate set with no
tasks in it, and returned an empty semantic half for the Issues facet forever
with nothing red anywhere (#3128).
the candidate set before it exists. Hand-written, those three
agree only by luck: adding `issue` to the SQL arm alone would set the
pre-filter to is_task=False, hand the Python arm a candidate set with no
tasks in it, and return an empty semantic half for the Issues facet forever
with nothing red anywhere.
They are now generated from one table. These tests pin the property that made
the trap possible, so it cannot come back by a different route.
So they are generated from one table, and these tests pin the property that
makes the trap possible, so it cannot come back by a different route.
"""
import pytest
@@ -104,8 +104,7 @@ def test_the_live_task_kinds_are_all_facets():
def test_non_task_facets_are_the_note_types_and_only_those():
"""Spelled out rather than derived, so adding a kind to `_FACETS` has to
be a deliberate edit in two places. `lesson` joined in milestone 385 step
2 (#3729) and is non-task on purpose: `is_task` IS `status is not None`,
be a deliberate edit in two places. `lesson` is non-task on purpose: `is_task` IS `status is not None`,
so a lesson that acquired a status would stop being a lesson."""
assert set(NON_TASK_FACETS) == {"note", "process", "snippet", "lesson"}