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:
2026-10-21 20:31:14 -04:00
co-authored by Claude Opus 5.5
parent 32ebc2b1d4
commit cc5401b2d3
5 changed files with 43 additions and 49 deletions
+12 -15
View File
@@ -25,16 +25,14 @@ logger = logging.getLogger(__name__)
_JOB_ID = "db_maintenance_vacuum" _JOB_ID = "db_maintenance_vacuum"
_DEFAULT_HOUR = 4 _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 # 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 # a lifespan hook that does not return is a worker that never serves. If the
# 2026-09-19 a host storage stall made one Postgres checkpoint of 14 buffers # app starts while the database host is stalled on storage, a query with no
# take 281 seconds against a 1.3-second baseline; the app restarted into the # deadline hangs, Hypercorn kills the worker at its 60-second lifespan timeout,
# tail of it, this query hung with no deadline, Hypercorn killed the worker at # and nothing retries a failed lifespan — a few minutes of disk trouble becomes
# its 60-second lifespan timeout, and nothing retries a failed lifespan. A # an outage that only a human restart clears.
# five-minute disk hiccup became a three-hour outage that only a human restart
# could clear.
# #
# Three seconds because the honest requirement is "don't hold up the boot", # 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 # 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: async def get_maintenance_hour() -> int:
"""The configured run-hour (UTC, 0–23), clamped; default 04:00. """The configured run-hour (UTC, 0–23), clamped; default 04:00.
BOUNDED, because the caller is a lifespan hook (rule 156). The fallback is BOUNDED, because the caller is a lifespan hook. The fallback is the same
not new behaviour invented for the timeout — this function already answers one this function gives a value it cannot parse: a database that will not
`_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". The
answer in three seconds is the same class of "no usable value here". What failure is a logged line and a default hour rather than the application
changes is that the failure is now a logged line and a default hour rather failing to start.
than the application failing to start.
Degrading to the default is the right trade in both directions: the cost of 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, 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): except (TimeoutError, asyncio.TimeoutError):
# WARNING, not debug: this is never normal, and it is the breadcrumb # 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. # Postgres's own log to be read.
logger.warning( logger.warning(
"db maintenance: reading db_maintenance_hour exceeded %.1fs; " "db maintenance: reading db_maintenance_hour exceeded %.1fs; "
+13 -14
View File
@@ -3,18 +3,17 @@
WHY THIS EXISTS WHY THIS EXISTS
--------------- ---------------
A literal gets written into a stylesheet when there is no role to reach for. 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: A house style with no "text on a filled colour" role gets a pure-white literal
the house style had no "text on a filled colour" role, so 76 call sites wrote at every call site that needs one — not out of defiance, but because nothing
a pure-white literal — not out of defiance, but because nothing existed to write exists to write instead. The correction is not a better ban list. It is
instead (#2275). The correction was not a better ban list. It was declaring the declaring the missing role.
missing role.
So the useful moment is CREATION. A system whose roles are named on day one 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 never presents the occasion for a literal, and never needs a list of values it
forbids. 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 The ROLES ship: `surface-page`, `text-primary`, `action-destructive` are
generic CSS-design vocabulary, not one operator's kit. Every install that has a generic CSS-design vocabulary, not one operator's kit. Every install that has a
page has a page background. 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. palette would be this operator's palette wearing product clothes.
A valueless token is already legible downstream: `render_stylesheet` emits it as 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 reports it under `valueless`. So a blank role reads as "to be decided" rather
than as breakage, without anything new. than as breakage, without anything new.
THE PREFIX IS THE INSTALL'S THE PREFIX IS THE INSTALL'S
--------------------------- ---------------------------
`--fs-` is FabledSword's convention, not the product's. The prefix is a A house prefix such as `--fs-` is one family's convention, not the
parameter with a neutral default; a caller that has a house convention passes product's. The prefix is a parameter with a neutral default; a caller that has
it. Baking `--fs-` in would put one family's naming into every install. a house convention passes it. Baking one in would put one family's naming into
every install.
FLAT, NOT PRESET FLAT, NOT PRESET
---------------- ----------------
One list, every group individually skippable, all on by default (operator's One list, every group individually skippable, all on by default. Presets
call, 2026-08-03). Presets keyed to app shape — web / CLI / docs — were keyed to app shape — web / CLI / docs — are not offered: they would require the product to hold opinions about
considered and rejected: they would require the product to hold opinions about
app categories, and a wrong category is worse than a generic list someone app categories, and a wrong category is worse than a generic list someone
prunes once. prunes once.
""" """
+6 -7
View File
@@ -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 WHY THIS EXISTS
@@ -33,7 +33,7 @@ the operator asked for.
HOW TO WRITE ONE 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" infrastructure or anything else a person drives through an agent. "Evidence"
is what the reader could open to check; "verified" is whatever checking means 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. for that work. The test beside this module refuses software-only vocabulary.
@@ -80,12 +80,11 @@ class ReplyShape:
CORE_KEY = "core" CORE_KEY = "core"
# ~550 tokens at four characters a token. The core is paid for in every # ~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 # session, so this is the ceiling the test holds it to — not a target. The
# at ~2,050 with the judgement line (milestone 500 step 2): close to full, so a # core sits close to it, so a sentence added has to replace one.
# sentence added now has to replace one.
CORE_BUDGET_CHARS = 2200 CORE_BUDGET_CHARS = 2200
# A slice is paid for only at its moment, and once per session; it can afford # 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 # 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. # full: handing a question back is what an ask is tempted to do.
SLICE_BUDGET_CHARS = 1600 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) 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 # 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. # moments panel beside it reads, so the two count the same stretch of work.
+5 -5
View File
@@ -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 `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", 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 # 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. # 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 # 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 # 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 # "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) 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 # 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 # `source` the host sends, because the same claim means different things
# depending on what just happened to the context: # 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: 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 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, stop existing, so the attention its claims record is ending too. A TIDY-UP,
+7 -8
View File
@@ -16,11 +16,10 @@ _UNSET = object()
async def _refresh_task_document(session, task_id: int) -> None: 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 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 indexed is half a surface: the entry is readable and unfindable, and the next session rebuilds what this one ruled
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 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` matching its old wording is the stale-vector problem `upsert_note_embedding`
already refuses to leave behind for a body. already refuses to leave behind for a body.
@@ -51,9 +50,9 @@ async def create_log(
content: str, content: str,
duration_minutes: int | None = None, duration_minutes: int | None = None,
) -> TaskLog: ) -> TaskLog:
# Whoever may WRITE the task may log on it (rule #78) — a collaborator on a # Whoever may WRITE the task may log on it — a collaborator on a shared
# shared project included. This used to be a bare owner filter, which # project included. An owner-only filter would refuse exactly the person a
# refused exactly the person a shared task exists for. # shared task exists for.
if not await can_write_note(user_id, task_id): if not await can_write_note(user_id, task_id):
raise ValueError(f"Task {task_id} not found") raise ValueError(f"Task {task_id} not found")
async with async_session() as session: async with async_session() as session:
@@ -61,7 +60,7 @@ async def create_log(
task = result.scalars().first() task = result.scalars().first()
if task is None: if task is None:
raise ValueError(f"Task {task_id} not found") 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 # unless the work is over: a retrospective note on a closed task is not
# a session picking it up. # a session picking it up.
if task.status not in (TaskStatus.done.value, TaskStatus.cancelled.value): 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 == exactly what a collaborator needs — `list_logs`' `TaskLog.user_id ==
user_id` returns them an empty list, which reads as "no work has been 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 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. read with the check left to the caller.
2. Newest first, because the question a reader asks of a work log is 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. "where does this actually stand", and the answer is the last entry.