@telorun/analyzer 0.70.0 → 0.71.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 (83) hide show
  1. package/dist/analysis-registry.d.ts.map +1 -1
  2. package/dist/analysis-registry.js +4 -5
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +50 -8
  5. package/dist/call-graph.d.ts.map +1 -1
  6. package/dist/call-graph.js +10 -4
  7. package/dist/cel-scope-query.d.ts +14 -0
  8. package/dist/cel-scope-query.d.ts.map +1 -1
  9. package/dist/cel-scope-query.js +36 -6
  10. package/dist/definition-registry.d.ts.map +1 -1
  11. package/dist/definition-registry.js +3 -4
  12. package/dist/flatten-for-analyzer.d.ts +2 -2
  13. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  14. package/dist/flatten-for-analyzer.js +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/manifest-visitor.d.ts.map +1 -1
  19. package/dist/manifest-visitor.js +11 -3
  20. package/dist/module-alias-scope.d.ts +65 -0
  21. package/dist/module-alias-scope.d.ts.map +1 -0
  22. package/dist/module-alias-scope.js +25 -0
  23. package/dist/{zone-module-documents.d.ts → module-documents.d.ts} +11 -7
  24. package/dist/module-documents.d.ts.map +1 -0
  25. package/dist/ref-sentinel-target.d.ts +38 -0
  26. package/dist/ref-sentinel-target.d.ts.map +1 -0
  27. package/dist/ref-sentinel-target.js +13 -0
  28. package/dist/resolve-schema-type-refs.d.ts.map +1 -1
  29. package/dist/resolve-schema-type-refs.js +2 -1
  30. package/dist/resolve-throws-union.d.ts +18 -1
  31. package/dist/resolve-throws-union.d.ts.map +1 -1
  32. package/dist/resolve-throws-union.js +93 -9
  33. package/dist/resolve-zone-requirements.d.ts +3 -3
  34. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  35. package/dist/resolve-zone-requirements.js +12 -12
  36. package/dist/telo-version.d.ts +1 -1
  37. package/dist/telo-version.js +1 -1
  38. package/dist/template-body.d.ts.map +1 -1
  39. package/dist/template-body.js +2 -4
  40. package/dist/types.d.ts +3 -3
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/validate-invocation-contract.d.ts +5 -0
  43. package/dist/validate-invocation-contract.d.ts.map +1 -1
  44. package/dist/validate-invocation-contract.js +114 -3
  45. package/dist/validate-logging.d.ts.map +1 -1
  46. package/dist/validate-logging.js +2 -2
  47. package/dist/validate-references.d.ts +16 -5
  48. package/dist/validate-references.d.ts.map +1 -1
  49. package/dist/validate-references.js +30 -12
  50. package/dist/validate-resource-inputs.d.ts +1 -26
  51. package/dist/validate-resource-inputs.d.ts.map +1 -1
  52. package/dist/validate-resource-inputs.js +12 -2
  53. package/dist/validate-schema-type-refs.d.ts.map +1 -1
  54. package/dist/validate-schema-type-refs.js +2 -1
  55. package/dist/validate-throws-coverage.d.ts +5 -1
  56. package/dist/validate-throws-coverage.d.ts.map +1 -1
  57. package/dist/validate-throws-coverage.js +6 -2
  58. package/package.json +2 -2
  59. package/src/analysis-registry.ts +4 -5
  60. package/src/analyzer.ts +61 -10
  61. package/src/call-graph.ts +9 -3
  62. package/src/cel-scope-query.ts +45 -8
  63. package/src/definition-registry.ts +3 -6
  64. package/src/flatten-for-analyzer.ts +3 -3
  65. package/src/index.ts +2 -2
  66. package/src/manifest-visitor.ts +11 -3
  67. package/src/module-alias-scope.ts +94 -0
  68. package/src/{zone-module-documents.ts → module-documents.ts} +10 -6
  69. package/src/ref-sentinel-target.ts +46 -0
  70. package/src/resolve-schema-type-refs.ts +2 -1
  71. package/src/resolve-throws-union.ts +123 -8
  72. package/src/resolve-zone-requirements.ts +14 -14
  73. package/src/telo-version.ts +1 -1
  74. package/src/template-body.ts +2 -5
  75. package/src/types.ts +3 -3
  76. package/src/validate-invocation-contract.ts +128 -2
  77. package/src/validate-logging.ts +2 -3
  78. package/src/validate-references.ts +41 -11
  79. package/src/validate-resource-inputs.ts +26 -3
  80. package/src/validate-schema-type-refs.ts +2 -1
  81. package/src/validate-throws-coverage.ts +12 -1
  82. package/dist/zone-module-documents.d.ts.map +0 -1
  83. /package/dist/{zone-module-documents.js → module-documents.js} +0 -0
@@ -1,5 +1,8 @@
1
- import { effectiveContractField, mappingFieldFor, needsContractMapping, } from "./extends-resolution.js";
1
+ import { ancestorChain, effectiveContractField, mappingFieldFor, needsContractMapping, } from "./extends-resolution.js";
2
+ import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
3
+ import { moduleAliasScope } from "./module-alias-scope.js";
2
4
  import { buildReferenceFieldMap, isRefEntry } from "./reference-field-map.js";
