@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.
@@ -6,9 +6,9 @@
6
6
  "": {
7
7
  "name": "behold-example-k8s",
8
8
  "dependencies": {
9
- "@intentius/chant": "^0.44.6",
10
- "@intentius/chant-lexicon-k8s": "^0.44.6",
11
- "@intentius/chant-lexicon-temporal": "^0.44.6"
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.6",
447
- "resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.6.tgz",
448
- "integrity": "sha512-VFqPKe2WsSAJoUvfyghVTlIcj+QRNn//JgSHToq6PDDuKPU7F34V/4f+EZpX4MF3gvwVkZc9d6FUwKio6t0uqg==",
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.6",
465
- "resolved": "https://registry.npmjs.org/@intentius/chant-k8s-client/-/chant-k8s-client-0.44.6.tgz",
466
- "integrity": "sha512-Do3zY0Bd2nIITDDrXYePwdOBdnNf59xn5J1aJA6gsg3dzXxR3dZ25YBAh+SQxKbRPuXIbu9rKd5lodaM8ZQDng==",
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.6",
475
- "resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-k8s/-/chant-lexicon-k8s-0.44.6.tgz",
476
- "integrity": "sha512-JP2Lo4fFV7FPbSTHapKuDgpvrsm3dbNBT9pMW9NQB2MoE/ZkMGf3z3/wsPnygLdjbYq29abVYmw7DzPqBmA1tw==",
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.6"
483
+ "@intentius/chant-k8s-client": "^0.44.7"
484
484
  },
485
485
  "peerDependencies": {
486
- "@intentius/chant": "^0.44.6",
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.6",
493
- "resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-temporal/-/chant-lexicon-temporal-0.44.6.tgz",
494
- "integrity": "sha512-Uta1NNOHjZpI04NLaB9lTjXxAlKM7TSRx4D6Yj76RyeSoIZzcvnFJ9lJjSaIGaN20rdjK1w43b8BxsLOZTrwyA==",
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.6",
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.4.0",
1149
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz",
1150
- "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==",
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": {
@@ -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.6",
11
- "@intentius/chant-lexicon-k8s": "^0.44.6",
12
- "@intentius/chant-lexicon-temporal": "^0.44.6"
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.6",
10
- "@intentius/chant-lexicon-aws": "^0.44.6",
11
- "@intentius/chant-lexicon-temporal": "^0.44.6"
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.6",
447
- "resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.44.6.tgz",
448
- "integrity": "sha512-VFqPKe2WsSAJoUvfyghVTlIcj+QRNn//JgSHToq6PDDuKPU7F34V/4f+EZpX4MF3gvwVkZc9d6FUwKio6t0uqg==",
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.6",
465
- "resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.44.6.tgz",
466
- "integrity": "sha512-2qGwWFCRLkT9MD5YFnhnZOGDCqPZg9la4MM8szlX8QxFFoQjAulJ8e+vjeCGbWoB8nbSIQ9kJOX8Yn46Pm37iQ==",
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.6",
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.6",
479
- "resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-temporal/-/chant-lexicon-temporal-0.44.6.tgz",
480
- "integrity": "sha512-Uta1NNOHjZpI04NLaB9lTjXxAlKM7TSRx4D6Yj76RyeSoIZzcvnFJ9lJjSaIGaN20rdjK1w43b8BxsLOZTrwyA==",
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.6",
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.6",
11
- "@intentius/chant-lexicon-aws": "^0.44.6",
12
- "@intentius/chant-lexicon-temporal": "^0.44.6"
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.9.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.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";
@@ -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
- /** Which step is mid-run ("emit" | "bridge" | null) the dial's progress. */
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
- const fix = e.bridge === "deferred-input" ? "becomes a deploy-time input" : "becomes a Terraform data source";
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 card doesn't yet SLIDE out of the Terraform box and into the chant project " +
602
- "beside last month's carves — it is marked in place. The Floci `--live` tier (a real terraform apply, a real " +
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");
@@ -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", () => {