@astrale-os/sdk 0.6.0-beta.1 → 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.
Files changed (98) hide show
  1. package/README.md +70 -0
  2. package/dist/application/action/action.d.ts +6 -0
  3. package/dist/application/action/authoring.d.ts +1 -1
  4. package/dist/application/action/context.d.ts +12 -2
  5. package/dist/application/action/define.d.ts +3 -3
  6. package/dist/application/action/index.d.ts +1 -1
  7. package/dist/application/dependency/index.d.ts +1 -1
  8. package/dist/application/dependency/invoker.d.ts +9 -4
  9. package/dist/application/mutation/authoring/properties.d.ts +5 -3
  10. package/dist/application/mutation/authoring/properties.js +35 -2
  11. package/dist/application/mutation/authoring/transition.js +4 -1
  12. package/dist/application/runtime/actions.js +3 -0
  13. package/dist/application/runtime/workflows.d.ts +2 -0
  14. package/dist/application/runtime/workflows.js +5 -0
  15. package/dist/application/workflow/authoring.d.ts +3 -2
  16. package/dist/application/workflow/context.d.ts +8 -2
  17. package/dist/application/workflow/define.d.ts +3 -3
  18. package/dist/application/workflow/define.js +19 -3
  19. package/dist/application/workflow/durable.d.ts +83 -0
  20. package/dist/application/workflow/durable.js +1 -0
  21. package/dist/application/workflow/index.d.ts +3 -2
  22. package/dist/application/workflow/runner/inline.js +3 -4
  23. package/dist/application/workflow/runner/runner.d.ts +2 -2
  24. package/dist/application/workflow/step/output.d.ts +2 -0
  25. package/dist/application/workflow/step/output.js +73 -0
  26. package/dist/application/workflow/workflow.d.ts +12 -0
  27. package/dist/deployment/environment.d.ts +6 -0
  28. package/dist/deployment/environment.js +6 -0
  29. package/dist/deployment/release/publication/seal.d.ts +1 -1
  30. package/dist/deployment/release/publication/seal.js +1 -1
  31. package/dist/deployment/release/release.d.ts +1 -1
  32. package/dist/deployment/verify/preflight/publication.js +2 -1
  33. package/dist/execution/identity/authentication.d.ts +9 -0
  34. package/dist/execution/identity/authentication.js +41 -34
  35. package/dist/execution/identity/callback.d.ts +12 -4
  36. package/dist/execution/identity/callback.js +9 -4
  37. package/dist/execution/identity/identity.d.ts +9 -0
  38. package/dist/execution/identity/identity.js +6 -2
  39. package/dist/execution/index.d.ts +3 -1
  40. package/dist/execution/index.js +2 -1
  41. package/dist/execution/invocation/admission/authority.d.ts +3 -3
  42. package/dist/execution/invocation/admission/input.d.ts +2 -2
  43. package/dist/execution/invocation/admission/input.js +16 -2
  44. package/dist/execution/invocation/dependency/bind.js +35 -13
  45. package/dist/execution/invocation/dispatch/context.d.ts +2 -1
  46. package/dist/execution/invocation/dispatch/context.js +1 -0
  47. package/dist/execution/invocation/invocation.js +7 -2
  48. package/dist/execution/serve.d.ts +6 -0
  49. package/dist/execution/serve.js +3 -0
  50. package/dist/execution/service/create.js +30 -0
  51. package/dist/execution/service/initialization.d.ts +4 -4
  52. package/dist/execution/service/initialization.js +23 -14
  53. package/dist/execution/service/service.d.ts +6 -0
  54. package/dist/execution/workflows/discovery.d.ts +14 -0
  55. package/dist/execution/workflows/discovery.js +151 -0
  56. package/dist/execution/workflows/engine.d.ts +52 -0
  57. package/dist/execution/workflows/engine.js +1 -0
  58. package/dist/execution/workflows/envelope.d.ts +12 -0
  59. package/dist/execution/workflows/envelope.js +46 -0
  60. package/dist/execution/workflows/failure.d.ts +11 -0
  61. package/dist/execution/workflows/failure.js +43 -0
  62. package/dist/execution/workflows/index.d.ts +4 -0
  63. package/dist/execution/workflows/index.js +3 -0
  64. package/dist/execution/workflows/options.d.ts +40 -0
  65. package/dist/execution/workflows/options.js +83 -0
  66. package/dist/execution/workflows/receiver.d.ts +22 -0
  67. package/dist/execution/workflows/receiver.js +99 -0
  68. package/dist/execution/workflows/route.d.ts +14 -0
  69. package/dist/execution/workflows/route.js +40 -0
  70. package/dist/execution/workflows/run.d.ts +22 -0
  71. package/dist/execution/workflows/run.js +211 -0
  72. package/dist/platform/schema/index.d.ts +2 -1
  73. package/dist/platform/schema/property-input.d.ts +15 -0
  74. package/dist/platform/schema/property-input.js +1 -0
  75. package/dist/platform/versioning/dependents.d.ts +18 -0
  76. package/dist/platform/versioning/dependents.js +15 -0
  77. package/dist/platform/versioning/index.d.ts +5 -0
  78. package/dist/platform/versioning/index.js +5 -0
  79. package/dist/platform/versioning/name.d.ts +37 -0
  80. package/dist/platform/versioning/name.js +57 -0
  81. package/dist/platform/versioning/reference.d.ts +48 -0
  82. package/dist/platform/versioning/reference.js +95 -0
  83. package/dist/platform/versioning/version.d.ts +72 -0
  84. package/dist/platform/versioning/version.js +98 -0
  85. package/dist/project/define.js +1 -1
  86. package/dist/testing/dataset/builder.d.ts +3 -3
  87. package/dist/tooling/linter/adapters/typescript/discover.d.ts +2 -2
  88. package/dist/tooling/linter/adapters/typescript/discover.js +11 -7
  89. package/dist/tooling/linter/implementations/source/global.js +1 -1
  90. package/dist/tooling/linter/lint.js +5 -4
  91. package/dist/tooling/linter/oxlint/run.d.ts +1 -1
  92. package/dist/tooling/linter/oxlint/run.js +5 -1
  93. package/dist/tooling/linter/policy/configuration.d.ts +3 -0
  94. package/dist/tooling/linter/policy/configuration.js +3 -1
  95. package/dist/tooling/linter/policy/ignore.d.ts +11 -0
  96. package/dist/tooling/linter/policy/ignore.js +101 -0
  97. package/dist/tooling/linter/policy/index.d.ts +1 -0
  98. package/package.json +15 -6
