@agentplat/mesh-sim 0.3.0-beta.2 → 0.3.0-beta.4

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 (185) hide show
  1. package/README.md +447 -2
  2. package/dist/collective-closed-loop-action.d.ts +80 -0
  3. package/dist/collective-closed-loop-action.d.ts.map +1 -0
  4. package/dist/collective-closed-loop-action.js +471 -0
  5. package/dist/collective-closed-loop-action.js.map +1 -0
  6. package/dist/collective-closed-loop-contracts.d.ts +75 -0
  7. package/dist/collective-closed-loop-contracts.d.ts.map +1 -0
  8. package/dist/collective-closed-loop-contracts.js +420 -0
  9. package/dist/collective-closed-loop-contracts.js.map +1 -0
  10. package/dist/collective-closed-loop-fault-matrix.d.ts +101 -0
  11. package/dist/collective-closed-loop-fault-matrix.d.ts.map +1 -0
  12. package/dist/collective-closed-loop-fault-matrix.js +253 -0
  13. package/dist/collective-closed-loop-fault-matrix.js.map +1 -0
  14. package/dist/collective-closed-loop-recovery.d.ts +70 -0
  15. package/dist/collective-closed-loop-recovery.d.ts.map +1 -0
  16. package/dist/collective-closed-loop-recovery.js +823 -0
  17. package/dist/collective-closed-loop-recovery.js.map +1 -0
  18. package/dist/collective-closed-loop-reference.d.ts +54 -0
  19. package/dist/collective-closed-loop-reference.d.ts.map +1 -0
  20. package/dist/collective-closed-loop-reference.js +1696 -0
  21. package/dist/collective-closed-loop-reference.js.map +1 -0
  22. package/dist/collective-closed-loop-replanning.d.ts +27 -0
  23. package/dist/collective-closed-loop-replanning.d.ts.map +1 -0
  24. package/dist/collective-closed-loop-replanning.js +151 -0
  25. package/dist/collective-closed-loop-replanning.js.map +1 -0
  26. package/dist/collective-closed-loop-resilience-campaign.d.ts +22 -0
  27. package/dist/collective-closed-loop-resilience-campaign.d.ts.map +1 -0
  28. package/dist/collective-closed-loop-resilience-campaign.js +196 -0
  29. package/dist/collective-closed-loop-resilience-campaign.js.map +1 -0
  30. package/dist/collective-closed-loop-resilience-contracts.d.ts +138 -0
  31. package/dist/collective-closed-loop-resilience-contracts.d.ts.map +1 -0
  32. package/dist/collective-closed-loop-resilience-contracts.js +677 -0
  33. package/dist/collective-closed-loop-resilience-contracts.js.map +1 -0
  34. package/dist/collective-closed-loop-runner.d.ts +133 -0
  35. package/dist/collective-closed-loop-runner.d.ts.map +1 -0
  36. package/dist/collective-closed-loop-runner.js +1429 -0
  37. package/dist/collective-closed-loop-runner.js.map +1 -0
  38. package/dist/collective-closed-loop-runtime.d.ts +95 -0
  39. package/dist/collective-closed-loop-runtime.d.ts.map +1 -0
  40. package/dist/collective-closed-loop-runtime.js +1192 -0
  41. package/dist/collective-closed-loop-runtime.js.map +1 -0
  42. package/dist/collective-effect-provenance.d.ts +5 -0
  43. package/dist/collective-effect-provenance.d.ts.map +1 -0
  44. package/dist/collective-effect-provenance.js +32 -0
  45. package/dist/collective-effect-provenance.js.map +1 -0
  46. package/dist/collective-environment.d.ts +49 -0
  47. package/dist/collective-environment.d.ts.map +1 -0
  48. package/dist/collective-environment.js +907 -0
  49. package/dist/collective-environment.js.map +1 -0
  50. package/dist/collective-progressive-scale-overlay.d.ts +39 -0
  51. package/dist/collective-progressive-scale-overlay.d.ts.map +1 -0
  52. package/dist/collective-progressive-scale-overlay.js +147 -0
  53. package/dist/collective-progressive-scale-overlay.js.map +1 -0
  54. package/dist/collective-progressive-scale.d.ts +202 -0
  55. package/dist/collective-progressive-scale.d.ts.map +1 -0
  56. package/dist/collective-progressive-scale.js +755 -0
  57. package/dist/collective-progressive-scale.js.map +1 -0
  58. package/dist/collective-statistical-campaign-aggregation.d.ts +97 -0
  59. package/dist/collective-statistical-campaign-aggregation.d.ts.map +1 -0
  60. package/dist/collective-statistical-campaign-aggregation.js +446 -0
  61. package/dist/collective-statistical-campaign-aggregation.js.map +1 -0
  62. package/dist/collective-statistical-campaign-artifact-stream.d.ts +66 -0
  63. package/dist/collective-statistical-campaign-artifact-stream.d.ts.map +1 -0
  64. package/dist/collective-statistical-campaign-artifact-stream.js +207 -0
  65. package/dist/collective-statistical-campaign-artifact-stream.js.map +1 -0
  66. package/dist/collective-statistical-campaign-bundle.d.ts +151 -0
  67. package/dist/collective-statistical-campaign-bundle.d.ts.map +1 -0
  68. package/dist/collective-statistical-campaign-bundle.js +885 -0
  69. package/dist/collective-statistical-campaign-bundle.js.map +1 -0
  70. package/dist/collective-statistical-campaign-executor.d.ts +131 -0
  71. package/dist/collective-statistical-campaign-executor.d.ts.map +1 -0
  72. package/dist/collective-statistical-campaign-executor.js +856 -0
  73. package/dist/collective-statistical-campaign-executor.js.map +1 -0
  74. package/dist/collective-statistical-campaign-normative-analysis.d.ts +85 -0
  75. package/dist/collective-statistical-campaign-normative-analysis.d.ts.map +1 -0
  76. package/dist/collective-statistical-campaign-normative-analysis.js +483 -0
  77. package/dist/collective-statistical-campaign-normative-analysis.js.map +1 -0
  78. package/dist/collective-statistical-campaign-normative-operation.d.ts +89 -0
  79. package/dist/collective-statistical-campaign-normative-operation.d.ts.map +1 -0
  80. package/dist/collective-statistical-campaign-normative-operation.js +266 -0
  81. package/dist/collective-statistical-campaign-normative-operation.js.map +1 -0
  82. package/dist/collective-statistical-campaign-normative-registry.d.ts +24 -0
  83. package/dist/collective-statistical-campaign-normative-registry.d.ts.map +1 -0
  84. package/dist/collective-statistical-campaign-normative-registry.js +163 -0
  85. package/dist/collective-statistical-campaign-normative-registry.js.map +1 -0
  86. package/dist/collective-statistical-campaign-registered-adapter.d.ts +16 -0
  87. package/dist/collective-statistical-campaign-registered-adapter.d.ts.map +1 -0
  88. package/dist/collective-statistical-campaign-registered-adapter.js +518 -0
  89. package/dist/collective-statistical-campaign-registered-adapter.js.map +1 -0
  90. package/dist/collective-statistical-campaign-scale.d.ts +85 -0
  91. package/dist/collective-statistical-campaign-scale.d.ts.map +1 -0
  92. package/dist/collective-statistical-campaign-scale.js +349 -0
  93. package/dist/collective-statistical-campaign-scale.js.map +1 -0
  94. package/dist/collective-trace-journal.d.ts +20 -0
  95. package/dist/collective-trace-journal.d.ts.map +1 -0
  96. package/dist/collective-trace-journal.js +105 -0
  97. package/dist/collective-trace-journal.js.map +1 -0
  98. package/dist/index.d.ts +25 -0
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +25 -0
  101. package/dist/index.js.map +1 -1
  102. package/dist/multi-domain-environment-contracts.d.ts +122 -0
  103. package/dist/multi-domain-environment-contracts.d.ts.map +1 -0
  104. package/dist/multi-domain-environment-contracts.js +14 -0
  105. package/dist/multi-domain-environment-contracts.js.map +1 -0
  106. package/dist/multi-domain-environment-runtime.d.ts +25 -0
  107. package/dist/multi-domain-environment-runtime.d.ts.map +1 -0
  108. package/dist/multi-domain-environment-runtime.js +493 -0
  109. package/dist/multi-domain-environment-runtime.js.map +1 -0
  110. package/dist/multi-domain-environment-validation.d.ts +33 -0
  111. package/dist/multi-domain-environment-validation.d.ts.map +1 -0
  112. package/dist/multi-domain-environment-validation.js +415 -0
  113. package/dist/multi-domain-environment-validation.js.map +1 -0
  114. package/dist/multi-domain-environment.d.ts +4 -0
  115. package/dist/multi-domain-environment.d.ts.map +1 -0
  116. package/dist/multi-domain-environment.js +4 -0
  117. package/dist/multi-domain-environment.js.map +1 -0
  118. package/dist/reducer-scenario.d.ts.map +1 -1
  119. package/dist/reducer-scenario.js +67 -19
  120. package/dist/reducer-scenario.js.map +1 -1
  121. package/dist/scalable-evaluation-contracts.d.ts +835 -0
  122. package/dist/scalable-evaluation-contracts.d.ts.map +1 -0
  123. package/dist/scalable-evaluation-contracts.js +22 -0
  124. package/dist/scalable-evaluation-contracts.js.map +1 -0
  125. package/dist/scalable-evaluation-durability.d.ts +16 -0
  126. package/dist/scalable-evaluation-durability.d.ts.map +1 -0
  127. package/dist/scalable-evaluation-durability.js +339 -0
  128. package/dist/scalable-evaluation-durability.js.map +1 -0
  129. package/dist/scalable-evaluation-evidence.d.ts +93 -0
  130. package/dist/scalable-evaluation-evidence.d.ts.map +1 -0
  131. package/dist/scalable-evaluation-evidence.js +470 -0
  132. package/dist/scalable-evaluation-evidence.js.map +1 -0
  133. package/dist/scalable-evaluation-reference-integrated.d.ts +87 -0
  134. package/dist/scalable-evaluation-reference-integrated.d.ts.map +1 -0
  135. package/dist/scalable-evaluation-reference-integrated.js +1318 -0
  136. package/dist/scalable-evaluation-reference-integrated.js.map +1 -0
  137. package/dist/scalable-evaluation-runner.d.ts +8 -0
  138. package/dist/scalable-evaluation-runner.d.ts.map +1 -0
  139. package/dist/scalable-evaluation-runner.js +1810 -0
  140. package/dist/scalable-evaluation-runner.js.map +1 -0
  141. package/dist/scalable-evaluation-runtime.d.ts +86 -0
  142. package/dist/scalable-evaluation-runtime.d.ts.map +1 -0
  143. package/dist/scalable-evaluation-runtime.js +1310 -0
  144. package/dist/scalable-evaluation-runtime.js.map +1 -0
  145. package/dist/scalable-evaluation-validation.d.ts +59 -0
  146. package/dist/scalable-evaluation-validation.d.ts.map +1 -0
  147. package/dist/scalable-evaluation-validation.js +377 -0
  148. package/dist/scalable-evaluation-validation.js.map +1 -0
  149. package/dist/scalable-evaluation.d.ts +8 -0
  150. package/dist/scalable-evaluation.d.ts.map +1 -0
  151. package/dist/scalable-evaluation.js +8 -0
  152. package/dist/scalable-evaluation.js.map +1 -0
  153. package/dist/sharded-simulation-contracts.d.ts +240 -0
  154. package/dist/sharded-simulation-contracts.d.ts.map +1 -0
  155. package/dist/sharded-simulation-contracts.js +199 -0
  156. package/dist/sharded-simulation-contracts.js.map +1 -0
  157. package/dist/sharded-simulation-runtime.d.ts +64 -0
  158. package/dist/sharded-simulation-runtime.d.ts.map +1 -0
  159. package/dist/sharded-simulation-runtime.js +755 -0
  160. package/dist/sharded-simulation-runtime.js.map +1 -0
  161. package/dist/sharded-simulation-validation.d.ts +10 -0
  162. package/dist/sharded-simulation-validation.d.ts.map +1 -0
  163. package/dist/sharded-simulation-validation.js +250 -0
  164. package/dist/sharded-simulation-validation.js.map +1 -0
  165. package/dist/sharded-simulation.d.ts +46 -0
  166. package/dist/sharded-simulation.d.ts.map +1 -0
  167. package/dist/sharded-simulation.js +32 -0
  168. package/dist/sharded-simulation.js.map +1 -0
  169. package/dist/verified-benchmark-contracts.d.ts +132 -0
  170. package/dist/verified-benchmark-contracts.d.ts.map +1 -0
  171. package/dist/verified-benchmark-contracts.js +2 -0
  172. package/dist/verified-benchmark-contracts.js.map +1 -0
  173. package/dist/verified-benchmark-runtime.d.ts +20 -0
  174. package/dist/verified-benchmark-runtime.d.ts.map +1 -0
  175. package/dist/verified-benchmark-runtime.js +199 -0
  176. package/dist/verified-benchmark-runtime.js.map +1 -0
  177. package/dist/verified-benchmark-validation.d.ts +64 -0
  178. package/dist/verified-benchmark-validation.d.ts.map +1 -0
  179. package/dist/verified-benchmark-validation.js +481 -0
  180. package/dist/verified-benchmark-validation.js.map +1 -0
  181. package/dist/verified-benchmark.d.ts +4 -0
  182. package/dist/verified-benchmark.d.ts.map +1 -0
  183. package/dist/verified-benchmark.js +4 -0
  184. package/dist/verified-benchmark.js.map +1 -0
  185. package/package.json +18 -8
