@intentius/chant-lexicon-terraform 0.60.0 → 0.61.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 (40) hide show
  1. package/dist/describe-resources.d.ts +59 -0
  2. package/dist/describe-resources.d.ts.map +1 -1
  3. package/dist/hcl/edges.d.ts +124 -0
  4. package/dist/hcl/edges.d.ts.map +1 -0
  5. package/dist/hcl/parse.d.ts +42 -0
  6. package/dist/hcl/parse.d.ts.map +1 -1
  7. package/dist/hcl/roots.d.ts +6 -0
  8. package/dist/hcl/roots.d.ts.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/integrity.json +2 -2
  12. package/dist/manifest.json +1 -1
  13. package/dist/op/activities/terraform.d.ts +44 -8
  14. package/dist/op/activities/terraform.d.ts.map +1 -1
  15. package/dist/op/adoption.d.ts +110 -35
  16. package/dist/op/adoption.d.ts.map +1 -1
  17. package/package.json +2 -2
  18. package/src/__fixtures__/ACCEPTANCE.md +92 -58
  19. package/src/__fixtures__/graph-roots/README.md +21 -0
  20. package/src/__fixtures__/graph-roots/app/main.tf +41 -0
  21. package/src/__fixtures__/graph-roots/app/modules/cdn/main.tf +7 -0
  22. package/src/__fixtures__/graph-roots/network/main.tf +16 -0
  23. package/src/__fixtures__/live-estate/README.md +65 -19
  24. package/src/__fixtures__/live-estate/adoptable.tf +13 -0
  25. package/src/__fixtures__/live-ls.json +5 -5
  26. package/src/__fixtures__/live-plan.json +67 -35
  27. package/src/composites/terraform-adopt-op.acceptance.test.ts +36 -50
  28. package/src/composites/terraform-apply-op.acceptance.test.ts +4 -2
  29. package/src/describe-resources.live.test.ts +76 -4
  30. package/src/describe-resources.test.ts +35 -0
  31. package/src/describe-resources.ts +110 -3
  32. package/src/hcl/edges.test.ts +207 -0
  33. package/src/hcl/edges.ts +316 -0
  34. package/src/hcl/parse.ts +53 -0
  35. package/src/hcl/roots.ts +14 -0
  36. package/src/index.ts +1 -0
  37. package/src/op/activities/choudoufu.test.ts +3 -3
  38. package/src/op/activities/terraform.ts +70 -21
  39. package/src/op/adoption.test.ts +129 -11
  40. package/src/op/adoption.ts +181 -49