package/README.md CHANGED
@@ -78,6 +78,13 @@ Authenticated handlers use Domain self authority for `kernel`, `graph`, `query`,
78
78
  with the incoming caller Grant. Explicit authority remains on `kernel` and `graph`; the former
79
79
  handler `client` is removed. See [the API and migration guide](docs/handler-dependencies.md).
80
80
 
81
+ ## Durable Workflows
82
+
83
+ A callable declared with `execution: { durable: true }` is implemented by `defineWorkflow` and runs
84
+ on the Kernel's workflow engine: a call returns its `K.Execution` at once, and the run survives
85
+ restarts with journaled Steps, durable timers and signals. Serve it with `serve({ durable })`. See
86
+ [the durable Workflow guide](docs/durable-workflows.md).
87
+
81
88
  ## Node Class icons
82
89
 
83
90
  Every Node Class authored through the SDK must explicitly declare a self-contained SVG icon.
@@ -213,6 +220,21 @@ Disabled rules are skipped before evaluation and remain visible in lint coverage
213
220
  project rule disable does not bypass generic Oxlint, project admission, type-universe checks, or
214
221
  linter tool failures.
215
222
 
223
+ A committed tree that is not Domain source, such as client mockups or a preview harness, can be
224
+ excluded from `astrale-domain lint` with root-relative globs:
225
+
226
+ ```json
227
+ {
228
+ "ignore": ["mockups/**", "prototypes/**"]
229
+ }
230
+ ```
231
+
232
+ Ignored paths are pruned before discovery: they are never read, parsed, counted against source
233
+ limits, included in the source snapshot digest, or passed to generic Oxlint. Each glob starts with a
234
+ literal root entry and may use `*` and `?` within a segment and `**` as a whole segment; a matched
235
+ directory excludes everything below it. A glob that would cover a declared layer (including a
236
+ remapped `sourcePath`) or a governed root file is rejected, so `ignore` cannot bypass the layout.
237
+
216
238
  Structured review [issues](issues/README.md) retain observation-first evidence and later governance
217
239
  attachments without becoming current contract authority.
218
240
 
@@ -246,6 +268,54 @@ URL, size, and ETag checks. Concurrent publication and snapshot observations sha
246
268
  cancelling one observer does not cancel another. `session.snapshot()` remains the complete,
247
269
  coherent Publication, bundle, and View catalog observation.
248
270
 
271
+ ## Headless scripts and Domain callables
272
+
273
+ Function admission requires the principal itself to be able to use the Function. A credential
274
+ minted for a user or key, such as `astrale token`, carries that identity as principal: it passes
275
+ Kernel callables, but a Domain callable that the identity reaches only through a Policy fails with
276
+ `2004`. The Session `exchange` option presents each Domain callable with a credential its declaring
277
+ Domain exchanged for the caller instead. The Domain becomes the principal, and the Policy is still
278
+ evaluated against the caller. Kernel callables, including graph reads and writes, keep the source
279
+ credential.
280
+
281
+ ```ts
282
+ import { connect, createSessionCredentialProvider } from '@astrale-os/sdk/client/session'
283
+
284
+ const session = connect({
285
+ url: 'https://instance.example/api',
286
+ auth: createSessionCredentialProvider({
287
+ ttlSeconds: 60,
288
+ // Return { credential, expiresAt } with expiresAt in Unix milliseconds.
289
+ mint: (signal) => mintSourceCredential(signal),
290
+ }),
291
+ exchange: {},
292
+ })
293
+
294
+ await session.call({ target: '/:orders.example:class.Order:create', input }) // Domain callable
295
+ await session.graph.get('@order-id') // Kernel callable: the source credential
296
+ ```
297
+
298
+ - **Hold the source in a provider.** Domain credentials are cached per source credential, so a
299
+ `resolve` that returns a new token on every call also exchanges on every call. The provider also
300
+ lets the Session renew a source that is too close to expiry to feed a Domain credential.
301
+ - **Lifetimes.** `exchange.ttlSeconds` (default 300) must exceed the provider's `ttlSeconds` plus a
302
+ 5-second settlement skew. A Domain credential never outlives its source.
303
+ - **Reading as a Domain.** To call Kernel callables as a Domain, for example to read Shell-owned
304
+ Classes, feed a second Session from an explicit exchange:
305
+ `createSessionCredentialProvider({ ttlSeconds: 60, mint: (signal) => session.exchange(shellIssuer, { signal }) })`.
306
+ `session.exchange(issuer)` caches nothing.
307
+ - **Current identity.** Use `session.auth.whoami()`; `@self` is a CLI shorthand.
308
+ - **Failures.** A failed exchange raises `ExchangeError` from `@astrale-os/sdk/client`. Its
309
+ `failure` names the reason (`discovery`, `unsupported`, `unavailable`, `rejected`,
310
+ `invalid-response` or `source-exhausted`); a `rejected` error carries the Domain's public error
311
+ as its cause. Branch on `failure`, not on the message.
312
+
313
+ | Credential presented | Principal | Kernel callable | Domain callable reached by Policy |
314
+ | --- | --- | --- | --- |
315
+ | Source credential (minted or key) | the identity | ✅ | ❌ `2004`, unless the identity holds `can_use` on it through a Group |
316
+ | Session with `exchange` | the declaring Domain, for Domain callables | ✅ with the source credential | ✅ Policy evaluated against the identity |
317
+ | `session.exchange(issuer)` held by a second Session | that Domain | ✅ as that Domain | only what that Domain may use |
318
+
249
319
  ## License
