const { test } = require('node:test'); const assert = require('node:assert/strict'); const { spawn } = require('node:child_process'); const fs = require('node:fs'); const http = require('node:http'); const os = require('node:os'); const path = require('node:path'); const SCRIPT = path.join(__dirname, '..', 'scripts', 'check-governance.js'); const REPOSITORY_CONFIG = path.join(__dirname, '..', '.github', 'labels.conf'); const REPOSITORY_LABELER = path.join(__dirname, '..', '.github', 'labeler.yml'); const REPOSITORY_MIRROR = path.join(__dirname, '..', '.ceremony'); const ROOT_AGENTS = path.join(__dirname, '..', 'AGENTS.md'); const PACKAGE_MANIFEST = path.join(__dirname, '..', 'package.json'); const PACKAGE_LOCK = path.join(__dirname, '..', 'package-lock.json'); const CEREMONY_REPOSITORY = 'https://forgejo.heavyduty.builders/heavy-duty/ceremony'; const CEREMONY_VERSION = '0.6.3'; const CEREMONY_WORKFLOWS = ['labels.yml', 'labels-sweep.yml']; const cleanups = []; process.on('exit', () => { for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); }); function writeConfig(contents) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-governance-test-')); cleanups.push(dir); const config = path.join(dir, 'labels.conf'); fs.writeFileSync(config, contents); return config; } function runValidator(config, apiUrl) { return new Promise((resolve) => { const child = spawn(process.execPath, [SCRIPT, '--config', config, '--api-url', apiUrl], { encoding: 'utf8', }); let stdout = ''; let stderr = ''; child.stdout.on('data', (chunk) => { stdout += chunk; }); child.stderr.on('data', (chunk) => { stderr += chunk; }); child.on('close', (status) => resolve({ status, stdout, stderr })); }); } async function withIdentityServer(logins, callback, redirects = new Map()) { const server = http.createServer((request, response) => { const login = decodeURIComponent(request.url.replace('/api/v1/users/', '')); if (redirects.has(login)) { response.writeHead(307, { location: `/api/v1/users/${redirects.get(login)}` }); response.end(); return; } response.writeHead(logins.has(login) ? 200 : 404, { 'content-type': 'application/json' }); response.end(JSON.stringify(logins.has(login) ? { login } : { message: 'not found' })); }); await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); const { port } = server.address(); try { await callback(`http://127.0.0.1:${port}/api/v1`); } finally { await new Promise((resolve) => server.close(resolve)); } } const validConfig = [ 'panel=codex-bot-andresmgsl glm-bot-andresmgsl claude-bot-andresmgsl kimi-bot-andresmgsl', 'triage-actors=claude-bot-andresmgsl', 'scope:cli|C5DEF5|src/ — the command surface (cli.js, api.js, config.js)', 'scope:packaging|C5DEF5|scripts/ and the release workflow — deb build, registry publish, apt install path', 'scope:manifests|C5DEF5|manifests/ — the fleet repo registry data', 'scope:ci|C5DEF5|.forgejo/workflows/ — the test and label gates', 'scope:docs|C5DEF5|README and docs/ — the prose contract', ].join('\n'); test('governance validator accepts the configured roster when every identity resolves', async () => { const config = writeConfig(`${validConfig}\n`); const logins = new Set(['codex-bot-andresmgsl', 'glm-bot-andresmgsl', 'claude-bot-andresmgsl', 'kimi-bot-andresmgsl']); await withIdentityServer(logins, async (apiUrl) => { const result = await runValidator(config, apiUrl); assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /4 identities resolved; 5 scope rows valid/); }); }); test('governance validator fails when a roster identity does not resolve', async () => { const config = writeConfig(`${validConfig.replace('kimi-bot-andresmgsl', 'kimi-bto-andresmgsl')}\n`); const logins = new Set(['codex-bot-andresmgsl', 'glm-bot-andresmgsl', 'claude-bot-andresmgsl', 'kimi-bot-andresmgsl']); await withIdentityServer(logins, async (apiUrl) => { const result = await runValidator(config, apiUrl); assert.notEqual(result.status, 0); assert.match(result.stderr, /kimi-bto-andresmgsl.*HTTP 404/); }); }); test('governance validator rejects a renamed identity that redirects to a live login', async () => { const config = writeConfig(`${validConfig.replaceAll('claude-bot-andresmgsl', 'cluade-bot-andresmgsl')}\n`); const logins = new Set(['codex-bot-andresmgsl', 'glm-bot-andresmgsl', 'claude-bot-andresmgsl', 'kimi-bot-andresmgsl']); const redirects = new Map([['cluade-bot-andresmgsl', 'claude-bot-andresmgsl']]); await withIdentityServer(logins, async (apiUrl) => { const result = await runValidator(config, apiUrl); assert.notEqual(result.status, 0); assert.match(result.stderr, /cluade-bot-andresmgsl.*HTTP 307/); }, redirects); }); test('governance validator rejects malformed scope rows before identity requests', async () => { const config = writeConfig(`${validConfig.replace('|C5DEF5|', '|not-a-color|')}\n`); const result = await runValidator(config, 'http://127.0.0.1:1/api/v1'); assert.notEqual(result.status, 0); assert.match(result.stderr, /malformed label row/); assert.doesNotMatch(result.stderr, /fetch failed/); }); test('repository governance config resolves the current four-member panel and five scopes', async () => { const logins = new Set(['codex-bot-andresmgsl', 'glm-bot-andresmgsl', 'claude-bot-andresmgsl', 'kimi-bot-andresmgsl']); await withIdentityServer(logins, async (apiUrl) => { const result = await runValidator(REPOSITORY_CONFIG, apiUrl); assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /4 identities resolved; 5 scope rows valid/); }); }); test('repository scope mapping covers every configured scope with the ruled paths', () => { const labeler = fs.readFileSync(REPOSITORY_LABELER, 'utf8'); const expected = { 'scope:cli': ['src/**'], 'scope:packaging': ['scripts/**', '.forgejo/workflows/release.yml'], 'scope:manifests': ['manifests/**'], 'scope:ci': ['.forgejo/workflows/**'], 'scope:docs': ['README.md', 'docs/**'], }; for (const [label, globs] of Object.entries(expected)) { assert.match(labeler, new RegExp(`^"${label}":`, 'm'), `${label} has no mapping`); for (const glob of globs) assert.ok(labeler.includes(JSON.stringify(glob)), `${label} does not map ${glob}`); } }); test('package lock versions match the package manifest', () => { const manifest = JSON.parse(fs.readFileSync(PACKAGE_MANIFEST, 'utf8')); const lock = JSON.parse(fs.readFileSync(PACKAGE_LOCK, 'utf8')); assert.equal(lock.version, manifest.version, 'package-lock.json version is stale'); assert.equal(lock.packages[''].version, manifest.version, 'package-lock.json root package version is stale'); }); test('repository carries the complete Forgejo 0.6.3 doctrine mirror and root router', () => { const vendored = ['AGENTS.md', 'TRIAGE.md', 'BUILDER.md', 'REVIEWER.md', 'LABELS.md', 'RELEASES.md']; for (const filename of vendored) { assert.ok(fs.statSync(path.join(REPOSITORY_MIRROR, filename)).isFile(), `${filename} is missing`); } const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8'); const sourceVersionRecord = `[heavy-duty/ceremony](${CEREMONY_REPOSITORY}) at ${CEREMONY_VERSION}`; assert.equal( mirrorReadme.split(sourceVersionRecord).length - 1, 2, 'mirror README does not identify the exact Forgejo ceremony source and version in both records', ); assert.match(mirrorReadme, /labels doctrine is vendored manually/); assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/); assert.doesNotMatch(mirrorReadme, /Machine-managed by|CI re-diffs them/); const rootAgents = fs.readFileSync(ROOT_AGENTS, 'utf8'); assert.ok( rootAgents.includes(`[heavy-duty/ceremony](${CEREMONY_REPOSITORY})`), 'root router does not identify the Forgejo ceremony repository', ); assert.match(rootAgents, /read\s+`.ceremony\/AGENTS\.md` first/i); }); test('repository workflow pins use the exact Forgejo ceremony version', () => { for (const workflow of CEREMONY_WORKFLOWS) { const contents = fs.readFileSync(path.join(__dirname, '..', '.forgejo', 'workflows', workflow), 'utf8'); const prefix = `uses: heavy-duty/ceremony/.github/workflows/${workflow}@`; const pins = contents.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith(prefix)); assert.deepEqual( pins, [`${prefix}${CEREMONY_VERSION}`], `${workflow} does not pin ceremony ${CEREMONY_VERSION}`, ); } });