@ankhorage/flow 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +3 -1
  3. package/dist/execution.d.ts +5 -0
  4. package/dist/execution.d.ts.map +1 -0
  5. package/dist/execution.js +5 -0
  6. package/dist/execution.js.map +1 -0
  7. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.d.ts +4 -0
  8. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.d.ts.map +1 -0
  9. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.js +219 -0
  10. package/dist/features/execution/adapters/FileFlowPersistenceAdapter.js.map +1 -0
  11. package/dist/features/execution/application/cancelDurableRunAsync.d.ts +8 -0
  12. package/dist/features/execution/application/cancelDurableRunAsync.d.ts.map +1 -0
  13. package/dist/features/execution/application/cancelDurableRunAsync.js +22 -0
  14. package/dist/features/execution/application/cancelDurableRunAsync.js.map +1 -0
  15. package/dist/features/execution/application/createDurableRunAsync.d.ts +12 -0
  16. package/dist/features/execution/application/createDurableRunAsync.d.ts.map +1 -0
  17. package/dist/features/execution/application/createDurableRunAsync.js +8 -0
  18. package/dist/features/execution/application/createDurableRunAsync.js.map +1 -0
  19. package/dist/features/execution/application/runDurableWorkerAsync.d.ts +52 -0
  20. package/dist/features/execution/application/runDurableWorkerAsync.d.ts.map +1 -0
  21. package/dist/features/execution/application/runDurableWorkerAsync.js +195 -0
  22. package/dist/features/execution/application/runDurableWorkerAsync.js.map +1 -0
  23. package/dist/features/execution/domain/calculateRetryDueAt.d.ts +4 -0
  24. package/dist/features/execution/domain/calculateRetryDueAt.d.ts.map +1 -0
  25. package/dist/features/execution/domain/calculateRetryDueAt.js +14 -0
  26. package/dist/features/execution/domain/calculateRetryDueAt.js.map +1 -0
  27. package/dist/features/execution/domain/createDurableRun.d.ts +7 -0
  28. package/dist/features/execution/domain/createDurableRun.d.ts.map +1 -0
  29. package/dist/features/execution/domain/createDurableRun.js +10 -0
  30. package/dist/features/execution/domain/createDurableRun.js.map +1 -0
  31. package/dist/features/execution/domain/createRetryWork.d.ts +4 -0
  32. package/dist/features/execution/domain/createRetryWork.d.ts.map +1 -0
  33. package/dist/features/execution/domain/createRetryWork.js +17 -0
  34. package/dist/features/execution/domain/createRetryWork.js.map +1 -0
  35. package/dist/features/execution/domain/isCronDueAt.d.ts +7 -0
  36. package/dist/features/execution/domain/isCronDueAt.d.ts.map +1 -0
  37. package/dist/features/execution/domain/isCronDueAt.js +114 -0
  38. package/dist/features/execution/domain/isCronDueAt.js.map +1 -0
  39. package/dist/features/execution/ports/FlowPersistencePort.d.ts +34 -0
  40. package/dist/features/execution/ports/FlowPersistencePort.d.ts.map +1 -0
  41. package/dist/features/execution/ports/FlowPersistencePort.js +2 -0
  42. package/dist/features/execution/ports/FlowPersistencePort.js.map +1 -0
  43. package/dist/features/security/application/createFlowSecurityPort.js +2 -2
  44. package/dist/features/security/application/createFlowSecurityPort.js.map +1 -1
  45. package/dist/index.d.ts +7 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +5 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/types/durableExecution.d.ts +55 -0
  50. package/dist/types/durableExecution.d.ts.map +1 -0
  51. package/dist/types/durableExecution.js +2 -0
  52. package/dist/types/durableExecution.js.map +1 -0
  53. package/package.json +5 -1
  54. package/src/execution.ts +9 -0
  55. package/src/features/execution/adapters/FileFlowPersistenceAdapter.ts +333 -0
  56. package/src/features/execution/application/cancelDurableRunAsync.ts +28 -0
  57. package/src/features/execution/application/createDurableRunAsync.ts +14 -0
  58. package/src/features/execution/application/runDurableWorkerAsync.ts +337 -0
  59. package/src/features/execution/domain/calculateRetryDueAt.ts +21 -0
  60. package/src/features/execution/domain/createDurableRun.ts +14 -0
  61. package/src/features/execution/domain/createRetryWork.ts +22 -0
  62. package/src/features/execution/domain/isCronDueAt.ts +131 -0
  63. package/src/features/execution/ports/FlowPersistencePort.ts +38 -0
  64. package/src/features/security/application/createFlowSecurityPort.ts +2 -2
  65. package/src/index.ts +14 -0
  66. package/src/types/durableExecution.ts +61 -0
