@telorun/analyzer 0.37.0 → 0.39.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 +14 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +17 -0
- 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 +169 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/inline-imports.d.ts.map +1 -1
- package/dist/inline-imports.js +1 -0
- 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/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/analysis-registry.ts +20 -0
- package/src/analyzer.ts +18 -0
- package/src/builtins.ts +176 -0
- package/src/index.ts +6 -0
- package/src/inline-imports.ts +1 -0
- 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/validate-extends.ts +8 -1
- package/src/validate-logging.ts +136 -0
|
@@ -8,6 +8,18 @@ export interface RefFieldEntry {
|
|
|
8
8
|
/** x-telo-context schema declared on this ref slot, if any. Describes the CEL invocation
|
|
9
9
|
* context available to resources placed in this slot. */
|
|
10
10
|
context?: Record<string, any>;
|
|
11
|
+
/** `x-telo-inline: true` — this slot accepts an inline `{kind, ...config}`
|
|
12
|
+
* definition, not only a `!ref`.
|
|
13
|
+
*
|
|
14
|
+
* Only meaningful on the *system* kinds (`Telo.Application` and friends),
|
|
15
|
+
* which are otherwise excluded from inline-resource normalization wholesale.
|
|
16
|
+
* Ordinary resource kinds accept inline definitions at every ref slot and
|
|
17
|
+
* need no annotation. The flag exists so `logging.sinks` can opt in without
|
|
18
|
+
* also legalizing an inline definition in `targets`, where the Application
|
|
19
|
+
* schema rejects one deliberately — normalization runs upstream of AJV, so
|
|
20
|
+
* an unconditional opt-in would rewrite the value into a valid shape before
|
|
21
|
+
* the schema ever saw it. */
|
|
22
|
+
inline?: boolean;
|
|
11
23
|
}
|
|
12
24
|
/** An entry for a field that declares an execution scope (x-telo-scope). */
|
|
13
25
|
export interface ScopeFieldEntry {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B;;;;;;;;;;kCAU8B;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED;;;;sEAIsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;wDAUwD;AACxD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAoCpF;AAED;+DAC+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAExE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF;AASD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAa/D;AAED;;;8CAG8C;AAC9C,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAInB"}
|
|
@@ -103,6 +103,13 @@ export function buildReferenceFieldMap(schema) {
|
|
|
103
103
|
}
|
|
104
104
|
return map;
|
|
105
105
|
}
|
|
106
|
+
/** `x-telo-inline` declared on any `anyOf` branch marks the whole slot as
|
|
107
|
+
* inline-accepting, matching how {@link collectRefs} unions branch refs. */
|
|
108
|
+
function collectInlineFlag(node) {
|
|
109
|
+
if (!Array.isArray(node.anyOf))
|
|
110
|
+
return false;
|
|
111
|
+
return node.anyOf.some((branch) => branch?.["x-telo-inline"] === true);
|
|
112
|
+
}
|
|
106
113
|
export function collectRefs(node) {
|
|
107
114
|
const refs = [];
|
|
108
115
|
if (typeof node["x-telo-ref"] === "string") {
|
|
@@ -156,6 +163,8 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
156
163
|
const entry = { refs, isArray: path.includes("[]") };
|
|
157
164
|
if (node["x-telo-context"])
|
|
158
165
|
entry.context = node["x-telo-context"];
|
|
166
|
+
if (node["x-telo-inline"] === true || collectInlineFlag(node))
|
|
167
|
+
entry.inline = true;
|
|
159
168
|
map.set(path, entry);
|
|
160
169
|
// A node can mix item-level ref branches (a bare string / `{kind, name}`)
|
|
161
170
|
// with object branches that carry their OWN nested refs — e.g. Application
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CA2B/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGnD,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAyBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiChG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CA2B/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGnD,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAyBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiChG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAwB5E;AAID,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtG;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUlF;AAED;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -285,6 +285,14 @@ export function celTypeSatisfiesJsonSchema(celType, schema) {
|
|
|
285
285
|
export function celPlaceholderForSchema(schema) {
|
|
286
286
|
if (schema.default !== undefined)
|
|
287
287
|
return schema.default;
|
|
288
|
+
// An enum-constrained field needs a placeholder drawn from the enum: the
|
|
289
|
+
// type-based fallbacks below ("" for a string, 0 for a number) satisfy `type`
|
|
290
|
+
// but violate `enum`, so a CEL expression feeding any enum field would report
|
|
291
|
+
// a spurious SCHEMA_VIOLATION against a value the author never wrote. The
|
|
292
|
+
// member chosen is irrelevant — only its acceptability to AJV matters, since
|
|
293
|
+
// the real value is checked at runtime once the expression resolves.
|
|
294
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0)
|
|
295
|
+
return schema.enum[0];
|
|
288
296
|
switch (schema.type) {
|
|
289
297
|
case "integer":
|
|
290
298
|
case "number":
|
|
@@ -2,25 +2,5 @@ import type { ResourceManifest } from "@telorun/sdk";
|
|
|
2
2
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
3
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
4
|
import { type AnalysisDiagnostic } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
* Phase 3b — Validate `extends` fields on Telo.Definition docs, and flag the legacy
|
|
7
|
-
* `capability: <UserAbstract>` overload with CAPABILITY_SHADOWS_EXTENDS so users migrate.
|
|
8
|
-
*
|
|
9
|
-
* `extends` uses alias form ("<Alias>.<Name>") resolved against the declaring file's
|
|
10
|
-
* Telo.Import declarations — same pattern as `kind:` prefixes. The analyzer pre-resolves
|
|
11
|
-
* via AliasResolver before register() is called, so by the time this validator runs,
|
|
12
|
-
* the definition's effective `extends` is either the canonical form (when the alias was
|
|
13
|
-
* known) or the original alias-prefixed string (when it wasn't).
|
|
14
|
-
*
|
|
15
|
-
* Diagnostics:
|
|
16
|
-
* - EXTENDS_MALFORMED: value not in "<Alias>.<Name>" alias form, or not resolvable
|
|
17
|
-
* via the declaring file's imports (alias unknown → can't distinguish from a typo).
|
|
18
|
-
* - EXTENDS_UNKNOWN_TARGET: alias resolves to a module, but that module has no
|
|
19
|
-
* registered definition with the target name.
|
|
20
|
-
* - EXTENDS_NON_ABSTRACT: target resolves to a Telo.Definition, not a Telo.Abstract.
|
|
21
|
-
* - CAPABILITY_SHADOWS_EXTENDS (warning): `capability` names a user-declared abstract
|
|
22
|
-
* (metadata.module !== "Telo"). Builtin lifecycle capabilities (Telo.Invocable, etc.)
|
|
23
|
-
* never trigger this — they're lifecycle roles by design.
|
|
24
|
-
*/
|
|
25
5
|
export declare function validateExtends(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
|
|
26
6
|
//# sourceMappingURL=validate-extends.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-extends.d.ts","sourceRoot":"","sources":["../src/validate-extends.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,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;
|
|
1
|
+
{"version":3,"file":"validate-extends.d.ts","sourceRoot":"","sources":["../src/validate-extends.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,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;AA8BzE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CA4JtB"}
|
package/dist/validate-extends.js
CHANGED
|
@@ -23,6 +23,8 @@ const EXTENDS_ALIAS_RE = /^[A-Z][A-Za-z0-9_]*\.[A-Z][A-Za-z0-9_]*$/;
|
|
|
23
23
|
* (metadata.module !== "Telo"). Builtin lifecycle capabilities (Telo.Invocable, etc.)
|
|
24
24
|
* never trigger this — they're lifecycle roles by design.
|
|
25
25
|
*/
|
|
26
|
+
/** The built-in namespace, resolvable without a `Telo.Import`. */
|
|
27
|
+
const TELO_BUILTIN_ALIAS = "Telo";
|
|
26
28
|
export function validateExtends(manifests, registry, aliases) {
|
|
27
29
|
const diagnostics = [];
|
|
28
30
|
// Defs forwarded from imported libraries carry `metadata.module` set to that
|
|
@@ -81,7 +83,11 @@ export function validateExtends(manifests, registry, aliases) {
|
|
|
81
83
|
}
|
|
82
84
|
else {
|
|
83
85
|
const prefix = extendsValue.slice(0, extendsValue.indexOf("."));
|
|
84
|
-
|
|
86
|
+
// `Telo` needs no import: the kernel built-ins are globally resolvable
|
|
87
|
+
// by design, which is what lets a sink author depend on the kernel
|
|
88
|
+
// contract (`extends: Telo.LogSink`) rather than on a standard-library
|
|
89
|
+
// module version — see kernel/specs/logging.md §10.2.
|
|
90
|
+
if (prefix !== TELO_BUILTIN_ALIAS && !aliases.hasAlias(prefix)) {
|
|
85
91
|
diagnostics.push({
|
|
86
92
|
severity: DiagnosticSeverity.Error,
|
|
87
93
|
code: "EXTENDS_MALFORMED",
|
|
@@ -0,0 +1,22 @@
|
|
|
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 `logging:` block — `kernel/specs/logging.md` §14.1
|
|
7
|
+
* and §10.3.
|
|
8
|
+
*
|
|
9
|
+
* Two things the spec explicitly says are statically detectable, so leaving them
|
|
10
|
+
* to a runtime failure would contradict Telo's "manifests must remain statically
|
|
11
|
+
* analyzable" goal:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Redaction paths** parse against the closed §14 grammar. §14.1's whole
|
|
14
|
+
* argument for a hand-written parser is that it makes paths checkable by
|
|
15
|
+
* `telo check`; a bad path must fail here, not silently fail to redact at
|
|
16
|
+
* runtime.
|
|
17
|
+
* 2. **`on_full: block`** is unimplementable on a single-threaded runtime and
|
|
18
|
+
* §10.3 calls it "statically detectable by `telo check`". Catching it here —
|
|
19
|
+
* rather than only at boot — is what lets an operator fix it before shipping.
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateLogging(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): AnalysisDiagnostic[];
|
|
22
|
+
//# sourceMappingURL=validate-logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-logging.d.ts","sourceRoot":"","sources":["../src/validate-logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,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;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CAqBtB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { parseRedactionPath, RedactionPathError } from "./redaction-path.js";
|
|
2
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
3
|
+
const SOURCE = "telo-analyzer";
|
|
4
|
+
/**
|
|
5
|
+
* Static validation of the `logging:` block — `kernel/specs/logging.md` §14.1
|
|
6
|
+
* and §10.3.
|
|
7
|
+
*
|
|
8
|
+
* Two things the spec explicitly says are statically detectable, so leaving them
|
|
9
|
+
* to a runtime failure would contradict Telo's "manifests must remain statically
|
|
10
|
+
* analyzable" goal:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Redaction paths** parse against the closed §14 grammar. §14.1's whole
|
|
13
|
+
* argument for a hand-written parser is that it makes paths checkable by
|
|
14
|
+
* `telo check`; a bad path must fail here, not silently fail to redact at
|
|
15
|
+
* runtime.
|
|
16
|
+
* 2. **`on_full: block`** is unimplementable on a single-threaded runtime and
|
|
17
|
+
* §10.3 calls it "statically detectable by `telo check`". Catching it here —
|
|
18
|
+
* rather than only at boot — is what lets an operator fix it before shipping.
|
|
19
|
+
*/
|
|
20
|
+
export function validateLogging(manifests, registry, aliases, aliasesByModule) {
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
for (const manifest of manifests) {
|
|
23
|
+
const kind = manifest.kind;
|
|
24
|
+
// Redaction paths live on the `logging:` block of the root Application and on
|
|
25
|
+
// any `Telo.Import` / `Telo.Library` doc carrying a per-import override.
|
|
26
|
+
if (kind === "Telo.Application" || kind === "Telo.Import" || kind === "Telo.Library") {
|
|
27
|
+
validateRedactPaths(manifest, diagnostics);
|
|
28
|
+
}
|
|
29
|
+
// `on_full` lives on any sink instance — an inline `logging.sinks[]` entry
|
|
30
|
+
// (extracted by Phase 2 into a first-class manifest by now) or a standalone
|
|
31
|
+
// sink resource declared elsewhere and reached via `!ref`.
|
|
32
|
+
if (isSinkKind(manifest, registry, aliases, aliasesByModule)) {
|
|
33
|
+
validateOnFull(manifest, diagnostics);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return diagnostics;
|
|
37
|
+
}
|
|
38
|
+
function validateRedactPaths(manifest, out) {
|
|
39
|
+
const name = manifest.metadata?.name;
|
|
40
|
+
const filePath = manifest.metadata?.source;
|
|
41
|
+
const resource = { kind: manifest.kind, name };
|
|
42
|
+
for (const { block, prefix } of loggingBlocks(manifest)) {
|
|
43
|
+
const paths = block.redact?.paths;
|
|
44
|
+
if (!Array.isArray(paths))
|
|
45
|
+
continue;
|
|
46
|
+
paths.forEach((path, index) => {
|
|
47
|
+
// A `!cel` path is a compiled/sentinel node by now, not a string — its
|
|
48
|
+
// value isn't known statically, so there is nothing to parse.
|
|
49
|
+
if (typeof path !== "string")
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
parseRedactionPath(path);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (!(err instanceof RedactionPathError))
|
|
56
|
+
throw err;
|
|
57
|
+
out.push({
|
|
58
|
+
severity: DiagnosticSeverity.Error,
|
|
59
|
+
code: "INVALID_REDACTION_PATH",
|
|
60
|
+
source: SOURCE,
|
|
61
|
+
message: `${manifest.kind}/${name ?? "(unnamed)"}: ${err.message}`,
|
|
62
|
+
data: { resource, filePath, path: `${prefix}redact.paths[${index}]` },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function validateOnFull(manifest, out) {
|
|
69
|
+
const onFull = manifest.on_full;
|
|
70
|
+
if (onFull !== "block")
|
|
71
|
+
return;
|
|
72
|
+
const name = manifest.metadata?.name;
|
|
73
|
+
const filePath = manifest.metadata?.source;
|
|
74
|
+
out.push({
|
|
75
|
+
severity: DiagnosticSeverity.Error,
|
|
76
|
+
code: "LOG_SINK_ON_FULL_UNSUPPORTED",
|
|
77
|
+
source: SOURCE,
|
|
78
|
+
message: `${manifest.kind}/${name ?? "(unnamed)"}: on_full: block is not supported on a ` +
|
|
79
|
+
`single-threaded runtime — blocking the producer would stall the writer. ` +
|
|
80
|
+
`Use drop_new or drop_old.`,
|
|
81
|
+
data: { resource: { kind: manifest.kind, name }, filePath, path: "on_full" },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** The root `logging:` block plus every per-import `logging:` override, each
|
|
85
|
+
* with the dotted path prefix a diagnostic anchors against. */
|
|
86
|
+
function loggingBlocks(manifest) {
|
|
87
|
+
const blocks = [];
|
|
88
|
+
const root = manifest.logging;
|
|
89
|
+
if (isObject(root))
|
|
90
|
+
blocks.push({ block: root, prefix: "logging." });
|
|
91
|
+
// Inline imports map: `imports.<Alias>.logging`.
|
|
92
|
+
const imports = manifest.imports;
|
|
93
|
+
if (isObject(imports)) {
|
|
94
|
+
for (const [alias, entry] of Object.entries(imports)) {
|
|
95
|
+
if (!isObject(entry))
|
|
96
|
+
continue;
|
|
97
|
+
const block = entry.logging;
|
|
98
|
+
if (isObject(block))
|
|
99
|
+
blocks.push({ block, prefix: `imports.${alias}.logging.` });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return blocks;
|
|
103
|
+
}
|
|
104
|
+
function isSinkKind(manifest, registry, aliases, aliasesByModule) {
|
|
105
|
+
if (typeof manifest.kind !== "string")
|
|
106
|
+
return false;
|
|
107
|
+
if (manifest.kind === "Telo.ConsoleSink" || manifest.kind === "Telo.FileSink")
|
|
108
|
+
return true;
|
|
109
|
+
const ownModule = manifest.metadata?.module;
|
|
110
|
+
const resolver = (ownModule ? aliasesByModule?.get(ownModule) : undefined) ?? aliases;
|
|
111
|
+
const canonical = resolver.resolveKind(manifest.kind) ?? manifest.kind;
|
|
112
|
+
return registry.resolve(canonical)?.capability === "Telo.Sink";
|
|
113
|
+
}
|
|
114
|
+
function isObject(value) {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@types/node": "^20.0.0",
|
|
49
49
|
"typescript": "^5.0.0",
|
|
50
50
|
"vitest": "^2.1.8",
|
|
51
|
-
"@telorun/sdk": "0.
|
|
51
|
+
"@telorun/sdk": "0.50.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@telorun/sdk": "*"
|
package/src/analysis-registry.ts
CHANGED
|
@@ -218,6 +218,26 @@ export class AnalysisRegistry {
|
|
|
218
218
|
return ctx.definitions?.resolve(kind) ?? (resolved ? ctx.definitions?.resolve(resolved) : undefined);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
/** A resolver scoped to `def`'s OWN module, for resolving that definition's
|
|
222
|
+
* `extends` target.
|
|
223
|
+
*
|
|
224
|
+
* `extends` aliases are lexically scoped to the declaring library — a library
|
|
225
|
+
* writes `extends: Cache.Store` against its own import map, and `Self.Host`
|
|
226
|
+
* against its own module name. The global alias table knows neither, so
|
|
227
|
+
* {@link resolveDefinition} silently returns undefined for them and callers
|
|
228
|
+
* fall back to an un-inherited view. Mirrors the module-scope selection in
|
|
229
|
+
* `expandedFieldMapForResource`. */
|
|
230
|
+
resolverForDefinition(def: {
|
|
231
|
+
metadata?: { module?: string };
|
|
232
|
+
}): (kind: string) => ResourceDefinition | undefined {
|
|
233
|
+
const ownModule = def?.metadata?.module;
|
|
234
|
+
const scope = (ownModule ? this.aliasesByModule.get(ownModule) : undefined) ?? this.aliases;
|
|
235
|
+
return (kind) => {
|
|
236
|
+
const canonical = scope.resolveKind(kind);
|
|
237
|
+
return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
221
241
|
/** Canonical kinds (`module.Name`) of every definition that extends the given
|
|
222
242
|
* abstract kind — the concrete implementations a caller may instantiate in
|
|
223
243
|
* its place. Empty when `kind` is not an abstract or has no implementations. */
|
package/src/analyzer.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from "./validate-cel-context.js";
|
|
41
41
|
import { buildEvalPaths, evalPathsCover } from "./eval-paths.js";
|
|
42
42
|
import { validateExtends } from "./validate-extends.js";
|
|
43
|
+
import { validateLogging } from "./validate-logging.js";
|
|
43
44
|
import { validateBaseMapping } from "./validate-base-mapping.js";
|
|
44
45
|
import { validateNestedInlineResources } from "./validate-nested-inline.js";
|
|
45
46
|
import { validateProviderCoherence } from "./validate-provider-coherence.js";
|
|
@@ -432,11 +433,17 @@ function buildStepContextSchema(
|
|
|
432
433
|
* handler dispatched directly, e.g. `Lambda.Function`), so it can't be rejected
|
|
433
434
|
* statically without false positives. This is the sound subset of the runtime rule.
|
|
434
435
|
*/
|
|
436
|
+
/** The built-in namespace: globally resolvable, crossing no import boundary. */
|
|
437
|
+
const TELO_BUILTIN_MODULE = "Telo";
|
|
438
|
+
|
|
435
439
|
const NON_INVOKABLE_CAPABILITIES = new Set([
|
|
436
440
|
"Telo.Provider",
|
|
437
441
|
"Telo.Mount",
|
|
438
442
|
"Telo.Type",
|
|
439
443
|
"Telo.Template",
|
|
444
|
+
// A sink is written to through a direct contract on the controller instance,
|
|
445
|
+
// never dispatched — so invoking one is statically wrong.
|
|
446
|
+
"Telo.Sink",
|
|
440
447
|
]);
|
|
441
448
|
|
|
442
449
|
/**
|
|
@@ -859,6 +866,12 @@ export class StaticAnalyzer {
|
|
|
859
866
|
// so state builds up across successive calls (e.g. incremental editor validation).
|
|
860
867
|
const ctx = registry?._context();
|
|
861
868
|
const aliases = ctx?.aliases ?? new AliasResolver();
|
|
869
|
+
// `Telo` crosses no import boundary — the kernel built-ins are globally
|
|
870
|
+
// resolvable, which is what lets `kind: Telo.ConsoleSink` and
|
|
871
|
+
// `extends: Telo.LogSink` work with no `imports:` entry (§10.2). The kernel
|
|
872
|
+
// registers the same ungated alias at boot; registering it here keeps the
|
|
873
|
+
// static and runtime halves agreeing.
|
|
874
|
+
aliases.registerUngatedAlias(TELO_BUILTIN_MODULE, TELO_BUILTIN_MODULE);
|
|
862
875
|
const defs = ctx?.definitions ?? new DefinitionRegistry();
|
|
863
876
|
|
|
864
877
|
// Register module identities and aliases.
|
|
@@ -908,6 +921,7 @@ export class StaticAnalyzer {
|
|
|
908
921
|
let libResolver = aliasesByModule.get(moduleName);
|
|
909
922
|
if (!libResolver) {
|
|
910
923
|
libResolver = new AliasResolver();
|
|
924
|
+
libResolver.registerUngatedAlias(TELO_BUILTIN_MODULE, TELO_BUILTIN_MODULE);
|
|
911
925
|
aliasesByModule.set(moduleName, libResolver);
|
|
912
926
|
}
|
|
913
927
|
libResolver.registerUngatedAlias("Self", moduleName);
|
|
@@ -1002,6 +1016,7 @@ export class StaticAnalyzer {
|
|
|
1002
1016
|
let libResolver = aliasesByModule.get(ownModule);
|
|
1003
1017
|
if (!libResolver) {
|
|
1004
1018
|
libResolver = new AliasResolver();
|
|
1019
|
+
libResolver.registerUngatedAlias(TELO_BUILTIN_MODULE, TELO_BUILTIN_MODULE);
|
|
1005
1020
|
aliasesByModule.set(ownModule, libResolver);
|
|
1006
1021
|
}
|
|
1007
1022
|
if (!libResolver.hasAlias("Self")) {
|
|
@@ -1082,6 +1097,9 @@ export class StaticAnalyzer {
|
|
|
1082
1097
|
diagnostics.push(
|
|
1083
1098
|
...validateSchemaTypeRefs(allManifests, defs, aliases, aliasesByModule, rootModules),
|
|
1084
1099
|
);
|
|
1100
|
+
// §14.1 / §10.3: redaction paths and `on_full: block` are statically
|
|
1101
|
+
// detectable, so they fail `telo check` rather than only at boot.
|
|
1102
|
+
diagnostics.push(...validateLogging(allManifests, defs, aliases, aliasesByModule));
|
|
1085
1103
|
}
|
|
1086
1104
|
resolveSchemaTypeRefs(allManifests, aliases, aliasesByModule);
|
|
1087
1105
|
|
package/src/builtins.ts
CHANGED
|
@@ -1,5 +1,110 @@
|
|
|
1
1
|
import type { ResourceDefinition } from "@telorun/sdk";
|
|
2
2
|
|
|
3
|
+
/** Descriptive provenance a module declares about itself, shared by
|
|
4
|
+
* `Telo.Application` and `Telo.Library`.
|
|
5
|
+
*
|
|
6
|
+
* These are *descriptive*, never *addressing* — nothing resolves, fetches,
|
|
7
|
+
* caches, or publishes based on them, so they do not conflict with
|
|
8
|
+
* identity-is-the-ref (which bans metadata from determining an artifact's
|
|
9
|
+
* location). `repository` is the location of the module's **source code**, in
|
|
10
|
+
* the same spirit as npm's `repository` / `license` / `homepage`. It is named
|
|
11
|
+
* `repository` rather than `source` because `source:` already means "where to
|
|
12
|
+
* fetch a dependency from" in the `imports` map.
|
|
13
|
+
*
|
|
14
|
+
* A publish transport projects these into whatever its backend surfaces —
|
|
15
|
+
* OCI maps them onto the standard `org.opencontainers.image.*` annotations;
|
|
16
|
+
* the HTTP registry stores the manifest verbatim, so they are preserved as
|
|
17
|
+
* declared with nothing to translate. */
|
|
18
|
+
const PROVENANCE_METADATA = {
|
|
19
|
+
description: { type: "string" },
|
|
20
|
+
repository: { type: "string" },
|
|
21
|
+
license: { type: "string" },
|
|
22
|
+
documentation: { type: "string" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** The six named levels of `kernel/specs/logging.md` §5.1. The full 1–24 OTel
|
|
26
|
+
* range stays valid on the wire; only these are nameable in a manifest. */
|
|
27
|
+
const LOG_LEVEL_ENUM = ["trace", "debug", "info", "warn", "error", "fatal"];
|
|
28
|
+
|
|
29
|
+
const DURATION_PATTERN = "^\\s*\\d+(\\.\\d+)?\\s*(ms|s|m|h)\\s*$";
|
|
30
|
+
|
|
31
|
+
/** Fields every sink kind inherits from `Telo.LogSink` (§12.1). A concrete sink
|
|
32
|
+
* kind may narrow a default but must not remove a field — a buffering policy
|
|
33
|
+
* that cannot be configured from the only permitted configuration source is
|
|
34
|
+
* not a policy. */
|
|
35
|
+
const LOG_SINK_COMMON_PROPERTIES = {
|
|
36
|
+
level: { type: "string", enum: LOG_LEVEL_ENUM },
|
|
37
|
+
buffer: { type: "integer", minimum: 1 },
|
|
38
|
+
on_full: { type: "string", enum: ["block", "drop_new", "drop_old"] },
|
|
39
|
+
flush_interval: { type: "string", pattern: DURATION_PATTERN },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Threshold / redaction / sampling — the fields an `imports:` entry may
|
|
43
|
+
* override for its subtree (§12.2). Deliberately excludes `sinks`: sinks are
|
|
44
|
+
* process-level I/O and belong to the root Application that owns the process,
|
|
45
|
+
* so an imported library can never open a log file on its importer's behalf. */
|
|
46
|
+
const LOGGING_SCOPE_PROPERTIES = {
|
|
47
|
+
level: { type: "string", enum: LOG_LEVEL_ENUM },
|
|
48
|
+
attributes: { type: "object" },
|
|
49
|
+
redact: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
paths: { type: "array", items: { type: "string" } },
|
|
53
|
+
censor: { type: "string" },
|
|
54
|
+
// Deletion destroys schema stability and hides that a field was present
|
|
55
|
+
// at all, so §14 offers this but never as the default.
|
|
56
|
+
remove: { type: "boolean" },
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
},
|
|
60
|
+
sampling: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
first: { type: "integer", minimum: 0 },
|
|
64
|
+
thereafter: { type: "integer", minimum: 0 },
|
|
65
|
+
tick: { type: "string", pattern: DURATION_PATTERN },
|
|
66
|
+
sampleErrors: { type: "boolean" },
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** The per-import `logging:` override. */
|
|
73
|
+
const IMPORT_LOGGING_SCHEMA = {
|
|
74
|
+
type: "object",
|
|
75
|
+
"x-telo-eval": "compile",
|
|
76
|
+
properties: LOGGING_SCOPE_PROPERTIES,
|
|
77
|
+
additionalProperties: false,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** The root Application's `logging:` block — the scope fields plus `sinks`.
|
|
81
|
+
*
|
|
82
|
+
* `x-telo-eval: compile` covers the whole block: every value resolves once at
|
|
83
|
+
* load, which is what lets a level come from the host environment through a
|
|
84
|
+
* `variables:` entry read with `!cel` rather than through a parallel
|
|
85
|
+
* `TELO_LOG_*` path that would be invisible to the analyzer and the editor
|
|
86
|
+
* (§12.3, D6). */
|
|
87
|
+
const ROOT_LOGGING_SCHEMA = {
|
|
88
|
+
type: "object",
|
|
89
|
+
"x-telo-eval": "compile",
|
|
90
|
+
properties: {
|
|
91
|
+
...LOGGING_SCOPE_PROPERTIES,
|
|
92
|
+
// A list rather than a keyed map because sinks are root-only and therefore
|
|
93
|
+
// never merged; with no merge to disambiguate, a list matches how Telo
|
|
94
|
+
// spells every other ref-or-inline collection. `x-telo-inline` opts this one
|
|
95
|
+
// slot into inline-resource extraction — see normalize-inline-resources.ts.
|
|
96
|
+
sinks: {
|
|
97
|
+
type: "array",
|
|
98
|
+
items: {
|
|
99
|
+
type: "object",
|
|
100
|
+
"x-telo-ref": "telo#LogSink",
|
|
101
|
+
"x-telo-inline": true,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
};
|
|
107
|
+
|
|
3
108
|
export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
4
109
|
{ kind: "Telo.Abstract", metadata: { name: "Template", module: "Telo" } },
|
|
5
110
|
{ kind: "Telo.Abstract", metadata: { name: "Runnable", module: "Telo" } },
|
|
@@ -12,6 +117,59 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
12
117
|
metadata: { name: "Provider", module: "Telo" },
|
|
13
118
|
schema: { "x-telo-eval": "compile" },
|
|
14
119
|
},
|
|
120
|
+
// The sink lifecycle role: attach, write a record, flush, detach. Deliberately
|
|
121
|
+
// payload-opaque — it carries no filtering and no encoding — so a future
|
|
122
|
+
// `Telo.TraceSink` reuses the same capability with a different record type.
|
|
123
|
+
// Scoped to record-stream sinks; metrics aggregate rather than stream and are
|
|
124
|
+
// not covered. See kernel/specs/logging.md §10.
|
|
125
|
+
{ kind: "Telo.Abstract", metadata: { name: "Sink", module: "Telo" } },
|
|
126
|
+
// The abstract every *log* sink kind extends, carrying the log-specific
|
|
127
|
+
// configuration. A kernel built-in resolvable without an import, so a sink
|
|
128
|
+
// author depends on the kernel contract rather than on a standard-library
|
|
129
|
+
// module version and kernel↔module skew never becomes a compatibility surface
|
|
130
|
+
// for "where do logs go".
|
|
131
|
+
{
|
|
132
|
+
kind: "Telo.Abstract",
|
|
133
|
+
metadata: { name: "LogSink", module: "Telo" },
|
|
134
|
+
capability: "Telo.Sink",
|
|
135
|
+
schema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: LOG_SINK_COMMON_PROPERTIES,
|
|
138
|
+
additionalProperties: true,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
kind: "Telo.Definition",
|
|
143
|
+
metadata: { name: "ConsoleSink", module: "Telo" },
|
|
144
|
+
capability: "Telo.Sink",
|
|
145
|
+
extends: "Telo.LogSink",
|
|
146
|
+
schema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
...LOG_SINK_COMMON_PROPERTIES,
|
|
150
|
+
destination: { type: "string", enum: ["stderr", "stdout"] },
|
|
151
|
+
encoding: { type: "string", enum: ["auto", "pretty", "json"] },
|
|
152
|
+
color: { type: "string", enum: ["auto", "always", "never"] },
|
|
153
|
+
},
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
kind: "Telo.Definition",
|
|
159
|
+
metadata: { name: "FileSink", module: "Telo" },
|
|
160
|
+
capability: "Telo.Sink",
|
|
161
|
+
extends: "Telo.LogSink",
|
|
162
|
+
schema: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
...LOG_SINK_COMMON_PROPERTIES,
|
|
166
|
+
destination: { type: "string" },
|
|
167
|
+
encoding: { type: "string", enum: ["json", "pretty"] },
|
|
168
|
+
},
|
|
169
|
+
required: ["destination"],
|
|
170
|
+
additionalProperties: false,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
15
173
|
{
|
|
16
174
|
kind: "Telo.Definition",
|
|
17
175
|
metadata: { name: "Abstract", module: "Telo" },
|
|
@@ -251,6 +409,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
251
409
|
{ type: "array", items: { type: "string" } },
|
|
252
410
|
],
|
|
253
411
|
},
|
|
412
|
+
logging: IMPORT_LOGGING_SCHEMA,
|
|
254
413
|
},
|
|
255
414
|
required: ["metadata", "source"],
|
|
256
415
|
additionalProperties: false,
|
|
@@ -271,6 +430,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
271
430
|
version: { type: "string" },
|
|
272
431
|
source: { type: "string" },
|
|
273
432
|
module: { type: "string" },
|
|
433
|
+
...PROVENANCE_METADATA,
|
|
274
434
|
},
|
|
275
435
|
required: ["name"],
|
|
276
436
|
additionalProperties: true,
|
|
@@ -408,6 +568,11 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
408
568
|
{ type: "array", items: { type: "string" } },
|
|
409
569
|
],
|
|
410
570
|
},
|
|
571
|
+
// Threshold / redaction / sampling override for this import's
|
|
572
|
+
// subtree. Attached to the import rather than to a map keyed
|
|
573
|
+
// by module name because an alias is already uniqueness-
|
|
574
|
+
// enforced, while module names collide (§12.2, D9).
|
|
575
|
+
logging: IMPORT_LOGGING_SCHEMA,
|
|
411
576
|
},
|
|
412
577
|
additionalProperties: false,
|
|
413
578
|
},
|
|
@@ -477,6 +642,11 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
477
642
|
additionalProperties: false,
|
|
478
643
|
},
|
|
479
644
|
},
|
|
645
|
+
// Structured logging configuration. The manifest is the only
|
|
646
|
+
// configuration source — there is no TELO_LOG_* variable and no logging
|
|
647
|
+
// CLI flag — so a level derived from the host environment goes through a
|
|
648
|
+
// `variables:` entry read with `!cel`. See kernel/specs/logging.md §12.
|
|
649
|
+
logging: ROOT_LOGGING_SCHEMA,
|
|
480
650
|
},
|
|
481
651
|
required: ["metadata"],
|
|
482
652
|
additionalProperties: false,
|
|
@@ -497,6 +667,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
497
667
|
version: { type: "string" },
|
|
498
668
|
source: { type: "string" },
|
|
499
669
|
module: { type: "string" },
|
|
670
|
+
...PROVENANCE_METADATA,
|
|
500
671
|
},
|
|
501
672
|
required: ["name"],
|
|
502
673
|
additionalProperties: true,
|
|
@@ -541,6 +712,11 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
541
712
|
{ type: "array", items: { type: "string" } },
|
|
542
713
|
],
|
|
543
714
|
},
|
|
715
|
+
// Threshold / redaction / sampling override for this import's
|
|
716
|
+
// subtree. Attached to the import rather than to a map keyed
|
|
717
|
+
// by module name because an alias is already uniqueness-
|
|
718
|
+
// enforced, while module names collide (§12.2, D9).
|
|
719
|
+
logging: IMPORT_LOGGING_SCHEMA,
|
|
544
720
|
},
|
|
545
721
|
additionalProperties: false,
|
|
546
722
|
},
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,12 @@ export {
|
|
|
33
33
|
resolveParent,
|
|
34
34
|
} from "./extends-resolution.js";
|
|
35
35
|
export type { DefResolver } from "./extends-resolution.js";
|
|
36
|
+
export {
|
|
37
|
+
hasIntermediateWildcard,
|
|
38
|
+
parseRedactionPath,
|
|
39
|
+
RedactionPathError,
|
|
40
|
+
} from "./redaction-path.js";
|
|
41
|
+
export type { RedactionSegment } from "./redaction-path.js";
|
|
36
42
|
export { buildReferenceFieldMap, isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
37
43
|
export type { ReferenceFieldMap, RefFieldEntry } from "./reference-field-map.js";
|
|
38
44
|
export { visitManifest } from "./manifest-visitor.js";
|
package/src/inline-imports.ts
CHANGED
|
@@ -55,6 +55,7 @@ export function inlineImportManifests(
|
|
|
55
55
|
...(entry.variables !== undefined ? { variables: entry.variables } : {}),
|
|
56
56
|
...(entry.secrets !== undefined ? { secrets: entry.secrets } : {}),
|
|
57
57
|
...(entry.runtime !== undefined ? { runtime: entry.runtime } : {}),
|
|
58
|
+
...(entry.logging !== undefined ? { logging: entry.logging } : {}),
|
|
58
59
|
} as unknown as ResourceManifest;
|
|
59
60
|
|
|
60
61
|
out.push({ manifest, position: synthPosition(modulePosition, alias, scalar) });
|