@telorun/analyzer 0.22.0 → 0.23.1

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,8 +55,8 @@ 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.9.0
59
- Sql: std/sql@0.8.0
58
+ Http: std/http-server@0.11.0
59
+ Sql: std/sql@0.9.0
60
60
  targets:
61
61
  - !ref Migrations
62
62
  - !ref Server
@@ -3,7 +3,14 @@
3
3
  export declare class AliasResolver {
4
4
  private readonly importAliases;
5
5
  private readonly importedKinds;
6
+ /** `${alias}.${suffix}` → canonical `<owningModule>.<Kind>` for kinds an import
7
+ * transitively RE-EXPORTS (`exports.kinds: [Alias.Kind]`), which don't live in the
8
+ * import's own module. Resolved before the normal `<module>.<suffix>` construction. */
9
+ private readonly reExportedKinds;
6
10
  registerImport(alias: string, targetModule: string, exportedKinds: string[]): void;
11
+ /** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
12
+ * (owned by a module the alias's target imports, possibly several hops away). */
13
+ registerKindReExport(alias: string, suffix: string, canonicalKind: string): void;
7
14
  /** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
8
15
  * Used to resolve an alias-qualified instance reference "Console.writeLine" to the
9
16
  * forwarded resource declared in that module. The `exports.resources` gate is enforced
@@ -19,4 +26,14 @@ export declare class AliasResolver {
19
26
  * back into its user-facing alias form (e.g. "Http.Server"). */
20
27
  aliasesFor(targetModule: string): string[];
21
28
  }
29
+ /**
30
+ * The alias resolver for a resource's own lexical scope. A resource that
31
+ * originated in an imported library (its `ownModule` names a non-root module —
32
+ * e.g. an inline handler extracted from an imported Http.Api) resolves its kind
33
+ * aliases against THAT library's import map, so an anonymous child inherits the
34
+ * lexical scope of the document that declares it. Returns undefined for
35
+ * root/consumer-owned resources (and unknown modules), so callers fall back to
36
+ * the root `aliases`.
37
+ */
38
+ export declare function scopeResolverForModule(ownModule: string | undefined, rootModules: Set<string>, aliasesByModule: Map<string, AliasResolver>): AliasResolver | undefined;
22
39
  //# sourceMappingURL=alias-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"alias-resolver.d.ts","sourceRoot":"","sources":["../src/alias-resolver.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF;;;;qDAIiD;IACjD,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAe7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C"}
1
+ {"version":3,"file":"alias-resolver.d.ts","sourceRoot":"","sources":["../src/alias-resolver.ts"],"names":[],"mappings":"AAAA;gFACgF;AAChF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAChE;;4FAEwF;IACxF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI;IAOlF;sFACkF;IAClF,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAIhF;;;;qDAIiD;IACjD,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIjD,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAmB7C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIhC,YAAY,IAAI,MAAM,EAAE;IAIxB;;qEAEiE;IACjE,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE;CAO3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,aAAa,GAAG,SAAS,CAI3B"}
@@ -3,12 +3,21 @@
3
3
  export class AliasResolver {
4
4
  importAliases = new Map();
5
5
  importedKinds = new Map();
6
+ /** `${alias}.${suffix}` → canonical `<owningModule>.<Kind>` for kinds an import
7
+ * transitively RE-EXPORTS (`exports.kinds: [Alias.Kind]`), which don't live in the
8
+ * import's own module. Resolved before the normal `<module>.<suffix>` construction. */
9
+ reExportedKinds = new Map();
6
10
  registerImport(alias, targetModule, exportedKinds) {
7
11
  this.importAliases.set(alias, targetModule);
8
12
  if (exportedKinds.length > 0) {
9
13
  this.importedKinds.set(alias, new Set(exportedKinds));
10
14
  }
11
15
  }
16
+ /** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
17
+ * (owned by a module the alias's target imports, possibly several hops away). */
18
+ registerKindReExport(alias, suffix, canonicalKind) {
19
+ this.reExportedKinds.set(`${alias}.${suffix}`, canonicalKind);
20
+ }
12
21
  /** Real module name an alias points at (e.g. "Console" → "console"), or undefined.
13
22
  * Used to resolve an alias-qualified instance reference "Console.writeLine" to the
14
23
  * forwarded resource declared in that module. The `exports.resources` gate is enforced
@@ -27,6 +36,11 @@ export class AliasResolver {
27
36
  return undefined;
28
37
  const prefix = kind.slice(0, dot);
29
38
  const suffix = kind.slice(dot + 1);
39
+ // Re-export takes precedence: a re-exported kind resolves to its true owning module,
40
+ // not `${prefix-target}.${suffix}` (and bypasses the gate — it's explicitly re-exported).
41
+ const reExported = this.reExportedKinds.get(`${prefix}.${suffix}`);
42
+ if (reExported)
43
+ return reExported;
30
44
  const realModule = this.importAliases.get(prefix);
31
45
  if (!realModule)
32
46
  return undefined;
@@ -53,3 +67,17 @@ export class AliasResolver {
53
67
  return result;
54
68
  }
55
69
  }
70
+ /**
71
+ * The alias resolver for a resource's own lexical scope. A resource that
72
+ * originated in an imported library (its `ownModule` names a non-root module —
73
+ * e.g. an inline handler extracted from an imported Http.Api) resolves its kind
74
+ * aliases against THAT library's import map, so an anonymous child inherits the
75
+ * lexical scope of the document that declares it. Returns undefined for
76
+ * root/consumer-owned resources (and unknown modules), so callers fall back to
77
+ * the root `aliases`.
78
+ */
79
+ export function scopeResolverForModule(ownModule, rootModules, aliasesByModule) {
80
+ return ownModule && !rootModules.has(ownModule)
81
+ ? aliasesByModule.get(ownModule)
82
+ : undefined;
83
+ }
@@ -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;AA4hB/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;IAkqBvB,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;IAerB,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,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA4hB/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;IAstBvB,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;IAerB,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
@@ -1,6 +1,6 @@
1
1
  import { defaultRegistry, isTaggedSentinel } from "@telorun/templating";
2
- import { AliasResolver } from "./alias-resolver.js";
3
- import { buildCelEnvironment, buildTypedCelEnvironment, } from "./cel-environment.js";
2
+ import { AliasResolver, scopeResolverForModule } from "./alias-resolver.js";
3
+ import { buildCelEnvironment, buildImportInputCelEnvironment, buildTypedCelEnvironment, } from "./cel-environment.js";
4
4
  import { DefinitionRegistry } from "./definition-registry.js";
5
5
  import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
6
6
  import { buildKernelGlobalsSchema, mergeKernelGlobalsIntoContext } from "./kernel-globals.js";
@@ -579,17 +579,18 @@ export class StaticAnalyzer {
579
579
  if (resolvedModuleName) {
580
580
  defs.registerModuleIdentity(resolvedNamespace ?? null, resolvedModuleName);
581
581
  }
582
+ // `metadata.reExportedKinds` (stamped by flattenForAnalyzer / the editor projection)
583
+ // maps an exported suffix to the true owning module's canonical kind for kinds this
584
+ // import transitively re-exports (`exports.kinds: [Alias.Kind]`).
585
+ const reExportedKinds = (m.metadata?.reExportedKinds ?? {});
582
586
  // Alias registration is scoped: consumer imports vs. imported-library imports.
583
- if (!ownModule || rootModules.has(ownModule)) {
584
- aliases.registerImport(alias, targetModule, exportedKinds);
585
- }
586
- else {
587
- let libResolver = aliasesByModule.get(ownModule);
588
- if (!libResolver) {
589
- libResolver = new AliasResolver();
590
- aliasesByModule.set(ownModule, libResolver);
591
- }
592
- libResolver.registerImport(alias, targetModule, exportedKinds);
587
+ const resolver = !ownModule || rootModules.has(ownModule)
588
+ ? aliases
589
+ : (aliasesByModule.get(ownModule) ??
590
+ aliasesByModule.set(ownModule, new AliasResolver()).get(ownModule));
591
+ resolver.registerImport(alias, targetModule, exportedKinds);
592
+ for (const [suffix, canonical] of Object.entries(reExportedKinds)) {
593
+ resolver.registerKindReExport(alias, suffix, canonical);
593
594
  }
594
595
  }
595
596
  }
@@ -735,6 +736,26 @@ export class StaticAnalyzer {
735
736
  }
736
737
  }
737
738
  }
739
+ // `exports.resources` entries are plain names: `Db` (local) or `Alias.Name` (re-export),
740
+ // mirroring `exports.kinds`. The `!ref` tag is not accepted here — a `!ref` parses to a
741
+ // sentinel object that the schema's CEL/ref exemption would silently pass, so reject any
742
+ // non-string entry with an actionable message instead.
743
+ const exportsResources = m.exports?.resources;
744
+ if (Array.isArray(exportsResources)) {
745
+ for (let i = 0; i < exportsResources.length; i++) {
746
+ if (typeof exportsResources[i] === "string")
747
+ continue;
748
+ diagnostics.push({
749
+ severity: DiagnosticSeverity.Error,
750
+ code: "INVALID_EXPORT",
751
+ source: SOURCE,
752
+ message: `Telo.Library exports.resources[${i}]: write the exported name as a plain string — ` +
753
+ `'Name' to export a local instance, or 'Alias.Name' to re-export an imported one. ` +
754
+ `The '!ref' tag is not allowed in exports.resources.`,
755
+ data: { resource, filePath, path: `exports.resources.${i}` },
756
+ });
757
+ }
758
+ }
738
759
  }
