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"
|
_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; "
|
||||||
|
|||||||
@@ -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.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user