@ankhorage/flow 0.0.0-stage → 0.4.1

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 (109) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +17 -2
  4. package/dist/ai.d.ts +7 -0
  5. package/dist/ai.d.ts.map +1 -0
  6. package/dist/ai.js +7 -0
  7. package/dist/ai.js.map +1 -0
  8. package/dist/features/execution/application/executeFlowAsync.d.ts +4 -0
  9. package/dist/features/execution/application/executeFlowAsync.d.ts.map +1 -0
  10. package/dist/features/execution/application/executeFlowAsync.js +211 -0
  11. package/dist/features/execution/application/executeFlowAsync.js.map +1 -0
  12. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.d.ts +5 -0
  13. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.d.ts.map +1 -0
  14. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.js +11 -0
  15. package/dist/features/execution/domain/assertFlowGraphSchemasSupported.js.map +1 -0
  16. package/dist/features/execution/domain/compileFlowGraph.d.ts +5 -0
  17. package/dist/features/execution/domain/compileFlowGraph.d.ts.map +1 -0
  18. package/dist/features/execution/domain/compileFlowGraph.js +76 -0
  19. package/dist/features/execution/domain/compileFlowGraph.js.map +1 -0
  20. package/dist/features/execution/domain/createDefaultNodeRegistry.d.ts +4 -0
  21. package/dist/features/execution/domain/createDefaultNodeRegistry.d.ts.map +1 -0
  22. package/dist/features/execution/domain/createDefaultNodeRegistry.js +15 -0
  23. package/dist/features/execution/domain/createDefaultNodeRegistry.js.map +1 -0
  24. package/dist/features/execution/domain/validateFlowDataSchema.d.ts +6 -0
  25. package/dist/features/execution/domain/validateFlowDataSchema.d.ts.map +1 -0
  26. package/dist/features/execution/domain/validateFlowDataSchema.js +144 -0
  27. package/dist/features/execution/domain/validateFlowDataSchema.js.map +1 -0
  28. package/dist/features/mcp/McpToolError.d.ts +6 -0
  29. package/dist/features/mcp/McpToolError.d.ts.map +1 -0
  30. package/dist/features/mcp/McpToolError.js +10 -0
  31. package/dist/features/mcp/McpToolError.js.map +1 -0
  32. package/dist/features/mcp/createMcpRemoteTransport.d.ts +8 -0
  33. package/dist/features/mcp/createMcpRemoteTransport.d.ts.map +1 -0
  34. package/dist/features/mcp/createMcpRemoteTransport.js +23 -0
  35. package/dist/features/mcp/createMcpRemoteTransport.js.map +1 -0
  36. package/dist/features/mcp/createMcpToolRegistry.d.ts +10 -0
  37. package/dist/features/mcp/createMcpToolRegistry.d.ts.map +1 -0
  38. package/dist/features/mcp/createMcpToolRegistry.js +185 -0
  39. package/dist/features/mcp/createMcpToolRegistry.js.map +1 -0
  40. package/dist/features/mcp/loadMcpSdkAsync.d.ts +12 -0
  41. package/dist/features/mcp/loadMcpSdkAsync.d.ts.map +1 -0
  42. package/dist/features/mcp/loadMcpSdkAsync.js +20 -0
  43. package/dist/features/mcp/loadMcpSdkAsync.js.map +1 -0
  44. package/dist/features/mcp/prepareMcpProviders.d.ts +4 -0
  45. package/dist/features/mcp/prepareMcpProviders.d.ts.map +1 -0
  46. package/dist/features/mcp/prepareMcpProviders.js +97 -0
  47. package/dist/features/mcp/prepareMcpProviders.js.map +1 -0
  48. package/dist/features/mcp/validateMcpSchema.d.ts +6 -0
  49. package/dist/features/mcp/validateMcpSchema.d.ts.map +1 -0
  50. package/dist/features/mcp/validateMcpSchema.js +220 -0
  51. package/dist/features/mcp/validateMcpSchema.js.map +1 -0
  52. package/dist/features/security/FlowSecurityError.d.ts +6 -0
  53. package/dist/features/security/FlowSecurityError.d.ts.map +1 -0
  54. package/dist/features/security/FlowSecurityError.js +10 -0
  55. package/dist/features/security/FlowSecurityError.js.map +1 -0
  56. package/dist/features/security/application/createFlowSecurityPort.d.ts +12 -0
  57. package/dist/features/security/application/createFlowSecurityPort.d.ts.map +1 -0
  58. package/dist/features/security/application/createFlowSecurityPort.js +208 -0
  59. package/dist/features/security/application/createFlowSecurityPort.js.map +1 -0
  60. package/dist/features/security/domain/validateFlowSecurityTarget.d.ts +4 -0
  61. package/dist/features/security/domain/validateFlowSecurityTarget.d.ts.map +1 -0
  62. package/dist/features/security/domain/validateFlowSecurityTarget.js +57 -0
  63. package/dist/features/security/domain/validateFlowSecurityTarget.js.map +1 -0
  64. package/dist/index.d.ts +9 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +7 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/mcp.d.ts +4 -0
  69. package/dist/mcp.d.ts.map +1 -0
  70. package/dist/mcp.js +3 -0
  71. package/dist/mcp.js.map +1 -0
  72. package/dist/social.d.ts +7 -0
  73. package/dist/social.d.ts.map +1 -0
  74. package/dist/social.js +7 -0
  75. package/dist/social.js.map +1 -0
  76. package/dist/types/execution.d.ts +33 -0
  77. package/dist/types/execution.d.ts.map +1 -0
  78. package/dist/types/execution.js +2 -0
  79. package/dist/types/execution.js.map +1 -0
  80. package/dist/types/mcp.d.ts +61 -0
  81. package/dist/types/mcp.d.ts.map +1 -0
  82. package/dist/types/mcp.js +2 -0
  83. package/dist/types/mcp.js.map +1 -0
  84. package/dist/types/security.d.ts +123 -0
  85. package/dist/types/security.d.ts.map +1 -0
  86. package/dist/types/security.js +2 -0
  87. package/dist/types/security.js.map +1 -0
  88. package/package.json +86 -4
  89. package/src/ai.ts +6 -0
  90. package/src/features/execution/application/executeFlowAsync.ts +338 -0
  91. package/src/features/execution/domain/assertFlowGraphSchemasSupported.ts +17 -0
  92. package/src/features/execution/domain/compileFlowGraph.ts +97 -0
  93. package/src/features/execution/domain/createDefaultNodeRegistry.ts +26 -0
  94. package/src/features/execution/domain/validateFlowDataSchema.ts +218 -0
  95. package/src/features/mcp/McpToolError.ts +10 -0
  96. package/src/features/mcp/createMcpRemoteTransport.ts +34 -0
  97. package/src/features/mcp/createMcpToolRegistry.ts +324 -0
  98. package/src/features/mcp/loadMcpSdkAsync.ts +32 -0
  99. package/src/features/mcp/prepareMcpProviders.ts +135 -0
  100. package/src/features/mcp/validateMcpSchema.ts +269 -0
  101. package/src/features/security/FlowSecurityError.ts +17 -0
  102. package/src/features/security/application/createFlowSecurityPort.ts +339 -0
  103. package/src/features/security/domain/validateFlowSecurityTarget.ts +73 -0
  104. package/src/index.ts +35 -0
  105. package/src/mcp.ts +11 -0
  106. package/src/social.ts +6 -0
  107. package/src/types/execution.ts +41 -0
  108. package/src/types/mcp.ts +70 -0
  109. package/src/types/security.ts +148 -0
