@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
package/README.md CHANGED
@@ -73,6 +73,16 @@ on the Kernel's workflow engine: a call returns its `K.Execution` at once, and t
73
73
  restarts with journaled Steps, durable timers and signals. Serve it with `serve({ durable })`. See
74
74
  [the durable Workflow guide](docs/durable-workflows.md).
75
75
 
76
+ ## Admission age
77
+
78
+ A callable declared with `admission: { maxAge: <seconds> }` (1 to 300) lets its deployment reuse an
79
+ admission the source Kernel granted, for the same Kernel, caller, target and Policy-read input fields,
80
+ while it is younger than that age: such a call skips its `Function.admit` round trip. Revocation then
81
+ reaches it within that age. Credential verification, the release pin and input validation still run
82
+ on every call. Admissions are kept in the deployment cache, `serve({ cache })`: a memory cache by
83
+ default, the Cache API on Cloudflare, and none with `cache: false`. See
84
+ [the admission age guide](docs/admission-age.md).
85
+
76
86
  ## Node Class icons
77
87
 
78
88
  Every Node Class authored through the SDK must explicitly declare a self-contained SVG icon.
@@ -50,20 +50,23 @@ export interface DomainGraphExecutor<DomainValue extends Domain = Domain> {
50
50
  /** Whose authority one Kernel call exercises: the incoming caller's or the installed Domain's. */
51
51
  export type AuthorityMode = 'caller' | 'self';
52
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.
53
+ * One value per authority the code may act under. `self` exists in every invocation; `caller`
54
+ * exists only when the call carried a credential, so it is absent from a call that carried none.
55
55
  */
56
- export type AuthoritySelection<Value, Selectable extends AuthorityMode = AuthorityMode> = Readonly<Record<Selectable, Value> & {
57
- readonly [Absent in Exclude<AuthorityMode, Selectable>]?: never;
56
+ export type AuthoritySelection<Value, Authority extends AuthorityMode = AuthorityMode> = Readonly<Record<Authority, Value> & {
57
+ readonly [Absent in Exclude<AuthorityMode, Authority>]?: never;
58
58
  }>;
59
- export type DomainGraphExecutors<DomainValue extends Domain = Domain, Selectable extends AuthorityMode = AuthorityMode> = Readonly<DomainGraphExecutor<DomainValue> & AuthoritySelection<DomainGraphExecutor<DomainValue>, Selectable>>;
59
+ export type DomainGraphExecutors<DomainValue extends Domain = Domain, Authority extends AuthorityMode = AuthorityMode> = Readonly<DomainGraphExecutor<DomainValue> & AuthoritySelection<DomainGraphExecutor<DomainValue>, Authority>>;
60
60
  /** Self-default Kernel session with one level of explicit authority selection. */
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>;
61
+ export type DomainKernelSessions<Authority extends AuthorityMode = AuthorityMode> = Readonly<BoundClientSession & AuthoritySelection<BoundClientSession, Authority>>;
62
+ /**
63
+ * What code acting under `Authority` holds toward the Kernel and its dependencies, with the
64
+ * installed Domain authority by default. An Action and a durable Step receive the same capabilities.
65
+ */
66
+ export interface DomainCapabilities<DomainValue extends Domain = Domain, Authority extends AuthorityMode = AuthorityMode> {
67
+ readonly dependencies: HandlerDependencies<DomainValue, Authority>;
68
+ readonly kernel: DomainKernelSessions<Authority>;
69
+ readonly graph: DomainGraphExecutors<DomainValue, Authority>;
67
70
  /** Convenience executor using the installed Domain authority (`graph.self`). */
68
71
  readonly query: QueryExecutor<DomainValue>;
69
72
  /** Convenience executor using the installed Domain authority (`graph.self`). */
@@ -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, AuthorityMode, AuthoritySelection, Caller, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, ResolvedCallable, } from './context.js';
3
+ export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, AuthorityMode, AuthoritySelection, Caller, DomainCapabilities, 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,5 +1,5 @@
1
1
  import type { Binary, Input } from '../../platform/client/index.js';
2
- import type { SessionRequestOptions, SessionRequestOptionsWithBody } from '../../platform/client/session/index.js';
2
+ import type { ExecutionRef, 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
5
  import type { AuthorityMode, AuthoritySelection, LocalCallable } from '../action/index.js';
@@ -11,7 +11,13 @@ type WithInputMode<C, Mode extends 'value' | 'binary'> = C extends unknown ? Cal
11
11
  type ValueFunctionOrStaticMethodOf<D extends Domain> = WithInputMode<FunctionOrStaticMethodOf<D>, 'value'>;
12
12
  type BinaryFunctionOf<D extends Domain> = WithInputMode<Extract<LocalCallable<D>, ResolvedFunction>, 'binary'>;
13
13
  type DependencyInputOf<C> = C extends unknown ? unknown extends CallableInputOf<C> ? Input : [CallableInputOf<C>] extends [void] ? undefined : CallableInputOf<C> : never;
14
- type DependencyResultOf<C> = unknown extends CallableOutputOf<C> ? unknown : CallableResultOf<C, Binary>;
14
+ type HandlerResultOf<C> = unknown extends CallableOutputOf<C> ? unknown : CallableResultOf<C, Binary>;
15
+ /** A durable callable answers with its Execution, as the Kernel Client's typed reference does. */
16
+ type DependencyResultOf<C> = C extends {
17
+ readonly execution: {
18
+ readonly durable: true;
19
+ };
20
+ } ? ExecutionRef<HandlerResultOf<C>> : HandlerResultOf<C>;
15
21
  /** One declared dependency, bound to one explicitly selected invocation authority. */
16
22
  export interface DependencyInvoker<D extends Domain> {
17
23
  readonly invoke: {
@@ -25,7 +31,7 @@ export interface DependencyInvoker<D extends Domain> {
25
31
  * self is the executing Domain; caller forwards the incoming caller Identity, and is absent from a
26
32
  * call that carried no credential. The canonical Kernel is exposed through context.kernel.
27
33
  */
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>;
34
+ export type HandlerDependencies<D extends Domain, Authority extends AuthorityMode = AuthorityMode> = Readonly<{
35
+ [Alias in keyof D['dependencies'] as D['dependencies'][Alias] extends schema.DomainOf<KernelSchema> ? never : Alias]: AuthoritySelection<DependencyInvoker<Extract<D['dependencies'][Alias], Domain>>, Authority>;
30
36
  }>;
31
37
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { IntegrationDefinition, Integrations } from './integration.js';
2
- import type { IntegrationOperation, IntegrationOperations, IntegrationReplay } from './operation.js';
2
+ import type { IntegrationOperation, IntegrationOperations, IntegrationReplay, ReplayableIntegrationDecision } from './operation.js';
3
3
  import type { Provider, ProviderImplementation } from './provider.js';
4
4
  type RuntimeDefinition = IntegrationDefinition<string, IntegrationOperations>;
5
5
  type ProviderOperations = Readonly<Record<string, (...input: readonly unknown[]) => unknown>>;
@@ -12,6 +12,13 @@ export interface DefineIntegration {
12
12
  readonly id: Id;
13
13
  readonly operations: Operations;
14
14
  }): IntegrationDefinition<Id, Operations>;
15
+ /**
16
+ * One operation and what another attempt of it may do. A decision that is never `unsafe` keeps
17
+ * the operation available to code that may run more than once, such as a durable Step.
18
+ */
19
+ operation<Input = void, Output = void>(input: {
20
+ readonly replay: ReplayableIntegrationDecision | ((input: Input) => ReplayableIntegrationDecision);
21
+ }): IntegrationOperation<Input, Output, ReplayableIntegrationDecision['kind']>;
15
22
  operation<Input = void, Output = void>(input: {
16
23
  readonly replay: IntegrationReplay<Input>;
17
24
  }): IntegrationOperation<Input, Output>;
@@ -2,7 +2,7 @@ export { createIntegrationClients, bindProviders } from './client.js';
2
2
  export type { IntegrationAttempt, IntegrationCallCoordinates, IntegrationClientFactory, IntegrationInvocationOwner, } from './client.js';
3
3
  export { admitIntegrations, defineIntegration, defineProvider, requireIntegration, requireProvider, } from './define.js';
4
4
  export type { DefineIntegration, ProviderBinding } from './define.js';
5
- export type { IntegrationClient, IntegrationClients, IntegrationDefinition, Integrations, } from './integration.js';
6
- export type { IntegrationExecution, IntegrationInput, IntegrationOperation, IntegrationOperations, IntegrationOutput, IntegrationReplay, IntegrationReplayDecision, } from './operation.js';
5
+ export type { IntegrationClient, IntegrationClients, IntegrationDefinition, Integrations, ReplayableIntegrationClient, ReplayableIntegrationClients, } from './integration.js';
6
+ export type { IntegrationExecution, IntegrationInput, IntegrationOperation, IntegrationOperations, IntegrationOutput, IntegrationReplay, IntegrationReplayDecision, IntegrationReplayKind, IntegrationReplayOf, ReplayableIntegrationDecision, } from './operation.js';
7
7
  export type { Provider, ProviderImplementation, Providers } from './provider.js';
8
8
  export type { IntegrationAuthentication, IntegrationMintInput } from './authentication.js';
@@ -1,4 +1,4 @@
1
- import type { IntegrationOperation, IntegrationOperations } from './operation.js';
1
+ import type { IntegrationOperation, IntegrationOperations, IntegrationReplayOf } from './operation.js';
2
2
  declare const INTEGRATION_DEFINITION: unique symbol;
3
3
  export interface IntegrationDefinition<Id extends string = string, Operations extends IntegrationOperations = IntegrationOperations> {
4
4
  readonly kind: 'integration';
@@ -16,4 +16,16 @@ export type IntegrationClient<Definition extends IntegrationDefinition> = Readon
16
16
  export type IntegrationClients<Definitions extends Integrations> = Readonly<{
17
17
  [Name in keyof Definitions]: IntegrationClient<Definitions[Name]>;
18
18
  }>;
19
+ type ReplayableOperationName<Definition extends IntegrationDefinition> = {
20
+ [Name in keyof Definition['operations']]: 'unsafe' extends IntegrationReplayOf<Definition['operations'][Name]> ? never : Name;
21
+ }[keyof Definition['operations']];
22
+ /** The operations of one Integration that may be attempted again: each is `safe` or keyed. */
23
+ export type ReplayableIntegrationClient<Definition extends IntegrationDefinition> = Pick<IntegrationClient<Definition>, ReplayableOperationName<Definition>>;
24
+ /**
25
+ * Integration clients of code that may run more than once, such as a durable Step. An operation
26
+ * whose replay decision may be `unsafe` is absent: another attempt could repeat its effect.
27
+ */
28
+ export type ReplayableIntegrationClients<Definitions extends Integrations> = Readonly<{
29
+ [Name in keyof Definitions]: ReplayableIntegrationClient<Definitions[Name]>;
30
+ }>;
19
31
  export {};
@@ -1,6 +1,7 @@
1
1
  import type { BoundClientSession } from '../../platform/client/session/index.js';
2
2
  import type { IntegrationAuthentication } from './authentication.js';
3
3
  declare const INTEGRATION_OPERATION: unique symbol;
4
+ declare const INTEGRATION_REPLAY: unique symbol;
4
5
  export type IntegrationReplayDecision = {
5
6
  readonly kind: 'safe';
6
7
  } | {
@@ -13,6 +14,12 @@ export type IntegrationReplayDecision = {
13
14
  } | {
14
15
  readonly kind: 'unsafe';
15
16
  };
17
+ /** What another attempt of an operation may do: anything, once per key, or nothing. */
18
+ export type IntegrationReplayKind = IntegrationReplayDecision['kind'];
19
+ /** A decision that allows another attempt of the operation. */
20
+ export type ReplayableIntegrationDecision = Exclude<IntegrationReplayDecision, {
21
+ readonly kind: 'unsafe';
22
+ }>;
16
23
  export type IntegrationReplay<Input> = IntegrationReplayDecision | ((input: Input) => IntegrationReplayDecision);
17
24
  export interface IntegrationExecution {
18
25
  readonly callId: string;
@@ -24,9 +31,11 @@ export interface IntegrationExecution {
24
31
  /** Domain-self transport authentication; absent only outside a Kernel invocation. */
25
32
  readonly auth?: IntegrationAuthentication;
26
33
  }
27
- export interface IntegrationOperation<Input = unknown, Output = unknown> {
34
+ export interface IntegrationOperation<Input = unknown, Output = unknown, Replay extends IntegrationReplayKind = IntegrationReplayKind> {
28
35
  readonly replay: IntegrationReplay<Input>;
29
36
  readonly [INTEGRATION_OPERATION]: (input: Input) => Output;
37
+ /** Every kind its replay decision may take, as its definition proves them. */
38
+ readonly [INTEGRATION_REPLAY]: Replay;
30
39
  }
31
40
  export type IntegrationOperations = Readonly<Record<string, IntegrationOperation<never, unknown>>>;
32
41
  export type IntegrationInput<Operation> = Operation extends {
@@ -35,4 +44,8 @@ export type IntegrationInput<Operation> = Operation extends {
35
44
  export type IntegrationOutput<Operation> = Operation extends {
36
45
  readonly [INTEGRATION_OPERATION]: (input: never) => infer Output;
37
46
  } ? Output : never;
47
+ /** The replay kinds an operation's definition proves; every kind when it proves none. */
48
+ export type IntegrationReplayOf<Operation> = Operation extends {
49
+ readonly [INTEGRATION_REPLAY]: infer Replay;
50
+ } ? Replay : IntegrationReplayKind;
38
51
  export {};
@@ -10,7 +10,7 @@ export async function executeMutation(client, domain, definition, input) {
10
10
  const operation = realizeMutation(definition, domain);
11
11
  try {
12
12
  const ast = MutationAST.build((builder) => operation.build(input, richMutationBuilder(builder)));
13
- return projectMutation(operation, await client.mutate(ast, { expected: domain.closure }), input);
13
+ return projectMutation(operation, await client.mutate(ast), input);
14
14
  }
15
15
  catch (cause) {
16
16
  const expected = mutationFailure(cause);
@@ -1,6 +1,12 @@
1
1
  export interface KernelMutationFailure extends Error {
2
2
  readonly code: `MUTATION_${string}`;
3
3
  readonly outcomeKnown: boolean;
4
+ /**
5
+ * The post-state rule a `MUTATION_POST_STATE_INVALID` refusal names, such as `duplicate-key`
6
+ * for a value a Class key already holds. Absent when the Kernel names none, and for every other
7
+ * refusal.
8
+ */
9
+ readonly rule?: string;
4
10
  }
5
11
  export interface StateTransitionFailure extends Error {
6
12
  readonly code: 'STATE_TRANSITION_ILLEGAL';
@@ -13,9 +13,10 @@ export function stateTransitionFailure(from, event) {
13
13
  export function mutationFailure(input) {
14
14
  if (!(input instanceof Error))
15
15
  return undefined;
16
- const code = mutationCode(input);
17
- if (code === undefined)
16
+ const reason = mutationReason(input);
17
+ if (reason === undefined)
18
18
  return undefined;
19
+ const { code } = reason;
19
20
  const explicit = input.outcomeKnown;
20
21
  if (explicit !== undefined && typeof explicit !== 'boolean')
21
22
  return undefined;
@@ -28,30 +29,35 @@ export function mutationFailure(input) {
28
29
  return undefined;
29
30
  return input;
30
31
  }
32
+ const rule = code === 'MUTATION_POST_STATE_INVALID' ? reason.rule : undefined;
31
33
  return Object.assign(new Error(input.message, { cause: input }), {
32
34
  code,
33
35
  outcomeKnown: explicit !== false && !UNKNOWN_OUTCOME_CODES.has(code),
36
+ ...(rule === undefined ? {} : { rule }),
34
37
  });
35
38
  }
36
- function mutationCode(input) {
39
+ /**
40
+ * The Mutation refusal an error carries, on itself, as its Kernel reason, or as its protocol data:
41
+ * its code and, from the same carrier's details, the post-state rule it names.
42
+ */
43
+ function mutationReason(input) {
37
44
  const value = input;
38
45
  if (value.code === 'STATE_TRANSITION_ILLEGAL')
39
- return value.code;
40
- for (const candidate of [
41
- value.code,
42
- directOrNestedCode(value.reason),
43
- directOrNestedCode(value.data),
44
- ]) {
45
- if (typeof candidate === 'string' && candidate.startsWith('MUTATION_')) {
46
- return candidate;
47
- }
46
+ return { code: value.code };
47
+ for (const carrier of [value, value.reason, value.data]) {
48
+ const code = typeof carrier === 'string' ? carrier : field(carrier, 'code');
49
+ if (typeof code !== 'string' || !code.startsWith('MUTATION_'))
50
+ continue;
51
+ const rule = field(field(carrier, 'details'), 'rule');
52
+ return {
53
+ code: code,
54
+ ...(typeof rule === 'string' ? { rule } : {}),
55
+ };
48
56
  }
49
57
  return undefined;
50
58
  }
51
- function directOrNestedCode(input) {
52
- return typeof input === 'string'
53
- ? input
54
- : input !== null && typeof input === 'object' && !Array.isArray(input)
55
- ? Reflect.get(input, 'code')
56
- : undefined;
59
+ function field(input, key) {
60
+ return input !== null && typeof input === 'object' && !Array.isArray(input)
61
+ ? Reflect.get(input, key)
62
+ : undefined;
57
63
  }
@@ -1,5 +1,5 @@
1
+ import { definedClass } from '@astrale-os/kernel-client/graph';
1
2
  import { Query, QueryAST as QueryDocument } from '@astrale-os/kernel-core/graph/query';
2
- import { ClassKey } from '@astrale-os/kernel-dsl/v1/addressing';
3
3
  import { QueryCompositionError } from '../composite/errors.js';
4
4
  import { collectionIdentityOf } from './identity.js';
5
5
  import { COLLECTION_QUERY_LIMITS } from './limits.js';
@@ -80,8 +80,13 @@ export function partitionCollectionUnion(domain, compiled, values, maximumLogica
80
80
  if (identities.has(identity))
81
81
  invalid(`Collection union returned duplicate identity ${identity}.`);
82
82
  identities.add(identity);
83
- const names = memberships.get(value.class) ?? memberNames(domain, compiled, value);
83
+ const names = memberships.has(value.class)
84
+ ? memberships.get(value.class)
85
+ : memberNames(domain, compiled, value);
84
86
  memberships.set(value.class, names);
87
+ // A Class added after this client was built belongs to no member it can name.
88
+ if (names === undefined)
89
+ continue;
85
90
  if (names.length === 0) {
86
91
  invalid(`Collection union returned ${value.class} outside every member extent.`);
87
92
  }
@@ -105,9 +110,11 @@ function realizeMember(definition, domain, outputPath, name) {
105
110
  }
106
111
  }
107
112
  function memberNames(domain, compiled, value) {
108
- const exact = domain.definition(ClassKey.ref(ClassKey(value.class)));
113
+ const exact = definedClass(domain, value.class);
114
+ if (exact === undefined)
115
+ return undefined;
109
116
  const kind = valueKind(value);
110
- if (exact?.ref.kind !== 'class' || exact.kind !== kind || exact.abstract) {
117
+ if (exact.kind !== kind || exact.abstract) {
111
118
  invalid(`Collection union returned inactive ${kind} Class ${value.class}.`);
112
119
  }
113
120
  const names = compiled.members.flatMap((member) => {
@@ -0,0 +1,15 @@
1
+ import type { QueryResult } from '@astrale-os/kernel-core/graph/query';
2
+ import type { Edge } from '../../platform/graph/edge/index.js';
3
+ import type { Node } from '../../platform/graph/node/index.js';
4
+ import type { Domain } from '../../platform/schema/index.js';
5
+ /**
6
+ * The members of `values` whose Class `domain` defines, in order. The Kernel answers in its active
7
+ * Schema, so a result may hold values of a Class added after the client was built.
8
+ */
9
+ export declare function definedValues<Value extends Node | Edge>(domain: Domain, values: readonly Value[]): readonly Value[];
10
+ /**
11
+ * Check every value of `result` against the Domain this client was built from, by the Kernel
12
+ * Client's rule: a value fails only when a Property its Class defines holds an invalid value or a
13
+ * required one is absent. Properties and Classes the client does not know pass.
14
+ */
15
+ export declare function requireConformance(domain: Domain, result: QueryResult): void;
@@ -0,0 +1,46 @@
1
+ import { definedClass, validateDefinedProperties } from '@astrale-os/kernel-client/graph';
2
+ import { collectionIdentityOf } from './collection/identity.js';
3
+ import { QueryValueMismatchError } from './failure.js';
4
+ /**
5
+ * The members of `values` whose Class `domain` defines, in order. The Kernel answers in its active
6
+ * Schema, so a result may hold values of a Class added after the client was built.
7
+ */
8
+ export function definedValues(domain, values) {
9
+ const defined = values.filter((value) => definedClass(domain, value.class) !== undefined);
10
+ return defined.length === values.length ? values : Object.freeze(defined);
11
+ }
12
+ /**
13
+ * Check every value of `result` against the Domain this client was built from, by the Kernel
14
+ * Client's rule: a value fails only when a Property its Class defines holds an invalid value or a
15
+ * required one is absent. Properties and Classes the client does not know pass.
16
+ */
17
+ export function requireConformance(domain, result) {
18
+ for (const value of valuesOf(result)) {
19
+ const exact = definedClass(domain, value.class);
20
+ if (exact === undefined)
21
+ continue;
22
+ const [diagnostic] = validateDefinedProperties(exact, value.props);
23
+ if (diagnostic !== undefined) {
24
+ throw new QueryValueMismatchError(`${collectionIdentityOf(value)} at ${diagnostic.pointer}: ${diagnostic.message}`);
25
+ }
26
+ }
27
+ }
28
+ function* valuesOf(result) {
29
+ if (result.kind === 'graph') {
30
+ yield* result.graph.nodes;
31
+ yield* result.graph.edges;
32
+ return;
33
+ }
34
+ if (result.kind === 'nodes') {
35
+ for (const item of result.nodes)
36
+ if (item.kind === 'value')
37
+ yield item.value;
38
+ return;
39
+ }
40
+ for (const item of result.edges) {
41
+ for (const component of [item.edge, item.source, item.target]) {
42
+ if (component.kind === 'value')
43
+ yield component.value;
44
+ }
45
+ }
46
+ }
@@ -3,6 +3,7 @@ import { isDomain } from '@astrale-os/kernel-dsl/v1/domain';
3
3
  import { requireUniqueCollectionValues } from './collection/identity.js';
4
4
  import { COLLECTION_QUERY_LIMITS } from './collection/limits.js';
5
5
  import { realizeCollectionQuery, registerCollectionQuery, } from './collection/metadata.js';
6
+ import { definedValues } from './conformance.js';
6
7
  import { queryResult } from './single/index.js';
7
8
  const DEFAULT_PAGE_SIZE = 256;
8
9
  const MAXIMUM_PAGES = 1_024;
@@ -27,7 +28,7 @@ export function defineCollectionQuery() {
27
28
  kind: 'nodes',
28
29
  projection: { kind: 'value' },
29
30
  }),
30
- project: (pages) => requireUniqueCollectionValues(queryResult.completeNodeValues(pages, specification.id), `Collection Query ${specification.id}`),
31
+ project: (pages) => definedValues(domain, requireUniqueCollectionValues(queryResult.completeNodeValues(pages, specification.id), `Collection Query ${specification.id}`)),
31
32
  };
32
33
  }
33
34
  return {
@@ -38,12 +39,12 @@ export function defineCollectionQuery() {
38
39
  kind: 'edges',
39
40
  projection: { edge: 'value', source: 'reference', target: 'reference' },
40
41
  }),
41
- project: (pages) => requireUniqueCollectionValues(Object.freeze(pages.flatMap((page) => queryResult.edgeItems(page, specification.id).map((item) => {
42
+ project: (pages) => definedValues(domain, requireUniqueCollectionValues(Object.freeze(pages.flatMap((page) => queryResult.edgeItems(page, specification.id).map((item) => {
42
43
  if (item.edge.kind !== 'value') {
43
44
  throw new TypeError(`Collection Query ${specification.id} must project Edge values.`);
44
45
  }
45
46
  return item.edge.value;
46
- }))), `Collection Query ${specification.id}`),
47
+ }))), `Collection Query ${specification.id}`)),
47
48
  };
48
49
  });
49
50
  registerCollectionQuery(definition, project);
@@ -6,7 +6,6 @@ import type { QueryDefinition } from '../query.js';
6
6
  export interface QueryClient {
7
7
  query<Ast extends QueryAST>(ast: Ast, options: {
8
8
  readonly page: QueryPageRequest;
9
- readonly expected?: Domain['closure'];
10
9
  }): Promise<QueryResponse<QueryResultFor<Ast>>>;
11
10
  }
12
11
  export declare function executeQuery<DomainValue extends Domain, Output>(client: QueryClient, domain: DomainValue, query: QueryDefinition<DomainValue, void, Output>): Promise<Output>;
@@ -1,6 +1,7 @@
1
1
  import { COLLECTION_QUERY_LIMITS } from '../collection/limits.js';
2
2
  import { compileCollectionUnion, partitionCollectionUnion } from '../collection/union.js';
3
3
  import { createQueryComposition, executeQueryPlan } from '../composite/index.js';
4
+ import { requireConformance } from '../conformance.js';
4
5
  import { realizeQuery } from '../define.js';
5
6
  import { queryFailure } from '../failure.js';
6
7
  import { completeSelection, queryResult } from '../single/index.js';
@@ -9,7 +10,12 @@ export async function executeQuery(client, domain, query, input) {
9
10
  if (client === null || typeof client !== 'object' || typeof client.query !== 'function') {
10
11
  throw new TypeError('Query execution requires a Client query capability.');
11
12
  }
12
- const execute = (ast, options) => client.query(ast, { ...options, expected: domain.closure });
13
+ // The Kernel answers in its active Schema; each value is checked against this client's Domain.
14
+ const execute = async (ast, options) => {
15
+ const response = await client.query(ast, options);
16
+ requireConformance(domain, response.result);
17
+ return response;
18
+ };
13
19
  const operation = realizeQuery(query, domain);
14
20
  try {
15
21
  if (operation.kind === 'single') {
@@ -7,4 +7,12 @@ export declare class QueryPaginationError extends Error implements QueryFailure
7
7
  readonly name = "QueryPaginationError";
8
8
  constructor(code: QueryPaginationErrorCode, message: string);
9
9
  }
10
+ /**
11
+ * A value the Kernel returned does not have the shape its Class has in the Domain this client was
12
+ * built from: one of its Properties changed type, or a Property the client requires is absent.
13
+ */
14
+ export declare class QueryValueMismatchError extends Error implements QueryFailure {
15
+ readonly name = "QueryValueMismatchError";
16
+ readonly code = "QUERY_VALUE_MISMATCH";
17
+ }
10
18
  export declare function queryFailure(input: unknown): QueryFailure | undefined;
@@ -6,6 +6,14 @@ export class QueryPaginationError extends Error {
6
6
  this.code = code;
7
7
  }
8
8
  }
9
+ /**
10
+ * A value the Kernel returned does not have the shape its Class has in the Domain this client was
11
+ * built from: one of its Properties changed type, or a Property the client requires is absent.
12
+ */
13
+ export class QueryValueMismatchError extends Error {
14
+ name = 'QueryValueMismatchError';
15
+ code = 'QUERY_VALUE_MISMATCH';
16
+ }
9
17
  export function queryFailure(input) {
10
18
  if (!(input instanceof Error))
11
19
  return undefined;
@@ -1,7 +1,7 @@
1
1
  export { defineCollectionQuery, defineCompositeQuery, defineQuery, realizeQuery } from './define.js';
2
2
  export type { CollectionQuerySpec } from './collection/metadata.js';
3
3
  export { executeQuery, type QueryClient, type QueryExecutor } from './execution/index.js';
4
- export { QueryPaginationError, type QueryFailure, type QueryPaginationErrorCode, } from './failure.js';
4
+ export { QueryPaginationError, QueryValueMismatchError, type QueryFailure, type QueryPaginationErrorCode, } from './failure.js';
5
5
  export type { CollectionQueryDefinition, CollectionValue, CompositeQueryDefinition, QueryAstOf, QueryDefinition, QueryInputOf, QueryOutputOf, SingleQueryDefinition, } from './query.js';
6
6
  export { queryResult } from './single/index.js';
7
7
  export type { CompleteQueryProjectionContext, PaginatedQuery, ProjectedQuery, QueryPages, QueryPagination, QueryProjectionContext, RawQuery, SingleQuery, } from './single/index.js';
@@ -1,6 +1,6 @@
1
1
  export { defineCollectionQuery, defineCompositeQuery, defineQuery, realizeQuery } from './define.js';
2
2
  export { executeQuery } from './execution/index.js';
3
- export { QueryPaginationError, } from './failure.js';
3
+ export { QueryPaginationError, QueryValueMismatchError, } from './failure.js';
4
4
  export { queryResult } from './single/index.js';
5
5
  /** Canonical Query language and authoring values accept DSL ResolvedClass directly. */
6
6
  export * from '@astrale-os/kernel-core/graph/query';
@@ -1,5 +1,5 @@
1
1
  export { defineWorkflow } 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 { Workflow, WorkflowAddress, WorkflowDefinition, WorkflowOptions } from './workflow.js';
5
5
  export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, } from '../error/index.js';
@@ -12,4 +12,4 @@ export type InlineWorkflowContext<Callable extends ResolvedCallable, DomainValue
12
12
  * Exact Workflow context for one callable: engine-journaled when its Schema declares
13
13
  * `execution: { durable: true }`, otherwise run inside its invocation.
14
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>;
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, Definitions, Errors> : InlineWorkflowContext<Callable, DomainValue, Definitions, Errors>;