@telorun/analyzer 0.71.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 (139) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +92 -15
  3. package/dist/builtins.d.ts.map +1 -1
  4. package/dist/builtins.js +71 -97
  5. package/dist/catch-scope.d.ts +72 -0
  6. package/dist/catch-scope.d.ts.map +1 -0
  7. package/dist/catch-scope.js +102 -0
  8. package/dist/cel-scope-query.d.ts.map +1 -1
  9. package/dist/cel-scope-query.js +32 -11
  10. package/dist/cel-scope.d.ts.map +1 -1
  11. package/dist/cel-scope.js +1 -0
  12. package/dist/deprecation.d.ts +21 -0
  13. package/dist/deprecation.d.ts.map +1 -0
  14. package/dist/deprecation.js +26 -0
  15. package/dist/eval-paths.d.ts +36 -5
  16. package/dist/eval-paths.d.ts.map +1 -1
  17. package/dist/eval-paths.js +47 -6
  18. package/dist/extends-resolution.d.ts +12 -0
  19. package/dist/extends-resolution.d.ts.map +1 -1
  20. package/dist/extends-resolution.js +23 -0
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -2
  24. package/dist/manifest-visitor.d.ts +17 -1
  25. package/dist/manifest-visitor.d.ts.map +1 -1
  26. package/dist/manifest-visitor.js +5 -1
  27. package/dist/migrations/report.d.ts +1 -1
  28. package/dist/migrations/report.d.ts.map +1 -1
  29. package/dist/migrations/report.js +5 -0
  30. package/dist/nearest-name.d.ts +22 -0
  31. package/dist/nearest-name.d.ts.map +1 -0
  32. package/dist/nearest-name.js +43 -0
  33. package/dist/ref-slot.d.ts +15 -0
  34. package/dist/ref-slot.d.ts.map +1 -1
  35. package/dist/ref-slot.js +7 -0
  36. package/dist/release/destinations.d.ts +59 -0
  37. package/dist/release/destinations.d.ts.map +1 -0
  38. package/dist/release/destinations.js +75 -0
  39. package/dist/release/index.d.ts +6 -2
  40. package/dist/release/index.d.ts.map +1 -1
  41. package/dist/release/index.js +3 -1
  42. package/dist/release/ledger.d.ts +20 -11
  43. package/dist/release/ledger.d.ts.map +1 -1
  44. package/dist/release/ledger.js +43 -14
  45. package/dist/release/release-plan.d.ts +3 -2
  46. package/dist/release/release-plan.d.ts.map +1 -1
  47. package/dist/release/release-plan.js +12 -19
  48. package/dist/release/workspace-config.d.ts +114 -34
  49. package/dist/release/workspace-config.d.ts.map +1 -1
  50. package/dist/release/workspace-config.js +322 -38
  51. package/dist/release/workspace-schema.d.ts +41 -0
  52. package/dist/release/workspace-schema.d.ts.map +1 -0
  53. package/dist/release/workspace-schema.js +77 -0
  54. package/dist/resolve-throws-union.d.ts +29 -1
  55. package/dist/resolve-throws-union.d.ts.map +1 -1
  56. package/dist/resolve-throws-union.js +111 -16
  57. package/dist/schema-compat.d.ts.map +1 -1
  58. package/dist/schema-compat.js +13 -1
  59. package/dist/schema-error-report.d.ts +7 -0
  60. package/dist/schema-error-report.d.ts.map +1 -1
  61. package/dist/schema-error-report.js +57 -8
  62. package/dist/schema-keywords.d.ts.map +1 -1
  63. package/dist/schema-keywords.js +3 -1
  64. package/dist/schema-walk.d.ts +27 -0
  65. package/dist/schema-walk.d.ts.map +1 -1
  66. package/dist/schema-walk.js +44 -0
  67. package/dist/telo-version.d.ts +1 -1
  68. package/dist/telo-version.js +1 -1
  69. package/dist/template-body.d.ts +10 -0
  70. package/dist/template-body.d.ts.map +1 -1
  71. package/dist/template-body.js +25 -0
  72. package/dist/types.d.ts +17 -0
  73. package/dist/types.d.ts.map +1 -1
  74. package/dist/types.js +11 -0
  75. package/dist/validate-cel-context.d.ts +23 -8
  76. package/dist/validate-cel-context.d.ts.map +1 -1
  77. package/dist/validate-cel-context.js +48 -16
  78. package/dist/validate-exports.d.ts +28 -0
  79. package/dist/validate-exports.d.ts.map +1 -0
  80. package/dist/validate-exports.js +146 -0
  81. package/dist/validate-extends.d.ts.map +1 -1
  82. package/dist/validate-extends.js +37 -1
  83. package/dist/validate-identifier-names.d.ts +2 -2
  84. package/dist/validate-identifier-names.d.ts.map +1 -1
  85. package/dist/validate-identifier-names.js +22 -7
  86. package/dist/validate-provider-coherence.d.ts +4 -8
  87. package/dist/validate-provider-coherence.d.ts.map +1 -1
  88. package/dist/validate-provider-coherence.js +12 -123
  89. package/dist/validate-ref-slots.d.ts +1 -1
  90. package/dist/validate-ref-slots.d.ts.map +1 -1
  91. package/dist/validate-ref-slots.js +34 -0
  92. package/dist/validate-references.d.ts.map +1 -1
  93. package/dist/validate-references.js +168 -63
  94. package/dist/validate-template-body.d.ts +36 -0
  95. package/dist/validate-template-body.d.ts.map +1 -0
  96. package/dist/validate-template-body.js +246 -0
  97. package/dist/validate-throws-coverage.d.ts.map +1 -1
  98. package/dist/validate-throws-coverage.js +236 -85
  99. package/package.json +3 -2
  100. package/src/analyzer.ts +118 -12
  101. package/src/builtins.ts +85 -97
  102. package/src/catch-scope.ts +157 -0
  103. package/src/cel-scope-query.ts +28 -9
  104. package/src/cel-scope.ts +1 -0
  105. package/src/deprecation.ts +36 -0
  106. package/src/eval-paths.ts +53 -6
  107. package/src/extends-resolution.ts +24 -0
  108. package/src/index.ts +9 -1
  109. package/src/manifest-visitor.ts +19 -2
  110. package/src/migrations/report.ts +5 -1
  111. package/src/nearest-name.ts +47 -0
  112. package/src/ref-slot.ts +19 -0
  113. package/src/release/destinations.ts +105 -0
  114. package/src/release/index.ts +33 -2
  115. package/src/release/ledger.ts +51 -21
  116. package/src/release/release-plan.ts +16 -26
  117. package/src/release/workspace-config.ts +483 -47
  118. package/src/release/workspace-schema.ts +99 -0
  119. package/src/resolve-throws-union.ts +139 -21
  120. package/src/schema-compat.ts +13 -0
  121. package/src/schema-error-report.ts +66 -10
  122. package/src/schema-keywords.ts +4 -1
  123. package/src/schema-walk.ts +56 -0
  124. package/src/telo-version.ts +1 -1
  125. package/src/template-body.ts +25 -0
  126. package/src/types.ts +18 -0
  127. package/src/validate-cel-context.ts +57 -16
  128. package/src/validate-exports.ts +185 -0
  129. package/src/validate-extends.ts +43 -0
  130. package/src/validate-identifier-names.ts +28 -9
  131. package/src/validate-provider-coherence.ts +17 -127
  132. package/src/validate-ref-slots.ts +41 -1
  133. package/src/validate-references.ts +174 -63
  134. package/src/validate-template-body.ts +274 -0
  135. package/src/validate-throws-coverage.ts +333 -92
  136. package/dist/validate-template-dispatch.d.ts +0 -27
  137. package/dist/validate-template-dispatch.d.ts.map +0 -1
  138. package/dist/validate-template-dispatch.js +0 -95
  139. package/src/validate-template-dispatch.ts +0 -99
