Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion docs/backlinks.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Auto-generated by scripts/generate.js — do not edit manually
// Generated: 2026-08-28
// Backlinks: 1159
window.CROSSWALK_BACKLINKS = [
{
Expand Down
3 changes: 1 addition & 2 deletions docs/data.js
Original file line number Diff line number Diff line change
@@ -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 = [
{
Expand Down
1 change: 0 additions & 1 deletion docs/frameworks-registry.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Auto-generated by scripts/generate.js — do not edit manually
// Generated: 2026-08-28
// Frameworks: 25
window.CROSSWALK_FRAMEWORKS = [
{
Expand Down
1 change: 0 additions & 1 deletion docs/incidents.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Auto-generated by scripts/generate.js — do not edit manually
// Generated: 2026-08-28
// Incidents: 131
window.CROSSWALK_INCIDENTS = [
{
Expand Down
12 changes: 6 additions & 6 deletions scripts/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand Down
14 changes: 14 additions & 0 deletions scripts/generate.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <date>` 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('[');
Expand Down
Loading