@telorun/analyzer 0.56.0 → 0.57.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 (40) hide show
  1. package/README.md +2 -2
  2. package/dist/analyzer.d.ts +5 -0
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +155 -94
  5. package/dist/flatten-for-analyzer.d.ts +20 -1
  6. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  7. package/dist/flatten-for-analyzer.js +48 -3
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -1
  11. package/dist/kernel-globals.d.ts +25 -11
  12. package/dist/kernel-globals.d.ts.map +1 -1
  13. package/dist/kernel-globals.js +54 -24
  14. package/dist/manifest-visitor.d.ts +4 -0
  15. package/dist/manifest-visitor.d.ts.map +1 -1
  16. package/dist/manifest-visitor.js +3 -3
  17. package/dist/module-file-claims.d.ts +65 -0
  18. package/dist/module-file-claims.d.ts.map +1 -0
  19. package/dist/module-file-claims.js +106 -0
  20. package/dist/schema-compat.d.ts.map +1 -1
  21. package/dist/schema-compat.js +12 -1
  22. package/dist/types.d.ts +34 -0
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/types.js +6 -0
  25. package/dist/validate-include-placement.d.ts +26 -0
  26. package/dist/validate-include-placement.d.ts.map +1 -0
  27. package/dist/validate-include-placement.js +67 -0
  28. package/dist/validate-throws-coverage.d.ts.map +1 -1
  29. package/dist/validate-throws-coverage.js +15 -12
  30. package/package.json +4 -3
  31. package/src/analyzer.ts +189 -131
  32. package/src/flatten-for-analyzer.ts +61 -3
  33. package/src/index.ts +5 -1
  34. package/src/kernel-globals.ts +74 -25
  35. package/src/manifest-visitor.ts +11 -3
  36. package/src/module-file-claims.ts +168 -0
  37. package/src/schema-compat.ts +19 -1
  38. package/src/types.ts +37 -0
  39. package/src/validate-include-placement.ts +70 -0
  40. package/src/validate-throws-coverage.ts +16 -11
@@ -11,20 +11,34 @@ import type { ResourceManifest } from "@telorun/sdk";
11
11
  * and referencing `variables.X` / `secrets.X`.
12
12
  */
13
13
  export declare const KERNEL_GLOBAL_NAMES: readonly ["variables", "secrets", "resources", "ports"];
14
+ /** Kernel globals as ONE resource's declaring module sees them. */
15
+ export interface KernelGlobalsIndex {
16
+ forResource(m: ResourceManifest): Record<string, any>;
17
+ }
14
18
  /**
15
- * Build a typed JSON Schema describing the kernel globals available in the
16
- * given manifest set. Used to merge into `x-telo-context` schemas so that
17
- * chain-access validation recognises kernel globals without module authors
19
+ * Build the typed JSON Schema describing the kernel globals available to each
20
+ * resource in a manifest set. Used to merge into `x-telo-context` schemas so
21
+ * that chain-access validation recognises kernel globals without module authors
18
22
  * having to re-declare them.
19
23
  *
20
- * - `variables` / `secrets`: typed from the root module doc prefer
21
- * Telo.Application when present, otherwise fall back to Telo.Library.
22
- * Applications are the root whose variables/secrets contract governs CEL
23
- * in the outer module; Libraries are only relevant when the caller scoped
24
- * the manifest list to a single library's file.
25
- * - `resources`: enumerates all non-system resource names
24
+ * `variables` / `secrets` / `ports` are typed **per declaring module**, because
25
+ * that is the contract the resource's CEL is evaluated against at runtime. An
26
+ * application analysis is flattened `selectModuleManifestsForAnalysis` drops
27
+ * an imported library's module doc and carries its config blocks across as
28
+ * `metadata.moduleGlobals` instead so a resource forwarded from a library is
29
+ * typed from that stamp, and everything else from the entry module's own doc
30
+ * (the only module doc left in the set).
31
+ *
32
+ * Typing every resource from the entry doc is what made a library's
33
+ * `variables.x` a hard error the library author could not act on, and — in the
34
+ * other direction — let a library read a variable it never declared whenever the
35
+ * app happened to declare that name.
36
+ *
37
+ * `resources` is NOT per module: it enumerates every non-system resource name in
38
+ * the set, and stays OPEN for a forwarded manifest (see `readModuleGlobals` for
39
+ * why a name list cannot be carried across the boundary honestly).
26
40
  */
