Skip to content

feat(standard)!: preferred citation system and qualified /cite/ aliases (ADR-0005) - #63

Merged
maehr merged 1 commit into
textrefs:stagingfrom
maehr:feat/preferred-citation-system
Aug 12, 2026
Merged

feat(standard)!: preferred citation system and qualified /cite/ aliases (ADR-0005)#63
maehr merged 1 commit into
textrefs:stagingfrom
maehr:feat/preferred-citation-system

Conversation

@maehr

@maehr maehr commented Aug 4, 2026

Copy link
Copy Markdown
Member

Closes #60 — raised by @stephenhart8, whose preferred-citation-system proposal this implements.

The problem

/cite/homer.odyssey/1.5/ drops the citation system even though the system is one of the three fields that seed the reference UUID (ADR-0002). Today that is unreachable rather than handled: a work source carries one scalar citation_system:, so alias keys are unique by construction and setAlias turns any clash into a build failure. The consequence is that a work under two citation systems cannot be represented at all.

That blocks the roadmap. Psalms under Masoretic vs. Vulgate versification, the Qur'an under Cairo vs. Flügel, Aristotle under Bekker (1094a1) and book/chapter (1.1) — the same locator string denotes a different passage. A bare alias there resolves silently to the wrong text, which is worse than a 404.

The decision (ADR-0005)

Every reference gets /cite/{work}/{system}/{locator}/. The bare /cite/{work}/{locator}/ is minted only for the work's preferred citation system.

/cite/plato.republic/514a/            → id/ref/dc799d4b-…  (bare, preferred only)
/cite/plato.republic/stephanus/514a/  → id/ref/dc799d4b-…  (qualified, always)

Collisions stop being merely detected and become structurally impossible: a qualified alias is keyed by the same tuple that seeds the UUID, and at most one system per work is preferred.

What changed

  • Work.preferred_citation_system_key — required and consumer-visible: JSON-LD context term tr:preferredCitationSystemKey, OpenAPI Work schema, and works.jsonl.
  • Authoring — the top-level citation_system: stays the preferred one; a work may add additional_systems: blocks, each with its own citation system, resolvers, locators, and reference_status:. The status default is asymmetric: the top-level block inherits the work's status, a fallback block defaults to draft and never inherits active. Adding a fallback system to an active work promotes nothing.
  • Source validation — YAML sources are parsed through Zod instead of an unchecked as WorkSource cast. That is what lets duplicate citation-system blocks fail at parse time, and it catches registry typos that previously landed silently.
  • Locators containing / are rejected before any alias is minted — the grammar distinguishes the two forms by segment count alone.
  • Invariants restated per ADR-0005: an active reference needs an active work and an active citation system for its own key; an active work needs an active preferred system; a draft fallback never downgrades the work. Invariant violations now appear in the thrown error, not only on stderr.
  • Presentation — work pages tag the preferred system; work pages, paginated reference lists, reference-page titles, and the rendered Chicago citation name the system when a work has more than one, so repeated locators stay distinguishable.
  • Tests — new npm test (node:test via tsx, no new dependency), wired into verify and verify:fast. Eight cases build a throwaway registry and compile it, so they exercise the real YAML → record path.

Alias permanence — please read

/cite/ aliases are presentational. Bare aliases may be retargeted: changing a work's preferred citation system changes what its bare alias resolves to, including for active works. There is no alias ledger and no freezing — this is documented in /standard/versioning/, /get-started/url-layout/, and governance §6 item 6. Qualified aliases and /id/ref/{uuid} identifiers are never retargeted.

Verification

  • npm test — 8/8 pass.
  • npm run build:data — 39,250/39,250 records valid. Aliases 39,228 → 78,432, exactly 2× the 39,204 references plus 24 mapping aliases. No reference UUID changed: plato.republic/514a still resolves to dc799d4b-9b17-5d76-85aa-dfd001c5321d, the identifier documented in the spec.
  • npm run verify:fastastro check clean, 90 pages, all internal links valid. The fixture now carries two systems sharing the locator 1; the built output confirms one bare and two qualified alias pages, correct redirect targets, the preferred tag, and distinct reference-page titles.

