@astrale-os/kernel-dsl 1.0.0-beta.14 → 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/builder/class/authoring.d.ts +43 -0
- package/dist/v1/builder/index.d.ts +1 -1
- package/dist/v1/builder/lowering/policy.js +1 -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 +41 -12
- package/dist/v1/builder/schema/projection/class.d.ts +116 -14
- package/dist/v1/bundle/codec.js +7 -7
- 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/language/core/facts.d.ts +17 -4
- package/dist/v1/language/core/facts.js +26 -5
- 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/validation/rule.d.ts +3 -0
- package/dist/v1/language/validation/rule.js +5 -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/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/index.d.ts +7 -0
- package/dist/v1/schema/index.js +3 -0
- package/dist/v1/schema/normalization/domain.d.ts +2 -0
- package/dist/v1/schema/normalization/domain.js +1 -1
- package/dist/v1/schema/resolution/context.js +7 -7
- 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 +3 -0
- package/dist/v1/schema/rule.js +16 -3
- package/dist/v1/schema/validation/callable.d.ts +33 -3
- package/dist/v1/schema/validation/callable.js +49 -15
- package/dist/v1/schema/validation/core.js +16 -10
- package/dist/v1/schema/validation/domain.js +2 -2
- 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 +28 -26
- package/dist/v1/schema/validation/domain.structure.js +22 -6
- 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 +2 -1
- package/dist/v1/schema/validation/policy/uses.d.ts +29 -0
- package/dist/v1/schema/validation/policy/uses.js +28 -48
- package/dist/v1/schema/validation/value.d.ts +3 -0
- package/dist/v1/schema/validation/value.js +3 -9
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { limits } from '../../limits.js';
|
|
1
2
|
import { parseIRegexp } from './ast.js';
|
|
2
|
-
import {
|
|
3
|
+
import { withinStateBudget } from './compile.js';
|
|
3
4
|
/** The refusal of one source, through the same AST and state count as compilation. */
|
|
4
5
|
export function refuseIRegexp(source) {
|
|
5
6
|
const expression = parseIRegexp(source);
|
|
@@ -13,5 +14,5 @@ export function isIRegexp(source) {
|
|
|
13
14
|
}
|
|
14
15
|
/** The authoring explanation of a `states` refusal of `subject` (`pattern`, `fullMatch`, ...). */
|
|
15
16
|
export function stateBudgetMessage(subject) {
|
|
16
|
-
return `${subject} expands its counted repetitions into more than ${
|
|
17
|
+
return `${subject} expands its counted repetitions into more than ${limits.regexp.maxStates} states, the I-Regexp implementation limit (RFC 9485 §8); nest fewer or smaller counts.`;
|
|
17
18
|
}
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
|
|
2
1
|
import { canonicalDomainSchemaAfterValidation } from '../canonical/revision.js';
|
|
3
2
|
import { emptyDomainSchemaLookup } from '../dependency/lookup.js';
|
|
4
|
-
import { SchemaError
|
|
3
|
+
import { SchemaError } from '../diagnostic.js';
|
|
5
4
|
import { rememberDomainClosure } from '../resolution/closure.js';
|
|
6
5
|
import { validateDomainSchemaSemantics } from '../validation/domain.js';
|
|
7
|
-
import {
|
|
6
|
+
import { structureDomainSchema } from '../validation/domain.route.js';
|
|
8
7
|
export function acceptDomainSchema(input, lookup = emptyDomainSchemaLookup()) {
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
admitted = acceptJsonValue(input);
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
if (error instanceof JsonBoundaryError) {
|
|
15
|
-
throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
|
|
16
|
-
}
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
19
|
-
const structured = routeDomainSchema(admitted);
|
|
8
|
+
const structured = structureDomainSchema(input);
|
|
20
9
|
const validation = validateDomainSchemaSemantics(structured, lookup);
|
|
21
10
|
if (validation.diagnostics.length > 0)
|
|
22
11
|
throw new SchemaError(validation.diagnostics);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ClassRef } from '../../addressing/index.js';
|
|
2
|
+
import type { CallableValidation } from '../../domain/validation/create.js';
|
|
3
|
+
import type { CallableDefinition } from '../../language/callable/model/callable.js';
|
|
4
|
+
import type { DomainSchema } from '../model/schema.js';
|
|
5
|
+
/** One admitted callable contract and the validators of its exact owner environment. */
|
|
6
|
+
export interface AcceptedCallable extends CallableValidation {
|
|
7
|
+
readonly definition: CallableDefinition;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Accept one callable contract in the exact environment of its owner Domain, by the one validation
|
|
11
|
+
* every contract passes wherever it is declared (`validateContract`, `validateContractPolicy`,
|
|
12
|
+
* `validateCheckBudget`): the structure of a Function declaration without `ref`, its value schemas
|
|
13
|
+
* against the owner registry, its execution modifier and admission age, and its Policy with the
|
|
14
|
+
* budget of its checks. `receiver` is the owner Node Class whose Node receives the call and which
|
|
15
|
+
* the Policy names `self`: the Class of a Function instance, whose Node carries the contract, or
|
|
16
|
+
* the owner of an instance Method. Without it, as at a Function declaration, the Policy names none.
|
|
17
|
+
* Returns the canonical definition and its validators.
|
|
18
|
+
*/
|
|
19
|
+
export declare function acceptCallable(input: unknown, owner: DomainSchema, options?: {
|
|
20
|
+
readonly receiver?: ClassRef;
|
|
21
|
+
}): AcceptedCallable;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
|
|
2
|
+
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
|
+
import { isBinaryInput } from '../../language/callable/model/callable.js';
|
|
4
|
+
import { measureChecks } from '../../language/policy/analysis/budgets.js';
|
|
5
|
+
import { PolicyTyping } from '../../language/policy/analysis/typing.js';
|
|
6
|
+
import { normalizePolicyChecks } from '../../language/policy/normalization/normalize.js';
|
|
7
|
+
import {} from '../../language/value/evaluate/preparation.js';
|
|
8
|
+
import { analyzeDefinitions } from '../analysis/class.js';
|
|
9
|
+
import { deepFreeze } from '../canonical/freeze.js';
|
|
10
|
+
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
11
|
+
import { canonicalizeCallable } from '../normalization/domain.js';
|
|
12
|
+
import { lookupForDomainClosure } from '../resolution/closure.js';
|
|
13
|
+
import { buildDomainResolutionContext } from '../resolution/context.js';
|
|
14
|
+
import { callableValidation } from '../resolution/domain.js';
|
|
15
|
+
import { createDomainValueSchemaRegistry } from '../resolution/value.registry.js';
|
|
16
|
+
import { validateContract } from '../validation/callable.js';
|
|
17
|
+
import { parseCallableContract } from '../validation/domain.structure.js';
|
|
18
|
+
import { validateCheckBudget } from '../validation/policy/budgets.js';
|
|
19
|
+
import { PolicyValidationContext } from '../validation/policy/context.js';
|
|
20
|
+
import { validatePolicyReferences } from '../validation/policy/references.js';
|
|
21
|
+
import { validateContractPolicy } from '../validation/policy/uses.js';
|
|
22
|
+
// An accepted DomainSchema is immutable and retains its closure, so its environment is reusable.
|
|
23
|
+
const environments = new WeakMap();
|
|
24
|
+
/**
|
|
25
|
+
* Accept one callable contract in the exact environment of its owner Domain, by the one validation
|
|
26
|
+
* every contract passes wherever it is declared (`validateContract`, `validateContractPolicy`,
|
|
27
|
+
* `validateCheckBudget`): the structure of a Function declaration without `ref`, its value schemas
|
|
28
|
+
* against the owner registry, its execution modifier and admission age, and its Policy with the
|
|
29
|
+
* budget of its checks. `receiver` is the owner Node Class whose Node receives the call and which
|
|
30
|
+
* the Policy names `self`: the Class of a Function instance, whose Node carries the contract, or
|
|
31
|
+
* the owner of an instance Method. Without it, as at a Function declaration, the Policy names none.
|
|
32
|
+
* Returns the canonical definition and its validators.
|
|
33
|
+
*/
|
|
34
|
+
export function acceptCallable(input, owner, options = {}) {
|
|
35
|
+
let value;
|
|
36
|
+
try {
|
|
37
|
+
value = acceptJsonValue(input);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof JsonBoundaryError) {
|
|
41
|
+
throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const callable = parseCallableContract(value);
|
|
46
|
+
const environment = ownerEnvironment(owner);
|
|
47
|
+
const { receiver } = options;
|
|
48
|
+
if (receiver !== undefined) {
|
|
49
|
+
const resolved = environment.resolution.resolve(receiver);
|
|
50
|
+
if (resolved === undefined ||
|
|
51
|
+
resolved.ref.kind !== 'class' ||
|
|
52
|
+
resolved.kind !== 'node' ||
|
|
53
|
+
definitionRefIndex(resolved.ref) !== definitionRefIndex(receiver) ||
|
|
54
|
+
receiver.origin !== owner.origin) {
|
|
55
|
+
throw new SchemaError([
|
|
56
|
+
diagnostic('DV_REF_RESOLVES', '', 'A receiver must be a Node Class of the owner Domain.'),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const issues = [];
|
|
61
|
+
validateContract(callable, '', environment.registry, issues);
|
|
62
|
+
if (typeof callable.policy === 'object') {
|
|
63
|
+
const policies = new PolicyValidationContext(environment.resolution, environment.analysis);
|
|
64
|
+
// Normalizes the owner's named Policies, which the Policy's checks are typed against.
|
|
65
|
+
validatePolicyReferences(policies);
|
|
66
|
+
validateContractPolicy(policies, new PolicyTyping(policies), callable, '', receiver);
|
|
67
|
+
// A document measures its own check trees (`validatePolicyBudgets`); this one is in none.
|
|
68
|
+
validateCheckBudget(policies, '/policy', measureChecks(normalizePolicyChecks(callable.policy)));
|
|
69
|
+
issues.push(...policies.diagnostics);
|
|
70
|
+
}
|
|
71
|
+
if (issues.length > 0)
|
|
72
|
+
throw new SchemaError(issues);
|
|
73
|
+
const definition = deepFreeze(canonicalizeCallable(callable));
|
|
74
|
+
const validation = callableValidation(isBinaryInput(definition.input) ? definition.input.metadata : definition.input, definition.output.mode === 'binary'
|
|
75
|
+
? undefined
|
|
76
|
+
: definition.output.mode === 'value'
|
|
77
|
+
? definition.output.schema
|
|
78
|
+
: definition.output.item, environment.registry,
|
|
79
|
+
// Patterns live with these validators: carried contracts never grow a process-wide pool.
|
|
80
|
+
new Map());
|
|
81
|
+
return Object.freeze({ ...validation, definition });
|
|
82
|
+
}
|
|
83
|
+
function ownerEnvironment(owner) {
|
|
84
|
+
const cached = environments.get(owner);
|
|
85
|
+
if (cached !== undefined)
|
|
86
|
+
return cached;
|
|
87
|
+
const lookup = lookupForDomainClosure(owner);
|
|
88
|
+
const resolution = buildDomainResolutionContext(owner, lookup);
|
|
89
|
+
const environment = Object.freeze({
|
|
90
|
+
resolution,
|
|
91
|
+
analysis: analyzeDefinitions(resolution),
|
|
92
|
+
registry: createDomainValueSchemaRegistry(owner, lookup),
|
|
93
|
+
});
|
|
94
|
+
environments.set(owner, environment);
|
|
95
|
+
return environment;
|
|
96
|
+
}
|
|
@@ -9,6 +9,8 @@ export interface Resolve {
|
|
|
9
9
|
export declare const resolve: Resolve;
|
|
10
10
|
export type { SchemaDiagnosticCode as DiagnosticCode } from './diagnostic.js';
|
|
11
11
|
export { acceptDomainSchema as accept } from './admission/accept.js';
|
|
12
|
+
export { acceptCallable } from './admission/callable.js';
|
|
13
|
+
export type { AcceptedCallable } from './admission/callable.js';
|
|
12
14
|
export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDomainSchema as serialize, } from './codec/schema.js';
|
|
13
15
|
export { revisionOfDomainSchema as revision } from './canonical/revision.js';
|
|
14
16
|
export { ANNOTATION_KEYWORDS, FIELD_ROLES, callableFingerprint } from './compatibility/index.js';
|
|
@@ -22,6 +24,11 @@ export { SchemaError as Error } from './diagnostic.js';
|
|
|
22
24
|
export type { SchemaDiagnostic as Diagnostic } from './diagnostic.js';
|
|
23
25
|
export { RULES } from './rule.js';
|
|
24
26
|
export type { SchemaRule as Rule, SchemaRuleStage as RuleStage } from './rule.js';
|
|
27
|
+
export { limits } from '../language/limits.js';
|
|
28
|
+
export type { LimitKey } from '../language/limits.js';
|
|
29
|
+
export { measurePolicies } from './validation/policy/budgets.js';
|
|
30
|
+
export type { PolicyMeasures } from './validation/policy/budgets.js';
|
|
31
|
+
export type { CheckMeasure, PolicyMeasure } from '../language/policy/analysis/budgets.js';
|
|
25
32
|
export type { DomainSchemaLookup as Lookup } from './dependency/lookup.js';
|
|
26
33
|
export type { DomainSchemaHeader as Header } from './codec/header.js';
|
|
27
34
|
export type { DomainDependency } from '../addressing/index.js';
|
package/dist/v1/schema/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { resolve as resolveDomainSchema } from './resolution/domain.js';
|
|
3
3
|
export const resolve = resolveDomainSchema;
|
|
4
4
|
export { acceptDomainSchema as accept } from './admission/accept.js';
|
|
5
|
+
export { acceptCallable } from './admission/callable.js';
|
|
5
6
|
export { decodeDomainSchema as decode, encodeDomainSchema as encode, serializeDomainSchema as serialize, } from './codec/schema.js';
|
|
6
7
|
export { revisionOfDomainSchema as revision } from './canonical/revision.js';
|
|
7
8
|
export { ANNOTATION_KEYWORDS, FIELD_ROLES, callableFingerprint } from './compatibility/index.js';
|
|
@@ -11,5 +12,7 @@ export { DOMAIN_FORMAT as FORMAT, DOMAIN_VERSION as VERSION } from './model/form
|
|
|
11
12
|
export { compile, load } from '../compiled/index.js';
|
|
12
13
|
export { SchemaError as Error } from './diagnostic.js';
|
|
13
14
|
export { RULES } from './rule.js';
|
|
15
|
+
export { limits } from '../language/limits.js';
|
|
16
|
+
export { measurePolicies } from './validation/policy/budgets.js';
|
|
14
17
|
/** The versioned value-schema grammar embedded in declarations. */
|
|
15
18
|
export * as values from './validation/value-api.js';
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { CallableDefinition } from '../../language/callable/model/callable.js';
|
|
1
2
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
2
3
|
export declare function normalizeDomainSchema(schema: DomainSchemaIR): DomainSchemaIR;
|
|
4
|
+
export declare function canonicalizeCallable(value: CallableDefinition): CallableDefinition;
|
|
@@ -118,7 +118,7 @@ function canonicalizeFunction(value) {
|
|
|
118
118
|
...canonicalizeCallable(value),
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
-
function canonicalizeCallable(value) {
|
|
121
|
+
export function canonicalizeCallable(value) {
|
|
122
122
|
const base = {
|
|
123
123
|
...withDescription(value),
|
|
124
124
|
input: isBinaryInput(value.input)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { dependencyLimits } from '../../addressing/domain-dependency.js';
|
|
2
1
|
import { definitionRefIndex } from '../../addressing/ordering.js';
|
|
3
2
|
import { DOMAIN_REVISION_PATTERN } from '../../addressing/patterns.js';
|
|
3
|
+
import { limits } from '../../language/limits.js';
|
|
4
4
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
5
5
|
import { revisionOfDomainSchema } from '../canonical/revision.js';
|
|
6
6
|
import { diagnostic } from '../diagnostic.js';
|
|
@@ -11,8 +11,8 @@ export function buildDomainResolutionContext(candidate, lookup) {
|
|
|
11
11
|
const dependencies = new Map();
|
|
12
12
|
const definitions = new Map();
|
|
13
13
|
const directOrigins = new Set();
|
|
14
|
-
if (Object.keys(candidate.dependencies).length >
|
|
15
|
-
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A Domain may declare at most ${
|
|
14
|
+
if (Object.keys(candidate.dependencies).length > limits.dependency.direct) {
|
|
15
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A Domain may declare at most ${limits.dependency.direct} direct dependency aliases.`));
|
|
16
16
|
}
|
|
17
17
|
for (const [alias, dependency] of Object.entries(candidate.dependencies)) {
|
|
18
18
|
const pointer = `/dependencies/${escapePointerToken(alias)}`;
|
|
@@ -121,16 +121,16 @@ function loadDependencyClosure(candidate, dependencies, lookup, diagnostics) {
|
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
123
|
dependencies.set(nested.origin, nested);
|
|
124
|
-
if (dependencies.size + 1 >
|
|
124
|
+
if (dependencies.size + 1 > limits.dependency.closure) {
|
|
125
125
|
if (!closureLimitReported) {
|
|
126
|
-
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A dependency closure may contain at most ${
|
|
126
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', '/dependencies', `A dependency closure may contain at most ${limits.dependency.closure} Domains.`));
|
|
127
127
|
closureLimitReported = true;
|
|
128
128
|
}
|
|
129
129
|
continue;
|
|
130
130
|
}
|
|
131
131
|
const depth = frame.depth + 1;
|
|
132
|
-
if (depth >
|
|
133
|
-
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', pointer, `Dependency depth ${depth} exceeds the maximum ${
|
|
132
|
+
if (depth > limits.dependency.depth) {
|
|
133
|
+
diagnostics.push(diagnostic('DV_DEPENDENCY_LIMITS', pointer, `Dependency depth ${depth} exceeds the maximum ${limits.dependency.depth}.`));
|
|
134
134
|
continue;
|
|
135
135
|
}
|
|
136
136
|
const key = `${dependency.origin}\u0000${dependency.revision}`;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { SourceDomain } from '../../domain/model/domain.js';
|
|
2
|
+
import type { CallableValidation } from '../../domain/validation/create.js';
|
|
3
|
+
import type { JsonSchema } from '../../language/value/model/schema.js';
|
|
4
|
+
import type { ValueSchemaRegistry } from '../../language/value/reference/registry.js';
|
|
2
5
|
import type { DomainSchema } from '../model/schema.js';
|
|
6
|
+
import { type PatternPool } from '../../language/value/evaluate/preparation.js';
|
|
3
7
|
/**
|
|
4
8
|
* Resolve one accepted Schema through its exact retained source closure. Acceptance validated it
|
|
5
9
|
* against that closure, which is the only way a Schema retains one, so it is not validated again.
|
|
6
10
|
*/
|
|
7
11
|
export declare function resolve<const S extends DomainSchema>(schema: S): SourceDomain<S>;
|
|
12
|
+
export declare function callableValidation(input: JsonSchema, output: JsonSchema | undefined, registry: ValueSchemaRegistry, patterns: PatternPool): CallableValidation;
|
|
@@ -88,7 +88,7 @@ function sourceValidation(closure, registries) {
|
|
|
88
88
|
}
|
|
89
89
|
return createDomainValidation(properties, callables);
|
|
90
90
|
}
|
|
91
|
-
function callableValidation(input, output, registry, patterns) {
|
|
91
|
+
export function callableValidation(input, output, registry, patterns) {
|
|
92
92
|
const prepared = prepareValueSchema(input, registry, '', patterns);
|
|
93
93
|
if (!prepared.compiled) {
|
|
94
94
|
throw new TypeError(`Accepted callable input cannot be prepared: ${prepared.message}`);
|
package/dist/v1/schema/rule.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SchemaDiagnosticCode } from './diagnostic.js';
|
|
2
|
+
import { type LimitKey } from '../language/limits.js';
|
|
2
3
|
/**
|
|
3
4
|
* What a rule reads to decide. `structure`: the shape of the document alone. `rule`: the whole
|
|
4
5
|
* document. `context`: the document and its environment, so one document can be accepted in one
|
|
@@ -21,6 +22,8 @@ export interface SchemaRule {
|
|
|
21
22
|
readonly statement: string;
|
|
22
23
|
/** What the feature the rule guards enforces on data or on calls, where it enforces something. */
|
|
23
24
|
readonly enforces?: string;
|
|
25
|
+
/** The limits of `limits` the rule enforces, by key, where it enforces some. */
|
|
26
|
+
readonly limits?: readonly [LimitKey, ...LimitKey[]];
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Every Schema diagnostic code with the rule it reports. A code added to the DSL does not compile
|
package/dist/v1/schema/rule.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defaultLimits } from '@astrale-os/kernel-dsl/value';
|
|
2
|
+
import { limits } from '../language/limits.js';
|
|
2
3
|
import { deepFreeze } from './canonical/freeze.js';
|
|
3
4
|
const ANY = ['{/path*}'];
|
|
4
5
|
const CHECKS = [
|
|
@@ -8,6 +9,7 @@ const CHECKS = [
|
|
|
8
9
|
];
|
|
9
10
|
const DEPENDENCIES = ['/dependencies/{alias}', '/dependencies'];
|
|
10
11
|
const count = (value) => value.toLocaleString('en-US');
|
|
12
|
+
const { dependency, policy } = limits;
|
|
11
13
|
const at = (field) => [
|
|
12
14
|
`/functions/{function}${field}`,
|
|
13
15
|
`/classes/{class}/methods/{method}${field}`,
|
|
@@ -68,7 +70,7 @@ export const RULES = deepFreeze({
|
|
|
68
70
|
DV_CORE_EDGE_CONSTRAINTS: {
|
|
69
71
|
stage: 'context',
|
|
70
72
|
positions: ['/core/edges', '/core/edges/{index}', '/core/nodes/{slug}'],
|
|
71
|
-
statement: 'The Core Edges of a Schema hold the noSelf and acyclic constraints of their Classes, and every Core Node, and every Definition a Core Edge names, the Edge counts of the endpoints
|
|
73
|
+
statement: 'The Core Edges of a Schema hold the noSelf and acyclic constraints of their Classes, and every Core Node, and every Definition a Core Edge names, the Edge counts of the concrete Edge Classes whose endpoints accept it, each over its own Edges, a Definition its maximum only.',
|
|
72
74
|
},
|
|
73
75
|
DV_CORE_EDGE_KEY: {
|
|
74
76
|
stage: 'rule',
|
|
@@ -128,7 +130,8 @@ export const RULES = deepFreeze({
|
|
|
128
130
|
DV_DEPENDENCY_LIMITS: {
|
|
129
131
|
stage: 'context',
|
|
130
132
|
positions: ['/dependencies', '/dependencies/{alias}'],
|
|
131
|
-
statement:
|
|
133
|
+
statement: `A Domain has at most ${dependency.direct} direct dependencies, a closure of at most ${dependency.closure} Domains and a dependency depth of at most ${dependency.depth}.`,
|
|
134
|
+
limits: ['dependency.direct', 'dependency.closure', 'dependency.depth'],
|
|
132
135
|
},
|
|
133
136
|
DV_DEPENDENCY_REVISION_COHERENCE: {
|
|
134
137
|
stage: 'context',
|
|
@@ -248,7 +251,16 @@ export const RULES = deepFreeze({
|
|
|
248
251
|
PL_BUDGET: {
|
|
249
252
|
stage: 'rule',
|
|
250
253
|
positions: ['/policies/{policy}', ...CHECKS],
|
|
251
|
-
statement:
|
|
254
|
+
statement: `A Policy stays within its budgets once expanded: pattern depth ${policy.patternDepth}, ${policy.normalizedBranches} normalized branches, ${policy.edgePredicates} Edge predicates a branch and ${policy.referencedDomains} referenced Domains; and checks within depth ${policy.checkDepth}, ${policy.checkBranches} branches and ${policy.checkLeaves} leaves.`,
|
|
255
|
+
limits: [
|
|
256
|
+
'policy.patternDepth',
|
|
257
|
+
'policy.normalizedBranches',
|
|
258
|
+
'policy.edgePredicates',
|
|
259
|
+
'policy.referencedDomains',
|
|
260
|
+
'policy.checkDepth',
|
|
261
|
+
'policy.checkBranches',
|
|
262
|
+
'policy.checkLeaves',
|
|
263
|
+
],
|
|
252
264
|
},
|
|
253
265
|
PL_COMPOSITION: {
|
|
254
266
|
stage: 'rule',
|
|
@@ -351,6 +363,7 @@ export const RULES = deepFreeze({
|
|
|
351
363
|
stage: 'structure',
|
|
352
364
|
positions: ANY,
|
|
353
365
|
statement: 'A string, list or number is no greater than the bound of its position.',
|
|
366
|
+
limits: ['policy.variablesPerExists', 'policy.repetition'],
|
|
354
367
|
},
|
|
355
368
|
STRUCTURE_MINIMUM: {
|
|
356
369
|
stage: 'structure',
|
|
@@ -1,7 +1,37 @@
|
|
|
1
|
+
import type { ClassRef } from '../../addressing/index.js';
|
|
2
|
+
import type { CallableDefinition } from '../../language/callable/model/callable.js';
|
|
3
|
+
import type { ValueSchemaRegistry } from '../../language/value/reference/registry.js';
|
|
1
4
|
import type { SchemaDiagnostic } from '../diagnostic.js';
|
|
2
5
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
3
6
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
7
|
+
* One callable contract a Domain schema declares: where it is written, and the Node Class that
|
|
8
|
+
* receives its call, which its Policy names `self`. A contract is validated by the same rules
|
|
9
|
+
* wherever it is: in a Domain schema, at its declaration, or on the Node of a Function instance,
|
|
10
|
+
* which carries it (`acceptCallable`).
|
|
6
11
|
*/
|
|
7
|
-
export
|
|
12
|
+
export interface DeclaredContract {
|
|
13
|
+
readonly contract: CallableDefinition;
|
|
14
|
+
/** Its declaration's JSON Pointer, which its diagnostics point within. */
|
|
15
|
+
readonly pointer: string;
|
|
16
|
+
/** The owner of an instance Method; a Function and a static Method have none. */
|
|
17
|
+
readonly receiver?: ClassRef;
|
|
18
|
+
/** The Node Class and name of a Method; absent for a Function. */
|
|
19
|
+
readonly method?: {
|
|
20
|
+
readonly owner: ClassRef;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Every callable contract one Domain schema declares: each Function, then each Node Method. */
|
|
25
|
+
export declare function declaredContracts(schema: DomainSchemaIR): readonly DeclaredContract[];
|
|
26
|
+
/**
|
|
27
|
+
* Validate the ref of every Function declaration, then the value schemas, execution modifier and
|
|
28
|
+
* admission age of every declared contract.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateContracts(schema: DomainSchemaIR, registry: ValueSchemaRegistry): readonly SchemaDiagnostic[];
|
|
31
|
+
/**
|
|
32
|
+
* Validate one callable contract written at `pointer`, except its Policy (`validateContractPolicy`):
|
|
33
|
+
* its value schemas against the owner registry, its execution modifier and its admission age.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateContract(contract: CallableDefinition, pointer: string, registry: ValueSchemaRegistry, diagnostics: SchemaDiagnostic[]): void;
|
|
36
|
+
/** Validate the execution modifier and admission age of one callable contract at `pointer`. */
|
|
37
|
+
export declare function validateCallableModifiers(declaration: CallableDefinition, pointer: string, diagnostics: SchemaDiagnostic[]): void;
|
|
@@ -4,32 +4,66 @@ import { isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS, } from '../../languag
|
|
|
4
4
|
import { isBinaryInput } from '../../language/callable/model/callable.js';
|
|
5
5
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
6
6
|
import { diagnostic } from '../diagnostic.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export function validateCallables(schema) {
|
|
12
|
-
const diagnostics = [];
|
|
7
|
+
import { validateCallable } from './value.js';
|
|
8
|
+
/** Every callable contract one Domain schema declares: each Function, then each Node Method. */
|
|
9
|
+
export function declaredContracts(schema) {
|
|
10
|
+
const contracts = [];
|
|
13
11
|
for (const [name, declaration] of Object.entries(schema.functions)) {
|
|
14
|
-
|
|
15
|
-
const ref = { origin: schema.origin, kind: 'function', name };
|
|
16
|
-
if (!equalDefinitionRefs(declaration.ref, ref)) {
|
|
17
|
-
diagnostics.push(diagnostic('DV_REF_RESOLVES', `${pointer}/ref`, `Function ref must be exactly ${Key.of(ref)}.`, Key.of(declaration.ref)));
|
|
18
|
-
}
|
|
19
|
-
validateExecution(declaration, pointer, diagnostics);
|
|
20
|
-
validateAdmission(declaration, pointer, diagnostics);
|
|
12
|
+
contracts.push({ contract: declaration, pointer: `/functions/${escapePointerToken(name)}` });
|
|
21
13
|
}
|
|
22
14
|
for (const [name, declaration] of Object.entries(schema.classes)) {
|
|
23
15
|
if (declaration.kind !== 'node')
|
|
24
16
|
continue;
|
|
17
|
+
const owner = { origin: schema.origin, kind: 'class', name };
|
|
25
18
|
for (const [method, value] of Object.entries(declaration.methods)) {
|
|
26
19
|
const pointer = `/classes/${escapePointerToken(name)}/methods/${escapePointerToken(method)}`;
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
contracts.push({
|
|
21
|
+
contract: value,
|
|
22
|
+
pointer,
|
|
23
|
+
...(value.static ? {} : { receiver: owner }),
|
|
24
|
+
method: { owner, name: method },
|
|
25
|
+
});
|
|
29
26
|
}
|
|
30
27
|
}
|
|
28
|
+
return contracts;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate the ref of every Function declaration, then the value schemas, execution modifier and
|
|
32
|
+
* admission age of every declared contract.
|
|
33
|
+
*/
|
|
34
|
+
export function validateContracts(schema, registry) {
|
|
35
|
+
const diagnostics = [];
|
|
36
|
+
for (const [name, declaration] of Object.entries(schema.functions)) {
|
|
37
|
+
validateFunctionRef(schema.origin, name, declaration.ref, diagnostics);
|
|
38
|
+
}
|
|
39
|
+
for (const { contract, pointer } of declaredContracts(schema)) {
|
|
40
|
+
validateContract(contract, pointer, registry, diagnostics);
|
|
41
|
+
}
|
|
31
42
|
return Object.freeze(diagnostics);
|
|
32
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A Function declaration names itself by its ref: its own key in its own Domain. The ref belongs to
|
|
46
|
+
* the declaration, not to its contract, which a Node carries without one.
|
|
47
|
+
*/
|
|
48
|
+
function validateFunctionRef(origin, name, declared, diagnostics) {
|
|
49
|
+
const ref = { origin, kind: 'function', name };
|
|
50
|
+
if (equalDefinitionRefs(declared, ref))
|
|
51
|
+
return;
|
|
52
|
+
diagnostics.push(diagnostic('DV_REF_RESOLVES', `/functions/${escapePointerToken(name)}/ref`, `Function ref must be exactly ${Key.of(ref)}.`, Key.of(declared)));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate one callable contract written at `pointer`, except its Policy (`validateContractPolicy`):
|
|
56
|
+
* its value schemas against the owner registry, its execution modifier and its admission age.
|
|
57
|
+
*/
|
|
58
|
+
export function validateContract(contract, pointer, registry, diagnostics) {
|
|
59
|
+
validateCallable(contract, pointer, registry, diagnostics);
|
|
60
|
+
validateCallableModifiers(contract, pointer, diagnostics);
|
|
61
|
+
}
|
|
62
|
+
/** Validate the execution modifier and admission age of one callable contract at `pointer`. */
|
|
63
|
+
export function validateCallableModifiers(declaration, pointer, diagnostics) {
|
|
64
|
+
validateExecution(declaration, pointer, diagnostics);
|
|
65
|
+
validateAdmission(declaration, pointer, diagnostics);
|
|
66
|
+
}
|
|
33
67
|
/**
|
|
34
68
|
* An admission age is a whole number of seconds within the language ceiling. It has no zero
|
|
35
69
|
* spelling: an absent admission age is the one canonical form of a call admitted afresh.
|
|
@@ -3,7 +3,7 @@ import { Key } from '../../addressing/key.js';
|
|
|
3
3
|
import { compareUnicode, definitionRefIndex } from '../../addressing/ordering.js';
|
|
4
4
|
import { isMethodExecutable } from '../../language/callable/executable.js';
|
|
5
5
|
import { coveringEdgeUniqueTuples, coveringNodeUniqueTuples, edgeUniqueKeys, nodeUniqueKeys, } from '../../language/class/unique.js';
|
|
6
|
-
import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, } from '../../language/core/facts.js';
|
|
6
|
+
import { checkCoreEdgeSet, checkHeldKeys, coreEndpointsSatisfy, DOMAIN_ENDPOINT_CLASS, } from '../../language/core/facts.js';
|
|
7
7
|
import { policyObjectKey, policyObjectOrigin, projectedMemberClass, } from '../../language/policy/model/policy.js';
|
|
8
8
|
import { validateJsonInstance } from '../../language/value/evaluate/validate.js';
|
|
9
9
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
@@ -83,7 +83,9 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
|
|
|
83
83
|
continue;
|
|
84
84
|
const endpointsSatisfy = coreEndpointsSatisfy(declaration, source, target, (endpoint, accepts) => endpointSatisfies(environment, endpoint, accepts));
|
|
85
85
|
if (!endpointsSatisfy) {
|
|
86
|
-
diagnostics.push(diagnostic('DV_CORE_EDGE_CLASS', pointer, `Core Edge endpoints do not satisfy ${formatDefinitionRef(edge.class)}
|
|
86
|
+
diagnostics.push(diagnostic('DV_CORE_EDGE_CLASS', pointer, `Core Edge endpoints do not satisfy ${formatDefinitionRef(edge.class)}.${source.kind === 'domain' || target.kind === 'domain'
|
|
87
|
+
? ` The owning Domain is a ${formatDefinitionRef(DOMAIN_ENDPOINT_CLASS)} Node: the endpoint that names it must accept that Class or one it extends.`
|
|
88
|
+
: ''}`, formatDefinitionRef(edge.class)));
|
|
87
89
|
continue;
|
|
88
90
|
}
|
|
89
91
|
edges.push({ source, target, declaration, value: edge, pointer });
|
|
@@ -92,7 +94,7 @@ function resolveCoreEdges(environment, analysis, registry, nodesByRef, diagnosti
|
|
|
92
94
|
}
|
|
93
95
|
function resolveCoreEndpoint(environment, ref, pointer, nodesByRef, diagnostics) {
|
|
94
96
|
if (ref.kind === 'domain')
|
|
95
|
-
return { kind: 'domain', ref, key: 'domain' };
|
|
97
|
+
return { kind: 'domain', ref, key: 'domain', classRef: DOMAIN_ENDPOINT_CLASS };
|
|
96
98
|
const undeclared = () => {
|
|
97
99
|
const key = policyObjectKey(ref);
|
|
98
100
|
diagnostics.push(diagnostic('DV_CORE_REF', pointer, `Core endpoint ${key} does not name a declared local Definition.`, key));
|
|
@@ -122,10 +124,14 @@ function declaresProjectedMember(environment, ref) {
|
|
|
122
124
|
return Object.hasOwn(candidate.policies, ref.name);
|
|
123
125
|
return Object.hasOwn(candidate.views, ref.name);
|
|
124
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* A Core Node is admitted by its own authored Class, a projected member by the Kernel declaration
|
|
129
|
+
* Class it projects as, and the owning Domain by the Kernel `Domain` Class of its Node: the write
|
|
130
|
+
* that projects the Edge binds exactly that Node, so an Edge Class that does not accept it can
|
|
131
|
+
* never be installed (DV-CORE-EDGE-CLASS).
|
|
132
|
+
*/
|
|
125
133
|
function endpointSatisfies(environment, endpoint, accepts) {
|
|
126
|
-
|
|
127
|
-
// Its concrete Class contract is therefore admitted by the projection compiler.
|
|
128
|
-
return endpoint.kind === 'domain' || classSatisfies(environment, endpoint.classRef, accepts);
|
|
134
|
+
return classSatisfies(environment, endpoint.classRef, accepts);
|
|
129
135
|
}
|
|
130
136
|
function validateCreationProperties(definition, properties, registry, code, pointer, diagnostics) {
|
|
131
137
|
const schema = {
|
|
@@ -181,11 +187,11 @@ function validateCoreEdgeSet(environment, edges, nodesByRef, diagnostics) {
|
|
|
181
187
|
report: (issue) => diagnostics.push(coreEdgeDiagnostic(issue)),
|
|
182
188
|
});
|
|
183
189
|
}
|
|
184
|
-
/** A projected member's degree is reported at the Edge that names it. */
|
|
190
|
+
/** A projected member's degree, and the owning Domain's, is reported at the Edge that names it. */
|
|
185
191
|
function factEndpoint(endpoint, pointer) {
|
|
186
|
-
return endpoint.kind === '
|
|
187
|
-
?
|
|
188
|
-
: endpoint;
|
|
192
|
+
return endpoint.kind === 'core'
|
|
193
|
+
? endpoint
|
|
194
|
+
: { key: endpoint.key, member: { class: endpoint.classRef, pointer } };
|
|
189
195
|
}
|
|
190
196
|
function coreEdgeDiagnostic(issue) {
|
|
191
197
|
switch (issue.kind) {
|
|
@@ -2,7 +2,7 @@ import { orderAndDeduplicate } from '../../language/validation/diagnostic.js';
|
|
|
2
2
|
import { analyzeDefinitions } from '../analysis/class.js';
|
|
3
3
|
import { buildDomainResolutionContext } from '../resolution/context.js';
|
|
4
4
|
import { createValueSchemaRegistry } from '../resolution/value.registry.js';
|
|
5
|
-
import {
|
|
5
|
+
import { validateContracts } from './callable.js';
|
|
6
6
|
import { validateDefinitions } from './class.js';
|
|
7
7
|
import { validateCore } from './core.js';
|
|
8
8
|
import { validatePolicies } from './policy/validate.js';
|
|
@@ -16,7 +16,7 @@ export function validateDomainSchemaSemantics(schema, lookup) {
|
|
|
16
16
|
...context.diagnostics,
|
|
17
17
|
...validateEmbeddedValueSchemas(schema, registry),
|
|
18
18
|
...validateDefinitions(context, analysis),
|
|
19
|
-
...
|
|
19
|
+
...validateContracts(schema, registry),
|
|
20
20
|
...validatePolicies(context, analysis),
|
|
21
21
|
...validateViews(context),
|
|
22
22
|
...validateCore(context, analysis, registry),
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Value as JsonValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read an unknown value as a Domain Schema document of its exact language contract: portable JSON
|
|
5
|
+
* (`DV_JSON_VALUE`), then the structure of its format and version. Throws `SchemaError`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function structureDomainSchema(input: unknown): DomainSchemaIR;
|
|
3
8
|
/** Select the exact Domain language contract before entering its structural grammar. */
|
|
4
9
|
export declare function routeDomainSchema(input: JsonValue): DomainSchemaIR;
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
import { accept as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
|
|
1
2
|
import { SchemaError, diagnostic } from '../diagnostic.js';
|
|
2
3
|
import { DOMAIN_FORMAT, DOMAIN_VERSION } from '../model/format.js';
|
|
3
4
|
import { parseDomainSchemaStructure } from './domain.structure.js';
|
|
4
5
|
const decoders = new Map([
|
|
5
6
|
[DOMAIN_FORMAT, new Map([[DOMAIN_VERSION, parseDomainSchemaStructure]])],
|
|
6
7
|
]);
|
|
8
|
+
/**
|
|
9
|
+
* Read an unknown value as a Domain Schema document of its exact language contract: portable JSON
|
|
10
|
+
* (`DV_JSON_VALUE`), then the structure of its format and version. Throws `SchemaError`.
|
|
11
|
+
*/
|
|
12
|
+
export function structureDomainSchema(input) {
|
|
13
|
+
let admitted;
|
|
14
|
+
try {
|
|
15
|
+
admitted = acceptJsonValue(input);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof JsonBoundaryError) {
|
|
19
|
+
throw new SchemaError([diagnostic('DV_JSON_VALUE', error.pointer, error.message)]);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return routeDomainSchema(admitted);
|
|
24
|
+
}
|
|
7
25
|
/** Select the exact Domain language contract before entering its structural grammar. */
|
|
8
26
|
export function routeDomainSchema(input) {
|
|
9
27
|
if (!isJsonObject(input))
|