Source-candidate: poe d9a564e6d20f5f0fd75ce45b12cbbf8bb4c9d568\nSource-candidate: magica38ba5762b06716db1dee00d0afec162656488f5c\nValidated321whole-file phase plus whole bus1349/model-proxy100/webui490; final gate tooling checks pending, not landed.
87 lines
5.1 KiB
JavaScript
87 lines
5.1 KiB
JavaScript
// B1/647: discovery changes while real retirement HTTP is in flight.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import http from 'node:http';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { createHash, randomBytes } from 'node:crypto';
|
|
import { Credentials } from '../src/credentials.mjs';
|
|
import { BusError } from '../src/broker.mjs';
|
|
import { ModelKeys } from '../src/model-keys.mjs';
|
|
import { ManagementClient } from '../../model-proxy/src/client.mjs';
|
|
const id = value => createHash('sha256').update(value).digest('hex').slice(0, 12);
|
|
|
|
async function fixture(t, boundary) {
|
|
const root = fs.mkdtempSync(join(tmpdir(), 'b1-boundary-'));
|
|
let server, credentials;
|
|
t.after(async () => {
|
|
try { credentials?.close(); }
|
|
finally {
|
|
server?.closeAllConnections();
|
|
if (server?.listening) await new Promise(resolve => server.close(resolve));
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
const configDir = join(root, 'config'), storeRoot = join(configDir, 'secrets', 'agents');
|
|
fs.mkdirSync(storeRoot, { recursive: true, mode: 0o700 });
|
|
const sentinel = `msk-${id(configDir)}-sentinel-${randomBytes(32).toString('base64url')}`;
|
|
const state = { keys: [sentinel], armed: false, discoveryFailed: false, calls: [], faults: 0, refusals: 0 };
|
|
const receipts = [];
|
|
server = http.createServer(async (req, res) => {
|
|
const management = req.url === '/v8/management/config/access/api-keys';
|
|
state.calls.push(management ? req.method : 'PROBE');
|
|
if (management) {
|
|
if (req.headers.authorization !== 'Bearer fx647-manage') { res.writeHead(401); res.end('{}'); return; }
|
|
if (req.method === 'GET') {
|
|
if (state.armed && boundary === 'management-GET' && !state.discoveryFailed) { state.discoveryFailed = true; state.faults++; }
|
|
res.end(JSON.stringify(state.keys)); return;
|
|
}
|
|
const chunks = []; for await (const chunk of req) chunks.push(chunk);
|
|
state.keys = JSON.parse(Buffer.concat(chunks));
|
|
res.end(JSON.stringify({ status: 'ok' })); return;
|
|
}
|
|
const status = state.keys.includes(req.headers.authorization?.slice(7)) ? 200 : 401;
|
|
if (state.armed && boundary === 'confirmed-401' && status === 401) { state.discoveryFailed = true; state.faults++; }
|
|
res.writeHead(status); res.end('{}');
|
|
});
|
|
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
credentials = new Credentials({ storeRoot, dataRoot: join(root, 'data'), modelAgents: ['demo/chief'] });
|
|
const manager = new ModelKeys({ credentials, configDir, baseUrl,
|
|
client: new ManagementClient({ baseUrl, key: 'fx647-manage', receipt: entry => receipts.push(entry) }),
|
|
receipt: entry => receipts.push(entry),
|
|
// Fault models authoritative discovery becoming uncertain. Do not replace the
|
|
// controller, mutation method, HTTP operation or private-record implementation.
|
|
runs: () => { if (state.discoveryFailed) { state.refusals++; throw new BusError('proxy-stop-failed'); } return []; } });
|
|
await manager.reconcile(); const first = await manager.ensure('demo/chief'); await manager.rotate('demo/chief');
|
|
state.calls.length = 0; receipts.length = 0; state.armed = true;
|
|
return { state, receipts, credentials, manager, oldKey: first.key, oldId: id(first.key),
|
|
retained: () => assert.equal(credentials.modelRecord('demo/chief').retiring.some(r => r.keyId === id(first.key)), true, 'local retiring record retained'),
|
|
valueFree: () => {
|
|
const output = JSON.stringify(receipts);
|
|
assert.equal(output.includes(first.key), false);
|
|
assert.equal(output.includes('fx647-manage'), false);
|
|
assert.equal(output.includes(credentials.modelRecord('demo/chief').key), false);
|
|
} };
|
|
}
|
|
|
|
test('B1 discovery failure during fresh management GET refuses before retirement PUT', async t => {
|
|
const f = await fixture(t, 'management-GET');
|
|
await assert.rejects(f.manager.sweep(), error => error.code === 'proxy-stop-failed', 'fresh discovery refusal required before remote mutation');
|
|
assert.equal(f.state.faults, 1); assert.equal(f.state.refusals, 1);
|
|
assert.deepEqual(f.state.calls, ['GET'], 'no removal PUT or extra mutation after discovery failure');
|
|
assert.equal(f.state.keys.includes(f.oldKey), true, 'remote old key retained');
|
|
f.retained(); f.valueFree();
|
|
});
|
|
|
|
test('B1 discovery failure during confirmed401 refuses before retiring metadata deletion', async t => {
|
|
const f = await fixture(t, 'confirmed-401');
|
|
await assert.rejects(f.manager.sweep(), error => error.code === 'proxy-stop-failed', 'fresh discovery refusal required before local deletion');
|
|
assert.equal(f.state.faults, 1); assert.equal(f.state.refusals, 1);
|
|
assert.deepEqual(f.state.calls, ['GET', 'PUT', 'GET', 'PROBE'], 'one remote revoke, no rollback or extra mutation');
|
|
assert.equal(f.state.keys.includes(f.oldKey), false, 'remote key already revoked; no rollback claim');
|
|
assert.ok(f.receipts.some(entry => entry.call === 'GET /v1/models' && entry.status === 401 && entry.keyIds.includes(f.oldId)), 'observed remote401 receipt');
|
|
f.retained(); f.valueFree();
|
|
});
|