@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.
Files changed (50) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +17 -0
  3. package/dist/builtins.d.ts.map +1 -1
  4. package/dist/builtins.js +146 -0
  5. package/dist/import-resolution-diagnostics.d.ts +20 -0
  6. package/dist/import-resolution-diagnostics.d.ts.map +1 -0
  7. package/dist/import-resolution-diagnostics.js +59 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +3 -0
  11. package/dist/inline-imports.d.ts.map +1 -1
  12. package/dist/inline-imports.js +1 -0
  13. package/dist/loaded-types.d.ts +12 -1
  14. package/dist/loaded-types.d.ts.map +1 -1
  15. package/dist/manifest-loader.d.ts.map +1 -1
  16. package/dist/manifest-loader.js +11 -1
  17. package/dist/normalize-inline-resources.d.ts.map +1 -1
  18. package/dist/normalize-inline-resources.js +20 -1
  19. package/dist/redaction-path.d.ts +49 -0
  20. package/dist/redaction-path.d.ts.map +1 -0
  21. package/dist/redaction-path.js +133 -0
  22. package/dist/reference-field-map.d.ts +12 -0
  23. package/dist/reference-field-map.d.ts.map +1 -1
  24. package/dist/reference-field-map.js +9 -0
  25. package/dist/schema-compat.d.ts.map +1 -1
  26. package/dist/schema-compat.js +8 -0
  27. package/dist/sources/local-path-ref.d.ts +4 -0
  28. package/dist/sources/local-path-ref.d.ts.map +1 -0
  29. package/dist/sources/local-path-ref.js +5 -0
  30. package/dist/validate-extends.d.ts +0 -20
  31. package/dist/validate-extends.d.ts.map +1 -1
  32. package/dist/validate-extends.js +7 -1
  33. package/dist/validate-logging.d.ts +22 -0
  34. package/dist/validate-logging.d.ts.map +1 -0
  35. package/dist/validate-logging.js +116 -0
  36. package/package.json +2 -2
  37. package/src/analyzer.ts +18 -0
  38. package/src/builtins.ts +152 -0
  39. package/src/import-resolution-diagnostics.ts +66 -0
  40. package/src/index.ts +8 -0
  41. package/src/inline-imports.ts +1 -0
  42. package/src/loaded-types.ts +12 -1
  43. package/src/manifest-loader.ts +11 -1
  44. package/src/normalize-inline-resources.ts +20 -1
  45. package/src/redaction-path.ts +142 -0
  46. package/src/reference-field-map.ts +20 -0
  47. package/src/schema-compat.ts +7 -0
  48. package/src/sources/local-path-ref.ts +5 -0
  49. package/src/validate-extends.ts +8 -1
  50. package/src/validate-logging.ts +136 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The redaction path grammar of `kernel/specs/logging.md` §14 — a hand-written
