@astrale-os/kernel-dsl 1.0.0-beta.11 → 1.0.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/v1/builder/callable/authoring.d.ts +1 -1
  2. package/dist/v1/builder/class/authoring.d.ts +6 -256
  3. package/dist/v1/builder/class/handle.d.ts +27 -0
  4. package/dist/v1/builder/class/handle.js +1 -0
  5. package/dist/v1/builder/class/input.d.ts +129 -0
  6. package/dist/v1/builder/class/input.js +1 -0
  7. package/dist/v1/builder/class/projection.d.ts +129 -0
  8. package/dist/v1/builder/class/projection.js +1 -0
  9. package/dist/v1/builder/core/authoring.d.ts +1 -1
  10. package/dist/v1/builder/index.d.ts +3 -1
  11. package/dist/v1/builder/lowering/class.d.ts +1 -1
  12. package/dist/v1/builder/lowering/schema/context.d.ts +2 -1
  13. package/dist/v1/builder/lowering/value/path.d.ts +1 -1
  14. package/dist/v1/builder/policy/authoring.d.ts +1 -1
  15. package/dist/v1/builder/schema/input.d.ts +1 -1
  16. package/dist/v1/builder/schema/projection/class.d.ts +2 -1
  17. package/dist/v1/builder/schema/projection/core-input.d.ts +2 -1
  18. package/dist/v1/builder/schema/projection/identity.d.ts +2 -1
  19. package/dist/v1/builder/schema/projection/property.d.ts +1 -1
  20. package/dist/v1/builder/value/path-metadata.d.ts +1 -1
  21. package/dist/v1/builder/value/path.d.ts +1 -1
  22. package/dist/v1/language/index.d.ts +1 -0
  23. package/dist/v1/language/index.js +1 -0
  24. package/dist/v1/language/validation/diagnostic.d.ts +1 -1
  25. package/dist/v1/language/value/inclusion/arrays.d.ts +3 -0
  26. package/dist/v1/language/value/inclusion/arrays.js +85 -0
  27. package/dist/v1/language/value/inclusion/assertions.d.ts +18 -0
  28. package/dist/v1/language/value/inclusion/assertions.js +29 -0
  29. package/dist/v1/language/value/inclusion/combinators.d.ts +3 -0
  30. package/dist/v1/language/value/inclusion/combinators.js +67 -0
  31. package/dist/v1/language/value/inclusion/equivalence.d.ts +4 -0
  32. package/dist/v1/language/value/inclusion/equivalence.js +74 -0
  33. package/dist/v1/language/value/inclusion/evidence.d.ts +34 -0
  34. package/dist/v1/language/value/inclusion/evidence.js +138 -0
  35. package/dist/v1/language/value/inclusion/includes.d.ts +17 -0
  36. package/dist/v1/language/value/inclusion/includes.js +25 -719
  37. package/dist/v1/language/value/inclusion/keywords.d.ts +5 -0
  38. package/dist/v1/language/value/inclusion/keywords.js +31 -0
  39. package/dist/v1/language/value/inclusion/kinds.d.ts +17 -0
  40. package/dist/v1/language/value/inclusion/kinds.js +57 -0
  41. package/dist/v1/language/value/inclusion/location.d.ts +24 -0
  42. package/dist/v1/language/value/inclusion/location.js +63 -0
  43. package/dist/v1/language/value/inclusion/numbers.d.ts +3 -0
  44. package/dist/v1/language/value/inclusion/numbers.js +41 -0
  45. package/dist/v1/language/value/inclusion/objects.d.ts +3 -0
  46. package/dist/v1/language/value/inclusion/objects.js +190 -0
  47. package/dist/v1/language/value/inclusion/strings.d.ts +3 -0
  48. package/dist/v1/language/value/inclusion/strings.js +29 -0
  49. package/dist/v1/language/value/inclusion/types.d.ts +3 -0
  50. package/dist/v1/language/value/inclusion/types.js +11 -0
  51. package/dist/v1/language/value/index.d.ts +1 -1
  52. package/dist/v1/language/value/index.js +1 -1
  53. package/dist/v1/language/value/ordering.d.ts +9 -0
  54. package/dist/v1/language/value/ordering.js +72 -9
  55. package/dist/v1/schema/validation/value-api.d.ts +9 -0
  56. package/dist/v1/schema/validation/value-api.js +23 -1
  57. package/dist/value/canonical.d.ts +3 -2
  58. package/dist/value/canonical.js +30 -6
  59. package/dist/value/decode.d.ts +7 -0
  60. package/dist/value/decode.js +159 -9
  61. package/dist/value/index.d.ts +1 -1
  62. package/dist/value/json.d.ts +1 -1
  63. package/dist/value/json.js +1 -1
  64. package/dist/value/types.d.ts +6 -0
  65. package/dist/value/unicode.d.ts +2 -1
  66. package/dist/value/unicode.js +2 -0
  67. package/package.json +1 -1
@@ -6,7 +6,8 @@ import type { PolicyObjectRef } from '../../../language/index.js';
6
6
  import type { ValueSchemaRegistry } from '../../../language/value/reference/registry.js';
7
7
  import type { DomainSchema, DomainSchemaIR } from '../../../schema/model/schema.js';
8
8
  import type { Function, PolicyObjectInput } from '../../callable/authoring.js';
9
- import type { Class, ClassReferenceInput } from '../../class/authoring.js';
9
+ import type { Class } from '../../class/handle.js';
10
+ import type { ClassReferenceInput } from '../../class/input.js';
10
11
  import type { CoreEndpoint, CoreNode } from '../../core/authoring.js';
11
12
  import type { Policy } from '../../policy/authoring.js';
12
13
  import type { View } from '../../view/authoring.js';
@@ -1,3 +1,3 @@
1
- import type { NodeClassReferenceInput } from '../../class/authoring.js';
1
+ import type { NodeClassReferenceInput } from '../../class/input.js';
2
2
  import type { ZodValueSchemaOverrideContext } from './zod.js';
3
3
  export declare function lowerPathSchema(context: ZodValueSchemaOverrideContext, resolveAccepts: (inputs: readonly NodeClassReferenceInput[], pointer: string) => readonly unknown[], pointer: string): void;
@@ -1,7 +1,7 @@
1
1
  import type { ClassRef } from '../../addressing/index.js';
2
2
  import type { PolicyCheckExpression, PolicyPatternExpression, PolicyPatternTerm as CanonicalPolicyPatternTerm, PolicyObjectRef } from '../../language/policy/model/policy.js';
3
3
  import type { PolicyObjectInput } from '../callable/authoring.js';
4
- import type { EdgeClassReferenceInput, NodeClassReferenceInput } from '../class/authoring.js';
4
+ import type { EdgeClassReferenceInput, NodeClassReferenceInput } from '../class/input.js';
5
5
  import type { AuthoringValue } from '../schema/handle.js';
6
6
  import type { Lazy } from '../schema/lazy.js';
