@telorun/analyzer 0.16.1 → 0.18.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
@@ -54,20 +54,13 @@ metadata:
54
54
  description: |
55
55
  A complete feedback collection REST API — no code, pure YAML.
56
56
  Persists entries to SQLite and serves them over HTTP.
57
+ imports:
58
+ Http: std/http-server@0.6.1
59
+ Sql: std/sql@0.5.1
57
60
  targets:
58
61
  - Migrations
59
62
  - Server
60
63
  ---
61
- kind: Telo.Import
62
- metadata:
63
- name: Http
64
- source: std/http-server@0.5.0
65
- ---
66
- kind: Telo.Import
67
- metadata:
68
- name: Sql
69
- source: std/sql@0.3.0
70
- ---
71
64
  # SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
72
65
  kind: Sql.Connection
73
66
  metadata:
@@ -127,7 +120,7 @@ routes:
127
120
  minLength: 1
128
121
  source:
129
122
  type: string
130
- required: [text]
123
+ required: [ text ]
131
124
  handler:
132
125
  kind: Sql.Exec
133
126
  connection:
@@ -157,7 +150,7 @@ routes:
157
150
  kind: Sql.Connection
158
151
  name: Db
159
152
  from: feedback
160
- columns: [id, text, source, score, created_at]
153
+ columns: [ id, text, source, score, created_at ]
161
154
  orderBy:
162
155
  - { column: created_at, direction: desc }
163
156
  response:
@@ -176,14 +169,14 @@ routes:
176
169
  properties:
177
170
  id:
178
171
  type: integer
179
- required: [id]
172
+ required: [ id ]
180
173
  handler:
181
174
  kind: Sql.Select
182
175
  connection:
183
176
  kind: Sql.Connection
184
177
  name: Db
185
178
  from: feedback
186
- columns: [id, text, source, score, created_at]
179
+ columns: [ id, text, source, score, created_at ]
187
180
  where:
188
181
  - { column: id, op: "=", value: "${{ request.params.id }}" }
189
182
  response:
@@ -1,6 +1,23 @@
1
1
  import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
2
2
  import { type ManifestVisitor } from "./manifest-visitor.js";
3
3
  import type { AnalysisContext } from "./types.js";
4
+ /** One reference field declared by a resource's definition, derived purely from
5
+ * the schema field map (independent of whether the manifest fills it). Editor
6
+ * hosts render these as ports / adapters on a node. */
7
+ export interface RefFieldInfo {
8
+ /** Field-map path with `[]` / `{}` markers (e.g. `targets[]`,
9
+ * `routes[].handler`, `encoder`). */
10
+ path: string;
11
+ /** True when the path traverses at least one array. */
12
+ isArray: boolean;
13
+ /** Accepted `x-telo-ref` constraint strings (e.g. `telo#Runnable`). */
14
+ refs: string[];
15
+ /** Distinct capabilities the slot may target (`Telo.Runnable`,
16
+ * `Telo.Service`, `Telo.Provider`, …) — one per resolvable constraint. The
17
+ * first classifies the port (node-capability → edge, ambient → picker); the
18
+ * full set validates drag-to-wire endpoints. Empty when none resolve. */
19
+ capabilities: string[];
20
+ }
4
21
  /**
5
22
  * Accumulates type and alias knowledge for a running kernel or analysis session.
6
23
  * Wraps AliasResolver and DefinitionRegistry into a single domain-level interface
@@ -23,6 +40,31 @@ export declare class AnalysisRegistry {
23
40
  * sub-schema (e.g. Server.notFoundHandler.returns[].content[mime].encoder).
24
41
  */
25
42
  iterateFieldEntries(resource: ResourceManifest, onRef: (fieldPath: string) => void, onScope: (fieldPath: string) => void): void;
