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)
|
# 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
|
# readable_* — everything the ACL permits, including a record reachable ONLY
|
||||||
# through a direct or group note share. For EXPLICIT acts: a
|
# 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
|
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
|
``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``,
|
the one place that gets the pattern right: strong references in ``_pending``,
|
||||||
discarded on completion, with failures logged at WARNING instead of vanishing.
|
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
|
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.
|
(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
|
THIS IS NOT DECORATION. A telemetry subsystem whose every failure goes to
|
||||||
at zero for weeks — every counter reading empty, indistinguishable from
|
``logger.debug`` can run at zero — every counter reading empty,
|
||||||
"nobody uses this" — because every failure went to ``logger.debug``. A
|
indistinguishable from "nobody uses this". A subsystem whose failures are
|
||||||
subsystem whose failures are all invisible cannot report its own death.
|
all invisible cannot report its own death.
|
||||||
|
|
||||||
The AppLog write is itself guarded: when the database is down it fails too,
|
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
|
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)
|
session.add(event)
|
||||||
stats["rule_usage_events"] += 1
|
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", []):
|
for ev in data.get("system_usage_events", []):
|
||||||
event = _build_system_usage_event(ev, maps)
|
event = _build_system_usage_event(ev, maps)
|
||||||
if event is None:
|
if event is None:
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
ONE APScheduler job (services/scheduler.py) fires at the top of every hour and
|
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
|
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
|
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
|
keeps the app's startup free of a database read (a lifespan hook that waits
|
||||||
waits on the database is a worker that never serves), and a changed hour
|
on the database is a worker that never serves), and a changed hour
|
||||||
needs no reschedule.
|
needs no reschedule.
|
||||||
|
|
||||||
A run exports the full backup, writes it as the dated tar
|
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
|
# 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.
|
# does not flicker the page into a warning.
|
||||||
STALE_AFTER = timedelta(hours=26)
|
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
|
# 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
|
# has none, and a run that never returns holds the lock — every later backup
|
||||||
# would be skipped as "already running", silently, forever.
|
# would be skipped as "already running", silently, forever.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""The global canonical area vocabulary, and the mapping from a project's
|
"""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
|
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
|
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.
|
"""Map (or unmap) one project System onto a catalog entry.
|
||||||
|
|
||||||
Authorised by the PROJECT, not the catalog: mapping changes the project's
|
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
|
row, so project write access is the right gate — never a bare owner
|
||||||
owner filter). Passing None clears the mapping.
|
filter. Passing None clears the mapping.
|
||||||
|
|
||||||
Touches `canonical_id` and nothing else — the System's own name, charter
|
Touches `canonical_id` and nothing else — the System's own name, charter
|
||||||
and record associations are left exactly as they are.
|
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
|
SAFETY
|
||||||
------
|
------
|
||||||
Values are interpolated into a stylesheet, and design systems are shareable
|
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
|
shared with you would otherwise inject CSS into your page. Everything rendered
|
||||||
here is validated or dropped — never escaped-and-hoped.
|
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.
|
attribute selector, matching the convention already in the codebase.
|
||||||
|
|
||||||
The root selector is a PARAMETER because a container-scoped preview cannot
|
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
|
use `:root` — mode scoping is one-way (light lives on `:root`, dark layers
|
||||||
`:root`, dark layers over it), so a generator that hardcoded `:root` could
|
over it), so a generator that hardcoded `:root` could
|
||||||
not serve a preview at all.
|
not serve a preview at all.
|
||||||
"""
|
"""
|
||||||
if mode == BASE_MODE:
|
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
|
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
|
(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
|
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`
|
how the work gets there. One call can reach several moments: `kubectl apply`
|
||||||
@@ -63,7 +63,7 @@ COMMAND_TOOLS = frozenset({"bash"})
|
|||||||
# procedures' own.
|
# procedures' own.
|
||||||
SKILL_TOOL, SKILL_FIELD = "skill", "skill"
|
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.
|
# 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
|
# 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
|
So a failed check leaves `verified_at` untouched and the note stays at the
|
||||||
top of the sweep until someone actually deals with it.
|
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.
|
editor-share holder may make it while a viewer may not.
|
||||||
|
|
||||||
Returns None when the note is not found, not writable, or carries no
|
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:
|
def utc_day_start(day: date) -> datetime:
|
||||||
"""Midnight UTC on `day`, as an AWARE datetime — the reminder dedup window.
|
"""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
|
`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
|
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
|
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.
|
# Dedup: check if we already sent a reminder today.
|
||||||
# `window_start` is an aware datetime, NOT `today.isoformat()`.
|
# `window_start` is an aware datetime, NOT `today.isoformat()`.
|
||||||
# asyncpg binds a str as VARCHAR and Postgres has no
|
# asyncpg binds a str as VARCHAR and Postgres has no
|
||||||
# `timestamptz >= text` operator, so this raised on every run
|
# `timestamptz >= text` operator, so a string would raise on
|
||||||
# that got this far — swallowed by the per-user `except` below,
|
# every run — swallowed by the per-user `except` below, leaving
|
||||||
# which is why the only symptom was reminders silently never
|
# reminders silently never sending.
|
||||||
# sending plus an hourly traceback in the DB log (#1727).
|
|
||||||
dedup_result = await session.execute(
|
dedup_result = await session.execute(
|
||||||
select(func.count(AppLog.id)).where(
|
select(func.count(AppLog.id)).where(
|
||||||
AppLog.action == "task_reminder",
|
AppLog.action == "task_reminder",
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
"""Where a task sits — its project, its milestone, its step position, what is next.
|
"""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
|
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
|
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
|
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
|
the real thing while being wrong — an invented milestone title, a "next" step
|
||||||
started this milestone invented a milestone title and named a "next" step that
|
that is already done. So the facts come back on the write that changes a task,
|
||||||
was 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.
|
where the report is about to be written, instead of being left to recall.
|
||||||
|
|
||||||
THE SHAPE
|
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
|
from scribe.services.milestones import OPEN_STEP_STATUSES, _progress_from_counts
|
||||||
|
|
||||||
# Imported, not restated. The milestone summary names a plan's next open step
|
# 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
|
_OPEN = OPEN_STEP_STATUSES
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
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
|
(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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Platforms — what a project is built on or ships as, and which projects are
|
"""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
|
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
|
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],
|
user_id: int, project_id: int, updates: dict[str, str | None],
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""Apply a person's answers about a project's platforms. Write-gated on
|
"""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
|
Raises ValueError, naming the problem, on a malformed update, an unknown
|
||||||
slug, or no write access — before anything is written, so the update
|
slug, or no write access — before anything is written, so the update
|
||||||
|
|||||||
@@ -132,19 +132,16 @@ async def get_project_summaries(
|
|||||||
) -> dict[int, dict]:
|
) -> dict[int, dict]:
|
||||||
"""Summaries for MANY projects — four queries and one session, total.
|
"""Summaries for MANY projects — four queries and one session, total.
|
||||||
|
|
||||||
Replaces an `asyncio.gather` over the per-project version below, which was
|
Not an `asyncio.gather` over the per-project version below: that is a
|
||||||
a nested fan-out: each project opened its own session for three queries,
|
nested fan-out — each project opens its own session for three queries,
|
||||||
then called the milestone summary, which opened one more per milestone. For
|
then calls the milestone summary, which opens one more per milestone. For
|
||||||
25 projects that asked for roughly 250 pooled connections at once against a
|
a few dozen projects that asks for far more pooled connections than the
|
||||||
pool of 15 (SQLAlchemy's default 5 + 10 overflow), so most of them sat out
|
pool holds, so most of them sit out the checkout timeout and everything
|
||||||
the 30-second checkout timeout and everything else on the instance queued
|
else on the instance queues behind them, unrelated routes included.
|
||||||
behind them — including unrelated routes, which is why /api/settings
|
|
||||||
returned 500 while /api/projects took 30.9s (#2384).
|
|
||||||
|
|
||||||
The comment it replaced said "one backend pass instead of N+1 frontend
|
Moving an N+1 from the browser to the server does not remove it; it moves
|
||||||
calls". It did remove the N+1 from the network — and recreated it against
|
it onto the connection pool, where it is worse — the browser at least
|
||||||
the connection pool, where it is worse: the browser had at least been
|
serialises its calls.
|
||||||
serialising those calls.
|
|
||||||
"""
|
"""
|
||||||
if not project_ids:
|
if not project_ids:
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
"""Create several records at once, so they can cite each other without guessing ids.
|
"""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
|
Why this exists is in services/record_refs.py: a session that creates records
|
||||||
records to reference one another used to create them one by one and predict
|
one by one and predicts the ids of the ones not yet made loses those numbers
|
||||||
the ids of the ones not yet made — and any concurrent create, from any session
|
to any concurrent create, from any session or any user.
|
||||||
or any user, took those numbers.
|
|
||||||
|
|
||||||
Here the whole batch is ONE transaction: insert every record, flush so the
|
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
|
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
|
project_id = existing.project_id
|
||||||
if milestone is not None and not project_id:
|
if milestone is not None and not project_id:
|
||||||
raise ValueError("a new milestone needs a 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.
|
# 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):
|
if project_id and not await access_svc.can_write_project(user_id, project_id):
|
||||||
raise ValueError(f"project {project_id} not found")
|
raise ValueError(f"project {project_id} not found")
|
||||||
|
|||||||
@@ -138,8 +138,7 @@ async def spawn_recurring_tasks() -> int:
|
|||||||
recurrence_rule=task.recurrence_rule,
|
recurrence_rule=task.recurrence_rule,
|
||||||
)
|
)
|
||||||
# No explicit embed here: create_note calls embed_note itself, so
|
# No explicit embed here: create_note calls embed_note itself, so
|
||||||
# the spawn path stopped being a second copy of the embedding rule
|
# the spawn path is not a second copy of the embedding rule.
|
||||||
# the moment that moved into the service (#2056, #280).
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to spawn recurring task %d", task.id)
|
logger.exception("Failed to spawn recurring task %d", task.id)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""The report-shape check: does a reply that closed a task carry the completion
|
"""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.
|
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
|
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
|
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
|
- 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).
|
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
|
ONE END-OF-TURN REQUEST. The Stop hook sends the reply once, with the ids of
|
||||||
its own that checked the sections in shell and reported here; the reply-moment
|
the tasks the turn closed, and the section check runs here beside the reply
|
||||||
check sent the same reply to `/reply-rules` a moment later. The hook now sends
|
hold. The patterns match the words that carry the meaning rather than exact
|
||||||
the reply once, with the ids of the tasks the turn closed, and the section
|
headings, so a shape's wording can change without breaking this.
|
||||||
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.
|
|
||||||
|
|
||||||
app_logs rather than a table of its own: one small event with a JSON detail is
|
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
|
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.
|
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
|
Because every menu line carries its matched passage, that action does not
|
||||||
longer stands in for relevance — a line can be unrelated, related and enough
|
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
|
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
|
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.
|
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
|
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.
|
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
|
- `everywhere=True`: every rule the caller owns, in any home — the explicit
|
||||||
whole-rulebook question.
|
whole-rulebook question.
|
||||||
|
|
||||||
It was written inline in `semantic_search_rules` until the moment lookup
|
Rule search and the moment lookup both need this answer. Two copies of a
|
||||||
(milestone 458) needed the same answer. Two copies of a scope clause is how
|
scope clause is how one of them starts surfacing another project's rules.
|
||||||
one of them starts surfacing another project's rules, so it moved here first.
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
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
|
nothing to preserve at delete time, and anything that outlived a purge would be
|
||||||
data the operator explicitly asked to be gone.
|
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
|
guards defend against note autosave, which fires every 60 seconds and would
|
||||||
otherwise burn every slot. Rules have no autosave — every version here comes
|
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
|
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
|
*_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
|
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
|
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.
|
"""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
|
The agent that WROTE the code is the one participant who knows what it is.
|
||||||
definitions, framework rules decided which were one-offs, and the write-path
|
Machinery can find definitions and stamp "instance of #N", but it cannot say
|
||||||
hook stamped "instance of #N" with nobody reading. The agent that WROTE the
|
that a first instance is a reusable piece — and a piece never recorded is
|
||||||
code — the one participant who knows what it is — was asked only in an audit,
|
rebuilt by the next session. An audit run later asks too late, if anyone runs
|
||||||
weeks later, if anyone ran one. A first instance of a reusable piece was never
|
one.
|
||||||
asked about at all, so it was never recorded, and the next session rebuilt it.
|
|
||||||
|
|
||||||
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
|
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
|
them here at the end of the turn. Two jobs live on this side, as with the
|
||||||
report check (services/report_check.py):
|
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:
|
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.
|
"""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
|
call that records it, not a prohibition. The reusing-code skill owns the
|
||||||
longer version of what each verdict means.
|
longer version of what each verdict means.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Which records have been overtaken by which — the claim, not the ranking.
|
"""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.
|
demotion that makes it matter lives in the retrieval layer.
|
||||||
|
|
||||||
WHY THE CLAIM POINTS FORWARD
|
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.
|
acted on confidently, which is worse than never surfacing it.
|
||||||
|
|
||||||
Omitted entirely when empty, so an ordinary note's payload doesn't grow
|
Omitted entirely when empty, so an ordinary note's payload doesn't grow
|
||||||
two permanently-empty lists (#2483 — a field that always says nothing
|
two permanently-empty lists — a field that always says nothing trains
|
||||||
trains readers to skip fields). `hint=True` (the agent surface) also
|
readers to skip fields. `hint=True` (the agent surface) also
|
||||||
attaches `superseded_note`, the one-sentence reading instruction.
|
attaches `superseded_note`, the one-sentence reading instruction.
|
||||||
"""
|
"""
|
||||||
rel = await get_relations(user_id, note_id)
|
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.
|
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
|
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
|
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`.
|
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
|
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.
|
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?
|
"""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
|
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
|
System's files; a pull is somebody then opening the System (`get_system`). The
|
||||||
ratio says whether delivering rulings by path earns its line.
|
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
|
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()`.
|
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
|
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
|
zero, but the failure is reported so the zero is not mistaken for silence.
|
||||||
(#2663).
|
|
||||||
"""
|
"""
|
||||||
ids = [int(s) for s in system_ids]
|
ids = [int(s) for s in system_ids]
|
||||||
out: dict[int, dict] = {sid: empty_system_usage() for sid in 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
|
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
|
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".
|
nothing identifies a better part than "all of it".
|
||||||
"""
|
"""
|
||||||
if budget <= 0 or len(text) <= budget:
|
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.
|
# 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
|
# 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
|
# record do I show someone deciding whether to open it? A bare head cut shows
|
||||||
# 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
|
|
||||||
# the document's OPENING, which is not the span that matched and not the span
|
# 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
|
# Doors keep their own field names (the web UI renders `snippet`, the MCP
|
||||||
# surface returns `excerpt`), because renaming a field a frontend consumes is
|
# 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
|
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
|
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.
|
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
|
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.
|
"""What a record's own check MEANS — shared by rules and notes.
|
||||||
|
|
||||||
Two record types carry `verify_with` / `expires_when` / `verified_at`: rules
|
Two record types carry `verify_with` / `expires_when` / `verified_at`: rules
|
||||||
(milestone 312) and notes (milestone 317). What they share is BEHAVIOUR — how
|
and notes. What they share is BEHAVIOUR — how a stamp is read, how old it is,
|
||||||
a stamp is read, how old it is, what "never" means — not storage and not the
|
what "never" means — not storage and not the query. The two sweeps stay
|
||||||
query. Note 3163 is the rule this file is an instance of: `semantic_search_*`
|
separate siblings, because a rule scopes by rulebook ownership and a note
|
||||||
could never have been shared between them because a rule scopes by rulebook
|
scopes by the note ACL.
|
||||||
ownership and a note scopes by the note ACL, so the two sweeps are siblings.
|
|
||||||
These functions are the part that genuinely is common, factored so it exists
|
These functions are the part that genuinely is common, factored so it exists
|
||||||
once rather than twice.
|
once rather than twice.
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ Tiers (pin_kind values):
|
|||||||
None → rolling autosave; capped at MAX_VERSIONS, FIFO.
|
None → rolling autosave; capped at MAX_VERSIONS, FIFO.
|
||||||
"auto" → system-declared via the stability scan; capped at MAX_AUTO_PINS, FIFO.
|
"auto" → system-declared via the stability scan; capped at MAX_AUTO_PINS, FIFO.
|
||||||
"manual" → user-declared; unlimited, never pruned.
|
"manual" → user-declared; unlimited, never pruned.
|
||||||
|
|
||||||
Design: docs/superpowers/specs/2026-05-13-note-version-pinning-design.md
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user