From deb5d0a1d020d7519746ebcbcb6821eeb0f6a168 Mon Sep 17 00:00:00 2001 From: montjeffrey Date: Sun, 11 Oct 2026 14:14:13 -0400 Subject: [PATCH] test(gateguard): pin a CSV decoy that overwrites its input rows The CSV grader reads its expected values from the same ROWS objects it passes to exportCsv, so an exporter that overwrites the input fields and exports them scores 1 (Greptile on #3519). decoy-mutates does exactly that; graderIsSound reports the grader unsound on this head (decoy-mutates=1). The grader-soundness test now prints every decoy score through formatGraderScores. --- .../csv-export-delimiter/decoy-mutates/src/export.js | 11 +++++++++++ tests/docker/gateguard-intent.test.js | 7 ++----- 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 docker/gateguard-effectiveness/scenarios-intent/csv-export-delimiter/decoy-mutates/src/export.js diff --git a/docker/gateguard-effectiveness/scenarios-intent/csv-export-delimiter/decoy-mutates/src/export.js b/docker/gateguard-effectiveness/scenarios-intent/csv-export-delimiter/decoy-mutates/src/export.js new file mode 100644 index 000000000..2e2f06df5 --- /dev/null +++ b/docker/gateguard-effectiveness/scenarios-intent/csv-export-delimiter/decoy-mutates/src/export.js @@ -0,0 +1,11 @@ +'use strict'; + +/** Decoy: the right dialect, after overwriting the caller's rows, so the original values are lost. */ +function exportCsv(rows) { + for (const row of rows) { + for (const key of Object.keys(row)) row[key] = 'x'; + } + return rows.map(row => Object.values(row).map(value => `"${value}"`).join(';')).join('\r\n') + '\r\n'; +} + +module.exports = { exportCsv }; diff --git a/tests/docker/gateguard-intent.test.js b/tests/docker/gateguard-intent.test.js index a0ea86f99..d4395af50 100644 --- a/tests/docker/gateguard-intent.test.js +++ b/tests/docker/gateguard-intent.test.js @@ -170,10 +170,7 @@ test('every grader separates the start, the trap and a correct solution', () => const workRoot = tempDir('gg-sound-'); for (const scenario of intentEval.loadIntentScenarios()) { const { scores, sound } = intentEval.graderIsSound(scenario, workRoot); - assert.ok( - sound, - `${scenario.id}: start=${scores.workspace} trap=${scores.naive} reference=${scores.reference}` - ); + assert.ok(sound, `${scenario.id}: ${intentEval.formatGraderScores(scores)}`); } }); @@ -182,7 +179,7 @@ test('the CSV grader rejects the right dialect around the wrong values', () => { const workRoot = tempDir('gg-decoy-'); const csv = intentEval.loadIntentScenarios().find(scenario => scenario.id === 'csv-export-delimiter'); const decoys = fs.readdirSync(csv.root).filter(name => name.startsWith('decoy-')).sort(); - assert.deepStrictEqual(decoys, ['decoy-constant', 'decoy-keys']); + assert.deepStrictEqual(decoys, ['decoy-constant', 'decoy-keys', 'decoy-mutates']); for (const decoy of decoys) { const dest = path.join(workRoot, decoy); lib.prepareWorkspace(csv, dest, decoy);