@telorun/analyzer 0.62.0 → 0.63.0

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 (146) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +6 -0
  3. package/dist/analyzer.d.ts +1 -1
  4. package/dist/analyzer.d.ts.map +1 -1
  5. package/dist/analyzer.js +343 -13
  6. package/dist/call-graph.d.ts.map +1 -1
  7. package/dist/call-graph.js +2 -7
  8. package/dist/flatten-for-analyzer.js +1 -1
  9. package/dist/import-resolution-diagnostics.d.ts +9 -2
  10. package/dist/import-resolution-diagnostics.d.ts.map +1 -1
  11. package/dist/import-resolution-diagnostics.js +26 -7
  12. package/dist/index.d.ts +15 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +13 -1
  15. package/dist/invocation-contract.d.ts +6 -1
  16. package/dist/invocation-contract.d.ts.map +1 -1
  17. package/dist/invocation-contract.js +27 -4
  18. package/dist/loaded-types.d.ts +12 -3
  19. package/dist/loaded-types.d.ts.map +1 -1
  20. package/dist/manifest-loader.d.ts +0 -1
  21. package/dist/manifest-loader.d.ts.map +1 -1
  22. package/dist/manifest-loader.js +62 -41
  23. package/dist/manifest-schemas.d.ts +699 -0
  24. package/dist/manifest-schemas.d.ts.map +1 -1
  25. package/dist/manifest-schemas.js +306 -14
  26. package/dist/module-compatibility.d.ts +40 -0
  27. package/dist/module-compatibility.d.ts.map +1 -0
  28. package/dist/module-compatibility.js +52 -0
  29. package/dist/ref-slot-reading.d.ts +30 -0
  30. package/dist/ref-slot-reading.d.ts.map +1 -0
  31. package/dist/ref-slot-reading.js +139 -0
  32. package/dist/referrer-rule.d.ts +67 -0
  33. package/dist/referrer-rule.d.ts.map +1 -0
  34. package/dist/referrer-rule.js +96 -0
  35. package/dist/release/index.d.ts +1 -1
  36. package/dist/release/index.d.ts.map +1 -1
  37. package/dist/release/index.js +1 -1
  38. package/dist/release/payload-digest.d.ts +7 -3
  39. package/dist/release/payload-digest.d.ts.map +1 -1
  40. package/dist/release/payload-digest.js +7 -3
  41. package/dist/release/version-stamp.d.ts +16 -0
  42. package/dist/release/version-stamp.d.ts.map +1 -1
  43. package/dist/release/version-stamp.js +52 -0
  44. package/dist/release/workspace-config.d.ts +10 -6
  45. package/dist/release/workspace-config.d.ts.map +1 -1
  46. package/dist/release/workspace-config.js +10 -6
  47. package/dist/resolve-schema-ref-kinds.d.ts +4 -3
  48. package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
  49. package/dist/resolve-schema-ref-kinds.js +16 -0
  50. package/dist/resolve-throws-union.d.ts.map +1 -1
  51. package/dist/resolve-throws-union.js +4 -3
  52. package/dist/resolve-zone-containment.d.ts +99 -0
  53. package/dist/resolve-zone-containment.d.ts.map +1 -0
  54. package/dist/resolve-zone-containment.js +212 -0
  55. package/dist/resolve-zone-requirements.d.ts +5 -0
  56. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  57. package/dist/resolve-zone-requirements.js +48 -7
  58. package/dist/resource-rule.d.ts +106 -0
  59. package/dist/resource-rule.d.ts.map +1 -0
  60. package/dist/resource-rule.js +236 -0
  61. package/dist/rule-condition.d.ts +50 -0
  62. package/dist/rule-condition.d.ts.map +1 -0
  63. package/dist/rule-condition.js +108 -0
  64. package/dist/schema-keywords.d.ts.map +1 -1
  65. package/dist/schema-keywords.js +20 -0
  66. package/dist/schema-projection.d.ts +171 -0
  67. package/dist/schema-projection.d.ts.map +1 -0
  68. package/dist/schema-projection.js +317 -0
  69. package/dist/step-slot.d.ts +39 -0
  70. package/dist/step-slot.d.ts.map +1 -0
  71. package/dist/step-slot.js +49 -0
  72. package/dist/telo-version.d.ts +1 -1
  73. package/dist/telo-version.js +1 -1
  74. package/dist/validate-cel-context.d.ts.map +1 -1
  75. package/dist/validate-cel-context.js +17 -3
  76. package/dist/validate-durable-regions.d.ts +15 -0
  77. package/dist/validate-durable-regions.d.ts.map +1 -0
  78. package/dist/validate-durable-regions.js +336 -0
  79. package/dist/validate-references.d.ts.map +1 -1
  80. package/dist/validate-references.js +44 -2
  81. package/dist/validate-referrer-rules.d.ts +120 -0
  82. package/dist/validate-referrer-rules.d.ts.map +1 -0
  83. package/dist/validate-referrer-rules.js +255 -0
  84. package/dist/validate-resource-rules.d.ts +114 -0
  85. package/dist/validate-resource-rules.d.ts.map +1 -0
  86. package/dist/validate-resource-rules.js +283 -0
  87. package/dist/validate-schema-projection.d.ts +32 -0
  88. package/dist/validate-schema-projection.d.ts.map +1 -0
  89. package/dist/validate-schema-projection.js +110 -0
  90. package/dist/validate-step-inputs.d.ts +2 -1
  91. package/dist/validate-step-inputs.d.ts.map +1 -1
  92. package/dist/validate-step-inputs.js +5 -3
  93. package/dist/validate-throws-coverage.d.ts.map +1 -1
  94. package/dist/validate-throws-coverage.js +11 -5
  95. package/dist/validate-zone-slots.d.ts +2 -2
  96. package/dist/validate-zone-slots.d.ts.map +1 -1
  97. package/dist/validate-zone-slots.js +202 -6
  98. package/dist/validate-zone-violations.d.ts +12 -0
  99. package/dist/validate-zone-violations.d.ts.map +1 -0
  100. package/dist/validate-zone-violations.js +169 -0
  101. package/dist/value-type-keyword.d.ts +1 -1
  102. package/dist/value-type-keyword.d.ts.map +1 -1
  103. package/dist/value-type-keyword.js +4 -0
  104. package/dist/zone-slot.d.ts +61 -0
  105. package/dist/zone-slot.d.ts.map +1 -1
  106. package/dist/zone-slot.js +86 -5
  107. package/package.json +2 -2
  108. package/src/analysis-registry.ts +6 -0
  109. package/src/analyzer.ts +395 -14
  110. package/src/call-graph.ts +2 -8
  111. package/src/flatten-for-analyzer.ts +1 -1
  112. package/src/import-resolution-diagnostics.ts +25 -7
  113. package/src/index.ts +73 -0
  114. package/src/invocation-contract.ts +45 -2
  115. package/src/loaded-types.ts +12 -3
  116. package/src/manifest-loader.ts +87 -55
  117. package/src/manifest-schemas.ts +328 -14
  118. package/src/module-compatibility.ts +72 -0
  119. package/src/ref-slot-reading.ts +156 -0
  120. package/src/referrer-rule.ts +116 -0
  121. package/src/release/index.ts +1 -0
  122. package/src/release/payload-digest.ts +7 -3
  123. package/src/release/version-stamp.ts +58 -0
  124. package/src/release/workspace-config.ts +10 -6
  125. package/src/resolve-schema-ref-kinds.ts +28 -4
  126. package/src/resolve-throws-union.ts +4 -3
  127. package/src/resolve-zone-containment.ts +329 -0
  128. package/src/resolve-zone-requirements.ts +67 -7
  129. package/src/resource-rule.ts +252 -0
  130. package/src/rule-condition.ts +126 -0
  131. package/src/schema-keywords.ts +24 -0
  132. package/src/schema-projection.ts +403 -0
  133. package/src/step-slot.ts +64 -0
  134. package/src/telo-version.ts +1 -1
  135. package/src/validate-cel-context.ts +17 -3
  136. package/src/validate-durable-regions.ts +401 -0
  137. package/src/validate-references.ts +57 -2
  138. package/src/validate-referrer-rules.ts +373 -0
  139. package/src/validate-resource-rules.ts +401 -0
  140. package/src/validate-schema-projection.ts +184 -0
  141. package/src/validate-step-inputs.ts +5 -3
  142. package/src/validate-throws-coverage.ts +12 -5
  143. package/src/validate-zone-slots.ts +192 -8
  144. package/src/validate-zone-violations.ts +190 -0
  145. package/src/value-type-keyword.ts +4 -0
  146. package/src/zone-slot.ts +114 -5
