@telorun/analyzer 0.21.0 → 0.23.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 (41) hide show
  1. package/README.md +9 -17
  2. package/dist/alias-resolver.d.ts +7 -0
  3. package/dist/alias-resolver.d.ts.map +1 -1
  4. package/dist/alias-resolver.js +14 -0
  5. package/dist/analyzer.d.ts.map +1 -1
  6. package/dist/analyzer.js +59 -15
  7. package/dist/builtins.d.ts.map +1 -1
  8. package/dist/builtins.js +10 -9
  9. package/dist/cel-environment.d.ts +8 -0
  10. package/dist/cel-environment.d.ts.map +1 -1
  11. package/dist/cel-environment.js +48 -0
  12. package/dist/flatten-for-analyzer.d.ts +52 -0
  13. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  14. package/dist/flatten-for-analyzer.js +192 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/resolve-ref-sentinels.d.ts +22 -7
  19. package/dist/resolve-ref-sentinels.d.ts.map +1 -1
  20. package/dist/resolve-ref-sentinels.js +46 -136
  21. package/dist/schema-compat.d.ts.map +1 -1
  22. package/dist/schema-compat.js +28 -8
  23. package/dist/validate-cel-context.d.ts.map +1 -1
  24. package/dist/validate-cel-context.js +5 -0
  25. package/dist/validate-reference-forms.d.ts +28 -0
  26. package/dist/validate-reference-forms.d.ts.map +1 -0
  27. package/dist/validate-reference-forms.js +91 -0
  28. package/dist/validate-references.d.ts.map +1 -1
  29. package/dist/validate-references.js +4 -37
  30. package/package.json +2 -2
  31. package/src/alias-resolver.ts +14 -0
  32. package/src/analyzer.ts +69 -19
  33. package/src/builtins.ts +10 -9
  34. package/src/cel-environment.ts +57 -0
  35. package/src/flatten-for-analyzer.ts +217 -4
  36. package/src/index.ts +7 -0
  37. package/src/resolve-ref-sentinels.ts +39 -133
  38. package/src/schema-compat.ts +27 -8
  39. package/src/validate-cel-context.ts +5 -0
  40. package/src/validate-reference-forms.ts +110 -0
  41. package/src/validate-references.ts +4 -39
package/README.md CHANGED
@@ -55,11 +55,11 @@ 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
- - Migrations
62
- - Server
61
+ - !ref Migrations
62
+ - !ref Server
63
63
  ---
64
64
  # SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
65
65
  kind: Sql.Connection
@@ -72,9 +72,7 @@ file: ./tmp/feedback.db
72
72
  kind: Sql.Migrations
73
73
  metadata:
74
74
  name: Migrations
75
- connection:
76
- kind: Sql.Connection
77
- name: Db
75
+ connection: !ref Db
78
76
  ---
79
77
  kind: Sql.Migration
80
78
  metadata:
@@ -101,7 +99,7 @@ openapi:
101
99
  version: 1.0.0
102
100
  mounts:
103
101
  - path: /v1
104
- type: Http.Api.FeedbackRoutes
102
+ mount: !ref FeedbackRoutes
105
103
  ---
106
104
  kind: Http.Api
107
105
  metadata:
@@ -123,9 +121,7 @@ routes:
123
121
  required: [ text ]
124
122
  handler:
125
123
  kind: Sql.Exec
126
- connection:
127
- kind: Sql.Connection
128
- name: Db
124
+ connection: !ref Db
129
125
  inputs:
130
126
  sql: "INSERT INTO feedback (text, source, score) VALUES (?, ?, ?)"
131
127
  bindings:
@@ -146,9 +142,7 @@ routes:
146
142
  method: GET
147
143
  handler:
148
144
  kind: Sql.Select
149
- connection:
150
- kind: Sql.Connection
151
- name: Db
145
+ connection: !ref Db
152
146
  from: feedback
153
147
  columns: [ id, text, source, score, created_at ]
154
148
  orderBy:
@@ -172,9 +166,7 @@ routes:
172
166
  required: [ id ]
