@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.
Files changed (120) hide show
  1. package/LICENSE +46 -0
  2. package/dist/activation.d.ts +50 -0
  3. package/dist/activation.js +113 -0
  4. package/dist/audio-presentation-projection.d.ts +23 -0
  5. package/dist/audio-presentation-projection.js +39 -0
  6. package/dist/author-scenario-source-v3.d.ts +34 -0
  7. package/dist/author-scenario-source-v3.js +1 -0
  8. package/dist/authoring-runtime.d.ts +76 -0
  9. package/dist/authoring-runtime.js +282 -0
  10. package/dist/builder-diagnostics.d.ts +3 -0
  11. package/dist/builder-diagnostics.js +8 -0
  12. package/dist/builder-node.d.ts +178 -0
  13. package/dist/builder-node.js +212 -0
  14. package/dist/builder.d.ts +32 -0
  15. package/dist/builder.js +127 -0
  16. package/dist/capability-registry.d.ts +32 -0
  17. package/dist/capability-registry.js +33 -0
  18. package/dist/compile-target-profile.d.ts +16 -0
  19. package/dist/compile-target-profile.js +70 -0
  20. package/dist/compiled-plan.d.ts +44 -0
  21. package/dist/compiled-plan.js +1 -0
  22. package/dist/compiler-compatibility-gate.d.ts +96 -0
  23. package/dist/compiler-compatibility-gate.js +609 -0
  24. package/dist/compiler-release-authority.d.ts +212 -0
  25. package/dist/compiler-release-authority.js +676 -0
  26. package/dist/compiler-stage-cache.d.ts +42 -0
  27. package/dist/compiler-stage-cache.js +100 -0
  28. package/dist/compiler-stage-registry.d.ts +48 -0
  29. package/dist/compiler-stage-registry.js +126 -0
  30. package/dist/compiler.d.ts +7 -0
  31. package/dist/compiler.js +68 -0
  32. package/dist/condition-graph.d.ts +46 -0
  33. package/dist/condition-graph.js +135 -0
  34. package/dist/diagnostics.d.ts +20 -0
  35. package/dist/diagnostics.js +20 -0
  36. package/dist/durable-intent.d.ts +29 -0
  37. package/dist/durable-intent.js +60 -0
  38. package/dist/effect-flow.d.ts +69 -0
  39. package/dist/effect-flow.js +98 -0
  40. package/dist/event-fabric.d.ts +81 -0
  41. package/dist/event-fabric.js +227 -0
  42. package/dist/event-lifecycle.d.ts +59 -0
  43. package/dist/event-lifecycle.js +161 -0
  44. package/dist/evidence.d.ts +37 -0
  45. package/dist/evidence.js +92 -0
  46. package/dist/gameplay-state.d.ts +127 -0
  47. package/dist/gameplay-state.js +214 -0
  48. package/dist/index.d.ts +66 -0
  49. package/dist/index.js +52 -0
  50. package/dist/isolated-replay.d.ts +81 -0
  51. package/dist/isolated-replay.js +316 -0
  52. package/dist/music-candidate-set.d.ts +38 -0
  53. package/dist/music-candidate-set.js +102 -0
  54. package/dist/natural-language-chunking.d.ts +10 -0
  55. package/dist/natural-language-chunking.js +62 -0
  56. package/dist/natural-language-ingestion.d.ts +14 -0
  57. package/dist/natural-language-ingestion.js +128 -0
  58. package/dist/natural-language-structured-decode.d.ts +2 -0
  59. package/dist/natural-language-structured-decode.js +69 -0
  60. package/dist/natural-language-world-types.d.ts +142 -0
  61. package/dist/natural-language-world-types.js +2 -0
  62. package/dist/neutral-source-compiler.d.ts +135 -0
  63. package/dist/neutral-source-compiler.js +608 -0
  64. package/dist/presentation-intent.d.ts +42 -0
  65. package/dist/presentation-intent.js +137 -0
  66. package/dist/recall-query-envelope.d.ts +32 -0
  67. package/dist/recall-query-envelope.js +51 -0
  68. package/dist/rewrite-compensation.d.ts +28 -0
  69. package/dist/rewrite-compensation.js +85 -0
  70. package/dist/runtime-contract.d.ts +56 -0
  71. package/dist/runtime-contract.js +96 -0
  72. package/dist/runtime-domain-capabilities.d.ts +12 -0
  73. package/dist/runtime-domain-capabilities.js +20 -0
  74. package/dist/runtime-sav.d.ts +117 -0
  75. package/dist/runtime-sav.js +356 -0
  76. package/dist/runtime-trace.d.ts +44 -0
  77. package/dist/runtime-trace.js +119 -0
  78. package/dist/runtime.d.ts +238 -0
  79. package/dist/runtime.js +1887 -0
  80. package/dist/scene-author-source.d.ts +13 -0
  81. package/dist/scene-author-source.js +25 -0
  82. package/dist/scene-state.d.ts +275 -0
  83. package/dist/scene-state.js +334 -0
  84. package/dist/scheduler.d.ts +55 -0
  85. package/dist/scheduler.js +235 -0
  86. package/dist/selector-ast.d.ts +24 -0
  87. package/dist/selector-ast.js +18 -0
  88. package/dist/semantic-verdict.d.ts +48 -0
  89. package/dist/semantic-verdict.js +101 -0
  90. package/dist/simulator.d.ts +45 -0
  91. package/dist/simulator.js +58 -0
  92. package/dist/source-canonical.d.ts +4 -0
  93. package/dist/source-canonical.js +51 -0
  94. package/dist/source-crypto.d.ts +20 -0
  95. package/dist/source-crypto.js +17 -0
  96. package/dist/source-revision.d.ts +22 -0
  97. package/dist/source-revision.js +43 -0
  98. package/dist/spatial-impact-receipt.d.ts +38 -0
  99. package/dist/spatial-impact-receipt.js +67 -0
  100. package/dist/temporal-rewrite.d.ts +59 -0
  101. package/dist/temporal-rewrite.js +214 -0
  102. package/dist/time-domains.d.ts +37 -0
  103. package/dist/time-domains.js +142 -0
  104. package/dist/timeline-revision.d.ts +28 -0
  105. package/dist/timeline-revision.js +59 -0
  106. package/dist/trigger-document-v3.d.ts +99 -0
  107. package/dist/trigger-document-v3.js +334 -0
  108. package/dist/turn-commit.d.ts +56 -0
  109. package/dist/turn-commit.js +190 -0
  110. package/dist/validator.d.ts +7 -0
  111. package/dist/validator.js +142 -0
  112. package/dist/value-graph.d.ts +98 -0
  113. package/dist/value-graph.js +318 -0
  114. package/dist/view-event-delivery.d.ts +47 -0
  115. package/dist/view-event-delivery.js +197 -0
  116. package/dist/view-interaction-manifest-v3.d.ts +60 -0
  117. package/dist/view-interaction-manifest-v3.js +198 -0
  118. package/dist/view-payload-schema.d.ts +17 -0
  119. package/dist/view-payload-schema.js +84 -0
  120. package/package.json +62 -0