7
7
  export interface PolicyEdgeInput {
@@ -1,6 +1,6 @@
1
1
  import type { DomainSchema } from '../../schema/model/schema.js';
2
2
  import type { Function } from '../callable/authoring.js';
3
- import type { Class } from '../class/authoring.js';
3
+ import type { Class } from '../class/handle.js';
4
4
  import type { CoreInput } from '../core/authoring.js';
5
5
  import type { Policy } from '../policy/authoring.js';
6
6
  import type { View } from '../view/authoring.js';
@@ -1,5 +1,6 @@
1
1
  import type * as language from '../../../language/index.js';
2
- import type { NodeClass, DirectedEdgeClass, UndirectedEdgeClass, ClassReferenceProjection, Class, ProjectionOfClass } from '../../class/authoring.js';
2
+ import type { NodeClass, DirectedEdgeClass, UndirectedEdgeClass, Class, ProjectionOfClass } from '../../class/handle.js';
3
+ import type { ClassReferenceProjection } from '../../class/projection.js';
3
4
  import type { DomainSchemaInput } from '../input.js';
4
5
  import type { OwnInstanceMethodProjectionsOf, OwnStaticMethodProjectionsOf } from './callable.js';
5
6
  import type { AbstractOf, ConstraintsOf, ResolveLazy, LocalNameOfReference, ClassesInputOf, LocalNameOf } from './identity.js';
@@ -1,6 +1,7 @@
1
1
  import type * as language from '../../../language/index.js';
2
2
  import type { BlobReceipt } from '../../../language/value/model/blob.js';
3
- import type { Class, AuthoredPropertyProjection } from '../../class/authoring.js';
3
+ import type { Class } from '../../class/handle.js';
4
+ import type { AuthoredPropertyProjection } from '../../class/projection.js';
4
5
  import type { ResolveLazy, IsMultiple } from './identity.js';
5
6
  import type { EffectivePropertyEntriesOfClass } from './property.js';
6
7
  /** Derive required, optional and forbidden Core Property inputs by name. */
@@ -1,4 +1,5 @@
1
- import type { NodeClass, DirectedEdgeClass, UndirectedEdgeClass, ClassReferenceProjection, ClassReferenceProjectionOf, Class, ClassIdentityProjectionOf, ClassOwnerProjectionOf, ClassOwnerProjection, ClassIdentityProjection } from '../../class/authoring.js';
1
+ import type { NodeClass, DirectedEdgeClass, UndirectedEdgeClass, Class } from '../../class/handle.js';
2
+ import type { ClassReferenceProjection, ClassReferenceProjectionOf, ClassIdentityProjectionOf, ClassOwnerProjectionOf, ClassOwnerProjection, ClassIdentityProjection } from '../../class/projection.js';
2
3
  import type { DomainSchemaInput } from '../input.js';
3
4
  /** Extract an authored abstract modifier with its default. */
4
5
  export type AbstractOf<Config> = Config extends {
@@ -1,5 +1,5 @@
1
1
  import type * as language from '../../../language/index.js';
2
- import type { AuthoredPropertyProjection } from '../../class/authoring.js';
2
+ import type { AuthoredPropertyProjection } from '../../class/projection.js';
3
3
  import type { DomainSchemaInput } from '../input.js';
4
4
  import type { RecordField, ClassInputOf, OwnerNameOf } from './identity.js';
5
5
  import type { PropertyProjection } from './model.js';
@@ -1,4 +1,4 @@
1
1
  import type { z } from 'zod';
2
- import type { NodeClassReferenceInput } from '../class/authoring.js';
2
+ import type { NodeClassReferenceInput } from '../class/input.js';
3
3
  export declare function rememberPath(schema: z.ZodType, accepts: readonly NodeClassReferenceInput[]): void;
4
4
  export declare function pathAccepts(schema: z.ZodType): readonly NodeClassReferenceInput[] | undefined;
@@ -1,5 +1,5 @@
1
1
  import { type ZodString } from 'zod';
2
- import type { NodeClassReferenceInput } from '../class/authoring.js';
2
+ import type { NodeClassReferenceInput } from '../class/input.js';
3
3
  declare const PATH_ACCEPTS: unique symbol;
4
4
  export type PathAuthoring<Accepts extends readonly NodeClassReferenceInput[] = readonly NodeClassReferenceInput[]> = ZodString & {
5
5
  readonly [PATH_ACCEPTS]: Accepts;
@@ -7,6 +7,7 @@ export type { JsonSchema as ValueSchema, JsonSchemaObject as ValueSchemaObject,
7
7
  export type { OrderingKind } from './value/ordering.js';
8
8
  export type { ValueProgram } from './value/compile/model.js';
9
9
  export { valueProgramIncludes } from './value/inclusion/includes.js';
10
+ export { isInstantValue } from './value/model/instant.js';
10
11
  export { stateMachine } from './state/index.js';
11
12
  export type { EventOf, StateMachine, StateOf, StateTopology } from './state/index.js';
12
13
  export type * from './callable/model/callable.js';
@@ -2,6 +2,7 @@ export { admissionInputFields, isAdmissionMaxAge, MAXIMUM_ADMISSION_AGE_SECONDS,
2
2
  export { isMethodExecutable } from './callable/executable.js';
3
3
  export { isBinaryInput } from './callable/model/callable.js';
4
4
  export { valueProgramIncludes } from './value/inclusion/includes.js';
5
+ export { isInstantValue } from './value/model/instant.js';
5
6
  export { stateMachine } from './state/index.js';
6
7
  export { admitsForeignSubclass, inheritanceOf } from './class/inheritance.js';
7
8
  export { edgeLifetimes, lifetimeScope } from './class/lifetime.js';
@@ -1,4 +1,4 @@
1
- export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'DOMAIN_VALIDATION_TARGET_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND';
1
+ export type DiagnosticCode = 'CALLABLE_INPUT_FIELD_UNKNOWN' | 'DOMAIN_CLASS_ABSTRACT' | 'DOMAIN_PROPERTY_INPUT_INVALID' | 'DOMAIN_PROPERTY_REQUIRED' | 'DOMAIN_PROPERTY_UNKNOWN' | 'DOMAIN_VALIDATION_TARGET_UNKNOWN' | 'VALUE_SCHEMA_INSTANCE_INVALID' | 'VS_DIALECT' | 'VS_DYNAMIC_FORBIDDEN' | 'VS_KEYWORD_CLOSED' | 'VS_META_SCHEMA' | 'VS_NUMBER_DOMAIN' | 'VS_PATH_EXTENSION' | 'VS_RECURSION_PRODUCTIVE' | 'VS_REFERENCE_FORM' | 'VS_REFERENCE_RESOLUTION' | 'VS_REGEX_PORTABLE' | 'VS_RESOURCE_ID' | 'VS_VALUE_KIND' | 'VS_VALUE_KIND_JSON';
2
2
  export interface Diagnostic<out Code extends DiagnosticCode = DiagnosticCode> {
3
3
  readonly code: Code;
4
4
  readonly pointer: string;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for the array size, uniqueness, item and `contains` keywords. */
3
+ export declare const arrayRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,85 @@
1
+ import { has } from './evidence.js';
2
+ import { isTop } from './keywords.js';
3
+ import { ARRAY } from './kinds.js';
4
+ import { childOf, childrenOf, schemaOf } from './location.js';
5
+ /** Rules for the array size, uniqueness, item and `contains` keywords. */
6
+ export const arrayRules = new Map([
7
+ [
8
+ 'maxItems',
9
+ ({ value, evidence }) => typeof value !== 'number' ||
10
+ !has(evidence, ARRAY) ||
11
+ (evidence.maxItems !== undefined && evidence.maxItems <= value),
12
+ ],
13
+ [
14
+ 'minItems',
15
+ ({ value, evidence }) => typeof value !== 'number' ||
16
+ !has(evidence, ARRAY) ||
17
+ value <= 0 ||
18
+ evidence.minItems >= value,
19
+ ],
20
+ [
21
+ 'uniqueItems',
22
+ ({ value, evidence }) => value !== true ||
23
+ !has(evidence, ARRAY) ||
24
+ evidence.uniqueItems ||
25
+ (evidence.maxItems !== undefined && evidence.maxItems <= 1),
26
+ ],
27
+ ['prefixItems', itemsHold],
28
+ ['items', itemsHold],
29
+ ['contains', containsHolds],
30
+ ['minContains', containsHolds],
31
+ ['maxContains', containsHolds],
32
+ ]);
33
+ function itemsHold({ inclusion, outer, schema, evidence }) {
34
+ if (!has(evidence, ARRAY))
35
+ return true;
36
+ const prefix = childrenOf(outer, 'prefixItems');
37
+ const rest = Object.hasOwn(schema, 'items') ? childOf(outer, 'items') : undefined;
38
+ const width = Math.max(prefix.length, ...evidence.conjuncts.map((conjunct) => childrenOf(conjunct, 'prefixItems').length));
39
+ // Every index at or beyond `width` meets `items` on both sides.
40
+ for (let index = 0; index <= width; index++) {
41
+ const required = index < prefix.length ? prefix[index] : rest;
42
+ if (required === undefined || isTop(schemaOf(required)))
43
+ continue;
44
+ if (evidence.maxItems !== undefined && evidence.maxItems <= index)
45
+ continue;
46
+ const admitted = evidence.conjuncts.flatMap((conjunct) => {
47
+ const own = childrenOf(conjunct, 'prefixItems');
48
+ if (index < own.length)
49
+ return [own[index]];
50
+ const items = childOf(conjunct, 'items');
51
+ return items === undefined ? [] : [items];
52
+ });
53
+ if (!admitted.some((constraint) => inclusion.includes(required, constraint)))
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+ function containsHolds({ inclusion, outer, schema, evidence }) {
59
+ const contains = childOf(outer, 'contains');
60
+ if (contains === undefined || !has(evidence, ARRAY))
61
+ return true;
62
+ const { minimum, maximum } = containsCounts(schema);
63
+ const ownContains = evidence.conjuncts.flatMap((conjunct) => {
64
+ const own = childOf(conjunct, 'contains');
65
+ if (own === undefined)
66
+ return [];
67
+ const conjunctSchema = schemaOf(conjunct);
68
+ return [{ at: own, ...containsCounts(conjunctSchema) }];
69
+ });
70
+ // Every inner match is an outer match, so outer counts at least as many.
71
+ const atLeast = minimum <= 0 ||
72
+ ownContains.some((own) => own.minimum >= minimum && inclusion.includes(contains, own.at));
73
+ // Every outer match is an inner match, so outer counts at most as many.
74
+ const atMost = maximum === Number.POSITIVE_INFINITY ||
75
+ (evidence.maxItems !== undefined && evidence.maxItems <= maximum) ||
76
+ ownContains.some((own) => own.maximum <= maximum && inclusion.includes(own.at, contains));
77
+ return atLeast && atMost;
78
+ }
79
+ /** The match counts `contains` requires, with the evaluator's defaults. */
80
+ function containsCounts(schema) {
81
+ return {
82
+ minimum: typeof schema.minContains === 'number' ? schema.minContains : 1,
83
+ maximum: typeof schema.maxContains === 'number' ? schema.maxContains : Number.POSITIVE_INFINITY,
84
+ };
85
+ }
@@ -0,0 +1,18 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { Evidence } from './evidence.js';
3
+ import type { Inclusion } from './includes.js';
4
+ import type { At } from './location.js';
5
+ /** One keyword of `outer`, to be proved from the evidence of `inner`. */
6
+ export interface Assertion {
7
+ readonly inclusion: Inclusion;
8
+ readonly keyword: string;
9
+ readonly value: JsonValue | undefined;
10
+ readonly outer: At;
11
+ readonly schema: Readonly<Record<string, JsonValue>>;
12
+ readonly inner: At;
13
+ readonly evidence: Evidence;
14
+ }
15
+ /** Whether one outer keyword holds for every value the inner evidence admits. */
16
+ export type Rule = (assertion: Assertion) => boolean;
17
+ /** Whether every keyword of the outer `schema` holds for the values `inner` admits. */
18
+ export declare function assertionsHold(inclusion: Inclusion, outer: At, schema: Readonly<Record<string, JsonValue>>, inner: At, evidence: Evidence): boolean;
@@ -0,0 +1,29 @@
1
+ import { arrayRules } from './arrays.js';
2
+ import { combinatorRules } from './combinators.js';
3
+ import { INERT } from './keywords.js';
4
+ import { numberRules } from './numbers.js';
5
+ import { objectRules } from './objects.js';
6
+ import { stringRules } from './strings.js';
7
+ import { typeRules } from './types.js';
8
+ const RULES = new Map([
9
+ ...combinatorRules,
10
+ ...typeRules,
11
+ ...numberRules,
12
+ ...stringRules,
13
+ ...arrayRules,
14
+ ...objectRules,
15
+ ]);
16
+ /** Whether every keyword of the outer `schema` holds for the values `inner` admits. */
17
+ export function assertionsHold(inclusion, outer, schema, inner, evidence) {
18
+ // Annotation-dependent keywords are decided only by equivalence.
19
+ if (Object.hasOwn(schema, 'unevaluatedProperties'))
20
+ return false;
21
+ if (Object.hasOwn(schema, 'unevaluatedItems'))
22
+ return false;
23
+ return Object.keys(schema).every((keyword) => {
24
+ const rule = RULES.get(keyword);
25
+ if (rule === undefined)
26
+ return INERT.has(keyword);
27
+ return rule({ inclusion, keyword, value: schema[keyword], outer, schema, inner, evidence });
28
+ });
29
+ }
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './assertions.js';
2
+ /** Rules for `$ref`, the boolean combinators and the conditional keywords. */
3
+ export declare const combinatorRules: ReadonlyMap<string, Rule>;
@@ -0,0 +1,67 @@
1
+ import { equivalent } from './equivalence.js';
2
+ import { childOf, childrenOf, referenceOf } from './location.js';
3
+ /** Rules for `$ref`, the boolean combinators and the conditional keywords. */
4
+ export const combinatorRules = new Map([
5
+ [
6
+ '$ref',
7
+ ({ inclusion, outer, inner }) => {
8
+ const target = referenceOf(outer);
9
+ return target !== undefined && inclusion.includes(target, inner);
10
+ },
11
+ ],
12
+ [
13
+ 'allOf',
14
+ ({ inclusion, outer, inner }) => childrenOf(outer, 'allOf').every((branch) => inclusion.includes(branch, inner)),
15
+ ],
16
+ [
17
+ 'anyOf',
18
+ ({ inclusion, outer, inner }) => childrenOf(outer, 'anyOf').some((branch) => inclusion.includes(branch, inner)),
19
+ ],
20
+ ['oneOf', () => false],
21
+ [
22
+ 'not',
23
+ ({ inclusion, outer, evidence }) => {
24
+ const negated = childOf(outer, 'not');
25
+ return negated !== undefined && excludes(inclusion, negated, evidence);
26
+ },
27
+ ],
28
+ ['if', conditionalHolds],
29
+ ['then', conditionalHolds],
30
+ ['else', conditionalHolds],
31
+ ]);
32
+ /** Whether no value admitted by `evidence` satisfies `negated`. */
33
+ function excludes(inclusion, negated, evidence) {
34
+ if ((inclusion.evidence(negated).kinds & evidence.kinds) === 0)
35
+ return true;
36
+ return evidence.conjuncts.some((conjunct) => {
37
+ const own = childOf(conjunct, 'not');
38
+ return own !== undefined && inclusion.includes(own, negated);
39
+ });
40
+ }
41
+ function conditionalHolds({ inclusion, outer, inner, evidence }) {
42
+ const condition = childOf(outer, 'if');
43
+ if (condition === undefined)
44
+ return true;
45
+ const then = childOf(outer, 'then');
46
+ const otherwise = childOf(outer, 'else');
47
+ const thenHolds = (fallback) => then === undefined ||
48
+ inclusion.includes(then, inner) ||
49
+ (fallback !== undefined && inclusion.includes(then, fallback));
50
+ const elseHolds = (fallback) => otherwise === undefined ||
51
+ inclusion.includes(otherwise, inner) ||
52
+ (fallback !== undefined && inclusion.includes(otherwise, fallback));
53
+ if (thenHolds() && elseHolds())
54
+ return true;
55
+ for (const conjunct of evidence.conjuncts) {
56
+ const own = childOf(conjunct, 'if');
57
+ if (own === undefined || !equivalent(inclusion, own, condition))
58
+ continue;
59
+ if (thenHolds(childOf(conjunct, 'then')) && elseHolds(childOf(conjunct, 'else')))
60
+ return true;
61
+ }
62
+ if (inclusion.includes(condition, inner))
63
+ return thenHolds();
64
+ if (excludes(inclusion, condition, evidence))
65
+ return elseHolds();
66
+ return false;
67
+ }
@@ -0,0 +1,4 @@
1
+ import type { Inclusion } from './includes.js';
2
+ import type { At } from './location.js';
3
+ /** Whether the two locations are bisimilar: equal assertions over paired subschemas. */
4
+ export declare function equivalent(inclusion: Inclusion, left: At, right: At): boolean;
@@ -0,0 +1,74 @@
1
+ import { jsonEqual } from '../evaluate/assertions.js';
2
+ import { INERT } from './keywords.js';
3
+ import { at, referenceOf, schemaOf } from './location.js';
4
+ /** Keywords whose subschemas the equivalence check compares positionally. */
5
+ const SUBSCHEMA_KEYWORDS = new Set([
6
+ 'additionalProperties',
7
+ 'allOf',
8
+ 'anyOf',
9
+ 'contains',
10
+ 'dependentSchemas',
11
+ 'else',
12
+ 'if',
13
+ 'items',
14
+ 'not',
15
+ 'oneOf',
16
+ 'patternProperties',
17
+ 'prefixItems',
18
+ 'properties',
19
+ 'propertyNames',
20
+ 'then',
21
+ 'unevaluatedItems',
22
+ 'unevaluatedProperties',
23
+ ]);
24
+ /** Whether the two locations are bisimilar: equal assertions over paired subschemas. */
25
+ export function equivalent(inclusion, left, right) {
26
+ const pending = [[left, right]];
27
+ const seen = new Set();
28
+ while (pending.length > 0) {
29
+ const [first, second] = pending.pop();
30
+ const key = inclusion.pair(first, second);
31
+ if (seen.has(key))
32
+ continue;
33
+ seen.add(key);
34
+ const a = schemaOf(first);
35
+ const b = schemaOf(second);
36
+ if (typeof a === 'boolean' || typeof b === 'boolean') {
37
+ if (a !== b)
38
+ return false;
39
+ continue;
40
+ }
41
+ const assertions = assertionKeys(a);
42
+ if (assertions.length !== assertionKeys(b).length)
43
+ return false;
44
+ for (const keyword of assertions) {
45
+ if (!Object.hasOwn(b, keyword) || !jsonEqual(a[keyword], b[keyword]))
46
+ return false;
47
+ }
48
+ const firstChildren = subschemaChildren(first);
49
+ const secondChildren = subschemaChildren(second);
50
+ if (firstChildren.size !== secondChildren.size)
51
+ return false;
52
+ for (const [path, index] of firstChildren) {
53
+ const other = secondChildren.get(path);
54
+ if (other === undefined)
55
+ return false;
56
+ pending.push([at(first.program, index), at(second.program, other)]);
57
+ }
58
+ const firstReference = referenceOf(first);
59
+ const secondReference = referenceOf(second);
60
+ if ((firstReference === undefined) !== (secondReference === undefined))
61
+ return false;
62
+ if (firstReference !== undefined)
63
+ pending.push([firstReference, secondReference]);
64
+ }
65
+ return true;
66
+ }
67
+ function assertionKeys(schema) {
68
+ return Object.keys(schema).filter((keyword) => !INERT.has(keyword) && !SUBSCHEMA_KEYWORDS.has(keyword) && keyword !== '$ref');
69
+ }
70
+ /** Subschema children keyed by their keyword path, without `$defs` or inert annotations. */
71
+ function subschemaChildren(location) {
72
+ const children = location.program.nodes[location.index]?.children ?? {};
73
+ return new Map(Object.entries(children).filter(([path]) => SUBSCHEMA_KEYWORDS.has(path.split('/', 1)[0].replaceAll('~1', '/').replaceAll('~0', '~'))));
74
+ }
@@ -0,0 +1,34 @@
1
+ import type { Value as JsonValue } from '@astrale-os/kernel-dsl/value';
2
+ import type { At } from './location.js';
3
+ /** One numeric bound, inclusive unless `exclusive`. */
4
+ export interface Bound {
5
+ readonly value: number;
6
+ readonly exclusive: boolean;
7
+ }
8
+ /** Over-approximation of the values one location admits, collected from its conjuncts. */
9
+ export interface Evidence {
10
+ /** The location, its `$ref` targets and its `allOf` branches, transitively. */
11
+ readonly conjuncts: readonly At[];
12
+ readonly kinds: number;
13
+ /** A finite superset of the admitted values, when one is known. */
14
+ readonly values?: readonly JsonValue[];
15
+ readonly upper?: Bound;
16
+ readonly lower?: Bound;
17
+ readonly multipleOf: readonly number[];
18
+ readonly maxLength?: number;
19
+ readonly minLength: number;
20
+ readonly patterns: ReadonlySet<string>;
21
+ readonly fullMatches: ReadonlySet<string>;
22
+ readonly maxItems?: number;
23
+ readonly minItems: number;
24
+ readonly uniqueItems: boolean;
25
+ readonly maxProperties?: number;
26
+ readonly minProperties: number;
27
+ readonly required: ReadonlySet<string>;
28
+ readonly dependentRequired: ReadonlyMap<string, ReadonlySet<string>>;
29
+ readonly valueKinds: ReadonlySet<string>;
30
+ }
31
+ /** Whether `evidence` may admit a value of one of `kinds`. */
32
+ export declare function has(evidence: Evidence, kinds: number): boolean;
33
+ /** The evidence the conjuncts of `location` carry together. */
34
+ export declare function evidenceOf(location: At): Evidence;
@@ -0,0 +1,138 @@
1
+ import { jsonEqual } from '../evaluate/assertions.js';
2
+ import { isValueObject as isJsonObject } from '../model/object.js';
3
+ import { ANY, OBJECT, STRING, finiteKinds, kindOf, typeMask } from './kinds.js';
4
+ import { conjunctsOf, schemaOf } from './location.js';
5
+ /** Whether `evidence` may admit a value of one of `kinds`. */
6
+ export function has(evidence, kinds) {
7
+ return (evidence.kinds & kinds) !== 0;
8
+ }
9
+ /** The evidence the conjuncts of `location` carry together. */
10
+ export function evidenceOf(location) {
11
+ const conjuncts = conjunctsOf(location);
12
+ let kinds = ANY;
13
+ let values;
14
+ let upper;
15
+ let lower;
16
+ const multipleOf = [];
17
+ let maxLength;
18
+ let minLength = 0;
19
+ const patterns = new Set();
20
+ const fullMatches = new Set();
21
+ let maxItems;
22
+ let minItems = 0;
23
+ let uniqueItems = false;
24
+ let maxProperties;
25
+ let minProperties = 0;
26
+ const required = new Set();
27
+ const dependentRequired = new Map();
28
+ const valueKinds = new Set();
29
+ const narrow = (candidates) => {
30
+ values =
31
+ values === undefined
32
+ ? [...candidates]
33
+ : values.filter((value) => candidates.some((candidate) => jsonEqual(value, candidate)));
34
+ };
35
+ for (const conjunct of conjuncts) {
36
+ const schema = schemaOf(conjunct);
37
+ if (schema === false)
38
+ kinds = 0;
39
+ if (!isJsonObject(schema))
40
+ continue;
41
+ kinds &= typeMask(schema.type);
42
+ if (Object.hasOwn(schema, 'const')) {
43
+ narrow([schema.const]);
44
+ kinds &= kindOf(schema.const);
45
+ }
46
+ if (Array.isArray(schema.enum)) {
47
+ narrow(schema.enum);
48
+ kinds &= schema.enum.reduce((mask, value) => mask | kindOf(value), 0);
49
+ }
50
+ const valueKind = schema['x-astrale-value-kind'];
51
+ if (typeof valueKind === 'string') {
52
+ valueKinds.add(valueKind);
53
+ if (valueKind === 'instant')
54
+ kinds &= STRING;
55
+ if (valueKind === 'blob')
56
+ kinds &= OBJECT;
57
+ }
58
+ upper = tighter(upper, schema.maximum, false, (a, b) => a < b);
59
+ upper = tighter(upper, schema.exclusiveMaximum, true, (a, b) => a < b);
60
+ lower = tighter(lower, schema.minimum, false, (a, b) => a > b);
61
+ lower = tighter(lower, schema.exclusiveMinimum, true, (a, b) => a > b);
62
+ if (typeof schema.multipleOf === 'number')
63
+ multipleOf.push(schema.multipleOf);
64
+ if (typeof schema.maxLength === 'number')
65
+ maxLength = min(maxLength, schema.maxLength);
66
+ if (typeof schema.minLength === 'number')
67
+ minLength = Math.max(minLength, schema.minLength);
68
+ if (typeof schema.pattern === 'string')
69
+ patterns.add(schema.pattern);
70
+ if (typeof schema.fullMatch === 'string')
71
+ fullMatches.add(schema.fullMatch);
72
+ if (typeof schema.maxItems === 'number')
73
+ maxItems = min(maxItems, schema.maxItems);
74
+ if (typeof schema.minItems === 'number')
75
+ minItems = Math.max(minItems, schema.minItems);
76
+ if (schema.uniqueItems === true)
77
+ uniqueItems = true;
78
+ if (typeof schema.maxProperties === 'number') {
79
+ maxProperties = min(maxProperties, schema.maxProperties);
80
+ }
81
+ if (typeof schema.minProperties === 'number') {
82
+ minProperties = Math.max(minProperties, schema.minProperties);
83
+ }
84
+ if (Array.isArray(schema.required)) {
85
+ for (const name of schema.required)
86
+ if (typeof name === 'string')
87
+ required.add(name);
88
+ }
89
+ if (isJsonObject(schema.dependentRequired)) {
90
+ for (const [name, dependencies] of Object.entries(schema.dependentRequired)) {
91
+ if (!Array.isArray(dependencies))
92
+ continue;
93
+ const known = dependentRequired.get(name) ?? new Set();
94
+ for (const dependency of dependencies) {
95
+ if (typeof dependency === 'string')
96
+ known.add(dependency);
97
+ }
98
+ dependentRequired.set(name, known);
99
+ }
100
+ }
101
+ }
102
+ const finite = values === undefined
103
+ ? finiteKinds(kinds)
104
+ : values.filter((value) => (kindOf(value) & kinds) !== 0);
105
+ return Object.freeze({
106
+ conjuncts,
107
+ kinds,
108
+ ...(finite === undefined ? {} : { values: finite }),
109
+ ...(upper === undefined ? {} : { upper }),
110
+ ...(lower === undefined ? {} : { lower }),
111
+ multipleOf,
112
+ ...(maxLength === undefined ? {} : { maxLength }),
113
+ minLength,
114
+ patterns,
115
+ fullMatches,
116
+ ...(maxItems === undefined ? {} : { maxItems }),
117
+ minItems,
118
+ uniqueItems,
119
+ ...(maxProperties === undefined ? {} : { maxProperties }),
120
+ minProperties,
121
+ required,
122
+ dependentRequired,
123
+ valueKinds,
124
+ });
125
+ }
126
+ function tighter(current, value, exclusive, stricter) {
127
+ if (typeof value !== 'number')
128
+ return current;
129
+ if (current === undefined ||
130
+ stricter(value, current.value) ||
131
+ (value === current.value && exclusive)) {
132
+ return { value, exclusive };
133
+ }
134
+ return current;
135
+ }
136
+ function min(current, value) {
137
+ return current === undefined ? value : Math.min(current, value);
138
+ }
@@ -1,4 +1,6 @@
1
1
  import type { ValueProgram } from '../compile/model.js';
2
+ import type { Evidence } from './evidence.js';
3
+ import type { At } from './location.js';
2
4
  /**
3
5
  * Whether `outer` admits every value `inner` admits, under the one V1 evaluator.
4
6
  *
@@ -7,3 +9,18 @@ import type { ValueProgram } from '../compile/model.js';
7
9
  * `inner`; a finite `inner` is decided exactly by evaluating its candidates.
8
10
  */
9
11
  export declare function valueProgramIncludes(outer: ValueProgram, inner: ValueProgram): boolean;
12
+ /** One inclusion question: the decided pairs, the pairs being decided and the evidence cache. */
13
+ export declare class Inclusion {
14
+ private readonly programs;
15
+ private readonly decided;
16
+ private readonly pending;
17
+ private readonly evidenceByAt;
18
+ /** Whether `outer` admits every value `inner` admits; a cyclic premise proves nothing. */
19
+ includes(outer: At, inner: At): boolean;
20
+ /** The evidence of one location, computed once per question. */
21
+ evidence(location: At): Evidence;
22
+ /** A key identifying an ordered pair of locations within this question. */
23
+ pair(outer: At, inner: At): string;
24
+ private decide;
25
+ private location;
26
+ }