@mcp-i/core 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/delegation/cascading-revocation.d.ts.map +1 -1
  2. package/dist/delegation/cascading-revocation.js +3 -1
  3. package/dist/delegation/cascading-revocation.js.map +1 -1
  4. package/dist/delegation/outbound-headers.d.ts +12 -12
  5. package/dist/delegation/outbound-headers.d.ts.map +1 -1
  6. package/dist/delegation/outbound-headers.js +12 -12
  7. package/dist/delegation/outbound-headers.js.map +1 -1
  8. package/dist/delegation/outbound-proof.d.ts +1 -1
  9. package/dist/delegation/outbound-proof.js +1 -1
  10. package/dist/delegation/statuslist-manager.d.ts +3 -0
  11. package/dist/delegation/statuslist-manager.d.ts.map +1 -1
  12. package/dist/delegation/statuslist-manager.js +13 -0
  13. package/dist/delegation/statuslist-manager.js.map +1 -1
  14. package/dist/middleware/with-mcpi-server.d.ts +1 -1
  15. package/dist/middleware/with-mcpi-server.d.ts.map +1 -1
  16. package/dist/middleware/with-mcpi-server.js.map +1 -1
  17. package/dist/middleware/with-mcpi.d.ts +14 -0
  18. package/dist/middleware/with-mcpi.d.ts.map +1 -1
  19. package/dist/middleware/with-mcpi.js +12 -0
  20. package/dist/middleware/with-mcpi.js.map +1 -1
  21. package/package.json +3 -2
  22. package/src/__tests__/audit/canonicalization-integrity.test.ts +243 -0
  23. package/src/__tests__/audit/graph-revocation-roundtrip.test.ts +280 -0
  24. package/src/__tests__/audit/helpers/crypto-helpers.ts +245 -0
  25. package/src/__tests__/audit/proof-boundary.test.ts +269 -0
  26. package/src/__tests__/audit/statuslist-bitstring-roundtrip.test.ts +135 -0
  27. package/src/__tests__/audit/vc-roundtrip.test.ts +290 -0
  28. package/src/delegation/__tests__/outbound-headers.test.ts +16 -16
  29. package/src/delegation/__tests__/transitive-access.test.ts +1233 -0
  30. package/src/delegation/__tests__/vc-issuer.integration.test.ts +136 -0
  31. package/src/delegation/__tests__/vc-jwt.test.ts +318 -0
  32. package/src/delegation/__tests__/vc-verifier.integration.test.ts +199 -0
  33. package/src/delegation/cascading-revocation.ts +3 -1
  34. package/src/delegation/outbound-headers.ts +16 -16
  35. package/src/delegation/outbound-proof.ts +1 -1
  36. package/src/delegation/statuslist-manager.ts +17 -0
  37. package/src/middleware/with-mcpi-server.ts +1 -5
  38. package/src/middleware/with-mcpi.ts +29 -0
  39. package/src/proof/__tests__/verifier.integration.test.ts +181 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Canonicalization Integrity Audit Tests
