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

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 (152) hide show
  1. package/dist/v1/addressing/definition-ref.js +21 -37
  2. package/dist/v1/addressing/key.js +2 -1
  3. package/dist/v1/addressing/ordering.d.ts +2 -0
  4. package/dist/v1/addressing/ordering.js +4 -0
  5. package/dist/v1/addressing/patterns.d.ts +1 -1
  6. package/dist/v1/addressing/patterns.js +2 -6
  7. package/dist/v1/builder/class/authoring.js +5 -28
  8. package/dist/v1/builder/class/property.d.ts +3 -9
  9. package/dist/v1/builder/class/property.js +1 -0
  10. package/dist/v1/builder/lowering/class.js +5 -1
  11. package/dist/v1/builder/lowering/policy.js +1 -0
  12. package/dist/v1/builder/lowering/schema/context.js +2 -4
  13. package/dist/v1/builder/lowering/value/path.js +2 -4
  14. package/dist/v1/builder/lowering/value/zod.js +1 -3
  15. package/dist/v1/builder/policy/authoring.js +3 -2
  16. package/dist/v1/builder/schema/define.d.ts +0 -1
  17. package/dist/v1/builder/schema/diagnostic.d.ts +2 -1
  18. package/dist/v1/builder/schema/diagnostic.js +5 -10
  19. package/dist/v1/builder/schema/projection.d.ts +1 -1
  20. package/dist/v1/builder/value/full-match.d.ts +0 -1
  21. package/dist/v1/builder/value/full-match.js +0 -1
  22. package/dist/v1/bundle/codec.js +4 -6
  23. package/dist/v1/bundle/diagnostic.d.ts +2 -0
  24. package/dist/v1/bundle/diagnostic.js +9 -7
  25. package/dist/v1/bundle/structure.js +4 -6
  26. package/dist/v1/compiled/admission/diagnostic.d.ts +1 -1
  27. package/dist/v1/compiled/admission/diagnostic.js +1 -3
  28. package/dist/v1/compiled/admission/revision.d.ts +9 -1
  29. package/dist/v1/compiled/admission/revision.js +6 -3
  30. package/dist/v1/compiled/admission/semantics/class.d.ts +4 -1
  31. package/dist/v1/compiled/admission/semantics/class.js +4 -8
  32. package/dist/v1/compiled/admission/semantics/core.d.ts +1 -24
  33. package/dist/v1/compiled/admission/semantics/core.js +54 -34
  34. package/dist/v1/compiled/admission/semantics/edge.d.ts +2 -4
  35. package/dist/v1/compiled/admission/semantics/edge.js +6 -65
  36. package/dist/v1/compiled/admission/semantics/policy/callable.js +0 -2
  37. package/dist/v1/compiled/admission/semantics/policy/contracts.d.ts +5 -20
  38. package/dist/v1/compiled/admission/semantics/policy/contracts.js +8 -21
  39. package/dist/v1/compiled/admission/semantics/policy/parity.js +13 -22
  40. package/dist/v1/compiled/admission/semantics/policy/pattern.d.ts +4 -1
  41. package/dist/v1/compiled/admission/semantics/policy/pattern.js +4 -16
  42. package/dist/v1/compiled/admission/semantics/policy/typing.d.ts +2 -24
  43. package/dist/v1/compiled/admission/semantics/policy/typing.js +19 -201
  44. package/dist/v1/compiled/compiler/compile.js +6 -8
  45. package/dist/v1/compiled/diagnostic.js +2 -3
  46. package/dist/v1/compiled/loader/load.d.ts +3 -2
  47. package/dist/v1/compiled/loader/load.js +10 -44
  48. package/dist/v1/domain/create.js +3 -5
  49. package/dist/v1/domain/namespace/member-collection.js +4 -6
  50. package/dist/v1/domain/validation/create.d.ts +4 -2
  51. package/dist/v1/domain/validation/create.js +21 -69
  52. package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
  53. package/dist/v1/language/class/inheritance.d.ts +18 -0
  54. package/dist/v1/language/class/inheritance.js +73 -0
  55. package/dist/v1/language/class/model/class.d.ts +4 -4
  56. package/dist/v1/language/class/model/edge.d.ts +2 -2
  57. package/dist/v1/language/class/model/property.d.ts +16 -7
  58. package/dist/v1/language/class/property-values.js +2 -4
  59. package/dist/v1/language/core/facts.d.ts +72 -0
  60. package/dist/v1/language/core/facts.js +95 -0
  61. package/dist/v1/language/definition/metadata.d.ts +5 -0
  62. package/dist/v1/language/definition/metadata.js +7 -0
  63. package/dist/v1/language/policy/analysis/branches.d.ts +8 -0
  64. package/dist/v1/language/policy/analysis/branches.js +2 -1
  65. package/dist/v1/language/policy/analysis/budgets.d.ts +13 -1
  66. package/dist/v1/language/policy/analysis/budgets.js +104 -0
  67. package/dist/v1/language/policy/analysis/typing.d.ts +48 -0
  68. package/dist/v1/language/policy/analysis/typing.js +338 -0
  69. package/dist/v1/language/policy/model/policy.d.ts +5 -5
  70. package/dist/v1/language/policy/normalization/normalize.js +17 -16
  71. package/dist/v1/language/validation/diagnostic.d.ts +15 -1
  72. package/dist/v1/language/validation/diagnostic.js +21 -0
  73. package/dist/v1/language/validation/rule.d.ts +26 -0
  74. package/dist/v1/language/validation/rule.js +116 -0
  75. package/dist/v1/language/value/compile/fields.js +4 -8
  76. package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
  77. package/dist/v1/language/value/profile/rules.js +17 -43
  78. package/dist/v1/language/value/profile/validate.js +1 -10
  79. package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
  80. package/dist/v1/language/value/profile/vocabulary.js +52 -5
  81. package/dist/v1/language/value/reference/recursion.js +2 -22
  82. package/dist/v1/language/value/reference/registry.index.js +2 -1
  83. package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
  84. package/dist/v1/language/value/syntax/keywords.js +4 -20
  85. package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
  86. package/dist/v1/language/value/syntax/pointer.d.ts +2 -0
  87. package/dist/v1/language/value/syntax/pointer.js +5 -2
  88. package/dist/v1/language/value/syntax/walk.d.ts +2 -0
  89. package/dist/v1/language/value/syntax/walk.js +19 -20
  90. package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
  91. package/dist/v1/schema/analysis/class.js +33 -15
  92. package/dist/v1/schema/compatibility/api.d.ts +1 -1
  93. package/dist/v1/schema/compatibility/coordinate.js +3 -3
  94. package/dist/v1/schema/compatibility/diff.js +2 -4
  95. package/dist/v1/schema/compatibility/meaning/footprint.js +16 -50
  96. package/dist/v1/schema/compatibility/model.d.ts +5 -0
  97. package/dist/v1/schema/compatibility/structure/requirements.js +26 -58
  98. package/dist/v1/schema/compatibility/walk.d.ts +17 -0
  99. package/dist/v1/schema/compatibility/walk.js +41 -0
  100. package/dist/v1/schema/diagnostic.d.ts +17 -3
  101. package/dist/v1/schema/diagnostic.js +14 -11
  102. package/dist/v1/schema/index.d.ts +3 -1
  103. package/dist/v1/schema/index.js +1 -0
  104. package/dist/v1/schema/model/schema.d.ts +19 -1
  105. package/dist/v1/schema/normalization/domain.js +1 -0
  106. package/dist/v1/schema/resolution/closure.js +2 -1
  107. package/dist/v1/schema/resolution/context.js +6 -6
  108. package/dist/v1/schema/rule.d.ts +29 -0
  109. package/dist/v1/schema/rule.js +415 -0
  110. package/dist/v1/schema/validation/callable.d.ts +4 -1
  111. package/dist/v1/schema/validation/callable.js +13 -6
  112. package/dist/v1/schema/validation/class.js +46 -96
  113. package/dist/v1/schema/validation/core.js +68 -202
  114. package/dist/v1/schema/validation/domain.js +1 -10
  115. package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
  116. package/dist/v1/schema/validation/domain.json-schema.js +183 -0
  117. package/dist/v1/schema/validation/domain.structure.d.ts +4018 -0
  118. package/dist/v1/schema/validation/domain.structure.js +343 -155
  119. package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
  120. package/dist/v1/schema/validation/json.json-schema.js +36 -0
  121. package/dist/v1/schema/validation/policy/budgets.js +5 -112
  122. package/dist/v1/schema/validation/policy/context.d.ts +5 -3
  123. package/dist/v1/schema/validation/policy/context.js +5 -3
  124. package/dist/v1/schema/validation/policy/objects.d.ts +1 -1
  125. package/dist/v1/schema/validation/policy/objects.js +6 -14
  126. package/dist/v1/schema/validation/policy/references.js +10 -27
  127. package/dist/v1/schema/validation/policy/typing.d.ts +3 -18
  128. package/dist/v1/schema/validation/policy/typing.js +22 -356
  129. package/dist/v1/schema/validation/policy/uses.d.ts +1 -1
  130. package/dist/v1/schema/validation/policy/uses.js +9 -25
  131. package/dist/v1/schema/validation/policy/validate.js +3 -2
  132. package/dist/v1/schema/validation/value-api.d.ts +12 -1
  133. package/dist/v1/schema/validation/value-api.js +12 -3
  134. package/dist/v1/schema/validation/value.js +8 -10
  135. package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
  136. package/dist/v1/schema/validation/value.json-schema.js +126 -0
  137. package/dist/v1/schema/validation/view.js +4 -8
  138. package/package.json +3 -1
  139. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +0 -5
  140. package/dist/v1/compiled/admission/semantics/cardinality.js +0 -128
  141. package/dist/v1/compiled/admission/semantics/policy/equality.d.ts +0 -8
  142. package/dist/v1/compiled/admission/semantics/policy/equality.js +0 -27
  143. package/dist/v1/compiled/admission/semantics/policy/measure.d.ts +0 -9
  144. package/dist/v1/compiled/admission/semantics/policy/measure.js +0 -81
  145. package/dist/v1/compiled/admission/semantics/policy/variants.d.ts +0 -10
  146. package/dist/v1/compiled/admission/semantics/policy/variants.js +0 -65
  147. package/dist/v1/language/class/index.d.ts +0 -8
  148. package/dist/v1/language/class/index.js +0 -1
  149. package/dist/v1/language/core/index.d.ts +0 -2
  150. package/dist/v1/language/core/index.js +0 -1
  151. package/dist/v1/language/value/index.d.ts +0 -11
  152. package/dist/v1/language/value/index.js +0 -8
@@ -3,39 +3,48 @@ import { Key, PropertyKey } from '../../addressing/index.js';
3
3
  import { definitionRefIndex } from '../../addressing/ordering.js';
4
4
  import { composeMethods } from '../../language/callable/composition.js';
5
5
  import { canonicalizeValueSchema } from '../../language/value/profile/canonicalize.js';
6
+ import { escapePointerToken } from '../../language/value/syntax/pointer.js';
6
7
  import { diagnostic } from '../diagnostic.js';
7
8
  const formatDefinitionRef = Key.of;
8
9
  export function analyzeDefinitions(environment) {
9
10
  const diagnostics = [];
10
11
  const result = new Map();
11
12
  const visiting = new Set();
13
+ // A dependency was accepted against this same exact closure: a rule breaks here only at a Class
14
+ // of the candidate, or on an incomplete closure, which the dependency diagnostics report. An
15
+ // unresolved parent is reported where the candidate names it, by Class validation.
16
+ const reporter = (owner) => (code, at, message, ref) => {
17
+ if (owner.origin !== environment.candidate.origin)
18
+ return;
19
+ const pointer = `/classes/${escapePointerToken(owner.name)}${at}`;
20
+ diagnostics.push(diagnostic(code, pointer, message, ref));
21
+ };
12
22
  const analyze = (ref) => {
13
23
  const key = definitionRefIndex(ref);
14
24
  const cached = result.get(key);
15
25
  if (cached !== undefined)
16
26
  return cached;
17
- if (visiting.has(key)) {
18
- diagnostics.push(diagnostic('DV_CLASS_ACYCLIC', '', `Inheritance cycle reaches ${formatDefinitionRef(ref)}.`, formatDefinitionRef(ref)));
19
- return undefined;
20
- }
21
27
  const resolved = environment.resolveAny(ref);
22
28
  if (resolved === undefined)
23
29
  return undefined;
24
30
  visiting.add(key);
31
+ const report = reporter(ref);
25
32
  const declaration = resolved.declaration;
26
33
  const parents = declaration.extends;
27
- const parentAnalyses = parents.flatMap((parent) => {
34
+ const parentAnalyses = parents.flatMap((parent, index) => {
28
35
  const target = environment.resolveAny(parent);
29
- if (target === undefined) {
30
- diagnostics.push(diagnostic('DV_REF_RESOLVES', '', `Inheritance ref ${formatDefinitionRef(parent)} does not resolve.`, formatDefinitionRef(parent)));
36
+ if (target?.ref.kind !== 'class')
37
+ return [];
38
+ if (visiting.has(definitionRefIndex(target.ref))) {
39
+ report('DV_CLASS_ACYCLIC', `/extends/${index}`, `Inheritance cycle reaches ${formatDefinitionRef(parent)}.`, formatDefinitionRef(parent));
31
40
  return [];
32
41
  }
33
- const analysis = target.ref.kind === 'class' ? analyze(target.ref) : undefined;
42
+ const analysis = analyze(target.ref);
34
43
  return analysis === undefined ? [] : [analysis];
35
44
  });
36
45
  const properties = mergeFields(parentAnalyses.map((parent) => parent.properties), ownFields(ref, declaration));
37
- const data = mergeData(ref, declaration, parentAnalyses, diagnostics);
38
- const methods = mergeMethods(ref, declaration, parentAnalyses, diagnostics);
46
+ const data = mergeData(ref, declaration, parentAnalyses, report);
47
+ const methods = mergeMethods(ref, declaration, parentAnalyses, report);
39
48
  const analysis = {
40
49
  ref,
41
50
  declaration,
@@ -82,7 +91,7 @@ function mergeFields(inherited, own) {
82
91
  }
83
92
  return result;
84
93
  }
85
- function mergeData(owner, declaration, parents, diagnostics) {
94
+ function mergeData(owner, declaration, parents, report) {
86
95
  let effective;
87
96
  for (const parent of parents) {
88
97
  if (parent.data === undefined)
@@ -92,14 +101,15 @@ function mergeData(owner, declaration, parents, diagnostics) {
92
101
  continue;
93
102
  }
94
103
  if (effective.identity !== parent.data.identity) {
95
- diagnostics.push(diagnostic('DV_DATA_COMPOSITION', '', `Data has distinct declaring owners ${formatDefinitionRef(effective.owner)} and ${formatDefinitionRef(parent.data.owner)}.`));
104
+ const index = declaration.extends.findIndex((ref) => definitionRefIndex(ref) === definitionRefIndex(parent.ref));
105
+ report('DV_DATA_COMPOSITION', `/extends/${index}`, `Data has distinct declaring owners ${formatDefinitionRef(effective.owner)} and ${formatDefinitionRef(parent.data.owner)}.`);
96
106
  }
97
107
  }
98
108
  const own = 'data' in declaration ? declaration.data : undefined;
99
109
  if (own === undefined)
100
110
  return effective;
101
111
  if (effective !== undefined) {
102
- diagnostics.push(diagnostic('DV_DATA_COMPOSITION', '', `Data redeclares the inherited contract from ${formatDefinitionRef(effective.owner)}.`));
112
+ report('DV_DATA_COMPOSITION', '/data', `Data redeclares the inherited contract from ${formatDefinitionRef(effective.owner)}.`);
103
113
  }
104
114
  return {
105
115
  identity: `${definitionRefIndex(owner)}\u0000data`,
@@ -107,12 +117,20 @@ function mergeData(owner, declaration, parents, diagnostics) {
107
117
  declaration: own,
108
118
  };
109
119
  }
110
- function mergeMethods(owner, declaration, parents, diagnostics) {
120
+ function mergeMethods(owner, declaration, parents, report) {
111
121
  const own = new Map(Object.entries('methods' in declaration ? declaration.methods : {}).map(([name, method]) => [
112
122
  name,
113
123
  { key: `${Key.of(owner)}.method.${name}`, owner, name, declaration: method },
114
124
  ]));
115
- return composeMethods(parents, own, declaration.abstract, (left, right) => sameMethodSignature(left.declaration, right.declaration), (message) => diagnostics.push(diagnostic('DV_METHOD_COMPOSITION', '', message, Key.of(owner))));
125
+ return composeMethods(parents, own, declaration.abstract, (left, right) => sameMethodSignature(left.declaration, right.declaration),
126
+ // A changed signature is the local Method's; an ambiguous or missing one needs a local Method.
127
+ (message, issue) => {
128
+ const local = issue.kind === 'signature'
129
+ ? [...own.values()].find(({ key }) => key === issue.local)
130
+ : undefined;
131
+ const at = local === undefined ? '/methods' : `/methods/${escapePointerToken(local.name)}`;
132
+ report('DV_METHOD_COMPOSITION', at, message, Key.of(owner));
133
+ });
116
134
  }
117
135
  /** The exact signature required by abstract Method composition. */
118
136
  export function sameMethodSignature(left, right) {
@@ -1,4 +1,4 @@
1
1
  export { compare, compareMeaning, compareStructure } from './compare.js';
2
2
  export { closedParents } from './extension.js';
3
3
  export { requiredBump } from './versioning/index.js';
4
- export type { Analysis, Assessment, Bump, Comparison, ComparisonEntry, ComparisonEvidence, ComparisonRequest, ComparisonScope, Facet, Fingerprint, MeaningComparison, Observation, RequiredBump, SchemaIdentity, StructureComparison, Subject, Usage, VersionChange, } from './model.js';
4
+ export type { Analysis, Assessment, Bump, Comparison, ComparisonEntry, ComparisonEvidence, ComparisonRequest, ComparisonScope, DiagnosticCode, Facet, Fingerprint, MeaningComparison, Observation, RequiredBump, SchemaIdentity, StructureComparison, Subject, Usage, VersionChange, } from './model.js';
@@ -20,10 +20,10 @@ export function keyForDefinitionRef(ref) {
20
20
  /** Interpret one canonical V1 Key as a Definition or a Class member location. */
21
21
  export function keyReference(key) {
22
22
  const semantic = key.slice(key.indexOf(':') + 1);
23
+ // A Core slug may hold `.`: the name of a Core key is the whole rest of its coordinate.
24
+ if (semantic.startsWith('core.'))
25
+ return { kind: 'core', name: semantic.slice('core.'.length) };
23
26
  const parts = semantic.split('.');
24
- if (parts[0] === 'core' && parts.length === 2) {
25
- return { kind: 'core', name: parts[1] };
26
- }
27
27
  if (parts.length === 2) {
28
28
  return { kind: parts[0], name: parts[1] };
29
29
  }
@@ -1,5 +1,6 @@
1
1
  import { compareUnicode } from '../../addressing/ordering.js';
2
2
  import { isValueObject as isJsonObject } from '../../language/value/model/object.js';
3
+ import { escapePointerToken } from '../../language/value/syntax/pointer.js';
3
4
  /** Structural differences between two portable values, leaf by leaf, in JSON Pointer order. */
4
5
  export function differences(before, after) {
5
6
  const found = [];
@@ -17,7 +18,7 @@ function collect(before, after, path, found) {
17
18
  if (isJsonObject(before) && isJsonObject(after)) {
18
19
  const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort(compareUnicode);
19
20
  for (const key of keys) {
20
- collect(before[key], after[key], `${path}/${escape(key)}`, found);
21
+ collect(before[key], after[key], `${path}/${escapePointerToken(key)}`, found);
21
22
  }
22
23
  return;
23
24
  }
@@ -34,6 +35,3 @@ function collect(before, after, path, found) {
34
35
  ...(after === undefined ? {} : { after }),
35
36
  }));
36
37
  }
37
- function escape(segment) {
38
- return segment.replaceAll('~', '~0').replaceAll('/', '~1');
39
- }
@@ -1,59 +1,25 @@
1
- import { Key } from '../../../addressing/index.js';
2
- import { compareAddedProperties, compareLostProperties } from '../comparators/classes.js';
3
- import { compareFacet } from '../comparators/index.js';
4
- import { contextInvalid } from '../context.js';
5
1
  import { callableReferences, classReferences, inherits, memberReferences, valueReferences, } from '../references.js';
2
+ import { walkReferences } from '../walk.js';
6
3
  /** Meaning follows complete callable contracts through the exact authored reference closure. */
7
4
  export function compareFootprint({ context, report, }) {
8
- const queue = [...context.roots];
9
- const visited = new Set();
10
- for (const reference of queue) {
11
- report.note(reference);
12
- const { coordinate, usage } = reference;
13
- const visit = `${coordinate.key}:${usage.relation}`;
14
- if (visited.has(visit))
15
- continue;
16
- visited.add(visit);
17
- const pair = context.pair(coordinate);
18
- const subject = pair.source;
19
- if (subject === undefined)
20
- throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
21
- let facet;
5
+ walkReferences({ context, report }, (subject, { usage }, reach) => {
22
6
  switch (subject.kind) {
23
- case 'class': {
24
- facet = inherits(usage) ? 'class.inherit' : 'class.reference';
25
- queue.push(...classReferences(subject, false));
7
+ case 'class':
8
+ reach(...classReferences(subject, false));
26
9
  if (inherits(usage))
27
- queue.push(...classReferences(subject, true));
28
- break;
29
- }
10
+ reach(...classReferences(subject, true));
11
+ return inherits(usage) ? 'class.inherit' : 'class.reference';
30
12
  case 'property':
31
- facet = 'property';
32
- queue.push(...valueReferences(subject));
33
- break;
13
+ reach(...valueReferences(subject));
14
+ return 'property';
34
15
  case 'method':
35
- facet = 'method.signature';
36
- queue.push(...callableReferences(subject));
37
- break;
38
- case 'member': {
39
- facet =
40
- subject.coordinate.ref.kind === 'function'
41
- ? 'function.signature'
42
- : subject.coordinate.ref.kind;
43
- queue.push(...memberReferences(subject));
44
- }
16
+ reach(...callableReferences(subject));
17
+ return 'method.signature';
18
+ case 'member':
19
+ reach(...memberReferences(subject));
20
+ return subject.coordinate.ref.kind === 'function'
21
+ ? 'function.signature'
22
+ : subject.coordinate.ref.kind;
45
23
  }
46
- if (Key.origin(coordinate.key) !== context.source.origin)
47
- continue;
48
- report.record({
49
- pair,
50
- facet,
51
- ...compareFacet({ context, pair, facet }),
52
- requirement: { kind: facet },
53
- });
54
- if (facet === 'class.inherit')
55
- compareAddedProperties({ context, pair, report });
56
- if (subject.kind === 'class')
57
- compareLostProperties({ pair, report });
58
- }
24
+ });
59
25
  }
@@ -4,6 +4,11 @@ import type { DomainSchema } from '../model/schema.js';
4
4
  import type { ReferenceSite } from '../resolution/origins.js';
5
5
  import type { MeaningDifference } from './diff.js';
6
6
  export type Fingerprint = `sha256:${string}`;
7
+ /**
8
+ * The codes a comparison request is refused with: the request, not a Schema, is invalid. A
9
+ * `schema.Error` carries them beside the codes of a refused Schema document.
10
+ */
11
+ export type DiagnosticCode = 'DM_CAPABILITY_INVALID' | 'DM_DEPENDENCY_CONTEXT_INVALID' | 'DM_DEPENDENCY_NOT_RETAINED' | 'DM_MEMBER_ORIGIN_MISMATCH' | 'DM_MEMBER_ROOT_INVALID';
7
12
  /** Inputs carry accepted schemas; a dependency's source is always its retained exact context. */
8
13
  export interface ComparisonRequest {
9
14
  readonly scope: {
@@ -1,79 +1,47 @@
1
- import { Key } from '../../../addressing/index.js';
2
1
  import { inheritanceOf } from '../../../language/class/inheritance.js';
3
- import { compareAddedProperties, compareLostProperties, inheritanceMeaning, } from '../comparators/classes.js';
2
+ import { inheritanceMeaning } from '../comparators/classes.js';
4
3
  import { compareFacet } from '../comparators/index.js';
5
4
  import { fingerprintMeaning } from '../comparators/values.js';
6
5
  import { contextInvalid } from '../context.js';
7
6
  import { methodCoordinate } from '../coordinate.js';
8
7
  import { closedParents } from '../extension.js';
9
8
  import { callableReferences, classReferences, inherits, memberReferences, usageOf, valueReferences, } from '../references.js';
9
+ import { walkReferences } from '../walk.js';
10
10
  /** Structure follows data and abstract obligations; concrete IO never expands its requirements. */
11
11
  export function compareRequirements({ context, report, }) {
12
- const queue = [...context.roots];
13
- const visited = new Set();
14
- for (const reference of queue) {
15
- report.note(reference);
16
- const { coordinate, usage } = reference;
17
- const visit = `${coordinate.key}:${usage.relation}`;
18
- if (visited.has(visit))
19
- continue;
20
- visited.add(visit);
21
- const pair = context.pair(coordinate);
22
- const subject = pair.source;
23
- if (subject === undefined)
24
- throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
25
- let facet;
12
+ walkReferences({ context, report }, (subject, { coordinate, usage }, reach) => {
26
13
  switch (subject.kind) {
27
- case 'class': {
28
- facet = inherits(usage) ? 'class.inherit' : 'class.reference';
29
- queue.push(...classReferences(subject, false));
14
+ case 'class':
15
+ reach(...classReferences(subject, false));
30
16
  if (inherits(usage)) {
31
- queue.push(...classReferences(subject, true));
17
+ reach(...classReferences(subject, true));
32
18
  for (const method of subject.analysis.requirements.values())
33
- queue.push({
19
+ reach({
34
20
  coordinate: methodCoordinate(method.owner, method.name),
35
21
  usage: usageOf(subject.schema, 'abstract', coordinate.key),
36
22
  });
37
23
  }
38
- break;
39
- }
24
+ return inherits(usage) ? 'class.inherit' : 'class.reference';
40
25
  case 'property':
41
- facet = 'property';
42
- queue.push(...valueReferences(subject));
43
- break;
44
- case 'method': {
45
- facet = usage.relation === 'abstract' ? 'method.inheritance' : 'method.existence';
46
- if (facet === 'method.inheritance')
47
- queue.push(...callableReferences(subject));
48
- break;
49
- }
50
- case 'member': {
51
- facet =
52
- subject.coordinate.ref.kind === 'function'
53
- ? 'function.existence'
54
- : subject.coordinate.ref.kind;
55
- if (facet !== 'function.existence')
56
- queue.push(...memberReferences(subject));
57
- }
26
+ reach(...valueReferences(subject));
27
+ return 'property';
28
+ case 'method':
29
+ if (usage.relation !== 'abstract')
30
+ return 'method.existence';
31
+ reach(...callableReferences(subject));
32
+ return 'method.inheritance';
33
+ case 'member':
34
+ if (subject.coordinate.ref.kind === 'function')
35
+ return 'function.existence';
36
+ reach(...memberReferences(subject));
37
+ return subject.coordinate.ref.kind;
58
38
  }
59
- if (Key.origin(coordinate.key) !== context.source.origin)
60
- continue;
61
- // Class membership does not establish an invocation dependency. Explicit member selection,
62
- // a Method the dependent declares as a capability, and inherited abstract obligations do.
63
- const required = facet !== 'method.existence' ||
64
- usage.relation === 'selection' ||
65
- usage.relation === 'capability';
66
- report.record({
67
- pair,
68
- facet,
69
- ...compareFacet({ context, pair, facet }),
70
- ...(required ? { requirement: { kind: facet } } : {}),
71
- });
72
- if (facet === 'class.inherit')
73
- compareAddedProperties({ context, pair, report });
74
- if (subject.kind === 'class')
75
- compareLostProperties({ pair, report });
76
- }
39
+ },
40
+ // Class membership does not establish an invocation dependency. Explicit member selection,
41
+ // a Method the dependent declares as a capability, and inherited abstract obligations do.
42
+ (facet, usage) => facet !== 'method.existence' ||
43
+ usage.relation === 'selection' ||
44
+ usage.relation === 'capability');
77
45
  compareReliance({ context, report });
78
46
  compareExtension({ context, report });
79
47
  }
@@ -0,0 +1,17 @@
1
+ import type { Usage } from './model.js';
2
+ import type { Reference } from './references.js';
3
+ import type { Report } from './report.js';
4
+ import type { ComparisonContext } from './scope.js';
5
+ import type { ResolvedSubject } from './subjects.js';
6
+ import { compareFacet } from './comparators/index.js';
7
+ type WalkedFacet = Parameters<typeof compareFacet>[0]['facet'];
8
+ /**
9
+ * Follow the reference closure from the roots once per coordinate and usage relation. `expand`
10
+ * names the facet a reached subject is compared under and `reach`es what it references; a subject
11
+ * of the compared origin is then recorded, as a requirement when `required` says so.
12
+ */
13
+ export declare function walkReferences({ context, report }: {
14
+ readonly context: ComparisonContext;
15
+ readonly report: Report;
16
+ }, expand: (subject: ResolvedSubject, reference: Reference, reach: (...references: readonly Reference[]) => void) => WalkedFacet, required?: (facet: WalkedFacet, usage: Usage) => boolean): void;
17
+ export {};
@@ -0,0 +1,41 @@
1
+ import { Key } from '../../addressing/index.js';
2
+ import { compareAddedProperties, compareLostProperties } from './comparators/classes.js';
3
+ import { compareFacet } from './comparators/index.js';
4
+ import { contextInvalid } from './context.js';
5
+ /**
6
+ * Follow the reference closure from the roots once per coordinate and usage relation. `expand`
7
+ * names the facet a reached subject is compared under and `reach`es what it references; a subject
8
+ * of the compared origin is then recorded, as a requirement when `required` says so.
9
+ */
10
+ export function walkReferences({ context, report }, expand, required = () => true) {
11
+ const queue = [...context.roots];
12
+ const reach = (...references) => {
13
+ queue.push(...references);
14
+ };
15
+ const visited = new Set();
16
+ for (const reference of queue) {
17
+ report.note(reference);
18
+ const { coordinate, usage } = reference;
19
+ const visit = `${coordinate.key}:${usage.relation}`;
20
+ if (visited.has(visit))
21
+ continue;
22
+ visited.add(visit);
23
+ const pair = context.pair(coordinate);
24
+ const subject = pair.source;
25
+ if (subject === undefined)
26
+ throw contextInvalid(`Retained acceptance context has no meaning for ${coordinate.key}.`, coordinate.key);
27
+ const facet = expand(subject, reference, reach);
28
+ if (Key.origin(coordinate.key) !== context.source.origin)
29
+ continue;
30
+ report.record({
31
+ pair,
32
+ facet,
33
+ ...compareFacet({ context, pair, facet }),
34
+ ...(required(facet, usage) ? { requirement: { kind: facet } } : {}),
35
+ });
36
+ if (facet === 'class.inherit')
37
+ compareAddedProperties({ context, pair, report });
38
+ if (subject.kind === 'class')
39
+ compareLostProperties({ pair, report });
40
+ }
41
+ }
@@ -1,11 +1,25 @@
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_ADMISSION' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CALLABLE_POLICY_AUTH' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_INHERITANCE' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_EDGE_UNIQUE' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_NODE_UNIQUE' | '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_FOREIGN_MINIMUM' | 'DV_EDGE_LIFETIME' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_EDGE_UNIQUE' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_NODE_UNIQUE' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_COMPOSITION' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | '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
- export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
1
+ import type { DiagnosticCode as LanguageDiagnosticCode } from '../language/validation/diagnostic.js';
2
+ import type { Diagnostic as LanguageDiagnostic } from '../language/validation/diagnostic.js';
3
+ import type { DiagnosticCode as CompatibilityDiagnosticCode } from './compatibility/model.js';
4
+ /** The codes a Schema document is refused with, one per rule of `RULES`. */
5
+ export type SchemaDiagnosticCode = 'DV_ACCEPTANCE_CONTEXT' | 'DV_CALLABLE_ADMISSION' | 'DV_CALLABLE_AUTH' | 'DV_CALLABLE_IO' | 'DV_CLASS_ABSTRACT' | 'DV_CLASS_ACYCLIC' | 'DV_CLASS_INHERITANCE' | 'DV_CLASS_KIND' | 'DV_CORE_EDGE_CLASS' | 'DV_CORE_EDGE_CONSTRAINTS' | 'DV_CORE_EDGE_KEY' | 'DV_CORE_EDGE_PROPERTIES' | 'DV_CORE_EDGE_UNIQUE' | 'DV_CORE_NODE_CLASS' | 'DV_CORE_NODE_PROPERTIES' | 'DV_CORE_NODE_UNIQUE' | '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_FOREIGN_MINIMUM' | 'DV_EDGE_LIFETIME' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNIQUE' | 'DV_ENTRYPOINT' | 'DV_JSON_VALUE' | 'DV_METHOD_ABSTRACT' | 'DV_METHOD_COMPOSITION' | 'DV_NODE_UNIQUE' | 'DV_PROPERTY_AUTO' | 'DV_PROPERTY_IMMUTABLE' | 'DV_PROPERTY_STATE' | 'DV_REF_RESOLVES' | 'DV_VALUE_SCHEMA' | 'PL_BUDGET' | 'PL_COMPOSITION' | 'PL_CONNECTIVITY' | 'PL_REF' | 'PL_REPEAT' | 'PL_TARGET' | 'PL_TYPING' | 'PL_USE' | 'PL_VARIABLE_SCOPE' | 'STRUCTURE_BINARY_INPUT' | 'STRUCTURE_CALLABLE_POLICY_AUTH' | 'STRUCTURE_DEPENDENCY_ALIAS' | 'STRUCTURE_DOMAIN_NAME' | 'STRUCTURE_DUPLICATE_ITEM' | 'STRUCTURE_EDGE_BLOB' | 'STRUCTURE_EDGE_ORIENTATION' | 'STRUCTURE_FORMAT' | 'STRUCTURE_KEY' | 'STRUCTURE_MAXIMUM' | 'STRUCTURE_MINIMUM' | 'STRUCTURE_PROPERTY_KEY' | 'STRUCTURE_PROTO_KEY' | 'STRUCTURE_REQUIRED' | 'STRUCTURE_STATE_TRANSITIONS' | 'STRUCTURE_TYPE' | 'STRUCTURE_UNION' | 'STRUCTURE_UNKNOWN_FIELD' | 'STRUCTURE_VALUE' | 'UNSUPPORTED_DOCUMENT_FORMAT' | 'UNSUPPORTED_DSL_VERSION';
6
+ /**
7
+ * A Schema refuses a document with its own codes, and a comparison request with the
8
+ * compatibility codes. `pointer` names the position the refused rule applies to, in the document
9
+ * or in the request; it is empty, the root, only when text does not parse or the document is not
10
+ * an object.
11
+ */
12
+ export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode | CompatibilityDiagnosticCode = SchemaDiagnosticCode | CompatibilityDiagnosticCode> {
3
13
  readonly code: Code;
4
14
  readonly pointer: string;
5
15
  readonly message: string;
6
16
  readonly ref?: string;
17
+ /** The code of the value-language refusal the diagnostic carries; its message is the message. */
18
+ readonly valueCode?: LanguageDiagnosticCode;
7
19
  }
8
- export declare function diagnostic<const Code extends SchemaDiagnosticCode>(code: Code, pointer: string, message: string, ref?: string): SchemaDiagnostic<Code>;
20
+ export declare function diagnostic<const Code extends SchemaDiagnosticCode | CompatibilityDiagnosticCode>(code: Code, pointer: string, message: string, ref?: string): SchemaDiagnostic<Code>;
21
+ /** A Schema diagnostic carrying one value-language refusal, at its position and with its code. */
22
+ export declare function valueDiagnostic<const Code extends SchemaDiagnosticCode>(code: Code, refusal: LanguageDiagnostic): SchemaDiagnostic<Code>;
9
23
  export declare class SchemaError extends Error {
10
24
  readonly diagnostics: readonly SchemaDiagnostic[];
11
25
  constructor(diagnostics: readonly SchemaDiagnostic[]);
@@ -1,21 +1,24 @@
1
- import { compareUnicode } from '../addressing/ordering.js';
1
+ import { formatDiagnostics, orderDiagnostics } from '../language/validation/diagnostic.js';
2
2
  export function diagnostic(code, pointer, message, ref) {
3
3
  return Object.freeze({ code, pointer, message, ...(ref === undefined ? {} : { ref }) });
4
4
  }
5
+ /** A Schema diagnostic carrying one value-language refusal, at its position and with its code. */
6
+ export function valueDiagnostic(code, refusal) {
7
+ const { pointer, message, ref } = refusal;
8
+ return Object.freeze({
9
+ code,
10
+ pointer,
11
+ message,
12
+ ...(ref === undefined ? {} : { ref }),
13
+ valueCode: refusal.code,
14
+ });
15
+ }
5
16
  export class SchemaError extends Error {
6
17
  diagnostics;
7
18
  constructor(diagnostics) {
8
- const ordered = [...diagnostics].sort(compareDiagnostic);
9
- super(ordered.map(formatDiagnostic).join('\n'));
19
+ const ordered = orderDiagnostics(diagnostics);
20
+ super(formatDiagnostics(ordered));
10
21
  this.name = 'SchemaError';
11
22
  this.diagnostics = Object.freeze(ordered);
12
23
  }
13
24
  }
14
- function compareDiagnostic(left, right) {
15
- return (compareUnicode(left.pointer, right.pointer) ||
16
- compareUnicode(left.code, right.code) ||
17
- compareUnicode(left.message, right.message));
18
- }
19
- function formatDiagnostic(value) {
20
- return `${value.pointer || '/'} [${value.code}] ${value.message}`;
21
- }
@@ -1,7 +1,7 @@
1
1
  /** Admit, encode, inspect, and semantically resolve canonical V1 DomainSchemas. */
2
2
  import type { SourceDomain } from '../domain/index.js';
3
3
  import type { DomainSchema } from './model/schema.js';
4
- export type { DomainSchema, DomainSchemaIR, DomainOfSchema as DomainOf } from './model/schema.js';
4
+ export type { DomainSchema, DomainSchemaIR, DomainSchemaDocument as Document, DomainOfSchema as DomainOf, } from './model/schema.js';
5
5
  export type ResolvedDomainOf<Schema extends DomainSchema> = SourceDomain<Schema>;
6
6
  export interface Resolve {
7
7
  <const Schema extends DomainSchema>(schema: Schema): ResolvedDomainOf<Schema>;
@@ -20,6 +20,8 @@ export { compile, load } from '../compiled/index.js';
20
20
  export type { CompiledSchema, DomainOfCompiled } from '../compiled/index.js';
21
21
  export { SchemaError as Error } from './diagnostic.js';
22
22
  export type { SchemaDiagnostic as Diagnostic } from './diagnostic.js';
23
+ export { RULES } from './rule.js';
24
+ export type { SchemaRule as Rule, SchemaRuleStage as RuleStage } from './rule.js';
23
25
  export type { DomainSchemaLookup as Lookup } from './dependency/lookup.js';
24
26
  export type { DomainSchemaHeader as Header } from './codec/header.js';
25
27
  export type { DomainDependency } from '../addressing/index.js';
@@ -10,5 +10,6 @@ export { header } from './codec/header.js';
10
10
  export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/format.js';
11
11
  export { compile, load } from '../compiled/index.js';
12
12
  export { SchemaError as Error } from './diagnostic.js';
13
+ export { RULES } from './rule.js';
13
14
  /** The versioned value-schema grammar embedded in declarations. */
14
15
  export * as values from './validation/value-api.js';
@@ -1,8 +1,10 @@
1
- import type { Key } from '../../addressing/index.js';
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { Key, Revision } from '../../addressing/index.js';
2
3
  import type { DomainDependencyMap, ViewRef } from '../../addressing/index.js';
3
4
  import type { Domain, DomainOfSource, DomainSource } from '../../domain/model/domain.js';
4
5
  import type { FunctionDefinition } from '../../language/callable/model/callable.js';
5
6
  import type { ClassDefinition } from '../../language/class/model/class.js';
7
+ import type { PropertyValues } from '../../language/class/property-values.js';
6
8
  import type { CoreContent } from '../../language/core/model/core.js';
7
9
  import type { PolicyDefinition } from '../../language/policy/model/policy.js';
8
10
  import type { ViewDefinition } from '../../language/view/model/view.js';
@@ -21,6 +23,22 @@ export interface DomainSchemaIR {
21
23
  readonly views: Readonly<Record<string, ViewDefinition>>;
22
24
  readonly core: CoreContent;
23
25
  }
26
+ /**
27
+ * The JSON document of a Domain schema, as tools write and exchange it: `DomainSchemaIR` without
28
+ * its in-process proofs. The structure the Kernel enforces has this type, which the build checks
29
+ * (`validation/domain.structure.typecheck.ts`), and the published JSON Schema describes it.
30
+ */
31
+ export type DomainSchemaDocument = DocumentOf<DomainSchemaIR>;
32
+ /**
33
+ * The JSON document a model type describes. A `Revision` and `PropertyValues` are in-process
34
+ * proofs: the document carries them as plain text and plain values.
35
+ */
36
+ export type DocumentOf<Model> = [JsonValue] extends [Model] ? Model : DocumentOfEach<Model>;
37
+ type DocumentOfEach<Model> = Model extends Revision ? string : Model extends PropertyValues ? {
38
+ readonly [Key in keyof Model as Key extends string ? Key : never]: DocumentOf<Model[Key]>;
39
+ } : Model extends string | number | boolean | null | undefined ? Model : {
40
+ readonly [Key in keyof Model]: DocumentOf<Model[Key]>;
41
+ };
24
42
  declare const DOMAIN_SCHEMA_BRAND: unique symbol;
25
43
  /**
26
44
  * Accepted canonical schema carrying its exact resolved Domain projection.
@@ -172,6 +172,7 @@ function canonicalizeProperty(value) {
172
172
  ? {}
173
173
  : {
174
174
  auto: {
175
+ // The distinct events of a non-empty list are a non-empty list.
175
176
  on: [...new Set(value.auto.on)].sort(compareUnicode),
176
177
  set: value.auto.set,
177
178
  },
@@ -20,8 +20,9 @@ export function requireDomainClosure(schema) {
20
20
  const closure = knownDomainClosure(schema);
21
21
  if (closure !== undefined)
22
22
  return closure;
23
+ // What the value lacks is the exact closure of its dependencies, retained only by acceptance.
23
24
  throw new SchemaError([
24
- diagnostic('DV_ACCEPTANCE_CONTEXT', '', `DomainSchema ${schema.origin} has no retained acceptance context; accept it again against its exact lookup.`),
25
+ diagnostic('DV_ACCEPTANCE_CONTEXT', '/dependencies', `DomainSchema ${schema.origin} has no retained acceptance context; accept it again against its exact lookup.`),
25
26
  ]);
26
27
  }
27
28
  export function lookupForDomainClosure(schema) {
@@ -1,6 +1,7 @@
1
1
  import { dependencyLimits } from '../../addressing/domain-dependency.js';
2
2
  import { definitionRefIndex } from '../../addressing/ordering.js';
3
3
  import { DOMAIN_REVISION_PATTERN } from '../../addressing/patterns.js';
4
+ import { escapePointerToken } from '../../language/value/syntax/pointer.js';
4
5
  import { revisionOfDomainSchema } from '../canonical/revision.js';
5
6
  import { diagnostic } from '../diagnostic.js';
6
7
  import { indexSchemaDefinitions } from './definition.index.js';
@@ -14,7 +15,7 @@ export function buildDomainResolutionContext(candidate, lookup) {
14
15
  diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A Domain may declare at most ${dependencyLimits.direct} direct dependency aliases.`));
15
16
  }
16
17
  for (const [alias, dependency] of Object.entries(candidate.dependencies)) {
17
- const pointer = `/dependencies/${escapeToken(alias)}`;
18
+ const pointer = `/dependencies/${escapePointerToken(alias)}`;
18
19
  if (!DOMAIN_REVISION_PATTERN.test(dependency.revision))
19
20
  continue;
20
21
  if (dependency.origin === candidate.origin) {
@@ -94,7 +95,7 @@ function loadDependencyClosure(candidate, dependencies, lookup, diagnostics) {
94
95
  continue;
95
96
  }
96
97
  const [alias, dependency] = edge;
97
- const pointer = frames.length === 1 ? `/dependencies/${escapeToken(alias)}` : '/dependencies';
98
+ const pointer = frames.length === 1 ? `/dependencies/${escapePointerToken(alias)}` : '/dependencies';
98
99
  if (activeOrigins.has(dependency.origin)) {
99
100
  diagnostics.push(diagnostic('DV_DEPENDENCY_ACYCLIC', pointer, `Dependency cycle reaches ${dependency.origin}.`));
100
101
  continue;
@@ -146,13 +147,12 @@ function loadDependencyClosure(candidate, dependencies, lookup, diagnostics) {
146
147
  }
147
148
  }
148
149
  function validateDependencyUse(candidate, directOrigins, diagnostics) {
150
+ // Each reference that cannot resolve is reported where it is written; the dependency it lacks
151
+ // belongs in the dependency map.
149
152
  const used = collectUsedForeignOrigins(candidate);
150
153
  for (const origin of used) {
151
154
  if (!directOrigins.has(origin)) {
152
- diagnostics.push(diagnostic('DV_DEPENDENCY_DIRECT', '', `Foreign origin ${origin} is referenced without an exact direct dependency.`));
155
+ diagnostics.push(diagnostic('DV_DEPENDENCY_DIRECT', '/dependencies', `Foreign origin ${origin} is referenced without an exact direct dependency.`));
153
156
  }
154
157
  }
155
158
  }
156
- function escapeToken(value) {
157
- return value.replaceAll('~', '~0').replaceAll('/', '~1');
158
- }