docs(6205): the remaining services files state the reason, not the instance that taught it
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m46s
CI & Build / Python tests (push) Successful in 2m36s
CI & Build / Build & push image (push) Successful in 31s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m46s
CI & Build / Python tests (push) Successful in 2m36s
CI & Build / Build & push image (push) Successful in 31s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user