@astrale-os/kernel-dsl 0.2.0-beta.44 → 0.2.0-beta.46

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 (53) hide show
  1. package/dist/v1/addressing/class-key.js +5 -4
  2. package/dist/v1/addressing/method-key.js +3 -2
  3. package/dist/v1/builder/lowering/value/zod.js +11 -2
  4. package/dist/v1/builder/value/full-match.js +6 -3
  5. package/dist/v1/compiled/admission/accept.js +27 -6
  6. package/dist/v1/compiled/admission/semantics.js +41 -16
  7. package/dist/v1/compiled/loader/load.js +1 -0
  8. package/dist/v1/domain/model/input.d.ts +3 -0
  9. package/dist/v1/domain/projection/classes.js +1 -0
  10. package/dist/v1/domain/validation/create.d.ts +2 -0
  11. package/dist/v1/domain/validation/create.js +12 -0
  12. package/dist/v1/language/class/resolved.d.ts +3 -0
  13. package/dist/v1/language/index.d.ts +2 -0
  14. package/dist/v1/language/index.js +1 -0
  15. package/dist/v1/language/policy/normalization/normalize.d.ts +4 -0
  16. package/dist/v1/language/policy/normalization/normalize.js +51 -13
  17. package/dist/v1/language/value/inclusion/includes.d.ts +9 -0
  18. package/dist/v1/language/value/inclusion/includes.js +777 -0
  19. package/dist/v1/language/value/profile/rules.js +11 -32
  20. package/dist/v1/language/value/regexp/ast.js +3 -64
  21. package/dist/v1/language/value/regexp/compile.d.ts +25 -2
  22. package/dist/v1/language/value/regexp/compile.js +48 -12
  23. package/dist/v1/language/value/regexp/syntax.d.ts +11 -1
  24. package/dist/v1/language/value/regexp/syntax.js +14 -2
  25. package/dist/v1/language/value/regexp/unicode.d.ts +32 -0
  26. package/dist/v1/language/value/regexp/unicode.js +139 -0
  27. package/dist/v1/language/value/syntax/full-match.js +6 -3
  28. package/dist/v1/schema/compatibility/comparators/callables.d.ts +7 -0
  29. package/dist/v1/schema/compatibility/comparators/callables.js +13 -0
  30. package/dist/v1/schema/compatibility/comparators/classes.d.ts +18 -0
  31. package/dist/v1/schema/compatibility/comparators/classes.js +54 -0
  32. package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
  33. package/dist/v1/schema/compatibility/comparators/index.js +14 -2
  34. package/dist/v1/schema/compatibility/evidence.d.ts +8 -1
  35. package/dist/v1/schema/compatibility/evidence.js +10 -1
  36. package/dist/v1/schema/compatibility/meaning/footprint.js +3 -1
  37. package/dist/v1/schema/compatibility/meaning/index.js +4 -2
  38. package/dist/v1/schema/compatibility/model.d.ts +11 -6
  39. package/dist/v1/schema/compatibility/references.d.ts +8 -1
  40. package/dist/v1/schema/compatibility/references.js +39 -6
  41. package/dist/v1/schema/compatibility/scope.d.ts +5 -0
  42. package/dist/v1/schema/compatibility/scope.js +5 -2
  43. package/dist/v1/schema/compatibility/structure/index.js +21 -2
  44. package/dist/v1/schema/compatibility/structure/requirements.js +26 -1
  45. package/dist/v1/schema/compatibility/subjects.js +20 -2
  46. package/dist/v1/schema/resolution/domain.js +1 -0
  47. package/dist/v1/schema/resolution/origins.d.ts +11 -4
  48. package/dist/v1/schema/resolution/origins.js +7 -8
  49. package/dist/v1/schema/validation/class.d.ts +10 -0
  50. package/dist/v1/schema/validation/class.js +4 -2
  51. package/dist/v1/schema/validation/policy/budgets.js +16 -3
  52. package/dist/v1/schema/validation/policy/typing.js +15 -0
  53. package/package.json +1 -1
@@ -49,6 +49,30 @@ export function classMeaning(context, coordinate, level, onto) {
49
49
  };
50
50
  return { meaning: deepFreeze(meaning), requirement };
51
51
  }
52
+ /**
53
+ * What a Policy pattern reads of an Edge it matches: its orientation and the Classes each endpoint
54
+ * accepts, without cardinalities or roles. Undirected endpoints form an unordered pair.
55
+ */
56
+ export function edgeEndpoints(context, coordinate) {
57
+ const declaration = context.schemas.get(coordinate.ref.origin)?.classes[coordinate.ref.name];
58
+ if (declaration?.kind !== 'edge')
59
+ return undefined;
60
+ const accepts = ({ accepts: refs }) => refs.map(keyForDefinitionRef).sort(compareUnicode);
61
+ return deepFreeze(declaration.orientation === 'directed'
62
+ ? {
63
+ coordinate: 'class',
64
+ orientation: 'directed',
65
+ source: accepts(declaration.endpoints.source),
66
+ target: accepts(declaration.endpoints.target),
67
+ }
68
+ : {
69
+ coordinate: 'class',
70
+ orientation: 'undirected',
71
+ endpoints: declaration.endpoints
72
+ .map(accepts)
73
+ .sort((left, right) => compareUnicode(left.join('\n'), right.join('\n'))),
74
+ });
75
+ }
52
76
  export function propertyMeaning(field) {
53
77
  return deepFreeze({
54
78
  coordinate: 'property',
@@ -75,6 +99,36 @@ function ancestorKeys(context, ref) {
75
99
  visit(ref);
76
100
  return [...seen].sort(compareUnicode);
77
101
  }
102
+ /**
103
+ * A Class a dependent references or inherits keeps every effective Property it had, whichever
104
+ * ancestor declares it. Each Property is compared on its owner, which still declares it when the
105
+ * Class stops extending that owner, and is not compared at all when another origin owns it: only the
106
+ * Class's own identity set shows those losses. Every loss is recorded, a Property its owner removed
107
+ * included, and only a loss is: a comparison that keeps every identity keeps its facts and evidence
108
+ * bytes, and an added ancestor or Property obliges nothing. The target is projected onto the source
109
+ * identities, as inherited ancestry is, so the differences name the lost Properties.
110
+ */
111
+ export function compareLostProperties({ pair, report, }) {
112
+ if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
113
+ return;
114
+ const kept = pair.target.analysis.properties;
115
+ const identities = [...pair.source.analysis.properties.keys()].sort(compareUnicode);
116
+ if (identities.every((identity) => kept.has(identity)))
117
+ return;
118
+ report.record({
119
+ pair,
120
+ facet: 'class.properties',
121
+ before: propertyIdentities(identities),
122
+ after: propertyIdentities(identities.filter((identity) => kept.has(identity))),
123
+ requirement: { kind: 'class.properties' },
124
+ });
125
+ }
126
+ function propertyIdentities(identities) {
127
+ return deepFreeze({
128
+ coordinate: 'class',
129
+ properties: Object.fromEntries(identities.map((identity) => [identity, true])),
130
+ });
131
+ }
78
132
  /** Newly required data is an obligation even though it was absent from the source footprint. */
79
133
  export function compareAddedProperties({ context, pair, report, }) {
80
134
  if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
@@ -5,7 +5,7 @@ import type { ComparisonContext, SubjectPair } from '../scope.js';
5
5
  export declare function compareFacet({ context, pair, facet, }: {
6
6
  readonly context: ComparisonContext;
7
7
  readonly pair: SubjectPair;
8
- readonly facet: Exclude<Facet, 'class.composition' | 'property.required'>;
8
+ readonly facet: Exclude<Facet, 'class.composition' | 'class.properties' | 'property.required'>;
9
9
  }): {
10
10
  readonly before?: Value;
11
11
  readonly after?: Value;
@@ -1,5 +1,5 @@
1
- import { methodInheritance, functionSignature, functionExistence, methodSignature, methodExistence, } from './callables.js';
2
- import { classMeaning, propertyMeaning } from './classes.js';
1
+ import { methodInheritance, functionSignature, functionExistence, methodObject, methodSignature, methodExistence, } from './callables.js';
2
+ import { classMeaning, edgeEndpoints, propertyMeaning } from './classes.js';
3
3
  import { memberMeaning } from './members.js';
4
4
  /** Shared facet comparisons know declarations, never installation or an analysis mode. */
5
5
  export function compareFacet({ context, pair, facet, }) {
@@ -11,6 +11,14 @@ export function compareFacet({ context, pair, facet, }) {
11
11
  const after = classMeaning(context.after, pair.coordinate, level, before?.requirement);
12
12
  return { before: before?.meaning, after: after?.meaning };
13
13
  }
14
+ if (facet === 'class.endpoints') {
15
+ if (pair.coordinate.kind !== 'class')
16
+ throw new TypeError('Class facet requires a Class coordinate.');
17
+ return {
18
+ before: edgeEndpoints(context.before, pair.coordinate),
19
+ after: edgeEndpoints(context.after, pair.coordinate),
20
+ };
21
+ }
14
22
  const value = (subject) => {
15
23
  if (subject === undefined)
16
24
  return undefined;
@@ -19,6 +27,10 @@ export function compareFacet({ context, pair, facet, }) {
19
27
  return subject.kind === 'property' ? propertyMeaning(subject.field) : undefined;
20
28
  case 'method.existence':
21
29
  return subject.kind === 'method' ? methodExistence(subject.method) : undefined;
30
+ case 'method.object':
31
+ return subject.kind === 'method'
32
+ ? methodObject(subject.method, subject.schema.classes[subject.method.owner.name])
33
+ : undefined;
22
34
  case 'method.signature':
23
35
  return subject.kind === 'method' ? methodSignature(subject.method) : undefined;
24
36
  case 'method.inheritance':
@@ -1,9 +1,16 @@
1
1
  import type { Analysis, ComparisonEvidence } from './model.js';
2
2
  /**
3
3
  * Only semantic facts and decisions are durable; provenance and diagnostic prose are not. Each
4
- * analysis names the version of the requirement policy its facts were assessed under.
4
+ * analysis names the oldest requirement policy that yields its facts: a version applies exactly when
5
+ * a fact it introduced is recorded, so a comparison without one keeps the earlier version's bytes.
5
6
  */
6
7
  export declare function evidenceOf({ kind, version, scope, entries, assessments, }: Omit<Analysis, 'evidence'> & {
7
8
  readonly kind: ComparisonEvidence['kind'];
8
9
  readonly version: ComparisonEvidence['version'];
9
10
  }): ComparisonEvidence;
11
+ /**
12
+ * Whether a referenced or inherited Class lost an effective Property. The fact is recorded only on a
13
+ * loss, so the evidence version that introduced it applies exactly when it is present
14
+ * (DM-CLASS-PROPERTIES).
15
+ */
16
+ export declare function losesProperties({ entries }: Pick<Analysis, 'entries'>): boolean;
@@ -2,7 +2,8 @@ import { compareUnicode } from '../../addressing/ordering.js';
2
2
  import { fingerprintMeaning } from './comparators/values.js';
3
3
  /**
4
4
  * Only semantic facts and decisions are durable; provenance and diagnostic prose are not. Each
5
- * analysis names the version of the requirement policy its facts were assessed under.
5
+ * analysis names the oldest requirement policy that yields its facts: a version applies exactly when
6
+ * a fact it introduced is recorded, so a comparison without one keeps the earlier version's bytes.
6
7
  */
7
8
  export function evidenceOf({ kind, version, scope, entries, assessments, }) {
8
9
  return Object.freeze({
@@ -38,6 +39,14 @@ export function evidenceOf({ kind, version, scope, entries, assessments, }) {
38
39
  }),
39
40
  });
40
41
  }
42
+ /**
43
+ * Whether a referenced or inherited Class lost an effective Property. The fact is recorded only on a
44
+ * loss, so the evidence version that introduced it applies exactly when it is present
45
+ * (DM-CLASS-PROPERTIES).
46
+ */
47
+ export function losesProperties({ entries }) {
48
+ return entries.some(({ observations }) => observations.some(({ facet }) => facet === 'class.properties'));
49
+ }
41
50
  /** Version-1 evidence keeps its original tags independently of public facet names. */
42
51
  function evidenceFacet(facet) {
43
52
  switch (facet) {
@@ -1,5 +1,5 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
- import { compareAddedProperties } from '../comparators/classes.js';
2
+ import { compareAddedProperties, compareLostProperties } from '../comparators/classes.js';
3
3
  import { compareFacet } from '../comparators/index.js';
4
4
  import { contextInvalid } from '../context.js';
5
5
  import { callableReferences, classReferences, inherits, memberReferences, valueReferences, } from '../references.js';
@@ -53,5 +53,7 @@ export function compareFootprint({ context, report, }) {
53
53
  });
54
54
  if (facet === 'class.inherit')
55
55
  compareAddedProperties({ context, pair, report });
56
+ if (subject.kind === 'class')
57
+ compareLostProperties({ pair, report });
56
58
  }
57
59
  }
@@ -1,4 +1,4 @@
1
- import { evidenceOf } from '../evidence.js';
1
+ import { evidenceOf, losesProperties } from '../evidence.js';
2
2
  import { createReport } from '../report.js';
3
3
  import { compareFootprint } from './footprint.js';
4
4
  export function analyzeMeaning(context) {
@@ -9,6 +9,8 @@ export function analyzeMeaning(context) {
9
9
  kind: 'meaning',
10
10
  ...result,
11
11
  equivalent: result.assessments.every(({ satisfied }) => satisfied),
12
- evidence: evidenceOf({ kind: 'meaning', version: 1, ...result }),
12
+ // Version 2 adds a referenced or inherited Class that lost an effective Property; without it,
13
+ // version 1.
14
+ evidence: evidenceOf({ kind: 'meaning', version: losesProperties(result) ? 2 : 1, ...result }),
13
15
  });
14
16
  }
@@ -46,7 +46,7 @@ export interface Usage {
46
46
  readonly key?: Key;
47
47
  readonly relation: ReferenceSite | 'member' | 'abstract' | 'selection' | 'capability';
48
48
  }
49
- export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'property' | 'property.required' | 'method.existence' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
49
+ export type Facet = 'class.reference' | 'class.inherit' | 'class.composition' | 'class.properties' | 'class.endpoints' | 'property' | 'property.required' | 'method.existence' | 'method.object' | 'method.inheritance' | 'method.signature' | 'function.existence' | 'function.signature' | 'policy' | 'view' | 'core';
50
50
  /** A fact about a compared facet, including unchanged facets. No admission decision is implied. */
51
51
  export interface Observation {
52
52
  readonly id: string;
@@ -74,14 +74,19 @@ export interface Assessment {
74
74
  }
75
75
  /**
76
76
  * Evidence is a semantic projection; presentation/provenance never enters its digest. Versions are
77
- * per kind. Meaning is version 1. Structure is version 2, or version 3 when declared capabilities
78
- * are among its roots, so a comparison without them keeps the version-2 bytes. Capability Keys join
79
- * the root Key set of both digests: one that is not already a root changes the meaning fingerprint,
80
- * never its version, and meaning evidence never records whether capabilities were declared.
77
+ * per kind, and a comparison names the oldest policy that yields its facts. Meaning is version 1.
78
+ * Structure is version 2, or version 3 when declared capabilities are among its roots, so a
79
+ * comparison without them keeps the version-2 bytes. Capability Keys join the root Key set of both
80
+ * digests: one that is not already a root changes the meaning fingerprint, never its version, and
81
+ * meaning evidence never records whether capabilities were declared. A referenced or inherited Class
82
+ * that loses an effective Property (`class.properties`) raises structure to version 4 and meaning to
83
+ * version 2, and structure is version 4 too when it records that a dependent Policy lost what it
84
+ * relies on (`method.object`, `class.endpoints`). Those facts are recorded only on such a loss, so
85
+ * every other comparison, every compatible one included, keeps its bytes.
81
86
  */
82
87
  export interface ComparisonEvidence {
83
88
  readonly kind: 'structure' | 'meaning';
84
- readonly version: 1 | 2 | 3;
89
+ readonly version: 1 | 2 | 3 | 4;
85
90
  readonly fingerprint: Fingerprint;
86
91
  }
87
92
  export interface Analysis {
@@ -1,8 +1,8 @@
1
- import type { Key } from '../../addressing/index.js';
2
1
  import type { DomainSchema } from '../model/schema.js';
3
2
  import type { Coordinate } from './coordinate.js';
4
3
  import type { Usage } from './model.js';
5
4
  import type { ResolvedSubject } from './subjects.js';
5
+ import { Key } from '../../addressing/index.js';
6
6
  export interface Reference {
7
7
  readonly coordinate: Coordinate;
8
8
  readonly usage: Usage;
@@ -14,6 +14,13 @@ export declare function schemaIdentity(schema: DomainSchema): Readonly<{
14
14
  export declare function usageOf(schema: DomainSchema, relation: Usage['relation'], key?: Key): Usage;
15
15
  /** References keep their declaring member and relation; no strongest-usage collapse happens here. */
16
16
  export declare function rootReferences(schema: DomainSchema): readonly Reference[];
17
+ /**
18
+ * What the dependent's own Policies rely on in one origin beyond naming a Definition: each Method a
19
+ * Policy object term names, which the Policy resolves as its owner's own executable declaration, and
20
+ * each Edge Class a Policy pattern matches, whose orientation and accepted endpoint Classes Policy
21
+ * typing reads. Policies the dependent reaches in a dependency are that dependency's own.
22
+ */
23
+ export declare function policyReliance(schema: DomainSchema, origin: Key.Origin): readonly Reference[];
17
24
  /** Existing DSL collectors own reference syntax. These functions attach comparison provenance. */
18
25
  export declare function valueReferences(subject: Extract<ResolvedSubject, {
19
26
  kind: 'property';
@@ -1,3 +1,4 @@
1
+ import { Key } from '../../addressing/index.js';
1
2
  import { revisionOfDomainSchema } from '../canonical/revision.js';
2
3
  import { collectCallableContractRefs, collectCallableRefs, collectClassPolicyRefs, collectCoreRefs, collectEndpointRefs, collectPolicyDefinitionRefs, collectValueDefinitionRefs, coreEndpointDefinitionRef, } from '../resolution/origins.js';
3
4
  import { classCoordinate, keyForDefinitionRef, memberCoordinate, methodCoordinate, propertyCoordinate, } from './coordinate.js';
@@ -11,13 +12,17 @@ export function usageOf(schema, relation, key) {
11
12
  ...(key === undefined ? {} : { key }),
12
13
  });
13
14
  }
15
+ /** A Method a Policy names stays a reference to its owner Class; `policyReliance` keeps the Method. */
14
16
  function collector(schema, result, key) {
15
- return (ref, relation) => result.push({
16
- coordinate: ref.kind === 'class'
17
- ? classCoordinate(ref)
18
- : memberCoordinate(ref),
19
- usage: usageOf(schema, relation, key),
20
- });
17
+ return (ref, relation) => {
18
+ const definition = ref.kind === 'method' ? ref.owner : ref;
19
+ result.push({
20
+ coordinate: definition.kind === 'class'
21
+ ? classCoordinate(definition)
22
+ : memberCoordinate(definition),
23
+ usage: usageOf(schema, relation, key),
24
+ });
25
+ };
21
26
  }
22
27
  /** References keep their declaring member and relation; no strongest-usage collapse happens here. */
23
28
  export function rootReferences(schema) {
@@ -49,6 +54,34 @@ export function rootReferences(schema) {
49
54
  collectCoreRefs(schema, collector(schema, result));
50
55
  return result;
51
56
  }
57
+ /**
58
+ * What the dependent's own Policies rely on in one origin beyond naming a Definition: each Method a
59
+ * Policy object term names, which the Policy resolves as its owner's own executable declaration, and
60
+ * each Edge Class a Policy pattern matches, whose orientation and accepted endpoint Classes Policy
61
+ * typing reads. Policies the dependent reaches in a dependency are that dependency's own.
62
+ */
63
+ export function policyReliance(schema, origin) {
64
+ const result = new Map();
65
+ const collect = (key) => (ref, site) => {
66
+ const coordinate = ref.kind === 'method'
67
+ ? methodCoordinate(ref.owner, ref.name)
68
+ : site === 'pattern'
69
+ ? classCoordinate(ref)
70
+ : undefined;
71
+ if (coordinate === undefined || Key.origin(coordinate.key) !== origin)
72
+ return;
73
+ result.set(`${coordinate.key}:${site}`, { coordinate, usage: usageOf(schema, site, key) });
74
+ };
75
+ for (const declaration of Object.values(schema.classes))
76
+ if (declaration.kind === 'node')
77
+ for (const [name, method] of Object.entries(declaration.methods))
78
+ collectCallableRefs(method, collect(methodCoordinate(declaration.ref, name).key));
79
+ for (const declaration of Object.values(schema.functions))
80
+ collectCallableRefs(declaration, collect(keyForDefinitionRef(declaration.ref)));
81
+ for (const declaration of Object.values(schema.policies))
82
+ collectPolicyDefinitionRefs(declaration, collect(keyForDefinitionRef(declaration.ref)));
83
+ return [...result.values()];
84
+ }
52
85
  /** Existing DSL collectors own reference syntax. These functions attach comparison provenance. */
53
86
  export function valueReferences(subject) {
54
87
  const result = [];
@@ -20,6 +20,11 @@ export interface ComparisonContext {
20
20
  readonly roots: readonly Reference[];
21
21
  /** Roots the dependent declares as capabilities on the compared origin; also in `roots`. */
22
22
  readonly capabilities: readonly Reference[];
23
+ /**
24
+ * What the dependent's own Policies rely on in the compared origin (`policyReliance`). Not roots:
25
+ * structure records them only when they fail, so a compatible comparison keeps its bytes.
26
+ */
27
+ readonly reliance: readonly Reference[];
23
28
  pair(coordinate: Coordinate): SubjectPair;
24
29
  }
25
30
  export declare function prepare(request: ComparisonRequest): ComparisonContext;
@@ -3,7 +3,7 @@ import { SchemaError, diagnostic } from '../diagnostic.js';
3
3
  import { knownDomainClosure } from '../resolution/closure.js';
4
4
  import { resolve } from '../resolution/domain.js';
5
5
  import { contextOf } from './context.js';
6
- import { rootReferences, schemaIdentity, usageOf } from './references.js';
6
+ import { policyReliance, rootReferences, schemaIdentity, usageOf } from './references.js';
7
7
  import { capabilityCoordinates, coordinateForKey, resolveSubject } from './subjects.js';
8
8
  export function prepare(request) {
9
9
  const { scope, target } = request;
@@ -41,6 +41,7 @@ export function prepare(request) {
41
41
  before,
42
42
  roots: [...rootReferences(dependent), ...capabilities],
43
43
  capabilities,
44
+ reliance: policyReliance(dependent, source.origin),
44
45
  });
45
46
  }
46
47
  /**
@@ -61,9 +62,10 @@ export function selectMembers(source, target, keys) {
61
62
  usage: usageOf(source, 'selection', key),
62
63
  })),
63
64
  capabilities: [],
65
+ reliance: [],
64
66
  });
65
67
  }
66
- function comparisonContext({ kind, source, target, dependent, before, roots, capabilities, }) {
68
+ function comparisonContext({ kind, source, target, dependent, before, roots, capabilities, reliance, }) {
67
69
  resolve(target);
68
70
  const after = contextOf(target);
69
71
  const pairs = new Map();
@@ -75,6 +77,7 @@ function comparisonContext({ kind, source, target, dependent, before, roots, cap
75
77
  after,
76
78
  roots,
77
79
  capabilities,
80
+ reliance,
78
81
  scope: Object.freeze({
79
82
  kind,
80
83
  source: schemaIdentity(source),
@@ -11,11 +11,30 @@ export function analyzeStructure(context) {
11
11
  kind: 'structure',
12
12
  ...result,
13
13
  compatible: result.assessments.every(({ satisfied }) => satisfied),
14
- // Version 3 adds declared capability roots; without one, evidence keeps its version-2 bytes.
14
+ // Each version adds one requirement policy and applies only when its fact is present: 3 a
15
+ // declared capability root, 4 a lost effective Property of a reached Class or a failed Policy
16
+ // reliance, facts recorded only when they fail. Without one, evidence keeps its version-2 bytes.
15
17
  evidence: evidenceOf({
16
18
  kind: 'structure',
17
- version: context.capabilities.length > 0 ? 3 : 2,
19
+ version: recordsFailureOnlyFact(result.assessments)
20
+ ? 4
21
+ : context.capabilities.length > 0
22
+ ? 3
23
+ : 2,
18
24
  ...result,
19
25
  }),
20
26
  });
21
27
  }
28
+ /**
29
+ * The facts structure records only when they fail: a referenced or inherited Class that lost an
30
+ * effective Property, and what a dependent Policy relies on (a Method object, a matched Edge's
31
+ * endpoints).
32
+ */
33
+ const FAILURE_ONLY_FACETS = new Set([
34
+ 'class.properties',
35
+ 'method.object',
36
+ 'class.endpoints',
37
+ ]);
38
+ function recordsFailureOnlyFact(assessments) {
39
+ return assessments.some(({ requirement }) => FAILURE_ONLY_FACETS.has(requirement.kind));
40
+ }
@@ -1,6 +1,7 @@
1
1
  import { Key } from '../../../addressing/index.js';
2
- import { compareAddedProperties } from '../comparators/classes.js';
2
+ import { compareAddedProperties, compareLostProperties } from '../comparators/classes.js';
3
3
  import { compareFacet } from '../comparators/index.js';
4
+ import { fingerprintMeaning } from '../comparators/values.js';
4
5
  import { contextInvalid } from '../context.js';
5
6
  import { methodCoordinate } from '../coordinate.js';
6
7
  import { callableReferences, classReferences, inherits, memberReferences, usageOf, valueReferences, } from '../references.js';
@@ -68,5 +69,29 @@ export function compareRequirements({ context, report, }) {
68
69
  });
69
70
  if (facet === 'class.inherit')
70
71
  compareAddedProperties({ context, pair, report });
72
+ if (subject.kind === 'class')
73
+ compareLostProperties({ pair, report });
74
+ }
75
+ compareReliance({ context, report });
76
+ }
77
+ /**
78
+ * What a dependent Policy relies on is recorded only when it fails: a Method it names that is gone
79
+ * or no longer executable by its owner, or an Edge it matches whose orientation or accepted endpoint
80
+ * Classes changed. A held reliance adds no fact, so a compatible comparison keeps its evidence bytes
81
+ * and no coordinate minted from it moves; a failed one is a refusal, from which none is minted.
82
+ */
83
+ function compareReliance({ context, report, }) {
84
+ for (const reference of context.reliance) {
85
+ const pair = context.pair(reference.coordinate);
86
+ if (pair.source === undefined)
87
+ throw contextInvalid(`Retained acceptance context has no meaning for ${reference.coordinate.key}.`, reference.coordinate.key);
88
+ const facet = reference.coordinate.kind === 'method' ? 'method.object' : 'class.endpoints';
89
+ const { before, after } = compareFacet({ context, pair, facet });
90
+ if (before !== undefined &&
91
+ after !== undefined &&
92
+ fingerprintMeaning(before) === fingerprintMeaning(after))
93
+ continue;
94
+ report.note(reference);
95
+ report.record({ pair, facet, before, after, requirement: { kind: facet } });
71
96
  }
72
97
  }
@@ -115,11 +115,29 @@ function capabilityCoordinate(source, key) {
115
115
  }
116
116
  function invalidCapability(key, index, reason) {
117
117
  throw new SchemaError([
118
- diagnostic('DM_CAPABILITY_INVALID', `/capabilities/${index}`, `Capability ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
118
+ diagnostic('DM_CAPABILITY_INVALID', `/capabilities/${index}`, `Capability ${shown(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
119
119
  ]);
120
120
  }
121
121
  function invalidMemberRoot(key, index, reason) {
122
122
  throw new SchemaError([
123
- diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Key ${JSON.stringify(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
123
+ diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Key ${shown(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
124
124
  ]);
125
125
  }
126
+ /**
127
+ * A caller-supplied item as diagnostic text. Formatting never throws, so the caller always gets the
128
+ * diagnostic: a bigint is shown as its literal, and another item JSON cannot encode, such as a cyclic
129
+ * object, is named by its type.
130
+ */
131
+ function shown(item) {
132
+ if (typeof item === 'bigint')
133
+ return `${item}n`;
134
+ try {
135
+ const text = JSON.stringify(item);
136
+ if (text !== undefined)
137
+ return text;
138
+ }
139
+ catch {
140
+ // Not encodable as JSON: named by its type below.
141
+ }
142
+ return `<${typeof item}>`;
143
+ }
@@ -66,6 +66,7 @@ function sourceValidation(closure, registries) {
66
66
  properties.set(PropertyKey.of(definition.ref, name), Object.freeze({
67
67
  ordering: classifyPreparedOrdering(prepared.value),
68
68
  validate: (input) => validatePreparedInstance(prepared.value, input),
69
+ program: () => prepared.value.program(),
69
70
  }));
70
71
  }
71
72
  if (definition.kind !== 'node')
@@ -1,4 +1,4 @@
1
- import type { DefinitionRef } from '../../addressing/index.js';
1
+ import type { DefinitionRef, MethodAddress } from '../../addressing/index.js';
2
2
  import type { FunctionDefinition, MethodDefinition } from '../../language/callable/model/callable.js';
3
3
  import type { ClassDefinition } from '../../language/class/model/class.js';
4
4
  import type { CoreEndpointRef } from '../../language/core/model/core.js';
@@ -7,10 +7,17 @@ import type { JsonSchema } from '../../language/value/model/schema.js';
7
7
  import type { DomainSchemaIR } from '../model/schema.js';
8
8
  /**
9
9
  * Where one schema references a Definition. `extends` and `core` mean the schema inherits from or
10
- * instantiates the Definition; every other site only refers to it.
10
+ * instantiates the Definition; every other site only refers to it. `pattern` is the Edge Class a
11
+ * Policy pattern matches, whose orientation and accepted endpoint Classes Policy typing reads.
11
12
  */
12
- export type ReferenceSite = 'extends' | 'endpoint' | 'value' | 'policy' | 'view' | 'core';
13
- export type ReferenceCollector = (ref: DefinitionRef, site: ReferenceSite) => void;
13
+ export type ReferenceSite = 'extends' | 'endpoint' | 'value' | 'policy' | 'pattern' | 'view' | 'core';
14
+ /**
15
+ * What a site names: a Definition, or the Class-owned Method a Policy object term names. A Policy
16
+ * keeps the Method itself, since it resolves that Method's own executable declaration; a collector
17
+ * that needs Definitions only takes its owner Class.
18
+ */
19
+ export type ReferencedDefinition = DefinitionRef | MethodAddress;
20
+ export type ReferenceCollector = (ref: ReferencedDefinition, site: ReferenceSite) => void;
14
21
  /** Collect only semantic refs, never strings or objects embedded as JSON data. */
15
22
  export declare function collectUsedForeignOrigins(schema: DomainSchemaIR): ReadonlySet<string>;
16
23
  /** Complete structured references in one schema, in canonical order. */
@@ -13,7 +13,8 @@ export function collectUsedForeignOrigins(schema) {
13
13
  export function collectDomainDefinitionRefs(schema) {
14
14
  const result = new Map();
15
15
  const add = (ref) => {
16
- result.set(definitionRefIndex(ref), ref);
16
+ const definition = ref.kind === 'method' ? ref.owner : ref;
17
+ result.set(definitionRefIndex(definition), definition);
17
18
  };
18
19
  for (const declaration of Object.values(schema.classes)) {
19
20
  collectClassRefs(declaration, add);
@@ -102,14 +103,14 @@ function collectPolicyPatternRefs(expression, add) {
102
103
  if ('sameNode' in expression) {
103
104
  for (const term of [expression.sameNode.left, expression.sameNode.right])
104
105
  if (term.kind === 'ref')
105
- add(term.ref.kind === 'method' ? term.ref.owner : term.ref, 'policy');
106
+ add(term.ref, 'policy');
106
107
  return;
107
108
  }
108
109
  if ('source' in expression) {
109
110
  for (const term of [expression.source, expression.target])
110
111
  if (term.kind === 'ref')
111
- add(term.ref.kind === 'method' ? term.ref.owner : term.ref, 'policy');
112
- add(expression.class, 'policy');
112
+ add(term.ref, 'policy');
113
+ add(expression.class, 'pattern');
113
114
  return;
114
115
  }
115
116
  if ('exists' in expression) {
@@ -128,15 +129,13 @@ function collectPolicyCheckRefs(expression, add) {
128
129
  if ('sameNode' in expression) {
129
130
  for (const term of [expression.sameNode.left, expression.sameNode.right])
130
131
  if (term.kind === 'ref')
131
- add(term.ref.kind === 'method' ? term.ref.owner : term.ref, 'policy');
132
+ add(term.ref, 'policy');
132
133
  return;
133
134
  }
134
135
  if ('check' in expression) {
135
136
  add(expression.check, 'policy');
136
137
  if (expression.object.kind === 'ref')
137
- add(expression.object.ref.kind === 'method'
138
- ? expression.object.ref.owner
139
- : expression.object.ref, 'policy');
138
+ add(expression.object.ref, 'policy');
140
139
  return;
141
140
  }
142
141
  const children = 'allOf' in expression ? expression.allOf : expression.anyOf;
@@ -1,5 +1,15 @@
1
+ import type { ClassRef } from '../../addressing/index.js';
2
+ import type { ClassDefinition } from '../../language/class/model/class.js';
1
3
  import type { DefinitionAnalysisResult } from '../analysis/class.js';
2
4
  import type { SchemaDiagnostic } from '../diagnostic.js';
3
5
  import type { DomainResolutionContext } from '../resolution/context.js';
4
6
  /** Validate the semantic contracts owned by Class declarations. */
5
7
  export declare function validateDefinitions(environment: DomainResolutionContext, analysis: DefinitionAnalysisResult): readonly SchemaDiagnostic[];
8
+ /** Whether `child` safely refines its parent Edge contract `parent`. */
9
+ export declare function edgeContractRefines(environment: DomainResolutionContext, child: Extract<ClassDefinition, {
10
+ readonly kind: 'edge';
11
+ }>, parent: Extract<ClassDefinition, {
12
+ readonly kind: 'edge';
13
+ }>): boolean;
14
+ /** Whether `candidate` is, or is a Node Class extending, one of `accepts`. */
15
+ export declare function classSatisfies(environment: DomainResolutionContext, candidate: ClassRef, accepts: readonly ClassRef[]): boolean;
@@ -149,7 +149,8 @@ function validateEdgeClassComposition(environment, diagnostics) {
149
149
  }
150
150
  }
151
151
  }
152
- function edgeContractRefines(environment, child, parent) {
152
+ /** Whether `child` safely refines its parent Edge contract `parent`. */
153
+ export function edgeContractRefines(environment, child, parent) {
153
154
  if (child.orientation !== parent.orientation)
154
155
  return false;
155
156
  if (parent.constraints.noSelf === true && child.constraints.noSelf !== true)
@@ -207,7 +208,8 @@ function undirectedEndpointsOverlap(environment, endpoints) {
207
208
  }
208
209
  return false;
209
210
  }
210
- function classSatisfies(environment, candidate, accepts) {
211
+ /** Whether `candidate` is, or is a Node Class extending, one of `accepts`. */
212
+ export function classSatisfies(environment, candidate, accepts) {
211
213
  if (accepts.some((ref) => definitionRefIndex(ref) === definitionRefIndex(candidate)))
212
214
  return true;
213
215
  const resolved = environment.resolveAny(candidate);
@@ -3,8 +3,14 @@ import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_D
3
3
  import { escapePointer } from './context.js';
4
4
  export { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, };
5
5
  export function validatePolicyBudgets(context) {
6
+ // Measure every distinct composition node once. A node's expanded measure is independent of the
7
+ // path that reaches it (the only path-sensitive outcome, the back-edge `undefined`, already holds
8
+ // for every node on or reaching a composition cycle, regardless of entry), so caching the
9
+ // completed result by node identity turns path enumeration over the composition DAG into work
10
+ // linear in the number of nodes while preserving the exact measure and PL_BUDGET decision.
11
+ const measures = new Map();
6
12
  for (const name of Object.keys(context.environment.candidate.policies)) {
7
- const measure = measurePolicy(context, name, new Set());
13
+ const measure = measurePolicy(context, name, new Set(), measures);
8
14
  if (measure === undefined)
9
15
  continue;
10
16
  const pointer = `/policies/${escapePointer(name)}`;
@@ -23,14 +29,21 @@ export function validatePolicyBudgets(context) {
23
29
  }
24
30
  }
25
31
  }
26
- function measurePolicy(context, name, visiting) {
32
+ function measurePolicy(context, name, visiting, measures) {
27
33
  if (visiting.has(name))
28
34
  return undefined;
35
+ if (measures.has(name))
36
+ return measures.get(name);
29
37
  const declaration = context.normalized.get(name);
30
38
  if (declaration === undefined)
31
39
  return undefined;
32
40
  const nested = new Set(visiting);
33
41
  nested.add(name);
42
+ const measure = measureDeclaration(context, declaration, nested, measures);
43
+ measures.set(name, measure);
44
+ return measure;
45
+ }
46
+ function measureDeclaration(context, declaration, nested, measures) {
34
47
  if ('match' in declaration) {
35
48
  return measurePattern(declaration.match, context.environment.candidate.origin);
36
49
  }
@@ -38,7 +51,7 @@ function measurePolicy(context, name, visiting) {
38
51
  const children = refs.flatMap((ref) => {
39
52
  if (!context.isLocalPolicy(ref))
40
53
  return [];
41
- const value = measurePolicy(context, ref.name, nested);
54
+ const value = measurePolicy(context, ref.name, nested, measures);
42
55
  return value === undefined ? [] : [value];
43
56
  });
44
57
  if (children.length !== refs.length || children.length === 0)