@intentius/behold 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/cli.js +463 -137
- 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 +89 -55
- 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 +2 -2
- package/web/app.js +26 -0
- package/web/carve-steps.js +65 -7
- package/web/carve-steps.test.js +15 -0
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "behold-example-k8s",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@intentius/chant": "^0.44.
|
|
10
|
-
"@intentius/chant-lexicon-k8s": "^0.44.
|
|
11
|
-
"@intentius/chant-lexicon-temporal": "^0.44.
|
|
9
|
+
"@intentius/chant": "^0.44.7",
|
|
10
|
+
"@intentius/chant-lexicon-k8s": "^0.44.7",
|
|
11
|
+
"@intentius/chant-lexicon-temporal": "^0.44.7"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"node_modules/@dagrejs/dagre": {
|
|
@@ -443,9 +443,9 @@
|
|
|
443
443
|
}
|
|
444
444
|
},
|
|
445
445
|
"node_modules/@intentius/chant": {
|
|
446
|
-
"version": "0.44.
|
|
447
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.
|
|
448
|
-
"integrity": "sha512-
|
|
446
|
+
"version": "0.44.7",
|
|
447
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.7.tgz",
|
|
448
|
+
"integrity": "sha512-etCchkfbWFHJ+XnTYVVn9GT4/gZi/O8SVphDXHeuPOfK5VvRxo0unveIUEsuRoHGHgzN9Y+aj0Jk15xhIE7Gzg==",
|
|
449
449
|
"license": "Apache-2.0",
|
|
450
450
|
"dependencies": {
|
|
451
451
|
"@dagrejs/dagre": "^3.0.0",
|
|
@@ -461,9 +461,9 @@
|
|
|
461
461
|
}
|
|
462
462
|
},
|
|
463
463
|
"node_modules/@intentius/chant-k8s-client": {
|
|
464
|
-
"version": "0.44.
|
|
465
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-k8s-client/-/chant-k8s-client-0.44.
|
|
466
|
-
"integrity": "sha512-
|
|
464
|
+
"version": "0.44.7",
|
|
465
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-k8s-client/-/chant-k8s-client-0.44.7.tgz",
|
|
466
|
+
"integrity": "sha512-G9VVki/qHZ+hbKrxDDnVVhfeZWoHgozLA7tjcY/lsa/fDIwI0xMJnAN/DJNcQLBpRscXm3y3Ki/PNQ9SxshmLA==",
|
|
467
467
|
"license": "Apache-2.0",
|
|
468
468
|
"optional": true,
|
|
469
469
|
"dependencies": {
|
|
@@ -471,34 +471,34 @@
|
|
|
471
471
|
}
|
|
472
472
|
},
|
|
473
473
|
"node_modules/@intentius/chant-lexicon-k8s": {
|
|
474
|
-
"version": "0.44.
|
|
475
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-k8s/-/chant-lexicon-k8s-0.44.
|
|
476
|
-
"integrity": "sha512-
|
|
474
|
+
"version": "0.44.7",
|
|
475
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-k8s/-/chant-lexicon-k8s-0.44.7.tgz",
|
|
476
|
+
"integrity": "sha512-h8QXOYoxjsAeA1w8koLQlZqOuSQcvWpEHKmwlf0AKPgwuVRiapwJI+UtuoBwL/AOrBKDh6Z5pW3zbo6vDltGvA==",
|
|
477
477
|
"license": "Apache-2.0",
|
|
478
478
|
"dependencies": {
|
|
479
479
|
"@types/js-yaml": "^4.0.9",
|
|
480
480
|
"js-yaml": "^4.1.1"
|
|
481
481
|
},
|
|
482
482
|
"optionalDependencies": {
|
|
483
|
-
"@intentius/chant-k8s-client": "^0.44.
|
|
483
|
+
"@intentius/chant-k8s-client": "^0.44.7"
|
|
484
484
|
},
|
|
485
485
|
"peerDependencies": {
|
|
486
|
-
"@intentius/chant": "^0.44.
|
|
486
|
+
"@intentius/chant": "^0.44.7",
|
|
487
487
|
"typescript": "^5.9.3",
|
|
488
488
|
"zod": "^4.3.6"
|
|
489
489
|
}
|
|
490
490
|
},
|
|
491
491
|
"node_modules/@intentius/chant-lexicon-temporal": {
|
|
492
|
-
"version": "0.44.
|
|
493
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-temporal/-/chant-lexicon-temporal-0.44.
|
|
494
|
-
"integrity": "sha512-
|
|
492
|
+
"version": "0.44.7",
|
|
493
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-temporal/-/chant-lexicon-temporal-0.44.7.tgz",
|
|
494
|
+
"integrity": "sha512-tA7DaXGG9dsHwbMO7SNQhdVnpgsdTtZNyRexS9MdVgS4xuWHIV9N76oISEd2hK76vwPXBD/d/e+2OT6amlse1A==",
|
|
495
495
|
"license": "Apache-2.0",
|
|
496
496
|
"dependencies": {
|
|
497
497
|
"@temporalio/common": "^1.17.2",
|
|
498
498
|
"js-yaml": "^4.1.1"
|
|
499
499
|
},
|
|
500
500
|
"peerDependencies": {
|
|
501
|
-
"@intentius/chant": "^0.44.
|
|
501
|
+
"@intentius/chant": "^0.44.7",
|
|
502
502
|
"typescript": "^5.9.3",
|
|
503
503
|
"zod": "^4.3.6"
|
|
504
504
|
}
|
|
@@ -1145,9 +1145,9 @@
|
|
|
1145
1145
|
}
|
|
1146
1146
|
},
|
|
1147
1147
|
"node_modules/ip-address": {
|
|
1148
|
-
"version": "10.
|
|
1149
|
-
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.
|
|
1150
|
-
"integrity": "sha512-
|
|
1148
|
+
"version": "10.5.0",
|
|
1149
|
+
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
|
|
1150
|
+
"integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
|
|
1151
1151
|
"license": "MIT",
|
|
1152
1152
|
"optional": true,
|
|
1153
1153
|
"engines": {
|
package/example-k8s/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"build": "./node_modules/.bin/chant build src --lexicon k8s -o app.yaml"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@intentius/chant": "^0.44.
|
|
11
|
-
"@intentius/chant-lexicon-k8s": "^0.44.
|
|
12
|
-
"@intentius/chant-lexicon-temporal": "^0.44.
|
|
10
|
+
"@intentius/chant": "^0.44.7",
|
|
11
|
+
"@intentius/chant-lexicon-k8s": "^0.44.7",
|
|
12
|
+
"@intentius/chant-lexicon-temporal": "^0.44.7"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "behold-example-writes",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@intentius/chant": "^0.44.
|
|
10
|
-
"@intentius/chant-lexicon-aws": "^0.44.
|
|
11
|
-
"@intentius/chant-lexicon-temporal": "^0.44.
|
|
9
|
+
"@intentius/chant": "^0.44.7",
|
|
10
|
+
"@intentius/chant-lexicon-aws": "^0.44.7",
|
|
11
|
+
"@intentius/chant-lexicon-temporal": "^0.44.7"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"node_modules/@dagrejs/dagre": {
|
|
@@ -443,9 +443,9 @@
|
|
|
443
443
|
}
|
|
444
444
|
},
|
|
445
445
|
"node_modules/@intentius/chant": {
|
|
446
|
-
"version": "0.44.
|
|
447
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.
|
|
448
|
-
"integrity": "sha512-
|
|
446
|
+
"version": "0.44.7",
|
|
447
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.7.tgz",
|
|
448
|
+
"integrity": "sha512-etCchkfbWFHJ+XnTYVVn9GT4/gZi/O8SVphDXHeuPOfK5VvRxo0unveIUEsuRoHGHgzN9Y+aj0Jk15xhIE7Gzg==",
|
|
449
449
|
"license": "Apache-2.0",
|
|
450
450
|
"dependencies": {
|
|
451
451
|
"@dagrejs/dagre": "^3.0.0",
|
|
@@ -461,30 +461,30 @@
|
|
|
461
461
|
}
|
|
462
462
|
},
|
|
463
463
|
"node_modules/@intentius/chant-lexicon-aws": {
|
|
464
|
-
"version": "0.44.
|
|
465
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.44.
|
|
466
|
-
"integrity": "sha512-
|
|
464
|
+
"version": "0.44.7",
|
|
465
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.44.7.tgz",
|
|
466
|
+
"integrity": "sha512-tlwcUv0zN5rTupRdJHdcYSsKbodiVjYaBAwG/omK4hYPtys3dZMjLVOt8bszBeTKp+h8boIEjhwpgQKpDAGmcQ==",
|
|
467
467
|
"license": "Apache-2.0",
|
|
468
468
|
"dependencies": {
|
|
469
469
|
"fflate": "^0.8.2",
|
|
470
470
|
"js-yaml": "^4.1.0"
|
|
471
471
|
},
|
|
472
472
|
"peerDependencies": {
|
|
473
|
-
"@intentius/chant": "^0.44.
|
|
473
|
+
"@intentius/chant": "^0.44.7",
|
|
474
474
|
"typescript": "^5.9.3"
|
|
475
475
|
}
|
|
476
476
|
},
|
|
477
477
|
"node_modules/@intentius/chant-lexicon-temporal": {
|
|
478
|
-
"version": "0.44.
|
|
479
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-temporal/-/chant-lexicon-temporal-0.44.
|
|
480
|
-
"integrity": "sha512-
|
|
478
|
+
"version": "0.44.7",
|
|
479
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-temporal/-/chant-lexicon-temporal-0.44.7.tgz",
|
|
480
|
+
"integrity": "sha512-tA7DaXGG9dsHwbMO7SNQhdVnpgsdTtZNyRexS9MdVgS4xuWHIV9N76oISEd2hK76vwPXBD/d/e+2OT6amlse1A==",
|
|
481
481
|
"license": "Apache-2.0",
|
|
482
482
|
"dependencies": {
|
|
483
483
|
"@temporalio/common": "^1.17.2",
|
|
484
484
|
"js-yaml": "^4.1.1"
|
|
485
485
|
},
|
|
486
486
|
"peerDependencies": {
|
|
487
|
-
"@intentius/chant": "^0.44.
|
|
487
|
+
"@intentius/chant": "^0.44.7",
|
|
488
488
|
"typescript": "^5.9.3",
|
|
489
489
|
"zod": "^4.3.6"
|
|
490
490
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
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.
|
|
12
|
-
"@intentius/chant-lexicon-temporal": "^0.44.
|
|
10
|
+
"@intentius/chant": "^0.44.7",
|
|
11
|
+
"@intentius/chant-lexicon-aws": "^0.44.7",
|
|
12
|
+
"@intentius/chant-lexicon-temporal": "^0.44.7"
|
|
13
13
|
}
|
|
14
14
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/behold",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "behold — a live control plane on chant. See your whole estate (every substrate in one graph), coloured by drift; act through delegated, gated Ops.",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@hono/node-server": "^2.0.5",
|
|
38
38
|
"@intentius/chant": "^0.38.0",
|
|
39
39
|
"@intentius/chant-k8s-client": "^0.44.5",
|
|
40
|
-
"@intentius/pinhole": "^0.3.
|
|
40
|
+
"@intentius/pinhole": "^0.3.4",
|
|
41
41
|
"hono": "^4.12.34"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
package/web/app.js
CHANGED
|
@@ -1233,6 +1233,32 @@ const carveActions = {
|
|
|
1233
1233
|
},
|
|
1234
1234
|
runEmit: () => runCarveStep("emit"),
|
|
1235
1235
|
runBridge: () => runCarveStep("bridge"),
|
|
1236
|
+
async runPlan() {
|
|
1237
|
+
if (carveState.busy) return;
|
|
1238
|
+
carveState.busy = "plan";
|
|
1239
|
+
carveState.error = null;
|
|
1240
|
+
renderPanelCarve();
|
|
1241
|
+
try {
|
|
1242
|
+
const res = await fetch("/api/carve/plan", {
|
|
1243
|
+
method: "POST",
|
|
1244
|
+
headers: { "content-type": "application/json" },
|
|
1245
|
+
body: "{}",
|
|
1246
|
+
});
|
|
1247
|
+
const body = await res.json().catch(() => ({ error: "plan returned an unreadable body", remedy: "" }));
|
|
1248
|
+
if (!res.ok || body.error) {
|
|
1249
|
+
carveState.error = { step: "handoff", ...body };
|
|
1250
|
+
showToast(`✗ terraform plan: ${body.error || res.status}`, false);
|
|
1251
|
+
} else {
|
|
1252
|
+
carveState.plan = body;
|
|
1253
|
+
showToast(body.noDestroy ? "✓ plan: nothing to destroy" : "✗ plan wants to destroy something", body.noDestroy);
|
|
1254
|
+
}
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
carveState.error = { step: "handoff", error: String((err && err.message) || err), remedy: "Is the behold server still running?" };
|
|
1257
|
+
} finally {
|
|
1258
|
+
carveState.busy = null;
|
|
1259
|
+
renderPanelCarve();
|
|
1260
|
+
}
|
|
1261
|
+
},
|
|
1236
1262
|
copy(text, el) {
|
|
1237
1263
|
const done = () => {
|
|
1238
1264
|
el.dataset.copied = "1";
|
package/web/carve-steps.js
CHANGED
|
@@ -63,7 +63,10 @@ export function initialCarveState() {
|
|
|
63
63
|
bridge: null,
|
|
64
64
|
/** Set when the user says they ran the handoff commands. */
|
|
65
65
|
handoff: false,
|
|
66
|
-
/**
|
|
66
|
+
/** POST /api/carve/plan's answer (live tier only). Estate-wide, so a new
|
|
67
|
+
* pick keeps it — the plan is about the armed Terraform, not the pick. */
|
|
68
|
+
plan: null,
|
|
69
|
+
/** Which step is mid-run ("emit" | "bridge" | "plan" | null). */
|
|
67
70
|
busy: null,
|
|
68
71
|
/** The last structured refusal, keyed by step id. */
|
|
69
72
|
error: null,
|
|
@@ -140,7 +143,18 @@ export function edgeLine(e) {
|
|
|
140
143
|
? `${e.survivor} reads ${attrs || "this resource"}`
|
|
141
144
|
: `this resource reads ${attrs || "something"} from ${e.survivor}`;
|
|
142
145
|
const where = via ? ` (in its ${via})` : "";
|
|
143
|
-
|
|
146
|
+
// Three bridge shapes chant hands back, not two: a survivor's own reference
|
|
147
|
+
// becomes a data source (`tf-data-source`) or a deploy-time input
|
|
148
|
+
// (`deferred-input`); an `output` block reading the carved resource becomes
|
|
149
|
+
// a one-line rewrite instead (`tf-output-rewrite`, chant#1638) — it is
|
|
150
|
+
// neither of the other two, and defaulting it to "data source" would name
|
|
151
|
+
// the wrong fix for a line that was never a data block to begin with.
|
|
152
|
+
const fix =
|
|
153
|
+
e.bridge === "deferred-input"
|
|
154
|
+
? "becomes a deploy-time input"
|
|
155
|
+
: e.bridge === "tf-output-rewrite"
|
|
156
|
+
? "becomes a one-line output rewrite"
|
|
157
|
+
: "becomes a Terraform data source";
|
|
144
158
|
const when = e.required === "at-apply" ? ", deferred until apply" : e.required === "immediately" ? ", needed immediately" : "";
|
|
145
159
|
return `${dir} — ${head}${where} → ${fix}${when}`;
|
|
146
160
|
}
|
|
@@ -367,7 +381,7 @@ export function renderCarvePanel(host, state, ctx, actions) {
|
|
|
367
381
|
pick: () => renderPick(body, state, ctx, actions),
|
|
368
382
|
emit: () => renderEmit(body, state, ctx, actions),
|
|
369
383
|
bridge: () => renderBridge(body, state, ctx, actions),
|
|
370
|
-
handoff: () => renderHandoff(body, state, actions),
|
|
384
|
+
handoff: () => renderHandoff(body, state, ctx, actions),
|
|
371
385
|
done: () => renderDone(body, state, ctx, actions),
|
|
372
386
|
};
|
|
373
387
|
renderers[step.id]();
|
|
@@ -415,6 +429,17 @@ function renderAdvise(body, ctx) {
|
|
|
415
429
|
`Terraform read from ${ctx.demo.fromLabel}/ in your demo copy. Emit and bridge write only into ${ctx.demo.outLabel}/.`,
|
|
416
430
|
),
|
|
417
431
|
);
|
|
432
|
+
if (ctx.demo.live) {
|
|
433
|
+
body.appendChild(
|
|
434
|
+
el(
|
|
435
|
+
"p",
|
|
436
|
+
"panel-muted carve-live-note",
|
|
437
|
+
`Live tier: the starred resources are REALLY applied into a scratch Floci (${ctx.demo.live.endpoint}, ` +
|
|
438
|
+
"deleted when this server exits) and the tfstate the advisor read was written by terraform, not shipped. " +
|
|
439
|
+
"The observe beat — chant reading the bucket live while Terraform still owns it — waits on chant#1647.",
|
|
440
|
+
),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
418
443
|
}
|
|
419
444
|
}
|
|
420
445
|
|
|
@@ -533,12 +558,38 @@ function renderBridge(body, state, ctx, actions) {
|
|
|
533
558
|
for (const a of b.proposals || []) body.appendChild(artifactBlock(a, actions));
|
|
534
559
|
}
|
|
535
560
|
|
|
536
|
-
function renderHandoff(body, state, actions) {
|
|
561
|
+
function renderHandoff(body, state, ctx, actions) {
|
|
537
562
|
const runbook = state.bridge && state.bridge.runbook;
|
|
538
563
|
if (!runbook) {
|
|
539
564
|
body.appendChild(el("p", "panel-muted", blockedReason(state, "handoff") || "no runbook yet"));
|
|
540
565
|
return;
|
|
541
566
|
}
|
|
567
|
+
|
|
568
|
+
// The live tier's read-only beat (#254): a REAL `terraform plan`. Before the
|
|
569
|
+
// operator pastes `state rm` it shows a no-op; after, the carved resource is
|
|
570
|
+
// simply absent from Terraform's world — not destroyed. That verdict line is
|
|
571
|
+
// the reason the tier exists.
|
|
572
|
+
if (ctx.demo && ctx.demo.live) {
|
|
573
|
+
const planBtn = el("button", "act carve-plan", state.busy === "plan" ? "planning…" : "▶ terraform plan (read-only)");
|
|
574
|
+
planBtn.disabled = !!state.busy;
|
|
575
|
+
planBtn.title = "Runs terraform plan in the demo copy against the scratch Floci. Reads everything, changes nothing.";
|
|
576
|
+
planBtn.addEventListener("click", actions.runPlan);
|
|
577
|
+
body.appendChild(planBtn);
|
|
578
|
+
if (state.plan) {
|
|
579
|
+
const verdict = el("div", "carve-plan-verdict");
|
|
580
|
+
verdict.appendChild(el("code", null, state.plan.planLine));
|
|
581
|
+
verdict.appendChild(
|
|
582
|
+
el(
|
|
583
|
+
"p",
|
|
584
|
+
"panel-muted",
|
|
585
|
+
state.plan.noDestroy
|
|
586
|
+
? "0 to destroy — the resource never blinked."
|
|
587
|
+
: "The plan wants to destroy something. Stop and read it before touching the estate.",
|
|
588
|
+
),
|
|
589
|
+
);
|
|
590
|
+
body.appendChild(verdict);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
542
593
|
// The one place the walkthrough refuses to be a button, stated out loud.
|
|
543
594
|
const why = el("div", "carve-refusal carve-human");
|
|
544
595
|
why.appendChild(el("div", "carve-refusal-title", "These two are not buttons, on purpose."));
|
|
@@ -594,13 +645,20 @@ function renderDone(body, state, ctx, actions) {
|
|
|
594
645
|
if (!state.handoff) {
|
|
595
646
|
body.appendChild(el("p", "panel-muted", "The handoff commands haven't been marked as run — this is the shape of the ending, not a claim about your estate."));
|
|
596
647
|
}
|
|
648
|
+
if (state.pick) {
|
|
649
|
+
const watch = el("a", "act carve-morph-link", "▶ watch it move");
|
|
650
|
+
watch.href = "/carve/morph?select=" + encodeURIComponent(state.pick.node.id);
|
|
651
|
+
watch.target = "_blank";
|
|
652
|
+
watch.rel = "noopener";
|
|
653
|
+
watch.title = "The estate frame, morphing: the card slides out of the Terraform box and into the chant project box beside last month's carves.";
|
|
654
|
+
body.appendChild(watch);
|
|
655
|
+
}
|
|
597
656
|
body.appendChild(
|
|
598
657
|
el(
|
|
599
658
|
"p",
|
|
600
659
|
"panel-muted carve-honesty",
|
|
601
|
-
"Deferred to the follow-up: the
|
|
602
|
-
"
|
|
603
|
-
"plan showing no destroy) is the other half of that follow-up.",
|
|
660
|
+
"Deferred to the follow-up: the Floci `--live` tier — a real terraform apply into a scratch emulator, " +
|
|
661
|
+
"a real plan showing no destroy.",
|
|
604
662
|
),
|
|
605
663
|
);
|
|
606
664
|
const again = el("button", "act", "↺ start over");
|
package/web/carve-steps.test.js
CHANGED
|
@@ -149,6 +149,21 @@ describe("edgeLine", () => {
|
|
|
149
149
|
expect(edgeLine({ direction: "outbound", survivor: "vpc", carved: "sg", attrs: ["id"] })).toContain("from vpc");
|
|
150
150
|
expect(edgeLine({ direction: "outbound", survivor: "vpc", carved: "sg", bridge: "deferred-input" })).toContain("deploy-time input");
|
|
151
151
|
});
|
|
152
|
+
|
|
153
|
+
it("names an output block's fix as a rewrite, not a data source (chant#1638)", () => {
|
|
154
|
+
const line = edgeLine({
|
|
155
|
+
direction: "inbound",
|
|
156
|
+
survivor: "output.assets_bucket",
|
|
157
|
+
carved: "aws_s3_bucket.assets",
|
|
158
|
+
attrs: ["bucket"],
|
|
159
|
+
via: ["value"],
|
|
160
|
+
bridge: "tf-output-rewrite",
|
|
161
|
+
required: "immediately",
|
|
162
|
+
});
|
|
163
|
+
expect(line).toContain("output.assets_bucket reads bucket");
|
|
164
|
+
expect(line).toContain("output rewrite");
|
|
165
|
+
expect(line).not.toContain("data source");
|
|
166
|
+
});
|
|
152
167
|
});
|
|
153
168
|
|
|
154
169
|
describe("pickFacts / carveable", () => {
|