173
167
  handler:
174
168
  kind: Sql.Select
175
- connection:
176
- kind: Sql.Connection
177
- name: Db
169
+ connection: !ref Db
178
170
  from: feedback
179
171
  columns: [ id, text, source, score, created_at ]
180
172
  where:
@@ -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
@@ -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"}
@@ -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;
@@ -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;AA2hB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAypBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAqBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
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;IA+sBvB,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
2
  import { AliasResolver } from "./alias-resolver.js";
3
- import { buildCelEnvironment, buildTypedCelEnvironment, } from "./cel-environment.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";
@@ -18,6 +18,7 @@ import { validateExtends } from "./validate-extends.js";
18
18
  import { validateNestedInlineResources } from "./validate-nested-inline.js";
19
19
  import { validateProviderCoherence } from "./validate-provider-coherence.js";
20
20
  import { validateReferences } from "./validate-references.js";
21
+ import { validateReferenceForms } from "./validate-reference-forms.js";
21
22
  import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
22
23
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
23
24
  const SELF_PREFIX = "Self.";
@@ -578,17 +579,18 @@ export class StaticAnalyzer {
578
579
  if (resolvedModuleName) {
579
580
  defs.registerModuleIdentity(resolvedNamespace ?? null, resolvedModuleName);
580
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 ?? {});
581
586
  // Alias registration is scoped: consumer imports vs. imported-library imports.
582
- if (!ownModule || rootModules.has(ownModule)) {
583
- aliases.registerImport(alias, targetModule, exportedKinds);
584
- }
585
- else {
586
- let libResolver = aliasesByModule.get(ownModule);
587
- if (!libResolver) {
588
- libResolver = new AliasResolver();
589
- aliasesByModule.set(ownModule, libResolver);
590
- }
591
- 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);
592
594
  }
593
595
  }
594
596
  }
@@ -646,13 +648,21 @@ export class StaticAnalyzer {
646
648
  : def;
647
649
  defs.register(normalized);
648
650
  }
651
+ // Reference-form validation — enforce `!ref` as the only reference shape.
652
+ // Runs on the RAW manifests, BEFORE inline extraction and sentinel
653
+ // resolution, while an author-written `{kind, name}` is still
654
+ // distinguishable from the resolver's own substitution (after Phase 2/2.5
655
+ // they are the same object).
656
+ if (!options?.skipValidation) {
657
+ diagnostics.push(...validateReferenceForms(manifests, defs, aliases, aliasesByModule));
658
+ }
649
659
  // Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
650
660
  const allManifests = normalizeInlineResources(manifests, defs, aliases, aliasesByModule);
651
661
  // Phase 2.5: resolve `!ref <name>` sentinels at every ref slot to canonical
652
662
  // {kind, name} objects so downstream phases (validation, dependency graph,
653
663
  // kernel controllers) see a uniform shape. Runs after normalize so both
654
664
  // original and inline-extracted manifests have their sentinels resolved.
655
- resolveRefSentinels(allManifests, defs, aliases, aliasesByModule);
665
+ resolveRefSentinels(allManifests, aliases, aliasesByModule);
656
666
  // Trusted-input fast path: when the caller has already attested that
657
667
  // this exact manifest set passes analysis (e.g. via the kernel's
658
668
  // hash-stamped `.validated.json` cache), skip the validation walk.
@@ -726,6 +736,26 @@ export class StaticAnalyzer {
726
736
  }
727
737
  }
728
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
+ }
729
759
  }
730
760
  // Build typed kernel globals schema so x-telo-context chain validation
731
761
  // recognises variables, secrets, resources, env automatically
@@ -795,8 +825,22 @@ export class StaticAnalyzer {
795
825
  },
796
826
  }
797
827
  : definition.schema;
