record tags as built: ADR-0032

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.
This commit is contained in:
Claude Opus 5
2026-07-30 02:22:28 +06:00
committed by bdeshi
parent 1c431169c7
commit 60fcb6235d
3 changed files with 23 additions and 19 deletions
+17
View File
@@ -367,3 +367,20 @@ Consequence: cheap — the guard cannot be forgotten, and the test is one symlin
go through the root handle, so no helper may take a `string` path and open it directly, and the root is go through the root handle, so no helper may take a `string` path and open it directly, and the root is
held for the life of the process. held for the life of the process.
Revisit if: never. A hand-rolled cleaner is strictly worse. Revisit if: never. A hand-rolled cleaner is strictly worse.
## ADR-0032 — Tags: one global namespace, grouped in the listing
Date: 2026-07-30 · Status: accepted (the tag half of withdrawn ADR-0018, now built; the feed half is still
deferred in `ideas/deferred-decisions.md`)
Decision: `tags` is one global namespace across every section. `/tags/{term}/` lists everything carrying a
term, `/{section}/tags/{term}/` narrows it, and listings group by section. The URL form of a term is
lowercased with whitespace hyphenated, preserving script, so case is not a distinction and Bengali passes
through unchanged. Structural metadata with a fixed term set — `series` and its kin — is not a tag.
Why: cross-type discovery is the point of a single-author site; one term spanning a comic, a poem and a
photo essay is the feature, not the noise. Per-section pools would fragment that to solve a readability
problem the View solves by grouping. And a free-form cross-cutting tag behaves nothing like a fixed term
set that drives engine behaviour, so conflating them makes both worse.
Consequence: cheap — one Query with an optional section predicate serves both listings, and the second
caller is what earned `Query.Tag` rather than a guess. Expensive — tag hygiene is the author's discipline
since nothing scopes terms, so `check` owes a near-duplicate report; and `tags` is now reserved at the top
level and inside every section.
Revisit if: the pool becomes unusable in practice — and then the answer is curation, not namespacing.
+1 -1
View File
@@ -1,6 +1,6 @@
# State # State
**Verified against:** `449850c` on 2026-07-30 — update this line every change. **Verified against:** `fbf24f7` on 2026-07-30 — update this line every change.
If this file disagrees with the code, the code is right and this file is a bug. If this file disagrees with the code, the code is right and this file is a bug.
## Inventory ## Inventory
+5 -18
View File
@@ -76,26 +76,13 @@ sets a policy once for everything beneath it. Expensive — resolution must be c
runs per bundle, and the set of cascadable keys must stay declared or it becomes unbounded config. 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. Revisit if: cascade resolution shows up in a render-path profile.
## Taxonomies and feed shape ## Feed shape
Deferred because no tag pages and no feeds in the MVP. Deferred because no feeds exist yet. The tag half of this became ADR-0032 when tags were built.
## Global flat tags, declared structural taxonomies Recorded direction: `/feed.xml` carries every type declared `primary`, `/{section}/feed.xml` carries a
Date: 2026-07-28 · Status: accepted section, and `/tags/{term}/feed.xml` falls out of the same Query. Which types are `primary` is part of the
Decision: `tags` is one global namespace across every type — `/tags/{tag}/` lists everything carrying it, type declaration, so this and declared types land together.
`/{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 ## Extras