@@ -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;AAiBzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA+B9B,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAWvF,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AA4DjE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAkG/F;0CAC0C;AAC1C,eAAO,MAAM,eAAe,GAAI,OAAO;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAG,OACT,CAAC;AAErD;8EAC8E;AAC9E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAqUD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACtC;AA6DD,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAkFD,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,EAC3B,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,EAAE;IAu/DvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAyBrB,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;AAiBzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAsC9B,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAWvF,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AA4DjE,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAoGpB;0CAC0C;AAC1C,eAAO,MAAM,eAAe,GAAI,OAAO;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAG,OACT,CAAC;AAErD;8EAC8E;AAC9E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAqUD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACtC;AA6DD,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAkFD,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,EAC3B,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,EAAE;IAmlEvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAyBrB,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
@@ -3,7 +3,8 @@ import { defaultRegistry, isRefSentinel, isTaggedSentinel, plainChainOf, } from
3
3
  import { AliasResolver, scopeResolverForModule, } from "./alias-resolver.js";
4
4
  import { buildCelEnvironment, isKindDocument, } from "./cel-environment.js";
5
5
  import { DefinitionRegistry } from "./definition-registry.js";
6
- import { effectiveAuthorSchema } from "./extends-resolution.js";
6
+ import { readDeprecation } from "./deprecation.js";
7
+ import { effectiveAuthorSchema, inheritedCapability, inheritedRequiredFields, } from "./extends-resolution.js";
7
8
  import { analyzerContractScope, resolveContract, } from "./invocation-contract.js";
8
9
  import { buildCallGraph } from "./call-graph.js";
9
10
  import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
@@ -44,9 +45,9 @@ import { validateSchemaTypeRefs } from "./validate-schema-type-refs.js";
44
45
  import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
45
46
  import { celTypeSatisfiesJsonSchema, checkSchemaCompatibility, navigateSchemaToExprPath, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
46
47
  import { collectValueSchemaIssues } from "./validate-value-schema.js";
47
- import { DiagnosticSeverity } from "./types.js";
48
+ import { DiagnosticSeverity, DiagnosticTag, } from "./types.js";
48
49
  import { extractAccessChains, } from "./validate-cel-context.js";
49
- import { celEvalModeAt, celEvalSites, mergeCelEvalSites, NO_CEL_EVAL_SITES, } from "./eval-paths.js";
50
+ import { celEvalModeAt, celEvalSites, implicitEvalSites, mergeCelEvalSites, NO_CEL_EVAL_SITES, } from "./eval-paths.js";
50
51
  import { BINDINGS_ANNOTATION, findBindingSites, resolveBindingOrder, } from "./cel-bindings.js";
51
52
  import { CEL_RESERVED_WORDS, checkName } from "./identifier-name.js";
52
53
  import { validateIdentifierNames } from "./validate-identifier-names.js";
@@ -65,7 +66,8 @@ import { kindSatisfies, validateReferences } from "./validate-references.js";
65
66
  import { validateReferenceForms } from "./validate-reference-forms.js";
66
67
  import { isInjectedDeclaration } from "./resource-input.js";
67
68
  import { validateResourceInputs } from "./validate-resource-inputs.js";
68
- import { validateTemplateDispatch } from "./validate-template-dispatch.js";
69
+ import { validateExports } from "./validate-exports.js";
70
+ import { validateTemplateBody } from "./validate-template-body.js";
69
71
  import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
70
72
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
71
73
  import { readStepSlot } from "./step-slot.js";
@@ -1308,7 +1310,7 @@ export class StaticAnalyzer {
1308
1310
  // unchecked where it is declared and its consequences land at whichever
1309
1311
  // CEL site reads it — for a hyphen, sometimes as silent arithmetic. Takes
1310
1312
  // the call graph for step names rather than re-walking the step arrays.
1311
- diagnostics.push(...validateIdentifierNames(allManifests, defs, aliases, rootModules, getCallGraph()));
1313
+ diagnostics.push(...validateIdentifierNames(allManifests, defs, aliases, rootModules, getCallGraph(), { aliasesByModule, rootModules }));
1312
1314
  // A file embed resolves at resource creation, so one written on a doc that
1313
1315
  // is never instantiated is read by nothing and would ship silently.
1314
1316
  diagnostics.push(...validateIncludePlacement(allManifests));
@@ -1652,6 +1654,45 @@ export class StaticAnalyzer {
1652
1654
  });
1653
1655
  continue;
1654
1656
  }
