@intentius/chant 0.64.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 (66) hide show
  1. package/dist/behaviour-delta.d.ts +181 -0
  2. package/dist/behaviour-delta.d.ts.map +1 -0
  3. package/dist/behaviour-http.d.ts +106 -0
  4. package/dist/behaviour-http.d.ts.map +1 -0
  5. package/dist/behaviour-overlay.d.ts +61 -0
  6. package/dist/behaviour-overlay.d.ts.map +1 -0
  7. package/dist/behaviour.d.ts +1174 -0
  8. package/dist/behaviour.d.ts.map +1 -0
  9. package/dist/cli/handlers/scenario.d.ts.map +1 -1
  10. package/dist/identity.d.ts +28 -0
  11. package/dist/identity.d.ts.map +1 -1
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/lexicon.d.ts +45 -0
  15. package/dist/lexicon.d.ts.map +1 -1
  16. package/dist/lifecycle/scenario-eval.d.ts +23 -5
  17. package/dist/lifecycle/scenario-eval.d.ts.map +1 -1
  18. package/dist/lifecycle/scenario.d.ts +45 -3
  19. package/dist/lifecycle/scenario.d.ts.map +1 -1
  20. package/dist/lifecycle/types.d.ts +17 -0
  21. package/dist/lifecycle/types.d.ts.map +1 -1
  22. package/dist/op/activities/activity-contracts.d.ts +42 -0
  23. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  24. package/dist/op/activities/index.d.ts +2 -0
  25. package/dist/op/activities/index.d.ts.map +1 -1
  26. package/dist/op/activities/predict-behaviour.d.ts +207 -0
  27. package/dist/op/activities/predict-behaviour.d.ts.map +1 -0
  28. package/dist/op/activities/reconcile.d.ts +86 -16
  29. package/dist/op/activities/reconcile.d.ts.map +1 -1
  30. package/dist/op/composites/behaviour-op.d.ts +57 -0
  31. package/dist/op/composites/behaviour-op.d.ts.map +1 -0
  32. package/dist/op/composites/index.d.ts +2 -0
  33. package/dist/op/composites/index.d.ts.map +1 -1
  34. package/dist/op/index.d.ts +2 -2
  35. package/dist/op/index.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/behaviour-delta.test.ts +331 -0
  38. package/src/behaviour-delta.ts +564 -0
  39. package/src/behaviour-http.test.ts +456 -0
  40. package/src/behaviour-http.ts +252 -0
  41. package/src/behaviour-overlay.test.ts +149 -0
  42. package/src/behaviour-overlay.ts +76 -0
  43. package/src/behaviour.test.ts +2011 -0
  44. package/src/behaviour.ts +2127 -0
  45. package/src/cli/handlers/scenario.test.ts +108 -0
  46. package/src/cli/handlers/scenario.ts +63 -16
  47. package/src/fold/subset-doc-parity.test.ts +60 -0
  48. package/src/identity.ts +31 -2
  49. package/src/index.ts +3 -0
  50. package/src/lexicon.ts +46 -0
  51. package/src/lifecycle/scenario-cost.test.ts +183 -0
  52. package/src/lifecycle/scenario-eval.ts +133 -6
  53. package/src/lifecycle/scenario.ts +72 -4
  54. package/src/lifecycle/types.ts +17 -0
  55. package/src/lint/rules/op/ops012-activity-contract.test.ts +31 -0
  56. package/src/op/activities/activity-contracts.ts +49 -0
  57. package/src/op/activities/index.ts +24 -0
  58. package/src/op/activities/predict-behaviour.test.ts +255 -0
  59. package/src/op/activities/predict-behaviour.ts +468 -0
  60. package/src/op/activities/reconcile.test.ts +84 -0
  61. package/src/op/activities/reconcile.ts +97 -24
  62. package/src/op/activity-contract-registry.test.ts +3 -0
  63. package/src/op/composites/behaviour-op.test.ts +56 -0
  64. package/src/op/composites/behaviour-op.ts +99 -0
  65. package/src/op/composites/index.ts +2 -0
  66. package/src/op/index.ts +2 -0
