@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,7 +1,7 @@
|
|
|
1
1
|
import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { type ZodArray, type ZodType, type core as zodCore } from 'zod';
|
|
3
3
|
import type { PolicyRef } from '../../addressing/index.js';
|
|
4
|
-
import type { FunctionAuth } from '../../language/callable/model/callable.js';
|
|
4
|
+
import type { CallableDefinition, FunctionAuth } from '../../language/callable/model/callable.js';
|
|
5
5
|
import type { PolicyCheckExpression, PolicyPatternExpression } from '../../language/policy/model/policy.js';
|
|
6
6
|
import type { DomainSchemaIR } from '../model/schema.js';
|
|
7
7
|
/**
|
|
@@ -3641,16 +3641,16 @@ export declare const domainSchemaStructure: import("zod").ZodObject<{
|
|
|
3641
3641
|
name: string;
|
|
3642
3642
|
};
|
|
3643
3643
|
}>, unknown>>]>>;
|
|
3644
|
-
ref: import("zod").ZodObject<{
|
|
3645
|
-
origin: import("zod").ZodString;
|
|
3646
|
-
kind: import("zod").ZodLiteral<"function">;
|
|
3647
|
-
name: import("zod").ZodString;
|
|
3648
|
-
}, zodCore.$strict>;
|
|
3649
3644
|
input: import("zod").ZodUnion<readonly [import("zod").ZodObject<{
|
|
3650
3645
|
mode: import("zod").ZodLiteral<"binary">;
|
|
3651
3646
|
metadata: import("zod").ZodUnion<readonly [import("zod").ZodBoolean, import("zod").ZodRecord<import("zod").ZodString, ZodType<JsonValue, unknown, zodCore.$ZodTypeInternals<JsonValue, unknown>>>]>;
|
|
3652
3647
|
maximumBytes: import("zod").ZodNumber;
|
|
3653
3648
|
}, zodCore.$strict>, import("zod").ZodUnion<readonly [import("zod").ZodBoolean, import("zod").ZodRecord<import("zod").ZodString, ZodType<JsonValue, unknown, zodCore.$ZodTypeInternals<JsonValue, unknown>>>]>]>;
|
|
3649
|
+
ref: import("zod").ZodObject<{
|
|
3650
|
+
origin: import("zod").ZodString;
|
|
3651
|
+
kind: import("zod").ZodLiteral<"function">;
|
|
3652
|
+
name: import("zod").ZodString;
|
|
3653
|
+
}, zodCore.$strict>;
|
|
3654
3654
|
}, zodCore.$strict> & ZodType<{
|
|
3655
3655
|
description?: string | undefined;
|
|
3656
3656
|
output: {
|
|
@@ -3706,16 +3706,16 @@ export declare const domainSchemaStructure: import("zod").ZodObject<{
|
|
|
3706
3706
|
name: string;
|
|
3707
3707
|
};
|
|
3708
3708
|
}> | undefined;
|
|
3709
|
-
ref: {
|
|
3710
|
-
origin: string;
|
|
3711
|
-
kind: "function";
|
|
3712
|
-
name: string;
|
|
3713
|
-
};
|
|
3714
3709
|
input: boolean | Record<string, JsonValue> | {
|
|
3715
3710
|
mode: "binary";
|
|
3716
3711
|
metadata: boolean | Record<string, JsonValue>;
|
|
3717
3712
|
maximumBytes: number;
|
|
3718
3713
|
};
|
|
3714
|
+
ref: {
|
|
3715
|
+
origin: string;
|
|
3716
|
+
kind: "function";
|
|
3717
|
+
name: string;
|
|
3718
|
+
};
|
|
3719
3719
|
} & AuthorizedPolicy, {
|
|
3720
3720
|
description?: string | undefined;
|
|
3721
3721
|
output: {
|
|
@@ -3735,16 +3735,16 @@ export declare const domainSchemaStructure: import("zod").ZodObject<{
|
|
|
3735
3735
|
} | undefined;
|
|
3736
3736
|
auth: "anonymous" | "authenticated" | "authorized";
|
|
3737
3737
|
policy?: unknown;
|
|
3738
|
-
ref: {
|
|
3739
|
-
origin: string;
|
|
3740
|
-
kind: "function";
|
|
3741
|
-
name: string;
|
|
3742
|
-
};
|
|
3743
3738
|
input: boolean | Record<string, unknown> | {
|
|
3744
3739
|
mode: "binary";
|
|
3745
3740
|
metadata: boolean | Record<string, unknown>;
|
|
3746
3741
|
maximumBytes: number;
|
|
3747
3742
|
};
|
|
3743
|
+
ref: {
|
|
3744
|
+
origin: string;
|
|
3745
|
+
kind: "function";
|
|
3746
|
+
name: string;
|
|
3747
|
+
};
|
|
3748
3748
|
}, zodCore.$ZodTypeInternals<{
|
|
3749
3749
|
description?: string | undefined;
|
|
3750
3750
|
output: {
|
|
@@ -3800,16 +3800,16 @@ export declare const domainSchemaStructure: import("zod").ZodObject<{
|
|
|
3800
3800
|
name: string;
|
|
3801
3801
|
};
|
|
3802
3802
|
}> | undefined;
|
|
3803
|
-
ref: {
|
|
3804
|
-
origin: string;
|
|
3805
|
-
kind: "function";
|
|
3806
|
-
name: string;
|
|
3807
|
-
};
|
|
3808
3803
|
input: boolean | Record<string, JsonValue> | {
|
|
3809
3804
|
mode: "binary";
|
|
3810
3805
|
metadata: boolean | Record<string, JsonValue>;
|
|
3811
3806
|
maximumBytes: number;
|
|
3812
3807
|
};
|
|
3808
|
+
ref: {
|
|
3809
|
+
origin: string;
|
|
3810
|
+
kind: "function";
|
|
3811
|
+
name: string;
|
|
3812
|
+
};
|
|
3813
3813
|
} & AuthorizedPolicy, {
|
|
3814
3814
|
description?: string | undefined;
|
|
3815
3815
|
output: {
|
|
@@ -3829,16 +3829,16 @@ export declare const domainSchemaStructure: import("zod").ZodObject<{
|
|
|
3829
3829
|
} | undefined;
|
|
3830
3830
|
auth: "anonymous" | "authenticated" | "authorized";
|
|
3831
3831
|
policy?: unknown;
|
|
3832
|
-
ref: {
|
|
3833
|
-
origin: string;
|
|
3834
|
-
kind: "function";
|
|
3835
|
-
name: string;
|
|
3836
|
-
};
|
|
3837
3832
|
input: boolean | Record<string, unknown> | {
|
|
3838
3833
|
mode: "binary";
|
|
3839
3834
|
metadata: boolean | Record<string, unknown>;
|
|
3840
3835
|
maximumBytes: number;
|
|
3841
3836
|
};
|
|
3837
|
+
ref: {
|
|
3838
|
+
origin: string;
|
|
3839
|
+
kind: "function";
|
|
3840
|
+
name: string;
|
|
3841
|
+
};
|
|
3842
3842
|
}>>>;
|
|
3843
3843
|
policies: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
|
|
3844
3844
|
ref: import("zod").ZodObject<{
|
|
@@ -4017,4 +4017,6 @@ export declare const domainSchemaStructure: import("zod").ZodObject<{
|
|
|
4017
4017
|
}, zodCore.$strict>;
|
|
4018
4018
|
}, zodCore.$strict>;
|
|
4019
4019
|
export declare function parseDomainSchemaStructure(input: unknown): DomainSchemaIR;
|
|
4020
|
+
/** Parse the structure of one callable contract: a Function declaration without its ref. */
|
|
4021
|
+
export declare function parseCallableContract(input: unknown): CallableDefinition;
|
|
4020
4022
|
export {};
|
|
@@ -3,7 +3,7 @@ import { PropertyKey as SemanticPropertyKey } from '../../addressing/index.js';
|
|
|
3
3
|
import { CORE_SLUG_PATTERN, DEPENDENCY_ALIAS_PATTERN, DOMAIN_ORIGIN_PATTERN, DOMAIN_REVISION_PATTERN, MEMBER_NAME_PATTERN, PROPERTY_KEY_PATTERN, } from '../../addressing/patterns.js';
|
|
4
4
|
import { MEDIA_TYPE_PATTERN } from '../../language/class/model/data.js';
|
|
5
5
|
import * as metadata from '../../language/definition/metadata.js';
|
|
6
|
-
import {
|
|
6
|
+
import { limits } from '../../language/limits.js';
|
|
7
7
|
import { BLOB_VALUE_KIND, isBlobValueSchema } from '../../language/value/model/blob.js';
|
|
8
8
|
import { escapePointerToken } from '../../language/value/syntax/pointer.js';
|
|
9
9
|
import { SchemaError, diagnostic, } from '../diagnostic.js';
|
|
@@ -245,8 +245,8 @@ const policyVariableSchema = union([
|
|
|
245
245
|
strictObject({ variable: policyPatternVariableSchema, selector: policyVariableSelectorSchema }),
|
|
246
246
|
]).register(documentJsonSchema, { id: 'policy-variable' });
|
|
247
247
|
const policyRepeatSchema = strictObject({
|
|
248
|
-
min: number().int().min(0).max(
|
|
249
|
-
max: number().int().min(0).max(
|
|
248
|
+
min: number().int().min(0).max(limits.policy.repetition),
|
|
249
|
+
max: number().int().min(0).max(limits.policy.repetition),
|
|
250
250
|
}).register(documentJsonSchema, { id: 'policy-repeat' });
|
|
251
251
|
const policyPatternExpressionSchema = lazy(() => union([
|
|
252
252
|
strictObject({
|
|
@@ -260,7 +260,7 @@ const policyPatternExpressionSchema = lazy(() => union([
|
|
|
260
260
|
}),
|
|
261
261
|
strictObject({
|
|
262
262
|
exists: strictObject({
|
|
263
|
-
nodes: nonEmpty(array(policyVariableSchema).max(
|
|
263
|
+
nodes: nonEmpty(array(policyVariableSchema).max(limits.policy.variablesPerExists)),
|
|
264
264
|
where: policyPatternExpressionSchema,
|
|
265
265
|
}),
|
|
266
266
|
}),
|
|
@@ -428,13 +428,18 @@ const classDeclarationSchema = discriminatedUnion('kind', [
|
|
|
428
428
|
nodeClassSchema,
|
|
429
429
|
edgeClassSchema,
|
|
430
430
|
]).register(documentJsonSchema, { id: 'class-definition' });
|
|
431
|
-
const
|
|
432
|
-
ref: functionRefSchema,
|
|
431
|
+
const functionContractShape = {
|
|
433
432
|
...callableShape,
|
|
434
433
|
input: union([binaryInputSchema, valueFunctionInputSchema]),
|
|
434
|
+
};
|
|
435
|
+
const functionDefinitionSchema = strictObject({
|
|
436
|
+
ref: functionRefSchema,
|
|
437
|
+
...functionContractShape,
|
|
435
438
|
})
|
|
436
439
|
.refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'))
|
|
437
440
|
.register(documentJsonSchema, { id: 'function-definition', ...authorizedPolicy });
|
|
441
|
+
// A callable contract has the structure of a Function declaration without its ref, wherever it is.
|
|
442
|
+
const callableContractSchema = strictObject(functionContractShape).refine((callable) => callable.auth === 'authorized' || callable.policy === undefined, callablePolicyIssue('function'));
|
|
438
443
|
const policyExpressionSchema = union([
|
|
439
444
|
strictObject({
|
|
440
445
|
match: policyPatternExpressionSchema,
|
|
@@ -516,6 +521,17 @@ function lostProtoKeys(input, parsed, path) {
|
|
|
516
521
|
...Object.keys(parsed).flatMap((key) => lostProtoKeys(Reflect.get(input, key), Reflect.get(parsed, key), [...path, key])),
|
|
517
522
|
];
|
|
518
523
|
}
|
|
524
|
+
/** Parse the structure of one callable contract: a Function declaration without its ref. */
|
|
525
|
+
export function parseCallableContract(input) {
|
|
526
|
+
const result = callableContractSchema.safeParse(input);
|
|
527
|
+
if (!result.success) {
|
|
528
|
+
throw new SchemaError(result.error.issues.flatMap((issue) => diagnosticsForIssue(issue, input)));
|
|
529
|
+
}
|
|
530
|
+
const lost = lostProtoKeys(input, result.data, []);
|
|
531
|
+
if (lost.length > 0)
|
|
532
|
+
throw new SchemaError(lost);
|
|
533
|
+
return result.data;
|
|
534
|
+
}
|
|
519
535
|
/**
|
|
520
536
|
* The structure admits the document the model types: this parameter checks one direction, and
|
|
521
537
|
* `domain.structure.typecheck.ts` both. The brands are the model's own proofs from here on.
|
|
@@ -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
|
}
|
|
@@ -52,7 +52,8 @@ function validatePatternNode(context, pattern, pointer, scope, declarations, use
|
|
|
52
52
|
context.resolveEdge(pattern.class, `${pointer}/class`);
|
|
53
53
|
validateTerm(context, pattern.source, `${pointer}/source`, scope, uses);
|
|
54
54
|
validateTerm(context, pattern.target, `${pointer}/target`, scope, uses);
|
|
55
|
-
// The structure bounds each to an integer from 0 to
|
|
55
|
+
// The structure bounds each to an integer from 0 to `limits.policy.repetition`; only their
|
|
56
|
+
// order is left.
|
|
56
57
|
if (pattern.repeat !== undefined && pattern.repeat.max < pattern.repeat.min) {
|
|
57
58
|
context.add('PL_REPEAT', `${pointer}/repeat`, 'Repeat requires min <= max.');
|
|
58
59
|
}
|
|
@@ -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)) {
|
|
@@ -150,8 +130,8 @@ function visitChecks(validation, typing, expression, context, pointer, leaves) {
|
|
|
150
130
|
values.forEach((child, index) => visitChecks(validation, typing, child, context, `${pointer}/${key}/${index}`, leaves));
|
|
151
131
|
}
|
|
152
132
|
function selfContracts(validation, context, pointer) {
|
|
153
|
-
if (
|
|
154
|
-
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.');
|
|
155
135
|
return undefined;
|
|
156
136
|
}
|
|
157
137
|
return [contractOfNodeDefinition(context.receiver)];
|
|
@@ -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;
|
|
@@ -7,22 +7,16 @@ export function validateEmbeddedValueSchemas(schema, registry) {
|
|
|
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
|
}
|