@astrale-os/sdk 0.6.0-beta.0 → 0.6.0-beta.10
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/README.md +70 -0
- package/dist/application/action/action.d.ts +6 -0
- package/dist/application/action/authoring.d.ts +1 -1
- package/dist/application/action/context.d.ts +12 -2
- package/dist/application/action/define.d.ts +3 -3
- package/dist/application/action/index.d.ts +1 -1
- package/dist/application/dependency/index.d.ts +1 -1
- package/dist/application/dependency/invoker.d.ts +9 -4
- package/dist/application/mutation/authoring/properties.d.ts +5 -3
- package/dist/application/mutation/authoring/properties.js +35 -2
- package/dist/application/mutation/authoring/transition.js +4 -1
- package/dist/application/runtime/actions.js +3 -0
- package/dist/application/runtime/workflows.d.ts +2 -0
- package/dist/application/runtime/workflows.js +5 -0
- package/dist/application/view/route.d.ts +8 -0
- package/dist/application/workflow/authoring.d.ts +3 -2
- package/dist/application/workflow/context.d.ts +8 -2
- package/dist/application/workflow/define.d.ts +3 -3
- package/dist/application/workflow/define.js +19 -3
- package/dist/application/workflow/durable.d.ts +83 -0
- package/dist/application/workflow/durable.js +1 -0
- package/dist/application/workflow/index.d.ts +3 -2
- package/dist/application/workflow/runner/inline.js +3 -4
- package/dist/application/workflow/runner/runner.d.ts +2 -2
- package/dist/application/workflow/step/output.d.ts +2 -0
- package/dist/application/workflow/step/output.js +73 -0
- package/dist/application/workflow/workflow.d.ts +12 -0
- package/dist/deployment/environment.d.ts +6 -0
- package/dist/deployment/environment.js +6 -0
- package/dist/deployment/release/publication/seal.d.ts +1 -1
- package/dist/deployment/release/publication/seal.js +1 -1
- package/dist/deployment/release/release.d.ts +1 -1
- package/dist/deployment/verify/preflight/publication.js +2 -1
- package/dist/execution/identity/authentication.d.ts +9 -0
- package/dist/execution/identity/authentication.js +41 -34
- package/dist/execution/identity/callback.d.ts +12 -4
- package/dist/execution/identity/callback.js +9 -4
- package/dist/execution/identity/identity.d.ts +9 -0
- package/dist/execution/identity/identity.js +6 -2
- package/dist/execution/index.d.ts +3 -1
- package/dist/execution/index.js +2 -1
- package/dist/execution/invocation/admission/authority.d.ts +3 -3
- package/dist/execution/invocation/admission/input.d.ts +2 -2
- package/dist/execution/invocation/admission/input.js +16 -2
- package/dist/execution/invocation/dependency/bind.js +35 -13
- package/dist/execution/invocation/dispatch/context.d.ts +2 -1
- package/dist/execution/invocation/dispatch/context.js +1 -0
- package/dist/execution/invocation/invocation.js +7 -2
- package/dist/execution/serve.d.ts +6 -0
- package/dist/execution/serve.js +3 -0
- package/dist/execution/service/create.js +30 -0
- package/dist/execution/service/initialization.d.ts +4 -4
- package/dist/execution/service/initialization.js +23 -14
- package/dist/execution/service/service.d.ts +6 -0
- package/dist/execution/workflows/discovery.d.ts +14 -0
- package/dist/execution/workflows/discovery.js +151 -0
- package/dist/execution/workflows/engine.d.ts +52 -0
- package/dist/execution/workflows/engine.js +1 -0
- package/dist/execution/workflows/envelope.d.ts +12 -0
- package/dist/execution/workflows/envelope.js +46 -0
- package/dist/execution/workflows/failure.d.ts +11 -0
- package/dist/execution/workflows/failure.js +43 -0
- package/dist/execution/workflows/index.d.ts +4 -0
- package/dist/execution/workflows/index.js +3 -0
- package/dist/execution/workflows/options.d.ts +40 -0
- package/dist/execution/workflows/options.js +83 -0
- package/dist/execution/workflows/receiver.d.ts +22 -0
- package/dist/execution/workflows/receiver.js +99 -0
- package/dist/execution/workflows/route.d.ts +14 -0
- package/dist/execution/workflows/route.js +40 -0
- package/dist/execution/workflows/run.d.ts +22 -0
- package/dist/execution/workflows/run.js +211 -0
- package/dist/platform/schema/index.d.ts +2 -1
- package/dist/platform/schema/property-input.d.ts +15 -0
- package/dist/platform/schema/property-input.js +1 -0
- package/dist/platform/versioning/dependents.d.ts +18 -0
- package/dist/platform/versioning/dependents.js +15 -0
- package/dist/platform/versioning/index.d.ts +5 -0
- package/dist/platform/versioning/index.js +5 -0
- package/dist/platform/versioning/name.d.ts +37 -0
- package/dist/platform/versioning/name.js +57 -0
- package/dist/platform/versioning/reference.d.ts +48 -0
- package/dist/platform/versioning/reference.js +95 -0
- package/dist/platform/versioning/version.d.ts +72 -0
- package/dist/platform/versioning/version.js +98 -0
- package/dist/project/define.js +1 -1
- package/dist/testing/dataset/builder.d.ts +3 -3
- package/dist/tooling/cli/deprecations.d.ts +9 -0
- package/dist/tooling/cli/deprecations.js +15 -0
- package/dist/tooling/cli/development/develop.js +4 -1
- package/dist/tooling/cli/orchestrate.js +4 -1
- package/dist/tooling/linter/adapters/typescript/discover.d.ts +2 -2
- package/dist/tooling/linter/adapters/typescript/discover.js +11 -7
- package/dist/tooling/linter/implementations/source/global.js +1 -1
- package/dist/tooling/linter/lint.js +5 -4
- package/dist/tooling/linter/oxlint/run.d.ts +1 -1
- package/dist/tooling/linter/oxlint/run.js +5 -1
- package/dist/tooling/linter/policy/configuration.d.ts +3 -0
- package/dist/tooling/linter/policy/configuration.js +3 -1
- package/dist/tooling/linter/policy/ignore.d.ts +11 -0
- package/dist/tooling/linter/policy/ignore.js +101 -0
- package/dist/tooling/linter/policy/index.d.ts +1 -0
- package/package.json +15 -6
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as value from '../../../platform/value/index.js';
|
|
2
|
+
/** Capture optional Object fields as absence without changing portable Array or scalar semantics. */
|
|
3
|
+
export function acceptStepOutput(input) {
|
|
4
|
+
if (input === undefined)
|
|
5
|
+
return input;
|
|
6
|
+
const captured = new WeakMap();
|
|
7
|
+
const omitted = new WeakMap();
|
|
8
|
+
const pending = [];
|
|
9
|
+
let members = 0;
|
|
10
|
+
function capture(source, depth) {
|
|
11
|
+
if (source === null || typeof source !== 'object')
|
|
12
|
+
return source;
|
|
13
|
+
const retained = captured.get(source);
|
|
14
|
+
if (retained !== undefined)
|
|
15
|
+
return retained;
|
|
16
|
+
if (depth >= value.defaultLimits.maxDepth) {
|
|
17
|
+
throw new value.BoundaryError('VALUE_LIMIT', 'Workflow Step container depth exceeds the portable limit', '');
|
|
18
|
+
}
|
|
19
|
+
const array = Array.isArray(source);
|
|
20
|
+
const snapshot = array ? [] : Object.create(Object.getPrototypeOf(source));
|
|
21
|
+
captured.set(source, snapshot);
|
|
22
|
+
pending.push({ source, snapshot, depth, array });
|
|
23
|
+
return snapshot;
|
|
24
|
+
}
|
|
25
|
+
const snapshot = capture(input, 0);
|
|
26
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
27
|
+
const { source, snapshot: container, depth, array } = pending[index];
|
|
28
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
29
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
30
|
+
// Bound this preparatory capture too; canonical admission subsequently checks each path.
|
|
31
|
+
members += keys.length - (array ? 1 : 0);
|
|
32
|
+
if (members > value.defaultLimits.maxMembers) {
|
|
33
|
+
throw new value.BoundaryError('VALUE_LIMIT', 'Workflow Step member count exceeds the portable limit', '');
|
|
34
|
+
}
|
|
35
|
+
const absent = new Set();
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const descriptor = Object.getOwnPropertyDescriptor(descriptors, key)
|
|
38
|
+
.value;
|
|
39
|
+
if ('value' in descriptor) {
|
|
40
|
+
if (!array &&
|
|
41
|
+
typeof key === 'string' &&
|
|
42
|
+
descriptor.enumerable &&
|
|
43
|
+
descriptor.value === undefined) {
|
|
44
|
+
// Validate this member's key, descriptor and budget before interpreting its absence.
|
|
45
|
+
descriptor.value = null;
|
|
46
|
+
absent.add(key);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
descriptor.value = capture(descriptor.value, depth + 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
Object.defineProperty(container, key, descriptor);
|
|
53
|
+
}
|
|
54
|
+
if (absent.size > 0)
|
|
55
|
+
omitted.set(container, absent);
|
|
56
|
+
}
|
|
57
|
+
// The canonical owner still rejects cycles, invalid descriptors/prototypes/keys and lossy values.
|
|
58
|
+
const output = value.accept(snapshot);
|
|
59
|
+
const pruning = [{ snapshot, output }];
|
|
60
|
+
for (let index = 0; index < pruning.length; index += 1) {
|
|
61
|
+
const pair = pruning[index];
|
|
62
|
+
if (pair.output === null || typeof pair.output !== 'object')
|
|
63
|
+
continue;
|
|
64
|
+
const source = pair.snapshot;
|
|
65
|
+
const target = pair.output;
|
|
66
|
+
for (const key of omitted.get(source) ?? [])
|
|
67
|
+
delete target[key];
|
|
68
|
+
for (const key of Object.keys(target)) {
|
|
69
|
+
pruning.push({ snapshot: source[key], output: target[key] });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
@@ -12,11 +12,23 @@ export type WorkflowAddress<Schema extends schema.DomainSchema> = ActionAddressO
|
|
|
12
12
|
export type CallableAtWorkflowAddress<Schema extends schema.DomainSchema, Address extends WorkflowAddress<Schema>> = WorkflowCallable<Extract<CallableAtAddress<Schema, Address>, ResolvedCallable>>;
|
|
13
13
|
export type Workflow<Callable extends ResolvedCallable, Definitions extends Integrations = Readonly<Record<never, never>>, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = (context: WorkflowContext<Callable, DomainValue, Definitions, Errors>) => Promise<ActionOutputOf<Callable>>;
|
|
14
14
|
declare const WORKFLOW_SCHEMA: unique symbol;
|
|
15
|
+
/** Options of one Workflow implementation. */
|
|
16
|
+
export interface WorkflowOptions<out Errors extends DeclaredErrors = DeclaredErrors> {
|
|
17
|
+
readonly errors?: Errors;
|
|
18
|
+
/**
|
|
19
|
+
* Durable only. Compatibility token journaled first by every run: keep it while a redeployment
|
|
20
|
+
* can replay the journals of running Executions, change it when it cannot, and those runs pause
|
|
21
|
+
* until they are resumed on compatible code or cancelled.
|
|
22
|
+
*/
|
|
23
|
+
readonly compat?: string;
|
|
24
|
+
}
|
|
15
25
|
/** Sealed multi-step Workflow awaiting canonical callable association by Runtime admission. */
|
|
16
26
|
export interface WorkflowDefinition<Schema extends schema.DomainSchema = schema.DomainSchema, Address extends WorkflowAddress<Schema> = WorkflowAddress<Schema>, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> {
|
|
17
27
|
readonly kind: 'workflow';
|
|
18
28
|
readonly address: Address;
|
|
19
29
|
readonly run: Workflow<CallableAtWorkflowAddress<Schema, Address>, Definitions, ResolvedDomainOf<Schema>, Errors>;
|
|
30
|
+
/** Durable compatibility token; absent means the default token. */
|
|
31
|
+
readonly compat?: string;
|
|
20
32
|
/** Invariant type-only Schema witness; absent from emitted JavaScript. */
|
|
21
33
|
readonly [WORKFLOW_SCHEMA]: (schema: Schema) => Schema;
|
|
22
34
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name of a project environment, the target a deployment is made for (`staging`, `production`,
|
|
3
|
+
* `pr-42`), as `defineProject` admits it: lower-case letters, digits, `.`, `_` and `-`, starting and
|
|
4
|
+
* ending with a letter or a digit. It stays Worker-safe because a deployment name renders it.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ENVIRONMENT_NAME: RegExp;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name of a project environment, the target a deployment is made for (`staging`, `production`,
|
|
3
|
+
* `pr-42`), as `defineProject` admits it: lower-case letters, digits, `.`, `_` and `-`, starting and
|
|
4
|
+
* ending with a letter or a digit. It stays Worker-safe because a deployment name renders it.
|
|
5
|
+
*/
|
|
6
|
+
export const ENVIRONMENT_NAME = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as publication from '@astrale-os/kernel-protocol/publication';
|
|
2
|
-
import * as protocolRoutes from '@astrale-os/kernel-protocol/routes';
|
|
2
|
+
import * as protocolRoutes from '@astrale-os/kernel-protocol/routes/legacy';
|
|
3
3
|
import type { Build } from '../../build/index.js';
|
|
4
4
|
import type { Addressing } from '../addressing.js';
|
|
5
5
|
export declare const DELIVERY_PATH: '/install';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as publication from '@astrale-os/kernel-protocol/publication';
|
|
2
|
-
import * as protocolRoutes from '@astrale-os/kernel-protocol/routes';
|
|
2
|
+
import * as protocolRoutes from '@astrale-os/kernel-protocol/routes/legacy';
|
|
3
3
|
import { evidenceOf } from '../../build/admission.js';
|
|
4
4
|
import { callables } from './callables.js';
|
|
5
5
|
import { manifest } from './manifest.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as publication from '@astrale-os/kernel-protocol/publication';
|
|
2
|
-
import type * as routes from '@astrale-os/kernel-protocol/routes';
|
|
2
|
+
import type * as routes from '@astrale-os/kernel-protocol/routes/legacy';
|
|
3
3
|
import type { Build } from '../build/index.js';
|
|
4
4
|
import type { Addressing } from './addressing.js';
|
|
5
5
|
export interface Release<BuildValue extends Build = Build> {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { publication as protocolPublication
|
|
1
|
+
import { publication as protocolPublication } from '@astrale-os/kernel-protocol';
|
|
2
|
+
import * as routes from '@astrale-os/kernel-protocol/routes/legacy';
|
|
2
3
|
import { evidenceOf } from '../../build/admission.js';
|
|
3
4
|
import { assemble } from '../../release/index.js';
|
|
4
5
|
/** Verify locally assemblable Publication evidence before any provider mutation. */
|
|
@@ -4,6 +4,7 @@ import type { AuthenticatedInvocation } from '../invocation/index.js';
|
|
|
4
4
|
import type { InvocationAdmissionInput } from '../invocation/index.js';
|
|
5
5
|
import type { IdentityNetworkPolicy, VerificationBasis, VerificationBasisResolver } from './jwks.js';
|
|
6
6
|
import type { SigningMaterial } from './signing.js';
|
|
7
|
+
import { ClientSession } from '../../platform/client/session/index.js';
|
|
7
8
|
export declare function admit(request: InvocationAdmissionInput, configuration: AuthenticationConfiguration): Promise<unknown>;
|
|
8
9
|
export interface AuthenticationConfiguration {
|
|
9
10
|
readonly issuer: IssuerId;
|
|
@@ -17,5 +18,13 @@ export declare function authenticate(credentialInput: CredentialInput, expectedK
|
|
|
17
18
|
readonly deadline: number;
|
|
18
19
|
readonly signal: AbortSignal;
|
|
19
20
|
}, configuration: AuthenticationConfiguration): Promise<AuthenticatedInvocation>;
|
|
21
|
+
/**
|
|
22
|
+
* The Domain's own authority toward one Kernel, for work no caller credential carries: a durable
|
|
23
|
+
* run acts as its executor, never as its initiator. The caller owns the returned Session.
|
|
24
|
+
*/
|
|
25
|
+
export declare function executorSession(kernel: IssuerId, execution: {
|
|
26
|
+
readonly deadline: number;
|
|
27
|
+
readonly signal: AbortSignal;
|
|
28
|
+
}, configuration: AuthenticationConfiguration): ClientSession;
|
|
20
29
|
export declare function acceptResolvedDelegation(input: unknown): Grant;
|
|
21
30
|
export type { VerificationBasis };
|
|
@@ -7,8 +7,8 @@ import { bindSession, ClientSession } from '../../platform/client/session/index.
|
|
|
7
7
|
import { NodeId } from '../../platform/graph/node/index.js';
|
|
8
8
|
import { Path } from '../../platform/graph/path/index.js';
|
|
9
9
|
import { projectError, protocolError } from '../invocation/result/index.js';
|
|
10
|
-
import { callbackIdentities, createCallbackCredentialResolver, maximumCallbackDelegationTtlSeconds, signDomainCallback, } from './callback.js';
|
|
11
|
-
import { CredentialRejected, IssuerUnavailable, boundedFetch, callbackTtl, invocationUrl, admitEndpoint, requireLive, } from './errors.js';
|
|
10
|
+
import { callbackIdentities, createCallbackCredentialResolver, maximumCallbackDelegationTtlSeconds, selfIdentity, signDomainCallback, } from './callback.js';
|
|
11
|
+
import { CredentialRejected, IssuerUnavailable, boundedFetch, callbackTtl, httpIssuer, invocationUrl, admitEndpoint, requireLive, } from './errors.js';
|
|
12
12
|
import { verifyCredential, verifyCredentialAgainst } from './jwks.js';
|
|
13
13
|
const DEFAULT_ROUTE_AGE_MS = 60_000;
|
|
14
14
|
export async function admit(request, configuration) {
|
|
@@ -32,10 +32,7 @@ export async function admit(request, configuration) {
|
|
|
32
32
|
}), {
|
|
33
33
|
signal: execution.signal,
|
|
34
34
|
timeoutMs: Math.max(1, execution.deadline - Date.now()),
|
|
35
|
-
delegate: {
|
|
36
|
-
ttlSeconds: callbackTtl(execution.deadline),
|
|
37
|
-
attenuation: { kind: 'identity', self: true },
|
|
38
|
-
},
|
|
35
|
+
delegate: { ttlSeconds: callbackTtl(execution.deadline) },
|
|
39
36
|
});
|
|
40
37
|
return result.value;
|
|
41
38
|
}
|
|
@@ -53,10 +50,11 @@ export async function authenticate(credentialInput, expectedKernel, execution, c
|
|
|
53
50
|
throw protocolError('AUTH_INVALID', 'Credential is invalid.');
|
|
54
51
|
}
|
|
55
52
|
const expressions = callbackIdentities(established.delegation.credential);
|
|
53
|
+
const session = (expression) => callbackSession(expectedKernel, expression, established.verified, execution, configuration);
|
|
56
54
|
const sessions = Object.freeze({
|
|
57
|
-
caller:
|
|
58
|
-
self:
|
|
59
|
-
union:
|
|
55
|
+
caller: session(expressions.caller),
|
|
56
|
+
self: session(expressions.self),
|
|
57
|
+
union: session(expressions.union),
|
|
60
58
|
});
|
|
61
59
|
const graph = Object.freeze({
|
|
62
60
|
caller: bindSession(sessions.caller, execution),
|
|
@@ -76,32 +74,41 @@ export async function authenticate(credentialInput, expectedKernel, execution, c
|
|
|
76
74
|
sessions.union.close();
|
|
77
75
|
},
|
|
78
76
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The Domain's own authority toward one Kernel, for work no caller credential carries: a durable
|
|
80
|
+
* run acts as its executor, never as its initiator. The caller owns the returned Session.
|
|
81
|
+
*/
|
|
82
|
+
export function executorSession(kernel, execution, configuration) {
|
|
83
|
+
const admitted = httpIssuer(kernel, configuration.policy.allowInsecureHttp === true);
|
|
84
|
+
admitEndpoint(invocationUrl(admitted), configuration.policy);
|
|
85
|
+
return callbackSession(admitted, selfIdentity, undefined, execution, configuration);
|
|
86
|
+
}
|
|
87
|
+
function callbackSession(kernel, expression, carrier, execution, configuration) {
|
|
88
|
+
const resolveCallback = createCallbackCredentialResolver(configuration.signing, configuration.issuer, configuration.subject, kernel, expression, carrier, execution);
|
|
89
|
+
return new ClientSession({
|
|
90
|
+
kernel,
|
|
91
|
+
fetch: boundedFetch(configuration.fetch, execution.signal),
|
|
92
|
+
auth: {
|
|
93
|
+
ttlSeconds: Math.min(callbackTtl(execution.deadline), maximumCallbackDelegationTtlSeconds),
|
|
94
|
+
async resolve(_call, signal) {
|
|
95
|
+
const callback = await resolveCallback(signal);
|
|
96
|
+
signal.throwIfAborted();
|
|
97
|
+
return {
|
|
98
|
+
credential: callback.credential,
|
|
99
|
+
delegate: { ttlSeconds: callback.delegateTtlSeconds },
|
|
100
|
+
};
|
|
101
101
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
},
|
|
103
|
+
policy: {
|
|
104
|
+
maximumRouteAgeMs: DEFAULT_ROUTE_AGE_MS,
|
|
105
|
+
...(configuration.policy.allowEndpoint === undefined
|
|
106
|
+
? {}
|
|
107
|
+
: { allowEndpoint: configuration.policy.allowEndpoint }),
|
|
108
|
+
...(configuration.policy.allowInsecureHttp === true ? { allowInsecureHttp: true } : {}),
|
|
109
|
+
},
|
|
110
|
+
timeoutMs: Math.max(1, execution.deadline - Date.now()),
|
|
111
|
+
});
|
|
105
112
|
}
|
|
106
113
|
async function establishCaller(input, audience, verification, execution, policy) {
|
|
107
114
|
requireLive(execution);
|
|
@@ -8,6 +8,11 @@ export interface SignedCallback {
|
|
|
8
8
|
readonly delegateTtlSeconds: number;
|
|
9
9
|
}
|
|
10
10
|
export type CallbackGrantMode = 'caller' | 'self' | 'union';
|
|
11
|
+
/** The Domain's own authority, independent of any caller. */
|
|
12
|
+
export declare const selfIdentity: Readonly<{
|
|
13
|
+
kind: 'identity';
|
|
14
|
+
self: true;
|
|
15
|
+
}>;
|
|
11
16
|
/** Exact callback Grant expressions corresponding to the Kernel Domain graph modes. */
|
|
12
17
|
export declare function callbackIdentities(nested: string | Uint8Array): Readonly<{
|
|
13
18
|
caller: Readonly<{
|
|
@@ -22,10 +27,10 @@ export declare function callbackIdentities(nested: string | Uint8Array): Readonl
|
|
|
22
27
|
kind: 'union';
|
|
23
28
|
operands: readonly (Readonly<{
|
|
24
29
|
kind: 'identity';
|
|
25
|
-
|
|
30
|
+
self: true;
|
|
26
31
|
}> | Readonly<{
|
|
27
32
|
kind: 'identity';
|
|
28
|
-
|
|
33
|
+
credential: string | Uint8Array<ArrayBufferLike>;
|
|
29
34
|
}>)[];
|
|
30
35
|
}>;
|
|
31
36
|
}>;
|
|
@@ -37,8 +42,11 @@ export declare function signCallback(signing: SigningMaterial, issuer: IssuerId,
|
|
|
37
42
|
readonly deadline: number;
|
|
38
43
|
readonly signal: AbortSignal;
|
|
39
44
|
}): Promise<string>;
|
|
40
|
-
/**
|
|
41
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Reuse one exact callback authority partition, refreshing only near its bounded expiry. Without a
|
|
47
|
+
* carrier the callback is the Domain's own authority, bounded by the execution alone.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createCallbackCredentialResolver(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, carrier: VerifiedCredential | undefined, execution: {
|
|
42
50
|
readonly deadline: number;
|
|
43
51
|
readonly signal: AbortSignal;
|
|
44
52
|
}): (signal: AbortSignal) => Promise<SignedCallback>;
|
|
@@ -4,10 +4,12 @@ import { requireLive } from './errors.js';
|
|
|
4
4
|
export const maximumCallbackTtlSeconds = 120;
|
|
5
5
|
export const maximumCallbackDelegationTtlSeconds = 60;
|
|
6
6
|
const CALLBACK_DELEGATION_SKEW_SECONDS = 5;
|
|
7
|
+
/** The Domain's own authority, independent of any caller. */
|
|
8
|
+
export const selfIdentity = Object.freeze({ kind: 'identity', self: true });
|
|
7
9
|
/** Exact callback Grant expressions corresponding to the Kernel Domain graph modes. */
|
|
8
10
|
export function callbackIdentities(nested) {
|
|
9
11
|
const caller = Object.freeze({ kind: 'identity', credential: nested });
|
|
10
|
-
const self =
|
|
12
|
+
const self = selfIdentity;
|
|
11
13
|
return Object.freeze({
|
|
12
14
|
caller,
|
|
13
15
|
self,
|
|
@@ -18,15 +20,18 @@ export function callbackIdentities(nested) {
|
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
export async function signDomainCallback(signing, issuer, subject, audience, execution) {
|
|
21
|
-
return (await sign(signing, issuer, subject, audience,
|
|
23
|
+
return (await sign(signing, issuer, subject, audience, selfIdentity, execution)).credential;
|
|
22
24
|
}
|
|
23
25
|
export async function signCallback(signing, issuer, subject, audience, expression, carrier, execution) {
|
|
24
26
|
const maximum = carrier.exp ?? Number.POSITIVE_INFINITY;
|
|
25
27
|
return (await sign(signing, issuer, subject, audience, expression, execution, maximum)).credential;
|
|
26
28
|
}
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Reuse one exact callback authority partition, refreshing only near its bounded expiry. Without a
|
|
31
|
+
* carrier the callback is the Domain's own authority, bounded by the execution alone.
|
|
32
|
+
*/
|
|
28
33
|
export function createCallbackCredentialResolver(signing, issuer, subject, audience, expression, carrier, execution) {
|
|
29
|
-
const maximum = carrier
|
|
34
|
+
const maximum = carrier?.exp ?? Number.POSITIVE_INFINITY;
|
|
30
35
|
let cached;
|
|
31
36
|
let pending;
|
|
32
37
|
return async (signal) => {
|
|
@@ -2,6 +2,7 @@ import type { Fetch } from '@astrale-os/kernel-client';
|
|
|
2
2
|
import type * as publicationProtocol from '@astrale-os/kernel-protocol/publication';
|
|
3
3
|
import type { Issuer, exchange, publication } from '@astrale-os/kernel-server';
|
|
4
4
|
import type { IssuerId, JsonWebKey } from '../../platform/auth/index.js';
|
|
5
|
+
import type { ClientSession } from '../../platform/client/session/index.js';
|
|
5
6
|
import type { InvocationAuthority } from '../invocation/index.js';
|
|
6
7
|
import type { ExchangeOptions } from './exchange.js';
|
|
7
8
|
import type { IdentityNetworkPolicy } from './jwks.js';
|
|
@@ -10,6 +11,14 @@ export interface Identity extends Issuer, InvocationAuthority {
|
|
|
10
11
|
readonly subject: string;
|
|
11
12
|
readonly tokenExchange?: exchange.Provider;
|
|
12
13
|
delivery(publication: publicationProtocol.Publication): publication.delivery.Provider;
|
|
14
|
+
/**
|
|
15
|
+
* The Domain's own authority toward one Kernel for work no caller credential carries, such as a
|
|
16
|
+
* durable run. Required to serve durable Workflows; the caller closes the Session.
|
|
17
|
+
*/
|
|
18
|
+
executor?(kernel: IssuerId, execution: {
|
|
19
|
+
readonly deadline: number;
|
|
20
|
+
readonly signal: AbortSignal;
|
|
21
|
+
}): ClientSession;
|
|
13
22
|
}
|
|
14
23
|
export interface IdentityInput extends IdentityNetworkPolicy {
|
|
15
24
|
readonly issuer: IssuerId;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { admit, authenticate } from './authentication.js';
|
|
1
|
+
import { admit, authenticate, executorSession } from './authentication.js';
|
|
2
2
|
import { publicationDelivery } from './delivery.js';
|
|
3
3
|
import { httpIssuer } from './errors.js';
|
|
4
4
|
import { maximumExchangeTtl, tokenExchange } from './exchange.js';
|
|
@@ -51,6 +51,7 @@ export function createIdentity(input) {
|
|
|
51
51
|
...(exchangeProvider === undefined ? {} : { tokenExchange: exchangeProvider }),
|
|
52
52
|
delivery: (publication) => publicationDelivery(material, issuer, input.subject, publication),
|
|
53
53
|
admit: (request) => admit(request, configuration),
|
|
54
|
+
executor: (kernel, execution) => executorSession(kernel, execution, configuration),
|
|
54
55
|
authenticate: (request) => authenticate(request.credential, request.via.issuer, request.context.execution, configuration),
|
|
55
56
|
});
|
|
56
57
|
}
|
|
@@ -67,6 +68,7 @@ export function identity(input) {
|
|
|
67
68
|
'delivery',
|
|
68
69
|
'admit',
|
|
69
70
|
'authenticate',
|
|
71
|
+
'executor',
|
|
70
72
|
]);
|
|
71
73
|
if (Reflect.ownKeys(input).some((key) => !allowed.has(key)) ||
|
|
72
74
|
typeof input.subject !== 'string' ||
|
|
@@ -74,7 +76,8 @@ export function identity(input) {
|
|
|
74
76
|
typeof input.loadPublicKeys !== 'function' ||
|
|
75
77
|
typeof input.delivery !== 'function' ||
|
|
76
78
|
typeof input.admit !== 'function' ||
|
|
77
|
-
typeof input.authenticate !== 'function'
|
|
79
|
+
typeof input.authenticate !== 'function' ||
|
|
80
|
+
(input.executor !== undefined && typeof input.executor !== 'function')) {
|
|
78
81
|
invalid();
|
|
79
82
|
}
|
|
80
83
|
const receiver = Object.freeze({ ...input });
|
|
@@ -92,6 +95,7 @@ export function identity(input) {
|
|
|
92
95
|
delivery: receiver.delivery.bind(receiver),
|
|
93
96
|
admit: receiver.admit.bind(receiver),
|
|
94
97
|
authenticate: receiver.authenticate.bind(receiver),
|
|
98
|
+
...(receiver.executor === undefined ? {} : { executor: receiver.executor.bind(receiver) }),
|
|
95
99
|
});
|
|
96
100
|
}
|
|
97
101
|
function invalid() {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { serve } from './serve.js';
|
|
2
2
|
export type { ExecutionContext, ServedApplication, ServeInput } from './serve.js';
|
|
3
|
-
export { createIdentity, type ExchangeOptions, type Identity, type IdentityInput, } from './identity/index.js';
|
|
3
|
+
export { createIdentity, maximumExchangeTtl, type ExchangeOptions, type Identity, type IdentityInput, } from './identity/index.js';
|
|
4
4
|
export { InvocationError } from './invocation/index.js';
|
|
5
5
|
export type { InvocationErrorCode } from './invocation/index.js';
|
|
6
|
+
export type { DurableEngine, DurableServing, DurableServingProvider } from './workflows/index.js';
|
|
7
|
+
export { isCanonicalDurableRequest, isLegacyDurableRequest } from './workflows/route.js';
|
package/dist/execution/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { serve } from './serve.js';
|
|
2
|
-
export { createIdentity, } from './identity/index.js';
|
|
2
|
+
export { createIdentity, maximumExchangeTtl, } from './identity/index.js';
|
|
3
3
|
export { InvocationError } from './invocation/index.js';
|
|
4
|
+
export { isCanonicalDurableRequest, isLegacyDurableRequest } from './workflows/route.js';
|
|
@@ -7,14 +7,14 @@ import type { Domain } from '../../../platform/schema/index.js';
|
|
|
7
7
|
export interface InvocationAdmissionInput {
|
|
8
8
|
readonly call: invocation.Call;
|
|
9
9
|
readonly credential?: string;
|
|
10
|
-
readonly via: invocation.Via;
|
|
10
|
+
readonly via: invocation.Via | invocation.PublicationVia;
|
|
11
11
|
readonly publication: publication.Publication;
|
|
12
12
|
readonly domain: Domain;
|
|
13
13
|
readonly context: InvocationContext;
|
|
14
14
|
}
|
|
15
15
|
export interface InvocationAuthenticationInput {
|
|
16
16
|
readonly credential: string;
|
|
17
|
-
readonly via: invocation.Via;
|
|
17
|
+
readonly via: invocation.Via | invocation.PublicationVia;
|
|
18
18
|
readonly publication: publication.Publication;
|
|
19
19
|
readonly domain: Domain;
|
|
20
20
|
readonly context: InvocationContext;
|
|
@@ -31,4 +31,4 @@ export interface InvocationAuthority {
|
|
|
31
31
|
}
|
|
32
32
|
/** Capture the execution Identity capability and receiver-bind both privileged operations. */
|
|
33
33
|
export declare function authority(input: InvocationAuthority): InvocationAuthority;
|
|
34
|
-
export declare function requireVia(request: HandlerRequest): invocation.Via;
|
|
34
|
+
export declare function requireVia(request: HandlerRequest): invocation.Via | invocation.PublicationVia;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { ResolvedCallable } from '../../../application/action/index.js';
|
|
2
|
-
export declare function input(callable: ResolvedCallable, value: unknown): void;
|
|
1
|
+
import type { ResolvedCallable, ActionBinaryInput } from '../../../application/action/index.js';
|
|
2
|
+
export declare function input(callable: ResolvedCallable, value: unknown, binary?: ActionBinaryInput): void;
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { isBinaryInput } from '@astrale-os/kernel-dsl/v1/language';
|
|
2
|
+
import { validateInput, protocolError } from '../result/index.js';
|
|
3
|
+
export function input(callable, value, binary) {
|
|
4
|
+
if (isBinaryInput(callable.input)) {
|
|
5
|
+
if (binary === undefined) {
|
|
6
|
+
throw protocolError('VALIDATION_ERROR', 'Binary Function request body is required.');
|
|
7
|
+
}
|
|
8
|
+
if (!Number.isSafeInteger(binary.length) ||
|
|
9
|
+
binary.length < 0 ||
|
|
10
|
+
binary.length > callable.input.maximumBytes) {
|
|
11
|
+
throw protocolError('LIMIT_EXCEEDED', 'Binary Function request body exceeds its limit.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else if (binary !== undefined) {
|
|
15
|
+
throw protocolError('VALIDATION_ERROR', 'Callable does not accept a binary request body.');
|
|
16
|
+
}
|
|
3
17
|
validateInput(callable, value);
|
|
4
18
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { isBinaryInput } from '@astrale-os/kernel-dsl/v1/language';
|
|
2
3
|
import { reference } from '../../../platform/client/session/index.js';
|
|
3
4
|
import { NodeId } from '../../../platform/graph/node/index.js';
|
|
4
5
|
import { Path } from '../../../platform/graph/path/index.js';
|
|
@@ -20,19 +21,14 @@ function bindDependency(dependency, session) {
|
|
|
20
21
|
const invoke = session.invoke.bind(session);
|
|
21
22
|
return Object.freeze({
|
|
22
23
|
async invoke(select, ...args) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const callable = dependency.callable(selected.key);
|
|
32
|
-
if (callable !== selected || Key.origin(callable.key) !== dependency.origin)
|
|
33
|
-
invalid();
|
|
34
|
-
if ('owner' in callable && (!callable.executable || !ownMethod(dependency, callable)))
|
|
35
|
-
invalid();
|
|
24
|
+
const callable = selectedCallable(dependency, select);
|
|
25
|
+
if (isBinaryInput(callable.input)) {
|
|
26
|
+
if ('owner' in callable || args.length !== 2)
|
|
27
|
+
invalid();
|
|
28
|
+
// Runtime selection is wider than the authored Function; mode was checked above.
|
|
29
|
+
const binaryReference = reference(dependency, callable);
|
|
30
|
+
return invoke(binaryReference, args[0], binaryOptions(args[1]));
|
|
31
|
+
}
|
|
36
32
|
if ('owner' in callable && !callable.static) {
|
|
37
33
|
if (args.length < 2 || args.length > 3 || typeof args[0] !== 'string')
|
|
38
34
|
invalid();
|
|
@@ -45,6 +41,22 @@ function bindDependency(dependency, session) {
|
|
|
45
41
|
},
|
|
46
42
|
});
|
|
47
43
|
}
|
|
44
|
+
function selectedCallable(domain, select) {
|
|
45
|
+
if (typeof select !== 'function')
|
|
46
|
+
invalid();
|
|
47
|
+
const selected = select(domain);
|
|
48
|
+
if (selected === null ||
|
|
49
|
+
typeof selected !== 'object' ||
|
|
50
|
+
!('key' in selected) ||
|
|
51
|
+
typeof selected.key !== 'string')
|
|
52
|
+
invalid();
|
|
53
|
+
const callable = domain.callable(selected.key);
|
|
54
|
+
if (callable !== selected || Key.origin(callable.key) !== domain.origin)
|
|
55
|
+
invalid();
|
|
56
|
+
if ('owner' in callable && (!callable.executable || !ownMethod(domain, callable)))
|
|
57
|
+
invalid();
|
|
58
|
+
return callable;
|
|
59
|
+
}
|
|
48
60
|
function ownMethod(domain, callable) {
|
|
49
61
|
const owner = Object.values(domain.classes).find((value) => value === callable.owner);
|
|
50
62
|
if (owner === undefined)
|
|
@@ -63,6 +75,16 @@ function options(value) {
|
|
|
63
75
|
// Canonical ClientSession admits option values and clamps them to the parent lifetime.
|
|
64
76
|
return value;
|
|
65
77
|
}
|
|
78
|
+
function binaryOptions(value) {
|
|
79
|
+
if (value === null ||
|
|
80
|
+
typeof value !== 'object' ||
|
|
81
|
+
Array.isArray(value) ||
|
|
82
|
+
!Object.hasOwn(value, 'body') ||
|
|
83
|
+
!(value.body instanceof Blob) ||
|
|
84
|
+
Reflect.ownKeys(value).some((key) => key !== 'body' && key !== 'timeoutMs'))
|
|
85
|
+
invalid();
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
66
88
|
function invalid() {
|
|
67
89
|
throw new TypeError('Dependency invocation requires one owned executable callable, its input, and valid instance/options.');
|
|
68
90
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HandlerRequest } from '@astrale-os/kernel-server';
|
|
2
|
-
import type { ActionExecution, Caller, DomainGraphExecutors, DomainKernelSessions } from '../../../application/action/index.js';
|
|
2
|
+
import type { ActionExecution, ActionBinaryInput, Caller, DomainGraphExecutors, DomainKernelSessions } from '../../../application/action/index.js';
|
|
3
3
|
import type { HandlerDependencies } from '../../../application/dependency/index.js';
|
|
4
4
|
import type { MutationExecutor } from '../../../application/mutation/index.js';
|
|
5
5
|
import type { QueryExecutor } from '../../../application/query/index.js';
|
|
@@ -10,6 +10,7 @@ import type { InvocationAuthentication } from '../admission/index.js';
|
|
|
10
10
|
export interface DispatchContext {
|
|
11
11
|
readonly domain: Domain;
|
|
12
12
|
readonly input: unknown;
|
|
13
|
+
readonly body?: ActionBinaryInput;
|
|
13
14
|
readonly caller: Caller;
|
|
14
15
|
readonly dependencies: HandlerDependencies<Domain> | null;
|
|
15
16
|
readonly kernel: DomainKernelSessions | null;
|
|
@@ -17,6 +17,7 @@ export function context(request, runtime, authentication, self) {
|
|
|
17
17
|
return Object.freeze({
|
|
18
18
|
domain: runtime.loaded.domain,
|
|
19
19
|
input: request.call.input,
|
|
20
|
+
...(request.context.binary === undefined ? {} : { body: request.context.binary }),
|
|
20
21
|
caller: authentication.caller,
|
|
21
22
|
kernel: authentication.graph === null
|
|
22
23
|
? null
|
|
@@ -26,11 +26,16 @@ export async function invoke(request, inputConfig) {
|
|
|
26
26
|
const selected = select(inputConfig.runtime.loaded.registry, selectedTarget);
|
|
27
27
|
if (selected === undefined)
|
|
28
28
|
return Object.freeze({ kind: 'miss' });
|
|
29
|
-
|
|
29
|
+
if (selected.implementation.kind === 'workflow' && selected.implementation.durable) {
|
|
30
|
+
// The Kernel records a durable call as an Execution and runs it on the engine; running it
|
|
31
|
+
// inline here would silently drop durability.
|
|
32
|
+
throw protocolError('UNSUPPORTED', 'A durable Workflow runs only as a Kernel Execution.');
|
|
33
|
+
}
|
|
34
|
+
input(selected.implementation.callable, request.call.input, request.context.binary);
|
|
30
35
|
const admittedAuthentication = await authentication(selected.implementation.callable, request, release, inputConfig.runtime.loaded.domain, inputConfig.authority);
|
|
31
36
|
const close = once(admittedAuthentication.close);
|
|
32
|
-
active(request.context);
|
|
33
37
|
try {
|
|
38
|
+
active(request.context);
|
|
34
39
|
const dispatchContext = context(request, inputConfig.runtime, admittedAuthentication, selected.self);
|
|
35
40
|
const output = selected.implementation.kind === 'action'
|
|
36
41
|
? await action(selected.implementation, dispatchContext)
|
|
@@ -5,6 +5,7 @@ import type { WorkflowRunner } from '../application/workflow/runner/index.js';
|
|
|
5
5
|
import type { Identity } from './identity/index.js';
|
|
6
6
|
import type { ReleaseLoader } from './service/index.js';
|
|
7
7
|
import type { ServiceServerOptions } from './service/index.js';
|
|
8
|
+
import type { DurableServingProvider } from './workflows/index.js';
|
|
8
9
|
export interface ExecutionContext {
|
|
9
10
|
waitUntil(promise: Promise<unknown>): void;
|
|
10
11
|
readonly sleep?: (durationMs: number) => Promise<void>;
|
|
@@ -17,6 +18,11 @@ export interface ServeInput<Environment, RuntimeValue extends Runtime, RuntimeEn
|
|
|
17
18
|
readonly server: ServiceServerOptions;
|
|
18
19
|
readonly delivery?: (environment: Environment) => publication.delivery.Provider | undefined;
|
|
19
20
|
readonly workflows?: WorkflowRunner;
|
|
21
|
+
/**
|
|
22
|
+
* Serve durable Workflows to the workflow engine; required when the Release declares any.
|
|
23
|
+
* Undefined for an environment that names no engine: engine requests are then refused.
|
|
24
|
+
*/
|
|
25
|
+
readonly durable?: (environment: Environment, request: Request) => ReturnType<DurableServingProvider>;
|
|
20
26
|
}
|
|
21
27
|
export interface ServedApplication<Environment> {
|
|
22
28
|
fetch(request: Request, environment: Environment, context?: ExecutionContext): Promise<Response>;
|
package/dist/execution/serve.js
CHANGED
|
@@ -22,6 +22,9 @@ export function serve(input) {
|
|
|
22
22
|
server: input.server,
|
|
23
23
|
...(input.delivery === undefined ? {} : { delivery: input.delivery(environment) }),
|
|
24
24
|
...(input.workflows === undefined ? {} : { workflows: input.workflows }),
|
|
25
|
+
...(input.durable === undefined
|
|
26
|
+
? {}
|
|
27
|
+
: { durable: (request) => input.durable(environment, request) }),
|
|
25
28
|
});
|
|
26
29
|
if (drained)
|
|
27
30
|
service.drain();
|