@astrale-os/sdk 0.6.0-beta.24 → 0.6.0-beta.25

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 (29) hide show
  1. package/README.md +6 -6
  2. package/dist/application/action/context.d.ts +29 -25
  3. package/dist/application/action/index.d.ts +1 -1
  4. package/dist/application/dependency/invoker.d.ts +5 -5
  5. package/dist/application/integration/operation.d.ts +2 -2
  6. package/dist/application/runtime/workflows.d.ts +1 -1
  7. package/dist/application/runtime/workflows.js +1 -4
  8. package/dist/application/workflow/context.d.ts +1 -1
  9. package/dist/application/workflow/define.d.ts +1 -1
  10. package/dist/application/workflow/define.js +1 -1
  11. package/dist/application/workflow/workflow.d.ts +1 -3
  12. package/dist/execution/identity/authentication.d.ts +10 -1
  13. package/dist/execution/identity/authentication.js +21 -0
  14. package/dist/execution/identity/identity.js +5 -1
  15. package/dist/execution/invocation/admission/authentication.d.ts +8 -5
  16. package/dist/execution/invocation/admission/authentication.js +39 -21
  17. package/dist/execution/invocation/admission/authority.d.ts +17 -1
  18. package/dist/execution/invocation/admission/authority.js +5 -2
  19. package/dist/execution/invocation/admission/index.d.ts +2 -2
  20. package/dist/execution/invocation/admission/index.js +1 -1
  21. package/dist/execution/invocation/dependency/bind.d.ts +2 -0
  22. package/dist/execution/invocation/dependency/bind.js +1 -2
  23. package/dist/execution/invocation/dispatch/context.d.ts +5 -5
  24. package/dist/execution/invocation/dispatch/context.js +16 -30
  25. package/dist/execution/invocation/dispatch/workflow.js +0 -4
  26. package/dist/execution/invocation/integration/call.d.ts +2 -1
  27. package/dist/execution/invocation/integration/call.js +1 -1
  28. package/dist/execution/invocation/integration/clients.d.ts +1 -1
  29. package/package.json +9 -8
package/README.md CHANGED
@@ -5,10 +5,8 @@ single-step Actions, explicit multi-step Workflows, provider-neutral Integration
5
5
  authoring, Domain composition, Deployment, and Execution without exposing DSL callable
6
6
  topology in implementation files.
7
7
 
8
- This draft moves the Domain name and entrypoint into its root Schema. The source is rebased on
9
- current SDK main, but the required canonical Kernel Schema contract is not yet published in the
10
- pinned dependency cohort. See [the migration and qualification boundary](docs/domain-schema-migration.md)
11
- before using or releasing this branch.
8
+ The Domain name and optional View entrypoint belong to its root Schema. See
9
+ [the Schema authoring contract](docs/domain-schema-migration.md).
12
10
 