739
760
  // Build typed kernel globals schema so x-telo-context chain validation
740
761
  // recognises variables, secrets, resources, env automatically
@@ -775,8 +796,15 @@ export class StaticAnalyzer {
775
796
  const resource = { kind: m.kind, name: m.metadata?.name };
776
797
  // Resolve kind through alias if needed; direct lookup takes priority so that
777
798
  // aliases whose name matches the module name (the common case) work without
778
- // path-derived name mangling.
779
- const resolvedKind = aliases.resolveKind(m.kind);
799
+ // path-derived name mangling. A resource that originated in an imported library
800
+ // (its `metadata.module` names a non-root module — e.g. an inline route handler
801
+ // extracted from an imported Http.Api) must resolve its kind alias against THAT
802
+ // library's import map, not the consumer's; an anonymous child inherits the
803
+ // lexical scope of the document that declares it. Mirrors the nested-inline and
804
+ // reference-resolution paths: own-module scope first, root/consumer aliases last.
805
+ const ownModule = m.metadata?.module;
806
+ const scopeResolver = scopeResolverForModule(ownModule, rootModules, aliasesByModule);
807
+ const resolvedKind = scopeResolver?.resolveKind(m.kind) ?? aliases.resolveKind(m.kind);
780
808
  const definition = defs.resolve(m.kind) ?? (resolvedKind ? defs.resolve(resolvedKind) : undefined);
781
809
  if (!definition) {
782
810
  const suggestedKind = computeSuggestKind(m.kind, aliases, defs);
@@ -804,8 +832,22 @@ export class StaticAnalyzer {
804
832
  },
805
833
  }
806
834
  : definition.schema;
807
- // Phase 1: CEL type checking — walk data+schema together, check env.check() return types
808
- const baseTypedEnv = buildTypedCelEnvironment(this.celEnv, m, undefined, moduleManifest);
835
+ // Phase 1: CEL type checking — walk data+schema together, check env.check() return types.
836
+ // A Telo.Import's variables/secrets are a config-only contract evaluated against the
837
+ // IMPORTING module's scope, so type them from the owning module doc (matched by
838
+ // `metadata.module`) and drop `resources`/`env` so referencing them is an error. A
839
+ // library's own internal import is validated against that library in the library's
840
+ // standalone analysis; in this flattened app pass the library doc is absent, so the
841
+ // importer is undefined here and variables/secrets fall back to a permissive `map`
842
+ // (no false positives) while resources/env stay rejected.
843
+ const importerModule = m.kind === "Telo.Import"
844
+ ? allManifests.find((mm) => (mm.kind === "Telo.Application" || mm.kind === "Telo.Library") &&
845
+ mm.metadata?.name ===
846
+ m.metadata?.module)
847
+ : undefined;
848
+ const baseTypedEnv = m.kind === "Telo.Import"
849
+ ? buildImportInputCelEnvironment(this.celEnv, importerModule)
850
+ : buildTypedCelEnvironment(this.celEnv, m, undefined, moduleManifest);
809
851
  const celIssues = collectCelTypeIssues(m, schema, "", definition, m, baseTypedEnv, this.celEnv, moduleManifest);
810
852
  // Phase 2+3: AJV on substituted data — CEL fields replaced with typed placeholders
811
853
  const ajvIssues = validateAgainstSchema(substituteCelFields(m, schema), schema);
@@ -830,9 +872,8 @@ export class StaticAnalyzer {
830
872
  // first, then the parent module's own aliases (for resources declared
831
873
  // inside an imported module), then the root aliases. Mirrors how the
832
874
  // analyzer resolves kinds elsewhere so module-scoped aliases don't
833
- // produce false UNDEFINED_KIND diagnostics.
834
- const ownModule = m.metadata?.module;
835
- const scopeResolver = ownModule && !rootModules.has(ownModule) ? aliasesByModule.get(ownModule) : undefined;
875
+ // produce false UNDEFINED_KIND diagnostics. `scopeResolver` is the
876
+ // owning module's resolver computed above.
836
877
  diagnostics.push(...validateNestedInlineResources(m, definition.schema, (kind) => {
837
878
  const direct = defs.resolve(kind);
838
879
  if (direct)
@@ -1047,7 +1088,7 @@ export class StaticAnalyzer {
1047
1088
  // Validate provider coherence rules for `provide:` template-target definitions.
1048
1089
  diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
1049
1090
  // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
1050
- diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv));
1091
+ diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules));
1051
1092
  // Warn about declared variables / secrets / ports that no CEL references.
1052
1093
  diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
1053
1094
  // Reroute diagnostics on synthetic (inline-extracted) resources back to
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAqd/C,CAAC"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAud/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -453,8 +453,10 @@ export const KERNEL_BUILTINS = [
453
453
  type: "object",
454
454
  properties: {
455
455
  kinds: { type: "array", items: { type: "string" } },
456
- // `variables` / `secrets` are reserved on the resources.<Alias> value-flow
457
- // surface, so a library may not export instances under those names.
456
+ // An entry is a bare name (`Db`, a locally-owned export) or a dotted `Alias.Name`
457
+ // (re-export of the instance reached via this library's import aliased `Alias`,
458
+ // under the name `Name`) — mirroring `exports.kinds`. `variables` / `secrets` are
459
+ // reserved on the resources.<Alias> value-flow surface, so they may not be exported.
458
460
  resources: {
459
461
  type: "array",
460
462
  items: { type: "string", not: { enum: ["variables", "secrets"] } },
@@ -13,4 +13,12 @@ export type { CelHandlers } from "@telorun/templating";
13
13
  * NOTE: The set of kernel globals registered here must match `KERNEL_GLOBAL_NAMES`
14
14
  * in kernel-globals.ts, which is used for chain-access validation. */
15
15
  export declare function buildTypedCelEnvironment(baseEnv: Environment, manifest: ResourceManifest, extraContextSchema?: Record<string, any> | null, rootModuleManifest?: ResourceManifest): Environment;
16
+ /** CEL environment for the `variables:`/`secrets:` expressions on a `Telo.Import`.
17
+ *
18
+ * Import inputs are a config-only contract: their expressions are evaluated
19
+ * against the IMPORTING module's `variables`/`secrets`, never the import's own
20
+ * values map (the bug) nor the imported child's. `resources`, `env`, and `ports`
21
+ * are registered as empty typed objects, so referencing them is a "No such key"
22
+ * error that steers authors to a typed `variables` entry. */
23
+ export declare function buildImportInputCelEnvironment(baseEnv: Environment, moduleManifest: ResourceManifest | undefined): Environment;
16
24
  //# sourceMappingURL=cel-environment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;uEASuE;AACvE,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAI/C,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,WAAW,CAuEb"}
1
+ {"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;uEASuE;AACvE,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAI/C,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,WAAW,CAuEb;AAuBD;;;;;;8DAM8D;AAC9D,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,gBAAgB,GAAG,SAAS,GAC3C,WAAW,CAwBb"}
@@ -85,3 +85,51 @@ rootModuleManifest) {
85
85
  return baseEnv.clone();
86
86
  }
87
87
  }
88
+ /** Register a `variables`/`secrets` namespace typed from a module doc's schema map
89
+ * (`{ name: <schema>, … }`), falling back to dyn `map` when absent or untyped. */
90
+ function registerConfigNamespace(env, block, name) {
91
+ if (block !== null && typeof block === "object" && !Array.isArray(block)) {
92
+ const entries = Object.entries(block).filter(([, v]) => v !== null && typeof v === "object" && !Array.isArray(v));
93
+ if (entries.length > 0) {
94
+ const schema = {};
95
+ for (const [k, v] of entries)
96
+ schema[k] = jsonSchemaToCelType(v);
97
+ env.registerVariable({ name, schema });
98
+ return;
99
+ }
100
+ }
101
+ env.registerVariable(name, "map");
102
+ }
103
+ /** CEL environment for the `variables:`/`secrets:` expressions on a `Telo.Import`.
104
+ *
105
+ * Import inputs are a config-only contract: their expressions are evaluated
106
+ * against the IMPORTING module's `variables`/`secrets`, never the import's own
107
+ * values map (the bug) nor the imported child's. `resources`, `env`, and `ports`
108
+ * are registered as empty typed objects, so referencing them is a "No such key"
109
+ * error that steers authors to a typed `variables` entry. */
110
+ export function buildImportInputCelEnvironment(baseEnv, moduleManifest) {
111
+ const env = baseEnv.clone();
112
+ for (const brand of Object.keys(VALUE_BRAND_BASE)) {
113
+ env.registerType(brand, { fields: {} });
114
+ }
115
+ const mod = moduleManifest;
116
+ // Typing variables/secrets from the importer's schema can fail on a malformed
117
+ // schema; degrade those to permissive `map` if so — but never lose the
118
+ // resources/env/ports rejection registered below (the catch is scoped so a
119
+ // typing failure can't silently re-open the config-only contract).
120
+ try {
121
+ registerConfigNamespace(env, mod?.variables, "variables");
122
+ registerConfigNamespace(env, mod?.secrets, "secrets");
123
+ }
124
+ catch {
125
+ env.registerVariable("variables", "map");
126
+ env.registerVariable("secrets", "map");
127
+ }
128
+ // Override the base env's dyn `resources`/`env`/`ports` with empty typed objects
129
+ // so any access (`resources.X`, `env.X`) is a "No such key" error — these
130
+ // surfaces are not part of the config-only import contract.
131
+ for (const name of ["resources", "env", "ports"]) {
132
+ env.registerVariable({ name, schema: {} });
133
+ }
134
+ return env;
135
+ }
@@ -1,5 +1,18 @@
1
1
  import type { ResourceManifest } from "@telorun/sdk";
2
2
  import type { LoadedGraph, LoadedModule } from "./loaded-types.js";
3
+ /** One parsed `exports.resources` / `exports.kinds` entry. `name` is the exported
4
+ * instance name or kind suffix (the part after the dot, or the whole entry); `alias`
5
+ * (when set) is this library's own import the entry RE-EXPORTS from. */
6
+ export interface ParsedExportEntry {
7
+ name: string;
8
+ alias?: string;
9
+ }
10
+ /** Parse a single dotted export entry: `Alias.Name` → `{name: "Name", alias: "Alias"}`,
11
+ * bare `Name` → `{name: "Name"}`. The single grammar for `exports.resources` and
12
+ * `exports.kinds`, shared by the kernel's import controller and the analyzer/editor so
13
+ * the dotted-name split can't drift. A leading dot (`.Name`) has no alias by design —
14
+ * the empty prefix isn't a valid alias. */
15
+ export declare function parseExportEntry(entry: string): ParsedExportEntry;
3
16
  /** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
4
17
  * CLI / kernel loader path) and the telo-editor's workspace projection so the
5
18
  * two cannot drift. Given one module's stamped manifests and whether that
@@ -42,6 +55,45 @@ export declare function selectModuleManifestsForAnalysis(moduleManifests: Resour
42
55
  * Position metadata (`positionIndex`) is NOT stamped on manifests —
43
56
  * callers look it up via `findPositions(graph, ...)` on the LoadedGraph. */
44
57
  export declare function flattenForAnalyzer(graph: LoadedGraph): ResourceManifest[];
58
+ /** A re-export declared in a library's `exports.resources` as a dotted `Alias.Name`:
59
+ * module `module` re-exports the instance `name` reached through its own import
60
+ * aliased `alias`. */
61
+ export interface ReExportSpec {
62
+ module: string;
63
+ alias: string;
64
+ name: string;
65
+ }
66
+ /** Extract re-export specs from a library doc's `exports.resources` — the dotted `Alias.Name`
67
+ * entries (bare-name locals are forwarded by the BFS instead). Shared by the CLI graph path
68
+ * and the editor's workspace projection so the two cannot drift. */
69
+ export declare function reExportSpecsFromExports(moduleName: string, exportsResources: readonly unknown[] | undefined): ReExportSpec[];
70
+ /** Forward re-exported instances (`exports.resources: [!ref Alias.name]`) transitively so a
71
+ * consumer's `!ref Consumer.name` resolves in `resolveRefSentinels` (keyed by the RE-EXPORTING
72
+ * module). The owning instance is already forwarded under its own module; here we emit an
73
+ * additional copy stamped under each re-exporting module, with an already-canonical kind. A
74
+ * fixpoint loop forwards chains of arbitrary depth (`app → api → domain → …`): each pass can
75
+ * resolve a re-export whose source was emitted in a prior pass. Graph-agnostic: `aliasToModule`
76
+ * maps `(module, alias)` to the imported module's name. Mutates `result` in place. */
77
+ export declare function forwardReExportManifests(result: ResourceManifest[], specs: readonly ReExportSpec[], aliasToModule: (module: string, alias: string) => string | undefined): void;
78
+ /** Resolve every library's `exports.kinds` to a per-module map `suffix → canonical
79
+ * <owningModule>.<Kind>`, following re-exports (`Alias.Kind`) transitively via a fixpoint.
80
+ * `modules` lists each library's name + its raw `exports.kinds`; `aliasToModule(module, alias)`
81
+ * maps one of that module's import aliases to the imported module's name. Graph-agnostic —
82
+ * shared by the CLI graph path and the editor's workspace projection. */
83
+ export declare function resolveExportedKinds(modules: ReadonlyArray<{
84
+ module: string;
85
+ exportsKinds: readonly string[];
86
+ }>, aliasToModule: (module: string, alias: string) => string | undefined): Map<string, Map<string, string>>;
87
+ /** Stamp `metadata.reExportedKinds` (suffix → canonical kind) onto every `Telo.Import` whose
88
+ * target re-exports kinds, so the analyzer can register the re-export mappings. Only entries
89
+ * that point at a module OTHER than the import's own target are stamped (genuine re-exports;
90
+ * a locally-defined kind resolves through the normal alias path). Stamped on `metadata` (which
91
+ * permits additional properties, like `resolvedModuleName`) since the `Telo.Import` schema
92
+ * forbids extra top-level fields. Shared by both paths. */
93
+ export declare function stampReExportedKinds(imports: ReadonlyArray<{
94
+ manifest: ResourceManifest;
95
+ targetModule: string;
96
+ }>, exportedKinds: Map<string, Map<string, string>>): void;
45
97
  /** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
46
98
  * with `metadata.module` stamped on non-module docs. The kernel's runtime
47
99
  * entry load uses this to convert a `Loader.loadModule` result into the
@@ -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;;;;;;;;;;;;;;;;;;;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
+ {"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,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAInE;;yEAEyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;4CAI4C;AAC5C,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAGjE;AAED;;;;;;;;;;;;;;;;;;;2CAmB2C;AAC3C,wBAAgB,gCAAgC,CAC9C,eAAe,EAAE,gBAAgB,EAAE,EACnC,MAAM,EAAE,OAAO,GACd,gBAAgB,EAAE,CA6BpB;AAED;;;;;;;;;;;;;;;;;;;6EAmB6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAiDzE;AAED;;uBAEuB;AACvB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;qEAEqE;AACrE,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GAC/C,YAAY,EAAE,CAShB;AAED;;;;;;uFAMuF;AACvF,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnE,IAAI,CAgDN;AAED;;;;0EAI0E;AAC1E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC,EAC3E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA8BlC;AAED;;;;;4DAK4D;AAC5D,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,CAAC;IAAE,QAAQ,EAAE,gBAAgB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,EAC5E,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,IAAI,CAWN;AAwCD;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
@@ -1,4 +1,13 @@
1
1
  import { isModuleKind } from "./module-kinds.js";
2
+ /** Parse a single dotted export entry: `Alias.Name` → `{name: "Name", alias: "Alias"}`,
3
+ * bare `Name` → `{name: "Name"}`. The single grammar for `exports.resources` and
4
+ * `exports.kinds`, shared by the kernel's import controller and the analyzer/editor so
5
+ * the dotted-name split can't drift. A leading dot (`.Name`) has no alias by design —
6
+ * the empty prefix isn't a valid alias. */
7
+ export function parseExportEntry(entry) {
8
+ const dot = entry.indexOf(".");
9
+ return dot > 0 ? { name: entry.slice(dot + 1), alias: entry.slice(0, dot) } : { name: entry };
10
+ }
2
11
  /** The import-boundary forwarding rule, shared by `flattenForAnalyzer` (the
3
12
  * CLI / kernel loader path) and the telo-editor's workspace projection so the
4
13
  * two cannot drift. Given one module's stamped manifests and whether that
@@ -23,7 +32,15 @@ export function selectModuleManifestsForAnalysis(moduleManifests, isRoot) {
23
32
  if (isRoot)
24
33
  return moduleManifests;
25
34
  const libDoc = moduleManifests.find((m) => isModuleKind(m.kind));
26
- const exportedResources = new Set(libDoc?.exports?.resources ?? []);
35
+ // An `exports.resources` entry is a bare name or a dotted `Alias.Name` (re-export). Only the
36
+ // export NAME matches a local instance below; re-exports are forwarded by `forwardReExports`.
37
+ const exportedResources = new Set();
38
+ for (const entry of libDoc?.exports
39
+ ?.resources ?? []) {
40
+ if (typeof entry !== "string")
41
+ continue;
42
+ exportedResources.add(parseExportEntry(entry).name);
43
+ }
27
44
  const out = [];
28
45
  for (const m of moduleManifests) {
29
46
  if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract" || m.kind === "Telo.Import") {
@@ -81,6 +98,7 @@ export function flattenForAnalyzer(graph) {
81
98
  result.push(...selectModuleManifestsForAnalysis(collectModuleManifests(targetModule), false));
82
99
  }
83
100
  }
101
+ forwardReExports(graph, result);
84
102
  // Stamp resolved import identity on every Telo.Import in the result by
85
103
  // reading the edge's pre-resolved name/namespace — no re-derivation from
86
104
  // manifest metadata. The edge is keyed by (owner-file, alias) which is
@@ -105,6 +123,179 @@ export function flattenForAnalyzer(graph) {
105
123
  }
106
124
  return result;
107
125
  }
126
+ /** Extract re-export specs from a library doc's `exports.resources` — the dotted `Alias.Name`
127
+ * entries (bare-name locals are forwarded by the BFS instead). Shared by the CLI graph path
128
+ * and the editor's workspace projection so the two cannot drift. */
129
+ export function reExportSpecsFromExports(moduleName, exportsResources) {
130
+ const specs = [];
131
+ for (const entry of exportsResources ?? []) {
132
+ if (typeof entry !== "string")
133
+ continue;
134
+ const { name, alias } = parseExportEntry(entry);
135
+ if (!alias || alias === "Self")
136
+ continue;
137
+ specs.push({ module: moduleName, alias, name });
138
+ }
139
+ return specs;
140
+ }
141
+ /** Forward re-exported instances (`exports.resources: [!ref Alias.name]`) transitively so a
142
+ * consumer's `!ref Consumer.name` resolves in `resolveRefSentinels` (keyed by the RE-EXPORTING
143
+ * module). The owning instance is already forwarded under its own module; here we emit an
144
+ * additional copy stamped under each re-exporting module, with an already-canonical kind. A
145
+ * fixpoint loop forwards chains of arbitrary depth (`app → api → domain → …`): each pass can
146
+ * resolve a re-export whose source was emitted in a prior pass. Graph-agnostic: `aliasToModule`
147
+ * maps `(module, alias)` to the imported module's name. Mutates `result` in place. */
148
+ export function forwardReExportManifests(result, specs, aliasToModule) {
149
+ // Index forwarded instances by `module\0name` (only re-export TARGETS are forwarded).
150
+ const forwarded = new Map();
151
+ for (const m of result) {
152
+ const meta = m.metadata;
153
+ if (meta?.forwardedExport && meta.module && meta.name) {
154
+ forwarded.set(`${meta.module}\0${meta.name}`, m);
155
+ }
156
+ }
157
+ // Canonicalize an authored/forwarded kind to a scope-independent `<module>.<Kind>` using the
158
+ // owning module's own import aliases. Idempotent: an already-canonical kind whose prefix isn't
159
+ // an alias of `ownerModule` is returned unchanged, so re-exports of re-exports stay stable.
160
+ const canonicalKind = (kind, ownerModule) => {
161
+ if (kind.startsWith("Self."))
162
+ return `${ownerModule}.${kind.slice("Self.".length)}`;
163
+ const dot = kind.indexOf(".");
164
+ if (dot <= 0)
165
+ return kind;
166
+ const target = aliasToModule(ownerModule, kind.slice(0, dot));
167
+ return target ? `${target}.${kind.slice(dot + 1)}` : kind;
168
+ };
169
+ // Fixpoint — bounded by the number of specs (each can be satisfied at most once).
170
+ for (let pass = 0; pass <= specs.length; pass++) {
171
+ let added = false;
172
+ for (const spec of specs) {
173
+ const key = `${spec.module}\0${spec.name}`;
174
+ if (forwarded.has(key))
175
+ continue;
176
+ const sourceModule = aliasToModule(spec.module, spec.alias);
177
+ if (!sourceModule)
178
+ continue;
179
+ const src = forwarded.get(`${sourceModule}\0${spec.name}`);
180
+ if (!src)
181
+ continue; // source not forwarded yet — a later pass may satisfy it
182
+ const kind = canonicalKind(src.kind, sourceModule);
183
+ const manifest = {
184
+ ...src,
185
+ kind,
186
+ metadata: {
187
+ ...src.metadata,
188
+ name: spec.name,
189
+ module: spec.module,
190
+ forwardedExport: true,
191
+ },
192
+ };
193
+ result.push(manifest);
194
+ forwarded.set(key, manifest);
195
+ added = true;
196
+ }
197
+ if (!added)
198
+ break;
199
+ }
200
+ }
201
+ /** Resolve every library's `exports.kinds` to a per-module map `suffix → canonical
202
+ * <owningModule>.<Kind>`, following re-exports (`Alias.Kind`) transitively via a fixpoint.
203
+ * `modules` lists each library's name + its raw `exports.kinds`; `aliasToModule(module, alias)`
204
+ * maps one of that module's import aliases to the imported module's name. Graph-agnostic —
205
+ * shared by the CLI graph path and the editor's workspace projection. */
206
+ export function resolveExportedKinds(modules, aliasToModule) {
207
+ const out = new Map();
208
+ const tableFor = (m) => {
209
+ let t = out.get(m);
210
+ if (!t)
211
+ out.set(m, (t = new Map()));
212
+ return t;
213
+ };
214
+ for (let pass = 0; pass <= modules.length; pass++) {
215
+ let changed = false;
216
+ for (const { module, exportsKinds } of modules) {
217
+ const table = tableFor(module);
218
+ for (const entry of exportsKinds) {
219
+ const { name: suffix, alias } = parseExportEntry(entry);
220
+ if (table.has(suffix))
221
+ continue;
222
+ if (!alias) {
223
+ table.set(suffix, `${module}.${suffix}`);
224
+ changed = true;
225
+ continue;
226
+ }
227
+ const source = aliasToModule(module, alias);
228
+ const canonical = source ? out.get(source)?.get(suffix) : undefined;
229
+ if (canonical) {
230
+ table.set(suffix, canonical);
231
+ changed = true;
232
+ }
233
+ }
234
+ }
235
+ if (!changed)
236
+ break;
237
+ }
238
+ return out;
239
+ }
240
+ /** Stamp `metadata.reExportedKinds` (suffix → canonical kind) onto every `Telo.Import` whose
241
+ * target re-exports kinds, so the analyzer can register the re-export mappings. Only entries
242
+ * that point at a module OTHER than the import's own target are stamped (genuine re-exports;
243
+ * a locally-defined kind resolves through the normal alias path). Stamped on `metadata` (which
244
+ * permits additional properties, like `resolvedModuleName`) since the `Telo.Import` schema
245
+ * forbids extra top-level fields. Shared by both paths. */
246
+ export function stampReExportedKinds(imports, exportedKinds) {
247
+ for (const { manifest, targetModule } of imports) {
248
+ const table = exportedKinds.get(targetModule);
249
+ if (!table)
250
+ continue;
251
+ const reExported = {};
252
+ for (const [suffix, canonical] of table) {
253
+ if (canonical !== `${targetModule}.${suffix}`)
254
+ reExported[suffix] = canonical;
255
+ }
256
+ if (Object.keys(reExported).length === 0)
257
+ continue;
258
+ manifest.metadata.reExportedKinds = reExported;
259
+ }
260
+ }
261
+ /** CLI/kernel adapter: collect re-export specs + alias map from a LoadedGraph. */
262
+ function forwardReExports(graph, result) {
263
+ const ownerSourceOf = new Map();
264
+ const specs = [];
265
+ const kindModules = [];
266
+ for (const [source, mod] of graph.modules) {
267
+ if (source === graph.rootSource)
268
+ continue; // root is an Application — no exports
269
+ const libDoc = mod.owner.manifests.find((m) => m && isModuleKind(m.kind));
270
+ const moduleName = libDoc?.metadata?.name;
271
+ if (!libDoc || !moduleName)
272
+ continue;
273
+ ownerSourceOf.set(moduleName, mod.owner.source);
274
+ specs.push(...reExportSpecsFromExports(moduleName, libDoc.exports?.resources));
275
+ kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds ?? [] });
276
+ }
277
+ const aliasToModule = (module, alias) => {
278
+ const ownerSource = ownerSourceOf.get(module);
279
+ return ownerSource
280
+ ? (graph.importEdges.get(ownerSource)?.get(alias)?.targetModuleName ?? undefined)
281
+ : undefined;
282
+ };
283
+ forwardReExportManifests(result, specs, aliasToModule);
284
+ // Resolve every library's re-exported kinds and stamp them onto the consumer-facing
285
+ // Telo.Import manifests so the analyzer can register the re-export mappings.
286
+ const exportedKinds = resolveExportedKinds(kindModules, aliasToModule);
287
+ const imports = [];
288
+ for (const m of result) {
289
+ if (m.kind !== "Telo.Import")
290
+ continue;
291
+ const owner = m.metadata?.source;
292
+ const alias = m.metadata?.name;
293
+ const target = owner && alias ? graph.importEdges.get(owner)?.get(alias)?.targetModuleName : undefined;
294
+ if (target)
295
+ imports.push({ manifest: m, targetModule: target });
296
+ }
297
+ stampReExportedKinds(imports, exportedKinds);
298
+ }
108
299
  /** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
109
300
  * with `metadata.module` stamped on non-module docs. The kernel's runtime
110
301
  * entry load uses this to convert a `Loader.loadModule` result into the
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { AnalysisRegistry } from "./analysis-registry.js";
2
2
  export type { RefFieldInfo } from "./analysis-registry.js";
3
3
  export { StaticAnalyzer } from "./analyzer.js";
4
4
  export type { GraphLoadError, ImportEdge, LoadedFile, LoadedGraph, LoadedModule, ParseError, } from "./loaded-types.js";
5
- export { flattenForAnalyzer, flattenLoadedModule, selectModuleManifestsForAnalysis, } from "./flatten-for-analyzer.js";
5
+ export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, type ParsedExportEntry, type ReExportSpec, } from "./flatten-for-analyzer.js";
6
6
  export { visitManifest } from "./manifest-visitor.js";
7
7
  export type { CelSiteEvent, ManifestVisitor, RefSiteEvent, ResourceEnterEvent, ResourceExitEvent, ScopeBoundaryEvent, SchemaFromSiteEvent, VisitOptions, } from "./manifest-visitor.js";
8
8
  export { Loader } from "./manifest-loader.js";