@intentius/chant 0.49.0 → 0.50.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 (247) hide show
  1. package/dist/audit/catalog.d.ts +13 -3
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +9 -0
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +6 -0
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/fetch.d.ts.map +1 -1
  8. package/dist/audit/report-html.d.ts.map +1 -1
  9. package/dist/audit/report-model.d.ts +6 -0
  10. package/dist/audit/report-model.d.ts.map +1 -1
  11. package/dist/audit/report.d.ts.map +1 -1
  12. package/dist/audit/rules-doc.d.ts.map +1 -1
  13. package/dist/audit/secrets.d.ts +95 -0
  14. package/dist/audit/secrets.d.ts.map +1 -0
  15. package/dist/audit/wrangler.d.ts +33 -0
  16. package/dist/audit/wrangler.d.ts.map +1 -0
  17. package/dist/build.d.ts.map +1 -1
  18. package/dist/cli/commands/audit.d.ts +7 -0
  19. package/dist/cli/commands/audit.d.ts.map +1 -1
  20. package/dist/cli/commands/build.d.ts +23 -0
  21. package/dist/cli/commands/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/components.d.ts +31 -0
  24. package/dist/cli/handlers/components.d.ts.map +1 -1
  25. package/dist/cli/handlers/lifecycle.d.ts +11 -0
  26. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  27. package/dist/cli/handlers/operator.d.ts +32 -0
  28. package/dist/cli/handlers/operator.d.ts.map +1 -0
  29. package/dist/cli/handlers/scenario.d.ts +39 -0
  30. package/dist/cli/handlers/scenario.d.ts.map +1 -0
  31. package/dist/cli/main.d.ts.map +1 -1
  32. package/dist/cli/mcp/server.d.ts +35 -2
  33. package/dist/cli/mcp/server.d.ts.map +1 -1
  34. package/dist/cli/mcp/types.d.ts +29 -1
  35. package/dist/cli/mcp/types.d.ts.map +1 -1
  36. package/dist/cli/registry.d.ts +14 -2
  37. package/dist/cli/registry.d.ts.map +1 -1
  38. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  39. package/dist/components/capability.d.ts +17 -2
  40. package/dist/components/capability.d.ts.map +1 -1
  41. package/dist/components/cli-support.d.ts +7 -0
  42. package/dist/components/cli-support.d.ts.map +1 -1
  43. package/dist/components/component.d.ts +15 -0
  44. package/dist/components/component.d.ts.map +1 -1
  45. package/dist/components/driver.d.ts.map +1 -1
  46. package/dist/components/verbs/index.d.ts +6 -1
  47. package/dist/components/verbs/index.d.ts.map +1 -1
  48. package/dist/components/verbs/run-agent.d.ts +499 -0
  49. package/dist/components/verbs/run-agent.d.ts.map +1 -0
  50. package/dist/components/verbs/sign.d.ts +30 -0
  51. package/dist/components/verbs/sign.d.ts.map +1 -1
  52. package/dist/composite.d.ts +6 -1
  53. package/dist/composite.d.ts.map +1 -1
  54. package/dist/discovery/collect.d.ts.map +1 -1
  55. package/dist/discovery/fold-import.d.ts +15 -1
  56. package/dist/discovery/fold-import.d.ts.map +1 -1
  57. package/dist/discovery/fold-rank.d.ts +66 -0
  58. package/dist/discovery/fold-rank.d.ts.map +1 -0
  59. package/dist/discovery/index.d.ts +15 -0
  60. package/dist/discovery/index.d.ts.map +1 -1
  61. package/dist/discovery/param-deps.d.ts +17 -0
  62. package/dist/discovery/param-deps.d.ts.map +1 -0
  63. package/dist/fold/fold.d.ts +55 -2
  64. package/dist/fold/fold.d.ts.map +1 -1
  65. package/dist/fold/subset.d.ts +21 -14
  66. package/dist/fold/subset.d.ts.map +1 -1
  67. package/dist/lexicon-schema.d.ts +2 -0
  68. package/dist/lexicon-schema.d.ts.map +1 -1
  69. package/dist/lexicon.d.ts +93 -0
  70. package/dist/lexicon.d.ts.map +1 -1
  71. package/dist/lifecycle/converge-ledger.d.ts +90 -0
  72. package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
  73. package/dist/lifecycle/deep-diff.d.ts +18 -0
  74. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  75. package/dist/lifecycle/deep-observe.d.ts +9 -1
  76. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  77. package/dist/lifecycle/gate-ledger.d.ts +33 -0
  78. package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
  79. package/dist/lifecycle/git.d.ts +145 -21
  80. package/dist/lifecycle/git.d.ts.map +1 -1
  81. package/dist/lifecycle/index.d.ts +4 -0
  82. package/dist/lifecycle/index.d.ts.map +1 -1
  83. package/dist/lifecycle/lease.d.ts +113 -0
  84. package/dist/lifecycle/lease.d.ts.map +1 -0
  85. package/dist/lifecycle/scenario-eval.d.ts +42 -0
  86. package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
  87. package/dist/lifecycle/scenario.d.ts +163 -0
  88. package/dist/lifecycle/scenario.d.ts.map +1 -0
  89. package/dist/lifecycle/symptoms.d.ts +63 -0
  90. package/dist/lifecycle/symptoms.d.ts.map +1 -0
  91. package/dist/lint/output-docs.d.ts +94 -0
  92. package/dist/lint/output-docs.d.ts.map +1 -0
  93. package/dist/lint/post-synth.d.ts +29 -0
  94. package/dist/lint/post-synth.d.ts.map +1 -1
  95. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
  96. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
  97. package/dist/lsp/lexicon-providers.d.ts +7 -0
  98. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  99. package/dist/op/activity-contract.d.ts +139 -0
  100. package/dist/op/activity-contract.d.ts.map +1 -0
  101. package/dist/op/builders.d.ts +42 -2
  102. package/dist/op/builders.d.ts.map +1 -1
  103. package/dist/op/converge-rule.d.ts +161 -0
  104. package/dist/op/converge-rule.d.ts.map +1 -0
  105. package/dist/op/generate-pipeline.d.ts +39 -0
  106. package/dist/op/generate-pipeline.d.ts.map +1 -0
  107. package/dist/op/index.d.ts +14 -0
  108. package/dist/op/index.d.ts.map +1 -1
  109. package/dist/op/local-executor.d.ts.map +1 -1
  110. package/dist/op/op-verb-class.d.ts +42 -0
  111. package/dist/op/op-verb-class.d.ts.map +1 -0
  112. package/dist/op/operator.d.ts +128 -0
  113. package/dist/op/operator.d.ts.map +1 -0
  114. package/dist/op/step-output-ref.d.ts +187 -0
  115. package/dist/op/step-output-ref.d.ts.map +1 -0
  116. package/dist/op/types.d.ts +18 -1
  117. package/dist/op/types.d.ts.map +1 -1
  118. package/dist/provenance.d.ts +73 -3
  119. package/dist/provenance.d.ts.map +1 -1
  120. package/dist/runtime-adapter.d.ts +7 -1
  121. package/dist/runtime-adapter.d.ts.map +1 -1
  122. package/dist/serializer.d.ts +18 -0
  123. package/dist/serializer.d.ts.map +1 -1
  124. package/dist/toml.d.ts +40 -5
  125. package/dist/toml.d.ts.map +1 -1
  126. package/package.json +1 -1
  127. package/src/audit/catalog.test.ts +1 -1
  128. package/src/audit/catalog.ts +75 -3
  129. package/src/audit/core.ts +9 -0
  130. package/src/audit/discover.ts +29 -2
  131. package/src/audit/fetch.test.ts +216 -3
  132. package/src/audit/fetch.ts +270 -59
  133. package/src/audit/report-html.ts +5 -2
  134. package/src/audit/report-model.ts +9 -0
  135. package/src/audit/report.test.ts +22 -0
  136. package/src/audit/report.ts +3 -2
  137. package/src/audit/rules-doc.ts +2 -0
  138. package/src/audit/secrets.test.ts +303 -0
  139. package/src/audit/secrets.ts +406 -0
  140. package/src/audit/wrangler.test.ts +230 -0
  141. package/src/audit/wrangler.ts +290 -0
  142. package/src/build.ts +8 -3
  143. package/src/cli/command-group.ts +1 -1
  144. package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
  145. package/src/cli/commands/audit.test.ts +215 -1
  146. package/src/cli/commands/audit.ts +86 -17
  147. package/src/cli/commands/build.test.ts +167 -2
  148. package/src/cli/commands/build.ts +114 -23
  149. package/src/cli/handlers/build.ts +2 -0
  150. package/src/cli/handlers/components.test.ts +199 -1
  151. package/src/cli/handlers/components.ts +160 -3
  152. package/src/cli/handlers/graph.test.ts +20 -0
  153. package/src/cli/handlers/graph.ts +10 -1
  154. package/src/cli/handlers/lifecycle.ts +12 -4
  155. package/src/cli/handlers/operator.test.ts +255 -0
  156. package/src/cli/handlers/operator.ts +240 -0
  157. package/src/cli/handlers/scenario.test.ts +456 -0
  158. package/src/cli/handlers/scenario.ts +330 -0
  159. package/src/cli/main.test.ts +23 -0
  160. package/src/cli/main.ts +72 -1
  161. package/src/cli/mcp/server.test.ts +265 -2
  162. package/src/cli/mcp/server.ts +84 -7
  163. package/src/cli/mcp/types.ts +27 -1
  164. package/src/cli/registry.ts +14 -2
  165. package/src/codegen/docs-rule-scanning.test.ts +42 -0
  166. package/src/codegen/docs-rule-scanning.ts +25 -2
  167. package/src/components/README.md +7 -0
  168. package/src/components/capability.ts +17 -2
  169. package/src/components/cli-support.test.ts +17 -0
  170. package/src/components/cli-support.ts +13 -1
  171. package/src/components/component-schema.test.ts +32 -0
  172. package/src/components/component.schema.json +6 -0
  173. package/src/components/component.test.ts +21 -0
  174. package/src/components/component.ts +15 -0
  175. package/src/components/driver.ts +12 -4
  176. package/src/components/verbs/index.ts +6 -1
  177. package/src/components/verbs/run-agent.test.ts +683 -0
  178. package/src/components/verbs/run-agent.ts +786 -0
  179. package/src/components/verbs/sign.test.ts +19 -0
  180. package/src/components/verbs/sign.ts +34 -2
  181. package/src/composite.ts +31 -2
  182. package/src/discovery/collect.ts +11 -2
  183. package/src/discovery/fold-import.test.ts +54 -0
  184. package/src/discovery/fold-import.ts +178 -38
  185. package/src/discovery/fold-rank.test.ts +197 -0
  186. package/src/discovery/fold-rank.ts +346 -0
  187. package/src/discovery/index.ts +16 -1
  188. package/src/discovery/param-deps.test.ts +118 -0
  189. package/src/discovery/param-deps.ts +170 -0
  190. package/src/fold/fold.test.ts +6 -2
  191. package/src/fold/fold.ts +184 -3
  192. package/src/fold/subset.test.ts +82 -19
  193. package/src/fold/subset.ts +79 -41
  194. package/src/lexicon-schema.ts +3 -0
  195. package/src/lexicon.ts +103 -2
  196. package/src/lifecycle/converge-ledger.test.ts +199 -0
  197. package/src/lifecycle/converge-ledger.ts +179 -0
  198. package/src/lifecycle/deep-diff.test.ts +79 -1
  199. package/src/lifecycle/deep-diff.ts +23 -0
  200. package/src/lifecycle/deep-observe.ts +13 -2
  201. package/src/lifecycle/gate-ledger.test.ts +103 -0
  202. package/src/lifecycle/gate-ledger.ts +140 -0
  203. package/src/lifecycle/git.test.ts +430 -0
  204. package/src/lifecycle/git.ts +446 -84
  205. package/src/lifecycle/index.ts +4 -0
  206. package/src/lifecycle/lease.test.ts +343 -0
  207. package/src/lifecycle/lease.ts +270 -0
  208. package/src/lifecycle/scenario-eval.test.ts +199 -0
  209. package/src/lifecycle/scenario-eval.ts +158 -0
  210. package/src/lifecycle/scenario.test.ts +195 -0
  211. package/src/lifecycle/scenario.ts +321 -0
  212. package/src/lifecycle/symptoms.test.ts +116 -0
  213. package/src/lifecycle/symptoms.ts +126 -0
  214. package/src/lint/output-docs.test.ts +220 -0
  215. package/src/lint/output-docs.ts +204 -0
  216. package/src/lint/post-synth.test.ts +97 -0
  217. package/src/lint/post-synth.ts +45 -0
  218. package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
  219. package/src/lint/rules/comp/comp.test.ts +49 -1
  220. package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
  221. package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
  222. package/src/lsp/lexicon-providers.test.ts +44 -0
  223. package/src/lsp/lexicon-providers.ts +11 -1
  224. package/src/op/activity-contract.test.ts +180 -0
  225. package/src/op/activity-contract.ts +278 -0
  226. package/src/op/builders-exports.test.ts +17 -1
  227. package/src/op/builders.ts +59 -5
  228. package/src/op/converge-rule.test.ts +179 -0
  229. package/src/op/converge-rule.ts +311 -0
  230. package/src/op/generate-pipeline.test.ts +53 -0
  231. package/src/op/generate-pipeline.ts +99 -0
  232. package/src/op/index.ts +30 -0
  233. package/src/op/local-executor.test.ts +92 -0
  234. package/src/op/local-executor.ts +45 -9
  235. package/src/op/op-verb-class.test.ts +126 -0
  236. package/src/op/op-verb-class.ts +115 -0
  237. package/src/op/operator.test.ts +346 -0
  238. package/src/op/operator.ts +213 -0
  239. package/src/op/step-output-ref.test.ts +334 -0
  240. package/src/op/step-output-ref.ts +453 -0
  241. package/src/op/types.ts +18 -1
  242. package/src/provenance.test.ts +151 -4
  243. package/src/provenance.ts +118 -4
  244. package/src/runtime-adapter.ts +31 -10
  245. package/src/serializer.ts +18 -0
  246. package/src/toml.test.ts +157 -384
  247. package/src/toml.ts +371 -5
