@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.
- 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/payload-digest.d.ts +7 -3
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +7 -3
- 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/payload-digest.ts +7 -3
- 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,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The strict half of the `x-telo-referrer-rules` accessor split
|
|
3
|
+
* (`validate-resource-rules.ts` / `validate-zone-slots.ts` precedent), plus the
|
|
4
|
+
* evaluation pass that runs a kind's rules against the resources referencing it.
|
|
5
|
+
*
|
|
6
|
+
* Both halves live here because they fail in opposite directions and must agree
|
|
7
|
+
* about what a rule MEANS: a declaration the reader cannot parse is silently
|
|
8
|
+
* unenforced — the check reads as passing when it never ran — while a rule that
|
|
9
|
+
* throws would otherwise be reported against a consumer's manifest, blaming an
|
|
10
|
+
* author for a defect in someone else's kind.
|
|
11
|
+
*
|
|
12
|
+
* A CONSUMER of the call graph with no traversal of its own: the referrers are
|
|
13
|
+
* `edgesTo` on the resource's node, exactly as the zone projection consumes the
|
|
14
|
+
* outgoing direction. What it hands back is plain findings; the caller pushes
|
|
15
|
+
* them.
|
|
16
|
+
*
|
|
17
|
+
* Ownership splits by whose defect it is, and lands one hop further out than for
|
|
18
|
+
* a resource rule. A VIOLATION belongs to the data — but the offending data is
|
|
19
|
+
* the REFERRER's, so the finding names that manifest and the analyzer reports it
|
|
20
|
+
* when the referrer is the entry's own. A rule that throws or exhausts its
|
|
21
|
+
* budget is a defect in the rule, anchored on the declaring definition.
|
|
22
|
+
*
|
|
23
|
+
* Browser-safe: no Node built-ins.
|
|
24
|
+
*/
|
|
25
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
26
|
+
import {
|
|
27
|
+
REFERRER_RULES_ANNOTATION,
|
|
28
|
+
readRawReferrerRules,
|
|
29
|
+
readReferrerRules,
|
|
30
|
+
type ReferrerRule,
|
|
31
|
+
} from "./referrer-rule.js";
|
|
32
|
+
import { celSourceOf, findDynamicLeaf, readNodes } from "./resource-rule.js";
|
|
33
|
+
import { RULE_BUDGET_MS, compileRuleCondition, conditionRefusals } from "./rule-condition.js";
|
|
34
|
+
|
|
35
|
+
export interface ReferrerRuleIssue {
|
|
36
|
+
code: "REFERRER_RULE_INVALID";
|
|
37
|
+
manifest: ResourceManifest;
|
|
38
|
+
path: string;
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One resource that reaches the resource under test, and where it does so. */
|
|
43
|
+
export interface Referrer {
|
|
44
|
+
readonly manifest: ResourceManifest;
|
|
45
|
+
readonly kind: string;
|
|
46
|
+
readonly name: string;
|
|
47
|
+
/** Concrete path of the slot in the referrer (`mounts[1].mount`) — the anchor. */
|
|
48
|
+
readonly path: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One rule's verdict on one referrer. */
|
|
52
|
+
export type ReferrerRuleFinding =
|
|
53
|
+
| { kind: "violation"; rule: ReferrerRule; referrer: Referrer; message: string }
|
|
54
|
+
| { kind: "skipped"; rule: ReferrerRule; referrer: Referrer; dynamicAt: string }
|
|
55
|
+
| { kind: "failed"; rule: ReferrerRule; referrer?: Referrer; reason: string }
|
|
56
|
+
| { kind: "over-budget"; rule: ReferrerRule; referrer: Referrer; elapsedMs: number };
|
|
57
|
+
|
|
58
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
59
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Report every way a kind's referrer-rule declarations are malformed. Runs on
|
|
64
|
+
* the `Telo.Definition` / `Telo.Abstract` doc, so a defect lands on the line the
|
|
65
|
+
* kind's author wrote rather than on a consumer's resource.
|
|
66
|
+
*
|
|
67
|
+
* The `referrer:` filter is NOT resolved here: it is canonicalized in the
|
|
68
|
+
* declaring module's scope by `resolveSchemaRefKinds`, the only pass holding
|
|
69
|
+
* that scope, and a name resolving to nothing is reported from there.
|
|
70
|
+
*/
|
|
71
|
+
export function validateReferrerRuleDeclarations(manifest: ResourceManifest): ReferrerRuleIssue[] {
|
|
72
|
+
const own = (manifest as unknown as Record<string, unknown>).schema;
|
|
73
|
+
const raw = readRawReferrerRules(own);
|
|
74
|
+
if (raw === undefined) return [];
|
|
75
|
+
|
|
76
|
+
const base = `schema.${REFERRER_RULES_ANNOTATION}`;
|
|
77
|
+
const issues: ReferrerRuleIssue[] = [];
|
|
78
|
+
const issue = (path: string, message: string): void => {
|
|
79
|
+
issues.push({ code: "REFERRER_RULE_INVALID", manifest, path, message });
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (!Array.isArray(raw)) {
|
|
83
|
+
issue(base, `'${REFERRER_RULES_ANNOTATION}' must be an array of rules.`);
|
|
84
|
+
return issues;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const seen = new Map<string, number>();
|
|
88
|
+
raw.forEach((entry, index) => {
|
|
89
|
+
const at = `${base}[${index}]`;
|
|
90
|
+
if (!isObject(entry)) {
|
|
91
|
+
issue(at, "A rule must be an object with 'condition', 'code' and 'message'.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const condition = celSourceOf(entry.condition);
|
|
96
|
+
if (condition === undefined || condition.length === 0) {
|
|
97
|
+
issue(
|
|
98
|
+
`${at}.condition`,
|
|
99
|
+
"A rule needs a 'condition' — a CEL expression over 'referrer' (the resource that " +
|
|
100
|
+
"references this one) and 'self', TRUE when the rule holds. Write it with the !cel tag.",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (typeof entry.code !== "string" || entry.code.length === 0) {
|
|
104
|
+
issue(
|
|
105
|
+
`${at}.code`,
|
|
106
|
+
"A rule needs a 'code' naming it. It is reported in the diagnostic's data.rule, " +
|
|
107
|
+
"not as a diagnostic code — every violation reports under REFERRER_RULE_VIOLATED.",
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
const first = seen.get(entry.code);
|
|
111
|
+
if (first !== undefined) {
|
|
112
|
+
issue(
|
|
113
|
+
`${at}.code`,
|
|
114
|
+
`Rule code '${entry.code}' is already used by rule ${first}. A code names one rule, ` +
|
|
115
|
+
"so two rules sharing it are indistinguishable in data.rule.",
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
seen.set(entry.code, index);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (typeof entry.message !== "string" || entry.message.length === 0) {
|
|
122
|
+
issue(
|
|
123
|
+
`${at}.message`,
|
|
124
|
+
"A rule needs a 'message' saying what the referrer must do — only the kind's author " +
|
|
125
|
+
"knows that, and the analyzer supplies only where and what.",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (entry.referrer !== undefined && typeof entry.referrer !== "string") {
|
|
129
|
+
issue(
|
|
130
|
+
`${at}.referrer`,
|
|
131
|
+
"'referrer' names the kind a referring resource must be, in the same alias-qualified " +
|
|
132
|
+
"grammar as extends: (Self.Server, Http.Server, Telo.Something). Omit it to apply the " +
|
|
133
|
+
"rule to every referrer.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (entry.severity !== undefined && entry.severity !== "error" && entry.severity !== "warning") {
|
|
137
|
+
issue(`${at}.severity`, "'severity' must be 'error' or 'warning'.");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (condition) {
|
|
141
|
+
for (const refusal of conditionRefusals(condition)) issue(`${at}.condition`, refusal);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return issues;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Run a kind's referrer rules against every resource that references one of its
|
|
150
|
+
* resources.
|
|
151
|
+
*
|
|
152
|
+
* `self` binds the referenced resource, `referrer` the one that reached it. A
|
|
153
|
+
* referrer reaching the same resource through several slots is judged ONCE — by
|
|
154
|
+
* manifest identity, since a name alone is module-scoped — because the condition
|
|
155
|
+
* reads the two manifests and nothing about the site, so a second site could only
|
|
156
|
+
* produce the identical verdict at a different path.
|
|
157
|
+
*/
|
|
158
|
+
export function evaluateReferrerRules(
|
|
159
|
+
manifest: ResourceManifest,
|
|
160
|
+
definitionSchema: unknown,
|
|
161
|
+
referrers: readonly Referrer[],
|
|
162
|
+
/** Whether a referrer of `kind` satisfies a rule's `referrer:` filter —
|
|
163
|
+
* Liskov-substitutable, so a child of the named kind matches. Supplied by the
|
|
164
|
+
* caller, which holds the definition registry. */
|
|
165
|
+
kindMatches: (filter: string, kind: string) => boolean,
|
|
166
|
+
): ReferrerRuleFinding[] {
|
|
167
|
+
const rules = readReferrerRules(definitionSchema);
|
|
168
|
+
if (rules.length === 0) return [];
|
|
169
|
+
|
|
170
|
+
const self = manifest as unknown as Record<string, unknown>;
|
|
171
|
+
const findings: ReferrerRuleFinding[] = [];
|
|
172
|
+
|
|
173
|
+
for (const rule of rules) {
|
|
174
|
+
const compiled = compileRuleCondition(rule.condition, ["self", "referrer"]);
|
|
175
|
+
if ("reason" in compiled) {
|
|
176
|
+
findings.push({ kind: "failed", rule, reason: compiled.reason });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const { parsed, chains } = compiled;
|
|
180
|
+
|
|
181
|
+
// Identity is the MANIFEST, never `(kind, name)`: resource names are
|
|
182
|
+
// module-scoped, so two libraries each declaring a `server` of the same kind
|
|
183
|
+
// would share one bucket and the second violation would be dropped in
|
|
184
|
+
// silence — the trap the migration provenance index names.
|
|
185
|
+
const seen = new Set<ResourceManifest>();
|
|
186
|
+
const started = Date.now();
|
|
187
|
+
for (const referrer of referrers) {
|
|
188
|
+
if (rule.referrer !== undefined && !kindMatches(rule.referrer, referrer.kind)) continue;
|
|
189
|
+
if (seen.has(referrer.manifest)) continue;
|
|
190
|
+
seen.add(referrer.manifest);
|
|
191
|
+
|
|
192
|
+
// Only the nodes this condition READS decide whether it can run — the
|
|
193
|
+
// resource-rule reasoning, and it bites harder here: the referrer is a
|
|
194
|
+
// whole manifest, so scanning all of it would disable the rule for any
|
|
195
|
+
// server carrying one unrelated expression.
|
|
196
|
+
let dynamicAt: string | undefined;
|
|
197
|
+
for (const node of readNodes(chains, {
|
|
198
|
+
self,
|
|
199
|
+
referrer: referrer.manifest as unknown as Record<string, unknown>,
|
|
200
|
+
})) {
|
|
201
|
+
dynamicAt = findDynamicLeaf(node);
|
|
202
|
+
if (dynamicAt !== undefined) break;
|
|
203
|
+
}
|
|
204
|
+
if (dynamicAt !== undefined) {
|
|
205
|
+
findings.push({ kind: "skipped", rule, referrer, dynamicAt });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let held: unknown;
|
|
210
|
+
try {
|
|
211
|
+
held = parsed({ self, referrer: referrer.manifest });
|
|
212
|
+
} catch (err) {
|
|
213
|
+
findings.push({
|
|
214
|
+
kind: "failed",
|
|
215
|
+
rule,
|
|
216
|
+
referrer,
|
|
217
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
218
|
+
});
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
if (held !== true) {
|
|
222
|
+
findings.push({ kind: "violation", rule, referrer, message: rule.message });
|
|
223
|
+
}
|
|
224
|
+
const elapsed = Date.now() - started;
|
|
225
|
+
if (elapsed > RULE_BUDGET_MS) {
|
|
226
|
+
findings.push({ kind: "over-budget", rule, referrer, elapsedMs: elapsed });
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return findings;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Whether a rule found any referrer to judge — the input to the never-exercised
|
|
236
|
+
* report, which is how a mistyped `referrer:` filter would otherwise disable a
|
|
237
|
+
* check in silence. */
|
|
238
|
+
export function referrerRuleExercised(
|
|
239
|
+
rule: ReferrerRule,
|
|
240
|
+
referrers: readonly Referrer[],
|
|
241
|
+
kindMatches: (filter: string, kind: string) => boolean,
|
|
242
|
+
): boolean {
|
|
243
|
+
if (rule.referrer === undefined) return referrers.length > 0;
|
|
244
|
+
const filter = rule.referrer;
|
|
245
|
+
return referrers.some((referrer) => kindMatches(filter, referrer.kind));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Where a referrer-rule finding is reported, and how loudly. Plain data, so the
|
|
249
|
+
* caller pushes it exactly as it does for the resource-rule reports. */
|
|
250
|
+
export interface ReferrerRuleDiagnostic {
|
|
251
|
+
code:
|
|
252
|
+
| "REFERRER_RULE_VIOLATED"
|
|
253
|
+
| "REFERRER_RULE_SKIPPED"
|
|
254
|
+
| "REFERRER_RULE_INVALID"
|
|
255
|
+
| "REFERRER_RULE_UNEXERCISED";
|
|
256
|
+
severity: "error" | "warning" | "information";
|
|
257
|
+
message: string;
|
|
258
|
+
/** The resource the finding is reported ON — the REFERRER for a violation,
|
|
259
|
+
* the declaring definition for a defect in the rule itself. */
|
|
260
|
+
manifest: ResourceManifest;
|
|
261
|
+
path?: string;
|
|
262
|
+
rule: string;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const nameOf = (manifest: ResourceManifest): string =>
|
|
266
|
+
(manifest.metadata?.name as string | undefined) ?? "<unnamed>";
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Map one resource's findings to what should be reported.
|
|
270
|
+
*
|
|
271
|
+
* A violation names the declaring kind in the message. For a resource rule the
|
|
272
|
+
* reported resource IS of the kind that declared it, so the origin is implicit;
|
|
273
|
+
* here it is not — the diagnostic lands on an `Http.Server` for a rule
|
|
274
|
+
* `HttpServer.Reference` wrote — and leaving that to the author's prose would
|
|
275
|
+
* make the trail depend on remembering to write it.
|
|
276
|
+
*/
|
|
277
|
+
export function reportReferrerRules(
|
|
278
|
+
manifest: ResourceManifest,
|
|
279
|
+
definition: ResourceManifest | undefined,
|
|
280
|
+
findings: readonly ReferrerRuleFinding[],
|
|
281
|
+
/** Whether the DECLARING definition is one of the entry's own modules. */
|
|
282
|
+
declarationIsOurs: boolean,
|
|
283
|
+
): ReferrerRuleDiagnostic[] {
|
|
284
|
+
const declaringKind = manifest.kind;
|
|
285
|
+
const out: ReferrerRuleDiagnostic[] = [];
|
|
286
|
+
|
|
287
|
+
for (const finding of findings) {
|
|
288
|
+
if (finding.kind === "violation") {
|
|
289
|
+
out.push({
|
|
290
|
+
code: "REFERRER_RULE_VIOLATED",
|
|
291
|
+
severity: finding.rule.severity,
|
|
292
|
+
message:
|
|
293
|
+
`${finding.referrer.kind}/${nameOf(finding.referrer.manifest)} at ` +
|
|
294
|
+
`'${finding.referrer.path}': required by ${declaringKind} — ${finding.message}`,
|
|
295
|
+
manifest: finding.referrer.manifest,
|
|
296
|
+
path: finding.referrer.path,
|
|
297
|
+
rule: finding.rule.code,
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (finding.kind === "skipped") {
|
|
302
|
+
out.push({
|
|
303
|
+
code: "REFERRER_RULE_SKIPPED",
|
|
304
|
+
severity: "information",
|
|
305
|
+
message:
|
|
306
|
+
`${finding.referrer.kind}/${nameOf(finding.referrer.manifest)}: rule ` +
|
|
307
|
+
`'${finding.rule.code}' from ${declaringKind} did not run at ` +
|
|
308
|
+
`'${finding.referrer.path}' — the value holds a CEL expression at ` +
|
|
309
|
+
`'${finding.dynamicAt}', which is not known until the resource is created. ` +
|
|
310
|
+
"Reported rather than dropped: a check whose coverage varies invisibly reads as passing.",
|
|
311
|
+
manifest: finding.referrer.manifest,
|
|
312
|
+
path: finding.referrer.path,
|
|
313
|
+
rule: finding.rule.code,
|
|
314
|
+
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const because =
|
|
319
|
+
finding.kind === "failed"
|
|
320
|
+
? `failed to evaluate: ${finding.reason}. Guard an optional field with 'in' or '.?'.`
|
|
321
|
+
: `exceeded its evaluation budget (${finding.elapsedMs}ms) and was stopped, so ` +
|
|
322
|
+
"coverage from here on is incomplete. Simplify the condition.";
|
|
323
|
+
out.push({
|
|
324
|
+
code: "REFERRER_RULE_INVALID",
|
|
325
|
+
severity: declarationIsOurs ? "error" : "warning",
|
|
326
|
+
message:
|
|
327
|
+
`Referrer rule '${finding.rule.code}' on kind '${declaringKind}' ${because} ` +
|
|
328
|
+
`This is a defect in the rule, not in ${nameOf(manifest)}` +
|
|
329
|
+
(declarationIsOurs ? "." : " — it is declared by a module this workspace does not own."),
|
|
330
|
+
// The two halves of the anchor move together or they name a node that
|
|
331
|
+
// does not exist: the rule's own declaration site is a path in the
|
|
332
|
+
// DEFINITION, while a referrer's slot path is a path in the REFERRER. A
|
|
333
|
+
// dependency's broken rule therefore lands on the referrer it was checking
|
|
334
|
+
// — a manifest the reader owns and where the path resolves — and on the
|
|
335
|
+
// referenced resource only when there is no referrer to name (a condition
|
|
336
|
+
// that failed to compile at all).
|
|
337
|
+
...(declarationIsOurs && definition
|
|
338
|
+
? {
|
|
339
|
+
manifest: definition,
|
|
340
|
+
path: `schema.${REFERRER_RULES_ANNOTATION}[${finding.rule.index}]`,
|
|
341
|
+
}
|
|
342
|
+
: finding.referrer
|
|
343
|
+
? { manifest: finding.referrer.manifest, path: finding.referrer.path }
|
|
344
|
+
: { manifest }),
|
|
345
|
+
rule: finding.rule.code,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** The report for a rule nothing ever exercised — here that means no resource of
|
|
353
|
+
* the kind was referenced by anything the filter matches, which is what a typo
|
|
354
|
+
* in `referrer:` looks like from the outside. */
|
|
355
|
+
export function reportUnexercisedReferrerRule(
|
|
356
|
+
definition: ResourceManifest,
|
|
357
|
+
rule: ReferrerRule,
|
|
358
|
+
): ReferrerRuleDiagnostic {
|
|
359
|
+
return {
|
|
360
|
+
code: "REFERRER_RULE_UNEXERCISED",
|
|
361
|
+
severity: "information",
|
|
362
|
+
message:
|
|
363
|
+
`Referrer rule '${rule.code}' never ran: nothing` +
|
|
364
|
+
(rule.referrer === undefined ? "" : ` of kind '${rule.referrer}'`) +
|
|
365
|
+
" references a resource of this kind, so nothing has proven the condition." +
|
|
366
|
+
(rule.referrer === undefined
|
|
367
|
+
? ""
|
|
368
|
+
: " A 'referrer' naming a kind no manifest uses disables the rule in silence."),
|
|
369
|
+
manifest: definition,
|
|
370
|
+
path: `schema.${REFERRER_RULES_ANNOTATION}[${rule.index}]`,
|
|
371
|
+
rule: rule.code,
|
|
372
|
+
};
|
|
373
|
+
}
|