@json-schema-engine/compiler 0.0.1

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/emit.d.ts +52 -0
  4. package/dist/emit.d.ts.map +1 -0
  5. package/dist/emit.js +166 -0
  6. package/dist/emit.js.map +1 -0
  7. package/dist/explain.d.ts +28 -0
  8. package/dist/explain.d.ts.map +1 -0
  9. package/dist/explain.js +42 -0
  10. package/dist/explain.js.map +1 -0
  11. package/dist/index.d.ts +132 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +169 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/plan.d.ts +83 -0
  16. package/dist/plan.d.ts.map +1 -0
  17. package/dist/plan.js +385 -0
  18. package/dist/plan.js.map +1 -0
  19. package/dist/runtime-compile.d.ts +30 -0
  20. package/dist/runtime-compile.d.ts.map +1 -0
  21. package/dist/runtime-compile.js +29 -0
  22. package/dist/runtime-compile.js.map +1 -0
  23. package/dist/runtime.d.ts +157 -0
  24. package/dist/runtime.d.ts.map +1 -0
  25. package/dist/runtime.js +336 -0
  26. package/dist/runtime.js.map +1 -0
  27. package/dist/serialize/apply.d.ts +28 -0
  28. package/dist/serialize/apply.d.ts.map +1 -0
  29. package/dist/serialize/apply.js +211 -0
  30. package/dist/serialize/apply.js.map +1 -0
  31. package/dist/serialize/context.d.ts +143 -0
  32. package/dist/serialize/context.d.ts.map +1 -0
  33. package/dist/serialize/context.js +93 -0
  34. package/dist/serialize/context.js.map +1 -0
  35. package/dist/serialize/expressions.d.ts +7 -0
  36. package/dist/serialize/expressions.d.ts.map +1 -0
  37. package/dist/serialize/expressions.js +177 -0
  38. package/dist/serialize/expressions.js.map +1 -0
  39. package/dist/serialize/guards.d.ts +7 -0
  40. package/dist/serialize/guards.d.ts.map +1 -0
  41. package/dist/serialize/guards.js +19 -0
  42. package/dist/serialize/guards.js.map +1 -0
  43. package/dist/serialize/index.d.ts +7 -0
  44. package/dist/serialize/index.d.ts.map +1 -0
  45. package/dist/serialize/index.js +266 -0
  46. package/dist/serialize/index.js.map +1 -0
  47. package/dist/serialize/keywords.d.ts +32 -0
  48. package/dist/serialize/keywords.d.ts.map +1 -0
  49. package/dist/serialize/keywords.js +131 -0
  50. package/dist/serialize/keywords.js.map +1 -0
  51. package/dist/serialize/messages.d.ts +36 -0
  52. package/dist/serialize/messages.d.ts.map +1 -0
  53. package/dist/serialize/messages.js +100 -0
  54. package/dist/serialize/messages.js.map +1 -0
  55. package/dist/serialize/names.d.ts +47 -0
  56. package/dist/serialize/names.d.ts.map +1 -0
  57. package/dist/serialize/names.js +115 -0
  58. package/dist/serialize/names.js.map +1 -0
  59. package/dist/serialize/spans.d.ts +34 -0
  60. package/dist/serialize/spans.d.ts.map +1 -0
  61. package/dist/serialize/spans.js +61 -0
  62. package/dist/serialize/spans.js.map +1 -0
  63. package/dist/serialize/statements.d.ts +16 -0
  64. package/dist/serialize/statements.d.ts.map +1 -0
  65. package/dist/serialize/statements.js +396 -0
  66. package/dist/serialize/statements.js.map +1 -0
  67. package/dist/serialize/unit.d.ts +8 -0
  68. package/dist/serialize/unit.d.ts.map +1 -0
  69. package/dist/serialize/unit.js +107 -0
  70. package/dist/serialize/unit.js.map +1 -0
  71. package/dist/standalone.d.ts +24 -0
  72. package/dist/standalone.d.ts.map +1 -0
  73. package/dist/standalone.js +166 -0
  74. package/dist/standalone.js.map +1 -0
  75. package/package.json +38 -0
  76. package/src/emit.ts +196 -0
  77. package/src/explain.ts +67 -0
  78. package/src/index.ts +366 -0
  79. package/src/plan.ts +496 -0
  80. package/src/runtime-compile.ts +90 -0
  81. package/src/runtime.ts +599 -0
  82. package/src/serialize/apply.ts +251 -0
  83. package/src/serialize/context.ts +161 -0
  84. package/src/serialize/expressions.ts +202 -0
  85. package/src/serialize/guards.ts +20 -0
  86. package/src/serialize/index.ts +432 -0
  87. package/src/serialize/keywords.ts +144 -0
  88. package/src/serialize/messages.ts +120 -0
  89. package/src/serialize/names.ts +136 -0
  90. package/src/serialize/spans.ts +88 -0
  91. package/src/serialize/statements.ts +531 -0
  92. package/src/serialize/unit.ts +118 -0
  93. package/src/standalone.ts +187 -0
