@astrale-os/kernel-dsl 1.0.0-beta.7 → 1.0.0-beta.9

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 (44) hide show
  1. package/dist/v1/builder/class/authoring.d.ts +29 -4
  2. package/dist/v1/builder/class/authoring.js +15 -5
  3. package/dist/v1/builder/index.d.ts +1 -1
  4. package/dist/v1/builder/lowering/class.js +12 -2
  5. package/dist/v1/builder/lowering/property-keys.d.ts +27 -0
  6. package/dist/v1/builder/lowering/property-keys.js +108 -0
  7. package/dist/v1/builder/lowering/schema/compile.js +8 -3
  8. package/dist/v1/builder/lowering/schema/context.d.ts +2 -0
  9. package/dist/v1/builder/lowering/schema/context.js +12 -0
  10. package/dist/v1/builder/lowering/schema/lifetime.d.ts +11 -0
  11. package/dist/v1/builder/lowering/schema/lifetime.js +55 -0
  12. package/dist/v1/builder/schema/diagnostic.d.ts +1 -1
  13. package/dist/v1/compiled/admission/semantics/cardinality.d.ts +1 -1
  14. package/dist/v1/compiled/admission/semantics/cardinality.js +16 -1
  15. package/dist/v1/compiled/admission/semantics/class.js +23 -0
  16. package/dist/v1/compiled/admission/semantics/core.js +16 -0
  17. package/dist/v1/compiled/admission/wire/class.js +24 -4
  18. package/dist/v1/language/class/inheritance.d.ts +9 -0
  19. package/dist/v1/language/class/inheritance.js +10 -1
  20. package/dist/v1/language/class/lifetime.d.ts +50 -11
  21. package/dist/v1/language/class/lifetime.js +89 -20
  22. package/dist/v1/language/class/model/class.d.ts +16 -1
  23. package/dist/v1/language/class/model/edge.d.ts +22 -6
  24. package/dist/v1/language/class/unique.d.ts +123 -13
  25. package/dist/v1/language/class/unique.js +238 -27
  26. package/dist/v1/language/index.d.ts +5 -5
  27. package/dist/v1/language/index.js +3 -3
  28. package/dist/v1/schema/compatibility/api.d.ts +1 -0
  29. package/dist/v1/schema/compatibility/api.js +1 -0
  30. package/dist/v1/schema/compatibility/extension.d.ts +11 -0
  31. package/dist/v1/schema/compatibility/extension.js +22 -0
  32. package/dist/v1/schema/compatibility/model.d.ts +7 -0
  33. package/dist/v1/schema/compatibility/scope.d.ts +2 -0
  34. package/dist/v1/schema/compatibility/scope.js +4 -1
  35. package/dist/v1/schema/compatibility/structure/requirements.js +14 -11
  36. package/dist/v1/schema/compatibility/versioning/surface.js +13 -8
  37. package/dist/v1/schema/diagnostic.d.ts +1 -1
  38. package/dist/v1/schema/normalization/domain.js +15 -4
  39. package/dist/v1/schema/validation/class.js +70 -1
  40. package/dist/v1/schema/validation/core.js +50 -0
  41. package/dist/v1/schema/validation/domain.structure.js +18 -4
  42. package/package.json +1 -1
  43. package/dist/v1/builder/lowering/core-properties.d.ts +0 -10
  44. package/dist/v1/builder/lowering/core-properties.js +0 -76
@@ -1,46 +1,257 @@
1
+ import { json } from '@astrale-os/kernel-dsl/value';
2
+ import { ClassKey } from '../../addressing/class-key.js';
3
+ import { compareUnicode } from '../../addressing/ordering.js';
4
+ import { PropertyKey } from '../../addressing/property-key.js';
5
+ /** The kinds of key fields, in canonical order: the endpoints, the slug, then the Properties. */
6
+ const KINDS = Object.freeze([
7
+ 'source',
8
+ 'target',
9
+ 'slug',
10
+ 'property',
11
+ ]);
12
+ /** The canonical order of forms: kind by kind, a form before every form it prefixes. */
13
+ const compareForms = compareSequences(compareKinds);
1
14
  /**
2
- * Every uniqueness tuple a directed Edge Class may declare, in canonical order: an endpoint, then
3
- * the slug. The source tuple names entries under their source; the target tuple, under their target.
15
+ * Every form a key tuple may take, in canonical order: each nonempty set of field kinds. A Property
16
+ * kind stands for one Property or more, whatever their keys.
4
17
  */
