docs(6205): task_logs, task_claims, reply_shapes, design_starter_roles, db_maintenance_scheduler comments drop citations and incident stories
- db_maintenance_scheduler states the storage-stall hazard the startup timeout guards, without the dated outage. - design_starter_roles states why a missing role produces literals, without the call-site count, the operator's call date or a project's name. - The rest drop milestone/step labels and record citations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,16 +25,14 @@ logger = logging.getLogger(__name__)
|
||||
_JOB_ID = "db_maintenance_vacuum"
|
||||
_DEFAULT_HOUR = 4
|
||||
|
||||
# HOW LONG A COLD START WILL WAIT FOR THIS ONE SETTING (#4181).
|
||||
# HOW LONG A COLD START WILL WAIT FOR THIS ONE SETTING.
|
||||
#
|
||||
# This read is the FIRST database call in the app's `before_serving` hook, and
|
||||
# a lifespan hook that does not return is a worker that never serves. On
|
||||
# 2026-09-19 a host storage stall made one Postgres checkpoint of 14 buffers
|
||||
# take 281 seconds against a 1.3-second baseline; the app restarted into the
|
||||
# tail of it, this query hung with no deadline, Hypercorn killed the worker at
|
||||
# its 60-second lifespan timeout, and nothing retries a failed lifespan. A
|
||||
# five-minute disk hiccup became a three-hour outage that only a human restart
|
||||
# could clear.
|
||||
# a lifespan hook that does not return is a worker that never serves. If the
|
||||
# app starts while the database host is stalled on storage, a query with no
|
||||
# deadline hangs, Hypercorn kills the worker at its 60-second lifespan timeout,
|
||||
# and nothing retries a failed lifespan — a few minutes of disk trouble becomes
|
||||
# an outage that only a human restart clears.
|
||||
#
|
||||
# Three seconds because the honest requirement is "don't hold up the boot",
|
||||
# not "get the right hour". The value is one small indexed row on a local
|
||||
@@ -47,12 +45,11 @@ _STARTUP_READ_TIMEOUT = 3.0
|
||||
async def get_maintenance_hour() -> int:
|
||||
"""The configured run-hour (UTC, 0–23), clamped; default 04:00.
|
||||
|
||||
BOUNDED, because the caller is a lifespan hook (rule 156). The fallback is
|
||||
not new behaviour invented for the timeout — this function already answers
|
||||
`_DEFAULT_HOUR` for a value it cannot parse, and a database that will not
|
||||
answer in three seconds is the same class of "no usable value here". What
|
||||
changes is that the failure is now a logged line and a default hour rather
|
||||
than the application failing to start.
|
||||
BOUNDED, because the caller is a lifespan hook. The fallback is the same
|
||||
one this function gives a value it cannot parse: a database that will not
|
||||
answer in three seconds is the same class of "no usable value here". The
|
||||
failure is a logged line and a default hour rather than the application
|
||||
failing to start.
|
||||
|
||||
Degrading to the default is the right trade in both directions: the cost of
|
||||
being wrong is that a VACUUM runs at 04:00 instead of the configured hour,
|
||||
@@ -66,7 +63,7 @@ async def get_maintenance_hour() -> int:
|
||||
)
|
||||
except (TimeoutError, asyncio.TimeoutError):
|
||||
# WARNING, not debug: this is never normal, and it is the breadcrumb
|
||||
# that would have named #4181 in seconds instead of requiring
|
||||
# that names a stalled database in seconds instead of requiring
|
||||
# Postgres's own log to be read.
|
||||
logger.warning(
|
||||
"db maintenance: reading db_maintenance_hour exceeded %.1fs; "
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
WHY THIS EXISTS
|
||||
---------------
|
||||
A literal gets written into a stylesheet when there is no role to reach for.
|
||||
That is the mechanism, and this codebase produced a clean demonstration of it:
|
||||
the house style had no "text on a filled colour" role, so 76 call sites wrote
|
||||
a pure-white literal — not out of defiance, but because nothing existed to write
|
||||
instead (#2275). The correction was not a better ban list. It was declaring the
|
||||
missing role.
|
||||
A house style with no "text on a filled colour" role gets a pure-white literal
|
||||
at every call site that needs one — not out of defiance, but because nothing
|
||||
exists to write instead. The correction is not a better ban list. It is
|
||||
declaring the missing role.
|
||||
|
||||
So the useful moment is CREATION. A system whose roles are named on day one
|
||||
never presents the occasion for a literal, and never needs a list of values it
|
||||
forbids.
|
||||
|
||||
WHAT SHIPS AND WHAT DOES NOT (rule #115)
|
||||
----------------------------------------
|
||||
WHAT SHIPS AND WHAT DOES NOT
|
||||
----------------------------
|
||||
The ROLES ship: `surface-page`, `text-primary`, `action-destructive` are
|
||||
generic CSS-design vocabulary, not one operator's kit. Every install that has a
|
||||
page has a page background.
|
||||
@@ -25,21 +24,21 @@ appears anywhere in this file, and none should ever be added to it — a default
|
||||
palette would be this operator's palette wearing product clothes.
|
||||
|
||||
A valueless token is already legible downstream: `render_stylesheet` emits it as
|
||||
a commented-out declaration in its group (#2299), and `stylesheet_for_system`
|
||||
a commented-out declaration in its group, and `stylesheet_for_system`
|
||||
reports it under `valueless`. So a blank role reads as "to be decided" rather
|
||||
than as breakage, without anything new.
|
||||
|
||||
THE PREFIX IS THE INSTALL'S
|
||||
---------------------------
|
||||
`--fs-` is FabledSword's convention, not the product's. The prefix is a
|
||||
parameter with a neutral default; a caller that has a house convention passes
|
||||
it. Baking `--fs-` in would put one family's naming into every install.
|
||||
A house prefix such as `--fs-` is one family's convention, not the
|
||||
product's. The prefix is a parameter with a neutral default; a caller that has
|
||||
a house convention passes it. Baking one in would put one family's naming into
|
||||
every install.
|
||||
|
||||
FLAT, NOT PRESET
|
||||
----------------
|
||||
One list, every group individually skippable, all on by default (operator's
|
||||
call, 2026-08-03). Presets keyed to app shape — web / CLI / docs — were
|
||||
considered and rejected: they would require the product to hold opinions about
|
||||
One list, every group individually skippable, all on by default. Presets
|
||||
keyed to app shape — web / CLI / docs — are not offered: they would require the product to hold opinions about
|
||||
app categories, and a wrong category is worse than a generic list someone
|
||||
prunes once.
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The default shapes of a reply, and the moment each one rides (milestone 500).
|
||||
"""The default shapes of a reply, and the moment each one rides.
|
||||
|
||||
WHY THIS EXISTS
|
||||
|
||||
@@ -33,7 +33,7 @@ the operator asked for.
|
||||
|
||||
HOW TO WRITE ONE
|
||||
|
||||
Domain-neutral (rule 115): the work may be software, configuration,
|
||||
Domain-neutral: the work may be software, configuration,
|
||||
infrastructure or anything else a person drives through an agent. "Evidence"
|
||||
is what the reader could open to check; "verified" is whatever checking means
|
||||
for that work. The test beside this module refuses software-only vocabulary.
|
||||
@@ -80,12 +80,11 @@ class ReplyShape:
|
||||
CORE_KEY = "core"
|
||||
|
||||
# ~550 tokens at four characters a token. The core is paid for in every
|
||||
# session, so this is the ceiling the test holds it to — not a target. It sits
|
||||
# at ~2,050 with the judgement line (milestone 500 step 2): close to full, so a
|
||||
# sentence added now has to replace one.
|
||||
# session, so this is the ceiling the test holds it to — not a target. The
|
||||
# core sits close to it, so a sentence added has to replace one.
|
||||
CORE_BUDGET_CHARS = 2200
|
||||
# A slice is paid for only at its moment, and once per session; it can afford
|
||||
# more than the core and still has to stay a slice, not the old skill again.
|
||||
# more than the core and still has to stay a slice, not a whole skill.
|
||||
# The asks slice is the largest (~1,530) because it carries who decides what in
|
||||
# full: handing a question back is what an ask is tempted to do.
|
||||
SLICE_BUDGET_CHARS = 1600
|
||||
@@ -289,7 +288,7 @@ async def record_delivery(user_id: int | None, forms: dict[str, str], *, via: st
|
||||
logger.debug("reply shape delivery not recorded", exc_info=True)
|
||||
|
||||
|
||||
# ── The operator's view (milestone 500 step 5) ──────────────────────────
|
||||
# ── The operator's view ─────────────────────────────────────────────────
|
||||
|
||||
# The window the Settings view counts deliveries over — the same 30 days the
|
||||
# moments panel beside it reads, so the two count the same stretch of work.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""A task's claim — which session is working it right now (milestone 381 step 2).
|
||||
"""A task's claim — which session is working it right now.
|
||||
|
||||
`status` says where THE WORK stands and is durable: `in_progress` means
|
||||
committed to, not finished. It cannot also say "someone is on this now",
|
||||
@@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
||||
# session gone overnight reads as gone. The cost either way is stated rather
|
||||
# than hidden: readers show the age beside `live`, never the boolean alone.
|
||||
#
|
||||
# NOT A SETTING, deliberately (#4385 weighed it). Settings are per user, and a
|
||||
# NOT A SETTING, deliberately. Settings are per user, and a
|
||||
# claim is read by everyone who can see the task: a per-user lease would make
|
||||
# one shared task read as live to one collaborator and dead to another, and
|
||||
# "is anyone on this?" only means something if every reader gets the same
|
||||
@@ -131,9 +131,9 @@ async def bind_session(user_id: int, task_id: int, session_id: str) -> dict | No
|
||||
return claim_state(note, now)
|
||||
|
||||
|
||||
# --- The readers (milestone 381 step 3) ---------------------------------------
|
||||
# --- The readers --------------------------------------------------------------
|
||||
#
|
||||
# A claim nobody reads is the state before this milestone. SessionStart is the
|
||||
# A claim nobody reads is worth nothing. SessionStart is the
|
||||
# reader that pays rent to the session that set it, and it branches on the
|
||||
# `source` the host sends, because the same claim means different things
|
||||
# depending on what just happened to the context:
|
||||
@@ -290,7 +290,7 @@ async def claims_for_session_start(
|
||||
|
||||
|
||||
async def release_session(user_id: int, session_id: str) -> int:
|
||||
"""Release every claim the caller holds under `session_id` (milestone 381 step 4).
|
||||
"""Release every claim the caller holds under `session_id`.
|
||||
|
||||
Called by the plugin's SessionEnd hook: the session's context is about to
|
||||
stop existing, so the attention its claims record is ending too. A TIDY-UP,
|
||||
|
||||
@@ -16,11 +16,10 @@ _UNSET = object()
|
||||
|
||||
|
||||
async def _refresh_task_document(session, task_id: int) -> None:
|
||||
"""Re-embed the task whose work log just changed (#4251).
|
||||
"""Re-embed the task whose work log just changed.
|
||||
|
||||
A task's embedded document carries its logs, so a log written and not
|
||||
indexed is #4241's half-surface wearing different clothes: the entry is
|
||||
readable and unfindable, and the next session rebuilds what this one ruled
|
||||
indexed is half a surface: the entry is readable and unfindable, and the next session rebuilds what this one ruled
|
||||
out. Create, edit and delete all go through here — an edited log that keeps
|
||||
matching its old wording is the stale-vector problem `upsert_note_embedding`
|
||||
already refuses to leave behind for a body.
|
||||
@@ -51,9 +50,9 @@ async def create_log(
|
||||
content: str,
|
||||
duration_minutes: int | None = None,
|
||||
) -> TaskLog:
|
||||
# Whoever may WRITE the task may log on it (rule #78) — a collaborator on a
|
||||
# shared project included. This used to be a bare owner filter, which
|
||||
# refused exactly the person a shared task exists for.
|
||||
# Whoever may WRITE the task may log on it — a collaborator on a shared
|
||||
# project included. An owner-only filter would refuse exactly the person a
|
||||
# shared task exists for.
|
||||
if not await can_write_note(user_id, task_id):
|
||||
raise ValueError(f"Task {task_id} not found")
|
||||
async with async_session() as session:
|
||||
@@ -61,7 +60,7 @@ async def create_log(
|
||||
task = result.scalars().first()
|
||||
if task is None:
|
||||
raise ValueError(f"Task {task_id} not found")
|
||||
# Logging IS working the task, so it stamps the claim (milestone 381) —
|
||||
# Logging IS working the task, so it stamps the claim —
|
||||
# unless the work is over: a retrospective note on a closed task is not
|
||||
# a session picking it up.
|
||||
if task.status not in (TaskStatus.done.value, TaskStatus.cancelled.value):
|
||||
@@ -101,7 +100,7 @@ async def logs_for_task(
|
||||
exactly what a collaborator needs — `list_logs`' `TaskLog.user_id ==
|
||||
user_id` returns them an empty list, which reads as "no work has been
|
||||
done" rather than "not yours". The permission question is asked of the
|
||||
note through `can_read_note` (rule #78), so this is not an unscoped
|
||||
note through `can_read_note`, so this is not an unscoped
|
||||
read with the check left to the caller.
|
||||
2. Newest first, because the question a reader asks of a work log is
|
||||
"where does this actually stand", and the answer is the last entry.
|
||||
|
||||
Reference in New Issue
Block a user