@@ -1,14 +1,42 @@
1
1
  /**
2
2
  * The adoption ledger: which live resources this estate's markers can claim,
3
- * read off `live-plan -json`'s document and rendered as text (#2105).
3
+ * read off `live-plan -json`'s document and rendered as text (#2105, #2241).
4
4
  *
5
5
  * Pure. No filesystem, no child process, no HCL parse — everything here is a
6
- * projection of GitHub issue #788's `unowned` section, which is the one place
7
- * choudoufu states both halves of the answer: the live resource sitting at a
8
- * declared instance's identity, and the `tofu-estate`/`tofu-address` pair that
9
- * would adopt it. Two tags is the whole ownership contract (choudoufu's
10
- * `live/MARKERS.md`), so those two values are the whole of what an adoption
11
- * needs to know.
6
+ * projection of two sections of choudoufu's document, which between them state
7
+ * both halves of the answer: the live resource an adoption would claim, and
8
+ * the `tofu-estate`/`tofu-address` pair that claims it. Two tags is the whole
9
+ * ownership contract (choudoufu's `live/MARKERS.md`), so those two values are
10
+ * the whole of what an adoption needs to know.
11
+ *
12
+ * ## The two sections, and why there are two
13
+ *
14
+ * `unowned[]` (choudoufu issue #788) is a live resource read at an identity
15
+ * the configuration itself declares: a log group's name is in the block, so an
16
+ * unmarked live one with that name is found by reading it.
17
+ *
18
+ * `adoptable[]` (choudoufu issue #962, shipped in v0.15.0) is a live resource
19
+ * the estate-wide sweep matched to a declared instance by content, for a
20
+ * declaration that carries no identity at all: EC2 assigns a VPC's id, so a
21
+ * declared `aws_vpc` lands in `omissions[]` as `NEEDS_DISCOVERY` and the live
22
+ * VPC standing at its `cidr_block` is found by comparing arguments. Each row
23
+ * carries `matched[]`, the arguments that agreed, and `adopt_command`, the
24
+ * paste-ready tagging command, so nothing on this path parses the human
25
+ * render any more. Before v0.15.0 the document had no row for that match at
26
+ * all and chant read the two regexes {@link parseAdoptionCommands} still
27
+ * holds, which is what chant #2168 measured and filed.
28
+ *
29
+ * The two sections are disjoint by construction, so this reads both and keys
30
+ * the union by declared address.
31
+ *
32
+ * ## What the empty ledger means
33
+ *
34
+ * `adoptable[]` and `swept[]` are populated only on a run that asked the
35
+ * estate-wide sweep the account-bounded question (`-adoption-only`, or
36
+ * `TOFU_LIVE_COLLECT_UNCLAIMED=1` on a `-json` run). {@link AdoptionLedger}
37
+ * carries `swept` for exactly that reason: an empty `adoptions` under an empty
38
+ * `swept` is "this run did not look", not "there is nothing to adopt", and
39
+ * {@link renderAdoptionLedger} says which.
12
40
  *
13
41
  * ## Why chant renders a ledger at all
14
42
  *
@@ -18,42 +46,62 @@
18
46
  * says why each unadoptable instance is unadoptable. But `-adoption-only` and
19
47
  * `-json` are refused together (`internal/command/live_plan.go`: "this run
20
48
  * cannot produce both reports at once"), and an Op that reports adoptables
21
- * needs the machine-readable document anyway — for the counts it publishes as
22
- * outcome attributes, and for the addresses an adoption step acts on. So the
49
+ * needs the machine-readable document anyway, for the counts it publishes as
50
+ * outcome attributes and for the addresses an adoption step acts on. So the
23
51
  * ledger below is rendered from the document that run already has, in the row
24
52
  * form `-adoption-only` prints, rather than paying for a third live read.
25
53
  *
26
54
  * ## Ambiguity
27
55
  *
28
- * `unowned[]` is one entry per live resource, keyed by the declared instance
29
- * whose identity found it, so two live resources at one declared identity are
30
- * two entries carrying the same `addr`. That is the ambiguous case: no single
31
- * tag write claims the address, and picking one of the two is a decision about
32
- * the estate rather than something a tool infers. {@link readAdoptionLedger}
33
- * separates those into `contested` and never lets them into `adoptions`, and
34
- * `TerraformAdoptOp` passes the contested list to its Adopt step so the Op's
35
- * result names what it refused as well as what it wrote.
56
+ * Both sections are one entry per live resource, keyed by the declared
57
+ * instance the resource was matched to, so two live resources at one
58
+ * declaration are two entries carrying the same `addr`. That is the ambiguous
59
+ * case: no single tag write claims the address, and picking one of the two is
60
+ * a decision about the estate rather than something a tool infers.
61
+ * {@link readAdoptionLedger} separates those into `contested` and never lets
62
+ * them into `adoptions`, and `TerraformAdoptOp` passes the contested list to
63
+ * its Adopt step so the Op's result names what it refused as well as what it
64
+ * wrote.
36
65
  */
66
+ /** One argument a content match rested on, as choudoufu's `adoptable[].matched[]`. */
67
+ export interface AdoptionMatch {
68
+ /** The argument's name in the declared block, `cidr_block` and the like. */
69
+ attribute: string;
70
+ /** The value both the declaration and the live resource carried. */
71
+ value: string;
72
+ }
37
73
  /** One live resource a marker write would bind to a declared instance. */
