@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
@@ -3,7 +3,7 @@ import { PropertyKey as SemanticPropertyKey } from '../../addressing/index.js';
3
3
  import { CORE_SLUG_PATTERN, DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, PROPERTY_KEY_PATTERN, } from '../../addressing/patterns.js';
4
4
  import { MEDIA_TYPE_PATTERN } from '../../language/class/model/data.js';
5
5
  import * as metadata from '../../language/definition/metadata.js';
6
- import { MAX_REPEAT, MAX_VARIABLES } from '../../language/policy/analysis/budgets.js';
6
+ import { limits } from '../../language/limits.js';
7
7
  import { BLOB_VALUE_KIND, isBlobValueSchema } from '../../language/value/model/blob.js';
8
8
  import { escapePointerToken } from '../../language/value/syntax/pointer.js';
9
9
  import { SchemaError, diagnostic, } from '../diagnostic.js';
@@ -245,8 +245,8 @@ const policyVariableSchema = union([
245
245
  strictObject({ variable: policyPatternVariableSchema, selector: policyVariableSelectorSchema }),
246
246
  ]).register(documentJsonSchema, { id: 'policy-variable' });
247
247
  const policyRepeatSchema = strictObject({
248
- min: number().int().min(0).max(MAX_REPEAT),
249
- max: number().int().min(0).max(MAX_REPEAT),
248
+ min: number().int().min(0).max(limits.policy.repetition),
249
+ max: number().int().min(0).max(limits.policy.repetition),
250
250
  }).register(documentJsonSchema, { id: 'policy-repeat' });
