From 12580050c089d772177ea9b6464cf24a71816f33 Mon Sep 17 00:00:00 2001 From: emmanuelgjr Date: Sun, 30 Aug 2026 10:10:52 -0400 Subject: [PATCH 1/2] T-ENG03: make the webapp data bundles deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every docs/*.js bundle carried a `// Generated: ` header. Nothing else in the generator output varies between runs, so that one line was the whole reason a regenerate on a different day showed four dirty files — and, since #30, the reason the Generator reproducibility job and the `committed entries match a fresh generation` test could only pass on the day the bundles were last committed. - generate.js: drop the run-date lines; the Source header now reads the version from package.json instead of a hard-coded, stale `v1.5.2`. - generate.test.mjs: assert no bundle header names a run or carries a date. - validate.yml: the reproducibility job now diffs every generated artefact (backlinks.json, backlinks.js, frameworks-registry.js too). - CONTRIBUTING.md: document the build contract — generated files, the determinism requirement, and why the bundles are committed (Pages serves docs/ from main; no deploy workflow exists). Determinism only. No structural, route, layout or logo change (C2). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0147wBugcuzLkswKPqgofcke --- .github/workflows/validate.yml | 7 ++++++- CONTRIBUTING.md | 22 ++++++++++++++++++++++ docs/backlinks.js | 1 - docs/data.js | 3 +-- docs/frameworks-registry.js | 1 - docs/incidents.js | 1 - scripts/generate.js | 12 ++++++------ scripts/generate.test.mjs | 14 ++++++++++++++ 8 files changed, 49 insertions(+), 12 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index fca25d8..b1d1e74 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -114,8 +114,13 @@ jobs: # And its output must match what is committed, so a hand-edit to a # generated file, or a source change that was never regenerated, fails # here rather than shipping. + # Every generated artefact is listed, and the generator is timestamp-free + # by design (T-ENG03) — a run-date in any header would fail this on day two. - name: Assert generated output is current - run: git diff --exit-code -- data/entries docs/data.js docs/incidents.js + run: >- + git diff --exit-code -- + data/entries data/backlinks.json + docs/data.js docs/backlinks.js docs/frameworks-registry.js docs/incidents.js unit-tests: name: Unit tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2311bf..37350d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,6 +122,28 @@ the corresponding JSON file in `/data/`. The schema is in [`data/schema.json`](data/schema.json). This keeps the machine-readable layer in sync with the markdown. +### Generated files + +`data/entries/*.json`, `data/backlinks.json` and the webapp bundles +(`docs/data.js`, `docs/backlinks.js`, `docs/frameworks-registry.js`, +`docs/incidents.js`) are **written by `scripts/generate.js`, never by hand**. +`data/stats.json` and the README badges come from `npm run stats`. + +The build is deterministic: running `npm run build` twice on the same +sources produces byte-identical output, and the generated files carry no +run date, machine name or other build-time value. CI regenerates everything +and fails on any diff, so after changing a mapping file run: + +```bash +npm run build # generate → validate → stats:check +npm test # includes the determinism and bundle checks +git diff --exit-code # must be clean apart from your intended change +``` + +GitHub Pages serves `docs/` straight from `main`, which is why the bundles +are committed rather than built on deploy. Keep it that way unless the +Pages source is deliberately switched to a workflow-based deploy. + --- ## Code of conduct diff --git a/docs/backlinks.js b/docs/backlinks.js index f323dd8..1c4a46e 100644 --- a/docs/backlinks.js +++ b/docs/backlinks.js @@ -1,5 +1,4 @@ // Auto-generated by scripts/generate.js — do not edit manually -// Generated: 2026-08-28 // Backlinks: 1159 window.CROSSWALK_BACKLINKS = [ { diff --git a/docs/data.js b/docs/data.js index 8442fd5..ab2b5c1 100644 --- a/docs/data.js +++ b/docs/data.js @@ -1,6 +1,5 @@ // Auto-generated by scripts/generate.js — do not edit manually -// Source: OWASP GenAI Crosswalk v1.5.2 -// Generated: 2026-08-28 +// Source: OWASP GenAI Crosswalk v4.0.0 // Entries: 51 window.CROSSWALK_DATA = [ { diff --git a/docs/frameworks-registry.js b/docs/frameworks-registry.js index c6abd6c..efa9a1b 100644 --- a/docs/frameworks-registry.js +++ b/docs/frameworks-registry.js @@ -1,5 +1,4 @@ // Auto-generated by scripts/generate.js — do not edit manually -// Generated: 2026-08-28 // Frameworks: 25 window.CROSSWALK_FRAMEWORKS = [ { diff --git a/docs/incidents.js b/docs/incidents.js index e329a5d..f8884a0 100644 --- a/docs/incidents.js +++ b/docs/incidents.js @@ -1,5 +1,4 @@ // Auto-generated by scripts/generate.js — do not edit manually -// Generated: 2026-08-28 // Incidents: 131 window.CROSSWALK_INCIDENTS = [ { diff --git a/scripts/generate.js b/scripts/generate.js index d1804f1..f73a06a 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -841,15 +841,18 @@ function main() { allEntries.push(entry); } - // Write bundled site data for GitHub Pages query interface + // Write bundled site data for GitHub Pages query interface. + // Bundle headers are deliberately timestamp-free: docs/ is served straight + // from main and CI asserts `git diff --exit-code` on these files, so a + // generated-on date would make every checkout dirty the next day. if (!DRY_RUN && !SINGLE_ID) { + const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version; const docsDir = path.join(ROOT, 'docs'); fs.mkdirSync(docsDir, { recursive: true }); const siteDataPath = path.join(docsDir, 'data.js'); const siteData = [ `// Auto-generated by scripts/generate.js — do not edit manually`, - `// Source: OWASP GenAI Crosswalk v1.5.2`, - `// Generated: ${new Date().toISOString().split('T')[0]}`, + `// Source: OWASP GenAI Crosswalk v${PKG_VERSION}`, `// Entries: ${allEntries.length}`, `window.CROSSWALK_DATA = ${JSON.stringify(allEntries, null, 2)};`, ].join('\n'); @@ -895,7 +898,6 @@ function main() { const siteBacklinksPath = path.join(docsDir, 'backlinks.js'); const siteBacklinks = [ `// Auto-generated by scripts/generate.js — do not edit manually`, - `// Generated: ${new Date().toISOString().split('T')[0]}`, `// Backlinks: ${backlinksArray.length}`, `window.CROSSWALK_BACKLINKS = ${JSON.stringify(backlinksArray, null, 2)};`, ].join('\n'); @@ -920,7 +922,6 @@ function main() { const fwRegistryPath = path.join(docsDir, 'frameworks-registry.js'); const fwRegistryData = [ `// Auto-generated by scripts/generate.js — do not edit manually`, - `// Generated: ${new Date().toISOString().split('T')[0]}`, `// Frameworks: ${fwRegistry.length}`, `window.CROSSWALK_FRAMEWORKS = ${JSON.stringify(fwRegistry, null, 2)};`, ].join('\n'); @@ -934,7 +935,6 @@ function main() { const incPath = path.join(docsDir, 'incidents.js'); const incData = [ `// Auto-generated by scripts/generate.js — do not edit manually`, - `// Generated: ${new Date().toISOString().split('T')[0]}`, `// Incidents: ${incDb.incidents.length}`, `window.CROSSWALK_INCIDENTS = ${JSON.stringify(incDb.incidents, null, 2)};`, ].join('\n'); diff --git a/scripts/generate.test.mjs b/scripts/generate.test.mjs index 8c31dce..c531ae4 100644 --- a/scripts/generate.test.mjs +++ b/scripts/generate.test.mjs @@ -117,6 +117,20 @@ test('DRAFT never survives into a stored enum field', () => { assert.deepEqual(leaked.slice(0, 5), [], `${leaked.length} field(s) stored the literal "DRAFT"`); }); +test('webapp bundles carry no build timestamp', () => { + // docs/ is served straight from main and CI diffs these files against a + // fresh generation. A `// Generated: ` header made that diff fail on + // every day but the one the bundle was committed — so the header must + // describe the data, never the run. + for (const f of BUNDLES.filter((b) => fs.existsSync(b))) { + const header = fs.readFileSync(f, 'utf8').split(/\r?\n/).filter((l) => l.startsWith('//')); + for (const line of header) { + assert.doesNotMatch(line, /Generated:/i, `${path.basename(f)} header names a run: ${line}`); + assert.doesNotMatch(line, /\d{4}-\d{2}-\d{2}/, `${path.basename(f)} header carries a date: ${line}`); + } + } +}); + test('webapp bundles stay in step with the entry files', () => { const src = fs.readFileSync(path.join(ROOT, 'docs', 'data.js'), 'utf8'); const start = src.indexOf('['); From b0445fefa396526d5f35bd011b60c372bffbded7 Mon Sep 17 00:00:00 2001 From: emmanuelgjr Date: Mon, 14 Sep 2026 10:51:07 -0400 Subject: [PATCH 2/2] Fix: MAESTRO layer descriptions still defined the superseded model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #32 corrected the seven layer titles in data/frameworks/maestro.json to the CSA model but left the descriptions behind, so L4-L7 each carried the right name and another layer's definition — L4 "Deployment & Infrastructure" described as "Tool access — covers MCP…", L7 "Agent Ecosystem" as "Human-agent interface". These ship in the npm package's registries, docs/frameworks-registry.js and the OSCAL catalog export. - All seven descriptions transcribed from the architecture table in llm-top10/LLM_MAESTRO.md, the file checkMaestroLayers() already treats as canonical. L1-L3 change too: their old scopes followed the same superseded split (MCP and tool registries sat under L4). - checkMaestroLayers() now compares descriptions as well as titles. Negative-tested: restoring the old L5 description fails the run. - Registry changelog entry added. Sub-controls untouched (issue #31). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014SfR2YzLxRH54DAVzDk8gR --- data/frameworks/maestro.json | 19 ++++++++++++------- docs/frameworks-registry.js | 19 ++++++++++++------- scripts/validate.js | 20 ++++++++++++++++---- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/data/frameworks/maestro.json b/data/frameworks/maestro.json index 8d2c0b4..ed47473 100644 --- a/data/frameworks/maestro.json +++ b/data/frameworks/maestro.json @@ -13,7 +13,7 @@ { "control_id": "L1", "title": "Foundation Models", - "description": "Base model layer — covers model selection, provenance, fine-tuning security, and model integrity threats.", + "description": "Base LLMs providing core reasoning and generation", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -45,7 +45,7 @@ { "control_id": "L2", "title": "Data Operations", - "description": "Data pipelines — covers RAG, vector stores, training data, embedding security, and data governance.", + "description": "Ingestion pipelines, storage, RAG, embeddings, vector stores", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -77,7 +77,7 @@ { "control_id": "L3", "title": "Agent Frameworks", - "description": "Agent runtime — covers agent orchestration, goal management, memory, and planning security.", + "description": "Orchestration platforms, tool registries, MCP, plugin ecosystems", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -109,7 +109,7 @@ { "control_id": "L4", "title": "Deployment & Infrastructure", - "description": "Tool access — covers MCP, API integrations, plugin security, and tool authorization.", + "description": "Servers, containers, networks, CI/CD, runtime environments", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -141,7 +141,7 @@ { "control_id": "L5", "title": "Evaluation & Observability", - "description": "Infrastructure — covers containerization, networking, secrets management, and runtime isolation.", + "description": "Monitoring, logging, telemetry, behavioural baselines", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -173,7 +173,7 @@ { "control_id": "L6", "title": "Security & Compliance", - "description": "Multi-agent coordination — covers inter-agent communication, delegation, consensus, and cascading failure prevention.", + "description": "Identity, access control, audit, governance, credential management", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -205,7 +205,7 @@ { "control_id": "L7", "title": "Agent Ecosystem", - "description": "Human-agent interface — covers user authentication, output validation, human oversight, and trust management.", + "description": "Multi-agent interaction, A2A communication, cascade dynamics", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -240,6 +240,11 @@ "date": "2026-04-09", "change": "Initial seed — 7 layers with 21 sub-controls from CSA MAESTRO framework", "author": "OWASP GenAI Data Security Initiative" + }, + { + "date": "2026-09-14", + "change": "Layer descriptions L1–L7 transcribed from the architecture table in llm-top10/LLM_MAESTRO.md. #32 corrected the titles to the CSA model but left the descriptions of the superseded one, so L4–L7 each carried the correct name and another layer's definition. Sub-controls unchanged (issue #31).", + "author": "OWASP GenAI Data Security Initiative" } ], "inventory_completeness": { diff --git a/docs/frameworks-registry.js b/docs/frameworks-registry.js index efa9a1b..92047f2 100644 --- a/docs/frameworks-registry.js +++ b/docs/frameworks-registry.js @@ -4653,7 +4653,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L1", "title": "Foundation Models", - "description": "Base model layer — covers model selection, provenance, fine-tuning security, and model integrity threats.", + "description": "Base LLMs providing core reasoning and generation", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4685,7 +4685,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L2", "title": "Data Operations", - "description": "Data pipelines — covers RAG, vector stores, training data, embedding security, and data governance.", + "description": "Ingestion pipelines, storage, RAG, embeddings, vector stores", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4717,7 +4717,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L3", "title": "Agent Frameworks", - "description": "Agent runtime — covers agent orchestration, goal management, memory, and planning security.", + "description": "Orchestration platforms, tool registries, MCP, plugin ecosystems", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4749,7 +4749,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L4", "title": "Deployment & Infrastructure", - "description": "Tool access — covers MCP, API integrations, plugin security, and tool authorization.", + "description": "Servers, containers, networks, CI/CD, runtime environments", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4781,7 +4781,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L5", "title": "Evaluation & Observability", - "description": "Infrastructure — covers containerization, networking, secrets management, and runtime isolation.", + "description": "Monitoring, logging, telemetry, behavioural baselines", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4813,7 +4813,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L6", "title": "Security & Compliance", - "description": "Multi-agent coordination — covers inter-agent communication, delegation, consensus, and cascading failure prevention.", + "description": "Identity, access control, audit, governance, credential management", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4845,7 +4845,7 @@ window.CROSSWALK_FRAMEWORKS = [ { "control_id": "L7", "title": "Agent Ecosystem", - "description": "Human-agent interface — covers user authentication, output validation, human oversight, and trust management.", + "description": "Multi-agent interaction, A2A communication, cascade dynamics", "parent": null, "function": "Architecture Layer", "kind": "layer" @@ -4880,6 +4880,11 @@ window.CROSSWALK_FRAMEWORKS = [ "date": "2026-04-09", "change": "Initial seed — 7 layers with 21 sub-controls from CSA MAESTRO framework", "author": "OWASP GenAI Data Security Initiative" + }, + { + "date": "2026-09-14", + "change": "Layer descriptions L1–L7 transcribed from the architecture table in llm-top10/LLM_MAESTRO.md. #32 corrected the titles to the CSA model but left the descriptions of the superseded one, so L4–L7 each carried the correct name and another layer's definition. Sub-controls unchanged (issue #31).", + "author": "OWASP GenAI Data Security Initiative" } ], "inventory_completeness": { diff --git a/scripts/validate.js b/scripts/validate.js index dce3942..a884d9a 100644 --- a/scripts/validate.js +++ b/scripts/validate.js @@ -766,11 +766,15 @@ function checkMaestroLayers() { const regPath = path.join(ROOT, 'data', 'frameworks', 'maestro.json'); if (!fs.existsSync(mdPath) || !fs.existsSync(regPath)) return true; - // The architecture table: | | L | ... | + // The architecture table: | | L | | | const canon = {}; + const canonDesc = {}; for (const line of fs.readFileSync(mdPath, 'utf8').split('\n')) { - const m = line.match(/^\|\s*([^|]+?)\s*\|\s*(L[1-7])\s*\|/); - if (m) canon[m[2]] = m[1].trim(); + const m = line.match(/^\|\s*([^|]+?)\s*\|\s*(L[1-7])\s*\|\s*([^|]+?)\s*\|/); + if (m) { + canon[m[2]] = m[1].trim(); + canonDesc[m[2]] = m[3].trim(); + } } if (Object.keys(canon).length !== 7) { warn('MAESTRO layers', `Could not read all seven layers from LLM_MAESTRO.md (found ${Object.keys(canon).length})`); @@ -784,6 +788,14 @@ function checkMaestroLayers() { `maestro.json ${c.control_id} is "${c.title}", but LLM_MAESTRO.md calls it "${canon[c.control_id]}"`); bad++; } + // #32 fixed the titles but left the superseded model's descriptions, so + // L4–L7 each had the right name and another layer's definition. A title + // check alone cannot see that. + if (c.kind === 'layer' && canonDesc[c.control_id] && c.description !== canonDesc[c.control_id]) { + fail('MAESTRO layers', + `maestro.json ${c.control_id} description "${c.description}" does not match LLM_MAESTRO.md: "${canonDesc[c.control_id]}"`); + bad++; + } } const incPath = path.join(ROOT, 'data', 'incidents.json'); @@ -803,7 +815,7 @@ function checkMaestroLayers() { } } - if (!bad) pass('MAESTRO layers', 'Registry and incident labels match all seven canonical layer names'); + if (!bad) pass('MAESTRO layers', 'Registry titles, layer descriptions and incident labels match the canonical architecture table'); return bad === 0; }