@astrale-os/kernel-dsl 1.0.0-beta.14 → 1.0.0-beta.16

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 (74) hide show
  1. package/dist/v1/addressing/domain-dependency.d.ts +0 -6
  2. package/dist/v1/addressing/domain-dependency.js +1 -6
  3. package/dist/v1/builder/class/authoring.d.ts +43 -0
  4. package/dist/v1/builder/index.d.ts +1 -1
  5. package/dist/v1/builder/lowering/policy.js +1 -0
  6. package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
  7. package/dist/v1/builder/lowering/schema/context.js +22 -0
  8. package/dist/v1/builder/policy/authoring.d.ts +84 -4
  9. package/dist/v1/builder/policy/authoring.js +41 -12
  10. package/dist/v1/builder/schema/projection/class.d.ts +116 -14
  11. package/dist/v1/bundle/codec.js +7 -7
  12. package/dist/v1/compiled/admission/accept.js +3 -3
  13. package/dist/v1/compiled/admission/closure.js +2 -2
  14. package/dist/v1/compiled/admission/semantics/core.js +3 -7
  15. package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
  16. package/dist/v1/compiled/admission/semantics/policy/typing.js +2 -2
  17. package/dist/v1/language/.spec/schemas/draft-2020-12.schema.json +462 -0
  18. package/dist/v1/language/core/facts.d.ts +17 -4
  19. package/dist/v1/language/core/facts.js +26 -5
  20. package/dist/v1/language/limits.d.ts +64 -0
  21. package/dist/v1/language/limits.js +60 -0
  22. package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
  23. package/dist/v1/language/policy/analysis/budgets.js +30 -20
  24. package/dist/v1/language/policy/analysis/checks.js +3 -2
  25. package/dist/v1/language/policy/analysis/typing.d.ts +4 -2
  26. package/dist/v1/language/policy/analysis/typing.js +136 -14
  27. package/dist/v1/language/validation/rule.d.ts +9 -0
  28. package/dist/v1/language/validation/rule.js +9 -7
  29. package/dist/v1/language/value/ordering.d.ts +5 -0
  30. package/dist/v1/language/value/ordering.js +14 -0
  31. package/dist/v1/language/value/reference/closure.js +3 -4
  32. package/dist/v1/language/value/reference/registry.index.d.ts +2 -0
  33. package/dist/v1/language/value/reference/registry.index.js +31 -15
  34. package/dist/v1/language/value/regexp/ast.js +6 -7
  35. package/dist/v1/language/value/regexp/compile.d.ts +5 -11
  36. package/dist/v1/language/value/regexp/compile.js +14 -12
  37. package/dist/v1/language/value/regexp/syntax.js +3 -2
  38. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +30 -2
  39. package/dist/v1/schema/admission/accept.js +3 -14
  40. package/dist/v1/schema/admission/callable.d.ts +21 -0
  41. package/dist/v1/schema/admission/callable.js +96 -0
  42. package/dist/v1/schema/index.d.ts +7 -0
  43. package/dist/v1/schema/index.js +3 -0
  44. package/dist/v1/schema/normalization/domain.d.ts +2 -0
  45. package/dist/v1/schema/normalization/domain.js +1 -1
  46. package/dist/v1/schema/resolution/context.js +7 -7
  47. package/dist/v1/schema/resolution/domain.d.ts +5 -0
  48. package/dist/v1/schema/resolution/domain.js +1 -1
  49. package/dist/v1/schema/rule.d.ts +9 -0
  50. package/dist/v1/schema/rule.js +29 -4
  51. package/dist/v1/schema/validation/callable.d.ts +33 -3
  52. package/dist/v1/schema/validation/callable.js +49 -15
  53. package/dist/v1/schema/validation/class.d.ts +6 -2
  54. package/dist/v1/schema/validation/class.js +18 -22
  55. package/dist/v1/schema/validation/core.js +16 -10
  56. package/dist/v1/schema/validation/domain.js +3 -3
  57. package/dist/v1/schema/validation/domain.route.d.ts +6 -1
  58. package/dist/v1/schema/validation/domain.route.js +18 -0
  59. package/dist/v1/schema/validation/domain.structure.d.ts +28 -26
  60. package/dist/v1/schema/validation/domain.structure.js +22 -6
  61. package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
  62. package/dist/v1/schema/validation/policy/budgets.js +89 -16
  63. package/dist/v1/schema/validation/policy/context.d.ts +1 -2
  64. package/dist/v1/schema/validation/policy/context.js +2 -7
  65. package/dist/v1/schema/validation/policy/references.js +2 -1
  66. package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
  67. package/dist/v1/schema/validation/policy/uses.js +28 -48
  68. package/dist/v1/schema/validation/value-api.d.ts +2 -0
  69. package/dist/v1/schema/validation/value-api.js +2 -0
  70. package/dist/v1/schema/validation/value.d.ts +3 -0
  71. package/dist/v1/schema/validation/value.js +3 -9
  72. package/dist/v1/schema/validation/value.json-schema.d.ts +3 -3
  73. package/dist/v1/schema/validation/value.json-schema.js +22 -4
  74. package/package.json +2 -1
@@ -4,11 +4,21 @@ import { diagnostic } from '../../validation/diagnostic.js';
4
4
  import { isValueObject as isJsonObject } from '../model/object.js';
5
5
  import { JSON_SCHEMA_DIALECT } from '../model/schema.js';
6
6
  import { GRAPH_NODE_PATH_SCHEMA_ID } from '../syntax/node-path/schema.js';
7
+ // A registry is an immutable value, and every value schema of a Domain is validated against the
8
+ // same one: an index that admitted it without a diagnostic is reused, so the closure is indexed
9
+ // once per registry rather than once per value schema.
10
+ const admittedIndexes = new WeakMap();
7
11
  export function indexRegistry(registry, pointer) {
12
+ const admitted = admittedIndexes.get(registry);
13
+ if (admitted !== undefined)
14
+ return { index: admitted, diagnostics: [] };
8
15
  const diagnostics = [];
9
16
  const domains = new Map();
10
17
  domains.set(registry.current.origin, registry.current);
11
- validateDomainRegistry(registry.current, `${pointer}/registry/current`, false, diagnostics);
18
+ // The registry is the closure the Kernel builds for the schema at `pointer`, not a document: a
19
+ // fault of one of its entries is a fault of the closure that schema is checked with, reported
20
+ // there, the entry named in the message.
21
+ validateDomainRegistry(registry.current, 'current', false, pointer, diagnostics);
12
22
  const dependencies = [
13
23
  ...registry.directDependencies.map((domain) => ({ domain, lane: 'directDependencies' })),
14
24
  ...registry.transitiveDependencies.map((domain) => ({
@@ -17,10 +27,9 @@ export function indexRegistry(registry, pointer) {
17
27
  })),
18
28
  ];
19
29
  dependencies.forEach(({ domain, lane }, index) => {
20
- const domainPointer = `${pointer}/registry/${lane}/${index}`;
21
- validateDomainRegistry(domain, domainPointer, true, diagnostics);
30
+ validateDomainRegistry(domain, `${lane}/${index}`, true, pointer, diagnostics);
22
31
  if (domain.origin === registry.current.origin || domains.has(domain.origin)) {
23
- diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', domainPointer, `Registry origin ${domain.origin} is duplicated in the exact closure.`));
32
+ diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', pointer, `Registry origin ${domain.origin} is duplicated in the exact closure, at ${lane}/${index}.`));
24
33
  return;
25
34
  }
26
35
  domains.set(domain.origin, domain);
@@ -37,31 +46,38 @@ export function indexRegistry(registry, pointer) {
37
46
  }
38
47
  }
39
48
  }
40
- validateGraphNodePathResource(registry.graphNodePath, `${pointer}/registry/graphNodePath`, diagnostics);
41
- return { index: { registry, domains, dependenciesByOwner }, diagnostics };
49
+ const nodeDefinitions = new Set();
50
+ for (const domain of domains.values()) {
51
+ for (const ref of domain.nodeDefinitions) {
52
+ if (ref.origin === domain.origin)
53
+ nodeDefinitions.add(definitionRefIndex(ref));
54
+ }
55
+ }
56
+ validateGraphNodePathResource(registry.graphNodePath, pointer, diagnostics);
57
+ const index = { registry, domains, dependenciesByOwner, nodeDefinitions };
58
+ if (diagnostics.length === 0)
59
+ admittedIndexes.set(registry, index);
60
+ return { index, diagnostics };
42
61
  }
43
62
  export function isOriginVisibleFrom(index, ownerOrigin, targetOrigin) {
44
63
  return (ownerOrigin === targetOrigin ||
45
64
  index.dependenciesByOwner.get(ownerOrigin)?.has(targetOrigin) === true);
46
65
  }
47
66
  export function hasNodeDefinition(index, ref, ownerOrigin) {
48
- if (!isOriginVisibleFrom(index, ownerOrigin, ref.origin))
49
- return false;
50
- return (index.domains
51
- .get(ref.origin)
52
- ?.nodeDefinitions.some((candidate) => definitionRefIndex(candidate) === definitionRefIndex(ref)) === true);
67
+ return (isOriginVisibleFrom(index, ownerOrigin, ref.origin) &&
68
+ index.nodeDefinitions.has(definitionRefIndex(ref)));
53
69
  }
54
- function validateDomainRegistry(domain, pointer, revisionRequired, diagnostics) {
70
+ function validateDomainRegistry(domain, entry, revisionRequired, pointer, diagnostics) {
55
71
  if (!DOMAIN_ORIGIN_PATTERN.test(domain.origin)) {
56
- diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', `${pointer}/origin`, 'Registry origin is invalid.'));
72
+ diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', pointer, `Registry origin is invalid, at ${entry}.`));
57
73
  }
58
74
  if ((revisionRequired && domain.revision === undefined) ||
59
75
  (domain.revision !== undefined && !DOMAIN_REVISION_PATTERN.test(domain.revision))) {
60
- diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', `${pointer}/revision`, 'A dependency-closure registry entry requires its exact sha256 revision.'));
76
+ diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', pointer, `A dependency-closure registry entry requires its exact sha256 revision, at ${entry}.`));
61
77
  }
62
78
  for (const ref of domain.nodeDefinitions) {
63
79
  if (ref.origin !== domain.origin) {
64
- diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', `${pointer}/nodeDefinitions`, 'A registry domain can list only its own Node Definitions.'));
80
+ diagnostics.push(diagnostic('VS_REFERENCE_RESOLUTION', pointer, `A registry domain can list only its own Node Definitions, at ${entry}.`));
65
81
  }
66
82
  }
67
83
  }
@@ -1,8 +1,7 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { categoryRanges, complement, mergeRanges } from './unicode.js';
2
3
  const CATEGORY = /^(?:L[lmotu]?|M[cen]?|N[dlo]?|P[cdefios]?|Z[lps]?|S[ckmo]?|C[cfno]?)$/u;
3
- const MAX_CODE_POINTS = 4096;
4
- const MAX_NESTING = 32;
5
- const MAX_REPEAT = 1000;
4
+ const { maxCodePoints, maxNesting, maxRepeat } = limits.regexp;
6
5
  const DOT_RANGES = [
7
6
  [0, 9],
8
7
  [11, 12],
@@ -14,7 +13,7 @@ export function parseIRegexp(source) {
14
13
  if (!isUnicodeScalarString(source) || source === '[^]')
15
14
  return undefined;
16
15
  const characters = Array.from(source);
17
- if (characters.length > MAX_CODE_POINTS)
16
+ if (characters.length > maxCodePoints)
18
17
  return undefined;
19
18
  const parser = new Parser(characters);
20
19
  const expression = parser.expression(undefined, 0);
@@ -30,7 +29,7 @@ class Parser {
30
29
  return this.index === this.characters.length;
31
30
  }
32
31
  expression(stop, depth) {
33
- if (depth > MAX_NESTING)
32
+ if (depth > maxNesting)
34
33
  return undefined;
35
34
  const alternatives = [];
36
35
  const first = this.sequence(stop, depth);
@@ -112,14 +111,14 @@ class Parser {
112
111
  quantifier() {
113
112
  this.index++;
114
113
  const minimum = this.integer();
115
- if (minimum === undefined || minimum > MAX_REPEAT)
114
+ if (minimum === undefined || minimum > maxRepeat)
116
115
  return undefined;
117
116
  let maximum = minimum;
118
117
  if (this.peek() === ',') {
119
118
  this.index++;
120
119
  maximum = this.integer();
121
120
  }
122
- if ((maximum !== undefined && (maximum > MAX_REPEAT || maximum < minimum)) ||
121
+ if ((maximum !== undefined && (maximum > maxRepeat || maximum < minimum)) ||
123
122
  this.peek() !== '}') {
124
123
  return undefined;
125
124
  }
@@ -1,16 +1,9 @@
1
1
  import type { Pattern, RunPattern } from '../compile/model.js';
2
2
  import type { Expression } from './ast.js';
3
3
  /**
4
- * The most states the machine of one I-Regexp may have (`limits.regexp.maxStates`), an
5
- * implementation limit as RFC 9485 §8 allows. Counted repetitions are expanded copy by copy, so the
6
- * parser's bounds on length, nesting and counts alone admit machines of billions of states. The
7
- * counted-run form never expands, so it is not subject to this budget.
8
- */
9
- export declare const MAX_STATES = 10000;
10
- /**
11
- * Thrown when an I-Regexp source compiles to a machine of more than `MAX_STATES` states. A short
12
- * branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and cost seconds per
13
- * validated value (P1-I-01); it is refused before any state is built.
4
+ * Thrown when an I-Regexp source compiles to a machine of more than `limits.regexp.maxStates`
5
+ * states. A short branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and
6
+ * cost seconds per validated value (P1-I-01); it is refused before any state is built.
14
7
  */
15
8
  export declare class PatternComplexityError extends RangeError {
16
9
  readonly limit: number;
@@ -20,7 +13,8 @@ export declare class PatternComplexityError extends RangeError {
20
13
  export declare function compilePattern(source: string): Pattern | RunPattern;
21
14
  /**
22
15
  * Whether a parsed I-Regexp compiles within the state budget: as counted runs, which never expand,
23
- * or as a machine of at most `MAX_STATES` states, counted from the AST before anything is built.
16
+ * or as a machine of at most `limits.regexp.maxStates` states, counted from the AST before anything
17
+ * is built.
24
18
  */
25
19
  export declare function withinStateBudget(expression: Expression): boolean;
26
20
  /**
@@ -1,19 +1,20 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { parseIRegexp } from './ast.js';
2
3
  /**
3
- * The most states the machine of one I-Regexp may have (`limits.regexp.maxStates`), an
4
- * implementation limit as RFC 9485 §8 allows. Counted repetitions are expanded copy by copy, so the
5
- * parser's bounds on length, nesting and counts alone admit machines of billions of states. The
6
- * counted-run form never expands, so it is not subject to this budget.
4
+ * The most states the machine of one I-Regexp may have, an implementation limit as RFC 9485 §8
5
+ * allows. Counted repetitions are expanded copy by copy, so the parser's bounds on length, nesting
6
+ * and counts alone admit machines of billions of states. The counted-run form never expands, so it
7
+ * is not subject to this budget.
7
8
  */
8
- export const MAX_STATES = 10_000;
9
+ const { maxStates } = limits.regexp;
9
10
  /**
10
- * Thrown when an I-Regexp source compiles to a machine of more than `MAX_STATES` states. A short
11
- * branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and cost seconds per
12
- * validated value (P1-I-01); it is refused before any state is built.
11
+ * Thrown when an I-Regexp source compiles to a machine of more than `limits.regexp.maxStates`
12
+ * states. A short branching source such as `(a{0,1000}){0,60}b` would unroll to ~120k states and
13
+ * cost seconds per validated value (P1-I-01); it is refused before any state is built.
13
14
  */
14
15
  export class PatternComplexityError extends RangeError {
15
16
  limit;
16
- constructor(limit = MAX_STATES) {
17
+ constructor(limit = maxStates) {
17
18
  super(`Cannot compile an I-Regexp machine of more than ${limit} states.`);
18
19
  this.limit = limit;
19
20
  this.name = 'PatternComplexityError';
@@ -27,7 +28,7 @@ export function compilePattern(source) {
27
28
  const runs = scalarRuns(expression);
28
29
  if (runs !== undefined)
29
30
  return Object.freeze({ source, runs });
30
- if (states(expression) > MAX_STATES)
31
+ if (states(expression) > maxStates)
31
32
  throw new PatternComplexityError();
32
33
  const builder = new Builder();
33
34
  const fragment = builder.expression(expression);
@@ -40,10 +41,11 @@ export function compilePattern(source) {
40
41
  }
41
42
  /**
42
43
  * Whether a parsed I-Regexp compiles within the state budget: as counted runs, which never expand,
43
- * or as a machine of at most `MAX_STATES` states, counted from the AST before anything is built.
44
+ * or as a machine of at most `limits.regexp.maxStates` states, counted from the AST before anything
45
+ * is built.
44
46
  */
45
47
  export function withinStateBudget(expression) {
46
- return scalarRuns(expression) !== undefined || states(expression) <= MAX_STATES;
48
+ return scalarRuns(expression) !== undefined || states(expression) <= maxStates;
47
49
  }
48
50
  /**
49
51
  * The number of states `Builder` builds for an expression, computed from the AST alone: `states`
@@ -1,5 +1,6 @@
1
+ import { limits } from '../../limits.js';
1
2
  import { parseIRegexp } from './ast.js';
2
- import { MAX_STATES, withinStateBudget } from './compile.js';
3
+ import { withinStateBudget } from './compile.js';
3
4
  /** The refusal of one source, through the same AST and state count as compilation. */
4
5
  export function refuseIRegexp(source) {
5
6
  const expression = parseIRegexp(source);
@@ -13,5 +14,5 @@ export function isIRegexp(source) {
13
14
  }
14
15
  /** The authoring explanation of a `states` refusal of `subject` (`pattern`, `fullMatch`, ...). */
15
16
  export function stateBudgetMessage(subject) {
16
- return `${subject} expands its counted repetitions into more than ${MAX_STATES} states, the I-Regexp implementation limit (RFC 9485 §8); nest fewer or smaller counts.`;
17
+ return `${subject} expands its counted repetitions into more than ${limits.regexp.maxStates} states, the I-Regexp implementation limit (RFC 9485 §8); nest fewer or smaller counts.`;
17
18
  }
@@ -46,7 +46,7 @@
46
46
  "code": "DV_VALUE_SCHEMA",
47
47
  "valueCode": "VS_PATH_EXTENSION",
48
48
  "stage": "rule",
49
- "statement": "x-astrale-path annotates only a direct Property of a callable input object, beside exactly the $ref of the graph NodePath schema, and reads target node, cardinality one and a non-empty set of Node Class refs it accepts.",
49
+ "statement": "x-astrale-path annotates only a direct Property of a callable input object, beside exactly the $ref of the graph NodePath schema.",
50
50
  "positions": [
51
51
  "#/$defs/value-location/anyOf/1/properties/x-astrale-path"
52
52
  ]
@@ -75,6 +75,7 @@
75
75
  "stage": "context",
76
76
  "statement": "A $ref resolves to a location of its own value schema, and x-astrale-path accepts Node Classes of the Domain or of a direct dependency.",
77
77
  "positions": [
78
+ "#/$defs/path-annotation/properties/accepts/items",
78
79
  "#/$defs/value-location/anyOf/1/properties/$ref"
79
80
  ]
80
81
  },
@@ -1384,6 +1385,33 @@
1384
1385
  "maxLength": 253,
1385
1386
  "pattern": "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
1386
1387
  },
1388
+ "path-annotation": {
1389
+ "type": "object",
1390
+ "properties": {
1391
+ "target": {
1392
+ "type": "string",
1393
+ "const": "node"
1394
+ },
1395
+ "cardinality": {
1396
+ "type": "string",
1397
+ "const": "one"
1398
+ },
1399
+ "accepts": {
1400
+ "type": "array",
1401
+ "minItems": 1,
1402
+ "uniqueItems": true,
1403
+ "items": {
1404
+ "$ref": "#/$defs/class-ref"
1405
+ }
1406
+ }
1407
+ },
1408
+ "required": [
1409
+ "target",
1410
+ "cardinality",
1411
+ "accepts"
1412
+ ],
1413
+ "additionalProperties": false
1414
+ },
1387
1415
  "policy-check-expr": {
1388
1416
  "anyOf": [
1389
1417
  {
@@ -2100,7 +2128,7 @@
2100
2128
  "type": "string"
2101
2129
  },
2102
2130
  "x-astrale-path": {
2103
- "type": "object"
2131
+ "$ref": "#/$defs/path-annotation"
2104
2132
  },
2105
2133
  "additionalProperties": {
2106
2134
  "$ref": "#/$defs/value-subschema"
@@ -1,22 +1,11 @@
1
- import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
2
1
  import { canonicalDomainSchemaAfterValidation } from '../canonical/revision.js';
3
2
  import { emptyDomainSchemaLookup } from '../dependency/lookup.js';
4
- import { SchemaError, diagnostic } from '../diagnostic.js';
3
+ import { SchemaError } from '../diagnostic.js';
5
4
  import { rememberDomainClosure } from '../resolution/closure.js';
6
5
  import { validateDomainSchemaSemantics } from '../validation/domain.js';
7
- import { routeDomainSchema } from '../validation/domain.route.js';
6
+ import { structureDomainSchema } from '../validation/domain.route.js';
8
7
  export function acceptDomainSchema(input, lookup = emptyDomainSchemaLookup()) {
9
- let admitted;
10
- try {
11
- admitted = acceptJsonValue(input);
12
- }
13
- catch (error) {
14
- if (error instanceof JsonBoundaryError) {
15
- throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
16
- }
17
- throw error;
18
- }
19
- const structured = routeDomainSchema(admitted);
8
+ const structured = structureDomainSchema(input);
20
9
  const validation = validateDomainSchemaSemantics(structured, lookup);
21
10
  if (validation.diagnostics.length > 0)
22
11
  throw new SchemaError(validation.diagnostics);
@@ -0,0 +1,21 @@
1
+ import type { ClassRef } from '../../addressing/index.js';
2
+ import type { CallableValidation } from '../../domain/validation/create.js';
3
+ import type { CallableDefinition } from '../../language/callable/model/callable.js';
4
+ import type { DomainSchema } from '../model/schema.js';
5
+ /** One admitted callable contract and the validators of its exact owner environment. */
6
+ export interface AcceptedCallable extends CallableValidation {
7
+ readonly definition: CallableDefinition;
8
+ }
9
+ /**
10
+ * Accept one callable contract in the exact environment of its owner Domain, by the one validation
11
+ * every contract passes wherever it is declared (`validateContract`, `validateContractPolicy`,
12
+ * `validateCheckBudget`): the structure of a Function declaration without `ref`, its value schemas
13
+ * against the owner registry, its execution modifier and admission age, and its Policy with the
14
+ * budget of its checks. `receiver` is the owner Node Class whose Node receives the call and which
15
+ * the Policy names `self`: the Class of a Function instance, whose Node carries the contract, or
16
+ * the owner of an instance Method. Without it, as at a Function declaration, the Policy names none.
17
+ * Returns the canonical definition and its validators.
18
+ */
19
+ export declare function acceptCallable(input: unknown, owner: DomainSchema, options?: {
20
+ readonly receiver?: ClassRef;
21
+ }): AcceptedCallable;
@@ -0,0 +1,96 @@
1
+ import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
2
+ import { definitionRefIndex } from '../../addressing/ordering.js';
3
+ import { isBinaryInput } from '../../language/callable/model/callable.js';
4
+ import { measureChecks } from '../../language/policy/analysis/budgets.js';
5
+ import { PolicyTyping } from '../../language/policy/analysis/typing.js';
6
+ import { normalizePolicyChecks } from '../../language/policy/normalization/normalize.js';
7
+ import {} from '../../language/value/evaluate/preparation.js';
8
+ import { analyzeDefinitions } from '../analysis/class.js';
9
+ import { deepFreeze } from '../canonical/freeze.js';
10
+ import { SchemaError, diagnostic } from '../diagnostic.js';
11
+ import { canonicalizeCallable } from '../normalization/domain.js';
12
+ import { lookupForDomainClosure } from '../resolution/closure.js';
13
+ import { buildDomainResolutionContext } from '../resolution/context.js';
14
+ import { callableValidation } from '../resolution/domain.js';
15
+ import { createDomainValueSchemaRegistry } from '../resolution/value.registry.js';
16
+ import { validateContract } from '../validation/callable.js';
17
+ import { parseCallableContract } from '../validation/domain.structure.js';
18
+ import { validateCheckBudget } from '../validation/policy/budgets.js';
19
+ import { PolicyValidationContext } from '../validation/policy/context.js';
20
+ import { validatePolicyReferences } from '../validation/policy/references.js';
21
+ import { validateContractPolicy } from '../validation/policy/uses.js';
22
+ // An accepted DomainSchema is immutable and retains its closure, so its environment is reusable.
23
+ const environments = new WeakMap();
24
+ /**
25
+ * Accept one callable contract in the exact environment of its owner Domain, by the one validation
26
+ * every contract passes wherever it is declared (`validateContract`, `validateContractPolicy`,
27
+ * `validateCheckBudget`): the structure of a Function declaration without `ref`, its value schemas
28
+ * against the owner registry, its execution modifier and admission age, and its Policy with the
29
+ * budget of its checks. `receiver` is the owner Node Class whose Node receives the call and which
30
+ * the Policy names `self`: the Class of a Function instance, whose Node carries the contract, or
31
+ * the owner of an instance Method. Without it, as at a Function declaration, the Policy names none.
32
+ * Returns the canonical definition and its validators.
33
+ */
34
+ export function acceptCallable(input, owner, options = {}) {
35
+ let value;
36
+ try {
37
+ value = acceptJsonValue(input);
38
+ }
39
+ catch (error) {
40
+ if (error instanceof JsonBoundaryError) {
41
+ throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
42
+ }
43
+ throw error;
44
+ }
45
+ const callable = parseCallableContract(value);
46
+ const environment = ownerEnvironment(owner);
47
+ const { receiver } = options;
48
+ if (receiver !== undefined) {
49
+ const resolved = environment.resolution.resolve(receiver);
50
+ if (resolved === undefined ||
51
+ resolved.ref.kind !== 'class' ||
52
+ resolved.kind !== 'node' ||
53
+ definitionRefIndex(resolved.ref) !== definitionRefIndex(receiver) ||
54
+ receiver.origin !== owner.origin) {
55
+ throw new SchemaError([
56
+ diagnostic('DV_REF_RESOLVES', '', 'A receiver must be a Node Class of the owner Domain.'),
57
+ ]);
58
+ }
59
+ }
60
+ const issues = [];
61
+ validateContract(callable, '', environment.registry, issues);
62
+ if (typeof callable.policy === 'object') {
63
+ const policies = new PolicyValidationContext(environment.resolution, environment.analysis);
64
+ // Normalizes the owner's named Policies, which the Policy's checks are typed against.
65
+ validatePolicyReferences(policies);
66
+ validateContractPolicy(policies, new PolicyTyping(policies), callable, '', receiver);
67
+ // A document measures its own check trees (`validatePolicyBudgets`); this one is in none.
68
+ validateCheckBudget(policies, '/policy', measureChecks(normalizePolicyChecks(callable.policy)));
69
+ issues.push(...policies.diagnostics);
70
+ }
71
+ if (issues.length > 0)
72
+ throw new SchemaError(issues);
73
+ const definition = deepFreeze(canonicalizeCallable(callable));
74
+ const validation = callableValidation(isBinaryInput(definition.input) ? definition.input.metadata : definition.input, definition.output.mode === 'binary'
75
+ ? undefined
76
+ : definition.output.mode === 'value'
77
+ ? definition.output.schema
78
+ : definition.output.item, environment.registry,
79
+ // Patterns live with these validators: carried contracts never grow a process-wide pool.
80
+ new Map());
81
+ return Object.freeze({ ...validation, definition });
82
+ }
83
+ function ownerEnvironment(owner) {
84
+ const cached = environments.get(owner);
85
+ if (cached !== undefined)
86
+ return cached;
87
+ const lookup = lookupForDomainClosure(owner);
88
+ const resolution = buildDomainResolutionContext(owner, lookup);
89
+ const environment = Object.freeze({
90
+ resolution,
91
+ analysis: analyzeDefinitions(resolution),
92
+ registry: createDomainValueSchemaRegistry(owner, lookup),
93
+ });
94
+ environments.set(owner, environment);
95
+ return environment;
96
+ }
@@ -9,6 +9,8 @@ export interface Resolve {
9
9
  export declare const resolve: Resolve;
10
10
  export type { SchemaDiagnosticCode as DiagnosticCode } from './diagnostic.js';
11
11
  export { acceptDomainSchema as accept } from './admission/accept.js';
12
+ export { acceptCallable } from './admission/callable.js';
13
+ export type { AcceptedCallable } from './admission/callable.js';
12
14
  export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDomainSchema as serialize, } from './codec/schema.js';
13
15
  export { revisionOfDomainSchema as revision } from './canonical/revision.js';
14
16
  export { ANNOTATION_KEYWORDS, FIELD_ROLES, callableFingerprint } from './compatibility/index.js';
@@ -22,6 +24,11 @@ export { SchemaError as Error } from './diagnostic.js';
22
24
  export type { SchemaDiagnostic as Diagnostic } from './diagnostic.js';
23
25
  export { RULES } from './rule.js';
24
26
  export type { SchemaRule as Rule, SchemaRuleStage as RuleStage } from './rule.js';
27
+ export { limits } from '../language/limits.js';
28
+ export type { LimitKey } from '../language/limits.js';
29
+ export { measurePolicies } from './validation/policy/budgets.js';
30
+ export type { PolicyMeasures } from './validation/policy/budgets.js';
31
+ export type { CheckMeasure, PolicyMeasure } from '../language/policy/analysis/budgets.js';
25
32
  export type { DomainSchemaLookup as Lookup } from './dependency/lookup.js';
26
33
  export type { DomainSchemaHeader as Header } from './codec/header.js';
27
34
  export type { DomainDependency } from '../addressing/index.js';
@@ -2,6 +2,7 @@
2
2
  import { resolve as resolveDomainSchema } from './resolution/domain.js';
3
3
  export const resolve = resolveDomainSchema;
4
4
  export { acceptDomainSchema as accept } from './admission/accept.js';
5
+ export { acceptCallable } from './admission/callable.js';
5
6
  export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDomainSchema as serialize, } from './codec/schema.js';
6
7
  export { revisionOfDomainSchema as revision } from './canonical/revision.js';
7
8
  export { ANNOTATION_KEYWORDS, FIELD_ROLES, callableFingerprint } from './compatibility/index.js';
@@ -11,5 +12,7 @@ export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/form
11
12
  export { compile, load } from '../compiled/index.js';
12
13
  export { SchemaError as Error } from './diagnostic.js';
13
14
  export { RULES } from './rule.js';
15
+ export { limits } from '../language/limits.js';
16
+ export { measurePolicies } from './validation/policy/budgets.js';
14
17
  /** The versioned value-schema grammar embedded in declarations. */
15
18
  export * as values from './validation/value-api.js';
@@ -1,2 +1,4 @@
1
+ import type { CallableDefinition } from '../../language/callable/model/callable.js';
1
2
  import type { DomainSchemaIR } from '../model/schema.js';
2
3
  export declare function normalizeDomainSchema(schema: DomainSchemaIR): DomainSchemaIR;
4
+ export declare function canonicalizeCallable(value: CallableDefinition): CallableDefinition;
@@ -118,7 +118,7 @@ function canonicalizeFunction(value) {
118
118
  ...canonicalizeCallable(value),
119
119
  };
120
120
  }
121
- function canonicalizeCallable(value) {
121
+ export function canonicalizeCallable(value) {
122
122
  const base = {
123
123
  ...withDescription(value),
124
124
  input: isBinaryInput(value.input)
@@ -1,6 +1,6 @@
1
- import { dependencyLimits } from '../../addressing/domain-dependency.js';
2
1
  import { definitionRefIndex } from '../../addressing/ordering.js';
3
2
  import { DOMAIN_REVISION_PATTERN } from '../../addressing/patterns.js';
3
+ import { limits } from '../../language/limits.js';
4
4
  import { escapePointerToken } from '../../language/value/syntax/pointer.js';
5
5
  import { revisionOfDomainSchema } from '../canonical/revision.js';
6
6
  import { diagnostic } from '../diagnostic.js';
@@ -11,8 +11,8 @@ export function buildDomainResolutionContext(candidate, lookup) {
11
11
  const dependencies = new Map();
12
12
  const definitions = new Map();
13
13
  const directOrigins = new Set();
14
- if (Object.keys(candidate.dependencies).length > dependencyLimits.direct) {
15
- diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A Domain may declare at most ${dependencyLimits.direct} direct dependency aliases.`));
14
+ if (Object.keys(candidate.dependencies).length > limits.dependency.direct) {
15
+ diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A Domain may declare at most ${limits.dependency.direct} direct dependency aliases.`));
16
16
  }
17
17
  for (const [alias, dependency] of Object.entries(candidate.dependencies)) {
18
18
  const pointer = `/dependencies/${escapePointerToken(alias)}`;
@@ -121,16 +121,16 @@ function loadDependencyClosure(candidate, dependencies, lookup, diagnostics) {
121
121
  continue;
122
122
  }
123
123
  dependencies.set(nested.origin, nested);
124
- if (dependencies.size + 1 > dependencyLimits.closure) {
124
+ if (dependencies.size + 1 > limits.dependency.closure) {
125
125
  if (!closureLimitReported) {
126
- diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A dependency closure may contain at most ${dependencyLimits.closure} Domains.`));
126
+ diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A dependency closure may contain at most ${limits.dependency.closure} Domains.`));
127
127
  closureLimitReported = true;
128
128
  }
129
129
  continue;
130
130
  }
131
131
  const depth = frame.depth + 1;
132
- if (depth > dependencyLimits.depth) {
133
- diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', pointer, `Dependency depth ${depth} exceeds the maximum ${dependencyLimits.depth}.`));
132
+ if (depth > limits.dependency.depth) {
133
+ diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', pointer, `Dependency depth ${depth} exceeds the maximum ${limits.dependency.depth}.`));
134
134
  continue;
135
135
  }
136
136
  const key = `${dependency.origin}\u0000${dependency.revision}`;
@@ -1,7 +1,12 @@
1
1
  import type { SourceDomain } from '../../domain/model/domain.js';
2
+ import type { CallableValidation } from '../../domain/validation/create.js';
3
+ import type { JsonSchema } from '../../language/value/model/schema.js';
4
+ import type { ValueSchemaRegistry } from '../../language/value/reference/registry.js';
2
5
  import type { DomainSchema } from '../model/schema.js';
6
+ import { type PatternPool } from '../../language/value/evaluate/preparation.js';
3
7
  /**
4
8
  * Resolve one accepted Schema through its exact retained source closure. Acceptance validated it
5
9
  * against that closure, which is the only way a Schema retains one, so it is not validated again.
6
10
  */
7
11
  export declare function resolve<const S extends DomainSchema>(schema: S): SourceDomain<S>;
12
+ export declare function callableValidation(input: JsonSchema, output: JsonSchema | undefined, registry: ValueSchemaRegistry, patterns: PatternPool): CallableValidation;
@@ -88,7 +88,7 @@ function sourceValidation(closure, registries) {
88
88
  }
89
89
  return createDomainValidation(properties, callables);
90
90
  }
91
- function callableValidation(input, output, registry, patterns) {
91
+ export function callableValidation(input, output, registry, patterns) {
92
92
  const prepared = prepareValueSchema(input, registry, '', patterns);
93
93
  if (!prepared.compiled) {
94
94
  throw new TypeError(`Accepted callable input cannot be prepared: ${prepared.message}`);
@@ -1,4 +1,5 @@
1
1
  import type { SchemaDiagnosticCode } from './diagnostic.js';
2
+ import { type LimitKey } from '../language/limits.js';
2
3
  /**
3
4
  * What a rule reads to decide. `structure`: the shape of the document alone. `rule`: the whole
4
5
  * document. `context`: the document and its environment, so one document can be accepted in one
@@ -21,6 +22,14 @@ export interface SchemaRule {
21
22
  readonly statement: string;
22
23
  /** What the feature the rule guards enforces on data or on calls, where it enforces something. */
23
24
  readonly enforces?: string;
25
+ /** The limits of `limits` the rule enforces, by key, where it enforces some. */
26
+ readonly limits?: readonly [LimitKey, ...LimitKey[]];
27
+ /**
28
+ * The conditions of the published JSON Schema of the document that state the rule, where some
29
+ * do: the URI fragment of each `if` or `not` subschema, `#/$defs/edge-class/if`, whose failure
30
+ * refuses a document with this rule's code.
31
+ */
32
+ readonly conditions?: readonly [string, ...string[]];
24
33
  }
25
34
  /**
26
35
  * Every Schema diagnostic code with the rule it reports. A code added to the DSL does not compile