* fix(2119): single SECURITY.md writer — auditor is return-only The gsd-security-auditor held Write/Edit and was instructed to write SECURITY.md (no <N>- prefix, no template frontmatter), while the orchestrator's Step 6 also wrote the correct padded <N>-SECURITY.md from templates/SECURITY.md. Two writers, two naming conventions, two shapes — the auditor's unprefixed file was invisible to the workflow's *-SECURITY.md glob detector and unparseable for the threats_open gate. Fix (option 1 from the issue): make the auditor return-only. - Remove Write/Edit from auditor's tools - Rewrite all 'Write SECURITY.md' instructions to 'Return structured verdict' with threats_open count - Add explicit constraint in workflow Step 5 spawn prompt - Update existing test (was asserting Write in tools — now asserts absence) - Add new regression test for single-writer contract - Update docs/AGENTS.md stale Tools/Produces rows - Regenerate golden fixtures + agent size baseline * docs(changeset): backfill PR number (#2154) * chore(#2119): regenerate pi/qwen golden fixtures after next merge The single-writer change edits gsd-core/workflows/secure-phase.md and agents/gsd-security-auditor.md; pi.json (added on next) and qwen.json (merge straggler) were the only runtime fixtures still holding pre-change hashes for those files. All other runtimes already reflect the change. Regenerated via the sanctioned gen-golden-install-parity script. * merge origin/next — regenerate goldens + baseline for merged state * fix slash-command syntax: /gsd-secure-phase → /gsd:secure-phase (#2154 CI fix)
This commit is contained in:
73
tests/secure-phase-single-writer.test.cjs
Normal file
73
tests/secure-phase-single-writer.test.cjs
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Regression test for #2119: /gsd-secure-phase dual SECURITY.md writers.
|
||||
*
|
||||
* The gsd-security-auditor agent must NOT have Write/Edit tools — the
|
||||
* orchestrator (secure-phase.md Step 6) is the sole SECURITY.md writer.
|
||||
* The auditor returns a structured verdict; it never writes files.
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const AGENT_PATH = path.join(__dirname, '..', 'agents', 'gsd-security-auditor.md');
|
||||
|
||||
function parseYamlTools(content) {
|
||||
const lines = content.split(/\r?\n/);
|
||||
let inFrontmatter = false;
|
||||
let inTools = false;
|
||||
const tools = [];
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed === '---') {
|
||||
inFrontmatter = !inFrontmatter;
|
||||
if (!inFrontmatter) break;
|
||||
continue;
|
||||
}
|
||||
if (!inFrontmatter) continue;
|
||||
if (trimmed.startsWith('tools:')) {
|
||||
inTools = true;
|
||||
continue;
|
||||
}
|
||||
if (inTools) {
|
||||
if (trimmed.startsWith('- ')) {
|
||||
tools.push(trimmed.slice(2).trim());
|
||||
} else if (trimmed && !trimmed.startsWith('#')) {
|
||||
inTools = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
|
||||
describe('#2119 — security auditor is return-only (no file writes)', () => {
|
||||
const content = fs.readFileSync(AGENT_PATH, 'utf-8');
|
||||
|
||||
test('auditor tools do not include Write or Edit', () => {
|
||||
const tools = parseYamlTools(content);
|
||||
assert.ok(tools.length > 0, 'tools list should be non-empty');
|
||||
assert.ok(
|
||||
!tools.includes('Write'),
|
||||
`Write must not be in auditor tools (got: ${tools.join(', ')}) — orchestrator is the sole SECURITY.md writer (#2119)`,
|
||||
);
|
||||
assert.ok(
|
||||
!tools.includes('Edit'),
|
||||
`Edit must not be in auditor tools (got: ${tools.join(', ')}) — orchestrator is the sole SECURITY.md writer (#2119)`,
|
||||
);
|
||||
});
|
||||
|
||||
test('auditor description does not claim to produce SECURITY.md', () => {
|
||||
const lines = content.split(/\r?\n/);
|
||||
const descLine = lines.find((l) => l.startsWith('description:'));
|
||||
assert.ok(descLine, 'description field must exist');
|
||||
assert.ok(
|
||||
!descLine.includes('Produces SECURITY.md'),
|
||||
'description must not claim to produce SECURITY.md — auditor returns a verdict, orchestrator writes (#2119)',
|
||||
);
|
||||
assert.ok(
|
||||
descLine.includes('Returns structured') || descLine.includes('returns'),
|
||||
'description should state the auditor returns a structured verdict',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user