diff --git a/.lycheeignore b/.lycheeignore index 228cdf5..e2b6c07 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -22,6 +22,7 @@ ^https://www\.ansi\.org/?$ ^https://www\.astm\.org/?$ ^https://www\.csagroup\.org/?$ +^https://www\.fil-idf\.org/?$ ^https://www\.iec\.ch/?$ # Local-asset patterns from VitePress era (not used by Astro, but harmless). diff --git a/astro.config.mjs b/astro.config.mjs index 80bba49..3e1e8d1 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -26,6 +26,11 @@ export default defineConfig({ { label: 'PubID Algebra', link: '/concepts/algebra' }, { label: 'Relationships', link: '/concepts/relationships' }, { label: 'Identifier Relations', link: '/concepts/identifier-relations' }, + { label: 'Lossless Faces', link: '/concepts/lossless-faces' }, + { label: 'Dual Perspectives', link: '/concepts/dual-perspectives' }, + { label: 'The D= Designator', link: '/concepts/draft-designator' }, + { label: 'Stage Words', link: '/concepts/stage-words' }, + { label: 'The Corpus Contract', link: '/concepts/corpus' }, { label: 'URN Mapping', link: '/concepts/urn' }, ], }, diff --git a/src/content/concepts/corpus.md b/src/content/concepts/corpus.md new file mode 100644 index 0000000..b614a46 --- /dev/null +++ b/src/content/concepts/corpus.md @@ -0,0 +1,59 @@ +--- +title: "The Corpus Contract" +description: "The shared, versioned record every implementation must reproduce." +--- + +# The Corpus Contract + +The corpus is the **single source of truth** for what an identifier +means and how it renders. The Ruby reference produces it; every mirror +consumes it. When the reference and a mirror disagree, the corpus — not +either implementation's memory — decides which row is wrong. + +## What a case records + +Each case names one document with its typed identity (the attributes, +not a string), and the forms that identity must produce: the human +canonical, the URN, and the serialized hash. Aliases are listed +separately, with the number of render/parse rounds they need to reach +the canonical. A case may also be a **negative** — an input that every +implementation must reject. + +Around the cases sit the gates that keep the record honest: + +- a **schema** the cases are validated against, so a missing field or a + wrong type fails before a human reads the diff; +- a **provenance manifest** pinning the reference commit, the grammar + commit and every payload's checksum — a changed payload without a + provenance receipt is treated as tampering; +- a **ledger** of known-pending cases, each with a reason and a + reference, and an alarm when a pending case starts passing; +- a **quarantine class** for rows the reference itself cannot + reproduce (see [Lossless Faces](/concepts/lossless-faces)), counted + so it can neither grow silently nor be forgotten. + +## The three-layer rhythm + +A change travels in one direction, with a receipt at every step: + +1. the **reference** changes and its corpus is regenerated, with + provenance; +2. the **mirror** pins the new corpus revision and re-derives its + ledger from a fresh run — never from the previous ledger; +3. the **package** is released and downstream consumers pin the + release. + +Skipping a receipt is the failure mode the contract exists to prevent: +a mirror that quietly kept an old corpus, or a ledger that recorded a +count instead of the failing identifiers, looks green while testing +something else. + +## Why it is the contract + +Because it is shared and executable. A doctrine ruling is not real +until a corpus row records its consequence; a grammar change is not +landed until the corpus regenerates byte-identically or the diff is +explained; a mirror is not at parity until it reproduces every row +except the documented, referenced exceptions. The corpus is how the +project turns prose rulings into something CI can refuse to merge +against. diff --git a/src/content/concepts/draft-designator.md b/src/content/concepts/draft-designator.md new file mode 100644 index 0000000..ef90676 --- /dev/null +++ b/src/content/concepts/draft-designator.md @@ -0,0 +1,46 @@ +--- +title: "The D= Designator" +description: "How IEEE project drafts spell their stage." +--- + +# The D= Designator + +An IEEE draft has two kinds of information that are easy to conflate: +the **project** it belongs to and the **stage** it has reached. The +project is spelled with a leading `P` (`P42010`), and the stage is +spelled with the `D=` designator (`D=CD`, `D=DIS`, `D=FDIS`). + +## What the designator carries + +- The stage token itself, in the organization's own vocabulary: + `D=CD`, `D=DIS`, `D=FDIS`. +- An iteration, glued to the stage with a dot: `D=CD.2` is the second + committee-draft iteration. The iteration is part of the designator, + not a separate suffix. +- A year, joined in IEEE's own format. The canonical face is a dash: + `P42010/D=CD-2020`. A colon spelling is accepted as an alias and + canonicalizes to the dash face. + +## What it does not mean + +- `P` is not "proposed". It marks the project; it is identity-bearing + and is never added or dropped by a renderer. +- A doubled `D` (`DDIS`) is not a stage. It is an alias spelling of + `DIS`; the canonical face is the single designator `D=DIS`. +- The designator is not a free-form suffix. A stage word outside the + vocabulary is a different construct, not a D= tail. + +## Two faces, one identity + +When a joint publication is led by ISO, the rendered face uses the +stage word in the ISO position (`IEEE/ISO/IEC CD 42010:2020`); the +IEEE face uses the designator. Both express the same stage, and both +re-parse to the same identity. Which face a catalogue prints follows +the perspective of the catalogue (see [Dual +Perspectives](/concepts/dual-perspectives)), not a preference of the +library. + +The invariant is round-tripping: a designator face must re-parse to the +same stage, iteration and year it printed. If it cannot, it is a +reference defect recorded in the corpus, not a spelling to normalize +away. diff --git a/src/content/concepts/dual-perspectives.md b/src/content/concepts/dual-perspectives.md new file mode 100644 index 0000000..df175dd --- /dev/null +++ b/src/content/concepts/dual-perspectives.md @@ -0,0 +1,45 @@ +--- +title: "Dual Perspectives" +description: "When two organizations assign one document two identifiers." +--- + +# Dual Perspectives + +Standards are often published jointly. `ISO/IEEE 11073-10101` and +`IEEE/ISO 11073-10101` name the same document, and the difference is +not a spelling preference: each organization assigns the identifier +from **its own perspective**, with its own lead publisher and its own +catalogue key. `ISO|IDF` and `IDF|ISO` are the same situation. + +## The rule + +Neither form is a canonicalization of the other. There is no "true" +order of the publishers, no hint that says which one a caller meant, +and no routing that silently converts one into the other. A dual +perspective is identity-bearing: the printed order is the assignment. + +This matters because the two organizations file the document under +different keys. A lookup performed against the ISO catalogue must use +the ISO-perspective identifier; a lookup against the IEEE catalogue +must use the IEEE-perspective one. Collapsing them would make one +catalogue answer for the other's key. + +## What follows for consumers + +- Parse the printed face and record which perspective it is. +- Route to the catalogue that owns that perspective — the printed + perspective *is* the routing information. +- If that catalogue does not hold the document, fall through to the + other publishers the parsed identifier names; do not rewrite the + identifier to force a match. + +## What does not exist + +- No canonical form that both sides normalize to. +- No "hint" parameter that selects a perspective out of band. +- No automatic conversion between perspectives during parse, render, + or URN generation. + +The two faces are peers. A library that makes one subordinate to the +other has quietly decided an ownership question that belongs to the +organizations. diff --git a/src/content/concepts/lossless-faces.md b/src/content/concepts/lossless-faces.md new file mode 100644 index 0000000..9cbf4b8 --- /dev/null +++ b/src/content/concepts/lossless-faces.md @@ -0,0 +1,48 @@ +--- +title: "Lossless Faces" +description: "Why a printed identifier keeps the form it was printed in." +--- + +# Lossless Faces + +A publication identifier is not a string that happens to spell a +document — the spelling *is* part of the identity. A catalogue prints +`IEEE P61850-9-3/D=FDIS-2016`, a citation prints +`IEC/IEEE P61850-9-3_FDIS, February 2016`, and both name the same +project draft. PubID treats each printed form as a **face**: it parses +the spelling it was given, records what each token meant, and renders a +canonical face that carries the same information. + +## The rule + +A transformation may not silently discard a token the source spelled. +When two spellings name one document, they are **aliases**, and the +canonical face is the one the reference's own renderer produces. An +alias is allowed to be lossy in its *surface* only if re-parsing the +canonical recovers the identity — which is what the corpus checks. + +This is why aliases can need more than one round trip. A spelling can +normalize into another alias rather than straight into the canonical; +the corpus records how many render/parse rounds each alias needs (its +`rounds` value), and both the exporter and the mirror walk them. + +## When the canonical cannot re-parse + +Some reference renders are themselves lossy — a narrative tail or a +joint render can produce a human face that the reference's own grammar +rejects. Those rows are not "bad data" to be re-spelled; they are +recorded as a **quarantine class** (`render_unreparseable`) with the +exact count, so the defect is visible and a fix trips the ledger. A +quarantine is a documented reference defect, never a silent skip. + +## The tests that keep it honest + +- Every corpus case's canonical must re-parse to itself. +- Every alias must reach the canonical within its recorded rounds. +- Every quarantine row must stay counted at the same number. +- A run that changes any of these numbers is a corpus round, with a + provenance receipt and an explanation — not an edit to make CI green. + +The faces are lossless in the only sense that matters: no consumer has +to guess which document was meant, and no authority's spelling is +overwritten by another's. diff --git a/src/content/concepts/stage-words.md b/src/content/concepts/stage-words.md new file mode 100644 index 0000000..d45e267 --- /dev/null +++ b/src/content/concepts/stage-words.md @@ -0,0 +1,48 @@ +--- +title: "Stage Words" +description: "How the word \"Draft\" and the stage vocabulary behave in a rendered identifier." +--- + +# Stage Words + +A rendered identifier says how far a document has progressed. The +vocabulary is organization-specific — `CD`, `DIS`, `FDIS` for ISO/IEC; +`Draft`, `Unapproved`, `Redline` for IEEE — and the same identity can +have a stage word in one face and a designator in another. + +## The draft word + +When a rendered face says a document is a draft, it says **`Draft` +alone**. It does not prepend the type (`Draft Standard`), repeat the +status (`Draft Unapproved Draft`), or invent a stage. + +The exception is a **project-stage draft**: when the draft belongs to a +project whose stage is itself the identity (an IEEE `P` project), the +type word is suppressed rather than embellished. A date-only draft +whose parsed designator carries no version takes the fabricated first +stage (`D1`); a designator that names an ISO stage token takes that +stage. + +## Stage words and designators + +When a joint publication is led by ISO, the stage prints as a word in +the ISO position. When IEEE leads, the stage prints as the `D=` +designator (see [The D= Designator](/concepts/draft-designator)). The +underlying stage is the same; the face follows the perspective. + +The stage also has a **harmonized code** — the numeric ladder ISO and +IEC share — which is what queries use when they ask "show me everything +at the enquiry stage" rather than "show me this token". Typed stages +carry both: the abbreviation the source printed and the harmonized +code(s) it maps to. A query by code must never miss a document because +its face printed a word instead. + +## What stage words must not do + +- They must not be dropped to make a render parseable; a face that + cannot re-parse is a corpus defect, not a licence to elide the stage. +- They must not be guessed from context when the source did not spell + them. A missing stage is missing; the library does not infer one from + a year or a project number. +- They must not be conjugated across faces. `FDIS` stays `FDIS`; the + alias `DFDIS` canonicalizes to it rather than becoming a new stage. diff --git a/src/pages/concepts/index.astro b/src/pages/concepts/index.astro index f7b10cf..6654c29 100644 --- a/src/pages/concepts/index.astro +++ b/src/pages/concepts/index.astro @@ -2,7 +2,7 @@ import BaseLayout from '~/layouts/BaseLayout.astro' import { getCollection } from 'astro:content' -const conceptOrder = ['anatomy', 'metaschema', 'components', 'algebra', 'relationships', 'identifier-relations', 'urn'] +const conceptOrder = ['anatomy', 'metaschema', 'components', 'algebra', 'relationships', 'identifier-relations', 'lossless-faces', 'dual-perspectives', 'draft-designator', 'stage-words', 'corpus', 'urn'] const items = (await getCollection('concepts')).sort((a, b) => conceptOrder.indexOf(a.id) - conceptOrder.indexOf(b.id)) ---