From 4f509e9e5e80e471c0ee3664e5af553bdaae0e3d Mon Sep 17 00:00:00 2001 From: emmanuelgjr Date: Fri, 2 Oct 2026 21:32:59 -0400 Subject: [PATCH] Webapp: count only mapped frameworks in the home-page headline The home page added every registry to the framework list, mapped or not, and summed every registry's controls into "Registry Controls". A registry that lands before its rows (NIST AI 600-1 in #187) would have shown 27 frameworks against the README's 26 mapped, and added 211 unmapped controls (+19%). Frameworks now come from the mapping rows alone, and the controls total only counts registries a row cites. No change to today's numbers: all 26 registries are mapped. New webapp test pins the hero to stats.json's frameworks.mapped. Co-Authored-By: Claude Opus 5.5 --- docs/index.html | 9 ++++++--- scripts/webapp.test.mjs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/index.html b/docs/index.html index 2a9bee8..cd15cf7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1319,8 +1319,9 @@

AI Security Standards Crosswalk

DATA.forEach(function(e) { e.mappings.forEach(function(m) { FW_SET.add(m.framework); }); }); - // Also include registry-only frameworks (e.g. CoSAI, EU AI Act CoP) - FW_REGISTRY.forEach(function(fw) { FW_SET.add(fw.name); }); + // A framework counts once a mapping row cites it. A registry nothing maps yet + // (transcribed ahead of its rows) stays out of every count and selector, so + // the headline matches the README's "frameworks mapped". FRAMEWORKS = Array.from(FW_SET).sort(); var ENTRY_MAP = {}; @@ -1807,7 +1808,9 @@

AI Security Standards Crosswalk

// Stats - var totalRegControls = FW_REGISTRY.reduce(function(s, fw) { return s + (fw.controls || []).length; }, 0); + var totalRegControls = FW_REGISTRY.reduce(function(s, fw) { + return FW_SET.has(fw.name) ? s + (fw.controls || []).length : s; + }, 0); var stats = [ [FRAMEWORKS.length, 'Frameworks'], [totalMappings.toLocaleString(), 'Mappings'], diff --git a/scripts/webapp.test.mjs b/scripts/webapp.test.mjs index 3ed1264..370ef7d 100644 --- a/scripts/webapp.test.mjs +++ b/scripts/webapp.test.mjs @@ -143,6 +143,24 @@ test('an unknown route falls back to a page, not a blank screen', () => { assert.ok(app.children.length > 0, 'the fallback rendered an empty page'); }); +// A registry can land before any row maps it (NIST AI 600-1 in #187). The home +// page must not count it as a framework, or add its controls to the headline. +test('the home page counts mapped frameworks only', () => { + const { window } = dom; + const stats = JSON.parse(readFileSync(path.join(ROOT, 'data', 'stats.json'), 'utf8')); + const mapped = new Set(window.CROSSWALK_DATA.flatMap((e) => e.mappings.map((m) => m.framework))); + const controls = window.CROSSWALK_FRAMEWORKS + .filter((fw) => mapped.has(fw.name)) + .reduce((s, fw) => s + (fw.controls || []).length, 0); + + const app = visit('#/'); + const cards = Object.fromEntries([...app.querySelectorAll('.stat-card')].map((c) => [ + c.querySelector('.stat-label').textContent, c.querySelector('.stat-num').textContent, + ])); + assert.equal(cards.Frameworks, String(stats.frameworks.mapped)); + assert.equal(cards['Registry Controls'], String(controls)); +}); + // The About page is the one place in the webapp allowed to carry the creator // credit, and this file is not — so the test checks that the section renders // with content, never the name itself.