Skip to content
Skip to main content
Novel Systems home
Decision log
D-066August 5, 2026

This file is published, and /changelog says what it is instead

Decided

this file is rendered at /engineering/decisions, parsed from the markdown rather than transcribed into a data module, one static page per entry; and /changelog now states in its own copy that the release history it shows is the roadmap in release-note form rather than an audited record of what was merged, and points at this log for the record that is.

Affects: lib/decision-log.ts (new), components/engineering/decision-prose.tsx (new), app/engineering/decisions/page.tsx (new), app/engineering/decisions/[slug]/page.tsx (new), app/changelog/page.tsx, app/engineering/page.tsx, app/sitemap.ts, config/routes.ts, config/site.ts, scripts/check-links.mjs.

What was wrong. lib/changelog.ts opens with a note admitting the five- release version history is illustrative. The note was true, it was accurate, and it was visible only to somebody who had cloned the repository. A buyer reading /changelog saw five dated releases with deprecation deadlines and had no reason to read them as anything other than shipped history. An honest disclosure in a private file is not a disclosure; it is a record of having thought about it.

Why not delete the release history. It was the first option and it is worse. The contract dates on that page — the 2027-01-15 removal deadline for the unversioned paths, the twelve-month deprecation window — are real commitments that an integrator plans against, and they are published nowhere else. Deleting the page removes a thing a buyer needs in order to remove a thing a buyer might misread. The page stays, with a panel that says plainly what it is and what it is not, and a link to the log that is the audited part.

Why parse rather than transcribe. The alternative shape was lib/decisions.ts holding the entries as data, the way lib/changelog.ts holds releases. Less parser code, and wrong within a week: the file that gets edited when a decision is made is this one, because it is the file that is already open. A transcribed copy diverges at the next entry and nobody notices — the exact failure mode this codebase keeps designing against, and the reason check:claims exists. Reading the source of truth means the site cannot lag the record, at the cost of a parser that has to be strict enough to fail loudly. It throws on an entry it cannot read, because a build that fails is a page nobody sees and a build that succeeds with a hole in it is a page everybody sees.

Three header conventions, and none of them were rewritten. Forty-four entries state the answer as **Decision:**, five as **Decided:**, and D-001 to D-016 as neither — they predate both and open with a ### The decision or ### What was decided heading instead. The tidy move is to rewrite sixteen entries into the newest shape. That is editing the record to suit the renderer, in a log whose entire claim is that it is the record, and it is the same instinct D-016 refused when it left a hundred flagged claims unedited. The parser reads all three. Where the summary came from the prose rather than from a field, the entry's page omits the "Decided" callout so it does not print the paragraph twice, and the index card shows it anyway because a card needs a sentence.

The link checker learned about dynamic segments. scripts/check-links.mjs walked app/ collecting one route per page.tsx and matched hrefs against that set exactly, so /engineering/decisions/[slug] was stored as a literal and every decision link would have been reported dead. It now enumerates a dynamic route's real values from the same artifact the pages render from — the ## D-nnn headings in this file — and exits non-zero if it reads none, because a checker that silently enumerates nothing passes every link it can no longer verify. The weaker alternative, matching dynamic routes by shape alone, would accept /engineering/decisions/d-0652 and hand the reader a 404, which is the defect the script exists to prevent. A dynamic route with no enumerator still falls back to shape matching, and that is the honest weaker answer for a route nobody has taught it about yet.

Verified in both directions, per D-032. A literal link to d-001 passes and a literal link to d-0652 is reported dead with its file; renaming the ## D- headings so the enumerator reads zero ids fails the check with an explanation rather than passing 178 links it could not resolve.

What would make this wrong. If an entry is ever written whose decision cannot be recovered from a field, a ### The decision heading, or its opening paragraph, the build fails and the fix is to state the decision, not to loosen the parser. And if this file grows past the point where parsing it at build time is noticeable, the answer is to cache the parse, not to transcribe it.