@astrale-os/sdk 0.6.0-beta.36 → 0.6.0-beta.38

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 (82) hide show
  1. package/dist/application/action/context.d.ts +14 -11
  2. package/dist/application/action/index.d.ts +1 -1
  3. package/dist/application/dependency/invoker.d.ts +10 -4
  4. package/dist/application/integration/define.d.ts +8 -1
  5. package/dist/application/integration/index.d.ts +2 -2
  6. package/dist/application/integration/integration.d.ts +13 -1
  7. package/dist/application/integration/operation.d.ts +14 -1
  8. package/dist/application/mutation/execution/execute.js +1 -1
  9. package/dist/application/mutation/failure.d.ts +6 -0
  10. package/dist/application/mutation/failure.js +24 -18
  11. package/dist/application/query/collection/union.js +11 -4
  12. package/dist/application/query/conformance.d.ts +15 -0
  13. package/dist/application/query/conformance.js +46 -0
  14. package/dist/application/query/define.js +4 -3
  15. package/dist/application/query/execution/execute.d.ts +0 -1
  16. package/dist/application/query/execution/execute.js +7 -1
  17. package/dist/application/query/failure.d.ts +8 -0
  18. package/dist/application/query/failure.js +8 -0
  19. package/dist/application/query/index.d.ts +1 -1
  20. package/dist/application/query/index.js +1 -1
  21. package/dist/application/workflow/authoring.d.ts +1 -1
  22. package/dist/application/workflow/context.d.ts +1 -1
  23. package/dist/application/workflow/durable.d.ts +55 -20
  24. package/dist/application/workflow/index.d.ts +1 -1
  25. package/dist/execution/identity/authentication.js +12 -6
  26. package/dist/execution/identity/callback.d.ts +7 -8
  27. package/dist/execution/identity/callback.js +17 -14
  28. package/dist/execution/invocation/capabilities/bind.d.ts +11 -0
  29. package/dist/execution/invocation/capabilities/bind.js +25 -0
  30. package/dist/execution/invocation/capabilities/index.d.ts +1 -0
  31. package/dist/execution/invocation/capabilities/index.js +1 -0
  32. package/dist/execution/invocation/dispatch/context.js +2 -21
  33. package/dist/execution/invocation/integration/authentication.d.ts +3 -3
  34. package/dist/execution/invocation/integration/authentication.js +7 -7
  35. package/dist/execution/invocation/integration/call.js +2 -2
  36. package/dist/execution/invocation/integration/index.d.ts +1 -0
  37. package/dist/execution/invocation/integration/index.js +1 -0
  38. package/dist/execution/invocation/invocation.js +2 -2
  39. package/dist/execution/invocation/lifecycle/active.d.ts +8 -2
  40. package/dist/execution/invocation/lifecycle/active.js +3 -3
  41. package/dist/execution/invocation/lifecycle/index.d.ts +1 -1
  42. package/dist/execution/service/create.js +1 -0
  43. package/dist/execution/workflows/engine.d.ts +12 -2
  44. package/dist/execution/workflows/index.d.ts +1 -1
  45. package/dist/execution/workflows/integrations.d.ts +13 -0
  46. package/dist/execution/workflows/integrations.js +25 -0
  47. package/dist/execution/workflows/options.js +3 -1
  48. package/dist/execution/workflows/receiver.d.ts +3 -0
  49. package/dist/execution/workflows/receiver.js +7 -2
  50. package/dist/execution/workflows/run.d.ts +3 -4
  51. package/dist/execution/workflows/run.js +32 -60
  52. package/dist/execution/workflows/steps.d.ts +42 -0
  53. package/dist/execution/workflows/steps.js +67 -0
  54. package/dist/testing/durable/engine.d.ts +111 -0
  55. package/dist/testing/durable/engine.js +540 -0
  56. package/dist/testing/durable/index.d.ts +3 -0
  57. package/dist/testing/durable/index.js +1 -0
  58. package/dist/testing/durable/steps.d.ts +30 -0
  59. package/dist/testing/durable/steps.js +40 -0
  60. package/dist/testing/index.d.ts +6 -1
  61. package/dist/testing/index.js +5 -1
  62. package/dist/tooling/cli/dotenv.d.ts +3 -3
  63. package/dist/tooling/cli/dotenv.js +3 -3
  64. package/dist/tooling/cli/log.d.ts +0 -2
  65. package/dist/tooling/cli/log.js +0 -2
  66. package/dist/tooling/linter/adapters/typescript/imports.d.ts +0 -1
  67. package/dist/tooling/linter/adapters/typescript/imports.js +1 -5
  68. package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
  69. package/dist/tooling/linter/adapters/typescript/index.js +1 -1
  70. package/dist/tooling/linter/adapters/typescript/syntax.d.ts +3 -3
  71. package/dist/tooling/linter/adapters/typescript/syntax.js +13 -25
  72. package/dist/tooling/linter/implementations/source/callable-requirements.d.ts +0 -1
  73. package/dist/tooling/linter/implementations/source/callable-requirements.js +1 -6
  74. package/dist/tooling/linter/implementations/source/dependencies.js +1 -62
  75. package/dist/tooling/linter/implementations/source/mutations.js +1 -12
  76. package/dist/tooling/linter/implementations/source/queries.js +1 -17
  77. package/dist/tooling/linter/implementations/source/shared.d.ts +7 -4
  78. package/dist/tooling/linter/implementations/source/shared.js +93 -46
  79. package/dist/tooling/linter/implementations/source/workflows.js +406 -16
  80. package/dist/tooling/linter/policy/generated.js +2 -2
  81. package/dist/tooling/linter/requirements/registry.js +3 -1
  82. package/package.json +6 -6
@@ -1,6 +1,6 @@
1
1
  export { defineWorkflow, isWorkflow } from './define.js';
2
2
  export type { InlineWorkflowContext, WorkflowContext } from './context.js';
3
- export type { DurableCallable, DurableExecution, DurableStep, DurableStepAuthority, DurableWorkflowContext, } from './durable.js';
3
+ export type { DurableCallable, DurableExecution, DurableStep, DurableStepAuthority, DurableWorkflowContext, SignalOutcome, } from './durable.js';
4
4
  export type { CallableAtWorkflowAddress, Workflow, WorkflowAddress, WorkflowCallable, WorkflowDefinition, WorkflowOptions, } from './workflow.js';
5
5
  export * as step from './step/index.js';
6
6
  export * as runner from './runner/index.js';
@@ -56,10 +56,10 @@ export async function authenticate(credentialInput, expectedKernel, execution, c
56
56
  throw protocolError('AUTH_INVALID', 'Credential is invalid.');
57
57
  }
58
58
  const expressions = callbackIdentities(established.delegation.credential);
59
- const session = (expression) => callbackSession(expectedKernel, expression, established.verified, execution, configuration);
59
+ const session = (expression, validUntil) => callbackSession(expectedKernel, expression, validUntil, execution, configuration);
60
60
  const sessions = Object.freeze({
61
- caller: session(expressions.caller),
62
- self: session(expressions.self),
61
+ caller: session(expressions.caller, established.validUntil),
62
+ self: session(expressions.self, established.verified.exp),
63
63
  });
