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

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 (57) 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/language/core/facts.d.ts +17 -4
  17. package/dist/v1/language/core/facts.js +26 -5
  18. package/dist/v1/language/limits.d.ts +64 -0
  19. package/dist/v1/language/limits.js +60 -0
  20. package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
  21. package/dist/v1/language/policy/analysis/budgets.js +30 -20
  22. package/dist/v1/language/policy/analysis/checks.js +3 -2
  23. package/dist/v1/language/policy/analysis/typing.js +4 -3
  24. package/dist/v1/language/validation/rule.d.ts +3 -0
  25. package/dist/v1/language/validation/rule.js +5 -1
  26. package/dist/v1/language/value/regexp/ast.js +6 -7
  27. package/dist/v1/language/value/regexp/compile.d.ts +5 -11
  28. package/dist/v1/language/value/regexp/compile.js +14 -12
  29. package/dist/v1/language/value/regexp/syntax.js +3 -2
  30. package/dist/v1/schema/admission/accept.js +3 -14
  31. package/dist/v1/schema/admission/callable.d.ts +21 -0
  32. package/dist/v1/schema/admission/callable.js +96 -0
  33. package/dist/v1/schema/index.d.ts +7 -0
  34. package/dist/v1/schema/index.js +3 -0
  35. package/dist/v1/schema/normalization/domain.d.ts +2 -0
  36. package/dist/v1/schema/normalization/domain.js +1 -1
  37. package/dist/v1/schema/resolution/context.js +7 -7
  38. package/dist/v1/schema/resolution/domain.d.ts +5 -0
  39. package/dist/v1/schema/resolution/domain.js +1 -1
  40. package/dist/v1/schema/rule.d.ts +3 -0
  41. package/dist/v1/schema/rule.js +16 -3
  42. package/dist/v1/schema/validation/callable.d.ts +33 -3
  43. package/dist/v1/schema/validation/callable.js +49 -15
  44. package/dist/v1/schema/validation/core.js +16 -10
  45. package/dist/v1/schema/validation/domain.js +2 -2
  46. package/dist/v1/schema/validation/domain.route.d.ts +6 -1
  47. package/dist/v1/schema/validation/domain.route.js +18 -0
  48. package/dist/v1/schema/validation/domain.structure.d.ts +28 -26
  49. package/dist/v1/schema/validation/domain.structure.js +22 -6
  50. package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
  51. package/dist/v1/schema/validation/policy/budgets.js +89 -16
  52. package/dist/v1/schema/validation/policy/references.js +2 -1
  53. package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
  54. package/dist/v1/schema/validation/policy/uses.js +28 -48
  55. package/dist/v1/schema/validation/value.d.ts +3 -0
  56. package/dist/v1/schema/validation/value.js +3 -9
  57. package/package.json +1 -1
@@ -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
  }
@@ -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,8 @@ 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[]];
24
27
  }
25
28
  /**
26
29
  * Every Schema diagnostic code with the rule it reports. A code added to the DSL does not compile
@@ -1,4 +1,5 @@
1
1
  import { defaultLimits } from '@astrale-os/kernel-dsl/value';
2
+ import { limits } from '../language/limits.js';
2
3
  import { deepFreeze } from './canonical/freeze.js';
3
4
  const ANY = ['{/path*}'];
4
5
  const CHECKS = [
@@ -8,6 +9,7 @@ const CHECKS = [
8
9
  ];
9
10
  const DEPENDENCIES = ['/dependencies/{alias}', '/dependencies'];
10
11
  const count = (value) => value.toLocaleString('en-US');
12
+ const { dependency, policy } = limits;
11
13
  const at = (field) => [
12
14
  `/functions/{function}${field}`,
13
15
  `/classes/{class}/methods/{method}${field}`,
@@ -68,7 +70,7 @@ export const RULES = deepFreeze({
68
70
  DV_CORE_EDGE_CONSTRAINTS: {
69
71
  stage: 'context',
70
72
  positions: ['/core/edges', '/core/edges/{index}', '/core/nodes/{slug}'],
71
- statement: 'The Core Edges of a Schema hold the noSelf and acyclic constraints of their Classes, and every Core Node, and every Definition a Core Edge names, the Edge counts of the endpoints that accept it, a Definition its maximum only.',
73
+ statement: 'The Core Edges of a Schema hold the noSelf and acyclic constraints of their Classes, and every Core Node, and every Definition a Core Edge names, the Edge counts of the concrete Edge Classes whose endpoints accept it, each over its own Edges, a Definition its maximum only.',
72
74
  },
73
75
  DV_CORE_EDGE_KEY: {
74
76
  stage: 'rule',
@@ -128,7 +130,8 @@ export const RULES = deepFreeze({
128
130
  DV_DEPENDENCY_LIMITS: {
129
131
  stage: 'context',
130
132
  positions: ['/dependencies', '/dependencies/{alias}'],
131
- statement: 'A Domain has at most 32 direct dependencies, a closure of at most 256 Domains and a dependency depth of at most 32.',
133
+ statement: `A Domain has at most ${dependency.direct} direct dependencies, a closure of at most ${dependency.closure} Domains and a dependency depth of at most ${dependency.depth}.`,
134
+ limits: ['dependency.direct', 'dependency.closure', 'dependency.depth'],
132
135
  },
133
136
  DV_DEPENDENCY_REVISION_COHERENCE: {
134
137
  stage: 'context',
@@ -248,7 +251,16 @@ export const RULES = deepFreeze({
248
251
  PL_BUDGET: {
249
252
  stage: 'rule',
250
253
  positions: ['/policies/{policy}', ...CHECKS],
251
- statement: 'A Policy stays within its budgets once expanded: pattern depth 4, 8 normalized branches, 6 Edge predicates a branch and 4 referenced Domains; and checks within depth 4, 8 branches and 8 leaves.',
254
+ statement: `A Policy stays within its budgets once expanded: pattern depth ${policy.patternDepth}, ${policy.normalizedBranches} normalized branches, ${policy.edgePredicates} Edge predicates a branch and ${policy.referencedDomains} referenced Domains; and checks within depth ${policy.checkDepth}, ${policy.checkBranches} branches and ${policy.checkLeaves} leaves.`,
255
+ limits: [
256
+ 'policy.patternDepth',
257
+ 'policy.normalizedBranches',
258
+ 'policy.edgePredicates',
259
+ 'policy.referencedDomains',
260
+ 'policy.checkDepth',
261
+ 'policy.checkBranches',
262
+ 'policy.checkLeaves',
263
+ ],
252
264
  },
253
265
  PL_COMPOSITION: {
254
266
  stage: 'rule',
@@ -351,6 +363,7 @@ export const RULES = deepFreeze({
351
363
  stage: 'structure',
352
364
  positions: ANY,
353
365
  statement: 'A string, list or number is no greater than the bound of its position.',
366
+ limits: ['policy.variablesPerExists', 'policy.repetition'],
354
367
  },
355
368
  STRUCTURE_MINIMUM: {
356
369
  stage: 'structure',
@@ -1,7 +1,37 @@
1
+ import type { ClassRef } from '../../addressing/index.js';
2
+ import type { CallableDefinition } from '../../language/callable/model/callable.js';
3
+ import type { ValueSchemaRegistry } from '../../language/value/reference/registry.js';
1
4
  import type { SchemaDiagnostic } from '../diagnostic.js';
2
5
  import type { DomainSchemaIR } from '../model/schema.js';
3
6
  /**
4
- * Validate the ref of every local Function, and the execution modifier and admission age of every
5
- * local Function and Node Method.
7
+ * One callable contract a Domain schema declares: where it is written, and the Node Class that
8
+ * receives its call, which its Policy names `self`. A contract is validated by the same rules
9
+ * wherever it is: in a Domain schema, at its declaration, or on the Node of a Function instance,
10
+ * which carries it (`acceptCallable`).
6
11
  */
7
- export declare function validateCallables(schema: DomainSchemaIR): readonly SchemaDiagnostic[];
12
+ export interface DeclaredContract {
13
+ readonly contract: CallableDefinition;
14
+ /** Its declaration's JSON Pointer, which its diagnostics point within. */
15
+ readonly pointer: string;
16
+ /** The owner of an instance Method; a Function and a static Method have none. */
17
+ readonly receiver?: ClassRef;
18
+ /** The Node Class and name of a Method; absent for a Function. */
19
+ readonly method?: {
20
+ readonly owner: ClassRef;
21
+ readonly name: string;
22
+ };
23
+ }
24
+ /** Every callable contract one Domain schema declares: each Function, then each Node Method. */
25
+ export declare function declaredContracts(schema: DomainSchemaIR): readonly DeclaredContract[];
26
+ /**
27
+ * Validate the ref of every Function declaration, then the value schemas, execution modifier and
28
+ * admission age of every declared contract.
29
+ */
30
+ export declare function validateContracts(schema: DomainSchemaIR, registry: ValueSchemaRegistry): readonly SchemaDiagnostic[];
31
+ /**
32
+ * Validate one callable contract written at `pointer`, except its Policy (`validateContractPolicy`):
33
+ * its value schemas against the owner registry, its execution modifier and its admission age.
34
+ */
35
+ export declare function validateContract(contract: CallableDefinition, pointer: string, registry: ValueSchemaRegistry, diagnostics: SchemaDiagnostic[]): void;
36
+ /** Validate the execution modifier and admission age of one callable contract at `pointer`. */
37
+ export declare function validateCallableModifiers(declaration: CallableDefinition, pointer: string, diagnostics: SchemaDiagnostic[]): void;
@@ -4,32 +4,66 @@ import { isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from '../../languag
4
4
  import { isBinaryInput } from '../../language/callable/model/callable.js';
5
5
  import { escapePointerToken } from '../../language/value/syntax/pointer.js';
6
6
  import { diagnostic } from '../diagnostic.js';
7
- /**
8
- * Validate the ref of every local Function, and the execution modifier and admission age of every
9
- * local Function and Node Method.
10
- */
11
- export function validateCallables(schema) {
12
- const diagnostics = [];
7
+ import { validateCallable } from './value.js';
8
+ /** Every callable contract one Domain schema declares: each Function, then each Node Method. */
9
+ export function declaredContracts(schema) {
10
+ const contracts = [];
13
11
  for (const [name, declaration] of Object.entries(schema.functions)) {
14
- const pointer = `/functions/${escapePointerToken(name)}`;
15
- const ref = { origin: schema.origin, kind: 'function', name };
16
- if (!equalDefinitionRefs(declaration.ref, ref)) {
17
- diagnostics.push(diagnostic('DV_REF_RESOLVES', `${pointer}/ref`, `Function ref must be exactly ${Key.of(ref)}.`, Key.of(declaration.ref)));
18
- }
19
- validateExecution(declaration, pointer, diagnostics);
20
- validateAdmission(declaration, pointer, diagnostics);
12
+ contracts.push({ contract: declaration, pointer: `/functions/${escapePointerToken(name)}` });
21
13
  }
22
14
  for (const [name, declaration] of Object.entries(schema.classes)) {
23
15
  if (declaration.kind !== 'node')
24
16
  continue;
17
+ const owner = { origin: schema.origin, kind: 'class', name };
25
18
  for (const [method, value] of Object.entries(declaration.methods)) {
26
19
  const pointer = `/classes/${escapePointerToken(name)}/methods/${escapePointerToken(method)}`;
27
- validateExecution(value, pointer, diagnostics);
28
- validateAdmission(value, pointer, diagnostics);
20
+ contracts.push({
21
+ contract: value,
22
+ pointer,
23
+ ...(value.static ? {} : { receiver: owner }),
24
+ method: { owner, name: method },
25
+ });
29
26
  }
30
27
  }
28
+ return contracts;
29
+ }
30
+ /**
31
+ * Validate the ref of every Function declaration, then the value schemas, execution modifier and
32
+ * admission age of every declared contract.
33
+ */
34
+ export function validateContracts(schema, registry) {
35
+ const diagnostics = [];
36
+ for (const [name, declaration] of Object.entries(schema.functions)) {
37
+ validateFunctionRef(schema.origin, name, declaration.ref, diagnostics);
38
+ }
39
+ for (const { contract, pointer } of declaredContracts(schema)) {
40
+ validateContract(contract, pointer, registry, diagnostics);
41
+ }
31
42
  return Object.freeze(diagnostics);
32
43
  }
44
+ /**
45
+ * A Function declaration names itself by its ref: its own key in its own Domain. The ref belongs to
46
+ * the declaration, not to its contract, which a Node carries without one.
47
+ */
48
+ function validateFunctionRef(origin, name, declared, diagnostics) {
49
+ const ref = { origin, kind: 'function', name };
50
+ if (equalDefinitionRefs(declared, ref))
51
+ return;
52
+ diagnostics.push(diagnostic('DV_REF_RESOLVES', `/functions/${escapePointerToken(name)}/ref`, `Function ref must be exactly ${Key.of(ref)}.`, Key.of(declared)));
53
+ }
54
+ /**
55
+ * Validate one callable contract written at `pointer`, except its Policy (`validateContractPolicy`):
56
+ * its value schemas against the owner registry, its execution modifier and its admission age.
57
+ */
58
+ export function validateContract(contract, pointer, registry, diagnostics) {
59
+ validateCallable(contract, pointer, registry, diagnostics);
60
+ validateCallableModifiers(contract, pointer, diagnostics);
61
+ }
62
+ /** Validate the execution modifier and admission age of one callable contract at `pointer`. */
63
+ export function validateCallableModifiers(declaration, pointer, diagnostics) {
64
+ validateExecution(declaration, pointer, diagnostics);
65
+ validateAdmission(declaration, pointer, diagnostics);
66
+ }
33
67
  /**
34
68
  * An admission age is a whole number of seconds within the language ceiling. It has no zero
35
69
  * spelling: an absent admission age is the one canonical form of a call admitted afresh.
@@ -3,7 +3,7 @@ import { Key } from '../../addressing/key.js';
3
3
  import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
4
4
  import { isMethodExecutable } from '../../language/callable/executable.js';
5
5
  import { coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueKeys, nodeUniqueKeys, } from '../../language/class/unique.js';
6
- import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, } from '../../language/core/facts.js';
6
+ import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, DOMAIN_ENDPOINT_CLASS, } from '../../language/core/facts.js';
7
7
  import { policyObjectKey, policyObjectOrigin, projectedMemberClass, } from '../../language/policy/model/policy.js';
8
8
  import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
9
9
  import { escapePointerToken } from '../../language/value/syntax/pointer.js';
@@ -83,7 +83,9 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
83
83
  continue;
84
84
  const endpointsSatisfy = coreEndpointsSatisfy(declaration, source, target, (endpoint, accepts) => endpointSatisfies(environment, endpoint, accepts));
85
85
  if (!endpointsSatisfy) {
86
- diagnostics.push(diagnostic('DV_CORE_EDGE_CLASS', pointer, `Core Edge endpoints do not satisfy ${formatDefinitionRef(edge.class)}.`, formatDefinitionRef(edge.class)));
86
+ diagnostics.push(diagnostic('DV_CORE_EDGE_CLASS', pointer, `Core Edge endpoints do not satisfy ${formatDefinitionRef(edge.class)}.${source.kind === 'domain' || target.kind === 'domain'
87
+ ? ` The owning Domain is a ${formatDefinitionRef(DOMAIN_ENDPOINT_CLASS)} Node: the endpoint that names it must accept that Class or one it extends.`
88
+ : ''}`, formatDefinitionRef(edge.class)));
87
89
  continue;
88
90
  }
89
91
  edges.push({ source, target, declaration, value: edge, pointer });
@@ -92,7 +94,7 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
92
94
  }
93
95
  function resolveCoreEndpoint(environment, ref, pointer, nodesByRef, diagnostics) {
94
96
  if (ref.kind === 'domain')
95
- return { kind: 'domain', ref, key: 'domain' };
97
+ return { kind: 'domain', ref, key: 'domain', classRef: DOMAIN_ENDPOINT_CLASS };
96
98
  const undeclared = () => {
97
99
  const key = policyObjectKey(ref);
98
100
  diagnostics.push(diagnostic('DV_CORE_REF', pointer, `Core endpoint ${key} does not name a declared local Definition.`, key));
@@ -122,10 +124,14 @@ function declaresProjectedMember(environment, ref) {
122
124
  return Object.hasOwn(candidate.policies, ref.name);
123
125
  return Object.hasOwn(candidate.views, ref.name);
124
126
  }
127
+ /**
128
+ * A Core Node is admitted by its own authored Class, a projected member by the Kernel declaration
129
+ * Class it projects as, and the owning Domain by the Kernel `Domain` Class of its Node: the write
130
+ * that projects the Edge binds exactly that Node, so an Edge Class that does not accept it can
131
+ * never be installed (DV-CORE-EDGE-CLASS).
132
+ */
125
133
  function endpointSatisfies(environment, endpoint, accepts) {
126
- // The owning Domain is a graph-projection identity, not a Definition declared by this DSL.
127
- // Its concrete Class contract is therefore admitted by the projection compiler.
128
- return endpoint.kind === 'domain' || classSatisfies(environment, endpoint.classRef, accepts);
134
+ return classSatisfies(environment, endpoint.classRef, accepts);
129
135
  }
130
136
  function validateCreationProperties(definition, properties, registry, code, pointer, diagnostics) {
131
137
  const schema = {
@@ -181,11 +187,11 @@ function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
181
187
  report: (issue) => diagnostics.push(coreEdgeDiagnostic(issue)),
182
188
  });
183
189
  }
184
- /** A projected member's degree is reported at the Edge that names it. */
190
+ /** A projected member's degree, and the owning Domain's, is reported at the Edge that names it. */
185
191
  function factEndpoint(endpoint, pointer) {
186
- return endpoint.kind === 'member'
187
- ? { key: endpoint.key, member: { class: endpoint.classRef, pointer } }
188
- : endpoint;
192
+ return endpoint.kind === 'core'
193
+ ? endpoint
194
+ : { key: endpoint.key, member: { class: endpoint.classRef, pointer } };
189
195
  }
190
196
  function coreEdgeDiagnostic(issue) {
191
197
  switch (issue.kind) {
@@ -2,7 +2,7 @@ import { orderAndDeduplicate } from '../../language/validation/diagnostic.js';
2
2
  import { analyzeDefinitions } from '../analysis/class.js';
3
3
  import { buildDomainResolutionContext } from '../resolution/context.js';
4
4
  import { createValueSchemaRegistry } from '../resolution/value.registry.js';
5
- import { validateCallables } from './callable.js';
5
+ import { validateContracts } from './callable.js';
6
6
  import { validateDefinitions } from './class.js';
7
7
  import { validateCore } from './core.js';
8
8
  import { validatePolicies } from './policy/validate.js';
@@ -16,7 +16,7 @@ export function validateDomainSchemaSemantics(schema, lookup) {
16
16
  ...context.diagnostics,
17
17
  ...validateEmbeddedValueSchemas(schema, registry),
18
18
  ...validateDefinitions(context, analysis),
19
- ...validateCallables(schema),
19
+ ...validateContracts(schema, registry),
20
20
  ...validatePolicies(context, analysis),
21
21
  ...validateViews(context),
22
22
  ...validateCore(context, analysis, registry),
@@ -1,4 +1,9 @@
1
- import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
1
+ import { type Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
2
  import type { DomainSchemaIR } from '../model/schema.js';
3
+ /**
4
+ * Read an unknown value as a Domain Schema document of its exact language contract: portable JSON
5
+ * (`DV_JSON_VALUE`), then the structure of its format and version. Throws `SchemaError`.
6
+ */
7
+ export declare function structureDomainSchema(input: unknown): DomainSchemaIR;
3
8
  /** Select the exact Domain language contract before entering its structural grammar. */
4
9
  export declare function routeDomainSchema(input: JsonValue): DomainSchemaIR;
@@ -1,9 +1,27 @@
1
+ import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
1
2
  import { SchemaError, diagnostic } from '../diagnostic.js';
2
3
  import { DOMAIN_FORMAT, DOMAIN_VERSION } from '../model/format.js';
3
4
  import { parseDomainSchemaStructure } from './domain.structure.js';
4
5
  const decoders = new Map([
5
6
  [DOMAIN_FORMAT, new Map([[DOMAIN_VERSION, parseDomainSchemaStructure]])],
6
7
  ]);
8
+ /**
9
+ * Read an unknown value as a Domain Schema document of its exact language contract: portable JSON
10
+ * (`DV_JSON_VALUE`), then the structure of its format and version. Throws `SchemaError`.
11
+ */
12
+ export function structureDomainSchema(input) {
13
+ let admitted;
14
+ try {
15
+ admitted = acceptJsonValue(input);
16
+ }
17
+ catch (error) {
18
+ if (error instanceof JsonBoundaryError) {
19
+ throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
20
+ }
21
+ throw error;
22
+ }
23
+ return routeDomainSchema(admitted);
24
+ }
7
25
  /** Select the exact Domain language contract before entering its structural grammar. */
8
26
  export function routeDomainSchema(input) {
9
27
  if (!isJsonObject(input))