@telorun/analyzer 0.65.0 → 0.66.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 (100) hide show
  1. package/dist/analyzer.d.ts +1 -1
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +157 -63
  4. package/dist/call-graph.d.ts.map +1 -1
  5. package/dist/call-graph.js +7 -1
  6. package/dist/cel-environment.d.ts +12 -0
  7. package/dist/cel-environment.d.ts.map +1 -1
  8. package/dist/cel-environment.js +35 -19
  9. package/dist/cel-scope.d.ts.map +1 -1
  10. package/dist/cel-scope.js +24 -4
  11. package/dist/definition-registry.d.ts +24 -0
  12. package/dist/definition-registry.d.ts.map +1 -1
  13. package/dist/definition-registry.js +41 -0
  14. package/dist/eval-paths.d.ts +68 -0
  15. package/dist/eval-paths.d.ts.map +1 -1
  16. package/dist/eval-paths.js +138 -0
  17. package/dist/flatten-for-analyzer.d.ts +1 -1
  18. package/dist/flatten-for-analyzer.js +1 -1
  19. package/dist/import-resolution-diagnostics.d.ts +1 -1
  20. package/dist/import-resolution-diagnostics.js +1 -1
  21. package/dist/index.d.ts +9 -7
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -5
  24. package/dist/manifest-visitor.d.ts.map +1 -1
  25. package/dist/manifest-visitor.js +8 -1
  26. package/dist/migrations/entries/index.d.ts.map +1 -1
  27. package/dist/migrations/entries/index.js +2 -0
  28. package/dist/migrations/entries/schema-prepare-bucket.json +27 -0
  29. package/dist/peer-binding.d.ts +232 -0
  30. package/dist/peer-binding.d.ts.map +1 -0
  31. package/dist/peer-binding.js +418 -0
  32. package/dist/ref-slot.d.ts +13 -0
  33. package/dist/ref-slot.d.ts.map +1 -1
  34. package/dist/ref-slot.js +26 -0
  35. package/dist/reference-field-map.d.ts +28 -0
  36. package/dist/reference-field-map.d.ts.map +1 -1
  37. package/dist/reference-field-map.js +25 -0
  38. package/dist/referrer-rule.d.ts +17 -0
  39. package/dist/referrer-rule.d.ts.map +1 -1
  40. package/dist/referrer-rule.js +4 -1
  41. package/dist/release/index.d.ts +1 -1
  42. package/dist/release/index.js +1 -1
  43. package/dist/resource-rule.d.ts +40 -27
  44. package/dist/resource-rule.d.ts.map +1 -1
  45. package/dist/resource-rule.js +61 -7
  46. package/dist/rule-condition.d.ts +11 -0
  47. package/dist/rule-condition.d.ts.map +1 -1
  48. package/dist/rule-condition.js +13 -0
  49. package/dist/schema-projection.d.ts +106 -4
  50. package/dist/schema-projection.d.ts.map +1 -1
  51. package/dist/schema-projection.js +206 -18
  52. package/dist/telo-version.d.ts +1 -1
  53. package/dist/telo-version.js +1 -1
  54. package/dist/validate-cel-context.d.ts +1 -17
  55. package/dist/validate-cel-context.d.ts.map +1 -1
  56. package/dist/validate-cel-context.js +5 -70
  57. package/dist/validate-reference-forms.d.ts +10 -0
  58. package/dist/validate-reference-forms.d.ts.map +1 -1
  59. package/dist/validate-reference-forms.js +23 -0
  60. package/dist/validate-references.d.ts.map +1 -1
  61. package/dist/validate-references.js +9 -2
  62. package/dist/validate-referrer-rules.d.ts +38 -4
  63. package/dist/validate-referrer-rules.d.ts.map +1 -1
  64. package/dist/validate-referrer-rules.js +139 -31
  65. package/dist/validate-resource-rules.d.ts +11 -2
  66. package/dist/validate-resource-rules.d.ts.map +1 -1
  67. package/dist/validate-resource-rules.js +18 -12
  68. package/dist/validate-schema-projection.d.ts.map +1 -1
  69. package/dist/validate-schema-projection.js +33 -2
  70. package/dist/with-synthetic-positions.d.ts +1 -1
  71. package/dist/with-synthetic-positions.js +1 -1
  72. package/package.json +3 -3
  73. package/src/analyzer.ts +191 -68
  74. package/src/call-graph.ts +7 -1
  75. package/src/cel-environment.ts +48 -19
  76. package/src/cel-scope.ts +24 -6
  77. package/src/definition-registry.ts +42 -0
  78. package/src/eval-paths.ts +152 -0
  79. package/src/flatten-for-analyzer.ts +1 -1
  80. package/src/import-resolution-diagnostics.ts +1 -1
  81. package/src/index.ts +45 -4
  82. package/src/manifest-visitor.ts +8 -1
  83. package/src/migrations/entries/index.ts +2 -0
  84. package/src/migrations/entries/schema-prepare-bucket.json +27 -0
  85. package/src/peer-binding.ts +641 -0
  86. package/src/ref-slot.ts +36 -0
  87. package/src/reference-field-map.ts +42 -0
  88. package/src/referrer-rule.ts +20 -1
  89. package/src/release/index.ts +1 -1
  90. package/src/resource-rule.ts +69 -7
  91. package/src/rule-condition.ts +15 -0
  92. package/src/schema-projection.ts +283 -20
  93. package/src/telo-version.ts +1 -1
  94. package/src/validate-cel-context.ts +6 -71
  95. package/src/validate-reference-forms.ts +22 -0
  96. package/src/validate-references.ts +15 -2
  97. package/src/validate-referrer-rules.ts +215 -28
  98. package/src/validate-resource-rules.ts +26 -12
  99. package/src/validate-schema-projection.ts +50 -1
  100. package/src/with-synthetic-positions.ts +1 -1