13
11
  [![CI](https://github.com/astrale-os/sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/astrale-os/sdk/actions/workflows/ci.yml)
14
12
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
@@ -60,11 +58,13 @@ Install the URL printed by `astrale-domain deploy` on each intended instance.
60
58
 
61
59
  ## Handler authority and dependencies
62
60
 
63
- Authenticated handlers use Domain self authority for `kernel`, `graph`, `query`, and `mutate`.
61
+ Handlers use Domain self authority for `kernel`, `graph`, `query`, and `mutate`.
64
62
  `dependencies.directory.caller.invoke(d => d.functions.lookup, input)` calls a direct declared
65
63
  dependency on behalf of the incoming caller Identity; `.self` calls it as the executing Domain.
66
64
  Explicit `caller` or `self` authority is also available on `kernel` and `graph`; the former
67
- handler `client` is removed. See [the API and migration guide](docs/handler-dependencies.md).
65
+ handler `client` is removed. A call to an `auth: 'anonymous'` callable that carries no credential
66
+ has no caller: its handler acts as the Domain alone, without the `caller` selections. See
67
+ [the API and migration guide](docs/handler-dependencies.md).
68
68
 
69
69
  ## Durable Workflows
70
70
 
@@ -47,37 +47,41 @@ export interface DomainGraphExecutor<DomainValue extends Domain = Domain> {
47
47
  readonly query: QueryExecutor<DomainValue>;
48
48
  readonly mutate: MutationExecutor<DomainValue>;
49
49
  }
50
- export type DomainGraphExecutors<DomainValue extends Domain = Domain> = Readonly<DomainGraphExecutor<DomainValue> & Record<'caller' | 'self', DomainGraphExecutor<DomainValue>>>;
50
+ /** Whose authority one Kernel call exercises: the incoming caller's or the installed Domain's. */
51
+ export type AuthorityMode = 'caller' | 'self';
52
+ /**
53
+ * One value per selectable authority. `self` exists in every invocation; `caller` exists only when
54
+ * the call carried a credential, so it is absent from a call that carried none.
55
+ */
56
+ export type AuthoritySelection<Value, Selectable extends AuthorityMode = AuthorityMode> = Readonly<Record<Selectable, Value> & {
57
+ readonly [Absent in Exclude<AuthorityMode, Selectable>]?: never;
58
+ }>;
59
+ export type DomainGraphExecutors<DomainValue extends Domain = Domain, Selectable extends AuthorityMode = AuthorityMode> = Readonly<DomainGraphExecutor<DomainValue> & AuthoritySelection<DomainGraphExecutor<DomainValue>, Selectable>>;
51
60
  /** Self-default Kernel session with one level of explicit authority selection. */
52
- export type DomainKernelSessions = Readonly<BoundClientSession & Record<'caller' | 'self', BoundClientSession>>;
53
- type AuthorityOf<Callable, DomainValue extends Domain> = Callable extends {
54
- readonly auth: 'anonymous';
55
- } ? {
56
- readonly caller: AnonymousCaller;
57
- readonly dependencies: null;
58
- readonly kernel: null;
59
- readonly graph: null;
60
- readonly query?: never;
61
- readonly mutate?: never;
62
- } | {
63
- readonly caller: AuthenticatedCaller;
64
- readonly dependencies: HandlerDependencies<DomainValue>;
65
- readonly kernel: DomainKernelSessions;
66
- readonly graph: DomainGraphExecutors<DomainValue>;
61
+ export type DomainKernelSessions<Selectable extends AuthorityMode = AuthorityMode> = Readonly<BoundClientSession & AuthoritySelection<BoundClientSession, Selectable>>;
62
+ /** What a handler holds toward the Kernel: the installed Domain authority by default. */
63
+ interface DomainCapabilities<DomainValue extends Domain, Selectable extends AuthorityMode> {
64
+ readonly dependencies: HandlerDependencies<DomainValue, Selectable>;
65
+ readonly kernel: DomainKernelSessions<Selectable>;
66
+ readonly graph: DomainGraphExecutors<DomainValue, Selectable>;
67
67
  /** Convenience executor using the installed Domain authority (`graph.self`). */
68
68
  readonly query: QueryExecutor<DomainValue>;
69
69
  /** Convenience executor using the installed Domain authority (`graph.self`). */
70
70
  readonly mutate: MutationExecutor<DomainValue>;
71
- } : {
71
+ }
72
+ /**
73
+ * A call to an `anonymous` callable that carries no credential has no caller, and its handler acts
74
+ * as the installed Domain alone. Every other call also selects its authenticated caller.
75
+ */
76
+ type AuthorityOf<Callable, DomainValue extends Domain> = Callable extends {
77
+ readonly auth: 'anonymous';
78
+ } ? ({
79
+ readonly caller: AnonymousCaller;
80
+ } & DomainCapabilities<DomainValue, 'self'>) | ({
72
81
  readonly caller: AuthenticatedCaller;
73
- readonly dependencies: HandlerDependencies<DomainValue>;
74
- readonly kernel: DomainKernelSessions;
75
- readonly graph: DomainGraphExecutors<DomainValue>;
76
- /** Convenience executor using the installed Domain authority (`graph.self`). */
77
- readonly query: QueryExecutor<DomainValue>;
78
- /** Convenience executor using the installed Domain authority (`graph.self`). */
79
- readonly mutate: MutationExecutor<DomainValue>;
80
- };
82
+ } & DomainCapabilities<DomainValue, AuthorityMode>) : {
83
+ readonly caller: AuthenticatedCaller;
84
+ } & DomainCapabilities<DomainValue, AuthorityMode>;
81
85
  type SelfOf<Callable> = Callable extends ResolvedMethod<unknown, unknown, false> ? {
82
86
  readonly self: NodeId;
83
87
  } : {
@@ -1,5 +1,5 @@
1
1
  export { defineAction, isAction } from './define.js';
2
2
  export type { Action, ActionAddress, ActionAddressOf, ActionDefinition, CallableAtAddress, LocalCallable, ResolvedDomainOf, } from './action.js';
3
- export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, Caller, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, ResolvedCallable, } from './context.js';
3
+ export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, AuthorityMode, AuthoritySelection, Caller, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, ResolvedCallable, } from './context.js';
4
4
  export type { ActionBinary, ActionOutputOf } from './output.js';
5
5
  export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, } from '../error/index.js';
@@ -2,7 +2,7 @@ import type { Binary, Input } from '../../platform/client/index.js';
2
2
  import type { SessionRequestOptions, SessionRequestOptionsWithBody } from '../../platform/client/session/index.js';
3
3
  import type { NodeId } from '../../platform/graph/node/index.js';
4
4
  import type { CallableInputOf, CallableInputModeOf, CallableOutputOf, CallableResultOf, Domain, ResolvedFunction, ResolvedMethod, KernelSchema, schema } from '../../platform/schema/index.js';
5
- import type { LocalCallable } from '../action/index.js';
5
+ import type { AuthorityMode, AuthoritySelection, LocalCallable } from '../action/index.js';
6
6
  export type DependencyCallOptions = Pick<SessionRequestOptions, 'timeoutMs' | 'idempotencyKey' | 'body'>;
7
7
  export type DependencyBinaryCallOptions = Pick<SessionRequestOptionsWithBody, 'timeoutMs' | 'idempotencyKey' | 'body'>;
8
8
  type InstanceMethodOf<D extends Domain> = Extract<LocalCallable<D>, ResolvedMethod<unknown, unknown, false>>;
@@ -22,10 +22,10 @@ export interface DependencyInvoker<D extends Domain> {
22
22
  }
23
23
  /**
24
24
  * Direct Schema aliases with explicit authority selection and no default invoke.
25
- * self is the executing Domain; caller forwards the incoming caller Identity.
26
- * The canonical Kernel is exposed through context.kernel.
25
+ * self is the executing Domain; caller forwards the incoming caller Identity, and is absent from a
26
+ * call that carried no credential. The canonical Kernel is exposed through context.kernel.
27
27
  */