3
+ * parser over a closed grammar.
4
+ *
5
+ * §14.1 makes this a security requirement rather than a style preference. The
6
+ * implementation this syntax is borrowed from compiles paths through the
7
+ * `Function` constructor and validates them by "evaluate it and see whether it
8
+ * parses", which is exactly why that implementation must forbid user input. A
9
+ * real parser removes the injection surface entirely and, as a bonus, makes
10
+ * paths statically checkable by `telo check`.
11
+ *
12
+ * Browser-safe by construction: this module is imported by both the analyzer's
13
+ * static check and the kernel's runtime redaction pass, so the grammar has one
14
+ * definition rather than two that can drift.
15
+ *
16
+ * Grammar:
17
+ *
18
+ * path := segment ( "." segment | bracket )*
19
+ * segment := bareKey | "*"
20
+ * bracket := "[" ( quoted | integer | "*" ) "]"
21
+ * quoted := '"' ... '"' | "'" ... "'"
22
+ *
23
+ * More than one wildcard per path is supported — `items[*].tokens[*].value` is
24
+ * valid. The one-wildcard limit in the best-known implementation is an artifact
25
+ * of how it compiles accessors, not a property of the grammar.
26
+ */
27
+ export class RedactionPathError extends Error {
28
+ code = "INVALID_REDACTION_PATH";
29
+ path;
30
+ offset;
31
+ constructor(path, offset, detail) {
32
+ super(`Invalid redaction path "${path}" at position ${offset}: ${detail}`);
33
+ this.name = "RedactionPathError";
34
+ this.path = path;
35
+ this.offset = offset;
36
+ }
37
+ }
38
+ const BARE_KEY_TERMINATORS = new Set([".", "[", "]"]);
39
+ /**
40
+ * Parse a redaction path into its segments. Throws {@link RedactionPathError}
41
+ * with the offending offset so `telo check` can point at the character rather
42
+ * than the whole path.
43
+ */
44
+ export function parseRedactionPath(path) {
45
+ if (path.length === 0)
46
+ throw new RedactionPathError(path, 0, "path is empty");
47
+ const segments = [];
48
+ let index = 0;
49
+ let expectSegment = true;
50
+ while (index < path.length) {
51
+ const char = path[index];
52
+ if (char === "[") {
53
+ index = parseBracket(path, index, segments);
54
+ expectSegment = false;
55
+ continue;
56
+ }
57
+ if (char === ".") {
58
+ if (expectSegment) {
59
+ throw new RedactionPathError(path, index, "expected a key before '.'");
60
+ }
61
+ index += 1;
62
+ expectSegment = true;
63
+ continue;
64
+ }
65
+ if (char === "]") {
66
+ throw new RedactionPathError(path, index, "unmatched ']'");
67
+ }
68
+ const start = index;
69
+ while (index < path.length && !BARE_KEY_TERMINATORS.has(path[index]))
70
+ index += 1;
71
+ const raw = path.slice(start, index);
72
+ if (raw.length === 0)
73
+ throw new RedactionPathError(path, start, "empty key");
74
+ segments.push(raw === "*" ? { kind: "wildcard" } : { kind: "key", name: raw });
75
+ expectSegment = false;
76
+ }
77
+ if (expectSegment) {
78
+ throw new RedactionPathError(path, path.length, "path ends with a trailing '.'");
79
+ }
80
+ return segments;
81
+ }
82
+ function parseBracket(path, open, segments) {
83
+ let index = open + 1;
84
+ if (index >= path.length)
85
+ throw new RedactionPathError(path, open, "unterminated '['");
86
+ const quote = path[index];
87
+ if (quote === '"' || quote === "'") {
88
+ index += 1;
89
+ const start = index;
90
+ while (index < path.length && path[index] !== quote)
91
+ index += 1;
92
+ if (index >= path.length) {
93
+ throw new RedactionPathError(path, start, `unterminated ${quote} quoted key`);
94
+ }
95
+ const name = path.slice(start, index);
96
+ if (name.length === 0)
97
+ throw new RedactionPathError(path, start, "empty quoted key");
98
+ index += 1;
99
+ if (path[index] !== "]") {
100
+ throw new RedactionPathError(path, index, "expected ']' after quoted key");
101
+ }
102
+ segments.push({ kind: "key", name });
103
+ return index + 1;
104
+ }
105
+ const start = index;
106
+ while (index < path.length && path[index] !== "]")
107
+ index += 1;
108
+ if (index >= path.length)
109
+ throw new RedactionPathError(path, open, "unterminated '['");
110
+ const raw = path.slice(start, index);
111
+ if (raw.length === 0)
112
+ throw new RedactionPathError(path, start, "empty '[]'");
113
+ if (raw === "*") {
114
+ segments.push({ kind: "wildcard" });
115
+ }
116
+ else if (/^\d+$/.test(raw)) {
117
+ segments.push({ kind: "key", name: raw });
118
+ }
119
+ else {
120
+ throw new RedactionPathError(path, start, `expected a quoted key, an integer index, or '*', got "${raw}" — quote it as ["${raw}"]`);
121
+ }
122
+ return index + 1;
123
+ }
124
+ /** `true` when the path contains a wildcard anywhere but its last segment.
125
+ * §14.2 measures intermediate wildcards at 25–55% over plain serialization,
126
+ * against 1–2% for explicit paths, so a runtime may warn when one is used. */
127
+ export function hasIntermediateWildcard(segments) {
128
+ for (let i = 0; i < segments.length - 1; i += 1) {
129
+ if (segments[i].kind === "wildcard")
130
+ return true;
131
+ }
132
+ return false;
133
+ }
@@ -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;CAC/B;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;AAED,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"}
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,CAiB5E;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"}
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"}
@@ -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":
@@ -0,0 +1,4 @@
1
+ /** True when `source` names an on-disk sibling manifest — a relative (`./`,
2
+ * `../`) or absolute (`/`) path — rather than a transport-owned remote ref. */
3
+ export declare function isLocalPathSource(source: string): boolean;
4
+ //# sourceMappingURL=local-path-ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-path-ref.d.ts","sourceRoot":"","sources":["../../src/sources/local-path-ref.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD"}
@@ -0,0 +1,5 @@
1
+ /** True when `source` names an on-disk sibling manifest — a relative (`./`,
2
+ * `../`) or absolute (`/`) path — rather than a transport-owned remote ref. */
3
+ export function isLocalPathSource(source) {
4
+ return source.startsWith(".") || source.startsWith("/");
5
+ }
@@ -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;AAOzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAwJtB"}
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"}
@@ -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
- if (!aliases.hasAlias(prefix)) {
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.38.0",
3
+ "version": "0.40.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.48.0"
51
+ "@telorun/sdk": "0.50.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
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