@usefragments/core 1.10.2 → 2.0.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 (130) hide show
  1. package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
  2. package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
  3. package/dist/chunk-7ULGH74M.js +66 -0
  4. package/dist/chunk-7ULGH74M.js.map +1 -0
  5. package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
  6. package/dist/chunk-BMPYIUZE.js.map +1 -0
  7. package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
  8. package/dist/chunk-ML5S6QNU.js.map +1 -0
  9. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  10. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  11. package/dist/chunk-PWIJMOI4.js +202 -0
  12. package/dist/chunk-PWIJMOI4.js.map +1 -0
  13. package/dist/chunk-RYFULE43.js +578 -0
  14. package/dist/chunk-RYFULE43.js.map +1 -0
  15. package/dist/codes/index.d.ts +2 -2
  16. package/dist/codes/index.js +3 -2
  17. package/dist/compiled-types/index.d.ts +327 -2
  18. package/dist/compiled-types/index.js +1 -1
  19. package/dist/generate/index.d.ts +3 -2
  20. package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
  21. package/dist/governance-telemetry.d.ts +6 -0
  22. package/dist/governance-telemetry.js +1 -1
  23. package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
  24. package/dist/index.d.ts +3280 -1305
  25. package/dist/index.js +2414 -350
  26. package/dist/index.js.map +1 -1
  27. package/dist/manifest.d.ts +228 -0
  28. package/dist/manifest.js +24 -0
  29. package/dist/manifest.js.map +1 -0
  30. package/dist/preview/index.js +45 -1
  31. package/dist/preview/index.js.map +1 -1
  32. package/dist/preview-runtime.d.ts +1 -2
  33. package/dist/preview-runtime.js +150 -14
  34. package/dist/preview-runtime.js.map +1 -1
  35. package/dist/react-types.d.ts +1 -2
  36. package/dist/registry.d.ts +1412 -203
  37. package/dist/registry.js +30 -3
  38. package/dist/schemas/index.d.ts +1 -1
  39. package/dist/schemas/index.js +3 -2
  40. package/dist/storyAdapter.d.ts +1 -2
  41. package/dist/storyAdapter.js +11 -49
  42. package/dist/storyAdapter.js.map +1 -1
  43. package/dist/test-utils.d.ts +3 -2
  44. package/dist/topology/index.d.ts +1 -1
  45. package/dist/topology/index.js +1 -1
  46. package/package.json +8 -2
  47. package/src/__tests__/contract-parser.test.ts +318 -277
  48. package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
  49. package/src/__tests__/preview-runtime.test.tsx +30 -8
  50. package/src/__tests__/schema.test.ts +191 -14
  51. package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
  52. package/src/analysis-plan/coverage.ts +181 -0
  53. package/src/analysis-plan/digest.ts +141 -0
  54. package/src/analysis-plan/index.ts +34 -0
  55. package/src/analysis-plan/types.ts +207 -0
  56. package/src/approved-contract-tokens.test.ts +39 -0
  57. package/src/approved-contract-tokens.ts +18 -0
  58. package/src/codes/__tests__/codes.test.ts +13 -0
  59. package/src/codes/codes.ts +40 -0
  60. package/src/compiled-types/index.ts +640 -39
  61. package/src/compiled-types/parse.test.ts +145 -4
  62. package/src/component-contract.ts +95 -53
  63. package/src/composition.ts +7 -13
  64. package/src/constants.ts +3 -6
  65. package/src/contract/hash.test.ts +20 -0
  66. package/src/contract/hash.ts +66 -9
  67. package/src/contract/index.ts +24 -1
  68. package/src/contract/manifest.test.ts +94 -0
  69. package/src/contract/manifest.ts +68 -0
  70. package/src/contract/preimage.test.ts +219 -1
  71. package/src/contract/preimage.ts +326 -6
  72. package/src/contract/stamp.test.ts +3 -0
  73. package/src/contract/stamp.ts +1 -1
  74. package/src/contract-parser.ts +54 -30
  75. package/src/defineFragment.test.ts +476 -91
  76. package/src/defineFragment.ts +204 -114
  77. package/src/domain-ids.test.ts +35 -0
  78. package/src/domain-ids.ts +61 -0
  79. package/src/evaluation/evaluate.test.ts +522 -0
  80. package/src/evaluation/evaluate.ts +690 -0
  81. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
  82. package/src/evaluation/index.ts +58 -0
  83. package/src/evaluation/receipt.ts +753 -0
  84. package/src/evaluation/types.ts +406 -0
  85. package/src/facts/builders.ts +2 -0
  86. package/src/facts/compile.ts +29 -6
  87. package/src/facts/fact-index.ts +13 -3
  88. package/src/facts/fact-integrity-v1.test.ts +172 -0
  89. package/src/facts/facts.test.ts +15 -0
  90. package/src/facts/ids.ts +46 -3
  91. package/src/facts/index.ts +14 -1
  92. package/src/facts/integrity.ts +134 -0
  93. package/src/facts/types.ts +36 -0
  94. package/src/governance-integrity.test.ts +1 -0
  95. package/src/governance-integrity.ts +5 -3
  96. package/src/governance-telemetry.ts +8 -0
  97. package/src/governance.ts +70 -8
  98. package/src/index.ts +230 -37
  99. package/src/preview/validation.test.ts +62 -0
  100. package/src/preview/validation.ts +48 -2
  101. package/src/preview-runtime.tsx +227 -20
  102. package/src/registry-install-plan.ts +200 -109
  103. package/src/registry-shards.test.ts +263 -0
  104. package/src/registry.ts +237 -0
  105. package/src/repository-binding.test.ts +50 -0
  106. package/src/repository-binding.ts +96 -0
  107. package/src/rules/families.test.ts +36 -0
  108. package/src/rules/finding.ts +7 -2
  109. package/src/rules/index.ts +17 -1
  110. package/src/rules/rule-config.test.ts +66 -0
  111. package/src/rules/rule-config.ts +73 -0
  112. package/src/rules/rules.test.ts +26 -0
  113. package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
  114. package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
  115. package/src/schema.ts +293 -113
  116. package/src/schemas/index.ts +1 -1
  117. package/src/storyAdapter.test.ts +68 -12
  118. package/src/storyAdapter.ts +44 -75
  119. package/src/topology/resolve-area.ts +1 -1
  120. package/src/types.ts +258 -40
  121. package/dist/chunk-3LLRNCPX.js.map +0 -1
  122. package/dist/chunk-RANPUC6C.js +0 -72
  123. package/dist/chunk-RANPUC6C.js.map +0 -1
  124. package/dist/chunk-XN3LSDPY.js.map +0 -1
  125. package/dist/chunk-YF65VYRY.js.map +0 -1
  126. package/src/fragment-types.ts +0 -214
  127. package/src/react-create-element.test.ts +0 -22
  128. package/src/react-create-element.ts +0 -12
  129. package/src/storyFilters.test.ts +0 -350
  130. package/src/storyFilters.ts +0 -253
