Skip to content

spec 010: information architecture requirements, approved, with D0 executed - #80

Merged
iancooper merged 4 commits into
masterfrom
docs/spec-010-requirements
Aug 7, 2026
Merged

spec 010: information architecture requirements, approved, with D0 executed#80
iancooper merged 4 commits into
masterfrom
docs/spec-010-requirements

Conversation

@iancooper

Copy link
Copy Markdown
Member

Spec 010's requirements phase, start to finish: written, reviewed, amended, approved, and its one gating experiment run against the live site.

Documents only — no page under contents/ changes. D0's actual site changes shipped separately as #77, #78 and #79.

The URL model, verified 110/110

The central finding, and everything else rests on it. A published URL is:

<slug(SUMMARY H2 section)>/[slug(ancestor page)/]*<slug(filename)>

A predictor reading only SUMMARY.md reproduces 110 of 110 published URLs against the live sitemap — no misses, no extras. Consequences that changed the plan the README assumed:

  • The path on disk plays no part. contents/ is flat and stays flat, so 010 moves no files — the restructure rewrites SUMMARY.md and .gitbook.yaml.
  • All 763 internal links survive, because every target stays put. Only inbound external links break. Much narrower than the README's "URL breakage is the dominant risk" — though anchor-level links still break on splits, and redirects cannot fix fragments.
  • Renaming a section moves every URL beneath it. Re-titling is not cosmetic; it is a URL change.

urlmap.py packages it: bare prints the tree, --verify checks the live site (exit 2 if unreachable — not a pass), --redirects OLD emits the yaml block and refuses with exit 1 if a page would lose its URL.

What the review changed

Every load-bearing figure was re-derived and all of them held — the first review in this programme to find no wrong number.

  • §2.2 — Q1 answered, not deferred. The sentence that had blocked the spec was quoted from its second half. In full, its governing clause is "as long as a page exists for a path, GitBook won't be looking for a possible redirect" — keyed on the path, not the page. That is our exact case. Corroborated by the published resolution order.
  • §2.3 — the risk that replaced it. Automatic 307s are consulted before .gitbook.yaml, so the originally-planned experiment could have passed vacuously.
  • §2.4 — the U+200B provenance. GitBook's own published .gitbook.yaml example still ships the two zero-width spaces this repo carried until 2026-08-05, with the redirects: snippet inside the same code block. Writing D2 by pasting the documented example reintroduces the bug. P0-3 sharpened to: type the block, never paste it.
  • SUMMARY.md:154 reads ## Under the Hood with a leading space no other heading has. CommonMark tolerates three; at four GitBook silently folds three pages into Task Queues. It produced a wrong re-derivation during the review itself.
  • AC7 is per-split, with partial completion an explicit valid end state — without which Q7's "interruptible" was contradicted by an acceptance criterion demanding all 26 splits.
  • Q8–Q10 added rather than decided — AC6's threshold, llms.txt's summary source, whether urlmap.py gates CI. Design calls; deciding them here would have been guessing.

§16 — D0 as executed

Question Answer
Does GitBook auto-redirect a Git-synced SUMMARY.md rename? Yes — 307, 25s after #77, with no redirects block in the repo
Are .gitbook.yaml redirects read on this site? Yes — probe key redirected after #78; a control key absent from the block kept 404ing
Does the plan permit them? Yes

The obvious version of the experiment would have lied. Shipping the rename alongside its redirect goes green on the automatic 307 while proving nothing about the config.

Three findings for design:

  • The redirect value is a repository path, resolved to wherever the page currently publishes — so an entry does not go stale when its page moves again. The block is written once, not re-derived after every move.
  • D2/D3 are belt-and-braces, not load-bearing — but nothing establishes automatic redirects persist, so ship the block anyway.
  • Redirects cache with stale-while-revalidate=2592000 — 30 days. A wrong redirect outlives its fix at the edge. Verify before merging.

Plus the trap: every cached response reports 200, genuine 404s and redirects alike. Only an uncached request shows the true 307; the reliable tell is that no genuine page response carries a location: header.

Checks

linkcheck.py clean (112 files) · pagelint.py 0 errors, 802 warnings (the deliberate using debt) · urlmap.py --verify 110/110 after the rename, so the predictor tracks reality through a change.

Next: /spec:design.

🤖 Generated with Claude Code

https://claude.ai/code/session_012tcdwxVb8NmKaX2S6fvyFg

iancooper and others added 4 commits August 7, 2026 18:31
Starts Spec 010 (Information Architecture). The requirements phase turned up
one finding that reshapes the spec, so it was verified rather than assumed.

A published GitBook URL is <section-slug>/[<ancestor-slug>/]*<page-slug>, where
the section slug comes from the SUMMARY.md H2 and the ancestors come from list
nesting. The path on disk plays no part: contents/ is flat. A predictor reading
only SUMMARY.md reproduces 110 of 110 published URLs against the live sitemap.

Four consequences:

- Renaming a section moves every URL beneath it, with no file touched.
- 010 moves no files. It rewrites SUMMARY.md and .gitbook.yaml.
- All 763 internal links survive, because every target stays in contents/.
  Only inbound external links break, which shrinks the README's dominant risk.
- SUMMARY nesting deepens the URL; nine pages publish three segments deep.

So the redirect map is derivable rather than transcribed. urlmap.py packages
the predictor with a --verify mode (checks against the live sitemap; an
unreachable authority exits 2, it does not pass) and a --redirects mode that
diffs two SUMMARY revisions. Proven in both directions, not just trusted to
pass: a section rename emits 11 redirects, unnesting the distributed locks
emits 7, and a page dropped from the tree is refused with exit 1.