1657
+ // A kind its own author marked `metadata.deprecated` still works exactly as
1658
+ // it did — the manifest is valid and keeps running — so this is a WARNING,
1659
+ // reported at the declaration because `kind:` is the line that has to
1660
+ // change. Emitted here rather than in a pass of its own so it reads the
1661
+ // kind THIS walk resolved: the resolution is alias- and gate-aware and
1662
+ // scope-dependent, and a second implementation of it would eventually
1663
+ // disagree about which definition a name means.
1664
+ //
1665
+ // Entry-module-scoped, like every other "not the consumer's to fix" check:
1666
+ // a library's internal use of a kind its own author deprecated is that
1667
+ // author's concern, and reporting it floods a consumer with lines they
1668
+ // cannot act on.
1669
+ //
1670
+ // No `DiagnosticFix`. `replacedBy` names a kind through the DECLARING
1671
+ // module's aliases; writing it into the consumer's file would produce a
1672
+ // prefix that resolves to nothing there — and a kind swap is not a
1673
+ // whole-value replacement anyway, since the successor needs its own import
1674
+ // and usually a different configuration.
1675
+ const deprecation = readDeprecation(definition.metadata);
1676
+ if (deprecation && (!ownModule || rootModules.has(ownModule))) {
1677
+ // Quoted CANONICALLY, not verbatim: `Self.Thing` is how the declaring
1678
+ // library names its own kind and means nothing where the warning lands.
1679
+ const declaringScope = scopeResolverForModule(definition.metadata?.module, rootModules, aliasesByModule) ?? aliases;
1680
+ const replacement = deprecation.replacedBy
1681
+ ? (declaringScope.resolveKind(deprecation.replacedBy) ?? deprecation.replacedBy)
1682
+ : undefined;
1683
+ diagnostics.push({
1684
+ severity: DiagnosticSeverity.Warning,
1685
+ code: "DEPRECATED_KIND",
1686
+ source: SOURCE,
1687
+ // Warning-grade AND a deprecation: the severity says it must
1688
+ // eventually be dealt with, the tag says what it is, and an editor
1689
+ // strikes the kind through on the strength of the second.
1690
+ tags: [DiagnosticTag.Deprecated],
1691
+ message: `Kind '${m.kind}' is deprecated: ${deprecation.reason}` +
1692
+ (replacement ? ` Use '${replacement}' instead.` : ""),
1693
+ data: { resource, filePath, path: "kind" },
1694
+ });
1695
+ }
1655
1696
  // Validate resource config against the definition's AUTHOR-FACING schema —
1656
1697
  // inheritance-resolved, with `kind` / `metadata` injected. See
1657
1698
  // `validationSchemaFor`, which is where both derivations and the reason
@@ -1706,12 +1747,37 @@ export class StaticAnalyzer {
1706
1747
  // is caught here rather than on the one input that selects it.
1707
1748
  const valueSchemaIssues = collectValueSchemaIssues(m, schema, allManifests);
1708
1749
  const issues = [...ajvIssues, ...valueSchemaIssues];
1750
+ // WHY A FIELD THIS KIND EXISTS TO SUPPLY IS STILL REQUIRED OF ITS
1751
+ // CONSUMER. A child that `extends` and declares no `base:` is authored
1752
+ // against merge(parent, own), so the parent's `required` stays on the
1753
+ // CHILD's surface — and a kind written to wire that field internally
1754
+ // then demands it from the consumer anyway. The `base:` mapping is what
1755
+ // narrows (with it the surface is the child's own schema alone), and
1756
+ // nothing in "is missing required property" points there, which is what
1757
+ // turns one mistake into a three-step dead end.
1758
+ //
1759
+ // Matched against names DERIVED from the parent, never by parsing the
1760
+ // message: which fields are inherited is a fact about the definition,
1761
+ // and a check that reads a validator's prose breaks when the prose does.
1762
+ const inheritedRequired = inheritedRequiredFields(definition, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k));
1763
+ const parentKind = definition?.extends;
1709
1764
  for (const issue of issues) {
1765
+ // Keyed on the STRUCTURED failure, never on the sentence: the field
1766
+ // names are derived from the parent, and matching them against the
1767
+ // validator's prose would break when the prose changes and mis-fire on
1768
+ // any other issue quoting the same name (an `additionalProperties`
1769
+ // rejection, an `enum` listing).
1770
+ const inherited = issue.keyword === "required" && issue.missingProperty !== undefined
1771
+ ? inheritedRequired.filter((f) => f === issue.missingProperty)
1772
+ : [];
1773
+ const hint = inherited.length > 0 && parentKind
1774
+ ? ` — ${inherited.map((f) => `'${f}'`).join(", ")} ${inherited.length > 1 ? "come" : "comes"} from '${parentKind}', which this kind extends without a 'base:' mapping, so the parent's required fields stay on this kind's author surface. Add 'base:' to set them internally; that also narrows the surface to this kind's own schema.`
1775
+ : "";
1710
1776
  diagnostics.push({
1711
1777
  severity: DiagnosticSeverity.Error,
1712
1778
  code: "SCHEMA_VIOLATION",
1713
1779
  source: SOURCE,
1714
- message: `${m.kind}/${resource.name}: ${issue.message}`,
1780
+ message: `${m.kind}/${resource.name}: ${issue.message}${hint}`,
1715
1781
  data: { resource, filePath, path: issue.path },
1716
1782
  });
1717
1783
  }
