@cynodia/axiom-server 0.14.0-alpha.1 → 0.14.0-alpha.3

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 (44) hide show
  1. package/conformance/live/aggregate-reset.json +1 -1
  2. package/conformance/live/dependency-commit-no-change.json +1 -1
  3. package/conformance/live/f2-state-ref-refused.json +1 -1
  4. package/conformance/live/initial-result.json +1 -1
  5. package/conformance/live/insert-on-filter-entry.json +1 -1
  6. package/conformance/live/limit-boundary.json +1 -1
  7. package/conformance/live/manifest.json +1 -1
  8. package/conformance/live/not-live-capable.json +1 -1
  9. package/conformance/live/provider-only-sequence.json +1 -1
  10. package/conformance/live/provider-record-remove.json +1 -1
  11. package/conformance/live/remove-on-filter-exit.json +1 -1
  12. package/conformance/live/update-with-move.json +1 -1
  13. package/conformance/live/update-without-move.json +1 -1
  14. package/conformance/workflow/manifest.json +1 -1
  15. package/conformance/workflow/workflow-action-on-error.json +1 -1
  16. package/conformance/workflow/workflow-action-retry.json +1 -1
  17. package/conformance/workflow/workflow-action-success.json +1 -1
  18. package/conformance/workflow/workflow-action-terminal-failure.json +1 -1
  19. package/conformance/workflow/workflow-branch-false.json +1 -1
  20. package/conformance/workflow/workflow-branch-true.json +1 -1
  21. package/conformance/workflow/workflow-cancel-waiting.json +1 -1
  22. package/conformance/workflow/workflow-event-match.json +1 -1
  23. package/conformance/workflow/workflow-event-nonmatch.json +1 -1
  24. package/conformance/workflow/workflow-event-timeout.json +1 -1
  25. package/conformance/workflow/workflow-start-complete.json +1 -1
  26. package/conformance/workflow/workflow-start-idempotency.json +1 -1
  27. package/conformance/workflow/workflow-timer.json +1 -1
  28. package/dist/authority-identity.d.ts +36 -5
  29. package/dist/authority-identity.js +77 -22
  30. package/dist/deps.d.ts +2 -2
  31. package/dist/deps.js +1 -1
  32. package/dist/server.js +18 -2
  33. package/dist/workflows.d.ts +25 -0
  34. package/dist/workflows.js +155 -8
  35. package/package.json +3 -3
  36. package/schema/protocol.v1.schema.json +1 -1
  37. package/schema/server-ir.v1.schema.json +1 -1
  38. package/schema/server-ir.v2.schema.json +1 -1
  39. package/schema/server-ir.v3.schema.json +1 -1
  40. package/schema/server-ir.v4.schema.json +1 -1
  41. package/schema/server-ir.v5.schema.json +1 -1
  42. package/schema/server-ir.v6.schema.json +1 -1
  43. package/schema/server-ir.v7.schema.json +1 -1
  44. package/schema/server-ir.v8.schema.json +1 -1
@@ -10,7 +10,7 @@
10
10
  "contract": "axiom.server.v6",
11
11
  "id": "live-query-conformance",
12
12
  "name": "Live Query Conformance",
13
- "version": "0.14.0-alpha.1",
13
+ "version": "0.14.0-alpha.3",
14
14
  "entities": [
15
15
  {
16
16
  "id": "entity_order",
@@ -10,7 +10,7 @@
10
10
  "contract": "axiom.server.v6",
11
11
  "id": "live-query-conformance",
12
12
  "name": "Live Query Conformance",
13
- "version": "0.14.0-alpha.1",
13
+ "version": "0.14.0-alpha.3",
14
14
  "entities": [
15
15
  {
16
16
  "id": "entity_order",
@@ -11,7 +11,7 @@
11
11
  "contract": "axiom.server.v6",
12
12
  "id": "live-query-conformance",
13
13
  "name": "Live Query Conformance",
14
- "version": "0.14.0-alpha.1",
14
+ "version": "0.14.0-alpha.3",
15
15
  "entities": [
16
16
  {
17
17
  "id": "entity_order",
@@ -9,7 +9,7 @@
9
9
  "contract": "axiom.server.v6",
10
10
  "id": "live-query-conformance",
11
11
  "name": "Live Query Conformance",
12
- "version": "0.14.0-alpha.1",
12
+ "version": "0.14.0-alpha.3",
13
13
  "entities": [
14
14
  {
15
15
  "id": "entity_order",
@@ -10,7 +10,7 @@
10
10
  "contract": "axiom.server.v6",
11
11
  "id": "live-query-conformance",
12
12
  "name": "Live Query Conformance",
13
- "version": "0.14.0-alpha.1",
13
+ "version": "0.14.0-alpha.3",
14
14
  "entities": [
15
15
  {
16
16
  "id": "entity_order",
@@ -9,7 +9,7 @@
9
9
  "contract": "axiom.server.v6",
10
10
  "id": "live-query-conformance",
11
11
  "name": "Live Query Conformance",
12
- "version": "0.14.0-alpha.1",
12
+ "version": "0.14.0-alpha.3",
13
13
  "entities": [
14
14
  {
15
15
  "id": "entity_order",
@@ -2,7 +2,7 @@
2
2
  "conformance": "axiom.conformance.v7",
3
3
  "baseContract": "axiom.server.v7",
4
4
  "protocol": "axiom.protocol.v1",
5
- "release": "0.14.0-alpha.1",
5
+ "release": "0.14.0-alpha.3",
6
6
  "description": "Portable live-query conformance fixtures (spec13 §152, §194). Each file carries a compiled axiom.server.v7 Server IR, a dataset of provider rows, the live query to open, its required initial result, and a script of committed mutations each paired with the live message that must follow (canonical update / whole reset / none). The runner also checks the primary invariant: folding the delivered stream must equal a fresh one-shot execution of the same QueryDef. Running one needs only a DataProvider and the semantics in docs/LIVE_QUERIES.md.",
7
7
  "fixtures": [
8
8
  {
@@ -10,7 +10,7 @@
10
10
  "contract": "axiom.server.v6",
11
11
  "id": "live-query-conformance",
12
12
  "name": "Live Query Conformance",
13
- "version": "0.14.0-alpha.1",
13
+ "version": "0.14.0-alpha.3",
14
14
  "entities": [
15
15
  {
16
16
  "id": "entity_order",
@@ -12,7 +12,7 @@
12
12
  "contract": "axiom.server.v6",
13
13
  "id": "live-query-conformance",
14
14
  "name": "Live Query Conformance",
15
- "version": "0.14.0-alpha.1",
15
+ "version": "0.14.0-alpha.3",
16
16
  "entities": [
17
17
  {
18
18
  "id": "entity_order",
@@ -9,7 +9,7 @@
9
9
  "contract": "axiom.server.v6",
10
10
  "id": "live-query-conformance",
11
11
  "name": "Live Query Conformance",
12
- "version": "0.14.0-alpha.1",
12
+ "version": "0.14.0-alpha.3",
13
13
  "entities": [
14
14
  {
15
15
  "id": "entity_order",
@@ -10,7 +10,7 @@
10
10
  "contract": "axiom.server.v6",
11
11
  "id": "live-query-conformance",
12
12
  "name": "Live Query Conformance",
13
- "version": "0.14.0-alpha.1",
13
+ "version": "0.14.0-alpha.3",
14
14
  "entities": [
15
15
  {
16
16
  "id": "entity_order",
@@ -10,7 +10,7 @@
10
10
  "contract": "axiom.server.v6",
11
11
  "id": "live-query-conformance",
12
12
  "name": "Live Query Conformance",
13
- "version": "0.14.0-alpha.1",
13
+ "version": "0.14.0-alpha.3",
14
14
  "entities": [
15
15
  {
16
16
  "id": "entity_order",
@@ -9,7 +9,7 @@
9
9
  "contract": "axiom.server.v6",
10
10
  "id": "live-query-conformance",
11
11
  "name": "Live Query Conformance",
12
- "version": "0.14.0-alpha.1",
12
+ "version": "0.14.0-alpha.3",
13
13
  "entities": [
14
14
  {
15
15
  "id": "entity_order",
@@ -2,7 +2,7 @@
2
2
  "conformance": "axiom.conformance.v8",
3
3
  "baseContract": "axiom.server.v8",
4
4
  "protocol": "axiom.protocol.v1",
5
- "release": "0.14.0-alpha.1",
5
+ "release": "0.14.0-alpha.3",
6
6
  "description": "Portable workflow conformance fixtures (spec14 §156-§159). Each file carries a compiled axiom.server.v8 Server IR, the start arguments, a deterministic driver script, and the required logical transition history + terminal state. Physical action attempts may be duplicated; the logical history must match exactly. Running one needs only the in-memory WorkflowStore and the semantics in docs/WORKFLOWS.md.",
7
7
  "fixtures": [
8
8
  {
@@ -31,7 +31,7 @@
31
31
  "contract": "axiom.server.v8",
32
32
  "id": "workflow-conformance",
33
33
  "name": "Workflow Conformance",
34
- "version": "0.14.0-alpha.1",
34
+ "version": "0.14.0-alpha.3",
35
35
  "entities": [
36
36
  {
37
37
  "id": "entity_pay",
@@ -49,7 +49,7 @@
49
49
  "contract": "axiom.server.v8",
50
50
  "id": "workflow-conformance",
51
51
  "name": "Workflow Conformance",
52
- "version": "0.14.0-alpha.1",
52
+ "version": "0.14.0-alpha.3",
53
53
  "entities": [
54
54
  {
55
55
  "id": "entity_pay",
@@ -21,7 +21,7 @@
21
21
  "contract": "axiom.server.v8",
22
22
  "id": "workflow-conformance",
23
23
  "name": "Workflow Conformance",
24
- "version": "0.14.0-alpha.1",
24
+ "version": "0.14.0-alpha.3",
25
25
  "entities": [
26
26
  {
27
27
  "id": "entity_pay",
@@ -27,7 +27,7 @@
27
27
  "contract": "axiom.server.v8",
28
28
  "id": "workflow-conformance",
29
29
  "name": "Workflow Conformance",
30
- "version": "0.14.0-alpha.1",
30
+ "version": "0.14.0-alpha.3",
31
31
  "entities": [
32
32
  {
33
33
  "id": "entity_pay",
@@ -20,7 +20,7 @@
20
20
  "contract": "axiom.server.v8",
21
21
  "id": "workflow-conformance",
22
22
  "name": "Workflow Conformance",
23
- "version": "0.14.0-alpha.1",
23
+ "version": "0.14.0-alpha.3",
24
24
  "entities": [
25
25
  {
26
26
  "id": "entity_pay",
@@ -20,7 +20,7 @@
20
20
  "contract": "axiom.server.v8",
21
21
  "id": "workflow-conformance",
22
22
  "name": "Workflow Conformance",
23
- "version": "0.14.0-alpha.1",
23
+ "version": "0.14.0-alpha.3",
24
24
  "entities": [
25
25
  {
26
26
  "id": "entity_pay",
@@ -31,7 +31,7 @@
31
31
  "contract": "axiom.server.v8",
32
32
  "id": "workflow-conformance",
33
33
  "name": "Workflow Conformance",
34
- "version": "0.14.0-alpha.1",
34
+ "version": "0.14.0-alpha.3",
35
35
  "entities": [
36
36
  {
37
37
  "id": "entity_pay",
@@ -30,7 +30,7 @@
30
30
  "contract": "axiom.server.v8",
31
31
  "id": "workflow-conformance",
32
32
  "name": "Workflow Conformance",
33
- "version": "0.14.0-alpha.1",
33
+ "version": "0.14.0-alpha.3",
34
34
  "entities": [
35
35
  {
36
36
  "id": "entity_pay",
@@ -28,7 +28,7 @@
28
28
  "contract": "axiom.server.v8",
29
29
  "id": "workflow-conformance",
30
30
  "name": "Workflow Conformance",
31
- "version": "0.14.0-alpha.1",
31
+ "version": "0.14.0-alpha.3",
32
32
  "entities": [
33
33
  {
34
34
  "id": "entity_pay",
@@ -27,7 +27,7 @@
27
27
  "contract": "axiom.server.v8",
28
28
  "id": "workflow-conformance",
29
29
  "name": "Workflow Conformance",
30
- "version": "0.14.0-alpha.1",
30
+ "version": "0.14.0-alpha.3",
31
31
  "entities": [
32
32
  {
33
33
  "id": "entity_pay",
@@ -17,7 +17,7 @@
17
17
  "contract": "axiom.server.v8",
18
18
  "id": "workflow-conformance",
19
19
  "name": "Workflow Conformance",
20
- "version": "0.14.0-alpha.1",
20
+ "version": "0.14.0-alpha.3",
21
21
  "entities": [
22
22
  {
23
23
  "id": "entity_pay",
@@ -17,7 +17,7 @@
17
17
  "contract": "axiom.server.v8",
18
18
  "id": "workflow-conformance",
19
19
  "name": "Workflow Conformance",
20
- "version": "0.14.0-alpha.1",
20
+ "version": "0.14.0-alpha.3",
21
21
  "entities": [
22
22
  {
23
23
  "id": "entity_pay",
@@ -23,7 +23,7 @@
23
23
  "contract": "axiom.server.v8",
24
24
  "id": "workflow-conformance",
25
25
  "name": "Workflow Conformance",
26
- "version": "0.14.0-alpha.1",
26
+ "version": "0.14.0-alpha.3",
27
27
  "entities": [
28
28
  {
29
29
  "id": "entity_pay",
@@ -10,15 +10,46 @@
10
10
  * The resulting {@link AuthorityCompatibilityKey} is what `createDurableWorkStore` stamps as
11
11
  * its `authorityKey`: an incompatible / older build then refuses to claim work a newer build
12
12
  * created (spec12 §44, §47).
13
+ *
14
+ * **spec14pt3 §5 G1 — one canonical semantic projection.** The slices this hashes are keyed
15
+ * by `core`'s {@link EXECUTABLE_KINDS} — the *same* list the graph-level `semanticFingerprint`
16
+ * iterates — through {@link SERVER_IR_EXECUTABLE_SLICES}. Phase 22 F3 was exactly the failure
17
+ * this now prevents: `WorkflowDef` had been added to `EXECUTABLE_KINDS` (so
18
+ * `semanticFingerprint(graph)` moved for a workflow semantic change) but this projection's
19
+ * hand-maintained slice list omitted `workflows`, so two authorities running different
20
+ * workflow semantics reported `compatible: true` and one silently advanced an in-flight
21
+ * instance under changed meaning. A `server` test now pins that every `EXECUTABLE_KINDS`
22
+ * member is projected here.
23
+ */
24
+ import { EXECUTABLE_KINDS, type AuthorityCompatibilityKey, type ServerIR } from './deps.js';
25
+ type ExecutableKind = (typeof EXECUTABLE_KINDS)[number];
26
+ /**
27
+ * The Server IR slice carrying each executable graph kind. Keyed by `core`'s
28
+ * {@link EXECUTABLE_KINDS} so this projection and the graph-level `semanticFingerprint`
29
+ * cannot disagree about what is executable (spec14pt3 §5, §6, §189).
30
+ *
31
+ * `since: 'v8'` marks a slice added after `axiom.server.v1..v7` were frozen: it contributes
32
+ * to the fingerprint **only when non-empty**, exactly as `core`'s `semanticProjection` skips
33
+ * an empty kind — so every pre-workflow (non-`WorkflowDef`) graph's authority fingerprint is
34
+ * byte-identical to what it was before this correction (spec14pt3 §35, §38, §103). The 13
35
+ * pre-v8 slices stay unconditionally present (an empty array included) to keep *their*
36
+ * frozen bytes unchanged.
13
37
  */
14
- import { type AuthorityCompatibilityKey, type ServerIR } from './deps.js';
38
+ declare const SERVER_IR_EXECUTABLE_SLICES: Record<ExecutableKind, {
39
+ field: keyof ServerIR;
40
+ shape: 'record' | 'list';
41
+ since?: 'v8';
42
+ }>;
15
43
  /**
16
- * The canonical executable-meaning projection of a Server IR: actions, constraints,
17
- * transition constraints, expression defs, integrations + operations, triggers, events,
18
- * subscriptions, storages, read policies, relationships, queries, plus the schema version.
19
- * Everything a distributed worker executes; nothing a rename touches.
44
+ * The canonical executable-meaning projection of a Server IR: everything a distributed
45
+ * worker executes — actions, constraints, transition constraints, expression defs,
46
+ * integrations + operations, triggers, events, subscriptions, storages, read policies,
47
+ * relationships, queries, **and workflows** — plus the schema version. Nothing a rename
48
+ * touches. Built by iterating {@link EXECUTABLE_KINDS} so a future primitive cannot be
49
+ * omitted (spec14pt3 F3 root cause).
20
50
  */
21
51
  export declare function serverIrSemanticProjection(ir: ServerIR): Record<string, unknown>;
52
+ export { SERVER_IR_EXECUTABLE_SLICES };
22
53
  export declare function serverIrSemanticFingerprint(ir: ServerIR): string;
23
54
  /** The full compatibility key for the authority executing this Server IR (spec12 §45). */
24
55
  export declare function serverIrCompatibilityKey(ir: ServerIR): AuthorityCompatibilityKey;
@@ -10,9 +10,19 @@
10
10
  * The resulting {@link AuthorityCompatibilityKey} is what `createDurableWorkStore` stamps as
11
11
  * its `authorityKey`: an incompatible / older build then refuses to claim work a newer build
12
12
  * created (spec12 §44, §47).
13
+ *
14
+ * **spec14pt3 §5 G1 — one canonical semantic projection.** The slices this hashes are keyed
15
+ * by `core`'s {@link EXECUTABLE_KINDS} — the *same* list the graph-level `semanticFingerprint`
16
+ * iterates — through {@link SERVER_IR_EXECUTABLE_SLICES}. Phase 22 F3 was exactly the failure
17
+ * this now prevents: `WorkflowDef` had been added to `EXECUTABLE_KINDS` (so
18
+ * `semanticFingerprint(graph)` moved for a workflow semantic change) but this projection's
19
+ * hand-maintained slice list omitted `workflows`, so two authorities running different
20
+ * workflow semantics reported `compatible: true` and one silently advanced an in-flight
21
+ * instance under changed meaning. A `server` test now pins that every `EXECUTABLE_KINDS`
22
+ * member is projected here.
13
23
  */
14
24
  import { createHash } from 'node:crypto';
15
- import { SEMANTIC_FINGERPRINT_VERSION, authorityCompatibilityKey, canonicalJSON, compatibilityKeyString, } from './deps.js';
25
+ import { EXECUTABLE_KINDS, SEMANTIC_FINGERPRINT_VERSION, authorityCompatibilityKey, canonicalJSON, canonicalWorkflowForFingerprint, compatibilityKeyString, } from './deps.js';
16
26
  const NON_SEMANTIC_KEYS = new Set(['name', 'description', 'label', 'metadata', 'axiom.authoring']);
17
27
  function stripNonSemantic(value) {
18
28
  if (Array.isArray(value))
@@ -28,41 +38,86 @@ function stripNonSemantic(value) {
28
38
  }
29
39
  return value;
30
40
  }
41
+ // Both are **total** over a hand-tampered `ServerIR` slice (spec14pt4 §31): a slice that is
42
+ // the wrong shape entirely (a string where an array is expected, say) projects to nothing
43
+ // rather than throwing. A structurally invalid workflow is then refused by the engine's
44
+ // admission validator with a structured `WorkflowIRError`; the compatibility key is never
45
+ // the place a malformed IR surfaces as a native error. Valid graphs always have
46
+ // array/object slices, so no valid fingerprint moves.
31
47
  function sortedRecord(record) {
32
- return Object.entries(record ?? {})
48
+ if (!record || typeof record !== 'object')
49
+ return [];
50
+ return Object.entries(record)
33
51
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
34
52
  .map(([, v]) => stripNonSemantic(v));
35
53
  }
36
54
  function sortedList(list) {
37
- return [...(list ?? [])]
38
- .sort((a, b) => (String(a.id) < String(b.id) ? -1 : String(a.id) > String(b.id) ? 1 : 0))
55
+ if (!Array.isArray(list))
56
+ return [];
57
+ return [...list]
58
+ .sort((a, b) => (String(a?.id) < String(b?.id) ? -1 : String(a?.id) > String(b?.id) ? 1 : 0))
39
59
  .map((entry) => stripNonSemantic(entry));
40
60
  }
41
61
  /**
42
- * The canonical executable-meaning projection of a Server IR: actions, constraints,
43
- * transition constraints, expression defs, integrations + operations, triggers, events,
44
- * subscriptions, storages, read policies, relationships, queries, plus the schema version.
45
- * Everything a distributed worker executes; nothing a rename touches.
62
+ * The Server IR slice carrying each executable graph kind. Keyed by `core`'s
63
+ * {@link EXECUTABLE_KINDS} so this projection and the graph-level `semanticFingerprint`
64
+ * cannot disagree about what is executable (spec14pt3 §5, §6, §189).
65
+ *
66
+ * `since: 'v8'` marks a slice added after `axiom.server.v1..v7` were frozen: it contributes
67
+ * to the fingerprint **only when non-empty**, exactly as `core`'s `semanticProjection` skips
68
+ * an empty kind — so every pre-workflow (non-`WorkflowDef`) graph's authority fingerprint is
69
+ * byte-identical to what it was before this correction (spec14pt3 §35, §38, §103). The 13
70
+ * pre-v8 slices stay unconditionally present (an empty array included) to keep *their*
71
+ * frozen bytes unchanged.
72
+ */
73
+ const SERVER_IR_EXECUTABLE_SLICES = {
74
+ action: { field: 'actions', shape: 'record' },
75
+ integration: { field: 'integrations', shape: 'list' },
76
+ 'integration-operation': { field: 'integrationOperations', shape: 'record' },
77
+ trigger: { field: 'triggers', shape: 'list' },
78
+ event: { field: 'events', shape: 'list' },
79
+ subscription: { field: 'subscriptions', shape: 'list' },
80
+ 'read-policy': { field: 'readPolicies', shape: 'list' },
81
+ query: { field: 'queries', shape: 'list' },
82
+ expression: { field: 'expressionDefs', shape: 'record' },
83
+ constraint: { field: 'constraints', shape: 'list' },
84
+ 'transition-constraint': { field: 'transitionConstraints', shape: 'list' },
85
+ storage: { field: 'storages', shape: 'list' },
86
+ relationship: { field: 'relationships', shape: 'list' },
87
+ workflow: { field: 'workflows', shape: 'list', since: 'v8' },
88
+ };
89
+ /**
90
+ * The canonical executable-meaning projection of a Server IR: everything a distributed
91
+ * worker executes — actions, constraints, transition constraints, expression defs,
92
+ * integrations + operations, triggers, events, subscriptions, storages, read policies,
93
+ * relationships, queries, **and workflows** — plus the schema version. Nothing a rename
94
+ * touches. Built by iterating {@link EXECUTABLE_KINDS} so a future primitive cannot be
95
+ * omitted (spec14pt3 F3 root cause).
46
96
  */
47
97
  export function serverIrSemanticProjection(ir) {
48
- return {
98
+ const projection = {
49
99
  fingerprintVersion: SEMANTIC_FINGERPRINT_VERSION,
50
100
  schemaVersion: ir.schemaVersion ?? 1,
51
- actions: sortedRecord(ir.actions),
52
- constraints: sortedList(ir.constraints),
53
- transitionConstraints: sortedList(ir.transitionConstraints),
54
- expressionDefs: sortedRecord(ir.expressionDefs),
55
- integrations: sortedList(ir.integrations),
56
- integrationOperations: sortedRecord(ir.integrationOperations),
57
- triggers: sortedList(ir.triggers),
58
- events: sortedList(ir.events),
59
- subscriptions: sortedList(ir.subscriptions),
60
- storages: sortedList(ir.storages),
61
- readPolicies: sortedList(ir.readPolicies),
62
- relationships: sortedList(ir.relationships),
63
- queries: sortedList(ir.queries),
64
101
  };
102
+ for (const kind of EXECUTABLE_KINDS) {
103
+ const slice = SERVER_IR_EXECUTABLE_SLICES[kind];
104
+ let raw = ir[slice.field];
105
+ // Workflows are canonicalized (authoring order of steps / inputs / bindings is not
106
+ // semantic) through the *same* core helper the graph-level fingerprint uses (spec14pt3
107
+ // §5 G1, §64), so the two projections cannot disagree about a workflow.
108
+ if (kind === 'workflow' && Array.isArray(raw)) {
109
+ raw = raw.map((w) => canonicalWorkflowForFingerprint(w));
110
+ }
111
+ const projected = slice.shape === 'record'
112
+ ? sortedRecord(raw)
113
+ : sortedList(raw);
114
+ if (slice.since === 'v8' && projected.length === 0)
115
+ continue;
116
+ projection[slice.field] = projected;
117
+ }
118
+ return projection;
65
119
  }
120
+ export { SERVER_IR_EXECUTABLE_SLICES };
66
121
  export function serverIrSemanticFingerprint(ir) {
67
122
  return createHash('sha256').update(canonicalJSON(serverIrSemanticProjection(ir))).digest('hex');
68
123
  }
package/dist/deps.d.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  * runtime explicit: it is the semantic engine and nothing else. No transport, no database
6
6
  * driver and no host API appears here.
7
7
  */
8
- export type { ActionDef, AnyNode, ApplicationIR, Authority, AuthorityCompatibilityKey, CompatibilityComparison, ConstraintDef, EntityDef, EventDef, Expression, FieldDef, FieldId, IntegrationDef, IntegrationOperationDef, LiteralValue, MigrationDef, MigrationOperation, MigrationOperationKind, NodeId, Operation, SchemaChangeClass, SchemaDiff, SchemaDiffEntry, SchemaProjection, QueryAggregate, QueryAggregateFunction, QueryDef, LiveCapability, LiveChange, LiveQueryDelta, MaterializedResult, QueryDependencySet, CommitChangeset, QueryNullsOrder, QueryPagination, QueryParameter, QueryProjection, QueryRelationshipUse, QuerySortDirection, QuerySortKey, ReadPolicyDef, RelationshipDef, RelationshipCardinality, ServerIR, StateDef, StorageDef, SubscriptionDef, SubscriptionBackpressurePolicy, SubscriptionLifecycleState, TransitionConstraintDef, TriggerDef, TriggerSpec, TypeRef, WorkflowDef, WorkflowStep, WorkflowActionStep, WorkflowWaitEventStep, WorkflowRetryPolicy, WorkflowInput, WorkflowBinding, WorkflowDuration, } from '@cynodia/axiom-core';
8
+ export type { ActionDef, AnyNode, ApplicationIR, Authority, AuthorityCompatibilityKey, CompatibilityComparison, ConstraintDef, EntityDef, EventDef, Expression, FieldDef, FieldId, IntegrationDef, IntegrationOperationDef, LiteralValue, MigrationDef, MigrationOperation, MigrationOperationKind, NodeId, Operation, SchemaChangeClass, SchemaDiff, SchemaDiffEntry, SchemaProjection, QueryAggregate, QueryAggregateFunction, QueryDef, LiveCapability, LiveChange, LiveQueryDelta, MaterializedResult, QueryDependencySet, CommitChangeset, QueryNullsOrder, QueryPagination, QueryParameter, QueryProjection, QueryRelationshipUse, QuerySortDirection, QuerySortKey, ReadPolicyDef, RelationshipDef, RelationshipCardinality, ServerIR, StateDef, StorageDef, SubscriptionDef, SubscriptionBackpressurePolicy, SubscriptionLifecycleState, TransitionConstraintDef, TriggerDef, TriggerSpec, TypeRef, WorkflowDef, WorkflowStep, WorkflowActionStep, WorkflowWaitEventStep, WorkflowRetryPolicy, WorkflowInput, WorkflowBinding, WorkflowDuration, WorkflowStructuralProblem, } from '@cynodia/axiom-core';
9
9
  export type { ActionOutcome, ActionResult, EffectIntentRecord, RuntimeDiagnostic, RuntimeDiagnosticCode, } from '@cynodia/axiom-runtime';
10
10
  export { PRINCIPAL, RUNTIME_DIAGNOSTIC_CODES, SERVER_IR_CONTRACT } from './runtime-deps.js';
11
- export { MIGRATION_OLD_SCOPE, MIGRATION_OPERATION_KINDS, diffSchema, canonicalJSON, walkExpression, applyDelta, commitAffectsQuery, diffResults, queryDependencies, queryLiveCapability, queryStateReferences, rowKey, LIVE_QUERY_NONDETERMINISTIC_BUILTINS, fingerprintProjection, migrationCoversDiff, migrationExpressions, migrationOperationEntityId, migrationOperationExpressions, migrationOperationFieldIds, migrationOperationReadFieldIds, migrationPath, schemaFingerprint, schemaProjection, semanticFingerprint, semanticProjection, authorityCompatibilityKey, compatibilityKeyString, compareAuthorityCompatibility, SEMANTIC_FINGERPRINT_VERSION, sortMigrations, WORKFLOW_EVENT_SCOPE, WORKFLOW_PRINCIPAL_SCOPE, WORKFLOW_STEP_TYPES, workflowStepById, workflowStepSuccessors, workflowStepExpressions, workflowActionIds, workflowEventIds, workflowReachableSteps, workflowHasCycle, workflowExpressions, } from '@cynodia/axiom-core';
11
+ export { MIGRATION_OLD_SCOPE, MIGRATION_OPERATION_KINDS, diffSchema, canonicalJSON, walkExpression, applyDelta, commitAffectsQuery, diffResults, queryDependencies, queryLiveCapability, queryStateReferences, rowKey, LIVE_QUERY_NONDETERMINISTIC_BUILTINS, fingerprintProjection, migrationCoversDiff, migrationExpressions, migrationOperationEntityId, migrationOperationExpressions, migrationOperationFieldIds, migrationOperationReadFieldIds, migrationPath, schemaFingerprint, schemaProjection, semanticFingerprint, semanticProjection, authorityCompatibilityKey, compatibilityKeyString, compareAuthorityCompatibility, EXECUTABLE_KINDS, SEMANTIC_FINGERPRINT_VERSION, sortMigrations, WORKFLOW_EVENT_SCOPE, WORKFLOW_PRINCIPAL_SCOPE, WORKFLOW_STEP_TYPES, workflowStepById, workflowStepSuccessors, workflowStepExpressions, workflowActionIds, workflowEventIds, workflowReachableSteps, workflowHasCycle, workflowExpressions, isWorkflowStep, workflowStructuralProblems, canonicalWorkflowForFingerprint, } from '@cynodia/axiom-core';
12
12
  export { BLOB_CHECKSUM_FIELD, BLOB_FILENAME_FIELD, BLOB_KEY_FIELD, BLOB_MEDIA_TYPE_FIELD, BLOB_SIZE_FIELD, DEFAULT_QUERY_MAX_PAGE_SIZE, DEFAULT_QUERY_PAGE_SIZE, DEFAULT_RELATIONSHIP_PAGE_SIZE, queryDefaultPageSize, queryExpressions, queryIsAggregate, queryIsGrouped, queryMaxPageSize, queryPaginationStrategy, queryRowEntityId, relationshipIsToOne, relationshipTargetEntityId, sortKeyDirection, sortKeyNulls, subscriptionAutoStart, subscriptionBackpressure, subscriptionDeduplicationWindow, subscriptionFailurePolicy, subscriptionIsRequired, subscriptionMaxAttempts, subscriptionQueueLimit, subscriptionReconnectPolicy, subscriptionSourceName, } from '@cynodia/axiom-core';
13
13
  //# sourceMappingURL=deps.d.ts.map
package/dist/deps.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { PRINCIPAL, RUNTIME_DIAGNOSTIC_CODES, SERVER_IR_CONTRACT } from './runtime-deps.js';
2
- export { MIGRATION_OLD_SCOPE, MIGRATION_OPERATION_KINDS, diffSchema, canonicalJSON, walkExpression, applyDelta, commitAffectsQuery, diffResults, queryDependencies, queryLiveCapability, queryStateReferences, rowKey, LIVE_QUERY_NONDETERMINISTIC_BUILTINS, fingerprintProjection, migrationCoversDiff, migrationExpressions, migrationOperationEntityId, migrationOperationExpressions, migrationOperationFieldIds, migrationOperationReadFieldIds, migrationPath, schemaFingerprint, schemaProjection, semanticFingerprint, semanticProjection, authorityCompatibilityKey, compatibilityKeyString, compareAuthorityCompatibility, SEMANTIC_FINGERPRINT_VERSION, sortMigrations, WORKFLOW_EVENT_SCOPE, WORKFLOW_PRINCIPAL_SCOPE, WORKFLOW_STEP_TYPES, workflowStepById, workflowStepSuccessors, workflowStepExpressions, workflowActionIds, workflowEventIds, workflowReachableSteps, workflowHasCycle, workflowExpressions, } from '@cynodia/axiom-core';
2
+ export { MIGRATION_OLD_SCOPE, MIGRATION_OPERATION_KINDS, diffSchema, canonicalJSON, walkExpression, applyDelta, commitAffectsQuery, diffResults, queryDependencies, queryLiveCapability, queryStateReferences, rowKey, LIVE_QUERY_NONDETERMINISTIC_BUILTINS, fingerprintProjection, migrationCoversDiff, migrationExpressions, migrationOperationEntityId, migrationOperationExpressions, migrationOperationFieldIds, migrationOperationReadFieldIds, migrationPath, schemaFingerprint, schemaProjection, semanticFingerprint, semanticProjection, authorityCompatibilityKey, compatibilityKeyString, compareAuthorityCompatibility, EXECUTABLE_KINDS, SEMANTIC_FINGERPRINT_VERSION, sortMigrations, WORKFLOW_EVENT_SCOPE, WORKFLOW_PRINCIPAL_SCOPE, WORKFLOW_STEP_TYPES, workflowStepById, workflowStepSuccessors, workflowStepExpressions, workflowActionIds, workflowEventIds, workflowReachableSteps, workflowHasCycle, workflowExpressions, isWorkflowStep, workflowStructuralProblems, canonicalWorkflowForFingerprint, } from '@cynodia/axiom-core';
3
3
  export { BLOB_CHECKSUM_FIELD, BLOB_FILENAME_FIELD, BLOB_KEY_FIELD, BLOB_MEDIA_TYPE_FIELD, BLOB_SIZE_FIELD, DEFAULT_QUERY_MAX_PAGE_SIZE, DEFAULT_QUERY_PAGE_SIZE, DEFAULT_RELATIONSHIP_PAGE_SIZE, queryDefaultPageSize, queryExpressions, queryIsAggregate, queryIsGrouped, queryMaxPageSize, queryPaginationStrategy, queryRowEntityId, relationshipIsToOne, relationshipTargetEntityId, sortKeyDirection, sortKeyNulls, subscriptionAutoStart, subscriptionBackpressure, subscriptionDeduplicationWindow, subscriptionFailurePolicy, subscriptionIsRequired, subscriptionMaxAttempts, subscriptionQueueLimit, subscriptionReconnectPolicy, subscriptionSourceName, } from '@cynodia/axiom-core';
package/dist/server.js CHANGED
@@ -1103,10 +1103,26 @@ export function createAxiomServer(options) {
1103
1103
  return response;
1104
1104
  }
1105
1105
  // ---- Durable workflows (spec14) --------------------------------------------------
1106
- const workflowStore = (ir.workflows?.length ?? 0) > 0 ? options.workflowStore ?? createMemoryWorkflowStore() : undefined;
1106
+ // spec14pt4 §29, §30 — a hand-tampered IR may carry `workflows` as the wrong shape
1107
+ // entirely (a string, an object, a one-element array holding `null`). Route anything
1108
+ // present-but-not-an-empty-array into the engine's total admission validator so it fails
1109
+ // closed with a structured `WorkflowIRError` rather than being silently ignored (`{}` has
1110
+ // no `.length`) or reaching a native `TypeError`.
1111
+ // Anything other than absent-or-empty-array is routed into the engine's total admission
1112
+ // validator (`null`, a string, an object, a non-empty array) so it fails closed rather
1113
+ // than being silently ignored (`{}` / `null` have no useful `.length`).
1114
+ const declaredWorkflows = ir.workflows;
1115
+ const workflowsDeclared = declaredWorkflows !== undefined &&
1116
+ (!Array.isArray(declaredWorkflows) || declaredWorkflows.length > 0);
1117
+ const workflowStore = workflowsDeclared
1118
+ ? options.workflowStore ?? createMemoryWorkflowStore()
1119
+ : undefined;
1107
1120
  const workflowEngine = workflowStore
1108
1121
  ? createWorkflowEngine({
1109
- workflows: ir.workflows ?? [],
1122
+ // Pass the declared value **through unchanged** (never `?? []`) so a tampered
1123
+ // `null` / string / object reaches the total admission validator instead of being
1124
+ // coalesced to an empty list (spec14pt4 §29, §63).
1125
+ workflows: declaredWorkflows,
1110
1126
  store: workflowStore,
1111
1127
  compatibilityFingerprint: compatibilityKeyStr,
1112
1128
  instanceId,
@@ -73,6 +73,14 @@ export interface WorkflowInspection {
73
73
  instanceRevision: number;
74
74
  failure?: Record<string, unknown>;
75
75
  output?: Record<string, unknown>;
76
+ /**
77
+ * Whether *this* authority build may semantically advance the instance (spec14pt3 §26,
78
+ * §164). `false` when the instance was created / last advanced under incompatible
79
+ * `WorkflowDef` semantics — read-only inspection stays available so an operator can see
80
+ * *why* it is not progressing rather than finding it silently stuck.
81
+ */
82
+ compatible: boolean;
83
+ incompatibleReason?: 'incompatible-build';
76
84
  }
77
85
  export interface WorkflowEngine {
78
86
  start(): Promise<void>;
@@ -107,5 +115,22 @@ export interface WorkflowEngine {
107
115
  /** Advance one instance now (used by the poll loop and directly after a local start). */
108
116
  advance(instanceId: string): Promise<void>;
109
117
  }
118
+ /**
119
+ * Structurally invalid workflow IR reached the authoritative runtime (spec14pt3 F2 §44-§49).
120
+ * `createWorkflowEngine` fails closed with this rather than letting a hand-tampered / stored /
121
+ * transported `ServerIR` reach a native `TypeError`, a silently skipped step or a wedged
122
+ * instance. `problems` is a structured list; nothing implementation-internal is exposed.
123
+ */
124
+ export declare class WorkflowIRError extends Error {
125
+ readonly code: "WORKFLOW_INVALID_IR";
126
+ readonly problems: ReadonlyArray<{
127
+ code: string;
128
+ message: string;
129
+ }>;
130
+ constructor(problems: ReadonlyArray<{
131
+ code: string;
132
+ message: string;
133
+ }>);
134
+ }
110
135
  export declare function createWorkflowEngine(options: WorkflowEngineOptions): WorkflowEngine;
111
136
  //# sourceMappingURL=workflows.d.ts.map
package/dist/workflows.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * PRINCIPAL).
13
13
  */
14
14
  import { createHash, randomUUID } from 'node:crypto';
15
- import { WORKFLOW_EVENT_SCOPE, WORKFLOW_PRINCIPAL_SCOPE, workflowStepById, } from './deps.js';
15
+ import { WORKFLOW_EVENT_SCOPE, WORKFLOW_PRINCIPAL_SCOPE, isWorkflowStep, workflowStepById, workflowStructuralProblems, } from './deps.js';
16
16
  import { workflowStartKey } from './workflow-store.js';
17
17
  // --------------------------------------------------------------- pure expression eval
18
18
  /**
@@ -132,6 +132,21 @@ function truthy(v) {
132
132
  function canonical(v) {
133
133
  return JSON.stringify(v ?? null);
134
134
  }
135
+ /**
136
+ * Structurally invalid workflow IR reached the authoritative runtime (spec14pt3 F2 §44-§49).
137
+ * `createWorkflowEngine` fails closed with this rather than letting a hand-tampered / stored /
138
+ * transported `ServerIR` reach a native `TypeError`, a silently skipped step or a wedged
139
+ * instance. `problems` is a structured list; nothing implementation-internal is exposed.
140
+ */
141
+ export class WorkflowIRError extends Error {
142
+ code = 'WORKFLOW_INVALID_IR';
143
+ problems;
144
+ constructor(problems) {
145
+ super(`workflow IR is structurally invalid: ${problems.map((p) => p.message).join('; ')}`);
146
+ this.name = 'WorkflowIRError';
147
+ this.problems = problems;
148
+ }
149
+ }
135
150
  const WF_DIAGNOSTIC = {
136
151
  UNKNOWN_WORKFLOW: 'WORKFLOW_NOT_FOUND',
137
152
  ARGUMENT_MISMATCH: 'WORKFLOW_ARGUMENT_MISMATCH',
@@ -144,7 +159,27 @@ export function createWorkflowEngine(options) {
144
159
  const pollMs = options.pollMs ?? 250;
145
160
  const recoverBatch = options.recoverBatch ?? 64;
146
161
  const store = options.store;
147
- const byId = new Map(options.workflows.map((w) => [String(w.id), w]));
162
+ // spec14pt3 F2 / spec14pt4 §11-§19, §25 — the single authoritative admission validator.
163
+ // The authoritative runtime is handed a `ServerIR` it did not necessarily compile itself;
164
+ // a structurally invalid or referentially inconsistent `WorkflowDef` — a malformed
165
+ // container (`workflows` / `steps` not an array), an unknown step kind, a dangling edge, a
166
+ // `bind` to an undeclared binding, a `producedBy` to a non-step, an expression `ref` to an
167
+ // id that is not in scope — must fail closed **here**, before any instance can be started
168
+ // or advanced, rather than surfacing as a native error or a permanently `running` wedge
169
+ // deep in execution (spec14pt4 §3, §26). Totally guarded: `options.workflows` itself may
170
+ // be the wrong shape.
171
+ if (!Array.isArray(options.workflows)) {
172
+ throw new WorkflowIRError([
173
+ { code: 'WORKFLOW_INVALID_IR', message: 'workflows is not an array' },
174
+ ]);
175
+ }
176
+ const irProblems = options.workflows.flatMap((w) => workflowStructuralProblems(w));
177
+ if (irProblems.length > 0) {
178
+ throw new WorkflowIRError(irProblems);
179
+ }
180
+ // Every entry has now passed the total structural + reference check above, so it is a
181
+ // plain object with a string id and a valid step array.
182
+ const byId = new Map(options.workflows.map((w) => [String(w?.id), w]));
148
183
  let pollTimer;
149
184
  let running = false;
150
185
  const advancing = new Set(); // local re-entrancy guard only
@@ -166,6 +201,21 @@ export function createWorkflowEngine(options) {
166
201
  }
167
202
  }
168
203
  // ---- scope + step evaluation -------------------------------------------------
204
+ /**
205
+ * spec14pt3 F3 — whether this authority build may interpret this durable instance. The
206
+ * instance durably records the `AuthorityCompatibilityKey` string it was created under
207
+ * (`{ serverContract, schemaFingerprint, semanticFingerprint }`, and `semanticFingerprint`
208
+ * now covers `WorkflowDef` executable meaning). A build whose key differs — a changed
209
+ * action target, event id, branch predicate, timer duration, retry policy or control-flow
210
+ * edge — must fail closed rather than advance the instance under changed semantics. A
211
+ * missing / malformed stored fingerprint is also incompatible (never "missing ⇒
212
+ * compatible", spec14pt3 §175).
213
+ */
214
+ function isCompatible(record) {
215
+ return (typeof record.compatibilityFingerprint === 'string' &&
216
+ record.compatibilityFingerprint.length > 0 &&
217
+ record.compatibilityFingerprint === options.compatibilityFingerprint);
218
+ }
169
219
  function scopeFor(record, eventPayload) {
170
220
  return {
171
221
  ...record.inputs,
@@ -210,17 +260,80 @@ export function createWorkflowEngine(options) {
210
260
  return;
211
261
  if (record.status === 'completed' || record.status === 'failed' || record.status === 'cancelled')
212
262
  return;
213
- if (record.compatibilityFingerprint !== options.compatibilityFingerprint) {
214
- // A semantically incompatible build must not advance this instance (spec14 §116).
263
+ if (!isCompatible(record)) {
264
+ // spec14pt3 F3 — fail closed *before* any semantic step: no ActionDef invoke,
265
+ // no event/timer/branch transition, no binding write, no `instanceRevision`
266
+ // advance. The instance is left exactly as it stands for a compatible authority
267
+ // to resume; incompatibility is an execution-environment problem, never an
268
+ // automatic `failed` / `cancelled` (spec14pt3 §23, §76, §156).
215
269
  return;
216
270
  }
217
271
  const workflow = byId.get(record.workflowId);
218
272
  if (!workflow)
219
273
  return;
220
274
  const step = workflowStepById(workflow, record.currentStepId);
221
- if (!step)
275
+ // spec14pt3 F2: admission validation already rejected a structurally invalid
276
+ // workflow, so this only fires if the in-memory definition was mutated after the
277
+ // engine started. Fail the instance with a structured reason rather than looping
278
+ // the poll forever (a silent permanent wedge is explicitly forbidden, §46).
279
+ if (!step || !isWorkflowStep(step)) {
280
+ await store
281
+ .transition({
282
+ instanceId: record.instanceId,
283
+ expectedRevision: record.instanceRevision,
284
+ fence,
285
+ next: {
286
+ status: 'failed',
287
+ currentStepId: record.currentStepId,
288
+ activationId: record.activationId,
289
+ attempt: 0,
290
+ pendingAction: null,
291
+ nextEligibleAt: null,
292
+ failure: { reason: 'workflow-invalid-step', stepId: record.currentStepId },
293
+ history: { kind: 'failed', stepId: record.currentStepId, detail: { reason: 'workflow-invalid-step' } },
294
+ },
295
+ })
296
+ .catch(() => { });
222
297
  return;
223
- const progressed = await runStep({ workflow, record, step, fence });
298
+ }
299
+ // spec14pt4 §27, §28 — defense in depth. Admission validation makes this
300
+ // unreachable for an admitted workflow, but if a corrupt durable instance or a
301
+ // step object mutated post-construction somehow reaches here and a transition
302
+ // (e.g. a branch predicate over a bad ref) throws, fail the instance with a
303
+ // structured reason. Never let the poll loop swallow the throw and retry forever
304
+ // (`pollOnce` catches, so an unhandled throw here IS a permanent `running` wedge).
305
+ let progressed;
306
+ try {
307
+ progressed = await runStep({ workflow, record, step, fence });
308
+ }
309
+ catch (error) {
310
+ await store
311
+ .transition({
312
+ instanceId: record.instanceId,
313
+ expectedRevision: record.instanceRevision,
314
+ fence,
315
+ next: {
316
+ status: 'failed',
317
+ currentStepId: record.currentStepId,
318
+ activationId: record.activationId,
319
+ attempt: 0,
320
+ pendingAction: null,
321
+ nextEligibleAt: null,
322
+ failure: {
323
+ reason: 'workflow-step-execution-error',
324
+ stepId: record.currentStepId,
325
+ detail: error instanceof Error ? error.message : String(error),
326
+ },
327
+ history: {
328
+ kind: 'failed',
329
+ stepId: record.currentStepId,
330
+ detail: { reason: 'workflow-step-execution-error' },
331
+ },
332
+ },
333
+ })
334
+ .catch(() => { });
335
+ return;
336
+ }
224
337
  if (!progressed)
225
338
  return;
226
339
  }
@@ -369,6 +482,11 @@ export function createWorkflowEngine(options) {
369
482
  case 'action': {
370
483
  return runActionStep({ ...args, step });
371
484
  }
485
+ default:
486
+ // Unreachable: `advance` fails the instance before calling `runStep` for a step
487
+ // that is not one of the six kinds, and admission validation rejects such IR
488
+ // outright. Present so the switch is total and never returns `undefined`.
489
+ return false;
372
490
  }
373
491
  }
374
492
  async function runActionStep(args) {
@@ -499,7 +617,10 @@ export function createWorkflowEngine(options) {
499
617
  if (eventSeq <= wait.sinceEventSeq)
500
618
  continue;
501
619
  const loaded = await store.load(wait.instanceId);
502
- if (!loaded || loaded.compatibilityFingerprint !== options.compatibilityFingerprint)
620
+ // spec14pt3 F3 — an incompatible build may journal the canonical event as
621
+ // infrastructure but must not apply its (changed) `where` / `bind` semantics to this
622
+ // instance. A compatible authority reconciles it later from the durable journal.
623
+ if (!loaded || !isCompatible(loaded))
503
624
  continue;
504
625
  const workflow = byId.get(loaded.workflowId ?? '');
505
626
  if (!workflow)
@@ -597,7 +718,7 @@ export function createWorkflowEngine(options) {
597
718
  compatibilityFingerprint: options.compatibilityFingerprint,
598
719
  };
599
720
  const instanceId = `wf_${createHash('sha256').update(workflowStartKey(start)).digest('hex').slice(0, 24)}_${randomUUID().slice(0, 8)}`;
600
- const { instance } = await store.createIdempotent(start, () => ({
721
+ const { instance, created } = await store.createIdempotent(start, () => ({
601
722
  instanceId,
602
723
  workflowId: request.workflowId,
603
724
  compatibilityFingerprint: options.compatibilityFingerprint,
@@ -606,6 +727,18 @@ export function createWorkflowEngine(options) {
606
727
  inputs,
607
728
  entryStepId: String(workflow.entry),
608
729
  }));
730
+ // spec14pt3 §161, §162 — a repeated start identity that resolves to an *existing*
731
+ // instance created under incompatible workflow semantics must NOT silently reuse it and
732
+ // continue it under this build. Return it, plainly labelled incompatible; never
733
+ // reinterpret it. (A brand-new instance created just now always matches this build.)
734
+ if (!created && !isCompatible(instance)) {
735
+ return {
736
+ error: {
737
+ code: WF_DIAGNOSTIC.INCOMPATIBLE,
738
+ message: `Workflow instance ${instance.instanceId} was created under workflow semantics incompatible with this authority build`,
739
+ },
740
+ };
741
+ }
609
742
  // Drive it forward immediately (local fast path); the poll loop is the durable backstop.
610
743
  await advance(instance.instanceId).catch(() => { });
611
744
  const fresh = (await store.load(instance.instanceId)) ?? instance;
@@ -620,6 +753,18 @@ export function createWorkflowEngine(options) {
620
753
  if (record.status === 'completed' || record.status === 'failed') {
621
754
  return { ok: true, status: record.status }; // idempotent; a terminal workflow is not resurrected
622
755
  }
756
+ // spec14pt3 §163 — cancellation writes a durable `cancelled` transition on this
757
+ // instance, so it is compatibility-gated like every other transition: an incompatible
758
+ // build must not transition an instance whose workflow meaning it does not share. The
759
+ // instance is left for a compatible authority to cancel or complete.
760
+ if (!isCompatible(record)) {
761
+ return {
762
+ error: {
763
+ code: WF_DIAGNOSTIC.INCOMPATIBLE,
764
+ message: `Workflow instance ${instanceId} is incompatible with this authority build; it cannot be cancelled here`,
765
+ },
766
+ };
767
+ }
623
768
  const result = await withOwnership(instanceId, async (fence) => {
624
769
  const fresh = await store.load(instanceId);
625
770
  if (!fresh || fresh.status === 'cancelled' || fresh.status === 'completed' || fresh.status === 'failed') {
@@ -663,6 +808,8 @@ export function createWorkflowEngine(options) {
663
808
  instanceRevision: record.instanceRevision,
664
809
  ...(record.failure ? { failure: record.failure } : {}),
665
810
  ...(record.output ? { output: record.output } : {}),
811
+ compatible: isCompatible(record),
812
+ ...(isCompatible(record) ? {} : { incompatibleReason: 'incompatible-build' }),
666
813
  };
667
814
  }
668
815
  async function pollOnce() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom-server",
3
- "version": "0.14.0-alpha.1",
3
+ "version": "0.14.0-alpha.3",
4
4
  "description": "Authoritative server runtime that executes an Axiom Server IR.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",
@@ -37,8 +37,8 @@
37
37
  "./schema/*": "./schema/*"
38
38
  },
39
39
  "dependencies": {
40
- "@cynodia/axiom-core": "0.14.0-alpha.1",
41
- "@cynodia/axiom-runtime": "0.14.0-alpha.1"
40
+ "@cynodia/axiom-core": "0.14.0-alpha.3",
41
+ "@cynodia/axiom-runtime": "0.14.0-alpha.3"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc -b tsconfig.json tsconfig.test.json",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom semantic protocol v1",
5
5
  "description": "What a client may send an authority and what it may receive back. A client requests semantic operations — \"invoke action X with arguments Y\" — never a mutation program. Nothing here mentions a transport.",
6
6
  "contract": "axiom.protocol.v1",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "oneOf": [
9
9
  {
10
10
  "$ref": "#/$defs/ServerRequest"
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v1",
5
5
  "description": "The normative structure of an `axiom.server.v1` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v1",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v2",
5
5
  "description": "The normative structure of an `axiom.server.v2` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v2",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v3",
5
5
  "description": "The normative structure of an `axiom.server.v3` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v3",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v4",
5
5
  "description": "The normative structure of an `axiom.server.v4` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v4",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v5",
5
5
  "description": "The normative structure of an `axiom.server.v5` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v5",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v6",
5
5
  "description": "The normative structure of an `axiom.server.v6` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v6",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v7",
5
5
  "description": "The normative structure of an `axiom.server.v7` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v7",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",
@@ -4,7 +4,7 @@
4
4
  "title": "Axiom Server IR v8",
5
5
  "description": "The normative structure of an `axiom.server.v8` document. Structure only: what a conforming runtime must *do* with it is the semantic contract, and the conformance fixtures are the executable statement of that.",
6
6
  "contract": "axiom.server.v8",
7
- "release": "0.14.0-alpha.1",
7
+ "release": "0.14.0-alpha.3",
8
8
  "type": "object",
9
9
  "required": [
10
10
  "contract",