@astrale-os/sdk 0.6.0-beta.35 → 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.
Files changed (104) hide show
  1. package/README.md +10 -0
  2. package/dist/application/action/context.d.ts +14 -11
  3. package/dist/application/action/index.d.ts +1 -1
  4. package/dist/application/dependency/invoker.d.ts +10 -4
  5. package/dist/application/integration/define.d.ts +8 -1
  6. package/dist/application/integration/index.d.ts +2 -2
  7. package/dist/application/integration/integration.d.ts +13 -1
  8. package/dist/application/integration/operation.d.ts +14 -1
  9. package/dist/application/mutation/execution/execute.js +1 -1
  10. package/dist/application/mutation/failure.d.ts +6 -0
  11. package/dist/application/mutation/failure.js +24 -18
  12. package/dist/application/query/collection/union.js +11 -4
  13. package/dist/application/query/conformance.d.ts +15 -0
  14. package/dist/application/query/conformance.js +46 -0
  15. package/dist/application/query/define.js +4 -3
  16. package/dist/application/query/execution/execute.d.ts +0 -1
  17. package/dist/application/query/execution/execute.js +7 -1
  18. package/dist/application/query/failure.d.ts +8 -0
  19. package/dist/application/query/failure.js +8 -0
  20. package/dist/application/query/index.d.ts +1 -1
  21. package/dist/application/query/index.js +1 -1
  22. package/dist/application/workflow/authoring.d.ts +1 -1
  23. package/dist/application/workflow/context.d.ts +1 -1
  24. package/dist/application/workflow/durable.d.ts +55 -20
  25. package/dist/application/workflow/index.d.ts +1 -1
  26. package/dist/execution/cache/cache.d.ts +17 -0
  27. package/dist/execution/cache/cache.js +11 -0
  28. package/dist/execution/cache/index.d.ts +2 -0
  29. package/dist/execution/cache/index.js +2 -0
  30. package/dist/execution/cache/memory.d.ts +13 -0
  31. package/dist/execution/cache/memory.js +48 -0
  32. package/dist/execution/identity/authentication.js +12 -6
  33. package/dist/execution/identity/callback.d.ts +7 -8
  34. package/dist/execution/identity/callback.js +17 -14
  35. package/dist/execution/index.d.ts +1 -0
  36. package/dist/execution/index.js +1 -0
  37. package/dist/execution/invocation/admission/age.d.ts +57 -0
  38. package/dist/execution/invocation/admission/age.js +208 -0
  39. package/dist/execution/invocation/admission/authentication.d.ts +2 -0
  40. package/dist/execution/invocation/admission/authentication.js +6 -2
  41. package/dist/execution/invocation/admission/index.d.ts +3 -2
  42. package/dist/execution/invocation/admission/index.js +3 -2
  43. package/dist/execution/invocation/admission/target.d.ts +11 -0
  44. package/dist/execution/invocation/admission/target.js +22 -3
  45. package/dist/execution/invocation/capabilities/bind.d.ts +11 -0
  46. package/dist/execution/invocation/capabilities/bind.js +25 -0
  47. package/dist/execution/invocation/capabilities/index.d.ts +1 -0
  48. package/dist/execution/invocation/capabilities/index.js +1 -0
  49. package/dist/execution/invocation/dispatch/context.js +2 -21
  50. package/dist/execution/invocation/handler.js +8 -2
  51. package/dist/execution/invocation/integration/authentication.d.ts +3 -3
  52. package/dist/execution/invocation/integration/authentication.js +7 -7
  53. package/dist/execution/invocation/integration/call.js +2 -2
  54. package/dist/execution/invocation/integration/index.d.ts +1 -0
  55. package/dist/execution/invocation/integration/index.js +1 -0
  56. package/dist/execution/invocation/invocation.d.ts +6 -0
  57. package/dist/execution/invocation/invocation.js +94 -19
  58. package/dist/execution/invocation/lifecycle/active.d.ts +8 -2
  59. package/dist/execution/invocation/lifecycle/active.js +3 -3
  60. package/dist/execution/invocation/lifecycle/index.d.ts +1 -1
  61. package/dist/execution/serve.d.ts +8 -0
  62. package/dist/execution/serve.js +7 -0
  63. package/dist/execution/service/create.js +2 -0
  64. package/dist/execution/service/service.d.ts +6 -0
  65. package/dist/execution/workflows/engine.d.ts +12 -2
  66. package/dist/execution/workflows/index.d.ts +1 -1
  67. package/dist/execution/workflows/integrations.d.ts +13 -0
  68. package/dist/execution/workflows/integrations.js +25 -0
  69. package/dist/execution/workflows/options.js +3 -1
  70. package/dist/execution/workflows/receiver.d.ts +3 -0
  71. package/dist/execution/workflows/receiver.js +7 -2
  72. package/dist/execution/workflows/run.d.ts +3 -4
  73. package/dist/execution/workflows/run.js +32 -60
  74. package/dist/execution/workflows/steps.d.ts +42 -0
  75. package/dist/execution/workflows/steps.js +67 -0
  76. package/dist/platform/schema/index.d.ts +1 -1
  77. package/dist/testing/durable/engine.d.ts +111 -0
  78. package/dist/testing/durable/engine.js +540 -0
  79. package/dist/testing/durable/index.d.ts +3 -0
  80. package/dist/testing/durable/index.js +1 -0
  81. package/dist/testing/durable/steps.d.ts +30 -0
  82. package/dist/testing/durable/steps.js +40 -0
  83. package/dist/testing/index.d.ts +6 -1
  84. package/dist/testing/index.js +5 -1
  85. package/dist/tooling/cli/dotenv.d.ts +3 -3
  86. package/dist/tooling/cli/dotenv.js +3 -3
  87. package/dist/tooling/cli/log.d.ts +0 -2
  88. package/dist/tooling/cli/log.js +0 -2
  89. package/dist/tooling/linter/adapters/typescript/imports.d.ts +0 -1
  90. package/dist/tooling/linter/adapters/typescript/imports.js +1 -5
  91. package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
  92. package/dist/tooling/linter/adapters/typescript/index.js +1 -1
  93. package/dist/tooling/linter/adapters/typescript/syntax.d.ts +3 -3
  94. package/dist/tooling/linter/adapters/typescript/syntax.js +13 -25
  95. package/dist/tooling/linter/implementations/source/callable-requirements.d.ts +0 -1
  96. package/dist/tooling/linter/implementations/source/callable-requirements.js +1 -6
  97. package/dist/tooling/linter/implementations/source/dependencies.js +12 -1
  98. package/dist/tooling/linter/implementations/source/mutations.js +1 -12
  99. package/dist/tooling/linter/implementations/source/queries.js +1 -17
  100. package/dist/tooling/linter/implementations/source/shared.d.ts +0 -4
  101. package/dist/tooling/linter/implementations/source/shared.js +1 -33
  102. package/dist/tooling/linter/implementations/source/workflows.js +5 -15
  103. package/dist/tooling/linter/policy/generated.js +2 -2
  104. package/package.json +6 -6