@@ -2076,8 +2142,14 @@ export class StaticAnalyzer {
2076
2142
  }
2077
2143
  // The non-eval-field check only applies to runtime resource instances:
2078
2144
  // structural / templating kinds (capability `Telo.Template`, or no
2079
- // definition) carry CEL the kernel evaluates by other rules.
2080
- const capability = e.definition?.capability;
2145
+ // definition) carry CEL the kernel evaluates by other rules. The
2146
+ // capability is INHERITED along `extends` — an inheritance kind
2147
+ // writes none of its own — and reading the declared one left the
2148
+ // rule off for every such kind, and its expressions untyped.
2149
+ const resolveDef = (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k);
2150
+ const capability = e.definition
2151
+ ? inheritedCapability(e.definition, resolveDef)
2152
+ : undefined;
2081
2153
  celRuleApplies =
2082
2154
  !!e.definition?.schema && capability !== undefined && capability !== "Telo.Template";
2083
2155
  if (celRuleApplies) {
@@ -2089,12 +2161,14 @@ export class StaticAnalyzer {
2089
2161
  // the own schema here reported `CEL_IN_NON_EVAL_FIELD` for an
2090
2162
  // expression the runtime evaluates correctly, which is the two halves
2091
2163
  // disagreeing about what the manifest means.
2092
- const ownSchema = effectiveAuthorSchema(e.definition, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k));
2164
+ const ownSchema = effectiveAuthorSchema(e.definition, resolveDef);
2093
2165
  const capabilityDef = capability ? defs.resolve(capability) : undefined;
2094
2166
  // A `Telo.Provider`'s fields are implicitly compile-eval — the
2095
2167
  // capability abstract carries the root annotation — so its reads are
2096
- // covered here without the provider restating anything.
2097
- celSites = mergeCelEvalSites(celEvalSites(ownSchema), celEvalSites(capabilityDef?.schema));
2168
+ // covered here without the provider restating anything. A base-form
2169
+ // child's own fields are too (`implicitEvalSites`): `base:` reads
2170
+ // them once at create().
2171
+ celSites = mergeCelEvalSites(celEvalSites(ownSchema), celEvalSites(capabilityDef?.schema), implicitEvalSites(e.definition));
2098
2172
  }
2099
2173
  else {
2100
2174
  celSites = NO_CEL_EVAL_SITES;
@@ -2363,10 +2437,13 @@ export class StaticAnalyzer {
2363
2437
  diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules, libraryManifests));
2364
2438
  // Warn about declared variables / secrets / ports that no CEL references.
2365
2439
  diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
2366
- // A `!ref` at a definition's dispatch slot must name a sibling `resources:`
2367
- // entry — the slot no reference pass reaches, so the tag would otherwise
2368
- // advertise a resolution nothing performs.
2369
- diagnostics.push(...validateTemplateDispatch(allManifests, rootModules));
2440
+ // A template body's reference surface its entry names, its dispatch slots
2441
+ // and the ref slots inside each entry — which no reference pass reaches,
2442
+ // since a `Telo.Definition` is in both skip sets.
2443
+ diagnostics.push(...validateTemplateBody(allManifests, defs, aliases, aliasesByModule, rootModules));
2444
+ // A library's export list, resolved against what it declares — otherwise a
2445
+ // listed name that exists nowhere fails in the consumer's file.
2446
+ diagnostics.push(...validateExports(allManifests, defs, aliases, rootModules));
2370
2447
  // A library's declared resource inputs, and every import that supplies them.
2371
2448
  diagnostics.push(...validateResourceInputs(allManifests, defs, aliases, rootModules, (supplied, required, isDeclaration) => kindSatisfies(supplied, required, defs, isDeclaration)));
2372
2449
  // Reroute diagnostics on synthetic (inline-extracted) resources back to
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA8OvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA+tB/C,CAAC"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAiFA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA8OvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA2pB/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -20,6 +20,60 @@ const observedStateSlot = {
20
20
  description: "What a resource of this kind reports while running, published at `resources.<name>.status.<field>`.",
21
21
  $ref: manifestFragmentRef("JsonSchema7"),
22
22
  };