@@ -0,0 +1,20 @@
1
+ export interface AuthorSourceCryptoPort {
2
+ encrypt(plaintext: Uint8Array): Promise<Readonly<{
3
+ algorithm: string;
4
+ key_id: string;
5
+ ciphertext: Uint8Array;
6
+ }>>;
7
+ decrypt(input: Readonly<{
8
+ algorithm: string;
9
+ key_id: string;
10
+ ciphertext: Uint8Array;
11
+ }>): Promise<Uint8Array>;
12
+ }
13
+ export interface EncryptedAuthorSourcePayloadV3 {
14
+ readonly algorithm: string;
15
+ readonly key_id: string;
16
+ readonly ciphertext: Uint8Array;
17
+ readonly ciphertext_sha256: string;
18
+ }
19
+ export declare function encryptAuthorSourcePayloadV3(plaintext: Uint8Array, cryptoPort: AuthorSourceCryptoPort): Promise<EncryptedAuthorSourcePayloadV3>;
20
+ export declare function decryptAuthorSourcePayloadV3(payload: EncryptedAuthorSourcePayloadV3, cryptoPort: AuthorSourceCryptoPort): Promise<Uint8Array>;
@@ -0,0 +1,17 @@
1
+ import { tmwSha256Hex } from "./runtime-contract.js";
2
+ export async function encryptAuthorSourcePayloadV3(plaintext, cryptoPort) {
3
+ const encrypted = await cryptoPort.encrypt(plaintext.slice());
4
+ if (encrypted.algorithm.length === 0 || encrypted.key_id.length === 0 || encrypted.ciphertext.length === 0)
5
+ throw new Error("source_encryption_invalid");
6
+ return Object.freeze({
7
+ algorithm: encrypted.algorithm,
8
+ key_id: encrypted.key_id,
9
+ ciphertext: encrypted.ciphertext.slice(),
10
+ ciphertext_sha256: await tmwSha256Hex(encrypted.ciphertext),
11
+ });
12
+ }
13
+ export async function decryptAuthorSourcePayloadV3(payload, cryptoPort) {
14
+ if (await tmwSha256Hex(payload.ciphertext) !== payload.ciphertext_sha256)
15
+ throw new Error("source_ciphertext_hash_mismatch");
16
+ return cryptoPort.decrypt({ algorithm: payload.algorithm, key_id: payload.key_id, ciphertext: payload.ciphertext.slice() });
17
+ }
@@ -0,0 +1,22 @@
1
+ import type { AuthorScenarioSourceV3, AuthorSourceIdentityV3 } from "./author-scenario-source-v3.js";
2
+ import { type AuthorSourceCryptoPort, type EncryptedAuthorSourcePayloadV3 } from "./source-crypto.js";
3
+ export interface EncryptedAuthorSourceRevisionV3 extends AuthorSourceIdentityV3, EncryptedAuthorSourcePayloadV3 {
4
+ readonly revision_schema_version: 3;
5
+ readonly previous_head_digest: string | null;
6
+ readonly revision_digest: string;
7
+ }
8
+ export interface AuthorSourceReadbackReceiptV3 extends AuthorSourceIdentityV3 {
9
+ readonly revision_digest: string;
10
+ readonly ciphertext_sha256: string;
11
+ readonly head_digest: string;
12
+ }
13
+ export declare function buildEncryptedAuthorSourceRevisionV3(source: AuthorScenarioSourceV3, previousHeadDigest: string | null, cryptoPort: AuthorSourceCryptoPort): Promise<EncryptedAuthorSourceRevisionV3>;
14
+ export declare function readbackAuthorSourceRevisionV3(revision: EncryptedAuthorSourceRevisionV3, expected: Readonly<{
15
+ owner_id: string;
16
+ scenario_id: string;
17
+ epoch: number;
18
+ head_digest: string;
19
+ }>, cryptoPort: AuthorSourceCryptoPort): Promise<Readonly<{
20
+ source: AuthorScenarioSourceV3;
21
+ receipt: AuthorSourceReadbackReceiptV3;
22
+ }>>;
@@ -0,0 +1,43 @@
1
+ import { authorScenarioSourceV3Digest, canonicalAuthorScenarioSourceV3Bytes } from "./source-canonical.js";
2
+ import { decryptAuthorSourcePayloadV3, encryptAuthorSourcePayloadV3 } from "./source-crypto.js";
3
+ export async function buildEncryptedAuthorSourceRevisionV3(source, previousHeadDigest, cryptoPort) {
4
+ const sourceSha256 = await authorScenarioSourceV3Digest(source);
5
+ const payload = await encryptAuthorSourcePayloadV3(canonicalAuthorScenarioSourceV3Bytes(source), cryptoPort);
6
+ const revisionDigest = await authorScenarioSourceV3Digest(Object.freeze({ ...source, previous_revision_digest: previousHeadDigest }));
7
+ return Object.freeze({
8
+ revision_schema_version: 3,
9
+ owner_id: source.owner_id,
10
+ scenario_id: source.scenario_id,
11
+ epoch: source.epoch,
12
+ source_revision: source.source_revision,
13
+ source_sha256: sourceSha256,
14
+ previous_head_digest: previousHeadDigest,
15
+ revision_digest: revisionDigest,
16
+ ...payload,
17
+ });
18
+ }
19
+ export async function readbackAuthorSourceRevisionV3(revision, expected, cryptoPort) {
20
+ if (revision.owner_id !== expected.owner_id || revision.scenario_id !== expected.scenario_id)
21
+ throw new Error("source_identity_mismatch");
22
+ if (revision.epoch !== expected.epoch)
23
+ throw new Error("stale_content_epoch");
24
+ const plaintext = await decryptAuthorSourcePayloadV3(revision, cryptoPort);
25
+ const source = JSON.parse(new TextDecoder().decode(plaintext));
26
+ if (await authorScenarioSourceV3Digest(source) !== revision.source_sha256)
27
+ throw new Error("source_hash_mismatch");
28
+ if (source.owner_id !== revision.owner_id || source.scenario_id !== revision.scenario_id || source.epoch !== revision.epoch || source.source_revision !== revision.source_revision)
29
+ throw new Error("source_revision_identity_mismatch");
30
+ return Object.freeze({
31
+ source,
32
+ receipt: Object.freeze({
33
+ owner_id: revision.owner_id,
34
+ scenario_id: revision.scenario_id,
35
+ epoch: revision.epoch,
36
+ source_revision: revision.source_revision,
37
+ source_sha256: revision.source_sha256,
38
+ revision_digest: revision.revision_digest,
39
+ ciphertext_sha256: revision.ciphertext_sha256,
40
+ head_digest: expected.head_digest,
41
+ }),
42
+ });
43
+ }
@@ -0,0 +1,38 @@
1
+ export declare const SPATIAL_IMPACT_RECEIPT_VERSION: 1;
2
+ export type SpatialImpactMetricV1 = "straight_line" | "reachable";
3
+ export interface SpatialImpactPoseV1 {
4
+ readonly subjectId: string;
5
+ readonly spatialId: string;
6
+ readonly frameId: string;
7
+ readonly xMeters: number;
8
+ readonly yMeters: number;
9
+ readonly elevationMeters: number;
10
+ readonly revision: number;
11
+ }
12
+ export interface SpatialImpactReceiptV1 {
13
+ readonly version: typeof SPATIAL_IMPACT_RECEIPT_VERSION;
14
+ readonly eventId: string;
15
+ readonly mapRevision: string;
16
+ readonly selectorId: string;
17
+ readonly metric: SpatialImpactMetricV1;
18
+ readonly distanceMeters: number | null;
19
+ readonly poses: ReadonlyArray<SpatialImpactPoseV1>;
20
+ readonly affectedIds: ReadonlyArray<string>;
21
+ readonly digest: string;
22
+ }
23
+ export interface FreezeSpatialImpactInputV1 {
24
+ readonly eventId: string;
25
+ readonly mapRevision: string;
26
+ readonly selectorId: string;
27
+ readonly metric: SpatialImpactMetricV1;
28
+ readonly distanceMeters?: number;
29
+ readonly poses: ReadonlyArray<SpatialImpactPoseV1>;
30
+ readonly affectedIds: ReadonlyArray<string>;
31
+ }
32
+ /**
33
+ * Freezes spatial inputs before an effect executes. Replay accepts this receipt
34
+ * as the authority and therefore never re-queries live poses or map state.
35
+ */
36
+ export declare function freezeSpatialImpactReceiptV1(input: FreezeSpatialImpactInputV1): Promise<SpatialImpactReceiptV1>;
37
+ /** Fails closed when journal/debug/player readback data was modified in transit. */
38
+ export declare function verifySpatialImpactReceiptV1(receipt: SpatialImpactReceiptV1): Promise<boolean>;
@@ -0,0 +1,67 @@
1
+ import { canonicalTmwJson, requireTmwIdentifier, requireTmwSafeInteger, requireTmwSha256, tmwSha256Hex, } from "./runtime-trace.js";
2
+ export const SPATIAL_IMPACT_RECEIPT_VERSION = 1;
3
+ function finite(value, label, minimum = Number.NEGATIVE_INFINITY) {
4
+ if (!Number.isFinite(value) || value < minimum) {
5
+ throw new Error(`spatial_impact_${label}_invalid`);
6
+ }
7
+ return Object.is(value, -0) ? 0 : value;
8
+ }
9
+ function canonicalPose(pose) {
10
+ return Object.freeze({
11
+ subjectId: requireTmwIdentifier(pose.subjectId, "spatial_impact_pose_subject"),
12
+ spatialId: requireTmwIdentifier(pose.spatialId, "spatial_impact_pose_spatial"),
13
+ frameId: requireTmwIdentifier(pose.frameId, "spatial_impact_pose_frame"),
14
+ xMeters: finite(pose.xMeters, "pose_x"),
15
+ yMeters: finite(pose.yMeters, "pose_y"),
16
+ elevationMeters: finite(pose.elevationMeters, "pose_elevation"),
17
+ revision: requireTmwSafeInteger(pose.revision, "spatial_impact_pose_revision"),
18
+ });
19
+ }
20
+ function canonicalPoses(poses) {
21
+ const bySubject = new Map();
22
+ for (const pose of poses) {
23
+ const next = canonicalPose(pose);
24
+ const existing = bySubject.get(next.subjectId);
25
+ if (existing !== undefined && canonicalTmwJson(existing) !== canonicalTmwJson(next)) {
26
+ throw new Error("spatial_impact_pose_subject_conflict");
27
+ }
28
+ bySubject.set(next.subjectId, next);
29
+ }
30
+ return Object.freeze([...bySubject.values()].sort((left, right) => left.subjectId.localeCompare(right.subjectId)));
31
+ }
32
+ function canonicalAffectedIds(ids) {
33
+ return Object.freeze([...new Set(ids.map((id) => requireTmwIdentifier(id, "spatial_impact_affected_id")))].sort());
34
+ }
35
+ /**
36
+ * Freezes spatial inputs before an effect executes. Replay accepts this receipt
37
+ * as the authority and therefore never re-queries live poses or map state.
38
+ */
39
+ export async function freezeSpatialImpactReceiptV1(input) {
40
+ const distanceMeters = input.distanceMeters === undefined
41
+ ? null
42
+ : finite(input.distanceMeters, "distance_meters", 0);
43
+ const unsigned = Object.freeze({
44
+ version: SPATIAL_IMPACT_RECEIPT_VERSION,
45
+ eventId: requireTmwIdentifier(input.eventId, "spatial_impact_event"),
46
+ mapRevision: requireTmwIdentifier(input.mapRevision, "spatial_impact_map_revision"),
47
+ selectorId: requireTmwIdentifier(input.selectorId, "spatial_impact_selector"),
48
+ metric: input.metric,
49
+ distanceMeters,
50
+ poses: canonicalPoses(input.poses),
51
+ affectedIds: canonicalAffectedIds(input.affectedIds),
52
+ });
53
+ if (unsigned.metric !== "straight_line" && unsigned.metric !== "reachable") {
54
+ throw new Error("spatial_impact_metric_invalid");
55
+ }
56
+ const digest = await tmwSha256Hex(canonicalTmwJson(unsigned));
57
+ return Object.freeze({ ...unsigned, digest });
58
+ }
59
+ /** Fails closed when journal/debug/player readback data was modified in transit. */
60
+ export async function verifySpatialImpactReceiptV1(receipt) {
61
+ const { digest: _digest, distanceMeters, ...frozen } = receipt;
62
+ const rebuilt = await freezeSpatialImpactReceiptV1({
63
+ ...frozen,
64
+ ...(distanceMeters === null ? {} : { distanceMeters }),
65
+ });
66
+ return requireTmwSha256(receipt.digest, "spatial_impact_digest") === rebuilt.digest;
67
+ }
@@ -0,0 +1,59 @@
1
+ import { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
2
+ import { type TmwJsonValue } from "./runtime-trace.js";
3
+ export interface TemporalRewriteInterval {
4
+ readonly fromWorldTime: number;
5
+ readonly toWorldTime: number;
6
+ readonly targetWorldTime: number;
7
+ }
8
+ export interface TemporalRewriteEventV3 {
9
+ readonly eventId: string;
10
+ readonly eventType: string;
11
+ readonly effectiveWorldTime: number;
12
+ readonly capabilityIds: ReadonlyArray<string>;
13
+ readonly payload: TmwJsonValue;
14
+ readonly payloadHash: string;
15
+ readonly replacementOf: ReadonlyArray<string>;
16
+ }
17
+ export interface TemporalRewriteCompensationProposalV3 {
18
+ readonly externalEffectId: string;
19
+ readonly policy: "retain_fact" | "project_only" | "requires_confirmation";
20
+ readonly reasonCode: string;
21
+ }
22
+ export interface TemporalRewriteDraftV3 {
23
+ readonly schemaVersion: 3;
24
+ readonly epoch: number;
25
+ readonly runtimeGeneration: typeof TMW_RUNTIME_GENERATION;
26
+ readonly runtimeContractHash: string;
27
+ readonly artifactDigest: string;
28
+ readonly scenarioId: string;
29
+ readonly sourceRevision: string;
30
+ readonly timelineRevision: string;
31
+ readonly turnId: string;
32
+ readonly operationId: string;
33
+ readonly idempotencyKey: string;
34
+ readonly interval: TemporalRewriteInterval;
35
+ readonly narrative: TmwJsonValue;
36
+ readonly narrativeDigest: string;
37
+ readonly events: ReadonlyArray<TemporalRewriteEventV3>;
38
+ readonly replacedEventIds: ReadonlyArray<string>;
39
+ readonly conflictCodes: ReadonlyArray<string>;
40
+ readonly evidenceRefs: ReadonlyArray<string>;
41
+ readonly compensationProposals: ReadonlyArray<TemporalRewriteCompensationProposalV3>;
42
+ readonly draftDigest: string;
43
+ }
44
+ export declare function canonicalTemporalRewriteEvent(value: unknown): TemporalRewriteEventV3;
45
+ export declare function verifyTemporalRewriteEvent(value: unknown): Promise<TemporalRewriteEventV3>;
46
+ export declare function parseTemporalRewriteDraft(value: unknown): TemporalRewriteDraftV3;
47
+ export declare function verifyTemporalRewriteDraft(value: unknown): Promise<TemporalRewriteDraftV3>;
48
+ export declare function assertTemporalRewriteBinding(draftInput: unknown, expected: Readonly<{
49
+ epoch: number;
50
+ runtimeContractHash: string;
51
+ artifactDigest: string;
52
+ scenarioId: string;
53
+ sourceRevision: string;
54
+ timelineRevision: string;
55
+ turnId: string;
56
+ operationId: string;
57
+ idempotencyKey: string;
58
+ interval: TemporalRewriteInterval;
59
+ }>): Promise<TemporalRewriteDraftV3>;
@@ -0,0 +1,214 @@
1
+ import { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
2
+ import { canonicalTmwJson, canonicalTmwJsonValue, requireTmwIdentifier, requireTmwSafeInteger, requireTmwSha256, tmwSha256Hex, } from "./runtime-trace.js";
3
+ const DRAFT_KEYS = [
4
+ "artifactDigest",
5
+ "compensationProposals",
6
+ "conflictCodes",
7
+ "draftDigest",
8
+ "epoch",
9
+ "events",
10
+ "evidenceRefs",
11
+ "idempotencyKey",
12
+ "interval",
13
+ "narrative",
14
+ "narrativeDigest",
15
+ "operationId",
16
+ "replacedEventIds",
17
+ "runtimeContractHash",
18
+ "runtimeGeneration",
19
+ "scenarioId",
20
+ "schemaVersion",
21
+ "sourceRevision",
22
+ "timelineRevision",
23
+ "turnId",
24
+ ];
25
+ function worldSecond(value, label) {
26
+ return requireTmwSafeInteger(value, `rewrite_${label}`);
27
+ }
28
+ function exactRecord(value, keys, label) {
29
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
30
+ throw new Error(`tmw_rewrite_${label}_object_required`);
31
+ }
32
+ const record = value;
33
+ if (Object.keys(record).sort().join("\0") !== [...keys].sort().join("\0")) {
34
+ throw new Error(`tmw_rewrite_${label}_exact_keys_invalid`);
35
+ }
36
+ return record;
37
+ }
38
+ function identifierArray(value, label) {
39
+ if (!Array.isArray(value))
40
+ throw new Error(`tmw_rewrite_${label}_array_required`);
41
+ const result = value.map((item) => requireTmwIdentifier(item, `rewrite_${label}`));
42
+ if (new Set(result).size !== result.length)
43
+ throw new Error(`tmw_rewrite_${label}_duplicate`);
44
+ return Object.freeze([...result].sort());
45
+ }
46
+ export function canonicalTemporalRewriteEvent(value) {
47
+ const record = exactRecord(value, [
48
+ "capabilityIds",
49
+ "effectiveWorldTime",
50
+ "eventId",
51
+ "eventType",
52
+ "payload",
53
+ "payloadHash",
54
+ "replacementOf",
55
+ ], "event");
56
+ return Object.freeze({
57
+ eventId: requireTmwIdentifier(record.eventId, "rewrite_event_id"),
58
+ eventType: requireTmwIdentifier(record.eventType, "rewrite_event_type"),
59
+ effectiveWorldTime: worldSecond(record.effectiveWorldTime, "event_world_time"),
60
+ capabilityIds: identifierArray(record.capabilityIds, "capability_ids"),
61
+ payload: canonicalTmwJsonValue(record.payload, "rewrite_event_payload"),
62
+ payloadHash: requireTmwSha256(record.payloadHash, "rewrite_event_payload_hash"),
63
+ replacementOf: identifierArray(record.replacementOf, "replacement_of"),
64
+ });
65
+ }
66
+ export async function verifyTemporalRewriteEvent(value) {
67
+ const event = canonicalTemporalRewriteEvent(value);
68
+ const payloadHash = await tmwSha256Hex(canonicalTmwJson(event.payload));
69
+ if (payloadHash !== event.payloadHash) {
70
+ throw new Error("tmw_rewrite_event_payload_hash_mismatch");
71
+ }
72
+ return event;
73
+ }
74
+ function parseCompensation(value) {
75
+ const record = exactRecord(value, ["externalEffectId", "policy", "reasonCode"], "compensation");
76
+ if (record.policy !== "retain_fact"
77
+ && record.policy !== "project_only"
78
+ && record.policy !== "requires_confirmation") {
79
+ throw new Error("tmw_rewrite_compensation_policy_invalid");
80
+ }
81
+ return Object.freeze({
82
+ externalEffectId: requireTmwIdentifier(record.externalEffectId, "rewrite_external_effect_id"),
83
+ policy: record.policy,
84
+ reasonCode: requireTmwIdentifier(record.reasonCode, "rewrite_reason_code"),
85
+ });
86
+ }
87
+ export function parseTemporalRewriteDraft(value) {
88
+ const record = exactRecord(value, DRAFT_KEYS, "draft");
89
+ if (record.schemaVersion !== 3 || record.runtimeGeneration !== 3) {
90
+ throw new Error("unsupported_runtime_generation");
91
+ }
92
+ const interval = exactRecord(record.interval, ["fromWorldTime", "targetWorldTime", "toWorldTime"], "interval");
93
+ const canonicalInterval = Object.freeze({
94
+ fromWorldTime: worldSecond(interval.fromWorldTime, "from_world_time"),
95
+ toWorldTime: worldSecond(interval.toWorldTime, "to_world_time"),
96
+ targetWorldTime: worldSecond(interval.targetWorldTime, "target_world_time"),
97
+ });
98
+ if (canonicalInterval.fromWorldTime > canonicalInterval.targetWorldTime
99
+ || canonicalInterval.targetWorldTime > canonicalInterval.toWorldTime) {
100
+ throw new Error("tmw_rewrite_interval_invalid");
101
+ }
102
+ if (!Array.isArray(record.events) || !Array.isArray(record.compensationProposals)) {
103
+ throw new Error("tmw_rewrite_collections_invalid");
104
+ }
105
+ const events = record.events.map(canonicalTemporalRewriteEvent);
106
+ const eventIds = events.map((event) => event.eventId);
107
+ if (new Set(eventIds).size !== eventIds.length)
108
+ throw new Error("tmw_rewrite_event_id_duplicate");
109
+ events.sort((left, right) => left.effectiveWorldTime - right.effectiveWorldTime
110
+ || (left.eventId < right.eventId ? -1 : 1));
111
+ for (const event of events) {
112
+ if (event.effectiveWorldTime < canonicalInterval.fromWorldTime
113
+ || event.effectiveWorldTime > canonicalInterval.targetWorldTime) {
114
+ throw new Error("tmw_rewrite_event_outside_target_interval");
115
+ }
116
+ }
117
+ const replacedEventIds = identifierArray(record.replacedEventIds, "replaced_event_ids");
118
+ const replacementClaims = events.flatMap((event) => event.replacementOf);
119
+ if (new Set(replacementClaims).size !== replacementClaims.length) {
120
+ throw new Error("tmw_rewrite_replacement_source_duplicate");
121
+ }
122
+ const canonicalClaims = [...replacementClaims].sort();
123
+ if (canonicalClaims.length !== replacedEventIds.length
124
+ || canonicalClaims.some((eventId, index) => eventId !== replacedEventIds[index])) {
125
+ throw new Error("tmw_rewrite_replaced_event_set_mismatch");
126
+ }
127
+ if (eventIds.some((eventId) => replacedEventIds.includes(eventId))) {
128
+ throw new Error("tmw_rewrite_source_replacement_event_id_collision");
129
+ }
130
+ const compensationProposals = record.compensationProposals.map(parseCompensation);
131
+ const compensationIds = compensationProposals.map((proposal) => proposal.externalEffectId);
132
+ if (new Set(compensationIds).size !== compensationIds.length) {
133
+ throw new Error("tmw_rewrite_compensation_effect_duplicate");
134
+ }
135
+ compensationProposals.sort((left, right) => left.externalEffectId < right.externalEffectId
136
+ ? -1
137
+ : left.externalEffectId > right.externalEffectId
138
+ ? 1
139
+ : 0);
140
+ return Object.freeze({
141
+ schemaVersion: 3,
142
+ epoch: requireTmwSafeInteger(record.epoch, "rewrite_epoch", 1),
143
+ runtimeGeneration: TMW_RUNTIME_GENERATION,
144
+ runtimeContractHash: requireTmwSha256(record.runtimeContractHash, "rewrite_contract_hash"),
145
+ artifactDigest: requireTmwSha256(record.artifactDigest, "rewrite_artifact_digest"),
146
+ scenarioId: requireTmwIdentifier(record.scenarioId, "rewrite_scenario_id"),
147
+ sourceRevision: requireTmwIdentifier(record.sourceRevision, "rewrite_source_revision"),
148
+ timelineRevision: requireTmwIdentifier(record.timelineRevision, "rewrite_timeline_revision"),
149
+ turnId: requireTmwIdentifier(record.turnId, "rewrite_turn_id"),
150
+ operationId: requireTmwIdentifier(record.operationId, "rewrite_operation_id"),
151
+ idempotencyKey: requireTmwIdentifier(record.idempotencyKey, "rewrite_idempotency_key"),
152
+ interval: canonicalInterval,
153
+ narrative: canonicalTmwJsonValue(record.narrative, "rewrite_narrative"),
154
+ narrativeDigest: requireTmwSha256(record.narrativeDigest, "rewrite_narrative_digest"),
155
+ events: Object.freeze(events),
156
+ replacedEventIds,
157
+ conflictCodes: identifierArray(record.conflictCodes, "conflict_codes"),
158
+ evidenceRefs: identifierArray(record.evidenceRefs, "evidence_refs"),
159
+ compensationProposals: Object.freeze(compensationProposals),
160
+ draftDigest: requireTmwSha256(record.draftDigest, "rewrite_draft_digest"),
161
+ });
162
+ }
163
+ export async function verifyTemporalRewriteDraft(value) {
164
+ const draft = parseTemporalRewriteDraft(value);
165
+ const narrativeDigest = await tmwSha256Hex(canonicalTmwJson(draft.narrative));
166
+ if (narrativeDigest !== draft.narrativeDigest) {
167
+ throw new Error("tmw_rewrite_narrative_digest_mismatch");
168
+ }
169
+ for (const event of draft.events) {
170
+ await verifyTemporalRewriteEvent(event);
171
+ }
172
+ const { draftDigest: _claimedDigest, ...digestImage } = draft;
173
+ const computedDigest = await tmwSha256Hex(canonicalTmwJson(digestImage));
174
+ if (computedDigest !== draft.draftDigest)
175
+ throw new Error("tmw_rewrite_draft_digest_mismatch");
176
+ return draft;
177
+ }
178
+ export async function assertTemporalRewriteBinding(draftInput, expected) {
179
+ const draft = await verifyTemporalRewriteDraft(draftInput);
180
+ for (const [label, actual, wanted] of [
181
+ ["epoch", draft.epoch, expected.epoch],
182
+ ["contract", draft.runtimeContractHash, expected.runtimeContractHash],
183
+ ["artifact", draft.artifactDigest, expected.artifactDigest],
184
+ ["scenario", draft.scenarioId, expected.scenarioId],
185
+ ["source", draft.sourceRevision, expected.sourceRevision],
186
+ ["timeline", draft.timelineRevision, expected.timelineRevision],
187
+ ["turn", draft.turnId, expected.turnId],
188
+ ["operation", draft.operationId, expected.operationId],
189
+ ["idempotency", draft.idempotencyKey, expected.idempotencyKey],
190
+ ]) {
191
+ if (actual !== wanted) {
192
+ if (label === "epoch")
193
+ throw new Error("stale_content_epoch");
194
+ if (label === "contract")
195
+ throw new Error("runtime_contract_hash_mismatch");
196
+ throw new Error(`tmw_rewrite_${label}_mismatch`);
197
+ }
198
+ }
199
+ const expectedInterval = Object.freeze({
200
+ fromWorldTime: worldSecond(expected.interval.fromWorldTime, "expected_from_world_time"),
201
+ toWorldTime: worldSecond(expected.interval.toWorldTime, "expected_to_world_time"),
202
+ targetWorldTime: worldSecond(expected.interval.targetWorldTime, "expected_target_world_time"),
203
+ });
204
+ if (expectedInterval.fromWorldTime > expectedInterval.targetWorldTime
205
+ || expectedInterval.targetWorldTime > expectedInterval.toWorldTime) {
206
+ throw new Error("tmw_rewrite_expected_interval_invalid");
207
+ }
208
+ if (draft.interval.fromWorldTime !== expectedInterval.fromWorldTime
209
+ || draft.interval.toWorldTime !== expectedInterval.toWorldTime
210
+ || draft.interval.targetWorldTime !== expectedInterval.targetWorldTime) {
211
+ throw new Error("tmw_rewrite_interval_mismatch");
212
+ }
213
+ return draft;
214
+ }
@@ -0,0 +1,37 @@
1
+ export declare const TMW_CLOCK_DOMAINS: readonly ["logical_seq", "world_time", "presentation_seq", "real_time"];
2
+ /** @deprecated Use TMW_CLOCK_DOMAINS. Kept as a source-compatible export name. */
3
+ export declare const TMW_TIME_DOMAINS: readonly ["logical_seq", "world_time", "presentation_seq", "real_time"];
4
+ export type TmwClockDomain = (typeof TMW_CLOCK_DOMAINS)[number];
5
+ /** @deprecated Use TmwClockDomain. */
6
+ export type TmwTimeDomain = TmwClockDomain;
7
+ export type CrossingDirection = "forward" | "backward";
8
+ export type CrossingPolicy = "forward_only" | "backward_only" | "both_directions";
9
+ export type RealTimeClock = "monotonic_session" | "active_play" | "wall_clock";
10
+ export type WallClockPolicy = "elapse_while_suspended" | "active_session_only" | "reject";
11
+ /**
12
+ * A trigger node's kind is intentionally not encoded here. Effective time is a
13
+ * clock coordinate shared by event, condition, effect and presentation nodes.
14
+ */
15
+ export interface TmwEffectiveTime<D extends TmwClockDomain = TmwClockDomain> {
16
+ readonly domain: D;
17
+ readonly due_value: number;
18
+ readonly effective_value: number;
19
+ readonly crossing_policy: CrossingPolicy;
20
+ readonly real_time_clock: D extends "real_time" ? RealTimeClock : null;
21
+ readonly wall_clock_policy: D extends "real_time" ? WallClockPolicy : null;
22
+ }
23
+ export type EventEffectiveTime = TmwEffectiveTime;
24
+ export type ConditionEffectiveTime = TmwEffectiveTime;
25
+ export type EffectEffectiveTime = TmwEffectiveTime;
26
+ export type PresentationEffectiveTime = TmwEffectiveTime;
27
+ export declare function canonicalTmwClockCoordinate(value: number, domain: TmwClockDomain): number;
28
+ export declare function isTmwEffectiveTime<D extends TmwClockDomain = TmwClockDomain>(value: unknown, expectedDomain?: D): value is TmwEffectiveTime<D>;
29
+ export declare function canonicalTmwEffectiveTime<D extends TmwClockDomain>(value: TmwEffectiveTime<D>, expectedDomain?: D): TmwEffectiveTime<D>;
30
+ export declare function compareTmwEffectiveTime(left: TmwEffectiveTime, right: TmwEffectiveTime): number;
31
+ export declare function crossingPolicyAllows(policy: CrossingPolicy, direction: CrossingDirection): boolean;
32
+ /** Materializes an authored due coordinate against a host-observed clock value. */
33
+ export declare function observeTmwEffectiveTime<D extends TmwClockDomain>(authored: TmwEffectiveTime<D>, observedValue: number): TmwEffectiveTime<D>;
34
+ /** Point-in-time eligibility. Repeated delivery is handled by operation identity. */
35
+ export declare function tmwEffectiveTimeIsDue(effectiveTimeInput: TmwEffectiveTime, observedValue: number, direction: CrossingDirection): boolean;
36
+ /** Strict boundary crossing used by durable scheduler continuations. */
37
+ export declare function tmwEffectiveTimeCrossedDue(effectiveTimeInput: TmwEffectiveTime, previousObservedValue: number, observedValue: number, direction: CrossingDirection): boolean;
@@ -0,0 +1,142 @@
1
+ export const TMW_CLOCK_DOMAINS = Object.freeze([
2
+ "logical_seq",
3
+ "world_time",
4
+ "presentation_seq",
5
+ "real_time",
6
+ ]);
7
+ /** @deprecated Use TMW_CLOCK_DOMAINS. Kept as a source-compatible export name. */
8
+ export const TMW_TIME_DOMAINS = TMW_CLOCK_DOMAINS;
9
+ const EFFECTIVE_TIME_KEYS = Object.freeze([
10
+ "crossing_policy",
11
+ "domain",
12
+ "due_value",
13
+ "effective_value",
14
+ "real_time_clock",
15
+ "wall_clock_policy",
16
+ ]);
17
+ const DOMAIN_ORDER = Object.freeze({
18
+ logical_seq: 0,
19
+ world_time: 1,
20
+ presentation_seq: 2,
21
+ real_time: 3,
22
+ });
23
+ function exactKeys(value, keys) {
24
+ return Object.keys(value).sort().join("\0") === [...keys].sort().join("\0");
25
+ }
26
+ function isFiniteCoordinate(value) {
27
+ return typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0);
28
+ }
29
+ export function canonicalTmwClockCoordinate(value, domain) {
30
+ if (!isFiniteCoordinate(value))
31
+ throw new Error("tmw_clock_coordinate_invalid");
32
+ if ((domain === "logical_seq" || domain === "presentation_seq")
33
+ && (!Number.isSafeInteger(value) || value < 0)) {
34
+ throw new Error("tmw_clock_coordinate_invalid");
35
+ }
36
+ return value;
37
+ }
38
+ function isClockDomain(value) {
39
+ return typeof value === "string"
40
+ && TMW_CLOCK_DOMAINS.includes(value);
41
+ }
42
+ function isCrossingPolicy(value) {
43
+ return value === "forward_only" || value === "backward_only" || value === "both_directions";
44
+ }
45
+ function isRealTimeClock(value) {
46
+ return value === "monotonic_session" || value === "active_play" || value === "wall_clock";
47
+ }
48
+ function isWallClockPolicy(value) {
49
+ return value === "elapse_while_suspended" || value === "active_session_only" || value === "reject";
50
+ }
51
+ export function isTmwEffectiveTime(value, expectedDomain) {
52
+ if (typeof value !== "object" || value === null || Array.isArray(value))
53
+ return false;
54
+ const record = value;
55
+ if (!exactKeys(record, EFFECTIVE_TIME_KEYS)
56
+ || !isClockDomain(record.domain)
57
+ || (expectedDomain !== undefined && record.domain !== expectedDomain)
58
+ || !isFiniteCoordinate(record.due_value)
59
+ || !isFiniteCoordinate(record.effective_value)
60
+ || !isCrossingPolicy(record.crossing_policy)) {
61
+ return false;
62
+ }
63
+ if ((record.domain === "logical_seq" || record.domain === "presentation_seq")
64
+ && (!Number.isSafeInteger(record.due_value)
65
+ || record.due_value < 0
66
+ || !Number.isSafeInteger(record.effective_value)
67
+ || record.effective_value < 0)) {
68
+ return false;
69
+ }
70
+ if (record.domain !== "real_time") {
71
+ return record.real_time_clock === null && record.wall_clock_policy === null;
72
+ }
73
+ if (!isRealTimeClock(record.real_time_clock) || !isWallClockPolicy(record.wall_clock_policy)) {
74
+ return false;
75
+ }
76
+ return record.real_time_clock === "wall_clock"
77
+ || record.wall_clock_policy === "active_session_only";
78
+ }
79
+ export function canonicalTmwEffectiveTime(value, expectedDomain) {
80
+ if (!isTmwEffectiveTime(value, expectedDomain))
81
+ throw new Error("tmw_effective_time_invalid");
82
+ return Object.freeze({
83
+ domain: value.domain,
84
+ due_value: value.due_value,
85
+ effective_value: value.effective_value,
86
+ crossing_policy: value.crossing_policy,
87
+ real_time_clock: value.real_time_clock,
88
+ wall_clock_policy: value.wall_clock_policy,
89
+ });
90
+ }
91
+ export function compareTmwEffectiveTime(left, right) {
92
+ return DOMAIN_ORDER[left.domain] - DOMAIN_ORDER[right.domain]
93
+ || left.due_value - right.due_value
94
+ || left.effective_value - right.effective_value
95
+ || left.crossing_policy.localeCompare(right.crossing_policy)
96
+ || String(left.real_time_clock).localeCompare(String(right.real_time_clock))
97
+ || String(left.wall_clock_policy).localeCompare(String(right.wall_clock_policy));
98
+ }
99
+ export function crossingPolicyAllows(policy, direction) {
100
+ if (!isCrossingPolicy(policy))
101
+ throw new Error("tmw_crossing_policy_invalid");
102
+ if (direction !== "forward" && direction !== "backward") {
103
+ throw new Error("tmw_crossing_direction_invalid");
104
+ }
105
+ return policy === "both_directions"
106
+ || (policy === "forward_only" && direction === "forward")
107
+ || (policy === "backward_only" && direction === "backward");
108
+ }
109
+ /** Materializes an authored due coordinate against a host-observed clock value. */
110
+ export function observeTmwEffectiveTime(authored, observedValue) {
111
+ const canonical = canonicalTmwEffectiveTime(authored);
112
+ return canonicalTmwEffectiveTime(Object.freeze({
113
+ ...canonical,
114
+ effective_value: canonicalTmwClockCoordinate(observedValue, canonical.domain),
115
+ }));
116
+ }
117
+ /** Point-in-time eligibility. Repeated delivery is handled by operation identity. */
118
+ export function tmwEffectiveTimeIsDue(effectiveTimeInput, observedValue, direction) {
119
+ const effectiveTime = canonicalTmwEffectiveTime(effectiveTimeInput);
120
+ const observed = canonicalTmwClockCoordinate(observedValue, effectiveTime.domain);
121
+ if (!crossingPolicyAllows(effectiveTime.crossing_policy, direction))
122
+ return false;
123
+ return direction === "forward"
124
+ ? observed >= effectiveTime.due_value
125
+ : observed <= effectiveTime.due_value;
126
+ }
127
+ /** Strict boundary crossing used by durable scheduler continuations. */
128
+ export function tmwEffectiveTimeCrossedDue(effectiveTimeInput, previousObservedValue, observedValue, direction) {
129
+ const effectiveTime = canonicalTmwEffectiveTime(effectiveTimeInput);
130
+ const previous = canonicalTmwClockCoordinate(previousObservedValue, effectiveTime.domain);
131
+ const observed = canonicalTmwClockCoordinate(observedValue, effectiveTime.domain);
132
+ if (!crossingPolicyAllows(effectiveTime.crossing_policy, direction))
133
+ return false;
134
+ if (direction === "forward") {
135
+ return observed >= previous
136
+ && previous < effectiveTime.due_value
137
+ && effectiveTime.due_value <= observed;
138
+ }
139
+ return observed <= previous
140
+ && observed <= effectiveTime.due_value
141
+ && effectiveTime.due_value < previous;
142
+ }