@@ -65,18 +65,22 @@ const execAsync = promisify(exec);
65
65
  * half works — plain paginated listing (no `/search/issues`, confirmed
66
66
  * absent from Forgejo's own OpenAPI spec) and the `.pull_request == null`
67
67
  * filter both behave exactly as they do against github.com. The *write*
68
- * half does not, for a reason no mock could have caught: `gh`'s own
68
+ * half did not, for a reason no mock could have caught: `gh`'s own
69
69
  * `GH_TOKEN`/`GITHUB_TOKEN` only authenticate a request to github.com or a
70
70
  * ghe.com subdomain (`gh help environment`), never a self-hosted Forgejo,
71
- * and this function's POST/PATCH calls (like `postOrUpdateComment`'s) carry
72
- * only `GH_TOKEN` — no `GH_HOST`, no `GH_ENTERPRISE_TOKEN`. Every write this
73
- * mode makes on a real Forgejo instance fails with `{"message":"token is
74
- * required"}` (HTTP 401), confirmed with `GH_DEBUG=api` sending no
75
- * `Authorization` header at all. The forgejo Op generator refuses
76
- * `findingMode: "issue"` by name for this reason (chant #2315); this
77
- * activity's own behavior is unchanged; a hand-authored (non-generated)
78
- * workflow that calls it directly against a Forgejo host will hit the same
79
- * 401 the generator now refuses to produce.
71
+ * and both this function's POST/PATCH calls and `postOrUpdateComment`'s
72
+ * carried only `GH_TOKEN`. Every write either made on a real Forgejo
73
+ * instance failed with `{"message":"token is required"}` (HTTP 401), with
74
+ * `GH_DEBUG=api` showing no `Authorization` header at all — which is what
75
+ * shipped in 0.62.0 and 0.63.0, `comment` mode included.
76
+ *
77
+ * Both paths now forward that credential as `GH_ENTERPRISE_TOKEN` as well as
78
+ * `GH_TOKEN`, which is the variable `gh` reads for a host in neither of those
79
+ * two classes (chant #2333). Re-run against the same live instance from a
80
+ * shell with no stored `gh auth login`, the identical POST now sends
81
+ * `Authorization: token …` and returns HTTP 201. See {@link ghCredentialEnv}
82
+ * for the whole probe, for why `GH_HOST` turned out not to be needed
83
+ * alongside it, and for why github.com's own behavior is untouched.
80
84
  */
81
85
  export type ReconcileMode = "pull-request" | "issue" | "report" | "comment";
82
86
 
@@ -277,8 +281,13 @@ export function issueMarker(op: string, env: string): string {
277
281
  return `<!-- chant-reconcile-issue:${markerSlug(op)}/${markerSlug(env)} -->`;
278
282
  }
279
283
 
280
- /** The slugify both markers share: everything outside `[A-Za-z0-9._-]` collapses to `-`. */
281
- function markerSlug(s: string): string {
284
+ /**
285
+ * The slugify every marker shares: everything outside `[A-Za-z0-9._-]`
286
+ * collapses to `-`. Exported for the behaviour finding's own marker
287
+ * (`./predict-behaviour.ts`), so a third marker cannot slugify differently
288
+ * from the two here.
289
+ */
290
+ export function markerSlug(s: string): string {
282
291
  return s.replace(/[^a-zA-Z0-9._-]+/g, "-");
283
292
  }
284
293
 
@@ -485,6 +494,69 @@ export function commentTokenFrom(env: Record<string, string | undefined>): Comme
485
494
  return undefined;
486
495
  }
487
496
 
497
+ /**
498
+ * The environment a `gh api` call carries {@link commentTokenFrom}'s resolved
499
+ * credential in (chant #2333).
500
+ *
501
+ * `GH_TOKEN` alone is not enough, and that is `gh`'s documented behavior
502
+ * rather than a bug in it. `gh` picks the token per *request host*:
503
+ * `GH_TOKEN`/`GITHUB_TOKEN` are scoped to github.com and `ghe.com`
504
+ * subdomains, and every other host — a self-hosted Forgejo, a self-hosted
505
+ * GitHub Enterprise Server — reads `GH_ENTERPRISE_TOKEN`/
506
+ * `GITHUB_ENTERPRISE_TOKEN` instead (`gh help environment`). #2291 fixed the
507
+ * *URL* half of reaching a non-github.com host and left this half untouched,
508
+ * so 0.62.0 and 0.63.0 both shipped a `comment` mode that posts to Forgejo
509
+ * with no `Authorization` header at all.
510
+ *
511
+ * Confirmed against a real Forgejo 12.0.4+gitea-1.22.0 instance under #2333,
512
+ * from a shell whose `GH_CONFIG_DIR` held no `gh auth login` — the condition
513
+ * a fresh Actions checkout starts from, and the one #2304's verification did
514
+ * not reproduce. Driving this function's own POST with `GH_DEBUG=api`:
515
+ * `GH_TOKEN` alone sent no `Authorization` header and got HTTP 401
516
+ * `{"message":"token is required"}`, `GH_TOKEN` with `GITHUB_TOKEN` beside it
517
+ * got the same 401, and `GH_TOKEN` with `GH_HOST` beside it got the same 401
518
+ * again. `GH_ENTERPRISE_TOKEN` sent `Authorization: token …` and got HTTP 201.
519
+ *
520
+ * So the fix is one variable, set to the same value: whichever class `gh`
521
+ * decides the host falls into, the credential it finds there is the one
522
+ * {@link commentTokenFrom} resolved. That is why this does not branch on the
523
+ * forge, and why it needs no host detection of chant's own — the thing that
524
+ * broke was `gh`'s host classification, and setting both classes to one value
525
+ * is what stops chant depending on it.
526
+ *
527
+ * ## Why `GH_HOST` is deliberately not set
528
+ *
529
+ * #2333 proposed `GH_ENTERPRISE_TOKEN` *plus* a `GH_HOST` derived from
530
+ * `GITHUB_API_URL`, on the reading that the pair was needed. It is not: the
531
+ * live instance took `GH_ENTERPRISE_TOKEN` on its own, with no `GH_HOST`
532
+ * anywhere in the environment, because {@link githubApiBaseFrom} already
533
+ * hands `gh api` a full URL and `gh` reads the host off that URL rather than
534
+ * off its default. `GH_HOST` sets the *default* host for calls that name no
535
+ * host — which is what `gh issue create`'s ambient fallback below relies on —
536
+ * so setting it would buy nothing here and put a variable into the
537
+ * environment of calls that do not want one.
538
+ *
539
+ * ## Why github.com is unchanged
540
+ *
541
+ * Both variables carry the same value, so the question is only which one
542
+ * `gh` reads, and github.com reads `GH_TOKEN`. Probed against the real
543
+ * api.github.com the same way: a valid `GH_TOKEN` with a deliberately
544
+ * garbage `GH_ENTERPRISE_TOKEN` beside it succeeded, and a garbage `GH_TOKEN`
545
+ * with a valid `GH_ENTERPRISE_TOKEN` beside it failed `Bad credentials`.
546
+ * `GH_TOKEN` wins on github.com whatever the enterprise variable holds, so
547
+ * the header a github.com call sends is byte-identical to the one it sent
548
+ * before this change.
549
+ *
550
+ * A *self-hosted* GHES host is in the same class as Forgejo and was equally
551
+ * broken — same `gh` code path, same missing header — so it is fixed by the
552
+ * same variable rather than merely preserved. That was inferred from `gh`'s
553
+ * documented scoping in #2332 and is not exercised here: no GHES instance was
554
+ * available to test against.
555
+ */
556
+ export function ghCredentialEnv(base: NodeJS.ProcessEnv, token: CommentToken): NodeJS.ProcessEnv {
557
+ return { ...base, GH_TOKEN: token.value, GH_ENTERPRISE_TOKEN: token.value };
558
+ }
559
+
488
560
  /** What a `comment`-mode step says on a pull request it has no credential for. */
489
561
  export function noCommentTokenMessage(repo: string, number: number): string {
490
562
  return (
@@ -518,10 +590,12 @@ export function noIssueTokenMessage(repo: string): string {
518
590
  * Every call targets a full URL built from {@link githubApiBaseFrom} rather
519
591
  * than the bare relative path this used before #2291 — see that function for
520
592
  * why a bare path broke Forgejo specifically. The token is resolved
521
- * explicitly via {@link commentTokenFrom} and forwarded as `GH_TOKEN`, which
522
- * is a strict superset of `gh`'s own ambient resolution: same value in the
523
- * common case, a named refusal instead of `gh`'s opaque 401 when neither is
524
- * set.
593
+ * explicitly via {@link commentTokenFrom} and forwarded through {@link
594
+ * ghCredentialEnv}, which is a strict superset of `gh`'s own ambient
595
+ * resolution: same value in the common case, a named refusal instead of
596
+ * `gh`'s opaque 401 when neither is set, and — since chant #2333 — the same
597
+ * value under `GH_ENTERPRISE_TOKEN` too, without which the full URL #2291
598
+ * built reached Forgejo carrying no credential.
525
599
  */
526
600
  async function postOrUpdateComment(
527
601
  ctx: PullRequestContext,
@@ -531,7 +605,7 @@ async function postOrUpdateComment(
531
605
  ): Promise<string> {
532
606
  const token = commentTokenFrom(process.env);
533
607
  if (!token) throw new Error(noCommentTokenMessage(ctx.repo, ctx.number));
534
- const env = { ...process.env, GH_TOKEN: token.value };
608
+ const env = ghCredentialEnv(process.env, token);
535
609
 
536
610
  const base = githubApiBaseFrom(process.env);
537
611
  const listUrl = `${base}/repos/${ctx.repo}/issues/${ctx.number}/comments`;
@@ -598,8 +672,8 @@ export type GhExec = (
598
672
  * Server the same way it reaches github.com.
599
673
  *
600
674
  * The credential is resolved and forwarded the same way too (chant #2320),
601
- * through {@link commentTokenFrom} and out as `GH_TOKEN` on every call. It
602
- * did not used to be: `reconcilePr` handed this `(cmd) => execAsync(cmd, {
675
+ * through {@link commentTokenFrom} and out through {@link ghCredentialEnv} on
676
+ * every call. It did not used to be: `reconcilePr` handed this `(cmd) => execAsync(cmd, {
603
677
  * signal })` with no `env` at all, so `CHANT_FORGEJO_TOKEN` never reached
604
678
  * `gh` and the one case that variable exists for — posting to a Forgejo
605
679
  * instance other than the one the job runs on, where `github.token`'s scope
@@ -643,10 +717,9 @@ export type GhExec = (
643
717
  * open` on a real Forgejo 12.0.4+gitea-1.22.0 instance interleaves pull
644
718
  * requests with issues exactly as GitHub's endpoint does, and the
645
719
  * `.pull_request == null` filter below excludes them correctly. The write
646
- * calls this function makes do not clear the same instance — see the
647
- * module doc's `issue` bullet for why, and why the forgejo Op generator
648
- * refuses this mode rather than generating a job that would 401 on every
649
- * run.
720
+ * calls this function makes did not clear the same instance until chant
721
+ * #2333 gave them a credential `gh` would apply to that host — see the
722
+ * module doc's `issue` bullet and {@link ghCredentialEnv}.
650
723
  *
651
724
  * So this reuses the recipe `postOrUpdateComment` already proved for PR
652
725
  * comments: list, `--paginate`, filter with `--jq` by an exact `startswith`
@@ -686,7 +759,7 @@ export async function postOrUpdateGithubIssue(
686
759
  ): Promise<string> {
687
760
  const token = commentTokenFrom(process.env);
688
761
  if (!token) throw new Error(noIssueTokenMessage(repo));
689
- const env = { ...process.env, GH_TOKEN: token.value };
762
+ const env = ghCredentialEnv(process.env, token);
690
763
 
691
764
  const base = githubApiBaseFrom(process.env);
692
765
  const listUrl = `${base}/repos/${repo}/issues?state=open`;
@@ -8,6 +8,9 @@ describe("loadActivityContracts", () => {
8
8
  const contracts = await loadActivityContracts();
9
9
  expect(contracts.get("shellCmd")).toBeDefined();
10
10
  expect(contracts.get("httpCheck")?.returns).toBeDefined();
11
+ // The behaviour activities (#2358) ship their contracts the same way.
12
+ expect(contracts.get("predictBehaviour")?.name).toBe("predictBehaviour");
13
+ expect(contracts.get("behaviourFinding")?.returns).toBeDefined();
11
14
  expect(contracts.get("lifecycleDiff")?.name).toBe("lifecycleDiff");
12
15
  });
13
16
 
@@ -0,0 +1,56 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { BehaviourOp } from "./behaviour-op";
3
+
4
+ /** Reach into the generated Op's phases → the behaviourFinding step. */
5
+ function findingStep(op: unknown): Record<string, unknown> {
6
+ const config = (op as { props: Record<string, unknown> }).props;
7
+ const phases = config.phases as Array<{ name: string; steps: Array<Record<string, unknown>> }>;
8
+ const predict = phases.find((p) => p.name === "Predict");
9
+ if (!predict) throw new Error("no Predict phase");
10
+ return predict.steps[0];
11
+ }
12
+
13
+ describe("BehaviourOp composite (#2358)", () => {
14
+ test("one phase, one step, calling behaviourFinding in comment mode with the Op's own name as `op`", () => {
15
+ const { op } = BehaviourOp({ name: "pr-behaviour", env: "prod", traffic: "1000 rps, p99" });
16
+ const step = findingStep(op);
17
+ expect(step.fn).toBe("behaviourFinding");
18
+ expect(step.args).toEqual({ environment: "prod", traffic: "1000 rps, p99", op: "pr-behaviour", mode: "comment" });
19
+ expect(step.outcomeAttribute).toEqual([
20
+ { name: "Refused", from: "refused" },
21
+ { name: "Comment", from: "commentUrl" },
22
+ ]);
23
+ });
24
+
25
+ test("report mode carries no comment outcome, and an explicit base reaches the step", () => {
26
+ const { op } = BehaviourOp({ name: "pr-behaviour", env: "prod", traffic: "1000 rps, p99", findingMode: "report", base: "main" });
27
+ const step = findingStep(op);
28
+ expect((step.args as { mode: string; base: string }).mode).toBe("report");
29
+ expect((step.args as { base: string }).base).toBe("main");
30
+ expect(step.outcomeAttribute).toEqual([{ name: "Refused", from: "refused" }]);
31
+ });
32
+
33
+ test("scope, stack and region flow into the step; no schedule ever lands on the Op", () => {
34
+ const { op } = BehaviourOp({
35
+ name: "pr-behaviour",
36
+ env: "prod",
37
+ traffic: "100 rps, p50",
38
+ stack: "checkout",
39
+ region: "us-east-1",
40
+ scope: { owned: true },
41
+ });
42
+ const args = findingStep(op).args as Record<string, unknown>;
43
+ expect(args.stack).toBe("checkout");
44
+ expect(args.region).toBe("us-east-1");
45
+ expect(args.owned).toBe(true);
46
+ const props = (op as unknown as { props: Record<string, unknown> }).props;
47
+ expect(props.schedule).toBeUndefined();
48
+ expect(props.labels).toEqual({ Behaviour: "true", Env: "prod" });
49
+ });
50
+
51
+ test("two Ops over one env carry two identities", () => {
52
+ const a = BehaviourOp({ name: "pr-behaviour", env: "prod", traffic: "100 rps, p50" });
53
+ const b = BehaviourOp({ name: "pr-behaviour-peak", env: "prod", traffic: "1000 rps, p99" });
54
+ expect((findingStep(a.op).args as { op: string }).op).not.toBe((findingStep(b.op).args as { op: string }).op);
55
+ });
56
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * BehaviourOp composite — the predicted delta of a pull request as a review
3
+ * finding (#2358, epic #2355).
4
+ *
5
+ * One phase, one step: `behaviourFinding` predicts the declared estate on the
6
+ * pull request's head and on its base branch, differences the two under the
7
+ * contract's rules, and posts the finding in `comment` mode on the pull
8
+ * request (GitHub, Forgejo) or as a note on the merge request (GitLab) that
9
+ * triggered the run. There is no `schedule` here and there never will be: the
10
+ * cadence is the pull request, and the trigger lives on the `ScheduledOpSpec`
11
+ * handed to `generateOpsPipeline` (`{ kind: "pull_request" }`), the way
12
+ * `TerraformWatchOp`'s does.
13
+ *
14
+ * `findingMode: "report"` returns the body without posting, for a `chant run`
15
+ * on a developer's machine with `base` named by hand.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * export const { op } = BehaviourOp({
20
+ * name: "pr-behaviour",
21
+ * env: "prod",
22
+ * traffic: "1000 rps, p99",
23
+ * });
24
+ * ```
25
+ */
26
+
27
+ import { Op, phase } from "../builders";
28
+ import type { OpResource } from "../resource";
29
+ import type { BehaviourFindingArgs, BehaviourFindingMode } from "../activities/predict-behaviour";
30
+
31
+ export interface BehaviourOpConfig {
32
+ /** Op name (kebab-case). Names the Op's output directory, is what `chant run` takes, and keys the comment's marker. */
33
+ name: string;
34
+ /** Environment the prediction is for. */
35
+ env: string;
36
+ /** The traffic level to predict at, verbatim: `1000 rps, p99`. */
37
+ traffic: string;
38
+ /**
39
+ * What to do with the finding. `comment` posts it on the triggering pull or
40
+ * merge request; `report` returns the body only.
41
+ * @default "comment"
42
+ */
43
+ findingMode?: BehaviourFindingMode;
44
+ /** The base branch, when the run cannot read it off its own event (a local `chant run`). */
45
+ base?: string;
46
+ /** Deployed stack, for a multi-stack project. */
47
+ stack?: string;
48
+ /** Region the stack is deployed in. */
49
+ region?: string;
50
+ /** Restrict to chant-owned resources. */
51
+ scope?: { owned?: boolean };
52
+ }
53
+
54
+ export interface BehaviourOpResources {
55
+ /** Op resource — the predict-both-sides-and-post Op. */
56
+ op: InstanceType<typeof OpResource>;
57
+ }
58
+
59
+ export function BehaviourOp(config: BehaviourOpConfig): BehaviourOpResources {
60
+ const mode = config.findingMode ?? "comment";
61
+ const args: BehaviourFindingArgs = {
62
+ environment: config.env,
63
+ traffic: config.traffic,
64
+ // `op` names this Op in the marker the sticky comment is found by (#2319).
65
+ op: config.name,
66
+ mode,
67
+ ...(config.base ? { base: config.base } : {}),
68
+ ...(config.stack ? { stack: config.stack } : {}),
69
+ ...(config.region ? { region: config.region } : {}),
70
+ ...(config.scope?.owned !== undefined ? { owned: config.scope.owned } : {}),
71
+ };
72
+
73
+ const op = Op({
74
+ name: config.name,
75
+ overview: `Predict the ${config.env} estate's behaviour at "${config.traffic}" against the base branch`,
76
+ labels: {
77
+ Behaviour: "true",
78
+ Env: config.env,
79
+ },
80
+ phases: [
81
+ phase("Predict", [
82
+ {
83
+ kind: "activity" as const,
84
+ fn: "behaviourFinding",
85
+ args: { ...args },
86
+ // The finding's headline is the posted comment, and whether either
87
+ // side refused: a refusal is a finding that says "no prediction",
88
+ // and a reader of the run ledger should see that without opening it.
89
+ outcomeAttribute: [
90
+ { name: "Refused", from: "refused" },
91
+ ...(mode === "comment" ? [{ name: "Comment", from: "commentUrl" }] : []),
92
+ ],
93
+ },
94
+ ]),
95
+ ],
96
+ });
97
+
98
+ return { op };
99
+ }
@@ -24,3 +24,5 @@ export { PipelineAuditOp } from "./pipeline-audit-op";
24
24
  export type { PipelineAuditOpConfig, PipelineAuditOpResources } from "./pipeline-audit-op";
25
25
  export { LexiconUpgradeOp, IN_SCOPE_LEXICONS } from "./lexicon-upgrade-op";
26
26
  export type { LexiconUpgradeOpConfig, LexiconUpgradeOpResources } from "./lexicon-upgrade-op";
27
+ export { BehaviourOp } from "./behaviour-op";
28
+ export type { BehaviourOpConfig, BehaviourOpResources } from "./behaviour-op";
package/src/op/index.ts CHANGED
@@ -21,6 +21,7 @@ export { isValidCronExpression, cronSyntaxMessage, cronMatches, cronDueBetween }
21
21
  export {
22
22
  WatchOp, ReconcileOp, ApplyOp, ConvergeOp,
23
23
  WorkflowAuditOp, PipelineAuditOp, LexiconUpgradeOp, IN_SCOPE_LEXICONS,
24
+ BehaviourOp,
24
25
  } from "./composites";
25
26
  export type {
26
27
  WatchOpConfig, WatchOpResources,
@@ -30,6 +31,7 @@ export type {
30
31
  WorkflowAuditOpConfig, WorkflowAuditOpResources,
31
32
  PipelineAuditOpConfig, PipelineAuditOpResources,
32
33
  LexiconUpgradeOpConfig, LexiconUpgradeOpResources,
34
+ BehaviourOpConfig, BehaviourOpResources,
33
35
  } from "./composites";
34
36
  export { receiptActivities, receiptCheckInput } from "./receipt-store";
35
37
  export type {