@telorun/analyzer 0.38.0 → 0.40.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/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +17 -0
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +146 -0
- package/dist/import-resolution-diagnostics.d.ts +20 -0
- package/dist/import-resolution-diagnostics.d.ts.map +1 -0
- package/dist/import-resolution-diagnostics.js +59 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/inline-imports.d.ts.map +1 -1
- package/dist/inline-imports.js +1 -0
- package/dist/loaded-types.d.ts +12 -1
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +11 -1
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +20 -1
- package/dist/redaction-path.d.ts +49 -0
- package/dist/redaction-path.d.ts.map +1 -0
- package/dist/redaction-path.js +133 -0
- package/dist/reference-field-map.d.ts +12 -0
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +9 -0
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +8 -0
- package/dist/sources/local-path-ref.d.ts +4 -0
- package/dist/sources/local-path-ref.d.ts.map +1 -0
- package/dist/sources/local-path-ref.js +5 -0
- package/dist/validate-extends.d.ts +0 -20
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +7 -1
- package/dist/validate-logging.d.ts +22 -0
- package/dist/validate-logging.d.ts.map +1 -0
- package/dist/validate-logging.js +116 -0
- package/package.json +2 -2
- package/src/analyzer.ts +18 -0
- package/src/builtins.ts +152 -0
- package/src/import-resolution-diagnostics.ts +66 -0
- package/src/index.ts +8 -0
- package/src/inline-imports.ts +1 -0
- package/src/loaded-types.ts +12 -1
- package/src/manifest-loader.ts +11 -1
- package/src/normalize-inline-resources.ts +20 -1
- package/src/redaction-path.ts +142 -0
- package/src/reference-field-map.ts +20 -0
- package/src/schema-compat.ts +7 -0
- package/src/sources/local-path-ref.ts +5 -0
- package/src/validate-extends.ts +8 -1
- package/src/validate-logging.ts +136 -0
|
@@ -0,0 +1,136 @@
|
|
|
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 { parseRedactionPath, RedactionPathError } from "./redaction-path.js";
|
|
5
|
+
import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const SOURCE = "telo-analyzer";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Static validation of the `logging:` block — `kernel/specs/logging.md` §14.1
|
|
11
|
+
* and §10.3.
|
|
12
|
+
*
|
|
13
|
+
* Two things the spec explicitly says are statically detectable, so leaving them
|
|
14
|
+
* to a runtime failure would contradict Telo's "manifests must remain statically
|
|
15
|
+
* analyzable" goal:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Redaction paths** parse against the closed §14 grammar. §14.1's whole
|
|
18
|
+
* argument for a hand-written parser is that it makes paths checkable by
|
|
19
|
+
* `telo check`; a bad path must fail here, not silently fail to redact at
|
|
20
|
+
* runtime.
|
|
21
|
+
* 2. **`on_full: block`** is unimplementable on a single-threaded runtime and
|
|
22
|
+
* §10.3 calls it "statically detectable by `telo check`". Catching it here —
|
|
23
|
+
* rather than only at boot — is what lets an operator fix it before shipping.
|
|
24
|
+
*/
|
|
25
|
+
export function validateLogging(
|
|
26
|
+
manifests: ResourceManifest[],
|
|
27
|
+
registry: DefinitionRegistry,
|
|
28
|
+
aliases: AliasResolver,
|
|
29
|
+
aliasesByModule?: Map<string, AliasResolver>,
|
|
30
|
+
): AnalysisDiagnostic[] {
|
|
31
|
+
const diagnostics: AnalysisDiagnostic[] = [];
|
|
32
|
+
|
|
33
|
+
for (const manifest of manifests) {
|
|
34
|
+
const kind = manifest.kind;
|
|
35
|
+
|
|
36
|
+
// Redaction paths live on the `logging:` block of the root Application and on
|
|
37
|
+
// any `Telo.Import` / `Telo.Library` doc carrying a per-import override.
|
|
38
|
+
if (kind === "Telo.Application" || kind === "Telo.Import" || kind === "Telo.Library") {
|
|
39
|
+
validateRedactPaths(manifest, diagnostics);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// `on_full` lives on any sink instance — an inline `logging.sinks[]` entry
|
|
43
|
+
// (extracted by Phase 2 into a first-class manifest by now) or a standalone
|
|
44
|
+
// sink resource declared elsewhere and reached via `!ref`.
|
|
45
|
+
if (isSinkKind(manifest, registry, aliases, aliasesByModule)) {
|
|
46
|
+
validateOnFull(manifest, diagnostics);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return diagnostics;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateRedactPaths(manifest: ResourceManifest, out: AnalysisDiagnostic[]): void {
|
|
54
|
+
const name = (manifest.metadata as { name?: string } | undefined)?.name;
|
|
55
|
+
const filePath = (manifest.metadata as { source?: string } | undefined)?.source;
|
|
56
|
+
const resource = { kind: manifest.kind, name };
|
|
57
|
+
|
|
58
|
+
for (const { block, prefix } of loggingBlocks(manifest)) {
|
|
59
|
+
const paths = (block.redact as { paths?: unknown } | undefined)?.paths;
|
|
60
|
+
if (!Array.isArray(paths)) continue;
|
|
61
|
+
paths.forEach((path, index) => {
|
|
62
|
+
// A `!cel` path is a compiled/sentinel node by now, not a string — its
|
|
63
|
+
// value isn't known statically, so there is nothing to parse.
|
|
64
|
+
if (typeof path !== "string") return;
|
|
65
|
+
try {
|
|
66
|
+
parseRedactionPath(path);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (!(err instanceof RedactionPathError)) throw err;
|
|
69
|
+
out.push({
|
|
70
|
+
severity: DiagnosticSeverity.Error,
|
|
71
|
+
code: "INVALID_REDACTION_PATH",
|
|
72
|
+
source: SOURCE,
|
|
73
|
+
message: `${manifest.kind}/${name ?? "(unnamed)"}: ${err.message}`,
|
|
74
|
+
data: { resource, filePath, path: `${prefix}redact.paths[${index}]` },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateOnFull(manifest: ResourceManifest, out: AnalysisDiagnostic[]): void {
|
|
82
|
+
const onFull = (manifest as { on_full?: unknown }).on_full;
|
|
83
|
+
if (onFull !== "block") return;
|
|
84
|
+
const name = (manifest.metadata as { name?: string } | undefined)?.name;
|
|
85
|
+
const filePath = (manifest.metadata as { source?: string } | undefined)?.source;
|
|
86
|
+
out.push({
|
|
87
|
+
severity: DiagnosticSeverity.Error,
|
|
88
|
+
code: "LOG_SINK_ON_FULL_UNSUPPORTED",
|
|
89
|
+
source: SOURCE,
|
|
90
|
+
message:
|
|
91
|
+
`${manifest.kind}/${name ?? "(unnamed)"}: on_full: block is not supported on a ` +
|
|
92
|
+
`single-threaded runtime — blocking the producer would stall the writer. ` +
|
|
93
|
+
`Use drop_new or drop_old.`,
|
|
94
|
+
data: { resource: { kind: manifest.kind, name }, filePath, path: "on_full" },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The root `logging:` block plus every per-import `logging:` override, each
|
|
99
|
+
* with the dotted path prefix a diagnostic anchors against. */
|
|
100
|
+
function loggingBlocks(
|
|
101
|
+
manifest: ResourceManifest,
|
|
102
|
+
): Array<{ block: Record<string, unknown>; prefix: string }> {
|
|
103
|
+
const blocks: Array<{ block: Record<string, unknown>; prefix: string }> = [];
|
|
104
|
+
const root = (manifest as { logging?: unknown }).logging;
|
|
105
|
+
if (isObject(root)) blocks.push({ block: root, prefix: "logging." });
|
|
106
|
+
|
|
107
|
+
// Inline imports map: `imports.<Alias>.logging`.
|
|
108
|
+
const imports = (manifest as { imports?: unknown }).imports;
|
|
109
|
+
if (isObject(imports)) {
|
|
110
|
+
for (const [alias, entry] of Object.entries(imports)) {
|
|
111
|
+
if (!isObject(entry)) continue;
|
|
112
|
+
const block = (entry as { logging?: unknown }).logging;
|
|
113
|
+
if (isObject(block)) blocks.push({ block, prefix: `imports.${alias}.logging.` });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return blocks;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isSinkKind(
|
|
120
|
+
manifest: ResourceManifest,
|
|
121
|
+
registry: DefinitionRegistry,
|
|
122
|
+
aliases: AliasResolver,
|
|
123
|
+
aliasesByModule?: Map<string, AliasResolver>,
|
|
124
|
+
): boolean {
|
|
125
|
+
if (typeof manifest.kind !== "string") return false;
|
|
126
|
+
if (manifest.kind === "Telo.ConsoleSink" || manifest.kind === "Telo.FileSink") return true;
|
|
127
|
+
const ownModule = (manifest.metadata as { module?: string } | undefined)?.module;
|
|
128
|
+
const resolver =
|
|
129
|
+
(ownModule ? aliasesByModule?.get(ownModule) : undefined) ?? aliases;
|
|
130
|
+
const canonical = resolver.resolveKind(manifest.kind) ?? manifest.kind;
|
|
131
|
+
return registry.resolve(canonical)?.capability === "Telo.Sink";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
|
+
}
|