5
- export const EDGE_UNIQUE_TUPLES = Object.freeze([
6
- Object.freeze(['source', 'slug']),
7
- Object.freeze(['target', 'slug']),
18
+ export const EDGE_UNIQUE_FORMS = Object.freeze(Array.from({ length: (1 << KINDS.length) - 1 }, (_, index) => Object.freeze(KINDS.filter((_, kind) => ((index + 1) & (1 << kind)) !== 0))).sort(compareForms));
19
+ /** The one form a Node key tuple takes: its fields are Properties, whatever their keys. */
20
+ export const NODE_UNIQUE_FORMS = Object.freeze([
21
+ Object.freeze(['property']),
8
22
  ]);
9
- /** The fields a tuple names, in canonical order. */
10
- const FIELDS = Object.freeze(['source', 'target', 'slug']);
11
- /** The tuple `value` spells, or undefined when it spells none. */
23
+ /** Every form of every kind, which a backend enforcing any key tuple announces. */
24
+ export const UNIQUE_FORMS = Object.freeze({
25
+ edge: EDGE_UNIQUE_FORMS,
26
+ node: NODE_UNIQUE_FORMS,
27
+ });
28
+ /** No form of either kind: a backend that announces none enforces no key tuple. */
29
+ export const NO_UNIQUE_FORMS = Object.freeze({
30
+ edge: Object.freeze([]),
31
+ node: Object.freeze([]),
32
+ });
33
+ /** The kind of `field`. */
34
+ function kindOf(field) {
35
+ return typeof field === 'string' ? field : 'property';
36
+ }
37
+ function compareKinds(left, right) {
38
+ return KINDS.indexOf(left) - KINDS.indexOf(right);
39
+ }
40
+ /** The canonical order of fields: source, target, slug, then Properties by key. */
41
+ export function compareEdgeUniqueFields(left, right) {
42
+ return (compareKinds(kindOf(left), kindOf(right)) ||
43
+ (typeof left === 'string' || typeof right === 'string'
44
+ ? 0
45
+ : compareUnicode(left.property, right.property)));
46
+ }
47
+ /** The canonical order of tuples: field by field, a tuple before every tuple it prefixes. */
48
+ export const compareEdgeUniqueTuples = compareSequences(compareEdgeUniqueFields);
49
+ /** The field `value` spells, detached, or undefined when it spells none. */
50
+ function edgeUniqueField(value) {
51
+ if (value === 'source' || value === 'target' || value === 'slug')
52
+ return value;
53
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
54
+ return undefined;
55
+ const keys = Object.keys(value);
56
+ const property = value.property;
57
+ return keys.length === 1 && keys[0] === 'property' && PropertyKey.is(property)
58
+ ? Object.freeze({ property })
59
+ : undefined;
60
+ }
61
+ /**
62
+ * The tuple `value` spells, detached, or undefined when it spells none: a nonempty list of fields,
63
+ * distinct and in canonical order, so one set of fields has one spelling.
64
+ */
12
65
  export function edgeUniqueTuple(value) {
66
+ if (!Array.isArray(value) || value.length === 0)
67
+ return undefined;
68
+ const fields = [];
69
+ for (const item of value) {
70
+ const field = edgeUniqueField(item);
71
+ if (field === undefined)
72
+ return undefined;
73
+ if (fields.length > 0 && compareEdgeUniqueFields(fields.at(-1), field) >= 0)
74
+ return undefined;
75
+ fields.push(field);
76
+ }
77
+ return Object.freeze(fields);
78
+ }
79
+ /** The canonical order of Node tuples: Property by Property, a tuple before every one it prefixes. */
80
+ export const compareNodeUniqueTuples = compareSequences(compareUnicode);
81
+ /**
82
+ * The Node tuple `value` spells, detached, or undefined when it spells none: a nonempty list of
83
+ * Property keys, distinct and in code-point order, so one set of Properties has one spelling.
84
+ */
85
+ export function nodeUniqueTuple(value) {
86
+ if (!Array.isArray(value) || value.length === 0)
87
+ return undefined;
88
+ const keys = [];
89
+ for (const key of value) {
90
+ if (!PropertyKey.is(key))
91
+ return undefined;
92
+ if (keys.length > 0 && compareUnicode(keys.at(-1), key) >= 0)
93
+ return undefined;
94
+ keys.push(key);
95
+ }
96
+ return Object.freeze(keys);
97
+ }
98
+ /** The form of `NODE_UNIQUE_FORMS` that `value` spells, or undefined when it spells none. */
99
+ export function nodeUniqueForm(value) {
100
+ return Array.isArray(value) && value.length === 1 && value[0] === 'property'
101
+ ? NODE_UNIQUE_FORMS[0]
102
+ : undefined;
103
+ }
104
+ /**
105
+ * Whether a backend announcing `forms` for Nodes enforces the Node tuple `value` spells: every
106
+ * Node tuple has the one Node form. False when `value` spells no tuple.
107
+ */
108
+ export function enforcesNodeUniqueTuple(forms, value) {
109
+ return forms.length > 0 && nodeUniqueTuple(value) !== undefined;
110
+ }
111
+ /** The form of `tuple`: the kinds of its fields, each once. */
112
+ export function edgeUniqueFormOf(tuple) {
113
+ const kinds = new Set(tuple.map(kindOf));
114
+ return EDGE_UNIQUE_FORMS.find((form) => form.length === kinds.size && form.every((kind) => kinds.has(kind)));
115
+ }
116
+ /** The form of `EDGE_UNIQUE_FORMS` that `value` spells, or undefined when it spells none. */
117
+ export function edgeUniqueForm(value) {
13
118
  if (!Array.isArray(value))
14
119
  return undefined;
15
- return EDGE_UNIQUE_TUPLES.find((tuple) => tuple.length === value.length && tuple.every((field, index) => field === value[index]));
120
+ return EDGE_UNIQUE_FORMS.find((form) => form.length === value.length && form.every((kind, index) => kind === value[index]));
16
121
  }
17
- /** Whether `tuples` lists the tuple `value` spells: false when it spells none. */
18
- export function includesEdgeUniqueTuple(tuples, value) {
122
+ /**
123
+ * Whether a backend announcing `forms` enforces the tuple `value` spells: its form is one of them.
124
+ * False when `value` spells no tuple, so a tuple is never taken as enforced by mistake.
125
+ */
126
+ export function enforcesEdgeUniqueTuple(forms, value) {
19
127
  const tuple = edgeUniqueTuple(value);
20
- return (tuple !== undefined &&
21
- tuples.some((candidate) => compareEdgeUniqueTuples(candidate, tuple) === 0));
22
- }
23
- /** The canonical order of tuples: field by field, source before target before slug. */
24
- export function compareEdgeUniqueTuples(left, right) {
25
- for (let index = 0; index < Math.min(left.length, right.length); index += 1) {
26
- const order = FIELDS.indexOf(left[index]) - FIELDS.indexOf(right[index]);
27
- if (order !== 0)
28
- return order;
29
- }
30
- return left.length - right.length;
128
+ if (tuple === undefined)
129
+ return false;
130
+ const form = edgeUniqueFormOf(tuple);
131
+ return forms.some((candidate) => compareForms(candidate, form) === 0);
132
+ }
133
+ /**
134
+ * Whether a Property may be a key field (D-S1): a single-kind scalar by its ordering facet, a
135
+ * string, number, boolean or instant, that no automatic program writes.
136
+ */
137
+ export function keyableProperty(ordering, auto) {
138
+ return ordering !== undefined && auto === undefined;
139
+ }
140
+ /**
141
+ * The values of the fields of `tuple` on `edge`, in order, or undefined when one of them is absent,
142
+ * so that an Edge without a slug or without a Property never collides.
143
+ */
144
+ export function edgeUniqueValues(tuple, edge) {
145
+ return keyValues(tuple, (field) => typeof field === 'string' ? edge[field] : propertyOf(edge.props, field.property));
31
146
  }
32
147
  /**
33
- * The value `edge` takes under `tuple`, or undefined when one of its fields is absent, so that an
34
- * Edge without a slug never collides: two distinct Edges of the Classes a tuple covers collide
35
- * exactly when they take one value.
148
+ * The value `edge` takes under `tuple`, the canonical JSON of the values of its fields, or
149
+ * undefined when one of them is absent: two distinct Edges of the Classes a tuple covers collide
150
+ * exactly when they take one value. Endpoints compare by NodeId, slugs by exact text and
151
+ * Properties by canonical Value.
36
152
  */
37
153
  export function edgeUniqueValue(tuple, edge) {
154
+ return serialized(edgeUniqueValues(tuple, edge));
155
+ }
156
+ /**
157
+ * The values of the Properties of `tuple` on `node`, in order, or undefined when one of them is
158
+ * absent, so that a Node without one of them never collides, like NULL in an SQL UNIQUE.
159
+ */
160
+ export function nodeUniqueValues(tuple, node) {
161
+ return keyValues(tuple, (key) => propertyOf(node.props, key));
162
+ }
163
+ /**
164
+ * The value `node` takes under `tuple`, the canonical JSON of the values of its Properties, or
165
+ * undefined when one of them is absent: two distinct Nodes of the Classes a tuple covers collide
166
+ * exactly when they take one value. Properties compare by canonical Value, nothing normalized.
167
+ */
168
+ export function nodeUniqueValue(tuple, node) {
169
+ return serialized(nodeUniqueValues(tuple, node));
170
+ }
171
+ /**
172
+ * The tuples covering `candidate`: those it and each of its ancestors declare, each with the Class
173
+ * declaring it, ancestors found by `resolve`. A descendant never restates them.
174
+ */
175
+ export function coveringEdgeUniqueTuples(candidate, resolve) {
176
+ return coveringTuples(candidate, resolve);
177
+ }
178
+ /**
179
+ * The Node tuples covering `candidate`: those it and each of its ancestors declare, each with the
180
+ * Class declaring it, ancestors found by `resolve`. A descendant never restates them.
181
+ */
182
+ export function coveringNodeUniqueTuples(candidate, resolve) {
183
+ return coveringTuples(candidate, resolve);
184
+ }
185
+ /**
186
+ * The keys `edge` holds: its value under each tuple of `tuples`, tagged by the Class declaring the
187
+ * tuple, so two Edges collide exactly when they hold one key.
188
+ */
189
+ export function edgeUniqueKeys(tuples, edge) {
190
+ return heldKeys(tuples, (fields) => edgeUniqueValue(fields, edge));
191
+ }
192
+ /**
193
+ * The keys `node` holds: its value under each tuple of `tuples`, tagged by the Class declaring the
194
+ * tuple, so two Nodes collide exactly when they hold one key.
195
+ */
196
+ export function nodeUniqueKeys(tuples, node) {
197
+ return heldKeys(tuples, (fields) => nodeUniqueValue(fields, node));
198
+ }
199
+ /** The values `read` gives the fields of `tuple`, in order, or undefined when one is absent. */
200
+ function keyValues(tuple, read) {
38
201
  const values = [];
39
202
  for (const field of tuple) {
40
- const value = edge[field];
203
+ const value = read(field);
41
204
  if (value === undefined)
42
205
  return undefined;
43
206
  values.push(value);
44
207
  }
45
- return JSON.stringify(values);
208
+ return Object.freeze(values);
209
+ }
210
+ /** The value of the Property `key` in `props`, or undefined when it is absent. */
211
+ function propertyOf(props, key) {
212
+ return Object.hasOwn(props, key) ? props[key] : undefined;
213
+ }
214
+ /** The canonical JSON of a key's values, or undefined when it has none. */
215
+ function serialized(values) {
216
+ return values === undefined ? undefined : json.serializeAdmitted(values);
217
+ }
218
+ /**
219
+ * The tuples `candidate` and its ancestors declare, each with its declarer. Only ancestors of the
220
+ * candidate's kind are visited, so every tuple is of that kind.
221
+ */
222
+ function coveringTuples(candidate, resolve) {
223
+ const tuples = [];
224
+ const seen = new Set();
225
+ const visit = (value) => {
226
+ if (seen.has(value) || value.kind !== candidate.kind)
227
+ return;
228
+ seen.add(value);
229
+ for (const fields of value.unique ?? [])
230
+ tuples.push(Object.freeze({ class: ClassKey.of(value.ref), fields }));
231
+ for (const ref of value.extends) {
232
+ const parent = resolve(ref);
233
+ if (parent !== undefined)
234
+ visit(parent);
235
+ }
236
+ };
237
+ visit(candidate);
238
+ return Object.freeze(tuples);
239
+ }
240
+ /** The keys a fact holds under `tuples`, its value under each tagged by the declaring Class. */
241
+ function heldKeys(tuples, valueOf) {
242
+ return tuples.flatMap(({ class: declaring, fields }) => {
243
+ const value = valueOf(fields);
244
+ return value === undefined ? [] : [JSON.stringify([declaring, fields, value])];
245
+ });
246
+ }
247
+ /** The lexicographic order of sequences by `compare`, a sequence before every one it prefixes. */
248
+ function compareSequences(compare) {
249
+ return (left, right) => {
250
+ for (let index = 0; index < Math.min(left.length, right.length); index += 1) {
251
+ const order = compare(left[index], right[index]);
252
+ if (order !== 0)
253
+ return order;
254
+ }
255
+ return left.length - right.length;
256
+ };
46
257
  }
@@ -14,11 +14,11 @@ export type * from './callable/resolved.js';
14
14
  export type * from './class/model/class.js';
15
15
  export type { DataDefinition, DataFacet, MediaType } from './class/model/data.js';
16
16
  export type * from './class/model/edge.js';
17
- export { inheritanceOf } from './class/inheritance.js';
18
- export { edgeLifetimes } from './class/lifetime.js';
19
- export type { EdgeLifetime } from './class/lifetime.js';
20
- export { EDGE_UNIQUE_TUPLES, compareEdgeUniqueTuples, edgeUniqueTuple, edgeUniqueValue, includesEdgeUniqueTuple, } from './class/unique.js';
21
- export type { EdgeUniqueFacts } from './class/unique.js';
17
+ export { admitsForeignSubclass, inheritanceOf } from './class/inheritance.js';
18
+ export { edgeLifetimes, lifetimeScope } from './class/lifetime.js';
19
+ export type { LifetimeScope } from './class/lifetime.js';
20
+ export { EDGE_UNIQUE_FORMS, NODE_UNIQUE_FORMS, NO_UNIQUE_FORMS, UNIQUE_FORMS, compareEdgeUniqueFields, compareEdgeUniqueTuples, compareNodeUniqueTuples, coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueForm, edgeUniqueFormOf, edgeUniqueKeys, edgeUniqueTuple, edgeUniqueValue, edgeUniqueValues, enforcesEdgeUniqueTuple, enforcesNodeUniqueTuple, keyableProperty, nodeUniqueForm, nodeUniqueKeys, nodeUniqueTuple, nodeUniqueValue, nodeUniqueValues, } from './class/unique.js';
21
+ export type { DeclaredEdgeUniqueTuple, DeclaredNodeUniqueTuple, EdgeUniqueFacts, NodeUniqueFacts, UniqueForms, } from './class/unique.js';
22
22
  export type * from './class/model/property.js';
23
23
  export type * from './class/resolved.js';
24
24
  export type * from './class/facets.js';
@@ -3,8 +3,8 @@ export { isMethodExecutable } from './callable/executable.js';
3
3
  export { isBinaryInput } from './callable/model/callable.js';
4
4
  export { valueProgramIncludes } from './value/inclusion/includes.js';
5
5
  export { stateMachine } from './state/index.js';
6
- export { inheritanceOf } from './class/inheritance.js';
7
- export { edgeLifetimes } from './class/lifetime.js';
8
- export { EDGE_UNIQUE_TUPLES, compareEdgeUniqueTuples, edgeUniqueTuple, edgeUniqueValue, includesEdgeUniqueTuple, } from './class/unique.js';
6
+ export { admitsForeignSubclass, inheritanceOf } from './class/inheritance.js';
7
+ export { edgeLifetimes, lifetimeScope } from './class/lifetime.js';
8
+ export { EDGE_UNIQUE_FORMS, NODE_UNIQUE_FORMS, NO_UNIQUE_FORMS, UNIQUE_FORMS, compareEdgeUniqueFields, compareEdgeUniqueTuples, compareNodeUniqueTuples, coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueForm, edgeUniqueFormOf, edgeUniqueKeys, edgeUniqueTuple, edgeUniqueValue, edgeUniqueValues, enforcesEdgeUniqueTuple, enforcesNodeUniqueTuple, keyableProperty, nodeUniqueForm, nodeUniqueKeys, nodeUniqueTuple, nodeUniqueValue, nodeUniqueValues, } from './class/unique.js';
9
9
  export { PropertyValues } from './class/property-values.js';
10
10
  export { isClassRuleChecks, policyVariableClass, policyVariableSelector, policyObjectKey, policyObjectOrigin, projectedMemberClass, } from './policy/model/policy.js';
@@ -1,3 +1,4 @@
1
1
  export { compare, compareMeaning, compareStructure } from './compare.js';
2
+ export { closedParents } from './extension.js';
2
3
  export { requiredBump } from './versioning/index.js';
3
4
  export type { Analysis, Assessment, Bump, Comparison, ComparisonEntry, ComparisonEvidence, ComparisonRequest, ComparisonScope, Facet, Fingerprint, MeaningComparison, Observation, RequiredBump, SchemaIdentity, StructureComparison, Subject, Usage, VersionChange, } from './model.js';
@@ -1,2 +1,3 @@
1
1
  export { compare, compareMeaning, compareStructure } from './compare.js';
2
+ export { closedParents } from './extension.js';
2
3
  export { requiredBump } from './versioning/index.js';
@@ -0,0 +1,11 @@
1
+ import type { Key } from '../../addressing/index.js';
2
+ import type { DomainSchema } from '../model/schema.js';
3
+ /**
4
+ * The Classes of the target's origin that a Class of the dependent extends and the target does not
5
+ * open to it, as Keys in order (DM-CLASS-INHERITANCE). A parent the target no longer declares is
6
+ * not closed: losing it is an ancestry change.
7
+ */
8
+ export declare function closedParents({ dependent, target, }: {
9
+ readonly dependent: DomainSchema;
10
+ readonly target: DomainSchema;
11
+ }): readonly Key[];
@@ -0,0 +1,22 @@
1
+ import { compareUnicode } from '../../addressing/ordering.js';
2
+ import { admitsForeignSubclass } from '../../language/class/inheritance.js';
3
+ import { keyForDefinitionRef } from './coordinate.js';
4
+ /**
5
+ * The Classes of the target's origin that a Class of the dependent extends and the target does not
6
+ * open to it, as Keys in order (DM-CLASS-INHERITANCE). A parent the target no longer declares is
7
+ * not closed: losing it is an ancestry change.
8
+ */
9
+ export function closedParents({ dependent, target, }) {
10
+ const closed = new Set();
11
+ if (dependent.origin === target.origin)
12
+ return [];
13
+ for (const declaration of Object.values(dependent.classes))
14
+ for (const parent of declaration.extends) {
15
+ if (parent.origin !== target.origin)
16
+ continue;
17
+ const declared = target.classes[parent.name];
18
+ if (declared !== undefined && !admitsForeignSubclass(declared))
19
+ closed.add(keyForDefinitionRef(parent));
20
+ }
21
+ return Object.freeze([...closed].sort(compareUnicode));
22
+ }
@@ -17,6 +17,13 @@ export interface ComparisonRequest {
17
17
  * unresolvable or unsupported Key of the target origin, is rejected with DM_CAPABILITY_INVALID.
18
18
  */
19
19
  readonly capabilities?: readonly Key[];
20
+ /**
21
+ * `true` when the dependent already holds its binding to the target, as the installer holds
22
+ * one a Domain was installed with: the Classes of the target origin it extends are then not
23
+ * judged for inheritance (DM-CLASS-INHERITANCE). Otherwise each must be `'open'` in the
24
+ * target. It enters no evidence: a held comparison has the facts of a compatible judged one.
25
+ */
26
+ readonly held?: boolean;
20
27
  } | {
21
28
  readonly kind: 'members';
22
29
  readonly source: DomainSchema;
@@ -25,6 +25,8 @@ export interface ComparisonContext {
25
25
  * structure records them only when they fail, so a compatible comparison keeps its bytes.
26
26
  */
27
27
  readonly reliance: readonly Reference[];
28
+ /** Whether the dependent holds its binding to the target, so its extensions are not judged. */
29
+ readonly held: boolean;
28
30
  pair(coordinate: Coordinate): SubjectPair;
29
31
  }
30
32
  export declare function prepare(request: ComparisonRequest): ComparisonContext;
@@ -42,6 +42,7 @@ export function prepare(request) {
42
42
  roots: [...rootReferences(dependent), ...capabilities],
43
43
  capabilities,
44
44
  reliance: policyReliance(dependent, source.origin),
45
+ held: scope.held === true,
45
46
  });
46
47
  }
47
48
  /**
@@ -63,9 +64,10 @@ export function selectMembers(source, target, keys) {
63
64
  })),
64
65
  capabilities: [],
65
66
  reliance: [],
67
+ held: false,
66
68
  });
67
69
  }
68
- function comparisonContext({ kind, source, target, dependent, before, roots, capabilities, reliance, }) {
70
+ function comparisonContext({ kind, source, target, dependent, before, roots, capabilities, reliance, held, }) {
69
71
  resolve(target);
70
72
  const after = contextOf(target);
71
73
  const pairs = new Map();
@@ -78,6 +80,7 @@ function comparisonContext({ kind, source, target, dependent, before, roots, cap
78
80
  roots,
79
81
  capabilities,
80
82
  reliance,
83
+ held,
81
84
  scope: Object.freeze({
82
85
  kind,
83
86
  source: schemaIdentity(source),
@@ -5,6 +5,7 @@ import { compareFacet } from '../comparators/index.js';
5
5
  import { fingerprintMeaning } from '../comparators/values.js';
6
6
  import { contextInvalid } from '../context.js';
7
7
  import { methodCoordinate } from '../coordinate.js';
8
+ import { closedParents } from '../extension.js';
8
9
  import { callableReferences, classReferences, inherits, memberReferences, usageOf, valueReferences, } from '../references.js';
9
10
  /** Structure follows data and abstract obligations; concrete IO never expands its requirements. */
10
11
  export function compareRequirements({ context, report, }) {
@@ -98,27 +99,29 @@ function compareReliance({ context, report, }) {
98
99
  }
99
100
  }
100
101
  /**
101
- * A Class one of the dependent's own Classes extends must still admit it: the dependent is refused
102
- * once the compared origin declares that Class `'final'`. The fact is recorded only when it fails,
103
- * like a Policy reliance, so a compatible comparison keeps its evidence bytes.
102
+ * A Class of the compared origin one of the dependent's own Classes extends must be `'open'` in the
103
+ * target, unless the dependent holds its binding to that target. The fact is recorded only when it
104
+ * fails, like a Policy reliance, so a compatible comparison keeps its evidence bytes: `'open'`, what
105
+ * a subclass of another Domain requires, before, and the target's inheritance after.
104
106
  */
105
107
  function compareExtension({ context, report, }) {
108
+ if (context.held || context.dependent === undefined)
109
+ return;
110
+ const closed = new Set(closedParents({ dependent: context.dependent, target: context.target }));
111
+ if (closed.size === 0)
112
+ return;
106
113
  for (const reference of context.roots) {
107
- if (reference.usage.relation !== 'extends')
108
- continue;
109
- if (Key.origin(reference.coordinate.key) !== context.source.origin)
114
+ if (reference.usage.relation !== 'extends' || !closed.has(reference.coordinate.key))
110
115
  continue;
111
116
  const pair = context.pair(reference.coordinate);
112
- if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
113
- continue;
114
- if (pair.target.analysis.declaration.inheritance !== 'final')
117
+ if (pair.target?.kind !== 'class')
115
118
  continue;
116
119
  report.note(reference);
117
120
  report.record({
118
121
  pair,
119
122
  facet: 'class.inheritance',
120
- before: inheritanceMeaning(inheritanceOf(pair.source.analysis.declaration)),
121
- after: inheritanceMeaning('final'),
123
+ before: inheritanceMeaning('open'),
124
+ after: inheritanceMeaning(inheritanceOf(pair.target.analysis.declaration)),
122
125
  requirement: { kind: 'class.inheritance' },
123
126
  });
124
127
  }
@@ -1,4 +1,4 @@
1
- import { inheritanceOf } from '../../../language/class/inheritance.js';
1
+ import { admitsForeignSubclass, inheritanceOf } from '../../../language/class/inheritance.js';
2
2
  import { methodInheritance } from '../comparators/callables.js';
3
3
  import { compareAddedProperties, inheritanceMeaning } from '../comparators/classes.js';
4
4
  import { compareFacet } from '../comparators/index.js';
@@ -57,16 +57,18 @@ export function compareSurface({ context, report, }) {
57
57
  }
58
58
  }
59
59
  /**
60
- * A changed inheritance is observed on its Class, and is major when the Class was `'open'` or
61
- * becomes `'final'`. Closing an `'open'` Class withdraws what its declaration granted other Domains;
62
- * acceptance refuses a subclass of a `'final'` Class of any Domain and admits one of another
63
- * Domain's `'internal'` Class (DV-CLASS-INHERITANCE), so finalizing that Class can refuse a dependent.
60
+ * A changed inheritance is observed on its Class, and is major when an `'open'` Class stops being
61
+ * open: only an `'open'` Class admits a subclass of another Domain (DM-CLASS-INHERITANCE). Closing
62
+ * or finalizing a Class no other Domain may extend takes nothing from a dependent; opening one
63
+ * grants an extension, which the floor, counting added members only, leaves at a patch.
64
64
  */
65
65
  function compareExtension({ pair, report, }) {
66
66
  if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
67
67
  return;
68
- const before = inheritanceOf(pair.source.analysis.declaration);
69
- const after = inheritanceOf(pair.target.analysis.declaration);
68
+ const source = pair.source.analysis.declaration;
69
+ const target = pair.target.analysis.declaration;
70
+ const before = inheritanceOf(source);
71
+ const after = inheritanceOf(target);
70
72
  if (before === after)
71
73
  return;
72
74
  report.record({
@@ -74,7 +76,10 @@ function compareExtension({ pair, report, }) {
74
76
  facet: 'class.inheritance',
75
77
  before: inheritanceMeaning(before),
76
78
  after: inheritanceMeaning(after),
77
- requirement: { kind: 'class.inheritance', satisfied: before !== 'open' && after !== 'final' },
79
+ requirement: {
80
+ kind: 'class.inheritance',
81
+ satisfied: !admitsForeignSubclass(source) || admitsForeignSubclass(target),
82
+ },
78
83
  });
79
84
  }
80
85
  function changed({ before, after }) {
@@ -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_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_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_FOREIGN_MINIMUM' | 'DV_EDGE_ROLE' | 'DV_EDGE_UNDIRECTED' | 'DV_ENTRYPOINT' | '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' | '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_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
2
  export interface SchemaDiagnostic<out Code extends SchemaDiagnosticCode = SchemaDiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -1,7 +1,7 @@
1
1
  import { json } from '@astrale-os/kernel-dsl/value';
2
2
  import { compareDefinitionRefs, compareUnicode } from '../../addressing/ordering.js';
3
3
  import { isBinaryInput } from '../../language/callable/model/callable.js';
4
- import { compareEdgeUniqueTuples } from '../../language/class/unique.js';
4
+ import { compareEdgeUniqueFields, compareEdgeUniqueTuples, compareNodeUniqueTuples, } from '../../language/class/unique.js';
5
5
  import { normalizeClassRule, normalizePolicyChecks, normalizePolicyExpression, } from '../../language/policy/normalization/normalize.js';
6
6
  import { admitStateTopology } from '../../language/state/topology.js';
7
7
  import { canonicalizeValueSchema } from '../../language/value/profile/canonicalize.js';
@@ -73,6 +73,7 @@ function canonicalizeClass(value) {
73
73
  ...(value.data === undefined ? {} : { data: { mediaType: value.data.mediaType } }),
74
74
  ...(value.icon === undefined ? {} : { icon: value.icon }),
75
75
  policies: canonicalizeNodePolicies(value.policies),
76
+ ...(value.unique === undefined ? {} : { unique: canonicalizeNodeUnique(value.unique) }),
76
77
  };
77
78
  }
78
79
  return value.orientation === 'directed'
@@ -82,9 +83,7 @@ function canonicalizeClass(value) {
82
83
  endpoints: canonicalizeDirectedEndpoints(value.endpoints),
83
84
  policies: value.policies,
84
85
  constraints: value.constraints,
85
- ...(value.unique === undefined
86
- ? {}
87
- : { unique: [...value.unique].sort(compareEdgeUniqueTuples) }),
86
+ ...(value.unique === undefined ? {} : { unique: canonicalizeUnique(value.unique) }),
88
87
  }
89
88
  : {
90
89
  ...base,
@@ -94,6 +93,18 @@ function canonicalizeClass(value) {
94
93
  constraints: value.constraints,
95
94
  };
96
95
  }
96
+ /** A set of Node key tuples, each a set of Property keys, both in their canonical order. */
97
+ function canonicalizeNodeUnique(tuples) {
98
+ return tuples
99
+ .map((tuple) => [...tuple].sort(compareUnicode))
100
+ .sort(compareNodeUniqueTuples);
101
+ }
102
+ /** A set of key tuples, each a set of fields: fields and tuples take their canonical order. */
103
+ function canonicalizeUnique(tuples) {
104
+ return tuples
105
+ .map((tuple) => [...tuple].sort(compareEdgeUniqueFields))
106
+ .sort(compareEdgeUniqueTuples);
107
+ }
97
108
  /** A rule keeps one spelling: normalized checks, or the Policy a lone `self` check names. */
98
109
  function canonicalizeNodePolicies(value) {
99
110
  return {