harness: withdraw five pre-code ADRs to deferred ideas

Twenty-seven ADRs existed before a line of Go. Five specified the shape of
unbuilt mechanisms — cache validity, declared types, the settings cascade,
taxonomies, extras — which breaks the rule against abstraction before a
second concrete use, in prose where the counters cannot see it. They move to
ideas/deferred-decisions.md as recorded intent and return shaped by whatever
implements them.

Citations retargeted throughout; where one was decoration the rule now stands
on its own reasoning. Type declarations and the cascade drop to [spec] with
the MVP behaviour stated instead, so the first prompts have less to build.
conventions.md names http.ServeMux as the router, closing a hole that invited
hand-rolling a path splitter. The ADR gate now checks a number is registered
in the log rather than headed by an entry, so withdrawals resolve and invented
numbers still fail. Two architecture invariants corrected: identity no longer
implies a required language suffix, and the duplicated permalink clause is gone.
This commit is contained in:
Claude Opus 5
2026-07-30 01:01:38 +06:00
committed by bdeshi
parent 02268f9121
commit 99f8c730b0
13 changed files with 180 additions and 144 deletions
+1
View File
@@ -39,6 +39,7 @@ you, not the agent; `verify.sh` keeps it honest without reading it into context.
## Index
- [deferred-decisions.md](deferred-decisions.md) — five pre-code ADRs demoted to intent; each returns when something implements it. **parked**
- [specs-as-secondary-artifacts.md](specs-as-secondary-artifacts.md) — optional per-feature specs, derived by default, plus the named-test convention. **parked**
- [engine-design-review.md](engine-design-review.md) — open design decisions for a multi-type site; items graduate to ADRs one at a time. **parked**
- [token-conservation.md](token-conservation.md) — cut agent token cost without losing output quality. **parked**
+119
View File
@@ -0,0 +1,119 @@
# Deferred decisions — recorded intent, not binding shape
Status: parked
Raised: 2026-07-30
Five ADRs were written before any code existed. They describe a direction worth keeping, but their
*shape* is speculation: the harness's own rule is no abstraction before its second concrete use, and a
field-level specification of an unbuilt mechanism breaks that rule in prose instead of code.
Their entries in `decisions.md` are now `Status: intent` stubs pointing here, so citations still resolve
and nothing is lost. Each returns as a full ADR when something implements it — and the implementation,
not this file, decides the shape.
## Cache validity model
Deferred because no cache exists, and the harness forbids one until requests feel slow.
## Cache validity is one record with five axes
Date: 2026-07-28 · Status: accepted
Decision: a cache entry carries a validity record — content dependencies, interaction fragment refs,
`valid-until`, engine+template epoch, and a cacheable flag — and is served only while all five hold.
`valid-until` is the minimum of the windows the render's Stages declare; Stages reach the clock through
an injected accessor, never `time.Now()`. An optional scheduled Effect may pre-warm entries, but
scheduling is never what makes a page correct.
Why: invalidation has five causes, not one. Content edits and interactions were already modelled; time
(old-article banner, relative dates, future-dated publication), epoch (a template fix silently serving
old HTML), and opt-out (search, a random-page route) were not. A scheduled re-render sweep expresses
only the time axis, and coarsely — relative dates would need a sweep finer than their own granularity.
Consequence: cheap — the record maps onto HTTP semantics (ETag from deps plus epoch, `Expires` from
`valid-until`, `no-store` for opt-out), so a reverse proxy or CDN in front is correct with no extra
code, and a new time-dependent Stage needs no central registry because windows compose by minimum.
Expensive — the Stage contract gains a return channel, and a Stage that reads the clock without
declaring a window would serve staleness silently, which is why the clock accessor is enforced by
`verify.sh` rather than trusted.
Revisit if: windows cannot express some invalidation cause — then add an axis, do not add a mechanism.
## Declared content types
Deferred because the MVP can default a type from its section directory; a declaration file earns itself at the seventh type or the first per-type override.
## Content types are declared, not coded
Date: 2026-07-28 · Status: accepted
Decision: each content type is a declaration — section, default view, ordering rule, feed membership,
required frontmatter, whether titleless is legal, applicable taxonomies. The binary embeds a default set
(`post`, `comic`, `art`, `writing`, `page`, `status`); a declaration file in the site root extends or
overrides it. Adding a type costs a declaration plus a template and no core change; an unparseable or
incoherent declaration is a loud startup failure.
Why: six types already carry per-type behaviour, described in prose. Prose becomes either a `switch type`
in the core — forbidden, and it would grow with every new type — or unwritten template convention. A
declaration is the only form that keeps the seventh type as cheap as the sixth.
Consequence: cheap — a new type is a site-repo change with no engine deploy (ADR-0011), and the content
`check` command gets its validation rules from the same declarations for free. Expensive — the engine
must treat the type set as data, so nothing may assume a fixed list, and per-type behaviour reachable
only from code (a bespoke View) still needs a template rather than a branch.
Revisit if: a type needs behaviour no declaration can express — then it is a View or an Effect, not a
new field on every type.
## Settings cascade
Deferred because frontmatter alone covers the MVP; a cascade earns itself when a section-wide policy is actually wanted.
## Settings cascade: site → section → bundle
Date: 2026-07-28 · Status: accepted
Decision: settings resolve down a cascade — site declaration, then each enclosing section's `_index`,
then the bundle's own frontmatter — with the nearest explicit value winning. The cascade carries stage
toggles, view selection, taxonomy defaults, cache flags, and metadata defaults; it carries *declared*
keys only, never arbitrary engine internals. Stages apply to everything by default and are switched off
by a cascade key, not by a predicate compiled into the stage.
Why: stages are mostly no-ops where they do not apply, and their early return is a check on content
shape ("no images here") which is type-independent and belongs in the stage anyway. The cases that really
need control are narrower and cut across types — dithering wrong on one diagram, autolinking wrong in one
poem — which a per-type stage set cannot express and a cascade can. It also unifies with template
selection, which wants the same resolution order.
Consequence: cheap — one resolution rule covers rendering, presentation and metadata defaults; a section
sets a policy once for everything beneath it. Expensive — resolution must be cheap and cached, since it
runs per bundle, and the set of cascadable keys must stay declared or it becomes unbounded config.
Revisit if: cascade resolution shows up in a render-path profile.
## Taxonomies and feed shape
Deferred because no tag pages and no feeds in the MVP.
## Global flat tags, declared structural taxonomies
Date: 2026-07-28 · Status: accepted
Decision: `tags` is one global namespace across every type — `/tags/{tag}/` lists everything carrying it,
`/{section}/tags/{tag}/` narrows to a section, and listing views group results by type. Structural
metadata with known terms that drives behaviour — `series`, `medium`, `genre` — is a *declared* taxonomy
on the type (ADR-0014) and never enters the tag pool. Feeds follow the same shape: `/feed.xml` carries
every type declared `primary`, `/{section}/feed.xml` carries a section, `/tags/{tag}/feed.xml` falls out
of the same Query.
Why: cross-type discovery is the point of a single-author site — one tag spanning a comic, a poem and a
photo essay is a feature. Per-section tag pools would fragment that for a readability problem better
solved by grouping in the View. But a tag is free-form and cross-cutting, while a structural taxonomy has
a fixed term set and changes what the engine does; conflating them makes both worse.
Consequence: cheap — one Query with an optional section predicate serves tag pages, section tag pages and
their feeds. Expensive — tag hygiene is now the author's discipline, since nothing scopes them; the
content `check` command should report near-duplicate terms.
Revisit if: the tag pool becomes unusable in practice — and then the answer is curation, not namespacing.
## Extras
Deferred because not in the MVP, and its shape should be discovered by building it.
## Extras: local assets, enumerated and browsable
Date: 2026-07-29 · Status: accepted
Decision: a bundle may hold a directory of supporting files — default `extras/`, named by a cascade key —
which the bundle scanner **skips entirely**: a `.md` in there is an asset, never a bundle. The engine
enumerates it as a tree, classifies each entry by extension, renders the ones it can (markdown, plain
text), and serves the rest as bytes behind the ADR-0024 guard. Two behaviours on one route:
`…/extras/{path}` renders the listing with that entry selected, `?raw` returns the bytes. Sorted by
filename; excluded from feeds, queries and search.
Why: drafts, notes and logs are worth publishing as artefacts of the process, and they are not bundles —
no frontmatter, no identity, no language variants. `architecture.md` already defines a Bundle as carrying
local assets; the only thing missing was enumerating them instead of merely referencing them relatively.
Consequence: cheap — no new primitive, and selecting an entry is an ordinary link, so the whole feature
works without JavaScript. Expensive — the scanner needs an exclusion rule it did not have, and the extras
segment becomes a name no child of a bundle may use, which is why it is a cascade key rather than a
constant.
Revisit if: extras need per-file metadata — and then they are bundles after all, and this entry was wrong.
+3 -3
View File
@@ -6,9 +6,9 @@ Raised: 2026-07-28
One review, many decisions. Each item below graduates independently: when one is settled it becomes an
ADR (or a change to its owning doc) and is struck from this list. When the list empties, delete the
file. Graduated so far: site root external to the engine repo (ADR-0011), Effect as the sixth
primitive (ADR-0012), the cache validity model (ADR-0013), declared content types (ADR-0014),
NFC normalisation and slug derivation (ADR-0015), sequence position (ADR-0016), the settings cascade
(ADR-0017), taxonomies and feeds (ADR-0018), template composition (ADR-0019), feature locality (ADR-0027).
primitive (ADR-0012), NFC normalisation and slug derivation (ADR-0015), sequence position (ADR-0016),
template composition (ADR-0019), feature locality (ADR-0027). Five more were recorded and then deferred
to `deferred-decisions.md`: cache validity, declared types, the cascade, taxonomies, extras.
## Why it came up