docs(spec): eCFR jump links — bidirectional CFR cite/URL (P41)
This commit is contained in:
126
docs/superpowers/specs/2026-08-17-ecfr-jump-links-design.md
Normal file
126
docs/superpowers/specs/2026-08-17-ecfr-jump-links-design.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# eCFR Jump Links — Bidirectional CFR Cite ↔ URL (P41)
|
||||||
|
|
||||||
|
Date: 2026-08-17. Milestone: P41. Requested as: "the same [as P40's FR
|
||||||
|
jump links] for eCFR — so a CFR cite goes back and forth."
|
||||||
|
|
||||||
|
## Verified ground truth (2026-08-17)
|
||||||
|
|
||||||
|
- `https://www.ecfr.gov/current/title-42/section-425.400` (short form)
|
||||||
|
**302-redirects server-side** to the canonical hierarchy URL
|
||||||
|
(`/current/title-42/chapter-IV/subchapter-B/part-425/subpart-E/section-425.400`)
|
||||||
|
— cite → URL needs no structure lookup; fragments survive redirects
|
||||||
|
client-side.
|
||||||
|
- Paragraph anchors on section pages are **semantic**:
|
||||||
|
`id="p-425.400(c)(1)(iv)"` — the fragment IS the cite's paragraph
|
||||||
|
path (316 such anchors on § 425.400). Both directions are therefore
|
||||||
|
deterministic string transforms.
|
||||||
|
- The eCFR versioner API is open (`/api/versioner/v1/titles.json`
|
||||||
|
responds), but the plain `/cite/` permalink endpoint 406s for
|
||||||
|
non-browser clients — irrelevant, the short form covers it.
|
||||||
|
- bib holds **699 `item_type='regulation'` items with eCFR URLs** —
|
||||||
|
both part-level (`…/title-42/part-414`) and section-level
|
||||||
|
(`…/part-405/section-405.534`) — the Title 42/45 Regulations
|
||||||
|
collections. These are the placement targets and the validation
|
||||||
|
index.
|
||||||
|
|
||||||
|
## Decisions (inherited from P40's user-selected pattern)
|
||||||
|
|
||||||
|
Consumers: all (notebooks, `:pincite:`, Zotero child links, CLI).
|
||||||
|
Addressing: cite (with/without paragraph path), URL (inverse), both
|
||||||
|
directions. Persistence: no anchor grab needed — the transform is
|
||||||
|
deterministic; the bib regulation index provides context/validation.
|
||||||
|
Placement discipline: only `place()`d links reach Zotero.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**`src/bib/cfrlink.py`** — parse, transmute, context, place. Hooks:
|
||||||
|
`bib/pincite.py` (`jump_url` covers the existing `cfr` locator type),
|
||||||
|
notebook helper `cfr_md()`, CLI `stack bib cfr-jump`. Zotero placement
|
||||||
|
reuses the P40 `fr_links` table + `_sync_fr_links` path unchanged
|
||||||
|
(rows carry `page=0` and `p_id=NULL` for non-FR links — the sync only
|
||||||
|
reads `label`/`url`; documented here rather than forking a parallel
|
||||||
|
table).
|
||||||
|
|
||||||
|
### Data model
|
||||||
|
|
||||||
|
`CfrCite` frozen dataclass: `title: int, part: str, section: str
|
||||||
|
("425.400", empty for part-level cites), paras: tuple[str, ...]
|
||||||
|
(("c","1","iv")), date: str ("" = current)`.
|
||||||
|
|
||||||
|
### Parse (`parse_cite(ref) -> CfrCite`)
|
||||||
|
|
||||||
|
Accepted spellings (case-insensitive, `C.F.R.` dots optional, `§`
|
||||||
|
optional, whitespace collapsed):
|
||||||
|
- `42 CFR 425.400(c)(1)(iv)` / `42 C.F.R. § 425.400(c)(1)(iv)`
|
||||||
|
- `42 CFR § 414.1425` / `42 CFR 414.1450(b)(1)`
|
||||||
|
- `42 CFR Part 425` / `42 CFR part 425 subpart E` (subpart ignored for
|
||||||
|
URL purposes — the short form resolves placement)
|
||||||
|
Title is required (bare `§ 425.400` raises naming the reason — the
|
||||||
|
title selects the eCFR tree). Paragraph path parsed from consecutive
|
||||||
|
parenthesized tokens.
|
||||||
|
|
||||||
|
### Transmute
|
||||||
|
|
||||||
|
- `url(cite) -> str`:
|
||||||
|
section: `https://www.ecfr.gov/current/title-{t}/section-{s}` +
|
||||||
|
(`#p-{s}({a})({b})…` when paras present);
|
||||||
|
part: `https://www.ecfr.gov/current/title-{t}/part-{p}`;
|
||||||
|
date-pinned: `/on/{date}/` replaces `/current/` when `cite.date`.
|
||||||
|
- `cite_of(url) -> str`: inverse over every eCFR URL form — short,
|
||||||
|
canonical full-hierarchy, `/on/{date}/`, with or without `#p-…`
|
||||||
|
fragment. Emits the canonical spelling `42 CFR 425.400(c)(1)(iv)`
|
||||||
|
(part-level: `42 CFR Part 425`).
|
||||||
|
- **Round-trip law**: `cite_of(url(parse_cite(x)))` == canonical
|
||||||
|
spelling of `x`, and `url(parse_cite(cite_of(u)))` reproduces `u`'s
|
||||||
|
short form — tested explicitly.
|
||||||
|
|
||||||
|
### Bib context + placement
|
||||||
|
|
||||||
|
- `item_for(cite, store) -> str | ""`: match against regulation
|
||||||
|
items' eCFR URLs — exact section item first, then the part item.
|
||||||
|
Powers placement targets and lightweight validation ("is this cited
|
||||||
|
reg in the library?"); a cite with no library item still transmutes
|
||||||
|
(URL construction never requires the store).
|
||||||
|
- `place(store, ref, *, label="") -> JumpLink`: resolve + `item_for`
|
||||||
|
(raises when no regulation item matches — placed links need a
|
||||||
|
parent), upsert into `fr_links` (page=0), synced by the existing
|
||||||
|
`_sync_fr_links` as linkMode=3 children.
|
||||||
|
- `md_link(ref, *, text="")` — store-free (deterministic URL).
|
||||||
|
- CLI: `stack bib cfr-jump REF [--md] [--place [--label L]]` and
|
||||||
|
`stack bib cfr-jump --url URL` for the reverse direction (prints the
|
||||||
|
cite).
|
||||||
|
|
||||||
|
### `:pincite:`
|
||||||
|
|
||||||
|
`Pincite.jump_url` gains the `cfr` branch: locators like
|
||||||
|
`42 CFR 425.400(c)` resolve through `cfrlink` (no store data needed);
|
||||||
|
non-title locators (`§ 3.2`-style, classified `section`) stay
|
||||||
|
URL-less. Degradation contract unchanged (`""`, never raise).
|
||||||
|
|
||||||
|
### Notebook
|
||||||
|
|
||||||
|
`cfr_md()` helper beside `fr_md()` (same graceful fallback shape, but
|
||||||
|
store-free); the CY2027 notebook's `42 CFR 414.1450(b)(1)` (APM
|
||||||
|
revival), `§ 414.1425(c)(5)(ii)` (QP alignment — spelled with title),
|
||||||
|
and the PQM section's `42 CFR § 425.400(c)` cites become live eCFR
|
||||||
|
links. Gate: `nb_integration.py` single-notebook run.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Parse: every accepted spelling + reject-without-title + part form.
|
||||||
|
- Transmute: URL forms incl. date pin and fragments; `cite_of` over
|
||||||
|
short/canonical/dated/fragmented URLs; both round-trip laws.
|
||||||
|
- Context: `item_for` section-over-part preference against a fixture
|
||||||
|
store; `place` on a regulation item; sync reuses the P40 linkMode=3
|
||||||
|
tests' path (one new test: CFR-placed row syncs identically).
|
||||||
|
- Live smoke: `url(parse_cite("42 CFR 425.400(c)(1)(iv)"))` fetches
|
||||||
|
200 with the fragment's anchor present in the body.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- eCFR structure/versioner grabs (no data needed for the transform;
|
||||||
|
revisit only if full-hierarchy canonical URLs or historical
|
||||||
|
snapshots become requirements).
|
||||||
|
- Quote → paragraph addressing for CFR text (would need section text
|
||||||
|
ingestion; FR quotes already cover the rulemaking corpus).
|
||||||
|
- Auto-creating regulation items for cites not in the library.
|
||||||
Reference in New Issue
Block a user