38
74
  export interface AdoptionCandidate {
39
- /** The declared instance address whose identity found the live resource. */
75
+ /** The declared instance address the live resource was matched to. */
40
76
  addr: string;
41
- /** The live resource's type, as choudoufu's `unowned[].type`. */
77
+ /** The live resource's type, as choudoufu's `type`. */
42
78
  type: string;
43
- /** The identity the live resource was read with — the handle a human, or a tagging call, needs. */
79
+ /** The identity the live resource was read with: the handle a human, or a tagging call, needs. */
44
80
  identity: string;
45
81
  /** The `tofu-estate` value that adopts it. */
46
82
  markerEstate: string;
47
83
  /** The `tofu-address` value that adopts it, escaped as choudoufu stores it. */
48
84
  markerAddress: string;
49
85
  /**
50
- * The paste-ready tagging command choudoufu printed for this address under
51
- * `live-plan -adoption-only`, when it printed one. Absent for a type whose
52
- * service has its own tagging call this fork does not spell out (IAM,
53
- * Route53, S3 and friends): the two marker values above are still the whole
54
- * contract, but the caller has to write them itself.
86
+ * The paste-ready tagging command choudoufu printed for this address, when
87
+ * it printed one. Absent for a type whose service has its own tagging call
88
+ * this fork does not spell out (IAM, Route53, S3 and friends): the two
89
+ * marker values above are still the whole contract, but the caller has to
90
+ * write them itself.
91
+ *
92
+ * An `adoptable[]` row carries its own (`adopt_command`, choudoufu #962). An
93
+ * `unowned[]` row does not, so a command for one of those comes from the
94
+ * `commands` map {@link parseAdoptionCommands} builds off the human render.
55
95
  */
56
96
  command?: string;
97
+ /**
98
+ * The arguments the declaration and the live resource agreed on exactly, in
99
+ * the order choudoufu's matcher compared them. Present on a content match
100
+ * (`adoptable[]`) and absent on a row found by reading a declared identity
101
+ * (`unowned[]`), which matched on the identity itself and has nothing else
102
+ * to name.
103
+ */
104
+ matched?: AdoptionMatch[];
57
105
  }
58
106
  /** What {@link readAdoptionLedger} found in one `live-plan -json` document. */
