@intentius/behold 0.29.0 → 0.29.2

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/dist/cli.js CHANGED
@@ -299,7 +299,7 @@ var K8sApiError, K8sTransportError, K8sClientUnavailableError, ExecCredentialNot
299
299
  var init_errors = __esm({
300
300
  "node_modules/@intentius/chant-k8s-client/src/errors.ts"() {
301
301
  K8sApiError = class _K8sApiError extends Error {
302
- constructor(statusCode, reason, apiMessage, target, status) {
302
+ constructor(statusCode, reason, apiMessage, target, status2) {
303
303
  super(
304
304
  `${target ? `${target}: ` : ""}${apiMessage || "request failed"} (HTTP ${statusCode}${reason ? `, ${reason}` : ""})`
305
305
  );
@@ -307,7 +307,7 @@ var init_errors = __esm({
307
307
  this.reason = reason;
308
308
  this.apiMessage = apiMessage;
309
309
  this.target = target;
310
- this.status = status;
310
+ this.status = status2;
311
311
  this.name = "K8sApiError";
312
312
  }
313
313
  statusCode;
@@ -348,16 +348,16 @@ var init_errors = __esm({
348
348
  * Kubernetes error and occasionally plain text from a proxy in front of one.
349
349
  */
350
350
  static fromResponse(statusCode, body, target) {
351
- let status;
351
+ let status2;
352
352
  try {
353
353
  const parsed = JSON.parse(body);
354
354
  if (parsed && typeof parsed === "object" && parsed.kind === "Status") {
355
- status = parsed;
355
+ status2 = parsed;
356
356
  }
357
357
  } catch {
358
358
  }
359
- const message2 = status?.message ?? firstLine(body) ?? "";
360
- return new _K8sApiError(statusCode, status?.reason, message2, target, status);
359
+ const message2 = status2?.message ?? firstLine(body) ?? "";
360
+ return new _K8sApiError(statusCode, status2?.reason, message2, target, status2);
361
361
  }
362
362
  };
363
363
  K8sTransportError = class extends Error {
@@ -496,13 +496,13 @@ function renderConflictReport(report) {
496
496
  );
497
497
  return lines2.join("\n");
498
498
  }
499
- function parseFieldConflicts(status, message2) {
500
- const fromCauses = causesOf(status);
499
+ function parseFieldConflicts(status2, message2) {
500
+ const fromCauses = causesOf(status2);
501
501
  if (fromCauses.length > 0) return fromCauses;
502
- return parseConflictMessage(message2 ?? status?.message ?? "");
502
+ return parseConflictMessage(message2 ?? status2?.message ?? "");
503
503
  }
504
- function causesOf(status) {
505
- const details = status?.details;
504
+ function causesOf(status2) {
505
+ const details = status2?.details;
506
506
  if (!details || typeof details !== "object") return [];
507
507
  const causes = details.causes;
508
508
  if (!Array.isArray(causes)) return [];
@@ -570,8 +570,8 @@ var init_conflict = __esm({
570
570
  conflicts;
571
571
  /** The field manager chant applied as, and which was refused. */
572
572
  fieldManager;
573
- constructor(statusCode, apiMessage, conflicts, fieldManager, target, status) {
574
- super(statusCode, status?.reason ?? "Conflict", apiMessage, target, status);
573
+ constructor(statusCode, apiMessage, conflicts, fieldManager, target, status2) {
574
+ super(statusCode, status2?.reason ?? "Conflict", apiMessage, target, status2);
575
575
  this.name = "FieldManagerConflictError";
576
576
  this.conflicts = conflicts;
577
577
  this.fieldManager = fieldManager;
@@ -763,9 +763,9 @@ function parseWatchFrames(chunk, carry = "") {
763
763
  }
764
764
  function isExpiredFrame(frame) {
765
765
  if (frame.type !== "ERROR") return false;
766
- const status = frame.object;
767
- if (status.code === 410) return true;
768
- return status.reason === "Expired" || status.reason === "Gone";
766
+ const status2 = frame.object;
767
+ if (status2.code === 410) return true;
768
+ return status2.reason === "Expired" || status2.reason === "Gone";
769
769
  }
770
770
  function resourceVersionOf(frame) {
771
771
  const rv = frame.object?.metadata?.resourceVersion;
@@ -943,16 +943,16 @@ async function createK8sClient(options2 = {}) {
943
943
  return { status: response.httpStatusCode, body: text };
944
944
  }
945
945
  async function sendJson(path, method, opts = {}) {
946
- const { status, body } = await send(path, method, opts);
947
- if (status < 200 || status > 299) {
948
- throw noted(K8sApiError.fromResponse(status, body, opts.target));
946
+ const { status: status2, body } = await send(path, method, opts);
947
+ if (status2 < 200 || status2 > 299) {
948
+ throw noted(K8sApiError.fromResponse(status2, body, opts.target));
949
949
  }
950
950
  try {
951
951
  return JSON.parse(body);
952
952
  } catch (err) {
953
953
  throw noted(
954
954
  new K8sTransportError(
955
- `the API server returned HTTP ${status} with a body that is not JSON`,
955
+ `the API server returned HTTP ${status2} with a body that is not JSON`,
956
956
  opts.target ?? `${method} ${path}`,
957
957
  { cause: err }
958
958
  )
@@ -1292,13 +1292,13 @@ async function createK8sClient(options2 = {}) {
1292
1292
  if (opts.sinceSeconds !== void 0) query.sinceSeconds = String(opts.sinceSeconds);
1293
1293
  if (opts.timestamps) query.timestamps = "true";
1294
1294
  const target = `${refText(ref2)} logs`;
1295
- const { status, body } = await send(`${objectPath(info, ref2.name, ref2.namespace)}/log`, "GET", {
1295
+ const { status: status2, body } = await send(`${objectPath(info, ref2.name, ref2.namespace)}/log`, "GET", {
1296
1296
  query: Object.keys(query).length > 0 ? query : void 0,
1297
1297
  signal: opts.signal,
1298
1298
  target
1299
1299
  });
1300
- if (status < 200 || status > 299) {
1301
- throw noted(K8sApiError.fromResponse(status, body, target));
1300
+ if (status2 < 200 || status2 > 299) {
1301
+ throw noted(K8sApiError.fromResponse(status2, body, target));
1302
1302
  }
1303
1303
  return body;
1304
1304
  }
@@ -1887,8 +1887,8 @@ function statusForBand(band, score) {
1887
1887
  return score >= 80 ? "good" : score >= 50 ? "warn" : "neutral";
1888
1888
  }
1889
1889
  }
1890
- function verdictForStatus(status) {
1891
- return status === "good" ? "carve now" : status === "warn" ? "boundary work" : "leave in Terraform";
1890
+ function verdictForStatus(status2) {
1891
+ return status2 === "good" ? "carve now" : status2 === "warn" ? "boundary work" : "leave in Terraform";
1892
1892
  }
1893
1893
  function tfTypeOf(address, kind) {
1894
1894
  if (kind === "module" || address.startsWith("module.")) return "module";
@@ -1942,7 +1942,7 @@ function carveReportToIr(report) {
1942
1942
  const resources = report.resources ?? [];
1943
1943
  const known = new Set(resources.map((r) => r.address));
1944
1944
  const nodes = resources.map((r) => {
1945
- const status = statusForBand(r.band, r.score);
1945
+ const status2 = statusForBand(r.band, r.score);
1946
1946
  const b = r.breakdown ?? {};
1947
1947
  const edges2 = boundaryEdgesOf(r);
1948
1948
  const patches = edges2.filter((e) => e.carved === r.address && directionOf(e) === "inbound").map((e) => e.survivor);
@@ -1954,9 +1954,9 @@ function carveReportToIr(report) {
1954
1954
  attrs: {
1955
1955
  // The drift palette's channel — `_`-prefixed, so it paints the card and
1956
1956
  // stays out of both the card's fields and the inspect pane's list.
1957
- _status: status,
1957
+ _status: status2,
1958
1958
  score: r.score,
1959
- carve: verdictForStatus(status),
1959
+ carve: verdictForStatus(status2),
1960
1960
  band: r.band,
1961
1961
  arithmetic: scoreArithmetic(r),
1962
1962
  boundaryWork: boundaryWorkOf(r),
@@ -4899,8 +4899,8 @@ function carveStatusArgs(from) {
4899
4899
  async function discoverCarveStates(dirs, read, io = nodeCarveIo, depth = MANIFEST_SCAN_DEPTH) {
4900
4900
  const byTarget = /* @__PURE__ */ new Map();
4901
4901
  for (const dir of dirs) {
4902
- const status = await read(dir).catch(() => void 0);
4903
- const paths = status?.carves ? status.carves.map((row) => resolve3(status.from ?? dir, row.path)) : listCarveManifests(dir, io, depth);
4902
+ const status2 = await read(dir).catch(() => void 0);
4903
+ const paths = status2?.carves ? status2.carves.map((row) => resolve3(status2.from ?? dir, row.path)) : listCarveManifests(dir, io, depth);
4904
4904
  mergeCarveStates(byTarget, paths, io);
4905
4905
  }
4906
4906
  return byTarget;
@@ -5828,10 +5828,10 @@ var require_utils = __commonJS({
5828
5828
  REGEX_SPECIAL_CHARS_GLOBAL
5829
5829
  } = require_constants();
5830
5830
  exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
5831
- exports.hasRegexChars = (str16) => REGEX_SPECIAL_CHARS.test(str16);
5832
- exports.isRegexChar = (str16) => str16.length === 1 && exports.hasRegexChars(str16);
5833
- exports.escapeRegex = (str16) => str16.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
5834
- exports.toPosixSlashes = (str16) => str16.replace(REGEX_BACKSLASH, "/");
5831
+ exports.hasRegexChars = (str17) => REGEX_SPECIAL_CHARS.test(str17);
5832
+ exports.isRegexChar = (str17) => str17.length === 1 && exports.hasRegexChars(str17);
5833
+ exports.escapeRegex = (str17) => str17.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
5834
+ exports.toPosixSlashes = (str17) => str17.replace(REGEX_BACKSLASH, "/");
5835
5835
  exports.isWindows = () => {
5836
5836
  if (typeof navigator !== "undefined" && navigator.platform) {
5837
5837
  const platform2 = navigator.platform.toLowerCase();
@@ -5842,8 +5842,8 @@ var require_utils = __commonJS({
5842
5842
  }
5843
5843
  return false;
5844
5844
  };
5845
- exports.removeBackslashes = (str16) => {
5846
- return str16.replace(REGEX_REMOVE_BACKSLASH, (match) => {
5845
+ exports.removeBackslashes = (str17) => {
5846
+ return str17.replace(REGEX_REMOVE_BACKSLASH, (match) => {
5847
5847
  return match === "\\" ? "" : match;
5848
5848
  });
5849
5849
  };
@@ -5933,7 +5933,7 @@ var require_scan = __commonJS({
5933
5933
  const slashes = [];
5934
5934
  const tokens = [];
5935
5935
  const parts = [];
5936
- let str16 = input;
5936
+ let str17 = input;
5937
5937
  let index = -1;
5938
5938
  let start = 0;
5939
5939
  let lastIndex = 0;
@@ -5952,10 +5952,10 @@ var require_scan = __commonJS({
5952
5952
  let code;
5953
5953
  let token = { value: "", depth: 0, isGlob: false };
5954
5954
  const eos = () => index >= length;
5955
- const peek = () => str16.charCodeAt(index + 1);
5955
+ const peek = () => str17.charCodeAt(index + 1);
5956
5956
  const advance = () => {
5957
5957
  prev = code;
5958
- return str16.charCodeAt(++index);
5958
+ return str17.charCodeAt(++index);
5959
5959
  };
5960
5960
  while (index < length) {
5961
5961
  code = advance();
@@ -6123,24 +6123,24 @@ var require_scan = __commonJS({
6123
6123
  isExtglob = false;
6124
6124
  isGlob = false;
6125
6125
  }
6126
- let base = str16;
6126
+ let base = str17;
6127
6127
  let prefix = "";
6128
6128
  let glob = "";
6129
6129
  if (start > 0) {
6130
- prefix = str16.slice(0, start);
6131
- str16 = str16.slice(start);
6130
+ prefix = str17.slice(0, start);
6131
+ str17 = str17.slice(start);
6132
6132
  lastIndex -= start;
6133
6133
  }
6134
6134
  if (base && isGlob === true && lastIndex > 0) {
6135
- base = str16.slice(0, lastIndex);
6136
- glob = str16.slice(lastIndex);
6135
+ base = str17.slice(0, lastIndex);
6136
+ glob = str17.slice(lastIndex);
6137
6137
  } else if (isGlob === true) {
6138
6138
  base = "";
6139
- glob = str16;
6139
+ glob = str17;
6140
6140
  } else {
6141
- base = str16;
6141
+ base = str17;
6142
6142
  }
6143
- if (base && base !== "" && base !== "/" && base !== str16) {
6143
+ if (base && base !== "" && base !== "/" && base !== str17) {
6144
6144
  if (isPathSeparator(base.charCodeAt(base.length - 1))) {
6145
6145
  base = base.slice(0, -1);
6146
6146
  }
@@ -7187,8 +7187,8 @@ var require_parse = __commonJS({
7187
7187
  if (opts2.noglobstar === true) return star;
7188
7188
  return `(${capture2}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
7189
7189
  };
7190
- const create = (str16) => {
7191
- switch (str16) {
7190
+ const create = (str17) => {
7191
+ switch (str17) {
7192
7192
  case "*":
7193
7193
  return `${nodot}${ONE_CHAR}${star}`;
7194
7194
  case ".*":
@@ -7206,7 +7206,7 @@ var require_parse = __commonJS({
7206
7206
  case "**/.*":
7207
7207
  return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
7208
7208
  default: {
7209
- const match = /^(.*?)\.(\w+)$/.exec(str16);
7209
+ const match = /^(.*?)\.(\w+)$/.exec(str17);
7210
7210
  if (!match) return;
7211
7211
  const source3 = create(match[1]);
7212
7212
  if (!source3) return;
@@ -7237,9 +7237,9 @@ var require_picomatch = __commonJS({
7237
7237
  var picomatch3 = (glob, options2, returnState = false) => {
7238
7238
  if (Array.isArray(glob)) {
7239
7239
  const fns = glob.map((input) => picomatch3(input, options2, returnState));
7240
- const arrayMatcher = (str16) => {
7240
+ const arrayMatcher = (str17) => {
7241
7241
  for (const isMatch of fns) {
7242
- const state2 = isMatch(str16);
7242
+ const state2 = isMatch(str17);
7243
7243
  if (state2) return state2;
7244
7244
  }
7245
7245
  return false;
@@ -7315,7 +7315,7 @@ var require_picomatch = __commonJS({
7315
7315
  const regex = glob instanceof RegExp ? glob : picomatch3.makeRe(glob, options2);
7316
7316
  return regex.test(utils.basename(input, { windows: posix5 }));
7317
7317
  };
7318
- picomatch3.isMatch = (str16, patterns, options2) => picomatch3(patterns, options2)(str16);
7318
+ picomatch3.isMatch = (str17, patterns, options2) => picomatch3(patterns, options2)(str17);
7319
7319
  picomatch3.parse = (pattern, options2) => {
7320
7320
  if (Array.isArray(pattern)) return pattern.map((p) => picomatch3.parse(p, options2));
7321
7321
  return parse(pattern, { ...options2, fastpaths: false });
@@ -11077,16 +11077,16 @@ function pipelineProgress(pipeline) {
11077
11077
  }));
11078
11078
  return { kind: "pipeline", status: "running", waves, components };
11079
11079
  }
11080
- function withJobStatus(state, job, status) {
11080
+ function withJobStatus(state, job, status2) {
11081
11081
  const components = state.components.map((c) => {
11082
11082
  if (c.component !== job) return c;
11083
- if ((c.status === "ok" || c.status === "failed") && status === "running") return c;
11084
- return { ...c, status };
11083
+ if ((c.status === "ok" || c.status === "failed") && status2 === "running") return c;
11084
+ return { ...c, status: status2 };
11085
11085
  });
11086
11086
  const waves = state.waves.map((w) => {
11087
11087
  const members = components.filter((c) => w.components.includes(c.component));
11088
- const status2 = members.some((c) => c.status === "failed") ? "failed" : members.every((c) => c.status === "ok") ? "ok" : members.some((c) => c.status !== "pending") ? "running" : "pending";
11089
- return { ...w, status: status2 };
11088
+ const status3 = members.some((c) => c.status === "failed") ? "failed" : members.every((c) => c.status === "ok") ? "ok" : members.some((c) => c.status !== "pending") ? "running" : "pending";
11089
+ return { ...w, status: status3 };
11090
11090
  });
11091
11091
  return { ...state, components, waves };
11092
11092
  }
@@ -11234,8 +11234,8 @@ function runViewToProgress(pipeline, view) {
11234
11234
  ...state,
11235
11235
  waves: state.waves.map((w) => {
11236
11236
  const members = state.components.filter((c) => w.components.includes(c.component));
11237
- const status = members.some((c) => c.status === "failed") ? "failed" : members.every((c) => c.status === "ok") ? "ok" : members.some((c) => c.status !== "pending") ? "running" : "pending";
11238
- return { ...w, status };
11237
+ const status2 = members.some((c) => c.status === "failed") ? "failed" : members.every((c) => c.status === "ok") ? "ok" : members.some((c) => c.status !== "pending") ? "running" : "pending";
11238
+ return { ...w, status: status2 };
11239
11239
  })
11240
11240
  };
11241
11241
  if (view.url) state = { ...state, url: view.url };
@@ -12010,8 +12010,8 @@ var FORGE_WORD = {
12010
12010
  local: "laptop",
12011
12011
  unknown: "?"
12012
12012
  };
12013
- function releaseChip(status) {
12014
- return `${status.gitSha.slice(0, 7)} via ${FORGE_WORD[status.forge]}`;
12013
+ function releaseChip(status2) {
12014
+ return `${status2.gitSha.slice(0, 7)} via ${FORGE_WORD[status2.forge]}`;
12015
12015
  }
12016
12016
  function releaseStatus(rec7) {
12017
12017
  if (!rec7) return void 0;
@@ -12060,7 +12060,7 @@ function joinTickVerdicts(ir, tick, now = Date.now()) {
12060
12060
  const v = byComponent.get(n.id);
12061
12061
  if (!v) return n;
12062
12062
  joined = true;
12063
- const status = {
12063
+ const status2 = {
12064
12064
  reconciliation: v.reconciliation,
12065
12065
  detail: v.detail,
12066
12066
  ...v.live !== void 0 ? { live: v.live } : {},
@@ -12080,7 +12080,7 @@ function joinTickVerdicts(ir, tick, now = Date.now()) {
12080
12080
  // are deliberately not passed, so this can only ever reach
12081
12081
  // `componentStatusColor`'s last branch.
12082
12082
  ...paint ? { _status: componentStatusColor({ reconciliation: v.reconciliation, detail: v.detail }) } : {},
12083
- _tickStatus: status
12083
+ _tickStatus: status2
12084
12084
  }
12085
12085
  };
12086
12086
  });
@@ -12756,11 +12756,11 @@ function tierMismatchNote(ir, tiers, currentTier) {
12756
12756
  if (values.length < 2) return void 0;
12757
12757
  const byLexicon = /* @__PURE__ */ new Map();
12758
12758
  for (const n of ir.nodes) {
12759
- const status = n.attrs?._status;
12760
- if (status !== "good" && status !== "accent") continue;
12759
+ const status2 = n.attrs?._status;
12760
+ if (status2 !== "good" && status2 !== "accent") continue;
12761
12761
  const lexicon = n.lexicon ?? "?";
12762
12762
  const c = byLexicon.get(lexicon) ?? { good: 0, accent: 0 };
12763
- c[status === "good" ? "good" : "accent"]++;
12763
+ c[status2 === "good" ? "good" : "accent"]++;
12764
12764
  byLexicon.set(lexicon, c);
12765
12765
  }
12766
12766
  for (const [lexicon, { good, accent }] of [...byLexicon].sort()) {
@@ -15655,7 +15655,7 @@ function collapseBoxes(ir, opts = {}) {
15655
15655
  const nodes2 = ids.flatMap((id2) => byId.get(id2) ?? []);
15656
15656
  const id = `${SUMMARY_PREFIX}${key}`;
15657
15657
  for (const n of nodes2) standsFor.set(n.id, id);
15658
- const status = summaryStatus(nodes2);
15658
+ const status2 = summaryStatus(nodes2);
15659
15659
  summaries.push({
15660
15660
  id,
15661
15661
  kind: "member",
@@ -15665,7 +15665,7 @@ function collapseBoxes(ir, opts = {}) {
15665
15665
  summary: boxBadgeText(nodes2),
15666
15666
  cards: nodes2.length,
15667
15667
  _collapsed: true,
15668
- ...status ? { _status: status } : {}
15668
+ ...status2 ? { _status: status2 } : {}
15669
15669
  }
15670
15670
  });
15671
15671
  delete groups[key];
@@ -16023,8 +16023,8 @@ function bandedPlan(ir, bands) {
16023
16023
  });
16024
16024
  });
16025
16025
  const statuses = new Set(members.map((id) => statusOf.get(id)));
16026
- const status = statuses.size === 1 ? [...statuses][0] : void 0;
16027
- boxes.push({ title, x: panelW / 2, y: top + panelH / 2, w: panelW, h: panelH, ...status ? { status } : {} });
16026
+ const status2 = statuses.size === 1 ? [...statuses][0] : void 0;
16027
+ boxes.push({ title, x: panelW / 2, y: top + panelH / 2, w: panelW, h: panelH, ...status2 ? { status: status2 } : {} });
16028
16028
  top += panelH + BAND_GAP;
16029
16029
  }
16030
16030
  return { placed, boxes, width: panelW, height: Math.max(1, top - BAND_GAP) };
@@ -17635,9 +17635,9 @@ function neutralizeNegations(lower) {
17635
17635
  const text = lower.replace(/\bnot[_\-\s]?[a-z]+\b/g, mark).replace(/\b[a-z]+\s*[=:]\s*false\b/g, mark);
17636
17636
  return { text, negated };
17637
17637
  }
17638
- function classifyHealth(status) {
17639
- if (!status) return "unknown";
17640
- const { text, negated } = neutralizeNegations(status.toLowerCase());
17638
+ function classifyHealth(status2) {
17639
+ if (!status2) return "unknown";
17640
+ const { text, negated } = neutralizeNegations(status2.toLowerCase());
17641
17641
  if (DEGRADED.test(text)) return "degraded";
17642
17642
  if (PROGRESSING.test(text)) return "progressing";
17643
17643
  if (negated) return "degraded";
@@ -17725,9 +17725,9 @@ function readyFromConditions(tree) {
17725
17725
  for (const c of conditions) {
17726
17726
  const cond = rec4(c);
17727
17727
  if (!cond || cond.type !== "Ready") continue;
17728
- const status = str5(cond.status);
17729
- if (status !== "True" && status !== "False" && status !== "Unknown") continue;
17730
- return { status, reason: str5(cond.reason) };
17728
+ const status2 = str5(cond.status);
17729
+ if (status2 !== "True" && status2 !== "False" && status2 !== "Unknown") continue;
17730
+ return { status: status2, reason: str5(cond.reason) };
17731
17731
  }
17732
17732
  return void 0;
17733
17733
  }
@@ -17796,8 +17796,8 @@ function unhappyCondition(o, type) {
17796
17796
  for (const c of raw) {
17797
17797
  const cond = rec4(c);
17798
17798
  if (!cond || cond.type !== type) continue;
17799
- const status = str5(cond.status);
17800
- const unhappy = type === "ReplicaFailure" ? status === "True" : status === "False";
17799
+ const status2 = str5(cond.status);
17800
+ const unhappy = type === "ReplicaFailure" ? status2 === "True" : status2 === "False";
17801
17801
  if (!unhappy) continue;
17802
17802
  const reason = str5(cond.reason);
17803
17803
  const message2 = str5(cond.message);
@@ -23544,11 +23544,30 @@ var TerragucciProgress = class {
23544
23544
  p.catch(() => this.cached = void 0);
23545
23545
  return p;
23546
23546
  }
23547
+ /**
23548
+ * #569: one commit's run view, not the newest: the kiosk keeps the graph on
23549
+ * the cycle its narration is about while the loop's next runs (the reset's
23550
+ * push back to the base) already went. Cached like `answer`, per commit; the
23551
+ * poll's watch stays on the newest.
23552
+ */
23553
+ answerFor(commit, fresh = false) {
23554
+ const key = `${this.o.checkoutProject?.() ?? ""}\0${commit}`;
23555
+ const at2 = Date.now();
23556
+ const hit = this.pinned.get(key);
23557
+ if (!fresh && hit && at2 - hit.at < PROGRESS_TTL_MS) return hit.answer;
23558
+ for (const [k, v] of this.pinned) if (at2 - v.at >= PROGRESS_TTL_MS) this.pinned.delete(k);
23559
+ const p = this.read(commit);
23560
+ this.pinned.set(key, { at: at2, answer: p });
23561
+ p.catch(() => this.pinned.delete(key));
23562
+ return p;
23563
+ }
23564
+ pinned = /* @__PURE__ */ new Map();
23547
23565
  /** Drop the cached answer: the index changed, so the newest commit may have. */
23548
23566
  forget() {
23549
23567
  this.cached = void 0;
23568
+ this.pinned.clear();
23550
23569
  }
23551
- async read() {
23570
+ async read(commit) {
23552
23571
  const v = this.o.validators?.();
23553
23572
  const src = this.o.source;
23554
23573
  let text;
@@ -23569,13 +23588,13 @@ var TerragucciProgress = class {
23569
23588
  const rows = doc.reports;
23570
23589
  const checkout = this.o.checkoutProject?.();
23571
23590
  const project = pickProject(rows, { ...this.o.project ? { project: this.o.project } : {}, ...checkout ? { checkoutProject: checkout } : {} }, src);
23572
- const row = newestApply(rows, project);
23591
+ const row = commit ? newestApply(rows.filter((r) => r.commit === commit || r.commit.startsWith(commit)), project) : newestApply(rows, project);
23573
23592
  if (!row) {
23574
- this.watch = void 0;
23593
+ if (!commit) this.watch = void 0;
23575
23594
  return {
23576
23595
  ...this.base(project, v?.run.validation),
23577
23596
  run: null,
23578
- absent: `index.json lists no tf-apply run of ${project} yet, so there is no run view to read.`,
23597
+ absent: commit ? `index.json lists no tf-apply run of ${project} on ${commit.slice(0, 12)} yet, so there is no run view of it to read.` : `index.json lists no tf-apply run of ${project} yet, so there is no run view to read.`,
23579
23598
  states: [],
23580
23599
  reads: [],
23581
23600
  waves: [],
@@ -23586,7 +23605,7 @@ var TerragucciProgress = class {
23586
23605
  }
23587
23606
  const key = runKey(row);
23588
23607
  const a = await this.readRun(project, row.commit, key, v?.run);
23589
- this.watch = { key, commit: row.commit, project, last: a };
23608
+ if (!commit) this.watch = { key, commit: row.commit, project, last: a };
23590
23609
  return a;
23591
23610
  }
23592
23611
  base(project, validation) {
@@ -23677,7 +23696,14 @@ var TerragucciProgress = class {
23677
23696
  function terragucciProgressRoute(app, progress) {
23678
23697
  app.get("/api/terragucci/progress", async (c) => {
23679
23698
  try {
23680
- return c.json(await progress.answer(new URL(c.req.url).searchParams.get("fresh") === "1"));
23699
+ const q = new URL(c.req.url).searchParams;
23700
+ const fresh = q.get("fresh") === "1";
23701
+ const commit = q.get("commit");
23702
+ if (commit !== null) {
23703
+ if (!/^[0-9a-f]{7,40}$/.test(commit)) return c.json({ error: "commit is a commit sha, 7 to 40 hex digits", code: "terragucci-progress" }, 400);
23704
+ return c.json(await progress.answerFor(commit, fresh));
23705
+ }
23706
+ return c.json(await progress.answer(fresh));
23681
23707
  } catch (e) {
23682
23708
  if (e instanceof TerragucciReadError) return c.json(e.refusal, 422);
23683
23709
  if (e instanceof ProgressGraphError) return c.json({ error: e.message, code: "terragucci-graph", remedy: "`behold doctor` names what the Terraform read needs." }, 422);
@@ -23770,6 +23796,123 @@ function kioskRoute(app, webRoot2, enabled) {
23770
23796
  });
23771
23797
  }
23772
23798
 
23799
+ // src/terragucci-runs.ts
23800
+ var RUNS_INDEX_KEY = "runs/index.json";
23801
+ var runKey2 = (id) => `runs/${id}.json`;
23802
+ var RUNS_READ_TTL_MS = 2e3;
23803
+ var RUN_STATUSES = ["queued", "running", "waiting", "success", "failure", "cancelled", "skipped"];
23804
+ var isRecord18 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
23805
+ var str14 = (v) => typeof v === "string";
23806
+ var int2 = (v) => typeof v === "number" && Number.isInteger(v);
23807
+ var timeOrNull = (v) => v === null || v === void 0 || str14(v);
23808
+ var status = (v) => str14(v) && RUN_STATUSES.includes(v);
23809
+ function jobProblem(j, withSteps, at2) {
23810
+ if (!isRecord18(j)) return `${at2} is not an object`;
23811
+ if (!int2(j.id)) return `${at2}.id is not an integer`;
23812
+ if (!str14(j.name)) return `${at2}.name is not a string`;
23813
+ if (!Array.isArray(j.needs) || !j.needs.every(str14)) return `${at2}.needs is not a list of job names`;
23814
+ if (!status(j.status)) return `${at2}.status is not one of ${RUN_STATUSES.join(", ")}`;
23815
+ if (!timeOrNull(j.started_at) || !timeOrNull(j.ended_at)) return `${at2}'s times are neither times nor null`;
23816
+ if (withSteps) {
23817
+ if (!Array.isArray(j.steps)) return `${at2}.steps is not a list`;
23818
+ for (const [i, s] of j.steps.entries()) {
23819
+ const sat = `${at2}.steps[${i}]`;
23820
+ if (!isRecord18(s)) return `${sat} is not an object`;
23821
+ if (!str14(s.name)) return `${sat}.name is not a string`;
23822
+ if (!str14(s.status)) return `${sat}.status is not a string`;
23823
+ if (!Array.isArray(s.log) || !s.log.every(str14)) return `${sat}.log is not a list of lines`;
23824
+ if (s.truncated !== void 0 && !int2(s.truncated)) return `${sat}.truncated is not an integer`;
23825
+ }
23826
+ }
23827
+ return void 0;
23828
+ }
23829
+ function runProblem(r, withSteps, at2) {
23830
+ if (!isRecord18(r)) return `${at2} is not an object`;
23831
+ if (!int2(r.id)) return `${at2}.id is not an integer`;
23832
+ if (!str14(r.title)) return `${at2}.title is not a string`;
23833
+ if (!status(r.status)) return `${at2}.status is not one of ${RUN_STATUSES.join(", ")}`;
23834
+ if (r.trigger !== null && r.trigger !== void 0 && !str14(r.trigger)) return `${at2}.trigger is not a string`;
23835
+ if (!timeOrNull(r.created_at) || !timeOrNull(r.started_at) || !timeOrNull(r.ended_at)) return `${at2}'s times are neither times nor null`;
23836
+ if (!Array.isArray(r.jobs)) return `${at2}.jobs is not a list`;
23837
+ for (const [i, j] of r.jobs.entries()) {
23838
+ const p = jobProblem(j, withSteps, `${at2}.jobs[${i}]`);
23839
+ if (p) return p;
23840
+ }
23841
+ return void 0;
23842
+ }
23843
+ function runsIndexProblem(doc) {
23844
+ if (!isRecord18(doc)) return "it is not an object";
23845
+ if (doc.schema !== "demo.runs/v1") return `schema is ${JSON.stringify(doc.schema)}, not demo.runs/v1`;
23846
+ if (!Array.isArray(doc.runs)) return "runs is not a list";
23847
+ for (const [i, r] of doc.runs.entries()) {
23848
+ const p = runProblem(r, false, `runs[${i}]`);
23849
+ if (p) return p;
23850
+ }
23851
+ return void 0;
23852
+ }
23853
+ function runProblemOf(doc, id) {
23854
+ if (!isRecord18(doc)) return "it is not an object";
23855
+ if (doc.schema !== "demo.run/v1") return `schema is ${JSON.stringify(doc.schema)}, not demo.run/v1`;
23856
+ if (doc.id !== id) return `its id is ${JSON.stringify(doc.id)}, not ${id}`;
23857
+ return runProblem(doc, true, "the run");
23858
+ }
23859
+ async function readDoc2(source2, key, what, problem, now) {
23860
+ const base = { source: source2.location, read: now.toISOString(), key };
23861
+ let body;
23862
+ try {
23863
+ body = await source2.read(key);
23864
+ } catch (e) {
23865
+ if (e instanceof TerragucciDenied) return { ...base, doc: null, error: deniedWords(e, what), code: "terragucci-runs" };
23866
+ return { ...base, doc: null, error: `behold could not read ${key} from ${source2.location}: ${e instanceof Error ? e.message : String(e)}`, code: "terragucci-runs" };
23867
+ }
23868
+ if (body === void 0) return { ...base, doc: null, absent: `${source2.location} has no ${key}: the demo's loop driver writes the pipeline runs there (terragucci's stack/demo.sh).` };
23869
+ let doc;
23870
+ try {
23871
+ doc = JSON.parse(body);
23872
+ } catch {
23873
+ return { ...base, doc: null, error: `${key} in ${source2.location} is not JSON.`, code: "terragucci-runs" };
23874
+ }
23875
+ const p = problem(doc);
23876
+ if (p) return { ...base, doc: null, error: `${key} in ${source2.location} is not the driver's format: ${p}.`, code: "terragucci-runs" };
23877
+ return { ...base, doc };
23878
+ }
23879
+ function readRunsIndex(source2, now = /* @__PURE__ */ new Date()) {
23880
+ return readDoc2(source2, RUNS_INDEX_KEY, "the demo's pipeline runs", runsIndexProblem, now);
23881
+ }
23882
+ function readRun(source2, id, now = /* @__PURE__ */ new Date()) {
23883
+ return readDoc2(source2, runKey2(id), `pipeline run ${id}`, (d) => runProblemOf(d, id), now);
23884
+ }
23885
+ function runIdOf(raw) {
23886
+ return /^[1-9][0-9]{0,15}$/.test(raw) ? Number(raw) : null;
23887
+ }
23888
+ function terragucciRunsRoutes(app, source2, opts = {}) {
23889
+ const cache6 = /* @__PURE__ */ new Map();
23890
+ const shared2 = (key, read) => {
23891
+ const at2 = Date.now();
23892
+ const hit = cache6.get(key);
23893
+ if (hit && at2 - hit.at < RUNS_READ_TTL_MS) return hit.read;
23894
+ for (const [k, v] of cache6) if (at2 - v.at >= RUNS_READ_TTL_MS) cache6.delete(k);
23895
+ const p = read();
23896
+ cache6.set(key, { at: at2, read: p });
23897
+ return p;
23898
+ };
23899
+ const answer = (a) => {
23900
+ const status2 = "error" in a ? 502 : "absent" in a ? 404 : 200;
23901
+ return { body: a, status: status2 };
23902
+ };
23903
+ const now = () => opts.now ? opts.now() : /* @__PURE__ */ new Date();
23904
+ app.get("/api/terragucci/runs", async (c) => {
23905
+ const { body, status: status2 } = answer(await shared2(RUNS_INDEX_KEY, () => readRunsIndex(source2, now())));
23906
+ return c.json(body, status2, { "cache-control": "no-store" });
23907
+ });
23908
+ app.get("/api/terragucci/runs/:id", async (c) => {
23909
+ const id = runIdOf(c.req.param("id"));
23910
+ if (id === null) return c.json({ error: "a run id is a positive integer", code: "terragucci-runs" }, 400);
23911
+ const { body, status: status2 } = answer(await shared2(runKey2(id), () => readRun(source2, id, now())));
23912
+ return c.json(body, status2, { "cache-control": "no-store" });
23913
+ });
23914
+ }
23915
+
23773
23916
  // src/terragucci-view.ts
23774
23917
  var TERRAGUCCI_READ_TTL_MS = 3e4;
23775
23918
  function repoBase(dirs) {
@@ -23890,6 +24033,8 @@ function terragucciRoutes(app, opts, dirs) {
23890
24033
  });
23891
24034
  });
23892
24035
  terragucciDemoRoute(app, source2, opts.now ? { now: opts.now } : {});
24036
+ const runsSource = opts.runsSource ? terragucciSource(opts.runsSource, { ...opts.aws ? { aws: opts.aws } : {}, ...opts.fetch ? { fetch: opts.fetch } : {} }) : source2;
24037
+ terragucciRunsRoutes(app, runsSource, opts.now ? { now: opts.now } : {});
23893
24038
  terragucciTimelineRoute(app, source2, {
23894
24039
  ...opts.project ? { project: opts.project } : {},
23895
24040
  checkoutProject: () => checkoutProject(repoBase(dirs())),
@@ -23954,7 +24099,7 @@ var heldByOf = (v) => {
23954
24099
  return h && typeof h === "object" && typeof h.hook === "string" && typeof h.moment === "string" ? { hook: h.hook, moment: h.moment, ...typeof h.target === "string" ? { target: h.target } : {} } : void 0;
23955
24100
  };
23956
24101
  var modeOf = (v) => v === "ledger" || v === "pr-review" || v === "sealed" ? v : void 0;
23957
- var str14 = (v) => typeof v === "string" && v !== "" ? v : void 0;
24102
+ var str15 = (v) => typeof v === "string" && v !== "" ? v : void 0;
23958
24103
  var strings3 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
23959
24104
  var time3 = (iso) => Date.parse(iso) || 0;
23960
24105
  function rowOf(line) {
@@ -23970,14 +24115,14 @@ function rowOf(line) {
23970
24115
  }
23971
24116
  function sealOf(r) {
23972
24117
  const s = r.seal;
23973
- const signed = s && s.signature ? { sealed: true, ...str14(s.signer) ? { signer: str14(s.signer) } : {} } : void 0;
24118
+ const signed = s && s.signature ? { sealed: true, ...str15(s.signer) ? { signer: str15(s.signer) } : {} } : void 0;
23974
24119
  if (r.review || r.via === "pr-review") return { seal: "review", ...signed };
23975
24120
  return signed ? { seal: "sealed", ...signed } : { seal: "unsigned" };
23976
24121
  }
23977
24122
  function reviewOf(r) {
23978
24123
  const rv = r.review ?? void 0;
23979
24124
  const pr = typeof rv?.pr === "number" ? rv.pr : typeof r.pr === "number" ? r.pr : void 0;
23980
- const head = str14(rv?.head) ?? str14(r.head);
24125
+ const head = str15(rv?.head) ?? str15(r.head);
23981
24126
  const approvers = strings3(rv?.approvers) ?? strings3(r.reviewers);
23982
24127
  if (pr === void 0 && head === void 0 && approvers === void 0) return void 0;
23983
24128
  return { ...pr !== void 0 ? { pr } : {}, ...head ? { head } : {}, ...approvers ? { approvers } : {} };
@@ -24003,7 +24148,7 @@ function decisionOf(d) {
24003
24148
  const r = v;
24004
24149
  if (!r || typeof r !== "object" || typeof r.wave !== "number" || typeof r.name !== "string" || typeof r.op !== "string" || typeof r.gate !== "string") return void 0;
24005
24150
  const outcomes2 = Array.isArray(r.outcomes) ? r.outcomes.filter((o) => !!o && typeof o === "object" && typeof o.moment === "string" && typeof o.name === "string" && typeof o.exit === "number" && typeof o.outcome === "string") : void 0;
24006
- return { wave: r.wave, name: r.name, op: r.op, gate: r.gate, ...str14(r.digest) ? { digest: str14(r.digest) } : {}, ...strings3(r.heldBy) ? { heldBy: strings3(r.heldBy) } : {}, ...r.heldOn !== void 0 ? { heldOn: r.heldOn } : {}, ...outcomes2 ? { outcomes: outcomes2 } : {} };
24151
+ return { wave: r.wave, name: r.name, op: r.op, gate: r.gate, ...str15(r.digest) ? { digest: str15(r.digest) } : {}, ...strings3(r.heldBy) ? { heldBy: strings3(r.heldBy) } : {}, ...r.heldOn !== void 0 ? { heldOn: r.heldOn } : {}, ...outcomes2 ? { outcomes: outcomes2 } : {} };
24007
24152
  }
24008
24153
  function deriveGateAudit(input) {
24009
24154
  const isOverride = input.isOverride ?? isOverrideGate2;
@@ -24028,17 +24173,17 @@ function deriveGateAudit(input) {
24028
24173
  const requests = [];
24029
24174
  for (const p of parsed) {
24030
24175
  if (!p.pending || p.line.removed) continue;
24031
- const digest = str14(p.r.planDigest);
24176
+ const digest = str15(p.r.planDigest);
24032
24177
  denials.set(`${String(p.r.gate)}
24033
24178
  ${digest ?? ""}`, p.r);
24034
- requests.push({ op: ledger.op, gate: String(p.r.gate), at: time3(String(p.r.timestamp)), ...digest ? { digest } : {}, ...str14(p.r.environment) ? { environment: str14(p.r.environment) } : {} });
24179
+ requests.push({ op: ledger.op, gate: String(p.r.gate), at: time3(String(p.r.timestamp)), ...digest ? { digest } : {}, ...str15(p.r.environment) ? { environment: str15(p.r.environment) } : {} });
24035
24180
  }
24036
24181
  for (const { line, n, r, pending } of parsed) {
24037
24182
  const gate = String(r.gate);
24038
24183
  const override = isOverride(ledger.op, gate);
24039
24184
  const removed = line.removed;
24040
24185
  if (pending && removed) continue;
24041
- const digest = str14(r.planDigest);
24186
+ const digest = str15(r.planDigest);
24042
24187
  const author = removed ?? line.added;
24043
24188
  const source2 = {
24044
24189
  path,
@@ -24057,7 +24202,7 @@ ${digest ?? ""}`, p.r);
24057
24202
  ...base,
24058
24203
  kind: override ? "override-requested" : "approval-requested",
24059
24204
  at: String(r.timestamp),
24060
- ...drop({ runId: str14(r.runId), expiresAt: str14(r.expiresAt), description: str14(r.description), url: str14(r.url), rules: strings3(r.rules), wave: waveOf(r.wave), heldBy: heldByOf(r.heldBy), approvalMode: modeOf(r.approvalMode) }),
24205
+ ...drop({ runId: str15(r.runId), expiresAt: str15(r.expiresAt), description: str15(r.description), url: str15(r.url), rules: strings3(r.rules), wave: waveOf(r.wave), heldBy: heldByOf(r.heldBy), approvalMode: modeOf(r.approvalMode) }),
24061
24206
  detail: extra2
24062
24207
  }));
24063
24208
  continue;
@@ -24085,13 +24230,13 @@ ${digest ?? ""}`) : void 0;
24085
24230
  at: String(r.timestamp),
24086
24231
  by: String(r.resolvedBy),
24087
24232
  seal,
24088
- ...drop({ sealed, signer, committedBy: line.added?.by, relayedBy: str14(r.relayedBy), approver: approverOf(r), note: str14(r.note), url: str14(r.url), review: reviewOf(r), rules }),
24233
+ ...drop({ sealed, signer, committedBy: line.added?.by, relayedBy: str15(r.relayedBy), approver: approverOf(r), note: str15(r.note), url: str15(r.url), review: reviewOf(r), rules }),
24089
24234
  detail: extra2
24090
24235
  });
24091
24236
  entries.push(entry);
24092
24237
  if (!override) {
24093
24238
  const at2 = time3(entry.at);
24094
- const env = str14(r.environment);
24239
+ const env = str15(r.environment);
24095
24240
  const asked = requests.some((q) => q.gate === gate && q.at <= at2 && compatible(q.digest, digest) && (q.environment === void 0 || env === void 0 || q.environment === env));
24096
24241
  if (!asked) {
24097
24242
  gaps.push({ kind: "approval-without-request", op: ledger.op, gate, ...digest ? { digest } : {}, detail: `${path} holds an approval of ${gate} by ${entry.by} at ${entry.at} with no pending fact of that gate${digest ? " and digest" : ""} before it` });
@@ -24838,21 +24983,21 @@ function controlRepo(dirs, roots = () => checkoutRoots(dirs).length) {
24838
24983
  }
24839
24984
  return n === 0 ? { config, projects } : void 0;
24840
24985
  }
24841
- var isRecord18 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
24842
- var str15 = (v) => typeof v === "string";
24843
- var int2 = (v) => typeof v === "number" && Number.isInteger(v);
24986
+ var isRecord19 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
24987
+ var str16 = (v) => typeof v === "string";
24988
+ var int3 = (v) => typeof v === "number" && Number.isInteger(v);
24844
24989
  function structuralRun2(doc) {
24845
- if (!isRecord18(doc)) return "it is not a JSON object";
24990
+ if (!isRecord19(doc)) return "it is not a JSON object";
24846
24991
  if (doc.schema !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(doc.schema)}, not ${RUN_SCHEMA2}`;
24847
- if (!str15(doc.project) || !str15(doc.commit) || !str15(doc.updated)) return "it lacks project, commit or updated";
24992
+ if (!str16(doc.project) || !str16(doc.commit) || !str16(doc.updated)) return "it lacks project, commit or updated";
24848
24993
  if (!Array.isArray(doc.roots) || !Array.isArray(doc.waves)) return "it has no roots or waves array";
24849
24994
  for (const [i, r] of doc.roots.entries()) {
24850
- if (!isRecord18(r) || !str15(r.root) || !int2(r.wave) || !Array.isArray(r.reads) || !r.reads.every(str15)) return `root ${i} lacks root, wave or reads`;
24851
- if (r.state !== void 0 && !(isRecord18(r.state) && str15(r.state.key))) return `root ${String(r.root)}'s state has no key`;
24852
- if (r.external !== void 0 && !(Array.isArray(r.external) && r.external.every((x) => isRecord18(x) && str15(x.data) && str15(x.key)))) return `root ${String(r.root)}'s external reads lack data or key`;
24995
+ if (!isRecord19(r) || !str16(r.root) || !int3(r.wave) || !Array.isArray(r.reads) || !r.reads.every(str16)) return `root ${i} lacks root, wave or reads`;
24996
+ if (r.state !== void 0 && !(isRecord19(r.state) && str16(r.state.key))) return `root ${String(r.root)}'s state has no key`;
24997
+ if (r.external !== void 0 && !(Array.isArray(r.external) && r.external.every((x) => isRecord19(x) && str16(x.data) && str16(x.key)))) return `root ${String(r.root)}'s external reads lack data or key`;
24853
24998
  }
24854
24999
  for (const [i, w] of doc.waves.entries()) {
24855
- if (!isRecord18(w) || !int2(w.number) || !Array.isArray(w.roots) || !Array.isArray(w.reads) || !str15(w.state) || !str15(w.gate)) return `wave ${i} lacks number, roots, reads, state or gate`;
25000
+ if (!isRecord19(w) || !int3(w.number) || !Array.isArray(w.roots) || !Array.isArray(w.reads) || !str16(w.state) || !str16(w.gate)) return `wave ${i} lacks number, roots, reads, state or gate`;
24856
25001
  }
24857
25002
  return void 0;
24858
25003
  }
@@ -24884,7 +25029,7 @@ function runViewValidator(dirs = []) {
24884
25029
  v = {
24885
25030
  validation: { by: "schema", terragucci: version },
24886
25031
  check(doc) {
24887
- const id = isRecord18(doc) ? doc.schema : void 0;
25032
+ const id = isRecord19(doc) ? doc.schema : void 0;
24888
25033
  if (id !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(id)}, not ${RUN_SCHEMA2}`;
24889
25034
  if (validate(doc)) return void 0;
24890
25035
  const e = validate.errors?.[0];
@@ -24905,7 +25050,7 @@ var refuse13 = (error, remedy) => {
24905
25050
  throw new TerragucciReadError(refusal(error, remedy));
24906
25051
  };
24907
25052
  var trimSlashes = (s) => s.replace(/^\/+|\/+$/g, "");
24908
- function runKey2(p, commit) {
25053
+ function runKey3(p, commit) {
24909
25054
  const page = p.run_view?.commit === commit ? p.run_view.page : void 0;
24910
25055
  if (page && !/^[a-z]+:\/\//i.test(page) && !page.startsWith("/") && !page.split("/").includes("..") && page.endsWith("/run.html")) {
24911
25056
  return `${trimSlashes(page.slice(0, -"run.html".length))}/run.json`;
@@ -24986,7 +25131,7 @@ async function readControlEstate(source2, config, opts = {}) {
24986
25131
  if (!commit) {
24987
25132
  missing = p.status === "ok" ? "no apply has run, so there is no run view" : p.status === "no-index" ? "no runs in the bucket yet" : `its index could not be read${p.error ? `: ${p.error}` : ""}`;
24988
25133
  } else {
24989
- const key = runKey2(p, commit);
25134
+ const key = runKey3(p, commit);
24990
25135
  let body;
24991
25136
  try {
24992
25137
  body = await get(key);
@@ -25607,9 +25752,9 @@ function yodelValidator(dirs = []) {
25607
25752
  pinned ??= compile({ common: common_schema_default, report: report_schema_default, drift: drift_schema_default }, { by: "schema", from: "pinned", commit: PINNED_COMMIT.slice(0, 7) });
25608
25753
  return pinned;
25609
25754
  }
25610
- var isRecord19 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
25611
- var looksLikeReport = (d) => isRecord19(d) && "generatedAt" in d && "environments" in d;
25612
- var looksLikeDrift = (d) => isRecord19(d) && "environment" in d && ("drifted" in d || "changed" in d);
25755
+ var isRecord20 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
25756
+ var looksLikeReport = (d) => isRecord20(d) && "generatedAt" in d && "environments" in d;
25757
+ var looksLikeDrift = (d) => isRecord20(d) && "environment" in d && ("drifted" in d || "changed" in d);
25613
25758
  function filesOf(source2) {
25614
25759
  let st;
25615
25760
  try {
@@ -25719,7 +25864,7 @@ function yodelReportRoute(app, source2, dirs) {
25719
25864
  init_terraform_member();
25720
25865
  import { existsSync as existsSync25, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync32, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
25721
25866
  import { dirname as dirname16, join as join44, resolve as resolve28 } from "node:path";
25722
- var isRecord20 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
25867
+ var isRecord21 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
25723
25868
  function linkDemoPins(work, probes = {}) {
25724
25869
  const file = join44(work, "chant.workspace.json");
25725
25870
  if (!existsSync25(file)) return { ok: true, linked: [] };
@@ -25729,12 +25874,12 @@ function linkDemoPins(work, probes = {}) {
25729
25874
  } catch {
25730
25875
  return { ok: true, linked: [] };
25731
25876
  }
25732
- if (!isRecord20(decl) || !Array.isArray(decl.pins)) return { ok: true, linked: [] };
25877
+ if (!isRecord21(decl) || !Array.isArray(decl.pins)) return { ok: true, linked: [] };
25733
25878
  const find = probes.resolve ?? ((pkg) => resolvedPackage(pkg));
25734
25879
  const linked = [];
25735
25880
  let rewrite = false;
25736
25881
  for (const pin of decl.pins) {
25737
- if (!isRecord20(pin) || typeof pin.package !== "string") continue;
25882
+ if (!isRecord21(pin) || typeof pin.package !== "string") continue;
25738
25883
  const pkg = pin.package;
25739
25884
  if (pkg === TERRAFORM_LEXICON_PKG) {
25740
25885
  const refusal2 = (probes.reader ?? terraformReaderState)().refusal;
@@ -28328,11 +28473,11 @@ async function runExport(cfg, outDir, opts = {}) {
28328
28473
  `
28329
28474
  );
28330
28475
  }
28331
- var isRecord21 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
28476
+ var isRecord22 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
28332
28477
  function dropSource(v) {
28333
28478
  if (Array.isArray(v)) return v.forEach(dropSource);
28334
- if (!isRecord21(v)) return;
28335
- if (isRecord21(v.attrs)) delete v.attrs.source;
28479
+ if (!isRecord22(v)) return;
28480
+ if (isRecord22(v.attrs)) delete v.attrs.source;
28336
28481
  for (const x of Object.values(v)) dropSource(x);
28337
28482
  }
28338
28483
  function shapeSnapshot(key, body, opts = {}) {
@@ -28342,7 +28487,7 @@ function shapeSnapshot(key, body, opts = {}) {
28342
28487
  } catch {
28343
28488
  return body;
28344
28489
  }
28345
- if (!isRecord21(doc)) return body;
28490
+ if (!isRecord22(doc)) return body;
28346
28491
  if (key === "/api/project") {
28347
28492
  delete doc.recents;
28348
28493
  delete doc.approver;
@@ -28412,7 +28557,7 @@ function manifestFiles(text) {
28412
28557
  } catch {
28413
28558
  return [];
28414
28559
  }
28415
- const map = isRecord21(doc) && isRecord21(doc.keyToFile) ? doc.keyToFile : {};
28560
+ const map = isRecord22(doc) && isRecord22(doc.keyToFile) ? doc.keyToFile : {};
28416
28561
  return Object.values(map).filter((f) => typeof f === "string" && /^snapshots\/[A-Za-z0-9=_.-]+$/.test(f) && !f.includes(".."));
28417
28562
  }
28418
28563
  function bundleFiles(outDir) {
@@ -28510,12 +28655,12 @@ function resolveCommit2(dir, given) {
28510
28655
  return { sha };
28511
28656
  }
28512
28657
  }
28513
- var isRecord22 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
28658
+ var isRecord23 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
28514
28659
  function parseHistory(text) {
28515
28660
  const doc = JSON.parse(text);
28516
28661
  if (!Array.isArray(doc)) throw new Error("history.json is not an array");
28517
- const str16 = (v) => typeof v === "string" ? v : "";
28518
- return doc.filter((e) => isRecord22(e) && typeof e.commit === "string" && COMMIT.test(e.commit)).map((e) => ({ commit: e.commit, at: str16(e.at), project: str16(e.project), generated: str16(e.generated) }));
28662
+ const str17 = (v) => typeof v === "string" ? v : "";
28663
+ return doc.filter((e) => isRecord23(e) && typeof e.commit === "string" && COMMIT.test(e.commit)).map((e) => ({ commit: e.commit, at: str17(e.at), project: str17(e.project), generated: str17(e.generated) }));
28519
28664
  }
28520
28665
  function nextHistory(old, entry, keep) {
28521
28666
  const all = [entry, ...old.filter((e) => e.commit !== entry.commit)];
@@ -28527,9 +28672,9 @@ var BUNDLE_PATH = /^[A-Za-z0-9=_.@+-]+(\/[A-Za-z0-9=_.@+-]+)*$/;
28527
28672
  var insideBundle = (f) => typeof f === "string" && BUNDLE_PATH.test(f) && !f.split("/").some((p) => p === "." || p === "..");
28528
28673
  function commitFiles(manifestText) {
28529
28674
  const doc = JSON.parse(manifestText);
28530
- if (!isRecord22(doc)) return ["manifest.json"];
28675
+ if (!isRecord23(doc)) return ["manifest.json"];
28531
28676
  const listed = Array.isArray(doc.files) ? doc.files.filter(insideBundle) : [];
28532
- const snaps = isRecord22(doc.keyToFile) ? Object.values(doc.keyToFile).filter(insideBundle) : [];
28677
+ const snaps = isRecord23(doc.keyToFile) ? Object.values(doc.keyToFile).filter(insideBundle) : [];
28533
28678
  const rest = [.../* @__PURE__ */ new Set([...listed, ...snaps])].filter((f) => f !== "index.html" && f !== "manifest.json").sort();
28534
28679
  return ["index.html", ...rest, "manifest.json"];
28535
28680
  }
@@ -28641,7 +28786,7 @@ Usage:
28641
28786
  [--yodel-report <file|dir>]
28642
28787
  [--publish s3://<bucket>/<prefix>/views/<name> [--commit <sha>] [--keep <n>]]
28643
28788
  behold serve <project-dir\u2026> [--port <n>] [--host <addr>] [--allow-host <name,\u2026>] [--hud <url>] [--env <name>] [--poll <secs>] [--local]
28644
- [--public] [--terragucci <src> [--terragucci-project <p>] [--terragucci-poll <secs>]]
28789
+ [--public] [--terragucci <src> [--terragucci-project <p>] [--terragucci-poll <secs>] [--terragucci-runs <src>]]
28645
28790
  [--yodel-report <file|dir>]
28646
28791
  behold carve <report.json> [--port <n>]
28647
28792
 
@@ -28742,6 +28887,11 @@ Options:
28742
28887
  moved to the page.
28743
28888
  0 turns it off. The fetch goes into behold's own cache
28744
28889
  (<tmpdir>/behold-lifecycle-*), never into the checkout.
28890
+ --terragucci-runs <src>
28891
+ serve with --terragucci only: where /kiosk reads the
28892
+ demo's pipeline runs (runs/index.json, runs/<id>.json),
28893
+ when not beside the reports: a directory, s3:// or
28894
+ http(s), as --terragucci takes.
28745
28895
  --yodel-report <file|dir>
28746
28896
  serve/export: mark SQL Yodeler's \`yodel report --json\`
28747
28897
  output and any \`yodel drift <env> --json\` beside it
@@ -28859,6 +29009,7 @@ ${USAGE}`);
28859
29009
  let terragucci;
28860
29010
  let terragucciProject;
28861
29011
  let terragucciPoll;
29012
+ let terragucciRuns;
28862
29013
  let yodelReport;
28863
29014
  let isPublic = false;
28864
29015
  const allowHosts = [];
@@ -28871,6 +29022,7 @@ ${USAGE}`);
28871
29022
  else if (a === "--terragucci") terragucci = rest[++i];
28872
29023
  else if (a === "--terragucci-project") terragucciProject = rest[++i];
28873
29024
  else if (a === "--terragucci-poll") terragucciPoll = Number(rest[++i]);
29025
+ else if (a === "--terragucci-runs") terragucciRuns = rest[++i];
28874
29026
  else if (a === "--yodel-report") yodelReport = rest[++i];
28875
29027
  else if (a === "--env") env = rest[++i];
28876
29028
  else if (a === "--poll") pollSecs = Number(rest[++i]);
@@ -28935,6 +29087,10 @@ ${USAGE}`);
28935
29087
  process.stderr.write("behold serve: --terragucci-poll needs --terragucci\n");
28936
29088
  process.exit(2);
28937
29089
  }
29090
+ if (terragucciRuns !== void 0 && terragucci === void 0) {
29091
+ process.stderr.write("behold serve: --terragucci-runs needs --terragucci\n");
29092
+ process.exit(2);
29093
+ }
28938
29094
  if (isPublic && projectDirs.length > 1) {
28939
29095
  process.stderr.write("behold serve: --public serves one project directory\n");
28940
29096
  process.exit(2);
@@ -28970,7 +29126,7 @@ ${USAGE}`);
28970
29126
  ...isPublic ? { public: true, layoutWrites: false } : {},
28971
29127
  ...target.workspace && hud ? { hud } : {},
28972
29128
  ...yodelReport ? { yodelReport: resolve33(yodelReport) } : {},
28973
- ...terragucci ? { terragucci: { source: terragucci, ...terragucciProject ? { project: terragucciProject } : {}, ...terragucciPoll !== void 0 ? { pollSecs: terragucciPoll } : {} } } : {}
29129
+ ...terragucci ? { terragucci: { source: terragucci, ...terragucciProject ? { project: terragucciProject } : {}, ...terragucciPoll !== void 0 ? { pollSecs: terragucciPoll } : {}, ...terragucciRuns ? { runsSource: terragucciRuns } : {} } } : {}
28974
29130
  });
28975
29131
  }
28976
29132
  var PUBLIC_TERRAGUCCI_POLL_SECS = 10;