43
+ /**
44
+ * Returns every reference field a resource's definition declares, with arity
45
+ * and the capability each slot targets — derived purely from the schema field
46
+ * map, so it lists slots even when the manifest leaves them empty. Editor
47
+ * hosts render these as node ports (drag-to-wire for node-capability targets,
48
+ * inline picker for ambient targets).
49
+ */
50
+ refFieldsForResource(resource: ResourceManifest): RefFieldInfo[];
51
+ /** Base capability an `x-telo-ref` constraint targets. A definition's declared
52
+ * `capability` is always one of the base capabilities, so it wins — this
53
+ * resolves user-defined abstracts (e.g. `std/ai#Model`, declared
54
+ * `capability: Telo.Invocable`) to the capability instances satisfy, not the
55
+ * abstract kind. Builtin abstracts (`telo#Runnable`) carry no `capability`
56
+ * field — there the kind itself *is* the capability. Undefined when
57
+ * unresolvable. */
58
+ capabilityForRef(xTeloRef: string): string | undefined;
59
+ /** Resolves the JSON Schema for a kind's `invoke()` inputs, for editor hosts
60
+ * that render a typed inputs form. Two-layer fallback mirroring the analyzer's
61
+ * template inputs typing: the definition's own `inputType`, then the
62
+ * `extends`-declared abstract's `inputType`. Resolves the inline
63
+ * (`{ kind: Type.JsonSchema, schema }`) and raw-schema forms; a bare named
64
+ * type reference is left unresolved (returns undefined) so the caller can fall
65
+ * back to a freeform map. Undefined when the kind declares no input contract. */
66
+ inputTypeForKind(kind: string): Record<string, unknown> | undefined;
67
+ private capabilitiesForRefs;
26
68
  /**
27
69
  * Walks a manifest's annotation sites (refs, scopes, schema-from, CEL) via
28
70
  * the shared manifest visitor, bound to this registry's definitions and
@@ -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;AAElD;;;;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,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,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"}
@@ -4,6 +4,7 @@ import { DefinitionRegistry } from "./definition-registry.js";
4
4
  import { computeSuggestKind, computeValidUserFacingKinds } from "./kind-suggest.js";
5
5
  import { visitManifest as runVisitManifest } from "./manifest-visitor.js";
6
6
  import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
7
+ import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
7
8
  /**
8
9
  * Accumulates type and alias knowledge for a running kernel or analysis session.
9
10
  * Wraps AliasResolver and DefinitionRegistry into a single domain-level interface
@@ -47,6 +48,79 @@ export class AnalysisRegistry {
47
48
  }
48
49
  }
49
50
  }
51
+ /**
52
+ * Returns every reference field a resource's definition declares, with arity
53
+ * and the capability each slot targets — derived purely from the schema field
54
+ * map, so it lists slots even when the manifest leaves them empty. Editor
55
+ * hosts render these as node ports (drag-to-wire for node-capability targets,
56
+ * inline picker for ambient targets).
57
+ */
58
+ refFieldsForResource(resource) {
59
+ const fieldMap = this.defs.expandedFieldMapForResource(resource, this.aliases, this.aliasesByModule);
60
+ if (!fieldMap)
61
+ return [];
62
+ const out = [];
63
+ for (const [path, entry] of fieldMap) {
64
+ if (!isRefEntry(entry))
65
+ continue;
66
+ out.push({
67
+ path,
68
+ isArray: entry.isArray,
69
+ refs: entry.refs,
70
+ capabilities: this.capabilitiesForRefs(entry.refs),
71
+ });
72
+ }
73
+ return out;
74
+ }
75
+ /** Base capability an `x-telo-ref` constraint targets. A definition's declared
76
+ * `capability` is always one of the base capabilities, so it wins — this
77
+ * resolves user-defined abstracts (e.g. `std/ai#Model`, declared
78
+ * `capability: Telo.Invocable`) to the capability instances satisfy, not the
79
+ * abstract kind. Builtin abstracts (`telo#Runnable`) carry no `capability`
80
+ * field — there the kind itself *is* the capability. Undefined when
81
+ * unresolvable. */
82
+ capabilityForRef(xTeloRef) {
83
+ const kind = this.defs.resolveRef(xTeloRef);
84
+ if (!kind)
85
+ return undefined;
86
+ const def = this.defs.resolve(kind);
87
+ if (!def)
88
+ return undefined;
89
+ return def.capability ?? kind;
90
+ }
91
+ /** Resolves the JSON Schema for a kind's `invoke()` inputs, for editor hosts
92
+ * that render a typed inputs form. Two-layer fallback mirroring the analyzer's
93
+ * template inputs typing: the definition's own `inputType`, then the
94
+ * `extends`-declared abstract's `inputType`. Resolves the inline
95
+ * (`{ kind: Type.JsonSchema, schema }`) and raw-schema forms; a bare named
96
+ * type reference is left unresolved (returns undefined) so the caller can fall
97
+ * back to a freeform map. Undefined when the kind declares no input contract. */
98
+ inputTypeForKind(kind) {
99
+ const def = this.resolveDefinition(kind);
100
+ if (!def)
101
+ return undefined;
102
+ const own = resolveTypeFieldToSchema(def.inputType, []);
103
+ if (own)
104
+ return own;
105
+ if (def.extends) {
106
+ const abstractDef = this.resolveDefinition(def.extends);
107
+ if (abstractDef) {
108
+ const inherited = resolveTypeFieldToSchema(abstractDef.inputType, []);
109
+ if (inherited)
110
+ return inherited;
111
+ }
112
+ }
113
+ return undefined;
114
+ }
115
+ capabilitiesForRefs(refs) {
116
+ const out = [];
117
+ for (const ref of refs) {
118
+ const cap = this.capabilityForRef(ref);
119
+ if (cap && !out.includes(cap))
120
+ out.push(cap);
121
+ }
122
+ return out;
123
+ }
50
124
  /**
51
125
  * Walks a manifest's annotation sites (refs, scopes, schema-from, CEL) via
52
126
  * the shared manifest visitor, bound to this registry's definitions and
@@ -1,9 +1,31 @@
1
1
  import type { ResourceManifest } from "@telorun/sdk";
2
2
  import type { LoadedGraph, LoadedModule } from "./loaded-types.js";
3
+ /** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
4
+ * CLI / kernel loader path) and the telo-editor's workspace projection so the
5
+ * two cannot drift. Given one module's stamped manifests and whether that
6
+ * module is the analysis entry (root), returns the manifests that cross into
7
+ * the consumer's flat analysis list:
8
+ *
9
+ * - **Root module** — every manifest is local; returned unchanged. The root's
10
+ * internals (CEL / schema / refs) are validated in full.
11
+ * - **Imported module** — only `Telo.Definition` / `Telo.Abstract` /
12
+ * `Telo.Import` docs cross unconditionally, plus the instances named in the
13
+ * module's `exports.resources` (stamped `metadata.forwardedExport: true`).
14
+ * The module doc and internal (unexported) instances are dropped — they
15
+ * belong to that module's own analysis pass.
16
+ *
17
+ * `forwardedExport` marks an instance as a cross-module resolution TARGET only
18
+ * (keyed by `metadata.module`), so `resolveRefSentinels` files it under
19
+ * `byModuleName` and `!ref Alias.name` resolves, while `validate-references` /
20
+ * the per-resource validation loop never re-walk or re-validate it against the
21
+ * consumer's scope. A consumer that instead emits every module doc as a peer
22
+ * local manifest silently breaks both. */
23
+ export declare function selectModuleManifestsForAnalysis(moduleManifests: ResourceManifest[], isRoot: boolean): ResourceManifest[];
3
24
  /** Produce the flat manifest list `analyze()` consumes today.
4
25
  *
5
26
  * Combines the entry module's manifests with `Telo.Definition`,
6
- * `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries.
27
+ * `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries
28
+ * (plus their `exports.resources` instances) via `selectModuleManifestsForAnalysis`.
7
29
  * Stamps three flavours of metadata along the way:
8
30
  *
9
31
  * - `metadata.source` and `metadata.sourceLine` — already on each manifest
@@ -1 +1 @@
1
- {"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;;;;;;;;;;;;;;;;;6EAkB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CA6EzE;AAED;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
1
+ {"version":3,"file":"flatten-for-analyzer.d.ts","sourceRoot":"","sources":["../src/flatten-for-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAc,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG/E;;;;;;;;;;;;;;;;;;;2CAmB2C;AAC3C,wBAAgB,gCAAgC,CAC9C,eAAe,EAAE,gBAAgB,EAAE,EACnC,MAAM,EAAE,OAAO,GACd,gBAAgB,EAAE,CAwBpB;AAED;;;;;;;;;;;;;;;;;;;6EAmB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CA+CzE;AAED;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
@@ -1,8 +1,50 @@
1
1
  import { isModuleKind } from "./module-kinds.js";
2
+ /** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
3
+ * CLI / kernel loader path) and the telo-editor's workspace projection so the
4
+ * two cannot drift. Given one module's stamped manifests and whether that
5
+ * module is the analysis entry (root), returns the manifests that cross into
6
+ * the consumer's flat analysis list:
7
+ *
8
+ * - **Root module** — every manifest is local; returned unchanged. The root's
9
+ * internals (CEL / schema / refs) are validated in full.
10
+ * - **Imported module** — only `Telo.Definition` / `Telo.Abstract` /
11
+ * `Telo.Import` docs cross unconditionally, plus the instances named in the
12
+ * module's `exports.resources` (stamped `metadata.forwardedExport: true`).
13
+ * The module doc and internal (unexported) instances are dropped — they
14
+ * belong to that module's own analysis pass.
15
+ *
16
+ * `forwardedExport` marks an instance as a cross-module resolution TARGET only
17
+ * (keyed by `metadata.module`), so `resolveRefSentinels` files it under
18
+ * `byModuleName` and `!ref Alias.name` resolves, while `validate-references` /
19
+ * the per-resource validation loop never re-walk or re-validate it against the
20
+ * consumer's scope. A consumer that instead emits every module doc as a peer
21
+ * local manifest silently breaks both. */
22
+ export function selectModuleManifestsForAnalysis(moduleManifests, isRoot) {
23
+ if (isRoot)
24
+ return moduleManifests;
25
+ const libDoc = moduleManifests.find((m) => isModuleKind(m.kind));
26
+ const exportedResources = new Set(libDoc?.exports?.resources ?? []);
27
+ const out = [];
28
+ for (const m of moduleManifests) {
29
+ if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract" || m.kind === "Telo.Import") {
30
+ out.push(m);
31
+ }
32
+ else if (!isModuleKind(m.kind) &&
33
+ typeof m.metadata?.name === "string" &&
34
+ exportedResources.has(m.metadata.name)) {
35
+ out.push({
36
+ ...m,
37
+ metadata: { ...m.metadata, forwardedExport: true },
38
+ });
39
+ }
40
+ }
41
+ return out;
42
+ }
2
43
  /** Produce the flat manifest list `analyze()` consumes today.
3
44
  *
4
45
  * Combines the entry module's manifests with `Telo.Definition`,
5
- * `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries.
46
+ * `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries
47
+ * (plus their `exports.resources` instances) via `selectModuleManifestsForAnalysis`.
6
48
  * Stamps three flavours of metadata along the way:
7
49
  *
8
50
  * - `metadata.source` and `metadata.sourceLine` — already on each manifest
@@ -20,8 +62,7 @@ import { isModuleKind } from "./module-kinds.js";
20
62
  * callers look it up via `findPositions(graph, ...)` on the LoadedGraph. */
21
63
  export function flattenForAnalyzer(graph) {
22
64
  const result = [];
23
- const stampedEntry = collectModuleManifests(graph.entry);
24
- result.push(...stampedEntry);
65
+ result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(graph.entry), true));
25
66
  const seen = new Set([graph.rootSource]);
26
67
  const queue = [graph.rootSource];
27
68
  while (queue.length > 0) {
@@ -37,31 +78,7 @@ export function flattenForAnalyzer(graph) {
37
78
  const targetModule = graph.modules.get(edge.targetSource);
38
79
  if (!targetModule)
39
80
  continue;
40
- const stamped = collectModuleManifests(targetModule);
41
- const libDoc = stamped.find((m) => isModuleKind(m.kind));
42
- const exportedResources = new Set(libDoc?.exports?.resources ?? []);
43
- for (const m of stamped) {
44
- if (m.kind === "Telo.Definition" ||
45
- m.kind === "Telo.Abstract" ||
46
- m.kind === "Telo.Import") {
47
- result.push(m);
48
- }
49
- else if (!isModuleKind(m.kind) &&
50
- typeof m.metadata?.name === "string" &&
51
- exportedResources.has(m.metadata.name)) {
52
- // Forward instances listed in the library's `exports.resources` so the
53
- // consumer's analyzer can resolve, gate, kind-check, and draw topology edges
54
- // for cross-module `!ref Alias.name`. The gate IS this forwarding — only
55
- // exported names cross the boundary. `metadata.forwardedExport` marks them as
56
- // cross-module resolution targets only (keyed by `metadata.module`), so ref
57
- // resolution / validation treats them as targets, never as local sources to
58
- // re-validate or walk.
59
- result.push({
60
- ...m,
61
- metadata: { ...m.metadata, forwardedExport: true },
62
- });
63
- }
64
- }
81
+ result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(targetModule), false));
65
82
  }
66
83
  }
67
84
  // Stamp resolved import identity on every Telo.Import in the result by
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { AnalysisRegistry } from "./analysis-registry.js";
2
+ export type { RefFieldInfo } from "./analysis-registry.js";
2
3
  export { StaticAnalyzer } from "./analyzer.js";
3
4
  export type { GraphLoadError, ImportEdge, LoadedFile, LoadedGraph, LoadedModule, ParseError, } from "./loaded-types.js";
4
- export { flattenForAnalyzer, flattenLoadedModule } from "./flatten-for-analyzer.js";
5
+ export { flattenForAnalyzer, flattenLoadedModule, selectModuleManifestsForAnalysis, } from "./flatten-for-analyzer.js";
5
6
  export { visitManifest } from "./manifest-visitor.js";
6
7
  export type { CelSiteEvent, ManifestVisitor, RefSiteEvent, ResourceEnterEvent, ResourceExitEvent, ScopeBoundaryEvent, SchemaFromSiteEvent, VisitOptions, } from "./manifest-visitor.js";
7
8
  export { Loader } from "./manifest-loader.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,GACnC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EACR,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { AnalysisRegistry } from "./analysis-registry.js";
2
2
  export { StaticAnalyzer } from "./analyzer.js";
3
- export { flattenForAnalyzer, flattenLoadedModule } from "./flatten-for-analyzer.js";
3
+ export { flattenForAnalyzer, flattenLoadedModule, selectModuleManifestsForAnalysis, } from "./flatten-for-analyzer.js";
4
4
  export { visitManifest } from "./manifest-visitor.js";
5
5
  export { Loader } from "./manifest-loader.js";
6
6
  export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.16.1",
3
+ "version": "0.18.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.17.0"
51
+ "@telorun/sdk": "0.18.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
@@ -6,6 +6,25 @@ import { computeSuggestKind, computeValidUserFacingKinds } from "./kind-suggest.
6
6
  import { visitManifest as runVisitManifest, type ManifestVisitor } from "./manifest-visitor.js";
7
7
  import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
8
8
  import type { AnalysisContext } from "./types.js";
9
+ import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
10
+
11
+ /** One reference field declared by a resource's definition, derived purely from
12
+ * the schema field map (independent of whether the manifest fills it). Editor
13
+ * hosts render these as ports / adapters on a node. */
14
+ export interface RefFieldInfo {
15
+ /** Field-map path with `[]` / `{}` markers (e.g. `targets[]`,
16
+ * `routes[].handler`, `encoder`). */
17
+ path: string;
18
+ /** True when the path traverses at least one array. */
19
+ isArray: boolean;
20
+ /** Accepted `x-telo-ref` constraint strings (e.g. `telo#Runnable`). */
21
+ refs: string[];
22
+ /** Distinct capabilities the slot may target (`Telo.Runnable`,
23
+ * `Telo.Service`, `Telo.Provider`, …) — one per resolvable constraint. The
24
+ * first classifies the port (node-capability → edge, ambient → picker); the
25
+ * full set validates drag-to-wire endpoints. Empty when none resolve. */
26
+ capabilities: string[];
27
+ }
9
28
 
10
29
  /**
11
30
  * Accumulates type and alias knowledge for a running kernel or analysis session.
@@ -63,6 +82,79 @@ export class AnalysisRegistry {
63
82
  }
64
83
  }
65
84
 
85
+ /**
86
+ * Returns every reference field a resource's definition declares, with arity
87
+ * and the capability each slot targets — derived purely from the schema field
88
+ * map, so it lists slots even when the manifest leaves them empty. Editor
89
+ * hosts render these as node ports (drag-to-wire for node-capability targets,
90
+ * inline picker for ambient targets).
91
+ */
92
+ refFieldsForResource(resource: ResourceManifest): RefFieldInfo[] {
93
+ const fieldMap = this.defs.expandedFieldMapForResource(
94
+ resource,
95
+ this.aliases,
96
+ this.aliasesByModule,
97
+ );
98
+ if (!fieldMap) return [];
99
+ const out: RefFieldInfo[] = [];
100
+ for (const [path, entry] of fieldMap) {
101
+ if (!isRefEntry(entry)) continue;
102
+ out.push({
103
+ path,
104
+ isArray: entry.isArray,
105
+ refs: entry.refs,
106
+ capabilities: this.capabilitiesForRefs(entry.refs),
107
+ });
108
+ }
109
+ return out;
110
+ }
111
+
112
+ /** Base capability an `x-telo-ref` constraint targets. A definition's declared
113
+ * `capability` is always one of the base capabilities, so it wins — this
114
+ * resolves user-defined abstracts (e.g. `std/ai#Model`, declared
115
+ * `capability: Telo.Invocable`) to the capability instances satisfy, not the
116
+ * abstract kind. Builtin abstracts (`telo#Runnable`) carry no `capability`
117
+ * field — there the kind itself *is* the capability. Undefined when
118
+ * unresolvable. */
119
+ capabilityForRef(xTeloRef: string): string | undefined {
120
+ const kind = this.defs.resolveRef(xTeloRef);
121
+ if (!kind) return undefined;
122
+ const def = this.defs.resolve(kind);
123
+ if (!def) return undefined;
124
+ return def.capability ?? kind;
125
+ }
126
+
127
+ /** Resolves the JSON Schema for a kind's `invoke()` inputs, for editor hosts
128
+ * that render a typed inputs form. Two-layer fallback mirroring the analyzer's
129
+ * template inputs typing: the definition's own `inputType`, then the
130
+ * `extends`-declared abstract's `inputType`. Resolves the inline
131
+ * (`{ kind: Type.JsonSchema, schema }`) and raw-schema forms; a bare named
132
+ * type reference is left unresolved (returns undefined) so the caller can fall
133
+ * back to a freeform map. Undefined when the kind declares no input contract. */
134
+ inputTypeForKind(kind: string): Record<string, unknown> | undefined {
135
+ const def = this.resolveDefinition(kind);
136
+ if (!def) return undefined;
137
+ const own = resolveTypeFieldToSchema(def.inputType, []);
138
+ if (own) return own;
139
+ if (def.extends) {
140
+ const abstractDef = this.resolveDefinition(def.extends);
141
+ if (abstractDef) {
142
+ const inherited = resolveTypeFieldToSchema(abstractDef.inputType, []);
143
+ if (inherited) return inherited;
144
+ }
145
+ }
146
+ return undefined;
147
+ }
148
+
149
+ private capabilitiesForRefs(refs: string[]): string[] {
150
+ const out: string[] = [];
151
+ for (const ref of refs) {
152
+ const cap = this.capabilityForRef(ref);
153
+ if (cap && !out.includes(cap)) out.push(cap);
154
+ }
155
+ return out;
156
+ }
157
+
66
158
  /**
67
159
  * Walks a manifest's annotation sites (refs, scopes, schema-from, CEL) via
68
160
  * the shared manifest visitor, bound to this registry's definitions and
@@ -2,10 +2,60 @@ import type { ResourceManifest } from "@telorun/sdk";
2
2
  import type { LoadedFile, LoadedGraph, LoadedModule } from "./loaded-types.js";
3
3
  import { isModuleKind } from "./module-kinds.js";
4
4
 
5
+ /** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
6
+ * CLI / kernel loader path) and the telo-editor's workspace projection so the
7
+ * two cannot drift. Given one module's stamped manifests and whether that
8
+ * module is the analysis entry (root), returns the manifests that cross into
9
+ * the consumer's flat analysis list:
10
+ *
11
+ * - **Root module** — every manifest is local; returned unchanged. The root's
12
+ * internals (CEL / schema / refs) are validated in full.
13
+ * - **Imported module** — only `Telo.Definition` / `Telo.Abstract` /
14
+ * `Telo.Import` docs cross unconditionally, plus the instances named in the
15
+ * module's `exports.resources` (stamped `metadata.forwardedExport: true`).
16
+ * The module doc and internal (unexported) instances are dropped — they
17
+ * belong to that module's own analysis pass.
18
+ *
19
+ * `forwardedExport` marks an instance as a cross-module resolution TARGET only
20
+ * (keyed by `metadata.module`), so `resolveRefSentinels` files it under
21
+ * `byModuleName` and `!ref Alias.name` resolves, while `validate-references` /
22
+ * the per-resource validation loop never re-walk or re-validate it against the
23
+ * consumer's scope. A consumer that instead emits every module doc as a peer
24
+ * local manifest silently breaks both. */
25
+ export function selectModuleManifestsForAnalysis(
26
+ moduleManifests: ResourceManifest[],
27
+ isRoot: boolean,
28
+ ): ResourceManifest[] {
29
+ if (isRoot) return moduleManifests;
30
+
31
+ const libDoc = moduleManifests.find((m) => isModuleKind(m.kind));
32
+ const exportedResources = new Set<string>(
33
+ (libDoc as { exports?: { resources?: string[] } } | undefined)?.exports?.resources ?? [],
34
+ );
35
+
36
+ const out: ResourceManifest[] = [];
37
+ for (const m of moduleManifests) {
38
+ if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract" || m.kind === "Telo.Import") {
39
+ out.push(m);
40
+ } else if (
41
+ !isModuleKind(m.kind) &&
42
+ typeof m.metadata?.name === "string" &&
43
+ exportedResources.has(m.metadata.name as string)
44
+ ) {
45
+ out.push({
46
+ ...m,
47
+ metadata: { ...m.metadata, forwardedExport: true } as ResourceManifest["metadata"],
48
+ });
49
+ }
50
+ }
51
+ return out;
52
+ }
53
+
5
54
  /** Produce the flat manifest list `analyze()` consumes today.
6
55
  *
7
56
  * Combines the entry module's manifests with `Telo.Definition`,
8
- * `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries.
57
+ * `Telo.Abstract`, and `Telo.Import` docs forwarded from imported libraries
58
+ * (plus their `exports.resources` instances) via `selectModuleManifestsForAnalysis`.
9
59
  * Stamps three flavours of metadata along the way:
10
60
  *
11
61
  * - `metadata.source` and `metadata.sourceLine` — already on each manifest
@@ -24,8 +74,7 @@ import { isModuleKind } from "./module-kinds.js";
24
74
  export function flattenForAnalyzer(graph: LoadedGraph): ResourceManifest[] {
25
75
  const result: ResourceManifest[] = [];
26
76
 
27
- const stampedEntry = collectModuleManifests(graph.entry);
28
- result.push(...stampedEntry);
77
+ result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(graph.entry), true));
29
78
 
30
79
  const seen = new Set<string>([graph.rootSource]);
31
80
  const queue: string[] = [graph.rootSource];
@@ -43,36 +92,7 @@ export function flattenForAnalyzer(graph: LoadedGraph): ResourceManifest[] {
43
92
  const targetModule = graph.modules.get(edge.targetSource);
44
93
  if (!targetModule) continue;
45
94
 
46
- const stamped = collectModuleManifests(targetModule);
47
- const libDoc = stamped.find((m) => isModuleKind(m.kind));
48
- const exportedResources = new Set<string>(
49
- (libDoc as { exports?: { resources?: string[] } } | undefined)?.exports?.resources ?? [],
50
- );
51
- for (const m of stamped) {
52
- if (
53
- m.kind === "Telo.Definition" ||
54
- m.kind === "Telo.Abstract" ||
55
- m.kind === "Telo.Import"
56
- ) {
57
- result.push(m);
58
- } else if (
59
- !isModuleKind(m.kind) &&
60
- typeof m.metadata?.name === "string" &&
61
- exportedResources.has(m.metadata.name as string)
62
- ) {
63
- // Forward instances listed in the library's `exports.resources` so the
64
- // consumer's analyzer can resolve, gate, kind-check, and draw topology edges
65
- // for cross-module `!ref Alias.name`. The gate IS this forwarding — only
66
- // exported names cross the boundary. `metadata.forwardedExport` marks them as
67
- // cross-module resolution targets only (keyed by `metadata.module`), so ref
68
- // resolution / validation treats them as targets, never as local sources to
69
- // re-validate or walk.
70
- result.push({
71
- ...m,
72
- metadata: { ...m.metadata, forwardedExport: true } as ResourceManifest["metadata"],
73
- });
74
- }
75
- }
95
+ result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(targetModule), false));
76
96
  }
77
97
  }
78
98
 
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { AnalysisRegistry } from "./analysis-registry.js";
2
+ export type { RefFieldInfo } from "./analysis-registry.js";
2
3
  export { StaticAnalyzer } from "./analyzer.js";
3
4
  export type {
4
5
  GraphLoadError,
@@ -8,7 +9,11 @@ export type {
8
9
  LoadedModule,
9
10
  ParseError,
10
11
  } from "./loaded-types.js";
11
- export { flattenForAnalyzer, flattenLoadedModule } from "./flatten-for-analyzer.js";
12
+ export {
13
+ flattenForAnalyzer,
14
+ flattenLoadedModule,
15
+ selectModuleManifestsForAnalysis,
16
+ } from "./flatten-for-analyzer.js";
12
17
  export { visitManifest } from "./manifest-visitor.js";
13
18
  export type {
14
19
  CelSiteEvent,