@@ -1 +1 @@
1
- {"version":3,"file":"createFlowSecurityPort.js","sourceRoot":"","sources":["../../../../src/features/security/application/createFlowSecurityPort.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SecretRef, SecretStoreAdapter } from '@ankhorage/contracts/secrets';\n\nimport type {\n FlowApprovalStorePort,\n FlowOperationStorePort,\n FlowRateLimitPort,\n FlowResolvedSecrets,\n FlowSecretCreateInput,\n FlowSecretMetadata,\n FlowSecretRevokeInput,\n FlowSecurityActor,\n FlowSecurityAuditEvent,\n FlowSecurityAuditPort,\n FlowSecurityGrant,\n FlowSecurityOperation,\n FlowSecurityOperationIdentity,\n FlowSecurityPermit,\n FlowSecurityPort,\n FlowSecurityTarget,\n} from '../../../types/security.js';\nimport { validateFlowSecurityTarget } from '../domain/validateFlowSecurityTarget.js';\nimport { FlowSecurityError } from '../FlowSecurityError.js';\n\nconst METADATA_CAPABILITY = 'secrets:metadata';\nconst CREATE_CAPABILITY = 'secrets:create';\nconst REVOKE_CAPABILITY = 'secrets:revoke';\ntype FlowSecurityDenialReason = Exclude<\n FlowSecurityAuditEvent['reason'],\n 'credential_create' | 'credential_revoke'\n>;\n\n/*** Creates the provider-neutral server-side authorization, approval, audit, and secret boundary. */\nexport const createFlowSecurityPort = (input: {\n readonly approvalStore: FlowApprovalStorePort;\n readonly audit: FlowSecurityAuditPort;\n readonly grants: readonly FlowSecurityGrant[];\n readonly operationStore: FlowOperationStorePort;\n readonly rateLimit: FlowRateLimitPort;\n readonly secretStore: SecretStoreAdapter;\n}): FlowSecurityPort => ({\n authorizeAsync: async (operation) => authorizeAsync(input, operation),\n createSecretAsync: async (request) => createSecretAsync(input, request),\n getSecretMetadataAsync: async ({ actor, ref }) => getSecretMetadataAsync(input, actor, ref),\n revokeSecretAsync: async (request) => revokeSecretAsync(input, request),\n});\n\n/*** Authorizes and durably claims one exact operation before resolving any secret material. */\nconst authorizeAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n operation: FlowSecurityOperation,\n): Promise<FlowSecurityPermit> => {\n const identity = identityOf(operation);\n const targetHost = validateFlowSecurityTarget(identity.target);\n if (!isGranted(input.grants, identity.actor, identity.capability))\n return denyAsync(input.audit, identity, targetHost, 'permission_denied');\n const approval = await input.approvalStore.getAsync(identity);\n if (!approval || approval.status === 'pending')\n return denyAsync(input.audit, identity, targetHost, 'approval_pending');\n if (approval.status !== 'approved' || !sameIdentity(approval.operation, identity))\n return denyAsync(input.audit, identity, targetHost, 'approval_rejected');\n if (!(await input.operationStore.claimAsync(identity)))\n return denyAsync(input.audit, identity, targetHost, 'idempotency_conflict');\n try {\n if (\n !(await input.rateLimit.acquireAsync({\n actor: identity.actor,\n capability: identity.capability,\n operationId: identity.operationId,\n }))\n )\n return denyAfterReleaseAsync(input, identity, targetHost, 'rate_limited');\n await writeAuditAsync(input.audit, identity, targetHost, 'permitted', 'permission_denied');\n return createPermit(input, identity);\n } catch (error) {\n await input.operationStore.releaseAsync(identity);\n throw error;\n }\n};\n\n/*** Creates a one-use permit that atomically consumes the durable operation claim before side effects. */\nconst createPermit = (\n input: Parameters<typeof createFlowSecurityPort>[0],\n identity: FlowSecurityOperationIdentity,\n): FlowSecurityPermit => ({\n operation: identity,\n executeAsync: async (callback) => {\n if (!(await input.operationStore.consumeAsync(identity)))\n throw new FlowSecurityError(\n 'idempotency_conflict',\n 'The operation permit has already been consumed.',\n );\n return resolveAndExecuteAsync(input, identity.actor, identity.secretRefs, callback);\n },\n});\n\n/*** Releases an unconsumed claim before emitting a retryable denial. */\nconst denyAfterReleaseAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n identity: FlowSecurityOperationIdentity,\n targetHost: string | undefined,\n reason: FlowSecurityDenialReason,\n): Promise<never> => {\n await input.operationStore.releaseAsync(identity);\n return denyAsync(input.audit, identity, targetHost, reason);\n};\n\n/*** Creates a credential only after an explicit server-side scoped grant and redacted audit. */\nconst createSecretAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n request: FlowSecretCreateInput,\n): Promise<FlowSecretMetadata> => {\n const actor = snapshotActor(request.actor);\n if (!isGranted(input.grants, actor, CREATE_CAPABILITY))\n return denyCredentialAsync(input.audit, actor, request.ref, CREATE_CAPABILITY);\n await writeCredentialAuditAsync(input.audit, actor, request.ref, CREATE_CAPABILITY);\n const result = await input.secretStore.create({\n kind: request.kind,\n payload: request.payload,\n ...(request.provider === undefined ? {} : { provider: request.provider }),\n ref: request.ref,\n scope: scopeOf(actor),\n });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'The requested secret is unavailable.');\n return result.data;\n};\n\n/*** Returns metadata only after an explicit actor, tenant, project, and environment grant. */\nconst getSecretMetadataAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n actor: FlowSecurityActor,\n ref: SecretRef,\n) => {\n const identity = snapshotActor(actor);\n if (!isGranted(input.grants, identity, METADATA_CAPABILITY))\n throw new FlowSecurityError(\n 'permission_denied',\n 'The actor is not permitted to inspect secrets.',\n );\n const result = await input.secretStore.getMetadata({ scope: scopeOf(identity), ref });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'The requested secret is unavailable.');\n return result.data;\n};\n\n/*** Revokes a credential only after an explicit server-side scoped grant and redacted audit. */\nconst revokeSecretAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n request: FlowSecretRevokeInput,\n): Promise<void> => {\n const actor = snapshotActor(request.actor);\n if (!isGranted(input.grants, actor, REVOKE_CAPABILITY))\n return denyCredentialAsync(input.audit, actor, request.ref, REVOKE_CAPABILITY);\n await writeCredentialAuditAsync(input.audit, actor, request.ref, REVOKE_CAPABILITY);\n const result = await input.secretStore.remove({ ref: request.ref, scope: scopeOf(actor) });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'The requested secret is unavailable.');\n};\n\n/*** Resolves secrets only in the server-side callback after durable consumption succeeds. */\nconst resolveAndExecuteAsync = async <T>(\n input: Parameters<typeof createFlowSecurityPort>[0],\n actor: FlowSecurityActor,\n secretRefs: readonly SecretRef[],\n callback: (secrets: FlowResolvedSecrets) => Promise<T>,\n): Promise<T> => {\n const resolved = await Promise.all(\n secretRefs.map(\n async (ref) => [ref, await resolveSecretAsync(input.secretStore, actor, ref)] as const,\n ),\n );\n return callback(Object.fromEntries(resolved));\n};\n\n/*** Resolves one published secret reference without exposing a provider error or payload in failures. */\nconst resolveSecretAsync = async (\n secretStore: SecretStoreAdapter,\n actor: FlowSecurityActor,\n ref: SecretRef,\n) => {\n const result = await secretStore.resolve({ scope: scopeOf(actor), ref });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'A required secret is unavailable.');\n return result.data;\n};\n\n/*** Creates a deterministic complete identity from untrusted operation input. */\nconst identityOf = (operation: FlowSecurityOperation): FlowSecurityOperationIdentity =>\n Object.freeze({\n actor: snapshotActor(operation.actor),\n capability: operation.capability,\n idempotencyKey: operation.idempotencyKey,\n operationId: operation.operationId,\n secretRefs: Object.freeze(unique(operation.secretRefs ?? [])),\n ...(operation.target\n ? {\n target: Object.freeze({\n allowedHosts: Object.freeze([...operation.target.allowedHosts]),\n url: operation.target.url,\n }),\n }\n : {}),\n });\n\n/*** Captures a runtime-immutable actor scope before any authorization boundary can await. */\nconst snapshotActor = (actor: FlowSecurityActor): FlowSecurityActor => Object.freeze({ ...actor });\n\n/*** Compares every authorization-relevant operation field without relying on object identity. */\nconst sameIdentity = (\n left: FlowSecurityOperationIdentity,\n right: FlowSecurityOperationIdentity,\n): boolean =>\n left.actor.id === right.actor.id &&\n left.actor.tenantId === right.actor.tenantId &&\n left.actor.projectId === right.actor.projectId &&\n left.actor.environment === right.actor.environment &&\n left.capability === right.capability &&\n left.idempotencyKey === right.idempotencyKey &&\n left.operationId === right.operationId &&\n sameStrings(left.secretRefs, right.secretRefs) &&\n sameTarget(left.target, right.target);\n\n/*** Compares target authority exactly so approvals cannot move an operation to another host. */\nconst sameTarget = (\n left: FlowSecurityTarget | undefined,\n right: FlowSecurityTarget | undefined,\n): boolean =>\n left === undefined || right === undefined\n ? left === right\n : left.url === right.url && sameStrings(left.allowedHosts, right.allowedHosts);\n\n/*** Matches two ordered identity arrays exactly. */\nconst sameStrings = (left: readonly string[], right: readonly string[]): boolean =>\n JSON.stringify(left) === JSON.stringify(right);\n\n/*** Matches a grant only when all actor isolation dimensions are equal. */\nconst isGranted = (\n grants: readonly FlowSecurityGrant[],\n actor: FlowSecurityActor,\n capability: string,\n): boolean =>\n grants.some(\n (grant) =>\n grant.actorId === actor.id &&\n grant.capability === capability &&\n grant.tenantId === actor.tenantId &&\n grant.projectId === actor.projectId &&\n grant.environment === actor.environment,\n );\n/*** Converts a trusted actor boundary to the published secret-store scope. */\nconst scopeOf = (actor: FlowSecurityActor) => ({\n projectId: actor.projectId,\n environment: actor.environment,\n});\n/*** Deduplicates secret references without changing their deterministic order. */\nconst unique = (refs: readonly SecretRef[]): readonly SecretRef[] => [...new Set(refs)];\n/*** Audits a redacted decision before returning a stable denial. */\nconst denyAsync = async (\n audit: FlowSecurityAuditPort,\n identity: FlowSecurityOperationIdentity,\n targetHost: string | undefined,\n reason: FlowSecurityDenialReason,\n): Promise<never> => {\n await writeAuditAsync(audit, identity, targetHost, 'denied', reason);\n throw new FlowSecurityError(reason, denialMessage(reason));\n};\n/*** Emits only identifiers and target host metadata to the audit edge. */\nconst writeAuditAsync = (\n audit: FlowSecurityAuditPort,\n identity: FlowSecurityOperationIdentity,\n targetHost: string | undefined,\n decision: FlowSecurityAuditEvent['decision'],\n reason: FlowSecurityAuditEvent['reason'],\n): Promise<void> =>\n audit.writeAsync({\n actorId: identity.actor.id,\n capability: identity.capability,\n decision,\n operationId: identity.operationId,\n reason,\n secretRefs: identity.secretRefs,\n ...(targetHost ? { targetHost } : {}),\n });\n/*** Audits a credential-management decision without accepting its payload at the audit edge. */\nconst writeCredentialAuditAsync = (\n audit: FlowSecurityAuditPort,\n actor: FlowSecurityActor,\n ref: SecretRef,\n capability: typeof CREATE_CAPABILITY | typeof REVOKE_CAPABILITY,\n): Promise<void> =>\n audit.writeAsync({\n actorId: actor.id,\n capability,\n decision: 'permitted',\n operationId: ref,\n reason: capability === CREATE_CAPABILITY ? 'credential_create' : 'credential_revoke',\n secretRefs: [ref],\n });\n/*** Audits and returns a stable denial for unauthorized credential management. */\nconst denyCredentialAsync = async (\n audit: FlowSecurityAuditPort,\n actor: FlowSecurityActor,\n ref: SecretRef,\n capability: typeof CREATE_CAPABILITY | typeof REVOKE_CAPABILITY,\n): Promise<never> => {\n await audit.writeAsync({\n actorId: actor.id,\n capability,\n decision: 'denied',\n operationId: ref,\n reason: 'permission_denied',\n secretRefs: [ref],\n });\n throw new FlowSecurityError(\n 'permission_denied',\n 'The actor is not permitted to perform this operation.',\n );\n};\n/*** Maps policy reasons to public redaction-safe error text. */\nconst denialMessage = (reason: FlowSecurityDenialReason): string => {\n switch (reason) {\n case 'approval_pending':\n return 'The operation requires an approved Flow approval gate.';\n case 'approval_rejected':\n return 'The Flow approval gate rejected this operation.';\n case 'idempotency_conflict':\n return 'The idempotency key is already bound to another operation.';\n case 'invalid_target':\n return 'The external target is not permitted.';\n case 'permission_denied':\n return 'The actor is not permitted to perform this operation.';\n case 'rate_limited':\n return 'The operation is rate limited.';\n case 'secret_unavailable':\n return 'A required secret is unavailable.';\n }\n const impossible: never = reason;\n return impossible;\n};\n"]}
1
+ {"version":3,"file":"createFlowSecurityPort.js","sourceRoot":"","sources":["../../../../src/features/security/application/createFlowSecurityPort.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SecretRef, SecretStoreAdapter } from '@ankhorage/contracts/secrets';\n\nimport type {\n FlowApprovalStorePort,\n FlowOperationStorePort,\n FlowRateLimitPort,\n FlowResolvedSecrets,\n FlowSecretCreateInput,\n FlowSecretMetadata,\n FlowSecretRevokeInput,\n FlowSecurityActor,\n FlowSecurityAuditEvent,\n FlowSecurityAuditPort,\n FlowSecurityGrant,\n FlowSecurityOperation,\n FlowSecurityOperationIdentity,\n FlowSecurityPermit,\n FlowSecurityPort,\n FlowSecurityTarget,\n} from '../../../types/security.js';\nimport { validateFlowSecurityTarget } from '../domain/validateFlowSecurityTarget.js';\nimport { FlowSecurityError } from '../FlowSecurityError.js';\n\nconst METADATA_CAPABILITY = 'secrets:metadata';\nconst CREATE_CAPABILITY = 'secrets:create';\nconst REVOKE_CAPABILITY = 'secrets:revoke';\ntype FlowSecurityDenialReason = Exclude<\n FlowSecurityAuditEvent['reason'],\n 'credential_create' | 'credential_revoke'\n>;\n\n/*** Creates the provider-neutral server-side authorization, approval, audit, and secret boundary. */\nexport const createFlowSecurityPort = (input: {\n readonly approvalStore: FlowApprovalStorePort;\n readonly audit: FlowSecurityAuditPort;\n readonly grants: readonly FlowSecurityGrant[];\n readonly operationStore: FlowOperationStorePort;\n readonly rateLimit: FlowRateLimitPort;\n readonly secretStore: SecretStoreAdapter;\n}): FlowSecurityPort => ({\n authorizeAsync: async (operation) => authorizeAsync(input, operation),\n createSecretAsync: async (request) => createSecretAsync(input, request),\n getSecretMetadataAsync: async ({ actor, ref }) => getSecretMetadataAsync(input, actor, ref),\n revokeSecretAsync: async (request) => revokeSecretAsync(input, request),\n});\n\n/*** Authorizes and durably claims one exact operation before resolving any secret material. */\nconst authorizeAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n operation: FlowSecurityOperation,\n): Promise<FlowSecurityPermit> => {\n const identity = identityOf(operation);\n const targetHost = validateFlowSecurityTarget(identity.target);\n if (!isGranted(input.grants, identity.actor, identity.capability))\n return denyAsync(input.audit, identity, targetHost, 'permission_denied');\n const approval = await input.approvalStore.getAsync(identity);\n if (approval?.status === 'pending')\n return denyAsync(input.audit, identity, targetHost, 'approval_pending');\n if (approval?.status !== 'approved' || !sameIdentity(approval.operation, identity))\n return denyAsync(input.audit, identity, targetHost, 'approval_rejected');\n if (!(await input.operationStore.claimAsync(identity)))\n return denyAsync(input.audit, identity, targetHost, 'idempotency_conflict');\n try {\n if (\n !(await input.rateLimit.acquireAsync({\n actor: identity.actor,\n capability: identity.capability,\n operationId: identity.operationId,\n }))\n )\n return denyAfterReleaseAsync(input, identity, targetHost, 'rate_limited');\n await writeAuditAsync(input.audit, identity, targetHost, 'permitted', 'permission_denied');\n return createPermit(input, identity);\n } catch (error) {\n await input.operationStore.releaseAsync(identity);\n throw error;\n }\n};\n\n/*** Creates a one-use permit that atomically consumes the durable operation claim before side effects. */\nconst createPermit = (\n input: Parameters<typeof createFlowSecurityPort>[0],\n identity: FlowSecurityOperationIdentity,\n): FlowSecurityPermit => ({\n operation: identity,\n executeAsync: async (callback) => {\n if (!(await input.operationStore.consumeAsync(identity)))\n throw new FlowSecurityError(\n 'idempotency_conflict',\n 'The operation permit has already been consumed.',\n );\n return resolveAndExecuteAsync(input, identity.actor, identity.secretRefs, callback);\n },\n});\n\n/*** Releases an unconsumed claim before emitting a retryable denial. */\nconst denyAfterReleaseAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n identity: FlowSecurityOperationIdentity,\n targetHost: string | undefined,\n reason: FlowSecurityDenialReason,\n): Promise<never> => {\n await input.operationStore.releaseAsync(identity);\n return denyAsync(input.audit, identity, targetHost, reason);\n};\n\n/*** Creates a credential only after an explicit server-side scoped grant and redacted audit. */\nconst createSecretAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n request: FlowSecretCreateInput,\n): Promise<FlowSecretMetadata> => {\n const actor = snapshotActor(request.actor);\n if (!isGranted(input.grants, actor, CREATE_CAPABILITY))\n return denyCredentialAsync(input.audit, actor, request.ref, CREATE_CAPABILITY);\n await writeCredentialAuditAsync(input.audit, actor, request.ref, CREATE_CAPABILITY);\n const result = await input.secretStore.create({\n kind: request.kind,\n payload: request.payload,\n ...(request.provider === undefined ? {} : { provider: request.provider }),\n ref: request.ref,\n scope: scopeOf(actor),\n });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'The requested secret is unavailable.');\n return result.data;\n};\n\n/*** Returns metadata only after an explicit actor, tenant, project, and environment grant. */\nconst getSecretMetadataAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n actor: FlowSecurityActor,\n ref: SecretRef,\n) => {\n const identity = snapshotActor(actor);\n if (!isGranted(input.grants, identity, METADATA_CAPABILITY))\n throw new FlowSecurityError(\n 'permission_denied',\n 'The actor is not permitted to inspect secrets.',\n );\n const result = await input.secretStore.getMetadata({ scope: scopeOf(identity), ref });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'The requested secret is unavailable.');\n return result.data;\n};\n\n/*** Revokes a credential only after an explicit server-side scoped grant and redacted audit. */\nconst revokeSecretAsync = async (\n input: Parameters<typeof createFlowSecurityPort>[0],\n request: FlowSecretRevokeInput,\n): Promise<void> => {\n const actor = snapshotActor(request.actor);\n if (!isGranted(input.grants, actor, REVOKE_CAPABILITY))\n return denyCredentialAsync(input.audit, actor, request.ref, REVOKE_CAPABILITY);\n await writeCredentialAuditAsync(input.audit, actor, request.ref, REVOKE_CAPABILITY);\n const result = await input.secretStore.remove({ ref: request.ref, scope: scopeOf(actor) });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'The requested secret is unavailable.');\n};\n\n/*** Resolves secrets only in the server-side callback after durable consumption succeeds. */\nconst resolveAndExecuteAsync = async <T>(\n input: Parameters<typeof createFlowSecurityPort>[0],\n actor: FlowSecurityActor,\n secretRefs: readonly SecretRef[],\n callback: (secrets: FlowResolvedSecrets) => Promise<T>,\n): Promise<T> => {\n const resolved = await Promise.all(\n secretRefs.map(\n async (ref) => [ref, await resolveSecretAsync(input.secretStore, actor, ref)] as const,\n ),\n );\n return callback(Object.fromEntries(resolved));\n};\n\n/*** Resolves one published secret reference without exposing a provider error or payload in failures. */\nconst resolveSecretAsync = async (\n secretStore: SecretStoreAdapter,\n actor: FlowSecurityActor,\n ref: SecretRef,\n) => {\n const result = await secretStore.resolve({ scope: scopeOf(actor), ref });\n if (!result.ok)\n throw new FlowSecurityError('secret_unavailable', 'A required secret is unavailable.');\n return result.data;\n};\n\n/*** Creates a deterministic complete identity from untrusted operation input. */\nconst identityOf = (operation: FlowSecurityOperation): FlowSecurityOperationIdentity =>\n Object.freeze({\n actor: snapshotActor(operation.actor),\n capability: operation.capability,\n idempotencyKey: operation.idempotencyKey,\n operationId: operation.operationId,\n secretRefs: Object.freeze(unique(operation.secretRefs ?? [])),\n ...(operation.target\n ? {\n target: Object.freeze({\n allowedHosts: Object.freeze([...operation.target.allowedHosts]),\n url: operation.target.url,\n }),\n }\n : {}),\n });\n\n/*** Captures a runtime-immutable actor scope before any authorization boundary can await. */\nconst snapshotActor = (actor: FlowSecurityActor): FlowSecurityActor => Object.freeze({ ...actor });\n\n/*** Compares every authorization-relevant operation field without relying on object identity. */\nconst sameIdentity = (\n left: FlowSecurityOperationIdentity,\n right: FlowSecurityOperationIdentity,\n): boolean =>\n left.actor.id === right.actor.id &&\n left.actor.tenantId === right.actor.tenantId &&\n left.actor.projectId === right.actor.projectId &&\n left.actor.environment === right.actor.environment &&\n left.capability === right.capability &&\n left.idempotencyKey === right.idempotencyKey &&\n left.operationId === right.operationId &&\n sameStrings(left.secretRefs, right.secretRefs) &&\n sameTarget(left.target, right.target);\n\n/*** Compares target authority exactly so approvals cannot move an operation to another host. */\nconst sameTarget = (\n left: FlowSecurityTarget | undefined,\n right: FlowSecurityTarget | undefined,\n): boolean =>\n left === undefined || right === undefined\n ? left === right\n : left.url === right.url && sameStrings(left.allowedHosts, right.allowedHosts);\n\n/*** Matches two ordered identity arrays exactly. */\nconst sameStrings = (left: readonly string[], right: readonly string[]): boolean =>\n JSON.stringify(left) === JSON.stringify(right);\n\n/*** Matches a grant only when all actor isolation dimensions are equal. */\nconst isGranted = (\n grants: readonly FlowSecurityGrant[],\n actor: FlowSecurityActor,\n capability: string,\n): boolean =>\n grants.some(\n (grant) =>\n grant.actorId === actor.id &&\n grant.capability === capability &&\n grant.tenantId === actor.tenantId &&\n grant.projectId === actor.projectId &&\n grant.environment === actor.environment,\n );\n/*** Converts a trusted actor boundary to the published secret-store scope. */\nconst scopeOf = (actor: FlowSecurityActor) => ({\n projectId: actor.projectId,\n environment: actor.environment,\n});\n/*** Deduplicates secret references without changing their deterministic order. */\nconst unique = (refs: readonly SecretRef[]): readonly SecretRef[] => [...new Set(refs)];\n/*** Audits a redacted decision before returning a stable denial. */\nconst denyAsync = async (\n audit: FlowSecurityAuditPort,\n identity: FlowSecurityOperationIdentity,\n targetHost: string | undefined,\n reason: FlowSecurityDenialReason,\n): Promise<never> => {\n await writeAuditAsync(audit, identity, targetHost, 'denied', reason);\n throw new FlowSecurityError(reason, denialMessage(reason));\n};\n/*** Emits only identifiers and target host metadata to the audit edge. */\nconst writeAuditAsync = (\n audit: FlowSecurityAuditPort,\n identity: FlowSecurityOperationIdentity,\n targetHost: string | undefined,\n decision: FlowSecurityAuditEvent['decision'],\n reason: FlowSecurityAuditEvent['reason'],\n): Promise<void> =>\n audit.writeAsync({\n actorId: identity.actor.id,\n capability: identity.capability,\n decision,\n operationId: identity.operationId,\n reason,\n secretRefs: identity.secretRefs,\n ...(targetHost ? { targetHost } : {}),\n });\n/*** Audits a credential-management decision without accepting its payload at the audit edge. */\nconst writeCredentialAuditAsync = (\n audit: FlowSecurityAuditPort,\n actor: FlowSecurityActor,\n ref: SecretRef,\n capability: typeof CREATE_CAPABILITY | typeof REVOKE_CAPABILITY,\n): Promise<void> =>\n audit.writeAsync({\n actorId: actor.id,\n capability,\n decision: 'permitted',\n operationId: ref,\n reason: capability === CREATE_CAPABILITY ? 'credential_create' : 'credential_revoke',\n secretRefs: [ref],\n });\n/*** Audits and returns a stable denial for unauthorized credential management. */\nconst denyCredentialAsync = async (\n audit: FlowSecurityAuditPort,\n actor: FlowSecurityActor,\n ref: SecretRef,\n capability: typeof CREATE_CAPABILITY | typeof REVOKE_CAPABILITY,\n): Promise<never> => {\n await audit.writeAsync({\n actorId: actor.id,\n capability,\n decision: 'denied',\n operationId: ref,\n reason: 'permission_denied',\n secretRefs: [ref],\n });\n throw new FlowSecurityError(\n 'permission_denied',\n 'The actor is not permitted to perform this operation.',\n );\n};\n/*** Maps policy reasons to public redaction-safe error text. */\nconst denialMessage = (reason: FlowSecurityDenialReason): string => {\n switch (reason) {\n case 'approval_pending':\n return 'The operation requires an approved Flow approval gate.';\n case 'approval_rejected':\n return 'The Flow approval gate rejected this operation.';\n case 'idempotency_conflict':\n return 'The idempotency key is already bound to another operation.';\n case 'invalid_target':\n return 'The external target is not permitted.';\n case 'permission_denied':\n return 'The actor is not permitted to perform this operation.';\n case 'rate_limited':\n return 'The operation is rate limited.';\n case 'secret_unavailable':\n return 'A required secret is unavailable.';\n }\n const impossible: never = reason;\n return impossible;\n};\n"]}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  /*** Headless graph compilation and deterministic single-process execution. */
2
+ export { createDurableRunAsync } from './features/execution/application/createDurableRunAsync.js';
2
3
  export { executeFlowAsync } from './features/execution/application/executeFlowAsync.js';
