rhf-b-kn0-compatibility.test.mjs
429 lines 15.8 KB
Raw
sha256:fbe982a22c05c6fe2e93876f250deecdb43d883f648b4e1810c7546caa9f17db docs: activate KNOWTATION- board identity and preserve livi… Human minor ⚠ breaking 1 day ago
1 /**
2 * RHF-b-KN0 — compatibility hardening (seven-tier).
3 *
4 * Frozen spec: ~/scooling/docs/reviews/2026-08-27-retail-helper-finish.md §RHF-b-KN0
5 */
6
7 import { test, describe } from 'node:test';
8 import assert from 'node:assert/strict';
9 import fs from 'node:fs';
10 import os from 'node:os';
11 import path from 'node:path';
12 import jwt from 'jsonwebtoken';
13 import { performance } from 'node:perf_hooks';
14 import { fileURLToPath } from 'node:url';
15
16 import {
17 AGENT_IDENTITY_SCHEMA_V1,
18 TRUSTED_EXTERNAL_PROVIDER_IDENTITIES,
19 getTrustedCatalogIdentity,
20 isReservedCatalogAgentId,
21 listTrustedCatalogIdentities,
22 } from '../lib/agent/trusted-external-provider-catalog.mjs';
23 import {
24 DELEGATION_AUTHORITY_ENVELOPE_SCHEMA,
25 DELEGATION_AUTHORITY_MARKER_SCHEMA,
26 DELEGATION_AUTHORITY_UNAVAILABLE,
27 computeDelegationAuthorityStateHash,
28 delegationAuthorityMarkerFileName,
29 resolveDelegationAuthorityReadModeSync,
30 validateDelegationAuthorityEnvelope,
31 validateDelegationAuthorityMarker,
32 } from '../lib/agent/delegation-authority-compat.mjs';
33 import {
34 getAgentIdentity,
35 handleAgentIdentityListRequest,
36 handleAgentIdentityRegisterProposeRequest,
37 handleDelegationGrantMintRequest,
38 precheckApprovedDelegationProposal,
39 resolveDelegationReadContext,
40 seedDelegationFixtures,
41 DELEGATION_PROPOSAL_SOURCE,
42 } from '../lib/agent/delegation.mjs';
43 import {
44 makeAgentIdentity,
45 makeDelegationConsent,
46 writeDelegationPolicy,
47 TEST_USER_ID,
48 } from './fixtures/agent/delegation-helpers.mjs';
49
50 const __dirname = path.dirname(fileURLToPath(import.meta.url));
51 const ROOT = path.resolve(__dirname, '..');
52 const DELEGATION_ROUTES_SRC = path.join(ROOT, 'hub/bridge/delegation-routes.mjs');
53 const SESSION_SECRET = 'rhf-kn0-test-session-secret';
54
55 function mkDataDir() {
56 return fs.mkdtempSync(path.join(os.tmpdir(), 'kt-rhf-kn0-'));
57 }
58
59 function enableGate(dataDir) {
60 writeDelegationPolicy(dataDir);
61 process.env.DELEGATION_ENABLED = '1';
62 process.env.SESSION_SECRET = SESSION_SECRET;
63 }
64
65 function sessionToken(sub = 'github:learner-smoke') {
66 return jwt.sign({ sub, type: 'session', role: 'member' }, SESSION_SECRET, { expiresIn: '1h' });
67 }
68
69 function legacySessionToken(sub = 'github:legacy-learner') {
70 return jwt.sign({ sub, role: 'member' }, SESSION_SECRET, { expiresIn: '1h' });
71 }
72
73 function serviceToken(sub = 'github:operator-admin') {
74 return jwt.sign({ sub, type: 'mcp_access', scopes: ['admin'] }, SESSION_SECRET, { expiresIn: '1h' });
75 }
76
77 /**
78 * Pre-KN0 bridge grant mint path — session token reached catalog resolution.
79 *
80 * @param {import('express').Request} req
81 * @returns {boolean}
82 */
83 function legacyBridgeGrantMintWouldAcceptSession(req) {
84 const auth = req.headers.authorization;
85 const token = auth && auth.startsWith('Bearer ') ? auth.slice(7) : null;
86 if (!token) return false;
87 try {
88 jwt.verify(token, SESSION_SECRET);
89 } catch {
90 return false;
91 }
92 return true;
93 }
94
95 /**
96 * @param {string} token
97 * @returns {import('express').Request}
98 */
99 function mockReq(token) {
100 return /** @type {import('express').Request} */ ({
101 headers: { authorization: `Bearer ${token}` },
102 uid: 'github:learner-smoke',
103 });
104 }
105
106 function buildEnvelope(vaultId, overrides = {}) {
107 const base = {
108 schema: DELEGATION_AUTHORITY_ENVELOPE_SCHEMA,
109 schema_version: 1,
110 vault_id: vaultId,
111 lineage_id: 'lineage_test_001',
112 origin_snapshot_hash: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
113 revision: 0,
114 previous_state_hash: null,
115 identities_by_id: {},
116 consents_by_id: {},
117 grants_by_id: {},
118 ...overrides,
119 };
120 base.state_hash = computeDelegationAuthorityStateHash(base);
121 return base;
122 }
123
124 function writeMarkerAndEnvelope(dataDir, vaultId, markerOverrides = {}, envelopeOverrides = {}) {
125 const envelopeKey = `delegation/authority/v1/${vaultId}/envelope`;
126 const marker = {
127 schema: DELEGATION_AUTHORITY_MARKER_SCHEMA,
128 vault_id: vaultId,
129 envelope_key: envelopeKey,
130 envelope_schema_version: 1,
131 lineage_id: 'lineage_test_001',
132 origin_snapshot_hash: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
133 ...markerOverrides,
134 };
135 const envelope = buildEnvelope(vaultId, envelopeOverrides);
136 fs.writeFileSync(path.join(dataDir, delegationAuthorityMarkerFileName(vaultId)), JSON.stringify(marker));
137 fs.writeFileSync(path.join(dataDir, path.basename(envelopeKey)), JSON.stringify(envelope));
138 return { marker, envelope };
139 }
140
141 function delegationProposal(body, meta, authorFields = {}) {
142 return {
143 proposal_id: 'prop-rhf-kn0',
144 source: DELEGATION_PROPOSAL_SOURCE,
145 vault_id: 'default',
146 body: JSON.stringify(body),
147 delegation_meta: meta,
148 ...authorFields,
149 };
150 }
151
152 describe('RHF-b-KN0 — unit', () => {
153 test('trusted catalog exposes agent_codex_retail exactly as frozen', () => {
154 assert.equal(TRUSTED_EXTERNAL_PROVIDER_IDENTITIES.length, 1);
155 const entry = TRUSTED_EXTERNAL_PROVIDER_IDENTITIES[0];
156 assert.deepEqual(entry, {
157 schema: AGENT_IDENTITY_SCHEMA_V1,
158 agent_id: 'agent_codex_retail',
159 kind: 'external_provider',
160 provider: 'codex',
161 owner_ref: 'org_ref:scooling',
162 registry_scope: 'global',
163 vault_id: null,
164 scope_ceiling: 'personal',
165 status: 'active',
166 created: '2026-08-27T00:00:00.000Z',
167 updated: '2026-08-27T00:00:00.000Z',
168 });
169 assert.equal(getTrustedCatalogIdentity('agent_codex_retail')?.provider, 'codex');
170 assert.equal(isReservedCatalogAgentId('agent_codex_retail'), true);
171 assert.equal(isReservedCatalogAgentId('agent_other'), false);
172 });
173
174 test('marker/envelope validators accept matching pair and reject hash mismatch', () => {
175 const vaultId = 'default';
176 const { marker, envelope } = writeMarkerAndEnvelope(mkDataDir(), vaultId);
177 assert.equal(validateDelegationAuthorityMarker(marker, vaultId).ok, true);
178 assert.equal(validateDelegationAuthorityEnvelope(envelope, marker, vaultId).ok, true);
179
180 const badEnvelope = {
181 ...envelope,
182 state_hash: 'sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
183 };
184 assert.equal(validateDelegationAuthorityEnvelope(badEnvelope, marker, vaultId).ok, false);
185 });
186
187 test('absent marker resolves to legacy mode', () => {
188 const dataDir = mkDataDir();
189 const mode = resolveDelegationAuthorityReadModeSync({ dataDir, vaultId: 'default' });
190 assert.deepEqual(mode, { ok: true, mode: 'legacy' });
191 });
192
193 test('bridge route source denies human session before vault/grant handler', () => {
194 const src = fs.readFileSync(DELEGATION_ROUTES_SRC, 'utf8');
195 const grantBlock = src.slice(src.indexOf("app.post('/api/v1/delegation/grants'"));
196 assert.match(grantBlock, /humanSessionTokenFromReq\(req\)/);
197 assert.match(grantBlock, /DELEGATION_HELPER_ACTOR_DENIED/);
198 const humanCheck = grantBlock.indexOf('humanSessionTokenFromReq(req)');
199 const vaultCheck = grantBlock.indexOf('vaultContext(req)');
200 assert.ok(humanCheck >= 0 && vaultCheck > humanCheck);
201 assert.ok(grantBlock.indexOf('handleDelegationGrantMintRequest') > vaultCheck);
202 });
203 });
204
205 describe('RHF-b-KN0 — integration', () => {
206 test('generic session mint denied before catalog resolution (legacy comparator)', () => {
207 const sessionReq = mockReq(sessionToken());
208 assert.equal(legacyBridgeGrantMintWouldAcceptSession(sessionReq), true);
209
210 const src = fs.readFileSync(DELEGATION_ROUTES_SRC, 'utf8');
211 assert.match(src, /function humanSessionTokenFromReq\(req\)/);
212 assert.match(src, /tokenClass === 'session' \|\| tokenClass === 'legacy_session'/);
213
214 const sessionPayload = jwt.verify(sessionToken(), SESSION_SECRET);
215 assert.equal(sessionPayload.type, 'session');
216 const legacyPayload = jwt.verify(legacySessionToken(), SESSION_SECRET);
217 assert.equal(legacyPayload.type, undefined);
218 });
219
220 test('catalog identity resolves without vault seed; reserved id cannot be shadowed', () => {
221 const dataDir = mkDataDir();
222 enableGate(dataDir);
223 const shadow = makeAgentIdentity({
224 agentId: 'agent_codex_retail',
225 kind: 'external_provider',
226 scopeCeiling: 'personal',
227 });
228 seedDelegationFixtures(dataDir, 'default', shadow);
229
230 const resolved = getAgentIdentity(dataDir, 'default', 'agent_codex_retail');
231 assert.equal(resolved?.schema, AGENT_IDENTITY_SCHEMA_V1);
232 assert.equal(resolved?.registry_scope, 'global');
233 assert.notEqual(resolved?.owner_ref, shadow.owner_ref);
234
235 const list = handleAgentIdentityListRequest({
236 dataDir,
237 vaultId: 'default',
238 kind: 'external_provider',
239 status: 'active',
240 });
241 assert.equal(list.ok, true);
242 const retail = list.payload.identities.filter((i) => i.agent_id === 'agent_codex_retail');
243 assert.equal(retail.length, 1);
244 assert.equal(retail[0].provider, 'codex');
245 });
246
247 test('identity register propose rejects reserved catalog id with 409', async () => {
248 const dataDir = mkDataDir();
249 enableGate(dataDir);
250 const result = await handleAgentIdentityRegisterProposeRequest({
251 dataDir,
252 vaultId: 'default',
253 userId: TEST_USER_ID,
254 kind: 'external_provider',
255 agentId: 'agent_codex_retail',
256 scopeCeiling: 'personal',
257 createProposal: async () => ({ proposal_id: 'prop_should_not_run' }),
258 });
259 assert.equal(result.ok, false);
260 assert.equal(result.status, 409);
261 assert.equal(result.code, 'AGENT_IDENTITY_RESERVED');
262 });
263
264 test('approved identity proposal precheck rejects reserved catalog id', () => {
265 const dataDir = mkDataDir();
266 enableGate(dataDir);
267 const body = makeAgentIdentity({ agentId: 'agent_codex_retail', kind: 'external_provider' });
268 const result = precheckApprovedDelegationProposal(
269 dataDir,
270 delegationProposal(body, { record_kind: 'agent_identity' }),
271 { author: TEST_USER_ID },
272 );
273 assert.equal(result.ok, false);
274 assert.equal(result.code, 'AGENT_IDENTITY_RESERVED');
275 });
276
277 test('valid active marker switches reads to envelope; bad marker fails closed', () => {
278 const dataDir = mkDataDir();
279 enableGate(dataDir);
280 const localIdentity = makeAgentIdentity({ agentId: 'agent_vault_only01' });
281 seedDelegationFixtures(dataDir, 'Business', localIdentity);
282
283 writeMarkerAndEnvelope(dataDir, 'Business', {}, {
284 identities_by_id: {
285 agent_vault_only01: localIdentity,
286 },
287 });
288 const envelopeIdentity = getAgentIdentity(dataDir, 'Business', 'agent_vault_only01');
289 assert.equal(envelopeIdentity?.agent_id, 'agent_vault_only01');
290
291 fs.writeFileSync(
292 path.join(dataDir, delegationAuthorityMarkerFileName('Business')),
293 JSON.stringify({ schema: DELEGATION_AUTHORITY_MARKER_SCHEMA, vault_id: 'Business' }),
294 );
295 const ctx = resolveDelegationReadContext(dataDir, 'Business');
296 assert.equal(ctx.ok, false);
297 assert.equal(ctx.code, DELEGATION_AUTHORITY_UNAVAILABLE);
298 });
299 });
300
301 describe('RHF-b-KN0 — e2e', () => {
302 test('grant mint uses vault actor when consent exists; session bridge gate blocks first', () => {
303 const dataDir = mkDataDir();
304 enableGate(dataDir);
305 const delegate = makeAgentIdentity({ agentId: 'agent_tutor_test01', kind: 'delegate' });
306 const consent = makeDelegationConsent({ agentId: 'agent_tutor_test01' });
307 consent.scope = 'personal';
308 delete consent.workspace_id;
309 seedDelegationFixtures(dataDir, 'default', delegate, consent);
310
311 const catalogActor = getAgentIdentity(dataDir, 'default', 'agent_codex_retail');
312 assert.equal(catalogActor?.kind, 'external_provider');
313
314 const mint = handleDelegationGrantMintRequest({
315 dataDir,
316 vaultId: 'default',
317 consentId: consent.consent_id,
318 actorAgentId: 'agent_tutor_test01',
319 });
320 assert.equal(mint.ok, true);
321
322 const sessionReq = mockReq(sessionToken());
323 assert.equal(legacyBridgeGrantMintWouldAcceptSession(sessionReq), true);
324 const serviceReq = mockReq(serviceToken());
325 assert.equal(legacyBridgeGrantMintWouldAcceptSession(serviceReq), true);
326 });
327 });
328
329 describe('RHF-b-KN0 — stress', () => {
330 test('many vault marker probes stay legacy without marker files', () => {
331 const dataDir = mkDataDir();
332 for (let i = 0; i < 128; i++) {
333 const mode = resolveDelegationAuthorityReadModeSync({ dataDir, vaultId: `vault_${i}` });
334 assert.deepEqual(mode, { ok: true, mode: 'legacy' });
335 }
336 });
337 });
338
339 describe('RHF-b-KN0 — data-integrity', () => {
340 test('state hash recompute detects tampered envelope fields', () => {
341 const envelope = buildEnvelope('default', {
342 consents_by_id: { dcons_x: { consent_id: 'dcons_x' } },
343 });
344 const tampered = { ...envelope, consents_by_id: { dcons_x: { consent_id: 'dcons_y' } } };
345 tampered.state_hash = envelope.state_hash;
346 const marker = {
347 schema: DELEGATION_AUTHORITY_MARKER_SCHEMA,
348 vault_id: 'default',
349 envelope_key: 'delegation/authority/v1/default/envelope',
350 envelope_schema_version: 1,
351 lineage_id: envelope.lineage_id,
352 origin_snapshot_hash: envelope.origin_snapshot_hash,
353 };
354 assert.equal(validateDelegationAuthorityEnvelope(tampered, marker, 'default').ok, false);
355 });
356
357 test('missing envelope with present marker returns unavailable not legacy', () => {
358 const dataDir = mkDataDir();
359 const marker = {
360 schema: DELEGATION_AUTHORITY_MARKER_SCHEMA,
361 vault_id: 'default',
362 envelope_key: 'delegation/authority/v1/default/envelope',
363 envelope_schema_version: 1,
364 lineage_id: 'lineage_missing_env',
365 origin_snapshot_hash: 'sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
366 };
367 fs.writeFileSync(path.join(dataDir, delegationAuthorityMarkerFileName('default')), JSON.stringify(marker));
368 const mode = resolveDelegationAuthorityReadModeSync({ dataDir, vaultId: 'default' });
369 assert.deepEqual(mode, { ok: false, code: DELEGATION_AUTHORITY_UNAVAILABLE });
370 });
371 });
372
373 describe('RHF-b-KN0 — performance', () => {
374 test('catalog lookup and legacy read-mode probe stay bounded', () => {
375 const dataDir = mkDataDir();
376 enableGate(dataDir);
377 seedDelegationFixtures(dataDir, 'default', makeAgentIdentity());
378
379 const t0 = performance.now();
380 for (let i = 0; i < 500; i++) {
381 getTrustedCatalogIdentity('agent_codex_retail');
382 resolveDelegationAuthorityReadModeSync({ dataDir, vaultId: 'default' });
383 }
384 const elapsed = performance.now() - t0;
385 assert.ok(elapsed < 250, `expected <250ms, got ${elapsed.toFixed(1)}ms`);
386 });
387 });
388
389 describe('RHF-b-KN0 — security', () => {
390 test('KN0 generic session and legacy_session classes are rejected at route gate', () => {
391 const src = fs.readFileSync(DELEGATION_ROUTES_SRC, 'utf8');
392 assert.match(src, /resolveActorTokenClass/);
393 assert.match(src, /legacy_session/);
394
395 const sessionReq = mockReq(sessionToken());
396 const legacyReq = mockReq(legacySessionToken());
397 assert.equal(legacyBridgeGrantMintWouldAcceptSession(sessionReq), true);
398 assert.equal(legacyBridgeGrantMintWouldAcceptSession(legacyReq), true);
399 });
400
401 test('reserved catalog cannot be registered via propose or apply precheck', async () => {
402 const dataDir = mkDataDir();
403 enableGate(dataDir);
404 const propose = await handleAgentIdentityRegisterProposeRequest({
405 dataDir,
406 vaultId: 'default',
407 userId: TEST_USER_ID,
408 kind: 'external_provider',
409 agentId: 'agent_codex_retail',
410 createProposal: async () => ({ proposal_id: 'prop_x' }),
411 });
412 assert.equal(propose.code, 'AGENT_IDENTITY_RESERVED');
413
414 const pre = precheckApprovedDelegationProposal(
415 dataDir,
416 delegationProposal(makeAgentIdentity({ agentId: 'agent_codex_retail' }), {
417 record_kind: 'agent_identity',
418 }),
419 { author: TEST_USER_ID },
420 );
421 assert.equal(pre.code, 'AGENT_IDENTITY_RESERVED');
422 });
423
424 test('listTrustedCatalogIdentities returns immutable copies', () => {
425 const list = listTrustedCatalogIdentities();
426 list[0].status = 'revoked';
427 assert.equal(getTrustedCatalogIdentity('agent_codex_retail')?.status, 'active');
428 });
429 });
File History 1 commit
sha256:fbe982a22c05c6fe2e93876f250deecdb43d883f648b4e1810c7546caa9f17db docs: activate KNOWTATION- board identity and preserve livi… Human minor 1 day ago