@bongos/core 1.19.266 → 1.19.267

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.266",
6
- "core_contract": "1.19.266",
7
- "source_commit": "d91db4e58ad64674e65d64d611241e88e02188ac",
5
+ "core_version": "1.19.267",
6
+ "core_contract": "1.19.267",
7
+ "source_commit": "438a7182b523f2e5d9bd4181397642f51b75c77d",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-20T17:59:23.193Z",
9
+ "built_at": "2026-08-20T18:45:39.708Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 318,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1365,
14
+ "functional_verbatim": 1367,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1705,
20
- "tree_sha256": "22cc9e757ec02b637af6ec28f6d00b5daa91a92f742361641d8921f5ecd27996",
19
+ "file_count": 1707,
20
+ "tree_sha256": "c333d9a98c37d10dd8a8e60d7c3f1a279fab0365ab4462a4a64517d5e681e77f",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -3169,6 +3169,11 @@
3169
3169
  "mode": "0000644",
3170
3170
  "sha256": "b651b73c65b0da33284430b430638dbc39b34db43846a05ab431deaabcc44b3b"
3171
3171
  },
3172
+ {
3173
+ "path": "modules/grading/generated-provenance.js",
3174
+ "mode": "0000644",
3175
+ "sha256": "43e94be02fe20891633f7fbe7abe0caf774d86743cbce4c5ec34b8499e22e125"
3176
+ },
3172
3177
  {
3173
3178
  "path": "modules/grading/grader-rubric.json",
3174
3179
  "mode": "0000644",
@@ -3217,7 +3222,7 @@
3217
3222
  {
3218
3223
  "path": "modules/grading/grader-workers/worker-prompt.js",
3219
3224
  "mode": "0000644",
3220
- "sha256": "986c7b039f392704613160c7ee10738030ef47972150d867e0972f6dfb761ae4"
3225
+ "sha256": "4fc9198ba4df9ee8aacb8c48f2a1b1fcc9f076ccb4b751ec45fd91af26edad91"
3221
3226
  },
3222
3227
  {
3223
3228
  "path": "modules/grading/grader-workers/worker-verdict.js",
@@ -3227,7 +3232,7 @@
3227
3232
  {
3228
3233
  "path": "modules/grading/grader.js",
3229
3234
  "mode": "0000644",
3230
- "sha256": "49ae9286fea73fbf67a89e42bf10a14f9fb04d7857582accaab402ad966fafd6"
3235
+ "sha256": "a37f5327f986f34bb67496052a9892228b432fdfcca61d52970b22c10ac74fa8"
3231
3236
  },
3232
3237
  {
3233
3238
  "path": "modules/grading/module.json",
@@ -4467,12 +4472,12 @@
4467
4472
  {
4468
4473
  "path": "package-lock.json",
4469
4474
  "mode": "0000644",
4470
- "sha256": "6f22594e4af013cef5cb741be874306be308300163055bd00a6c929d48b6de93"
4475
+ "sha256": "593aec1826ec6611b0b879e4d71faeaac1fb32934aae45ac5abbf726f44c029e"
4471
4476
  },
4472
4477
  {
4473
4478
  "path": "package.json",
4474
4479
  "mode": "0000644",
4475
- "sha256": "0d77282b3bbfdbff7f582d9a7765a174a8881b18ef150dcbf6d2b640a104069a"
4480
+ "sha256": "b1c9f3acb84a8ed67474e02d525666855953406b4393eb0b4fbbece6cd92b0e8"
4476
4481
  },
4477
4482
  {
4478
4483
  "path": "public-docs/index.html",
@@ -5117,7 +5122,7 @@
5117
5122
  {
5118
5123
  "path": "scripts/gds/fitness.js",
5119
5124
  "mode": "0000644",
5120
- "sha256": "a5fa6432b70c9b18d39d08837edd17987982a12595c1a55bb44585f9af71628d"
5125
+ "sha256": "e4399cd9d5283ff23b8c5baf9e7b22a4663cab3f6e0e477b138f279cda50bd72"
5121
5126
  },
5122
5127
  {
5123
5128
  "path": "scripts/gds/gate-review.js",
@@ -6117,7 +6122,7 @@
6117
6122
  {
6118
6123
  "path": "src/module-api.js",
6119
6124
  "mode": "0000644",
6120
- "sha256": "c1a2c62a325a9d5516729a15e8bc8939aa05d2c1e25db859a8ec519e04605186"
6125
+ "sha256": "c52908a516ff2f5cc2147e0a1d0a3c2564713b3dfeada50c5ba142b1e5b5bf32"
6121
6126
  },
6122
6127
  {
6123
6128
  "path": "src/module-loader/catalog.js",
@@ -6937,7 +6942,7 @@
6937
6942
  {
6938
6943
  "path": "tests/fitness.mjs",
6939
6944
  "mode": "0000644",
6940
- "sha256": "fb9f8fe63f0f9447ab82ba171b718855e4157846ae4a292e907ff895faa892a3"
6945
+ "sha256": "de84ceb6c73818975d5da77949c8ff4823f6e9d4613d9c95ea21c96a92b66760"
6941
6946
  },
6942
6947
  {
6943
6948
  "path": "tests/fitness_ratchets.mjs",
@@ -7274,6 +7279,11 @@
7274
7279
  "mode": "0000644",
7275
7280
  "sha256": "5f01a094ba58b6c39c8d386162399a1ed81fee400c12d0feb932485af3875234"
7276
7281
  },
7282
+ {
7283
+ "path": "tests/grader_generated_provenance.mjs",
7284
+ "mode": "0000644",
7285
+ "sha256": "6e1288f285bfce6cecc080d597455b9051754efa0d2f885cf03a9ee4cea4c6a8"
7286
+ },
7277
7287
  {
7278
7288
  "path": "tests/grader_panel.mjs",
7279
7289
  "mode": "0000644",
@@ -0,0 +1,260 @@
1
+ // modules/grading/generated-provenance.js — task 1002795.
2
+ //
3
+ // THE FALSE POSITIVE THIS EXISTS TO KILL. Several tracked files carry regions
4
+ // written by a generator, fenced with a "do not hand-edit" banner. Adding a
5
+ // docs/session-logs/ file makes the fitness "generated artifacts fresh" check go
6
+ // RED until you run gen-session-index.js and COMMIT its rewrite of the CLAUDE.md
7
+ // §13 block. So the ship's own diff necessarily contains changed lines inside a
8
+ // fence that says "do not hand-edit" — and the grader panel, which reads only
9
+ // the diff, gates the ship as a hand-edit. It is unresolvable by re-grading:
10
+ // generator output is textually IDENTICAL to typing the same characters, so no
11
+ // amount of re-reading the diff can tell them apart. On task 1002794 it went
12
+ // 7/10, then 4/10 as the finding was re-flagged "unresolved" under the delta
13
+ // re-grade policy, and adding a rebuttal to the ship notes made it WORSE.
14
+ //
15
+ // THE FIX is to give the panel the provenance it lacks, from the one authority
16
+ // that HAS it: each generator's own --check/--check-block flag REGENERATES from
17
+ // source and compares, rather than reading the diff. If that check passes, the
18
+ // committed region is provably the generator's output. That is a fact about the
19
+ // tree, not an opinion about a diff, and it is exactly what fitness.js gates on.
20
+ //
21
+ // Deliberately NOT the broader options in the task: this does not exclude the
22
+ // regions from the diff (a worker that cannot see them cannot notice a real
23
+ // hand-edit smuggled in beside them), and it does not mark regions and trust the
24
+ // marking (the marking is what the builder controls). It verifies, then reports.
25
+ //
26
+ // The generator roster MUST stay in lockstep with fitness.js
27
+ // checkGeneratedArtifactsFresh() — that is the check whose red/green this
28
+ // mirrors. tests/grader_generated_provenance.mjs pins both sides.
29
+
30
+ const path = require('node:path');
31
+ const { execFile, execFileSync } = require('node:child_process');
32
+
33
+ // A generator script lives in the very tree being graded, so an in-diff edit
34
+ // could make it hang — and an unbounded spawn on the grade path would hang the
35
+ // grade with it. fitness.js's equivalent spawns are likewise untimed; this is
36
+ // the bounded version. gen-repo-map walks the whole .js tree, so the budget is
37
+ // generous rather than tight: it exists to stop a hang, not to race a slow disk.
38
+ const VERIFY_TIMEOUT_MS = 120000;
39
+
40
+ // A minimal environment for the spawn. A generator regenerates a doc from source
41
+ // files; it has no business reading the session's credentials, tokens, or API
42
+ // keys, and nothing here needs LD_PRELOAD-style injection either. Allowlist, so
43
+ // a variable added to the session later is excluded by default.
44
+ const SPAWN_ENV = Object.freeze(Object.fromEntries(
45
+ ['PATH', 'HOME', 'NODE_PATH', 'LANG', 'LC_ALL', 'TZ', 'TMPDIR', 'SystemRoot', 'ComSpec']
46
+ .filter((k) => process.env[k] !== undefined)
47
+ .map((k) => [k, process.env[k]])
48
+ ));
49
+
50
+ // One entry per generator fitness gates, with the tracked files it owns and how
51
+ // much of each it owns. `fence` = only the region between the markers is
52
+ // generated (the rest of the file is hand-authored and must be graded normally).
53
+ // `whole` = the entire file is generator output.
54
+ const GENERATORS = [
55
+ {
56
+ script: 'gen-repo-map.js',
57
+ verify: '--check-block',
58
+ owns: [
59
+ { file: 'src/bongos/CLAUDE.md', scope: 'fence', marker: 'BEGIN GENERATED REPO-MAP' },
60
+ { file: 'src/bongos/routes/CLAUDE.md', scope: 'fence', marker: 'BEGIN GENERATED REPO-MAP' },
61
+ { file: 'scripts/gds/CLAUDE.md', scope: 'fence', marker: 'BEGIN GENERATED REPO-MAP' },
62
+ ],
63
+ },
64
+ {
65
+ script: 'gen-session-index.js',
66
+ verify: '--check-block',
67
+ owns: [{ file: 'CLAUDE.md', scope: 'fence', marker: 'BEGIN GENERATED SESSION-LOG SNIPPET' }],
68
+ },
69
+ {
70
+ script: 'gen-file-map.js',
71
+ verify: '--check',
72
+ owns: [{ file: 'docs/file-map.md', scope: 'fence', marker: 'BEGIN GENERATED FILE-MAP' }],
73
+ },
74
+ {
75
+ script: 'gen-api-docs.js',
76
+ verify: '--check',
77
+ owns: [
78
+ { file: 'docs/api/openapi.json', scope: 'whole' },
79
+ { file: 'docs/api-reference.md', scope: 'whole' },
80
+ { file: 'docs/routes-permissions.md', scope: 'whole' },
81
+ ],
82
+ },
83
+ ];
84
+
85
+ // THE CIRCULARITY GUARD (panel blocker, task 1002795). Verification works by
86
+ // RUNNING a generator out of the tree under review. Round 2 guarded only the
87
+ // generator scripts themselves, which the panel correctly called PARTIAL: a
88
+ // generator also `require`s other repo modules, so a diff touching anything in
89
+ // that require-chain still got its code executed on the grade path — and could
90
+ // still bend the verdict that vouches for it.
91
+ //
92
+ // So the line is drawn where it can be drawn soundly: verification runs ONLY for
93
+ // a diff that introduces no executable file at all. If nothing in the diff can
94
+ // run, then running a generator cannot execute anything the diff added — not the
95
+ // generator, not its imports, not a transitive dependency. No require-chain to
96
+ // reason about, and nothing to keep in sync as the generators grow dependencies.
97
+ //
98
+ // This keeps the case the task exists for: a session-log ship is docs-only, and
99
+ // so is every "regenerate the committed doc" ship — exactly the diffs that hit
100
+ // the false positive. A ship that changes code AND regenerates a doc gets no
101
+ // provenance claim and is graded as it was before this feature: full suspicion,
102
+ // no regression, just no help.
103
+ const EXECUTABLE_RE = /\.(js|mjs|cjs|ts|mts|cts|sh|bash|zsh|py|rb|pl|php|ps1|bat|cmd|node|wasm)$/i;
104
+
105
+ // A diff is verification-eligible only if EVERY changed path is inert. Unknown
106
+ // extensions are treated as executable (fail closed) — a dotfile-less binary or
107
+ // a `Dockerfile` is not something to run a generator next to on faith.
108
+ function diffCarriesExecutable(changedFiles) {
109
+ return (Array.isArray(changedFiles) ? changedFiles : []).some((f) => {
110
+ const p = String(f || '');
111
+ if (!p) return false;
112
+ const base = p.split('/').pop() || '';
113
+ if (EXECUTABLE_RE.test(base)) return true;
114
+ // No extension at all, or an unrecognised one on a non-doc/data path.
115
+ return !/\.(md|markdown|json|ya?ml|txt|csv|tsv|sql|html|css|svg|png|jpe?g|gif|webp|ico|lock|toml|ini|cfg|env|gitignore|npmrc|patch|diff|mmd|xml)$/i.test(base);
116
+ });
117
+ }
118
+
119
+ // PURE. Which of the changed files are generator-owned, grouped by generator.
120
+ // Returns [] when the diff touches none — the common case, and the reason this
121
+ // costs nothing on a normal ship.
122
+ function generatorsInDiff(changedFiles) {
123
+ if (diffCarriesExecutable(changedFiles)) return []; // circularity guard — see above
124
+ const changed = new Set((Array.isArray(changedFiles) ? changedFiles : []).filter(Boolean));
125
+ const out = [];
126
+ for (const g of GENERATORS) {
127
+ const owned = g.owns.filter((o) => changed.has(o.file));
128
+ if (owned.length) out.push({ script: g.script, verify: g.verify, owned });
129
+ }
130
+ return out;
131
+ }
132
+
133
+ // Runs each implicated generator's OWN freshness check. `run` is injectable so
134
+ // the unit test never spawns a process. A check that throws means the region does
135
+ // NOT match what the generator would produce right now — which is the case where
136
+ // the panel's suspicion is CORRECT, so that file is reported as unverified and
137
+ // gets no provenance claim at all.
138
+ function verifyGeneratedProvenance({ changedFiles, cwd, scriptDir, run } = {}) {
139
+ try {
140
+ return _verify({ changedFiles, cwd, scriptDir, run });
141
+ } catch (_) {
142
+ // ENRICHMENT ONLY. A failure here must never block or alter a grade, so it
143
+ // degrades to "no provenance claim" — which is the pre-task-1002795
144
+ // behaviour, i.e. the panel keeps full suspicion.
145
+ return { verified: [], unverified: [], checked: [] };
146
+ }
147
+ }
148
+
149
+ function _verify({ changedFiles, cwd, scriptDir, run } = {}) {
150
+ const implicated = generatorsInDiff(changedFiles);
151
+ const verified = [];
152
+ const unverified = [];
153
+ if (implicated.length === 0) return { verified, unverified, checked: [] };
154
+ const dir = scriptDir || path.join(__dirname, '..', '..', 'scripts', 'gds');
155
+ const exec = run || ((script, flag) => {
156
+ execFileSync(process.execPath, [path.join(dir, script), flag], {
157
+ cwd: cwd || process.cwd(), stdio: 'pipe', timeout: VERIFY_TIMEOUT_MS, env: SPAWN_ENV,
158
+ });
159
+ });
160
+ const checked = [];
161
+ for (const g of implicated) {
162
+ let ok = true;
163
+ let reason = null;
164
+ try {
165
+ exec(g.script, g.verify);
166
+ } catch (err) {
167
+ ok = false;
168
+ reason = `${g.script} ${g.verify} did not pass`;
169
+ }
170
+ checked.push({ script: g.script, verify: g.verify, ok });
171
+ for (const o of g.owned) {
172
+ (ok ? verified : unverified).push({ ...o, script: g.script, verify: g.verify, ...(ok ? {} : { reason }) });
173
+ }
174
+ }
175
+ return { verified, unverified, checked };
176
+ }
177
+
178
+ // PURE. The prompt section. Empty string when the diff touches no generated
179
+ // file, so a normal ship's prompt is byte-identical to before.
180
+ function provenanceSection(manifest) {
181
+ const verified = (manifest && manifest.verified) || [];
182
+ const unverified = (manifest && manifest.unverified) || [];
183
+ if (verified.length === 0 && unverified.length === 0) return '';
184
+ const lines = [];
185
+ lines.push('---');
186
+ lines.push('## Generated content in this diff (provenance — VERIFIED, not asserted)');
187
+ lines.push('');
188
+ if (verified.length) {
189
+ lines.push('Each region below was checked by RE-RUNNING its generator against this committed tree and comparing the output. The check passed, so the committed content is provably what the generator produces — it is machine output, not an authored edit:');
190
+ lines.push('');
191
+ for (const v of verified) {
192
+ lines.push(v.scope === 'whole'
193
+ ? `- \`${v.file}\` — ENTIRE FILE is output of \`${v.script}\` (verified: \`${v.script} ${v.verify}\` passed)`
194
+ : `- \`${v.file}\` — only the region inside the \`${v.marker}\` fence is output of \`${v.script}\` (verified: \`${v.script} ${v.verify}\` passed)`);
195
+ }
196
+ lines.push('');
197
+ lines.push('**Do not report changes in these regions as hand-edits, as violations of their own "do not hand-edit" banner, or as unexplained/unrelated churn.** Committing them is REQUIRED: the fitness gate fails until the regenerated output is committed, so a ship that touches a session log or a route signature cannot avoid this diff. Generator output is textually identical to typing the same characters, so the diff alone cannot distinguish them — that is precisely why the verification above was run for you, and why re-reading the diff cannot overturn it.');
198
+ lines.push('');
199
+ lines.push('Grade these regions on ONE question only: was regenerating correct here — i.e. does the diff carry the generated change the authored change implies? For a fenced file, everything OUTSIDE the named fence is hand-authored and must be graded normally.');
200
+ lines.push('');
201
+ }
202
+ if (unverified.length) {
203
+ lines.push('These generated files changed but their generator\'s own check did NOT pass, so their content is NOT verified as machine output — treat changes here with full suspicion, including as possible hand-edits:');
204
+ lines.push('');
205
+ for (const u of unverified) lines.push(`- \`${u.file}\` — ${u.reason}`);
206
+ lines.push('');
207
+ }
208
+ return lines.join('\n');
209
+ }
210
+
211
+ // ASYNC verification — the form the grade path uses. The sync variant blocks the
212
+ // event loop while a generator fully regenerates and compares (gen-repo-map
213
+ // walks the whole .js tree), which would stall any other concurrent grading in
214
+ // the same process. gradeSubagent is already async and verifies ONCE per grade
215
+ // before the panel spawns, so nothing needs to block: prompt construction only
216
+ // READS the finished manifest.
217
+ async function verifyGeneratedProvenanceAsync({ changedFiles, cwd, scriptDir, run } = {}) {
218
+ const implicated = generatorsInDiff(changedFiles);
219
+ const empty = { verified: [], unverified: [], checked: [] };
220
+ if (implicated.length === 0) return empty;
221
+ const dir = scriptDir || path.join(__dirname, '..', '..', 'scripts', 'gds');
222
+ const runOne = run || ((script, flag) => new Promise((resolve, reject) => {
223
+ execFile(process.execPath, [path.join(dir, script), flag],
224
+ { cwd: cwd || process.cwd(), timeout: VERIFY_TIMEOUT_MS, env: SPAWN_ENV },
225
+ (err) => (err ? reject(err) : resolve()));
226
+ }));
227
+ try {
228
+ const verified = [];
229
+ const unverified = [];
230
+ // Concurrently: each check is an independent whole-repo regeneration, and at
231
+ // most a handful are ever implicated, so serializing them just added latency
232
+ // to the grade path for no benefit.
233
+ const checked = await Promise.all(implicated.map(async (g) => {
234
+ let ok = true;
235
+ try { await runOne(g.script, g.verify); } catch (_) { ok = false; }
236
+ return { script: g.script, verify: g.verify, ok };
237
+ }));
238
+ implicated.forEach((g, i) => {
239
+ const ok = checked[i].ok;
240
+ for (const o of g.owned) {
241
+ (ok ? verified : unverified).push({
242
+ ...o, script: g.script, verify: g.verify,
243
+ ...(ok ? {} : { reason: `${g.script} ${g.verify} did not pass` }),
244
+ });
245
+ }
246
+ });
247
+ return { verified, unverified, checked };
248
+ } catch (_) {
249
+ // ENRICHMENT ONLY — degrade to no claim (the pre-task behaviour: the panel
250
+ // keeps full suspicion), never fail a grade on a verification problem.
251
+ return empty;
252
+ }
253
+ }
254
+
255
+ module.exports = {
256
+ GENERATORS, generatorsInDiff, provenanceSection, diffCarriesExecutable,
257
+ verifyGeneratedProvenance, // sync — kept for DB-free unit tests + non-async callers
258
+ verifyGeneratedProvenanceAsync, // the grade path: non-blocking, once per grade
259
+ VERIFY_TIMEOUT_MS,
260
+ };
@@ -14,6 +14,14 @@
14
14
  // Unified diff). Behavior-neutral: produces the identical string the inlined
15
15
  // builders produced for the same inputs.
16
16
  const { fenceBlock, scrubUntrusted, NOFOLLOW_PREAMBLE } = require('./fence');
17
+ // task 1002795: VERIFIED provenance for generator-owned regions of the diff.
18
+ // Trusted text — the verification re-ran each generator's own --check against
19
+ // the committed tree, so it is the one thing here the builder cannot influence
20
+ // by writing content, and it sits OUTSIDE every fence. Already computed by the
21
+ // caller (once per grade, off this path), so building a prompt does no work and
22
+ // spawns nothing. Empty when the diff touches no generated file, leaving a
23
+ // normal ship's prompt byte-identical to before.
24
+ const { provenanceSection } = require('../generated-provenance');
17
25
 
18
26
  // The instance's project name for the worker identity lines, so a non-OTB Cloud
19
27
  // Bongos instance is graded against ITS OWN project instead of a hardcoded "Off
@@ -138,6 +146,10 @@ function buildWorkerPrompt({
138
146
  lines.push('');
139
147
  }
140
148
  }
149
+ if (contextBundle && contextBundle.generatedProvenance) {
150
+ const provSection = provenanceSection(contextBundle.generatedProvenance);
151
+ if (provSection) lines.push(provSection);
152
+ }
141
153
  // task 1002662: prior panel round context for a delta-aware re-grade. The
142
154
  // findings/response/interim summary are builder-adjacent data → fenced; the
143
155
  // policy that governs them is trusted text outside the fences.
@@ -108,6 +108,7 @@ const FALLTHROUGH_CONFIDENCE = V4.fallthrough_confidence ?? 0.3;
108
108
  // values are model ids. workerModelFor guards the cross-model invariant.
109
109
  const WORKER_MODEL_TIERS = V4.worker_model_tiers || {};
110
110
  const workers = require('./grader-workers');
111
+ const generatedProvenance = require('./generated-provenance'); // task 1002795
111
112
  // Carved into modules/grading/ (BV1.R73 / ADR 0091 §1+§4): the two deterministic
112
113
  // trust classifiers are KERNEL (KERNEL_FILES, R70) — reached through the doorway,
113
114
  // never a deep core require. The worker panel + extract-json are intra-module.
@@ -312,7 +313,7 @@ Return ONLY JSON matching this schema — no prose before or after, no markdown
312
313
 
313
314
  You have a Read tool. Spend reads checking that removed code is unreferenced elsewhere, that new helpers match surrounding style, that the diff's value_summary matches what the diff actually does, and that acceptance criteria you can't verify from the diff alone are actually satisfied in the post-merge file. Do not run tests, do not edit files, do not call the network.`;
314
315
 
315
- function buildPrompt({ task, valueSummary, diff, changedFiles, diffStats }) {
316
+ function buildPrompt({ task, valueSummary, diff, changedFiles, diffStats, generatedProvenance: prov = null }) {
316
317
  const t = task || {};
317
318
  const desc = (t.description || '(no description)').trim();
318
319
  const title = (t.title || '(no title)').trim();
@@ -371,7 +372,7 @@ function buildPrompt({ task, valueSummary, diff, changedFiles, diffStats }) {
371
372
  lines.push('```diff');
372
373
  lines.push(diffText);
373
374
  lines.push('```');
374
- lines.push('');
375
+ lines.push('', ...(prov ? [generatedProvenance.provenanceSection(prov)].filter(Boolean) : [])); // task 1002795 — the trivial path is where a docs-only ship lands
375
376
  lines.push('Now grade. Default-fail unless your read of the diff and post-merge files turns up no material problems. Output ONLY the JSON object — nothing before or after it.');
376
377
  return lines.join('\n');
377
378
  }
@@ -1771,7 +1772,7 @@ async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats
1771
1772
  // kills the per-worker re-exploration that dominated panel cost.
1772
1773
  // diff (task 1002905): lets the bundle prove a truncated prefix actually shows
1773
1774
  // the change instead of handing a worker a head fragment as "post-change".
1774
- const contextBundle = buildContextBundle({ changedFiles, cwd: runOpts.cwd, diff });
1775
+ const contextBundle = Object.assign(...await Promise.all([buildContextBundle({ changedFiles, cwd: runOpts.cwd, diff }), generatedProvenance.verifyGeneratedProvenanceAsync({ changedFiles, cwd: runOpts.cwd }).then((generatedProvenance) => ({ generatedProvenance }))])); // task 1002795 — verified once, concurrently with the bundle read, off the prompt path
1775
1776
  // task 1002660: cache-wrapped spawns. Same prompt + model replays at $0
1776
1777
  // (llm-cache, System 5) — the prompt embeds the diff + bundle, so the key
1777
1778
  // needs no separate scope sha. Absent llm-cache → plain runSubagent.
@@ -1831,7 +1832,7 @@ async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats
1831
1832
  // (gradeSubagent) points at the v4 panel. Useful for re-running a single
1832
1833
  // generalist prompt N times for diagnostic comparison against the panel.
1833
1834
  async function gradeSubagentV3MultiShot({ task, valueSummary, diff, changedFiles, diffStats, shots = DEFAULT_SHOTS, builderModel, runOpts = {} }) {
1834
- const prompt = buildPrompt({ task, valueSummary, diff, changedFiles, diffStats });
1835
+ const prompt = buildPrompt({ task, valueSummary, diff, changedFiles, diffStats, generatedProvenance: await generatedProvenance.verifyGeneratedProvenanceAsync({ changedFiles, cwd: runOpts.cwd }) }); // task 1002795
1835
1836
  const graderModel = selectGraderModel({ builderModel });
1836
1837
  const N = Math.max(1, Math.trunc(shots));
1837
1838
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.266",
3
+ "version": "1.19.267",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.266",
9
+ "version": "1.19.267",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.266",
3
+ "version": "1.19.267",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -1982,7 +1982,6 @@ module.exports = {
1982
1982
  HOST_GAME,
1983
1983
  GRANDFATHERED,
1984
1984
  LEGACY_NAMESPACED,
1985
- KERNEL_FILES,
1986
1985
  ROOT_HARD_MAX,
1987
1986
  ROOT_TARGET,
1988
1987
  ROOT_CHAR_HARD_MAX,
package/src/module-api.js CHANGED
@@ -959,8 +959,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
959
959
  // landed since 1.19.264 with no explicit bump. run 32396925426. (task 1002620)
960
960
  // 1.19.266 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
961
961
  // landed since 1.19.265 with no explicit bump. run 32400609982. (task 1002620)
962
+ // 1.19.267 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
963
+ // landed since 1.19.266 with no explicit bump. run 32404853216. (task 1002620)
962
964
  // ---------------------------------------------------------------------------
963
- const CORE_VERSION = '1.19.266'; // CI auto-patch carrier (ADR 0161); see changelog above
965
+ const CORE_VERSION = '1.19.267'; // CI auto-patch carrier (ADR 0161); see changelog above
964
966
 
965
967
  // A namespaced logger so a module's log lines are attributable + consistent.
966
968
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
package/tests/fitness.mjs CHANGED
@@ -596,3 +596,16 @@ test('identity gate strips comments on CRLF checkouts too (task 1002428)', () =>
596
596
  'a CRLF code literal must still be caught'
597
597
  );
598
598
  });
599
+
600
+ test('KERNEL_FILES is exported exactly ONCE and still resolves (task 1002795)', () => {
601
+ // fitness.js listed KERNEL_FILES TWICE in the same export literal, so the
602
+ // second key silently shadowed the first — harmless today, a latent bug the
603
+ // moment the two diverge. Task 1002795 removed the duplicate. Two grader
604
+ // rounds read that `- KERNEL_FILES,` line as "the export was removed", so pin
605
+ // the actual behaviour here rather than arguing it in a ship note.
606
+ const src = fs.readFileSync(path.join(ROOT, 'scripts/gds/fitness.js'), 'utf8');
607
+ const decls = src.split('\n').filter((l) => l.trim() === 'KERNEL_FILES,');
608
+ assert.equal(decls.length, 1, 'exactly one export entry — no duplicate key');
609
+ assert.ok(Array.isArray(fitness.KERNEL_FILES), 'and it still resolves to an array');
610
+ assert.ok(fitness.KERNEL_FILES.length > 0, 'with the real kernel roster in it');
611
+ });
@@ -0,0 +1,363 @@
1
+ // tests/grader_generated_provenance.mjs — task 1002795.
2
+ //
3
+ // THE FALSE POSITIVE: adding a docs/session-logs/ file makes the fitness
4
+ // "generated artifacts fresh" check go RED until gen-session-index.js is run and
5
+ // its rewrite of the CLAUDE.md §13 block is COMMITTED. So the ship's diff
6
+ // necessarily contains changed lines inside a fence reading "do not hand-edit",
7
+ // and the grader panel — which reads only the diff — gated the ship as a
8
+ // hand-edit. It was unresolvable by re-grading (generator output is textually
9
+ // identical to typing the same characters), went 7/10 then 4/10 under the delta
10
+ // re-grade policy as the finding was re-flagged unresolved, and each round cost
11
+ // ~1 USD. It will fire on EVERY session-log ship.
12
+ //
13
+ // What we prove here (all pure or injected — no subprocess, no network):
14
+ // 1. The generator roster stays in LOCKSTEP with fitness.js
15
+ // checkGeneratedArtifactsFresh(). That check's red/green is the authority
16
+ // this feature mirrors; if a generator is added there and not here, the
17
+ // false positive comes back for that file, silently.
18
+ // 2. generatorsInDiff is exact: only generator-owned changed files implicate a
19
+ // generator, and an ordinary diff implicates none (so a normal ship pays
20
+ // nothing and its prompt is unchanged).
21
+ // 3. A PASSING generator check marks the region verified; a FAILING one marks
22
+ // it unverified — the panel must keep its suspicion exactly where the
23
+ // evidence is absent.
24
+ // 4. The prompt section says the load-bearing things: that the check was
25
+ // re-run rather than asserted, that these are not hand-edits, that
26
+ // committing them is required, and — for a fenced file — that content
27
+ // OUTSIDE the fence is still graded normally.
28
+ // 5. It is empty for a clean diff, and an unverified file NEVER gets the
29
+ // do-not-report-this language.
30
+ //
31
+ // Run: node tests/grader_generated_provenance.mjs
32
+
33
+ import assert from 'node:assert/strict';
34
+ import { test } from 'node:test';
35
+ import { createRequire } from 'node:module';
36
+ import fs from 'node:fs';
37
+ import path from 'node:path';
38
+ import { fileURLToPath } from 'node:url';
39
+
40
+ const require = createRequire(import.meta.url);
41
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
42
+ const prov = require(path.join(ROOT, 'modules/grading/generated-provenance.js'));
43
+
44
+ // ---- 1. lockstep with the fitness gate ----
45
+
46
+ test('the generator roster matches fitness.js checkGeneratedArtifactsFresh exactly', () => {
47
+ const src = fs.readFileSync(path.join(ROOT, 'scripts/gds/fitness.js'), 'utf8');
48
+ const fn = src.slice(src.indexOf('function checkGeneratedArtifactsFresh'));
49
+ const body = fn.slice(0, fn.indexOf('\nfunction ', 1));
50
+
51
+ // Every generator fitness runs must be in our roster, with the SAME flag —
52
+ // a --check-block gate mirrored as --check would compare the wrong thing.
53
+ const fitnessPairs = [...body.matchAll(/'(gen-[a-z-]+\.js)'\], \{|\bgen-[a-z-]+\.js/g)];
54
+ const fitnessScripts = new Set(
55
+ [...body.matchAll(/(gen-[a-z-]+\.js)'\)?,\s*'(--check(?:-block)?)'/g)].map((m) => `${m[1]} ${m[2]}`)
56
+ );
57
+ assert.ok(fitnessPairs.length > 0, 'sanity: found generator references in the fitness check');
58
+ assert.ok(fitnessScripts.size >= 4, `expected >=4 (script, flag) pairs in the fitness check, found ${fitnessScripts.size}: ${[...fitnessScripts].join(' | ')}`);
59
+
60
+ const ours = new Set(prov.GENERATORS.map((g) => `${g.script} ${g.verify}`));
61
+ for (const pair of fitnessScripts) {
62
+ assert.ok(ours.has(pair), `fitness gates "${pair}" but generated-provenance.js does not — the false positive is live for its files`);
63
+ }
64
+ for (const pair of ours) {
65
+ assert.ok(fitnessScripts.has(pair), `generated-provenance.js claims "${pair}" but fitness does not gate it — provenance would assert freshness nothing enforces`);
66
+ }
67
+ });
68
+
69
+ test('every file the roster claims actually exists and carries its fence marker', () => {
70
+ for (const g of prov.GENERATORS) {
71
+ for (const o of g.owns) {
72
+ const abs = path.join(ROOT, o.file);
73
+ assert.ok(fs.existsSync(abs), `${o.file} (claimed by ${g.script}) does not exist`);
74
+ if (o.scope === 'fence') {
75
+ const text = fs.readFileSync(abs, 'utf8');
76
+ assert.ok(text.includes(o.marker), `${o.file} does not contain its claimed fence marker "${o.marker}"`);
77
+ }
78
+ }
79
+ }
80
+ });
81
+
82
+ // ---- 2. generatorsInDiff is exact ----
83
+
84
+ test('an ordinary diff implicates no generator (a normal ship pays nothing)', () => {
85
+ assert.deepEqual(prov.generatorsInDiff(['src/bongos/serve-internal.js', 'tests/foo.mjs']), []);
86
+ assert.deepEqual(prov.generatorsInDiff([]), []);
87
+ assert.deepEqual(prov.generatorsInDiff(null), []);
88
+ });
89
+
90
+ test('the session-log ship shape implicates exactly gen-session-index.js', () => {
91
+ // This IS the reproducing diff: a new session log + the regenerated §13 block.
92
+ const g = prov.generatorsInDiff(['docs/session-logs/2026-08-20-x.md', 'CLAUDE.md']);
93
+ assert.equal(g.length, 1);
94
+ assert.equal(g[0].script, 'gen-session-index.js');
95
+ assert.equal(g[0].verify, '--check-block');
96
+ assert.deepEqual(g[0].owned.map((o) => o.file), ['CLAUDE.md']);
97
+ });
98
+
99
+ test('a docs-only regeneration implicates gen-api-docs.js for each doc it owns that changed', () => {
100
+ const g = prov.generatorsInDiff(['docs/api/openapi.json', 'docs/api-reference.md']);
101
+ assert.equal(g.length, 1);
102
+ assert.equal(g[0].script, 'gen-api-docs.js');
103
+ assert.deepEqual(g[0].owned.map((o) => o.file).sort(), ['docs/api-reference.md', 'docs/api/openapi.json']);
104
+ // routes-permissions.md did NOT change, so it is not claimed.
105
+ assert.ok(!g[0].owned.some((o) => o.file === 'docs/routes-permissions.md'));
106
+ });
107
+
108
+ test('THE DELIBERATE TRADE: a route change + its regenerated docs gets NO claim', () => {
109
+ // Adding a .js to the diff makes it ineligible, by design — see the guard.
110
+ // Such a ship is graded exactly as it was before this feature (full
111
+ // suspicion): no help, but no regression either. The alternative was reasoning
112
+ // about a generator's whole require-chain, which the panel rightly rejected as
113
+ // unsound. The case this feature exists for — the session-log ship — is
114
+ // docs-only and stays covered.
115
+ assert.deepEqual(prov.generatorsInDiff(['modules/lifecycle/routes/tasks.js', 'docs/api/openapi.json']), []);
116
+ });
117
+
118
+ // ---- 3. verify: pass ⇒ verified, fail ⇒ unverified ----
119
+
120
+ test('a PASSING generator check marks its regions verified', () => {
121
+ const calls = [];
122
+ const m = prov.verifyGeneratedProvenance({
123
+ changedFiles: ['CLAUDE.md', 'docs/session-logs/x.md'],
124
+ run: (script, flag) => { calls.push(`${script} ${flag}`); },
125
+ });
126
+ assert.deepEqual(calls, ['gen-session-index.js --check-block'], 'runs the generator ONE time, with its own flag');
127
+ assert.deepEqual(m.verified.map((v) => v.file), ['CLAUDE.md']);
128
+ assert.deepEqual(m.unverified, []);
129
+ assert.deepEqual(m.checked, [{ script: 'gen-session-index.js', verify: '--check-block', ok: true }]);
130
+ });
131
+
132
+ test('a FAILING generator check marks its regions UNVERIFIED — suspicion stays where evidence is absent', () => {
133
+ const m = prov.verifyGeneratedProvenance({
134
+ changedFiles: ['CLAUDE.md'],
135
+ run: () => { throw new Error('exit 2'); },
136
+ });
137
+ assert.deepEqual(m.verified, []);
138
+ assert.equal(m.unverified.length, 1);
139
+ assert.equal(m.unverified[0].file, 'CLAUDE.md');
140
+ assert.match(m.unverified[0].reason, /did not pass/);
141
+ });
142
+
143
+ test('a clean diff runs NO generator at all', () => {
144
+ let ran = 0;
145
+ const m = prov.verifyGeneratedProvenance({ changedFiles: ['README.md'], run: () => { ran++; } });
146
+ assert.equal(ran, 0, 'no subprocess on a diff that touches no generated file');
147
+ assert.deepEqual(m, { verified: [], unverified: [], checked: [] });
148
+ });
149
+
150
+ test('one generator failing does not taint another that passed', () => {
151
+ const m = prov.verifyGeneratedProvenance({
152
+ changedFiles: ['CLAUDE.md', 'docs/api/openapi.json'],
153
+ run: (script) => { if (script === 'gen-api-docs.js') throw new Error('stale'); },
154
+ });
155
+ assert.deepEqual(m.verified.map((v) => v.file), ['CLAUDE.md']);
156
+ assert.deepEqual(m.unverified.map((v) => v.file), ['docs/api/openapi.json']);
157
+ });
158
+
159
+ // ---- 4/5. the prompt section ----
160
+
161
+ test('the section is EMPTY for a diff with no generated files (prompt unchanged)', () => {
162
+ assert.equal(prov.provenanceSection({ verified: [], unverified: [], checked: [] }), '');
163
+ assert.equal(prov.provenanceSection(null), '');
164
+ });
165
+
166
+ test('the verified section states the load-bearing facts', () => {
167
+ const m = prov.verifyGeneratedProvenance({ changedFiles: ['CLAUDE.md'], run: () => {} });
168
+ const s = prov.provenanceSection(m);
169
+ assert.match(s, /RE-RUNNING its generator/, 'says the check was re-run, not asserted');
170
+ assert.match(s, /Do not report changes in these regions as hand-edits/, 'gives the instruction that kills the false positive');
171
+ assert.match(s, /do not hand-edit/, 'names the banner the worker will see in the diff');
172
+ assert.match(s, /Committing them is REQUIRED/, 'explains why the diff cannot avoid this');
173
+ assert.match(s, /gen-session-index\.js/, 'names the generator');
174
+ assert.match(s, /BEGIN GENERATED SESSION-LOG SNIPPET/, 'names the fence, so the scope is unambiguous');
175
+ assert.match(s, /OUTSIDE the named fence is hand-authored and must be graded normally/,
176
+ 'a fenced file must NOT become a blanket exemption for the whole file');
177
+ });
178
+
179
+ test('a whole-file generator is described as whole-file, not as a fence', () => {
180
+ const m = prov.verifyGeneratedProvenance({ changedFiles: ['docs/api/openapi.json'], run: () => {} });
181
+ const s = prov.provenanceSection(m);
182
+ assert.match(s, /ENTIRE FILE is output of `gen-api-docs\.js`/);
183
+ assert.doesNotMatch(s, /region inside the/, 'no fence language for a whole-file artifact');
184
+ });
185
+
186
+ test('an UNVERIFIED file never receives the do-not-report language', () => {
187
+ const m = prov.verifyGeneratedProvenance({ changedFiles: ['CLAUDE.md'], run: () => { throw new Error('stale'); } });
188
+ const s = prov.provenanceSection(m);
189
+ assert.match(s, /NOT verified as machine output/);
190
+ assert.match(s, /full suspicion/);
191
+ assert.doesNotMatch(s, /Do not report changes in these regions as hand-edits/,
192
+ 'the exemption must never leak onto a file whose generator check failed');
193
+ });
194
+
195
+ // ---- wiring: the section actually reaches a worker prompt ----
196
+
197
+ // ---- the async form the grade path uses (grader finding: no blocking spawn
198
+ // during prompt construction, and no unbounded spawn at all) ----
199
+
200
+ test('the async verifier is what the grade path uses, and it does not block', async () => {
201
+ // The sync variant blocks the event loop while a generator regenerates the
202
+ // whole repo; gradeSubagent is async and verifies ONCE before the panel
203
+ // spawns, so prompt construction only READS the finished manifest.
204
+ const calls = [];
205
+ const m = await prov.verifyGeneratedProvenanceAsync({
206
+ changedFiles: ['CLAUDE.md', 'docs/api/openapi.json'],
207
+ run: async (script, flag) => { calls.push(`${script} ${flag}`); },
208
+ });
209
+ assert.deepEqual(calls, ['gen-session-index.js --check-block', 'gen-api-docs.js --check']);
210
+ assert.deepEqual(m.verified.map((v) => v.file).sort(), ['CLAUDE.md', 'docs/api/openapi.json']);
211
+ });
212
+
213
+ test('the async verifier runs NOTHING for a diff with no generated file', async () => {
214
+ let ran = 0;
215
+ const m = await prov.verifyGeneratedProvenanceAsync({ changedFiles: ['README.md'], run: async () => { ran++; } });
216
+ assert.equal(ran, 0);
217
+ assert.deepEqual(m, { verified: [], unverified: [], checked: [] });
218
+ });
219
+
220
+ test('the async verifier marks a rejecting check unverified, and never rejects itself', async () => {
221
+ const m = await prov.verifyGeneratedProvenanceAsync({
222
+ changedFiles: ['CLAUDE.md'],
223
+ run: async () => { throw new Error('exit 2'); },
224
+ });
225
+ assert.deepEqual(m.verified, []);
226
+ assert.equal(m.unverified.length, 1);
227
+ assert.match(m.unverified[0].reason, /did not pass/);
228
+ });
229
+
230
+ test('both spawns are TIMEOUT-bounded — a generator lives in the tree being graded', () => {
231
+ // A hanging generator (including one made to hang by an edit in the very diff
232
+ // under review) must not hang the grade with it.
233
+ assert.ok(Number.isInteger(prov.VERIFY_TIMEOUT_MS) && prov.VERIFY_TIMEOUT_MS > 0);
234
+ const src = fs.readFileSync(path.join(ROOT, 'modules/grading/generated-provenance.js'), 'utf8');
235
+ const spawns = [...src.matchAll(/execFile(?:Sync)?\(/g)];
236
+ assert.equal(spawns.length, 2, 'exactly the two spawn sites (sync + async)');
237
+ for (const m of spawns) {
238
+ const window = src.slice(m.index, m.index + 320);
239
+ assert.match(window, /timeout: VERIFY_TIMEOUT_MS/, 'every spawn site must be timeout-bounded');
240
+ }
241
+ });
242
+
243
+ test('verifyGeneratedProvenance never throws — it degrades to no claim', () => {
244
+ // A broken runner must not take the grade down with it; the pre-task
245
+ // behaviour (full suspicion, no provenance) is the safe degradation.
246
+ const m = prov.verifyGeneratedProvenance({
247
+ changedFiles: ['CLAUDE.md'],
248
+ run: () => { throw Object.assign(new Error('boom'), { code: 'EACCES' }); },
249
+ });
250
+ assert.ok(m, 'returns a manifest rather than throwing');
251
+ // A throwing check is a FAILED check, so the region is unverified, not exempt.
252
+ assert.deepEqual(m.verified, []);
253
+ assert.equal(m.unverified.length, 1);
254
+ });
255
+
256
+ test('the shared worker prompt renders a manifest carried on the bundle, and spawns nothing', () => {
257
+ const { buildWorkerPrompt } = require(path.join(ROOT, 'modules/grading/grader-workers/worker-prompt.js'));
258
+ const manifest = prov.verifyGeneratedProvenance({ changedFiles: ['CLAUDE.md'], run: () => {} });
259
+ const base = {
260
+ header: 'H', task: { id: 1, title: 't', description: 'd' }, valueSummary: 'v',
261
+ diff: 'diff --git a/CLAUDE.md b/CLAUDE.md', changedFiles: ['CLAUDE.md'],
262
+ taskHeader: '## Task', descLabel: '**D:**', emptyFilesText: '(none)',
263
+ readFilesHint: 'hint', finalInstruction: 'go',
264
+ };
265
+ const withProv = buildWorkerPrompt({ ...base, contextBundle: { files: [], skipped: [], generatedProvenance: manifest } });
266
+ assert.match(withProv, /Generated content in this diff/);
267
+ assert.match(withProv, /Do not report changes in these regions as hand-edits/);
268
+
269
+ // No manifest on the bundle → prompt byte-identical to before this task.
270
+ const without = buildWorkerPrompt({ ...base, contextBundle: { files: [], skipped: [] } });
271
+ assert.doesNotMatch(without, /Generated content in this diff/);
272
+ });
273
+
274
+ test('the generalist (trivial-path) prompt renders it too — that is where a docs-only ship lands', () => {
275
+ const grader = require(path.join(ROOT, 'modules/grading/grader.js'));
276
+ const manifest = prov.verifyGeneratedProvenance({ changedFiles: ['CLAUDE.md'], run: () => {} });
277
+ const base = {
278
+ task: { id: 1, title: 't', description: 'd' }, valueSummary: 'v', diff: 'd',
279
+ diffStats: { files_changed: 1, lines_added: 3, lines_removed: 3 },
280
+ };
281
+ const p = grader.buildPrompt({ ...base, changedFiles: ['CLAUDE.md'], generatedProvenance: manifest });
282
+ assert.match(p, /Do not report changes in these regions as hand-edits/);
283
+ const plain = grader.buildPrompt({ ...base, changedFiles: ['src/x.js'] });
284
+ assert.doesNotMatch(plain, /Generated content in this diff/);
285
+ });
286
+
287
+ test('grader.js verifies once per grade, OFF the prompt-construction path', () => {
288
+ // The finding this answers: a blocking spawn during prompt construction would
289
+ // stall other concurrent grading. Both grade paths must await the ASYNC
290
+ // verifier and hand the finished manifest to the prompt builders.
291
+ const src = fs.readFileSync(path.join(ROOT, 'modules/grading/grader.js'), 'utf8');
292
+ const sites = [...src.matchAll(/generatedProvenance\.verifyGeneratedProvenanceAsync\(/g)];
293
+ assert.equal(sites.length, 2, 'the panel path and the trivial path each verify once, via the ASYNC verifier');
294
+ assert.doesNotMatch(src, /verifyGeneratedProvenance\(\{/, 'grader.js must not call the BLOCKING sync verifier');
295
+ // And the prompt builders must not spawn: worker-prompt only reads the bundle.
296
+ const wp = fs.readFileSync(path.join(ROOT, 'modules/grading/grader-workers/worker-prompt.js'), 'utf8');
297
+ assert.doesNotMatch(wp, /verifyGeneratedProvenance/, 'the prompt scaffold must only RENDER, never verify');
298
+ });
299
+
300
+ // ---- the circularity guard (panel blocker) ----
301
+
302
+ test('verification runs ONLY for a diff that introduces no executable file', () => {
303
+ // Round 2 guarded only the generator scripts; the panel correctly called that
304
+ // partial, because a generator also requires other repo modules. The sound
305
+ // line: if nothing in the diff can RUN, running a generator cannot execute
306
+ // anything the diff added — no require-chain to reason about.
307
+ assert.ok(!prov.diffCarriesExecutable(['CLAUDE.md', 'docs/session-logs/x.md']), 'the motivating case stays eligible');
308
+ assert.ok(!prov.diffCarriesExecutable(['docs/api/openapi.json', 'docs/api-reference.md']));
309
+ assert.ok(prov.diffCarriesExecutable(['scripts/gds/gen-repo-map.js']), 'the generator itself');
310
+ assert.ok(prov.diffCarriesExecutable(['CLAUDE.md', 'modules/lifecycle/db.js']), 'anything a generator might require');
311
+ assert.ok(prov.diffCarriesExecutable(['CLAUDE.md', 'deploy.sh']), 'shell too');
312
+ assert.ok(prov.diffCarriesExecutable(['CLAUDE.md', 'tests/x.mjs']));
313
+ assert.ok(prov.diffCarriesExecutable(['Dockerfile']), 'unknown/extension-less paths fail CLOSED');
314
+ });
315
+
316
+ test('an ineligible diff SPAWNS NOTHING and claims nothing', async () => {
317
+ let ran = 0;
318
+ const files = ['CLAUDE.md', 'modules/lifecycle/db.js'];
319
+ const sync = prov.verifyGeneratedProvenance({ changedFiles: files, run: () => { ran++; } });
320
+ const asyncM = await prov.verifyGeneratedProvenanceAsync({ changedFiles: files, run: async () => { ran++; } });
321
+ assert.equal(ran, 0, 'no generator is executed beside code the diff introduced');
322
+ for (const m of [sync, asyncM]) {
323
+ assert.deepEqual(m.verified, [], 'no verified claim');
324
+ assert.deepEqual(m.unverified, [], 'and no unverified entry either — silence, not a verdict');
325
+ assert.equal(prov.provenanceSection(m), '', 'the panel keeps full suspicion, exactly as before this feature');
326
+ }
327
+ });
328
+
329
+ test('both spawn sites pass a minimal env allowlist — a generator needs no credentials', () => {
330
+ const src = fs.readFileSync(path.join(ROOT, 'modules/grading/generated-provenance.js'), 'utf8');
331
+ const spawns = [...src.matchAll(/execFile(?:Sync)?\(/g)];
332
+ assert.equal(spawns.length, 2);
333
+ for (const m of spawns) {
334
+ const window = src.slice(m.index, m.index + 340);
335
+ assert.match(window, /env: SPAWN_ENV/, 'every spawn site must pass the allowlisted env');
336
+ assert.match(window, /timeout: VERIFY_TIMEOUT_MS/, 'and stay timeout-bounded');
337
+ }
338
+ // The allowlist must not carry anything credential-shaped.
339
+ const list = src.slice(src.indexOf('const SPAWN_ENV'), src.indexOf('const SPAWN_ENV') + 400);
340
+ assert.doesNotMatch(list, /TOKEN|KEY|SECRET|PASSWORD|DATABASE_URL|LD_PRELOAD/i);
341
+ });
342
+
343
+ test('the implicated checks run concurrently, not in series', async () => {
344
+ let inFlight = 0;
345
+ let maxInFlight = 0;
346
+ await prov.verifyGeneratedProvenanceAsync({
347
+ changedFiles: ['CLAUDE.md', 'docs/api/openapi.json', 'docs/file-map.md'],
348
+ run: async () => {
349
+ inFlight += 1;
350
+ maxInFlight = Math.max(maxInFlight, inFlight);
351
+ await new Promise((r) => setImmediate(r));
352
+ inFlight -= 1;
353
+ },
354
+ });
355
+ assert.ok(maxInFlight > 1, `expected overlapping checks, saw max ${maxInFlight} in flight`);
356
+ });
357
+
358
+ test('the bundle read and the verification run CONCURRENTLY, not in series', () => {
359
+ const src = fs.readFileSync(path.join(ROOT, 'modules/grading/grader.js'), 'utf8');
360
+ const line = src.split('\n').find((l) => l.includes('verifyGeneratedProvenanceAsync') && l.includes('buildContextBundle'));
361
+ assert.ok(line, 'the panel path builds the bundle and verifies on one statement');
362
+ assert.match(line, /Promise\.all/, 'two independent reads must not be serialized on the grade path');
363
+ });