@telorun/analyzer 0.52.0 → 0.54.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 +8 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +21 -3
- package/dist/analyzer.d.ts +3 -2
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +193 -26
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +32 -12
- package/dist/call-graph.d.ts +189 -0
- package/dist/call-graph.d.ts.map +1 -0
- package/dist/call-graph.js +617 -0
- package/dist/dependency-graph.d.ts +17 -7
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +36 -65
- package/dist/flatten-for-analyzer.d.ts +8 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +32 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/manifest-navigation.d.ts +32 -0
- package/dist/manifest-navigation.d.ts.map +1 -0
- package/dist/manifest-navigation.js +91 -0
- package/dist/manifest-visitor.js +1 -1
- package/dist/ref-slot.d.ts +125 -0
- package/dist/ref-slot.d.ts.map +1 -0
- package/dist/ref-slot.js +226 -0
- package/dist/reference-field-map.d.ts +15 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +29 -35
- package/dist/resolve-schema-ref-kinds.d.ts +4 -0
- package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
- package/dist/resolve-schema-ref-kinds.js +31 -8
- package/dist/resolve-zone-requirements.d.ts +110 -0
- package/dist/resolve-zone-requirements.d.ts.map +1 -0
- package/dist/resolve-zone-requirements.js +541 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-module-metadata.d.ts +38 -0
- package/dist/validate-module-metadata.d.ts.map +1 -0
- package/dist/validate-module-metadata.js +256 -0
- package/dist/validate-observed-state.d.ts +14 -13
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +21 -88
- package/dist/validate-ref-slots.d.ts +48 -0
- package/dist/validate-ref-slots.d.ts.map +1 -0
- package/dist/validate-ref-slots.js +219 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +8 -1
- package/dist/validate-zone-slots.d.ts +39 -0
- package/dist/validate-zone-slots.d.ts.map +1 -0
- package/dist/validate-zone-slots.js +114 -0
- package/dist/zone-module-documents.d.ts +27 -0
- package/dist/zone-module-documents.d.ts.map +1 -0
- package/dist/zone-module-documents.js +1 -0
- package/dist/zone-slot.d.ts +61 -0
- package/dist/zone-slot.d.ts.map +1 -0
- package/dist/zone-slot.js +91 -0
- package/package.json +3 -3
- package/src/analysis-registry.ts +20 -2
- package/src/analyzer.ts +211 -24
- package/src/builtins.ts +32 -12
- package/src/call-graph.ts +827 -0
- package/src/dependency-graph.ts +34 -68
- package/src/flatten-for-analyzer.ts +32 -0
- package/src/index.ts +51 -0
- package/src/manifest-navigation.ts +91 -0
- package/src/manifest-visitor.ts +1 -1
- package/src/ref-slot.ts +273 -0
- package/src/reference-field-map.ts +39 -36
- package/src/resolve-schema-ref-kinds.ts +34 -7
- package/src/resolve-zone-requirements.ts +781 -0
- package/src/types.ts +8 -0
- package/src/validate-module-metadata.ts +335 -0
- package/src/validate-observed-state.ts +26 -92
- package/src/validate-ref-slots.ts +293 -0
- package/src/validate-references.ts +8 -1
- package/src/validate-zone-slots.ts +175 -0
- package/src/zone-module-documents.ts +27 -0
- package/src/zone-slot.ts +116 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
+
import { buildCallGraph, } from "./call-graph.js";
|
|
3
|
+
import { enclosingOf, propertySchemas, resolveLocalRef, } from "./manifest-navigation.js";
|
|
4
|
+
import { readProvidesZone, readRequiresZone } from "./zone-slot.js";
|
|
5
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
6
|
+
const SOURCE = "telo-analyzer";
|
|
7
|
+
/** Resolve a possibly alias-form kind to its definition in the DECLARING
|
|
8
|
+
* module's scope — the same layering `AnalysisRegistry.resolveDefinitionIn`
|
|
9
|
+
* uses. */
|
|
10
|
+
function definitionResolver(defs, aliases, aliasesByModule) {
|
|
11
|
+
return (kind, module) => {
|
|
12
|
+
const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
|
|
13
|
+
const canonical = scope.resolveKind(kind);
|
|
14
|
+
return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const canonicalOf = (def) => `${def.metadata.module}.${def.metadata.name}`;
|
|
18
|
+
/**
|
|
19
|
+
* Correlation identity — the resolved DECLARATION SITE, mirroring runtime
|
|
20
|
+
* instance identity exactly. A named module-level resource is
|
|
21
|
+
* `(declaring file, name)` — the file, not the owning module name, because a
|
|
22
|
+
* re-exported instance is forwarded once per re-exporting module under that
|
|
23
|
+
* module's name while its declaration site survives the copy. A
|
|
24
|
+
* `with:`-scoped resource is its scope site plus name (one instance per scope
|
|
25
|
+
* run); an inline declaration is its own generated node.
|
|
26
|
+
*/
|
|
27
|
+
function correlationIdOf(node) {
|
|
28
|
+
if (node.scoped)
|
|
29
|
+
return `${node.scopeOwner}\0${node.scopeSite}\0${node.name}`;
|
|
30
|
+
const source = node.manifest.metadata?.source ?? "";
|
|
31
|
+
return `${source}\0${node.name}`;
|
|
32
|
+
}
|
|
33
|
+
const labelOf = (node) => `${node.kind} '${node.name}'`;
|
|
34
|
+
/** The schema node at a field-map path (`steps`, `routes[].handler`), following
|
|
35
|
+
* `[]` into `items`, `{}` into `additionalProperties` and local `$defs` refs —
|
|
36
|
+
* where a slot's zone annotations live. */
|
|
37
|
+
function schemaNodeAt(rootSchema, slotPath) {
|
|
38
|
+
if (!rootSchema)
|
|
39
|
+
return undefined;
|
|
40
|
+
let current = rootSchema;
|
|
41
|
+
for (const segment of slotPath.split(".")) {
|
|
42
|
+
if (!current)
|
|
43
|
+
return undefined;
|
|
44
|
+
const bare = segment.replace(/(\[\]|\{\})+$/g, "");
|
|
45
|
+
let next = propertySchemas(current).find(([k]) => k === bare)?.[1];
|
|
46
|
+
for (const marker of segment.slice(bare.length).match(/\[\]|\{\}/g) ?? []) {
|
|
47
|
+
next = resolveLocalRef(marker === "[]"
|
|
48
|
+
? next?.items
|
|
49
|
+
: next?.additionalProperties, rootSchema);
|
|
50
|
+
if (!next || typeof next !== "object")
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
current = resolveLocalRef(next, rootSchema);
|
|
54
|
+
}
|
|
55
|
+
return current;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The LOCAL name a correlation-key hop resolves to, or undefined when the value
|
|
59
|
+
* is not a reference or does not name something local.
|
|
60
|
+
*
|
|
61
|
+
* **A cross-module reference is undefined, deliberately**, matching the kernel's
|
|
62
|
+
* `referencedName` exactly: `!ref Alias.name` names an instance in another
|
|
63
|
+
* module's scope, and the only index available here is flat and
|
|
64
|
+
* module-unscoped, so taking the bare name would bind to whatever local
|
|
65
|
+
* resource happens to share it. Correlation is an identity comparison — binding
|
|
66
|
+
* it to the wrong resource is worse than leaving it uncorrelated, which is the
|
|
67
|
+
* under-approximating direction the whole pass leans on. `Self.` is a local
|
|
68
|
+
* name written the long way and does resolve.
|
|
69
|
+
*
|
|
70
|
+
* This deliberately differs from `call-graph`'s `refTargetName`, which answers a
|
|
71
|
+
* different question (what an EDGE points at, cross-module included, for a graph
|
|
72
|
+
* whose consumers tolerate an unresolved target) — hence two functions rather
|
|
73
|
+
* than one shared helper.
|
|
74
|
+
*/
|
|
75
|
+
function refName(value) {
|
|
76
|
+
if (isRefSentinel(value)) {
|
|
77
|
+
const source = value.source;
|
|
78
|
+
const dot = source.indexOf(".");
|
|
79
|
+
if (dot <= 0)
|
|
80
|
+
return source;
|
|
81
|
+
return source.slice(0, dot) === "Self" ? source.slice(dot + 1) : undefined;
|
|
82
|
+
}
|
|
83
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
84
|
+
return undefined;
|
|
85
|
+
const v = value;
|
|
86
|
+
const pure = typeof v.kind === "string" &&
|
|
87
|
+
typeof v.name === "string" &&
|
|
88
|
+
Object.keys(v).every((k) => k === "kind" || k === "name" || k === "alias" || k === "__ref");
|
|
89
|
+
if (!pure)
|
|
90
|
+
return undefined;
|
|
91
|
+
const alias = v.alias;
|
|
92
|
+
if (typeof alias === "string" && alias !== "Self")
|
|
93
|
+
return undefined;
|
|
94
|
+
return v.name;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve an ordered correlation-key pointer list against a resource's
|
|
98
|
+
* manifest, first hit winning — the static counterpart of the kernel's walk.
|
|
99
|
+
* A pointer may traverse a `!ref` into the referenced resource's own manifest
|
|
100
|
+
* (read field → resolve reference → read field); traversal is mechanical, so
|
|
101
|
+
* no kind is named here. Returns undefined when nothing resolves — the
|
|
102
|
+
* requirement then discharges uncorrelated, the under-approximating side.
|
|
103
|
+
*/
|
|
104
|
+
function resolveStaticKey(start, pointers, resolveName) {
|
|
105
|
+
for (const pointer of pointers) {
|
|
106
|
+
if (!pointer.startsWith("/"))
|
|
107
|
+
continue;
|
|
108
|
+
let manifest = start.manifest;
|
|
109
|
+
let context = start;
|
|
110
|
+
let value = undefined;
|
|
111
|
+
let failed = false;
|
|
112
|
+
const segments = pointer.slice(1).split("/");
|
|
113
|
+
for (let i = 0; i < segments.length; i++) {
|
|
114
|
+
const segment = segments[i].replace(/~1/g, "/").replace(/~0/g, "~");
|
|
115
|
+
if (i > 0) {
|
|
116
|
+
// Traverse the previous hop's reference into its declaration.
|
|
117
|
+
const name = refName(value);
|
|
118
|
+
const target = name ? resolveName(name, context) : undefined;
|
|
119
|
+
if (!target) {
|
|
120
|
+
failed = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
manifest = target.manifest;
|
|
124
|
+
context = target;
|
|
125
|
+
}
|
|
126
|
+
value = manifest?.[segment];
|
|
127
|
+
if (value === undefined || value === null) {
|
|
128
|
+
failed = true;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (failed)
|
|
133
|
+
continue;
|
|
134
|
+
const terminalName = refName(value);
|
|
135
|
+
const target = terminalName ? resolveName(terminalName, context) : undefined;
|
|
136
|
+
if (target)
|
|
137
|
+
return target;
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
/** Anchor a provider-side single pointer at the object enclosing the slot,
|
|
142
|
+
* then resolve exactly like a requirer key. */
|
|
143
|
+
function resolveProviderKey(provider, edge, pointer, resolveName) {
|
|
144
|
+
const enclosing = enclosingOf(provider.manifest, edge.path);
|
|
145
|
+
if (enclosing === provider.manifest || enclosing === undefined) {
|
|
146
|
+
return resolveStaticKey(provider, [pointer], resolveName);
|
|
147
|
+
}
|
|
148
|
+
// Slot inside an array item: resolve the first hop off the item, further
|
|
149
|
+
// hops through references as usual.
|
|
150
|
+
const synthetic = { ...provider, manifest: enclosing };
|
|
151
|
+
return resolveStaticKey(synthetic, [pointer], resolveName);
|
|
152
|
+
}
|
|
153
|
+
/** Run the projection over one graph. */
|
|
154
|
+
export function projectZoneRequirements(args) {
|
|
155
|
+
const { graph, defs, aliases, aliasesByModule, reportModules, seeds, exportOf } = args;
|
|
156
|
+
const resolveDef = definitionResolver(defs, aliases, aliasesByModule);
|
|
157
|
+
const diagnostics = [];
|
|
158
|
+
const openExports = new Map();
|
|
159
|
+
const reported = new Set();
|
|
160
|
+
// Scope-local name index, so a scoped resource's pointers resolve against its
|
|
161
|
+
// scope siblings first — the order `ScopeContext` and `!ref` agree on.
|
|
162
|
+
const scopeLocal = new Map();
|
|
163
|
+
for (const node of graph.nodes.values()) {
|
|
164
|
+
if (node.type !== "resource" || !node.scoped)
|
|
165
|
+
continue;
|
|
166
|
+
const key = `${node.scopeOwner}\0${node.scopeSite}`;
|
|
167
|
+
let bucket = scopeLocal.get(key);
|
|
168
|
+
if (!bucket)
|
|
169
|
+
scopeLocal.set(key, (bucket = new Map()));
|
|
170
|
+
bucket.set(node.name, node);
|
|
171
|
+
}
|
|
172
|
+
const resolveName = (name, from) => {
|
|
173
|
+
if (from.scoped) {
|
|
174
|
+
const local = scopeLocal.get(`${from.scopeOwner}\0${from.scopeSite}`)?.get(name);
|
|
175
|
+
if (local)
|
|
176
|
+
return local;
|
|
177
|
+
}
|
|
178
|
+
return graph.resourceByName(name);
|
|
179
|
+
};
|
|
180
|
+
const acceptedFor = (zone) => {
|
|
181
|
+
const out = new Set([zone]);
|
|
182
|
+
for (const def of defs.getByExtends(zone)) {
|
|
183
|
+
const module = def.metadata?.module;
|
|
184
|
+
if (module && def.metadata?.name)
|
|
185
|
+
out.add(`${module}.${def.metadata.name}`);
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
};
|
|
189
|
+
const moduleOf = (node) => node.manifest.metadata?.module;
|
|
190
|
+
const reportable = (node) => {
|
|
191
|
+
const module = moduleOf(node);
|
|
192
|
+
return module === undefined || reportModules.has(module);
|
|
193
|
+
};
|
|
194
|
+
const emit = (severity, code, edge, caller, req, via, why) => {
|
|
195
|
+
if (!reportable(caller))
|
|
196
|
+
return;
|
|
197
|
+
const dedupe = `${code}\0${edge.from}\0${edge.path}\0${req.key}`;
|
|
198
|
+
if (reported.has(dedupe))
|
|
199
|
+
return;
|
|
200
|
+
reported.add(dedupe);
|
|
201
|
+
const wanted = req.correlationLabel
|
|
202
|
+
? `a ${req.zone} zone on ${req.correlationLabel}`
|
|
203
|
+
: `a ${req.zone} zone`;
|
|
204
|
+
const path = [...via, `${caller.name}.${edge.path}`].join(" → ");
|
|
205
|
+
const reason = req.reason ? ` ${req.reason}.` : "";
|
|
206
|
+
diagnostics.push({
|
|
207
|
+
severity,
|
|
208
|
+
code,
|
|
209
|
+
source: SOURCE,
|
|
210
|
+
message: `${req.origin} requires ${wanted}, and the path ${path} ${why}.${reason}`,
|
|
211
|
+
data: {
|
|
212
|
+
resource: { kind: caller.kind, name: caller.name },
|
|
213
|
+
filePath: caller.manifest.metadata?.source,
|
|
214
|
+
path: edge.path,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
const visited = new Set();
|
|
219
|
+
const propagate = (node, req, via) => {
|
|
220
|
+
const visitKey = `${node.id}\0${req.key}`;
|
|
221
|
+
if (visited.has(visitKey))
|
|
222
|
+
return;
|
|
223
|
+
visited.add(visitKey);
|
|
224
|
+
// A requirement reaching an exported instance is part of that export's
|
|
225
|
+
// contract — recorded, and propagation continues (the export may also be
|
|
226
|
+
// reached internally, where an enclosing provider can discharge it).
|
|
227
|
+
const exportName = exportOf?.(node);
|
|
228
|
+
if (exportName !== undefined) {
|
|
229
|
+
let bucket = openExports.get(exportName);
|
|
230
|
+
if (!bucket)
|
|
231
|
+
openExports.set(exportName, (bucket = []));
|
|
232
|
+
if (!bucket.some((r) => `${r.zone}\0${r.correlation ?? ""}` === req.key)) {
|
|
233
|
+
bucket.push({
|
|
234
|
+
zone: req.zone,
|
|
235
|
+
correlation: req.correlation,
|
|
236
|
+
correlationLabel: req.correlationLabel,
|
|
237
|
+
correlationName: req.correlationName,
|
|
238
|
+
reason: req.reason,
|
|
239
|
+
origin: req.origin,
|
|
240
|
+
via: [...via],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const edge of graph.edgesTo(node.id)) {
|
|
245
|
+
const from = graph.nodes.get(edge.from);
|
|
246
|
+
if (!from)
|
|
247
|
+
continue;
|
|
248
|
+
const caller = from.type === "step"
|
|
249
|
+
? graph.nodes.get(from.owner)
|
|
250
|
+
: from;
|
|
251
|
+
if (!caller || caller.type !== "resource")
|
|
252
|
+
continue;
|
|
253
|
+
// Discharge: the slot provides a zone whose kind satisfies the
|
|
254
|
+
// requirement and whose correlation payload is the same declaration
|
|
255
|
+
// site. Checked BEFORE termination, so a terminating provider slot
|
|
256
|
+
// (a detached durable body) still discharges its own zone.
|
|
257
|
+
const callerDef = resolveDef(caller.kind, moduleOf(caller));
|
|
258
|
+
const slotSchema = schemaNodeAt(callerDef?.schema, edge.slot);
|
|
259
|
+
const provides = readProvidesZone(slotSchema);
|
|
260
|
+
if (provides && callerDef && req.accepted.has(canonicalOf(callerDef))) {
|
|
261
|
+
const providerKey = provides.key
|
|
262
|
+
? resolveProviderKey(caller, edge, provides.key, resolveName)
|
|
263
|
+
: undefined;
|
|
264
|
+
const discharged = req.correlation === undefined ||
|
|
265
|
+
(providerKey !== undefined && correlationIdOf(providerKey) === req.correlation);
|
|
266
|
+
if (discharged)
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
// A dynamic selector is already a hard diagnostic (validate-ref-slots);
|
|
270
|
+
// zones do not re-report it — and must not propagate through a use they
|
|
271
|
+
// cannot read.
|
|
272
|
+
if (edge.unresolvedReason === "dynamic")
|
|
273
|
+
continue;
|
|
274
|
+
if (edge.use.length === 0) {
|
|
275
|
+
emit(DiagnosticSeverity.Warning, "ZONE_REQUIREMENT_DEFERRED", edge, caller, req, via, "reaches a slot that declares no use, so whether the zone survives it cannot be decided statically; the runtime check remains the enforcement");
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (edge.unresolved) {
|
|
279
|
+
emit(DiagnosticSeverity.Warning, "ZONE_REQUIREMENT_DEFERRED", edge, caller, req, via, `reaches a slot whose use selector could not be resolved (${edge.unresolvedReason}), so whether the zone survives it cannot be decided statically; the runtime check remains the enforcement`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (edge.use.every((u) => u === "dependency" || u === "schema"))
|
|
283
|
+
continue;
|
|
284
|
+
if (edge.use.length > 1) {
|
|
285
|
+
// The zone's lifetime extends through the edge only if EVERY member is
|
|
286
|
+
// `call` — a set says several relations hold at once, not that one of
|
|
287
|
+
// them might.
|
|
288
|
+
if (edge.use.every((u) => u === "call")) {
|
|
289
|
+
continueUp(caller, edge, req, via);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// A member the runtime guarantees is cleared makes the set decidably
|
|
293
|
+
// wrong, not undecidable: the detached dispatch is NEVER inside the
|
|
294
|
+
// caller's zone, so the requirement — a universal claim — is violated
|
|
295
|
+
// on that path. What stays unknown is only how often that path is
|
|
296
|
+
// taken, and "sometimes throws" is not a working manifest.
|
|
297
|
+
//
|
|
298
|
+
// This is the one place the plan's original rule was inverted, and
|
|
299
|
+
// deliberately. That rule existed to stop `Cache.View`'s UNCONDITIONAL
|
|
300
|
+
// `[call, detached]` from hard-erroring every cached transactional call
|
|
301
|
+
// under the default `revalidate: sync`, where the controller never
|
|
302
|
+
// detaches — but the same change that added this pass re-annotated that
|
|
303
|
+
// slot as a case map, so a set now appears only where its detach really
|
|
304
|
+
// happens. The justification went with the annotation.
|
|
305
|
+
const guaranteedCleared = edge.use.filter((u) => u === "detached" || u === "trigger.inbound");
|
|
306
|
+
if (guaranteedCleared.length > 0) {
|
|
307
|
+
emit(DiagnosticSeverity.Error, "ZONE_REQUIREMENT_UNSATISFIED", edge, caller, req, via, `reaches a slot that dispatches its target several ways in one invocation ([${edge.use.join(", ")}]), and the ${guaranteedCleared.join(" / ")} dispatch is guaranteed a fresh context — so on that path the zone is gone`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
// Everything else in a set is undecidable rather than wrong — a
|
|
311
|
+
// `trigger.consumer` member means a drain site MIGHT be inside the
|
|
312
|
+
// zone, which no static reading can settle.
|
|
313
|
+
emit(DiagnosticSeverity.Warning, "ZONE_REQUIREMENT_DEFERRED", edge, caller, req, via, `reaches a slot whose declared use is the set [${edge.use.join(", ")}]; whether the zone survives depends on where the consumer drains it, so it cannot be decided statically and the runtime check remains the enforcement`);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
switch (edge.use[0]) {
|
|
317
|
+
case "call":
|
|
318
|
+
continueUp(caller, edge, req, via);
|
|
319
|
+
break;
|
|
320
|
+
case "detached":
|
|
321
|
+
emit(DiagnosticSeverity.Error, "ZONE_REQUIREMENT_UNSATISFIED", edge, caller, req, via, "detaches there — the runtime guarantees the detached work a fresh context, outside every zone its caller was in");
|
|
322
|
+
break;
|
|
323
|
+
case "trigger.inbound":
|
|
324
|
+
emit(DiagnosticSeverity.Error, "ZONE_REQUIREMENT_UNSATISFIED", edge, caller, req, via, "is an inbound trigger registration — the handler runs on a fresh context driven by a request or timer, outside every zone");
|
|
325
|
+
break;
|
|
326
|
+
case "trigger.consumer":
|
|
327
|
+
emit(DiagnosticSeverity.Warning, "ZONE_REQUIREMENT_DEFERRED", edge, caller, req, via, "is dispatched when a returned value is drained, so where it runs is the drain site's choice; the runtime check remains the enforcement");
|
|
328
|
+
break;
|
|
329
|
+
// dependency / schema singletons were filtered above.
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const continueUp = (caller, edge, req, via) => {
|
|
334
|
+
// Nothing encloses an Application's boot targets: an open requirement
|
|
335
|
+
// arriving there surfaces at boot.
|
|
336
|
+
if (caller.kind === "Telo.Application") {
|
|
337
|
+
emit(DiagnosticSeverity.Error, "ZONE_REQUIREMENT_UNSATISFIED", edge, caller, req, via, "reaches the application's boot targets, which nothing encloses");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
propagate(caller, req, [...via, caller.name]);
|
|
341
|
+
};
|
|
342
|
+
// ── Origins: instances of the entry graph whose annotated field is present.
|
|
343
|
+
for (const node of graph.nodes.values()) {
|
|
344
|
+
if (node.type !== "resource")
|
|
345
|
+
continue;
|
|
346
|
+
const meta = node.manifest.metadata;
|
|
347
|
+
// A forwarded export's requirements are derived by ITS library's own stage
|
|
348
|
+
// (with the internal graph in hand) and seeded below — deriving them here
|
|
349
|
+
// against the flattened view would resolve correlation against a graph
|
|
350
|
+
// that no longer holds the library's internals.
|
|
351
|
+
if (meta?.forwardedExport)
|
|
352
|
+
continue;
|
|
353
|
+
const def = resolveDef(node.kind, meta?.module);
|
|
354
|
+
const schema = def?.schema;
|
|
355
|
+
if (!def || !schema?.properties)
|
|
356
|
+
continue;
|
|
357
|
+
for (const [field, propSchema] of Object.entries(schema.properties)) {
|
|
358
|
+
const requires = readRequiresZone(propSchema);
|
|
359
|
+
if (!requires)
|
|
360
|
+
continue;
|
|
361
|
+
if (node.manifest[field] === undefined)
|
|
362
|
+
continue;
|
|
363
|
+
const zoneDef = resolveDef(requires.zone, def.metadata.module);
|
|
364
|
+
if (!zoneDef)
|
|
365
|
+
continue; // ZONE_PROVIDER_UNRESOLVED is reported at registration
|
|
366
|
+
const zone = canonicalOf(zoneDef);
|
|
367
|
+
const target = resolveStaticKey(node, requires.key, resolveName);
|
|
368
|
+
const req = {
|
|
369
|
+
zone,
|
|
370
|
+
accepted: acceptedFor(zone),
|
|
371
|
+
correlation: target ? correlationIdOf(target) : undefined,
|
|
372
|
+
correlationLabel: target ? labelOf(target) : undefined,
|
|
373
|
+
correlationName: target?.name,
|
|
374
|
+
reason: requires.reason,
|
|
375
|
+
origin: labelOf(node),
|
|
376
|
+
key: "",
|
|
377
|
+
};
|
|
378
|
+
req.key = `${req.zone}\0${req.correlation ?? ""}`;
|
|
379
|
+
propagate(node, req, [node.name]);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ── Seeds: imported libraries' derived export contracts, attached at the
|
|
383
|
+
// forwarded nodes the importer's graph actually holds.
|
|
384
|
+
if (seeds && seeds.size > 0) {
|
|
385
|
+
for (const node of graph.nodes.values()) {
|
|
386
|
+
if (node.type !== "resource")
|
|
387
|
+
continue;
|
|
388
|
+
const meta = node.manifest.metadata;
|
|
389
|
+
if (!meta?.forwardedExport || !meta.module)
|
|
390
|
+
continue;
|
|
391
|
+
const specs = seeds.get(`${meta.module}\0${node.name}`);
|
|
392
|
+
if (!specs)
|
|
393
|
+
continue;
|
|
394
|
+
for (const spec of specs) {
|
|
395
|
+
const zoneDef = defs.resolve(spec.zone);
|
|
396
|
+
const req = {
|
|
397
|
+
zone: spec.zone,
|
|
398
|
+
accepted: zoneDef ? acceptedFor(spec.zone) : new Set([spec.zone]),
|
|
399
|
+
correlation: spec.correlation,
|
|
400
|
+
correlationLabel: spec.correlationLabel,
|
|
401
|
+
correlationName: spec.correlationName,
|
|
402
|
+
reason: spec.reason,
|
|
403
|
+
origin: spec.origin,
|
|
404
|
+
key: `${spec.zone}\0${spec.correlation ?? ""}`,
|
|
405
|
+
};
|
|
406
|
+
propagate(node, req, [...spec.via, node.name]);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return { diagnostics, openExports };
|
|
411
|
+
}
|
|
412
|
+
/** Content signature over a library's loaded documents — what makes a
|
|
413
|
+
* workspace library invalidate on the keystroke that changed it while a
|
|
414
|
+
* published library (immutable bytes) hits every time. FNV-1a over the JSON
|
|
415
|
+
* projection; collisions only stale a warning-level derivation, and the
|
|
416
|
+
* projection is cheap relative to the graph build it guards. */
|
|
417
|
+
export function zoneDocumentsSignature(manifests) {
|
|
418
|
+
let hash = 0x811c9dc5;
|
|
419
|
+
const mix = (text) => {
|
|
420
|
+
for (let i = 0; i < text.length; i++) {
|
|
421
|
+
hash ^= text.charCodeAt(i);
|
|
422
|
+
hash = Math.imul(hash, 0x01000193);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
for (const m of manifests)
|
|
426
|
+
mix(JSON.stringify(m) ?? "");
|
|
427
|
+
return (hash >>> 0).toString(16);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Derive one library's export contracts: build the library-scoped graph from
|
|
431
|
+
* its full documents (which the flattened analysis view no longer holds), run
|
|
432
|
+
* the projection derive-only, and keep what reaches an exported instance.
|
|
433
|
+
*/
|
|
434
|
+
export function deriveLibraryExportRequirements(docs, defs, aliases, aliasesByModule, cache) {
|
|
435
|
+
const signature = docs.signature ?? zoneDocumentsSignature(docs.manifests);
|
|
436
|
+
const cached = cache?.get(docs.sourceId);
|
|
437
|
+
if (cached && cached.signature === signature)
|
|
438
|
+
return cached.exports;
|
|
439
|
+
const exported = new Set(docs.exportedNames);
|
|
440
|
+
const graph = buildCallGraph(docs.manifests, defs, { aliases, aliasesByModule });
|
|
441
|
+
const { openExports } = projectZoneRequirements({
|
|
442
|
+
graph,
|
|
443
|
+
defs,
|
|
444
|
+
aliases,
|
|
445
|
+
aliasesByModule,
|
|
446
|
+
reportModules: new Set(),
|
|
447
|
+
exportOf: (node) => !node.scoped && exported.has(node.name) ? node.name : undefined,
|
|
448
|
+
});
|
|
449
|
+
cache?.set(docs.sourceId, { signature, exports: openExports });
|
|
450
|
+
return openExports;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The zone stage of `analyze()`: per-library export derivation (cached), the
|
|
454
|
+
* entry projection with those contracts seeded, and — when the entry is a
|
|
455
|
+
* library — the export-satisfiability check, at the one desk where it is
|
|
456
|
+
* fixable.
|
|
457
|
+
*/
|
|
458
|
+
export function runZoneAnalysis(args) {
|
|
459
|
+
const { manifests, graph, defs, aliases, aliasesByModule, rootModules } = args;
|
|
460
|
+
// Per-library export contracts, derived over each library's own documents.
|
|
461
|
+
const seeds = new Map();
|
|
462
|
+
for (const docs of args.moduleDocuments ?? []) {
|
|
463
|
+
const contracts = deriveLibraryExportRequirements(docs, defs, aliases, aliasesByModule, args.cache);
|
|
464
|
+
for (const [exportName, specs] of contracts) {
|
|
465
|
+
if (specs.length > 0)
|
|
466
|
+
seeds.set(`${docs.module}\0${exportName}`, specs);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// The entry library's own export surface, for the satisfiability check.
|
|
470
|
+
const rootLibraries = [];
|
|
471
|
+
for (const m of manifests) {
|
|
472
|
+
if (m.kind !== "Telo.Library")
|
|
473
|
+
continue;
|
|
474
|
+
const name = m.metadata?.name;
|
|
475
|
+
if (!name || !rootModules.has(name))
|
|
476
|
+
continue;
|
|
477
|
+
const exportedNames = new Set();
|
|
478
|
+
for (const entry of m.exports?.resources ?? []) {
|
|
479
|
+
if (typeof entry !== "string")
|
|
480
|
+
continue;
|
|
481
|
+
const dot = entry.indexOf(".");
|
|
482
|
+
exportedNames.add(dot > 0 ? entry.slice(dot + 1) : entry);
|
|
483
|
+
}
|
|
484
|
+
if (exportedNames.size > 0)
|
|
485
|
+
rootLibraries.push({ module: name, exportedNames, doc: m });
|
|
486
|
+
}
|
|
487
|
+
const exportOf = rootLibraries.length > 0
|
|
488
|
+
? (node) => {
|
|
489
|
+
if (node.scoped)
|
|
490
|
+
return undefined;
|
|
491
|
+
const module = node.manifest.metadata?.module;
|
|
492
|
+
const lib = rootLibraries.find((l) => l.module === module);
|
|
493
|
+
return lib?.exportedNames.has(node.name) ? node.name : undefined;
|
|
494
|
+
}
|
|
495
|
+
: undefined;
|
|
496
|
+
const { diagnostics, openExports } = projectZoneRequirements({
|
|
497
|
+
graph,
|
|
498
|
+
defs,
|
|
499
|
+
aliases,
|
|
500
|
+
aliasesByModule,
|
|
501
|
+
reportModules: rootModules,
|
|
502
|
+
seeds: seeds.size > 0 ? seeds : undefined,
|
|
503
|
+
exportOf,
|
|
504
|
+
});
|
|
505
|
+
// An export whose requirement correlates on a resource importers cannot
|
|
506
|
+
// reach is unsatisfiable by construction — raised HERE, at the exporting
|
|
507
|
+
// library, never by an importer against a file it does not own.
|
|
508
|
+
for (const lib of rootLibraries) {
|
|
509
|
+
for (const [exportName, specs] of openExports) {
|
|
510
|
+
if (!lib.exportedNames.has(exportName))
|
|
511
|
+
continue;
|
|
512
|
+
const node = graph.resourceByName(exportName);
|
|
513
|
+
if (!node || node.manifest.metadata?.module !== lib.module) {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
for (const spec of specs) {
|
|
517
|
+
if (spec.correlationName === undefined)
|
|
518
|
+
continue; // uncorrelated — satisfiable by any zone
|
|
519
|
+
if (lib.exportedNames.has(spec.correlationName))
|
|
520
|
+
continue; // importers can reach it
|
|
521
|
+
diagnostics.push({
|
|
522
|
+
severity: DiagnosticSeverity.Error,
|
|
523
|
+
code: "ZONE_EXPORT_UNSATISFIABLE",
|
|
524
|
+
source: SOURCE,
|
|
525
|
+
message: `exported resource '${exportName}' carries an open requirement: ${spec.origin} requires ` +
|
|
526
|
+
`a ${spec.zone} zone on ${spec.correlationLabel}, which this library does not export — ` +
|
|
527
|
+
`no importer can satisfy it. Export '${spec.correlationName}' too (importers wrap ` +
|
|
528
|
+
`'${exportName}' in their own zone on it), or export a resource that goes through the ` +
|
|
529
|
+
`provider instead of '${exportName}' directly.` +
|
|
530
|
+
(spec.reason ? ` ${spec.reason}.` : ""),
|
|
531
|
+
data: {
|
|
532
|
+
resource: { kind: node.kind, name: node.name },
|
|
533
|
+
filePath: node.manifest.metadata?.source,
|
|
534
|
+
path: "",
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return diagnostics;
|
|
541
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ZoneModuleDocuments } from "./zone-module-documents.js";
|
|
1
2
|
/** Matches LSP DiagnosticSeverity values exactly.
|
|
2
3
|
* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticSeverity */
|
|
3
4
|
export declare const DiagnosticSeverity: {
|
|
@@ -74,6 +75,13 @@ export interface LoaderInitOptions {
|
|
|
74
75
|
}
|
|
75
76
|
export interface AnalysisOptions {
|
|
76
77
|
strictContexts?: boolean;
|
|
78
|
+
/** Imported libraries' FULL document sets, for the zone stage's per-library
|
|
79
|
+
* export derivation — the flattened analysis view forwards only each
|
|
80
|
+
* library's export surface, never its internal dispatch chain. Collected
|
|
81
|
+
* from a LoadedGraph via `collectZoneModuleDocuments`. Omitting it skips
|
|
82
|
+
* the derivation (the under-approximating direction — the runtime check
|
|
83
|
+
* remains the enforcement). */
|
|
84
|
+
moduleDocuments?: ZoneModuleDocuments[];
|
|
77
85
|
/** When true, `analyze()` runs the state-mutating setup (module identity /
|
|
78
86
|
* alias / definition registration plus `normalizeInlineResources`) but
|
|
79
87
|
* skips every diagnostic-producing pass — per-resource validation, the
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;mEAO+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;;sDAUkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;IACpE;;;;+EAI2E;IAC3E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC,CAAC;CAC5E"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;mEAO+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;oCAKgC;IAChC,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACxC;;;;;;;;;;sDAUkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;IACpE;;;;+EAI2E;IAC3E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC,CAAC;CAC5E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Static validation of the `metadata:` block on module docs (`Telo.Application` /
|
|
7
|
+
* `Telo.Library`) and of `metadata.deprecated` wherever it appears.
|
|
8
|
+
*
|
|
9
|
+
* These fields are descriptive — nothing in the kernel branches on them — but they
|
|
10
|
+
* are the module's public face: a hub indexes them, and a consumer reads them
|
|
11
|
+
* before deciding to import. That is exactly why they need checking. A field the
|
|
12
|
+
* runtime ignores has no failure mode that would ever surface it, so a mistyped
|
|
13
|
+
* `licence:` or `deprecatd:` is invisible forever, and the module ships claiming
|
|
14
|
+
* nothing while its author believes otherwise.
|
|
15
|
+
*
|
|
16
|
+
* The vocabulary stays **open** — `metadata` accepts any key, because a publisher
|
|
17
|
+
* may carry their own — so an unknown key is only reported when it is a near-miss
|
|
18
|
+
* of a known one. That catches the typo without closing the set.
|
|
19
|
+
*
|
|
20
|
+
* **Everything here is a WARNING, and fatal only at `telo publish`** (see
|
|
21
|
+
* {@link PUBLISH_BLOCKING_CODES}). Refusing to *run* a manifest over a field no
|
|
22
|
+
* runtime reads gets the cost backwards: `version: 1.0` is a YAML float rather
|
|
23
|
+
* than a string, which is a real mistake worth reporting, but stopping the app
|
|
24
|
+
* from starting over it is worse than the mistake. Publication is the moment
|
|
25
|
+
* these fields become consequential — they are projected onto the artifact's
|
|
26
|
+
* annotations and indexed by the hub — so that is where they block.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Codes that must not block running a manifest but MUST block publishing one.
|
|
30
|
+
*
|
|
31
|
+
* Kept as a set rather than a severity because the two audiences differ: a
|
|
32
|
+
* developer running a manifest wants to know, a publisher must be stopped. If a
|
|
33
|
+
* later check earns the same treatment, add its code here rather than inventing
|
|
34
|
+
* a third severity level.
|
|
35
|
+
*/
|
|
36
|
+
export declare const PUBLISH_BLOCKING_CODES: ReadonlySet<string>;
|
|
37
|
+
export declare function validateModuleMetadata(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
|
|
38
|
+
//# sourceMappingURL=validate-module-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-module-metadata.d.ts","sourceRoot":"","sources":["../src/validate-module-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAKrD,CAAC;AAiDH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CA8CtB"}
|