@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,28 @@
|
|
|
1
|
+
import { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
|
|
2
|
+
export interface TimelineRevisionV3 {
|
|
3
|
+
readonly revisionId: string;
|
|
4
|
+
readonly parentRevisionId: string | null;
|
|
5
|
+
readonly revisionSequence: number;
|
|
6
|
+
readonly epoch: number;
|
|
7
|
+
readonly runtimeGeneration: typeof TMW_RUNTIME_GENERATION;
|
|
8
|
+
readonly runtimeContractHash: string;
|
|
9
|
+
readonly artifactDigest: string;
|
|
10
|
+
readonly sourceRevision: string;
|
|
11
|
+
readonly worldlineId: string;
|
|
12
|
+
readonly operationId: string;
|
|
13
|
+
readonly ledgerHead: string;
|
|
14
|
+
readonly narrativeDigest: string;
|
|
15
|
+
readonly savSectionDigest: string;
|
|
16
|
+
readonly intentSetDigest: string;
|
|
17
|
+
readonly presentationIntentSetDigest: string;
|
|
18
|
+
readonly createdFrom: "fresh" | "turn" | "temporal_rewrite";
|
|
19
|
+
readonly active: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface TimelineHeadV3 {
|
|
22
|
+
readonly revisionId: string;
|
|
23
|
+
readonly revisionSequence: number;
|
|
24
|
+
readonly ledgerHead: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function canonicalTimelineRevision(input: TimelineRevisionV3): TimelineRevisionV3;
|
|
27
|
+
export declare function assertTimelineSuccessor(current: TimelineHeadV3 | null, candidateInput: TimelineRevisionV3): TimelineRevisionV3;
|
|
28
|
+
export declare function timelineHead(revisionInput: TimelineRevisionV3): TimelineHeadV3;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
|
|
2
|
+
import { requireTmwIdentifier, requireTmwSafeInteger, requireTmwSha256, } from "./runtime-trace.js";
|
|
3
|
+
export function canonicalTimelineRevision(input) {
|
|
4
|
+
if (input.runtimeGeneration !== 3)
|
|
5
|
+
throw new Error("unsupported_runtime_generation");
|
|
6
|
+
if (input.createdFrom !== "fresh" && input.createdFrom !== "turn"
|
|
7
|
+
&& input.createdFrom !== "temporal_rewrite") {
|
|
8
|
+
throw new Error("tmw_timeline_created_from_invalid");
|
|
9
|
+
}
|
|
10
|
+
if (typeof input.active !== "boolean")
|
|
11
|
+
throw new Error("tmw_timeline_active_invalid");
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
revisionId: requireTmwIdentifier(input.revisionId, "timeline_revision_id"),
|
|
14
|
+
parentRevisionId: input.parentRevisionId === null
|
|
15
|
+
? null
|
|
16
|
+
: requireTmwIdentifier(input.parentRevisionId, "timeline_parent_revision_id"),
|
|
17
|
+
revisionSequence: requireTmwSafeInteger(input.revisionSequence, "timeline_revision_sequence"),
|
|
18
|
+
epoch: requireTmwSafeInteger(input.epoch, "timeline_epoch", 1),
|
|
19
|
+
runtimeGeneration: TMW_RUNTIME_GENERATION,
|
|
20
|
+
runtimeContractHash: requireTmwSha256(input.runtimeContractHash, "timeline_contract_hash"),
|
|
21
|
+
artifactDigest: requireTmwSha256(input.artifactDigest, "timeline_artifact_digest"),
|
|
22
|
+
sourceRevision: requireTmwIdentifier(input.sourceRevision, "timeline_source_revision"),
|
|
23
|
+
worldlineId: requireTmwIdentifier(input.worldlineId, "timeline_worldline_id"),
|
|
24
|
+
operationId: requireTmwIdentifier(input.operationId, "timeline_operation_id"),
|
|
25
|
+
ledgerHead: requireTmwSha256(input.ledgerHead, "timeline_ledger_head"),
|
|
26
|
+
narrativeDigest: requireTmwSha256(input.narrativeDigest, "timeline_narrative_digest"),
|
|
27
|
+
savSectionDigest: requireTmwSha256(input.savSectionDigest, "timeline_sav_digest"),
|
|
28
|
+
intentSetDigest: requireTmwSha256(input.intentSetDigest, "timeline_intent_digest"),
|
|
29
|
+
presentationIntentSetDigest: requireTmwSha256(input.presentationIntentSetDigest, "timeline_presentation_intent_digest"),
|
|
30
|
+
createdFrom: input.createdFrom,
|
|
31
|
+
active: input.active,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function assertTimelineSuccessor(current, candidateInput) {
|
|
35
|
+
const candidate = canonicalTimelineRevision(candidateInput);
|
|
36
|
+
if (!candidate.active)
|
|
37
|
+
throw new Error("tmw_timeline_candidate_not_active");
|
|
38
|
+
if (current === null) {
|
|
39
|
+
if (candidate.parentRevisionId !== null || candidate.revisionSequence !== 0) {
|
|
40
|
+
throw new Error("tmw_timeline_fresh_parent_invalid");
|
|
41
|
+
}
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
if (candidate.parentRevisionId !== current.revisionId) {
|
|
45
|
+
throw new Error("tmw_timeline_parent_mismatch");
|
|
46
|
+
}
|
|
47
|
+
if (candidate.revisionSequence !== current.revisionSequence + 1) {
|
|
48
|
+
throw new Error("tmw_timeline_sequence_mismatch");
|
|
49
|
+
}
|
|
50
|
+
return candidate;
|
|
51
|
+
}
|
|
52
|
+
export function timelineHead(revisionInput) {
|
|
53
|
+
const revision = canonicalTimelineRevision(revisionInput);
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
revisionId: revision.revisionId,
|
|
56
|
+
revisionSequence: revision.revisionSequence,
|
|
57
|
+
ledgerHead: revision.ledgerHead,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { CapabilityCheckedEffectV3 } from "./capability-registry.js";
|
|
2
|
+
import type { TmwResult } from "./diagnostics.js";
|
|
3
|
+
import { TMW_RUNTIME_GENERATION, TRIGGER_DOCUMENT_V3_VERSION, type TmwJsonPrimitive } from "./runtime-contract.js";
|
|
4
|
+
import type { SelectorAstV3 } from "./selector-ast.js";
|
|
5
|
+
import { type ConditionEffectiveTime, type EffectEffectiveTime, type EventEffectiveTime, type PresentationEffectiveTime } from "./time-domains.js";
|
|
6
|
+
import type { EffectExecutionPolicy, EffectRewritePolicy } from "./effect-flow.js";
|
|
7
|
+
import { type PresentationPayloadV3 } from "./presentation-intent.js";
|
|
8
|
+
import { type TmwValueGraphV1, type TmwSceneValueFieldV1 } from "./value-graph.js";
|
|
9
|
+
export interface TmwNaturalLanguageConditionV3 {
|
|
10
|
+
readonly op: "nl_condition";
|
|
11
|
+
/** Stable leaf identity within the owning condition expression. */
|
|
12
|
+
readonly stable_id: string;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
readonly locale: string;
|
|
15
|
+
readonly anchor?: SelectorAstV3;
|
|
16
|
+
}
|
|
17
|
+
export type TmwSemanticConditionExpressionV3 = TmwNaturalLanguageConditionV3;
|
|
18
|
+
export type TmwConditionExpressionV3 = {
|
|
19
|
+
readonly op: "literal";
|
|
20
|
+
readonly value: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
readonly op: "value_input";
|
|
23
|
+
readonly port: "value";
|
|
24
|
+
} | {
|
|
25
|
+
readonly op: "scene_field";
|
|
26
|
+
readonly field: TmwSceneValueFieldV1;
|
|
27
|
+
readonly comparison: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains";
|
|
28
|
+
readonly value: import("./runtime-contract.js").TmwJsonValue;
|
|
29
|
+
} | {
|
|
30
|
+
readonly op: "event_field";
|
|
31
|
+
readonly path: readonly string[];
|
|
32
|
+
readonly comparison: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains";
|
|
33
|
+
readonly value: TmwJsonPrimitive;
|
|
34
|
+
} | {
|
|
35
|
+
readonly op: "selector_exists";
|
|
36
|
+
readonly selector: SelectorAstV3;
|
|
37
|
+
} | TmwNaturalLanguageConditionV3 | {
|
|
38
|
+
readonly op: "not";
|
|
39
|
+
readonly value: TmwConditionExpressionV3;
|
|
40
|
+
} | {
|
|
41
|
+
readonly op: "all" | "any";
|
|
42
|
+
readonly values: readonly TmwConditionExpressionV3[];
|
|
43
|
+
};
|
|
44
|
+
export interface TriggerEventNodeV3 {
|
|
45
|
+
readonly kind: "event";
|
|
46
|
+
readonly stable_id: string;
|
|
47
|
+
/** Canonical authoring/runtime gate. Missing legacy input materializes as true. */
|
|
48
|
+
readonly enabled?: boolean;
|
|
49
|
+
readonly event_type: string;
|
|
50
|
+
/** Explicit rewrite-retention authority; never inferred from downstream capabilities. */
|
|
51
|
+
readonly hard_fact: boolean;
|
|
52
|
+
readonly effective_time: EventEffectiveTime;
|
|
53
|
+
readonly selector: SelectorAstV3 | null;
|
|
54
|
+
}
|
|
55
|
+
export interface TriggerConditionNodeV3 {
|
|
56
|
+
readonly kind: "condition";
|
|
57
|
+
readonly stable_id: string;
|
|
58
|
+
readonly enabled?: boolean;
|
|
59
|
+
readonly effective_time: ConditionEffectiveTime;
|
|
60
|
+
readonly expression: TmwConditionExpressionV3;
|
|
61
|
+
}
|
|
62
|
+
export interface TriggerEffectNodeV3 extends CapabilityCheckedEffectV3 {
|
|
63
|
+
readonly kind: "effect";
|
|
64
|
+
readonly stable_id: string;
|
|
65
|
+
readonly enabled?: boolean;
|
|
66
|
+
readonly effective_time: EffectEffectiveTime;
|
|
67
|
+
readonly execution_policy: EffectExecutionPolicy;
|
|
68
|
+
readonly rewrite_policy: EffectRewritePolicy;
|
|
69
|
+
readonly priority: number;
|
|
70
|
+
readonly idempotency_key: string;
|
|
71
|
+
}
|
|
72
|
+
export interface TriggerPresentationNodeV3 {
|
|
73
|
+
readonly kind: "presentation";
|
|
74
|
+
readonly stable_id: string;
|
|
75
|
+
readonly enabled?: boolean;
|
|
76
|
+
readonly effective_time: PresentationEffectiveTime;
|
|
77
|
+
readonly intent_type: string;
|
|
78
|
+
readonly payload: PresentationPayloadV3;
|
|
79
|
+
}
|
|
80
|
+
export type TriggerNodeV3 = TriggerEventNodeV3 | TriggerConditionNodeV3 | TriggerEffectNodeV3 | TriggerPresentationNodeV3;
|
|
81
|
+
export type TriggerNodeKindV3 = "event" | "condition" | "effect" | "presentation";
|
|
82
|
+
export interface TriggerEdgeV3 {
|
|
83
|
+
readonly from_stable_id: string;
|
|
84
|
+
readonly to_stable_id: string;
|
|
85
|
+
}
|
|
86
|
+
export interface TriggerDocumentV3 {
|
|
87
|
+
readonly version: typeof TRIGGER_DOCUMENT_V3_VERSION;
|
|
88
|
+
readonly runtime_generation: typeof TMW_RUNTIME_GENERATION;
|
|
89
|
+
readonly runtime_contract_hash: string;
|
|
90
|
+
readonly registry_version: string;
|
|
91
|
+
readonly events: readonly TriggerEventNodeV3[];
|
|
92
|
+
readonly conditions: readonly TriggerConditionNodeV3[];
|
|
93
|
+
readonly effects: readonly TriggerEffectNodeV3[];
|
|
94
|
+
readonly presentations: readonly TriggerPresentationNodeV3[];
|
|
95
|
+
readonly edges: readonly TriggerEdgeV3[];
|
|
96
|
+
readonly value_graph?: TmwValueGraphV1;
|
|
97
|
+
}
|
|
98
|
+
export declare function parseTriggerDocumentV3(value: unknown): TmwResult<TriggerDocumentV3>;
|
|
99
|
+
export declare function allTriggerNodesV3(document: TriggerDocumentV3): readonly TriggerNodeV3[];
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { orderTmwDiagnostics, tmwDiagnostic, tmwFailure, tmwSuccess } from "./diagnostics.js";
|
|
2
|
+
import { isSha256Hex, TMW_RUNTIME_GENERATION, TRIGGER_DOCUMENT_V3_VERSION } from "./runtime-contract.js";
|
|
3
|
+
import { isTmwEffectiveTime } from "./time-domains.js";
|
|
4
|
+
import { parsePresentationPayloadV3 } from "./presentation-intent.js";
|
|
5
|
+
import { isTmwTypedValueV1, parseTmwValueGraphV1 } from "./value-graph.js";
|
|
6
|
+
const ROOT_KEYS = ["conditions", "edges", "effects", "events", "presentations", "registry_version", "runtime_contract_hash", "runtime_generation", "version"];
|
|
7
|
+
const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,127})$/u;
|
|
8
|
+
const CONDITION_COMPARISONS = new Set(["eq", "neq", "gt", "gte", "lt", "lte", "contains"]);
|
|
9
|
+
const MAX_CONDITION_DEPTH = 32;
|
|
10
|
+
function record(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
12
|
+
}
|
|
13
|
+
function exactKeys(value, keys) {
|
|
14
|
+
return Object.keys(value).sort().join("\0") === [...keys].sort().join("\0");
|
|
15
|
+
}
|
|
16
|
+
function identifier(value) {
|
|
17
|
+
return typeof value === "string" && IDENTIFIER.test(value) && value.normalize("NFC") === value;
|
|
18
|
+
}
|
|
19
|
+
function jsonPrimitive(value) {
|
|
20
|
+
return value === null
|
|
21
|
+
|| typeof value === "string"
|
|
22
|
+
|| typeof value === "boolean"
|
|
23
|
+
|| (typeof value === "number" && Number.isFinite(value));
|
|
24
|
+
}
|
|
25
|
+
function canonicalLocale(value) {
|
|
26
|
+
if (typeof value !== "string" || value.length === 0 || value.normalize("NFC") !== value)
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
const canonical = Intl.getCanonicalLocales(value);
|
|
30
|
+
return canonical.length === 1 && canonical[0] === value;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseSelector(value, path, diagnostics, depth = 0) {
|
|
37
|
+
if (depth > 32) {
|
|
38
|
+
diagnostics.push(tmwDiagnostic("selector_depth_exceeded", path, "selector depth exceeds 32"));
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const node = record(value);
|
|
42
|
+
if (node === null || typeof node.op !== "string") {
|
|
43
|
+
diagnostics.push(tmwDiagnostic("selector_invalid", path, "selector must be an object with op"));
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (node.op === "entity_id" || node.op === "entity_kind") {
|
|
47
|
+
const key = node.op === "entity_id" ? "entity_id" : "entity_kind";
|
|
48
|
+
if (!exactKeys(node, ["op", key]) || !identifier(node[key]))
|
|
49
|
+
diagnostics.push(tmwDiagnostic("selector_invalid", path, `invalid ${node.op}`));
|
|
50
|
+
}
|
|
51
|
+
else if (node.op === "attribute") {
|
|
52
|
+
if (!exactKeys(node, ["comparison", "op", "path", "source", "value"]) || !Array.isArray(node.path) || node.path.length === 0 || !node.path.every(identifier)) {
|
|
53
|
+
diagnostics.push(tmwDiagnostic("selector_invalid", path, "invalid attribute selector"));
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
parseSelector(node.source, `${path}/source`, diagnostics, depth + 1);
|
|
57
|
+
}
|
|
58
|
+
else if (node.op === "union" || node.op === "intersection") {
|
|
59
|
+
if (!exactKeys(node, ["op", "selectors"]) || !Array.isArray(node.selectors) || node.selectors.length === 0)
|
|
60
|
+
diagnostics.push(tmwDiagnostic("selector_invalid", path, "selector set must be non-empty"));
|
|
61
|
+
else
|
|
62
|
+
node.selectors.forEach((item, index) => parseSelector(item, `${path}/selectors/${index}`, diagnostics, depth + 1));
|
|
63
|
+
}
|
|
64
|
+
else if (node.op === "difference") {
|
|
65
|
+
if (!exactKeys(node, ["left", "op", "right"]))
|
|
66
|
+
diagnostics.push(tmwDiagnostic("selector_invalid", path, "invalid difference selector"));
|
|
67
|
+
else {
|
|
68
|
+
parseSelector(node.left, `${path}/left`, diagnostics, depth + 1);
|
|
69
|
+
parseSelector(node.right, `${path}/right`, diagnostics, depth + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
diagnostics.push(tmwDiagnostic("selector_unknown_op", `${path}/op`, `unknown selector op ${node.op}`));
|
|
74
|
+
return node;
|
|
75
|
+
}
|
|
76
|
+
function parseConditionExpression(value, path, diagnostics, depth = 0, visiting = new WeakSet(), semanticStableIds = new Set()) {
|
|
77
|
+
if (depth >= MAX_CONDITION_DEPTH) {
|
|
78
|
+
diagnostics.push(tmwDiagnostic("condition_depth_exceeded", path, `condition depth exceeds ${MAX_CONDITION_DEPTH}`));
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const node = record(value);
|
|
82
|
+
if (node === null || typeof node.op !== "string") {
|
|
83
|
+
diagnostics.push(tmwDiagnostic("condition_expression_invalid", path, "condition must be an object with op"));
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (visiting.has(node)) {
|
|
87
|
+
diagnostics.push(tmwDiagnostic("condition_cycle", path, "condition expression must be acyclic"));
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
visiting.add(node);
|
|
91
|
+
try {
|
|
92
|
+
if (node.op === "value_input") {
|
|
93
|
+
if (!exactKeys(node, ["op", "port"]) || node.port !== "value") {
|
|
94
|
+
diagnostics.push(tmwDiagnostic("condition_value_input_invalid", path, "value_input requires the boolean value port"));
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return Object.freeze({ op: "value_input", port: "value" });
|
|
98
|
+
}
|
|
99
|
+
if (node.op === "scene_field") {
|
|
100
|
+
if (!exactKeys(node, ["op", "field", "comparison", "value"]) || !["world_time", "location", "scene_id", "present_entity_ids"].includes(String(node.field)) || !CONDITION_COMPARISONS.has(String(node.comparison))) {
|
|
101
|
+
diagnostics.push(tmwDiagnostic("condition_scene_field_invalid", path, "scene_field requires a supported Scene field and comparison"));
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const expectedValid = node.field === "world_time" ? typeof node.value === "number" && Number.isFinite(node.value)
|
|
105
|
+
: node.field === "scene_id" ? typeof node.value === "string"
|
|
106
|
+
: node.field === "present_entity_ids" ? node.comparison === "contains" ? typeof node.value === "string" : Array.isArray(node.value) && node.value.every((v) => typeof v === "string")
|
|
107
|
+
: node.value === null || isTmwTypedValueV1("spatial_ref", node.value);
|
|
108
|
+
const comparisonValid = node.field === "world_time" ? node.comparison !== "contains" : node.field === "location" ? ["eq", "neq"].includes(String(node.comparison)) : ["eq", "neq", "contains"].includes(String(node.comparison));
|
|
109
|
+
if (!expectedValid || !comparisonValid) {
|
|
110
|
+
diagnostics.push(tmwDiagnostic("condition_scene_field_type_invalid", path, "Scene comparison operand type mismatch"));
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return Object.freeze({ op: "scene_field", field: node.field, comparison: node.comparison, value: node.value });
|
|
114
|
+
}
|
|
115
|
+
if (node.op === "literal") {
|
|
116
|
+
if (!exactKeys(node, ["op", "value"]) || typeof node.value !== "boolean") {
|
|
117
|
+
diagnostics.push(tmwDiagnostic("condition_literal_invalid", path, "literal requires exactly a boolean value"));
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return Object.freeze({ op: "literal", value: node.value });
|
|
121
|
+
}
|
|
122
|
+
if (node.op === "event_field") {
|
|
123
|
+
if (!exactKeys(node, ["comparison", "op", "path", "value"])
|
|
124
|
+
|| !Array.isArray(node.path)
|
|
125
|
+
|| node.path.length === 0
|
|
126
|
+
|| !node.path.every(identifier)
|
|
127
|
+
|| !CONDITION_COMPARISONS.has(String(node.comparison))
|
|
128
|
+
|| !jsonPrimitive(node.value)) {
|
|
129
|
+
diagnostics.push(tmwDiagnostic("condition_event_field_invalid", path, "event_field has invalid, missing, or unknown fields"));
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
op: "event_field",
|
|
134
|
+
path: Object.freeze([...node.path]),
|
|
135
|
+
comparison: node.comparison,
|
|
136
|
+
value: node.value,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (node.op === "selector_exists") {
|
|
140
|
+
if (!exactKeys(node, ["op", "selector"])) {
|
|
141
|
+
diagnostics.push(tmwDiagnostic("condition_selector_invalid", path, "selector_exists has invalid, missing, or unknown fields"));
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const selector = parseSelector(node.selector, `${path}/selector`, diagnostics);
|
|
145
|
+
return selector === null ? null : Object.freeze({ op: "selector_exists", selector });
|
|
146
|
+
}
|
|
147
|
+
if (node.op === "nl_condition") {
|
|
148
|
+
const keys = node.anchor === undefined
|
|
149
|
+
? ["locale", "op", "stable_id", "text"]
|
|
150
|
+
: ["anchor", "locale", "op", "stable_id", "text"];
|
|
151
|
+
if (!exactKeys(node, keys)
|
|
152
|
+
|| !identifier(node.stable_id)
|
|
153
|
+
|| typeof node.text !== "string"
|
|
154
|
+
|| node.text.length === 0
|
|
155
|
+
|| node.text.trim() !== node.text
|
|
156
|
+
|| node.text.normalize("NFC") !== node.text
|
|
157
|
+
|| !canonicalLocale(node.locale)) {
|
|
158
|
+
diagnostics.push(tmwDiagnostic("condition_nl_invalid", path, "nl_condition has invalid, missing, or unknown fields"));
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (semanticStableIds.has(node.stable_id)) {
|
|
162
|
+
diagnostics.push(tmwDiagnostic("condition_nl_stable_id_duplicate", `${path}/stable_id`, "nl_condition stable_id must be unique within its condition expression", [node.stable_id]));
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
semanticStableIds.add(node.stable_id);
|
|
166
|
+
const anchor = node.anchor === undefined
|
|
167
|
+
? undefined
|
|
168
|
+
: parseSelector(node.anchor, `${path}/anchor`, diagnostics);
|
|
169
|
+
if (node.anchor !== undefined && anchor === null)
|
|
170
|
+
return null;
|
|
171
|
+
if (anchor === undefined || anchor === null)
|
|
172
|
+
return Object.freeze({
|
|
173
|
+
op: "nl_condition",
|
|
174
|
+
stable_id: node.stable_id,
|
|
175
|
+
text: node.text,
|
|
176
|
+
locale: node.locale,
|
|
177
|
+
});
|
|
178
|
+
return Object.freeze({
|
|
179
|
+
op: "nl_condition",
|
|
180
|
+
stable_id: node.stable_id,
|
|
181
|
+
text: node.text,
|
|
182
|
+
locale: node.locale,
|
|
183
|
+
anchor,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (node.op === "not") {
|
|
187
|
+
if (!exactKeys(node, ["op", "value"])) {
|
|
188
|
+
diagnostics.push(tmwDiagnostic("condition_not_invalid", path, "not requires exactly one value"));
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const child = parseConditionExpression(node.value, `${path}/value`, diagnostics, depth + 1, visiting, semanticStableIds);
|
|
192
|
+
return child === null ? null : Object.freeze({ op: "not", value: child });
|
|
193
|
+
}
|
|
194
|
+
if (node.op === "all" || node.op === "any") {
|
|
195
|
+
if (!exactKeys(node, ["op", "values"]) || !Array.isArray(node.values) || node.values.length === 0) {
|
|
196
|
+
diagnostics.push(tmwDiagnostic("condition_values_invalid", path, `${node.op} requires a non-empty values array and no unknown fields`));
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const values = node.values.map((item, index) => parseConditionExpression(item, `${path}/values/${index}`, diagnostics, depth + 1, visiting, semanticStableIds));
|
|
200
|
+
if (values.some((item) => item === null))
|
|
201
|
+
return null;
|
|
202
|
+
return Object.freeze({
|
|
203
|
+
op: node.op,
|
|
204
|
+
values: Object.freeze(values),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
diagnostics.push(tmwDiagnostic("condition_unknown_op", `${path}/op`, `unknown condition op ${node.op}`));
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
visiting.delete(node);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function parseNodeArray(value, kind, path, diagnostics) {
|
|
215
|
+
if (!Array.isArray(value)) {
|
|
216
|
+
diagnostics.push(tmwDiagnostic("node_array_required", path, "expected an array"));
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
return value.flatMap((item, index) => {
|
|
220
|
+
const nodePath = `${path}/${index}`;
|
|
221
|
+
const rawNode = record(item);
|
|
222
|
+
if (rawNode === null || !identifier(rawNode.stable_id)) {
|
|
223
|
+
diagnostics.push(tmwDiagnostic("stable_id_invalid", `${nodePath}/stable_id`, "stable_id is required and must be canonical"));
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const stableId = rawNode.stable_id;
|
|
227
|
+
const node = {
|
|
228
|
+
...rawNode,
|
|
229
|
+
enabled: rawNode.enabled === undefined ? true : rawNode.enabled,
|
|
230
|
+
};
|
|
231
|
+
if (typeof node.enabled !== "boolean") {
|
|
232
|
+
diagnostics.push(tmwDiagnostic("node_enabled_invalid", `${nodePath}/enabled`, "enabled must be boolean", [stableId]));
|
|
233
|
+
}
|
|
234
|
+
if (node.kind !== kind)
|
|
235
|
+
diagnostics.push(tmwDiagnostic("node_kind_invalid", `${nodePath}/kind`, `node kind must equal ${kind}`, [stableId]));
|
|
236
|
+
if (!isTmwEffectiveTime(node.effective_time))
|
|
237
|
+
diagnostics.push(tmwDiagnostic("effective_time_invalid", `${nodePath}/effective_time`, "expected a canonical TMW3 clock coordinate", [stableId]));
|
|
238
|
+
if (kind === "event") {
|
|
239
|
+
if (!exactKeys(node, ["effective_time", "enabled", "event_type", "hard_fact", "kind", "selector", "stable_id"])
|
|
240
|
+
|| !identifier(node.event_type)
|
|
241
|
+
|| typeof node.hard_fact !== "boolean")
|
|
242
|
+
diagnostics.push(tmwDiagnostic("event_node_invalid", nodePath, "event node has invalid or unknown fields", [stableId]));
|
|
243
|
+
if (node.selector !== null)
|
|
244
|
+
parseSelector(node.selector, `${nodePath}/selector`, diagnostics);
|
|
245
|
+
}
|
|
246
|
+
else if (kind === "condition") {
|
|
247
|
+
if (!exactKeys(node, ["effective_time", "enabled", "expression", "kind", "stable_id"])) {
|
|
248
|
+
diagnostics.push(tmwDiagnostic("condition_node_invalid", nodePath, "condition node has invalid or unknown fields", [stableId]));
|
|
249
|
+
}
|
|
250
|
+
const expression = parseConditionExpression(node.expression, `${nodePath}/expression`, diagnostics);
|
|
251
|
+
if (expression !== null)
|
|
252
|
+
node.expression = expression;
|
|
253
|
+
}
|
|
254
|
+
else if (kind === "effect") {
|
|
255
|
+
if (!exactKeys(node, ["arguments", "capability_id", "effective_time", "enabled", "execution_policy", "idempotency_key", "kind", "priority", "rewrite_policy", "selector", "stable_id"])
|
|
256
|
+
|| !identifier(node.capability_id)
|
|
257
|
+
|| record(node.arguments) === null
|
|
258
|
+
|| (node.execution_policy !== "attempt_once" && node.execution_policy !== "ensure_until")
|
|
259
|
+
|| (node.rewrite_policy !== "normal_only" && node.rewrite_policy !== "rewrite_only" && node.rewrite_policy !== "normal_and_rewrite")
|
|
260
|
+
|| !Number.isSafeInteger(node.priority)
|
|
261
|
+
|| node.priority < 0
|
|
262
|
+
|| !identifier(node.idempotency_key)) {
|
|
263
|
+
diagnostics.push(tmwDiagnostic("effect_node_invalid", nodePath, "effect node has invalid, missing, or unknown fields", [stableId]));
|
|
264
|
+
}
|
|
265
|
+
if (node.selector !== null)
|
|
266
|
+
parseSelector(node.selector, `${nodePath}/selector`, diagnostics);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const presentationPayload = parsePresentationPayloadV3(node.payload);
|
|
270
|
+
if (!exactKeys(node, ["effective_time", "enabled", "intent_type", "kind", "payload", "stable_id"]) || !identifier(node.intent_type) || presentationPayload === null || node.intent_type !== presentationPayload.kind)
|
|
271
|
+
diagnostics.push(tmwDiagnostic("presentation_node_invalid", nodePath, "presentation node has invalid fields or mismatched intent type", [stableId]));
|
|
272
|
+
}
|
|
273
|
+
return [node];
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
export function parseTriggerDocumentV3(value) {
|
|
277
|
+
const diagnostics = [];
|
|
278
|
+
const root = record(value);
|
|
279
|
+
if (root === null)
|
|
280
|
+
return tmwFailure([tmwDiagnostic("tmw3_required", "$", "TriggerDocumentV3 object required")]);
|
|
281
|
+
if (!exactKeys(root, root.value_graph === undefined ? ROOT_KEYS : [...ROOT_KEYS, "value_graph"]))
|
|
282
|
+
diagnostics.push(tmwDiagnostic("document_exact_keys_invalid", "$", "TriggerDocumentV3 contains missing or unknown fields"));
|
|
283
|
+
if (root.version !== TRIGGER_DOCUMENT_V3_VERSION)
|
|
284
|
+
diagnostics.push(tmwDiagnostic("tmw3_required", "$/version", "version must equal 3"));
|
|
285
|
+
if (root.runtime_generation !== TMW_RUNTIME_GENERATION)
|
|
286
|
+
diagnostics.push(tmwDiagnostic("unsupported_runtime_generation", "$/runtime_generation", "runtime_generation must equal 3"));
|
|
287
|
+
if (!isSha256Hex(root.runtime_contract_hash))
|
|
288
|
+
diagnostics.push(tmwDiagnostic("runtime_contract_hash_invalid", "$/runtime_contract_hash", "expected lowercase SHA-256"));
|
|
289
|
+
if (!identifier(root.registry_version))
|
|
290
|
+
diagnostics.push(tmwDiagnostic("registry_version_invalid", "$/registry_version", "registry_version must be canonical"));
|
|
291
|
+
const events = parseNodeArray(root.events, "event", "$/events", diagnostics);
|
|
292
|
+
const conditions = parseNodeArray(root.conditions, "condition", "$/conditions", diagnostics);
|
|
293
|
+
const effects = parseNodeArray(root.effects, "effect", "$/effects", diagnostics);
|
|
294
|
+
const presentations = parseNodeArray(root.presentations, "presentation", "$/presentations", diagnostics);
|
|
295
|
+
const edges = [];
|
|
296
|
+
let valueGraph;
|
|
297
|
+
if (root.value_graph !== undefined) {
|
|
298
|
+
try {
|
|
299
|
+
valueGraph = parseTmwValueGraphV1(root.value_graph);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
diagnostics.push(tmwDiagnostic("value_graph_invalid", "$/value_graph", error instanceof Error ? error.message : "invalid value graph"));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (!Array.isArray(root.edges))
|
|
306
|
+
diagnostics.push(tmwDiagnostic("edge_array_required", "$/edges", "expected an array"));
|
|
307
|
+
else
|
|
308
|
+
root.edges.forEach((item, index) => {
|
|
309
|
+
const edge = record(item);
|
|
310
|
+
const path = `$/edges/${index}`;
|
|
311
|
+
if (edge === null || !exactKeys(edge, ["from_stable_id", "to_stable_id"]) || !identifier(edge.from_stable_id) || !identifier(edge.to_stable_id))
|
|
312
|
+
diagnostics.push(tmwDiagnostic("edge_invalid", path, "edge has invalid or unknown fields"));
|
|
313
|
+
else
|
|
314
|
+
edges.push(edge);
|
|
315
|
+
});
|
|
316
|
+
const ordered = orderTmwDiagnostics(diagnostics);
|
|
317
|
+
if (ordered.some((diagnostic) => diagnostic.severity === "error"))
|
|
318
|
+
return tmwFailure(ordered);
|
|
319
|
+
return tmwSuccess(Object.freeze({
|
|
320
|
+
version: TRIGGER_DOCUMENT_V3_VERSION,
|
|
321
|
+
runtime_generation: TMW_RUNTIME_GENERATION,
|
|
322
|
+
runtime_contract_hash: root.runtime_contract_hash,
|
|
323
|
+
registry_version: root.registry_version,
|
|
324
|
+
events: Object.freeze(events),
|
|
325
|
+
conditions: Object.freeze(conditions),
|
|
326
|
+
effects: Object.freeze(effects),
|
|
327
|
+
presentations: Object.freeze(presentations),
|
|
328
|
+
edges: Object.freeze(edges),
|
|
329
|
+
...(valueGraph === undefined ? {} : { value_graph: valueGraph }),
|
|
330
|
+
}), ordered);
|
|
331
|
+
}
|
|
332
|
+
export function allTriggerNodesV3(document) {
|
|
333
|
+
return [...document.events, ...document.conditions, ...document.effects, ...document.presentations];
|
|
334
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
|
|
2
|
+
import { type TmwJsonValue } from "./runtime-trace.js";
|
|
3
|
+
export type TurnCommitTerminalKind = "committed" | "failed" | "aborted" | "disposed";
|
|
4
|
+
export interface TurnCommitIdentity {
|
|
5
|
+
readonly operationId: string;
|
|
6
|
+
readonly turnId: string;
|
|
7
|
+
readonly epoch: number;
|
|
8
|
+
readonly runtimeGeneration: typeof TMW_RUNTIME_GENERATION;
|
|
9
|
+
readonly runtimeContractHash: string;
|
|
10
|
+
readonly artifactDigest: string;
|
|
11
|
+
readonly sourceRevision: string;
|
|
12
|
+
readonly timelineRevision: string;
|
|
13
|
+
readonly leaseId: string;
|
|
14
|
+
}
|
|
15
|
+
export interface TurnCommitTerminalReceipt extends TurnCommitIdentity {
|
|
16
|
+
readonly schemaVersion: 3;
|
|
17
|
+
readonly terminal: TurnCommitTerminalKind;
|
|
18
|
+
readonly reasonCode: string | null;
|
|
19
|
+
readonly operationDigest: string;
|
|
20
|
+
readonly budgetReceiptDigest: string;
|
|
21
|
+
readonly stageDigests: Readonly<Record<string, string>>;
|
|
22
|
+
readonly activationReceiptDigest: string | null;
|
|
23
|
+
readonly preActivationTerminalDigest: string;
|
|
24
|
+
}
|
|
25
|
+
export interface TurnCommitTerminalStore {
|
|
26
|
+
read(operationId: string): Promise<TurnCommitTerminalReceipt | null>;
|
|
27
|
+
putIfAbsent(operationId: string, receipt: TurnCommitTerminalReceipt): Promise<Readonly<{
|
|
28
|
+
status: "stored" | "exists";
|
|
29
|
+
receipt: TurnCommitTerminalReceipt;
|
|
30
|
+
}>>;
|
|
31
|
+
}
|
|
32
|
+
export interface TurnCommitOperationSnapshot {
|
|
33
|
+
readonly identity: TurnCommitIdentity;
|
|
34
|
+
readonly operationDigest: string;
|
|
35
|
+
readonly stageDigests: Readonly<Record<string, string>>;
|
|
36
|
+
readonly terminal: TurnCommitTerminalReceipt | null;
|
|
37
|
+
}
|
|
38
|
+
export declare class DurableTurnCommitOperation {
|
|
39
|
+
#private;
|
|
40
|
+
private constructor();
|
|
41
|
+
static open(input: Readonly<{
|
|
42
|
+
identity: TurnCommitIdentity;
|
|
43
|
+
operation: TmwJsonValue;
|
|
44
|
+
store: TurnCommitTerminalStore;
|
|
45
|
+
}>): Promise<DurableTurnCommitOperation>;
|
|
46
|
+
recordStage(stageInput: string, digestInput: string): void;
|
|
47
|
+
commit(input: Readonly<{
|
|
48
|
+
budgetReceiptDigest: string;
|
|
49
|
+
activationReceiptDigest: string;
|
|
50
|
+
}>): Promise<TurnCommitTerminalReceipt>;
|
|
51
|
+
fail(reasonCode: string, budgetReceiptDigest: string): Promise<TurnCommitTerminalReceipt>;
|
|
52
|
+
abort(reasonCode: string, budgetReceiptDigest: string): Promise<TurnCommitTerminalReceipt>;
|
|
53
|
+
dispose(reasonCode: string, budgetReceiptDigest: string): Promise<TurnCommitTerminalReceipt>;
|
|
54
|
+
snapshot(): TurnCommitOperationSnapshot;
|
|
55
|
+
previewCommittedTerminalDigest(budgetReceiptDigestInput: string): Promise<string>;
|
|
56
|
+
}
|