@openprose/reactor-cradle 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +270 -0
- package/dist/assert/index.d.ts +35 -0
- package/dist/assert/index.d.ts.map +1 -0
- package/dist/assert/index.js +398 -0
- package/dist/baselines/cost-thesis/index.d.ts +103 -0
- package/dist/baselines/cost-thesis/index.d.ts.map +1 -0
- package/dist/baselines/cost-thesis/index.js +337 -0
- package/dist/baselines/naive-loop/index.d.ts +46 -0
- package/dist/baselines/naive-loop/index.d.ts.map +1 -0
- package/dist/baselines/naive-loop/index.js +188 -0
- package/dist/baselines/no-memo/index.d.ts +84 -0
- package/dist/baselines/no-memo/index.d.ts.map +1 -0
- package/dist/baselines/no-memo/index.js +226 -0
- package/dist/doubles/clock.d.ts +9 -0
- package/dist/doubles/clock.d.ts.map +1 -0
- package/dist/doubles/clock.js +42 -0
- package/dist/doubles/storage.d.ts +24 -0
- package/dist/doubles/storage.d.ts.map +1 -0
- package/dist/doubles/storage.js +191 -0
- package/dist/eval/index.d.ts +204 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +596 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/policy-author/index.d.ts +103 -0
- package/dist/policy-author/index.d.ts.map +1 -0
- package/dist/policy-author/index.js +358 -0
- package/dist/policy-drift/index.d.ts +64 -0
- package/dist/policy-drift/index.d.ts.map +1 -0
- package/dist/policy-drift/index.js +495 -0
- package/dist/policy-replay/index.d.ts +106 -0
- package/dist/policy-replay/index.d.ts.map +1 -0
- package/dist/policy-replay/index.js +361 -0
- package/dist/provider-parity/index.d.ts +91 -0
- package/dist/provider-parity/index.d.ts.map +1 -0
- package/dist/provider-parity/index.js +439 -0
- package/dist/recompile/index.d.ts +161 -0
- package/dist/recompile/index.d.ts.map +1 -0
- package/dist/recompile/index.js +690 -0
- package/dist/release-candidate/index.d.ts +139 -0
- package/dist/release-candidate/index.d.ts.map +1 -0
- package/dist/release-candidate/index.js +553 -0
- package/dist/release-parity/index.d.ts +80 -0
- package/dist/release-parity/index.d.ts.map +1 -0
- package/dist/release-parity/index.js +1035 -0
- package/dist/replay/model-gateway.d.ts +25 -0
- package/dist/replay/model-gateway.d.ts.map +1 -0
- package/dist/replay/model-gateway.js +166 -0
- package/dist/replay/parity.d.ts +110 -0
- package/dist/replay/parity.d.ts.map +1 -0
- package/dist/replay/parity.js +232 -0
- package/dist/rollback/index.d.ts +106 -0
- package/dist/rollback/index.d.ts.map +1 -0
- package/dist/rollback/index.js +694 -0
- package/dist/scenario/parser.d.ts +11 -0
- package/dist/scenario/parser.d.ts.map +1 -0
- package/dist/scenario/parser.js +490 -0
- package/dist/scenario/runner.d.ts +12 -0
- package/dist/scenario/runner.d.ts.map +1 -0
- package/dist/scenario/runner.js +345 -0
- package/dist/scenario/synthetic-world-adapter.d.ts +4 -0
- package/dist/scenario/synthetic-world-adapter.d.ts.map +1 -0
- package/dist/scenario/synthetic-world-adapter.js +82 -0
- package/dist/scenario/time.d.ts +4 -0
- package/dist/scenario/time.d.ts.map +1 -0
- package/dist/scenario/time.js +45 -0
- package/dist/scenario/types.d.ts +149 -0
- package/dist/scenario/types.d.ts.map +1 -0
- package/dist/scenario/types.js +5 -0
- package/dist/spikes/index.d.ts +10 -0
- package/dist/spikes/index.d.ts.map +1 -0
- package/dist/spikes/index.js +29 -0
- package/dist/spikes/k1-ensemble-spread.d.ts +88 -0
- package/dist/spikes/k1-ensemble-spread.d.ts.map +1 -0
- package/dist/spikes/k1-ensemble-spread.js +396 -0
- package/dist/spikes/k2-policy-author.d.ts +25 -0
- package/dist/spikes/k2-policy-author.d.ts.map +1 -0
- package/dist/spikes/k2-policy-author.js +503 -0
- package/dist/spikes/live-refresh.d.ts +150 -0
- package/dist/spikes/live-refresh.d.ts.map +1 -0
- package/dist/spikes/live-refresh.js +511 -0
- package/dist/world/index.d.ts +2 -0
- package/dist/world/index.d.ts.map +1 -0
- package/dist/world/index.js +17 -0
- package/dist/world/synthetic-world.d.ts +104 -0
- package/dist/world/synthetic-world.d.ts.map +1 -0
- package/dist/world/synthetic-world.js +449 -0
- package/package.json +139 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SyntheticWorldConnectorV0 = exports.STATIC_SURPRISE_PROFILE_V0 = exports.SYNTHETIC_WORLD_VERSION_V0 = exports.SYNTHETIC_WORLD_SCHEMA_V0 = void 0;
|
|
4
|
+
exports.createSyntheticWorldV0 = createSyntheticWorldV0;
|
|
5
|
+
exports.createSyntheticWorldConnectorV0 = createSyntheticWorldConnectorV0;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
exports.SYNTHETIC_WORLD_SCHEMA_V0 = "openprose.reactor.synthetic-world";
|
|
8
|
+
exports.SYNTHETIC_WORLD_VERSION_V0 = 0;
|
|
9
|
+
const ISO_INSTANT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
10
|
+
exports.STATIC_SURPRISE_PROFILE_V0 = Object.freeze({ kind: "static" });
|
|
11
|
+
class SyntheticWorldConnectorV0 {
|
|
12
|
+
#profile;
|
|
13
|
+
#sources = new Map();
|
|
14
|
+
#history = [];
|
|
15
|
+
#asOf;
|
|
16
|
+
#asOfEpochMs;
|
|
17
|
+
constructor(input) {
|
|
18
|
+
const profile = cloneProfile(input.profile);
|
|
19
|
+
if (profile.kind === "adversarial-silent") {
|
|
20
|
+
throw new Error(`synthetic world surprise profile ${profile.kind} is typed but not implemented in C2`);
|
|
21
|
+
}
|
|
22
|
+
this.#profile = profile;
|
|
23
|
+
this.#asOfEpochMs = parseReplayableInstant(input.initial_as_of, "initial_as_of");
|
|
24
|
+
this.#asOf = canonicalizeInstant(input.initial_as_of, this.#asOfEpochMs);
|
|
25
|
+
for (const source of input.sources) {
|
|
26
|
+
this.#addSource(source);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
read(request) {
|
|
30
|
+
const asOfEpochMs = parseReplayableInstant(request.as_of, "as_of");
|
|
31
|
+
const asOf = canonicalizeInstant(request.as_of, asOfEpochMs);
|
|
32
|
+
assertNotAfter(asOfEpochMs, this.#asOfEpochMs, "read as_of", "world time");
|
|
33
|
+
const source = this.#readSource(request.source_id);
|
|
34
|
+
const revision = readRevisionAt(source, asOfEpochMs, asOf);
|
|
35
|
+
const surprise = createZeroSurpriseReport({
|
|
36
|
+
profile: this.#profile.kind,
|
|
37
|
+
as_of: asOf,
|
|
38
|
+
event_index: this.#history.length,
|
|
39
|
+
});
|
|
40
|
+
const readPayload = this.#readPayload({
|
|
41
|
+
source,
|
|
42
|
+
revision,
|
|
43
|
+
as_of: asOf,
|
|
44
|
+
surprise,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
payload: deepFreezeJsonObject(readPayload),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
advance(input) {
|
|
51
|
+
const nextEpochMs = parseReplayableInstant(input.as_of, "as_of");
|
|
52
|
+
const asOf = canonicalizeInstant(input.as_of, nextEpochMs);
|
|
53
|
+
assertNotBefore(nextEpochMs, this.#asOfEpochMs, "advance as_of", "world time");
|
|
54
|
+
this.#asOf = asOf;
|
|
55
|
+
this.#asOfEpochMs = nextEpochMs;
|
|
56
|
+
if (input.kind === "time") {
|
|
57
|
+
const advanceInput = input.event_id === undefined
|
|
58
|
+
? {
|
|
59
|
+
kind: "time",
|
|
60
|
+
as_of: asOf,
|
|
61
|
+
}
|
|
62
|
+
: {
|
|
63
|
+
kind: "time",
|
|
64
|
+
as_of: asOf,
|
|
65
|
+
event_id: input.event_id,
|
|
66
|
+
};
|
|
67
|
+
return this.#recordAdvance(advanceInput);
|
|
68
|
+
}
|
|
69
|
+
const source = this.#readSource(input.source_id);
|
|
70
|
+
let materialChange = false;
|
|
71
|
+
if (input.payload !== undefined) {
|
|
72
|
+
const payload = createPayloadSnapshot(input.payload);
|
|
73
|
+
const current = latestRevision(source);
|
|
74
|
+
if (payload.canonical !== current.payload_canonical) {
|
|
75
|
+
materialChange = true;
|
|
76
|
+
assertMaterialChangeAllowed({
|
|
77
|
+
profile: this.#profile,
|
|
78
|
+
source_event_ordinal: nextSourceEventOrdinal(this.#history),
|
|
79
|
+
});
|
|
80
|
+
source.revisions.push(createSourceRevision({
|
|
81
|
+
material_version: current.material_version + 1,
|
|
82
|
+
materialized_at: asOf,
|
|
83
|
+
materialized_at_epoch_ms: nextEpochMs,
|
|
84
|
+
payload,
|
|
85
|
+
payload_hash: payloadContentHash(payload.canonical),
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const advanceInput = input.event_id === undefined
|
|
90
|
+
? {
|
|
91
|
+
kind: "source-event",
|
|
92
|
+
as_of: asOf,
|
|
93
|
+
source_id: source.source_id,
|
|
94
|
+
}
|
|
95
|
+
: {
|
|
96
|
+
kind: "source-event",
|
|
97
|
+
as_of: asOf,
|
|
98
|
+
event_id: input.event_id,
|
|
99
|
+
source_id: source.source_id,
|
|
100
|
+
};
|
|
101
|
+
return this.#recordAdvance({
|
|
102
|
+
...advanceInput,
|
|
103
|
+
material_change: materialChange,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
currentAsOf() {
|
|
107
|
+
return this.#asOf;
|
|
108
|
+
}
|
|
109
|
+
history() {
|
|
110
|
+
return Object.freeze(this.#history.map((record) => cloneAdvanceRecord(record)));
|
|
111
|
+
}
|
|
112
|
+
#addSource(seed) {
|
|
113
|
+
assertNonEmptyString(seed.source_id, "source_id");
|
|
114
|
+
if (this.#sources.has(seed.source_id)) {
|
|
115
|
+
throw new Error(`duplicate synthetic world source_id ${seed.source_id}`);
|
|
116
|
+
}
|
|
117
|
+
if (seed.payload_hash !== undefined) {
|
|
118
|
+
assertNonEmptyString(seed.payload_hash, "payload_hash");
|
|
119
|
+
}
|
|
120
|
+
const sourceAsOfRaw = seed.materialized_at ?? this.#asOf;
|
|
121
|
+
const sourceEpochMs = parseReplayableInstant(sourceAsOfRaw, `source ${seed.source_id} materialized_at`);
|
|
122
|
+
assertNotAfter(sourceEpochMs, this.#asOfEpochMs, `source ${seed.source_id} materialized_at`, "initial_as_of");
|
|
123
|
+
const payload = createPayloadSnapshot(seed.payload);
|
|
124
|
+
const revisionBase = {
|
|
125
|
+
material_version: 0,
|
|
126
|
+
materialized_at: canonicalizeInstant(sourceAsOfRaw, sourceEpochMs),
|
|
127
|
+
materialized_at_epoch_ms: sourceEpochMs,
|
|
128
|
+
payload: payload.value,
|
|
129
|
+
payload_canonical: payload.canonical,
|
|
130
|
+
};
|
|
131
|
+
const revision = seed.payload_hash === undefined
|
|
132
|
+
? revisionBase
|
|
133
|
+
: { ...revisionBase, payload_hash: seed.payload_hash };
|
|
134
|
+
this.#sources.set(seed.source_id, {
|
|
135
|
+
source_id: seed.source_id,
|
|
136
|
+
revisions: [revision],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
#readSource(sourceId) {
|
|
140
|
+
assertNonEmptyString(sourceId, "source_id");
|
|
141
|
+
const source = this.#sources.get(sourceId);
|
|
142
|
+
if (source === undefined) {
|
|
143
|
+
throw new Error(`unknown synthetic world source_id ${sourceId}`);
|
|
144
|
+
}
|
|
145
|
+
return source;
|
|
146
|
+
}
|
|
147
|
+
#recordAdvance(input) {
|
|
148
|
+
const eventIndex = this.#history.length;
|
|
149
|
+
const eventId = input.event_id ?? `synthetic-world-event-${eventIndex}`;
|
|
150
|
+
assertNonEmptyString(eventId, "event_id");
|
|
151
|
+
const surpriseEvents = createSurpriseEvents({
|
|
152
|
+
profile: this.#profile.kind,
|
|
153
|
+
as_of: input.as_of,
|
|
154
|
+
event_id: eventId,
|
|
155
|
+
material_change: input.material_change ?? false,
|
|
156
|
+
...(input.source_id === undefined ? {} : { source_id: input.source_id }),
|
|
157
|
+
});
|
|
158
|
+
const base = {
|
|
159
|
+
kind: input.kind,
|
|
160
|
+
event_id: eventId,
|
|
161
|
+
event_index: eventIndex,
|
|
162
|
+
as_of: input.as_of,
|
|
163
|
+
surprise: createSurpriseReport({
|
|
164
|
+
profile: this.#profile.kind,
|
|
165
|
+
as_of: input.as_of,
|
|
166
|
+
event_index: eventIndex,
|
|
167
|
+
surprise_events: surpriseEvents,
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
const record = input.source_id === undefined
|
|
171
|
+
? base
|
|
172
|
+
: { ...base, source_id: input.source_id };
|
|
173
|
+
this.#history.push(record);
|
|
174
|
+
return cloneAdvanceRecord(record);
|
|
175
|
+
}
|
|
176
|
+
#readPayload(input) {
|
|
177
|
+
const base = {
|
|
178
|
+
schema: exports.SYNTHETIC_WORLD_SCHEMA_V0,
|
|
179
|
+
v: exports.SYNTHETIC_WORLD_VERSION_V0,
|
|
180
|
+
profile: this.#profile.kind,
|
|
181
|
+
source_id: input.source.source_id,
|
|
182
|
+
as_of: input.as_of,
|
|
183
|
+
materialized_at: input.revision.materialized_at,
|
|
184
|
+
material_version: input.revision.material_version,
|
|
185
|
+
state: cloneJsonValue(input.revision.payload),
|
|
186
|
+
surprise: input.surprise,
|
|
187
|
+
};
|
|
188
|
+
return input.revision.payload_hash === undefined
|
|
189
|
+
? base
|
|
190
|
+
: {
|
|
191
|
+
...base,
|
|
192
|
+
payload_hash: input.revision.payload_hash,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.SyntheticWorldConnectorV0 = SyntheticWorldConnectorV0;
|
|
197
|
+
function createSyntheticWorldV0(input) {
|
|
198
|
+
return new SyntheticWorldConnectorV0({
|
|
199
|
+
initial_as_of: input.initial_instant,
|
|
200
|
+
profile: normalizeProfile(input.profile),
|
|
201
|
+
sources: input.sources.map((source) => normalizeCreateSource(source)),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function createSyntheticWorldConnectorV0(input) {
|
|
205
|
+
if (input instanceof SyntheticWorldConnectorV0) {
|
|
206
|
+
return input;
|
|
207
|
+
}
|
|
208
|
+
return new SyntheticWorldConnectorV0(input);
|
|
209
|
+
}
|
|
210
|
+
function normalizeProfile(profile) {
|
|
211
|
+
if (typeof profile !== "string") {
|
|
212
|
+
return profile;
|
|
213
|
+
}
|
|
214
|
+
switch (profile) {
|
|
215
|
+
case "static":
|
|
216
|
+
return exports.STATIC_SURPRISE_PROFILE_V0;
|
|
217
|
+
case "periodic-surprise":
|
|
218
|
+
throw new Error("periodic-surprise profile requires an explicit every_events config");
|
|
219
|
+
case "adversarial-silent":
|
|
220
|
+
throw new Error("adversarial-silent profile requires an explicit silent_after_events config");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function normalizeCreateSource(source) {
|
|
224
|
+
const base = {
|
|
225
|
+
source_id: source.id,
|
|
226
|
+
payload: source.payload,
|
|
227
|
+
};
|
|
228
|
+
const withPayloadHash = source.payload_hash === undefined
|
|
229
|
+
? base
|
|
230
|
+
: { ...base, payload_hash: source.payload_hash };
|
|
231
|
+
return source.materialized_at === undefined
|
|
232
|
+
? withPayloadHash
|
|
233
|
+
: { ...withPayloadHash, materialized_at: source.materialized_at };
|
|
234
|
+
}
|
|
235
|
+
function createZeroSurpriseReport(input) {
|
|
236
|
+
return createSurpriseReport({ ...input, surprise_events: [] });
|
|
237
|
+
}
|
|
238
|
+
function createSurpriseReport(input) {
|
|
239
|
+
return deepFreezeJsonObject({
|
|
240
|
+
profile: input.profile,
|
|
241
|
+
as_of: input.as_of,
|
|
242
|
+
event_index: input.event_index,
|
|
243
|
+
surprise_count: input.surprise_events.length,
|
|
244
|
+
material_change: input.surprise_events.some((event) => event.kind === "material-change"),
|
|
245
|
+
surprise_events: input.surprise_events.map((event) => ({ ...event })),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function createSurpriseEvents(input) {
|
|
249
|
+
if (!input.material_change || input.source_id === undefined) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
return Object.freeze([
|
|
253
|
+
{
|
|
254
|
+
kind: "material-change",
|
|
255
|
+
source_id: input.source_id,
|
|
256
|
+
as_of: input.as_of,
|
|
257
|
+
event_id: input.event_id,
|
|
258
|
+
profile: input.profile,
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
function cloneAdvanceRecord(record) {
|
|
263
|
+
const base = {
|
|
264
|
+
kind: record.kind,
|
|
265
|
+
event_id: record.event_id,
|
|
266
|
+
event_index: record.event_index,
|
|
267
|
+
as_of: record.as_of,
|
|
268
|
+
surprise: cloneSurpriseReport(record.surprise),
|
|
269
|
+
};
|
|
270
|
+
return Object.freeze(record.source_id === undefined
|
|
271
|
+
? base
|
|
272
|
+
: { ...base, source_id: record.source_id });
|
|
273
|
+
}
|
|
274
|
+
function cloneSurpriseReport(report) {
|
|
275
|
+
return deepFreezeJsonObject({
|
|
276
|
+
profile: report.profile,
|
|
277
|
+
as_of: report.as_of,
|
|
278
|
+
event_index: report.event_index,
|
|
279
|
+
surprise_count: report.surprise_count,
|
|
280
|
+
material_change: report.material_change,
|
|
281
|
+
surprise_events: report.surprise_events.map((event) => ({ ...event })),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function readRevisionAt(source, asOfEpochMs, asOf) {
|
|
285
|
+
for (let index = source.revisions.length - 1; index >= 0; index -= 1) {
|
|
286
|
+
const revision = source.revisions[index];
|
|
287
|
+
if (revision !== undefined &&
|
|
288
|
+
revision.materialized_at_epoch_ms <= asOfEpochMs) {
|
|
289
|
+
return revision;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
throw new Error(`source_id ${source.source_id} has no synthetic world state at ${asOf}`);
|
|
293
|
+
}
|
|
294
|
+
function latestRevision(source) {
|
|
295
|
+
const revision = source.revisions[source.revisions.length - 1];
|
|
296
|
+
if (revision === undefined) {
|
|
297
|
+
throw new Error(`source_id ${source.source_id} has no revisions`);
|
|
298
|
+
}
|
|
299
|
+
return revision;
|
|
300
|
+
}
|
|
301
|
+
function createSourceRevision(input) {
|
|
302
|
+
const base = {
|
|
303
|
+
material_version: input.material_version,
|
|
304
|
+
materialized_at: input.materialized_at,
|
|
305
|
+
materialized_at_epoch_ms: input.materialized_at_epoch_ms,
|
|
306
|
+
payload: input.payload.value,
|
|
307
|
+
payload_canonical: input.payload.canonical,
|
|
308
|
+
};
|
|
309
|
+
return input.payload_hash === undefined
|
|
310
|
+
? base
|
|
311
|
+
: { ...base, payload_hash: input.payload_hash };
|
|
312
|
+
}
|
|
313
|
+
function nextSourceEventOrdinal(history) {
|
|
314
|
+
return history.filter((record) => record.kind === "source-event").length + 1;
|
|
315
|
+
}
|
|
316
|
+
function assertMaterialChangeAllowed(input) {
|
|
317
|
+
switch (input.profile.kind) {
|
|
318
|
+
case "static":
|
|
319
|
+
throw new Error("static surprise profile cannot apply material source changes");
|
|
320
|
+
case "periodic-surprise":
|
|
321
|
+
if (input.source_event_ordinal % input.profile.every_events !== 0) {
|
|
322
|
+
throw new Error(`periodic-surprise material changes are only allowed on every ${input.profile.every_events} source events`);
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function cloneProfile(profile) {
|
|
328
|
+
switch (profile.kind) {
|
|
329
|
+
case "static":
|
|
330
|
+
return exports.STATIC_SURPRISE_PROFILE_V0;
|
|
331
|
+
case "periodic-surprise":
|
|
332
|
+
if (!Number.isSafeInteger(profile.every_events) ||
|
|
333
|
+
profile.every_events <= 0) {
|
|
334
|
+
throw new RangeError("periodic-surprise every_events must be a positive safe integer");
|
|
335
|
+
}
|
|
336
|
+
return Object.freeze({
|
|
337
|
+
kind: "periodic-surprise",
|
|
338
|
+
every_events: profile.every_events,
|
|
339
|
+
});
|
|
340
|
+
case "adversarial-silent":
|
|
341
|
+
return Object.freeze({
|
|
342
|
+
kind: "adversarial-silent",
|
|
343
|
+
silent_after_events: Object.freeze([...profile.silent_after_events]),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function createPayloadSnapshot(payload) {
|
|
348
|
+
const canonical = renderCanonical(payload);
|
|
349
|
+
return {
|
|
350
|
+
value: cloneJsonValue(payload),
|
|
351
|
+
canonical,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function payloadContentHash(canonical) {
|
|
355
|
+
const digest = (0, node_crypto_1.createHash)("sha256").update(canonical).digest("hex");
|
|
356
|
+
return `sha256:${digest}`;
|
|
357
|
+
}
|
|
358
|
+
function cloneJsonValue(value) {
|
|
359
|
+
return deepFreezeJsonValue(JSON.parse(renderCanonical(value)));
|
|
360
|
+
}
|
|
361
|
+
function deepFreezeJsonObject(value) {
|
|
362
|
+
return deepFreezeJsonValue(value);
|
|
363
|
+
}
|
|
364
|
+
function deepFreezeJsonValue(value) {
|
|
365
|
+
if (value !== null && typeof value === "object") {
|
|
366
|
+
if (Array.isArray(value)) {
|
|
367
|
+
for (const item of value) {
|
|
368
|
+
deepFreezeJsonValue(item);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
for (const item of Object.values(value)) {
|
|
373
|
+
deepFreezeJsonValue(item);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
Object.freeze(value);
|
|
377
|
+
}
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
function parseReplayableInstant(instant, label) {
|
|
381
|
+
if (!ISO_INSTANT_PATTERN.test(instant)) {
|
|
382
|
+
throw new RangeError(`${label} must be an ISO-8601 UTC instant`);
|
|
383
|
+
}
|
|
384
|
+
const epochMs = Date.parse(instant);
|
|
385
|
+
if (!Number.isFinite(epochMs)) {
|
|
386
|
+
throw new RangeError(`${label} must be a valid ISO-8601 UTC instant`);
|
|
387
|
+
}
|
|
388
|
+
const canonical = new Date(epochMs).toISOString();
|
|
389
|
+
if (instant !== canonical && instant !== canonical.replace(".000Z", "Z")) {
|
|
390
|
+
throw new RangeError(`${label} must be a valid ISO-8601 UTC instant`);
|
|
391
|
+
}
|
|
392
|
+
return epochMs;
|
|
393
|
+
}
|
|
394
|
+
function canonicalizeInstant(instant, epochMs) {
|
|
395
|
+
const canonical = new Date(epochMs).toISOString();
|
|
396
|
+
return instant === canonical ? instant : canonical;
|
|
397
|
+
}
|
|
398
|
+
function assertNotBefore(actualEpochMs, minimumEpochMs, actualLabel, minimumLabel) {
|
|
399
|
+
if (actualEpochMs < minimumEpochMs) {
|
|
400
|
+
throw new RangeError(`${actualLabel} must not be before ${minimumLabel}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function assertNotAfter(actualEpochMs, maximumEpochMs, actualLabel, maximumLabel) {
|
|
404
|
+
if (actualEpochMs > maximumEpochMs) {
|
|
405
|
+
throw new RangeError(`${actualLabel} must not be after ${maximumLabel}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function assertNonEmptyString(value, label) {
|
|
409
|
+
if (value.length === 0) {
|
|
410
|
+
throw new Error(`${label} must be non-empty`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function renderCanonical(value) {
|
|
414
|
+
if (value === null) {
|
|
415
|
+
return "null";
|
|
416
|
+
}
|
|
417
|
+
switch (typeof value) {
|
|
418
|
+
case "boolean":
|
|
419
|
+
return value ? "true" : "false";
|
|
420
|
+
case "number":
|
|
421
|
+
if (!Number.isFinite(value)) {
|
|
422
|
+
throw new TypeError("Cannot canonicalize non-finite numbers");
|
|
423
|
+
}
|
|
424
|
+
return JSON.stringify(value);
|
|
425
|
+
case "string":
|
|
426
|
+
return JSON.stringify(value);
|
|
427
|
+
case "object":
|
|
428
|
+
if (Array.isArray(value)) {
|
|
429
|
+
return `[${value.map((item) => renderCanonical(item)).join(",")}]`;
|
|
430
|
+
}
|
|
431
|
+
return renderCanonicalObject(value);
|
|
432
|
+
case "undefined":
|
|
433
|
+
case "bigint":
|
|
434
|
+
case "function":
|
|
435
|
+
case "symbol":
|
|
436
|
+
throw new TypeError(`Cannot canonicalize ${typeof value}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function renderCanonicalObject(value) {
|
|
440
|
+
const fields = [];
|
|
441
|
+
for (const key of Object.keys(value).sort()) {
|
|
442
|
+
const item = value[key];
|
|
443
|
+
if (item === undefined) {
|
|
444
|
+
throw new TypeError(`Cannot canonicalize undefined field ${key}`);
|
|
445
|
+
}
|
|
446
|
+
fields.push(`${JSON.stringify(key)}:${renderCanonical(item)}`);
|
|
447
|
+
}
|
|
448
|
+
return `{${fields.join(",")}}`;
|
|
449
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openprose/reactor-cradle",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Deterministic Cradle test harness for the OpenProse Reactor.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./assert": {
|
|
14
|
+
"types": "./dist/assert/index.d.ts",
|
|
15
|
+
"default": "./dist/assert/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./eval": {
|
|
18
|
+
"types": "./dist/eval/index.d.ts",
|
|
19
|
+
"default": "./dist/eval/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./spikes": {
|
|
22
|
+
"types": "./dist/spikes/index.d.ts",
|
|
23
|
+
"default": "./dist/spikes/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./spikes/live-refresh": {
|
|
26
|
+
"types": "./dist/spikes/live-refresh.d.ts",
|
|
27
|
+
"default": "./dist/spikes/live-refresh.js"
|
|
28
|
+
},
|
|
29
|
+
"./spikes/k1-ensemble-spread": {
|
|
30
|
+
"types": "./dist/spikes/k1-ensemble-spread.d.ts",
|
|
31
|
+
"default": "./dist/spikes/k1-ensemble-spread.js"
|
|
32
|
+
},
|
|
33
|
+
"./spikes/k2-policy-author": {
|
|
34
|
+
"types": "./dist/spikes/k2-policy-author.d.ts",
|
|
35
|
+
"default": "./dist/spikes/k2-policy-author.js"
|
|
36
|
+
},
|
|
37
|
+
"./doubles/clock": {
|
|
38
|
+
"types": "./dist/doubles/clock.d.ts",
|
|
39
|
+
"default": "./dist/doubles/clock.js"
|
|
40
|
+
},
|
|
41
|
+
"./doubles/storage": {
|
|
42
|
+
"types": "./dist/doubles/storage.d.ts",
|
|
43
|
+
"default": "./dist/doubles/storage.js"
|
|
44
|
+
},
|
|
45
|
+
"./policy-author": {
|
|
46
|
+
"types": "./dist/policy-author/index.d.ts",
|
|
47
|
+
"default": "./dist/policy-author/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./policy-drift": {
|
|
50
|
+
"types": "./dist/policy-drift/index.d.ts",
|
|
51
|
+
"default": "./dist/policy-drift/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./policy-replay": {
|
|
54
|
+
"types": "./dist/policy-replay/index.d.ts",
|
|
55
|
+
"default": "./dist/policy-replay/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./recompile": {
|
|
58
|
+
"types": "./dist/recompile/index.d.ts",
|
|
59
|
+
"default": "./dist/recompile/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./release-parity": {
|
|
62
|
+
"types": "./dist/release-parity/index.d.ts",
|
|
63
|
+
"default": "./dist/release-parity/index.js"
|
|
64
|
+
},
|
|
65
|
+
"./release-candidate": {
|
|
66
|
+
"types": "./dist/release-candidate/index.d.ts",
|
|
67
|
+
"default": "./dist/release-candidate/index.js"
|
|
68
|
+
},
|
|
69
|
+
"./rollback": {
|
|
70
|
+
"types": "./dist/rollback/index.d.ts",
|
|
71
|
+
"default": "./dist/rollback/index.js"
|
|
72
|
+
},
|
|
73
|
+
"./replay/model-gateway": {
|
|
74
|
+
"types": "./dist/replay/model-gateway.d.ts",
|
|
75
|
+
"default": "./dist/replay/model-gateway.js"
|
|
76
|
+
},
|
|
77
|
+
"./replay/parity": {
|
|
78
|
+
"types": "./dist/replay/parity.d.ts",
|
|
79
|
+
"default": "./dist/replay/parity.js"
|
|
80
|
+
},
|
|
81
|
+
"./scenario/parser": {
|
|
82
|
+
"types": "./dist/scenario/parser.d.ts",
|
|
83
|
+
"default": "./dist/scenario/parser.js"
|
|
84
|
+
},
|
|
85
|
+
"./scenario/runner": {
|
|
86
|
+
"types": "./dist/scenario/runner.d.ts",
|
|
87
|
+
"default": "./dist/scenario/runner.js"
|
|
88
|
+
},
|
|
89
|
+
"./scenario/time": {
|
|
90
|
+
"types": "./dist/scenario/time.d.ts",
|
|
91
|
+
"default": "./dist/scenario/time.js"
|
|
92
|
+
},
|
|
93
|
+
"./scenario/types": {
|
|
94
|
+
"types": "./dist/scenario/types.d.ts",
|
|
95
|
+
"default": "./dist/scenario/types.js"
|
|
96
|
+
},
|
|
97
|
+
"./world": {
|
|
98
|
+
"types": "./dist/world/index.d.ts",
|
|
99
|
+
"default": "./dist/world/index.js"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"files": [
|
|
103
|
+
"dist/**/*.js",
|
|
104
|
+
"dist/**/*.d.ts",
|
|
105
|
+
"dist/**/*.d.ts.map",
|
|
106
|
+
"!dist/**/__tests__/**",
|
|
107
|
+
"!dist/**/*.test.js",
|
|
108
|
+
"!dist/**/*.test.d.ts",
|
|
109
|
+
"!dist/**/*.test.d.ts.map",
|
|
110
|
+
"README.md"
|
|
111
|
+
],
|
|
112
|
+
"sideEffects": false,
|
|
113
|
+
"license": "MIT",
|
|
114
|
+
"repository": {
|
|
115
|
+
"type": "git",
|
|
116
|
+
"url": "git+https://github.com/openprose/prose.git",
|
|
117
|
+
"directory": "packages/reactor-cradle"
|
|
118
|
+
},
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"@openprose/reactor": "0.1.0-rc.1"
|
|
121
|
+
},
|
|
122
|
+
"devDependencies": {
|
|
123
|
+
"@types/node": "^22.10.7",
|
|
124
|
+
"typescript": "^5.9.3"
|
|
125
|
+
},
|
|
126
|
+
"publishConfig": {
|
|
127
|
+
"access": "public",
|
|
128
|
+
"provenance": true
|
|
129
|
+
},
|
|
130
|
+
"engines": {
|
|
131
|
+
"node": ">=20.0.0"
|
|
132
|
+
},
|
|
133
|
+
"scripts": {
|
|
134
|
+
"build": "pnpm --dir ../.. --filter @openprose/reactor build && tsc -p tsconfig.build.json",
|
|
135
|
+
"typecheck": "pnpm --dir ../.. --filter @openprose/reactor build && tsc --noEmit -p tsconfig.json",
|
|
136
|
+
"test:runtime": "pnpm build && node --test $(find dist -name '*.test.js' -type f | sort)",
|
|
137
|
+
"test": "pnpm typecheck && pnpm test:runtime"
|
|
138
|
+
}
|
|
139
|
+
}
|