@idlekit/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/index.d.ts +12 -0
  2. package/dist/index.js +12 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/scenario/compile.d.ts +25 -0
  5. package/dist/scenario/compile.js +60 -0
  6. package/dist/scenario/compile.js.map +1 -1
  7. package/dist/scenario/scenarioContract.d.ts +152 -0
  8. package/dist/scenario/scenarioContract.js +2815 -0
  9. package/dist/scenario/scenarioContract.js.map +1 -0
  10. package/dist/sim/analysis/analyzers.d.ts +245 -0
  11. package/dist/sim/analysis/analyzers.js +1076 -0
  12. package/dist/sim/analysis/analyzers.js.map +1 -0
  13. package/dist/sim/analysis/contracts.d.ts +147 -0
  14. package/dist/sim/analysis/contracts.js +1230 -0
  15. package/dist/sim/analysis/contracts.js.map +1 -0
  16. package/dist/sim/analysis/distribution.d.ts +199 -0
  17. package/dist/sim/analysis/distribution.js +1246 -0
  18. package/dist/sim/analysis/distribution.js.map +1 -0
  19. package/dist/sim/analysis/eta.d.ts +118 -0
  20. package/dist/sim/analysis/eta.js +968 -70
  21. package/dist/sim/analysis/eta.js.map +1 -1
  22. package/dist/sim/analysis/experiment.d.ts +300 -0
  23. package/dist/sim/analysis/experiment.js +2206 -0
  24. package/dist/sim/analysis/experiment.js.map +1 -0
  25. package/dist/sim/analysis/growth.d.ts +2 -0
  26. package/dist/sim/analysis/growth.js +5 -1
  27. package/dist/sim/analysis/growth.js.map +1 -1
  28. package/dist/sim/analysis/growthFit.d.ts +183 -0
  29. package/dist/sim/analysis/growthFit.js +1798 -0
  30. package/dist/sim/analysis/growthFit.js.map +1 -0
  31. package/dist/sim/analysis/inspect.d.ts +236 -0
  32. package/dist/sim/analysis/inspect.js +388 -0
  33. package/dist/sim/analysis/inspect.js.map +1 -0
  34. package/dist/sim/analysis/metrics.d.ts +91 -0
  35. package/dist/sim/analysis/metrics.js +266 -0
  36. package/dist/sim/analysis/metrics.js.map +1 -0
  37. package/dist/sim/analysis/prestigeCycle.d.ts +6 -0
  38. package/dist/sim/analysis/prestigeCycle.js +10 -1
  39. package/dist/sim/analysis/prestigeCycle.js.map +1 -1
  40. package/dist/sim/analysis/registry.d.ts +41 -0
  41. package/dist/sim/analysis/registry.js +54 -0
  42. package/dist/sim/analysis/registry.js.map +1 -0
  43. package/dist/sim/analysis/resetActions.d.ts +7 -0
  44. package/dist/sim/analysis/resetActions.js +45 -0
  45. package/dist/sim/analysis/resetActions.js.map +1 -0
  46. package/dist/sim/analysis/resetCycle.d.ts +175 -0
  47. package/dist/sim/analysis/resetCycle.js +1009 -0
  48. package/dist/sim/analysis/resetCycle.js.map +1 -0
  49. package/dist/sim/analysis/resetOptimization.d.ts +198 -0
  50. package/dist/sim/analysis/resetOptimization.js +2187 -0
  51. package/dist/sim/analysis/resetOptimization.js.map +1 -0
  52. package/dist/sim/analysis/sensitivity.d.ts +73 -0
  53. package/dist/sim/analysis/sensitivity.js +160 -0
  54. package/dist/sim/analysis/sensitivity.js.map +1 -0
  55. package/dist/sim/monteCarlo.d.ts +12 -0
  56. package/dist/sim/monteCarlo.js +33 -4
  57. package/dist/sim/monteCarlo.js.map +1 -1
  58. package/dist/sim/observation.d.ts +22 -0
  59. package/dist/sim/observation.js +33 -0
  60. package/dist/sim/observation.js.map +1 -1
  61. package/dist/sim/offline.js +9 -2
  62. package/dist/sim/offline.js.map +1 -1
  63. package/dist/sim/random.d.ts +15 -0
  64. package/dist/sim/random.js +28 -0
  65. package/dist/sim/random.js.map +1 -1
  66. package/dist/sim/session.d.ts +7 -0
  67. package/dist/sim/session.js +13 -0
  68. package/dist/sim/session.js.map +1 -1
  69. package/dist/sim/simulator.js +7 -1
  70. package/dist/sim/simulator.js.map +1 -1
  71. package/dist/sim/step.d.ts +67 -1
  72. package/dist/sim/step.js +100 -18
  73. package/dist/sim/step.js.map +1 -1
  74. package/dist/sim/strategy/opt/freshRun.d.ts +3 -0
  75. package/dist/sim/strategy/opt/freshRun.js +6 -0
  76. package/dist/sim/strategy/opt/freshRun.js.map +1 -0
  77. package/dist/sim/strategy/opt/runner.js +2 -2
  78. package/dist/sim/strategy/opt/runner.js.map +1 -1
  79. package/dist/sim/strategy/planner.d.ts +26 -0
  80. package/dist/sim/strategy/planner.js +30 -0
  81. package/dist/sim/strategy/planner.js.map +1 -1
  82. package/package.json +2 -2
