@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.
- package/dist/behaviour-delta.d.ts +181 -0
- package/dist/behaviour-delta.d.ts.map +1 -0
- package/dist/behaviour-http.d.ts +106 -0
- package/dist/behaviour-http.d.ts.map +1 -0
- package/dist/behaviour-overlay.d.ts +61 -0
- package/dist/behaviour-overlay.d.ts.map +1 -0
- package/dist/behaviour.d.ts +1174 -0
- package/dist/behaviour.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/identity.d.ts +28 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +45 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +23 -5
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts +45 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/types.d.ts +17 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +42 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/predict-behaviour.d.ts +207 -0
- package/dist/op/activities/predict-behaviour.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +86 -16
- package/dist/op/activities/reconcile.d.ts.map +1 -1
- package/dist/op/composites/behaviour-op.d.ts +57 -0
- package/dist/op/composites/behaviour-op.d.ts.map +1 -0
- package/dist/op/composites/index.d.ts +2 -0
- package/dist/op/composites/index.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -2
- package/dist/op/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/behaviour-delta.test.ts +331 -0
- package/src/behaviour-delta.ts +564 -0
- package/src/behaviour-http.test.ts +456 -0
- package/src/behaviour-http.ts +252 -0
- package/src/behaviour-overlay.test.ts +149 -0
- package/src/behaviour-overlay.ts +76 -0
- package/src/behaviour.test.ts +2011 -0
- package/src/behaviour.ts +2127 -0
- package/src/cli/handlers/scenario.test.ts +108 -0
- package/src/cli/handlers/scenario.ts +63 -16
- package/src/fold/subset-doc-parity.test.ts +60 -0
- package/src/identity.ts +31 -2
- package/src/index.ts +3 -0
- package/src/lexicon.ts +46 -0
- package/src/lifecycle/scenario-cost.test.ts +183 -0
- package/src/lifecycle/scenario-eval.ts +133 -6
- package/src/lifecycle/scenario.ts +72 -4
- package/src/lifecycle/types.ts +17 -0
- package/src/lint/rules/op/ops012-activity-contract.test.ts +31 -0
- package/src/op/activities/activity-contracts.ts +49 -0
- package/src/op/activities/index.ts +24 -0
- package/src/op/activities/predict-behaviour.test.ts +255 -0
- package/src/op/activities/predict-behaviour.ts +468 -0
- package/src/op/activities/reconcile.test.ts +84 -0
- package/src/op/activities/reconcile.ts +97 -24
- package/src/op/activity-contract-registry.test.ts +3 -0
- package/src/op/composites/behaviour-op.test.ts +56 -0
- package/src/op/composites/behaviour-op.ts +99 -0
- package/src/op/composites/index.ts +2 -0
- 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
|
|
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
|
|
72
|
-
* only `GH_TOKEN
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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
|
-
/**
|
|
281
|
-
|
|
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
|
|
522
|
-
* is a strict superset of `gh`'s own ambient
|
|
523
|
-
* common case, a named refusal instead of
|
|
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 =
|
|
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
|
|
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
|
|
647
|
-
*
|
|
648
|
-
*
|
|
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 =
|
|
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 {
|