Skip to content

Spec 001 T005 and T006: a no-go, and the figures drawn in a step of their own - #95

Merged
adamjohnwright merged 13 commits into
mainfrom
t005/measure-13
Nov 2, 2026
Merged

adamjohnwright merged 13 commits into
mainfrom
t005/measure-13

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

Spec 001, T005 and T006. Replaces #83 to #94, with their reviews' findings fixed.

T005: a no-go. The render service, in the book's mix (85% reactions, 6% diagrams, 1% illustrations, 8% selections), cold, on a private copy of release 98's graph, on an 8-core host:

Concurrency Figures/s The book's 19,313 Core-seconds a figure
1 0.394 13 h 37 min 3.5
2 0.712 7 h 32 min 4.0
4 1.166 4 h 36 min 4.4
8 1.523 3 h 31 min 4.6

SC-003's figure budget needs 7.3 a second; the processors are the limit, and the cost per figure rises with concurrency. What T005 did not run, and why, is in research R7. Script: ci/measure/throughput.ts.

T006: the route taken. The figures are drawn by a step of their own before the book: pdf-export figures writes them to a folder with a manifest tying it to its release (new contracts/figures.md), after running the book's release-file and character checks; book --figures reads them, refuses a stale folder, and asks no render service. SC-003 is the book's time from figures already drawn; new SC-010 covers the figures' step. New task T046; T024, T029, T031, T037, T039 and T045 follow; contracts, research, data model, quickstart and plan updated to match.

From #83's review: the script chose a selection's ancestor differently from the book and stalled its client while sampling CPU; both fixed and all four settings re-run. R7's per-figure cost claims now follow from the table.

From #84's review: the folder's manifest carries a fingerprint (the graph's release number and checksum, the content service's release, the render service's build); figures refuses to resume another fingerprint and book faults it as stale-folder; a file is moved into place before its manifest entry; T046 follows T024. The runs' raw outputs are in ci/measure/results/2026-11-02/.

From #85's review: the fingerprint's parts are specified where they are read (render build in contracts/render.md, the graph checksum in data-model, the content release endpoint), and what it cannot see is stated and asked of WebsiteAngular (R10, T042). The manifest is an append-only journal; the script probes the service and fails on missing figures.

From #86's review: a figures folder is complete only with a closing complete line written after the end-of-run check; book faults incomplete-folder and no-checksum.

From #87's review: a failed run marks its folder failed and is never resumed; the book requires the last whole line to be complete.

From #88's review: each figure records the render build and content release that drew it, replacing the end check and the failed line; a resume cuts a torn tail before appending.

From #89's review: no resuming. figures needs an empty --out; a stopped run is rerun from scratch, so no folder mixes two runs or deployments. (This supersedes the per-figure build lines and resume rules described for #85–#88.)

From #90's review: a figure's own fault no longer stops the run: the rest are drawn and every fault named; a timeout is retried once like a 503.

From #91's review: a figure missing for want of an answer triggers the health read, so a dead service stops the run at once.

From #92's review: a breaker stops a run whose render service is stuck while its health still answers; a 504 is retried once; --timeout is a stated flag; missing figures carry a cause.

From #93's review: a 500 is the figure's own failure (failed) and never trips the breaker; the breaker's reset and clock are defined.

🤖 Generated with Claude Code

adamjohnwright and others added 13 commits November 2, 2026 18:27
…heir own

T005 measured the render service in the book's mix on an 8-core host: at most
1.41 figures a second (concurrency 8), limited by the processors at about 3
core-seconds a figure, against the 7.3 a second SC-003's figure budget needs.
The script is ci/measure/throughput.ts.

T006 takes a third route, the user's choice: `pdf-export figures` draws every
figure the book needs into a folder before the book (SC-010, contracts/figures.md),
and `book --figures` reads them, asking no render service. SC-003 is now the
book's time from figures already drawn. T046 adds the command; T024, T029, T037
and T039 follow; T005's single-figure measurement moves to T007.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e folder tied to its release

The measuring script now chooses a selection's ancestor as the book does
(nearestAncestor, a top-level pathway last), samples the container's CPU
without stalling the client, rejects bad numbers, and keeps the mix when a
selection has no ancestor. Re-run on all four settings: 1.523 figures a
second at best, 3.5-4.6 core-seconds a figure, rising with concurrency.

The figures' folder gets a manifest (release; each figure's kind, source and
selection): `book` refuses a stale or other release's folder, and `figures`
redraws only what changed. `figures` takes --files and runs the book's
release-file and character checks before drawing. The book's own run, contracts,
research R3/R7/R9/R10, data-model, quickstart, plan and tasks no longer say the
book asks the render service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…safe resume, task order

The figures' manifest now carries a fingerprint: the graph's DBInfo release
number and checksum, the release of the content service behind the render
service, and the render service's build. `figures` checks the content service's
release before drawing and refuses to resume a folder of another fingerprint;
`book` faults a folder of another graph as stale-folder. A file is moved into
place before its entry is written, and a file with no entry is drawn again.
T046 now follows T024, whose preflight it runs.