package/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ // @json-schema-engine/compiler public API (M6.2 vertical slice): compile a registered
2
+ // schema into a specialized flag-mode validator. Static subschemas become
3
+ // emitted JS; dynamic islands and every fallback cause trampoline to the
4
+ // interpreter through core's evaluateFragment, so the compiled artifact is
5
+ // exactly as correct as the interpreter — never less complete.
6
+ import { DEFAULT_MAX_DEPTH, OutputOptionsError, assembleResult, renderBasic, resolveOutputDemand, } from "@json-schema-engine/core";
7
+ import { buildPlan } from "./plan.js";
8
+ import { serializePlan } from "./serialize/index.js";
9
+ import { finishTrace, makeRuntime } from "./runtime.js";
10
+ import { instantiate, instantiateList, instantiateListAnn, instantiateTrace, } from "./runtime-compile.js";
11
+ export { buildPlan } from "./plan.js";
12
+ export { explainCompilation } from "./explain.js";
13
+ export { serializePlan } from "./serialize/index.js";
14
+ export { emitStandalone, StandaloneUnsupportedError } from "./standalone.js";
15
+ /**
16
+ * Compile a registered schema into a flag-mode validator. The artifact
17
+ * binds to a snapshot of the engine's schema and dialect registries taken at
18
+ * compile time, plus the engine's pattern cache: schemas registered,
19
+ * re-registered, or given a new dialect later are invisible to it, and a
20
+ * reference unresolved at compile time stays unresolved for it (E1).
21
+ */
22
+ export function compileValidator(engine, schemaUri, options = {}) {
23
+ const plan = buildPlan(engine, schemaUri);
24
+ // Compilation is synchronous, so the plan (built from the live registry)
25
+ // and this snapshot see one state; the artifact's islands never see a
26
+ // later registration.
27
+ const registry = engine.registry.snapshot();
28
+ const source = serializePlan(plan, registry, {
29
+ flags: options.conservative
30
+ ? { inline: false, plainData: false }
31
+ : { inline: true, plainData: true },
32
+ });
33
+ const runtime = makeRuntime(registry, engine.patternCache, plan.patterns, options.maxDepth ?? DEFAULT_MAX_DEPTH, false, undefined, engine.formats, plan.formats);
34
+ const validate = instantiate(source, runtime, plan.targets.map((t) => t.ref));
35
+ return {
36
+ validate: (instance) => validate(instance),
37
+ plan,
38
+ source,
39
+ };
40
+ }
41
+ /**
42
+ * Compile a registered schema into a list-output validator (D9e): flat,
43
+ * interpreter-exact error units — the same elements
44
+ * `Engine.evaluate(uri, x, { output: "list" }).errors` yields, in the same
45
+ * order (list artifacts never short-circuit; every branch runs, DESIGN §7).
46
+ * Error-unit objects materialize only on failure paths. Binds to registry
47
+ * snapshots exactly like {@link compileValidator}.
48
+ */
49
+ export function compileList(engine, schemaUri, options = {}) {
50
+ const selection = options.annotations ?? false;
51
+ const collect = selection !== false;
52
+ const errorParams = options.errorParams ?? false;
53
+ const plan = buildPlan(engine, schemaUri, { output: "list" });
54
+ const registry = engine.registry.snapshot();
55
+ const source = serializePlan(plan, registry, {
56
+ flags: options.conservative
57
+ ? { inline: false, plainData: false }
58
+ : { inline: true, plainData: true },
59
+ output: "list",
60
+ listParams: errorParams,
61
+ annotate: collect ? { selection } : undefined,
62
+ });
63
+ const runtime = makeRuntime(registry, engine.patternCache, plan.patterns, options.maxDepth ?? DEFAULT_MAX_DEPTH, errorParams, collect ? { selection } : undefined, engine.formats, plan.formats);
64
+ const targets = plan.targets.map((t) => t.ref);
65
+ const root = registry.rootRef(schemaUri);
66
+ const rootLocation = `${root.baseUri}#${root.pointer}`;
67
+ const keep = typeof selection === "object" ? selection.keep : undefined;
68
+ if (!collect) {
69
+ const evaluateList = instantiateList(source, runtime, targets);
70
+ return {
71
+ evaluateList,
72
+ basic: (instance) => {
73
+ const { valid, errors } = evaluateList(instance);
74
+ return renderBasic(valid, rootLocation, errors, []);
75
+ },
76
+ plan,
77
+ source,
78
+ };
79
+ }
80
+ // The emitted evaluator returns the raw (static-list-filtered) annotation
81
+ // array; the wrapper applies `keep` (over the native unit, as the
82
+ // interpreter does) and the valid-only presence rule, matching
83
+ // `Engine.evaluate`'s Result.annotations; `basic()` renders that same
84
+ // flat surface through core's renderer.
85
+ const rawEval = instantiateListAnn(source, runtime, targets);
86
+ const evaluateList = (instance) => {
87
+ const r = rawEval(instance);
88
+ if (!r.valid)
89
+ return { valid: false, errors: r.errors };
90
+ const anns = keep ? r.annotations.filter(keep) : r.annotations;
91
+ return { valid: true, errors: r.errors, annotations: anns };
92
+ };
93
+ return {
94
+ evaluateList,
95
+ basic: (instance) => {
96
+ const r = evaluateList(instance);
97
+ return renderBasic(r.valid, rootLocation, r.errors, r.annotations ?? []);
98
+ },
99
+ plan,
100
+ source,
101
+ };
102
+ }
103
+ /**
104
+ * Compile a registered schema into an evaluator that renders every output
105
+ * format: a list artifact that also records each application's node —
106
+ * locations, keyword verdicts, and the records it raised — into core's
107
+ * located tree, so the documents render through the same code as the
108
+ * interpreter's. Format, level, and `trace` are chosen per evaluation; the
109
+ * annotation selection, `errorParams`, and whether irrelevant records are
110
+ * retained (`verbose`) are fixed here (D5). Without retention the records
111
+ * are discarded at the cut and a verbose-level request throws
112
+ * {@link OutputOptionsError}. Binds to registry snapshots exactly like
113
+ * {@link compileValidator}.
114
+ */
115
+ export function compileEvaluator(engine, schemaUri, options = {}) {
116
+ const selection = options.annotations ?? false;
117
+ const collect = selection !== false;
118
+ const errorParams = options.errorParams ?? false;
119
+ const retain = options.verbose ?? false;
120
+ const plan = buildPlan(engine, schemaUri, { output: "list" });
121
+ const registry = engine.registry.snapshot();
122
+ const source = serializePlan(plan, registry, {
123
+ flags: options.conservative
124
+ ? { inline: false, plainData: false }
125
+ : { inline: true, plainData: true },
126
+ output: "list",
127
+ listParams: errorParams,
128
+ annotate: collect ? { selection } : undefined,
129
+ trace: true,
130
+ });
131
+ const runtime = makeRuntime(registry, engine.patternCache, plan.patterns, options.maxDepth ?? DEFAULT_MAX_DEPTH, errorParams, collect ? { selection } : undefined, engine.formats, plan.formats, true, retain);
132
+ const run = instantiateTrace(source, runtime, plan.targets.map((t) => t.ref));
133
+ const root = registry.rootRef(schemaUri);
134
+ const rootLocation = `${root.baseUri}#${root.pointer}`;
135
+ const keep = typeof selection === "object" ? selection.keep : undefined;
136
+ const evaluate = (instance, evaluateOptions = {}) => {
137
+ // The controls fixed at compile time are rejected, not ignored (ADR
138
+ // 0003: every combination is supported or refused).
139
+ for (const fixed of ["annotations", "errorParams", "positions"]) {
140
+ if (fixed in evaluateOptions) {
141
+ throw new OutputOptionsError(`'${fixed}' is fixed when the evaluator is compiled`);
142
+ }
143
+ }
144
+ // The compiled selection stands in for the option; `flag` is admitted
145
+ // whatever the selection, since it renders no records.
146
+ const demand = {
147
+ ...resolveOutputDemand({
148
+ output: evaluateOptions.output,
149
+ verbose: evaluateOptions.verbose,
150
+ trace: evaluateOptions.trace,
151
+ }),
152
+ annotations: selection,
153
+ };
154
+ if (demand.verbose && !retain) {
155
+ throw new OutputOptionsError("the verbose level needs an evaluator compiled with verbose: true");
156
+ }
157
+ const st = run(instance);
158
+ if (demand.format === "flag")
159
+ return { valid: st.valid };
160
+ const { units, root: node } = finishTrace(st, keep, demand.verbose);
161
+ return assembleResult(demand, st.valid, units, node, rootLocation, evaluateOptions.trace === true);
162
+ };
163
+ return {
164
+ evaluate: evaluate,
165
+ plan,
166
+ source,
167
+ };
168
+ }
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { type Engine, type SchemaRef, type SubschemaApplication } from "@json-schema-engine/core";
2
+ /** Why a unit is interpreted rather than compiled. */
3
+ export type FallbackCause = "dynamic" | "unlowerable" | "cycle" | "nonSchema";
4
+ /** Static evaluated-name coverage for one schema object (slice: own-node trio only). */
5
+ export interface StaticNameCoverage {
6
+ names: readonly string[];
7
+ patterns: readonly string[];
8
+ coversAllNames: boolean;
9
+ prefixCount: number;
10
+ coversAllIndexes: boolean;
11
+ }
12
+ /** One application edge out of a unit, resolved at plan time. */
13
+ export interface PlannedApplication {
14
+ keyword: string;
15
+ app: SubschemaApplication;
16
+ targetKey: string;
17
+ }
18
+ export interface PlannedUnit {
19
+ key: string;
20
+ ref: SchemaRef;
21
+ kind: "static" | "interpreted";
22
+ cause?: FallbackCause;
23
+ /** static units: resolved outgoing application edges (keyword order preserved) */
24
+ edges: PlannedApplication[];
25
+ /** static coverage for this schema object, when computable (slice rule) */
26
+ coverage: StaticNameCoverage | null;
27
+ /**
28
+ * Consumer compiled with RUNTIME evaluated-set tracking instead of the
29
+ * interpreter (COMPILED-CONSUMERS.md phase B): a flag-mode consumer whose
30
+ * evaluated coverage is dynamic (static licensing failed), or ANY list-mode
31
+ * consumer (list plans never static-license — see PlanOptions.output).
32
+ * `coverage` stays null; the unit's body threads a runtime coverage channel
33
+ * through its in-place closure (its {@link inRegion} members) and its
34
+ * consumer keywords read it. Never set together with {@link inRegion}
35
+ * (nested tracked consumers island).
36
+ */
37
+ tracking?: boolean;
38
+ /**
39
+ * A static unit in some tracked unit's in-place coverage region: reachable
40
+ * from a tracked (or region) unit via in-place edges. Emitted with a second
41
+ * calling convention (a trailing coverage channel) so its producers'
42
+ * post-success coverage flows to the consumer (COMPILED-CONSUMERS.md phase B).
43
+ */
44
+ inRegion?: boolean;
45
+ /** true when any apply path from this unit can reach an interpreted unit */
46
+ reachesInterpreted: boolean;
47
+ /** number of planned edges targeting this unit (D9 inline licensing) */
48
+ useCount: number;
49
+ }
50
+ export interface CompilationPlan {
51
+ rootKey: string;
52
+ units: Map<string, PlannedUnit>;
53
+ /** every regex source any static unit tests (pattern + coverage patterns) */
54
+ patterns: string[];
55
+ /** every format name any static unit's asserting `format` tests */
56
+ formats: string[];
57
+ /** interpreted units in stable order; index = target-table slot */
58
+ targets: PlannedUnit[];
59
+ }
60
+ /**
61
+ * Build the compilation plan for one registered root schema. The walk mirrors
62
+ * the registration walk's position logic by construction: descent uses the
63
+ * same analyze() facts and the same registry.child pointer navigation.
64
+ */
65
+ /** Options for {@link buildPlan}. */
66
+ export interface PlanOptions {
67
+ /**
68
+ * The artifact's output mode. Static-coverage licensing for unevaluated*
69
+ * consumers is sound for "flag" only: coverage models the parent-SUCCESS
70
+ * path, but when a contributor (e.g. prefixItems inside an allOf branch)
71
+ * FAILS, the interpreter drops its annotations and unevaluated* reports
72
+ * additional errors. That difference is verdict-invisible — the failing
73
+ * contributor already fails the parent — but list output must reproduce
74
+ * the interpreter's error units exactly. So "list" plans NEVER static-
75
+ * license a consumer: every consumer is compiled with runtime coverage
76
+ * tracking (`tracking`), which reproduces the drop-on-failure behavior by
77
+ * construction (a failed application's channel span truncates, so the sweep
78
+ * covers less). "flag" keeps the static-coverage fast path.
79
+ */
80
+ output?: "flag" | "list";
81
+ }
82
+ export declare function buildPlan(engine: Engine, schemaUri: string, options?: PlanOptions): CompilationPlan;
83
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,MAAM,EAEX,KAAK,SAAS,EAEd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,sDAAsD;AACtD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,aAAa,GACb,OAAO,GACP,WAAW,CAAC;AAEhB,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,SAAS,CAAC;IACf,IAAI,EAAE,QAAQ,GAAG,aAAa,CAAC;IAC/B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,kFAAkF;IAClF,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,kBAAkB,EAAE,OAAO,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mEAAmE;IACnE,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAOD;;;;GAIG;AACH,qCAAqC;AACrC,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,WAAgB,GACxB,eAAe,CA8QjB"}
package/dist/plan.js ADDED
@@ -0,0 +1,385 @@
1
+ // Compilation planner (M6.2): classifies every reachable schema node as a
2
+ // static (compilable) or interpreted (trampoline) unit, using ONLY public
3
+ // core APIs and analyze() facts — never keyword names (D1). Conservative by
4
+ // design: anything uncertain falls back to the interpreter, which is always
5
+ // correct.
6
+ import { UnresolvableRefError, } from "@json-schema-engine/core";
7
+ const unitKey = (ref) => `${ref.baseUri}#${ref.pointer}`;
8
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
9
+ export function buildPlan(engine, schemaUri, options = {}) {
10
+ const registry = engine.registry;
11
+ const units = new Map();
12
+ const patterns = new Set();
13
+ const formats = new Set();
14
+ const rootRef = registry.rootRef(schemaUri);
15
+ // inPlaceChain: units connected to the current edge via consecutive
16
+ // in-place applications — a back-edge into this chain is an in-place
17
+ // cycle (same-cursor re-entry, InfiniteLoopError class) and its target
18
+ // must stay interpreted. A descending edge resets the chain.
19
+ const plan = (ref, inPlaceChain) => {
20
+ const key = unitKey(ref);
21
+ const existing = units.get(key);
22
+ if (existing)
23
+ return existing;
24
+ const unit = {
25
+ key,
26
+ ref,
27
+ kind: "static",
28
+ edges: [],
29
+ coverage: null,
30
+ reachesInterpreted: false,
31
+ useCount: 0,
32
+ };
33
+ units.set(key, unit);
34
+ const node = ref.node;
35
+ if (typeof node === "boolean")
36
+ return unit; // trivially static
37
+ if (!isObj(node)) {
38
+ unit.kind = "interpreted";
39
+ unit.cause = "nonSchema"; // interpreter D19 backstop reports it lazily
40
+ return unit;
41
+ }
42
+ // No dialect allowlist: keyword facts are the compiler's whole window
43
+ // into semantics (D1), so a dialect compiles exactly when every present
44
+ // keyword lowers — the per-keyword check below. That is what lets a
45
+ // dialect PACKAGE (draft-04, future OAS dialects) become compilable
46
+ // purely by shipping lower() on its behaviors; the only dialect-LEVEL
47
+ // semantic the planner must mirror is refIgnoresSiblings, handled
48
+ // generically here.
49
+ const dialect = registry.dialectFor(ref.baseUri);
50
+ // draft-07/06 (D18, engine.ts:397): a $ref makes every sibling keyword
51
+ // act as if absent — plan/lower ONLY $ref, exactly as applySchemaAtDepth
52
+ // skips every non-$ref entry when refOnly. Without this, a draft-07
53
+ // $ref-with-siblings unit would compile both the ref AND the siblings,
54
+ // a real divergence from the interpreter (unreachable before this
55
+ // milestone, since every legacy unit was interpreted regardless).
56
+ const refOnly = dialect.refIgnoresSiblings && Object.hasOwn(node, "$ref");
57
+ const present = [];
58
+ let consumerPresent = false;
59
+ for (const entry of dialect.ordered) {
60
+ if (refOnly && entry.name !== "$ref")
61
+ continue;
62
+ if (!Object.hasOwn(node, entry.name))
63
+ continue;
64
+ const behavior = entry.behavior;
65
+ const value = node[entry.name];
66
+ const facts = behavior.analyze?.(value, { schema: node }) ?? {};
67
+ if (facts.dynamicScopeSensitive) {
68
+ unit.kind = "interpreted";
69
+ unit.cause = "dynamic";
70
+ return unit;
71
+ }
72
+ if (typeof behavior.lower !== "function") {
73
+ unit.kind = "interpreted";
74
+ unit.cause = "unlowerable";
75
+ return unit;
76
+ }
77
+ // A coverage consumer (unevaluated*) declares `consumes` AND an
78
+ // evaluated-coverage fact; a keyword consuming other dependency data
79
+ // (then/else reading if's outcome) needs no channel.
80
+ if ((facts.consumes?.length ?? 0) > 0 &&
81
+ (facts.evaluatesNames !== undefined ||
82
+ facts.evaluatesIndexes !== undefined)) {
83
+ consumerPresent = true;
84
+ }
85
+ for (const rx of facts.regexes ?? [])
86
+ patterns.add(rx);
87
+ for (const fmt of facts.formats ?? [])
88
+ formats.add(fmt);
89
+ present.push({ name: entry.name, facts });
90
+ }
91
+ // Unknown keywords: annotations, elided in flag mode.
92
+ // Consumer licensing (D9a): a consumer lowers only when its coverage
93
+ // kind is statically known — from this object's own contributors plus,
94
+ // transitively, unconditional asserting in-place applications (allOf
95
+ // conjuncts, $ref chains). Anything runtime-conditional (anyOf/oneOf/
96
+ // if/dependentSchemas branches, if's condition, dynamic references,
97
+ // cycles) makes that coverage kind dynamic and the node interpreted.
98
+ if (consumerPresent) {
99
+ if (options.output === "list") {
100
+ // See PlanOptions.output: list mode NEVER static-licenses. Static
101
+ // coverage models only the parent-SUCCESS path (the M6.6 finding) —
102
+ // a FAILING contributor's dropped annotations make the interpreter
103
+ // emit additional unevaluated* errors, verdict-invisible but list-
104
+ // visible. Runtime tracking reproduces that by construction: the
105
+ // failed application's channel span truncates and the consumer sweep
106
+ // then covers less. So every list consumer is tracked; the unit stays
107
+ // static and CONTINUES to edge resolution (its tracked body needs
108
+ // planned in-place edges to thread the channel), and the region
109
+ // fixpoint below threads its in-place closure.
110
+ unit.tracking = true;
111
+ }
112
+ else {
113
+ // Flag mode: static-coverage licensing is sound (verdict-only), so a
114
+ // consumer whose coverage kind is statically known keeps the fast path.
115
+ const halves = coverageHalves(registry, ref, new Set(), true);
116
+ const needsNames = present.some((k) => (k.facts.consumes?.length ?? 0) > 0 && k.facts.evaluatesNames);
117
+ const needsIndexes = present.some((k) => (k.facts.consumes?.length ?? 0) > 0 && k.facts.evaluatesIndexes);
118
+ if ((needsNames && halves.name === null) ||
119
+ (needsIndexes && halves.index === null)) {
120
+ // Static licensing failed. FLAG mode compiles the consumer anyway,
121
+ // with runtime evaluated-set tracking (coverage stays null); the
122
+ // unit stays static and CONTINUES to edge resolution / child
123
+ // planning like any static unit — its tracked body needs planned
124
+ // in-place edges to thread the coverage channel through. The region
125
+ // membership of those edges is computed after the walk (below).
126
+ unit.tracking = true;
127
+ }
128
+ else {
129
+ unit.coverage = {
130
+ names: halves.name ? [...halves.name.names] : [],
131
+ patterns: halves.name ? halves.name.patterns : [],
132
+ coversAllNames: halves.name?.all ?? false,
133
+ prefixCount: halves.index?.prefix ?? 0,
134
+ coversAllIndexes: halves.index?.all ?? false,
135
+ };
136
+ for (const pat of unit.coverage.patterns)
137
+ patterns.add(pat);
138
+ }
139
+ }
140
+ }
141
+ // Resolve application edges; plan children.
142
+ for (const { name, facts } of present) {
143
+ for (const app of facts.applications ?? []) {
144
+ let target;
145
+ try {
146
+ if (app.ref !== undefined) {
147
+ target = registry.resolveRef(app.ref, ref.baseUri);
148
+ }
149
+ else if (app.sibling !== undefined) {
150
+ target = registry.child(ref, [app.sibling, ...app.path]);
151
+ }
152
+ else {
153
+ target = registry.child(ref, [name, ...app.path]);
154
+ }
155
+ }
156
+ catch (err) {
157
+ if (err instanceof UnresolvableRefError) {
158
+ // Lazy-failure parity: the interpreter throws only when the
159
+ // reference is actually followed, so the whole node falls back.
160
+ unit.kind = "interpreted";
161
+ unit.cause = "unlowerable";
162
+ unit.edges = [];
163
+ return unit;
164
+ }
165
+ throw err;
166
+ }
167
+ const targetKey = unitKey(target);
168
+ if (app.mode === "inPlace") {
169
+ if (inPlaceChain.includes(targetKey) || targetKey === key) {
170
+ // In-place cycle: same-cursor re-entry. The interpreter's
171
+ // seen-set gives exact InfiniteLoopError parity.
172
+ const t = units.get(targetKey) ?? plan(target, [...inPlaceChain, key]);
173
+ t.kind = "interpreted";
174
+ t.cause = "cycle";
175
+ t.edges = [];
176
+ unit.edges.push({ keyword: name, app, targetKey });
177
+ continue;
178
+ }
179
+ plan(target, [...inPlaceChain, key]);
180
+ }
181
+ else {
182
+ plan(target, []);
183
+ }
184
+ unit.edges.push({ keyword: name, app, targetKey });
185
+ }
186
+ }
187
+ return unit;
188
+ };
189
+ const root = plan(rootRef, []);
190
+ // Coverage-region fixpoint (COMPILED-CONSUMERS.md phase B). A tracked
191
+ // consumer's coverage comes from its own producers AND, recursively, its
192
+ // asserting in-place applications; each static unit in that in-place closure
193
+ // must report its post-success coverage through the runtime channel, so it
194
+ // gets the second (channel-threaded) calling convention (inRegion). Seeded
195
+ // by tracked units, propagated through in-place edges of static units.
196
+ //
197
+ // Exception — a nested tracked consumer reached inside the closure: threading
198
+ // one unit's channel through another's consumer is a v1 non-goal, so it is
199
+ // re-classified interpreted and becomes an ISLAND. The parent then trampolines
200
+ // it (fragCov) and folds its harvested root coverage — no nested channel.
201
+ // Islands and already-interpreted targets reached by region edges stay
202
+ // interpreted. A consumer-bearing STATIC-coverage unit stays a normal region
203
+ // member (its produces flow like any producer's).
204
+ const regionStack = [...units.values()]
205
+ .filter((u) => u.tracking)
206
+ .map((u) => u.key);
207
+ while (regionStack.length > 0) {
208
+ const u = units.get(regionStack.pop());
209
+ if (u.kind !== "static")
210
+ continue; // a seed islanded by another seed
211
+ for (const edge of u.edges) {
212
+ if (edge.app.mode !== "inPlace")
213
+ continue;
214
+ const m = units.get(edge.targetKey);
215
+ if (m.kind !== "static")
216
+ continue; // island target: stays interpreted
217
+ // Boolean subschemas contribute no coverage and fold to a literal at the
218
+ // call site — never a channel-threaded member.
219
+ if (typeof m.ref.node === "boolean")
220
+ continue;
221
+ if (m.tracking) {
222
+ // Nested tracked consumer: island it (v1 nested-channel simplification).
223
+ m.kind = "interpreted";
224
+ m.cause = "unlowerable";
225
+ m.edges = [];
226
+ delete m.tracking;
227
+ continue;
228
+ }
229
+ if (!m.inRegion) {
230
+ m.inRegion = true;
231
+ regionStack.push(m.key);
232
+ }
233
+ }
234
+ }
235
+ // reachesInterpreted fixpoint over the edge graph.
236
+ let changed = true;
237
+ while (changed) {
238
+ changed = false;
239
+ for (const unit of units.values()) {
240
+ if (unit.kind === "interpreted" || unit.reachesInterpreted)
241
+ continue;
242
+ for (const edge of unit.edges) {
243
+ const t = units.get(edge.targetKey);
244
+ if (t.kind === "interpreted" || t.reachesInterpreted) {
245
+ unit.reachesInterpreted = true;
246
+ changed = true;
247
+ break;
248
+ }
249
+ }
250
+ }
251
+ }
252
+ for (const unit of units.values()) {
253
+ if (unit.kind !== "static")
254
+ continue;
255
+ for (const edge of unit.edges)
256
+ units.get(edge.targetKey).useCount++;
257
+ }
258
+ const targets = [...units.values()].filter((u) => u.kind === "interpreted");
259
+ return {
260
+ rootKey: root.key,
261
+ units,
262
+ patterns: [...patterns],
263
+ formats: [...formats],
264
+ targets,
265
+ };
266
+ }
267
+ /**
268
+ * The evaluated-coverage a schema node contributes at its own cursor (D9a),
269
+ * including — transitively — unconditional asserting in-place applications.
270
+ * `excludeConsumers` is true only for the licensing node itself: a
271
+ * consumer's own coverage fact describes the state AFTER it runs. Inside
272
+ * transitive targets, consumer facts count (post-success contribution).
273
+ * Facts come from analyze() and are valid regardless of which tier
274
+ * evaluates the target, so contribution is independent of compilability.
275
+ */
276
+ function coverageHalves(registry, ref, visiting, excludeConsumers) {
277
+ const key = unitKey(ref);
278
+ if (visiting.has(key))
279
+ return { name: null, index: null }; // cycle
280
+ visiting.add(key);
281
+ try {
282
+ const node = ref.node;
283
+ if (typeof node === "boolean") {
284
+ // Contributes nothing; `false` fails the parent, making coverage moot.
285
+ return {
286
+ name: { names: new Set(), patterns: [], all: false },
287
+ index: { prefix: 0, all: false },
288
+ };
289
+ }
290
+ if (!isObj(node))
291
+ return { name: null, index: null };
292
+ // Coverage comes from analyze() facts alone, dialect-agnostic (D1) —
293
+ // the keyword-author contract is that evaluates* facts are complete.
294
+ const dialect = registry.dialectFor(ref.baseUri);
295
+ // Same $ref-only reading as buildPlan's refOnly (engine.ts:397): a
296
+ // draft-07/06 sibling contributes nothing when $ref is present.
297
+ const refOnly = dialect.refIgnoresSiblings && Object.hasOwn(node, "$ref");
298
+ const acc = {
299
+ name: { names: new Set(), patterns: [], all: false },
300
+ index: { prefix: 0, all: false },
301
+ };
302
+ const fold = (h) => {
303
+ if (acc.name && h.name) {
304
+ for (const n of h.name.names)
305
+ acc.name.names.add(n);
306
+ acc.name.patterns.push(...h.name.patterns);
307
+ acc.name.all ||= h.name.all;
308
+ }
309
+ else
310
+ acc.name = null;
311
+ if (acc.index && h.index) {
312
+ acc.index.prefix = Math.max(acc.index.prefix, h.index.prefix);
313
+ acc.index.all ||= h.index.all;
314
+ }
315
+ else
316
+ acc.index = null;
317
+ };
318
+ for (const entry of dialect.ordered) {
319
+ if (refOnly && entry.name !== "$ref")
320
+ continue;
321
+ if (!Object.hasOwn(node, entry.name))
322
+ continue;
323
+ const value = node[entry.name];
324
+ const facts = entry.behavior.analyze?.(value, { schema: node }) ?? {};
325
+ if (facts.dynamicScopeSensitive)
326
+ return { name: null, index: null };
327
+ const isConsumer = (facts.consumes?.length ?? 0) > 0;
328
+ if (!(excludeConsumers && isConsumer)) {
329
+ if (acc.name && facts.evaluatesNames) {
330
+ const c = facts.evaluatesNames;
331
+ if (c.kind === "dynamic")
332
+ acc.name = null;
333
+ else if (c.kind === "all")
334
+ acc.name.all = true;
335
+ else if (c.kind === "names")
336
+ for (const n of c.names)
337
+ acc.name.names.add(n);
338
+ else
339
+ acc.name.patterns.push(...c.patterns);
340
+ }
341
+ if (acc.index && facts.evaluatesIndexes) {
342
+ const c = facts.evaluatesIndexes;
343
+ if (c.kind === "dynamic")
344
+ acc.index = null;
345
+ else if (c.kind === "prefix")
346
+ acc.index.prefix = Math.max(acc.index.prefix, c.count);
347
+ // allFrom's start is bounded by this node's own prefix
348
+ // contribution (items starts after sibling prefixItems), so the
349
+ // per-node union covers everything.
350
+ else
351
+ acc.index.all = true;
352
+ }
353
+ }
354
+ for (const app of facts.applications ?? []) {
355
+ if (app.mode !== "inPlace")
356
+ continue; // child cursors: no contribution here
357
+ if (app.inverted)
358
+ continue; // never survives the parent-success path
359
+ if (app.conditional || !app.asserts) {
360
+ // Runtime-conditional contribution (branching, or if's condition
361
+ // merging only on its own success): statically unknowable.
362
+ return { name: null, index: null };
363
+ }
364
+ let target;
365
+ try {
366
+ target =
367
+ app.ref !== undefined
368
+ ? registry.resolveRef(app.ref, ref.baseUri)
369
+ : registry.child(ref, [app.sibling ?? entry.name, ...app.path]);
370
+ }
371
+ catch {
372
+ return { name: null, index: null };
373
+ }
374
+ fold(coverageHalves(registry, target, visiting, false));
375
+ }
376
+ if (!acc.name && !acc.index)
377
+ return acc; // both dynamic already
378
+ }
379
+ return acc;
380
+ }
381
+ finally {
382
+ visiting.delete(key);
383
+ }
384
+ }
385
+ //# sourceMappingURL=plan.js.map