@intentius/behold 0.29.11 → 0.29.13
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/package.json +1 -1
- package/web/kiosk-roots.test.js +2 -2
- package/web/kiosk.js +2 -2
- package/web/kiosk.test.js +1 -1
- package/web/terragucci-change.js +12 -12
- package/web/terragucci-change.test.js +4 -4
- package/web/terragucci-components-live.js +2 -2
- package/web/terragucci-components-live.test.js +2 -2
- package/web/terragucci-estate.js +1 -1
- package/web/terragucci.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/behold",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.13",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"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.",
|
package/web/kiosk-roots.test.js
CHANGED
|
@@ -38,8 +38,8 @@ describe("a colour per root (behold#571)", () => {
|
|
|
38
38
|
|
|
39
39
|
describe("root names in a line (behold#571)", () => {
|
|
40
40
|
it("finds whole-word names, possessives and names in brackets, and keeps the line's text", () => {
|
|
41
|
-
const line = "app reads subnet_id from network,
|
|
42
|
-
expect(names(line)).toEqual(["app", "network", "network"
|
|
41
|
+
const line = "app reads subnet_id from network, set when network applies.";
|
|
42
|
+
expect(names(line)).toEqual(["app", "network", "network"]);
|
|
43
43
|
expect(rootSegments(line, ROOTS).map((s) => s.text).join("")).toBe(line);
|
|
44
44
|
expect(names("approves wave 2 (app) with network's key")).toEqual(["app", "network"]);
|
|
45
45
|
expect(names("app: Plan: 0 to add, 1 to change, 0 to destroy.")).toEqual(["app"]);
|
package/web/kiosk.js
CHANGED
|
@@ -223,7 +223,7 @@ export function unreviewedLines(answer) {
|
|
|
223
223
|
if (!pr) return [];
|
|
224
224
|
return pr.reads
|
|
225
225
|
.filter((r) => r.outputs === "planned" && r.unknown && r.unknown.length)
|
|
226
|
-
.map((r) => `${r.root} reads ${r.unknown.join(", ")} from ${r.upstream},
|
|
226
|
+
.map((r) => `${r.root} reads ${r.unknown.join(", ")} from ${r.upstream}, set when ${r.upstream} applies.`);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
/**
|
|
@@ -457,7 +457,7 @@ export function factLines(demo, answer) {
|
|
|
457
457
|
if (totals) out.push(`pull request #${pr.run.pull_request} changes ${rootsCount(pr.totals_roots) || "across roots"}: ${totals}`);
|
|
458
458
|
const b = blastRows(answer);
|
|
459
459
|
if (b && b.changed.length) out.push(`blast radius: ${b.changed.join(", ")}${b.downstream.length ? `; downstream, nearest first: ${b.downstream.join(", ")}` : ""}`);
|
|
460
|
-
if (b && b.unplanned.length) out.push(`
|
|
460
|
+
if (b && b.unplanned.length) out.push(`unchanged: ${b.unplanned.join(", ")}`);
|
|
461
461
|
const cost = costLine(pr.cost);
|
|
462
462
|
if (cost) out.push(cost);
|
|
463
463
|
return out;
|
package/web/kiosk.test.js
CHANGED
|
@@ -49,7 +49,7 @@ describe("frameSrc (#569)", () => {
|
|
|
49
49
|
|
|
50
50
|
describe("factLines (#569)", () => {
|
|
51
51
|
it("says the pull request's totals and blast radius", () => {
|
|
52
|
-
expect(factLines(demo("planned"), answer)).toEqual(["pull request #7 changes across 3 roots: 3 update", "blast radius: app, edge, network", "
|
|
52
|
+
expect(factLines(demo("planned"), answer)).toEqual(["pull request #7 changes across 3 roots: 3 update", "blast radius: app, edge, network", "unchanged: other"]);
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
|
package/web/terragucci-change.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { ago, clock, hrefOf } from "./terragucci.js";
|
|
24
24
|
|
|
25
25
|
export const STATE_WORDS = {
|
|
26
|
-
"not-started": "
|
|
26
|
+
"not-started": "queued",
|
|
27
27
|
planned: "planned",
|
|
28
28
|
waiting: "waiting for an approval",
|
|
29
29
|
applying: "applying",
|
|
@@ -119,13 +119,13 @@ export function readCardIds(answer, e) {
|
|
|
119
119
|
return [`${r.member}/${r.name}/data.terraform_remote_state.${data}`, `${r.member}/${r.name}/data.terraform_estate_outputs.${data}`];
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
/** "5 create · 1 update", no-ops and zeros left out; "
|
|
122
|
+
/** "5 create · 1 update", no-ops and zeros left out; "unchanged" when nothing moves. */
|
|
123
123
|
export function totalsLine(totals) {
|
|
124
124
|
if (!totals) return null;
|
|
125
125
|
const order = ["create", "update", "replace", "delete", "forget", "import", "read"];
|
|
126
126
|
const keys = [...order.filter((k) => k in totals), ...Object.keys(totals).filter((k) => !order.includes(k) && k !== "no-op").sort()];
|
|
127
127
|
const parts = keys.filter((k) => totals[k] > 0).map((k) => `${totals[k]} ${k}`);
|
|
128
|
-
return parts.length ? parts.join(" · ") : "
|
|
128
|
+
return parts.length ? parts.join(" · ") : "unchanged";
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/** #568 step 2: "across 3 roots", from `totals_roots` (report minor 35); null on an older report, which has no count. */
|
|
@@ -216,7 +216,7 @@ export function composeLine(pr) {
|
|
|
216
216
|
return null;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
const APPLY_WORDS = { applied: "applied", partial: "applied in part", failed: "failed", "not-started": "
|
|
219
|
+
const APPLY_WORDS = { applied: "applied", partial: "applied in part", failed: "failed", "not-started": "queued", "no-changes": "unchanged" };
|
|
220
220
|
const STATE_AS = { written: "state written", unchanged: "state as it was, nothing to write", "not-written": "state not written" };
|
|
221
221
|
|
|
222
222
|
/** One member of a composed run as planned: "app as module.app: 1 create". */
|
|
@@ -333,7 +333,7 @@ export function changeHead(answer, progress, now = new Date()) {
|
|
|
333
333
|
const stage = changeStage(answer, progress);
|
|
334
334
|
if (stage === "pull-request") {
|
|
335
335
|
const r = answer.pull_request.run;
|
|
336
|
-
return { text: `pull request #${r.pull_request}, planned ${clock(r.finished, now)}, ${ago(r.finished, now)};
|
|
336
|
+
return { text: `pull request #${r.pull_request}, planned ${clock(r.finished, now)}, ${ago(r.finished, now)}; the run view follows the merge`, href: hrefOf(answer, r.report), pr: r.pull_request_url || null };
|
|
337
337
|
}
|
|
338
338
|
if (stage === "apply") {
|
|
339
339
|
const r = progress.run;
|
|
@@ -494,7 +494,7 @@ export function rootStates(answer, progress) {
|
|
|
494
494
|
const blast = pr.blast && Array.isArray(pr.blast.roots) ? pr.blast.roots : null;
|
|
495
495
|
const readsUnknown = (pr.reads || []).some((r) => r.root === root && r.unknown && r.unknown.length) || !!(c && c.held_by_unknown && c.held_by_unknown.includes(root));
|
|
496
496
|
if (blast && !blast.includes(root) && !readsUnknown) {
|
|
497
|
-
roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} ·
|
|
497
|
+
roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} · unchanged`, facts: [] };
|
|
498
498
|
continue;
|
|
499
499
|
}
|
|
500
500
|
const facts = [];
|
|
@@ -505,7 +505,7 @@ export function rootStates(answer, progress) {
|
|
|
505
505
|
roots[root] = { wave: w.number, state: "planned", tone: STATE_TONE.planned, words: `wave ${w.number} · planned${c ? ", composed" : ""}`, facts };
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
|
-
for (const root of known) if (!roots[root]) roots[root] = { wave: null, state: "outside", tone: STATE_TONE.outside, words: "
|
|
508
|
+
for (const root of known) if (!roots[root]) roots[root] = { wave: null, state: "outside", tone: STATE_TONE.outside, words: "unchanged", facts: [] };
|
|
509
509
|
return { stage, roots };
|
|
510
510
|
}
|
|
511
511
|
if (stage === "apply") {
|
|
@@ -515,7 +515,7 @@ export function rootStates(answer, progress) {
|
|
|
515
515
|
const inRun = [...composedOf].filter(([, n]) => n === w.number).map(([r]) => r);
|
|
516
516
|
for (const root of [...new Set([...w.roots, ...(c ? c.roots : []), ...inRun])]) {
|
|
517
517
|
if (!rootChanges(w, root)) {
|
|
518
|
-
roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} ·
|
|
518
|
+
roots[root] = { wave: w.number, state: "unchanged", tone: STATE_TONE.unchanged, words: `wave ${w.number} · unchanged`, facts: [] };
|
|
519
519
|
continue;
|
|
520
520
|
}
|
|
521
521
|
const m = c && c.members ? c.members.find((x) => x.root === root) : null;
|
|
@@ -529,7 +529,7 @@ export function rootStates(answer, progress) {
|
|
|
529
529
|
if (m && m.apply) facts.push(memberApplyLine(m).replace(`${root}: `, "")); // this root's own part, not the wave's
|
|
530
530
|
else if (c && !composedHere) {
|
|
531
531
|
// Beside a composed run in its wave: the wave's figures are the run's, not this root's.
|
|
532
|
-
} else if (totals && state !== "not-started") facts.push(totals === "
|
|
532
|
+
} else if (totals && state !== "not-started") facts.push(totals === "unchanged" ? `${together ? "the wave: " : ""}unchanged` : `${together ? "the wave " : ""}${state === "applied" ? "applied" : state === "failed" || state === "refused" ? "planned" : "plans"} ${totals}`);
|
|
533
533
|
if (state === "failed" || state === "refused") {
|
|
534
534
|
const before = progress.states.filter((x) => x.number < w.number && x.state === "applied").map((x) => x.number);
|
|
535
535
|
if (before.length) facts.push(`wave ${list(before)} stays applied`);
|
|
@@ -544,12 +544,12 @@ export function rootStates(answer, progress) {
|
|
|
544
544
|
return { stage, roots };
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
-
/** The short label on a read's line: "reads network's planned http_proxy_port,
|
|
547
|
+
/** The short label on a read's line: "reads network's planned http_proxy_port, set when network applies". */
|
|
548
548
|
export function readLabel(e) {
|
|
549
549
|
if (e.composed !== undefined) {
|
|
550
550
|
return `reads ${e.to} in wave ${e.composed}'s composed run${e.unknown.length ? `: ${list(e.unknown)} known once ${e.to}'s part applies` : ""}`;
|
|
551
551
|
}
|
|
552
|
-
if (e.outputs === "planned") return e.unknown.length ? `reads ${list(e.unknown)} from ${e.to}
|
|
552
|
+
if (e.outputs === "planned") return e.unknown.length ? `reads ${list(e.unknown)} from ${e.to}, set when ${e.to} applies` : `reads ${e.to}'s planned outputs`;
|
|
553
553
|
return `reads ${e.to}'s applied state`;
|
|
554
554
|
}
|
|
555
555
|
|
|
@@ -607,7 +607,7 @@ export function viewCaption(answer, progress, now = new Date()) {
|
|
|
607
607
|
const stage = changeStage(answer, progress);
|
|
608
608
|
if (stage === "pull-request") {
|
|
609
609
|
const r = answer.pull_request.run;
|
|
610
|
-
return `the plan of pull request #${r.pull_request} at ${clock(r.finished, now)}
|
|
610
|
+
return `the plan of pull request #${r.pull_request} at ${clock(r.finished, now)}, before the merge`;
|
|
611
611
|
}
|
|
612
612
|
if (stage === "apply") {
|
|
613
613
|
const r = progress.run;
|
|
@@ -23,7 +23,7 @@ describe("rootStates (#569)", () => {
|
|
|
23
23
|
expect(stage).toBe("pull-request");
|
|
24
24
|
expect(r.network).toMatchObject({ wave: 1, state: "planned", tone: "plan", words: "wave 1 · planned", facts: ["plan f8851fb61921"] });
|
|
25
25
|
expect(r.app.facts).toEqual(["plans again once wave 1 applies", "will wait for an approval"]);
|
|
26
|
-
expect(r.other).toMatchObject({ state: "outside", words: "
|
|
26
|
+
expect(r.other).toMatchObject({ state: "outside", words: "unchanged" });
|
|
27
27
|
});
|
|
28
28
|
it("shows a failed wave's roots failed and the waves before it applied", () => {
|
|
29
29
|
const p = run(
|
|
@@ -60,7 +60,7 @@ describe("rootStates, a root that plans no change (#569 review)", () => {
|
|
|
60
60
|
const p = run([{ number: 1, state: "waiting", roots: ["network", "other"], reads: [], digest: "jcs1-sha256:365a4f0d54d7aa", totals: { create: 5 }, changed: ["network"] }], []);
|
|
61
61
|
const { roots: r } = rootStates(prAnswer, p);
|
|
62
62
|
expect(r.network.state).toBe("waiting");
|
|
63
|
-
expect(r.other).toMatchObject({ state: "unchanged", tone: "idle", words: "wave 1 ·
|
|
63
|
+
expect(r.other).toMatchObject({ state: "unchanged", tone: "idle", words: "wave 1 · unchanged", facts: [] });
|
|
64
64
|
});
|
|
65
65
|
it("takes a composed member's own no-changes over the wave's state", () => {
|
|
66
66
|
const p = run([{ number: 1, state: "failed", roots: ["network"], reads: [], composed: { roots: ["network", "edge"], members: [{ root: "network", module: "m", apply: "failed" }, { root: "edge", module: "e", apply: "no-changes" }] } }], []);
|
|
@@ -76,7 +76,7 @@ describe("rootStates, a root that plans no change (#569 review)", () => {
|
|
|
76
76
|
|
|
77
77
|
describe("readLabel (#569)", () => {
|
|
78
78
|
it("says what is read and when it is known", () => {
|
|
79
|
-
expect(readLabel({ from: "app", to: "network", outputs: "planned", unknown: ["http_proxy_port"] })).toBe("reads http_proxy_port from network
|
|
79
|
+
expect(readLabel({ from: "app", to: "network", outputs: "planned", unknown: ["http_proxy_port"] })).toBe("reads http_proxy_port from network, set when network applies");
|
|
80
80
|
expect(readLabel({ from: "app", to: "network", outputs: "planned", unknown: [] })).toBe("reads network's planned outputs");
|
|
81
81
|
expect(readLabel({ from: "app", to: "network", outputs: "applied", unknown: [] })).toBe("reads network's applied state");
|
|
82
82
|
expect(readLabel({ from: "app", to: "network", outputs: "planned", unknown: ["subnet_id"], composed: 1 })).toBe("reads network in wave 1's composed run: subnet_id known once network's part applies");
|
|
@@ -93,7 +93,7 @@ describe("readCardIds (#569)", () => {
|
|
|
93
93
|
describe("viewCaption (#569)", () => {
|
|
94
94
|
it("says which run the marks come from", () => {
|
|
95
95
|
const now = new Date("2026-10-15T03:30:00Z");
|
|
96
|
-
expect(viewCaption(prAnswer, null, now)).toBe("the plan of pull request #201 at 03:11 UTC
|
|
96
|
+
expect(viewCaption(prAnswer, null, now)).toBe("the plan of pull request #201 at 03:11 UTC, before the merge");
|
|
97
97
|
expect(viewCaption(prAnswer, run([{ number: 1, state: "applied", roots: ["network"], reads: [] }], []), now)).toBe("the run of merge 6b9f7e25, as of 03:20 UTC");
|
|
98
98
|
});
|
|
99
99
|
});
|
|
@@ -30,13 +30,13 @@ export const JOB_TONE = { queued: "idle", running: "busy", waiting: "wait", succ
|
|
|
30
30
|
/** A card's state in one or two words, when the full words don't fit the card. */
|
|
31
31
|
export const SHORT_WORDS = {
|
|
32
32
|
planned: "planned",
|
|
33
|
-
"not-started": "
|
|
33
|
+
"not-started": "queued",
|
|
34
34
|
waiting: "waiting",
|
|
35
35
|
applying: "applying",
|
|
36
36
|
applied: "applied",
|
|
37
37
|
refused: "refused",
|
|
38
38
|
failed: "failed",
|
|
39
|
-
unchanged: "
|
|
39
|
+
unchanged: "unchanged",
|
|
40
40
|
outside: "not in this run",
|
|
41
41
|
};
|
|
42
42
|
|
|
@@ -98,7 +98,7 @@ describe("the component marks (#571)", () => {
|
|
|
98
98
|
network: ["applied", "ok", "applied"],
|
|
99
99
|
other: ["outside", "idle", "not in this run"],
|
|
100
100
|
app: ["waiting", "wait", "waiting for an approval"],
|
|
101
|
-
edge: ["not-started", "idle", "
|
|
101
|
+
edge: ["not-started", "idle", "queued"],
|
|
102
102
|
});
|
|
103
103
|
expect(m.cards.app.short).toBe("waiting");
|
|
104
104
|
expect(m.waves[2]).toMatchObject({ job: "apply-wave-2", state: "waiting", tone: "wait", title: "apply-wave-2 · held at the gate" });
|
|
@@ -120,7 +120,7 @@ describe("the component marks (#571)", () => {
|
|
|
120
120
|
const m = componentMarks(answer, null, facets, { runs: runs(run(11, MERGE, [job("apply-wave-2", "waiting")])), pin: `pull-41-a1b2c3d4` });
|
|
121
121
|
expect(m.stage).toBe("pull-request");
|
|
122
122
|
expect(m.cards.network).toMatchObject({ state: "planned", words: "planned" });
|
|
123
|
-
expect(m.cards.other).toMatchObject({ state: "outside", words: "
|
|
123
|
+
expect(m.cards.other).toMatchObject({ state: "outside", words: "unchanged" });
|
|
124
124
|
expect(m.waves[2]).toMatchObject({ state: "planned", status: null, title: "apply-wave-2" });
|
|
125
125
|
expect(m.waves[1].facts).toEqual(["plan aaaaaaaaaaaa"]);
|
|
126
126
|
});
|
package/web/terragucci-estate.js
CHANGED
|
@@ -211,7 +211,7 @@ export function renderEstateSvg(answer, now = new Date(), doc = document) {
|
|
|
211
211
|
const run = runLine(p, now);
|
|
212
212
|
g.appendChild(titled(doc, svgEl(doc, "text", { x: b.x + PAD, y: b.y + 55, class: `tg-project-run tg-${run.tone}` }, run.text), run.title));
|
|
213
213
|
for (const w of b.waves) {
|
|
214
|
-
g.appendChild(svgEl(doc, "text", { x: w.x, y: w.y + 14, class: `tg-wave-label tg-wave-${w.state}`, "data-wave": w.number }, `wave ${w.number} · ${w.state === "not-started" ? "
|
|
214
|
+
g.appendChild(svgEl(doc, "text", { x: w.x, y: w.y + 14, class: `tg-wave-label tg-wave-${w.state}`, "data-wave": w.number }, `wave ${w.number} · ${w.state === "not-started" ? "queued" : w.state}`));
|
|
215
215
|
for (const r of w.roots) {
|
|
216
216
|
const node = svgEl(doc, "g", { class: "tg-root-node", "data-root": r.root });
|
|
217
217
|
node.appendChild(svgEl(doc, "rect", { x: r.x, y: r.y, width: r.w, height: r.h, rx: 4 }));
|
package/web/terragucci.js
CHANGED
|
@@ -86,7 +86,7 @@ export function stageCell(verdict, stage, now = new Date()) {
|
|
|
86
86
|
if (verdict.approval === "waiting") return { text: `wave ${verdict.run.wave} waiting, ${age}`, tone: "wave", title };
|
|
87
87
|
return { text: `wave ${verdict.run.wave} ${verdict.applied ? "applied" : verdict.approval || "ran"}, ${age}`, tone: "ok", title };
|
|
88
88
|
}
|
|
89
|
-
if (verdict.changes === 0) return { text: `${stage === "tf-drift" ? "none" : "
|
|
89
|
+
if (verdict.changes === 0) return { text: `${stage === "tf-drift" ? "none" : "unchanged"}, ${age}`, tone: "ok", title };
|
|
90
90
|
const noun = stage === "tf-drift" ? "drifted" : `change${verdict.changes === 1 ? "" : "s"}`;
|
|
91
91
|
const pr = verdict.run.pull_request ? ` (PR #${verdict.run.pull_request})` : "";
|
|
92
92
|
return { text: `${verdict.changes} ${noun}${pr}, ${age}`, tone: stage === "tf-drift" ? "drift" : "plan", title };
|