rhf-b-kn0-compatibility.test.mjs
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