docs(6209): test prose in the present tense — "used to", "previously", "before this" restated as the failure shape
Twenty-eight test files described the code they guard by what it did before a fix. Each is now the property and the failure it prevents, in the present tense: the backfill snapshot, the per-dial calibration query, the rule scope, the lesson trigger filter, the subshell global, the trash owner scope, the version readout fallback, and others. A docstring line that narrated the backup suite's own history is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
"""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
|
||||
takes minutes, so anything edited inside that window was re-embedded by its own
|
||||
write path and then OVERWRITTEN with the pre-edit text the scan had captured —
|
||||
and stamped at the current chunker version, so the next boot considered it
|
||||
current and never repaired it. The record stayed findable by what it used to
|
||||
say, silently, until someone happened to edit it again.
|
||||
The failure these pin: a backfill that SELECTs the text alongside the id and
|
||||
then iterates that snapshot, sleeping between records. A run over a real corpus
|
||||
takes minutes, so anything edited inside that window is re-embedded by its own
|
||||
write path and then OVERWRITTEN with the pre-edit text the scan captured — and
|
||||
stamped at the current chunker version, so the next boot considers it current
|
||||
and never repairs it. The record stays findable by what it used to say,
|
||||
silently, until someone happens to edit it again.
|
||||
|
||||
That window is exactly "forward work continues while a long backfill runs",
|
||||
which is the normal condition, not an unlucky one.
|
||||
|
||||
@@ -195,8 +195,8 @@ async def test_a_dial_is_read_per_dial_not_from_the_newest_two_rows():
|
||||
|
||||
"The newest two rows" and "the newest row of each dial" differ the moment
|
||||
one dial moves more often than the other — which is the normal case, since
|
||||
floors get walked and budgets rarely do. Before this was one query per dial,
|
||||
a surface with three floor changes and one budget change reported the budget
|
||||
floors get walked and budgets rarely do. Without a query per dial, a
|
||||
surface with three floor changes and one budget change reports the budget
|
||||
as untouched: a WRONG calibration answer rather than a missing one, and
|
||||
wrong in the direction a reader would not think to check.
|
||||
"""
|
||||
|
||||
@@ -171,7 +171,7 @@ def _assert_nothing_thin_and_nothing_lost(title: str, body: str) -> list[str]:
|
||||
|
||||
|
||||
def test_a_heading_over_one_long_paragraph_is_not_a_chunk_of_its_own():
|
||||
"""A work log whose entry is one long paragraph: the split used to flush
|
||||
"""A work log whose entry is one long paragraph: the split must not flush
|
||||
the heading as its own piece before cutting the paragraph."""
|
||||
entry = " ".join(["The deploy was verified against the live instance."] * 40)
|
||||
_assert_nothing_thin_and_nothing_lost(
|
||||
|
||||
@@ -8,10 +8,10 @@ choice is the only moment a correction is cheap. The tool docstring IS the
|
||||
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
|
||||
carried a real disambiguator, and `create_note` — far and away the
|
||||
highest-volume surface — carried none. Guidance sat in the rarest tool and
|
||||
was missing from the most common one.
|
||||
The gap tends to be lopsided: `create_rule` and `start_planning` are where a
|
||||
disambiguator gets written first, while `create_note` — far and away the
|
||||
highest-volume surface — is where it matters most. Guidance that sits only in
|
||||
the rarest tools misses the most common one.
|
||||
|
||||
WHAT THIS PINS, AND WHAT IT DOES NOT
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ resolves is indistinguishable from work still in progress. The practice names
|
||||
clients, a bare `await` on a stream — supplying the deadline is part of
|
||||
using it, not a hardening pass for later.
|
||||
|
||||
Before this guard, no request in the app carried one.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ RESTORED_USERNAME = "backup_roundtrip_restored"
|
||||
@pytest_asyncio.fixture
|
||||
async def source():
|
||||
"""A snippet, a process, and an issue that arose from a work task — the
|
||||
shapes whose identity the backup used to drop — exported as backup rows.
|
||||
shapes whose identity a backup can drop — exported as backup rows.
|
||||
|
||||
Deleted children-first: `arose_from_id` is a real FK, so removing the
|
||||
origin while the issue still points at it is asking the database a
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""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
|
||||
injected into every other project's sessions. What a mock cannot show is the
|
||||
A rule lives in a rulebook topic (global) or on one project. Retrieval that
|
||||
ignores the home and searches every rule the user owns injects each project's
|
||||
rules into every other project's sessions. What a mock cannot show is the
|
||||
join doing the scoping: these seed real rules with hand-made vectors and stub
|
||||
only the embedder, so every rule is an equally good match and the home alone
|
||||
decides what comes back.
|
||||
|
||||
@@ -121,11 +121,11 @@ async def test_the_repeat_is_counted_not_reported_as_a_result():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_call_where_everything_was_already_seen_is_readable():
|
||||
"""The fact that could not be expressed before.
|
||||
"""A call where every hit was already seen is not a call nothing cleared.
|
||||
|
||||
Previously this call logged zero results with no suppression count, so it
|
||||
was indistinguishable from a bar nothing cleared — and the operator tuning
|
||||
that bar would have been reading the wrong number.
|
||||
Logged as zero results with no suppression count, it would be
|
||||
indistinguishable from a bar nothing cleared — and the operator tuning
|
||||
that bar would be reading the wrong number.
|
||||
"""
|
||||
rec = MagicMock()
|
||||
hits = [(0.80, fake_note(id=11, title="Pool sizing decision", user_id=1))]
|
||||
|
||||
@@ -15,13 +15,13 @@ worth having wherever a record is stored composed and edited in parts: not
|
||||
"does the strip remove the marker" — it did — but "does an edit that changes
|
||||
nothing change nothing".
|
||||
|
||||
WHAT WENT WRONG. The derivation used to filter out body LINES matching the
|
||||
trigger marker's pattern, which is `re.M` and therefore matches one line.
|
||||
`when_to_apply` is free text and is routinely several paragraphs; the
|
||||
composer writes all of it after the marker, so paragraphs 2..n were ordinary
|
||||
lines that the filter had no reason to touch. They came back as "the
|
||||
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
|
||||
WHAT GOES WRONG. A derivation that filters out body LINES matching the
|
||||
trigger marker's pattern, which is `re.M` and therefore matches one line,
|
||||
leaves the rest of the trigger behind. `when_to_apply` is free text and is
|
||||
routinely several paragraphs; the composer writes all of it after the marker,
|
||||
so paragraphs 2..n are ordinary lines that the filter has no reason to touch.
|
||||
They come back as "the insight", the full trigger is prepended again, and the
|
||||
stored lesson gains (n-1) paragraphs per update — silently, compounding, and in the half of the
|
||||
document that does the retrieving.
|
||||
|
||||
On a three-paragraph trigger the body grows by the length of paragraphs 2 and
|
||||
@@ -180,7 +180,7 @@ def test_the_insight_is_derived_from_the_row_not_from_the_body_alone():
|
||||
"""Why the function takes a note.
|
||||
|
||||
The body on its own does not say where the trigger ends — that is the
|
||||
information the old shape did not have and guessed at. The mirror in
|
||||
information a line-wise filter does not have and guesses at. The mirror in
|
||||
`data` is where it comes from, so a caller holding only the body string
|
||||
cannot be served, and this pins that the argument is the record."""
|
||||
note = _stored(MANY_PARAGRAPHS, INSIGHT, [])
|
||||
|
||||
@@ -16,11 +16,10 @@ a real promise rather than a shape coincidence.
|
||||
|
||||
THE CONTRACT: a `limit` without an `offset` is a truncation with no
|
||||
continuation. The caller is told there are 250 results and handed 50, with no
|
||||
way to ask for the rest. Both tools that had this were capped over a service
|
||||
that already accepted an offset — `snippets_svc.list_snippets(offset=0)` was
|
||||
simply not exposed, and `list_processes` passed a hardcoded `offset=0` into
|
||||
`query_knowledge`. The capability existed one layer down in both cases; only
|
||||
the door was missing.
|
||||
way to ask for the rest. The usual cause is a tool capped over a service that
|
||||
already accepts an offset — the service's `offset` simply not exposed, or a
|
||||
hardcoded `offset=0` passed into `query_knowledge`. The capability exists one
|
||||
layer down; only the door is missing.
|
||||
|
||||
As in `test_mcp_auth`, the CANDIDATES are derived and the DECISION is explicit.
|
||||
Deriving the exemption too would make the contract follow a naming convention,
|
||||
|
||||
@@ -363,9 +363,9 @@ async def test_enter_project_fires_the_coverage_seed_on_the_owner(
|
||||
@pytest.mark.asyncio
|
||||
async def test_enter_project_hands_back_the_design_system_when_the_project_has_one():
|
||||
"""The handshake is where an agent learns what binds it, and a design
|
||||
system binds the same way a rule does. Before this it was reachable only by
|
||||
an agent that already knew to call resolve_design_system — so the standards
|
||||
were present in the store and absent from the work."""
|
||||
system binds the same way a rule does. Reachable only by an agent that
|
||||
already knows to call resolve_design_system, the standards would be present
|
||||
in the store and absent from the work."""
|
||||
p = fake_project(id=5, design_system_id=9)
|
||||
design = {"id": 9, "title": "App kit", "guidance": [{"title": "House"}],
|
||||
"token_count": 95, "token_groups": ["surface"],
|
||||
|
||||
@@ -168,8 +168,8 @@ async def test_fable_search_content_type_filters_at_service_layer():
|
||||
async def test_each_specific_kind_reaches_the_engine_filter_it_names(
|
||||
content_type, expected
|
||||
):
|
||||
"""`note_type` and `task_kind` were parameters of the search all along —
|
||||
what was missing was a way to ask for them from the agent's door. Asking
|
||||
"""`note_type` and `task_kind` are parameters of the search, and the
|
||||
agent's door passes them through. Asking
|
||||
for a snippet must narrow to snippets, not merely to non-tasks."""
|
||||
_user_id_ctx.set(7)
|
||||
mock_search = AsyncMock(return_value=[])
|
||||
|
||||
@@ -134,7 +134,7 @@ async def _enter(*stubs):
|
||||
async def test_enter_project_stays_small_however_large_the_project():
|
||||
"""The ceiling is the point: a long-lived project's handshake must not
|
||||
grow with its history. 200 milestones with 5k-character plans, 60 project
|
||||
rules and 40 Systems would be well over 1M characters in the old shape."""
|
||||
rules and 40 Systems would be well over 1M characters carried whole."""
|
||||
project = fake_project(id=5, design_system_id=9, goal=GOAL,
|
||||
description="Background. " * 300)
|
||||
rules = {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
Covers the payload shaping and the two contracts that make this safe to leave in
|
||||
the hot path: telemetry never raises, and telemetry never blocks. Plus the thing
|
||||
the feature exists for — that the write-path PLACE arm is now recorded, since
|
||||
before this it surfaced snippets while leaving no trace anywhere.
|
||||
the feature exists for — that the write-path PLACE arm is recorded, so a
|
||||
snippet it surfaces leaves a trace.
|
||||
"""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ async def test_update_preference_forwards_what_taught_the_change():
|
||||
|
||||
def test_the_rule_door_still_asks_before_writing():
|
||||
"""Half one of the asymmetry. If this fails, the gate was tidied away and
|
||||
preferences are no longer the exception — they are just the same thing.
|
||||
preferences are not the exception — they are just the same thing.
|
||||
"""
|
||||
doc = _doc(MODULE, "create_rule").lower()
|
||||
asks = ("approve", "propose", "ask", "question")
|
||||
|
||||
@@ -70,8 +70,8 @@ def test_the_reply_carries_a_length_discipline():
|
||||
def test_the_reply_gets_a_second_pass_for_what_can_go():
|
||||
"""Cutting is a separate act from writing, and needs saying separately.
|
||||
|
||||
The pre-existing check asked whether anything was MISSING, which a bloated
|
||||
reply passes.
|
||||
A check that asks only whether anything is MISSING is one a bloated reply
|
||||
passes.
|
||||
"""
|
||||
text = _flat()
|
||||
assert "what can go" in text
|
||||
|
||||
@@ -134,9 +134,8 @@ async def test_an_unknown_surface_is_refused_before_anything_is_written():
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("dial", ["threshold", "limit", "", "FLOOR"])
|
||||
async def test_an_unknown_dial_is_refused(dial):
|
||||
"""The near-misses are the old vocabulary — `threshold` and `limit` are what
|
||||
these were called before this step, so they are exactly what a stale caller
|
||||
will send, and a silent no-op there would be indistinguishable from a
|
||||
"""The near-misses are the generic vocabulary — `threshold` and `limit` are
|
||||
what a caller that has not read the dial names will send, and a silent no-op there would be indistinguishable from a
|
||||
change that did not take."""
|
||||
session, ctx = _patches()
|
||||
with ctx[0], ctx[1], ctx[2], ctx[3], pytest.raises(ValueError):
|
||||
|
||||
@@ -446,9 +446,9 @@ async def test_the_prompt_arm_retrieves_against_what_the_operator_SAID():
|
||||
Both other arms are keyed on an act — a file write, a command. A rule that
|
||||
governs what to SAY has no act in front of it: extract intent from loose
|
||||
phrasing, raise a conflict before acting, end a finding with an offer all
|
||||
bind on a response. Before this, the operator's message reached only
|
||||
`semantic_search_notes`, so no rule had ever been retrieved against a
|
||||
thing the operator actually said.
|
||||
bind on a response. If the operator's message reached only
|
||||
`semantic_search_notes`, no rule would ever be retrieved against a thing
|
||||
the operator actually said.
|
||||
"""
|
||||
rec = MagicMock()
|
||||
search = AsyncMock(return_value=[(0.79, fake_rule(
|
||||
|
||||
@@ -119,13 +119,13 @@ def test_an_unusable_marker_is_refused_rather_than_sent(tmp_path, body):
|
||||
|
||||
|
||||
def test_the_reason_survives_the_command_substitution_that_fetches_it(tmp_path):
|
||||
"""Regression on a bug this nearly shipped with.
|
||||
"""The reason cannot be a global the function sets.
|
||||
|
||||
The reason used to be a global the function set, which every caller reads
|
||||
through `$( )` — a subshell, so the assignment died with it and the caller
|
||||
saw an UNSET variable. The hooks all run `set -u`, where reading one aborts
|
||||
the script: a directory with a misaddressed marker would have cost the
|
||||
session its entire SessionStart context, to fetch a warning about a file.
|
||||
Every caller reads the function through `$( )` — a subshell, so a global
|
||||
assignment dies with it and the caller sees an UNSET variable. The hooks all
|
||||
run `set -u`, where reading one aborts the script: a directory with a
|
||||
misaddressed marker would cost the session its entire SessionStart context,
|
||||
to fetch a warning about a file.
|
||||
|
||||
So the reason comes back through stdout with the id, and this asserts on
|
||||
the shape the caller actually uses: read in a subshell, split, and used
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
Ordered straight off a `*_embeddings` table, the planner walks the HNSW index:
|
||||
it takes ~`hnsw.ef_search` (40) nearest chunks from every owner and project
|
||||
and filters them afterwards, so an in-scope record behind 40 nearer ones the
|
||||
caller cannot see is silently dropped. The fix is one shape, `_rank_scoped`,
|
||||
and these pin that every search goes through it — the rule search was fixed
|
||||
alone first, and the other three kept the fault because nothing said they
|
||||
were the same query.
|
||||
caller cannot see is silently dropped. The remedy is one shape, `_rank_scoped`,
|
||||
and these pin that every search goes through it — fixed in one search alone,
|
||||
the others keep the fault, because nothing says they are the same query.
|
||||
|
||||
The integration lane's crowd tests show the behaviour on a real index; these
|
||||
are the guard that fails on the old shape whatever plan Postgres picks.
|
||||
are the guard that fails on a rank-then-scope query whatever plan Postgres
|
||||
picks.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -54,7 +54,7 @@ def test_every_semantic_search_ranks_through_the_scoped_shape():
|
||||
calls = _calls(fn)
|
||||
assert "_rank_scoped" in calls, f"{name} does not rank through _rank_scoped"
|
||||
assert "_scoped_chunks" in calls, f"{name} does not build its scope as chunks"
|
||||
# Its own ORDER BY is the old shape: a distance ordered on the
|
||||
# Its own ORDER BY is the rank-then-scope shape: a distance ordered on the
|
||||
# embedding table, which the index serves before the scope applies.
|
||||
assert "order_by" not in calls, f"{name} orders by distance itself"
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ need only a stand-in and a builder.
|
||||
|
||||
The full FK-remapping round-trip needs real Postgres and lives in the
|
||||
`test_integration_backup_*_roundtrip.py` modules, which drive the real
|
||||
`restore_full_backup`. That sentence used to read "it is not written yet";
|
||||
four of those files exist now, the newest covering the three `code_shapes`
|
||||
columns the import guard below found missing.
|
||||
`restore_full_backup`.
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
@@ -268,18 +266,16 @@ def test_every_column_is_exported_or_declared_excluded(table):
|
||||
"""THE COLUMN GUARD — _NOT_INCLUDED's shape, one level down.
|
||||
|
||||
The table guard below catches a whole table going missing. It cannot catch
|
||||
a COLUMN going missing from a table it already considers covered, which is
|
||||
how nine of them vanished from `notes` alone: note_type and task_kind, so
|
||||
every snippet and process restored as a plain note and every issue and
|
||||
spike as `work`; arose_from_id, so every provenance edge went; the
|
||||
recurrence pair, so recurring tasks stopped recurring. Plus milestones.body
|
||||
— which IS the plan — and repo_bindings.ref.
|
||||
a COLUMN going missing from a table it already considers covered, and the
|
||||
cost is large: without note_type and task_kind every snippet and process
|
||||
restores as a plain note and every issue and spike as `work`; without
|
||||
arose_from_id every provenance edge goes; without the recurrence pair
|
||||
recurring tasks stop recurring.
|
||||
|
||||
Each arrived the same way: added to the model and the migration, both of
|
||||
which fail loudly, and never to the serialiser, which fails silently.
|
||||
|
||||
A new column must now be exported or named in _COLUMN_EXCLUSIONS with a
|
||||
reason. Forgetting is no longer expressible.
|
||||
A column arrives in the model and the migration, both of which fail loudly,
|
||||
and is easy to leave out of the serialiser, which fails silently. So a new
|
||||
column must be exported or named in _COLUMN_EXCLUSIONS with a reason, and
|
||||
forgetting is not expressible.
|
||||
"""
|
||||
model, helper = _column_guard_targets()[table]
|
||||
[row] = helper([_stand_in(model)])
|
||||
@@ -536,8 +532,7 @@ def test_every_table_is_either_backed_up_or_explicitly_excluded():
|
||||
)
|
||||
|
||||
# And the reverse: a name in either list that no longer exists is a lie the
|
||||
# guard would otherwise keep telling. This half is what caught "embeddings",
|
||||
# "invitations" and "password_resets" — three entries that named nothing.
|
||||
# guard would otherwise keep telling.
|
||||
phantom = accounted - schema
|
||||
assert not phantom, (
|
||||
f"backup lists table(s) that are not in the schema: {sorted(phantom)}. "
|
||||
|
||||
@@ -86,9 +86,8 @@ async def test_build_autoinject_hint_titles_only_with_margin_gate():
|
||||
assert "get_note(id)" in out["context"]
|
||||
assert "↳" not in out["context"]
|
||||
# Telemetry fired for BOTH retrievals this path runs: the scored menu and
|
||||
# the reuse-slot query competing against it. The slot's query used to be
|
||||
# the one unlogged retrieval on this path — the hit it displaced was in
|
||||
# retrieval_logs, and the query that displaced it must be too.
|
||||
# the reuse-slot query competing against it. The hit the slot displaces is
|
||||
# in retrieval_logs, so the query that displaced it must be too.
|
||||
sources = [c.kwargs["source"] for c in rec.call_args_list]
|
||||
assert sources == ["auto_inject", "reuse_slot", "lesson_slot"]
|
||||
|
||||
@@ -235,9 +234,9 @@ async def test_a_project_id_that_does_not_resolve_is_reported_not_dropped():
|
||||
another instance, or names a deleted project, the session has to be told —
|
||||
it cannot infer it from an absence.
|
||||
|
||||
This used to render nothing whatsoever: the branch hung off `if project_id`
|
||||
as an `elif`, so an id that was sent and failed took the outer arm, found
|
||||
no project, and fell out of the block having said nothing at all.
|
||||
The trap is a branch hung off `if project_id` as an `elif`: an id that was
|
||||
sent and failed takes the outer arm, finds no project, and falls out of the
|
||||
block having said nothing at all.
|
||||
"""
|
||||
from scribe.services.plugin_context import build_session_context
|
||||
with patch("scribe.services.plugin_context.projects_svc.get_project",
|
||||
|
||||
@@ -929,10 +929,10 @@ async def test_a_section_is_complete_only_from_its_latest_contributor(
|
||||
def test_a_call_that_returned_nothing_still_records_what_it_nearly_showed():
|
||||
"""The whole point, at the payload grain.
|
||||
|
||||
This is the row a threshold is tuned from and the one that used to carry no
|
||||
score at all: `top_score` and `min_score` are both null here, correctly, and
|
||||
a reader was left unable to tell a bar rejecting 0.71s from one rejecting
|
||||
0.30s. Both render as a zero-result call.
|
||||
This is the row a threshold is tuned from: `top_score` and `min_score` are
|
||||
both null here, correctly, and without the near-miss a reader cannot tell a
|
||||
bar rejecting 0.71s from one rejecting 0.30s. Both render as a zero-result
|
||||
call.
|
||||
"""
|
||||
p = _build_payload(
|
||||
user_id=1, source="pre_tool_rule", query="git push --force",
|
||||
|
||||
@@ -82,8 +82,8 @@ def test_parse_does_not_promote_a_caller_tag_to_language():
|
||||
|
||||
|
||||
def test_caller_tag_survives_an_update_round_trip_without_a_language():
|
||||
# Regression: the tag used to be read back as the language, then dropped
|
||||
# from the extra-tag set on re-compose — so it silently disappeared.
|
||||
# A tag read back as the language would be dropped from the extra-tag set
|
||||
# on re-compose — and silently disappear.
|
||||
tags = s.compose_tags("", ["auth"])
|
||||
fields = s.parse_snippet_fields("n — u", s.compose_body(code="x = 1"), tags)
|
||||
extra = [t for t in tags if t not in (s.SNIPPET_TAG, fields["language"])]
|
||||
|
||||
@@ -124,8 +124,8 @@ async def test_purge_expired_deletes_across_models_when_positive():
|
||||
|
||||
|
||||
def test_owner_clause_scopes_every_model():
|
||||
"""Regression: every trash op must owner-scope, including topics/rules
|
||||
which previously had NO owner check (IDOR across tenants)."""
|
||||
"""Every trash op must owner-scope, topics and rules included — a model
|
||||
with no owner check is an IDOR across tenants."""
|
||||
from scribe.services.trash import _owner_clause, _MODEL_FOR
|
||||
from scribe.models.note import Note
|
||||
from scribe.models.rulebook import Rulebook, RulebookTopic, Rule
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Editing a shared record: an editor grant is enough, a viewer grant is not.
|
||||
|
||||
The agent and web paths used to disagree — and worse, within the agent path
|
||||
`delete_snippet` honoured editor shares while `update_snippet` did not, so a
|
||||
colleague's snippet could be destroyed through an agent but not improved. Both
|
||||
now resolve the read scope and then require WRITE, which is what the sharing UI
|
||||
The agent and web paths must agree, and so must the doors within one path: a
|
||||
`delete_snippet` that honours editor shares beside an `update_snippet` that
|
||||
does not lets a colleague's snippet be destroyed through an agent but not
|
||||
improved. Every door resolves the read scope and then requires WRITE, which is what the sharing UI
|
||||
already promises: viewer / editor / admin are distinct grants.
|
||||
|
||||
A record readable-but-not-writable raises PermissionError rather than reporting
|
||||
|
||||
@@ -33,11 +33,11 @@ def test_something_actually_reads_the_version_endpoint():
|
||||
|
||||
|
||||
def test_the_footer_does_not_default_to_a_plausible_version():
|
||||
"""The regression this readout was built to remove.
|
||||
"""A failed fetch does not render a plausible version.
|
||||
|
||||
`appVersion` used to start life as the literal `"dev"` and the fetch
|
||||
swallowed its own failure, so an instance that could not answer rendered
|
||||
exactly what a healthy local build renders. Two very different states, one
|
||||
If `appVersion` starts life as the literal `"dev"` and the fetch swallows
|
||||
its own failure, an instance that cannot answer renders exactly what a
|
||||
healthy local build renders. Two very different states, one
|
||||
string, and no way to tell them apart from the page.
|
||||
|
||||
Pinned as "the ref does not start at a version-shaped literal" rather than
|
||||
|
||||
@@ -1342,10 +1342,10 @@ async def test_a_pulled_snippet_already_seen_is_evidence_not_menu():
|
||||
# ...and the menu carries it, marked.
|
||||
seven = next(line for line in out["context"].splitlines() if "#7" in line)
|
||||
assert "seen" in seven
|
||||
# #8 (0.80) now falls OUTSIDE the margin band, because #7 (0.91) anchors it
|
||||
# #8 (0.80) falls OUTSIDE the margin band, because #7 (0.91) anchors it
|
||||
# at 0.81 instead of being filtered out first. That is the point rather than
|
||||
# a casualty: the band measures distance from the best answer, and the best
|
||||
# answer here is #7 — suppressing it used to promote a materially weaker hit
|
||||
# answer here is #7 — suppressing it would promote a materially weaker hit
|
||||
# into a slot it had not earned, with nothing in the output saying so.
|
||||
assert out["note_ids"] == [7]
|
||||
# The stamp saw the pull and the resemblance score for this payload.
|
||||
|
||||
Reference in New Issue
Block a user