@@ -0,0 +1,772 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildAnalysisPlanV1, type CoverageRegionV1 } from "../analysis-plan/index.js";
3
+ import { contractHash } from "../contract/hash.js";
4
+ import { projectContractPreimage } from "../contract/preimage.js";
5
+ import { evaluationReceiptIdFromDigest } from "../domain-ids.js";
6
+ import { FactIndex } from "../facts/fact-index.js";
7
+ import { makeTokenDefinitionFact } from "../facts/builders.js";
8
+ import { repositoryBindingDigestV1 } from "../repository-binding.js";
9
+ import {
10
+ EVALUATOR_VERSION_V2,
11
+ evaluate,
12
+ evaluationInputDigestV2,
13
+ evaluationResultDigestV2,
14
+ evaluationInputV2Schema,
15
+ evaluationReceiptDigestV1,
16
+ evaluationReceiptMintInputV1Schema,
17
+ evaluationReceiptV1Schema,
18
+ evaluationResultV2Schema,
19
+ parseEvaluationReceiptV1,
20
+ providerProofDigestV1,
21
+ type EvaluationInputV2,
22
+ type EvaluationReceiptMintInputV1,
23
+ type EvaluationReceiptSemanticV1,
24
+ type ProviderProofV1,
25
+ } from "./index.js";
26
+
27
+ const A = "a".repeat(64);
28
+ const B = "b".repeat(64);
29
+ const C = "c".repeat(64);
30
+
31
+ const binding = {
32
+ provider: "github",
33
+ providerInstanceId: "github.com",
34
+ repositoryExternalId: "R_1",
35
+ bindingId: "binding_1",
36
+ };
37
+
38
+ const CONTRACT_PREIMAGE = projectContractPreimage({
39
+ components: [],
40
+ tokens: [],
41
+ canonicalMappings: [],
42
+ policy: {},
43
+ });
44
+ const FCID = contractHash(CONTRACT_PREIMAGE);
45
+
46
+ function plan() {
47
+ return buildAnalysisPlanV1({
48
+ binding,
49
+ source: {
50
+ commitId: "head-sha",
51
+ defaultBranch: "main",
52
+ acquiredBy: "github_app",
53
+ },
54
+ contract: { fcid: FCID, artifactDigest: B },
55
+ config: { path: "fragments.config.ts", digest: C },
56
+ profile: { id: "react-web-v1", version: "1", capabilities: ["tsx"] },
57
+ regions: [
58
+ {
59
+ regionId: "tsx",
60
+ pathPattern: "src/**/*.tsx",
61
+ kind: "component_source",
62
+ language: "typescript",
63
+ required: true,
64
+ requiredDialects: ["tsx"],
65
+ analyzer: { id: "react", interfaceVersion: "1", implementationVersion: "1" },
66
+ },
67
+ ],
68
+ limits: {
69
+ maxSourceFiles: 1_000,
70
+ maxSourceBytes: 10_000_000,
71
+ maxFileBytes: 1_000_000,
72
+ maxFacts: 10_000,
73
+ maxRegions: 100,
74
+ maxDiagnostics: 1_000,
75
+ maxDurationMs: 60_000,
76
+ },
77
+ evaluatorVersion: "evaluation-kernel:v2",
78
+ createdAt: "2026-09-01T00:00:00.000Z",
79
+ });
80
+ }
81
+
82
+ const coverage: CoverageRegionV1[] = [
83
+ { regionId: "tsx", state: "analyzed", fileCount: 4, byteCount: 400, diagnosticIds: [] },
84
+ ];
85
+
86
+ function evaluationInput(): EvaluationInputV2 {
87
+ const analysisPlan = plan();
88
+ return {
89
+ schemaVersion: 2,
90
+ binding,
91
+ analysisPlan,
92
+ contract: { fcid: analysisPlan.contract.fcid, preimage: CONTRACT_PREIMAGE, active: true },
93
+ factSchemaVersion: "facts:v1",
94
+ facts: new FactIndex(),
95
+ factsDigest: B,
96
+ coverage,
97
+ policy: { failOnWarnings: false },
98
+ trust: { source: "verified", mappings: {} },
99
+ waivers: [],
100
+ integrity: {
101
+ governance: "healthy",
102
+ inertConfigDiagnosticCount: 0,
103
+ profile: { state: "known", profileId: "react-web-v1", profileVersion: "1" },
104
+ facts: { state: "healthy", coalescedDuplicateCount: 0 },
105
+ },
106
+ evaluatedAt: Date.UTC(2026, 8, 1),
107
+ evaluatorVersion: "evaluation-kernel:v2",
108
+ };
109
+ }
110
+
111
+ function providerProof(): ProviderProofV1 {
112
+ const semantic = {
113
+ schemaVersion: 1 as const,
114
+ adapterId: "github-app",
115
+ adapterVersion: "1",
116
+ providerInstanceId: "github.com",
117
+ bindingDigest: repositoryBindingDigestV1(binding),
118
+ repositoryExternalId: "R_1",
119
+ sourceCommitId: "head-sha",
120
+ eventExternalId: "delivery-1",
121
+ changeExternalId: "42",
122
+ publisherExternalId: "fragments-app",
123
+ };
124
+ return { ...semantic, proofDigest: providerProofDigestV1(semantic) };
125
+ }
126
+
127
+ function receiptSemantic(
128
+ overrides: Partial<EvaluationReceiptSemanticV1> = {}
129
+ ): EvaluationReceiptSemanticV1 {
130
+ return {
131
+ schemaVersion: 1,
132
+ trust: "source_verified",
133
+ authority: "enforcement_eligible",
134
+ binding,
135
+ sourceCommitId: "head-sha",
136
+ fcid: FCID,
137
+ analysisPlanDigest: plan().digest,
138
+ factsDigest: B,
139
+ inputDigest: C,
140
+ resultDigest: A,
141
+ evaluatorVersion: "evaluation-kernel:v2",
142
+ providerProof: providerProof(),
143
+ ...overrides,
144
+ };
145
+ }
146
+
147
+ describe("EvaluationInputV2", () => {
148
+ it("accepts the complete facts-only input and rejects unknown keys", () => {
149
+ expect(evaluationInputV2Schema.parse(evaluationInput())).toMatchObject({ schemaVersion: 2 });
150
+ expect(() => evaluationInputV2Schema.parse({ ...evaluationInput(), summary: {} })).toThrow();
151
+ });
152
+
153
+ it("rejects plan/binding/FCID/evaluator mismatch and missing required coverage", () => {
154
+ expect(() =>
155
+ evaluationInputV2Schema.parse({
156
+ ...evaluationInput(),
157
+ binding: { ...binding, repositoryExternalId: "R_other" },
158
+ })
159
+ ).toThrow();
160
+ expect(() =>
161
+ evaluationInputV2Schema.parse({
162
+ ...evaluationInput(),
163
+ contract: { ...evaluationInput().contract, fcid: B },
164
+ })
165
+ ).toThrow();
166
+ expect(() => evaluationInputV2Schema.parse({ ...evaluationInput(), coverage: [] })).toThrow();
167
+ expect(() =>
168
+ evaluationInputV2Schema.parse({ ...evaluationInput(), evaluatorVersion: "other" })
169
+ ).toThrow();
170
+ });
171
+
172
+ it("rejects empty/duplicate waiver matches and malformed integrity unions", () => {
173
+ const waiver = {
174
+ waiverId: "w-1",
175
+ scope: { kind: "rule" as const, ruleId: "components/prefer-library" },
176
+ matchedFindingFingerprints: ["f-1"],
177
+ reason: "Migration in progress",
178
+ };
179
+ expect(() =>
180
+ evaluationInputV2Schema.parse({
181
+ ...evaluationInput(),
182
+ waivers: [{ ...waiver, matchedFindingFingerprints: [] }],
183
+ })
184
+ ).toThrow();
185
+ expect(() =>
186
+ evaluationInputV2Schema.parse({
187
+ ...evaluationInput(),
188
+ waivers: [{ ...waiver, matchedFindingFingerprints: ["f-1", "f-1"] }],
189
+ })
190
+ ).toThrow();
191
+ expect(() =>
192
+ evaluationInputV2Schema.parse({
193
+ ...evaluationInput(),
194
+ integrity: {
195
+ ...evaluationInput().integrity,
196
+ profile: { state: "known", profileId: "react-web-v1" },
197
+ },
198
+ })
199
+ ).toThrow();
200
+ });
201
+
202
+ it("enforces the analysis-plan fact limit", () => {
203
+ const basePlan = plan();
204
+ const {
205
+ schemaVersion: _schemaVersion,
206
+ analysisPlanId: _analysisPlanId,
207
+ obligationsDigest: _obligationsDigest,
208
+ digest: _digest,
209
+ ...planInput
210
+ } = basePlan;
211
+ const limitedPlan = buildAnalysisPlanV1({
212
+ ...planInput,
213
+ limits: { ...planInput.limits, maxFacts: 1 },
214
+ });
215
+ const facts = new FactIndex();
216
+ facts.addMany([
217
+ makeTokenDefinitionFact({ name: "--one", value: "1px" }),
218
+ makeTokenDefinitionFact({ name: "--two", value: "2px" }),
219
+ ]);
220
+ expect(() =>
221
+ evaluationInputV2Schema.parse({
222
+ ...evaluationInput(),
223
+ analysisPlan: limitedPlan,
224
+ facts,
225
+ })
226
+ ).toThrow(/Fact count/u);
227
+ });
228
+ });
229
+
230
+ describe("EvaluationResultV2", () => {
231
+ it("preserves reasons, counts, derived output, and integrity explanation", () => {
232
+ const result = {
233
+ schemaVersion: 2,
234
+ verdict: "pass",
235
+ reasons: [],
236
+ findings: [],
237
+ counts: { blocked: 0, advisory: 0, pendingCloudTrust: 0 },
238
+ derived: { warnPresent: false, gatingFindingsPresent: false },
239
+ coverageSummary: {
240
+ plannedRegions: 1,
241
+ observedRegions: 1,
242
+ requiredRegions: 1,
243
+ requiredByState: {
244
+ analyzed: 1,
245
+ unsupported: 0,
246
+ excluded_by_contract: 0,
247
+ skipped_limit: 0,
248
+ failed: 0,
249
+ },
250
+ optionalByState: {
251
+ analyzed: 0,
252
+ unsupported: 0,
253
+ excluded_by_contract: 0,
254
+ skipped_limit: 0,
255
+ failed: 0,
256
+ },
257
+ eligibleFiles: 4,
258
+ analyzedFiles: 4,
259
+ analyzedBytes: 400,
260
+ },
261
+ integrity: {
262
+ fcidVerified: true,
263
+ analysisPlanVerified: true,
264
+ factIntegrity: "healthy",
265
+ requiredCoverage: "complete",
266
+ governance: "healthy",
267
+ profile: "known",
268
+ },
269
+ inputDigest: A,
270
+ resultDigest: B,
271
+ } as const;
272
+ expect(evaluationResultV2Schema.parse(result)).toMatchObject({ verdict: "pass" });
273
+ expect(() =>
274
+ evaluationResultV2Schema.parse({
275
+ ...result,
276
+ reasons: ["factConflict", "factConflict"],
277
+ })
278
+ ).toThrow(/unique/u);
279
+ });
280
+
281
+ it("holds an integrity-class reason at indeterminate even beside blocked findings", () => {
282
+ // Mirrors the kernel: `inertConfigPresent` (an INTEGRITY reason) beside
283
+ // `blockedFindings` is `indeterminate`, never `block`.
284
+ const result = {
285
+ schemaVersion: 2,
286
+ verdict: "indeterminate",
287
+ reasons: ["inertConfigPresent", "blockedFindings"],
288
+ findings: [
289
+ {
290
+ fingerprint: "f-block",
291
+ ruleId: "r-block",
292
+ severity: "error",
293
+ state: "blocked",
294
+ stateReasons: [],
295
+ },
296
+ ],
297
+ counts: { blocked: 1, advisory: 0, pendingCloudTrust: 0 },
298
+ derived: { warnPresent: false, gatingFindingsPresent: true },
299
+ coverageSummary: {
300
+ plannedRegions: 1,
301
+ observedRegions: 1,
302
+ requiredRegions: 1,
303
+ requiredByState: {
304
+ analyzed: 1,
305
+ unsupported: 0,
306
+ excluded_by_contract: 0,
307
+ skipped_limit: 0,
308
+ failed: 0,
309
+ },
310
+ optionalByState: {
311
+ analyzed: 0,
312
+ unsupported: 0,
313
+ excluded_by_contract: 0,
314
+ skipped_limit: 0,
315
+ failed: 0,
316
+ },
317
+ eligibleFiles: 1,
318
+ analyzedFiles: 1,
319
+ analyzedBytes: 100,
320
+ },
321
+ integrity: {
322
+ fcidVerified: true,
323
+ analysisPlanVerified: true,
324
+ factIntegrity: "healthy",
325
+ requiredCoverage: "complete",
326
+ governance: "healthy",
327
+ profile: "known",
328
+ },
329
+ inputDigest: A,
330
+ resultDigest: B,
331
+ } as const;
332
+ expect(evaluationResultV2Schema.parse(result)).toMatchObject({ verdict: "indeterminate" });
333
+ expect(() => evaluationResultV2Schema.parse({ ...result, verdict: "block" })).toThrow(
334
+ /verdict must be indeterminate/u
335
+ );
336
+ });
337
+
338
+ it("rejects impossible counts, derived values, findings, reasons, and coverage", () => {
339
+ const result = evaluationResultV2Schema.parse({
340
+ schemaVersion: 2,
341
+ verdict: "pass",
342
+ reasons: [],
343
+ findings: [],
344
+ counts: { blocked: 0, advisory: 0, pendingCloudTrust: 0 },
345
+ derived: { warnPresent: false, gatingFindingsPresent: false },
346
+ coverageSummary: {
347
+ plannedRegions: 1,
348
+ observedRegions: 1,
349
+ requiredRegions: 1,
350
+ requiredByState: {
351
+ analyzed: 1,
352
+ unsupported: 0,
353
+ excluded_by_contract: 0,
354
+ skipped_limit: 0,
355
+ failed: 0,
356
+ },
357
+ optionalByState: {
358
+ analyzed: 0,
359
+ unsupported: 0,
360
+ excluded_by_contract: 0,
361
+ skipped_limit: 0,
362
+ failed: 0,
363
+ },
364
+ eligibleFiles: 4,
365
+ analyzedFiles: 4,
366
+ analyzedBytes: 400,
367
+ },
368
+ integrity: {
369
+ fcidVerified: true,
370
+ analysisPlanVerified: true,
371
+ factIntegrity: "healthy",
372
+ requiredCoverage: "complete",
373
+ governance: "healthy",
374
+ profile: "known",
375
+ },
376
+ inputDigest: A,
377
+ resultDigest: B,
378
+ });
379
+
380
+ expect(() =>
381
+ evaluationResultV2Schema.parse({
382
+ ...result,
383
+ counts: { ...result.counts, blocked: 1 },
384
+ })
385
+ ).toThrow(/count must match/u);
386
+ expect(() =>
387
+ evaluationResultV2Schema.parse({
388
+ ...result,
389
+ findings: [
390
+ {
391
+ fingerprint: "f-warn",
392
+ ruleId: "r-warn",
393
+ severity: "warn",
394
+ state: "advisory",
395
+ stateReasons: [],
396
+ },
397
+ ],
398
+ counts: { blocked: 0, advisory: 1, pendingCloudTrust: 0 },
399
+ derived: { ...result.derived, warnPresent: false },
400
+ })
401
+ ).toThrow(/warnPresent/u);
402
+ expect(() =>
403
+ evaluationResultV2Schema.parse({
404
+ ...result,
405
+ findings: [
406
+ {
407
+ fingerprint: "f-1",
408
+ ruleId: "r-1",
409
+ severity: "error",
410
+ state: "advisory",
411
+ stateReasons: [],
412
+ },
413
+ {
414
+ fingerprint: "f-1",
415
+ ruleId: "r-2",
416
+ severity: "info",
417
+ state: "advisory",
418
+ stateReasons: [],
419
+ },
420
+ ],
421
+ counts: { blocked: 0, advisory: 2, pendingCloudTrust: 0 },
422
+ })
423
+ ).toThrow(/fingerprints must be unique/u);
424
+ expect(() =>
425
+ evaluationResultV2Schema.parse({
426
+ ...result,
427
+ reasons: ["factConflict"],
428
+ })
429
+ ).toThrow(/factConflict must match/u);
430
+ expect(() =>
431
+ evaluationResultV2Schema.parse({
432
+ ...result,
433
+ coverageSummary: { ...result.coverageSummary, observedRegions: 2 },
434
+ })
435
+ ).toThrow(/counts must reconcile/u);
436
+ });
437
+
438
+ it("never treats incomplete or absent required coverage as a pass", () => {
439
+ const base = {
440
+ schemaVersion: 2 as const,
441
+ reasons: ["requiredCoverageMissing" as const],
442
+ findings: [],
443
+ counts: { blocked: 0, advisory: 0, pendingCloudTrust: 0 },
444
+ derived: { warnPresent: false, gatingFindingsPresent: false },
445
+ coverageSummary: {
446
+ plannedRegions: 1,
447
+ observedRegions: 1,
448
+ requiredRegions: 0,
449
+ requiredByState: {
450
+ analyzed: 0,
451
+ unsupported: 0,
452
+ excluded_by_contract: 0,
453
+ skipped_limit: 0,
454
+ failed: 0,
455
+ },
456
+ optionalByState: {
457
+ analyzed: 1,
458
+ unsupported: 0,
459
+ excluded_by_contract: 0,
460
+ skipped_limit: 0,
461
+ failed: 0,
462
+ },
463
+ eligibleFiles: 1,
464
+ analyzedFiles: 1,
465
+ analyzedBytes: 100,
466
+ },
467
+ integrity: {
468
+ fcidVerified: true,
469
+ analysisPlanVerified: true,
470
+ factIntegrity: "healthy" as const,
471
+ requiredCoverage: "no_required_regions" as const,
472
+ governance: "healthy" as const,
473
+ profile: "known" as const,
474
+ },
475
+ inputDigest: A,
476
+ resultDigest: B,
477
+ };
478
+ expect(evaluationResultV2Schema.parse({ ...base, verdict: "indeterminate" })).toMatchObject({
479
+ verdict: "indeterminate",
480
+ });
481
+ expect(() => evaluationResultV2Schema.parse({ ...base, verdict: "pass" })).toThrow(
482
+ /verdict must be indeterminate/u
483
+ );
484
+
485
+ expect(() =>
486
+ evaluationResultV2Schema.parse({
487
+ ...base,
488
+ verdict: "pass",
489
+ reasons: ["requiredCoverageIncomplete"],
490
+ coverageSummary: {
491
+ ...base.coverageSummary,
492
+ requiredRegions: 1,
493
+ requiredByState: { ...base.coverageSummary.requiredByState, failed: 1 },
494
+ optionalByState: { ...base.coverageSummary.optionalByState, analyzed: 0 },
495
+ },
496
+ integrity: { ...base.integrity, requiredCoverage: "incomplete" },
497
+ })
498
+ ).toThrow(/verdict must be indeterminate/u);
499
+ });
500
+
501
+ it("preserves warnPresent when same-fingerprint warning evidence collapses under an error", () => {
502
+ const collapsed = {
503
+ schemaVersion: 2 as const,
504
+ verdict: "pass" as const,
505
+ reasons: [],
506
+ findings: [
507
+ {
508
+ fingerprint: "same-fingerprint",
509
+ ruleId: "harder-rule",
510
+ severity: "error" as const,
511
+ state: "advisory" as const,
512
+ stateReasons: ["harder same-fingerprint evidence retained"],
513
+ },
514
+ ],
515
+ counts: { blocked: 0, advisory: 1, pendingCloudTrust: 0 },
516
+ derived: { warnPresent: true, gatingFindingsPresent: false },
517
+ coverageSummary: {
518
+ plannedRegions: 1,
519
+ observedRegions: 1,
520
+ requiredRegions: 1,
521
+ requiredByState: {
522
+ analyzed: 1,
523
+ unsupported: 0,
524
+ excluded_by_contract: 0,
525
+ skipped_limit: 0,
526
+ failed: 0,
527
+ },
528
+ optionalByState: {
529
+ analyzed: 0,
530
+ unsupported: 0,
531
+ excluded_by_contract: 0,
532
+ skipped_limit: 0,
533
+ failed: 0,
534
+ },
535
+ eligibleFiles: 1,
536
+ analyzedFiles: 1,
537
+ analyzedBytes: 100,
538
+ },
539
+ integrity: {
540
+ fcidVerified: true,
541
+ analysisPlanVerified: true,
542
+ factIntegrity: "healthy" as const,
543
+ requiredCoverage: "complete" as const,
544
+ governance: "healthy" as const,
545
+ profile: "known" as const,
546
+ },
547
+ inputDigest: A,
548
+ resultDigest: B,
549
+ };
550
+ expect(evaluationResultV2Schema.parse(collapsed).derived.warnPresent).toBe(true);
551
+ });
552
+ });
553
+
554
+ describe("evaluate V2", () => {
555
+ it("validates stored semantic digests without a second evaluation", () => {
556
+ const input = evaluationInput();
557
+ const result = evaluate(input);
558
+ const { resultDigest, ...semantic } = result;
559
+ expect(evaluationInputDigestV2(input)).toBe(result.inputDigest);
560
+ expect(evaluationResultDigestV2(semantic)).toBe(resultDigest);
561
+ expect(evaluationResultDigestV2({ ...semantic, verdict: "block" })).not.toBe(resultDigest);
562
+ expect(evaluationInputDigestV2({ ...input, evaluatedAt: input.evaluatedAt + 1 })).not.toBe(
563
+ result.inputDigest
564
+ );
565
+ });
566
+
567
+ it("produces the checked-in deterministic pass result for complete healthy evidence", () => {
568
+ const result = evaluate(evaluationInput());
569
+
570
+ expect(result).toMatchObject({
571
+ schemaVersion: 2,
572
+ verdict: "pass",
573
+ reasons: [],
574
+ counts: { blocked: 0, advisory: 0, pendingCloudTrust: 0 },
575
+ integrity: {
576
+ fcidVerified: true,
577
+ analysisPlanVerified: true,
578
+ factIntegrity: "healthy",
579
+ requiredCoverage: "complete",
580
+ governance: "healthy",
581
+ profile: "known",
582
+ },
583
+ });
584
+ expect(result.inputDigest).toBe(
585
+ "9c89d84e33f7569030440720c3c467622507c5aa0d1e9ca6eeb0fc4f5e045f7e"
586
+ );
587
+ expect(result.resultDigest).toBe(
588
+ "c3fe439dd196bdd7b9ca2d4d60e620282cebd9302f94af8e6ce509199471ef57"
589
+ );
590
+ });
591
+
592
+ it.each([
593
+ {
594
+ name: "unsupported required coverage",
595
+ patch: {
596
+ coverage: [
597
+ {
598
+ regionId: "tsx",
599
+ state: "unsupported" as const,
600
+ fileCount: 4,
601
+ byteCount: 400,
602
+ diagnosticIds: ["analysis.unsupported.tsx"],
603
+ },
604
+ ],
605
+ },
606
+ reason: "requiredCoverageIncomplete",
607
+ },
608
+ {
609
+ name: "canonical fact conflict",
610
+ patch: {
611
+ integrity: {
612
+ ...evaluationInput().integrity,
613
+ facts: {
614
+ state: "conflict" as const,
615
+ coalescedDuplicateCount: 0,
616
+ conflicts: [
617
+ {
618
+ canonicalKey: "token:--space-2",
619
+ factKind: "token_definition",
620
+ state: "conflict" as const,
621
+ values: [
622
+ {
623
+ valueDigest: A,
624
+ factIds: ["fact-a"],
625
+ analyzerIds: ["css"],
626
+ evidenceRefs: ["src/a.css:1"],
627
+ },
628
+ {
629
+ valueDigest: B,
630
+ factIds: ["fact-b"],
631
+ analyzerIds: ["scss"],
632
+ evidenceRefs: ["src/b.scss:1"],
633
+ },
634
+ ],
635
+ },
636
+ ],
637
+ },
638
+ },
639
+ },
640
+ reason: "factConflict",
641
+ },
642
+ {
643
+ name: "unknown profile",
644
+ patch: {
645
+ integrity: {
646
+ ...evaluationInput().integrity,
647
+ profile: { state: "unknown" as const, profileId: "react-web-v1" },
648
+ },
649
+ },
650
+ reason: "unknownAnalysisProfile",
651
+ },
652
+ ])("fails closed for $name", ({ patch, reason }) => {
653
+ const result = evaluate({ ...evaluationInput(), ...patch });
654
+ expect(result.verdict).toBe("indeterminate");
655
+ expect(result.reasons).toContain(reason);
656
+ });
657
+
658
+ it("normalizes coverage, waiver, and provenance order before digesting", () => {
659
+ const first = evaluationInput();
660
+ first.waivers = [
661
+ {
662
+ waiverId: "waiver-2",
663
+ scope: { kind: "rule", ruleId: "r-2" },
664
+ matchedFindingFingerprints: ["finding-b", "finding-a"],
665
+ reason: "Migration",
666
+ },
667
+ {
668
+ waiverId: "waiver-1",
669
+ scope: { kind: "finding", fingerprint: "finding-c" },
670
+ matchedFindingFingerprints: ["finding-c"],
671
+ reason: "Exception",
672
+ },
673
+ ];
674
+ const second = evaluationInput();
675
+ second.waivers = [
676
+ first.waivers[1]!,
677
+ {
678
+ ...first.waivers[0]!,
679
+ matchedFindingFingerprints: ["finding-a", "finding-b"],
680
+ },
681
+ ];
682
+
683
+ expect(evaluate(first).inputDigest).toBe(evaluate(second).inputDigest);
684
+ expect(evaluate(first).resultDigest).toBe(evaluate(second).resultDigest);
685
+ });
686
+
687
+ it("rejects evaluator and fact-schema versions it cannot interpret", () => {
688
+ expect(EVALUATOR_VERSION_V2).toBe("evaluation-kernel:v2");
689
+ expect(() =>
690
+ evaluate({ ...evaluationInput(), evaluatorVersion: "evaluation-kernel:v3" })
691
+ ).toThrow(/Unsupported evaluator version/u);
692
+ expect(() => evaluate({ ...evaluationInput(), factSchemaVersion: "facts:v2" })).toThrow(
693
+ /Unsupported fact schema version/u
694
+ );
695
+ });
696
+ });
697
+
698
+ describe("EvaluationReceiptV1", () => {
699
+ it("has a checked-in canonical digest and verifies all derived identity", () => {
700
+ const semantic = receiptSemantic();
701
+ const digest = evaluationReceiptDigestV1(semantic);
702
+ expect(digest).toBe("09b0294ed27a24f89d0432b19c519b4ad5c2bceffc982d2aa1fa370b5ac6b016");
703
+ const receipt = {
704
+ ...semantic,
705
+ receiptId: evaluationReceiptIdFromDigest(digest),
706
+ mintedAt: "2026-09-01T01:00:00.000Z",
707
+ digest,
708
+ };
709
+ expect(parseEvaluationReceiptV1(receipt)).toEqual(evaluationReceiptV1Schema.parse(receipt));
710
+ });
711
+
712
+ it("pins both input and result digests", () => {
713
+ const base = evaluationReceiptDigestV1(receiptSemantic());
714
+ expect(evaluationReceiptDigestV1(receiptSemantic({ inputDigest: B }))).not.toBe(base);
715
+ expect(evaluationReceiptDigestV1(receiptSemantic({ resultDigest: C }))).not.toBe(base);
716
+ });
717
+
718
+ it.each(["local", "ci_attested", "unknown"] as const)("keeps %s evidence advisory", (trust) => {
719
+ const semantic = receiptSemantic({ trust, authority: "advisory", providerProof: undefined });
720
+ expect(evaluationReceiptDigestV1(semantic)).toMatch(/^[0-9a-f]{64}$/u);
721
+ expect(() =>
722
+ evaluationReceiptDigestV1({ ...semantic, authority: "enforcement_eligible" })
723
+ ).toThrow();
724
+ });
725
+
726
+ it("requires exact provider proof for source-verified enforcement", () => {
727
+ expect(() =>
728
+ evaluationReceiptDigestV1(
729
+ receiptSemantic({ providerProof: { ...providerProof(), sourceCommitId: "other" } })
730
+ )
731
+ ).toThrow();
732
+ expect(() =>
733
+ evaluationReceiptDigestV1(receiptSemantic({ providerProof: undefined }))
734
+ ).toThrow();
735
+ expect(() =>
736
+ evaluationReceiptDigestV1(
737
+ receiptSemantic({ authority: "advisory", providerProof: undefined })
738
+ )
739
+ ).toThrow();
740
+ expect(() =>
741
+ evaluationReceiptDigestV1(receiptSemantic({ trust: "ci_attested", authority: "advisory" }))
742
+ ).toThrow();
743
+ });
744
+
745
+ it("does not let an external mint payload submit authority or proof", () => {
746
+ const external = {
747
+ schemaVersion: 1,
748
+ trust: "ci_attested",
749
+ binding,
750
+ sourceCommitId: "head-sha",
751
+ fcid: A,
752
+ analysisPlanDigest: plan().digest,
753
+ factsDigest: B,
754
+ inputDigest: C,
755
+ resultDigest: A,
756
+ evaluatorVersion: "evaluation-kernel:v2",
757
+ } satisfies EvaluationReceiptMintInputV1;
758
+ // @ts-expect-error Source-verified trust is derived with provider proof by the application.
759
+ const sourceVerifiedMintInput: EvaluationReceiptMintInputV1 = {
760
+ ...external,
761
+ trust: "source_verified",
762
+ };
763
+ void sourceVerifiedMintInput;
764
+ expect(evaluationReceiptMintInputV1Schema.parse(external)).toEqual(external);
765
+ expect(() =>
766
+ evaluationReceiptMintInputV1Schema.parse({ ...external, authority: "enforcement_eligible" })
767
+ ).toThrow();
768
+ expect(() =>
769
+ evaluationReceiptMintInputV1Schema.parse({ ...external, providerProof: providerProof() })
770
+ ).toThrow();
771
+ });
772
+ });