Follow-ups

  • docs: document preferred and additional citation systems registry#11 documents the new authoring fields. No registry data change is required — the compiler derives preferred_citation_system_key from the existing top-level citation_system:.
  • Deferred (recorded in the ADR): real multi-system registry data, which needs a curated second citation-system profile and attested reference points; and per-community or per-language preference.

Update: #62 merged (as a squash commit, b365cfc), so this branch was rebased onto current staging — it's no longer stacked and carries only its own commit. Also folded in while rebasing:

  • ADR-0005 status flipped ProposedAccepted, matching the convention feat(standard)!: collapse record lifecycle to draft → active (ADR-0004) #62 itself set.
  • Addressed the Copilot review comment on scripts/compile.test.ts: expectCompileError now uses t.mock.method(console, 'error', …) scoped to the test's TestContext instead of manually reassigning the global, so the mock can't leak across concurrently-run tests.

All verification numbers above were re-run and confirmed against the rebased branch.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 4, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements ADR-0005’s preferred citation system model by introducing qualified /cite/{work}/{system}/{locator} aliases for every reference while minting bare /cite/{work}/{locator} only for a work’s preferred system, enabling multi-citation-system works without alias collisions. It also rolls in ADR-0004’s lifecycle simplification (draftactive) across schema, compiler invariants, fixtures, and documentation, and adds compiler-focused tests.

Changes:

  • Add Work.preferred_citation_system_key to the standard schema + JSON-LD/OpenAPI, and update rendering to disambiguate repeated locators across citation systems.
  • Update the compiler to validate YAML sources with Zod, support additional_systems blocks, mint qualified + (preferred-only) bare /cite/ aliases, reject locators containing /, and enforce updated status dependency invariants.
  • Add node:test-based compiler tests and wire npm test into verify / verify:fast.

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
standard/schema/work.ts Adds required preferred_citation_system_key to Work.
standard/schema/common.ts Removes candidate and documents the ADR-0004 lifecycle vocabulary.
src/pages/reg/work/[key]/refs/[page].astro Sorts refs by system+locator and labels system when a work is multi-system.
src/pages/id/work/[key]/index.astro Labels the preferred system and tags refs by system when multi-system.
src/pages/id/ref/[uuid]/index.astro Disambiguates titles/citations/description for multi-system works.
src/lib/registry.fixture.ts Updates fixture records to ADR-0004 statuses and adds a second citation system case.
src/lib/citation.ts Extends Chicago formatting to optionally include citation system label.
src/content/docs/standard/versioning.md Documents ADR-0004/0005 invariants and alias rules (qualified vs bare, retargeting).
src/content/docs/standard/specification.md Updates spec examples and adds preferred_citation_system_key + multi-system guidance.
src/content/docs/standard/json-ld.md Adds JSON-LD context term for preferred_citation_system_key.
src/content/docs/standard/identifier-syntax.md Updates persistence language to attach at active.
src/content/docs/get-started/url-layout.md Updates /cite/ URL forms and permanence expectations.
src/content/docs/get-started/index.md Updates getting-started example to mention qualified /cite/ form.
src/content/docs/get-started/how-it-works.md Updates examples to reflect ADR-0004 statuses.
src/content/docs/get-started/authoring.md Documents additional_systems authoring and the reference_status default asymmetry.
src/content/docs/de/association/governance.md Updates legally binding German governance lifecycle/persistence wording for ADR-0004.
src/content/docs/community/roadmap.md Updates roadmap note to reference ADR-0004.
src/content/docs/community/contributing.md Updates review-track text/diagram for ADR-0004 lifecycle and draft landing.
src/content/docs/association/governance.md Updates English governance lifecycle/persistence wording for ADR-0004.
src/components/Tombstone.astro Updates draft-banner copy to reference promotion to active.
scripts/source-schema.ts Adds strict Zod schemas for YAML source validation and duplicate-system rejection.
scripts/compile.ts Implements ADR-0005 alias minting + additional systems + invariant updates; adds locator / rejection; adds fixture-root override for tests.
scripts/compile.test.ts Adds compiler tests for alias grammar and ADR-0004/0005 dependency rules.
ROADMAP.md Updates roadmap note to reference ADR-0004.
README.md Updates wording to reflect registry examples are draft data.
public/contexts/v1.jsonld Adds context term for preferred_citation_system_key.
package.json Adds npm test and integrates it into verify/verify:fast.
decisions/ADR-0005-preferred-citation-system.md Adds ADR-0005 detailing preferred citation system + alias strategy.
decisions/ADR-0004-lean-lifecycle.md Adds ADR-0004 detailing lifecycle collapse to draftactive.
decisions/ADR-0003-draft-lifecycle-ephemeral-identity.md Marks ADR-0003 as superseded by ADR-0004 and updates links.
CONTRIBUTING.md Updates contribution tracks and promotion language for ADR-0004.
api/openapi.yaml Removes candidate and adds required preferred_citation_system_key on Work.
AGENTS.md Updates /cite/ URL layout description (qualified always; bare preferred-only and retargetable).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/compile.test.ts
@maehr

