feat(bus,webui): bus read of credential status; the Console shows it (#1549, row 60)

The credentials read returns service, account, role, date kind, date and
state for the capability's business, to human and reader sessions only.
It never returns a token, file path or variable name. Author Filbert,
reviewers Dewey (27558) and Darkwing (27575), candidate manifest 13541657.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 19:37:06 -05:00
co-authored by Claude Opus 5.5
parent 6a0d43854a
commit 1771bf98b2
9 changed files with 313 additions and 15 deletions
+25 -1
View File
@@ -126,15 +126,38 @@ test('decisions route serves the roles and recent lists from the reader view', a
assert.equal(calls.length, before);
});
// Row 60 (#1549): what the bus host loaded, metadata only, for the reader's business.
test('credentials route serves the broker credential status from the reader view', async t => {
const store = new Store(tmp(t, 'webui-bus-credentials-'));
t.after(() => store.close());
const status = [
{ instance: 'demo/coder', service: 'gitea', date: '2000-01-01', state: 'rotation-due' },
{ instance: 'other/coder', service: 'gitea', date: '2099-01-01', state: 'valid' },
];
const b = new Broker({ store, businesses, credentialStatus: () => status });
const reader = b.bindReader({ business: 'demo' });
const calls = [];
const bus = views({ call: async (verb, args = {}) => { calls.push([verb, args]); return structuredClone(b.request(reader, { verb, args })); } });
const web = await startServer({ port: 0, board: 'http://127.0.0.1:9', bus });
t.after(() => close(web));
const r = await fetch(`http://127.0.0.1:${web.address().port}/api/bus/credentials`);
assert.equal(r.status, 200); assert.equal(r.headers.get('cache-control'), 'no-store');
const body = await r.json();
assert.match(body.at, /^\d{4}-\d\d-\d\dT/);
assert.deepEqual(body.rows, [{ service: 'gitea', account: null, role: 'coder', dateKind: 'rotateBy', date: '2000-01-01', state: 'rotation-due' }]);
assert.deepEqual(calls, [['credentials', {}]]);
});
test('bus routes are GET-only, have no write verb, and keep the same-origin checks', async t => {
const calls = [];
const bus = Object.fromEntries(['inbox', 'tasks', 'agents', 'trail', 'decisions'].map(v => [v, async () => { calls.push(v); return []; }]));
const bus = Object.fromEntries(['inbox', 'tasks', 'agents', 'trail', 'decisions', 'credentials'].map(v => [v, async () => { calls.push(v); return []; }]));
const web = await startServer({ port: 0, board: 'http://127.0.0.1:9', bus });
t.after(() => close(web));
const base = `http://127.0.0.1:${web.address().port}`;
for (const method of ['POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS']) {
assert.equal((await fetch(base + '/api/bus/inbox', { method })).status, 405, method);
assert.equal((await fetch(base + '/api/bus/decisions?list=roles', { method })).status, 405, method);
assert.equal((await fetch(base + '/api/bus/credentials', { method })).status, 405, method);
}
for (const path of ['/api/bus/decide', '/api/bus/seen', '/api/bus/', '/api/bus/inbox/x', '/api/bus/request', '/api/bus/raise']) {
assert.equal((await fetch(base + path)).status, 404, path);
@@ -219,6 +242,7 @@ test('busReader takes --business, else the live bus host, else refuses', async t
const pinned = busReader({ dataRoot, socket: 'sock', business: 'pinned', cli });
assert.equal((await pinned.inbox()).request.business, 'pinned');
assert.deepEqual((await pinned.decisions('recent')).request, { business: 'pinned', verb: 'decisions', args: { list: 'recent' } });
assert.deepEqual((await pinned.credentials()).request, { business: 'pinned', verb: 'credentials', args: {} });
const reader = busReader({ dataRoot, socket: 'sock', cli });
const code = () => reader.agents().then(() => 'resolved', e => e.code);