3
+ *
4
+ * Verifies that JSON canonicalization (RFC 8785 JCS) produces deterministic
5
+ * output, that assertJsonSafe rejects dangerous inputs, and that
6
+ * canonicalization is consistent across modules.
7
+ */
8
+
9
+ import { describe, it, expect } from 'vitest';
10
+ import { canonicalizeJSON } from '../../delegation/utils.js';
11
+ import { canonicalize } from 'json-canonicalize';
12
+ import { ProofGenerator } from '../../proof/generator.js';
13
+ import { NodeCryptoProvider } from '../utils/node-crypto-provider.js';
14
+ import { MemoryIdentityProvider } from '../../providers/memory.js';
15
+
16
+ describe('Canonicalization Integrity Audit', () => {
17
+ // ── Determinism ───────────────────────────────────────────────
18
+
19
+ describe('deterministic output', () => {
20
+ it('should produce identical output regardless of key insertion order', () => {
21
+ const a = canonicalizeJSON({ b: 1, a: 2 });
22
+ const b = canonicalizeJSON({ a: 2, b: 1 });
23
+ expect(a).toBe(b);
24
+ });
25
+
26
+ it('should produce identical output for nested objects with different key orders', () => {
27
+ const a = canonicalizeJSON({
28
+ z: { y: 1, x: 2 },
29
+ a: { c: 3, b: 4 },
30
+ });
31
+ const b = canonicalizeJSON({
32
+ a: { b: 4, c: 3 },
33
+ z: { x: 2, y: 1 },
34
+ });
35
+ expect(a).toBe(b);
36
+ });
37
+
38
+ it('should handle arrays with objects in deterministic order', () => {
39
+ const a = canonicalizeJSON([
40
+ { z: 1, a: 2 },
41
+ { b: 3, a: 4 },
42
+ ]);
43
+ const b = canonicalizeJSON([
44
+ { a: 2, z: 1 },
45
+ { a: 4, b: 3 },
46
+ ]);
47
+ expect(a).toBe(b);
48
+ });
49
+
50
+ it('should handle deeply nested structures deterministically', () => {
51
+ const a = canonicalizeJSON({
52
+ level1: {
53
+ level2: {
54
+ level3: { c: 3, b: 2, a: 1 },
55
+ },
56
+ },
57
+ });
58
+ const b = canonicalizeJSON({
59
+ level1: {
60
+ level2: {
61
+ level3: { a: 1, b: 2, c: 3 },
62
+ },
63
+ },
64
+ });
65
+ expect(a).toBe(b);
66
+ });
67
+ });
68
+
69
+ // ── assertJsonSafe Guards ─────────────────────────────────────
70
+
71
+ describe('assertJsonSafe rejection of non-JSON values', () => {
72
+ it('should reject Infinity', () => {
73
+ expect(() => canonicalizeJSON({ val: Infinity })).toThrow(TypeError);
74
+ });
75
+
76
+ it('should reject -Infinity', () => {
77
+ expect(() => canonicalizeJSON({ val: -Infinity })).toThrow(TypeError);
78
+ });
79
+
80
+ it('should reject NaN', () => {
81
+ expect(() => canonicalizeJSON({ val: NaN })).toThrow(TypeError);
82
+ });
83
+
84
+ it('should reject undefined', () => {
85
+ expect(() => canonicalizeJSON(undefined)).toThrow(TypeError);
86
+ });
87
+
88
+ it('should reject functions', () => {
89
+ expect(() => canonicalizeJSON({ fn: () => {} })).toThrow(TypeError);
90
+ });
91
+
92
+ it('should reject symbols', () => {
93
+ expect(() => canonicalizeJSON({ sym: Symbol('test') })).toThrow(TypeError);
94
+ });
95
+
96
+ it('should reject bigint', () => {
97
+ expect(() => canonicalizeJSON({ big: BigInt(42) })).toThrow(TypeError);
98
+ });
99
+
100
+ it('should reject nested non-finite values', () => {
101
+ expect(() =>
102
+ canonicalizeJSON({ nested: { deep: { val: NaN } } })
103
+ ).toThrow(TypeError);
104
+ });
105
+
106
+ it('should reject non-finite values in arrays', () => {
107
+ expect(() => canonicalizeJSON([1, 2, Infinity])).toThrow(TypeError);
108
+ });
109
+ });
110
+
111
+ // ── Valid JSON Values ─────────────────────────────────────────
112
+
113
+ describe('valid JSON values are accepted', () => {
114
+ it('should accept null', () => {
115
+ expect(() => canonicalizeJSON(null)).not.toThrow();
116
+ });
117
+
118
+ it('should accept booleans', () => {
119
+ expect(() => canonicalizeJSON(true)).not.toThrow();
120
+ expect(() => canonicalizeJSON(false)).not.toThrow();
121
+ });
122
+
123
+ it('should accept finite numbers', () => {
124
+ expect(() => canonicalizeJSON(42)).not.toThrow();
125
+ expect(() => canonicalizeJSON(-0.5)).not.toThrow();
126
+ expect(() => canonicalizeJSON(0)).not.toThrow();
127
+ });
128
+
129
+ it('should accept strings', () => {
130
+ expect(() => canonicalizeJSON('hello')).not.toThrow();
131
+ });
132
+
133
+ it('should accept empty objects and arrays', () => {
134
+ expect(() => canonicalizeJSON({})).not.toThrow();
135
+ expect(() => canonicalizeJSON([])).not.toThrow();
136
+ });
137
+ });
138
+
139
+ // ── Cross-Module Consistency ──────────────────────────────────
140
+
141
+ describe('cross-module consistency', () => {
142
+ it('should match json-canonicalize output for safe inputs', () => {
143
+ const input = {
144
+ method: 'tools/call',
145
+ params: { name: 'test-tool', arguments: { x: 1, y: 'hello' } },
146
+ };
147
+
148
+ const fromUtils = canonicalizeJSON(input);
149
+ const fromLib = canonicalize(input);
150
+
151
+ expect(fromUtils).toBe(fromLib);
152
+ });
153
+
154
+ it('should match for VC-like structures', () => {
155
+ const vcLike = {
156
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
157
+ type: ['VerifiableCredential', 'DelegationCredential'],
158
+ issuer: 'did:key:z6MkTest',
159
+ credentialSubject: {
160
+ id: 'did:key:z6MkSubject',
161
+ delegation: {
162
+ scopes: ['tools:read'],
163
+ constraints: { notAfter: 1234567890 },
164
+ },
165
+ },
166
+ };
167
+
168
+ expect(canonicalizeJSON(vcLike)).toBe(canonicalize(vcLike));
169
+ });
170
+ });
171
+
172
+ // ── ProofGenerator Hash Determinism ───────────────────────────
173
+
174
+ describe('ProofGenerator hash determinism across key orderings', () => {
175
+ it('should produce same requestHash for objects with different key orders', async () => {
176
+ const crypto = new NodeCryptoProvider();
177
+ const identityProvider = new MemoryIdentityProvider(crypto);
178
+ const agent = await identityProvider.getIdentity();
179
+
180
+ const gen = new ProofGenerator(
181
+ { did: agent.did, kid: agent.kid, privateKey: agent.privateKey, publicKey: agent.publicKey },
182
+ crypto
183
+ );
184
+
185
+ const session = {
186
+ sessionId: 'sess_canon_test',
187
+ audience: 'did:web:server.example.com',
188
+ nonce: 'test-nonce-canon',
189
+ timestamp: Math.floor(Date.now() / 1000),
190
+ createdAt: Math.floor(Date.now() / 1000),
191
+ lastActivity: Math.floor(Date.now() / 1000),
192
+ ttlMinutes: 30,
193
+ identityState: 'anonymous' as const,
194
+ };
195
+
196
+ const request1 = { method: 'tools/call', params: { x: 1, y: 2, z: 3 } };
197
+ const request2 = { method: 'tools/call', params: { z: 3, x: 1, y: 2 } };
198
+ const response = { data: { result: 'ok' } };
199
+
200
+ const proof1 = await gen.generateProof(request1, response, session);
201
+ const proof2 = await gen.generateProof(request2, response, session);
202
+
203
+ expect(proof1.meta.requestHash).toBe(proof2.meta.requestHash);
204
+ });
205
+
206
+ it('should produce different hashes for genuinely different inputs', async () => {
207
+ const crypto = new NodeCryptoProvider();
208
+ const identityProvider = new MemoryIdentityProvider(crypto);
209
+ const agent = await identityProvider.getIdentity();
210
+
211
+ const gen = new ProofGenerator(
212
+ { did: agent.did, kid: agent.kid, privateKey: agent.privateKey, publicKey: agent.publicKey },
213
+ crypto
214
+ );
215
+
216
+ const session = {
217
+ sessionId: 'sess_diff_test',
218
+ audience: 'did:web:server.example.com',
219
+ nonce: 'test-nonce-diff',
220
+ timestamp: Math.floor(Date.now() / 1000),
221
+ createdAt: Math.floor(Date.now() / 1000),
222
+ lastActivity: Math.floor(Date.now() / 1000),
223
+ ttlMinutes: 30,
224
+ identityState: 'anonymous' as const,
225
+ };
226
+
227
+ const response = { data: { result: 'ok' } };
228
+
229
+ const proof1 = await gen.generateProof(
230
+ { method: 'tools/call', params: { input: 'alice' } },
231
+ response,
232
+ session
233
+ );
234
+ const proof2 = await gen.generateProof(
235
+ { method: 'tools/call', params: { input: 'bob' } },
236
+ response,
237
+ session
238
+ );
239
+
240
+ expect(proof1.meta.requestHash).not.toBe(proof2.meta.requestHash);
241
+ });
242
+ });
243
+ });
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Graph-Revocation Round-Trip Audit Tests
3
+ *
4
+ * Tests the delegation graph → cascading revocation pipeline with real
5
+ * StatusList2021 bitstring encoding/decoding and real gzip compression.
6
+ * No mocking of graph storage, status lists, or compression.
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, beforeAll } from 'vitest';
10
+ import { DelegationGraphManager } from '../../delegation/delegation-graph.js';
11
+ import { CascadingRevocationManager } from '../../delegation/cascading-revocation.js';
12
+ import type { AgentIdentity } from '../../providers/base.js';
13
+ import {
14
+ createRealCryptoProvider,
15
+ createRealIdentity,
16
+ createRealStatusListManager,
17
+ MemoryDelegationGraphStorage,
18
+ type RealStatusListSetup,
19
+ } from './helpers/crypto-helpers.js';
20
+ import { NodeCryptoProvider } from '../utils/node-crypto-provider.js';
21
+
22
+ describe('Graph-Revocation Round-Trip Audit', () => {
23
+ let crypto: NodeCryptoProvider;
24
+ let issuerIdentity: AgentIdentity;
25
+ let graph: DelegationGraphManager;
26
+ let graphStorage: MemoryDelegationGraphStorage;
27
+ let statusListSetup: RealStatusListSetup;
28
+ let revocationManager: CascadingRevocationManager;
29
+
30
+ // Create identities for chain: issuer -> agentA -> agentB -> agentC
31
+ let agentA: AgentIdentity;
32
+ let agentB: AgentIdentity;
33
+ let agentC: AgentIdentity;
34
+
35
+ beforeAll(async () => {
36
+ crypto = createRealCryptoProvider();
37
+ issuerIdentity = await createRealIdentity(crypto);
38
+ agentA = await createRealIdentity(crypto);
39
+ agentB = await createRealIdentity(crypto);
40
+ agentC = await createRealIdentity(crypto);
41
+ });
42
+
43
+ beforeEach(async () => {
44
+ graphStorage = new MemoryDelegationGraphStorage();
45
+ graph = new DelegationGraphManager(graphStorage);
46
+
47
+ statusListSetup = createRealStatusListManager(crypto, issuerIdentity, {
48
+ statusListBaseUrl: 'https://status.test.example.com',
49
+ });
50
+
51
+ revocationManager = new CascadingRevocationManager(graph, statusListSetup.manager);
52
+ });
53
+
54
+ // Helper: register a delegation and allocate a status entry
55
+ async function registerWithStatus(
56
+ id: string,
57
+ parentId: string | null,
58
+ issuerDid: string,
59
+ subjectDid: string
60
+ ): Promise<string> {
61
+ const statusEntry = await statusListSetup.manager.allocateStatusEntry('revocation');
62
+ await graph.registerDelegation({
63
+ id,
64
+ parentId,
65
+ issuerDid,
66
+ subjectDid,
67
+ credentialStatusId: statusEntry.id,
68
+ });
69
+ return statusEntry.id;
70
+ }
71
+
72
+ // ── Chain Validation ──────────────────────────────────────────
73
+
74
+ describe('chain validation with real graph', () => {
75
+ it('should validate a correctly chained delegation', async () => {
76
+ // issuer -> agentA -> agentB -> agentC
77
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
78
+ await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
79
+ await registerWithStatus('del-grandchild', 'del-child', agentB.did, agentC.did);
80
+
81
+ const result = await graph.validateChain('del-grandchild');
82
+ expect(result.valid).toBe(true);
83
+ });
84
+
85
+ it('should reject chain with issuer/subject mismatch', async () => {
86
+ // root: issuer -> agentA, child: agentC -> agentB (mismatch: agentC != agentA)
87
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
88
+ await registerWithStatus('del-child', 'del-root', agentC.did, agentB.did);
89
+
90
+ const result = await graph.validateChain('del-child');
91
+ expect(result.valid).toBe(false);
92
+ expect(result.reason).toBeDefined();
93
+ });
94
+ });
95
+
96
+ // ── Cascading Revocation ──────────────────────────────────────
97
+
98
+ describe('cascading revocation with real StatusList2021', () => {
99
+ it('should revoke root and cascade to all descendants via StatusList', async () => {
100
+ const rootStatusId = await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
101
+ const childStatusId = await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
102
+ const grandchildStatusId = await registerWithStatus('del-gc', 'del-child', agentB.did, agentC.did);
103
+
104
+ const events = await revocationManager.revokeDelegation('del-root');
105
+
106
+ expect(events.length).toBe(3);
107
+ expect(events[0]!.isRoot).toBe(true);
108
+
109
+ // Verify via real StatusList that all are revoked
110
+ const rootRevoked = await revocationManager.isRevoked('del-root');
111
+ const childRevoked = await revocationManager.isRevoked('del-child');
112
+ const gcRevoked = await revocationManager.isRevoked('del-gc');
113
+
114
+ expect(rootRevoked.revoked).toBe(true);
115
+ expect(childRevoked.revoked).toBe(true);
116
+ expect(gcRevoked.revoked).toBe(true);
117
+ });
118
+
119
+ it('should only revoke descendants, not siblings or ancestors', async () => {
120
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
121
+ await registerWithStatus('del-child1', 'del-root', agentA.did, agentB.did);
122
+ await registerWithStatus('del-child2', 'del-root', agentA.did, agentC.did);
123
+
124
+ // Revoke child1 only
125
+ await revocationManager.revokeDelegation('del-child1');
126
+
127
+ const child1 = await revocationManager.isRevoked('del-child1');
128
+ const child2 = await revocationManager.isRevoked('del-child2');
129
+ const root = await revocationManager.isRevoked('del-root');
130
+
131
+ expect(child1.revoked).toBe(true);
132
+ expect(child2.revoked).toBe(false);
133
+ expect(root.revoked).toBe(false);
134
+ });
135
+
136
+ it('should detect ancestor revocation through chain walk', async () => {
137
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
138
+ await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
139
+
140
+ await revocationManager.revokeDelegation('del-root');
141
+
142
+ const childStatus = await revocationManager.isRevoked('del-child');
143
+ expect(childStatus.revoked).toBe(true);
144
+ expect(childStatus.revokedAncestor).toBe('del-root');
145
+ });
146
+ });
147
+
148
+ // ── Restore Behavior ──────────────────────────────────────────
149
+
150
+ describe('restore delegation', () => {
151
+ it('should restore root but NOT restore cascaded children', async () => {
152
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
153
+ await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
154
+
155
+ // Revoke root (cascades to child)
156
+ await revocationManager.revokeDelegation('del-root');
157
+
158
+ const rootBefore = await revocationManager.isRevoked('del-root');
159
+ const childBefore = await revocationManager.isRevoked('del-child');
160
+ expect(rootBefore.revoked).toBe(true);
161
+ expect(childBefore.revoked).toBe(true);
162
+
163
+ // Restore root only
164
+ await revocationManager.restoreDelegation('del-root');
165
+
166
+ const rootAfter = await revocationManager.isRevoked('del-root');
167
+ const childAfter = await revocationManager.isRevoked('del-child');
168
+
169
+ expect(rootAfter.revoked).toBe(false);
170
+ // Child should STILL be revoked — restore is not recursive
171
+ expect(childAfter.revoked).toBe(true);
172
+ });
173
+ });
174
+
175
+ // ── Dry-Run ───────────────────────────────────────────────────
176
+
177
+ describe('dry-run mode', () => {
178
+ it('should not modify StatusList bits in dry-run', async () => {
179
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
180
+ await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
181
+
182
+ const events = await revocationManager.revokeDelegation('del-root', { dryRun: true });
183
+
184
+ expect(events.length).toBe(2);
185
+
186
+ // Nothing should actually be revoked
187
+ const root = await revocationManager.isRevoked('del-root');
188
+ const child = await revocationManager.isRevoked('del-child');
189
+
190
+ expect(root.revoked).toBe(false);
191
+ expect(child.revoked).toBe(false);
192
+ });
193
+ });
194
+
195
+ // ── Deep Chain ────────────────────────────────────────────────
196
+
197
+ describe('deep chain cascading', () => {
198
+ it('should cascade through a depth-10 chain', async () => {
199
+ // Build chain of depth 10
200
+ const dids = [issuerIdentity, agentA, agentB, agentC];
201
+ let parentId: string | null = null;
202
+
203
+ for (let i = 0; i < 10; i++) {
204
+ const issuerIdx = i % dids.length;
205
+ const subjectIdx = (i + 1) % dids.length;
206
+ const id = `del-depth-${i}`;
207
+
208
+ await registerWithStatus(
209
+ id,
210
+ parentId,
211
+ dids[issuerIdx]!.did,
212
+ dids[subjectIdx]!.did
213
+ );
214
+ parentId = id;
215
+ }
216
+
217
+ // Revoke root — all 9 descendants should be revoked
218
+ const events = await revocationManager.revokeDelegation('del-depth-0');
219
+ expect(events.length).toBe(10);
220
+
221
+ for (let i = 0; i < 10; i++) {
222
+ const status = await revocationManager.isRevoked(`del-depth-${i}`);
223
+ expect(status.revoked).toBe(true);
224
+ }
225
+ });
226
+ });
227
+
228
+ // ── Concurrent Revocation ─────────────────────────────────────
229
+
230
+ describe('concurrent revocation', () => {
231
+ it('should handle concurrent revocation of sibling subtrees', async () => {
232
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
233
+ await registerWithStatus('del-child1', 'del-root', agentA.did, agentB.did);
234
+ await registerWithStatus('del-gc1', 'del-child1', agentB.did, agentC.did);
235
+ await registerWithStatus('del-child2', 'del-root', agentA.did, agentC.did);
236
+ await registerWithStatus('del-gc2', 'del-child2', agentC.did, agentB.did);
237
+
238
+ // Revoke both subtrees concurrently
239
+ const [events1, events2] = await Promise.all([
240
+ revocationManager.revokeDelegation('del-child1'),
241
+ revocationManager.revokeDelegation('del-child2'),
242
+ ]);
243
+
244
+ expect(events1.length).toBeGreaterThanOrEqual(2);
245
+ expect(events2.length).toBeGreaterThanOrEqual(2);
246
+
247
+ // All 4 nodes should be revoked
248
+ for (const id of ['del-child1', 'del-gc1', 'del-child2', 'del-gc2']) {
249
+ const status = await revocationManager.isRevoked(id);
250
+ expect(status.revoked).toBe(true);
251
+ }
252
+
253
+ // Root should not be revoked
254
+ const root = await revocationManager.isRevoked('del-root');
255
+ expect(root.revoked).toBe(false);
256
+ });
257
+ });
258
+
259
+ // ── validateDelegation ────────────────────────────────────────
260
+
261
+ describe('validateDelegation', () => {
262
+ it('should return valid for non-revoked, well-formed chain', async () => {
263
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
264
+ await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
265
+
266
+ const result = await revocationManager.validateDelegation('del-child');
267
+ expect(result.valid).toBe(true);
268
+ });
269
+
270
+ it('should return invalid after ancestor revocation', async () => {
271
+ await registerWithStatus('del-root', null, issuerIdentity.did, agentA.did);
272
+ await registerWithStatus('del-child', 'del-root', agentA.did, agentB.did);
273
+
274
+ await revocationManager.revokeDelegation('del-root');
275
+
276
+ const result = await revocationManager.validateDelegation('del-child');
277
+ expect(result.valid).toBe(false);
278
+ });
279
+ });
280
+ });