@world-engines/tmw 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +46 -0
- package/dist/activation.d.ts +50 -0
- package/dist/activation.js +113 -0
- package/dist/audio-presentation-projection.d.ts +23 -0
- package/dist/audio-presentation-projection.js +39 -0
- package/dist/author-scenario-source-v3.d.ts +34 -0
- package/dist/author-scenario-source-v3.js +1 -0
- package/dist/authoring-runtime.d.ts +76 -0
- package/dist/authoring-runtime.js +282 -0
- package/dist/builder-diagnostics.d.ts +3 -0
- package/dist/builder-diagnostics.js +8 -0
- package/dist/builder-node.d.ts +178 -0
- package/dist/builder-node.js +212 -0
- package/dist/builder.d.ts +32 -0
- package/dist/builder.js +127 -0
- package/dist/capability-registry.d.ts +32 -0
- package/dist/capability-registry.js +33 -0
- package/dist/compile-target-profile.d.ts +16 -0
- package/dist/compile-target-profile.js +70 -0
- package/dist/compiled-plan.d.ts +44 -0
- package/dist/compiled-plan.js +1 -0
- package/dist/compiler-compatibility-gate.d.ts +96 -0
- package/dist/compiler-compatibility-gate.js +609 -0
- package/dist/compiler-release-authority.d.ts +212 -0
- package/dist/compiler-release-authority.js +676 -0
- package/dist/compiler-stage-cache.d.ts +42 -0
- package/dist/compiler-stage-cache.js +100 -0
- package/dist/compiler-stage-registry.d.ts +48 -0
- package/dist/compiler-stage-registry.js +126 -0
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +68 -0
- package/dist/condition-graph.d.ts +46 -0
- package/dist/condition-graph.js +135 -0
- package/dist/diagnostics.d.ts +20 -0
- package/dist/diagnostics.js +20 -0
- package/dist/durable-intent.d.ts +29 -0
- package/dist/durable-intent.js +60 -0
- package/dist/effect-flow.d.ts +69 -0
- package/dist/effect-flow.js +98 -0
- package/dist/event-fabric.d.ts +81 -0
- package/dist/event-fabric.js +227 -0
- package/dist/event-lifecycle.d.ts +59 -0
- package/dist/event-lifecycle.js +161 -0
- package/dist/evidence.d.ts +37 -0
- package/dist/evidence.js +92 -0
- package/dist/gameplay-state.d.ts +127 -0
- package/dist/gameplay-state.js +214 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +52 -0
- package/dist/isolated-replay.d.ts +81 -0
- package/dist/isolated-replay.js +316 -0
- package/dist/music-candidate-set.d.ts +38 -0
- package/dist/music-candidate-set.js +102 -0
- package/dist/natural-language-chunking.d.ts +10 -0
- package/dist/natural-language-chunking.js +62 -0
- package/dist/natural-language-ingestion.d.ts +14 -0
- package/dist/natural-language-ingestion.js +128 -0
- package/dist/natural-language-structured-decode.d.ts +2 -0
- package/dist/natural-language-structured-decode.js +69 -0
- package/dist/natural-language-world-types.d.ts +142 -0
- package/dist/natural-language-world-types.js +2 -0
- package/dist/neutral-source-compiler.d.ts +135 -0
- package/dist/neutral-source-compiler.js +608 -0
- package/dist/presentation-intent.d.ts +42 -0
- package/dist/presentation-intent.js +137 -0
- package/dist/recall-query-envelope.d.ts +32 -0
- package/dist/recall-query-envelope.js +51 -0
- package/dist/rewrite-compensation.d.ts +28 -0
- package/dist/rewrite-compensation.js +85 -0
- package/dist/runtime-contract.d.ts +56 -0
- package/dist/runtime-contract.js +96 -0
- package/dist/runtime-domain-capabilities.d.ts +12 -0
- package/dist/runtime-domain-capabilities.js +20 -0
- package/dist/runtime-sav.d.ts +117 -0
- package/dist/runtime-sav.js +356 -0
- package/dist/runtime-trace.d.ts +44 -0
- package/dist/runtime-trace.js +119 -0
- package/dist/runtime.d.ts +238 -0
- package/dist/runtime.js +1887 -0
- package/dist/scene-author-source.d.ts +13 -0
- package/dist/scene-author-source.js +25 -0
- package/dist/scene-state.d.ts +275 -0
- package/dist/scene-state.js +334 -0
- package/dist/scheduler.d.ts +55 -0
- package/dist/scheduler.js +235 -0
- package/dist/selector-ast.d.ts +24 -0
- package/dist/selector-ast.js +18 -0
- package/dist/semantic-verdict.d.ts +48 -0
- package/dist/semantic-verdict.js +101 -0
- package/dist/simulator.d.ts +45 -0
- package/dist/simulator.js +58 -0
- package/dist/source-canonical.d.ts +4 -0
- package/dist/source-canonical.js +51 -0
- package/dist/source-crypto.d.ts +20 -0
- package/dist/source-crypto.js +17 -0
- package/dist/source-revision.d.ts +22 -0
- package/dist/source-revision.js +43 -0
- package/dist/spatial-impact-receipt.d.ts +38 -0
- package/dist/spatial-impact-receipt.js +67 -0
- package/dist/temporal-rewrite.d.ts +59 -0
- package/dist/temporal-rewrite.js +214 -0
- package/dist/time-domains.d.ts +37 -0
- package/dist/time-domains.js +142 -0
- package/dist/timeline-revision.d.ts +28 -0
- package/dist/timeline-revision.js +59 -0
- package/dist/trigger-document-v3.d.ts +99 -0
- package/dist/trigger-document-v3.js +334 -0
- package/dist/turn-commit.d.ts +56 -0
- package/dist/turn-commit.js +190 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.js +142 -0
- package/dist/value-graph.d.ts +98 -0
- package/dist/value-graph.js +318 -0
- package/dist/view-event-delivery.d.ts +47 -0
- package/dist/view-event-delivery.js +197 -0
- package/dist/view-interaction-manifest-v3.d.ts +60 -0
- package/dist/view-interaction-manifest-v3.js +198 -0
- package/dist/view-payload-schema.d.ts +17 -0
- package/dist/view-payload-schema.js +84 -0
- package/package.json +62 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { preSceneRuntimeContractHashV3, TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
|
|
2
|
+
import { canonicalDurableIntent } from "./durable-intent.js";
|
|
3
|
+
import { canonicalAudioPresentationProjection } from "./audio-presentation-projection.js";
|
|
4
|
+
import { emptyTmwGameplayStateV1, persistentTmwGameplayStateV1 } from "./gameplay-state.js";
|
|
5
|
+
import { canonicalSchedulerSnapshot } from "./scheduler.js";
|
|
6
|
+
import { canonicalTmwPendingSceneClockCrossingV1, canonicalTmwSceneStateV1, } from "./scene-state.js";
|
|
7
|
+
import { canonicalTimelineRevision, } from "./timeline-revision.js";
|
|
8
|
+
import { canonicalTmwJson, canonicalTmwJsonValue, requireTmwIdentifier, requireTmwSafeInteger, requireTmwSha256, tmwSha256Hex, } from "./runtime-trace.js";
|
|
9
|
+
export const TMW_RUNTIME_SAV_EXTENSION_ID = "tmw.runtime.v3";
|
|
10
|
+
export const TMW_RUNTIME_SAV_SCHEMA_VERSION = 3;
|
|
11
|
+
export const TMW_RUNTIME_SAV_CODEC = "application/vnd.world-engines.tmw-runtime-v3+json;canonical=1";
|
|
12
|
+
function canonicalBinding(input) {
|
|
13
|
+
if (input.runtimeGeneration !== 3)
|
|
14
|
+
throw new Error("unsupported_runtime_generation");
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
epoch: requireTmwSafeInteger(input.epoch, "sav_epoch", 1),
|
|
17
|
+
runtimeGeneration: TMW_RUNTIME_GENERATION,
|
|
18
|
+
runtimeContractHash: requireTmwSha256(input.runtimeContractHash, "sav_contract_hash"),
|
|
19
|
+
artifactDigest: requireTmwSha256(input.artifactDigest, "sav_artifact_digest"),
|
|
20
|
+
sourceRevision: requireTmwIdentifier(input.sourceRevision, "sav_source_revision"),
|
|
21
|
+
activeTimelineRevision: requireTmwIdentifier(input.activeTimelineRevision, "sav_timeline_revision"),
|
|
22
|
+
turnId: requireTmwIdentifier(input.turnId, "sav_turn_id"),
|
|
23
|
+
preActivationTerminalDigest: requireTmwSha256(input.preActivationTerminalDigest, "sav_pre_activation_terminal_digest"),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function uniqueSortedIdentifiers(values, label) {
|
|
27
|
+
const canonical = values.map((value) => requireTmwIdentifier(value, label));
|
|
28
|
+
if (new Set(canonical).size !== canonical.length)
|
|
29
|
+
throw new Error(`tmw_${label}_duplicate`);
|
|
30
|
+
return Object.freeze([...canonical].sort());
|
|
31
|
+
}
|
|
32
|
+
function uniqueSortedDigests(values, label) {
|
|
33
|
+
const canonical = values.map((value) => requireTmwSha256(value, label));
|
|
34
|
+
if (new Set(canonical).size !== canonical.length)
|
|
35
|
+
throw new Error(`tmw_${label}_duplicate`);
|
|
36
|
+
return Object.freeze([...canonical].sort());
|
|
37
|
+
}
|
|
38
|
+
function canonicalRecoveryOperations(values) {
|
|
39
|
+
const byOperationId = new Map();
|
|
40
|
+
for (const value of values) {
|
|
41
|
+
const operation = canonicalTmwJsonValue(value, "sav_world_mutation_recovery_operation");
|
|
42
|
+
if (typeof operation !== "object" || operation === null || Array.isArray(operation)) {
|
|
43
|
+
throw new Error("tmw_sav_world_mutation_recovery_operation_invalid");
|
|
44
|
+
}
|
|
45
|
+
const operationRecord = operation;
|
|
46
|
+
const operationId = requireTmwIdentifier(operationRecord.operationId, "sav_world_mutation_recovery_operation_id");
|
|
47
|
+
if (byOperationId.has(operationId)) {
|
|
48
|
+
throw new Error("tmw_sav_world_mutation_recovery_operation_duplicate");
|
|
49
|
+
}
|
|
50
|
+
byOperationId.set(operationId, operation);
|
|
51
|
+
}
|
|
52
|
+
return Object.freeze([...byOperationId.entries()]
|
|
53
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
54
|
+
.map(([, operation]) => operation));
|
|
55
|
+
}
|
|
56
|
+
export function canonicalRuntimeSavPayload(input) {
|
|
57
|
+
if (input.schemaVersion !== 3)
|
|
58
|
+
throw new Error("unsupported_sav_generation");
|
|
59
|
+
const binding = canonicalBinding(input);
|
|
60
|
+
const scheduler = canonicalSchedulerSnapshot(input.scheduler);
|
|
61
|
+
const scene = input.scene === null ? null : canonicalTmwSceneStateV1(input.scene);
|
|
62
|
+
if (!Array.isArray(input.pendingSceneClockCrossings))
|
|
63
|
+
throw new Error("tmw_sav_pending_scene_crossings_invalid");
|
|
64
|
+
const pendingSceneClockCrossings = input.pendingSceneClockCrossings.map(canonicalTmwPendingSceneClockCrossingV1);
|
|
65
|
+
if (new Set(pendingSceneClockCrossings.map((item) => item.eventId)).size !== pendingSceneClockCrossings.length) {
|
|
66
|
+
throw new Error("tmw_sav_pending_scene_crossing_duplicate");
|
|
67
|
+
}
|
|
68
|
+
const projectedWorldTime = scheduler.cursor?.worldTime ?? null;
|
|
69
|
+
if (scene !== null && projectedWorldTime !== scene.worldTime) {
|
|
70
|
+
throw new Error("tmw_sav_scene_scheduler_world_time_mismatch");
|
|
71
|
+
}
|
|
72
|
+
const intents = input.durableIntents.map(canonicalDurableIntent).sort((left, right) => left.intentId < right.intentId ? -1 : left.intentId > right.intentId ? 1 : 0);
|
|
73
|
+
if (new Set(intents.map((value) => value.intentId)).size !== intents.length) {
|
|
74
|
+
throw new Error("tmw_sav_intent_duplicate");
|
|
75
|
+
}
|
|
76
|
+
for (const suspension of scheduler.suspensions) {
|
|
77
|
+
if (suspension.epoch !== binding.epoch
|
|
78
|
+
|| suspension.runtimeContractHash !== binding.runtimeContractHash
|
|
79
|
+
|| suspension.artifactDigest !== binding.artifactDigest
|
|
80
|
+
|| suspension.sourceRevision !== binding.sourceRevision
|
|
81
|
+
|| suspension.timelineRevision !== binding.activeTimelineRevision) {
|
|
82
|
+
throw new Error("tmw_sav_suspension_binding_mismatch");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const intent of intents) {
|
|
86
|
+
if (intent.epoch !== binding.epoch
|
|
87
|
+
|| intent.runtimeContractHash !== binding.runtimeContractHash
|
|
88
|
+
|| intent.artifactDigest !== binding.artifactDigest
|
|
89
|
+
|| intent.sourceRevision !== binding.sourceRevision
|
|
90
|
+
|| intent.timelineRevision !== binding.activeTimelineRevision) {
|
|
91
|
+
throw new Error("tmw_sav_intent_binding_mismatch");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const activeTimeline = canonicalTimelineRevision(input.activeTimeline);
|
|
95
|
+
if (activeTimeline.revisionId !== binding.activeTimelineRevision
|
|
96
|
+
|| activeTimeline.active !== true
|
|
97
|
+
|| activeTimeline.epoch !== binding.epoch
|
|
98
|
+
|| activeTimeline.runtimeContractHash !== binding.runtimeContractHash
|
|
99
|
+
|| activeTimeline.artifactDigest !== binding.artifactDigest
|
|
100
|
+
|| activeTimeline.sourceRevision !== binding.sourceRevision
|
|
101
|
+
|| activeTimeline.ledgerHead !== input.ledgerHead) {
|
|
102
|
+
throw new Error("tmw_sav_timeline_binding_mismatch");
|
|
103
|
+
}
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
schemaVersion: 3,
|
|
106
|
+
...binding,
|
|
107
|
+
activeTimeline,
|
|
108
|
+
ledgerHead: requireTmwSha256(input.ledgerHead, "sav_ledger_head"),
|
|
109
|
+
eventLedger: canonicalTmwJsonValue(input.eventLedger, "sav_event_ledger"),
|
|
110
|
+
narrative: canonicalTmwJsonValue(input.narrative, "sav_narrative"),
|
|
111
|
+
gameplayState: persistentTmwGameplayStateV1(input.gameplayState ?? emptyTmwGameplayStateV1()),
|
|
112
|
+
gameplayInitialState: persistentTmwGameplayStateV1(input.gameplayInitialState ?? input.gameplayState ?? emptyTmwGameplayStateV1()),
|
|
113
|
+
scene,
|
|
114
|
+
pendingSceneClockCrossings: Object.freeze(pendingSceneClockCrossings),
|
|
115
|
+
scheduler,
|
|
116
|
+
conditionState: canonicalTmwJsonValue(input.conditionState, "sav_condition_state"),
|
|
117
|
+
durableIntents: Object.freeze(intents),
|
|
118
|
+
presentationIntents: Object.freeze(input.presentationIntents.map((intent) => canonicalTmwJsonValue(intent, "sav_presentation_intent"))),
|
|
119
|
+
audioPresentation: input.audioPresentation == null
|
|
120
|
+
? null
|
|
121
|
+
: canonicalAudioPresentationProjection(input.audioPresentation),
|
|
122
|
+
semanticVerdictRefs: uniqueSortedIdentifiers(input.semanticVerdictRefs, "sav_verdict_ref"),
|
|
123
|
+
candidateSetRevisions: uniqueSortedIdentifiers(input.candidateSetRevisions, "sav_candidate_revision"),
|
|
124
|
+
operationReceiptDigests: uniqueSortedDigests(input.operationReceiptDigests, "sav_operation_receipt_digest"),
|
|
125
|
+
completedOperationIds: uniqueSortedIdentifiers(input.completedOperationIds ?? [], "sav_completed_operation_id"),
|
|
126
|
+
worldMutationRecoveryOperations: canonicalRecoveryOperations(input.worldMutationRecoveryOperations),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function hexToBytes(hex) {
|
|
130
|
+
return Uint8Array.from(hex.match(/.{2}/g)?.map((pair) => Number.parseInt(pair, 16)) ?? []);
|
|
131
|
+
}
|
|
132
|
+
function bytesToHex(bytes) {
|
|
133
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
134
|
+
}
|
|
135
|
+
export async function runtimeSavSectionDigest(section) {
|
|
136
|
+
return await runtimeSavPayloadDigest(await decodeRuntimeSavSectionSelfBound(section));
|
|
137
|
+
}
|
|
138
|
+
export async function runtimeSavPayloadDigest(input) {
|
|
139
|
+
const payload = canonicalRuntimeSavPayload(input);
|
|
140
|
+
const { savSectionDigest: _selfDigest, ...timelineWithoutSelfDigest } = payload.activeTimeline;
|
|
141
|
+
return await tmwSha256Hex(canonicalTmwJson({
|
|
142
|
+
...payload,
|
|
143
|
+
activeTimeline: timelineWithoutSelfDigest,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
function canonicalPublishedMigrationSide(input, label, requireCompiledPlan) {
|
|
147
|
+
const side = Object.freeze({
|
|
148
|
+
contentEpoch: requireTmwSafeInteger(input.contentEpoch, `${label}_epoch`, 1),
|
|
149
|
+
reviewedHeadDigest: requireTmwSha256(input.reviewedHeadDigest, `${label}_reviewed_head_digest`),
|
|
150
|
+
runtimeContractHash: requireTmwSha256(input.runtimeContractHash, `${label}_runtime_contract_hash`),
|
|
151
|
+
artifactDigest: requireTmwSha256(input.artifactDigest, `${label}_artifact_digest`),
|
|
152
|
+
sourceRevision: requireTmwIdentifier(input.sourceRevision, `${label}_source_revision`),
|
|
153
|
+
...(requireCompiledPlan
|
|
154
|
+
? { compiledPlanSha256: requireTmwSha256(input.compiledPlanSha256, `${label}_compiled_plan_sha256`) }
|
|
155
|
+
: {}),
|
|
156
|
+
});
|
|
157
|
+
return side;
|
|
158
|
+
}
|
|
159
|
+
/** Fail closed on a replay, cross-scenario, source, epoch, or plan identity drift. */
|
|
160
|
+
export function canonicalPreSceneRuntimeSavPublishedMigrationV1(input) {
|
|
161
|
+
if (input.schemaVersion !== 1)
|
|
162
|
+
throw new Error("tmw_sav_published_migration_schema_unsupported");
|
|
163
|
+
const predecessor = canonicalPublishedMigrationSide(input.predecessor, "sav_migration_predecessor", false);
|
|
164
|
+
const successor = canonicalPublishedMigrationSide(input.successor, "sav_migration_successor", true);
|
|
165
|
+
if (predecessor.reviewedHeadDigest === successor.reviewedHeadDigest
|
|
166
|
+
|| predecessor.contentEpoch !== successor.contentEpoch
|
|
167
|
+
|| predecessor.runtimeContractHash === successor.runtimeContractHash) {
|
|
168
|
+
throw new Error("tmw_sav_published_migration_identity_invalid");
|
|
169
|
+
}
|
|
170
|
+
return Object.freeze({
|
|
171
|
+
schemaVersion: 1,
|
|
172
|
+
scenarioId: requireTmwIdentifier(input.scenarioId, "sav_migration_scenario_id"),
|
|
173
|
+
predecessor,
|
|
174
|
+
successor: successor,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/** Explicit pre-Scene migration; normal decoders never synthesize Scene from a malformed field. */
|
|
178
|
+
export async function migratePreSceneRuntimeSavV3(legacyInput, authority) {
|
|
179
|
+
const raw = legacyInput;
|
|
180
|
+
if (Object.prototype.hasOwnProperty.call(raw, "scene")
|
|
181
|
+
|| Object.prototype.hasOwnProperty.call(raw, "pendingSceneClockCrossings")) {
|
|
182
|
+
throw new Error("tmw_sav_scene_migration_not_legacy");
|
|
183
|
+
}
|
|
184
|
+
const expectedLegacyHash = await preSceneRuntimeContractHashV3();
|
|
185
|
+
const successorRuntimeContractHash = requireTmwSha256(authority.successorRuntimeContractHash, "sav_migration_successor_contract_hash");
|
|
186
|
+
const expectedLegacyArtifactDigest = requireTmwSha256(authority.expectedLegacyArtifactDigest, "sav_migration_legacy_artifact_digest");
|
|
187
|
+
if (legacyInput.runtimeContractHash !== expectedLegacyHash || expectedLegacyHash === successorRuntimeContractHash) {
|
|
188
|
+
throw new Error("tmw_sav_scene_migration_contract_mismatch");
|
|
189
|
+
}
|
|
190
|
+
if (legacyInput.artifactDigest !== expectedLegacyArtifactDigest) {
|
|
191
|
+
throw new Error("tmw_sav_scene_migration_artifact_mismatch");
|
|
192
|
+
}
|
|
193
|
+
const scheduler = canonicalSchedulerSnapshot(legacyInput.scheduler);
|
|
194
|
+
const scene = canonicalTmwSceneStateV1({
|
|
195
|
+
schemaVersion: 1,
|
|
196
|
+
sceneId: authority.sceneId,
|
|
197
|
+
revision: 0,
|
|
198
|
+
worldTime: scheduler.cursor?.worldTime ?? null,
|
|
199
|
+
presentEntityNodeIds: authority.presentEntityNodeIds,
|
|
200
|
+
spatialRef: authority.spatialRef,
|
|
201
|
+
manuscriptNotes: Object.freeze([]),
|
|
202
|
+
});
|
|
203
|
+
const validatedLegacy = canonicalRuntimeSavPayload({
|
|
204
|
+
...legacyInput,
|
|
205
|
+
scene,
|
|
206
|
+
pendingSceneClockCrossings: Object.freeze([]),
|
|
207
|
+
});
|
|
208
|
+
const legacyPayloadDigest = await tmwSha256Hex(canonicalTmwJson(legacyInput));
|
|
209
|
+
const operationId = requireTmwIdentifier(authority.operationId, "sav_migration_operation_id");
|
|
210
|
+
const successorArtifactDigest = requireTmwSha256(authority.successorArtifactDigest, "sav_migration_successor_artifact_digest");
|
|
211
|
+
const successorRevision = `tmw3:scene-migration:${legacyPayloadDigest}`;
|
|
212
|
+
const provisional = Object.freeze({
|
|
213
|
+
...validatedLegacy,
|
|
214
|
+
runtimeContractHash: successorRuntimeContractHash,
|
|
215
|
+
artifactDigest: successorArtifactDigest,
|
|
216
|
+
activeTimelineRevision: successorRevision,
|
|
217
|
+
turnId: requireTmwIdentifier(authority.turnId, "sav_migration_turn_id"),
|
|
218
|
+
preActivationTerminalDigest: legacyPayloadDigest,
|
|
219
|
+
activeTimeline: Object.freeze({
|
|
220
|
+
...validatedLegacy.activeTimeline,
|
|
221
|
+
revisionId: successorRevision,
|
|
222
|
+
parentRevisionId: validatedLegacy.activeTimelineRevision,
|
|
223
|
+
revisionSequence: validatedLegacy.activeTimeline.revisionSequence + 1,
|
|
224
|
+
runtimeContractHash: successorRuntimeContractHash,
|
|
225
|
+
artifactDigest: successorArtifactDigest,
|
|
226
|
+
operationId,
|
|
227
|
+
savSectionDigest: "0".repeat(64),
|
|
228
|
+
createdFrom: "turn",
|
|
229
|
+
}),
|
|
230
|
+
scene,
|
|
231
|
+
pendingSceneClockCrossings: Object.freeze([]),
|
|
232
|
+
scheduler: Object.freeze({
|
|
233
|
+
cursor: scheduler.cursor ?? null,
|
|
234
|
+
suspensions: Object.freeze(scheduler.suspensions.map((suspension) => Object.freeze({
|
|
235
|
+
...suspension,
|
|
236
|
+
runtimeContractHash: successorRuntimeContractHash,
|
|
237
|
+
artifactDigest: successorArtifactDigest,
|
|
238
|
+
timelineRevision: successorRevision,
|
|
239
|
+
}))),
|
|
240
|
+
}),
|
|
241
|
+
durableIntents: Object.freeze(validatedLegacy.durableIntents.map((intent) => Object.freeze({
|
|
242
|
+
...intent,
|
|
243
|
+
runtimeContractHash: successorRuntimeContractHash,
|
|
244
|
+
artifactDigest: successorArtifactDigest,
|
|
245
|
+
timelineRevision: successorRevision,
|
|
246
|
+
}))),
|
|
247
|
+
operationReceiptDigests: Object.freeze([...validatedLegacy.operationReceiptDigests, legacyPayloadDigest]),
|
|
248
|
+
completedOperationIds: Object.freeze([...(validatedLegacy.completedOperationIds ?? []), operationId]),
|
|
249
|
+
});
|
|
250
|
+
const successorPayloadDigest = await runtimeSavPayloadDigest(provisional);
|
|
251
|
+
const payload = canonicalRuntimeSavPayload(Object.freeze({
|
|
252
|
+
...provisional,
|
|
253
|
+
activeTimeline: Object.freeze({ ...provisional.activeTimeline, savSectionDigest: successorPayloadDigest }),
|
|
254
|
+
}));
|
|
255
|
+
return Object.freeze({ legacyPayloadDigest, successorPayloadDigest, payload });
|
|
256
|
+
}
|
|
257
|
+
export async function encodeRuntimeSavSection(input) {
|
|
258
|
+
const payload = canonicalRuntimeSavPayload(input);
|
|
259
|
+
const bytes = new TextEncoder().encode(canonicalTmwJson(payload));
|
|
260
|
+
const digest = await tmwSha256Hex(bytes);
|
|
261
|
+
return Object.freeze({
|
|
262
|
+
extension_id: TMW_RUNTIME_SAV_EXTENSION_ID,
|
|
263
|
+
schema_version: TMW_RUNTIME_SAV_SCHEMA_VERSION,
|
|
264
|
+
payload: bytes,
|
|
265
|
+
codec: TMW_RUNTIME_SAV_CODEC,
|
|
266
|
+
payload_sha256: hexToBytes(digest),
|
|
267
|
+
required: true,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function parsePayload(value) {
|
|
271
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
272
|
+
throw new Error("tmw_sav_payload_invalid");
|
|
273
|
+
}
|
|
274
|
+
return canonicalRuntimeSavPayload(value);
|
|
275
|
+
}
|
|
276
|
+
export async function decodeRuntimeSavSection(section, expected) {
|
|
277
|
+
const payload = await decodeRuntimeSavSectionForScope(section, expected);
|
|
278
|
+
const binding = canonicalBinding(expected);
|
|
279
|
+
for (const [label, actual, wanted] of [
|
|
280
|
+
["timeline", payload.activeTimelineRevision, binding.activeTimelineRevision],
|
|
281
|
+
["turn", payload.turnId, binding.turnId],
|
|
282
|
+
[
|
|
283
|
+
"terminal",
|
|
284
|
+
payload.preActivationTerminalDigest,
|
|
285
|
+
binding.preActivationTerminalDigest,
|
|
286
|
+
],
|
|
287
|
+
]) {
|
|
288
|
+
if (actual !== wanted)
|
|
289
|
+
throw new Error(`tmw_sav_${label}_mismatch`);
|
|
290
|
+
}
|
|
291
|
+
return payload;
|
|
292
|
+
}
|
|
293
|
+
export async function decodeRuntimeSavSectionForScope(section, expected) {
|
|
294
|
+
const payload = await decodeRuntimeSavSectionSelfBound(section);
|
|
295
|
+
if (expected.runtimeGeneration !== 3)
|
|
296
|
+
throw new Error("unsupported_runtime_generation");
|
|
297
|
+
for (const [label, actual, wanted] of [
|
|
298
|
+
["epoch", payload.epoch, expected.epoch],
|
|
299
|
+
["contract", payload.runtimeContractHash, expected.runtimeContractHash],
|
|
300
|
+
["artifact", payload.artifactDigest, expected.artifactDigest],
|
|
301
|
+
["source", payload.sourceRevision, expected.sourceRevision],
|
|
302
|
+
]) {
|
|
303
|
+
if (actual !== wanted) {
|
|
304
|
+
if (label === "epoch")
|
|
305
|
+
throw new Error("stale_content_epoch");
|
|
306
|
+
if (label === "contract")
|
|
307
|
+
throw new Error("runtime_contract_hash_mismatch");
|
|
308
|
+
throw new Error(`tmw_sav_${label}_mismatch`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return payload;
|
|
312
|
+
}
|
|
313
|
+
export async function decodeRuntimeSavSectionSelfBound(section) {
|
|
314
|
+
if (section.extension_id !== TMW_RUNTIME_SAV_EXTENSION_ID)
|
|
315
|
+
throw new Error("sav_section_missing");
|
|
316
|
+
if (section.schema_version !== TMW_RUNTIME_SAV_SCHEMA_VERSION) {
|
|
317
|
+
throw new Error("unsupported_sav_generation");
|
|
318
|
+
}
|
|
319
|
+
if (section.codec !== TMW_RUNTIME_SAV_CODEC || section.required !== true) {
|
|
320
|
+
throw new Error("tmw_sav_section_contract_invalid");
|
|
321
|
+
}
|
|
322
|
+
const actualDigest = await tmwSha256Hex(section.payload);
|
|
323
|
+
if (bytesToHex(section.payload_sha256) !== actualDigest)
|
|
324
|
+
throw new Error("tmw_sav_payload_hash_mismatch");
|
|
325
|
+
let parsed;
|
|
326
|
+
try {
|
|
327
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(section.payload));
|
|
328
|
+
}
|
|
329
|
+
catch (cause) {
|
|
330
|
+
throw new Error("tmw_sav_payload_decode_failed", { cause });
|
|
331
|
+
}
|
|
332
|
+
const payload = parsePayload(parsed);
|
|
333
|
+
return payload;
|
|
334
|
+
}
|
|
335
|
+
export async function requireRuntimeSavSection(sections, expected) {
|
|
336
|
+
const matches = sections.filter((section) => section.extension_id === TMW_RUNTIME_SAV_EXTENSION_ID);
|
|
337
|
+
if (matches.length === 0)
|
|
338
|
+
throw new Error("sav_section_missing");
|
|
339
|
+
if (matches.length !== 1)
|
|
340
|
+
throw new Error("tmw_sav_section_duplicate");
|
|
341
|
+
const section = matches[0];
|
|
342
|
+
if (section === undefined)
|
|
343
|
+
throw new Error("sav_section_missing");
|
|
344
|
+
return await decodeRuntimeSavSection(section, expected);
|
|
345
|
+
}
|
|
346
|
+
export async function requireRuntimeSavSectionForScope(sections, expected) {
|
|
347
|
+
const matches = sections.filter((section) => section.extension_id === TMW_RUNTIME_SAV_EXTENSION_ID);
|
|
348
|
+
if (matches.length === 0)
|
|
349
|
+
throw new Error("sav_section_missing");
|
|
350
|
+
if (matches.length !== 1)
|
|
351
|
+
throw new Error("tmw_sav_section_duplicate");
|
|
352
|
+
const section = matches[0];
|
|
353
|
+
if (section === undefined)
|
|
354
|
+
throw new Error("sav_section_missing");
|
|
355
|
+
return await decodeRuntimeSavSectionForScope(section, expected);
|
|
356
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type TmwJsonPrimitive = null | boolean | number | string;
|
|
2
|
+
export type TmwJsonValue = TmwJsonPrimitive | ReadonlyArray<TmwJsonValue> | Readonly<{
|
|
3
|
+
[key: string]: TmwJsonValue;
|
|
4
|
+
}>;
|
|
5
|
+
export interface RuntimeTraceEntry {
|
|
6
|
+
readonly sequence: number;
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
readonly stableId: string;
|
|
9
|
+
readonly detail: Readonly<Record<string, TmwJsonValue>>;
|
|
10
|
+
}
|
|
11
|
+
export interface RuntimeTraceSnapshot {
|
|
12
|
+
readonly maxEntries: number;
|
|
13
|
+
readonly nextSequence: number;
|
|
14
|
+
readonly truncatedEntries: number;
|
|
15
|
+
readonly entries: ReadonlyArray<RuntimeTraceEntry>;
|
|
16
|
+
}
|
|
17
|
+
/** Content-free clock audit fields; payloads and author source never enter trace. */
|
|
18
|
+
export interface RuntimeClockTraceDetail extends Readonly<Record<string, TmwJsonValue>> {
|
|
19
|
+
readonly clockDomain: "logical_seq" | "world_time" | "presentation_seq" | "real_time";
|
|
20
|
+
readonly observedValue: number | null;
|
|
21
|
+
readonly dueValue: number;
|
|
22
|
+
readonly previousObservedValue: number | null;
|
|
23
|
+
readonly crossingDirection: "forward" | "backward";
|
|
24
|
+
}
|
|
25
|
+
export declare class TmwRuntimeInvariantError extends Error {
|
|
26
|
+
readonly code: string;
|
|
27
|
+
constructor(code: string);
|
|
28
|
+
}
|
|
29
|
+
export declare function requireTmwIdentifier(value: string, label: string): string;
|
|
30
|
+
export declare function requireTmwSha256(value: string, label: string): string;
|
|
31
|
+
export declare function requireTmwSafeInteger(value: number, label: string, minimum?: number): number;
|
|
32
|
+
export declare function canonicalTmwJsonValue(value: unknown, label?: string): TmwJsonValue;
|
|
33
|
+
export declare function canonicalTmwJson(value: unknown): string;
|
|
34
|
+
export declare function tmwSha256Hex(value: unknown): Promise<string>;
|
|
35
|
+
export declare class BoundedRuntimeTrace {
|
|
36
|
+
#private;
|
|
37
|
+
constructor(maxEntries?: number);
|
|
38
|
+
append(input: Readonly<{
|
|
39
|
+
kind: string;
|
|
40
|
+
stableId: string;
|
|
41
|
+
detail?: Readonly<Record<string, unknown>>;
|
|
42
|
+
}>): RuntimeTraceEntry;
|
|
43
|
+
snapshot(): RuntimeTraceSnapshot;
|
|
44
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const IDENTIFIER = /^[^\0\r\n]{1,512}$/;
|
|
2
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
3
|
+
const DEFAULT_MAX_TRACE_ENTRIES = 256;
|
|
4
|
+
const MAX_TRACE_ENTRIES = 4096;
|
|
5
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
6
|
+
export class TmwRuntimeInvariantError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code) {
|
|
9
|
+
super(`tmw_runtime_${code}`);
|
|
10
|
+
this.name = "TmwRuntimeInvariantError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function requireTmwIdentifier(value, label) {
|
|
15
|
+
if (typeof value !== "string" || !IDENTIFIER.test(value)) {
|
|
16
|
+
throw new TmwRuntimeInvariantError(`${label}_invalid`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
export function requireTmwSha256(value, label) {
|
|
21
|
+
if (typeof value !== "string" || !SHA256_HEX.test(value)) {
|
|
22
|
+
throw new TmwRuntimeInvariantError(`${label}_invalid`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
export function requireTmwSafeInteger(value, label, minimum = 0) {
|
|
27
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
28
|
+
throw new TmwRuntimeInvariantError(`${label}_invalid`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function canonicalizeJson(value, path, ancestors) {
|
|
33
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
34
|
+
return value;
|
|
35
|
+
if (typeof value === "number") {
|
|
36
|
+
if (!Number.isFinite(value))
|
|
37
|
+
throw new TmwRuntimeInvariantError(`${path}_non_finite`);
|
|
38
|
+
return Object.is(value, -0) ? 0 : value;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value !== "object")
|
|
41
|
+
throw new TmwRuntimeInvariantError(`${path}_non_json`);
|
|
42
|
+
if (ancestors.has(value))
|
|
43
|
+
throw new TmwRuntimeInvariantError(`${path}_cycle`);
|
|
44
|
+
const nextAncestors = new Set(ancestors);
|
|
45
|
+
nextAncestors.add(value);
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return Object.freeze(value.map((item, index) => canonicalizeJson(item, `${path}_${index}`, nextAncestors)));
|
|
48
|
+
}
|
|
49
|
+
const prototype = Object.getPrototypeOf(value);
|
|
50
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
51
|
+
throw new TmwRuntimeInvariantError(`${path}_prototype_invalid`);
|
|
52
|
+
}
|
|
53
|
+
const source = value;
|
|
54
|
+
const result = Object.create(null);
|
|
55
|
+
for (const key of Object.keys(source).sort()) {
|
|
56
|
+
requireTmwIdentifier(key, `${path}_key`);
|
|
57
|
+
if (source[key] === undefined)
|
|
58
|
+
throw new TmwRuntimeInvariantError(`${path}_${key}_undefined`);
|
|
59
|
+
result[key] = canonicalizeJson(source[key], `${path}_${key}`, nextAncestors);
|
|
60
|
+
}
|
|
61
|
+
return Object.freeze(result);
|
|
62
|
+
}
|
|
63
|
+
export function canonicalTmwJsonValue(value, label = "json") {
|
|
64
|
+
return canonicalizeJson(value, label, new Set());
|
|
65
|
+
}
|
|
66
|
+
export function canonicalTmwJson(value) {
|
|
67
|
+
return JSON.stringify(canonicalTmwJsonValue(value));
|
|
68
|
+
}
|
|
69
|
+
export async function tmwSha256Hex(value) {
|
|
70
|
+
const bytes = typeof value === "string"
|
|
71
|
+
? TEXT_ENCODER.encode(value)
|
|
72
|
+
: value instanceof Uint8Array
|
|
73
|
+
? value
|
|
74
|
+
: TEXT_ENCODER.encode(canonicalTmwJson(value));
|
|
75
|
+
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
|
|
76
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
77
|
+
}
|
|
78
|
+
export class BoundedRuntimeTrace {
|
|
79
|
+
#maxEntries;
|
|
80
|
+
#entries = [];
|
|
81
|
+
#nextSequence = 0;
|
|
82
|
+
#truncatedEntries = 0;
|
|
83
|
+
constructor(maxEntries = DEFAULT_MAX_TRACE_ENTRIES) {
|
|
84
|
+
this.#maxEntries = requireTmwSafeInteger(maxEntries, "trace_max_entries", 1);
|
|
85
|
+
if (this.#maxEntries > MAX_TRACE_ENTRIES) {
|
|
86
|
+
throw new TmwRuntimeInvariantError("trace_max_entries_exceeded");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
append(input) {
|
|
90
|
+
if (this.#nextSequence === Number.MAX_SAFE_INTEGER) {
|
|
91
|
+
throw new TmwRuntimeInvariantError("trace_sequence_exhausted");
|
|
92
|
+
}
|
|
93
|
+
const detail = canonicalTmwJsonValue(input.detail ?? {}, "trace_detail");
|
|
94
|
+
if (detail === null || Array.isArray(detail) || typeof detail !== "object") {
|
|
95
|
+
throw new TmwRuntimeInvariantError("trace_detail_invalid");
|
|
96
|
+
}
|
|
97
|
+
const entry = Object.freeze({
|
|
98
|
+
sequence: this.#nextSequence,
|
|
99
|
+
kind: requireTmwIdentifier(input.kind, "trace_kind"),
|
|
100
|
+
stableId: requireTmwIdentifier(input.stableId, "trace_stable_id"),
|
|
101
|
+
detail: detail,
|
|
102
|
+
});
|
|
103
|
+
this.#nextSequence += 1;
|
|
104
|
+
if (this.#entries.length === this.#maxEntries) {
|
|
105
|
+
this.#entries.shift();
|
|
106
|
+
this.#truncatedEntries += 1;
|
|
107
|
+
}
|
|
108
|
+
this.#entries.push(entry);
|
|
109
|
+
return entry;
|
|
110
|
+
}
|
|
111
|
+
snapshot() {
|
|
112
|
+
return Object.freeze({
|
|
113
|
+
maxEntries: this.#maxEntries,
|
|
114
|
+
nextSequence: this.#nextSequence,
|
|
115
|
+
truncatedEntries: this.#truncatedEntries,
|
|
116
|
+
entries: Object.freeze([...this.#entries]),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|