4
+ export { calculateRetryDueAt } from './features/execution/domain/calculateRetryDueAt.js';
3
5
  export { compileFlowGraph } from './features/execution/domain/compileFlowGraph.js';
4
6
  export { createDefaultNodeRegistry } from './features/execution/domain/createDefaultNodeRegistry.js';
7
+ export { createDurableRun } from './features/execution/domain/createDurableRun.js';
8
+ export { createRetryWork } from './features/execution/domain/createRetryWork.js';
9
+ export { isCronDueAt } from './features/execution/domain/isCronDueAt.js';
10
+ export type { FlowPersistencePort } from './features/execution/ports/FlowPersistencePort.js';
5
11
  export { createFlowSecurityPort } from './features/security/application/createFlowSecurityPort.js';
6
12
  export { FlowSecurityError } from './features/security/FlowSecurityError.js';
13
+ export type { FlowDefinitionRef, FlowDurableRun, FlowDurableWork, FlowOutboxRecord, FlowRetryPolicy, FlowRunCheckpoint, } from './types/durableExecution.js';
7
14
  export type { FlowClock, FlowCompiledGraph, FlowExecutionResult, FlowNodeOperation, FlowNodeOperationResult, FlowNodeRegistry, FlowRunRequest, } from './types/execution.js';
8
15
  export type { FlowApprovalStorePort, FlowOperationStorePort, FlowOutboundTransportPort, FlowRateLimitPort, FlowResolvedSecrets, FlowSecretCreateInput, FlowSecretMetadata, FlowSecretRevokeInput, FlowSecurityActor, FlowSecurityApproval, FlowSecurityAuditEvent, FlowSecurityAuditPort, FlowSecurityGrant, FlowSecurityOperation, FlowSecurityOperationIdentity, FlowSecurityPermit, FlowSecurityPort, FlowSecurityTarget, } from './types/security.js';
9
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,sBAAsB,EAAE,MAAM,2DAA2D,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,6BAA6B,EAC7B,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AACzE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mDAAmD,CAAC;AAC7F,OAAO,EAAE,sBAAsB,EAAE,MAAM,2DAA2D,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,6BAA6B,EAC7B,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  /*** Headless graph compilation and deterministic single-process execution. */
2
+ export { createDurableRunAsync } from './features/execution/application/createDurableRunAsync.js';
2
3
  export { executeFlowAsync } from './features/execution/application/executeFlowAsync.js';
4
+ export { calculateRetryDueAt } from './features/execution/domain/calculateRetryDueAt.js';
3
5
  export { compileFlowGraph } from './features/execution/domain/compileFlowGraph.js';
