delegation-hosted-proposal-l1b.test.mjs
427 lines 14.9 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago
1 /**
2 * Phase 7C-L1b — hosted delegation proposal parity (canister propose + bridge apply).
3 *
4 * Tiers: unit, integration, e2e, stress, data-integrity, performance, security.
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11
12 import {
13 mergeDelegationFrontmatter,
14 normalizeCanisterProposalForDelegationPrecheck,
15 isDelegationProposalIntent,
16 createDelegationProposalOnCanister,
17 applyApprovedDelegationProposalFromCanister,
18 FM_PROPOSAL_SOURCE,
19 FM_RECORD_KIND,
20 } from '../lib/agent/delegation-hosted-proposal.mjs';
21 import {
22 DELEGATION_PROPOSAL_SOURCE,
23 precheckApprovedDelegationProposal,
24 applyDelegationProposalToIndex,
25 handleDelegationGrantMintRequest,
26 getAgentIdentity,
27 getConsent,
28 seedDelegationFixtures,
29 } from '../lib/agent/delegation.mjs';
30 import { writeDelegationPolicy, makeAgentIdentity, makeDelegationConsent, TEST_USER_ID } from './fixtures/agent/delegation-helpers.mjs';
31
32 const __dirname = path.dirname(fileURLToPath(import.meta.url));
33 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-delegation-l1b');
34
35 describe('7C-L1b delegation hosted proposal — unit', () => {
36 it('isDelegationProposalIntent recognizes delegation intents', () => {
37 assert.equal(isDelegationProposalIntent('agent_identity_register'), true);
38 assert.equal(isDelegationProposalIntent('delegation_consent_create'), true);
39 assert.equal(isDelegationProposalIntent('flow_edit'), false);
40 });
41
42 it('mergeDelegationFrontmatter embeds source and record kind', () => {
43 const fm = mergeDelegationFrontmatter({ agent_id: 'agent_x' }, { record_kind: 'agent_identity', agent_id: 'agent_x' });
44 assert.equal(fm[FM_PROPOSAL_SOURCE], DELEGATION_PROPOSAL_SOURCE);
45 assert.equal(fm[FM_RECORD_KIND], 'agent_identity');
46 assert.equal(fm.agent_id, 'agent_x');
47 });
48
49 it('normalizeCanisterProposalForDelegationPrecheck maps frontmatter to delegation_meta', () => {
50 const fm = mergeDelegationFrontmatter({}, { record_kind: 'delegation_consent', consent_id: 'dcons_abc' });
51 const normalized = normalizeCanisterProposalForDelegationPrecheck({
52 proposal_id: 'prop-1',
53 intent: 'delegation_consent_create',
54 status: 'approved',
55 vault_id: 'Business',
56 body: '{}',
57 frontmatter: JSON.stringify(fm),
58 });
59 assert.ok(normalized);
60 assert.equal(normalized.source, DELEGATION_PROPOSAL_SOURCE);
61 assert.equal(normalized.delegation_meta.record_kind, 'delegation_consent');
62 assert.equal(normalized.delegation_meta.consent_id, 'dcons_abc');
63 });
64 });
65
66 describe('7C-L1b delegation hosted proposal — integration', () => {
67 const dataDir = path.join(tmpRoot, 'integration', 'data');
68 const vaultId = 'Business';
69
70 beforeEach(() => {
71 fs.rmSync(path.join(tmpRoot, 'integration'), { recursive: true, force: true });
72 fs.mkdirSync(dataDir, { recursive: true });
73 writeDelegationPolicy(dataDir);
74 process.env.DELEGATION_ENABLED = '1';
75 });
76
77 afterEach(() => {
78 delete process.env.DELEGATION_ENABLED;
79 });
80
81 it('createDelegationProposalOnCanister POSTs frontmatter to canister', async () => {
82 const calls = [];
83 const originalFetch = globalThis.fetch;
84 globalThis.fetch = async (url, init) => {
85 calls.push({ url: String(url), init });
86 return {
87 ok: true,
88 status: 200,
89 text: async () =>
90 JSON.stringify({ proposal_id: 'prop-canister-1', path: 'meta/agents/smoke.md', status: 'proposed' }),
91 };
92 };
93 try {
94 const proposal = await createDelegationProposalOnCanister({
95 canisterUrl: 'https://canister.test',
96 dataDir,
97 sessionBound: true,
98 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
99 input: {
100 path: 'meta/agents/smoke.md',
101 body: '{"schema":"knowtation.agent_identity/v0"}',
102 intent: 'agent_identity_register',
103 vault_id: vaultId,
104 review_queue: 'delegation',
105 proposed_by: 'google:owner-smoke',
106 delegation_meta: { record_kind: 'agent_identity', agent_id: 'agent_smoke01' },
107 },
108 });
109 assert.equal(proposal.proposal_id, 'prop-canister-1');
110 assert.equal(calls.length, 1);
111 assert.match(calls[0].url, /\/api\/v1\/proposals$/);
112 const sent = JSON.parse(String(calls[0].init.body));
113 assert.equal(sent.intent, 'agent_identity_register');
114 assert.equal(sent.source, DELEGATION_PROPOSAL_SOURCE);
115 assert.equal(sent.evaluation_status, 'pending');
116 assert.equal(sent.frontmatter[FM_PROPOSAL_SOURCE], DELEGATION_PROPOSAL_SOURCE);
117 } finally {
118 globalThis.fetch = originalFetch;
119 }
120 });
121
122 it('createDelegationProposalOnCanister forwards canister error code', async () => {
123 const originalFetch = globalThis.fetch;
124 globalThis.fetch = async () => ({
125 ok: false,
126 status: 403,
127 text: async () =>
128 JSON.stringify({ error: 'Gateway authentication required', code: 'GATEWAY_AUTH_REQUIRED' }),
129 });
130 try {
131 await assert.rejects(
132 () =>
133 createDelegationProposalOnCanister({
134 canisterUrl: 'https://canister.test',
135 dataDir,
136 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
137 input: {
138 path: 'meta/delegation/consents/x.md',
139 body: '{}',
140 intent: 'delegation_consent_create',
141 review_queue: 'delegation',
142 proposed_by: 'google:learner',
143 delegation_meta: { record_kind: 'delegation_consent', consent_id: 'dcons_x' },
144 },
145 }),
146 (err) => {
147 assert.equal(err.status, 403);
148 assert.equal(err.code, 'GATEWAY_AUTH_REQUIRED');
149 return true;
150 },
151 );
152 } finally {
153 globalThis.fetch = originalFetch;
154 }
155 });
156
157 it('applyApprovedDelegationProposalFromCanister updates bridge index after canister approve', async () => {
158 const identity = makeAgentIdentity({ agentId: 'agent_l1b_smoke01' });
159 const body = JSON.stringify(identity);
160 const fm = mergeDelegationFrontmatter(
161 { agent_id: identity.agent_id },
162 { record_kind: 'agent_identity', agent_id: identity.agent_id },
163 );
164 const originalFetch = globalThis.fetch;
165 globalThis.fetch = async () => ({
166 ok: true,
167 status: 200,
168 text: async () =>
169 JSON.stringify({
170 proposal_id: 'prop-l1b-identity',
171 path: 'meta/agents/l1b01.md',
172 status: 'approved',
173 vault_id: vaultId,
174 intent: 'agent_identity_register',
175 created_by: TEST_USER_ID,
176 body,
177 frontmatter: JSON.stringify(fm),
178 }),
179 });
180 try {
181 const result = await applyApprovedDelegationProposalFromCanister({
182 dataDir,
183 canisterUrl: 'https://canister.test',
184 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
185 proposalId: 'prop-l1b-identity',
186 });
187 assert.equal(result.ok, true);
188 assert.equal(result.payload.applied, true);
189 const stored = getAgentIdentity(dataDir, vaultId, identity.agent_id);
190 assert.ok(stored);
191 assert.equal(stored.agent_id, identity.agent_id);
192 } finally {
193 globalThis.fetch = originalFetch;
194 }
195 });
196 });
197
198 describe('7C-L1b delegation hosted proposal — e2e', () => {
199 const dataDir = path.join(tmpRoot, 'e2e', 'data');
200 const vaultId = 'Business';
201
202 beforeEach(() => {
203 fs.rmSync(path.join(tmpRoot, 'e2e'), { recursive: true, force: true });
204 fs.mkdirSync(dataDir, { recursive: true });
205 writeDelegationPolicy(dataDir);
206 process.env.DELEGATION_ENABLED = '1';
207 });
208
209 afterEach(() => {
210 delete process.env.DELEGATION_ENABLED;
211 });
212
213 it('identity approve apply → consent approve apply → grant mint', async () => {
214 const identity = makeAgentIdentity({ agentId: 'agent_l1b_e2e01' });
215 const consentBody = makeDelegationConsent({
216 consentId: 'dcons_l1b_e2e01',
217 agentId: identity.agent_id,
218 });
219
220 const identityProposal = {
221 proposal_id: 'prop-id-e2e',
222 path: 'meta/agents/l1b_e2e.md',
223 status: 'approved',
224 vault_id: vaultId,
225 intent: 'agent_identity_register',
226 created_by: TEST_USER_ID,
227 body: JSON.stringify(identity),
228 frontmatter: JSON.stringify(
229 mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: identity.agent_id }),
230 ),
231 };
232 const consentProposal = {
233 proposal_id: 'prop-consent-e2e',
234 path: 'meta/delegation/consents/l1b_e2e.md',
235 status: 'approved',
236 vault_id: vaultId,
237 intent: 'delegation_consent_create',
238 created_by: TEST_USER_ID,
239 body: JSON.stringify(consentBody),
240 frontmatter: JSON.stringify(
241 mergeDelegationFrontmatter({}, { record_kind: 'delegation_consent', consent_id: consentBody.consent_id }),
242 ),
243 };
244
245 let fetchCount = 0;
246 const originalFetch = globalThis.fetch;
247 globalThis.fetch = async () => {
248 fetchCount += 1;
249 const payload = fetchCount === 1 ? identityProposal : consentProposal;
250 return { ok: true, status: 200, text: async () => JSON.stringify(payload) };
251 };
252
253 try {
254 const idApply = await applyApprovedDelegationProposalFromCanister({
255 dataDir,
256 canisterUrl: 'https://canister.test',
257 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
258 proposalId: identityProposal.proposal_id,
259 });
260 assert.equal(idApply.ok, true);
261
262 const consentApply = await applyApprovedDelegationProposalFromCanister({
263 dataDir,
264 canisterUrl: 'https://canister.test',
265 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
266 proposalId: consentProposal.proposal_id,
267 });
268 assert.equal(consentApply.ok, true);
269
270 const mint = handleDelegationGrantMintRequest({
271 dataDir,
272 vaultId,
273 consentId: consentBody.consent_id,
274 actorAgentId: identity.agent_id,
275 taskRef: 'task_hw_week3',
276 });
277 assert.equal(mint.ok, true);
278 assert.match(mint.payload.bearer, /^dgrnt_bearer_/);
279 } finally {
280 globalThis.fetch = originalFetch;
281 }
282 });
283 });
284
285 describe('7C-L1b delegation hosted proposal — stress', () => {
286 it('normalize 200 canister rows without throwing', () => {
287 for (let i = 0; i < 200; i += 1) {
288 const fm = mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: `agent_st_${i}` });
289 const out = normalizeCanisterProposalForDelegationPrecheck({
290 proposal_id: `prop-${i}`,
291 intent: 'agent_identity_register',
292 frontmatter: JSON.stringify(fm),
293 body: '{}',
294 });
295 assert.ok(out);
296 }
297 });
298 });
299
300 describe('7C-L1b delegation hosted proposal — data-integrity', () => {
301 const dataDir = path.join(tmpRoot, 'di', 'data');
302 const vaultId = 'default';
303
304 beforeEach(() => {
305 fs.rmSync(path.join(tmpRoot, 'di'), { recursive: true, force: true });
306 fs.mkdirSync(dataDir, { recursive: true });
307 writeDelegationPolicy(dataDir);
308 process.env.DELEGATION_ENABLED = '1';
309 });
310
311 afterEach(() => {
312 delete process.env.DELEGATION_ENABLED;
313 });
314
315 it('precheck + apply preserves consent evidence_ref with proposal id', () => {
316 const identity = makeAgentIdentity({ agentId: 'agent_di_test01' });
317 seedDelegationFixtures(dataDir, vaultId, identity);
318 const consentBody = makeDelegationConsent({ consentId: 'dcons_di_test01', agentId: identity.agent_id });
319 const proposal = normalizeCanisterProposalForDelegationPrecheck({
320 proposal_id: 'prop-di-consent',
321 status: 'approved',
322 vault_id: vaultId,
323 intent: 'delegation_consent_create',
324 created_by: TEST_USER_ID,
325 body: JSON.stringify(consentBody),
326 frontmatter: JSON.stringify(
327 mergeDelegationFrontmatter({}, { record_kind: 'delegation_consent', consent_id: consentBody.consent_id }),
328 ),
329 });
330 assert.ok(proposal);
331 const pre = precheckApprovedDelegationProposal(dataDir, proposal, { author: TEST_USER_ID });
332 assert.equal(pre.ok, true);
333 applyDelegationProposalToIndex(dataDir, pre);
334 const stored = getConsent(dataDir, vaultId, consentBody.consent_id);
335 assert.equal(stored.evidence_ref, 'proposal:prop-di-consent');
336 });
337 });
338
339 describe('7C-L1b delegation hosted proposal — performance', () => {
340 it('normalizeCanisterProposalForDelegationPrecheck completes 1000 rows under 500ms', () => {
341 const fm = mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: 'agent_perf' });
342 const row = {
343 proposal_id: 'prop-perf',
344 intent: 'agent_identity_register',
345 frontmatter: JSON.stringify(fm),
346 body: '{}',
347 };
348 const start = performance.now();
349 for (let i = 0; i < 1000; i += 1) {
350 normalizeCanisterProposalForDelegationPrecheck(row);
351 }
352 assert.ok(performance.now() - start < 500);
353 });
354 });
355
356 describe('7C-L1b delegation hosted proposal — security', () => {
357 const dataDir = path.join(tmpRoot, 'sec', 'data');
358
359 beforeEach(() => {
360 fs.rmSync(path.join(tmpRoot, 'sec'), { recursive: true, force: true });
361 fs.mkdirSync(dataDir, { recursive: true });
362 writeDelegationPolicy(dataDir);
363 process.env.DELEGATION_ENABLED = '1';
364 });
365
366 afterEach(() => {
367 delete process.env.DELEGATION_ENABLED;
368 });
369
370 it('apply rejects non-delegation canister proposals', async () => {
371 const originalFetch = globalThis.fetch;
372 globalThis.fetch = async () => ({
373 ok: true,
374 status: 200,
375 text: async () =>
376 JSON.stringify({
377 proposal_id: 'prop-script',
378 path: 'projects/x/script-proposal.md',
379 status: 'approved',
380 intent: 'script_proposal',
381 body: 'hello',
382 frontmatter: '{}',
383 }),
384 });
385 try {
386 const result = await applyApprovedDelegationProposalFromCanister({
387 dataDir,
388 canisterUrl: 'https://canister.test',
389 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': 'default' },
390 proposalId: 'prop-script',
391 });
392 assert.equal(result.ok, false);
393 assert.equal(result.code, 'BAD_REQUEST');
394 } finally {
395 globalThis.fetch = originalFetch;
396 }
397 });
398
399 it('apply rejects unapproved delegation proposals', async () => {
400 const fm = mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: 'agent_sec01' });
401 const originalFetch = globalThis.fetch;
402 globalThis.fetch = async () => ({
403 ok: true,
404 status: 200,
405 text: async () =>
406 JSON.stringify({
407 proposal_id: 'prop-pending',
408 status: 'proposed',
409 intent: 'agent_identity_register',
410 body: '{}',
411 frontmatter: JSON.stringify(fm),
412 }),
413 });
414 try {
415 const result = await applyApprovedDelegationProposalFromCanister({
416 dataDir,
417 canisterUrl: 'https://canister.test',
418 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': 'default' },
419 proposalId: 'prop-pending',
420 });
421 assert.equal(result.ok, false);
422 assert.equal(result.code, 'CONFLICT');
423 } finally {
424 globalThis.fetch = originalFetch;
425 }
426 });
427 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago