@astrale-os/kernel-dsl 0.2.0-beta.45 → 0.2.0-beta.47

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 (76) 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/class/property.d.ts +3 -1
  4. package/dist/v1/builder/class/property.js +9 -1
  5. package/dist/v1/builder/lowering/schema/compile.js +1 -1
  6. package/dist/v1/builder/lowering/value/compile.js +3 -0
  7. package/dist/v1/builder/lowering/value/zod.js +11 -2
  8. package/dist/v1/builder/lowering/view.d.ts +2 -2
  9. package/dist/v1/builder/lowering/view.js +2 -14
  10. package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
  11. package/dist/v1/builder/value/full-match.js +6 -3
  12. package/dist/v1/builder/view/authoring.d.ts +3 -7
  13. package/dist/v1/builder/view/authoring.js +5 -2
  14. package/dist/v1/compiled/admission/accept.js +31 -6
  15. package/dist/v1/compiled/admission/semantics.js +49 -16
  16. package/dist/v1/compiled/compiler/compile.js +1 -0
  17. package/dist/v1/compiled/loader/load.js +1 -0
  18. package/dist/v1/domain/create.js +0 -19
  19. package/dist/v1/domain/model/domain.d.ts +0 -1
  20. package/dist/v1/domain/model/input.d.ts +3 -0
  21. package/dist/v1/domain/projection/classes.js +1 -0
  22. package/dist/v1/domain/validation/create.d.ts +2 -0
  23. package/dist/v1/domain/validation/create.js +12 -0
  24. package/dist/v1/language/class/model/property.d.ts +3 -0
  25. package/dist/v1/language/class/resolved.d.ts +3 -0
  26. package/dist/v1/language/index.d.ts +2 -0
  27. package/dist/v1/language/index.js +1 -0
  28. package/dist/v1/language/policy/normalization/normalize.d.ts +4 -0
  29. package/dist/v1/language/policy/normalization/normalize.js +51 -13
  30. package/dist/v1/language/value/inclusion/includes.d.ts +9 -0
  31. package/dist/v1/language/value/inclusion/includes.js +777 -0
  32. package/dist/v1/language/value/profile/rules.js +11 -32
  33. package/dist/v1/language/value/regexp/ast.js +3 -64
  34. package/dist/v1/language/value/regexp/compile.d.ts +25 -2
  35. package/dist/v1/language/value/regexp/compile.js +48 -12
  36. package/dist/v1/language/value/regexp/syntax.d.ts +11 -1
  37. package/dist/v1/language/value/regexp/syntax.js +14 -2
  38. package/dist/v1/language/value/regexp/unicode.d.ts +32 -0
  39. package/dist/v1/language/value/regexp/unicode.js +139 -0
  40. package/dist/v1/language/value/syntax/full-match.js +6 -3
  41. package/dist/v1/language/view/model/view.d.ts +6 -0
  42. package/dist/v1/language/view/resolved.d.ts +2 -2
  43. package/dist/v1/schema/analysis/class.d.ts +2 -1
  44. package/dist/v1/schema/analysis/class.js +1 -0
  45. package/dist/v1/schema/compatibility/comparators/callables.d.ts +7 -0
  46. package/dist/v1/schema/compatibility/comparators/callables.js +13 -0
  47. package/dist/v1/schema/compatibility/comparators/classes.d.ts +18 -0
  48. package/dist/v1/schema/compatibility/comparators/classes.js +54 -0
  49. package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
  50. package/dist/v1/schema/compatibility/comparators/index.js +14 -2
  51. package/dist/v1/schema/compatibility/context.d.ts +6 -2
  52. package/dist/v1/schema/compatibility/context.js +12 -6
  53. package/dist/v1/schema/compatibility/evidence.d.ts +8 -1
  54. package/dist/v1/schema/compatibility/evidence.js +10 -1
  55. package/dist/v1/schema/compatibility/fields.d.ts +2 -0
  56. package/dist/v1/schema/compatibility/fields.js +1 -0
  57. package/dist/v1/schema/compatibility/meaning/footprint.js +3 -1
  58. package/dist/v1/schema/compatibility/meaning/index.js +4 -2
  59. package/dist/v1/schema/compatibility/model.d.ts +11 -6
  60. package/dist/v1/schema/compatibility/references.d.ts +8 -1
  61. package/dist/v1/schema/compatibility/references.js +39 -6
  62. package/dist/v1/schema/compatibility/scope.d.ts +5 -0
  63. package/dist/v1/schema/compatibility/scope.js +5 -2
  64. package/dist/v1/schema/compatibility/structure/index.js +21 -2
  65. package/dist/v1/schema/compatibility/structure/requirements.js +26 -1
  66. package/dist/v1/schema/compatibility/subjects.js +20 -2
  67. package/dist/v1/schema/diagnostic.d.ts +1 -1
  68. package/dist/v1/schema/normalization/domain.js +1 -0
  69. package/dist/v1/schema/resolution/domain.d.ts +4 -1
  70. package/dist/v1/schema/resolution/domain.js +7 -7
  71. package/dist/v1/schema/resolution/origins.d.ts +11 -4
  72. package/dist/v1/schema/resolution/origins.js +7 -8
  73. package/dist/v1/schema/validation/class.d.ts +10 -0
  74. package/dist/v1/schema/validation/class.js +13 -2
  75. package/dist/v1/schema/validation/domain.structure.js +1 -0
  76. 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':
@@ -2,11 +2,15 @@ import type { ClassRef, DefinitionRef, Key as SemanticKey } from '../../addressi
2
2
  import type { DefinitionAnalysis, EffectiveMethod } from '../analysis/class.js';
3
3
  import type { DomainSchema } from '../model/schema.js';
4
4
  import { SchemaError } from '../diagnostic.js';
5
- /** One accepted root with its exact closure and a memoized semantic analysis per owner. */
5
+ /** One accepted root with its exact closure and a memoized Definition analysis per owner. */
6
6
  export interface Context {
7
7
  readonly schemas: ReadonlyMap<string, DomainSchema>;
8
- analysis(owner: DomainSchema): ReadonlyMap<string, DefinitionAnalysis> | undefined;
8
+ analysis(owner: DomainSchema): ReadonlyMap<string, DefinitionAnalysis>;
9
9
  }
10
+ /**
11
+ * Every owner passed acceptance against the closure it retains, which is the only way a schema
12
+ * gets one, so its analysis is derived without validating it again.
13
+ */
10
14
  export declare function contextOf(root: DomainSchema): Context;
11
15
  export declare function effectiveDefinition(context: Context, owner: DomainSchema, ref: ClassRef): DefinitionAnalysis | undefined;
12
16
  /** Effective Methods are keyed by name; a coordinate names one by its exact Method Key. */
@@ -1,8 +1,13 @@
1
1
  import { MethodKey } from '../../addressing/index.js';
2
2
  import { definitionRefIndex } from '../../addressing/ordering.js';
3
+ import { analyzeDefinitions } from '../analysis/class.js';
3
4
  import { SchemaError, diagnostic } from '../diagnostic.js';
4
5
  import { knownDomainClosure, lookupForDomainClosure } from '../resolution/closure.js';
