delegation-hosted-proposal-l1b.test.mjs
383 lines 13.4 KB
Raw
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 10 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 } 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 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
97 input: {
98 path: 'meta/agents/smoke.md',
99 body: '{"schema":"knowtation.agent_identity/v0"}',
100 intent: 'agent_identity_register',
101 vault_id: vaultId,
102 review_queue: 'delegation',
103 delegation_meta: { record_kind: 'agent_identity', agent_id: 'agent_smoke01' },
104 },
105 });
106 assert.equal(proposal.proposal_id, 'prop-canister-1');
107 assert.equal(calls.length, 1);
108 assert.match(calls[0].url, /\/api\/v1\/proposals$/);
109 const sent = JSON.parse(String(calls[0].init.body));
110 assert.equal(sent.intent, 'agent_identity_register');
111 assert.equal(sent.frontmatter[FM_PROPOSAL_SOURCE], DELEGATION_PROPOSAL_SOURCE);
112 } finally {
113 globalThis.fetch = originalFetch;
114 }
115 });
116
117 it('applyApprovedDelegationProposalFromCanister updates bridge index after canister approve', async () => {
118 const identity = makeAgentIdentity({ agentId: 'agent_l1b_smoke01' });
119 const body = JSON.stringify(identity);
120 const fm = mergeDelegationFrontmatter(
121 { agent_id: identity.agent_id },
122 { record_kind: 'agent_identity', agent_id: identity.agent_id },
123 );
124 const originalFetch = globalThis.fetch;
125 globalThis.fetch = async () => ({
126 ok: true,
127 status: 200,
128 text: async () =>
129 JSON.stringify({
130 proposal_id: 'prop-l1b-identity',
131 path: 'meta/agents/l1b01.md',
132 status: 'approved',
133 vault_id: vaultId,
134 intent: 'agent_identity_register',
135 body,
136 frontmatter: JSON.stringify(fm),
137 }),
138 });
139 try {
140 const result = await applyApprovedDelegationProposalFromCanister({
141 dataDir,
142 canisterUrl: 'https://canister.test',
143 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
144 proposalId: 'prop-l1b-identity',
145 });
146 assert.equal(result.ok, true);
147 assert.equal(result.payload.applied, true);
148 const stored = getAgentIdentity(dataDir, vaultId, identity.agent_id);
149 assert.ok(stored);
150 assert.equal(stored.agent_id, identity.agent_id);
151 } finally {
152 globalThis.fetch = originalFetch;
153 }
154 });
155 });
156
157 describe('7C-L1b delegation hosted proposal — e2e', () => {
158 const dataDir = path.join(tmpRoot, 'e2e', 'data');
159 const vaultId = 'Business';
160
161 beforeEach(() => {
162 fs.rmSync(path.join(tmpRoot, 'e2e'), { recursive: true, force: true });
163 fs.mkdirSync(dataDir, { recursive: true });
164 writeDelegationPolicy(dataDir);
165 process.env.DELEGATION_ENABLED = '1';
166 });
167
168 afterEach(() => {
169 delete process.env.DELEGATION_ENABLED;
170 });
171
172 it('identity approve apply → consent approve apply → grant mint', async () => {
173 const identity = makeAgentIdentity({ agentId: 'agent_l1b_e2e01' });
174 const consentBody = makeDelegationConsent({
175 consentId: 'dcons_l1b_e2e01',
176 agentId: identity.agent_id,
177 });
178
179 const identityProposal = {
180 proposal_id: 'prop-id-e2e',
181 path: 'meta/agents/l1b_e2e.md',
182 status: 'approved',
183 vault_id: vaultId,
184 intent: 'agent_identity_register',
185 body: JSON.stringify(identity),
186 frontmatter: JSON.stringify(
187 mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: identity.agent_id }),
188 ),
189 };
190 const consentProposal = {
191 proposal_id: 'prop-consent-e2e',
192 path: 'meta/delegation/consents/l1b_e2e.md',
193 status: 'approved',
194 vault_id: vaultId,
195 intent: 'delegation_consent_create',
196 body: JSON.stringify(consentBody),
197 frontmatter: JSON.stringify(
198 mergeDelegationFrontmatter({}, { record_kind: 'delegation_consent', consent_id: consentBody.consent_id }),
199 ),
200 };
201
202 let fetchCount = 0;
203 const originalFetch = globalThis.fetch;
204 globalThis.fetch = async () => {
205 fetchCount += 1;
206 const payload = fetchCount === 1 ? identityProposal : consentProposal;
207 return { ok: true, status: 200, text: async () => JSON.stringify(payload) };
208 };
209
210 try {
211 const idApply = await applyApprovedDelegationProposalFromCanister({
212 dataDir,
213 canisterUrl: 'https://canister.test',
214 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
215 proposalId: identityProposal.proposal_id,
216 });
217 assert.equal(idApply.ok, true);
218
219 const consentApply = await applyApprovedDelegationProposalFromCanister({
220 dataDir,
221 canisterUrl: 'https://canister.test',
222 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': vaultId },
223 proposalId: consentProposal.proposal_id,
224 });
225 assert.equal(consentApply.ok, true);
226
227 const mint = handleDelegationGrantMintRequest({
228 dataDir,
229 vaultId,
230 consentId: consentBody.consent_id,
231 actorAgentId: identity.agent_id,
232 taskRef: 'task_hw_week3',
233 });
234 assert.equal(mint.ok, true);
235 assert.match(mint.payload.bearer, /^dgrnt_bearer_/);
236 } finally {
237 globalThis.fetch = originalFetch;
238 }
239 });
240 });
241
242 describe('7C-L1b delegation hosted proposal — stress', () => {
243 it('normalize 200 canister rows without throwing', () => {
244 for (let i = 0; i < 200; i += 1) {
245 const fm = mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: `agent_st_${i}` });
246 const out = normalizeCanisterProposalForDelegationPrecheck({
247 proposal_id: `prop-${i}`,
248 intent: 'agent_identity_register',
249 frontmatter: JSON.stringify(fm),
250 body: '{}',
251 });
252 assert.ok(out);
253 }
254 });
255 });
256
257 describe('7C-L1b delegation hosted proposal — data-integrity', () => {
258 const dataDir = path.join(tmpRoot, 'di', 'data');
259 const vaultId = 'default';
260
261 beforeEach(() => {
262 fs.rmSync(path.join(tmpRoot, 'di'), { recursive: true, force: true });
263 fs.mkdirSync(dataDir, { recursive: true });
264 writeDelegationPolicy(dataDir);
265 process.env.DELEGATION_ENABLED = '1';
266 });
267
268 afterEach(() => {
269 delete process.env.DELEGATION_ENABLED;
270 });
271
272 it('precheck + apply preserves consent evidence_ref with proposal id', () => {
273 const identity = makeAgentIdentity({ agentId: 'agent_di_test01' });
274 seedDelegationFixtures(dataDir, vaultId, identity);
275 const consentBody = makeDelegationConsent({ consentId: 'dcons_di_test01', agentId: identity.agent_id });
276 const proposal = normalizeCanisterProposalForDelegationPrecheck({
277 proposal_id: 'prop-di-consent',
278 status: 'approved',
279 vault_id: vaultId,
280 intent: 'delegation_consent_create',
281 body: JSON.stringify(consentBody),
282 frontmatter: JSON.stringify(
283 mergeDelegationFrontmatter({}, { record_kind: 'delegation_consent', consent_id: consentBody.consent_id }),
284 ),
285 });
286 assert.ok(proposal);
287 const pre = precheckApprovedDelegationProposal(dataDir, proposal);
288 assert.equal(pre.ok, true);
289 applyDelegationProposalToIndex(dataDir, pre);
290 const stored = getConsent(dataDir, vaultId, consentBody.consent_id);
291 assert.equal(stored.evidence_ref, 'proposal:prop-di-consent');
292 });
293 });
294
295 describe('7C-L1b delegation hosted proposal — performance', () => {
296 it('normalizeCanisterProposalForDelegationPrecheck completes 1000 rows under 500ms', () => {
297 const fm = mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: 'agent_perf' });
298 const row = {
299 proposal_id: 'prop-perf',
300 intent: 'agent_identity_register',
301 frontmatter: JSON.stringify(fm),
302 body: '{}',
303 };
304 const start = performance.now();
305 for (let i = 0; i < 1000; i += 1) {
306 normalizeCanisterProposalForDelegationPrecheck(row);
307 }
308 assert.ok(performance.now() - start < 500);
309 });
310 });
311
312 describe('7C-L1b delegation hosted proposal — security', () => {
313 const dataDir = path.join(tmpRoot, 'sec', 'data');
314
315 beforeEach(() => {
316 fs.rmSync(path.join(tmpRoot, 'sec'), { recursive: true, force: true });
317 fs.mkdirSync(dataDir, { recursive: true });
318 writeDelegationPolicy(dataDir);
319 process.env.DELEGATION_ENABLED = '1';
320 });
321
322 afterEach(() => {
323 delete process.env.DELEGATION_ENABLED;
324 });
325
326 it('apply rejects non-delegation canister proposals', async () => {
327 const originalFetch = globalThis.fetch;
328 globalThis.fetch = async () => ({
329 ok: true,
330 status: 200,
331 text: async () =>
332 JSON.stringify({
333 proposal_id: 'prop-script',
334 path: 'projects/x/script-proposal.md',
335 status: 'approved',
336 intent: 'script_proposal',
337 body: 'hello',
338 frontmatter: '{}',
339 }),
340 });
341 try {
342 const result = await applyApprovedDelegationProposalFromCanister({
343 dataDir,
344 canisterUrl: 'https://canister.test',
345 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': 'default' },
346 proposalId: 'prop-script',
347 });
348 assert.equal(result.ok, false);
349 assert.equal(result.code, 'BAD_REQUEST');
350 } finally {
351 globalThis.fetch = originalFetch;
352 }
353 });
354
355 it('apply rejects unapproved delegation proposals', async () => {
356 const fm = mergeDelegationFrontmatter({}, { record_kind: 'agent_identity', agent_id: 'agent_sec01' });
357 const originalFetch = globalThis.fetch;
358 globalThis.fetch = async () => ({
359 ok: true,
360 status: 200,
361 text: async () =>
362 JSON.stringify({
363 proposal_id: 'prop-pending',
364 status: 'proposed',
365 intent: 'agent_identity_register',
366 body: '{}',
367 frontmatter: JSON.stringify(fm),
368 }),
369 });
370 try {
371 const result = await applyApprovedDelegationProposalFromCanister({
372 dataDir,
373 canisterUrl: 'https://canister.test',
374 headers: { 'X-User-Id': 'owner', 'X-Vault-Id': 'default' },
375 proposalId: 'prop-pending',
376 });
377 assert.equal(result.ok, false);
378 assert.equal(result.code, 'CONFLICT');
379 } finally {
380 globalThis.fetch = originalFetch;
381 }
382 });
383 });
File History 1 commit
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 10 days ago