251
251
  const policyPatternExpressionSchema = lazy(() => union([
252
252
  strictObject({
@@ -260,7 +260,7 @@ const policyPatternExpressionSchema = lazy(() => union([
260
260
  }),
261
261
  strictObject({
262
262
  exists: strictObject({
263
- nodes: nonEmpty(array(policyVariableSchema).max(MAX_VARIABLES)),
263
+ nodes: nonEmpty(array(policyVariableSchema).max(limits.policy.variablesPerExists)),
264
264
  where: policyPatternExpressionSchema,
265
265
  }),
266
266
  }),
@@ -428,13 +428,18 @@ const classDeclarationSchema = discriminatedUnion('kind', [
428
428
  nodeClassSchema,
429
429
  edgeClassSchema,
430
430
  ]).register(documentJsonSchema, { id: 'class-definition' });
431
- const functionDefinitionSchema = strictObject({
432
- ref: functionRefSchema,
431
+ const functionContractShape = {
433
432
  ...callableShape,
434
433
  input: union([binaryInputSchema, valueFunctionInputSchema]),
434
+ };
435
+ const functionDefinitionSchema = strictObject({
436
+ ref: functionRefSchema,
437
+ ...functionContractShape,
435
438
  })
436
439
  .refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'))
437
440
  .register(documentJsonSchema, { id: 'function-definition', ...authorizedPolicy });
441
+ // A callable contract has the structure of a Function declaration without its ref, wherever it is.
442
+ const callableContractSchema = strictObject(functionContractShape).refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'));
438
443
  const policyExpressionSchema = union([
439
444
  strictObject({
440
445
  match: policyPatternExpressionSchema,
@@ -516,6 +521,17 @@ function lostProtoKeys(input, parsed, path) {
516
521
  ...Object.keys(parsed).flatMap((key) => lostProtoKeys(Reflect.get(input, key), Reflect.get(parsed, key), [...path, key])),
517
522
  ];
518
523
  }
524
+ /** Parse the structure of one callable contract: a Function declaration without its ref. */
525
+ export function parseCallableContract(input) {
526
+ const result = callableContractSchema.safeParse(input);
527
+ if (!result.success) {
528
+ throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
529
+ }
530
+ const lost = lostProtoKeys(input, result.data, []);
531
+ if (lost.length > 0)
532
+ throw new SchemaError(lost);
533
+ return result.data;
534
+ }
519
535
  /**
520
536
  * The structure admits the document the model types: this parameter checks one direction, and
521
537
  * `domain.structure.typecheck.ts` both. The brands are the model's own proofs from here on.
@@ -1,4 +1,35 @@
1
+ import type { PolicyExpression } from '../../../language/policy/model/policy.js';
2
+ import type { DomainSchemaIR } from '../../model/schema.js';
1
3
  import type { PolicyValidationContext } from './context.js';
2
- import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks } from '../../../language/policy/analysis/budgets.js';
3
- export { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, };
4
+ import { type CheckMeasure, type PolicyMeasure } from '../../../language/policy/analysis/budgets.js';
5
+ /** What `PL_BUDGET` compares with `limits.policy` in one document, frozen. */
6
+ export interface PolicyMeasures {
7
+ /** Each named Policy the DSL can measure, by name. */
8
+ readonly policies: Readonly<Record<string, PolicyMeasure>>;
9
+ /**
10
+ * The named Policies the DSL cannot measure, in document order: a composition that reaches
11
+ * itself, or that names a Policy the document does not declare or one it cannot measure.
12
+ */
13
+ readonly unmeasured: readonly string[];
14
+ /**
15
+ * Each check tree by the JSON Pointer of its position: the Policy of a Function or a Method, or
16
+ * the read or traverse rule of a Node Class.
17
+ */
18
+ readonly checks: Readonly<Record<string, CheckMeasure>>;
19
+ }
20
+ /**
21
+ * Measure the Policies of a document as `PL_BUDGET` does: it reads the document's structure alone,
22
+ * so a document the structure admits is measured whatever its other rules decide. Throws
23
+ * `SchemaError` with the diagnostics of a document that is not portable JSON or that its
24
+ * structure refuses.
25
+ */
26
+ export declare function measurePolicies(input: unknown): PolicyMeasures;
27
+ /** The measures of one structured document, from its Policies once normalized. */
28
+ export declare function measureDomainPolicies(schema: DomainSchemaIR, normalized?: ReadonlyMap<string, PolicyExpression>): PolicyMeasures;
29
+ /** `PL_BUDGET`: every measure of the document within `limits.policy`. */
4
30
  export declare function validatePolicyBudgets(context: PolicyValidationContext): void;
31
+ /**
32
+ * `PL_BUDGET` for one check tree at `pointer`, from its measure: the document's trees, and the
33
+ * checks of a contract a Node carries, which no document holds.
34
+ */
35
+ export declare function validateCheckBudget(context: PolicyValidationContext, pointer: string, measure: CheckMeasure): void;
@@ -1,25 +1,98 @@
1
- import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, policyMeasurer, } from '../../../language/policy/analysis/budgets.js';
1
+ import { limits } from '../../../language/limits.js';
2
+ import { exceededLimits, measureChecks, policyMeasurer, } from '../../../language/policy/analysis/budgets.js';
3
+ import { isClassRuleChecks } from '../../../language/policy/model/policy.js';
4
+ import { normalizePolicyChecks, normalizePolicyExpression, } from '../../../language/policy/normalization/normalize.js';
2
5
  import { escapePointerToken } from '../../../language/value/syntax/pointer.js';
3
- export { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, };
4
- export function validatePolicyBudgets(context) {
5
- const measurePolicy = policyMeasurer(context.environment.candidate.origin, context.normalized, (ref) => context.isLocalPolicy(ref));
6
- for (const name of Object.keys(context.environment.candidate.policies)) {
6
+ import { deepFreeze } from '../../canonical/freeze.js';
7
+ import { structureDomainSchema } from '../domain.route.js';
8
+ import { SITE } from './uses.js';
9
+ /**
10
+ * Measure the Policies of a document as `PL_BUDGET` does: it reads the document's structure alone,
11
+ * so a document the structure admits is measured whatever its other rules decide. Throws
12
+ * `SchemaError` with the diagnostics of a document that is not portable JSON or that its
13
+ * structure refuses.
14
+ */
15
+ export function measurePolicies(input) {
16
+ return measureDomainPolicies(structureDomainSchema(input));
17
+ }
18
+ /** The measures of one structured document, from its Policies once normalized. */
19
+ export function measureDomainPolicies(schema, normalized = normalizedPolicies(schema)) {
20
+ const measurePolicy = policyMeasurer(schema.origin, normalized, (ref) => ref.origin === schema.origin &&
21
+ ref.kind === 'policy' &&
22
+ Object.hasOwn(schema.policies, ref.name));
23
+ const policies = {};
24
+ const unmeasured = [];
25
+ for (const name of Object.keys(schema.policies)) {
7
26
  const measure = measurePolicy(name);
8
27
  if (measure === undefined)
9
- continue;
10
- const pointer = `/policies/${escapePointerToken(name)}`;
11
- if (measure.depth > MAX_PATTERN_DEPTH) {
12
- context.add('PL_BUDGET', pointer, `Expanded Pattern depth ${measure.depth} exceeds ${MAX_PATTERN_DEPTH}.`);
28
+ unmeasured.push(name);
29
+ else
30
+ policies[name] = measure;
31
+ }
32
+ const checks = {};
33
+ for (const [pointer, expression] of checkTrees(schema)) {
34
+ checks[pointer] = measureChecks(normalizePolicyChecks(expression));
35
+ }
36
+ return deepFreeze({ policies, unmeasured, checks });
37
+ }
38
+ /** `PL_BUDGET`: every measure of the document within `limits.policy`. */
39
+ export function validatePolicyBudgets(context) {
40
+ const measures = measureDomainPolicies(context.environment.candidate, context.normalized);
41
+ for (const [name, measure] of Object.entries(measures.policies)) {
42
+ for (const limit of exceededLimits(measure)) {
43
+ context.add('PL_BUDGET', `/policies/${escapePointerToken(name)}`, REFUSAL[limit](measure));
13
44
  }
14
- if (measure.edgeCounts.length > MAX_BRANCHES) {
15
- context.add('PL_BUDGET', pointer, `Expanded Pattern has more than ${MAX_BRANCHES} normalized branches.`);
45
+ }
46
+ for (const [pointer, measure] of Object.entries(measures.checks)) {
47
+ validateCheckBudget(context, pointer, measure);
48
+ }
49
+ }
50
+ /**
51
+ * `PL_BUDGET` for one check tree at `pointer`, from its measure: the document's trees, and the
52
+ * checks of a contract a Node carries, which no document holds.
53
+ */
54
+ export function validateCheckBudget(context, pointer, measure) {
55
+ if (exceededLimits(measure).length === 0)
56
+ return;
57
+ const { checkDepth, checkBranches, checkLeaves } = limits.policy;
58
+ // A callable holds its checks at `policy`; a Node Class rule at `policies/{operation}`.
59
+ const site = SITE[pointer.endsWith('/policy') ? 'callable' : 'class'];
60
+ context.add('PL_BUDGET', pointer, `${site.checks} require depth <= ${checkDepth}, branches <= ${checkBranches}, and leaves <= ${checkLeaves}; got ${measure.checkDepth}, ${measure.checkBranches}, ${measure.checkLeaves}.`);
61
+ }
62
+ /** How `PL_BUDGET` explains each limit a named Policy exceeds. */
63
+ const REFUSAL = {
64
+ patternDepth: (measure) => `Expanded Pattern depth ${measure.patternDepth} exceeds ${limits.policy.patternDepth}.`,
65
+ normalizedBranches: () => `Expanded Pattern has more than ${limits.policy.normalizedBranches} normalized branches.`,
66
+ edgePredicates: (measure) => `A normalized branch contains ${measure.edgePredicates} graph predicates; the maximum is ${limits.policy.edgePredicates}.`,
67
+ referencedDomains: (measure) => `Expanded Pattern references ${measure.referencedDomains} Domains; the maximum is ${limits.policy.referencedDomains}.`,
68
+ };
69
+ function normalizedPolicies(schema) {
70
+ return new Map(Object.entries(schema.policies).map(([name, declaration]) => [
71
+ name,
72
+ normalizePolicyExpression(declaration.expression),
73
+ ]));
74
+ }
75
+ /** Every check tree of a document with the pointer of its position, in document order. */
76
+ function* checkTrees(schema) {
77
+ for (const [name, declaration] of Object.entries(schema.classes)) {
78
+ const owner = `/classes/${escapePointerToken(name)}`;
79
+ for (const operation of ['read', 'traverse']) {
80
+ const rule = declaration.policies[operation];
81
+ if (typeof rule === 'object' && isClassRuleChecks(rule)) {
82
+ yield [`${owner}/policies/${operation}`, rule];
83
+ }
16
84
  }
17
- const edgeMaximum = Math.max(0, ...measure.edgeCounts);
18
- if (edgeMaximum > MAX_EDGES) {
19
- context.add('PL_BUDGET', pointer, `A normalized branch contains ${edgeMaximum} graph predicates; the maximum is ${MAX_EDGES}.`);
85
+ if (declaration.kind !== 'node')
86
+ continue;
87
+ for (const [method, value] of Object.entries(declaration.methods)) {
88
+ if (typeof value.policy === 'object') {
89
+ yield [`${owner}/methods/${escapePointerToken(method)}/policy`, value.policy];
90
+ }
20
91
  }
21
- if (measure.domains.size > MAX_DOMAINS) {
22
- context.add('PL_BUDGET', pointer, `Expanded Pattern references ${measure.domains.size} Domains; the maximum is ${MAX_DOMAINS}.`);
92
+ }
93
+ for (const [name, declaration] of Object.entries(schema.functions)) {
94
+ if (typeof declaration.policy === 'object') {
95
+ yield [`/functions/${escapePointerToken(name)}/policy`, declaration.policy];
23
96
  }
24
97
  }
25
98
  }
@@ -22,8 +22,7 @@ export declare class PolicyValidationContext implements PolicyTypingScope {
22
22
  edgeDeclaration(ref: ClassRef): Extract<ClassDefinition, {
23
23
  readonly kind: 'edge';
24
24
  }> | undefined;
25
- /** Return true when candidate is the required Class or a same-kind descendant. */
26
- classExtends(candidate: ClassRef, required: ClassRef): boolean;
25
+ resolveClass(ref: ClassRef): ClassDefinition | undefined;
27
26
  /** The contract a Policy pattern reference types, reporting an invalid one under `/policies`. */
28
27
  referenceContract(ref: PolicyObjectRef): ObjectContract | undefined;
29
28
  add(code: SchemaDiagnosticCode, pointer: string, message: string, ref?: string): void;
@@ -1,6 +1,5 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
2
  import { definitionRefIndex } from '../../../addressing/ordering.js';
3
- import { classSatisfies } from '../../../language/class/inheritance.js';
4
3
  import { diagnostic } from '../../diagnostic.js';
5
4
  import { referenceContracts } from './objects.js';
6
5
  const formatDefinitionRef = Key.of;
@@ -63,12 +62,8 @@ export class PolicyValidationContext {
63
62
  }
64
63
  return resolved.declaration;
65
64
  }
66
- /** Return true when candidate is the required Class or a same-kind descendant. */
67
- classExtends(candidate, required) {
68
- const resolve = (ref) => this.environment.resolveAny(ref)?.declaration;
69
- const actual = resolve(candidate);
70
- const target = resolve(required);
71
- return actual !== undefined && target !== undefined && classSatisfies(actual, target, resolve);
65
+ resolveClass(ref) {
66
+ return this.environment.resolveAny(ref)?.declaration;
72
67
  }
73
68
  /** The contract a Policy pattern reference types, reporting an invalid one under `/policies`. */
74
69
  referenceContract(ref) {
@@ -52,7 +52,8 @@ function validatePatternNode(context, pattern, pointer, scope, declarations, use
52
52
  context.resolveEdge(pattern.class, `${pointer}/class`);
53
53
  validateTerm(context, pattern.source, `${pointer}/source`, scope, uses);
54
54
  validateTerm(context, pattern.target, `${pointer}/target`, scope, uses);
55
- // The structure bounds each to an integer from 0 to MAX_REPEAT; only their order is left.
55
+ // The structure bounds each to an integer from 0 to `limits.policy.repetition`; only their
56
+ // order is left.
56
57
  if (pattern.repeat !== undefined && pattern.repeat.max < pattern.repeat.min) {
57
58
  context.add('PL_REPEAT', `${pointer}/repeat`, 'Repeat requires min <= max.');
58
59
  }
@@ -1,5 +1,34 @@
1
+ import type { CallableDefinition } from '../../../language/callable/model/callable.js';
1
2
  import type { PolicyTyping } from '../../../language/policy/analysis/typing.js';
3
+ import type { PolicyCheckExpression } from '../../../language/policy/model/policy.js';
4
+ import type { JsonSchema } from '../../../language/value/model/schema.js';
2
5
  import type { PolicyValidationContext } from './context.js';
6
+ import { type ClassRef } from '../../../addressing/index.js';
7
+ interface CheckContext {
8
+ readonly site: keyof typeof SITE;
9
+ readonly pointer: string;
10
+ readonly input: JsonSchema;
11
+ /** The Node Class that receives the call, which the checks name `self`; none, and none is named. */
12
+ readonly receiver?: ClassRef;
13
+ }
14
+ /** How a refusal names the checks of each site and the contract of their objects. */
15
+ export declare const SITE: {
16
+ readonly callable: {
17
+ readonly checks: 'Callable checks';
18
+ readonly objects: 'the callable object contract';
19
+ };
20
+ readonly class: {
21
+ readonly checks: 'Class rule checks';
22
+ readonly objects: 'the Class rule object contract';
23
+ };
24
+ };
3
25
  export declare function validateReadUses(context: PolicyValidationContext, typing: PolicyTyping): void;
4
26
  export declare function validateTraverseUses(context: PolicyValidationContext, typing: PolicyTyping): void;
5
27
  export declare function validateCallableUses(context: PolicyValidationContext, typing: PolicyTyping): void;
28
+ /**
29
+ * Validate the Policy of one callable contract written at `pointer`, wherever it is declared:
30
+ * `self` names the Node of the `receiver` Class, and nothing where the call has no receiver.
31
+ */
32
+ export declare function validateContractPolicy(context: PolicyValidationContext, typing: PolicyTyping, contract: CallableDefinition, pointer: string, receiver: ClassRef | undefined): void;
33
+ export declare function validateChecks(validation: PolicyValidationContext, typing: PolicyTyping, expression: PolicyCheckExpression, context: CheckContext): void;
34
+ export {};
@@ -6,12 +6,12 @@ import { checksNameTheCaller, isClassRuleChecks } from '../../../language/policy
6
6
  import { normalizePolicyChecks } from '../../../language/policy/normalization/normalize.js';
7
7
  import { GRAPH_NODE_PATH_SCHEMA_ID } from '../../../language/value/syntax/node-path/schema.js';
8
8
  import { escapePointerToken } from '../../../language/value/syntax/pointer.js';
9
- import { MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, measureChecks } from './budgets.js';
9
+ import { declaredContracts } from '../callable.js';
10
10
  import { referenceContracts } from './objects.js';
11
11
  import { contractOfNodeDefinition } from './typing.js';
12
12
  const formatDefinitionRef = Key.of;
13
13
  /** How a refusal names the checks of each site and the contract of their objects. */
14
- const SITE = {
14
+ export const SITE = {
15
15
  callable: { checks: 'Callable checks', objects: 'the callable object contract' },
16
16
  class: { checks: 'Class rule checks', objects: 'the Class rule object contract' },
17
17
  };
@@ -34,7 +34,6 @@ function validateClassPolicyUses(context, typing, operation) {
34
34
  pointer,
35
35
  input: {},
36
36
  receiver: { origin: context.environment.candidate.origin, kind: 'class', name },
37
- instanceMethod: true,
38
37
  });
39
38
  continue;
40
39
  }
@@ -56,58 +55,39 @@ function validateClassPolicyUses(context, typing, operation) {
56
55
  }
57
56
  }
58
57
  export function validateCallableUses(context, typing) {
59
- for (const [name, declaration] of Object.entries(context.environment.candidate.functions)) {
60
- if (typeof declaration.policy !== 'object')
61
- continue;
62
- validateChecks(context, typing, declaration.policy, {
63
- site: 'callable',
64
- pointer: `/functions/${escapePointerToken(name)}/policy`,
65
- input: isBinaryInput(declaration.input) ? declaration.input.metadata : declaration.input,
66
- instanceMethod: false,
67
- });
68
- }
69
- for (const [name, declaration] of Object.entries(context.environment.candidate.classes)) {
70
- if (declaration.kind !== 'node')
71
- continue;
72
- validateDeclaredMethods(context, typing, 'classes', name, declaration.methods, {
73
- origin: context.environment.candidate.origin,
74
- kind: 'class',
75
- name,
76
- });
58
+ for (const { contract, pointer, receiver, method } of declaredContracts(context.environment.candidate)) {
59
+ // Definition analysis is the authority for the effective callable. A missing Method is already
60
+ // a definition diagnostic, so avoid inventing a second Policy interpretation for it.
61
+ if (method !== undefined) {
62
+ const owner = context.analysis.definitions.get(definitionRefIndex(method.owner));
63
+ if (owner !== undefined && !owner.methods.has(method.name))
64
+ continue;
65
+ }
66
+ validateContractPolicy(context, typing, contract, pointer, receiver);
77
67
  }
78
68
  }
79
- function validateDeclaredMethods(context, typing, namespace, name, methods, receiver) {
80
- const ownerAnalysis = context.analysis.definitions.get(definitionRefIndex(receiver));
81
- for (const [methodName, method] of Object.entries(methods)) {
82
- if (typeof method.policy !== 'object')
83
- continue;
84
- // Definition analysis is the authority for the effective callable. A
85
- // missing entry is already a definition diagnostic, so avoid inventing a
86
- // second Policy interpretation for it.
87
- if (ownerAnalysis !== undefined && !ownerAnalysis.methods.has(methodName))
88
- continue;
89
- validateChecks(context, typing, method.policy, {
90
- site: 'callable',
91
- pointer: `/${namespace}/${escapePointerToken(name)}/methods/${escapePointerToken(methodName)}/policy`,
92
- input: method.input,
93
- receiver,
94
- instanceMethod: !method.static,
95
- });
96
- }
69
+ /**
70
+ * Validate the Policy of one callable contract written at `pointer`, wherever it is declared:
71
+ * `self` names the Node of the `receiver` Class, and nothing where the call has no receiver.
72
+ */
73
+ export function validateContractPolicy(context, typing, contract, pointer, receiver) {
74
+ if (typeof contract.policy !== 'object')
75
+ return;
76
+ validateChecks(context, typing, contract.policy, {
77
+ site: 'callable',
78
+ pointer: `${pointer}/policy`,
79
+ input: isBinaryInput(contract.input) ? contract.input.metadata : contract.input,
80
+ ...(receiver === undefined ? {} : { receiver }),
81
+ });
97
82
  }
98
- function validateChecks(validation, typing, expression, context) {
83
+ export function validateChecks(validation, typing, expression, context) {
99
84
  // Checks decide the caller the way a named Policy does, so, as a Policy connects its subject,
100
85
  // each way they hold must pass through a check of the caller.
101
86
  if (!checksNameTheCaller(expression)) {
102
87
  validation.add('PL_CONNECTIVITY', context.pointer, `${SITE[context.site].checks} must check a Policy on every branch: sameNode alone names no caller.`);
103
88
  }
89
+ // Their budget is checked with the named Policies', from the same measure (`budgets.ts`).
104
90
  const normalized = normalizePolicyChecks(expression);
105
- const measure = measureChecks(normalized);
106
- if (measure.depth > MAX_CHECK_DEPTH ||
107
- measure.branches > MAX_BRANCHES ||
108
- measure.leaves > MAX_CHECK_LEAVES) {
109
- validation.add('PL_BUDGET', context.pointer, `${SITE[context.site].checks} require depth <= ${MAX_CHECK_DEPTH}, branches <= ${MAX_BRANCHES}, and leaves <= ${MAX_CHECK_LEAVES}; got ${measure.depth}, ${measure.branches}, ${measure.leaves}.`);
110
- }
111
91
  const leaves = new Map();
112
92
  visitChecks(validation, typing, normalized, context, context.pointer, leaves);
113
93
  for (const [object, className] of neverAdmittedClasses(normalized, leaves)) {
@@ -150,8 +130,8 @@ function visitChecks(validation, typing, expression, context, pointer, leaves) {
150
130
  values.forEach((child, index) => visitChecks(validation, typing, child, context, `${pointer}/${key}/${index}`, leaves));
151
131
  }
152
132
  function selfContracts(validation, context, pointer) {
153
- if (!context.instanceMethod || context.receiver === undefined) {
154
- validation.add('PL_USE', pointer, 'self is valid only for a Node instance Method.');
133
+ if (context.receiver === undefined) {
134
+ validation.add('PL_USE', pointer, 'self is valid only where a Node receives the call: a Node instance Method, or a Function instance, whose Node carries the contract.');
155
135
  return undefined;
156
136
  }
157
137
  return [contractOfNodeDefinition(context.receiver)];
@@ -10,6 +10,8 @@ export type { BlobOptions, BlobReceipt, BlobRef, BlobSchema, } from '../../langu
10
10
  export type { JsonSchema as Schema, JsonSchemaObject as SchemaObject, PathAnnotation, PropertyObjectSchema as PropertySchema, ValueSchemaContext as Context, } from '../../language/value/model/schema.js';
11
11
  export { instant } from '../../language/value/model/instant.js';
12
12
  export type { OrderingKind } from '../../language/value/ordering.js';
13
+ export { VALUE_KINDS } from '../../language/value/ordering.js';
14
+ export { GRAPH_NODE_PATH_SCHEMA_ID as NODE_PATH } from '../../language/value/syntax/node-path/schema.js';
13
15
  export { DECLARED_VALUE_KINDS as DECLARED_KINDS, FORBIDDEN_KEYWORDS, SUBSCHEMA_KEYWORDS, VALUE_KEYWORDS as KEYWORDS, } from '../../language/value/profile/vocabulary.js';
14
16
  export type { DeclaredValueKind as DeclaredKind, ValueKeyword as Keyword, } from '../../language/value/profile/vocabulary.js';
15
17
  export { VALUE_RULES as RULES } from '../../language/validation/rule.js';
@@ -8,6 +8,8 @@ const registries = new WeakMap();
8
8
  export { VALUE_SCHEMA_DIALECT as DIALECT, VALUE_SCHEMA_VOCABULARY as VOCABULARY, } from '../../language/value/model/schema.js';
9
9
  export { blob, isBlobValueSchema as isBlob } from '../../language/value/model/blob.js';
10
10
  export { instant } from '../../language/value/model/instant.js';
11
+ export { VALUE_KINDS } from '../../language/value/ordering.js';
12
+ export { GRAPH_NODE_PATH_SCHEMA_ID as NODE_PATH } from '../../language/value/syntax/node-path/schema.js';
11
13
  export { DECLARED_VALUE_KINDS as DECLARED_KINDS, FORBIDDEN_KEYWORDS, SUBSCHEMA_KEYWORDS, VALUE_KEYWORDS as KEYWORDS, } from '../../language/value/profile/vocabulary.js';
12
14
  export { VALUE_RULES as RULES } from '../../language/validation/rule.js';
13
15
  /** Validate one complete JSON value under the exact accepted schema that owns the contract. */
@@ -1,4 +1,7 @@
1
+ import type { CallableDefinition } from '../../language/callable/model/callable.js';
1
2
  import type { ValueSchemaRegistry } from '../../language/value/reference/registry.js';
2
3
  import type { SchemaDiagnostic } from '../diagnostic.js';
3
4
  import type { DomainSchemaIR } from '../model/schema.js';
4
5
  export declare function validateEmbeddedValueSchemas(schema: DomainSchemaIR, registry: ValueSchemaRegistry): readonly SchemaDiagnostic[];
6
+ /** Validate the value schemas of one callable contract at `pointer`. */
7
+ export declare function validateCallable(declaration: CallableDefinition, pointer: string, registry: ValueSchemaRegistry, diagnostics: SchemaDiagnostic[]): void;
@@ -7,22 +7,16 @@ export function validateEmbeddedValueSchemas(schema, registry) {
7
7
  for (const [name, declaration] of Object.entries(schema.classes)) {
8
8
  validateDefinition(declaration, `/classes/${escapePointerToken(name)}`, registry, diagnostics);
9
9
  }
10
- for (const [name, declaration] of Object.entries(schema.functions)) {
11
- validateCallable(declaration, `/functions/${escapePointerToken(name)}`, registry, diagnostics);
12
- }
10
+ // Callable contracts are validated at their site, as one contract (`validateContracts`).
13
11
  return Object.freeze(diagnostics);
14
12
  }
15
13
  function validateDefinition(declaration, pointer, registry, diagnostics) {
16
14
  for (const [name, property] of Object.entries(declaration.properties)) {
17
15
  validate(property.schema, 'property-facet', `${pointer}/properties/${escapePointerToken(name)}/schema`, registry, diagnostics);
18
16
  }
19
- if ('methods' in declaration) {
20
- for (const [name, method] of Object.entries(declaration.methods)) {
21
- validateCallable(method, `${pointer}/methods/${escapePointerToken(name)}`, registry, diagnostics);
22
- }
23
- }
24
17
  }
25
- function validateCallable(declaration, pointer, registry, diagnostics) {
18
+ /** Validate the value schemas of one callable contract at `pointer`. */
19
+ export function validateCallable(declaration, pointer, registry, diagnostics) {
26
20
  if (isBinaryInput(declaration.input)) {
27
21
  validate(declaration.input.metadata, 'callable-input', `${pointer}/input/metadata`, registry, diagnostics);
28
22
  }
@@ -13,7 +13,7 @@ export declare const valueSchemaRules: {
13
13
  readonly 'value-path': {
14
14
  readonly code: 'DV_VALUE_SCHEMA';
15
15
  readonly valueCode: 'VS_PATH_EXTENSION';
16
- readonly 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.';
16
+ readonly statement: 'x-astrale-path annotates only a direct Property of a callable input object, beside exactly the $ref of the graph NodePath schema.';
17
17
  };
18
18
  readonly 'value-recursion': {
19
19
  readonly code: 'DV_VALUE_SCHEMA';
@@ -40,8 +40,8 @@ export type ValueSchemaRule = keyof typeof valueSchemaRules;
40
40
  /**
41
41
  * The value language in JSON Schema, for the document that embeds value schemas: `root` constrains
42
42
  * the `value-schema` definition with the published dialect and what the profile adds to it,
43
- * `definitions` holds the locations it reaches, and `nodeProperty` constrains a Node Property
44
- * definition. A named check leaves `x-astrale-rule` at the positions it constrains.
43
+ * `definitions` holds the locations it reaches and the members of the Path annotation, and
44
+ * `nodeProperty` constrains a Node Property definition. A named check leaves `x-astrale-rule` at the positions it constrains.
45
45
  */
46
46
  export declare function valueSchemaJsonSchema(): {
47
47
  readonly root: readonly JsonSchema[];
@@ -10,10 +10,11 @@ export const valueSchemaRules = {
10
10
  valueCode: 'VS_VALUE_KIND',
11
11
  statement: 'A blob is declared only by the whole schema of a Property, exactly as values.blob writes it.',
12
12
  },
13
+ // Its members are stated (`path-annotation`); where it stands is named.
13
14
  'value-path': {
14
15
  code: 'DV_VALUE_SCHEMA',
15
16
  valueCode: 'VS_PATH_EXTENSION',
16
- 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.',
17
+ statement: 'x-astrale-path annotates only a direct Property of a callable input object, beside exactly the $ref of the graph NodePath schema.',
17
18
  },
18
19
  'value-recursion': {
19
20
  code: 'DV_VALUE_SCHEMA',
@@ -39,11 +40,12 @@ export const valueSchemaRules = {
39
40
  const KIND = 'x-astrale-value-kind';
40
41
  const LOCATION = '#/$defs/value-location';
41
42
  const SUBSCHEMA = '#/$defs/value-subschema';
43
+ const PATH = '#/$defs/path-annotation';
42
44
  /**
43
45
  * The value language in JSON Schema, for the document that embeds value schemas: `root` constrains
44
46
  * the `value-schema` definition with the published dialect and what the profile adds to it,
45
- * `definitions` holds the locations it reaches, and `nodeProperty` constrains a Node Property
46
- * definition. A named check leaves `x-astrale-rule` at the positions it constrains.
47
+ * `definitions` holds the locations it reaches and the members of the Path annotation, and
48
+ * `nodeProperty` constrains a Node Property definition. A named check leaves `x-astrale-rule` at the positions it constrains.
47
49
  */
48
50
  export function valueSchemaJsonSchema() {
49
51
  return {
@@ -79,7 +81,7 @@ export function valueSchemaJsonSchema() {
79
81
  enum: { type: 'array', uniqueItems: true },
80
82
  fullMatch: { type: 'string', 'x-astrale-rule': 'value-regexp' },
81
83
  pattern: { type: 'string', 'x-astrale-rule': 'value-regexp' },
82
- 'x-astrale-path': { type: 'object', 'x-astrale-rule': 'value-path' },
84
+ 'x-astrale-path': { $ref: PATH, 'x-astrale-rule': 'value-path' },
83
85
  ...Object.fromEntries(SUBSCHEMA_KEYWORDS.single.map((keyword) => [keyword, { $ref: SUBSCHEMA }])),
84
86
  ...Object.fromEntries(SUBSCHEMA_KEYWORDS.array.map((keyword) => [
85
87
  keyword,
@@ -99,6 +101,22 @@ export function valueSchemaJsonSchema() {
99
101
  // oxlint-disable-next-line unicorn/no-thenable -- the JSON Schema keyword
100
102
  then: { required: ['type'], properties: { type: { const: 'string' } } },
101
103
  }),
104
+ // The Path annotation: the one Node a Path field resolves, of a Node Class it accepts.
105
+ 'path-annotation': {
106
+ type: 'object',
107
+ properties: {
108
+ target: { type: 'string', const: 'node' },
109
+ cardinality: { type: 'string', const: 'one' },
110
+ accepts: {
111
+ type: 'array',
112
+ minItems: 1,
113
+ uniqueItems: true,
114
+ items: { $ref: '#/$defs/class-ref', 'x-astrale-rule': 'value-reference-resolution' },
115
+ },
116
+ },
117
+ required: ['target', 'cardinality', 'accepts'],
118
+ additionalProperties: false,
119
+ },
102
120
  // A location below the root: no $schema, and no blob.
103
121
  'value-subschema': {
104
122
  allOf: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-dsl",
3
- "version": "1.0.0-beta.14",
3
+ "version": "1.0.0-beta.16",
4
4
  "description": "Astrale Kernel DSL - Schema authoring, inference, and IR serialization",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -81,6 +81,7 @@
81
81
  },
82
82
  "./v1/schema/domain-schema.schema.json": "./dist/v1/schema/.spec/schemas/domain-schema.schema.json",
83
83
  "./v1/schema/value-schema.schema.json": "./dist/v1/language/.spec/schemas/value-schema.schema.json",
84
+ "./v1/schema/draft-2020-12.schema.json": "./dist/v1/language/.spec/schemas/draft-2020-12.schema.json",
84
85
  "./v1/value/full-match": {
85
86
  "types": "./dist/v1/value/full-match.d.ts",
86
87
  "import": "./dist/v1/value/full-match.js"