5
+ import { checkSchemaCompatibility } from "./schema-compat.js";
3
6
  import { DiagnosticSeverity } from "./types.js";
4
7
  const SOURCE = "telo-analyzer";
5
8
  /**
@@ -18,6 +21,11 @@ const SOURCE = "telo-analyzer";
18
21
  * whose input contract is now `inputType:`.
19
22
  * - CONTRACT_TYPE_NOT_FOUND: a contract names a type that is not declared in
20
23
  * scope, so every call through it would fail at dispatch.
24
+ * - CONTRACT_NOT_SUBSTITUTABLE: a definition replaces an ancestor's contract
25
+ * with a shape that cannot stand in for it. The one check nothing else makes:
26
+ * contracts resolve to the nearest declaration in BOTH halves, so a child that
27
+ * declares its own is compared against its abstract by neither the analyzer
28
+ * nor dispatch.
21
29
  *
22
30
  * Deliberately NOT diagnosed: an input that is neither `required:` nor
23
31
  * defaulted. It is indistinguishable from a genuinely optional one — `Ai.Text`
@@ -29,8 +37,7 @@ const SOURCE = "telo-analyzer";
29
37
  export function validateInvocationContract(manifests, registry, aliases, aliasesByModule = new Map()) {
30
38
  const diagnostics = [];
31
39
  const resolveDef = (kind, from) => {
32
- const module = from?.metadata?.module;
33
- const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
40
+ const scope = moduleAliasScope(from?.metadata, aliases, aliasesByModule);
34
41
  return registry.resolve(kind) ?? registry.resolve(scope.resolveKind(kind) ?? kind);
35
42
  };
36
43
  // A published dependency's declarations are not the consumer's to fix.
@@ -58,6 +65,7 @@ export function validateInvocationContract(manifests, registry, aliases, aliases
58
65
  if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") {
59
66
  checkMappingRequired(m, resource, filePath, resolveDef, diagnostics);
60
67
  checkContractResolves(m, md, manifests, resource, filePath, diagnostics);
68
+ checkAncestorSubstitutability(m, md, manifests, resource, filePath, resolveDef, diagnostics);
61
69
  continue;
62
70
  }
63
71
  // A RESOURCE (an instance of some kind) — a leftover `inputs:` map is only
@@ -318,6 +326,109 @@ function namedTypeReference(value) {
318
326
  return undefined;
319
327
  return typeof ref.name === "string" ? ref.name : undefined;
320
328
  }
329
+ /**
330
+ * A REPLACING CONTRACT MUST STILL STAND IN FOR THE ONE IT REPLACES.
331
+ *
332
+ * Contracts resolve to the NEAREST declaration and never merge, which is right —
333
+ * a call signature is not additive, and a backend narrowing `status` to the
334
+ * states it actually has could not express that through a merge. But the
335
+ * consequence is that an abstract's contract binds only the children that
336
+ * declare none of their own: a child that declares one is checked against
337
+ * nothing, statically or at dispatch, since `resolveBoundContract` stops at the
338
+ * same nearest declaration. So an abstract could state a floor every
339
+ * implementation must answer with (`Durable.Run`'s `runId` / `status`, the
340
+ * identity every later question about a run is asked by) and have that floor hold
341
+ * for exactly the implementations that declared nothing — while the real ones,
342
+ * which all declare their own, escaped it. A slot typed by the abstract would
343
+ * then be typed by a promise nothing kept.
344
+ *
345
+ * The rule is substitutability, and its direction differs per contract because
346
+ * one is produced and the other consumed:
347
+ *
348
+ * - `outputType` is COVARIANT — a consumer written against the ancestor reads
349
+ * the ancestor's shape, so the child's output must be acceptable where the
350
+ * ancestor's is declared. Dropping a required property is what breaks it.
351
+ * - `inputType` is CONTRAVARIANT — a caller written against the ancestor sends
352
+ * the ancestor's shape, so the ancestor's input must be acceptable where the
353
+ * child's is declared. Requiring an input the ancestor never mentions is what
354
+ * breaks it: no such caller could satisfy it.
355
+ *
356
+ * **Only an ABSTRACT ancestor's contract is a contract**, and that narrowing is a
357
+ * decision rather than an oversight. Extending a CONCRETE kind is how a friendlier
358
+ * schema is put over an existing controller — `base:` reshapes its config and
359
+ * `inputs:` / `result:` translate its call signature — so a child there is
360
+ * SUPPOSED to present different inputs, and demanding substitutability would
361
+ * reject the pattern the standard library and every custom-kind example are built
362
+ * on. An abstract has no implementation to reshape: extending one is a claim to
363
+ * BE the thing, its contract is written for implementors, and a slot typed by it
364
+ * is polymorphic by construction — which is exactly where an unkept promise has
365
+ * nowhere to be caught.
366
+ *
367
+ * A direction the child BRIDGES (`inputs:` for inputs, `result:` for outputs) is
368
+ * skipped for the same reason one hop down: the mapping is the author saying the
369
+ * shapes differ deliberately and are translated.
370
+ *
371
+ * The comparison is {@link checkSchemaCompatibility}, which reports only DEFINITE
372
+ * mismatches — a union, an absent `type`, an undeclared argument all read as
373
+ * compatible — so narrowing a value's type, adding an optional property or
374
+ * restricting an enum stays legal, which is the whole point of letting a child
375
+ * replace the contract at all.
376
+ */
377
+ function checkAncestorSubstitutability(m, md, manifests, resource, filePath, resolveDef, diagnostics) {
378
+ const def = m;
379
+ for (const direction of ["inputType", "outputType"]) {
380
+ const own = md[direction];
381
+ if (own === undefined || own === null)
382
+ continue;
383
+ // A declared bridge says the shapes differ on purpose and are translated.
384
+ if (md[mappingFieldFor(direction)] != null)
385
+ continue;
386
+ // The nearest ANCESTOR that declares it — the contract this one replaces.
387
+ // Walking past self is the whole question: `contractDeclarer` would answer
388
+ // "this one", which is what nothing checks.
389
+ const ancestor = ancestorChain(def, resolveDef).find((a) => {
390
+ const inherited = a[direction];
391
+ return inherited !== undefined && inherited !== null;
392
+ });
393
+ if (!ancestor || ancestor.kind !== "Telo.Abstract")
394
+ continue;
395
+ const inherited = ancestor[direction];
396
+ // The WHOLE manifest set, which is what `analyzerContractScope`'s
397
+ // `typeManifestsFor` hands `resolveContract` — so this resolves a named type
398
+ // exactly as the resolver the kernel binds with does. Filtering to the
399
+ // declaring module looked more careful and was strictly worse: a contract
400
+ // naming a shape from a shared module resolved to nothing, and an
401
+ // unresolvable side is skipped, so the check silently switched itself off for
402
+ // precisely the libraries that factor their shapes out. Nothing else caught
403
+ // it either — `CONTRACT_TYPE_NOT_FOUND` finds the type through its own global
404
+ // fallback, so such a kind passed `telo check` with no diagnostic at all.
405
+ const ownSchema = resolveTypeFieldToSchema(own, manifests);
406
+ const ancestorSchema = resolveTypeFieldToSchema(inherited, manifests);
407
+ // An unresolvable side says nothing about compatibility; CONTRACT_TYPE_NOT_FOUND
408
+ // is what reports a contract that names a type nothing declares.
409
+ if (!ownSchema || !ancestorSchema)
410
+ continue;
411
+ const { compatible, issues } = direction === "outputType"
412
+ ? checkSchemaCompatibility(ownSchema, ancestorSchema)
413
+ : checkSchemaCompatibility(ancestorSchema, ownSchema);
414
+ if (compatible)
415
+ continue;
416
+ const ancestorName = `${ancestor.metadata?.module ?? ""}.${ancestor.metadata?.name ?? "?"}`.replace(/^\./, "");
417
+ const why = direction === "outputType"
418
+ ? `every caller that holds this through '${ancestorName}' reads the shape that kind declares`
419
+ : `a caller that holds this through '${ancestorName}' sends the shape that kind declares`;
420
+ diagnostics.push({
421
+ severity: DiagnosticSeverity.Error,
422
+ code: "CONTRACT_NOT_SUBSTITUTABLE",
423
+ source: SOURCE,
424
+ message: `${m.kind}/${resource.name}: \`${direction}\` replaces the one declared by '${ancestorName}' ` +
425
+ `with a shape that cannot stand in for it — ${issues.join("; ")}. Contracts replace rather ` +
426
+ `than merge, so nothing re-checks this at dispatch: ${why}. Restate the fields ` +
427
+ `'${ancestorName}' declares, or drop \`${direction}\` to inherit the contract unchanged.`,
428
+ data: { resource, filePath, path: direction },
429
+ });
430
+ }
431
+ }
321
432
  /** A child that inherits its controller and REPLACES a contract must bridge it:
322
433
  * contracts resolve to the nearest declaration and never merge, so the
323
434
  * inherited controller only understands its own shape. Without the mapping the
@@ -1 +1 @@
1
- {"version":3,"file":"validate-logging.d.ts","sourceRoot":"","sources":["../src/validate-logging.ts"],"names":[],"mappings":"AAAA,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;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CAqBtB"}
1
+ {"version":3,"file":"validate-logging.d.ts","sourceRoot":"","sources":["../src/validate-logging.ts"],"names":[],"mappings":"AAAA,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;AAKzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CAqBtB"}
@@ -1,5 +1,6 @@
1
1
  import { parseRedactionPath, RedactionPathError } from "./redaction-path.js";
2
2
  import { DiagnosticSeverity } from "./types.js";
3
+ import { moduleAliasScope } from "./module-alias-scope.js";
3
4
  const SOURCE = "telo-analyzer";
4
5
  /**
5
6
  * Static validation of the `logging:` block — `kernel/specs/logging.md` §14.1
@@ -106,8 +107,7 @@ function isSinkKind(manifest, registry, aliases, aliasesByModule) {
106
107
  return false;
107
108
  if (manifest.kind === "Telo.ConsoleSink" || manifest.kind === "Telo.FileSink")
108
109
  return true;
109
- const ownModule = manifest.metadata?.module;
110
- const resolver = (ownModule ? aliasesByModule?.get(ownModule) : undefined) ?? aliases;
110
+ const resolver = moduleAliasScope(manifest.metadata, aliases, aliasesByModule);
111
111
  const canonical = resolver.resolveKind(manifest.kind) ?? manifest.kind;
112
112
  return registry.resolve(canonical)?.capability === "Telo.Sink";
113
113
  }
@@ -11,12 +11,23 @@ import type { DefinitionRegistry } from "./definition-registry.js";
11
11
  *
12
12
  * A value satisfies the slot when it transitively extends the target kind, or —
13
13
  * for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
14
- * subtype index for both, and an abstract is satisfied only by an implementer,
15
- * never by the abstract itself (which is non-instantiable). Accepts a constraint
16
- * that resolves to nothing, and an abstract with no loaded implementations:
17
- * partial context, where a rejection would be a guess.
14
+ * subtype index for both. Accepts a constraint that resolves to nothing, and an
15
+ * abstract with no loaded implementations: partial context, where a rejection
16
+ * would be a guess.
17
+ *
18
+ * An abstract is satisfied only by an implementer, never by the abstract itself,
19
+ * because a resource declared `kind: <some abstract>` is refused at `create()`
20
+ * and reported as `ABSTRACT_KIND_INSTANTIATED` at its declaration — so accepting
21
+ * it here would leave the reference sites silent about a manifest that cannot
22
+ * run. `isDeclaration` is the ONE exception and it is not an instance: a
23
+ * library's `resources:` entry is constrained by kind alone, so its kind-only
24
+ * stand-in routinely IS an abstract (`connection: {kind: Sql.Connection}`), and
25
+ * every use of that name inside the library is a reference to it.
18
26
  */