4
6
  export { createDefaultNodeRegistry } from './features/execution/domain/createDefaultNodeRegistry.js';
7
+ export { createDurableRun } from './features/execution/domain/createDurableRun.js';
8
+ export { createRetryWork } from './features/execution/domain/createRetryWork.js';
9
+ export { isCronDueAt } from './features/execution/domain/isCronDueAt.js';
5
10
  export { createFlowSecurityPort } from './features/security/application/createFlowSecurityPort.js';
6
11
  export { FlowSecurityError } from './features/security/FlowSecurityError.js';
7
12
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,sBAAsB,EAAE,MAAM,2DAA2D,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC","sourcesContent":["/*** Headless graph compilation and deterministic single-process execution. */\nexport { executeFlowAsync } from './features/execution/application/executeFlowAsync.js';\nexport { compileFlowGraph } from './features/execution/domain/compileFlowGraph.js';\nexport { createDefaultNodeRegistry } from './features/execution/domain/createDefaultNodeRegistry.js';\nexport { createFlowSecurityPort } from './features/security/application/createFlowSecurityPort.js';\nexport { FlowSecurityError } from './features/security/FlowSecurityError.js';\nexport type {\n FlowClock,\n FlowCompiledGraph,\n FlowExecutionResult,\n FlowNodeOperation,\n FlowNodeOperationResult,\n FlowNodeRegistry,\n FlowRunRequest,\n} from './types/execution.js';\nexport type {\n FlowApprovalStorePort,\n FlowOperationStorePort,\n FlowOutboundTransportPort,\n FlowRateLimitPort,\n FlowResolvedSecrets,\n FlowSecretCreateInput,\n FlowSecretMetadata,\n FlowSecretRevokeInput,\n FlowSecurityActor,\n FlowSecurityApproval,\n FlowSecurityAuditEvent,\n FlowSecurityAuditPort,\n FlowSecurityGrant,\n FlowSecurityOperation,\n FlowSecurityOperationIdentity,\n FlowSecurityPermit,\n FlowSecurityPort,\n FlowSecurityTarget,\n} from './types/security.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAEzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2DAA2D,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC","sourcesContent":["/*** Headless graph compilation and deterministic single-process execution. */\nexport { createDurableRunAsync } from './features/execution/application/createDurableRunAsync.js';\nexport { executeFlowAsync } from './features/execution/application/executeFlowAsync.js';\nexport { calculateRetryDueAt } from './features/execution/domain/calculateRetryDueAt.js';\nexport { compileFlowGraph } from './features/execution/domain/compileFlowGraph.js';\nexport { createDefaultNodeRegistry } from './features/execution/domain/createDefaultNodeRegistry.js';\nexport { createDurableRun } from './features/execution/domain/createDurableRun.js';\nexport { createRetryWork } from './features/execution/domain/createRetryWork.js';\nexport { isCronDueAt } from './features/execution/domain/isCronDueAt.js';\nexport type { FlowPersistencePort } from './features/execution/ports/FlowPersistencePort.js';\nexport { createFlowSecurityPort } from './features/security/application/createFlowSecurityPort.js';\nexport { FlowSecurityError } from './features/security/FlowSecurityError.js';\nexport type {\n FlowDefinitionRef,\n FlowDurableRun,\n FlowDurableWork,\n FlowOutboxRecord,\n FlowRetryPolicy,\n FlowRunCheckpoint,\n} from './types/durableExecution.js';\nexport type {\n FlowClock,\n FlowCompiledGraph,\n FlowExecutionResult,\n FlowNodeOperation,\n FlowNodeOperationResult,\n FlowNodeRegistry,\n FlowRunRequest,\n} from './types/execution.js';\nexport type {\n FlowApprovalStorePort,\n FlowOperationStorePort,\n FlowOutboundTransportPort,\n FlowRateLimitPort,\n FlowResolvedSecrets,\n FlowSecretCreateInput,\n FlowSecretMetadata,\n FlowSecretRevokeInput,\n FlowSecurityActor,\n FlowSecurityApproval,\n FlowSecurityAuditEvent,\n FlowSecurityAuditPort,\n FlowSecurityGrant,\n FlowSecurityOperation,\n FlowSecurityOperationIdentity,\n FlowSecurityPermit,\n FlowSecurityPort,\n FlowSecurityTarget,\n} from './types/security.js';\n"]}
@@ -0,0 +1,55 @@
1
+ import type { DataContractValue } from '@ankhorage/contracts/data';
2
+ import type { FlowSecurityOperation } from './security.js';
3
+ export interface FlowDefinitionRef {
4
+ readonly id: string;
5
+ readonly version: number;
6
+ }
7
+ export interface FlowDurableRun {
8
+ readonly checkpoint?: FlowRunCheckpoint;
9
+ readonly createdAt: string;
10
+ readonly flow: FlowDefinitionRef;
11
+ readonly id: string;
12
+ readonly revision: number;
13
+ readonly status: 'cancelled' | 'failed' | 'queued' | 'running' | 'succeeded' | 'waiting_approval';
14
+ readonly updatedAt: string;
15
+ }
16
+ export interface FlowDurableWork {
17
+ readonly attempt: number;
18
+ readonly dueAt: string;
19
+ /** Explicitly separates side-effect-free internal work from a security-protected provider effect. */
20
+ readonly effectKind: 'internal' | 'protected';
21
+ readonly flow: FlowDefinitionRef;
22
+ readonly id: string;
23
+ readonly idempotencyKey?: string;
24
+ readonly lease?: {
25
+ readonly expiresAt: string;
26
+ readonly fencingToken: number;
27
+ readonly workerId: string;
28
+ };
29
+ readonly runId: string;
30
+ readonly securityOperation?: FlowSecurityOperation;
31
+ readonly stepId?: string;
32
+ readonly status: 'cancelled' | 'claimed' | 'completed' | 'ready';
33
+ }
34
+ export interface FlowRetryPolicy {
35
+ readonly baseDelayMs: number;
36
+ readonly maxAttempts: number;
37
+ readonly maxDelayMs: number;
38
+ }
39
+ /** Persisted state for each completed or in-flight Flow step. */
40
+ export interface FlowRunCheckpoint {
41
+ readonly completedStepIds: readonly string[];
42
+ readonly data?: DataContractValue;
43
+ readonly failedStepIds: readonly string[];
44
+ }
45
+ /** A durable outbox record separates at-least-once delivery from an idempotent application effect. */
46
+ export interface FlowOutboxRecord {
47
+ readonly createdAt: string;
48
+ readonly id: string;
49
+ readonly idempotencyKey: string;
50
+ readonly payload?: DataContractValue;
51
+ readonly runId: string;
52
+ readonly status: 'delivered' | 'pending';
53
+ readonly workId: string;
54
+ }
55
+ //# sourceMappingURL=durableExecution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durableExecution.d.ts","sourceRoot":"","sources":["../../src/types/durableExecution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,kBAAkB,CAAC;IAClG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qGAAqG;IACrG,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;CAClE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=durableExecution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durableExecution.js","sourceRoot":"","sources":["../../src/types/durableExecution.ts"],"names":[],"mappings":"","sourcesContent":["import type { DataContractValue } from '@ankhorage/contracts/data';\n\nimport type { FlowSecurityOperation } from './security.js';\n\nexport interface FlowDefinitionRef {\n readonly id: string;\n readonly version: number;\n}\n\nexport interface FlowDurableRun {\n readonly checkpoint?: FlowRunCheckpoint;\n readonly createdAt: string;\n readonly flow: FlowDefinitionRef;\n readonly id: string;\n readonly revision: number;\n readonly status: 'cancelled' | 'failed' | 'queued' | 'running' | 'succeeded' | 'waiting_approval';\n readonly updatedAt: string;\n}\n\nexport interface FlowDurableWork {\n readonly attempt: number;\n readonly dueAt: string;\n /** Explicitly separates side-effect-free internal work from a security-protected provider effect. */\n readonly effectKind: 'internal' | 'protected';\n readonly flow: FlowDefinitionRef;\n readonly id: string;\n readonly idempotencyKey?: string;\n readonly lease?: {\n readonly expiresAt: string;\n readonly fencingToken: number;\n readonly workerId: string;\n };\n readonly runId: string;\n readonly securityOperation?: FlowSecurityOperation;\n readonly stepId?: string;\n readonly status: 'cancelled' | 'claimed' | 'completed' | 'ready';\n}\n\nexport interface FlowRetryPolicy {\n readonly baseDelayMs: number;\n readonly maxAttempts: number;\n readonly maxDelayMs: number;\n}\n\n/** Persisted state for each completed or in-flight Flow step. */\nexport interface FlowRunCheckpoint {\n readonly completedStepIds: readonly string[];\n readonly data?: DataContractValue;\n readonly failedStepIds: readonly string[];\n}\n\n/** A durable outbox record separates at-least-once delivery from an idempotent application effect. */\nexport interface FlowOutboxRecord {\n readonly createdAt: string;\n readonly id: string;\n readonly idempotencyKey: string;\n readonly payload?: DataContractValue;\n readonly runId: string;\n readonly status: 'delivered' | 'pending';\n readonly workId: string;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/flow",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Standalone workflow engine foundation for Ankhorage.",
5
5
  "keywords": [
6
6
  "ankhorage",
@@ -21,6 +21,10 @@
21
21
  "types": "./dist/ai.d.ts",
22
22
  "default": "./dist/ai.js"
23
23
  },
