@telorun/analyzer 0.43.0 → 0.44.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 (35) hide show
  1. package/dist/analysis-registry.d.ts +4 -4
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +3 -3
  4. package/dist/analyzer.d.ts.map +1 -1
  5. package/dist/analyzer.js +84 -1
  6. package/dist/builtins.js +7 -7
  7. package/dist/definition-registry.d.ts +49 -22
  8. package/dist/definition-registry.d.ts.map +1 -1
  9. package/dist/definition-registry.js +67 -61
  10. package/dist/loaded-types.d.ts +13 -6
  11. package/dist/loaded-types.d.ts.map +1 -1
  12. package/dist/manifest-loader.d.ts.map +1 -1
  13. package/dist/manifest-loader.js +1 -0
  14. package/dist/reconcile-module-versions.d.ts +2 -2
  15. package/dist/reconcile-module-versions.d.ts.map +1 -1
  16. package/dist/reconcile-module-versions.js +69 -17
  17. package/dist/reference-field-map.d.ts +2 -1
  18. package/dist/reference-field-map.d.ts.map +1 -1
  19. package/dist/resolve-schema-ref-kinds.d.ts +62 -0
  20. package/dist/resolve-schema-ref-kinds.d.ts.map +1 -0
  21. package/dist/resolve-schema-ref-kinds.js +81 -0
  22. package/dist/sources/manifest-cache.d.ts +14 -4
  23. package/dist/sources/manifest-cache.d.ts.map +1 -1
  24. package/dist/sources/manifest-cache.js +15 -6
  25. package/package.json +2 -2
  26. package/src/analysis-registry.ts +4 -4
  27. package/src/analyzer.ts +85 -2
  28. package/src/builtins.ts +7 -7
  29. package/src/definition-registry.ts +67 -66
  30. package/src/loaded-types.ts +13 -6
  31. package/src/manifest-loader.ts +1 -0
  32. package/src/reconcile-module-versions.ts +69 -17
  33. package/src/reference-field-map.ts +2 -1
  34. package/src/resolve-schema-ref-kinds.ts +118 -0
  35. package/src/sources/manifest-cache.ts +26 -8
@@ -13,8 +13,8 @@ export interface VersionReconciliation {
13
13
  diagnostics: AnalysisDiagnostic[];
14
14
  }
15
15
  /**
16
- * Reconcile a loaded import graph so each module identity (`namespace/name`)
17
- * resolves to a single version. Within a shared major the highest version wins
16
+ * Reconcile a loaded import graph so each module location (an import ref minus
17
+ * its version) resolves to a single version. Within a shared major the highest version wins
18
18
  * (a non-lossy hoist, given Telo's additive-only pre-1.0 policy); a major
19
19
  * mismatch is a hard conflict. Mutates `importEdges` in place — every edge that
20
20
  * pointed at a losing source is repointed at the winner — so `flattenForAnalyzer`
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile-module-versions.d.ts","sourceRoot":"","sources":["../src/reconcile-module-versions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;0CAI0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;2EACuE;IACvE,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAwKD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAClC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAChD,qBAAqB,CAiDvB"}
1
+ {"version":3,"file":"reconcile-module-versions.d.ts","sourceRoot":"","sources":["../src/reconcile-module-versions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;0CAI0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;2EACuE;IACvE,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAuMD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAClC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAChD,qBAAqB,CAsEvB"}
@@ -64,27 +64,56 @@ function compareVersions(a, b) {
64
64
  }
65
65
  return 0;
66
66
  }
67
- /** Read a loaded module's `namespace/name` identity, version, and raw owner
68
- * text. Returns `null` for modules without a namespace: only a registry
69
- * identity (`<namespace>/<name>`) is a stable cross-import key. Two namespace-
70
- * less local libraries that merely share a `metadata.name` are distinct modules
71
- * reached via distinct source URLs — reconciling them would drop one and break
72
- * its kinds; the same local file reached via two paths is already collapsed by
73
- * canonical-source dedup, so there is nothing left to reconcile here. */
74
- function moduleIdentityOf(mod) {
67
+ /** The location identity of an import ref: the ref with its version stripped.
68
+ * Two refs share an identity when they address the same module at different
69
+ * versions, whatever transport owns them:
70
+ *
71
+ * "std/kv-store@0.3.0" → "std/kv-store"
72
+ * "oci://ghcr.io/acme/s3@1.2.0" → "oci://ghcr.io/acme/s3"
73
+ * "https://x.com/lib/telo.yaml" → itself (a URL carries no version)
74
+ *
75
+ * Returns `null` for a relative path, which addresses one file on the
76
+ * publisher's disk and is therefore not a cross-import key: two local libraries
77
+ * that merely agree on `metadata.name` are distinct modules, and reconciling
78
+ * them would drop one and break its kinds. The same local file reached via two
79
+ * paths is already collapsed by canonical-source dedup.
80
+ *
81
+ * **What this key cannot relate.** It compares ref *spellings*, so it groups by
82
+ * origin exactly and nothing else. Two consequences, both accepted:
83
+ *
84
+ * - A module imported once by a registry ref and once by a relative path is two
85
+ * groups, so a version skew between them is not hoisted. Keying on what the
86
+ * module declares about itself would catch that case, but only by trusting a
87
+ * self-declared identity — which is what this change removes, and which
88
+ * cannot tell two same-named modules from different origins apart.
89
+ * - A bare `std/kv-store@0.4.0` and the equivalent direct
90
+ * `https://<registry>/std/kv-store/0.4.0/telo.yaml` are two groups. Relating
91
+ * them needs the configured registry base, which this pure, browser-safe
92
+ * function does not have. */
93
+ function refIdentity(ref) {
94
+ const base = ref.split("#")[0];
95
+ if (!base || base.startsWith(".") || base.startsWith("/") || base.startsWith("file:")) {
96
+ return null;
97
+ }
98
+ const lastSlash = base.lastIndexOf("/");
99
+ const at = base.lastIndexOf("@");
100
+ return at > lastSlash && at > 0 ? base.slice(0, at) : base;
101
+ }
102
+ /** Read a loaded module's version and raw owner text under the location
103
+ * identity the import edge reached it by. The identity comes from the ref, not
104
+ * from anything the module declares about itself — a module's own metadata
105
+ * cannot distinguish two same-named modules published to different origins. */
106
+ function moduleIdentityOf(mod, identity) {
75
107
  const doc = mod.owner.manifests.find((m) => m && isModuleKind(m.kind));
76
108
  if (!doc)
77
109
  return null;
78
110
  const meta = doc.metadata;
79
- const name = meta?.name;
80
- if (typeof name !== "string" || name.length === 0)
81
- return null;
82
- if (typeof meta.namespace !== "string" || meta.namespace.length === 0)
111
+ if (typeof meta?.name !== "string" || meta.name.length === 0)
83
112
  return null;
84
113
  const version = typeof meta.version === "string" ? meta.version : "";
85
114
  return {
86
115
  source: mod.owner.source,
87
- identity: `${meta.namespace}/${name}`,
116
+ identity,
88
117
  version,
89
118
  parsed: parseVersion(version),
90
119
  text: mod.owner.text,
@@ -154,8 +183,8 @@ function hoistDiagnostic(identity, importerSource, alias, loser, winner, conflic
154
183
  return null;
155
184
  }
156
185
  /**
157
- * Reconcile a loaded import graph so each module identity (`namespace/name`)
158
- * resolves to a single version. Within a shared major the highest version wins
186
+ * Reconcile a loaded import graph so each module location (an import ref minus
187
+ * its version) resolves to a single version. Within a shared major the highest version wins
159
188
  * (a non-lossy hoist, given Telo's additive-only pre-1.0 policy); a major
160
189
  * mismatch is a hard conflict. Mutates `importEdges` in place — every edge that
161
190
  * pointed at a losing source is repointed at the winner — so `flattenForAnalyzer`
@@ -165,10 +194,33 @@ function hoistDiagnostic(identity, importerSource, alias, loser, winner, conflic
165
194
  export function reconcileModuleVersions(modules, importEdges) {
166
195
  const overrides = new Map();
167
196
  const diagnostics = [];
197
+ // Location identity per resolved module, taken from the ref that reached it.
198
+ // Two refs at different versions resolve to different canonical sources, so a
199
+ // source normally maps to exactly one identity; the entry module has no
200
+ // inbound edge and needs none (it is never reconciled against itself).
201
+ //
202
+ // One source CAN be reached by two spellings of the same location (a bare
203
+ // registry ref and the direct URL it resolves to). Both name the same module
204
+ // at the same version, so either identity groups it correctly — but the choice
205
+ // must not depend on edge iteration order, or the same graph could reconcile
206
+ // differently across runs. First edge wins.
207
+ const identityBySource = new Map();
208
+ for (const aliasMap of importEdges.values()) {
209
+ for (const edge of aliasMap.values()) {
210
+ if (identityBySource.has(edge.targetSource))
211
+ continue;
212
+ const identity = refIdentity(edge.targetRef);
213
+ if (identity)
214
+ identityBySource.set(edge.targetSource, identity);
215
+ }
216
+ }
168
217
  const groups = new Map();
169
218
  const infoBySource = new Map();
170
- for (const mod of modules.values()) {
171
- const info = moduleIdentityOf(mod);
219
+ for (const [source, mod] of modules) {
220
+ const identity = identityBySource.get(source);
221
+ if (!identity)
222
+ continue;
223
+ const info = moduleIdentityOf(mod, identity);
172
224
  if (!info)
173
225
  continue;
174
226
  infoBySource.set(info.source, info);
@@ -1,6 +1,7 @@
1
1
  /** An entry for a field that carries one or more x-telo-ref constraints. */
2
2
  export interface RefFieldEntry {
3
- /** One or more canonical ref strings ("namespace/module#TypeName" or "telo#TypeName").
3
+ /** One or more canonical kind keys ("<module>.<Kind>"), or the deprecated
4
+ * identity form ("<namespace>/<module>#<Kind>") for a legacy published module.
4
5
  * Multiple entries arise from anyOf branches. */
5
6
  refs: string[];
6
7
  /** True when the field path traversed through at least one array (path contains "[]"). */
@@ -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;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"}
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;;sDAEkD;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"}
@@ -0,0 +1,62 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ /** Why an `x-telo-ref` constraint did not canonicalize.
4
+ *
5
+ * - `legacy` — the deprecated `<namespace>/<module>#<Kind>` identity form,
6
+ * still resolved through the identity table.
7
+ * - `unknown` — the prefix is not an alias in the declaring module's scope: a
8
+ * typo, a missing `imports:` entry — or a value that was already
9
+ * canonical, which the caller separates by asking the registry.
10
+ * - `gated` — the alias is known and the target owns the kind, but its
11
+ * `exports.kinds` does not list it. */
12
+ export type RefConstraintReason = "legacy" | "unknown" | "gated";
13
+ /** An `x-telo-ref` constraint that did not canonicalize. */
14
+ export interface RefConstraintIssue {
15
+ /** The constraint string exactly as authored. */
16
+ ref: string;
17
+ /** Dotted path to the annotated schema node within the doc, e.g.
18
+ * `schema.properties.store`. Points the author at the slot, not just the doc. */
19
+ path: string;
20
+ /** The `Telo.Definition` / `Telo.Abstract` doc that declares the slot. */
21
+ manifest: ResourceManifest;
22
+ reason: RefConstraintReason;
23
+ /** For `gated`: the target module and the kinds it does export. */
24
+ gate?: {
25
+ module: string;
26
+ exported: string[];
27
+ };
28
+ /** Aliases the declaring scope does know — the "did you mean" material for
29
+ * an `unknown` prefix. */
30
+ knownAliases?: string[];
31
+ }
32
+ /** True for the legacy identity form, which is split on `#` by the identity table. */
33
+ export declare function isLegacyRefIdentity(ref: string): boolean;
34
+ /**
35
+ * Rewrites the `x-telo-ref` constraints on one definition doc from the alias
36
+ * form (`KvStore.Store`, `Self.Store`, `Telo.Invocable`) to the canonical
37
+ * `<module>.<Kind>` key the definition registry is keyed by.
38
+ *
39
+ * `resolver` must be the scope of the module that DECLARES the definition, not
40
+ * the consumer's: an imported library names its dependencies by its own aliases.
41
+ * This mirrors how `extends:` and `capability:` are pre-resolved before
42
+ * registration, so the registry never needs module context to answer a ref query.
43
+ *
44
+ * Every constraint that does not canonicalize is returned, tagged with why. That
45
+ * matters more than it looks: an unresolved constraint leaves a string naming no
46
+ * registered kind, and the reference check treats an unknown target as partial
47
+ * context and skips it — so an unreported one would silently let the slot accept
48
+ * anything. The authored value is left in place either way, so a diagnostic
49
+ * quotes what the author actually wrote.
50
+ *
51
+ * An `unknown` result also covers an already-canonical value (`kv-store.Store`
52
+ * names a module, not an alias), which is what keeps the rewrite idempotent —
53
+ * the caller drops those by checking the definition registry once every kind is
54
+ * registered.
55
+ *
56
+ * The walk covers the whole doc rather than a fixed field list: a constraint can
57
+ * sit in `schema`, `inputType`, `outputType`, or a `$defs` entry nested in any of
58
+ * them, and rewriting one that appears in a template body's inline schema is
59
+ * correct too.
60
+ */
61
+ export declare function resolveSchemaRefKinds(definition: ResourceManifest, resolver: Pick<AliasResolver, "resolveKindResult" | "knownAliases">): RefConstraintIssue[];
62
+ //# sourceMappingURL=resolve-schema-ref-kinds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-schema-ref-kinds.d.ts","sourceRoot":"","sources":["../src/resolve-schema-ref-kinds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD;;;;;;;;sDAQsD;AACtD,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAEjE,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;sFACkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,mEAAmE;IACnE,IAAI,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC9C;+BAC2B;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,mBAAmB,GAAG,cAAc,CAAC,GAClE,kBAAkB,EAAE,CAiDtB"}
@@ -0,0 +1,81 @@
1
+ const REF_ANNOTATION = "x-telo-ref";
2
+ /** True for the legacy identity form, which is split on `#` by the identity table. */
3
+ export function isLegacyRefIdentity(ref) {
4
+ return ref.includes("#");
5
+ }
6
+ /**
7
+ * Rewrites the `x-telo-ref` constraints on one definition doc from the alias
8
+ * form (`KvStore.Store`, `Self.Store`, `Telo.Invocable`) to the canonical
9
+ * `<module>.<Kind>` key the definition registry is keyed by.
10
+ *
11
+ * `resolver` must be the scope of the module that DECLARES the definition, not
12
+ * the consumer's: an imported library names its dependencies by its own aliases.
13
+ * This mirrors how `extends:` and `capability:` are pre-resolved before
14
+ * registration, so the registry never needs module context to answer a ref query.
15
+ *
16
+ * Every constraint that does not canonicalize is returned, tagged with why. That
17
+ * matters more than it looks: an unresolved constraint leaves a string naming no
18
+ * registered kind, and the reference check treats an unknown target as partial
19
+ * context and skips it — so an unreported one would silently let the slot accept
20
+ * anything. The authored value is left in place either way, so a diagnostic
21
+ * quotes what the author actually wrote.
22
+ *
23
+ * An `unknown` result also covers an already-canonical value (`kv-store.Store`
24
+ * names a module, not an alias), which is what keeps the rewrite idempotent —
25
+ * the caller drops those by checking the definition registry once every kind is
26
+ * registered.
27
+ *
28
+ * The walk covers the whole doc rather than a fixed field list: a constraint can
29
+ * sit in `schema`, `inputType`, `outputType`, or a `$defs` entry nested in any of
30
+ * them, and rewriting one that appears in a template body's inline schema is
31
+ * correct too.
32
+ */
33
+ export function resolveSchemaRefKinds(definition, resolver) {
34
+ const issues = [];
35
+ const record = (ref, path) => {
36
+ if (isLegacyRefIdentity(ref)) {
37
+ issues.push({ ref, path, manifest: definition, reason: "legacy" });
38
+ return;
39
+ }
40
+ const result = resolver.resolveKindResult(ref);
41
+ if (result.status === "ok")
42
+ return;
43
+ issues.push(result.status === "gated"
44
+ ? {
45
+ ref,
46
+ path,
47
+ manifest: definition,
48
+ reason: "gated",
49
+ gate: { module: result.module, exported: result.exported },
50
+ }
51
+ : {
52
+ ref,
53
+ path,
54
+ manifest: definition,
55
+ reason: "unknown",
56
+ knownAliases: resolver.knownAliases(),
57
+ });
58
+ };
59
+ const walk = (value, path) => {
60
+ if (value === null || typeof value !== "object")
61
+ return;
62
+ if (Array.isArray(value)) {
63
+ value.forEach((item, i) => walk(item, `${path}[${i}]`));
64
+ return;
65
+ }
66
+ const obj = value;
67
+ const ref = obj[REF_ANNOTATION];
68
+ if (typeof ref === "string" && ref) {
69
+ const result = isLegacyRefIdentity(ref) ? null : resolver.resolveKindResult(ref);
70
+ if (result?.status === "ok")
71
+ obj[REF_ANNOTATION] = result.kind;
72
+ else
73
+ record(ref, path);
74
+ }
75
+ for (const key of Object.keys(obj)) {
76
+ walk(obj[key], path ? `${path}.${key}` : key);
77
+ }
78
+ };
79
+ walk(definition, "");
80
+ return issues;
81
+ }
@@ -7,16 +7,26 @@ export declare const MANIFEST_CACHE_BASE_URL = "https://manifests.telo.sh";
7
7
  * stores per version — `{ transport, host, path, version }` — so the tracker's
8
8
  * write key and the editor's read key come from the same function and never
9
9
  * drift. `path` is the slash-separated repo/module path (multi-segment OCI
10
- * repos nest as prefixes). */
10
+ * repos nest as prefixes), empty for a module addressed at a host's root. */
11
11
  export interface ManifestCacheCoords {
12
12
  transport: string;
13
13
  host: string;
14
14
  path: string;
15
- version: string;
15
+ /** Omit only when the ref carries no version to key by — a direct `https://`
16
+ * URL addresses exactly one file, so its path alone is already unambiguous.
17
+ * Every ref whose grammar has a version segment must supply it, or two
18
+ * versions of one module would collide on a single key. */
19
+ version?: string;
20
+ /** File within the version directory. Defaults to the module manifest, which
21
+ * is the only file the hub's bucket stores; the local install cache also
22
+ * holds each `include:` partial, which is named here. */
23
+ file?: string;
16
24
  }
17
25
  /** Deterministic cache key for one module version:
18
- * `<transport>/<host>/<path…>/<version>/telo.yaml`. Returns `null` when any
19
- * coordinate is empty or would traverse out of the key space. */
26
+ * `<transport>/<host>/<path…>/<version>/<file>`, where `version` is omitted
27
+ * when the coordinates carry none and `file` defaults to the module manifest.
28
+ * Returns `null` when any coordinate is empty or would traverse out of the key
29
+ * space. */
20
30
  export declare function manifestCacheKey(coords: ManifestCacheCoords): string | null;
21
31
  /** Cache coordinates for an `oci://host/repo@tag` ref. Returns `null` when the
22
32
  * ref carries no explicit tag (a defaulted `latest` or a `sha256:` digest is
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-cache.d.ts","sourceRoot":"","sources":["../../src/sources/manifest-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD;;iFAEiF;AACjF,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAEnE;;;;+BAI+B;AAC/B,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD;;kEAEkE;AAClE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAO3E;AAED;;;6EAG6E;AAC7E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAY9E;AAID;;2DAE2D;AAC3D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;;;;;oEASoE;AACpE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAuB/F;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,mBAAmB,EACzC,OAAO,GAAE,MAAgC,GACxC,MAAM,GAAG,IAAI,CAKf;AAED;;;;;+DAK+D;AAC/D,qBAAa,mBAAoB,YAAW,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,MAAgC;IAEtE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAQxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAalE;;;iEAG6D;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAOxD"}
1
+ {"version":3,"file":"manifest-cache.d.ts","sourceRoot":"","sources":["../../src/sources/manifest-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD;;iFAEiF;AACjF,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAEnE;;;;8EAI8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;gEAG4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;8DAE0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD;;;;aAIa;AACb,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAe3E;AAED;;;6EAG6E;AAC7E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAY9E;AAID;;2DAE2D;AAC3D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;;;;;oEASoE;AACpE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAuB/F;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,mBAAmB,EACzC,OAAO,GAAE,MAAgC,GACxC,MAAM,GAAG,IAAI,CAKf;AAED;;;;;+DAK+D;AAC/D,qBAAa,mBAAoB,YAAW,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,MAAgC;IAEtE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAQxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAalE;;;iEAG6D;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAOxD"}
@@ -10,15 +10,24 @@ function invalidSegment(segment) {
10
10
  return segment === "" || segment === "." || segment === ".." || segment.includes("\\");
11
11
  }
12
12
  /** Deterministic cache key for one module version:
13
- * `<transport>/<host>/<path…>/<version>/telo.yaml`. Returns `null` when any
14
- * coordinate is empty or would traverse out of the key space. */
13
+ * `<transport>/<host>/<path…>/<version>/<file>`, where `version` is omitted
14
+ * when the coordinates carry none and `file` defaults to the module manifest.
15
+ * Returns `null` when any coordinate is empty or would traverse out of the key
16
+ * space. */
15
17
  export function manifestCacheKey(coords) {
16
- const { transport, host, path, version } = coords;
17
- const segments = [transport, host, ...path.split("/"), version];
18
- if (segments.some(invalidSegment) || transport.includes("/") || host.includes("/") || version.includes("/")) {
18
+ const { transport, host, path, version, file } = coords;
19
+ const segments = [transport, host, ...(path ? path.split("/") : [])];
20
+ if (version !== undefined)
21
+ segments.push(version);
22
+ segments.push(file ?? DEFAULT_MANIFEST_FILENAME);
23
+ if (segments.some(invalidSegment) ||
24
+ transport.includes("/") ||
25
+ host.includes("/") ||
26
+ (version !== undefined && version.includes("/")) ||
27
+ (file !== undefined && file.includes("/"))) {
19
28
  return null;
20
29
  }
21
- return `${segments.join("/")}/${DEFAULT_MANIFEST_FILENAME}`;
30
+ return segments.join("/");
22
31
  }
23
32
  /** Cache coordinates for an `oci://host/repo@tag` ref. Returns `null` when the
24
33
  * ref carries no explicit tag (a defaulted `latest` or a `sha256:` digest is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.43.0",
3
+ "version": "0.44.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.54.0"
51
+ "@telorun/sdk": "0.56.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
@@ -17,7 +17,7 @@ export interface RefFieldInfo {
17
17
  path: string;
18
18
  /** True when the path traverses at least one array. */
19
19
  isArray: boolean;
20
- /** Accepted `x-telo-ref` constraint strings (e.g. `telo#Runnable`). */
20
+ /** Accepted `x-telo-ref` constraint strings, canonicalized (e.g. `Telo.Runnable`). */
21
21
  refs: string[];
22
22
  /** Distinct capabilities the slot may target (`Telo.Runnable`,
23
23
  * `Telo.Service`, `Telo.Provider`, …) — one per resolvable constraint. The
@@ -119,9 +119,9 @@ export class AnalysisRegistry {
119
119
 
120
120
  /** Base capability an `x-telo-ref` constraint targets. A definition's declared
121
121
  * `capability` is always one of the base capabilities, so it wins — this
122
- * resolves user-defined abstracts (e.g. `std/ai#Model`, declared
122
+ * resolves user-defined abstracts (e.g. `ai.Model`, declared
123
123
  * `capability: Telo.Invocable`) to the capability instances satisfy, not the
124
- * abstract kind. Builtin abstracts (`telo#Runnable`) carry no `capability`
124
+ * abstract kind. Builtin abstracts (`Telo.Runnable`) carry no `capability`
125
125
  * field — there the kind itself *is* the capability. Undefined when
126
126
  * unresolvable. */
127
127
  capabilityForRef(xTeloRef: string): string | undefined {
@@ -306,7 +306,7 @@ export class AnalysisRegistry {
306
306
  }
307
307
 
308
308
  /** Returns every user-facing (alias-form) kind that satisfies the given
309
- * `x-telo-ref` constraint string (e.g. `"telo#Invocable"`, `"std/sql#Connection"`).
309
+ * `x-telo-ref` constraint string, canonicalized (e.g. `"Telo.Invocable"`, `"sql.Connection"`).
310
310
  * Resolution mirrors `validateReferences.checkKind`: abstract targets expand to
311
311
  * the set of definitions extending them; concrete targets yield just themselves.
312
312
  * Returns `undefined` when the ref can't be resolved (e.g. unregistered identity),
package/src/analyzer.ts CHANGED
@@ -20,6 +20,7 @@ import { isModuleKind } from "./module-kinds.js";
20
20
  import { normalizeInlineResources } from "./normalize-inline-resources.js";
21
21
  import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
22
22
  import { resolveRefSentinels } from "./resolve-ref-sentinels.js";
23
+ import { resolveSchemaRefKinds, type RefConstraintIssue } from "./resolve-schema-ref-kinds.js";
23
24
  import { resolveSchemaTypeRefs } from "./resolve-schema-type-refs.js";
24
25
  import { validateSchemaTypeRefs } from "./validate-schema-type-refs.js";
25
26
  import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
@@ -1035,6 +1036,7 @@ export class StaticAnalyzer {
1035
1036
  // declaring scope's resolver, so `extendedBy` is keyed by canonical kind regardless
1036
1037
  // of alias choices. `capability` covers the legacy implements-this-abstract overload;
1037
1038
  // `extends` is the canonical first-class form.
1039
+ const refConstraintIssues: RefConstraintIssue[] = [];
1038
1040
  for (const m of manifests) {
1039
1041
  if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
1040
1042
  const def = m as unknown as ResourceDefinition;
@@ -1043,6 +1045,15 @@ export class StaticAnalyzer {
1043
1045
  ownModule && !rootModules.has(ownModule)
1044
1046
  ? (aliasesByModule.get(ownModule) ?? new AliasResolver())
1045
1047
  : aliases;
1048
+ // Canonicalize alias-form `x-telo-ref` constraints in the DECLARING module's
1049
+ // scope, before the schema reaches `register()` and the lazily-built field
1050
+ // maps. Same pre-resolution `capability` / `extends` get below.
1051
+ const issues = resolveSchemaRefKinds(m, scopeResolver);
1052
+ // Report only for definitions the author can edit. A published dependency
1053
+ // still on the deprecated form — or with a constraint that no longer
1054
+ // resolves — is not the consumer's to fix, and every import would
1055
+ // otherwise flood `telo check` with unactionable noise.
1056
+ if (!ownModule || rootModules.has(ownModule)) refConstraintIssues.push(...issues);
1046
1057
  const resolvedCapability = def.capability
1047
1058
  ? (scopeResolver.resolveKind(def.capability) ?? def.capability)
1048
1059
  : def.capability;
@@ -1063,6 +1074,57 @@ export class StaticAnalyzer {
1063
1074
  // distinguishable from the resolver's own substitution (after Phase 2/2.5
1064
1075
  // they are the same object).
1065
1076
  if (!options?.skipValidation) {
1077
+ for (const issue of refConstraintIssues) {
1078
+ // An `unknown` prefix is also what an ALREADY-CANONICAL value looks like
1079
+ // (`kv-store.Store` names a module, not an alias). Now that every kind is
1080
+ // registered, the registry separates the two — anything it resolves was
1081
+ // canonical, anything it doesn't names nothing at all.
1082
+ if (issue.reason === "unknown" && defs.resolve(issue.ref)) continue;
1083
+ const resource = {
1084
+ kind: issue.manifest.kind,
1085
+ name: issue.manifest.metadata?.name as string,
1086
+ };
1087
+ const filePath = (issue.manifest.metadata as { source?: string } | undefined)?.source;
1088
+ const data = { resource, filePath, path: issue.path };
1089
+ if (issue.reason === "legacy") {
1090
+ diagnostics.push({
1091
+ severity: DiagnosticSeverity.Warning,
1092
+ code: "X_TELO_REF_LEGACY_IDENTITY",
1093
+ source: SOURCE,
1094
+ message:
1095
+ `x-telo-ref '${issue.ref}' at '${issue.path}' uses the deprecated ` +
1096
+ `'<namespace>/<module>#<Kind>' form. Write the target as an alias-qualified kind ` +
1097
+ `instead — '<Alias>.<Kind>' for a module declared in this file's 'imports:' map, ` +
1098
+ `'Self.<Kind>' for a kind in this library, or 'Telo.<Kind>' for a built-in capability.`,
1099
+ data,
1100
+ });
1101
+ } else if (issue.reason === "gated") {
1102
+ diagnostics.push({
1103
+ severity: DiagnosticSeverity.Error,
1104
+ code: "KIND_NOT_EXPORTED",
1105
+ source: SOURCE,
1106
+ message:
1107
+ `x-telo-ref '${issue.ref}' at '${issue.path}' targets a kind module ` +
1108
+ `'${issue.gate?.module}' does not export. Add ` +
1109
+ `'${issue.ref.slice(issue.ref.indexOf(".") + 1)}' to that module's exports.kinds. ` +
1110
+ `Exported kinds: ${issue.gate?.exported.join(", ") || "(none)"}.`,
1111
+ data,
1112
+ });
1113
+ } else {
1114
+ diagnostics.push({
1115
+ severity: DiagnosticSeverity.Error,
1116
+ code: "X_TELO_REF_UNRESOLVED",
1117
+ source: SOURCE,
1118
+ message:
1119
+ `x-telo-ref '${issue.ref}' at '${issue.path}' names no kind. The prefix must be an ` +
1120
+ `import alias declared in this file's 'imports:' map, 'Self' for a kind in this ` +
1121
+ `library, or 'Telo' for a built-in capability. An unresolvable constraint would ` +
1122
+ `leave the slot accepting any resource. Known aliases: ` +
1123
+ `${issue.knownAliases?.join(", ") || "(none)"}.`,
1124
+ data,
1125
+ });
1126
+ }
1127
+ }
1066
1128
  diagnostics.push(...validateReferenceForms(manifests, defs, aliases, aliasesByModule));
1067
1129
  }
1068
1130
 
@@ -1089,10 +1151,31 @@ export class StaticAnalyzer {
1089
1151
  rootModules.has(ownModule) ? aliases : (aliasesByModule.get(ownModule) ?? new AliasResolver());
1090
1152
  const canonicalKind = scopeResolver.resolveKind(m.kind as string) ?? (m.kind as string);
1091
1153
  if (defs.resolve(canonicalKind)?.capability !== "Telo.Type") continue;
1092
- defs.registerNamedTypeSchema(
1093
- canonicalTypeSchemaId(ownModule, m.metadata.name as string),
1154
+ const typeName = m.metadata.name as string;
1155
+ const registered = defs.registerNamedTypeSchema(
1156
+ canonicalTypeSchemaId(ownModule, typeName),
1094
1157
  m.schema as Record<string, any>,
1095
1158
  );
1159
+ // Kinds and named types share one `telo://<module>/<Name>` id space. A
1160
+ // collision would leave every `$ref` to that id resolving to the kind's
1161
+ // schema — validating the wrong shape, silently — so it is an error, not
1162
+ // a last-writer-wins.
1163
+ if (!registered && !options?.skipValidation) {
1164
+ diagnostics.push({
1165
+ severity: DiagnosticSeverity.Error,
1166
+ code: "DUPLICATE_SCHEMA_ID",
1167
+ source: SOURCE,
1168
+ message:
1169
+ `Type '${typeName}' collides with the kind '${ownModule}.${typeName}': both claim the ` +
1170
+ `schema id '${canonicalTypeSchemaId(ownModule, typeName)}'. A '$ref' to it would ` +
1171
+ `resolve to the kind's schema. Rename one of them.`,
1172
+ data: {
1173
+ resource: { kind: m.kind, name: typeName },
1174
+ filePath: (m.metadata as { source?: string } | undefined)?.source,
1175
+ path: "metadata.name",
1176
+ },
1177
+ });
1178
+ }
1096
1179
  }
1097
1180
  if (!options?.skipValidation) {
1098
1181
  diagnostics.push(
package/src/builtins.ts CHANGED
@@ -97,7 +97,7 @@ const ROOT_LOGGING_SCHEMA = {
97
97
  type: "array",
98
98
  items: {
99
99
  type: "object",
100
- "x-telo-ref": "telo#LogSink",
100
+ "x-telo-ref": "Telo.LogSink",
101
101
  "x-telo-inline": true,
102
102
  },
103
103
  },
@@ -444,8 +444,8 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
444
444
  type: "array",
445
445
  items: {
446
446
  anyOf: [
447
- { type: "string", "x-telo-ref": "telo#Runnable" },
448
- { type: "string", "x-telo-ref": "telo#Service" },
447
+ { type: "string", "x-telo-ref": "Telo.Runnable" },
448
+ { type: "string", "x-telo-ref": "Telo.Service" },
449
449
  // Post-resolution shape that `resolveRefSentinels`
450
450
  // substitutes a `!ref <name>` sentinel into. The
451
451
  // adjacent `x-telo-ref` constraints govern the kind
@@ -469,8 +469,8 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
469
469
  properties: {
470
470
  ref: {
471
471
  anyOf: [
472
- { type: "string", "x-telo-ref": "telo#Runnable" },
473
- { type: "string", "x-telo-ref": "telo#Service" },
472
+ { type: "string", "x-telo-ref": "Telo.Runnable" },
473
+ { type: "string", "x-telo-ref": "Telo.Service" },
474
474
  {
475
475
  type: "object",
476
476
  required: ["kind", "name"],
@@ -510,8 +510,8 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
510
510
  },
511
511
  additionalProperties: true,
512
512
  anyOf: [
513
- { "x-telo-ref": "telo#Invocable" },
514
- { "x-telo-ref": "telo#Runnable" },
513
+ { "x-telo-ref": "Telo.Invocable" },
514
+ { "x-telo-ref": "Telo.Runnable" },
515
515
  ],
516
516
  },
517
517
  inputs: { type: "object", additionalProperties: true },