@@ -74,10 +74,12 @@ const SUPPORTED_CASES: SubsetCase[] = [
74
74
 
75
75
  const UNSUPPORTED_CASES: SubsetCase[] = [
76
76
  { name: "function call as a value", expr: `getName()` },
77
- { name: "method call as a value", expr: `config.getName()` },
78
- // chant #1082 — a registered helper NAME reached through a namespace is
79
- // still a method call, and still rejected by both sides.
80
- { name: "registered helper name reached as a method", expr: `helpers.phase("Apply", [])` },
77
+ // chant #1966 moved "method call as a value" and "registered helper name
78
+ // reached as a method" out of this strict-parity list and into "documented
79
+ // divergences" below — a method call now folds when its receiver resolves,
80
+ // which is exactly the identifier-resolution asymmetry every other
81
+ // divergence there already documents.
82
+ //
81
83
  // ...and a registered helper with an unfoldable argument is rejected on the
82
84
  // argument, by both sides, at the argument's own position.
83
85
  { name: "registered helper with an unfoldable argument", expr: `phase("Apply", [getName()])` },
@@ -258,28 +260,85 @@ describe("documented divergences — NOT unified by design (see subset.ts module
258
260
  expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
259
261
  });
260
262
 
261
- test("composite step access: fold still rejects a call as a value; EVL001 accepts .step access (chant #1544, opt-in only)", () => {
263
+ test("method call on an unresolved receiver: fold rejects; EVL001 accepts the shape (chant #1966, resolution-dependent)", () => {
264
+ // `config.getName()` — chant #1966 widened `findSubsetViolation` to
265
+ // accept a method call unconditionally (mirroring `.step` and a nested
266
+ // `new` before it), because whether the receiver resolves to a value
267
+ // with that method is resolution-dependent (module doc, point 1) and out
268
+ // of reach for a syntax-only classifier. fold() still rejects it here on
269
+ // the receiver: `config` isn't a `const` this file has.
270
+ const source = `const bad = new Thing({ x: config.getName() });`;
271
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
272
+ const consts = collectConsts(sourceFile);
273
+ const badInit = consts.get("bad") as ts.NewExpression;
274
+
275
+ expect(() => foldResource(badInit, consts, [])).toThrow(FoldError);
276
+
277
+ const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
278
+ expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
279
+ });
280
+
281
+ test("registered helper name reached as a method: not the registered call, on either side — rejected for a different reason since #1966", () => {
282
+ // chant #1082 — a registered helper NAME reached through a namespace is
283
+ // still a method call, never the bare registered call: `fold()`'s
284
+ // `{__helper}` shape only ever matches a BARE identifier callee, so
285
+ // `helpers.phase(...)` never gets that treatment, before or after #1966.
286
+ // What changed is WHY it's rejected — `helpers` is an unresolved
287
+ // identifier now reached through the general method-call case, not "no
288
+ // case for a property-access callee" — same divergence direction as the
289
+ // test above.
290
+ const source = `const bad = new Thing({ x: helpers.phase("Apply", []) });`;
291
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
292
+ const consts = collectConsts(sourceFile);
293
+ const badInit = consts.get("bad") as ts.NewExpression;
294
+
295
+ let error: unknown;
296
+ try {
297
+ foldResource(badInit, consts, []);
298
+ } catch (e) {
299
+ error = e;
300
+ }
301
+ expect(error).toBeInstanceOf(FoldError);
302
+ expect((error as FoldError).message).toContain("unresolved identifier: helpers");
303
+
304
+ const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
305
+ expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
306
+ });
307
+
308
+ test("composite step access: NO LONGER a divergence (chant #1174) — both fold and EVL001 accept it", () => {
262
309
  // `Checkout({...}).step` — the single-action Composite() wrapper idiom
263
310
  // every lexicon's own docs/examples embed inline inside a Job's
264
- // `steps:` array. fold() has no way to invoke an arbitrary composite
265
- // factory, so it still rejects this shape and falls the file back to
266
- // the run path (documented, correct behavior — never an error). EVL001
267
- // now opts INTO treating it as shape-valid via `allowCompositeStepAccess`
268
- // — a caller of `findSubsetViolation` that does not pass that flag
269
- // (fold(), or this very call below) is completely unaffected.
311
+ // `steps:` array. This was a documented, EVL-only-permissive divergence
312
+ // from chant #1544 until #1174: `fold()` had no way to invoke an
313
+ // arbitrary composite factory, so it fell the file back to run
314
+ // (correct, not an error) while EVL001 already opted into treating the
315
+ // shape as valid via a since-removed `allowCompositeStepAccess` flag.
316
+ //
317
+ // fold() now folds this to a `{__compositeStep, args}` envelope
318
+ // (../fold/fold.ts) that ../discovery/fold-import.ts's bridge resolves
319
+ // through the folding file's own imports — interpreted when the callee
320
+ // is a project-file registered Composite (chant #1023), invoked for
321
+ // real otherwise (every lexicon-package composite, which is what
322
+ // `Checkout` is) — and reads `.step` off the REAL result. Kept as a
323
+ // test rather than deleted: the assertion that the divergence stays
324
+ // closed.
270
325
  const source = `const bad = new Thing({ x: Checkout({}).step });`;
271
326
  const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
272
327
  const consts = collectConsts(sourceFile);
273
328
  const badInit = consts.get("bad") as ts.NewExpression;
274
329
 
275
- expect(() => foldResource(badInit, consts, [])).toThrow(FoldError);
330
+ expect(foldResource(badInit, consts, [])).toEqual({
331
+ __resource: "Thing",
332
+ props: { x: { __compositeStep: "Checkout", args: [{}] } },
333
+ });
276
334
 
277
- // findSubsetViolation with no third argument (fold()'s own answer, and
278
- // every pre-#1544 caller) is unchanged — still a violation.
279
- const bareCallExpr = (badInit.arguments![0] as ts.ObjectLiteralExpression).properties[0];
280
- expect(findSubsetViolation(bareCallExpr)).toBeDefined();
335
+ // findSubsetViolation (fold()'s own shared predicate, no opt-in needed
336
+ // anymore) agrees the shape is admissible.
337
+ const stepAccessExpr = (
338
+ (badInit.arguments![0] as ts.ObjectLiteralExpression).properties[0] as ts.PropertyAssignment
339
+ ).initializer;
340
+ expect(findSubsetViolation(stepAccessExpr)).toBeUndefined();
281
341
 
282
- // EVL001 itself opts in and stops flagging it.
283
342
  const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
284
343
  expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
285
344
  });
@@ -378,10 +437,14 @@ describe("findSubsetViolation — optional intrinsic registry (#1044)", () => {
378
437
  expect(v?.message).toContain("function call as a value is not foldable: Ref(...)");
379
438
  });
380
439
 
381
- test("the registry doesn't widen anything else: a name in it without the opt-in, a method call, and .map all stay violations", () => {
440
+ test("the registry doesn't widen anything else: a name in it without the opt-in, a closure-taking method, and a plain call stay violations", () => {
382
441
  const notOptedIn: IntrinsicDef[] = [{ name: "Reference", isTag: false }];
383
442
  expect(findSubsetViolation(propValue(`Reference("db")`), notOptedIn)).toBeDefined();
384
- expect(findSubsetViolation(propValue(`aws.Ref("db")`), REF)).toBeDefined();
443
+ // chant #1966 — `aws.Ref("db")` is a method call (`Ref` reached through a
444
+ // namespace, not a bare identifier), so it's no longer here: shape-wise
445
+ // it's now the same resolution-dependent divergence
446
+ // "method call on an unresolved receiver" documents above, unrelated to
447
+ // whether `Ref` itself is opted into `foldsAsCall`.
385
448
  expect(findSubsetViolation(propValue(`cidrs.map((c) => c)`, `const cidrs = [];`), REF)).toBeDefined();
386
449
  expect(findSubsetViolation(propValue(`makeName("a")`), REF)).toBeDefined();
387
450
  });
@@ -1,6 +1,6 @@
1
1
  import * as ts from "typescript";
2
2
  import { isFoldableHelperName } from "./foldable-helpers";
3
- import { intrinsicCallFolds, type IntrinsicDef } from "../lexicon";
3
+ import { intrinsicCallFolds, intrinsicCallFoldsEagerly, type IntrinsicDef } from "../lexicon";
4
4
 
5
5
  /**
6
6
  * subset — the single canonical definition of chant's statically-foldable
@@ -231,19 +231,18 @@ export function unsupportedExpressionMessage(node: ts.Node): string {
231
231
  export function checkObjectMember(
232
232
  prop: ts.ObjectLiteralElementLike,
233
233
  intrinsics?: readonly IntrinsicDef[],
234
- allowCompositeStepAccess?: boolean,
235
234
  ): SubsetViolation | undefined {
236
235
  if (ts.isPropertyAssignment(prop)) {
237
236
  if (!isLiteralPropertyName(prop.name)) {
238
237
  return violation(prop.name, computedPropertyNameMessage(prop.name));
239
238
  }
240
- return findSubsetViolation(prop.initializer, intrinsics, allowCompositeStepAccess);
239
+ return findSubsetViolation(prop.initializer, intrinsics);
241
240
  }
242
241
  if (ts.isShorthandPropertyAssignment(prop)) {
243
242
  return undefined;
244
243
  }
245
244
  if (ts.isSpreadAssignment(prop)) {
246
- return findSubsetViolation(prop.expression, intrinsics, allowCompositeStepAccess);
245
+ return findSubsetViolation(prop.expression, intrinsics);
247
246
  }
248
247
  return violation(prop, UNSUPPORTED_OBJECT_MEMBER_MESSAGE);
249
248
  }
@@ -252,10 +251,9 @@ export function checkObjectMember(
252
251
  function checkArrayElement(
253
252
  el: ts.Expression,
254
253
  intrinsics?: readonly IntrinsicDef[],
255
- allowCompositeStepAccess?: boolean,
256
254
  ): SubsetViolation | undefined {
257
- if (ts.isSpreadElement(el)) return findSubsetViolation(el.expression, intrinsics, allowCompositeStepAccess);
258
- return findSubsetViolation(el, intrinsics, allowCompositeStepAccess);
255
+ if (ts.isSpreadElement(el)) return findSubsetViolation(el.expression, intrinsics);
256
+ return findSubsetViolation(el, intrinsics);
259
257
  }
260
258
 
261
259
  /**
@@ -268,23 +266,29 @@ function checkArrayElement(
268
266
  * `fold()`-evaluation-order) unsupported node, or `undefined` when `node`'s
269
267
  * whole shape is foldable.
270
268
  *
271
- * `allowCompositeStepAccess` (chant #1544) is an EVL-only, MORE-permissive
272
- * divergence in the same direction as points 1/2/2b/2c in the module doc
273
- * above: a call immediately narrowed to a single member access — the
274
- * `Checkout({...}).step` shape every lexicon's single-action `Composite()`
275
- * wrappers document as their embedded-inline idiom (see composites.mdx —
276
- * "normally embedded inline as `checkout.step` inside a `Job`'s `steps`
277
- * array") — is treated as shape-valid. `fold()` itself never passes this
278
- * (it has no such parameter to pass; only EVL001 opts in), so `fold()`'s own
279
- * behavior is unchanged: it still falls the file back to the run path for
280
- * this shape, exactly as composites.mdx describes as the expected, correct
281
- * outcome — not an error. What changes is that EVL001 stops treating that
282
- * ordinary, documented fallback as a lint error.
269
+ * The `<call>(...).step` composite-consumer idiom (`Checkout({...}).step`,
270
+ * every lexicon's single-action `Composite()` wrapper embedded inline in a
271
+ * `Job`'s `steps` array — see composites.mdx) used to be an EVL-only,
272
+ * MORE-permissive divergence here (chant #1544's `allowCompositeStepAccess`
273
+ * opt-in parameter, since removed): `fold()` had no way to invoke an
274
+ * arbitrary composite factory, so it fell the file back to run for this
275
+ * shape (documented, correct behavior, never an error) while this shared
276
+ * predicate had to be told to stop treating that fallback as a lint error.
277
+ * chant #1174 closed the divergence — `fold()` now folds exactly this shape
278
+ * (narrowed to the literal member name `"step"`, the one idiom actually in
279
+ * use) — so this classifier accepts it unconditionally, the same way it
280
+ * already accepts a nested `new Type(...)` unconditionally since #1169.
281
+ *
282
+ * chant #1966 adds a CallExpression whose callee is a property access —
283
+ * `github.actor.toString()`, `matrix("os").toString()`, `[...].join(",")` —
284
+ * to the same unconditional-acceptance set: whether the receiver actually
285
+ * folds to a value with a callable method of that name is resolution-
286
+ * dependent (module doc, point 1), so this classifier stays permissive and
287
+ * only recurses into shape.
283
288
  */
284
289
  export function findSubsetViolation(
285
290
  node: ts.Node,
286
291
  intrinsics?: readonly IntrinsicDef[],
287
- allowCompositeStepAccess?: boolean,
288
292
  ): SubsetViolation | undefined {
289
293
  if (
290
294
  ts.isParenthesizedExpression(node) ||
@@ -292,7 +296,7 @@ export function findSubsetViolation(
292
296
  ts.isSatisfiesExpression(node) ||
293
297
  ts.isNonNullExpression(node)
294
298
  ) {
295
- return findSubsetViolation(node.expression, intrinsics, allowCompositeStepAccess);
299
+ return findSubsetViolation(node.expression, intrinsics);
296
300
  }
297
301
 
298
302
  if (
@@ -327,7 +331,7 @@ export function findSubsetViolation(
327
331
 
328
332
  if (ts.isTemplateExpression(node)) {
329
333
  for (const span of node.templateSpans) {
330
- const v = findSubsetViolation(span.expression, intrinsics, allowCompositeStepAccess);
334
+ const v = findSubsetViolation(span.expression, intrinsics);
331
335
  if (v) return v;
332
336
  }
333
337
  return undefined;
@@ -335,7 +339,7 @@ export function findSubsetViolation(
335
339
 
336
340
  if (ts.isObjectLiteralExpression(node)) {
337
341
  for (const prop of node.properties) {
338
- const v = checkObjectMember(prop, intrinsics, allowCompositeStepAccess);
342
+ const v = checkObjectMember(prop, intrinsics);
339
343
  if (v) return v;
340
344
  }
341
345
  return undefined;
@@ -343,35 +347,35 @@ export function findSubsetViolation(
343
347
 
344
348
  if (ts.isArrayLiteralExpression(node)) {
345
349
  for (const el of node.elements) {
346
- const v = checkArrayElement(el, intrinsics, allowCompositeStepAccess);
350
+ const v = checkArrayElement(el, intrinsics);
347
351
  if (v) return v;
348
352
  }
349
353
  return undefined;
350
354
  }
351
355
 
352
356
  if (ts.isPropertyAccessExpression(node)) {
353
- // chant #1544 — see this function's doc comment. `<call>(...).step`
354
- // (any callee, any argument shape) is the composite-consumer idiom, EVL
355
- // opt-in only: `fold()` never sets `allowCompositeStepAccess`, so it is
356
- // unaffected and still falls back to the run path for this shape.
357
- if (allowCompositeStepAccess && node.name.text === "step" && ts.isCallExpression(node.expression)) {
357
+ // chant #1174 — see this function's doc comment. `<call>(...).step`
358
+ // (any callee, any argument shape) is the composite-consumer idiom, and
359
+ // `fold()` now folds it too (narrowed to the literal member `"step"` —
360
+ // see fold.ts's PropertyAccessExpression branch).
361
+ if (node.name.text === "step" && ts.isCallExpression(node.expression)) {
358
362
  return undefined;
359
363
  }
360
- return findSubsetViolation(node.expression, intrinsics, allowCompositeStepAccess);
364
+ return findSubsetViolation(node.expression, intrinsics);
361
365
  }
362
366
 
363
367
  if (ts.isElementAccessExpression(node)) {
364
368
  if (!isLiteralElementKey(node.argumentExpression)) {
365
369
  return violation(node.argumentExpression, dynamicElementAccessMessage(node.argumentExpression), "EVL003");
366
370
  }
367
- return findSubsetViolation(node.expression, intrinsics, allowCompositeStepAccess);
371
+ return findSubsetViolation(node.expression, intrinsics);
368
372
  }
369
373
 
370
374
  if (ts.isPrefixUnaryExpression(node)) {
371
375
  if (!SUPPORTED_UNARY_OPERATORS.has(node.operator)) {
372
376
  return violation(node, UNSUPPORTED_UNARY_MESSAGE);
373
377
  }
374
- return findSubsetViolation(node.operand, intrinsics, allowCompositeStepAccess);
378
+ return findSubsetViolation(node.operand, intrinsics);
375
379
  }
376
380
 
377
381
  if (ts.isBinaryExpression(node)) {
@@ -382,17 +386,17 @@ export function findSubsetViolation(
382
386
  // Flow-insensitive — see module doc: fold() short-circuits &&/||/?? and
383
387
  // only evaluates the taken side; EVL requires both sides shape-valid.
384
388
  return (
385
- findSubsetViolation(node.left, intrinsics, allowCompositeStepAccess) ??
386
- findSubsetViolation(node.right, intrinsics, allowCompositeStepAccess)
389
+ findSubsetViolation(node.left, intrinsics) ??
390
+ findSubsetViolation(node.right, intrinsics)
387
391
  );
388
392
  }
389
393
 
390
394
  if (ts.isConditionalExpression(node)) {
391
395
  // Flow-insensitive — see module doc: fold() only folds the taken branch.
392
396
  return (
393
- findSubsetViolation(node.condition, intrinsics, allowCompositeStepAccess) ??
394
- findSubsetViolation(node.whenTrue, intrinsics, allowCompositeStepAccess) ??
395
- findSubsetViolation(node.whenFalse, intrinsics, allowCompositeStepAccess)
397
+ findSubsetViolation(node.condition, intrinsics) ??
398
+ findSubsetViolation(node.whenTrue, intrinsics) ??
399
+ findSubsetViolation(node.whenFalse, intrinsics)
396
400
  );
397
401
  }
398
402
 
@@ -403,14 +407,14 @@ export function findSubsetViolation(
403
407
  // argument is classified on its own terms and nothing is rejected merely
404
408
  // for being in the "wrong" position.
405
409
  for (const arg of node.arguments ?? []) {
406
- const v = findSubsetViolation(arg, intrinsics, allowCompositeStepAccess);
410
+ const v = findSubsetViolation(arg, intrinsics);
407
411
  if (v) return v;
408
412
  }
409
413
  return undefined;
410
414
  }
411
415
 
412
416
  if (ts.isSpreadElement(node)) {
413
- return findSubsetViolation(node.expression, intrinsics, allowCompositeStepAccess);
417
+ return findSubsetViolation(node.expression, intrinsics);
414
418
  }
415
419
 
416
420
  if (ts.isCallExpression(node)) {
@@ -426,7 +430,7 @@ export function findSubsetViolation(
426
430
  // ever be MORE permissive than `fold()`, never stricter.
427
431
  if (ts.isIdentifier(node.expression) && isFoldableHelperName(node.expression.text)) {
428
432
  for (const arg of node.arguments) {
429
- const v = findSubsetViolation(arg, intrinsics, allowCompositeStepAccess);
433
+ const v = findSubsetViolation(arg, intrinsics);
430
434
  if (v) return v;
431
435
  }
432
436
  return undefined;
@@ -446,12 +450,46 @@ export function findSubsetViolation(
446
450
  intrinsics.some((i) => i.name === (node.expression as ts.Identifier).text && intrinsicCallFolds(i))
447
451
  ) {
448
452
  for (const arg of node.arguments) {
449
- const v = findSubsetViolation(arg, intrinsics, allowCompositeStepAccess);
453
+ const v = findSubsetViolation(arg, intrinsics);
450
454
  if (v) return v;
451
455
  }
452
456
  return undefined;
453
457
  }
454
458
 
459
+ // chant #1966 — the eager counterpart of the #1044 case above: a lexicon
460
+ // intrinsic registered with `foldsEagerly` instead of `foldsAsCall`
461
+ // ({@link intrinsicCallFoldsEagerly}, ../lexicon.ts). Same registry-gated
462
+ // shape, same reasoning.
463
+ if (
464
+ intrinsics &&
465
+ ts.isIdentifier(node.expression) &&
466
+ intrinsics.some((i) => i.name === (node.expression as ts.Identifier).text && intrinsicCallFoldsEagerly(i))
467
+ ) {
468
+ for (const arg of node.arguments) {
469
+ const v = findSubsetViolation(arg, intrinsics);
470
+ if (v) return v;
471
+ }
472
+ return undefined;
473
+ }
474
+
475
+ // chant #1966 — a method call (`github.actor.toString()`, `[...].join(",")`,
476
+ // `matrix("os").toString()`): `fold()` now folds this shape unconditionally
477
+ // — the receiver must fold to a real value with the named method, which is
478
+ // resolution-dependent (module doc, point 1) and so out of reach for a
479
+ // syntax-only classifier, exactly the same asymmetry as `.step` above and
480
+ // a same-file `new` used as a value (#1169). Accepted here regardless of
481
+ // the intrinsics registry, unlike the two cases just above: nothing about
482
+ // whether a method call folds depends on a lexicon's registration.
483
+ if (ts.isPropertyAccessExpression(node.expression)) {
484
+ const v = findSubsetViolation(node.expression.expression, intrinsics);
485
+ if (v) return v;
486
+ for (const arg of node.arguments) {
487
+ const argViolation = findSubsetViolation(arg, intrinsics);
488
+ if (argViolation) return argViolation;
489
+ }
490
+ return undefined;
491
+ }
492
+
455
493
  return violation(node, callExpressionMessage(node));
456
494
  }
457
495
 
@@ -21,6 +21,9 @@ export const IntrinsicDefSchema = z.object({
21
21
  // registration written before #1044 means. Only an explicit `true` opts a
22
22
  // call into folding. See IntrinsicDef.foldsAsCall in ../lexicon.ts.
23
23
  foldsAsCall: z.boolean().optional(),
24
+ // chant #1966 — same default-off shape as foldsAsCall, for the EAGER
25
+ // call-form opt-in. See IntrinsicDef.foldsEagerly in ../lexicon.ts.
26
+ foldsEagerly: z.boolean().optional(),
24
27
  });
25
28
 
26
29
  // ---------------------------------------------------------------------------
package/src/lexicon.ts CHANGED
@@ -226,6 +226,29 @@ export interface IntrinsicDef {
226
226
  * run path would have called, from the module the source itself named.
227
227
  */
228
228
  readonly foldsAsCall?: boolean;
229
+ /**
230
+ * chant #1966 — opt this intrinsic's PLAIN-CALL form into EAGER folding:
231
+ * unlike {@link foldsAsCall}, which defers to a `{__intrinsic}` envelope
232
+ * revived later, `fold()` (../fold/fold.ts) calls the real function
233
+ * immediately, with the folded arguments, and its real return value IS the
234
+ * folded value.
235
+ *
236
+ * Exists for a lexicon helper whose result is commonly embedded directly in
237
+ * a template literal (`` `${matrix("os")}` ``, github's
238
+ * `matrix`/`inputs`/…) — an envelope is still a plain `{__intrinsic, args}`
239
+ * object when the enclosing template folds, so `String()` coercion at fold
240
+ * time would stringify it as `"[object Object]"` instead of the real value.
241
+ * Eager evaluation produces the real, already-live result instead.
242
+ *
243
+ * Same admission bar as {@link foldsAsCall} (a pure function of its
244
+ * arguments — no I/O, no mutable state, no dependency on anything but its
245
+ * arguments) and mutually exclusive with both {@link isTag} and
246
+ * `foldsAsCall`: a plain call folds exactly one way.
247
+ * `../discovery/fold-import.ts`'s `resolveActiveLexiconExport` checks this
248
+ * to admit the function export into `externals` at all — a lexicon
249
+ * package's function export is excluded there otherwise.
250
+ */
251
+ readonly foldsEagerly?: boolean;
229
252
  }
230
253
 
231
254
  /**
@@ -257,8 +280,8 @@ export interface IntrinsicDef {
257
280
  * `dist/manifest.json`) that may predate either field. `undefined` there
258
281
  * means what it always did — not a tag, not opted in.
259
282
  */
260
- export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean }): boolean {
261
- return intrinsicTagFolds(def) || intrinsicCallFolds(def);
283
+ export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean; foldsEagerly?: boolean }): boolean {
284
+ return intrinsicTagFolds(def) || intrinsicCallFolds(def) || intrinsicCallFoldsEagerly(def);
262
285
  }
263
286
 
264
287
  /** True when this intrinsic's TAGGED-TEMPLATE form folds (`Sub\`...\``) — see {@link intrinsicFolds}. */
@@ -280,6 +303,15 @@ export function intrinsicCallFolds(def: { isTag?: boolean; foldsAsCall?: boolean
280
303
  return def.isTag !== true && def.foldsAsCall === true;
281
304
  }
282
305
 
306
+ /**
307
+ * True when this intrinsic's PLAIN-CALL form folds EAGERLY (chant #1966) —
308
+ * i.e. the lexicon opted it in via {@link IntrinsicDef.foldsEagerly}.
309
+ * `isTag: true` disqualifies, same as {@link intrinsicCallFolds}.
310
+ */
311
+ export function intrinsicCallFoldsEagerly(def: { isTag?: boolean; foldsEagerly?: boolean }): boolean {
312
+ return def.isTag !== true && def.foldsEagerly === true;
313
+ }
314
+
283
315
  /**
284
316
  * Options passed to a MigrationSource by `chant migrate`.
285
317
  */
@@ -406,6 +438,61 @@ export interface ComponentPipelineResult {
406
438
  jobs: ComponentPipelineJob[];
407
439
  }
408
440
 
441
+ /**
442
+ * Finding-mode a scheduled Op surfaces on drift — the vocabulary the
443
+ * temporal-lexicon composites (`WorkflowAuditOp`/`PipelineAuditOp`/
444
+ * `ReconcileOp`) already declare on the Op itself (#927). The mode is baked
445
+ * into the Op's own activity args at build time and is never re-passed on the
446
+ * generated CI invocation; here it decides only what token/permission wiring
447
+ * the generated job needs to act on a finding — elevated write access for
448
+ * `issue`/`pull-request`/`merge-request`, none for `report`.
449
+ */
450
+ export type OpFindingMode = "report" | "issue" | "pull-request" | "merge-request";
451
+
452
+ /** One scheduled Op to generate CI for — the cron-triggered counterpart to a component (generate mode). */
453
+ export interface ScheduledOpSpec {
454
+ /** Op name (`*.op.ts`'s `Op({ name })`) — what `chant run <name>` targets. */
455
+ name: string;
456
+ /** Cron expression driving the CI-native trigger — the CI-only alternative to a Temporal `TemporalSchedule`. */
457
+ schedule: string;
458
+ /** This Op's finding-mode, for permission/token wiring only (see {@link OpFindingMode}). Default: "report" — no elevated permissions. */
459
+ findingMode?: OpFindingMode;
460
+ }
461
+
462
+ /** One CI job generated for a scheduled Op. */
463
+ export interface OpPipelineJob {
464
+ /** CI job name (safe as a YAML key). */
465
+ jobName: string;
466
+ /** The Op this job runs. */
467
+ op: string;
468
+ /** Cron expression this job's workflow is scheduled on. */
469
+ schedule: string;
470
+ /** The finding-mode wired for this job. */
471
+ findingMode: OpFindingMode;
472
+ }
473
+
474
+ /**
475
+ * One generated CI file for a scheduled Op. Unlike a component pipeline —
476
+ * one combined document for the whole graph — a cron trigger is
477
+ * workflow-scoped in every CI provider chant targets (GitHub Actions'
478
+ * `on.schedule`, a GitLab pipeline schedule bound to one `.gitlab-ci.yml`), so
479
+ * each scheduled Op gets its own file.
480
+ */
481
+ export interface OpPipelineFile {
482
+ /** Suggested file name (e.g. `<op-name>.yml`), relative to the provider's workflow directory. */
483
+ name: string;
484
+ /** The synthesized CI YAML for this Op. */
485
+ yaml: string;
486
+ }
487
+
488
+ /** The synthesized CI pipeline for a set of scheduled Ops (generate mode's Op counterpart, #927). */
489
+ export interface OpPipelineResult {
490
+ /** One file per scheduled Op. */
491
+ files: OpPipelineFile[];
492
+ /** Every generated job, in emit order — one per Op. */
493
+ jobs: OpPipelineJob[];
494
+ }
495
+
409
496
  /**
410
497
  * Live status of a single deploy unit (a CloudFormation stack, a K8s release, …)
411
498
  * addressed by its deployed name — the per-component presence signal
@@ -640,6 +727,20 @@ export interface LexiconPlugin {
640
727
  options?: ComponentPipelineOptions,
641
728
  ): ComponentPipelineResult;
642
729
 
730
+ /**
731
+ * Generate CI for a set of scheduled, stateless Ops — the cron-triggered
732
+ * sibling of {@link generateComponentPipeline} (#927). Each
733
+ * `ScheduledOpSpec` becomes its own CI file (cron triggers are
734
+ * workflow-scoped in every CI provider chant targets), invoking `chant run
735
+ * <name>` on the local executor — the same one-shot invocation `chant run
736
+ * <op>` runs by hand. Only CI-provider lexicons (gitlab, github, forgejo)
737
+ * implement this; omit for lexicons that are not CI providers.
738
+ */
739
+ generateOpPipeline?(
740
+ ops: ScheduledOpSpec[],
741
+ options?: ComponentPipelineOptions,
742
+ ): OpPipelineResult;
743
+
643
744
  /**
644
745
  * Render this lexicon's config-declared build roots into entities (#1548
645
746
  * piece 3) — the seam for a build root that is NOT typed chant source. The