64
64
  const graph = Object.freeze({
65
65
  caller: bindSession(sessions.caller, execution),
@@ -111,13 +111,18 @@ export function executorSession(kernel, executionId, execution, configuration) {
111
111
  admitEndpoint(invocationUrl(admitted), configuration.policy);
112
112
  return callbackSession(admitted, selfIdentity, undefined, execution, configuration, executionId);
113
113
  }
114
- function callbackSession(kernel, expression, carrier, execution, configuration, executionId) {
115
- const resolveCallback = createCallbackCredentialResolver(configuration.signing, configuration.issuer, configuration.subject, kernel, expression, carrier, execution, executionId);
114
+ /**
115
+ * A Session whose callbacks carry `expression`, none outliving the execution or `validUntil`, the
116
+ * earliest expiry in Unix seconds of the credentials they derive from.
117
+ */
118
+ function callbackSession(kernel, expression, validUntil, execution, configuration, executionId) {
119
+ const resolveCallback = createCallbackCredentialResolver(configuration.signing, configuration.issuer, configuration.subject, kernel, expression, validUntil, execution, executionId);
120
+ const expiresAt = Math.min(execution.deadline, (validUntil ?? Number.POSITIVE_INFINITY) * 1_000);
116
121
  return new ClientSession({
117
122
  kernel,
118
123
  fetch: boundedFetch(configuration.fetch, execution.signal),
119
124
  auth: {
120
- ttlSeconds: Math.min(callbackTtl(execution.deadline), maximumCallbackDelegationTtlSeconds),
125
+ ttlSeconds: Math.min(callbackTtl(expiresAt), maximumCallbackDelegationTtlSeconds),
121
126
  async resolve(_call, signal) {
122
127
  const callback = await resolveCallback(signal);
123
128
  signal.throwIfAborted();
@@ -159,6 +164,7 @@ async function establishCaller(input, audience, verification, execution, policy)
159
164
  }
160
165
  return Object.freeze({
161
166
  verified: context.verified,
167
+ validUntil: Math.min(context.verified.exp ?? Number.POSITIVE_INFINITY, proof.verified.exp ?? Number.POSITIVE_INFINITY),
162
168
  delegation: carried,
163
169
  caller: Object.freeze({
164
170
  kind: 'authenticated',
@@ -1,4 +1,4 @@
1
- import type { IssuerId, UnresolvedIdentityExpr, VerifiedCredential } from '../../platform/auth/index.js';
1
+ import type { IssuerId, UnresolvedIdentityExpr } from '../../platform/auth/index.js';
2
2
  import type { NodeId } from '../../platform/graph/node/index.js';
3
3
  import type { SigningMaterial } from './signing.js';
4
4
  export declare const maximumCallbackTtlSeconds = 120;
@@ -29,17 +29,16 @@ export declare function signDomainCallback(signing: SigningMaterial, issuer: Iss
29
29
  readonly deadline: number;
30
30
  readonly signal: AbortSignal;
31
31
  }): Promise<string>;
32
- export declare function signCallback(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, carrier: VerifiedCredential, execution: {
33
- readonly deadline: number;
34
- readonly signal: AbortSignal;
35
- }): Promise<string>;
36
32
  /**
37
- * Reuse one exact callback authority partition, refreshing only near its bounded expiry. Without a
38
- * carrier the callback is the Domain's own authority, bounded by the execution alone. An
33
+ * Reuse one exact callback authority partition, refreshing only near its bounded expiry. No callback
34
+ * outlives `validUntil`, the earliest expiry in Unix seconds of the credentials it derives from: the
35
+ * Kernel folds each of them into the callback's validity, so a longer callback could not carry its
36
+ * delegation. Without it the callback is the Domain's own authority, bounded by the execution alone.
37
+ * Once that authority can no longer carry a callback, the call ends as DEADLINE_EXCEEDED. An
39
38
  * `executionId` is the durable Execution a step's executor credential names in `EXECUTION_CLAIM`;
40
39
  * no caller-bound callback names one.
41
40
  */
42
- export declare function createCallbackCredentialResolver(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, carrier: VerifiedCredential | undefined, execution: {
41
+ export declare function createCallbackCredentialResolver(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, validUntil: number | undefined, execution: {
43
42
  readonly deadline: number;
44
43
  readonly signal: AbortSignal;
45
44
  }, executionId?: NodeId): (signal: AbortSignal) => Promise<SignedCallback>;
@@ -17,18 +17,17 @@ export function callbackIdentities(nested) {
17
17
  export async function signDomainCallback(signing, issuer, subject, audience, execution) {
18
18
  return (await sign(signing, issuer, subject, audience, selfIdentity, execution)).credential;
19
19
  }
20
- export async function signCallback(signing, issuer, subject, audience, expression, carrier, execution) {
21
- const maximum = carrier.exp ?? Number.POSITIVE_INFINITY;
22
- return (await sign(signing, issuer, subject, audience, expression, execution, maximum)).credential;
23
- }
24
20
  /**
25
- * Reuse one exact callback authority partition, refreshing only near its bounded expiry. Without a
26
- * carrier the callback is the Domain's own authority, bounded by the execution alone. An
21
+ * Reuse one exact callback authority partition, refreshing only near its bounded expiry. No callback
22
+ * outlives `validUntil`, the earliest expiry in Unix seconds of the credentials it derives from: the
23
+ * Kernel folds each of them into the callback's validity, so a longer callback could not carry its
24
+ * delegation. Without it the callback is the Domain's own authority, bounded by the execution alone.
25
+ * Once that authority can no longer carry a callback, the call ends as DEADLINE_EXCEEDED. An
27
26
  * `executionId` is the durable Execution a step's executor credential names in `EXECUTION_CLAIM`;
28
27
  * no caller-bound callback names one.
29
28
  */
30
- export function createCallbackCredentialResolver(signing, issuer, subject, audience, expression, carrier, execution, executionId) {
31
- const maximum = carrier?.exp ?? Number.POSITIVE_INFINITY;
29
+ export function createCallbackCredentialResolver(signing, issuer, subject, audience, expression, validUntil, execution, executionId) {
30
+ const maximum = validUntil ?? Number.POSITIVE_INFINITY;
32
31
  let cached;
33
32
  let pending;
34
33
  return async (signal) => {
@@ -42,9 +41,8 @@ export function createCallbackCredentialResolver(signing, issuer, subject, audie
42
41
  if (pending === undefined) {
43
42
  const current = sign(signing, issuer, subject, audience, expression, execution, maximum, executionId).then((signed) => {
44
43
  const delegateTtlSeconds = Math.min(maximumCallbackDelegationTtlSeconds, signed.expiresAt - signed.issuedAt - CALLBACK_DELEGATION_SKEW_SECONDS);
45
- if (delegateTtlSeconds < 1) {
46
- throw protocolError('AUTH_INVALID', 'Credential is invalid.');
47
- }
44
+ if (delegateTtlSeconds < 1)
45
+ throw exhausted(execution, maximum);
48
46
  return (cached = Object.freeze({
49
47
  credential: signed.credential,
50
48
  expiresAt: signed.expiresAt,
@@ -69,9 +67,8 @@ async function sign(signing, issuer, subject, audience, expression, execution, m
69
67
  requireLive(execution);
70
68
  const now = Math.floor(Date.now() / 1_000);
71
69
  const expiresAt = Math.min(maximum, Math.floor(execution.deadline / 1_000), now + maximumCallbackTtlSeconds);
72
- if (!Number.isFinite(expiresAt) || expiresAt <= now) {
73
- throw protocolError('AUTH_INVALID', 'Credential is invalid.');
74
- }
70
+ if (!Number.isFinite(expiresAt) || expiresAt <= now)
71
+ throw exhausted(execution, maximum);
75
72
  const credential = await withSignal(new SignJWT({
76
73
  grant: { v: 1, expr: expression },
77
74
  ...(executionId === undefined ? {} : { [EXECUTION_CLAIM]: executionId }),
@@ -89,6 +86,12 @@ async function sign(signing, issuer, subject, audience, expression, execution, m
89
86
  requireLive(execution);
90
87
  return Object.freeze({ credential, issuedAt: now, expiresAt });
91
88
  }
89
+ /** The authority a callback derives from ends before the callback could serve a call. */
90
+ function exhausted(execution, maximum) {
91
+ return protocolError('DEADLINE_EXCEEDED', maximum * 1_000 < execution.deadline
92
+ ? 'Caller authority has expired.'
93
+ : 'Invocation deadline was exceeded.');
94
+ }
92
95
  function withSignal(operation, signal) {
93
96
  if (signal.aborted)
94
97
  return Promise.reject(signal.reason);
@@ -0,0 +1,11 @@
1
+ import type { DomainCapabilities } from '../../../application/action/index.js';
2
+ import type { BoundClientSession } from '../../../platform/client/session/index.js';
3
+ import type { Domain } from '../../../platform/schema/index.js';
4
+ import type { InvocationSessions } from '../admission/index.js';
5
+ /**
6
+ * Bind what code acting under `sessions` holds: self-default Kernel and graph, and dependency
7
+ * invokers per authority. An Action and a durable Step receive these same capabilities.
8
+ */
9
+ export declare function bindCapabilities(domain: Domain, sessions: Readonly<Record<'caller' | 'self', BoundClientSession>>): DomainCapabilities;
10
+ export declare function bindCapabilities(domain: Domain, sessions: Readonly<Record<'self', BoundClientSession>>): DomainCapabilities<Domain, 'self'>;
11
+ export declare function bindCapabilities(domain: Domain, sessions: InvocationSessions): DomainCapabilities | DomainCapabilities<Domain, 'self'>;
@@ -0,0 +1,25 @@
1
+ import { bindMutation } from '../../../application/mutation/execution/executor.js';
2
+ import { bindQuery } from '../../../application/query/execution/executor.js';
3
+ import { bindDependencies } from '../dependency/index.js';
4
+ export function bindCapabilities(domain, sessions) {
5
+ const graph = bindGraph(sessions, domain);
6
+ return Object.freeze({
7
+ kernel: Object.freeze({ ...sessions.self, ...sessions }),
8
+ dependencies: bindDependencies(domain, sessions),
9
+ graph,
10
+ query: graph.self.query,
11
+ mutate: graph.self.mutate,
12
+ });
13
+ }
14
+ function bindGraph(sessions, domain) {
15
+ const bind = (client) => Object.freeze({
16
+ query: bindQuery(client, domain),
17
+ mutate: bindMutation(client, domain),
18
+ });
19
+ const self = bind(sessions.self);
20
+ return Object.freeze({
21
+ ...self,
22
+ ...('caller' in sessions ? { caller: bind(sessions.caller) } : {}),
23
+ self,
24
+ });
25
+ }
@@ -0,0 +1 @@
1
+ export { bindCapabilities } from './bind.js';
@@ -0,0 +1 @@
1
+ export { bindCapabilities } from './bind.js';
@@ -1,10 +1,7 @@
1
- import { bindMutation } from '../../../application/mutation/execution/executor.js';
2
- import { bindQuery } from '../../../application/query/execution/executor.js';
3
- import { bindDependencies } from '../dependency/index.js';
1
+ import { bindCapabilities } from '../capabilities/index.js';
4
2
  import { clients } from '../integration/index.js';
5
3
  export function context(request, runtime, authentication, self) {
6
4
  const sessions = authentication.graph;
7
- const graph = bindGraph(sessions, runtime.loaded.domain);
8
5
  const execution = Object.freeze({
9
6
  signal: request.context.execution.signal,
10
7
  deadline: request.context.execution.deadline,
@@ -20,11 +17,7 @@ export function context(request, runtime, authentication, self) {
20
17
  input: request.call.input,
21
18
  ...(request.context.binary === undefined ? {} : { body: request.context.binary }),
22
19
  caller: authentication.caller,
23
- kernel: Object.freeze({ ...sessions.self, ...sessions }),
24
- dependencies: bindDependencies(runtime.loaded.domain, sessions),
25
- graph,
26
- query: graph.self.query,
27
- mutate: graph.self.mutate,
20
+ ...bindCapabilities(runtime.loaded.domain, sessions),
28
21
  integrations: clients(runtime, request.context, {
29
22
  ...sessions,
30
23
  ...(authentication.caller.kind !== 'authenticated' ||
@@ -36,15 +29,3 @@ export function context(request, runtime, authentication, self) {
36
29
  ...(self === undefined ? {} : { self }),
37
30
  });
38
31
  }
39
- function bindGraph(sessions, domain) {
40
- const bind = (client) => Object.freeze({
41
- query: bindQuery(client, domain),
42
- mutate: bindMutation(client, domain),
43
- });
44
- const self = bind(sessions.self);
45
- return Object.freeze({
46
- ...self,
47
- ...('caller' in sessions ? { caller: bind(sessions.caller) } : {}),
48
- self,
49
- });
50
- }
@@ -1,5 +1,5 @@
1
- import type { InvocationContext } from '@astrale-os/kernel-server';
2
1
  import type { IntegrationAuthentication } from '../../../application/integration/index.js';
3
2
  import type { BoundClientSession } from '../../../platform/client/session/index.js';
4
- /** Capture only self mint, bounded by this invocation's verified credential horizon. */
5
- export declare function bindIntegrationAuthentication(context: InvocationContext, self: BoundClientSession, expiresAt?: number): IntegrationAuthentication;
3
+ import type { Lifetime } from '../lifecycle/index.js';
4
+ /** Capture only self mint, bounded by its lifetime and the verified credential horizon. */
5
+ export declare function bindIntegrationAuthentication(lifetime: Lifetime, self: BoundClientSession, expiresAt?: number): IntegrationAuthentication;
@@ -3,15 +3,15 @@ import { active } from '../lifecycle/index.js';
3
3
  import { protocolError } from '../result/index.js';
4
4
  // Match the callback resolver's reserve; a child credential cannot outlive its source.
5
5
  const TRANSPORT_RESERVE_SECONDS = 5;
6
- /** Capture only self mint, bounded by this invocation's verified credential horizon. */
7
- export function bindIntegrationAuthentication(context, self, expiresAt) {
6
+ /** Capture only self mint, bounded by its lifetime and the verified credential horizon. */
7
+ export function bindIntegrationAuthentication(lifetime, self, expiresAt) {
8
8
  if (expiresAt !== undefined && !Number.isFinite(expiresAt)) {
9
9
  throw new TypeError('Invalid Integration authentication expiry.');
10
10
  }
11
11
  const mint = self.auth.mint.bind(self.auth);
12
12
  return Object.freeze({
13
13
  async mint(input) {
14
- active(context);
14
+ active(lifetime);
15
15
  if (input === null || typeof input !== 'object' || Array.isArray(input))
16
16
  invalidInput();
17
17
  const keys = Reflect.ownKeys(input);
@@ -25,16 +25,16 @@ export function bindIntegrationAuthentication(context, self, expiresAt) {
25
25
  ttlSeconds < 1 ||
26
26
  ttlSeconds > 60)
27
27
  invalidInput();
28
- const horizon = Math.min(context.execution.deadline / 1000, expiresAt ?? Infinity);
28
+ const horizon = Math.min(lifetime.deadline / 1000, expiresAt ?? Infinity);
29
29
  const remaining = Math.floor(horizon) - Math.floor(Date.now() / 1000) - TRANSPORT_RESERVE_SECONDS;
30
30
  if (remaining < 1) {
31
31
  throw protocolError('DEADLINE_EXCEEDED', 'Insufficient lifetime for Integration credential.');
32
32
  }
33
33
  const credential = await mint({ audience, ttlSeconds: Math.min(ttlSeconds, remaining) }, {
34
- signal: context.execution.signal,
35
- timeoutMs: Math.max(1, context.execution.deadline - Date.now()),
34
+ signal: lifetime.signal,
35
+ timeoutMs: Math.max(1, lifetime.deadline - Date.now()),
36
36
  });
37
- active(context);
37
+ active(lifetime);
38
38
  // Inspect only the result of the trusted Kernel call, not a Provider-supplied token.
39
39
  // The reserve is an optimization; this absolute check is the lifetime boundary.
40
40
  const expiry = credentials.inspect(credential).claims.exp;
@@ -6,11 +6,11 @@ export function calls(context, sessions = null) {
6
6
  const invoke = sessions?.caller?.invoke.bind(sessions.caller);
7
7
  const auth = sessions === null
8
8
  ? undefined
9
- : bindIntegrationAuthentication(context, sessions.self, sessions.expiresAt);
9
+ : bindIntegrationAuthentication(context.execution, sessions.self, sessions.expiresAt);
10
10
  let sequence = 0;
11
11
  return Object.freeze({
12
12
  invoke(call) {
13
- active(context);
13
+ active(context.execution);
14
14
  sequence += 1;
15
15
  return call.attempt({
16
16
  callId: `${prefix}:${sequence}`,
@@ -1,2 +1,3 @@
1
+ export { bindIntegrationAuthentication } from './authentication.js';
1
2
  export { calls } from './call.js';
2
3
  export { clients } from './clients.js';
@@ -1,2 +1,3 @@
1
+ export { bindIntegrationAuthentication } from './authentication.js';
1
2
  export { calls } from './call.js';
2
3
  export { clients } from './clients.js';
@@ -8,7 +8,7 @@ import { project, protocolError } from './result/index.js';
8
8
  const MISS = Object.freeze({ kind: 'miss' });
9
9
  const FUNCTION_MISS = 'FUNCTION_MISS';
10
10
  export async function invoke(request, inputConfig) {
11
- active(request.context);
11
+ active(request.context.execution);
12
12
  if (!pinsRunningRelease(request, inputConfig.runtime.loaded.release)) {
13
13
  throw protocolError('CONFLICT', 'Invocation release differs from the running Release.');
14
14
  }
@@ -111,7 +111,7 @@ async function dispatchAdmitted(request, inputConfig, admitted, established) {
111
111
  input(selected.implementation.callable, request.call.input, request.context.binary);
112
112
  admittedAuthentication ??= await authentication(selected.implementation.callable, request, inputConfig.runtime.loaded.domain, inputConfig.authority);
113
113
  close ??= once(admittedAuthentication.close);
114
- active(request.context);
114
+ active(request.context.execution);
115
115
  const dispatchContext = context(request, inputConfig.runtime, admittedAuthentication, selected.self);
116
116
  const output = selected.implementation.kind === 'action'
117
117
  ? await action(selected.implementation, dispatchContext)
@@ -1,4 +1,10 @@
1
- import type { HandlerResult, InvocationContext } from '@astrale-os/kernel-server';
2
- export declare function active(context: InvocationContext): void;
1
+ import type { HandlerResult } from '@astrale-os/kernel-server';
2
+ /** The bounds of one unit of work: an invocation, or one attempt of a durable Step. */
3
+ export interface Lifetime {
4
+ readonly signal: AbortSignal;
5
+ /** Absolute Unix time in milliseconds. */
6
+ readonly deadline: number;
7
+ }
8
+ export declare function active(lifetime: Lifetime): void;
3
9
  /** Close authenticated resources after finite results or stream settlement. */
4
10
  export declare function settle(result: HandlerResult, close: () => void): HandlerResult;
@@ -1,9 +1,9 @@
1
1
  import { protocolError } from '../result/index.js';
2
- export function active(context) {
3
- if (context.execution.signal.aborted) {
2
+ export function active(lifetime) {
3
+ if (lifetime.signal.aborted) {
4
4
  throw protocolError('CANCELLED', 'Invocation was cancelled.');
5
5
  }
6
- if (Date.now() >= context.execution.deadline) {
6
+ if (Date.now() >= lifetime.deadline) {
7
7
  throw protocolError('DEADLINE_EXCEEDED', 'Invocation deadline was exceeded.');
8
8
  }
9
9
  }
@@ -1 +1 @@
1
- export { active, settle } from './active.js';
1
+ export { active, settle, type Lifetime } from './active.js';
@@ -72,6 +72,7 @@ export function createService(input) {
72
72
  domain: runtime.loaded.domain,
73
73
  identity: selectedIdentity,
74
74
  serving: configuration,
75
+ integrations: runtime.clients,
75
76
  });
76
77
  });
77
78
  let closing;
@@ -15,6 +15,8 @@ export interface DurableEngine {
15
15
  readonly metadata?: Record<string, string>;
16
16
  }) => Error;
17
17
  readonly CancelledError: abstract new (...input: never[]) => Error;
18
+ /** The rejection of `orTimeout` once its duration elapses first. */
19
+ readonly TimeoutError: abstract new (...input: never[]) => Error;
18
20
  }
19
21
  export interface EngineWorkflowDefinition {
20
22
  readonly name: string;
@@ -34,12 +36,20 @@ export interface EngineRetryPolicy {
34
36
  readonly maxInterval: number;
35
37
  readonly exponentiationFactor: number;
36
38
  }
39
+ /**
40
+ * A promise the engine journals. Its combinators are journaled too, so a replay settles them as the
41
+ * first run did; racing it against another promise with `Promise.race` would not be.
42
+ */
43
+ export interface EnginePromise<Output> extends PromiseLike<Output> {
44
+ /** Settle with this promise, or reject with `TimeoutError` once `millis` elapse first. */
45
+ orTimeout(millis: number): PromiseLike<unknown>;
46
+ }
37
47
  /** Journaled operations of one workflow run. */
38
48
  export interface EngineWorkflowContext {
39
49
  run<Output>(name: string, action: () => Promise<Output>): PromiseLike<Output>;
40
50
  sleep(durationMs: number, name?: string): PromiseLike<void>;
41
- promise<Output>(name: string): {
42
- get(): PromiseLike<Output>;
51
+ promise(name: string): {
52
+ get(): EnginePromise<unknown>;
43
53
  };
44
54
  request(): {
45
55
  readonly attemptCompletedSignal: AbortSignal;
@@ -1,4 +1,4 @@
1
- export type { DurableEngine, EngineRetryPolicy, EngineSharedContext, EngineWorkflowContext, EngineWorkflowDefinition, } from './engine.js';
1
+ export type { DurableEngine, EnginePromise, EngineRetryPolicy, EngineSharedContext, EngineWorkflowContext, EngineWorkflowDefinition, } from './engine.js';
2
2
  export { durableServing, type AdmittedDurableServing, type DurableServing, type DurableServingProvider, } from './options.js';
3
3
  export { createDurableReceiver, type DurableReceiver } from './receiver.js';
4
4
  export { isDurableRequest, isCanonicalDurableRequest } from './route.js';
@@ -0,0 +1,13 @@
1
+ import type { IntegrationAuthentication, IntegrationInvocationOwner } from '../../application/integration/index.js';
2
+ import type { Lifetime } from '../invocation/lifecycle/index.js';
3
+ /**
4
+ * The Integration calls of one Step attempt. Each call takes the Step's next derived key as its
5
+ * call id, so every attempt presents the same ids in the same order, and a keyed operation of
6
+ * source `execution` sends the same idempotency key. A retried Step may run an operation again,
7
+ * so an `unsafe` one is refused before its Provider runs. There is no caller to invoke for.
8
+ */
9
+ export declare function stepCalls(input: {
10
+ readonly next: () => string;
11
+ readonly lifetime: Lifetime;
12
+ readonly auth: IntegrationAuthentication;
13
+ }): IntegrationInvocationOwner;
@@ -0,0 +1,25 @@
1
+ import { active } from '../invocation/lifecycle/index.js';
2
+ /**
3
+ * The Integration calls of one Step attempt. Each call takes the Step's next derived key as its
4
+ * call id, so every attempt presents the same ids in the same order, and a keyed operation of
5
+ * source `execution` sends the same idempotency key. A retried Step may run an operation again,
6
+ * so an `unsafe` one is refused before its Provider runs. There is no caller to invoke for.
7
+ */
8
+ export function stepCalls(input) {
9
+ const { next, lifetime, auth } = input;
10
+ return Object.freeze({
11
+ invoke(call) {
12
+ if (call.replay.kind === 'unsafe') {
13
+ throw new TypeError(`Integration operation ${call.integration}.${call.operation} is unsafe, and a retried ` +
14
+ 'durable Step would run it again.');
15
+ }
16
+ active(lifetime);
17
+ return call.attempt({
18
+ callId: next(),
19
+ signal: lifetime.signal,
20
+ deadline: lifetime.deadline,
21
+ auth,
22
+ });
23
+ },
24
+ });
25
+ }
@@ -18,7 +18,8 @@ export function durableServing(input) {
18
18
  typeof engine.createEndpointHandler !== 'function' ||
19
19
  typeof engine.workflow !== 'function' ||
20
20
  typeof engine.TerminalError !== 'function' ||
21
- typeof engine.CancelledError !== 'function') {
21
+ typeof engine.CancelledError !== 'function' ||
22
+ typeof engine.TimeoutError !== 'function') {
22
23
  invalid('Durable serving requires the Restate SDK Fetch entrypoint of this platform.');
23
24
  }
24
25
  const fetch = input.fetch ?? globalThis.fetch.bind(globalThis);
@@ -39,6 +40,7 @@ export function durableServing(input) {
39
40
  workflow: engine.workflow.bind(engine),
40
41
  TerminalError: engine.TerminalError,
41
42
  CancelledError: engine.CancelledError,
43
+ TimeoutError: engine.TimeoutError,
42
44
  }),
43
45
  fetch,
44
46
  retentionMs: bounded(input.retentionMs, 7 * DAY_MS, HOUR_MS, 365 * DAY_MS, 'retentionMs'),
@@ -1,3 +1,4 @@
1
+ import type { IntegrationClientFactory, Integrations } from '../../application/integration/index.js';
1
2
  import type { RuntimeWorkflow } from '../../application/runtime/workflows.js';
2
3
  import type { Domain } from '../../platform/schema/index.js';
3
4
  import type { Identity } from '../identity/index.js';
@@ -18,4 +19,6 @@ export declare function createDurableReceiver(input: {
18
19
  readonly domain: Domain;
19
20
  readonly identity: Identity;
20
21
  readonly serving: AdmittedDurableServing;
22
+ /** Integration clients of the initialized Runtime, which every Step holds. */
23
+ readonly integrations: IntegrationClientFactory<Integrations>;
21
24
  }): DurableReceiver;
@@ -1,6 +1,7 @@
1
1
  import { workflowEngineDiscovery, WorkflowEngineUnavailable } from './discovery.js';
2
2
  import { durableRoute } from './route.js';
3
3
  import { durableHandlers } from './run.js';
4
+ import { sessionSteps } from './steps.js';
4
5
  const MAXIMUM_ENDPOINTS = 256;
5
6
  /**
6
7
  * Route `…/invoke/restate/<Kernel issuer>/<declaration key>/<service>/{discover,invoke/…}` to one
@@ -14,7 +15,11 @@ export function createDurableReceiver(input) {
14
15
  if (executor === undefined) {
15
16
  throw new TypeError('Durable Workflows require an execution Identity that can act as the Domain.');
16
17
  }
17
- const bound = executor.bind(input.identity);
18
+ const steps = sessionSteps({
19
+ executor: executor.bind(input.identity),
20
+ domain: input.domain,
21
+ integrations: input.integrations,
22
+ });
18
23
  const declared = new Map(input.workflows
19
24
  .filter((workflow) => workflow.durable)
20
25
  .map((workflow) => [workflow.callable.key, workflow]));
@@ -48,8 +53,8 @@ export function createDurableReceiver(input) {
48
53
  handlers: durableHandlers({
49
54
  workflow,
50
55
  domain: input.domain,
51
- executor: bound,
52
56
  serving: input.serving,
57
+ steps,
53
58
  kernel,
54
59
  }),
55
60
  options: {
@@ -1,16 +1,16 @@
1
1
  import type { RuntimeWorkflow } from '../../application/runtime/workflows.js';
2
2
  import type { IssuerId } from '../../platform/auth/index.js';
3
3
  import type { Domain } from '../../platform/schema/index.js';
4
- import type { Identity } from '../identity/index.js';
5
4
  import type { EngineSharedContext, EngineWorkflowContext } from './engine.js';
6
5
  import type { AdmittedDurableServing } from './options.js';
7
- type Executor = NonNullable<Identity['executor']>;
6
+ import type { StepAuthorities } from './steps.js';
8
7
  export interface DurableRun {
9
8
  readonly kernel: IssuerId;
10
9
  readonly workflow: RuntimeWorkflow;
11
10
  readonly domain: Domain;
12
- readonly executor: Executor;
13
11
  readonly serving: AdmittedDurableServing;
12
+ /** What each Step attempt holds. */
13
+ readonly steps: StepAuthorities;
14
14
  }
15
15
  /** Engine handlers of one durable Workflow: the `run` and the shared `signal`. */
16
16
  export declare function durableHandlers(input: DurableRun): Readonly<{
@@ -19,4 +19,3 @@ export declare function durableHandlers(input: DurableRun): Readonly<{
19
19
  accepted: boolean;
20
20
  }>;
21
21
  }>;
22
- export {};