@@ -1,11 +1,14 @@
1
1
  import type { IssuerId } from '../../platform/auth/index.js';
2
- import type { BoundClientSession, ExecutionOutcome, ExecutionRef } from '../../platform/client/session/index.js';
2
+ import type { ExecutionOutcome, ExecutionRef } from '../../platform/client/session/index.js';
3
3
  import type { NodeId } from '../../platform/graph/node/index.js';
4
4
  import type { CallableInputOf, Domain, ResolvedMethod } from '../../platform/schema/index.js';
5
5
  import type { Value } from '../../platform/value/index.js';
6
+ import type { DomainCapabilities, DomainKernelSessions } from '../action/index.js';
7
+ import type { HandlerDependencies } from '../dependency/index.js';
6
8
  import type { DeclaredErrorFactory, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
9
+ import type { Integrations, ReplayableIntegrationClients } from '../integration/index.js';
7
10
  import type { Mutation, MutationExecutor } from '../mutation/index.js';
8
- import type { QueryExecutor } from '../query/index.js';
11
+ import type { QueryDefinition } from '../query/index.js';
9
12
  /** A callable whose Schema declares `execution: { durable: true }`. */
10
13
  export interface DurableCallable {
11
14
  readonly execution: {
@@ -31,31 +34,46 @@ export interface DurableExecution {
31
34
  readonly receiver?: NodeId;
32
35
  }
33
36
  /**
34
- * The Domain's own authority inside one Step. Kernel access exists only here, so every Kernel call
35
- * belongs to a journaled Step and a replay never repeats it.
37
+ * What one Step holds: the capabilities of an Action, acting as the Domain alone. Kernel access
38
+ * exists only here, so every Kernel call belongs to a journaled Step and a replay never repeats it.
39
+ * Every key below comes from one sequence derived from the Execution, the Step and the position of
40
+ * the call, which restarts with each attempt: a retried Step sends the same keys again.
36
41
  */
37
- export interface DurableStepAuthority<DomainValue extends Domain = Domain> {
42
+ export interface DurableStepAuthority<DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>> extends DomainCapabilities<DomainValue, 'self'> {
38
43
  /**
39
44
  * Kernel Session acting as the Domain. Each `invoke` of a durable callable without an explicit
40
- * `idempotencyKey` carries one derived from the Execution, the Step and its position, so a
41
- * retried Step reaches the same durable child Execution. An `invoke` of any other callable
42
- * carries no key, which the Kernel would refuse, and runs again when the Step is retried.
45
+ * `idempotencyKey` carries the next derived key, so a retried Step reaches the same durable child
46
+ * Execution. An `invoke` of any other callable carries no key, which the Kernel would refuse, and
47
+ * runs again when the Step is retried.
43
48
  */
44
- readonly kernel: BoundClientSession;
45
- readonly query: QueryExecutor<DomainValue>;
49
+ readonly kernel: DomainKernelSessions<'self'>;
50
+ /** Dependency invokers acting as the Domain, keyed like `kernel.invoke`. */
51
+ readonly dependencies: HandlerDependencies<DomainValue, 'self'>;
46
52
  /**
47
- * Each Mutation carries a key derived from the Execution, the Step and its position. The Kernel
48
- * commits a receipt with the write and answers a retried Step with its first result.
53
+ * Each Mutation carries the next derived key. The Kernel commits a receipt with the write and
54
+ * answers a retried Step with its first result.
49
55
  */
50
56
  readonly mutate: MutationExecutor<DomainValue>;
57
+ /**
58
+ * Operations a retried Step may attempt again. A keyed operation of source `execution` receives
59
+ * the next derived key as its idempotency key. An operation whose decision may be `unsafe` is
60
+ * absent, and refused before its Provider runs if its decision is `unsafe` anyway.
61
+ */
62
+ readonly integrations: ReplayableIntegrationClients<Definitions>;
51
63
  }
52
- export interface DurableStep<DomainValue extends Domain = Domain> {
64
+ export interface DurableStep<DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>> {
53
65
  /**
54
66
  * Run `execute` once and journal its portable output; a replay returns the journaled output. A
55
- * Step may run again when its outcome was not journaled: its Mutations and durable calls are
56
- * answered from their keys, and any other effect must be idempotent.
67
+ * Step may run again when its outcome was not journaled: its Mutations, durable calls and keyed
68
+ * Integration operations are answered from their keys, and any other effect must be idempotent.
57
69
  */
58
- run<Output>(id: string, execute: (authority: DurableStepAuthority<DomainValue>) => Output | Promise<Output>): Promise<Output>;
70
+ run<Output>(id: string, execute: (authority: DurableStepAuthority<DomainValue, Definitions>) => Output | Promise<Output>): Promise<Output>;
71
+ /**
72
+ * One journaled Query. A replay returns the result of its first run, so a later Step that depends
73
+ * on it resends the same request whenever it is retried.
74
+ */
75
+ query<Output>(id: string, query: QueryDefinition<DomainValue, void, Output>): Promise<Output>;
76
+ query<Input, Output>(id: string, query: QueryDefinition<DomainValue, Input, Output>, input: Input): Promise<Output>;
59
77
  /**
60
78
  * One journaled Mutation. A Step replayed after its commit receives the result of that commit
61
79
  * from the Kernel's receipt instead of writing again.
@@ -67,18 +85,35 @@ type DurableSelfOf<Callable> = Callable extends ResolvedMethod<unknown, unknown,
67
85
  } : {
68
86
  readonly self?: never;
69
87
  };
88
+ /** What a bounded wait for a signal ended with: the signal's value, or its timeout. */
89
+ export type SignalOutcome<Signal extends Value = Value> = {
90
+ readonly kind: 'signal';
91
+ readonly value: Signal;
92
+ } | {
93
+ readonly kind: 'timeout';
94
+ };
70
95
  /** Context of a durable Workflow: engine-journaled, acting as its Domain. */
71
- export type DurableWorkflowContext<Callable, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = {
96
+ export type DurableWorkflowContext<Callable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = {
72
97
  readonly domain: DomainValue;
73
98
  readonly input: CallableInputOf<Callable>;
74
99
  readonly execution: DurableExecution;
75
100
  /** Construct one implementation-declared safe business error for `throw error(...)`. */
76
101
  readonly error: DeclaredErrorFactory<Errors>;
77
- readonly step: DurableStep<DomainValue>;
102
+ readonly step: DurableStep<DomainValue, Definitions>;
78
103
  /** Durable timer kept by the engine, even while the Domain is idle or down. */
79
104
  sleep(durationMs: number): Promise<void>;
80
- /** Resolve once `Execution.signal({ name, value })` delivers `name`, before or during the wait. */
81
- promise<Signal extends Value = Value>(name: string): Promise<Signal>;
105
+ /**
106
+ * Wait for the value `Execution.signal({ name, value })` delivers once under `name`, whether it
107
+ * was sent before or during the wait.
108
+ */
109
+ awaitSignal<Signal extends Value = Value>(name: string): Promise<Signal>;
110
+ /**
111
+ * Wait at most `timeoutMs` for the signal `name`. The engine journals which came first, so a
112
+ * replay answers the same. A signal sent after the timeout is kept for a later wait.
113
+ */
114
+ awaitSignal<Signal extends Value = Value>(name: string, options: {
115
+ readonly timeoutMs: number;
116
+ }): Promise<SignalOutcome<Signal>>;
82
117
  /** Whether `error` is the engine's cooperative cancellation, which the Workflow may compensate. */
83
118
  cancelled(error: unknown): boolean;
84
119
  /** Wait durably for another Execution to end, by journaled polling of its `result`. */
@@ -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';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Disposable entries one deployment keeps across its requests, and across its isolates where the
3
+ * platform shares them. An entry may vanish at any time and never outlives its expiry. A reader
4
+ * treats a failed or late read as a miss, so nothing that must persist or be coordinated belongs
5
+ * here: every consumer stays correct when the cache keeps nothing.
6
+ */
7
+ export interface DeploymentCache {
8
+ /** The value kept under `key`, or undefined when it expired, vanished or was never kept. */
9
+ get(key: string): Promise<string | undefined>;
10
+ /**
11
+ * Keep `value` under `key` until `expiresAt` (Unix milliseconds) at the latest. The cache may
12
+ * drop it sooner, or not keep it at all.
13
+ */
14
+ put(key: string, value: string, expiresAt: number): Promise<void>;
15
+ }
16
+ /** Capture a caller-supplied cache, receiver-bound, after checking its shape. */
17
+ export declare function deploymentCache(input: DeploymentCache): DeploymentCache;
@@ -0,0 +1,11 @@
1
+ /** Capture a caller-supplied cache, receiver-bound, after checking its shape. */
2
+ export function deploymentCache(input) {
3
+ if (input === null ||
4
+ typeof input !== 'object' ||
5
+ Array.isArray(input) ||
6
+ typeof input.get !== 'function' ||
7
+ typeof input.put !== 'function') {
8
+ throw new TypeError('Deployment cache is invalid.');
9
+ }
10
+ return Object.freeze({ get: input.get.bind(input), put: input.put.bind(input) });
11
+ }
@@ -0,0 +1,2 @@
1
+ export { deploymentCache, type DeploymentCache } from './cache.js';
2
+ export { DEFAULT_MEMORY_CACHE_ENTRIES, memoryCache, type MemoryCacheOptions } from './memory.js';
@@ -0,0 +1,2 @@
1
+ export { deploymentCache } from './cache.js';
2
+ export { DEFAULT_MEMORY_CACHE_ENTRIES, memoryCache } from './memory.js';
@@ -0,0 +1,13 @@
1
+ import type { DeploymentCache } from './cache.js';
2
+ /** Entries one memory cache keeps by default before it evicts the least recently used. */
3
+ export declare const DEFAULT_MEMORY_CACHE_ENTRIES = 1024;
4
+ export interface MemoryCacheOptions {
5
+ /** Most entries kept at once; the least recently used is evicted first. */
6
+ readonly maximumEntries?: number;
7
+ }
8
+ /**
9
+ * A deployment cache held in this process or isolate: bounded, least recently used first out, and
10
+ * an entry is gone at its expiry. It keeps values only, never a pending operation, so no request
11
+ * waits on another's I/O.
12
+ */
13
+ export declare function memoryCache(options?: MemoryCacheOptions, now?: () => number): DeploymentCache;
@@ -0,0 +1,48 @@
1
+ /** Entries one memory cache keeps by default before it evicts the least recently used. */
2
+ export const DEFAULT_MEMORY_CACHE_ENTRIES = 1_024;
3
+ /**
4
+ * A deployment cache held in this process or isolate: bounded, least recently used first out, and
5
+ * an entry is gone at its expiry. It keeps values only, never a pending operation, so no request
6
+ * waits on another's I/O.
7
+ */
8
+ export function memoryCache(options = {}, now = Date.now) {
9
+ if (options === null || typeof options !== 'object' || Array.isArray(options))
10
+ invalid();
11
+ const maximum = options.maximumEntries ?? DEFAULT_MEMORY_CACHE_ENTRIES;
12
+ if (!Number.isSafeInteger(maximum) || maximum < 1) {
13
+ throw new TypeError('Memory cache maximumEntries must be a positive integer.');
14
+ }
15
+ const entries = new Map();
16
+ return Object.freeze({
17
+ async get(key) {
18
+ if (typeof key !== 'string')
19
+ invalid();
20
+ const entry = entries.get(key);
21
+ if (entry === undefined)
22
+ return undefined;
23
+ entries.delete(key);
24
+ if (entry.expiresAt <= now())
25
+ return undefined;
26
+ entries.set(key, entry);
27
+ return entry.value;
28
+ },
29
+ async put(key, value, expiresAt) {
30
+ if (typeof key !== 'string' || typeof value !== 'string' || !Number.isFinite(expiresAt)) {
31
+ invalid();
32
+ }
33
+ entries.delete(key);
34
+ if (expiresAt <= now())
35
+ return;
36
+ while (entries.size >= maximum) {
37
+ const oldest = entries.keys().next().value;
38
+ if (oldest === undefined)
39
+ break;
40
+ entries.delete(oldest);
41
+ }
42
+ entries.set(key, Object.freeze({ value, expiresAt }));
43
+ },
44
+ });
45
+ }
46
+ function invalid() {
47
+ throw new TypeError('Memory cache input is invalid.');
48
+ }
@@ -56,10 +56,10 @@ export async function authenticate(credentialInput, expectedKernel, execution, c
56
56
  throw protocolError('AUTH_INVALID', 'Credential is invalid.');
57
57
  }
58
58
  const expressions = callbackIdentities(established.delegation.credential);
59
- const session = (expression) => callbackSession(expectedKernel, expression, established.verified, execution, configuration);
59
+ const session = (expression, validUntil) => callbackSession(expectedKernel, expression, validUntil, execution, configuration);
60
60
  const sessions = Object.freeze({
61
- caller: session(expressions.caller),
62
- self: session(expressions.self),
61
+ caller: session(expressions.caller, established.validUntil),
62
+ self: session(expressions.self, established.verified.exp),
63
63
  });
64
64
  const graph = Object.freeze({
65
65
  caller: bindSession(sessions.caller, execution),
@@ -111,13 +111,18 @@ export function executorSession(kernel, executionId, execution, configuration) {
111
111
  admitEndpoint(invocationUrl(admitted), configuration.policy);
112
112
  return callbackSession(admitted, selfIdentity, undefined, execution, configuration, executionId);
113
113
  }
114
- function callbackSession(kernel, expression, carrier, execution, configuration, executionId) {
115
- const resolveCallback = createCallbackCredentialResolver(configuration.signing, configuration.issuer, configuration.subject, kernel, expression, carrier, execution, executionId);
114
+ /**
115
+ * A Session whose callbacks carry `expression`, none outliving the execution or `validUntil`, the
116
+ * earliest expiry in Unix seconds of the credentials they derive from.
117
+ */
118
+ function callbackSession(kernel, expression, validUntil, execution, configuration, executionId) {
119
+ const resolveCallback = createCallbackCredentialResolver(configuration.signing, configuration.issuer, configuration.subject, kernel, expression, validUntil, execution, executionId);
120
+ const expiresAt = Math.min(execution.deadline, (validUntil ?? Number.POSITIVE_INFINITY) * 1_000);
116
121
  return new ClientSession({
117
122
  kernel,
118
123
  fetch: boundedFetch(configuration.fetch, execution.signal),
119
124
  auth: {
120
- ttlSeconds: Math.min(callbackTtl(execution.deadline), maximumCallbackDelegationTtlSeconds),
125
+ ttlSeconds: Math.min(callbackTtl(expiresAt), maximumCallbackDelegationTtlSeconds),
121
126
  async resolve(_call, signal) {
122
127
  const callback = await resolveCallback(signal);
123
128
  signal.throwIfAborted();
@@ -159,6 +164,7 @@ async function establishCaller(input, audience, verification, execution, policy)
159
164
  }
160
165
  return Object.freeze({
161
166
  verified: context.verified,
167
+ validUntil: Math.min(context.verified.exp ?? Number.POSITIVE_INFINITY, proof.verified.exp ?? Number.POSITIVE_INFINITY),
162
168
  delegation: carried,
163
169
  caller: Object.freeze({
164
170
  kind: 'authenticated',
@@ -1,4 +1,4 @@
1
- import type { IssuerId, UnresolvedIdentityExpr, VerifiedCredential } from '../../platform/auth/index.js';
1
+ import type { IssuerId, UnresolvedIdentityExpr } from '../../platform/auth/index.js';
2
2
  import type { NodeId } from '../../platform/graph/node/index.js';
3
3
  import type { SigningMaterial } from './signing.js';
4
4
  export declare const maximumCallbackTtlSeconds = 120;
@@ -29,17 +29,16 @@ export declare function signDomainCallback(signing: SigningMaterial, issuer: Iss
29
29
  readonly deadline: number;
30
30
  readonly signal: AbortSignal;
31
31
  }): Promise<string>;
32
- export declare function signCallback(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, carrier: VerifiedCredential, execution: {
33
- readonly deadline: number;
34
- readonly signal: AbortSignal;
35
- }): Promise<string>;
36
32
  /**
37
- * Reuse one exact callback authority partition, refreshing only near its bounded expiry. Without a
38
- * carrier the callback is the Domain's own authority, bounded by the execution alone. An
33
+ * Reuse one exact callback authority partition, refreshing only near its bounded expiry. No callback
34
+ * outlives `validUntil`, the earliest expiry in Unix seconds of the credentials it derives from: the
35
+ * Kernel folds each of them into the callback's validity, so a longer callback could not carry its
36
+ * delegation. Without it the callback is the Domain's own authority, bounded by the execution alone.
37
+ * Once that authority can no longer carry a callback, the call ends as DEADLINE_EXCEEDED. An
39
38
  * `executionId` is the durable Execution a step's executor credential names in `EXECUTION_CLAIM`;
40
39
  * no caller-bound callback names one.
41
40
  */
42
- export declare function createCallbackCredentialResolver(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, carrier: VerifiedCredential | undefined, execution: {
41
+ export declare function createCallbackCredentialResolver(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, validUntil: number | undefined, execution: {
43
42
  readonly deadline: number;
44
43
  readonly signal: AbortSignal;
45
44
  }, executionId?: NodeId): (signal: AbortSignal) => Promise<SignedCallback>;
@@ -17,18 +17,17 @@ export function callbackIdentities(nested) {
17
17
  export async function signDomainCallback(signing, issuer, subject, audience, execution) {
18
18
  return (await sign(signing, issuer, subject, audience, selfIdentity, execution)).credential;
19
19
  }
20
- export async function signCallback(signing, issuer, subject, audience, expression, carrier, execution) {
21
- const maximum = carrier.exp ?? Number.POSITIVE_INFINITY;
22
- return (await sign(signing, issuer, subject, audience, expression, execution, maximum)).credential;
23
- }
24
20
  /**
25
- * Reuse one exact callback authority partition, refreshing only near its bounded expiry. Without a
26
- * carrier the callback is the Domain's own authority, bounded by the execution alone. An
21
+ * Reuse one exact callback authority partition, refreshing only near its bounded expiry. No callback
22
+ * outlives `validUntil`, the earliest expiry in Unix seconds of the credentials it derives from: the
23
+ * Kernel folds each of them into the callback's validity, so a longer callback could not carry its
24
+ * delegation. Without it the callback is the Domain's own authority, bounded by the execution alone.
25
+ * Once that authority can no longer carry a callback, the call ends as DEADLINE_EXCEEDED. An
27
26
  * `executionId` is the durable Execution a step's executor credential names in `EXECUTION_CLAIM`;
28
27
  * no caller-bound callback names one.
29
28
  */
30
- export function createCallbackCredentialResolver(signing, issuer, subject, audience, expression, carrier, execution, executionId) {
31
- const maximum = carrier?.exp ?? Number.POSITIVE_INFINITY;
29
+ export function createCallbackCredentialResolver(signing, issuer, subject, audience, expression, validUntil, execution, executionId) {
30
+ const maximum = validUntil ?? Number.POSITIVE_INFINITY;
32
31
  let cached;
33
32
  let pending;
34
33
  return async (signal) => {
@@ -42,9 +41,8 @@ export function createCallbackCredentialResolver(signing, issuer, subject, audie
42
41
  if (pending === undefined) {
43
42
  const current = sign(signing, issuer, subject, audience, expression, execution, maximum, executionId).then((signed) => {
44
43
  const delegateTtlSeconds = Math.min(maximumCallbackDelegationTtlSeconds, signed.expiresAt - signed.issuedAt - CALLBACK_DELEGATION_SKEW_SECONDS);
45
- if (delegateTtlSeconds < 1) {
46
- throw protocolError('AUTH_INVALID', 'Credential is invalid.');
47
- }
44
+ if (delegateTtlSeconds < 1)
45
+ throw exhausted(execution, maximum);
48
46
  return (cached = Object.freeze({
49
47
  credential: signed.credential,
50
48
  expiresAt: signed.expiresAt,
@@ -69,9 +67,8 @@ async function sign(signing, issuer, subject, audience, expression, execution, m
69
67
  requireLive(execution);
70
68
  const now = Math.floor(Date.now() / 1_000);
71
69
  const expiresAt = Math.min(maximum, Math.floor(execution.deadline / 1_000), now + maximumCallbackTtlSeconds);
72
- if (!Number.isFinite(expiresAt) || expiresAt <= now) {
73
- throw protocolError('AUTH_INVALID', 'Credential is invalid.');
74
- }
70
+ if (!Number.isFinite(expiresAt) || expiresAt <= now)
71
+ throw exhausted(execution, maximum);
75
72
  const credential = await withSignal(new SignJWT({
76
73
  grant: { v: 1, expr: expression },
77
74
  ...(executionId === undefined ? {} : { [EXECUTION_CLAIM]: executionId }),
@@ -89,6 +86,12 @@ async function sign(signing, issuer, subject, audience, expression, execution, m
89
86
  requireLive(execution);
90
87
  return Object.freeze({ credential, issuedAt: now, expiresAt });
91
88
  }
89
+ /** The authority a callback derives from ends before the callback could serve a call. */
90
+ function exhausted(execution, maximum) {
91
+ return protocolError('DEADLINE_EXCEEDED', maximum * 1_000 < execution.deadline
92
+ ? 'Caller authority has expired.'
93
+ : 'Invocation deadline was exceeded.');
94
+ }
92
95
  function withSignal(operation, signal) {
93
96
  if (signal.aborted)
94
97
  return Promise.reject(signal.reason);
@@ -1,5 +1,6 @@
1
1
  export { serve } from './serve.js';
2
2
  export type { ExecutionContext, ServedDomain, ServeInput } from './serve.js';
3
+ export { DEFAULT_MEMORY_CACHE_ENTRIES, memoryCache, type DeploymentCache, type MemoryCacheOptions, } from './cache/index.js';
3
4
  export { createIdentity, maximumExchangeTtl, type ExchangeOptions, type Identity, type IdentityInput, } from './identity/index.js';
4
5
  export type { DurableEngine, DurableServing, DurableServingProvider } from './workflows/index.js';
5
6
  export { isCanonicalDurableRequest } from './workflows/route.js';
@@ -1,3 +1,4 @@
1
1
  export { serve } from './serve.js';
2
+ export { DEFAULT_MEMORY_CACHE_ENTRIES, memoryCache, } from './cache/index.js';
2
3
  export { createIdentity, maximumExchangeTtl, } from './identity/index.js';
3
4
  export { isCanonicalDurableRequest } from './workflows/route.js';
@@ -0,0 +1,57 @@
1
+ import type * as invocation from '@astrale-os/kernel-protocol/invocation';
2
+ import type { RuntimeRegistry } from '../../../application/runtime/index.js';
3
+ import type { DeploymentCache } from '../../cache/index.js';
4
+ import type { Admission, InvocationTarget } from './target.js';
5
+ /**
6
+ * How long reading a kept admission may take before the call asks its Kernel instead. Reads of a
7
+ * shared platform cache answer in single-digit milliseconds; `Function.admit` costs tens.
8
+ */
9
+ export declare const ADMISSION_RECALL_TIMEOUT_MS = 30;
10
+ /** The callables a call can be admitted to, when one of them declares an admission age. */
11
+ export interface AdmissionScope {
12
+ /** Keys of the implementations the call target names: an admission to any other is not kept. */
13
+ readonly callables: ReadonlySet<string>;
14
+ /** The input fields their Policies read, the only part of the input an admission depends on. */
15
+ readonly fields: readonly string[];
16
+ }
17
+ /**
18
+ * Whose admission one call asks for: the source Kernel, the release this deployment serves, and
19
+ * the caller its credential proved, verified here before any kept admission is read.
20
+ */
21
+ export interface AdmissionPartition {
22
+ readonly kernel: string;
23
+ readonly release: string;
24
+ /** Absent for a call that carries no credential. */
25
+ readonly caller?: {
26
+ readonly principal: string;
27
+ readonly identity: string;
28
+ };
29
+ }
30
+ /**
31
+ * The scope of a call whose target names, before the graph is read, implementations of which one
32
+ * declares an admission age. Undefined otherwise: a target that names no local implementation (a
33
+ * callable Node id, a path ending in a traversal), only callables without an age, or a durable
34
+ * Workflow, which runs only as a Kernel Execution. The Path language dispatches exactly, so a named
35
+ * target selects one declaration; the fields are still the union over every implementation named.
36
+ */
37
+ export declare function admissionScope(registry: RuntimeRegistry, call: invocation.Call): AdmissionScope | undefined;
38
+ /** Whether any implementation of this registry declares an admission age. */
39
+ export declare function declaresAdmissionAge(registry: RuntimeRegistry): boolean;
40
+ /**
41
+ * The cache key of one call's admission: a SHA-256 over the source Kernel, the served release, the
42
+ * verified principal and caller, the canonical call target (receiver included) and the input
43
+ * projected on the fields its Policy reads. A missing field differs from `null`, object keys are
44
+ * sorted at every depth, and no other part of the input enters the key.
45
+ */
46
+ export declare function admissionKey(scope: AdmissionScope, partition: AdmissionPartition, call: invocation.Call): Promise<string>;
47
+ /**
48
+ * The target of an admission this deployment kept under `key`, while it is younger than the age it
49
+ * carried. A missing, expired, malformed, out-of-scope, failed or late read is a miss.
50
+ */
51
+ export declare function recallAdmission(cache: DeploymentCache, key: string, scope: AdmissionScope): Promise<InvocationTarget | undefined>;
52
+ /**
53
+ * Keep a successful admission that carries an age, until `sentAt` plus that age, `sentAt` being when
54
+ * `Function.admit` was asked. Written in the background: the call never waits on the cache, and a
55
+ * cache that fails to keep it only costs a later call its admission round trip.
56
+ */
57
+ export declare function keepAdmission(cache: DeploymentCache, key: string, scope: AdmissionScope, admitted: Admission, sentAt: number, background: (task: () => PromiseLike<unknown>) => void): void;