19
- export declare function kindSatisfies(resolved: string, targetKind: string, registry: DefinitionRegistry): boolean;
27
+ export declare function kindSatisfies(resolved: string, targetKind: string, registry: DefinitionRegistry,
28
+ /** The referent is a kind-only stand-in for a `resources:` entry rather than a
29
+ * resource anything instantiates, so identity satisfies an abstract slot. */
30
+ isDeclaration?: boolean): boolean;
20
31
  /**
21
32
  * Phase 3 — Reference validation.
22
33
  *
@@ -1 +1 @@
1
- {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAwBT;AA8DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA2gBtB"}
1
+ {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB;AAC5B;8EAC8E;AAC9E,aAAa,UAAQ,GACpB,OAAO,CAwBT;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA0hBtB"}
@@ -5,6 +5,8 @@ import { navigateJsonPointer, substituteCelFields } from "./schema-compat.js";
5
5
  import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
6
6
  import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
7
7
  import { DiagnosticSeverity } from "./types.js";
8
+ import { moduleAliasScope } from "./module-alias-scope.js";
9
+ import { isInjectedDeclaration } from "./resource-input.js";
8
10
  const SOURCE = "telo-analyzer";
9
11
  /**
10
12
  * Liskov substitutability at a kind constraint: is `resolved` (a canonical
@@ -16,17 +18,28 @@ const SOURCE = "telo-analyzer";
16
18
  *
17
19
  * A value satisfies the slot when it transitively extends the target kind, or —
18
20
  * for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
19
- * subtype index for both, and an abstract is satisfied only by an implementer,
20
- * never by the abstract itself (which is non-instantiable). Accepts a constraint
21
- * that resolves to nothing, and an abstract with no loaded implementations:
22
- * partial context, where a rejection would be a guess.
21
+ * subtype index for both. Accepts a constraint that resolves to nothing, and an
22
+ * abstract with no loaded implementations: partial context, where a rejection
23
+ * would be a guess.
24
+ *
25
+ * An abstract is satisfied only by an implementer, never by the abstract itself,
26
+ * because a resource declared `kind: <some abstract>` is refused at `create()`
27
+ * and reported as `ABSTRACT_KIND_INSTANTIATED` at its declaration — so accepting
28
+ * it here would leave the reference sites silent about a manifest that cannot
29
+ * run. `isDeclaration` is the ONE exception and it is not an instance: a
30
+ * library's `resources:` entry is constrained by kind alone, so its kind-only
31
+ * stand-in routinely IS an abstract (`connection: {kind: Sql.Connection}`), and
32
+ * every use of that name inside the library is a reference to it.
23
33
  */
24
- export function kindSatisfies(resolved, targetKind, registry) {
34
+ export function kindSatisfies(resolved, targetKind, registry,
35
+ /** The referent is a kind-only stand-in for a `resources:` entry rather than a
36
+ * resource anything instantiates, so identity satisfies an abstract slot. */
37
+ isDeclaration = false) {
25
38
  const canonical = registry.resolveRef(targetKind) ?? targetKind;
26
39
  const targetDef = registry.resolve(canonical);
27
40
  if (!targetDef)
28
41
  return true;
29
- if (targetDef.kind !== "Telo.Abstract" && resolved === canonical)
42
+ if ((targetDef.kind !== "Telo.Abstract" || isDeclaration) && resolved === canonical)
30
43
  return true;
31
44
  const subtypes = registry.getByExtends(canonical);
32
45
  if (subtypes.some((d) => `${d.metadata.module}.${d.metadata.name}` === resolved))
@@ -53,7 +66,9 @@ export function kindSatisfies(resolved, targetKind, registry) {
53
66
  /** {@link kindSatisfies} at every kind a slot accepts, rendered as messages.
54
67
  * The acceptance rule is not restated here — only what to say when it fails,
55
68
  * which needs the slot's alternatives and the target's flavour. */
56
- function checkKind(kind, entry, registry, aliases) {
69
+ function checkKind(kind, entry, registry, aliases,
70
+ /** See {@link kindSatisfies}: the referent is a `resources:` stand-in. */
71
+ isDeclaration = false) {
57
72
  const resolved = aliases.resolveKind(kind) ?? kind;
58
73
  // A qualified kind whose prefix names no import in this scope is a bad NAME
59
74
  // rather than a bad type — `{kind: NotAnAlias.Script}` where only `Lib` is
@@ -76,7 +91,7 @@ function checkKind(kind, entry, registry, aliases) {
76
91
  const targetDef = registry.resolve(targetKind);
77
92
  if (!targetDef)
78
93
  return [];
79
- if (!unknownAlias && kindSatisfies(resolved, targetKind, registry))
94
+ if (!unknownAlias && kindSatisfies(resolved, targetKind, registry, isDeclaration))
80
95
  return [];
81
96
  const subtypes = registry.getByExtends(targetKind);
82
97
  const subtypeKinds = new Set(subtypes.map((d) => `${d.metadata.module}.${d.metadata.name}`));
@@ -311,7 +326,7 @@ export function validateReferences(resources, context) {
311
326
  });
312
327
  return;
313
328
  }
314
- const kindErrors = checkKind(target.kind, entry, registry, aliases);
329
+ const kindErrors = checkKind(target.kind, entry, registry, aliases, isInjectedDeclaration(target));
315
330
  if (kindErrors.length > 0) {
316
331
  diagnostics.push({
317
332
  severity: DiagnosticSeverity.Error,
@@ -359,7 +374,11 @@ export function validateReferences(resources, context) {
359
374
  return;
360
375
  }
361
376
  // 2. Kind check
362
- const kindErrors = checkKind(refVal.kind, entry, registry, aliases);
377
+ const objectTarget = typeof refVal.name === "string"
378
+ ? (visibleScopeManifests.find((m) => m.metadata?.name === refVal.name) ??
379
+ byName.get(refVal.name))
380
+ : undefined;
381
+ const kindErrors = checkKind(refVal.kind, entry, registry, aliases, isInjectedDeclaration(objectTarget));
363
382
  if (kindErrors.length > 0) {
364
383
  diagnostics.push({
365
384
  severity: DiagnosticSeverity.Error,
@@ -448,8 +467,7 @@ export function validateReferences(resources, context) {
448
467
  if (!isAbsolute && anchorName.includes(".")) {
449
468
  const resolvedResourceKind = aliases.resolveKind(r.kind) ?? r.kind;
450
469
  const resourceDef = registry.resolve(r.kind) ?? registry.resolve(resolvedResourceKind);
451
- const owningModule = resourceDef?.metadata?.module;
452
- const ownerScope = (owningModule ? aliasesByModule?.get(owningModule) : undefined) ?? aliases;
470
+ const ownerScope = moduleAliasScope(resourceDef?.metadata, aliases, aliasesByModule);
453
471
  const targetKind = ownerScope.resolveKind(anchorName);
454
472
  if (!targetKind) {
455
473
  // Names the ALIAS, not the whole kind path: the alias is what an
@@ -2,34 +2,9 @@ import type { ResourceManifest } from "@telorun/sdk";
2
2
  import type { AliasResolver } from "./alias-resolver.js";
3
3
  import type { DefinitionRegistry } from "./definition-registry.js";
4
4
  import { type AnalysisDiagnostic } from "./types.js";
5
- /**
6
- * The strict half of {@link readResourceInputs} — the `validate-ref-slots.ts`
7
- * split applied to the resource-input boundary, and not optional for the same
8
- * reason: the two sides fail in OPPOSITE directions. An unreadable DECLARATION
9
- * leaves the library referencing a name nothing stands behind; an unchecked
10
- * INJECTION hands a library an instance of a kind it never asked for, which
11
- * surfaces as a method-missing failure inside someone else's module.
12
- *
13
- * Two surfaces, scoped the same way every other declaration check is — to the
14
- * entry's own modules, since a published dependency's block is not the
15
- * consumer's to fix:
16
- *
17
- * - **The library's own declaration.** Each entry's alias-qualified `kind:` must
18
- * resolve in the DECLARING module's scope (`RESOURCE_INPUT_KIND_UNRESOLVED`).
19
- * A constraint that resolves to nothing accepts anything, which is the same
20
- * hole `X_TELO_REF_UNRESOLVED` exists to close one level down.
21
- * - **The injection site.** Every declared entry must be supplied
22
- * (`RESOURCE_INPUT_MISSING` — the same failure as a missing required
23
- * variable), nothing beyond them may be (`RESOURCE_INPUT_UNKNOWN`), the value
24
- * must name a resource that exists (`RESOURCE_INPUT_UNRESOLVED`), and its
25
- * kind must satisfy the declared constraint transitively
26
- * (`RESOURCE_INPUT_KIND_MISMATCH`).
27
- *
28
- * Browser-safe.
29
- */
30
5
  export declare function validateResourceInputs(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, rootModules: Set<string>,
31
6
  /** Kind acceptance, transitively — the `checkKind` rule `validate-references`
32
7
  * applies at an ordinary ref slot, passed in rather than re-derived so the
33
8
  * two cannot disagree about what satisfies a constraint. */
34
- acceptsKind: (suppliedKind: string, requiredKind: string) => boolean): AnalysisDiagnostic[];
9
+ acceptsKind: (suppliedKind: string, requiredKind: string, suppliedIsDeclaration: boolean) => boolean): AnalysisDiagnostic[];
35
10
  //# sourceMappingURL=validate-resource-inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AASzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC;AACxB;;6DAE6D;AAC7D,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GACnE,kBAAkB,EAAE,CA6LtB"}
1
+ {"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AAqCzE,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC;AACxB;;6DAE6D;AAC7D,WAAW,EAAE,CACX,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,qBAAqB,EAAE,OAAO,KAC3B,OAAO,GACX,kBAAkB,EAAE,CAyMtB"}
@@ -1,6 +1,6 @@
1
1
  import { isRefSentinel } from "@telorun/templating";
2
2
  import { findDynamicLeaf } from "./resource-rule.js";
3
- import { readResourceInputs, readSuppliedResources } from "./resource-input.js";
3
+ import { isInjectedDeclaration, readResourceInputs, readSuppliedResources, } from "./resource-input.js";
4
4
  import { DiagnosticSeverity } from "./types.js";
5
5
  const SOURCE = "telo-analyzer";
6
6
  /**
@@ -28,6 +28,7 @@ const SOURCE = "telo-analyzer";
28
28
  *
29
29
  * Browser-safe.
30
30
  */
31
+ const declaringModule = (m) => m.metadata?.module;
31
32
  export function validateResourceInputs(manifests, registry, aliases, rootModules,
32
33
  /** Kind acceptance, transitively — the `checkKind` rule `validate-references`
33
34
  * applies at an ordinary ref slot, passed in rather than re-derived so the
@@ -186,7 +187,16 @@ acceptsKind) {
186
187
  if (typeof suppliedKind !== "string")
187
188
  continue;
188
189
  const canonical = aliases.resolveKind(suppliedKind) ?? suppliedKind;
189
- if (acceptsKind(canonical, entry))
190
+ // A library forwarding an input it was itself handed supplies a kind-only
191
+ // stand-in, whose kind is routinely the abstract the target declares. That
192
+ // is identity against an abstract, which an ordinary reference is refused
193
+ // — the referent there would be a resource nothing can instantiate, while
194
+ // here it is another declaration.
195
+ const suppliedName = value.name;
196
+ const supplied = typeof suppliedName === "string"
197
+ ? manifests.find((c) => c.metadata?.name === suppliedName && isOwn(declaringModule(c)))
198
+ : undefined;
199
+ if (acceptsKind(canonical, entry, isInjectedDeclaration(supplied)))
190
200
  continue;
191
201
  out.push({
192
202
  severity: DiagnosticSeverity.Error,
@@ -1 +1 @@
1
- {"version":3,"file":"validate-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/validate-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKzE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,kBAAkB,EAAE,CAuDtB"}
1
+ {"version":3,"file":"validate-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/validate-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMzE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,kBAAkB,EAAE,CAuDtB"}
@@ -1,5 +1,6 @@
1
1
  import { canonicalTypeSchemaId, parseTeloTypeRef } from "@telorun/sdk";
2
2
  import { DiagnosticSeverity } from "./types.js";
3
+ import { moduleAliasScope } from "./module-alias-scope.js";
3
4
  const SOURCE = "telo-analyzer";
4
5
  const SCHEMA_FIELDS = ["schema", "inputType", "outputType"];
5
6
  /**
@@ -28,7 +29,7 @@ export function validateSchemaTypeRefs(manifests, registry, aliases, aliasesByMo
28
29
  // validated against their own library when it's analyzed as a root.
29
30
  if (ownModule && !rootModules.has(ownModule))
30
31
  continue;
31
- const resolver = (ownModule ? aliasesByModule.get(ownModule) : undefined) ?? aliases;
32
+ const resolver = moduleAliasScope(m.metadata, aliases, aliasesByModule);
32
33
  const filePath = m.metadata?.source;
33
34
  const label = `${m.kind}/${name}`;
34
35
  const walk = (value, path) => {
@@ -4,5 +4,9 @@ import { type AliasResolver } from "./alias-resolver.js";
4
4
  import type { DefinitionRegistry } from "./definition-registry.js";
5
5
  import { type AnalysisDiagnostic } from "./types.js";
6
6
  /** Entry point — invoked once per analyze() run. */
7
- export declare function validateThrowsCoverage(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, env: Environment, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>): AnalysisDiagnostic[];
7
+ export declare function validateThrowsCoverage(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, env: Environment, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>,
8
+ /** Each imported library's full document set, so the walk can follow an
9
+ * exported entry point into the siblings it invokes — which a consumer's
10
+ * flat set does not carry. */
11
+ moduleManifests?: Map<string, ResourceManifest[]>): AnalysisDiagnostic[];
8
12
  //# sourceMappingURL=validate-throws-coverage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}
1
+ {"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa;AACpC;;+BAE+B;AAC/B,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAa,GAC3D,kBAAkB,EAAE,CAyDtB"}
@@ -431,10 +431,14 @@ function schemaHasStepContext(schema) {
431
431
  return false;
432
432
  }
433
433
  /** Entry point — invoked once per analyze() run. */
434
- export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set()) {
434
+ export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set(),
435
+ /** Each imported library's full document set, so the walk can follow an
436
+ * exported entry point into the siblings it invokes — which a consumer's
437
+ * flat set does not carry. */
438
+ moduleManifests = new Map()) {
435
439
  const diagnostics = [];
436
440
  diagnostics.push(...validateThrowsDeclarations(manifests));
437
- const resolveCtx = createResolveCtx(manifests, defs, aliases, aliasesByModule, rootModules);
441
+ const resolveCtx = createResolveCtx(manifests, defs, aliases, aliasesByModule, rootModules, moduleManifests);
438
442
  // The alias resolver for a manifest's own lexical scope — an imported library's
439
443
  // resolver when it owns the manifest, else undefined (fall back to root aliases).
440
444
  const scopeResolverFor = (m) => scopeResolverForModule(m.metadata?.module, rootModules, aliasesByModule);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.70.0",
3
+ "version": "0.71.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -49,7 +49,7 @@
49
49
  "@types/node": "^20.0.0",
50
50
  "typescript": "^5.0.0",
51
51
  "vitest": "^2.1.8",
52
- "@telorun/sdk": "0.86.0"
52
+ "@telorun/sdk": "0.87.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@telorun/sdk": "*"
@@ -9,6 +9,7 @@ import { visitManifest as runVisitManifest, type ManifestVisitor } from "./manif
9
9
  import type { ContractDirection, DefResolver } from "./extends-resolution.js";
10
10
  import { resolveContract } from "./invocation-contract.js";
11
11
  import { createResolveCtx, resolveThrowsUnion } from "./resolve-throws-union.js";
12
+ import { moduleAliasScope } from "./module-alias-scope.js";
12
13
  import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
13
14
  import { resolveSchemaTypeRefs as resolveSchemaTypeRefsIn } from "./resolve-schema-type-refs.js";
14
15
  import type { AnalysisContext } from "./types.js";
@@ -258,8 +259,7 @@ export class AnalysisRegistry {
258
259
  */
259
260
  resolveSchemaFrom(schemaFrom: string, declaringKind: string): Record<string, any> | undefined {
260
261
  const def = this.resolveDefinition(declaringKind);
261
- const ownerModule = (def?.metadata as { module?: string } | undefined)?.module;
262
- const scope = (ownerModule ? this.aliasesByModule.get(ownerModule) : undefined) ?? this.aliases;
262
+ const scope = moduleAliasScope(def?.metadata, this.aliases, this.aliasesByModule);
263
263
  return this.defs.resolveSchemaFromNode(schemaFrom, scope);
264
264
  }
265
265
 
@@ -337,7 +337,7 @@ export class AnalysisRegistry {
337
337
  * the kind was read off. Falls back to the global table when the module is
338
338
  * unknown or is a root. */
339
339
  resolveDefinitionIn(kind: string, module?: string): ResourceDefinition | undefined {
340
- const scope = (module ? this.aliasesByModule.get(module) : undefined) ?? this.aliases;
340
+ const scope = moduleAliasScope({ module }, this.aliases, this.aliasesByModule);
341
341
  const canonical = scope.resolveKind(kind);
342
342
  return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
343
343
  }
@@ -354,8 +354,7 @@ export class AnalysisRegistry {
354
354
  resolverForDefinition(def: {
355
355
  metadata?: { module?: string };
356
356
  }): (kind: string) => ResourceDefinition | undefined {
357
- const ownModule = def?.metadata?.module;
358
- const scope = (ownModule ? this.aliasesByModule.get(ownModule) : undefined) ?? this.aliases;
357
+ const scope = moduleAliasScope(def?.metadata, this.aliases, this.aliasesByModule);
359
358
  return (kind) => {
360
359
  const canonical = scope.resolveKind(kind);
361
360
  return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
package/src/analyzer.ts CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  } from "./validate-observed-state.js";
48
48
  import { computeSuggestKind } from "./kind-suggest.js";
49
49
  import { visitManifest } from "./manifest-visitor.js";
50
+ import { declaringModuleScope, moduleAliasScope } from "./module-alias-scope.js";
50
51
  import { isModuleKind } from "./module-kinds.js";
51
52
  import { normalizeInlineResources } from "./normalize-inline-resources.js";
52
53
  import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
@@ -999,10 +1000,10 @@ export class StaticAnalyzer {
999
1000
  if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
1000
1001
  const def = m as unknown as ResourceDefinition;
1001
1002
  const ownModule = (def.metadata as { module?: string } | undefined)?.module;
1002
- const scopeResolver =
1003
- ownModule && !rootModules.has(ownModule)
1004
- ? (aliasesByModule.get(ownModule) ?? new AliasResolver())
1005
- : aliases;
1003
+ const scopeResolver = declaringModuleScope(ownModule, aliases, {
1004
+ aliasesByModule,
1005
+ rootModules,
1006
+ });
1006
1007
  // Canonicalize alias-form `x-telo-ref` constraints in the DECLARING module's
1007
1008
  // scope, before the schema reaches `register()` and the lazily-built field
1008
1009
  // maps. Same pre-resolution `capability` / `extends` get below.
@@ -1425,7 +1426,7 @@ export class StaticAnalyzer {
1425
1426
  // analyzer knowing it exists: going native costs a module, not a change
1426
1427
  // here.
1427
1428
  const resolveRegionDef = (kind: string, module?: string) => {
1428
- const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
1429
+ const scope = moduleAliasScope({ module }, aliases, aliasesByModule);
1429
1430
  const canonical = scope.resolveKind(kind);
1430
1431
  return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
1431
1432
  };
@@ -1456,8 +1457,10 @@ export class StaticAnalyzer {
1456
1457
  if (!ownModule || !m.metadata?.name || typeof m.schema !== "object" || m.schema === null) {
1457
1458
  continue;
1458
1459
  }
1459
- const scopeResolver =
1460
- rootModules.has(ownModule) ? aliases : (aliasesByModule.get(ownModule) ?? new AliasResolver());
1460
+ const scopeResolver = declaringModuleScope(ownModule, aliases, {
1461
+ aliasesByModule,
1462
+ rootModules,
1463
+ });
1461
1464
  const canonicalKind = scopeResolver.resolveKind(m.kind as string) ?? (m.kind as string);
1462
1465
  if (defs.resolve(canonicalKind)?.capability !== "Telo.Type") continue;
1463
1466
  const typeName = m.metadata.name as string;
@@ -1877,6 +1880,37 @@ export class StaticAnalyzer {
1877
1880
  continue;
1878
1881
  }
1879
1882
 
1883
+ // An abstract names a CONTRACT for reference slots, not something to
1884
+ // instantiate — the kernel refuses it outright at `create()`. Reported
1885
+ // here, at the declaration, because that is the line the author has to
1886
+ // change and because a runtime-only refusal is exactly the shape static
1887
+ // analysis exists to remove. The hint mirrors the kernel's: the concrete
1888
+ // implementations this analysis holds, or the reason there are none.
1889
+ //
1890
+ // Injected declarations never reach this loop (skipped above), which is
1891
+ // what keeps a `resources:` entry — kind-only and routinely abstract by
1892
+ // design — out of it.
1893
+ if (definition.kind === "Telo.Abstract") {
1894
+ const canonical = resolvedKind ?? m.kind;
1895
+ const impls = defs
1896
+ .getByExtends(canonical)
1897
+ .filter((d) => d.kind !== "Telo.Abstract")
1898
+ .map((d) => `${d.metadata.module}.${d.metadata.name}`);
1899
+ const kindInfo =
1900
+ canonical !== m.kind ? `'${m.kind}' (resolved to '${canonical}')` : `'${m.kind}'`;
1901
+ const hint = impls.length
1902
+ ? `instantiate a concrete implementation: ${impls.join(", ")}`
1903
+ : "no concrete implementations are registered — import a module that provides one";
1904
+ diagnostics.push({
1905
+ severity: DiagnosticSeverity.Error,
1906
+ code: "ABSTRACT_KIND_INSTANTIATED",
1907
+ source: SOURCE,
1908
+ message: `Kind ${kindInfo} is abstract and cannot be instantiated directly; ${hint}.`,
1909
+ data: { resource, filePath, path: "kind" },
1910
+ });
1911
+ continue;
1912
+ }
1913
+
1880
1914
  // Validate resource config against the definition's AUTHOR-FACING schema —
1881
1915
  // inheritance-resolved, with `kind` / `metadata` injected. See
1882
1916
  // `validationSchemaFor`, which is where both derivations and the reason
@@ -2690,9 +2724,25 @@ export class StaticAnalyzer {
2690
2724
  // Validate provider coherence rules for `provide:` template-target definitions.
2691
2725
  diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
2692
2726
 
2693
- // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
2727
+ // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7).
2728
+ // The library document sets collected for the zone stage serve here too: a
2729
+ // library's exported entry point is forwarded into the flat set while the
2730
+ // siblings it invokes are not, so without them the walk stops at the
2731
+ // boundary and reports an empty union for a body that plainly throws.
2732
+ const libraryManifests = new Map<string, ResourceManifest[]>();
2733
+ for (const doc of options?.moduleDocuments ?? []) {
2734
+ libraryManifests.set(doc.module, doc.manifests);
2735
+ }
2694
2736
  diagnostics.push(
2695
- ...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules),
2737
+ ...validateThrowsCoverage(
2738
+ allManifests,
2739
+ defs,
2740
+ aliases,
2741
+ this.celEnv,
2742
+ aliasesByModule,
2743
+ rootModules,
2744
+ libraryManifests,
2745
+ ),
2696
2746
  );
2697
2747
 
2698
2748
  // Warn about declared variables / secrets / ports that no CEL references.
@@ -2710,7 +2760,8 @@ export class StaticAnalyzer {
2710
2760
  defs,
2711
2761
  aliases,
2712
2762
  rootModules,
2713
- (supplied, required) => kindSatisfies(supplied, required, defs),
2763
+ (supplied, required, isDeclaration) =>
2764
+ kindSatisfies(supplied, required, defs, isDeclaration),
2714
2765
  ),
2715
2766
  );
2716
2767