maehr commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

@stephenhart8 are you interested in reviewing this PR?

…es (ADR-0005)

Closes textrefs#60, raised by @stephenhart8, whose preferred-citation-system
proposal this implements.

Aliases dropped the citation system even though it seeds the reference
UUID, so `/cite/homer.odyssey/1.5` could not distinguish two systems that
share a locator string. Every reference now also gets a qualified
`/cite/{work}/{system}/{locator}`; the bare `/cite/{work}/{locator}` is
minted only for the work's preferred system. Existing short URLs keep
working and no reference UUID changes.

- `Work.preferred_citation_system_key` is required and consumer-visible
  (JSON-LD context term, OpenAPI schema, works.jsonl).
- Work sources gain `additional_systems:` for fallback systems and
  `reference_status:` per block. The top-level block inherits the work's
  status; a fallback block defaults to `draft` and never inherits
  `active`, so adding a system promotes nothing by accident.
- YAML sources are now validated with Zod instead of an unchecked cast,
  which is what lets duplicate citation-system blocks fail at parse time.
- Locators containing `/` are rejected before alias minting: the grammar
  tells the two forms apart by segment count alone.
- Invariants restated per ADR-0005: an active reference needs an active
  work and an active system; an active work needs an active preferred
  system; draft fallbacks never downgrade the work.
- Work pages, paginated reference lists, reference-page titles, and the
  rendered Chicago citation name the system when a work has more than
  one, so repeated locators stay distinguishable.
- New `npm test` (node:test via tsx) covers the alias grammar and every
  new invariant; wired into `verify` and `verify:fast`.

Breaking: `Work` gains a required field, so works.jsonl consumers must
adapt. Bare `/cite/` aliases are explicitly retargetable — only `/id/`
identifiers are permanent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@maehr
maehr force-pushed the feat/preferred-citation-system branch from a863491 to 9110d05 Compare August 4, 2026 15:03
@maehr
maehr merged commit 5c2a6f3 into textrefs:staging Aug 12, 2026
2 checks passed
maehr added a commit to maehr/textrefs.org that referenced this pull request Aug 12, 2026
Replaces the skos:exactMatch / skos:closeMatch relation enum with
alternateOf (prov:alternateOf) and isReferencedBy (dcterms:isReferencedBy),
selected by target kind rather than by confidence. closeMatch is removed
rather than retained-but-unusable: removing an enum value is breaking and
this change is already breaking, while re-adding one later is additive.

Status: Proposed, and no code changes yet — the issues stay open until the
implementation lands. Stacked on textrefs#63 (ADR-0005), which introduces the Zod
authoring schema the new enum needs.

Refs textrefs#58, refs textrefs#59.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants