hoi4-agent-tools 2.2.0 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +30 -21
  3. package/dist/hoi4_agent_tools/core/clausewitz-evaluation.js +90 -0
  4. package/dist/hoi4_agent_tools/core/engine.js +5 -0
  5. package/dist/hoi4_agent_tools/core/index.js +57 -1
  6. package/dist/hoi4_agent_tools/core/source/ast-path.js +41 -0
  7. package/dist/hoi4_agent_tools/core/source/index.js +1 -0
  8. package/dist/hoi4_agent_tools/event/algorithms.js +178 -1
  9. package/dist/hoi4_agent_tools/event/graph.js +24 -9
  10. package/dist/hoi4_agent_tools/event/limits.js +5 -2
  11. package/dist/hoi4_agent_tools/event/service.js +182 -45
  12. package/dist/hoi4_agent_tools/mcp/server/create.js +3 -1
  13. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +13 -63
  14. package/dist/hoi4_agent_tools/mcp/tools/probability.js +413 -0
  15. package/dist/hoi4_agent_tools/mcp/tools/technology.js +4 -1
  16. package/dist/hoi4_agent_tools/probability/adapters.js +344 -0
  17. package/dist/hoi4_agent_tools/probability/compare.js +178 -0
  18. package/dist/hoi4_agent_tools/probability/evaluation.js +649 -0
  19. package/dist/hoi4_agent_tools/probability/index.js +13 -0
  20. package/dist/hoi4_agent_tools/probability/model.js +5 -0
  21. package/dist/hoi4_agent_tools/probability/rational.js +145 -0
  22. package/dist/hoi4_agent_tools/probability/render.js +307 -0
  23. package/dist/hoi4_agent_tools/probability/sequence.js +858 -0
  24. package/dist/hoi4_agent_tools/probability/service.js +1168 -0
  25. package/dist/hoi4_agent_tools/probability/simulation.js +545 -0
  26. package/dist/hoi4_agent_tools/probability/source-analysis.js +458 -0
  27. package/dist/hoi4_agent_tools/probability/sweep.js +265 -0
  28. package/dist/hoi4_agent_tools/probability/trigger-evaluator.js +280 -0
  29. package/dist/hoi4_agent_tools/probability/value-evaluator.js +357 -0
  30. package/dist/hoi4_agent_tools/runtime.js +109 -7
  31. package/dist/hoi4_agent_tools/schemas/probability.js +930 -0
  32. package/dist/hoi4_agent_tools/technology/graph.js +30 -3
  33. package/dist/hoi4_agent_tools/technology/queries.js +70 -3
  34. package/dist/hoi4_agent_tools/technology/service.js +38 -2
  35. package/dist/hoi4_agent_tools/version.js +1 -1
  36. package/docs/README.md +1 -0
  37. package/docs/development.md +2 -0
  38. package/docs/events.md +2 -2
  39. package/docs/probability.md +80 -0
  40. package/docs/research/probability-adapter-evidence.md +64 -0
  41. package/docs/setup.md +2 -0
  42. package/docs/technology.md +4 -2
  43. package/examples/clients/codex-config.toml +3 -0
  44. package/examples/clients/generic.json +8 -0
  45. package/examples/probability/adaptive-event-pool.json +92 -0
  46. package/examples/probability/adaptive-event-sequence-input.json +57 -0
  47. package/examples/probability/event-mtth-scenarios.json +57 -0
  48. package/examples/probability/event-mtth-simulate-input.json +37 -0
  49. package/examples/probability/focus-route-scenarios.json +61 -0
  50. package/examples/probability/focus-route-sweep-input.json +36 -0
  51. package/package.json +19 -8
  52. package/schemas/configuration.schema.json +747 -0
  53. package/schemas/continuous-focus-palette.schema.json +325 -0
  54. package/schemas/custom-weighted-pool.schema.json +298 -0
  55. package/schemas/event-feature-manifest.schema.json +63 -0
  56. package/schemas/focus-plan.schema.json +927 -0
  57. package/schemas/focus-planning-sidecar.schema.json +407 -0
  58. package/schemas/gui-animation-source.schema.json +176 -0
  59. package/schemas/gui-helper.schema.json +301 -0
  60. package/schemas/gui-scenario.schema.json +298 -0
  61. package/schemas/map-operation.schema.json +2284 -0
  62. package/schemas/operation-result.schema.json +327 -0
  63. package/schemas/probability-analysis-result.schema.json +2211 -0
  64. package/schemas/probability-compare-input.schema.json +664 -0
  65. package/schemas/probability-evaluate-input.schema.json +665 -0
  66. package/schemas/probability-inspect-input.schema.json +97 -0
  67. package/schemas/probability-render-input.schema.json +141 -0
  68. package/schemas/probability-scenario-set.schema.json +430 -0
  69. package/schemas/probability-sequence-input.schema.json +886 -0
  70. package/schemas/probability-simulate-input.schema.json +691 -0
  71. package/schemas/probability-sweep-input.schema.json +702 -0
  72. package/server.json +3 -3
