forked from heavy-duty/stoke
Fix governance roster and mirror claims
This commit is contained in:
parent
47aed6f1de
commit
db36cf2efc
4 changed files with 32 additions and 16 deletions
|
|
@ -1,11 +1,10 @@
|
||||||
# .ceremony/ — the vendored doctrine mirror
|
# .ceremony/ — the vendored doctrine mirror
|
||||||
|
|
||||||
Machine-managed by heavy-duty/ceremony's `actions/docs-sync`. Never edit
|
Never edit these files ad hoc. The six manifest-listed doctrine files are
|
||||||
these files here: they are byte-identical copies of
|
byte-identical copies of
|
||||||
[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony) at this
|
[heavy-duty/ceremony](https://github.com/heavy-duty/ceremony) at 0.6.1, but
|
||||||
repository's pinned ref, and CI re-diffs them on every PR — a hand edit
|
stoke does not run `docs-sync` or re-diff the mirror in CI. Change doctrine
|
||||||
goes red. They are changed in heavy-duty/ceremony, through its own flow,
|
upstream through its own flow, then re-vendor it here when the pin moves.
|
||||||
and arrive here when the pin moves.
|
|
||||||
|
|
||||||
The labels doctrine is vendored manually from heavy-duty/ceremony at 0.6.1
|
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`.
|
because stoke keeps its own tag-driven `.forgejo/workflows/release.yml`.
|
||||||
|
|
|
||||||
4
.github/labels.conf
vendored
4
.github/labels.conf
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
panel=codex-bot-andresmgsl glm-bot-andresmgsl cluade-bot-andresmgsl kimi-bot-andresmgsl
|
panel=codex-bot-andresmgsl glm-bot-andresmgsl claude-bot-andresmgsl kimi-bot-andresmgsl
|
||||||
triage-actors=cluade-bot-andresmgsl
|
triage-actors=claude-bot-andresmgsl
|
||||||
scope:cli|C5DEF5|src/ — the command surface (cli.js, api.js, config.js)
|
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:packaging|C5DEF5|scripts/ and the release workflow — deb build, registry publish, apt install path
|
||||||
scope:manifests|C5DEF5|manifests/ — the fleet repo registry data
|
scope:manifests|C5DEF5|manifests/ — the fleet repo registry data
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ async function validateIdentities(apiUrl, identities) {
|
||||||
const endpoint = `${apiUrl.replace(/\/$/, '')}/users/${encodeURIComponent(login)}`;
|
const endpoint = `${apiUrl.replace(/\/$/, '')}/users/${encodeURIComponent(login)}`;
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(endpoint);
|
response = await fetch(endpoint, { redirect: 'manual' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`${login}: fetch failed: ${error.message}`);
|
throw new Error(`${login}: fetch failed: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 server = http.createServer((request, response) => {
|
||||||
const login = decodeURIComponent(request.url.replace('/api/v1/users/', ''));
|
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.writeHead(logins.has(login) ? 200 : 404, { 'content-type': 'application/json' });
|
||||||
response.end(JSON.stringify(logins.has(login) ? { login } : { message: 'not found' }));
|
response.end(JSON.stringify(logins.has(login) ? { login } : { message: 'not found' }));
|
||||||
});
|
});
|
||||||
|
|
@ -53,8 +58,8 @@ async function withIdentityServer(logins, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const validConfig = [
|
const validConfig = [
|
||||||
'panel=codex-bot-andresmgsl glm-bot-andresmgsl cluade-bot-andresmgsl kimi-bot-andresmgsl',
|
'panel=codex-bot-andresmgsl glm-bot-andresmgsl claude-bot-andresmgsl kimi-bot-andresmgsl',
|
||||||
'triage-actors=cluade-bot-andresmgsl',
|
'triage-actors=claude-bot-andresmgsl',
|
||||||
'scope:cli|C5DEF5|src/ — the command surface (cli.js, api.js, config.js)',
|
'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:packaging|C5DEF5|scripts/ and the release workflow — deb build, registry publish, apt install path',
|
||||||
'scope:manifests|C5DEF5|manifests/ — the fleet repo registry data',
|
'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 () => {
|
test('governance validator accepts the configured roster when every identity resolves', async () => {
|
||||||
const config = writeConfig(`${validConfig}\n`);
|
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) => {
|
await withIdentityServer(logins, async (apiUrl) => {
|
||||||
const result = await runValidator(config, apiUrl);
|
const result = await runValidator(config, apiUrl);
|
||||||
assert.equal(result.status, 0, result.stderr);
|
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 () => {
|
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 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) => {
|
await withIdentityServer(logins, async (apiUrl) => {
|
||||||
const result = await runValidator(config, apiUrl);
|
const result = await runValidator(config, apiUrl);
|
||||||
assert.notEqual(result.status, 0);
|
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 () => {
|
test('governance validator rejects malformed scope rows before identity requests', async () => {
|
||||||
const config = writeConfig(`${validConfig.replace('|C5DEF5|', '|not-a-color|')}\n`);
|
const config = writeConfig(`${validConfig.replace('|C5DEF5|', '|not-a-color|')}\n`);
|
||||||
const result = await runValidator(config, 'http://127.0.0.1:1/api/v1');
|
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/);
|
assert.doesNotMatch(result.stderr, /fetch failed/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('repository governance config resolves the four-member panel and five scopes', async () => {
|
test('repository governance config resolves the current four-member panel and five scopes', async () => {
|
||||||
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) => {
|
await withIdentityServer(logins, async (apiUrl) => {
|
||||||
const result = await runValidator(REPOSITORY_CONFIG, apiUrl);
|
const result = await runValidator(REPOSITORY_CONFIG, apiUrl);
|
||||||
assert.equal(result.status, 0, result.stderr);
|
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');
|
const mirrorReadme = fs.readFileSync(path.join(REPOSITORY_MIRROR, 'README.md'), 'utf8');
|
||||||
assert.match(mirrorReadme, /labels doctrine is vendored manually/);
|
assert.match(mirrorReadme, /labels doctrine is vendored manually/);
|
||||||
assert.doesNotMatch(mirrorReadme, /The pin lives in `.github\/workflows\/release\.yml`/);
|
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);
|
assert.match(fs.readFileSync(ROOT_AGENTS, 'utf8'), /read\s+`.ceremony\/AGENTS\.md` first/i);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue