@astrale-os/kernel-dsl 1.0.0-beta.3 → 1.0.0-beta.5

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 (34) hide show
  1. package/dist/v1/builder/callable/authoring.d.ts +10 -4
  2. package/dist/v1/builder/callable/authoring.js +2 -0
  3. package/dist/v1/builder/lowering/callable.js +1 -0
  4. package/dist/v1/builder/schema/projection/callable.d.ts +7 -5
  5. package/dist/v1/builder/schema/projection/model.d.ts +4 -2
  6. package/dist/v1/builder/schema/projection/resolved-class.d.ts +2 -1
  7. package/dist/v1/compiled/admission/semantics/class.js +6 -1
  8. package/dist/v1/compiled/admission/wire/callable.js +11 -1
  9. package/dist/v1/compiled/compiler/compile.js +2 -0
  10. package/dist/v1/domain/create.js +1 -0
  11. package/dist/v1/domain/projection/classes.js +3 -0
  12. package/dist/v1/index.d.ts +1 -0
  13. package/dist/v1/language/callable/admission.d.ts +15 -0
  14. package/dist/v1/language/callable/admission.js +43 -0
  15. package/dist/v1/language/callable/model/callable.d.ts +10 -0
  16. package/dist/v1/language/callable/resolved.d.ts +5 -1
  17. package/dist/v1/language/index.d.ts +1 -0
  18. package/dist/v1/language/index.js +1 -0
  19. package/dist/v1/language/value/evaluate/validate.js +4 -0
  20. package/dist/v1/language/value/evaluate/validity.optimization.d.ts +14 -0
  21. package/dist/v1/language/value/evaluate/validity.optimization.js +342 -0
  22. package/dist/v1/schema/compatibility/fields.d.ts +11 -3
  23. package/dist/v1/schema/compatibility/fields.js +6 -3
  24. package/dist/v1/schema/diagnostic.d.ts +1 -1
  25. package/dist/v1/schema/normalization/domain.js +2 -0
  26. package/dist/v1/schema/validation/callable.d.ts +1 -1
  27. package/dist/v1/schema/validation/callable.js +17 -2
  28. package/dist/v1/schema/validation/class.js +7 -1
  29. package/dist/v1/schema/validation/domain.structure.js +5 -0
  30. package/dist/value/canonical.d.ts +5 -0
  31. package/dist/value/canonical.js +12 -0
  32. package/dist/value/json.d.ts +1 -1
  33. package/dist/value/json.js +1 -1
  34. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import type { Value as PortableValue } from '@astrale-os/kernel-dsl/value';
2
2
  import type { z } from 'zod';
3
3
  import type { PolicyRef } from '../../addressing/index.js';
4
- import type { BinaryInputProjection, CallableExecution, FunctionAuth } from '../../language/callable/model/callable.js';
4
+ import type { BinaryInputProjection, CallableAdmission, CallableExecution, FunctionAuth } from '../../language/callable/model/callable.js';
5
5
  import type { PolicyCheckExpression, PolicyCheckObject, PolicyObjectRef } from '../../language/policy/model/policy.js';
6
6
  import type { Class } from '../class/authoring.js';
7
7
  import type { ValueSchemaInput } from '../class/property.js';
@@ -62,6 +62,8 @@ export interface CallableInputBase<Input extends FunctionInputSchema = ValueSche
62
62
  readonly output: CallableOutputInput;
63
63
  /** `{ durable: true }` makes an invocation return an Execution reference. */
64
64
  readonly execution?: CallableExecution;
65
+ /** `{ maxAge }`: how old, in seconds, the admission a call is served under may be. */
66
+ readonly admission?: CallableAdmission;
65
67
  }
66
68
  type ConcreteCallableInput<Input extends FunctionInputSchema = ValueSchemaInput> = CallableInputBase<Input> & ({
67
69
  readonly auth: 'anonymous';
@@ -83,7 +85,7 @@ export type MethodInput = ConcreteCallableInput & {
83
85
  readonly abstract?: boolean;
84
86
  };
85
87
  export type CallableInput = FunctionInput | MethodInput;
86
- export interface CallableProjection<out Input = unknown, out Output = unknown, out Auth extends FunctionAuth = FunctionAuth, out Static extends boolean = boolean, out Abstract extends boolean = boolean, out Execution extends CallableExecution | undefined = CallableExecution | undefined> {
88
+ export interface CallableProjection<out Input = unknown, out Output = unknown, out Auth extends FunctionAuth = FunctionAuth, out Static extends boolean = boolean, out Abstract extends boolean = boolean, out Execution extends CallableExecution | undefined = CallableExecution | undefined, out Admission extends CallableAdmission | undefined = CallableAdmission | undefined> {
87
89
  readonly [CALLABLE_PROJECTION_VALUE]: true;
88
90
  readonly input: Input;
89
91
  readonly output: Output;
@@ -91,6 +93,7 @@ export interface CallableProjection<out Input = unknown, out Output = unknown, o
91
93
  readonly static: Static;
92
94
  readonly abstract: Abstract;
93
95
  readonly execution: Execution;
96
+ readonly admission: Admission;
94
97
  }
95
98
  type ValueInputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Value : Schema extends z.ZodType ? z.input<Schema> : PortableValue;
96
99
  type FunctionInputOf<Schema> = Schema extends BinaryInputAuthoring<infer Metadata> ? BinaryInputProjection<ValueInputOf<Metadata>> : ValueInputOf<Schema>;
@@ -98,6 +101,9 @@ type ValueOutputOf<Schema> = Schema extends TypedValueSchema<infer Value> ? Valu
98
101
  type ExecutionOfConfig<Config extends CallableInput> = Config extends {
99
102
  readonly execution: infer Execution extends CallableExecution;
100
103
  } ? Execution : undefined;
104
+ type AdmissionOfConfig<Config extends CallableInput> = Config extends {
105
+ readonly admission: infer Admission extends CallableAdmission;
106
+ } ? Admission : undefined;
101
107
  type CallableOutputOf<Input extends CallableOutputInput> = Input extends StreamOutput<infer Item> ? {
102
108
  readonly mode: 'stream';
103
109
  readonly item: ValueOutputOf<Item>;
@@ -120,12 +126,12 @@ export type ProjectionOfCallable<Value extends Function | Method> = Value extend
120
126
  export declare function callableConfiguration(value: Function | Method): CallableInput;
121
127
  type PolicyObjectAuthoring = Class | Function | Method | Policy | View | CoreNode;
122
128
  export type PolicyObjectInput = Lazy<PolicyObjectAuthoring | PolicyObjectRef>;
123
- export declare function func<const Config extends FunctionInput, Input = FunctionInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>>(config: Config): Function<CallableProjection<Input, Output, Auth, false, false, Execution>>;
129
+ export declare function func<const Config extends FunctionInput, Input = FunctionInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>, Admission extends CallableAdmission | undefined = AdmissionOfConfig<Config>>(config: Config): Function<CallableProjection<Input, Output, Auth, false, false, Execution, Admission>>;
124
130
  export declare function method<const Config extends MethodInput, Input = ValueInputOf<Config['input']>, Output = CallableOutputOf<Config['output']>, Auth extends FunctionAuth = Config['auth'], Static extends boolean = Config extends {
125
131
  readonly static: true;
126
132
  } ? true : false, Abstract extends boolean = Config extends {
127
133
  readonly abstract: infer Value extends boolean;
128
- } ? Value : false, Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>>(config: Config): Method<CallableProjection<Input, Output, Auth, Static, Abstract, Execution>>;
134
+ } ? Value : false, Execution extends CallableExecution | undefined = ExecutionOfConfig<Config>, Admission extends CallableAdmission | undefined = AdmissionOfConfig<Config>>(config: Config): Method<CallableProjection<Input, Output, Auth, Static, Abstract, Execution, Admission>>;
129
135
  export declare function callableInputField(name: string): Extract<PolicyCheckObject, {
130
136
  readonly kind: 'input';
131
137
  }>;
@@ -18,6 +18,7 @@ export function func(config) {
18
18
  'static',
19
19
  'abstract',
20
20
  'execution',
21
+ 'admission',
21
22
  ]);
22
23
  const value = authoringValue('function', {});
23
24
  callableInputs.set(value, { ...config });
@@ -33,6 +34,7 @@ export function method(config) {
33
34
  'static',
34
35
  'abstract',
35
36
  'execution',
37
+ 'admission',
36
38
  ]);
37
39
  const value = authoringValue('method', {});
38
40
  callableInputs.set(value, { ...config });
@@ -42,6 +42,7 @@ export class V1CallableCompiler {
42
42
  input,
43
43
  output,
44
44
  ...(config.execution === undefined ? {} : { execution: config.execution }),
45
+ ...(config.admission === undefined ? {} : { admission: config.admission }),
45
46
  };
46
47
  const contract = auth === 'authorized'
47
48
  ? { ...base, auth, ...(policy === undefined ? {} : { policy }) }
@@ -18,6 +18,7 @@ type CallableConfig<Value> = Value extends Method<infer Config> ? Config extends
18
18
  type CallableInputValue<Value extends Method | Function> = CallableConfig<Value>['input'];
19
19
  type CallableOutputValue<Value extends Method | Function> = CallableConfig<Value>['output'];
20
20
  type CallableExecutionOf<Value extends Method | Function> = CallableConfig<Value>['execution'];
21
+ type CallableAdmissionOf<Value extends Method | Function> = CallableConfig<Value>['admission'];
21
22
  type CallableStaticOf<Value extends Method> = CallableConfig<Value> extends {
22
23
  readonly static: true;
23
24
  } ? true : false;
@@ -25,11 +26,11 @@ type CallableAbstractOf<Value extends Method> = CallableConfig<Value> extends {
25
26
  readonly abstract: infer Abstract extends boolean;
26
27
  } ? Abstract : false;
27
28
  type CallableAuthOf<Value extends Method | Function> = CallableConfig<Value>['auth'];
28
- type MethodProjectionOfEntry<Origin extends string, OwnerName extends string, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodEntry<infer Owner, infer Name, infer Value> ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>, CallableAbstractOf<Value>, CallableAuthOf<Value>, Name, language.MethodKey<Origin, OwnerName, Name>, MethodExecutableOf<AbstractOf<ClassInputOf<Owner>>, CallableAbstractOf<Value>>, CallableExecutionOf<Value>> : never;
29
+ type MethodProjectionOfEntry<Origin extends string, OwnerName extends string, Entry> = Entry extends language.ResolvedMethod ? MethodProjectionOfResolved<Entry> : Entry extends AuthoredMethodEntry<infer Owner, infer Name, infer Value> ? MethodProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableStaticOf<Value>, CallableAbstractOf<Value>, CallableAuthOf<Value>, Name, language.MethodKey<Origin, OwnerName, Name>, MethodExecutableOf<AbstractOf<ClassInputOf<Owner>>, CallableAbstractOf<Value>>, CallableExecutionOf<Value>, CallableAdmissionOf<Value>> : never;
29
30
  type MethodProjectionOfEntries<Origin extends string, OwnerName extends string, Entries> = Entries extends unknown ? MethodProjectionOfEntry<Origin, OwnerName, Entries> : never;
30
31
  type MethodProjectionOfResolved<Value> = Value extends language.ResolvedMethod<infer Input, infer Output, infer Static, infer Abstract, infer Auth, infer _Policy, infer Key> & {
31
32
  readonly name: infer Name extends string;
32
- } ? MethodProjection<Input, Output, Static, Abstract, Auth, Name, Key, Value['executable'], Value['execution']> : never;
33
+ } ? MethodProjection<Input, Output, Static, Abstract, Auth, Name, Key, Value['executable'], Value['execution'], Value['admission']> : never;
33
34
  /** Capture each Method through its qualified lookup key. */
34
35
  export type MethodsOfCollection<Value> = Value extends {
35
36
  readonly byKey: infer ByKey extends Readonly<Record<language.MethodKey, language.ResolvedMethod>>;
@@ -41,11 +42,12 @@ export type OwnStaticMethodProjectionsOf<Origin extends string, Name extends str
41
42
  /** Bind an authored Function to its exact Domain coordinates. */
42
43
  export type FunctionProjectionOfAuthoring<Origin extends string, Name extends string, Value> = Value extends Function ? FunctionProjection<CallableInputValue<Value>, CallableOutputValue<Value>, CallableAuthOf<Value>, language.FunctionRef<Name> & {
43
44
  readonly origin: Origin;
44
- }, language.DefinitionKey<Origin, 'function', Name>, CallableExecutionOf<Value>> : never;
45
- /** Restore a resolved Function while preserving its execution modifier. */
46
- export type ResolvedFunctionOfProjection<Value extends FunctionProjection> = Value extends FunctionProjection<infer Input, infer Output, infer Auth, infer Ref, infer Key, infer Execution> ? language.ResolvedFunction<Input, Output, Auth, language.CallablePolicy> & {
45
+ }, language.DefinitionKey<Origin, 'function', Name>, CallableExecutionOf<Value>, CallableAdmissionOf<Value>> : never;
46
+ /** Restore a resolved Function while preserving its execution modifier and admission age. */
47
+ export type ResolvedFunctionOfProjection<Value extends FunctionProjection> = Value extends FunctionProjection<infer Input, infer Output, infer Auth, infer Ref, infer Key, infer Execution, infer Admission> ? language.ResolvedFunction<Input, Output, Auth, language.CallablePolicy> & {
47
48
  readonly ref: Ref;
48
49
  readonly key: Key;
49
50
  readonly execution: Execution;
51
+ readonly admission: Admission;
50
52
  } : never;
51
53
  export {};
@@ -10,7 +10,7 @@ export interface PropertyProjection<Value = unknown, Required extends boolean =
10
10
  readonly input: Input;
11
11
  }
12
12
  /** A Method’s exact callable, ownership and executable contracts. */
13
- export interface MethodProjection<Input = unknown, Output = unknown, Static extends boolean = boolean, Abstract extends boolean = boolean, Auth extends language.FunctionAuth = language.FunctionAuth, Name extends string = string, Key extends language.MethodKey = language.MethodKey, Executable extends boolean = boolean, Execution extends language.CallableExecution | undefined = language.CallableExecution | undefined> {
13
+ export interface MethodProjection<Input = unknown, Output = unknown, Static extends boolean = boolean, Abstract extends boolean = boolean, Auth extends language.FunctionAuth = language.FunctionAuth, Name extends string = string, Key extends language.MethodKey = language.MethodKey, Executable extends boolean = boolean, Execution extends language.CallableExecution | undefined = language.CallableExecution | undefined, Admission extends language.CallableAdmission | undefined = language.CallableAdmission | undefined> {
14
14
  readonly input: Input;
15
15
  readonly output: Output;
16
16
  readonly static: Static;
@@ -20,15 +20,17 @@ export interface MethodProjection<Input = unknown, Output = unknown, Static exte
20
20
  readonly name: Name;
21
21
  readonly key: Key;
22
22
  readonly execution: Execution;
23
+ readonly admission: Admission;
23
24
  }
24
25
  /** A Function’s exact callable and declaration contracts. */
25
- export interface FunctionProjection<Input = unknown, Output = unknown, Auth extends language.FunctionAuth = language.FunctionAuth, Ref extends language.FunctionRef = language.FunctionRef, Key extends language.DefinitionKey<string, 'function'> = language.DefinitionKey<string, 'function'>, Execution extends language.CallableExecution | undefined = language.CallableExecution | undefined> {
26
+ export interface FunctionProjection<Input = unknown, Output = unknown, Auth extends language.FunctionAuth = language.FunctionAuth, Ref extends language.FunctionRef = language.FunctionRef, Key extends language.DefinitionKey<string, 'function'> = language.DefinitionKey<string, 'function'>, Execution extends language.CallableExecution | undefined = language.CallableExecution | undefined, Admission extends language.CallableAdmission | undefined = language.CallableAdmission | undefined> {
26
27
  readonly input: Input;
27
28
  readonly output: Output;
28
29
  readonly auth: Auth;
29
30
  readonly ref: Ref;
30
31
  readonly key: Key;
31
32
  readonly execution: Execution;
33
+ readonly admission: Admission;
32
34
  }
33
35
  /** A compact Class witness with effective members and exact self facets. */
34
36
  export interface ClassProjection<Kind extends language.ClassKind = language.ClassKind, Definition extends Extract<language.ClassDefinition, {
@@ -56,10 +56,11 @@ type ProjectionPropertyByName<Entries extends PropertyProjection> = Readonly<{
56
56
  /** Restore qualified lookup and unambiguous named Property access. */
57
57
  type PropertyCollectionOfProjection<Entries extends PropertyProjection> = language.ResolvedPropertyCollection<ProjectionPropertyByKey<Entries>, ProjectionPropertyByName<Entries>>;
58
58
  type MethodCollectionOperation = 'byKey' | 'get';
59
- type ResolvedMethodOfProjection<Value> = Value extends MethodProjection<infer Input, infer Output, infer Static, infer Abstract, infer Auth, infer Name, infer Key, infer Executable, infer Execution> ? language.ResolvedMethod<Input, Output, Static, Abstract, Auth, language.CallablePolicy, Key> & {
59
+ type ResolvedMethodOfProjection<Value> = Value extends MethodProjection<infer Input, infer Output, infer Static, infer Abstract, infer Auth, infer Name, infer Key, infer Executable, infer Execution, infer Admission> ? language.ResolvedMethod<Input, Output, Static, Abstract, Auth, language.CallablePolicy, Key> & {
60
60
  readonly name: Name;
61
61
  readonly executable: Executable;
62
62
  readonly execution: Execution;
63
+ readonly admission: Admission;
63
64
  } : never;
64
65
  type ProjectionMethodByKey<Entries extends MethodProjection> = Readonly<{
65
66
  [Key in Entries['key']]: ResolvedMethodOfProjection<Extract<Entries, {
@@ -116,9 +116,14 @@ export function validateClassFacets(context) {
116
116
  }
117
117
  if (located.value.kind === 'node') {
118
118
  for (const [index, method] of located.value.methods.entries()) {
119
- if (!isMethodExecutable(located.value, method) && method.policy !== undefined) {
119
+ if (isMethodExecutable(located.value, method))
120
+ continue;
121
+ if (method.policy !== undefined) {
120
122
  fail(`${located.pointer}/methods/${index}/policy`, 'An abstract Method cannot have a Policy.');
121
123
  }
124
+ if (method.admission !== undefined) {
125
+ fail(`${located.pointer}/methods/${index}/admission`, 'An abstract Method cannot have an admission age.');
126
+ }
122
127
  }
123
128
  }
124
129
  }
@@ -1,4 +1,5 @@
1
1
  import {} from '@astrale-os/kernel-dsl/value';
2
+ import { isAdmissionMaxAge } from '../../../language/callable/admission.js';
2
3
  import {} from '../../model/schema.js';
3
4
  import { invalid, pointer } from '../diagnostic.js';
4
5
  import { callablePolicy } from './policy.js';
@@ -16,7 +17,7 @@ export function compiledCallable(input, values, method, path) {
16
17
  const value = object(input, 'Compiled Callable', path);
17
18
  exact(value, method
18
19
  ? ['name', 'auth', 'static', 'abstract', 'input', 'output']
19
- : ['ref', 'auth', 'input', 'output'], 'Compiled Callable', path, ['policy', 'description', 'execution', 'fields']);
20
+ : ['ref', 'auth', 'input', 'output'], 'Compiled Callable', path, ['policy', 'description', 'execution', 'admission', 'fields']);
20
21
  if (method) {
21
22
  memberName(value.name, 'Method name', `${path}/name`);
22
23
  boolean(value.static, 'Method static', `${path}/static`);
@@ -87,4 +88,13 @@ export function compiledCallable(input, values, method, path) {
87
88
  invalid('durable Callable output', 'COMPILED_SOURCE_INVALID', `${outputPath}/mode`);
88
89
  }
89
90
  }
91
+ if (value.admission !== undefined) {
92
+ const admissionPath = `${path}/admission`;
93
+ const admission = object(value.admission, 'Callable admission', admissionPath);
94
+ exact(admission, ['maxAge'], 'Callable admission', admissionPath);
95
+ // An absent admission age is the only spelling of a call admitted afresh.
96
+ if (!isAdmissionMaxAge(admission.maxAge)) {
97
+ invalid('Callable admission age', 'COMPILED_SOURCE_INVALID', `${admissionPath}/maxAge`);
98
+ }
99
+ }
90
100
  }
@@ -89,6 +89,7 @@ function compileEntry(schema, lookup) {
89
89
  static: method.static,
90
90
  abstract: method.abstract,
91
91
  ...(method.execution === undefined ? {} : { execution: method.execution }),
92
+ ...(method.admission === undefined ? {} : { admission: method.admission }),
92
93
  input,
93
94
  ...(fields === undefined ? {} : { fields }),
94
95
  output: output(method.output, `${pointer}/methods/${name}/output`),
@@ -117,6 +118,7 @@ function compileEntry(schema, lookup) {
117
118
  ...(definition.policy === undefined ? {} : { policy: definition.policy }),
118
119
  ...(definition.description === undefined ? {} : { description: definition.description }),
119
120
  ...(definition.execution === undefined ? {} : { execution: definition.execution }),
121
+ ...(definition.admission === undefined ? {} : { admission: definition.admission }),
120
122
  input,
121
123
  ...(fields === undefined ? {} : { fields }),
122
124
  output: output(definition.output, `/functions/${definition.ref.name}/output`),
@@ -130,6 +130,7 @@ function createDefinitions(declarations, classesByKey, input) {
130
130
  input: definition.input,
131
131
  output: definition.output,
132
132
  ...(definition.execution === undefined ? {} : { execution: definition.execution }),
133
+ ...(definition.admission === undefined ? {} : { admission: definition.admission }),
133
134
  auth: definition.auth,
134
135
  policy: definition.policy,
135
136
  ...(inputFields === undefined ? {} : { inputFields }),
@@ -70,6 +70,9 @@ export function createClasses(definitions, validation) {
70
70
  ...(methodDefinition.execution === undefined
71
71
  ? {}
72
72
  : { execution: methodDefinition.execution }),
73
+ ...(methodDefinition.admission === undefined
74
+ ? {}
75
+ : { admission: methodDefinition.admission }),
73
76
  auth: methodDefinition.auth,
74
77
  policy: methodDefinition.policy,
75
78
  static: methodDefinition.static,
@@ -14,6 +14,7 @@ export { isDomain } from './domain/index.js';
14
14
  export type * as builder from './builder/index.js';
15
15
  export type { PathAuthoring, TypedValueSchema } from './builder/index.js';
16
16
  export type { PropertyValues as PropertyValuesValue } from './language/index.js';
17
+ export type { BlobReceipt, BlobRef } from './language/value/model/blob.js';
17
18
  export type { CompiledSchema } from './compiled/index.js';
18
19
  export * as schema from './schema/index.js';
19
20
  export * as bundle from './bundle/index.js';
@@ -0,0 +1,15 @@
1
+ import type { CallablePolicy } from '../policy/model/policy.js';
2
+ /**
3
+ * The language ceiling of a declared admission age, in seconds. Raising it later widens the
4
+ * language; lowering it would refuse accepted Schemas.
5
+ */
6
+ export declare const MAXIMUM_ADMISSION_AGE_SECONDS = 300;
7
+ /** Whether `value` is a declarable admission age: an integer from 1 to the language ceiling. */
8
+ export declare function isAdmissionMaxAge(value: unknown): value is number;
9
+ /**
10
+ * The input fields an admission of this callable reads: the `field` of every `input` check object
11
+ * of its Policy, unique and in Unicode code-point order. A callable without Policy checks reads no
12
+ * input field. Two calls that agree on these fields, and on everything else the admission reads,
13
+ * receive the same admission.
14
+ */
15
+ export declare function admissionInputFields(policy: CallablePolicy | undefined): readonly string[];
@@ -0,0 +1,43 @@
1
+ import { compareUnicode } from '../../addressing/ordering.js';
2
+ /**
3
+ * The language ceiling of a declared admission age, in seconds. Raising it later widens the
4
+ * language; lowering it would refuse accepted Schemas.
5
+ */
6
+ export const MAXIMUM_ADMISSION_AGE_SECONDS = 300;
7
+ /** Whether `value` is a declarable admission age: an integer from 1 to the language ceiling. */
8
+ export function isAdmissionMaxAge(value) {
9
+ return (typeof value === 'number' &&
10
+ Number.isInteger(value) &&
11
+ value >= 1 &&
12
+ value <= MAXIMUM_ADMISSION_AGE_SECONDS);
13
+ }
14
+ /**
15
+ * The input fields an admission of this callable reads: the `field` of every `input` check object
16
+ * of its Policy, unique and in Unicode code-point order. A callable without Policy checks reads no
17
+ * input field. Two calls that agree on these fields, and on everything else the admission reads,
18
+ * receive the same admission.
19
+ */
20
+ export function admissionInputFields(policy) {
21
+ if (policy === undefined || policy === true)
22
+ return Object.freeze([]);
23
+ const fields = new Set();
24
+ collectInputFields(policy, fields);
25
+ return Object.freeze([...fields].sort(compareUnicode));
26
+ }
27
+ function collectInputFields(expression, fields) {
28
+ if ('check' in expression) {
29
+ if (expression.object.kind === 'input')
30
+ fields.add(expression.object.field);
31
+ return;
32
+ }
33
+ if ('sameNode' in expression) {
34
+ for (const object of [expression.sameNode.left, expression.sameNode.right]) {
35
+ if (object.kind === 'input')
36
+ fields.add(object.field);
37
+ }
38
+ return;
39
+ }
40
+ for (const child of 'allOf' in expression ? expression.allOf : expression.anyOf) {
41
+ collectInputFields(child, fields);
42
+ }
43
+ }
@@ -21,6 +21,15 @@ export type OutputMode = CallableOutput['mode'];
21
21
  export interface CallableExecution {
22
22
  readonly durable: boolean;
23
23
  }
24
+ /**
25
+ * Admission age of a callable: how old, in whole seconds, the admission decision used for one of its
26
+ * calls may be. A destination may reuse an admission it received within that age; a Runtime that
27
+ * never reuses one conforms. Absent means every call is admitted afresh, and `maxAge` is an integer
28
+ * from 1 to `MAXIMUM_ADMISSION_AGE_SECONDS`.
29
+ */
30
+ export interface CallableAdmission {
31
+ readonly maxAge: number;
32
+ }
24
33
  /** A binary Function receives portable metadata and one required byte stream. */
25
34
  export interface BinaryInput {
26
35
  readonly mode: 'binary';
@@ -39,6 +48,7 @@ export interface CallableDefinitionBase<Input extends CallableInputContract = Ca
39
48
  readonly input: Input;
40
49
  readonly output: CallableOutput;
41
50
  readonly execution?: CallableExecution;
51
+ readonly admission?: CallableAdmission;
42
52
  }
43
53
  export type CallableDefinition<Input extends CallableInputContract = CallableInputContract> = CallableDefinitionBase<Input> & ({
44
54
  readonly auth: 'anonymous' | 'authenticated';
@@ -2,7 +2,7 @@ import type { DefinitionKey, FunctionRef, Key, MethodKey } from '../../addressin
2
2
  import type { ResolvedClass } from '../class/resolved.js';
3
3
  import type { CallablePolicy } from '../policy/model/policy.js';
4
4
  import type { Diagnostic } from '../validation/diagnostic.js';
5
- import type { BinaryInputProjection, CallableExecution, FunctionDefinition, MethodDefinition } from './model/callable.js';
5
+ import type { BinaryInputProjection, CallableAdmission, CallableExecution, FunctionDefinition, MethodDefinition } from './model/callable.js';
6
6
  import type { FunctionAuth } from './model/callable.js';
7
7
  type CallableDefinitionOf<Definition extends FunctionDefinition | MethodDefinition, Auth extends FunctionAuth, Policy extends CallablePolicy> = Omit<Definition, 'auth' | 'policy'> & (Auth extends FunctionAuth ? {
8
8
  readonly auth: Auth;
@@ -22,6 +22,8 @@ export interface ResolvedMethod<out Input = unknown, out Output = unknown, out S
22
22
  readonly output: MethodDefinition['output'];
23
23
  /** Present only for a durable Method. */
24
24
  readonly execution?: CallableExecution;
25
+ /** Present only when the Method declares an admission age. */
26
+ readonly admission?: CallableAdmission;
25
27
  readonly auth: Auth;
26
28
  readonly policy?: Auth extends 'authorized' ? Policy : never;
27
29
  readonly static: Static;
@@ -53,6 +55,8 @@ export interface ResolvedFunction<out Input = unknown, out Output = unknown, out
53
55
  readonly output: FunctionDefinition['output'];
54
56
  /** Present only for a durable Function. */
55
57
  readonly execution?: CallableExecution;
58
+ /** Present only when the Function declares an admission age. */
59
+ readonly admission?: CallableAdmission;
56
60
  readonly auth: Auth;
57
61
  readonly policy?: Auth extends 'authorized' ? Policy : never;
58
62
  /** Present only when the callable input is a safely enumerable closed object. */
@@ -1,3 +1,4 @@
1
+ export { admissionInputFields, isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from './callable/admission.js';
1
2
  export { isMethodExecutable } from './callable/executable.js';
2
3
  export { isBinaryInput } from './callable/model/callable.js';
3
4
  export type { ClassRef, CoreRef, DefinitionBase, DefinitionKey, DefinitionRef, FunctionRef, MethodKey, PolicyRef, PropertyKey, ViewRef, } from '../addressing/index.js';
@@ -1,3 +1,4 @@
1
+ export { admissionInputFields, isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from './callable/admission.js';
1
2
  export { isMethodExecutable } from './callable/executable.js';
2
3
  export { isBinaryInput } from './callable/model/callable.js';
3
4
  export { valueProgramIncludes } from './value/inclusion/includes.js';
@@ -2,6 +2,7 @@ import { diagnostic } from '../../validation/diagnostic.js';
2
2
  import { validateCanonicalJsonDomain } from '../profile/json.validate.js';
3
3
  import { evaluateInstance } from './evaluator.js';
4
4
  import { prepareValueSchema, } from './preparation.js';
5
+ import { satisfies } from './validity.optimization.js';
5
6
  const validatorCache = new WeakMap();
6
7
  /**
7
8
  * Validate one complete portable value with the admitted V1 Draft 2020-12 profile.
@@ -30,6 +31,9 @@ export function validatePreparedInstance(compiled, value) {
30
31
  : instanceDiagnostics(compiled, value, '');
31
32
  }
32
33
  function instanceDiagnostics(compiled, value, pointer) {
34
+ // A value the compiled check finds valid has nothing to report; any other is evaluated.
35
+ if (satisfies(compiled, value) === true)
36
+ return [];
33
37
  const result = evaluateInstance(compiled, value);
34
38
  if (result.valid)
35
39
  return [];
@@ -0,0 +1,14 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { CompiledInstanceSchema } from './types.js';
3
+ /**
4
+ * Whether a value satisfies its prepared schema, as the evaluator decides it, without building a
5
+ * failure, an instance pointer or an annotation: the evaluator's verdict, or undefined when this
6
+ * check cannot give it. Only the evaluator reports why a value fails, so a caller keeps it for
7
+ * every value this check does not find valid.
8
+ *
9
+ * Each schema location is compiled once into a closure over its keywords, retained with the
10
+ * prepared schema. A schema whose closure holds an annotation-dependent keyword
11
+ * (`unevaluatedItems`, `unevaluatedProperties`) is left to the evaluator, as is a value nested
12
+ * deeper than this check follows, and any value whose check throws.
13
+ */
14
+ export declare function satisfies(compiled: CompiledInstanceSchema, value: JsonValue): boolean | undefined;
@@ -0,0 +1,342 @@
1
+ import { isCanonicalBlobRef } from '../model/blob.js';
2
+ import { isInstantValue } from '../model/instant.js';
3
+ import { isValueObject as isJsonObject } from '../model/object.js';
4
+ import { codePointLength, isMultipleOf, jsonEqual, matchesType, unique } from './assertions.js';
5
+ /**
6
+ * Whether a value satisfies its prepared schema, as the evaluator decides it, without building a
7
+ * failure, an instance pointer or an annotation: the evaluator's verdict, or undefined when this
8
+ * check cannot give it. Only the evaluator reports why a value fails, so a caller keeps it for
9
+ * every value this check does not find valid.
10
+ *
11
+ * Each schema location is compiled once into a closure over its keywords, retained with the
12
+ * prepared schema. A schema whose closure holds an annotation-dependent keyword
13
+ * (`unevaluatedItems`, `unevaluatedProperties`) is left to the evaluator, as is a value nested
14
+ * deeper than this check follows, and any value whose check throws.
15
+ */
16
+ export function satisfies(compiled, value) {
17
+ let checks = compilations.get(compiled);
18
+ if (checks === undefined) {
19
+ checks = annotationFree(compiled) ? new Map() : null;
20
+ compilations.set(compiled, checks);
21
+ }
22
+ if (checks === null)
23
+ return undefined;
24
+ const outer = verdicts;
25
+ verdicts = new Map();
26
+ try {
27
+ return checkOf(compiled, checks, compiled.root)(value, 0);
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ finally {
33
+ verdicts = outer;
34
+ }
35
+ }
36
+ /** Per prepared schema: its checks by location, or null when its closure needs annotations. */
37
+ const compilations = new WeakMap();
38
+ /** Locations one evaluation follows before leaving the value to the evaluator. */
39
+ const MAXIMUM_DEPTH = 1_024;
40
+ const UNDECIDED = new Error('The value is left to the evaluator.');
41
+ /**
42
+ * The verdicts of the current evaluation by referenced location and value. Only a reference reaches
43
+ * one location from several places, so each referenced location is checked once per value, as the
44
+ * evaluator memoizes it, and a schema that references a location twice per level stays linear. A
45
+ * location entered again on the same value is a cycle, left to the evaluator.
46
+ */
47
+ let verdicts = new Map();
48
+ function annotationFree(compiled) {
49
+ for (const { schema } of compiled.locations.values()) {
50
+ if (isJsonObject(schema) &&
51
+ (Object.hasOwn(schema, 'unevaluatedItems') || Object.hasOwn(schema, 'unevaluatedProperties'))) {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
57
+ /** The check of one location, compiled on its first use so that references may recur. */
58
+ function checkOf(compiled, checks, location) {
59
+ let check = checks.get(location);
60
+ if (check === undefined) {
61
+ let body;
62
+ check = (value, depth) => {
63
+ if (depth > MAXIMUM_DEPTH)
64
+ throw UNDECIDED;
65
+ body ??= compileLocation(compiled, checks, location);
66
+ return body(value, depth);
67
+ };
68
+ checks.set(location, check);
69
+ }
70
+ return check;
71
+ }
72
+ /**
73
+ * The conjunction of the location's keywords, each as the evaluator applies it: an applicator
74
+ * checks the same value at its child location, a keyword of one type passes every other type.
75
+ */
76
+ function compileLocation(compiled, checks, location) {
77
+ const schema = location.schema;
78
+ if (schema === true)
79
+ return () => true;
80
+ if (schema === false)
81
+ return () => false;
82
+ const child = (keyword, member) => checkOf(compiled, checks, compiled.child(location, keyword, member));
83
+ const parts = [];
84
+ if (Object.hasOwn(schema, '$ref')) {
85
+ const target = compiled.reference(location);
86
+ if (target === undefined)
87
+ throw UNDECIDED;
88
+ const check = checkOf(compiled, checks, target);
89
+ parts.push((value, depth) => {
90
+ let byValue = verdicts.get(target);
91
+ if (byValue === undefined)
92
+ verdicts.set(target, (byValue = new Map()));
93
+ const known = byValue.get(value);
94
+ if (known === null)
95
+ throw UNDECIDED;
96
+ if (known !== undefined)
97
+ return known;
98
+ byValue.set(value, null);
99
+ const verdict = check(value, depth + 1);
100
+ byValue.set(value, verdict);
101
+ return verdict;
102
+ });
103
+ }
104
+ compileCompositions(schema, child, parts);
105
+ compileCommon(schema, parts);
106
+ compileNumber(schema, parts);
107
+ compileString(compiled, schema, parts);
108
+ compileArray(schema, child, parts);
109
+ compileObject(compiled, schema, child, parts);
110
+ if (parts.length === 0)
111
+ return () => true;
112
+ if (parts.length === 1)
113
+ return parts[0];
114
+ return (value, depth) => {
115
+ for (const part of parts)
116
+ if (!part(value, depth))
117
+ return false;
118
+ return true;
119
+ };
120
+ }
121
+ function compileCompositions(schema, child, parts) {
122
+ const allOf = schemaList(schema.allOf)?.map((_, index) => child('allOf', index));
123
+ if (allOf !== undefined) {
124
+ parts.push((value, depth) => allOf.every((check) => check(value, depth + 1)));
125
+ }
126
+ const anyOf = schemaList(schema.anyOf)?.map((_, index) => child('anyOf', index));
127
+ if (anyOf !== undefined) {
128
+ parts.push((value, depth) => anyOf.some((check) => check(value, depth + 1)));
129
+ }
130
+ const oneOf = schemaList(schema.oneOf)?.map((_, index) => child('oneOf', index));
131
+ if (oneOf !== undefined) {
132
+ parts.push((value, depth) => {
133
+ let admitted = 0;
134
+ for (const check of oneOf)
135
+ if (check(value, depth + 1) && ++admitted > 1)
136
+ return false;
137
+ return admitted === 1;
138
+ });
139
+ }
140
+ if (isSchema(schema.not)) {
141
+ const not = child('not');
142
+ parts.push((value, depth) => !not(value, depth + 1));
143
+ }
144
+ if (isSchema(schema.if)) {
145
+ const condition = child('if');
146
+ const then = isSchema(schema.then) ? child('then') : undefined;
147
+ const otherwise = isSchema(schema.else) ? child('else') : undefined;
148
+ parts.push((value, depth) => {
149
+ const branch = condition(value, depth + 1) ? then : otherwise;
150
+ return branch === undefined || branch(value, depth + 1);
151
+ });
152
+ }
153
+ }
154
+ function compileCommon(schema, parts) {
155
+ const kind = schema['x-astrale-value-kind'];
156
+ if (kind === 'instant')
157
+ parts.push((value) => isInstantValue(value));
158
+ if (kind === 'blob')
159
+ parts.push((value) => isCanonicalBlobRef(value));
160
+ const type = schema.type;
161
+ if (typeof type === 'string')
162
+ parts.push((value) => matchesType(type, value));
163
+ else if (Array.isArray(type)) {
164
+ const types = type.filter((candidate) => typeof candidate === 'string');
165
+ parts.push((value) => types.some((candidate) => matchesType(candidate, value)));
166
+ }
167
+ if (Object.hasOwn(schema, 'const')) {
168
+ const expected = schema.const;
169
+ parts.push((value) => jsonEqual(value, expected));
170
+ }
171
+ const choices = schema.enum;
172
+ if (Array.isArray(choices)) {
173
+ parts.push((value) => choices.some((candidate) => jsonEqual(value, candidate)));
174
+ }
175
+ }
176
+ function compileNumber(schema, parts) {
177
+ const { multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum } = schema;
178
+ const bounded = typeof multipleOf === 'number' ||
179
+ typeof maximum === 'number' ||
180
+ typeof exclusiveMaximum === 'number' ||
181
+ typeof minimum === 'number' ||
182
+ typeof exclusiveMinimum === 'number';
183
+ if (!bounded)
184
+ return;
185
+ parts.push((value) => typeof value !== 'number' ||
186
+ ((typeof multipleOf !== 'number' || isMultipleOf(value, multipleOf)) &&
187
+ (typeof maximum !== 'number' || value <= maximum) &&
188
+ (typeof exclusiveMaximum !== 'number' || value < exclusiveMaximum) &&
189
+ (typeof minimum !== 'number' || value >= minimum) &&
190
+ (typeof exclusiveMinimum !== 'number' || value > exclusiveMinimum)));
191
+ }
192
+ function compileString(compiled, schema, parts) {
193
+ const { maxLength, minLength, pattern, fullMatch } = schema;
194
+ if (typeof maxLength === 'number' || typeof minLength === 'number') {
195
+ // For n UTF-16 units, ceil(n / 2) <= code points <= n: count only when the bounds cannot
196
+ // decide, as the evaluator does.
197
+ parts.push((value) => {
198
+ if (typeof value !== 'string')
199
+ return true;
200
+ if ((typeof maxLength !== 'number' || value.length <= maxLength) &&
201
+ (typeof minLength !== 'number' || Math.ceil(value.length / 2) >= minLength)) {
202
+ return true;
203
+ }
204
+ const length = codePointLength(value);
205
+ return ((typeof maxLength !== 'number' || length <= maxLength) &&
206
+ (typeof minLength !== 'number' || length >= minLength));
207
+ });
208
+ }
209
+ if (typeof pattern === 'string') {
210
+ const matcher = compiled.matcher(pattern);
211
+ parts.push((value) => typeof value !== 'string' || matcher.test(value));
212
+ }
213
+ if (typeof fullMatch === 'string') {
214
+ const matcher = compiled.matcher(fullMatch);
215
+ parts.push((value) => typeof value !== 'string' || matcher.matches(value));
216
+ }
217
+ }
218
+ function compileArray(schema, child, parts) {
219
+ const { maxItems, minItems } = schema;
220
+ if (typeof maxItems === 'number' || typeof minItems === 'number' || schema.uniqueItems === true) {
221
+ const distinct = schema.uniqueItems === true;
222
+ parts.push((value) => !Array.isArray(value) ||
223
+ ((typeof maxItems !== 'number' || value.length <= maxItems) &&
224
+ (typeof minItems !== 'number' || value.length >= minItems) &&
225
+ (!distinct || unique(value))));
226
+ }
227
+ const prefix = schemaList(schema.prefixItems)?.map((_, index) => child('prefixItems', index));
228
+ const items = isSchema(schema.items) ? child('items') : undefined;
229
+ if (prefix !== undefined || items !== undefined) {
230
+ const leading = prefix ?? [];
231
+ parts.push((value, depth) => {
232
+ if (!Array.isArray(value))
233
+ return true;
234
+ for (let index = 0; index < value.length; index++) {
235
+ const check = index < leading.length ? leading[index] : items;
236
+ if (check !== undefined && !check(value[index], depth + 1))
237
+ return false;
238
+ }
239
+ return true;
240
+ });
241
+ }
242
+ if (isSchema(schema.contains)) {
243
+ const contains = child('contains');
244
+ const least = typeof schema.minContains === 'number' ? schema.minContains : 1;
245
+ const most = typeof schema.maxContains === 'number' ? schema.maxContains : Number.POSITIVE_INFINITY;
246
+ parts.push((value, depth) => {
247
+ if (!Array.isArray(value))
248
+ return true;
249
+ let matches = 0;
250
+ for (const item of value)
251
+ if (contains(item, depth + 1))
252
+ matches++;
253
+ return matches >= least && matches <= most;
254
+ });
255
+ }
256
+ }
257
+ function compileObject(compiled, schema, child, parts) {
258
+ const { maxProperties, minProperties } = schema;
259
+ const required = Array.isArray(schema.required)
260
+ ? schema.required.filter((name) => typeof name === 'string')
261
+ : [];
262
+ const dependentRequired = isJsonObject(schema.dependentRequired)
263
+ ? Object.entries(schema.dependentRequired).flatMap(([name, names]) => Array.isArray(names)
264
+ ? [[name, names.filter((other) => typeof other === 'string')]]
265
+ : [])
266
+ : [];
267
+ if (typeof maxProperties === 'number' ||
268
+ typeof minProperties === 'number' ||
269
+ required.length > 0 ||
270
+ dependentRequired.length > 0) {
271
+ parts.push((value) => {
272
+ if (!isJsonObject(value))
273
+ return true;
274
+ if (typeof maxProperties === 'number' || typeof minProperties === 'number') {
275
+ const count = Object.keys(value).length;
276
+ if (typeof maxProperties === 'number' && count > maxProperties)
277
+ return false;
278
+ if (typeof minProperties === 'number' && count < minProperties)
279
+ return false;
280
+ }
281
+ for (const name of required)
282
+ if (!Object.hasOwn(value, name))
283
+ return false;
284
+ for (const [name, names] of dependentRequired) {
285
+ if (Object.hasOwn(value, name) && !names.every((other) => Object.hasOwn(value, other))) {
286
+ return false;
287
+ }
288
+ }
289
+ return true;
290
+ });
291
+ }
292
+ // Each member is checked by every pattern it matches, by its declared schema, and by the
293
+ // additional schema when neither declares it.
294
+ const declared = isJsonObject(schema.properties) ? schema.properties : {};
295
+ const properties = new Map(Object.keys(declared).map((name) => [name, child('properties', name)]));
296
+ const patterns = isJsonObject(schema.patternProperties)
297
+ ? Object.keys(schema.patternProperties).map((source) => [compiled.matcher(source), child('patternProperties', source)])
298
+ : [];
299
+ const additional = isSchema(schema.additionalProperties)
300
+ ? child('additionalProperties')
301
+ : undefined;
302
+ const names = isSchema(schema.propertyNames) ? child('propertyNames') : undefined;
303
+ if (properties.size > 0 || patterns.length > 0 || additional !== undefined || names) {
304
+ parts.push((value, depth) => {
305
+ if (!isJsonObject(value))
306
+ return true;
307
+ for (const name of Object.keys(value)) {
308
+ const member = value[name];
309
+ let matched = false;
310
+ for (const [matcher, check] of patterns) {
311
+ if (!matcher.test(name))
312
+ continue;
313
+ matched = true;
314
+ if (!check(member, depth + 1))
315
+ return false;
316
+ }
317
+ const check = Object.hasOwn(declared, name) ? properties.get(name) : undefined;
318
+ if (check !== undefined) {
319
+ if (!check(member, depth + 1))
320
+ return false;
321
+ }
322
+ else if (!matched && additional !== undefined && !additional(member, depth + 1)) {
323
+ return false;
324
+ }
325
+ if (names !== undefined && !names(name, depth + 1))
326
+ return false;
327
+ }
328
+ return true;
329
+ });
330
+ }
331
+ if (isJsonObject(schema.dependentSchemas)) {
332
+ const dependent = Object.keys(schema.dependentSchemas).map((name) => [name, child('dependentSchemas', name)]);
333
+ parts.push((value, depth) => !isJsonObject(value) ||
334
+ dependent.every(([name, check]) => !Object.hasOwn(value, name) || check(value, depth + 1)));
335
+ }
336
+ }
337
+ function schemaList(input) {
338
+ return Array.isArray(input) ? input : undefined;
339
+ }
340
+ function isSchema(input) {
341
+ return typeof input === 'boolean' || isJsonObject(input);
342
+ }
@@ -50,9 +50,11 @@ export declare const PROPERTY_FIELDS: Readonly<{
50
50
  }>;
51
51
  }>;
52
52
  /**
53
- * The auth level and Policy check are decided by the Runtime at invocation, never compiled against.
54
- * The execution modifier decides what an invocation returns; an accepted Schema carries it only
55
- * when durable, so ordinary callables keep their meaning unchanged.
53
+ * The auth level, the Policy check and the admission age are authorization: decided by the Runtime
54
+ * at invocation, never compiled against. The admission age only bounds how old the admission a
55
+ * destination decides a call under may be. The execution modifier decides what an invocation
56
+ * returns; an accepted Schema carries it only when durable, so ordinary callables keep their meaning
57
+ * unchanged.
56
58
  */
57
59
  export declare const CALLABLE_FIELDS: Readonly<{
58
60
  ref: Readonly<{
@@ -63,6 +65,9 @@ export declare const CALLABLE_FIELDS: Readonly<{
63
65
  static: "always";
64
66
  abstract: "always";
65
67
  execution: "always";
68
+ admission: Readonly<{
69
+ excluded: 'authorization';
70
+ }>;
66
71
  auth: Readonly<{
67
72
  excluded: 'authorization';
68
73
  }>;
@@ -148,6 +153,9 @@ export declare const FIELD_ROLES: Readonly<{
148
153
  static: "always";
149
154
  abstract: "always";
150
155
  execution: "always";
156
+ admission: Readonly<{
157
+ excluded: 'authorization';
158
+ }>;
151
159
  auth: Readonly<{
152
160
  excluded: 'authorization';
153
161
  }>;
@@ -27,9 +27,11 @@ export const PROPERTY_FIELDS = Object.freeze({
27
27
  description: ANNOTATION,
28
28
  });
29
29
  /**
30
- * The auth level and Policy check are decided by the Runtime at invocation, never compiled against.
31
- * The execution modifier decides what an invocation returns; an accepted Schema carries it only
32
- * when durable, so ordinary callables keep their meaning unchanged.
30
+ * The auth level, the Policy check and the admission age are authorization: decided by the Runtime
31
+ * at invocation, never compiled against. The admission age only bounds how old the admission a
32
+ * destination decides a call under may be. The execution modifier decides what an invocation
33
+ * returns; an accepted Schema carries it only when durable, so ordinary callables keep their meaning
34
+ * unchanged.
33
35
  */
34
36
  export const CALLABLE_FIELDS = Object.freeze({
35
37
  ref: IDENTITY,
@@ -38,6 +40,7 @@ export const CALLABLE_FIELDS = Object.freeze({
38
40
  static: 'always',
39
41
  abstract: 'always',
40
42
  execution: 'always',
43
+ admission: AUTHORIZATION,
41
44
  auth: AUTHORIZATION,
42
45
  policy: AUTHORIZATION,
43
46
  description: ANNOTATION,
@@ -1,4 +1,4 @@
1
- export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_FOREIGN_MINIMUM' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
1
+ export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_ADMISSION' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_FOREIGN_MINIMUM' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
2
2
  export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -115,6 +115,8 @@ function canonicalizeCallable(value) {
115
115
  output: canonicalizeCallableOutput(value.output),
116
116
  // Ordinary execution has one canonical form, so existing revisions never move.
117
117
  ...(value.execution?.durable === true ? { execution: { durable: true } } : {}),
118
+ // Absence is the one spelling of a call admitted afresh; validation refuses any other.
119
+ ...(value.admission === undefined ? {} : { admission: { maxAge: value.admission.maxAge } }),
118
120
  };
119
121
  return value.auth === 'authorized'
120
122
  ? {
@@ -1,4 +1,4 @@
1
1
  import type { SchemaDiagnostic } from '../diagnostic.js';
2
2
  import type { DomainSchemaIR } from '../model/schema.js';
3
- /** Validate the execution modifier of every local Function and Node Method. */
3
+ /** Validate the execution modifier and admission age of every local Function and Node Method. */
4
4
  export declare function validateCallables(schema: DomainSchemaIR): readonly SchemaDiagnostic[];
@@ -1,10 +1,13 @@
1
+ import { isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from '../../language/callable/admission.js';
1
2
  import { isBinaryInput } from '../../language/callable/model/callable.js';
2
3
  import { diagnostic } from '../diagnostic.js';
3
- /** Validate the execution modifier of every local Function and Node Method. */
4
+ /** Validate the execution modifier and admission age of every local Function and Node Method. */
4
5
  export function validateCallables(schema) {
5
6
  const diagnostics = [];
6
7
  for (const [name, declaration] of Object.entries(schema.functions)) {
7
- validateExecution(declaration, `/functions/${escapePointer(name)}`, diagnostics);
8
+ const pointer = `/functions/${escapePointer(name)}`;
9
+ validateExecution(declaration, pointer, diagnostics);
10
+ validateAdmission(declaration, pointer, diagnostics);
8
11
  }
9
12
  for (const [name, declaration] of Object.entries(schema.classes)) {
10
13
  if (declaration.kind !== 'node')
@@ -12,10 +15,22 @@ export function validateCallables(schema) {
12
15
  for (const [method, value] of Object.entries(declaration.methods)) {
13
16
  const pointer = `/classes/${escapePointer(name)}/methods/${escapePointer(method)}`;
14
17
  validateExecution(value, pointer, diagnostics);
18
+ validateAdmission(value, pointer, diagnostics);
15
19
  }
16
20
  }
17
21
  return Object.freeze(diagnostics);
18
22
  }
23
+ /**
24
+ * An admission age is a whole number of seconds within the language ceiling. It has no zero
25
+ * spelling: an absent admission age is the one canonical form of a call admitted afresh.
26
+ */
27
+ function validateAdmission(declaration, pointer, diagnostics) {
28
+ if (declaration.admission === undefined)
29
+ return;
30
+ if (isAdmissionMaxAge(declaration.admission.maxAge))
31
+ return;
32
+ diagnostics.push(diagnostic('DV_CALLABLE_ADMISSION', `${pointer}/admission/maxAge`, `An admission age is an integer number of seconds from 1 to ${MAXIMUM_ADMISSION_AGE_SECONDS}; omit it to admit every call afresh.`));
33
+ }
19
34
  /**
20
35
  * An Execution records its initiating caller and its admitted input, from which the run is
21
36
  * resubmitted, and retains one portable outcome. A durable callable therefore needs a known caller,
@@ -87,9 +87,15 @@ function validateFacet(environment, declaration, pointer, diagnostics) {
87
87
  return;
88
88
  for (const [name, method] of Object.entries(declaration.methods)) {
89
89
  const methodPointer = `${pointer}/methods/${escapePointer(name)}`;
90
- if (!isMethodExecutable(declaration, method) && method.policy !== undefined) {
90
+ if (isMethodExecutable(declaration, method))
91
+ continue;
92
+ if (method.policy !== undefined) {
91
93
  diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/policy`, 'Abstract Methods cannot have a Policy.'));
92
94
  }
95
+ // An admission age qualifies the admission of an executable declaration, like its Policy.
96
+ if (method.admission !== undefined) {
97
+ diagnostics.push(diagnostic('DV_METHOD_ABSTRACT', `${methodPointer}/admission`, 'Abstract Methods cannot have an admission age.'));
98
+ }
93
99
  }
94
100
  }
95
101
  function validateParents(environment, owner, ownerKind, parents, diagnostics) {
@@ -195,11 +195,16 @@ const valueFunctionInputSchema = jsonSchemaSchema.refine((value) => typeof value
195
195
  const callableExecutionSchema = strictObject({
196
196
  durable: boolean(),
197
197
  });
198
+ // Its range is language meaning, refused by callable validation with DV_CALLABLE_ADMISSION.
199
+ const callableAdmissionSchema = strictObject({
200
+ maxAge: number(),
201
+ });
198
202
  const callableShape = {
199
203
  description: descriptionSchema.optional(),
200
204
  input: jsonSchemaSchema,
201
205
  output: callableOutputSchema,
202
206
  execution: callableExecutionSchema.optional(),
207
+ admission: callableAdmissionSchema.optional(),
203
208
  auth: enumeration(['anonymous', 'authenticated', 'authorized']),
204
209
  policy: union([literal(true), policyCheckExpressionSchema]).optional(),
205
210
  };
@@ -5,3 +5,8 @@ export declare function serialize(input: unknown, options?: Options): string;
5
5
  /** Serialize a trusted portable value whose default structural admission proof is still valid. */
6
6
  export declare function serializeAdmitted(input: Value): string;
7
7
  export declare function encode(input: unknown, options?: Options): Uint8Array;
8
+ /**
9
+ * The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`: an
10
+ * admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
11
+ */
12
+ export declare function byteLength(input: Value): number;
@@ -31,6 +31,18 @@ function serializeSnapshot(input, maxBytes) {
31
31
  export function encode(input, options) {
32
32
  return encoder.encode(serialize(input, options));
33
33
  }
34
+ /**
35
+ * The UTF-8 byte length of a trusted Value's canonical JSON, `encode(input).byteLength`: an
36
+ * admission-owned immutable root counted its bytes when it was admitted, so it is not serialized.
37
+ */
38
+ export function byteLength(input) {
39
+ if (input !== null && typeof input === 'object') {
40
+ const bytes = immutableSnapshotBytes(input);
41
+ if (bytes !== undefined)
42
+ return bytes;
43
+ }
44
+ return utf8Length(serializeAdmitted(input));
45
+ }
34
46
  function serializeValue(value) {
35
47
  if (value === null)
36
48
  return 'null';
@@ -1,3 +1,3 @@
1
- export { encode, serialize, serializeAdmitted } from './canonical.js';
1
+ export { byteLength, encode, serialize, serializeAdmitted } from './canonical.js';
2
2
  export { decode } from './decode.js';
3
3
  export { DecodeError } from './error.js';
@@ -1,3 +1,3 @@
1
- export { encode, serialize, serializeAdmitted } from './canonical.js';
1
+ export { byteLength, encode, serialize, serializeAdmitted } from './canonical.js';
2
2
  export { decode } from './decode.js';
3
3
  export { DecodeError } from './error.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-dsl",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {