@intentius/terragucci 0.4.2 → 0.4.4

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/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type GenerateSettings } from "./generate-config";
1
2
  export declare const BINARIES: readonly ["terraform", "tofu", "choudoufu"];
2
3
  export declare const FORGES: readonly ["github", "gitlab", "forgejo"];
3
4
  export declare const GATES: readonly ["always", "on-destroy", "never"];
@@ -57,6 +58,30 @@ export type ApplyWhen = (typeof APPLY_WHEN)[number];
57
58
  export type Locks = (typeof LOCKS)[number];
58
59
  export type ApplyMerge = (typeof APPLY_MERGE)[number];
59
60
  export type ApplyRequire = (typeof APPLY_REQUIRES)[number];
61
+ /** The stages a step runs before or after (`steps:`). `drift` is the drift job's refresh-only plan. */
62
+ export declare const STEP_STAGES: readonly ["init", "plan", "apply", "drift"];
63
+ export type StepStage = (typeof STEP_STAGES)[number];
64
+ /** What a step's non-zero exit does: fail the root (the default), or hold its wave for an approval. */
65
+ export declare const STEP_FAILURES: readonly ["fail", "approve"];
66
+ export type StepFailure = (typeof STEP_FAILURES)[number];
67
+ export declare const STEP_KEYS: readonly ["name", "run", "before", "after", "roots", "on_failure"];
68
+ /**
69
+ * One entry of `steps:`. `run` is a shell command, run in the root's
70
+ * directory. Exactly one of `before` and `after` names the stage. `roots`
71
+ * are globs of the roots it runs for (every root when unset).
72
+ * `on_failure: approve` turns a non-zero exit into a hold: the root's wave
73
+ * waits for an approval of its set digest, whatever `gate` says. Only a step
74
+ * that runs before the gate is decided can hold it: one before or after
75
+ * init or plan.
76
+ */
77
+ export interface StepSettings {
78
+ run: string;
79
+ name?: string;
80
+ before?: StepStage;
81
+ after?: StepStage;
82
+ roots?: string[];
83
+ on_failure?: StepFailure;
84
+ }
60
85
  /**
61
86
  * `apply:`: when a change applies. `when: merge` (the default) applies the
62
87
  * default branch after a merge. `when: pull-request` applies an open pull
@@ -78,6 +103,12 @@ export interface ApplySettings {
78
103
  merge?: ApplyMerge;
79
104
  merge_token_env?: string;
80
105
  requires?: ApplyRequire[];
106
+ /**
107
+ * Minutes between runs of the resume job, which applies a waiting wave once
108
+ * an approval of its digest is on chant/lifecycle (resume.ts). Off when
109
+ * unset. 5 to 60.
110
+ */
111
+ resume?: number;
81
112
  }
82
113
  /**
83
114
  * Policy as code, off unless set. `tf-plan` runs the engine over each planned
@@ -169,6 +200,7 @@ export declare const RESPONSES: {
169
200
  readonly tips: readonly ["pull-request", "off"];
170
201
  readonly fmt: readonly ["commit", "off"];
171
202
  readonly publish: readonly ["notes", "off"];
203
+ /** next-wave: with `rollouts:` set, init writes a job that runs `respond rollout` on that schedule, so a merged and applied wave's next one opens within one interval. off leaves the job out. */
172
204
  readonly rollout: readonly ["next-wave", "off"];
173
205
  readonly "version-bump": readonly ["off", "suggest"];
174
206
  /** terragucci#30: a typed decision flags a pull request whose description leaves out what its plan destroys or replaces. Needs `decide:`. */
@@ -236,14 +268,37 @@ export interface AgentCommentSettings {
236
268
  timeout?: number;
237
269
  }
238
270
  export declare const AGENT_COMMENT_KEYS: readonly ["command", "key_secret", "max_turns", "timeout"];