59
107
  export interface AdoptionLedger {
@@ -68,20 +116,29 @@ export interface AdoptionLedger {
68
116
  contested: AdoptionCandidate[];
69
117
  /** How many distinct declared addresses are contested. */
70
118
  ambiguous: number;
119
+ /**
120
+ * `swept[]`: every resource type the estate-wide sweep listed in full on the
121
+ * run that produced the document. Empty means the run never asked the
122
+ * account-bounded question, so an empty `adoptions` beside it is silence
123
+ * rather than a finding.
124
+ */
125
+ swept: string[];
71
126
  }
72
127
  /**
73
- * Project a `live-plan -json` document's `unowned` section into the adoptable
74
- * and the contested sets.
128
+ * Project a `live-plan -json` document's `unowned` and `adoptable` sections
129
+ * into the adoptable and the contested sets.
75
130
  *
76
131
  * An entry counts as a candidate only when choudoufu offered both marker
77
- * values. Both empty means adoption was not this run's to offer — the resource
132
+ * values. Both empty means adoption was not this run's to offer: the resource
78
133
  * belongs to another estate (`tofu_estate` names it), or the run had no estate
79
- * name of its own — and such an entry is neither adoptable nor contested here,
134
+ * name of its own, and such an entry is neither adoptable nor contested here,
80
135
  * because there is no tag write to refuse.
81
136
  *
82
137
  * `commands` maps a declared address to the paste-ready tagging command
83
- * choudoufu printed for it, from {@link parseAdoptionCommands}; omit it when
84
- * the run had no `-adoption-only` render to read one out of.
138
+ * choudoufu printed for it, from {@link parseAdoptionCommands}. An
139
+ * `adoptable[]` row carries its own command in the document and never needs
140
+ * the map; omit the map entirely when the run had no `-adoption-only` render
141
+ * to read one out of.
85
142
  */
86
143
  export declare function readAdoptionLedger(document: unknown, commands?: ReadonlyMap<string, string>): AdoptionLedger;
87
144
  /**
@@ -100,6 +157,17 @@ export declare function readAdoptionLedger(document: unknown, commands?: Readonl
100
157
  * choudoufu already wrote.
101
158
  *
102
159
  * An address the render offered no command for is simply absent from the map.
160
+ *
161
+ * ## Which caller still needs this (#2241)
162
+ *
163
+ * One: the `unowned[]` half of {@link readAdoptionLedger}. choudoufu's
164
+ * `views.StatelessUnowned` has no command field, so a live resource found at
165
+ * an identity the configuration declares still gets its paste-ready command
166
+ * from the human render and from nowhere else. The `adoptable[]` half no
167
+ * longer reads a line of text: those rows carry `adopt_command` in the
168
+ * document itself since v0.15.0 (choudoufu #962), which is what made the
169
+ * adopt path stop resting on two regexes over a render nobody promised to
170
+ * keep stable.
103
171
  */
104
172
  export declare function parseAdoptionCommands(ledgerText: string): Map<string, string>;
105
173
  /**
@@ -109,10 +177,17 @@ export declare function parseAdoptionCommands(ledgerText: string): Map<string, s
109
177
  * One line per adoptable match, naming the declared address, the live resource
110
178
  * it binds, and the two tag values that adopt it, in the token forms
111
179
  * `-adoption-only` prints them in (`<addr> <- <type> <identity>`,
112
- * `tofu-estate=`, `tofu-address=`). Contested addresses follow, listed and not
113
- * offered. An empty ledger still renders a line: "nothing adoptable" is a
114
- * result a reader wants, and a section that vanishes reads as one that was
115
- * never computed.
180
+ * `tofu-estate=`, `tofu-address=`). A content match adds the arguments it
181
+ * rested on under its row, in the same `matched on:` form the human render
182
+ * uses, because "these two agreed on this cidr" is the whole evidence for a
183
+ * match nobody read an identity for. Contested addresses follow, listed and
184
+ * not offered.
185
+ *
186
+ * An empty ledger still renders a line: "nothing adoptable" is a result a
187
+ * reader wants, and a section that vanishes reads as one that was never
188
+ * computed. Which of the two empties it is comes from `swept`: a run that
189
+ * asked no estate-wide sweep found nothing because it did not look, and
190
+ * saying so is the difference between a report and a silence.
116
191
  */
117
192
  export declare function renderAdoptionLedger(ledger: AdoptionLedger, estate?: string): string;
118
193
  //# sourceMappingURL=adoption.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adoption.d.ts","sourceRoot":"","sources":["../../src/op/adoption.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAcD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,cAAc,CAqC5G;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoB7E;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgDpF"}
1
+ {"version":3,"file":"adoption.d.ts","sourceRoot":"","sources":["../../src/op/adoption.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAsCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,cAAc,CA2D5G;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoB7E;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CA0DpF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-terraform",
3
- "version": "0.60.0",
3
+ "version": "0.61.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "peerDependencies": {
58
58
  "zod": "^4.3.6",
59
- "@intentius/chant": "^0.60.0",
59
+ "@intentius/chant": "^0.61.0",
60
60
  "typescript": "^5.9.3"
61
61
  },
62
62
  "description": "terraform lexicon for chant, existing Terraform root modules as declarable entities",
@@ -16,12 +16,14 @@ and stays where it is.
16
16
 
17
17
  ## The binary and the emulator these rows name
18
18
 
19
- `choudoufu v0.14.0` below means the published release binary, not a source
20
- build: `gh release download v0.14.0 -R INTENTIUS/choudoufu -p
21
- 'choudoufu_v0.14.0_darwin_arm64.tar.gz' -p SHA256SUMS`, verified against
22
- `SHA256SUMS` (`41c705d9b5fec47100c4f2fb9ab0694f0160b31a4e0661e877b4d9821bc464e3`),
23
- extracted and put first on PATH. `choudoufu version` prints `choudoufu v0.14.0
24
- (based on OpenTofu v1.13.0-dev)`.
19
+ `choudoufu v0.15.0` below means the published release binary, not a source
20
+ build: `gh release download v0.15.0 -R INTENTIUS/choudoufu -p
21
+ 'choudoufu_v0.15.0_darwin_arm64.tar.gz' -p SHA256SUMS`, verified against
22
+ `SHA256SUMS` (`db29573cb7d8dfa7205eeaecc0e4bcf154e941b8bb8179fb93ad53ac5c959409`),
23
+ extracted and put first on PATH. `choudoufu version` prints `choudoufu v0.15.0
24
+ (based on OpenTofu v1.13.0-dev)`. A row that still names `v0.14.0` means the
25
+ same recipe one tag back
26
+ (`41c705d9b5fec47100c4f2fb9ab0694f0160b31a4e0661e877b4d9821bc464e3`).
25
27
 
26
28
  `the pinned floci emulator` means choudoufu's own smoke stack image,
27
29
  `ghcr.io/lex00/floci@sha256:a39185cc3971d0188663d61043cb038dff1260d8a975b1aa72c4e2bb1feac3cb`
@@ -47,10 +49,11 @@ resolvable).
47
49
  npx vitest run lexicons/terraform/src/composites/terraform-apply-op.acceptance.test.ts
48
50
  ```
49
51
 
50
- Last passed: 2026-09-07, `Terraform v1.15.8` on `darwin_arm64`, with
52
+ Last passed: 2026-09-08, `Terraform v1.15.8` on `darwin_arm64`, with
51
53
  `hashicorp/null` resolved from the public registry for the fixture's `~> 3.2`
52
- constraint. No emulator is involved. Re-run by chant #2168 alongside the two
53
- live blocks in the same file, so the file's three rows all come from one run.
54
+ constraint. No emulator is involved. Re-run by chant #2241 alongside every
55
+ other block in this file, so all four rows come from one run; #2168 had run it
56
+ on 2026-09-07 the same way.
54
57
 
55
58
  ### 2. `TerraformApplyOp applies a live root against choudoufu's emulator`
56
59
 
@@ -63,21 +66,22 @@ carrying choudoufu's own message rather than as thrown errors, then applies
63
66
  the same file once the world is restored.
64
67
 
65
68
  Gated on a `choudoufu` on PATH, on it reporting a release version at or above
66
- `MIN_CHOUDOUFU_VERSION` (0.14.0), and on `CHOUDOUFU_EMULATOR_ENDPOINT`. Not
67
- gated on choudoufu #894 and never was: the plan half here is the stock `plan
68
- -out` path and reads no JSON document.
69
+ `MIN_CHOUDOUFU_VERSION` (0.15.0 since chant #2241), and on
70
+ `CHOUDOUFU_EMULATOR_ENDPOINT`. Not gated on choudoufu #894 and never was: the
71
+ plan half here is the stock `plan -out` path and reads no JSON document.
69
72
 
70
73
  ```
71
74
  npx vitest run lexicons/terraform/src/composites/terraform-apply-op.acceptance.test.ts
72
75
  ```
73
76
 
74
- Last passed: 2026-09-07, choudoufu v0.14.0 with the pinned floci emulator up,
75
- recorded by chant #2168. That run is reproducible from this file: the binary
76
- is a release asset and the emulator is a pinned digest. Before it, the only
77
- record was PR #2157's prose ("the acceptance suite 3 passed against v0.13.0
78
- with the emulator up", merged at `81200b5d97c15920619de4b109c4393ec3223720`),
79
- which #2220 could not reproduce because no binary existed on the machine that
80
- wrote this file.
77
+ Last passed: 2026-09-08, choudoufu v0.15.0 with the pinned floci emulator up,
78
+ recorded by chant #2241; before that 2026-09-07 on v0.14.0, recorded by chant
79
+ #2168. Both runs are reproducible from this file: the binary is a release
80
+ asset and the emulator is a pinned digest. Before them, the only record was PR
81
+ #2157's prose ("the acceptance suite 3 passed against v0.13.0 with the
82
+ emulator up", merged at `81200b5d97c15920619de4b109c4393ec3223720`), which
83
+ #2220 could not reproduce because no binary existed on the machine that wrote
84
+ this file.
81
85
 
82
86
  ### 3. `choudoufu live-check and live-plan against the fixture`
83
87
 
@@ -94,12 +98,12 @@ PATH is gone.
94
98
  npx vitest run lexicons/terraform/src/op/activities/choudoufu.acceptance.test.ts
95
99
  ```
96
100
 
97
- Last passed: 2026-09-07, choudoufu v0.14.0 with the pinned floci emulator up,
98
- recorded by chant #2168. That is the first pass this block has ever had as a
99
- suite, and the first the `live-plan` test has ever had at all. It had been
100
- gated shut on choudoufu #894 since commit `65821d76` (2026-09-06 12:45Z);
101
- before that gate, PR #2135's body records a hand verification of the
102
- `live-check` half only.
101
+ Last passed: 2026-09-08, choudoufu v0.15.0 with the pinned floci emulator up,
102
+ recorded by chant #2241; before that 2026-09-07 on v0.14.0, recorded by chant
103
+ #2168, which was the first pass this block ever had as a suite and the first
104
+ the `live-plan` test ever had at all. It had been gated shut on choudoufu #894
105
+ since commit `65821d76` (2026-09-06 12:45Z); before that gate, PR #2135's body
106
+ records a hand verification of the `live-check` half only.
103
107
 
104
108
  Two chant-side changes in #2168 were what the pass needed, both consequences
105
109
  of what choudoufu PR 915 shipped. `-estate` is still refused beside a declared
@@ -112,51 +116,59 @@ first real run of this block found.
112
116
  ### 4. `TerraformAdoptOp adopts an unmarked live resource`
113
117
 
114
118
  `composites/terraform-adopt-op.acceptance.test.ts`. One test: create an
115
- unmarked VPC directly against the emulator so it is a live resource this
116
- estate does not own at an identity the `live-adopt/` root declares, run the
117
- Ledger step and expect exactly one adoptable match carrying the two marker
118
- values, run the Adopt step and let it write them, then re-plan and expect the
119
- estate to own the same VPC with nothing left adoptable.
119
+ unmarked VPC directly against the emulator at the cidr the `live-adopt/` root
120
+ declares, so it is a live resource this estate does not own and the sweep can
121
+ match to a declaration by content, run the Ledger step and expect exactly one
122
+ adoptable match carrying the two marker values, run the Adopt step and let it
123
+ write them, then re-plan and expect the estate to own the same VPC with
124
+ nothing left adoptable.
120
125
 
121
126
  Gated on a `choudoufu` on PATH, on an `aws` CLI on PATH (the unmarked
122
- resource is created and adopted through it), on
123
- `CHOUDOUFU_EMULATOR_ENDPOINT`, and then on
124
- `CHOUDOUFU_ADOPTABLE_NOT_IN_DOCUMENT`, which replaced the #894 gate and names
125
- choudoufu #962.
127
+ resource is created and adopted through it), and on
128
+ `CHOUDOUFU_EMULATOR_ENDPOINT`. Those three and nothing else since chant
129
+ #2241: the `CHOUDOUFU_ADOPTABLE_NOT_IN_DOCUMENT` constant that named choudoufu
130
+ #962, and the #894 gate before it, are both gone.
126
131
 
