'use strict'; // allow-test-rule: source-text-is-product [#3212] // The bug is in workflow/config contracts consumed by agents at runtime. const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); const { spawnSync } = require('node:child_process'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { cleanup } = require('./helpers.cjs'); const ROOT = path.join(__dirname, '..'); function read(relativePath) { return fs.readFileSync(path.join(ROOT, relativePath), 'utf8'); } function runGsd(args, cwd) { return spawnSync(process.execPath, [path.join(ROOT, 'gsd-core/bin/gsd-tools.cjs'), ...args], { cwd, encoding: 'utf8', }); } describe('bug #3212 execute-phase stall detection and safe resume', () => { test('config schemas register executor stall detector keys', () => { // After Cycle 5 (#3536), both CJS and SDK source from the manifest. // Use the CJS runtime Set for CJS; use the manifest directly for SDK-side // verification (since config-schema.ts no longer has inline literals). const { VALID_CONFIG_KEYS: cjsKeys } = require('../gsd-core/bin/lib/config-schema.cjs'); const manifest = JSON.parse(read('gsd-core/bin/shared/config-schema.manifest.json')); const manifestKeys = new Set(manifest.validKeys); for (const key of ['executor.stall_detect_interval_minutes', 'executor.stall_threshold_minutes']) { assert.ok(cjsKeys.has(key), `CJS VALID_CONFIG_KEYS must include ${key}`); assert.ok(manifestKeys.has(key), `Manifest validKeys must include ${key} (SDK sources from manifest)`); } }); test('configuration docs describe stall detector defaults', () => { const docs = read('docs/CONFIGURATION.md'); assert.match(docs, /`executor\.stall_detect_interval_minutes`\s*\|\s*number\s*\|\s*`5`/); assert.match(docs, /`executor\.stall_threshold_minutes`\s*\|\s*number\s*\|\s*`10`/); }); test('config-get returns schema defaults for executor stall detector keys', (t) => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3212-')); t.after(() => cleanup(tmp)); fs.mkdirSync(path.join(tmp, '.planning')); fs.writeFileSync(path.join(tmp, '.planning/config.json'), '{}\n'); const interval = runGsd(['config-get', 'executor.stall_detect_interval_minutes', '--raw'], tmp); const threshold = runGsd(['config-get', 'executor.stall_threshold_minutes', '--raw'], tmp); assert.equal(interval.status, 0, interval.stderr); assert.equal(interval.stdout.trim(), '5'); assert.equal(threshold.status, 0, threshold.stderr); assert.equal(threshold.stdout.trim(), '10'); }); test('execute-phase verifies partial-plan drift before dispatch', () => { const workflow = read('gsd-core/workflows/execute-phase.md'); assert.match(workflow, / { const workflow = read('gsd-core/workflows/execute-phase.md'); assert.match(workflow, /EXECUTOR_STALL_INTERVAL_MINUTES=.*executor\.stall_detect_interval_minutes/); assert.match(workflow, /EXECUTOR_STALL_THRESHOLD_MINUTES=.*executor\.stall_threshold_minutes/); assert.match(workflow, /DISPATCH_TS=/, 'execute-phase must record dispatch timestamp'); assert.match(workflow, /EXPECTED_BRANCH=/, 'execute-phase must record expected branch'); assert.match(workflow, /git log "\$\{EXPECTED_BRANCH\}" --since="\$\{DISPATCH_TS\}"/, 'stall check must inspect branch commits since dispatch'); assert.match(workflow, /continue waiting/, 'stall warning must offer continue waiting'); assert.match(workflow, /kill and retry/, 'stall warning must offer kill and retry'); assert.match(workflow, /kill and switch to inline execution/, 'stall warning must offer inline fallback'); }); test('execute-plan documents atomic close-out invariant', () => { const workflow = read('gsd-core/workflows/execute-plan.md'); assert.match(workflow, //, 'execute-plan must contain a formal atomic close-out invariant'); assert.match(workflow, /production-code commit\(s\) -> SUMMARY commit -> STATE\/ROADMAP update/, 'invariant must name the legal close-out sequence'); assert.match(workflow, /only legal half-state is mid-production-commits/, 'invariant must define the only legal half-state'); }); test('forensics includes the partial-plan drift detector', () => { const workflow = read('gsd-core/workflows/forensics.md'); assert.match(workflow, /Partial-plan Drift Detection/); assert.match(workflow, /commits exist but SUMMARY.md is missing/); assert.match(workflow, /safe-resume verifier/); }); });