271
+ /**
272
+ * `review`: a model reviews each pull request's intent against its plan
273
+ * (review-agent.ts), off unless `agent` is true. It posts a note and never
274
+ * approves; a `tf-apply` wave's policy reads its risk as `input.review`.
275
+ */
276
+ export interface ReviewSettings {
277
+ /** Turns the review on. */
278
+ agent?: boolean;
279
+ /** The command line, run with the prompt on stdin; it prints the review. Default: Claude Code in print mode with no tools (REVIEW_COMMAND in review-agent.ts). */
280
+ command?: string;
281
+ /** The secret holding the model's API key, mapped into the review command's step alone. Default `ANTHROPIC_API_KEY`. */
282
+ key_secret?: string;
283
+ /** The instructions file, read from the default branch. Default `.terragucci/review.md`. */
284
+ instructions?: string;
285
+ /** Minutes before the review job is stopped. Default 10. */
286
+ timeout?: number;
287
+ }
288
+ export declare const REVIEW_KEYS: readonly ["agent", "command", "key_secret", "instructions", "timeout"];
239
289
  /** The settings one project (or one repo) can carry. Every key is optional. */
240
290
  export interface ProjectSettings {
241
291
  /** Globs of root directories. Detected when absent. */
242
292
  roots?: string[];
243
293
  /** The binary the pipeline runs. Detected when absent. */
244
294
  binary?: Binary;
245
- /** The binary's version. Read from the roots' `required_version` when it pins one. */
246
- version?: string;
295
+ /**
296
+ * The binary's version. Read from the roots' `required_version` when it pins one. As a map of root
297
+ * glob to release, the version each root it matches runs (tofu and terraform, plain roots only).
298
+ */
299
+ version?: string | Record<string, string>;
300
+ /** Each plain root's backend, provider and version files, which `terragucci generate` writes; see generate.ts. */
301
+ generate?: GenerateSettings;
247
302
  /** The forge, for a host terragucci cannot name. */
248
303
  forge?: ForgeName;
249
304
  /** Where the project lives, for a forge not on https or the default port. */
@@ -256,8 +311,10 @@ export interface ProjectSettings {
256
311
  apply?: ApplySettings;
257
312
  /** When a pull request takes its root locks; see LOCKS. */
258
313
  locks?: Locks;
314
+ /** `canary`: globs for the wave that applies first. `jobs`: the most jobs one wave's roots spread across (plain roots, GitHub and Forgejo). */
259
315
  waves?: {
260
316
  canary?: string[];
317
+ jobs?: number;
261
318
  };
262
319
  /** A cron schedule for tf-drift, or false. */
263
320
  drift?: string | false;
@@ -268,13 +325,33 @@ export interface ProjectSettings {
268
325
  */
269
326
  synth?: string;
270
327
  /**
271
- * Chat notifications: the names of the secrets holding a Slack or Teams
272
- * incoming webhook. An apply job whose wave waits, is refused or fails
273
- * posts to each (notify.ts).
328
+ * Commands run before and after a root's init, plan, apply and drift, in
329
+ * the stage's own job, on its checkout, with its environment less the forge
330
+ * tokens. Read from terragucci.yml at base, never from the change under
331
+ * review (./steps.ts).
332
+ */
333
+ steps?: StepSettings[];
334
+ /**
335
+ * The image every job runs in, in place of terragucci's: one built FROM
336
+ * the terragucci image for the binary, so the job still has terragucci and
337
+ * the binary, plus what the steps need.
338
+ */
339
+ image?: string;
340
+ /**
341
+ * Notifications: the names of the secrets holding a Slack or Teams
342
+ * incoming webhook, and a generic webhook's address with the key that
343
+ * signs its body. An apply job whose wave waits, is refused or fails posts
344
+ * to each (notify.ts), and the drift job posts drift to Slack and Teams.
345
+ * `relay` names the customer's relay (relay.ts): a waiting wave's Slack
346
+ * message gets Approve and Decline buttons, its Teams card the reply
347
+ * `@<relay> approve wave-<k> <digest>`.
274
348
  */
275
349
  notify?: {
276
350
  slack?: string;
277
351
  teams?: string;
352
+ webhook?: string;
353
+ webhook_key?: string;
354
+ relay?: string;
278
355
  };
279
356
  /**
280
357
  * Cost estimates per root in the plan note: Infracost on the customer's
@@ -288,6 +365,14 @@ export interface ProjectSettings {
288
365
  * schedule (comment-gitlab.ts), since GitLab starts no pipeline for a note.
289
366
  */
290
367
  comments?: string | false;
368
+ /**
369
+ * A cron schedule, or false: init writes a job that runs `terragucci respond
370
+ * rollout --mode apply` on it, which opens the next wave of every rollout in
371
+ * flight once the last one merged and applied. A single repo's key: a
372
+ * control repo's rollout spans its projects, so it is continued from the
373
+ * control repo. `respond.rollout: off` leaves the job out.
374
+ */
375
+ rollouts?: string | false;
291
376
  /** GitLab only: how the project keeps its forge token; see TOKEN_PROTECTIONS. */
292
377
  gitlab?: {
293
378
  token?: GitLabToken;
@@ -322,10 +407,7 @@ export interface ProjectSettings {
322
407
  trace_url?: string;
323
408
  };
324
409
  tips?: boolean;
325
- modules?: {
326
- path?: string;
327
- publish?: string | string[];
328
- };
410
+ modules?: ModulesSettings;
329
411
  /**
330
412
  * Cloud identities the pipeline takes over OIDC, so no long-lived keys sit in CI.
331
413
  * Plan runs pull-request code and gets the read-only identity; apply gets the
@@ -338,6 +420,12 @@ export interface ProjectSettings {
338
420
  terragrunt?: TerragruntSettings;
339
421
  /** Opt-in policy checks over each plan; see PolicySettings. */
340
422
  policy?: PolicySettings;
423
+ /**
424
+ * `atlantis plan` and `atlantis apply` comments read as `/terragucci plan`
425
+ * and `/terragucci apply`. Off by default. The alias changes the words
426
+ * only: the same checks decide (comment.ts).
427
+ */
428
+ atlantis_comments?: boolean;
341
429
  /** The response to each pipeline event; see RESPONSES. */
342
430
  respond?: Partial<Record<RespondEvent, string>>;
343
431
  /**
@@ -349,6 +437,8 @@ export interface ProjectSettings {
349
437
  token_env: string;
350
438
  comment?: boolean | AgentCommentSettings;
351
439
  };
440
+ /** The AI review of a pull request's intent against its plan; see ReviewSettings. Off when absent. */
441
+ review?: ReviewSettings;
352
442
  /** The typed-decision service; see DecideSettings. Off when absent. A project's `decide` replaces the defaults' whole. */
353
443
  decide?: DecideSettings;
354
444
  /** The AWS region whose CloudTrail drift attribution reads. Default: the region the aws CLI already uses. */
@@ -367,6 +457,7 @@ export declare function responseTo(settings: ProjectSettings, event: RespondEven
367
457
  export type CostSettings = true | {
368
458
  key_secret?: string;
369
459
  command?: string;
460
+ approve_above?: number;
370
461
  };
371
462
  /** The secret Infracost's key is read from when `cost.key_secret` is unset; the job gets it as this variable too. */
372
463
  export declare const COST_KEY_SECRET = "INFRACOST_API_KEY";
@@ -379,6 +470,18 @@ export interface ResolvedSettings extends ProjectSettings {
379
470
  env: Record<string, string>;
380
471
  }
381
472
  export declare const BUILT_IN: ResolvedSettings;
473
+ /**
474
+ * The keys a project's jobs read from the project's own terragucci.yml: the
475
+ * plan and apply stages, `respond`, `approve` and `check-policy` read them
476
+ * there, and no pipeline flag carries them. A control repo's `reconcile`
477
+ * writes each one its settings give the project, other than the built-in
478
+ * value, into that file (init.ts). Every other key reaches a project through
479
+ * the pipeline init writes (flags, the job's environment, its steps and
480
+ * files, such as `apply.resume` and `notify.webhook`). `url` is the project's
481
+ * own clone URL and `rollouts` belongs to a single repo, so `defaults`
482
+ * refuses both.
483
+ */
484
+ export declare const PROJECT_FILE_KEYS: readonly ["policy", "reports", "approval", "gate", "roots", "waves", "parallelism", "synth", "steps", "drift", "cost", "tips", "runtime", "telemetry", "respond", "review", "decide", "audit_region", "modules", "terragrunt", "token_env", "generate"];
382
485
  export declare class ConfigError extends Error {
383
486
  /** Every problem found, when the error is a validation failure. */
384
487
  readonly problems?: string[];
@@ -389,6 +492,44 @@ export declare function checkMode(mode: string): "dry-run" | "apply";
389
492
  export declare const CONFIG_NAMES: string[];
390
493
  /** The config file in `dir`, or undefined. Two of them is an error. */
391
494
  export declare function findConfig(dir: string): string | undefined;
495
+ /** A publisher in another repo whose releases `modules.require: attested` checks. */
496
+ export interface TrustedModuleSource {
497
+ /** How the roots' module sources begin: an `oci://` prefix, or the publisher's git URL with or without `git::`. */
498
+ source: string;
499
+ /** The publisher's cosign public key, a path in this repo. */
500
+ key: string;
501
+ /** The git repository whose `chant/lifecycle` branch holds the publisher's release ledger. */
502
+ ledger: string;
503
+ }
504
+ export interface ModulesSettings {
505
+ path?: string;
506
+ publish?: string | string[];
507
+ /** Sign each release, write its provenance and SBOM, and record it in the release ledger. `true` reads the key at cosign.pub. */
508
+ attest?: boolean | {
509
+ key?: string;
510
+ };
511
+ /** `attested`: tf-check and tf-plan refuse a root that pins a release of a checked source unless it verifies. */
512
+ require?: "attested";
513
+ /** Publishers in other repos whose releases `require` checks. */
514
+ trusted?: TrustedModuleSource[];
515
+ }
516
+ /** Why a control repo's projects take no `rollouts` job: each project's pipeline sees only its own roots. */
517
+ /** Why `waves.jobs` is refused on GitLab: a split wave's shares hold one apply lock between them on GitHub and Forgejo, and GitLab's apply jobs take a resource group one job at a time. */
518
+ export declare const WAVE_JOBS_NOT_GITLAB = "a wave splits across jobs on GitHub and Forgejo; GitLab runs one apply job at a time in its resource group, so leave waves.jobs unset there";
519
+ export declare const WAVE_JOBS_NOT_PR_APPLY = "apply.when: pull-request applies every wave in the one job a comment starts, so a wave has no jobs to spread across; leave waves.jobs unset";
520
+ export declare const WAVE_JOBS_NOT_TERRAGRUNT = "a Terragrunt wave applies its units with one run --all in one job; waves.jobs splits a wave of plain roots, so leave it unset";
521
+ export declare const ROLLOUTS_SINGLE_REPO = "a control repo's rollout plans its waves across every project, and a project's pipeline sees only its own roots; leave rollouts unset and run terragucci respond rollout --mode apply on a schedule in the control repo";
522
+ /**
523
+ * What `synth` rules out, each because it would edit the roots the synth
524
+ * command writes. Those files are output, not in git: a change to them is
525
+ * lost at the next synth, and their source is the app's code (a CDK Terrain
526
+ * app's TypeScript), which terragucci does not edit.
527
+ */
528
+ export declare const SYNTH_DRIFT_PR_SHORT = "synth writes the roots, so a live value belongs in the app that writes them, which terragucci does not edit";
529
+ export declare const SYNTH_DRIFT_PR = "the drift pull request writes each live value into a root's own files, and with synth the command writes those files and git does not hold them, so the value belongs in the app that writes them, which terragucci does not edit; set respond.drift to attribute, which names who changed each value in the drift issue, or to off";
530
+ export declare const SYNTH_ROLLOUTS = "a rollout moves a pin in each root's files or its lock file, and with synth the command writes those files and git does not hold them, so the pin is in the app that writes them; move it there";
531
+ /** The problems `synth` finds in one project's settings: a drift schedule whose response is the pull request, and a rollouts schedule. */
532
+ export declare function synthProblems(s: ProjectSettings, where: string): string[];
392
533
  /** Why `comments` is GitLab's alone: the other forges start a job for each comment. */
393
534
  export declare const COMMENTS_GITLAB_ONLY = "comments is for GitLab, which starts no pipeline for a merge request note; GitHub and Forgejo start the comment jobs from the comment itself, so leave comments unset";
394
535
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/terragucci",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "The whole Terraform lifecycle, handled: one config file, generated pipelines for GitHub, GitLab and Forgejo.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,7 +17,8 @@
17
17
  ".": {
18
18
  "types": "./dist/types.d.ts"
19
19
  },
20
- "./report.schema.json": "./dist/report.schema.json"
20
+ "./report.schema.json": "./dist/report.schema.json",
21
+ "./notify.schema.json": "./dist/notify.schema.json"
21
22
  },
22
23
  "files": [
23
24
  "dist",