@@ -0,0 +1,1310 @@
1
+ import { deepFreezePlanning, } from "@agentplat/collective-planning";
2
+ import { createMultiDomainScenarioManifestV1, validateMultiDomainEnvironmentDescriptorV1, validateMultiDomainObservationEnvelopeV1, validateMultiDomainScenarioManifestV1, } from "./multi-domain-environment-validation.js";
3
+ import { shardedSimulationDigestV1 } from "./sharded-simulation-contracts.js";
4
+ import { isVerifiedScalableEvaluationPerturbationReceiptV1, isVerifiedScalableEvaluationRecoveryReceiptV1, } from "./scalable-evaluation-evidence.js";
5
+ import { isReferenceIntegratedScalableEvaluationTeamPortV1, REFERENCE_INTEGRATED_SCALABLE_EVALUATION_IMPLEMENTATION_ID_V1, } from "./scalable-evaluation-reference-integrated.js";
6
+ import { scalableEvaluationDigestV1, validateScalableEvaluationDefinitionV1, validateScalableEvaluationTeamDescriptorV1, } from "./scalable-evaluation-validation.js";
7
+ const DIGEST = /^sha256:[0-9a-f]{64}$/u;
8
+ const MAXIMUM_METRICS_PER_BASELINE = 64;
9
+ const ACCOUNTING_DOMAINS = ["physical", "social", "cyber"];
10
+ const encoder = new TextEncoder();
11
+ const scalableEvaluationRuntimeInvokersV1 = new WeakMap();
12
+ export class ScalableEvaluationEnvironmentCancelledErrorV1 extends Error {
13
+ constructor() {
14
+ super("scalable evaluation environment opening was cancelled");
15
+ this.name = "ScalableEvaluationEnvironmentCancelledErrorV1";
16
+ }
17
+ }
18
+ /**
19
+ * Creates and opens one environment only. It does not start sessions, bind
20
+ * shards, invoke teams, or execute a campaign.
21
+ */
22
+ export async function openScalableEvaluationEnvironmentV1(input) {
23
+ assertEnvironmentNotCancelled(input.abortSignal);
24
+ const descriptor = validateMultiDomainEnvironmentDescriptorV1(input.adapter.descriptor);
25
+ const definition = validateScalableEvaluationDefinitionV1(input.definition, descriptor);
26
+ const expected = createMultiDomainScenarioManifestV1({
27
+ descriptor,
28
+ definition: definition.scenario,
29
+ });
30
+ const manifest = validateMultiDomainScenarioManifestV1(await input.adapter.createScenario(definition.scenario), descriptor);
31
+ assertEnvironmentNotCancelled(input.abortSignal);
32
+ if (manifest.manifestDigest !== expected.manifestDigest ||
33
+ manifest.manifestDigest !== definition.scenarioManifestDigest)
34
+ fail("environment_scenario_substitution");
35
+ const bridge = await input.adapter.openScenario({ manifest });
36
+ if (!bridge ||
37
+ typeof bridge !== "object" ||
38
+ [
39
+ "createSession",
40
+ "startEpisode",
41
+ "bindShardAssignments",
42
+ "pullPartialObservation",
43
+ "requestEffect",
44
+ "deliverCrossShardBatch",
45
+ "checkpoint",
46
+ "restore",
47
+ ].some((method) => typeof bridge[method] !==
48
+ "function"))
49
+ fail("environment_bridge_invalid");
50
+ assertEnvironmentNotCancelled(input.abortSignal);
51
+ return Object.freeze({ schemaVersion: 1, manifest, bridge });
52
+ }
53
+ function assertEnvironmentNotCancelled(signal) {
54
+ if (signal?.aborted)
55
+ throw new ScalableEvaluationEnvironmentCancelledErrorV1();
56
+ }
57
+ /** Binds either implementation to either side without granting evaluator state. */
58
+ export function bindScalableEvaluationTeamsV1(input) {
59
+ validateDefinitionDigestOnly(input.definition);
60
+ const byId = new Map();
61
+ for (const port of input.ports) {
62
+ if (!port || typeof port.stepV1 !== "function")
63
+ fail("team_port_invalid");
64
+ const descriptor = validateScalableEvaluationTeamDescriptorV1(port.descriptor);
65
+ if (descriptor.implementationId ===
66
+ REFERENCE_INTEGRATED_SCALABLE_EVALUATION_IMPLEMENTATION_ID_V1 &&
67
+ !isReferenceIntegratedScalableEvaluationTeamPortV1(port))
68
+ fail("reference_integrated_team_port_not_genuine");
69
+ if (byId.has(descriptor.teamId))
70
+ fail("team_port_duplicate");
71
+ byId.set(descriptor.teamId, port);
72
+ }
73
+ const left = byId.get(input.definition.matchup.leftTeamId);
74
+ const right = byId.get(input.definition.matchup.rightTeamId);
75
+ if (!left || !right)
76
+ fail("team_port_missing");
77
+ const expected = new Map(input.definition.teams.map((team) => [team.teamId, team]));
78
+ for (const port of [left, right]) {
79
+ const registered = expected.get(port.descriptor.teamId);
80
+ if (!registered ||
81
+ registered.descriptorDigest !== port.descriptor.descriptorDigest)
82
+ fail("team_port_descriptor_mismatch");
83
+ }
84
+ return Object.freeze({
85
+ schemaVersion: 1,
86
+ matchup: input.definition.matchup,
87
+ left,
88
+ right,
89
+ });
90
+ }
91
+ /**
92
+ * Incremental, provider-neutral evaluation accounting. State grows with two
93
+ * teams, three domains, configured perturbations, and a bounded record tail;
94
+ * it never grows with the declared agent population.
95
+ */
96
+ export class InMemoryScalableEvaluationRuntimeV1 {
97
+ #definition;
98
+ #descriptor;
99
+ #teams = new Map();
100
+ #perturbationPlan = new Map();
101
+ #baselines = new Map();
102
+ #perturbationObservations = new Map();
103
+ #recoveries = new Map();
104
+ #teamEnvironmentBindings = new Map();
105
+ #recordTail = [];
106
+ #recordTailCursor = 0;
107
+ #revision = 0;
108
+ #predecessorStateDigest = null;
109
+ constructor(input) {
110
+ this.#descriptor = validateMultiDomainEnvironmentDescriptorV1(input.descriptor);
111
+ this.#definition = validateScalableEvaluationDefinitionV1(input.definition, this.#descriptor);
112
+ for (const perturbation of this.#definition.perturbations)
113
+ this.#perturbationPlan.set(perturbation.perturbationId, perturbation);
114
+ for (const team of this.#definition.teams) {
115
+ this.#teams.set(team.teamId, {
116
+ descriptor: team,
117
+ sequence: 0,
118
+ lastLogicalTime: 0,
119
+ chainDigest: scalableEvaluationDigestV1("accounting-genesis", {
120
+ definitionDigest: this.#definition.definitionDigest,
121
+ teamId: team.teamId,
122
+ }),
123
+ counters: mutableZeroCounters(),
124
+ byDomain: {
125
+ physical: mutableZeroCounters(),
126
+ social: mutableZeroCounters(),
127
+ cyber: mutableZeroCounters(),
128
+ },
129
+ });
130
+ }
131
+ const invokers = Object.freeze({
132
+ definition: this.#definition,
133
+ bindTeamEnvironment: canonicalBindTeamEnvironmentV1.bind(this),
134
+ recordAccounting: canonicalRecordAccountingV1.bind(this),
135
+ recordPartialObservation: canonicalRecordPartialObservationV1.bind(this),
136
+ registerRecoveryBaseline: canonicalRegisterRecoveryBaselineV1.bind(this),
137
+ recordPerturbationObservation: canonicalRecordPerturbationObservationV1.bind(this),
138
+ recordRecoverySample: canonicalRecordRecoverySampleV1.bind(this),
139
+ snapshot: canonicalSnapshotV1.bind(this),
140
+ compare: canonicalCompareV1.bind(this),
141
+ exportState: canonicalExportStateV1.bind(this),
142
+ });
143
+ scalableEvaluationRuntimeInvokersV1.set(this, invokers);
144
+ Object.defineProperties(this, {
145
+ definition: immutableValue(this.#definition, true),
146
+ bindTeamEnvironmentV1: immutableMethod(invokers.bindTeamEnvironment),
147
+ recordAccountingV1: immutableMethod(invokers.recordAccounting),
148
+ recordPartialObservationV1: immutableMethod(invokers.recordPartialObservation),
149
+ registerRecoveryBaselineV1: immutableMethod(invokers.registerRecoveryBaseline),
150
+ recordPerturbationObservationV1: immutableMethod(invokers.recordPerturbationObservation),
151
+ recordRecoverySampleV1: immutableMethod(invokers.recordRecoverySample),
152
+ snapshotV1: immutableMethod(invokers.snapshot),
153
+ compareV1: immutableMethod(invokers.compare),
154
+ exportStateV1: immutableMethod(invokers.exportState),
155
+ });
156
+ if (input.state)
157
+ this.#restoreState(input.state);
158
+ }
159
+ get definition() {
160
+ return this.#definition;
161
+ }
162
+ /** Binds evaluator evidence to the exact environment run for one team. */
163
+ bindTeamEnvironmentV1(input) {
164
+ this.#team(input.teamId);
165
+ identifier(input.sessionId, "team_environment_session_id");
166
+ identifier(input.episodeId, "team_environment_episode_id");
167
+ const current = this.#teamEnvironmentBindings.get(input.teamId);
168
+ if (current) {
169
+ if (current.sessionId !== input.sessionId ||
170
+ current.episodeId !== input.episodeId)
171
+ fail("team_environment_binding_changed");
172
+ return;
173
+ }
174
+ if ([...this.#teamEnvironmentBindings.values()].some((binding) => binding.sessionId === input.sessionId ||
175
+ binding.episodeId === input.episodeId))
176
+ fail("team_environment_binding_not_isolated");
177
+ this.#advanceRevision();
178
+ this.#teamEnvironmentBindings.set(input.teamId, freeze({
179
+ sessionId: input.sessionId,
180
+ episodeId: input.episodeId,
181
+ }));
182
+ }
183
+ recordAccountingV1(input) {
184
+ const value = validateAccountingInput(input);
185
+ const team = this.#team(value.teamId);
186
+ if (!this.#definition.domains.includes(value.domain))
187
+ fail("accounting_domain_not_in_scenario");
188
+ if (value.sequence !== team.sequence + 1)
189
+ fail("accounting_sequence_invalid");
190
+ if (value.logicalTime < team.lastLogicalTime)
191
+ fail("accounting_logical_time_regression");
192
+ validateActivitySemantics(value, this.#definition.domains, this.#definition.partialObservability.allowCrossDomainAggregation);
193
+ const cumulative = addCounters(team.counters, value);
194
+ const domainCumulative = Object.fromEntries(ACCOUNTING_DOMAINS.map((domain) => [
195
+ domain,
196
+ addCounters(team.byDomain[domain], domainDelta(value, domain)),
197
+ ]));
198
+ const budget = this.#definition.profile.budget;
199
+ if (cumulative.interactions > budget.maximumInteractions)
200
+ fail("interaction_budget_exceeded");
201
+ if (cumulative.messages > budget.maximumMessages)
202
+ fail("message_budget_exceeded");
203
+ if (cumulative.messageBytes > budget.maximumMessageBytes)
204
+ fail("message_byte_budget_exceeded");
205
+ const previousChainDigest = team.chainDigest;
206
+ const recordBody = {
207
+ ...value,
208
+ previousChainDigest,
209
+ };
210
+ const recordDigest = scalableEvaluationDigestV1("accounting-record", recordBody);
211
+ const chainDigest = scalableEvaluationDigestV1("accounting-chain", {
212
+ previousChainDigest,
213
+ recordDigest,
214
+ });
215
+ const record = freeze({
216
+ ...recordBody,
217
+ recordDigest,
218
+ chainDigest,
219
+ cumulative: freezeCounters(cumulative),
220
+ });
221
+ this.#advanceRevision();
222
+ assignCounters(team.counters, cumulative);
223
+ for (const domain of ACCOUNTING_DOMAINS)
224
+ assignCounters(team.byDomain[domain], domainCumulative[domain]);
225
+ team.sequence = value.sequence;
226
+ team.lastLogicalTime = value.logicalTime;
227
+ team.chainDigest = chainDigest;
228
+ this.#retain(record);
229
+ return freeze({
230
+ schemaVersion: 1,
231
+ teamId: team.descriptor.teamId,
232
+ sequence: team.sequence,
233
+ recordDigest,
234
+ chainDigest,
235
+ cumulative: freezeCounters(team.counters),
236
+ remainingInteractions: budget.maximumInteractions - team.counters.interactions,
237
+ remainingMessages: budget.maximumMessages - team.counters.messages,
238
+ remainingMessageBytes: budget.maximumMessageBytes - team.counters.messageBytes,
239
+ });
240
+ }
241
+ /** Validates a public multi-domain delivery before accounting one pull. */
242
+ recordPartialObservationV1(input) {
243
+ nonNegativeInteger(input.peerIndex, "observation_peer_index");
244
+ if (input.peerIndex >= this.#definition.profile.agentCount)
245
+ fail("observation_peer_out_of_range");
246
+ const delivery = input.delivery;
247
+ if (delivery.schemaVersion !== 1 ||
248
+ delivery.peerIndex !== input.peerIndex ||
249
+ delivery.logicalTime !== input.accounting.logicalTime ||
250
+ typeof delivery.requestId !== "string" ||
251
+ delivery.requestId.length === 0 ||
252
+ !Array.isArray(delivery.observations) ||
253
+ delivery.observations.length >
254
+ this.#definition.partialObservability.maximumObservationsPerPull ||
255
+ !(delivery.nextCursor === null ||
256
+ (typeof delivery.nextCursor === "string" &&
257
+ delivery.nextCursor.length > 0)) ||
258
+ !DIGEST.test(delivery.deliveryDigest))
259
+ fail("partial_observation_delivery_invalid");
260
+ const { deliveryDigest: ignored, ...deliveryBody } = delivery;
261
+ if (shardedSimulationDigestV1("sharded-simulation-observation-delivery-v1", deliveryBody) !== delivery.deliveryDigest)
262
+ fail("partial_observation_delivery_digest_invalid");
263
+ if (input.accounting.evidenceDigest !== delivery.deliveryDigest ||
264
+ input.observations.length !== delivery.observations.length ||
265
+ scalableEvaluationDigestV1("observation-list", input.observations) !==
266
+ scalableEvaluationDigestV1("observation-list", delivery.observations))
267
+ fail("partial_observation_binding_invalid");
268
+ if (encoder.encode(JSON.stringify(delivery.observations)).byteLength >
269
+ this.#definition.scenario.resourceBudget.maximumObservationBytes)
270
+ fail("partial_observation_scenario_budget_exceeded");
271
+ const observationCountsByDomain = {
272
+ physical: 0,
273
+ social: 0,
274
+ cyber: 0,
275
+ };
276
+ for (const observation of input.observations) {
277
+ const validated = validateMultiDomainObservationEnvelopeV1(observation, this.#descriptor);
278
+ if (!this.#definition.domains.includes(validated.domain))
279
+ fail("partial_observation_domain_not_in_scenario");
280
+ if (validated.logicalTime > delivery.logicalTime)
281
+ fail("partial_observation_from_future");
282
+ if (!this.#definition.partialObservability.allowCrossDomainAggregation &&
283
+ validated.domain !== input.accounting.domain)
284
+ fail("partial_observation_domain_mismatch");
285
+ observationCountsByDomain[validated.domain] += 1;
286
+ }
287
+ return this.recordAccountingV1({
288
+ ...input.accounting,
289
+ kind: "observation",
290
+ interactionCount: 1,
291
+ messageCount: 0,
292
+ messageBytes: 0,
293
+ observationCount: input.observations.length,
294
+ observationCountsByDomain,
295
+ actionCount: 0,
296
+ successfulOutcomeCount: 0,
297
+ failedOutcomeCount: 0,
298
+ });
299
+ }
300
+ registerRecoveryBaselineV1(input) {
301
+ identifier(input.baselineId, "baseline_id");
302
+ const team = this.#team(input.teamId);
303
+ this.#domain(input.domain);
304
+ nonNegativeInteger(input.establishedAtLogicalTime, "baseline_logical_time");
305
+ if (input.establishedAtLogicalTime < team.lastLogicalTime)
306
+ fail("baseline_logical_time_regression");
307
+ const metrics = normalizeBaselineMetrics(input.metrics);
308
+ const key = domainKey(input.teamId, input.domain);
309
+ if ([...this.#recoveries.values()].some((recovery) => recovery.teamId === input.teamId &&
310
+ recovery.domain === input.domain &&
311
+ !recovery.withinBaselineTolerance))
312
+ fail("baseline_replacement_during_recovery");
313
+ const body = {
314
+ schemaVersion: 1,
315
+ baselineId: input.baselineId,
316
+ teamId: input.teamId,
317
+ domain: input.domain,
318
+ establishedAtLogicalTime: input.establishedAtLogicalTime,
319
+ metrics,
320
+ };
321
+ const baseline = freeze({
322
+ ...body,
323
+ baselineDigest: scalableEvaluationDigestV1("recovery-baseline", body),
324
+ });
325
+ this.#advanceRevision();
326
+ this.#baselines.set(key, baseline);
327
+ team.lastLogicalTime = input.establishedAtLogicalTime;
328
+ return baseline;
329
+ }
330
+ /**
331
+ * Registers the occurrence at its injection watermark. Calls made after a
332
+ * later accounting record fail closed, so recovery cost starts at injection
333
+ * even when observedAtLogicalTime is later.
334
+ */
335
+ recordPerturbationObservationV1(input) {
336
+ const receipt = input.receipt;
337
+ if (!isVerifiedScalableEvaluationPerturbationReceiptV1(receipt))
338
+ fail("perturbation_evidence_not_verified");
339
+ this.#requireTeamEnvironmentBinding(receipt.teamId, receipt.sessionId, receipt.episodeId);
340
+ const perturbation = this.#perturbationPlan.get(receipt.perturbationId);
341
+ if (!perturbation)
342
+ fail("perturbation_not_registered");
343
+ const team = this.#team(receipt.teamId);
344
+ if (!perturbation.targetTeamIds.includes(receipt.teamId))
345
+ fail("perturbation_team_not_targeted");
346
+ if (receipt.evaluationDefinitionDigest !==
347
+ this.#definition.definitionDigest ||
348
+ receipt.scenarioManifestDigest !==
349
+ this.#definition.scenarioManifestDigest ||
350
+ receipt.adapterDescriptorDigest !==
351
+ this.#definition.adapterDescriptorDigest ||
352
+ receipt.perturbationConfigurationDigest !==
353
+ perturbation.configurationDigest ||
354
+ receipt.scheduledAtLogicalTime !== perturbation.scheduledAtLogicalTime)
355
+ fail("perturbation_evidence_scope_invalid");
356
+ if (receipt.injectedAtLogicalTime < perturbation.scheduledAtLogicalTime ||
357
+ receipt.injectedAtLogicalTime < team.lastLogicalTime)
358
+ fail("perturbation_time_invalid");
359
+ const key = recoveryKey(receipt.perturbationId, receipt.teamId);
360
+ if (this.#perturbationObservations.has(key))
361
+ fail("perturbation_observation_duplicate");
362
+ const baseline = this.#baselines.get(domainKey(receipt.teamId, perturbation.domain));
363
+ if (!baseline ||
364
+ baseline.establishedAtLogicalTime >= receipt.injectedAtLogicalTime)
365
+ fail("recovery_baseline_unavailable");
366
+ const body = {
367
+ schemaVersion: 1,
368
+ perturbationId: perturbation.perturbationId,
369
+ teamId: receipt.teamId,
370
+ domain: perturbation.domain,
371
+ scheduledAtLogicalTime: perturbation.scheduledAtLogicalTime,
372
+ injectedAtLogicalTime: receipt.injectedAtLogicalTime,
373
+ observedAtLogicalTime: receipt.injectedAtLogicalTime,
374
+ evidenceDigest: receipt.receiptDigest,
375
+ };
376
+ const observation = freeze({
377
+ ...body,
378
+ observationDigest: scalableEvaluationDigestV1("perturbation-observation", body),
379
+ });
380
+ this.#advanceRevision();
381
+ this.#perturbationObservations.set(key, observation);
382
+ this.#recoveries.set(key, {
383
+ perturbationId: perturbation.perturbationId,
384
+ teamId: receipt.teamId,
385
+ domain: perturbation.domain,
386
+ baseline,
387
+ observedAtLogicalTime: receipt.injectedAtLogicalTime,
388
+ startingInteractions: team.counters.interactions,
389
+ startingMessages: team.counters.messages,
390
+ recoveredAtLogicalTime: null,
391
+ withinBaselineTolerance: false,
392
+ latestSampleDigest: null,
393
+ recoveryInteractions: 0,
394
+ recoveryMessages: 0,
395
+ });
396
+ team.lastLogicalTime = receipt.injectedAtLogicalTime;
397
+ return observation;
398
+ }
399
+ recordRecoverySampleV1(input) {
400
+ const measurement = input.measurement;
401
+ if (!isVerifiedScalableEvaluationRecoveryReceiptV1(measurement))
402
+ fail("recovery_evidence_not_verified");
403
+ this.#requireTeamEnvironmentBinding(measurement.teamId, measurement.sessionId, measurement.episodeId);
404
+ const team = this.#team(measurement.teamId);
405
+ const recovery = this.#recoveries.get(recoveryKey(measurement.perturbationId, measurement.teamId));
406
+ if (!recovery)
407
+ fail("recovery_episode_unavailable");
408
+ if (recovery.withinBaselineTolerance)
409
+ fail("recovery_episode_already_complete");
410
+ if (measurement.evaluationDefinitionDigest !==
411
+ this.#definition.definitionDigest ||
412
+ measurement.scenarioManifestDigest !==
413
+ this.#definition.scenarioManifestDigest ||
414
+ measurement.adapterDescriptorDigest !==
415
+ this.#definition.adapterDescriptorDigest ||
416
+ measurement.domain !== recovery.domain)
417
+ fail("recovery_evidence_scope_invalid");
418
+ const perturbation = this.#perturbationPlan.get(measurement.perturbationId);
419
+ if (!perturbation ||
420
+ measurement.perturbationConfigurationDigest !==
421
+ perturbation.configurationDigest ||
422
+ measurement.scheduledAtLogicalTime !== perturbation.scheduledAtLogicalTime)
423
+ fail("recovery_evidence_scope_invalid");
424
+ if (measurement.logicalTime < recovery.observedAtLogicalTime ||
425
+ measurement.logicalTime < team.lastLogicalTime)
426
+ fail("recovery_sample_time_invalid");
427
+ const metrics = normalizeRecoveryMetrics(measurement.metrics, recovery.baseline.metrics);
428
+ let maximumDistanceBasisPoints = 0;
429
+ let withinBaselineTolerance = true;
430
+ for (let index = 0; index < metrics.length; index += 1) {
431
+ const metric = metrics[index];
432
+ const target = recovery.baseline.metrics[index];
433
+ const distance = Math.abs(metric.valueBasisPoints - target.valueBasisPoints);
434
+ maximumDistanceBasisPoints = Math.max(maximumDistanceBasisPoints, distance);
435
+ if (distance > target.toleranceBasisPoints)
436
+ withinBaselineTolerance = false;
437
+ }
438
+ const recoveryInteractions = team.counters.interactions - recovery.startingInteractions;
439
+ const recoveryMessages = team.counters.messages - recovery.startingMessages;
440
+ const body = {
441
+ schemaVersion: 1,
442
+ sampleId: measurement.sampleId,
443
+ perturbationId: recovery.perturbationId,
444
+ teamId: recovery.teamId,
445
+ domain: recovery.domain,
446
+ logicalTime: measurement.logicalTime,
447
+ metrics,
448
+ maximumDistanceBasisPoints,
449
+ withinBaselineTolerance,
450
+ recoveryInteractions,
451
+ recoveryMessages,
452
+ measurement,
453
+ };
454
+ const sample = freeze({
455
+ ...body,
456
+ sampleDigest: scalableEvaluationDigestV1("recovery-sample", body),
457
+ });
458
+ this.#advanceRevision();
459
+ recovery.latestSampleDigest = sample.sampleDigest;
460
+ recovery.withinBaselineTolerance = withinBaselineTolerance;
461
+ recovery.recoveryInteractions = recoveryInteractions;
462
+ recovery.recoveryMessages = recoveryMessages;
463
+ recovery.recoveredAtLogicalTime = withinBaselineTolerance
464
+ ? measurement.logicalTime
465
+ : null;
466
+ team.lastLogicalTime = measurement.logicalTime;
467
+ return sample;
468
+ }
469
+ snapshotV1() {
470
+ const teamSummaries = [...this.#teams.values()]
471
+ .sort((left, right) => left.descriptor.teamId.localeCompare(right.descriptor.teamId))
472
+ .map((team) => this.#teamSummary(team));
473
+ const perturbationObservations = [
474
+ ...this.#perturbationObservations.values(),
475
+ ].sort((left, right) => left.perturbationId.localeCompare(right.perturbationId) ||
476
+ left.teamId.localeCompare(right.teamId));
477
+ const recoverySummaries = [...this.#recoveries.values()]
478
+ .sort((left, right) => left.perturbationId.localeCompare(right.perturbationId) ||
479
+ left.teamId.localeCompare(right.teamId))
480
+ .map(recoverySummary);
481
+ const recentAccountingRecords = this.#orderedRecordTail();
482
+ const body = {
483
+ schemaVersion: 1,
484
+ evaluationDefinitionDigest: this.#definition.definitionDigest,
485
+ profileDigest: this.#definition.profile.profileDigest,
486
+ teamSummaries,
487
+ perturbationObservations,
488
+ recoverySummaries,
489
+ recentAccountingRecords,
490
+ };
491
+ return freeze({
492
+ ...body,
493
+ snapshotDigest: scalableEvaluationDigestV1("snapshot", body),
494
+ });
495
+ }
496
+ compareV1() {
497
+ const left = this.#teamSummary(this.#team(this.#definition.matchup.leftTeamId));
498
+ const right = this.#teamSummary(this.#team(this.#definition.matchup.rightTeamId));
499
+ const leftMinusRight = freeze({
500
+ interactions: left.counters.interactions - right.counters.interactions,
501
+ messages: left.counters.messages - right.counters.messages,
502
+ messageBytes: left.counters.messageBytes - right.counters.messageBytes,
503
+ observations: left.counters.observations - right.counters.observations,
504
+ actions: left.counters.actions - right.counters.actions,
505
+ successfulOutcomes: left.counters.successfulOutcomes - right.counters.successfulOutcomes,
506
+ failedOutcomes: left.counters.failedOutcomes - right.counters.failedOutcomes,
507
+ recoveredEpisodes: left.recoveredEpisodeCount - right.recoveredEpisodeCount,
508
+ completedRecoveryInteractions: left.completedRecoveryInteractions -
509
+ right.completedRecoveryInteractions,
510
+ completedRecoveryMessages: left.completedRecoveryMessages - right.completedRecoveryMessages,
511
+ });
512
+ const body = {
513
+ schemaVersion: 1,
514
+ comparisonKind: "team-vs-team",
515
+ definitionDigest: this.#definition.definitionDigest,
516
+ matchupDigest: this.#definition.matchup.matchupDigest,
517
+ left,
518
+ right,
519
+ leftMinusRight,
520
+ };
521
+ return freeze({
522
+ ...body,
523
+ comparisonDigest: scalableEvaluationDigestV1("comparison", body),
524
+ });
525
+ }
526
+ /** Canonical process-independent state. The record ring remains bounded. */
527
+ exportStateV1() {
528
+ const teams = [...this.#teams.values()]
529
+ .sort((left, right) => left.descriptor.teamId.localeCompare(right.descriptor.teamId))
530
+ .map((team) => freeze({
531
+ schemaVersion: 1,
532
+ descriptor: team.descriptor,
533
+ sequence: team.sequence,
534
+ lastLogicalTime: team.lastLogicalTime,
535
+ chainDigest: team.chainDigest,
536
+ counters: freezeCounters(team.counters),
537
+ countersByDomain: freezeDomainCounters(team.byDomain),
538
+ }));
539
+ const baselines = [...this.#baselines.values()].sort((left, right) => domainKey(left.teamId, left.domain).localeCompare(domainKey(right.teamId, right.domain)));
540
+ const perturbationObservations = [
541
+ ...this.#perturbationObservations.values(),
542
+ ].sort((left, right) => recoveryKey(left.perturbationId, left.teamId).localeCompare(recoveryKey(right.perturbationId, right.teamId)));
543
+ const recoveries = [...this.#recoveries.values()]
544
+ .sort((left, right) => recoveryKey(left.perturbationId, left.teamId).localeCompare(recoveryKey(right.perturbationId, right.teamId)))
545
+ .map((recovery) => freeze({
546
+ schemaVersion: 1,
547
+ perturbationId: recovery.perturbationId,
548
+ teamId: recovery.teamId,
549
+ domain: recovery.domain,
550
+ baseline: recovery.baseline,
551
+ observedAtLogicalTime: recovery.observedAtLogicalTime,
552
+ startingInteractions: recovery.startingInteractions,
553
+ startingMessages: recovery.startingMessages,
554
+ recoveredAtLogicalTime: recovery.recoveredAtLogicalTime,
555
+ withinBaselineTolerance: recovery.withinBaselineTolerance,
556
+ latestSampleDigest: recovery.latestSampleDigest,
557
+ recoveryInteractions: recovery.recoveryInteractions,
558
+ recoveryMessages: recovery.recoveryMessages,
559
+ }));
560
+ const environmentBindings = [...this.#teamEnvironmentBindings]
561
+ .sort(([left], [right]) => left.localeCompare(right))
562
+ .map(([teamId, binding]) => freeze({ teamId, ...binding }));
563
+ const body = {
564
+ schemaVersion: 1,
565
+ definitionDigest: this.#definition.definitionDigest,
566
+ adapterDescriptorDigest: this.#descriptor.descriptorDigest,
567
+ revision: this.#revision,
568
+ predecessorStateDigest: this.#predecessorStateDigest,
569
+ teams,
570
+ baselines,
571
+ perturbationObservations,
572
+ recoveries,
573
+ environmentBindings,
574
+ recordTail: Object.freeze(this.#recordTail.filter((record) => record !== undefined)),
575
+ recordTailCursor: this.#recordTailCursor,
576
+ };
577
+ return freeze({
578
+ ...body,
579
+ stateDigest: scalableEvaluationDigestV1("runtime-state", body),
580
+ });
581
+ }
582
+ #restoreState(state) {
583
+ const value = validateRuntimeStateV1(state, this.#definition, this.#descriptor.descriptorDigest);
584
+ this.#teams.clear();
585
+ for (const team of value.teams)
586
+ this.#teams.set(team.descriptor.teamId, {
587
+ descriptor: team.descriptor,
588
+ sequence: team.sequence,
589
+ lastLogicalTime: team.lastLogicalTime,
590
+ chainDigest: team.chainDigest,
591
+ counters: { ...team.counters },
592
+ byDomain: {
593
+ physical: { ...team.countersByDomain.physical },
594
+ social: { ...team.countersByDomain.social },
595
+ cyber: { ...team.countersByDomain.cyber },
596
+ },
597
+ });
598
+ this.#baselines.clear();
599
+ for (const baseline of value.baselines)
600
+ this.#baselines.set(domainKey(baseline.teamId, baseline.domain), baseline);
601
+ this.#perturbationObservations.clear();
602
+ for (const observation of value.perturbationObservations)
603
+ this.#perturbationObservations.set(recoveryKey(observation.perturbationId, observation.teamId), observation);
604
+ this.#recoveries.clear();
605
+ for (const recovery of value.recoveries)
606
+ this.#recoveries.set(recoveryKey(recovery.perturbationId, recovery.teamId), { ...recovery });
607
+ this.#teamEnvironmentBindings.clear();
608
+ for (const binding of value.environmentBindings)
609
+ this.#teamEnvironmentBindings.set(binding.teamId, {
610
+ sessionId: binding.sessionId,
611
+ episodeId: binding.episodeId,
612
+ });
613
+ this.#recordTail.length = 0;
614
+ this.#recordTail.push(...value.recordTail);
615
+ this.#recordTailCursor = value.recordTailCursor;
616
+ this.#revision = value.revision;
617
+ this.#predecessorStateDigest = value.predecessorStateDigest;
618
+ }
619
+ #advanceRevision() {
620
+ this.#predecessorStateDigest = this.exportStateV1().stateDigest;
621
+ this.#revision += 1;
622
+ }
623
+ #team(teamId) {
624
+ const team = this.#teams.get(teamId);
625
+ if (!team)
626
+ fail("team_not_registered");
627
+ return team;
628
+ }
629
+ #requireTeamEnvironmentBinding(teamId, sessionId, episodeId) {
630
+ const binding = this.#teamEnvironmentBindings.get(teamId);
631
+ if (!binding ||
632
+ binding.sessionId !== sessionId ||
633
+ binding.episodeId !== episodeId)
634
+ fail("evaluation_evidence_environment_binding_invalid");
635
+ }
636
+ #domain(domain) {
637
+ if (!this.#definition.domains.includes(domain))
638
+ fail("domain_not_in_scenario");
639
+ }
640
+ #retain(record) {
641
+ const limit = this.#definition.profile.budget.maximumRetainedRecords;
642
+ if (this.#recordTail.length < limit) {
643
+ this.#recordTail.push(record);
644
+ return;
645
+ }
646
+ this.#recordTail[this.#recordTailCursor] = record;
647
+ this.#recordTailCursor = (this.#recordTailCursor + 1) % limit;
648
+ }
649
+ #orderedRecordTail() {
650
+ const records = this.#recordTail.filter((record) => record !== undefined);
651
+ if (records.length < this.#definition.profile.budget.maximumRetainedRecords ||
652
+ this.#recordTailCursor === 0)
653
+ return Object.freeze([...records]);
654
+ return Object.freeze([
655
+ ...records.slice(this.#recordTailCursor),
656
+ ...records.slice(0, this.#recordTailCursor),
657
+ ]);
658
+ }
659
+ #teamSummary(team) {
660
+ const recoveries = [...this.#recoveries.values()].filter((recovery) => recovery.teamId === team.descriptor.teamId);
661
+ const completed = recoveries.filter((recovery) => recovery.withinBaselineTolerance);
662
+ const body = {
663
+ schemaVersion: 1,
664
+ teamId: team.descriptor.teamId,
665
+ architecture: team.descriptor.architecture,
666
+ lastSequence: team.sequence,
667
+ lastLogicalTime: team.lastLogicalTime,
668
+ counters: freezeCounters(team.counters),
669
+ countersByDomain: freezeDomainCounters(team.byDomain),
670
+ accountingChainDigest: team.chainDigest,
671
+ recoveryEpisodeCount: recoveries.length,
672
+ recoveredEpisodeCount: completed.length,
673
+ recoveryRateBasisPoints: recoveries.length === 0
674
+ ? 0
675
+ : Math.floor((completed.length * 10_000) / recoveries.length),
676
+ completedRecoveryInteractions: completed.reduce((sum, recovery) => safeAdd(sum, recovery.recoveryInteractions, "completed_recovery_interactions"), 0),
677
+ completedRecoveryMessages: completed.reduce((sum, recovery) => safeAdd(sum, recovery.recoveryMessages, "completed_recovery_messages"), 0),
678
+ };
679
+ return freeze({
680
+ ...body,
681
+ summaryDigest: scalableEvaluationDigestV1("team-summary", body),
682
+ });
683
+ }
684
+ }
685
+ const canonicalBindTeamEnvironmentV1 = InMemoryScalableEvaluationRuntimeV1.prototype.bindTeamEnvironmentV1;
686
+ const canonicalRecordAccountingV1 = InMemoryScalableEvaluationRuntimeV1.prototype.recordAccountingV1;
687
+ const canonicalRecordPartialObservationV1 = InMemoryScalableEvaluationRuntimeV1.prototype.recordPartialObservationV1;
688
+ const canonicalRegisterRecoveryBaselineV1 = InMemoryScalableEvaluationRuntimeV1.prototype.registerRecoveryBaselineV1;
689
+ const canonicalRecordPerturbationObservationV1 = InMemoryScalableEvaluationRuntimeV1.prototype.recordPerturbationObservationV1;
690
+ const canonicalRecordRecoverySampleV1 = InMemoryScalableEvaluationRuntimeV1.prototype.recordRecoverySampleV1;
691
+ const canonicalSnapshotV1 = InMemoryScalableEvaluationRuntimeV1.prototype.snapshotV1;
692
+ const canonicalCompareV1 = InMemoryScalableEvaluationRuntimeV1.prototype.compareV1;
693
+ const canonicalExportStateV1 = InMemoryScalableEvaluationRuntimeV1.prototype.exportStateV1;
694
+ /** Nominal check for the module-owned scalable evaluation runtime. */
695
+ export function isInMemoryScalableEvaluationRuntimeV1(value) {
696
+ return Boolean(value &&
697
+ (typeof value === "object" || typeof value === "function") &&
698
+ scalableEvaluationRuntimeInvokersV1.has(value));
699
+ }
700
+ export function scalableEvaluationRuntimeDefinitionV1(runtime) {
701
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).definition;
702
+ }
703
+ export function invokeScalableEvaluationBindTeamEnvironmentV1(runtime, input) {
704
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).bindTeamEnvironment(input);
705
+ }
706
+ export function invokeScalableEvaluationRecordAccountingV1(runtime, input) {
707
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).recordAccounting(input);
708
+ }
709
+ export function invokeScalableEvaluationRecordPartialObservationV1(runtime, input) {
710
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).recordPartialObservation(input);
711
+ }
712
+ export function invokeScalableEvaluationRegisterRecoveryBaselineV1(runtime, input) {
713
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).registerRecoveryBaseline(input);
714
+ }
715
+ export function invokeScalableEvaluationRecordPerturbationObservationV1(runtime, input) {
716
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).recordPerturbationObservation(input);
717
+ }
718
+ export function invokeScalableEvaluationRecordRecoverySampleV1(runtime, input) {
719
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).recordRecoverySample(input);
720
+ }
721
+ export function invokeScalableEvaluationSnapshotV1(runtime) {
722
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).snapshot();
723
+ }
724
+ export function invokeScalableEvaluationCompareV1(runtime) {
725
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).compare();
726
+ }
727
+ export function invokeScalableEvaluationExportStateV1(runtime) {
728
+ return requireScalableEvaluationRuntimeInvokersV1(runtime).exportState();
729
+ }
730
+ function requireScalableEvaluationRuntimeInvokersV1(runtime) {
731
+ const invokers = scalableEvaluationRuntimeInvokersV1.get(runtime);
732
+ if (!invokers)
733
+ fail("runtime_not_genuine");
734
+ return invokers;
735
+ }
736
+ export function createScalableEvaluationRuntimeV1(input) {
737
+ return new InMemoryScalableEvaluationRuntimeV1(input);
738
+ }
739
+ export function restoreScalableEvaluationRuntimeV1(input) {
740
+ return new InMemoryScalableEvaluationRuntimeV1(input);
741
+ }
742
+ function validateRuntimeStateV1(input, definition, descriptorDigest) {
743
+ if (!input ||
744
+ typeof input !== "object" ||
745
+ input.schemaVersion !== 1 ||
746
+ input.definitionDigest !== definition.definitionDigest ||
747
+ input.adapterDescriptorDigest !== descriptorDigest ||
748
+ !Number.isSafeInteger(input.revision) ||
749
+ input.revision < 0 ||
750
+ (input.revision === 0) !== (input.predecessorStateDigest === null) ||
751
+ (input.predecessorStateDigest !== null &&
752
+ !DIGEST.test(input.predecessorStateDigest)) ||
753
+ !Array.isArray(input.teams) ||
754
+ input.teams.length !== definition.teams.length ||
755
+ !Array.isArray(input.baselines) ||
756
+ input.baselines.length >
757
+ definition.teams.length * definition.domains.length ||
758
+ !Array.isArray(input.perturbationObservations) ||
759
+ input.perturbationObservations.length >
760
+ definition.perturbations.length * definition.teams.length ||
761
+ !Array.isArray(input.recoveries) ||
762
+ input.recoveries.length >
763
+ definition.perturbations.length * definition.teams.length ||
764
+ !Array.isArray(input.environmentBindings) ||
765
+ (input.revision === 0
766
+ ? input.environmentBindings.length !== 0
767
+ : input.environmentBindings.length !== definition.teams.length) ||
768
+ !Array.isArray(input.recordTail) ||
769
+ input.recordTail.length >
770
+ definition.profile.budget.maximumRetainedRecords ||
771
+ !Number.isSafeInteger(input.recordTailCursor) ||
772
+ input.recordTailCursor < 0 ||
773
+ !DIGEST.test(input.stateDigest))
774
+ fail("runtime_state_invalid");
775
+ const { stateDigest: ignored, ...body } = input;
776
+ if (scalableEvaluationDigestV1("runtime-state", body) !== input.stateDigest)
777
+ fail("runtime_state_digest_invalid");
778
+ const expectedTeams = [...definition.teams].sort((left, right) => left.teamId.localeCompare(right.teamId));
779
+ for (let index = 0; index < input.teams.length; index += 1) {
780
+ const team = input.teams[index];
781
+ const expected = expectedTeams[index];
782
+ if (!team ||
783
+ team.schemaVersion !== 1 ||
784
+ team.descriptor.teamId !== expected.teamId ||
785
+ team.descriptor.descriptorDigest !== expected.descriptorDigest ||
786
+ !Number.isSafeInteger(team.sequence) ||
787
+ team.sequence < 0 ||
788
+ !Number.isSafeInteger(team.lastLogicalTime) ||
789
+ team.lastLogicalTime < 0 ||
790
+ !DIGEST.test(team.chainDigest))
791
+ fail("runtime_state_team_invalid");
792
+ validateStateCounters(team.counters, "runtime_state_team_counters");
793
+ for (const domain of ACCOUNTING_DOMAINS)
794
+ validateStateCounters(team.countersByDomain[domain], "runtime_state_domain_counters");
795
+ for (const domain of ACCOUNTING_DOMAINS)
796
+ if (!definition.domains.includes(domain) &&
797
+ !sameCounters(team.countersByDomain[domain], mutableZeroCounters()))
798
+ fail("runtime_state_inactive_domain_counters_invalid");
799
+ for (const counter of [
800
+ "interactions",
801
+ "messages",
802
+ "messageBytes",
803
+ "observations",
804
+ "actions",
805
+ "successfulOutcomes",
806
+ "failedOutcomes",
807
+ ])
808
+ if (ACCOUNTING_DOMAINS.reduce((sum, domain) => sum + team.countersByDomain[domain][counter], 0) !== team.counters[counter])
809
+ fail("runtime_state_domain_counter_sum_invalid");
810
+ if (team.counters.interactions >
811
+ definition.profile.budget.maximumInteractions ||
812
+ team.counters.messages > definition.profile.budget.maximumMessages ||
813
+ team.counters.messageBytes > definition.profile.budget.maximumMessageBytes)
814
+ fail("runtime_state_budget_invalid");
815
+ }
816
+ const baselineKeys = new Set();
817
+ const baselinesByKey = new Map();
818
+ for (const baseline of input.baselines) {
819
+ const metrics = normalizeBaselineMetrics(baseline.metrics);
820
+ const baselineBody = {
821
+ schemaVersion: 1,
822
+ baselineId: baseline.baselineId,
823
+ teamId: baseline.teamId,
824
+ domain: baseline.domain,
825
+ establishedAtLogicalTime: baseline.establishedAtLogicalTime,
826
+ metrics,
827
+ };
828
+ const baselineKey = domainKey(baseline.teamId, baseline.domain);
829
+ if (!expectedTeams.some((team) => team.teamId === baseline.teamId) ||
830
+ !definition.domains.includes(baseline.domain) ||
831
+ baselineKeys.has(baselineKey) ||
832
+ scalableEvaluationDigestV1("recovery-baseline", baselineBody) !==
833
+ baseline.baselineDigest)
834
+ fail("runtime_state_baseline_invalid");
835
+ baselineKeys.add(baselineKey);
836
+ baselinesByKey.set(baselineKey, baseline);
837
+ }
838
+ const observationKeys = new Set();
839
+ const observationsByKey = new Map();
840
+ for (const observation of input.perturbationObservations) {
841
+ const perturbation = definition.perturbations.find((entry) => entry.perturbationId === observation.perturbationId);
842
+ const observationKey = recoveryKey(observation.perturbationId, observation.teamId);
843
+ const { observationDigest, ...observationBody } = observation;
844
+ if (observation.schemaVersion !== 1 ||
845
+ !perturbation ||
846
+ !perturbation.targetTeamIds.includes(observation.teamId) ||
847
+ observation.domain !== perturbation.domain ||
848
+ observation.scheduledAtLogicalTime !==
849
+ perturbation.scheduledAtLogicalTime ||
850
+ !Number.isSafeInteger(observation.injectedAtLogicalTime) ||
851
+ observation.injectedAtLogicalTime < observation.scheduledAtLogicalTime ||
852
+ observation.observedAtLogicalTime !== observation.injectedAtLogicalTime ||
853
+ !DIGEST.test(observation.evidenceDigest) ||
854
+ observationKeys.has(observationKey) ||
855
+ scalableEvaluationDigestV1("perturbation-observation", observationBody) !== observationDigest)
856
+ fail("runtime_state_perturbation_observation_invalid");
857
+ observationKeys.add(observationKey);
858
+ observationsByKey.set(observationKey, observation);
859
+ }
860
+ const recoveryKeys = new Set();
861
+ for (const recovery of input.recoveries) {
862
+ const recoveryStateKey = recoveryKey(recovery.perturbationId, recovery.teamId);
863
+ const perturbation = definition.perturbations.find((candidate) => candidate.perturbationId === recovery.perturbationId);
864
+ const observation = observationsByKey.get(recoveryStateKey);
865
+ const team = input.teams.find((candidate) => candidate.descriptor.teamId === recovery.teamId);
866
+ const canonicalBaseline = baselinesByKey.get(domainKey(recovery.teamId, recovery.domain));
867
+ if (recovery.schemaVersion !== 1 ||
868
+ recoveryKeys.has(recoveryStateKey) ||
869
+ !perturbation ||
870
+ !observation ||
871
+ !team ||
872
+ recovery.domain !== perturbation.domain ||
873
+ observation.domain !== recovery.domain ||
874
+ recovery.baseline.teamId !== recovery.teamId ||
875
+ recovery.baseline.domain !== recovery.domain ||
876
+ !canonicalBaseline ||
877
+ scalableEvaluationDigestV1("runtime-recovery-baseline-binding", recovery.baseline) !==
878
+ scalableEvaluationDigestV1("runtime-recovery-baseline-binding", canonicalBaseline) ||
879
+ !Number.isSafeInteger(recovery.observedAtLogicalTime) ||
880
+ recovery.observedAtLogicalTime !== observation.observedAtLogicalTime ||
881
+ recovery.observedAtLogicalTime > team.lastLogicalTime ||
882
+ !Number.isSafeInteger(recovery.startingInteractions) ||
883
+ recovery.startingInteractions < 0 ||
884
+ recovery.startingInteractions > team.counters.interactions ||
885
+ !Number.isSafeInteger(recovery.startingMessages) ||
886
+ recovery.startingMessages < 0 ||
887
+ recovery.startingMessages > team.counters.messages ||
888
+ !(recovery.recoveredAtLogicalTime === null ||
889
+ (Number.isSafeInteger(recovery.recoveredAtLogicalTime) &&
890
+ recovery.recoveredAtLogicalTime >= recovery.observedAtLogicalTime &&
891
+ recovery.recoveredAtLogicalTime <= team.lastLogicalTime)) ||
892
+ typeof recovery.withinBaselineTolerance !== "boolean" ||
893
+ recovery.withinBaselineTolerance !==
894
+ (recovery.recoveredAtLogicalTime !== null) ||
895
+ !Number.isSafeInteger(recovery.recoveryInteractions) ||
896
+ recovery.recoveryInteractions < 0 ||
897
+ recovery.recoveryInteractions >
898
+ team.counters.interactions - recovery.startingInteractions ||
899
+ !Number.isSafeInteger(recovery.recoveryMessages) ||
900
+ recovery.recoveryMessages < 0 ||
901
+ recovery.recoveryMessages >
902
+ team.counters.messages - recovery.startingMessages ||
903
+ (recovery.latestSampleDigest !== null &&
904
+ !DIGEST.test(recovery.latestSampleDigest)) ||
905
+ (recovery.latestSampleDigest === null &&
906
+ (recovery.withinBaselineTolerance ||
907
+ recovery.recoveryInteractions !== 0 ||
908
+ recovery.recoveryMessages !== 0)) ||
909
+ (recovery.withinBaselineTolerance && recovery.latestSampleDigest === null))
910
+ fail("runtime_state_recovery_invalid");
911
+ recoveryKeys.add(recoveryStateKey);
912
+ }
913
+ if (recoveryKeys.size !== observationKeys.size ||
914
+ [...observationKeys].some((observationKey) => !recoveryKeys.has(observationKey)))
915
+ fail("runtime_state_recovery_set_invalid");
916
+ const bindingTeams = new Set();
917
+ const sessionIds = new Set();
918
+ const episodeIds = new Set();
919
+ for (const binding of input.environmentBindings) {
920
+ identifier(binding.teamId, "runtime_state_binding_team_id");
921
+ identifier(binding.sessionId, "runtime_state_binding_session_id");
922
+ identifier(binding.episodeId, "runtime_state_binding_episode_id");
923
+ if (!expectedTeams.some((team) => team.teamId === binding.teamId) ||
924
+ bindingTeams.has(binding.teamId) ||
925
+ sessionIds.has(binding.sessionId) ||
926
+ episodeIds.has(binding.episodeId))
927
+ fail("runtime_state_environment_binding_invalid");
928
+ bindingTeams.add(binding.teamId);
929
+ sessionIds.add(binding.sessionId);
930
+ episodeIds.add(binding.episodeId);
931
+ }
932
+ const tailLimit = definition.profile.budget.maximumRetainedRecords;
933
+ const totalRecordCount = input.teams.reduce((total, team) => safeAdd(total, team.sequence, "runtime_state_sequence"), 0);
934
+ const expectedTailLength = Math.min(totalRecordCount, tailLimit);
935
+ const expectedTailCursor = totalRecordCount <= tailLimit
936
+ ? 0
937
+ : (totalRecordCount - tailLimit) % tailLimit;
938
+ if (input.recordTail.length !== expectedTailLength ||
939
+ input.recordTailCursor !== expectedTailCursor)
940
+ fail("runtime_state_record_cursor_invalid");
941
+ const orderedTail = input.recordTail.length === tailLimit && input.recordTailCursor !== 0
942
+ ? [
943
+ ...input.recordTail.slice(input.recordTailCursor),
944
+ ...input.recordTail.slice(0, input.recordTailCursor),
945
+ ]
946
+ : [...input.recordTail];
947
+ const retainedByTeam = new Map();
948
+ const eventIds = new Set();
949
+ for (const record of orderedTail) {
950
+ const { previousChainDigest, recordDigest, chainDigest, cumulative, ...accounting } = record;
951
+ const validatedAccounting = validateAccountingInput(accounting);
952
+ validateActivitySemantics(validatedAccounting, definition.domains, definition.partialObservability.allowCrossDomainAggregation);
953
+ validateStateCounters(cumulative, "runtime_state_record_cumulative");
954
+ const team = input.teams.find((candidate) => candidate.descriptor.teamId === record.teamId);
955
+ if (!team ||
956
+ record.sequence > team.sequence ||
957
+ record.logicalTime > team.lastLogicalTime ||
958
+ eventIds.has(record.eventId) ||
959
+ !DIGEST.test(previousChainDigest) ||
960
+ scalableEvaluationDigestV1("accounting-record", {
961
+ ...accounting,
962
+ previousChainDigest,
963
+ }) !== recordDigest ||
964
+ scalableEvaluationDigestV1("accounting-chain", {
965
+ previousChainDigest,
966
+ recordDigest,
967
+ }) !== chainDigest)
968
+ fail("runtime_state_record_invalid");
969
+ eventIds.add(record.eventId);
970
+ const teamRecords = retainedByTeam.get(record.teamId) ?? [];
971
+ const previous = teamRecords.at(-1);
972
+ if (previous) {
973
+ if (record.sequence !== previous.sequence + 1 ||
974
+ record.previousChainDigest !== previous.chainDigest ||
975
+ !sameCounters(record.cumulative, addCounters({ ...previous.cumulative }, validatedAccounting)))
976
+ fail("runtime_state_record_continuity_invalid");
977
+ }
978
+ else if (record.sequence === 1) {
979
+ const genesis = scalableEvaluationDigestV1("accounting-genesis", {
980
+ definitionDigest: definition.definitionDigest,
981
+ teamId: record.teamId,
982
+ });
983
+ if (record.previousChainDigest !== genesis ||
984
+ !sameCounters(record.cumulative, addCounters(mutableZeroCounters(), validatedAccounting)))
985
+ fail("runtime_state_record_genesis_invalid");
986
+ }
987
+ teamRecords.push(record);
988
+ retainedByTeam.set(record.teamId, teamRecords);
989
+ }
990
+ for (const team of input.teams) {
991
+ const records = retainedByTeam.get(team.descriptor.teamId) ?? [];
992
+ const latest = records.at(-1);
993
+ const genesis = scalableEvaluationDigestV1("accounting-genesis", {
994
+ definitionDigest: definition.definitionDigest,
995
+ teamId: team.descriptor.teamId,
996
+ });
997
+ if (team.sequence === 0) {
998
+ if (team.chainDigest !== genesis ||
999
+ !sameCounters(team.counters, mutableZeroCounters()) ||
1000
+ records.length !== 0)
1001
+ fail("runtime_state_team_genesis_invalid");
1002
+ }
1003
+ else if (latest &&
1004
+ (latest.sequence !== team.sequence ||
1005
+ latest.chainDigest !== team.chainDigest ||
1006
+ !sameCounters(latest.cumulative, team.counters)))
1007
+ fail("runtime_state_team_head_invalid");
1008
+ if (records.length === team.sequence && records[0]?.sequence === 1) {
1009
+ const reconstructed = {
1010
+ physical: mutableZeroCounters(),
1011
+ social: mutableZeroCounters(),
1012
+ cyber: mutableZeroCounters(),
1013
+ };
1014
+ for (const record of records) {
1015
+ const { previousChainDigest: ignoredPrevious, recordDigest: ignoredRecord, chainDigest: ignoredChain, cumulative: ignoredCumulative, ...accounting } = record;
1016
+ const validated = validateAccountingInput(accounting);
1017
+ for (const domain of ACCOUNTING_DOMAINS)
1018
+ reconstructed[domain] = addCounters(reconstructed[domain], domainDelta(validated, domain));
1019
+ }
1020
+ for (const domain of ACCOUNTING_DOMAINS)
1021
+ if (!sameCounters(reconstructed[domain], team.countersByDomain[domain]))
1022
+ fail("runtime_state_domain_counter_history_invalid");
1023
+ }
1024
+ }
1025
+ const minimumRevision = totalRecordCount +
1026
+ input.environmentBindings.length +
1027
+ input.baselines.length +
1028
+ input.perturbationObservations.length;
1029
+ if (input.revision < minimumRevision)
1030
+ fail("runtime_state_revision_inconsistent");
1031
+ if (input.environmentBindings.some((binding, index) => binding.teamId !== expectedTeams[index]?.teamId))
1032
+ fail("runtime_state_environment_binding_order_invalid");
1033
+ if (input.teams.some((team, index) => team.descriptor.teamId !== expectedTeams[index]?.teamId))
1034
+ fail("runtime_state_team_order_invalid");
1035
+ return freeze(input);
1036
+ }
1037
+ function sameCounters(left, right) {
1038
+ return (left.interactions === right.interactions &&
1039
+ left.messages === right.messages &&
1040
+ left.messageBytes === right.messageBytes &&
1041
+ left.observations === right.observations &&
1042
+ left.actions === right.actions &&
1043
+ left.successfulOutcomes === right.successfulOutcomes &&
1044
+ left.failedOutcomes === right.failedOutcomes);
1045
+ }
1046
+ function validateStateCounters(counters, label) {
1047
+ if (!counters || typeof counters !== "object")
1048
+ fail(`${label}_invalid`);
1049
+ for (const value of Object.values(counters))
1050
+ if (!Number.isSafeInteger(value) || value < 0)
1051
+ fail(`${label}_invalid`);
1052
+ }
1053
+ function validateDefinitionDigestOnly(definition) {
1054
+ if (!definition ||
1055
+ typeof definition !== "object" ||
1056
+ definition.schemaVersion !== 1 ||
1057
+ !Array.isArray(definition.teams) ||
1058
+ definition.teams.length !== 2)
1059
+ fail("definition_invalid");
1060
+ const { definitionDigest, ...body } = definition;
1061
+ if (!DIGEST.test(definitionDigest) ||
1062
+ scalableEvaluationDigestV1("definition", body) !== definitionDigest)
1063
+ fail("definition_digest_invalid");
1064
+ }
1065
+ function recoverySummary(recovery) {
1066
+ const body = {
1067
+ schemaVersion: 1,
1068
+ perturbationId: recovery.perturbationId,
1069
+ teamId: recovery.teamId,
1070
+ domain: recovery.domain,
1071
+ baselineDigest: recovery.baseline.baselineDigest,
1072
+ observedAtLogicalTime: recovery.observedAtLogicalTime,
1073
+ recoveredAtLogicalTime: recovery.recoveredAtLogicalTime,
1074
+ withinBaselineTolerance: recovery.withinBaselineTolerance,
1075
+ recoveryInteractions: recovery.recoveryInteractions,
1076
+ recoveryMessages: recovery.recoveryMessages,
1077
+ latestSampleDigest: recovery.latestSampleDigest,
1078
+ };
1079
+ return freeze({
1080
+ ...body,
1081
+ summaryDigest: scalableEvaluationDigestV1("recovery-summary", body),
1082
+ });
1083
+ }
1084
+ function normalizeBaselineMetrics(input) {
1085
+ if (!Array.isArray(input) ||
1086
+ input.length === 0 ||
1087
+ input.length > MAXIMUM_METRICS_PER_BASELINE)
1088
+ fail("baseline_metrics_invalid");
1089
+ const metrics = [...input]
1090
+ .map((metric) => {
1091
+ if (!metric || typeof metric !== "object")
1092
+ fail("baseline_metric_invalid");
1093
+ identifier(metric.metricId, "baseline_metric_id");
1094
+ basisPoints(metric.valueBasisPoints, "baseline_metric_value");
1095
+ basisPoints(metric.toleranceBasisPoints, "baseline_metric_tolerance");
1096
+ return {
1097
+ metricId: metric.metricId,
1098
+ valueBasisPoints: metric.valueBasisPoints,
1099
+ toleranceBasisPoints: metric.toleranceBasisPoints,
1100
+ };
1101
+ })
1102
+ .sort((left, right) => left.metricId.localeCompare(right.metricId));
1103
+ if (new Set(metrics.map((metric) => metric.metricId)).size !== metrics.length)
1104
+ fail("baseline_metric_duplicate");
1105
+ return freeze(metrics);
1106
+ }
1107
+ function normalizeRecoveryMetrics(input, targets) {
1108
+ if (!Array.isArray(input) || input.length !== targets.length)
1109
+ fail("recovery_metrics_invalid");
1110
+ const metrics = [...input]
1111
+ .map((metric) => {
1112
+ if (!metric || typeof metric !== "object")
1113
+ fail("recovery_metric_invalid");
1114
+ identifier(metric.metricId, "recovery_metric_id");
1115
+ basisPoints(metric.valueBasisPoints, "recovery_metric_value");
1116
+ return {
1117
+ metricId: metric.metricId,
1118
+ valueBasisPoints: metric.valueBasisPoints,
1119
+ };
1120
+ })
1121
+ .sort((left, right) => left.metricId.localeCompare(right.metricId));
1122
+ for (let index = 0; index < targets.length; index += 1)
1123
+ if (metrics[index].metricId !== targets[index].metricId)
1124
+ fail("recovery_metric_set_mismatch");
1125
+ return freeze(metrics);
1126
+ }
1127
+ function validateAccountingInput(input) {
1128
+ if (!input || typeof input !== "object" || input.schemaVersion !== 1)
1129
+ fail("accounting_input_invalid");
1130
+ identifier(input.eventId, "accounting_event_id");
1131
+ identifier(input.teamId, "accounting_team_id");
1132
+ positiveInteger(input.sequence, "accounting_sequence");
1133
+ nonNegativeInteger(input.logicalTime, "accounting_logical_time");
1134
+ if (!new Set(["physical", "social", "cyber"]).has(input.domain))
1135
+ fail("accounting_domain_invalid");
1136
+ if (!new Set(["observation", "message", "decision", "action", "recovery"]).has(input.kind))
1137
+ fail("accounting_kind_invalid");
1138
+ for (const [label, value] of [
1139
+ ["interaction_count", input.interactionCount],
1140
+ ["message_count", input.messageCount],
1141
+ ["message_bytes", input.messageBytes],
1142
+ ["observation_count", input.observationCount],
1143
+ ["action_count", input.actionCount],
1144
+ ["successful_outcome_count", input.successfulOutcomeCount],
1145
+ ["failed_outcome_count", input.failedOutcomeCount],
1146
+ ])
1147
+ nonNegativeInteger(value, label);
1148
+ positiveInteger(input.interactionCount, "interaction_count");
1149
+ const observationCountsByDomain = normalizeObservationCounts(input.observationCountsByDomain);
1150
+ digest(input.evidenceDigest, "accounting_evidence_digest");
1151
+ return freeze({
1152
+ schemaVersion: 1,
1153
+ eventId: input.eventId,
1154
+ teamId: input.teamId,
1155
+ sequence: input.sequence,
1156
+ logicalTime: input.logicalTime,
1157
+ domain: input.domain,
1158
+ kind: input.kind,
1159
+ interactionCount: input.interactionCount,
1160
+ messageCount: input.messageCount,
1161
+ messageBytes: input.messageBytes,
1162
+ observationCount: input.observationCount,
1163
+ observationCountsByDomain,
1164
+ actionCount: input.actionCount,
1165
+ successfulOutcomeCount: input.successfulOutcomeCount,
1166
+ failedOutcomeCount: input.failedOutcomeCount,
1167
+ evidenceDigest: input.evidenceDigest,
1168
+ });
1169
+ }
1170
+ function validateActivitySemantics(input, activeDomains, allowCrossDomainAggregation) {
1171
+ const observationTotal = ACCOUNTING_DOMAINS.reduce((sum, domain) => safeAdd(sum, input.observationCountsByDomain[domain], "observation_count"), 0);
1172
+ if ((input.messageCount === 0) !== (input.messageBytes === 0) ||
1173
+ input.messageBytes < input.messageCount ||
1174
+ observationTotal !== input.observationCount ||
1175
+ ACCOUNTING_DOMAINS.some((domain) => !activeDomains.includes(domain) &&
1176
+ input.observationCountsByDomain[domain] !== 0) ||
1177
+ (!allowCrossDomainAggregation &&
1178
+ ACCOUNTING_DOMAINS.some((domain) => domain !== input.domain &&
1179
+ input.observationCountsByDomain[domain] !== 0)) ||
1180
+ safeAdd(input.successfulOutcomeCount, input.failedOutcomeCount, "outcome_count") > input.interactionCount)
1181
+ fail("accounting_counts_inconsistent");
1182
+ if (input.kind === "message" && input.messageCount === 0)
1183
+ fail("message_activity_empty");
1184
+ if (input.kind === "action" && input.actionCount === 0)
1185
+ fail("action_activity_empty");
1186
+ }
1187
+ function normalizeObservationCounts(input) {
1188
+ if (!input ||
1189
+ typeof input !== "object" ||
1190
+ Array.isArray(input) ||
1191
+ Object.keys(input).sort().join("\u0000") !==
1192
+ [...ACCOUNTING_DOMAINS].sort().join("\u0000"))
1193
+ fail("observation_domain_counts_invalid");
1194
+ for (const domain of ACCOUNTING_DOMAINS)
1195
+ nonNegativeInteger(input[domain], `observation_${domain}_count`);
1196
+ return freeze({
1197
+ physical: input.physical,
1198
+ social: input.social,
1199
+ cyber: input.cyber,
1200
+ });
1201
+ }
1202
+ function domainDelta(input, domain) {
1203
+ const primary = domain === input.domain;
1204
+ return {
1205
+ ...input,
1206
+ interactionCount: primary ? input.interactionCount : 0,
1207
+ messageCount: primary ? input.messageCount : 0,
1208
+ messageBytes: primary ? input.messageBytes : 0,
1209
+ observationCount: input.observationCountsByDomain[domain],
1210
+ actionCount: primary ? input.actionCount : 0,
1211
+ successfulOutcomeCount: primary ? input.successfulOutcomeCount : 0,
1212
+ failedOutcomeCount: primary ? input.failedOutcomeCount : 0,
1213
+ };
1214
+ }
1215
+ function mutableZeroCounters() {
1216
+ return {
1217
+ interactions: 0,
1218
+ messages: 0,
1219
+ messageBytes: 0,
1220
+ observations: 0,
1221
+ actions: 0,
1222
+ successfulOutcomes: 0,
1223
+ failedOutcomes: 0,
1224
+ };
1225
+ }
1226
+ function addCounters(current, delta) {
1227
+ return {
1228
+ interactions: safeAdd(current.interactions, delta.interactionCount, "interaction_count"),
1229
+ messages: safeAdd(current.messages, delta.messageCount, "message_count"),
1230
+ messageBytes: safeAdd(current.messageBytes, delta.messageBytes, "message_bytes"),
1231
+ observations: safeAdd(current.observations, delta.observationCount, "observation_count"),
1232
+ actions: safeAdd(current.actions, delta.actionCount, "action_count"),
1233
+ successfulOutcomes: safeAdd(current.successfulOutcomes, delta.successfulOutcomeCount, "successful_outcome_count"),
1234
+ failedOutcomes: safeAdd(current.failedOutcomes, delta.failedOutcomeCount, "failed_outcome_count"),
1235
+ };
1236
+ }
1237
+ function assignCounters(target, source) {
1238
+ target.interactions = source.interactions;
1239
+ target.messages = source.messages;
1240
+ target.messageBytes = source.messageBytes;
1241
+ target.observations = source.observations;
1242
+ target.actions = source.actions;
1243
+ target.successfulOutcomes = source.successfulOutcomes;
1244
+ target.failedOutcomes = source.failedOutcomes;
1245
+ }
1246
+ function freezeCounters(counters) {
1247
+ return freeze({ ...counters });
1248
+ }
1249
+ function freezeDomainCounters(counters) {
1250
+ return freeze({
1251
+ physical: freezeCounters(counters.physical),
1252
+ social: freezeCounters(counters.social),
1253
+ cyber: freezeCounters(counters.cyber),
1254
+ });
1255
+ }
1256
+ function domainKey(teamId, domain) {
1257
+ return `${teamId}\u0000${domain}`;
1258
+ }
1259
+ function recoveryKey(perturbationId, teamId) {
1260
+ return `${perturbationId}\u0000${teamId}`;
1261
+ }
1262
+ function safeAdd(left, right, label) {
1263
+ const result = left + right;
1264
+ if (!Number.isSafeInteger(result))
1265
+ fail(`${label}_overflow`);
1266
+ return result;
1267
+ }
1268
+ function identifier(value, label) {
1269
+ if (typeof value !== "string" ||
1270
+ value.length === 0 ||
1271
+ value.length > 256 ||
1272
+ !value.trim())
1273
+ fail(`${label}_invalid`);
1274
+ }
1275
+ function digest(value, label) {
1276
+ if (typeof value !== "string" || !DIGEST.test(value))
1277
+ fail(`${label}_invalid`);
1278
+ }
1279
+ function positiveInteger(value, label) {
1280
+ if (!Number.isSafeInteger(value) || value <= 0)
1281
+ fail(`${label}_invalid`);
1282
+ }
1283
+ function nonNegativeInteger(value, label) {
1284
+ if (!Number.isSafeInteger(value) || value < 0)
1285
+ fail(`${label}_invalid`);
1286
+ }
1287
+ function basisPoints(value, label) {
1288
+ if (!Number.isSafeInteger(value) ||
1289
+ value < 0 ||
1290
+ value > 10_000)
1291
+ fail(`${label}_invalid`);
1292
+ }
1293
+ function freeze(value) {
1294
+ return deepFreezePlanning(value);
1295
+ }
1296
+ function immutableMethod(value) {
1297
+ return immutableValue(value, false);
1298
+ }
1299
+ function immutableValue(value, enumerable) {
1300
+ return {
1301
+ value,
1302
+ writable: false,
1303
+ configurable: false,
1304
+ enumerable,
1305
+ };
1306
+ }
1307
+ function fail(code) {
1308
+ throw new TypeError(`scalable_evaluation_${code}`);
1309
+ }
1310
+ //# sourceMappingURL=scalable-evaluation-runtime.js.map