250
320
 
251
321
  Apache License 2.0 — see [LICENSE](LICENSE) for details.
@@ -29,6 +29,12 @@ export type ActionAddressOf<Callable> = Callable extends {
29
29
  export type ResolvedDomainOf<Schema extends schema.DomainSchema> = Extract<schema.DomainOf<Schema>, Domain>;
30
30
  /** SDK Action address derived mechanically from the canonical local callable key. */
31
31
  export type ActionAddress<Schema extends schema.DomainSchema> = ActionAddressOf<LocalCallable<ResolvedDomainOf<Schema>>>;
32
+ /** Address of a callable an Action may implement: a durable callable needs a Workflow. */
33
+ export type ImmediateActionAddress<Schema extends schema.DomainSchema> = ActionAddressOf<Exclude<LocalCallable<ResolvedDomainOf<Schema>>, {
34
+ readonly execution: {
35
+ readonly durable: true;
36
+ };
37
+ }>>;
32
38
  export type CallableAtAddress<Schema extends schema.DomainSchema, Address extends string> = LocalCallable<ResolvedDomainOf<Schema>> extends infer Callable ? Callable extends unknown ? ActionAddressOf<Callable> extends Address ? Callable : never : never : never;
33
39
  export type Action<Callable extends ResolvedCallable, Definitions extends Integrations = Readonly<Record<never, never>>, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = (context: ActionContext<Callable, DomainValue, Definitions, Errors>) => Promise<ActionOutputOf<Callable>>;
34
40
  declare const ACTION_SCHEMA: unique symbol;
@@ -1,5 +1,5 @@
1
1
  export { defineAction } from './define.js';
2
2
  export type { Action, ActionDefinition } from './action.js';
3
- export type { ActionContext, ActionExecution, Caller, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, } from './context.js';
3
+ export type { ActionContext, ActionBinaryInput, ActionExecution, Caller, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, } 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';
@@ -1,7 +1,7 @@
1
1
  import type { AuthenticationEvidence, Grant, IdentityId } from '../../platform/auth/index.js';
2
2
  import type { BoundClientSession } from '../../platform/client/session/index.js';
3
3
  import type { NodeId } from '../../platform/graph/node/index.js';
4
- import type { CallableInputOf, Domain, ResolvedFunction, ResolvedMethod } from '../../platform/schema/index.js';
4
+ import type { CallableInputOf, CallableInputModeOf, Domain, ResolvedFunction, ResolvedMethod } from '../../platform/schema/index.js';
5
5
  import type { HandlerDependencies } from '../dependency/index.js';
6
6
  import type { DeclaredErrorFactory, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
7
7
  import type { IntegrationClients, Integrations } from '../integration/index.js';
@@ -34,6 +34,16 @@ export interface ActionExecution {
34
34
  body(): Uint8Array;
35
35
  };
36
36
  }
37
+ /** One admitted, single-consumer binary Function request body. */
38
+ export interface ActionBinaryInput {
39
+ readonly body: ReadableStream<Uint8Array>;
40
+ readonly length: number;
41
+ }
42
+ type BinaryInputOf<Callable> = CallableInputModeOf<Callable> extends 'binary' ? {
43
+ readonly body: ActionBinaryInput;
44
+ } : {
45
+ readonly body?: never;
46
+ };
37
47
  export interface DomainGraphExecutor<DomainValue extends Domain = Domain> {
38
48
  readonly query: QueryExecutor<DomainValue>;
39
49
  readonly mutate: MutationExecutor<DomainValue>;
@@ -83,5 +93,5 @@ export type ActionContext<Callable extends ResolvedCallable, DomainValue extends
83
93
  readonly execution: ActionExecution;
84
94
  /** Construct one implementation-declared safe business error for `throw error(...)`. */
85
95
  readonly error: DeclaredErrorFactory<Errors>;
86
- } & AuthorityOf<Callable, DomainValue> & SelfOf<Callable>;
96
+ } & AuthorityOf<Callable, DomainValue> & SelfOf<Callable> & BinaryInputOf<Callable>;
87
97
  export {};
@@ -1,12 +1,12 @@
1
1
  import type { schema } from '../../platform/schema/index.js';
2
2
  import type { DeclaredErrorOptions, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
3
3
  import type { Integrations } from '../integration/index.js';
4
- import type { Action, ActionAddress, ActionDefinition, CallableAtAddress, ResolvedDomainOf } from './action.js';
4
+ import type { Action, ActionDefinition, CallableAtAddress, ImmediateActionAddress, ResolvedDomainOf } from './action.js';
5
5
  import type { ResolvedCallable } from './context.js';
6
6
  /** Define one simple single-step implementation by its SDK Action address. */
7
7
  export declare function defineAction<Schema extends schema.DomainSchema, Definitions extends Integrations = Readonly<Record<never, never>>>(): {
8
- <const Address extends ActionAddress<Schema>>(address: Address, run: Action<Extract<CallableAtAddress<Schema, Address>, ResolvedCallable>, Definitions, ResolvedDomainOf<Schema>, NoDeclaredErrors>): ActionDefinition<Schema, Address, Definitions, NoDeclaredErrors>;
9
- <const Address extends ActionAddress<Schema>, const Errors extends DeclaredErrors>(address: Address, options: DeclaredErrorOptions<Errors>, run: Action<Extract<CallableAtAddress<Schema, Address>, ResolvedCallable>, Definitions, ResolvedDomainOf<Schema>, Errors>): ActionDefinition<Schema, Address, Definitions, Errors>;
8
+ <const Address extends ImmediateActionAddress<Schema>>(address: Address, run: Action<Extract<CallableAtAddress<Schema, Address>, ResolvedCallable>, Definitions, ResolvedDomainOf<Schema>, NoDeclaredErrors>): ActionDefinition<Schema, Address, Definitions, NoDeclaredErrors>;
9
+ <const Address extends ImmediateActionAddress<Schema>, const Errors extends DeclaredErrors>(address: Address, options: DeclaredErrorOptions<Errors>, run: Action<Extract<CallableAtAddress<Schema, Address>, ResolvedCallable>, Definitions, ResolvedDomainOf<Schema>, Errors>): ActionDefinition<Schema, Address, Definitions, Errors>;
10
10
  };
11
11
  /** True only for an Action produced by defineAction in this SDK package root. */
12
12
  export declare function isAction(input: unknown): input is ActionDefinition;
@@ -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, ActionExecution, AnonymousCaller, AuthenticatedCaller, Caller, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, ResolvedCallable, } from './context.js';
3
+ export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, 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';
@@ -1 +1 @@
1
- export type { DependencyCallOptions, DependencyInvoker, HandlerDependencies } from './invoker.js';
1
+ export type { DependencyBinaryCallOptions, DependencyCallOptions, DependencyInvoker, HandlerDependencies, } from './invoker.js';
@@ -1,18 +1,23 @@
1
1
  import type { Binary, Input } from '../../platform/client/index.js';
2
- import type { SessionRequestOptions } from '../../platform/client/session/index.js';
2
+ import type { SessionRequestOptions, SessionRequestOptionsWithBody } from '../../platform/client/session/index.js';
3
3
  import type { NodeId } from '../../platform/graph/node/index.js';
4
- import type { CallableInputOf, CallableOutputOf, CallableResultOf, Domain, ResolvedMethod, KernelSchema, schema } from '../../platform/schema/index.js';
4
+ import type { CallableInputOf, CallableInputModeOf, CallableOutputOf, CallableResultOf, Domain, ResolvedFunction, ResolvedMethod, KernelSchema, schema } from '../../platform/schema/index.js';
5
5
  import type { LocalCallable } from '../action/index.js';
6
- export type DependencyCallOptions = Pick<SessionRequestOptions, 'timeoutMs' | 'idempotencyKey'>;
6
+ export type DependencyCallOptions = Pick<SessionRequestOptions, 'timeoutMs' | 'idempotencyKey' | 'body'>;
7
+ export type DependencyBinaryCallOptions = Pick<SessionRequestOptionsWithBody, 'timeoutMs' | 'idempotencyKey' | 'body'>;
7
8
  type InstanceMethodOf<D extends Domain> = Extract<LocalCallable<D>, ResolvedMethod<unknown, unknown, false>>;
8
9
  type FunctionOrStaticMethodOf<D extends Domain> = Exclude<LocalCallable<D>, ResolvedMethod<unknown, unknown, false>>;
10
+ type WithInputMode<C, Mode extends 'value' | 'binary'> = C extends unknown ? CallableInputModeOf<C> extends Mode ? C : never : never;
11
+ type ValueFunctionOrStaticMethodOf<D extends Domain> = WithInputMode<FunctionOrStaticMethodOf<D>, 'value'>;
12
+ type BinaryFunctionOf<D extends Domain> = WithInputMode<Extract<LocalCallable<D>, ResolvedFunction>, 'binary'>;
9
13
  type DependencyInputOf<C> = C extends unknown ? unknown extends CallableInputOf<C> ? Input : [CallableInputOf<C>] extends [void] ? undefined : CallableInputOf<C> : never;
10
14
  type DependencyResultOf<C> = unknown extends CallableOutputOf<C> ? unknown : CallableResultOf<C, Binary>;
11
15
  /** One declared dependency, bound to one explicitly selected invocation authority. */
12
16
  export interface DependencyInvoker<D extends Domain> {
13
17
  readonly invoke: {
14
- <const C extends FunctionOrStaticMethodOf<D>>(select: (domain: D) => C, input: NoInfer<DependencyInputOf<C>>, options?: DependencyCallOptions): Promise<DependencyResultOf<C>>;
18
+ <const C extends ValueFunctionOrStaticMethodOf<D>>(select: (domain: D) => C, input: NoInfer<DependencyInputOf<C>>, options?: DependencyCallOptions): Promise<DependencyResultOf<C>>;
15
19
  <const C extends InstanceMethodOf<D>>(select: (domain: D) => C, instance: NodeId, input: NoInfer<DependencyInputOf<C>>, options?: DependencyCallOptions): Promise<DependencyResultOf<C>>;
20
+ <const C extends BinaryFunctionOf<D>>(select: (domain: D) => C, metadata: NoInfer<DependencyInputOf<C>>, options: DependencyBinaryCallOptions): Promise<DependencyResultOf<C>>;
16
21
  };
17
22
  }
18
23
  /**
@@ -1,9 +1,11 @@
1
1
  import type { MutationPropsInput } from '@astrale-os/kernel-core/graph/mutate';
2
2
  import type { PropertyInputOf, ResolvedClass, ResolvedProperty } from '@astrale-os/kernel-dsl/v1';
3
+ import { PropertyValues } from '@astrale-os/kernel-dsl/v1/language';
4
+ import type { StoredPropertyInputOf } from '../../../platform/schema/property-input.js';
3
5
  export type RichPropertyInput<Class> = PropertyInputOf<Class>;
4
6
  export type RichPropertyName<Class> = Extract<keyof PropertyInputOf<Class>, string>;
5
7
  export interface RichPropertyConditions<Class> {
6
- readonly equals?: RichPropertyInput<Class>;
8
+ readonly equals?: StoredPropertyInputOf<Class>;
7
9
  readonly absent?: readonly RichPropertyName<Class>[];
8
10
  }
9
11
  export interface RichPropertyDelta<Class> {
@@ -12,11 +14,11 @@ export interface RichPropertyDelta<Class> {
12
14
  }
13
15
  export declare function properties<Class extends ResolvedClass>(selected: Class, input: RichPropertyInput<Class>): MutationPropsInput;
14
16
  export declare function conditions<Class extends ResolvedClass>(selected: Class, input: RichPropertyConditions<Class>): Readonly<{
15
- equals?: Readonly<Record<`${string}:class.${string}.property.${string}`, import("@astrale-os/kernel-dsl/value").Value>> | undefined;
17
+ equals?: PropertyValues | undefined;
16
18
  absent?: readonly `${string}:class.${string}.property.${string}`[] | undefined;
17
19
  }>;
18
20
  export declare function delta<Class extends ResolvedClass>(selected: Class, input: RichPropertyDelta<Class>): Readonly<{
19
- set: Readonly<Record<`${string}:class.${string}.property.${string}`, import("@astrale-os/kernel-dsl/value").Value>>;
21
+ set: Readonly<Record<`${string}:class.${string}.property.${string}`, import("@astrale-os/kernel-dsl/v1/language").PropertyValue>>;
20
22
  unset: readonly `${string}:class.${string}.property.${string}`[];
21
23
  }>;
22
24
  export declare function resolvedProperty<Class extends ResolvedClass>(selected: Class, name: RichPropertyName<Class>): ResolvedProperty;
@@ -1,10 +1,39 @@
1
1
  import { PropertyKey } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import { PropertyValues } from '@astrale-os/kernel-dsl/v1/language';
2
3
  export function properties(selected, input) {
3
- return selected.properties.from(input);
4
+ const ordinary = Object.create(null);
5
+ const receipts = [];
6
+ const sources = new Map();
7
+ for (const [name, value] of Object.entries(input)) {
8
+ const property = resolvedProperty(selected, name);
9
+ const previous = sources.get(property.key);
10
+ if (previous !== undefined) {
11
+ throw new TypeError(`Property ${property.key} is supplied through both ${JSON.stringify(previous)} and ${JSON.stringify(name)}.`);
12
+ }
13
+ sources.set(property.key, name);
14
+ if (isBlobProperty(property)) {
15
+ if (typeof value !== 'string') {
16
+ throw new TypeError(`Blob Property ${property.key} requires an upload receipt.`);
17
+ }
18
+ receipts.push([property.key, value]);
19
+ }
20
+ else {
21
+ ordinary[name] = value;
22
+ }
23
+ }
24
+ // Blob receipts are admitted by the Kernel executor. A property's ordinary validator expects
25
+ // the stored Ref, so run it only for ordinary write values before combining the two sets.
26
+ const admitted = selected.properties.from(ordinary);
27
+ return receipts.length === 0
28
+ ? admitted
29
+ : PropertyValues.create([
30
+ ...Object.entries(admitted).map(([key, value]) => [PropertyKey(key), value]),
31
+ ...receipts,
32
+ ]);
4
33
  }
5
34
  export function conditions(selected, input) {
6
35
  return Object.freeze({
7
- ...(input.equals === undefined ? {} : { equals: properties(selected, input.equals) }),
36
+ ...(input.equals === undefined ? {} : { equals: selected.properties.from(input.equals) }),
8
37
  ...(input.absent === undefined
9
38
  ? {}
10
39
  : {
@@ -12,6 +41,10 @@ export function conditions(selected, input) {
12
41
  }),
13
42
  });
14
43
  }
44
+ function isBlobProperty(property) {
45
+ const contract = property.definition.schema;
46
+ return (contract !== null && typeof contract === 'object' && contract['x-astrale-value-kind'] === 'blob');
47
+ }
15
48
  export function delta(selected, input) {
16
49
  return Object.freeze({
17
50
  set: properties(selected, input.set ?? {}),
@@ -49,7 +49,10 @@ export function authorStateTransition(core, input) {
49
49
  class: selectedClassKey,
50
50
  props: conditions(selectedClass, {
51
51
  ...(suppliedAbsent === undefined ? {} : { absent: suppliedAbsent }),
52
- equals: { ...suppliedEquals, ...from },
52
+ equals: {
53
+ ...suppliedEquals,
54
+ ...from,
55
+ },
53
56
  }),
54
57
  });
55
58
  core.updateNode({
@@ -16,6 +16,9 @@ export function admitActions(callables, input) {
16
16
  if (callable === undefined) {
17
17
  throw new TypeError(`Runtime Action ${definition.address} is not root-local and executable.`);
18
18
  }
19
+ if (callable.execution?.durable === true) {
20
+ throw new TypeError(`Runtime Action ${definition.address} is durable; implement it with defineWorkflow.`);
21
+ }
19
22
  return Object.freeze({
20
23
  kind: 'action',
21
24
  address: definition.address,
@@ -6,6 +6,8 @@ export interface RuntimeWorkflow {
6
6
  readonly address: string;
7
7
  readonly callable: ResolvedCallable;
8
8
  readonly definition: WorkflowDefinition;
9
+ /** Declared `execution: { durable: true }`: runs on the workflow engine, never inline. */
10
+ readonly durable: boolean;
9
11
  }
10
12
  /** Associate admitted Workflows with their exact authenticated value callables. */
11
13
  export declare function admitWorkflows(callables: readonly RuntimeCallable[], input: unknown): readonly RuntimeWorkflow[];
@@ -22,11 +22,16 @@ export function admitWorkflows(callables, input) {
22
22
  if (callable.output.mode !== 'value') {
23
23
  throw new TypeError(`Runtime Workflow ${definition.address} must have value output.`);
24
24
  }
25
+ const durable = callable.execution?.durable === true;
26
+ if (!durable && definition.compat !== undefined) {
27
+ throw new TypeError(`Runtime Workflow ${definition.address} declares compat but its callable is not durable.`);
28
+ }
25
29
  return Object.freeze({
26
30
  kind: 'workflow',
27
31
  address: definition.address,
28
32
  callable,
29
33
  definition,
34
+ durable,
30
35
  });
31
36
  });
32
37
  return Object.freeze(workflows);
@@ -1,4 +1,5 @@
1
1
  export { defineWorkflow } from './define.js';
2
- export type { WorkflowContext } from './context.js';
3
- export type { Workflow, WorkflowAddress, WorkflowDefinition } from './workflow.js';
2
+ export type { InlineWorkflowContext, WorkflowContext } from './context.js';
3
+ export type { DurableCallable, DurableExecution, DurableStep, DurableStepAuthority, DurableWorkflowContext, } from './durable.js';
4
+ export type { Workflow, WorkflowAddress, WorkflowDefinition, WorkflowOptions } from './workflow.js';
4
5
  export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, } from '../error/index.js';
@@ -2,8 +2,14 @@ import type { Domain } from '../../platform/schema/index.js';
2
2
  import type { ActionContext, ResolvedCallable } from '../action/index.js';