5
- import { validateDomainSchemaSemantics } from '../validation/domain.js';
6
+ import { buildDomainResolutionContext } from '../resolution/context.js';
7
+ /**
8
+ * Every owner passed acceptance against the closure it retains, which is the only way a schema
9
+ * gets one, so its analysis is derived without validating it again.
10
+ */
6
11
  export function contextOf(root) {
7
12
  const schemas = new Map([
8
13
  [root.origin, root],
@@ -12,16 +17,17 @@ export function contextOf(root) {
12
17
  return {
13
18
  schemas,
14
19
  analysis(owner) {
15
- if (!analyses.has(owner)) {
16
- const validation = validateDomainSchemaSemantics(owner, lookupForDomainClosure(owner));
17
- analyses.set(owner, validation.diagnostics.length > 0 ? undefined : validation.analysis.definitions);
20
+ let definitions = analyses.get(owner);
21
+ if (definitions === undefined) {
22
+ definitions = analyzeDefinitions(buildDomainResolutionContext(owner, lookupForDomainClosure(owner))).definitions;
23
+ analyses.set(owner, definitions);
18
24
  }
19
- return analyses.get(owner);
25
+ return definitions;
20
26
  },
21
27
  };
22
28
  }
23
29
  export function effectiveDefinition(context, owner, ref) {
24
- return context.analysis(owner)?.get(definitionRefIndex(ref));
30
+ return context.analysis(owner).get(definitionRefIndex(ref));
25
31
  }
26
32
  /** Effective Methods are keyed by name; a coordinate names one by its exact Method Key. */
27
33
  export function effectiveMethod(analysis, key) {
@@ -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) {
@@ -43,6 +43,7 @@ export declare const PROPERTY_FIELDS: Readonly<{
43
43
  schema: "always";
44
44
  required: "always";
45
45
  auto: "always";
46
+ immutable: "always";
46
47
  state: "always";
47
48
  description: Readonly<{
48
49
  excluded: 'annotation';
@@ -132,6 +133,7 @@ export declare const FIELD_ROLES: Readonly<{
132
133
  schema: "always";
133
134
  required: "always";
134
135
  auto: "always";
136
+ immutable: "always";
135
137
  state: "always";
136
138
  description: Readonly<{
137
139
  excluded: 'annotation';
@@ -22,6 +22,7 @@ export const PROPERTY_FIELDS = Object.freeze({
22
22
  schema: 'always',
23
23
  required: 'always',
24
24
  auto: 'always',
25
+ immutable: 'always',
25
26
  state: 'always',
26
27
  description: ANNOTATION,
27
28
  });
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'DV_VIEW_TARGET' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
1
+ export type SchemaDiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_MEMBER_ROOT_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEANING_INCONSISTENT' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_REF' | 'DV_DATA_COMPOSITION' | 'DV_DEPENDENCY_ACYCLIC' | 'DV_DEPENDENCY_DIRECT' | 'DV_DEPENDENCY_EXACT' | 'DV_DEPENDENCY_LIMITS' | 'DV_DEPENDENCY_REVISION_COHERENCE' | 'DV_DEPENDENCY_UNIQUE' | 'DV_EDGE_ACCEPTS' | 'DV_EDGE_CLASS_COMPOSITION' | 'DV_EDGE_CONSTRAINT' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'DV_VIEW_TARGET' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_ABSTRACT_POLICY' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_INVALID' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_MULTIPLE' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
2
2
  export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -142,6 +142,7 @@ function canonicalizeProperty(value) {
142
142
  set: value.auto.set,
143
143
  },
144
144
  }),
145
+ ...(value.immutable === undefined ? {} : { immutable: value.immutable }),
145
146
  ...(value.description === undefined ? {} : { description: value.description }),
146
147
  ...(value.state === undefined
147
148
  ? {}
@@ -1,4 +1,7 @@
1
1
  import type { SourceDomain } from '../../domain/model/domain.js';
2
2
  import type { DomainSchema } from '../model/schema.js';
3
- /** Resolve one accepted Schema through its exact retained source closure. */
3
+ /**
4
+ * Resolve one accepted Schema through its exact retained source closure. Acceptance validated it
5
+ * against that closure, which is the only way a Schema retains one, so it is not validated again.
6
+ */
4
7
  export declare function resolve<const S extends DomainSchema>(schema: S): SourceDomain<S>;
@@ -9,12 +9,14 @@ import { validateValueProgram } from '../../language/value/evaluate/program.js';
9
9
  import { validateJsonInstance, validatePreparedInstance, } from '../../language/value/evaluate/validate.js';
10
10
  import { classifyPreparedOrdering } from '../../language/value/ordering.js';
11
11
  import { revisionOfDomainSchema } from '../canonical/revision.js';
12
- import { SchemaError } from '../diagnostic.js';
13
- import { validateDomainSchemaSemantics } from '../validation/domain.js';
14
12
  import { lookupForDomainClosure } from './closure.js';
13
+ import { buildDomainResolutionContext } from './context.js';
15
14
  import { createDomainValueSchemaRegistry } from './value.registry.js';
16
15
  const resolvedSchemas = new WeakMap();
17
- /** Resolve one accepted Schema through its exact retained source closure. */
16
+ /**
17
+ * Resolve one accepted Schema through its exact retained source closure. Acceptance validated it
18
+ * against that closure, which is the only way a Schema retains one, so it is not validated again.
19
+ */
18
20
  export function resolve(schema) {
19
21
  const cached = resolvedSchemas.get(schema);
20
22
  if (cached !== undefined)
@@ -24,10 +26,7 @@ export function resolve(schema) {
24
26
  return resolved;
25
27
  }
26
28
  function resolveWithLookup(schema, lookup) {
27
- const validation = validateDomainSchemaSemantics(schema, lookup);
28
- if (validation.diagnostics.length > 0)
29
- throw new SchemaError(validation.diagnostics);
30
- const closure = [schema, ...validation.context.dependencies.values()];
29
+ const closure = [schema, ...buildDomainResolutionContext(schema, lookup).dependencies.values()];
31
30
  const registries = new Map(closure.map((owner) => [owner.origin, createDomainValueSchemaRegistry(owner, lookup)]));
32
31
  const input = {
33
32
  root: Object.freeze({
@@ -66,6 +65,7 @@ function sourceValidation(closure, registries) {
66
65
  properties.set(PropertyKey.of(definition.ref, name), Object.freeze({
67
66
  ordering: classifyPreparedOrdering(prepared.value),
68
67
  validate: (input) => validatePreparedInstance(prepared.value, input),
68
+ program: () => prepared.value.program(),
69
69
  }));
70
70
  }
71
71
  if (definition.kind !== 'node')