From db36cf2efcd474979f195dde2bc6ae0249cdde9d Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl <4+codex-bot-andresmgsl@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:23:51 +0000 Subject: [PATCH] Fix governance roster and mirror claims --- .ceremony/README.md | 11 +++++------ .github/labels.conf | 4 ++-- scripts/check-governance.js | 2 +- test/governance.test.js | 31 ++++++++++++++++++++++++------- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/.ceremony/README.md b/.ceremony/README.md index 75d701a..8efcf24 100644 --- a/.ceremony/README.md +++ b/.ceremony/README.md @@ -1,11 +1,10 @@ # .ceremony/ — the vendored doctrine mirror -Machine-managed by heavy-duty/ceremony's `actions/docs-sync`. Never edit -these files here: they are byte-identical copies of -[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony) at this -repository's pinned ref, and CI re-diffs them on every PR — a hand edit -goes red. They are changed in heavy-duty/ceremony, through its own flow, -and arrive here when the pin moves. +Never edit these files ad hoc. The six manifest-listed doctrine files are +byte-identical copies of +[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony) at 0.6.1, but +stoke does not run `docs-sync` or re-diff the mirror in CI. Change doctrine +upstream through its own flow, then re-vendor it here when the pin moves. The labels doctrine is vendored manually from heavy-duty/ceremony at 0.6.1 because stoke keeps its own tag-driven `.forgejo/workflows/release.yml`. diff --git a/.github/labels.conf b/.github/labels.conf index dced10a..cd4c22b 100644 --- a/.github/labels.conf +++ b/.github/labels.conf @@ -1,5 +1,5 @@ -panel=codex-bot-andresmgsl glm-bot-andresmgsl cluade-bot-andresmgsl kimi-bot-andresmgsl -triage-actors=cluade-bot-andresmgsl +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 diff --git a/scripts/check-governance.js b/scripts/check-governance.js index 22a4951..ed50139 100644 --- a/scripts/check-governance.js +++ b/scripts/check-governance.js @@ -60,7 +60,7 @@ async function validateIdentities(apiUrl, identities) { const endpoint = `${apiUrl.replace(/\/$/, '')}/users/${encodeURIComponent(login)}`; let response; try { - response = await fetch(endpoint); + response = await fetch(endpoint, { redirect: 'manual' }); } catch (error) { throw new Error(`${login}: fetch failed: ${error.message}`); } diff --git a/test/governance.test.js b/test/governance.test.js index 1cf1341..62eca37 100644 --- a/test/governance.test.js +++ b/test/governance.test.js @@ -37,9 +37,14 @@ function runValidator(config, apiUrl) { }); } -async function withIdentityServer(logins, callback) { +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' })); }); @@ -53,8 +58,8 @@ async function withIdentityServer(logins, callback) { } const validConfig = [ - 'panel=codex-bot-andresmgsl glm-bot-andresmgsl cluade-bot-andresmgsl kimi-bot-andresmgsl', - 'triage-actors=cluade-bot-andresmgsl', + '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', @@ -64,7 +69,7 @@ const validConfig = [ 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', 'cluade-bot-andresmgsl', 'kimi-bot-andresmgsl']); + 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); @@ -74,7 +79,7 @@ test('governance validator accepts the configured roster when every identity res 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', 'cluade-bot-andresmgsl', 'kimi-bot-andresmgsl']); + 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); @@ -82,6 +87,17 @@ test('governance validator fails when a roster identity does not resolve', async }); }); +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'); @@ -90,8 +106,8 @@ test('governance validator rejects malformed scope rows before identity requests assert.doesNotMatch(result.stderr, /fetch failed/); }); -test('repository governance config resolves the four-member panel and five scopes', async () => { - const logins = new Set(['codex-bot-andresmgsl', 'glm-bot-andresmgsl', 'cluade-bot-andresmgsl', 'kimi-bot-andresmgsl']); +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); @@ -123,5 +139,6 @@ test('repository carries the complete 0.6.1 doctrine mirror and root router', () const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8'); 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/); assert.match(fs.readFileSync(ROOT_AGENTS, 'utf8'), /read\s+`.ceremony\/AGENTS\.md` first/i); });