@longarc/mdash 3.1.2 → 3.1.3
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.
- package/README.md +86 -23
- package/SECURITY.md +254 -0
- package/dist/accountability/engine.d.ts +27 -0
- package/dist/accountability/engine.d.ts.map +1 -0
- package/dist/accountability/engine.js +148 -0
- package/dist/accountability/engine.js.map +1 -0
- package/dist/accountability/types.d.ts +46 -0
- package/dist/accountability/types.d.ts.map +1 -0
- package/dist/accountability/types.js +8 -0
- package/dist/accountability/types.js.map +1 -0
- package/dist/checkpoint/engine.d.ts.map +1 -1
- package/dist/checkpoint/engine.js +4 -0
- package/dist/checkpoint/engine.js.map +1 -1
- package/dist/context/compose.d.ts +62 -0
- package/dist/context/compose.d.ts.map +1 -0
- package/dist/context/compose.js +286 -0
- package/dist/context/compose.js.map +1 -0
- package/dist/context/crypto/hash.d.ts +100 -0
- package/dist/context/crypto/hash.d.ts.map +1 -0
- package/dist/context/crypto/hash.js +248 -0
- package/dist/context/crypto/hash.js.map +1 -0
- package/dist/context/crypto/hmac.d.ts +80 -0
- package/dist/context/crypto/hmac.d.ts.map +1 -0
- package/dist/context/crypto/hmac.js +192 -0
- package/dist/context/crypto/hmac.js.map +1 -0
- package/dist/context/crypto/index.d.ts +7 -0
- package/dist/context/crypto/index.d.ts.map +1 -0
- package/dist/context/crypto/index.js +7 -0
- package/dist/context/crypto/index.js.map +1 -0
- package/dist/context/engine-v3.0-backup.d.ts +197 -0
- package/dist/context/engine-v3.0-backup.d.ts.map +1 -0
- package/dist/context/engine-v3.0-backup.js +392 -0
- package/dist/context/engine-v3.0-backup.js.map +1 -0
- package/dist/context/fragment.d.ts +99 -0
- package/dist/context/fragment.d.ts.map +1 -0
- package/dist/context/fragment.js +316 -0
- package/dist/context/fragment.js.map +1 -0
- package/dist/context/index.d.ts +99 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +180 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/provenance.d.ts +80 -0
- package/dist/context/provenance.d.ts.map +1 -0
- package/dist/context/provenance.js +294 -0
- package/dist/context/provenance.js.map +1 -0
- package/dist/context/resolve.d.ts +106 -0
- package/dist/context/resolve.d.ts.map +1 -0
- package/dist/context/resolve.js +440 -0
- package/dist/context/resolve.js.map +1 -0
- package/dist/context/store.d.ts +156 -0
- package/dist/context/store.d.ts.map +1 -0
- package/dist/context/store.js +396 -0
- package/dist/context/store.js.map +1 -0
- package/dist/context/types.d.ts +463 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +94 -0
- package/dist/context/types.js.map +1 -0
- package/dist/context/utils/atomic.d.ts +76 -0
- package/dist/context/utils/atomic.d.ts.map +1 -0
- package/dist/context/utils/atomic.js +159 -0
- package/dist/context/utils/atomic.js.map +1 -0
- package/dist/context/utils/credit.d.ts +65 -0
- package/dist/context/utils/credit.d.ts.map +1 -0
- package/dist/context/utils/credit.js +164 -0
- package/dist/context/utils/credit.js.map +1 -0
- package/dist/context/utils/index.d.ts +13 -0
- package/dist/context/utils/index.d.ts.map +1 -0
- package/dist/context/utils/index.js +13 -0
- package/dist/context/utils/index.js.map +1 -0
- package/dist/context/utils/utility.d.ts +63 -0
- package/dist/context/utils/utility.d.ts.map +1 -0
- package/dist/context/utils/utility.js +141 -0
- package/dist/context/utils/utility.js.map +1 -0
- package/dist/core/commitment.d.ts +25 -2
- package/dist/core/commitment.d.ts.map +1 -1
- package/dist/core/commitment.js +44 -6
- package/dist/core/commitment.js.map +1 -1
- package/dist/core/crypto.d.ts +2 -0
- package/dist/core/crypto.d.ts.map +1 -1
- package/dist/core/crypto.js +12 -0
- package/dist/core/crypto.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -10
- package/dist/index.js.map +1 -1
- package/dist/mcca/engine.d.ts.map +1 -1
- package/dist/mcca/engine.js +5 -4
- package/dist/mcca/engine.js.map +1 -1
- package/dist/physics/engine.d.ts +1 -0
- package/dist/physics/engine.d.ts.map +1 -1
- package/dist/physics/engine.js +36 -2
- package/dist/physics/engine.js.map +1 -1
- package/dist/provenance/api-handler.d.ts +45 -0
- package/dist/provenance/api-handler.d.ts.map +1 -0
- package/dist/provenance/api-handler.js +223 -0
- package/dist/provenance/api-handler.js.map +1 -0
- package/dist/provenance/api-types.d.ts +108 -0
- package/dist/provenance/api-types.d.ts.map +1 -0
- package/dist/provenance/api-types.js +9 -0
- package/dist/provenance/api-types.js.map +1 -0
- package/dist/provenance/index.d.ts +6 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/provenance/index.js +3 -0
- package/dist/provenance/index.js.map +1 -0
- package/dist/provenance/provenance-engine.d.ts +63 -0
- package/dist/provenance/provenance-engine.d.ts.map +1 -0
- package/dist/provenance/provenance-engine.js +311 -0
- package/dist/provenance/provenance-engine.js.map +1 -0
- package/dist/provenance/types.d.ts +193 -0
- package/dist/provenance/types.d.ts.map +1 -0
- package/dist/provenance/types.js +9 -0
- package/dist/provenance/types.js.map +1 -0
- package/dist/tee/engine.d.ts.map +1 -1
- package/dist/tee/engine.js +14 -0
- package/dist/tee/engine.js.map +1 -1
- package/dist/warrant/engine.d.ts +24 -1
- package/dist/warrant/engine.d.ts.map +1 -1
- package/dist/warrant/engine.js +76 -1
- package/dist/warrant/engine.js.map +1 -1
- package/dist/zk/engine.d.ts.map +1 -1
- package/dist/zk/engine.js +7 -4
- package/dist/zk/engine.js.map +1 -1
- package/docs/SECURITY-PATCHES.md +170 -0
- package/package.json +17 -5
- package/src/__tests__/accountability.test.ts +308 -0
- package/src/__tests__/l1-verification-modes.test.ts +424 -0
- package/src/__tests__/phase1.benchmark.test.ts +94 -0
- package/src/__tests__/phase1.test.ts +0 -77
- package/src/__tests__/phase2-4.benchmark.test.ts +60 -0
- package/src/__tests__/phase2-4.test.ts +1 -52
- package/src/__tests__/provenance/api-handler.test.ts +356 -0
- package/src/__tests__/provenance/provenance-engine.test.ts +628 -0
- package/src/__tests__/sa-2026-008.test.ts +45 -0
- package/src/__tests__/sa-2026-009.test.ts +86 -0
- package/src/__tests__/sa-2026-010.test.ts +72 -0
- package/src/__tests__/sa-2026-012.test.ts +65 -0
- package/src/__tests__/sa-2026-nfc.test.ts +40 -0
- package/src/__tests__/security.test.ts +786 -0
- package/src/accountability/engine.ts +230 -0
- package/src/accountability/types.ts +58 -0
- package/src/checkpoint/engine.ts +4 -0
- package/src/context/__tests__/caret-v0.2.0.test.ts +860 -0
- package/src/context/__tests__/integration.test.ts +356 -0
- package/src/context/compose.ts +388 -0
- package/src/context/crypto/hash.ts +277 -0
- package/src/context/crypto/hmac.ts +253 -0
- package/src/context/crypto/index.ts +29 -0
- package/src/context/engine-v3.0-backup.ts +598 -0
- package/src/context/fragment.ts +454 -0
- package/src/context/index.ts +427 -0
- package/src/context/provenance.ts +380 -0
- package/src/context/resolve.ts +581 -0
- package/src/context/store.ts +503 -0
- package/src/context/types.ts +679 -0
- package/src/context/utils/atomic.ts +207 -0
- package/src/context/utils/credit.ts +224 -0
- package/src/context/utils/index.ts +13 -0
- package/src/context/utils/utility.ts +200 -0
- package/src/core/commitment.ts +129 -67
- package/src/core/crypto.ts +13 -0
- package/src/index.ts +62 -10
- package/src/mcca/engine.ts +5 -4
- package/src/physics/engine.ts +40 -3
- package/src/provenance/api-handler.ts +248 -0
- package/src/provenance/api-types.ts +112 -0
- package/src/provenance/index.ts +19 -0
- package/src/provenance/provenance-engine.ts +387 -0
- package/src/provenance/types.ts +211 -0
- package/src/tee/engine.ts +16 -0
- package/src/warrant/engine.ts +89 -1
- package/src/zk/engine.ts +8 -4
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProvenanceEngine Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Validates model provenance attestation: identity creation,
|
|
5
|
+
* chain building, query verification, and the distilled model
|
|
6
|
+
* failure scenario.
|
|
7
|
+
*
|
|
8
|
+
* Invariants under test:
|
|
9
|
+
* PROVENANCE-INV-001: Identity attestation is immutable after creation
|
|
10
|
+
* PROVENANCE-INV-002: Provenance chain hash changes if ANY component changes
|
|
11
|
+
* PROVENANCE-INV-003: Verdict is deterministic: same chain = same verdict
|
|
12
|
+
* PROVENANCE-INV-004: ZK proof verifies without access to the chain data
|
|
13
|
+
* PROVENANCE-INV-005: A model with no identity attestation has verdict.isComplete = false
|
|
14
|
+
* PROVENANCE-INV-006: Distilled models cannot produce valid provider signatures
|
|
15
|
+
* PROVENANCE-INV-007: Chain attestation (L1 hash) includes identity + warrants + behavior
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
19
|
+
import { ProvenanceEngine } from '../../provenance/provenance-engine.js';
|
|
20
|
+
import { sha256, sha256Object, deterministicStringify } from '../../core/crypto.js';
|
|
21
|
+
import type {
|
|
22
|
+
ModelIdentityAttestation,
|
|
23
|
+
ProvenanceChain,
|
|
24
|
+
GlossEntry,
|
|
25
|
+
} from '../../provenance/types.js';
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Fixtures
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
function makeModel(): ModelIdentityAttestation['model'] {
|
|
32
|
+
return {
|
|
33
|
+
name: 'claude-opus-4.6',
|
|
34
|
+
version: '4.6.0',
|
|
35
|
+
manifestHash: 'abc123def456',
|
|
36
|
+
provider: 'anthropic',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeConstraints(): ModelIdentityAttestation['constraints'] {
|
|
41
|
+
return {
|
|
42
|
+
safetyTier: 'high',
|
|
43
|
+
authorizedDomains: ['code_generation', 'analysis', 'reasoning'],
|
|
44
|
+
excludedDomains: ['weapons', 'malware'],
|
|
45
|
+
maxContextWindow: 200000,
|
|
46
|
+
reasoningEnabled: true,
|
|
47
|
+
custom: { maxOutputTokens: 32000 },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function makeDeployment(): ModelIdentityAttestation['deployment'] {
|
|
52
|
+
return {
|
|
53
|
+
environment: 'api',
|
|
54
|
+
region: 'us-east-1',
|
|
55
|
+
tenantId: 'longarc-studios',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function makeGlossEntries(count: number, violations: number = 0): GlossEntry[] {
|
|
60
|
+
const entries: GlossEntry[] = [];
|
|
61
|
+
for (let i = 0; i < count; i++) {
|
|
62
|
+
entries.push({
|
|
63
|
+
id: `gloss-${i}`,
|
|
64
|
+
type: i < violations ? 'violation' : 'action',
|
|
65
|
+
agentId: 'agent-001',
|
|
66
|
+
sessionId: `session-${i % 3}`,
|
|
67
|
+
timestamp: new Date(Date.now() - (count - i) * 1000).toISOString(),
|
|
68
|
+
isViolation: i < violations,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return entries;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function makeWarrantHashes(count: number): Promise<string[]> {
|
|
75
|
+
const hashes: string[] = [];
|
|
76
|
+
for (let i = 0; i < count; i++) {
|
|
77
|
+
hashes.push(await sha256(`warrant-${i}`));
|
|
78
|
+
}
|
|
79
|
+
return hashes;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Identity Attestation Tests
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
describe('ProvenanceEngine', () => {
|
|
87
|
+
let engine: ProvenanceEngine;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
engine = new ProvenanceEngine();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('Identity Attestation', () => {
|
|
94
|
+
it('creates valid attestation with all required fields', async () => {
|
|
95
|
+
const attestation = await engine.createIdentityAttestation(
|
|
96
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(attestation.id).toBeTruthy();
|
|
100
|
+
expect(attestation.model.name).toBe('claude-opus-4.6');
|
|
101
|
+
expect(attestation.model.version).toBe('4.6.0');
|
|
102
|
+
expect(attestation.model.provider).toBe('anthropic');
|
|
103
|
+
expect(attestation.constraints.safetyTier).toBe('high');
|
|
104
|
+
expect(attestation.deployment.environment).toBe('api');
|
|
105
|
+
expect(attestation.attestation.l1Hash).toBeTruthy();
|
|
106
|
+
expect(attestation.attestation.providerSignature).toBeTruthy();
|
|
107
|
+
expect(attestation.attestation.timestamp).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('attestation ID is deterministic (same inputs = same ID)', async () => {
|
|
111
|
+
const a1 = await engine.createIdentityAttestation(
|
|
112
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
113
|
+
);
|
|
114
|
+
const engine2 = new ProvenanceEngine();
|
|
115
|
+
const a2 = await engine2.createIdentityAttestation(
|
|
116
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(a1.id).toBe(a2.id);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('L1 hash is deterministic', async () => {
|
|
123
|
+
const a1 = await engine.createIdentityAttestation(
|
|
124
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
125
|
+
);
|
|
126
|
+
const engine2 = new ProvenanceEngine();
|
|
127
|
+
const a2 = await engine2.createIdentityAttestation(
|
|
128
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(a1.attestation.l1Hash).toBe(a2.attestation.l1Hash);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('provider signature is present and deterministic', async () => {
|
|
135
|
+
const a1 = await engine.createIdentityAttestation(
|
|
136
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(a1.attestation.providerSignature).toMatch(/^[a-f0-9]{64}$/);
|
|
140
|
+
|
|
141
|
+
const engine2 = new ProvenanceEngine();
|
|
142
|
+
const a2 = await engine2.createIdentityAttestation(
|
|
143
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
144
|
+
);
|
|
145
|
+
expect(a1.attestation.providerSignature).toBe(a2.attestation.providerSignature);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('missing model name throws', async () => {
|
|
149
|
+
const model = makeModel();
|
|
150
|
+
model.name = '';
|
|
151
|
+
await expect(
|
|
152
|
+
engine.createIdentityAttestation(model, makeConstraints(), makeDeployment())
|
|
153
|
+
).rejects.toThrow('Model name is required');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('missing provider throws', async () => {
|
|
157
|
+
const model = makeModel();
|
|
158
|
+
model.provider = '';
|
|
159
|
+
await expect(
|
|
160
|
+
engine.createIdentityAttestation(model, makeConstraints(), makeDeployment())
|
|
161
|
+
).rejects.toThrow('Model provider is required');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('missing version throws', async () => {
|
|
165
|
+
const model = makeModel();
|
|
166
|
+
model.version = '';
|
|
167
|
+
await expect(
|
|
168
|
+
engine.createIdentityAttestation(model, makeConstraints(), makeDeployment())
|
|
169
|
+
).rejects.toThrow('Model version is required');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('attestation timestamp is ISO format', async () => {
|
|
173
|
+
const attestation = await engine.createIdentityAttestation(
|
|
174
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(attestation.attestation.timestamp).toMatch(
|
|
178
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('different model inputs produce different IDs', async () => {
|
|
183
|
+
const a1 = await engine.createIdentityAttestation(
|
|
184
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
185
|
+
);
|
|
186
|
+
const model2 = { ...makeModel(), name: 'gpt-5' };
|
|
187
|
+
const a2 = await engine.createIdentityAttestation(
|
|
188
|
+
model2, makeConstraints(), makeDeployment()
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(a1.id).not.toBe(a2.id);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Provenance Chain Tests
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
describe('Provenance Chain', () => {
|
|
200
|
+
let identity: ModelIdentityAttestation;
|
|
201
|
+
|
|
202
|
+
beforeEach(async () => {
|
|
203
|
+
identity = await engine.createIdentityAttestation(
|
|
204
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('complete chain has isComplete = true', async () => {
|
|
209
|
+
const warrants = await makeWarrantHashes(3);
|
|
210
|
+
const entries = makeGlossEntries(10);
|
|
211
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
212
|
+
|
|
213
|
+
expect(chain.verdict.isComplete).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('missing warrants produces warrant_gap flag', async () => {
|
|
217
|
+
const entries = makeGlossEntries(10);
|
|
218
|
+
const chain = await engine.buildProvenanceChain(identity, [], entries);
|
|
219
|
+
|
|
220
|
+
expect(chain.verdict.flags).toContain('warrant_gap');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('missing behavioral record produces no_behavior flag', async () => {
|
|
224
|
+
const warrants = await makeWarrantHashes(3);
|
|
225
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, []);
|
|
226
|
+
|
|
227
|
+
expect(chain.verdict.flags).toContain('no_behavior');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('drift score > 0.5 produces drift_detected flag', async () => {
|
|
231
|
+
const warrants = await makeWarrantHashes(3);
|
|
232
|
+
const entries = makeGlossEntries(10);
|
|
233
|
+
const chain = await engine.buildProvenanceChain(
|
|
234
|
+
identity, warrants, entries, { compositeScore: 0.6, recommendation: 'tighten' }
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(chain.verdict.flags).toContain('drift_detected');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('drift score > 0.8 mentions quarantine in assessment', async () => {
|
|
241
|
+
const warrants = await makeWarrantHashes(3);
|
|
242
|
+
const entries = makeGlossEntries(10);
|
|
243
|
+
const chain = await engine.buildProvenanceChain(
|
|
244
|
+
identity, warrants, entries, { compositeScore: 0.85, recommendation: 'quarantine' }
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(chain.verdict.assessment.toLowerCase()).toContain('quarantine');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('chain hash changes when any component changes (INV-002)', async () => {
|
|
251
|
+
const warrants = await makeWarrantHashes(3);
|
|
252
|
+
const entries = makeGlossEntries(10);
|
|
253
|
+
|
|
254
|
+
const chain1 = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
255
|
+
const chain2 = await engine.buildProvenanceChain(
|
|
256
|
+
identity, warrants, makeGlossEntries(20)
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(chain1.chainAttestation.l1Hash).not.toBe(chain2.chainAttestation.l1Hash);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('same chain produces same verdict (INV-003)', async () => {
|
|
263
|
+
const warrants = await makeWarrantHashes(3);
|
|
264
|
+
const entries = makeGlossEntries(10);
|
|
265
|
+
|
|
266
|
+
const chain1 = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
267
|
+
|
|
268
|
+
const engine2 = new ProvenanceEngine();
|
|
269
|
+
const identity2 = await engine2.createIdentityAttestation(
|
|
270
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
271
|
+
);
|
|
272
|
+
const chain2 = await engine2.buildProvenanceChain(identity2, warrants, entries);
|
|
273
|
+
|
|
274
|
+
expect(chain1.verdict.isComplete).toBe(chain2.verdict.isComplete);
|
|
275
|
+
expect(chain1.verdict.confidence).toBe(chain2.verdict.confidence);
|
|
276
|
+
expect(chain1.verdict.flags).toEqual(chain2.verdict.flags);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('chain with zero violations has high confidence', async () => {
|
|
280
|
+
const warrants = await makeWarrantHashes(5);
|
|
281
|
+
const entries = makeGlossEntries(20, 0);
|
|
282
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
283
|
+
|
|
284
|
+
expect(chain.verdict.confidence).toBeGreaterThanOrEqual(0.9);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('chain with critical violations has lower confidence', async () => {
|
|
288
|
+
const warrants = await makeWarrantHashes(3);
|
|
289
|
+
const entries = makeGlossEntries(10, 5); // 50% violations
|
|
290
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
291
|
+
|
|
292
|
+
expect(chain.verdict.confidence).toBeLessThan(0.9);
|
|
293
|
+
expect(chain.verdict.flags).toContain('high_violation_rate');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('chain attestation includes all three components (INV-007)', async () => {
|
|
297
|
+
const warrants = await makeWarrantHashes(3);
|
|
298
|
+
const entries = makeGlossEntries(10);
|
|
299
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
300
|
+
|
|
301
|
+
// Verify the chain attestation hash was built from identity + warrants + behavior
|
|
302
|
+
expect(chain.chainAttestation.l1Hash).toBeTruthy();
|
|
303
|
+
expect(chain.chainAttestation.l1Hash).toMatch(/^[a-f0-9]{64}$/);
|
|
304
|
+
expect(chain.chainAttestation.timestamp).toBeTruthy();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('behavioral record counts sessions correctly', async () => {
|
|
308
|
+
// makeGlossEntries uses sessionId = session-{i%3}, so 10 entries = 3 sessions
|
|
309
|
+
const entries = makeGlossEntries(10);
|
|
310
|
+
const warrants = await makeWarrantHashes(3);
|
|
311
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
312
|
+
|
|
313
|
+
expect(chain.behavioralRecord.totalSessions).toBe(3);
|
|
314
|
+
expect(chain.behavioralRecord.totalActions).toBe(10);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('tampered identity attestation throws on chain build', async () => {
|
|
318
|
+
const tampered = { ...identity };
|
|
319
|
+
tampered.attestation = { ...tampered.attestation, l1Hash: 'aaaa'.repeat(16) };
|
|
320
|
+
|
|
321
|
+
const warrants = await makeWarrantHashes(3);
|
|
322
|
+
const entries = makeGlossEntries(10);
|
|
323
|
+
|
|
324
|
+
await expect(
|
|
325
|
+
engine.buildProvenanceChain(tampered, warrants, entries)
|
|
326
|
+
).rejects.toThrow('hash mismatch');
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Provenance Query Tests
|
|
332
|
+
// ============================================================================
|
|
333
|
+
|
|
334
|
+
describe('Provenance Query', () => {
|
|
335
|
+
let identity: ModelIdentityAttestation;
|
|
336
|
+
|
|
337
|
+
beforeEach(async () => {
|
|
338
|
+
identity = await engine.createIdentityAttestation(
|
|
339
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
340
|
+
);
|
|
341
|
+
const warrants = await makeWarrantHashes(3);
|
|
342
|
+
const entries = makeGlossEntries(10);
|
|
343
|
+
await engine.buildProvenanceChain(identity, warrants, entries);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('identity query returns identity data', async () => {
|
|
347
|
+
const response = await engine.verifyProvenance({
|
|
348
|
+
queryType: 'identity',
|
|
349
|
+
modelName: 'claude-opus-4.6',
|
|
350
|
+
modelVersion: '4.6.0',
|
|
351
|
+
requireHardwareAttestation: false,
|
|
352
|
+
generateZkProof: false,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(response.chain.identity.model.name).toBe('claude-opus-4.6');
|
|
356
|
+
expect(response.chain.identity.model.provider).toBe('anthropic');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('constraints query returns identity + constraints', async () => {
|
|
360
|
+
const response = await engine.verifyProvenance({
|
|
361
|
+
queryType: 'constraints',
|
|
362
|
+
modelName: 'claude-opus-4.6',
|
|
363
|
+
modelVersion: '4.6.0',
|
|
364
|
+
requireHardwareAttestation: false,
|
|
365
|
+
generateZkProof: false,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
expect(response.chain.identity.constraints.safetyTier).toBe('high');
|
|
369
|
+
expect(response.chain.identity.constraints.authorizedDomains).toContain('code_generation');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('behavior query returns behavioral record', async () => {
|
|
373
|
+
const response = await engine.verifyProvenance({
|
|
374
|
+
queryType: 'behavior',
|
|
375
|
+
modelName: 'claude-opus-4.6',
|
|
376
|
+
modelVersion: '4.6.0',
|
|
377
|
+
requireHardwareAttestation: false,
|
|
378
|
+
generateZkProof: false,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(response.chain.behavioralRecord.totalActions).toBe(10);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('full_chain query returns complete chain', async () => {
|
|
385
|
+
const response = await engine.verifyProvenance({
|
|
386
|
+
queryType: 'full_chain',
|
|
387
|
+
modelName: 'claude-opus-4.6',
|
|
388
|
+
modelVersion: '4.6.0',
|
|
389
|
+
requireHardwareAttestation: false,
|
|
390
|
+
generateZkProof: false,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(response.chain.verdict.isComplete).toBe(true);
|
|
394
|
+
expect(response.chain.warrantHistory.totalWarrants).toBe(3);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('requireHardwareAttestation includes L2 placeholder', async () => {
|
|
398
|
+
const response = await engine.verifyProvenance({
|
|
399
|
+
queryType: 'identity',
|
|
400
|
+
modelName: 'claude-opus-4.6',
|
|
401
|
+
modelVersion: '4.6.0',
|
|
402
|
+
requireHardwareAttestation: true,
|
|
403
|
+
generateZkProof: false,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
expect(response.chain.identity.attestation.l2Quote).toBeTruthy();
|
|
407
|
+
expect(response.chain.identity.attestation.l2Quote).toContain('tee-quote-');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('generateZkProof includes proof + verification key', async () => {
|
|
411
|
+
const response = await engine.verifyProvenance({
|
|
412
|
+
queryType: 'full_chain',
|
|
413
|
+
modelName: 'claude-opus-4.6',
|
|
414
|
+
modelVersion: '4.6.0',
|
|
415
|
+
requireHardwareAttestation: false,
|
|
416
|
+
generateZkProof: true,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
expect(response.zkProof).toBeTruthy();
|
|
420
|
+
expect(response.zkProof!.claim).toBeTruthy();
|
|
421
|
+
expect(response.zkProof!.proof).toMatch(/^[a-f0-9]{64}$/);
|
|
422
|
+
expect(response.zkProof!.verificationKey).toMatch(/^[a-f0-9]{64}$/);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('response hash is deterministic', async () => {
|
|
426
|
+
// Two queries with the same params against the same engine produce
|
|
427
|
+
// different timestamps, so responseHash will differ. But the hash
|
|
428
|
+
// itself must be a valid SHA-256 hex string.
|
|
429
|
+
const response = await engine.verifyProvenance({
|
|
430
|
+
queryType: 'identity',
|
|
431
|
+
modelName: 'claude-opus-4.6',
|
|
432
|
+
modelVersion: '4.6.0',
|
|
433
|
+
requireHardwareAttestation: false,
|
|
434
|
+
generateZkProof: false,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(response.responseHash).toMatch(/^[a-f0-9]{64}$/);
|
|
438
|
+
expect(response.timestamp).toBeTruthy();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('unknown model returns verdict.isComplete = false (INV-005)', async () => {
|
|
442
|
+
const response = await engine.verifyProvenance({
|
|
443
|
+
queryType: 'full_chain',
|
|
444
|
+
modelName: 'nonexistent-model',
|
|
445
|
+
modelVersion: '1.0.0',
|
|
446
|
+
requireHardwareAttestation: false,
|
|
447
|
+
generateZkProof: false,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(response.chain.verdict.isComplete).toBe(false);
|
|
451
|
+
expect(response.chain.verdict.confidence).toBe(0);
|
|
452
|
+
expect(response.chain.verdict.flags).toContain('no_identity');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('ZK claim content varies by queryType (INV-004)', async () => {
|
|
456
|
+
const identityResp = await engine.verifyProvenance({
|
|
457
|
+
queryType: 'identity',
|
|
458
|
+
modelName: 'claude-opus-4.6',
|
|
459
|
+
modelVersion: '4.6.0',
|
|
460
|
+
requireHardwareAttestation: false,
|
|
461
|
+
generateZkProof: true,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const behaviorResp = await engine.verifyProvenance({
|
|
465
|
+
queryType: 'behavior',
|
|
466
|
+
modelName: 'claude-opus-4.6',
|
|
467
|
+
modelVersion: '4.6.0',
|
|
468
|
+
requireHardwareAttestation: false,
|
|
469
|
+
generateZkProof: true,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(identityResp.zkProof!.claim).toContain('identity attestation');
|
|
473
|
+
expect(behaviorResp.zkProof!.claim).toContain('actions');
|
|
474
|
+
expect(identityResp.zkProof!.proof).not.toBe(behaviorResp.zkProof!.proof);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// ============================================================================
|
|
479
|
+
// Integration Tests
|
|
480
|
+
// ============================================================================
|
|
481
|
+
|
|
482
|
+
describe('Integration', () => {
|
|
483
|
+
it('full flow: create identity, build chain, verify provenance', async () => {
|
|
484
|
+
const identity = await engine.createIdentityAttestation(
|
|
485
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
486
|
+
);
|
|
487
|
+
expect(identity.id).toBeTruthy();
|
|
488
|
+
|
|
489
|
+
const warrants = await makeWarrantHashes(5);
|
|
490
|
+
const entries = makeGlossEntries(20, 1);
|
|
491
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
492
|
+
expect(chain.verdict.isComplete).toBe(true);
|
|
493
|
+
|
|
494
|
+
const response = await engine.verifyProvenance({
|
|
495
|
+
queryType: 'full_chain',
|
|
496
|
+
modelName: 'claude-opus-4.6',
|
|
497
|
+
modelVersion: '4.6.0',
|
|
498
|
+
requireHardwareAttestation: false,
|
|
499
|
+
generateZkProof: true,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
expect(response.chain.verdict.isComplete).toBe(true);
|
|
503
|
+
expect(response.chain.warrantHistory.totalWarrants).toBe(5);
|
|
504
|
+
expect(response.chain.behavioralRecord.violations).toBe(1);
|
|
505
|
+
expect(response.zkProof).toBeTruthy();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('distilled model scenario: no identity = verification fails', async () => {
|
|
509
|
+
// A distilled model has no identity attestation registered.
|
|
510
|
+
// Verification must return isComplete = false.
|
|
511
|
+
const response = await engine.verifyProvenance({
|
|
512
|
+
queryType: 'full_chain',
|
|
513
|
+
modelName: 'distilled-model-v1',
|
|
514
|
+
modelVersion: '1.0.0',
|
|
515
|
+
requireHardwareAttestation: false,
|
|
516
|
+
generateZkProof: false,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
expect(response.chain.verdict.isComplete).toBe(false);
|
|
520
|
+
expect(response.chain.verdict.confidence).toBe(0);
|
|
521
|
+
expect(response.chain.verdict.assessment).toContain('No identity attestation found');
|
|
522
|
+
expect(response.chain.verdict.flags).toContain('no_identity');
|
|
523
|
+
expect(response.chain.identity.id).toBe('');
|
|
524
|
+
expect(response.chain.identity.attestation.providerSignature).toBe('');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('model with clean history: high confidence, no flags', async () => {
|
|
528
|
+
const identity = await engine.createIdentityAttestation(
|
|
529
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
const warrants = await makeWarrantHashes(10);
|
|
533
|
+
const entries = makeGlossEntries(50, 0);
|
|
534
|
+
const chain = await engine.buildProvenanceChain(identity, warrants, entries);
|
|
535
|
+
|
|
536
|
+
expect(chain.verdict.isComplete).toBe(true);
|
|
537
|
+
expect(chain.verdict.confidence).toBeGreaterThanOrEqual(0.9);
|
|
538
|
+
expect(chain.verdict.flags).toEqual([]);
|
|
539
|
+
expect(chain.verdict.assessment).toContain('no anomalies');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('model with drift: flags present, lower confidence', async () => {
|
|
543
|
+
const identity = await engine.createIdentityAttestation(
|
|
544
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
const warrants = await makeWarrantHashes(5);
|
|
548
|
+
const entries = makeGlossEntries(30, 2);
|
|
549
|
+
const chain = await engine.buildProvenanceChain(
|
|
550
|
+
identity, warrants, entries,
|
|
551
|
+
{ compositeScore: 0.7, recommendation: 'tighten' }
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
expect(chain.verdict.flags).toContain('drift_detected');
|
|
555
|
+
expect(chain.verdict.confidence).toBeLessThan(1.0);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('cross-query consistency: same model returns same chain', async () => {
|
|
559
|
+
const identity = await engine.createIdentityAttestation(
|
|
560
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
561
|
+
);
|
|
562
|
+
const warrants = await makeWarrantHashes(3);
|
|
563
|
+
const entries = makeGlossEntries(10);
|
|
564
|
+
await engine.buildProvenanceChain(identity, warrants, entries);
|
|
565
|
+
|
|
566
|
+
const r1 = await engine.verifyProvenance({
|
|
567
|
+
queryType: 'identity',
|
|
568
|
+
modelName: 'claude-opus-4.6',
|
|
569
|
+
modelVersion: '4.6.0',
|
|
570
|
+
requireHardwareAttestation: false,
|
|
571
|
+
generateZkProof: false,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const r2 = await engine.verifyProvenance({
|
|
575
|
+
queryType: 'full_chain',
|
|
576
|
+
modelName: 'claude-opus-4.6',
|
|
577
|
+
modelVersion: '4.6.0',
|
|
578
|
+
requireHardwareAttestation: false,
|
|
579
|
+
generateZkProof: false,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
expect(r1.chain.identity.id).toBe(r2.chain.identity.id);
|
|
583
|
+
expect(r1.chain.chainAttestation.l1Hash).toBe(r2.chain.chainAttestation.l1Hash);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('multiple models can coexist with separate chains', async () => {
|
|
587
|
+
const claude = await engine.createIdentityAttestation(
|
|
588
|
+
makeModel(), makeConstraints(), makeDeployment()
|
|
589
|
+
);
|
|
590
|
+
const gpt = await engine.createIdentityAttestation(
|
|
591
|
+
{ name: 'gpt-5', version: '5.0.0', provider: 'openai' },
|
|
592
|
+
makeConstraints(),
|
|
593
|
+
{ environment: 'enterprise' }
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
expect(claude.id).not.toBe(gpt.id);
|
|
597
|
+
|
|
598
|
+
const r1 = await engine.verifyProvenance({
|
|
599
|
+
queryType: 'identity',
|
|
600
|
+
modelName: 'claude-opus-4.6',
|
|
601
|
+
modelVersion: '4.6.0',
|
|
602
|
+
requireHardwareAttestation: false,
|
|
603
|
+
generateZkProof: false,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const r2 = await engine.verifyProvenance({
|
|
607
|
+
queryType: 'identity',
|
|
608
|
+
modelName: 'gpt-5',
|
|
609
|
+
modelVersion: '5.0.0',
|
|
610
|
+
requireHardwareAttestation: false,
|
|
611
|
+
generateZkProof: false,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
expect(r1.chain.identity.model.provider).toBe('anthropic');
|
|
615
|
+
expect(r2.chain.identity.model.provider).toBe('openai');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('missing deployment environment throws', async () => {
|
|
619
|
+
await expect(
|
|
620
|
+
engine.createIdentityAttestation(
|
|
621
|
+
makeModel(),
|
|
622
|
+
makeConstraints(),
|
|
623
|
+
{ environment: '' }
|
|
624
|
+
)
|
|
625
|
+
).rejects.toThrow('Deployment environment is required');
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SA-2026-008: Cross-module seal consistency
|
|
3
|
+
* Verifies that context/crypto/hmac and core/crypto produce
|
|
4
|
+
* identical seals for the same input and key after HKDF unification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
deriveKey,
|
|
10
|
+
hmacSeal as coreHmacSeal,
|
|
11
|
+
hmacVerify as coreHmacVerify,
|
|
12
|
+
} from '../core/crypto.js';
|
|
13
|
+
import {
|
|
14
|
+
hmacSeal as ctxHmacSeal,
|
|
15
|
+
hmacVerify as ctxHmacVerify,
|
|
16
|
+
} from '../context/crypto/hmac.js';
|
|
17
|
+
|
|
18
|
+
const TEST_KEY = 'a]S$8dP!kL2#mN5&wQ9^xR1@vZ3*yT7!';
|
|
19
|
+
const TEST_DATA = { action: 'test', amount: 100, agent: 'test-agent' };
|
|
20
|
+
|
|
21
|
+
describe('SA-2026-008: Cross-module seal consistency', () => {
|
|
22
|
+
it('context/crypto/hmac derives same key as core/crypto for same input', async () => {
|
|
23
|
+
const coreKey = await deriveKey(TEST_KEY);
|
|
24
|
+
const coreSeal = await coreHmacSeal(TEST_DATA, coreKey);
|
|
25
|
+
const ctxSeal = await ctxHmacSeal(TEST_DATA, TEST_KEY);
|
|
26
|
+
|
|
27
|
+
expect(coreSeal).toBe(ctxSeal);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('context/crypto/hmac verify accepts core-produced seals', async () => {
|
|
31
|
+
const coreKey = await deriveKey(TEST_KEY);
|
|
32
|
+
const coreSeal = await coreHmacSeal(TEST_DATA, coreKey);
|
|
33
|
+
|
|
34
|
+
const verified = await ctxHmacVerify(TEST_DATA, coreSeal, TEST_KEY);
|
|
35
|
+
expect(verified).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('core/crypto verify accepts context-produced seals', async () => {
|
|
39
|
+
const ctxSeal = await ctxHmacSeal(TEST_DATA, TEST_KEY);
|
|
40
|
+
|
|
41
|
+
const coreKey = await deriveKey(TEST_KEY);
|
|
42
|
+
const verified = await coreHmacVerify(TEST_DATA, ctxSeal, coreKey);
|
|
43
|
+
expect(verified).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|