@telorun/analyzer 0.62.1 → 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.
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +6 -0
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +343 -13
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +2 -7
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/import-resolution-diagnostics.d.ts +9 -2
- package/dist/import-resolution-diagnostics.d.ts.map +1 -1
- package/dist/import-resolution-diagnostics.js +26 -7
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/invocation-contract.d.ts +6 -1
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +27 -4
- package/dist/loaded-types.d.ts +12 -3
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts +0 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +62 -41
- package/dist/manifest-schemas.d.ts +699 -0
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +306 -14
- package/dist/module-compatibility.d.ts +40 -0
- package/dist/module-compatibility.d.ts.map +1 -0
- package/dist/module-compatibility.js +52 -0
- package/dist/ref-slot-reading.d.ts +30 -0
- package/dist/ref-slot-reading.d.ts.map +1 -0
- package/dist/ref-slot-reading.js +139 -0
- package/dist/referrer-rule.d.ts +67 -0
- package/dist/referrer-rule.d.ts.map +1 -0
- package/dist/referrer-rule.js +96 -0
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +1 -1
- package/dist/release/version-stamp.d.ts +16 -0
- package/dist/release/version-stamp.d.ts.map +1 -1
- package/dist/release/version-stamp.js +52 -0
- package/dist/release/workspace-config.d.ts +10 -6
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +10 -6
- package/dist/resolve-schema-ref-kinds.d.ts +4 -3
- package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
- package/dist/resolve-schema-ref-kinds.js +16 -0
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +4 -3
- package/dist/resolve-zone-containment.d.ts +99 -0
- package/dist/resolve-zone-containment.d.ts.map +1 -0
- package/dist/resolve-zone-containment.js +212 -0
- package/dist/resolve-zone-requirements.d.ts +5 -0
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +48 -7
- package/dist/resource-rule.d.ts +106 -0
- package/dist/resource-rule.d.ts.map +1 -0
- package/dist/resource-rule.js +236 -0
- package/dist/rule-condition.d.ts +50 -0
- package/dist/rule-condition.d.ts.map +1 -0
- package/dist/rule-condition.js +108 -0
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +20 -0
- package/dist/schema-projection.d.ts +171 -0
- package/dist/schema-projection.d.ts.map +1 -0
- package/dist/schema-projection.js +317 -0
- package/dist/step-slot.d.ts +39 -0
- package/dist/step-slot.d.ts.map +1 -0
- package/dist/step-slot.js +49 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +17 -3
- package/dist/validate-durable-regions.d.ts +15 -0
- package/dist/validate-durable-regions.d.ts.map +1 -0
- package/dist/validate-durable-regions.js +336 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +44 -2
- package/dist/validate-referrer-rules.d.ts +120 -0
- package/dist/validate-referrer-rules.d.ts.map +1 -0
- package/dist/validate-referrer-rules.js +255 -0
- package/dist/validate-resource-rules.d.ts +114 -0
- package/dist/validate-resource-rules.d.ts.map +1 -0
- package/dist/validate-resource-rules.js +283 -0
- package/dist/validate-schema-projection.d.ts +32 -0
- package/dist/validate-schema-projection.d.ts.map +1 -0
- package/dist/validate-schema-projection.js +110 -0
- package/dist/validate-step-inputs.d.ts +2 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +5 -3
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +11 -5
- package/dist/validate-zone-slots.d.ts +2 -2
- package/dist/validate-zone-slots.d.ts.map +1 -1
- package/dist/validate-zone-slots.js +202 -6
- package/dist/validate-zone-violations.d.ts +12 -0
- package/dist/validate-zone-violations.d.ts.map +1 -0
- package/dist/validate-zone-violations.js +169 -0
- package/dist/value-type-keyword.d.ts +1 -1
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +4 -0
- package/dist/zone-slot.d.ts +61 -0
- package/dist/zone-slot.d.ts.map +1 -1
- package/dist/zone-slot.js +86 -5
- package/package.json +2 -2
- package/src/analysis-registry.ts +6 -0
- package/src/analyzer.ts +395 -14
- package/src/call-graph.ts +2 -8
- package/src/flatten-for-analyzer.ts +1 -1
- package/src/import-resolution-diagnostics.ts +25 -7
- package/src/index.ts +73 -0
- package/src/invocation-contract.ts +45 -2
- package/src/loaded-types.ts +12 -3
- package/src/manifest-loader.ts +87 -55
- package/src/manifest-schemas.ts +328 -14
- package/src/module-compatibility.ts +72 -0
- package/src/ref-slot-reading.ts +156 -0
- package/src/referrer-rule.ts +116 -0
- package/src/release/index.ts +1 -0
- package/src/release/version-stamp.ts +58 -0
- package/src/release/workspace-config.ts +10 -6
- package/src/resolve-schema-ref-kinds.ts +28 -4
- package/src/resolve-throws-union.ts +4 -3
- package/src/resolve-zone-containment.ts +329 -0
- package/src/resolve-zone-requirements.ts +67 -7
- package/src/resource-rule.ts +252 -0
- package/src/rule-condition.ts +126 -0
- package/src/schema-keywords.ts +24 -0
- package/src/schema-projection.ts +403 -0
- package/src/step-slot.ts +64 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-cel-context.ts +17 -3
- package/src/validate-durable-regions.ts +401 -0
- package/src/validate-references.ts +57 -2
- package/src/validate-referrer-rules.ts +373 -0
- package/src/validate-resource-rules.ts +401 -0
- package/src/validate-schema-projection.ts +184 -0
- package/src/validate-step-inputs.ts +5 -3
- package/src/validate-throws-coverage.ts +12 -5
- package/src/validate-zone-slots.ts +192 -8
- package/src/validate-zone-violations.ts +190 -0
- package/src/value-type-keyword.ts +4 -0
- package/src/zone-slot.ts +114 -5
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { propertySchemas, resolveLocalRef } from "./manifest-navigation.js";
|
|
2
|
+
import { isStepSlot } from "./step-slot.js";
|
|
3
|
+
import { readProvidesZone } from "./zone-slot.js";
|
|
4
|
+
const moduleOf = (node) => node.manifest.metadata?.module;
|
|
5
|
+
/** The schema node at a field-map path, following `[]` into `items`, `{}` into
|
|
6
|
+
* `additionalProperties` and local `$defs` refs. Same navigation the projection
|
|
7
|
+
* performs — a slot's annotations live wherever this lands. */
|
|
8
|
+
function schemaNodeAt(rootSchema, slotPath) {
|
|
9
|
+
if (!rootSchema)
|
|
10
|
+
return undefined;
|
|
11
|
+
let current = rootSchema;
|
|
12
|
+
for (const segment of slotPath.split(".")) {
|
|
13
|
+
if (!current)
|
|
14
|
+
return undefined;
|
|
15
|
+
const bare = segment.replace(/(\[\]|\{\})+$/g, "");
|
|
16
|
+
let next = propertySchemas(current).find(([k]) => k === bare)?.[1];
|
|
17
|
+
for (const marker of segment.slice(bare.length).match(/\[\]|\{\}/g) ?? []) {
|
|
18
|
+
next = resolveLocalRef(marker === "[]"
|
|
19
|
+
? next?.items
|
|
20
|
+
: next?.additionalProperties, rootSchema);
|
|
21
|
+
if (!next || typeof next !== "object")
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
current = resolveLocalRef(next, rootSchema);
|
|
25
|
+
}
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Does a zone's lifetime extend through this edge?
|
|
30
|
+
*
|
|
31
|
+
* EVERY member of `use` must be `call`. This is the landed reduction, and its
|
|
32
|
+
* asymmetry with the propagation rule is deliberate: a set says several
|
|
33
|
+
* relations hold at once, so a slot declaring `[call, detached]` really does
|
|
34
|
+
* detach on some dispatch, and a detached dispatch is never inside the caller's
|
|
35
|
+
* zone. An edge whose `use` could not be read extends nothing either — the
|
|
36
|
+
* conservative direction HERE, since over-reporting containment would invent
|
|
37
|
+
* failures inside regions that are correct.
|
|
38
|
+
*/
|
|
39
|
+
function extendsZone(edge) {
|
|
40
|
+
return edge.use.length > 0 && !edge.unresolved && edge.use.every((u) => u === "call");
|
|
41
|
+
}
|
|
42
|
+
/** Uses on an edge that leave the region, for the boundary record. */
|
|
43
|
+
function escapingUses(edge) {
|
|
44
|
+
return edge.use.filter((u) => u !== "call" && u !== "dependency" && u !== "schema");
|
|
45
|
+
}
|
|
46
|
+
/** Walk down from one entry edge, collecting what the zone reaches. */
|
|
47
|
+
function collect(graph, entries, contents, boundaries) {
|
|
48
|
+
const queue = [...entries];
|
|
49
|
+
while (queue.length > 0) {
|
|
50
|
+
const current = queue.shift();
|
|
51
|
+
if (contents.has(current.node.id))
|
|
52
|
+
continue;
|
|
53
|
+
contents.set(current.node.id, { node: current.node, via: current.via });
|
|
54
|
+
// A resource's own step nodes are part of whatever region reaches the
|
|
55
|
+
// resource: a step is where a check anchors (`the retry at
|
|
56
|
+
// importAll/fetch`), and its outgoing edge is the dispatch a rule judges.
|
|
57
|
+
const stepNodes = current.node.type === "resource" ? graph.steps(current.node.id) : [];
|
|
58
|
+
for (const step of stepNodes) {
|
|
59
|
+
if (contents.has(step.id))
|
|
60
|
+
continue;
|
|
61
|
+
queue.push({
|
|
62
|
+
node: step,
|
|
63
|
+
via: [...current.via, step.name ? `step '${step.name}'` : step.path],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const owner = current.node.type === "resource"
|
|
67
|
+
? current.node
|
|
68
|
+
: graph.nodes.get(current.node.owner);
|
|
69
|
+
for (const edge of graph.edgesFrom(current.node.id)) {
|
|
70
|
+
if (edge.use.every((u) => u === "dependency" || u === "schema"))
|
|
71
|
+
continue;
|
|
72
|
+
const target = edge.to ? graph.nodes.get(edge.to) : undefined;
|
|
73
|
+
if (!extendsZone(edge)) {
|
|
74
|
+
if (owner && escapingUses(edge).length > 0) {
|
|
75
|
+
boundaries.push({
|
|
76
|
+
edge,
|
|
77
|
+
from: owner,
|
|
78
|
+
via: current.via,
|
|
79
|
+
escaping: escapingUses(edge),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!target)
|
|
85
|
+
continue;
|
|
86
|
+
queue.push({
|
|
87
|
+
node: target,
|
|
88
|
+
via: [...current.via, `${edge.slot} → ${edge.toName}`],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Every region in the graph opened by a slot declaring `attribute`.
|
|
95
|
+
*
|
|
96
|
+
* The provider itself is NOT in `contents` — a zone constrains what runs inside
|
|
97
|
+
* its body, not the resource that establishes it. A transaction's own
|
|
98
|
+
* `connection:` dependency is outside the region it opens, and a rule that
|
|
99
|
+
* treated the provider as contained would report the provider against its own
|
|
100
|
+
* constraint.
|
|
101
|
+
*/
|
|
102
|
+
export function findZoneRegions(graph, resolveDef, attribute) {
|
|
103
|
+
const regions = [];
|
|
104
|
+
for (const node of graph.nodes.values()) {
|
|
105
|
+
if (node.type !== "resource")
|
|
106
|
+
continue;
|
|
107
|
+
const def = resolveDef(node.kind, moduleOf(node));
|
|
108
|
+
const rootSchema = def?.schema;
|
|
109
|
+
if (!rootSchema)
|
|
110
|
+
continue;
|
|
111
|
+
// The providing slots of this kind, found by walking its own declared
|
|
112
|
+
// properties rather than by knowing any kind's field names.
|
|
113
|
+
for (const [slot, slotSchema] of providingSlots(rootSchema)) {
|
|
114
|
+
const provides = readProvidesZone(slotSchema);
|
|
115
|
+
const reason = provides?.attributes[attribute];
|
|
116
|
+
if (!provides || typeof reason !== "string")
|
|
117
|
+
continue;
|
|
118
|
+
const contents = new Map();
|
|
119
|
+
const boundaries = [];
|
|
120
|
+
// Shape one: the slot carries a step array natively. Its steps are the
|
|
121
|
+
// body, and they are owned by this resource, so they are found by path
|
|
122
|
+
// rather than by an edge.
|
|
123
|
+
if (isStepSlot(slotSchema)) {
|
|
124
|
+
const entries = graph
|
|
125
|
+
.steps(node.id)
|
|
126
|
+
.filter((step) => step.array === slot || step.array.startsWith(`${slot}[`))
|
|
127
|
+
.map((step) => ({
|
|
128
|
+
node: step,
|
|
129
|
+
via: [step.name ? `step '${step.name}'` : step.path],
|
|
130
|
+
}));
|
|
131
|
+
collect(graph, entries, contents, boundaries);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Shape two: the slot references an executable. Its edges are the body.
|
|
135
|
+
// Entered regardless of the slot's OWN use — a providing slot
|
|
136
|
+
// establishes its zone before the enclosing lifetime terminates on that
|
|
137
|
+
// use, which is what lets a detached durable body both shed every
|
|
138
|
+
// enclosing zone and open its own.
|
|
139
|
+
const entries = graph
|
|
140
|
+
.edgesFrom(node.id)
|
|
141
|
+
.filter((edge) => edge.slot === slot && edge.to)
|
|
142
|
+
.map((edge) => ({
|
|
143
|
+
node: graph.nodes.get(edge.to),
|
|
144
|
+
via: [`${slot} → ${edge.toName}`],
|
|
145
|
+
}))
|
|
146
|
+
.filter((entry) => entry.node !== undefined);
|
|
147
|
+
collect(graph, entries, contents, boundaries);
|
|
148
|
+
}
|
|
149
|
+
regions.push({
|
|
150
|
+
attribute,
|
|
151
|
+
reason,
|
|
152
|
+
provider: node,
|
|
153
|
+
slot,
|
|
154
|
+
attributes: provides.attributes,
|
|
155
|
+
contents,
|
|
156
|
+
boundaries,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return regions;
|
|
161
|
+
}
|
|
162
|
+
/** Field-map paths of every slot in a kind's schema carrying a provides-zone
|
|
163
|
+
* annotation, with the schema node at each. Walks properties, array items and
|
|
164
|
+
* `additionalProperties`, resolving local `$ref`s — the paths the call graph's
|
|
165
|
+
* own edges are keyed by, so a slot found here matches an edge's `slot`. */
|
|
166
|
+
function providingSlots(rootSchema) {
|
|
167
|
+
const found = [];
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
const walk = (schema, path) => {
|
|
170
|
+
const node = resolveLocalRef(schema, rootSchema);
|
|
171
|
+
if (!node || typeof node !== "object" || seen.has(node))
|
|
172
|
+
return;
|
|
173
|
+
seen.add(node);
|
|
174
|
+
if (path && readProvidesZone(node))
|
|
175
|
+
found.push([path, node]);
|
|
176
|
+
for (const [key, child] of propertySchemas(node)) {
|
|
177
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
178
|
+
walk(child, childPath);
|
|
179
|
+
// A slot's zone annotation may sit on the ARRAY (`steps`) or on its item
|
|
180
|
+
// (`routes[].handler`); both are real field-map paths.
|
|
181
|
+
const items = resolveLocalRef(child?.items, rootSchema);
|
|
182
|
+
if (items)
|
|
183
|
+
walk(items, `${childPath}[]`);
|
|
184
|
+
const additional = resolveLocalRef(child?.additionalProperties, rootSchema);
|
|
185
|
+
if (additional)
|
|
186
|
+
walk(additional, `${childPath}{}`);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
walk(rootSchema, "");
|
|
190
|
+
return found;
|
|
191
|
+
}
|
|
192
|
+
/** Convenience for a consumer that only wants membership: every node id inside
|
|
193
|
+
* any region opened by `attribute`, mapped to the region that contains it. */
|
|
194
|
+
export function containmentIndex(regions) {
|
|
195
|
+
const index = new Map();
|
|
196
|
+
for (const region of regions) {
|
|
197
|
+
for (const id of region.contents.keys()) {
|
|
198
|
+
if (!index.has(id))
|
|
199
|
+
index.set(id, region);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return index;
|
|
203
|
+
}
|
|
204
|
+
/** The manifests a region's contents belong to — what a diagnostic anchors on. */
|
|
205
|
+
export function regionManifests(region) {
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const { node } of region.contents.values()) {
|
|
208
|
+
if (node.type === "resource")
|
|
209
|
+
out.push(node.manifest);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
@@ -35,6 +35,11 @@ export interface ZoneRequirementSpec {
|
|
|
35
35
|
* check compares against `exports.resources`. */
|
|
36
36
|
correlationName?: string;
|
|
37
37
|
reason?: string;
|
|
38
|
+
/** Attributes the satisfying zone must declare. Carried across the export
|
|
39
|
+
* boundary with the rest of the requirement: a consumer discharging a
|
|
40
|
+
* library's open requirement must meet the same guarantee the library's own
|
|
41
|
+
* internals would have. */
|
|
42
|
+
attributes?: string[];
|
|
38
43
|
/** Label of the requiring resource (`sql.Command 'charge'`). */
|
|
39
44
|
origin: string;
|
|
40
45
|
/** Resource names on the propagation path so far, origin first. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-zone-requirements.d.ts","sourceRoot":"","sources":["../src/resolve-zone-requirements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAEL,KAAK,SAAS,EAEd,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAMnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;uCAEuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb;;wBAEoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;sDACkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED,6EAA6E;AAC7E,MAAM,MAAM,sBAAsB,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;AAExE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,sBAAsB,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-zone-requirements.d.ts","sourceRoot":"","sources":["../src/resolve-zone-requirements.ts"],"names":[],"mappings":"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"}
|
|
@@ -232,6 +232,7 @@ export function projectZoneRequirements(args) {
|
|
|
232
232
|
if (!bucket.some((r) => `${r.zone}\0${r.correlation ?? ""}` === req.key)) {
|
|
233
233
|
bucket.push({
|
|
234
234
|
zone: req.zone,
|
|
235
|
+
...(req.attributes.length > 0 ? { attributes: req.attributes } : {}),
|
|
235
236
|
correlation: req.correlation,
|
|
236
237
|
correlationLabel: req.correlationLabel,
|
|
237
238
|
correlationName: req.correlationName,
|
|
@@ -255,16 +256,44 @@ export function projectZoneRequirements(args) {
|
|
|
255
256
|
// site. Checked BEFORE termination, so a terminating provider slot
|
|
256
257
|
// (a detached durable body) still discharges its own zone.
|
|
257
258
|
const callerDef = resolveDef(caller.kind, moduleOf(caller));
|
|
258
|
-
|
|
259
|
+
// WHICH slot establishes the zone depends on the body's SHAPE, and both
|
|
260
|
+
// shapes are real. A ref body annotates the slot the edge itself names
|
|
261
|
+
// (`Sql.Transaction.steps`, holding a `!ref` to an executable). A NATIVE
|
|
262
|
+
// body annotates the step ARRAY, while the edge comes from a step inside
|
|
263
|
+
// it and names that step's own `invoke:` — so looking only at `edge.slot`
|
|
264
|
+
// finds no annotation and the enclosing zone never discharges anything.
|
|
265
|
+
// Handling one shape would make every requirement inside a natively-bodied
|
|
266
|
+
// zone unsatisfiable, which is exactly what a durable workflow's body is.
|
|
267
|
+
const bodySlot = from.type === "step" ? from.array.replace(/\[\d+\].*$/, "") : edge.slot;
|
|
268
|
+
const slotSchema = schemaNodeAt(callerDef?.schema, bodySlot);
|
|
259
269
|
const provides = readProvidesZone(slotSchema);
|
|
260
270
|
if (provides && callerDef && req.accepted.has(canonicalOf(callerDef))) {
|
|
271
|
+
// A native body's annotation sits on the array, whose enclosing object
|
|
272
|
+
// IS the resource root — so its correlation pointer anchors there,
|
|
273
|
+
// rather than at the step item the edge happens to come from.
|
|
261
274
|
const providerKey = provides.key
|
|
262
|
-
?
|
|
275
|
+
? from.type === "step"
|
|
276
|
+
? resolveStaticKey(caller, [provides.key], resolveName)
|
|
277
|
+
: resolveProviderKey(caller, edge, provides.key, resolveName)
|
|
263
278
|
: undefined;
|
|
264
279
|
const discharged = req.correlation === undefined ||
|
|
265
280
|
(providerKey !== undefined && correlationIdOf(providerKey) === req.correlation);
|
|
266
|
-
if (discharged)
|
|
281
|
+
if (discharged) {
|
|
282
|
+
// The zone is the right KIND but may not make the promise the
|
|
283
|
+
// requirement is built on. Reported here rather than at the providing
|
|
284
|
+
// module, because this is the only point where both halves are in
|
|
285
|
+
// hand: the requirement's demanded attributes and the slot that would
|
|
286
|
+
// satisfy it. The requirement still discharges — the zone IS open, so
|
|
287
|
+
// reporting it unsatisfied as well would name the same defect twice
|
|
288
|
+
// with opposite words.
|
|
289
|
+
const missing = req.attributes.filter((a) => !(a in provides.attributes));
|
|
290
|
+
if (missing.length > 0) {
|
|
291
|
+
emit(DiagnosticSeverity.Error, "ZONE_ATTRIBUTE_MISSING", edge, caller, req, via, `reaches a ${canonicalOf(callerDef)} zone that does not declare ` +
|
|
292
|
+
`${missing.join(", ")}, so the guarantee this requirement is built on ` +
|
|
293
|
+
`does not hold inside it`);
|
|
294
|
+
}
|
|
267
295
|
continue;
|
|
296
|
+
}
|
|
268
297
|
}
|
|
269
298
|
// A dynamic selector is already a hard diagnostic (validate-ref-slots);
|
|
270
299
|
// zones do not re-report it — and must not propagate through a use they
|
|
@@ -352,14 +381,24 @@ export function projectZoneRequirements(args) {
|
|
|
352
381
|
continue;
|
|
353
382
|
const def = resolveDef(node.kind, meta?.module);
|
|
354
383
|
const schema = def?.schema;
|
|
355
|
-
if (!def || !schema
|
|
384
|
+
if (!def || !schema)
|
|
356
385
|
continue;
|
|
357
|
-
|
|
358
|
-
|
|
386
|
+
// A requirement at the SCHEMA ROOT is unconditional: every instance of the
|
|
387
|
+
// kind must be inside the zone, whatever it is configured with. A parking
|
|
388
|
+
// kind is exactly that shape — it has no option that turns the requirement
|
|
389
|
+
// on — and a field-level annotation would have to be attached to some
|
|
390
|
+
// arbitrary property and would then read "…when you set this one".
|
|
391
|
+
const slots = [[schema, undefined]];
|
|
392
|
+
for (const [field, propSchema] of Object.entries((schema.properties ?? {}))) {
|
|
393
|
+
slots.push([propSchema, field]);
|
|
394
|
+
}
|
|
395
|
+
for (const [slotSchema, field] of slots) {
|
|
396
|
+
const requires = readRequiresZone(slotSchema);
|
|
359
397
|
if (!requires)
|
|
360
398
|
continue;
|
|
361
|
-
if (node.manifest[field] === undefined)
|
|
399
|
+
if (field !== undefined && node.manifest[field] === undefined) {
|
|
362
400
|
continue;
|
|
401
|
+
}
|
|
363
402
|
const zoneDef = resolveDef(requires.zone, def.metadata.module);
|
|
364
403
|
if (!zoneDef)
|
|
365
404
|
continue; // ZONE_PROVIDER_UNRESOLVED is reported at registration
|
|
@@ -368,6 +407,7 @@ export function projectZoneRequirements(args) {
|
|
|
368
407
|
const req = {
|
|
369
408
|
zone,
|
|
370
409
|
accepted: acceptedFor(zone),
|
|
410
|
+
attributes: requires.attributes,
|
|
371
411
|
correlation: target ? correlationIdOf(target) : undefined,
|
|
372
412
|
correlationLabel: target ? labelOf(target) : undefined,
|
|
373
413
|
correlationName: target?.name,
|
|
@@ -396,6 +436,7 @@ export function projectZoneRequirements(args) {
|
|
|
396
436
|
const req = {
|
|
397
437
|
zone: spec.zone,
|
|
398
438
|
accepted: zoneDef ? acceptedFor(spec.zone) : new Set([spec.zone]),
|
|
439
|
+
attributes: spec.attributes ?? [],
|
|
399
440
|
correlation: spec.correlation,
|
|
400
441
|
correlationLabel: spec.correlationLabel,
|
|
401
442
|
correlationName: spec.correlationName,
|
|
@@ -0,0 +1,106 @@
|
|
|
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 declare const RESOURCE_RULES_ANNOTATION = "x-telo-resource-rules";
|
|
25
|
+
export type ResourceRuleSeverity = "error" | "warning";
|
|
26
|
+
export interface ResourceRule {
|
|
27
|
+
/** JSON Pointer to the collection to iterate. Absent = the whole-resource
|
|
28
|
+
* form, which reports at the resource rather than at an element. */
|
|
29
|
+
readonly in?: string;
|
|
30
|
+
/** CEL source. TRUE when the rule holds. */
|
|
31
|
+
readonly condition: string;
|
|
32
|
+
/** The rule's own name, carried in `data.rule`. Never a diagnostic code —
|
|
33
|
+
* every violation reports under the analyzer-owned envelope. */
|
|
34
|
+
readonly code: string;
|
|
35
|
+
readonly message: string;
|
|
36
|
+
readonly severity: ResourceRuleSeverity;
|
|
37
|
+
/** Position in the annotation array — the anchor for a declaration defect. */
|
|
38
|
+
readonly index: number;
|
|
39
|
+
}
|
|
40
|
+
/** One element a rule is evaluated against. */
|
|
41
|
+
export interface RuleSubject {
|
|
42
|
+
/** Dotted/bracketed path from the resource root, for `data.path`. */
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly value: unknown;
|
|
45
|
+
/** Present when the collection is a map: the entry's key. */
|
|
46
|
+
readonly key?: string;
|
|
47
|
+
}
|
|
48
|
+
/** A precompiled `!cel` node keeps its author-written text on `source`; a plain
|
|
49
|
+
* string is taken verbatim so a rule reads the same however the loader was
|
|
50
|
+
* configured (a round-trip view runs with `compile` off). */
|
|
51
|
+
export declare function celSourceOf(value: unknown): string | undefined;
|
|
52
|
+
/** The annotation exactly as written, for the strict half. `undefined` when the
|
|
53
|
+
* kind declares none; a non-array is returned as-is so the shape can be
|
|
54
|
+
* reported rather than silently skipped. */
|
|
55
|
+
export declare function readRawResourceRules(schema: unknown): unknown;
|
|
56
|
+
/** Every rule this kind declares that is well-formed enough to run. */
|
|
57
|
+
export declare function readResourceRules(schema: unknown): ResourceRule[];
|
|
58
|
+
/** Split a JSON Pointer into its unescaped segments. `/` alone is the root. */
|
|
59
|
+
export declare function pointerSegments(pointer: string): string[] | undefined;
|
|
60
|
+
/** Navigate a pointer within a value, stopping at anything that is not a plain
|
|
61
|
+
* container. Returns `undefined` for a pointer that does not resolve — which a
|
|
62
|
+
* resource legitimately produces by omitting an optional collection. */
|
|
63
|
+
export declare function resolvePointer(value: unknown, pointer: string): unknown;
|
|
64
|
+
/** Path prefix for a pointer, in the dotted/bracketed spelling diagnostics use
|
|
65
|
+
* (`foreignKeys[0].references`), so `data.path` is one grammar throughout. */
|
|
66
|
+
export declare function pointerToPath(pointer: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* The elements a rule iterates. An array yields one subject per item, a map one
|
|
69
|
+
* per entry with its key bound; a collection that is absent yields none, which
|
|
70
|
+
* is a rule that had nothing to say rather than a rule that failed.
|
|
71
|
+
*
|
|
72
|
+
* A scalar at the pointer is `undefined` — not an empty list — because that is a
|
|
73
|
+
* declaration defect the strict half reports, and an empty list would hide it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveRuleSubjects(config: unknown, pointer: string): RuleSubject[] | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Path of the first CEL leaf inside a value, or `undefined` when every leaf is
|
|
78
|
+
* literal. A rule reading an expression would be evaluating a placeholder, so
|
|
79
|
+
* the subject is skipped — and the skip is reported, never silent.
|
|
80
|
+
*
|
|
81
|
+
* Stops at nested inline `{ kind }` declarations for the reason every other walk
|
|
82
|
+
* does: that CEL belongs to the nested kind, evaluated in its own scope.
|
|
83
|
+
*/
|
|
84
|
+
export declare function findDynamicLeaf(value: unknown, base?: string): string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* The nodes a condition actually READS, resolved against the resource and the
|
|
87
|
+
* element under test — the input to the dynamic-leaf check.
|
|
88
|
+
*
|
|
89
|
+
* Scanning the whole subject instead would make the check useless in exactly
|
|
90
|
+
* the shape it matters most: a resource-wide rule takes the whole resource as
|
|
91
|
+
* its subject, so one unrelated `!cel` anywhere (a `version:` read from
|
|
92
|
+
* `module.version`, which is the conventional spelling) would switch off every
|
|
93
|
+
* such rule. Chains are the same primitive the binding-order derivation uses:
|
|
94
|
+
* parsed, never lexed, so a name inside a string literal reads nothing.
|
|
95
|
+
*
|
|
96
|
+
* A chain stops at a computed index (`INDEX_SEGMENT`): what it selects is not
|
|
97
|
+
* known statically, so the node reached so far is what gets checked — the
|
|
98
|
+
* over-approximating direction, which errs toward skipping rather than toward
|
|
99
|
+
* evaluating a placeholder.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readNodes(chains: readonly (readonly string[])[],
|
|
102
|
+
/** The bindings in scope, by name — `self`/`this`/`key` for a resource rule,
|
|
103
|
+
* `self`/`referrer` for a referrer rule. A chain rooted at a name that is not
|
|
104
|
+
* bound reads nothing. */
|
|
105
|
+
roots: Record<string, unknown>): unknown[];
|
|
106
|
+
//# sourceMappingURL=resource-rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-rule.d.ts","sourceRoot":"","sources":["../src/resource-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B;yEACqE;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;qEACiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAeD;;8DAE8D;AAC9D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI9D;AAED;;6CAE6C;AAC7C,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAG7D;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,EAAE,CA0BjE;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAOrE;AAED;;yEAEyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBvE;AAED;+EAC+E;AAC/E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,GAAG,SAAS,CAe3B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,MAAM,GAAG,SAAS,CAiB7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE;AACtC;;2BAE2B;AAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,EAAE,CAqBX"}
|