@telorun/analyzer 0.72.0 → 0.73.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 (90) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +50 -13
  3. package/dist/builtins.d.ts.map +1 -1
  4. package/dist/builtins.js +71 -97
  5. package/dist/cel-scope-query.d.ts.map +1 -1
  6. package/dist/cel-scope-query.js +32 -11
  7. package/dist/cel-scope.d.ts.map +1 -1
  8. package/dist/cel-scope.js +1 -0
  9. package/dist/eval-paths.d.ts +36 -5
  10. package/dist/eval-paths.d.ts.map +1 -1
  11. package/dist/eval-paths.js +47 -6
  12. package/dist/extends-resolution.d.ts +12 -0
  13. package/dist/extends-resolution.d.ts.map +1 -1
  14. package/dist/extends-resolution.js +23 -0
  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/nearest-name.d.ts +22 -0
  19. package/dist/nearest-name.d.ts.map +1 -0
  20. package/dist/nearest-name.js +43 -0
  21. package/dist/release/destinations.d.ts +59 -0
  22. package/dist/release/destinations.d.ts.map +1 -0
  23. package/dist/release/destinations.js +75 -0
  24. package/dist/release/index.d.ts +6 -2
  25. package/dist/release/index.d.ts.map +1 -1
  26. package/dist/release/index.js +3 -1
  27. package/dist/release/ledger.d.ts +20 -11
  28. package/dist/release/ledger.d.ts.map +1 -1
  29. package/dist/release/ledger.js +43 -14
  30. package/dist/release/release-plan.d.ts +3 -2
  31. package/dist/release/release-plan.d.ts.map +1 -1
  32. package/dist/release/release-plan.js +12 -19
  33. package/dist/release/workspace-config.d.ts +114 -34
  34. package/dist/release/workspace-config.d.ts.map +1 -1
  35. package/dist/release/workspace-config.js +322 -38
  36. package/dist/release/workspace-schema.d.ts +41 -0
  37. package/dist/release/workspace-schema.d.ts.map +1 -0
  38. package/dist/release/workspace-schema.js +77 -0
  39. package/dist/schema-error-report.d.ts +7 -0
  40. package/dist/schema-error-report.d.ts.map +1 -1
  41. package/dist/schema-error-report.js +9 -4
  42. package/dist/telo-version.d.ts +1 -1
  43. package/dist/telo-version.js +1 -1
  44. package/dist/template-body.d.ts +10 -0
  45. package/dist/template-body.d.ts.map +1 -1
  46. package/dist/template-body.js +25 -0
  47. package/dist/validate-cel-context.d.ts +23 -8
  48. package/dist/validate-cel-context.d.ts.map +1 -1
  49. package/dist/validate-cel-context.js +48 -16
  50. package/dist/validate-exports.d.ts +28 -0
  51. package/dist/validate-exports.d.ts.map +1 -0
  52. package/dist/validate-exports.js +146 -0
  53. package/dist/validate-extends.d.ts.map +1 -1
  54. package/dist/validate-extends.js +37 -1
  55. package/dist/validate-provider-coherence.d.ts +4 -8
  56. package/dist/validate-provider-coherence.d.ts.map +1 -1
  57. package/dist/validate-provider-coherence.js +12 -123
  58. package/dist/validate-references.d.ts.map +1 -1
  59. package/dist/validate-references.js +141 -60
  60. package/dist/validate-template-body.d.ts +36 -0
  61. package/dist/validate-template-body.d.ts.map +1 -0
  62. package/dist/validate-template-body.js +246 -0
  63. package/package.json +3 -2
  64. package/src/analyzer.ts +64 -11
  65. package/src/builtins.ts +85 -97
  66. package/src/cel-scope-query.ts +28 -9
  67. package/src/cel-scope.ts +1 -0
  68. package/src/eval-paths.ts +53 -6
  69. package/src/extends-resolution.ts +24 -0
  70. package/src/index.ts +1 -0
  71. package/src/nearest-name.ts +47 -0
  72. package/src/release/destinations.ts +105 -0
  73. package/src/release/index.ts +33 -2
  74. package/src/release/ledger.ts +51 -21
  75. package/src/release/release-plan.ts +16 -26
  76. package/src/release/workspace-config.ts +483 -47
  77. package/src/release/workspace-schema.ts +99 -0
  78. package/src/schema-error-report.ts +16 -4
  79. package/src/telo-version.ts +1 -1
  80. package/src/template-body.ts +25 -0
  81. package/src/validate-cel-context.ts +57 -16
  82. package/src/validate-exports.ts +185 -0
  83. package/src/validate-extends.ts +43 -0
  84. package/src/validate-provider-coherence.ts +17 -127
  85. package/src/validate-references.ts +141 -60
  86. package/src/validate-template-body.ts +274 -0
  87. package/dist/validate-template-dispatch.d.ts +0 -27
  88. package/dist/validate-template-dispatch.d.ts.map +0 -1
  89. package/dist/validate-template-dispatch.js +0 -95
  90. package/src/validate-template-dispatch.ts +0 -99
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The shape of `telo-workspace.yaml`, declared as data.
3
+ *
4
+ * One declaration read by two consumers — the strict half that reports what a
5
+ * marker got wrong, and the editor's completion list. Two hand-maintained key
6
+ * lists drift, and the drift is silent in the worst direction: an editor
7
+ * offering a key the checker rejects.
8
+ *
9
+ * Written as JSON Schema because that is what the shape *is*, and because the
10
+ * `description` a completion item shows and the type a diagnostic names are the
11
+ * same two facts a schema already carries. It is not compiled by AJV — the
12
+ * strict half walks it, which is what lets a wrong value say `'registry' must be
13
+ * the publish destination base, as a string` instead of a keyword trace.
14
+ */
15
+ /**
16
+ * Every block, and every key in it, is optional — a marker whose whole content
17
+ * is comments is valid, and is what a runner seeds for the cache anchor alone.
18
+ */
19
+ export const WORKSPACE_SCHEMA = {
20
+ release: {
21
+ description: "How `telo release` behaves: where modules are, where they publish, and which of their paths are not release-relevant.",
22
+ properties: {
23
+ registry: {
24
+ type: "string",
25
+ description: "Publish destination base. A module's ref is `<registry>/<its own directory name>`. Omitted: --registry, then TELO_OCI_REGISTRY, then the base each module's own ledger entry recorded.",
26
+ examples: ["oci://ghcr.io/telorun"],
27
+ },
28
+ ignore: {
29
+ type: "string[]",
30
+ description: "Paths under a module whose changes are not release-relevant, so no changelog fragment is asked for. Module-relative, gitignore-style. Declaring it replaces the built-in default; [] ignores nothing.",
31
+ examples: ["**/tests/**", "**/docs/**", "**/plans/**", "**/README.md", "**/CHANGELOG.md"],
32
+ },
33
+ modules: {
34
+ type: "entry[]",
35
+ description: "The subtrees that may hold modules — a place to look, never a module: what makes a directory a module is its telo.yaml. Workspace-relative, gitignore-style, last match wins. A bare string is an entry with no overrides.",
36
+ },
37
+ },
38
+ },
39
+ env: {
40
+ description: "How `telo run` resolves a manifest's environment.",
41
+ properties: {
42
+ roots: {
43
+ type: "string[]",
44
+ description: "How far up the .env walk may climb. Workspace-relative, gitignore-style, matched against each ancestor directory; the nearest match stops the walk. Omitted: the walk stops at this file.",
45
+ },
46
+ files: {
47
+ type: "string[]",
48
+ description: "Which files are collected in each directory, later winning within one directory. Filenames only — a / or a glob is an error. Declaring it replaces the default; [] collects none.",
49
+ examples: [".env", ".env.local"],
50
+ },
51
+ },
52
+ },
53
+ };
54
+ /** Keys a `release.modules` entry may carry in its object form. `path` is the
55
+ * pattern; the rest override the block's own keys, key-wise. */
56
+ export const MODULE_ENTRY_KEYS = {
57
+ path: {
58
+ type: "string",
59
+ description: "The gitignore-style pattern this entry matches modules with.",
60
+ },
61
+ registry: WORKSPACE_SCHEMA.release.properties.registry,
62
+ ignore: WORKSPACE_SCHEMA.release.properties.ignore,
63
+ };
64
+ export const WORKSPACE_BLOCKS = Object.keys(WORKSPACE_SCHEMA);
65
+ /** Built-in when `release.ignore` is absent. Every pattern carries `**\/` — the
66
+ * anchoring at a module root is the defect this replaced, so anchoring some and
67
+ * not others would reproduce it partially. */
68
+ export const DEFAULT_RELEASE_IGNORE = [
69
+ "**/tests/**",
70
+ "**/docs/**",
71
+ "**/plans/**",
72
+ "**/README.md",
73
+ "**/CHANGELOG.md",
74
+ ];
75
+ /** Built-in when `env.files` is absent — the pair the walk hardcoded before it
76
+ * was authorable, in the order it applied them. */
77
+ export const DEFAULT_ENV_FILES = [".env", ".env.local"];
@@ -42,6 +42,13 @@ export interface SchemaIssue {
42
42
  message: string;
43
43
  /** Dotted path to the field (e.g. "config.handler"). Empty string means root. */
44
44
  path: string;
45
+ /** The AJV keyword that produced it, so a consumer can key on WHAT failed
46
+ * rather than on how the sentence reads. Prose is the renderer's to change;
47
+ * a caller matching on it breaks silently when it does, and mis-fires on any
48
+ * other issue whose text happens to quote the same name. */
49
+ keyword?: string;
50
+ /** For `required`, the property that is missing. */
51
+ missingProperty?: string;
45
52
  }
46
53
  export declare function formatSingleError(err: AjvErrorLike): string;
47
54
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"schema-error-report.d.ts","sourceRoot":"","sources":["../src/schema-error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;gDACgD;AAChD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAuCD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA6B3D;AAoGD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,EAAE,CAsF5F;AAqHD;;sFAEsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAaxD;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAKrF;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIjF"}
1
+ {"version":3,"file":"schema-error-report.d.ts","sourceRoot":"","sources":["../src/schema-error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;gDACgD;AAChD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb;;;iEAG6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAuCD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CA6B3D;AAoGD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,EAAE,CAsF5F;AAqHD;;sFAEsF;AACtF,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAaxD;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAUrF;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIjF"}
@@ -386,10 +386,15 @@ export function ajvErrorToPath(err) {
386
386
  }
387
387
  /** Reduced, path-anchored issues — what a diagnostic list is built from. */
388
388
  export function schemaIssues(errors) {
389
- return reduceSchemaErrors(errors).map((err) => ({
390
- message: formatSingleError(err),
391
- path: ajvErrorToPath(err),
392
- }));
389
+ return reduceSchemaErrors(errors).map((err) => {
390
+ const missing = (err.params ?? {}).missingProperty;
391
+ return {
392
+ message: formatSingleError(err),
393
+ path: ajvErrorToPath(err),
394
+ ...(err.keyword ? { keyword: err.keyword } : {}),
395
+ ...(typeof missing === "string" ? { missingProperty: missing } : {}),
396
+ };
397
+ });
393
398
  }
394
399
  /** Reduced, rendered as one sentence — what a thrown runtime error carries. */
395
400
  export function formatAjvErrors(errors) {
@@ -1,3 +1,3 @@
1
1
  /** The surface generation this analyzer implements. */
2
- export declare const TELO_SURFACE_VERSION = "0.88.0";
2
+ export declare const TELO_SURFACE_VERSION = "0.89.0";
3
3
  //# sourceMappingURL=telo-version.d.ts.map
@@ -5,4 +5,4 @@
5
5
  // its release identity, this is the scale a module's `requires.telo` range is
6
6
  // written against, and every kernel in every language reports the same scale.
7
7
  /** The surface generation this analyzer implements. */
8
- export const TELO_SURFACE_VERSION = "0.88.0";
8
+ export const TELO_SURFACE_VERSION = "0.89.0";
@@ -33,6 +33,16 @@ export interface TemplateBody {
33
33
  /** The template bodies a manifest declares — empty for anything that is not a
34
34
  * `Telo.Definition` with a `resources:` array. */
35
35
  export declare function templateBodies(m: ResourceManifest, registry: DefinitionRegistry, aliases: AliasResolver | undefined, scopes: ModuleScopes | undefined): TemplateBody[];
36
+ /** What a definition's dispatch slot names: the `resources:` entry a `!ref` at
37
+ * `slot` (`invoke` / `provide` / `run` / `mount`) resolves to, with its kind.
38
+ * The entry is matched by its LITERAL `metadata.name`; a slot holding anything
39
+ * but a `!ref`, or naming no entry, resolves to nothing — `validate-template-body`
40
+ * is what reports either. `Self.<entry>` is the explicit self-qualifier. */
41
+ export declare function dispatchTargetOf(definition: Record<string, any>, slot: string): {
42
+ entry: Record<string, any> | undefined;
43
+ kind: string;
44
+ name: string;
45
+ } | undefined;
36
46
  /** True when a CEL path lies at or inside a body's own subtree. */
37
47
  export declare function pathInBody(path: string, prefix: string): boolean;
38
48
  /** The body a CEL path belongs to, or undefined for a path in the definition's
@@ -1 +1 @@
1
- {"version":3,"file":"template-body.d.ts","sourceRoot":"","sources":["../src/template-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B;qBACiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,kBAAkB,GAAG,SAAS,CAAC;CAC5C;AAED;mDACmD;AACnD,wBAAgB,cAAc,CAC5B,CAAC,EAAE,gBAAgB,EACnB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,YAAY,EAAE,CA0BhB;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;+EAE+E;AAC/E,wBAAgB,WAAW,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EACtD,MAAM,EAAE,SAAS,CAAC,EAAE,EACpB,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,SAAS,CAEf;AAED;;;;6CAI6C;AAC7C,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQxF"}
1
+ {"version":3,"file":"template-body.d.ts","sourceRoot":"","sources":["../src/template-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B;qBACiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,kBAAkB,GAAG,SAAS,CAAC;CAC5C;AAED;mDACmD;AACnD,wBAAgB,cAAc,CAC5B,CAAC,EAAE,gBAAgB,EACnB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,YAAY,EAAE,CA0BhB;AAED;;;;6EAI6E;AAC7E,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAcpF;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;+EAE+E;AAC/E,wBAAgB,WAAW,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EACtD,MAAM,EAAE,SAAS,CAAC,EAAE,EACpB,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,SAAS,CAEf;AAED;;;;6CAI6C;AAC7C,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQxF"}
@@ -1,3 +1,4 @@
1
+ import { isRefSentinel } from "@telorun/templating";
1
2
  import { moduleAliasScope } from "./module-alias-scope.js";
2
3
  /** The template bodies a manifest declares — empty for anything that is not a
3
4
  * `Telo.Definition` with a `resources:` array. */
@@ -30,6 +31,30 @@ export function templateBodies(m, registry, aliases, scopes) {
30
31
  }
31
32
  return out;
32
33
  }
34
+ /** What a definition's dispatch slot names: the `resources:` entry a `!ref` at
35
+ * `slot` (`invoke` / `provide` / `run` / `mount`) resolves to, with its kind.
36
+ * The entry is matched by its LITERAL `metadata.name`; a slot holding anything
37
+ * but a `!ref`, or naming no entry, resolves to nothing — `validate-template-body`
38
+ * is what reports either. `Self.<entry>` is the explicit self-qualifier. */
39
+ export function dispatchTargetOf(definition, slot) {
40
+ const value = definition[slot];
41
+ if (!isRefSentinel(value))
42
+ return undefined;
43
+ const source = value.source;
44
+ const name = source.startsWith("Self.") ? source.slice("Self.".length) : source;
45
+ const bodies = definition.resources;
46
+ if (!Array.isArray(bodies))
47
+ return undefined;
48
+ for (const body of bodies) {
49
+ if (!body || typeof body !== "object")
50
+ continue;
51
+ const entry = body;
52
+ if (entry.metadata?.name !== name || typeof entry.kind !== "string")
53
+ continue;
54
+ return { entry, kind: entry.kind, name };
55
+ }
56
+ return undefined;
57
+ }
33
58
  /** True when a CEL path lies at or inside a body's own subtree. */
34
59
  export function pathInBody(path, prefix) {
35
60
  return (path === prefix || path.startsWith(`${prefix}.`) || path.startsWith(`${prefix}[`));
@@ -13,6 +13,11 @@ export interface ContextResolveOpts {
13
13
  aliases?: {
14
14
  resolveKind(kind: string): string | undefined;
15
15
  };
16
+ /** Per-declaring-module alias tables, so a kind read off a forwarded
17
+ * definition resolves in the module that wrote it. */
18
+ aliasesByModule?: ReadonlyMap<string, {
19
+ resolveKind(kind: string): string | undefined;
20
+ }>;
16
21
  allManifests?: Record<string, any>[];
17
22
  }
18
23
  export declare function resolveTypeFieldToSchema(value: unknown, allManifests: Record<string, any>[], ancestry?: ReadonlySet<string>): Record<string, any> | undefined;
@@ -39,18 +44,20 @@ export declare function resolveTypeFieldToSchema(value: unknown, allManifests: R
39
44
  * Example: `properties.self.x-telo-context-from-root: "schema"` reads
40
45
  * `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
41
46
  *
42
- * - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
43
- * resolves it via the definition registry, and returns that kind's `<field>` schema
44
- * (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
45
- * target's declared output shape.
47
+ * - `x-telo-context-from-ref-kind`: reads a KIND from `manifestRoot.<refPath>` —
48
+ * a field holding a kind name, or a definition's dispatch slot holding a
49
+ * `!ref` to a `resources:` entry (whose kind it is) — and returns the
50
+ * `<field>` schema (e.g. `outputType`/`inputType`) the entry itself declares,
51
+ * else the one the kind's definition declares. Used to type `result` against
52
+ * the dispatch target's declared output shape.
46
53
  *
47
54
  * Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
48
55
  *
49
- * Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
50
- * `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
51
- * and returns the `outputType` schema.
56
+ * Example: `x-telo-context-from-ref-kind: "provide#outputType"` resolves
57
+ * `manifestRoot.provide` (`!ref source`) to the entry named `source`, and
58
+ * returns its `outputType` schema, or its kind's.
52
59
  *
53
- * Accepts either a single string or an array of strings. With an array, paths
60
+ * Accepts either a single string or an array of strings. With an array, slots
54
61
  * are tried in order and the first one that resolves to a usable schema wins —
55
62
  * used by `result:` to find its dispatch target under whichever entry-point
56
63
  * field (`provide:` or `invoke:`) the definition declares.
@@ -70,6 +77,14 @@ export declare function resolveContextAnnotations(schema: Record<string, any>, m
70
77
  * e.g. exprPath="routes[0].inputs.q", scope="$.routes[*].inputs" → manifest.routes[0]
71
78
  */
72
79
  export declare function getManifestItem(exprPath: string, scope: string, manifest: Record<string, any>): Record<string, any>;
80
+ /** The kind an `x-telo-context-from-ref-kind` path names: a field holding a
81
+ * kind NAME (`targetKind`), or a definition's dispatch slot holding a `!ref`
82
+ * to a `resources:` entry, which carries the kind — and, where it narrows
83
+ * one, the contract field itself. */
84
+ export declare function kindAtPath(manifestRoot: Record<string, any>, refPath: string): {
85
+ kind: string;
86
+ entry?: Record<string, any>;
87
+ } | undefined;
73
88
  /**
74
89
  * Walk a JSON Schema tree and collect all `x-telo-context` annotations,
75
90
  * returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
@@ -1 +1 @@
1
- {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAatF,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AA2B3E,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AA4CD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0EjC;AAyID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAoLrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAuBrB;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD"}
1
+ {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAetF,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AA2B3E,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF;2DACuD;IACvD,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE;QAAE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AA4CD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0EjC;AAyID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgMrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAuBrB;AAqBD;;;sCAGsC;AACtC,wBAAgB,UAAU,CACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,GAAG,SAAS,CAa3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD"}
@@ -1,7 +1,9 @@
1
1
  export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
2
2
  import { elementSchemaOf, isLiveSlot, mergeTypeSchemas, parseCanonicalTypeSchemaId, } from "@telorun/sdk";
3
3
  import { KERNEL_BUILTINS } from "./builtins.js";
4
+ import { moduleAliasScope } from "./module-alias-scope.js";
4
5
  import { withRefSlotsAsReadings } from "./ref-slot-reading.js";
6
+ import { dispatchTargetOf } from "./template-body.js";
5
7
  // Where CEL is evaluated is one reader (`eval-paths.ts`), and the region half of
6
8
  // it moved there so the scope walk and the `x-telo-eval` walk answer the same
7
9
  // question in one place. Re-exported: this module is where every existing
@@ -279,18 +281,20 @@ function collectionBindingWithheld(schema, manifestRoot, allManifests) {
279
281
  * Example: `properties.self.x-telo-context-from-root: "schema"` reads
280
282
  * `manifestRoot.schema` and uses it as the schema of the `self` CEL variable.
281
283
  *
282
- * - `x-telo-context-from-ref-kind`: reads a kind name from `manifestRoot.<refPath>`,
283
- * resolves it via the definition registry, and returns that kind's `<field>` schema
284
- * (e.g. `outputType`/`inputType`). Used to type `result` against the dispatch
285
- * target's declared output shape.
284
+ * - `x-telo-context-from-ref-kind`: reads a KIND from `manifestRoot.<refPath>` —
285
+ * a field holding a kind name, or a definition's dispatch slot holding a
286
+ * `!ref` to a `resources:` entry (whose kind it is) — and returns the
287
+ * `<field>` schema (e.g. `outputType`/`inputType`) the entry itself declares,
288
+ * else the one the kind's definition declares. Used to type `result` against
289
+ * the dispatch target's declared output shape.
286
290
  *
287
291
  * Syntax: `<refPath>#<field>` — slashes traverse the manifest tree.
288
292
  *
289
- * Example: `x-telo-context-from-ref-kind: "provide/kind#outputType"` reads
290
- * `manifestRoot.provide.kind` as a kind name, looks up the kind's Telo.Definition,
291
- * and returns the `outputType` schema.
293
+ * Example: `x-telo-context-from-ref-kind: "provide#outputType"` resolves
294
+ * `manifestRoot.provide` (`!ref source`) to the entry named `source`, and
295
+ * returns its `outputType` schema, or its kind's.
292
296
  *
293
- * Accepts either a single string or an array of strings. With an array, paths
297
+ * Accepts either a single string or an array of strings. With an array, slots
294
298
  * are tried in order and the first one that resolves to a usable schema wins —
295
299
  * used by `result:` to find its dispatch target under whichever entry-point
296
300
  * field (`provide:` or `invoke:`) the definition declares.
@@ -311,7 +315,7 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
311
315
  const normalizedOpts = Array.isArray(opts)
312
316
  ? { allManifests: opts }
313
317
  : (opts ?? {});
314
- const { manifestRoot = manifestItem, defs, aliases, allManifests } = normalizedOpts;
318
+ const { manifestRoot = manifestItem, defs, aliases, aliasesByModule, allManifests } = normalizedOpts;
315
319
  const from = schema["x-telo-context-from"];
316
320
  if (from) {
317
321
  const navigated = navigatePath(manifestItem, from.split("/"));
@@ -391,20 +395,28 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
391
395
  }
392
396
  }
393
397
  if (defs) {
398
+ // The kind is read off THIS document, so it is spelled in the alias scope
399
+ // of the module that declared it — a library's dispatch target is written
400
+ // through an import the consumer has no reason to have.
401
+ const scope = moduleAliasScope(manifestRoot.metadata, aliases, aliasesByModule);
394
402
  for (const fromRefKind of fromRefKinds) {
395
403
  const hashIdx = fromRefKind.indexOf("#");
396
404
  if (hashIdx <= 0)
397
405
  continue;
398
406
  const refPath = fromRefKind.slice(0, hashIdx);
399
407
  const field = fromRefKind.slice(hashIdx + 1);
400
- const kindValue = navigatePath(manifestRoot, refPath.split("/"));
401
- if (typeof kindValue !== "string" || kindValue.length === 0)
408
+ const target = kindAtPath(manifestRoot, refPath);
409
+ if (!target)
402
410
  continue;
403
- const canonical = aliases?.resolveKind(kindValue) ?? kindValue;
404
- const def = defs.resolve(canonical);
405
- const typeField = def
406
- ? def[field]
407
- : undefined;
411
+ // The entry's own field first — a per-instance narrowing (`outputType:`
412
+ // on the entry) is what the kernel binds — then the kind's.
413
+ const own = target.entry?.[field];
414
+ const ownResolved = own !== undefined ? resolveTypeFieldToSchema(own, allManifests ?? []) : undefined;
415
+ if (ownResolved && typeof ownResolved === "object")
416
+ return ownResolved;
417
+ const canonical = scope?.resolveKind(target.kind) ?? target.kind;
418
+ const def = defs.resolve(canonical) ?? defs.resolve(target.kind);
419
+ const typeField = def ? def[field] : undefined;
408
420
  const resolved = resolveTypeFieldToSchema(typeField, allManifests ?? []);
409
421
  if (resolved && typeof resolved === "object") {
410
422
  return resolved;
@@ -516,6 +528,26 @@ function navigatePath(obj, segments) {
516
528
  }
517
529
  return cur;
518
530
  }
531
+ /** The kind an `x-telo-context-from-ref-kind` path names: a field holding a
532
+ * kind NAME (`targetKind`), or a definition's dispatch slot holding a `!ref`
533
+ * to a `resources:` entry, which carries the kind — and, where it narrows
534
+ * one, the contract field itself. */
535
+ export function kindAtPath(manifestRoot, refPath) {
536
+ const segments = refPath.split("/");
537
+ const value = navigatePath(manifestRoot, segments);
538
+ if (typeof value === "string")
539
+ return value.length > 0 ? { kind: value } : undefined;
540
+ // The dispatch-slot reading is a fact about ONE built-in document, not about
541
+ // the annotation: `resources:` is a `Telo.Definition`'s entry list. The
542
+ // annotation itself is generic vocabulary any kind may declare, and without
543
+ // this gate a third-party kind whose own `resources:` array means something
544
+ // else would have a slot silently resolved against it — a wrong answer with
545
+ // nothing reporting it, which is worse than no answer.
546
+ if (segments.length !== 1 || manifestRoot.kind !== "Telo.Definition")
547
+ return undefined;
548
+ const dispatch = dispatchTargetOf(manifestRoot, segments[0]);
549
+ return dispatch ? { kind: dispatch.kind, entry: dispatch.entry } : undefined;
550
+ }
519
551
  /**
520
552
  * Walk a JSON Schema tree and collect all `x-telo-context` annotations,
521
553
  * returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
@@ -0,0 +1,28 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ import { type AnalysisDiagnostic } from "./types.js";
5
+ /**
6
+ * A LIBRARY'S EXPORT LIST IS CHECKED WHERE IT IS WRITTEN.
7
+ *
8
+ * `exports.kinds` and `exports.resources` are a library's public contract, and
9
+ * they were read as a GATE only: a listed name resolved when a consumer asked
10
+ * for it, and one that named nothing failed in the consumer's file
11
+ * (`UNDEFINED_KIND` on `kind: L.Client`, with nothing wrong in that file) while
12
+ * the author who could fix it saw a clean check. Both entries are resolved here,
13
+ * against what the library declares:
14
+ *
15
+ * - a bare kind names a `Telo.Definition` / `Telo.Abstract` of this module, and
16
+ * `Alias.Kind` re-exports through an import declared in this file
17
+ * (`EXPORT_KIND_UNKNOWN`). The one mistake that reads as intended — a bare
18
+ * name that IS a kind, of an imported module — gets the re-export spelling
19
+ * in its message, because a clean check was read as confirmation it worked;
20
+ * - a bare instance names a resource declared in this module, and
21
+ * `Alias.name` an instance the aliased import exports
22
+ * (`EXPORT_RESOURCE_UNKNOWN`).
23
+ *
24
+ * Runs on the library as an ENTRY — a consumer's flattened analysis drops the
25
+ * library doc, and the list is not the consumer's to fix. Browser-safe.
26
+ */
27
+ export declare function validateExports(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, rootModules: ReadonlySet<string>): AnalysisDiagnostic[];
28
+ //# sourceMappingURL=validate-exports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-exports.d.ts","sourceRoot":"","sources":["../src/validate-exports.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,kBAAkB,EAAE,CAoJtB"}
@@ -0,0 +1,146 @@
1
+ import { nearestName } from "./nearest-name.js";
2
+ import { parseExportEntry } from "./flatten-for-analyzer.js";
3
+ import { DiagnosticSeverity } from "./types.js";
4
+ const SOURCE = "telo-analyzer";
5
+ /**
6
+ * A LIBRARY'S EXPORT LIST IS CHECKED WHERE IT IS WRITTEN.
7
+ *
8
+ * `exports.kinds` and `exports.resources` are a library's public contract, and
9
+ * they were read as a GATE only: a listed name resolved when a consumer asked
10
+ * for it, and one that named nothing failed in the consumer's file
11
+ * (`UNDEFINED_KIND` on `kind: L.Client`, with nothing wrong in that file) while
12
+ * the author who could fix it saw a clean check. Both entries are resolved here,
13
+ * against what the library declares:
14
+ *
15
+ * - a bare kind names a `Telo.Definition` / `Telo.Abstract` of this module, and
16
+ * `Alias.Kind` re-exports through an import declared in this file
17
+ * (`EXPORT_KIND_UNKNOWN`). The one mistake that reads as intended — a bare
18
+ * name that IS a kind, of an imported module — gets the re-export spelling
19
+ * in its message, because a clean check was read as confirmation it worked;
20
+ * - a bare instance names a resource declared in this module, and
21
+ * `Alias.name` an instance the aliased import exports
22
+ * (`EXPORT_RESOURCE_UNKNOWN`).
23
+ *
24
+ * Runs on the library as an ENTRY — a consumer's flattened analysis drops the
25
+ * library doc, and the list is not the consumer's to fix. Browser-safe.
26
+ */
27
+ export function validateExports(manifests, registry, aliases, rootModules) {
28
+ const out = [];
29
+ for (const lib of manifests) {
30
+ if (lib.kind !== "Telo.Library")
31
+ continue;
32
+ const moduleName = lib.metadata?.name;
33
+ if (!moduleName || !rootModules.has(moduleName))
34
+ continue;
35
+ const exports = lib.exports;
36
+ if (!exports || typeof exports !== "object")
37
+ continue;
38
+ const filePath = lib.metadata?.source;
39
+ const resource = { kind: lib.kind, name: moduleName };
40
+ const own = (m) => {
41
+ const mod = m.metadata?.module;
42
+ return mod === undefined || mod === moduleName;
43
+ };
44
+ const declaredKinds = new Set();
45
+ const instances = new Set();
46
+ const importAliases = new Map();
47
+ const forwardedByModule = new Map();
48
+ for (const m of manifests) {
49
+ const name = m.metadata?.name;
50
+ if (typeof name !== "string")
51
+ continue;
52
+ const meta = m.metadata;
53
+ if (meta.forwardedExport && meta.module) {
54
+ let set = forwardedByModule.get(meta.module);
55
+ if (!set)
56
+ forwardedByModule.set(meta.module, (set = new Set()));
57
+ set.add(name);
58
+ continue;
59
+ }
60
+ if (!own(m))
61
+ continue;
62
+ if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract")
63
+ declaredKinds.add(name);
64
+ else if (m.kind === "Telo.Import")
65
+ importAliases.set(name, meta.resolvedModuleName);
66
+ else if (m.kind !== "Telo.Library" && m.kind !== "Telo.Application")
67
+ instances.add(name);
68
+ }
69
+ const report = (code, path, message, fix) => out.push({
70
+ severity: DiagnosticSeverity.Error,
71
+ code,
72
+ source: SOURCE,
73
+ message: `Telo.Library/${moduleName}: ${message}`,
74
+ data: { resource, filePath, path, ...(fix ? { fix: { replacement: fix } } : {}) },
75
+ });
76
+ const kinds = exports.kinds;
77
+ if (Array.isArray(kinds)) {
78
+ kinds.forEach((entry, i) => {
79
+ if (typeof entry !== "string")
80
+ return;
81
+ const path = `exports.kinds[${i}]`;
82
+ const { alias, name } = parseExportEntry(entry);
83
+ if (alias) {
84
+ if (!importAliases.has(alias)) {
85
+ report("EXPORT_KIND_UNKNOWN", path, `'exports.kinds: ${entry}' re-exports through '${alias}', which is not an import ` +
86
+ `of this library. Imports: ${[...importAliases.keys()].join(", ") || "(none)"}.`);
87
+ return;
88
+ }
89
+ const canonical = aliases.resolveKind(entry);
90
+ if (canonical && !registry.resolve(canonical)) {
91
+ report("EXPORT_KIND_UNKNOWN", path, `'exports.kinds: ${entry}' names no kind '${name}' exported by '${alias}'.`);
92
+ }
93
+ return;
94
+ }
95
+ if (declaredKinds.has(name))
96
+ return;
97
+ // The natural first attempt at a re-export: the imported kind's bare
98
+ // suffix. It resolves as a kind — just not as one this library owns.
99
+ const viaImport = [...importAliases.keys()].find((a) => {
100
+ const canonical = aliases.resolveKind(`${a}.${name}`);
101
+ return canonical !== undefined && registry.resolve(canonical) !== undefined;
102
+ });
103
+ if (viaImport) {
104
+ report("EXPORT_KIND_UNKNOWN", path, `'exports.kinds: ${name}' is not a kind this library declares — it is ` +
105
+ `'${viaImport}.${name}', an imported kind. Re-export it as '${viaImport}.${name}', ` +
106
+ `or declare a local kind that extends it.`, `${viaImport}.${name}`);
107
+ return;
108
+ }
109
+ const suggestion = nearestName(name, [...declaredKinds]);
110
+ report("EXPORT_KIND_UNKNOWN", path, `'exports.kinds: ${name}' names no Telo.Definition or Telo.Abstract of this library. ` +
111
+ `Declared: ${[...declaredKinds].join(", ") || "(none)"}.` +
112
+ (suggestion ? ` Did you mean '${suggestion}'?` : ""), suggestion);
113
+ });
114
+ }
115
+ const resources = exports.resources;
116
+ if (Array.isArray(resources)) {
117
+ resources.forEach((entry, i) => {
118
+ if (typeof entry !== "string")
119
+ return;
120
+ const path = `exports.resources[${i}]`;
121
+ const { alias, name } = parseExportEntry(entry);
122
+ if (alias) {
123
+ if (!importAliases.has(alias)) {
124
+ report("EXPORT_RESOURCE_UNKNOWN", path, `'exports.resources: ${entry}' re-exports through '${alias}', which is not an ` +
125
+ `import of this library. Imports: ${[...importAliases.keys()].join(", ") || "(none)"}.`);
126
+ return;
127
+ }
128
+ const targetModule = importAliases.get(alias);
129
+ const exported = targetModule ? forwardedByModule.get(targetModule) : undefined;
130
+ if (exported && !exported.has(name)) {
131
+ report("EXPORT_RESOURCE_UNKNOWN", path, `'exports.resources: ${entry}' names no instance '${name}' exported by '${alias}'. ` +
132
+ `Exported: ${[...exported].join(", ") || "(none)"}.`);
133
+ }
134
+ return;
135
+ }
136
+ if (instances.has(name))
137
+ return;
138
+ const suggestion = nearestName(name, [...instances]);
139
+ report("EXPORT_RESOURCE_UNKNOWN", path, `'exports.resources: ${name}' names no resource declared in this library. ` +
140
+ `Declared: ${[...instances].join(", ") || "(none)"}.` +
141
+ (suggestion ? ` Did you mean '${suggestion}'?` : ""), suggestion);
142
+ });
143
+ }
144
+ }
145
+ return out;
146
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-extends.d.ts","sourceRoot":"","sources":["../src/validate-extends.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAiCzE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAqMtB"}
1
+ {"version":3,"file":"validate-extends.d.ts","sourceRoot":"","sources":["../src/validate-extends.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AASnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAiCzE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CA2OtB"}
@@ -1,4 +1,4 @@
1
- import { controllerBearingAncestor, effectiveAuthorSchema, inheritedCapability, isInheritedDelegation, } from "./extends-resolution.js";
1
+ import { controllerBearingAncestor, effectiveAuthorSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, } from "./extends-resolution.js";
2
2
  import { DiagnosticSeverity } from "./types.js";
3
3
  const SOURCE = "telo-analyzer";
4
4
  /** Alias-form pattern for `extends`: "<Alias>.<AbstractName>", two PascalCase segments. */
@@ -59,6 +59,39 @@ export function validateExtends(manifests, registry, aliases) {
59
59
  const filePath = m.metadata?.source;
60
60
  const resource = { kind: m.kind, name };
61
61
  const label = `${m.kind}/${name}`;
62
+ // `base:` is read on exactly one path — the inherited-controller one, which
63
+ // the kernel takes only when the definition has NO body of its own. A
64
+ // `resources:` block or a dispatch slot selects the template path, and
65
+ // `base:` is then never evaluated: the kind silently became an empty
66
+ // template, and the failure landed in the CONSUMER's resource as a `{}`
67
+ // where a parent instance should have been. Refused here, where the two
68
+ // blocks sit side by side, and at registration in the kernel.
69
+ //
70
+ // WHETHER there is a body is `hasOwnControllerOrTemplate` — the same
71
+ // predicate the kernel branches on, so the two halves cannot disagree about
72
+ // what a body is. Deciding it here with a key scan did disagree: an empty
73
+ // `resources: []` is falsy to a length test and truthy to the kernel's, so
74
+ // it passed `telo check` and threw `ERR_BASE_WITH_TEMPLATE_BODY` at boot —
75
+ // a brand-new guard shipping with the very gap it was written to close. The
76
+ // scan survives for one job only: naming the offending key in the message.
77
+ if (m.base != null) {
78
+ const hasBody = hasOwnControllerOrTemplate(m);
79
+ const bodyKey = ["resources", "controllers", "invoke", "run", "provide", "mount"].find((key) => m[key] !== undefined) ?? "resources";
80
+ if (hasBody) {
81
+ diagnostics.push({
82
+ severity: DiagnosticSeverity.Error,
83
+ code: "BASE_WITH_TEMPLATE_BODY",
84
+ source: SOURCE,
85
+ message: `${label}: 'base:' maps this kind's config onto the inherited controller of ` +
86
+ `'${typeof extendsOf(m) === "string" ? extendsOf(m) : "<no extends>"}', so the ` +
87
+ `definition may not also declare '${bodyKey}:'. With '${bodyKey}:' the kind is a ` +
88
+ `template and 'base:' is never evaluated. Either drop 'base:' and dispatch to a ` +
89
+ `'resources:' entry with '!ref', or drop '${bodyKey}:' and build the parent's config ` +
90
+ `in 'base:' alone.`,
91
+ data: { resource, filePath, path: bodyKey },
92
+ });
93
+ }
94
+ }
62
95
  // --- extends validation ---
63
96
  // At this point `m.extends` is whatever the manifest declared (alias form, e.g.
64
97
  // "Ai.Model"). Resolve through the AliasResolver to the canonical form before
@@ -219,3 +252,6 @@ export function validateExtends(manifests, registry, aliases) {
219
252
  }
220
253
  return diagnostics;
221
254
  }
255
+ function extendsOf(m) {
256
+ return m.extends;
257
+ }