Fix #35 (registries): migrate the framework registries off the swapped ids - #111
Merged
Merged
Conversation
`parseControlTable()` tried `looksLikeId()`, which rejects every identifier containing a space — `Req 6.2.4`, `Art. 9`, `§5.3` — then fell through to "column 0 is the id". On 611 rows that wrote the requirement prose into `control_id` and the identifier into `control_name`, so an id could not be followed back to its framework and the control-level join resolved by luck. scripts/control-ids.js holds a per-framework identifier grammar for the ten frameworks affected. A row is scanned for its framework's own id shape, wherever that sits: the id becomes `control_id`, the rest of that cell the name, and the requirement moves to `notes`. When the table has no name column the name comes from the framework registry's title for that id rather than repeating the id. Canonical forms follow the ruling on #35 — §5.3, Art. 9, Req 6.2.4, NHI-5, bare ISO clauses, SP 800-218A keeping NIST's -PS — with CIS identified by safeguard and the control carried as `parent` (A), and the SP 800-53 rows inside the 800-82 files kept and labelled `id_source` so they are not read as 800-82 sections (B). Two parser bugs surfaced on the way: - a section can hold more than one table, and the second table's header row was parsed as data. Splitting on the |---| separators drops those 10 junk rows and gives each table its own headers, which is what lets the SP 800-53 rows keep their titles. - two-column tables lost the requirement text entirely once the id stopped being mis-filed; it now becomes `notes`. Prose-shaped ids across the corpus: 611 -> 0. Mappings 3,781 -> 3,771, the difference being those 10 header rows. 969 rows changed id; no row was removed, and no severity, relationship or confidence changed. checkControlIdShapes() in validate.js fails the build if any of the 1,337 rows in those frameworks stops carrying a well-formed identifier. The registries were seeded from these broken rows and still hold prose ids; migrating them changes headline control counts and the webapp, so it follows separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d ids
extract-registry.js seeded data/frameworks/ from the mapping rows while the
parser had the columns the wrong way round, so 573 registry items across ten
frameworks carried a requirement sentence as their `control_id` and the real
identifier in `title`:
{ "control_id": "Hijacked agent with excess privilege causes larger blast
radius", "title": "NHI-5 Over-Privileged NHI" }
Nothing is invented here. Each item is re-read with the same grammar the
parser now uses, so the identifier comes out of the title, the remainder
becomes the title, and the description is kept. Items that already had a
proper id are left alone and win on the title when a converted item collapses
onto them. 14 CIS safeguards cited by mappings but missing from the registry
are added, titled from the row. Registry-side ids also allow the CIS parent
controls (CIS-16) that no mapping row cites.
SOC 2 209 -> 46 EU AI Act 145 -> 30
OWASP NHI Top 10 104 -> 10 ISO/IEC 42001 87 -> 56
CWE/CVE 76 -> 61 PCI DSS v4.0 72 -> 49
CIS Controls v8.1 70 -> 68 NIST SP 800-82 59 -> 26
OWASP AI Testing 58 -> 9 NIST SP 800-218A 52 -> 12
Registry controls 1,541 -> 991; registry items 1,685 -> 1,120. The count falls
because the duplicates were artefacts: one prose item per mapping row, where
the framework has one control. Dangling references from mappings into the
registries: 153 -> 1 (a FedRAMP row, unrelated to #35).
validate.js now checks registry ids as well as mapping ids, and the prose-id
baseline in exports.test.mjs is emptied rather than lowered, so no framework
is exempt any more.
Found on the way, not fixed here: 345 U+FFFD replacement characters remain in
15 registry files. The encoding guard added by T-ACC06 covers Markdown only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # scripts/control-ids.js # scripts/validate.js
emmanuelgjr
added a commit
that referenced
this pull request
Sep 18, 2026
…m the published standards (#115) * Fix #112: recover 328 replacement characters in the registries, and guard data files Fourteen registry files carried 328 U+FFFD replacement characters, mostly where an em dash belongs. They ship: the registries go out in the npm package, in docs/frameworks-registry.js and in the OSCAL/STIX exports. Every character is recovered from the source text rather than assumed. The registries were extracted from the mapping rows, so for each corrupted string the text either side of the character is looked up in the Markdown and the entry JSON, and the character that sits between them is read off. A character is taken only when every match agrees; the window narrows from 24 to 8 characters when a description concatenates several rows, and keeps the raw slice when only one word follows, since trimming to a word boundary could otherwise empty it. All 328 resolved, every one to U+2014. None was guessed, and no other character was found. The guard from T-ACC06 only ever read Markdown, which is why this sat unnoticed. It now also reads data/frameworks/*.json, data/entries/*.json and data/*.json, checking for U+FFFD alone — the arrow heuristic is tuned for prose and diagrams, not JSON. Negative-tested: a replacement character injected into soc2.json fails validation, naming the file and line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Title the 19 registry entries that carried only their identifier, from the published standards After #110 and #111, 35 mapping rows still named themselves after their id — "Req 11.3 | Req 11.3" — because neither the mapping table nor the registry held a title for those 19 ids. The text now comes from the standards themselves, transcribed rather than written. PCI DSS, 17 requirements: the headings as printed in PCI SSC's own "PCI DSS v4.0 SAQ D for Merchants", extracted with pdftotext from the published PDF so the wording is the document's, not a paraphrase. Req 11.3 reads "External and internal vulnerabilities are regularly identified, prioritized, and addressed." SOC 2, PI1.3 and P7.1: TSP section 100, 2017 Trust Services Criteria (with Revised Points of Focus — 2022). The AICPA download needs a session, so the wording was taken from two independent copies of that document and compared; both agree character for character. Only entries whose title was empty or equal to their own id were filled, so no curated title is overwritten. Rows with no published title available were left alone — there are none left. Mapping rows whose control_name is merely their control_id: 35 -> 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #110 — base is
fix/swapped-control-ids, so review that one first. Together they close #35.Why
extract-registry.jsseededdata/frameworks/from the mapping rows while the parser had the columns the wrong way round. 573 registry items across ten frameworks ended up with a requirement sentence as their identifier:{ "control_id": "Hijacked agent with excess privilege causes larger blast radius", "title": "NHI-5 Over-Privileged NHI" }These ids ship in the npm package,
docs/frameworks-registry.jsand the OSCAL catalogs.What this does
Each item is re-read with the same grammar the parser now uses — the identifier comes out of the title, the remainder becomes the title, the description is kept. Nothing is invented:
CIS-16) that no mapping row cites, since the registry legitimately holds both levels;B.2) is recognised alongside Annex A;Registry controls 1,541 → 991. Registry items 1,685 → 1,120.
The count falls because the removed entries were artefacts, not content: the seeding produced one registry item per mapping row, where the framework has one control. The OWASP AI Testing Guide has nine categories, not 58; the NHI Top 10 has ten entries, not 104. The requirement text those items carried is not lost — it lives in each mapping row's
notesafter #110.Dangling references from mappings into registries: 153 → 1 (one FedRAMP row, unrelated to #35).
Guards
checkControlIdShapes()now validates registry ids as well as mapping ids.exports.test.mjsis emptied, not lowered, so no framework is exempt any more and any sentence-shaped id in an OSCAL export fails the build.Verification
node scripts/validate.js: 0 errors, 87 warnings, 326 passed.npm run test:scripts: 85 passed, 0 failed, twice.npm run stats:checkcurrent; generator reruns clean.For your attention
data/stats.jsondrops from 1,541 to 991 registry controls, and the webapp renders that from the data layer, so it updates by itself. But the About timeline still contains the historical line "Registry items typed by kind — 1,372 controls" from v4.0. It is a dated record of what shipped then, so I have not touched it (C2). Tell me if you want a new timeline row noting the correction.Req 11.3), because neither the table nor the registry has a title for those ids. Filling them needs the published requirement text, not a guess.🤖 Generated with Claude Code