From fa4f88747e2c0412f06631a3ddc3572ac782596e Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 21 Oct 2026 20:34:24 -0400 Subject: [PATCH] docs(6205): the remaining services files state the reason, not the instance that taught it The 1-2 hit tail: access, background, backup, backup_job, canonical_systems, design_stylesheet, forge_connections, moment_actions, notes, notifications, placement, planning, platforms, projects, record_batch, recurrence, report_check, retrieval_review, rule_scope, rule_versions, scheduler, shape_check, supersession, system_rulings, system_usage, text, trash, verification, version_pinning. Also corrected while reading: - backup.py said restore step 16 fills the System map; step 15 does. - version_pinning.py pointed at a design doc that is not in the repo. Co-Authored-By: Claude Opus 5.5 --- src/scribe/services/access.py | 2 +- src/scribe/services/background.py | 10 +++++----- src/scribe/services/backup.py | 2 +- src/scribe/services/backup_job.py | 6 +++--- src/scribe/services/canonical_systems.py | 6 +++--- src/scribe/services/design_stylesheet.py | 6 +++--- src/scribe/services/forge_connections.py | 2 +- src/scribe/services/moment_actions.py | 4 ++-- src/scribe/services/notes.py | 2 +- src/scribe/services/notifications.py | 9 ++++----- src/scribe/services/placement.py | 9 ++++----- src/scribe/services/planning.py | 2 +- src/scribe/services/platforms.py | 4 ++-- src/scribe/services/projects.py | 21 +++++++++------------ src/scribe/services/record_batch.py | 9 ++++----- src/scribe/services/recurrence.py | 3 +-- src/scribe/services/report_check.py | 13 +++++-------- src/scribe/services/retrieval_review.py | 6 +++--- src/scribe/services/rule_scope.py | 7 +++---- src/scribe/services/rule_versions.py | 2 +- src/scribe/services/scheduler.py | 4 ++-- src/scribe/services/shape_check.py | 17 ++++++++--------- src/scribe/services/supersession.py | 6 +++--- src/scribe/services/system_rulings.py | 4 ++-- src/scribe/services/system_usage.py | 5 ++--- src/scribe/services/text.py | 8 +++----- src/scribe/services/trash.py | 2 +- src/scribe/services/verification.py | 9 ++++----- src/scribe/services/version_pinning.py | 2 -- 29 files changed, 82 insertions(+), 100 deletions(-) diff --git a/src/scribe/services/access.py b/src/scribe/services/access.py index 31f8cc87..5bad6094 100644 --- a/src/scribe/services/access.py +++ b/src/scribe/services/access.py @@ -260,7 +260,7 @@ async def can_write_design_system(user_id: int, design_system_id: int) -> bool: # --------------------------------------------------------------------------- # Set-based visibility (for LIST queries) # -# Two scopes, deliberately different (decision note 2094): +# Two scopes, deliberately different: # # readable_* — everything the ACL permits, including a record reachable ONLY # through a direct or group note share. For EXPLICIT acts: a diff --git a/src/scribe/services/background.py b/src/scribe/services/background.py index eb80c28f..60779d7e 100644 --- a/src/scribe/services/background.py +++ b/src/scribe/services/background.py @@ -2,7 +2,7 @@ The event loop holds only a WEAK reference to a task, so a bare ``create_task`` with no other holder can be garbage-collected mid-flight — a -write that never errors and never lands (the #2663 GC footgun). This module is +write that never errors and never lands. This module is the one place that gets the pattern right: strong references in ``_pending``, discarded on completion, with failures logged at WARNING instead of vanishing. @@ -36,10 +36,10 @@ async def report_telemetry_failure(subsystem: str, site: str) -> None: WARNING to the process log every time; one AppLog error row per process per (subsystem, site) so the admin UI shows the outage without host access. - THIS IS NOT DECORATION. #2663 is the record of a telemetry subsystem running - at zero for weeks — every counter reading empty, indistinguishable from - "nobody uses this" — because every failure went to ``logger.debug``. A - subsystem whose failures are all invisible cannot report its own death. + THIS IS NOT DECORATION. A telemetry subsystem whose every failure goes to + ``logger.debug`` can run at zero — every counter reading empty, + indistinguishable from "nobody uses this". A subsystem whose failures are + all invisible cannot report its own death. The AppLog write is itself guarded: when the database is down it fails too, and that is fine. The WARNING already said so, and a canary must never take diff --git a/src/scribe/services/backup.py b/src/scribe/services/backup.py index c0db8758..bfe5bad5 100644 --- a/src/scribe/services/backup.py +++ b/src/scribe/services/backup.py @@ -2781,7 +2781,7 @@ async def _restore_v2(data: dict) -> dict: session.add(event) stats["rule_usage_events"] += 1 - # And the System twin, after the Systems (step 16 fills their map). + # And the System twin, after the Systems (step 15 fills their map). for ev in data.get("system_usage_events", []): event = _build_system_usage_event(ev, maps) if event is None: diff --git a/src/scribe/services/backup_job.py b/src/scribe/services/backup_job.py index 4e197330..a8909f34 100644 --- a/src/scribe/services/backup_job.py +++ b/src/scribe/services/backup_job.py @@ -3,8 +3,8 @@ ONE APScheduler job (services/scheduler.py) fires at the top of every hour and reads the config at fire time: when backups are on and the hour is the chosen one, it runs. Reading at fire time rather than scheduling the chosen hour -keeps the app's startup free of a database read (#4181 — a lifespan hook that -waits on the database is a worker that never serves), and a changed hour +keeps the app's startup free of a database read (a lifespan hook that waits +on the database is a worker that never serves), and a changed hour needs no reschedule. A run exports the full backup, writes it as the dated tar @@ -40,7 +40,7 @@ KEEP_RUNS = 30 # room for one slow run, so a backup that lands at 02:40 instead of 02:05 # does not flicker the page into a warning. STALE_AFTER = timedelta(hours=26) -# The whole run's deadline (rule 156: a wait with no deadline is a bug). The +# The whole run's deadline, because a wait with no deadline is a bug. The # S3 upload has its own 15-minute timeout, but a folder on a hung NFS mount # has none, and a run that never returns holds the lock — every later backup # would be skipped as "already running", silently, forever. diff --git a/src/scribe/services/canonical_systems.py b/src/scribe/services/canonical_systems.py index d6c43136..408a240b 100644 --- a/src/scribe/services/canonical_systems.py +++ b/src/scribe/services/canonical_systems.py @@ -1,5 +1,5 @@ """The global canonical area vocabulary, and the mapping from a project's -Systems onto it (milestone 307 step 1, decision note 3026). +Systems onto it. A `System` is per-project. Nothing outside a project can reference one, so a rule that spans projects has no way to say "this is about CI" without chaining @@ -210,8 +210,8 @@ async def set_system_canonical( """Map (or unmap) one project System onto a catalog entry. Authorised by the PROJECT, not the catalog: mapping changes the project's - row, so project write access is the right gate (rule 78 — never a bare - owner filter). Passing None clears the mapping. + row, so project write access is the right gate — never a bare owner + filter. Passing None clears the mapping. Touches `canonical_id` and nothing else — the System's own name, charter and record associations are left exactly as they are. diff --git a/src/scribe/services/design_stylesheet.py b/src/scribe/services/design_stylesheet.py index 1a1e1b42..fc4ead4c 100644 --- a/src/scribe/services/design_stylesheet.py +++ b/src/scribe/services/design_stylesheet.py @@ -24,7 +24,7 @@ with every new element, where a purpose name (`--action-primary`) is reused. SAFETY ------ Values are interpolated into a stylesheet, and design systems are shareable -records (rule #47). A value of `red; } body { display: none` in a system someone +records. A value of `red; } body { display: none` in a system someone shared with you would otherwise inject CSS into your page. Everything rendered here is validated or dropped — never escaped-and-hoped. """ @@ -75,8 +75,8 @@ def selector_for_mode(mode: str, root_selector: str = ":root") -> str: attribute selector, matching the convention already in the codebase. The root selector is a PARAMETER because a container-scoped preview cannot - use `:root` — #251 recorded that mode scoping is one-way (light lives on - `:root`, dark layers over it), so a generator that hardcoded `:root` could + use `:root` — mode scoping is one-way (light lives on `:root`, dark layers + over it), so a generator that hardcoded `:root` could not serve a preview at all. """ if mode == BASE_MODE: diff --git a/src/scribe/services/forge_connections.py b/src/scribe/services/forge_connections.py index 9fdf633f..d1cfa7d3 100644 --- a/src/scribe/services/forge_connections.py +++ b/src/scribe/services/forge_connections.py @@ -1,4 +1,4 @@ -"""User-level forge connection CRUD — the keyring rows get_forges reads (#2778). +"""User-level forge connection CRUD — the keyring rows get_forges reads. A connection is a user's read-only credential for one forge host; one row per (user, host) keeps host-keyed resolution deterministic with no default-pointer diff --git a/src/scribe/services/moment_actions.py b/src/scribe/services/moment_actions.py index 58d6e728..dc42e09e 100644 --- a/src/scribe/services/moment_actions.py +++ b/src/scribe/services/moment_actions.py @@ -1,4 +1,4 @@ -"""Which actions reach which moment: shipped defaults plus each install's own (milestone 458 step 2). +"""Which actions reach which moment: shipped defaults plus each install's own. The catalog (`services/moments.py`) says what the moments ARE. This module says how the work gets there. One call can reach several moments: `kubectl apply` @@ -63,7 +63,7 @@ COMMAND_TOOLS = frozenset({"bash"}) # procedures' own. SKILL_TOOL, SKILL_FIELD = "skill", "skill" -# A procedure also reaches the moment it is FOR (step 5): loading the +# A procedure also reaches the moment it is FOR: loading the # reporting procedure is a report as surely as the reply that follows it. # # The product's own skills declare theirs in their SKILL.md frontmatter diff --git a/src/scribe/services/notes.py b/src/scribe/services/notes.py index 440090df..8c6bc2e0 100644 --- a/src/scribe/services/notes.py +++ b/src/scribe/services/notes.py @@ -845,7 +845,7 @@ async def mark_note_verified( So a failed check leaves `verified_at` untouched and the note stays at the top of the sweep until someone actually deals with it. - Write access, not read (rules 47/78): stamping is a mutation, and an + Write access, not read: stamping is a mutation, and an editor-share holder may make it while a viewer may not. Returns None when the note is not found, not writable, or carries no diff --git a/src/scribe/services/notifications.py b/src/scribe/services/notifications.py index 00fb0cda..7abc1203 100644 --- a/src/scribe/services/notifications.py +++ b/src/scribe/services/notifications.py @@ -149,7 +149,7 @@ async def send_invitation_email(email: str, invite_url: str, invited_by_username def utc_day_start(day: date) -> datetime: """Midnight UTC on `day`, as an AWARE datetime — the reminder dedup window. - Deliberately a `datetime` and not the bare `date` (#1727). Comparing a + Deliberately a `datetime` and not the bare `date`. Comparing a `timestamptz` column against a `date` does work, via an implicit cast — but Postgres resolves that cast in the SESSION's TimeZone, so the window would move with a server setting nobody remembers is load-bearing. An aware UTC @@ -200,10 +200,9 @@ async def check_due_tasks() -> None: # Dedup: check if we already sent a reminder today. # `window_start` is an aware datetime, NOT `today.isoformat()`. # asyncpg binds a str as VARCHAR and Postgres has no - # `timestamptz >= text` operator, so this raised on every run - # that got this far — swallowed by the per-user `except` below, - # which is why the only symptom was reminders silently never - # sending plus an hourly traceback in the DB log (#1727). + # `timestamptz >= text` operator, so a string would raise on + # every run — swallowed by the per-user `except` below, leaving + # reminders silently never sending. dedup_result = await session.execute( select(func.count(AppLog.id)).where( AppLog.action == "task_reminder", diff --git a/src/scribe/services/placement.py b/src/scribe/services/placement.py index 8ba435cf..787396f4 100644 --- a/src/scribe/services/placement.py +++ b/src/scribe/services/placement.py @@ -1,13 +1,12 @@ """Where a task sits — its project, its milestone, its step position, what is next. -WHY THIS EXISTS (milestone 409 step 1) +WHY THIS EXISTS An agent reporting finished work to the operator is asked to place it: which milestone, which step of how many, what comes next. Without those facts in hand it reconstructs them from memory, and a reconstruction reads exactly like -the real thing while being wrong — the drafting of the feature note that -started this milestone invented a milestone title and named a "next" step that -was already done. So the facts come back on the write that changes a task, +the real thing while being wrong — an invented milestone title, a "next" step +that is already done. So the facts come back on the write that changes a task, where the report is about to be written, instead of being left to recall. THE SHAPE @@ -57,7 +56,7 @@ from scribe.services import access as access_svc from scribe.services.milestones import OPEN_STEP_STATUSES, _progress_from_counts # Imported, not restated. The milestone summary names a plan's next open step -# too (#4154), and the two answers must agree about what "open" means. +# too, and the two answers must agree about what "open" means. _OPEN = OPEN_STEP_STATUSES logger = logging.getLogger(__name__) diff --git a/src/scribe/services/planning.py b/src/scribe/services/planning.py index c0d759af..6fba8740 100644 --- a/src/scribe/services/planning.py +++ b/src/scribe/services/planning.py @@ -9,7 +9,7 @@ kind=plan task is retired going forward — start_planning never creates one. Steps passed up front are created in the SAME transaction as the milestone (services/record_batch.py), so the plan body and the steps can cite each other -through `{{ref:N}}` placeholders instead of predicted ids (#4016). +through `{{ref:N}}` placeholders instead of predicted ids. """ from __future__ import annotations diff --git a/src/scribe/services/platforms.py b/src/scribe/services/platforms.py index 675c72a5..d56a07bc 100644 --- a/src/scribe/services/platforms.py +++ b/src/scribe/services/platforms.py @@ -1,5 +1,5 @@ """Platforms — what a project is built on or ships as, and which projects are -which (milestone 463 step 2). +which. Membership is what makes "is this idea in family for that project?" a lookup rather than a judgment: a family idea is for some platforms, and it reaches @@ -266,7 +266,7 @@ async def set_project_platforms( user_id: int, project_id: int, updates: dict[str, str | None], ) -> list[dict]: """Apply a person's answers about a project's platforms. Write-gated on - the project (rule 78). Platforms not named are left exactly as they are. + the project. Platforms not named are left exactly as they are. Raises ValueError, naming the problem, on a malformed update, an unknown slug, or no write access — before anything is written, so the update diff --git a/src/scribe/services/projects.py b/src/scribe/services/projects.py index afdfafda..964e469a 100644 --- a/src/scribe/services/projects.py +++ b/src/scribe/services/projects.py @@ -132,19 +132,16 @@ async def get_project_summaries( ) -> dict[int, dict]: """Summaries for MANY projects — four queries and one session, total. - Replaces an `asyncio.gather` over the per-project version below, which was - a nested fan-out: each project opened its own session for three queries, - then called the milestone summary, which opened one more per milestone. For - 25 projects that asked for roughly 250 pooled connections at once against a - pool of 15 (SQLAlchemy's default 5 + 10 overflow), so most of them sat out - the 30-second checkout timeout and everything else on the instance queued - behind them — including unrelated routes, which is why /api/settings - returned 500 while /api/projects took 30.9s (#2384). + Not an `asyncio.gather` over the per-project version below: that is a + nested fan-out — each project opens its own session for three queries, + then calls the milestone summary, which opens one more per milestone. For + a few dozen projects that asks for far more pooled connections than the + pool holds, so most of them sit out the checkout timeout and everything + else on the instance queues behind them, unrelated routes included. - The comment it replaced said "one backend pass instead of N+1 frontend - calls". It did remove the N+1 from the network — and recreated it against - the connection pool, where it is worse: the browser had at least been - serialising those calls. + Moving an N+1 from the browser to the server does not remove it; it moves + it onto the connection pool, where it is worse — the browser at least + serialises its calls. """ if not project_ids: return {} diff --git a/src/scribe/services/record_batch.py b/src/scribe/services/record_batch.py index 0d650f81..e9c5cef5 100644 --- a/src/scribe/services/record_batch.py +++ b/src/scribe/services/record_batch.py @@ -1,9 +1,8 @@ """Create several records at once, so they can cite each other without guessing ids. -Why this exists is in services/record_refs.py (#4016): a session that needs -records to reference one another used to create them one by one and predict -the ids of the ones not yet made — and any concurrent create, from any session -or any user, took those numbers. +Why this exists is in services/record_refs.py: a session that creates records +one by one and predicts the ids of the ones not yet made loses those numbers +to any concurrent create, from any session or any user. Here the whole batch is ONE transaction: insert every record, flush so the sequence assigns the real ids, rewrite each `{{ref:N}}` with the id and title @@ -128,7 +127,7 @@ async def create_batch( project_id = existing.project_id if milestone is not None and not project_id: raise ValueError("a new milestone needs a project_id") - # Share-aware (rule 78): a collaborator with write access to a shared + # Share-aware: a collaborator with write access to a shared # project may plan in it; a bare owner filter would refuse them. if project_id and not await access_svc.can_write_project(user_id, project_id): raise ValueError(f"project {project_id} not found") diff --git a/src/scribe/services/recurrence.py b/src/scribe/services/recurrence.py index 5fed9c70..d2af913a 100644 --- a/src/scribe/services/recurrence.py +++ b/src/scribe/services/recurrence.py @@ -138,8 +138,7 @@ async def spawn_recurring_tasks() -> int: recurrence_rule=task.recurrence_rule, ) # No explicit embed here: create_note calls embed_note itself, so - # the spawn path stopped being a second copy of the embedding rule - # the moment that moved into the service (#2056, #280). + # the spawn path is not a second copy of the embedding rule. except Exception: logger.exception("Failed to spawn recurring task %d", task.id) continue diff --git a/src/scribe/services/report_check.py b/src/scribe/services/report_check.py index 2c1dbed1..b51f50cd 100644 --- a/src/scribe/services/report_check.py +++ b/src/scribe/services/report_check.py @@ -1,7 +1,7 @@ """The report-shape check: does a reply that closed a task carry the completion sections, and what is it sent back with when it does not. -WHY THIS EXISTS (milestone 409 step 5) +WHY THIS EXISTS Everything that helps an agent write a readable completion report arrives BEFORE the reply is written. The Stop hook is the one moment the finished reply @@ -13,13 +13,10 @@ exists, so a reply that closed a task is checked for the completion sections - OWNING THE WORDS the agent is sent back with, so every client's hook says the same thing (plugin/PACKAGING.md: hooks carry timing and transport). -ONE END-OF-TURN REQUEST (milestone 500 step 4). This used to be a Stop hook of -its own that checked the sections in shell and reported here; the reply-moment -check sent the same reply to `/reply-rules` a moment later. The hook now sends -the reply once, with the ids of the tasks the turn closed, and the section -check runs here beside the reply hold. The patterns are the ones the shell -used, matched on the words that carry the meaning rather than exact headings, -so a shape's wording can change without breaking this. +ONE END-OF-TURN REQUEST. The Stop hook sends the reply once, with the ids of +the tasks the turn closed, and the section check runs here beside the reply +hold. The patterns match the words that carry the meaning rather than exact +headings, so a shape's wording can change without breaking this. app_logs rather than a table of its own: one small event with a JSON detail is what that table holds, it already has retention and an admin viewer, and diff --git a/src/scribe/services/retrieval_review.py b/src/scribe/services/retrieval_review.py index cb1a95f3..169c1c1e 100644 --- a/src/scribe/services/retrieval_review.py +++ b/src/scribe/services/retrieval_review.py @@ -1,8 +1,8 @@ -"""Judging what a retrieval arm offered — the review pass (#4772). +"""Judging what a retrieval arm offered — the review pass. The usage tables record what the reader DID with a line: opened it or not. -Since every menu line carries its matched passage (#4364), that action no -longer stands in for relevance — a line can be unrelated, related and enough +Because every menu line carries its matched passage, that action does not +stand in for relevance — a line can be unrelated, related and enough as shown, related and already in context, or related and ignored, and all four are "not opened". Only someone reading the query beside the line can tell them apart. This module is how that reading gets done and kept. diff --git a/src/scribe/services/rule_scope.py b/src/scribe/services/rule_scope.py index 408735f1..9e6fe784 100644 --- a/src/scribe/services/rule_scope.py +++ b/src/scribe/services/rule_scope.py @@ -1,4 +1,4 @@ -"""Which rules a caller may be shown — a rule's HOME, stated once (milestone 414). +"""Which rules a caller may be shown — a rule's HOME, stated once. A rule lives in a rulebook topic — GLOBAL, it applies wherever its owner works — or on one project, where it applies to that project and nowhere else. @@ -13,9 +13,8 @@ home, so it is answered here and nowhere else: - `everywhere=True`: every rule the caller owns, in any home — the explicit whole-rulebook question. -It was written inline in `semantic_search_rules` until the moment lookup -(milestone 458) needed the same answer. Two copies of a scope clause is how -one of them starts surfacing another project's rules, so it moved here first. +Rule search and the moment lookup both need this answer. Two copies of a +scope clause is how one of them starts surfacing another project's rules. """ from __future__ import annotations diff --git a/src/scribe/services/rule_versions.py b/src/scribe/services/rule_versions.py index a13d09a6..0cb43cc5 100644 --- a/src/scribe/services/rule_versions.py +++ b/src/scribe/services/rule_versions.py @@ -8,7 +8,7 @@ lifecycle: a soft delete keeps the rule row whole and restorable, so there is nothing to preserve at delete time, and anything that outlived a purge would be data the operator explicitly asked to be gone. -WHAT IS DELIBERATELY NOT COPIED FROM note_versions (milestone 323). Both of its +WHAT IS DELIBERATELY NOT COPIED FROM note_versions. Both of its guards defend against note autosave, which fires every 60 seconds and would otherwise burn every slot. Rules have no autosave — every version here comes from a deliberate `update_rule` — so both guards would only ever discard a real diff --git a/src/scribe/services/scheduler.py b/src/scribe/services/scheduler.py index 1489930c..306bebc8 100644 --- a/src/scribe/services/scheduler.py +++ b/src/scribe/services/scheduler.py @@ -1,6 +1,6 @@ -"""One APScheduler job bridged into the asyncio loop — the shape the four +"""One APScheduler job bridged into the asyncio loop — the shape the *_scheduler modules (recurrence spawn, auto-pin scan, trash purge, DB -maintenance) each used to carry a private copy of. +maintenance) share instead of each carrying a private copy. APScheduler's BackgroundScheduler fires from a worker thread; the work is async and must run on the app's loop, so the fire is bridged with diff --git a/src/scribe/services/shape_check.py b/src/scribe/services/shape_check.py index 15c0f23c..cd62b9b7 100644 --- a/src/scribe/services/shape_check.py +++ b/src/scribe/services/shape_check.py @@ -1,15 +1,14 @@ """The end-of-turn shape check: what a turn wrote that nobody has judged, and what the agent is asked. -WHY THIS EXISTS (milestone 439) +WHY THIS EXISTS -The shape ledger used to be filled by machinery: extraction found the -definitions, framework rules decided which were one-offs, and the write-path -hook stamped "instance of #N" with nobody reading. The agent that WROTE the -code — the one participant who knows what it is — was asked only in an audit, -weeks later, if anyone ran one. A first instance of a reusable piece was never -asked about at all, so it was never recorded, and the next session rebuilt it. +The agent that WROTE the code is the one participant who knows what it is. +Machinery can find definitions and stamp "instance of #N", but it cannot say +that a first instance is a reusable piece — and a piece never recorded is +rebuilt by the next session. An audit run later asks too late, if anyone runs +one. -So the question moves to the moment the knowledge exists. The client's write +So the question is asked at the moment the knowledge exists. The client's write hooks keep a ledger of the definitions each turn wrote; its Stop hook sends them here at the end of the turn. Two jobs live on this side, as with the report check (services/report_check.py): @@ -66,7 +65,7 @@ def _evidence_note(evidence: dict) -> str: def block_reason(unjudged: list[dict], *, project_id: int, repo: str) -> str: """What the agent is told at the end of a turn that wrote unjudged shapes. - Phrased as the practice wanted (note #3565): what to decide and the one + Phrased as a practice: what to decide and the one call that records it, not a prohibition. The reusing-code skill owns the longer version of what each verdict means. """ diff --git a/src/scribe/services/supersession.py b/src/scribe/services/supersession.py index 9444b55a..bf0af830 100644 --- a/src/scribe/services/supersession.py +++ b/src/scribe/services/supersession.py @@ -1,6 +1,6 @@ """Which records have been overtaken by which — the claim, not the ranking. -Step 2 of milestone #278. This module only records and reads the relation; the +This module only records and reads the relation; the demotion that makes it matter lives in the retrieval layer. WHY THE CLAIM POINTS FORWARD @@ -202,8 +202,8 @@ async def attach_relations(user_id: int, note_id: int, data: dict, *, hint: bool acted on confidently, which is worse than never surfacing it. Omitted entirely when empty, so an ordinary note's payload doesn't grow - two permanently-empty lists (#2483 — a field that always says nothing - trains readers to skip fields). `hint=True` (the agent surface) also + two permanently-empty lists — a field that always says nothing trains + readers to skip fields. `hint=True` (the agent surface) also attaches `superseded_note`, the one-sentence reading instruction. """ rel = await get_relations(user_id, note_id) diff --git a/src/scribe/services/system_rulings.py b/src/scribe/services/system_rulings.py index 2e584834..dba7b8d2 100644 --- a/src/scribe/services/system_rulings.py +++ b/src/scribe/services/system_rulings.py @@ -1,4 +1,4 @@ -"""An area's rulings, delivered when its files are touched (milestone 444). +"""An area's rulings, delivered when its files are touched. A RULING is the operator's decision about how one area of the work must behave. It lives in a `Rulings` section at the end of the area's System description @@ -13,7 +13,7 @@ patterns or it does not. It takes no budget and no floor from the retrieval arms beside it, and writes no retrieval_logs row — there is no score distribution for it to join. Its surfacings go to `system_usage_events`. -ONCE IN FULL, THEN A REFERENCE (the #3750 shape). The hook keeps the Systems +ONCE IN FULL, THEN A REFERENCE. The hook keeps the Systems already shown this session and passes them back; a repeat renders as one short line rather than the rulings again, and is not counted as a surfacing. diff --git a/src/scribe/services/system_usage.py b/src/scribe/services/system_usage.py index 001d7887..321cc037 100644 --- a/src/scribe/services/system_usage.py +++ b/src/scribe/services/system_usage.py @@ -1,6 +1,6 @@ """System usage telemetry — were an area's rulings read once shown? -The twin of `note_usage` and `rule_usage` for Systems (milestone 444). The +The twin of `note_usage` and `rule_usage` for Systems. The rulings arm shows a System's rulings when a command or edit touches the System's files; a pull is somebody then opening the System (`get_system`). The ratio says whether delivering rulings by path earns its line. @@ -97,8 +97,7 @@ async def usage_for_systems(system_ids: list[int]) -> dict[int, dict]: One GROUP BY for the whole list, never a query per row — this decorates a list view. Systems with no events come back as `empty_system_usage()`. Fails open: a readout that could break the Systems list is worse than a - zero, but the failure is reported so the zero is not mistaken for silence - (#2663). + zero, but the failure is reported so the zero is not mistaken for silence. """ ids = [int(s) for s in system_ids] out: dict[int, dict] = {sid: empty_system_usage() for sid in ids} diff --git a/src/scribe/services/text.py b/src/scribe/services/text.py index 0d2ce3cc..53501f76 100644 --- a/src/scribe/services/text.py +++ b/src/scribe/services/text.py @@ -25,7 +25,7 @@ def elide(text: str, budget: int) -> tuple[str, bool]: This is the fallback, not the goal. Where the system knows WHICH span of a record is the relevant one — a semantic search knows exactly that, and - stores it (#4243) — show that span and say so. Reach for this only when + stores it — show that span and say so. Reach for this only when nothing identifies a better part than "all of it". """ if budget <= 0 or len(text) <= budget: @@ -41,11 +41,9 @@ def elide(text: str, budget: int) -> tuple[str, bool]: # What a search result shows a reader, as one decision made in one place. # # Every door onto a semantic search faces the same question — which span of a -# record do I show someone deciding whether to open it? — and each used to -# answer it separately with a bare head cut of a different length: 240 -# characters in the MCP search, 200 in the web's knowledge search. Both showed +# record do I show someone deciding whether to open it? A bare head cut shows # the document's OPENING, which is not the span that matched and not the span -# the ranking was built on. +# the ranking was built on, and a cut per door gives each a different answer. # # Doors keep their own field names (the web UI renders `snippet`, the MCP # surface returns `excerpt`), because renaming a field a frontend consumes is diff --git a/src/scribe/services/trash.py b/src/scribe/services/trash.py index 3d027cfd..c4e57f8b 100644 --- a/src/scribe/services/trash.py +++ b/src/scribe/services/trash.py @@ -170,7 +170,7 @@ async def restore_entity(user_id: int, entity_type: str, entity_id: int) -> int The inverse of `delete(user_id, entity_type, entity_id)`, which returns a batch id the caller usually doesn't keep. Callers that need to undo their own - soft-delete later — snippet un-merge (#2165) — know the entity id, not the + soft-delete later — snippet un-merge — know the entity id, not the batch, and looking it up here keeps them from reaching into the column set. Restores the whole batch on purpose, not just the row: the batch is the diff --git a/src/scribe/services/verification.py b/src/scribe/services/verification.py index 865e627d..5938cdd0 100644 --- a/src/scribe/services/verification.py +++ b/src/scribe/services/verification.py @@ -1,11 +1,10 @@ """What a record's own check MEANS — shared by rules and notes. Two record types carry `verify_with` / `expires_when` / `verified_at`: rules -(milestone 312) and notes (milestone 317). What they share is BEHAVIOUR — how -a stamp is read, how old it is, what "never" means — not storage and not the -query. Note 3163 is the rule this file is an instance of: `semantic_search_*` -could never have been shared between them because a rule scopes by rulebook -ownership and a note scopes by the note ACL, so the two sweeps are siblings. +and notes. What they share is BEHAVIOUR — how a stamp is read, how old it is, +what "never" means — not storage and not the query. The two sweeps stay +separate siblings, because a rule scopes by rulebook ownership and a note +scopes by the note ACL. These functions are the part that genuinely is common, factored so it exists once rather than twice. diff --git a/src/scribe/services/version_pinning.py b/src/scribe/services/version_pinning.py index ce1fdbdb..58fcdd9b 100644 --- a/src/scribe/services/version_pinning.py +++ b/src/scribe/services/version_pinning.py @@ -8,8 +8,6 @@ Tiers (pin_kind values): None → rolling autosave; capped at MAX_VERSIONS, FIFO. "auto" → system-declared via the stability scan; capped at MAX_AUTO_PINS, FIFO. "manual" → user-declared; unlimited, never pruned. - -Design: docs/superpowers/specs/2026-05-13-note-version-pinning-design.md """ from __future__ import annotations