798
- // Phase 1: CEL type checking — walk data+schema together, check env.check() return types
799
- const baseTypedEnv = buildTypedCelEnvironment(this.celEnv, m, undefined, moduleManifest);
828
+ // Phase 1: CEL type checking — walk data+schema together, check env.check() return types.
829
+ // A Telo.Import's variables/secrets are a config-only contract evaluated against the
830
+ // IMPORTING module's scope, so type them from the owning module doc (matched by
831
+ // `metadata.module`) and drop `resources`/`env` so referencing them is an error. A
832
+ // library's own internal import is validated against that library in the library's
833
+ // standalone analysis; in this flattened app pass the library doc is absent, so the
834
+ // importer is undefined here and variables/secrets fall back to a permissive `map`
835
+ // (no false positives) while resources/env stay rejected.
836
+ const importerModule = m.kind === "Telo.Import"
837
+ ? allManifests.find((mm) => (mm.kind === "Telo.Application" || mm.kind === "Telo.Library") &&
838
+ mm.metadata?.name ===
839
+ m.metadata?.module)
840
+ : undefined;
841
+ const baseTypedEnv = m.kind === "Telo.Import"
842
+ ? buildImportInputCelEnvironment(this.celEnv, importerModule)
843
+ : buildTypedCelEnvironment(this.celEnv, m, undefined, moduleManifest);
800
844
  const celIssues = collectCelTypeIssues(m, schema, "", definition, m, baseTypedEnv, this.celEnv, moduleManifest);
801
845
  // Phase 2+3: AJV on substituted data — CEL fields replaced with typed placeholders
802
846
  const ajvIssues = validateAgainstSchema(substituteCelFields(m, schema), schema);
@@ -1058,7 +1102,7 @@ export class StaticAnalyzer {
1058
1102
  // Resolve !ref sentinels after normalize so both the original and
1059
1103
  // inline-extracted manifests get their refs canonicalized to
1060
1104
  // {kind, name} for the kernel that consumes this output.
1061
- resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
1105
+ resolveRefSentinels(normalized, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
1062
1106
  return normalized;
1063
1107
  }
1064
1108
  prepare(manifests, registry) {
@@ -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,EAsd/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
@@ -234,7 +234,7 @@ export const KERNEL_BUILTINS = [
234
234
  },
235
235
  // Gated reference: run() a Runnable/Service only when the
236
236
  // `when` CEL guard holds. Discriminated by the `ref` key. `ref`
237
- // is a bare name or a resolved `!ref` (`{ kind, name }`).
237
+ // is a `!ref` that resolves to the `{ kind, name }` shape below.
238
238
  {
239
239
  type: "object",
240
240
  required: ["ref"],
@@ -262,12 +262,11 @@ export const KERNEL_BUILTINS = [
262
262
  // with an optional `name` (for steps.<name>.result plumbing),
263
263
  // `when` guard, and `inputs`. Discriminated by the `invoke` key.
264
264
  // Control flow (if/while/switch/try) is not available here —
265
- // reach for Run.Sequence. `invoke` is ref-only and must resolve
266
- // to a `{ kind, name }` reference (a `!ref` / `{kind,name}`):
267
- // requiring `name` rejects an inline `{ kind }` definition (no
268
- // name) at analysis instead of failing at boot with an undefined
269
- // resource name. The Invocable/Runnable kind set mirrors
270
- // Run.Sequence invoke steps.
265
+ // reach for Run.Sequence. `invoke` is ref-only: a `!ref` that
266
+ // resolves to the `{ kind, name }` shape below. Requiring `name`
267
+ // rejects an inline `{ kind }` definition (no name) at analysis
268
+ // instead of failing at boot with an undefined resource name. The
269
+ // Invocable/Runnable kind set mirrors Run.Sequence invoke steps.
271
270
  {
272
271
  type: "object",
273
272
  required: ["invoke"],
@@ -454,8 +453,10 @@ export const KERNEL_BUILTINS = [
454
453
  type: "object",
455
454
  properties: {
456
455
  kinds: { type: "array", items: { type: "string" } },
457
- // `variables` / `secrets` are reserved on the resources.<Alias> value-flow
458
- // 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.
459
460
  resources: {
460
461
  type: "array",
461
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"}