23
+ /** The `self`-only CEL scope a dispatch slot's name template is written in. */
24
+ const dispatchSelfContext = {
25
+ type: "object",
26
+ additionalProperties: false,
27
+ properties: { self: { "x-telo-context-from-root": "schema" } },
28
+ };
29
+ /**
30
+ * A `Telo.Definition` dispatch slot: which `resources:` entry receives the call.
31
+ *
32
+ * Three shapes, and the union is what keeps the slot CONSTRAINED for manifests
33
+ * no pass walks — `validate-template-body` resolves the target and its
34
+ * capability, but it is entry-scoped, so a dependency's definition would
35
+ * otherwise have nothing checking this slot at all. An array or a number fails
36
+ * every branch here, which is the floor AJV used to provide.
37
+ *
38
+ * `anyOf`, never `oneOf`: a `!ref` sentinel is an object, so it would match both
39
+ * the sentinel branch and any permissive object branch and then fail for
40
+ * matching twice. That is also how the sentinel used to pass — the legacy object
41
+ * branch required nothing, so it accepted one by accident; here it is a branch
42
+ * of its own that says so.
43
+ */
44
+ function dispatchSlot(title, description) {
45
+ return {
46
+ title,
47
+ description,
48
+ anyOf: [
49
+ // `!ref <entry>` — the spelling to write.
50
+ {
51
+ type: "object",
52
+ required: ["__tagged", "engine", "source"],
53
+ properties: {
54
+ __tagged: { const: true },
55
+ engine: { const: "ref" },
56
+ source: { type: "string", minLength: 1 },
57
+ },
58
+ additionalProperties: false,
59
+ },
60
+ // Legacy: a name template expanded against `self`. Read forever, because
61
+ // published artifacts carry it; `DEPRECATED_TEMPLATE_DISPATCH_FORM` is
62
+ // what moves an author off it.
63
+ { type: "string", "x-telo-context": dispatchSelfContext },
64
+ // Legacy: `{ kind?, name }`, where `name` is that same template.
65
+ {
66
+ type: "object",
67
+ required: ["name"],
68
+ properties: {
69
+ kind: { type: "string" },
70
+ name: { type: "string", "x-telo-context": dispatchSelfContext },
71
+ },
72
+ additionalProperties: true,
73
+ },
74
+ ],
75
+ };
76
+ }
23
77
  /** Descriptive provenance a module declares about itself, shared by
24
78
  * `Telo.Application` and `Telo.Library`.
25
79
  *
@@ -459,99 +513,19 @@ export const KERNEL_BUILTINS = [
459
513
  },
460
514
  },
461
515
  },
462
- invoke: {
463
- oneOf: [
464
- {
465
- type: "string",
466
- "x-telo-context": {
467
- type: "object",
468
- additionalProperties: false,
469
- properties: {
470
- self: { "x-telo-context-from-root": "schema" },
471
- },
472
- },
473
- },
474
- {
475
- type: "object",
476
- additionalProperties: true,
477
- properties: {
478
- kind: { type: "string" },
479
- name: {
480
- type: "string",
481
- "x-telo-context": {
482
- type: "object",
483
- additionalProperties: false,
484
- properties: {
485
- self: { "x-telo-context-from-root": "schema" },
486
- },
487
- },
488
- },
489
- },
490
- },
491
- ],
492
- },
493
- provide: {
494
- type: "object",
495
- additionalProperties: true,
496
- properties: {
497
- kind: { type: "string" },
498
- name: {
499
- type: "string",
500
- "x-telo-context": {
501
- type: "object",
502
- additionalProperties: false,
503
- properties: {
504
- self: { "x-telo-context-from-root": "schema" },
505
- },
506
- },
507
- },
508
- },
509
- },
510
- run: {
511
- type: "string",
512
- "x-telo-context": {
513
- type: "object",
514
- additionalProperties: false,
515
- properties: {
516
- self: { "x-telo-context-from-root": "schema" },
517
- },
518
- },
519
- },
520
- // Mount dispatch: names the `resources:` entry (a Telo.Mount, e.g. an
521
- // Http.Api) whose `register()` this definition delegates to. Same
522
- // string / { kind, name } grammar as `invoke:`. The named child stays
523
- // persistent so the produced mount's routes can `!ref` its siblings.
524
- mount: {
525
- oneOf: [
526
- {
527
- type: "string",
528
- "x-telo-context": {
529
- type: "object",
530
- additionalProperties: false,
531
- properties: {
532
- self: { "x-telo-context-from-root": "schema" },
533
- },
534
- },
535
- },
536
- {
537
- type: "object",
538
- additionalProperties: true,
539
- properties: {
540
- kind: { type: "string" },
541
- name: {
542
- type: "string",
543
- "x-telo-context": {
544
- type: "object",
545
- additionalProperties: false,
546
- properties: {
547
- self: { "x-telo-context-from-root": "schema" },
548
- },
549
- },
550
- },
551
- },
552
- },
553
- ],
554
- },
516
+ // A dispatch slot names the `resources:` entry that receives the call,
517
+ // as `!ref <entry>` — the one spelling every reference in Telo has. The
518
+ // string and `{ kind, name }` forms it used to admit (a CEL name
519
+ // template matched against CEL-named entries) were the removed
520
+ // reference object surviving in the one place nothing resolved it;
521
+ // `validate-template-body` is what resolves this slot, so the schema
522
+ // stays open here rather than reporting the same defect twice.
523
+ invoke: dispatchSlot("Invoke target", "The `resources:` entry whose `invoke()` this kind dispatches to, as `!ref <entry>`."),
524
+ provide: dispatchSlot("Provide target", "The `resources:` entry whose `invoke()` produces this provider's value, as `!ref <entry>`."),
525
+ run: dispatchSlot("Run target", "The `resources:` entry whose `run()` this kind dispatches to, as `!ref <entry>`."),
526
+ // The named child stays persistent so the produced mount's routes can
527
+ // `!ref` its siblings.
528
+ mount: dispatchSlot("Mount target", "The `resources:` entry (a Telo.Mount, e.g. an Http.Api) whose `register()` this kind delegates to, as `!ref <entry>`."),
555
529
  inputs: {
556
530
  type: "object",
557
531
  additionalProperties: true,
@@ -572,11 +546,11 @@ export const KERNEL_BUILTINS = [
572
546
  additionalProperties: false,
573
547
  properties: {
574
548
  self: { "x-telo-context-from-root": "schema" },
549
+ // Typed from the dispatch target's declared output: the slot
550
+ // holds a `!ref` to a `resources:` entry, resolved to that
551
+ // entry's kind (or its own `outputType`, where it narrows one).
575
552
  result: {
576
- "x-telo-context-from-ref-kind": [
577
- "provide/kind#outputType",
578
- "invoke/kind#outputType",
579
- ],
553
+ "x-telo-context-from-ref-kind": ["provide#outputType", "invoke#outputType"],
580
554
  },
581
555
  },
582
556
  },
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Which `catches:` lists answer for one dispatch site.
3
+ *
4
+ * A catch list is one rung of a SCOPE LADDER — a route's, its router's, its
5
+ * server's — and every rule over those lists needs the same two answers: what
6
+ * a scope list is checked against, and which scopes enclose a given site. This
7
+ * module owns that model; the rule checks in `validate-throws-coverage.ts` read
8
+ * one answer instead of rebuilding it beside four unrelated jobs.
9
+ *
10
+ * Browser-safe: no Node built-ins.
11
+ */
12
+ import type { DefinitionRegistry } from "./definition-registry.js";
13
+ import type { AliasResolver } from "./alias-resolver.js";
14
+ import type { ResourceManifest } from "@telorun/sdk";
15
+ import { type ResolveCtx } from "./resolve-throws-union.js";
16
+ /** What a list proves it renders: the codes its coverage-proving `when:` clauses
17
+ * name, and whether it ends in a catch-all.
18
+ *
19
+ * Unfiltered by any denominator, because this is also what an enclosing scope
20
+ * contributes DOWNWARD — a server entry naming a code the server's own closure
21
+ * could not enumerate still renders it for the route that throws it. Filtering
22
+ * the local answer against the declared union happens where that answer is
23
+ * used, and subtracting from the declared set makes the two equivalent there. */
24
+ export interface ProvenCoverage {
25
+ codes: Set<string>;
26
+ hasCatchAll: boolean;
27
+ }
28
+ export declare const NO_COVERAGE: ProvenCoverage;
29
+ /** A declaration inside another resource's `x-telo-scope` array, kept with the
30
+ * resource that encloses it and the path it is written at in that resource's
31
+ * document. Both are needed: the owner is the only place its kind's alias scope
32
+ * can be found, and the only document position lookup can reach. */
33
+ export interface ScopedManifest {
34
+ manifest: ResourceManifest;
35
+ owner: ResourceManifest;
36
+ /** Concrete owner-relative path of the declaration (`with[0]`). */
37
+ path: string;
38
+ }
39
+ /** Every `with:`-scoped declaration in the set, once each.
40
+ *
41
+ * Scoped resources are absent from the flat manifest list, so every check that
42
+ * iterates it skips them — and standing a server up around a test is exactly
43
+ * that shape, so the sanctioned pattern was the one the pass could not see.
44
+ * Discovered through the shared visitor rather than a second scope walk. */
45
+ export declare function collectScopedManifests(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, rootModules: Set<string>): ScopedManifest[];
46
+ /** Which resources each resource's scope list encloses, along the slots that
47
+ * declare `throwsThrough` — one fact stated once, since the edge a throws
48
+ * closure crosses is the edge a catch scope encloses through. */
49
+ export declare function buildEnclosers(manifests: ResourceManifest[], definitionOf: (m: ResourceManifest) => {
50
+ schema?: Record<string, any>;
51
+ } | undefined, moduleOf: (m: ResourceManifest) => string | undefined, ctx: ResolveCtx): Map<ResourceManifest, ResourceManifest[]>;
52
+ /**
53
+ * What every scope enclosing a resource is guaranteed to render for it.
54
+ *
55
+ * **Across enclosers this INTERSECTS, and that is the whole correctness of the
56
+ * reduction.** Coverage claims that a throw cannot escape unrendered, so it holds
57
+ * only when EVERY path to the site renders it — a router mounted on a public
58
+ * server with a catch-all and an internal one without is covered on one path and
59
+ * bare on the other, and unioning the two reported it as fully covered while the
60
+ * internal server answered with the built-in envelope. A resource with no
61
+ * encloser contributes nothing rather than everything: the empty set is the
62
+ * identity for the site's own coverage, and treating "no paths" as "all paths
63
+ * agree" would assert coverage no list provides.
64
+ *
65
+ * A resource's OWN scope list is unioned in, because it applies on every path.
66
+ *
67
+ * Memoized, and a cycle among `throwsThrough` edges resolves to what has been
68
+ * accumulated so far rather than raising: this answers what a scope renders, and
69
+ * no cycle makes that answer larger.
70
+ */
71
+ export declare function enclosingCoverage(manifest: ResourceManifest, ownScope: Map<ResourceManifest, ProvenCoverage>, enclosers: Map<ResourceManifest, ResourceManifest[]>, memo?: Map<ResourceManifest, ProvenCoverage>, walking?: Set<ResourceManifest>): ProvenCoverage;
72
+ //# sourceMappingURL=catch-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catch-scope.d.ts","sourceRoot":"","sources":["../src/catch-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEhF;;;;;;;kFAOkF;AAClF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,cAAyD,CAAC;AAEpF;;;qEAGqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;6EAK6E;AAC7E,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,cAAc,EAAE,CAgBlB;AAED;;kEAEkE;AAClE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,YAAY,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,GAAG,SAAS,EACnF,QAAQ,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,EACrD,GAAG,EAAE,UAAU,GACd,GAAG,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,CAe3C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAC,EAC/C,SAAS,EAAE,GAAG,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,EACpD,IAAI,GAAE,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAa,EACvD,OAAO,GAAE,GAAG,CAAC,gBAAgB,CAAa,GACzC,cAAc,CAgChB"}
@@ -0,0 +1,102 @@
1
+ import { visitManifest } from "./manifest-visitor.js";
2
+ import { forEachDrivenSlot } from "./schema-walk.js";
3
+ import { resolveRefManifest } from "./resolve-throws-union.js";
4
+ export const NO_COVERAGE = { codes: new Set(), hasCatchAll: false };
5
+ /** Every `with:`-scoped declaration in the set, once each.
6
+ *
7
+ * Scoped resources are absent from the flat manifest list, so every check that
8
+ * iterates it skips them — and standing a server up around a test is exactly
9
+ * that shape, so the sanctioned pattern was the one the pass could not see.
10
+ * Discovered through the shared visitor rather than a second scope walk. */
11
+ export function collectScopedManifests(manifests, defs, aliases, aliasesByModule, rootModules) {
12
+ const scoped = [];
13
+ visitManifest(manifests, defs, {
14
+ onScope: (event) => {
15
+ for (const { manifest, path } of event.declarations) {
16
+ if (!manifest?.kind || !manifest.metadata?.name)
17
+ continue;
18
+ scoped.push({ manifest, owner: event.source, path });
19
+ }
20
+ },
21
+ }, { aliases, aliasesByModule, rootModules });
22
+ return scoped;
23
+ }
24
+ /** Which resources each resource's scope list encloses, along the slots that
25
+ * declare `throwsThrough` — one fact stated once, since the edge a throws
26
+ * closure crosses is the edge a catch scope encloses through. */
27
+ export function buildEnclosers(manifests, definitionOf, moduleOf, ctx) {
28
+ const enclosers = new Map();
29
+ for (const manifest of manifests) {
30
+ const definition = definitionOf(manifest);
31
+ if (!definition?.schema)
32
+ continue;
33
+ forEachDrivenSlot(definition.schema, manifest, (driven) => {
34
+ if (driven.kind !== "ref" || !driven.slot.throwsThrough)
35
+ return;
36
+ const target = resolveRefManifest(driven.data, ctx, moduleOf(manifest));
37
+ if (!target || target === manifest)
38
+ return;
39
+ const list = enclosers.get(target);
40
+ if (list)
41
+ list.push(manifest);
42
+ else
43
+ enclosers.set(target, [manifest]);
44
+ });
45
+ }
46
+ return enclosers;
47
+ }
48
+ /**
49
+ * What every scope enclosing a resource is guaranteed to render for it.
50
+ *
51
+ * **Across enclosers this INTERSECTS, and that is the whole correctness of the
52
+ * reduction.** Coverage claims that a throw cannot escape unrendered, so it holds
53
+ * only when EVERY path to the site renders it — a router mounted on a public
54
+ * server with a catch-all and an internal one without is covered on one path and
55
+ * bare on the other, and unioning the two reported it as fully covered while the
56
+ * internal server answered with the built-in envelope. A resource with no
57
+ * encloser contributes nothing rather than everything: the empty set is the
58
+ * identity for the site's own coverage, and treating "no paths" as "all paths
59
+ * agree" would assert coverage no list provides.
60
+ *
61
+ * A resource's OWN scope list is unioned in, because it applies on every path.
62
+ *
63
+ * Memoized, and a cycle among `throwsThrough` edges resolves to what has been
64
+ * accumulated so far rather than raising: this answers what a scope renders, and
65
+ * no cycle makes that answer larger.
66
+ */
67
+ export function enclosingCoverage(manifest, ownScope, enclosers, memo = new Map(), walking = new Set()) {
68
+ const cached = memo.get(manifest);
69
+ if (cached)
70
+ return cached;
71
+ if (walking.has(manifest))
72
+ return NO_COVERAGE;
73
+ walking.add(manifest);
74
+ try {
75
+ const own = ownScope.get(manifest);
76
+ const result = {
77
+ codes: new Set(own?.codes ?? []),
78
+ hasCatchAll: own?.hasCatchAll ?? false,
79
+ };
80
+ const outer = (enclosers.get(manifest) ?? []).map((e) => enclosingCoverage(e, ownScope, enclosers, memo, walking));
81
+ if (outer.length > 0) {
82
+ const [first, ...rest] = outer;
83
+ const shared = new Set(first.codes);
84
+ let everyCatchAll = first.hasCatchAll;
85
+ for (const other of rest) {
86
+ for (const code of [...shared])
87
+ if (!other.codes.has(code))
88
+ shared.delete(code);
89
+ everyCatchAll &&= other.hasCatchAll;
90
+ }
91
+ for (const code of shared)
92
+ result.codes.add(code);
93
+ if (everyCatchAll)
94
+ result.hasCatchAll = true;
95
+ }
96
+ memo.set(manifest, result);
97
+ return result;
98
+ }
99
+ finally {
100
+ walking.delete(manifest);
101
+ }
102
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"cel-scope-query.d.ts","sourceRoot":"","sources":["../src/cel-scope-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAkB9D;;;sCAGsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;iEAI6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoBD;gEACgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IActB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C;;oDAEgD;IAChD,OAAO,CAAC,OAAO,CAA+B;IAC9C;;;;sEAIkE;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsD;gBAG9D,SAAS,EAAE,gBAAgB,EAAE,EAC9C,GAAG,EAAE,oBAAoB,EACzB,MAAM,CAAC,EAAE,WAAW;IAgCtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;IAE3C;+DAC2D;IAC3D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS;IAI7F;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAe3D;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,CACpB,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,GACd,sBAAsB,GAAG,SAAS;IA0CrC;oFACgF;IAChF,OAAO,CAAC,QAAQ;IAgFhB;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAqBrF;;;;;;;sDAOkD;IAClD,OAAO,CAAC,aAAa;IAOrB;wEACoE;IACpE,OAAO,CAAC,YAAY;CAarB"}
1
+ {"version":3,"file":"cel-scope-query.d.ts","sourceRoot":"","sources":["../src/cel-scope-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAmB9D;;;sCAGsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;iEAI6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoBD;gEACgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IActB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C;;oDAEgD;IAChD,OAAO,CAAC,OAAO,CAA+B;IAC9C;;;;sEAIkE;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsD;gBAG9D,SAAS,EAAE,gBAAgB,EAAE,EAC9C,GAAG,EAAE,oBAAoB,EACzB,MAAM,CAAC,EAAE,WAAW;IAgCtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;IAE3C;+DAC2D;IAC3D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS;IAI7F;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAe3D;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,CACpB,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,GACd,sBAAsB,GAAG,SAAS;IA0CrC;oFACgF;IAChF,OAAO,CAAC,QAAQ;IAkGhB;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAqBrF;;;;;;;sDAOkD;IAClD,OAAO,CAAC,aAAa;IAOrB;wEACoE;IACpE,OAAO,CAAC,YAAY;CAarB"}
@@ -4,6 +4,7 @@ import { buildKernelGlobalsIndex } from "./kernel-globals.js";
4
4
  import { moduleAliasScope } from "./module-alias-scope.js";
5
5
  import { isModuleKind } from "./module-kinds.js";
6
6
  import { navigateConcretePath } from "./manifest-path.js";
7
+ import { kindAtPath } from "./validate-cel-context.js";
7
8
  import { findManifest } from "./find-manifest.js";
8
9
  import { resolveLocalRef, walkStepArray } from "./schema-walk.js";
9
10
  import { readStepSlot } from "./step-slot.js";
@@ -200,16 +201,30 @@ export class CelScopeQuery {
200
201
  return { manifest: target, path: refFrom.slice(slash + 1).split("/").join("."), propertyMap: false };
201
202
  }
202
203
  // A kind's own declaration: the target is the `Telo.Definition` document,
203
- // which is an ordinary manifest in the set.
204
+ // which is an ordinary manifest in the set. The slot holds a `!ref` to a
205
+ // `resources:` entry; the entry's own field wins where it narrows one, else
206
+ // the declaration is the entry's KIND. The first slot that resolves to an
207
+ // entry is the one the type resolver reads, so it is the one navigated.
204
208
  const fromRefKind = annotated["x-telo-context-from-ref-kind"];
205
- const first = Array.isArray(fromRefKind) ? fromRefKind[0] : fromRefKind;
206
- if (typeof first === "string") {
207
- const hash = first.indexOf("#");
209
+ const slots = Array.isArray(fromRefKind) ? fromRefKind : [fromRefKind];
210
+ for (const slotSpec of slots) {
211
+ if (typeof slotSpec !== "string")
212
+ continue;
213
+ const hash = slotSpec.indexOf("#");
214
+ // `continue`, never `return`: a malformed spec is one entry of a list the
215
+ // type resolver walks to the end, and aborting here made the two answer
216
+ // differently for the same annotation.
208
217
  if (hash <= 0)
209
- return undefined;
210
- const kindValue = navigateConcretePath(root, first.slice(0, hash).split("/").join("."));
211
- if (typeof kindValue !== "string")
212
- return undefined;
218
+ continue;
219
+ const field = slotSpec.slice(hash + 1);
220
+ const namedKind = kindAtPath(root, slotSpec.slice(0, hash));
221
+ if (!namedKind)
222
+ continue;
223
+ if (namedKind.entry?.[field] !== undefined) {
224
+ const index = root.resources.indexOf(namedKind.entry);
225
+ return { manifest: resource, path: `resources[${index}].${field}`, propertyMap: false };
226
+ }
227
+ const kindValue = namedKind.kind;
213
228
  // The kind was read off THIS resource, so it is spelled in the alias scope
214
229
  // of the module that declared it — not the entry's.
215
230
  const scope = moduleAliasScope(resource.metadata, this.ctx.aliases, this.ctx.aliasesByModule);
@@ -229,10 +244,16 @@ export class CelScopeQuery {
229
244
  const target = (owningModule
230
245
  ? named.find((m) => m.metadata?.module === owningModule)
231
246
  : undefined) ?? (owningModule && named.length > 1 ? undefined : named[0]);
232
- if (!target)
233
- return undefined;
234
- return { manifest: target, path: first.slice(hash + 1), propertyMap: false };
247
+ // Fall through to the next spec when this one names no kind, or names one
248
+ // that does not declare the field — the same order the TYPE resolver
249
+ // takes, so go-to-declaration lands where the type came from instead of on
250
+ // a field the target never had.
251
+ if (!target || target[field] === undefined)
252
+ continue;
253
+ return { manifest: target, path: field, propertyMap: false };
235
254
  }
255
+ if (slots.some((s) => typeof s === "string"))
256
+ return undefined;
236
257
  // `x-telo-context-element-from` / `-collection-from` type a binding from an
237
258
  // EXPRESSION, so there is no declaration to navigate to.
238
259
  return undefined;