@@ -0,0 +1,123 @@
1
+ import type { SecretMetadata, SecretPayload, SecretRef } from '@ankhorage/contracts/secrets';
2
+ /** Trusted execution identity supplied by the host authentication boundary. */
3
+ export interface FlowSecurityActor {
4
+ readonly id: string;
5
+ readonly tenantId: string;
6
+ readonly projectId: string;
7
+ readonly environment: string;
8
+ }
9
+ /** An exact capability grant; omitted dimensions never imply access. */
10
+ export interface FlowSecurityGrant {
11
+ readonly actorId: string;
12
+ readonly capability: string;
13
+ readonly tenantId: string;
14
+ readonly projectId: string;
15
+ readonly environment: string;
16
+ }
17
+ /** A protected external operation presented to the security boundary. */
18
+ export interface FlowSecurityOperation {
19
+ readonly actor: FlowSecurityActor;
20
+ readonly capability: string;
21
+ readonly idempotencyKey: string;
22
+ readonly operationId: string;
23
+ readonly secretRefs?: readonly SecretRef[];
24
+ readonly target?: FlowSecurityTarget;
25
+ }
26
+ /** An outbound destination and its exact host authority. */
27
+ export interface FlowSecurityTarget {
28
+ readonly allowedHosts: readonly string[];
29
+ readonly url: string;
30
+ }
31
+ /** The complete immutable identity that grants, claims, and consumes one operation. */
32
+ export interface FlowSecurityOperationIdentity {
33
+ readonly actor: FlowSecurityActor;
34
+ readonly capability: string;
35
+ readonly idempotencyKey: string;
36
+ readonly operationId: string;
37
+ readonly secretRefs: readonly SecretRef[];
38
+ readonly target?: FlowSecurityTarget;
39
+ }
40
+ /** A durable human decision bound to exactly one complete operation identity. */
41
+ export interface FlowSecurityApproval {
42
+ readonly operation: FlowSecurityOperationIdentity;
43
+ readonly status: 'approved' | 'pending' | 'rejected';
44
+ }
45
+ /** Host-owned persistence boundary for approval-gate state. */
46
+ export interface FlowApprovalStorePort {
47
+ getAsync(operation: FlowSecurityOperationIdentity): Promise<FlowSecurityApproval | undefined>;
48
+ }
49
+ /**
50
+ * Host-owned durable exactly-once boundary. Every transition must be atomic across workers and
51
+ * restarts. `releaseAsync` may only release an unconsumed claim for this exact identity.
52
+ */
53
+ export interface FlowOperationStorePort {
54
+ claimAsync(operation: FlowSecurityOperationIdentity): Promise<boolean>;
55
+ consumeAsync(operation: FlowSecurityOperationIdentity): Promise<boolean>;
56
+ releaseAsync(operation: FlowSecurityOperationIdentity): Promise<void>;
57
+ }
58
+ /**
59
+ * Host-owned network boundary that verifies every actual peer before opening a connection.
60
+ * It must enforce the supplied host allowlist, resolve immediately before every connection,
61
+ * reject private or special-use resolved addresses, and reapply that policy to redirects.
62
+ */
63
+ export interface FlowOutboundTransportPort {
64
+ fetchAsync(input: {
65
+ readonly allowedHosts: readonly string[];
66
+ readonly init?: RequestInit;
67
+ readonly url: string;
68
+ }): Promise<Response>;
69
+ }
70
+ /** Host-owned rate limiter. A denied acquisition must have no external side effect. */
71
+ export interface FlowRateLimitPort {
72
+ acquireAsync(input: {
73
+ readonly actor: FlowSecurityActor;
74
+ readonly capability: string;
75
+ readonly operationId: string;
76
+ }): Promise<boolean>;
77
+ }
78
+ /** Redaction-safe audit edge. Secret values and payloads are intentionally absent. */
79
+ export interface FlowSecurityAuditPort {
80
+ writeAsync(event: FlowSecurityAuditEvent): Promise<void>;
81
+ }
82
+ /** Immutable audit metadata for one permit or denial. */
83
+ export interface FlowSecurityAuditEvent {
84
+ readonly actorId: string;
85
+ readonly capability: string;
86
+ readonly decision: 'denied' | 'permitted';
87
+ readonly operationId: string;
88
+ readonly reason: 'approval_pending' | 'approval_rejected' | 'credential_create' | 'credential_revoke' | 'idempotency_conflict' | 'invalid_target' | 'permission_denied' | 'rate_limited' | 'secret_unavailable';
89
+ readonly secretRefs: readonly SecretRef[];
90
+ readonly targetHost?: string;
91
+ }
92
+ /** Metadata-only credential view safe for administrative or graph-facing callers. */
93
+ export type FlowSecretMetadata = SecretMetadata;
94
+ export type FlowResolvedSecrets = Readonly<Record<SecretRef, Readonly<Record<string, string>>>>;
95
+ /** Protected server-side credential creation input. Its scope is always derived from the actor. */
96
+ export interface FlowSecretCreateInput {
97
+ readonly actor: FlowSecurityActor;
98
+ readonly kind: string;
99
+ readonly payload: SecretPayload;
100
+ readonly provider?: string;
101
+ readonly ref: SecretRef;
102
+ }
103
+ /** Protected server-side credential revocation input. Its scope is always derived from the actor. */
104
+ export interface FlowSecretRevokeInput {
105
+ readonly actor: FlowSecurityActor;
106
+ readonly ref: SecretRef;
107
+ }
108
+ /** One-use authorization result. Raw secrets stay private to the returned server closure. */
109
+ export interface FlowSecurityPermit {
110
+ readonly operation: FlowSecurityOperationIdentity;
111
+ executeAsync<T>(operation: (secrets: FlowResolvedSecrets) => Promise<T>): Promise<T>;
112
+ }
113
+ /** Public server-side boundary consumed by provider, AI, MCP, and social adapters. */
114
+ export interface FlowSecurityPort {
115
+ authorizeAsync(operation: FlowSecurityOperation): Promise<FlowSecurityPermit>;
116
+ createSecretAsync(input: FlowSecretCreateInput): Promise<FlowSecretMetadata>;
117
+ getSecretMetadataAsync(input: {
118
+ readonly actor: FlowSecurityActor;
119
+ readonly ref: SecretRef;
120
+ }): Promise<FlowSecretMetadata>;
121
+ revokeSecretAsync(input: FlowSecretRevokeInput): Promise<void>;
122
+ }
123
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/types/security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/types/security.ts"],"names":[],"mappings":"","sourcesContent":["import type { SecretMetadata, SecretPayload, SecretRef } from '@ankhorage/contracts/secrets';\n\n/** Trusted execution identity supplied by the host authentication boundary. */\nexport interface FlowSecurityActor {\n readonly id: string;\n readonly tenantId: string;\n readonly projectId: string;\n readonly environment: string;\n}\n\n/** An exact capability grant; omitted dimensions never imply access. */\nexport interface FlowSecurityGrant {\n readonly actorId: string;\n readonly capability: string;\n readonly tenantId: string;\n readonly projectId: string;\n readonly environment: string;\n}\n\n/** A protected external operation presented to the security boundary. */\nexport interface FlowSecurityOperation {\n readonly actor: FlowSecurityActor;\n readonly capability: string;\n readonly idempotencyKey: string;\n readonly operationId: string;\n readonly secretRefs?: readonly SecretRef[];\n readonly target?: FlowSecurityTarget;\n}\n\n/** An outbound destination and its exact host authority. */\nexport interface FlowSecurityTarget {\n readonly allowedHosts: readonly string[];\n readonly url: string;\n}\n\n/** The complete immutable identity that grants, claims, and consumes one operation. */\nexport interface FlowSecurityOperationIdentity {\n readonly actor: FlowSecurityActor;\n readonly capability: string;\n readonly idempotencyKey: string;\n readonly operationId: string;\n readonly secretRefs: readonly SecretRef[];\n readonly target?: FlowSecurityTarget;\n}\n\n/** A durable human decision bound to exactly one complete operation identity. */\nexport interface FlowSecurityApproval {\n readonly operation: FlowSecurityOperationIdentity;\n readonly status: 'approved' | 'pending' | 'rejected';\n}\n\n/** Host-owned persistence boundary for approval-gate state. */\nexport interface FlowApprovalStorePort {\n getAsync(operation: FlowSecurityOperationIdentity): Promise<FlowSecurityApproval | undefined>;\n}\n\n/**\n * Host-owned durable exactly-once boundary. Every transition must be atomic across workers and\n * restarts. `releaseAsync` may only release an unconsumed claim for this exact identity.\n */\nexport interface FlowOperationStorePort {\n claimAsync(operation: FlowSecurityOperationIdentity): Promise<boolean>;\n consumeAsync(operation: FlowSecurityOperationIdentity): Promise<boolean>;\n releaseAsync(operation: FlowSecurityOperationIdentity): Promise<void>;\n}\n\n/**\n * Host-owned network boundary that verifies every actual peer before opening a connection.\n * It must enforce the supplied host allowlist, resolve immediately before every connection,\n * reject private or special-use resolved addresses, and reapply that policy to redirects.\n */\nexport interface FlowOutboundTransportPort {\n fetchAsync(input: {\n readonly allowedHosts: readonly string[];\n readonly init?: RequestInit;\n readonly url: string;\n }): Promise<Response>;\n}\n\n/** Host-owned rate limiter. A denied acquisition must have no external side effect. */\nexport interface FlowRateLimitPort {\n acquireAsync(input: {\n readonly actor: FlowSecurityActor;\n readonly capability: string;\n readonly operationId: string;\n }): Promise<boolean>;\n}\n\n/** Redaction-safe audit edge. Secret values and payloads are intentionally absent. */\nexport interface FlowSecurityAuditPort {\n writeAsync(event: FlowSecurityAuditEvent): Promise<void>;\n}\n\n/** Immutable audit metadata for one permit or denial. */\nexport interface FlowSecurityAuditEvent {\n readonly actorId: string;\n readonly capability: string;\n readonly decision: 'denied' | 'permitted';\n readonly operationId: string;\n readonly reason:\n | 'approval_pending'\n | 'approval_rejected'\n | 'credential_create'\n | 'credential_revoke'\n | 'idempotency_conflict'\n | 'invalid_target'\n | 'permission_denied'\n | 'rate_limited'\n | 'secret_unavailable';\n readonly secretRefs: readonly SecretRef[];\n readonly targetHost?: string;\n}\n\n/** Metadata-only credential view safe for administrative or graph-facing callers. */\nexport type FlowSecretMetadata = SecretMetadata;\nexport type FlowResolvedSecrets = Readonly<Record<SecretRef, Readonly<Record<string, string>>>>;\n\n/** Protected server-side credential creation input. Its scope is always derived from the actor. */\nexport interface FlowSecretCreateInput {\n readonly actor: FlowSecurityActor;\n readonly kind: string;\n readonly payload: SecretPayload;\n readonly provider?: string;\n readonly ref: SecretRef;\n}\n\n/** Protected server-side credential revocation input. Its scope is always derived from the actor. */\nexport interface FlowSecretRevokeInput {\n readonly actor: FlowSecurityActor;\n readonly ref: SecretRef;\n}\n\n/** One-use authorization result. Raw secrets stay private to the returned server closure. */\nexport interface FlowSecurityPermit {\n readonly operation: FlowSecurityOperationIdentity;\n executeAsync<T>(operation: (secrets: FlowResolvedSecrets) => Promise<T>): Promise<T>;\n}\n\n/** Public server-side boundary consumed by provider, AI, MCP, and social adapters. */\nexport interface FlowSecurityPort {\n authorizeAsync(operation: FlowSecurityOperation): Promise<FlowSecurityPermit>;\n createSecretAsync(input: FlowSecretCreateInput): Promise<FlowSecretMetadata>;\n getSecretMetadataAsync(input: {\n readonly actor: FlowSecurityActor;\n readonly ref: SecretRef;\n }): Promise<FlowSecretMetadata>;\n revokeSecretAsync(input: FlowSecretRevokeInput): Promise<void>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,88 @@
1
1
  {
2
2
  "name": "@ankhorage/flow",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.1",
4
+ "description": "Standalone workflow engine foundation for Ankhorage.",
5
+ "keywords": [
6
+ "ankhorage",
7
+ "flow",
8
+ "workflow",
9
+ "automation"
10
+ ],
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "sideEffects": false,
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./ai": {
21
+ "types": "./dist/ai.d.ts",
22
+ "default": "./dist/ai.js"
23
+ },
24
+ "./mcp": {
25
+ "types": "./dist/mcp.d.ts",
26
+ "default": "./dist/mcp.js"
27
+ },
28
+ "./social": {
29
+ "types": "./dist/social.d.ts",
30
+ "default": "./dist/social.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src",
36
+ "package.json",
37
+ "README.md",
38
+ "CHANGELOG.md",
39
+ "LICENSE"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/ankhorage/flow.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/ankhorage/flow/issues"
50
+ },
51
+ "homepage": "https://github.com/ankhorage/flow#readme",
52
+ "license": "MIT",
53
+ "scripts": {
54
+ "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.json",
55
+ "changeset": "ankhorage-changeset",
56
+ "changeset:status": "ankhorage-changeset status --since=origin/main",
57
+ "docs": "paradox",
58
+ "lint": "ankhorage-eslint . --max-warnings=0",
59
+ "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
60
+ "format": "ankhorage-prettier --write .",
61
+ "format:check": "ankhorage-prettier --check .",
62
+ "knip:check": "ankhorage-knip",
63
+ "test": "bun test src test",
64
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node-esm.json && tsc --noEmit -p tsconfig.test.json",
65
+ "version-packages": "ankhorage-changeset version"
66
+ },
67
+ "devDependencies": {
68
+ "@ankhorage/devtools": "^2.4.3",
69
+ "@ankhorage/paradox": "^0.3.6",
70
+ "@ankhorage/secrets-supabase-vault": "^1.0.0",
71
+ "@modelcontextprotocol/sdk": "^1.32.2",
72
+ "@types/bun": "^1.4.2",
73
+ "@types/node": "^26.6.6",
74
+ "typescript": "^6.0.3"
75
+ },
76
+ "packageManager": "bun@1.4.2",
77
+ "dependencies": {
78
+ "@ankhorage/contracts": "^26.7.0"
79
+ },
80
+ "peerDependencies": {
81
+ "@modelcontextprotocol/sdk": "^1.32.2"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "@modelcontextprotocol/sdk": {
85
+ "optional": true
86
+ }
87
+ }
88
+ }
package/src/ai.ts ADDED
@@ -0,0 +1,6 @@
1
+ /***
2
+ * Reserved public subpath for future AI-provider adapters.
3
+ *
4
+ * This bootstrap intentionally provides no AI execution, configuration, or dependencies.
5
+ */
6
+ export {};
@@ -0,0 +1,338 @@
1
+ import type { DataContractValue } from '@ankhorage/contracts/data';
2
+ import type {
3
+ FlowDataRef,
4
+ FlowEdge,
5
+ FlowError,
6
+ FlowNode,
7
+ FlowPortId,
8
+ FlowRunSnapshot,
9
+ FlowStepSnapshot,
10
+ } from '@ankhorage/contracts/flow';
11
+
12
+ import type {
13
+ FlowClock,
14
+ FlowCompiledGraph,
15
+ FlowExecutionResult,
16
+ FlowNodeRegistry,
17
+ FlowRunRequest,
18
+ } from '../../../types/execution.js';
19
+ import { assertFlowGraphSchemasSupported } from '../domain/assertFlowGraphSchemasSupported.js';
20
+ import { validateFlowDataSchema } from '../domain/validateFlowDataSchema.js';
21
+
22
+ /*** Execute a compiled graph in deterministic order within one process. */
23
+ export const executeFlowAsync = async (
24
+ graph: FlowCompiledGraph,
25
+ registry: FlowNodeRegistry,
26
+ request: FlowRunRequest,
27
+ clock: FlowClock = { now: () => new Date() },
28
+ ): Promise<FlowExecutionResult> => {
29
+ const trigger = graph.definition.triggers.find(({ id }) => id === request.triggerId);
30
+ if (trigger?.kind !== 'manual') throw new Error('FLOW_TRIGGER_NOT_MANUAL');
31
+ assertFlowGraphSchemasSupported(graph.definition.nodes, request.schemaRegistry);
32
+ const startedAt = clock.now().toISOString();
33
+ const state = await executeNodesAsync(graph, registry, request, clock);
34
+ return {
35
+ run: createRun(
36
+ graph,
37
+ request,
38
+ state.steps,
39
+ startedAt,
40
+ clock.now().toISOString(),
41
+ state.status ?? 'succeeded',
42
+ state.error,
43
+ ),
44
+ };
45
+ };
46
+
47
+ /*** Iterate compiled nodes in deterministic order until completion or cancellation. */
48
+ const executeNodesAsync = async (
49
+ graph: FlowCompiledGraph,
50
+ registry: FlowNodeRegistry,
51
+ request: FlowRunRequest,
52
+ clock: FlowClock,
53
+ ): Promise<ExecutionState> => {
54
+ const state: ExecutionState = {
55
+ activatedNodeIds: new Set(
56
+ graph.topologicalNodeIds.filter(
57
+ (nodeId) => !graph.definition.edges.some((edge) => edge.target.nodeId === nodeId),
58
+ ),
59
+ ),
60
+ inputs: {},
61
+ steps: [],
62
+ };
63
+ for (const nodeId of graph.topologicalNodeIds) {
64
+ if (request.signal?.aborted) return { ...state, status: 'cancelled' };
65
+ const node = graph.definition.nodes.find((candidate) => candidate.id === nodeId);
66
+ if (!node) throw new Error(`FLOW_GRAPH_UNKNOWN_NODE:${nodeId}`);
67
+ const result = await executeAvailableNodeAsync(node, state, registry, request, clock);
68
+ if (!result) continue;
69
+ state.steps.push(result.step);
70
+ if (result.step.status === 'cancelled') return { ...state, status: 'cancelled' };
71
+ propagateOutput(
72
+ graph.edgesBySourceNodeId[node.id] ?? [],
73
+ result.output,
74
+ result.step.status === 'failed' ? 'failure' : 'success',
75
+ state,
76
+ );
77
+ }
78
+ const failedStep = state.steps.find((step) => step.status === 'failed');
79
+ return { ...state, status: failedStep ? 'failed' : 'succeeded', error: failedStep?.error };
80
+ };
81
+
82
+ interface ExecutionState {
83
+ readonly activatedNodeIds: Set<string>;
84
+ readonly error?: FlowError;
85
+ readonly inputs: Record<string, Record<FlowPortId, FlowDataRef>>;
86
+ readonly status?: FlowRunSnapshot['status'];
87
+ readonly steps: FlowStepSnapshot[];
88
+ }
89
+
90
+ /*** Execute one node only when all of its required inputs were propagated. */
91
+ const executeAvailableNodeAsync = async (
92
+ node: FlowNode,
93
+ state: ExecutionState,
94
+ registry: FlowNodeRegistry,
95
+ request: FlowRunRequest,
96
+ clock: FlowClock,
97
+ ): Promise<
98
+ | { readonly output: Readonly<Record<FlowPortId, FlowDataRef>>; readonly step: FlowStepSnapshot }
99
+ | undefined
100
+ > => {
101
+ if (!state.activatedNodeIds.has(node.id)) return undefined;
102
+ const input = state.inputs[node.id] ?? request.input ?? {};
103
+ if (!hasRequiredInput(node, input)) return undefined;
104
+ return executeNodeAsync(node, input, registry, request.signal, clock, request.schemaRegistry);
105
+ };
106
+
107
+ /*** Determine whether every required input port has a propagated value. */
108
+ const hasRequiredInput = (
109
+ node: FlowNode,
110
+ input: Readonly<Record<FlowPortId, FlowDataRef>>,
111
+ ): boolean =>
112
+ node.ports
113
+ .filter((port) => port.direction === 'input' && port.required)
114
+ .every((port) => input[port.id] !== undefined);
115
+
116
+ /*** Execute one node and convert failures to redaction-safe contract errors. */
117
+ const executeNodeAsync = async (
118
+ node: FlowNode,
119
+ input: Readonly<Record<FlowPortId, FlowDataRef>>,
120
+ registry: FlowNodeRegistry,
121
+ signal: AbortSignal | undefined,
122
+ clock: FlowClock,
123
+ schemas: FlowRunRequest['schemaRegistry'],
124
+ ): Promise<{
125
+ readonly output: Readonly<Record<FlowPortId, FlowDataRef>>;
126
+ readonly step: FlowStepSnapshot;
127
+ }> => {
128
+ const startedAt = clock.now().toISOString();
129
+ try {
130
+ assertPortSchemas(node, input, 'input', schemas);
131
+ const operationId = `${node.capability.providerId}:${node.capability.capabilityId}`;
132
+ const operation = Object.entries(registry).find(([id]) => id === operationId)?.[1];
133
+ if (!operation) throw new Error(`FLOW_OPERATION_UNREGISTERED:${operationId}`);
134
+ const output =
135
+ (
136
+ await operation.executeAsync({
137
+ config: node.config,
138
+ input,
139
+ node,
140
+ signal: signal ?? new AbortController().signal,
141
+ })
142
+ ).output ?? {};
143
+ if (signal?.aborted)
144
+ return { output: {}, step: createCancelledStep(node, input, startedAt, clock) };
145
+ assertOutputPorts(node, output);
146
+ assertPortSchemas(node, output, 'output', schemas);
147
+ return { output, step: createSucceededStep(node, input, output, startedAt, clock) };
148
+ } catch (reason) {
149
+ const error = asFlowError(reason);
150
+ return {
151
+ output: {},
152
+ step: {
153
+ id: `${node.id}:1`,
154
+ nodeId: node.id,
155
+ status: 'failed',
156
+ attempt: 1,
157
+ startedAt,
158
+ completedAt: clock.now().toISOString(),
159
+ input,
160
+ error,
161
+ },
162
+ };
163
+ }
164
+ };
165
+
166
+ /*** Create an immutable successful step snapshot. */
167
+ const createSucceededStep = (
168
+ node: FlowNode,
169
+ input: Readonly<Record<FlowPortId, FlowDataRef>>,
170
+ output: Readonly<Record<FlowPortId, FlowDataRef>>,
171
+ startedAt: string,
172
+ clock: FlowClock,
173
+ ): FlowStepSnapshot => ({
174
+ id: `${node.id}:1`,
175
+ nodeId: node.id,
176
+ status: 'succeeded',
177
+ attempt: 1,
178
+ startedAt,
179
+ completedAt: clock.now().toISOString(),
180
+ input,
181
+ output,
182
+ });
183
+
184
+ /*** Create a terminal snapshot when cancellation occurs during asynchronous node execution. */
185
+ const createCancelledStep = (
186
+ node: FlowNode,
187
+ input: Readonly<Record<FlowPortId, FlowDataRef>>,
188
+ startedAt: string,
189
+ clock: FlowClock,
190
+ ): FlowStepSnapshot => ({
191
+ id: `${node.id}:1`,
192
+ nodeId: node.id,
193
+ status: 'cancelled',
194
+ attempt: 1,
195
+ startedAt,
196
+ completedAt: clock.now().toISOString(),
197
+ input,
198
+ });
199
+
200
+ /*** Reject operation outputs that do not correspond to declared output ports. */
201
+ const assertOutputPorts = (
202
+ node: FlowNode,
203
+ output: Readonly<Record<FlowPortId, FlowDataRef>>,
204
+ ): void => {
205
+ const outputIds = new Set(
206
+ node.ports.filter((port) => port.direction === 'output').map(({ id }) => id),
207
+ );
208
+ if (Object.keys(output).some((portId) => !outputIds.has(portId)))
209
+ throw new Error(`FLOW_OUTPUT_UNDECLARED_PORT:${node.id}`);
210
+ const missingRequired = node.ports.some(
211
+ (port) => port.direction === 'output' && port.required && output[port.id] === undefined,
212
+ );
213
+ if (missingRequired) throw new Error(`FLOW_OUTPUT_REQUIRED_PORT_MISSING:${node.id}`);
214
+ };
215
+
216
+ /*** Validate inline port values against their declared portable schemas. */
217
+ const assertPortSchemas = (
218
+ node: FlowNode,
219
+ values: Readonly<Record<FlowPortId, FlowDataRef>>,
220
+ direction: 'input' | 'output',
221
+ schemas: FlowRunRequest['schemaRegistry'],
222
+ ): void => {
223
+ for (const port of node.ports.filter((candidate) => candidate.direction === direction)) {
224
+ const value = values[port.id];
225
+ const schema =
226
+ port.data?.schema ?? (port.data?.schemaRef ? { ref: port.data.schemaRef } : undefined);
227
+ if (value?.kind === 'inline' && schema && !validateFlowDataSchema(value.value, schema, schemas))
228
+ throw new Error(`FLOW_PORT_SCHEMA_MISMATCH:${node.id}:${port.id}`);
229
+ }
230
+ };
231
+
232
+ /*** Propagate outputs only over edges whose declared condition matches the output value. */
233
+ const propagateOutput = (
234
+ edges: readonly FlowEdge[],
235
+ output: Readonly<Record<FlowPortId, FlowDataRef>>,
236
+ outcome: 'failure' | 'success',
237
+ state: ExecutionState,
238
+ ): void => {
239
+ for (const edge of edges) {
240
+ const value = output[edge.source.portId];
241
+ if (!matchesEdge(edge, value, outcome)) continue;
242
+ state.activatedNodeIds.add(edge.target.nodeId);
243
+ if (value)
244
+ state.inputs[edge.target.nodeId] = {
245
+ ...state.inputs[edge.target.nodeId],
246
+ [edge.target.portId]: value,
247
+ };
248
+ }
249
+ };
250
+
251
+ /*** Evaluate an edge condition without executing arbitrary expressions. */
252
+ const matchesEdge = (
253
+ edge: FlowEdge,
254
+ value: FlowDataRef | undefined,
255
+ outcome: 'failure' | 'success',
256
+ ): boolean => {
257
+ const condition = edge.condition ?? { kind: 'always' as const };
258
+ if (condition.kind === 'always') return outcome === 'success';
259
+ if (condition.kind === 'outcome') return condition.outcome === outcome;
260
+ if (value?.kind !== 'inline') return false;
261
+ const selected = resolvePointer(value.value, condition.path);
262
+ return selected !== undefined && structurallyEqual(selected, condition.equals);
263
+ };
264
+
265
+ /*** Resolve an RFC 6901 pointer against serializable inline data. */
266
+ const resolvePointer = (
267
+ value: DataContractValue,
268
+ pointer: string,
269
+ ): DataContractValue | undefined => {
270
+ if (pointer === '') return value;
271
+ if (!pointer.startsWith('/')) return undefined;
272
+ return pointer
273
+ .slice(1)
274
+ .split('/')
275
+ .reduce<DataContractValue | undefined>((current, segment) => {
276
+ if (current === undefined || current === null || typeof current !== 'object')
277
+ return undefined;
278
+ const key = segment.replaceAll('~1', '/').replaceAll('~0', '~');
279
+ if (isDataArray(current)) return current.find((_item, index) => index === Number(key));
280
+ return isDataRecord(current)
281
+ ? Object.entries(current).find(([candidateKey]) => candidateKey === key)?.[1]
282
+ : undefined;
283
+ }, value);
284
+ };
285
+
286
+ /*** Compare JSON-compatible values with object-key-order independence. */
287
+ const structurallyEqual = (left: DataContractValue, right: DataContractValue): boolean =>
288
+ JSON.stringify(normalizeValue(left)) === JSON.stringify(normalizeValue(right));
289
+
290
+ /*** Canonicalize object member ordering for structural equality. */
291
+ const normalizeValue = (value: DataContractValue): DataContractValue => {
292
+ if (Array.isArray(value)) return value.map(normalizeValue);
293
+ if (value !== null && typeof value === 'object')
294
+ return Object.fromEntries(
295
+ Object.entries(value)
296
+ .sort(([left], [right]) => left.localeCompare(right))
297
+ .map(([key, item]) => [key, normalizeValue(item)]),
298
+ );
299
+ return value;
300
+ };
301
+
302
+ /*** Narrow a serializable non-array object to an indexable data record. */
303
+ const isDataRecord = (value: object): value is Readonly<Record<string, DataContractValue>> =>
304
+ !Array.isArray(value);
305
+
306
+ /*** Narrow a serializable array without losing its element type. */
307
+ const isDataArray = (value: object): value is readonly DataContractValue[] => Array.isArray(value);
308
+
309
+ /*** Translate host failures to structured contract errors without exposing arbitrary objects. */
310
+ const asFlowError = (reason: unknown): FlowError => ({
311
+ code:
312
+ reason instanceof Error && /^FLOW_[A-Z0-9_]+(?::|$)/.test(reason.message)
313
+ ? (reason.message.split(':')[0] ?? 'FLOW_OPERATION_FAILED')
314
+ : 'FLOW_OPERATION_FAILED',
315
+ message: 'The operation failed.',
316
+ retryable: false,
317
+ });
318
+
319
+ /*** Build the immutable run result emitted by the single-process executor. */
320
+ const createRun = (
321
+ graph: FlowCompiledGraph,
322
+ request: FlowRunRequest,
323
+ steps: readonly FlowStepSnapshot[],
324
+ startedAt: string,
325
+ completedAt: string,
326
+ status: FlowRunSnapshot['status'],
327
+ error?: FlowError,
328
+ ): FlowRunSnapshot => ({
329
+ id: request.runId,
330
+ flow: { id: graph.definition.id, version: graph.definition.version },
331
+ status,
332
+ trigger: { triggerId: request.triggerId, kind: 'manual' },
333
+ createdAt: startedAt,
334
+ startedAt,
335
+ completedAt,
336
+ steps,
337
+ error,
338
+ });
@@ -0,0 +1,17 @@
1
+ import type { DataSchemaRegistry } from '@ankhorage/contracts/data';
2
+ import type { FlowNode } from '@ankhorage/contracts/flow';
3
+
4
+ import { assertFlowDataSchemaSupported } from './validateFlowDataSchema.js';
5
+
6
+ /*** Validate every declared graph port schema before any operation can perform a side effect. */
7
+ export const assertFlowGraphSchemasSupported = (
8
+ nodes: readonly FlowNode[],
9
+ schemas: DataSchemaRegistry | undefined,
10
+ ): void => {
11
+ for (const node of nodes)
12
+ for (const port of node.ports) {
13
+ const schema =
14
+ port.data?.schema ?? (port.data?.schemaRef ? { ref: port.data.schemaRef } : undefined);
15
+ if (schema) assertFlowDataSchemaSupported(schema, schemas);
16
+ }
17
+ };