@cynodia/axiom-server 0.14.0-alpha.1 → 0.14.0-alpha.2
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/conformance/live/aggregate-reset.json +1 -1
- package/conformance/live/dependency-commit-no-change.json +1 -1
- package/conformance/live/f2-state-ref-refused.json +1 -1
- package/conformance/live/initial-result.json +1 -1
- package/conformance/live/insert-on-filter-entry.json +1 -1
- package/conformance/live/limit-boundary.json +1 -1
- package/conformance/live/manifest.json +1 -1
- package/conformance/live/not-live-capable.json +1 -1
- package/conformance/live/provider-only-sequence.json +1 -1
- package/conformance/live/provider-record-remove.json +1 -1
- package/conformance/live/remove-on-filter-exit.json +1 -1
- package/conformance/live/update-with-move.json +1 -1
- package/conformance/live/update-without-move.json +1 -1
- package/conformance/workflow/manifest.json +1 -1
- package/conformance/workflow/workflow-action-on-error.json +1 -1
- package/conformance/workflow/workflow-action-retry.json +1 -1
- package/conformance/workflow/workflow-action-success.json +1 -1
- package/conformance/workflow/workflow-action-terminal-failure.json +1 -1
- package/conformance/workflow/workflow-branch-false.json +1 -1
- package/conformance/workflow/workflow-branch-true.json +1 -1
- package/conformance/workflow/workflow-cancel-waiting.json +1 -1
- package/conformance/workflow/workflow-event-match.json +1 -1
- package/conformance/workflow/workflow-event-nonmatch.json +1 -1
- package/conformance/workflow/workflow-event-timeout.json +1 -1
- package/conformance/workflow/workflow-start-complete.json +1 -1
- package/conformance/workflow/workflow-start-idempotency.json +1 -1
- package/conformance/workflow/workflow-timer.json +1 -1
- package/dist/authority-identity.d.ts +36 -5
- package/dist/authority-identity.js +64 -19
- package/dist/deps.d.ts +2 -2
- package/dist/deps.js +1 -1
- package/dist/workflows.d.ts +25 -0
- package/dist/workflows.js +105 -6
- package/package.json +3 -3
- package/schema/protocol.v1.schema.json +1 -1
- package/schema/server-ir.v1.schema.json +1 -1
- package/schema/server-ir.v2.schema.json +1 -1
- package/schema/server-ir.v3.schema.json +1 -1
- package/schema/server-ir.v4.schema.json +1 -1
- package/schema/server-ir.v5.schema.json +1 -1
- package/schema/server-ir.v6.schema.json +1 -1
- package/schema/server-ir.v7.schema.json +1 -1
- package/schema/server-ir.v8.schema.json +1 -1
|
@@ -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.
|
|
5
|
+
"release": "0.14.0-alpha.2",
|
|
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
|
{
|
|
@@ -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.
|
|
5
|
+
"release": "0.14.0-alpha.2",
|
|
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
|
{
|
|
@@ -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
|
-
|
|
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:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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))
|
|
@@ -39,30 +49,65 @@ function sortedList(list) {
|
|
|
39
49
|
.map((entry) => stripNonSemantic(entry));
|
|
40
50
|
}
|
|
41
51
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
52
|
+
* The Server IR slice carrying each executable graph kind. Keyed by `core`'s
|
|
53
|
+
* {@link EXECUTABLE_KINDS} so this projection and the graph-level `semanticFingerprint`
|
|
54
|
+
* cannot disagree about what is executable (spec14pt3 §5, §6, §189).
|
|
55
|
+
*
|
|
56
|
+
* `since: 'v8'` marks a slice added after `axiom.server.v1..v7` were frozen: it contributes
|
|
57
|
+
* to the fingerprint **only when non-empty**, exactly as `core`'s `semanticProjection` skips
|
|
58
|
+
* an empty kind — so every pre-workflow (non-`WorkflowDef`) graph's authority fingerprint is
|
|
59
|
+
* byte-identical to what it was before this correction (spec14pt3 §35, §38, §103). The 13
|
|
60
|
+
* pre-v8 slices stay unconditionally present (an empty array included) to keep *their*
|
|
61
|
+
* frozen bytes unchanged.
|
|
62
|
+
*/
|
|
63
|
+
const SERVER_IR_EXECUTABLE_SLICES = {
|
|
64
|
+
action: { field: 'actions', shape: 'record' },
|
|
65
|
+
integration: { field: 'integrations', shape: 'list' },
|
|
66
|
+
'integration-operation': { field: 'integrationOperations', shape: 'record' },
|
|
67
|
+
trigger: { field: 'triggers', shape: 'list' },
|
|
68
|
+
event: { field: 'events', shape: 'list' },
|
|
69
|
+
subscription: { field: 'subscriptions', shape: 'list' },
|
|
70
|
+
'read-policy': { field: 'readPolicies', shape: 'list' },
|
|
71
|
+
query: { field: 'queries', shape: 'list' },
|
|
72
|
+
expression: { field: 'expressionDefs', shape: 'record' },
|
|
73
|
+
constraint: { field: 'constraints', shape: 'list' },
|
|
74
|
+
'transition-constraint': { field: 'transitionConstraints', shape: 'list' },
|
|
75
|
+
storage: { field: 'storages', shape: 'list' },
|
|
76
|
+
relationship: { field: 'relationships', shape: 'list' },
|
|
77
|
+
workflow: { field: 'workflows', shape: 'list', since: 'v8' },
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The canonical executable-meaning projection of a Server IR: everything a distributed
|
|
81
|
+
* worker executes — actions, constraints, transition constraints, expression defs,
|
|
82
|
+
* integrations + operations, triggers, events, subscriptions, storages, read policies,
|
|
83
|
+
* relationships, queries, **and workflows** — plus the schema version. Nothing a rename
|
|
84
|
+
* touches. Built by iterating {@link EXECUTABLE_KINDS} so a future primitive cannot be
|
|
85
|
+
* omitted (spec14pt3 F3 root cause).
|
|
46
86
|
*/
|
|
47
87
|
export function serverIrSemanticProjection(ir) {
|
|
48
|
-
|
|
88
|
+
const projection = {
|
|
49
89
|
fingerprintVersion: SEMANTIC_FINGERPRINT_VERSION,
|
|
50
90
|
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
91
|
};
|
|
92
|
+
for (const kind of EXECUTABLE_KINDS) {
|
|
93
|
+
const slice = SERVER_IR_EXECUTABLE_SLICES[kind];
|
|
94
|
+
let raw = ir[slice.field];
|
|
95
|
+
// Workflows are canonicalized (authoring order of steps / inputs / bindings is not
|
|
96
|
+
// semantic) through the *same* core helper the graph-level fingerprint uses (spec14pt3
|
|
97
|
+
// §5 G1, §64), so the two projections cannot disagree about a workflow.
|
|
98
|
+
if (kind === 'workflow' && Array.isArray(raw)) {
|
|
99
|
+
raw = raw.map((w) => canonicalWorkflowForFingerprint(w));
|
|
100
|
+
}
|
|
101
|
+
const projected = slice.shape === 'record'
|
|
102
|
+
? sortedRecord(raw)
|
|
103
|
+
: sortedList(raw);
|
|
104
|
+
if (slice.since === 'v8' && projected.length === 0)
|
|
105
|
+
continue;
|
|
106
|
+
projection[slice.field] = projected;
|
|
107
|
+
}
|
|
108
|
+
return projection;
|
|
65
109
|
}
|
|
110
|
+
export { SERVER_IR_EXECUTABLE_SLICES };
|
|
66
111
|
export function serverIrSemanticFingerprint(ir) {
|
|
67
112
|
return createHash('sha256').update(canonicalJSON(serverIrSemanticProjection(ir))).digest('hex');
|
|
68
113
|
}
|
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/workflows.d.ts
CHANGED
|
@@ -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,6 +159,15 @@ export function createWorkflowEngine(options) {
|
|
|
144
159
|
const pollMs = options.pollMs ?? 250;
|
|
145
160
|
const recoverBatch = options.recoverBatch ?? 64;
|
|
146
161
|
const store = options.store;
|
|
162
|
+
// spec14pt3 F2 — admission validation. The authoritative runtime is handed a `ServerIR`
|
|
163
|
+
// it did not necessarily compile itself; a structurally invalid `WorkflowDef` (unknown
|
|
164
|
+
// step kind, dangling edge, malformed timer/terminal, missing entry) must fail closed
|
|
165
|
+
// *here*, before any instance can be started or advanced, rather than surfacing as a
|
|
166
|
+
// native error or a permanently stuck instance deep in execution (spec14pt3 §46, §48).
|
|
167
|
+
const irProblems = options.workflows.flatMap((w) => workflowStructuralProblems(w));
|
|
168
|
+
if (irProblems.length > 0) {
|
|
169
|
+
throw new WorkflowIRError(irProblems);
|
|
170
|
+
}
|
|
147
171
|
const byId = new Map(options.workflows.map((w) => [String(w.id), w]));
|
|
148
172
|
let pollTimer;
|
|
149
173
|
let running = false;
|
|
@@ -166,6 +190,21 @@ export function createWorkflowEngine(options) {
|
|
|
166
190
|
}
|
|
167
191
|
}
|
|
168
192
|
// ---- scope + step evaluation -------------------------------------------------
|
|
193
|
+
/**
|
|
194
|
+
* spec14pt3 F3 — whether this authority build may interpret this durable instance. The
|
|
195
|
+
* instance durably records the `AuthorityCompatibilityKey` string it was created under
|
|
196
|
+
* (`{ serverContract, schemaFingerprint, semanticFingerprint }`, and `semanticFingerprint`
|
|
197
|
+
* now covers `WorkflowDef` executable meaning). A build whose key differs — a changed
|
|
198
|
+
* action target, event id, branch predicate, timer duration, retry policy or control-flow
|
|
199
|
+
* edge — must fail closed rather than advance the instance under changed semantics. A
|
|
200
|
+
* missing / malformed stored fingerprint is also incompatible (never "missing ⇒
|
|
201
|
+
* compatible", spec14pt3 §175).
|
|
202
|
+
*/
|
|
203
|
+
function isCompatible(record) {
|
|
204
|
+
return (typeof record.compatibilityFingerprint === 'string' &&
|
|
205
|
+
record.compatibilityFingerprint.length > 0 &&
|
|
206
|
+
record.compatibilityFingerprint === options.compatibilityFingerprint);
|
|
207
|
+
}
|
|
169
208
|
function scopeFor(record, eventPayload) {
|
|
170
209
|
return {
|
|
171
210
|
...record.inputs,
|
|
@@ -210,16 +249,42 @@ export function createWorkflowEngine(options) {
|
|
|
210
249
|
return;
|
|
211
250
|
if (record.status === 'completed' || record.status === 'failed' || record.status === 'cancelled')
|
|
212
251
|
return;
|
|
213
|
-
if (record
|
|
214
|
-
//
|
|
252
|
+
if (!isCompatible(record)) {
|
|
253
|
+
// spec14pt3 F3 — fail closed *before* any semantic step: no ActionDef invoke,
|
|
254
|
+
// no event/timer/branch transition, no binding write, no `instanceRevision`
|
|
255
|
+
// advance. The instance is left exactly as it stands for a compatible authority
|
|
256
|
+
// to resume; incompatibility is an execution-environment problem, never an
|
|
257
|
+
// automatic `failed` / `cancelled` (spec14pt3 §23, §76, §156).
|
|
215
258
|
return;
|
|
216
259
|
}
|
|
217
260
|
const workflow = byId.get(record.workflowId);
|
|
218
261
|
if (!workflow)
|
|
219
262
|
return;
|
|
220
263
|
const step = workflowStepById(workflow, record.currentStepId);
|
|
221
|
-
|
|
264
|
+
// spec14pt3 F2: admission validation already rejected a structurally invalid
|
|
265
|
+
// workflow, so this only fires if the in-memory definition was mutated after the
|
|
266
|
+
// engine started. Fail the instance with a structured reason rather than looping
|
|
267
|
+
// the poll forever (a silent permanent wedge is explicitly forbidden, §46).
|
|
268
|
+
if (!step || !isWorkflowStep(step)) {
|
|
269
|
+
await store
|
|
270
|
+
.transition({
|
|
271
|
+
instanceId: record.instanceId,
|
|
272
|
+
expectedRevision: record.instanceRevision,
|
|
273
|
+
fence,
|
|
274
|
+
next: {
|
|
275
|
+
status: 'failed',
|
|
276
|
+
currentStepId: record.currentStepId,
|
|
277
|
+
activationId: record.activationId,
|
|
278
|
+
attempt: 0,
|
|
279
|
+
pendingAction: null,
|
|
280
|
+
nextEligibleAt: null,
|
|
281
|
+
failure: { reason: 'workflow-invalid-step', stepId: record.currentStepId },
|
|
282
|
+
history: { kind: 'failed', stepId: record.currentStepId, detail: { reason: 'workflow-invalid-step' } },
|
|
283
|
+
},
|
|
284
|
+
})
|
|
285
|
+
.catch(() => { });
|
|
222
286
|
return;
|
|
287
|
+
}
|
|
223
288
|
const progressed = await runStep({ workflow, record, step, fence });
|
|
224
289
|
if (!progressed)
|
|
225
290
|
return;
|
|
@@ -369,6 +434,11 @@ export function createWorkflowEngine(options) {
|
|
|
369
434
|
case 'action': {
|
|
370
435
|
return runActionStep({ ...args, step });
|
|
371
436
|
}
|
|
437
|
+
default:
|
|
438
|
+
// Unreachable: `advance` fails the instance before calling `runStep` for a step
|
|
439
|
+
// that is not one of the six kinds, and admission validation rejects such IR
|
|
440
|
+
// outright. Present so the switch is total and never returns `undefined`.
|
|
441
|
+
return false;
|
|
372
442
|
}
|
|
373
443
|
}
|
|
374
444
|
async function runActionStep(args) {
|
|
@@ -499,7 +569,10 @@ export function createWorkflowEngine(options) {
|
|
|
499
569
|
if (eventSeq <= wait.sinceEventSeq)
|
|
500
570
|
continue;
|
|
501
571
|
const loaded = await store.load(wait.instanceId);
|
|
502
|
-
|
|
572
|
+
// spec14pt3 F3 — an incompatible build may journal the canonical event as
|
|
573
|
+
// infrastructure but must not apply its (changed) `where` / `bind` semantics to this
|
|
574
|
+
// instance. A compatible authority reconciles it later from the durable journal.
|
|
575
|
+
if (!loaded || !isCompatible(loaded))
|
|
503
576
|
continue;
|
|
504
577
|
const workflow = byId.get(loaded.workflowId ?? '');
|
|
505
578
|
if (!workflow)
|
|
@@ -597,7 +670,7 @@ export function createWorkflowEngine(options) {
|
|
|
597
670
|
compatibilityFingerprint: options.compatibilityFingerprint,
|
|
598
671
|
};
|
|
599
672
|
const instanceId = `wf_${createHash('sha256').update(workflowStartKey(start)).digest('hex').slice(0, 24)}_${randomUUID().slice(0, 8)}`;
|
|
600
|
-
const { instance } = await store.createIdempotent(start, () => ({
|
|
673
|
+
const { instance, created } = await store.createIdempotent(start, () => ({
|
|
601
674
|
instanceId,
|
|
602
675
|
workflowId: request.workflowId,
|
|
603
676
|
compatibilityFingerprint: options.compatibilityFingerprint,
|
|
@@ -606,6 +679,18 @@ export function createWorkflowEngine(options) {
|
|
|
606
679
|
inputs,
|
|
607
680
|
entryStepId: String(workflow.entry),
|
|
608
681
|
}));
|
|
682
|
+
// spec14pt3 §161, §162 — a repeated start identity that resolves to an *existing*
|
|
683
|
+
// instance created under incompatible workflow semantics must NOT silently reuse it and
|
|
684
|
+
// continue it under this build. Return it, plainly labelled incompatible; never
|
|
685
|
+
// reinterpret it. (A brand-new instance created just now always matches this build.)
|
|
686
|
+
if (!created && !isCompatible(instance)) {
|
|
687
|
+
return {
|
|
688
|
+
error: {
|
|
689
|
+
code: WF_DIAGNOSTIC.INCOMPATIBLE,
|
|
690
|
+
message: `Workflow instance ${instance.instanceId} was created under workflow semantics incompatible with this authority build`,
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
609
694
|
// Drive it forward immediately (local fast path); the poll loop is the durable backstop.
|
|
610
695
|
await advance(instance.instanceId).catch(() => { });
|
|
611
696
|
const fresh = (await store.load(instance.instanceId)) ?? instance;
|
|
@@ -620,6 +705,18 @@ export function createWorkflowEngine(options) {
|
|
|
620
705
|
if (record.status === 'completed' || record.status === 'failed') {
|
|
621
706
|
return { ok: true, status: record.status }; // idempotent; a terminal workflow is not resurrected
|
|
622
707
|
}
|
|
708
|
+
// spec14pt3 §163 — cancellation writes a durable `cancelled` transition on this
|
|
709
|
+
// instance, so it is compatibility-gated like every other transition: an incompatible
|
|
710
|
+
// build must not transition an instance whose workflow meaning it does not share. The
|
|
711
|
+
// instance is left for a compatible authority to cancel or complete.
|
|
712
|
+
if (!isCompatible(record)) {
|
|
713
|
+
return {
|
|
714
|
+
error: {
|
|
715
|
+
code: WF_DIAGNOSTIC.INCOMPATIBLE,
|
|
716
|
+
message: `Workflow instance ${instanceId} is incompatible with this authority build; it cannot be cancelled here`,
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
}
|
|
623
720
|
const result = await withOwnership(instanceId, async (fence) => {
|
|
624
721
|
const fresh = await store.load(instanceId);
|
|
625
722
|
if (!fresh || fresh.status === 'cancelled' || fresh.status === 'completed' || fresh.status === 'failed') {
|
|
@@ -663,6 +760,8 @@ export function createWorkflowEngine(options) {
|
|
|
663
760
|
instanceRevision: record.instanceRevision,
|
|
664
761
|
...(record.failure ? { failure: record.failure } : {}),
|
|
665
762
|
...(record.output ? { output: record.output } : {}),
|
|
763
|
+
compatible: isCompatible(record),
|
|
764
|
+
...(isCompatible(record) ? {} : { incompatibleReason: 'incompatible-build' }),
|
|
666
765
|
};
|
|
667
766
|
}
|
|
668
767
|
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.
|
|
3
|
+
"version": "0.14.0-alpha.2",
|
|
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.
|
|
41
|
-
"@cynodia/axiom-runtime": "0.14.0-alpha.
|
|
40
|
+
"@cynodia/axiom-core": "0.14.0-alpha.2",
|
|
41
|
+
"@cynodia/axiom-runtime": "0.14.0-alpha.2"
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
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.
|
|
7
|
+
"release": "0.14.0-alpha.2",
|
|
8
8
|
"type": "object",
|
|
9
9
|
"required": [
|
|
10
10
|
"contract",
|