24
+ "./execution": {
25
+ "types": "./dist/execution.d.ts",
26
+ "default": "./dist/execution.js"
27
+ },
24
28
  "./mcp": {
25
29
  "types": "./dist/mcp.d.ts",
26
30
  "default": "./dist/mcp.js"
@@ -0,0 +1,9 @@
1
+ /*** Node-specific durable-execution adapters. */
2
+ export { FileFlowPersistenceAdapter } from './features/execution/adapters/FileFlowPersistenceAdapter.js';
3
+ export { cancelDurableRunAsync } from './features/execution/application/cancelDurableRunAsync.js';
4
+ export {
5
+ type FlowDurableEffectPort,
6
+ type FlowWorkerOutcome,
7
+ type FlowWorkerResult,
8
+ runDurableWorkerAsync,
9
+ } from './features/execution/application/runDurableWorkerAsync.js';
@@ -0,0 +1,333 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ import type {
6
+ FlowDurableRun,
7
+ FlowDurableWork,
8
+ FlowOutboxRecord,
9
+ } from '../../../types/durableExecution.js';
10
+ import type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';
11
+
12
+ /*** Create a self-hostable, single-process JSON persistence adapter for durable Flow state. */
13
+ export const FileFlowPersistenceAdapter = (path: string): FlowPersistencePort => {
14
+ const persistencePath = resolve(path);
15
+ const serializedCall = getSerializedCall(persistencePath);
16
+ return {
17
+ cancelRunWorkAsync: (runId) =>
18
+ serializedCall.runAsync(() => cancelRunWorkAsync(persistencePath, runId)),
19
+ claimDueWorkAsync: (input) =>
20
+ serializedCall.runAsync(() => claimDueWorkAsync(persistencePath, input)),
21
+ completeWorkAsync: (input) =>
22
+ serializedCall.runAsync(() => completeWorkAsync(persistencePath, input)),
23
+ createOutboxAsync: (record) =>
24
+ serializedCall.runAsync(() => createOutboxAsync(persistencePath, record)),
25
+ createRunAsync: (run) => serializedCall.runAsync(() => createRunAsync(persistencePath, run)),
26
+ enqueueWorkAsync: (work) =>
27
+ serializedCall.runAsync(() => enqueueWorkAsync(persistencePath, work)),
28
+ markOutboxDeliveredAsync: (id) =>
29
+ serializedCall.runAsync(() => markOutboxDeliveredAsync(persistencePath, id)),
30
+ readOutboxAsync: (id) => serializedCall.runAsync(() => readOutboxAsync(persistencePath, id)),
31
+ readRunAsync: (id) => serializedCall.runAsync(() => readRunAsync(persistencePath, id)),
32
+ recoverExpiredClaimsAsync: (now) =>
33
+ serializedCall.runAsync(() => recoverExpiredClaimsAsync(persistencePath, now)),
34
+ releaseWorkAsync: (input) =>
35
+ serializedCall.runAsync(() => releaseWorkAsync(persistencePath, input)),
36
+ transitionRunAsync: (input) =>
37
+ serializedCall.runAsync(() => transitionRunAsync(persistencePath, input)),
38
+ };
39
+ };
40
+
41
+ /** Cancel every nonterminal delivery for a run after its durable cancellation transition. */
42
+ const cancelRunWorkAsync = async (path: string, runId: string): Promise<readonly string[]> =>
43
+ updateStoreAsync(path, (store) => {
44
+ const cancelled = store.work
45
+ .filter(
46
+ (work) => work.runId === runId && (work.status === 'ready' || work.status === 'claimed'),
47
+ )
48
+ .map((work) => ({ ...work, lease: undefined, status: 'cancelled' as const }));
49
+ return {
50
+ result: cancelled.map((work) => work.id),
51
+ store: { ...store, work: replaceWork(store.work, cancelled) },
52
+ };
53
+ });
54
+
55
+ interface FileFlowStore {
56
+ readonly nextLeaseFencingToken: number;
57
+ readonly outbox: readonly FlowOutboxRecord[];
58
+ readonly runs: readonly FlowDurableRun[];
59
+ readonly work: readonly FlowDurableWork[];
60
+ }
61
+
62
+ /** Persist a stable outbox identity before any external effect is attempted. */
63
+ const createOutboxAsync = async (path: string, record: FlowOutboxRecord): Promise<void> =>
64
+ updateStoreAsync(path, (store) => {
65
+ const existing = store.outbox.find((candidate) => candidate.id === record.id);
66
+ if (existing !== undefined && existing.idempotencyKey !== record.idempotencyKey)
67
+ throw new Error(`FLOW_OUTBOX_CONFLICT:${record.id}`);
68
+ return {
69
+ result: undefined,
70
+ store: existing === undefined ? { ...store, outbox: [...store.outbox, copy(record)] } : store,
71
+ };
72
+ });
73
+
74
+ interface SerializedCall {
75
+ readonly runAsync: <Result>(operation: () => Promise<Result>) => Promise<Result>;
76
+ }
77
+
78
+ const serializedCallsByPath = new Map<string, SerializedCall>();
79
+
80
+ /*** Share serialization for every adapter instance targeting one normalized persistence path. */
81
+ const getSerializedCall = (path: string): SerializedCall => {
82
+ const existing = serializedCallsByPath.get(path);
83
+ if (existing !== undefined) return existing;
84
+ const serializedCall = createSerializedCall();
85
+ serializedCallsByPath.set(path, serializedCall);
86
+ return serializedCall;
87
+ };
88
+
89
+ /*** Serialize one file path so concurrent callbacks cannot claim or overwrite the same state. */
90
+ const createSerializedCall = (): SerializedCall => {
91
+ let previous = Promise.resolve();
92
+ return {
93
+ runAsync: async (operation) => {
94
+ const current = previous.then(operation, operation);
95
+ previous = current.then(
96
+ () => undefined,
97
+ () => undefined,
98
+ );
99
+ return current;
100
+ },
101
+ };
102
+ };
103
+
104
+ /*** Claim due work in stable order and record a lease before it is returned to a worker. */
105
+ const claimDueWorkAsync = async (
106
+ path: string,
107
+ input: Parameters<FlowPersistencePort['claimDueWorkAsync']>[0],
108
+ ): Promise<readonly FlowDurableWork[]> =>
109
+ updateStoreAsync(path, (store) => {
110
+ const claimed = store.work
111
+ .filter((work) => work.status === 'ready' && work.dueAt <= input.now.toISOString())
112
+ .sort(
113
+ (left, right) => left.dueAt.localeCompare(right.dueAt) || left.id.localeCompare(right.id),
114
+ )
115
+ .slice(0, input.limit)
116
+ .map((work, index) => claimWork(work, input, store.nextLeaseFencingToken + index));
117
+ return {
118
+ result: claimed,
119
+ store: {
120
+ ...store,
121
+ nextLeaseFencingToken: store.nextLeaseFencingToken + claimed.length,
122
+ work: replaceWork(store.work, claimed),
123
+ },
124
+ };
125
+ });
126
+
127
+ /*** Complete only the unexpired fenced lease returned by the successful claim. */
128
+ const completeWorkAsync = async (
129
+ path: string,
130
+ { id, leaseFencingToken, now, workerId }: Parameters<FlowPersistencePort['completeWorkAsync']>[0],
131
+ ): Promise<boolean> =>
132
+ updateStoreAsync(path, (store) => {
133
+ const work = store.work.find((candidate) => candidate.id === id);
134
+ if (
135
+ work?.status !== 'claimed' ||
136
+ work.lease?.workerId !== workerId ||
137
+ work.lease.fencingToken !== leaseFencingToken ||
138
+ work.lease.expiresAt <= now.toISOString()
139
+ )
140
+ return { result: false, store };
141
+ return {
142
+ result: true,
143
+ store: { ...store, work: replaceWork(store.work, [{ ...work, status: 'completed' }]) },
144
+ };
145
+ });
146
+
147
+ /*** Persist a new run once, rejecting a duplicate durable identity. */
148
+ const createRunAsync = async (path: string, run: FlowDurableRun): Promise<void> =>
149
+ updateStoreAsync(path, (store) => {
150
+ if (store.runs.some((candidate) => candidate.id === run.id))
151
+ throw new Error(`FLOW_RUN_EXISTS:${run.id}`);
152
+ return { result: undefined, store: { ...store, runs: [...store.runs, copy(run)] } };
153
+ });
154
+
155
+ /*** Persist one immutable work identity once, rejecting duplicate delivery records. */
156
+ const enqueueWorkAsync = async (path: string, work: FlowDurableWork): Promise<void> =>
157
+ updateStoreAsync(path, (store) => {
158
+ if (store.work.some((candidate) => candidate.id === work.id))
159
+ throw new Error(`FLOW_WORK_EXISTS:${work.id}`);
160
+ return { result: undefined, store: { ...store, work: [...store.work, copy(work)] } };
161
+ });
162
+
163
+ /** Mark an effect delivery complete; duplicate marks are harmless and do not rerun the effect. */
164
+ const markOutboxDeliveredAsync = async (path: string, id: string): Promise<boolean> =>
165
+ updateStoreAsync(path, (store) => {
166
+ const record = store.outbox.find((candidate) => candidate.id === id);
167
+ if (record === undefined) return { result: false, store };
168
+ return {
169
+ result: true,
170
+ store: {
171
+ ...store,
172
+ outbox: replaceOutbox(store.outbox, [{ ...record, status: 'delivered' }]),
173
+ },
174
+ };
175
+ });
176
+
177
+ /** Read a copied outbox snapshot without exposing adapter-owned mutable state. */
178
+ const readOutboxAsync = async (path: string, id: string): Promise<FlowOutboxRecord | undefined> =>
179
+ copy((await readStoreAsync(path)).outbox.find((record) => record.id === id));
180
+
181
+ /*** Read a copied run snapshot so callers cannot mutate persisted state in-place. */
182
+ const readRunAsync = async (path: string, id: string): Promise<FlowDurableRun | undefined> =>
183
+ copy((await readStoreAsync(path)).runs.find((run) => run.id === id));
184
+
185
+ /*** Make expired claims available for recovery after a worker crash or stop. */
186
+ const recoverExpiredClaimsAsync = async (path: string, now: Date): Promise<readonly string[]> =>
187
+ updateStoreAsync(path, (store) => {
188
+ const recovered = store.work
189
+ .filter(
190
+ (work) =>
191
+ work.status === 'claimed' &&
192
+ work.lease?.expiresAt !== undefined &&
193
+ work.lease.expiresAt <= now.toISOString(),
194
+ )
195
+ .map((work) => ({ ...work, lease: undefined, status: 'ready' as const }));
196
+ return {
197
+ result: recovered.map((work) => work.id),
198
+ store: { ...store, work: replaceWork(store.work, recovered) },
199
+ };
200
+ });
201
+
202
+ /** Return a fenced claim to the queue without permitting a stale worker to reschedule it. */
203
+ const releaseWorkAsync = async (
204
+ path: string,
205
+ input: Parameters<FlowPersistencePort['releaseWorkAsync']>[0],
206
+ ): Promise<boolean> =>
207
+ updateStoreAsync(path, (store) => {
208
+ const work = store.work.find((candidate) => candidate.id === input.id);
209
+ if (
210
+ work?.status !== 'claimed' ||
211
+ work.lease?.workerId !== input.workerId ||
212
+ work.lease.fencingToken !== input.leaseFencingToken
213
+ )
214
+ return { result: false, store };
215
+ return {
216
+ result: true,
217
+ store: {
218
+ ...store,
219
+ work: replaceWork(store.work, [
220
+ { ...work, dueAt: input.dueAt, lease: undefined, status: 'ready' },
221
+ ]),
222
+ },
223
+ };
224
+ });
225
+
226
+ /*** Store a revision-checked run transition while preserving its immutable flow reference. */
227
+ const transitionRunAsync = async (
228
+ path: string,
229
+ { expectedRevision, run }: Parameters<FlowPersistencePort['transitionRunAsync']>[0],
230
+ ): Promise<boolean> =>
231
+ updateStoreAsync(path, (store) => {
232
+ const current = store.runs.find((candidate) => candidate.id === run.id);
233
+ if (!sameFlow(current ?? run, run) || current?.revision !== expectedRevision)
234
+ return { result: false, store };
235
+ if (run.revision !== expectedRevision + 1) throw new Error('FLOW_RUN_REVISION_INVALID');
236
+ return { result: true, store: { ...store, runs: replaceRuns(store.runs, [copy(run)]) } };
237
+ });
238
+
239
+ /*** Build a claimed work record with an explicit bounded lease. */
240
+ const claimWork = (
241
+ work: FlowDurableWork,
242
+ input: Parameters<FlowPersistencePort['claimDueWorkAsync']>[0],
243
+ fencingToken: number,
244
+ ): FlowDurableWork => ({
245
+ ...work,
246
+ lease: {
247
+ expiresAt: new Date(input.now.getTime() + input.leaseDurationMs).toISOString(),
248
+ fencingToken,
249
+ workerId: input.workerId,
250
+ },
251
+ status: 'claimed',
252
+ });
253
+
254
+ /*** Replace changed work entries by identity without exposing a mutable adapter collection. */
255
+ const replaceWork = (
256
+ work: readonly FlowDurableWork[],
257
+ replacements: readonly FlowDurableWork[],
258
+ ): readonly FlowDurableWork[] =>
259
+ work.map(
260
+ (candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate,
261
+ );
262
+
263
+ /*** Replace changed run entries by identity without exposing a mutable adapter collection. */
264
+ const replaceRuns = (
265
+ runs: readonly FlowDurableRun[],
266
+ replacements: readonly FlowDurableRun[],
267
+ ): readonly FlowDurableRun[] =>
268
+ runs.map(
269
+ (candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate,
270
+ );
271
+
272
+ /** Replace durable outbox records by identity. */
273
+ const replaceOutbox = (
274
+ records: readonly FlowOutboxRecord[],
275
+ replacements: readonly FlowOutboxRecord[],
276
+ ): readonly FlowOutboxRecord[] =>
277
+ records.map(
278
+ (candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate,
279
+ );
280
+
281
+ /*** Read persisted state or initialize an empty store when it has not yet been created. */
282
+ const readStoreAsync = async (path: string): Promise<FileFlowStore> => {
283
+ try {
284
+ return readFileStore(JSON.parse(await readFile(path, 'utf8')));
285
+ } catch (reason) {
286
+ if (isMissingFile(reason)) return { nextLeaseFencingToken: 1, outbox: [], runs: [], work: [] };
287
+ throw reason;
288
+ }
289
+ };
290
+
291
+ /*** Validate the adapter-owned persistence envelope before using its fencing counter. */
292
+ const readFileStore = (value: unknown): FileFlowStore => {
293
+ if (
294
+ typeof value !== 'object' ||
295
+ value === null ||
296
+ !('nextLeaseFencingToken' in value) ||
297
+ typeof value.nextLeaseFencingToken !== 'number' ||
298
+ !Number.isSafeInteger(value.nextLeaseFencingToken) ||
299
+ value.nextLeaseFencingToken < 1 ||
300
+ !('runs' in value) ||
301
+ !Array.isArray(value.runs) ||
302
+ !('work' in value) ||
303
+ !Array.isArray(value.work) ||
304
+ !('outbox' in value) ||
305
+ !Array.isArray(value.outbox)
306
+ )
307
+ throw new Error('FLOW_PERSISTENCE_INVALID');
308
+ return value as FileFlowStore;
309
+ };
310
+
311
+ /*** Apply one single-process transaction and atomically replace the persisted JSON snapshot. */
312
+ const updateStoreAsync = async <Result>(
313
+ path: string,
314
+ update: (store: FileFlowStore) => { readonly result: Result; readonly store: FileFlowStore },
315
+ ): Promise<Result> => {
316
+ const updated = update(await readStoreAsync(path));
317
+ await mkdir(dirname(path), { recursive: true });
318
+ const temporaryPath = `${path}.${randomUUID()}.tmp`;
319
+ await writeFile(temporaryPath, JSON.stringify(updated.store));
320
+ await rename(temporaryPath, path);
321
+ return copy(updated.result);
322
+ };
323
+
324
+ /*** Preserve adapter encapsulation by returning data that callers cannot mutate in-place. */
325
+ const copy = <Value>(value: Value): Value => structuredClone(value);
326
+
327
+ /*** Distinguish an absent first-run store from real filesystem or JSON errors. */
328
+ const isMissingFile = (reason: unknown): reason is NodeJS.ErrnoException =>
329
+ typeof reason === 'object' && reason !== null && 'code' in reason && reason.code === 'ENOENT';
330
+
331
+ /*** Reject a transition that attempts to repoint a run at another immutable flow revision. */
332
+ const sameFlow = (left: FlowDurableRun, right: FlowDurableRun): boolean =>
333
+ left.flow.id === right.flow.id && left.flow.version === right.flow.version;
@@ -0,0 +1,28 @@
1
+ import type { FlowDurableRun } from '../../../types/durableExecution.js';
2
+ import type { FlowClock } from '../../../types/execution.js';
3
+ import type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';
4
+
5
+ /** Cancel a durable run and atomically prevent future ready or claimed deliveries from starting. */
6
+ export const cancelDurableRunAsync = async (
7
+ runId: string,
8
+ dependencies: { readonly clock: FlowClock; readonly persistence: FlowPersistencePort },
9
+ ): Promise<boolean> => {
10
+ const run = await dependencies.persistence.readRunAsync(runId);
11
+ if (run === undefined || run.status === 'cancelled' || isTerminal(run)) return false;
12
+ const transitioned = await dependencies.persistence.transitionRunAsync({
13
+ expectedRevision: run.revision,
14
+ run: {
15
+ ...run,
16
+ revision: run.revision + 1,
17
+ status: 'cancelled',
18
+ updatedAt: dependencies.clock.now().toISOString(),
19
+ },
20
+ });
21
+ if (!transitioned) return false;
22
+ await dependencies.persistence.cancelRunWorkAsync(runId);
23
+ return true;
24
+ };
25
+
26
+ /** Leave completed and terminally failed runs immutable. */
27
+ const isTerminal = (run: FlowDurableRun): boolean =>
28
+ run.status === 'failed' || run.status === 'succeeded';