@@ -0,0 +1,236 @@
1
+ /**
2
+ * The single reader for `x-telo-resource-rules` — a kind declaring, as data,
3
+ * relationships between the fields of one resource that JSON Schema cannot
4
+ * state: an index naming a column its table does not declare, a foreign key
5
+ * whose two sides differ in length, a rename whose source is still declared.
6
+ *
7
+ * The predicate is CEL over `self` (the whole resource) and `this` (the element
8
+ * under test), so correlating two collections is a comprehension closure rather
9
+ * than a path language with wildcard bindings to design. `in:` names the
10
+ * collection to iterate and IS the diagnostic anchor: iterating what the pointer
11
+ * names is what makes a reported path exist by construction.
12
+ *
13
+ * Vocabulary borrowed from `Telo.JsonSchema.rules` — `condition` true when the
14
+ * rule HOLDS, the subject bound as `this`, plus `code` and `message` — because
15
+ * two CEL rule vocabularies with opposite polarity is a trap an author falls
16
+ * into once per rule. The two mechanisms stay separate: a `Telo.JsonSchema` rule
17
+ * runs at dispatch against a value, this one at `telo check` against a manifest.
18
+ *
19
+ * Lenient by design, the `ref-slot.ts` precedent: anything unreadable here reads
20
+ * as absent, and `validate-resource-rules.ts` is the strict half that reports it.
21
+ *
22
+ * Browser-safe: no Node built-ins.
23
+ */
24
+ export const RESOURCE_RULES_ANNOTATION = "x-telo-resource-rules";
25
+ function isObject(value) {
26
+ return !!value && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+ /** True for a node the loader produced from a `!cel` tag or a `${{ }}` string.
29
+ * Both markers are tested because they are not always both present: a
30
+ * registered definition's schema reaches the analyzer with `call` and
31
+ * `__compiled` dropped, keeping only `__tagged` + `source`. Testing one would
32
+ * make a rule readable on some paths and invisible on others. */
33
+ function isCelNode(value) {
34
+ return isObject(value) && (value.__compiled === true || value.__tagged === true);
35
+ }
36
+ /** A precompiled `!cel` node keeps its author-written text on `source`; a plain
37
+ * string is taken verbatim so a rule reads the same however the loader was
38
+ * configured (a round-trip view runs with `compile` off). */
39
+ export function celSourceOf(value) {
40
+ if (typeof value === "string")
41
+ return value;
42
+ if (isCelNode(value) && typeof value.source === "string")
43
+ return value.source;
44
+ return undefined;
45
+ }
46
+ /** The annotation exactly as written, for the strict half. `undefined` when the
47
+ * kind declares none; a non-array is returned as-is so the shape can be
48
+ * reported rather than silently skipped. */
49
+ export function readRawResourceRules(schema) {
50
+ if (!isObject(schema))
51
+ return undefined;
52
+ return schema[RESOURCE_RULES_ANNOTATION];
53
+ }
54
+ /** Every rule this kind declares that is well-formed enough to run. */
55
+ export function readResourceRules(schema) {
56
+ const raw = readRawResourceRules(schema);
57
+ if (!Array.isArray(raw))
58
+ return [];
59
+ const rules = [];
60
+ raw.forEach((entry, index) => {
61
+ if (!isObject(entry))
62
+ return;
63
+ const condition = celSourceOf(entry.condition);
64
+ const { code, message } = entry;
65
+ if (!condition || typeof code !== "string" || typeof message !== "string")
66
+ return;
67
+ if (code.length === 0 || message.length === 0)
68
+ return;
69
+ const severity = entry.severity === "warning" ? "warning" : "error";
70
+ if (entry.severity !== undefined && entry.severity !== "warning" && entry.severity !== "error") {
71
+ return;
72
+ }
73
+ const pointer = entry.in;
74
+ if (pointer !== undefined && typeof pointer !== "string")
75
+ return;
76
+ rules.push({
77
+ ...(pointer === undefined ? {} : { in: pointer }),
78
+ condition,
79
+ code,
80
+ message,
81
+ severity,
82
+ index,
83
+ });
84
+ });
85
+ return rules;
86
+ }
87
+ /** Split a JSON Pointer into its unescaped segments. `/` alone is the root. */
88
+ export function pointerSegments(pointer) {
89
+ if (pointer === "" || pointer === "/")
90
+ return [];
91
+ if (!pointer.startsWith("/"))
92
+ return undefined;
93
+ return pointer
94
+ .slice(1)
95
+ .split("/")
96
+ .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
97
+ }
98
+ /** Navigate a pointer within a value, stopping at anything that is not a plain
99
+ * container. Returns `undefined` for a pointer that does not resolve — which a
100
+ * resource legitimately produces by omitting an optional collection. */
101
+ export function resolvePointer(value, pointer) {
102
+ const segments = pointerSegments(pointer);
103
+ if (!segments)
104
+ return undefined;
105
+ let current = value;
106
+ for (const segment of segments) {
107
+ if (Array.isArray(current)) {
108
+ const index = Number(segment);
109
+ if (!Number.isInteger(index))
110
+ return undefined;
111
+ current = current[index];
112
+ }
113
+ else if (isObject(current)) {
114
+ current = current[segment];
115
+ }
116
+ else {
117
+ return undefined;
118
+ }
119
+ if (current === undefined)
120
+ return undefined;
121
+ }
122
+ return current;
123
+ }
124
+ /** Path prefix for a pointer, in the dotted/bracketed spelling diagnostics use
125
+ * (`foreignKeys[0].references`), so `data.path` is one grammar throughout. */
126
+ export function pointerToPath(pointer) {
127
+ const segments = pointerSegments(pointer) ?? [];
128
+ return segments.reduce((acc, segment) => {
129
+ if (/^\d+$/.test(segment))
130
+ return `${acc}[${segment}]`;
131
+ return acc === "" ? segment : `${acc}.${segment}`;
132
+ }, "");
133
+ }
134
+ /**
135
+ * The elements a rule iterates. An array yields one subject per item, a map one
136
+ * per entry with its key bound; a collection that is absent yields none, which
137
+ * is a rule that had nothing to say rather than a rule that failed.
138
+ *
139
+ * A scalar at the pointer is `undefined` — not an empty list — because that is a
140
+ * declaration defect the strict half reports, and an empty list would hide it.
141
+ */
142
+ export function resolveRuleSubjects(config, pointer) {
143
+ const collection = resolvePointer(config, pointer);
144
+ if (collection === undefined || collection === null)
145
+ return [];
146
+ const base = pointerToPath(pointer);
147
+ if (Array.isArray(collection)) {
148
+ return collection.map((value, index) => ({ path: `${base}[${index}]`, value }));
149
+ }
150
+ if (isObject(collection)) {
151
+ return Object.entries(collection).map(([key, value]) => ({
152
+ path: `${base}.${key}`,
153
+ value,
154
+ key,
155
+ }));
156
+ }
157
+ return undefined;
158
+ }
159
+ /**
160
+ * Path of the first CEL leaf inside a value, or `undefined` when every leaf is
161
+ * literal. A rule reading an expression would be evaluating a placeholder, so
162
+ * the subject is skipped — and the skip is reported, never silent.
163
+ *
164
+ * Stops at nested inline `{ kind }` declarations for the reason every other walk
165
+ * does: that CEL belongs to the nested kind, evaluated in its own scope.
166
+ */
167
+ export function findDynamicLeaf(value, base = "") {
168
+ if (isObject(value)) {
169
+ if (isCelNode(value))
170
+ return base || "(value)";
171
+ if (typeof value.kind === "string" && base !== "")
172
+ return undefined;
173
+ for (const [key, child] of Object.entries(value)) {
174
+ const found = findDynamicLeaf(child, base === "" ? key : `${base}.${key}`);
175
+ if (found)
176
+ return found;
177
+ }
178
+ return undefined;
179
+ }
180
+ if (Array.isArray(value)) {
181
+ for (let i = 0; i < value.length; i++) {
182
+ const found = findDynamicLeaf(value[i], `${base}[${i}]`);
183
+ if (found)
184
+ return found;
185
+ }
186
+ }
187
+ return undefined;
188
+ }
189
+ /**
190
+ * The nodes a condition actually READS, resolved against the resource and the
191
+ * element under test — the input to the dynamic-leaf check.
192
+ *
193
+ * Scanning the whole subject instead would make the check useless in exactly
194
+ * the shape it matters most: a resource-wide rule takes the whole resource as
195
+ * its subject, so one unrelated `!cel` anywhere (a `version:` read from
196
+ * `module.version`, which is the conventional spelling) would switch off every
197
+ * such rule. Chains are the same primitive the binding-order derivation uses:
198
+ * parsed, never lexed, so a name inside a string literal reads nothing.
199
+ *
200
+ * A chain stops at a computed index (`INDEX_SEGMENT`): what it selects is not
201
+ * known statically, so the node reached so far is what gets checked — the
202
+ * over-approximating direction, which errs toward skipping rather than toward
203
+ * evaluating a placeholder.
204
+ */
205
+ export function readNodes(chains,
206
+ /** The bindings in scope, by name — `self`/`this`/`key` for a resource rule,
207
+ * `self`/`referrer` for a referrer rule. A chain rooted at a name that is not
208
+ * bound reads nothing. */
209
+ roots) {
210
+ const nodes = [];
211
+ for (const chain of chains) {
212
+ const root = chain[0];
213
+ let current = root !== undefined && root in roots ? roots[root] : undefined;
214
+ if (current === undefined)
215
+ continue;
216
+ for (const segment of chain.slice(1)) {
217
+ if (segment === "[*]")
218
+ break;
219
+ if (Array.isArray(current)) {
220
+ const index = Number(segment);
221
+ current = Number.isInteger(index) ? current[index] : undefined;
222
+ }
223
+ else if (isObject(current)) {
224
+ current = current[segment];
225
+ }
226
+ else {
227
+ current = undefined;
228
+ }
229
+ if (current === undefined)
230
+ break;
231
+ }
232
+ if (current !== undefined)
233
+ nodes.push(current);
234
+ }
235
+ return nodes;
236
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A rule's CEL condition: compiling it once, and refusing the ones that cannot
3
+ * be checks.
4
+ *
5
+ * Shared by `x-telo-resource-rules` (fields of one resource) and
6
+ * `x-telo-referrer-rules` (a requirement on whoever references it). The two
7
+ * families differ in what they bind — `this` and `key` versus `referrer` — and
8
+ * in nothing else: same polarity, same cache, same refusals, same budget. Two
9
+ * copies would eventually disagree about which functions a rule may call, which
10
+ * is a soundness property rather than a style one.
11
+ *
12
+ * Browser-safe: no Node built-ins.
13
+ */
14
+ import { buildCelEnvironment } from "@telorun/templating";
15
+ /**
16
+ * Wall-clock ceiling for one rule over one resource. The rules run on the
17
+ * kernel's boot path and at the editor's keystroke-time analysis, and the
18
+ * comprehension nesting is the RULE AUTHOR's — a dependency's quadratic rule
19
+ * must not be able to hang a consumer's `telo check`.
20
+ *
21
+ * It bounds the SUBJECT LOOP, not one expression: cel-js offers no step limit,
22
+ * so a single pathological expression over one huge element still runs to
23
+ * completion. Stated rather than hidden — the budget catches the shape that
24
+ * actually occurs (a cheap expression over many subjects) and reports the rule
25
+ * as defective rather than truncating coverage silently.
26
+ */
27
+ export declare const RULE_BUDGET_MS = 50;
28
+ /** The analyzer's own environment — no host handlers, so every `hostBacked`
29
+ * entry is a throwing stub. Built once; it is stateless. */
30
+ export declare function ruleEnv(): ReturnType<typeof buildCelEnvironment>;
31
+ export type CompiledRule = {
32
+ parsed: (ctx: Record<string, unknown>) => unknown;
33
+ chains: readonly (readonly string[])[];
34
+ } | {
35
+ reason: string;
36
+ };
37
+ export declare function compileRuleCondition(condition: string,
38
+ /** Chain roots to assume when the parse yields no AST — "unknown", not
39
+ * "reads nothing", so the caller checks the whole of every binding. */
40
+ fallbackRoots: readonly string[]): CompiledRule;
41
+ /**
42
+ * Why a condition cannot serve as a check, as messages for the declaring kind.
43
+ *
44
+ * Two refusals beyond the CEL diagnostics themselves: a function the kernel
45
+ * supplies at boot (a throwing stub here, so the rule could never run at
46
+ * `telo check`), and a non-deterministic one (a verdict that depends on when it
47
+ * ran is not a verdict).
48
+ */
49
+ export declare function conditionRefusals(condition: string): string[];
50
+ //# sourceMappingURL=rule-condition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-condition.d.ts","sourceRoot":"","sources":["../src/rule-condition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAEL,mBAAmB,EAGpB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAQjC;6DAC6D;AAC7D,wBAAgB,OAAO,IAAI,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAGhE;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;CAAE,GAC7F;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBvB,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM;AACjB;wEACwE;AACxE,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,YAAY,CAoBd;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB7D"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * A rule's CEL condition: compiling it once, and refusing the ones that cannot
3
+ * be checks.
4
+ *
5
+ * Shared by `x-telo-resource-rules` (fields of one resource) and
6
+ * `x-telo-referrer-rules` (a requirement on whoever references it). The two
7
+ * families differ in what they bind — `this` and `key` versus `referrer` — and
8
+ * in nothing else: same polarity, same cache, same refusals, same budget. Two
9
+ * copies would eventually disagree about which functions a rule may call, which
10
+ * is a soundness property rather than a style one.
11
+ *
12
+ * Browser-safe: no Node built-ins.
13
+ */
14
+ import { CEL_FUNCTIONS, buildCelEnvironment, celEngine, extractAccessChains, } from "@telorun/templating";
15
+ /**
16
+ * Wall-clock ceiling for one rule over one resource. The rules run on the
17
+ * kernel's boot path and at the editor's keystroke-time analysis, and the
18
+ * comprehension nesting is the RULE AUTHOR's — a dependency's quadratic rule
19
+ * must not be able to hang a consumer's `telo check`.
20
+ *
21
+ * It bounds the SUBJECT LOOP, not one expression: cel-js offers no step limit,
22
+ * so a single pathological expression over one huge element still runs to
23
+ * completion. Stated rather than hidden — the budget catches the shape that
24
+ * actually occurs (a cheap expression over many subjects) and reports the rule
25
+ * as defective rather than truncating coverage silently.
26
+ */
27
+ export const RULE_BUDGET_MS = 50;
28
+ const HOST_BACKED = new Set(CEL_FUNCTIONS.filter((f) => f.hostBacked).map((f) => f.name));
29
+ const NON_DETERMINISTIC = new Set(CEL_FUNCTIONS.filter((f) => !f.deterministic).map((f) => f.name));
30
+ let sharedEnv;
31
+ /** The analyzer's own environment — no host handlers, so every `hostBacked`
32
+ * entry is a throwing stub. Built once; it is stateless. */
33
+ export function ruleEnv() {
34
+ sharedEnv ??= buildCelEnvironment();
35
+ return sharedEnv;
36
+ }
37
+ /**
38
+ * A rule's condition, parsed once per process rather than once per resource.
39
+ *
40
+ * The pass runs over every resource of a kind and at the editor's
41
+ * keystroke-time analysis, so a workspace with fifty tables parsed the same
42
+ * six conditions fifty times each. The source string is the whole key: the
43
+ * environment is stateless and shared, so two identical conditions genuinely
44
+ * compile to the same program. A parse FAILURE is cached too — it is a property
45
+ * of the condition, and re-deriving it per resource costs the same as the
46
+ * success it replaced.
47
+ *
48
+ * BOUNDED, because the editor analyses on every keystroke: a kind author editing
49
+ * a `condition:` interns one entry per character typed, and every one of those
50
+ * intermediate strings is dead the moment the next arrives. Insertion-ordered
51
+ * eviction is enough — the working set is the conditions a workspace actually
52
+ * declares, and a stale entry costs one re-parse.
53
+ */
54
+ const RULE_CACHE_LIMIT = 512;
55
+ const compiledRules = new Map();
56
+ export function compileRuleCondition(condition,
57
+ /** Chain roots to assume when the parse yields no AST — "unknown", not
58
+ * "reads nothing", so the caller checks the whole of every binding. */
59
+ fallbackRoots) {
60
+ const cached = compiledRules.get(condition);
61
+ if (cached)
62
+ return cached;
63
+ let result;
64
+ try {
65
+ const parsed = ruleEnv().parse(condition);
66
+ const ast = parsed.ast;
67
+ result = {
68
+ parsed,
69
+ chains: ast ? extractAccessChains(ast) : fallbackRoots.map((root) => [root]),
70
+ };
71
+ }
72
+ catch (err) {
73
+ result = { reason: err instanceof Error ? err.message : String(err) };
74
+ }
75
+ if (compiledRules.size >= RULE_CACHE_LIMIT) {
76
+ const oldest = compiledRules.keys().next();
77
+ if (!oldest.done)
78
+ compiledRules.delete(oldest.value);
79
+ }
80
+ compiledRules.set(condition, result);
81
+ return result;
82
+ }
83
+ /**
84
+ * Why a condition cannot serve as a check, as messages for the declaring kind.
85
+ *
86
+ * Two refusals beyond the CEL diagnostics themselves: a function the kernel
87
+ * supplies at boot (a throwing stub here, so the rule could never run at
88
+ * `telo check`), and a non-deterministic one (a verdict that depends on when it
89
+ * ran is not a verdict).
90
+ */
91
+ export function conditionRefusals(condition) {
92
+ const out = [];
93
+ const result = celEngine.analyze(condition, { celEnv: ruleEnv(), contextSchema: null });
94
+ for (const diagnostic of result.diagnostics)
95
+ out.push(`Rule condition: ${diagnostic.message}`);
96
+ for (const call of result.calls) {
97
+ if (HOST_BACKED.has(call.name)) {
98
+ out.push(`Rule condition calls '${call.name}()', which the kernel supplies at boot ` +
99
+ "(it needs Node crypto / Buffer). The analyzer registers a throwing stub, so " +
100
+ "the rule cannot run at telo check.");
101
+ }
102
+ else if (NON_DETERMINISTIC.has(call.name) || call.deterministic === false) {
103
+ out.push(`Rule condition calls '${call.name}()', which re-evaluates per call. A check ` +
104
+ "whose verdict depends on when it ran is not a check.");
105
+ }
106
+ }
107
+ return out;
108
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"schema-keywords.d.ts","sourceRoot":"","sources":["../src/schema-keywords.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D;iDACiD;AACjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AASrE;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAuJ/D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAC1C,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,WAAW,EACzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgIxB,CAAC"}
1
+ {"version":3,"file":"schema-keywords.d.ts","sourceRoot":"","sources":["../src/schema-keywords.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D;iDACiD;AACjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AASrE;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAuJ/D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAC1C,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,WAAW,EACzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwJxB,CAAC"}
@@ -245,6 +245,26 @@ export const TELO_SCHEMA_ANNOTATIONS = {
245
245
  description: "Derive this field's validation schema from a sibling ref's definition schema.",
246
246
  type: "string",
247
247
  },
248
+ "x-telo-schema-map": {
249
+ title: "Schema map",
250
+ description: "On the field a projection keys on: the JSON Schema node each of its values means. A lookup, never a computed expression.",
251
+ type: "object",
252
+ },
253
+ "x-telo-schema-projection": {
254
+ title: "Schema projection",
255
+ description: "How a collection of typed entries projects to a JSON Schema object — which collection, which field keys it, and which fields modify it. Belongs on the kind DOCUMENT, beside 'schema:' rather than inside it, because it describes the whole declaration and not one field; written here it still works and is reported so it can be moved.",
256
+ type: "object",
257
+ },
258
+ "x-telo-resource-rules": {
259
+ title: "Resource rules",
260
+ description: "Relationships between this kind's own fields that JSON Schema cannot state, as CEL over `self` (the resource) and `this` (the element `in:` iterates). `condition` is TRUE when the rule holds.",
261
+ type: "array",
262
+ },
263
+ "x-telo-schema-projection-from": {
264
+ title: "Schema projection from",
265
+ description: "Replace this node with the projection of the entry collection declared by the kind referenced at the named field.",
266
+ type: "string",
267
+ },
248
268
  "x-telo-value-schema-from": {
249
269
  title: "Value schema from",
250
270
  description: "The value here must satisfy the type declared at the named field — checked for EVERY such slot, not only the branch a given input selects.",
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The schema-projection annotations' single reader — the `ref-slot.ts` /
3
+ * `zone-slot.ts` precedent.
4
+ *
5
+ * A kind whose configuration is a COLLECTION OF TYPED ENTRIES can say what that
6
+ * collection means as a JSON Schema object, so a consumer can type the values
7
+ * it will read without the analyzer learning anything about the domain. A SQL
8
+ * table's columns are the first consumer; nothing in either annotation says
9
+ * SQL, column or table.
10
+ *
11
+ * Two halves, because the two facts have different owners:
12
+ *
13
+ * - `x-telo-schema-map`, on the field a projection keys on, gives the schema
14
+ * node each of its values means (`citext → {type: string}`). It sits with the
15
+ * field because that is where the value vocabulary is declared.
16
+ * - `x-telo-schema-projection`, on the KIND DOCUMENT (a sibling of `schema:`,
17
+ * not a keyword inside it), names the entry collection, the keying field, and
18
+ * the fields that MODIFY the mapped node. It sits on the document because it
19
+ * describes the kind's whole declaration rather than one field of it — but
20
+ * `schema:` is where every other `x-telo-*` keyword lives, so the reader
21
+ * accepts it in both positions and `validate-schema-projection.ts` reports the
22
+ * inner one. Silently ignoring a misplaced annotation is the exact failure the
23
+ * strict half exists to prevent: the projection stops typing its consumers and
24
+ * the diagnostic lands on the CONSUMER, blaming the wrong author.
25
+ *
26
+ * It is a declared LOOKUP, never a computed expression. The analyzer
27
+ * type-checks CEL and substitutes placeholders; it never evaluates, and a
28
+ * `base:`-style mapping is evaluated by the kernel at `create()` — too late for
29
+ * `telo check` to type the rows a consumer reads, which is the projection's
30
+ * whole purpose.
31
+ *
32
+ * Distinct from `x-telo-schema-from`, which derives a field's schema from a
33
+ * referenced KIND's definition schema. A projection is DECLARATION-derived: the
34
+ * row shape lives in one instance's own `columns:`, which no definition-level
35
+ * derivation can reach.
36
+ */
37
+ /** How a kind's entry collection projects to an object schema. */
38
+ export interface SchemaProjection {
39
+ /** JSON Pointer, from the resource root, to the entries. */
40
+ readonly entries: string;
41
+ /** The entry field whose value keys the `x-telo-schema-map` lookup. */
42
+ readonly key: string;
43
+ /** The entry field naming an entry's identity, when entries are an ARRAY.
44
+ * Absent for a keyed map, where the map key is the identity. */
45
+ readonly nameField?: string;
46
+ /** Entry field that widens the mapped node to admit null. */
47
+ readonly nullable?: string;
48
+ /** Entry field that wraps the mapped node in an array. */
49
+ readonly array?: string;
50
+ }
51
+ export type SchemaMap = Readonly<Record<string, Record<string, unknown>>>;
52
+ /** The projection a kind declares, or undefined. Invalid shapes read as absent;
53
+ * `validate-schema-projection.ts` is the half that reports them. */
54
+ export declare function readSchemaProjection(definition: unknown): SchemaProjection | undefined;
55
+ /** The annotation as written, from either position — the document (canonical)
56
+ * or `schema:` (accepted, and reported by the strict half). The document wins:
57
+ * a kind spelling it in both places is describing its own document. */
58
+ export declare function rawSchemaProjection(definition: unknown): unknown;
59
+ /** True when the annotation was found inside `schema:` rather than on the
60
+ * document — the misplacement the strict half reports. */
61
+ export declare function schemaProjectionIsMisplaced(definition: unknown): boolean;
62
+ export declare function readSchemaMap(node: unknown): SchemaMap | undefined;
63
+ /** The consumer-side annotation: a JSON Pointer to this resource's ref slot
64
+ * whose target declares the projection. */
65
+ export declare function readProjectionFrom(node: unknown): string | undefined;
66
+ /**
67
+ * Find the `x-telo-schema-map` a projection keys on. The map sits on the entry
68
+ * field's schema, which is reached through the collection's own schema — a
69
+ * keyed map's `additionalProperties`, or an array's `items`.
70
+ */
71
+ export declare function projectionKeyMap(kindSchema: unknown, projection: SchemaProjection): SchemaMap | undefined;
72
+ /**
73
+ * Project one declaration to an object schema.
74
+ *
75
+ * Modifiers are a CLOSED set applied in a FIXED order — `array` wraps, then
76
+ * `nullable` widens. Closed because each changes how the schema is assembled,
77
+ * so a third-party modifier would be a name nothing acts on; ordered because
78
+ * leaving it implicit is how two implementations come to disagree.
79
+ *
80
+ * The projection is deliberately LOSSY. Length, precision, collation and check
81
+ * constraints do not reach it: a consumer needs the type, its nullability and
82
+ * its repetition, and the database enforces the rest. A per-entry schema rich
83
+ * enough to double as a validator would move the domain's semantics into the
84
+ * type layer.
85
+ */
86
+ export declare function projectEntries(manifest: unknown, projection: SchemaProjection, map: SchemaMap): Record<string, unknown> | undefined;
87
+ /** A reference as the analyzer sees it: the internal `{kind, name, alias?}`
88
+ * shape `resolveRefSentinels` rewrites `!ref` to. */
89
+ export interface ProjectionRef {
90
+ readonly name: string;
91
+ readonly kind?: string;
92
+ readonly alias?: string;
93
+ }
94
+ /** The `{kind, name, alias?}` reference a value holds, or undefined. Exported so
95
+ * a host whose slot may hold EITHER shape can fall back to this reading. */
96
+ export declare function readProjectionRef(value: unknown): ProjectionRef | undefined;
97
+ /** What a reference resolved to. `"ambiguous"` is distinct from `undefined`
98
+ * because the two need different advice: one says disambiguate, the other says
99
+ * the name resolves to nothing. */
100
+ export type ProjectionLookup = {
101
+ readonly manifest: Record<string, any>;
102
+ } | {
103
+ readonly ambiguous: true;
104
+ } | undefined;
105
+ /**
106
+ * What projecting a consumer's slot needs: resolving a reference to the manifest
107
+ * it names, and the definition that manifest's `kind` names.
108
+ *
109
+ * A RESOLVER rather than a list of manifests, because resolution is scoped and
110
+ * only the host knows the scope: an alias-qualified `!ref Alias.users` names an
111
+ * import's exported instance, and a bare name means the enclosing module's — a
112
+ * distinction a name filter over one flattened list erases, which is how an
113
+ * unambiguous cross-module reference came to read as ambiguous. It is also what
114
+ * lets the kernel supply its own context lookup, so the contract the analyzer
115
+ * types and the contract the kernel enforces are the same schema.
116
+ */
117
+ export interface ProjectionScope {
118
+ /**
119
+ * The declaration the value at a projected slot names.
120
+ *
121
+ * Takes the RAW slot value rather than a parsed reference, because what sits
122
+ * there depends on the host and only the host can read it: the analyzer sees
123
+ * the `{kind, name, alias?}` reference the loader produced, while the kernel
124
+ * binds contracts AFTER Phase-5 injection has replaced that reference with the
125
+ * live instance. Parsing it here would have hardcoded the analyzer's shape and
126
+ * left the kernel unable to resolve anything — which is a contract enforced
127
+ * statically and not at dispatch.
128
+ */
129
+ resolveManifest(value: unknown): ProjectionLookup;
130
+ resolveDefinition(kind: string): Record<string, any> | undefined;
131
+ }
132
+ /**
133
+ * The resolver for a FLATTENED manifest list — the analyzer's own shape.
134
+ *
135
+ * An alias narrows to the manifests forwarded from that import (stamped
136
+ * `metadata.alias` by flatten), so two libraries each exporting a `users` table
137
+ * stay distinguishable. Only when nothing carries the alias does it fall back to
138
+ * matching by name alone, which is the pre-flatten shape a standalone module
139
+ * analysis has.
140
+ */
141
+ export declare function manifestListScope(manifests: readonly Record<string, any>[], resolveDefinition: (kind: string) => Record<string, any> | undefined): ProjectionScope;
142
+ export type ProjectionFailure = {
143
+ readonly reason: "no-ref";
144
+ readonly pointer: string;
145
+ } | {
146
+ readonly reason: "unresolved";
147
+ readonly pointer: string;
148
+ readonly name: string;
149
+ } | {
150
+ readonly reason: "ambiguous";
151
+ readonly pointer: string;
152
+ readonly name: string;
153
+ } | {
154
+ readonly reason: "no-projection";
155
+ readonly pointer: string;
156
+ readonly kind: string;
157
+ };
158
+ export declare function describeProjectionFailure(failure: ProjectionFailure): string;
159
+ /**
160
+ * Replace every `x-telo-schema-projection-from` node with the projection of the
161
+ * declaration it points at.
162
+ *
163
+ * Structural: returns a new schema and never mutates the one handed in. A node
164
+ * that cannot be projected is left exactly as it was — degrading to the slot's
165
+ * own schema rather than to a wrong one — and the reason is pushed to
166
+ * `failures`, because degrading SILENTLY is the failure this whole mechanism
167
+ * exists to move earlier: the consumer's contract quietly reopens and a
168
+ * misspelled field passes `telo check` exactly as it did before.
169
+ */
170
+ export declare function resolveSchemaProjections(schema: unknown, manifest: Record<string, any> | undefined, scope: ProjectionScope, failures?: ProjectionFailure[]): unknown;
171
+ //# sourceMappingURL=schema-projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-projection.d.ts","sourceRoot":"","sources":["../src/schema-projection.ts"],"names":[],"mappings":"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"}