Skip to content

Reference entity type, generated references roster, and the docs Appendix

Status: open/accepted

  • A new reference entity type (prefix RF, incrementing ids, docs/planning/references/) catalogs external documentation, research, and artifacts as first-class entities: an optional url in frontmatter, a transcludable Summary, and a Material section that may summarize, copy, or transclude the source. (§1, §2)
  • docs/references.md is a new build artifact — the references roster, generated from reference entities beside index.md and glossary.md. The docs generate artifact list grows to index | glossary | references | site. (§3)
  • Terms and references move into the Appendix: index.md’s existing # Appendix gains 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 PREFIXES registry and the entity schemas in the same docs generate site pass, so the page can never again claim “nine entity types” after the tenth ships. (§5)
  • The type is named reference, not evidenceD-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)

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.

AspectChoice
Type namereference — one instance is one external source
Packageplugin/lib/model/entities/reference/
Instances dirdocs/planning/references/ (pluralize("reference") — zero wiring)
PrefixRF (R reserved for Requirement per D-0002-entity-identifier-shape; RE mis-parses as requirement)
NumberingIncrementing (RF-0001) — curated, low-volume catalog per D-0002’s split
FilenameRF-NNNN-<slug>.md, e.g. RF-0001-obsidian-bases.md
WikilinkRF-NNNN-<slug>, piped for display: Obsidian Bases
Create opsdlc reference create [<slug>] --title <name> [--url <url>] — slug derived from --title when omitted (mirrors term create)

Frontmatter — common fields per the shared CommonFrontmatter base (plugin/lib/model/entities/_common.ts), plus:

FieldRequired?ShapeDefaultNotes
typerequiredreferenceValidator-dispatch tag
idrequired^RF-[0-9A-Z]{4}$Immutable; matches filename
titlerequiredstringThe source’s name (Obsidian Bases, Nygard — Documenting Architecture Decisions)
urloptionalstring, format: uriThe external location. Optional — a reference may be a book, a local artifact, or offline material
statusrequiredenum (see Lifecycle)open/active
relatedoptionallist of wikilinks[]The Cited by column: the decisions/standards/tasks that lean on this source

Body manifest (order: lenient, allow_unknown: true):

SectionRequired?Notes
Summaryrequired1–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
MaterialoptionalThe substance, in whichever form fits: a prose summary, copied excerpts (quoted, with attribution), or transcluded artifacts (... embeds of files stored alongside the entity)
NotesoptionalCommentary 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:

StatusMeaningRoster table
open/activeIn use — cited by current decisions/standardsActive
open/draftCaptured, not yet vetted or not yet load-bearingEmerging
closed/retiredNo longer leaned on; kept for link stabilityObsoleted

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.

AspectChoice
Artifact namereferencesARTIFACT_NAMES becomes index | glossary | references | site, canonical order
Surfacesdlc docs generate references for a scoped run; bare invocation includes it
ColumnsReference | 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:
OrderingAlphabetical by title, case-insensitive — ids carry identity, not order (glossary precedent)
BucketingbucketFor reused: active table + Emerging / retired follow-up with status, omitted when empty
Empty corpusPreamble 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.

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:

SectionContent
## GlossaryThe terms table (Term / Definition / Source) — the same buildGlossaryData output glossary.md renders, second projection, one pass, zero drift — plus a pointer line to glossary
## ReferencesThe 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
└─ References

The 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.

<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:

BlockSource
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 countDerived — "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.

ChoiceOptionsTakenWhy
Type namereference, source, revive evidence (D-ORMG-data-model deferred row)referenceEvidence 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
PrefixRF, RE, REFRFR reserved for Requirement (D-0002); RE reads “requirement” as easily as “reference”; three letters is budget for no gain. RF parses single-pass
Numberingincrementing, base-36incrementingCurated, low-volume, human-cited — same criteria as Term
url placementfrontmatter field, References body section, bothfrontmatterMachine-readable (link checking, future fetch/archive tooling) and renderable as a roster column. The body’s Material section holds content, not location
Link column textfull URL, fixed label (“link”), URL hosthostDeterministic, compact, informative; full URLs wreck table layout, fixed labels say nothing
Index placementnew top-level sections, the existing # AppendixAppendixThe 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 contentpointer lines only, full generated tablesfull tablesThe 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 pageleave hand-written, fully generated, generated tables + static narrativetables + static narrativeThe 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 pageautogenerate from docs/references.md, generate from entitiesfrom entitiesThe site renders no Obsidian transclusions — it needs the inlined Summary text, which only the entities carry. Same source, second projection (site glossary precedent)
  • 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.ts PREFIXES gains the row, which also turns the commit-time identifier gate on for docs/planning/references/.
  • ARTIFACT_NAMES grows to four; one sdlc docs generate pass keeps index.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.md gets 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 docs skill and per-skill doc widen to name four artifacts.

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, optional url in URI format; plus the body contract: Summary required with ^summary guidance, Material/Notes optional), definition.md per S-0005, body-template.eta
  • ops/create.tssdlc reference create <slug> --title <name> [--url <url>], incrementing mint, delegating to runCreate
  • identifier.ts PREFIXES row { 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 setup creates docs/planning/references/; entities audit/validate/migrate discover the type; goldens re-snapshot (reference noun)

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, bucketFor reused, host-labelled Link cells) + references_template.eta with the fixed preamble
  • ARTIFACT_NAMES gains references; artifactPath, renderArtifact, op enum, docs-help golden
  • index_template.eta Appendix gains the Glossary and References sections; IndexData gains both; Start here gains a references row
  • docs/references.md born generated (empty-corpus preamble)
  • Tests: references assembly units, empty-corpus tolerance, byte-stable idempotency, op positional selection; fixture references under tests/fixtures/mini/
  • docs skill + 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, url as link, cited-by as code ids); idempotency tests
  • Site sidebar: trailing Appendix group (Glossary moves in, References joins)
  • Changelog entry
  • data-model.md generated per §5: static narrative blocks + identity table from PREFIXES + 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.md and 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.ts into site_*.eta templates — the same data-to-template split the wiki artifacts declare; raw render (no whitespace normalization) so output is byte-identical
  • URL liveness checking, fetching, or archival snapshots of external content — future tooling the url field 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-docs semantic review.
  • Fetch metadata: does a reference want accessed:/archived: dates once snapshot tooling exists? Deferred until that tooling is real (last_reviewed covers the human loop today).
  • Material provenance: should copied excerpts carry a structured attribution shape (beyond prose convention)? Revisit if excerpts accumulate.

← Back to Decisions