@astrale-os/kernel-dsl 0.2.0-beta.44 → 0.2.0-beta.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/v1/addressing/class-key.js +5 -4
- package/dist/v1/addressing/method-key.js +3 -2
- package/dist/v1/builder/lowering/value/zod.js +11 -2
- package/dist/v1/builder/value/full-match.js +6 -3
- package/dist/v1/compiled/admission/accept.js +27 -6
- package/dist/v1/compiled/admission/semantics.js +41 -16
- package/dist/v1/compiled/loader/load.js +1 -0
- package/dist/v1/domain/model/input.d.ts +3 -0
- package/dist/v1/domain/projection/classes.js +1 -0
- package/dist/v1/domain/validation/create.d.ts +2 -0
- package/dist/v1/domain/validation/create.js +12 -0
- package/dist/v1/language/class/resolved.d.ts +3 -0
- package/dist/v1/language/index.d.ts +2 -0
- package/dist/v1/language/index.js +1 -0
- package/dist/v1/language/policy/normalization/normalize.d.ts +4 -0
- package/dist/v1/language/policy/normalization/normalize.js +51 -13
- package/dist/v1/language/value/inclusion/includes.d.ts +9 -0
- package/dist/v1/language/value/inclusion/includes.js +777 -0
- package/dist/v1/language/value/profile/rules.js +11 -32
- package/dist/v1/language/value/regexp/ast.js +3 -64
- package/dist/v1/language/value/regexp/compile.d.ts +25 -2
- package/dist/v1/language/value/regexp/compile.js +48 -12
- package/dist/v1/language/value/regexp/syntax.d.ts +11 -1
- package/dist/v1/language/value/regexp/syntax.js +14 -2
- package/dist/v1/language/value/regexp/unicode.d.ts +32 -0
- package/dist/v1/language/value/regexp/unicode.js +139 -0
- package/dist/v1/language/value/syntax/full-match.js +6 -3
- package/dist/v1/schema/compatibility/comparators/callables.d.ts +7 -0
- package/dist/v1/schema/compatibility/comparators/callables.js +13 -0
- package/dist/v1/schema/compatibility/comparators/classes.d.ts +18 -0
- package/dist/v1/schema/compatibility/comparators/classes.js +54 -0
- package/dist/v1/schema/compatibility/comparators/index.d.ts +1 -1
- package/dist/v1/schema/compatibility/comparators/index.js +14 -2
- package/dist/v1/schema/compatibility/evidence.d.ts +8 -1
- package/dist/v1/schema/compatibility/evidence.js +10 -1
- package/dist/v1/schema/compatibility/meaning/footprint.js +3 -1
- package/dist/v1/schema/compatibility/meaning/index.js +4 -2
- package/dist/v1/schema/compatibility/model.d.ts +11 -6
- package/dist/v1/schema/compatibility/references.d.ts +8 -1
- package/dist/v1/schema/compatibility/references.js +39 -6
- package/dist/v1/schema/compatibility/scope.d.ts +5 -0
- package/dist/v1/schema/compatibility/scope.js +5 -2
- package/dist/v1/schema/compatibility/structure/index.js +21 -2
- package/dist/v1/schema/compatibility/structure/requirements.js +26 -1
- package/dist/v1/schema/compatibility/subjects.js +20 -2
- package/dist/v1/schema/resolution/domain.js +1 -0
- package/dist/v1/schema/resolution/origins.d.ts +11 -4
- package/dist/v1/schema/resolution/origins.js +7 -8
- package/dist/v1/schema/validation/class.d.ts +10 -0
- package/dist/v1/schema/validation/class.js +4 -2
- package/dist/v1/schema/validation/policy/budgets.js +16 -3
- package/dist/v1/schema/validation/policy/typing.js +15 -0
- package/package.json +1 -1
|
@@ -49,6 +49,30 @@ export function classMeaning(context, coordinate, level, onto) {
|
|
|
49
49
|
};
|
|
50
50
|
return { meaning: deepFreeze(meaning), requirement };
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* What a Policy pattern reads of an Edge it matches: its orientation and the Classes each endpoint
|
|
54
|
+
* accepts, without cardinalities or roles. Undirected endpoints form an unordered pair.
|
|
55
|
+
*/
|
|
56
|
+
export function edgeEndpoints(context, coordinate) {
|
|
57
|
+
const declaration = context.schemas.get(coordinate.ref.origin)?.classes[coordinate.ref.name];
|
|
58
|
+
if (declaration?.kind !== 'edge')
|
|
59
|
+
return undefined;
|
|
60
|
+
const accepts = ({ accepts: refs }) => refs.map(keyForDefinitionRef).sort(compareUnicode);
|
|
61
|
+
return deepFreeze(declaration.orientation === 'directed'
|
|
62
|
+
? {
|
|
63
|
+
coordinate: 'class',
|
|
64
|
+
orientation: 'directed',
|
|
65
|
+
source: accepts(declaration.endpoints.source),
|
|
66
|
+
target: accepts(declaration.endpoints.target),
|
|
67
|
+
}
|
|
68
|
+
: {
|
|
69
|
+
coordinate: 'class',
|
|
70
|
+
orientation: 'undirected',
|
|
71
|
+
endpoints: declaration.endpoints
|
|
72
|
+
.map(accepts)
|
|
73
|
+
.sort((left, right) => compareUnicode(left.join('\n'), right.join('\n'))),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
52
76
|
export function propertyMeaning(field) {
|
|
53
77
|
return deepFreeze({
|
|
54
78
|
coordinate: 'property',
|
|
@@ -75,6 +99,36 @@ function ancestorKeys(context, ref) {
|
|
|
75
99
|
visit(ref);
|
|
76
100
|
return [...seen].sort(compareUnicode);
|
|
77
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* A Class a dependent references or inherits keeps every effective Property it had, whichever
|
|
104
|
+
* ancestor declares it. Each Property is compared on its owner, which still declares it when the
|
|
105
|
+
* Class stops extending that owner, and is not compared at all when another origin owns it: only the
|
|
106
|
+
* Class's own identity set shows those losses. Every loss is recorded, a Property its owner removed
|
|
107
|
+
* included, and only a loss is: a comparison that keeps every identity keeps its facts and evidence
|
|
108
|
+
* bytes, and an added ancestor or Property obliges nothing. The target is projected onto the source
|
|
109
|
+
* identities, as inherited ancestry is, so the differences name the lost Properties.
|
|
110
|
+
*/
|
|
111
|
+
export function compareLostProperties({ pair, report, }) {
|
|
112
|
+
if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
|
|
113
|
+
return;
|
|
114
|
+
const kept = pair.target.analysis.properties;
|
|
115
|
+
const identities = [...pair.source.analysis.properties.keys()].sort(compareUnicode);
|
|
116
|
+
if (identities.every((identity) => kept.has(identity)))
|
|
117
|
+
return;
|
|
118
|
+
report.record({
|
|
119
|
+
pair,
|
|
120
|
+
facet: 'class.properties',
|
|
121
|
+
before: propertyIdentities(identities),
|
|
122
|
+
after: propertyIdentities(identities.filter((identity) => kept.has(identity))),
|
|
123
|
+
requirement: { kind: 'class.properties' },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function propertyIdentities(identities) {
|
|
127
|
+
return deepFreeze({
|
|
128
|
+
coordinate: 'class',
|
|
129
|
+
properties: Object.fromEntries(identities.map((identity) => [identity, true])),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
78
132
|
/** Newly required data is an obligation even though it was absent from the source footprint. */
|
|
79
133
|
export function compareAddedProperties({ context, pair, report, }) {
|
|
80
134
|
if (pair.source?.kind !== 'class' || pair.target?.kind !== 'class')
|
|
@@ -5,7 +5,7 @@ import type { ComparisonContext, SubjectPair } from '../scope.js';
|
|
|
5
5
|
export declare function compareFacet({ context, pair, facet, }: {
|
|
6
6
|
readonly context: ComparisonContext;
|
|
7
7
|
readonly pair: SubjectPair;
|
|
8
|
-
readonly facet: Exclude<Facet, 'class.composition' | 'property.required'>;
|
|
8
|
+
readonly facet: Exclude<Facet, 'class.composition' | 'class.properties' | 'property.required'>;
|
|
9
9
|
}): {
|
|
10
10
|
readonly before?: Value;
|
|
11
11
|
readonly after?: Value;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { methodInheritance, functionSignature, functionExistence, methodSignature, methodExistence, } from './callables.js';
|
|
2
|
-
import { classMeaning, propertyMeaning } from './classes.js';
|
|
1
|
+
import { methodInheritance, functionSignature, functionExistence, methodObject, methodSignature, methodExistence, } from './callables.js';
|
|
2
|
+
import { classMeaning, edgeEndpoints, propertyMeaning } from './classes.js';
|
|
3
3
|
import { memberMeaning } from './members.js';
|
|
4
4
|
/** Shared facet comparisons know declarations, never installation or an analysis mode. */
|
|
5
5
|
export function compareFacet({ context, pair, facet, }) {
|
|
@@ -11,6 +11,14 @@ export function compareFacet({ context, pair, facet, }) {
|
|
|
11
11
|
const after = classMeaning(context.after, pair.coordinate, level, before?.requirement);
|
|
12
12
|
return { before: before?.meaning, after: after?.meaning };
|
|
13
13
|
}
|
|
14
|
+
if (facet === 'class.endpoints') {
|
|
15
|
+
if (pair.coordinate.kind !== 'class')
|
|
16
|
+
throw new TypeError('Class facet requires a Class coordinate.');
|
|
17
|
+
return {
|
|
18
|
+
before: edgeEndpoints(context.before, pair.coordinate),
|
|
19
|
+
after: edgeEndpoints(context.after, pair.coordinate),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
14
22
|
const value = (subject) => {
|
|
15
23
|
if (subject === undefined)
|
|
16
24
|
return undefined;
|
|
@@ -19,6 +27,10 @@ export function compareFacet({ context, pair, facet, }) {
|
|
|
19
27
|
return subject.kind === 'property' ? propertyMeaning(subject.field) : undefined;
|
|
20
28
|
case 'method.existence':
|
|
21
29
|
return subject.kind === 'method' ? methodExistence(subject.method) : undefined;
|
|
30
|
+
case 'method.object':
|
|
31
|
+
return subject.kind === 'method'
|
|
32
|
+
? methodObject(subject.method, subject.schema.classes[subject.method.owner.name])
|
|
33
|
+
: undefined;
|
|
22
34
|
case 'method.signature':
|
|
23
35
|
return subject.kind === 'method' ? methodSignature(subject.method) : undefined;
|
|
24
36
|
case 'method.inheritance':
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { Analysis, ComparisonEvidence } from './model.js';
|
|
2
2
|
/**
|
|
3
3
|
* Only semantic facts and decisions are durable; provenance and diagnostic prose are not. Each
|
|
4
|
-
* analysis names the
|
|
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
|
|
5
|
+
* analysis names the oldest requirement policy that yields its facts: a version applies exactly when
|
|
6
|
+
* a fact it introduced is recorded, so a comparison without one keeps the earlier version's bytes.
|
|
6
7
|
*/
|
|
7
8
|
export function evidenceOf({ kind, version, scope, entries, assessments, }) {
|
|
8
9
|
return Object.freeze({
|
|
@@ -38,6 +39,14 @@ export function evidenceOf({ kind, version, scope, entries, assessments, }) {
|
|
|
38
39
|
}),
|
|
39
40
|
});
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a referenced or inherited Class lost an effective Property. The fact is recorded only on a
|
|
44
|
+
* loss, so the evidence version that introduced it applies exactly when it is present
|
|
45
|
+
* (DM-CLASS-PROPERTIES).
|
|
46
|
+
*/
|
|
47
|
+
export function losesProperties({ entries }) {
|
|
48
|
+
return entries.some(({ observations }) => observations.some(({ facet }) => facet === 'class.properties'));
|
|
49
|
+
}
|
|
41
50
|
/** Version-1 evidence keeps its original tags independently of public facet names. */
|
|
42
51
|
function evidenceFacet(facet) {
|
|
43
52
|
switch (facet) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Key } from '../../../addressing/index.js';
|
|
2
|
-
import { compareAddedProperties } from '../comparators/classes.js';
|
|
2
|
+
import { compareAddedProperties, compareLostProperties } from '../comparators/classes.js';
|
|
3
3
|
import { compareFacet } from '../comparators/index.js';
|
|
4
4
|
import { contextInvalid } from '../context.js';
|
|
5
5
|
import { callableReferences, classReferences, inherits, memberReferences, valueReferences, } from '../references.js';
|
|
@@ -53,5 +53,7 @@ export function compareFootprint({ context, report, }) {
|
|
|
53
53
|
});
|
|
54
54
|
if (facet === 'class.inherit')
|
|
55
55
|
compareAddedProperties({ context, pair, report });
|
|
56
|
+
if (subject.kind === 'class')
|
|
57
|
+
compareLostProperties({ pair, report });
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { evidenceOf } from '../evidence.js';
|
|
1
|
+
import { evidenceOf, losesProperties } from '../evidence.js';
|
|
2
2
|
import { createReport } from '../report.js';
|
|
3
3
|
import { compareFootprint } from './footprint.js';
|
|
4
4
|
export function analyzeMeaning(context) {
|
|
@@ -9,6 +9,8 @@ export function analyzeMeaning(context) {
|
|
|
9
9
|
kind: 'meaning',
|
|
10
10
|
...result,
|
|
11
11
|
equivalent: result.assessments.every(({ satisfied }) => satisfied),
|
|
12
|
-
|
|
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
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
:
|
|
19
|
-
|
|
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
|
-
//
|
|
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:
|
|
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 ${
|
|
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 ${
|
|
123
|
+
diagnostic('DM_MEMBER_ROOT_INVALID', `/roots/${index}`, `Key ${shown(key)} ${reason}.`, typeof key === 'string' ? key : undefined),
|
|
124
124
|
]);
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A caller-supplied item as diagnostic text. Formatting never throws, so the caller always gets the
|
|
128
|
+
* diagnostic: a bigint is shown as its literal, and another item JSON cannot encode, such as a cyclic
|
|
129
|
+
* object, is named by its type.
|
|
130
|
+
*/
|
|
131
|
+
function shown(item) {
|
|
132
|
+
if (typeof item === 'bigint')
|
|
133
|
+
return `${item}n`;
|
|
134
|
+
try {
|
|
135
|
+
const text = JSON.stringify(item);
|
|
136
|
+
if (text !== undefined)
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Not encodable as JSON: named by its type below.
|
|
141
|
+
}
|
|
142
|
+
return `<${typeof item}>`;
|
|
143
|
+
}
|
|
@@ -66,6 +66,7 @@ function sourceValidation(closure, registries) {
|
|
|
66
66
|
properties.set(PropertyKey.of(definition.ref, name), Object.freeze({
|
|
67
67
|
ordering: classifyPreparedOrdering(prepared.value),
|
|
68
68
|
validate: (input) => validatePreparedInstance(prepared.value, input),
|
|
69
|
+
program: () => prepared.value.program(),
|
|
69
70
|
}));
|
|
70
71
|
}
|
|
71
72
|
if (definition.kind !== 'node')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DefinitionRef } from '../../addressing/index.js';
|
|
1
|
+
import type { DefinitionRef, MethodAddress } from '../../addressing/index.js';
|
|
2
2
|
import type { FunctionDefinition, MethodDefinition } from '../../language/callable/model/callable.js';
|
|
3
3
|
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
4
4
|
import type { CoreEndpointRef } from '../../language/core/model/core.js';
|
|
@@ -7,10 +7,17 @@ import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
|
7
7
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
8
8
|
/**
|
|
9
9
|
* Where one schema references a Definition. `extends` and `core` mean the schema inherits from or
|
|
10
|
-
* instantiates the Definition; every other site only refers to it.
|
|
10
|
+
* instantiates the Definition; every other site only refers to it. `pattern` is the Edge Class a
|
|
11
|
+
* Policy pattern matches, whose orientation and accepted endpoint Classes Policy typing reads.
|
|
11
12
|
*/
|
|
12
|
-
export type ReferenceSite = 'extends' | 'endpoint' | 'value' | 'policy' | 'view' | 'core';
|
|
13
|
-
|
|
13
|
+
export type ReferenceSite = 'extends' | 'endpoint' | 'value' | 'policy' | 'pattern' | 'view' | 'core';
|
|
14
|
+
/**
|
|
15
|
+
* What a site names: a Definition, or the Class-owned Method a Policy object term names. A Policy
|
|
16
|
+
* keeps the Method itself, since it resolves that Method's own executable declaration; a collector
|
|
17
|
+
* that needs Definitions only takes its owner Class.
|
|
18
|
+
*/
|
|
19
|
+
export type ReferencedDefinition = DefinitionRef | MethodAddress;
|
|
20
|
+
export type ReferenceCollector = (ref: ReferencedDefinition, site: ReferenceSite) => void;
|
|
14
21
|
/** Collect only semantic refs, never strings or objects embedded as JSON data. */
|
|
15
22
|
export declare function collectUsedForeignOrigins(schema: DomainSchemaIR): ReadonlySet<string>;
|
|
16
23
|
/** Complete structured references in one schema, in canonical order. */
|
|
@@ -13,7 +13,8 @@ export function collectUsedForeignOrigins(schema) {
|
|
|
13
13
|
export function collectDomainDefinitionRefs(schema) {
|
|
14
14
|
const result = new Map();
|
|
15
15
|
const add = (ref) => {
|
|
16
|
-
|
|
16
|
+
const definition = ref.kind === 'method' ? ref.owner : ref;
|
|
17
|
+
result.set(definitionRefIndex(definition), definition);
|
|
17
18
|
};
|
|
18
19
|
for (const declaration of Object.values(schema.classes)) {
|
|
19
20
|
collectClassRefs(declaration, add);
|
|
@@ -102,14 +103,14 @@ function collectPolicyPatternRefs(expression, add) {
|
|
|
102
103
|
if ('sameNode' in expression) {
|
|
103
104
|
for (const term of [expression.sameNode.left, expression.sameNode.right])
|
|
104
105
|
if (term.kind === 'ref')
|
|
105
|
-
add(term.ref
|
|
106
|
+
add(term.ref, 'policy');
|
|
106
107
|
return;
|
|
107
108
|
}
|
|
108
109
|
if ('source' in expression) {
|
|
109
110
|
for (const term of [expression.source, expression.target])
|
|
110
111
|
if (term.kind === 'ref')
|
|
111
|
-
add(term.ref
|
|
112
|
-
add(expression.class, '
|
|
112
|
+
add(term.ref, 'policy');
|
|
113
|
+
add(expression.class, 'pattern');
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
115
116
|
if ('exists' in expression) {
|
|
@@ -128,15 +129,13 @@ function collectPolicyCheckRefs(expression, add) {
|
|
|
128
129
|
if ('sameNode' in expression) {
|
|
129
130
|
for (const term of [expression.sameNode.left, expression.sameNode.right])
|
|
130
131
|
if (term.kind === 'ref')
|
|
131
|
-
add(term.ref
|
|
132
|
+
add(term.ref, 'policy');
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
135
|
if ('check' in expression) {
|
|
135
136
|
add(expression.check, 'policy');
|
|
136
137
|
if (expression.object.kind === 'ref')
|
|
137
|
-
add(expression.object.ref
|
|
138
|
-
? expression.object.ref.owner
|
|
139
|
-
: expression.object.ref, 'policy');
|
|
138
|
+
add(expression.object.ref, 'policy');
|
|
140
139
|
return;
|
|
141
140
|
}
|
|
142
141
|
const children = 'allOf' in expression ? expression.allOf : expression.anyOf;
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import type { ClassRef } from '../../addressing/index.js';
|
|
2
|
+
import type { ClassDefinition } from '../../language/class/model/class.js';
|
|
1
3
|
import type { DefinitionAnalysisResult } from '../analysis/class.js';
|
|
2
4
|
import type { SchemaDiagnostic } from '../diagnostic.js';
|
|
3
5
|
import type { DomainResolutionContext } from '../resolution/context.js';
|
|
4
6
|
/** Validate the semantic contracts owned by Class declarations. */
|
|
5
7
|
export declare function validateDefinitions(environment: DomainResolutionContext, analysis: DefinitionAnalysisResult): readonly SchemaDiagnostic[];
|
|
8
|
+
/** Whether `child` safely refines its parent Edge contract `parent`. */
|
|
9
|
+
export declare function edgeContractRefines(environment: DomainResolutionContext, child: Extract<ClassDefinition, {
|
|
10
|
+
readonly kind: 'edge';
|
|
11
|
+
}>, parent: Extract<ClassDefinition, {
|
|
12
|
+
readonly kind: 'edge';
|
|
13
|
+
}>): boolean;
|
|
14
|
+
/** Whether `candidate` is, or is a Node Class extending, one of `accepts`. */
|
|
15
|
+
export declare function classSatisfies(environment: DomainResolutionContext, candidate: ClassRef, accepts: readonly ClassRef[]): boolean;
|
|
@@ -149,7 +149,8 @@ function validateEdgeClassComposition(environment, diagnostics) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
|
|
152
|
+
/** Whether `child` safely refines its parent Edge contract `parent`. */
|
|
153
|
+
export function edgeContractRefines(environment, child, parent) {
|
|
153
154
|
if (child.orientation !== parent.orientation)
|
|
154
155
|
return false;
|
|
155
156
|
if (parent.constraints.noSelf === true && child.constraints.noSelf !== true)
|
|
@@ -207,7 +208,8 @@ function undirectedEndpointsOverlap(environment, endpoints) {
|
|
|
207
208
|
}
|
|
208
209
|
return false;
|
|
209
210
|
}
|
|
210
|
-
|
|
211
|
+
/** Whether `candidate` is, or is a Node Class extending, one of `accepts`. */
|
|
212
|
+
export function classSatisfies(environment, candidate, accepts) {
|
|
211
213
|
if (accepts.some((ref) => definitionRefIndex(ref) === definitionRefIndex(candidate)))
|
|
212
214
|
return true;
|
|
213
215
|
const resolved = environment.resolveAny(candidate);
|
|
@@ -3,8 +3,14 @@ import { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_D
|
|
|
3
3
|
import { escapePointer } from './context.js';
|
|
4
4
|
export { BRANCH_OVERFLOW, MAX_BRANCHES, MAX_CHECK_DEPTH, MAX_CHECK_LEAVES, MAX_DOMAINS, MAX_EDGES, MAX_PATTERN_DEPTH, MAX_REPEAT, MAX_VARIABLES, measureChecks, };
|
|
5
5
|
export function validatePolicyBudgets(context) {
|
|
6
|
+
// Measure every distinct composition node once. A node's expanded measure is independent of the
|
|
7
|
+
// path that reaches it (the only path-sensitive outcome, the back-edge `undefined`, already holds
|
|
8
|
+
// for every node on or reaching a composition cycle, regardless of entry), so caching the
|
|
9
|
+
// completed result by node identity turns path enumeration over the composition DAG into work
|
|
10
|
+
// linear in the number of nodes while preserving the exact measure and PL_BUDGET decision.
|
|
11
|
+
const measures = new Map();
|
|
6
12
|
for (const name of Object.keys(context.environment.candidate.policies)) {
|
|
7
|
-
const measure = measurePolicy(context, name, new Set());
|
|
13
|
+
const measure = measurePolicy(context, name, new Set(), measures);
|
|
8
14
|
if (measure === undefined)
|
|
9
15
|
continue;
|
|
10
16
|
const pointer = `/policies/${escapePointer(name)}`;
|
|
@@ -23,14 +29,21 @@ export function validatePolicyBudgets(context) {
|
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
|
-
function measurePolicy(context, name, visiting) {
|
|
32
|
+
function measurePolicy(context, name, visiting, measures) {
|
|
27
33
|
if (visiting.has(name))
|
|
28
34
|
return undefined;
|
|
35
|
+
if (measures.has(name))
|
|
36
|
+
return measures.get(name);
|
|
29
37
|
const declaration = context.normalized.get(name);
|
|
30
38
|
if (declaration === undefined)
|
|
31
39
|
return undefined;
|
|
32
40
|
const nested = new Set(visiting);
|
|
33
41
|
nested.add(name);
|
|
42
|
+
const measure = measureDeclaration(context, declaration, nested, measures);
|
|
43
|
+
measures.set(name, measure);
|
|
44
|
+
return measure;
|
|
45
|
+
}
|
|
46
|
+
function measureDeclaration(context, declaration, nested, measures) {
|
|
34
47
|
if ('match' in declaration) {
|
|
35
48
|
return measurePattern(declaration.match, context.environment.candidate.origin);
|
|
36
49
|
}
|
|
@@ -38,7 +51,7 @@ function measurePolicy(context, name, visiting) {
|
|
|
38
51
|
const children = refs.flatMap((ref) => {
|
|
39
52
|
if (!context.isLocalPolicy(ref))
|
|
40
53
|
return [];
|
|
41
|
-
const value = measurePolicy(context, ref.name, nested);
|
|
54
|
+
const value = measurePolicy(context, ref.name, nested, measures);
|
|
42
55
|
return value === undefined ? [] : [value];
|
|
43
56
|
});
|
|
44
57
|
if (children.length !== refs.length || children.length === 0)
|