@telorun/analyzer 0.36.0 → 0.38.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.
@@ -7,7 +7,16 @@ export declare class AliasResolver {
7
7
  * transitively RE-EXPORTS (`exports.kinds: [Alias.Kind]`), which don't live in the
8
8
  * import's own module. Resolved before the normal `<module>.<suffix>` construction. */
9
9
  private readonly reExportedKinds;
10
- registerImport(alias: string, targetModule: string, exportedKinds: string[]): void;
10
+ /** Register an import alias gated to the target's `exports.kinds`. Only listed kinds
11
+ * resolve; an empty list exports nothing. `exportedKinds` is `undefined` for exactly one
12
+ * case — the target declares no `exports.kinds` at all (the legacy permissive default,
13
+ * kept so already-published module versions stay importable), which is the site to delete
14
+ * when kinds go private. For an alias crossing no import boundary use
15
+ * `registerUngatedAlias`. Mirrors `ModuleContext.registerImport`. */
16
+ registerImport(alias: string, targetModule: string, exportedKinds?: readonly string[]): void;
17
+ /** Register an alias that crosses no import boundary and is therefore never gated —
18
+ * `Self`, a library resolving its own kinds. See `ModuleContext.registerUngatedAlias`. */
19
+ registerUngatedAlias(alias: string, targetModule: string): void;
11
20
  /** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
12
21
  * (owned by a module the alias's target imports, possibly several hops away). */
13
22
  registerKindReExport(alias: string, suffix: string, canonicalKind: string): void;
@@ -17,7 +26,27 @@ export declare class AliasResolver {
17
26
  * upstream by `flattenForAnalyzer` (only exported instances are forwarded), so a name
18
27
  * that isn't exported simply won't be found. */
19
28
  moduleForAlias(alias: string): string | undefined;
20
- /** Resolves "Http.Api" "http-server.Api". Returns undefined if alias is unknown. */
29
+ /** Resolve an alias-qualified kind, reporting WHY it failed. One parse, one gate check
30
+ * callers that need the reason and callers that only need the name share this, so they
31
+ * cannot drift on dot-splitting, re-export precedence, or gate order.
32
+ *
33
+ * - `ok` — resolved to a canonical `<module>.<Kind>`.
34
+ * - `gated` — the alias is known and the target owns the name, but its `exports.kinds`
35
+ * does not list it. A distinct outcome so callers can say "not exported"
36
+ * instead of the misleading "no such kind".
37
+ * - `unknown` — unqualified kind, or an alias this scope never imported. */
38
+ resolveKindResult(kind: string): {
39
+ status: "ok";
40
+ kind: string;
41
+ } | {
42
+ status: "gated";
43
+ module: string;
44
+ exported: string[];
45
+ } | {
46
+ status: "unknown";
47
+ };
48
+ /** Resolves "Http.Api" → "http-server.Api". Undefined if the alias is unknown OR the kind
49
+ * is gated out — use `resolveKindResult` when the difference matters. */
21
50
  resolveKind(kind: string): string | undefined;
22
51
  hasAlias(alias: string): boolean;
23
52
  knownAliases(): string[];
@@ -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;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"}
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;;;;;0EAKsE;IACtE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAO5F;+FAC2F;IAC3F,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAI/D;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;;;;;;;;iFAQ6E;IAC7E,iBAAiB,CACf,IAAI,EAAE,MAAM,GAEV;QAAE,MAAM,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC9B;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,GACvD;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE;IAoBzB;8EAC0E;IAC1E,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK7C,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"}
@@ -7,12 +7,23 @@ export class AliasResolver {
7
7
  * transitively RE-EXPORTS (`exports.kinds: [Alias.Kind]`), which don't live in the
8
8
  * import's own module. Resolved before the normal `<module>.<suffix>` construction. */
9
9
  reExportedKinds = new Map();
10
+ /** Register an import alias gated to the target's `exports.kinds`. Only listed kinds
11
+ * resolve; an empty list exports nothing. `exportedKinds` is `undefined` for exactly one
12
+ * case — the target declares no `exports.kinds` at all (the legacy permissive default,
13
+ * kept so already-published module versions stay importable), which is the site to delete
14
+ * when kinds go private. For an alias crossing no import boundary use
15
+ * `registerUngatedAlias`. Mirrors `ModuleContext.registerImport`. */
10
16
  registerImport(alias, targetModule, exportedKinds) {
11
17
  this.importAliases.set(alias, targetModule);
12
- if (exportedKinds.length > 0) {
18
+ if (exportedKinds !== undefined) {
13
19
  this.importedKinds.set(alias, new Set(exportedKinds));
14
20
  }
15
21
  }
22
+ /** Register an alias that crosses no import boundary and is therefore never gated —
23
+ * `Self`, a library resolving its own kinds. See `ModuleContext.registerUngatedAlias`. */
24
+ registerUngatedAlias(alias, targetModule) {
25
+ this.importAliases.set(alias, targetModule);
26
+ }
16
27
  /** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
17
28
  * (owned by a module the alias's target imports, possibly several hops away). */
18
29
  registerKindReExport(alias, suffix, canonicalKind) {
@@ -26,28 +37,43 @@ export class AliasResolver {
26
37
  moduleForAlias(alias) {
27
38
  return this.importAliases.get(alias);
28
39
  }
29
- /** Resolves "Http.Api" "http-server.Api". Returns undefined if alias is unknown. */
30
- resolveKind(kind) {
31
- if (!kind) {
32
- return undefined;
33
- }
40
+ /** Resolve an alias-qualified kind, reporting WHY it failed. One parse, one gate check
41
+ * callers that need the reason and callers that only need the name share this, so they
42
+ * cannot drift on dot-splitting, re-export precedence, or gate order.
43
+ *
44
+ * - `ok` — resolved to a canonical `<module>.<Kind>`.
45
+ * - `gated` — the alias is known and the target owns the name, but its `exports.kinds`
46
+ * does not list it. A distinct outcome so callers can say "not exported"
47
+ * instead of the misleading "no such kind".
48
+ * - `unknown` — unqualified kind, or an alias this scope never imported. */
49
+ resolveKindResult(kind) {
50
+ if (!kind)
51
+ return { status: "unknown" };
34
52
  const dot = kind.indexOf(".");
35
53
  if (dot === -1)
36
- return undefined;
54
+ return { status: "unknown" };
37
55
  const prefix = kind.slice(0, dot);
38
56
  const suffix = kind.slice(dot + 1);
39
57
  // 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).
58
+ // not `${prefix-target}.${suffix}`. It is listed in the re-exporting library's own
59
+ // `exports.kinds` (that is how it got here), so this is not a gate bypass.
41
60
  const reExported = this.reExportedKinds.get(`${prefix}.${suffix}`);
42
61
  if (reExported)
43
- return reExported;
62
+ return { status: "ok", kind: reExported };
44
63
  const realModule = this.importAliases.get(prefix);
45
64
  if (!realModule)
46
- return undefined;
65
+ return { status: "unknown" };
47
66
  const allowed = this.importedKinds.get(prefix);
48
- if (allowed !== undefined && !allowed.has(suffix))
49
- return undefined;
50
- return `${realModule}.${suffix}`;
67
+ if (allowed !== undefined && !allowed.has(suffix)) {
68
+ return { status: "gated", module: realModule, exported: [...allowed] };
69
+ }
70
+ return { status: "ok", kind: `${realModule}.${suffix}` };
71
+ }
72
+ /** Resolves "Http.Api" → "http-server.Api". Undefined if the alias is unknown OR the kind
73
+ * is gated out — use `resolveKindResult` when the difference matters. */
74
+ resolveKind(kind) {
75
+ const r = this.resolveKindResult(kind);
76
+ return r.status === "ok" ? r.kind : undefined;
51
77
  }
52
78
  hasAlias(alias) {
53
79
  return this.importAliases.has(alias);
@@ -29,7 +29,12 @@ export declare class AnalysisRegistry {
29
29
  private readonly aliasesByModule;
30
30
  registerDefinition(def: ResourceDefinition): void;
31
31
  registerModuleIdentity(namespace: string | null, name: string): void;
32
- registerImport(alias: string, target: string, kinds: string[]): void;
32
+ /** `kinds` is the target's `exports.kinds` gate — only listed kinds resolve, and an
33
+ * empty list exports nothing. Omit it only for a target declaring no `exports.kinds`
34
+ * (the legacy permissive default); see `AliasResolver.registerImport`. */
35
+ registerImport(alias: string, target: string, kinds?: readonly string[]): void;
36
+ /** An alias crossing no import boundary, never gated — `Self`, the `Telo` built-ins. */
37
+ registerUngatedAlias(alias: string, target: string): void;
33
38
  resolveKind(kind: string): string | undefined;
34
39
  /**
35
40
  * Iterates a resource's reference and scope fields as declared by its definition.
@@ -91,6 +96,20 @@ export declare class AnalysisRegistry {
91
96
  */
92
97
  builtinDefinitions(): ResourceDefinition[];
93
98
  resolveDefinition(kind: string): ResourceDefinition | undefined;
99
+ /** A resolver scoped to `def`'s OWN module, for resolving that definition's
100
+ * `extends` target.
101
+ *
102
+ * `extends` aliases are lexically scoped to the declaring library — a library
103
+ * writes `extends: Cache.Store` against its own import map, and `Self.Host`
104
+ * against its own module name. The global alias table knows neither, so
105
+ * {@link resolveDefinition} silently returns undefined for them and callers
106
+ * fall back to an un-inherited view. Mirrors the module-scope selection in
107
+ * `expandedFieldMapForResource`. */
108
+ resolverForDefinition(def: {
109
+ metadata?: {
110
+ module?: string;
111
+ };
112
+ }): (kind: string) => ResourceDefinition | undefined;
94
113
  /** Canonical kinds (`module.Name`) of every definition that extends the given
95
114
  * abstract kind — the concrete implementations a caller may instantiate in
96
115
  * its place. Empty when `kind` is not an abstract or has no implementations. */
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"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"}
@@ -20,9 +20,16 @@ export class AnalysisRegistry {
20
20
  registerModuleIdentity(namespace, name) {
21
21
  this.defs.registerModuleIdentity(namespace, name);
22
22
  }
23
+ /** `kinds` is the target's `exports.kinds` gate — only listed kinds resolve, and an
24
+ * empty list exports nothing. Omit it only for a target declaring no `exports.kinds`
25
+ * (the legacy permissive default); see `AliasResolver.registerImport`. */
23
26
  registerImport(alias, target, kinds) {
24
27
  this.aliases.registerImport(alias, target, kinds);
25
28
  }
29
+ /** An alias crossing no import boundary, never gated — `Self`, the `Telo` built-ins. */
30
+ registerUngatedAlias(alias, target) {
31
+ this.aliases.registerUngatedAlias(alias, target);
32
+ }
26
33
  resolveKind(kind) {
27
34
  return this.aliases.resolveKind(kind);
28
35
  }
@@ -171,6 +178,23 @@ export class AnalysisRegistry {
171
178
  const resolved = ctx.aliases?.resolveKind(kind);
172
179
  return ctx.definitions?.resolve(kind) ?? (resolved ? ctx.definitions?.resolve(resolved) : undefined);
173
180
  }
181
+ /** A resolver scoped to `def`'s OWN module, for resolving that definition's
182
+ * `extends` target.
183
+ *
184
+ * `extends` aliases are lexically scoped to the declaring library — a library
185
+ * writes `extends: Cache.Store` against its own import map, and `Self.Host`
186
+ * against its own module name. The global alias table knows neither, so
187
+ * {@link resolveDefinition} silently returns undefined for them and callers
188
+ * fall back to an un-inherited view. Mirrors the module-scope selection in
189
+ * `expandedFieldMapForResource`. */
190
+ resolverForDefinition(def) {
191
+ const ownModule = def?.metadata?.module;
192
+ const scope = (ownModule ? this.aliasesByModule.get(ownModule) : undefined) ?? this.aliases;
193
+ return (kind) => {
194
+ const canonical = scope.resolveKind(kind);
195
+ return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
196
+ };
197
+ }
174
198
  /** Canonical kinds (`module.Name`) of every definition that extends the given
175
199
  * abstract kind — the concrete implementations a caller may instantiate in
176
200
  * its place. Empty when `kind` is not an abstract or has no implementations. */
@@ -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;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAoB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuxB/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;IAgzBvB,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;IAmBrB,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;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAoB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuxB/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;IAm1BvB,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;IAmBrB,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
@@ -735,15 +735,14 @@ export class StaticAnalyzer {
735
735
  // Lets same-library `extends:` work (e.g. `extends: Self.Encoder` for a
736
736
  // concrete kind whose abstract lives in the same Telo.Library) without
737
737
  // requiring a self-import (which would loop the loader). Resolves
738
- // through the same alias machinery as user-declared Telo.Imports
739
- // honours the library's `exports.kinds` list, no special cases.
738
+ // through the same alias machinery as user-declared Telo.Imports.
740
739
  if (moduleName) {
741
740
  // `Self` resolves the library's own kinds UNGATED — a library may reference
742
741
  // its own kinds regardless of `exports.kinds`, which gates importers, not
743
742
  // internal use. This is what lets a library declare an instance of a kind it
744
743
  // does not export (e.g. console's `writeLine`) to enforce a singleton.
745
744
  if (rootModules.has(moduleName)) {
746
- aliases.registerImport("Self", moduleName, []);
745
+ aliases.registerUngatedAlias("Self", moduleName);
747
746
  }
748
747
  else {
749
748
  let libResolver = aliasesByModule.get(moduleName);
@@ -751,14 +750,19 @@ export class StaticAnalyzer {
751
750
  libResolver = new AliasResolver();
752
751
  aliasesByModule.set(moduleName, libResolver);
753
752
  }
754
- libResolver.registerImport("Self", moduleName, []);
753
+ libResolver.registerUngatedAlias("Self", moduleName);
755
754
  }
756
755
  }
757
756
  }
758
757
  if (m.kind === "Telo.Import") {
759
758
  const alias = m.metadata.name;
760
759
  const source = m.source;
761
- const exportedKinds = m.exports?.kinds ?? [];
760
+ // The target's `exports.kinds` gate, stamped onto this import by
761
+ // `stampExportedKinds` (the `Telo.Import` doc has no `exports` field of its own —
762
+ // the target `Telo.Library` doc it comes from is dropped for non-root modules).
763
+ // Undefined means the target declares no gate, i.e. unrestricted.
764
+ const exportedKinds = m.metadata
765
+ ?.exportedKinds;
762
766
  const resolvedModuleName = m.metadata.resolvedModuleName;
763
767
  const resolvedNamespace = m.metadata.resolvedNamespace;
764
768
  const ownModule = m.metadata?.module;
@@ -833,7 +837,7 @@ export class StaticAnalyzer {
833
837
  aliasesByModule.set(ownModule, libResolver);
834
838
  }
835
839
  if (!libResolver.hasAlias("Self")) {
836
- libResolver.registerImport("Self", ownModule, []);
840
+ libResolver.registerUngatedAlias("Self", ownModule);
837
841
  }
838
842
  }
839
843
  // Register definitions from Telo.Definition AND Telo.Abstract resources.
@@ -1042,7 +1046,35 @@ export class StaticAnalyzer {
1042
1046
  // reference-resolution paths: own-module scope first, root/consumer aliases last.
1043
1047
  const ownModule = m.metadata?.module;
1044
1048
  const scopeResolver = scopeResolverForModule(ownModule, rootModules, aliasesByModule);
1045
- const resolvedKind = scopeResolver?.resolveKind(m.kind) ?? aliases.resolveKind(m.kind);
1049
+ // Either scope resolving it wins; otherwise a gate rejection is more specific than
1050
+ // "unknown alias", so surface that.
1051
+ const scoped = scopeResolver?.resolveKindResult(m.kind);
1052
+ const rooted = aliases.resolveKindResult(m.kind);
1053
+ const kindResult = scoped?.status === "ok"
1054
+ ? scoped
1055
+ : rooted.status === "ok"
1056
+ ? rooted
1057
+ : scoped?.status === "gated"
1058
+ ? scoped
1059
+ : rooted;
1060
+ // The gate is checked BEFORE any definition lookup. `defs` is keyed
1061
+ // `<metadata.module>.<Kind>`, so a library whose `metadata.name` equals the alias it
1062
+ // is imported under (e.g. module `Foo` imported as `Foo`) makes the raw `defs.resolve(m.kind)`
1063
+ // below hit directly — which would accept a kind the module does not export, leaving
1064
+ // the analyzer more permissive than the kernel.
1065
+ if (kindResult.status === "gated") {
1066
+ diagnostics.push({
1067
+ severity: DiagnosticSeverity.Error,
1068
+ code: "KIND_NOT_EXPORTED",
1069
+ source: SOURCE,
1070
+ message: `Kind '${m.kind}' is not exported by module '${kindResult.module}'. ` +
1071
+ `Add '${m.kind.slice(m.kind.indexOf(".") + 1)}' to that module's exports.kinds ` +
1072
+ `to make it importable. Exported kinds: ${kindResult.exported.join(", ") || "(none)"}.`,
1073
+ data: { resource, filePath, path: "kind" },
1074
+ });
1075
+ continue;
1076
+ }
1077
+ const resolvedKind = kindResult.status === "ok" ? kindResult.kind : undefined;
1046
1078
  const definition = defs.resolve(m.kind) ?? (resolvedKind ? defs.resolve(resolvedKind) : undefined);
1047
1079
  if (!definition) {
1048
1080
  const suggestedKind = computeSuggestKind(m.kind, aliases, defs);
@@ -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,EAujB/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;AAwBvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAyjB/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -1,3 +1,24 @@
1
+ /** Descriptive provenance a module declares about itself, shared by
2
+ * `Telo.Application` and `Telo.Library`.
3
+ *
4
+ * These are *descriptive*, never *addressing* — nothing resolves, fetches,
5
+ * caches, or publishes based on them, so they do not conflict with
6
+ * identity-is-the-ref (which bans metadata from determining an artifact's
7
+ * location). `repository` is the location of the module's **source code**, in
8
+ * the same spirit as npm's `repository` / `license` / `homepage`. It is named
9
+ * `repository` rather than `source` because `source:` already means "where to
10
+ * fetch a dependency from" in the `imports` map.
11
+ *
12
+ * A publish transport projects these into whatever its backend surfaces —
13
+ * OCI maps them onto the standard `org.opencontainers.image.*` annotations;
14
+ * the HTTP registry stores the manifest verbatim, so they are preserved as
15
+ * declared with nothing to translate. */
16
+ const PROVENANCE_METADATA = {
17
+ description: { type: "string" },
18
+ repository: { type: "string" },
19
+ license: { type: "string" },
20
+ documentation: { type: "string" },
21
+ };
1
22
  export const KERNEL_BUILTINS = [
2
23
  { kind: "Telo.Abstract", metadata: { name: "Template", module: "Telo" } },
3
24
  { kind: "Telo.Abstract", metadata: { name: "Runnable", module: "Telo" } },
@@ -269,6 +290,7 @@ export const KERNEL_BUILTINS = [
269
290
  version: { type: "string" },
270
291
  source: { type: "string" },
271
292
  module: { type: "string" },
293
+ ...PROVENANCE_METADATA,
272
294
  },
273
295
  required: ["name"],
274
296
  additionalProperties: true,
@@ -495,6 +517,7 @@ export const KERNEL_BUILTINS = [
495
517
  version: { type: "string" },
496
518
  source: { type: "string" },
497
519
  module: { type: "string" },
520
+ ...PROVENANCE_METADATA,
498
521
  },
499
522
  required: ["name"],
500
523
  additionalProperties: true,
@@ -82,7 +82,7 @@ export declare function forwardReExportManifests(result: ResourceManifest[], spe
82
82
  * shared by the CLI graph path and the editor's workspace projection. */
83
83
  export declare function resolveExportedKinds(modules: ReadonlyArray<{
84
84
  module: string;
85
- exportsKinds: readonly string[];
85
+ exportsKinds: readonly string[] | undefined;
86
86
  }>, aliasToModule: (module: string, alias: string) => string | undefined): Map<string, Map<string, string>>;
87
87
  /** Stamp `metadata.reExportedKinds` (suffix → canonical kind) onto every `Telo.Import` whose
88
88
  * target re-exports kinds, so the analyzer can register the re-export mappings. Only entries
@@ -94,6 +94,25 @@ export declare function stampReExportedKinds(imports: ReadonlyArray<{
94
94
  manifest: ResourceManifest;
95
95
  targetModule: string;
96
96
  }>, exportedKinds: Map<string, Map<string, string>>): void;
97
+ /** Stamp `metadata.exportedKinds` (the target's `exports.kinds` gate as bare suffixes) onto
98
+ * every `Telo.Import` whose target declares one, so the analyzer enforces the same gate the
99
+ * kernel does. Stamped on `metadata` for the same reason as `reExportedKinds` — the
100
+ * `Telo.Import` schema forbids extra top-level fields.
101
+ *
102
+ * The gate comes from the DECLARED entries, parsed exactly as the kernel parses them
103
+ * (`parseExportEntry` — `Alias.Kind` yields `Kind`), never from the resolved re-export
104
+ * table. That table drops a re-export whose source module declares no `exports.kinds`, so
105
+ * deriving the gate from it would reject `exports.kinds: [Cee.Thing]` — a gate the kernel
106
+ * honours — as "not exported".
107
+ *
108
+ * A target that declares no `exports.kinds` is absent from `gatedModules` and goes unstamped,
109
+ * which the analyzer reads as unrestricted. That is the legacy permissive default, kept so
110
+ * already-published module versions stay importable; making kinds private by default means
111
+ * stamping every loaded target instead, which breaks those versions (see import-controller). */
112
+ export declare function stampExportedKinds(imports: ReadonlyArray<{
113
+ manifest: ResourceManifest;
114
+ targetModule: string;
115
+ }>, declaredKinds: ReadonlyMap<string, readonly string[]>): void;
97
116
  /** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
98
117
  * with `metadata.module` stamped on non-module docs. The kernel's runtime
99
118
  * 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,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
+ {"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,GAAG,SAAS,CAAA;CAAE,CAAC,EACvF,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,CA2ClC;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;AAED;;;;;;;;;;;;;;iGAciG;AACjG,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,aAAa,CAAC;IAAE,QAAQ,EAAE,gBAAgB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,EAC5E,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,GACpD,IAAI,CAMN;AAgDD;;;;;6BAK6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAEzE"}
@@ -205,6 +205,10 @@ export function forwardReExportManifests(result, specs, aliasToModule) {
205
205
  * shared by the CLI graph path and the editor's workspace projection. */
206
206
  export function resolveExportedKinds(modules, aliasToModule) {
207
207
  const out = new Map();
208
+ // Modules declaring NO `exports.kinds` export every kind they define (the legacy permissive
209
+ // default), so they have no table to look a re-exported suffix up in. `[]` is a real gate
210
+ // that exports nothing — the two must stay distinguishable here.
211
+ const ungated = new Set(modules.filter((m) => m.exportsKinds === undefined).map((m) => m.module));
208
212
  const tableFor = (m) => {
209
213
  let t = out.get(m);
210
214
  if (!t)
@@ -215,7 +219,7 @@ export function resolveExportedKinds(modules, aliasToModule) {
215
219
  let changed = false;
216
220
  for (const { module, exportsKinds } of modules) {
217
221
  const table = tableFor(module);
218
- for (const entry of exportsKinds) {
222
+ for (const entry of exportsKinds ?? []) {
219
223
  const { name: suffix, alias } = parseExportEntry(entry);
220
224
  if (table.has(suffix))
221
225
  continue;
@@ -225,7 +229,14 @@ export function resolveExportedKinds(modules, aliasToModule) {
225
229
  continue;
226
230
  }
227
231
  const source = aliasToModule(module, alias);
228
- const canonical = source ? out.get(source)?.get(suffix) : undefined;
232
+ // Re-exporting FROM an ungated module resolves straight to it: the kernel derives
233
+ // the gate from the raw entry list and allows this, so dropping it here would make
234
+ // the analyzer reject a manifest the runtime accepts — the exact shape the migration
235
+ // path relies on (a gated wrapper over an already-published, ungated module).
236
+ const canonical = source
237
+ ? (out.get(source)?.get(suffix) ??
238
+ (ungated.has(source) ? `${source}.${suffix}` : undefined))
239
+ : undefined;
229
240
  if (canonical) {
230
241
  table.set(suffix, canonical);
231
242
  changed = true;
@@ -258,11 +269,38 @@ export function stampReExportedKinds(imports, exportedKinds) {
258
269
  manifest.metadata.reExportedKinds = reExported;
259
270
  }
260
271
  }
272
+ /** Stamp `metadata.exportedKinds` (the target's `exports.kinds` gate as bare suffixes) onto
273
+ * every `Telo.Import` whose target declares one, so the analyzer enforces the same gate the
274
+ * kernel does. Stamped on `metadata` for the same reason as `reExportedKinds` — the
275
+ * `Telo.Import` schema forbids extra top-level fields.
276
+ *
277
+ * The gate comes from the DECLARED entries, parsed exactly as the kernel parses them
278
+ * (`parseExportEntry` — `Alias.Kind` yields `Kind`), never from the resolved re-export
279
+ * table. That table drops a re-export whose source module declares no `exports.kinds`, so
280
+ * deriving the gate from it would reject `exports.kinds: [Cee.Thing]` — a gate the kernel
281
+ * honours — as "not exported".
282
+ *
283
+ * A target that declares no `exports.kinds` is absent from `gatedModules` and goes unstamped,
284
+ * which the analyzer reads as unrestricted. That is the legacy permissive default, kept so
285
+ * already-published module versions stay importable; making kinds private by default means
286
+ * stamping every loaded target instead, which breaks those versions (see import-controller). */
287
+ export function stampExportedKinds(imports, declaredKinds) {
288
+ for (const { manifest, targetModule } of imports) {
289
+ const declared = declaredKinds.get(targetModule);
290
+ if (!declared)
291
+ continue;
292
+ manifest.metadata.exportedKinds = [...declared];
293
+ }
294
+ }
261
295
  /** CLI/kernel adapter: collect re-export specs + alias map from a LoadedGraph. */
262
296
  function forwardReExports(graph, result) {
263
297
  const ownerSourceOf = new Map();
264
298
  const specs = [];
265
299
  const kindModules = [];
300
+ // Modules that DECLARE `exports.kinds` → the gate, as bare suffixes parsed the same way
301
+ // the kernel parses them. A module that declares none is absent, leaving importers ungated
302
+ // (see stampExportedKinds).
303
+ const declaredKinds = new Map();
266
304
  for (const [source, mod] of graph.modules) {
267
305
  if (source === graph.rootSource)
268
306
  continue; // root is an Application — no exports
@@ -272,7 +310,10 @@ function forwardReExports(graph, result) {
272
310
  continue;
273
311
  ownerSourceOf.set(moduleName, mod.owner.source);
274
312
  specs.push(...reExportSpecsFromExports(moduleName, libDoc.exports?.resources));
275
- kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds ?? [] });
313
+ kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds });
314
+ if (libDoc.exports?.kinds !== undefined) {
315
+ declaredKinds.set(moduleName, libDoc.exports.kinds.map((e) => parseExportEntry(e).name));
316
+ }
276
317
  }
277
318
  const aliasToModule = (module, alias) => {
278
319
  const ownerSource = ownerSourceOf.get(module);
@@ -295,6 +336,7 @@ function forwardReExports(graph, result) {
295
336
  imports.push({ manifest: m, targetModule: target });
296
337
  }
297
338
  stampReExportedKinds(imports, exportedKinds);
339
+ stampExportedKinds(imports, declaredKinds);
298
340
  }
299
341
  /** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
300
342
  * with `metadata.module` stamped on non-module docs. The kernel's runtime
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, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, type ParsedExportEntry, type ReExportSpec, } from "./flatten-for-analyzer.js";
5
+ export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampExportedKinds, stampReExportedKinds, type ParsedExportEntry, type ReExportSpec, } from "./flatten-for-analyzer.js";
6
6
  export { buildEvalPaths, evalPathCovers } from "./eval-paths.js";
7
7
  export { ancestorChain, controllerBearingAncestor, effectiveAuthorSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, resolveParent, } from "./extends-resolution.js";
8
8
  export type { DefResolver } from "./extends-resolution.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampReExportedKinds, } from "./flatten-for-analyzer.js";
3
+ export { flattenForAnalyzer, flattenLoadedModule, forwardReExportManifests, parseExportEntry, reExportSpecsFromExports, resolveExportedKinds, selectModuleManifestsForAnalysis, stampExportedKinds, stampReExportedKinds, } from "./flatten-for-analyzer.js";
4
4
  export { buildEvalPaths, evalPathCovers } from "./eval-paths.js";
5
5
  export { ancestorChain, controllerBearingAncestor, effectiveAuthorSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, resolveParent, } from "./extends-resolution.js";
6
6
  export { buildReferenceFieldMap, isRefEntry, isScopeEntry } from "./reference-field-map.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.36.0",
3
+ "version": "0.38.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.44.0"
51
+ "@telorun/sdk": "0.48.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@telorun/sdk": "*"
@@ -8,13 +8,25 @@ export class AliasResolver {
8
8
  * import's own module. Resolved before the normal `<module>.<suffix>` construction. */
9
9
  private readonly reExportedKinds = new Map<string, string>();
10
10
 
11
- registerImport(alias: string, targetModule: string, exportedKinds: string[]): void {
11
+ /** Register an import alias gated to the target's `exports.kinds`. Only listed kinds
12
+ * resolve; an empty list exports nothing. `exportedKinds` is `undefined` for exactly one
13
+ * case — the target declares no `exports.kinds` at all (the legacy permissive default,
14
+ * kept so already-published module versions stay importable), which is the site to delete
15
+ * when kinds go private. For an alias crossing no import boundary use
16
+ * `registerUngatedAlias`. Mirrors `ModuleContext.registerImport`. */
17
+ registerImport(alias: string, targetModule: string, exportedKinds?: readonly string[]): void {
12
18
  this.importAliases.set(alias, targetModule);
13
- if (exportedKinds.length > 0) {
19
+ if (exportedKinds !== undefined) {
14
20
  this.importedKinds.set(alias, new Set(exportedKinds));
15
21
  }
16
22
  }
17
23
 
24
+ /** Register an alias that crosses no import boundary and is therefore never gated —
25
+ * `Self`, a library resolving its own kinds. See `ModuleContext.registerUngatedAlias`. */
26
+ registerUngatedAlias(alias: string, targetModule: string): void {
27
+ this.importAliases.set(alias, targetModule);
28
+ }
29
+
18
30
  /** Register that `<alias>.<suffix>` re-exports the kind canonically named `canonicalKind`
19
31
  * (owned by a module the alias's target imports, possibly several hops away). */
20
32
  registerKindReExport(alias: string, suffix: string, canonicalKind: string): void {
@@ -30,24 +42,45 @@ export class AliasResolver {
30
42
  return this.importAliases.get(alias);
31
43
  }
32
44
 
33
- /** Resolves "Http.Api" "http-server.Api". Returns undefined if alias is unknown. */
34
- resolveKind(kind: string): string | undefined {
35
- if (!kind) {
36
- return undefined;
37
- }
45
+ /** Resolve an alias-qualified kind, reporting WHY it failed. One parse, one gate check
46
+ * callers that need the reason and callers that only need the name share this, so they
47
+ * cannot drift on dot-splitting, re-export precedence, or gate order.
48
+ *
49
+ * - `ok` — resolved to a canonical `<module>.<Kind>`.
50
+ * - `gated` — the alias is known and the target owns the name, but its `exports.kinds`
51
+ * does not list it. A distinct outcome so callers can say "not exported"
52
+ * instead of the misleading "no such kind".
53
+ * - `unknown` — unqualified kind, or an alias this scope never imported. */
54
+ resolveKindResult(
55
+ kind: string,
56
+ ):
57
+ | { status: "ok"; kind: string }
58
+ | { status: "gated"; module: string; exported: string[] }
59
+ | { status: "unknown" } {
60
+ if (!kind) return { status: "unknown" };
38
61
  const dot = kind.indexOf(".");
39
- if (dot === -1) return undefined;
62
+ if (dot === -1) return { status: "unknown" };
40
63
  const prefix = kind.slice(0, dot);
41
64
  const suffix = kind.slice(dot + 1);
42
65
  // Re-export takes precedence: a re-exported kind resolves to its true owning module,
43
- // not `${prefix-target}.${suffix}` (and bypasses the gate — it's explicitly re-exported).
66
+ // not `${prefix-target}.${suffix}`. It is listed in the re-exporting library's own
67
+ // `exports.kinds` (that is how it got here), so this is not a gate bypass.
44
68
  const reExported = this.reExportedKinds.get(`${prefix}.${suffix}`);
45
- if (reExported) return reExported;
69
+ if (reExported) return { status: "ok", kind: reExported };
46
70
  const realModule = this.importAliases.get(prefix);
47
- if (!realModule) return undefined;
71
+ if (!realModule) return { status: "unknown" };
48
72
  const allowed = this.importedKinds.get(prefix);
49
- if (allowed !== undefined && !allowed.has(suffix)) return undefined;
50
- return `${realModule}.${suffix}`;
73
+ if (allowed !== undefined && !allowed.has(suffix)) {
74
+ return { status: "gated", module: realModule, exported: [...allowed] };
75
+ }
76
+ return { status: "ok", kind: `${realModule}.${suffix}` };
77
+ }
78
+
79
+ /** Resolves "Http.Api" → "http-server.Api". Undefined if the alias is unknown OR the kind
80
+ * is gated out — use `resolveKindResult` when the difference matters. */
81
+ resolveKind(kind: string): string | undefined {
82
+ const r = this.resolveKindResult(kind);
83
+ return r.status === "ok" ? r.kind : undefined;
51
84
  }
52
85
 
53
86
  hasAlias(alias: string): boolean {
@@ -44,10 +44,18 @@ export class AnalysisRegistry {
44
44
  this.defs.registerModuleIdentity(namespace, name);
45
45
  }
46
46
 
47
- registerImport(alias: string, target: string, kinds: string[]): void {
47
+ /** `kinds` is the target's `exports.kinds` gate — only listed kinds resolve, and an
48
+ * empty list exports nothing. Omit it only for a target declaring no `exports.kinds`
49
+ * (the legacy permissive default); see `AliasResolver.registerImport`. */
50
+ registerImport(alias: string, target: string, kinds?: readonly string[]): void {
48
51
  this.aliases.registerImport(alias, target, kinds);
49
52
  }
50
53
 
54
+ /** An alias crossing no import boundary, never gated — `Self`, the `Telo` built-ins. */
55
+ registerUngatedAlias(alias: string, target: string): void {
56
+ this.aliases.registerUngatedAlias(alias, target);
57
+ }
58
+
51
59
  resolveKind(kind: string): string | undefined {
52
60
  return this.aliases.resolveKind(kind);
53
61
  }
@@ -210,6 +218,26 @@ export class AnalysisRegistry {
210
218
  return ctx.definitions?.resolve(kind) ?? (resolved ? ctx.definitions?.resolve(resolved) : undefined);
211
219
  }
212
220
 
221
+ /** A resolver scoped to `def`'s OWN module, for resolving that definition's
222
+ * `extends` target.
223
+ *
224
+ * `extends` aliases are lexically scoped to the declaring library — a library
225
+ * writes `extends: Cache.Store` against its own import map, and `Self.Host`
226
+ * against its own module name. The global alias table knows neither, so
227
+ * {@link resolveDefinition} silently returns undefined for them and callers
228
+ * fall back to an un-inherited view. Mirrors the module-scope selection in
229
+ * `expandedFieldMapForResource`. */
230
+ resolverForDefinition(def: {
231
+ metadata?: { module?: string };
232
+ }): (kind: string) => ResourceDefinition | undefined {
233
+ const ownModule = def?.metadata?.module;
234
+ const scope = (ownModule ? this.aliasesByModule.get(ownModule) : undefined) ?? this.aliases;
235
+ return (kind) => {
236
+ const canonical = scope.resolveKind(kind);
237
+ return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
238
+ };
239
+ }
240
+
213
241
  /** Canonical kinds (`module.Name`) of every definition that extends the given
214
242
  * abstract kind — the concrete implementations a caller may instantiate in
215
243
  * its place. Empty when `kind` is not an abstract or has no implementations. */
package/src/analyzer.ts CHANGED
@@ -896,29 +896,33 @@ export class StaticAnalyzer {
896
896
  // Lets same-library `extends:` work (e.g. `extends: Self.Encoder` for a
897
897
  // concrete kind whose abstract lives in the same Telo.Library) without
898
898
  // requiring a self-import (which would loop the loader). Resolves
899
- // through the same alias machinery as user-declared Telo.Imports
900
- // honours the library's `exports.kinds` list, no special cases.
899
+ // through the same alias machinery as user-declared Telo.Imports.
901
900
  if (moduleName) {
902
901
  // `Self` resolves the library's own kinds UNGATED — a library may reference
903
902
  // its own kinds regardless of `exports.kinds`, which gates importers, not
904
903
  // internal use. This is what lets a library declare an instance of a kind it
905
904
  // does not export (e.g. console's `writeLine`) to enforce a singleton.
906
905
  if (rootModules.has(moduleName)) {
907
- aliases.registerImport("Self", moduleName, []);
906
+ aliases.registerUngatedAlias("Self", moduleName);
908
907
  } else {
909
908
  let libResolver = aliasesByModule.get(moduleName);
910
909
  if (!libResolver) {
911
910
  libResolver = new AliasResolver();
912
911
  aliasesByModule.set(moduleName, libResolver);
913
912
  }
914
- libResolver.registerImport("Self", moduleName, []);
913
+ libResolver.registerUngatedAlias("Self", moduleName);
915
914
  }
916
915
  }
917
916
  }
918
917
  if (m.kind === "Telo.Import") {
919
918
  const alias = m.metadata.name as string;
920
919
  const source = (m as any).source as string | undefined;
921
- const exportedKinds: string[] = (m as any).exports?.kinds ?? [];
920
+ // The target's `exports.kinds` gate, stamped onto this import by
921
+ // `stampExportedKinds` (the `Telo.Import` doc has no `exports` field of its own —
922
+ // the target `Telo.Library` doc it comes from is dropped for non-root modules).
923
+ // Undefined means the target declares no gate, i.e. unrestricted.
924
+ const exportedKinds = (m.metadata as { exportedKinds?: string[] } | undefined)
925
+ ?.exportedKinds;
922
926
  const resolvedModuleName = (m.metadata as any).resolvedModuleName as string | undefined;
923
927
  const resolvedNamespace = (m.metadata as any).resolvedNamespace as
924
928
  | string
@@ -1001,7 +1005,7 @@ export class StaticAnalyzer {
1001
1005
  aliasesByModule.set(ownModule, libResolver);
1002
1006
  }
1003
1007
  if (!libResolver.hasAlias("Self")) {
1004
- libResolver.registerImport("Self", ownModule, []);
1008
+ libResolver.registerUngatedAlias("Self", ownModule);
1005
1009
  }
1006
1010
  }
1007
1011
 
@@ -1227,7 +1231,38 @@ export class StaticAnalyzer {
1227
1231
  // reference-resolution paths: own-module scope first, root/consumer aliases last.
1228
1232
  const ownModule = (m.metadata as { module?: string } | undefined)?.module;
1229
1233
  const scopeResolver = scopeResolverForModule(ownModule, rootModules, aliasesByModule);
1230
- const resolvedKind = scopeResolver?.resolveKind(m.kind) ?? aliases.resolveKind(m.kind);
1234
+ // Either scope resolving it wins; otherwise a gate rejection is more specific than
1235
+ // "unknown alias", so surface that.
1236
+ const scoped = scopeResolver?.resolveKindResult(m.kind);
1237
+ const rooted = aliases.resolveKindResult(m.kind);
1238
+ const kindResult =
1239
+ scoped?.status === "ok"
1240
+ ? scoped
1241
+ : rooted.status === "ok"
1242
+ ? rooted
1243
+ : scoped?.status === "gated"
1244
+ ? scoped
1245
+ : rooted;
1246
+
1247
+ // The gate is checked BEFORE any definition lookup. `defs` is keyed
1248
+ // `<metadata.module>.<Kind>`, so a library whose `metadata.name` equals the alias it
1249
+ // is imported under (e.g. module `Foo` imported as `Foo`) makes the raw `defs.resolve(m.kind)`
1250
+ // below hit directly — which would accept a kind the module does not export, leaving
1251
+ // the analyzer more permissive than the kernel.
1252
+ if (kindResult.status === "gated") {
1253
+ diagnostics.push({
1254
+ severity: DiagnosticSeverity.Error,
1255
+ code: "KIND_NOT_EXPORTED",
1256
+ source: SOURCE,
1257
+ message:
1258
+ `Kind '${m.kind}' is not exported by module '${kindResult.module}'. ` +
1259
+ `Add '${m.kind.slice(m.kind.indexOf(".") + 1)}' to that module's exports.kinds ` +
1260
+ `to make it importable. Exported kinds: ${kindResult.exported.join(", ") || "(none)"}.`,
1261
+ data: { resource, filePath, path: "kind" },
1262
+ });
1263
+ continue;
1264
+ }
1265
+ const resolvedKind = kindResult.status === "ok" ? kindResult.kind : undefined;
1231
1266
  const definition =
1232
1267
  defs.resolve(m.kind) ?? (resolvedKind ? defs.resolve(resolvedKind) : undefined);
1233
1268
  if (!definition) {
package/src/builtins.ts CHANGED
@@ -1,5 +1,27 @@
1
1
  import type { ResourceDefinition } from "@telorun/sdk";
2
2
 
3
+ /** Descriptive provenance a module declares about itself, shared by
4
+ * `Telo.Application` and `Telo.Library`.
5
+ *
6
+ * These are *descriptive*, never *addressing* — nothing resolves, fetches,
7
+ * caches, or publishes based on them, so they do not conflict with
8
+ * identity-is-the-ref (which bans metadata from determining an artifact's
9
+ * location). `repository` is the location of the module's **source code**, in
10
+ * the same spirit as npm's `repository` / `license` / `homepage`. It is named
11
+ * `repository` rather than `source` because `source:` already means "where to
12
+ * fetch a dependency from" in the `imports` map.
13
+ *
14
+ * A publish transport projects these into whatever its backend surfaces —
15
+ * OCI maps them onto the standard `org.opencontainers.image.*` annotations;
16
+ * the HTTP registry stores the manifest verbatim, so they are preserved as
17
+ * declared with nothing to translate. */
18
+ const PROVENANCE_METADATA = {
19
+ description: { type: "string" },
20
+ repository: { type: "string" },
21
+ license: { type: "string" },
22
+ documentation: { type: "string" },
23
+ };
24
+
3
25
  export const KERNEL_BUILTINS: ResourceDefinition[] = [
4
26
  { kind: "Telo.Abstract", metadata: { name: "Template", module: "Telo" } },
5
27
  { kind: "Telo.Abstract", metadata: { name: "Runnable", module: "Telo" } },
@@ -271,6 +293,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
271
293
  version: { type: "string" },
272
294
  source: { type: "string" },
273
295
  module: { type: "string" },
296
+ ...PROVENANCE_METADATA,
274
297
  },
275
298
  required: ["name"],
276
299
  additionalProperties: true,
@@ -497,6 +520,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
497
520
  version: { type: "string" },
498
521
  source: { type: "string" },
499
522
  module: { type: "string" },
523
+ ...PROVENANCE_METADATA,
500
524
  },
501
525
  required: ["name"],
502
526
  additionalProperties: true,
@@ -239,10 +239,16 @@ export function forwardReExportManifests(
239
239
  * maps one of that module's import aliases to the imported module's name. Graph-agnostic —
240
240
  * shared by the CLI graph path and the editor's workspace projection. */
241
241
  export function resolveExportedKinds(
242
- modules: ReadonlyArray<{ module: string; exportsKinds: readonly string[] }>,
242
+ modules: ReadonlyArray<{ module: string; exportsKinds: readonly string[] | undefined }>,
243
243
  aliasToModule: (module: string, alias: string) => string | undefined,
244
244
  ): Map<string, Map<string, string>> {
245
245
  const out = new Map<string, Map<string, string>>();
246
+ // Modules declaring NO `exports.kinds` export every kind they define (the legacy permissive
247
+ // default), so they have no table to look a re-exported suffix up in. `[]` is a real gate
248
+ // that exports nothing — the two must stay distinguishable here.
249
+ const ungated = new Set(
250
+ modules.filter((m) => m.exportsKinds === undefined).map((m) => m.module),
251
+ );
246
252
  const tableFor = (m: string): Map<string, string> => {
247
253
  let t = out.get(m);
248
254
  if (!t) out.set(m, (t = new Map()));
@@ -252,7 +258,7 @@ export function resolveExportedKinds(
252
258
  let changed = false;
253
259
  for (const { module, exportsKinds } of modules) {
254
260
  const table = tableFor(module);
255
- for (const entry of exportsKinds) {
261
+ for (const entry of exportsKinds ?? []) {
256
262
  const { name: suffix, alias } = parseExportEntry(entry);
257
263
  if (table.has(suffix)) continue;
258
264
  if (!alias) {
@@ -261,7 +267,14 @@ export function resolveExportedKinds(
261
267
  continue;
262
268
  }
263
269
  const source = aliasToModule(module, alias);
264
- const canonical = source ? out.get(source)?.get(suffix) : undefined;
270
+ // Re-exporting FROM an ungated module resolves straight to it: the kernel derives
271
+ // the gate from the raw entry list and allows this, so dropping it here would make
272
+ // the analyzer reject a manifest the runtime accepts — the exact shape the migration
273
+ // path relies on (a gated wrapper over an already-published, ungated module).
274
+ const canonical = source
275
+ ? (out.get(source)?.get(suffix) ??
276
+ (ungated.has(source) ? `${source}.${suffix}` : undefined))
277
+ : undefined;
265
278
  if (canonical) {
266
279
  table.set(suffix, canonical);
267
280
  changed = true;
@@ -295,11 +308,41 @@ export function stampReExportedKinds(
295
308
  }
296
309
  }
297
310
 
311
+ /** Stamp `metadata.exportedKinds` (the target's `exports.kinds` gate as bare suffixes) onto
312
+ * every `Telo.Import` whose target declares one, so the analyzer enforces the same gate the
313
+ * kernel does. Stamped on `metadata` for the same reason as `reExportedKinds` — the
314
+ * `Telo.Import` schema forbids extra top-level fields.
315
+ *
316
+ * The gate comes from the DECLARED entries, parsed exactly as the kernel parses them
317
+ * (`parseExportEntry` — `Alias.Kind` yields `Kind`), never from the resolved re-export
318
+ * table. That table drops a re-export whose source module declares no `exports.kinds`, so
319
+ * deriving the gate from it would reject `exports.kinds: [Cee.Thing]` — a gate the kernel
320
+ * honours — as "not exported".
321
+ *
322
+ * A target that declares no `exports.kinds` is absent from `gatedModules` and goes unstamped,
323
+ * which the analyzer reads as unrestricted. That is the legacy permissive default, kept so
324
+ * already-published module versions stay importable; making kinds private by default means
325
+ * stamping every loaded target instead, which breaks those versions (see import-controller). */
326
+ export function stampExportedKinds(
327
+ imports: ReadonlyArray<{ manifest: ResourceManifest; targetModule: string }>,
328
+ declaredKinds: ReadonlyMap<string, readonly string[]>,
329
+ ): void {
330
+ for (const { manifest, targetModule } of imports) {
331
+ const declared = declaredKinds.get(targetModule);
332
+ if (!declared) continue;
333
+ (manifest.metadata as Record<string, unknown>).exportedKinds = [...declared];
334
+ }
335
+ }
336
+
298
337
  /** CLI/kernel adapter: collect re-export specs + alias map from a LoadedGraph. */
299
338
  function forwardReExports(graph: LoadedGraph, result: ResourceManifest[]): void {
300
339
  const ownerSourceOf = new Map<string, string>();
301
340
  const specs: ReExportSpec[] = [];
302
- const kindModules: Array<{ module: string; exportsKinds: string[] }> = [];
341
+ const kindModules: Array<{ module: string; exportsKinds: string[] | undefined }> = [];
342
+ // Modules that DECLARE `exports.kinds` → the gate, as bare suffixes parsed the same way
343
+ // the kernel parses them. A module that declares none is absent, leaving importers ungated
344
+ // (see stampExportedKinds).
345
+ const declaredKinds = new Map<string, readonly string[]>();
303
346
  for (const [source, mod] of graph.modules) {
304
347
  if (source === graph.rootSource) continue; // root is an Application — no exports
305
348
  const libDoc = mod.owner.manifests.find((m) => m && isModuleKind(m.kind)) as
@@ -309,7 +352,10 @@ function forwardReExports(graph: LoadedGraph, result: ResourceManifest[]): void
309
352
  if (!libDoc || !moduleName) continue;
310
353
  ownerSourceOf.set(moduleName, mod.owner.source);
311
354
  specs.push(...reExportSpecsFromExports(moduleName, libDoc.exports?.resources));
312
- kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds ?? [] });
355
+ kindModules.push({ module: moduleName, exportsKinds: libDoc.exports?.kinds });
356
+ if (libDoc.exports?.kinds !== undefined) {
357
+ declaredKinds.set(moduleName, libDoc.exports.kinds.map((e) => parseExportEntry(e).name));
358
+ }
313
359
  }
314
360
  const aliasToModule = (module: string, alias: string): string | undefined => {
315
361
  const ownerSource = ownerSourceOf.get(module);
@@ -331,6 +377,7 @@ function forwardReExports(graph: LoadedGraph, result: ResourceManifest[]): void
331
377
  if (target) imports.push({ manifest: m, targetModule: target });
332
378
  }
333
379
  stampReExportedKinds(imports, exportedKinds);
380
+ stampExportedKinds(imports, declaredKinds);
334
381
  }
335
382
 
336
383
  /** Project a LoadedModule (owner + partials) to a flat ResourceManifest[]
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ export {
17
17
  reExportSpecsFromExports,
18
18
  resolveExportedKinds,
19
19
  selectModuleManifestsForAnalysis,
20
+ stampExportedKinds,
20
21
  stampReExportedKinds,
21
22
  type ParsedExportEntry,
22
23
  type ReExportSpec,