@actuarial-ts/data 0.6.1 → 0.7.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.
@@ -2,8 +2,13 @@ import {
2
2
  DiagnosticValidationError,
3
3
  compileDiagnosticDefinition,
4
4
  prepareDiagnosticData,
5
+ prepareDiagnosticDataCompact,
5
6
  runMetricDiagnostics,
7
+ runMetricDiagnosticsCompact,
6
8
  validateDiagnosticGroupingConfiguration,
9
+ validateCompactDiagnosticGroupingConfiguration,
10
+ type CompactMetricDiagnosticsResult,
11
+ type CompactPreparedDiagnosticData,
7
12
  type CompiledDiagnosticDefinition,
8
13
  type DiagnosticCompletePeriodCutoff,
9
14
  type DiagnosticDeepReadonly,
@@ -14,6 +19,7 @@ import {
14
19
  type DiagnosticsFilter,
15
20
  type JsonValue,
16
21
  type MetricDiagnosticsResult,
22
+ type PreparedDiagnosticData,
17
23
  type DiagnosticValidationIssue,
18
24
  diagnosticRecord,
19
25
  isDiagnosticToken,
@@ -22,9 +28,11 @@ import {
22
28
  import { z } from "zod";
23
29
  import {
24
30
  reviewPreparedDiagnosticData,
31
+ reviewPreparedDiagnosticDataCompact,
25
32
  validateDiagnosticReviewEvidence,
26
33
  type DiagnosticReviewEvidence,
27
34
  type DiagnosticReviewReceipt,
35
+ type CompactDiagnosticReviewReceipt,
28
36
  } from "./diagnosticPreparedReview.js";
29
37
 
30
38
  // Zod 3 validates but drops the literal __proto__ key while assembling records.
@@ -46,16 +54,10 @@ function recordSchema<T extends z.ZodTypeAny>(value: T) {
46
54
 
47
55
  const tokenSchema = z
48
56
  .string()
49
- .refine(
50
- isDiagnosticToken,
51
- "Expected a nonempty token with valid Unicode and no U+0000",
52
- );
57
+ .refine(isDiagnosticToken, "Expected a nonempty token with valid Unicode and no U+0000");
53
58
  const jsonStringSchema = z
54
59
  .string()
55
- .refine(
56
- isWellFormedDiagnosticString,
57
- "Expected valid Unicode without U+0000",
58
- );
60
+ .refine(isWellFormedDiagnosticString, "Expected valid Unicode without U+0000");
59
61
  const sourceSchema = z
60
62
  .object({
61
63
  artifactId: tokenSchema,
@@ -65,10 +67,7 @@ const sourceSchema = z
65
67
  sourceCell: tokenSchema.optional(),
66
68
  })
67
69
  .strict();
68
- const rawNumberSchema = z.custom<number>(
69
- (value) => typeof value === "number",
70
- "Expected number",
71
- );
70
+ const rawNumberSchema = z.custom<number>((value) => typeof value === "number", "Expected number");
72
71
  const measuresSchema = recordSchema(z.union([rawNumberSchema, z.null()]));
73
72
  const lossBase = {
74
73
  recordId: tokenSchema,
@@ -80,9 +79,7 @@ const lossBase = {
80
79
  measures: measuresSchema,
81
80
  };
82
81
  const lossSchema = z.discriminatedUnion("rowType", [
83
- z
84
- .object({ ...lossBase, rowType: z.literal("claim"), claimId: tokenSchema })
85
- .strict(),
82
+ z.object({ ...lossBase, rowType: z.literal("claim"), claimId: tokenSchema }).strict(),
86
83
  z.object({ ...lossBase, rowType: z.literal("aggregate") }).strict(),
87
84
  ]);
88
85
  const exposureSchema = z
@@ -139,12 +136,8 @@ const jsonSchema: z.ZodType<JsonValue> = z.lazy(() =>
139
136
  );
140
137
  const policySchema = z
141
138
  .object({
142
- allowedReviewStatuses: z
143
- .array(z.enum(["pass", "warning", "not-evaluated", "fail"]))
144
- .optional(),
145
- allowedMetricFindingSeverities: z
146
- .array(z.enum(["info", "warning", "fail"]))
147
- .optional(),
139
+ allowedReviewStatuses: z.array(z.enum(["pass", "warning", "not-evaluated", "fail"])).optional(),
140
+ allowedMetricFindingSeverities: z.array(z.enum(["info", "warning", "fail"])).optional(),
148
141
  rationaleRef: tokenSchema.optional(),
149
142
  })
150
143
  .strict();
@@ -165,18 +158,10 @@ const runSchema = z
165
158
  })
166
159
  .strict();
167
160
 
168
- export type DiagnosticAllowedReviewStatus =
169
- | "pass"
170
- | "warning"
171
- | "not-evaluated"
172
- | "fail";
161
+ export type DiagnosticAllowedReviewStatus = "pass" | "warning" | "not-evaluated" | "fail";
173
162
  export interface DiagnosticExecutionPolicyInput {
174
163
  readonly allowedReviewStatuses?: readonly DiagnosticAllowedReviewStatus[];
175
- readonly allowedMetricFindingSeverities?: readonly (
176
- | "info"
177
- | "warning"
178
- | "fail"
179
- )[];
164
+ readonly allowedMetricFindingSeverities?: readonly ("info" | "warning" | "fail")[];
180
165
  readonly rationaleRef?: string;
181
166
  }
182
167
  export interface DiagnosticRunInput {
@@ -209,21 +194,23 @@ export interface ValidatedDiagnosticRunInput {
209
194
  readonly groupDimensions: Readonly<Record<string, JsonValue>>;
210
195
  readonly policy: {
211
196
  readonly allowedReviewStatuses: readonly DiagnosticAllowedReviewStatus[];
212
- readonly allowedMetricFindingSeverities: readonly (
213
- | "info"
214
- | "warning"
215
- | "fail"
216
- )[];
197
+ readonly allowedMetricFindingSeverities: readonly ("info" | "warning" | "fail")[];
217
198
  readonly rationaleRef: string | null;
218
199
  };
219
200
  }
201
+
202
+ type DiagnosticRunInputContent = Omit<
203
+ ValidatedDiagnosticRunInput,
204
+ typeof validatedDiagnosticRunInputBrand
205
+ >;
206
+ declare const compactValidatedDiagnosticRunInputBrand: unique symbol;
207
+ /** Validated owned input whose preparation does not eagerly expand identity evidence. */
208
+ export interface CompactValidatedDiagnosticRunInput extends DiagnosticRunInputContent {
209
+ readonly [compactValidatedDiagnosticRunInputBrand]: true;
210
+ }
220
211
  export interface DiagnosticExecutionGateReceipt {
221
212
  readonly allowedReviewStatuses: readonly DiagnosticAllowedReviewStatus[];
222
- readonly allowedMetricFindingSeverities: readonly (
223
- | "info"
224
- | "warning"
225
- | "fail"
226
- )[];
213
+ readonly allowedMetricFindingSeverities: readonly ("info" | "warning" | "fail")[];
227
214
  readonly rationaleRef: string | null;
228
215
  readonly reviewGate: "passed" | "blocked";
229
216
  readonly metricGate: "not-run" | "passed" | "blocked";
@@ -275,16 +262,63 @@ export type ValidatedMetricDiagnosticsOutcome =
275
262
  };
276
263
  };
277
264
 
265
+ /** A distinct authenticated run; it cannot be substituted for an eager receipt. */
266
+ export interface CompletedCompactMetricDiagnosticsRun {
267
+ readonly status: "completed";
268
+ readonly prepared: CompactPreparedDiagnosticData;
269
+ readonly review: CompactDiagnosticReviewReceipt;
270
+ readonly result: DiagnosticDeepReadonly<CompactMetricDiagnosticsResult>;
271
+ readonly runPresetId: string | null;
272
+ readonly datasetArtifactId: string | null;
273
+ readonly groupMap: Readonly<Record<string, string>>;
274
+ readonly groupDimensions: Readonly<Record<string, JsonValue>>;
275
+ readonly gate: DiagnosticExecutionGateReceipt & {
276
+ readonly reviewGate: "passed";
277
+ readonly metricGate: "passed";
278
+ };
279
+ }
280
+ type CompactRunMetadata = Omit<CompletedCompactMetricDiagnosticsRun, "status" | "result" | "gate">;
281
+ export type CompactMetricDiagnosticsOutcome =
282
+ | CompletedCompactMetricDiagnosticsRun
283
+ | (CompactRunMetadata & {
284
+ readonly status: "blocked";
285
+ readonly stage: "review";
286
+ readonly result: null;
287
+ readonly gate: DiagnosticExecutionGateReceipt & {
288
+ readonly reviewGate: "blocked";
289
+ readonly metricGate: "not-run";
290
+ };
291
+ })
292
+ | (CompactRunMetadata & {
293
+ readonly status: "blocked";
294
+ readonly stage: "metric";
295
+ readonly result: DiagnosticDeepReadonly<CompactMetricDiagnosticsResult>;
296
+ readonly gate: DiagnosticExecutionGateReceipt & {
297
+ readonly reviewGate: "passed";
298
+ readonly metricGate: "blocked";
299
+ };
300
+ });
301
+
278
302
  const authentic = new WeakSet<object>();
279
- function freeze<T>(
280
- value: T,
281
- seen = new WeakSet<object>(),
282
- ): DiagnosticDeepReadonly<T> {
303
+ // Only owned, frozen inputs can enter this cache. Weak keys do not retain a
304
+ // completed analysis after its caller releases it, and JSON cannot restore it.
305
+ const preparedByInput = new WeakMap<ValidatedDiagnosticRunInput, PreparedDiagnosticData>();
306
+ const compactPreparedByInput = new WeakMap<
307
+ CompactValidatedDiagnosticRunInput,
308
+ CompactPreparedDiagnosticData
309
+ >();
310
+ const compactCompleted = new WeakSet<object>();
311
+ // Retain the exact immutable validated input only while its completed run lives.
312
+ // Reconstructing from the audit would lose the original optional/raw-value form.
313
+ const compactInputByCompletedRun = new WeakMap<
314
+ CompletedCompactMetricDiagnosticsRun,
315
+ CompactValidatedDiagnosticRunInput
316
+ >();
317
+ function freeze<T>(value: T, seen = new WeakSet<object>()): DiagnosticDeepReadonly<T> {
283
318
  if (value === null || typeof value !== "object" || seen.has(value))
284
319
  return value as DiagnosticDeepReadonly<T>;
285
320
  seen.add(value);
286
- for (const child of Object.values(value as Record<string, unknown>))
287
- freeze(child, seen);
321
+ for (const child of Object.values(value as Record<string, unknown>)) freeze(child, seen);
288
322
  return Object.freeze(value) as DiagnosticDeepReadonly<T>;
289
323
  }
290
324
  function issues(error: z.ZodError): DiagnosticValidationError {
@@ -306,28 +340,19 @@ function issues(error: z.ZodError): DiagnosticValidationError {
306
340
  function codeUnit(left: string, right: string): number {
307
341
  return left < right ? -1 : left > right ? 1 : 0;
308
342
  }
309
- function sortedRecord<T>(
310
- value: Readonly<Record<string, T>>,
311
- ): Readonly<Record<string, T>> {
343
+ function sortedRecord<T>(value: Readonly<Record<string, T>>): Readonly<Record<string, T>> {
312
344
  const result = diagnosticRecord<T>();
313
- for (const key of Object.keys(value).sort(codeUnit))
314
- result[key] = value[key]!;
345
+ for (const key of Object.keys(value).sort(codeUnit)) result[key] = value[key]!;
315
346
  return result;
316
347
  }
317
348
 
318
349
  function explicitUndefinedIssues(value: unknown): DiagnosticValidationIssue[] {
319
350
  const found: DiagnosticValidationIssue[] = [];
320
- const stack: { readonly value: unknown; readonly path: string }[] = [
321
- { value, path: "$" },
322
- ];
351
+ const stack: { readonly value: unknown; readonly path: string }[] = [{ value, path: "$" }];
323
352
  const seen = new WeakSet<object>();
324
353
  while (stack.length > 0) {
325
354
  const current = stack.pop()!;
326
- if (
327
- current.value === null ||
328
- typeof current.value !== "object" ||
329
- seen.has(current.value)
330
- )
355
+ if (current.value === null || typeof current.value !== "object" || seen.has(current.value))
331
356
  continue;
332
357
  seen.add(current.value);
333
358
  for (const [key, child] of Object.entries(current.value)) {
@@ -355,38 +380,29 @@ function explicitUndefinedIssues(value: unknown): DiagnosticValidationIssue[] {
355
380
  return found;
356
381
  }
357
382
 
358
- export function validateDiagnosticRunInput(
359
- value: unknown,
360
- ): ValidatedDiagnosticRunInput {
383
+ // Both public gateways share the same full validation/ownership boundary.
384
+ // Selecting compact storage never invokes the eager preparation first.
385
+ function validateRunInputContent(value: unknown): DiagnosticRunInputContent {
361
386
  const undefinedIssues = explicitUndefinedIssues(value);
362
- if (undefinedIssues.length > 0)
363
- throw new DiagnosticValidationError(undefinedIssues);
387
+ if (undefinedIssues.length > 0) throw new DiagnosticValidationError(undefinedIssues);
364
388
  const parsed = runSchema.safeParse(value);
365
389
  if (!parsed.success) throw issues(parsed.error);
366
- const definition = compileDiagnosticDefinition(
367
- parsed.data.definition as DiagnosticDefinition,
390
+ const definition = compileDiagnosticDefinition(parsed.data.definition as DiagnosticDefinition);
391
+ const relationIssues: DiagnosticValidationIssue[] = parsed.data.losses.flatMap((row, index) =>
392
+ row.rowType === definition.definition.lossRowGrain
393
+ ? []
394
+ : [
395
+ {
396
+ domain: "input" as const,
397
+ code: "invalid-input-relationship" as const,
398
+ path: `$.losses[${index}].rowType`,
399
+ message: "Loss row type does not match definition grain",
400
+ },
401
+ ],
368
402
  );
369
- const relationIssues: DiagnosticValidationIssue[] =
370
- parsed.data.losses.flatMap((row, index) =>
371
- row.rowType === definition.definition.lossRowGrain
372
- ? []
373
- : [
374
- {
375
- domain: "input" as const,
376
- code: "invalid-input-relationship" as const,
377
- path: `$.losses[${index}].rowType`,
378
- message: "Loss row type does not match definition grain",
379
- },
380
- ],
381
- );
382
403
  for (const [index, row] of (parsed.data.exposures ?? []).entries()) {
383
- const measure = definition.definition.measures.find(
384
- (item) => item.id === row.measureId,
385
- );
386
- if (
387
- measure?.exposureTiming === "valuation-specific" &&
388
- row.valuation === undefined
389
- )
404
+ const measure = definition.definition.measures.find((item) => item.id === row.measureId);
405
+ if (measure?.exposureTiming === "valuation-specific" && row.valuation === undefined)
390
406
  relationIssues.push({
391
407
  domain: "input",
392
408
  code: "missing-required",
@@ -394,22 +410,11 @@ export function validateDiagnosticRunInput(
394
410
  message: "Valuation-specific exposure requires valuation",
395
411
  });
396
412
  }
397
- if (relationIssues.length)
398
- throw new DiagnosticValidationError(relationIssues);
399
- const review = parsed.data.policy?.allowedReviewStatuses ?? [
400
- "pass",
401
- "warning",
402
- "not-evaluated",
403
- ];
404
- const metric = parsed.data.policy?.allowedMetricFindingSeverities ?? [
405
- "info",
406
- "warning",
407
- ];
413
+ if (relationIssues.length) throw new DiagnosticValidationError(relationIssues);
414
+ const review = parsed.data.policy?.allowedReviewStatuses ?? ["pass", "warning", "not-evaluated"];
415
+ const metric = parsed.data.policy?.allowedMetricFindingSeverities ?? ["info", "warning"];
408
416
  const rationale = parsed.data.policy?.rationaleRef ?? null;
409
- if (
410
- (review.includes("fail") || metric.includes("fail")) &&
411
- rationale === null
412
- )
417
+ if ((review.includes("fail") || metric.includes("fail")) && rationale === null)
413
418
  throw new DiagnosticValidationError([
414
419
  {
415
420
  domain: "configuration",
@@ -419,24 +424,16 @@ export function validateDiagnosticRunInput(
419
424
  },
420
425
  ]);
421
426
  const reviewEvidence =
422
- parsed.data.reviewEvidence === undefined ||
423
- parsed.data.reviewEvidence === null
427
+ parsed.data.reviewEvidence === undefined || parsed.data.reviewEvidence === null
424
428
  ? null
425
- : validateDiagnosticReviewEvidence(
426
- parsed.data.reviewEvidence,
427
- "$.reviewEvidence",
428
- );
429
+ : validateDiagnosticReviewEvidence(parsed.data.reviewEvidence, "$.reviewEvidence");
429
430
  const reviewOrder: readonly DiagnosticAllowedReviewStatus[] = [
430
431
  "pass",
431
432
  "warning",
432
433
  "not-evaluated",
433
434
  "fail",
434
435
  ];
435
- const metricOrder: readonly ("info" | "warning" | "fail")[] = [
436
- "info",
437
- "warning",
438
- "fail",
439
- ];
436
+ const metricOrder: readonly ("info" | "warning" | "fail")[] = ["info", "warning", "fail"];
440
437
  for (const key of [
441
438
  ...Object.keys(parsed.data.groupMap ?? {}),
442
439
  ...Object.keys(parsed.data.groupDimensions ?? {}),
@@ -447,8 +444,7 @@ export function validateDiagnosticRunInput(
447
444
  domain: "configuration",
448
445
  code: "invalid-string",
449
446
  path: `$.groupMap[${JSON.stringify(key)}]`,
450
- message:
451
- "Group key must be a nonempty token with valid Unicode and no U+0000",
447
+ message: "Group key must be a nonempty token with valid Unicode and no U+0000",
452
448
  },
453
449
  ]);
454
450
  const result = freeze({
@@ -461,39 +457,76 @@ export function validateDiagnosticRunInput(
461
457
  reviewEvidence,
462
458
  runPresetId: parsed.data.runPresetId ?? null,
463
459
  datasetArtifactId: parsed.data.datasetArtifactId ?? null,
464
- groupMap: sortedRecord(parsed.data.groupMap ?? diagnosticRecord()),
465
- groupDimensions: sortedRecord(
466
- parsed.data.groupDimensions ?? diagnosticRecord(),
460
+ groupMap: sortedRecord<string>(parsed.data.groupMap ?? diagnosticRecord<string>()),
461
+ groupDimensions: sortedRecord<JsonValue>(
462
+ parsed.data.groupDimensions ?? diagnosticRecord<JsonValue>(),
467
463
  ),
468
464
  policy: {
469
- allowedReviewStatuses: reviewOrder.filter((status) =>
470
- review.includes(status),
471
- ),
472
- allowedMetricFindingSeverities: metricOrder.filter((severity) =>
473
- metric.includes(severity),
474
- ),
465
+ allowedReviewStatuses: reviewOrder.filter((status) => review.includes(status)),
466
+ allowedMetricFindingSeverities: metricOrder.filter((severity) => metric.includes(severity)),
475
467
  rationaleRef: rationale,
476
468
  },
477
- }) as unknown as ValidatedDiagnosticRunInput;
478
- const prepared = prepareDiagnosticData({
479
- definition: result.definition,
480
- losses: result.losses,
481
- exposures: result.exposures,
482
- ...(result.filter === null ? {} : { filter: result.filter }),
483
- completePeriodCutoffs: result.completePeriodCutoffs,
484
- ...(result.expectedCells === null
485
- ? {}
486
- : { expectedCells: result.expectedCells }),
487
469
  });
470
+ return result;
471
+ }
472
+
473
+ function preparationInput(input: DiagnosticRunInputContent) {
474
+ return {
475
+ definition: input.definition,
476
+ losses: input.losses,
477
+ exposures: input.exposures,
478
+ ...(input.filter === null ? {} : { filter: input.filter }),
479
+ completePeriodCutoffs: input.completePeriodCutoffs,
480
+ ...(input.expectedCells === null ? {} : { expectedCells: input.expectedCells }),
481
+ };
482
+ }
483
+
484
+ export function validateDiagnosticRunInput(value: unknown): ValidatedDiagnosticRunInput {
485
+ const result = validateRunInputContent(value) as ValidatedDiagnosticRunInput;
486
+ const prepared = prepareDiagnosticData(preparationInput(result));
488
487
  validateDiagnosticGroupingConfiguration({
489
488
  prepared,
490
489
  groupMap: result.groupMap,
491
490
  groupDimensions: result.groupDimensions,
492
491
  });
493
492
  authentic.add(result);
493
+ preparedByInput.set(result, prepared);
494
494
  return result;
495
495
  }
496
496
 
497
+ /** Validate, own and prepare inputs without eagerly materializing identity graphs. */
498
+ export function validateDiagnosticRunInputCompact(
499
+ value: unknown,
500
+ ): CompactValidatedDiagnosticRunInput {
501
+ const result = validateRunInputContent(value) as CompactValidatedDiagnosticRunInput;
502
+ const prepared = prepareDiagnosticDataCompact(preparationInput(result));
503
+ validateCompactDiagnosticGroupingConfiguration({
504
+ prepared,
505
+ groupMap: result.groupMap,
506
+ groupDimensions: result.groupDimensions,
507
+ });
508
+ compactPreparedByInput.set(result, prepared);
509
+ return result;
510
+ }
511
+
512
+ export function assertCompactValidatedDiagnosticRunInput(
513
+ value: unknown,
514
+ ): asserts value is CompactValidatedDiagnosticRunInput {
515
+ if (
516
+ value === null ||
517
+ typeof value !== "object" ||
518
+ !compactPreparedByInput.has(value as CompactValidatedDiagnosticRunInput)
519
+ )
520
+ throw new DiagnosticValidationError([
521
+ {
522
+ domain: "input",
523
+ code: "invalid-input-relationship",
524
+ path: "$",
525
+ message: "Value is not an authentic compact validated diagnostic run input",
526
+ },
527
+ ]);
528
+ }
529
+
497
530
  export function assertValidatedDiagnosticRunInput(
498
531
  value: unknown,
499
532
  ): asserts value is ValidatedDiagnosticRunInput {
@@ -513,16 +546,9 @@ export function runValidatedMetricDiagnostics(
513
546
  input: ValidatedDiagnosticRunInput,
514
547
  ): ValidatedMetricDiagnosticsOutcome {
515
548
  assertValidatedDiagnosticRunInput(input);
516
- const prepared = prepareDiagnosticData({
517
- definition: input.definition,
518
- losses: input.losses,
519
- exposures: input.exposures,
520
- ...(input.filter === null ? {} : { filter: input.filter }),
521
- completePeriodCutoffs: input.completePeriodCutoffs,
522
- ...(input.expectedCells === null
523
- ? {}
524
- : { expectedCells: input.expectedCells }),
525
- });
549
+ // Validation already prepared these exact immutable inputs and checked their
550
+ // grouping. Reuse the authentic result without skipping any execution gate.
551
+ const prepared = preparedByInput.get(input)!;
526
552
  validateDiagnosticGroupingConfiguration({
527
553
  prepared,
528
554
  groupMap: input.groupMap,
@@ -547,10 +573,7 @@ export function runValidatedMetricDiagnostics(
547
573
  (check) => !input.policy.allowedReviewStatuses.includes(check.status),
548
574
  ) ||
549
575
  review.evaluations.some(
550
- (evaluation) =>
551
- !input.policy.allowedReviewStatuses.includes(
552
- evaluationStatus(evaluation),
553
- ),
576
+ (evaluation) => !input.policy.allowedReviewStatuses.includes(evaluationStatus(evaluation)),
554
577
  );
555
578
  const base = {
556
579
  prepared,
@@ -560,8 +583,13 @@ export function runValidatedMetricDiagnostics(
560
583
  groupMap: input.groupMap,
561
584
  groupDimensions: input.groupDimensions,
562
585
  };
586
+ // These graphs were deeply frozen by their authentic SDK constructors. Walk
587
+ // only the new outcome envelope; a shallow-frozen caller object never enters
588
+ // this set, so the public input boundary still checks/freezes every child.
589
+ const frozenSdkGraphs = new WeakSet<object>([prepared, review]);
590
+ const freezeOutcome = <T>(value: T) => freeze(value, frozenSdkGraphs);
563
591
  if (disallowedReview)
564
- return freeze({
592
+ return freezeOutcome({
565
593
  ...base,
566
594
  status: "blocked" as const,
567
595
  stage: "review" as const,
@@ -577,13 +605,14 @@ export function runValidatedMetricDiagnostics(
577
605
  groupMap: input.groupMap,
578
606
  groupDimensions: input.groupDimensions,
579
607
  });
608
+ frozenSdkGraphs.add(result);
580
609
  const disallowedMetric = result.findings.some(
581
610
  (finding) =>
582
611
  finding.category !== "structural" &&
583
612
  !input.policy.allowedMetricFindingSeverities.includes(finding.severity),
584
613
  );
585
614
  if (disallowedMetric)
586
- return freeze({
615
+ return freezeOutcome({
587
616
  ...base,
588
617
  status: "blocked" as const,
589
618
  stage: "metric" as const,
@@ -594,7 +623,7 @@ export function runValidatedMetricDiagnostics(
594
623
  metricGate: "blocked" as const,
595
624
  },
596
625
  }) as ValidatedMetricDiagnosticsOutcome;
597
- const outcome = freeze({
626
+ const outcome = freezeOutcome({
598
627
  ...base,
599
628
  status: "completed" as const,
600
629
  result,
@@ -620,3 +649,121 @@ export function assertCompletedValidatedMetricDiagnosticsRun(
620
649
  },
621
650
  ]);
622
651
  }
652
+
653
+ /** Run every review and metric gate while retaining complete compact evidence. */
654
+ export function runValidatedMetricDiagnosticsCompact(
655
+ input: CompactValidatedDiagnosticRunInput,
656
+ ): CompactMetricDiagnosticsOutcome {
657
+ assertCompactValidatedDiagnosticRunInput(input);
658
+ const prepared = compactPreparedByInput.get(input)!;
659
+ validateCompactDiagnosticGroupingConfiguration({
660
+ prepared,
661
+ groupMap: input.groupMap,
662
+ groupDimensions: input.groupDimensions,
663
+ });
664
+ const review = reviewPreparedDiagnosticDataCompact({
665
+ prepared,
666
+ evidence: input.reviewEvidence as DiagnosticReviewEvidence | null,
667
+ });
668
+ const counts = review.evaluations.summary;
669
+ // Aggregate check status and individual effective status are both necessary:
670
+ // an allowed failure must not hide a disallowed not-evaluated row (or vice versa).
671
+ const effectiveCounts: Readonly<Record<DiagnosticAllowedReviewStatus, number>> = {
672
+ pass: counts.pass,
673
+ warning: counts.warning,
674
+ "not-evaluated": counts.notEvaluated,
675
+ fail: counts.fail,
676
+ };
677
+ const disallowedReview =
678
+ review.report.checks.some(
679
+ (check) => !input.policy.allowedReviewStatuses.includes(check.status),
680
+ ) ||
681
+ (Object.keys(effectiveCounts) as DiagnosticAllowedReviewStatus[]).some(
682
+ (status) =>
683
+ effectiveCounts[status] > 0 && !input.policy.allowedReviewStatuses.includes(status),
684
+ );
685
+ const base = {
686
+ prepared,
687
+ review,
688
+ runPresetId: input.runPresetId,
689
+ datasetArtifactId: input.datasetArtifactId,
690
+ groupMap: input.groupMap,
691
+ groupDimensions: input.groupDimensions,
692
+ };
693
+ const frozenSdkGraphs = new WeakSet<object>([prepared, review]);
694
+ const freezeOutcome = <T>(value: T) => freeze(value, frozenSdkGraphs);
695
+ if (disallowedReview)
696
+ return freezeOutcome({
697
+ ...base,
698
+ status: "blocked" as const,
699
+ stage: "review" as const,
700
+ result: null,
701
+ gate: {
702
+ ...input.policy,
703
+ reviewGate: "blocked" as const,
704
+ metricGate: "not-run" as const,
705
+ },
706
+ });
707
+ const result = runMetricDiagnosticsCompact({
708
+ prepared,
709
+ groupMap: input.groupMap,
710
+ groupDimensions: input.groupDimensions,
711
+ });
712
+ frozenSdkGraphs.add(result);
713
+ const disallowedMetric = result.findings.some(
714
+ (finding) =>
715
+ finding.category !== "structural" &&
716
+ !input.policy.allowedMetricFindingSeverities.includes(finding.severity),
717
+ );
718
+ if (disallowedMetric)
719
+ return freezeOutcome({
720
+ ...base,
721
+ status: "blocked" as const,
722
+ stage: "metric" as const,
723
+ result,
724
+ gate: {
725
+ ...input.policy,
726
+ reviewGate: "passed" as const,
727
+ metricGate: "blocked" as const,
728
+ },
729
+ });
730
+ const outcome = freezeOutcome({
731
+ ...base,
732
+ status: "completed" as const,
733
+ result,
734
+ gate: {
735
+ ...input.policy,
736
+ reviewGate: "passed" as const,
737
+ metricGate: "passed" as const,
738
+ },
739
+ });
740
+ compactCompleted.add(outcome);
741
+ compactInputByCompletedRun.set(outcome, input);
742
+ return outcome;
743
+ }
744
+
745
+ export function assertCompletedCompactMetricDiagnosticsRun(
746
+ value: unknown,
747
+ ): asserts value is CompletedCompactMetricDiagnosticsRun {
748
+ if (value === null || typeof value !== "object" || !compactCompleted.has(value))
749
+ throw new DiagnosticValidationError([
750
+ {
751
+ domain: "input",
752
+ code: "invalid-input-relationship",
753
+ path: "$",
754
+ message: "Value is not an authentic completed compact diagnostic run",
755
+ },
756
+ ]);
757
+ }
758
+
759
+ /**
760
+ * Return the original SDK-owned validated input for an authentic completed run.
761
+ * This is the same deeply immutable input owner, not a reconstruction from its
762
+ * normalized audit or a mutable copy of the caller's upload values.
763
+ */
764
+ export function getCompletedCompactDiagnosticRunInput(
765
+ run: CompletedCompactMetricDiagnosticsRun,
766
+ ): CompactValidatedDiagnosticRunInput {
767
+ assertCompletedCompactMetricDiagnosticsRun(run);
768
+ return compactInputByCompletedRun.get(run)!;
769
+ }