@intentius/behold 0.9.0 → 0.9.1
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/README.md +5 -4
- package/dist/cli.js +32 -22
- package/example-argo-estate/app-a/package.json +2 -2
- package/example-argo-estate/app-b/package.json +2 -2
- package/example-argo-estate/control-plane/package.json +2 -2
- package/example-argo-estate/package-lock.json +20 -20
- package/example-carve/README.md +74 -47
- package/example-carve/app/package-lock.json +9 -9
- package/example-carve/app/package.json +2 -2
- package/example-carve/carve-report.json +140 -50
- package/example-carve/legacy-tf/outputs.tf +9 -4
- package/example-flux-estate/app-a/package.json +2 -2
- package/example-flux-estate/app-b/package.json +2 -2
- package/example-flux-estate/control-plane/package.json +2 -2
- package/example-flux-estate/package-lock.json +20 -20
- package/example-k8s/package-lock.json +21 -21
- package/example-k8s/package.json +3 -3
- package/example-writes/package-lock.json +14 -14
- package/example-writes/package.json +3 -3
- package/package.json +1 -1
- package/web/carve-steps.js +12 -1
- package/web/carve-steps.test.js +15 -0
package/README.md
CHANGED
|
@@ -317,10 +317,11 @@ stepper on the panel's **Carve** tab:
|
|
|
317
317
|
6. **Done** — the card is marked chant-owned at the observe position;
|
|
318
318
|
`terraform import` reverses all of it.
|
|
319
319
|
|
|
320
|
-
The Emit step reports `chant lint`, not `chant build`:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
320
|
+
The Emit step reports `chant lint`, not `chant build`: `build` fails on the
|
|
321
|
+
emitted bucket (scored 84 by the advisor) on one rule — WAW042, a TLS-deny
|
|
322
|
+
bucket policy the source Terraform never declared — and the panel links the
|
|
323
|
+
reason. `example-carve/README.md` has the estate's full story, the band table,
|
|
324
|
+
and the offline/`--live` split.
|
|
324
325
|
|
|
325
326
|
behold writes only into the demo copy it made — `app/carveout/`, and nothing
|
|
326
327
|
else. Your Terraform is never edited; see AGENTS.md, "Invariant".
|
package/dist/cli.js
CHANGED
|
@@ -4052,6 +4052,7 @@ function scoreArithmetic(r) {
|
|
|
4052
4052
|
const terms = [];
|
|
4053
4053
|
if (p.inbound) terms.push(`- 12x${b.inbound ?? 0} inbound`);
|
|
4054
4054
|
if (p.outbound) terms.push(`- 4x${b.outbound ?? 0} outbound`);
|
|
4055
|
+
if (p.outputs) terms.push(`- 4x${b.outputs ?? 0} output`);
|
|
4055
4056
|
if (p.tier) terms.push(`- 15x${(b.tier ?? 1) - 1} tier${b.tier}`);
|
|
4056
4057
|
if (p.dynamic) terms.push(`- 10 dynamic`);
|
|
4057
4058
|
if (p.instances) terms.push(`- 3x${(b.instances ?? 1) - 1} instances`);
|
|
@@ -4066,6 +4067,7 @@ function boundaryWorkOf(r) {
|
|
|
4066
4067
|
const parts = [];
|
|
4067
4068
|
if (b.inbound) parts.push(`${b.inbound} inbound (a data-source patch each)`);
|
|
4068
4069
|
if (b.outbound) parts.push(`${b.outbound} outbound (a deferred input each)`);
|
|
4070
|
+
if (b.outputs) parts.push(`${b.outputs} output block(s) reading it (a one-line rewrite each)`);
|
|
4069
4071
|
if ((b.tier ?? 1) > 1) parts.push(`tier ${b.tier} map`);
|
|
4070
4072
|
if (b.hasDynamic) parts.push("count/for_each/data present");
|
|
4071
4073
|
if ((b.instances ?? 1) > 1) parts.push(`${b.instances} instances`);
|
|
@@ -4106,6 +4108,7 @@ function carveReportToIr(report) {
|
|
|
4106
4108
|
tier: b.tier === null || b.tier === void 0 ? "none" : b.tier,
|
|
4107
4109
|
inbound: b.inbound ?? 0,
|
|
4108
4110
|
outbound: b.outbound ?? 0,
|
|
4111
|
+
outputs: b.outputs ?? 0,
|
|
4109
4112
|
instances: b.instances ?? 1,
|
|
4110
4113
|
dynamic: b.hasDynamic ?? false,
|
|
4111
4114
|
// The predicted diff, when the report carries the edge lists: who needs
|
|
@@ -4596,7 +4599,7 @@ var refuse2 = (code, error, remedy) => ({
|
|
|
4596
4599
|
ok: false,
|
|
4597
4600
|
refusal: { error, code, remedy }
|
|
4598
4601
|
});
|
|
4599
|
-
var BUILD_CAVEAT =
|
|
4602
|
+
var BUILD_CAVEAT = "The gate shown is `chant lint`, not `chant build`. chant#1637's fold is applied now: the bucket's versioning and public-access-block sub-resources come out as native props, not just folded-in mentions. `chant build` still fails, but on one rule only \u2014 WAW042, a TLS-deny bucket policy the source Terraform never declared. That's real drift the estate inherited, not a gap in the carve. example-carve/README.md, \"Why build still fails\", has the full statement.";
|
|
4600
4603
|
function insideDemo(root, p) {
|
|
4601
4604
|
const rel = relative(resolve2(root), resolve2(p));
|
|
4602
4605
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
@@ -5324,6 +5327,7 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
5324
5327
|
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
5325
5328
|
import { join as join11 } from "node:path";
|
|
5326
5329
|
import { platform } from "node:os";
|
|
5330
|
+
var DOCKER_DOWN_DETAIL = "docker is down";
|
|
5327
5331
|
function probe(cmd, args) {
|
|
5328
5332
|
return new Promise((resolve8) => {
|
|
5329
5333
|
let out = "";
|
|
@@ -5340,12 +5344,12 @@ function probe(cmd, args) {
|
|
|
5340
5344
|
proc.on("close", (code) => resolve8({ code: code ?? 1, out }));
|
|
5341
5345
|
});
|
|
5342
5346
|
}
|
|
5343
|
-
async function dockerAvailable() {
|
|
5344
|
-
const { code } = await
|
|
5347
|
+
async function dockerAvailable(run3) {
|
|
5348
|
+
const { code } = await run3("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
5345
5349
|
return code === 0;
|
|
5346
5350
|
}
|
|
5347
|
-
async function dockerRunning(nameFilter) {
|
|
5348
|
-
const { code, out } = await
|
|
5351
|
+
async function dockerRunning(run3, nameFilter) {
|
|
5352
|
+
const { code, out } = await run3("docker", ["ps", "--filter", `name=${nameFilter}`, "--format", "{{.Names}}"]);
|
|
5349
5353
|
if (code !== 0) return [];
|
|
5350
5354
|
return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
5351
5355
|
}
|
|
@@ -5362,10 +5366,11 @@ function projectLexicons(projectDir) {
|
|
|
5362
5366
|
return [];
|
|
5363
5367
|
}
|
|
5364
5368
|
}
|
|
5365
|
-
async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
5369
|
+
async function detectSubstrates(projectDir, preview = false, boundContext, probes = {}) {
|
|
5370
|
+
const run3 = probes.probe ?? probe;
|
|
5366
5371
|
const subs = [];
|
|
5367
5372
|
const lexicons = projectLexicons(projectDir);
|
|
5368
|
-
const docker = await dockerAvailable();
|
|
5373
|
+
const docker = await dockerAvailable(run3);
|
|
5369
5374
|
subs.push({
|
|
5370
5375
|
name: "docker",
|
|
5371
5376
|
label: "Docker",
|
|
@@ -5373,9 +5378,10 @@ async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
|
5373
5378
|
detail: docker ? "daemon running" : "daemon not running",
|
|
5374
5379
|
bringUp: docker || platform() !== "darwin" ? void 0 : { label: "open -a Docker", cmd: "open", args: ["-a", "Docker"] }
|
|
5375
5380
|
});
|
|
5376
|
-
const
|
|
5381
|
+
const DOCKER_BLOCKED = { status: "blocked", detail: DOCKER_DOWN_DETAIL };
|
|
5382
|
+
const dep = (up, upDetail, offDetail) => !docker ? DOCKER_BLOCKED : { status: up ? "up" : offDetail === "on-demand (pipeline run)" ? "on-demand" : "down", detail: up ? upDetail : offDetail };
|
|
5377
5383
|
if (lexicons.includes("aws")) {
|
|
5378
|
-
const floci = docker ? await dockerRunning("^floci$|^chant-floci$") : [];
|
|
5384
|
+
const floci = docker ? await dockerRunning(run3, "^floci$|^chant-floci$") : [];
|
|
5379
5385
|
const d = dep(floci.length > 0, "container up on :4566", "not running");
|
|
5380
5386
|
subs.push({
|
|
5381
5387
|
name: "floci",
|
|
@@ -5390,7 +5396,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
|
5390
5396
|
];
|
|
5391
5397
|
for (const [lexicon, name, label, pattern, port] of emulators) {
|
|
5392
5398
|
if (!lexicons.includes(lexicon)) continue;
|
|
5393
|
-
const running = docker ? await dockerRunning(pattern) : [];
|
|
5399
|
+
const running = docker ? await dockerRunning(run3, pattern) : [];
|
|
5394
5400
|
subs.push({
|
|
5395
5401
|
name,
|
|
5396
5402
|
label,
|
|
@@ -5405,7 +5411,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
|
5405
5411
|
];
|
|
5406
5412
|
for (const [name, label, marker, script] of forges) {
|
|
5407
5413
|
if (!existsSync9(join11(projectDir, marker))) continue;
|
|
5408
|
-
const c = docker ? await dockerRunning(name) : [];
|
|
5414
|
+
const c = docker ? await dockerRunning(run3, name) : [];
|
|
5409
5415
|
const d = dep(c.length > 0, "container up", "on-demand (pipeline run)");
|
|
5410
5416
|
subs.push({
|
|
5411
5417
|
name,
|
|
@@ -5415,15 +5421,19 @@ async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
|
5415
5421
|
});
|
|
5416
5422
|
}
|
|
5417
5423
|
if (lexicons.includes("k8s")) {
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5424
|
+
if (!docker) {
|
|
5425
|
+
subs.push({ name: "k3d", label: "k3d", ...DOCKER_BLOCKED });
|
|
5426
|
+
} else {
|
|
5427
|
+
const k3d = await run3("k3d", ["cluster", "list", "--no-headers"]);
|
|
5428
|
+
const clusters = k3d.code === 0 ? k3d.out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean) : [];
|
|
5429
|
+
subs.push({
|
|
5430
|
+
name: "k3d",
|
|
5431
|
+
label: "k3d",
|
|
5432
|
+
status: k3d.code === 127 ? "unknown" : clusters.length ? "up" : "down",
|
|
5433
|
+
detail: k3d.code === 127 ? "k3d not installed" : clusters.length ? `${clusters.length} cluster(s)` : "no clusters",
|
|
5434
|
+
bringUp: k3d.code !== 127 && !clusters.length ? scriptBringUp(projectDir, "scripts/local/local-up.sh", "local-up") : void 0
|
|
5435
|
+
});
|
|
5436
|
+
}
|
|
5427
5437
|
}
|
|
5428
5438
|
if (lexicons.includes("fly")) {
|
|
5429
5439
|
const endpoint = process.env.FLY_FLAPS_BASE_URL;
|
|
@@ -5435,7 +5445,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
|
5435
5445
|
});
|
|
5436
5446
|
}
|
|
5437
5447
|
if (existsSync9(join11(projectDir, ".github", "workflows"))) {
|
|
5438
|
-
const gh = await
|
|
5448
|
+
const gh = await run3("gh", ["auth", "status"]);
|
|
5439
5449
|
const ready = gh.code === 0;
|
|
5440
5450
|
subs.push({
|
|
5441
5451
|
name: "github",
|
|
@@ -5458,7 +5468,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext) {
|
|
|
5458
5468
|
});
|
|
5459
5469
|
}
|
|
5460
5470
|
if (lexicons.includes("helm")) {
|
|
5461
|
-
const helm = await
|
|
5471
|
+
const helm = await run3("helm", ["version", "--short"]);
|
|
5462
5472
|
const ambient = helm.code === 0 && !boundContext ? await ambientContext() : void 0;
|
|
5463
5473
|
const ctx = boundContext ?? ambient ?? "";
|
|
5464
5474
|
subs.push({
|
|
@@ -14,22 +14,22 @@
|
|
|
14
14
|
"app-a": {
|
|
15
15
|
"name": "behold-example-argo-app-a",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@intentius/chant": "^0.44.
|
|
18
|
-
"@intentius/chant-lexicon-k8s": "^0.44.
|
|
17
|
+
"@intentius/chant": "^0.44.7",
|
|
18
|
+
"@intentius/chant-lexicon-k8s": "^0.44.7"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"app-b": {
|
|
22
22
|
"name": "behold-example-argo-app-b",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@intentius/chant": "^0.44.
|
|
25
|
-
"@intentius/chant-lexicon-k8s": "^0.44.
|
|
24
|
+
"@intentius/chant": "^0.44.7",
|
|
25
|
+
"@intentius/chant-lexicon-k8s": "^0.44.7"
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"control-plane": {
|
|
29
29
|
"name": "behold-example-argo-control-plane",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@intentius/chant": "^0.44.
|
|
32
|
-
"@intentius/chant-lexicon-k8s": "^0.44.
|
|
31
|
+
"@intentius/chant": "^0.44.7",
|
|
32
|
+
"@intentius/chant-lexicon-k8s": "^0.44.7"
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"node_modules/@dagrejs/dagre": {
|
|
@@ -464,9 +464,9 @@
|
|
|
464
464
|
}
|
|
465
465
|
},
|
|
466
466
|
"node_modules/@intentius/chant": {
|
|
467
|
-
"version": "0.44.
|
|
468
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.
|
|
469
|
-
"integrity": "sha512-
|
|
467
|
+
"version": "0.44.7",
|
|
468
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.7.tgz",
|
|
469
|
+
"integrity": "sha512-etCchkfbWFHJ+XnTYVVn9GT4/gZi/O8SVphDXHeuPOfK5VvRxo0unveIUEsuRoHGHgzN9Y+aj0Jk15xhIE7Gzg==",
|
|
470
470
|
"license": "Apache-2.0",
|
|
471
471
|
"dependencies": {
|
|
472
472
|
"@dagrejs/dagre": "^3.0.0",
|
|
@@ -482,9 +482,9 @@
|
|
|
482
482
|
}
|
|
483
483
|
},
|
|
484
484
|
"node_modules/@intentius/chant-k8s-client": {
|
|
485
|
-
"version": "0.44.
|
|
486
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-k8s-client/-/chant-k8s-client-0.44.
|
|
487
|
-
"integrity": "sha512-
|
|
485
|
+
"version": "0.44.7",
|
|
486
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-k8s-client/-/chant-k8s-client-0.44.7.tgz",
|
|
487
|
+
"integrity": "sha512-G9VVki/qHZ+hbKrxDDnVVhfeZWoHgozLA7tjcY/lsa/fDIwI0xMJnAN/DJNcQLBpRscXm3y3Ki/PNQ9SxshmLA==",
|
|
488
488
|
"license": "Apache-2.0",
|
|
489
489
|
"optional": true,
|
|
490
490
|
"dependencies": {
|
|
@@ -492,19 +492,19 @@
|
|
|
492
492
|
}
|
|
493
493
|
},
|
|
494
494
|
"node_modules/@intentius/chant-lexicon-k8s": {
|
|
495
|
-
"version": "0.44.
|
|
496
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-k8s/-/chant-lexicon-k8s-0.44.
|
|
497
|
-
"integrity": "sha512-
|
|
495
|
+
"version": "0.44.7",
|
|
496
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-k8s/-/chant-lexicon-k8s-0.44.7.tgz",
|
|
497
|
+
"integrity": "sha512-h8QXOYoxjsAeA1w8koLQlZqOuSQcvWpEHKmwlf0AKPgwuVRiapwJI+UtuoBwL/AOrBKDh6Z5pW3zbo6vDltGvA==",
|
|
498
498
|
"license": "Apache-2.0",
|
|
499
499
|
"dependencies": {
|
|
500
500
|
"@types/js-yaml": "^4.0.9",
|
|
501
501
|
"js-yaml": "^4.1.1"
|
|
502
502
|
},
|
|
503
503
|
"optionalDependencies": {
|
|
504
|
-
"@intentius/chant-k8s-client": "^0.44.
|
|
504
|
+
"@intentius/chant-k8s-client": "^0.44.7"
|
|
505
505
|
},
|
|
506
506
|
"peerDependencies": {
|
|
507
|
-
"@intentius/chant": "^0.44.
|
|
507
|
+
"@intentius/chant": "^0.44.7",
|
|
508
508
|
"typescript": "^5.9.3",
|
|
509
509
|
"zod": "^4.3.6"
|
|
510
510
|
}
|
|
@@ -1071,9 +1071,9 @@
|
|
|
1071
1071
|
}
|
|
1072
1072
|
},
|
|
1073
1073
|
"node_modules/ip-address": {
|
|
1074
|
-
"version": "10.
|
|
1075
|
-
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.
|
|
1076
|
-
"integrity": "sha512-
|
|
1074
|
+
"version": "10.5.0",
|
|
1075
|
+
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
|
|
1076
|
+
"integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
|
|
1077
1077
|
"license": "MIT",
|
|
1078
1078
|
"optional": true,
|
|
1079
1079
|
"engines": {
|
package/example-carve/README.md
CHANGED
|
@@ -49,7 +49,7 @@ end of the walkthrough has somewhere to land.
|
|
|
49
49
|
|
|
50
50
|
## Bands
|
|
51
51
|
|
|
52
|
-
Real output, from `chant carve advise` at chant 0.44.
|
|
52
|
+
Real output, from `chant carve advise` at chant 0.44.7. Reproduce it with:
|
|
53
53
|
|
|
54
54
|
```sh
|
|
55
55
|
npm install -D @cdktf/hcl2json # once, anywhere on your PATH resolution
|
|
@@ -60,35 +60,45 @@ chant carve advise --from example-carve/legacy-tf \
|
|
|
60
60
|
| score | address | band | why | role in the script |
|
|
61
61
|
|---|---|---|---|---|
|
|
62
62
|
| 100 | `aws_cloudwatch_log_group.worker` | clean leaf | clean 1:1 native map, no boundary edges | the free first move: named, not carved |
|
|
63
|
-
|
|
|
63
|
+
| 84 | `aws_s3_bucket.assets` | clean leaf | 1 inbound, 1 output | **the star** — the resource we carve on camera |
|
|
64
64
|
| 71 | `module.cdn` | carvable w/ edits | 1 outbound, tier 2, data present | one module, so module scoring appears |
|
|
65
65
|
| 69 | `aws_iam_role.api` | carvable w/ edits | 1 inbound, 1 outbound, tier 2 | what an inbound edge costs |
|
|
66
66
|
| 69 | `aws_vpc_endpoint.ssm` | carvable w/ edits | 4 outbound, tier 2 | outbound edges are cheap, but they add up |
|
|
67
|
-
|
|
|
68
|
-
| 46 | `aws_subnet.private_a` | leave in Terraform | 3 inbound, 2 outbound, data present | |
|
|
69
|
-
| 46 | `aws_subnet.private_b` | leave in Terraform | 3 inbound, 2 outbound, data present | |
|
|
67
|
+
| 61 | `aws_lambda_function.api` | carvable w/ edits | 1 output, 5 outbound, tier 2 | the honest yellow: next month, not today |
|
|
70
68
|
| 43 | `aws_security_group.lambda` | leave in Terraform | 2 inbound, 2 outbound, tier 2, data present | |
|
|
71
|
-
|
|
|
69
|
+
| 42 | `aws_subnet.private_a` | leave in Terraform | 3 inbound, 1 output, 2 outbound, data present | |
|
|
70
|
+
| 42 | `aws_subnet.private_b` | leave in Terraform | 3 inbound, 1 output, 2 outbound, data present | |
|
|
71
|
+
| 32 | `aws_vpc.main` | leave in Terraform | 5 inbound, 1 output, 1 outbound | the grey anchor: this stays, and that's fine |
|
|
72
72
|
| 0 | `aws_network_acl.private` | leave in Terraform | no known native mapping | |
|
|
73
73
|
| 0 | `random_pet.suffix` | leave in Terraform | no known native mapping | the long tail, stated plainly |
|
|
74
74
|
|
|
75
75
|
Bands: 80-100 carve now, 50-79 carve with boundary edits, 0-49 leave in Terraform.
|
|
76
76
|
|
|
77
|
-
The scoring model is `100 - 12*inbound - 4*outbound - 15*(tier-1) -
|
|
78
|
-
- 3*(instances-1)`, clamped, with unmapped types pinned at 0.
|
|
77
|
+
The scoring model is `100 - 12*inbound - 4*outbound - 4*outputs - 15*(tier-1) -
|
|
78
|
+
10*dynamic - 3*(instances-1)`, clamped, with unmapped types pinned at 0. The
|
|
79
|
+
`outputs` term is chant#1638 (chant 0.44.7): an `output` block that reads a
|
|
80
|
+
resource is a boundary edge too now, `bridge: "tf-output-rewrite"`, because
|
|
81
|
+
carving that resource means rewriting the output the same way carving a
|
|
82
|
+
resource with an inbound reference means adding a data source. Every number
|
|
79
83
|
above is that arithmetic on the edges in `legacy-tf/`, which is why the estate
|
|
80
84
|
is shaped the way it is:
|
|
81
85
|
|
|
82
|
-
- The bucket has
|
|
83
|
-
`ASSETS_BUCKET` environment variable
|
|
86
|
+
- The bucket has one inbound edge — `aws_lambda_function.api`'s
|
|
87
|
+
`ASSETS_BUCKET` environment variable — and, since `outputs.tf`'s
|
|
88
|
+
`assets_bucket` reads it too, one output edge. Two edges, two rewrites, 84.
|
|
89
|
+
`assets_bucket` is deliberate: a downstream consumer of this state reads the
|
|
90
|
+
bucket name today, so the carve has a second thing to bridge, not just the
|
|
91
|
+
lambda's env var.
|
|
84
92
|
- `aws_s3_bucket_versioning.assets` and `aws_s3_bucket_public_access_block.assets`
|
|
85
93
|
share the bucket's name, so they fold into its carve set. They are not ranked
|
|
86
94
|
separately and their edges to the bucket are not counted against it.
|
|
87
|
-
- Five resources point at the VPC
|
|
88
|
-
|
|
95
|
+
- Five resources point at the VPC, and its existing `vpc_id` output reads it
|
|
96
|
+
too. Carving it would mean patching all five survivors and rewriting the
|
|
97
|
+
output, and 32 says so.
|
|
89
98
|
- The subnets and the security group each read a data source, which costs 10.
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
The subnets also feed `private_subnet_ids`, an existing output, which costs
|
|
100
|
+
4 more apiece and drops them under the security group's 43 — the one place
|
|
101
|
+
the outputs term reorders the table, not just re-scores it.
|
|
92
102
|
|
|
93
103
|
No resource uses `count` or `for_each`, so the instance-count penalty is zero
|
|
94
104
|
and `--state` produces byte-identical scores to a `.tf`-only parse. The bands do
|
|
@@ -109,14 +119,20 @@ chant carve advise --from example-carve/legacy-tf \
|
|
|
109
119
|
|
|
110
120
|
from the repository root, so the `from` field stays a relative path.
|
|
111
121
|
|
|
112
|
-
`app/` pins chant ^0.44.
|
|
113
|
-
advisor only publishes a top-level `version: 1` and
|
|
114
|
-
lists from 0.44.6 (chant#1636)
|
|
115
|
-
is identical but no resource carries a
|
|
116
|
-
falls back to naming inbound/outbound
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
`app/` pins chant ^0.44.7 and that floor is load-bearing, not housekeeping, in
|
|
123
|
+
two layers. The advisor only publishes a top-level `version: 1` and
|
|
124
|
+
per-resource `boundary` edge lists from 0.44.6 (chant#1636) — run the same
|
|
125
|
+
command on 0.44.4 and every score is identical but no resource carries a
|
|
126
|
+
`boundary` field at all, so the Pick step falls back to naming inbound/outbound
|
|
127
|
+
COUNTS instead of naming the edges the carve severs. 0.44.7 adds a second
|
|
128
|
+
layer on top: `output` blocks read as boundary edges (chant#1638), so the
|
|
129
|
+
bucket scores 84 instead of 88 and the report's `patchOnCarve` list names
|
|
130
|
+
`output.assets_bucket` alongside `aws_lambda_function.api`; and `carve emit`'s
|
|
131
|
+
fold is applied, not just reported (chant#1637), so the emitted bucket actually
|
|
132
|
+
carries `VersioningConfiguration` and `PublicAccessBlockConfiguration` instead
|
|
133
|
+
of leaving them in an "unmapped attributes" comment. The committed report above
|
|
134
|
+
is byte-identical to what 0.44.7 regenerates, which is why the demo's fallback
|
|
135
|
+
path and its fresh-run path show the same picture.
|
|
120
136
|
|
|
121
137
|
## The six beats
|
|
122
138
|
|
|
@@ -124,20 +140,21 @@ These are the six steps on the panel's Carve tab. behold runs beats 4 and 5
|
|
|
124
140
|
itself, into `app/carveout/` in the copy; beat 6 is copy buttons, on purpose.
|
|
125
141
|
|
|
126
142
|
1. **The green star.** The estate view, banded. Two resources are green; the
|
|
127
|
-
100 is a log group nobody will miss, so the eye lands on the
|
|
128
|
-
2. **The arithmetic.** Open `aws_s3_bucket.assets`: 100 minus 12 for
|
|
129
|
-
inbound edge
|
|
130
|
-
pane shows the terms.
|
|
131
|
-
3. **The cut edge.** Highlight the
|
|
132
|
-
`aws_lambda_function.api` reads the bucket name
|
|
143
|
+
100 is a log group nobody will miss, so the eye lands on the 84 next to it.
|
|
144
|
+
2. **The arithmetic.** Open `aws_s3_bucket.assets`: 100 minus 12 for the
|
|
145
|
+
inbound edge, minus 4 more for the output block that reads it. The score is
|
|
146
|
+
not a vibe, it is a subtraction, and the inspect pane shows the terms.
|
|
147
|
+
3. **The cut edge.** Highlight the two dependencies the carve severs:
|
|
148
|
+
`aws_lambda_function.api` reads the bucket name, and the `assets_bucket`
|
|
149
|
+
output reads it too. Two one-line rewrites, not one.
|
|
133
150
|
4. **Emit.** `carve emit --from legacy-tf --state legacy-tf/terraform.tfstate
|
|
134
151
|
--select aws_s3_bucket.assets` adopts the bucket from state into typed chant
|
|
135
|
-
source, folds both sub-resources into the carve set
|
|
136
|
-
|
|
137
|
-
result exits clean.
|
|
152
|
+
source, folds both sub-resources into the carve set AS native props
|
|
153
|
+
(chant#1637's fold, applied), and writes a boundary report. Nothing is
|
|
154
|
+
applied, nothing is destroyed, and `chant lint` on the result exits clean.
|
|
138
155
|
5. **The bridge.** One `data "aws_s3_bucket" "assets"` block in the surviving
|
|
139
|
-
Terraform
|
|
140
|
-
surviving plan is whole again.
|
|
156
|
+
Terraform; `aws_lambda_function.api` and the `assets_bucket` output both
|
|
157
|
+
read from that instead. The surviving plan is whole again.
|
|
141
158
|
6. **The handoff.** Two commands: `terraform state rm aws_s3_bucket.assets`,
|
|
142
159
|
then chant takes the observe position. `terraform plan` shows no destroy.
|
|
143
160
|
|
|
@@ -169,22 +186,32 @@ walkthrough actually carves, are covered (chant's `just carve-emit-e2e` is the
|
|
|
169
186
|
prior art). The VPC, lambda and CloudFront blocks are scenery for the advisor's
|
|
170
187
|
grey band, so expect `-target` rather than one apply of the whole estate.
|
|
171
188
|
|
|
172
|
-
##
|
|
189
|
+
## Why build still fails
|
|
173
190
|
|
|
174
191
|
`carve emit --state --select aws_s3_bucket.assets` reports "Folded in:
|
|
175
|
-
`aws_s3_bucket_public_access_block.assets`, `aws_s3_bucket_versioning.assets`"
|
|
176
|
-
and
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Terraform attributes" comment
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
and
|
|
187
|
-
|
|
192
|
+
`aws_s3_bucket_public_access_block.assets`, `aws_s3_bucket_versioning.assets`",
|
|
193
|
+
and as of chant 0.44.7 (chant#1637, chant PR #1640) the emitted `Bucket`
|
|
194
|
+
actually carries both: `VersioningConfiguration` and
|
|
195
|
+
`PublicAccessBlockConfiguration` come out as native props, not an "unmapped
|
|
196
|
+
Terraform attributes" comment. That used to be the rough edge here — a fold
|
|
197
|
+
that was reported but not applied, so `chant build` failed two AWS policy
|
|
198
|
+
rules on source the advisor called clean. It is fixed now.
|
|
199
|
+
|
|
200
|
+
`chant lint` on the emitted project passes with warnings. `chant build` still
|
|
201
|
+
fails, but on one rule: WAW042, no bucket policy denying non-TLS requests.
|
|
202
|
+
That is not a carve-tooling gap — grep `legacy-tf/` for
|
|
203
|
+
`aws_s3_bucket_policy` and there isn't one. The Terraform never declared a
|
|
204
|
+
TLS-deny policy for this bucket, in any form; chant's post-synth checks
|
|
205
|
+
noticed the moment the resource had a native representation to check. Nothing
|
|
206
|
+
here would show up scanning the `.tf` by eye, because there is nothing to
|
|
207
|
+
find — only an absence.
|
|
208
|
+
|
|
209
|
+
Verified against chant 0.44.7. The walkthrough's Emit beat still shows `lint`,
|
|
210
|
+
not `build` — the step's "why lint and not build?" note says why on screen —
|
|
211
|
+
but the reason changed: `build` doesn't fail because the fold is incomplete
|
|
212
|
+
anymore, it fails because the estate really does have a policy gap. "chant
|
|
213
|
+
found a policy gap Terraform never noticed" is the honest line to close the
|
|
214
|
+
video on.
|
|
188
215
|
|
|
189
216
|
## Values
|
|
190
217
|
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "behold-example-carve-app",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@intentius/chant": "^0.44.
|
|
10
|
-
"@intentius/chant-lexicon-aws": "^0.44.
|
|
9
|
+
"@intentius/chant": "^0.44.7",
|
|
10
|
+
"@intentius/chant-lexicon-aws": "^0.44.7"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"node_modules/@dagrejs/dagre": {
|
|
@@ -442,9 +442,9 @@
|
|
|
442
442
|
}
|
|
443
443
|
},
|
|
444
444
|
"node_modules/@intentius/chant": {
|
|
445
|
-
"version": "0.44.
|
|
446
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.
|
|
447
|
-
"integrity": "sha512-
|
|
445
|
+
"version": "0.44.7",
|
|
446
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.7.tgz",
|
|
447
|
+
"integrity": "sha512-etCchkfbWFHJ+XnTYVVn9GT4/gZi/O8SVphDXHeuPOfK5VvRxo0unveIUEsuRoHGHgzN9Y+aj0Jk15xhIE7Gzg==",
|
|
448
448
|
"license": "Apache-2.0",
|
|
449
449
|
"dependencies": {
|
|
450
450
|
"@dagrejs/dagre": "^3.0.0",
|
|
@@ -460,16 +460,16 @@
|
|
|
460
460
|
}
|
|
461
461
|
},
|
|
462
462
|
"node_modules/@intentius/chant-lexicon-aws": {
|
|
463
|
-
"version": "0.44.
|
|
464
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.44.
|
|
465
|
-
"integrity": "sha512-
|
|
463
|
+
"version": "0.44.7",
|
|
464
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.44.7.tgz",
|
|
465
|
+
"integrity": "sha512-tlwcUv0zN5rTupRdJHdcYSsKbodiVjYaBAwG/omK4hYPtys3dZMjLVOt8bszBeTKp+h8boIEjhwpgQKpDAGmcQ==",
|
|
466
466
|
"license": "Apache-2.0",
|
|
467
467
|
"dependencies": {
|
|
468
468
|
"fflate": "^0.8.2",
|
|
469
469
|
"js-yaml": "^4.1.0"
|
|
470
470
|
},
|
|
471
471
|
"peerDependencies": {
|
|
472
|
-
"@intentius/chant": "^0.44.
|
|
472
|
+
"@intentius/chant": "^0.44.7",
|
|
473
473
|
"typescript": "^5.9.3"
|
|
474
474
|
}
|
|
475
475
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"build": "./node_modules/.bin/chant build src --lexicon aws -o template.json"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@intentius/chant": "^0.44.
|
|
11
|
-
"@intentius/chant-lexicon-aws": "^0.44.
|
|
10
|
+
"@intentius/chant": "^0.44.7",
|
|
11
|
+
"@intentius/chant-lexicon-aws": "^0.44.7"
|
|
12
12
|
}
|
|
13
13
|
}
|