@astrale-os/sdk 0.6.0-beta.36 → 0.6.0-beta.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/action/context.d.ts +14 -11
- package/dist/application/action/index.d.ts +1 -1
- package/dist/application/dependency/invoker.d.ts +10 -4
- package/dist/application/integration/define.d.ts +8 -1
- package/dist/application/integration/index.d.ts +2 -2
- package/dist/application/integration/integration.d.ts +13 -1
- package/dist/application/integration/operation.d.ts +14 -1
- package/dist/application/mutation/execution/execute.js +1 -1
- package/dist/application/mutation/failure.d.ts +6 -0
- package/dist/application/mutation/failure.js +24 -18
- package/dist/application/query/collection/union.js +11 -4
- package/dist/application/query/conformance.d.ts +15 -0
- package/dist/application/query/conformance.js +46 -0
- package/dist/application/query/define.js +4 -3
- package/dist/application/query/execution/execute.d.ts +0 -1
- package/dist/application/query/execution/execute.js +7 -1
- package/dist/application/query/failure.d.ts +8 -0
- package/dist/application/query/failure.js +8 -0
- package/dist/application/query/index.d.ts +1 -1
- package/dist/application/query/index.js +1 -1
- package/dist/application/workflow/authoring.d.ts +1 -1
- package/dist/application/workflow/context.d.ts +1 -1
- package/dist/application/workflow/durable.d.ts +55 -20
- package/dist/application/workflow/index.d.ts +1 -1
- package/dist/execution/identity/authentication.js +12 -6
- package/dist/execution/identity/callback.d.ts +7 -8
- package/dist/execution/identity/callback.js +17 -14
- package/dist/execution/invocation/capabilities/bind.d.ts +11 -0
- package/dist/execution/invocation/capabilities/bind.js +25 -0
- package/dist/execution/invocation/capabilities/index.d.ts +1 -0
- package/dist/execution/invocation/capabilities/index.js +1 -0
- package/dist/execution/invocation/dispatch/context.js +2 -21
- package/dist/execution/invocation/integration/authentication.d.ts +3 -3
- package/dist/execution/invocation/integration/authentication.js +7 -7
- package/dist/execution/invocation/integration/call.js +2 -2
- package/dist/execution/invocation/integration/index.d.ts +1 -0
- package/dist/execution/invocation/integration/index.js +1 -0
- package/dist/execution/invocation/invocation.js +2 -2
- package/dist/execution/invocation/lifecycle/active.d.ts +8 -2
- package/dist/execution/invocation/lifecycle/active.js +3 -3
- package/dist/execution/invocation/lifecycle/index.d.ts +1 -1
- package/dist/execution/service/create.js +1 -0
- package/dist/execution/workflows/engine.d.ts +12 -2
- package/dist/execution/workflows/index.d.ts +1 -1
- package/dist/execution/workflows/integrations.d.ts +13 -0
- package/dist/execution/workflows/integrations.js +25 -0
- package/dist/execution/workflows/options.js +3 -1
- package/dist/execution/workflows/receiver.d.ts +3 -0
- package/dist/execution/workflows/receiver.js +7 -2
- package/dist/execution/workflows/run.d.ts +3 -4
- package/dist/execution/workflows/run.js +32 -60
- package/dist/execution/workflows/steps.d.ts +42 -0
- package/dist/execution/workflows/steps.js +67 -0
- package/dist/testing/durable/engine.d.ts +111 -0
- package/dist/testing/durable/engine.js +540 -0
- package/dist/testing/durable/index.d.ts +3 -0
- package/dist/testing/durable/index.js +1 -0
- package/dist/testing/durable/steps.d.ts +30 -0
- package/dist/testing/durable/steps.js +40 -0
- package/dist/testing/index.d.ts +6 -1
- package/dist/testing/index.js +5 -1
- package/dist/tooling/cli/dotenv.d.ts +3 -3
- package/dist/tooling/cli/dotenv.js +3 -3
- package/dist/tooling/cli/log.d.ts +0 -2
- package/dist/tooling/cli/log.js +0 -2
- package/dist/tooling/linter/adapters/typescript/imports.d.ts +0 -1
- package/dist/tooling/linter/adapters/typescript/imports.js +1 -5
- package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
- package/dist/tooling/linter/adapters/typescript/index.js +1 -1
- package/dist/tooling/linter/adapters/typescript/syntax.d.ts +3 -3
- package/dist/tooling/linter/adapters/typescript/syntax.js +13 -25
- package/dist/tooling/linter/implementations/source/callable-requirements.d.ts +0 -1
- package/dist/tooling/linter/implementations/source/callable-requirements.js +1 -6
- package/dist/tooling/linter/implementations/source/dependencies.js +12 -1
- package/dist/tooling/linter/implementations/source/mutations.js +1 -12
- package/dist/tooling/linter/implementations/source/queries.js +1 -17
- package/dist/tooling/linter/implementations/source/shared.d.ts +0 -4
- package/dist/tooling/linter/implementations/source/shared.js +1 -33
- package/dist/tooling/linter/implementations/source/workflows.js +5 -15
- package/dist/tooling/linter/policy/generated.js +2 -2
- 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,
|
|
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
|
-
|
|
115
|
-
|
|
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(
|
|
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
|
|
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.
|
|
38
|
-
*
|
|
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,
|
|
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.
|
|
26
|
-
*
|
|
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,
|
|
31
|
-
const maximum =
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
|
7
|
-
export function bindIntegrationAuthentication(
|
|
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(
|
|
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(
|
|
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:
|
|
35
|
-
timeoutMs: Math.max(1,
|
|
34
|
+
signal: lifetime.signal,
|
|
35
|
+
timeoutMs: Math.max(1, lifetime.deadline - Date.now()),
|
|
36
36
|
});
|
|
37
|
-
active(
|
|
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}`,
|
|
@@ -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
|
|
2
|
-
|
|
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(
|
|
3
|
-
if (
|
|
2
|
+
export function active(lifetime) {
|
|
3
|
+
if (lifetime.signal.aborted) {
|
|
4
4
|
throw protocolError('CANCELLED', 'Invocation was cancelled.');
|
|
5
5
|
}
|
|
6
|
-
if (Date.now() >=
|
|
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';
|
|
@@ -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
|
|
42
|
-
get():
|
|
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
|
|
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
|
|
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 {};
|