27
- export declare function buildKernelGlobalsSchema(manifests: ResourceManifest[],
41
+ export declare function buildKernelGlobalsIndex(manifests: ResourceManifest[],
28
42
  /** Every resource a CEL read can name, including scope-declared ones (see
29
43
  * `buildObservedStateIndex`). Kinds that declare a `status:` get a typed,
30
44
  * closed `status` node; every other resource node stays open, so no flat read
@@ -32,7 +46,7 @@ export declare function buildKernelGlobalsSchema(manifests: ResourceManifest[],
32
46
  resources?: ReadonlyMap<string, {
33
47
  kind: string;
34
48
  status?: Record<string, any>;
35
- }>): Record<string, any>;
49
+ }>): KernelGlobalsIndex;
36
50
  /**
37
51
  * Merge kernel globals into an `x-telo-context` schema so chain-access
38
52
  * validation recognises `variables`, `secrets`, `resources`, `ports`
@@ -1 +1 @@
1
- {"version":3,"file":"kernel-globals.d.ts","sourceRoot":"","sources":["../src/kernel-globals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,yDAA0D,CAAC;AAS3F;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE;AAC7B;;;2CAG2C;AAC3C,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,GAC9E,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CA+CrB;AA4CD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB"}
1
+ {"version":3,"file":"kernel-globals.d.ts","sourceRoot":"","sources":["../src/kernel-globals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,yDAA0D,CAAC;AAS3F,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,gBAAgB,EAAE;AAC7B;;;2CAG2C;AAC3C,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,GAC9E,kBAAkB,CA0BpB;AAmGD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB"}
@@ -19,26 +19,68 @@ const SYSTEM_KINDS = new Set([
19
19
  "Telo.Abstract",
20
20
  ]);
21
21
  /**
22
- * Build a typed JSON Schema describing the kernel globals available in the
23
- * given manifest set. Used to merge into `x-telo-context` schemas so that
24
- * chain-access validation recognises kernel globals without module authors
22
+ * Build the typed JSON Schema describing the kernel globals available to each
23
+ * resource in a manifest set. Used to merge into `x-telo-context` schemas so
24
+ * that chain-access validation recognises kernel globals without module authors
25
25
  * having to re-declare them.
26
26
  *
27
- * - `variables` / `secrets`: typed from the root module doc prefer
28
- * Telo.Application when present, otherwise fall back to Telo.Library.
29
- * Applications are the root whose variables/secrets contract governs CEL
30
- * in the outer module; Libraries are only relevant when the caller scoped
31
- * the manifest list to a single library's file.
32
- * - `resources`: enumerates all non-system resource names
27
+ * `variables` / `secrets` / `ports` are typed **per declaring module**, because
28
+ * that is the contract the resource's CEL is evaluated against at runtime. An
29
+ * application analysis is flattened `selectModuleManifestsForAnalysis` drops
30
+ * an imported library's module doc and carries its config blocks across as
31
+ * `metadata.moduleGlobals` instead so a resource forwarded from a library is
32
+ * typed from that stamp, and everything else from the entry module's own doc
33
+ * (the only module doc left in the set).
34
+ *
35
+ * Typing every resource from the entry doc is what made a library's
36
+ * `variables.x` a hard error the library author could not act on, and — in the
37
+ * other direction — let a library read a variable it never declared whenever the
38
+ * app happened to declare that name.
39
+ *
40
+ * `resources` is NOT per module: it enumerates every non-system resource name in
41
+ * the set, and stays OPEN for a forwarded manifest (see `readModuleGlobals` for
42
+ * why a name list cannot be carried across the boundary honestly).
33
43
  */
34
- export function buildKernelGlobalsSchema(manifests,
44
+ export function buildKernelGlobalsIndex(manifests,
35
45
  /** Every resource a CEL read can name, including scope-declared ones (see
36
46
  * `buildObservedStateIndex`). Kinds that declare a `status:` get a typed,
37
47
  * closed `status` node; every other resource node stays open, so no flat read
38
48
  * that passes today can start failing. */
39
49
  resources) {
40
- const moduleManifest = manifests.find((m) => m.kind === "Telo.Application") ??
50
+ const entryDoc = manifests.find((m) => m.kind === "Telo.Application") ??
41
51
  manifests.find((m) => m.kind === "Telo.Library");
52
+ const entrySchema = globalsSchema(entryDoc, buildResourcesSchema(manifests, resources));
53
+ const openResources = { type: "object", additionalProperties: true };
54
+ const byModule = new Map();
55
+ return {
56
+ forResource(m) {
57
+ const meta = m.metadata;
58
+ const stamped = meta?.moduleGlobals;
59
+ if (!stamped)
60
+ return entrySchema;
61
+ const key = meta?.module ?? "";
62
+ const cached = byModule.get(key);
63
+ if (cached)
64
+ return cached;
65
+ const schema = globalsSchema(stamped, openResources);
66
+ byModule.set(key, schema);
67
+ return schema;
68
+ },
69
+ };
70
+ }
71
+ function globalsSchema(doc, resourcesSchema) {
72
+ return {
73
+ type: "object",
74
+ properties: {
75
+ variables: buildSchemaMapSchema(doc?.variables),
76
+ secrets: buildSchemaMapSchema(doc?.secrets),
77
+ resources: resourcesSchema,
78
+ ports: buildPortsSchema(doc?.ports),
79
+ },
80
+ };
81
+ }
82
+ /** Every non-system resource name in the set, plus the scope-declared ones. */
83
+ function buildResourcesSchema(manifests, resources) {
42
84
  const resourceProps = {};
43
85
  for (const m of manifests) {
44
86
  const name = m.metadata?.name;
@@ -67,19 +109,7 @@ resources) {
67
109
  continue;
68
110
  applyObservedStateNode(resourceProps, key, entry.status);
69
111
  }
70
- return {
71
- type: "object",
72
- properties: {
73
- variables: buildSchemaMapSchema(moduleManifest?.variables),
74
- secrets: buildSchemaMapSchema(moduleManifest?.secrets),
75
- resources: {
76
- type: "object",
77
- properties: resourceProps,
78
- additionalProperties: false,
79
- },
80
- ports: buildPortsSchema(moduleManifest?.ports),
81
- },
82
- };
112
+ return { type: "object", properties: resourceProps, additionalProperties: false };
83
113
  }
84
114
  /** Build the closed `ports` chain-access schema: each declared port is an
85
115
  * integer, so `ports.<name>` resolves and `ports.typo` (or member access past
@@ -1,4 +1,5 @@
1
1
  import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
2
+ import { type CelSurface } from "@telorun/templating";
2
3
  import type { AliasResolver } from "./alias-resolver.js";
3
4
  import type { DefinitionRegistry } from "./definition-registry.js";
4
5
  import { type RefFieldEntry, type SchemaFromFieldEntry } from "./reference-field-map.js";
@@ -100,6 +101,9 @@ export interface CelSiteEvent {
100
101
  contextSchema?: Record<string, any>;
101
102
  /** Scope of the matched context (e.g. `$.routes[*].handler`), if matched. */
102
103
  matchedScope?: string;
104
+ /** Where `expr` sits in the scalar at `path`, and the delimiters to restore
105
+ * around a corrected expression. See `CelSurface`. */
106
+ surface: CelSurface;
103
107
  }
104
108
  export interface ManifestVisitor {
105
109
  onResourceEnter?(e: ResourceEnterEvent): void;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAGlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,gBAAgB,CAAC;IACzB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,gBAAgB,CAAC;IACzB,wEAAwE;IACxE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,uEAAuE;IACvE,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACpD;iFAC6E;IAC7E,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B;;2EAEuE;IACvE,eAAe,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,KAAK,EAAE,OAAO,CAAC;IACf,oEAAoE;IACpE,KAAK,EAAE,aAAa,CAAC;IACrB;iEAC6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,qBAAqB,EAAE,gBAAgB,EAAE,CAAC;IAC1C;;;;;4BAKwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,gBAAgB,CAAC;IACzB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB;;mEAE+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,CAAC,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,CAAC,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACtC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,YAAY,CAAC,CAAC,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC;;+EAE2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;wDAMoD;IACpD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAsDD,wBAAgB,aAAa,CAC3B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,YAAiB,GACzB,IAAI,CAuKN"}
1
+ {"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { isRefSentinel, isTaggedSentinel, walkCelExpressions } from "@telorun/templating";
1
+ import { isRefSentinel, isTaggedSentinel, walkCelExpressions, } from "@telorun/templating";
2
2
  import { isRefEntry, isSchemaFromEntry, isScopeEntry, resolveFieldEntries, } from "./reference-field-map.js";
3
3
  import { extractContextsFromSchema, pathMatchesScope } from "./validate-cel-context.js";
4
4
  /** Synthetic entry for a value-tree-discovered ref — these carry no declared
@@ -189,7 +189,7 @@ export function visitManifest(resources, registry, visitor, options = {}) {
189
189
  }
190
190
  if (wantsCel) {
191
191
  const contexts = definition?.schema ? extractContextsFromSchema(definition.schema) : [];
192
- walkCelExpressions(r, "", (expr, path, engineName) => {
192
+ walkCelExpressions(r, "", (expr, path, engineName, surface) => {
193
193
  let contextSchema;
194
194
  let matchedScope;
195
195
  for (const ctx of contexts) {
@@ -199,7 +199,7 @@ export function visitManifest(resources, registry, visitor, options = {}) {
199
199
  break;
200
200
  }
201
201
  }
202
- visitor.onCel({ source: r, path, expr, engineName, contextSchema, matchedScope });
202
+ visitor.onCel({ source: r, path, expr, engineName, contextSchema, matchedScope, surface });
203
203
  });
204
204
  }
205
205
  visitor.onResourceExit?.({ source: r });
@@ -0,0 +1,65 @@
1
+ import { type TemplatingEngineRegistry } from "@telorun/templating";
2
+ import { type ArtifactSelector } from "./artifact-selector.js";
3
+ /**
4
+ * One module-relative file a manifest names, and the artifact layer it belongs
5
+ * to.
6
+ *
7
+ * The single answer to "why is this file in the payload", replacing two
8
+ * derivations that happened to agree: publish used to re-parse the manifest with
9
+ * PURL knowledge hardcoded into the CLI, and any second vocabulary — a tag that
10
+ * embeds a file, say — would have had to be added there by hand. Here the
11
+ * knowledge sits with whoever owns the syntax: a controller candidate is read by
12
+ * this module, and a tagged value is read by the engine that owns its tag, via
13
+ * `TemplatingEngine.fileClaims`. Publish maps role to layer and recognises
14
+ * neither.
15
+ *
16
+ * Deliberately NOT hung off `analyze()`. That pass runs over a flattened,
17
+ * import-inclusive manifest set, so its claims would mix in imported libraries'
18
+ * files — whose paths are relative to *their* module and must never join this
19
+ * artifact — and it would make packaging, today derivable offline from manifest
20
+ * text, a product of resolving the whole import graph. This is per-module by
21
+ * construction and needs nothing but the text.
22
+ *
23
+ * Browser-safe, like the rest of the analyzer: parsing and string work only, no
24
+ * filesystem. Whether a claimed file EXISTS is a separate question, asked by the
25
+ * Node-side caller that has a directory to look in.
26
+ */
27
+ interface ClaimBase {
28
+ /** Module-root-relative POSIX path — relative to the directory holding
29
+ * `telo.yaml`, never to the file the claim was written in. Publish inlines
30
+ * every `include:` partial into the published `telo.yaml`, so a
31
+ * per-file-relative path would change meaning in the artifact. */
32
+ readonly path: string;
33
+ /** Where the claim came from, for diagnostics: the PURL, or `!<tag>` and the
34
+ * path of the value that carried it. */
35
+ readonly origin: string;
36
+ }
37
+ /**
38
+ * A **discriminated union**, not one shape with optional fields: a controller
39
+ * layer is one per selector and carries sibling patterns, and an assets layer is
40
+ * neither. Optional fields on a single shape put the consumer one `!` away from
41
+ * a crash inside `selectorKey` with no useful message, and let a producer emit a
42
+ * controller claim with no selector that nothing would reject.
43
+ */
44
+ export type ModuleFileClaim = (ClaimBase & {
45
+ readonly role: "controller";
46
+ readonly selector: ArtifactSelector;
47
+ /** Extra payload patterns that belong in the same layer as this claim —
48
+ * `.gitignore`-style globs over the selected files, matched by the
49
+ * caller, which is the side that knows what was selected. */
50
+ readonly siblings: readonly string[];
51
+ }) | (ClaimBase & {
52
+ readonly role: "assets";
53
+ });
54
+ /**
55
+ * Every module-relative file the manifest names, from every syntax that can name
56
+ * one.
57
+ *
58
+ * `manifestText` is one module's `telo.yaml`. Publish passes the text it is
59
+ * about to ship — i.e. after `include:` partials have been inlined — but the
60
+ * answer does not depend on that: claims are root-relative, so collecting them
61
+ * before or after inlining gives the same set.
62
+ */
63
+ export declare function collectModuleFileClaims(manifestText: string, registry?: TemplatingEngineRegistry): ModuleFileClaim[];
64
+ export {};
65
+ //# sourceMappingURL=module-file-claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-file-claims.d.ts","sourceRoot":"","sources":["../src/module-file-claims.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEpG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,UAAU,SAAS;IACjB;;;uEAGmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;6CACyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACvB,CAAC,SAAS,GAAG;IACX,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;kEAE8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,GACF,CAAC,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAiF9C;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,QAAQ,GAAE,wBAA4C,GACrD,eAAe,EAAE,CAanB"}
@@ -0,0 +1,106 @@
1
+ import { defaultCustomTags, defaultRegistry, walkCelExpressions, } from "@telorun/templating";
2
+ import { PackageURL } from "packageurl-js";
3
+ import { parseAllDocuments } from "yaml";
4
+ import { selectorFromQualifiers, selectorKey } from "./artifact-selector.js";
5
+ /** `pkg:telo/local/<format>?path=…` — the bundled-controller delivery mode.
6
+ * Anything else (`pkg:npm`, `pkg:cargo`) fetches from its own ecosystem and
7
+ * contributes no layer. */
8
+ const BUNDLED_TYPE = "telo";
9
+ const BUNDLED_NAMESPACE = "local";
10
+ /** Qualifier naming extra files that belong in a controller's layer — what an
11
+ * entry point loads but the manifest cannot otherwise see (a `.wasm` beside its
12
+ * glue, a native library opened at runtime). */
13
+ const SIBLINGS_QUALIFIER = "siblings";
14
+ /** Normalize a `path=` / sibling value to the manifest-relative POSIX form the
15
+ * file selector returns, so membership is a string comparison. */
16
+ function normalizeRelative(value) {
17
+ return value.replace(/^\.\//, "").replace(/\\/g, "/");
18
+ }
19
+ /** Bundled-controller claims from one document's `controllers:` list. */
20
+ function controllerClaims(json) {
21
+ const candidates = json?.controllers;
22
+ if (!Array.isArray(candidates))
23
+ return [];
24
+ const claims = [];
25
+ for (const candidate of candidates) {
26
+ if (typeof candidate !== "string")
27
+ continue;
28
+ let parsed;
29
+ try {
30
+ parsed = PackageURL.fromString(candidate);
31
+ }
32
+ catch {
33
+ // Not a parseable PURL — claim collection is not the place to reject it;
34
+ // the analyzer's own validation and the controller loader both report it
35
+ // with better context.
36
+ continue;
37
+ }
38
+ if (parsed.type !== BUNDLED_TYPE || parsed.namespace !== BUNDLED_NAMESPACE)
39
+ continue;
40
+ const entry = parsed.qualifiers?.path;
41
+ if (typeof entry !== "string" || entry === "")
42
+ continue;
43
+ claims.push({
44
+ role: "controller",
45
+ path: normalizeRelative(entry),
46
+ selector: selectorFromQualifiers(parsed.name, parsed.qualifiers, `controller "${candidate}"`),
47
+ siblings: String(parsed.qualifiers?.[SIBLINGS_QUALIFIER] ?? "")
48
+ .split(",")
49
+ .map((p) => p.trim())
50
+ .filter((p) => p !== ""),
51
+ origin: candidate,
52
+ });
53
+ }
54
+ return claims;
55
+ }
56
+ /** Claims contributed by tagged values, asked of the engine that owns each tag.
57
+ * The walk reaches every tagged scalar in the document, so an engine that
58
+ * embeds files is discovered wherever its tag was written.
59
+ *
60
+ * The layer role is assigned HERE, not by the engine: an engine reports what it
61
+ * embeds, and which layer that belongs in is this module's vocabulary. A file a
62
+ * tag embeds is read only when the resource holding it is created, so `assets`
63
+ * — the lazily-fetched layer — is what it is. */
64
+ function taggedClaims(json, registry) {
65
+ const claims = [];
66
+ walkCelExpressions(json, "", (source, path, engineName) => {
67
+ const engine = registry.get(engineName);
68
+ for (const claim of engine?.fileClaims?.(source) ?? []) {
69
+ claims.push({ role: "assets", path: claim.path, origin: `!${engineName} at '${path}'` });
70
+ }
71
+ });
72
+ return claims;
73
+ }
74
+ /** Identity of a claim for de-duplication: the same file claimed twice by two
75
+ * resources is one file in one layer. Role and selector are part of it because
76
+ * a file two controller candidates both claim is genuinely copied into each of
77
+ * their layers — dropping one would leave a platform's layer short a file it
78
+ * declared it needs. */
79
+ function claimKey(claim) {
80
+ const selector = claim.role === "controller" ? selectorKey(claim.selector) : "";
81
+ return `${claim.role}\0${selector}\0${claim.path}`;
82
+ }
83
+ /**
84
+ * Every module-relative file the manifest names, from every syntax that can name
85
+ * one.
86
+ *
87
+ * `manifestText` is one module's `telo.yaml`. Publish passes the text it is
88
+ * about to ship — i.e. after `include:` partials have been inlined — but the
89
+ * answer does not depend on that: claims are root-relative, so collecting them
90
+ * before or after inlining gives the same set.
91
+ */
92
+ export function collectModuleFileClaims(manifestText, registry = defaultRegistry()) {
93
+ const seen = new Set();
94
+ const claims = [];
95
+ for (const doc of parseAllDocuments(manifestText, { customTags: defaultCustomTags() })) {
96
+ const json = doc.toJSON();
97
+ for (const claim of [...controllerClaims(json), ...taggedClaims(json, registry)]) {
98
+ const key = claimKey(claim);
99
+ if (seen.has(key))
100
+ continue;
101
+ seen.add(key);
102
+ claims.push(claim);
103
+ }
104
+ }
105
+ return claims;
106
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAWpD;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;AAkED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CA0C/E;AAqBD,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;AAyDD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAwBlF;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;AAChC;;;;;;;;6DAQ6D;AAC7D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACrC,IAAI,SAAK,GACR,OAAO,CAgDT"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAYA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAWpD;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;AAkED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CA0C/E;AAqBD,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;AAyDD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAwBlF;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;AAChC;;;;;;;;6DAQ6D;AAC7D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACrC,IAAI,SAAK,GACR,OAAO,CA2DT"}
@@ -1,6 +1,6 @@
1
1
  import AjvModule from "ajv";
2
2
  import addFormats from "ajv-formats";
3
- import { isRefSentinel, isTaggedSentinel, ManifestRootSchema, normalizeRefSlots } from "@telorun/templating";
3
+ import { INCLUDE_BYTES_ENGINE, INCLUDE_ENGINE_NAMES, isRefSentinel, isTaggedSentinel, ManifestRootSchema, normalizeRefSlots, } from "@telorun/templating";
4
4
  import { binaryKeyword, isBinarySlot } from "./binary-slot.js";
5
5
  const Ajv = AjvModule.default ?? AjvModule;
6
6
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
@@ -537,6 +537,17 @@ onSubstitute, path = "") {
537
537
  if (isRefSentinel(data)) {
538
538
  return data;
539
539
  }
540
+ // A file embed's type is a CONSTANT of the tag, not a function of the slot:
541
+ // `!include-text` always produces a string and `!include-bytes` always
542
+ // produces bytes. Collapsing them to a slot-shaped placeholder like a CEL
543
+ // expression would make every slot accept both, so a byte embed at a
544
+ // `type: string` field passed `telo check` and failed at resource creation —
545
+ // and the reverse (text at an `x-telo-binary` slot) did too. Substituting the
546
+ // real type lets AJV and the `x-telo-binary` keyword reject both directions
547
+ // statically, with no new diagnostic code.
548
+ if (isTaggedSentinel(data) && INCLUDE_ENGINE_NAMES.has(data.engine)) {
549
+ return data.engine === INCLUDE_BYTES_ENGINE ? new Uint8Array() : "";
550
+ }
540
551
  if (isTaggedSentinel(data)) {
541
552
  mark();
542
553
  return celPlaceholderForSchema(resolved);
package/dist/types.d.ts CHANGED
@@ -26,6 +26,37 @@ export interface Range {
26
26
  export type PositionIndex = Map<string, Range>;
27
27
  /** LSP-compatible Diagnostic shape. range is optional because parsed YAML may not carry
28
28
  * position info when only the parsed object (not raw text) is available. */
29
+ /** A mechanically applicable repair, carried unchanged from whatever produced
30
+ * it (a templating engine, a kind-name suggestion) to every consumer: CLI
31
+ * JSON, IDE CodeActions, an agent applying it without re-deriving it from
32
+ * prose.
33
+ *
34
+ * `replacement` is the **whole** value at the diagnostic's `path`, corrected —
35
+ * never a fragment — so applying it needs no knowledge of the language inside.
36
+ * There is deliberately no sub-range: carrying one beside a whole-value
37
+ * replacement gives the field two readings, and the minimal-edit reading
38
+ * (splice `replacement` at `range`) produces garbage because the two measure
39
+ * different strings.
40
+ *
41
+ * One shape rather than one per producer: a `fix` field beside a
42
+ * `suggestedKind` field beside a CEL-specific one would leave every host
43
+ * wiring a separate action path for what is the same gesture. */
44
+ export interface DiagnosticFix {
45
+ readonly replacement: string;
46
+ }
47
+ /** The `data` stamp diagnostics carry. Loose by design — passes bolt their own
48
+ * keys on — but the fields every consumer reads are declared. */
49
+ export interface DiagnosticData {
50
+ resource?: {
51
+ kind: string;
52
+ name: string;
53
+ };
54
+ filePath?: string;
55
+ /** Dotted path of the offending value within its resource. */
56
+ path?: string;
57
+ fix?: DiagnosticFix;
58
+ [key: string]: unknown;
59
+ }
29
60
  export interface AnalysisDiagnostic {
30
61
  range?: Range;
31
62
  severity?: DiagnosticSeverity;
@@ -36,6 +67,9 @@ export interface AnalysisDiagnostic {
36
67
  /** Telo-specific extras such as { resource: { kind, name }, path } */
37
68
  data?: unknown;
38
69
  }
70
+ /** Single reader for a diagnostic's fix, so no consumer re-derives the shape
71
+ * by hand-casting `data`. */
72
+ export declare function diagnosticFix(d: AnalysisDiagnostic): DiagnosticFix | undefined;
39
73
  export interface ManifestSource {
40
74
  supports(url: string): boolean;
41
75
  read(url: string): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;mEAO+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC;6FACyF;IACzF,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;oCAKgC;IAChC,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACxC;;;;;;;;;;sDAUkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;gEAKgE;AAChE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IACtD,WAAW,CAAC,EAAE,OAAO,0BAA0B,EAAE,kBAAkB,CAAC;IACpE;;;;+EAI2E;IAC3E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC,CAAC;CAC5E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -8,3 +8,9 @@ export const DiagnosticSeverity = {
8
8
  };
9
9
  /** Default entry-point filename when a directory is given instead of a file. */
10
10
  export const DEFAULT_MANIFEST_FILENAME = "telo.yaml";
11
+ /** Single reader for a diagnostic's fix, so no consumer re-derives the shape
12
+ * by hand-casting `data`. */
13
+ export function diagnosticFix(d) {
14
+ const fix = d.data?.fix;
15
+ return fix && typeof fix.replacement === "string" ? fix : undefined;
16
+ }
@@ -0,0 +1,26 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { type AnalysisDiagnostic } from "./types.js";
3
+ /**
4
+ * An `!include-text` / `!include-bytes` in a doc that is never instantiated is
5
+ * never read.
6
+ *
7
+ * The two tags resolve when the resource owning them is created — deferred so
8
+ * that loading a manifest does not pull payload layers, which is the property
9
+ * the artifact spec protects by giving `telo.yaml` a layer of its own. The cost
10
+ * of that choice is this dead spot: a doc with no `create()` has no moment at
11
+ * which the file would be read, so the value stays an unresolved marker and
12
+ * whatever reads it sees a sentinel object instead of the file's contents.
13
+ *
14
+ * Nothing else would report it. There is no controller to validate against a
15
+ * schema and no runtime consumer to fail, so the manifest ships looking correct
16
+ * — exactly the silent-no-op failure mode that makes a descriptive field worth
17
+ * checking. Hence an error at the one place that can see it.
18
+ *
19
+ * Deliberately narrow: it reports only where the argument is complete ("this
20
+ * doc is never instantiated"). A JSON-Schema region *inside* a definition —
21
+ * `schema:`, `inputType:` — is equally unreadable, but that is a general
22
+ * question about tags in schema metadata rather than one about these two, and
23
+ * no pass answers it for any tag today.
24
+ */
25
+ export declare function validateIncludePlacement(manifests: ResourceManifest[]): AnalysisDiagnostic[];
26
+ //# sourceMappingURL=validate-include-placement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-include-placement.d.ts","sourceRoot":"","sources":["../src/validate-include-placement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAmBzE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,kBAAkB,EAAE,CA0B5F"}
@@ -0,0 +1,67 @@
1
+ import { INCLUDE_ENGINE_NAMES, walkCelExpressions } from "@telorun/templating";
2
+ import { DiagnosticSeverity } from "./types.js";
3
+ const SOURCE = "telo-analyzer";
4
+ /**
5
+ * Docs that are never instantiated as resources.
6
+ *
7
+ * `Telo.Application` / `Telo.Library` are module declarations, and `Telo.Import`
8
+ * is a dependency edge — none of the three reaches a controller's `create()`.
9
+ * `Telo.Definition` and `Telo.Abstract` are deliberately absent: a definition's
10
+ * template body (`resources:` / `invoke:` / `run:` / `provide:`) DOES become
11
+ * resources, so an embed there resolves normally.
12
+ */
13
+ const NEVER_INSTANTIATED = new Set([
14
+ "Telo.Application",
15
+ "Telo.Library",
16
+ "Telo.Import",
17
+ ]);
18
+ /**
19
+ * An `!include-text` / `!include-bytes` in a doc that is never instantiated is
20
+ * never read.
21
+ *
22
+ * The two tags resolve when the resource owning them is created — deferred so
23
+ * that loading a manifest does not pull payload layers, which is the property
24
+ * the artifact spec protects by giving `telo.yaml` a layer of its own. The cost
25
+ * of that choice is this dead spot: a doc with no `create()` has no moment at
26
+ * which the file would be read, so the value stays an unresolved marker and
27
+ * whatever reads it sees a sentinel object instead of the file's contents.
28
+ *
29
+ * Nothing else would report it. There is no controller to validate against a
30
+ * schema and no runtime consumer to fail, so the manifest ships looking correct
31
+ * — exactly the silent-no-op failure mode that makes a descriptive field worth
32
+ * checking. Hence an error at the one place that can see it.
33
+ *
34
+ * Deliberately narrow: it reports only where the argument is complete ("this
35
+ * doc is never instantiated"). A JSON-Schema region *inside* a definition —
36
+ * `schema:`, `inputType:` — is equally unreadable, but that is a general
37
+ * question about tags in schema metadata rather than one about these two, and
38
+ * no pass answers it for any tag today.
39
+ */
40
+ export function validateIncludePlacement(manifests) {
41
+ const out = [];
42
+ for (const manifest of manifests) {
43
+ if (!NEVER_INSTANTIATED.has(manifest.kind))
44
+ continue;
45
+ const name = manifest.metadata?.name;
46
+ const filePath = manifest.metadata?.source;
47
+ walkCelExpressions(manifest, "", (source, path, engineName) => {
48
+ if (!INCLUDE_ENGINE_NAMES.has(engineName))
49
+ return;
50
+ out.push({
51
+ severity: DiagnosticSeverity.Error,
52
+ code: "INCLUDE_OUTSIDE_RESOURCE",
53
+ source: SOURCE,
54
+ message: `${manifest.kind}${name ? `/${name}` : ""}: \`!${engineName} ${source}\` at '${path}' is ` +
55
+ `never read — a ${manifest.kind} doc is not instantiated, and a file embed is resolved ` +
56
+ `when the resource holding it is created. Move it onto the resource that needs the ` +
57
+ `file, or read the file at runtime with Fs.File.`,
58
+ data: {
59
+ resource: { kind: manifest.kind, name: name ?? "" },
60
+ filePath,
61
+ path,
62
+ },
63
+ });
64
+ });
65
+ }
66
+ return out;
67
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA0ezE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}
1
+ {"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA+ezE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}