The script states its two limits (EHLDs told by hasEHLD; the render container's
CPU only), checks free memory and --render at the start, times out and reports
docker stats, and exits 1 with no extrapolation when its memory guard stops it.
The four runs' outputs are kept in ci/measure/results/2026-11-02/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd cannot see

The folder's fingerprint is now specified where it is read: the render
service's health `build` in contracts/render.md (refused when `unknown`), the
graph's DBInfo checksum in data-model (present on release 98; missing is a
fault), and the content service's release by GET /data/database/version. The
contract states plainly what it cannot see: `build` hashes the service's code,
not the website page that draws, and `--content` is the operator's word; both
are asked of WebsiteAngular (R10, T042). The manifest is an append-only journal
with one writer, a redrawn figure loses its old file first, the fingerprint is
read again at the end, and a refused resume names a new --out. T046 extends the
render stub with /health and adds a content stub; T024 adds the checksum.

The script probes the render service and checks memory before sampling, records
release, species, seed and figures asked, and exits 1 when any figure is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…when its run is

`figures` ends its journal with a `complete` line repeating the fingerprint,
written only once every figure is in place and the end-of-run check passed;
`book` faults a folder without it (incomplete-folder) and a graph without a
checksum (no-checksum). A torn last line is drawn again on resume. The
content-service check is described as what it is (the service named by
--content), the two asks of WebsiteAngular are named, and T046 tests the cases
the review listed. The script records the render service's build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esumed

A failed end-of-run check appends a `failed` line, and a folder holding one is
never resumed, so a rollback cannot complete a folder drawn under two builds.
`--out` must be empty or a resumable journal; anything else is refused. The
book reads a folder only when the journal's last whole line is a `complete`
equal to its first and no line is torn. A change undone within one run is
stated as unseen, with a per-drawing header asked of WebsiteAngular (T042) so
each figure can record what drew it. T046 now sits after T024 in tasks.md, and
both gain the tests the review listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ild that drew it

Each figure's journal line records the render build and content release read
just before it was drawn; a difference from the fingerprint is exit 1 at the
next figure, a resume redraws any figure drawn under another build, and the
book faults one as stale-figure. That replaces the end-of-run check and the
`failed` line, and covers a run killed mid-change and rolled back. A resume
cuts a torn last line off before appending; a figure failing its label check
gets no line; the EHLD summary's hash joins the fingerprint; `complete` is
equal to the first line field by field. T029's label tests write their own
figures' folder, `book`'s label check being a backstop for a folder changed by
hand. The script reports how many CPU samples its means rest on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…suming

Resuming a figures' folder kept producing ways for a folder to mix what two
runs or two deployments drew. `figures` now refuses a non-empty --out, and a
run that stops for any reason leaves a folder the book refuses; the next run
starts afresh. A figure's line is appended only after its file is synced and in
place and the render build and content release, read again, still match the
fingerprint. The book's folder checks (incomplete-folder, no-checksum,
stale-folder, stale-summary, stale-figure) are listed once, in
contracts/figures.md, and cited from cli.md and T024, which tests each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t stop the run

With no resuming, stopping at the first refused figure would cost a whole run
per fault. A figure's own fault (refused after the retry, timed out, failing its
label check) now leaves it with no file and no line while the run draws the
rest, then exits 1 naming them all; only a changed deployment stops the run at
once. A timeout is retried once like a 503 (T046 changes T014's client). The
book's stale-figure covers a chosen figure with no line and a line for an event
with no figure; T024 and T046 gain the matching tests, and T037 follows T046.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the run

A figure missing for want of an answer (no connection, a timeout, a 5xx) is
followed by the same health and content-release read as a drawn figure; no
answer or a change stops the run at once, so a hung or dead render service is
not waited out figure by figure. The service's own timeout sits below the
client's. A timeout is retried only in `figures`, not `serve`. The report is
written however the run ends; two lines for one figure are incomplete-folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a stated timeout

A render service stuck while its health still answers is caught by a breaker:
2 x --parallel figures in a row unanswered, or nothing drawn for 5 x
--timeout, stops the run. `figures` retries a 504 once as well as a timeout.
Its client timeout is a flag, --timeout (default 60 s), which the render
service's own timeout must sit below. The client names each missing figure's
cause (unanswered, refused, invalid); only unanswered triggers the health read
and counts towards the breaker. The report names causes and why a run stopped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s trip the breaker

A 500 (or another 5xx but 502-504) is the figure's own failure, cause `failed`:
it neither triggers the health read nor counts towards the breaker, so a run of
figures failing on one ancestor's diagram never stops the run. `unanswered` is
no connection, a timeout, a body cut short, or 502-504 after the retry. The
breaker's count resets on any other answer, and its clock runs from the start
of drawing or the last answer of any kind. A label failure is cause `label`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t does

T046 still gave the breaker's old rule; it now cites contracts/figures.md's
(count reset by any other answer, a clock from the start or the last answer),
with a test that slow 500s do not trip it. `failed` and `unanswered` no
longer overlap on 502-504, and a 502 is not said to be retried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant