Changelog
Release history for MSRBot.io
See docs/buildlog.md for details of v1.0.0 released on Nov 26, 2025.
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Added
- Publisher-scoped keyword folds (
keywordDecisions.json→publisherFolds). A fold that applies to one publisher's keywords only, for an acronym another field uses differently. First use: IETF's "IAB" is the Internet Architecture Board, while in media IAB is the Immersive Audio Bitstream (SMPTE ST 2098-2). The controlled termIABis replaced byInternet Architecture Board. The IETF parser and the extractor's conform step map IETF's "IAB" to it, the unambiguous long forms fold to it everywhere, and "IAB" from any other publisher is left alone. The 13 IETF docs that carriedIABswitch on their next extract. The fold-target test coverspublisherFoldstoo. - IETF seed backfill follows lineage relations, not just citations.
seed-backfill-ietfalso seeds RFCs named bystatus.supersededBy,amendedBy,supersedesandamendson IETF docs we hold (the RFC index's obsoleted-by / updated-by edges, which the MSI builds lineages from) when they're not in the registry. Before, an RFC that replaced one we hold was only picked up if something cited it: 270 were reachable only this way. With--limit, the newer editions come first, then cited RFCs, then older editions. seed-backfill-ietf --citedtakes only the citation backlog (cited RFCs, then drafts), so a backfill can finish the citations first and see what the relation seeding adds afterwards.- The weekly IETF extract seeds new editions. It now runs
seed-backfill-ietf --relations --writefirst, so an RFC that newly obsoletes or updates one we hold is extracted in the same run and committed with its seed. Before, the weekly run only refreshed the old RFC's status. Merge after the backfill is done: until then the first weekly run would seed every outstanding relation target at once (533 at the time of writing). - IETF seed backfill in batches.
npm run seed-backfill-ietf -- --limit Ntakes only the first N missing seeds (RFCs in number order, then drafts), and--out <file>writes them as a list forextractDocs.js --only. The remaining backlog (1,260 refs: 1,173 RFCs, 87 drafts) can now be run and reviewed 100 at a time. npm run extract-smpte-journal: SMPTE journal and conference papers from a local library (#2381). New SMPTE Motion Imaging Journal issues and conferences reach MSRBot one way: the maintainer downloads them from SMPTE's AWS library. They can't be fetched on a schedule or from the web. The newsmpte-journalprovider reads that local copy and runs it through the sameextractDocs.jspipeline as the other extractors. It reads each paper'scontent_batchfile (metadata, keywords, authors) and itsFTXML/full text (references).- How it updates: new papers are added, unchanged ones skipped, and a field SMPTE changed in a re-download is updated with its old value kept. Fields locked with
excludeChangesstay as they are. - It never deletes. Hand-added keywords and authors' bios, affiliations and ORCIDs are kept. A paper's reference list replaces the stored one as a unit.
- Local copy required. Without the library XML on disk (a missing path, an empty folder, or Dropbox online-only placeholders), it prints "nothing to do" and leaves the registry alone.
- Verified one-to-one. Deleting the 300 SMPTE papers from 2025–2026 (and their 1,521 orphan references) and re-extracting them reproduced every doc and every reference exactly.
- Flags:
--source <dir>(default_source/SMPTE) and--from <year>(default 2025). Seedocs/commands.md. - References for 2015–2023 SMPTE papers, from their PDFs (
extract-smpte-journal --pdf-refs). SMPTE's library delivery for these years has no full text and no reference files (seesrc/main/reports/smpte-upstream/referenceFilesTruthTable.md), so these papers had no references. With--pdf-refs, thesmpte-journalprovider reads the reference section of each paper's published PDF (pulled from SMPTE's AWS library). It adds references only, to papers that have none, and never touches other fields. - Splitting: numbered lists are rebuilt by number, because two-column pages extract the right column first. Roman-numbered lists and unnumbered bibliographies are handled too. When the numbered pass finds fewer than two entries, a fallback reads bracket keys (
[Wang09],[2]SMPTE), footnote-style numbers and bullets, and is kept only when most entries read as citations. Page footers, figure captions, the conference disclaimer footer, run-on author bios, wrapped access dates and body text after the bibliography are dropped. - Crossref cross-check, recorded per paper. Each list's
$meta.notegives Crossref's reference count for the DOI next to the count parsed from the PDF, as a record of what SMPTE's delivery lost. A mismatch setsreviewRequired. Crossref is never a source: no data from it is written. - Resolution: DOI, SMPTE volume + issue + first page, an SMPTE paper's exact title (venue and year checked),
refMap(full citation, then title), thenparseCiteDesignator; the rest become orphans with their printed text. - Other Sources and Bibliography: a second list after the main one ("Other Sources", "Additional References", or a "Bibliography" after numbered "References") is appended. A PDF that opens with the tail of the previous article in the issue has that article's references dropped, up to the paper's own title.
- Crossref fill (
--crossref-fill), a reviewed one-time exception. For papers where the PDF parse falls short of Crossref's list, Crossref's unmatched entries are added, after the maintainer reviewed the gaps against the PDFs. Entries already present are recognized by DOI, text, or the DOI's title; empty entries are skipped, and an ambiguous paper gets nothing. Each addition is markedmapSource: crossref-fillin the MRI, and the list's note says how many came from Crossref. - Flags:
--to <year>(last year to read),--pdf-refsand--crossref-fill. Seedocs/commands.md. - Reference parser: more spelled-out lead-ins.
normalizePublisherLeadInnow accepts a dash-spaced name ("International Telecommunication Union – Radiocommunication (ITU-R), Recommendation BT.709-6" →R-REC-BT.709-6) and spelled-out SMPTE document types ("SMPTE Recommended Practice 168" →SMPTE.RP168, "SMPTE Standard 2084:2014" →SMPTE.ST2084.2014), and "EBU Technical Document 3344" →EBU.Tech3344. - One keyword rules file, shared by extractors.
src/main/lib/keywordConform.jsconforms publisher keywords tocontrolledKeywords: it clusters spelling variants, folds acronyms and long forms, and title-cases ALL-CAPS terms while keeping acronyms.src/main/config/keywordDecisions.jsonholds all the rules (folds, drops, splits, acronym casing), merged from three earlier one-off cleanup passes. The rules reproduce the keywords of every SMPTE paper from 2024–2026 from its raw terms.src/main/lib/authorBios.jsputs each author bio on the author whose name it contains. status.placeholderand a placeholder banner (schema 2.8.0). The flag marks an edition known to exist from evidence but not yet confirmed against the document: a later edition's "Revision of" line, citations, or earlier manual research. It's independent ofhref, since a copy may later come from the publisher, an archive or self-hosting. Flagged doc pages show a banner: "Placeholder record. This edition is known from evidence … but hasn't been confirmed against the document itself."extract-manualcan set it, and a record'smetaSourcesnow set the provenance of status fields on update.- Placeholders report from the MSI. Every MSI build marks flagged editions in their lineage (
statusPlaceholder, plus a lineagecounts.placeholders) and writessrc/main/reports/masterSuiteIndex-placeholders.jsonand.md. Together they are the list of placeholder editions still to be confirmed, by lineage, with their evidence and the edition that confirms them. Cited editions with no record at all stay with theirMISSING REFissues. - 2,276 SMPTE journal articles and conference papers that were never in the registry. These are SMPTE Motion Imaging Journal articles from 2016–2023 (1,856) and conference papers from 2015–2023 (420), the gap #1171 said must be filled before the
_source/scrub. They come from SMPTE's IDAMS<publication>XML, a shape no earlier pass read.src/main/scripts/extras/smpte-canonical-audit/idamsIngest.jsmints them, and its report isidamsIngest.md. - Fields: 1,321 abstracts, authors on 1,316 docs (with affiliations, ORCIDs and 1,019 bios), and keywords on 802. Labels, volume/number and publisher fields follow the 2010–2015 docs next to them. Conference papers carry the conference title, ISBN and start date. Author names use the source's Unicode spelling where it has one ("Hammershøj", not "Hammershoj").
- contentType: SMPTE's own value, corrected in 792 docs where it contradicts settled registry rulings. Example: 352 "Ad Page" items SMPTE files as
content-announcebecomeadvert, matching all 42 existing "Ad Page" docs. SMPTE's value stays incontentType$meta.originalValue. - Facet chips: eight keywords reach the 30-doc chip threshold and appear as chips: Artificial Intelligence, Video Compression, IP, Wide Color Gamut, 8K, PTP, Live Production, OTT.
- Keywords and author bios for existing SMPTE journal and conference papers, from the same source.
idamsFieldBackfill.jsfills empty fields on docs matched by exact DOI and skipsexcludeChangeslocks. ThecanonicalLibrary.*.jsondump that earlier passes used leaves these fields out. - Keywords: 65 docs (7 journal articles, 58 conference papers).
- Bios: 1,060 bios on 469 docs. SMPTE's XML often files one author's bio under a co-author (swaps and rotations on 11 papers), so each bio goes to the author it names. The report lists all 23 moves.
- Keyword handling shared by both passes (
idamsPublication.js). IEEE terms keep their own capitalization ("Digital TV", "DVB-S2", "192 kHz"). A term that is a variant of an existing one folds onto it: "High Dynamic Range (HDR)" → HDR, "Post-production" → "Post Production". Variants among new terms land once, under the spelling most docs use. Together the passes add about 2,780controlledKeywords, mostly single-doc terms kept under the indexing policy.
Changed
- Controlled keywords have their own file:
src/main/config/controlledKeywords.json(#2433). The list (4,554 terms) was 4,556 ofsite.json's 5,006 lines, so the site settings were hard to find and every keyword batch made a large diff in that file. It now sits next tokeywordDecisions.jsonas{ "_about", "terms" }, sorted withlocaleCompare(3 terms moved:YANG,YANG Extension,Y′CBCR).site.jsonno longer has the key. No terms were added or removed. - One loader. Validate, the extractor's conform step, the IETF and SMPTE journal parsers,
keywords-syncand the fold-target test all read the list throughloadControlledKeywords()insrc/main/lib/keywordConform.js.keywords-sync --writeandconfig-sortwrite it throughsaveControlledKeywords(), which keeps it sorted, one term per line. - A missing or broken list now fails validate. Before, validate read an unreadable list as empty and skipped the keyword check, so every keyword passed.
- Validate's messages name the new file. The IETF and SMPTE extract workflows find the "Unknown Keywords" block for the PR body by the stable phrases "Unknown keywords not in" and "registry has keywords outside", so the path can change without the block silently disappearing.
- Validate checks the hand-added facet chips. Each
site.jsonfacetKeywordCuration.addchip must token-match some doc keyword. Chips need not be controlled terms (AIand3Dare folded away, but matchAI Ethicsand3D Cinema), and the build quietly drops a chip that matches nothing. It follows the keyword validation mode: an error in strict mode, a warning with--warn. tokenContains()(the chips' whole-token match) moved frombuild.search-index.js, which defined it twice, tokeywordConform.js, so the build and validate share it.- Publisher config has its own file, one entry per publisher:
src/main/config/publishers.json(#2436). The sixsite.jsonpublisher keys (publisherUrls,publisherLogos,publisherLogosDark,publisherLogoAliases,publishersDateless,publisherLogoHeight) become one entry per publisher (url,logo,logoDark,aliases,dateless), sorted by name, pluslogoHeight.site.jsonis down to 172 lines of site settings. - One loader.
build.jsreads it throughloadPublisherConfig()insrc/main/lib/publishers.js, which returns the flat maps the build used before. The two hard-codedpublisherLogoHeight: 25values now readlogoHeight. The client-side_data/publisher-logos.jsonandpublisher-urls.jsonkeep their shape.config-sortkeeps the file sorted. - Dateless covers aliases. A dateless publisher's aliases are dateless too. Before, the list matched only the exact publisher name, so the doc filed under "Aldus Corp/Adobe" (TIFF 6.0) still offered an undated citation variant, and so did the "US Dept of Commerce" (NIST) doc.
- Fixes: ECMA's logo path had a stray
src/site/prefix, so its logo was broken. The "British Kinematography" alias now points at "International Moving Image Society" (was "Internationl…"), a placeholder entry until the IMIS publisher is filled in. - New
publishers.test.js: entries sorted, no unknown fields, every logo file exists, aliases resolve, dateless covers aliases, and an alias listed under two publishers throws. - Doc rendering config has its own file, and citation templates are
.hbsfiles:src/main/config/docRendering.json(#2437).site.jsonnow holds only site identity and page-head settings (14 keys, 22 lines, down from 5,006 when this cleanup started). - One entry per type.
nonLineageDocTypesandtitleLabelDocTypesbecomedocTypes({ "Book": { "titleLabel": true, "nonLineage": true } }, 21 types).contentTypeLabelsandnoPageContentTypesbecomecontentTypes({ "advert": { "label": "Advertisement", "noPage": true } }, 24 types).facetKeywordCurationbecomesfacets. All sorted by name, andconfig-sortkeeps them that way. - Citation templates are Handlebars files,
src/main/templates/citations/<style>.<variant>.hbs(8 of them:generic,smpteandtextstyles), instead of escaped one-line JSON strings. A new style or variant is a new file. - One loader.
loadDocRendering()insrc/main/lib/docRendering.jsreturns the lists the build, the registry's{year}/folders (registry.js), the suite index, page gating, the search index and validate read. The search index no longer falls back to an empty page-gate list when the config can't be read. - New
docRendering.test.js: sorted, known flags only, and every template the build uses exists and compiles. - Faster site build: about 2½ minutes → about 48 s locally, with byte-identical output. Four changes, found by profiling
npm run build: - Citation templates were compiled on every call, up to 12 times per doc page across about 15,700 pages. Each is now compiled once.
- The build loaded the registry's ~26,000 files from disk several times.
cacheDocReads()inregistry.js(used only by the build) reads each file once; every load still parses fresh objects. AsaveDoc()drops the cache. - Per-doc output (doc pages, reference-tree pages,
api/doc/*.json: ~16–33k files each) was written one awaited file at a time. It now runs 64 at a time. docIds that differ only by case (they share a path on macOS) are still written alone, in registry order. - Reference sorting called
localeComparewith options on every comparison, which builds a collator each time. The build and the search API now use one sharedIntl.Collator(same order). - Retired the SMPTE backlog tooling. The one-off scripts in
src/main/scripts/extras/smpte-canonical-audit/(31) and their reports (41, about 37 MB) are gone. The parts worth keeping now live in shared code and thesmpte-journalprovider.src/main/reports/smpte-upstream/stays. - Standards cited publisher-first now parse. Bibliographies (SMPTE's journal among them) often name the publisher before the designator: "SMPTE, ST 2084:2014", "International Organization for Standardization/International Electrotechnical Commission (ISO/IEC) 23009-1:2019", "… (ITU-R), Recommendation BT.709-6".
parseRefIdreturned nothing for these. It now strips the publisher name first. The fix is inreferencing.js, so manual extraction and every other extractor pick it up. It also corrects en-dash versions ("BT.2100–2"), "ST-2022-6" and the "IISO/IEC" typo. Author-date citations and designators followed by a parenthetical ("CEA-608-E (ANSI)") are left alone. - Two citation helpers in
referencing.js.parseCiteDesignator()reads a citation's designator only from the text before its title, so a paper whose title mentions a standard isn't linked to it.tidyCiteText()produces a JATS/NLM<ref>citation as printed, without the<label>number or the extra spaces around punctuation.mriReplaceSighting()lets a corrected re-parse replace citation text the MRI recorded earlier. extract-ietf -- --only <ids>: extract just these seeds. Takes doc IDs (RFC7595), seed URLs, draft names or a JSON array file, and bypasses the filter list for them. A backlog can be worked through in batches without re-extracting every unfiltered seed each time.- IETF requests are paced and retried. Requests to each host start at least 400 ms apart (
MSRBOTREQUESTGAP_MS), and 403/429/5xx responses are retried up to 3 times with backoff, honouringRetry-After. Datatracker answers bursts with 403s, and the extractor used to fire about four requests per RFC at once with no retry. - IETF runs keep the RFC index they used. When an IETF extract adds or updates docs, it saves the live
rfc-index.xmlit parsed tosrc/main/input/sources/ietf/rfc-index.xml, and the workflow commits it with the data. Each IETF data PR then carries the index it was built from. Runs with no doc changes write nothing. That file is also the offline fallback, which before pointed at a path that never existed. - References list in paper order. Doc pages sorted references as plain strings, so a paper's ref10 came before ref2 (1, 10, 11, …). The sort is now numeric-aware. This also puts ST 2 before ST 12.
Fixed
- Old RFC layouts (IETF seed backfill, batch 1). RFCs below 100 were treated as drafts (RFC20 became
IETF.RFC20): the IETF provider, the URL rules and the RFC-number resolver required 3 to 5 digits.parseRefIdreads the series spelled out or with "#" ("Request For Comments 870", "[RFC#635, …]"). The RFC page parser handles more pre-1990 layouts: - every line indented, headings too (RFC905): a numbered References heading ends at the next numbered heading at its indent, so the spec body is no longer read as citations;
- double-spaced pages (RFC878): wrapped entries stay whole;
- "3.CESU74" with no space after the number, a label-only line ("BELS74"), and a bracket that opens a wrapped line ("[RFC#635, …]", RFC675) no longer merge or split entries;
- a running header split over two lines at a page break (RFC761's "January 1980" / "Transmission Control Protocol");
- list numbers and labels are left off orphan citations ("1 . L. G. Roberts", RFC756; "CEKA74 V. Cerf", RFC675), and ISO draft entries ("DP 8072 …", RFC905) are kept as orphans.
Across 211 RFC pages, nothing real is lost: RFC635, RFC791 and RFC870 newly resolve, and the only dropped IDs are RFC905's body-text matches and RFC2459's "[Supersedes FIPS PUB 180 …]" note. MRI replay (67,267 citations): 4 newly resolve, none changed.
- Old RFC layouts (IETF seed backfill, batch 2).
- Annotated reference lists (RFC740): the note under each entry is no longer part of the citation, and the list number goes even when the entry opens with a quote.
- The RFC archive's transcriber note ("[ This RFC was put into machine readable form …]" and the "Notes by …" that follow, RFC635) ends the references block.
- A "References" heading glued onto the first entry is dropped from the citation (RFC635).
- "1) NIC document 8246, …" entries are kept as orphans without the "1)" (RFC333).
- A numbered entry that wraps onto the next page no longer resolves to the RFC itself through the page header's link and gets dropped as a self-reference. RFC1032's entry 10 and three RFC675 entries were lost this way.
Across 214 RFC pages, nothing is lost.
- A glossary entry named "References" is not the references section (batch 3). RFC767's field list has " References" (indented, with a definition under it), and the parser read everything from there to the real REFERENCES section as citations: 28 junk orphans of its data-structure grammar. A plain-text heading indented one to eight columns, not numbered and not in capitals, with a more-indented paragraph under it, is now taken for a definition. Across 215 RFC pages only RFC767 changes (44 → 16 orphans).
- A bit rate is not a citation's year (batch 4). The ITU-T/CCITT rule took the first 19xx/20xx number as the year, so RFC1406's "Recommendation G.732, … Operating at 2048 kbit/s, July 1988" became
T-REC-G.732.2048(and O.162 likewise). Numbers followed by a rate or size unit, and years past next year, are skipped:T-REC-G.732.1988. MRI replay: those two citations change, nothing else. - Old RFC layouts and forms (IETF seed backfill, batch 5).
- A References heading written as plain text (no section link) now ends at the next real section or appendix heading. RFC1583's appendices A–G were read as references, and "[IP address, mask]" became an orphan. Entries the htmlizer turned into section links don't count as headings (RFC756): they reuse a section number already taken, or read as a citation.
parseRefIdreads an IEEE designator with a quoted title before the date ("IEEE Standard 802.6, "Distributed Queue Dual Bus …", December 1990" →IEEE.STD802.6.1990).- It also reads ISO's stage written out ("ISO International Standard (IS) 3309, …, 1979" →
ISO.3309.1979; "International Organization for Standardization, "…", IS 3309, October 1984" →ISO.3309.1984). An addendum ("ISO IS 9595 1989: DAD1") stays an orphan.
MRI replay (68,486 citations): 13 newly resolve (8 of them SMPTE journal cites of "ISO/IEC IS 13818"), none changed. Across 318 RFC pages (100 from this batch), only RFC1583, RFC1209, RFC1510 and RFC1547 change.
- More NIST, INCITS, W3C and IEEE forms; a section about references (batch 11 review).
parseRefIdreads "ANSI INCITS.342:2001" (RFC3821 →INCITS.342.2001), "Federal Information Processing Standard 186-2" without "Publication" (RFC5091), a bare "SP 800-38A" after the spelled-out NIST (RFC5289 →NIST.SP.800-38A), a W3C TR name wrapped after its stage ("W3C REC REC- html40-19980424", RFC5122), and an IEEE project number ("IEEE Std P1363", RFC8422 →IEEE.STD1363). When a document has a "Normative References" section that discusses references and a twin under its own "References" heading (RFC3967 §1.1 vs §6.1), only the twin is the list; RFC3967's RFC1321 and RFC2104 move to informative, where §6.2 puts them. MRI replay: 21 newly resolve, none changed. - ITU-T and IEEE title-first forms; lettered annexes (batch 11).
parseRefIdreads an ITU-T lead-in with the title before the number ("ITU-T, "The Directory -- Models," X.501, 1993", "ITU-T Recommendation "…", Rec. Q.931") and IEEE's catalogue titles (IEEE, "1588-2008 - IEEE Standard for …", "802.1AS-2011 - …", "IEEE802.1ab …", December 2004). A lettered annex heading ("Annex B. Changes since RFC 5430", RFC6460) ends the references block. MRI replay: 18 newly resolve, none changed; RFC7273 gains its PTP/AVB IEEE references, RFC3672 its X.501/X.680/X.690, RFC2128 its ISDN Q.93x. - A capitalized file name in a link is not an Internet-Draft (batch 11). RFC4880's FIPS 186 entry links NIST's "…/drafts/fips186-3/Draft-FIPS-186-3March2006.pdf", and the parser minted
IETF.draft-fips-186-3, which was then seeded and 404'd. A draft name must be lowercase after "draft-" (real ones always are, even when a sentence capitalizes the D: "Draft-ietf-codec-ambisonics-00" still resolves). RFC4880's entry resolves toNIST.FIPS.186-2, and RFC1949's "draft-ietf-idmr-cbt-spec-XX" placeholders become orphans instead of draft IDs that could never resolve. - IEEE 802, ITU-T and ISO forms from batch 10.
parseRefIdreads an undated IEEE 802 lead-in ("IEEE 802.11 Wireless LAN standard.", "IEEE, 802.1p, …", "IEEE 802.1AB, …", RFC3819) and a two-level designator ("IEEE Standard 802.15.1, 2002", RFC4067); a co-designation ("IEEE 802.3/ISO 8802-3") stays with ISO, and drafts ("802.16e/D8", "P1387.2 … (Draft)") stay orphans. ITU-T spelled out with the year after the number ("… Standardization Sector, "…", X.509(2000)", RFC4523 →T-REC-X.509.2000), unless it's a co-publication with ISO. A wrapped ISO part number ("ISO/IEC 9594- 8:2001", also after a stage code: "ISO/DIS 3166- 2") no longer gives the malformedISO.9594-, no ISO/IEC rule emits an ID ending in a hyphen ("ISO/IEC 13818-Parts 1, 2, 3" →ISO.13818), and "ISO 7-bit coded character set" / "ISO 8-bit code" (RFC1345, RFC1502) aren't read as ISO 7 and ISO 8. MRI replay for that guard: 8 malformed IDs fixed (including SMPTE journal cites), 2 misreads no longer resolve, nothing else changes.refMapmaps RSA's "PKCS #6: Extended-Certificate Syntax Standard, November 1993" (no version) toRSA.PKCS6.v1.5.1993. MRI replay: 8 newly resolve; 5 malformed IDs fixed (ISO.9594-,ISO.10646-,ISO.8824-→ full part and year), and one ISO 8859 citation listing all nine parts now dates Part 1 by its own year (1987, not Part 9's 1990). - ISO/IEC Technical Reports get their own IDs. "ISO/IEC TR 9577:1990" →
ISO.TR9577.1990, matching iso.org's label: the TR stays in the ID, so a report can't collide with a standard of the same number. Also "Technical Report 9575" in an ISO citation (RFC1629). A working draft ("Working Draft ISO/IEC TR 23008-14") doesn't resolve. MRI replay: 11 newly resolve (TR 9577 in two editions, TR 9575, TR 11017, and SMPTE journal cites of TR 15938, TR 16066 and TR 23008-14), none changed. - More forms and junk from batch 9.
parseRefIdreads an ANSI X-series designator with no year ("ANSI X12.6 …, Washington D.C.: DISA", RFC1767 →ANSI.X12.6), leaves a placeholder year undated ("ANSI X9.57-199x" →ANSI.X9.57, RFC2459), and accepts the misnamed "International Standards 8602 … International Standards Organization, December 1986" (RFC2030 →ISO.8602.1986). "[Bellovin, 1992]"-style author-year keys come off orphan text (RFC2196: 123 entries), and an ASCII table in the references (RFC3250's "+----+------" change log) is not a citation. MRI replay: 5 newly resolve, none changed; RFC2246, RFC2311, RFC2409 and RFC2633 date "[X3.106] ANSI X3.106, …, 1983" as the MRI already does (ANSI.X3.106.1983). - A bare "ITU" lead-in (batch 9).
parseRefIdreads "ITU Q.931, Digital Subscriber Signaling System No. 1 …" and "ITU Q. 2965.1, …" (RFC3108) →T-REC-Q.931, taking a year only from after the designator ("ITU Q.1901" is not dated 1901). "ITU-T …" stays with the existing rules, which keep it undated. MRI replay: 15 newly resolve, none changed; RFC3108 resolves 12 more, and RFC2846, RFC3191 and RFC3192 gainT-REC-E.164.1997. - Ditto authors, spelled-out ANSI X-series, "ver." for PKCS, R#### labels (RFC2828). RFC2828's reference list writes "---," for "same author as above" in 41 entries. The parser now substitutes the previous entry's author, and
parseRefIdreads "American National Standards Institute, "…", X9.17, 4 Apr 1985" →ANSI.X9.17.1985(X3.4, US-ASCII, is filed underINCITS, asrefMapdoes). MRI replay: 7 newly resolve (including SMPTE journal cites of X3.4 and X3.210), and RFC8446's ECDSA citation moves fromANS.X9.62.2005(no document) toANSI.X9.62.2005, matching every other X9.62 citation. PKCS reads "ver. 1.5" as well as "version 1.5" (RSA.PKCS7.v1.5.1993), and RFC2828's "R1591" label resolves toRFC1591(the entry names neither the number nor the right authors). Across ~815 RFC pages only RFC2828 changes (+7 resolved). - More labels off orphan text; two non-citations (batch 8). A ditto for the previous author ("---, "Financial Institution Key Management …"", RFC2828), a key and colon before the authors ("ARM: Armijo, M.", RFC2782), and a bracketed author-year key before the authors ("[Dyer 1987] Dyer, S.", "[SSM ARCH] Holbrook, H.") are left off; a spaced label with no author after it ("[GB 2312]", "[ITU RR]") is kept. The RC4 licensing note (RFC2433, RFC2548, RFC2759) and "… (this memo, section 4)" registration lines (RFC3297) are not citations. Committed orphans renamed on re-extract: RFC911's six "[Mills 83] Mills, D.L." entries.
- More prefixes left off orphan text (batch 7). An unbracketed label before the author ("COMER Comer, D., …", "ZEN Kehoe, B., …", RFC1392) or before a repeat of itself ("BIG-LAN "BIG-LAN …"", "SAX SAX: …"), and a leading ": " (RFC2025, also after a "[1]:" label) or "- " (RFC2065, RFC1750). Only orphan text changes; 17 committed orphans get new IDs when their docs are re-extracted.
- OSF DCE RFCs are not IETF RFCs, and never-issued RFCs are never seeded. RFC1510 cites "Open Software Foundation DCE Request for Comments 26". The spelled-out RFC rule read it as IETF
RFC26, which was never issued, so the backfill seeded a URL that 404s.parseRefIdnow leaves a DCE/OSF "Request for Comments" (or "RFC") alone, andseed-backfill-ietfskips the 188 numbers the RFC index lists as not issued. MRI replay: only that citation changes. - More ISO and IEEE forms; label-per-line lists (batch 6).
parseRefIdreads "International Standard 7809, "…", ISO, 1984" (RFC935: ISO after the title, year at the end) →ISO.7809.1984, a part number after an en dash ("International Standard 13818–1", SMPTE journal) →ISO.13818-1.2000, and "IEEE Standards 802-1990" (RFC1490) →IEEE.STD802.1990. A draft ("Draft International Standard 8802.2") stays an orphan. RFC1812 prints each entry's label on its own line ("FORWARD:3."); the label is left off the orphan text. MRI replay (69,037 citations): 17 newly resolve, none changed; across 417 RFC pages only RFC935, RFC1294 and RFC1490 change (all gains). - Re-extracting a doc whose references are all orphans replaces them. When nothing in a list resolved (RFC635), the parser returned no
references, so the extractor skipped the compare step: new orphans were added next to the old ones, and stale ones were never removed. Six IETF docs have orphan-only lists (RFC635, RFC678, RFC741, RFC1843, RFC1958, one draft). - ISBN showed "[object Object]" on 419 conference papers. The schema allows
isbnas a string or{ print, electronic }, likeissn, but the doc template and citations printed it raw. The doc page now shows Print / Electronic like ISSN, and citations use one value. - Journal (FTXML) references, 2024–2026. The #1248 extraction put the ref-list heading into each paper's first citation ("References 1. R. Xu …"), kept the reference number, and added spaces around punctuation ("Xu , “ Survey ,” 16 ( 3 )").
extractFtxmlRefs.js --refreshre-parses all 176 FTXML papers and replaces their citation text. Standards citations now link to the standard: 352 references resolve, up from 54. - IETF re-extracts rewrote data that hadn't changed. The 2026-10-08 IETF run updated 40 RFCs when only two had real changes. The rest came from sources that changed on the RFC Editor and Datatracker side. A re-extract now keeps the stored value when the incoming one says the same thing in another form (
utils/reconcileIncoming.js, run byextractDocs.jsfor every provider): - Authors: initials-form names from Datatracker
doc.json("R. Braden") no longer replace full names ("Robert T. Braden"). Order, "Dr." and accent folding ("Bjorklund" for "Björklund") don't count as changes. A different or added author still updates. The IETF parser also gets full names again:doc.jsonand the RFC index now give initials only, so it reads the Datatracker doc page'sarticle:authormeta (each author's profile name, in author order), when the count matchesdoc.json. - URLs: a
resolvedHref/hrefthat differs only by a trailing slash is kept. rfc-editor.org now redirects/info/rfcNto/info/rfcN/. - Abstracts: a whitespace-only difference is kept.
- Keywords: held controlled keywords are never dropped. New ones are added unless they're a case, plural, hyphen or one-letter variant of a held one ("WOTS" for "W-OTS"). A held keyword outside
controlledKeywordsis first conformed to the shared rules: dropped ifdropslists it, and replaced by the controlled term its fold or casing points to ("Aaa" →AAA, "Transport Layer Security" →TLS). A re-extract therefore cleans up keywords an earlier run stored, without a reset. The index's "eneryption" typo folds toEncryption. - Status lists: the same IDs in another order are kept.
- IETF relations from the RFC index only.
amends,amendedBy,supersedesandsupersededBycame from the RFC index merged with the RFC info page. The redesigned info page leaked stray IDs (RFC1035 "amended" the RFCs it obsoletes; RFC9190 "amended" RFC2119). The index is now the only source when it has the RFC, and the info page is a fallback. - Dated ITU-T references lost their year. "ITU-T Recommendation X.660, July 2011" resolved to undated
T-REC-X.660instead ofT-REC-X.660.2011after #2380's lead-in rewrite. That rewrite now applies to ITU-R only. - Non-citation lines minted as orphan references. The RFC8323 WebSocket example steps ("2. The CoAP client establishes …") were already filtered from the bad-refs report, but they still became orphan refs. The filter now runs before minting.
- IETF keywords follow the shared rules. IETF index keywords now go through the
foldsanddropsinkeywordDecisions.json, like SMPTE's. So "internet of things" becomes the controlledIoTinstead of re-adding a term the June keyword scrub removed, and contentless index words ("values", "implementations") are left out. A word the index writes with capitals past its first letter keeps its casing (BOOTP,TLSv1.0,IPv6,WebSocket,PKIX; these came out as "Bootp" and "Ipv6"). A term already incontrolledKeywordstakes that casing instead ("MAIL" →Mail). "NNTP", "TLS", "WS" and "WSS" stay all caps, "of"/"in" stay lowercase, and an abbreviation listed on its own as "(MTU)" loses the parentheses. - New rules from the first IETF backfill batch: folds Maximum Transmission Unit →
MTU, Uniform Resource Name →URN, Internet Assigned Numbers Authority →IANA, the index typo "emal address" →Email Address, Codepoint →Code Point, and TELNET/HOSTNAME →Telnet/Hostname. Drops: values, implementations, deprecate, symmetric, well-known, authenticator, protocol constant, protocol parameter, transport protocol layer, international data algorithm. 35 IETF terms joincontrolledKeywords(protocols, specs and registries such asTLS,FTP,NETCONF,PKIX,X.509, and the Telnet option namesTOPT-*). - Batch 2: folds the long forms into their acronyms (Transport Layer Security →
TLS, Datagram TLS →DTLS, Simple Network Management Protocol →SNMP, Internationalized Resource Identifier →IRI, Entertainment Identifier Registry →EIDR, and others) and the RSA variants intoRSA,RSA-OAEPandRSA-PSS. Drops unauthenticated, security attacks, embedded internet. 42 terms joincontrolledKeywords. The parser also strips a leading "and"/"or" (the index lists "and URN" as a keyword) and keeps version tokens as written ("PKCS #1 v1.5", not "V1.5"). - RFC appendices parsed as references. For RFCs whose HTML is converted from text (most before 2019), the last References heading ran to the end of the page, so appendices, acknowledgments and author addresses were read as citations. RFC8446's state-machine diagrams, RFC7296's message flows, RFC8017's algorithm steps and page-break residue ("[Page 85") became orphan refs. A references block now ends at the first heading outside its own section ("Appendix A" after "12. References"), or at an unnumbered section ("Acknowledgments", "Authors' Addresses"), which htmlized RFCs print as unindented text. Page footers ("[Page 85]") are no longer read as reference labels. RFC821-style key lines ("[1] ASCII", where the citation follows on the next line) and footnotes inside old reference lists ("[**] Editor's Note", "<4> …") are no longer reported or saved as orphans. On a 37-RFC sample, no real reference is lost.
- Run log reports orphans as orphans. The extract log and PR body listed every citation without a standard ID under "Unparseable References Found", on every run, including orphans already in the MRI. Those are parsed citations kept as orphan refs, not failures. The log now has three parts: New orphan refs (created by this run, with their slug; capped at 20 in the PR body), a count of orphans already in the MRI that were seen again, and Unparseable references for only the citations that couldn't be recorded at all.
mriRecordSightingreturnscreatedfor a newly minted orphan. Orphans also store the citation without its reference-list label: "[DH] Diffie, W. …" was saved as "DH ] Diffie, W. …". Since orphan IDs are hashes of that text, the labelled and unlabelled captures of one citation (RFC7296 had both for 2 citations) now collapse into one orphan. A citation's wrapped author line that looks like a new entry ("Kohlweiss, M., Pan, J., …" inside RFC8446's [BDFKPPRSZZ16]) is no longer saved as a second orphan holding the tail of the first. An old numbered-list label ("3. Pickens, J., …") is stripped like "[3]", and a citation's wrapped "Latest version available at <…>" line (RFC7303) is not a citation. A citation that resolves once its label is stripped is cited by its ID instead of becoming an orphan: RFC8439's "Zhenqing2012 ] Zhenqing, S., …" now matches itsrefMapentry and cites10.1007-978-3-642-37682-5_24. Simulated over the 143 RFCs the backfill covers so far: 202 orphans, each a real citation. - Standards as xml2rfc prints them.
parseRefIdnow reads "ISO Standard 10646:2014" / "ISO Standard 26324, 2012", "ANSI X3.106, 1983", "IEEE 802 Std 802.11F(TM)-2003", "IEEE 1003.1, 2013 Edition", and a closing "IEEE 754". Replaying all 63,771 recorded MRI citations: 11 newly resolve, and none that resolved before changes. - Batch 5: folds "Hyptertext Transfer Protocol" (index typo) →
HTTP, Data Model →Data Modeling, Digital Security →Security; drops template, text, HTTP content, HTTP payload; 24 terms joincontrolledKeywords(OCSP,KLV,Diameter, the HTTP/1.1 series topics, privacy terms such asAnonymityandPseudonymity,Encoder,Parser, and others). - Batch 6: folds the long and variant forms (Hypertext Transfer Protocol →
HTTP, Network Configuration Protocol →NETCONF, Simple Mail Transfer →SMTP, Email I18n →EAI, Ports →Port Numbers, the index typo "Optical Asymmetric Encryption Padding" →RSA-OAEP, and others). Drops allocation, assignment, procedures, policy, secure, and RFC6066's TLS extension names (server_nameand five more: protocol identifiers, not topics). 19 terms joincontrolledKeywords. The IETF acronym list gains DTLS, ECN, IDN, IDNA, RSA and SubjectPublicKeyInfo. A re-run also conforms held keywords when the source gives none, and removes the field when every held keyword is dropped. - Batch 7: drops RFC5322's header field names (
From,To,Subject,Message-ID, theResent-*fields and others: protocol identifiers, not topics) and generic terms; folds long forms and variants (Augmented Backus-Naur Form →ABNF, Authenticated Encryption With Associated Data →AEAD, Extensible Authentication Protocol →EAP, E-mail →Email, X.509 v3 →X.509, Webdav →WebDAV, and others). 12 terms joincontrolledKeywords, includingUniversal Labels(SMPTE ULs, RFC5119) andPacketCable. - Batch 8: IPsec, LDAP, SHA-2, DOCSIS and ONC RPC terms; folds long forms to their acronyms (Encapsulating Security Payload →
ESP, Lightweight Directory Access Protocol →LDAP, Cable Modem Termination System →CMTS, …) and index typos ("LDAV3", "EMACScript", "Channel Utilizazation"). Splits "Authentication Integrity" intoAuthenticationandIntegrity: the IETF parser and the re-run conform step now applysplitsas well as folds and drops. Drops only terms that name no subject (Format, Key, Node, …) and RFC4510's labels for other LDAP RFCs; 44 terms joincontrolledKeywords. - Batch 9: cryptography, MANET routing, S/MIME and EAP terms; folds long forms (Elliptic Curve Digital Signature Algorithm →
ECDSA, Globally Unique Identifier and GUID →UUID, Mobile Ad-hoc Network →MANET, …), the three EAP authenticator types →EAP Authenticator, and RFC4086's separate "random" + "numbers" →Random Numbers. 38 terms joincontrolledKeywords. - Batch 10: the RFC index sometimes lists a phrase as one-word keywords ("federal", "information", "processing", "standard"). The IETF parser and the re-run conform step now rejoin consecutive keywords when the joined phrase is a fold or a controlled term, so RFC3174 gets
FIPSand RFC3075 getsXMLinstead of the fragments. New folds make those land (extensible markup language →XML, multipurpose internet mail extension →MIME, uniform resource names →URN, data-over-cable service interface specifications →DOCSIS, …), plus CRL, PKI, GSMP, SMTP, TIFF, ISO and OID forms. 15 terms joincontrolledKeywords, includingAdvanced Encryption Standard, kept apart fromAES(the facet chip for the Audio Engineering Society). - Batch 11: more split index phrases rejoined (internet engineering task force →
IETF, escape clause, character data, payment system); long forms folded (Asynchronous Transfer Mode →ATM, Remote Authentication Dial in User Service →RADIUS, Domain Name System →DNS, Simple Public Key Infrastructure →SPKI, WG →Working Group, …); 17 terms joincontrolledKeywords. - Batch 12: more split phrases rejoined (cipher block chaining →
CBC, hyper text markup language →HTML, privacy enhanced mail →PEM, world-wide web, intellectual property, standard generalized (language) →SGML); "transport protocol layer" now folds toTLSinstead of being dropped (batch 1).Applicationis a controlled term ("applications" folds to it rather than being dropped). 17 terms joincontrolledKeywords, including plainHTML(RFC1866 is HTML 2.0; the existing term isHypertext Markup Language (HTML5)) andIAB. - Batch 13: split phrases rejoined (wide area information servers →
WAIS, serial line, request for comment →RFC; batch 10's drop of "RFC" is lifted, since RFC1543 Instructions to RFC Authors is about RFCs); GB2312-80 →GB 2312, IPV6-AH →AH, BINHEX →BinHex; generic terms dropped (status, procedure, registered, allocated, specification, standard, link, character); 13 terms joincontrolledKeywords. - Batch 14: Character Sets →
Character Set, Electronic-mail →Email; drops information, locating, set; 11 terms joincontrolledKeywords(Gopher,IMAP2,MD4,Message Handling Systems, the PEM series labels, …). - Batch 15:
NetBIOSand four more Telnet option names (TOPT-DATA,TOPT-NAWS,TOPT-TERM,TOPT-TS) joincontrolledKeywords. - Batch 16:
Notation(the JSON Schema drafts) joinscontrolledKeywords. - Stable on re-run: conforming repeats until nothing changes, and the IETF parser ends with the same step. A fold can make a new joinable pair: the index lists "Internet", "Protocol", "Security", which folds to
IP,Securityand then joins toIPSEC. Before, one pass of the stored list made that join while the incoming one didn't, so every re-extract movedIPandSecurityto the end (RFC2104, RFC4306). RFC1826, RFC2104 and RFC4306 lose those two words, which mean IPsec, already listed. - Seed backfill batch 1: 20 terms join
controlledKeywords: the RFC index's protocol namesARP,ICMP,GGP,HMP,RLP,IP-E,IP-EEand 13 more Telnet option names (TOPT-BYTE…TOPT-SUPO). Address Resolution Protocol →ARPand Internet Control Message Protocol →ICMPfold. The index mnemonicsQUOTE(RFC865) andTIME(RFC868) are dropped: as controlled terms they'd recase any later "Time" keyword from other publishers. - Seed backfill batch 2: 16 terms join
controlledKeywords: protocol namesIRTP,MPM,NETRJS,RDP,RJE,SFTP(RFC913's Simple File Transfer Protocol),POP2, Telnet optionsTOPT-OM,TOPT-TACACS,TOPT-TLN,Tenex, and RFC635's topics (Acknowledgment,Duplicate Detection,Host Retransmission,Multipacket Messages,Sequencing). Folds: Post Office Protocol →POP, Remote Job Entry →RJE, Reliable Data Protocol →RDP, Paged →Paging, Acknowledgement →Acknowledgment. Drops:Version 2, RFC635's section title "Theoretical and Practical Motivation For Redesign", andCLOCK(RFC778; same reason asTIME). - Seed backfill batch 3: 12 terms join
controlledKeywords:HEMS,IPSO,NFILE,PCMAIL,SGMP,VMTP,TP-TCP,TOPT-3270,TOPT-X.3,TOPT-XDL,DVMRP,SLIP. The RFC index'sIP-DVMRP,IP-SLIPandIP-IEEEfold toDVMRP,SLIPandIEEE 802. - Seed backfill batch 4: the RFC index splits phrases into single words, so fragments are dropped and the phrases fold back together.
- 72 terms join
controlledKeywords: RFC index protocol names and mnemonics (EGP,IGMP,NTP,TFTP,IS-IS,CLNP,X.25,MIB-II,SNMP-,PPP/… MIB, threeTOPT-, …) and topics (OSI,Directory Services,Distinguished Names,Naming,Resource Records,Window Scale,Line Speed,Terminal Access,Technical Reports,Facsimile,Unix,Booting). - Folds: open systems interconnection →
OSI, trivial file transfer →TFTP, internet society →ISOC, NTPv2/NTPv3 →NTP, remote-login/remote-logon/BSD Login →Remote Login, domain names →DNS, gateway(s) →Routers, directory →Directory Services, directory names →Distinguished Names, management framework →Network Management, and the long form ofPAWS.routernow folds toRoutersinstead ofNetworks. - Drops: 21 single-word fragments (on, open, point, line, speed, window, scale, …),
c=us(RFC1255), andALERTS(RFC1224; same reason asTIME).NETFAXjoins too (72 terms). - Seed backfill batch 5: 42 terms join
controlledKeywords: OSPF terms (OSPF,SPF,LSA,Link State,Flooding,OSPF-NSSA,OSPF-Multi),DHCP,CIDR,TACACS,ECMP,TLD,UPS,Routing,IP Address,IEEE 802.3and RFC index MIB/protocol names. Folds rebuild split phrases (open shortest path first →OSPF, dynamic host configuration →DHCP, uninterruptible power supply →UPS, equal-cost multipath →ECMP, not so stubby →OSPF-NSSA, …) and map index forms (FR→Frame Relay,802.3→IEEE 802.3, classless →CIDR, fax →Facsimile, port →Port Numbers). Drops: the phrase fragments andDAYTIME,FINGER(same reason asTIME).Beeperstays apart fromPaging(RFC683's file pages). - Seed backfill batch 6: 51 terms join
controlledKeywords(protocols such asRIP,BGP,POP3,IMAP4,ESMTP,RTCP,ZLIB,DEFLATE,GZIP,X.400; RFC index MIB and protocol names; topicsFirewalls,Checksum,CRC,Hub,Addressing). Folds rebuild split phrases (transmission control protocol →TCP, point-to-point protocol →PPP, mail transfer protocol →SMTP, next generation →IPng, …) and fix index typos (energyption →Encryption, crypgraphic →Cryptography, asychronous →ATM). 22 fragments are dropped. - Seed backfill batch 7: 71 terms join
controlledKeywords(protocols such asRSVP,SASL,LMTP,RMON,OTP,DES,HMAC,QoS,IESG,ACL, RFC index mnemonics, and topicsSource Quench,Fair Queueing,Random Drop,Integrated Services,Message Authentication Code, the last kept apart fromMAC, Media Access Control). 29 folds rebuild split phrases (SQ →Source Quench, uniform resource locators →URL, internet message access protocol →IMAP, internet architecture board →IAB, internet engineering steering group →IESG, …). 21 fragments are dropped. - Seed backfill batch 11 (644 RFCs): 905 new keywords, the largest batch. 532 terms join
controlledKeywords(139 acronyms such asCBOR,COSE,JOSE,JWT,DKIM,DANE,ICE,TURN,RPKI,CAPWAP,6LoWPAN,NAT64, and 311 topic phrases, mostly single-doc). 189 folds map long forms to their acronyms (JSON Web Token →JWT, Datagram Transport Layer Security →DTLS, Network Time Protocol →NTP, …) and fix typos ("AES in Couner With Cbc-mac Mode" →AES-CCM, "Internet Message Acess Procotol" →IMAP, "Atrribute Certificate" →Attribute Certificate); emergency-call variants fold toEmergency Services. 266 are dropped: generic words (all of RFC7282's consensus vocabulary: Hum, Vote, Capitulation, …), generic phrases, and terms a document defines for its own use (RFC6749's Access Token, Authorization Grant, …), as with RFC6066's TLS extension names. An initials match isn't trusted blindly: "Constrained-node Network" is notCNN, "Power Sourcing Equipment" is notPSE, "Private-key Information" is notPKI. - Seed backfill batch 10 (400 RFCs): 148 terms join
controlledKeywords: protocols and index mnemonics (XMPP,CPIM,MSDP,NHRP,NAT-PT,RSVP-TE,DS-TE,SOAP,OCSP,SPNEGO,NTLM,L2VPN/L3VPN, …) and topics (Calendaring,Certification Authority,CE Router/PE Router,Cipher Suite,Pre-shared Key,Initialization Vector,Suite B,Time Zone,Wireless LAN,IEEE 802.11,IEEE 802.15.4,ITU, …). 158 folds map long forms, typos ("Simle Mail Transfer Protocol", "Instante Message") and variants to them; 201 fragments and descriptive phrases are dropped. The short keysCA,MN,IVandCEfold only for IETF (publisherFolds), since another field may mean something else by them (CA is also content authentication); "Certification Authority" / "Certificate Authority" fold for everyone. - Seed backfill batch 9 (400 RFCs): 157 terms join
controlledKeywords(protocols and index mnemonics such asCOPS,GMPLS,ROHC,SACK,SIIT,PIM-SM,MEGACO,HSRP,SNTP,IRC,NFS,SONET,SDH, plusAssured Forwarding,Comfort Noise,Congestion Manager,Reliable Multicast). 85 folds map long forms and index typos to their acronyms (Common Open Policy Service →COPS, RObust Header Compression →ROHC, "Hyper Text Cashing Protocol" →HTCP, "Autonomous Transfer Mode" →ATM, "Architechure Board" →IAB, …) and AF/EF toAssured Forwarding/Expedited Forwarding. 103 generic words and fragments are dropped, including RFC2323's April Fools terms. - Seed backfill batch 8 (400 RFCs): 174 terms join
controlledKeywords(protocols and index mnemonics such asSCTP,MPLS,LDP,L2TP,PGP,RTSP,SRTP,STUN,VRRP,DNSSEC,DHCPv6,ICMPv6,IDNA,OLSR,MIKEY,NAT,VPN,DiffServ,BEEP, and topics such asInstant Messaging,Key Management,Flow Label,Mobile IP,Autonomous System). 160 folds map long forms to their acronyms (Session Initiation Protocol →SIP, Pretty Good Privacy →PGP, Web Distributed Authoring and Versioning →WebDAV, …), fix casing (Mbone →MBONE, Snmpv3 →SNMPv3) and typos (Isamkp →ISAKMP, Crytographic, Secruity). 146 generic words and description fragments are dropped. RFC3547'sDOI(IPsec Domain of Interpretation) is dropped rather than added, so it doesn't collide with Digital Object Identifier, andDomain of Interpretationis the term instead.MTAstays ambiguous;Media Terminal Adapteris separate. - Orphan-only reference lists get the extractor's
$meta. A reference list made only of orphan refs (RFC678: two citations, neither with a standard ID) had no$metafrom the merge, so canonicalize injected a default marked"source": "manual". The extractor now writes the list's$meta(parsed, with a note andsourceUrl) when it attaches orphans. - More reference forms and boundaries (batch 6).
parseRefIdreads "Federal Information Processing Standard (FIPS) 180-2", "FIPS Publication 180-3", and an undated "(ANSI) X9.62". An unindented heading is recognized with links in it ("Appendix: Changes from <a…>RFC 4634</a>", RFC6234), so that change list is no longer read as citations. A heading must follow a blank line and has no period, so RFC2060's unindented entries ("Work in Progress." on a wrapped line) don't end the block. Across 153 RFC pages: no real reference lost. MRI replay: 4 newly resolve, none changed. - IEEE 802 citations and long labels (batch 7).
parseRefIdreads an IEEE designator with a letter suffix or a comma before the year: "IEEE Standard 802.1X-2004", "IEEE Standard 802.11, 2003", "IEEE Standard 802.1X, December 2004". MRI replay: 11 newly resolve, none changed. Reference-list labels may start with a digit and run to 64 characters ("802.1X", "3GPP-SA3-030736"), so those orphans store the citation without the label. RFC5246's "Copyright (C) The IETF Trust" line is not a citation. - SCTE, ETSI and ANSI forms (batch 8).
parseRefIdreads SCTE outside the "ANSI/SCTE" form ("… Specification SCTE 22-2 2002", "SCTE 127-2007", "SCTE 35 2023r1" →SCTE.35.2023), "(ANSI/SCTE) 67 2010" and part numbers ("ANSI/SCTE 23-3 2005"), "ETSI Standard EN 300 429", a citation that opens with an ETSI EN number ("EN 300 001 V1.5.1 (1998-10)"), and "ANSI X3.106, \"<title>\", …, 1983".refMap.jsonmaps "ANSI X3.4, 1986" (US-ASCII) to the registry'sINCITS.X3.4.1986, not the bareANSI.X3.4.1986. MRI replay: 32 newly resolve (many from SMPTE papers); the only changed results are the 5 US-ASCII citations moving to INCITS andISO.5-4-1995→ISO.5-4.1995. Whether all ANSI X3 standards should be filed under INCITS is left for the orphan/variant cleanup. - FIPS, IEEE, ETS, 3GPP and SECG forms (batch 9).
parseRefIdreads "Federal Information Processing Standards Publication 197" (no "FIPS" token), "(IEEE) Standard 1363-2000", "GSM 03.20 (ETS 300 534)", "3rd Generation Partnership Project" spelled out (not 3GPP2), and SECG "SEC 1 …, Version 1.0, September 2000" →SECG.SEC1.v1.2000-09. This corrects a citation that resolved to the wrong edition: "(IEEE) Standard 1588-2019 (Revision of IEEE Standard 1588-2008)" gaveIEEE.STD1588.2008, nowIEEE.STD1588.2019. MRI replay: 9 newly resolve, 1 corrected. Contact lines ("Phone: … EMail: …") and a citation's URL wrapped onto its own line are not orphans, and labels may contain "/" ("[ISO/IEC-18033-2]"). - URLs printed in citations (batch 10). Old RFCs print a citation's URL as text, often wrapped across lines ("…/TR/2000/WD-xsl- 20000327/xslspec.html"), and the W3C, Unicode and GitHub rules only read a citation's link.
parseRefIdnow uses the first URL printed in the citation (rejoined; bare "www." included) when there is no link. The W3C rule also accepts the older/YYYY/MM/layout and a page after the date. Also new: Unicode "TR15 … Revision 18" →UNICODE.STD.TR15-18, "IEEE 1363: <title>. August 2000." →IEEE.STD1363.2000, and arefMap.jsonentry for Adobe TIFF 6.0 →TIFF.r6.19920603. The truncated RFC3075/RFC3275 entries ("Brickley, R.V. Guha. … CR-rdf-schema-20000327/") now resolve through their URLs. MRI replay: 45 newly resolve (33 W3C); 1 corrected (an RDF Schema CR citation that resolved to RDF Model and Syntax). - Unnumbered reference headings end their block (batch 11). An unanchored "References" / "Normative References" heading (RFC2629, RFC2898, RFC5246) now also ends at the next unindented heading ("Author's Address", "Contact Information & About PKCS"), so an appendix's numbered lists, RFC2898's "About PKCS" text and the copyright notice are no longer read as citations. The start skips the blank lines before the heading, so the heading itself isn't taken as the end; 173 RFC pages lose no reference. Also: RFC2616's editors' change markers ("[jg639") are not orphans; a bare-URL check runs on the raw line only, so "[5] http://…" references are kept; and
refMap.jsonmaps XML 1.0 (February 1998) toW3C.xml.19980210. - RSA PKCS, NIST SP 500, UCS and US-ASCII 1968 (batch 12).
parseRefIdmaps RSA Laboratories PKCS citations to the registry'sRSA.PKCS<n>.v<version>.<year>form (it had refMap entries for PKCS #5 and #8 only), but only when RSA Laboratories or RSA Data Security is the publisher, so papers about PKCS #1 stay orphans. It also reads NIST "Special Publication 500-202".refMap.jsonadds ISO/IEC 10646-1:1993 by title (requiring "ISO", since China's GB 13000.1 has the same title) and the "USA Standard Code for Information Interchange, X3.4 … 1968" form ofINCITS.X3.4.1968. MRI replay: 23 newly resolve (17 PKCS), none changed. A reference that is only contact details (RFC2246[RSADSI]"Contact RSA Data Security, Inc., Tel: …") is not an orphan. - Reference entries across page breaks (batch 13). In RFC pages converted from text, a page break (the "[Page N]" footer, the divider and the next page's running header) ended a reference entry, and its continuation was read as a separate citation. RFC1700 split
[RFC1468],[BBN1822]and others this way ("Keio University, Panda Programming, June 1993."). Entries now continue across page breaks with the page furniture removed. The prose fallback skips a paragraph that is already part of an entry a marker pass read, which also drops the unlabelled duplicate copies of labelled entries it used to emit, and one false citation (RFC1766 cited RFC822 only through its RFC1327 reference's title). 199 RFC pages: no real reference lost. Empty entries (a lone label like RFC1700's[ANSI-X12], or only "Work in Progress.") are not orphans. A bracketed label with spaces ([GB 2312]) is stripped only when the citation repeats its designator ([ECMA TR/53]is kept). - ISO "International Standard NNNN", FIPS PUB in parentheses (batch 14).
parseRefIdreads "International Organization for Standardization, International Standard 8824, December 1987" →ISO.8824.1987, and "Federal Information Processing Standards Publication (FIPS PUB) 81" →NIST.FIPS.81. This also corrects "(FIPS PUB) 46-1", which lost its revision (NIST.FIPS.46→NIST.FIPS.46-1).refMap.jsonmaps RFC1502's numberless ISO titles (names of languages 1988 →ISO.639.1988; coded graphic character set for text communication →ISO.6937). Parenthesized list labels ("(4) IBM …", RFC1345) are stripped from orphan text. - Old-RFC citation text (batch 15). Orphan text no longer keeps a list number that follows a bracketed label (RFC1032 "… ] 2. Defense Communications Agency …"), a trailing page footer ("… Fourth Edition. Sirbu [Page 8", RFC1049), or RFC1032's retrieval notes ("[ DDN-NEWS:DDN-MGT-BULLETIN-32.TXT ]", "[ Not online ]"). The two captures of one RFC1032 entry now collapse into one orphan.
- Internet-Draft references without archive XML (batch 16). Draft references came only from the archive XML (
ietf.org/archive/id/<draft>.xml), which text-only and older drafts don't have: 12 of the 21 drafts in the backfill would have had no references. The IETF parser now falls back to the Datatracker page (datatracker.ietf.org/doc/html/<draft>). The RFC HTML reference parser reads it once it accepts Datatracker's<h2>/<h3>section headings and its page-break layout, and stops at the end of the draft text (</pre></div>) so the site's logo, buttons and version list aren't read as references. 203 RFC pages: no change. - Internet-Draft citations and "-bis" drafts (batch 16).
parseRefIdresolves an xml2rfc anchor ("I-D.ietf-core-coap-tcp-tls", all that an included bibxml reference leaves) and a versioned draft name printed in a citation ("…, Internet-Draft, draft-ietf-tls-dtls13-39, …") toIETF.draft-…. MRI replay: 112 newly resolve, none changed. The IETF parser no longer takes an RFC number from a draft's name:draft-ietf-netmod-rfc8022bis-11anddraft-ietf-tls-rfc8446bis-03were extracted as RFC8022 and RFC8446. - Internet-Draft names must look like drafts. Any "draft-xxxx" token in an RFC page became an
IETF.draft-xxxxID, so RFC1052's 1988 letter codes ("DRAFT-SMI", "DRAFT-AAAA", which it explains are temporary notation) became 7 bogus draft IDs, andseed-backfill-ietfwould have proposed them as seeds. A token now needs a lowercase name with at least two parts after "draft-". - Doubled surnames in Datatracker profiles. A profile name that repeats its last word ("Jeff Downs Downs", RFC6597) is collapsed ("Jeff Downs"). Only names of three or more words are affected.
- NIST SP revisions stay lowercase. A NIST SP number read from citation text was uppercased whole, so a DOI in the text ("NIST.SP.800-56Ar3") became
NIST.SP.800-56AR3, a second ID for the same publication. The revision now staysrN, as NIST writes it.
Added
- 317 SMPTE placeholder editions and
revisionOfon 627 SMPTE documents, read from PDF front pages (#2373). A SMPTE front page names the edition it replaces ("SMPTE 259M-2008 — Revision of SMPTE 259M-2006"). A scan of every PDF release on pub.smpte.org found 628 such lines. - Placeholders: 317 named editions had no digital copy and weren't in the registry, so each is now a placeholder record. It has no
href, is superseded by the edition that names it, and has astatusNoteciting that document. One is the one-off Engineering Committee Report SMPTE ECR 1-1978. revisionOf: the 627 documents that name a predecessor now carry it. Before this, only 15 HTML releases did. Some links cross document families (ST 12-2 ← RP 188, ST 377-1 ← ST 377M).- Impact: 91 of the 153 open SMPTE missing references now resolve (#2242). MSI lineages go from 1,446 to 1,454.
- New docType "Engineering Committee Report" (schema 2.7.0). A one-off SMPTE designation (ECR), abbreviated "ECR" on the site. The only known instance is ECR 1-1978, revised by EG 1:1990.
src/main/scripts/extras/scanRevisionOf.jsreads a document's predecessor from its PDF front page. A SMPTE front page names the edition it replaces ("SMPTE 259M-2008 — Revision of SMPTE 259M-2006"). The tool parses that line, including older RP/EG/RDD pages that omit "SMPTE" and lines that run on into the document's own designator, and maps the predecessor withparseRefId.- Modes:
--pdf <url|path>reads one PDF, for manual extraction.--docsand--all-smptescan registry documents through their pub.smpte.org release pages and write a JSON report. It never writes the registry. - Docs:
docs/manual-extraction.mdand themsrbot-extractskill now recordrevisionOffrom that line, and describe placeholder records for predecessors with no digital copy. - Guards against false matches: it reads every predecessor named on the line, stops at the document's own designator (including one printed with a different year), and skips a number that follows an unrecognized prefix ("AES3-1992", "XYZ 4-1980").
- ECR: SMPTE Engineering Committee Reports now parse ("SMPTE ECR 1-1978" →
SMPTE.ECR1.1978). - New devDependency
unpdf(MIT, 2.1 MB, no dependencies) for PDF text extraction. The repo had no PDF text tooling, andpdfjs-distis 35 MB. src/main/scripts/extras/rekeyRefs.jsre-keys stored citations after a parser fix. Citations written by earlier runs or one-off backfills keep the refId the parser produced at the time.--refs A,Bre-parses each citing doc's verbatim cite (from the MRI sightings) and replaces the stored refId when the result differs.--rename OLD=NEWhandles citations with no cite text, e.g. after a registry merge. Dry run by default;--applywrites, recording the prior list in<type>$meta.originalValue.- CTA, ICC, W3C author-date and GitHub citations parse. New parser families read CTA designators ("CTA 861-G", "ANSI/CTA-608-E S-2019" →
CTA.608-ES.2019), ICC specifications ("Specification ICC.1:2022" or a color.orgICC.1-2022link →ICC.1.2022) and W3C author-date cites ("World Wide Web Consortium (W3C) (2004, October 28). XML Schema Part 1: …" →W3C.xmlschema-1.20041028). A cite with no other match whose link is a GitHub repository root becomesGITHUB.<owner>.<repo>. New refMap entries cover Kakadu (KAKADUSOFTWARE.COM), OpenJPEG (OPENJPEG.ORG) and the MovieLabs BT.709-to-HDR10 best practice (MOVIELABS.BT709-HDR10.v1.0). These are the seven citations from ST 2067-21:2026-07 and ST 2094-50:2026-08 that were left unparsed. Across the 49,925 distinct citations in the MRI, 24 more now parse, with none lost or changed.
Changed
- Registry data cleanup from the SMPTE missing-ref audit (#2242).
- Renamed:
CMR.ML→MOVIELABS.CMR, the publisher-prefixed form, matchingMOVIELABS.BT709-HDR10.v1.0(#2164). - Duplicate records merged into the one kept:
SMPTE.RP27.3.1989→SMPTE.RP27-3.1989andSMPTE.RP27.4.1994→SMPTE.RP27-4.1994. Dotted part numbers aren't the registry's numbering.SMPTE.ST292-0.2011, a Zoho duplicate, → the extractor'sSMPTE.OV292-0.2011.- What a merge keeps: each merge only adds the duplicate's fields (abstract, ISBN, ICS codes, approval date and so on) and takes its DOI, the form registered at doi.org.
RP27-4keeps its parsed referenceANSI.IT2.19.1990, not the duplicate's hand-enteredNAPM.IT2.19.1994. - Re-keyed citations: 43 citations in 40 docs, all re-derived from their verbatim cites with
rekeyRefs.jsafter the group C and D parser fixes. Each list keeps its prior value in<type>$meta.originalValue. - Notes:
SMPTE.ST386.2004andSMPTE.ST387.2004gain adetailsnote. They cite "SMPTE 305.2M-2002", an edition that was probably never published (ST 305 went from 305.2M-2000 to ST 305:2005). The citation is kept as printed.
Fixed
- ECR documents key into the MSI. The SMPTE keying pattern didn't include the new ECR type, so
SMPTE.ECR1.1978was filed as UNKEYED (#2374). It now has its own lineage. - Old SMPTE designators parse to the right id (#2242, group D).
- Bare ANSI/SMPTE numbers: "ANSI/SMPTE 40–1991" was caught by the ANSI co-designation rule and lost its type (
SMPTE.40.1991). It is nowSMPTE.ST40.1991. - En and em dashes before the year: "SMPTE 292M– 1998" and "SMPTE 2021–2008" are now read with their year. Before, the year was dropped (or the cite didn't parse), so the undated ref silently resolved to the newest edition.
- Dotted suffixes:
- A dotted suffix after a type is a part ("ST 363.2-2002" →
SMPTE.ST363-2.2002). - RP 210 numbers its revisions with a dot ("RP 210.4-2002" →
SMPTE.RP210.2002). - refMap maps ST 305's revision "305.2M" (and the typo "3052M") to
SMPTE.ST305.<year>. - Leading zeros are dropped ("SMPTE 0352–2010" →
SMPTE.ST352.2010, "RDD 09" →SMPTE.RDD9), except for AG, whose ids are zero-padded (SMPTE.AG02). - Impact: across the MRI, 61 citations change, and 36 of them now resolve to a registry doc (24 before). 13 that resolved only because their year was dropped now keep that year and show as missing.
- SMPTE citations that parsed to the wrong id (#2242, group C). "SMPTE ST 336M:2007" kept the legacy "M" (
SMPTE.ST336M.2007, nowSMPTE.ST336.2007; alsoRP 160M-1991→SMPTE.RP160.1991). Part 0 of a suite ("SMPTE ST 2081-0:2015 — … Roadmap") is its Overview Document, so it is nowSMPTE.OV2081-0.2015whatever type it is cited as (alsoOV2082-0,OV425-0). New refMap entries: "SMPTE RP 27.3-1989" and "27.4-1994" →SMPTE.RP27-3.1989/SMPTE.RP27-4.1994(RP 27 parts; other dotted SMPTE designators are unchanged pending a decision); "SMPTE 299-2009" →SMPTE.ST299-1.2009(ST 299M became ST 299-1); "[SMPTE RP 2242] — SMPTE Labels Register" →SMPTE.RP224(the "2" is a footnote marker). Across the MRI, 10 citations change, all to these ids. - SMPTE citations by printed designation resolve to the released document. Some SMPTE documents are printed with a different year from their library release. RP 86 was released 1990-06-05 but published as "RP 86-1991", so its docId is
SMPTE.RP86.1990while citations saySMPTE.RP86.1991. The 1991 form is also its registered DOI (10.5594/SMPTE.RP86.1991, which resolves to the 1990 release). The resolver now treats a doc's SMPTE DOI as an alias for that doc, after an exact docId match and before any edition matching, so the citation keeps the designation it was written with and links to the document. This resolvesSMPTE.RP86.1991→SMPTE.RP86.1990,SMPTE.RP103.1995→SMPTE.RP103.1994(rp0103-1995PDF) andSMPTE.ST165.1999→SMPTE.ST165.1994(st0165-1999PDF). Of the 116 SMPTE docs whose DOI differs from their docId, no two share a DOI. Where a DOI suffix is itself another doc's docId (SMPTE.ST400.2004), the exact docId wins. - A renamed or removed document no longer lingers in the MRI as a missing reference. The MRI cleanup that drops uncited entries trusted each entry's stored "source present" flag, which is only refreshed after the cleanup runs. After
CMR.MLwas renamed toMOVIELABS.CMR, the uncitedCMR.MLentry survived the rebuild and was counted as a missing reference. The cleanup now checks the registry directly. - SMPTE DOIs are checked at doi.org before they're written. The extractor built the DOI for HTML releases from the year alone (
10.5594/SMPTE.ST2094-50.2026), while SMPTE registered recent ones with the month (10.5594/SMPTE.ST2094-50.2026-08). PDF-only releases already took the month from the docId, so ST 2110-41:2026-06 was right while ST 2094-50:2026-08 and ST 2067-21:2026-07 got a DOI that doesn't resolve. SMPTE's registrations don't follow a date cutoff: 2024–2025 docs are registered year-only, and the month form starts in 2026. Both paths now start from the same default: the month form from 2026 on, the year-only form for 2023–2025. For every doc dated 2023 or later, the extractor then asks doi.org which form is registered, the default first and then the other one. That catches exceptions such as ST 2136-1:2026-02, registered year-only. If neither form can be confirmed, the doc keeps the default form, logs a warning and flags the DOI for review, instead of marking the link "verified". So a new 2026 document whose DOI isn't registered yet still gets the month. Four registry docs carried a DOI that didn't resolve: ST 2067-201:2026-03, EG 428-23:2026-04, ST 2067-21:2026-07 and ST 2094-50:2026-08. The SMPTE re-extract in #2164 corrected them. - A dated reference no longer resolves to a different edition. When the cited edition wasn't in the registry, the MRI pointed it at the newest edition of the same document. "Specification ICC.1:2022" showed as ICC.1:2010, and SMPTE ST 274:1998 as ST 274:2008. A dated reference now resolves only to an edition from the year it cites, and otherwise is listed as a missing reference under its own id. Undated references still resolve to the newest edition. Across the 5,980 refIds in the MRI, 201 that pointed at another edition become missing (137 SMPTE, 40 ISO, 24 other), and no other resolution changes.
Added
hrefAlternatesfor documents published in several languages (schema 2.6.0). One record per document.hrefis the primary link, the English edition when one exists, andhrefAlternateslists the other editions as{ language, href }. The doc page lists each edition's URL in the Link row, followed by "(French edition)"-style labels (English first), lists every edition language in the Language row ("French, English"; English only when an English edition exists), links the original-language title to its edition, and the research skill'srecords.mddocuments the field.translatedBygainscontributorfor third-party translations, at document level and on eachhrefAlternatesentry ("(French edition, contributor translation)"); the search API andsearch.schema.jsonemit it likemsrbot. It is first used for CST-RT-040 and CST-RT-047, whose English editions are official (translatedBy: "publisher").- More CST recommendations, added with
npm run extract-manual. Each has the French original title and abstract plus an English translation flagged for review, and its citations are parsed from CST's own text. - Batch 1, sound/picture sync: CST-RT-009-2000 (television), CST-RT-015-2007 (35 mm release prints), CST-RT-025-2011 (cinema).
- Batch 2, cinema sound and acoustics: CST-RT-003-2009 (advertising and trailer loudness), CST-RT-007-2001 (processor level alignment), CST-RT-013 (auditorium sound level, 2006), CST-RT-014-2001 (background noise in technical facilities), CST-RT-022-2011 (AES channel assignment), CST-RT-041-2021 (auditorium acoustics).
- Batch 3, projection and auditoriums: CST-RT-005-2002 (35 mm projection), CST-RT-012-2003 (spectator comfort), CST-RT-020-2023 (open-air projection), CST-RT-032-2012 and CST-RT-033-2012 (measurement methods for digital projection and sound), CST-RT-034-2012 and CST-RT-035-2012 (digital projection characteristics and auditorium dimensions, both cover-marked drafts), CST-RT-045-2019 (projection equipment maintenance).
- Batch 4, files, archive, accessibility and TV:
- CST-RT-018-2017 (TV advertising safe areas);
- CST-RT-021-2016 (the "mezzanine file"), with its color annex CST-RT-021-Annexe-2016 as a linked record;
- CST-RT-026-2012 (archival master);
- CST-RT-039-2015 (DCP deliverables for accessibility), which appears in the D-Cinema and the Captions, Subtitles and Accessibility portals.
- Batch 5, documents other CST records cite, plus VR:
- CST-RT-031-2012 (surveying auditorium dimensions), cited by RT-035;
- CST-RT-040-2016 ("ready for broadcast" files), cited by RT-018, with CST's official English edition as
hrefand the French one inhrefAlternates; - CST-RT-047-2023 (virtual reality), with English and French editions.
ISDCF.DCNC(update path): the 14 normative references from ISDCF's References page (ISO 639 and 3166, the IANA Language Subtag Registry, BCP 47 as RFC 5646 and RFC 4647, ISDCF Doc 7, SMPTE ST 428-12 and four ST 429 parts, and MovieLabs ratings). Also its French and Japanese contributor translations, with provenance from ISDCF's Translations page.
RT-011, RT-017, RT-030 and RT-043 (with its note) are not added, because they fall outside MSRBot's media scope.
- DCI DCSS citations parse. A new parser family reads "Digital Cinema System Specification Version 1.2", "V1. 0", "version 1.4.2" and similar citations. Across the 51,081 citations in the MRI, 13 more now parse, with none lost or changed.
refMap.jsonone-offs found during the CST extraction:- "norme AES 3" →
AES3; - the French "convention de nommage du cinéma numérique" →
ISDCF.DCNC; - three SMPTE journal papers (The Restoration Business Part 4, Stability of Photographic Film Part VI, Proposal of a System Architecture for Digital Film Archives) → their DOI records;
- the Helt & La Torre SMPTE 2014 conference paper →
10.5594-M001556.
Two non-CST citations of the film-archive paper now link too.
- ISDCF document citations parse. A new parser family maps ISDCF paper URLs (
…/papers/ISDCF-Doc8-…pdf, in the cite text or itshref) and "ISDCF Doc 7" / "ISDCF Document 10" toISDCF.D08-style ids, which resolve to the registry edition; Doc 1 maps toISDCF.DCNC.refMap.jsonadds the P-HFR paper URL and ISDCF's wording for the IANA Language Subtag Registry and MovieLabs Common Metadata Ratings. Across the 51,144 citations in the MRI, 4 more now parse (two SMPTE journal citations each of ISDCF Doc 8 and P-HFR), with none lost or changed. - A bare "BCP 47" citation records both of its RFCs. BCP 47 is RFC 5646 (language tags) plus RFC 4647 (matching), and both are in the registry.
parseRefIdmaps a bare "BCP 47" (or a…/bcp47link) to the set idBCP47.mriRecordSightingandextractDocsthen record each member instead, throughexpandRefIdand a smallREF_EXPANSIONStable inreferencing.js. A citation that names its RFC ("BCP 47 (RFC 5646)") keeps that RFC. No existing MRI citation changes. findSourceDocIdForRefIdexported fromreferencing.js. This read-only lookup is whatmriFlushuses, so previews and checks, such as themsrbot-extractmapping preview, show real resolutions instead of approximations.
Changed
- API versions bumped for the additive fields in this release. Documents schema 2.6.0 (
hrefAlternates,translatedBy: contributor)./api/doc/{docId}.jsonapiVersiongoes1.0.0 → 1.1.0, and/api/documents.jsonand/api/search/go2.0.0 → 2.1.0, because rows can now carrytranslatedBy: "contributor"./api/stats.jsonstays at 1.1.0. The API page used to say "Current API version: 1.0.0"; it now lists each endpoint's version and the schema version. The research skill'smsrbot.pyaccepts any2.xsearch index, so it is unaffected. msrbot-extractskill updated from the CST batches. It now covers:- mapping previews with the real resolver;
- the requester review step;
- full-date padding;
- series citations as
<id>.ALLPARTSwhen an MSI suite exists; - looking up cited papers in the registry before accepting an orphan;
- errata that share a version prefix;
- portal keywords;
- using the update path for later changes;
- scope (media only, "Not extracted (and why)" in the PR) and filling reference gaps recursively, stopping at non-media references;
- one record per language edition set (
hrefAlternates); - unparted and "M" citations never resolve to a part: they go to their own base, and supersession leads on;
- contributors open tooling fixes found during an extraction as a separate PR from the data (maintainers may combine them, as separate commits).
AGENTS.mdanddocs/manual-extraction.mdsay the same.
Fixed
- Re-extracts don't mark an unchanged list of objects as overridden. The update path compared values with plain
JSON.stringify, so key order mattered. Canonicalize sorts keys inside array entries (such ashrefAlternates), and an unchanged value then came outoverridden, with anoriginalValueidentical to the new one. The comparison now sorts keys first. - An undated or unparted reference never rolls onto a different part. When no edition of the cited base existed, the resolver fell back to any
<base>-Nor<base>.Ndocument, so it picked the newest part. For example, "SMPTE 299M" and "SMPTE 299-2009" linked to ST 299-1 or 299-2, ISO 13818 to 13818-1, and RP 27 to RP 27-4. A reference now resolves only to an edition of its own base: a date suffix (.2009) or a DCI version (.v1.4…). ITU-Nrevisions are the exception, because they are editions. 22 refs that had rolled onto a part are now unresolved until their base edition is in the registry. Supersession is unchanged: ST 377M resolves toSMPTE.ST377.2004, whosesupersededByleads to ST 377-1, so the reader can follow it. This reverses the "SMPTE 299-2009 → ST299-1.2009" example from v2.7.0. - "All parts" references link to their suite page on doc pages again. This had been broken site-wide since the MRI v2 doc-page work (#1097, 2026-06-18). Every ALLPARTS reference has an MRI entry, so the template rendered them as plain "EXTERNAL" citations instead of suite links; examples are SMPTE AG 16's ISO 80000, ST 382's ST 379 (MXF Generic Container) and DCI's ISO 11664. The reference tree was unaffected. An ALLPARTS reference with no MSI suite, such as RFC 1494's ISO 10021, now shows "NOT IN REGISTRY" instead of linking to the bare
/suites/index. - DCSS version citations link to the specification, not an errata sheet. The registry files each DCSS errata release under the same version prefix, so ranking sent "DCSS Version 1.2" to "Errata 44-45".
refMap.jsonnow maps each version that has a specification record (1.2, 1.3, 1.4, 1.4.1, 1.4.2, 1.4.5) to it. - DCI
YYYY-MMDDeditions are ranked by date. Undated DCI references picked an arbitrary or older edition. UndatedDCI.DCSSnow resolves to the v1.4.5 specification (it was a 2019 v1.3 errata sheet), andDCI.DCA-HDRto its 2024-02-28 edition (it was 2022). extract-manualupdates keep the record's provenance, and re-extracts don't flag unchanged references. When a manual record updates an existing document, its per-field source hints now apply; a keyword update had come outparsed, high confidence, instead ofinferred. For every provider, orphan citations minted in the run are now counted when comparing references, so re-extracting a document whose citations haven't changed no longer marks them as overridden.- Manual records label
docLabelandhrefasparsed. The pipeline treats those two fields asresolved(computed), which is right for scrapers, but in a manual record they are read from the publisher.docs/manual-extraction.mdand the skill also had it wrong thatpublicationDatecould beYYYYorYYYY-MM. The schema requires a full date, and year-only or month-only dates are padded with the registry's standard note. - Research skill 1.5.4: padded dates aren't stated as exact days.
publicationDateis always a full date, but a source with only a year or month is padded to-01-01or-01, and$meta.notesays so (about 2,500 records). - The skill and
prompt.mdnow state only the known precision. msrbot.py getreportspublicationDatePrecision: year|month.records.md,endpoints.mdandsearch.schema.jsonno longer claimYYYY/YYYY-MMvalues appear.
This was found while adding CST-RT-015, which is dated only by year.
Added
- Manual extraction through the extractor pipeline (
npm run extract-manual). For publishers without an extractor, a person or AI agent reads the publisher's site and PDFs and writes a records file. The newmanualprovider (src/main/scripts/providers/manual.*) feeds those records toextractDocs.js, which handles them exactly like SMPTE and IETF output: - citations go through
parseRefIdwith MRI sightings, and unparsed ones become orphan slugs that keep their citation text; $meta, URL resolution, merging with existing records, the PR log and the MRI flush all run as usual.
Records can set provenance per field: injectMeta now honours per-field source and sourceUrl hints, so a translated title can be inferred while the label is parsed. An explicit refId on a citation is logged as a manual:explicit judgment call.
This is the required path when an AI tool does the extraction. The output is a first pass that a person must verify by hand; for sources that change over time, a real provider with a scheduled run is still the right tool. In Claude Code, the new repo-only project skill .claude/skills/msrbot-extract/ walks through it: reading sources, writing records, the requester review, running, and verification. AGENTS.md, CONTRIBUTING, docs/commands.md, the README and the new docs/manual-extraction.md all document it. New test: manualProvider.test.js.
- The reference parser reads French and European citation styles, plus CST and AFNOR designators. This came out of hand-extracting CST recommendations.
parseRefIdnow normalises spaced thousands and dashes ("ISO 26 428 – 3" →ISO.26428-3, which used to parse asISO.26), ISO/DIS, CD and R drafts, a space before ":year", French spellings (UIT-R → ITU-R, CEI → IEC), "ITU–R : BT 709 - 6", "SMPTE ST2067-40",SMPTE, «RP 177-1993, "ST 2110–10" and "EBU – R95". It also has two new families: - CST, e.g. "CST RT 031 – Projection – 2012" →
CST.RT031.2012, plusCST.RT021annex.2016andCST.NT001; - AFNOR, e.g. "NF S27-100:2014" →
AFNOR.NFS27-100.2014, and "NF EN 61947-2" →AFNOR.NFEN61947-2.
Across all 50,972 citations in the MRI, 91 previously unparsed citations now parse, 189 gain a part or year, and none are lost. refMap.json gains three CST one-offs and is now alphabetised by npm run config-sort. New test: referencing.citeTypography.test.js, with 41 real citations. keying.js also gains a CST rule, so CST documents form MSI lineages (CST|RT|028|), with annexes keyed as supplements (#2085).
- New publisher: CST (Commission Supérieure Technique de l'Image et du Son). The first document is CST-RT-028-2026 Digital Projection – Subtitles – Characteristics, Dimensions and Positioning (
CST.RT028.2026), parsed from CST's PDF and site without an extractor. It is the first doc to use the language fields:language: fr, with the French title and abstract indocTitleOriginal/abstractOriginaland an English translation (translatedBy: msrbot,$meta.source: inferred, review required). Its eight cited references are recorded in the MRI with CST's citation text (mapSource: parsed): - registry docs link normally
- ISO 26428-7 and ISO 8567 are canonical refs with no registry doc yet
- the AFNOR NF S27-100 and ARCOM charter citations are orphan slugs
CST also gets a site logo and publisher link.
- Fields for non-English documents (schema 2.5.0).
docTitleandabstractstay in English. The new optional fields are: language: the BCP 47 tag of the published languagedocTitleOriginalandabstractOriginal: the publisher's own wordingtranslatedBy:publisherfor an official English title, ormsrbotfor our translation
Doc pages show the original title under the English one, with a Translation row in the Metadata card naming what MSRBot.io translated (e.g. "Title and abstract translated from French by MSRBot.io"), plus a Language row and the original-language abstract. The site search index and the API viewer also search docTitleOriginal. In the APIs, /api/documents.json rows gain docTitleOriginal, language and translatedBy, and /api/search/ rows gain titleOriginal, lang and translatedBy (also in search.schema.json); all are emitted only for non-English documents, so English rows are unchanged. The research skill (1.5.3) and prompt.md cite the original title when the English is MSRBot's translation, and msrbot.py find/search/get match and return the new fields. CONTRIBUTING documents these fields and spells out $meta.source semantics: parsed means read from the publisher's document, inferred means derived (translation or constructed value), and manual means set by a person.
Changed
- The skill carries its own version (
msrbot-research1.5.1).SKILL.mdfrontmatter gainsmetadata.version, the Agent Skills spec's place for it. The claude.ai zip contains only the skill folder, notplugin.json, so zip installs previously had no version of ours at all; claude.ai's "V1/V2" labels are its own upload counter. A newnpm testcheck (researchSkill.test.js) fails ifmetadata.versionandresearch/.claude-plugin/plugin.jsondiffer, and also checks the frontmatternameand description length. AGENTS.md andresearch/README.mdsay to bump both.
Fixed
- ISO 26428-2 title, and stray "docLabel" text.
ISO.26428-2.2008carried Part 3's title; it is now "Part 2: Audio characteristics". A case-insensitive replace of "label" with "docLabel" during the per-doc migration (#1108) had also changed 7 more strings: - the titles of ISO 26428-3 and SMPTE ST 298:1997 (e.g. "Universal docLabels for …");
- the
detailstext of ST 291-1, ST 268-1 and ISDCF D04; - the recorded original title of both ST 400 editions.
- ITU references link to the right Recommendation and edition.
R-REC-BT.1680-style ids lost their number as a fake year and resolved to the newest BT doc (21 refs, e.g. BT.1886, BT.1700, BS.2076, linked to BT.2020-2 or BS.1352-4). ITUYYYYMMeditions weren't ranked, so undated BT.601 and BT.709 picked an arbitrary edition (601-4, 709-1); they now pick the newest (601-7, 709-6). A dated cite with no exact id now prefers the edition from that year (X.509 1997 →T-REC-X.509.199706; "SMPTE 299-2009" →SMPTE.ST299-1.2009instead of 299-2:2010).
- Undated
Publisher.NNNNreferences link to the right doc (#2067). The MRI read the document number in refs likeISO.8601as a year and stripped it, leaving the baseISO. As a result, 23 ISO refs (including ISO 3166 and ISO 8601) resolved to the newest ISO doc, ISO/CIE 11664-5:2024, andIEC.1179resolved to IEC 60958. The trailing number is no longer stripped when only a publisher prefix would remain.mriFlushnow also drops a pointer it set itself once its presence check stops confirming it; extractor-set pointers are kept as before. 8 of the refs now resolve to their own registry editions (e.g.ISO.8601→ISO.8601.2004), and 16 becomeknown-publisher-no-doc. A newnpm testcheck,referencing.datedTail.test.js, covers this. msrbot.py searchno longer reads a stale search index (skill 1.5.2). The helper cached the search files for 6 hours, so a copy from before the two-level format change (apiVersion 1 → 2) crashedsearchwithKeyError: 'index'. The small/api/search/files are no longer cached, and an unsupportedapiVersionis refused with a clear error.- Topic searches cover every document type and publisher (skill 1.5.2). Internal claude.ai runs copied the skill's example scope ("SMPTE standards, RPs and EGs, plus ISDCF"). They missed SMPTE RDD 52:2020 for a global DCP distribution question, and ISO 26432-2 for the LFE question. The skill now says to scope by publisher: search all of a publisher's document types (RDDs, overview documents and specifications included; papers only when asked), across every publisher in the domain (D-Cinema: SMPTE, ISO, DCI, ISDCF). New rules:
- read every shard of every in-scope type and never pick shards by number range (a rerun skipped ST 1–402 as "unlikely"); say when journal articles and conference papers weren't searched;
- don't characterize document types, approval processes or how binding a document is from memory (a run claimed RDDs skip due process, which is false);
- don't claim more coverage than the search showed;
- when a fetch tool's completeness answer is unclear, re-fetch asking only for count and last id.
prompt.md mirrors these rules.
Added
msrbot-researchhandles topic questions (skill 1.5.0). A colleague's claude.ai test asked for D-Cinema standards on the LFE/subwoofer frequency range. Every fact in the answer was verified, but the list came from memory and missed SMPTE EG 432-2:2006. The skill now:- searches the
/api/search/shards before recalling; - asks the fetch tool for complete lists and matches them itself;
- fetches each candidate and follows its references one hop;
- answers PARTIAL, naming the gaps and any recalled candidates, unless every in-scope shard was read whole.
Search shards are now the preferred way to find a docId: a number with no row in a shard read whole is a true NOT FOUND, so "SMPTE ST 2067-99" no longer depends on the truncated suites.json. A truncated file that stops near the subject is treated as a lead. scripts/msrbot.py gains search (titles and keywords). prompt.md, references/endpoints.md, the research/README.md checklist and the /ai page are updated, with a new fifth test question (the LFE topic).
/api/search/: a compact search index sized for AI fetch tools (first piece of #2036). There are three levels, and every file is ≤ ~25 KB so chat fetch tools read it whole:/api/search/index.json(~18 KB) lists publishers, docTypes and counts./api/search/{publisher}.jsonlists that publisher's shards, with each shard'sfirst/lastdocId range./api/search/{publisher}/{docType}[-{n}].jsonshards hold{ id, label, title, keywords, status, date }rows, natural-sorted by docId.
Rows are current editions. Superseded editions are excluded when their replacement is in MSRBot, and kept (marked superseded) when it isn't, as for 13 older RFC/ITU/IEEE/ETSI documents, so every document MSRBot holds has a row. It totals 330 shards and 26,084 rows; SMPTE standards span 6 shards. The files are byte-stable (no build timestamps), validated by /api/schemas/search.schema.json (apiVersion 2.0.0; 1.0.0 was a single-level index with ~50 KB shards, live briefly before release), and listed on the API Explorer page and in the Dev Tools menu. Motivation: a claude.ai test asked a topic question about the LFE channel, and the skill couldn't search titles because the full slices are 0.4–5 MB, so it missed SMPTE EG 432-2:2006 even though "LFE" is in its title and keywords. A later run found ~50 KB shards reported as cut off when an agent asked for verbatim matches, hence 25 KB.
Changed
research/…/scripts/msrbot.pycarries the PrZ3 copyright header, like the repo's other scripts. Skill plugin version 1.4.1; the next release'smsrbot-research.zipincludes it.
Fixed
Build MSI + MRI (PR)no longer fails on Dependabot PRs. Dependabot branches live in this repo, so the workflow took the privileged path and tried to mint the PrZ3 Unit app token — butpullrequestevents from Dependabot read the separate Dependabot secret store, whereAPPID/APPPRIVATEKEYdo not exist, so the job died on its first step. Dependabot now takes the same read-only drift-check path as fork PRs. TheSAMEREPOgate is renamedCANWRITE, which is what it actually decides.
Added
- "Use MSRBot with AI" page at msrbot.io/ai/. It explains what the
msrbot-researchskill does and how to get it: - claude.ai / Desktop, with a download button pointing at
releases/latest/download/msrbot-research.zip; - a whole Claude organization (Sync from GitHub);
- Claude Code;
- ChatGPT, Gemini and Copilot.
It also has the copy-paste prompt with a Copy button, and the four test questions. The prompt is read from research/prompt.md at build time, so the site can't drift from the repo. The page is linked from the home page's Explore row and the Dev Tools menu, listed in sitemap.xml, and ai is reserved as a portal slug.
Changed
- Release zip is attached automatically again. Immutable releases are now off for this repo, so the
Attach research skill to releaseworkflow can run onrelease: published. It attachesmsrbot-research.zipseconds after any release is published, with no manual step (this replaces v2.4.1's draft-then-run-workflow flow). It can still be run by hand with a tag, and it skips releases that are immutable (v2.3.0 through v2.4.1). AGENTS.md andresearch/README.mdare updated.
Fixed
- Release zip workflow works with immutable releases. v2.4.0's attach step failed with "Cannot upload assets to an immutable release": the repo locks releases on publish, so a
release: publishedtrigger is always too late. The workflow is now Publish release (manual run with a tag). It attachesmsrbot-research.zipto a draft release and then publishes it. v2.4.0 shipped without the zip and can't be amended; the next release carries it. AGENTS.md release hygiene andresearch/README.mddocument the draft-then-publish steps.
Added
research/: MSRBot research skill for AI assistants.msrbot-researchis an Agent Skill, packaged as a Claude Code plugin. It makes assistants answer media-standards questions only from MSRBot records fetched during the conversation:- it cites each record's URL and
lastModified; - it follows
supersededBy, and cross-checks the family list, to find the current edition; - it names every medium- or low-confidence
$metafield it relies on; - it separates NOT FOUND (a complete check) from COULD NOT VERIFY (a blocked or truncated lookup), so a cut-off search never reads as absence.
It includes a read-only helper using only the Python standard library (scripts/msrbot.py: find / family / get / current / editions / ref). research/prompt.md carries the same rules as a copy-paste prompt for ChatGPT and other tools, and research/README.md is the install guide with a four-question test checklist. The skill was refined over four rounds of claude.ai testing (plugin version 1.4.0). It tracks epic #2032; update it as the lookup, search, lineage and MCP work lands.
- Plugin marketplace at the repo root (
.claude-plugin/marketplace.json). Install with/plugin marketplace add PrZ3r/MSRBot.io, then/plugin install msrbot-research@msrbot. A claude.ai org Owner can also sync the repo under Plugins & skills → Sync from GitHub. - Every release carries the skill. The new
Attach research skill to releaseworkflow (.github/workflows/release-skill-zip.yml) buildsmsrbot-research.zipfrom the release's tagged commit when a release is published, and attaches it.releases/latest/download/msrbot-research.zipis therefore always the newest copy. It can be run by hand for an existing release.
Changed
- README: new Use MSRBot with AI assistants section, covering what the skill does and how to get it for claude.ai, a whole org, Claude Code, and other AI tools.
- AGENTS.md release hygiene: the zip is attached automatically; bump
research/.claude-plugin/plugin.jsonversionif the skill changed; publish releases from a user account (releases created with the workflowGITHUB_TOKENdon't fire the attach step).
Changed
- Contributor path documented —
docs/CONTRIBUTINGSHORT.mdexplains how a fork contributor graduates to a branch: what each stage gets from CI, that GitHub's approval step is asked once per new contributor (firsttime_contributors) rather than per PR, and that the step guards runner time (npm ciruns the PR's own lifecycle scripts) rather than credentials, since fork runs get no secrets. - Fork PRs now run the checks — the preview and MSI/MRI workflows gated their whole job on
head.repo.full_name == github.repository, so a fork PR got no CI at all. Only writing needs privileges, so the gates moved onto the individual write steps: fork PRs now run checkout, canonicalize (sanity),validate,npm testand a full site build, and the MSI/MRI job rebuilds both reports and reports any drift in its job summary. What still can't happen for a fork is the preview deploy, thechore(reports)commit and the PR-body summary — GitHub withholds secrets and write tokens from fork PRs (not something "approve and run" changes on a public repo), and Actions cannot push to a fork's branch. Both workflows now check out the PR's merge commit, which is also what makes a fork's head usable.npm testadded to the preview checks. - Issue + PR templates refreshed for the post-#1266 workflows — PR validation checklist had the wrong script names (
build:msi/build:mri→build-msi/build-mri), and now states that MSI/MRI are rebuilt by CI inside the PR (don't hand-editsrc/main/reports/;git pullafter the bot'schore(reports)commit), plus prompts for the data-change and refMap before/after callouts AGENTS.md asks for. Feature template gained Acceptance / Related sections and a pointer to the current surfaces; bug template gained a Where checklist with the live workflow names. Removed the duplicate.github/PULLREQUESTTEMPLATE.md(it differed frompullrequesttemplate.mdonly in case, so the two couldn't coexist in a checkout on a case-insensitive filesystem).docs/CONTRIBUTING_SHORT.md: local-checks list de-duplicated and split into always-run vs depends-on-what-you-touched, CI-behaviour table replaced the stale "MSI/MRI run on push to main" list, and a Branch or fork? section explains that fork PRs get no secrets, so the preview deploy and MSI/MRI rebuild skip for them.
Fixed
- gh-pages publish no longer lazy-fetches from the remote —
publishGhPages.shclones blob-less, sogit write-treecouldn't find the blobs the carried-over tree references and fetched them from the promisor remote on every deploy;git diff's rename detection did the same. Nowwrite-tree --missing-okanddiff --no-renames, so a publish needs no extra round trip (and works where that fetch is refused — it failed outright when run outside Actions). - gh-pages deploy no longer fails after a successful publish —
publishGhPages.shhashes ~50k files into its throwaway clone, which trips git's auto-gc; the background repack raced thetrap … EXITcleanup, sorm -rfhit "Directory not empty" and its exit code failed an already-finished deploy (seen on the PR #2022 preview: gh-pages published, step still red). Housekeeping is now disabled in the temp clone (gc.auto=0,gc.autoDetach=false,maintenance.auto=false) and cleanup can no longer set the step's exit status.
Added
- SMPTE canonical-repository audit (phase 1) — new backfill project against the two canonical corpora SMPTE delivered under
source/SMPTE/(Journal Article Repository, 24,389 XMLs 1916–2026;Conference Repository, 2,075 XMLs 1969–2025). Corpus mapped across three XML shapes (<publication>IEEE IDAMS pre-2024 ~97%,<contentbatch>IEEE content-delivery 2024–26 wind-down,<article>NLM JATS post-IEEE; 2024 = the SMPTE-leaves-IEEE transition with dual delivery). SMPTE's own published importer (smpte-journal-library, BSD-3) adopted as the required-minimum floor —runSmpteCanonicalImport.mjsdumps 24,173 journal + 1,999 conference articles to canonical JSON. Cross-check: 23,408 registry docs matched by DOI, ~2,753 canonical-only docs to ingest, 654 registry-only docs missing upstream. All project tooling/reports undersrc/main/{scripts/extras,reports}/smpte-canonical-audit/for one-line cleanup. (PR #1238) - Schema 2.4.0 —
authors[].email+authors[].orcid,isbnmedia split (string |{print, electronic}, mirroringissn),conferenceLocation,conferenceDate {start,end},journalTitle(era-accurate journal name at time of publication). Approved via a full-corpus source field census (sourceFieldCensus.mjs: 26,464 XMLs, 1,986 distinct element paths classified); license/sourceIds promotions reviewed and vetoed. (PR #1238) - Canonical field backfills — era-accurate
journalTitleon 21,905 journal docs (Transactions → JSMPE → SMPTE Journal → MIJ, as published); 2,378publicationDatemonth corrections (Jan-1 placeholders → canonical month); 462 abstracts; 240 vocab-mapped keyword fills (56 curated additions tocontrolledKeywords, 294 → 350, with a 35-entry synonym fold table); 2,217 title updates from the 2,294-row drift review (canonical richer: subtitles, full book-review titles; 68 registry-richer rows kept for push-back; IEEE inline<tex>/<sup>/<inf>markup converted to Unicode — restored fractions the original HIGHWIRE import lost, e.g. "4½-Inch Image-Orthicon"); 60 author fixes (45 OCR/prefix name corrections + 15 count-mismatch rebuilds, bio/affiliation preserved); 5 pubYear digit-error fixes with shard re-homing. All writes$meta-stampedsmpte-canonical-repo@v1withoriginalValue. (PR #1238) - XY twin merge + DOI truth table — verified via the doi.org handle API that the DOI registrar is case-insensitive:
10.5594/J18049≡10.5594/j18049, one DOI owned by ONE of the two same-numbered documents; SMPTE re-registered the other under theXYsuffix. The registry had imported the re-registered article twice (HIGHWIRE plain-DOI mispointer + canonical XY): 147/147 pairs identical pages → merged viamergeXyTwins.js(XY survivor keeps richest fields — 45 reference unions, 4 author, 3 abstract, 2 title takes; survivordoi$meta.noterecords the duplicative plain-DOI registration; donors retired). J/j case-siblings (150 pairs, 0 same-year/title — genuinely different documents) never touched.smpte-upstream/doiTruthTable.md: all 147 XY DOIs correctly registered; 107 plain DOIs correctly owned by case-siblings; 39 plain DOIs still point at IEEE Xplore (legacy registrations — registrar update list for SMPTE).smpte-upstream/awsLibraryMissing.md: 654 registry docs with resolving DOIs absent from the canonical library. (PR #1239) sourceopaque-file split —moveOpaqueSourceFiles.jsrelocated 163,806 non-parseable files (PDF/images/zip/office, 165.49 GB) fromsource/to a sibling_archive/, preserving directory structure; parseable XML/JSON/XSD stays in place for extractors.- Phase 3a/3b NLM ref-list extraction —
extractSmpteJournalRefs.js+extractSmpteConferenceRefs.jspulled ~38k journal-article and conference-paper references (canonical refIds + source-anchored slugs) from the HIGHWIRE NLM corpus intoreferences[]+ MRI, with the #1229 content-hash + cite-text pre-check baked into 3b. (PRs #1228, #1230) - Zoho status cross-fill —
status.withdrawnDate+status.statusNotefield defs added to the Zoho cross-fill; statusNote transform drops supersession blurb (already derived fromsupersedes*) and Zoho-internal markers ("THIS IS A DUPLICATE RECORD…"). (PR #1235) - Phase 1a resolver — new
resolveSmpteSourceRefs.v2.jsreplays PR #1111's leftover unresolved-refs bucket against today's corpus + MRI v2 slug system. Filters to 602 entries from Standards-family source docs (Standard / EG / RP / RDD / Specification / Technical Specification / Administrative Guideline); the remaining 359 entries from Journal Article + Conference Paper sources stay in the report for Phase 3a/3b. Resolution chain: vol+pages SMPTE-self-cite (against the ~18k ingested journal corpus) →parseRefId(<standardnum>, <online-cite>)→parseRefId(cite, online-cite)→mapRefByCite→ conservative 1:1 title-match against the registry → MRI v2 slug-mint fall-through. Dry-run summary: 26 canonical resolutions (17 direct registry hits + 9mri-known-no-doc) and 576 slug-mints for refs PR #1111 silently dropped. Under MRI v2, slug-minted refs are NOT silent — they land in the source doc'sreferences[], the MRI entry carries the raw<ref>XML + citation text, and the doc page renders them inline as<cite>citation text</cite>with anEXTERNALbadge instead of dropping them. Slugs can graduate to canonical refIds later viaresolveOrphansonce a new parser family or refMap entry covers them. Tooling commit lands the script + dry-run reports (src/main/reports/smpteSourceRefs.v2.{json,md}); the registry-mutating--applyis handed to the user per the bulk-apply convention.refsReaudit.{json,md}refreshed to reflect the post-apply state: total ref-entries audited goes 8,995 → 9,547 (+552 after content-hash dedup),orphan-slugcount goes 70 → 598 (+528 — the slug-mint recovery), and headline "resolved %" goes 82.71% → 78.16% — note: the % drop is the right direction. The earlier 82.71% was inflated by silently dropping the 602 unresolved refs; under the slug system they're visible as orphan slugs in the denominator, so the rate gets larger faster than the resolved numerator. The corpus is now more honest about what's known vs unknown, even if the headline number looks worse. - Pre-#1171 audit pass — refreshed
mriCoverageGaps.{json,md}(PASS — slug-system invariant holds across the 528 new orphan slugs; everydoc.references[]entry present inMRI.refs[]) andrefsReaudit.unmappedFields.{json,md}. Establishes the baseline registry/refs state ahead of the #1171rawSourceenvelope migration. Validators run clean:npm run validate(all 4 registries pass),npm run validate-mri-coverage(PASS, 9,547 entries audited). - Docs touch-up —
docs/smpte-source-backfill.mdupdated: original "~961 unresolved" markers now reflect the 602 → consumed-by-Phase-1a / 359 → remain-for-Phase-3a/3b split. Outcome block calls out PR #1210 explicitly. - Authors-to-object-form migration + SMPTE HIGHWIRE backfill — closes #1196. Two-pass
migrateAuthorsToObjectForm.js: (1) walk all 21,389 NLM XMLs under_source/SMPTE/HIGHWIRE/to build adocId → [{name, affiliation?, bio?}]index from<contrib-group>+<aff>+<bio>cross-references; (2) walk the registry and migrate every doc whoseauthors[]is still in legacy string form to schema 2.3.0 object form, enriching from the HIGHWIRE index where DOIs match and author counts agree. Idempotent — docs already in object form (e.g. the 10.5594-j18501 v2.1.0 demo) are skipped. Dry-run summary against the current corpus: 26,445 total docs, 15,694 with noauthors[], 1 already-object, 10,750 to migrate — of those, 9,805 enriched from HIGHWIRE (registry name preserved verbatim, NLM<aff>attached asaffiliationand<bio>asbioper author), 945 shape-only (non-SMPTE long tail with no HIGHWIRE source), 1 count-mismatch fallback (HIGHWIRE author-count differs from registry — shape-only for safety). Net data added: 11,331 affiliations + 1,602 bios across the SMPTE journal-article corpus. Tooling commit lands the script + dry-run report (authorsMigration.{json,md}); registry-mutating--applyis handed to the user per the bulk-apply convention. Invoke directly:node src/main/scripts/extras/migrateAuthorsToObjectForm.js [--apply]. - IETF extractor — shape-only authors object form — providers/ietf.parse.js emits
[{name}]instead of["X"]at both author boundaries (RFC docs + archive-XML docs).pickFirstArrayWithSourcemade polymorphic so it preserves objects with.namewhile still string-coercing other fields (keywords). No source-archive mining yet; affiliation/organization extraction from RFC XML<organization>, Datatracker JSON, and HTML meta tracked in #1211 (small focused PR, requires a re-extract of the 52 IETF docs). - Author Affiliation facet disabled in build.search-index.js + docList.js. The #1196 backfill landed 6,936 distinct raw affiliation strings — every spelling/punctuation variant from the source XML survives (
"RCA Manufacturing Co., Camden, N. J."vs"RCA Manufacturing Co., Camden, N.J."vs"RCA Manufacturing Co."are 3 buckets for 1 institution). Even ~500 raw strings would be unusable as a picker. Per-docaffiliationsarray still emitted on each idx row so full-text search keeps matching affiliation strings and?f.affiliations=<exact string>URL filters still work. Affiliation normalization / fuzzy clustering (collapse ~7k raw → ~1–2k canonical institutions, then re-enable the picker) tracked in #1214.
Changed
articleType→contentType(field + vocabulary) — registry contentType values conform to SMPTE's ContentTypeEnum (10 value renames across 8,823 docs:research-article→orig-research,abstract→summary-abstract,obituary→obit,book-review→review,calendar→future-events,announcement→content-announce,correction/addendum→errata,letter→opinion,reprint→orig-research) PLUS nine retained MSRBot-finer values (meeting-report, news, other, editorial, discussion, review-article, introduction, oration, article-commentary). The field itself renamedarticleType→contentTypeacross 24,209 docs and 10 code surfaces (schema, templates, search index, page gatenoPageContentTypes,contentTypeLabels, buildStats, extractors, client JS) with a?f.articleType=→?f.contentType=URL back-compat alias. The vestigial 1-doccontentTypefree-string field was dropped first. (PR #1238)- contentType distribution cleanup (small → big) — every tier reviewed by title against the canonical labels: 1,504
summary-abstractconference/journal papers were full papers mislabeled by the HIGHWIRE delivery →orig-research(with 28 title-review exceptions to discussion/oration/tutorial/introduction); theinfo-societycatch-all split 3,581 department pages onto the finer enum (toc1,259,front-cover1,020,list-staff1,028,advert274) + 710 review-approved reclasses (545 hidden research papers);newsconsolidated 1,137 moves incl. the New-Products split-brain;orig-researchinverse-scan moved 376 departmental docs out (committee reports → meeting-report 252, Forewords → introduction, first use ofawards); officer-message rule (all "Message from the …" → info-society, 53 caught in orig-research/toc);otheremptied. Full decision trail insmpte-canonical-audit/reports. - gh-pages kept as a single commit; faster deploys (#1265)
- Why: gh-pages had about 1,419 commits of build output (most of the ~6.5 GB repo). Pages only serves the latest commit, and every version can be rebuilt from
main. - New
src/main/scripts/ci/publishGhPages.sh(modessite,preview <n>,remove-previews <n…>), used by all four gh-pages writers:build-msr-site,pr-build-preview,pr-preview-cleanupandpr-preview-sweeper. - It publishes gh-pages as one commit with no parent, so every deploy replaces the history.
- Nothing is checked out: it clones only gh-pages' directory structure and builds the new tree in git's index, so only changed files are uploaded.
- It replaces the clone →
rm -rf→cp -r→git add -Asequence, the 152k-file checkout, and thepull --rebaseretry loops. - Safe with concurrent deploys, no lock: it pushes with
--force-with-leaseagainst the commit it cloned. If another deploy landed first, it rebuilds on the new tip and retries, so no deploy can overwrite another's. (A sharedconcurrencygroup would drop all but one queued run.) - Production checkout ~8 min faster:
build-msr-site.ymlchecked out withfetch-depth: 0, pulling every branch's full history including gh-pages (479 s per run). The build only needsHEAD's SHA, so it now uses a depth-1 checkout. - Rolling back: re-run "Build MSRBot.io Site and Test" (
workflow_dispatch) on an earliermaincommit, or revert onmain. There is no gh-pages history to revert. - The first deploy after merge drops the old gh-pages history. GitHub reclaims the space on its own garbage-collection schedule; ask GitHub Support to run GC if the reported repo size doesn't fall.
- MSI/MRI rebuilt inside the PR; issue sync moved to after merge (#1266, parts B–D)
- Why: every data merge used to set off a chain: a site build (using stale MSI/MRI), then MSI (which polled up to 60 min for that build), then MRI (via
workflow_run), then bot report PRs, then a second site build. Skip-marker artifacts andonlyMetaguards existed only to stop that loop. - New
build-reports-pr.yml: runs on PRs touching data/input/config/src/main/lib/or the MSI/MRI scripts, including the bot's extract PRs. - It first merges the latest
maininto the branch, so reports are always "main + this PR". Conflicts only in report files resolve tomain's copy and are then regenerated; any other conflict stops for a human (src/main/scripts/ci/mergeMainForReports.sh). - It then rebuilds MSI and MRI (full replay) and commits any report change back to the PR branch as
chore(reports): rebuild MSI/MRI. Contributors need to pull before pushing again. - It keeps an auto-updated MSI / MRI section in the PR body (
src/main/scripts/ci/reportsPrSummary.js), rewritten in place between hidden markers: lineage and UNKEYED changes, MRI ref/resolved counts, newly resolved and newly missing refs, and the changed files. - After any data/report merge to
main, the newrefresh-data-prs.ymlmergesmaininto every other open data PR (nothing is rebuilt onmainitself). That re-triggers the rebuild there, so overlapping PRs (e.g. the SMPTE and IETF extracts) never ship stale counts and never conflict on the reports' summary lines. - The PR preview waits for the rebuild (
pr-build-preview.ymlgets areports-gatejob). If the rebuild pushes a report commit, that commit's preview is the one that builds, so a preview never shows stale reports. Site/template-only PRs proceed after a ~90 s check. - One merge now ships data and reports together, and the site builds once.
- A manual run on
mainopens a report PR instead. - New
sync-report-issues.yml: runs on pushes tomainthat changemasterSuiteIndex.json/mripresenceaudit.json, weekly (Wed 05:30 UTC), and on demand. It syncsUNKEYEDandMISSING REFissues using scripts moved out of the inline workflow code intosrc/main/scripts/ci/(with tests). - Fixes in the missing-ref sync:
- Issue bodies are compared without the per-run footer. Before this, every run "updated" up to 75 unchanged issues, used up its budget, and never created new issues. That's why only 295 of 974 missing refs had issues.
- Resolved issues are closed before new ones are created.
- Duplicate open issues (same title) are closed, keeping the oldest.
- Expect the first runs to close about 70 resolved issues and 48 duplicates, then file about 680 new
MISSING REFissues at up to 75 per run until the tracker matches the audit. - MRI build ~18× faster (275 s → 15 s locally for the full 26.8k-doc replay).
mriRecordSightingrecounted every MRI ref (Object.keys(mri.refs).length, 46k keys) on each of the 55k sightings, which was about 90% of the run.mriFlushalready computes the stats once. The output is identical. - No more timestamps in the reports.
generatedAtis gone frommasterSuiteIndex.json,mri/index.jsonandmripresenceaudit.json, and MSI'ssourceHash(a hash of the whole registry) is gone too. Git history records when they changed. Rebuilding an unchanged registry is now byte-identical, so there's nothing to "ignore", and concurrent data PRs no longer all rewrite the same header lines.suites.jsondrops the two pass-through fields; nothing read them. - The completed one-shot
migrateMriToShards.jshas been removed now that its migration is applied. - UNKEYED issues now actually sync after merges. The old step was gated on
github.ref == main, which apull_request: closedtrigger never satisfies, so it only ran on manual dispatch. - Removed:
build-master-suite-index.ymlandbuild-master-reference-index.yml(the chain, skip markers and thechore/build-mastersuite/chore/build-masterreferencebot PRs).validate-urls.ymlis unchanged; itsworkflow_runtrigger on the removed MRI workflow simply no longer fires, and its schedule and throttle are unaffected. - MRI stored as one file per ref (#1266, part A)
- Why:
src/main/reports/masterReferenceIndex.jsonhad reached 99.97 MB, about 4.9 MB under GitHub's 100 MB per-file push limit. The upcoming IETF extraction would have pushed it over. - New layout: the MRI now lives in a directory store at
src/main/reports/mri/: index.jsonholds everything except the refs.- Each ref is one pretty-printed file:
refs/{prefix}/{refId}.jsonfor canonical refs, andrefs/orphan/{sourceDoc}/{refXmlId}.jsonfor orphan slugs. - 46,349 files, the largest about 0.1 MB.
- Store library: every reader and writer now goes through
src/main/lib/mriStore.js(loadMri/writeMri). loadMrireturns the same object shape as before.writeMrirewrites only shards whose content changed, and bumpsgeneratedAtonly when something changed. An unchanged MRI therefore produces no diff, and a changed one shows exactly which refs changed.- Directory names are lower-cased, and case-only siblings (SMPTE
J/j) get a~hashsuffix, so checkouts are safe on case-insensitive filesystems. - Presence audit:
mripresenceaudit.jsonnow lists only non-orphan missing refs, the rows the missing-ref issue workflow andseedBackfill.ietf.jsuse. Orphans are counted (orphanCount,orphansListed: false), not listed. Size drops from 41.5 MB to 1.0 MB. - Migration: one-shot
src/main/scripts/extras/migrateMriToShards.js(dry-run by default,--apply) verifies the shards are byte-identical to the monolith before deleting it. - Workflows: the MRI and extract workflows now commit the
mri/directory. - Tests: new
mriStore.test.js;referencing.flush.test.jsported to the store. - Deterministic build output (smaller gh-pages deploys) — per-item pages and files no longer embed build time or the git SHA, so a rebuild of unchanged data is byte-identical and the deploy's
git add -Acommits only files whose content changed (previously every one of ~50k files was rewritten on every deploy). Doc pages' footer now shows Record updated (latestupdatedin any$metablock of the record, falling back topublicationDate) instead of Site version / Generated on; reftree, suite, collection and portal pages show neither. Site-level pages (home, docs/groups/projects lists, suites/reftree indexes, API, changelog, about) keep the site version stamp./api/doc/{docId}.jsondrops the per-buildgeneratedAtin favour of a content-derivedlastModified(the/api/documents.jsonindex keepsgeneratedAt).sitemap.xmllastmodis the doc's content date (core pages: newest doc date) instead of build time. - Build speedups — per-doc
/docs/{docId}/pages are rendered once (documents pass) instead of in all three documents-registry passes;referencedByis computed from a single reverse index instead of an O(N²) scan; reference-tree attach uses a key lookup instead ofObject.keys(...).includes. - Extras + reports hygiene — 15 completed one-shot backfill/migration scripts and 24 obsolete report artifacts removed (registry
$metaprovenance stamps survive independently);docs/smpte-source-backfill.mdtracking doc deleted per its own checklist; ongoing tooling kept:validateMriCoverage.js,resolveOrphans.js,auditRegistryFields.js. Backfill-driven canonical-audit scripts and reports segregated under project subfolders; durable SMPTE handoff reports undersrc/main/reports/smpte-upstream/. (PR #1235, #1239)
Fixed
- Case-sensitive DOI joins — SMPTE reuses DOI numbers across eras distinguished only by case (
10.5594/J180271917 vs10.5594/j180272011 are different documents; ~148 registry pairs). Audit matching had lowercased DOIs, cross-matching 4 of 7,371 applied contentType writes — repaired, all audit joins now exact-case. pubYear drift collapsed 114 → 5 on rematch (the survivors were genuine digit errors, all canonical-confirmed and fixed). (PR #1238) - Per-doc page emission vs case-colliding docIds — on a case-insensitive filesystem (macOS local dev)
build/docs/10.5594-J18049/and…/j18049/are the same directory, so the page-gate cleanuprmfor a gated doc could delete its ungated case-sibling's freshly built page. Gate removals now run as a first pass before any writes (order-independent), a gated doc only removes a page whose canonical URL matches its exact-case docId, and the 148 collision groups are logged once per build. Production (Linux → gh-pages, case-sensitive) was never affected. (PR #1239) - Orphan-slug re-anchoring after the XY merge — the merge united donor references into XY survivors but left source-anchored orphan slugs (
orphan/<donorId>/…) pointing at retired docIds; the build lost lineage keys for 477 refs and a subsequent MRI prune deleted their entries.fixMergedOrphanSlugs.jsrenamed all 477 slugs to survivor anchors across 45 docs and restored the pruned MRI entries (full citation text/raw XML) from the committed MRI viagit show— 477/477 recovered, 0 collisions. (PR #1239) J18005volume/issue — carriedvolume: 120, number: 1leaked from its 2011 MIJ case-sibling; corrected to Transactions vol 2, issue 6 per the DOI registration and its own docLabel. (PR #1239)- smpte-journal-library
PubDate.monthgetter returnsyear(copy-paste bug in model.mjs) — worked around locally by readingmonthin our serializer; upstream PR candidate for the SMPTE repo. Also surfaced for upstream:info-authoroutside ContentTypeEnum, a duplicate Front Cover in the 2023 MIJ issue 7, a missingissuenumin a 2018 doc, canonical OCR errors ("are projector"), and the M001489 canonical mislabel. mriFlushno longer clobbers extractor-setresolvedDocIdpointers on every build. The "resolution truth" branch in referencing.js used to treatfindSourceDocIdForRefId(e.refId)(refId-as-its-own-docId lookup) as the sole authority — when an extractor mapped a non-docId refId to a real registry doc (the N-to-1 slug→docId case the slug architecture was designed for), flush demoted the entry back toresolvedDocId: null, needsResolve: "known-publisher-no-doc"every time. Surfaced in PR #1201's auto-generated MRI data diff:IETF.draft-ietf-tls-rfc8446bis-03 → RFC8446(mapped by the IETFietf-rfc-html-fallback/rfc-textresolution) reverted to unresolved, and the resolved/known-pub-no-doc stats moved 1814→1813 / 932→933 — same shape would have hit every parser-family resolution Phase 1b lands. Fix: theelsebranch now preserves an existingresolvedDocIdthat still points at a registered doc (viahasDocIdOrBase), and only demotes when the existing pointer has gone stale or was never set. Sibling logic in the secondary flush path patched the same way. New regression test (referencing.flush.test.js) pins both branches;npm testruns it after the existing registry smoke tests.
Removed
Added
- MRI v2 — slug-keyed citation system: every cited reference is now addressable, renderable, and auditable. Closes #902 (
[FEATURE] Slug info for missing refs— the[NOT IN REGISTRY]bare-slug render is replaced by inline<cite>refId — citation text</cite>+EXTERNALbadge for every MRI-known ref). A foundational shift in how unresolved references are modelled, surfaced, and progressively resolved over time. Before: refs the parser couldn't shape into a canonical refId fell off intobadRefs.latest.json(a stale-by-day-2 sidecar) orMRI.orphans.unmapped[](a flat list with no slug identity, unable to be cited fromdoc.references[]). After: every ref the build sees lands inMRI.refs[]as a first-class entry — either as a canonical-form slug (ASME.B1.1.1989,RFC1642) when the parser recognised a publisher family, or as a source-anchored slug (orphan/<sourceDoc>/<refXmlId>for raw-XML refs,orphan/<sourceDoc>/h:<contentHash>for cite-only refs) when it didn't. Doc files cite slugs as strings inreferences[]; future resolution work touches only MRI'sresolvedDocIdpointer, not the doc files (re-extracts may converge a slug to its canonical refId, but the MRI entry retains the per-sighting audit trail). Authoritative architecture reference: docs/mri-citation-system.md — covers how adoc.references[]string actually resolves, the slug ↔ docId relationship, the resolution lifecycle, and worked lookup recipes. Concretely: - Every
MRI.refs[]entry now carriesresolvedDocId(the registry doc this ref points at, ornull),needsResolve(null/"known-publisher-no-doc"/"unknown-publisher"), andcontentHash— a 16-hex SHA-256 of the normalised raw<ref>XML that groups sightings of the same citation across multiple source docs, so one resolution decision propagates to every sighting that shares the hash. - New
synthesizeCiteFromRawRefhelper parses authors/article-title/pub-title/standardnum/volume/pages/year out of raw<ref>XML (both APTARA<refauthorgrp>/<refauthor>/<init>/<refsurname>and NLM<name>/<surname>/<given-names>shapes) and composes a human-readable citation string. Wired into the mint path so new orphans auto-populatecitationTextwhen extractors passrawRefbut no explicit cite. Backfilled 1,283 existing MRI entries that had raw XML but nocitationText— including books like Ousterhout's Tcl and the Tk Toolkit_ (2nd ed., Addison-Wesley, 2009) that previously rendered as the bare slug. docId.hbsrenders MRI-known refs inline as<cite>refId — citation text</cite>with anEXTERNALbadge, instead of the bareNOT IN REGISTRYpill. The HandlebarsgetStatushelper returns"MRI-KNOWN"and the newmriCitehelper produces the markup, with optional<a>wrap when MRI carries anhref. Applies to the normative + bibliographic refs list and the supersededBy / amendedBy lists.- Client-side cite rendering on the reference tree. New
build/api/mri-cite-map.jsonsidecar (~150 KB, ~982 entries) ships alongside the docs API.refTree.jsfetches it at init alongsidedocuments.json/suites.json, and for tree nodes whose target isn't in the registry but is in MRI renders the citation text inline plus anEXTERNALbadge. Root-view of an MRI-only ref disables the Set as new root button since there's no doc to drill into. - The 69 source-anchored orphan slugs are now cited from their source docs'
references[](27 docs touched). PR #1111's source-ref extractor had written them into MRI's legacyorphans.unmapped[]flat list but never written a citing string into the source doc — the slug migration created MRI slug keys but the doc files were still unaware. Now backfilled: e.g.SMPTE.RP2073-2.2014's normative refs are 3 canonical + 3 orphan slugs = the full six refs its source XML carries, including the previously-invisible Ousterhout book. - Build console is silent now. The two warning classes the slug system makes redundant (1,298
No lineage key derivable+ 120-294[WARN:getStatus] docId "X" not found in registry, the latter scaling with the number of citing docs) are both fully suppressed when MRI has the citation info, or — for refs in neither registry nor MRI — silenced entirely on the grounds that the MRI presence audit + thebuild-master-reference-index.ymlauto-issue workflow (#937-style "MISSING REF: RFC1642" issues) already capture the same population. The[Refs] X: missing-lineage refs (unique) = Nper-doc summary line counts genuinely-unknown refs only. - Forward-compatible resolution. When you later run the IETF extractor and
RFC1642lands as a registry doc, MRI's entry just getsresolvedDocId: "RFC1642"set andneedsResolvecleared — no doc file edits. The renderer'sfollowMriResolutionhelper chases the pointer ingetStatus/refHref/getLabelso the doc page link goes straight to the resolved doc. When a new parser family graduates acontentHashgroup of orphan slugs, every sibling sighting upgrades in one pass. TheMRI.refs where resolvedDocId === nullquery is the live backlog — no more stale-by-day-2 reports. extractDocs.jswrites orphan slugs from extract end-to-end.onBadRefsnow routes throughmriRecordSighting(which returns the minted slug) and queues each slug for application to the source doc'sreferences[]right before save. So a fresh ingestion of, e.g., RFC1101 lands with["…", "orphan/RFC1101/h:e25f0fbf"]inreferences.bibliographic[], MRI gets the matching entry, and the post-extract prune leaves it alone because the doc cites it. The legacysrc/main/reports/badRefs.latest.jsonsidecar is removed.resolveOrphans.js(new extras script) — idempotent MRI-only retry pass. Walks every entry withresolvedDocId === null, runs each throughregistry-direct/parseRefId/mapRefByCite, and graduates anything that now hits.contentHash-sibling propagation means one resolution updates every entry in a hash group in the same pass. No doc files touched. Run as often as you want — each newly-ingested target doc or newly-added parser family / refMap entry gives this pass more to graduate.- Auto-issue workflow filtered.
build-master-reference-index.ymlnow filtersaudit.missing[]toneedsResolve === 'known-publisher-no-doc'before creating "MISSING REF: X" issues. Source-anchored orphan slugs stay queryable through the audit and MRI itself but no longer flood the issue tracker. The audit emitsknownPubNoDocCount+orphanCountsummary fields alongsidemissingCountso dashboards can read intent without re-scanning. - New
src/main/scripts/extras/migrateMriToSlugSchema.jshandles the in-place schema lift on existing MRI (idempotent);referencing.js's_ensureRefandmriRecordSightingmint the slug schema going forward;documents.validate.jscross-checksgit ls-filesagainstfs.readdirSyncper doc to catch macOS case-only drift locally before CI. - SMPTE NLM journal-article + conference-paper backfill — ~19,587 NLM-extracted SMPTE journal articles (1916–2015 Transactions / SMPE / SMPTE Journal / MIJ era) and ~1,503 conference papers loaded into the per-doc registry from
_source/SMPTE/HIGHWIRE/. Newsrc/main/scripts/extras/extractSmpteJournalArticles.jshandles the NLM XML; corpus selection via--corpus journal|conference|both. Closes #1172. - APTARA journal-issue cross-fill (~22.5k docs) —
extractSmpteJournalIssues.jsenriches existing journal-article docs with structured metadata (ISSN, copyright, publisherLocation, articleType, abbrevTitle) fromsource/SMPTE/APTARA/...journalmetadataXMLs. Limit-aware chunking counts actual writes, not iteration position, so resumable runs converge. articleTypepage gate —site.json#noPageArticleTypeslistsarticleTypevalues that don't get rendered site pages. Initial set:obituary,other,news,calendar,announcement,correction,addendum,reprint. Gated docs are skipped at the per-doc page, reference-tree, and sitemap emit loops, and dropped fromsearch-index.json/facets.jsonso they don't appear in the browse list either. Stale pages from earlier builds are removed on rebuild. Gated docs remain fully present in the API and registry — the gate only suppresses generated pages, not data. Newsrc/main/lib/pageGate.jsdrives the decision; matching is case-insensitive but exact-value.articleTypeshown on doc pages — surfaced indocId.hbsdirectly below Doc Type when present.- Journal Article breakdown in
/api/stats.json— newdocuments.journalArticlesblock:{ total, articleTypes, byArticleType }(sorted descending by count). StatsapiVersionbumps1.0.0 → 1.1.0. - SMPTE DOI re-registration ask list —
src/main/reports/smpteDoiReRegistrationAskList.{md,csv}enumerates the 96 SMPTE docs where the registered DOI form drifted from the registry-canonical form (malformed double-prefix, year-mismatch, registrar typo, amendment-notation drift), grouped by pattern, withshould-be (canonical)target per row — direct hand-off format for SMPTE DOI re-reg. - Keyword vocab scrub — new
src/main/scripts/extras/scrubKeywordVocab.jsaudits every doc'skeywords[]againstsite.json#controlledKeywords(canonical / case-drift / out-of-vocab / synonym buckets) and applies AUTO_FIX renames + DROP cleanup in--applymode. Report atsrc/main/reports/keywordVocabScrub.md. - Refs re-audit + schema 2.3.0 prep. Two new extras walkers (
reaudit-refs,reaudit-unmapped-fields) re-classify every ref-entry across the now-complete corpus and tally source-XML element paths against schema 2.2.0 + the existingsourceInventory.smpte.schemaMap.mddecisions, so we can scope the upcoming ref-resolution/extraction passes without guessing. Headline numbers from the refs walker: 8,995 ref-entries across 1,066 docs at 82.71% resolved (66.60% direct, 16.11% via MRI); 1,485 aremri-known-no-doc(canonical refIds whose targets aren't ingested — top families: IEC 127, ITU-R 108, ISO 107, ITU-T 61, ATSC 42, ANSI 39, intra-SMPTE 32) and 70 are source-anchored orphan slugs. Zerounparseable— every string inreferences[]already routes throughparseRefIdor the slug system. Source-XML walker confirms Phase 3a/3b's universe (~13,418component/reflist/refsightings in 1.5k sampled APTARA + Allen Press files; ~2,865 NLM-shapearticle/back/ref-list/refin 1.5k HIGHWIRE Source Bak; full corpus scales to roughly hundreds of thousands of citation sightings, much of which dedupes through the MRI content-hash). Reports atsrc/main/reports/refsReaudit.{json,md}andsrc/main/reports/refsReaudit.unmappedFields.{json,md}. - Schema 2.3.0 —
authors[]items now accept an object form{ name, bio?, affiliation? }in addition to the legacy string form, so NLM journal-article author metadata (1,939biossightings + 1,774organizationsightings surfaced byreauditUnmappedFields.js) can land as first-class data on Phase 3a extraction.$idbumps2.2.0 → 2.3.0; the legacy string form remains valid for every existing doc. Worked sample landed at 10.5594-j18501.json — Study on the Acceptance of Higher-Frame-Rate Stereoscopic 3D in Digital Cinema (Ruppel, Alff, Göllner) — all three authors populated withname+bio+affiliationparsed from_source/SMPTE/APTARA/.../MIJR15Vol124No1.xml. Validator passes (0 errors across 26,445 docs). Issue #1196 tracks the full migration sweep + extractor updates (IETF, NLM/HIGHWIRE, APTARA, Allen Press, Zoho cross-fill). - MRI coverage validator — new
npm run validate-mri-coverage(validateMriCoverage.js) asserts the slug-system invariant: every string in any doc'sreferences.{normative,bibliographic,supersededBy,amendedBy}[]must exist as a key inMRI.refs[]. Exit 0 on clean, exit 1 with a structured per-leak report (docId, category, ref string, leak-kind classification) when broken, exit 2 on script-level errors. Runs clean on current corpus — 8,995 ref-entries across 1,066 docs all present in MRI's 2,816 entries (3.2× dedup via content-hash collapse). Locks in the invariant against future extractor regressions so a render-time "NOT IN MRI" never reaches the UI — the build should fail loudly first. Reports persist tosrc/main/reports/mriCoverageGaps.{json,md}even on success for the audit trail. CI wiring is the next pass; the script is meant to drop intobuild-master-reference-index.ymlafter the MRI flush. - Schema 2.2.0 + 2.3.0 fields rendered on doc pages, indexed for search, and added as browse facets — closes #1097. Eleven new rows on
docId.hbs:volume/number/pages/chapter(basic citation block — somehow never rendered before despite being in the schema since v1),approvalDate,abbrevTitle,copyright({holder, year}as© year holder),publisherLocation({city, country}inline next to publisher),issn(handles both legacy string + 2.2.0{print, electronic}object — renders asPrint: 1545-0279 | Electronic: 2160-2492),icsCodes([{code, description}]— each code as a clickable link with the ISO description shown inline after), and a rewrite of theauthors[]loop to handle the 2.3.0 object form — one author per line;nameleft, optionalaffiliationright-aligned as a muted text link to the affiliation facet; optional<details>bio expander matching the existing "Undated variant" pattern; subtleborder-topdivider between authors so multi-author blocks with bios don't crowd. Legacy string-formauthors[](the entire 26,444-doc corpus pre-#1196 migration) keeps rendering as before via fall-through. Doc list card previews also gain three new touches:articleTypechip in the badge row right afterdocType;Affiliation:row between badge row and publication date;ICS:row above keywords with the ISO description as a hover tooltip. Search index (build.search-index.js) now indexesdoiAliases[](so ISBN-form / legacy DOIs resolve to canonical doc),abbrevTitle,authors[].affiliation, andauthors[].bio(full-text). Three new browse facets on the doc list —articleType(research-article6,147 docs,orig-research683, etc.),icsCodes.code(top:33.160.01Audio/video/audiovisual systems @ 813 docs across 19 codes), andauthors[].affiliation(sparse today — populates as #1196 migration + Phase 3a extraction land). Label maps emitted intofacets.jsonso picker + applied-filter pills both render friendly forms:articleTypeLabelsdriven by site.json's 22-entry map (research-article → Research Article,orig-research → Original Research,info-society → Society Information, etc., covering both gated and ungated values), andicsCodeLabelscollected during build (first non-empty description per ISO code wins — ISO codes are canonical, so any doc's copy serves). The facet picker for ICS shows<code>{{code}}</code> {{description}}so the meaning is readable at a glance, not just on hover. Pure-internal fields (standardId,productNumber,familyId,journalAcronym,releaseTag,contentType,docElement,depositDate,resolvedHref,workInfo,xmlNamespace, status process internals likestage/state/latestVersion/versionless/publicCd/withdrawnNotice) remain API-only by design — the doc-page metadata block is for citation + browse context, not committee/process plumbing.
Changed
/api/documents.jsonis now a lightweight index (apiVersionbumps1.0.0 → 2.0.0) — the full-bundle shape grew past GitHub's 100 MB per-file limit on thegh-pagesbranch as the SMPTE journal-article backfill landed. The endpoint now emits one row per doc —{ docId, publisher, docType, docLabel, docTitle, articleType?, path }— each linking to/api/doc/{docId}.jsonfor the full record with$metaprovenance. Drops the file from ~120 MB to ~7 MB and stays small as the corpus grows. Per-doc shards are unchanged and remain the canonical full-data endpoint. Closes #1173.- SMPTE journal-article DOI case is preserved end-to-end —
10.5594/J(uppercase J — pre-1955 Transactions era) and10.5594/j(lowercase j — 2010+ Motion Imaging Journal series) are now treated as distinct DOI namespaces pointing to different articles.doiToDocIdinparseSourceName.jsno longer force-uppercases the leading letter;documents.validate.js+lib/registry.jssort case-sensitively soj/Joccupy distinct positions. The earlierfixLowercaseSmpteDocIds.jscleanup that conflated lowercase-j with case drift is deleted; its 308 wrongly-deleted MIJ articles are restored. Closes #1188. - J/JXY same-article DOI twins cross-merged (130 docs) — Transactions-era articles registered under both bare
J(NLM-extracted) andJXY(APTARA-extracted) forms now share the union of metadata (NLM-rich abstract/authors/precise date + APTARA-structured ISSN/copyright/publisherLocation). 159 phantom bareJfiles that case-collide with their lowercasejsibling were deleted — their DOIs case-insensitively resolved to the 2010+ MIJ article, never to the 1917 Transactions content their files claimed. - 30 MIJ ISSN corrections — 2012-era lowercase-j docs that APTARA tagged with the predecessor SMPTE Journal print ISSN (
0036-1682) are corrected to the MIJ ISSN pair (1545-0279/2160-2492).$meta.originalValuepreserves the APTARA-source form. keyword.normalize.jscleanups —normalizeKeyword/splitAndNormalizeKeywordsaccept an optionalextraAcronymsMap for per-corpus overrides. IETF XMLDSig spec-element names (AgreementMethod,DigestMethod, etc.) moved out of the globalACRONYMMAPinto an IETF-only map inproviders/ietf.parse.js. Thesignturemethodtypo is corrected on import while$meta.originalValuepreserves the RFC source.crossfillSmpteFromZoho.jsswitched to the central normalizer (drops its duplicated localKEYWORDSYNONYMS).controlledKeywordsonce-over — pluralised singular drift (Subtitle → Subtitles,Interface → Interfaces,Network → Networks,Type → Types), collapsed acronym/long-form pairs (Look-up Table → LUT,Common LUT Format → CLF,Microservice → Microservices,Internet of Things → IoT,USB Type-C → USB-C), promoted ~31 in-use external terms (AFD, DPX, MIB, LUT, CLF, RTP, TTML, IMSC, TIFF, etc.) into the vocab, removed theSigntureMethodtypo +Eneryptionduplicate, fixed Title-casedOf/Inconjunctions in multi-word terms, and dropped redundant noise (SMPTE,Society of Motion Picture and Television Engineers,Languages,Status,SNMP).- Zoho standards cross-fill expanded —
crossfillSmpteFromZoho.jsaddskeywords(Keywords + Topics columns, deduped through the central normalizer) and a no-op short-circuit so identical-set merges don't churn$metatimestamps. validate-urls.ymlcadence dropped from weekly to biweekly — cron now fires on the 1st and 15th of each month (was every Saturday). The duplicate-run throttle onworkflow_run-triggered executions widens from 24 hours to 14 days, matching the cron cadence, so the upstreamBuild MasterReference Indextrigger can no longer fire a redundant full URL sweep between scheduled runs. Driven by the corpus passing ~26k docs (each sweep now takes hours, publishers throttle the request rate, and standards URLs rarely drift inside a 14-day window). Throttle label renameddaily-throttle→biweekly-throttlein the skip-reason output for clarity.
Fixed
validate-urls.ymlApp-token mid-job expiry — the App token minted viaactions/create-github-app-token@v3at job start is hard-capped at a 1-hour TTL by GitHub. TheRun URL validationstep routinely takes 90+ minutes against the now-26k-doc corpus, so by the time theAuto-commit audit back to mainstep ran the original token was dead andgit pushexited 128 withfatal: could not read Username for 'https://github.com'(see run #27777168738). Fix: re-mint the App token (steps.app-token-refresh) right after the audit read and before any push, then explicitly inject the fresh token into theoriginremote URL viagit remote set-urlsogit pushuses it instead of the expired credential stored in.git/configbyactions/checkout. All three downstream auth points (auto-commit push, base-sync fetch,Create PR for normalized URLsvia peter-evans) switched tosteps.app-token-refresh.outputs.token. Initial checkout still uses the originalsteps.app-tokensince it fires within seconds of mint.- CI
Validate registriespost-merge case-mismatch — 683 SMPTE journal-article files were tracked by git with lowercase-j paths but carried uppercase-JdocIdfields (macOS case-insensitive FS hid the drift locally); renamed to match content. Closes #1189.
Removed
src/main/scripts/extras/fixLowercaseSmpteDocIds.js— actively harmful; conflated SMPTE's intentional lowercase-j DOI namespace with case drift and unlinked 308 real MIJ articles as "colliders" against pre-1955 Transactions counterparts.
Major release — the document registry model is inverted. The source of truth moves from the monolithic src/main/data/documents.json to one JSON file per document under src/main/data/docs/, sharded by {publisher}/{docType}/ (with a {year}/ level for title-identified docTypes). The monolith, per-publisher/docType slices, and the per-docId API all become build artifacts. See #1108.
Added
- Per-doc document registry — the registry source of truth is now one JSON file per document under
src/main/data/docs/{publisher}/{docType}/{docId}.json; title-identified docTypes (site.json#titleLabelDocTypes) add a{year}/level. Removes the single-file scale ceiling (GitHub's 50/100 MB limits, whole-file rewrites, unreviewable diffs) ahead of the journal-article backfill. - New
src/main/lib/registry.js— central registry access:loadAllDocs,loadDoc,saveDoc,slug,docIdSlug,docPath. - New
npm run new-doc— scaffolds a new per-doc file from the template straight into its correct shard path. - New
npm run assemble(build.assemble-registry.js) — emits per-publisher and per-publisher/docType registry slices underbuild/. - New one-time
src/main/scripts/migrate.explode-documents.js— explodes the legacydocuments.jsoninto the per-doc tree (dry-run by default;--apply). Removed after the v2.0.0 migration; preserved in the commit history if ever needed again. - New
npm test(src/main/scripts/test/registry.test.js) — self-contained smoke tests pinning theslug/docIdSlug/docPathinvariants insrc/main/lib/registry.js(including theunknown/undatedbuckets and the year third-shard).
Changed
documents.json, the per-publisher/docType slices, and the per-docId API are now build artifacts assembled from the per-doc registry — never hand-edited.npm run canonicalizeruns per-file: key-sorts each doc, injects$meta, re-homes any file not at the shard path its own fields derive, and prunes emptied directories.npm run validateruns per-file and adds a path-consistency check — a file must sit where itspublisher/docType/docId/publicationDatefields derive. Each per-doc file is validated directly against the item schema, so Ajv error paths read/docIdetc. rather than/0/docId.extractDocswrites only the docs a run touched, each to its own shard file, viasaveDoc()(which re-homes on publisher/docType/docId changes).npm run buildnow emitsbuild/api/stats.jsonitself — the formerbuild-statsworkflow step is folded in.build-msi/build-mri--inis now optional, defaulting to the per-doc registry; ~13 registry read sites swapped toloadAllDocs().- Extract, URL-validate, and index-build workflows updated to operate on
src/main/data/docs/.
Removed
npm run docs-sortandnpm run docs-fix(andsrc/main/scripts/utils/docIdSort.js) — array order is now derived from filenames;canonicalizeowns ordering and placement.- The separate "Generate API stats" workflow step — folded into
npm run build.
Added
- Added
npm run seed-backfill-ietfhelper (src/main/scripts/utils/seedBackfill.ietf.js) to compare MRI presence-audit missing RFC refs againstsrc/main/input/seedUrls.ietf.json, with: - dry-run reporting (default)
--writemode to append missing RFC seeds and canonicalize/dedupe the full seed list.
Changed
- IETF RFC HTML reference extraction now includes a modern xml2rfc-HTML path:
- Detects modern RFC pages via
xml2rfcgenerator metadata and/orapplication/rfc+xmlalternate links. - Parses structured
dl.referencesentries usingdt/ddboundaries for normative/informative sections. - Falls back to legacy section/anchor heuristics only when structured extraction is unavailable or incomplete.
- Expanded keyword normalization acronym map in
src/main/scripts/utils/keyword.normalize.js: - added additional crypto/protocol acronyms (for example
XMSS,WOTS,W-OTS,WOTS+,W-OTS+) - reformatted acronym definitions to sorted one-per-line entries for readability and safer diffs.
- Reference normalization now includes generic DOI/ISBN fallback parsing in
parseRefId, reducing manualrefMapbackfills for citations that include canonical identifiers. - Reference normalization now includes generic 3GPP Technical Specification parsing from cite text (including Draft TS forms), with month-aware suffixes (e.g.,
3GPP.33.501.202107). badRefs.latest.jsonwriting now merges per provider into a single snapshot file:- each bad-ref item includes
provider - each extract run replaces only the current provider's items and preserves other providers' entries
- Updated
npm run seed-backfill-ietf(src/main/scripts/utils/seedBackfill.ietf.js) to also backfill missingIETF.draft-*refs from MRI presence-audit (in addition to RFC refs), including draft filename-extension normalization (.txt/.xml/.html/.pdf).
Fixed
- URL validation throttle false positives on skip-only runs — refined
.github/workflows/validate-urls.ymldaily throttle to count only runs that actually executedRun URL validationsuccessfully; skip-only successful runs (for example, upstream open-PR marker skips) no longer satisfy throttle. - IETF filter prefix overmatch in seed-first extraction — fixed
src/main/scripts/providers/ietf.discovery.jsURL filtering so short RFC filters (for example,.../rfc861) no longer overmatch longer IDs (for example,.../rfc8615,.../rfc8820): - filter comparisons now use normalized URL forms
- prefix matching now requires explicit intent via trailing
/in the filter entry. - W3C dated TR stage references not resolving — expanded W3C URL parsing in
parseRefIdto resolve dated/TR/YYYY/<STAGE>-<shortname>-<date>forms beyond REC (for exampleWD-CSP3-20160913,CR-referrer-policy-20170126). - MRI add-then-prune churn across extract/build-MRI workflows — extraction now prunes MRI variants to current
documents.jsonreference truth before flush, preventing transient rawVariants (for example self-cites or non-persisted sightings) from being added by extract and then removed by laterbuildMasterReferenceIndexruns. - Resolved citations leaking into
badRefs.latest.json— tightened bad-ref suppression in bothextractRefs(src/main/lib/referencing.js) and extract report persistence (src/main/scripts/extractDocs.js) so any citation that resolves viaparseRefIdormapRefByCiteis excluded from bad-ref output, eliminating stale false positives during mixed parser-path runs. - NIST SP reference normalization gap — added generic NIST SP parsing in
parseRefIdfor CSRC.../publications/detail/sp/.../rev-...URLs and text forms likeNIST 800-67, Rev. 2, producing canonical IDs such asNIST.SP.800-67r2. - Legacy RFC appendix/procedure spillover into unparseable refs — tightened IETF HTML fallback boundaries and numbered-item badRef gating to avoid treating appendix example steps (for example CoAP WebSocket procedure lines) as bibliographic references.
- IETF draft token misclassification/normalization issues — improved draft extraction to:
- strip filename extensions from draft IDs (
.txt/.xml/.html/.pdf) - reject generic filename false positives (for example
...preliminary-draft-4.pdf) - prefer
href-derived draft IDs over cite-derived variants when both are present - choose the longest valid draft token to avoid truncated wrapped-text matches.
- IETF legacy heading detection gaps — broadened fallback heading recognition for
Normative References/Informative Referencesin<span class="h2">and<span class="h3">variants. - Reference mapping coverage gaps — expanded
src/main/input/refMap.jsonwith additional DOI/IANA/IAB/OMA/GitHub/arXiv and legacy citation variants resolved during IETF backfill passes.
Added
- Added
npm run local-servershortcut to start a local HTTP server for previewing the built site. - Added shared keyword normalization utility at
src/main/scripts/utils/keyword.normalize.jsto centralize acronym/special-case keyword casing rules used during ingestion and keyword sync. - Added persistent bad-reference reporting snapshot at
src/main/reports/badRefs.latest.jsonfrom extraction runs, so unresolved refs can be backfilled outside PR log text. - Added
npm run review-refshelper (src/main/scripts/utils/review.refs.js) to manage reference review state: npm run review-refs -- listto enumerate flagged docs.- Expanded
npm run review-refs -- listreporting to be provider/publisher agnostic and reference-type agnostic: - covers all docs/providers
- reports both
normativeandbibliographicreview flags - correlates with
badRefs.latestand reports unflagged docs with bad refs npm run review-refs -- resolve <DOCID...>to clear review flags after manual verification.- Updated
npm run review-refs -- resolve <DOCID...>to clear review flags for bothreferences.normative$metaandreferences.bibliographic$meta. - Added extraction parser diagnostics flagging for mixed reference layouts (
MIXEDREFLAYOUT_RISK) and propagated this as structured review metadata instead of bad-ref noise.
Changed
- Refactored
src/main/scripts/providers/ietf.parse.jsto use shared keyword normalization (splitAndNormalizeKeywords) instead of inline acronym/title-case logic. - Refactored
src/main/scripts/utils/keywords.sync.jsto use shared keyword normalization (normalizeKeyword) instead of inline acronym/title-case logic. - Extended keyword acronym normalization to preserve
SMTPuppercase consistently across parser/sync flows. - Extraction workflows (
extract-docs-ietf.yml,extract-docs-smpte.yml) now append unknown-keyword warnings fromnpm run validate -- --warnoutput into PR notes, so warn-only keyword drift is visible before merge. - Extraction workflows now track
src/main/reports/badRefs.latest.jsonin extract PRs (and no longer depend on per-run bad-ref log artifacts). - Removed old
statsAPI veiwer template. - Mixed-layout reference risk now lands in
references.bibliographic$metawith: reviewRequired: trueflag: "MIXEDREFLAYOUT_RISK ..."
and downgrades confidence to medium for that field until reviewed.
- Updated docs schema to permit new
$metakeys:reviewRequiredandflag.
Fixed
- gh-pages push contention (#910) — replaced
peaceiris/actions-gh-pageswith manual git deploy in PR Build Preview and main site build workflows; added push-with-retry (pull --rebase, up to 3 attempts) to all four workflows that push togh-pages(site build, PR preview, PR cleanup, PR sweeper). The site build's two-step cleanup-then-publish is now a single atomic commit. - URL validation over-triggering — added a daily throttle for workflow-chain URL validation so
Validate Document URLsskips workflow-run invocations if a successful URL validation already completed within the previous 24 hours. - IETF references canonicalization noise on new extracts — fixed new-document extraction/merge so empty
references.normative/references.bibliographicarrays are not persisted; IETF parser now emits sparsereferenceskeys (only when non-empty), preventing canonicalization from injecting manualreferences.normative$metafor parser-empty placeholders. - Docs index search in PR previews — fixed docs search asset loading in
src/site/js/docList.jsto usewindow.msrAssetPrefixwith relative fallbacks instead of root-absolute/docs/...paths, so searches return results on preview URLs under subpaths (for example,/pr/<num>/docs/) while continuing to work locally. - IETF reference boundary/parsing regressions in legacy RFC HTML — tightened fallback section detection and stop conditions to reduce non-reference soak-through while still capturing appendix-based reference content:
- Added strict old-page bibliography boundary support for
<hr class='noprint'/> <!--NewPage--> <pre class='newpage'> ... Bibliography ... BIBLIOGRAPHY .... - Added appendix heading support for
Appendix <X>: Recommended readingas bibliographic reference bounds. - Updated prose fallback stop logic so
Appendixheadings do not prematurely terminate parsing when the active bound is a recommended-reading reference section. - Backfilled cite→refId normalization rules in
src/main/input/refMap.jsonfor unresolved legacy citations (notably RFC732/RFC733/RFC2130 reference blocks, including ARPANET NIC, ANSI X3.51, and Jerman-Blazic bibliography entries).
Added
- API Explorer page at
/api/— searchable, filterable document browser with URL parameter syncing, pagination, and an inline JSON viewer for inspecting full provenance records. - Full-provenance JSON API — static endpoints for machine consumption:
/api/documents.json— full registry with all source fields and provenance metadata./api/doc/{docId}.json— per-document JSON with full record./api/stats.json— registry statistics and metadata (withmeta.repoUrl,meta.changelogUrl).- JSON Schema publishing at
/api/schemas/— existing schemas (documents,groups,portals,projects) are now served as static assets for consumer validation. - API versioning — all API JSON responses include
$schemaandapiVersionfields; initial API version is1.0.0. - Machine-readable discovery — added
<link rel="alternate" type="application/json">and<link rel="describedby" type="application/schema+json">to the API Explorer page and all document detail pages. - OpenSearch JSON template —
opensearch.xmlnow includes a JSON response URL (/api/?q={searchTerms}) alongside the existing HTML template. - JSON-LD SearchAction — structured data now includes search actions for both
/docs/and/api/endpoints. - Source Data (JSON) panel on document detail pages — collapsible card showing the full registry record with a direct link to the per-document API endpoint.
- Internal Changelog page at
/changelog/— rendered fromCHANGELOG.mdas styled cards, replacing external GitHub blob links. - Added API Explorer and schema links to the Dev Tools & Resources popover and site footer.
- Added API link on the homepage.
Changed
- Renamed "Dev Tools" navigation label to "Dev Tools & Resources."
- Updated README badges and Key Artifacts to reference the new API Explorer and internal changelog.
- Updated sitemap to include
/api/and/changelog/entries.
Fixed
- Fixed suites/collections page document rendering when publisher labels differ by composite forms (for example,
ISO/IECdocs underISOcollections); collection matching now normalizes publisher aliases/composites before filtering. - Fixed JSON-LD
SearchActiontarget URLs missing path separator aftercanonicalBase.
Added
- Providerized extraction architecture:
- Added SMPTE discovery provider module at
src/main/scripts/providers/smpte.discovery.js. - Added SMPTE parser provider module at
src/main/scripts/providers/smpte.parse.js. - Added IETF discovery provider module at
src/main/scripts/providers/ietf.discovery.js. - Added IETF parser provider module at
src/main/scripts/providers/ietf.parse.js. - Added provider-specific metadata configs:
src/main/scripts/providers/smpte.meta.jssrc/main/scripts/providers/ietf.meta.js- Added provider registry at
src/main/scripts/providers/index.js. - Added optional document schema fields for citation structure:
volume,number,pages,chapter,edition.- Added explicit npm alias
extract:smptefor provider-targeted extraction. - Added dedicated IETF extraction workflow:
.github/workflows/extract-docs-ietf.yml(separate branch/PR path from SMPTE extraction). - Added keyword governance utilities and config source:
- Added
controlledKeywordslist insrc/main/config/site.json. - Added
keywords-syncutility atsrc/main/scripts/utils/keywords.sync.js(npm run keywords-sync, dry-run by default,--writeto apply). - Added centralized command/flags documentation at
docs/commands.md. - Added and expanded
AGENTS.mdguidance for branch naming, issue/PR label usage, PR hygiene, validation expectations, repo guardrails, and changelog/documentation/provenance expectations.
Changed
- Refactored
extractDocs.jsto be provider-agnostic orchestration (merge, metadata, MRI, and logging), with provider-specific discovery/parsing moved out of main script. - Extraction provider selection is now explicit via
--provider; implicit/default provider execution was removed. - Renamed SMPTE extraction workflow to
extract-docs-smpte.yml(Extract Documents - SMPTE) and aligned workflow references/triggers accordingly. - Updated docs and badges to reference the renamed SMPTE extraction workflow.
- Updated validation architecture for keywords:
- Removed hard keyword enum enforcement from
documents.schema.json. - Moved keyword conformance checks to
documents.validate.jsagainstsrc/main/config/site.json#controlledKeywords. - Added keyword validation mode controls for
npm run validate: - default strict mode (
--error) - optional warn mode (
--warn) for unknown keyword drift checks. - Extraction workflows now run keyword validation in warn mode; build/local validation remains strict by default.
- Expanded IETF extraction behavior:
- RFC extraction now uses RFC Index XML (
rfc-index.xml) as first-pass canonical metadata for seeded RFCs, with per-document sources used as enrichment/fallback. - RFC field source precedence is now explicit; status relations (
obsoletes/obsoleted-by/updates/updated-by) are sourced from RFC Index XML + RFC info<dl>merge, eliminating loose relation text fallback. - RFC author precedence now prefers Datatracker
doc.jsonauthors (richer names) over RFC Index XML, with HTML/info fallbacks. - Added RFC Index XML/XSD mapping contract and required-field coverage warnings in IETF parser for schema-backed extraction hygiene.
- RFC relation fields now derive from RFC info page relation
<dl>parsing (no broad relation text fallback injection). - Non-RFC extraction now enriches from archive XML (
/archive/id/*.xml) for front-matter fields and keywords. - Non-RFC keywords are normalized to project keyword style (Title Case with preserved acronyms/common forms such as
JSON,URN,B-Chain,DCinema,DCP*,SHA-1). - RFC reference parsing now uses RFC HTML section-aware extraction with strict
NormativevsInformative/Bibliographicbucketing and overlap guards. - RFC fallback reference slicing is now bounded to reference sections, next section heading, and page-break markers to avoid body/header/footer soak-through.
- IETF reference sightings now write to MRI for both RFC HTML and non-RFC XML paths using final document IDs.
- Expanded shared reference normalization rules in
src/main/lib/referencing.js: - RFC IDs normalize leading zeros (e.g.,
RFC0821→RFC821). - W3C
REC-*URL forms normalize to canonical W3C shortname IDs (noREC-prefix in docId). - Added href-first resolvers for Unicode and Mozilla Bugzilla references.
- Added improved ISO hyphenated designator parsing (e.g.,
ISO-8859-1:1987). - Updated project docs with provider extraction and keyword-governance guidance in
README.mdandCONTRIBUTING.md. - Updated docs to link
docs/commands.mdfromREADME.mdandCONTRIBUTING.md. - Enhanced Portal document listings with additional context fields:
- Display of
docTypeandpublicationDatein document tables. - New Doc Type filter, aligned with existing Publisher filtering.
- Extended Portal sorting controls to support:
- Sorting by Type and Published date.
- Ascending / descending sort direction for all supported sort keys, consistent with Suites and Collections.
- Updated RefTree unresolved-document UX:
- Unresolved nodes remain visible and navigable in-tree, but now display muted/italic labels with a
NOT IN REGISTRYbadge. - In the Current Tree Root card, unresolved docs no longer click through to
/docs/:docId/; in-registry roots remain clickable. - Improved docs page reference-list readability:
- Added explicit spacing between normative/bibliographic reference labels and their status tokens (e.g.,
[Active],[SUITE]). - Updated
docs/CONTRIBUTING_SHORT.mdto align branch prefix guidance and add an explicit Unreleased changelog checklist item for workflow/policy/behavior changes. - Simplified PR preview check behavior by removing custom check-run/status publication from preview workflow and relying on the single native workflow job check context.
- Added MSI→MRI chain guard in MRI workflow to skip MRI when MSI already opened a PR (artifact marker present), preventing duplicate chained data PRs.
- Hardened MRI missing-ref issue upsert behavior with no-op update skipping and per-run mutation budget (
MAX_MUTATIONS), reducing secondary GitHub rate-limit failures. - Stopped MSI/MRI metadata-only auto-commits to default branch; report timestamp/date-only churn is now ignored unless content-change PR criteria are met.
- Refined home page information architecture and responsive layout:
- Reduced card density, improved section hierarchy, and rebalanced content columns.
- Updated portal home rendering to a scalable list layout for growth.
- Refined footer layout/content hierarchy:
- Improved responsive alignment/spacing, constrained divider width to container, and added explicit developer/issue links.
- Standardized branding presentation with PrZ3/MSR marks and config-driven copyright year.
- Updated workflow trigger path:
- Site build (
Build MSRBot.io Site and Test) now runs onpushtomain. - URL validation now triggers from MRI completion (plus schedule/manual), not from site build completion.
- PR gate remains
PR Build Preview (MSRBot.io site)onpull_request. - Added focused documents-registry helper scripts:
npm run docs-sortto sortsrc/main/data/documents.jsonbydocId.npm run docs-validateas explicit docs validation alias.npm run docs-fixto run sort + validation in one step for manual doc edits.- Updated
docIdSortbehavior for low-noise editing: - Removed legacy
.baksidecar creation. - Preserved per-entry object formatting and reordered entries only.
- Aligned sort comparator with validator ordering (
toUpperCase()lexical) to prevent sort/validate mismatch loops.
Fixed
- Fixed OM remap path in extraction by correcting title variable scope usage, enabling OM ID remapping updates to apply correctly.
- Fixed README weekly schedule Markdown table separator to render correctly with all columns.
- Fixed doc citation “Copy (undated)” behavior on doc pages so undated snippet blocks copy correctly (no blank clipboard payload).
- Fixed undated citation snippet
<cite id>generation to strip only terminal date suffixes for undated variants, while leaving dated variants unchanged.
Primary changes delivered via https://github.com/PrZ3r/MSRBot.io/pull/695
Added
- Automated extraction of
Scopein HTML documents to map toabstract. - Introduced Portals: curated, first-class landing pages that aggregate documents across suites, collections, publishers, and document types.
- First (3) portals:
/dcinema/,/imf/,/accessibility/ - Added a complete Portal build and schema pipeline, supporting:
- Keyword-based document matching.
- Explicit pinning and post-resolution filtering.
- Shared narrative/overview sections.
- Curated resource collections.
- Delivered a Suites-aligned Portal UX, including:
- Searchable document tables with abstracts.
- Expandable previews (shared behavior with Suites).
- Visual muting of withdrawn and superseded documents.
- Structured, card-based overview and resource sections.
Portal Behavior & UX Details
- Portals render as dedicated pages with stable URLs (e.g.
/dcinema/). - Portal document listings support:
- Default sorting by
docLabel. - Search, publisher filtering, and sortable columns.
- Abstract previews with More/Less expansion.
- Portal overview sections support shared explanatory content using the same card patterns as Suites.
- Resource sections support:
- Grouping by category.
- Independent collapsible sections.
- Per-resource description expansion for long content.
- Portal navigation dynamically adapts based on available content (Overview / Docs / Resources).
Changed
- Backfilled (auto and manually)
abstractfields for DC and IMFcollections
Fixed
- Fixed rendering of
abstractparagraph breaks insuites.
Primary changes delivered via https://github.com/PrZ3r/MSRBot.io/pull/678
Added
- Introduced first-class Suites and Collections as distinct core concepts:
- Suites represent true multipart standards (shared lineage number).
- Collections represent related documents without formal parts.
- Suites and collections share UX but retain distinct semantics.
- Added full Suites / Collections build pipeline, emitting:
build/suites/_data/suites.json(mixed, with explicitkind: suite | collection).- Dedicated pages at
/suites/:slug/for both suites and collections. - Index page supporting mixed display with filtering by kind.
- Implemented docSuiteTitle extraction and propagation:
- HTML: derived directly from
pubSuiteTitle. - PDF: parsed as text before first em-dash.
- Integrated across search index, citations, RefTree roots, suite cards, and doc detail pages.
- Enabled full ALLPARTS resolution:
- Supports ISO and SMPTE ALLPARTS identifiers.
- Doc detail pages resolve ALLPARTS to suite pages with correct labels and status.
- RefTree displays suites as non-clickable parents that expand to child documents.
- Added guardrails and explicit metadata to prevent future regressions:
- Explicit
kind: suite | collection. - Flags for
SUITETITLEMISMATCH. - Hard exclusions for unsupported publishers and document types.
Changed
- Finalized and locked build order to ensure correctness and stability:
- Documents → MSI → Suites/Collections → Pages.
- Updated suite and collection rendering:
- Suites show all documents, including withdrawn (visually muted).
- Collections hide parts column and sort by label.
- Abstract previews and expand/collapse behavior added.
- Refined RefTree behavior:
- RefTrees may display suites but never re-center on them.
- Suite labels replace ALLPARTS identifiers where applicable.
- Normalized publisher handling for edge cases (e.g., ANSI/ASA) so suite and collection lookups resolve correctly.
Fixed
- Fixed ALLPARTS resolution failures where document type previously blocked linking.
- Corrected publisher logo and link resolution on suite and collection pages.
- Resolved reference edge cases for W3C documents.
- Eliminated legacy suite/collection duplication and silent clobbering in the build process.