fluncle 0.213.0 → 0.214.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.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +141 -20
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -610,7 +610,7 @@ function parseVersion(version) {
610
610
  var currentVersion;
611
611
  var init_version = __esm(() => {
612
612
  init_output();
613
- currentVersion = "0.213.0".trim() ? "0.213.0".trim() : "0.1.0";
613
+ currentVersion = "0.214.0".trim() ? "0.214.0".trim() : "0.1.0";
614
614
  });
615
615
 
616
616
  // src/update-notifier.ts
@@ -2452,7 +2452,7 @@ function parseVersion2(version) {
2452
2452
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2453
2453
  var init_version2 = __esm(() => {
2454
2454
  init_output();
2455
- currentVersion2 = "0.213.0".trim() ? "0.213.0".trim() : "0.1.0";
2455
+ currentVersion2 = "0.214.0".trim() ? "0.214.0".trim() : "0.1.0";
2456
2456
  });
2457
2457
 
2458
2458
  // ../../packages/registry/src/index.ts
@@ -4478,9 +4478,21 @@ __export(exports_admin_telemetry, {
4478
4478
  });
4479
4479
  async function telemetryCommand(options) {
4480
4480
  const params = new URLSearchParams({ limit: String(options.limit) });
4481
+ if (options.blind === true) {
4482
+ params.set("blind", "true");
4483
+ }
4481
4484
  if (options.cursor !== undefined) {
4482
4485
  params.set("cursor", options.cursor);
4483
4486
  }
4487
+ if (options.liar === true) {
4488
+ params.set("liar", "true");
4489
+ }
4490
+ if (options.missing === true) {
4491
+ params.set("missing", "true");
4492
+ }
4493
+ if (options.missingField !== undefined) {
4494
+ params.set("missingField", options.missingField);
4495
+ }
4484
4496
  if (options.ok !== undefined) {
4485
4497
  params.set("ok", String(options.ok));
4486
4498
  }
@@ -4507,16 +4519,50 @@ function cell(value) {
4507
4519
  function padded(columns, widths) {
4508
4520
  return columns.map((column, index) => column.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
4509
4521
  }
4510
- function telemetryLines(page) {
4522
+ function cadence(expectedIntervalMs) {
4523
+ if (expectedIntervalMs === null) {
4524
+ return "-";
4525
+ }
4526
+ const units = [
4527
+ { label: "d", milliseconds: 24 * 60 * 60 * 1000 },
4528
+ { label: "h", milliseconds: 60 * 60 * 1000 },
4529
+ { label: "m", milliseconds: 60 * 1000 },
4530
+ { label: "s", milliseconds: 1000 }
4531
+ ];
4532
+ for (const unit of units) {
4533
+ if (expectedIntervalMs % unit.milliseconds === 0) {
4534
+ return `${expectedIntervalMs / unit.milliseconds}${unit.label}`;
4535
+ }
4536
+ }
4537
+ return `${expectedIntervalMs}ms`;
4538
+ }
4539
+ function telemetryLines(page, options = {}) {
4511
4540
  if (!page.available) {
4512
4541
  return ["Telemetry database is not configured."];
4513
4542
  }
4514
- if (page.rows.length === 0) {
4543
+ if (options.missing === true || page.missingRoster.length > 0) {
4544
+ if (page.missingRoster.length === 0) {
4545
+ return ["No expected writers are missing."];
4546
+ }
4547
+ const missingHeader = ["MISSING UNIT", "CADENCE"];
4548
+ const missingRows = page.missingRoster.map((entry) => [
4549
+ entry.unit,
4550
+ cadence(entry.expectedIntervalMs)
4551
+ ]);
4552
+ const missingWidths = missingHeader.map((header, index) => Math.max(header.length, ...missingRows.map((row) => row[index]?.length ?? 0)));
4553
+ return [
4554
+ `Expected writers with no run (${missingRows.length})`,
4555
+ padded(missingHeader, missingWidths),
4556
+ ...missingRows.map((row) => padded(row, missingWidths))
4557
+ ];
4558
+ }
4559
+ if (page.rows.length === 0 && page.rollups.length === 0) {
4515
4560
  return ["No run events matched."];
4516
4561
  }
4517
- const rollupHeader = ["UNIT", "RUNS", "LAST", "OK=0", "LIAR", "BLIND"];
4562
+ const rollupHeader = ["UNIT", "CADENCE", "RUNS", "LAST", "OK=0", "LIAR", "BLIND"];
4518
4563
  const rollupRows = page.rollups.map((rollup) => [
4519
4564
  rollup.unit,
4565
+ cadence(rollup.expectedIntervalMs),
4520
4566
  String(rollup.runCount),
4521
4567
  rollup.lastOccurredAt,
4522
4568
  String(rollup.failedCount),
@@ -4547,15 +4593,18 @@ function telemetryLines(page) {
4547
4593
  row.summaryStatus
4548
4594
  ]);
4549
4595
  const runWidths = runHeader.map((header, index) => Math.max(header.length, ...runRows.map((row) => row[index]?.length ?? 0)));
4550
- const lines = [
4551
- `Unit rollups (${page.totalCount} matching runs)`,
4552
- padded(rollupHeader, rollupWidths),
4553
- ...rollupRows.map((row) => padded(row, rollupWidths)),
4554
- "",
4555
- `Runs (${page.rows.length} on this page)`,
4556
- padded(runHeader, runWidths),
4557
- ...runRows.map((row) => padded(row, runWidths))
4558
- ];
4596
+ const lines = [];
4597
+ if (rollupRows.length > 0) {
4598
+ lines.push("Unit rollups (all runs in the selected time/unit window)", padded(rollupHeader, rollupWidths), ...rollupRows.map((row) => padded(row, rollupWidths)));
4599
+ }
4600
+ if (lines.length > 0) {
4601
+ lines.push("");
4602
+ }
4603
+ if (runRows.length === 0) {
4604
+ lines.push("No evidence rows matched.");
4605
+ } else {
4606
+ lines.push(`Evidence rows (${page.rows.length} of ${page.totalCount} matching; this page)`, padded(runHeader, runWidths), ...runRows.map((row) => padded(row, runWidths)));
4607
+ }
4559
4608
  if (page.nextCursor !== null) {
4560
4609
  lines.push("", `Next cursor: ${page.nextCursor}`);
4561
4610
  }
@@ -9768,7 +9817,19 @@ function addAdminCommands(program2) {
9768
9817
  adminTelemetry.action(() => {
9769
9818
  adminTelemetry.outputHelp();
9770
9819
  });
9771
- adminTelemetry.command("read").description("Run-ledger rows plus per-unit rollups").option("--unit <unit>", "Only this systemd unit").option("--since <iso>", "Only runs at or after this occurred-at instant").option("--until <iso>", "Only runs at or before this occurred-at instant").option("--ok <true|false>", "Filter by the Worker's derived run verdict").option("--limit <limit>", "Rows to show (1-100)", "50").option("--cursor <cursor>", "Opaque cursor from the previous page").option("--json", "Print the lossless 20-column rows as JSON", false).action(async (options) => {
9820
+ adminTelemetry.command("read").description("Run-ledger rows plus per-unit rollups").option("--unit <unit>", "Only this systemd unit").option("--since <iso|age>", "Only runs at or after this instant (ISO, 24h, or 90m)").option("--until <iso>", "Only runs at or before this occurred-at instant").option("--ok <true|false>", "Filter by the Worker's derived run verdict").option("--liar", "Only rows whose claimed ok contradicts the derived verdict").option("--blind", "Only rows missing every work counter").option("--missing-field <name>", "Only rows missing this canonical counter").option("--missing", "List expected writers with no run in the selected window").option("--limit <limit>", "Rows to show (1-100)", "50").option("--cursor <cursor>", "Opaque cursor from the previous page").option("--json", "Print lossless rows, rollups, and missing writers as JSON", false).addHelpText("after", `
9821
+ Evidence and rollups:
9822
+ --ok, --liar, --blind, and --missing-field filter evidence rows only.
9823
+ RUNS always counts every run in the selected --unit/--since/--until window.
9824
+ --missing is a separate roster view; combine it only with --unit/--since/--until.
9825
+ --missing-field names: checked, produced, queue_depth, errors, expected_interval_ms.
9826
+
9827
+ JSON field reference:
9828
+ top-level ok Request acknowledgement only; never a run verdict.
9829
+ rows[].ok Worker's derived verdict for that run.
9830
+ rollups[] Whole-window unit totals, including expectedIntervalMs.
9831
+ missingRoster[] Expected writers with no run in the selected window.
9832
+ `).action(async (options) => {
9772
9833
  const { telemetryCommand: telemetryCommand2 } = await Promise.resolve().then(() => (init_admin_telemetry(), exports_admin_telemetry));
9773
9834
  await runAdminTelemetry(options, telemetryCommand2);
9774
9835
  });
@@ -12677,13 +12738,62 @@ function parseTelemetryOk(value) {
12677
12738
  }
12678
12739
  return value === "true";
12679
12740
  }
12680
- function parseTelemetryTimestamp(value, flag) {
12741
+ var telemetryMissingFields = [
12742
+ "checked",
12743
+ "errors",
12744
+ "expected_interval_ms",
12745
+ "produced",
12746
+ "queue_depth"
12747
+ ];
12748
+ function parseTelemetryMissingField(value) {
12681
12749
  if (value === undefined) {
12682
12750
  return;
12683
12751
  }
12752
+ const field = telemetryMissingFields.find((candidate) => candidate === value);
12753
+ if (field === undefined) {
12754
+ throw new Error(`--missing-field must be one of: ${telemetryMissingFields.join(", ")}`);
12755
+ }
12756
+ return field;
12757
+ }
12758
+ function parseExplicitTelemetryInstant(value) {
12759
+ if (value.length > 64) {
12760
+ return null;
12761
+ }
12762
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.exec(value);
12763
+ if (match === null) {
12764
+ return null;
12765
+ }
12766
+ const year = Number(match[1]);
12767
+ const month = Number(match[2]);
12768
+ const day = Number(match[3]);
12769
+ const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
12770
+ const daysInMonth = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
12771
+ const maximumDay = daysInMonth[month - 1];
12772
+ if (maximumDay === undefined || day < 1 || day > maximumDay) {
12773
+ return null;
12774
+ }
12684
12775
  const timestamp = new Date(value);
12685
- if (Number.isNaN(timestamp.getTime())) {
12686
- throw new Error(`${flag} must be an ISO-8601 timestamp`);
12776
+ return Number.isNaN(timestamp.getTime()) ? null : timestamp;
12777
+ }
12778
+ function parseTelemetryTimestamp(value, flag) {
12779
+ if (value === undefined) {
12780
+ return;
12781
+ }
12782
+ const relative = flag === "--since" ? value.match(/^([1-9]\d*)([mhdw])$/) : null;
12783
+ if (relative !== null) {
12784
+ const amount = Number(relative[1]);
12785
+ const unitMs = relative[2] === "w" ? 604800000 : relative[2] === "d" ? 86400000 : relative[2] === "h" ? 3600000 : 60000;
12786
+ const durationMs = amount * unitMs;
12787
+ const maximumDurationMs = 315360000000;
12788
+ if (value.length > 32 || !Number.isSafeInteger(amount) || !Number.isSafeInteger(durationMs) || durationMs > maximumDurationMs) {
12789
+ throw new Error("--since relative age must not exceed 3650d");
12790
+ }
12791
+ return value;
12792
+ }
12793
+ const timestamp = parseExplicitTelemetryInstant(value);
12794
+ if (timestamp === null) {
12795
+ const expected = flag === "--since" ? "an explicit ISO-8601 instant (with Z or offset) or a relative age like 24h or 90m" : "an explicit ISO-8601 instant with Z or offset";
12796
+ throw new Error(`${flag} must be ${expected}`);
12687
12797
  }
12688
12798
  return timestamp.toISOString();
12689
12799
  }
@@ -12755,14 +12865,23 @@ async function runAdminAttention(options, attentionQueueCommand2) {
12755
12865
  `));
12756
12866
  }
12757
12867
  async function runAdminTelemetry(options, telemetryCommand2) {
12868
+ if (options.missing === true && (options.blind === true || options.cursor !== undefined || options.liar === true || options.missingField !== undefined || options.ok !== undefined)) {
12869
+ throw new Error("--missing cannot be combined with --ok, --liar, --blind, --missing-field, or --cursor");
12870
+ }
12758
12871
  const since = parseTelemetryTimestamp(options.since, "--since");
12759
12872
  const until = parseTelemetryTimestamp(options.until, "--until");
12760
- if (since !== undefined && until !== undefined && since > until) {
12873
+ const sinceMs = since === undefined ? Number.NaN : Date.parse(since);
12874
+ const untilMs = until === undefined ? Number.NaN : Date.parse(until);
12875
+ if (Number.isFinite(sinceMs) && Number.isFinite(untilMs) && sinceMs > untilMs) {
12761
12876
  throw new Error("--since must be before or equal to --until");
12762
12877
  }
12763
12878
  const page = await telemetryCommand2({
12879
+ blind: options.blind,
12764
12880
  cursor: options.cursor,
12881
+ liar: options.liar,
12765
12882
  limit: parseTelemetryLimit(options.limit),
12883
+ missing: options.missing,
12884
+ missingField: parseTelemetryMissingField(options.missingField),
12766
12885
  ok: parseTelemetryOk(options.ok),
12767
12886
  since,
12768
12887
  unit: options.unit,
@@ -12773,7 +12892,7 @@ async function runAdminTelemetry(options, telemetryCommand2) {
12773
12892
  return;
12774
12893
  }
12775
12894
  const { telemetryLines: telemetryLines2 } = await Promise.resolve().then(() => (init_admin_telemetry(), exports_admin_telemetry));
12776
- console.log(telemetryLines2(page).join(`
12895
+ console.log(telemetryLines2(page, { missing: options.missing === true }).join(`
12777
12896
  `));
12778
12897
  }
12779
12898
  async function runAdminQueue(options, queueCommand2) {
@@ -13240,6 +13359,7 @@ var stringOptions = new Set([
13240
13359
  "--max-hop",
13241
13360
  "--max-overlap",
13242
13361
  "--metrics",
13362
+ "--missing-field",
13243
13363
  "--mime",
13244
13364
  "--min-phrase-words",
13245
13365
  "--model",
@@ -13323,5 +13443,6 @@ if (__require.main == __require.module) {
13323
13443
  await main();
13324
13444
  }
13325
13445
  export {
13446
+ parseTelemetryTimestamp,
13326
13447
  createProgram
13327
13448
  };
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.213.0"
34
+ "version": "0.214.0"
35
35
  }