Reference entity type, generated references roster, and the docs Appendix
Status: open/accepted
Summary
Section titled “Summary”- A new
referenceentity type (prefixRF, incrementing ids,docs/planning/references/) catalogs external documentation, research, and artifacts as first-class entities: an optionalurlin frontmatter, a transcludable Summary, and a Material section that may summarize, copy, or transclude the source. (§1, §2) docs/references.mdis a new build artifact — the references roster, generated from reference entities besideindex.mdandglossary.md. Thedocs generateartifact list grows toindex | glossary | references | site. (§3)- Terms and references move into the Appendix:
index.md’s existing# Appendixgains generated Glossary and References sections, and the docs site’s sidebar gains a trailing Appendix group holding the Glossary and References pages. (§4) - The site’s Architecture → Data model page becomes generated:
its identity and roster tables derive from the
PREFIXESregistry and the entity schemas in the samedocs generate sitepass, so the page can never again claim “nine entity types” after the tenth ships. (§5) - The type is named
reference, notevidence— D-ORMG-data-model’s deferred Evidence is a high-volume per-claim support record; Reference is a curated, low-volume source catalog. Evidence stays deferred. (Options considered) - Migration is the dogfood: the external sources SDLC already leans on (Obsidian Bases, Eta, Starlight, Bun, lefthook, JSON Schema, the ADR lineage, …) are minted as the seed corpus. (Migration)
Context
Section titled “Context”SDLC’s planning corpus cites external material constantly — Obsidian’s
Bases and transclusion behavior shape the wiki contract, Eta and
Starlight shape the generators, JSON Schema draft 2020-12 shapes every
entity schema — but none of it is catalogued. A sweep of
docs/planning/ finds the only repeated external URLs buried inline
(help.obsidian.md/bases twice); everything else the project leans on
is cited from memory or not at all. There is no place to put a
research note, a fetched excerpt, or a pointer to a paper where it can
carry identity, lifecycle, and inbound links.
D-B4CA-term-entity-and-generated-glossary built the pattern this
decision reuses: a curated vocabulary became a first-class entity type
plus a generated roster, assembled by sdlc docs generate in the same
deterministic pass as index.md. References are the same shape of
problem — a curated catalog that wants per-item identity and a
generated front-door view — pointed at external sources instead of
internal vocabulary.
Two adjacent gaps ride along. index.md already ends in an
# Appendix (holding only Conventions), and the site has no appendix
at all — the glossary sits as a top-level orphan in the sidebar. And
the site’s hand-written Data model page opens with “SDLC tracks
nine entity types” — already false since Term shipped, falser
once Reference does. Both are drift the generator family exists to
eliminate.
Decision
Section titled “Decision”1. The reference entity type
Section titled “1. The reference entity type”| Aspect | Choice |
|---|---|
| Type name | reference — one instance is one external source |
| Package | plugin/lib/model/entities/reference/ |
| Instances dir | docs/planning/references/ (pluralize("reference") — zero wiring) |
| Prefix | RF (R reserved for Requirement per D-0002-entity-identifier-shape; RE mis-parses as requirement) |
| Numbering | Incrementing (RF-0001) — curated, low-volume catalog per D-0002’s split |
| Filename | RF-NNNN-<slug>.md, e.g. RF-0001-obsidian-bases.md |
| Wikilink | RF-NNNN-<slug>, piped for display: Obsidian Bases |
| Create op | sdlc reference create [<slug>] --title <name> [--url <url>] — slug derived from --title when omitted (mirrors term create) |
2. Frontmatter and body shape
Section titled “2. Frontmatter and body shape”Frontmatter — common fields per the shared CommonFrontmatter base
(plugin/lib/model/entities/_common.ts), plus:
| Field | Required? | Shape | Default | Notes |
|---|---|---|---|---|
type | required | reference | Validator-dispatch tag | |
id | required | ^RF-[0-9A-Z]{4}$ | Immutable; matches filename | |
title | required | string | The source’s name (Obsidian Bases, Nygard — Documenting Architecture Decisions) | |
url | optional | string, format: uri | The external location. Optional — a reference may be a book, a local artifact, or offline material | |
status | required | enum (see Lifecycle) | open/active | |
related | optional | list of wikilinks | [] | The Cited by column: the decisions/standards/tasks that lean on this source |
Body manifest (order: lenient, allow_unknown: true):
| Section | Required? | Notes |
|---|---|---|
| Summary | required | 1–3 sentences: what the source is and why SDLC leans on it. The paragraph carries ^summary for transclusion. Table-cell discipline: one paragraph, no lists, no headings |
| Material | optional | The substance, in whichever form fits: a prose summary, copied excerpts (quoted, with attribution), or transcluded artifacts (... embeds of files stored alongside the entity) |
| Notes | optional | Commentary that is SDLC’s own — how the source is applied here, caveats, version pinning |
Lifecycle — identical to Term’s, so the generator buckets apply verbatim:
| Status | Meaning | Roster table |
|---|---|---|
open/active | In use — cited by current decisions/standards | Active |
open/draft | Captured, not yet vetted or not yet load-bearing | Emerging |
closed/retired | No longer leaned on; kept for link stability | Obsoleted |
3. The generated references artifact
Section titled “3. The generated references artifact”docs/references.md is a build artifact beside index.md and
glossary.md: never hand-edited, regenerated by the same pass,
byte-stable across re-runs.
| Aspect | Choice |
|---|---|
| Artifact name | references — ARTIFACT_NAMES becomes index | glossary | references | site, canonical order |
| Surface | sdlc docs generate references for a scoped run; bare invocation includes it |
| Columns | Reference | Summary | Link | Cited by — Reference is a bold piped wikilink, Summary transcludes ^summary, Link renders [<host>](<url>) (blank when no url), Cited by renders related: |
| Ordering | Alphabetical by title, case-insensitive — ids carry identity, not order (glossary precedent) |
| Bucketing | bucketFor reused: active table + Emerging / retired follow-up with status, omitted when empty |
| Empty corpus | Preamble renders, tables omit — references.md is born generated before the seed corpus lands |
The Link cell’s text is the URL’s host (help.obsidian.md) —
deterministic, compact, and tells the reader where they’re going
without the full URL’s noise.
4. Appendix — the index and the site
Section titled “4. Appendix — the index and the site”The vocabulary and the source catalog are supporting material: they back the reference sections rather than being destinations themselves. Both front doors put them in an Appendix.
index.md — the existing # Appendix (today: Conventions only)
grows two generated sections ahead of Conventions:
| Section | Content |
|---|---|
## Glossary | The terms table (Term / Definition / Source) — the same buildGlossaryData output glossary.md renders, second projection, one pass, zero drift — plus a pointer line to glossary |
## References | The references table per §3, plus a pointer line to references |
IndexData gains glossary and references sections; the template
renders them with the existing activeTable/followupTable helpers.
The standalone artifacts remain the wikilink targets (glossary,
references) — inbound links never point at index headings.
The site (<docs_site>/ — the root named by docs_site: in
sdlc.yaml, currently sites/df-docs/) — the sidebar gains a
trailing Appendix group; the top-level Glossary entry moves into
it:
Overview / Architecture / Reference / Planning / Roadmap / Changelog / Appendix ├─ Glossary └─ ReferencesThe site References page is generated like the site Glossary page:
summaries inlined (the site renders no Obsidian transclusions), the
url as a real link, cited-by as code-formatted ids.
Per-entity child pages: every term and reference also gets a full
generated page — /glossary/<slug>/, /references/<slug>/ — rendered
from the entity body (wikilinks to sibling terms/references resolve to
child routes; other targets code-format). The roster cells link
through to the child pages and carry <a id="<slug>"> row anchors;
the sidebar’s Appendix groups gain collapsed per-entity children that
navigate within the roster pages via those anchors, carried by the
generated sidebar module (<docs_site>/src/generated/sidebar.mjs —
the whole sidebar is generated per
D-0010-deterministic-site-assembly, which generalizes this
projection to every entity type). The same module feeds a Starlight
route middleware (<docs_site>/src/routeData.ts) that injects the
entries into the rosters’ On this page nav — the tables carry no
headings for the default TOC to find.
5. The Data model page becomes generated
Section titled “5. The Data model page becomes generated”<docs_site>/src/content/docs/architecture/data-model.md was
hand-written with a hardcoded entity count that drifted. It joins the
site artifact pass:
| Block | Source |
|---|---|
| Narrative prose (where schemas live, identity rationale, relationships diagram, status-field walk) | Static blocks owned by the generator — hand-authored knowledge, reviewed as code |
| Identity table (Type / Prefix / Numbering / Wikilink shape) | Each schema’s id pattern (prefix) joined with the PREFIXES registry (plugin/lib/model/identifier.ts) for numbering |
| Entity roster table (Type / Role / Key fields beyond the common base) | Entity packages: definition.md’s Purpose first sentence (Role) and the Zod schema’s properties minus the CommonFrontmatter base’s |
| The type count | Derived — "SDLC tracks **N entity types**" is computed, never typed |
The page carries the AUTO-GENERATED banner and the same
writeIfChanged idempotency as every other site page. The
relationships diagram stays static prose: it encodes design intent
(forward-linking rationale) that no schema field carries.
Options considered
Section titled “Options considered”| Choice | Options | Taken | Why |
|---|---|---|---|
| Type name | reference, source, revive evidence (D-ORMG-data-model deferred row) | reference | Evidence is per-claim support — high-volume, base-36, machine-leaning; this is a curated catalog of sources, different lifecycle and volume. Reviving Evidence for it would bend D-ORMG’s definition. source collides with the glossary’s Source column and reads ambiguous in prose |
| Prefix | RF, RE, REF | RF | R reserved for Requirement (D-0002); RE reads “requirement” as easily as “reference”; three letters is budget for no gain. RF parses single-pass |
| Numbering | incrementing, base-36 | incrementing | Curated, low-volume, human-cited — same criteria as Term |
url placement | frontmatter field, References body section, both | frontmatter | Machine-readable (link checking, future fetch/archive tooling) and renderable as a roster column. The body’s Material section holds content, not location |
| Link column text | full URL, fixed label (“link”), URL host | host | Deterministic, compact, informative; full URLs wreck table layout, fixed labels say nothing |
| Index placement | new top-level sections, the existing # Appendix | Appendix | The user-facing reference sections answer “what is the project”; vocabulary and sources are supporting material. The Appendix exists; Conventions already models the pattern |
| Index Appendix content | pointer lines only, full generated tables | full tables | The index already inlines every other entity table — pointers would make terms/references the only second-class types. Same pass renders both projections, so duplication carries zero drift risk |
| Data model page | leave hand-written, fully generated, generated tables + static narrative | tables + static narrative | The tables are pure registry/schema projections — generating them kills the drift class. The narrative encodes judgment no schema carries; forcing it through a generator buys nothing |
| Site references page | autogenerate from docs/references.md, generate from entities | from entities | The site renders no Obsidian transclusions — it needs the inlined Summary text, which only the entities carry. Same source, second projection (site glossary precedent) |
Consequences
Section titled “Consequences”- Research and source-capture become entity operations:
sdlc reference create, fill Summary/Material, regenerate. A fetched excerpt or research note gets identity, lifecycle, and inbound links instead of dying in a chat transcript. - Amends D-0002-entity-identifier-shape: prefix table gains
RF | Reference | Incrementing. Amends D-ORMG-data-model: the Planning-meta layer gains the Reference row; the Evidence row’s note records that the curated-catalog slice of its territory is now Reference’s.identifier.tsPREFIXESgains the row, which also turns the commit-time identifier gate on fordocs/planning/references/. ARTIFACT_NAMESgrows to four; onesdlc docs generatepass keepsindex.md,glossary.md,references.md, and the site in lockstep.- The site sidebar restructures: Glossary stops being a top-level entry; an Appendix group anchors the end of the nav.
- The Data model page joins the generated set — hand-edits to it stop
being meaningful, and
data-model.mdgets the AUTO-GENERATED banner. The page’s entity count and field tables update by construction when a type ships. - Term and Reference now demonstrate the full pattern twice: entity type → generated wiki roster → generated site page. The third catalog type (whatever it is) should be near-mechanical.
- The
docsskill and per-skill doc widen to name four artifacts.
Migration
Section titled “Migration”The dogfood: SDLC’s own external sources move onto the model. Phases are ordered by dependency; each is one ticket, extracted from this section when planning starts. Phases 1–2 are pure addition.
Phase 1 — substrate: the reference package
Section titled “Phase 1 — substrate: the reference package”-
plugin/lib/model/entities/reference/—schema.ts(Zod frontmatter schema: RF id pattern, status enum, optionalurlin URI format; plus the body contract: Summary required with^summaryguidance, Material/Notes optional),definition.mdper S-0005,body-template.eta -
ops/create.ts—sdlc reference create <slug> --title <name> [--url <url>], incrementing mint, delegating torunCreate -
identifier.tsPREFIXESrow{ RF, Reference, references, incrementing } - Amend D-0002-entity-identifier-shape prefix table and D-ORMG-data-model roster (Reference row; Evidence row note)
- Verify zero-wire coverage:
sdlc project setupcreatesdocs/planning/references/;entities audit/validate/migratediscover the type; goldens re-snapshot (referencenoun)
Phase 2 — generator: the references artifact and the index Appendix
Section titled “Phase 2 — generator: the references artifact and the index Appendix”-
buildReferencesData(planningDir)(alphabetical by title,bucketForreused, host-labelled Link cells) +references_template.etawith the fixed preamble -
ARTIFACT_NAMESgainsreferences;artifactPath,renderArtifact, op enum,docs-helpgolden -
index_template.etaAppendix gains the Glossary and References sections;IndexDatagains both; Start here gains areferencesrow -
docs/references.mdborn generated (empty-corpus preamble) - Tests: references assembly units, empty-corpus tolerance,
byte-stable idempotency, op positional selection; fixture
references under
tests/fixtures/mini/ -
docsskill + per-skill doc widen to four artifacts
Phase 3 — site: references page and the Appendix group
Section titled “Phase 3 — site: references page and the Appendix group”- Site References page generated from reference entities
(summaries inlined,
urlas link, cited-by as code ids); idempotency tests - Site sidebar: trailing Appendix group (Glossary moves in, References joins)
- Changelog entry
Phase 4 — site: the Data model page
Section titled “Phase 4 — site: the Data model page”-
data-model.mdgenerated per §5: static narrative blocks + identity table fromPREFIXES+ roster table from entity schemas + derived type count; AUTO-GENERATED banner;writeIfChanged; tests - Verify the generated page against the current hand-written one — content parity for everything the tables carry, narrative preserved verbatim where still true
Phase 5 — corpus: seed references (the dogfood)
Section titled “Phase 5 — corpus: seed references (the dogfood)”- Mint the seed corpus from sources SDLC demonstrably leans on:
Obsidian Bases, Obsidian embeds/transclusion, Eta, Astro
Starlight, Bun, lefthook, JSON Schema 2020-12, Zod, Nygard ADRs
(~8–10), each with
url, Summary,related:to its citing entities - Re-point inline external URLs in planning docs at the new entities where a wikilink reads better (best-effort; no mandate)
- Regenerate all artifacts;
references.mdand the site pages fill - Close the phase tickets
Phase 6 — site: per-entity child pages + anchor sidebar
Section titled “Phase 6 — site: per-entity child pages + anchor sidebar”- One generated page per term/reference (
/glossary/<slug>/,/references/<slug>/) from the full entity body; wikilinks to sibling entities resolve to child routes; orphan sweep - Roster cells link to the child pages and carry
<a id>row anchors; sidebar Appendix children navigate to the anchors via the generated sidebar module (<docs_site>/src/generated/sidebar.mjs) - The rosters’ On this page nav lists every entry: Starlight
route middleware (
<docs_site>/src/routeData.ts) maps the same generated items onto TocItems (tables have no headings for the default TOC to find)
Phase 7 — site: layouts onto Eta templates
Section titled “Phase 7 — site: layouts onto Eta templates”- Every site page’s layout moves from inline template literals in
site.ts/data_model.tsintosite_*.etatemplates — the same data-to-template split the wiki artifacts declare; raw render (no whitespace normalization) so output is byte-identical
Out of scope
Section titled “Out of scope”- URL liveness checking, fetching, or archival snapshots of external
content — future tooling the
urlfield enables but this decision doesn’t build. - Citation enforcement (requiring decisions to cite references) — the catalog is opt-in.
- The Evidence type — stays deferred in D-ORMG-data-model; per-claim support records remain a different animal.
- Migrating every inline URL in the corpus — the seed corpus covers load-bearing sources; the long tail moves opportunistically.
- Generating the other hand-written site pages (overview,
deterministic-first, …) — the Data model page is the one whose
content is registry-shaped; the rest stay under
/dev-update-docssemantic review.
Open questions
Section titled “Open questions”- Fetch metadata: does a reference want
accessed:/archived:dates once snapshot tooling exists? Deferred until that tooling is real (last_reviewedcovers the human loop today). - Material provenance: should copied excerpts carry a structured attribution shape (beyond prose convention)? Revisit if excerpts accumulate.
References
Section titled “References”- D-B4CA-term-entity-and-generated-glossary — the pattern this decision reuses, one type later.
- D-0010-deterministic-site-assembly — generalizes this decision’s site projections: the whole sidebar becomes one generated module and every entity type gets roster + child pages.
- D-0002-entity-identifier-shape — identifier grammar; prefix table amended here.
- D-0004-entity-definition-architecture — the entity-package contract the reference type follows.
- D-0007-deterministic-op-substrate — the op registry
reference createanddocs generatelive in. - D-H7FS-op-substrate-surface — the command surface;
referencejoins as an entity noun. - D-ORMG-data-model — entity roster; gains the type, keeps Evidence deferred.
- S-0004-sdlc-cli-llm-head-deterministic-tail — the head/tail split the generator follows.
- S-0005-entity-definition-contract — the companion-doc contract
definition.mdfollows.