127
132
  ```
128
133
  npx vitest run lexicons/terraform/src/composites/terraform-adopt-op.acceptance.test.ts
129
134
  ```
130
135
 
131
- Last passed: never, and the reason changed on 2026-09-07 rather than going
132
- away. chant #2168 dropped the #894 gate and ran the block against choudoufu
133
- v0.14.0 and the pinned floci emulator. It reached the document and stopped
134
- there, with `ledger.adoptions` empty:
136
+ Last passed: 2026-09-08, choudoufu v0.15.0 with the pinned floci emulator up,
137
+ recorded by chant #2241. **That is the first time this block has ever passed,
138
+ on any binary.**
139
+
140
+ ```
141
+ ✓ TerraformAdoptOp adopts an unmarked live resource > ledgers one adoptable
142
+ VPC, writes its two markers, and re-plans with it owned 18614ms
143
+ ```
144
+
145
+ What it needed was choudoufu PR 963, released in v0.15.0. `live-plan -json`'s
146
+ document now carries the estate-wide sweep's content matches as an
147
+ `adoptable[]` section, with `swept[]` beside it, and each row carries the two
148
+ marker values, the arguments the match rested on, and the tagging command that
149
+ writes them. Before that the match existed only in the human `-adoption-only`
150
+ render, which choudoufu refuses alongside `-json`, so an `aws_vpc` (EC2
151
+ assigns the id, so no argument in the block determines it) reached
152
+ `omissions[].reason = "NEEDS_DISCOVERY"` and never `unowned[]`, and
153
+ `ledger.adoptions` came back empty:
135
154
 