@@ -33,7 +33,7 @@ export declare class StaticAnalyzer {
33
33
  * `DUPLICATE_RESOURCE_NAME` reads those fields to tell a pipeline echo
34
34
  * apart from a genuine collision, and downstream diagnostic positioning
35
35
  * depends on them too. Real callers stamp positions already (the `Loader`,
36
- * `flattenForAnalyzer`, the telo-editor's `emitDocsFor`, the VSCode
36
+ * `flattenForAnalyzer`, telo studio's `emitDocsFor`, the VSCode
37
37
  * extension). Programmatic callers — tests, ad-hoc scripts — should pass
38
38
  * their inputs through `withSyntheticPositions(...)` before calling
39
39
  * `analyze()`. A missing position throws a clear error rather than
@@ -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,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA8B9B,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;AAoDjE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA2F/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;IAqxDvB,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;AA8B9B,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;AAwDjE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA+F/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;IAu4DvB,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
@@ -1,7 +1,7 @@
1
1
  import { canonicalTypeSchemaId, OBSERVED_STATE_KEY } from "@telorun/sdk";
2
2
  import { defaultRegistry, isRefSentinel, isTaggedSentinel, plainChainOf, } from "@telorun/templating";
3
3
  import { AliasResolver, scopeResolverForModule, } from "./alias-resolver.js";
4
- import { buildCelEnvironment, } from "./cel-environment.js";
4
+ import { buildCelEnvironment, isKindDocument, } from "./cel-environment.js";
5
5
  import { DefinitionRegistry } from "./definition-registry.js";
6
6
  import { effectiveAuthorSchema } from "./extends-resolution.js";
7
7
  import { analyzerContractScope, resolveContract, } from "./invocation-contract.js";
@@ -30,10 +30,11 @@ export { analyzerContractScope } from "./invocation-contract.js";
30
30
  import { validateZoneSlotDeclarations } from "./validate-zone-slots.js";
31
31
  import { validateSchemaProjection, } from "./validate-schema-projection.js";
32
32
  import { evaluateResourceRules, reportResourceRules, reportUnexercisedRule, ruleExercised, validateResourceRuleDeclarations, } from "./validate-resource-rules.js";
33
- import { readResourceRules } from "./resource-rule.js";
33
+ import { pointerToPath, readResourceRules } from "./resource-rule.js";
34
34
  import { readReferrerRules } from "./referrer-rule.js";
35
35
  import { evaluateReferrerRules, referrerRuleExercised, reportReferrerRules, reportUnexercisedReferrerRule, validateReferrerRuleDeclarations, } from "./validate-referrer-rules.js";
36
- import { describeProjectionFailure, } from "./schema-projection.js";
36
+ import { analyzerPeerBinder, analyzerPeersTarget } from "./peer-binding.js";
37
+ import { describeProjectionFailure, manifestListScope, resolveSchemaProjections, } from "./schema-projection.js";
37
38
  import { validateDynamicSelectors, validateRefSlotDeclarations, } from "./validate-ref-slots.js";
38
39
  import { validateValueTypeSlots, } from "./validate-value-type-slots.js";
39
40
  import { resolveSchemaTypeRefs } from "./resolve-schema-type-refs.js";
@@ -42,8 +43,8 @@ import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
42
43
  import { celTypeSatisfiesJsonSchema, checkSchemaCompatibility, navigateSchemaToExprPath, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
43
44
  import { collectValueSchemaIssues } from "./validate-value-schema.js";
44
45
  import { DiagnosticSeverity } from "./types.js";
45
- import { extractAccessChains, extractCelRegionScopes, pathMatchesScope, } from "./validate-cel-context.js";
46
- import { buildEvalPaths, evalPathsCover } from "./eval-paths.js";
46
+ import { extractAccessChains, } from "./validate-cel-context.js";
47
+ import { celEvalModeAt, celEvalSites, mergeCelEvalSites, NO_CEL_EVAL_SITES, } from "./eval-paths.js";
47
48
  import { BINDINGS_ANNOTATION, findBindingSites, resolveBindingOrder, } from "./cel-bindings.js";
48
49
  import { CEL_RESERVED_WORDS, checkName } from "./identifier-name.js";
49
50
  import { validateIdentifierNames } from "./validate-identifier-names.js";
@@ -554,7 +555,7 @@ export class StaticAnalyzer {
554
555
  * `DUPLICATE_RESOURCE_NAME` reads those fields to tell a pipeline echo
555
556
  * apart from a genuine collision, and downstream diagnostic positioning
556
557
  * depends on them too. Real callers stamp positions already (the `Loader`,
557
- * `flattenForAnalyzer`, the telo-editor's `emitDocsFor`, the VSCode
558
+ * `flattenForAnalyzer`, telo studio's `emitDocsFor`, the VSCode
558
559
  * extension). Programmatic callers — tests, ad-hoc scripts — should pass
559
560
  * their inputs through `withSyntheticPositions(...)` before calling
560
561
  * `analyze()`. A missing position throws a clear error rather than
@@ -776,6 +777,11 @@ export class StaticAnalyzer {
776
777
  const projectionIssues = [];
777
778
  const resourceRuleIssues = [];
778
779
  const referrerRuleIssues = [];
780
+ /** Definition docs of the entry's own modules that may declare rules. Their
781
+ * referrer-rule declarations are checked after every kind is registered,
782
+ * because a `peers:` pointer is checked against the REFERRER kind's schema
783
+ * and that kind may be declared later in the same file. */
784
+ const ownRuleDeclarers = [];
779
785
  // A rule that never had anything to iterate is never proven — the second way
780
786
  // coverage varies invisibly, beside the dynamic-leaf skip. Tracked across the
781
787
  // whole run and reported once, since "empty on every resource" is not a fact
@@ -822,7 +828,10 @@ export class StaticAnalyzer {
822
828
  // inherited field resolves — which is what lets a rule shared by every
823
829
  // backend be declared once on the abstract they extend.
824
830
  resourceRuleIssues.push(...validateResourceRuleDeclarations(m, effectiveAuthorSchema(m, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k))));
825
- referrerRuleIssues.push(...validateReferrerRuleDeclarations(m));
831
+ // Deferred to after the registration loop: the `peers:` half is checked
832
+ // against the REFERRER kind's schema, and a rule may name a kind
833
+ // declared later in the same file.
834
+ ownRuleDeclarers.push(m);
826
835
  for (const rule of readReferrerRules(m.schema)) {
827
836
  referrerRuleExercise.set(`${m.metadata?.module}.${m.metadata?.name}#${rule.code}`, {
828
837
  manifest: m,
@@ -873,6 +882,23 @@ export class StaticAnalyzer {
873
882
  : def;
874
883
  defs.register(normalized);
875
884
  }
885
+ /**
886
+ * What a peer rule's `peers:` pointer names in the kind its `referrer:`
887
+ * filters to.
888
+ *
889
+ * Liskov in BOTH directions, which is what the check has to be: the filter
890
+ * is usually an abstract (`Sql.Schema`, so one rule serves every backend)
891
+ * while the collection is declared by the backends that implement it, so a
892
+ * pointer resolving on any candidate resolves the rule. Reported only when
893
+ * NO candidate declares it, or when every candidate that does holds plain
894
+ * data — the two shapes where the rule would see no declaration at all.
895
+ */
896
+ const peersTarget = analyzerPeersTarget(defs);
897
+ if (!options?.skipValidation) {
898
+ for (const declarer of ownRuleDeclarers) {
899
+ referrerRuleIssues.push(...validateReferrerRuleDeclarations(declarer, { peersTarget }));
900
+ }
901
+ }
876
902
  // Reference-form validation — enforce `!ref` as the only reference shape.
877
903
  // Runs on the RAW manifests, BEFORE inline extraction and sentinel
878
904
  // resolution, while an author-written `{kind, name}` is still
@@ -1277,13 +1303,12 @@ export class StaticAnalyzer {
1277
1303
  if (options?.skipValidation) {
1278
1304
  return suppressUnreadableModuleDiagnostics(diagnostics, unreadableFiles);
1279
1305
  }
1280
- // Build a name→manifest map for looking up referenced resources
1281
- const byName = new Map();
1282
- for (const m of allManifests) {
1283
- if (m.metadata?.name) {
1284
- byName.set(m.metadata.name, m);
1285
- }
1286
- }
1306
+ // ONE binder for the whole run, built beside the binding it serves: it caches
1307
+ // each referrer's resolved collection, which is what keeps a rule over an
1308
+ // n-entry collection from re-resolving that collection once per entry.
1309
+ const referrerRuleContext = {
1310
+ peerBinder: analyzerPeerBinder(defs, aliases, allManifests),
1311
+ };
1287
1312
  // Fail loud on definition schemas AJV cannot compile. `validateAgainstSchema`
1288
1313
  // and `validateWithRefs` swallow compile failures (returning no issues),
1289
1314
  // which would silently skip schema validation for every resource of that
@@ -1420,6 +1445,44 @@ export class StaticAnalyzer {
1420
1445
  moduleManifest,
1421
1446
  observedStateContext,
1422
1447
  });
1448
+ /**
1449
+ * The schema a resource of this kind is validated against, per DEFINITION.
1450
+ *
1451
+ * Both halves derive a fresh object — `effectiveAuthorSchema` merges along
1452
+ * `extends`, and the closed-schema branch spreads `kind` / `metadata` in —
1453
+ * so asking per resource handed `validateResourceConfig` a schema object it
1454
+ * had never seen. That registry memoizes its compiled AJV validator by
1455
+ * object IDENTITY, precisely because every resource of a kind is checked
1456
+ * against the same one at keystroke time, so a fresh object per resource
1457
+ * recompiled the whole kind schema per resource. Keyed on the definition,
1458
+ * which is stable for the run.
1459
+ */
1460
+ const authorSchemaCache = new WeakMap();
1461
+ const validationSchemaFor = (def) => {
1462
+ const cached = authorSchemaCache.get(def);
1463
+ if (cached)
1464
+ return cached;
1465
+ const authorSchema = effectiveAuthorSchema(def, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k));
1466
+ // `kind` and `metadata` are implicit on every resource — inject them so
1467
+ // module authors don't have to repeat them under `additionalProperties:
1468
+ // false`.
1469
+ const schema = authorSchema.additionalProperties === false
1470
+ ? {
1471
+ ...authorSchema,
1472
+ properties: {
1473
+ kind: { type: "string" },
1474
+ metadata: { type: "object" },
1475
+ ...authorSchema.properties,
1476
+ },
1477
+ }
1478
+ : authorSchema;
1479
+ authorSchemaCache.set(def, schema);
1480
+ return schema;
1481
+ };
1482
+ // One scope for the whole run: it closes over the manifest list and the
1483
+ // registry, neither of which changes per resource, and it is asked once per
1484
+ // schema-valued slot that carries a projection.
1485
+ const resourceProjectionScope = manifestListScope(allManifests, (kind) => (defs.resolve(kind) ?? defs.resolve(aliases.resolveKind(kind) ?? kind)));
1423
1486
  // Validate each non-definition, non-system resource
1424
1487
  for (const m of allManifests) {
1425
1488
  const filePath = m.metadata?.source;
@@ -1510,34 +1573,47 @@ export class StaticAnalyzer {
1510
1573
  continue;
1511
1574
  }
1512
1575
  // Validate resource config against the definition's AUTHOR-FACING schema —
1513
- // inheritance-resolved: with `base:` the child's own schema (parent config
1514
- // is internal), else `merge(parent, own)` so a `base:`-less `extends` child
1515
- // is validated against the inherited fields it may set. For a definition
1516
- // that neither extends nor uses `base:` this is exactly its own schema.
1517
- // `kind` and `metadata` are implicit on every resource — inject them so module
1518
- // authors don't have to repeat them when using additionalProperties: false.
1519
- const authorSchema = effectiveAuthorSchema(definition, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k));
1520
- if (authorSchema && Object.keys(authorSchema).length > 0) {
1521
- const schema = authorSchema.additionalProperties === false
1522
- ? {
1523
- ...authorSchema,
1524
- properties: {
1525
- kind: { type: "string" },
1526
- metadata: { type: "object" },
1527
- ...authorSchema.properties,
1528
- },
1529
- }
1530
- : authorSchema;
1576
+ // inheritance-resolved, with `kind` / `metadata` injected. See
1577
+ // `validationSchemaFor`, which is where both derivations and the reason
1578
+ // they are memoized per definition live.
1579
+ const schema = validationSchemaFor(definition);
1580
+ if (Object.keys(schema).length > 0) {
1531
1581
  // Phase 1: collect the pure-CEL leaves and the schema of the slot each
1532
1582
  // flows into. The expression's own type is resolved later, by the
1533
1583
  // engine walk that owns type-checking; this half only knows the target.
1534
1584
  for (const slot of collectCelValueSlots(m, schema, "")) {
1535
1585
  celReturnSlots.push({ manifest: m, resource, filePath, ...slot });
1536
1586
  }
1587
+ // A kind's own schema may point a slot at a PROJECTION — of a resource it
1588
+ // references, or (the empty pointer) of this very declaration, which is
1589
+ // what types a table's seed rows against its own `columns:`. Resolved
1590
+ // PER RESOURCE, because a projection is declaration-derived: the same
1591
+ // kind schema yields a different row shape for every table.
1592
+ //
1593
+ // A failure here is REPORTED, never dropped. An entry that could not be
1594
+ // read vanishes from the projected shape, so the slot typed from it then
1595
+ // rejects a field the author did declare ("'status' is not allowed") with
1596
+ // nothing saying why — the projection's own failure mode, blaming the
1597
+ // wrong line. Entry-module-scoped, like every other schema issue.
1598
+ const projectionFailures = [];
1599
+ const projected = resolveSchemaProjections(schema, m, resourceProjectionScope, projectionFailures);
1600
+ if (!ownModule || rootModules.has(ownModule)) {
1601
+ for (const failure of projectionFailures) {
1602
+ diagnostics.push({
1603
+ severity: DiagnosticSeverity.Error,
1604
+ code: "SCHEMA_PROJECTION_FROM_UNRESOLVED",
1605
+ source: SOURCE,
1606
+ message: `${m.kind}/${resource.name}: ${describeProjectionFailure(failure)}`,
1607
+ data: { resource, filePath, path: pointerToPath(failure.pointer) },
1608
+ });
1609
+ }
1610
+ }
1537
1611
  // Phase 2+3: AJV on substituted data — CEL fields replaced with typed
1538
1612
  // placeholders. Through the REGISTRY, so a kind whose schema references
1539
1613
  // a shape declared elsewhere is checked on the instance that holds it.
1540
- const ajvIssues = defs.validateResourceConfig(substituteCelFields(m, schema, undefined, { external: (ref) => defs.schemaForId(ref) }), schema);
1614
+ const ajvIssues = defs.validateResourceConfig(substituteCelFields(m, projected, undefined, {
1615
+ external: (ref) => defs.schemaForId(ref),
1616
+ }), projected);
1541
1617
  // Phase 4: value slots that must satisfy a type declared elsewhere on
1542
1618
  // the resource (`x-telo-value-schema-from`) — e.g. every row of a
1543
1619
  // decision table against its declared `outputType`, so a mistyped branch
@@ -1563,10 +1639,10 @@ export class StaticAnalyzer {
1563
1639
  // `validate-resource-rules.ts`, the shape every neighbouring pass uses:
1564
1640
  // issues out, one emit here.
1565
1641
  const ruleDeclarer = definition.metadata?.module;
1566
- for (const report of reportResourceRules(m, definition, evaluateResourceRules(m, authorSchema), !ruleDeclarer || rootModules.has(ruleDeclarer))) {
1642
+ for (const report of reportResourceRules(m, definition, evaluateResourceRules(m, schema), !ruleDeclarer || rootModules.has(ruleDeclarer))) {
1567
1643
  diagnostics.push(resourceRuleDiagnostic(report));
1568
1644
  }
1569
- for (const rule of readResourceRules(authorSchema)) {
1645
+ for (const rule of readResourceRules(schema)) {
1570
1646
  const key = `${definition.metadata?.module}.${definition.metadata?.name}#${rule.code}`;
1571
1647
  const tracked = ruleExercise.get(key);
1572
1648
  if (!tracked)
@@ -1581,10 +1657,10 @@ export class StaticAnalyzer {
1581
1657
  // literal appears on the referring side, where the spelling would be the
1582
1658
  // consumer's import alias rather than anything the rule's author controls.
1583
1659
  // A consumer of the shared call graph, never a second traversal.
1584
- const referrerRules = readReferrerRules(authorSchema);
1660
+ const referrerRules = readReferrerRules(schema);
1585
1661
  if (referrerRules.length > 0) {
1586
1662
  const referrers = referrersOf(m, getCallGraph());
1587
- for (const report of reportReferrerRules(m, definition, evaluateReferrerRules(m, authorSchema, referrers, kindMatches), !ruleDeclarer || rootModules.has(ruleDeclarer))) {
1663
+ for (const report of reportReferrerRules(m, definition, evaluateReferrerRules(m, schema, referrers, kindMatches, referrerRuleContext), !ruleDeclarer || rootModules.has(ruleDeclarer))) {
1588
1664
  // A VIOLATION is the referrer's data, so it is reported only when that
1589
1665
  // manifest is the entry's own — the same direction a resource-rule
1590
1666
  // violation takes, one hop further out.
@@ -1593,6 +1669,15 @@ export class StaticAnalyzer {
1593
1669
  continue;
1594
1670
  }
1595
1671
  diagnostics.push(referrerRuleDiagnostic(report));
1672
+ // A rule that VIOLATED plainly ran, whatever its peer set held. The
1673
+ // unexercised report exists to catch silence — a `peers:` naming
1674
+ // nothing makes every `!peers.exists(…)` pass vacuously — so a finding
1675
+ // is exactly the evidence it asks for.
1676
+ if (report.code === "REFERRER_RULE_VIOLATED") {
1677
+ const violated = referrerRuleExercise.get(`${definition.metadata?.module}.${definition.metadata?.name}#${report.rule}`);
1678
+ if (violated)
1679
+ violated.exercised = true;
1680
+ }
1596
1681
  }
1597
1682
  for (const rule of referrerRules) {
1598
1683
  const key = `${definition.metadata?.module}.${definition.metadata?.name}#${rule.code}`;
@@ -1600,8 +1685,9 @@ export class StaticAnalyzer {
1600
1685
  if (!tracked)
1601
1686
  continue;
1602
1687
  tracked.seen = true;
1603
- if (referrerRuleExercised(rule, referrers, kindMatches))
1688
+ if (referrerRuleExercised(rule, referrers, kindMatches, referrerRuleContext)) {
1604
1689
  tracked.exercised = true;
1690
+ }
1605
1691
  }
1606
1692
  }
1607
1693
  // Validate inline resources nested inside this resource's body (e.g. a
@@ -1769,18 +1855,15 @@ export class StaticAnalyzer {
1769
1855
  // same schema an expression is typed against.
1770
1856
  let celStepContextSchema;
1771
1857
  let celErrorScopes = new Map();
1772
- // Region coverage for the "CEL in a non-eval field" check: the union of
1773
- // `x-telo-eval` paths (own + capability) and `x-telo-context` /
1774
- // `x-telo-step-context` / `x-telo-error-context` scopes. A `!cel` outside
1775
- // every region is read as a literal the runtime never evaluates it.
1776
- let celEvalPaths = [];
1858
+ // Where this kind says its values are evaluated — `x-telo-eval` paths (own +
1859
+ // capability) and the regions covering their contents. A `!cel` outside every
1860
+ // one of them is read as a literal, and one under a compile path resolves at
1861
+ // startup, where observed state cannot exist yet. Both questions are
1862
+ // `celEvalModeAt`, which the editor asks of the same sites.
1863
+ let celSites = NO_CEL_EVAL_SITES;
1777
1864
  // The bindings field this kind declares (if any), read by the CEL sites that
1778
1865
  // see the names it introduces.
1779
1866
  let celBindingSites;
1780
- // The compile half alone: a field that resolves at startup, where observed
1781
- // state cannot exist yet.
1782
- let celCompilePaths = [];
1783
- let celRegionScopes = [];
1784
1867
  let celRuleApplies = false;
1785
1868
  visitManifest(allManifests, defs, {
1786
1869
  onResourceEnter: (e) => {
@@ -1912,23 +1995,23 @@ export class StaticAnalyzer {
1912
1995
  celRuleApplies =
1913
1996
  !!e.definition?.schema && capability !== undefined && capability !== "Telo.Template";
1914
1997
  if (celRuleApplies) {
1915
- const ownSchema = e.definition.schema;
1916
- const own = buildEvalPaths(ownSchema);
1998
+ // The INHERITANCE-RESOLVED schema, not the kind's own: without
1999
+ // `base:`, an `extends` child is authored against merge(parent, own),
2000
+ // so a CEL-bearing field the parent declares is a legal field on the
2001
+ // child and the kernel expands it — it stamps the merged schema at
2002
+ // definition registration and builds eval paths from that. Reading
2003
+ // the own schema here reported `CEL_IN_NON_EVAL_FIELD` for an
2004
+ // expression the runtime evaluates correctly, which is the two halves
2005
+ // disagreeing about what the manifest means.
2006
+ const ownSchema = effectiveAuthorSchema(e.definition, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k));
1917
2007
  const capabilityDef = capability ? defs.resolve(capability) : undefined;
1918
- const parent = capabilityDef?.schema
1919
- ? buildEvalPaths(capabilityDef.schema)
1920
- : { compile: [], runtime: [] };
1921
- celEvalPaths = [...own.compile, ...own.runtime, ...parent.compile, ...parent.runtime];
1922
2008
  // A `Telo.Provider`'s fields are implicitly compile-eval — the
1923
2009
  // capability abstract carries the root annotation — so its reads are
1924
2010
  // covered here without the provider restating anything.
1925
- celCompilePaths = [...own.compile, ...parent.compile];
1926
- celRegionScopes = extractCelRegionScopes(ownSchema);
2011
+ celSites = mergeCelEvalSites(celEvalSites(ownSchema), celEvalSites(capabilityDef?.schema));
1927
2012
  }
1928
2013
  else {
1929
- celEvalPaths = [];
1930
- celCompilePaths = [];
1931
- celRegionScopes = [];
2014
+ celSites = NO_CEL_EVAL_SITES;
1932
2015
  }
1933
2016
  },
1934
2017
  onCel: (e) => {
@@ -1944,8 +2027,7 @@ export class StaticAnalyzer {
1944
2027
  if (celRuleApplies &&
1945
2028
  engineName === "cel" &&
1946
2029
  celScope.invocationContextSchema === undefined &&
1947
- !evalPathsCover(celEvalPaths, path) &&
1948
- !celRegionScopes.some((scope) => pathMatchesScope(path, scope)) &&
2030
+ celEvalModeAt(celSites, path) === null &&
1949
2031
  !pathCrossesNestedResource(m, path)) {
1950
2032
  diagnostics.push({
1951
2033
  severity: DiagnosticSeverity.Error,
@@ -1969,7 +2051,7 @@ export class StaticAnalyzer {
1969
2051
  // the two-level shape it publishes under, so a cross-module read
1970
2052
  // is checked exactly like a local one.
1971
2053
  const reported = observedState.get(read.alias ? `${read.alias}.${read.name}` : read.name);
1972
- if (celRuleApplies && evalPathsCover(celCompilePaths, path)) {
2054
+ if (celRuleApplies && celEvalModeAt(celSites, path) === "compile") {
1973
2055
  diagnostics.push({
1974
2056
  severity: DiagnosticSeverity.Error,
1975
2057
  code: "OBSERVED_STATE_IN_STARTUP_FIELD",
@@ -2013,7 +2095,19 @@ export class StaticAnalyzer {
2013
2095
  contextSchema: e.contextSchema,
2014
2096
  matchedScope,
2015
2097
  });
2016
- const result = engine.analyze(expr, { celEnv: typedEnv, contextSchema: effectiveContext });
2098
+ const result = engine.analyze(expr, {
2099
+ celEnv: typedEnv,
2100
+ contextSchema: effectiveContext,
2101
+ // `scopeFor` registers every kernel global and every name the site's
2102
+ // context declares, so a root this environment does not know is one
2103
+ // nothing puts in scope. Two places where the CEL belongs to another
2104
+ // scope, both already recognised by the non-eval-field check: a kind
2105
+ // document, whose CEL is written for whoever instantiates the kind,
2106
+ // and anything below a nested inline `{ kind }`, whose CEL the
2107
+ // nested kind evaluates — and which is analyzed again, in its own
2108
+ // scope, as the resource it was extracted into.
2109
+ rootsDeclared: !isKindDocument(m) && !pathCrossesNestedResource(m, path),
2110
+ });
2017
2111
  if (result.type !== undefined) {
2018
2112
  let byPath = celTypeByPath.get(m);
2019
2113
  if (!byPath)
@@ -2049,7 +2143,7 @@ export class StaticAnalyzer {
2049
2143
  // intent (a boot timestamp, a run id), so it warns rather than
2050
2144
  // blocking. The engine reports which calls re-evaluate; the eval mode
2051
2145
  // is manifest policy and stays here.
2052
- if (celRuleApplies && evalPathsCover(celCompilePaths, path)) {
2146
+ if (celRuleApplies && celEvalModeAt(celSites, path) === "compile") {
2053
2147
  const volatile = [
2054
2148
  ...new Set(result.calls.filter((c) => c.deterministic === false).map((c) => c.name)),
2055
2149
  ].sort();
@@ -1 +1 @@
1
- {"version":3,"file":"call-graph.d.ts","sourceRoot":"","sources":["../src/call-graph.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"call-graph.d.ts","sourceRoot":"","sources":["../src/call-graph.ts"],"names":[],"mappings":"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"}
@@ -119,7 +119,13 @@ function resolveUseAtSite(entry, root, concretePath, schemaDefault) {
119
119
  if (resolved)
120
120
  return { use: resolved };
121
121
  }
122
- const slot = { kinds: entry.refs, uses: entry.uses, useCases: entry.useCases, inline: false };
122
+ const slot = {
123
+ kinds: entry.refs,
124
+ uses: entry.uses,
125
+ useCases: entry.useCases,
126
+ inline: false,
127
+ valueBranches: [],
128
+ };
123
129
  const unresolvedReason = isTaggedSentinel(selector)
124
130
  ? "dynamic"
125
131
  : selector === undefined
@@ -13,6 +13,18 @@ export type { CelHandlers } from "@telorun/templating";
13
13
  * NOTE: The set of kernel globals registered here must match `KERNEL_GLOBAL_NAMES`
14
14
  * in kernel-globals.ts, which is used for chain-access validation. */
15
15
  export declare function buildTypedCelEnvironment(baseEnv: Environment, manifest: ResourceManifest, extraContextSchema?: Record<string, any> | null, rootModuleManifest?: ResourceManifest): Environment;
16
+ /**
17
+ * A kind document — whose CEL is written for whoever instantiates the kind, not
18
+ * evaluated in the declaring module's own scope.
19
+ *
20
+ * Its `examples:` show a consumer's route reading `request` and `result`, its
21
+ * `description`s show `${{ secrets.API_KEY }}`, and a rule condition reads the
22
+ * `self` / `referrer` its own evaluator binds. None of those names are in scope
23
+ * where they are WRITTEN, and all of them are correct where they are READ — so
24
+ * every check that asks "is this name in scope here" has to stand down on these
25
+ * documents, or it reports errors nobody can fix without deleting the example.
26
+ */
27
+ export declare function isKindDocument(manifest: ResourceManifest): boolean;
16
28
  /** CEL environment for the `variables:`/`secrets:` expressions on a `Telo.Import`.
17
29
  *
18
30
  * Import inputs are a config-only contract: their expressions are evaluated
@@ -1 +1 @@
1
- {"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAcrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;uEASuE;AACvE,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAI/C,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,WAAW,CAgGb;AAuBD;;;;;;8DAM8D;AAC9D,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,gBAAgB,GAAG,SAAS,GAC3C,WAAW,CAkCb"}
1
+ {"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAcrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;uEASuE;AACvE,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAI/C,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,WAAW,CA8Gb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAElE;AAuBD;;;;;;8DAM8D;AAC9D,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,gBAAgB,GAAG,SAAS,GAC3C,WAAW,CAkCb"}
@@ -35,24 +35,26 @@ rootModuleManifest) {
35
35
  for (const brand of Object.keys(VALUE_BRAND_BASE)) {
36
36
  env.registerType(brand, { fields: {} });
37
37
  }
38
- // Build typed ObjectSchema from manifest.variables if it looks like a schema map
39
- const vars = manifest.variables;
40
- if (vars !== null && typeof vars === "object" && !Array.isArray(vars)) {
41
- const entries = Object.entries(vars).filter(([, v]) => v !== null && typeof v === "object" && !Array.isArray(v));
42
- if (entries.length > 0) {
43
- const schema = {};
44
- for (const [k, v] of entries) {
45
- schema[k] = jsonSchemaToCelType(v);
46
- }
47
- env.registerVariable({ name: "variables", schema });
48
- }
49
- else {
50
- env.registerVariable("variables", "map");
51
- }
52
- }
53
- else {
54
- env.registerVariable("variables", "map");
55
- }
38
+ // `variables` / `secrets`: the DECLARING module's blocks, which is the
39
+ // contract the resource's CEL is evaluated against at runtime. Read the
40
+ // same three ways `ports` and `module` are, and for the same reason — a
41
+ // resource doc does not carry them, so typing from `manifest` alone left
42
+ // every ordinary resource with an open `variables` and no check at all,
43
+ // while `ports.<typo>` one line away was an error.
44
+ //
45
+ // Order matters: a module-identity doc analyzing itself carries its own
46
+ // block; a resource forwarded from an imported library carries its
47
+ // library's as `metadata.moduleGlobals`, which must win over the consuming
48
+ // application's; everything else is the entry module's own doc.
49
+ const moduleGlobals = manifest.metadata?.moduleGlobals;
50
+ // A KIND document is the exception, and it is not a detail: the CEL inside
51
+ // a `Telo.Definition`'s `schema:` — an `examples:` entry, a `description`
52
+ // showing `${{ secrets.API_KEY }}` — illustrates what a CONSUMER writes, in
53
+ // the consumer's scope. Closing those over the declaring module's blocks
54
+ // reported an error against a name the module never meant to declare, and
55
+ // one nobody could fix without deleting the example.
56
+ const root = (isKindDocument(manifest) ? undefined : rootModuleManifest);
57
+ registerConfigNamespace(env, manifest.variables ?? moduleGlobals?.variables ?? root?.variables, "variables");
56
58
  // `ports` namespace: each entry types as the brand its `protocol` selects
57
59
  // (tcp → TcpPort, udp → UdpPort), so `${{ ports.http }}` carries a nominal
58
60
  // type that consuming fields can check against.
@@ -73,7 +75,7 @@ rootModuleManifest) {
73
75
  else {
74
76
  env.registerVariable("ports", "map");
75
77
  }
76
- env.registerVariable("secrets", "map");
78
+ registerConfigNamespace(env, manifest.secrets ?? moduleGlobals?.secrets ?? root?.secrets, "secrets");
77
79
  env.registerVariable("resources", "map");
78
80
  // `module` — the declaring module's own `metadata`, so a manifest reads its
79
81
  // version instead of restating it. A resource forwarded from an imported
@@ -110,6 +112,20 @@ rootModuleManifest) {
110
112
  return baseEnv.clone();
111
113
  }
112
114
  }
115
+ /**
116
+ * A kind document — whose CEL is written for whoever instantiates the kind, not
117
+ * evaluated in the declaring module's own scope.
118
+ *
119
+ * Its `examples:` show a consumer's route reading `request` and `result`, its
120
+ * `description`s show `${{ secrets.API_KEY }}`, and a rule condition reads the
121
+ * `self` / `referrer` its own evaluator binds. None of those names are in scope
122
+ * where they are WRITTEN, and all of them are correct where they are READ — so
123
+ * every check that asks "is this name in scope here" has to stand down on these
124
+ * documents, or it reports errors nobody can fix without deleting the example.
125
+ */
126
+ export function isKindDocument(manifest) {
127
+ return manifest.kind === "Telo.Definition" || manifest.kind === "Telo.Abstract";
128
+ }
113
129
  /** Register a `variables`/`secrets` namespace typed from a module doc's schema map
114
130
  * (`{ name: <schema>, … }`), falling back to dyn `map` when absent or untyped. */
115
131
  function registerConfigNamespace(env, block, name) {
@@ -1 +1 @@
1
- {"version":3,"file":"cel-scope.d.ts","sourceRoot":"","sources":["../src/cel-scope.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cel-scope.d.ts","sourceRoot":"","sources":["../src/cel-scope.ts"],"names":[],"mappings":"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"}
package/dist/cel-scope.js CHANGED
@@ -293,10 +293,16 @@ export class CelScopeResolver {
293
293
  enterResource(m, definition) {
294
294
  const { allManifests, defs, aliases, scopes } = this.inputs;
295
295
  this.invocationContext = m.metadata?.xTeloInvocationContext;
296
- this.stepContext = definition?.schema
297
- ? buildStepContextSchema(m, definition.schema, allManifests, defs, aliases, scopes)
296
+ // The INHERITANCE-RESOLVED schema at both sites, for the reason the CEL
297
+ // context regions use it: an `extends` child is authored against
298
+ // merge(parent, own), so a step body or a `catch:` region the parent
299
+ // declares is in force on the child. All three consumers share one memo so
300
+ // they cannot come to disagree about which schema a kind means.
301
+ const authorSchema = defs.effectiveSchemaOf(definition);
302
+ this.stepContext = authorSchema
303
+ ? buildStepContextSchema(m, authorSchema, allManifests, defs, aliases, scopes)
298
304
  : undefined;
299
- this.errorScopes = collectErrorContextScopes(definition?.schema);
305
+ this.errorScopes = collectErrorContextScopes(authorSchema);
300
306
  }
301
307
  /**
302
308
  * What the expression at `site` is typed against.
@@ -337,7 +343,21 @@ export class CelScopeResolver {
337
343
  const base = matched ?? { type: "object", properties: {}, additionalProperties: true };
338
344
  matched = {
339
345
  ...base,
340
- properties: { ...(base.properties ?? {}), steps: this.stepContext },
346
+ properties: {
347
+ // `inputs` is in scope beside `steps` wherever a step body runs — the
348
+ // step engine evaluates a step against the enclosing kind's own
349
+ // arguments, which is what `steps[0].inputs.x` reads. Modelled here
350
+ // because it was modelled NOWHERE: the step context is open, so a
351
+ // chain through it was never checked and the name's absence went
352
+ // unnoticed until something asked whether the root existed.
353
+ //
354
+ // OPEN, not typed from the kind's `inputType`: closing it would newly
355
+ // reject every read of an argument the contract does not spell out,
356
+ // which is a separate decision from knowing the name is legal.
357
+ inputs: { type: "object", additionalProperties: true },
358
+ ...(base.properties ?? {}),
359
+ steps: this.stepContext,
360
+ },
341
361
  };
342
362
  }
343
363
  // `error` is only in scope inside an error-bearing branch (e.g. a