@telorun/analyzer 0.18.0 → 0.19.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
@@ -55,7 +55,7 @@ metadata:
55
55
  A complete feedback collection REST API — no code, pure YAML.
56
56
  Persists entries to SQLite and serves them over HTTP.
57
57
  imports:
58
- Http: std/http-server@0.6.1
58
+ Http: std/http-server@0.8.0
59
59
  Sql: std/sql@0.5.1
60
60
  targets:
61
61
  - Migrations
@@ -64,6 +64,13 @@ export declare class AnalysisRegistry {
64
64
  * type reference is left unresolved (returns undefined) so the caller can fall
65
65
  * back to a freeform map. Undefined when the kind declares no input contract. */
66
66
  inputTypeForKind(kind: string): Record<string, unknown> | undefined;
67
+ /** Resolves the JSON Schema for a kind's `invoke()` / `run()` output, for
68
+ * editor hosts that render a typed output signature. Mirrors
69
+ * {@link inputTypeForKind}: the definition's own `outputType`, then the
70
+ * `extends`-declared abstract's `outputType`. Resolves the inline and
71
+ * raw-schema forms; a bare named type reference is left unresolved. Undefined
72
+ * when the kind declares no output contract. */
73
+ outputTypeForKind(kind: string): Record<string, unknown> | undefined;
67
74
  private capabilitiesForRefs;
68
75
  /**
69
76
  * Walks a manifest's annotation sites (refs, scopes, schema-from, CEL) via
@@ -96,6 +103,16 @@ export declare class AnalysisRegistry {
96
103
  /** Returns the closest user-facing kind to `badKind`, or undefined when nothing
97
104
  * is close enough (or multiple candidates tie). Case-sensitive. */
98
105
  suggestKind(badKind: string): string | undefined;
106
+ /** Returns the **canonical** (`module.Type`) kinds that satisfy an `x-telo-ref`
107
+ * constraint — an abstract target expands to its implementations (via the
108
+ * extends / capability index), a concrete target yields just itself.
109
+ * Resolution mirrors `validateReferences.checkKind`. Unlike
110
+ * {@link userFacingKindsForRef} this is import-independent: it includes
111
+ * locally-defined kinds (no alias), so callers can test whether an existing
112
+ * resource's kind satisfies the ref by canonicalizing it (`resolveKind`) and
113
+ * membership-checking here. Returns `undefined` when the ref can't be
114
+ * resolved (e.g. unregistered identity). */
115
+ acceptedKindsForRef(xTeloRef: string): Set<string> | undefined;
99
116
  /** Returns every user-facing (alias-form) kind that satisfies the given
100
117
  * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
101
118
  * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD;;wDAEwD;AACxD,MAAM,WAAW,YAAY;IAC3B;0CACsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;8EAG0E;IAC1E,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAIjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIpE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI7C;;;;;;;OAOG;IACH,mBAAmB,CACjB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAkBP;;;;;;OAMG;IACH,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,YAAY,EAAE;IAoBhE;;;;;;wBAMoB;IACpB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQtD;;;;;;sFAMkF;IAClF,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAenE,OAAO,CAAC,mBAAmB;IAS3B;;;;;;OAMG;IACH,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzF,IAAI;IAQP;;;;OAIG;IACH,kBAAkB,IAAI,kBAAkB,EAAE;IAI1C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAM/D,QAAQ,IAAI,MAAM,EAAE;IAIpB;mEAC+D;IAC/D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC;;;gCAG4B;IAC5B,oBAAoB,IAAI,MAAM,EAAE;IAIhC;wEACoE;IACpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;;;;gEAK4D;IAC5D,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IA+B7D,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
1
+ {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAqC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD;;wDAEwD;AACxD,MAAM,WAAW,YAAY;IAC3B;0CACsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;8EAG0E;IAC1E,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI;IAIjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIpE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI7C;;;;;;;OAOG;IACH,mBAAmB,CACjB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GACnC,IAAI;IAkBP;;;;;;OAMG;IACH,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,YAAY,EAAE;IAoBhE;;;;;;wBAMoB;IACpB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQtD;;;;;;sFAMkF;IAClF,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAenE;;;;;qDAKiD;IACjD,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAepE,OAAO,CAAC,mBAAmB;IAS3B;;;;;;OAMG;IACH,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzF,IAAI;IAQP;;;;OAIG;IACH,kBAAkB,IAAI,kBAAkB,EAAE;IAI1C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAM/D,QAAQ,IAAI,MAAM,EAAE;IAIpB;mEAC+D;IAC/D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAIxC;;;gCAG4B;IAC5B,oBAAoB,IAAI,MAAM,EAAE;IAIhC;wEACoE;IACpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;;;;;;;iDAQ6C;IAC7C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS;IAoB9D;;;;;gEAK4D;IAC5D,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAiB7D,qFAAqF;IACrF,QAAQ,IAAI,eAAe;CAG5B"}
@@ -112,6 +112,29 @@ export class AnalysisRegistry {
112
112
  }
113
113
  return undefined;
114
114
  }
115
+ /** Resolves the JSON Schema for a kind's `invoke()` / `run()` output, for
116
+ * editor hosts that render a typed output signature. Mirrors
117
+ * {@link inputTypeForKind}: the definition's own `outputType`, then the
118
+ * `extends`-declared abstract's `outputType`. Resolves the inline and
119
+ * raw-schema forms; a bare named type reference is left unresolved. Undefined
120
+ * when the kind declares no output contract. */
121
+ outputTypeForKind(kind) {
122
+ const def = this.resolveDefinition(kind);
123
+ if (!def)
124
+ return undefined;
125
+ const own = resolveTypeFieldToSchema(def.outputType, []);
126
+ if (own)
127
+ return own;
128
+ if (def.extends) {
129
+ const abstractDef = this.resolveDefinition(def.extends);
130
+ if (abstractDef) {
131
+ const inherited = resolveTypeFieldToSchema(abstractDef.outputType, []);
132
+ if (inherited)
133
+ return inherited;
134
+ }
135
+ }
136
+ return undefined;
137
+ }
115
138
  capabilitiesForRefs(refs) {
116
139
  const out = [];
117
140
  for (const ref of refs) {
@@ -168,31 +191,46 @@ export class AnalysisRegistry {
168
191
  suggestKind(badKind) {
169
192
  return computeSuggestKind(badKind, this.aliases, this.defs);
170
193
  }
171
- /** Returns every user-facing (alias-form) kind that satisfies the given
172
- * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
173
- * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
174
- * the set of definitions extending them; concrete targets yield just themselves.
175
- * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
176
- * so callers can fall back to the unfiltered kind list. */
177
- userFacingKindsForRef(xTeloRef) {
194
+ /** Returns the **canonical** (`module.Type`) kinds that satisfy an `x-telo-ref`
195
+ * constraint an abstract target expands to its implementations (via the
196
+ * extends / capability index), a concrete target yields just itself.
197
+ * Resolution mirrors `validateReferences.checkKind`. Unlike
198
+ * {@link userFacingKindsForRef} this is import-independent: it includes
199
+ * locally-defined kinds (no alias), so callers can test whether an existing
200
+ * resource's kind satisfies the ref by canonicalizing it (`resolveKind`) and
201
+ * membership-checking here. Returns `undefined` when the ref can't be
202
+ * resolved (e.g. unregistered identity). */
203
+ acceptedKindsForRef(xTeloRef) {
178
204
  const targetKind = this.defs.resolveRef(xTeloRef);
179
205
  if (!targetKind)
180
206
  return undefined;
181
207
  const targetDef = this.defs.resolve(targetKind);
182
208
  if (!targetDef)
183
209
  return undefined;
184
- const canonicalKinds = [];
210
+ const out = new Set();
185
211
  if (targetDef.kind === "Telo.Abstract") {
186
212
  for (const def of this.defs.getByExtends(targetKind)) {
187
213
  const module = def.metadata?.module;
188
214
  if (module && def.metadata?.name) {
189
- canonicalKinds.push(`${module}.${def.metadata.name}`);
215
+ out.add(`${module}.${def.metadata.name}`);
190
216
  }
191
217
  }
192
218
  }
193
219
  else {
194
- canonicalKinds.push(targetKind);
220
+ out.add(targetKind);
195
221
  }
222
+ return out;
223
+ }
224
+ /** Returns every user-facing (alias-form) kind that satisfies the given
225
+ * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
226
+ * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
227
+ * the set of definitions extending them; concrete targets yield just themselves.
228
+ * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
229
+ * so callers can fall back to the unfiltered kind list. */
230
+ userFacingKindsForRef(xTeloRef) {
231
+ const canonicalKinds = this.acceptedKindsForRef(xTeloRef);
232
+ if (!canonicalKinds)
233
+ return undefined;
196
234
  const out = new Set();
197
235
  for (const kind of canonicalKinds) {
198
236
  const dot = kind.indexOf(".");
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuhB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAypBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA2hB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAypBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
package/dist/analyzer.js CHANGED
@@ -442,8 +442,11 @@ function collectCelTypeIssues(data, schema, path, definition, manifest, baseType
442
442
  }
443
443
  else if (data !== null && typeof data === "object") {
444
444
  const props = (schema.properties ?? {});
445
+ const mapValueSchema = schema.additionalProperties && typeof schema.additionalProperties === "object"
446
+ ? schema.additionalProperties
447
+ : {};
445
448
  for (const [k, v] of Object.entries(data)) {
446
- issues.push(...collectCelTypeIssues(v, (props[k] ?? {}), path ? `${path}.${k}` : k, definition, manifest, baseTypedEnv, rootEnv, rootModuleManifest));
449
+ issues.push(...collectCelTypeIssues(v, (props[k] ?? mapValueSchema), path ? `${path}.${k}` : k, definition, manifest, baseTypedEnv, rootEnv, rootModuleManifest));
447
450
  }
448
451
  }
449
452
  return issues;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.18.0",
3
+ "version": "0.19.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.18.0"
51
+ "@telorun/sdk": "0.19.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
@@ -146,6 +146,27 @@ export class AnalysisRegistry {
146
146
  return undefined;
147
147
  }
148
148
 
149
+ /** Resolves the JSON Schema for a kind's `invoke()` / `run()` output, for
150
+ * editor hosts that render a typed output signature. Mirrors
151
+ * {@link inputTypeForKind}: the definition's own `outputType`, then the
152
+ * `extends`-declared abstract's `outputType`. Resolves the inline and
153
+ * raw-schema forms; a bare named type reference is left unresolved. Undefined
154
+ * when the kind declares no output contract. */
155
+ outputTypeForKind(kind: string): Record<string, unknown> | undefined {
156
+ const def = this.resolveDefinition(kind);
157
+ if (!def) return undefined;
158
+ const own = resolveTypeFieldToSchema(def.outputType, []);
159
+ if (own) return own;
160
+ if (def.extends) {
161
+ const abstractDef = this.resolveDefinition(def.extends);
162
+ if (abstractDef) {
163
+ const inherited = resolveTypeFieldToSchema(abstractDef.outputType, []);
164
+ if (inherited) return inherited;
165
+ }
166
+ }
167
+ return undefined;
168
+ }
169
+
149
170
  private capabilitiesForRefs(refs: string[]): string[] {
150
171
  const out: string[] = [];
151
172
  for (const ref of refs) {
@@ -213,29 +234,44 @@ export class AnalysisRegistry {
213
234
  return computeSuggestKind(badKind, this.aliases, this.defs);
214
235
  }
215
236
 
216
- /** Returns every user-facing (alias-form) kind that satisfies the given
217
- * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
218
- * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
219
- * the set of definitions extending them; concrete targets yield just themselves.
220
- * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
221
- * so callers can fall back to the unfiltered kind list. */
222
- userFacingKindsForRef(xTeloRef: string): string[] | undefined {
237
+ /** Returns the **canonical** (`module.Type`) kinds that satisfy an `x-telo-ref`
238
+ * constraint an abstract target expands to its implementations (via the
239
+ * extends / capability index), a concrete target yields just itself.
240
+ * Resolution mirrors `validateReferences.checkKind`. Unlike
241
+ * {@link userFacingKindsForRef} this is import-independent: it includes
242
+ * locally-defined kinds (no alias), so callers can test whether an existing
243
+ * resource's kind satisfies the ref by canonicalizing it (`resolveKind`) and
244
+ * membership-checking here. Returns `undefined` when the ref can't be
245
+ * resolved (e.g. unregistered identity). */
246
+ acceptedKindsForRef(xTeloRef: string): Set<string> | undefined {
223
247
  const targetKind = this.defs.resolveRef(xTeloRef);
224
248
  if (!targetKind) return undefined;
225
249
  const targetDef = this.defs.resolve(targetKind);
226
250
  if (!targetDef) return undefined;
227
251
 
228
- const canonicalKinds: string[] = [];
252
+ const out = new Set<string>();
229
253
  if (targetDef.kind === "Telo.Abstract") {
230
254
  for (const def of this.defs.getByExtends(targetKind)) {
231
255
  const module = (def.metadata as { module?: string } | undefined)?.module;
232
256
  if (module && def.metadata?.name) {
233
- canonicalKinds.push(`${module}.${def.metadata.name as string}`);
257
+ out.add(`${module}.${def.metadata.name as string}`);
234
258
  }
235
259
  }
236
260
  } else {
237
- canonicalKinds.push(targetKind);
261
+ out.add(targetKind);
238
262
  }
263
+ return out;
264
+ }
265
+
266
+ /** Returns every user-facing (alias-form) kind that satisfies the given
267
+ * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
268
+ * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
269
+ * the set of definitions extending them; concrete targets yield just themselves.
270
+ * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
271
+ * so callers can fall back to the unfiltered kind list. */
272
+ userFacingKindsForRef(xTeloRef: string): string[] | undefined {
273
+ const canonicalKinds = this.acceptedKindsForRef(xTeloRef);
274
+ if (!canonicalKinds) return undefined;
239
275
 
240
276
  const out = new Set<string>();
241
277
  for (const kind of canonicalKinds) {
package/src/analyzer.ts CHANGED
@@ -540,11 +540,15 @@ function collectCelTypeIssues(
540
540
  }
541
541
  } else if (data !== null && typeof data === "object") {
542
542
  const props = (schema.properties ?? {}) as Record<string, any>;
543
+ const mapValueSchema =
544
+ schema.additionalProperties && typeof schema.additionalProperties === "object"
545
+ ? (schema.additionalProperties as Record<string, any>)
546
+ : {};
543
547
  for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
544
548
  issues.push(
545
549
  ...collectCelTypeIssues(
546
550
  v,
547
- (props[k] ?? {}) as Record<string, any>,
551
+ (props[k] ?? mapValueSchema) as Record<string, any>,
548
552
  path ? `${path}.${k}` : k,
549
553
  definition,
550
554
  manifest,