136
155
  ```
137
156
  AssertionError: expected [] to have a length of 1 but got +0
138
157
  ❯ terraform-adopt-op.acceptance.test.ts:116:34
139
158
  ```
140
159
 
141
- The document's `unowned[]` is the resources found at an identity the
142
- configuration itself declares, and it works: an unmarked
143
- `aws_cloudwatch_log_group` in the same fixture shape comes back on the same
144
- binary with `adopt_tofu_estate` and `adopt_tofu_address` on it, which is the
145
- shape `../live-plan.json` recorded. An `aws_vpc` has no such identity, so the
146
- document reports `omissions[].reason = "NEEDS_DISCOVERY"` and leaves
147
- `unowned[]` empty. The VPC is matched by choudoufu's content matcher during
148
- the estate-wide unclaimed sweep instead, and that match is printed only in the
149
- human render's "Adoptable" section, for which `views.LivePlanDocument` has no
150
- field. `-adoption-only` is refused alongside `-json`, and
151
- `TOFU_LIVE_COLLECT_UNCLAIMED=1` on the `-json` run leaves `"unowned": []`
152
- while the text run beside it prints `Adoptable: 1 live resource matches a
153
- declared resource`. Filed upstream as choudoufu #962
154
- (https://github.com/INTENTIUS/choudoufu/issues/962), which carries the full
155
- measurement; chant #2168 has the same.
160
+ That was chant #2168's measurement on 2026-09-07 against v0.14.0, filed
161
+ upstream as choudoufu #962
162
+ (https://github.com/INTENTIUS/choudoufu/issues/962), which carries the whole
163
+ of it. Two chant-side changes in #2241 were what the pass needed on top of the
164
+ release: `choudoufuLivePlan` puts `TOFU_LIVE_COLLECT_UNCLAIMED=1` on the
165
+ `-json` run under `adoptionOnly`, because a `-json` run asks no estate-wide
166
+ sweep of its own and the section is empty without one; and
167
+ `readAdoptionLedger` reads both `unowned[]` and `adoptable[]` into one ledger.
156
168
 
157
169
  The fixture was deliberately left as an `aws_vpc`. A log group would make the
158
- block pass and would stop it proving the content-matcher path, which is the
159
- only thing it exists to prove.
170
+ block pass off `unowned[]` alone and would stop it proving the content-matcher
171
+ path, which is the only thing it exists to prove.
160
172
 
161
173
  ## Running everything that can run on a machine with no choudoufu
162
174
 
@@ -169,9 +181,31 @@ npx vitest run \
169
181
 
170
182
  With `terraform` on PATH and the registry reachable, that is 1 passed and 5
171
183
  skipped, and each skip names its own reason in the block title. With
172
- choudoufu v0.14.0, the `aws` CLI and the emulator all present, it is 5 passed
173
- and 1 skipped: block 4 is the skip. On 2026-09-07 that is exactly what it
174
- printed.
184
+ choudoufu v0.15.0, the `aws` CLI and the emulator all present, it is 6 passed
185
+ and 0 skipped: no block in this file skips any more. On 2026-09-08 that is
186
+ exactly what it printed.
187
+
188
+ ```
189
+ Test Files 3 passed (3)
190
+ Tests 6 passed (6)
191
+ ```
192
+
193
+ Bring the emulator up on a fixed port from choudoufu's own compose file rather
194
+ than through `just smoke`, which runs one scenario and tears the stack down
195
+ again:
196
+
197
+ ```
198
+ cd <choudoufu checkout>
199
+ FLOCI_IMAGE="$(cat live/floci-image)" FLOCI_PORT=4660 \
200
+ OPENTOFU_IMAGE=unused SMOKE_WORK=/tmp \
201
+ docker compose -p chant-acceptance -f live/smoke/docker-compose.yml up -d floci
202
+ export CHOUDOUFU_EMULATOR_ENDPOINT=http://localhost:4660
203
+ ```
204
+
205
+ Each run wants a fresh emulator: the adopt block creates an unmarked VPC at a
206
+ fixed cidr, and a second one left over from an earlier run is a contested
207
+ address rather than an adoptable match, which is a real refusal and not a
208
+ flake. `docker compose -p chant-acceptance ... down -v` between runs.
175
209
 
176
210
  ## Keeping this file honest
177
211
 
@@ -0,0 +1,21 @@
1
+ # graph-roots
2
+
3
+ Two root modules with real references between their blocks, for the graph-IR
4
+ edge and grouping tests (chant #2265, #2266).
5
+
6
+ Everything the two issues asked to have pinned by a test rather than by prose
7
+ is here exactly once:
8
+
9
+ - every reference form that becomes an edge: `resource`, `data`, `module`,
10
+ `var` and `local`
11
+ - a `depends_on`, which is an edge with no attribute
12
+ - a `count` block, which stays ONE node and one edge per target
13
+ - a local child module, so a reference resolves inside the child's own scope
14
+ and never against the root's
15
+ - a `provider = aws.replica` meta-argument, which is deliberately NOT an edge
16
+ - two roots that share nothing, which is what a cross-root edge would have to
17
+ invent
18
+
19
+ `app` reads a policy by NAME through a data source rather than by reference,
20
+ which is how the estate these issues were filed against composes its roots.
21
+ No edge should appear between the two roots because of it.
@@ -0,0 +1,41 @@
1
+ variable "region" {
2
+ type = string
3
+ }
4
+
5
+ variable "subnets" {
6
+ type = list(string)
7
+ }
8
+
9
+ provider "aws" {
10
+ alias = "replica"
11
+ region = var.region
12
+ }
13
+
14
+ data "aws_iam_policy" "boundary" {
15
+ name = "waterpark-boundary"
16
+ }
17
+
18
+ resource "aws_s3_bucket" "assets" {
19
+ provider = aws.replica
20
+ bucket = "app-assets"
21
+ }
22
+
23
+ module "cdn" {
24
+ source = "./modules/cdn"
25
+
26
+ bucket = aws_s3_bucket.assets.id
27
+ }
28
+
29
+ resource "aws_instance" "web" {
30
+ count = length(var.subnets)
31
+ subnet_id = var.subnets[count.index]
32
+ depends_on = [aws_s3_bucket.assets, module.cdn]
33
+
34
+ tags = {
35
+ boundary = data.aws_iam_policy.boundary.arn
36
+ }
37
+ }
38
+
39
+ output "cdn_url" {
40
+ value = module.cdn.url
41
+ }
@@ -0,0 +1,7 @@
1
+ variable "bucket" {
2
+ type = string
3
+ }
4
+
5
+ resource "aws_cloudfront_distribution" "cdn" {
6
+ origin_id = var.bucket
7
+ }
@@ -0,0 +1,16 @@
1
+ variable "cidr" {
2
+ type = string
3
+ }
4
+
5
+ locals {
6
+ base_tags = { Team = "platform" }
7
+ }
8
+
9
+ resource "aws_vpc" "main" {
10
+ cidr_block = var.cidr
11
+ tags = local.base_tags
12
+ }
13
+
14
+ output "vpc_id" {
15
+ value = aws_vpc.main.id
16
+ }