@@ -0,0 +1,2206 @@
1
+ import * as _validateReport_1 from "typia/lib/internal/_validateReport";
2
+ import * as _createStandardSchema_1 from "typia/lib/internal/_createStandardSchema";
3
+ import typia, {} from "typia";
4
+ import { compileScenario } from "../../scenario/compile";
5
+ import { canonicalJson, sha256Text } from "../../scenario/scenarioContract";
6
+ import { createRunFactory } from "../runFactory";
7
+ import { runScenario } from "../simulator";
8
+ import { runElapsedSec } from "../types";
9
+ import { assignmentFitsParamSpace, elasticityFromChange, localElasticity, relativeParameterChange, paretoFrontBy, } from "./sensitivity";
10
+ /**
11
+ * Experiment spec v1.
12
+ * Separate from ScenarioV1 and from strategy-only TuneSpecV1.
13
+ * The first method is a fixed candidate sweep plus one-at-a-time local sensitivity.
14
+ */
15
+ const EXPERIMENT_ALGORITHM_VERSION = "experiment-oat-v1";
16
+ const EXPERIMENT_ESTIMATOR_VERSION = "one-at-a-time-local-v1";
17
+ const EXPERIMENT_SCHEMA_ID = "https://idlekit.dev/schemas/experiment.v1.schema.json";
18
+ const EXPERIMENT_METHOD = "fixed-sweep-and-one-at-a-time";
19
+ /**
20
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-tune-signature TuneSpec stays strategy-only. The experiment spec is a separate object.
21
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-tune-signature #365035c Re-read the section: strategy tune still scores seeds 7 and 11 as 9, and the experiment spec is not a TuneSpec.
22
+ */
23
+ export const experimentSeparateSpec = "experiment-spec-not-tunespec";
24
+ /**
25
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-registered-setter Assignment uses a registered identifier path and the model or strategy schema. JSONPath, prototype, and eval paths are refused before a run.
26
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-registered-setter #847f7d0 Re-read the section: a prototype path, an out-of-range integer, a non-finite choice value, a non-finite space bound, a schema-invalid rate, a stripped path, overlapping paths, a session dimension, a repeated seed, a non-finite limit, and a repeated constraint id throw before model create.
27
+ */
28
+ export const experimentRegisteredSetter = "registered-key-not-jsonpath";
29
+ /**
30
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-local-elasticity Constant income reaches T/p. Local elasticity is signed and is not a Sobol or global index.
31
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-local-elasticity #bd1aa44 Re-read the section: rate 2 reaches in 5 with elasticity -0.5, rate 0.5 reaches in 20 with elasticity -2, maxPicks stays flat, and the conformance relation checks -p/p' for divisor rates.
32
+ */
33
+ export const experimentLocalOat = "one-at-a-time-local-not-sobol";
34
+ /**
35
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-candidate-order Candidate order does not change the report, and the input scenario object is not written.
36
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-candidate-order #7cede33 Re-read the section: reversed candidate ids keep the same effective hash, the scenario JSON is unchanged, the one compile per experiment brings create to 6, and elasticity rows follow the sorted dimension ids.
37
+ */
38
+ export const experimentCandidateOrder = "sorted-candidates-unmutated-scenario";
39
+ /**
40
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-censored-horizon A goal missed inside the horizon stays censored. That time is not the horizon and is not differentiated.
41
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-censored-horizon #befcebb Re-read the section: horizon 9 with target 10 leaves the reach time null, not 9, the reach time is read on the way while the run continues to the horizon, and no scenario stop condition or step budget ends it early.
42
+ */
43
+ export const experimentCensoredHorizon = "censored-time-not-horizon";
44
+ /**
45
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-pareto-unknown Pareto keeps per-metric tradeoffs. An unknown objective is uncompared, not a win.
46
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-pareto-unknown #440698a Re-read the section: A, B, and C are non-dominated, D is dominated, E is uncompared, money is compared pairwise in the effective engine, and the elasticity stays in that engine until it is text.
47
+ */
48
+ export const experimentParetoUnknown = "unknown-is-not-superior";
49
+ /**
50
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-task-descriptor Each task records its id, parameter JSON, logical seed, and engine and model versions. It does not store a function.
51
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-task-descriptor #10a3f42 Re-read the section: the income candidate changes incomeRate and the policy candidate changes maxPicks on the same seed, both from the raw scenario params applied in dimension id order, and a string probe id is JSON text.
52
+ */
53
+ export const experimentTaskDescriptor = "task-id-params-seed-version";
54
+ const WARNINGS = [
55
+ "one-at-a-time local sensitivity is not global sensitivity and is not a Sobol index",
56
+ "a censored reach time is not the horizon and is not differentiated",
57
+ "an unknown or censored objective is not a superior Pareto result",
58
+ "constraint violations are listed per metric and are not a weighted score",
59
+ ];
60
+ export function experimentSchemaId() {
61
+ return EXPERIMENT_SCHEMA_ID;
62
+ }
63
+ export function experimentAlgorithmVersion() {
64
+ return EXPERIMENT_ALGORITHM_VERSION;
65
+ }
66
+ export function experimentEstimatorVersion() {
67
+ return EXPERIMENT_ESTIMATOR_VERSION;
68
+ }
69
+ export class ExperimentRejected extends Error {
70
+ constructor(message) {
71
+ super(message);
72
+ this.name = "ExperimentRejected";
73
+ }
74
+ }
75
+ /*
76
+ * typia runs on first call, not at module load. A plugin that the bundled CLI loads
77
+ * imports core source without the transform, and a module-load call would throw there.
78
+ */
79
+ let validateExperimentArtifactValidator;
80
+ /**
81
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-task-descriptor The generated validator accepts this artifact.
82
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-task-descriptor #10a3f42 Re-read the section: the wire object is experiment v1 and the method is fixed-sweep-and-one-at-a-time.
83
+ */
84
+ export const validateExperimentArtifact = (input) => (validateExperimentArtifactValidator ??= (() => {
85
+ const _ip0 = input => 1 === input["schemaVersion"];
86
+ const _ip1 = input => "idlekit.experiment.v1" === input["contract"];
87
+ const _ip2 = input => "one-at-a-time-local-v1" === input["estimatorVersion"];
88
+ const _ip3 = input => "experiment-oat-v1" === input["algorithmVersion"];
89
+ const _ip4 = input => "fixed-sweep-and-one-at-a-time" === input["method"];
90
+ const _ip5 = input => false === input["globalSensitivity"];
91
+ const _ip6 = input => false === input["sobol"];
92
+ const _ip7 = input => "object" === typeof input["scenario"] && null !== input["scenario"] && _io1(input["scenario"]);
93
+ const _ip8 = input => "string" === typeof input["originalSpecHash"];
94
+ const _ip9 = input => "string" === typeof input["effectiveSpecHash"];
95
+ const _ip10 = input => "string" === typeof input["effectiveRunHash"];
96
+ const _ip11 = input => "object" === typeof input["budget"] && null !== input["budget"] && _io2(input["budget"]);
97
+ const _ip12 = input => Array.isArray(input["tasks"]) && input["tasks"].every(elem => "object" === typeof elem && null !== elem && _io3(elem));
98
+ const _ip13 = input => Array.isArray(input["candidates"]) && input["candidates"].every(elem => "object" === typeof elem && null !== elem && _io4(elem));
99
+ const _ip14 = input => Array.isArray(input["elasticities"]) && input["elasticities"].every(elem => "object" === typeof elem && null !== elem && _io6(elem));
100
+ const _ip15 = input => "object" === typeof input["pareto"] && null !== input["pareto"] && _io7(input["pareto"]);
101
+ const _ip16 = input => Array.isArray(input["warnings"]) && input["warnings"].every(elem => "string" === typeof elem);
102
+ const _ip17 = input => "string" === typeof input["taskId"];
103
+ const _ip18 = input => "string" === typeof input["candidateId"];
104
+ const _ip19 = input => "number" === typeof input["logicalSeed"];
105
+ const _ip20 = input => "string" === typeof input["engineId"];
106
+ const _ip21 = input => "string" === typeof input["engineVersion"];
107
+ const _ip22 = input => "string" === typeof input["modelId"];
108
+ const _ip23 = input => "number" === typeof input["modelVersion"];
109
+ const _ip24 = input => null === input["strategyId"] || "string" === typeof input["strategyId"];
110
+ const _ip25 = input => "string" === typeof input["modelParams"];
111
+ const _ip26 = input => "string" === typeof input["strategyParams"];
112
+ const _ip27 = input => "string" === typeof input["sessionPattern"];
113
+ const _ip28 = input => "number" === typeof input["sessionDays"];
114
+ const _ip29 = input => "number" === typeof input["horizonSec"];
115
+ const _ip30 = input => "number" === typeof input["stepSec"];
116
+ const _ip31 = input => "endMoney" === input["metricId"] || "timeToMoney" === input["metricId"] || "varBenefit" === input["metricId"];
117
+ const _ip32 = input => "censored" === input["status"] || "failed" === input["status"] || "observed" === input["status"] || "rejected" === input["status"];
118
+ const _ip33 = input => null === input["value"] || "string" === typeof input["value"];
119
+ const _ve0 = "Readonly<{ schemaVersion: 1; contract: \"idlekit.experiment.v1\"; estimatorVersion: \"one-at-a-time-local-v1\"; algorithmVersion: \"experiment-oat-v1\"; method: \"fixed-sweep-and-one-at-a-time\"; ... 11 more ...; warnings: readonly string[]; }>";
120
+ const _vp0 = (input, _path, _exceptionable = true) => 1 === input["schemaVersion"] || _report(_exceptionable, {
121
+ path: _path + ".schemaVersion",
122
+ expected: "1",
123
+ value: input["schemaVersion"]
124
+ });
125
+ const _vp1 = (input, _path, _exceptionable = true) => "idlekit.experiment.v1" === input["contract"] || _report(_exceptionable, {
126
+ path: _path + ".contract",
127
+ expected: "\"idlekit.experiment.v1\"",
128
+ value: input["contract"]
129
+ });
130
+ const _vp2 = (input, _path, _exceptionable = true) => "one-at-a-time-local-v1" === input["estimatorVersion"] || _report(_exceptionable, {
131
+ path: _path + ".estimatorVersion",
132
+ expected: "\"one-at-a-time-local-v1\"",
133
+ value: input["estimatorVersion"]
134
+ });
135
+ const _vp3 = (input, _path, _exceptionable = true) => "experiment-oat-v1" === input["algorithmVersion"] || _report(_exceptionable, {
136
+ path: _path + ".algorithmVersion",
137
+ expected: "\"experiment-oat-v1\"",
138
+ value: input["algorithmVersion"]
139
+ });
140
+ const _vp4 = (input, _path, _exceptionable = true) => "fixed-sweep-and-one-at-a-time" === input["method"] || _report(_exceptionable, {
141
+ path: _path + ".method",
142
+ expected: "\"fixed-sweep-and-one-at-a-time\"",
143
+ value: input["method"]
144
+ });
145
+ const _vp5 = (input, _path, _exceptionable = true) => false === input["globalSensitivity"] || _report(_exceptionable, {
146
+ path: _path + ".globalSensitivity",
147
+ expected: "false",
148
+ value: input["globalSensitivity"]
149
+ });
150
+ const _vp6 = (input, _path, _exceptionable = true) => false === input["sobol"] || _report(_exceptionable, {
151
+ path: _path + ".sobol",
152
+ expected: "false",
153
+ value: input["sobol"]
154
+ });
155
+ const _vp7 = (input, _path, _exceptionable = true) => ("object" === typeof input["scenario"] && null !== input["scenario"] || _report(_exceptionable, {
156
+ path: _path + ".scenario",
157
+ expected: "Readonly<{ ref: string; hash: string; }>",
158
+ value: input["scenario"]
159
+ })) && _vo1(input["scenario"], _path + ".scenario", true && _exceptionable) || _report(_exceptionable, {
160
+ path: _path + ".scenario",
161
+ expected: "Readonly<{ ref: string; hash: string; }>",
162
+ value: input["scenario"]
163
+ });
164
+ const _vp8 = (input, _path, _exceptionable = true) => "string" === typeof input["originalSpecHash"] || _report(_exceptionable, {
165
+ path: _path + ".originalSpecHash",
166
+ expected: "string",
167
+ value: input["originalSpecHash"]
168
+ });
169
+ const _vp9 = (input, _path, _exceptionable = true) => "string" === typeof input["effectiveSpecHash"] || _report(_exceptionable, {
170
+ path: _path + ".effectiveSpecHash",
171
+ expected: "string",
172
+ value: input["effectiveSpecHash"]
173
+ });
174
+ const _vp10 = (input, _path, _exceptionable = true) => "string" === typeof input["effectiveRunHash"] || _report(_exceptionable, {
175
+ path: _path + ".effectiveRunHash",
176
+ expected: "string",
177
+ value: input["effectiveRunHash"]
178
+ });
179
+ const _vp11 = (input, _path, _exceptionable = true) => ("object" === typeof input["budget"] && null !== input["budget"] || _report(_exceptionable, {
180
+ path: _path + ".budget",
181
+ expected: "Readonly<{ candidates: number; seeds: number; usedCandidates: number; usedSeeds: number; }>",
182
+ value: input["budget"]
183
+ })) && _vo2(input["budget"], _path + ".budget", true && _exceptionable) || _report(_exceptionable, {
184
+ path: _path + ".budget",
185
+ expected: "Readonly<{ candidates: number; seeds: number; usedCandidates: number; usedSeeds: number; }>",
186
+ value: input["budget"]
187
+ });
188
+ const _vp12 = (input, _path, _exceptionable = true) => (Array.isArray(input["tasks"]) || _report(_exceptionable, {
189
+ path: _path + ".tasks",
190
+ expected: _ve2,
191
+ value: input["tasks"]
192
+ })) && input["tasks"].map((elem, _index9) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
193
+ path: _path + ".tasks[" + _index9 + "]",
194
+ expected: _ve1,
195
+ value: elem
196
+ })) && _vo3(elem, _path + ".tasks[" + _index9 + "]", true && _exceptionable) || _report(_exceptionable, {
197
+ path: _path + ".tasks[" + _index9 + "]",
198
+ expected: _ve1,
199
+ value: elem
200
+ })).every(flag => flag) || _report(_exceptionable, {
201
+ path: _path + ".tasks",
202
+ expected: _ve2,
203
+ value: input["tasks"]
204
+ });
205
+ const _ve1 = "Readonly<{ taskId: string; candidateId: string; logicalSeed: number; engineId: string; engineVersion: string; modelId: string; modelVersion: number; strategyId: string | null; modelParams: string; ... 7 more ...; value: string | null; }>";
206
+ const _ve2 = "ReadonlyArray<Readonly<{ taskId: string; candidateId: string; logicalSeed: number; engineId: string; engineVersion: string; modelId: string; modelVersion: number; strategyId: string | null; modelParams: string; ... 7 more ...; value: string | null; }>>";
207
+ const _vp13 = (input, _path, _exceptionable = true) => (Array.isArray(input["candidates"]) || _report(_exceptionable, {
208
+ path: _path + ".candidates",
209
+ expected: _ve4,
210
+ value: input["candidates"]
211
+ })) && input["candidates"].map((elem, _index10) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
212
+ path: _path + ".candidates[" + _index10 + "]",
213
+ expected: _ve3,
214
+ value: elem
215
+ })) && _vo4(elem, _path + ".candidates[" + _index10 + "]", true && _exceptionable) || _report(_exceptionable, {
216
+ path: _path + ".candidates[" + _index10 + "]",
217
+ expected: _ve3,
218
+ value: elem
219
+ })).every(flag => flag) || _report(_exceptionable, {
220
+ path: _path + ".candidates",
221
+ expected: _ve4,
222
+ value: input["candidates"]
223
+ });
224
+ const _ve3 = "Readonly<{ id: string; metrics: readonly Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>[]; constraintViolations: readonly string[]; }>";
225
+ const _ve4 = "ReadonlyArray<Readonly<{ id: string; metrics: readonly Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>[]; constraintViolations: readonly string[]; }>>";
226
+ const _vp14 = (input, _path, _exceptionable = true) => (Array.isArray(input["elasticities"]) || _report(_exceptionable, {
227
+ path: _path + ".elasticities",
228
+ expected: _ve6,
229
+ value: input["elasticities"]
230
+ })) && input["elasticities"].map((elem, _index11) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
231
+ path: _path + ".elasticities[" + _index11 + "]",
232
+ expected: _ve5,
233
+ value: elem
234
+ })) && _vo6(elem, _path + ".elasticities[" + _index11 + "]", true && _exceptionable) || _report(_exceptionable, {
235
+ path: _path + ".elasticities[" + _index11 + "]",
236
+ expected: _ve5,
237
+ value: elem
238
+ })).every(flag => flag) || _report(_exceptionable, {
239
+ path: _path + ".elasticities",
240
+ expected: _ve6,
241
+ value: input["elasticities"]
242
+ });
243
+ const _ve5 = "Readonly<{ dimensionId: string; candidateId: string; metricId: ExperimentMetricId; method: \"one-at-a-time-local\"; methodVersion: 1; globalSensitivity: false; sobol: false; status: \"observed\" | \"unavailable\"; elasticity: string | null; absoluteChange: string | null; direction: \"flat\" | ... 2 more ... | \"unavailable\";...";
244
+ const _ve6 = "ReadonlyArray<Readonly<{ dimensionId: string; candidateId: string; metricId: ExperimentMetricId; method: \"one-at-a-time-local\"; methodVersion: 1; globalSensitivity: false; sobol: false; status: \"observed\" | \"unavailable\"; elasticity: string | null; absoluteChange: string | null; direction: \"flat\" | ... 2 more ... | \"unavailable\";...>";
245
+ const _vp15 = (input, _path, _exceptionable = true) => ("object" === typeof input["pareto"] && null !== input["pareto"] || _report(_exceptionable, {
246
+ path: _path + ".pareto",
247
+ expected: "Readonly<{ sense: \"per-metric\"; nonDominated: readonly string[]; uncompared: readonly string[]; }>",
248
+ value: input["pareto"]
249
+ })) && _vo7(input["pareto"], _path + ".pareto", true && _exceptionable) || _report(_exceptionable, {
250
+ path: _path + ".pareto",
251
+ expected: "Readonly<{ sense: \"per-metric\"; nonDominated: readonly string[]; uncompared: readonly string[]; }>",
252
+ value: input["pareto"]
253
+ });
254
+ const _vp16 = (input, _path, _exceptionable = true) => (Array.isArray(input["warnings"]) || _report(_exceptionable, {
255
+ path: _path + ".warnings",
256
+ expected: "ReadonlyArray<string>",
257
+ value: input["warnings"]
258
+ })) && input["warnings"].map((elem, _index12) => "string" === typeof elem || _report(_exceptionable, {
259
+ path: _path + ".warnings[" + _index12 + "]",
260
+ expected: "string",
261
+ value: elem
262
+ })).every(flag => flag) || _report(_exceptionable, {
263
+ path: _path + ".warnings",
264
+ expected: "ReadonlyArray<string>",
265
+ value: input["warnings"]
266
+ });
267
+ const _vp17 = (input, _path, _exceptionable = true) => "string" === typeof input["taskId"] || _report(_exceptionable, {
268
+ path: _path + ".taskId",
269
+ expected: "string",
270
+ value: input["taskId"]
271
+ });
272
+ const _vp18 = (input, _path, _exceptionable = true) => "string" === typeof input["candidateId"] || _report(_exceptionable, {
273
+ path: _path + ".candidateId",
274
+ expected: "string",
275
+ value: input["candidateId"]
276
+ });
277
+ const _vp19 = (input, _path, _exceptionable = true) => "number" === typeof input["logicalSeed"] || _report(_exceptionable, {
278
+ path: _path + ".logicalSeed",
279
+ expected: "number",
280
+ value: input["logicalSeed"]
281
+ });
282
+ const _vp20 = (input, _path, _exceptionable = true) => "string" === typeof input["engineId"] || _report(_exceptionable, {
283
+ path: _path + ".engineId",
284
+ expected: "string",
285
+ value: input["engineId"]
286
+ });
287
+ const _vp21 = (input, _path, _exceptionable = true) => "string" === typeof input["engineVersion"] || _report(_exceptionable, {
288
+ path: _path + ".engineVersion",
289
+ expected: "string",
290
+ value: input["engineVersion"]
291
+ });
292
+ const _vp22 = (input, _path, _exceptionable = true) => "string" === typeof input["modelId"] || _report(_exceptionable, {
293
+ path: _path + ".modelId",
294
+ expected: "string",
295
+ value: input["modelId"]
296
+ });
297
+ const _vp23 = (input, _path, _exceptionable = true) => "number" === typeof input["modelVersion"] || _report(_exceptionable, {
298
+ path: _path + ".modelVersion",
299
+ expected: "number",
300
+ value: input["modelVersion"]
301
+ });
302
+ const _vp24 = (input, _path, _exceptionable = true) => null === input["strategyId"] || "string" === typeof input["strategyId"] || _report(_exceptionable, {
303
+ path: _path + ".strategyId",
304
+ expected: "(null | string)",
305
+ value: input["strategyId"]
306
+ });
307
+ const _vp25 = (input, _path, _exceptionable = true) => "string" === typeof input["modelParams"] || _report(_exceptionable, {
308
+ path: _path + ".modelParams",
309
+ expected: "string",
310
+ value: input["modelParams"]
311
+ });
312
+ const _vp26 = (input, _path, _exceptionable = true) => "string" === typeof input["strategyParams"] || _report(_exceptionable, {
313
+ path: _path + ".strategyParams",
314
+ expected: "string",
315
+ value: input["strategyParams"]
316
+ });
317
+ const _vp27 = (input, _path, _exceptionable = true) => "string" === typeof input["sessionPattern"] || _report(_exceptionable, {
318
+ path: _path + ".sessionPattern",
319
+ expected: "string",
320
+ value: input["sessionPattern"]
321
+ });
322
+ const _vp28 = (input, _path, _exceptionable = true) => "number" === typeof input["sessionDays"] || _report(_exceptionable, {
323
+ path: _path + ".sessionDays",
324
+ expected: "number",
325
+ value: input["sessionDays"]
326
+ });
327
+ const _vp29 = (input, _path, _exceptionable = true) => "number" === typeof input["horizonSec"] || _report(_exceptionable, {
328
+ path: _path + ".horizonSec",
329
+ expected: "number",
330
+ value: input["horizonSec"]
331
+ });
332
+ const _vp30 = (input, _path, _exceptionable = true) => "number" === typeof input["stepSec"] || _report(_exceptionable, {
333
+ path: _path + ".stepSec",
334
+ expected: "number",
335
+ value: input["stepSec"]
336
+ });
337
+ const _vp31 = (input, _path, _exceptionable = true) => "endMoney" === input["metricId"] || "timeToMoney" === input["metricId"] || "varBenefit" === input["metricId"] || _report(_exceptionable, {
338
+ path: _path + ".metricId",
339
+ expected: "(\"endMoney\" | \"timeToMoney\" | \"varBenefit\")",
340
+ value: input["metricId"]
341
+ });
342
+ const _vp32 = (input, _path, _exceptionable = true) => "censored" === input["status"] || "failed" === input["status"] || "observed" === input["status"] || "rejected" === input["status"] || _report(_exceptionable, {
343
+ path: _path + ".status",
344
+ expected: "(\"censored\" | \"failed\" | \"observed\" | \"rejected\")",
345
+ value: input["status"]
346
+ });
347
+ const _vp33 = (input, _path, _exceptionable = true) => null === input["value"] || "string" === typeof input["value"] || _report(_exceptionable, {
348
+ path: _path + ".value",
349
+ expected: "(null | string)",
350
+ value: input["value"]
351
+ });
352
+ const _io0 = input => _ip0(input) && _ip1(input) && _ip2(input) && _ip3(input) && _ip4(input) && _ip5(input) && _ip6(input) && _ip7(input) && _ip8(input) && _ip9(input) && _ip10(input) && _ip11(input) && _ip12(input) && _ip13(input) && _ip14(input) && _ip15(input) && _ip16(input);
353
+ const _io1 = input => "string" === typeof input.ref && "string" === typeof input.hash;
354
+ const _io2 = input => "number" === typeof input.candidates && "number" === typeof input.seeds && "number" === typeof input.usedCandidates && "number" === typeof input.usedSeeds;
355
+ const _io3 = input => _ip17(input) && _ip18(input) && _ip19(input) && _ip20(input) && _ip21(input) && _ip22(input) && _ip23(input) && _ip24(input) && _ip25(input) && _ip26(input) && _ip27(input) && _ip28(input) && _ip29(input) && _ip30(input) && _ip31(input) && _ip32(input) && _ip33(input);
356
+ const _io4 = input => "string" === typeof input.id && (Array.isArray(input.metrics) && input.metrics.every(elem => "object" === typeof elem && null !== elem && _io5(elem))) && (Array.isArray(input.constraintViolations) && input.constraintViolations.every(elem => "string" === typeof elem));
357
+ const _io5 = input => _ip31(input) && _ip32(input) && _ip33(input);
358
+ const _io6 = input => "string" === typeof input.dimensionId && _ip18(input) && _ip31(input) && "one-at-a-time-local" === input.method && 1 === input.methodVersion && _ip5(input) && _ip6(input) && ("observed" === input.status || "unavailable" === input.status) && (null === input.elasticity || "string" === typeof input.elasticity) && (null === input.absoluteChange || "string" === typeof input.absoluteChange) && ("flat" === input.direction || "higher" === input.direction || "lower" === input.direction || "unavailable" === input.direction);
359
+ const _io7 = input => "per-metric" === input.sense && (Array.isArray(input.nonDominated) && input.nonDominated.every(elem => "string" === typeof elem)) && (Array.isArray(input.uncompared) && input.uncompared.every(elem => "string" === typeof elem));
360
+ const _vo0 = (input, _path, _exceptionable = true) => [_vp0(input, _path, true && _exceptionable), _vp1(input, _path, true && _exceptionable), _vp2(input, _path, true && _exceptionable), _vp3(input, _path, true && _exceptionable), _vp4(input, _path, true && _exceptionable), _vp5(input, _path, true && _exceptionable), _vp6(input, _path, true && _exceptionable), _vp7(input, _path, true && _exceptionable), _vp8(input, _path, true && _exceptionable), _vp9(input, _path, true && _exceptionable), _vp10(input, _path, true && _exceptionable), _vp11(input, _path, true && _exceptionable), _vp12(input, _path, true && _exceptionable), _vp13(input, _path, true && _exceptionable), _vp14(input, _path, true && _exceptionable), _vp15(input, _path, true && _exceptionable), _vp16(input, _path, true && _exceptionable)].every(flag => flag);
361
+ const _vo1 = (input, _path, _exceptionable = true) => ["string" === typeof input.ref || _report(_exceptionable, {
362
+ path: _path + ".ref",
363
+ expected: "string",
364
+ value: input.ref
365
+ }), "string" === typeof input.hash || _report(_exceptionable, {
366
+ path: _path + ".hash",
367
+ expected: "string",
368
+ value: input.hash
369
+ })].every(flag => flag);
370
+ const _vo2 = (input, _path, _exceptionable = true) => ["number" === typeof input.candidates || _report(_exceptionable, {
371
+ path: _path + ".candidates",
372
+ expected: "number",
373
+ value: input.candidates
374
+ }), "number" === typeof input.seeds || _report(_exceptionable, {
375
+ path: _path + ".seeds",
376
+ expected: "number",
377
+ value: input.seeds
378
+ }), "number" === typeof input.usedCandidates || _report(_exceptionable, {
379
+ path: _path + ".usedCandidates",
380
+ expected: "number",
381
+ value: input.usedCandidates
382
+ }), "number" === typeof input.usedSeeds || _report(_exceptionable, {
383
+ path: _path + ".usedSeeds",
384
+ expected: "number",
385
+ value: input.usedSeeds
386
+ })].every(flag => flag);
387
+ const _vo3 = (input, _path, _exceptionable = true) => [_vp17(input, _path, true && _exceptionable), _vp18(input, _path, true && _exceptionable), _vp19(input, _path, true && _exceptionable), _vp20(input, _path, true && _exceptionable), _vp21(input, _path, true && _exceptionable), _vp22(input, _path, true && _exceptionable), _vp23(input, _path, true && _exceptionable), _vp24(input, _path, true && _exceptionable), _vp25(input, _path, true && _exceptionable), _vp26(input, _path, true && _exceptionable), _vp27(input, _path, true && _exceptionable), _vp28(input, _path, true && _exceptionable), _vp29(input, _path, true && _exceptionable), _vp30(input, _path, true && _exceptionable), _vp31(input, _path, true && _exceptionable), _vp32(input, _path, true && _exceptionable), _vp33(input, _path, true && _exceptionable)].every(flag => flag);
388
+ const _vo4 = (input, _path, _exceptionable = true) => ["string" === typeof input.id || _report(_exceptionable, {
389
+ path: _path + ".id",
390
+ expected: "string",
391
+ value: input.id
392
+ }), (Array.isArray(input.metrics) || _report(_exceptionable, {
393
+ path: _path + ".metrics",
394
+ expected: "ReadonlyArray<Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>>",
395
+ value: input.metrics
396
+ })) && input.metrics.map((elem, _index13) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
397
+ path: _path + ".metrics[" + _index13 + "]",
398
+ expected: "Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>",
399
+ value: elem
400
+ })) && _vo5(elem, _path + ".metrics[" + _index13 + "]", true && _exceptionable) || _report(_exceptionable, {
401
+ path: _path + ".metrics[" + _index13 + "]",
402
+ expected: "Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>",
403
+ value: elem
404
+ })).every(flag => flag) || _report(_exceptionable, {
405
+ path: _path + ".metrics",
406
+ expected: "ReadonlyArray<Readonly<{ metricId: ExperimentMetricId; status: ExperimentMetricStatus; value: string | null; }>>",
407
+ value: input.metrics
408
+ }), (Array.isArray(input.constraintViolations) || _report(_exceptionable, {
409
+ path: _path + ".constraintViolations",
410
+ expected: "ReadonlyArray<string>",
411
+ value: input.constraintViolations
412
+ })) && input.constraintViolations.map((elem, _index14) => "string" === typeof elem || _report(_exceptionable, {
413
+ path: _path + ".constraintViolations[" + _index14 + "]",
414
+ expected: "string",
415
+ value: elem
416
+ })).every(flag => flag) || _report(_exceptionable, {
417
+ path: _path + ".constraintViolations",
418
+ expected: "ReadonlyArray<string>",
419
+ value: input.constraintViolations
420
+ })].every(flag => flag);
421
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp31(input, _path, true && _exceptionable), _vp32(input, _path, true && _exceptionable), _vp33(input, _path, true && _exceptionable)].every(flag => flag);
422
+ const _vo6 = (input, _path, _exceptionable = true) => ["string" === typeof input.dimensionId || _report(_exceptionable, {
423
+ path: _path + ".dimensionId",
424
+ expected: "string",
425
+ value: input.dimensionId
426
+ }), _vp18(input, _path, true && _exceptionable), _vp31(input, _path, true && _exceptionable), "one-at-a-time-local" === input.method || _report(_exceptionable, {
427
+ path: _path + ".method",
428
+ expected: "\"one-at-a-time-local\"",
429
+ value: input.method
430
+ }), 1 === input.methodVersion || _report(_exceptionable, {
431
+ path: _path + ".methodVersion",
432
+ expected: "1",
433
+ value: input.methodVersion
434
+ }), _vp5(input, _path, true && _exceptionable), _vp6(input, _path, true && _exceptionable), "observed" === input.status || "unavailable" === input.status || _report(_exceptionable, {
435
+ path: _path + ".status",
436
+ expected: "(\"observed\" | \"unavailable\")",
437
+ value: input.status
438
+ }), null === input.elasticity || "string" === typeof input.elasticity || _report(_exceptionable, {
439
+ path: _path + ".elasticity",
440
+ expected: "(null | string)",
441
+ value: input.elasticity
442
+ }), null === input.absoluteChange || "string" === typeof input.absoluteChange || _report(_exceptionable, {
443
+ path: _path + ".absoluteChange",
444
+ expected: "(null | string)",
445
+ value: input.absoluteChange
446
+ }), "flat" === input.direction || "higher" === input.direction || "lower" === input.direction || "unavailable" === input.direction || _report(_exceptionable, {
447
+ path: _path + ".direction",
448
+ expected: "(\"flat\" | \"higher\" | \"lower\" | \"unavailable\")",
449
+ value: input.direction
450
+ })].every(flag => flag);
451
+ const _vo7 = (input, _path, _exceptionable = true) => ["per-metric" === input.sense || _report(_exceptionable, {
452
+ path: _path + ".sense",
453
+ expected: "\"per-metric\"",
454
+ value: input.sense
455
+ }), (Array.isArray(input.nonDominated) || _report(_exceptionable, {
456
+ path: _path + ".nonDominated",
457
+ expected: "ReadonlyArray<string>",
458
+ value: input.nonDominated
459
+ })) && input.nonDominated.map((elem, _index15) => "string" === typeof elem || _report(_exceptionable, {
460
+ path: _path + ".nonDominated[" + _index15 + "]",
461
+ expected: "string",
462
+ value: elem
463
+ })).every(flag => flag) || _report(_exceptionable, {
464
+ path: _path + ".nonDominated",
465
+ expected: "ReadonlyArray<string>",
466
+ value: input.nonDominated
467
+ }), (Array.isArray(input.uncompared) || _report(_exceptionable, {
468
+ path: _path + ".uncompared",
469
+ expected: "ReadonlyArray<string>",
470
+ value: input.uncompared
471
+ })) && input.uncompared.map((elem, _index16) => "string" === typeof elem || _report(_exceptionable, {
472
+ path: _path + ".uncompared[" + _index16 + "]",
473
+ expected: "string",
474
+ value: elem
475
+ })).every(flag => flag) || _report(_exceptionable, {
476
+ path: _path + ".uncompared",
477
+ expected: "ReadonlyArray<string>",
478
+ value: input.uncompared
479
+ })].every(flag => flag);
480
+ const __is = input => "object" === typeof input && null !== input && _io0(input);
481
+ let errors;
482
+ let _report;
483
+ return _createStandardSchema_1._createStandardSchema(input => {
484
+ if (false === __is(input)) {
485
+ errors = [];
486
+ _report = _validateReport_1._validateReport(errors);
487
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _report(true, {
488
+ path: _path + "",
489
+ expected: _ve0,
490
+ value: input
491
+ })) && _vo0(input, _path + "", true) || _report(true, {
492
+ path: _path + "",
493
+ expected: _ve0,
494
+ value: input
495
+ }))(input, "$input", true);
496
+ const success = 0 === errors.length;
497
+ return success ? {
498
+ success,
499
+ data: input
500
+ } : {
501
+ success,
502
+ errors,
503
+ data: input
504
+ };
505
+ }
506
+ return {
507
+ success: true,
508
+ data: input
509
+ };
510
+ });
511
+ })())(input);
512
+ function buildExperimentJsonSchema() {
513
+ return {
514
+ components: {
515
+ schemas: {
516
+ "ReadonlyschemaVersion1contractidlekit_x2E_experiment_x2E_v1estimatorVersionone-at-a-time-local-v1algorithmVersionexperiment-oat-v1methodfixed-sweep-and-one-at-a-time---11more---warningsreadonlystring-x86544DB8D4D46D5D": {
517
+ type: "object",
518
+ properties: {
519
+ schemaVersion: {
520
+ "const": 1
521
+ },
522
+ contract: {
523
+ "const": "idlekit.experiment.v1"
524
+ },
525
+ estimatorVersion: {
526
+ "const": "one-at-a-time-local-v1"
527
+ },
528
+ algorithmVersion: {
529
+ "const": "experiment-oat-v1"
530
+ },
531
+ method: {
532
+ "const": "fixed-sweep-and-one-at-a-time"
533
+ },
534
+ globalSensitivity: {
535
+ "const": false
536
+ },
537
+ sobol: {
538
+ "const": false
539
+ },
540
+ scenario: {
541
+ $ref: "#/components/schemas/Readonlyrefstringhashstring"
542
+ },
543
+ originalSpecHash: {
544
+ type: "string"
545
+ },
546
+ effectiveSpecHash: {
547
+ type: "string"
548
+ },
549
+ effectiveRunHash: {
550
+ type: "string"
551
+ },
552
+ budget: {
553
+ $ref: "#/components/schemas/ReadonlycandidatesnumberseedsnumberusedCandidatesnumberusedSeedsnumber"
554
+ },
555
+ tasks: {
556
+ type: "array",
557
+ items: {
558
+ $ref: "#/components/schemas/ReadonlytaskIdstringcandidateIdstringlogicalSeednumberengineIdstringengineVersionstringmodelIdstringmodelVersionnumberstrategyIdstringnullmodelParamsstring---7more---valuestringnull"
559
+ }
560
+ },
561
+ candidates: {
562
+ type: "array",
563
+ items: {
564
+ $ref: "#/components/schemas/ReadonlyidstringmetricsreadonlyReadonlymetricIdExperimentMetricIdstatusExperimentMetricStatusvaluestringnullconstraintViolationsreadonlystring"
565
+ }
566
+ },
567
+ elasticities: {
568
+ type: "array",
569
+ items: {
570
+ $ref: "#/components/schemas/ReadonlydimensionIdstringcandidateIdstringmetricIdExperimentMetricIdmethodone-at-a-time-localmethodVersion1globalSensitivityfalsesobolfalsestatusobservedunavailableelasticitystringnullabsoluteChangestringnulldirectionflat---2more---unavailable---"
571
+ }
572
+ },
573
+ pareto: {
574
+ $ref: "#/components/schemas/Readonlysenseper-metricnonDominatedreadonlystringuncomparedreadonlystring"
575
+ },
576
+ warnings: {
577
+ type: "array",
578
+ items: {
579
+ type: "string"
580
+ }
581
+ }
582
+ },
583
+ required: [
584
+ "schemaVersion",
585
+ "contract",
586
+ "estimatorVersion",
587
+ "algorithmVersion",
588
+ "method",
589
+ "globalSensitivity",
590
+ "sobol",
591
+ "scenario",
592
+ "originalSpecHash",
593
+ "effectiveSpecHash",
594
+ "effectiveRunHash",
595
+ "budget",
596
+ "tasks",
597
+ "candidates",
598
+ "elasticities",
599
+ "pareto",
600
+ "warnings"
601
+ ],
602
+ additionalProperties: false
603
+ },
604
+ Readonlyrefstringhashstring: {
605
+ type: "object",
606
+ properties: {
607
+ ref: {
608
+ type: "string"
609
+ },
610
+ hash: {
611
+ type: "string"
612
+ }
613
+ },
614
+ required: [
615
+ "ref",
616
+ "hash"
617
+ ],
618
+ additionalProperties: false
619
+ },
620
+ ReadonlycandidatesnumberseedsnumberusedCandidatesnumberusedSeedsnumber: {
621
+ type: "object",
622
+ properties: {
623
+ candidates: {
624
+ type: "number"
625
+ },
626
+ seeds: {
627
+ type: "number"
628
+ },
629
+ usedCandidates: {
630
+ type: "number"
631
+ },
632
+ usedSeeds: {
633
+ type: "number"
634
+ }
635
+ },
636
+ required: [
637
+ "candidates",
638
+ "seeds",
639
+ "usedCandidates",
640
+ "usedSeeds"
641
+ ],
642
+ additionalProperties: false
643
+ },
644
+ "ReadonlytaskIdstringcandidateIdstringlogicalSeednumberengineIdstringengineVersionstringmodelIdstringmodelVersionnumberstrategyIdstringnullmodelParamsstring---7more---valuestringnull": {
645
+ type: "object",
646
+ properties: {
647
+ taskId: {
648
+ type: "string"
649
+ },
650
+ candidateId: {
651
+ type: "string"
652
+ },
653
+ logicalSeed: {
654
+ type: "number"
655
+ },
656
+ engineId: {
657
+ type: "string"
658
+ },
659
+ engineVersion: {
660
+ type: "string"
661
+ },
662
+ modelId: {
663
+ type: "string"
664
+ },
665
+ modelVersion: {
666
+ type: "number"
667
+ },
668
+ strategyId: {
669
+ oneOf: [
670
+ {
671
+ type: "null"
672
+ },
673
+ {
674
+ type: "string"
675
+ }
676
+ ]
677
+ },
678
+ modelParams: {
679
+ type: "string"
680
+ },
681
+ strategyParams: {
682
+ type: "string"
683
+ },
684
+ sessionPattern: {
685
+ type: "string"
686
+ },
687
+ sessionDays: {
688
+ type: "number"
689
+ },
690
+ horizonSec: {
691
+ type: "number"
692
+ },
693
+ stepSec: {
694
+ type: "number"
695
+ },
696
+ metricId: {
697
+ $ref: "#/components/schemas/ExperimentMetricId"
698
+ },
699
+ status: {
700
+ $ref: "#/components/schemas/ExperimentMetricStatus"
701
+ },
702
+ value: {
703
+ oneOf: [
704
+ {
705
+ type: "null"
706
+ },
707
+ {
708
+ type: "string"
709
+ }
710
+ ]
711
+ }
712
+ },
713
+ required: [
714
+ "taskId",
715
+ "candidateId",
716
+ "logicalSeed",
717
+ "engineId",
718
+ "engineVersion",
719
+ "modelId",
720
+ "modelVersion",
721
+ "strategyId",
722
+ "modelParams",
723
+ "strategyParams",
724
+ "sessionPattern",
725
+ "sessionDays",
726
+ "horizonSec",
727
+ "stepSec",
728
+ "metricId",
729
+ "status",
730
+ "value"
731
+ ],
732
+ additionalProperties: false
733
+ },
734
+ ExperimentMetricId: {
735
+ oneOf: [
736
+ {
737
+ "const": "endMoney"
738
+ },
739
+ {
740
+ "const": "timeToMoney"
741
+ },
742
+ {
743
+ "const": "varBenefit"
744
+ }
745
+ ]
746
+ },
747
+ ExperimentMetricStatus: {
748
+ oneOf: [
749
+ {
750
+ "const": "censored"
751
+ },
752
+ {
753
+ "const": "failed"
754
+ },
755
+ {
756
+ "const": "observed"
757
+ },
758
+ {
759
+ "const": "rejected"
760
+ }
761
+ ]
762
+ },
763
+ ReadonlyidstringmetricsreadonlyReadonlymetricIdExperimentMetricIdstatusExperimentMetricStatusvaluestringnullconstraintViolationsreadonlystring: {
764
+ type: "object",
765
+ properties: {
766
+ id: {
767
+ type: "string"
768
+ },
769
+ metrics: {
770
+ type: "array",
771
+ items: {
772
+ $ref: "#/components/schemas/ReadonlymetricIdExperimentMetricIdstatusExperimentMetricStatusvaluestringnull"
773
+ }
774
+ },
775
+ constraintViolations: {
776
+ type: "array",
777
+ items: {
778
+ type: "string"
779
+ }
780
+ }
781
+ },
782
+ required: [
783
+ "id",
784
+ "metrics",
785
+ "constraintViolations"
786
+ ],
787
+ additionalProperties: false
788
+ },
789
+ ReadonlymetricIdExperimentMetricIdstatusExperimentMetricStatusvaluestringnull: {
790
+ type: "object",
791
+ properties: {
792
+ metricId: {
793
+ $ref: "#/components/schemas/ExperimentMetricId"
794
+ },
795
+ status: {
796
+ $ref: "#/components/schemas/ExperimentMetricStatus"
797
+ },
798
+ value: {
799
+ oneOf: [
800
+ {
801
+ type: "null"
802
+ },
803
+ {
804
+ type: "string"
805
+ }
806
+ ]
807
+ }
808
+ },
809
+ required: [
810
+ "metricId",
811
+ "status",
812
+ "value"
813
+ ],
814
+ additionalProperties: false
815
+ },
816
+ "ReadonlydimensionIdstringcandidateIdstringmetricIdExperimentMetricIdmethodone-at-a-time-localmethodVersion1globalSensitivityfalsesobolfalsestatusobservedunavailableelasticitystringnullabsoluteChangestringnulldirectionflat---2more---unavailable---": {
817
+ type: "object",
818
+ properties: {
819
+ dimensionId: {
820
+ type: "string"
821
+ },
822
+ candidateId: {
823
+ type: "string"
824
+ },
825
+ metricId: {
826
+ $ref: "#/components/schemas/ExperimentMetricId"
827
+ },
828
+ method: {
829
+ "const": "one-at-a-time-local"
830
+ },
831
+ methodVersion: {
832
+ "const": 1
833
+ },
834
+ globalSensitivity: {
835
+ "const": false
836
+ },
837
+ sobol: {
838
+ "const": false
839
+ },
840
+ status: {
841
+ oneOf: [
842
+ {
843
+ "const": "observed"
844
+ },
845
+ {
846
+ "const": "unavailable"
847
+ }
848
+ ]
849
+ },
850
+ elasticity: {
851
+ oneOf: [
852
+ {
853
+ type: "null"
854
+ },
855
+ {
856
+ type: "string"
857
+ }
858
+ ]
859
+ },
860
+ absoluteChange: {
861
+ oneOf: [
862
+ {
863
+ type: "null"
864
+ },
865
+ {
866
+ type: "string"
867
+ }
868
+ ]
869
+ },
870
+ direction: {
871
+ oneOf: [
872
+ {
873
+ "const": "flat"
874
+ },
875
+ {
876
+ "const": "higher"
877
+ },
878
+ {
879
+ "const": "lower"
880
+ },
881
+ {
882
+ "const": "unavailable"
883
+ }
884
+ ]
885
+ }
886
+ },
887
+ required: [
888
+ "dimensionId",
889
+ "candidateId",
890
+ "metricId",
891
+ "method",
892
+ "methodVersion",
893
+ "globalSensitivity",
894
+ "sobol",
895
+ "status",
896
+ "elasticity",
897
+ "absoluteChange",
898
+ "direction"
899
+ ],
900
+ additionalProperties: false
901
+ },
902
+ "Readonlysenseper-metricnonDominatedreadonlystringuncomparedreadonlystring": {
903
+ type: "object",
904
+ properties: {
905
+ sense: {
906
+ "const": "per-metric"
907
+ },
908
+ nonDominated: {
909
+ type: "array",
910
+ items: {
911
+ type: "string"
912
+ }
913
+ },
914
+ uncompared: {
915
+ type: "array",
916
+ items: {
917
+ type: "string"
918
+ }
919
+ }
920
+ },
921
+ required: [
922
+ "sense",
923
+ "nonDominated",
924
+ "uncompared"
925
+ ],
926
+ additionalProperties: false
927
+ }
928
+ }
929
+ },
930
+ schema: {
931
+ type: "object",
932
+ properties: {
933
+ schemaVersion: {
934
+ "const": 1
935
+ },
936
+ contract: {
937
+ "const": "idlekit.experiment.v1"
938
+ },
939
+ estimatorVersion: {
940
+ "const": "one-at-a-time-local-v1"
941
+ },
942
+ algorithmVersion: {
943
+ "const": "experiment-oat-v1"
944
+ },
945
+ method: {
946
+ "const": "fixed-sweep-and-one-at-a-time"
947
+ },
948
+ globalSensitivity: {
949
+ "const": false
950
+ },
951
+ sobol: {
952
+ "const": false
953
+ },
954
+ scenario: {
955
+ $ref: "#/components/schemas/Readonlyrefstringhashstring"
956
+ },
957
+ originalSpecHash: {
958
+ type: "string"
959
+ },
960
+ effectiveSpecHash: {
961
+ type: "string"
962
+ },
963
+ effectiveRunHash: {
964
+ type: "string"
965
+ },
966
+ budget: {
967
+ $ref: "#/components/schemas/ReadonlycandidatesnumberseedsnumberusedCandidatesnumberusedSeedsnumber"
968
+ },
969
+ tasks: {
970
+ type: "array",
971
+ items: {
972
+ $ref: "#/components/schemas/ReadonlytaskIdstringcandidateIdstringlogicalSeednumberengineIdstringengineVersionstringmodelIdstringmodelVersionnumberstrategyIdstringnullmodelParamsstring---7more---valuestringnull"
973
+ }
974
+ },
975
+ candidates: {
976
+ type: "array",
977
+ items: {
978
+ $ref: "#/components/schemas/ReadonlyidstringmetricsreadonlyReadonlymetricIdExperimentMetricIdstatusExperimentMetricStatusvaluestringnullconstraintViolationsreadonlystring"
979
+ }
980
+ },
981
+ elasticities: {
982
+ type: "array",
983
+ items: {
984
+ $ref: "#/components/schemas/ReadonlydimensionIdstringcandidateIdstringmetricIdExperimentMetricIdmethodone-at-a-time-localmethodVersion1globalSensitivityfalsesobolfalsestatusobservedunavailableelasticitystringnullabsoluteChangestringnulldirectionflat---2more---unavailable---"
985
+ }
986
+ },
987
+ pareto: {
988
+ $ref: "#/components/schemas/Readonlysenseper-metricnonDominatedreadonlystringuncomparedreadonlystring"
989
+ },
990
+ warnings: {
991
+ type: "array",
992
+ items: {
993
+ type: "string"
994
+ }
995
+ }
996
+ },
997
+ required: [
998
+ "schemaVersion",
999
+ "contract",
1000
+ "estimatorVersion",
1001
+ "algorithmVersion",
1002
+ "method",
1003
+ "globalSensitivity",
1004
+ "sobol",
1005
+ "scenario",
1006
+ "originalSpecHash",
1007
+ "effectiveSpecHash",
1008
+ "effectiveRunHash",
1009
+ "budget",
1010
+ "tasks",
1011
+ "candidates",
1012
+ "elasticities",
1013
+ "pareto",
1014
+ "warnings"
1015
+ ],
1016
+ additionalProperties: false
1017
+ },
1018
+ version: "3.1"
1019
+ };
1020
+ }
1021
+ let experimentJsonSchemaUnit;
1022
+ /**
1023
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-tune-signature The structural schema is OpenAPI 3.1 from typia.
1024
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-tune-signature #365035c Re-read the section: schema version is 3.1 and the contract id is idlekit.experiment.v1.
1025
+ */
1026
+ export function experimentJsonSchema() {
1027
+ experimentJsonSchemaUnit ??= buildExperimentJsonSchema();
1028
+ return experimentJsonSchemaUnit;
1029
+ }
1030
+ let validateExperimentSpecValidator;
1031
+ export const validateExperimentSpec = (input) => (validateExperimentSpecValidator ??= (() => {
1032
+ const _ip0 = input => "endMoney" === input["metricId"] || "timeToMoney" === input["metricId"] || "varBenefit" === input["metricId"];
1033
+ const _ip1 = input => "string" === typeof input["id"];
1034
+ const _ip2 = input => "number" === typeof input["min"];
1035
+ const _ip3 = input => "number" === typeof input["max"];
1036
+ const _ve0 = "Readonly<{ schemaVersion: 1; scenario: Readonly<{ ref: string; hash: string; }>; model: Readonly<{ id: string; version: number; }>; strategy?: Readonly<{ id: string; }> | undefined; ... 10 more ...; session: Readonly<...>; }>";
1037
+ const _vp0 = (input, _path, _exceptionable = true) => "endMoney" === input["metricId"] || "timeToMoney" === input["metricId"] || "varBenefit" === input["metricId"] || _report(_exceptionable, {
1038
+ path: _path + ".metricId",
1039
+ expected: "(\"endMoney\" | \"timeToMoney\" | \"varBenefit\")",
1040
+ value: input["metricId"]
1041
+ });
1042
+ const _ve1 = "Readonly<{ id: string; target: \"model\" | \"session\" | \"strategy\"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>";
1043
+ const _ve2 = "ReadonlyArray<Readonly<{ id: string; target: \"model\" | \"session\" | \"strategy\"; path: string; space: ExperimentParamSpace; baseline: ExperimentScalar; probes?: readonly ExperimentScalar[] | undefined; }>>";
1044
+ const _ve3 = "(ReadonlyArray<Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>> | undefined)";
1045
+ const _vp1 = (input, _path, _exceptionable = true) => "string" === typeof input["id"] || _report(_exceptionable, {
1046
+ path: _path + ".id",
1047
+ expected: "string",
1048
+ value: input["id"]
1049
+ });
1050
+ const _ve4 = "(Readonly<{ kind: \"bool\"; }> | Readonly<{ kind: \"choice\"; values: readonly ExperimentScalar[]; }> | Readonly<{ kind: \"int\"; min: number; max: number; }> | Readonly<{ kind: \"number\"; min: number; max: number; scale?: \"linear\" | \"log\" | undefined; }>)";
1051
+ const _vp2 = (input, _path, _exceptionable = true) => "number" === typeof input["min"] || _report(_exceptionable, {
1052
+ path: _path + ".min",
1053
+ expected: "number",
1054
+ value: input["min"]
1055
+ });
1056
+ const _vp3 = (input, _path, _exceptionable = true) => "number" === typeof input["max"] || _report(_exceptionable, {
1057
+ path: _path + ".max",
1058
+ expected: "number",
1059
+ value: input["max"]
1060
+ });
1061
+ const _ve5 = "(Readonly<{ kind: \"bool\"; }> | Readonly<{ kind: \"int\"; min: number; max: number; }> | Readonly<{ kind: \"number\"; min: number; max: number; scale?: \"linear\" | \"log\" | undefined; }> | Readonly<{ kind: \"choice\"; values: readonly ExperimentScalar[]; }>)";
1062
+ const _io0 = input => 1 === input.schemaVersion && ("object" === typeof input.scenario && null !== input.scenario && _io1(input.scenario)) && ("object" === typeof input.model && null !== input.model && _io2(input.model)) && (undefined === input.strategy || "object" === typeof input.strategy && null !== input.strategy && _io3(input.strategy)) && _ip0(input) && (undefined === input.goal || "object" === typeof input.goal && null !== input.goal && _io4(input.goal)) && (undefined === input.objectives || Array.isArray(input.objectives) && input.objectives.every(elem => "object" === typeof elem && null !== elem && _io5(elem))) && (undefined === input.constraints || Array.isArray(input.constraints) && input.constraints.every(elem => "object" === typeof elem && null !== elem && _io6(elem))) && (Array.isArray(input.dimensions) && input.dimensions.every(elem => "object" === typeof elem && null !== elem && _io7(elem))) && (undefined === input.candidates || Array.isArray(input.candidates) && input.candidates.every(elem => "object" === typeof elem && null !== elem && _io12(elem))) && ("object" === typeof input.budget && null !== input.budget && _io14(input.budget)) && (Array.isArray(input.seeds) && input.seeds.every(elem => "number" === typeof elem)) && "number" === typeof input.horizonSec && "number" === typeof input.stepSec && ("object" === typeof input.session && null !== input.session && _io15(input.session));
1063
+ const _io1 = input => "string" === typeof input.ref && "string" === typeof input.hash;
1064
+ const _io10 = input => "number" === input.kind && _ip2(input) && _ip3(input) && (undefined === input.scale || "linear" === input.scale || "log" === input.scale);
1065
+ const _io11 = input => "choice" === input.kind && (Array.isArray(input.values) && input.values.every(elem => null === elem || "string" === typeof elem || "number" === typeof elem || "boolean" === typeof elem));
1066
+ const _io12 = input => _ip1(input) && (Array.isArray(input.assignments) && input.assignments.every(elem => "object" === typeof elem && null !== elem && _io13(elem)));
1067
+ const _io13 = input => "string" === typeof input.dimensionId && (null === input.value || "string" === typeof input.value || "number" === typeof input.value || "boolean" === typeof input.value);
1068
+ const _io14 = input => "number" === typeof input.candidates && "number" === typeof input.seeds;
1069
+ const _io15 = input => "string" === typeof input.pattern && "number" === typeof input.days;
1070
+ const _io2 = input => _ip1(input) && "number" === typeof input.version;
1071
+ const _io3 = input => _ip1(input);
1072
+ const _io4 = input => "number" === typeof input.target;
1073
+ const _io5 = input => _ip1(input) && _ip0(input) && ("higher" === input.sense || "lower" === input.sense);
1074
+ const _io6 = input => _ip1(input) && _ip0(input) && ("gte" === input.op || "lte" === input.op) && "number" === typeof input.limit;
1075
+ const _io7 = input => _ip1(input) && ("model" === input.target || "session" === input.target || "strategy" === input.target) && "string" === typeof input.path && ("object" === typeof input.space && null !== input.space && _iu0(input.space)) && (null === input.baseline || "string" === typeof input.baseline || "number" === typeof input.baseline || "boolean" === typeof input.baseline) && (undefined === input.probes || Array.isArray(input.probes) && input.probes.every(elem => null === elem || "string" === typeof elem || "number" === typeof elem || "boolean" === typeof elem));
1076
+ const _io8 = input => "bool" === input.kind;
1077
+ const _io9 = input => "int" === input.kind && _ip2(input) && _ip3(input);
1078
+ const _iu0 = input => (() => {
1079
+ if ("bool" === input.kind)
1080
+ return _io8(input);
1081
+ else if ("int" === input.kind)
1082
+ return _io9(input);
1083
+ else if ("number" === input.kind)
1084
+ return _io10(input);
1085
+ else if ("choice" === input.kind)
1086
+ return _io11(input);
1087
+ else
1088
+ return false;
1089
+ })();
1090
+ const _vo0 = (input, _path, _exceptionable = true) => [1 === input.schemaVersion || _report(_exceptionable, {
1091
+ path: _path + ".schemaVersion",
1092
+ expected: "1",
1093
+ value: input.schemaVersion
1094
+ }), ("object" === typeof input.scenario && null !== input.scenario || _report(_exceptionable, {
1095
+ path: _path + ".scenario",
1096
+ expected: "Readonly<{ ref: string; hash: string; }>",
1097
+ value: input.scenario
1098
+ })) && _vo1(input.scenario, _path + ".scenario", true && _exceptionable) || _report(_exceptionable, {
1099
+ path: _path + ".scenario",
1100
+ expected: "Readonly<{ ref: string; hash: string; }>",
1101
+ value: input.scenario
1102
+ }), ("object" === typeof input.model && null !== input.model || _report(_exceptionable, {
1103
+ path: _path + ".model",
1104
+ expected: "Readonly<{ id: string; version: number; }>",
1105
+ value: input.model
1106
+ })) && _vo2(input.model, _path + ".model", true && _exceptionable) || _report(_exceptionable, {
1107
+ path: _path + ".model",
1108
+ expected: "Readonly<{ id: string; version: number; }>",
1109
+ value: input.model
1110
+ }), undefined === input.strategy || ("object" === typeof input.strategy && null !== input.strategy || _report(_exceptionable, {
1111
+ path: _path + ".strategy",
1112
+ expected: "(Readonly<{ id: string; }> | undefined)",
1113
+ value: input.strategy
1114
+ })) && _vo3(input.strategy, _path + ".strategy", true && _exceptionable) || _report(_exceptionable, {
1115
+ path: _path + ".strategy",
1116
+ expected: "(Readonly<{ id: string; }> | undefined)",
1117
+ value: input.strategy
1118
+ }), _vp0(input, _path, true && _exceptionable), undefined === input.goal || ("object" === typeof input.goal && null !== input.goal || _report(_exceptionable, {
1119
+ path: _path + ".goal",
1120
+ expected: "(Readonly<{ target: number; }> | undefined)",
1121
+ value: input.goal
1122
+ })) && _vo4(input.goal, _path + ".goal", true && _exceptionable) || _report(_exceptionable, {
1123
+ path: _path + ".goal",
1124
+ expected: "(Readonly<{ target: number; }> | undefined)",
1125
+ value: input.goal
1126
+ }), undefined === input.objectives || (Array.isArray(input.objectives) || _report(_exceptionable, {
1127
+ path: _path + ".objectives",
1128
+ expected: "(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; sense: \"higher\" | \"lower\"; }>> | undefined)",
1129
+ value: input.objectives
1130
+ })) && input.objectives.map((elem, _index9) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
1131
+ path: _path + ".objectives[" + _index9 + "]",
1132
+ expected: "Readonly<{ id: string; metricId: ExperimentMetricId; sense: \"higher\" | \"lower\"; }>",
1133
+ value: elem
1134
+ })) && _vo5(elem, _path + ".objectives[" + _index9 + "]", true && _exceptionable) || _report(_exceptionable, {
1135
+ path: _path + ".objectives[" + _index9 + "]",
1136
+ expected: "Readonly<{ id: string; metricId: ExperimentMetricId; sense: \"higher\" | \"lower\"; }>",
1137
+ value: elem
1138
+ })).every(flag => flag) || _report(_exceptionable, {
1139
+ path: _path + ".objectives",
1140
+ expected: "(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; sense: \"higher\" | \"lower\"; }>> | undefined)",
1141
+ value: input.objectives
1142
+ }), undefined === input.constraints || (Array.isArray(input.constraints) || _report(_exceptionable, {
1143
+ path: _path + ".constraints",
1144
+ expected: "(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; op: \"gte\" | \"lte\"; limit: number; }>> | undefined)",
1145
+ value: input.constraints
1146
+ })) && input.constraints.map((elem, _index10) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
1147
+ path: _path + ".constraints[" + _index10 + "]",
1148
+ expected: "Readonly<{ id: string; metricId: ExperimentMetricId; op: \"gte\" | \"lte\"; limit: number; }>",
1149
+ value: elem
1150
+ })) && _vo6(elem, _path + ".constraints[" + _index10 + "]", true && _exceptionable) || _report(_exceptionable, {
1151
+ path: _path + ".constraints[" + _index10 + "]",
1152
+ expected: "Readonly<{ id: string; metricId: ExperimentMetricId; op: \"gte\" | \"lte\"; limit: number; }>",
1153
+ value: elem
1154
+ })).every(flag => flag) || _report(_exceptionable, {
1155
+ path: _path + ".constraints",
1156
+ expected: "(ReadonlyArray<Readonly<{ id: string; metricId: ExperimentMetricId; op: \"gte\" | \"lte\"; limit: number; }>> | undefined)",
1157
+ value: input.constraints
1158
+ }), (Array.isArray(input.dimensions) || _report(_exceptionable, {
1159
+ path: _path + ".dimensions",
1160
+ expected: _ve2,
1161
+ value: input.dimensions
1162
+ })) && input.dimensions.map((elem, _index11) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
1163
+ path: _path + ".dimensions[" + _index11 + "]",
1164
+ expected: _ve1,
1165
+ value: elem
1166
+ })) && _vo7(elem, _path + ".dimensions[" + _index11 + "]", true && _exceptionable) || _report(_exceptionable, {
1167
+ path: _path + ".dimensions[" + _index11 + "]",
1168
+ expected: _ve1,
1169
+ value: elem
1170
+ })).every(flag => flag) || _report(_exceptionable, {
1171
+ path: _path + ".dimensions",
1172
+ expected: _ve2,
1173
+ value: input.dimensions
1174
+ }), undefined === input.candidates || (Array.isArray(input.candidates) || _report(_exceptionable, {
1175
+ path: _path + ".candidates",
1176
+ expected: _ve3,
1177
+ value: input.candidates
1178
+ })) && input.candidates.map((elem, _index12) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
1179
+ path: _path + ".candidates[" + _index12 + "]",
1180
+ expected: "Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>",
1181
+ value: elem
1182
+ })) && _vo12(elem, _path + ".candidates[" + _index12 + "]", true && _exceptionable) || _report(_exceptionable, {
1183
+ path: _path + ".candidates[" + _index12 + "]",
1184
+ expected: "Readonly<{ id: string; assignments: readonly Readonly<{ dimensionId: string; value: ExperimentScalar; }>[]; }>",
1185
+ value: elem
1186
+ })).every(flag => flag) || _report(_exceptionable, {
1187
+ path: _path + ".candidates",
1188
+ expected: _ve3,
1189
+ value: input.candidates
1190
+ }), ("object" === typeof input.budget && null !== input.budget || _report(_exceptionable, {
1191
+ path: _path + ".budget",
1192
+ expected: "Readonly<{ candidates: number; seeds: number; }>",
1193
+ value: input.budget
1194
+ })) && _vo14(input.budget, _path + ".budget", true && _exceptionable) || _report(_exceptionable, {
1195
+ path: _path + ".budget",
1196
+ expected: "Readonly<{ candidates: number; seeds: number; }>",
1197
+ value: input.budget
1198
+ }), (Array.isArray(input.seeds) || _report(_exceptionable, {
1199
+ path: _path + ".seeds",
1200
+ expected: "ReadonlyArray<number>",
1201
+ value: input.seeds
1202
+ })) && input.seeds.map((elem, _index13) => "number" === typeof elem || _report(_exceptionable, {
1203
+ path: _path + ".seeds[" + _index13 + "]",
1204
+ expected: "number",
1205
+ value: elem
1206
+ })).every(flag => flag) || _report(_exceptionable, {
1207
+ path: _path + ".seeds",
1208
+ expected: "ReadonlyArray<number>",
1209
+ value: input.seeds
1210
+ }), "number" === typeof input.horizonSec || _report(_exceptionable, {
1211
+ path: _path + ".horizonSec",
1212
+ expected: "number",
1213
+ value: input.horizonSec
1214
+ }), "number" === typeof input.stepSec || _report(_exceptionable, {
1215
+ path: _path + ".stepSec",
1216
+ expected: "number",
1217
+ value: input.stepSec
1218
+ }), ("object" === typeof input.session && null !== input.session || _report(_exceptionable, {
1219
+ path: _path + ".session",
1220
+ expected: "Readonly<{ pattern: string; days: number; }>",
1221
+ value: input.session
1222
+ })) && _vo15(input.session, _path + ".session", true && _exceptionable) || _report(_exceptionable, {
1223
+ path: _path + ".session",
1224
+ expected: "Readonly<{ pattern: string; days: number; }>",
1225
+ value: input.session
1226
+ })].every(flag => flag);
1227
+ const _vo1 = (input, _path, _exceptionable = true) => ["string" === typeof input.ref || _report(_exceptionable, {
1228
+ path: _path + ".ref",
1229
+ expected: "string",
1230
+ value: input.ref
1231
+ }), "string" === typeof input.hash || _report(_exceptionable, {
1232
+ path: _path + ".hash",
1233
+ expected: "string",
1234
+ value: input.hash
1235
+ })].every(flag => flag);
1236
+ const _vo10 = (input, _path, _exceptionable = true) => ["number" === input.kind || _report(_exceptionable, {
1237
+ path: _path + ".kind",
1238
+ expected: "\"number\"",
1239
+ value: input.kind
1240
+ }), _vp2(input, _path, true && _exceptionable), _vp3(input, _path, true && _exceptionable), undefined === input.scale || "linear" === input.scale || "log" === input.scale || _report(_exceptionable, {
1241
+ path: _path + ".scale",
1242
+ expected: "(\"linear\" | \"log\" | undefined)",
1243
+ value: input.scale
1244
+ })].every(flag => flag);
1245
+ const _vo11 = (input, _path, _exceptionable = true) => ["choice" === input.kind || _report(_exceptionable, {
1246
+ path: _path + ".kind",
1247
+ expected: "\"choice\"",
1248
+ value: input.kind
1249
+ }), (Array.isArray(input.values) || _report(_exceptionable, {
1250
+ path: _path + ".values",
1251
+ expected: "ReadonlyArray<ExperimentScalar>",
1252
+ value: input.values
1253
+ })) && input.values.map((elem, _index15) => null === elem || "string" === typeof elem || "number" === typeof elem || "boolean" === typeof elem || _report(_exceptionable, {
1254
+ path: _path + ".values[" + _index15 + "]",
1255
+ expected: "(boolean | null | number | string)",
1256
+ value: elem
1257
+ })).every(flag => flag) || _report(_exceptionable, {
1258
+ path: _path + ".values",
1259
+ expected: "ReadonlyArray<ExperimentScalar>",
1260
+ value: input.values
1261
+ })].every(flag => flag);
1262
+ const _vo12 = (input, _path, _exceptionable = true) => [_vp1(input, _path, true && _exceptionable), (Array.isArray(input.assignments) || _report(_exceptionable, {
1263
+ path: _path + ".assignments",
1264
+ expected: "ReadonlyArray<Readonly<{ dimensionId: string; value: ExperimentScalar; }>>",
1265
+ value: input.assignments
1266
+ })) && input.assignments.map((elem, _index16) => ("object" === typeof elem && null !== elem || _report(_exceptionable, {
1267
+ path: _path + ".assignments[" + _index16 + "]",
1268
+ expected: "Readonly<{ dimensionId: string; value: ExperimentScalar; }>",
1269
+ value: elem
1270
+ })) && _vo13(elem, _path + ".assignments[" + _index16 + "]", true && _exceptionable) || _report(_exceptionable, {
1271
+ path: _path + ".assignments[" + _index16 + "]",
1272
+ expected: "Readonly<{ dimensionId: string; value: ExperimentScalar; }>",
1273
+ value: elem
1274
+ })).every(flag => flag) || _report(_exceptionable, {
1275
+ path: _path + ".assignments",
1276
+ expected: "ReadonlyArray<Readonly<{ dimensionId: string; value: ExperimentScalar; }>>",
1277
+ value: input.assignments
1278
+ })].every(flag => flag);
1279
+ const _vo13 = (input, _path, _exceptionable = true) => ["string" === typeof input.dimensionId || _report(_exceptionable, {
1280
+ path: _path + ".dimensionId",
1281
+ expected: "string",
1282
+ value: input.dimensionId
1283
+ }), null === input.value || "string" === typeof input.value || "number" === typeof input.value || "boolean" === typeof input.value || _report(_exceptionable, {
1284
+ path: _path + ".value",
1285
+ expected: "(boolean | null | number | string)",
1286
+ value: input.value
1287
+ })].every(flag => flag);
1288
+ const _vo14 = (input, _path, _exceptionable = true) => ["number" === typeof input.candidates || _report(_exceptionable, {
1289
+ path: _path + ".candidates",
1290
+ expected: "number",
1291
+ value: input.candidates
1292
+ }), "number" === typeof input.seeds || _report(_exceptionable, {
1293
+ path: _path + ".seeds",
1294
+ expected: "number",
1295
+ value: input.seeds
1296
+ })].every(flag => flag);
1297
+ const _vo15 = (input, _path, _exceptionable = true) => ["string" === typeof input.pattern || _report(_exceptionable, {
1298
+ path: _path + ".pattern",
1299
+ expected: "string",
1300
+ value: input.pattern
1301
+ }), "number" === typeof input.days || _report(_exceptionable, {
1302
+ path: _path + ".days",
1303
+ expected: "number",
1304
+ value: input.days
1305
+ })].every(flag => flag);
1306
+ const _vo2 = (input, _path, _exceptionable = true) => [_vp1(input, _path, true && _exceptionable), "number" === typeof input.version || _report(_exceptionable, {
1307
+ path: _path + ".version",
1308
+ expected: "number",
1309
+ value: input.version
1310
+ })].every(flag => flag);
1311
+ const _vo3 = (input, _path, _exceptionable = true) => [_vp1(input, _path, true && _exceptionable)].every(flag => flag);
1312
+ const _vo4 = (input, _path, _exceptionable = true) => ["number" === typeof input.target || _report(_exceptionable, {
1313
+ path: _path + ".target",
1314
+ expected: "number",
1315
+ value: input.target
1316
+ })].every(flag => flag);
1317
+ const _vo5 = (input, _path, _exceptionable = true) => [_vp1(input, _path, true && _exceptionable), _vp0(input, _path, true && _exceptionable), "higher" === input.sense || "lower" === input.sense || _report(_exceptionable, {
1318
+ path: _path + ".sense",
1319
+ expected: "(\"higher\" | \"lower\")",
1320
+ value: input.sense
1321
+ })].every(flag => flag);
1322
+ const _vo6 = (input, _path, _exceptionable = true) => [_vp1(input, _path, true && _exceptionable), _vp0(input, _path, true && _exceptionable), "gte" === input.op || "lte" === input.op || _report(_exceptionable, {
1323
+ path: _path + ".op",
1324
+ expected: "(\"gte\" | \"lte\")",
1325
+ value: input.op
1326
+ }), "number" === typeof input.limit || _report(_exceptionable, {
1327
+ path: _path + ".limit",
1328
+ expected: "number",
1329
+ value: input.limit
1330
+ })].every(flag => flag);
1331
+ const _vo7 = (input, _path, _exceptionable = true) => [_vp1(input, _path, true && _exceptionable), "model" === input.target || "session" === input.target || "strategy" === input.target || _report(_exceptionable, {
1332
+ path: _path + ".target",
1333
+ expected: "(\"model\" | \"session\" | \"strategy\")",
1334
+ value: input.target
1335
+ }), "string" === typeof input.path || _report(_exceptionable, {
1336
+ path: _path + ".path",
1337
+ expected: "string",
1338
+ value: input.path
1339
+ }), ("object" === typeof input.space && null !== input.space || _report(_exceptionable, {
1340
+ path: _path + ".space",
1341
+ expected: _ve4,
1342
+ value: input.space
1343
+ })) && _vu0(input.space, _path + ".space", true && _exceptionable) || _report(_exceptionable, {
1344
+ path: _path + ".space",
1345
+ expected: _ve4,
1346
+ value: input.space
1347
+ }), null === input.baseline || "string" === typeof input.baseline || "number" === typeof input.baseline || "boolean" === typeof input.baseline || _report(_exceptionable, {
1348
+ path: _path + ".baseline",
1349
+ expected: "(boolean | null | number | string)",
1350
+ value: input.baseline
1351
+ }), undefined === input.probes || (Array.isArray(input.probes) || _report(_exceptionable, {
1352
+ path: _path + ".probes",
1353
+ expected: "(ReadonlyArray<ExperimentScalar> | undefined)",
1354
+ value: input.probes
1355
+ })) && input.probes.map((elem, _index14) => null === elem || "string" === typeof elem || "number" === typeof elem || "boolean" === typeof elem || _report(_exceptionable, {
1356
+ path: _path + ".probes[" + _index14 + "]",
1357
+ expected: "(boolean | null | number | string)",
1358
+ value: elem
1359
+ })).every(flag => flag) || _report(_exceptionable, {
1360
+ path: _path + ".probes",
1361
+ expected: "(ReadonlyArray<ExperimentScalar> | undefined)",
1362
+ value: input.probes
1363
+ })].every(flag => flag);
1364
+ const _vo8 = (input, _path, _exceptionable = true) => ["bool" === input.kind || _report(_exceptionable, {
1365
+ path: _path + ".kind",
1366
+ expected: "\"bool\"",
1367
+ value: input.kind
1368
+ })].every(flag => flag);
1369
+ const _vo9 = (input, _path, _exceptionable = true) => ["int" === input.kind || _report(_exceptionable, {
1370
+ path: _path + ".kind",
1371
+ expected: "\"int\"",
1372
+ value: input.kind
1373
+ }), _vp2(input, _path, true && _exceptionable), _vp3(input, _path, true && _exceptionable)].every(flag => flag);
1374
+ const _vu0 = (input, _path, _exceptionable = true) => (() => {
1375
+ if ("bool" === input.kind)
1376
+ return _vo8(input, _path, true && _exceptionable);
1377
+ else if ("int" === input.kind)
1378
+ return _vo9(input, _path, true && _exceptionable);
1379
+ else if ("number" === input.kind)
1380
+ return _vo10(input, _path, true && _exceptionable);
1381
+ else if ("choice" === input.kind)
1382
+ return _vo11(input, _path, true && _exceptionable);
1383
+ else
1384
+ return _report(_exceptionable, {
1385
+ path: _path,
1386
+ expected: _ve5,
1387
+ value: input
1388
+ });
1389
+ })();
1390
+ const __is = input => "object" === typeof input && null !== input && _io0(input);
1391
+ let errors;
1392
+ let _report;
1393
+ return _createStandardSchema_1._createStandardSchema(input => {
1394
+ if (false === __is(input)) {
1395
+ errors = [];
1396
+ _report = _validateReport_1._validateReport(errors);
1397
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _report(true, {
1398
+ path: _path + "",
1399
+ expected: _ve0,
1400
+ value: input
1401
+ })) && _vo0(input, _path + "", true) || _report(true, {
1402
+ path: _path + "",
1403
+ expected: _ve0,
1404
+ value: input
1405
+ }))(input, "$input", true);
1406
+ const success = 0 === errors.length;
1407
+ return success ? {
1408
+ success,
1409
+ data: input
1410
+ } : {
1411
+ success,
1412
+ errors,
1413
+ data: input
1414
+ };
1415
+ }
1416
+ return {
1417
+ success: true,
1418
+ data: input
1419
+ };
1420
+ });
1421
+ })())(input);
1422
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
1423
+ // Canonical JSON drops `__type`, so a parameter with that name would vanish from task descriptors and hashes.
1424
+ const BANNED_SEGMENT = new Set(["__proto__", "prototype", "constructor", "eval", "__type"]);
1425
+ function reject(message) {
1426
+ throw new ExperimentRejected(`Experiment ${message}`);
1427
+ }
1428
+ export function assertRegisteredPath(path, target) {
1429
+ if (typeof path !== "string" || path.length === 0 || path !== path.trim())
1430
+ reject("parameter path is empty");
1431
+ if (/[\$\[\]\(\)]/.test(path) || path.includes("..") || path.includes("/") || path.includes("\\") || path.includes(" ")) {
1432
+ reject("parameter path is not a registered identifier");
1433
+ }
1434
+ const parts = path.split(".");
1435
+ if (parts.length === 0 || parts.some((part) => !IDENTIFIER.test(part) || BANNED_SEGMENT.has(part))) {
1436
+ reject("parameter path is not a registered identifier");
1437
+ }
1438
+ if (target === "session" && (parts.length !== 1 || (parts[0] !== "pattern" && parts[0] !== "days"))) {
1439
+ reject("session path must be pattern or days");
1440
+ }
1441
+ return parts;
1442
+ }
1443
+ export function setRegisteredParam(root, path, value) {
1444
+ const parts = assertRegisteredPath(path, "model");
1445
+ // Copy only the objects on the assigned path, with their prototype and property descriptors.
1446
+ // Every other value keeps its identity and exact number. A copied property is configurable, so a frozen
1447
+ // or read-only source can still take the assigned value as a new own data property.
1448
+ const copy = (source) => {
1449
+ const descriptors = Object.getOwnPropertyDescriptors(source);
1450
+ for (const descriptor of Object.values(descriptors))
1451
+ descriptor.configurable = true;
1452
+ return Object.create(Object.getPrototypeOf(source), descriptors);
1453
+ };
1454
+ const put = (target, key, entry) => {
1455
+ Object.defineProperty(target, key, { value: entry, writable: true, enumerable: true, configurable: true });
1456
+ };
1457
+ const clone = copy(root);
1458
+ let cursor = clone;
1459
+ for (let index = 0; index < parts.length - 1; index += 1) {
1460
+ const key = parts[index];
1461
+ // assertRegisteredPath already bans these. The inline guard keeps the write visibly safe at the assignment.
1462
+ if (key === "__proto__" || key === "constructor" || key === "prototype")
1463
+ reject("parameter path is not a registered identifier");
1464
+ const next = Object.prototype.hasOwnProperty.call(cursor, key) ? cursor[key] : undefined;
1465
+ const copied = next === null || typeof next !== "object" || Array.isArray(next) ? {} : copy(next);
1466
+ put(cursor, key, copied);
1467
+ cursor = copied;
1468
+ }
1469
+ const leaf = parts[parts.length - 1];
1470
+ if (leaf === "__proto__" || leaf === "constructor" || leaf === "prototype")
1471
+ reject("parameter path is not a registered identifier");
1472
+ put(cursor, leaf, value);
1473
+ return clone;
1474
+ }
1475
+ function byText(left, right) {
1476
+ return left < right ? -1 : left > right ? 1 : 0;
1477
+ }
1478
+ function normalizeSpec(spec) {
1479
+ return {
1480
+ ...spec,
1481
+ dimensions: [...spec.dimensions].sort((left, right) => byText(left.id, right.id)),
1482
+ ...(spec.candidates
1483
+ ? {
1484
+ candidates: [...spec.candidates]
1485
+ .map((candidate) => ({
1486
+ ...candidate,
1487
+ assignments: [...candidate.assignments].sort((left, right) => byText(left.dimensionId, right.dimensionId)),
1488
+ }))
1489
+ .sort((left, right) => byText(left.id, right.id)),
1490
+ }
1491
+ : {}),
1492
+ };
1493
+ }
1494
+ /** Two dimensions on one target overlap when one path equals the other or is a dotted prefix of it. */
1495
+ function pathsOverlap(left, right) {
1496
+ return left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
1497
+ }
1498
+ function requireSpec(input) {
1499
+ const parsed = validateExperimentSpec(input);
1500
+ if (!parsed.success) {
1501
+ reject(`spec rejected: ${parsed.errors.map((error) => error.path).join(", ")}`);
1502
+ }
1503
+ const spec = parsed.data;
1504
+ if (!Number.isFinite(spec.horizonSec) || spec.horizonSec <= 0)
1505
+ reject("horizon must be a positive finite number");
1506
+ // A JSON literal such as 1e400 parses to Infinity, which the artifact and canonical JSON cannot carry.
1507
+ if (!Number.isFinite(spec.model.version) || Object.is(spec.model.version, -0))
1508
+ reject("model version must be finite and not -0");
1509
+ if (spec.strategy !== undefined && spec.strategy.id.length === 0)
1510
+ reject("strategy id must not be empty");
1511
+ if (!Number.isFinite(spec.stepSec) || spec.stepSec <= 0)
1512
+ reject("step must be a positive finite number");
1513
+ if (!Number.isInteger(spec.budget.candidates) || spec.budget.candidates < 1)
1514
+ reject("candidate budget must be a positive integer");
1515
+ if (!Number.isInteger(spec.budget.seeds) || spec.budget.seeds < 1)
1516
+ reject("seed budget must be a positive integer");
1517
+ if (spec.seeds.length === 0 || spec.seeds.some((seed) => !Number.isFinite(seed)))
1518
+ reject("seeds must be finite");
1519
+ if (new Set(spec.seeds).size !== spec.seeds.length)
1520
+ reject("seeds must be unique logical seeds");
1521
+ // Task ids, logical seeds, and canonical JSON write -0 as 0, while ctx.seed would still carry -0.
1522
+ if (spec.seeds.some((seed) => Object.is(seed, -0)))
1523
+ reject("seeds must not be -0");
1524
+ if (spec.seeds.length > spec.budget.seeds)
1525
+ reject("seed budget exceeded");
1526
+ if (!Number.isFinite(spec.session.days) || spec.session.days <= 0)
1527
+ reject("session days must be positive");
1528
+ const ids = new Set();
1529
+ for (const [index, dimension] of spec.dimensions.entries()) {
1530
+ if (ids.has(dimension.id))
1531
+ reject(`duplicate dimension ${dimension.id}`);
1532
+ ids.add(dimension.id);
1533
+ assertRegisteredPath(dimension.path, dimension.target);
1534
+ // A JSON literal such as 1e400 parses to Infinity, and canonical JSON would hash it as null.
1535
+ if (dimension.space.kind === "int" || dimension.space.kind === "number") {
1536
+ if (!Number.isFinite(dimension.space.min) || !Number.isFinite(dimension.space.max))
1537
+ reject(`space bounds for ${dimension.id} must be finite`);
1538
+ }
1539
+ else if (dimension.space.kind === "choice" && dimension.space.values.some((entry) => typeof entry === "number" && !Number.isFinite(entry))) {
1540
+ reject(`choice values for ${dimension.id} must be finite`);
1541
+ }
1542
+ // Each candidate runs one continuous horizon. No session schedule is executed, so a session value would not reach the run.
1543
+ if (dimension.target === "session")
1544
+ reject(`session dimension ${dimension.id} is not executed: each candidate runs one continuous horizon`);
1545
+ for (const other of spec.dimensions.slice(0, index)) {
1546
+ if (other.target === dimension.target && pathsOverlap(other.path, dimension.path)) {
1547
+ reject(`dimensions ${other.id} and ${dimension.id} target the same ${dimension.target} parameter`);
1548
+ }
1549
+ }
1550
+ if (!assignmentFitsParamSpace(dimension.space, dimension.baseline))
1551
+ reject(`baseline for ${dimension.id} is outside its parameter space`);
1552
+ for (const probe of dimension.probes ?? []) {
1553
+ if (!assignmentFitsParamSpace(dimension.space, probe))
1554
+ reject(`probe for ${dimension.id} is outside its parameter space`);
1555
+ }
1556
+ }
1557
+ // An objective id names one Pareto objective. A repeated id would be compared as two independent positions.
1558
+ const objectiveIds = new Set();
1559
+ for (const objective of spec.objectives ?? []) {
1560
+ if (objectiveIds.has(objective.id))
1561
+ reject(`duplicate objective ${objective.id}`);
1562
+ objectiveIds.add(objective.id);
1563
+ }
1564
+ // A violation lists only the constraint id, so the id must name one constraint. A JSON literal such as 1e400 parses to Infinity.
1565
+ const constraintIds = new Set();
1566
+ for (const constraint of spec.constraints ?? []) {
1567
+ if (constraintIds.has(constraint.id))
1568
+ reject(`duplicate constraint ${constraint.id}`);
1569
+ constraintIds.add(constraint.id);
1570
+ if (!Number.isFinite(constraint.limit))
1571
+ reject(`constraint ${constraint.id} limit must be finite`);
1572
+ }
1573
+ return spec;
1574
+ }
1575
+ /**
1576
+ * Generated probe id. A string probe is written as JSON text, so `1` and `"1"`, or `true` and `"true"`, stay distinct.
1577
+ * A number, boolean, or null probe is written as its plain text.
1578
+ */
1579
+ function probeCandidateId(dimension, probe) {
1580
+ return `oat:${dimension.id}:${typeof probe === "string" ? JSON.stringify(probe) : String(probe)}`;
1581
+ }
1582
+ function plannedCandidates(spec) {
1583
+ const explicit = spec.candidates ?? [];
1584
+ const probes = spec.dimensions.some((dimension) => (dimension.probes?.length ?? 0) > 0);
1585
+ const generated = [];
1586
+ if (explicit.length === 0 || probes) {
1587
+ const baselineAssignments = spec.dimensions.map((dimension) => ({ dimensionId: dimension.id, value: dimension.baseline }));
1588
+ generated.push({ id: "baseline", assignments: baselineAssignments });
1589
+ for (const dimension of [...spec.dimensions].sort((left, right) => byText(left.id, right.id))) {
1590
+ for (const probe of dimension.probes ?? []) {
1591
+ generated.push({
1592
+ id: probeCandidateId(dimension, probe),
1593
+ assignments: spec.dimensions.map((item) => ({
1594
+ dimensionId: item.id,
1595
+ value: item.id === dimension.id ? probe : item.baseline,
1596
+ })),
1597
+ });
1598
+ }
1599
+ }
1600
+ }
1601
+ const merged = [...explicit, ...generated].sort((left, right) => byText(left.id, right.id));
1602
+ if (merged.length === 0)
1603
+ reject("experiment requires at least one candidate");
1604
+ if (merged.length > spec.budget.candidates)
1605
+ reject("candidate budget exceeded");
1606
+ const seen = new Set();
1607
+ for (const candidate of merged) {
1608
+ if (candidate.id.length === 0 || seen.has(candidate.id))
1609
+ reject(`duplicate candidate ${candidate.id}`);
1610
+ seen.add(candidate.id);
1611
+ }
1612
+ return merged;
1613
+ }
1614
+ function hasOwnPath(root, path) {
1615
+ let cursor = root;
1616
+ for (const part of path.split(".")) {
1617
+ if (cursor === null || typeof cursor !== "object" || !Object.prototype.hasOwnProperty.call(cursor, part))
1618
+ return false;
1619
+ cursor = cursor[part];
1620
+ }
1621
+ return true;
1622
+ }
1623
+ /**
1624
+ * Checks the candidate params like scenario compile does in legacy-raw mode. With a schema, its output only
1625
+ * confirms that every assigned path is registered: a schema that strips that path would leave the run at its
1626
+ * default while the task claims the assignment. Without a schema, the path must already be in the scenario or
1627
+ * default params, so a typo is not added as a key the factory ignores. The candidate keeps the raw params, so
1628
+ * unassigned fields stay as the scenario wrote them and `create` receives the same representation as a scenario run.
1629
+ */
1630
+ function checkCandidateParams(schema, baseParams, params, assigned, label) {
1631
+ if (!schema) {
1632
+ for (const { dimension } of assigned) {
1633
+ if (!hasOwnPath(baseParams, dimension.path)) {
1634
+ reject(`${label} has no params schema and its scenario params do not register ${dimension.path} for ${dimension.id}`);
1635
+ }
1636
+ }
1637
+ return;
1638
+ }
1639
+ // A detached copy: a schema that writes its input cannot reach the candidate, the base, or the caller.
1640
+ const result = schema["~standard"].validate(cloneJsonData(params));
1641
+ if (!result.success) {
1642
+ reject(`${label} params rejected: ${result.issues.map((issue) => issue.message).join("; ")}`);
1643
+ }
1644
+ for (const { dimension } of assigned) {
1645
+ if (!hasOwnPath(result.value, dimension.path))
1646
+ reject(`${label} params schema does not register ${dimension.path} for ${dimension.id}`);
1647
+ }
1648
+ }
1649
+ function asParamRecord(params, label) {
1650
+ if (params === undefined)
1651
+ return {};
1652
+ if (params === null || typeof params !== "object" || Array.isArray(params)) {
1653
+ reject(`${label} params of the scenario are not an object, so a path cannot be assigned`);
1654
+ }
1655
+ return params;
1656
+ }
1657
+ /**
1658
+ * Task descriptors and hashes write params as canonical JSON, so the params must be JSON data: null, booleans,
1659
+ * strings, finite numbers other than -0, arrays, and plain objects. A Date, a class instance, a function, or an
1660
+ * undefined field would run as one value and be recorded as another, and could not be copied faithfully.
1661
+ * An object reached twice (a shared reference or a cycle) and a `__type` key, which canonical JSON drops,
1662
+ * are refused too.
1663
+ */
1664
+ /** An enumerable, writable, configurable data property: the only kind JSON.parse creates. */
1665
+ function isOrdinaryData(descriptor) {
1666
+ return (descriptor !== undefined &&
1667
+ "value" in descriptor &&
1668
+ descriptor.enumerable === true &&
1669
+ descriptor.writable === true &&
1670
+ descriptor.configurable === true);
1671
+ }
1672
+ /**
1673
+ * `allowUndefined` lets an object field hold undefined. Canonical JSON drops that field and spec and scenario
1674
+ * readers treat it as absent. Params do not get this allowance, because a factory can see the key.
1675
+ */
1676
+ function isJsonData(value, seen = new Set(), allowUndefined = false) {
1677
+ if (value === null || typeof value === "boolean" || typeof value === "string")
1678
+ return true;
1679
+ if (typeof value === "number")
1680
+ return Number.isFinite(value) && !Object.is(value, -0);
1681
+ if (typeof value !== "object" || seen.has(value))
1682
+ return false;
1683
+ // Visited objects stay recorded, so a shared reference fails like a cycle.
1684
+ seen.add(value);
1685
+ // A frozen, sealed, or non-extensible object is observable but writes the same JSON as an ordinary one.
1686
+ if (!Object.isExtensible(value))
1687
+ return false;
1688
+ if (Array.isArray(value)) {
1689
+ // Only a plain array of index slots: no subclass prototype, no named or symbol own property, no holes or accessors.
1690
+ if (Object.getPrototypeOf(value) !== Array.prototype)
1691
+ return false;
1692
+ // Every slot 0..length-1 is an own enumerable data property, and there is no other own key but length.
1693
+ if (Reflect.ownKeys(value).length !== value.length + 1)
1694
+ return false;
1695
+ for (let index = 0; index < value.length; index += 1) {
1696
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
1697
+ if (!isOrdinaryData(descriptor) || !isJsonData(descriptor.value, seen, allowUndefined))
1698
+ return false;
1699
+ }
1700
+ return true;
1701
+ }
1702
+ const prototype = Object.getPrototypeOf(value);
1703
+ if (prototype !== Object.prototype && prototype !== null)
1704
+ return false;
1705
+ return Reflect.ownKeys(value).every((key) => {
1706
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1707
+ return (typeof key === "string" &&
1708
+ key !== "__type" &&
1709
+ isOrdinaryData(descriptor) &&
1710
+ ((allowUndefined && descriptor.value === undefined) || isJsonData(descriptor.value, seen, allowUndefined)));
1711
+ });
1712
+ }
1713
+ /** One visited set spans the model and strategy params, so an object shared between them is refused too. */
1714
+ function requireJsonParams(params, label, seen) {
1715
+ // Task descriptors write absent params as null, so an explicit null root would share that record.
1716
+ if (params === null)
1717
+ reject(`${label} params must be an object or omitted, not null`);
1718
+ if (params !== undefined && !isJsonData(params, seen))
1719
+ reject(`${label} params must be JSON data, because task descriptors and hashes record them as JSON`);
1720
+ }
1721
+ /**
1722
+ * The whole scenario is checked before it is hashed: every field the run reads must be one canonical JSON records.
1723
+ * Canonical JSON writes Infinity as null and -0 as 0 and skips non-enumerable fields, so a constraint such as
1724
+ * maxActionsPerStep or a hidden policy.mode could change the run but not the hash. Params and the initial state
1725
+ * then get the stricter params rule.
1726
+ */
1727
+ function requireScenarioData(scenario) {
1728
+ const seen = new Set();
1729
+ requireJsonParams(scenario.model.params, "model", seen);
1730
+ requireJsonParams(scenario.strategy?.params, "strategy", seen);
1731
+ // The initial state, vars included, reaches every run and the scenario hash. It must be JSON data too.
1732
+ if (!isJsonData(scenario.initial, seen))
1733
+ reject("scenario initial state must be JSON data, because the scenario hash records it as JSON");
1734
+ if (!isJsonData(scenario, new Set(), true))
1735
+ reject("scenario must be JSON data, because the scenario hash records it as JSON");
1736
+ }
1737
+ /**
1738
+ * A private copy of JSON data with object keys in canonical order. A factory that mutates its params cannot reach
1739
+ * another run or the caller, and two params with one canonical JSON reach a factory with one key order.
1740
+ */
1741
+ function cloneJsonData(value) {
1742
+ return value === undefined ? value : JSON.parse(canonicalJson(value));
1743
+ }
1744
+ function candidateBase(spec, scenario, registries) {
1745
+ if (scenario.model.id !== spec.model.id || scenario.model.version !== spec.model.version) {
1746
+ reject(`model ${spec.model.id}@${spec.model.version} does not match the scenario model ${scenario.model.id}@${scenario.model.version}`);
1747
+ }
1748
+ const scenarioStrategy = scenario.strategy;
1749
+ if (spec.strategy && scenarioStrategy && scenarioStrategy.id !== spec.strategy.id) {
1750
+ reject(`strategy ${spec.strategy.id} does not match the scenario strategy ${scenarioStrategy.id}`);
1751
+ }
1752
+ const strategyId = spec.strategy?.id ?? scenarioStrategy?.id ?? null;
1753
+ // An empty id is falsy, so the run plan would build no strategy while the task names one.
1754
+ if (strategyId === "")
1755
+ reject("strategy id must not be empty");
1756
+ if (strategyId === null) {
1757
+ if (spec.dimensions.some((dimension) => dimension.target === "strategy"))
1758
+ reject("strategy id is required");
1759
+ return { modelParams: scenario.model.params, strategyId: null, strategyParams: {} };
1760
+ }
1761
+ const strategyFactory = registries.strategyRegistry.get(strategyId);
1762
+ if (!strategyFactory)
1763
+ reject(`strategy not found: ${strategyId}`);
1764
+ const strategyParams = scenarioStrategy?.params ?? strategyFactory.defaultParams ?? {};
1765
+ // Factory defaults are checked on their own: they are not part of the scenario object.
1766
+ if (scenarioStrategy?.params === undefined)
1767
+ requireJsonParams(strategyParams, "strategy", new Set());
1768
+ return { modelParams: scenario.model.params, strategyId, strategyParams };
1769
+ }
1770
+ function prepareCandidate(spec, base, candidate, registries) {
1771
+ const dimensions = new Map(spec.dimensions.map((dimension) => [dimension.id, dimension]));
1772
+ const modelAssigned = [];
1773
+ const strategyAssigned = [];
1774
+ const applied = new Set();
1775
+ for (const assignment of candidate.assignments) {
1776
+ const dimension = dimensions.get(assignment.dimensionId);
1777
+ if (!dimension)
1778
+ reject(`unknown dimension ${assignment.dimensionId}`);
1779
+ if (applied.has(dimension.id))
1780
+ reject(`duplicate assignment ${dimension.id}`);
1781
+ applied.add(dimension.id);
1782
+ assertRegisteredPath(dimension.path, dimension.target);
1783
+ if (!assignmentFitsParamSpace(dimension.space, assignment.value)) {
1784
+ reject(`value for ${dimension.id} is outside its parameter space`);
1785
+ }
1786
+ if (dimension.target === "model")
1787
+ modelAssigned.push({ dimension, value: assignment.value });
1788
+ else if (dimension.target === "strategy")
1789
+ strategyAssigned.push({ dimension, value: assignment.value });
1790
+ else
1791
+ reject(`session dimension ${dimension.id} is not executed`);
1792
+ }
1793
+ for (const dimension of spec.dimensions) {
1794
+ if (!applied.has(dimension.id))
1795
+ reject(`candidate ${candidate.id} omits ${dimension.id}`);
1796
+ }
1797
+ // Assignments apply in dimension id order, the order behind the effective spec hash, so a factory sees one key order.
1798
+ const assign = (params, assigned, label) => assigned.length === 0
1799
+ ? params
1800
+ : [...assigned].sort((left, right) => byText(left.dimension.id, right.dimension.id)).reduce((acc, { dimension, value }) => setRegisteredParam(acc, dimension.path, value),
1801
+ // setRegisteredParam copies the root with its prototype, so the scenario object is not spread here.
1802
+ asParamRecord(params, label));
1803
+ const modelFactory = registries.modelRegistry.get(spec.model.id, spec.model.version);
1804
+ if (!modelFactory)
1805
+ reject(`model not found: ${spec.model.id}@${spec.model.version}`);
1806
+ const modelParams = assign(base.modelParams, modelAssigned, "model");
1807
+ checkCandidateParams(modelFactory.paramsSchema, base.modelParams, modelParams, modelAssigned, "model");
1808
+ let strategyParams = {};
1809
+ if (base.strategyId !== null) {
1810
+ const strategyFactory = registries.strategyRegistry.get(base.strategyId);
1811
+ if (!strategyFactory)
1812
+ reject(`strategy not found: ${base.strategyId}`);
1813
+ strategyParams = assign(base.strategyParams, strategyAssigned, "strategy");
1814
+ checkCandidateParams(strategyFactory.paramsSchema, base.strategyParams, strategyParams, strategyAssigned, "strategy");
1815
+ }
1816
+ // Private snapshots: the task records exactly these, and every run gets its own copy of them.
1817
+ return {
1818
+ id: candidate.id,
1819
+ modelParams: cloneJsonData(modelParams),
1820
+ strategyParams: cloneJsonData(strategyParams),
1821
+ sessionPattern: spec.session.pattern,
1822
+ sessionDays: spec.session.days,
1823
+ };
1824
+ }
1825
+ function metricIds(spec) {
1826
+ const ids = new Set([spec.metricId]);
1827
+ for (const objective of spec.objectives ?? [])
1828
+ ids.add(objective.metricId);
1829
+ for (const constraint of spec.constraints ?? [])
1830
+ ids.add(constraint.metricId);
1831
+ return [...ids];
1832
+ }
1833
+ function readBenefit(vars) {
1834
+ if (vars === null || typeof vars !== "object")
1835
+ return null;
1836
+ const value = vars.benefit;
1837
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1838
+ }
1839
+ /** Metrics at the horizon. The reach time is the first committed state at or above the target, read on the way. */
1840
+ function observeMetrics(E, spec, end, startT, reachedAt,
1841
+ /** The runner elapsed clock. A censored reach time is later than it. */
1842
+ elapsedSec) {
1843
+ const amount = end.wallet.money.amount;
1844
+ const benefit = readBenefit(end.vars);
1845
+ return metricIds(spec).map((metricId) => {
1846
+ if (metricId === "timeToMoney") {
1847
+ if (reachedAt === undefined) {
1848
+ return { metricId, status: "censored", value: null, lowerBound: { sec: elapsedSec, strict: true } };
1849
+ }
1850
+ const elapsed = reachedAt - startT;
1851
+ return { metricId, status: "observed", value: String(elapsed), num: elapsed };
1852
+ }
1853
+ if (metricId === "endMoney") {
1854
+ if (!E.isFinite(amount))
1855
+ return { metricId, status: "censored", value: null };
1856
+ return { metricId, status: "observed", value: E.toString(amount), amount };
1857
+ }
1858
+ if (benefit === null)
1859
+ return { metricId, status: "censored", value: null };
1860
+ return { metricId, status: "observed", value: String(benefit), num: benefit };
1861
+ });
1862
+ }
1863
+ function failedMetrics(spec) {
1864
+ return metricIds(spec).map((metricId) => ({ metricId, status: "failed", value: null }));
1865
+ }
1866
+ /**
1867
+ * Arithmetic mean over every seed. One failed seed fails the candidate metric and one censored seed censors it,
1868
+ * so a mean never mixes a missing draw with observed ones. Money is averaged in the engine.
1869
+ */
1870
+ function aggregate(E, rows, metricId) {
1871
+ const picked = rows.map((row) => row.metrics.find((metric) => metric.metricId === metricId));
1872
+ if (picked.some((metric) => metric === undefined || metric.status === "failed")) {
1873
+ return { metricId, status: "failed", value: null };
1874
+ }
1875
+ if (picked.some((metric) => metric.status !== "observed" || metric.value === null)) {
1876
+ // Every seed is either observed or censored with a known lower bound, so the mean has a lower bound too.
1877
+ const bounded = picked.every((metric) => metric.num !== undefined || metric.lowerBound !== undefined);
1878
+ if (metricId !== "timeToMoney" || !bounded)
1879
+ return { metricId, status: "censored", value: null };
1880
+ const bounds = picked.map((metric) => metric.num ?? metric.lowerBound.sec);
1881
+ const total = bounds.reduce((sum, bound) => sum + bound, 0);
1882
+ const sec = Number.isFinite(total) ? total / bounds.length : scaledMean(bounds);
1883
+ const strict = picked.some((metric) => metric.lowerBound?.strict === true);
1884
+ return { metricId, status: "censored", value: null, lowerBound: { sec, strict } };
1885
+ }
1886
+ const observed = picked;
1887
+ const count = observed.length;
1888
+ if (metricId === "endMoney") {
1889
+ // Sum, then divide, so subnormal amounts keep their mean. Only when the sum leaves the engine range,
1890
+ // divide before adding, so large finite amounts keep a finite mean.
1891
+ const sum = observed.reduce((total, metric) => E.add(total, metric.amount), E.zero());
1892
+ const mean = E.isFinite(sum) ? E.div(sum, count) : scaledEngineMean(E, observed.map((metric) => metric.amount));
1893
+ if (!E.isFinite(mean))
1894
+ return { metricId, status: "censored", value: null };
1895
+ return { metricId, status: "observed", value: E.toString(mean), amount: mean };
1896
+ }
1897
+ // Sum, then divide, so subnormal values keep their mean. Only when the sum overflows, divide before adding,
1898
+ // so two finite values near the double limit keep a finite mean.
1899
+ const values = observed.map((metric) => metric.num);
1900
+ const sum = values.reduce((total, value) => total + value, 0);
1901
+ const mean = Number.isFinite(sum) ? sum / count : scaledMean(values);
1902
+ if (!Number.isFinite(mean))
1903
+ return { metricId, status: "censored", value: null };
1904
+ return { metricId, status: "observed", value: String(mean), num: mean };
1905
+ }
1906
+ /**
1907
+ * Mean of finite values whose sum overflows. Each value is divided by the largest magnitude, so every scaled
1908
+ * term is at most 1 and the scaled sum stays finite. The mean of the scaled terms is then scaled back.
1909
+ */
1910
+ function scaledMean(values) {
1911
+ const scale = values.reduce((largest, value) => Math.max(largest, Math.abs(value)), 0);
1912
+ return scale * (values.reduce((total, value) => total + value / scale, 0) / values.length);
1913
+ }
1914
+ function scaledEngineMean(E, values) {
1915
+ const scale = values.reduce((largest, value) => (E.absLog10(value) > E.absLog10(largest) ? value : largest), values[0]);
1916
+ const magnitude = E.cmp(scale, E.zero()) < 0 ? E.sub(E.zero(), scale) : scale;
1917
+ const scaled = values.reduce((total, value) => E.add(total, E.divN(value, magnitude)), E.zero());
1918
+ return E.mulN(magnitude, E.div(scaled, values.length));
1919
+ }
1920
+ function isObserved(reading) {
1921
+ return reading !== undefined && reading.status === "observed" && (reading.amount !== undefined || reading.num !== undefined);
1922
+ }
1923
+ function compareReadings(E, left, right) {
1924
+ if (left.amount !== undefined && right.amount !== undefined)
1925
+ return E.cmp(left.amount, right.amount);
1926
+ const a = left.num;
1927
+ const b = right.num;
1928
+ return a < b ? -1 : a > b ? 1 : 0;
1929
+ }
1930
+ function violates(E, reading, constraint) {
1931
+ const order = reading.amount !== undefined
1932
+ ? E.cmp(reading.amount, E.from(constraint.limit))
1933
+ : reading.num < constraint.limit ? -1 : reading.num > constraint.limit ? 1 : 0;
1934
+ return constraint.op === "lte" ? order > 0 : order < 0;
1935
+ }
1936
+ function readingElasticity(E, baselineP, probeP, baseline, probe) {
1937
+ if (!isObserved(baseline) || !isObserved(probe))
1938
+ return localElasticity({ baselineP, probeP, baselineY: null, probeY: null });
1939
+ if (baseline.amount !== undefined && probe.amount !== undefined) {
1940
+ const delta = E.sub(probe.amount, baseline.amount);
1941
+ // Two finite amounts of opposite sign can differ by more than the engine range. Their ratio stays finite.
1942
+ const finiteDelta = E.isFinite(delta);
1943
+ const order = E.cmp(probe.amount, baseline.amount);
1944
+ const unavailable = elasticityFromChange({
1945
+ baselineP,
1946
+ probeP,
1947
+ absoluteChange: finiteDelta ? E.toString(delta) : null,
1948
+ direction: order > 0 ? "higher" : order < 0 ? "lower" : "flat",
1949
+ relativeChange: null,
1950
+ });
1951
+ const pChanged = Number.isFinite(baselineP) && Number.isFinite(probeP) && probeP !== baselineP;
1952
+ if (!(E.cmp(baseline.amount, E.zero()) > 0) || !(baselineP > 0) || !pChanged)
1953
+ return unavailable;
1954
+ // The ratio of two engine amounts can leave the double range, so the elasticity stays in the engine until it is text.
1955
+ const relative = finiteDelta ? E.divN(delta, baseline.amount) : E.sub(E.divN(probe.amount, baseline.amount), E.from(1));
1956
+ // A tiny positive baseline can make the parameter change overflow. Dividing by it would report a false 0.
1957
+ const parameterChange = relativeParameterChange(baselineP, probeP);
1958
+ if (!Number.isFinite(parameterChange) || parameterChange === 0)
1959
+ return unavailable;
1960
+ const elasticity = E.div(relative, parameterChange);
1961
+ if (!E.isFinite(elasticity))
1962
+ return unavailable;
1963
+ return { ...unavailable, status: "observed", elasticity: E.toString(elasticity) };
1964
+ }
1965
+ return localElasticity({ baselineP, probeP, baselineY: baseline.num ?? null, probeY: probe.num ?? null });
1966
+ }
1967
+ /**
1968
+ * Run options every candidate inherits from the compiled scenario.
1969
+ * A scenario stop condition or step budget would end a candidate before the spec horizon, and a report trace
1970
+ * would keep every state of every candidate and seed while the experiment reads only the end state.
1971
+ *
1972
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-censored-horizon Removes the scenario stop condition, step budget, and trace, so each candidate runs to the horizon without retaining states.
1973
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-censored-horizon #befcebb Re-read the section: until, maxSteps, and trace are removed and the rest of the run options stay.
1974
+ */
1975
+ export function experimentRunBase(run) {
1976
+ const { until: _until, maxSteps: _maxSteps, trace: _trace, ...horizonRun } = run;
1977
+ return horizonRun;
1978
+ }
1979
+ /** Every rejection that does not need a model instance. No factory `create` runs here. */
1980
+ function prepareExperiment(request) {
1981
+ // Validate the spec shape and the execution state first, so an untyped caller gets ExperimentRejected
1982
+ // rather than a TypeError from hashing, for example on a bigint.
1983
+ const spec = requireSpec(request.spec);
1984
+ const metrics = metricIds(spec);
1985
+ // A JSON literal such as 1e400 parses to Infinity, and canonical JSON would hash it as null.
1986
+ if (spec.goal !== undefined && !Number.isFinite(spec.goal.target))
1987
+ reject("goal target must be finite");
1988
+ if (metrics.includes("timeToMoney") && spec.goal === undefined)
1989
+ reject("timeToMoney requires a finite goal target");
1990
+ // Every spec field the run reads must be one both spec hashes record.
1991
+ if (!isJsonData(request.spec, new Set(), true))
1992
+ reject("spec must be JSON data, because the spec hashes record it as JSON");
1993
+ requireScenarioData(request.scenario);
1994
+ let scenarioHash;
1995
+ try {
1996
+ scenarioHash = sha256Text(canonicalJson(request.scenario));
1997
+ }
1998
+ catch {
1999
+ reject("scenario cannot be written as canonical JSON");
2000
+ }
2001
+ if (scenarioHash !== spec.scenario.hash)
2002
+ reject("scenario hash does not match the experiment spec");
2003
+ // Both spec hashes are taken now, so a field canonical JSON cannot write fails before any model is created.
2004
+ let specHashes;
2005
+ try {
2006
+ specHashes = { original: sha256Text(canonicalJson(request.spec)), effective: sha256Text(canonicalJson(normalizeSpec(spec))) };
2007
+ }
2008
+ catch {
2009
+ reject("spec cannot be written as canonical JSON");
2010
+ }
2011
+ const base = candidateBase(spec, request.scenario, request);
2012
+ const prepared = plannedCandidates(spec).map((candidate) => prepareCandidate(spec, base, candidate, request));
2013
+ return { spec, metrics, base, prepared, specHashes };
2014
+ }
2015
+ /**
2016
+ * Runs every spec, scenario-hash, path, and parameter check of `runExperiment` without creating a model.
2017
+ * A caller that compiles or opens a run first calls this, so a rejected spec never reaches a factory.
2018
+ *
2019
+ * @evidence docs/requirements/active/parameter-experiments.md#req-pr14-registered-setter Throws the same rejections as runExperiment before any model create.
2020
+ * @evidenceReview docs/requirements/active/parameter-experiments.md#req-pr14-registered-setter #847f7d0 Re-read the section: the hash, spec, path, and schema checks run and no factory create is called.
2021
+ */
2022
+ export function checkExperimentSpec(request) {
2023
+ prepareExperiment(request);
2024
+ }
2025
+ export function runExperiment(request) {
2026
+ const { spec, metrics, base, prepared, specHashes } = prepareExperiment(request);
2027
+ const measuresReach = metrics.includes("timeToMoney");
2028
+ const E = request.engine;
2029
+ // The plan builds the strategy, so compile does not build the scenario one.
2030
+ const { strategy: _scenarioStrategy, ...withoutStrategy } = request.scenario;
2031
+ // Compile reads copies, so its model create cannot write the caller's params or initial state.
2032
+ const compiled = compileScenario({
2033
+ E,
2034
+ scenario: {
2035
+ ...withoutStrategy,
2036
+ model: { ...withoutStrategy.model, params: cloneJsonData(withoutStrategy.model.params) },
2037
+ initial: cloneJsonData(withoutStrategy.initial),
2038
+ },
2039
+ registry: request.modelRegistry,
2040
+ strategyRegistry: request.strategyRegistry,
2041
+ opts: { allowSuffixNotation: true },
2042
+ });
2043
+ const shared = { ...compiled, run: experimentRunBase(compiled.run) };
2044
+ const bindingFor = createRunFactory({
2045
+ models: request.modelRegistry,
2046
+ strategies: request.strategyRegistry,
2047
+ });
2048
+ const target = measuresReach && spec.goal !== undefined ? E.from(spec.goal.target) : undefined;
2049
+ // Stopping at the goal is safe only when no other metric reads the horizon state.
2050
+ const stopAtReach = metrics.every((metricId) => metricId === "timeToMoney");
2051
+ const tasks = [];
2052
+ const perCandidate = [];
2053
+ for (const candidate of prepared) {
2054
+ const rows = [];
2055
+ for (const seed of spec.seeds) {
2056
+ // Each seed binds its own copy of the params, so a factory that mutates them cannot change a later seed.
2057
+ const binding = bindingFor.bind(shared, {
2058
+ statefulModel: true,
2059
+ model: { id: spec.model.id, version: spec.model.version, params: cloneJsonData(candidate.modelParams) },
2060
+ });
2061
+ const taskId = `${candidate.id}#${seed}`;
2062
+ const plan = {
2063
+ contract: "idlekit.execution-plan",
2064
+ version: 1,
2065
+ stepSec: spec.stepSec,
2066
+ durationSec: spec.horizonSec,
2067
+ seed,
2068
+ ...(base.strategyId !== null ? { strategyId: base.strategyId, strategyParams: cloneJsonData(candidate.strategyParams) } : {}),
2069
+ eventLog: { enabled: false, maxEvents: 0 },
2070
+ };
2071
+ let readings;
2072
+ try {
2073
+ const opened = binding.fresh({ trialId: taskId, seed, plan });
2074
+ const scenario = opened.scenario;
2075
+ let reachedAt;
2076
+ const result = runScenario({
2077
+ ...scenario,
2078
+ run: {
2079
+ ...scenario.run,
2080
+ ...(target !== undefined
2081
+ ? {
2082
+ until: (state) => {
2083
+ if (reachedAt === undefined && E.cmp(state.wallet.money.amount, target) >= 0)
2084
+ reachedAt = state.t;
2085
+ return stopAtReach && reachedAt !== undefined;
2086
+ },
2087
+ }
2088
+ : {}),
2089
+ },
2090
+ });
2091
+ readings = observeMetrics(E, spec, result.end, result.start.t, reachedAt, runElapsedSec(result));
2092
+ }
2093
+ catch {
2094
+ readings = failedMetrics(spec);
2095
+ }
2096
+ const primary = readings.find((metric) => metric.metricId === spec.metricId) ?? failedMetrics(spec)[0];
2097
+ tasks.push({
2098
+ taskId,
2099
+ candidateId: candidate.id,
2100
+ logicalSeed: seed,
2101
+ engineId: request.engineId,
2102
+ engineVersion: request.engineVersion,
2103
+ modelId: spec.model.id,
2104
+ modelVersion: spec.model.version,
2105
+ strategyId: base.strategyId,
2106
+ modelParams: canonicalJson(candidate.modelParams ?? null),
2107
+ strategyParams: canonicalJson(candidate.strategyParams ?? null),
2108
+ sessionPattern: candidate.sessionPattern,
2109
+ sessionDays: candidate.sessionDays,
2110
+ horizonSec: spec.horizonSec,
2111
+ stepSec: spec.stepSec,
2112
+ metricId: spec.metricId,
2113
+ status: primary.status,
2114
+ value: primary.value,
2115
+ });
2116
+ rows.push({ seed, metrics: readings });
2117
+ }
2118
+ perCandidate.push({ id: candidate.id, rows });
2119
+ }
2120
+ const measured = perCandidate.map((candidate) => {
2121
+ const readings = metrics.map((metricId) => aggregate(E, candidate.rows, metricId));
2122
+ const constraintViolations = (spec.constraints ?? [])
2123
+ .filter((constraint) => {
2124
+ const reading = readings.find((metric) => metric.metricId === constraint.metricId);
2125
+ // A censored reach time is later than its lower bound. That proves an lte violation once the bound passes the limit.
2126
+ const bound = reading !== undefined && reading.status === "censored" ? reading.lowerBound : undefined;
2127
+ if (isObserved(reading))
2128
+ return violates(E, reading, constraint);
2129
+ return constraint.op === "lte" && bound !== undefined && (bound.sec > constraint.limit || (bound.strict && bound.sec >= constraint.limit));
2130
+ })
2131
+ .map((constraint) => constraint.id);
2132
+ return { id: candidate.id, readings, constraintViolations };
2133
+ });
2134
+ const baseline = measured.find((candidate) => candidate.id === "baseline");
2135
+ const elasticities = baseline
2136
+ ? // The normalized order, so reordered dimensions with one effective hash give one artifact.
2137
+ [...spec.dimensions].sort((left, right) => byText(left.id, right.id)).flatMap((dimension) => (dimension.probes ?? []).flatMap((probe) => {
2138
+ const candidateId = probeCandidateId(dimension, probe);
2139
+ const probeCandidate = measured.find((candidate) => candidate.id === candidateId);
2140
+ if (!probeCandidate)
2141
+ return [];
2142
+ return metrics.map((metricId) => {
2143
+ const local = readingElasticity(E, typeof dimension.baseline === "number" ? dimension.baseline : Number.NaN, typeof probe === "number" ? probe : Number.NaN, baseline.readings.find((metric) => metric.metricId === metricId), probeCandidate.readings.find((metric) => metric.metricId === metricId));
2144
+ return {
2145
+ dimensionId: dimension.id,
2146
+ candidateId,
2147
+ metricId,
2148
+ method: "one-at-a-time-local",
2149
+ methodVersion: 1,
2150
+ globalSensitivity: false,
2151
+ sobol: false,
2152
+ status: local.status,
2153
+ elasticity: local.elasticity,
2154
+ absoluteChange: local.absoluteChange,
2155
+ direction: local.direction,
2156
+ };
2157
+ });
2158
+ }))
2159
+ : [];
2160
+ const objectives = spec.objectives ?? [];
2161
+ // Each pair of candidates is compared in the engine. Ranks would chain the engine tolerance through a third value.
2162
+ const pareto = objectives.length === 0
2163
+ ? { sense: "per-metric", nonDominated: [], uncompared: [] }
2164
+ : paretoFrontBy(measured.map((candidate) => ({
2165
+ id: candidate.id,
2166
+ objectives: objectives.map((objective) => {
2167
+ const reading = candidate.readings.find((metric) => metric.metricId === objective.metricId);
2168
+ return { sense: objective.sense, value: isObserved(reading) ? reading : null };
2169
+ }),
2170
+ })), (left, right) => compareReadings(E, left, right));
2171
+ const candidates = measured.map((candidate) => ({
2172
+ id: candidate.id,
2173
+ metrics: candidate.readings.map(({ metricId, status, value }) => ({ metricId, status, value })),
2174
+ constraintViolations: candidate.constraintViolations,
2175
+ }));
2176
+ tasks.sort((left, right) => byText(left.candidateId, right.candidateId) || left.logicalSeed - right.logicalSeed);
2177
+ const artifact = {
2178
+ schemaVersion: 1,
2179
+ contract: "idlekit.experiment.v1",
2180
+ estimatorVersion: EXPERIMENT_ESTIMATOR_VERSION,
2181
+ algorithmVersion: EXPERIMENT_ALGORITHM_VERSION,
2182
+ method: EXPERIMENT_METHOD,
2183
+ globalSensitivity: false,
2184
+ sobol: false,
2185
+ scenario: spec.scenario,
2186
+ originalSpecHash: specHashes.original,
2187
+ effectiveSpecHash: specHashes.effective,
2188
+ effectiveRunHash: request.effectiveRunHash,
2189
+ budget: {
2190
+ candidates: spec.budget.candidates,
2191
+ seeds: spec.budget.seeds,
2192
+ usedCandidates: prepared.length,
2193
+ usedSeeds: spec.seeds.length,
2194
+ },
2195
+ tasks,
2196
+ candidates: [...candidates].sort((left, right) => byText(left.id, right.id)),
2197
+ elasticities,
2198
+ pareto,
2199
+ warnings: WARNINGS,
2200
+ };
2201
+ const checked = validateExperimentArtifact(artifact);
2202
+ if (!checked.success)
2203
+ reject("artifact failed its own structural validator");
2204
+ return artifact;
2205
+ }
2206
+ //# sourceMappingURL=experiment.js.map