feat(api): publish an alias index so clients resolve a locator without UUIDv5 (#84) - #88
Merged
Merged
Conversation
…extrefs#84) The only public handle on a reference is its UUIDv5, seeded from the work key, the citation system key, and the locator (ADR-0002). A client that knows a passage but ships no UUIDv5 implementation could reach the record only through the /cite/ redirect, which is HTML. Publish the same lookup as data. Each work gets one JSON map of locator to reference UUID, grouped by citation system key, so a client resolves a passage in two fetches and parses JSON alone. The index comes from the reference records, not from the flat alias table: that table mixes /cite/ aliases targeting reference IRIs with external mapping identifiers targeting work IRIs, and the records already carry the three fields the index groups by. Values are bare UUIDs, not IRIs. That halves the largest file (1.15 MB rather than 2.1 MB for tanakh), and /id/ref/{uuid}.json needs the UUID anyway. The body is minified and carries no @context: it is a lookup table, not JSON-LD. Every work gets a file, including a work with no references. A client that holds a work key must be able to tell "no references yet" from "wrong key", and only a 200 with an empty refs object does that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MoyDrm1kdM6tuisCHarWk2
) data/README.md and data/.gitignore both name a compiled aliases.json. No code wrote it. Close that gap. writeDump() hard-coded JSONL serialisation and its media type into the resource map, so a plain JSON resource did not fit. Split the pure halves apart. dumpResources() builds every body, and describeResource() derives the Frictionless bytes and sha256. writeDump() keeps the I/O and stays private, because it derives its path from the module-level distRoot, and compile.ts is imported by every Astro page through src/lib/registry.ts. Sort the alias keys, so the body — and therefore its hash — depends on the registry content alone, never on the order the compiler visited the work files in. Add dist/dump/aliases.json to the release workflow. It matches neither existing glob, and fail_on_unmatched_files is set, so a tagged release would otherwise ship a descriptor listing a resource that is not there. The four JSONL bodies are byte-identical to before the refactor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MoyDrm1kdM6tuisCHarWk2
Add both artifacts to the OpenAPI contract, with the WorkAliasIndex and AliasTable schemas. Both responses declare application/json, the media type the static host actually sends. Correct the statements that the two new files falsify. The Collections tag described JSON-LD collections alone. The Bulk tag claimed every /dump/ artifact arrives as application/octet-stream. The datapackage description counted four resources. The media-type paragraph asserted that every .json body is JSON-LD by content. Add a "Find a reference by its locator" section to the URL layout page, with the two-fetch procedure worked through Republic 514a. Bring the parity statements in the standard, the authoring guide, and the README up to date with the sixth dump artifact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MoyDrm1kdM6tuisCHarWk2
There was a problem hiding this comment.
Pull request overview
This PR adds a published, static alias lookup surface so clients can resolve {work_key, citation_system_key, locator} to a reference UUID without needing to implement UUIDv5 themselves, and it formalizes the alias table as a first-class dump resource alongside the existing JSONL exports.
Changes:
- Add a per-work locator index endpoint at
/reg/work/{key}/aliases.jsonbuilt from compiled reference records. - Emit
/dump/aliases.jsonand include it in the dump resource set (with bytes + sha256 indatapackage.json), with corresponding tests. - Document and advertise the new artifacts across the OpenAPI spec, site docs, and release workflow packaging.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/pages/reg/work/[key]/aliases.json.ts | New per-work JSON locator index endpoint generated at build time. |
| src/pages/id/work/[key]/index.astro | Advertises the locator index as an alternate link and adds a UI link to it. |
| src/lib/alias-index.ts | Implements deterministic per-work alias index construction from reference records. |
| src/lib/alias-index.test.ts | Unit tests covering reachability, sorting determinism, collision cases, and __proto__ locator safety. |
| scripts/compile.ts | Adds dumpResources() / describeResource() and emits /dump/aliases.json as a described dump resource. |
| scripts/compile.test.ts | Tests dump resource set shape, alias dump body properties, and descriptor bytes/sha256 correctness. |
| api/openapi.yaml | Documents /reg/work/{key}/aliases.json, /dump/aliases.json, and updates bulk/collection semantics. |
| src/content/docs/get-started/url-layout.md | Documents how clients resolve locators via the per-work index and the new bulk alias dump. |
| src/content/docs/get-started/authoring.md | Updates data build documentation to include aliases.json in dump output. |
| src/content/docs/standard/versioning.md | Updates dump layout and alias permanence section to reflect the published alias artifacts. |
| README.md | Updates command table to reflect that compile:data emits aliases.json too. |
| AGENTS.md | Updates repo layout notes to include the per-work locator index under /reg/. |
| .github/workflows/release.yml | Ensures dist/dump/aliases.json is included in release uploads (with fail-on-unmatched intact). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Publish the reference lookup as data, so a client that knows
{work, citation system, locator}can reach the record without a UUIDv5 implementation of its own.Closes #84.
Commit type
fix— bug fixfeat— new featuredocs— documentation onlyrefactor— code restructure without behaviour changechore/build/ci— tooling, dependencies, CIArea
Related issues
Closes #84. Companion to #83 (collection endpoints), which this builds on. Complementary to #42 (client-side UUID generator): #42 lets an author mint an identifier for a reference that is not yet in the registry; this lets a client look up one that is. Relevant to #52 (WebMCP) and #74 (
datapackage.jsondescriptors).What lands
1.
/reg/work/{key}/aliases.json— a per-work locator index.{ "work_key": "plato.republic", "preferred_citation_system_key": "stephanus", "refs": { "stephanus": { "514a": "dc799d4b-9b17-5d76-85aa-dfd001c5321d" } } }Two fetches resolve a passage: read
refs[system][locator], thenGET /id/ref/{uuid}.json. For a bare locator with no system, usepreferred_citation_system_key.2.
/dump/aliases.json— the complete alias table, registered indatapackage.jsonwith a byte count and asha256:hash.data/README.mdanddata/.gitignorehave named this artifact all along; no code wrote it.Decisions worth reviewing
/id/ref/{uuid}.jsonneeds the UUID anyway. The per-work index is plain JSON with no@context— it is a lookup table, not JSON-LD, and the OpenAPI and the docs say so./cite/aliases targeting reference IRIs with external mapping identifiers targeting work IRIs./dump/aliases.jsonkeeps full IRIs for exactly that reason, so a consumer can tell the two kinds apart.src/pages/reg/work/[key]/refs/[page].astrostays the documented fallback..github/workflows/release.ymlis in this PR.aliases.jsonmatches neither existing glob andfail_on_unmatched_files: trueis set, so a tagged release would otherwise ship a descriptor listing a resource that is not in the release. Splitting the workflow line into its ownmainPR would open exactly that window.CONTRIBUTING.mdroutes infra tomain; the release workflow only fires on tags cut frommain, so this reaches production through the nextstaging → mainsquash with no gap.Acceptance criteria
{system, locator}idof itsCanonicalReferencedist/dump/aliases.jsonindatapackage.jsonwith bytes + sha256sha256:dbaba8b5…, verified against the file on diskstagingnpm run verifyandnpm run validate:dataCriterion 5 — measured sizes
Built against the pinned submodule. 204,361 pages, 67,959 references, 135,942 alias entries.
aliases.jsontanakhhomer.iliadhomer.odysseynew-testamentaristotle.nicomachean-ethicsplato.republicLargest index:
tanakhat 1.12 MB raw, 566 KB gzipped — under the 2 MB cap, so no pagination. All 12 files together are 3.33 MB./dump/aliases.jsonis 12.89 MB raw, 1.97 MB gzipped. For proportion,references.jsonlis already 53.6 MB, so the dump grows by about a quarter. GitHub Pages compressesapplication/json, so the gzip column is the wire cost. The table is derivable fromreferences.jsonlplusworks.jsonl; it is published for convenience, which is what criterion 4 anddata/README.mdask for.Tests
src/lib/alias-index.test.ts— 12 tests againstregistry.fixture.ts, which is the hard case: one work, two citation systems, the same locator1under both, one active reference and one draft. Covers reachability, UUID-not-IRI values, ADR-0005 two-system separation, empty-refsworks, draft inclusion, order independence, no caller mutation, and a locator spelled__proto__(a locator is author-controlled, so plain assignment would set a prototype and silently drop the entry — henceObject.fromEntries).scripts/compile.test.ts— 7 new tests over the extracteddumpResources()/describeResource(), including an independent recomputation of everybytesandsha256, and a guard that the four JSONL bodies did not move.writeDump()stays private: it derives its path from the module-leveldistRoot, andscripts/compile.tsis imported by every Astro page throughsrc/lib/registry.ts, so an exported filesystem writer would be reachable from page render code.Note for review
All 12 works currently use exactly one citation system, so the nested
refs[system]shape is exercised only by the fixture. That is deliberate — the fixture is the only place the ADR-0005 collision case exists today.Testing
npm run verify:fastpasses locally for routine docs, styling, or route worknpm run verifypasses locally for registry-data, standard, release, production-build, or CI changesnpm run validate:datapasses locally for registry-data and standard PRsChecklist