@astrale-os/kernel-dsl 1.0.0-beta.13 → 1.0.0-beta.15
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/domain-dependency.d.ts +0 -6
- package/dist/v1/addressing/domain-dependency.js +1 -6
- package/dist/v1/addressing/key.js +2 -1
- package/dist/v1/addressing/patterns.d.ts +1 -0
- package/dist/v1/addressing/patterns.js +2 -1
- package/dist/v1/builder/class/authoring.d.ts +43 -0
- package/dist/v1/builder/class/property.d.ts +3 -9
- package/dist/v1/builder/class/property.js +1 -0
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/class.js +5 -1
- package/dist/v1/builder/lowering/policy.js +2 -0
- package/dist/v1/builder/lowering/schema/context.d.ts +5 -0
- package/dist/v1/builder/lowering/schema/context.js +22 -0
- package/dist/v1/builder/policy/authoring.d.ts +84 -4
- package/dist/v1/builder/policy/authoring.js +44 -14
- package/dist/v1/builder/schema/projection/class.d.ts +116 -14
- package/dist/v1/bundle/codec.js +9 -9
- package/dist/v1/bundle/diagnostic.d.ts +2 -0
- package/dist/v1/bundle/diagnostic.js +6 -0
- package/dist/v1/bundle/structure.js +2 -2
- package/dist/v1/compiled/admission/accept.js +3 -3
- package/dist/v1/compiled/admission/closure.js +2 -2
- package/dist/v1/compiled/admission/semantics/core.js +3 -7
- package/dist/v1/compiled/admission/semantics/policy/parity.js +3 -10
- package/dist/v1/domain/validation/create.d.ts +4 -1
- package/dist/v1/domain/validation/create.js +21 -34
- package/dist/v1/language/.spec/schemas/value-schema.schema.json +22 -0
- package/dist/v1/language/class/model/class.d.ts +4 -4
- package/dist/v1/language/class/model/edge.d.ts +2 -2
- package/dist/v1/language/class/model/property.d.ts +16 -7
- package/dist/v1/language/core/facts.d.ts +17 -4
- package/dist/v1/language/core/facts.js +26 -5
- package/dist/v1/language/definition/metadata.d.ts +5 -0
- package/dist/v1/language/definition/metadata.js +7 -0
- package/dist/v1/language/limits.d.ts +64 -0
- package/dist/v1/language/limits.js +60 -0
- package/dist/v1/language/policy/analysis/budgets.d.ts +39 -18
- package/dist/v1/language/policy/analysis/budgets.js +30 -20
- package/dist/v1/language/policy/analysis/checks.js +3 -2
- package/dist/v1/language/policy/analysis/typing.js +4 -3
- package/dist/v1/language/policy/model/policy.d.ts +5 -5
- package/dist/v1/language/policy/normalization/normalize.js +17 -16
- package/dist/v1/language/validation/diagnostic.d.ts +1 -1
- package/dist/v1/language/validation/rule.d.ts +29 -0
- package/dist/v1/language/validation/rule.js +120 -0
- package/dist/v1/language/value/profile/meta-schema.compiled.js +4 -1
- package/dist/v1/language/value/profile/rules.js +13 -37
- package/dist/v1/language/value/profile/vocabulary.d.ts +35 -2
- package/dist/v1/language/value/profile/vocabulary.js +52 -5
- package/dist/v1/language/value/reference/registry.index.js +2 -1
- package/dist/v1/language/value/regexp/ast.js +6 -7
- package/dist/v1/language/value/regexp/compile.d.ts +5 -11
- package/dist/v1/language/value/regexp/compile.js +14 -12
- package/dist/v1/language/value/regexp/syntax.js +3 -2
- package/dist/v1/language/value/syntax/keywords.d.ts +3 -3
- package/dist/v1/language/value/syntax/keywords.js +4 -20
- package/dist/v1/language/value/syntax/node-path/validate.js +2 -3
- package/dist/v1/schema/.spec/schemas/domain-schema.schema.json +2336 -0
- package/dist/v1/schema/admission/accept.js +3 -14
- package/dist/v1/schema/admission/callable.d.ts +21 -0
- package/dist/v1/schema/admission/callable.js +96 -0
- package/dist/v1/schema/analysis/class.js +33 -15
- package/dist/v1/schema/compatibility/api.d.ts +1 -1
- package/dist/v1/schema/compatibility/coordinate.js +3 -3
- package/dist/v1/schema/compatibility/model.d.ts +5 -0
- package/dist/v1/schema/diagnostic.d.ts +17 -3
- package/dist/v1/schema/diagnostic.js +11 -0
- package/dist/v1/schema/index.d.ts +10 -1
- package/dist/v1/schema/index.js +4 -0
- package/dist/v1/schema/model/schema.d.ts +19 -1
- package/dist/v1/schema/normalization/domain.d.ts +2 -0
- package/dist/v1/schema/normalization/domain.js +2 -1
- package/dist/v1/schema/resolution/closure.js +2 -1
- package/dist/v1/schema/resolution/context.js +10 -8
- package/dist/v1/schema/resolution/domain.d.ts +5 -0
- package/dist/v1/schema/resolution/domain.js +1 -1
- package/dist/v1/schema/rule.d.ts +32 -0
- package/dist/v1/schema/rule.js +428 -0
- package/dist/v1/schema/validation/callable.d.ts +35 -2
- package/dist/v1/schema/validation/callable.js +51 -8
- package/dist/v1/schema/validation/class.js +36 -44
- package/dist/v1/schema/validation/core.js +19 -13
- package/dist/v1/schema/validation/domain.js +2 -2
- package/dist/v1/schema/validation/domain.json-schema.d.ts +15 -0
- package/dist/v1/schema/validation/domain.json-schema.js +183 -0
- package/dist/v1/schema/validation/domain.route.d.ts +6 -1
- package/dist/v1/schema/validation/domain.route.js +18 -0
- package/dist/v1/schema/validation/domain.structure.d.ts +4020 -0
- package/dist/v1/schema/validation/domain.structure.js +359 -153
- package/dist/v1/schema/validation/json.json-schema.d.ts +24 -0
- package/dist/v1/schema/validation/json.json-schema.js +36 -0
- package/dist/v1/schema/validation/policy/budgets.d.ts +33 -2
- package/dist/v1/schema/validation/policy/budgets.js +89 -16
- package/dist/v1/schema/validation/policy/references.js +6 -21
- package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
- package/dist/v1/schema/validation/policy/uses.js +28 -51
- package/dist/v1/schema/validation/value-api.d.ts +12 -1
- package/dist/v1/schema/validation/value-api.js +10 -1
- package/dist/v1/schema/validation/value.d.ts +3 -0
- package/dist/v1/schema/validation/value.js +6 -12
- package/dist/v1/schema/validation/value.json-schema.d.ts +51 -0
- package/dist/v1/schema/validation/value.json-schema.js +126 -0
- package/package.json +3 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { core as zodCore } from 'zod';
|
|
2
|
+
type JsonSchema = zodCore.JSONSchema.JSONSchema;
|
|
3
|
+
/**
|
|
4
|
+
* The check that a document is portable JSON, which `schema.decode` and `schema.accept` make first.
|
|
5
|
+
* JSON Schema reads a parsed value and compares numbers by their value: it states that no string or
|
|
6
|
+
* member name holds U+0000, and cannot state negative zero, a lone surrogate, a UTF-8 size, a
|
|
7
|
+
* nesting depth without one definition per level, a count of members, or what the text was.
|
|
8
|
+
*/
|
|
9
|
+
export declare const jsonValueRules: {
|
|
10
|
+
readonly 'portable-json': {
|
|
11
|
+
readonly code: 'DV_JSON_VALUE';
|
|
12
|
+
readonly statement: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type JsonValueRule = keyof typeof jsonValueRules;
|
|
16
|
+
/**
|
|
17
|
+
* Portable JSON in JSON Schema: `root` constrains the whole document, and `definitions` holds every
|
|
18
|
+
* value it reaches and every string. The named check leaves `x-astrale-rule` on `portable-json`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function jsonValueJsonSchema(): {
|
|
21
|
+
readonly root: JsonSchema;
|
|
22
|
+
readonly definitions: Readonly<Record<string, JsonSchema>>;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RULES } from '../rule.js';
|
|
2
|
+
/**
|
|
3
|
+
* The check that a document is portable JSON, which `schema.decode` and `schema.accept` make first.
|
|
4
|
+
* JSON Schema reads a parsed value and compares numbers by their value: it states that no string or
|
|
5
|
+
* member name holds U+0000, and cannot state negative zero, a lone surrogate, a UTF-8 size, a
|
|
6
|
+
* nesting depth without one definition per level, a count of members, or what the text was.
|
|
7
|
+
*/
|
|
8
|
+
export const jsonValueRules = {
|
|
9
|
+
'portable-json': { code: 'DV_JSON_VALUE', statement: RULES.DV_JSON_VALUE.statement },
|
|
10
|
+
};
|
|
11
|
+
const VALUE = '#/$defs/portable-json';
|
|
12
|
+
const TEXT = '#/$defs/portable-string';
|
|
13
|
+
/**
|
|
14
|
+
* Portable JSON in JSON Schema: `root` constrains the whole document, and `definitions` holds every
|
|
15
|
+
* value it reaches and every string. The named check leaves `x-astrale-rule` on `portable-json`.
|
|
16
|
+
*/
|
|
17
|
+
export function jsonValueJsonSchema() {
|
|
18
|
+
return {
|
|
19
|
+
root: { $ref: VALUE },
|
|
20
|
+
definitions: {
|
|
21
|
+
'portable-json': {
|
|
22
|
+
anyOf: [
|
|
23
|
+
{ type: 'null' },
|
|
24
|
+
{ type: 'boolean' },
|
|
25
|
+
{ type: 'number' },
|
|
26
|
+
{ $ref: TEXT },
|
|
27
|
+
{ type: 'array', items: { $ref: VALUE } },
|
|
28
|
+
{ type: 'object', propertyNames: { $ref: TEXT }, additionalProperties: { $ref: VALUE } },
|
|
29
|
+
],
|
|
30
|
+
'x-astrale-rule': 'portable-json',
|
|
31
|
+
},
|
|
32
|
+
// The pattern is the character itself, which every regular-expression dialect reads.
|
|
33
|
+
'portable-string': { type: 'string', not: { pattern: '\u0000' } },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -1,4 +1,35 @@
|
|
|
1
|
+
import type { PolicyExpression } from '../../../language/policy/model/policy.js';
|
|
2
|
+
import type { DomainSchemaIR } from '../../model/schema.js';
|
|
1
3
|
import type { PolicyValidationContext } from './context.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
+
import { type CheckMeasure, type PolicyMeasure } from '../../../language/policy/analysis/budgets.js';
|
|
5
|
+
/** What `PL_BUDGET` compares with `limits.policy` in one document, frozen. */
|
|
6
|
+
export interface PolicyMeasures {
|
|
7
|
+
/** Each named Policy the DSL can measure, by name. */
|
|
8
|
+
readonly policies: Readonly<Record<string, PolicyMeasure>>;
|
|
9
|
+
/**
|
|
10
|
+
* The named Policies the DSL cannot measure, in document order: a composition that reaches
|
|
11
|
+
* itself, or that names a Policy the document does not declare or one it cannot measure.
|
|
12
|
+
*/
|
|
13
|
+
readonly unmeasured: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Each check tree by the JSON Pointer of its position: the Policy of a Function or a Method, or
|
|
16
|
+
* the read or traverse rule of a Node Class.
|
|
17
|
+
*/
|
|
18
|
+
readonly checks: Readonly<Record<string, CheckMeasure>>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Measure the Policies of a document as `PL_BUDGET` does: it reads the document's structure alone,
|
|
22
|
+
* so a document the structure admits is measured whatever its other rules decide. Throws
|
|
23
|
+
* `SchemaError` with the diagnostics of a document that is not portable JSON or that its
|
|
24
|
+
* structure refuses.
|
|
25
|
+
*/
|
|
26
|
+
export declare function measurePolicies(input: unknown): PolicyMeasures;
|
|
27
|
+
/** The measures of one structured document, from its Policies once normalized. */
|
|
28
|
+
export declare function measureDomainPolicies(schema: DomainSchemaIR, normalized?: ReadonlyMap<string, PolicyExpression>): PolicyMeasures;
|
|
29
|
+
/** `PL_BUDGET`: every measure of the document within `limits.policy`. */
|
|
4
30
|
export declare function validatePolicyBudgets(context: PolicyValidationContext): void;
|
|
31
|
+
/**
|
|
32
|
+
* `PL_BUDGET` for one check tree at `pointer`, from its measure: the document's trees, and the
|
|
33
|
+
* checks of a contract a Node carries, which no document holds.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateCheckBudget(context: PolicyValidationContext, pointer: string, measure: CheckMeasure): void;
|
|
@@ -1,25 +1,98 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { limits } from '../../../language/limits.js';
|
|
2
|
+
import { exceededLimits, measureChecks, policyMeasurer, } from '../../../language/policy/analysis/budgets.js';
|
|
3
|
+
import { isClassRuleChecks } from '../../../language/policy/model/policy.js';
|
|
4
|
+
import { normalizePolicyChecks, normalizePolicyExpression, } from '../../../language/policy/normalization/normalize.js';
|
|
2
5
|
import { escapePointerToken } from '../../../language/value/syntax/pointer.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
import { deepFreeze } from '../../canonical/freeze.js';
|
|
7
|
+
import { structureDomainSchema } from '../domain.route.js';
|
|
8
|
+
import { SITE } from './uses.js';
|
|
9
|
+
/**
|
|
10
|
+
* Measure the Policies of a document as `PL_BUDGET` does: it reads the document's structure alone,
|
|
11
|
+
* so a document the structure admits is measured whatever its other rules decide. Throws
|
|
12
|
+
* `SchemaError` with the diagnostics of a document that is not portable JSON or that its
|
|
13
|
+
* structure refuses.
|
|
14
|
+
*/
|
|
15
|
+
export function measurePolicies(input) {
|
|
16
|
+
return measureDomainPolicies(structureDomainSchema(input));
|
|
17
|
+
}
|
|
18
|
+
/** The measures of one structured document, from its Policies once normalized. */
|
|
19
|
+
export function measureDomainPolicies(schema, normalized = normalizedPolicies(schema)) {
|
|
20
|
+
const measurePolicy = policyMeasurer(schema.origin, normalized, (ref) => ref.origin === schema.origin &&
|
|
21
|
+
ref.kind === 'policy' &&
|
|
22
|
+
Object.hasOwn(schema.policies, ref.name));
|
|
23
|
+
const policies = {};
|
|
24
|
+
const unmeasured = [];
|
|
25
|
+
for (const name of Object.keys(schema.policies)) {
|
|
7
26
|
const measure = measurePolicy(name);
|
|
8
27
|
if (measure === undefined)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
unmeasured.push(name);
|
|
29
|
+
else
|
|
30
|
+
policies[name] = measure;
|
|
31
|
+
}
|
|
32
|
+
const checks = {};
|
|
33
|
+
for (const [pointer, expression] of checkTrees(schema)) {
|
|
34
|
+
checks[pointer] = measureChecks(normalizePolicyChecks(expression));
|
|
35
|
+
}
|
|
36
|
+
return deepFreeze({ policies, unmeasured, checks });
|
|
37
|
+
}
|
|
38
|
+
/** `PL_BUDGET`: every measure of the document within `limits.policy`. */
|
|
39
|
+
export function validatePolicyBudgets(context) {
|
|
40
|
+
const measures = measureDomainPolicies(context.environment.candidate, context.normalized);
|
|
41
|
+
for (const [name, measure] of Object.entries(measures.policies)) {
|
|
42
|
+
for (const limit of exceededLimits(measure)) {
|
|
43
|
+
context.add('PL_BUDGET', `/policies/${escapePointerToken(name)}`, REFUSAL[limit](measure));
|
|
13
44
|
}
|
|
14
|
-
|
|
15
|
-
|
|
45
|
+
}
|
|
46
|
+
for (const [pointer, measure] of Object.entries(measures.checks)) {
|
|
47
|
+
validateCheckBudget(context, pointer, measure);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `PL_BUDGET` for one check tree at `pointer`, from its measure: the document's trees, and the
|
|
52
|
+
* checks of a contract a Node carries, which no document holds.
|
|
53
|
+
*/
|
|
54
|
+
export function validateCheckBudget(context, pointer, measure) {
|
|
55
|
+
if (exceededLimits(measure).length === 0)
|
|
56
|
+
return;
|
|
57
|
+
const { checkDepth, checkBranches, checkLeaves } = limits.policy;
|
|
58
|
+
// A callable holds its checks at `policy`; a Node Class rule at `policies/{operation}`.
|
|
59
|
+
const site = SITE[pointer.endsWith('/policy') ? 'callable' : 'class'];
|
|
60
|
+
context.add('PL_BUDGET', pointer, `${site.checks} require depth <= ${checkDepth}, branches <= ${checkBranches}, and leaves <= ${checkLeaves}; got ${measure.checkDepth}, ${measure.checkBranches}, ${measure.checkLeaves}.`);
|
|
61
|
+
}
|
|
62
|
+
/** How `PL_BUDGET` explains each limit a named Policy exceeds. */
|
|
63
|
+
const REFUSAL = {
|
|
64
|
+
patternDepth: (measure) => `Expanded Pattern depth ${measure.patternDepth} exceeds ${limits.policy.patternDepth}.`,
|
|
65
|
+
normalizedBranches: () => `Expanded Pattern has more than ${limits.policy.normalizedBranches} normalized branches.`,
|
|
66
|
+
edgePredicates: (measure) => `A normalized branch contains ${measure.edgePredicates} graph predicates; the maximum is ${limits.policy.edgePredicates}.`,
|
|
67
|
+
referencedDomains: (measure) => `Expanded Pattern references ${measure.referencedDomains} Domains; the maximum is ${limits.policy.referencedDomains}.`,
|
|
68
|
+
};
|
|
69
|
+
function normalizedPolicies(schema) {
|
|
70
|
+
return new Map(Object.entries(schema.policies).map(([name, declaration]) => [
|
|
71
|
+
name,
|
|
72
|
+
normalizePolicyExpression(declaration.expression),
|
|
73
|
+
]));
|
|
74
|
+
}
|
|
75
|
+
/** Every check tree of a document with the pointer of its position, in document order. */
|
|
76
|
+
function* checkTrees(schema) {
|
|
77
|
+
for (const [name, declaration] of Object.entries(schema.classes)) {
|
|
78
|
+
const owner = `/classes/${escapePointerToken(name)}`;
|
|
79
|
+
for (const operation of ['read', 'traverse']) {
|
|
80
|
+
const rule = declaration.policies[operation];
|
|
81
|
+
if (typeof rule === 'object' && isClassRuleChecks(rule)) {
|
|
82
|
+
yield [`${owner}/policies/${operation}`, rule];
|
|
83
|
+
}
|
|
16
84
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
85
|
+
if (declaration.kind !== 'node')
|
|
86
|
+
continue;
|
|
87
|
+
for (const [method, value] of Object.entries(declaration.methods)) {
|
|
88
|
+
if (typeof value.policy === 'object') {
|
|
89
|
+
yield [`${owner}/methods/${escapePointerToken(method)}/policy`, value.policy];
|
|
90
|
+
}
|
|
20
91
|
}
|
|
21
|
-
|
|
22
|
-
|
|
92
|
+
}
|
|
93
|
+
for (const [name, declaration] of Object.entries(schema.functions)) {
|
|
94
|
+
if (typeof declaration.policy === 'object') {
|
|
95
|
+
yield [`/functions/${escapePointerToken(name)}/policy`, declaration.policy];
|
|
23
96
|
}
|
|
24
97
|
}
|
|
25
98
|
}
|
|
@@ -3,7 +3,6 @@ import { branchesOf, isConnected, targetMode } from '../../../language/policy/an
|
|
|
3
3
|
import { policyVariableClass } from '../../../language/policy/model/policy.js';
|
|
4
4
|
import { normalizePolicyExpression, normalizePolicyPattern, } from '../../../language/policy/normalization/normalize.js';
|
|
5
5
|
import { escapePointerToken } from '../../../language/value/syntax/pointer.js';
|
|
6
|
-
import { MAX_REPEAT, MAX_VARIABLES } from './budgets.js';
|
|
7
6
|
import { referenceContracts } from './objects.js';
|
|
8
7
|
export function validatePolicyReferences(context) {
|
|
9
8
|
for (const [name, declaration] of Object.entries(context.environment.candidate.policies)) {
|
|
@@ -15,9 +14,9 @@ export function validatePolicyReferences(context) {
|
|
|
15
14
|
const expression = declaration.expression;
|
|
16
15
|
context.normalized.set(name, normalizePolicyExpression(expression));
|
|
17
16
|
if ('match' in expression)
|
|
18
|
-
validateMatch(context, name, expression.match, pointer);
|
|
17
|
+
validateMatch(context, name, expression.match, `${pointer}/expression`);
|
|
19
18
|
else
|
|
20
|
-
validateComposition(context, expression, pointer);
|
|
19
|
+
validateComposition(context, expression, `${pointer}/expression`);
|
|
21
20
|
}
|
|
22
21
|
validateCycles(context);
|
|
23
22
|
}
|
|
@@ -53,22 +52,14 @@ function validatePatternNode(context, pattern, pointer, scope, declarations, use
|
|
|
53
52
|
context.resolveEdge(pattern.class, `${pointer}/class`);
|
|
54
53
|
validateTerm(context, pattern.source, `${pointer}/source`, scope, uses);
|
|
55
54
|
validateTerm(context, pattern.target, `${pointer}/target`, scope, uses);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
min < 0 ||
|
|
61
|
-
max < min ||
|
|
62
|
-
max > MAX_REPEAT) {
|
|
63
|
-
context.add('PL_REPEAT', `${pointer}/repeat`, `Repeat requires integral bounds 0 <= min <= max <= ${MAX_REPEAT}.`);
|
|
64
|
-
}
|
|
55
|
+
// The structure bounds each to an integer from 0 to `limits.policy.repetition`; only their
|
|
56
|
+
// order is left.
|
|
57
|
+
if (pattern.repeat !== undefined && pattern.repeat.max < pattern.repeat.min) {
|
|
58
|
+
context.add('PL_REPEAT', `${pointer}/repeat`, 'Repeat requires min <= max.');
|
|
65
59
|
}
|
|
66
60
|
return;
|
|
67
61
|
}
|
|
68
62
|
if ('exists' in pattern) {
|
|
69
|
-
if (pattern.exists.nodes.length > MAX_VARIABLES) {
|
|
70
|
-
context.add('PL_BUDGET', `${pointer}/exists/nodes`, `An exists expression declares ${pattern.exists.nodes.length} variables; the maximum is ${MAX_VARIABLES}.`);
|
|
71
|
-
}
|
|
72
63
|
const nested = new Set(scope);
|
|
73
64
|
for (const [index, node] of pattern.exists.nodes.entries()) {
|
|
74
65
|
const id = node.variable.id;
|
|
@@ -92,9 +83,6 @@ function validatePatternNode(context, pattern, pointer, scope, declarations, use
|
|
|
92
83
|
return;
|
|
93
84
|
}
|
|
94
85
|
const { key, values } = patternOperands(pattern);
|
|
95
|
-
if (values.length === 0) {
|
|
96
|
-
context.add('PL_COMPOSITION', `${pointer}/${key}`, `${key} must be non-empty.`);
|
|
97
|
-
}
|
|
98
86
|
values.forEach((child, index) => validatePatternNode(context, child, `${pointer}/${key}/${index}`, scope, declarations, uses));
|
|
99
87
|
}
|
|
100
88
|
function validateTerm(context, term, pointer, scope, uses) {
|
|
@@ -112,9 +100,6 @@ function validateTerm(context, term, pointer, scope, uses) {
|
|
|
112
100
|
function validateComposition(context, declaration, pointer) {
|
|
113
101
|
const key = 'allOf' in declaration ? 'allOf' : 'anyOf';
|
|
114
102
|
const refs = 'allOf' in declaration ? declaration.allOf : declaration.anyOf;
|
|
115
|
-
if (refs.length === 0) {
|
|
116
|
-
context.add('PL_COMPOSITION', `${pointer}/${key}`, `${key} must be non-empty.`);
|
|
117
|
-
}
|
|
118
103
|
refs.forEach((ref, index) => context.resolvePolicy(ref, `${pointer}/${key}/${index}`));
|
|
119
104
|
}
|
|
120
105
|
function validateCycles(context) {
|
|
@@ -1,5 +1,34 @@
|
|
|
1
|
+
import type { CallableDefinition } from '../../../language/callable/model/callable.js';
|
|
1
2
|
import type { PolicyTyping } from '../../../language/policy/analysis/typing.js';
|
|
3
|
+
import type { PolicyCheckExpression } from '../../../language/policy/model/policy.js';
|
|
4
|
+
import type { JsonSchema } from '../../../language/value/model/schema.js';
|
|
2
5
|
import type { PolicyValidationContext } from './context.js';
|
|
6
|
+
import { type ClassRef } from '../../../addressing/index.js';
|
|
7
|
+
interface CheckContext {
|
|
8
|
+
readonly site: keyof typeof SITE;
|
|
9
|
+
readonly pointer: string;
|
|
10
|
+
readonly input: JsonSchema;
|
|
11
|
+
/** The Node Class that receives the call, which the checks name `self`; none, and none is named. */
|
|
12
|
+
readonly receiver?: ClassRef;
|
|
13
|
+
}
|
|
14
|
+
/** How a refusal names the checks of each site and the contract of their objects. */
|
|
15
|
+
export declare const SITE: {
|
|
16
|
+
readonly callable: {
|
|
17
|
+
readonly checks: 'Callable checks';
|
|
18
|
+
readonly objects: 'the callable object contract';
|
|
19
|
+
};
|
|
20
|
+
readonly class: {
|
|
21
|
+
readonly checks: 'Class rule checks';
|
|
22
|
+
readonly objects: 'the Class rule object contract';
|
|
23
|
+
};
|
|
24
|
+
};
|
|
3
25
|
export declare function validateReadUses(context: PolicyValidationContext, typing: PolicyTyping): void;
|
|
4
26
|
export declare function validateTraverseUses(context: PolicyValidationContext, typing: PolicyTyping): void;
|
|
5
27
|
export declare function validateCallableUses(context: PolicyValidationContext, typing: PolicyTyping): void;
|
|
28
|
+
/**
|
|
29
|
+
* Validate the Policy of one callable contract written at `pointer`, wherever it is declared:
|
|
30
|
+
* `self` names the Node of the `receiver` Class, and nothing where the call has no receiver.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateContractPolicy(context: PolicyValidationContext, typing: PolicyTyping, contract: CallableDefinition, pointer: string, receiver: ClassRef | undefined): void;
|
|
33
|
+
export declare function validateChecks(validation: PolicyValidationContext, typing: PolicyTyping, expression: PolicyCheckExpression, context: CheckContext): void;
|
|
34
|
+
export {};
|
|
@@ -6,12 +6,12 @@ import { checksNameTheCaller, isClassRuleChecks } from '../../../language/policy
|
|
|
6
6
|
import { normalizePolicyChecks } from '../../../language/policy/normalization/normalize.js';
|
|
7
7
|
import { GRAPH_NODE_PATH_SCHEMA_ID } from '../../../language/value/syntax/node-path/schema.js';
|
|
8
8
|
import { escapePointerToken } from '../../../language/value/syntax/pointer.js';
|
|
9
|
-
import {
|
|
9
|
+
import { declaredContracts } from '../callable.js';
|
|
10
10
|
import { referenceContracts } from './objects.js';
|
|
11
11
|
import { contractOfNodeDefinition } from './typing.js';
|
|
12
12
|
const formatDefinitionRef = Key.of;
|
|
13
13
|
/** How a refusal names the checks of each site and the contract of their objects. */
|
|
14
|
-
const SITE = {
|
|
14
|
+
export const SITE = {
|
|
15
15
|
callable: { checks: 'Callable checks', objects: 'the callable object contract' },
|
|
16
16
|
class: { checks: 'Class rule checks', objects: 'the Class rule object contract' },
|
|
17
17
|
};
|
|
@@ -34,7 +34,6 @@ function validateClassPolicyUses(context, typing, operation) {
|
|
|
34
34
|
pointer,
|
|
35
35
|
input: {},
|
|
36
36
|
receiver: { origin: context.environment.candidate.origin, kind: 'class', name },
|
|
37
|
-
instanceMethod: true,
|
|
38
37
|
});
|
|
39
38
|
continue;
|
|
40
39
|
}
|
|
@@ -56,58 +55,39 @@ function validateClassPolicyUses(context, typing, operation) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
export function validateCallableUses(context, typing) {
|
|
59
|
-
for (const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
for (const [name, declaration] of Object.entries(context.environment.candidate.classes)) {
|
|
70
|
-
if (declaration.kind !== 'node')
|
|
71
|
-
continue;
|
|
72
|
-
validateDeclaredMethods(context, typing, 'classes', name, declaration.methods, {
|
|
73
|
-
origin: context.environment.candidate.origin,
|
|
74
|
-
kind: 'class',
|
|
75
|
-
name,
|
|
76
|
-
});
|
|
58
|
+
for (const { contract, pointer, receiver, method } of declaredContracts(context.environment.candidate)) {
|
|
59
|
+
// Definition analysis is the authority for the effective callable. A missing Method is already
|
|
60
|
+
// a definition diagnostic, so avoid inventing a second Policy interpretation for it.
|
|
61
|
+
if (method !== undefined) {
|
|
62
|
+
const owner = context.analysis.definitions.get(definitionRefIndex(method.owner));
|
|
63
|
+
if (owner !== undefined && !owner.methods.has(method.name))
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
validateContractPolicy(context, typing, contract, pointer, receiver);
|
|
77
67
|
}
|
|
78
68
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
input: method.input,
|
|
93
|
-
receiver,
|
|
94
|
-
instanceMethod: !method.static,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate the Policy of one callable contract written at `pointer`, wherever it is declared:
|
|
71
|
+
* `self` names the Node of the `receiver` Class, and nothing where the call has no receiver.
|
|
72
|
+
*/
|
|
73
|
+
export function validateContractPolicy(context, typing, contract, pointer, receiver) {
|
|
74
|
+
if (typeof contract.policy !== 'object')
|
|
75
|
+
return;
|
|
76
|
+
validateChecks(context, typing, contract.policy, {
|
|
77
|
+
site: 'callable',
|
|
78
|
+
pointer: `${pointer}/policy`,
|
|
79
|
+
input: isBinaryInput(contract.input) ? contract.input.metadata : contract.input,
|
|
80
|
+
...(receiver === undefined ? {} : { receiver }),
|
|
81
|
+
});
|
|
97
82
|
}
|
|
98
|
-
function validateChecks(validation, typing, expression, context) {
|
|
83
|
+
export function validateChecks(validation, typing, expression, context) {
|
|
99
84
|
// Checks decide the caller the way a named Policy does, so, as a Policy connects its subject,
|
|
100
85
|
// each way they hold must pass through a check of the caller.
|
|
101
86
|
if (!checksNameTheCaller(expression)) {
|
|
102
87
|
validation.add('PL_CONNECTIVITY', context.pointer, `${SITE[context.site].checks} must check a Policy on every branch: sameNode alone names no caller.`);
|
|
103
88
|
}
|
|
89
|
+
// Their budget is checked with the named Policies', from the same measure (`budgets.ts`).
|
|
104
90
|
const normalized = normalizePolicyChecks(expression);
|
|
105
|
-
const measure = measureChecks(normalized);
|
|
106
|
-
if (measure.depth > MAX_CHECK_DEPTH ||
|
|
107
|
-
measure.branches > MAX_BRANCHES ||
|
|
108
|
-
measure.leaves > MAX_CHECK_LEAVES) {
|
|
109
|
-
validation.add('PL_BUDGET', context.pointer, `${SITE[context.site].checks} require depth <= ${MAX_CHECK_DEPTH}, branches <= ${MAX_BRANCHES}, and leaves <= ${MAX_CHECK_LEAVES}; got ${measure.depth}, ${measure.branches}, ${measure.leaves}.`);
|
|
110
|
-
}
|
|
111
91
|
const leaves = new Map();
|
|
112
92
|
visitChecks(validation, typing, normalized, context, context.pointer, leaves);
|
|
113
93
|
for (const [object, className] of neverAdmittedClasses(normalized, leaves)) {
|
|
@@ -147,14 +127,11 @@ function visitChecks(validation, typing, expression, context, pointer, leaves) {
|
|
|
147
127
|
return;
|
|
148
128
|
}
|
|
149
129
|
const { key, values } = checkOperands(expression);
|
|
150
|
-
if (values.length === 0) {
|
|
151
|
-
validation.add('PL_COMPOSITION', `${pointer}/${key}`, `${key} must be non-empty.`);
|
|
152
|
-
}
|
|
153
130
|
values.forEach((child, index) => visitChecks(validation, typing, child, context, `${pointer}/${key}/${index}`, leaves));
|
|
154
131
|
}
|
|
155
132
|
function selfContracts(validation, context, pointer) {
|
|
156
|
-
if (
|
|
157
|
-
validation.add('PL_USE', pointer, 'self is valid only
|
|
133
|
+
if (context.receiver === undefined) {
|
|
134
|
+
validation.add('PL_USE', pointer, 'self is valid only where a Node receives the call: a Node instance Method, or a Function instance, whose Node carries the contract.');
|
|
158
135
|
return undefined;
|
|
159
136
|
}
|
|
160
137
|
return [contractOfNodeDefinition(context.receiver)];
|
|
@@ -10,11 +10,22 @@ export type { BlobOptions, BlobReceipt, BlobRef, BlobSchema, } from '../../langu
|
|
|
10
10
|
export type { JsonSchema as Schema, JsonSchemaObject as SchemaObject, PathAnnotation, PropertyObjectSchema as PropertySchema, ValueSchemaContext as Context, } from '../../language/value/model/schema.js';
|
|
11
11
|
export { instant } from '../../language/value/model/instant.js';
|
|
12
12
|
export type { OrderingKind } from '../../language/value/ordering.js';
|
|
13
|
+
export { DECLARED_VALUE_KINDS as DECLARED_KINDS, FORBIDDEN_KEYWORDS, SUBSCHEMA_KEYWORDS, VALUE_KEYWORDS as KEYWORDS, } from '../../language/value/profile/vocabulary.js';
|
|
14
|
+
export type { DeclaredValueKind as DeclaredKind, ValueKeyword as Keyword, } from '../../language/value/profile/vocabulary.js';
|
|
15
|
+
export { VALUE_RULES as RULES } from '../../language/validation/rule.js';
|
|
16
|
+
export type { ValueRule as Rule, ValueRuleStage as RuleStage, } from '../../language/validation/rule.js';
|
|
13
17
|
/** Validate one complete JSON value under the exact accepted schema that owns the contract. */
|
|
14
18
|
export declare function validate(owner: DomainSchema, contract: JsonSchema, input: unknown, pointer?: string): readonly Diagnostic[];
|
|
15
19
|
/** Return the one portable scalar ordering domain admitted by a property schema, if any. */
|
|
16
20
|
export declare function classifyOrdering(owner: DomainSchema, contract: JsonSchema): OrderingKind | undefined;
|
|
17
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The value kind of a property schema: what a graph language may test on the property (Y4). It is
|
|
23
|
+
* the one kind of every non-null value the schema admits: `text` for strings, `number` for numbers
|
|
24
|
+
* and integers, `instant` for strings declared `x-astrale-value-kind: instant`, `enum` for a finite
|
|
25
|
+
* set of strings (`const` or `enum`), `boolean`, and `set` for arrays of `uniqueItems`. An
|
|
26
|
+
* ambiguous union, an object, an array with repetitions, a blob or a schema that admits no value is
|
|
27
|
+
* `json`. `$ref`, `allOf`, `anyOf` and `oneOf` are followed; `null` is a point of every kind.
|
|
28
|
+
*/
|
|
18
29
|
export declare function classifyValueKind(owner: DomainSchema, contract: JsonSchema): ValueKind;
|
|
19
30
|
/**
|
|
20
31
|
* A warning for each class property whose value kind is `json` (an ambiguous union, an object, an
|
|
@@ -8,6 +8,8 @@ const registries = new WeakMap();
|
|
|
8
8
|
export { VALUE_SCHEMA_DIALECT as DIALECT, VALUE_SCHEMA_VOCABULARY as VOCABULARY, } from '../../language/value/model/schema.js';
|
|
9
9
|
export { blob, isBlobValueSchema as isBlob } from '../../language/value/model/blob.js';
|
|
10
10
|
export { instant } from '../../language/value/model/instant.js';
|
|
11
|
+
export { DECLARED_VALUE_KINDS as DECLARED_KINDS, FORBIDDEN_KEYWORDS, SUBSCHEMA_KEYWORDS, VALUE_KEYWORDS as KEYWORDS, } from '../../language/value/profile/vocabulary.js';
|
|
12
|
+
export { VALUE_RULES as RULES } from '../../language/validation/rule.js';
|
|
11
13
|
/** Validate one complete JSON value under the exact accepted schema that owns the contract. */
|
|
12
14
|
export function validate(owner, contract, input, pointer = '') {
|
|
13
15
|
return validateJsonInstance(contract, input, registryFor(owner), pointer);
|
|
@@ -16,7 +18,14 @@ export function validate(owner, contract, input, pointer = '') {
|
|
|
16
18
|
export function classifyOrdering(owner, contract) {
|
|
17
19
|
return classifyOrderingWithRegistry(contract, registryFor(owner));
|
|
18
20
|
}
|
|
19
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The value kind of a property schema: what a graph language may test on the property (Y4). It is
|
|
23
|
+
* the one kind of every non-null value the schema admits: `text` for strings, `number` for numbers
|
|
24
|
+
* and integers, `instant` for strings declared `x-astrale-value-kind: instant`, `enum` for a finite
|
|
25
|
+
* set of strings (`const` or `enum`), `boolean`, and `set` for arrays of `uniqueItems`. An
|
|
26
|
+
* ambiguous union, an object, an array with repetitions, a blob or a schema that admits no value is
|
|
27
|
+
* `json`. `$ref`, `allOf`, `anyOf` and `oneOf` are followed; `null` is a point of every kind.
|
|
28
|
+
*/
|
|
20
29
|
export function classifyValueKind(owner, contract) {
|
|
21
30
|
return classifyValueKindWithRegistry(contract, registryFor(owner));
|
|
22
31
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { CallableDefinition } from '../../language/callable/model/callable.js';
|
|
1
2
|
import type { ValueSchemaRegistry } from '../../language/value/reference/registry.js';
|
|
2
3
|
import type { SchemaDiagnostic } from '../diagnostic.js';
|
|
3
4
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
4
5
|
export declare function validateEmbeddedValueSchemas(schema: DomainSchemaIR, registry: ValueSchemaRegistry): readonly SchemaDiagnostic[];
|
|
6
|
+
/** Validate the value schemas of one callable contract at `pointer`. */
|
|
7
|
+
export declare function validateCallable(declaration: CallableDefinition, pointer: string, registry: ValueSchemaRegistry, diagnostics: SchemaDiagnostic[]): void;
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
import { isBinaryInput } from '../../language/callable/model/callable.js';
|
|
2
2
|
import { validateValueSchema } from '../../language/value/profile/validate.js';
|
|
3
3
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
4
|
-
import {
|
|
4
|
+
import { valueDiagnostic } from '../diagnostic.js';
|
|
5
5
|
export function validateEmbeddedValueSchemas(schema, registry) {
|
|
6
6
|
const diagnostics = [];
|
|
7
7
|
for (const [name, declaration] of Object.entries(schema.classes)) {
|
|
8
8
|
validateDefinition(declaration, `/classes/${escapePointerToken(name)}`, registry, diagnostics);
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
validateCallable(declaration, `/functions/${escapePointerToken(name)}`, registry, diagnostics);
|
|
12
|
-
}
|
|
10
|
+
// Callable contracts are validated at their site, as one contract (`validateContracts`).
|
|
13
11
|
return Object.freeze(diagnostics);
|
|
14
12
|
}
|
|
15
13
|
function validateDefinition(declaration, pointer, registry, diagnostics) {
|
|
16
14
|
for (const [name, property] of Object.entries(declaration.properties)) {
|
|
17
15
|
validate(property.schema, 'property-facet', `${pointer}/properties/${escapePointerToken(name)}/schema`, registry, diagnostics);
|
|
18
16
|
}
|
|
19
|
-
if ('methods' in declaration) {
|
|
20
|
-
for (const [name, method] of Object.entries(declaration.methods)) {
|
|
21
|
-
validateCallable(method, `${pointer}/methods/${escapePointerToken(name)}`, registry, diagnostics);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
17
|
}
|
|
25
|
-
|
|
18
|
+
/** Validate the value schemas of one callable contract at `pointer`. */
|
|
19
|
+
export function validateCallable(declaration, pointer, registry, diagnostics) {
|
|
26
20
|
if (isBinaryInput(declaration.input)) {
|
|
27
21
|
validate(declaration.input.metadata, 'callable-input', `${pointer}/input/metadata`, registry, diagnostics);
|
|
28
22
|
}
|
|
@@ -37,7 +31,7 @@ function validateCallable(declaration, pointer, registry, diagnostics) {
|
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
33
|
function validate(schema, context, pointer, registry, diagnostics) {
|
|
40
|
-
for (const
|
|
41
|
-
diagnostics.push(
|
|
34
|
+
for (const refusal of validateValueSchema(schema, context, registry, pointer)) {
|
|
35
|
+
diagnostics.push(valueDiagnostic('DV_VALUE_SCHEMA', refusal));
|
|
42
36
|
}
|
|
43
37
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { core as zodCore } from 'zod';
|
|
2
|
+
type JsonSchema = zodCore.JSONSchema.JSONSchema;
|
|
3
|
+
/**
|
|
4
|
+
* The checks on value schemas JSON Schema cannot state, which the published document names with
|
|
5
|
+
* the positions it constrains. The Kernel reports each as `DV_VALUE_SCHEMA`, with its value code.
|
|
6
|
+
*/
|
|
7
|
+
export declare const valueSchemaRules: {
|
|
8
|
+
readonly 'value-blob': {
|
|
9
|
+
readonly code: 'DV_VALUE_SCHEMA';
|
|
10
|
+
readonly valueCode: 'VS_VALUE_KIND';
|
|
11
|
+
readonly statement: 'A blob is declared only by the whole schema of a Property, exactly as values.blob writes it.';
|
|
12
|
+
};
|
|
13
|
+
readonly 'value-path': {
|
|
14
|
+
readonly code: 'DV_VALUE_SCHEMA';
|
|
15
|
+
readonly valueCode: 'VS_PATH_EXTENSION';
|
|
16
|
+
readonly statement: 'x-astrale-path annotates only a direct Property of a callable input object, beside exactly the $ref of the graph NodePath schema, and reads target node, cardinality one and a non-empty set of Node Class refs it accepts.';
|
|
17
|
+
};
|
|
18
|
+
readonly 'value-recursion': {
|
|
19
|
+
readonly code: 'DV_VALUE_SCHEMA';
|
|
20
|
+
readonly valueCode: 'VS_RECURSION_PRODUCTIVE';
|
|
21
|
+
readonly statement: 'Every reference cycle of a value schema crosses a keyword that steps into the value: to a member, a member name or an item.';
|
|
22
|
+
};
|
|
23
|
+
readonly 'value-reference-form': {
|
|
24
|
+
readonly code: 'DV_VALUE_SCHEMA';
|
|
25
|
+
readonly valueCode: 'VS_REFERENCE_FORM';
|
|
26
|
+
readonly statement: 'A $ref is a JSON Pointer fragment into its own value schema, or the id of the graph NodePath schema beside x-astrale-path.';
|
|
27
|
+
};
|
|
28
|
+
readonly 'value-reference-resolution': {
|
|
29
|
+
readonly code: 'DV_VALUE_SCHEMA';
|
|
30
|
+
readonly valueCode: 'VS_REFERENCE_RESOLUTION';
|
|
31
|
+
readonly statement: 'A $ref resolves to a location of its own value schema, and x-astrale-path accepts Node Classes of the Domain or of a direct dependency.';
|
|
32
|
+
};
|
|
33
|
+
readonly 'value-regexp': {
|
|
34
|
+
readonly code: 'DV_VALUE_SCHEMA';
|
|
35
|
+
readonly valueCode: 'VS_REGEX_PORTABLE';
|
|
36
|
+
readonly statement: 'Every pattern, fullMatch and patternProperties name is an RFC 9485 I-Regexp within the regular-expression limits.';
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export type ValueSchemaRule = keyof typeof valueSchemaRules;
|
|
40
|
+
/**
|
|
41
|
+
* The value language in JSON Schema, for the document that embeds value schemas: `root` constrains
|
|
42
|
+
* the `value-schema` definition with the published dialect and what the profile adds to it,
|
|
43
|
+
* `definitions` holds the locations it reaches, and `nodeProperty` constrains a Node Property
|
|
44
|
+
* definition. A named check leaves `x-astrale-rule` at the positions it constrains.
|
|
45
|
+
*/
|
|
46
|
+
export declare function valueSchemaJsonSchema(): {
|
|
47
|
+
readonly root: readonly JsonSchema[];
|
|
48
|
+
readonly definitions: Readonly<Record<string, JsonSchema>>;
|
|
49
|
+
readonly nodeProperty: JsonSchema;
|
|
50
|
+
};
|
|
51
|
+
export {};
|