@actuarial-ts/agents 0.5.0 → 0.6.1
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 +32 -128
- package/dist/diagnostics.d.ts +69 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +655 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/divergence.d.ts +4 -3
- package/dist/divergence.d.ts.map +1 -1
- package/dist/divergence.js +30 -10
- package/dist/divergence.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/judgment.d.ts +75 -2
- package/dist/judgment.d.ts.map +1 -1
- package/dist/judgment.js +28 -10
- package/dist/judgment.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/promotion.d.ts.map +1 -1
- package/dist/promotion.js +2 -0
- package/dist/promotion.js.map +1 -1
- package/dist/remote.d.ts +7 -9
- package/dist/remote.d.ts.map +1 -1
- package/dist/tools.d.ts +26 -12
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +132 -23
- package/dist/tools.js.map +1 -1
- package/package.json +12 -11
- package/src/diagnostics.ts +833 -0
- package/src/divergence.ts +73 -29
- package/src/errors.ts +7 -0
- package/src/index.ts +1 -0
- package/src/judgment.ts +56 -20
- package/src/mcp.ts +2 -2
- package/src/promotion.ts +2 -0
- package/src/tools.ts +351 -54
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertCompiledDiagnosticDefinition,
|
|
3
|
+
isDiagnosticToken,
|
|
4
|
+
isDiagnosticPlainRecord,
|
|
5
|
+
type CompiledDiagnosticDefinition,
|
|
6
|
+
type DiagnosticDeepReadonly,
|
|
7
|
+
} from "@actuarial-ts/core";
|
|
8
|
+
import {
|
|
9
|
+
assertVerifiedDiagnosticRunProvenance,
|
|
10
|
+
type VerifiedDiagnosticRunProvenance,
|
|
11
|
+
} from "@actuarial-ts/compliance";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { AgentsError } from "./errors.js";
|
|
14
|
+
import {
|
|
15
|
+
defineActuarialTool,
|
|
16
|
+
type DefinedActuarialTool,
|
|
17
|
+
type ToolEnvelopeFailure,
|
|
18
|
+
} from "./tools.js";
|
|
19
|
+
|
|
20
|
+
const tokenSchema = z.string().min(1).refine(isDiagnosticToken);
|
|
21
|
+
const selectedInstanceIdsSchema: z.ZodType<readonly string[]> = z
|
|
22
|
+
.array(tokenSchema)
|
|
23
|
+
.min(1);
|
|
24
|
+
export const diagnosticAgentToolInputSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
runPresetId: tokenSchema,
|
|
27
|
+
instanceIds: selectedInstanceIdsSchema,
|
|
28
|
+
view: z.enum(["emergence", "triangles", "latest-diagonal"]),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
export type DiagnosticAgentView = "emergence" | "triangles" | "latest-diagonal";
|
|
32
|
+
|
|
33
|
+
export interface DiagnosticAgentToolInput {
|
|
34
|
+
readonly runPresetId: string;
|
|
35
|
+
readonly instanceIds: readonly string[];
|
|
36
|
+
readonly view: DiagnosticAgentView;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DiagnosticAgentPresetExecutionInput {
|
|
40
|
+
readonly tenantId: string;
|
|
41
|
+
readonly instanceIds: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DiagnosticAgentRunPreset {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly definitionIntegrity: string;
|
|
47
|
+
readonly allowedInstanceIds: readonly string[];
|
|
48
|
+
readonly execute: (
|
|
49
|
+
input: DiagnosticAgentPresetExecutionInput,
|
|
50
|
+
) => Promise<VerifiedDiagnosticRunProvenance>;
|
|
51
|
+
}
|
|
52
|
+
export interface CreateDiagnosticSelectionToolInput {
|
|
53
|
+
readonly definition: CompiledDiagnosticDefinition;
|
|
54
|
+
readonly runPresets: readonly DiagnosticAgentRunPreset[];
|
|
55
|
+
readonly id?: string;
|
|
56
|
+
readonly description?: string;
|
|
57
|
+
readonly tenantContextKey?: string;
|
|
58
|
+
}
|
|
59
|
+
export type DiagnosticAgentDisplayProjection =
|
|
60
|
+
| {
|
|
61
|
+
readonly view: "emergence" | "latest-diagonal";
|
|
62
|
+
readonly points: readonly DiagnosticAgentDisplayPoint[];
|
|
63
|
+
}
|
|
64
|
+
| {
|
|
65
|
+
readonly view: "triangles";
|
|
66
|
+
readonly triangles: VerifiedDiagnosticRunProvenance["result"]["triangles"];
|
|
67
|
+
};
|
|
68
|
+
export type DiagnosticAgentDisplayPoint = Omit<
|
|
69
|
+
VerifiedDiagnosticRunProvenance["result"]["emergence"][number],
|
|
70
|
+
"components"
|
|
71
|
+
>;
|
|
72
|
+
export interface DiagnosticAgentToolSuccess {
|
|
73
|
+
readonly success: true;
|
|
74
|
+
readonly data: {
|
|
75
|
+
readonly runPresetId: string;
|
|
76
|
+
readonly instanceIds: readonly string[];
|
|
77
|
+
readonly definitionIntegrity: string;
|
|
78
|
+
readonly formulaFingerprints: Readonly<Record<string, string>>;
|
|
79
|
+
readonly calculationFingerprints: Readonly<Record<string, string>>;
|
|
80
|
+
readonly runFingerprint: string;
|
|
81
|
+
readonly resultFingerprint: string;
|
|
82
|
+
readonly runResultFingerprint: string;
|
|
83
|
+
readonly review: VerifiedDiagnosticRunProvenance["review"];
|
|
84
|
+
readonly display: DiagnosticAgentDisplayProjection;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export type DiagnosticAgentToolResult =
|
|
88
|
+
DiagnosticDeepReadonly<DiagnosticAgentToolSuccess> | ToolEnvelopeFailure;
|
|
89
|
+
|
|
90
|
+
const toolFailureSchema = z
|
|
91
|
+
.object({
|
|
92
|
+
success: z.literal(false),
|
|
93
|
+
error: z.object({ code: z.string(), message: z.string() }).strict(),
|
|
94
|
+
})
|
|
95
|
+
.strict();
|
|
96
|
+
const tagSchema = z.string().regex(/^fnv1a64-jcs-v1:[0-9a-f]{16}$/);
|
|
97
|
+
const finiteNullable = z.number().finite().nullable();
|
|
98
|
+
/** Zod 3 drops an own `__proto__` key when assembling records. Encode every
|
|
99
|
+
* key during validation and decode only after its value has been validated. */
|
|
100
|
+
function recordSchema<T extends z.ZodTypeAny>(valueSchema: T) {
|
|
101
|
+
return z
|
|
102
|
+
.record(
|
|
103
|
+
z.string().transform((key) => `:${key}`),
|
|
104
|
+
valueSchema,
|
|
105
|
+
)
|
|
106
|
+
.transform((record): Record<string, z.output<T>> =>
|
|
107
|
+
Object.fromEntries(
|
|
108
|
+
Object.entries(record).map(([key, value]) => [key.slice(1), value]),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const jsonSchema: z.ZodType<import("@actuarial-ts/core").JsonValue> = z.lazy(
|
|
113
|
+
() =>
|
|
114
|
+
z.union([
|
|
115
|
+
z.string(),
|
|
116
|
+
z.number().finite(),
|
|
117
|
+
z.boolean(),
|
|
118
|
+
z.null(),
|
|
119
|
+
z.array(jsonSchema),
|
|
120
|
+
recordSchema(jsonSchema),
|
|
121
|
+
]),
|
|
122
|
+
);
|
|
123
|
+
const sourceSchema = z
|
|
124
|
+
.object({
|
|
125
|
+
artifactId: tokenSchema,
|
|
126
|
+
sourceFile: tokenSchema.optional(),
|
|
127
|
+
sourceSheet: tokenSchema.optional(),
|
|
128
|
+
sourceRow: z.number().int().nonnegative().optional(),
|
|
129
|
+
sourceCell: tokenSchema.optional(),
|
|
130
|
+
})
|
|
131
|
+
.strict();
|
|
132
|
+
const quantitySchema = z
|
|
133
|
+
.object({
|
|
134
|
+
kind: z.enum(["amount", "count", "exposure"]),
|
|
135
|
+
unit: tokenSchema,
|
|
136
|
+
basisId: tokenSchema.optional(),
|
|
137
|
+
countPopulationId: tokenSchema.optional(),
|
|
138
|
+
exposureBasisId: tokenSchema.optional(),
|
|
139
|
+
value: finiteNullable,
|
|
140
|
+
})
|
|
141
|
+
.strict();
|
|
142
|
+
const statsSchema = z
|
|
143
|
+
.object({
|
|
144
|
+
sum: finiteNullable,
|
|
145
|
+
value: finiteNullable,
|
|
146
|
+
observed: z.number().int().nonnegative(),
|
|
147
|
+
missing: z.number().int().nonnegative(),
|
|
148
|
+
imputedZero: z.number().int().nonnegative(),
|
|
149
|
+
nonFinite: z.number().int().nonnegative(),
|
|
150
|
+
structural: z.number().int().nonnegative(),
|
|
151
|
+
deduplicated: z.number().int().nonnegative(),
|
|
152
|
+
})
|
|
153
|
+
.strict();
|
|
154
|
+
const findingSchema = z
|
|
155
|
+
.object({
|
|
156
|
+
code: tokenSchema,
|
|
157
|
+
message: z.string().min(1),
|
|
158
|
+
severity: z.enum(["info", "warning", "fail"]),
|
|
159
|
+
category: z.enum([
|
|
160
|
+
"structural",
|
|
161
|
+
"aggregation",
|
|
162
|
+
"calculation",
|
|
163
|
+
"rule",
|
|
164
|
+
"presentation",
|
|
165
|
+
]),
|
|
166
|
+
ruleId: tokenSchema.optional(),
|
|
167
|
+
measureId: tokenSchema.optional(),
|
|
168
|
+
instanceId: tokenSchema.optional(),
|
|
169
|
+
expressionPath: z.string().optional(),
|
|
170
|
+
offendingKey: z.string().optional(),
|
|
171
|
+
sourceGroup: tokenSchema.optional(),
|
|
172
|
+
group: tokenSchema.optional(),
|
|
173
|
+
origin: tokenSchema.optional(),
|
|
174
|
+
valuation: tokenSchema.optional(),
|
|
175
|
+
developmentAge: z.number().int().nonnegative().optional(),
|
|
176
|
+
ageUnit: tokenSchema.optional(),
|
|
177
|
+
recordId: tokenSchema.optional(),
|
|
178
|
+
claimId: tokenSchema.optional(),
|
|
179
|
+
exposureKey: tokenSchema.optional(),
|
|
180
|
+
sources: z.array(sourceSchema),
|
|
181
|
+
})
|
|
182
|
+
.strict();
|
|
183
|
+
const overflowSchema = z
|
|
184
|
+
.object({ expressionPath: z.string(), sources: z.array(sourceSchema) })
|
|
185
|
+
.strict();
|
|
186
|
+
const metricRuleSchema = z
|
|
187
|
+
.object({
|
|
188
|
+
ruleId: tokenSchema,
|
|
189
|
+
status: z.enum(["pass", "triggered", "not-evaluated"]),
|
|
190
|
+
severity: z.enum(["warning", "fail"]),
|
|
191
|
+
left: finiteNullable,
|
|
192
|
+
right: finiteNullable,
|
|
193
|
+
relation: z.enum(["less", "equal", "greater"]).nullable(),
|
|
194
|
+
notEvaluatedReasons: z.array(
|
|
195
|
+
z.enum([
|
|
196
|
+
"missing",
|
|
197
|
+
"imputed",
|
|
198
|
+
"non-finite",
|
|
199
|
+
"structural-ambiguity",
|
|
200
|
+
"aggregation-overflow",
|
|
201
|
+
"expression-overflow",
|
|
202
|
+
"tolerance-overflow",
|
|
203
|
+
]),
|
|
204
|
+
),
|
|
205
|
+
expressionOverflows: z.array(overflowSchema),
|
|
206
|
+
code: z.string().nullable(),
|
|
207
|
+
message: z.string().nullable(),
|
|
208
|
+
})
|
|
209
|
+
.strict();
|
|
210
|
+
const presentationSchema = z
|
|
211
|
+
.object({
|
|
212
|
+
displayName: z.string().min(1),
|
|
213
|
+
description: z.string().min(1),
|
|
214
|
+
displayUnit: tokenSchema,
|
|
215
|
+
scale: z.number().finite().positive(),
|
|
216
|
+
numeratorLabel: z.string().min(1),
|
|
217
|
+
denominatorLabel: z.string().min(1),
|
|
218
|
+
value: finiteNullable,
|
|
219
|
+
})
|
|
220
|
+
.strict();
|
|
221
|
+
const evaluationSchema = z
|
|
222
|
+
.object({
|
|
223
|
+
instanceId: tokenSchema,
|
|
224
|
+
instanceVersion: tokenSchema,
|
|
225
|
+
formulaId: tokenSchema,
|
|
226
|
+
formulaVersion: tokenSchema,
|
|
227
|
+
semanticReferences: z
|
|
228
|
+
.object({
|
|
229
|
+
amountBasisIds: z.array(tokenSchema),
|
|
230
|
+
countPopulationIds: z.array(tokenSchema),
|
|
231
|
+
exposureBasisIds: z.array(tokenSchema),
|
|
232
|
+
})
|
|
233
|
+
.strict(),
|
|
234
|
+
formulaFingerprint: tagSchema,
|
|
235
|
+
calculationFingerprint: tagSchema,
|
|
236
|
+
definitionIntegrity: tagSchema,
|
|
237
|
+
calculation: z
|
|
238
|
+
.object({
|
|
239
|
+
numerator: quantitySchema,
|
|
240
|
+
denominator: quantitySchema,
|
|
241
|
+
value: finiteNullable,
|
|
242
|
+
})
|
|
243
|
+
.strict(),
|
|
244
|
+
presentation: presentationSchema,
|
|
245
|
+
components: recordSchema(statsSchema),
|
|
246
|
+
rules: z.array(metricRuleSchema),
|
|
247
|
+
findings: z.array(findingSchema),
|
|
248
|
+
})
|
|
249
|
+
.strict();
|
|
250
|
+
const pointSchema = z
|
|
251
|
+
.object({
|
|
252
|
+
group: tokenSchema,
|
|
253
|
+
sourceGroups: z.array(tokenSchema),
|
|
254
|
+
dimensions: jsonSchema.optional(),
|
|
255
|
+
origin: tokenSchema,
|
|
256
|
+
valuation: tokenSchema,
|
|
257
|
+
developmentAge: z.number().int().nonnegative(),
|
|
258
|
+
ageUnit: tokenSchema,
|
|
259
|
+
components: recordSchema(statsSchema),
|
|
260
|
+
metrics: recordSchema(evaluationSchema),
|
|
261
|
+
findings: z.array(findingSchema),
|
|
262
|
+
})
|
|
263
|
+
.strict();
|
|
264
|
+
const triangleCellSchema = z
|
|
265
|
+
.object({
|
|
266
|
+
origin: tokenSchema,
|
|
267
|
+
valuation: tokenSchema,
|
|
268
|
+
developmentAge: z.number().int().nonnegative(),
|
|
269
|
+
ageUnit: tokenSchema,
|
|
270
|
+
evaluation: evaluationSchema,
|
|
271
|
+
})
|
|
272
|
+
.strict();
|
|
273
|
+
const triangleSchema = z
|
|
274
|
+
.object({
|
|
275
|
+
group: tokenSchema,
|
|
276
|
+
instanceId: tokenSchema,
|
|
277
|
+
origins: z.array(tokenSchema),
|
|
278
|
+
developmentAges: z.array(z.number().int().nonnegative()),
|
|
279
|
+
ageUnit: tokenSchema,
|
|
280
|
+
calculationValues: z.array(z.array(finiteNullable)),
|
|
281
|
+
presentationValues: z.array(z.array(finiteNullable)),
|
|
282
|
+
cells: z.array(z.array(triangleCellSchema.nullable())),
|
|
283
|
+
})
|
|
284
|
+
.strict();
|
|
285
|
+
const reviewCoordinateSchema = z
|
|
286
|
+
.object({
|
|
287
|
+
sourceGroup: tokenSchema,
|
|
288
|
+
origin: tokenSchema,
|
|
289
|
+
valuation: tokenSchema,
|
|
290
|
+
developmentAge: z.number().int().nonnegative().safe(),
|
|
291
|
+
ageUnit: tokenSchema,
|
|
292
|
+
})
|
|
293
|
+
.strict();
|
|
294
|
+
const cellReviewScopeSchema = z
|
|
295
|
+
.object({
|
|
296
|
+
kind: z.literal("cell"),
|
|
297
|
+
cell: reviewCoordinateSchema,
|
|
298
|
+
sources: z.array(sourceSchema),
|
|
299
|
+
})
|
|
300
|
+
.strict();
|
|
301
|
+
const pairReviewScopeSchema = z
|
|
302
|
+
.object({
|
|
303
|
+
kind: z.literal("valuation-pair"),
|
|
304
|
+
previous: reviewCoordinateSchema,
|
|
305
|
+
current: reviewCoordinateSchema,
|
|
306
|
+
sources: z.array(sourceSchema),
|
|
307
|
+
})
|
|
308
|
+
.strict();
|
|
309
|
+
const controlReviewScopeSchema = z
|
|
310
|
+
.object({
|
|
311
|
+
kind: z.literal("control-total"),
|
|
312
|
+
projection: z.discriminatedUnion("kind", [
|
|
313
|
+
z
|
|
314
|
+
.object({ kind: z.literal("valuation"), valuation: tokenSchema })
|
|
315
|
+
.strict(),
|
|
316
|
+
z.object({ kind: z.literal("latest-valuation-per-origin") }).strict(),
|
|
317
|
+
z.object({ kind: z.literal("all-cells") }).strict(),
|
|
318
|
+
]),
|
|
319
|
+
filter: z
|
|
320
|
+
.object({
|
|
321
|
+
sourceGroups: z.array(tokenSchema).nullable(),
|
|
322
|
+
origins: z.array(tokenSchema).nullable(),
|
|
323
|
+
originFrom: tokenSchema.nullable(),
|
|
324
|
+
originThrough: tokenSchema.nullable(),
|
|
325
|
+
valuations: z.array(tokenSchema).nullable(),
|
|
326
|
+
valuationFrom: tokenSchema.nullable(),
|
|
327
|
+
valuationThrough: tokenSchema.nullable(),
|
|
328
|
+
minDevelopmentAge: z.number().int().nonnegative().safe().nullable(),
|
|
329
|
+
maxDevelopmentAge: z.number().int().nonnegative().safe().nullable(),
|
|
330
|
+
})
|
|
331
|
+
.strict()
|
|
332
|
+
.nullable(),
|
|
333
|
+
selectedCellCount: z.number().int().nonnegative().safe(),
|
|
334
|
+
selectedContributionCount: z.number().int().nonnegative().safe(),
|
|
335
|
+
sources: z.array(sourceSchema),
|
|
336
|
+
})
|
|
337
|
+
.strict();
|
|
338
|
+
const reviewScopeSchema = z.discriminatedUnion("kind", [
|
|
339
|
+
cellReviewScopeSchema,
|
|
340
|
+
pairReviewScopeSchema,
|
|
341
|
+
controlReviewScopeSchema,
|
|
342
|
+
]);
|
|
343
|
+
const dataFindingContextSchema = z
|
|
344
|
+
.object({
|
|
345
|
+
ruleId: tokenSchema.optional(),
|
|
346
|
+
measureId: tokenSchema.optional(),
|
|
347
|
+
expressionPath: z.string().optional(),
|
|
348
|
+
offendingKey: z.string().optional(),
|
|
349
|
+
groupingKey: tokenSchema.optional(),
|
|
350
|
+
cachedEvidenceId: tokenSchema.optional(),
|
|
351
|
+
sourceGroup: tokenSchema.optional(),
|
|
352
|
+
origin: tokenSchema.optional(),
|
|
353
|
+
valuation: tokenSchema.optional(),
|
|
354
|
+
developmentAge: z.number().int().nonnegative().safe().optional(),
|
|
355
|
+
ageUnit: tokenSchema.optional(),
|
|
356
|
+
recordId: tokenSchema.optional(),
|
|
357
|
+
claimId: tokenSchema.optional(),
|
|
358
|
+
exposureKey: tokenSchema.optional(),
|
|
359
|
+
group: tokenSchema.optional(),
|
|
360
|
+
sourceFile: tokenSchema.optional(),
|
|
361
|
+
sourceRow: z.number().int().nonnegative().safe().optional(),
|
|
362
|
+
sources: z.array(sourceSchema).optional(),
|
|
363
|
+
reviewScope: reviewScopeSchema.optional(),
|
|
364
|
+
})
|
|
365
|
+
.strict();
|
|
366
|
+
const dataFindingSchema = z
|
|
367
|
+
.object({
|
|
368
|
+
code: tokenSchema,
|
|
369
|
+
message: z.string(),
|
|
370
|
+
context: dataFindingContextSchema.optional(),
|
|
371
|
+
})
|
|
372
|
+
.strict();
|
|
373
|
+
const reviewEvaluationBaseSchema = z
|
|
374
|
+
.object({
|
|
375
|
+
ruleId: tokenSchema,
|
|
376
|
+
status: z.enum(["pass", "triggered", "not-evaluated"]),
|
|
377
|
+
severity: z.enum(["warning", "fail"]),
|
|
378
|
+
triggerReason: z
|
|
379
|
+
.enum([
|
|
380
|
+
"predicate",
|
|
381
|
+
"missing-input",
|
|
382
|
+
"aggregation-overflow",
|
|
383
|
+
"expression-overflow",
|
|
384
|
+
"tolerance-overflow",
|
|
385
|
+
])
|
|
386
|
+
.nullable(),
|
|
387
|
+
left: finiteNullable,
|
|
388
|
+
right: finiteNullable,
|
|
389
|
+
relation: z.enum(["less", "equal", "greater"]).nullable(),
|
|
390
|
+
notEvaluatedReasons: metricRuleSchema.shape.notEvaluatedReasons,
|
|
391
|
+
expressionOverflows: z.array(
|
|
392
|
+
overflowSchema.extend({ coordinate: reviewCoordinateSchema.nullable() }),
|
|
393
|
+
),
|
|
394
|
+
})
|
|
395
|
+
.strict();
|
|
396
|
+
const comparabilitySchema = z.discriminatedUnion("kind", [
|
|
397
|
+
z.object({ kind: z.literal("compiler-proven") }).strict(),
|
|
398
|
+
z
|
|
399
|
+
.object({
|
|
400
|
+
kind: z.literal("caller-asserted"),
|
|
401
|
+
rationaleArtifactId: tokenSchema,
|
|
402
|
+
})
|
|
403
|
+
.strict(),
|
|
404
|
+
]);
|
|
405
|
+
const reviewEvaluationSchema = z.discriminatedUnion("ruleKind", [
|
|
406
|
+
reviewEvaluationBaseSchema.extend({
|
|
407
|
+
ruleKind: z.literal("compare"),
|
|
408
|
+
scope: cellReviewScopeSchema,
|
|
409
|
+
}),
|
|
410
|
+
reviewEvaluationBaseSchema.extend({
|
|
411
|
+
ruleKind: z.literal("reconcile"),
|
|
412
|
+
scope: cellReviewScopeSchema,
|
|
413
|
+
}),
|
|
414
|
+
reviewEvaluationBaseSchema.extend({
|
|
415
|
+
ruleKind: z.literal("monotonic"),
|
|
416
|
+
scope: pairReviewScopeSchema,
|
|
417
|
+
}),
|
|
418
|
+
reviewEvaluationBaseSchema.extend({
|
|
419
|
+
ruleKind: z.literal("control-total"),
|
|
420
|
+
scope: controlReviewScopeSchema,
|
|
421
|
+
}),
|
|
422
|
+
reviewEvaluationBaseSchema.extend({
|
|
423
|
+
ruleKind: z.literal("layer-order"),
|
|
424
|
+
scope: cellReviewScopeSchema,
|
|
425
|
+
comparability: comparabilitySchema,
|
|
426
|
+
}),
|
|
427
|
+
]);
|
|
428
|
+
const reviewSchemaBase = z
|
|
429
|
+
.object({
|
|
430
|
+
definitionIntegrity: tagSchema,
|
|
431
|
+
preparationFingerprint: tagSchema,
|
|
432
|
+
report: z
|
|
433
|
+
.object({
|
|
434
|
+
checks: z.array(
|
|
435
|
+
z
|
|
436
|
+
.object({
|
|
437
|
+
id: tokenSchema,
|
|
438
|
+
description: z.string(),
|
|
439
|
+
status: z.enum(["pass", "warning", "fail", "not-evaluated"]),
|
|
440
|
+
details: z.array(z.string()),
|
|
441
|
+
findings: z.array(dataFindingSchema),
|
|
442
|
+
})
|
|
443
|
+
.strict(),
|
|
444
|
+
),
|
|
445
|
+
summary: z
|
|
446
|
+
.object({
|
|
447
|
+
pass: z.number().int().nonnegative(),
|
|
448
|
+
warning: z.number().int().nonnegative(),
|
|
449
|
+
fail: z.number().int().nonnegative(),
|
|
450
|
+
notEvaluated: z.number().int().nonnegative(),
|
|
451
|
+
})
|
|
452
|
+
.strict(),
|
|
453
|
+
})
|
|
454
|
+
.strict(),
|
|
455
|
+
evaluations: z.array(reviewEvaluationSchema),
|
|
456
|
+
evidence: z
|
|
457
|
+
.object({
|
|
458
|
+
groupingAssignments: z.array(
|
|
459
|
+
z
|
|
460
|
+
.object({
|
|
461
|
+
key: tokenSchema,
|
|
462
|
+
group: tokenSchema,
|
|
463
|
+
source: sourceSchema.optional(),
|
|
464
|
+
})
|
|
465
|
+
.strict(),
|
|
466
|
+
),
|
|
467
|
+
cachedFormulas: z.array(
|
|
468
|
+
z
|
|
469
|
+
.object({
|
|
470
|
+
id: tokenSchema,
|
|
471
|
+
source: sourceSchema.optional(),
|
|
472
|
+
formula: z.string().optional(),
|
|
473
|
+
cachedValue: finiteNullable.optional(),
|
|
474
|
+
declaredFormulaSource: z.boolean(),
|
|
475
|
+
})
|
|
476
|
+
.strict(),
|
|
477
|
+
),
|
|
478
|
+
})
|
|
479
|
+
.strict()
|
|
480
|
+
.nullable(),
|
|
481
|
+
reportFingerprint: tagSchema,
|
|
482
|
+
})
|
|
483
|
+
.strict();
|
|
484
|
+
const normalizedSourceSchema = sourceSchema.extend({
|
|
485
|
+
sourceFile: tokenSchema.nullable(),
|
|
486
|
+
sourceSheet: tokenSchema.nullable(),
|
|
487
|
+
sourceRow: z.number().int().nonnegative().safe().nullable(),
|
|
488
|
+
sourceCell: tokenSchema.nullable(),
|
|
489
|
+
});
|
|
490
|
+
const normalizedSourcesSchema = z.array(normalizedSourceSchema);
|
|
491
|
+
const normalizedCellScopeSchema = cellReviewScopeSchema.extend({
|
|
492
|
+
sources: normalizedSourcesSchema,
|
|
493
|
+
});
|
|
494
|
+
const normalizedPairScopeSchema = pairReviewScopeSchema.extend({
|
|
495
|
+
sources: normalizedSourcesSchema,
|
|
496
|
+
});
|
|
497
|
+
const normalizedControlScopeSchema = controlReviewScopeSchema.extend({
|
|
498
|
+
sources: normalizedSourcesSchema,
|
|
499
|
+
});
|
|
500
|
+
const normalizedScopeSchema = z.discriminatedUnion("kind", [
|
|
501
|
+
normalizedCellScopeSchema,
|
|
502
|
+
normalizedPairScopeSchema,
|
|
503
|
+
normalizedControlScopeSchema,
|
|
504
|
+
]);
|
|
505
|
+
const normalizedReviewEvaluationBaseSchema = reviewEvaluationBaseSchema.extend({
|
|
506
|
+
expressionOverflows: z.array(
|
|
507
|
+
overflowSchema.extend({
|
|
508
|
+
sources: normalizedSourcesSchema,
|
|
509
|
+
coordinate: reviewCoordinateSchema.nullable(),
|
|
510
|
+
}),
|
|
511
|
+
),
|
|
512
|
+
});
|
|
513
|
+
const normalizedReviewEvaluationSchema = z.discriminatedUnion("ruleKind", [
|
|
514
|
+
normalizedReviewEvaluationBaseSchema.extend({
|
|
515
|
+
ruleKind: z.literal("compare"),
|
|
516
|
+
scope: normalizedCellScopeSchema,
|
|
517
|
+
}),
|
|
518
|
+
normalizedReviewEvaluationBaseSchema.extend({
|
|
519
|
+
ruleKind: z.literal("reconcile"),
|
|
520
|
+
scope: normalizedCellScopeSchema,
|
|
521
|
+
}),
|
|
522
|
+
normalizedReviewEvaluationBaseSchema.extend({
|
|
523
|
+
ruleKind: z.literal("monotonic"),
|
|
524
|
+
scope: normalizedPairScopeSchema,
|
|
525
|
+
}),
|
|
526
|
+
normalizedReviewEvaluationBaseSchema.extend({
|
|
527
|
+
ruleKind: z.literal("control-total"),
|
|
528
|
+
scope: normalizedControlScopeSchema,
|
|
529
|
+
}),
|
|
530
|
+
normalizedReviewEvaluationBaseSchema.extend({
|
|
531
|
+
ruleKind: z.literal("layer-order"),
|
|
532
|
+
scope: normalizedCellScopeSchema,
|
|
533
|
+
comparability: comparabilitySchema,
|
|
534
|
+
}),
|
|
535
|
+
]);
|
|
536
|
+
const normalizedFindingSchema = dataFindingSchema.extend({
|
|
537
|
+
context: dataFindingContextSchema
|
|
538
|
+
.extend({
|
|
539
|
+
sources: normalizedSourcesSchema.optional(),
|
|
540
|
+
reviewScope: normalizedScopeSchema.optional(),
|
|
541
|
+
})
|
|
542
|
+
.optional(),
|
|
543
|
+
});
|
|
544
|
+
const evidenceShape = reviewSchemaBase.shape.evidence.unwrap().shape;
|
|
545
|
+
const normalizedEvidenceSchema = z
|
|
546
|
+
.object({
|
|
547
|
+
groupingAssignments: z.array(
|
|
548
|
+
evidenceShape.groupingAssignments.element.extend({
|
|
549
|
+
source: normalizedSourceSchema.optional(),
|
|
550
|
+
}),
|
|
551
|
+
),
|
|
552
|
+
cachedFormulas: z.array(
|
|
553
|
+
evidenceShape.cachedFormulas.element.extend({
|
|
554
|
+
source: normalizedSourceSchema.optional(),
|
|
555
|
+
}),
|
|
556
|
+
),
|
|
557
|
+
})
|
|
558
|
+
.strict()
|
|
559
|
+
.nullable();
|
|
560
|
+
const reviewSchema = reviewSchemaBase.extend({
|
|
561
|
+
identityBody: z
|
|
562
|
+
.object({
|
|
563
|
+
definitionIntegrity: tagSchema,
|
|
564
|
+
preparationFingerprint: tagSchema,
|
|
565
|
+
evidence: normalizedEvidenceSchema,
|
|
566
|
+
checks: z.array(
|
|
567
|
+
reviewSchemaBase.shape.report.shape.checks.element
|
|
568
|
+
.omit({ description: true, details: true })
|
|
569
|
+
.extend({ findings: z.array(normalizedFindingSchema) }),
|
|
570
|
+
),
|
|
571
|
+
summary: reviewSchemaBase.shape.report.shape.summary,
|
|
572
|
+
evaluations: z.array(normalizedReviewEvaluationSchema),
|
|
573
|
+
})
|
|
574
|
+
.strict(),
|
|
575
|
+
});
|
|
576
|
+
const displaySchema = z.discriminatedUnion("view", [
|
|
577
|
+
z
|
|
578
|
+
.object({
|
|
579
|
+
view: z.literal("emergence"),
|
|
580
|
+
points: z.array(pointSchema.omit({ components: true })),
|
|
581
|
+
})
|
|
582
|
+
.strict(),
|
|
583
|
+
z
|
|
584
|
+
.object({
|
|
585
|
+
view: z.literal("triangles"),
|
|
586
|
+
triangles: z.array(triangleSchema),
|
|
587
|
+
})
|
|
588
|
+
.strict(),
|
|
589
|
+
z
|
|
590
|
+
.object({
|
|
591
|
+
view: z.literal("latest-diagonal"),
|
|
592
|
+
points: z.array(pointSchema.omit({ components: true })),
|
|
593
|
+
})
|
|
594
|
+
.strict(),
|
|
595
|
+
]);
|
|
596
|
+
const toolSuccessSchema = z
|
|
597
|
+
.object({
|
|
598
|
+
success: z.literal(true),
|
|
599
|
+
data: z
|
|
600
|
+
.object({
|
|
601
|
+
runPresetId: tokenSchema,
|
|
602
|
+
instanceIds: z.array(tokenSchema).min(1),
|
|
603
|
+
definitionIntegrity: tagSchema,
|
|
604
|
+
formulaFingerprints: recordSchema(tagSchema),
|
|
605
|
+
calculationFingerprints: recordSchema(tagSchema),
|
|
606
|
+
runFingerprint: tagSchema,
|
|
607
|
+
resultFingerprint: tagSchema,
|
|
608
|
+
runResultFingerprint: tagSchema,
|
|
609
|
+
review: reviewSchema,
|
|
610
|
+
display: displaySchema,
|
|
611
|
+
})
|
|
612
|
+
.strict(),
|
|
613
|
+
})
|
|
614
|
+
.strict();
|
|
615
|
+
/** Strict model-visible output schema, including the wrapper's failure branch. */
|
|
616
|
+
export const diagnosticAgentToolResultSchema = z.union([
|
|
617
|
+
toolSuccessSchema,
|
|
618
|
+
toolFailureSchema,
|
|
619
|
+
]) as unknown as z.ZodType<DiagnosticAgentToolResult>;
|
|
620
|
+
export type DiagnosticSelectionTool = DefinedActuarialTool<
|
|
621
|
+
DiagnosticAgentToolInput,
|
|
622
|
+
DiagnosticAgentToolResult
|
|
623
|
+
>;
|
|
624
|
+
|
|
625
|
+
function token(value: unknown, label: string): asserts value is string {
|
|
626
|
+
if (!isDiagnosticToken(value))
|
|
627
|
+
throw new AgentsError(
|
|
628
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
629
|
+
`${label} must be a nonempty token`,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
function sortUniqueRequested(values: readonly string[]): string[] {
|
|
633
|
+
const result = [...new Set(values)];
|
|
634
|
+
result.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export function createDiagnosticSelectionTool(
|
|
639
|
+
input: CreateDiagnosticSelectionToolInput,
|
|
640
|
+
): DiagnosticSelectionTool {
|
|
641
|
+
try {
|
|
642
|
+
assertCompiledDiagnosticDefinition(input.definition);
|
|
643
|
+
} catch {
|
|
644
|
+
throw new AgentsError(
|
|
645
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
646
|
+
"definition must be an authentic compiled diagnostic definition",
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
const definition = input.definition;
|
|
650
|
+
const id = input.id === undefined ? "run_diagnostic_selection" : input.id;
|
|
651
|
+
const description =
|
|
652
|
+
input.description === undefined
|
|
653
|
+
? "Run a host-approved diagnostic preset for selected registered metric instances."
|
|
654
|
+
: input.description;
|
|
655
|
+
const tenantKey =
|
|
656
|
+
input.tenantContextKey === undefined ? "projectId" : input.tenantContextKey;
|
|
657
|
+
token(id, "tool id");
|
|
658
|
+
token(tenantKey, "tenant context key");
|
|
659
|
+
if (typeof description !== "string" || description.trim().length === 0)
|
|
660
|
+
throw new AgentsError(
|
|
661
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
662
|
+
"description must be nonblank",
|
|
663
|
+
);
|
|
664
|
+
if (!Array.isArray(input.runPresets) || input.runPresets.length === 0)
|
|
665
|
+
throw new AgentsError(
|
|
666
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
667
|
+
"at least one approved diagnostic preset is required",
|
|
668
|
+
);
|
|
669
|
+
const known = new Set(definition.definition.instances.map((item) => item.id));
|
|
670
|
+
const catalog = new Map<
|
|
671
|
+
string,
|
|
672
|
+
{
|
|
673
|
+
definitionIntegrity: string;
|
|
674
|
+
allowedInstanceIds: readonly string[];
|
|
675
|
+
execute: DiagnosticAgentRunPreset["execute"];
|
|
676
|
+
}
|
|
677
|
+
>();
|
|
678
|
+
for (const preset of input.runPresets) {
|
|
679
|
+
if (!isDiagnosticPlainRecord(preset))
|
|
680
|
+
throw new AgentsError(
|
|
681
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
682
|
+
"Each diagnostic preset must be a plain record",
|
|
683
|
+
);
|
|
684
|
+
token(preset.id, "preset id");
|
|
685
|
+
if (catalog.has(preset.id))
|
|
686
|
+
throw new AgentsError(
|
|
687
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
688
|
+
`duplicate preset ${preset.id}`,
|
|
689
|
+
);
|
|
690
|
+
if (preset.definitionIntegrity !== definition.definitionIntegrity)
|
|
691
|
+
throw new AgentsError(
|
|
692
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
693
|
+
`preset ${preset.id} targets another definition`,
|
|
694
|
+
);
|
|
695
|
+
if (typeof preset.execute !== "function")
|
|
696
|
+
throw new AgentsError(
|
|
697
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
698
|
+
`preset ${preset.id} has no executor`,
|
|
699
|
+
);
|
|
700
|
+
const seen = new Set<string>();
|
|
701
|
+
if (!Array.isArray(preset.allowedInstanceIds))
|
|
702
|
+
throw new AgentsError(
|
|
703
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
704
|
+
`preset ${preset.id} must declare allowed instance IDs`,
|
|
705
|
+
);
|
|
706
|
+
for (const instanceId of preset.allowedInstanceIds) {
|
|
707
|
+
token(instanceId, "allowed instance id");
|
|
708
|
+
if (seen.has(instanceId))
|
|
709
|
+
throw new AgentsError(
|
|
710
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
711
|
+
`preset ${preset.id} repeats ${instanceId}`,
|
|
712
|
+
);
|
|
713
|
+
if (!known.has(instanceId))
|
|
714
|
+
throw new AgentsError(
|
|
715
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
716
|
+
`preset ${preset.id} references unknown instance ${instanceId}`,
|
|
717
|
+
);
|
|
718
|
+
seen.add(instanceId);
|
|
719
|
+
}
|
|
720
|
+
if (seen.size === 0)
|
|
721
|
+
throw new AgentsError(
|
|
722
|
+
"BAD_DIAGNOSTIC_CATALOG",
|
|
723
|
+
`preset ${preset.id} has no allowed instances`,
|
|
724
|
+
);
|
|
725
|
+
catalog.set(
|
|
726
|
+
preset.id,
|
|
727
|
+
Object.freeze({
|
|
728
|
+
definitionIntegrity: preset.definitionIntegrity,
|
|
729
|
+
allowedInstanceIds: Object.freeze([...seen].sort()),
|
|
730
|
+
execute: preset.execute as DiagnosticAgentRunPreset["execute"],
|
|
731
|
+
}),
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
return defineActuarialTool({
|
|
735
|
+
id,
|
|
736
|
+
description,
|
|
737
|
+
kind: "read",
|
|
738
|
+
tenant: "required",
|
|
739
|
+
tenantKey,
|
|
740
|
+
inputSchema: diagnosticAgentToolInputSchema,
|
|
741
|
+
outputSchema: diagnosticAgentToolResultSchema,
|
|
742
|
+
execute: async (raw, tenant): Promise<DiagnosticAgentToolSuccess> => {
|
|
743
|
+
const preset = catalog.get(raw.runPresetId);
|
|
744
|
+
if (!preset)
|
|
745
|
+
throw new AgentsError(
|
|
746
|
+
"UNKNOWN_DIAGNOSTIC_PRESET",
|
|
747
|
+
`Unknown diagnostic preset ${raw.runPresetId}`,
|
|
748
|
+
);
|
|
749
|
+
const selected = sortUniqueRequested(raw.instanceIds);
|
|
750
|
+
if (selected.some((item) => !preset.allowedInstanceIds.includes(item)))
|
|
751
|
+
throw new AgentsError(
|
|
752
|
+
"UNAPPROVED_DIAGNOSTIC_INSTANCE",
|
|
753
|
+
"One or more diagnostic instances are not approved by the selected preset",
|
|
754
|
+
);
|
|
755
|
+
const provenance = await preset.execute({
|
|
756
|
+
tenantId: tenant,
|
|
757
|
+
instanceIds: selected,
|
|
758
|
+
});
|
|
759
|
+
try {
|
|
760
|
+
assertVerifiedDiagnosticRunProvenance(provenance);
|
|
761
|
+
} catch {
|
|
762
|
+
throw new AgentsError(
|
|
763
|
+
"DIAGNOSTIC_RUN_MISMATCH",
|
|
764
|
+
"Preset executor returned unauthenticated diagnostic provenance",
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const filter = provenance.manifest.filter;
|
|
768
|
+
if (
|
|
769
|
+
provenance.definition.identities.definition !==
|
|
770
|
+
definition.definitionIntegrity ||
|
|
771
|
+
provenance.manifest.runPresetId !== raw.runPresetId ||
|
|
772
|
+
!filter ||
|
|
773
|
+
JSON.stringify(filter.instanceIds ?? []) !== JSON.stringify(selected)
|
|
774
|
+
)
|
|
775
|
+
throw new AgentsError(
|
|
776
|
+
"DIAGNOSTIC_RUN_MISMATCH",
|
|
777
|
+
"Verified run does not match the selected definition, preset, and exact instance set",
|
|
778
|
+
);
|
|
779
|
+
const instances = definition.definition.instances.filter((item) =>
|
|
780
|
+
selected.includes(item.id),
|
|
781
|
+
);
|
|
782
|
+
const formulaIds = [
|
|
783
|
+
...new Set(instances.map((item) => item.formulaId)),
|
|
784
|
+
].sort();
|
|
785
|
+
const formulaFingerprints = Object.fromEntries(
|
|
786
|
+
formulaIds.map((formulaId) => [
|
|
787
|
+
formulaId,
|
|
788
|
+
provenance.definition.identities.formulaById[formulaId]!,
|
|
789
|
+
]),
|
|
790
|
+
);
|
|
791
|
+
const calculationFingerprints = Object.fromEntries(
|
|
792
|
+
selected.map((instanceId) => [
|
|
793
|
+
instanceId,
|
|
794
|
+
provenance.definition.identities.calculationByInstanceId[instanceId]!,
|
|
795
|
+
]),
|
|
796
|
+
);
|
|
797
|
+
const displayPoints = (
|
|
798
|
+
points: VerifiedDiagnosticRunProvenance["result"]["emergence"],
|
|
799
|
+
): readonly DiagnosticAgentDisplayPoint[] =>
|
|
800
|
+
points.map(({ components: _components, ...point }) => point);
|
|
801
|
+
const display =
|
|
802
|
+
raw.view === "emergence"
|
|
803
|
+
? {
|
|
804
|
+
view: "emergence" as const,
|
|
805
|
+
points: displayPoints(provenance.result.emergence),
|
|
806
|
+
}
|
|
807
|
+
: raw.view === "triangles"
|
|
808
|
+
? {
|
|
809
|
+
view: "triangles" as const,
|
|
810
|
+
triangles: provenance.result.triangles,
|
|
811
|
+
}
|
|
812
|
+
: {
|
|
813
|
+
view: "latest-diagonal" as const,
|
|
814
|
+
points: displayPoints(provenance.result.latestDiagonal),
|
|
815
|
+
};
|
|
816
|
+
return {
|
|
817
|
+
success: true,
|
|
818
|
+
data: {
|
|
819
|
+
runPresetId: raw.runPresetId,
|
|
820
|
+
instanceIds: selected,
|
|
821
|
+
definitionIntegrity: provenance.definition.identities.definition,
|
|
822
|
+
formulaFingerprints,
|
|
823
|
+
calculationFingerprints,
|
|
824
|
+
runFingerprint: provenance.runFingerprint,
|
|
825
|
+
resultFingerprint: provenance.resultFingerprint,
|
|
826
|
+
runResultFingerprint: provenance.runResultFingerprint,
|
|
827
|
+
review: provenance.review,
|
|
828
|
+
display,
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
}
|