3
3
  import type { DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
4
4
  import type { Integrations } from '../integration/index.js';
5
+ import type { DurableCallable, DurableWorkflowContext } from './durable.js';
5
6
  import type { WorkflowStep } from './step/index.js';
6
- /** Exact multi-step Workflow context for one authenticated value-output callable. */
7
- export type WorkflowContext<Callable extends ResolvedCallable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = ActionContext<Callable, DomainValue, Definitions, Errors> & {
7
+ /** Multi-step context of one authenticated value-output callable run inside its invocation. */
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> & {
8
9
  readonly step: WorkflowStep;
9
10
  };
11
+ /**
12
+ * Exact Workflow context for one callable: engine-journaled when its Schema declares
13
+ * `execution: { durable: true }`, otherwise run inside its invocation.
14
+ */
15
+ export type WorkflowContext<Callable extends ResolvedCallable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = Callable extends DurableCallable ? DurableWorkflowContext<Callable, DomainValue, Errors> : InlineWorkflowContext<Callable, DomainValue, Definitions, Errors>;
@@ -1,11 +1,11 @@
1
1
  import type { schema } from '../../platform/schema/index.js';
2
2
  import type { ResolvedDomainOf } from '../action/index.js';
3
- import type { DeclaredErrorOptions, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
3
+ import type { DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
4
4
  import type { Integrations } from '../integration/index.js';
5
- import type { CallableAtWorkflowAddress, Workflow, WorkflowAddress, WorkflowDefinition } from './workflow.js';
5
+ import type { CallableAtWorkflowAddress, Workflow, WorkflowAddress, WorkflowDefinition, WorkflowOptions } from './workflow.js';
6
6
  /** Define one authenticated 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
- <const Address extends WorkflowAddress<Schema>, const Errors extends DeclaredErrors>(address: Address, options: DeclaredErrorOptions<Errors>, run: Workflow<CallableAtWorkflowAddress<Schema, Address>, Definitions, ResolvedDomainOf<Schema>, Errors>): WorkflowDefinition<Schema, Address, Definitions, Errors>;
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>;
10
10
  };
11
11
  export declare function isWorkflow(input: unknown): input is WorkflowDefinition;
@@ -1,11 +1,12 @@
1
1
  import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
2
2
  import { rememberDeclaredErrors } from '../error/index.js';
3
3
  const admittedWorkflows = new WeakSet();
4
+ const COMPAT_TOKEN = /^[A-Za-z0-9._~-]{1,64}$/u;
4
5
  /** Define one authenticated multi-step value-output implementation by its SDK Action address. */
5
6
  export function defineWorkflow() {
6
7
  return function define(address, optionsOrRun, possibleRun) {
7
8
  acceptWorkflowAddress(address);
8
- const options = possibleRun === undefined ? undefined : optionsOrRun;
9
+ const options = possibleRun === undefined ? undefined : acceptOptions(optionsOrRun);
9
10
  const run = possibleRun ?? optionsOrRun;
10
11
  if (typeof run !== 'function')
11
12
  throw new TypeError('Workflow run must be a function.');
@@ -13,13 +14,28 @@ export function defineWorkflow() {
13
14
  kind: 'workflow',
14
15
  address,
15
16
  run,
17
+ ...(options?.compat === undefined ? {} : { compat: options.compat }),
16
18
  });
17
- if (options !== undefined)
18
- rememberDeclaredErrors(workflow, options);
19
+ if (options?.errors !== undefined)
20
+ rememberDeclaredErrors(workflow, { errors: options.errors });
19
21
  admittedWorkflows.add(workflow);
20
22
  return workflow;
21
23
  };
22
24
  }
25
+ function acceptOptions(input) {
26
+ if (input === null ||
27
+ typeof input !== 'object' ||
28
+ Array.isArray(input) ||
29
+ Reflect.ownKeys(input).some((key) => key !== 'errors' && key !== 'compat')) {
30
+ throw new TypeError('Workflow options accept only errors and compat.');
31
+ }
32
+ const options = input;
33
+ if (options.compat !== undefined &&
34
+ (typeof options.compat !== 'string' || !COMPAT_TOKEN.test(options.compat))) {
35
+ throw new TypeError(`Invalid Workflow compat token: ${JSON.stringify(options.compat)}.`);
36
+ }
37
+ return options;
38
+ }
23
39
  export function isWorkflow(input) {
24
40
  return input !== null && typeof input === 'object' && admittedWorkflows.has(input);
25
41
  }
@@ -0,0 +1,83 @@
1
+ import type { IssuerId } from '../../platform/auth/index.js';
2
+ import type { BoundClientSession, ExecutionOutcome, ExecutionRef } from '../../platform/client/session/index.js';
3
+ import type { NodeId } from '../../platform/graph/node/index.js';
4
+ import type { CallableInputOf, Domain, ResolvedMethod } from '../../platform/schema/index.js';
5
+ import type { Value } from '../../platform/value/index.js';
6
+ import type { DeclaredErrorFactory, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
7
+ import type { Mutation, MutationExecutor } from '../mutation/index.js';
8
+ import type { QueryExecutor } from '../query/index.js';
9
+ /** A callable whose Schema declares `execution: { durable: true }`. */
10
+ export interface DurableCallable {
11
+ readonly execution: {
12
+ readonly durable: true;
13
+ };
14
+ }
15
+ /** Attested facts of the running Execution. They are data, never authority. */
16
+ export interface DurableExecution {
17
+ /** The `K.Execution` node recording this call. */
18
+ readonly id: NodeId;
19
+ readonly function: {
20
+ readonly id: NodeId;
21
+ readonly version: `sha256:${string}`;
22
+ };
23
+ /** Kernel that recorded the Execution; every Step calls it back as the Domain. */
24
+ readonly kernel: IssuerId;
25
+ /** Who started the Execution. The Workflow never receives their credential. */
26
+ readonly initiator: {
27
+ readonly principal: NodeId;
28
+ readonly caller: NodeId;
29
+ };
30
+ /** Receiver of a durable instance Method. */
31
+ readonly receiver?: NodeId;
32
+ }
33
+ /**
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.
36
+ */
37
+ export interface DurableStepAuthority<DomainValue extends Domain = Domain> {
38
+ /**
39
+ * Kernel Session acting as the Domain. Each `invoke` without an explicit `idempotencyKey` carries
40
+ * one derived from the Execution, the Step and its position, so a retried Step reaches the same
41
+ * durable child Execution.
42
+ */
43
+ readonly kernel: BoundClientSession;
44
+ readonly query: QueryExecutor<DomainValue>;
45
+ /** Each Mutation carries a key derived from the Execution, the Step and its position. */
46
+ readonly mutate: MutationExecutor<DomainValue>;
47
+ }
48
+ export interface DurableStep<DomainValue extends Domain = Domain> {
49
+ /**
50
+ * Run `execute` once and journal its portable output; a replay returns the journaled output. A
51
+ * Step may run again when its outcome was not journaled, so its effects must be idempotent.
52
+ */
53
+ run<Output>(id: string, execute: (authority: DurableStepAuthority<DomainValue>) => Output | Promise<Output>): Promise<Output>;
54
+ /**
55
+ * One journaled Mutation. Give it a zero-match precondition on a natural key (for example the
56
+ * Execution id) and map that failure in `reject`, so a Step replayed after its commit finds its
57
+ * own effect instead of repeating it.
58
+ */
59
+ mutate<Input, Output>(id: string, mutation: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
60
+ }
61
+ type DurableSelfOf<Callable> = Callable extends ResolvedMethod<unknown, unknown, false> ? {
62
+ readonly self: NodeId;
63
+ } : {
64
+ readonly self?: never;
65
+ };
66
+ /** Context of a durable Workflow: engine-journaled, acting as its Domain. */
67
+ export type DurableWorkflowContext<Callable, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = {
68
+ readonly domain: DomainValue;
69
+ readonly input: CallableInputOf<Callable>;
70
+ readonly execution: DurableExecution;
71
+ /** Construct one implementation-declared safe business error for `throw error(...)`. */
72
+ readonly error: DeclaredErrorFactory<Errors>;
73
+ readonly step: DurableStep<DomainValue>;
74
+ /** Durable timer kept by the engine, even while the Domain is idle or down. */
75
+ sleep(durationMs: number): Promise<void>;
76
+ /** Resolve once `Execution.signal({ name, value })` delivers `name`, before or during the wait. */
77
+ promise<Signal extends Value = Value>(name: string): Promise<Signal>;
78
+ /** Whether `error` is the engine's cooperative cancellation, which the Workflow may compensate. */
79
+ cancelled(error: unknown): boolean;
80
+ /** Wait durably for another Execution to end, by journaled polling of its `result`. */
81
+ awaitExecution<Output>(execution: ExecutionRef<Output>): Promise<ExecutionOutcome<Output>>;
82
+ } & DurableSelfOf<Callable>;
83
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  export { defineWorkflow, isWorkflow } from './define.js';
2
- export type { WorkflowContext } from './context.js';
3
- export type { CallableAtWorkflowAddress, Workflow, WorkflowAddress, WorkflowCallable, WorkflowDefinition, } from './workflow.js';
2
+ export type { InlineWorkflowContext, WorkflowContext } from './context.js';
3
+ export type { DurableCallable, DurableExecution, DurableStep, DurableStepAuthority, DurableWorkflowContext, } from './durable.js';
4
+ export type { CallableAtWorkflowAddress, Workflow, WorkflowAddress, WorkflowCallable, WorkflowDefinition, WorkflowOptions, } from './workflow.js';
4
5
  export * as step from './step/index.js';
5
6
  export * as runner from './runner/index.js';
6
7
  export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, } from '../error/index.js';
@@ -1,11 +1,12 @@
1
- import * as value from '../../../platform/value/index.js';
2
1
  import { acceptStepId } from '../step/index.js';
2
+ import { acceptStepOutput } from '../step/output.js';
3
3
  /** Process-local runner for tests and explicitly non-durable embeddings. */
4
4
  export function createInlineWorkflowRunner() {
5
5
  async function run(workflow, context) {
6
6
  const execution = createInlineStep(context.execution.signal);
7
7
  const complete = Object.freeze({ ...context, step: execution.step });
8
8
  try {
9
+ // Runtime dispatches only non-durable Workflows inline; durable ones run on the engine.
9
10
  return await workflow.run(complete);
10
11
  }
11
12
  finally {
@@ -37,10 +38,8 @@ function createInlineStep(signal) {
37
38
  started.add(accepted);
38
39
  execution = (async () => {
39
40
  const output = await execute();
40
- if (output === undefined)
41
- return output;
42
41
  try {
43
- return value.accept(output);
42
+ return acceptStepOutput(output);
44
43
  }
45
44
  catch (cause) {
46
45
  throw new TypeError(`Workflow Step ${accepted} output must be portable.`, {
@@ -2,9 +2,9 @@ import type { Domain, schema } from '../../../platform/schema/index.js';
2
2
  import type { ActionOutputOf, ResolvedCallable, 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
- import type { WorkflowContext } from '../context.js';
5
+ import type { InlineWorkflowContext } from '../context.js';
6
6
  import type { CallableAtWorkflowAddress, WorkflowAddress, WorkflowDefinition } from '../workflow.js';
7
- export type WorkflowExecutionContext<Callable extends ResolvedCallable, Definitions extends Integrations, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = Omit<WorkflowContext<Callable, DomainValue, Definitions, Errors>, 'step'>;
7
+ export type WorkflowExecutionContext<Callable extends ResolvedCallable, Definitions extends Integrations, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = Omit<InlineWorkflowContext<Callable, DomainValue, Definitions, Errors>, 'step'>;
8
8
  export interface WorkflowRunner {
9
9
  run<Schema extends schema.DomainSchema, Address extends WorkflowAddress<Schema>, Definitions extends Integrations, Errors extends DeclaredErrors, Callable extends CallableAtWorkflowAddress<Schema, Address> = CallableAtWorkflowAddress<Schema, Address>>(workflow: WorkflowDefinition<Schema, Address, Definitions, Errors>, context: WorkflowExecutionContext<Callable, Definitions, ResolvedDomainOf<Schema>, Errors>): Promise<ActionOutputOf<Callable>>;
10
10
  }
@@ -0,0 +1,2 @@
1
+ /** Capture optional Object fields as absence without changing portable Array or scalar semantics. */
2
+ export declare function acceptStepOutput<Output>(input: Output): Output;