@@ -0,0 +1,930 @@
1
+ import { z } from 'zod/v4';
2
+ import { diagnosticSchema, sourceLocationSchema, workspaceIdSchema } from './common.js';
3
+ import { artifactLinkSchema } from './transaction.js';
4
+ export const probabilityAdapterIdSchema = z.enum([
5
+ 'event_mean_time_to_happen',
6
+ 'event_option_ai_chance',
7
+ 'decision_ai_will_do',
8
+ 'mission_ai_will_do',
9
+ 'national_focus_ai_will_do',
10
+ 'technology_ai_will_do',
11
+ 'doctrine_ai_will_do',
12
+ 'direct_random',
13
+ 'random_list',
14
+ 'ai_strategy_factor',
15
+ 'custom_weighted_pool',
16
+ ]);
17
+ const scalarSchema = z.union([z.string().max(4096), z.number(), z.boolean(), z.null()]);
18
+ const scenarioValueSchema = z.union([scalarSchema, z.array(scalarSchema).max(100_000)]);
19
+ const workspaceReferenceSchema = z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/u);
20
+ const distributionSchema = z
21
+ .object({
22
+ kind: z.enum(['uniform', 'triangular', 'normal', 'lognormal', 'categorical', 'empirical']),
23
+ min: z.number().optional(),
24
+ max: z.number().optional(),
25
+ mode: z.number().optional(),
26
+ mean: z.number().optional(),
27
+ stddev: z.number().positive().optional(),
28
+ values: z.array(scalarSchema).min(1).max(100_000).optional(),
29
+ probabilities: z.array(z.number().min(0).max(1)).min(1).max(100_000).optional(),
30
+ })
31
+ .strict()
32
+ .superRefine((distribution, context) => {
33
+ if ((distribution.kind === 'uniform' || distribution.kind === 'triangular') &&
34
+ (distribution.min === undefined ||
35
+ distribution.max === undefined ||
36
+ distribution.min > distribution.max))
37
+ context.addIssue({ code: 'custom', message: `${distribution.kind} requires min <= max` });
38
+ if (distribution.kind === 'triangular' &&
39
+ (distribution.mode === undefined ||
40
+ distribution.min === undefined ||
41
+ distribution.max === undefined ||
42
+ distribution.mode < distribution.min ||
43
+ distribution.mode > distribution.max))
44
+ context.addIssue({ code: 'custom', message: 'triangular mode must be inside min and max' });
45
+ if ((distribution.kind === 'normal' || distribution.kind === 'lognormal') &&
46
+ (distribution.mean === undefined || distribution.stddev === undefined))
47
+ context.addIssue({
48
+ code: 'custom',
49
+ message: `${distribution.kind} requires mean and stddev`,
50
+ });
51
+ if ((distribution.kind === 'categorical' || distribution.kind === 'empirical') &&
52
+ distribution.values === undefined)
53
+ context.addIssue({ code: 'custom', message: `${distribution.kind} requires values` });
54
+ if (distribution.probabilities !== undefined &&
55
+ distribution.values !== undefined &&
56
+ distribution.probabilities.length !== distribution.values.length)
57
+ context.addIssue({ code: 'custom', message: 'probabilities must match values length' });
58
+ if (distribution.probabilities !== undefined &&
59
+ Math.abs(distribution.probabilities.reduce((sum, value) => sum + value, 0) - 1) > 1e-9)
60
+ context.addIssue({ code: 'custom', message: 'probabilities must sum to one' });
61
+ });
62
+ const uncertainInputSchema = z
63
+ .object({
64
+ path: z.string().min(1).max(1024),
65
+ range: z.object({ min: z.number(), max: z.number() }).strict().optional(),
66
+ alternatives: z.array(scalarSchema).min(1).max(100_000).optional(),
67
+ distribution: distributionSchema.optional(),
68
+ unresolved: z.literal(true).optional(),
69
+ })
70
+ .strict()
71
+ .superRefine((value, context) => {
72
+ const variants = [value.range, value.alternatives, value.distribution, value.unresolved].filter((entry) => entry !== undefined);
73
+ if (variants.length !== 1)
74
+ context.addIssue({
75
+ code: 'custom',
76
+ message: 'Each uncertain input requires exactly one range, alternatives, distribution, or unresolved marker',
77
+ });
78
+ if (value.range !== undefined && value.range.min > value.range.max)
79
+ context.addIssue({
80
+ code: 'custom',
81
+ path: ['range'],
82
+ message: 'Range min must not exceed max',
83
+ });
84
+ });
85
+ export const probabilityScenarioSchema = z
86
+ .object({
87
+ id: z.string().min(1).max(512),
88
+ label: z.string().max(4096).optional(),
89
+ prevalence: z.number().min(0).max(1).optional(),
90
+ actor: z.string().max(256).optional(),
91
+ date: z.string().max(64).optional(),
92
+ state: z.record(z.string().min(1).max(1024), scenarioValueSchema),
93
+ flags: z.array(z.string().min(1).max(512)).max(100_000).optional(),
94
+ eventTargets: z.record(z.string().max(512), z.string().max(512)).optional(),
95
+ candidateOverrides: z.record(z.string().max(512), z.boolean()).optional(),
96
+ uncertainInputs: z.array(uncertainInputSchema).max(10_000).optional(),
97
+ correlations: z
98
+ .array(z
99
+ .object({
100
+ left: z.string().min(1).max(1024),
101
+ right: z.string().min(1).max(1024),
102
+ coefficient: z.number().min(-1).max(1),
103
+ })
104
+ .strict())
105
+ .max(10_000)
106
+ .optional(),
107
+ schedule: z
108
+ .array(z
109
+ .object({
110
+ atDay: z.number().min(0),
111
+ set: z.record(z.string().max(1024), scenarioValueSchema),
112
+ clearFlags: z.array(z.string().max(512)).max(100_000).optional(),
113
+ addFlags: z.array(z.string().max(512)).max(100_000).optional(),
114
+ })
115
+ .strict())
116
+ .max(100_000)
117
+ .optional(),
118
+ })
119
+ .strict()
120
+ .superRefine((scenario, context) => {
121
+ const paths = scenario.uncertainInputs?.map(({ path }) => path) ?? [];
122
+ if (new Set(paths).size !== paths.length)
123
+ context.addIssue({
124
+ code: 'custom',
125
+ path: ['uncertainInputs'],
126
+ message: 'Uncertain input paths must be unique within a scenario',
127
+ });
128
+ for (const [index, correlation] of (scenario.correlations ?? []).entries()) {
129
+ if (!paths.includes(correlation.left) || !paths.includes(correlation.right))
130
+ context.addIssue({
131
+ code: 'custom',
132
+ path: ['correlations', index],
133
+ message: 'Correlation endpoints must name uncertain inputs in this scenario',
134
+ });
135
+ if (correlation.left === correlation.right)
136
+ context.addIssue({
137
+ code: 'custom',
138
+ path: ['correlations', index],
139
+ message: 'Self-correlation is implicit and must not be declared',
140
+ });
141
+ }
142
+ });
143
+ export const probabilityScenarioSetSchema = z
144
+ .object({
145
+ schemaVersion: z.literal('1.0').default('1.0'),
146
+ id: z.string().min(1).max(512),
147
+ workspaceId: workspaceReferenceSchema.optional(),
148
+ description: z.string().max(8192).optional(),
149
+ surfaceHint: probabilityAdapterIdSchema.optional(),
150
+ scenarios: z.array(probabilityScenarioSchema).min(1).max(10_000),
151
+ })
152
+ .strict();
153
+ export const probabilityMetricSchema = z.enum([
154
+ 'conditional_probability',
155
+ 'raw_value',
156
+ 'cumulative_chance',
157
+ 'effective_mtth_days',
158
+ ]);
159
+ export const probabilityAcceptanceBandSchema = z
160
+ .object({
161
+ id: z.string().min(1).max(512),
162
+ label: z.string().max(4096).optional(),
163
+ scenarioId: z.string().min(1).max(512).optional(),
164
+ candidateId: z.string().min(1).max(512),
165
+ metric: probabilityMetricSchema,
166
+ min: z.number().optional(),
167
+ max: z.number().optional(),
168
+ })
169
+ .strict()
170
+ .superRefine((band, context) => {
171
+ if (band.min === undefined && band.max === undefined)
172
+ context.addIssue({ code: 'custom', message: 'Acceptance band requires min, max, or both' });
173
+ if (band.min !== undefined && band.max !== undefined && band.min > band.max)
174
+ context.addIssue({ code: 'custom', message: 'Acceptance band min must not exceed max' });
175
+ if ((band.metric === 'conditional_probability' || band.metric === 'cumulative_chance') &&
176
+ ((band.min !== undefined && (band.min < 0 || band.min > 1)) ||
177
+ (band.max !== undefined && (band.max < 0 || band.max > 1))))
178
+ context.addIssue({
179
+ code: 'custom',
180
+ message: `${band.metric} acceptance bounds must be between zero and one`,
181
+ });
182
+ });
183
+ export const probabilityDiagnosticThresholdsSchema = z
184
+ .object({
185
+ dominantProbability: z.number().min(0).max(1).optional(),
186
+ dominantScenarioPrevalence: z.number().min(0).max(1).optional(),
187
+ starvedProbability: z.number().min(0).max(1).optional(),
188
+ negligibleCumulativeChance: z.number().min(0).max(1).optional(),
189
+ extremeGrowthRatio: z.number().positive().optional(),
190
+ thresholdCliffDelta: z.number().positive().optional(),
191
+ rareOutcomeMinimumObservations: z.number().int().positive().optional(),
192
+ })
193
+ .strict();
194
+ export const probabilitySamplingMethodSchema = z.enum(['latin_hypercube', 'pseudo_random']);
195
+ export const probabilitySourceSchema = z
196
+ .object({
197
+ identifier: z.string().min(1).max(1024).optional(),
198
+ path: z.string().min(1).max(1024).optional(),
199
+ line: z.number().int().min(1).optional(),
200
+ inlineClausewitz: z.string().max(16_777_216).optional(),
201
+ virtualPatch: z.string().max(16_777_216).optional(),
202
+ expectedSourceHash: z
203
+ .string()
204
+ .regex(/^[a-f0-9]{64}$/u)
205
+ .optional(),
206
+ })
207
+ .strict()
208
+ .refine((value) => value.identifier !== undefined ||
209
+ value.path !== undefined ||
210
+ value.inlineClausewitz !== undefined ||
211
+ value.virtualPatch !== undefined, 'Source requires an identifier, path, inline Clausewitz source, or virtual patch');
212
+ const customPoolCandidateSchema = z
213
+ .object({
214
+ id: z.string().min(1).max(512),
215
+ category: z.string().max(512).optional(),
216
+ weight: z.union([z.number(), z.string().max(1024)]),
217
+ cap: z.union([z.number(), z.string().max(1024)]).optional(),
218
+ eligibleWhen: z.string().max(4096).optional(),
219
+ oneTime: z.boolean().optional(),
220
+ cooldownDays: z.number().min(0).optional(),
221
+ })
222
+ .strict();
223
+ const customPoolActionSchema = z
224
+ .object({
225
+ operation: z.enum([
226
+ 'set',
227
+ 'add',
228
+ 'multiply',
229
+ 'cap',
230
+ 'remove',
231
+ 'cooldown',
232
+ 'reset_category',
233
+ 'compress_timer',
234
+ 'reset_timer',
235
+ 'terminate',
236
+ ]),
237
+ target: z.string().min(1).max(1024),
238
+ value: scalarSchema.optional(),
239
+ })
240
+ .strict();
241
+ export const customWeightedPoolManifestSchema = z
242
+ .object({
243
+ schemaVersion: z.literal('1.0').default('1.0'),
244
+ id: z.string().min(1).max(512),
245
+ description: z.string().max(8192).optional(),
246
+ selection: z
247
+ .object({
248
+ mode: z.enum(['categorical_weighted', 'independent_chances']),
249
+ cadence: z.enum(['daily', 'weekly', 'monthly', 'timer']),
250
+ timerMinDays: z.number().min(0).optional(),
251
+ timerMaxDays: z.number().min(0).optional(),
252
+ rounding: z.enum(['exact', 'floor', 'ceil', 'nearest']).optional(),
253
+ })
254
+ .strict(),
255
+ state: z.record(z.string().max(1024), scalarSchema).optional(),
256
+ candidates: z.array(customPoolCandidateSchema).min(1).max(100_000),
257
+ recovery: z
258
+ .array(z
259
+ .object({
260
+ cadence: z.enum(['daily', 'weekly', 'monthly']),
261
+ target: z.string().min(1).max(1024),
262
+ amount: z.union([z.number(), z.string().max(1024)]),
263
+ cap: z.union([z.number(), z.string().max(1024)]).optional(),
264
+ })
265
+ .strict())
266
+ .max(100_000)
267
+ .optional(),
268
+ transitions: z
269
+ .array(z
270
+ .object({
271
+ when: z.string().max(4096),
272
+ actions: z.array(customPoolActionSchema).max(100_000),
273
+ })
274
+ .strict())
275
+ .max(100_000),
276
+ })
277
+ .strict()
278
+ .superRefine((manifest, context) => {
279
+ const candidateIds = new Set(manifest.candidates.map(({ id }) => id));
280
+ if (candidateIds.size !== manifest.candidates.length)
281
+ context.addIssue({
282
+ code: 'custom',
283
+ path: ['candidates'],
284
+ message: 'Candidate IDs must be unique',
285
+ });
286
+ if (manifest.selection.timerMinDays !== undefined &&
287
+ manifest.selection.timerMaxDays !== undefined &&
288
+ manifest.selection.timerMinDays > manifest.selection.timerMaxDays)
289
+ context.addIssue({
290
+ code: 'custom',
291
+ path: ['selection'],
292
+ message: 'timerMinDays must not exceed timerMaxDays',
293
+ });
294
+ const categories = new Set(manifest.candidates.flatMap(({ category }) => (category === undefined ? [] : [category])));
295
+ const validateTarget = (target, path) => {
296
+ if (target === 'selected.candidate' || target === 'selection')
297
+ return;
298
+ if (target.startsWith('state.')) {
299
+ if (!Object.hasOwn(manifest.state ?? {}, target.slice('state.'.length)))
300
+ context.addIssue({
301
+ code: 'custom',
302
+ path,
303
+ message: `Transition target ${target} is not declared in state`,
304
+ });
305
+ return;
306
+ }
307
+ if (target.startsWith('candidate.')) {
308
+ const id = target.split('.')[1];
309
+ if (id === undefined || !candidateIds.has(id))
310
+ context.addIssue({
311
+ code: 'custom',
312
+ path,
313
+ message: `Transition target ${target} names an unknown candidate`,
314
+ });
315
+ return;
316
+ }
317
+ if (target.startsWith('category.')) {
318
+ const id = target.slice('category.'.length);
319
+ if (!categories.has(id))
320
+ context.addIssue({
321
+ code: 'custom',
322
+ path,
323
+ message: `Transition target ${target} names an unknown category`,
324
+ });
325
+ return;
326
+ }
327
+ if (target.startsWith('selection.'))
328
+ return;
329
+ context.addIssue({
330
+ code: 'custom',
331
+ path,
332
+ message: `Transition target ${target} is outside declared pool state`,
333
+ });
334
+ };
335
+ for (const [index, recovery] of (manifest.recovery ?? []).entries())
336
+ validateTarget(recovery.target, ['recovery', index, 'target']);
337
+ for (const [transitionIndex, transition] of manifest.transitions.entries())
338
+ for (const [actionIndex, action] of transition.actions.entries())
339
+ validateTarget(action.target, [
340
+ 'transitions',
341
+ transitionIndex,
342
+ 'actions',
343
+ actionIndex,
344
+ 'target',
345
+ ]);
346
+ });
347
+ export const probabilityOutputSchema = z.enum([
348
+ 'json',
349
+ 'ranking',
350
+ 'matrix',
351
+ 'waterfall',
352
+ 'timing',
353
+ 'sensitivity',
354
+ 'threshold',
355
+ 'sequence',
356
+ 'comparison',
357
+ 'unresolved',
358
+ ]);
359
+ export const probabilityRenderFilterSchema = z
360
+ .object({
361
+ scenarioIds: z.array(z.string().min(1).max(512)).max(10_000).optional(),
362
+ candidateIds: z.array(z.string().min(1).max(512)).max(100_000).optional(),
363
+ diagnosticSeverities: z
364
+ .array(z.enum(['info', 'warning', 'error', 'blocker']))
365
+ .max(4)
366
+ .optional(),
367
+ sourcePaths: z.array(z.string().min(1).max(4096)).max(10_000).optional(),
368
+ metrics: z.array(probabilityMetricSchema).max(4).optional(),
369
+ offset: z.number().int().min(0).default(0),
370
+ limit: z.number().int().min(1).max(100_000).default(10_000),
371
+ })
372
+ .strict();
373
+ export const probabilityCommonInputSchema = z
374
+ .object({
375
+ workspaceId: workspaceIdSchema,
376
+ adapter: probabilityAdapterIdSchema,
377
+ source: probabilitySourceSchema,
378
+ scenarioSet: probabilityScenarioSetSchema,
379
+ candidatePool: z.array(z.string().min(1).max(512)).max(100_000).optional(),
380
+ horizonDays: z.number().positive().max(1_000_000).optional(),
381
+ metrics: z.array(probabilityMetricSchema).min(1).max(4).optional(),
382
+ acceptanceBands: z.array(probabilityAcceptanceBandSchema).max(100_000).optional(),
383
+ diagnosticThresholds: probabilityDiagnosticThresholdsSchema.optional(),
384
+ outputs: z.array(probabilityOutputSchema).max(10).optional(),
385
+ refresh: z.boolean().optional(),
386
+ })
387
+ .strict();
388
+ export const probabilityInspectInputSchema = z
389
+ .object({
390
+ workspaceId: workspaceIdSchema,
391
+ adapter: probabilityAdapterIdSchema.optional(),
392
+ source: probabilitySourceSchema.optional(),
393
+ candidatePool: z.array(z.string().min(1).max(512)).max(100_000).optional(),
394
+ refresh: z.boolean().optional(),
395
+ })
396
+ .strict();
397
+ export const probabilityEvaluateInputSchema = probabilityCommonInputSchema;
398
+ export const probabilitySweepInputSchema = probabilityCommonInputSchema
399
+ .extend({
400
+ sweep: z
401
+ .object({
402
+ paths: z.array(z.string().min(1).max(1024)).min(1).max(32),
403
+ steps: z.number().int().min(2).max(10_000).default(25),
404
+ pairwise: z.boolean().default(false),
405
+ findRankReversals: z.boolean().default(true),
406
+ })
407
+ .strict(),
408
+ })
409
+ .strict();
410
+ export const probabilitySimulateInputSchema = probabilityCommonInputSchema
411
+ .extend({
412
+ samples: z.number().int().min(100).max(10_000_000).default(100_000),
413
+ seed: z.number().int().min(-2_147_483_648).max(2_147_483_647).default(1),
414
+ confidenceLevel: z.number().min(0.5).max(0.9999).default(0.95),
415
+ samplingMethod: probabilitySamplingMethodSchema.default('latin_hypercube'),
416
+ })
417
+ .strict();
418
+ export const probabilitySequenceInputSchema = z
419
+ .object({
420
+ workspaceId: workspaceIdSchema,
421
+ scenarioSet: probabilityScenarioSetSchema,
422
+ customPoolManifest: customWeightedPoolManifestSchema,
423
+ horizonDays: z.number().positive().max(1_000_000),
424
+ maxSteps: z.number().int().min(1).max(100_000).default(1_000),
425
+ samples: z.number().int().min(100).max(10_000_000).default(100_000),
426
+ seed: z.number().int().min(-2_147_483_648).max(2_147_483_647).default(1),
427
+ confidenceLevel: z.number().min(0.5).max(0.9999).default(0.95),
428
+ acceptanceBands: z.array(probabilityAcceptanceBandSchema).max(100_000).optional(),
429
+ diagnosticThresholds: probabilityDiagnosticThresholdsSchema.optional(),
430
+ outputs: z.array(probabilityOutputSchema).max(10).optional(),
431
+ })
432
+ .strict();
433
+ export const probabilityCompareInputSchema = z
434
+ .object({
435
+ workspaceId: workspaceIdSchema,
436
+ adapter: probabilityAdapterIdSchema,
437
+ before: probabilitySourceSchema,
438
+ after: probabilitySourceSchema,
439
+ scenarioSet: probabilityScenarioSetSchema,
440
+ candidatePool: z.array(z.string().min(1).max(512)).max(100_000).optional(),
441
+ horizonDays: z.number().positive().max(1_000_000).optional(),
442
+ acceptanceBands: z.array(probabilityAcceptanceBandSchema).max(100_000).optional(),
443
+ diagnosticThresholds: probabilityDiagnosticThresholdsSchema.optional(),
444
+ outputs: z.array(probabilityOutputSchema).max(10).optional(),
445
+ refresh: z.boolean().optional(),
446
+ })
447
+ .strict();
448
+ export const probabilityRenderInputSchema = z
449
+ .object({
450
+ workspaceId: workspaceIdSchema,
451
+ analysisId: z.string().min(1).max(256),
452
+ expectedScenarioHash: z
453
+ .string()
454
+ .regex(/^[a-f0-9]{64}$/u)
455
+ .optional(),
456
+ outputs: z.array(probabilityOutputSchema).min(1).max(10),
457
+ includeHtml: z.boolean().default(false),
458
+ filter: probabilityRenderFilterSchema.optional(),
459
+ })
460
+ .strict();
461
+ const rationalJsonSchema = z
462
+ .object({
463
+ numerator: z.string().regex(/^-?\d+$/u),
464
+ denominator: z.string().regex(/^\d+$/u),
465
+ decimal: z.string(),
466
+ value: z.number(),
467
+ })
468
+ .strict();
469
+ const probabilityProvenanceSchema = z
470
+ .object({
471
+ path: z.string(),
472
+ rootKind: z.string(),
473
+ loadOrder: z.number().int(),
474
+ sourceHash: z.string(),
475
+ location: sourceLocationSchema.optional(),
476
+ astPath: z.array(z.string()).optional(),
477
+ symbol: z.string().optional(),
478
+ helperChain: z.array(z.string()).optional(),
479
+ })
480
+ .strict();
481
+ const probabilityUnresolvedSchema = z
482
+ .object({
483
+ code: z.string(),
484
+ message: z.string(),
485
+ path: z.string().optional(),
486
+ candidateId: z.string().optional(),
487
+ provenance: probabilityProvenanceSchema.optional(),
488
+ details: z.record(z.string(), z.unknown()).optional(),
489
+ })
490
+ .strict();
491
+ const valueTraceSchema = z
492
+ .object({
493
+ operation: z.enum(['base', 'add', 'factor', 'eligibility', 'external_factor', 'normalization']),
494
+ expression: z.string(),
495
+ applied: z.enum(['true', 'false', 'unresolved']),
496
+ value: rationalJsonSchema.optional(),
497
+ before: rationalJsonSchema.optional(),
498
+ after: rationalJsonSchema.optional(),
499
+ provenance: probabilityProvenanceSchema.optional(),
500
+ conditionExpression: z.string().optional(),
501
+ note: z.string().optional(),
502
+ })
503
+ .strict();
504
+ const adapterDescriptorSchema = z
505
+ .object({
506
+ id: probabilityAdapterIdSchema,
507
+ version: z.string(),
508
+ gameVersion: z.string(),
509
+ supportedGameVersions: z.array(z.string()),
510
+ selectionRule: z.enum([
511
+ 'proportional_categorical',
512
+ 'uniform_score_race',
513
+ 'independent_chance',
514
+ 'median_daily_hazard',
515
+ 'score_only',
516
+ 'custom_declared',
517
+ ]),
518
+ capabilities: z
519
+ .object({
520
+ eligibility: z.boolean(),
521
+ rawScore: z.boolean(),
522
+ normalizedProbability: z.boolean(),
523
+ timeDistribution: z.boolean(),
524
+ sequence: z.boolean(),
525
+ })
526
+ .strict(),
527
+ completePoolRequired: z.boolean(),
528
+ confidence: z.enum(['documented', 'documented_with_runtime_boundary', 'manifest_defined']),
529
+ sourceBlockTypes: z.array(z.string()),
530
+ candidateDiscoveryRules: z.array(z.string()),
531
+ eligibilityRules: z.array(z.string()),
532
+ modifierOrder: z.array(z.string()),
533
+ poolNormalizationRules: z.array(z.string()),
534
+ evaluationCadence: z.string(),
535
+ timingConversion: z.array(z.string()),
536
+ scopeExpectations: z.array(z.string()),
537
+ supportedExpressions: z.array(z.string()),
538
+ unsupportedConstructs: z.array(z.string()),
539
+ evidence: z.array(z.string()),
540
+ testFixtures: z.array(z.string()),
541
+ limitations: z.array(z.string()),
542
+ })
543
+ .strict();
544
+ const candidateAnalysisSchema = z
545
+ .object({
546
+ id: z.string(),
547
+ eligibility: z.enum(['true', 'false', 'unresolved']),
548
+ supportLevel: z.enum(['exact', 'bounded', 'sampled', 'score_only', 'external', 'unsupported']),
549
+ rawValue: rationalJsonSchema.nullable().optional(),
550
+ rawInterval: z.object({ min: z.number(), max: z.number() }).strict().optional(),
551
+ conditionalProbability: z.number().min(0).max(1).nullable().optional(),
552
+ exactConditionalProbability: rationalJsonSchema.optional(),
553
+ pathProbability: z.number().min(0).max(1).nullable().optional(),
554
+ exactPathProbability: rationalJsonSchema.optional(),
555
+ pathProbabilityInterval: z
556
+ .object({ low: z.number().min(0).max(1), high: z.number().min(0).max(1) })
557
+ .strict()
558
+ .optional(),
559
+ conditionalProbabilityInterval: z
560
+ .object({ low: z.number().min(0).max(1), high: z.number().min(0).max(1) })
561
+ .strict()
562
+ .optional(),
563
+ confidenceInterval: z
564
+ .object({ low: z.number().min(0).max(1), high: z.number().min(0).max(1) })
565
+ .strict()
566
+ .optional(),
567
+ sampledFrequency: z.number().min(0).max(1).optional(),
568
+ effectiveMtthDays: z.number().nonnegative().optional(),
569
+ timingModel: z.string().optional(),
570
+ timingQuantilesDays: z
571
+ .object({ p10: z.number(), p50: z.number(), p90: z.number(), p95: z.number() })
572
+ .strict()
573
+ .optional(),
574
+ timingQuantileIntervals: z
575
+ .object({
576
+ p10: z.object({ low: z.number(), high: z.number() }).strict(),
577
+ p50: z.object({ low: z.number(), high: z.number() }).strict(),
578
+ p90: z.object({ low: z.number(), high: z.number() }).strict(),
579
+ p95: z.object({ low: z.number(), high: z.number() }).strict(),
580
+ })
581
+ .strict()
582
+ .optional(),
583
+ timingSampleCount: z.number().int().nonnegative().optional(),
584
+ timingEvaluations: z.number().int().nonnegative().optional(),
585
+ timingReservoirCapacity: z.number().int().positive().optional(),
586
+ timingMethod: z.literal('sampled_discrete_daily_hazard').optional(),
587
+ timingConfidenceMethod: z.literal('normal_order_statistic').optional(),
588
+ cumulativeChance: z.number().min(0).max(1).optional(),
589
+ cumulativeChanceInterval: z
590
+ .object({ low: z.number().min(0).max(1), high: z.number().min(0).max(1) })
591
+ .strict()
592
+ .optional(),
593
+ rank: z.number().int().min(1).nullable().optional(),
594
+ trace: z.array(valueTraceSchema),
595
+ provenance: z.array(probabilityProvenanceSchema),
596
+ unresolved: z.array(probabilityUnresolvedSchema),
597
+ })
598
+ .strict();
599
+ const scenarioAnalysisSchema = z
600
+ .object({
601
+ id: z.string(),
602
+ label: z.string().optional(),
603
+ prevalence: z.number().min(0).max(1).optional(),
604
+ poolComplete: z.boolean(),
605
+ supportLevel: z.enum(['exact', 'bounded', 'sampled', 'score_only', 'external', 'unsupported']),
606
+ candidates: z.array(candidateAnalysisSchema),
607
+ poolTotal: rationalJsonSchema.optional(),
608
+ horizonDays: z.number().positive().optional(),
609
+ survivalChance: z.number().min(0).max(1).optional(),
610
+ survivalChanceInterval: z
611
+ .object({ low: z.number().min(0).max(1), high: z.number().min(0).max(1) })
612
+ .strict()
613
+ .optional(),
614
+ timingIntervals: z
615
+ .array(z
616
+ .object({
617
+ startDay: z.number().nonnegative(),
618
+ endDay: z.number().nonnegative(),
619
+ eligibility: z.enum(['true', 'false', 'unresolved']),
620
+ effectiveMtthDays: z.number().nonnegative().optional(),
621
+ minimumHazardContribution: z.number().nonnegative(),
622
+ maximumHazardContribution: z.number().nonnegative(),
623
+ })
624
+ .strict())
625
+ .optional(),
626
+ summary: z
627
+ .object({
628
+ topOutcomes: z.array(z.string()),
629
+ bottomEligibleOutcomes: z.array(z.string()),
630
+ impossibleOutcomes: z.array(z.string()),
631
+ unresolvedOutcomes: z.array(z.string()),
632
+ dominantFactors: z.array(z.string()),
633
+ closestRankReversal: z
634
+ .object({
635
+ candidates: z.tuple([z.string(), z.string()]),
636
+ gap: z.number().nonnegative(),
637
+ metric: z.enum(['probability', 'raw_value']),
638
+ })
639
+ .strict()
640
+ .optional(),
641
+ })
642
+ .strict(),
643
+ unresolved: z.array(probabilityUnresolvedSchema),
644
+ })
645
+ .strict();
646
+ const probabilityMetadataSchema = z
647
+ .object({
648
+ workspaceId: z.string(),
649
+ workspaceIdentity: z.string(),
650
+ sourceRevision: z.string(),
651
+ sourceHash: z.string(),
652
+ scenarioHash: z.string(),
653
+ candidatePoolHash: z.string(),
654
+ gameVersion: z.string(),
655
+ gameVersionVerification: z
656
+ .object({
657
+ status: z.enum(['workspace_verified', 'adapter_target_only']),
658
+ adapterTarget: z.string(),
659
+ sourcePath: z.string().optional(),
660
+ observedVersion: z.string().optional(),
661
+ observedRawVersion: z.string().optional(),
662
+ observedChecksum: z.string().optional(),
663
+ })
664
+ .strict(),
665
+ adapterId: probabilityAdapterIdSchema,
666
+ adapterVersion: z.string(),
667
+ requestedMetrics: z.array(probabilityMetricSchema).min(1).max(4).optional(),
668
+ seed: z.number().int().optional(),
669
+ samples: z.number().int().nonnegative().optional(),
670
+ numericalPrecision: z.string(),
671
+ cacheKey: z.string(),
672
+ })
673
+ .strict();
674
+ const sweepPointSchema = z
675
+ .object({
676
+ scenarioId: z.string(),
677
+ path: z.string(),
678
+ value: z.number(),
679
+ values: z.record(z.string(), z.number()).optional(),
680
+ candidates: z.array(z
681
+ .object({
682
+ id: z.string(),
683
+ rawValue: z.number().optional(),
684
+ conditionalProbability: z.number().optional(),
685
+ rank: z.number().int().optional(),
686
+ })
687
+ .strict()),
688
+ })
689
+ .strict();
690
+ const localElasticitySchema = z
691
+ .object({
692
+ scenarioId: z.string(),
693
+ path: z.string(),
694
+ candidateId: z.string(),
695
+ metric: z.enum(['conditional_probability', 'raw_value']),
696
+ between: z.tuple([z.number(), z.number()]),
697
+ slope: z.number(),
698
+ elasticity: z.number().optional(),
699
+ })
700
+ .strict();
701
+ const pairwiseInteractionSchema = z
702
+ .object({
703
+ scenarioId: z.string(),
704
+ paths: z.tuple([z.string(), z.string()]),
705
+ candidateId: z.string(),
706
+ metric: z.enum(['conditional_probability', 'raw_value']),
707
+ cell: z
708
+ .object({ left: z.tuple([z.number(), z.number()]), right: z.tuple([z.number(), z.number()]) })
709
+ .strict(),
710
+ mixedDifference: z.number(),
711
+ mixedDifferencePerUnit: z.number(),
712
+ })
713
+ .strict();
714
+ const simulationSummarySchema = z
715
+ .object({
716
+ scenarioId: z.string(),
717
+ samples: z.number().int().positive(),
718
+ seed: z.number().int(),
719
+ samplingMethod: probabilitySamplingMethodSchema,
720
+ rng: z.literal('mulberry32'),
721
+ stoppingRule: z.literal('fixed_sample_budget'),
722
+ confidenceLevel: z.number().min(0.5).max(0.9999),
723
+ confidenceMethod: z.literal('wilson_score'),
724
+ effectiveSampleSize: z.number().int().nonnegative(),
725
+ globalImportance: z.array(z
726
+ .object({
727
+ path: z.string(),
728
+ candidateId: z.string(),
729
+ metric: z.literal('raw_value'),
730
+ score: z.number().min(0).max(1),
731
+ method: z.literal('absolute_pearson_correlation'),
732
+ observations: z.number().int().nonnegative(),
733
+ })
734
+ .strict()),
735
+ convergence: z
736
+ .object({
737
+ firstHalfSamples: z.number().int().nonnegative(),
738
+ secondHalfSamples: z.number().int().nonnegative(),
739
+ maximumFrequencyDelta: z.number().min(0).max(1),
740
+ })
741
+ .strict(),
742
+ candidates: z.array(z
743
+ .object({
744
+ id: z.string(),
745
+ frequency: z.number().min(0).max(1).optional(),
746
+ confidenceInterval: z
747
+ .object({ low: z.number().min(0).max(1), high: z.number().min(0).max(1) })
748
+ .strict()
749
+ .optional(),
750
+ observedSelections: z.number().int().nonnegative().optional(),
751
+ rawMean: z.number().optional(),
752
+ eligibilityFrequency: z.number().min(0).max(1),
753
+ timingQuantilesDays: z
754
+ .object({ p10: z.number(), p50: z.number(), p90: z.number(), p95: z.number() })
755
+ .strict()
756
+ .optional(),
757
+ timingQuantileIntervals: z
758
+ .object({
759
+ p10: z.object({ low: z.number(), high: z.number() }).strict(),
760
+ p50: z.object({ low: z.number(), high: z.number() }).strict(),
761
+ p90: z.object({ low: z.number(), high: z.number() }).strict(),
762
+ p95: z.object({ low: z.number(), high: z.number() }).strict(),
763
+ })
764
+ .strict()
765
+ .optional(),
766
+ timingSampleCount: z.number().int().nonnegative().optional(),
767
+ timingEvaluations: z.number().int().nonnegative().optional(),
768
+ timingReservoirCapacity: z.number().int().positive().optional(),
769
+ timingMethod: z.literal('sampled_discrete_daily_hazard').optional(),
770
+ timingConfidenceMethod: z.literal('normal_order_statistic').optional(),
771
+ })
772
+ .strict()),
773
+ })
774
+ .strict();
775
+ const sequenceSummarySchema = z
776
+ .object({
777
+ scenarioId: z.string(),
778
+ method: z.enum(['exact_state_distribution', 'bounded_beam', 'seeded_monte_carlo']),
779
+ steps: z.number().int().nonnegative(),
780
+ samples: z.number().int().positive().optional(),
781
+ seed: z.number().int().optional(),
782
+ rng: z.literal('mulberry32').optional(),
783
+ stoppingRule: z.literal('fixed_sample_budget').optional(),
784
+ confidenceLevel: z.number().min(0.5).max(0.9999).optional(),
785
+ terminalProbability: z.number().min(0).max(1),
786
+ stateCount: z.number().int().nonnegative(),
787
+ omittedProbability: z.number().min(0).max(1).optional(),
788
+ candidates: z.array(z
789
+ .object({
790
+ id: z.string(),
791
+ nextSelectionProbability: z.number().min(0).max(1),
792
+ expectedSelections: z.number().nonnegative(),
793
+ everSelectedProbability: z.number().min(0).max(1),
794
+ starvationProbability: z.number().min(0).max(1),
795
+ expectedFirstSelectionDay: z.number().nonnegative().optional(),
796
+ countDistribution: z.array(z
797
+ .object({
798
+ count: z.number().int().nonnegative(),
799
+ probability: z.number().min(0).max(1),
800
+ })
801
+ .strict()),
802
+ })
803
+ .strict()),
804
+ categories: z.array(z
805
+ .object({
806
+ id: z.string(),
807
+ nextSelectionProbability: z.number().min(0).max(1),
808
+ expectedSelections: z.number().nonnegative(),
809
+ everSelectedProbability: z.number().min(0).max(1),
810
+ starvationProbability: z.number().min(0).max(1),
811
+ expectedFirstSelectionDay: z.number().nonnegative().optional(),
812
+ })
813
+ .strict()),
814
+ topPaths: z.array(z
815
+ .object({
816
+ candidateIds: z.array(z.string()),
817
+ probability: z.number().min(0).max(1),
818
+ terminal: z.boolean(),
819
+ endDay: z.number().nonnegative(),
820
+ })
821
+ .strict()),
822
+ timeline: z.array(z
823
+ .object({
824
+ step: z.number().int().nonnegative(),
825
+ day: z.number().nonnegative(),
826
+ leadingCandidate: z.string().optional(),
827
+ terminalProbability: z.number().min(0).max(1),
828
+ })
829
+ .strict()),
830
+ })
831
+ .strict();
832
+ const comparisonSummarySchema = z
833
+ .object({
834
+ beforeAnalysisId: z.string(),
835
+ afterAnalysisId: z.string(),
836
+ scenarioChanges: z.array(z
837
+ .object({
838
+ scenarioId: z.string(),
839
+ candidateId: z.string(),
840
+ rawDelta: z.number().optional(),
841
+ probabilityDelta: z.number().optional(),
842
+ timingDeltaDays: z.number().optional(),
843
+ cumulativeChanceDelta: z.number().optional(),
844
+ rankDelta: z.number().int().optional(),
845
+ eligibilityChange: z
846
+ .object({
847
+ before: z.enum(['true', 'false', 'unresolved']),
848
+ after: z.enum(['true', 'false', 'unresolved']),
849
+ })
850
+ .strict()
851
+ .optional(),
852
+ unresolvedDelta: z.number().int(),
853
+ attribution: z.array(z.string()),
854
+ changedAstPaths: z.array(z
855
+ .object({
856
+ change: z.enum(['added', 'removed']),
857
+ path: z.string(),
858
+ astPath: z.array(z.string()),
859
+ })
860
+ .strict()),
861
+ })
862
+ .strict()),
863
+ assumptionsChanged: z.boolean(),
864
+ adapterChanged: z.boolean(),
865
+ regressions: z.array(z
866
+ .object({
867
+ code: z.string(),
868
+ message: z.string(),
869
+ scenarioId: z.string().optional(),
870
+ candidateId: z.string().optional(),
871
+ })
872
+ .strict()),
873
+ })
874
+ .strict();
875
+ export const probabilityAnalysisResultSchema = z
876
+ .object({
877
+ schemaVersion: z.literal('1.0'),
878
+ status: z.enum(['complete', 'partial', 'blocked', 'cancelled', 'stale']),
879
+ operation: z.enum([
880
+ 'inspect',
881
+ 'evaluate',
882
+ 'sweep',
883
+ 'simulate',
884
+ 'sequence',
885
+ 'compare',
886
+ 'render',
887
+ ]),
888
+ analysisId: z.string(),
889
+ metadata: probabilityMetadataSchema,
890
+ adapter: adapterDescriptorSchema,
891
+ scenarios: z.array(scenarioAnalysisSchema),
892
+ prevalenceAggregate: z
893
+ .object({
894
+ prevalenceTotal: z.number().nonnegative(),
895
+ candidates: z.array(z
896
+ .object({
897
+ id: z.string(),
898
+ eligibilityPrevalence: z.number().min(0).max(1),
899
+ weightedConditionalProbability: z.number().min(0).max(1).optional(),
900
+ })
901
+ .strict()),
902
+ })
903
+ .strict()
904
+ .optional(),
905
+ diagnostics: z.array(diagnosticSchema),
906
+ unresolved: z.array(probabilityUnresolvedSchema),
907
+ sweep: z
908
+ .object({
909
+ points: z.array(sweepPointSchema),
910
+ rankReversals: z.array(z
911
+ .object({
912
+ scenarioId: z.string(),
913
+ path: z.string(),
914
+ between: z.tuple([z.number(), z.number()]),
915
+ beforeLeader: z.string(),
916
+ afterLeader: z.string(),
917
+ })
918
+ .strict()),
919
+ breakpoints: z.array(sweepPointSchema),
920
+ localElasticities: z.array(localElasticitySchema),
921
+ pairwiseInteractions: z.array(pairwiseInteractionSchema),
922
+ })
923
+ .strict()
924
+ .optional(),
925
+ simulation: z.array(simulationSummarySchema).optional(),
926
+ sequence: sequenceSummarySchema.optional(),
927
+ comparison: comparisonSummarySchema.optional(),
928
+ resources: z.array(artifactLinkSchema),
929
+ })
930
+ .strict();