28
- export type HandlerDependencies<D extends Domain> = Readonly<{
29
- [Alias in keyof D['dependencies'] as D['dependencies'][Alias] extends schema.DomainOf<KernelSchema> ? never : Alias]: Readonly<Record<'caller' | 'self', DependencyInvoker<Extract<D['dependencies'][Alias], Domain>>>>;
28
+ export type HandlerDependencies<D extends Domain, Selectable extends AuthorityMode = AuthorityMode> = Readonly<{
29
+ [Alias in keyof D['dependencies'] as D['dependencies'][Alias] extends schema.DomainOf<KernelSchema> ? never : Alias]: AuthoritySelection<DependencyInvoker<Extract<D['dependencies'][Alias], Domain>>, Selectable>;
30
30
  }>;
31
31
  export {};
@@ -19,9 +19,9 @@ export interface IntegrationExecution {
19
19
  readonly signal: AbortSignal;
20
20
  readonly deadline: number;
21
21
  readonly idempotencyKey?: string;
22
- /** Exact caller-bound invocation capability; absent for an anonymous invocation. */
22
+ /** Exact caller-bound invocation capability; absent when the call carried no credential. */
23
23
  readonly invoke?: BoundClientSession['invoke'];
24
- /** Domain-self transport authentication; absent for an anonymous invocation. */
24
+ /** Domain-self transport authentication; absent only outside a Kernel invocation. */
25
25
  readonly auth?: IntegrationAuthentication;
26
26
  }
27
27
  export interface IntegrationOperation<Input = unknown, Output = unknown> {
@@ -9,5 +9,5 @@ export interface RuntimeWorkflow {
9
9
  /** Declared `execution: { durable: true }`: runs on the workflow engine, never inline. */
10
10
  readonly durable: boolean;
11
11
  }
12
- /** Associate admitted Workflows with their exact authenticated value callables. */
12
+ /** Associate admitted Workflows with their exact value callables. */
13
13
  export declare function admitWorkflows(callables: readonly RuntimeCallable[], input: unknown): readonly RuntimeWorkflow[];
@@ -1,6 +1,6 @@
1
1
  import { isWorkflow } from '../workflow/index.js';
2
2
  import { runtimeCallableAt } from './callables.js';
3
- /** Associate admitted Workflows with their exact authenticated value callables. */
3
+ /** Associate admitted Workflows with their exact value callables. */
4
4
  export function admitWorkflows(callables, input) {
5
5
  if (!Array.isArray(input))
6
6
  throw new TypeError('Runtime workflows must be an array.');
@@ -16,9 +16,6 @@ export function admitWorkflows(callables, input) {
16
16
  if (callable === undefined) {
17
17
  throw new TypeError(`Runtime Workflow ${definition.address} is not root-local and executable.`);
18
18
  }
19
- if (callable.auth === 'anonymous') {
20
- throw new TypeError(`Runtime Workflow ${definition.address} must be authenticated.`);
21
- }
22
19
  if (callable.output.mode !== 'value') {
23
20
  throw new TypeError(`Runtime Workflow ${definition.address} must have value output.`);
24
21
  }
@@ -4,7 +4,7 @@ import type { DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
4
4
  import type { Integrations } from '../integration/index.js';
5
5
  import type { DurableCallable, DurableWorkflowContext } from './durable.js';
6
6
  import type { WorkflowStep } from './step/index.js';
7
- /** Multi-step context of one authenticated value-output callable run inside its invocation. */
7
+ /** Multi-step context of one value-output callable run inside its invocation. */
8
8
  export type InlineWorkflowContext<Callable extends ResolvedCallable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = ActionContext<Callable, DomainValue, Definitions, Errors> & {
9
9
  readonly step: WorkflowStep;
10
10
  };
@@ -3,7 +3,7 @@ import type { ResolvedDomainOf } from '../action/index.js';
3
3
  import type { DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
4
4
  import type { Integrations } from '../integration/index.js';
5
5
  import type { CallableAtWorkflowAddress, Workflow, WorkflowAddress, WorkflowDefinition, WorkflowOptions } from './workflow.js';
6
- /** Define one authenticated multi-step value-output implementation by its SDK Action address. */
6
+ /** Define one multi-step value-output implementation by its SDK Action address. */
7
7
  export declare function defineWorkflow<Schema extends schema.DomainSchema, Definitions extends Integrations = Readonly<Record<never, never>>>(): {
8
8
  <const Address extends WorkflowAddress<Schema>>(address: Address, run: Workflow<CallableAtWorkflowAddress<Schema, Address>, Definitions, ResolvedDomainOf<Schema>, NoDeclaredErrors>): WorkflowDefinition<Schema, Address, Definitions, NoDeclaredErrors>;
9
9
  <const Address extends WorkflowAddress<Schema>, const Errors extends DeclaredErrors = NoDeclaredErrors>(address: Address, options: WorkflowOptions<Errors>, run: Workflow<CallableAtWorkflowAddress<Schema, Address>, Definitions, ResolvedDomainOf<Schema>, Errors>): WorkflowDefinition<Schema, Address, Definitions, Errors>;
@@ -2,7 +2,7 @@ import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
2
2
  import { rememberDeclaredErrors } from '../error/index.js';
3
3
  const admittedWorkflows = new WeakSet();
4
4
  const COMPAT_TOKEN = /^[A-Za-z0-9._~-]{1,64}$/u;
5
- /** Define one authenticated multi-step value-output implementation by its SDK Action address. */
5
+ /** Define one multi-step value-output implementation by its SDK Action address. */
6
6
  export function defineWorkflow() {
7
7
  return function define(address, optionsOrRun, possibleRun) {
8
8
  acceptWorkflowAddress(address);
@@ -3,9 +3,7 @@ import type { ActionAddressOf, ActionOutputOf, CallableAtAddress, LocalCallable,
3
3
  import type { DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
4
4
  import type { Integrations } from '../integration/index.js';
5
5
  import type { WorkflowContext } from './context.js';
6
- export type WorkflowCallable<Callable> = Callable extends ResolvedCallable ? Callable extends {
7
- readonly auth: 'anonymous';
8
- } ? never : CallableOutputOf<Callable> extends {
6
+ export type WorkflowCallable<Callable> = Callable extends ResolvedCallable ? CallableOutputOf<Callable> extends {
9
7
  readonly mode: 'value';
10
8
  } ? Callable : never : never;
11
9
  export type WorkflowAddress<Schema extends schema.DomainSchema> = ActionAddressOf<WorkflowCallable<LocalCallable<ResolvedDomainOf<Schema>>>>;
@@ -1,6 +1,6 @@
1
1
  import type { Fetch } from '@astrale-os/kernel-client';
2
2
  import type { CredentialInput, IdentityId, IssuerId } from '../../platform/auth/index.js';
3
- import type { AuthenticatedInvocation } from '../invocation/index.js';
3
+ import type { AuthenticatedInvocation, SelfInvocation } 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';
@@ -26,6 +26,15 @@ export declare function authenticate(credentialInput: CredentialInput, expectedK
26
26
  readonly deadline: number;
27
27
  readonly signal: AbortSignal;
28
28
  }, configuration: AuthenticationConfiguration): Promise<AuthenticatedInvocation>;
29
+ /**
30
+ * The Domain's own authority toward the source Kernel of an admitted call that carries no
31
+ * credential. No caller credential bounds it: its callbacks live as long as the execution, exactly
32
+ * as the `self` half of an authenticated call does within its carrier.
33
+ */
34
+ export declare function selfInvocation(kernel: IssuerId, execution: {
35
+ readonly deadline: number;
36
+ readonly signal: AbortSignal;
37
+ }, configuration: AuthenticationConfiguration): SelfInvocation;
29
38
  /**
30
39
  * The Domain's own authority toward one Kernel for a step of the durable Execution `executionId`,
31
40
  * which no caller credential carries: the run acts as its executor, never as its initiator. Every
@@ -78,6 +78,27 @@ export async function authenticate(credentialInput, expectedKernel, execution, c
78
78
  },
79
79
  });
80
80
  }
81
+ /**
82
+ * The Domain's own authority toward the source Kernel of an admitted call that carries no
83
+ * credential. No caller credential bounds it: its callbacks live as long as the execution, exactly
84
+ * as the `self` half of an authenticated call does within its carrier.
85
+ */
86
+ export function selfInvocation(kernel, execution, configuration) {
87
+ requireLive(execution);
88
+ const admitted = httpIssuer(kernel, configuration.policy.allowInsecureHttp === true);
89
+ admitEndpoint(invocationUrl(admitted), configuration.policy);
90
+ const session = callbackSession(admitted, selfIdentity, undefined, execution, configuration);
91
+ let closed = false;
92
+ return Object.freeze({
93
+ graph: Object.freeze({ self: bindSession(session, execution) }),
94
+ close() {
95
+ if (closed)
96
+ return;
97
+ closed = true;
98
+ session.close();
99
+ },
100
+ });
101
+ }
81
102
  /**
82
103
  * The Domain's own authority toward one Kernel for a step of the durable Execution `executionId`,
83
104
  * which no caller credential carries: the run acts as its executor, never as its initiator. Every
@@ -1,4 +1,4 @@
1
- import { admit, authenticate, executorSession } from './authentication.js';
1
+ import { admit, authenticate, executorSession, selfInvocation } from './authentication.js';
2
2
  import { releaseDelivery } from './delivery.js';
3
3
  import { httpIssuer } from './errors.js';
4
4
  import { maximumExchangeTtl, tokenExchange } from './exchange.js';
@@ -53,6 +53,7 @@ export function createIdentity(input) {
53
53
  admit: (request) => admit(request, configuration),
54
54
  executor: (kernel, executionId, execution) => executorSession(kernel, executionId, execution, configuration),
55
55
  authenticate: (request) => authenticate(request.credential, request.via.issuer, request.context.execution, configuration),
56
+ self: async (request) => selfInvocation(request.via.issuer, request.context.execution, configuration),
56
57
  });
57
58
  }
58
59
  export function identity(input) {
@@ -68,6 +69,7 @@ export function identity(input) {
68
69
  'delivery',
69
70
  'admit',
70
71
  'authenticate',
72
+ 'self',
71
73
  'executor',
72
74
  ]);
73
75
  if (Reflect.ownKeys(input).some((key) => !allowed.has(key)) ||
@@ -77,6 +79,7 @@ export function identity(input) {
77
79
  typeof input.delivery !== 'function' ||
78
80
  typeof input.admit !== 'function' ||
79
81
  typeof input.authenticate !== 'function' ||
82
+ typeof input.self !== 'function' ||
80
83
  (input.executor !== undefined && typeof input.executor !== 'function')) {
81
84
  invalid();
82
85
  }
@@ -95,6 +98,7 @@ export function identity(input) {
95
98
  delivery: receiver.delivery.bind(receiver),
96
99
  admit: receiver.admit.bind(receiver),
97
100
  authenticate: receiver.authenticate.bind(receiver),
101
+ self: receiver.self.bind(receiver),
98
102
  ...(receiver.executor === undefined ? {} : { executor: receiver.executor.bind(receiver) }),
99
103
  });
100
104
  }
@@ -1,12 +1,15 @@
1
1
  import type { HandlerRequest } from '@astrale-os/kernel-server';
2
2
  import type { AnonymousCaller, ResolvedCallable } from '../../../application/action/index.js';
3
3
  import type { Domain } from '../../../platform/schema/index.js';
4
- import type { AuthenticatedInvocation, InvocationAuthority } from './authority.js';
5
- export interface AnonymousInvocation {
4
+ import type { AuthenticatedInvocation, InvocationAuthority, SelfInvocation } from './authority.js';
5
+ /** A call without a credential: no caller, and the installed Domain's own capabilities. */
6
+ export interface AnonymousInvocation extends SelfInvocation {
6
7
  readonly caller: AnonymousCaller;
7
- readonly kernel: null;
8
- readonly graph: null;
9
- close(): void;
10
8
  }
11
9
  export type InvocationAuthentication = AnonymousInvocation | AuthenticatedInvocation;
10
+ /**
11
+ * The sessions one invocation holds toward its source Kernel: the Domain's own always, its
12
+ * caller's only when the call carried a credential.
13
+ */
14
+ export type InvocationSessions = InvocationAuthentication['graph'];
12
15
  export declare function authentication(callable: ResolvedCallable, request: HandlerRequest, domain: Domain, authority: InvocationAuthority): Promise<InvocationAuthentication>;
@@ -3,16 +3,12 @@ import { bindSession } from '../../../platform/client/session/index.js';
3
3
  import { NodeId } from '../../../platform/graph/node/index.js';
4
4
  import { protocolError } from '../result/index.js';
5
5
  import { requireVia } from './authority.js';
6
- const ANONYMOUS = Object.freeze({
7
- caller: Object.freeze({ kind: 'anonymous' }),
8
- kernel: null,
9
- graph: null,
10
- close() { },
11
- });
6
+ const ANONYMOUS = Object.freeze({ kind: 'anonymous' });
12
7
  export async function authentication(callable, request, domain, authority) {
13
8
  const credential = request.credential;
14
- if (callable.auth === 'anonymous' && credential === undefined)
15
- return ANONYMOUS;
9
+ if (callable.auth === 'anonymous' && credential === undefined) {
10
+ return anonymous(await authority.self({ via: requireVia(request), domain, context: request.context }));
11
+ }
16
12
  if (credential === undefined) {
17
13
  throw protocolError('AUTH_MISSING', 'Authentication is required.');
18
14
  }
@@ -35,7 +31,7 @@ export async function authentication(callable, request, domain, authority) {
35
31
  invalid();
36
32
  }
37
33
  const caller = admitCaller(authenticated.caller);
38
- const graphInput = admitGraph(authenticated.graph);
34
+ const graphInput = admitGraph(authenticated.graph, ['caller', 'self']);
39
35
  const graph = Object.freeze({
40
36
  caller: bindSession(graphInput.caller),
41
37
  self: bindSession(graphInput.self),
@@ -43,24 +39,46 @@ export async function authentication(callable, request, domain, authority) {
43
39
  return Object.freeze({ caller, graph, close });
44
40
  }
45
41
  catch (cause) {
46
- if (close === undefined)
47
- invalid(cause);
48
- try {
49
- close();
50
- }
51
- catch (cleanup) {
52
- throw new AggregateError([cause, cleanup], 'Invocation authority returned invalid authenticated capabilities and cleanup failed.');
42
+ rejectClosing(close, cause);
43
+ }
44
+ }
45
+ /** Admit the Domain's own capabilities for a call that carries no credential. */
46
+ function anonymous(own) {
47
+ let close;
48
+ try {
49
+ close = captureClose(own);
50
+ if (own === null ||
51
+ typeof own !== 'object' ||
52
+ Array.isArray(own) ||
53
+ Reflect.ownKeys(own).some((key) => key !== 'graph' && key !== 'close') ||
54
+ close === undefined) {
55
+ invalid();
53
56
  }
57
+ const graph = Object.freeze({ self: bindSession(admitGraph(own.graph, ['self']).self) });
58
+ return Object.freeze({ caller: ANONYMOUS, graph, close });
59
+ }
60
+ catch (cause) {
61
+ rejectClosing(close, cause);
62
+ }
63
+ }
64
+ /** Release whatever the authority opened before refusing its invalid capabilities. */
65
+ function rejectClosing(close, cause) {
66
+ if (close === undefined)
54
67
  invalid(cause);
68
+ try {
69
+ close();
70
+ }
71
+ catch (cleanup) {
72
+ throw new AggregateError([cause, cleanup], 'Invocation authority returned invalid capabilities and cleanup failed.');
55
73
  }
74
+ invalid(cause);
56
75
  }
57
- function admitGraph(input) {
76
+ function admitGraph(input, modes) {
58
77
  if (input === null ||
59
78
  typeof input !== 'object' ||
60
79
  Array.isArray(input) ||
61
- !Object.hasOwn(input, 'caller') ||
62
- !Object.hasOwn(input, 'self') ||
63
- Reflect.ownKeys(input).some((key) => key !== 'caller' && key !== 'self')) {
80
+ modes.some((mode) => !Object.hasOwn(input, mode)) ||
81
+ Reflect.ownKeys(input).some((key) => !modes.includes(key))) {
64
82
  invalid();
65
83
  }
66
84
  return input;
@@ -134,6 +152,6 @@ function optionalTime(input) {
134
152
  return input === undefined || (typeof input === 'number' && Number.isFinite(input));
135
153
  }
136
154
  function invalid(cause) {
137
- const message = 'Invocation authority returned invalid authenticated capabilities.';
155
+ const message = 'Invocation authority returned invalid capabilities.';
138
156
  throw cause === undefined ? new TypeError(message) : new TypeError(message, { cause });
139
157
  }
@@ -16,12 +16,22 @@ export interface InvocationAuthenticationInput {
16
16
  readonly domain: Domain;
17
17
  readonly context: InvocationContext;
18
18
  }
19
+ export interface InvocationSelfInput {
20
+ readonly via: invocation.Via;
21
+ readonly domain: Domain;
22
+ readonly context: InvocationContext;
23
+ }
19
24
  /** Authenticated capabilities before the SDK admits and derives application caller context. */
20
25
  export interface AuthenticatedInvocation {
21
26
  readonly caller: AuthenticatedCaller;
22
27
  readonly graph: Readonly<Record<'caller' | 'self', BoundClientSession>>;
23
28
  close(): void;
24
29
  }
30
+ /** The Domain's own capabilities, before the SDK admits them for a call without a credential. */
31
+ export interface SelfInvocation {
32
+ readonly graph: Readonly<Record<'self', BoundClientSession>>;
33
+ close(): void;
34
+ }
25
35
  export interface InvocationAuthority {
26
36
  /**
27
37
  * The source Kernel's `Function.admit` of a remote call: resolves to the admitted target, or
@@ -30,8 +40,14 @@ export interface InvocationAuthority {
30
40
  */
31
41
  admit(input: InvocationAdmissionInput): Promise<unknown>;
32
42
  authenticate(input: InvocationAuthenticationInput): Promise<AuthenticatedInvocation>;
43
+ /**
44
+ * The Domain's own authority toward the source Kernel, for an admitted call to an `anonymous`
45
+ * callable that carries no credential: its handler acts as the installed Domain, never as a
46
+ * caller.
47
+ */
48
+ self(input: InvocationSelfInput): Promise<SelfInvocation>;
33
49
  }
34
- /** Capture the execution Identity capability and receiver-bind both privileged operations. */
50
+ /** Capture the execution Identity capability and receiver-bind its privileged operations. */
35
51
  export declare function authority(input: InvocationAuthority): InvocationAuthority;
36
52
  /**
37
53
  * The routed witness of a remote call, already admitted by the Kernel Server against the served
@@ -1,15 +1,18 @@
1
1
  import { protocolError } from '../result/index.js';
2
- /** Capture the execution Identity capability and receiver-bind both privileged operations. */
2
+ /** Capture the execution Identity capability and receiver-bind its privileged operations. */
3
3
  export function authority(input) {
4
4
  if (input === null || typeof input !== 'object' || Array.isArray(input))
5
5
  invalid();
6
- if (typeof input.admit !== 'function' || typeof input.authenticate !== 'function') {
6
+ if (typeof input.admit !== 'function' ||
7
+ typeof input.authenticate !== 'function' ||
8
+ typeof input.self !== 'function') {
7
9
  invalid();
8
10
  }
9
11
  const receiver = Object.freeze({ ...input });
10
12
  return Object.freeze({
11
13
  admit: receiver.admit.bind(receiver),
12
14
  authenticate: receiver.authenticate.bind(receiver),
15
+ self: receiver.self.bind(receiver),
13
16
  });
14
17
  }
15
18
  /**
@@ -1,5 +1,5 @@
1
1
  export { authority, requireVia } from './authority.js';
2
- export type { AuthenticatedInvocation, InvocationAdmissionInput, InvocationAuthenticationInput, InvocationAuthority, } from './authority.js';
3
- export { authentication, type InvocationAuthentication } from './authentication.js';
2
+ export type { AuthenticatedInvocation, InvocationAdmissionInput, InvocationAuthenticationInput, InvocationAuthority, InvocationSelfInput, SelfInvocation, } from './authority.js';
3
+ export { authentication, type AnonymousInvocation, type InvocationAuthentication, type InvocationSessions, } from './authentication.js';
4
4
  export { input } from './input.js';
5
5
  export { target, type InvocationTarget } from './target.js';
@@ -1,4 +1,4 @@
1
1
  export { authority, requireVia } from './authority.js';
2
- export { authentication } from './authentication.js';
2
+ export { authentication, } from './authentication.js';
3
3
  export { input } from './input.js';
4
4
  export { target } from './target.js';
@@ -1,5 +1,7 @@
1
1
  import type { HandlerDependencies } from '../../../application/dependency/index.js';
2
2
  import type { BoundClientSession } from '../../../platform/client/session/index.js';
3
3
  import type { Domain } from '../../../platform/schema/index.js';
4
+ import type { InvocationSessions } from '../admission/index.js';
4
5
  /** Bind each explicit authority to its admitted session; metadata remains inert. */
5
6
  export declare function bindDependencies<D extends Domain>(domain: D, sessions: Readonly<Record<'caller' | 'self', BoundClientSession>>): HandlerDependencies<D>;
7
+ export declare function bindDependencies<D extends Domain>(domain: D, sessions: InvocationSessions): HandlerDependencies<D> | HandlerDependencies<D, 'self'>;
@@ -3,14 +3,13 @@ import { isBinaryInput } from '@astrale-os/kernel-dsl/v1/language';
3
3
  import { reference } from '../../../platform/client/session/index.js';
4
4
  import { NodeId } from '../../../platform/graph/node/index.js';
5
5
  import { Path } from '../../../platform/graph/path/index.js';
6
- /** Bind each explicit authority to its admitted session; metadata remains inert. */
7
6
  export function bindDependencies(domain, sessions) {
8
7
  const dependencies = Object.create(null);
9
8
  for (const [alias, dependency] of Object.entries(domain.dependencies)) {
10
9
  if (dependency.origin === 'kernel.astrale.ai')
11
10
  continue;
12
11
  dependencies[alias] = Object.freeze({
13
- caller: bindDependency(dependency, sessions.caller),
12
+ ...('caller' in sessions ? { caller: bindDependency(dependency, sessions.caller) } : {}),
14
13
  self: bindDependency(dependency, sessions.self),
15
14
  });
16
15
  }
@@ -12,11 +12,11 @@ export interface DispatchContext {
12
12
  readonly input: unknown;
13
13
  readonly body?: ActionBinaryInput;
14
14
  readonly caller: Caller;
15
- readonly dependencies: HandlerDependencies<Domain> | null;
16
- readonly kernel: DomainKernelSessions | null;
17
- readonly graph: DomainGraphExecutors | null;
18
- readonly query?: QueryExecutor;
19
- readonly mutate?: MutationExecutor;
15
+ readonly dependencies: HandlerDependencies<Domain> | HandlerDependencies<Domain, 'self'>;
16
+ readonly kernel: DomainKernelSessions | DomainKernelSessions<'self'>;
17
+ readonly graph: DomainGraphExecutors | DomainGraphExecutors<Domain, 'self'>;
18
+ readonly query: QueryExecutor;
19
+ readonly mutate: MutationExecutor;
20
20
  readonly integrations: Readonly<Record<string, unknown>>;
21
21
  readonly execution: ActionExecution;
22
22
  readonly self?: NodeId;
@@ -3,7 +3,8 @@ import { bindQuery } from '../../../application/query/execution/executor.js';
3
3
  import { bindDependencies } from '../dependency/index.js';
4
4
  import { clients } from '../integration/index.js';
5
5
  export function context(request, runtime, authentication, self) {
6
- const graph = authentication.graph === null ? null : bindGraph(authentication.graph, runtime.loaded.domain);
6
+ const sessions = authentication.graph;
7
+ const graph = bindGraph(sessions, runtime.loaded.domain);
7
8
  const execution = Object.freeze({
8
9
  signal: request.context.execution.signal,
9
10
  deadline: request.context.execution.deadline,
@@ -19,46 +20,31 @@ export function context(request, runtime, authentication, self) {
19
20
  input: request.call.input,
20
21
  ...(request.context.binary === undefined ? {} : { body: request.context.binary }),
21
22
  caller: authentication.caller,
22
- kernel: authentication.graph === null
23
- ? null
24
- : Object.freeze({
25
- ...authentication.graph.self,
26
- ...authentication.graph,
27
- }),
28
- dependencies: authentication.graph === null
29
- ? null
30
- : bindDependencies(runtime.loaded.domain, authentication.graph),
23
+ kernel: Object.freeze({ ...sessions.self, ...sessions }),
24
+ dependencies: bindDependencies(runtime.loaded.domain, sessions),
31
25
  graph,
32
- ...(graph === null
33
- ? {}
34
- : {
35
- query: graph.self.query,
36
- mutate: graph.self.mutate,
37
- }),
38
- integrations: clients(runtime, request.context, authentication.graph === null
39
- ? null
40
- : {
41
- caller: authentication.graph.caller,
42
- self: authentication.graph.self,
43
- ...(authentication.caller.authentication.expiresAt === undefined
44
- ? {}
45
- : {
46
- expiresAt: authentication.caller.authentication.expiresAt,
47
- }),
48
- }),
26
+ query: graph.self.query,
27
+ mutate: graph.self.mutate,
28
+ integrations: clients(runtime, request.context, {
29
+ ...sessions,
30
+ ...(authentication.caller.kind !== 'authenticated' ||
31
+ authentication.caller.authentication.expiresAt === undefined
32
+ ? {}
33
+ : { expiresAt: authentication.caller.authentication.expiresAt }),
34
+ }),
49
35
  execution,
50
36
  ...(self === undefined ? {} : { self }),
51
37
  });
52
38
  }
53
- function bindGraph(clients, domain) {
39
+ function bindGraph(sessions, domain) {
54
40
  const bind = (client) => Object.freeze({
55
41
  query: bindQuery(client, domain),
56
42
  mutate: bindMutation(client, domain),
57
43
  });
58
- const self = bind(clients.self);
44
+ const self = bind(sessions.self);
59
45
  return Object.freeze({
60
46
  ...self,
61
- caller: bind(clients.caller),
47
+ ...('caller' in sessions ? { caller: bind(sessions.caller) } : {}),
62
48
  self,
63
49
  });
64
50
  }
@@ -1,12 +1,8 @@
1
1
  import { declaredErrorFactory, declaredErrorOf } from '../../../application/error/index.js';
2
2
  import { protocolError } from '../result/index.js';
3
3
  export async function workflow(selected, context, runner) {
4
- if (context.caller.kind !== 'authenticated' || context.kernel === null) {
5
- throw protocolError('AUTH_MISSING', 'Workflow authentication is required.');
6
- }
7
4
  const complete = Object.freeze({
8
5
  ...context,
9
- caller: context.caller,
10
6
  error: declaredErrorFactory(selected.definition),
11
7
  });
12
8
  try {
@@ -2,7 +2,8 @@ import type { InvocationContext } from '@astrale-os/kernel-server';
2
2
  import type { IntegrationInvocationOwner } from '../../../application/integration/index.js';
3
3
  import type { BoundClientSession } from '../../../platform/client/session/index.js';
4
4
  export interface IntegrationInvocationSessions {
5
- readonly caller: BoundClientSession;
5
+ /** Absent when the call carried no credential: there is no caller to invoke for. */
6
+ readonly caller?: BoundClientSession;
6
7
  readonly self: BoundClientSession;
7
8
  /** Verified incoming carrier expiry, in Unix seconds. */
8
9
  readonly expiresAt?: number;
@@ -3,7 +3,7 @@ import { bindIntegrationAuthentication } from './authentication.js';
3
3
  /** One invocation-owned logical Integration call sequence. */
4
4
  export function calls(context, sessions = null) {
5
5
  const prefix = context.idempotencyKey ?? JSON.stringify([context.invocation.source, context.invocation.id]);
6
- const invoke = sessions?.caller.invoke.bind(sessions.caller);
6
+ const invoke = sessions?.caller?.invoke.bind(sessions.caller);
7
7
  const auth = sessions === null
8
8
  ? undefined
9
9
  : bindIntegrationAuthentication(context, sessions.self, sessions.expiresAt);
@@ -2,4 +2,4 @@ import type { InvocationContext } from '@astrale-os/kernel-server';
2
2
  import type { IntegrationClients, Integrations } from '../../../application/integration/index.js';
3
3
  import type { InitializedRuntime } from '../../runtime/index.js';
4
4
  import type { IntegrationInvocationSessions } from './call.js';
5
- export declare function clients(runtime: InitializedRuntime, context: InvocationContext, sessions: IntegrationInvocationSessions | null): IntegrationClients<Integrations>;
5
+ export declare function clients(runtime: InitializedRuntime, context: InvocationContext, sessions: IntegrationInvocationSessions): IntegrationClients<Integrations>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.6.0-beta.24",
3
+ "version": "0.6.0-beta.25",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",
@@ -257,11 +257,11 @@
257
257
  "registry": "https://registry.npmjs.org/"
258
258
  },
259
259
  "dependencies": {
260
- "@astrale-os/kernel-client": "0.6.0-beta.87",
261
- "@astrale-os/kernel-core": "0.9.0-beta.66",
262
- "@astrale-os/kernel-dsl": "0.2.0-beta.48",
263
- "@astrale-os/kernel-protocol": "0.5.0-beta.72",
264
- "@astrale-os/kernel-server": "0.5.0-beta.74",
260
+ "@astrale-os/kernel-client": "0.6.0-beta.88",
261
+ "@astrale-os/kernel-core": "0.9.0-beta.67",
262
+ "@astrale-os/kernel-dsl": "0.2.0-beta.49",
263
+ "@astrale-os/kernel-protocol": "0.5.0-beta.73",
264
+ "@astrale-os/kernel-server": "0.5.0-beta.75",
265
265
  "@lucide/icons": "1.34.0",
266
266
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
267
267
  "cross-spawn": "7.0.6",
@@ -287,6 +287,7 @@
287
287
  "husky": "~9.1.7",
288
288
  "lint-staged": "~17.3.0",
289
289
  "mermaid": "^11.16.1",
290
+ "miniflare": "5.20260811.1-alpha",
290
291
  "oxfmt": "0.63.0",
291
292
  "oxlint": "1.78.0",
292
293
  "preact": "^10.29.8",
@@ -316,10 +317,10 @@
316
317
  "lint-staged": {
317
318
  "*.{js,cjs,mjs,ts,tsx}": [
318
319
  "oxlint --disable-nested-config --fix",
319
- "oxfmt --write"
320
+ "oxfmt --no-error-on-unmatched-pattern --write"
320
321
  ],
321
322
  "*.{json,yml,yaml}": [
322
- "oxfmt --write"
323
+ "oxfmt --no-error-on-unmatched-pattern --write"
323
324
  ]
324
325
  },
325
326
  "devEngines": {