The queue said to re-adopt the taxonomy decision when tags landed and I did not, so ideas/deferred-decisions.md claimed no tag pages existed while /tags/ had been serving for two commits. ADR-0032 records what the code actually does; only the feed half stays deferred, and it lands with declared types since feed membership is part of a type declaration. Folded from a separate state commit: state: bump verified-against; narrow the declared-types trigger Sequences do not need a type declaration — membership and ordering come from frontmatter — so the trigger for declared types is feeds or check, whichever lands first.
107 lines
7.1 KiB
Markdown
107 lines
7.1 KiB
Markdown
# 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.
|
|
|
|
## Feed shape
|
|
|
|
Deferred because no feeds exist yet. The tag half of this became ADR-0032 when tags were built.
|
|
|
|
Recorded direction: `/feed.xml` carries every type declared `primary`, `/{section}/feed.xml` carries a
|
|
section, and `/tags/{term}/feed.xml` falls out of the same Query. Which types are `primary` is part of the
|
|
type declaration, so this and declared types land together.
|
|
|
|
## 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.
|