@telorun/analyzer 0.25.0 → 0.27.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/README.md CHANGED
@@ -23,7 +23,7 @@ Built to be language-agnostic and infinitely extensible.
23
23
 
24
24
  ```bash
25
25
  # Reconcile your manifest into a running backend
26
- $ telo ./examples/hello-api.yaml
26
+ $ telo ./examples/hello-api
27
27
 
28
28
  {"level":30,"time":1771610393008,"pid":1310178,"hostname":"dev","msg":"Server listening at http://127.0.0.1:8844"}
29
29
  ```
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAud/C,CAAC"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA2hB/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -59,12 +59,28 @@ export const KERNEL_BUILTINS = [
59
59
  items: {
60
60
  type: "object",
61
61
  additionalProperties: true,
62
+ // Resource bodies are `self`-only for config: per-call `inputs` is
63
+ // NOT in scope here. Each entry is a persistent child created once at
64
+ // init() and reused, so its config cannot depend on call-time data —
65
+ // that flows through the top-level `inputs:` sibling into the dispatch
66
+ // target's invoke().
67
+ //
68
+ // The exception is CEL the child's OWN controller evaluates later
69
+ // against a runtime context it owns (e.g. an Http.Api evaluating route
70
+ // CEL per request). Those `request` / `result` / `steps` / `error`
71
+ // variables are deferred — the template controller preserves them
72
+ // untouched (see resource-template-controller.ts) — so they are
73
+ // exposed here permissively. Their deep shape is the child kind's
74
+ // concern, not the template's, so they type as open values.
62
75
  "x-telo-context": {
63
76
  type: "object",
64
77
  additionalProperties: false,
65
78
  properties: {
66
79
  self: { "x-telo-context-from-root": "schema" },
67
- inputs: { "x-telo-context-from-root": "inputType" },
80
+ request: {},
81
+ result: {},
82
+ steps: {},
83
+ error: {},
68
84
  },
69
85
  },
70
86
  },
@@ -127,6 +143,41 @@ export const KERNEL_BUILTINS = [
127
143
  },
128
144
  },
129
145
  },
146
+ // Mount dispatch: names the `resources:` entry (a Telo.Mount, e.g. an
147
+ // Http.Api) whose `register()` this definition delegates to. Same
148
+ // string / { kind, name } grammar as `invoke:`. The named child stays
149
+ // persistent so the produced mount's routes can `!ref` its siblings.
150
+ mount: {
151
+ oneOf: [
152
+ {
153
+ type: "string",
154
+ "x-telo-context": {
155
+ type: "object",
156
+ additionalProperties: false,
157
+ properties: {
158
+ self: { "x-telo-context-from-root": "schema" },
159
+ },
160
+ },
161
+ },
162
+ {
163
+ type: "object",
164
+ additionalProperties: true,
165
+ properties: {
166
+ kind: { type: "string" },
167
+ name: {
168
+ type: "string",
169
+ "x-telo-context": {
170
+ type: "object",
171
+ additionalProperties: false,
172
+ properties: {
173
+ self: { "x-telo-context-from-root": "schema" },
174
+ },
175
+ },
176
+ },
177
+ },
178
+ },
179
+ ],
180
+ },
130
181
  inputs: {
131
182
  type: "object",
132
183
  additionalProperties: true,
@@ -298,6 +349,16 @@ export const KERNEL_BUILTINS = [
298
349
  type: "array",
299
350
  items: { type: "string" },
300
351
  },
352
+ // Files bundled alongside `telo.yaml` into the module's registry
353
+ // artifact (`module.tar.gz`) — static assets served by Http.Static,
354
+ // templates, etc. Ordered `.gitignore`-style patterns resolved against
355
+ // the manifest dir at publish time. Analyzer-only role: accept the
356
+ // field (the schema is additionalProperties:false); the analyzer never
357
+ // reads the assets. See kernel/nodejs/plans/bundle-controllers.md.
358
+ files: {
359
+ type: "array",
360
+ items: { type: "string" },
361
+ },
301
362
  // Inline imports — name-keyed map sugar for separate `Telo.Import`
302
363
  // documents. The key is the PascalCase alias (the import's
303
364
  // `metadata.name`). Each value is either a bare source string
@@ -422,6 +483,13 @@ export const KERNEL_BUILTINS = [
422
483
  type: "array",
423
484
  items: { type: "string" },
424
485
  },
486
+ // Files bundled into the module's registry artifact — same semantics as
487
+ // the Telo.Application `files` field above (a library may ship bundled
488
+ // templates, migrations, seed data).
489
+ files: {
490
+ type: "array",
491
+ items: { type: "string" },
492
+ },
425
493
  // Inline imports — same name-keyed map sugar as Telo.Application; the
426
494
  // loader desugars each entry into a synthetic Telo.Import. See the
427
495
  // Application schema above and analyzer/nodejs/src/inline-imports.ts.
@@ -18,6 +18,14 @@ import { type AnalysisDiagnostic } from "./types.js";
18
18
  * kind is registered but not a `Telo.Invocable`.
19
19
  * - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
20
20
  * declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
21
+ * - MOUNT_ON_NON_MOUNT: `mount:` declared on a definition whose `capability` is
22
+ * not `Telo.Mount`.
23
+ * - MOUNT_DISPATCHER_CONFLICT: `mount:` co-exists with another dispatch
24
+ * entry-point (`invoke:` / `run:` / `provide:`).
25
+ * - MOUNT_TARGET_UNKNOWN: `mount.name` does not resolve to an entry in
26
+ * `resources:`.
27
+ * - MOUNT_TARGET_NOT_MOUNTABLE: `mount.name` resolves to a resource whose kind
28
+ * is registered but not a `Telo.Mount`.
21
29
  */
22
30
  export declare function validateProviderCoherence(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
23
31
  //# sourceMappingURL=validate-provider-coherence.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.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;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAyItB"}
1
+ {"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.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;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAsNtB"}
@@ -16,6 +16,14 @@ const SOURCE = "telo-analyzer";
16
16
  * kind is registered but not a `Telo.Invocable`.
17
17
  * - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
18
18
  * declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
19
+ * - MOUNT_ON_NON_MOUNT: `mount:` declared on a definition whose `capability` is
20
+ * not `Telo.Mount`.
21
+ * - MOUNT_DISPATCHER_CONFLICT: `mount:` co-exists with another dispatch
22
+ * entry-point (`invoke:` / `run:` / `provide:`).
23
+ * - MOUNT_TARGET_UNKNOWN: `mount.name` does not resolve to an entry in
24
+ * `resources:`.
25
+ * - MOUNT_TARGET_NOT_MOUNTABLE: `mount.name` resolves to a resource whose kind
26
+ * is registered but not a `Telo.Mount`.
19
27
  */
20
28
  export function validateProviderCoherence(manifests, registry, aliases) {
21
29
  const diagnostics = [];
@@ -45,11 +53,13 @@ export function validateProviderCoherence(manifests, registry, aliases) {
45
53
  const provide = md.provide;
46
54
  const invoke = md.invoke;
47
55
  const run = md.run;
56
+ const mount = md.mount;
48
57
  const controllers = md.controllers;
49
58
  const resources = md.resources;
50
59
  const hasProvide = provide !== undefined && provide !== null;
51
60
  const hasInvoke = invoke !== undefined && invoke !== null;
52
61
  const hasRun = run !== undefined && run !== null;
62
+ const hasMount = mount !== undefined && mount !== null;
53
63
  const hasControllers = Array.isArray(controllers) && controllers.length > 0;
54
64
  if (hasProvide && capability !== "Telo.Provider") {
55
65
  diagnostics.push({
@@ -133,6 +143,77 @@ export function validateProviderCoherence(manifests, registry, aliases) {
133
143
  }
134
144
  }
135
145
  }
146
+ if (hasMount && capability !== "Telo.Mount") {
147
+ diagnostics.push({
148
+ severity: DiagnosticSeverity.Error,
149
+ code: "MOUNT_ON_NON_MOUNT",
150
+ source: SOURCE,
151
+ message: `${label}: 'mount:' is only valid on definitions with 'capability: Telo.Mount' ` +
152
+ `(found '${capability ?? "<unset>"}'). Use 'invoke:' / 'run:' / 'provide:' for other capabilities.`,
153
+ data: { resource, filePath, path: "mount" },
154
+ });
155
+ }
156
+ if (hasMount && (hasInvoke || hasRun || hasProvide)) {
157
+ const conflict = hasInvoke ? "invoke" : hasRun ? "run" : "provide";
158
+ diagnostics.push({
159
+ severity: DiagnosticSeverity.Error,
160
+ code: "MOUNT_DISPATCHER_CONFLICT",
161
+ source: SOURCE,
162
+ message: `${label}: 'mount:' cannot co-exist with '${conflict}:'. ` +
163
+ `A definition declares exactly one dispatch entry-point.`,
164
+ data: { resource, filePath, path: "mount" },
165
+ });
166
+ }
167
+ if (hasMount) {
168
+ // Resolve the target's name from either form: the bare string (the
169
+ // primary, documented form — `mount: api`) or the object's `name`. A CEL
170
+ // target (`${{ … }}`) can only be checked at runtime, so skip those.
171
+ let mountedName;
172
+ if (typeof mount === "string") {
173
+ if (!mount.includes("${{"))
174
+ mountedName = mount;
175
+ }
176
+ else if (typeof mount === "object" && !Array.isArray(mount)) {
177
+ const mountObj = mount;
178
+ if (typeof mountObj.name === "string" && !mountObj.name.includes("${{")) {
179
+ mountedName = mountObj.name;
180
+ }
181
+ }
182
+ const mountPath = typeof mount === "string" ? "mount" : "mount.name";
183
+ if (mountedName && Array.isArray(resources)) {
184
+ const match = resources.find((r) => {
185
+ const meta = r?.metadata;
186
+ return typeof meta?.name === "string" && meta.name === mountedName;
187
+ });
188
+ if (!match) {
189
+ diagnostics.push({
190
+ severity: DiagnosticSeverity.Error,
191
+ code: "MOUNT_TARGET_UNKNOWN",
192
+ source: SOURCE,
193
+ message: `${label}: '${mountPath}: ${mountedName}' does not match any entry's ` +
194
+ `metadata.name in 'resources:'.`,
195
+ data: { resource, filePath, path: mountPath },
196
+ });
197
+ }
198
+ else if (typeof match.kind === "string") {
199
+ const resolvedKind = aliases.resolveKind(match.kind) ?? match.kind;
200
+ const targetDef = registry.resolve(resolvedKind) ?? registry.resolve(match.kind);
201
+ if (targetDef && targetDef.kind === "Telo.Definition") {
202
+ const targetCap = targetDef.capability;
203
+ if (typeof targetCap === "string" && targetCap !== "Telo.Mount") {
204
+ diagnostics.push({
205
+ severity: DiagnosticSeverity.Error,
206
+ code: "MOUNT_TARGET_NOT_MOUNTABLE",
207
+ source: SOURCE,
208
+ message: `${label}: '${mountPath}: ${mountedName}' resolves to a ${match.kind} ` +
209
+ `(capability '${targetCap}'); 'mount:' requires a Telo.Mount target.`,
210
+ data: { resource, filePath, path: mountPath },
211
+ });
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
136
217
  if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
137
218
  diagnostics.push({
138
219
  severity: DiagnosticSeverity.Error,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.25.0",
3
+ "version": "0.27.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.34.0"
51
+ "@telorun/sdk": "0.36.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
package/src/builtins.ts CHANGED
@@ -61,12 +61,28 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
61
61
  items: {
62
62
  type: "object",
63
63
  additionalProperties: true,
64
+ // Resource bodies are `self`-only for config: per-call `inputs` is
65
+ // NOT in scope here. Each entry is a persistent child created once at
66
+ // init() and reused, so its config cannot depend on call-time data —
67
+ // that flows through the top-level `inputs:` sibling into the dispatch
68
+ // target's invoke().
69
+ //
70
+ // The exception is CEL the child's OWN controller evaluates later
71
+ // against a runtime context it owns (e.g. an Http.Api evaluating route
72
+ // CEL per request). Those `request` / `result` / `steps` / `error`
73
+ // variables are deferred — the template controller preserves them
74
+ // untouched (see resource-template-controller.ts) — so they are
75
+ // exposed here permissively. Their deep shape is the child kind's
76
+ // concern, not the template's, so they type as open values.
64
77
  "x-telo-context": {
65
78
  type: "object",
66
79
  additionalProperties: false,
67
80
  properties: {
68
81
  self: { "x-telo-context-from-root": "schema" },
69
- inputs: { "x-telo-context-from-root": "inputType" },
82
+ request: {},
83
+ result: {},
84
+ steps: {},
85
+ error: {},
70
86
  },
71
87
  },
72
88
  },
@@ -129,6 +145,41 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
129
145
  },
130
146
  },
131
147
  },
148
+ // Mount dispatch: names the `resources:` entry (a Telo.Mount, e.g. an
149
+ // Http.Api) whose `register()` this definition delegates to. Same
150
+ // string / { kind, name } grammar as `invoke:`. The named child stays
151
+ // persistent so the produced mount's routes can `!ref` its siblings.
152
+ mount: {
153
+ oneOf: [
154
+ {
155
+ type: "string",
156
+ "x-telo-context": {
157
+ type: "object",
158
+ additionalProperties: false,
159
+ properties: {
160
+ self: { "x-telo-context-from-root": "schema" },
161
+ },
162
+ },
163
+ },
164
+ {
165
+ type: "object",
166
+ additionalProperties: true,
167
+ properties: {
168
+ kind: { type: "string" },
169
+ name: {
170
+ type: "string",
171
+ "x-telo-context": {
172
+ type: "object",
173
+ additionalProperties: false,
174
+ properties: {
175
+ self: { "x-telo-context-from-root": "schema" },
176
+ },
177
+ },
178
+ },
179
+ },
180
+ },
181
+ ],
182
+ },
132
183
  inputs: {
133
184
  type: "object",
134
185
  additionalProperties: true,
@@ -300,6 +351,16 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
300
351
  type: "array",
301
352
  items: { type: "string" },
302
353
  },
354
+ // Files bundled alongside `telo.yaml` into the module's registry
355
+ // artifact (`module.tar.gz`) — static assets served by Http.Static,
356
+ // templates, etc. Ordered `.gitignore`-style patterns resolved against
357
+ // the manifest dir at publish time. Analyzer-only role: accept the
358
+ // field (the schema is additionalProperties:false); the analyzer never
359
+ // reads the assets. See kernel/nodejs/plans/bundle-controllers.md.
360
+ files: {
361
+ type: "array",
362
+ items: { type: "string" },
363
+ },
303
364
  // Inline imports — name-keyed map sugar for separate `Telo.Import`
304
365
  // documents. The key is the PascalCase alias (the import's
305
366
  // `metadata.name`). Each value is either a bare source string
@@ -424,6 +485,13 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
424
485
  type: "array",
425
486
  items: { type: "string" },
426
487
  },
488
+ // Files bundled into the module's registry artifact — same semantics as
489
+ // the Telo.Application `files` field above (a library may ship bundled
490
+ // templates, migrations, seed data).
491
+ files: {
492
+ type: "array",
493
+ items: { type: "string" },
494
+ },
427
495
  // Inline imports — same name-keyed map sugar as Telo.Application; the
428
496
  // loader desugars each entry into a synthetic Telo.Import. See the
429
497
  // Application schema above and analyzer/nodejs/src/inline-imports.ts.
@@ -21,6 +21,14 @@ const SOURCE = "telo-analyzer";
21
21
  * kind is registered but not a `Telo.Invocable`.
22
22
  * - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
23
23
  * declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
24
+ * - MOUNT_ON_NON_MOUNT: `mount:` declared on a definition whose `capability` is
25
+ * not `Telo.Mount`.
26
+ * - MOUNT_DISPATCHER_CONFLICT: `mount:` co-exists with another dispatch
27
+ * entry-point (`invoke:` / `run:` / `provide:`).
28
+ * - MOUNT_TARGET_UNKNOWN: `mount.name` does not resolve to an entry in
29
+ * `resources:`.
30
+ * - MOUNT_TARGET_NOT_MOUNTABLE: `mount.name` resolves to a resource whose kind
31
+ * is registered but not a `Telo.Mount`.
24
32
  */
25
33
  export function validateProviderCoherence(
26
34
  manifests: ResourceManifest[],
@@ -52,12 +60,14 @@ export function validateProviderCoherence(
52
60
  const provide = md.provide;
53
61
  const invoke = md.invoke;
54
62
  const run = md.run;
63
+ const mount = md.mount;
55
64
  const controllers = md.controllers;
56
65
  const resources = md.resources;
57
66
 
58
67
  const hasProvide = provide !== undefined && provide !== null;
59
68
  const hasInvoke = invoke !== undefined && invoke !== null;
60
69
  const hasRun = run !== undefined && run !== null;
70
+ const hasMount = mount !== undefined && mount !== null;
61
71
  const hasControllers = Array.isArray(controllers) && controllers.length > 0;
62
72
 
63
73
  if (hasProvide && capability !== "Telo.Provider") {
@@ -149,6 +159,81 @@ export function validateProviderCoherence(
149
159
  }
150
160
  }
151
161
 
162
+ if (hasMount && capability !== "Telo.Mount") {
163
+ diagnostics.push({
164
+ severity: DiagnosticSeverity.Error,
165
+ code: "MOUNT_ON_NON_MOUNT",
166
+ source: SOURCE,
167
+ message:
168
+ `${label}: 'mount:' is only valid on definitions with 'capability: Telo.Mount' ` +
169
+ `(found '${capability ?? "<unset>"}'). Use 'invoke:' / 'run:' / 'provide:' for other capabilities.`,
170
+ data: { resource, filePath, path: "mount" },
171
+ });
172
+ }
173
+
174
+ if (hasMount && (hasInvoke || hasRun || hasProvide)) {
175
+ const conflict = hasInvoke ? "invoke" : hasRun ? "run" : "provide";
176
+ diagnostics.push({
177
+ severity: DiagnosticSeverity.Error,
178
+ code: "MOUNT_DISPATCHER_CONFLICT",
179
+ source: SOURCE,
180
+ message:
181
+ `${label}: 'mount:' cannot co-exist with '${conflict}:'. ` +
182
+ `A definition declares exactly one dispatch entry-point.`,
183
+ data: { resource, filePath, path: "mount" },
184
+ });
185
+ }
186
+
187
+ if (hasMount) {
188
+ // Resolve the target's name from either form: the bare string (the
189
+ // primary, documented form — `mount: api`) or the object's `name`. A CEL
190
+ // target (`${{ … }}`) can only be checked at runtime, so skip those.
191
+ let mountedName: string | undefined;
192
+ if (typeof mount === "string") {
193
+ if (!mount.includes("${{")) mountedName = mount;
194
+ } else if (typeof mount === "object" && !Array.isArray(mount)) {
195
+ const mountObj = mount as { name?: unknown };
196
+ if (typeof mountObj.name === "string" && !mountObj.name.includes("${{")) {
197
+ mountedName = mountObj.name;
198
+ }
199
+ }
200
+ const mountPath = typeof mount === "string" ? "mount" : "mount.name";
201
+ if (mountedName && Array.isArray(resources)) {
202
+ const match = resources.find((r) => {
203
+ const meta = (r as { metadata?: { name?: unknown } })?.metadata;
204
+ return typeof meta?.name === "string" && meta.name === mountedName;
205
+ }) as { kind?: unknown } | undefined;
206
+ if (!match) {
207
+ diagnostics.push({
208
+ severity: DiagnosticSeverity.Error,
209
+ code: "MOUNT_TARGET_UNKNOWN",
210
+ source: SOURCE,
211
+ message:
212
+ `${label}: '${mountPath}: ${mountedName}' does not match any entry's ` +
213
+ `metadata.name in 'resources:'.`,
214
+ data: { resource, filePath, path: mountPath },
215
+ });
216
+ } else if (typeof match.kind === "string") {
217
+ const resolvedKind = aliases.resolveKind(match.kind) ?? match.kind;
218
+ const targetDef = registry.resolve(resolvedKind) ?? registry.resolve(match.kind);
219
+ if (targetDef && targetDef.kind === "Telo.Definition") {
220
+ const targetCap = (targetDef as { capability?: unknown }).capability;
221
+ if (typeof targetCap === "string" && targetCap !== "Telo.Mount") {
222
+ diagnostics.push({
223
+ severity: DiagnosticSeverity.Error,
224
+ code: "MOUNT_TARGET_NOT_MOUNTABLE",
225
+ source: SOURCE,
226
+ message:
227
+ `${label}: '${mountPath}: ${mountedName}' resolves to a ${match.kind} ` +
228
+ `(capability '${targetCap}'); 'mount:' requires a Telo.Mount target.`,
229
+ data: { resource, filePath, path: mountPath },
230
+ });
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+
152
237
  if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
153
238
  diagnostics.push({
154
239
  severity: DiagnosticSeverity.Error,