Two corrections recorded:

- The README's Out of Scope section is stale. It assigns page splitting to 011
  and puts page bodies out of scope; splitting moved into 010 on 2026-08-03 and
  worklist.md names 010 as its executor. Page bodies are in scope.
- The README's redirect example targets transports/RabbitMQConfiguration.md, a
  directory that does not exist. GitBook targets are repository paths, so every
  target here is contents/<FileName>.md.
- worklist.md section 1 says twelve of its 42 rows say keep. It is sixteen rows
  across fifteen pages; TickerQScheduler.md is listed in both 6a and 6e. The
  split/keep breakdown is 26/16. Seventh figure in this programme to be wrong,
  and it fits the pattern: the rules held, the counts did not.

Six open questions for the maintainer, the first of which is a gate rather than
a question: GitBook's docs say a redirect needs the old page removed, and our
pages are never removed. Land one section rename as a small PR and verify it on
the live site before committing to a 110-page rewrite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tcdwxVb8NmKaX2S6fvyFg
As scoped, 010 carries a full SUMMARY.md rewrite, a redirect mechanism, 26
page splits, three tools and four content fixes. The two demonstrator splits
in 011 took a session each, and this has twenty-six. Better to state the
question than leave a reviewer to find it.

The two halves are separable and section 2 is why: the restructure touches
SUMMARY.md and .gitbook.yaml and no page bodies, while the splits touch only
page bodies. They share no file but SUMMARY.md.

Recommends keeping one spec and sequencing it — restructure first, splits
second — because a split page needs a name, a SUMMARY entry and maybe a
redirect, and doing that against a tree about to change means touching it
twice, which is the reasoning that moved splitting out of 011. Sequencing
answers the size objection without reintroducing the double-touch. If the
reviewer disagrees, the clean cut is after the restructure lands.

Also flags Q1 and Q7 as the two that change the shape of the spec, and notes
Q1 should be settled by experiment rather than discussion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tcdwxVb8NmKaX2S6fvyFg
…sk that replaces it

Every load-bearing figure re-derived and all held — the first review in this
programme to find no wrong number. What changed is the reasoning, not the data.

Q1 is answered from the documentation rather than deferred. The sentence that
blocked it had been quoted from its second half; its governing clause is "as long
as a page exists FOR A PATH" — keyed on the path, not the page — and GitBook's
published resolution order falls through to .gitbook.yaml exactly when a URL
fails to resolve. Both describe our case.

What survives is a different risk and it inverts the experiment: automatic 307s
are consulted BEFORE .gitbook.yaml, so a section rename that "works" may prove
nothing about the redirect block while it sits silently broken. D0 now publishes
the rename twice — without the redirect entry, then with it — and compares. A
check that passes has not necessarily checked anything.

Also established: the two U+200B zero-width spaces this repo carried until
2026-08-05 came from GitBook's own .gitbook.yaml example, which still ships them
today, with the redirects: snippet inside the same code block. P0-3 goes from a
general caution to a targeted one — type the block, never paste it.

And SUMMARY.md:154 reads " ## Under the Hood" with a leading space no other
heading has. CommonMark tolerates three; at four GitBook folds three pages into
Task Queues without erroring. It produced a wrong re-derivation during this
review.

- §2.2/§2.3/§2.4 added; §2.1's "one open risk" removed
- D0 added, first; AC5 split into AC5a (before the rewrite) and AC5b (after merge)
- AC7 is per-split, partial completion an explicit valid end state — without which
  Q7's "interruptible" was contradicted by the acceptance criteria
- Q8–Q10 added rather than decided: AC6's threshold, llms.txt's summary source,
  whether urlmap.py gates CI

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tcdwxVb8NmKaX2S6fvyFg
… method mattered

D0 ran as PRs #77, #78 and #79. Both halves answered, and the answer to the first
one invalidated the planned second.

GitBook automatically redirects a Git-synced SUMMARY.md rename: 307 to the new
path 25 seconds after #77 merged, with no redirects block anywhere in the repo.
Shipping the rename alongside its redirect — the obvious way — would have gone
green on that automatic 307 while proving nothing about .gitbook.yaml.

That then made the planned step 2 untestable, because the moved path now resolves
and the resolution order never reaches .gitbook.yaml. Step 2 was redesigned around
a probe key for a path that had never existed, which no automatic redirect can
mask, plus a control key absent from the block. The probe redirected after #78;
the control kept 404ing. That pairing is what makes it a measurement.

Three findings that change how D2 and D3 get built:

- The redirect VALUE is a repository path and GitBook resolves it to wherever the
  page CURRENTLY publishes, so an entry does not go stale when its page moves
  again. The block can be written once rather than re-derived after every move.
- Automatic redirects appear to cover 010's whole case, but nothing establishes
  they PERSIST. Ship the block anyway as the safety net.
- Redirect responses cache with stale-while-revalidate=2592000 — 30 days. The
  probe key kept redirecting for over an hour after #79 removed it. A wrong
  redirect outlives its fix at the edge, so verify before merging, not after.

And the trap worth carrying: every cached response reports 200, including genuine
404s and genuine redirects. Only an uncached request shows the true 307. The
reliable tell is that no genuine page response carries a location: header.

Also measured: GitBook sync latency is 25–45 seconds. PyYAML is absent in this
environment and ruby -ryaml was used instead, so D7's parser choice needs deciding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tcdwxVb8NmKaX2S6fvyFg
@iancooper
iancooper merged commit 7d774d2 into master Aug 7, 2026
6 checks passed
@iancooper
iancooper deleted the docs/spec-010-requirements branch August 7, 2026 18:48
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