@intentius/behold 0.29.7 → 0.29.9

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.
@@ -0,0 +1,528 @@
1
+ // behold#571: what a log line in the kiosk is, in a newcomer's words. One
2
+ // entry per kind of line terragucci, chant (and the tools they call) print in
3
+ // a pipeline's log: the pattern that knows it, the tool and the feature it
4
+ // belongs to, one sentence on what it means, the docs page that explains the
5
+ // feature, and where the line is printed (`repo:path:line`, read at the
6
+ // tool's main branch). `sample` is a line a real run printed (or, where
7
+ // marked, one made from the source); the test runs every pattern against its
8
+ // sample and against the fixture runs, and fails if a pattern stops matching
9
+ // its sample or two patterns match one line.
10
+ //
11
+ // The kiosk draws an outlined chip (the tool and the feature) on the first
12
+ // line of each feature in a job and a faint dot on the later ones; pointing
13
+ // at either, or tapping it, opens the note with a link to the docs.
14
+
15
+ /** Where each tool's docs live. A docs path is relative to its tool's base. */
16
+ export const DOCS_BASES = {
17
+ terragucci: "https://intentius.io/terragucci",
18
+ chant: "https://intentius.io/chant",
19
+ behold: "https://intentius.io/behold",
20
+ choudoufu: "https://intentius.io/choudoufu",
21
+ };
22
+
23
+ /** The tools in the order the features strip lists them. */
24
+ export const PRODUCTS = ["terragucci", "chant", "choudoufu", "behold"];
25
+
26
+ /**
27
+ * The lines a kiosk with nobody at it opens by itself, one at a time, as they
28
+ * scroll into view: the waves, a late plan, the digest, the gate, the hold,
29
+ * the approval spent, a plan that changed after its approval, and an output
30
+ * nobody reviewed.
31
+ */
32
+ export const TOUR = ["T04", "T05", "T07", "T08", "T09", "T12", "T14", "T17", "T20", "T21"];
33
+
34
+ export const ANNOTATIONS = [
35
+ {
36
+ id: "T01", product: "terragucci", feature: "Affected roots",
37
+ pattern: String.raw`^(?:wave \d+: compose: )?affected: \d+ of \d+ (?:roots|units) against \S+, \d+ dependents after them$`,
38
+ note: "The pull request plans the roots its change touches against the base branch, and the roots that depend on them too.",
39
+ docs: { product: "terragucci", path: "/reference/report/#running-it" },
40
+ source: "terragucci:packages/terragucci/src/report/stage.ts:1915",
41
+ sample: "affected: 2 of 4 roots against origin/main, 1 dependents after them",
42
+ },
43
+ {
44
+ id: "T02", product: "terragucci", feature: "Affected roots",
45
+ pattern: String.raw`^(?:wave \d+: compose: )?affected: (\S+) reads the state of (.+)$`,
46
+ note: "This root is planned because it reads the state of a changed root, so a change upstream can change its plan.",
47
+ docs: { product: "terragucci", path: "/guides/track-cross-state-edges/" },
48
+ source: "terragucci:packages/terragucci/src/report/stage.ts:1913",
49
+ sample: "affected: edge reads the state of app",
50
+ },
51
+ {
52
+ id: "T03", product: "terragucci", feature: "Parallel planning",
53
+ pattern: String.raw`^(?:wave \d+ of \d+: )?planning (?:up to \d+ roots at once|one root at a time) \((.+)\)$`,
54
+ note: "How many roots the job plans side by side, and the setting or backend that set that limit.",
55
+ docs: { product: "terragucci", path: "/reference/scale/" },
56
+ source: "terragucci:packages/terragucci/src/report/stage.ts:1096",
57
+ sample: "planning up to 4 roots at once (the s3 backend)",
58
+ },
59
+ {
60
+ id: "T04", product: "terragucci", feature: "Waves",
61
+ pattern: String.raw`^wave (\d+) of (\d+): planning (?!up to \d+ roots at once|one root at a time)(.+)$`,
62
+ note: "The apply job starts planning the roots in this wave; waves run in order, so a root plans after the roots it depends on apply.",
63
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#waves" },
64
+ source: "terragucci:packages/terragucci/src/apply.ts:1688",
65
+ sample: "wave 1 of 3: planning network, other",
66
+ },
67
+ {
68
+ id: "T05", product: "terragucci", feature: "Late planning",
69
+ pattern: String.raw`^wave \d+ of \d+: (\S+) plans on the state (.+) applied$`,
70
+ note: "This root is planned only after the waves it reads from applied, so its plan sees their real state.",
71
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#plans-are-made-late" },
72
+ source: "terragucci:packages/terragucci/src/apply.ts:1444",
73
+ sample: "wave 2 of 3: app plans on the state network (wave 1) applied",
74
+ },
75
+ {
76
+ id: "T06", product: "terragucci", feature: "Cost estimate",
77
+ pattern: String.raw`^(?:wave \d+ of \d+: )?cost: (?:(\S+): )?[+-]?\d+\.\d+ [A-Z]{3} a month(?: over \d+ of \d+ roots)?$`,
78
+ note: "The cost estimator's guess at the monthly cost change of one root or of the whole run.",
79
+ docs: { product: "terragucci", path: "/guides/estimate-cost/" },
80
+ source: "terragucci:packages/terragucci/src/report/cost.ts:136",
81
+ sample: "cost: network: 0.00 USD a month",
82
+ },
83
+ {
84
+ id: "T07", product: "terragucci", feature: "Set digest",
85
+ pattern: String.raw`^wave \d+ of \d+: set digest (jcs1-sha256:[0-9a-f]{64}), \d+ changes?, \d+ destroys?$`,
86
+ note: "One digest over every plan in the wave; an approval names this digest, so it covers exactly these plans.",
87
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#approval-binding" },
88
+ source: "terragucci:packages/terragucci/src/apply.ts:1773",
89
+ sample: "wave 1 of 3: set digest jcs1-sha256:2e1cfef37c06a0cc7b3c287739bfa6bbd0cc141122bb4def1211782592d45011, 1 change, 0 destroys",
90
+ },
91
+ {
92
+ id: "T08", product: "chant", feature: "Gate check",
93
+ pattern: String.raw`^wave \d+ \(([^)]+)\): gate (\S+) \(read at [0-9a-f]{12}(?:, where the wave is absent)?\), digest (\S+): not-required$`,
94
+ note: "The gate policy, read at the base commit, lets this wave apply with no approval because its plans match no rule that needs one.",
95
+ docs: { product: "terragucci", path: "/reference/stages/#gate-policy" },
96
+ source: "chant:packages/core/src/op/op-waves-run.ts:1546",
97
+ sample: "wave 1 (wave-1): gate on-destructive (read at d3aaee4bf231), digest jcs1-sha256:2e1cfef37c06a0cc7b3c287739bfa6bbd0cc141122bb4def1211782592d45011: not-required",
98
+ },
99
+ {
100
+ id: "T09", product: "chant", feature: "Gate check",
101
+ pattern: String.raw`^wave \d+ \(([^)]+)\): gate (\S+) \(read at [0-9a-f]{12}(?:, where the wave is absent)?\), digest (\S+): approved by (\S+)(?: \(review of #\d+\))?(?:; held by (.+))?$`,
102
+ note: "The wave's gate needed an approval, and one recorded for this exact digest lets the wave apply.",
103
+ docs: { product: "terragucci", path: "/reference/stages/#gate-policy" },
104
+ source: "chant:packages/core/src/op/op-waves-run.ts:1546",
105
+ sample: "wave 2 (wave-2): gate on-destructive (read at d3aaee4bf231), digest jcs1-sha256:95b79f4946fffd67b2d3da8971084852c4182623d8223b4a307f7067375fe37a: approved by terragucci-admin",
106
+ },
107
+ {
108
+ id: "T10", product: "terragucci", feature: "Approval mode",
109
+ pattern: String.raw`^wave \d+ of \d+: approval (ledger|pr-review|sealed) \((.+)\)$`,
110
+ note: "Which kind of approval this wave accepts (a ledger record, a pull request review or a signed record) and where that setting came from.",
111
+ docs: { product: "terragucci", path: "/guides/approve-a-wave/#approval-modes" },
112
+ source: "terragucci:packages/terragucci/src/apply.ts:2912",
113
+ sample: "wave 2 of 3: approval ledger (the default)",
114
+ },
115
+ {
116
+ id: "T11", product: "terragucci", feature: "Waiting for approval",
117
+ pattern: String.raw`^wave \d+ of \d+ waits for an approval of digest (jcs1-sha256:[0-9a-f]{64})\. Read its plans above, then approve it with:$`,
118
+ note: "The wave stops here until someone reads its plans and approves this digest with the command printed below.",
119
+ docs: { product: "terragucci", path: "/guides/approve-a-wave/" },
120
+ source: "terragucci:packages/terragucci/src/apply.ts:3000",
121
+ sample: "wave 2 of 3 waits for an approval of digest jcs1-sha256:95b79f4946fffd67b2d3da8971084852c4182623d8223b4a307f7067375fe37a. Read its plans above, then approve it with:",
122
+ },
123
+ {
124
+ id: "T12", product: "chant", feature: "Waiting for approval",
125
+ pattern: String.raw`^Nothing in wave \d+ or after it ran\. (?:Read the plans, then|It waits on .+\.) approve: (chant approve \S+ \S+ --plan \S+) \(with --resume, the approval also starts this job again\)$`,
126
+ note: "The job ended without applying this wave or any later one; the printed chant approve command records the approval and can restart the job.",
127
+ docs: { product: "chant", path: "/guide/op-waves/#resume-the-waiting-job" },
128
+ source: "chant:packages/core/src/op/op-waves-run.ts:1587",
129
+ sample: "Nothing in wave 2 or after it ran. Read the plans, then approve: chant approve tf-apply wave-2 --plan jcs1-sha256:95b79f4946fffd67b2d3da8971084852c4182623d8223b4a307f7067375fe37a (with --resume, the approval also starts this job again)",
130
+ },
131
+ {
132
+ id: "T13", product: "terragucci", feature: "Approved",
133
+ pattern: String.raw`^wave \d+ of \d+: approved by (\S+) for this digest$`,
134
+ note: "The wave goes ahead and applies because an approval of its exact digest exists.",
135
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#approval-binding" },
136
+ source: "terragucci:packages/terragucci/src/apply.ts:2918",
137
+ sample: "wave 2 of 3: approved by terragucci-admin for this digest",
138
+ },
139
+ {
140
+ id: "T14", product: "chant", feature: "Approval spent",
141
+ pattern: String.raw`^wave \d+ \(([^)]+)\) applies under the approval by (\S+) at (\S+), which is now spent$`,
142
+ note: "The wave applies under this approval, and the approval is used up so it cannot apply the same plans a second time.",
143
+ docs: { product: "chant", path: "/guide/op-waves/#spend-an-approval" },
144
+ source: "chant:packages/core/src/op/op-waves-run.ts:2185",
145
+ sample: "wave 2 (wave-2) applies under the approval by terragucci-admin at 2026-10-15T20:55:19.422Z, which is now spent",
146
+ },
147
+ {
148
+ id: "T15", product: "chant", feature: "Approval spent",
149
+ pattern: String.raw`^wave \d+ \(([^)]+)\): the approval of (\S+) by (\S+) at (\S+) is spent: a wave applied under it at (\S+)\. Applying it again needs a new approval`,
150
+ note: "This approval was already used by an earlier apply, so applying these plans again needs a new approval.",
151
+ docs: { product: "chant", path: "/guide/op-waves/#spend-an-approval" },
152
+ source: "chant:packages/core/src/op/op-waves-run.ts:1802",
153
+ sample: "wave 1 (wave-1): the approval of jcs1-sha256:365a4f0d54d735250162c4befe19115733f758d572b030af2bfcc1dd0578d5a0 by terragucci-admin at 2026-10-15T19:54:16.269Z is spent: a wave applied under it at 2026-10-15T19:54:22.333Z. Applying it again needs a new approval",
154
+ },
155
+ {
156
+ id: "T16", product: "terragucci", feature: "Approval spent",
157
+ pattern: String.raw`^wave \d+ of \d+: the approval of (\S+) by (\S+) was used by the apply of those plans, so these plans need an approval of their own$`,
158
+ note: "An earlier apply used up the approval of these same plans, so this run waits for a fresh approval.",
159
+ docs: { product: "chant", path: "/guide/op-waves/#spend-an-approval" },
160
+ source: "terragucci:packages/terragucci/src/apply.ts:2796",
161
+ sample: "wave 2 of 3: the approval of jcs1-sha256:50e8e91d611adfb47150a6785803089d642c769aad050b18cc397e4afcc692f6 by terragucci-admin was used by the apply of those plans, so these plans need an approval of their own",
162
+ },
163
+ {
164
+ id: "T17", product: "terragucci", feature: "Changed after approval",
165
+ pattern: String.raw`^wave \d+ changed after it was approved, so nothing in it was applied\. approved: (\S+); planned now: (\S+)\. `,
166
+ note: "The wave planned to a different digest than the one approved, so nothing applied; the new plan needs its own approval.",
167
+ docs: { product: "terragucci", path: "/guides/fix-a-refused-wave/" },
168
+ source: "terragucci:packages/terragucci/src/apply.ts:2983",
169
+ sample: "wave 1 changed after it was approved, so nothing in it was applied. approved: jcs1-sha256:c7d57fcf0bf559242e2104f4d0ed562888f6bd99ed8c5af350db462771169877; planned now: jcs1-sha256:0be8353d2143e463577e05eca00b62cabd51791aaa383051930e384340812052. Read the new plan, then approve it: terragucci approve wave-1 --plan jcs1-sha256:0be8353d2143e463577e05eca00b62cabd51791aaa383051930e384340812052",
170
+ },
171
+ {
172
+ id: "T18", product: "terragucci", feature: "Changed after approval",
173
+ pattern: String.raw`^wave \d+ of \d+: approved by (\S+), but these roots planned differently since: (.+)$`,
174
+ note: "Names the roots whose plans differ from the approved ones, which is why the approval no longer covers the wave.",
175
+ docs: { product: "terragucci", path: "/tutorial/changed-wave/" },
176
+ source: "terragucci:packages/terragucci/src/apply.ts:2984",
177
+ sample: "wave 1 of 3: approved by terragucci-admin, but these roots planned differently since: network",
178
+ },
179
+ {
180
+ id: "T19", product: "chant", feature: "Step hold",
181
+ pattern: String.raw`^(\S+) hook (\S+)(?: \([^)]*\))? exited (\d+), so wave \d+ \(([^)]+)\) waits for an approval of its set digest$`,
182
+ note: "A step set to hold on failure exited non-zero, so the wave waits for an approval whatever its gate says.",
183
+ docs: { product: "terragucci", path: "/guides/run-steps/#step-failures" },
184
+ source: "chant:packages/core/src/op/op-waves-run.ts:939",
185
+ sample: "after-plan hook held exited 1, so wave 1 (wave-1) waits for an approval of its set digest",
186
+ },
187
+ {
188
+ id: "T20", product: "terragucci", feature: "Unreviewed outputs",
189
+ pattern: String.raw`^wave \d+ of \d+: nobody reviewed the plan it made on those outputs, so it waits for an approval of it(?: although gate is \S+)?$`,
190
+ note: "This wave planned on outputs nobody could see in review, so it waits for an approval even when its gate would let it through.",
191
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#linked-roots" },
192
+ source: "terragucci:packages/terragucci/src/apply.ts:2562",
193
+ sample: "wave 1 of 3: nobody reviewed the plan it made on those outputs, so it waits for an approval of it although gate is on-destructive",
194
+ },
195
+ {
196
+ id: "T21", product: "terragucci", feature: "Unreviewed outputs",
197
+ pattern: String.raw`^wave \d+ of \d+: (\S+) read (.+), unknown in (.+) and known only once wave (\d+) applied it$`,
198
+ note: "A root read an output that was unknown at plan time and only got a value when an earlier wave applied.",
199
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#linked-roots" },
200
+ source: "terragucci:packages/terragucci/src/unknown-reads.ts:185",
201
+ sample: "wave 1 of 3: app read subnet_id of network, unknown in wave 1's plan of network in this job and known only once wave 1 applied it",
202
+ },
203
+ {
204
+ id: "T22", product: "terragucci", feature: "Unreviewed outputs",
205
+ pattern: String.raw`^wave \d+ of \d+: (\S+) reads? no output of (\S+) that (.+) left unknown$`,
206
+ note: "Every output this root reads was already known when it was planned, so its plan holds no values nobody reviewed.",
207
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#linked-roots" },
208
+ source: "terragucci:packages/terragucci/src/unknown-reads.ts:142",
209
+ sample: "wave 2 of 3: app reads no output of network that wave 1's plan of network in this push (2026/10/e8863ee426177316d84499a5b8401c4b6adfb546/tf-apply-wave-1) left unknown",
210
+ },
211
+ {
212
+ id: "T23", product: "terragucci", feature: "Linked roots",
213
+ pattern: String.raw`^(\S+): plans on the planned outputs of (\S+)(?:, (.+) known once it applies)?$`,
214
+ note: "In a pull request this root plans against the planned outputs of its upstream, so the note shows the combined effect before anything applies.",
215
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#linked-roots" },
216
+ source: "terragucci:packages/terragucci/src/planned-outputs.ts:84",
217
+ sample: "app: plans on the planned outputs of network",
218
+ },
219
+ {
220
+ id: "T24", product: "terragucci", feature: "Composed run",
221
+ pattern: String.raw`^(?:wave \d+: )?compose: (.+) (?:apply|plan) as one composed run in wave \d+(?: \((\S+)\))?$`,
222
+ note: "These roots are planned or applied together as modules of one generated root instead of one at a time.",
223
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#a-composed-run" },
224
+ source: "terragucci:packages/terragucci/src/report/stage.ts:1433",
225
+ sample: "compose: app, edge, network plan as one composed run in wave 1",
226
+ },
227
+ {
228
+ id: "T25", product: "terragucci", feature: "Composed run",
229
+ pattern: String.raw`^wave \d+ of \d+: (\S+): (.+) across \d+ roots, composed digest (jcs1-sha256:[0-9a-f]{64})$`,
230
+ note: "The combined plan of the composed run and the single digest an approval of it must name.",
231
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#a-composed-run" },
232
+ source: "terragucci:packages/terragucci/src/apply.ts:2020",
233
+ sample: "wave 1 of 3: compose:wave-1: Plan: 5 to add, 0 to change, 0 to destroy. across 3 roots, composed digest jcs1-sha256:e9a35e2f68482bb1dc6c874a0918b2b85bcc53b77c1379b5d1d487d812afbe2d",
234
+ },
235
+ {
236
+ id: "T26", product: "terragucci", feature: "Unreviewed outputs",
237
+ pattern: String.raw`^wave \d+ of \d+: (\S+) reads (.+), unknown at plan time and computed in this apply$`,
238
+ note: "Inside a composed run this root reads a value that only gets computed during the apply, so nobody saw it in the plan.",
239
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#approval" },
240
+ source: "terragucci:packages/terragucci/src/apply.ts:2022",
241
+ sample: "wave 1 of 3: app reads subnet_id of network, unknown at plan time and computed in this apply",
242
+ },
243
+ {
244
+ id: "T27", product: "terragucci", feature: "Composed run",
245
+ pattern: String.raw`^wave \d+ of \d+: (\S+) \((\S+)\): (?:applied|no-changes|[a-z-]+), \d+ of \d+ changes; its state (written|as it was|not written.*)$`,
246
+ note: "The outcome for one root inside the composed run, and whether its own state file was written back.",
247
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#the-composed-root" },
248
+ source: "terragucci:packages/terragucci/src/apply.ts:2056",
249
+ sample: "wave 1 of 3: network (module.network): applied, 1 of 1 changes; its state written",
250
+ },
251
+ {
252
+ id: "T28", product: "terragucci", feature: "Apply result",
253
+ pattern: String.raw`^applied (\S+): Resources: \d+ added, \d+ changed, \d+ destroyed$`,
254
+ note: "The resource counts Terraform reported when it finished applying this root.",
255
+ docs: { product: "terragucci", path: "/concepts/how-it-works/#a-change-start-to-finish" },
256
+ source: "terragucci:packages/terragucci/src/apply.ts:798",
257
+ sample: "applied other: Resources: 0 added, 0 changed, 0 destroyed",
258
+ },
259
+ {
260
+ id: "T29", product: "terragucci", feature: "State version",
261
+ pattern: String.raw`^(\S+): state (\S+)(?:, versions (on|off|unknown)(?: \((.+)\))?| version (\S+))$`,
262
+ note: "Where the root's state lives after the apply and which stored version holds it, so you can find or restore it later.",
263
+ docs: { product: "terragucci", path: "/guides/find-a-state-version/" },
264
+ source: "terragucci:packages/terragucci/src/apply.ts:3480",
265
+ sample: "network: state s3://demo-terraform-state/network.tfstate, versions off (bucket versioning is off for demo-terraform-state, so it keeps only the latest state)",
266
+ },
267
+ {
268
+ id: "T30", product: "terragucci", feature: "Apply result",
269
+ pattern: String.raw`^wave \d+ of \d+ applied$`,
270
+ note: "Every root in this wave applied, so the next wave can start.",
271
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#waves" },
272
+ source: "terragucci:packages/terragucci/src/apply.ts:1850",
273
+ sample: "wave 1 of 3 applied",
274
+ },
275
+ {
276
+ id: "T31", product: "chant", feature: "Apply failed",
277
+ pattern: String.raw`^wave \d+ \(([^)]+)\): apply failed, so none of (.+) counts as applied$`,
278
+ note: "The wave's apply failed. None of its roots count as applied and later waves do not run.",
279
+ docs: { product: "terragucci", path: "/reference/responses/#apply-failed" },
280
+ source: "chant:packages/core/src/op/op-waves-run.ts:2092",
281
+ sample: "wave 2 (wave-2): apply failed, so none of app counts as applied",
282
+ },
283
+ {
284
+ id: "T32", product: "terragucci", feature: "Run report",
285
+ pattern: String.raw`^wave \d+: report in terragucci-report/, slowest root (\S+) \(([\d.]+)s\)$`,
286
+ note: "The wave's report was written to terragucci-report/, and this root took longest to run.",
287
+ docs: { product: "terragucci", path: "/reference/report/" },
288
+ source: "terragucci:packages/terragucci/src/apply.ts:1560",
289
+ sample: "wave 1: report in terragucci-report/, slowest root network (0.25s)",
290
+ },
291
+ {
292
+ id: "T33", product: "terragucci", feature: "Reports bucket",
293
+ pattern: String.raw`^wave \d+: report copied to (\S+?)(?:, at (\S+))?$`,
294
+ note: "The wave's report was uploaded to the reports bucket under this prefix, so it outlives the CI job.",
295
+ docs: { product: "terragucci", path: "/guides/keep-reports-in-a-bucket/" },
296
+ source: "terragucci:packages/terragucci/src/apply.ts:1566",
297
+ sample: "wave 1: report copied to demo/forgejo:3000/terragucci-admin/demo/2026/10/e8863ee426177316d84499a5b8401c4b6adfb546/tf-apply-wave-1",
298
+ },
299
+ {
300
+ id: "T34", product: "terragucci", feature: "Run view",
301
+ pattern: String.raw`^wave \d+: run view at (\S+)/run\.html$`,
302
+ note: "Where the page showing this whole run, wave by wave, was written in the reports bucket.",
303
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#the-run-view" },
304
+ source: "terragucci:packages/terragucci/src/apply.ts:1483",
305
+ sample: "wave 1: run view at demo/forgejo:3000/terragucci-admin/demo/runs/e8863ee426177316d84499a5b8401c4b6adfb546/run.html",
306
+ },
307
+ {
308
+ id: "T35", product: "terragucci", feature: "Reports bucket",
309
+ pattern: String.raw`^copied to the bucket under (\S+); index rewritten at (.+)$`,
310
+ note: "The plan report was uploaded to the bucket and the index that lists every run was updated.",
311
+ docs: { product: "terragucci", path: "/guides/keep-reports-in-a-bucket/" },
312
+ source: "terragucci:packages/terragucci/src/cli.ts:478",
313
+ sample: "copied to the bucket under demo/forgejo:3000/terragucci-admin/demo/2026/10/3660bae82703755ab670502bb0bf7e48b599fe80/tf-plan; index rewritten at demo/forgejo:3000/terragucci-admin/demo/index.json and demo/index.json",
314
+ },
315
+ {
316
+ id: "T36", product: "terragucci", feature: "Apply from a comment",
317
+ pattern: String.raw`^terragucci comment-apply: apply pull request (\d+)'s merge commit ([0-9a-f]{8})(?: through wave \d+)? for (\S+)$`,
318
+ note: "A comment on a merged pull request asked to apply its merge commit, and the job goes ahead on behalf of that user.",
319
+ docs: { product: "terragucci", path: "/guides/re-plan-from-a-comment/#apply-a-merged-pull-request" },
320
+ source: "terragucci:packages/terragucci/src/comment-apply.ts:255",
321
+ sample: "terragucci comment-apply: apply pull request 1086's merge commit e8863ee4 for terragucci-admin",
322
+ },
323
+ {
324
+ id: "T37", product: "terragucci", feature: "Plan note",
325
+ pattern: String.raw`^terragucci plan-note: posted the plan note on pull request (\d+)$`,
326
+ note: "The plan summary was posted as a comment on the pull request.",
327
+ docs: { product: "terragucci", path: "/reference/report/#the-plan-note" },
328
+ source: "terragucci:packages/terragucci/src/plan-note.ts:180",
329
+ sample: "terragucci plan-note: posted the plan note on pull request 1086",
330
+ },
331
+ {
332
+ id: "T38", product: "terragucci", feature: "Plan note",
333
+ pattern: String.raw`^terragucci plan-note: terragucci/plan is (\S+) on ([0-9a-f]{8}): (.+)$`,
334
+ note: "terragucci set the terragucci/plan commit status on the pull request's head with a one-line count of what the plan does.",
335
+ docs: { product: "terragucci", path: "/reference/pipeline/#statuses-and-stale-plans" },
336
+ source: "terragucci:packages/terragucci/src/plan-note.ts:191",
337
+ sample: "terragucci plan-note: terragucci/plan is success on 3660bae8: 3 roots, 3 groups, 0 destroys",
338
+ },
339
+ {
340
+ id: "T39", product: "chant", feature: "Plan grouping",
341
+ pattern: String.raw`^\d+ (?:members?|roots?|units?|instances?): \d+ groups?, .*\d+ (?:destroy or replacement|destroys or replacements)(?:, .+)?\.$`,
342
+ note: "The plan summary's first line counts the planned roots and how many groups and destroys they add up to.",
343
+ docs: { product: "terragucci", path: "/concepts/why-plans-are-grouped/#grouping" },
344
+ source: "chant:packages/core/src/plan-summary.ts:684",
345
+ sample: "3 members: 3 groups, 0 destroys or replacements.",
346
+ },
347
+ {
348
+ id: "T40", product: "chant", feature: "Plan grouping",
349
+ pattern: String.raw`^Group [0-9a-f]+: \d+ \S+(?: of \S+)? \(outlier\)`,
350
+ note: "This group's change differs from what similar roots do, so it is flagged for a closer look.",
351
+ docs: { product: "terragucci", path: "/tutorial/one-note/" },
352
+ source: "chant:packages/core/src/plan-summary.ts:690",
353
+ sample: "Group 46b88a607c5f: 1 member (outlier), change:",
354
+ },
355
+ {
356
+ id: "T41", product: "chant", feature: "Plan grouping",
357
+ pattern: String.raw`^Destroys and replacements \(\d+\):$`,
358
+ note: "The plan summary lists every resource the plans destroy or replace before anything else.",
359
+ docs: { product: "terragucci", path: "/concepts/why-plans-are-grouped/" },
360
+ source: "chant:packages/core/src/plan-summary.ts:726",
361
+ sample: "Destroys and replacements (1):",
362
+ },
363
+ {
364
+ id: "T42", product: "terragucci", feature: "Validate",
365
+ pattern: String.raw`^valid (\S+)$`,
366
+ note: "The check job ran validate on this root and it passed.",
367
+ docs: { product: "terragucci", path: "/reference/stages/#check" },
368
+ source: "terragucci:packages/terragucci/src/check.ts:187",
369
+ sample: "valid app",
370
+ },
371
+ {
372
+ id: "T43", product: "terragucci", feature: "Tips",
373
+ pattern: String.raw`^(.+): (opened|open) (https?://\S+) \((terragucci/tip/\S+): (.+)\)$`,
374
+ note: "terragucci opened (or found already open) a small pull request that applies one of its setup tips, such as adding a canary wave.",
375
+ docs: { product: "terragucci", path: "/reference/responses/#tips-fmt-and-release-notes" },
376
+ source: "terragucci:packages/terragucci/src/respond/index.ts:151",
377
+ sample: "Add a canary wave: app: opened http://forgejo:3000/terragucci-admin/demo/pulls/1087 (terragucci/tip/canary: terragucci.yml)",
378
+ },
379
+ {
380
+ id: "T44", product: "terragucci", feature: "Eligible approvers",
381
+ pattern: String.raw`^wave \d+ of \d+: (\S+) (.+), so \1's (approval|review) doesn't count$`,
382
+ note: "This person had a hand in the change. Their approval or review does not count toward the gate.",
383
+ docs: { product: "terragucci", path: "/guides/approve-a-wave/#eligible-approvers" },
384
+ source: "terragucci:packages/terragucci/src/involved.ts:175",
385
+ sample: "wave 2 of 3: demo-author opened pull request 1086, so demo-author's review doesn't count", // made from the source, no run printed it yet
386
+ },
387
+ {
388
+ id: "T45", product: "terragucci", feature: "Eligible approvers",
389
+ pattern: String.raw`^wave \d+ of \d+: an approval counts only from someone who had no hand in the change, so not from (.+)$`,
390
+ note: "An approval from the people named here does not count because they had a hand in the change.",
391
+ docs: { product: "terragucci", path: "/guides/approve-a-wave/#eligible-approvers" },
392
+ source: "terragucci:packages/terragucci/src/apply.ts:3001",
393
+ sample: "wave 2 of 3: an approval counts only from someone who had no hand in the change, so not from demo-author", // made from the source, no run printed it yet
394
+ },
395
+ {
396
+ id: "T46", product: "terragucci", feature: "Changed after approval",
397
+ pattern: String.raw`^Wave \d+ applies nothing: its plans changed after the approval \(approved [0-9a-f]{12}, now [0-9a-f]{12}\)\.$`,
398
+ note: "The reply terragucci posts on a refused wave. Its plans changed after the approval, and it gives both short digests.",
399
+ docs: { product: "terragucci", path: "/reference/responses/#wave-refused" },
400
+ source: "terragucci:packages/terragucci/src/respond/refused.ts:79",
401
+ sample: "Wave 1 applies nothing: its plans changed after the approval (approved c7d57fcf0bf5, now 0be8353d2143).",
402
+ },
403
+ {
404
+ id: "T47", product: "terragucci", feature: "Composed run",
405
+ pattern: String.raw`^(\S+): in the composed run as (\S+): (.+)$`,
406
+ note: "In the pull request plan, this root's changes as a module of the composed run.",
407
+ docs: { product: "terragucci", path: "/concepts/waves-and-approvals/#a-composed-run" },
408
+ source: "terragucci:packages/terragucci/src/report/stage.ts:1575",
409
+ sample: "network: in the composed run as module.network: Plan: 1 to add, 0 to change, 0 to destroy.",
410
+ },
411
+ {
412
+ id: "T48", product: "terragucci", feature: "Apply result",
413
+ pattern: String.raw`^all roots applied$`,
414
+ note: "The apply job finished every group of roots with no failure.",
415
+ docs: { product: "terragucci", path: "/concepts/how-it-works/#a-change-start-to-finish" },
416
+ source: "terragucci:packages/terragucci/src/render.ts:1146",
417
+ sample: "all roots applied",
418
+ },
419
+ ];
420
+
421
+ /** A runner's timestamp at the start of a line, as Forgejo and GitHub print it. */
422
+ const STAMP = /^\d{4}-\d\d-\d\dT[\d:.]+Z /;
423
+
424
+ /** The line without its runner timestamp. */
425
+ export function stripStamp(line) {
426
+ return typeof line === "string" ? line.replace(STAMP, "") : "";
427
+ }
428
+
429
+ let compiled = null;
430
+ const rules = () => (compiled ||= ANNOTATIONS.map((a) => ({ a, re: new RegExp(a.pattern) })));
431
+
432
+ /** The entry a line matches, the first in the catalogue's order; null for a line none knows. */
433
+ export function annotationOf(line) {
434
+ const text = stripStamp(line);
435
+ if (!text) return null;
436
+ for (const { a, re } of rules()) if (re.test(text)) return a;
437
+ return null;
438
+ }
439
+
440
+ /** Every entry a line matches (the test's: one at most). */
441
+ export function annotationsOf(line) {
442
+ const text = stripStamp(line);
443
+ return text ? rules().filter(({ re }) => re.test(text)).map(({ a }) => a) : [];
444
+ }
445
+
446
+ /** The entry with this id, or null. */
447
+ export function annotationById(id) {
448
+ return ANNOTATIONS.find((a) => a.id === id) || null;
449
+ }
450
+
451
+ /** The docs page's address. */
452
+ export function docsUrl(a) {
453
+ const base = DOCS_BASES[a.docs.product] || DOCS_BASES[a.product];
454
+ return `${base}${a.docs.path}`;
455
+ }
456
+
457
+ /** A feature's key: its tool and its name. */
458
+ export const featureKey = (a) => `${a.product}|${a.feature}`;
459
+
460
+ /** The chip's words: the tool and the feature. */
461
+ export const chipWords = (a) => `${a.product} · ${a.feature}`;
462
+
463
+ /**
464
+ * The marks of one job's log, its steps in order: for each annotated line
465
+ * (`<step index>:<line number>`), its entry and whether it is the first line
466
+ * of its feature in the job (a chip) or a later one (a dot). Line numbers are
467
+ * the log's, after a kept log's cut (`truncated`). `hide(line)` leaves a line
468
+ * out, as the page does.
469
+ */
470
+ export function jobMarks(steps, { hide = null } = {}) {
471
+ const marks = new Map();
472
+ const seen = new Set();
473
+ (steps || []).forEach((s, i) => {
474
+ const from = s.truncated || 0;
475
+ (s.log || []).forEach((line, j) => {
476
+ if (hide && hide(line)) return;
477
+ const a = annotationOf(line);
478
+ if (!a) return;
479
+ const key = featureKey(a);
480
+ marks.set(`${i}:${from + j + 1}`, { a, first: !seen.has(key) });
481
+ seen.add(key);
482
+ });
483
+ });
484
+ return marks;
485
+ }
486
+
487
+ /**
488
+ * The features a run's logs show, by tool in PRODUCTS' order, each feature
489
+ * once with the first line that shows it: `[{ product, features: [{ feature,
490
+ * a, job, step, n }] }]`. Jobs in the order given (the page's), steps and
491
+ * lines in the log's order. Only the features some line matched.
492
+ */
493
+ export function runFeatures(jobs, { hide = null } = {}) {
494
+ const first = new Map();
495
+ for (const job of jobs || []) {
496
+ (job.steps || []).forEach((s, i) => {
497
+ const from = s.truncated || 0;
498
+ (s.log || []).forEach((line, j) => {
499
+ if (hide && hide(line)) return;
500
+ const a = annotationOf(line);
501
+ if (!a || first.has(featureKey(a))) return;
502
+ first.set(featureKey(a), { feature: a.feature, a, job: job.name, step: i, n: from + j + 1 });
503
+ });
504
+ });
505
+ }
506
+ const all = [...first.values()];
507
+ return PRODUCTS.map((product) => ({ product, features: all.filter((f) => f.a.product === product) })).filter((g) => g.features.length);
508
+ }
509
+
510
+ /**
511
+ * The first line of a run's logs that an entry of `ids` knows and `pick`
512
+ * accepts (`pick(text, a)`): `{ a, job, step, n, text }`, or null.
513
+ */
514
+ export function findLine(run, ids, pick = () => true) {
515
+ for (const job of (run && run.jobs) || []) {
516
+ const steps = job.steps || [];
517
+ for (let i = 0; i < steps.length; i++) {
518
+ const s = steps[i];
519
+ const from = s.truncated || 0;
520
+ const log = s.log || [];
521
+ for (let j = 0; j < log.length; j++) {
522
+ const a = annotationOf(log[j]);
523
+ if (a && ids.includes(a.id) && pick(stripStamp(log[j]), a)) return { a, job: job.name, step: i, n: from + j + 1, text: stripStamp(log[j]) };
524
+ }
525
+ }
526
+ }
527
+ return null;
528
+ }
@@ -0,0 +1,83 @@
1
+ // behold#571: the catalogue of log lines the kiosk annotates. Every pattern
2
+ // has to keep matching the line it was written for, no line may match two
3
+ // patterns, and the fixture runs (real CI logs) must come out with the
4
+ // features the kiosk shows.
5
+ import { readFileSync, readdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { describe, expect, it } from "vitest";
8
+ import { ANNOTATIONS, DOCS_BASES, PRODUCTS, TOUR, annotationOf, annotationsOf, docsUrl, findLine, jobMarks, runFeatures, stripStamp } from "./kiosk-annotations.js";
9
+
10
+ const FIX = join(import.meta.dirname, "..", "src", "__fixtures__", "terragucci-demo", "runs");
11
+ const runs = readdirSync(FIX).filter((f) => f !== "index.json").map((f) => JSON.parse(readFileSync(join(FIX, f), "utf8")));
12
+ const run = (id) => runs.find((r) => r.id === id);
13
+ const lines = runs.flatMap((r) => r.jobs.flatMap((j) => (j.steps || []).flatMap((s) => s.log || [])));
14
+
15
+ describe("the log annotations' catalogue (behold#571)", () => {
16
+ it("has 48 entries, each with a unique id, a tool, a feature, one sentence, a docs page and a source", () => {
17
+ expect(ANNOTATIONS).toHaveLength(48);
18
+ expect(new Set(ANNOTATIONS.map((a) => a.id)).size).toBe(ANNOTATIONS.length);
19
+ for (const a of ANNOTATIONS) {
20
+ expect(PRODUCTS).toContain(a.product);
21
+ expect(Object.keys(DOCS_BASES)).toContain(a.docs.product);
22
+ expect(a.docs.path).toMatch(/^\/[\w\-/#.]*$/);
23
+ expect(docsUrl(a)).toMatch(/^https:\/\/intentius\.io\/(terragucci|chant|behold|choudoufu)\//);
24
+ expect(a.feature.length).toBeGreaterThan(0);
25
+ expect(a.note.length).toBeLessThanOrEqual(160);
26
+ expect(a.source).toMatch(/^(terragucci|chant|behold|choudoufu):[\w./-]+:\d+$/);
27
+ expect(() => new RegExp(a.pattern)).not.toThrow();
28
+ }
29
+ for (const id of TOUR) expect(ANNOTATIONS.map((a) => a.id)).toContain(id);
30
+ });
31
+
32
+ it("matches every pattern against its own sample, and that sample against no other pattern", () => {
33
+ const bad = ANNOTATIONS.filter((a) => {
34
+ const hits = annotationsOf(a.sample);
35
+ return hits.length !== 1 || hits[0].id !== a.id;
36
+ }).map((a) => `${a.id}: ${annotationsOf(a.sample).map((x) => x.id).join(",") || "no match"}`);
37
+ expect(bad).toEqual([]);
38
+ });
39
+
40
+ it("never matches one fixture line with two patterns", () => {
41
+ const twice = lines.filter((l) => annotationsOf(l).length > 1).map((l) => `${annotationsOf(l).map((a) => a.id).join("+")}: ${l}`);
42
+ expect(twice).toEqual([]);
43
+ });
44
+
45
+ it("knows the held wave's lines in the fixture runs", () => {
46
+ const ids = new Set(lines.map((l) => annotationOf(l)).filter(Boolean).map((a) => a.id));
47
+ for (const id of ["T04", "T05", "T07", "T08", "T09", "T11", "T12", "T13", "T14", "T20", "T21", "T36"]) expect(ids).toContain(id);
48
+ });
49
+
50
+ it("strips a runner's timestamp before matching", () => {
51
+ expect(stripStamp("2026-10-15T20:53:35.5434274Z wave 2 of 3 applied")).toBe("wave 2 of 3 applied");
52
+ expect(annotationOf("2026-10-15T20:53:35.5434274Z wave 2 of 3 applied").id).toBe("T30");
53
+ expect(annotationOf("nothing anyone knows")).toBe(null);
54
+ });
55
+
56
+ it("chips a feature's first line in a job and dots the later ones", () => {
57
+ const job = run(445).jobs.find((j) => j.name === "apply-comment");
58
+ const marks = jobMarks(job.steps);
59
+ const waves = [...marks.entries()].filter(([, m]) => m.a.id === "T04");
60
+ expect(waves.length).toBe(3);
61
+ expect(waves.map(([, m]) => m.first)).toEqual([true, false, false]);
62
+ // Features, not patterns: T30 and T28 are one feature, so only the first of them is a chip.
63
+ const applied = [...marks.values()].filter((m) => m.a.feature === "Apply result");
64
+ expect(applied.filter((m) => m.first)).toHaveLength(1);
65
+ });
66
+
67
+ it("lists the run's features by tool, each with its first line", () => {
68
+ const groups = runFeatures(run(444).jobs);
69
+ expect(groups.map((g) => g.product)).toEqual(["terragucci", "chant"]);
70
+ const t = groups[0].features.find((f) => f.a.id === "T11");
71
+ expect(t).toMatchObject({ job: "apply-wave-2", feature: "Waiting for approval" });
72
+ expect(run(444).jobs.find((j) => j.name === t.job).steps[t.step].log[t.n - 1]).toMatch(/waits for an approval of digest/);
73
+ const hide = (l) => /wave 2/.test(l);
74
+ expect(runFeatures(run(444).jobs, { hide }).flatMap((g) => g.features).some((f) => f.a.id === "T11")).toBe(false);
75
+ });
76
+
77
+ it("finds a run's first line of some patterns", () => {
78
+ const f = findLine(run(445), ["T14"]);
79
+ expect(f).toMatchObject({ job: "apply-comment", a: { id: "T14" } });
80
+ expect(f.text).toMatch(/which is now spent$/);
81
+ expect(findLine(run(443), ["T14"])).toBe(null);
82
+ });
83
+ });