@actuarial-ts/data 0.6.0 → 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.
- package/README.md +3 -3
- package/dist/compactDiagnosticJson.d.ts +24 -0
- package/dist/compactDiagnosticJson.d.ts.map +1 -0
- package/dist/compactDiagnosticJson.js +200 -0
- package/dist/compactDiagnosticJson.js.map +1 -0
- package/dist/diagnosticInput.d.ts +55 -4
- package/dist/diagnosticInput.d.ts.map +1 -1
- package/dist/diagnosticInput.js +499 -35
- package/dist/diagnosticInput.js.map +1 -1
- package/dist/diagnosticPreparedReview.d.ts +72 -6
- package/dist/diagnosticPreparedReview.d.ts.map +1 -1
- package/dist/diagnosticPreparedReview.js +749 -38
- package/dist/diagnosticPreparedReview.js.map +1 -1
- package/dist/exposure.d.ts.map +1 -1
- package/dist/exposure.js +25 -9
- package/dist/exposure.js.map +1 -1
- package/dist/lossRun.d.ts.map +1 -1
- package/dist/lossRun.js +31 -8
- package/dist/lossRun.js.map +1 -1
- package/dist/review.d.ts +1 -2
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +33 -4
- package/dist/review.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
- package/src/compactDiagnosticJson.ts +216 -0
- package/src/diagnosticInput.ts +746 -51
- package/src/diagnosticPreparedReview.ts +1189 -37
- package/src/exposure.ts +61 -16
- package/src/lossRun.ts +55 -13
- package/src/review.ts +177 -40
- package/src/version.ts +1 -1
package/src/diagnosticInput.ts
CHANGED
|
@@ -2,7 +2,13 @@ import {
|
|
|
2
2
|
DiagnosticValidationError,
|
|
3
3
|
compileDiagnosticDefinition,
|
|
4
4
|
prepareDiagnosticData,
|
|
5
|
+
prepareDiagnosticDataCompact,
|
|
5
6
|
runMetricDiagnostics,
|
|
7
|
+
runMetricDiagnosticsCompact,
|
|
8
|
+
validateDiagnosticGroupingConfiguration,
|
|
9
|
+
validateCompactDiagnosticGroupingConfiguration,
|
|
10
|
+
type CompactMetricDiagnosticsResult,
|
|
11
|
+
type CompactPreparedDiagnosticData,
|
|
6
12
|
type CompiledDiagnosticDefinition,
|
|
7
13
|
type DiagnosticCompletePeriodCutoff,
|
|
8
14
|
type DiagnosticDeepReadonly,
|
|
@@ -13,62 +19,751 @@ import {
|
|
|
13
19
|
type DiagnosticsFilter,
|
|
14
20
|
type JsonValue,
|
|
15
21
|
type MetricDiagnosticsResult,
|
|
22
|
+
type PreparedDiagnosticData,
|
|
16
23
|
type DiagnosticValidationIssue,
|
|
24
|
+
diagnosticRecord,
|
|
25
|
+
isDiagnosticToken,
|
|
26
|
+
isWellFormedDiagnosticString,
|
|
17
27
|
} from "@actuarial-ts/core";
|
|
18
28
|
import { z } from "zod";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
import {
|
|
30
|
+
reviewPreparedDiagnosticData,
|
|
31
|
+
reviewPreparedDiagnosticDataCompact,
|
|
32
|
+
validateDiagnosticReviewEvidence,
|
|
33
|
+
type DiagnosticReviewEvidence,
|
|
34
|
+
type DiagnosticReviewReceipt,
|
|
35
|
+
type CompactDiagnosticReviewReceipt,
|
|
36
|
+
} from "./diagnosticPreparedReview.js";
|
|
37
|
+
|
|
38
|
+
// Zod 3 validates but drops the literal __proto__ key while assembling records.
|
|
39
|
+
// Encode every key reversibly during validation, then restore owned data keys.
|
|
40
|
+
// The same adapter is exercised by the shared three-shore prototype-key corpus.
|
|
41
|
+
function recordSchema<T extends z.ZodTypeAny>(value: T) {
|
|
42
|
+
return z
|
|
43
|
+
.record(
|
|
44
|
+
z.string().transform((key) => `:${key}`),
|
|
45
|
+
value,
|
|
46
|
+
)
|
|
47
|
+
.transform(
|
|
48
|
+
(record) =>
|
|
49
|
+
Object.fromEntries(
|
|
50
|
+
Object.entries(record).map(([key, item]) => [key.slice(1), item]),
|
|
51
|
+
) as Record<string, z.output<T>>,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tokenSchema = z
|
|
56
|
+
.string()
|
|
57
|
+
.refine(isDiagnosticToken, "Expected a nonempty token with valid Unicode and no U+0000");
|
|
58
|
+
const jsonStringSchema = z
|
|
59
|
+
.string()
|
|
60
|
+
.refine(isWellFormedDiagnosticString, "Expected valid Unicode without U+0000");
|
|
61
|
+
const sourceSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
artifactId: tokenSchema,
|
|
64
|
+
sourceFile: tokenSchema.optional(),
|
|
65
|
+
sourceSheet: tokenSchema.optional(),
|
|
66
|
+
sourceRow: z.number().int().nonnegative().safe().optional(),
|
|
67
|
+
sourceCell: tokenSchema.optional(),
|
|
68
|
+
})
|
|
69
|
+
.strict();
|
|
70
|
+
const rawNumberSchema = z.custom<number>((value) => typeof value === "number", "Expected number");
|
|
71
|
+
const measuresSchema = recordSchema(z.union([rawNumberSchema, z.null()]));
|
|
72
|
+
const lossBase = {
|
|
73
|
+
recordId: tokenSchema,
|
|
74
|
+
sourceGroup: tokenSchema,
|
|
75
|
+
origin: tokenSchema,
|
|
76
|
+
valuation: tokenSchema,
|
|
77
|
+
complete: z.boolean(),
|
|
78
|
+
source: sourceSchema.optional(),
|
|
79
|
+
measures: measuresSchema,
|
|
80
|
+
};
|
|
81
|
+
const lossSchema = z.discriminatedUnion("rowType", [
|
|
82
|
+
z.object({ ...lossBase, rowType: z.literal("claim"), claimId: tokenSchema }).strict(),
|
|
83
|
+
z.object({ ...lossBase, rowType: z.literal("aggregate") }).strict(),
|
|
84
|
+
]);
|
|
85
|
+
const exposureSchema = z
|
|
86
|
+
.object({
|
|
87
|
+
key: tokenSchema,
|
|
88
|
+
sourceGroup: tokenSchema,
|
|
89
|
+
origin: tokenSchema,
|
|
90
|
+
valuation: tokenSchema.optional(),
|
|
91
|
+
measureId: tokenSchema,
|
|
92
|
+
value: z.union([rawNumberSchema, z.null()]),
|
|
93
|
+
complete: z.boolean(),
|
|
94
|
+
source: sourceSchema.optional(),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
const filterSchema = z
|
|
98
|
+
.object({
|
|
99
|
+
sourceGroups: z.array(tokenSchema).optional(),
|
|
100
|
+
outputGroups: z.array(tokenSchema).optional(),
|
|
101
|
+
origins: z.array(tokenSchema).optional(),
|
|
102
|
+
originFrom: tokenSchema.optional(),
|
|
103
|
+
originThrough: tokenSchema.optional(),
|
|
104
|
+
valuations: z.array(tokenSchema).optional(),
|
|
105
|
+
valuationFrom: tokenSchema.optional(),
|
|
106
|
+
valuationThrough: tokenSchema.optional(),
|
|
107
|
+
minDevelopmentAge: z.number().int().nonnegative().safe().optional(),
|
|
108
|
+
maxDevelopmentAge: z.number().int().nonnegative().safe().optional(),
|
|
109
|
+
instanceIds: z.array(tokenSchema).optional(),
|
|
110
|
+
})
|
|
111
|
+
.strict();
|
|
112
|
+
const cutoffSchema = z
|
|
113
|
+
.object({
|
|
114
|
+
sourceGroup: tokenSchema,
|
|
115
|
+
originThrough: tokenSchema.nullable(),
|
|
116
|
+
valuationThrough: tokenSchema.nullable(),
|
|
117
|
+
})
|
|
118
|
+
.strict();
|
|
119
|
+
const expectedSchema = z
|
|
120
|
+
.object({
|
|
121
|
+
sourceGroup: tokenSchema,
|
|
122
|
+
origin: tokenSchema,
|
|
123
|
+
valuation: tokenSchema,
|
|
124
|
+
source: sourceSchema.optional(),
|
|
125
|
+
})
|
|
126
|
+
.strict();
|
|
127
|
+
const jsonSchema: z.ZodType<JsonValue> = z.lazy(() =>
|
|
128
|
+
z.union([
|
|
129
|
+
z.null(),
|
|
130
|
+
z.boolean(),
|
|
131
|
+
z.number().finite(),
|
|
132
|
+
jsonStringSchema,
|
|
133
|
+
z.array(jsonSchema),
|
|
134
|
+
recordSchema(jsonSchema),
|
|
135
|
+
]),
|
|
136
|
+
);
|
|
137
|
+
const policySchema = z
|
|
138
|
+
.object({
|
|
139
|
+
allowedReviewStatuses: z.array(z.enum(["pass", "warning", "not-evaluated", "fail"])).optional(),
|
|
140
|
+
allowedMetricFindingSeverities: z.array(z.enum(["info", "warning", "fail"])).optional(),
|
|
141
|
+
rationaleRef: tokenSchema.optional(),
|
|
142
|
+
})
|
|
143
|
+
.strict();
|
|
144
|
+
const runSchema = z
|
|
145
|
+
.object({
|
|
146
|
+
definition: z.unknown(),
|
|
147
|
+
losses: z.array(lossSchema),
|
|
148
|
+
exposures: z.array(exposureSchema).optional(),
|
|
149
|
+
filter: filterSchema.optional(),
|
|
150
|
+
completePeriodCutoffs: z.array(cutoffSchema).optional(),
|
|
151
|
+
expectedCells: z.array(expectedSchema).optional(),
|
|
152
|
+
reviewEvidence: z.unknown().nullable().optional(),
|
|
153
|
+
runPresetId: tokenSchema.optional(),
|
|
154
|
+
datasetArtifactId: tokenSchema.optional(),
|
|
155
|
+
groupMap: recordSchema(tokenSchema).optional(),
|
|
156
|
+
groupDimensions: recordSchema(jsonSchema).optional(),
|
|
157
|
+
policy: policySchema.optional(),
|
|
158
|
+
})
|
|
159
|
+
.strict();
|
|
32
160
|
|
|
33
161
|
export type DiagnosticAllowedReviewStatus = "pass" | "warning" | "not-evaluated" | "fail";
|
|
34
|
-
export interface DiagnosticExecutionPolicyInput {
|
|
35
|
-
|
|
162
|
+
export interface DiagnosticExecutionPolicyInput {
|
|
163
|
+
readonly allowedReviewStatuses?: readonly DiagnosticAllowedReviewStatus[];
|
|
164
|
+
readonly allowedMetricFindingSeverities?: readonly ("info" | "warning" | "fail")[];
|
|
165
|
+
readonly rationaleRef?: string;
|
|
166
|
+
}
|
|
167
|
+
export interface DiagnosticRunInput {
|
|
168
|
+
readonly definition: DiagnosticDefinition;
|
|
169
|
+
readonly losses: readonly DiagnosticLossInput[];
|
|
170
|
+
readonly exposures?: readonly DiagnosticExposureObservation[];
|
|
171
|
+
readonly filter?: DiagnosticsFilter;
|
|
172
|
+
readonly completePeriodCutoffs?: readonly DiagnosticCompletePeriodCutoff[];
|
|
173
|
+
readonly expectedCells?: readonly DiagnosticExpectedCell[];
|
|
174
|
+
readonly reviewEvidence?: DiagnosticReviewEvidence | null;
|
|
175
|
+
readonly runPresetId?: string;
|
|
176
|
+
readonly datasetArtifactId?: string;
|
|
177
|
+
readonly groupMap?: Readonly<Record<string, string>>;
|
|
178
|
+
readonly groupDimensions?: Readonly<Record<string, JsonValue>>;
|
|
179
|
+
readonly policy?: DiagnosticExecutionPolicyInput;
|
|
180
|
+
}
|
|
36
181
|
declare const validatedDiagnosticRunInputBrand: unique symbol;
|
|
37
|
-
export interface ValidatedDiagnosticRunInput {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
182
|
+
export interface ValidatedDiagnosticRunInput {
|
|
183
|
+
readonly [validatedDiagnosticRunInputBrand]: true;
|
|
184
|
+
readonly definition: CompiledDiagnosticDefinition;
|
|
185
|
+
readonly losses: readonly DiagnosticDeepReadonly<DiagnosticLossInput>[];
|
|
186
|
+
readonly exposures: readonly DiagnosticDeepReadonly<DiagnosticExposureObservation>[];
|
|
187
|
+
readonly filter: DiagnosticDeepReadonly<DiagnosticsFilter> | null;
|
|
188
|
+
readonly completePeriodCutoffs: readonly DiagnosticCompletePeriodCutoff[];
|
|
189
|
+
readonly expectedCells: readonly DiagnosticExpectedCell[] | null;
|
|
190
|
+
readonly reviewEvidence: DiagnosticDeepReadonly<DiagnosticReviewEvidence> | null;
|
|
191
|
+
readonly runPresetId: string | null;
|
|
192
|
+
readonly datasetArtifactId: string | null;
|
|
193
|
+
readonly groupMap: Readonly<Record<string, string>>;
|
|
194
|
+
readonly groupDimensions: Readonly<Record<string, JsonValue>>;
|
|
195
|
+
readonly policy: {
|
|
196
|
+
readonly allowedReviewStatuses: readonly DiagnosticAllowedReviewStatus[];
|
|
197
|
+
readonly allowedMetricFindingSeverities: readonly ("info" | "warning" | "fail")[];
|
|
198
|
+
readonly rationaleRef: string | null;
|
|
199
|
+
};
|
|
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
|
+
}
|
|
211
|
+
export interface DiagnosticExecutionGateReceipt {
|
|
212
|
+
readonly allowedReviewStatuses: readonly DiagnosticAllowedReviewStatus[];
|
|
213
|
+
readonly allowedMetricFindingSeverities: readonly ("info" | "warning" | "fail")[];
|
|
214
|
+
readonly rationaleRef: string | null;
|
|
215
|
+
readonly reviewGate: "passed" | "blocked";
|
|
216
|
+
readonly metricGate: "not-run" | "passed" | "blocked";
|
|
217
|
+
}
|
|
218
|
+
export interface CompletedValidatedMetricDiagnosticsRun {
|
|
219
|
+
readonly status: "completed";
|
|
220
|
+
readonly prepared: import("@actuarial-ts/core").PreparedDiagnosticData;
|
|
221
|
+
readonly review: DiagnosticReviewReceipt;
|
|
222
|
+
readonly result: DiagnosticDeepReadonly<MetricDiagnosticsResult>;
|
|
223
|
+
readonly runPresetId: string | null;
|
|
224
|
+
readonly datasetArtifactId: string | null;
|
|
225
|
+
readonly groupMap: Readonly<Record<string, string>>;
|
|
226
|
+
readonly groupDimensions: Readonly<Record<string, JsonValue>>;
|
|
227
|
+
readonly gate: DiagnosticExecutionGateReceipt & {
|
|
228
|
+
readonly reviewGate: "passed";
|
|
229
|
+
readonly metricGate: "passed";
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export type ValidatedMetricDiagnosticsOutcome =
|
|
233
|
+
| CompletedValidatedMetricDiagnosticsRun
|
|
234
|
+
| {
|
|
235
|
+
readonly status: "blocked";
|
|
236
|
+
readonly stage: "review";
|
|
237
|
+
readonly prepared: import("@actuarial-ts/core").PreparedDiagnosticData;
|
|
238
|
+
readonly review: DiagnosticReviewReceipt;
|
|
239
|
+
readonly result: null;
|
|
240
|
+
readonly runPresetId: string | null;
|
|
241
|
+
readonly datasetArtifactId: string | null;
|
|
242
|
+
readonly groupMap: Readonly<Record<string, string>>;
|
|
243
|
+
readonly groupDimensions: Readonly<Record<string, JsonValue>>;
|
|
244
|
+
readonly gate: DiagnosticExecutionGateReceipt & {
|
|
245
|
+
readonly reviewGate: "blocked";
|
|
246
|
+
readonly metricGate: "not-run";
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
| {
|
|
250
|
+
readonly status: "blocked";
|
|
251
|
+
readonly stage: "metric";
|
|
252
|
+
readonly prepared: import("@actuarial-ts/core").PreparedDiagnosticData;
|
|
253
|
+
readonly review: DiagnosticReviewReceipt;
|
|
254
|
+
readonly result: DiagnosticDeepReadonly<MetricDiagnosticsResult>;
|
|
255
|
+
readonly runPresetId: string | null;
|
|
256
|
+
readonly datasetArtifactId: string | null;
|
|
257
|
+
readonly groupMap: Readonly<Record<string, string>>;
|
|
258
|
+
readonly groupDimensions: Readonly<Record<string, JsonValue>>;
|
|
259
|
+
readonly gate: DiagnosticExecutionGateReceipt & {
|
|
260
|
+
readonly reviewGate: "passed";
|
|
261
|
+
readonly metricGate: "blocked";
|
|
262
|
+
};
|
|
263
|
+
};
|
|
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
|
+
});
|
|
41
301
|
|
|
42
302
|
const authentic = new WeakSet<object>();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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> {
|
|
318
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
319
|
+
return value as DiagnosticDeepReadonly<T>;
|
|
320
|
+
seen.add(value);
|
|
321
|
+
for (const child of Object.values(value as Record<string, unknown>)) freeze(child, seen);
|
|
322
|
+
return Object.freeze(value) as DiagnosticDeepReadonly<T>;
|
|
323
|
+
}
|
|
324
|
+
function issues(error: z.ZodError): DiagnosticValidationError {
|
|
325
|
+
return new DiagnosticValidationError(
|
|
326
|
+
error.issues.map((issue) => ({
|
|
327
|
+
domain: (issue.path[0] === "definition"
|
|
328
|
+
? "definition"
|
|
329
|
+
: issue.path[0] === "losses" ||
|
|
330
|
+
issue.path[0] === "exposures" ||
|
|
331
|
+
issue.path[0] === "reviewEvidence"
|
|
332
|
+
? "input"
|
|
333
|
+
: "configuration") as "definition" | "input" | "configuration",
|
|
334
|
+
code: issue.code === "unrecognized_keys" ? "unknown-key" : "invalid-type",
|
|
335
|
+
path: `$${issue.path.map((part) => (typeof part === "number" ? `[${part}]` : /^[A-Za-z_$][\w$]*$/.test(part) ? `.${part}` : `[${JSON.stringify(part)}]`)).join("")}`,
|
|
336
|
+
message: issue.message,
|
|
337
|
+
})),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
function codeUnit(left: string, right: string): number {
|
|
341
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
342
|
+
}
|
|
343
|
+
function sortedRecord<T>(value: Readonly<Record<string, T>>): Readonly<Record<string, T>> {
|
|
344
|
+
const result = diagnosticRecord<T>();
|
|
345
|
+
for (const key of Object.keys(value).sort(codeUnit)) result[key] = value[key]!;
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function explicitUndefinedIssues(value: unknown): DiagnosticValidationIssue[] {
|
|
350
|
+
const found: DiagnosticValidationIssue[] = [];
|
|
351
|
+
const stack: { readonly value: unknown; readonly path: string }[] = [{ value, path: "$" }];
|
|
352
|
+
const seen = new WeakSet<object>();
|
|
353
|
+
while (stack.length > 0) {
|
|
354
|
+
const current = stack.pop()!;
|
|
355
|
+
if (current.value === null || typeof current.value !== "object" || seen.has(current.value))
|
|
356
|
+
continue;
|
|
357
|
+
seen.add(current.value);
|
|
358
|
+
for (const [key, child] of Object.entries(current.value)) {
|
|
359
|
+
const path = Array.isArray(current.value)
|
|
360
|
+
? `${current.path}[${key}]`
|
|
361
|
+
: /^[A-Za-z_$][\w$]*$/.test(key)
|
|
362
|
+
? `${current.path}.${key}`
|
|
363
|
+
: `${current.path}[${JSON.stringify(key)}]`;
|
|
364
|
+
if (child === undefined)
|
|
365
|
+
found.push({
|
|
366
|
+
domain: path.startsWith("$.definition")
|
|
367
|
+
? "definition"
|
|
368
|
+
: path.startsWith("$.losses") ||
|
|
369
|
+
path.startsWith("$.exposures") ||
|
|
370
|
+
path.startsWith("$.reviewEvidence")
|
|
371
|
+
? "input"
|
|
372
|
+
: "configuration",
|
|
373
|
+
code: "invalid-type",
|
|
374
|
+
path,
|
|
375
|
+
message: "Explicit undefined is not allowed",
|
|
376
|
+
});
|
|
377
|
+
else stack.push({ value: child, path });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return found;
|
|
381
|
+
}
|
|
382
|
+
|
|
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 {
|
|
386
|
+
const undefinedIssues = explicitUndefinedIssues(value);
|
|
387
|
+
if (undefinedIssues.length > 0) throw new DiagnosticValidationError(undefinedIssues);
|
|
388
|
+
const parsed = runSchema.safeParse(value);
|
|
389
|
+
if (!parsed.success) throw issues(parsed.error);
|
|
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
|
+
],
|
|
402
|
+
);
|
|
403
|
+
for (const [index, row] of (parsed.data.exposures ?? []).entries()) {
|
|
404
|
+
const measure = definition.definition.measures.find((item) => item.id === row.measureId);
|
|
405
|
+
if (measure?.exposureTiming === "valuation-specific" && row.valuation === undefined)
|
|
406
|
+
relationIssues.push({
|
|
407
|
+
domain: "input",
|
|
408
|
+
code: "missing-required",
|
|
409
|
+
path: `$.exposures[${index}].valuation`,
|
|
410
|
+
message: "Valuation-specific exposure requires valuation",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
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"];
|
|
416
|
+
const rationale = parsed.data.policy?.rationaleRef ?? null;
|
|
417
|
+
if ((review.includes("fail") || metric.includes("fail")) && rationale === null)
|
|
418
|
+
throw new DiagnosticValidationError([
|
|
419
|
+
{
|
|
420
|
+
domain: "configuration",
|
|
421
|
+
code: "missing-required",
|
|
422
|
+
path: "$.policy.rationaleRef",
|
|
423
|
+
message: "A rationale is required when fail outcomes are allowed",
|
|
424
|
+
},
|
|
425
|
+
]);
|
|
426
|
+
const reviewEvidence =
|
|
427
|
+
parsed.data.reviewEvidence === undefined || parsed.data.reviewEvidence === null
|
|
428
|
+
? null
|
|
429
|
+
: validateDiagnosticReviewEvidence(parsed.data.reviewEvidence, "$.reviewEvidence");
|
|
430
|
+
const reviewOrder: readonly DiagnosticAllowedReviewStatus[] = [
|
|
431
|
+
"pass",
|
|
432
|
+
"warning",
|
|
433
|
+
"not-evaluated",
|
|
434
|
+
"fail",
|
|
435
|
+
];
|
|
436
|
+
const metricOrder: readonly ("info" | "warning" | "fail")[] = ["info", "warning", "fail"];
|
|
437
|
+
for (const key of [
|
|
438
|
+
...Object.keys(parsed.data.groupMap ?? {}),
|
|
439
|
+
...Object.keys(parsed.data.groupDimensions ?? {}),
|
|
440
|
+
])
|
|
441
|
+
if (!isDiagnosticToken(key))
|
|
442
|
+
throw new DiagnosticValidationError([
|
|
443
|
+
{
|
|
444
|
+
domain: "configuration",
|
|
445
|
+
code: "invalid-string",
|
|
446
|
+
path: `$.groupMap[${JSON.stringify(key)}]`,
|
|
447
|
+
message: "Group key must be a nonempty token with valid Unicode and no U+0000",
|
|
448
|
+
},
|
|
449
|
+
]);
|
|
450
|
+
const result = freeze({
|
|
451
|
+
definition,
|
|
452
|
+
losses: parsed.data.losses,
|
|
453
|
+
exposures: parsed.data.exposures ?? [],
|
|
454
|
+
filter: parsed.data.filter ?? null,
|
|
455
|
+
completePeriodCutoffs: parsed.data.completePeriodCutoffs ?? [],
|
|
456
|
+
expectedCells: parsed.data.expectedCells ?? null,
|
|
457
|
+
reviewEvidence,
|
|
458
|
+
runPresetId: parsed.data.runPresetId ?? null,
|
|
459
|
+
datasetArtifactId: parsed.data.datasetArtifactId ?? null,
|
|
460
|
+
groupMap: sortedRecord<string>(parsed.data.groupMap ?? diagnosticRecord<string>()),
|
|
461
|
+
groupDimensions: sortedRecord<JsonValue>(
|
|
462
|
+
parsed.data.groupDimensions ?? diagnosticRecord<JsonValue>(),
|
|
463
|
+
),
|
|
464
|
+
policy: {
|
|
465
|
+
allowedReviewStatuses: reviewOrder.filter((status) => review.includes(status)),
|
|
466
|
+
allowedMetricFindingSeverities: metricOrder.filter((severity) => metric.includes(severity)),
|
|
467
|
+
rationaleRef: rationale,
|
|
468
|
+
},
|
|
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));
|
|
487
|
+
validateDiagnosticGroupingConfiguration({
|
|
488
|
+
prepared,
|
|
489
|
+
groupMap: result.groupMap,
|
|
490
|
+
groupDimensions: result.groupDimensions,
|
|
491
|
+
});
|
|
492
|
+
authentic.add(result);
|
|
493
|
+
preparedByInput.set(result, prepared);
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
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
|
+
|
|
530
|
+
export function assertValidatedDiagnosticRunInput(
|
|
531
|
+
value: unknown,
|
|
532
|
+
): asserts value is ValidatedDiagnosticRunInput {
|
|
533
|
+
if (value === null || typeof value !== "object" || !authentic.has(value))
|
|
534
|
+
throw new DiagnosticValidationError([
|
|
535
|
+
{
|
|
536
|
+
domain: "input",
|
|
537
|
+
code: "invalid-input-relationship",
|
|
538
|
+
path: "$",
|
|
539
|
+
message: "Value is not an authentic validated diagnostic run input",
|
|
540
|
+
},
|
|
541
|
+
]);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const completed = new WeakSet<object>();
|
|
545
|
+
export function runValidatedMetricDiagnostics(
|
|
546
|
+
input: ValidatedDiagnosticRunInput,
|
|
547
|
+
): ValidatedMetricDiagnosticsOutcome {
|
|
548
|
+
assertValidatedDiagnosticRunInput(input);
|
|
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)!;
|
|
552
|
+
validateDiagnosticGroupingConfiguration({
|
|
553
|
+
prepared,
|
|
554
|
+
groupMap: input.groupMap,
|
|
555
|
+
groupDimensions: input.groupDimensions,
|
|
556
|
+
});
|
|
557
|
+
const review = reviewPreparedDiagnosticData({
|
|
558
|
+
prepared,
|
|
559
|
+
evidence: input.reviewEvidence as DiagnosticReviewEvidence | null,
|
|
560
|
+
});
|
|
561
|
+
const evaluationStatus = (
|
|
562
|
+
evaluation: DiagnosticReviewReceipt["evaluations"][number],
|
|
563
|
+
): DiagnosticAllowedReviewStatus =>
|
|
564
|
+
evaluation.expressionOverflows.length > 0
|
|
565
|
+
? "fail"
|
|
566
|
+
: evaluation.status === "not-evaluated"
|
|
567
|
+
? "not-evaluated"
|
|
568
|
+
: evaluation.status === "triggered"
|
|
569
|
+
? evaluation.severity
|
|
570
|
+
: "pass";
|
|
571
|
+
const disallowedReview =
|
|
572
|
+
review.report.checks.some(
|
|
573
|
+
(check) => !input.policy.allowedReviewStatuses.includes(check.status),
|
|
574
|
+
) ||
|
|
575
|
+
review.evaluations.some(
|
|
576
|
+
(evaluation) => !input.policy.allowedReviewStatuses.includes(evaluationStatus(evaluation)),
|
|
577
|
+
);
|
|
578
|
+
const base = {
|
|
579
|
+
prepared,
|
|
580
|
+
review,
|
|
581
|
+
runPresetId: input.runPresetId,
|
|
582
|
+
datasetArtifactId: input.datasetArtifactId,
|
|
583
|
+
groupMap: input.groupMap,
|
|
584
|
+
groupDimensions: input.groupDimensions,
|
|
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);
|
|
591
|
+
if (disallowedReview)
|
|
592
|
+
return freezeOutcome({
|
|
593
|
+
...base,
|
|
594
|
+
status: "blocked" as const,
|
|
595
|
+
stage: "review" as const,
|
|
596
|
+
result: null,
|
|
597
|
+
gate: {
|
|
598
|
+
...input.policy,
|
|
599
|
+
reviewGate: "blocked" as const,
|
|
600
|
+
metricGate: "not-run" as const,
|
|
601
|
+
},
|
|
602
|
+
}) as ValidatedMetricDiagnosticsOutcome;
|
|
603
|
+
const result = runMetricDiagnostics({
|
|
604
|
+
prepared,
|
|
605
|
+
groupMap: input.groupMap,
|
|
606
|
+
groupDimensions: input.groupDimensions,
|
|
607
|
+
});
|
|
608
|
+
frozenSdkGraphs.add(result);
|
|
609
|
+
const disallowedMetric = result.findings.some(
|
|
610
|
+
(finding) =>
|
|
611
|
+
finding.category !== "structural" &&
|
|
612
|
+
!input.policy.allowedMetricFindingSeverities.includes(finding.severity),
|
|
613
|
+
);
|
|
614
|
+
if (disallowedMetric)
|
|
615
|
+
return freezeOutcome({
|
|
616
|
+
...base,
|
|
617
|
+
status: "blocked" as const,
|
|
618
|
+
stage: "metric" as const,
|
|
619
|
+
result,
|
|
620
|
+
gate: {
|
|
621
|
+
...input.policy,
|
|
622
|
+
reviewGate: "passed" as const,
|
|
623
|
+
metricGate: "blocked" as const,
|
|
624
|
+
},
|
|
625
|
+
}) as ValidatedMetricDiagnosticsOutcome;
|
|
626
|
+
const outcome = freezeOutcome({
|
|
627
|
+
...base,
|
|
628
|
+
status: "completed" as const,
|
|
629
|
+
result,
|
|
630
|
+
gate: {
|
|
631
|
+
...input.policy,
|
|
632
|
+
reviewGate: "passed" as const,
|
|
633
|
+
metricGate: "passed" as const,
|
|
634
|
+
},
|
|
635
|
+
}) as unknown as CompletedValidatedMetricDiagnosticsRun;
|
|
636
|
+
completed.add(outcome);
|
|
637
|
+
return outcome;
|
|
638
|
+
}
|
|
639
|
+
export function assertCompletedValidatedMetricDiagnosticsRun(
|
|
640
|
+
value: unknown,
|
|
641
|
+
): asserts value is CompletedValidatedMetricDiagnosticsRun {
|
|
642
|
+
if (value === null || typeof value !== "object" || !completed.has(value))
|
|
643
|
+
throw new DiagnosticValidationError([
|
|
644
|
+
{
|
|
645
|
+
domain: "input",
|
|
646
|
+
code: "invalid-input-relationship",
|
|
647
|
+
path: "$",
|
|
648
|
+
message: "Value is not an authentic completed diagnostic run",
|
|
649
|
+
},
|
|
650
|
+
]);
|
|
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
|
+
}
|