rigscope 0.1.8 → 0.2.1

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 (94) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -1
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_not-found.html +1 -1
  12. package/.next/server/app/_not-found.rsc +3 -3
  13. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  14. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
  15. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  16. package/.next/server/app/administration/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/administration.html +1 -1
  18. package/.next/server/app/administration.rsc +3 -3
  19. package/.next/server/app/administration.segments/_full.segment.rsc +3 -3
  20. package/.next/server/app/administration.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +3 -3
  22. package/.next/server/app/alerts/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/alerts.html +1 -1
  24. package/.next/server/app/alerts.rsc +3 -3
  25. package/.next/server/app/alerts.segments/_full.segment.rsc +3 -3
  26. package/.next/server/app/alerts.segments/_tree.segment.rsc +2 -2
  27. package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +3 -3
  28. package/.next/server/app/connections/page.js +1 -1
  29. package/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  30. package/.next/server/app/connections.html +1 -1
  31. package/.next/server/app/connections.rsc +4 -4
  32. package/.next/server/app/connections.segments/_full.segment.rsc +4 -4
  33. package/.next/server/app/connections.segments/_tree.segment.rsc +2 -2
  34. package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +4 -4
  35. package/.next/server/app/history/page.js +2 -2
  36. package/.next/server/app/history/page_client-reference-manifest.js +1 -1
  37. package/.next/server/app/history.html +1 -1
  38. package/.next/server/app/history.rsc +6 -6
  39. package/.next/server/app/history.segments/_full.segment.rsc +6 -6
  40. package/.next/server/app/history.segments/_tree.segment.rsc +2 -2
  41. package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +6 -6
  42. package/.next/server/app/index.html +1 -1
  43. package/.next/server/app/index.rsc +3 -3
  44. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  45. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +1 -1
  49. package/.next/server/app/login.rsc +3 -3
  50. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  51. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  52. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  53. package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/processes.html +1 -1
  57. package/.next/server/app/processes.rsc +3 -3
  58. package/.next/server/app/processes.segments/_full.segment.rsc +3 -3
  59. package/.next/server/app/processes.segments/_tree.segment.rsc +2 -2
  60. package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +3 -3
  61. package/.next/server/app-paths-manifest.json +1 -1
  62. package/.next/server/chunks/161.js +1 -1
  63. package/.next/server/chunks/168.js +1 -1
  64. package/.next/server/middleware-build-manifest.js +1 -1
  65. package/.next/server/pages/404.html +1 -1
  66. package/.next/server/pages/500.html +1 -1
  67. package/.next/static/chunks/408-351fe4a1e9a2d556.js +1 -0
  68. package/.next/static/chunks/939-92930fde48061e20.js +1 -0
  69. package/.next/static/chunks/app/connections/page-7d18b901f2338f20.js +1 -0
  70. package/.next/static/chunks/app/history/page-adc25194b30c689d.js +3 -0
  71. package/.next/static/css/{f5d3162842dcac31.css → 2abef6d49c1c2970.css} +1 -1
  72. package/ARCHITECTURE.md +43 -15
  73. package/CHANGELOG.md +62 -0
  74. package/README.md +10 -3
  75. package/dist/cli.cjs +868 -511
  76. package/dist/collector.cjs +603 -120
  77. package/dist/hub.cjs +3429 -1451
  78. package/dist/ops.cjs +1178 -888
  79. package/dist/system.cjs +1322 -363
  80. package/docs/configuration.md +34 -1
  81. package/docs/installation.md +12 -6
  82. package/docs/monitoring.md +60 -17
  83. package/docs/operations.md +78 -22
  84. package/docs/system-installation.md +11 -4
  85. package/docs/telemetry-storage.md +258 -0
  86. package/package.json +3 -1
  87. package/packaging/collector.env.example +6 -0
  88. package/packaging/system/rigscope-collector.service.example +3 -0
  89. package/.next/static/chunks/408-9fdba9e4e956ef08.js +0 -1
  90. package/.next/static/chunks/939-514e554087419c71.js +0 -1
  91. package/.next/static/chunks/app/connections/page-75abd16fc495fcbd.js +0 -1
  92. package/.next/static/chunks/app/history/page-a25ba8fab7bd7cb9.js +0 -3
  93. /package/.next/static/{ip43TIgmaByEBn2GFtvfO → JbaLjudQSxrMUD_VHPrnD}/_buildManifest.js +0 -0
  94. /package/.next/static/{ip43TIgmaByEBn2GFtvfO → JbaLjudQSxrMUD_VHPrnD}/_ssgManifest.js +0 -0
package/dist/system.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/cli/system.ts
@@ -86,23 +96,23 @@ __export(system_exports, {
86
96
  verifySystemHealth: () => verifySystemHealth
87
97
  });
88
98
  module.exports = __toCommonJS(system_exports);
89
- var import_node_fs3 = require("node:fs");
99
+ var import_node_fs6 = require("node:fs");
90
100
  var import_node_child_process = require("node:child_process");
91
- var import_node_crypto5 = require("node:crypto");
92
- var import_node_path4 = require("node:path");
93
- var import_node_sqlite = require("node:sqlite");
101
+ var import_node_crypto7 = require("node:crypto");
102
+ var import_node_path6 = require("node:path");
103
+ var import_node_sqlite3 = require("node:sqlite");
94
104
  var import_node_util = require("node:util");
95
105
 
96
106
  // src/cli/health.ts
97
107
  var import_node_http = require("node:http");
98
108
  var MAX_HEALTH_BODY_BYTES = 64 * 1024;
99
109
  function probeHealth(target, timeoutMs = 3e3) {
100
- return new Promise((resolve4) => {
110
+ return new Promise((resolve5) => {
101
111
  let settled = false;
102
112
  const finish = (result) => {
103
113
  if (settled) return;
104
114
  settled = true;
105
- resolve4(result);
115
+ resolve5(result);
106
116
  };
107
117
  const options = target.kind === "unix" ? { socketPath: target.socketPath, path: target.path, method: "GET", headers: { Host: target.hostHeader } } : { host: target.host, port: target.port, path: target.path, method: "GET" };
108
118
  let req;
@@ -185,7 +195,7 @@ function bounded(value, min, max) {
185
195
  return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
186
196
  }
187
197
  function delay(ms) {
188
- return new Promise((resolve4) => setTimeout(resolve4, ms));
198
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
189
199
  }
190
200
  function errorText(error) {
191
201
  return error instanceof Error ? error.message : String(error);
@@ -377,6 +387,9 @@ var SHARING_KEYS = [
377
387
  "processArguments"
378
388
  ];
379
389
 
390
+ // collector/destinations/queue.ts
391
+ var DEFAULT_COLLECTOR_QUEUE_MODE = "memory";
392
+
380
393
  // collector/config.ts
381
394
  var ConfigError = class extends Error {
382
395
  issues;
@@ -582,6 +595,18 @@ function parseDestinations(env, issues, destinations) {
582
595
  }
583
596
  }
584
597
  }
598
+ var QUEUE_MODES = ["memory", "durable"];
599
+ function parseQueueMode(env, issues) {
600
+ const raw = env["COLLECTOR_QUEUE_MODE"]?.trim();
601
+ if (raw === void 0 || raw === "") {
602
+ return DEFAULT_COLLECTOR_QUEUE_MODE;
603
+ }
604
+ if (!QUEUE_MODES.includes(raw)) {
605
+ issues.push(`COLLECTOR_QUEUE_MODE must be one of ${QUEUE_MODES.join(" or ")}`);
606
+ return DEFAULT_COLLECTOR_QUEUE_MODE;
607
+ }
608
+ return raw;
609
+ }
585
610
  function parseIntervalMs(env, issues) {
586
611
  const raw = env["COLLECTOR_INTERVAL_MS"]?.trim();
587
612
  if (raw === void 0 || raw === "") {
@@ -612,6 +637,7 @@ function parseCollectorConfig(env) {
612
637
  }
613
638
  const destinations = [];
614
639
  const intervalMs = parseIntervalMs(env, issues);
640
+ const queueMode = parseQueueMode(env, issues);
615
641
  parseDestinations(env, issues, destinations);
616
642
  if (issues.length > 0) {
617
643
  throw new ConfigError(issues);
@@ -621,7 +647,8 @@ function parseCollectorConfig(env) {
621
647
  hostname,
622
648
  hostnameIsMockDefault: rawHostname === "",
623
649
  intervalMs,
624
- destinations
650
+ destinations,
651
+ queueMode
625
652
  };
626
653
  }
627
654
 
@@ -789,6 +816,13 @@ async function enrollCollector(path, connectUrl, enrollmentToken, sharing, repla
789
816
  // src/hub/config.ts
790
817
  var import_node_net = require("node:net");
791
818
  var import_node_path3 = require("node:path");
819
+
820
+ // src/contracts/telemetry.ts
821
+ var MAX_PERSISTENT_FLUSH_MS = 5e3;
822
+ var MIN_EPOCH = Date.parse("2000-01-01T00:00:00Z");
823
+ var MAX_EPOCH = Date.parse("2200-01-01T00:00:00Z");
824
+
825
+ // src/hub/config.ts
792
826
  var MAX_HUB_COLLECTORS_BYTES = 1024 * 1024;
793
827
  var MAX_COLLECTOR_ID_LENGTH = 128;
794
828
  var MAX_MACHINE_ID_LENGTH = 128;
@@ -816,6 +850,7 @@ function parseHubConfig(env) {
816
850
  maxCollectorConnections = parsed;
817
851
  }
818
852
  const connectionBudget = maxCollectorConnections === void 0 ? {} : { maxCollectorConnections };
853
+ const history = parseHistoryConfig(env);
819
854
  if (publicOrigin === void 0 !== (socketPath === void 0)) {
820
855
  throw new HubConfigError(
821
856
  "HUB_PUBLIC_ORIGIN and HUB_SOCKET_PATH must be set together: remote mode needs both the exact HTTPS origin and the Unix socket path"
@@ -835,6 +870,7 @@ function parseHubConfig(env) {
835
870
  collectors,
836
871
  development,
837
872
  remote: parseRemoteConfig(publicOrigin, socketPath),
873
+ history,
838
874
  ...connectionBudget
839
875
  };
840
876
  }
@@ -844,9 +880,65 @@ function parseHubConfig(env) {
844
880
  collectors,
845
881
  development,
846
882
  remote: null,
883
+ history,
847
884
  ...connectionBudget
848
885
  };
849
886
  }
887
+ var DEFAULT_MEMORY_BUDGET_BYTES = 128 * 1024 * 1024;
888
+ var DEFAULT_PERSISTENT_FLUSH_MS = MAX_PERSISTENT_FLUSH_MS;
889
+ var DEFAULT_RECORDING_MAX_DURATION_MS = 60 * 6e4;
890
+ var DEFAULT_RECORDING_MAX_BYTES = 64 * 1024 * 1024;
891
+ var DEFAULT_RECORDING_BACKLOG_MS = 10 * 6e4;
892
+ var DEFAULT_MAX_ACTIVE_RECORDINGS = 4;
893
+ var HOUR_MS = 36e5;
894
+ var DAY_MS = 864e5;
895
+ function parseHistoryConfig(env) {
896
+ const modeValue = env.HUB_TELEMETRY_MODE ?? "memory";
897
+ if (modeValue !== "memory" && modeValue !== "persistent") {
898
+ throw new HubConfigError("HUB_TELEMETRY_MODE must be memory or persistent");
899
+ }
900
+ const memoryBudgetBytes = envInteger(
901
+ env.HUB_HISTORY_MEMORY_BYTES,
902
+ DEFAULT_MEMORY_BUDGET_BYTES,
903
+ 1 * 1024 * 1024,
904
+ 16 * 1024 * 1024 * 1024,
905
+ "HUB_HISTORY_MEMORY_BYTES"
906
+ );
907
+ const flushIntervalMs = envInteger(
908
+ env.HUB_TELEMETRY_FLUSH_MS,
909
+ DEFAULT_PERSISTENT_FLUSH_MS,
910
+ 1e3,
911
+ MAX_PERSISTENT_FLUSH_MS,
912
+ "HUB_TELEMETRY_FLUSH_MS"
913
+ );
914
+ const retention = {
915
+ rawMs: envInteger(env.HUB_RETENTION_RAW_MS, 2 * DAY_MS, 0, 400 * DAY_MS, "HUB_RETENTION_RAW_MS"),
916
+ tenSecondMs: envInteger(env.HUB_RETENTION_10S_MS, 2 * HOUR_MS, 0, 30 * DAY_MS, "HUB_RETENTION_10S_MS"),
917
+ oneMinuteMs: envInteger(env.HUB_RETENTION_1M_MS, 30 * DAY_MS, 0, 400 * DAY_MS, "HUB_RETENTION_1M_MS"),
918
+ oneHourMs: envInteger(env.HUB_RETENTION_1H_MS, 400 * DAY_MS, 0, 800 * DAY_MS, "HUB_RETENTION_1H_MS"),
919
+ receiptsMs: envInteger(env.HUB_RECEIPT_RETENTION_MS, 7 * DAY_MS, HOUR_MS, 400 * DAY_MS, "HUB_RECEIPT_RETENTION_MS"),
920
+ gapsMs: envInteger(env.HUB_GAP_RETENTION_MS, 400 * DAY_MS, 0, 800 * DAY_MS, "HUB_GAP_RETENTION_MS")
921
+ };
922
+ const recordings = {
923
+ directory: nonEmpty(env.HUB_RECORDINGS_DIR) ?? null,
924
+ maxDurationMs: envInteger(env.HUB_RECORDING_MAX_DURATION_MS, DEFAULT_RECORDING_MAX_DURATION_MS, 1e3, 24 * 60 * 6e4, "HUB_RECORDING_MAX_DURATION_MS"),
925
+ maxBytes: envInteger(env.HUB_RECORDING_MAX_BYTES, DEFAULT_RECORDING_MAX_BYTES, 64 * 1024, 1024 * 1024 * 1024, "HUB_RECORDING_MAX_BYTES"),
926
+ backlogMs: envInteger(env.HUB_RECORDING_BACKLOG_MS, DEFAULT_RECORDING_BACKLOG_MS, 0, 24 * 60 * 6e4, "HUB_RECORDING_BACKLOG_MS"),
927
+ maxActive: envInteger(env.HUB_RECORDING_MAX_ACTIVE, DEFAULT_MAX_ACTIVE_RECORDINGS, 1, 32, "HUB_RECORDING_MAX_ACTIVE")
928
+ };
929
+ return { mode: modeValue, memoryBudgetBytes, flushIntervalMs, retention, recordings };
930
+ }
931
+ function envInteger(raw, fallback, min, max, name) {
932
+ if (raw === void 0 || raw === "") return fallback;
933
+ if (!/^\d+$/.test(raw)) {
934
+ throw new HubConfigError(`${name} must be a nonnegative integer`);
935
+ }
936
+ const value = Number(raw);
937
+ if (!Number.isSafeInteger(value) || value < min || value > max) {
938
+ throw new HubConfigError(`${name} must be between ${min} and ${max}`);
939
+ }
940
+ return value;
941
+ }
850
942
  function nonEmpty(value) {
851
943
  return value === void 0 || value === "" ? void 0 : value;
852
944
  }
@@ -1020,6 +1112,851 @@ function boundedString(value, field, message, maxLength) {
1020
1112
  return value;
1021
1113
  }
1022
1114
 
1115
+ // src/cli/machine-transition.ts
1116
+ var import_node_fs5 = require("node:fs");
1117
+ var import_node_path5 = require("node:path");
1118
+ var import_node_crypto6 = require("node:crypto");
1119
+
1120
+ // src/operations/database.ts
1121
+ var import_node_fs4 = require("node:fs");
1122
+ var import_node_sqlite2 = require("node:sqlite");
1123
+ var sqlite2 = __toESM(require("node:sqlite"));
1124
+
1125
+ // src/hub/storage/db.ts
1126
+ var import_node_sqlite = require("node:sqlite");
1127
+ var sqlite = __toESM(require("node:sqlite"));
1128
+
1129
+ // src/hub/auth/sessions.ts
1130
+ var DEFAULT_SESSION_TTL_MS = 12 * 60 * 60 * 1e3;
1131
+
1132
+ // src/hub/admin/store.ts
1133
+ var MAX_SCOPE_BYTES = 16 * 1024;
1134
+ function adminSchema(db) {
1135
+ db.exec(`
1136
+ CREATE TABLE IF NOT EXISTS users (
1137
+ username TEXT PRIMARY KEY,
1138
+ password_hash TEXT NOT NULL,
1139
+ scope_json TEXT NOT NULL,
1140
+ is_admin INTEGER NOT NULL DEFAULT 0,
1141
+ disabled INTEGER NOT NULL DEFAULT 0
1142
+ );
1143
+
1144
+ CREATE TABLE IF NOT EXISTS admin_audit (
1145
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1146
+ actor TEXT NOT NULL,
1147
+ action TEXT NOT NULL,
1148
+ target TEXT NOT NULL,
1149
+ at_ms INTEGER NOT NULL
1150
+ );
1151
+
1152
+ CREATE TABLE IF NOT EXISTS admin_meta (
1153
+ key TEXT PRIMARY KEY,
1154
+ value TEXT NOT NULL
1155
+ );
1156
+
1157
+ CREATE INDEX IF NOT EXISTS admin_audit_at ON admin_audit (at_ms DESC);
1158
+ `);
1159
+ }
1160
+
1161
+ // src/hub/alerts/schema.ts
1162
+ function installAlertSchema(db) {
1163
+ db.exec(`
1164
+ -- Rules are durable. Per-rule pending/incident state lives here so a
1165
+ -- restart can continue a duration or avoid opening a duplicate incident.
1166
+ CREATE TABLE IF NOT EXISTS alert_rules (
1167
+ id TEXT PRIMARY KEY,
1168
+ name TEXT NOT NULL,
1169
+ machine_id TEXT NOT NULL,
1170
+ metric TEXT NOT NULL,
1171
+ resource_id TEXT NOT NULL DEFAULT '',
1172
+ condition TEXT NOT NULL,
1173
+ threshold REAL,
1174
+ duration_ms INTEGER NOT NULL DEFAULT 0,
1175
+ enabled INTEGER NOT NULL DEFAULT 1,
1176
+ maintenance_until_ms INTEGER,
1177
+ state TEXT NOT NULL DEFAULT 'ok',
1178
+ pending_since_ms INTEGER,
1179
+ active_incident_id TEXT,
1180
+ last_value REAL,
1181
+ last_observed_at_ms INTEGER,
1182
+ created_at_ms INTEGER NOT NULL,
1183
+ updated_at_ms INTEGER NOT NULL
1184
+ );
1185
+
1186
+ CREATE INDEX IF NOT EXISTS alert_rules_machine
1187
+ ON alert_rules (machine_id);
1188
+
1189
+ -- Incident history is bounded by the engine, but never loses an open row.
1190
+ CREATE TABLE IF NOT EXISTS alert_incidents (
1191
+ id TEXT PRIMARY KEY,
1192
+ rule_id TEXT NOT NULL,
1193
+ rule_name TEXT NOT NULL,
1194
+ machine_id TEXT NOT NULL,
1195
+ metric TEXT NOT NULL,
1196
+ resource_id TEXT NOT NULL DEFAULT '',
1197
+ condition TEXT NOT NULL,
1198
+ status TEXT NOT NULL,
1199
+ started_at_ms INTEGER NOT NULL,
1200
+ resolved_at_ms INTEGER,
1201
+ started_value REAL,
1202
+ resolved_value REAL,
1203
+ last_observed_at_ms INTEGER NOT NULL,
1204
+ message TEXT NOT NULL
1205
+ );
1206
+
1207
+ CREATE INDEX IF NOT EXISTS alert_incidents_started
1208
+ ON alert_incidents (started_at_ms DESC);
1209
+ CREATE INDEX IF NOT EXISTS alert_incidents_machine_started
1210
+ ON alert_incidents (machine_id, started_at_ms DESC);
1211
+ CREATE INDEX IF NOT EXISTS alert_incidents_rule_status
1212
+ ON alert_incidents (rule_id, status);
1213
+ CREATE UNIQUE INDEX IF NOT EXISTS alert_incidents_one_open
1214
+ ON alert_incidents (rule_id) WHERE status = 'open';
1215
+
1216
+ -- One durable job per logical incident event. The primary key is the
1217
+ -- deterministic idempotency key sent to the webhook receiver.
1218
+ CREATE TABLE IF NOT EXISTS alert_jobs (
1219
+ id TEXT PRIMARY KEY,
1220
+ rule_id TEXT NOT NULL,
1221
+ incident_id TEXT NOT NULL,
1222
+ machine_id TEXT NOT NULL,
1223
+ event TEXT NOT NULL,
1224
+ status TEXT NOT NULL,
1225
+ attempts INTEGER NOT NULL DEFAULT 0,
1226
+ max_attempts INTEGER NOT NULL DEFAULT 6,
1227
+ next_attempt_ms INTEGER,
1228
+ last_attempt_ms INTEGER,
1229
+ last_error TEXT,
1230
+ created_at_ms INTEGER NOT NULL,
1231
+ delivered_at_ms INTEGER,
1232
+ payload_json TEXT NOT NULL,
1233
+ UNIQUE (incident_id, event)
1234
+ );
1235
+
1236
+ CREATE INDEX IF NOT EXISTS alert_jobs_due
1237
+ ON alert_jobs (status, next_attempt_ms);
1238
+ CREATE INDEX IF NOT EXISTS alert_jobs_created
1239
+ ON alert_jobs (created_at_ms DESC);
1240
+
1241
+ -- Exactly one webhook channel. URL, headers and optional HMAC secret are
1242
+ -- private database state and are never returned by the browser API.
1243
+ CREATE TABLE IF NOT EXISTS alert_webhook (
1244
+ id INTEGER PRIMARY KEY CHECK (id = 1),
1245
+ url TEXT NOT NULL,
1246
+ headers_json TEXT NOT NULL DEFAULT '{}',
1247
+ secret TEXT,
1248
+ enabled INTEGER NOT NULL DEFAULT 1,
1249
+ updated_at_ms INTEGER NOT NULL,
1250
+ last_test_at_ms INTEGER,
1251
+ last_test_ok INTEGER,
1252
+ last_error TEXT
1253
+ );
1254
+
1255
+ -- Last LIVE record accepted by the engine. Used to ignore duplicate or
1256
+ -- older records after restart; replay never calls observe.
1257
+ CREATE TABLE IF NOT EXISTS alert_machine_progress (
1258
+ machine_id TEXT PRIMARY KEY,
1259
+ collector_id TEXT NOT NULL,
1260
+ run_id TEXT NOT NULL,
1261
+ seq INTEGER NOT NULL,
1262
+ received_at_ms INTEGER NOT NULL,
1263
+ updated_at_ms INTEGER NOT NULL
1264
+ );
1265
+
1266
+ -- Resources this rule has actually observed. A resource missing from an
1267
+ -- otherwise-ok family after being seen is unavailable, not a silent
1268
+ -- recovery to "normal".
1269
+ CREATE TABLE IF NOT EXISTS alert_observed_resources (
1270
+ rule_id TEXT NOT NULL,
1271
+ metric TEXT NOT NULL,
1272
+ resource_id TEXT NOT NULL,
1273
+ first_seen_ms INTEGER NOT NULL,
1274
+ last_seen_ms INTEGER NOT NULL,
1275
+ PRIMARY KEY (rule_id, metric, resource_id)
1276
+ );
1277
+ `);
1278
+ }
1279
+
1280
+ // src/hub/enrollment/registry.ts
1281
+ function installRegistrySchema(db) {
1282
+ db.exec(`
1283
+ CREATE TABLE IF NOT EXISTS collector_registry (
1284
+ collector_id TEXT PRIMARY KEY, machine_id TEXT NOT NULL UNIQUE, token TEXT NOT NULL UNIQUE,
1285
+ revoked INTEGER NOT NULL DEFAULT 0, created_at_ms INTEGER NOT NULL, updated_at_ms INTEGER NOT NULL
1286
+ );
1287
+ CREATE TABLE IF NOT EXISTS collector_registry_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
1288
+ CREATE TABLE IF NOT EXISTS collector_grants (
1289
+ token_hash TEXT PRIMARY KEY, expires_at_ms INTEGER NOT NULL, replace_machine_id TEXT,
1290
+ consumed_by TEXT, request_id TEXT, recovery_hash TEXT, recovery_until_ms INTEGER, actor TEXT NOT NULL
1291
+ );
1292
+ CREATE TABLE IF NOT EXISTS collector_audit (
1293
+ id INTEGER PRIMARY KEY, at_ms INTEGER NOT NULL, actor TEXT NOT NULL, action TEXT NOT NULL, target TEXT NOT NULL
1294
+ );
1295
+ `);
1296
+ }
1297
+
1298
+ // src/hub/storage/migrations.ts
1299
+ var MIGRATIONS = [
1300
+ {
1301
+ version: 1,
1302
+ name: "telemetry",
1303
+ up(db) {
1304
+ db.exec(`
1305
+ -- Hub-local machine presentation metadata; never required for ingest.
1306
+ CREATE TABLE machine_meta (
1307
+ machine_id TEXT PRIMARY KEY,
1308
+ display_name TEXT,
1309
+ tags_json TEXT NOT NULL DEFAULT '[]',
1310
+ updated_at_ms INTEGER NOT NULL
1311
+ );
1312
+
1313
+ -- Integer series catalog. resource_id is '' for single-resource
1314
+ -- families and e.g. "core:3", a GPU/filesystem/disk/interface id for
1315
+ -- per-resource families.
1316
+ CREATE TABLE series (
1317
+ series_id INTEGER PRIMARY KEY,
1318
+ machine_id TEXT NOT NULL,
1319
+ metric TEXT NOT NULL,
1320
+ resource_id TEXT NOT NULL DEFAULT '',
1321
+ UNIQUE (machine_id, metric, resource_id)
1322
+ );
1323
+
1324
+ -- Raw samples. WITHOUT ROWID makes (series_id, ts_ms) the clustered
1325
+ -- primary key, so a history range read is one index range scan.
1326
+ -- A NULL value is an explicit unavailable reading, never zero.
1327
+ CREATE TABLE points_raw (
1328
+ series_id INTEGER NOT NULL,
1329
+ ts_ms INTEGER NOT NULL,
1330
+ value REAL,
1331
+ PRIMARY KEY (series_id, ts_ms)
1332
+ ) WITHOUT ROWID;
1333
+
1334
+ -- One-minute rollups, maintained on insert (including late/replayed
1335
+ -- data): min/max preserved, count of non-NULL values, null_count of
1336
+ -- explicit unavailable readings. sum is 0 for a null-only bucket and
1337
+ -- is only interpreted together with count.
1338
+ CREATE TABLE points_1m (
1339
+ series_id INTEGER NOT NULL,
1340
+ bucket_ms INTEGER NOT NULL,
1341
+ min REAL,
1342
+ max REAL,
1343
+ sum REAL NOT NULL DEFAULT 0,
1344
+ count INTEGER NOT NULL DEFAULT 0,
1345
+ null_count INTEGER NOT NULL DEFAULT 0,
1346
+ PRIMARY KEY (series_id, bucket_ms)
1347
+ ) WITHOUT ROWID;
1348
+
1349
+ CREATE TABLE points_1h (
1350
+ series_id INTEGER NOT NULL,
1351
+ bucket_ms INTEGER NOT NULL,
1352
+ min REAL,
1353
+ max REAL,
1354
+ sum REAL NOT NULL DEFAULT 0,
1355
+ count INTEGER NOT NULL DEFAULT 0,
1356
+ null_count INTEGER NOT NULL DEFAULT 0,
1357
+ PRIMARY KEY (series_id, bucket_ms)
1358
+ ) WITHOUT ROWID;
1359
+
1360
+ -- Durable receipt / dedup key. Every accepted record kind has a row
1361
+ -- here before its side effects are committed.
1362
+ CREATE TABLE sample_receipts (
1363
+ collector_id TEXT NOT NULL,
1364
+ run_id TEXT NOT NULL,
1365
+ seq INTEGER NOT NULL,
1366
+ machine_id TEXT NOT NULL,
1367
+ kind TEXT NOT NULL,
1368
+ mode TEXT NOT NULL,
1369
+ captured_at_ms INTEGER NOT NULL,
1370
+ received_at_ms INTEGER NOT NULL,
1371
+ expected_interval_ms INTEGER,
1372
+ PRIMARY KEY (collector_id, run_id, seq)
1373
+ ) WITHOUT ROWID;
1374
+
1375
+ -- Freshness and gap detection scan a machine's receipt timeline.
1376
+ CREATE INDEX sample_receipts_machine_captured
1377
+ ON sample_receipts (machine_id, captured_at_ms);
1378
+ -- Retention deletes oldest receipts without an unbounded scan.
1379
+ CREATE INDEX sample_receipts_captured
1380
+ ON sample_receipts (captured_at_ms);
1381
+
1382
+ -- Collector-reported dropped intervals.
1383
+ CREATE TABLE gaps (
1384
+ collector_id TEXT NOT NULL,
1385
+ run_id TEXT NOT NULL,
1386
+ seq INTEGER NOT NULL,
1387
+ machine_id TEXT NOT NULL,
1388
+ from_ms INTEGER NOT NULL,
1389
+ to_ms INTEGER NOT NULL,
1390
+ dropped INTEGER NOT NULL,
1391
+ reason TEXT NOT NULL,
1392
+ received_at_ms INTEGER NOT NULL,
1393
+ PRIMARY KEY (collector_id, run_id, seq)
1394
+ ) WITHOUT ROWID;
1395
+
1396
+ CREATE INDEX gaps_machine_to ON gaps (machine_id, to_ms);
1397
+ -- Retention deletes oldest gaps without a full scan.
1398
+ CREATE INDEX gaps_to ON gaps (to_ms);
1399
+
1400
+ -- Latest LIVE metrics per machine, persisted across restart. A replay
1401
+ -- batch never touches this table.
1402
+ CREATE TABLE machine_latest (
1403
+ machine_id TEXT PRIMARY KEY,
1404
+ collector_id TEXT NOT NULL,
1405
+ run_id TEXT NOT NULL,
1406
+ seq INTEGER NOT NULL,
1407
+ captured_at_ms INTEGER NOT NULL,
1408
+ received_at_ms INTEGER NOT NULL,
1409
+ payload_json TEXT NOT NULL
1410
+ );
1411
+
1412
+ -- Latest inventory per machine. Inventory is not time-series data.
1413
+ CREATE TABLE inventory (
1414
+ machine_id TEXT PRIMARY KEY,
1415
+ captured_at_ms INTEGER NOT NULL,
1416
+ received_at_ms INTEGER NOT NULL,
1417
+ inventory_json TEXT NOT NULL
1418
+ );
1419
+ `);
1420
+ }
1421
+ },
1422
+ {
1423
+ version: 2,
1424
+ name: "latest_session_identity",
1425
+ up(db) {
1426
+ const columns = new Set(
1427
+ db.prepare("PRAGMA table_info(machine_latest)").all().map(
1428
+ (column) => column.name
1429
+ )
1430
+ );
1431
+ if (!columns.has("boot_id")) {
1432
+ db.exec("ALTER TABLE machine_latest ADD COLUMN boot_id TEXT NOT NULL DEFAULT ''");
1433
+ }
1434
+ if (!columns.has("expected_interval_ms")) {
1435
+ db.exec("ALTER TABLE machine_latest ADD COLUMN expected_interval_ms INTEGER");
1436
+ }
1437
+ if (!columns.has("hostname")) {
1438
+ db.exec("ALTER TABLE machine_latest ADD COLUMN hostname TEXT");
1439
+ }
1440
+ }
1441
+ },
1442
+ { version: 3, name: "admin", up: adminSchema },
1443
+ { version: 4, name: "alerts", up: installAlertSchema },
1444
+ { version: 5, name: "collector_registry", up: installRegistrySchema },
1445
+ { version: 6, name: "telemetry_modes_and_recordings", up: telemetryModesAndRecordings }
1446
+ ];
1447
+ function telemetryModesAndRecordings(db) {
1448
+ db.exec(`
1449
+ CREATE TABLE IF NOT EXISTS points_10s (
1450
+ series_id INTEGER NOT NULL,
1451
+ bucket_ms INTEGER NOT NULL,
1452
+ min REAL,
1453
+ max REAL,
1454
+ sum REAL NOT NULL DEFAULT 0,
1455
+ count INTEGER NOT NULL DEFAULT 0,
1456
+ null_count INTEGER NOT NULL DEFAULT 0,
1457
+ PRIMARY KEY (series_id, bucket_ms)
1458
+ ) WITHOUT ROWID;
1459
+
1460
+ CREATE TABLE IF NOT EXISTS recordings (
1461
+ id TEXT PRIMARY KEY,
1462
+ created_by TEXT NOT NULL,
1463
+ status TEXT NOT NULL,
1464
+ machines_json TEXT NOT NULL,
1465
+ metrics_json TEXT NOT NULL,
1466
+ resolution_ms INTEGER NOT NULL,
1467
+ created_at_ms INTEGER NOT NULL,
1468
+ started_at_ms INTEGER NOT NULL,
1469
+ stopped_at_ms INTEGER,
1470
+ deadline_ms INTEGER NOT NULL,
1471
+ max_bytes INTEGER NOT NULL,
1472
+ bytes_written INTEGER NOT NULL DEFAULT 0,
1473
+ file_name TEXT NOT NULL,
1474
+ include_backlog INTEGER NOT NULL DEFAULT 0,
1475
+ reason TEXT,
1476
+ error TEXT
1477
+ );
1478
+
1479
+ CREATE INDEX IF NOT EXISTS recordings_created
1480
+ ON recordings (created_at_ms DESC);
1481
+ `);
1482
+ }
1483
+
1484
+ // src/hub/storage/db.ts
1485
+ var DEFAULT_MAX_DATABASE_DISK_BYTES = 2 * 1024 * 1024 * 1024;
1486
+ var HUB_WAL_RESERVE_BYTES = 128 * 1024 * 1024;
1487
+
1488
+ // src/operations/fs.ts
1489
+ var import_node_fs3 = require("node:fs");
1490
+ var import_node_crypto5 = require("node:crypto");
1491
+ var import_node_path4 = require("node:path");
1492
+ var PRIVATE_DIR_MODE = 448;
1493
+ var PRIVATE_FILE_MODE = 384;
1494
+ var OperationsError = class extends Error {
1495
+ code;
1496
+ constructor(code, message, options) {
1497
+ super(message, options);
1498
+ this.name = "OperationsError";
1499
+ this.code = code;
1500
+ }
1501
+ };
1502
+ function errorMessage(error) {
1503
+ return error instanceof Error ? error.message : String(error);
1504
+ }
1505
+ function timestampSlug(ms = Date.now()) {
1506
+ return new Date(ms).toISOString().replace(/[:.]/g, "-");
1507
+ }
1508
+ function isPrivateMode(mode) {
1509
+ return (mode & 63) === 0;
1510
+ }
1511
+ function ensurePrivateDir(path) {
1512
+ const absolute = (0, import_node_path4.resolve)(path);
1513
+ (0, import_node_fs3.mkdirSync)(absolute, { recursive: true, mode: PRIVATE_DIR_MODE });
1514
+ const stat = (0, import_node_fs3.lstatSync)(absolute);
1515
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
1516
+ throw new OperationsError("unsafe-path", `${absolute} must be a real directory`);
1517
+ }
1518
+ if (!isPrivateMode(stat.mode)) {
1519
+ throw new OperationsError(
1520
+ "insecure-permissions",
1521
+ `${absolute} must be private (0700 or stricter); found ${formatMode(stat.mode)}`
1522
+ );
1523
+ }
1524
+ }
1525
+ function assertPrivateRegularFile(path, label) {
1526
+ const absolute = (0, import_node_path4.resolve)(path);
1527
+ let stat;
1528
+ try {
1529
+ stat = (0, import_node_fs3.lstatSync)(absolute);
1530
+ } catch (error) {
1531
+ throw new OperationsError("missing-file", `${label} does not exist: ${absolute}`, { cause: error });
1532
+ }
1533
+ if (!stat.isFile() || stat.isSymbolicLink()) {
1534
+ throw new OperationsError("unsafe-path", `${label} must be a regular file, not a symlink: ${absolute}`);
1535
+ }
1536
+ if (!isPrivateMode(stat.mode)) {
1537
+ throw new OperationsError(
1538
+ "insecure-permissions",
1539
+ `${label} must be private (0600/0400); found ${formatMode(stat.mode)}: ${absolute}`
1540
+ );
1541
+ }
1542
+ return stat;
1543
+ }
1544
+ function formatMode(mode) {
1545
+ return `0${(mode & 511).toString(8).padStart(3, "0")}`;
1546
+ }
1547
+ function writeFileAtomic(path, content, mode) {
1548
+ const absolute = (0, import_node_path4.resolve)(path);
1549
+ const parent = (0, import_node_path4.dirname)(absolute);
1550
+ const temporary = (0, import_node_path4.join)(parent, `.${(0, import_node_path4.basename)(absolute)}.tmp-${(0, import_node_crypto5.randomUUID)()}`);
1551
+ let fd = null;
1552
+ try {
1553
+ fd = (0, import_node_fs3.openSync)(temporary, "wx", mode);
1554
+ (0, import_node_fs3.writeFileSync)(fd, content);
1555
+ (0, import_node_fs3.fsyncSync)(fd);
1556
+ (0, import_node_fs3.closeSync)(fd);
1557
+ fd = null;
1558
+ (0, import_node_fs3.chmodSync)(temporary, mode);
1559
+ (0, import_node_fs3.renameSync)(temporary, absolute);
1560
+ fsyncDir(parent);
1561
+ } catch (error) {
1562
+ if (fd !== null) {
1563
+ try {
1564
+ (0, import_node_fs3.closeSync)(fd);
1565
+ } catch {
1566
+ }
1567
+ }
1568
+ removeIfExists(temporary);
1569
+ throw error;
1570
+ }
1571
+ }
1572
+ function writePrivateFile(path, content) {
1573
+ ensurePrivateDir((0, import_node_path4.dirname)((0, import_node_path4.resolve)(path)));
1574
+ writeFileAtomic(path, content, PRIVATE_FILE_MODE);
1575
+ }
1576
+ function fsyncDir(path) {
1577
+ let fd = null;
1578
+ try {
1579
+ fd = (0, import_node_fs3.openSync)(path, "r");
1580
+ (0, import_node_fs3.fsyncSync)(fd);
1581
+ } catch {
1582
+ } finally {
1583
+ if (fd !== null) {
1584
+ try {
1585
+ (0, import_node_fs3.closeSync)(fd);
1586
+ } catch {
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+ function removeIfExists(path) {
1592
+ try {
1593
+ (0, import_node_fs3.unlinkSync)(path);
1594
+ } catch (error) {
1595
+ if (error.code !== "ENOENT") throw error;
1596
+ }
1597
+ }
1598
+ function fileSize(path) {
1599
+ try {
1600
+ const stat = (0, import_node_fs3.statSync)(path);
1601
+ return stat.isFile() ? stat.size : 0;
1602
+ } catch (error) {
1603
+ if (error.code === "ENOENT") return 0;
1604
+ throw error;
1605
+ }
1606
+ }
1607
+ function resolvePath(value, label) {
1608
+ if (typeof value !== "string" || value.trim() === "") {
1609
+ throw new OperationsError("invalid-argument", `${label} must be a nonempty path`);
1610
+ }
1611
+ return (0, import_node_path4.resolve)(value);
1612
+ }
1613
+ function findHubLock(databasePath) {
1614
+ const absolute = (0, import_node_path4.resolve)(databasePath);
1615
+ const parent = (0, import_node_path4.dirname)(absolute);
1616
+ const envLock = process.env.RIGSCOPE_HUB_LOCK_FILE;
1617
+ const candidates = [
1618
+ ...envLock ? [(0, import_node_path4.resolve)(envLock)] : [],
1619
+ `${absolute}.lock`,
1620
+ `${absolute}-lock`,
1621
+ `${absolute}.hub.lock`,
1622
+ (0, import_node_path4.join)(parent, "hub.lock"),
1623
+ (0, import_node_path4.join)(parent, "hub.pid")
1624
+ // `run/hub.lock` is the component supervisor lease, not a database
1625
+ // lease. Treating it as a hub lock would make an upgrade's own held lease
1626
+ // look like a live hub during database backup/restore.
1627
+ ];
1628
+ for (const candidate of candidates) {
1629
+ try {
1630
+ (0, import_node_fs3.lstatSync)(candidate);
1631
+ return candidate;
1632
+ } catch (error) {
1633
+ if (error.code !== "ENOENT") throw error;
1634
+ }
1635
+ }
1636
+ return null;
1637
+ }
1638
+ function assertNoActiveHubLock(databasePath) {
1639
+ const lock = findHubLock(databasePath);
1640
+ if (lock !== null) {
1641
+ throw new OperationsError(
1642
+ "hub-active",
1643
+ `an active hub lock exists at ${lock}; stop the hub, verify no process owns it, and remove the lock only after explicit inspection`
1644
+ );
1645
+ }
1646
+ }
1647
+
1648
+ // src/operations/database.ts
1649
+ var SUPPORTED_SCHEMA_VERSION = MIGRATIONS.length === 0 ? 0 : MIGRATIONS[MIGRATIONS.length - 1].version;
1650
+ var BUSY_TIMEOUT_MS = 5e3;
1651
+ var PRIVATE_FILE_MODE2 = 384;
1652
+ var COUNTED_TABLES = /* @__PURE__ */ new Set([
1653
+ "schema_migrations",
1654
+ "collector_registry",
1655
+ "collector_registry_meta",
1656
+ "collector_grants",
1657
+ "collector_audit",
1658
+ "users",
1659
+ "admin_audit",
1660
+ "admin_meta",
1661
+ "machine_meta",
1662
+ "machine_latest",
1663
+ "inventory",
1664
+ "points_10s",
1665
+ "gaps",
1666
+ "recordings",
1667
+ "alert_rules",
1668
+ "alert_incidents",
1669
+ "alert_jobs",
1670
+ "alert_webhook",
1671
+ "alert_machine_progress"
1672
+ ]);
1673
+ function openReadOnly(path) {
1674
+ const db = new import_node_sqlite2.DatabaseSync(path, { readOnly: true, timeout: BUSY_TIMEOUT_MS });
1675
+ db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
1676
+ return db;
1677
+ }
1678
+ function rowsOf(statement) {
1679
+ return statement.all();
1680
+ }
1681
+ function firstValue(row) {
1682
+ return row === void 0 ? void 0 : Object.values(row)[0];
1683
+ }
1684
+ function integrityResult(db) {
1685
+ const rows = rowsOf(db.prepare("PRAGMA integrity_check"));
1686
+ const lines = rows.map((row) => String(firstValue(row) ?? ""));
1687
+ const text = lines.length === 0 ? "no integrity result" : lines.join("; ");
1688
+ return { text, ok: lines.length === 1 && lines[0] === "ok" };
1689
+ }
1690
+ function tableNames(db) {
1691
+ return rowsOf(
1692
+ db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
1693
+ ).map((row) => String(row.name));
1694
+ }
1695
+ function readLedger(db, hasTable) {
1696
+ if (!hasTable) return [];
1697
+ return rowsOf(db.prepare("SELECT version, name FROM schema_migrations ORDER BY version")).map((row) => ({
1698
+ version: Number(row.version),
1699
+ name: String(row.name)
1700
+ }));
1701
+ }
1702
+ function ledgerProblem(ledger) {
1703
+ if (ledger.length === 0) return "schema_migrations exists but has no applied migrations";
1704
+ for (const [index, row] of ledger.entries()) {
1705
+ if (!Number.isSafeInteger(row.version) || row.version < 1) return `invalid migration version ${row.version}`;
1706
+ if (row.version !== index + 1) {
1707
+ return `migration history is not contiguous at version ${row.version}`;
1708
+ }
1709
+ const expected = MIGRATIONS.find((migration) => migration.version === row.version);
1710
+ if (expected === void 0) {
1711
+ return row.version > SUPPORTED_SCHEMA_VERSION ? `schema version ${row.version} is newer than this build (${SUPPORTED_SCHEMA_VERSION})` : `unknown migration version ${row.version}`;
1712
+ }
1713
+ if (row.name !== expected.name) {
1714
+ return `migration ${row.version} is named "${row.name}", expected "${expected.name}"`;
1715
+ }
1716
+ }
1717
+ return null;
1718
+ }
1719
+ function describeOpenDatabase(db, path, statMode) {
1720
+ const integrity = integrityResult(db);
1721
+ const tables = tableNames(db);
1722
+ const hasSchemaMigrations = tables.includes("schema_migrations");
1723
+ const ledger = readLedger(db, hasSchemaMigrations);
1724
+ const schemaVersion = ledger.length === 0 ? 0 : ledger[ledger.length - 1].version;
1725
+ const problem = hasSchemaMigrations ? ledgerProblem(ledger) : "schema_migrations is missing";
1726
+ const journal = db.prepare("PRAGMA journal_mode").get();
1727
+ const pageSize = Number(db.prepare("PRAGMA page_size").get()?.page_size ?? 0);
1728
+ const pageCount = Number(db.prepare("PRAGMA page_count").get()?.page_count ?? 0);
1729
+ const counted = tables.map((name) => {
1730
+ if (!COUNTED_TABLES.has(name)) return { name };
1731
+ try {
1732
+ const row = db.prepare(`SELECT COUNT(*) AS n FROM "${name.replace(/"/g, '""')}"`).get();
1733
+ return { name, rows: Number(row?.n ?? 0) };
1734
+ } catch {
1735
+ return { name };
1736
+ }
1737
+ });
1738
+ return {
1739
+ path,
1740
+ fileBytes: fileSize(path),
1741
+ walBytes: fileSize(`${path}-wal`),
1742
+ shmBytes: fileSize(`${path}-shm`),
1743
+ mode: `0${(statMode & 511).toString(8).padStart(3, "0")}`,
1744
+ integrity: integrity.text,
1745
+ integrityOk: integrity.ok,
1746
+ journalMode: String(firstValue(journal) ?? "unknown"),
1747
+ pageSize,
1748
+ pageCount,
1749
+ hasSchemaMigrations,
1750
+ schemaVersion,
1751
+ supportedSchemaVersion: SUPPORTED_SCHEMA_VERSION,
1752
+ newerSchema: schemaVersion > SUPPORTED_SCHEMA_VERSION,
1753
+ ledger,
1754
+ ledgerProblem: problem,
1755
+ tables: counted,
1756
+ registryPresent: tables.includes("collector_registry"),
1757
+ adminPresent: tables.includes("users"),
1758
+ alertsPresent: tables.includes("alert_rules") && tables.includes("alert_incidents") && tables.includes("alert_jobs")
1759
+ };
1760
+ }
1761
+ function chmodSidecars(path) {
1762
+ for (const sidecar of [`${path}-wal`, `${path}-shm`]) {
1763
+ try {
1764
+ const stat = (0, import_node_fs4.lstatSync)(sidecar);
1765
+ if (stat.isFile() && !isPrivateMode(stat.mode)) (0, import_node_fs4.chmodSync)(sidecar, PRIVATE_FILE_MODE2);
1766
+ } catch {
1767
+ }
1768
+ }
1769
+ }
1770
+ function inspectDatabase(path) {
1771
+ const absolute = resolvePath(path, "database");
1772
+ const stat = assertPrivateRegularFile(absolute, "database");
1773
+ const db = openReadOnly(absolute);
1774
+ try {
1775
+ return describeOpenDatabase(db, absolute, stat.mode);
1776
+ } finally {
1777
+ db.close();
1778
+ chmodSidecars(absolute);
1779
+ }
1780
+ }
1781
+ var MACHINE_HISTORY_TABLES = [
1782
+ "points_raw",
1783
+ "points_10s",
1784
+ "points_1m",
1785
+ "points_1h",
1786
+ "sample_receipts",
1787
+ "gaps",
1788
+ "machine_latest",
1789
+ "inventory",
1790
+ "alert_observed_resources",
1791
+ "alert_machine_progress",
1792
+ "alert_incidents",
1793
+ "series"
1794
+ ];
1795
+ function clearMachineHistory(database) {
1796
+ const path = resolvePath(database, "database");
1797
+ if (!(0, import_node_fs4.existsSync)(path)) return { deleted: {}, integrity: "absent" };
1798
+ assertPrivateRegularFile(path, "database");
1799
+ assertNoActiveHubLock(path);
1800
+ const db = new import_node_sqlite2.DatabaseSync(path, { timeout: BUSY_TIMEOUT_MS });
1801
+ const deleted = {};
1802
+ try {
1803
+ db.exec("PRAGMA busy_timeout = 5000");
1804
+ const present = new Set(
1805
+ db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => row.name)
1806
+ );
1807
+ db.exec("BEGIN IMMEDIATE");
1808
+ try {
1809
+ for (const table of MACHINE_HISTORY_TABLES) {
1810
+ if (!present.has(table)) continue;
1811
+ const result = db.prepare(`DELETE FROM ${table}`).run();
1812
+ deleted[table] = Number(result.changes);
1813
+ }
1814
+ db.exec("COMMIT");
1815
+ } catch (error) {
1816
+ try {
1817
+ db.exec("ROLLBACK");
1818
+ } catch {
1819
+ }
1820
+ throw error;
1821
+ }
1822
+ const autoVacuum = db.prepare("PRAGMA auto_vacuum").get();
1823
+ if (autoVacuum && Object.values(autoVacuum)[0] === 2) db.exec("PRAGMA incremental_vacuum(1024)");
1824
+ try {
1825
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1826
+ } catch {
1827
+ }
1828
+ } finally {
1829
+ db.close();
1830
+ }
1831
+ return { deleted, integrity: inspectDatabase(path).integrity };
1832
+ }
1833
+
1834
+ // src/cli/machine-transition.ts
1835
+ var MACHINE_DATA_TRANSITION_FILE = "machine-data-transition.json";
1836
+ var STAGED_QUEUE_PREFIX = "outbox.pre-transition-";
1837
+ function machineDataTransitionPath(stateRoot) {
1838
+ return (0, import_node_path5.join)(stateRoot, MACHINE_DATA_TRANSITION_FILE);
1839
+ }
1840
+ function readMachineDataTransition(stateRoot) {
1841
+ const path = machineDataTransitionPath(stateRoot);
1842
+ try {
1843
+ const stat = (0, import_node_fs5.lstatSync)(path);
1844
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0) {
1845
+ throw new Error("marker must be a private regular file");
1846
+ }
1847
+ const value = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
1848
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid marker");
1849
+ const record = value;
1850
+ if (typeof record.transitionedAt !== "string" || !Number.isFinite(Date.parse(record.transitionedAt))) {
1851
+ throw new Error("invalid transition date");
1852
+ }
1853
+ return { transitionedAt: record.transitionedAt, release: typeof record.release === "string" ? record.release : null };
1854
+ } catch (error) {
1855
+ if (error.code === "ENOENT") return null;
1856
+ throw new OperationsError(
1857
+ "invalid-transition-marker",
1858
+ `Cannot determine whether the one-time reset completed; preserving machine data. Repair ${path}: ${errorMessage(error)}`,
1859
+ { cause: error }
1860
+ );
1861
+ }
1862
+ }
1863
+ function recordMachineDataTransition(stateRoot, release, write = (path, content) => writePrivateFile(path, content)) {
1864
+ const marker = { transitionedAt: (/* @__PURE__ */ new Date()).toISOString(), release };
1865
+ write(machineDataTransitionPath(stateRoot), `${JSON.stringify(marker, null, 2)}
1866
+ `);
1867
+ }
1868
+ function newMachineDataCleanup(transitioned) {
1869
+ return {
1870
+ report: { transitioned, databaseRows: null, collectorQueuesRemoved: false, warning: null },
1871
+ stagedQueues: null,
1872
+ databaseCleared: false
1873
+ };
1874
+ }
1875
+ function performMachineDataCleanup(stateRoot, paths, now = Date.now, deps = {}) {
1876
+ if (readMachineDataTransition(stateRoot) !== null) return newMachineDataCleanup(false);
1877
+ const cleanup = newMachineDataCleanup(true);
1878
+ const clear = deps.clear ?? clearMachineHistory;
1879
+ const warnings = [];
1880
+ try {
1881
+ if ((0, import_node_fs5.existsSync)(paths.queueTree)) {
1882
+ const staged = (0, import_node_path5.join)((0, import_node_path5.dirname)(paths.queueTree), `${STAGED_QUEUE_PREFIX}${timestampSlug(now())}-${(0, import_node_crypto6.randomUUID)()}`);
1883
+ (0, import_node_fs5.renameSync)(paths.queueTree, staged);
1884
+ cleanup.stagedQueues = staged;
1885
+ cleanup.report.collectorQueuesRemoved = true;
1886
+ }
1887
+ } catch (error) {
1888
+ warnings.push(`collector queues: ${errorMessage(error)}`);
1889
+ }
1890
+ try {
1891
+ if ((0, import_node_fs5.existsSync)(paths.database)) {
1892
+ cleanup.databaseCleared = true;
1893
+ cleanup.report.databaseRows = clear(paths.database).deleted;
1894
+ }
1895
+ } catch (error) {
1896
+ warnings.push(`hub database: ${errorMessage(error)}`);
1897
+ }
1898
+ cleanup.report.warning = warnings.length > 0 ? warnings.join("; ") : null;
1899
+ return cleanup;
1900
+ }
1901
+ function undoMachineDataCleanup(paths, stagedQueues) {
1902
+ if (stagedQueues === null || !(0, import_node_fs5.existsSync)(stagedQueues)) return [];
1903
+ try {
1904
+ if ((0, import_node_fs5.existsSync)(paths.queueTree)) {
1905
+ const failedQueue = `${paths.queueTree}.failed-transition-${(0, import_node_crypto6.randomUUID)()}`;
1906
+ (0, import_node_fs5.renameSync)(paths.queueTree, failedQueue);
1907
+ try {
1908
+ (0, import_node_fs5.renameSync)(stagedQueues, paths.queueTree);
1909
+ } catch (error) {
1910
+ (0, import_node_fs5.renameSync)(failedQueue, paths.queueTree);
1911
+ throw error;
1912
+ }
1913
+ return [`restored the collector queue tree; replacement runtime queue preserved at ${failedQueue}`];
1914
+ }
1915
+ (0, import_node_fs5.renameSync)(stagedQueues, paths.queueTree);
1916
+ return ["restored the collector queue tree the transition had staged aside"];
1917
+ } catch (error) {
1918
+ return [`machine data rollback could not restore the collector queue: ${errorMessage(error)}`];
1919
+ }
1920
+ }
1921
+ function completeMachineDataCleanup(stateRoot, cleanup, release, write) {
1922
+ if (!cleanup.report.transitioned) return;
1923
+ if (cleanup.report.warning !== null) return;
1924
+ try {
1925
+ recordMachineDataTransition(stateRoot, release, write);
1926
+ } catch (error) {
1927
+ cleanup.report.warning = `could not record transition completion: ${errorMessage(error)}`;
1928
+ return;
1929
+ }
1930
+ if (cleanup.stagedQueues !== null) {
1931
+ try {
1932
+ (0, import_node_fs5.rmSync)(cleanup.stagedQueues, { recursive: true, force: true });
1933
+ } catch (error) {
1934
+ cleanup.report.warning = `transition completed; staged queue remains at ${cleanup.stagedQueues}: ${errorMessage(error)}`;
1935
+ }
1936
+ }
1937
+ }
1938
+ function machineDataReportRows(report) {
1939
+ if (report.databaseRows === null) return null;
1940
+ return Object.values(report.databaseRows).reduce((sum, rows) => sum + rows, 0);
1941
+ }
1942
+ function machineDataKeptNote() {
1943
+ return "accounts, sessions, enrollment and grants, settings, alert rules/jobs/webhook settings, machine names/tags and recordings were kept";
1944
+ }
1945
+ function describeMachineDataReport(report) {
1946
+ const summary = report.transitioned ? (() => {
1947
+ const rows = machineDataReportRows(report);
1948
+ const parts = [
1949
+ rows === null ? "no hub database" : `${rows} database row(s)`,
1950
+ ...report.collectorQueuesRemoved ? ["the collector queue tree"] : []
1951
+ ];
1952
+ return `Machine data: one-time RAM-first transition discarded ${parts.join(" and ")}. The ${machineDataKeptNote()}.`;
1953
+ })() : "Machine data: one-time transition already completed; durable history and collector queues preserved.";
1954
+ return {
1955
+ summary,
1956
+ warning: report.warning === null ? null : `Machine-data cleanup warning (${report.warning}). The upgrade is healthy; cleanup without a completion marker is retried on the next upgrade.`
1957
+ };
1958
+ }
1959
+
1023
1960
  // src/cli/system.ts
1024
1961
  var SYSTEM_SCOPE = "system";
1025
1962
  var DEFAULT_SYSTEM_ACCOUNTS = {
@@ -1129,13 +2066,13 @@ function createSystemHost(overrides = {}) {
1129
2066
  }
1130
2067
  function defaultSystemPaths(overrides = {}) {
1131
2068
  return {
1132
- prefix: (0, import_node_path4.resolve)(overrides.prefix ?? "/opt/rigscope"),
1133
- configDir: (0, import_node_path4.resolve)(overrides.configDir ?? "/etc/rigscope"),
1134
- stateDir: (0, import_node_path4.resolve)(overrides.stateDir ?? "/var/lib/rigscope"),
1135
- unitsDir: (0, import_node_path4.resolve)(overrides.unitsDir ?? "/etc/systemd/system"),
1136
- sysusersDir: (0, import_node_path4.resolve)(overrides.sysusersDir ?? "/etc/sysusers.d"),
1137
- udevRulesDir: (0, import_node_path4.resolve)(overrides.udevRulesDir ?? "/etc/udev/rules.d"),
1138
- powercapRoot: (0, import_node_path4.resolve)(overrides.powercapRoot ?? "/sys/class/powercap")
2069
+ prefix: (0, import_node_path6.resolve)(overrides.prefix ?? "/opt/rigscope"),
2070
+ configDir: (0, import_node_path6.resolve)(overrides.configDir ?? "/etc/rigscope"),
2071
+ stateDir: (0, import_node_path6.resolve)(overrides.stateDir ?? "/var/lib/rigscope"),
2072
+ unitsDir: (0, import_node_path6.resolve)(overrides.unitsDir ?? "/etc/systemd/system"),
2073
+ sysusersDir: (0, import_node_path6.resolve)(overrides.sysusersDir ?? "/etc/sysusers.d"),
2074
+ udevRulesDir: (0, import_node_path6.resolve)(overrides.udevRulesDir ?? "/etc/udev/rules.d"),
2075
+ powercapRoot: (0, import_node_path6.resolve)(overrides.powercapRoot ?? "/sys/class/powercap")
1139
2076
  };
1140
2077
  }
1141
2078
  function unitNamesFor(role) {
@@ -1150,11 +2087,11 @@ function rolesFor(role) {
1150
2087
  return { hub: role !== "collector", collector: role !== "hub" };
1151
2088
  }
1152
2089
  function isWithin(parent, candidate) {
1153
- const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(parent), (0, import_node_path4.resolve)(candidate));
1154
- return rel === "" || rel !== ".." && !rel.startsWith(`..${import_node_path4.sep}`) && !(0, import_node_path4.isAbsolute)(rel);
2090
+ const rel = (0, import_node_path6.relative)((0, import_node_path6.resolve)(parent), (0, import_node_path6.resolve)(candidate));
2091
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${import_node_path6.sep}`) && !(0, import_node_path6.isAbsolute)(rel);
1155
2092
  }
1156
2093
  function ensureAbsolute(path, label) {
1157
- const absolute = (0, import_node_path4.resolve)(path);
2094
+ const absolute = (0, import_node_path6.resolve)(path);
1158
2095
  if (/[\0\r\n]/.test(absolute)) throw new SystemError("unsafe-path", `${label} contains control characters`);
1159
2096
  if (absolute === "/") throw new SystemError("unsafe-path", `${label} must not be the filesystem root`);
1160
2097
  for (const forbidden of ["/home", "/root", "/proc", "/sys", "/dev", "/boot"]) {
@@ -1168,7 +2105,7 @@ function validateSystemPaths(input = {}) {
1168
2105
  const paths = defaultSystemPaths(input);
1169
2106
  for (const [label, value] of Object.entries(paths)) {
1170
2107
  if (label === "powercapRoot") {
1171
- if (!(0, import_node_path4.isAbsolute)(value) || /[\0\r\n]/.test(value) || (0, import_node_path4.resolve)(value) === "/") {
2108
+ if (!(0, import_node_path6.isAbsolute)(value) || /[\0\r\n]/.test(value) || (0, import_node_path6.resolve)(value) === "/") {
1172
2109
  throw new SystemError("unsafe-path", `powercap root is unsafe: ${value}`);
1173
2110
  }
1174
2111
  } else {
@@ -1223,19 +2160,19 @@ function runRequired(host, command, args, label, options) {
1223
2160
  function runMaybe(host, command, args, options) {
1224
2161
  return host.run(command, args, options);
1225
2162
  }
1226
- function sha256File(path) {
1227
- return (0, import_node_crypto5.createHash)("sha256").update((0, import_node_fs3.readFileSync)(path)).digest("hex");
2163
+ function sha256File2(path) {
2164
+ return (0, import_node_crypto7.createHash)("sha256").update((0, import_node_fs6.readFileSync)(path)).digest("hex");
1228
2165
  }
1229
2166
  function atomicWrite(path, content, mode) {
1230
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true, mode: 493 });
1231
- const temporary = (0, import_node_path4.join)((0, import_node_path4.dirname)(path), `.${(0, import_node_path4.basename)(path)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2167
+ (0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(path), { recursive: true, mode: 493 });
2168
+ const temporary = (0, import_node_path6.join)((0, import_node_path6.dirname)(path), `.${(0, import_node_path6.basename)(path)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
1232
2169
  try {
1233
- (0, import_node_fs3.writeFileSync)(temporary, content, { mode });
1234
- (0, import_node_fs3.chmodSync)(temporary, mode);
1235
- (0, import_node_fs3.renameSync)(temporary, path);
2170
+ (0, import_node_fs6.writeFileSync)(temporary, content, { mode });
2171
+ (0, import_node_fs6.chmodSync)(temporary, mode);
2172
+ (0, import_node_fs6.renameSync)(temporary, path);
1236
2173
  } catch (error) {
1237
2174
  try {
1238
- (0, import_node_fs3.unlinkSync)(temporary);
2175
+ (0, import_node_fs6.unlinkSync)(temporary);
1239
2176
  } catch {
1240
2177
  }
1241
2178
  throw error;
@@ -1244,12 +2181,12 @@ function atomicWrite(path, content, mode) {
1244
2181
  function ensureDirectory(path, mode, privateDirectory = false) {
1245
2182
  let stat;
1246
2183
  try {
1247
- stat = (0, import_node_fs3.lstatSync)(path);
2184
+ stat = (0, import_node_fs6.lstatSync)(path);
1248
2185
  } catch (error) {
1249
2186
  if (error.code !== "ENOENT") throw error;
1250
- (0, import_node_fs3.mkdirSync)(path, { recursive: true, mode });
1251
- (0, import_node_fs3.chmodSync)(path, mode);
1252
- stat = (0, import_node_fs3.lstatSync)(path);
2187
+ (0, import_node_fs6.mkdirSync)(path, { recursive: true, mode });
2188
+ (0, import_node_fs6.chmodSync)(path, mode);
2189
+ stat = (0, import_node_fs6.lstatSync)(path);
1253
2190
  }
1254
2191
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
1255
2192
  throw new SystemError("unsafe-path", `${path} must be a real directory`);
@@ -1264,8 +2201,8 @@ function isDefaultSystemLayout(paths) {
1264
2201
  }
1265
2202
  function assertManagedPathSafety(paths, enforceRoot) {
1266
2203
  for (const path of [paths.prefix, paths.configDir, paths.stateDir, paths.unitsDir, paths.sysusersDir, paths.udevRulesDir]) {
1267
- if (!(0, import_node_fs3.existsSync)(path)) continue;
1268
- const stat = (0, import_node_fs3.lstatSync)(path);
2204
+ if (!(0, import_node_fs6.existsSync)(path)) continue;
2205
+ const stat = (0, import_node_fs6.lstatSync)(path);
1269
2206
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
1270
2207
  throw new SystemError("unsafe-path", `${path} must be a real directory`);
1271
2208
  }
@@ -1278,8 +2215,8 @@ function assertManagedPathSafety(paths, enforceRoot) {
1278
2215
  }
1279
2216
  }
1280
2217
  function writeManagedFile(path, content, mode, replace) {
1281
- if ((0, import_node_fs3.existsSync)(path)) {
1282
- const stat = (0, import_node_fs3.lstatSync)(path);
2218
+ if ((0, import_node_fs6.existsSync)(path)) {
2219
+ const stat = (0, import_node_fs6.lstatSync)(path);
1283
2220
  if (!stat.isFile() || stat.isSymbolicLink()) {
1284
2221
  throw new SystemError("unsafe-path", `${path} must be a regular file, not a symlink`);
1285
2222
  }
@@ -1318,7 +2255,7 @@ function parseEnvValue(value) {
1318
2255
  function readEnvDocument(path) {
1319
2256
  let raw;
1320
2257
  try {
1321
- raw = (0, import_node_fs3.readFileSync)(path, "utf8");
2258
+ raw = (0, import_node_fs6.readFileSync)(path, "utf8");
1322
2259
  } catch (error) {
1323
2260
  if (error.code === "ENOENT") return null;
1324
2261
  throw error;
@@ -1365,43 +2302,43 @@ function connectDestinationUrl(value) {
1365
2302
  return url.href;
1366
2303
  }
1367
2304
  function hubDatabasePath(paths) {
1368
- return (0, import_node_path4.join)(paths.stateDir, "hub", "telemetry.sqlite");
2305
+ return (0, import_node_path6.join)(paths.stateDir, "hub", "telemetry.sqlite");
1369
2306
  }
1370
2307
  function hubEnrollmentPath(paths) {
1371
- return (0, import_node_path4.join)(paths.stateDir, "hub", "enrollment.json");
2308
+ return (0, import_node_path6.join)(paths.stateDir, "hub", "enrollment.json");
1372
2309
  }
1373
2310
  function hubSocketPath(paths) {
1374
- return (0, import_node_path4.join)(paths.stateDir, "hub", "run", "hub.sock");
2311
+ return (0, import_node_path6.join)(paths.stateDir, "hub", "run", "hub.sock");
1375
2312
  }
1376
2313
  function collectorStatePath(paths) {
1377
- return (0, import_node_path4.join)(paths.stateDir, "collector", "state.json");
2314
+ return (0, import_node_path6.join)(paths.stateDir, "collector", "state.json");
1378
2315
  }
1379
2316
  function hubEnvPath(paths) {
1380
- return (0, import_node_path4.join)(paths.configDir, "hub.env");
2317
+ return (0, import_node_path6.join)(paths.configDir, "hub.env");
1381
2318
  }
1382
2319
  function collectorEnvPath(paths) {
1383
- return (0, import_node_path4.join)(paths.configDir, "collector.env");
2320
+ return (0, import_node_path6.join)(paths.configDir, "collector.env");
1384
2321
  }
1385
2322
  function sysusersPath(paths) {
1386
- return (0, import_node_path4.join)(paths.sysusersDir, SYSUSERS_NAME);
2323
+ return (0, import_node_path6.join)(paths.sysusersDir, SYSUSERS_NAME);
1387
2324
  }
1388
2325
  function powerSysusersPath(paths) {
1389
- return (0, import_node_path4.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
2326
+ return (0, import_node_path6.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
1390
2327
  }
1391
2328
  function raplRulePath(paths) {
1392
- return (0, import_node_path4.join)(paths.udevRulesDir, RAPL_RULE_NAME);
2329
+ return (0, import_node_path6.join)(paths.udevRulesDir, RAPL_RULE_NAME);
1393
2330
  }
1394
2331
  function stateFilePath(paths) {
1395
- return (0, import_node_path4.join)(paths.stateDir, STATE_FILE_NAME);
2332
+ return (0, import_node_path6.join)(paths.stateDir, STATE_FILE_NAME);
1396
2333
  }
1397
2334
  function readCollectorIdentity(path) {
1398
- if (!(0, import_node_fs3.existsSync)(path)) return null;
1399
- const stat = (0, import_node_fs3.lstatSync)(path);
2335
+ if (!(0, import_node_fs6.existsSync)(path)) return null;
2336
+ const stat = (0, import_node_fs6.lstatSync)(path);
1400
2337
  if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
1401
2338
  if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be private (0600)`);
1402
2339
  let raw;
1403
2340
  try {
1404
- raw = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
2341
+ raw = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
1405
2342
  } catch {
1406
2343
  throw new SystemError("invalid-file", `${path} is not valid JSON`);
1407
2344
  }
@@ -1435,7 +2372,7 @@ function readCollectorIdentity(path) {
1435
2372
  return { collectorId: record.collectorId, destinations };
1436
2373
  }
1437
2374
  function writeCollectorIdentity(path, identity) {
1438
- ensureDirectory((0, import_node_path4.dirname)(path), 448, true);
2375
+ ensureDirectory((0, import_node_path6.dirname)(path), 448, true);
1439
2376
  atomicWrite(path, `${JSON.stringify(identity)}
1440
2377
  `, 384);
1441
2378
  }
@@ -1475,7 +2412,7 @@ function ensureHubOwner(paths) {
1475
2412
  if (!Array.isArray(parsed)) throw new SystemError("invalid-config", "HUB_BROWSER_USERS must be a JSON array");
1476
2413
  if (parsed.length > 0) return void 0;
1477
2414
  }
1478
- const password = (0, import_node_crypto5.randomBytes)(18).toString("base64url");
2415
+ const password = (0, import_node_crypto7.randomBytes)(18).toString("base64url");
1479
2416
  const users = [{ username: "owner", passwordHash: hashPassword(password), machineIds: "*", admin: true }];
1480
2417
  writeEnvValue(hubEnvPath(paths), "HUB_BROWSER_USERS", JSON.stringify(users));
1481
2418
  return password;
@@ -1526,8 +2463,8 @@ function updateHubCollectorRegistry(paths, identity) {
1526
2463
  }
1527
2464
  function registerCollectorInHubDatabase(paths, identity, host) {
1528
2465
  const database = hubDatabasePath(paths);
1529
- if (!(0, import_node_fs3.existsSync)(database)) return { inserted: false };
1530
- const db = new import_node_sqlite.DatabaseSync(database, { timeout: 5e3 });
2466
+ if (!(0, import_node_fs6.existsSync)(database)) return { inserted: false };
2467
+ const db = new import_node_sqlite3.DatabaseSync(database, { timeout: 5e3 });
1531
2468
  try {
1532
2469
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
1533
2470
  if (!table) return { inserted: false };
@@ -1541,8 +2478,8 @@ function registerCollectorInHubDatabase(paths, identity, host) {
1541
2478
  }
1542
2479
  function findCollectorInHubDatabase(paths, collectorId) {
1543
2480
  const database = hubDatabasePath(paths);
1544
- if (!(0, import_node_fs3.existsSync)(database)) return null;
1545
- const db = new import_node_sqlite.DatabaseSync(database, { readOnly: true, timeout: 5e3 });
2481
+ if (!(0, import_node_fs6.existsSync)(database)) return null;
2482
+ const db = new import_node_sqlite3.DatabaseSync(database, { readOnly: true, timeout: 5e3 });
1546
2483
  try {
1547
2484
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
1548
2485
  if (!table) return null;
@@ -1554,11 +2491,11 @@ function findCollectorInHubDatabase(paths, collectorId) {
1554
2491
  }
1555
2492
  function createLocalEnrollmentGrant(paths, host) {
1556
2493
  const database = hubDatabasePath(paths);
1557
- if (!(0, import_node_fs3.existsSync)(database)) throw new SystemError("hub-unavailable", "the hub database does not exist yet");
1558
- const token = (0, import_node_crypto5.randomBytes)(32).toString("base64url");
1559
- const tokenHash = (0, import_node_crypto5.createHash)("sha256").update(token).digest("hex");
2494
+ if (!(0, import_node_fs6.existsSync)(database)) throw new SystemError("hub-unavailable", "the hub database does not exist yet");
2495
+ const token = (0, import_node_crypto7.randomBytes)(32).toString("base64url");
2496
+ const tokenHash = (0, import_node_crypto7.createHash)("sha256").update(token).digest("hex");
1560
2497
  const expiresAt = host.now() + 10 * 60 * 1e3;
1561
- const db = new import_node_sqlite.DatabaseSync(database, { timeout: 5e3 });
2498
+ const db = new import_node_sqlite3.DatabaseSync(database, { timeout: 5e3 });
1562
2499
  try {
1563
2500
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_grants'").get();
1564
2501
  if (!table) throw new SystemError("hub-unavailable", "the hub registry is not initialized");
@@ -1608,7 +2545,7 @@ async function pairCollectorWithHub(options) {
1608
2545
  note: "the local hub has no reachable loopback or HTTPS destination"
1609
2546
  };
1610
2547
  }
1611
- if (!(0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, UNIT_NAMES.hub))) {
2548
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, UNIT_NAMES.hub))) {
1612
2549
  return { paired: false, awaiting: true, hubWasActive: false, note: "the local hub unit is not installed" };
1613
2550
  }
1614
2551
  const hubWasActive = systemdActive(host, UNIT_NAMES.hub);
@@ -1621,7 +2558,7 @@ async function pairCollectorWithHub(options) {
1621
2558
  destinations: [{ url: destination.url, token: registered.token, sharing: { ...SYSTEM_DEFAULT_SHARING } }]
1622
2559
  };
1623
2560
  writeCollectorIdentity(collectorStatePath(paths), adopted);
1624
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
2561
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
1625
2562
  return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
1626
2563
  }
1627
2564
  }
@@ -1629,7 +2566,7 @@ async function pairCollectorWithHub(options) {
1629
2566
  try {
1630
2567
  grant = createLocalEnrollmentGrant(paths, host);
1631
2568
  } catch (error) {
1632
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
2569
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
1633
2570
  return {
1634
2571
  paired: false,
1635
2572
  awaiting: true,
@@ -1647,18 +2584,18 @@ async function pairCollectorWithHub(options) {
1647
2584
  note: `enrollment through the live hub failed: ${error instanceof Error ? error.message : String(error)}`
1648
2585
  };
1649
2586
  } finally {
1650
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
2587
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
1651
2588
  }
1652
2589
  if (!collectorConfigured(paths).configured) {
1653
2590
  return { paired: false, awaiting: true, hubWasActive, note: "the hub accepted enrollment but no destination was saved" };
1654
2591
  }
1655
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
2592
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
1656
2593
  return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
1657
2594
  }
1658
2595
  const identity = {
1659
- collectorId: existing?.collectorId ?? (0, import_node_crypto5.randomUUID)(),
1660
- machineId: (0, import_node_crypto5.randomUUID)(),
1661
- token: (0, import_node_crypto5.randomBytes)(32).toString("base64url")
2596
+ collectorId: existing?.collectorId ?? (0, import_node_crypto7.randomUUID)(),
2597
+ machineId: (0, import_node_crypto7.randomUUID)(),
2598
+ token: (0, import_node_crypto7.randomBytes)(32).toString("base64url")
1662
2599
  };
1663
2600
  const registry = registerCollectorInHubDatabase(paths, identity, host);
1664
2601
  let token = identity.token;
@@ -1685,7 +2622,7 @@ async function pairCollectorWithHub(options) {
1685
2622
  }
1686
2623
  ];
1687
2624
  writeCollectorIdentity(collectorStatePath(paths), next);
1688
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
2625
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
1689
2626
  return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
1690
2627
  }
1691
2628
  function unitCommon(name, description, paths, account, envPath, execStart, extraService = []) {
@@ -1702,7 +2639,7 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
1702
2639
  "Type=simple",
1703
2640
  `User=${account}`,
1704
2641
  `Group=${account}`,
1705
- `WorkingDirectory=${(0, import_node_path4.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
2642
+ `WorkingDirectory=${(0, import_node_path6.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
1706
2643
  `EnvironmentFile=${envPath.replace(/%/g, "%%")}`,
1707
2644
  "Environment=NODE_ENV=production",
1708
2645
  "Restart=on-failure",
@@ -1737,13 +2674,13 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
1737
2674
  }
1738
2675
  function renderSystemHubUnit(options) {
1739
2676
  const { paths, accounts } = options;
1740
- const hubScript = (0, import_node_path4.join)(paths.prefix, "current", "dist", "hub.cjs");
2677
+ const hubScript = (0, import_node_path6.join)(paths.prefix, "current", "dist", "hub.cjs");
1741
2678
  const extra = [
1742
2679
  `Environment=${systemdQuote(`HUB_DATABASE_PATH=${options.database}`)}`,
1743
2680
  `Environment=${systemdQuote(`HUB_STATE_FILE=${options.enrollment}`)}`,
1744
2681
  ...options.remote ? [`Environment=${systemdQuote(`HUB_SOCKET_PATH=${options.socket}`)}`] : [],
1745
- `ReadWritePaths=${systemdQuote((0, import_node_path4.join)(paths.stateDir, "hub"))}`,
1746
- `ReadOnlyPaths=${systemdQuote((0, import_node_path4.join)(paths.prefix, "current"))}`,
2682
+ `ReadWritePaths=${systemdQuote((0, import_node_path6.join)(paths.stateDir, "hub"))}`,
2683
+ `ReadOnlyPaths=${systemdQuote((0, import_node_path6.join)(paths.prefix, "current"))}`,
1747
2684
  "PrivateDevices=yes",
1748
2685
  "TimeoutStopSec=15s"
1749
2686
  ];
@@ -1762,14 +2699,14 @@ function renderSystemHubUnit(options) {
1762
2699
  }
1763
2700
  function renderSystemCollectorUnit(options) {
1764
2701
  const { paths, accounts } = options;
1765
- const collectorScript = (0, import_node_path4.join)(paths.prefix, "current", "dist", "collector.cjs");
2702
+ const collectorScript = (0, import_node_path6.join)(paths.prefix, "current", "dist", "collector.cjs");
1766
2703
  const extra = [
1767
2704
  ...options.powerAccess ? [`SupplementaryGroups=${accounts.powerGroup}`] : [],
1768
2705
  `Environment=${systemdQuote(`COLLECTOR_MODE=real`)}`,
1769
2706
  `Environment=${systemdQuote(`COLLECTOR_STATE_FILE=${options.collectorState}`)}`,
1770
- `Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0, import_node_path4.join)(paths.stateDir, "collector", "health.json")}`)}`,
1771
- `ReadWritePaths=${systemdQuote((0, import_node_path4.join)(paths.stateDir, "collector"))}`,
1772
- `ReadOnlyPaths=${systemdQuote((0, import_node_path4.join)(paths.prefix, "current"))}`,
2707
+ `Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0, import_node_path6.join)(paths.stateDir, "collector", "health.json")}`)}`,
2708
+ `ReadWritePaths=${systemdQuote((0, import_node_path6.join)(paths.stateDir, "collector"))}`,
2709
+ `ReadOnlyPaths=${systemdQuote((0, import_node_path6.join)(paths.prefix, "current"))}`,
1773
2710
  "TimeoutStopSec=10s"
1774
2711
  ];
1775
2712
  return [
@@ -1842,11 +2779,15 @@ function collectorEnvTemplate() {
1842
2779
  "# COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect",
1843
2780
  "# COLLECTOR_ENROLL_TOKEN=<one-use grant>",
1844
2781
  "# COLLECTOR_MODE=real",
2782
+ "# Unacknowledged samples wait in RAM by default: no queue is written to disk",
2783
+ "# and a collector restart loses what was never acknowledged. Set this to durable",
2784
+ "# to keep them in a per-hub SQLite outbox across collector restarts.",
2785
+ "# COLLECTOR_QUEUE_MODE=durable",
1845
2786
  ""
1846
2787
  ].join("\n");
1847
2788
  }
1848
2789
  function assertPrivateSourceFile(path, label) {
1849
- const stat = (0, import_node_fs3.lstatSync)(path);
2790
+ const stat = (0, import_node_fs6.lstatSync)(path);
1850
2791
  if (!stat.isFile() || stat.isSymbolicLink()) {
1851
2792
  throw new SystemError("unsafe-adoption", `${label} must be a regular file: ${path}`);
1852
2793
  }
@@ -1857,7 +2798,7 @@ function assertPrivateSourceFile(path, label) {
1857
2798
  function readJsonObject(path) {
1858
2799
  let raw;
1859
2800
  try {
1860
- raw = (0, import_node_fs3.readFileSync)(path, "utf8");
2801
+ raw = (0, import_node_fs6.readFileSync)(path, "utf8");
1861
2802
  } catch (error) {
1862
2803
  throw new SystemError("missing-file", `cannot read ${path}: ${error.message}`);
1863
2804
  }
@@ -1880,8 +2821,8 @@ function isCanonicalComponents(value) {
1880
2821
  }
1881
2822
  function readSystemState(paths) {
1882
2823
  const path = stateFilePath(paths);
1883
- if (!(0, import_node_fs3.existsSync)(path)) return null;
1884
- const stat = (0, import_node_fs3.lstatSync)(path);
2824
+ if (!(0, import_node_fs6.existsSync)(path)) return null;
2825
+ const stat = (0, import_node_fs6.lstatSync)(path);
1885
2826
  if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
1886
2827
  if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be 0600`);
1887
2828
  const raw = readJsonObject(path);
@@ -1913,7 +2854,7 @@ function readSystemState(paths) {
1913
2854
  }
1914
2855
  function systemStateIsLegacy(paths) {
1915
2856
  const path = stateFilePath(paths);
1916
- if (!(0, import_node_fs3.existsSync)(path)) return false;
2857
+ if (!(0, import_node_fs6.existsSync)(path)) return false;
1917
2858
  let raw;
1918
2859
  try {
1919
2860
  raw = readJsonObject(path);
@@ -1964,24 +2905,24 @@ function validateVersion(value) {
1964
2905
  return value;
1965
2906
  }
1966
2907
  function releaseVersion(root) {
1967
- const metadata = readJsonObject((0, import_node_path4.join)(root, "package.json"));
2908
+ const metadata = readJsonObject((0, import_node_path6.join)(root, "package.json"));
1968
2909
  if (Object.hasOwn(metadata, "rigscopeConfigVersion")) {
1969
2910
  throw new SystemError("invalid-release", "release uses unfinished configuration metadata; select a canonical release");
1970
2911
  }
1971
2912
  return validateVersion(metadata.version);
1972
2913
  }
1973
2914
  function releaseSchemaVersion(root) {
1974
- const manifestPath = (0, import_node_path4.join)(root, "manifest.json");
1975
- if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
2915
+ const manifestPath = (0, import_node_path6.join)(root, "manifest.json");
2916
+ if (!(0, import_node_fs6.existsSync)(manifestPath)) return null;
1976
2917
  const manifest = readJsonObject(manifestPath);
1977
2918
  return Number.isSafeInteger(manifest.schemaVersion) ? Number(manifest.schemaVersion) : null;
1978
2919
  }
1979
2920
  function assertReleaseArtifacts(root) {
1980
2921
  for (const artifact of REQUIRED_ARTIFACTS) {
1981
- const path = (0, import_node_path4.join)(root, artifact);
2922
+ const path = (0, import_node_path6.join)(root, artifact);
1982
2923
  let stat;
1983
2924
  try {
1984
- stat = (0, import_node_fs3.lstatSync)(path);
2925
+ stat = (0, import_node_fs6.lstatSync)(path);
1985
2926
  } catch {
1986
2927
  throw new SystemError("missing-artifact", `release is missing ${artifact}`);
1987
2928
  }
@@ -1989,12 +2930,12 @@ function assertReleaseArtifacts(root) {
1989
2930
  throw new SystemError("invalid-artifact", `release artifact is not a non-empty regular file: ${artifact}`);
1990
2931
  }
1991
2932
  }
1992
- const buildId = (0, import_node_fs3.readFileSync)((0, import_node_path4.join)(root, ".next", "BUILD_ID"), "utf8").trim();
2933
+ const buildId = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, ".next", "BUILD_ID"), "utf8").trim();
1993
2934
  if (buildId === "" || /\s/.test(buildId)) throw new SystemError("invalid-artifact", ".next/BUILD_ID is invalid");
1994
2935
  for (const directory of [".next/server", ".next/static"]) {
1995
2936
  let stat;
1996
2937
  try {
1997
- stat = (0, import_node_fs3.lstatSync)((0, import_node_path4.join)(root, directory));
2938
+ stat = (0, import_node_fs6.lstatSync)((0, import_node_path6.join)(root, directory));
1998
2939
  } catch {
1999
2940
  throw new SystemError("missing-artifact", `release is missing ${directory}/`);
2000
2941
  }
@@ -2002,10 +2943,10 @@ function assertReleaseArtifacts(root) {
2002
2943
  }
2003
2944
  }
2004
2945
  function verifyReleaseManifest(root) {
2005
- const manifestPath = (0, import_node_path4.join)(root, "manifest.json");
2006
- if (!(0, import_node_fs3.existsSync)(manifestPath)) return;
2946
+ const manifestPath = (0, import_node_path6.join)(root, "manifest.json");
2947
+ if (!(0, import_node_fs6.existsSync)(manifestPath)) return;
2007
2948
  const manifest = readJsonObject(manifestPath);
2008
- const packageJson = readJsonObject((0, import_node_path4.join)(root, "package.json"));
2949
+ const packageJson = readJsonObject((0, import_node_path6.join)(root, "package.json"));
2009
2950
  if (manifest.name !== void 0 && manifest.name !== packageJson.name) {
2010
2951
  throw new SystemError("manifest-mismatch", "release manifest name does not match package.json");
2011
2952
  }
@@ -2024,17 +2965,17 @@ function verifyReleaseManifest(root) {
2024
2965
  if (typeof relativePath !== "string" || relativePath === "" || relativePath.startsWith("/") || relativePath.includes("\\") || relativePath.split("/").includes("..")) {
2025
2966
  throw new SystemError("invalid-manifest", `release manifest contains an unsafe path: ${String(relativePath)}`);
2026
2967
  }
2027
- const absolute = (0, import_node_path4.join)(root, relativePath);
2028
- const stat = (0, import_node_fs3.lstatSync)(absolute);
2968
+ const absolute = (0, import_node_path6.join)(root, relativePath);
2969
+ const stat = (0, import_node_fs6.lstatSync)(absolute);
2029
2970
  if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("invalid-manifest", `release artifact is not a regular file: ${relativePath}`);
2030
2971
  if (entry.bytes !== stat.size) throw new SystemError("artifact-mismatch", `release artifact size changed: ${relativePath}`);
2031
- if (entry.sha256 !== sha256File(absolute)) throw new SystemError("artifact-mismatch", `release artifact hash changed: ${relativePath}`);
2972
+ if (entry.sha256 !== sha256File2(absolute)) throw new SystemError("artifact-mismatch", `release artifact hash changed: ${relativePath}`);
2032
2973
  listed.add(relativePath);
2033
2974
  }
2034
2975
  const visit = (current) => {
2035
- for (const entry of (0, import_node_fs3.readdirSync)(current, { withFileTypes: true })) {
2036
- const absolute = (0, import_node_path4.join)(current, entry.name);
2037
- const rel = (0, import_node_path4.relative)(root, absolute).split(import_node_path4.sep).join("/");
2976
+ for (const entry of (0, import_node_fs6.readdirSync)(current, { withFileTypes: true })) {
2977
+ const absolute = (0, import_node_path6.join)(current, entry.name);
2978
+ const rel = (0, import_node_path6.relative)(root, absolute).split(import_node_path6.sep).join("/");
2038
2979
  if (rel === "node_modules" || rel.startsWith("node_modules/") || rel === ".next/cache" || rel.startsWith(".next/cache/")) continue;
2039
2980
  if (entry.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${rel}`);
2040
2981
  if (entry.isDirectory()) {
@@ -2049,49 +2990,49 @@ function verifyReleaseManifest(root) {
2049
2990
  }
2050
2991
  function assertNoSymlinks(root) {
2051
2992
  const visit = (path) => {
2052
- const stat = (0, import_node_fs3.lstatSync)(path);
2053
- if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${(0, import_node_path4.relative)(root, path)}`);
2993
+ const stat = (0, import_node_fs6.lstatSync)(path);
2994
+ if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${(0, import_node_path6.relative)(root, path)}`);
2054
2995
  if (!stat.isDirectory()) return;
2055
- for (const entry of (0, import_node_fs3.readdirSync)(path)) visit((0, import_node_path4.join)(path, entry));
2996
+ for (const entry of (0, import_node_fs6.readdirSync)(path)) visit((0, import_node_path6.join)(path, entry));
2056
2997
  };
2057
2998
  visit(root);
2058
2999
  }
2059
3000
  function copyReleaseTree(source, destination) {
2060
3001
  const visit = (current, target) => {
2061
- const stat = (0, import_node_fs3.lstatSync)(current);
2062
- if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${(0, import_node_path4.relative)(source, current)}`);
3002
+ const stat = (0, import_node_fs6.lstatSync)(current);
3003
+ if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${(0, import_node_path6.relative)(source, current)}`);
2063
3004
  if (stat.isDirectory()) {
2064
- (0, import_node_fs3.mkdirSync)(target, { recursive: true, mode: 493 });
2065
- for (const entry of (0, import_node_fs3.readdirSync)(current)) {
2066
- const child = (0, import_node_path4.join)(current, entry);
2067
- const rel = (0, import_node_path4.relative)(source, child);
3005
+ (0, import_node_fs6.mkdirSync)(target, { recursive: true, mode: 493 });
3006
+ for (const entry of (0, import_node_fs6.readdirSync)(current)) {
3007
+ const child = (0, import_node_path6.join)(current, entry);
3008
+ const rel = (0, import_node_path6.relative)(source, child);
2068
3009
  if (rel === ".git" || rel === ".next-dev" || rel === ".next/cache" || rel === "node_modules" || rel.startsWith("node_modules/")) {
2069
3010
  continue;
2070
3011
  }
2071
- visit(child, (0, import_node_path4.join)(target, entry));
3012
+ visit(child, (0, import_node_path6.join)(target, entry));
2072
3013
  }
2073
3014
  return;
2074
3015
  }
2075
- if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0, import_node_path4.relative)(source, current)}`);
2076
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(target), { recursive: true, mode: 493 });
2077
- (0, import_node_fs3.copyFileSync)(current, target);
2078
- (0, import_node_fs3.chmodSync)(target, 420);
3016
+ if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0, import_node_path6.relative)(source, current)}`);
3017
+ (0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(target), { recursive: true, mode: 493 });
3018
+ (0, import_node_fs6.copyFileSync)(current, target);
3019
+ (0, import_node_fs6.chmodSync)(target, 420);
2079
3020
  };
2080
3021
  visit(source, destination);
2081
3022
  }
2082
3023
  function copyReleaseAllowlisted(source, destination) {
2083
- for (const entry of (0, import_node_fs3.readdirSync)(source)) {
3024
+ for (const entry of (0, import_node_fs6.readdirSync)(source)) {
2084
3025
  if (!PACKAGE_TOP_LEVEL.has(entry)) continue;
2085
3026
  if (entry === "node_modules") continue;
2086
- const child = (0, import_node_path4.join)(source, entry);
2087
- const target = (0, import_node_path4.join)(destination, entry);
2088
- const stat = (0, import_node_fs3.lstatSync)(child);
3027
+ const child = (0, import_node_path6.join)(source, entry);
3028
+ const target = (0, import_node_path6.join)(destination, entry);
3029
+ const stat = (0, import_node_fs6.lstatSync)(child);
2089
3030
  if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${entry}`);
2090
3031
  if (stat.isDirectory()) {
2091
3032
  copyReleaseTree(child, target);
2092
3033
  } else if (stat.isFile()) {
2093
- (0, import_node_fs3.copyFileSync)(child, target);
2094
- (0, import_node_fs3.chmodSync)(target, 420);
3034
+ (0, import_node_fs6.copyFileSync)(child, target);
3035
+ (0, import_node_fs6.chmodSync)(target, 420);
2095
3036
  }
2096
3037
  }
2097
3038
  }
@@ -2126,28 +3067,28 @@ function extractArchive(archive, stage, host) {
2126
3067
  }
2127
3068
  function discardStagedRelease(staged) {
2128
3069
  if (staged === null) return;
2129
- (0, import_node_fs3.rmSync)(staged.stageDir, { recursive: true, force: true });
3070
+ (0, import_node_fs6.rmSync)(staged.stageDir, { recursive: true, force: true });
2130
3071
  }
2131
3072
  function releaseTargetDir(paths, version) {
2132
- return (0, import_node_path4.join)(paths.prefix, "releases", version);
3073
+ return (0, import_node_path6.join)(paths.prefix, "releases", version);
2133
3074
  }
2134
3075
  function assertReleaseTargetAvailable(paths, releaseDir, replaceRelease) {
2135
- if (!(0, import_node_fs3.existsSync)(releaseDir)) return;
3076
+ if (!(0, import_node_fs6.existsSync)(releaseDir)) return;
2136
3077
  const current = currentReleaseDir(paths);
2137
3078
  const previous = previousReleaseDir(paths);
2138
3079
  if (!replaceRelease || current === releaseDir || previous === releaseDir) {
2139
3080
  throw new SystemError(
2140
3081
  "release-exists",
2141
- `release ${(0, import_node_path4.basename)(releaseDir)} is already installed${current === releaseDir || previous === releaseDir ? " or retained" : ""}; choose a new version`
3082
+ `release ${(0, import_node_path6.basename)(releaseDir)} is already installed${current === releaseDir || previous === releaseDir ? " or retained" : ""}; choose a new version`
2142
3083
  );
2143
3084
  }
2144
3085
  }
2145
3086
  function stageRelease(options) {
2146
- const source = (0, import_node_fs3.realpathSync)((0, import_node_path4.resolve)(options.source));
2147
- const stat = (0, import_node_fs3.lstatSync)(source);
3087
+ const source = (0, import_node_fs6.realpathSync)((0, import_node_path6.resolve)(options.source));
3088
+ const stat = (0, import_node_fs6.lstatSync)(source);
2148
3089
  ensureDirectory(options.paths.prefix, 493);
2149
- const stageDir = (0, import_node_path4.join)(options.paths.prefix, `.stage-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2150
- (0, import_node_fs3.mkdirSync)(stageDir, { recursive: true, mode: 448 });
3090
+ const stageDir = (0, import_node_path6.join)(options.paths.prefix, `.stage-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3091
+ (0, import_node_fs6.mkdirSync)(stageDir, { recursive: true, mode: 448 });
2151
3092
  try {
2152
3093
  if (stat.isDirectory() && !stat.isSymbolicLink()) {
2153
3094
  copyReleaseAllowlisted(source, stageDir);
@@ -2165,7 +3106,7 @@ function stageRelease(options) {
2165
3106
  artifacts: [...REQUIRED_ARTIFACTS]
2166
3107
  };
2167
3108
  } catch (error) {
2168
- (0, import_node_fs3.rmSync)(stageDir, { recursive: true, force: true });
3109
+ (0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
2169
3110
  throw error;
2170
3111
  }
2171
3112
  }
@@ -2191,13 +3132,13 @@ function prepareStagedRelease(options) {
2191
3132
  }
2192
3133
  }
2193
3134
  function commitStagedRelease(options) {
2194
- const releasesDir = (0, import_node_path4.join)(options.paths.prefix, "releases");
3135
+ const releasesDir = (0, import_node_path6.join)(options.paths.prefix, "releases");
2195
3136
  ensureDirectory(releasesDir, 493);
2196
3137
  const releaseDir = releaseTargetDir(options.paths, options.staged.version);
2197
3138
  assertReleaseTargetAvailable(options.paths, releaseDir, options.replaceRelease);
2198
- if ((0, import_node_fs3.existsSync)(releaseDir)) (0, import_node_fs3.rmSync)(releaseDir, { recursive: true, force: true });
2199
- (0, import_node_fs3.renameSync)(options.staged.stageDir, releaseDir);
2200
- ensureDirectory((0, import_node_path4.join)(releaseDir, ".next", "cache"), 493);
3139
+ if ((0, import_node_fs6.existsSync)(releaseDir)) (0, import_node_fs6.rmSync)(releaseDir, { recursive: true, force: true });
3140
+ (0, import_node_fs6.renameSync)(options.staged.stageDir, releaseDir);
3141
+ ensureDirectory((0, import_node_path6.join)(releaseDir, ".next", "cache"), 493);
2201
3142
  return {
2202
3143
  version: options.staged.version,
2203
3144
  releaseDir,
@@ -2206,20 +3147,20 @@ function commitStagedRelease(options) {
2206
3147
  };
2207
3148
  }
2208
3149
  function dependencyNames(root) {
2209
- const pkg = readJsonObject((0, import_node_path4.join)(root, "package.json"));
3150
+ const pkg = readJsonObject((0, import_node_path6.join)(root, "package.json"));
2210
3151
  const dependencies = pkg.dependencies;
2211
3152
  if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) return [];
2212
3153
  return Object.keys(dependencies).filter((name) => /^[A-Za-z0-9@/._-]+$/.test(name));
2213
3154
  }
2214
3155
  function missingDependencies(root) {
2215
- return dependencyNames(root).filter((name) => !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(root, "node_modules", name, "package.json")));
3156
+ return dependencyNames(root).filter((name) => !(0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, "node_modules", name, "package.json")));
2216
3157
  }
2217
3158
  function installDependencies(root, host, command = "npm") {
2218
- const hasLock = (0, import_node_fs3.existsSync)((0, import_node_path4.join)(root, "package-lock.json"));
3159
+ const hasLock = (0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, "package-lock.json"));
2219
3160
  const args = hasLock ? ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"];
2220
- const siblingNpm = (0, import_node_path4.join)((0, import_node_path4.dirname)(process.execPath), "npm");
2221
- const executable = command === "npm" && (0, import_node_fs3.existsSync)(siblingNpm) ? siblingNpm : command;
2222
- const nodeBin = (0, import_node_path4.dirname)(process.execPath);
3161
+ const siblingNpm = (0, import_node_path6.join)((0, import_node_path6.dirname)(process.execPath), "npm");
3162
+ const executable = command === "npm" && (0, import_node_fs6.existsSync)(siblingNpm) ? siblingNpm : command;
3163
+ const nodeBin = (0, import_node_path6.dirname)(process.execPath);
2223
3164
  const path = [nodeBin, process.env.PATH ?? ""].filter(Boolean).join(":");
2224
3165
  runRequired(host, executable, args, "production dependency installation", {
2225
3166
  cwd: root,
@@ -2228,21 +3169,21 @@ function installDependencies(root, host, command = "npm") {
2228
3169
  });
2229
3170
  }
2230
3171
  function currentReleaseDir(paths) {
2231
- const link = (0, import_node_path4.join)(paths.prefix, "current");
3172
+ const link = (0, import_node_path6.join)(paths.prefix, "current");
2232
3173
  try {
2233
- const target = (0, import_node_fs3.readlinkSync)(link);
2234
- const absolute = (0, import_node_path4.isAbsolute)(target) ? target : (0, import_node_path4.resolve)(paths.prefix, target);
2235
- return isWithin((0, import_node_path4.join)(paths.prefix, "releases"), absolute) ? absolute : null;
3174
+ const target = (0, import_node_fs6.readlinkSync)(link);
3175
+ const absolute = (0, import_node_path6.isAbsolute)(target) ? target : (0, import_node_path6.resolve)(paths.prefix, target);
3176
+ return isWithin((0, import_node_path6.join)(paths.prefix, "releases"), absolute) ? absolute : null;
2236
3177
  } catch {
2237
3178
  return null;
2238
3179
  }
2239
3180
  }
2240
3181
  function previousReleaseDir(paths) {
2241
- const link = (0, import_node_path4.join)(paths.prefix, "previous");
3182
+ const link = (0, import_node_path6.join)(paths.prefix, "previous");
2242
3183
  try {
2243
- const target = (0, import_node_fs3.readlinkSync)(link);
2244
- const absolute = (0, import_node_path4.isAbsolute)(target) ? target : (0, import_node_path4.resolve)(paths.prefix, target);
2245
- return isWithin((0, import_node_path4.join)(paths.prefix, "releases"), absolute) ? absolute : null;
3184
+ const target = (0, import_node_fs6.readlinkSync)(link);
3185
+ const absolute = (0, import_node_path6.isAbsolute)(target) ? target : (0, import_node_path6.resolve)(paths.prefix, target);
3186
+ return isWithin((0, import_node_path6.join)(paths.prefix, "releases"), absolute) ? absolute : null;
2246
3187
  } catch {
2247
3188
  return null;
2248
3189
  }
@@ -2250,11 +3191,11 @@ function previousReleaseDir(paths) {
2250
3191
  function replaceSymlink(link, target, host) {
2251
3192
  const temporary = `${link}.tmp-${process.pid}-${Date.now()}`;
2252
3193
  try {
2253
- (0, import_node_fs3.symlinkSync)(target, temporary);
2254
- (0, import_node_fs3.renameSync)(temporary, link);
3194
+ (0, import_node_fs6.symlinkSync)(target, temporary);
3195
+ (0, import_node_fs6.renameSync)(temporary, link);
2255
3196
  } catch (error) {
2256
3197
  try {
2257
- (0, import_node_fs3.unlinkSync)(temporary);
3198
+ (0, import_node_fs6.unlinkSync)(temporary);
2258
3199
  } catch {
2259
3200
  }
2260
3201
  throw error;
@@ -2263,8 +3204,8 @@ function replaceSymlink(link, target, host) {
2263
3204
  }
2264
3205
  function removeSymlink(link) {
2265
3206
  try {
2266
- const stat = (0, import_node_fs3.lstatSync)(link);
2267
- if (stat.isSymbolicLink()) (0, import_node_fs3.unlinkSync)(link);
3207
+ const stat = (0, import_node_fs6.lstatSync)(link);
3208
+ if (stat.isSymbolicLink()) (0, import_node_fs6.unlinkSync)(link);
2268
3209
  } catch {
2269
3210
  }
2270
3211
  }
@@ -2277,18 +3218,18 @@ function nodeVersion(host, nodePath) {
2277
3218
  return output;
2278
3219
  }
2279
3220
  function ensureRootOwnedNode(options) {
2280
- const selected = (0, import_node_path4.resolve)(options.nodePath ?? process.execPath);
2281
- const real = (0, import_node_fs3.realpathSync)(selected);
3221
+ const selected = (0, import_node_path6.resolve)(options.nodePath ?? process.execPath);
3222
+ const real = (0, import_node_fs6.realpathSync)(selected);
2282
3223
  nodeVersion(options.host, real);
2283
- const stat = (0, import_node_fs3.statSync)(real);
2284
- const insideRelease = isWithin((0, import_node_path4.join)(options.paths.prefix, "releases"), real);
3224
+ const stat = (0, import_node_fs6.statSync)(real);
3225
+ const insideRelease = isWithin((0, import_node_path6.join)(options.paths.prefix, "releases"), real);
2285
3226
  const unsafe = stat.uid !== 0 || (stat.mode & 18) !== 0 || options.copyNode === true || insideRelease;
2286
3227
  if (!unsafe || options.trustedNode === true) return real;
2287
- const target = (0, import_node_path4.join)(options.paths.prefix, "node", "node");
3228
+ const target = (0, import_node_path6.join)(options.paths.prefix, "node", "node");
2288
3229
  if (!options.apply) return target;
2289
- ensureDirectory((0, import_node_path4.join)(options.paths.prefix, "node"), 493);
2290
- (0, import_node_fs3.copyFileSync)(real, target);
2291
- (0, import_node_fs3.chmodSync)(target, 493);
3230
+ ensureDirectory((0, import_node_path6.join)(options.paths.prefix, "node"), 493);
3231
+ (0, import_node_fs6.copyFileSync)(real, target);
3232
+ (0, import_node_fs6.chmodSync)(target, 493);
2292
3233
  runRequired(options.host, "chown", ["root:root", target], "node ownership");
2293
3234
  nodeVersion(options.host, target);
2294
3235
  return target;
@@ -2317,8 +3258,8 @@ function validatedOrigin(value) {
2317
3258
  return value;
2318
3259
  }
2319
3260
  function readAdoptionSetup(source) {
2320
- const configPath = (0, import_node_path4.join)(source, "config.json");
2321
- if (!(0, import_node_fs3.existsSync)(configPath)) {
3261
+ const configPath = (0, import_node_path6.join)(source, "config.json");
3262
+ if (!(0, import_node_fs6.existsSync)(configPath)) {
2322
3263
  throw new SystemError("invalid-adoption", "adopted installation has no config.json");
2323
3264
  }
2324
3265
  assertPrivateSourceFile(configPath, "adopted setup");
@@ -2366,35 +3307,35 @@ function detectUserInstallations(options = {}) {
2366
3307
  const roots = options.homeRoots ?? ["/home", "/root"];
2367
3308
  const found = [];
2368
3309
  for (const homeRoot of roots) {
2369
- if (!(0, import_node_fs3.existsSync)(homeRoot)) continue;
2370
- const homes = homeRoot === "/root" ? ["/root"] : (0, import_node_fs3.readdirSync)(homeRoot).map((entry) => (0, import_node_path4.join)(homeRoot, entry));
3310
+ if (!(0, import_node_fs6.existsSync)(homeRoot)) continue;
3311
+ const homes = homeRoot === "/root" ? ["/root"] : (0, import_node_fs6.readdirSync)(homeRoot).map((entry) => (0, import_node_path6.join)(homeRoot, entry));
2371
3312
  for (const home of homes) {
2372
3313
  let stat;
2373
3314
  try {
2374
- stat = (0, import_node_fs3.lstatSync)(home);
3315
+ stat = (0, import_node_fs6.lstatSync)(home);
2375
3316
  } catch {
2376
3317
  continue;
2377
3318
  }
2378
3319
  if (!stat.isDirectory()) continue;
2379
- const unitDir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
3320
+ const unitDir = (0, import_node_path6.join)(home, ".config", "systemd", "user");
2380
3321
  let units = [];
2381
3322
  try {
2382
- units = (0, import_node_fs3.readdirSync)(unitDir);
3323
+ units = (0, import_node_fs6.readdirSync)(unitDir);
2383
3324
  } catch {
2384
3325
  units = [];
2385
3326
  }
2386
3327
  for (const unitName of units) {
2387
3328
  if (!/^rigscope(?:-[0-9a-f]{12})?(?:-(?:hub|collector))?\.service$/.test(unitName)) continue;
2388
- const unitPath = (0, import_node_path4.join)(unitDir, unitName);
3329
+ const unitPath = (0, import_node_path6.join)(unitDir, unitName);
2389
3330
  let execStart = null;
2390
3331
  try {
2391
- const line = (0, import_node_fs3.readFileSync)(unitPath, "utf8").split("\n").find((item) => item.startsWith("ExecStart="));
3332
+ const line = (0, import_node_fs6.readFileSync)(unitPath, "utf8").split("\n").find((item) => item.startsWith("ExecStart="));
2392
3333
  execStart = line ? line.slice("ExecStart=".length) : null;
2393
3334
  } catch {
2394
3335
  }
2395
- const stateCandidates = [(0, import_node_path4.join)(home, ".local", "state", "rigscope")];
2396
- const stateDir = stateCandidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate)) ?? null;
2397
- found.push({ user: (0, import_node_path4.basename)(home), home, unitPath, unitName, execStart, stateDir });
3336
+ const stateCandidates = [(0, import_node_path6.join)(home, ".local", "state", "rigscope")];
3337
+ const stateDir = stateCandidates.find((candidate) => (0, import_node_fs6.existsSync)(candidate)) ?? null;
3338
+ found.push({ user: (0, import_node_path6.basename)(home), home, unitPath, unitName, execStart, stateDir });
2398
3339
  }
2399
3340
  }
2400
3341
  }
@@ -2402,9 +3343,9 @@ function detectUserInstallations(options = {}) {
2402
3343
  }
2403
3344
  function lockIsLive(path) {
2404
3345
  try {
2405
- const stat = (0, import_node_fs3.lstatSync)(path);
3346
+ const stat = (0, import_node_fs6.lstatSync)(path);
2406
3347
  if (!stat.isFile() || stat.size > 4096) return { exists: true, live: false, pid: null, ambiguous: true };
2407
- const owner = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
3348
+ const owner = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
2408
3349
  const pid = owner.pid;
2409
3350
  if (!Number.isSafeInteger(pid) || pid < 1) return { exists: true, live: false, pid: null, ambiguous: true };
2410
3351
  try {
@@ -2422,9 +3363,9 @@ function lockIsLive(path) {
2422
3363
  }
2423
3364
  function directoryLocks(directory) {
2424
3365
  return [
2425
- (0, import_node_path4.join)(directory, "hub", "telemetry.sqlite.hub.lock"),
2426
- (0, import_node_path4.join)(directory, "run", "rigscope.lock"),
2427
- (0, import_node_path4.join)(directory, "collector", "collector-runtime.lock")
3366
+ (0, import_node_path6.join)(directory, "hub", "telemetry.sqlite.hub.lock"),
3367
+ (0, import_node_path6.join)(directory, "run", "rigscope.lock"),
3368
+ (0, import_node_path6.join)(directory, "collector", "collector-runtime.lock")
2428
3369
  ];
2429
3370
  }
2430
3371
  function assertDirectoryStopped(directory, scope = "local") {
@@ -2455,13 +3396,13 @@ function assertSystemStartAllowed(paths, components) {
2455
3396
  }
2456
3397
  function detectPowerDriver(moduleRoot = "/sys/module") {
2457
3398
  const candidates = ["intel_rapl_msr", "intel_rapl_common", "intel_rapl_tpmi", "intel_rapl", "amd_energy"];
2458
- return candidates.filter((name) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(moduleRoot, name)));
3399
+ return candidates.filter((name) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(moduleRoot, name)));
2459
3400
  }
2460
3401
  function readPasswd(path = "/etc/passwd") {
2461
3402
  const users = /* @__PURE__ */ new Map();
2462
3403
  let lines;
2463
3404
  try {
2464
- lines = (0, import_node_fs3.readFileSync)(path, "utf8").split("\n");
3405
+ lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
2465
3406
  } catch {
2466
3407
  return users;
2467
3408
  }
@@ -2478,7 +3419,7 @@ function readGroups(path = "/etc/group") {
2478
3419
  const groups = /* @__PURE__ */ new Map();
2479
3420
  let lines;
2480
3421
  try {
2481
- lines = (0, import_node_fs3.readFileSync)(path, "utf8").split("\n");
3422
+ lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
2482
3423
  } catch {
2483
3424
  return groups;
2484
3425
  }
@@ -2506,7 +3447,7 @@ function inspectPowerAccess(options = {}) {
2506
3447
  const sysusers = options.sysusersPath ?? powerSysusersPath(paths);
2507
3448
  let ruleText = null;
2508
3449
  try {
2509
- ruleText = (0, import_node_fs3.readFileSync)(rulePath, "utf8");
3450
+ ruleText = (0, import_node_fs6.readFileSync)(rulePath, "utf8");
2510
3451
  } catch {
2511
3452
  ruleText = null;
2512
3453
  }
@@ -2523,27 +3464,27 @@ function inspectPowerAccess(options = {}) {
2523
3464
  const domains = [];
2524
3465
  let entries = [];
2525
3466
  try {
2526
- entries = (0, import_node_fs3.readdirSync)(paths.powercapRoot);
3467
+ entries = (0, import_node_fs6.readdirSync)(paths.powercapRoot);
2527
3468
  } catch {
2528
3469
  entries = [];
2529
3470
  }
2530
3471
  for (const entry of entries.filter((name) => /^intel-rapl:\d+$/.test(name)).sort()) {
2531
- const domainPath = (0, import_node_path4.join)(paths.powercapRoot, entry);
3472
+ const domainPath = (0, import_node_path6.join)(paths.powercapRoot, entry);
2532
3473
  let name = entry;
2533
3474
  try {
2534
- name = (0, import_node_fs3.readFileSync)((0, import_node_path4.join)(domainPath, "name"), "utf8").trim() || entry;
3475
+ name = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(domainPath, "name"), "utf8").trim() || entry;
2535
3476
  } catch {
2536
3477
  }
2537
3478
  if (!/^package/.test(name)) continue;
2538
- const energyPath = (0, import_node_path4.join)(domainPath, "energy_uj");
2539
- const maxRangePath = (0, import_node_path4.join)(domainPath, "max_energy_range_uj");
3479
+ const energyPath = (0, import_node_path6.join)(domainPath, "energy_uj");
3480
+ const maxRangePath = (0, import_node_path6.join)(domainPath, "max_energy_range_uj");
2540
3481
  const readStat = (path) => {
2541
3482
  if (options.statOverride) {
2542
3483
  const override = options.statOverride(path);
2543
3484
  return override === null ? null : { mode: modeString(override.mode), uid: override.uid, gid: override.gid };
2544
3485
  }
2545
3486
  try {
2546
- const stat = (0, import_node_fs3.statSync)(path);
3487
+ const stat = (0, import_node_fs6.statSync)(path);
2547
3488
  return { mode: modeString(stat.mode), uid: stat.uid, gid: stat.gid };
2548
3489
  } catch {
2549
3490
  return null;
@@ -2577,7 +3518,7 @@ function inspectPowerAccess(options = {}) {
2577
3518
  problems.push(`collector account ${accounts.collector} is not a member of ${accounts.powerGroup}`);
2578
3519
  }
2579
3520
  if (!compliant) problems.push(`persistent udev rule is missing or not compliant: ${rulePath}`);
2580
- if (!(0, import_node_fs3.existsSync)(sysusers)) problems.push(`power sysusers definition is missing: ${sysusers}`);
3521
+ if (!(0, import_node_fs6.existsSync)(sysusers)) problems.push(`power sysusers definition is missing: ${sysusers}`);
2581
3522
  for (const domain of domains) {
2582
3523
  if (!domain.energyExists) problems.push(`${domain.name}: energy_uj is unavailable`);
2583
3524
  else if (domain.energy?.worldReadable) problems.push(`${domain.name}: energy_uj is world-readable`);
@@ -2596,7 +3537,7 @@ function inspectPowerAccess(options = {}) {
2596
3537
  collectorGids,
2597
3538
  group: { name: accounts.powerGroup, exists: group !== null, gid: group?.gid ?? null, members: group?.members ?? [] },
2598
3539
  rule: { path: rulePath, present: ruleText !== null, compliant },
2599
- sysusers: { path: sysusers, present: (0, import_node_fs3.existsSync)(sysusers) },
3540
+ sysusers: { path: sysusers, present: (0, import_node_fs6.existsSync)(sysusers) },
2600
3541
  collectorCanRead,
2601
3542
  persistent: compliant && group !== null && collector !== null && group.members.includes(accounts.collector),
2602
3543
  worldReadable,
@@ -2605,15 +3546,15 @@ function inspectPowerAccess(options = {}) {
2605
3546
  };
2606
3547
  }
2607
3548
  async function backupDatabase(source, output, host) {
2608
- ensureDirectory((0, import_node_path4.dirname)(output), 448, true);
2609
- const db = new import_node_sqlite.DatabaseSync(source, { readOnly: true, timeout: 5e3 });
3549
+ ensureDirectory((0, import_node_path6.dirname)(output), 448, true);
3550
+ const db = new import_node_sqlite3.DatabaseSync(source, { readOnly: true, timeout: 5e3 });
2610
3551
  try {
2611
- await (0, import_node_sqlite.backup)(db, output);
3552
+ await (0, import_node_sqlite3.backup)(db, output);
2612
3553
  } finally {
2613
3554
  db.close();
2614
3555
  }
2615
- (0, import_node_fs3.chmodSync)(output, 384);
2616
- const check = new import_node_sqlite.DatabaseSync(output, { readOnly: true });
3556
+ (0, import_node_fs6.chmodSync)(output, 384);
3557
+ const check = new import_node_sqlite3.DatabaseSync(output, { readOnly: true });
2617
3558
  try {
2618
3559
  const result = check.prepare("PRAGMA integrity_check").get();
2619
3560
  if (!result || Object.values(result)[0] !== "ok") throw new SystemError("invalid-backup", `backup integrity check failed: ${output}`);
@@ -2623,8 +3564,8 @@ async function backupDatabase(source, output, host) {
2623
3564
  void host;
2624
3565
  }
2625
3566
  function databaseSchema(path) {
2626
- if (!(0, import_node_fs3.existsSync)(path)) return null;
2627
- const db = new import_node_sqlite.DatabaseSync(path, { readOnly: true, timeout: 5e3 });
3567
+ if (!(0, import_node_fs6.existsSync)(path)) return null;
3568
+ const db = new import_node_sqlite3.DatabaseSync(path, { readOnly: true, timeout: 5e3 });
2628
3569
  try {
2629
3570
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'").get();
2630
3571
  if (!table) return null;
@@ -2636,34 +3577,34 @@ function databaseSchema(path) {
2636
3577
  }
2637
3578
  function replaceDatabaseFromBackup(backupPath, target, safetyBackup, host) {
2638
3579
  assertPrivateSourceFile(backupPath, "database backup");
2639
- const backup = new import_node_sqlite.DatabaseSync(backupPath, { readOnly: true });
3580
+ const backup3 = new import_node_sqlite3.DatabaseSync(backupPath, { readOnly: true });
2640
3581
  try {
2641
- if (Object.values(backup.prepare("PRAGMA integrity_check").get())[0] !== "ok") throw new SystemError("invalid-backup", "rollback backup integrity failed");
3582
+ if (Object.values(backup3.prepare("PRAGMA integrity_check").get())[0] !== "ok") throw new SystemError("invalid-backup", "rollback backup integrity failed");
2642
3583
  } finally {
2643
- backup.close();
3584
+ backup3.close();
2644
3585
  }
2645
- ensureDirectory((0, import_node_path4.dirname)(target), 448, true);
2646
- if (safetyBackup !== null && (0, import_node_fs3.existsSync)(target)) {
3586
+ ensureDirectory((0, import_node_path6.dirname)(target), 448, true);
3587
+ if (safetyBackup !== null && (0, import_node_fs6.existsSync)(target)) {
2647
3588
  void host;
2648
3589
  }
2649
- const temporary = (0, import_node_path4.join)((0, import_node_path4.dirname)(target), `.restore-${process.pid}-${Date.now()}.sqlite`);
2650
- (0, import_node_fs3.copyFileSync)(backupPath, temporary);
2651
- (0, import_node_fs3.chmodSync)(temporary, 384);
3590
+ const temporary = (0, import_node_path6.join)((0, import_node_path6.dirname)(target), `.restore-${process.pid}-${Date.now()}.sqlite`);
3591
+ (0, import_node_fs6.copyFileSync)(backupPath, temporary);
3592
+ (0, import_node_fs6.chmodSync)(temporary, 384);
2652
3593
  for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
2653
3594
  try {
2654
- (0, import_node_fs3.unlinkSync)(sidecar);
3595
+ (0, import_node_fs6.unlinkSync)(sidecar);
2655
3596
  } catch {
2656
3597
  }
2657
3598
  }
2658
- (0, import_node_fs3.renameSync)(temporary, target);
2659
- (0, import_node_fs3.chmodSync)(target, 384);
3599
+ (0, import_node_fs6.renameSync)(temporary, target);
3600
+ (0, import_node_fs6.chmodSync)(target, 384);
2660
3601
  }
2661
3602
  function newestTimestamp() {
2662
3603
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2663
3604
  }
2664
3605
  function adoptUserState(options) {
2665
- const source = (0, import_node_path4.resolve)(options.source);
2666
- if (!(0, import_node_fs3.existsSync)(source)) throw new SystemError("invalid-adoption", `user installation does not exist: ${source}`);
3606
+ const source = (0, import_node_path6.resolve)(options.source);
3607
+ if (!(0, import_node_fs6.existsSync)(source)) throw new SystemError("invalid-adoption", `user installation does not exist: ${source}`);
2667
3608
  if (isWithin(options.paths.prefix, source) || isWithin(options.paths.stateDir, source) || isWithin(source, options.paths.stateDir)) {
2668
3609
  throw new SystemError("invalid-adoption", "adoption source and system paths must not overlap");
2669
3610
  }
@@ -2673,10 +3614,10 @@ function adoptUserState(options) {
2673
3614
  const { port, origin } = setup;
2674
3615
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
2675
3616
  if (options.role !== "collector") {
2676
- const sourceDatabase = (0, import_node_path4.join)(source, "hub", "telemetry.sqlite");
2677
- if ((0, import_node_fs3.existsSync)(sourceDatabase)) {
3617
+ const sourceDatabase = (0, import_node_path6.join)(source, "hub", "telemetry.sqlite");
3618
+ if ((0, import_node_fs6.existsSync)(sourceDatabase)) {
2678
3619
  assertPrivateSourceFile(sourceDatabase, "adopted database");
2679
- const database = new import_node_sqlite.DatabaseSync(sourceDatabase, { readOnly: true });
3620
+ const database = new import_node_sqlite3.DatabaseSync(sourceDatabase, { readOnly: true });
2680
3621
  try {
2681
3622
  const row = database.prepare("PRAGMA integrity_check").get();
2682
3623
  if (!row || Object.values(row)[0] !== "ok") throw new SystemError("invalid-adoption", "adopted database failed its integrity check");
@@ -2684,12 +3625,12 @@ function adoptUserState(options) {
2684
3625
  database.close();
2685
3626
  }
2686
3627
  }
2687
- const sourceEnrollment = (0, import_node_path4.join)(source, "hub", "enrollment.json");
2688
- if ((0, import_node_fs3.existsSync)(sourceEnrollment)) assertPrivateSourceFile(sourceEnrollment, "adopted enrollment");
3628
+ const sourceEnrollment = (0, import_node_path6.join)(source, "hub", "enrollment.json");
3629
+ if ((0, import_node_fs6.existsSync)(sourceEnrollment)) assertPrivateSourceFile(sourceEnrollment, "adopted enrollment");
2689
3630
  }
2690
3631
  if (options.role !== "hub") {
2691
- const sourceCollectorState = (0, import_node_path4.join)(source, "collector", "state.json");
2692
- if ((0, import_node_fs3.existsSync)(sourceCollectorState)) {
3632
+ const sourceCollectorState = (0, import_node_path6.join)(source, "collector", "state.json");
3633
+ if ((0, import_node_fs6.existsSync)(sourceCollectorState)) {
2693
3634
  assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
2694
3635
  readCollectorIdentity(sourceCollectorState);
2695
3636
  }
@@ -2698,7 +3639,7 @@ function adoptUserState(options) {
2698
3639
  const warnings = [];
2699
3640
  const targetDatabase = hubDatabasePath(options.paths);
2700
3641
  const targetCollector = collectorStatePath(options.paths);
2701
- if (options.apply && (0, import_node_fs3.existsSync)(targetDatabase)) {
3642
+ if (options.apply && (0, import_node_fs6.existsSync)(targetDatabase)) {
2702
3643
  throw new SystemError("destination-exists", `refusing to overwrite ${targetDatabase}`);
2703
3644
  }
2704
3645
  const hubEnv = options.role === "collector" ? null : [
@@ -2733,62 +3674,42 @@ function adoptUserState(options) {
2733
3674
  port
2734
3675
  };
2735
3676
  }
2736
- function copyAdoptedTree(source, target, copied, skipLocks = true) {
2737
- const visit = (current, destination) => {
2738
- const stat = (0, import_node_fs3.lstatSync)(current);
2739
- if (stat.isSymbolicLink()) return;
2740
- if (stat.isDirectory()) {
2741
- (0, import_node_fs3.mkdirSync)(destination, { recursive: true, mode: 448 });
2742
- for (const entry of (0, import_node_fs3.readdirSync)(current)) visit((0, import_node_path4.join)(current, entry), (0, import_node_path4.join)(destination, entry));
2743
- return;
2744
- }
2745
- if (!stat.isFile()) return;
2746
- if (skipLocks && /\.(lock|sock)$/.test(current)) return;
2747
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(destination), { recursive: true, mode: 448 });
2748
- (0, import_node_fs3.copyFileSync)(current, destination);
2749
- (0, import_node_fs3.chmodSync)(destination, 384);
2750
- copied.push({ source: current, target: destination, sha256: sha256File(destination), bytes: stat.size });
2751
- };
2752
- visit(source, target);
2753
- }
2754
3677
  async function performAdoptionCopyAsync(options) {
2755
3678
  const { source, paths, role, host, copy } = options;
2756
3679
  assertDirectoryStopped(source, role);
2757
3680
  const targetDatabase = hubDatabasePath(paths);
2758
- const sourceDatabase = (0, import_node_path4.join)(source, "hub", "telemetry.sqlite");
2759
- if (role !== "collector" && (0, import_node_fs3.existsSync)(sourceDatabase) && !(0, import_node_fs3.existsSync)(targetDatabase)) {
3681
+ const sourceDatabase = (0, import_node_path6.join)(source, "hub", "telemetry.sqlite");
3682
+ if (role !== "collector" && (0, import_node_fs6.existsSync)(sourceDatabase) && !(0, import_node_fs6.existsSync)(targetDatabase)) {
2760
3683
  assertPrivateSourceFile(sourceDatabase, "adopted database");
2761
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
3684
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
2762
3685
  await backupDatabase(sourceDatabase, targetDatabase, host);
2763
3686
  copy.record.files.push({
2764
3687
  source: sourceDatabase,
2765
3688
  target: targetDatabase,
2766
- sha256: sha256File(targetDatabase),
2767
- bytes: (0, import_node_fs3.statSync)(targetDatabase).size
3689
+ sha256: sha256File2(targetDatabase),
3690
+ bytes: (0, import_node_fs6.statSync)(targetDatabase).size
2768
3691
  });
2769
3692
  }
2770
- const sourceCollectorState = (0, import_node_path4.join)(source, "collector", "state.json");
2771
- if (role !== "hub" && (0, import_node_fs3.existsSync)(sourceCollectorState)) {
3693
+ const sourceCollectorState = (0, import_node_path6.join)(source, "collector", "state.json");
3694
+ if (role !== "hub" && (0, import_node_fs6.existsSync)(sourceCollectorState)) {
2772
3695
  assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
2773
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
3696
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
2774
3697
  const targetCollector = collectorStatePath(paths);
2775
- (0, import_node_fs3.copyFileSync)(sourceCollectorState, targetCollector);
2776
- (0, import_node_fs3.chmodSync)(targetCollector, 384);
3698
+ (0, import_node_fs6.copyFileSync)(sourceCollectorState, targetCollector);
3699
+ (0, import_node_fs6.chmodSync)(targetCollector, 384);
2777
3700
  copy.record.files.push({
2778
3701
  source: sourceCollectorState,
2779
3702
  target: targetCollector,
2780
- sha256: sha256File(targetCollector),
2781
- bytes: (0, import_node_fs3.statSync)(targetCollector).size
3703
+ sha256: sha256File2(targetCollector),
3704
+ bytes: (0, import_node_fs6.statSync)(targetCollector).size
2782
3705
  });
2783
- const outbox = (0, import_node_path4.join)(source, "collector", "outbox");
2784
- if ((0, import_node_fs3.existsSync)(outbox)) copyAdoptedTree(outbox, (0, import_node_path4.join)(paths.stateDir, "collector", "outbox"), copy.record.files);
2785
3706
  }
2786
- const enrollment = (0, import_node_path4.join)(source, "hub", "enrollment.json");
2787
- if (role !== "collector" && (0, import_node_fs3.existsSync)(enrollment)) {
3707
+ const enrollment = (0, import_node_path6.join)(source, "hub", "enrollment.json");
3708
+ if (role !== "collector" && (0, import_node_fs6.existsSync)(enrollment)) {
2788
3709
  const target = hubEnrollmentPath(paths);
2789
- (0, import_node_fs3.copyFileSync)(enrollment, target);
2790
- (0, import_node_fs3.chmodSync)(target, 384);
2791
- copy.record.files.push({ source: enrollment, target, sha256: sha256File(target), bytes: (0, import_node_fs3.statSync)(target).size });
3710
+ (0, import_node_fs6.copyFileSync)(enrollment, target);
3711
+ (0, import_node_fs6.chmodSync)(target, 384);
3712
+ copy.record.files.push({ source: enrollment, target, sha256: sha256File2(target), bytes: (0, import_node_fs6.statSync)(target).size });
2792
3713
  }
2793
3714
  }
2794
3715
  function writeSysusersFiles(paths, accounts, role, power, host) {
@@ -2828,15 +3749,15 @@ function writePowerRule(paths, accounts) {
2828
3749
  writeManagedFile(raplRulePath(paths), renderRaplRule(accounts), 420, true);
2829
3750
  }
2830
3751
  function powerOriginalPath(paths) {
2831
- return (0, import_node_path4.join)(paths.stateDir, POWER_STATE_FILE_NAME);
3752
+ return (0, import_node_path6.join)(paths.stateDir, POWER_STATE_FILE_NAME);
2832
3753
  }
2833
3754
  function capturePowerOriginals(paths) {
2834
3755
  const files = {};
2835
3756
  for (const domain of packageDomainPaths(paths.powercapRoot)) {
2836
3757
  for (const name of ["energy_uj", "max_energy_range_uj"]) {
2837
- const path = (0, import_node_path4.join)(domain, name);
3758
+ const path = (0, import_node_path6.join)(domain, name);
2838
3759
  try {
2839
- const stat = (0, import_node_fs3.statSync)(path);
3760
+ const stat = (0, import_node_fs6.statSync)(path);
2840
3761
  files[path] = { mode: stat.mode & 511, uid: stat.uid, gid: stat.gid };
2841
3762
  } catch {
2842
3763
  }
@@ -2848,8 +3769,8 @@ function capturePowerOriginals(paths) {
2848
3769
  function applyPowerAccess(paths, accounts, host) {
2849
3770
  for (const domain of packageDomainPaths(paths.powercapRoot)) {
2850
3771
  for (const name of ["energy_uj", "max_energy_range_uj"]) {
2851
- const path = (0, import_node_path4.join)(domain, name);
2852
- if (!(0, import_node_fs3.existsSync)(path)) continue;
3772
+ const path = (0, import_node_path6.join)(domain, name);
3773
+ if (!(0, import_node_fs6.existsSync)(path)) continue;
2853
3774
  runRequired(host, "chgrp", [accounts.powerGroup, path], `power counter group ${path}`);
2854
3775
  runRequired(host, "chmod", ["0440", path], `power counter mode ${path}`);
2855
3776
  }
@@ -2862,9 +3783,9 @@ function resumePowerRule(paths, host) {
2862
3783
  }
2863
3784
  function packageDomainPaths(root) {
2864
3785
  try {
2865
- return (0, import_node_fs3.readdirSync)(root).filter((entry) => /^intel-rapl:\d+$/.test(entry)).map((entry) => (0, import_node_path4.join)(root, entry)).filter((path) => {
3786
+ return (0, import_node_fs6.readdirSync)(root).filter((entry) => /^intel-rapl:\d+$/.test(entry)).map((entry) => (0, import_node_path6.join)(root, entry)).filter((path) => {
2866
3787
  try {
2867
- return /^package/.test((0, import_node_fs3.readFileSync)((0, import_node_path4.join)(path, "name"), "utf8").trim());
3788
+ return /^package/.test((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(path, "name"), "utf8").trim());
2868
3789
  } catch {
2869
3790
  return false;
2870
3791
  }
@@ -2884,8 +3805,8 @@ function restorePowerOriginals(paths, host) {
2884
3805
  const files = { ...originals?.files ?? {} };
2885
3806
  for (const domain of packageDomainPaths(paths.powercapRoot)) {
2886
3807
  for (const name of ["energy_uj", "max_energy_range_uj"]) {
2887
- const path = (0, import_node_path4.join)(domain, name);
2888
- if (!(0, import_node_fs3.existsSync)(path)) continue;
3808
+ const path = (0, import_node_path6.join)(domain, name);
3809
+ if (!(0, import_node_fs6.existsSync)(path)) continue;
2889
3810
  const original = files[path];
2890
3811
  if (original) {
2891
3812
  runRequired(host, "chown", [`${original.uid}:${original.gid}`, path], "restore counter ownership");
@@ -2901,7 +3822,7 @@ function restorePowerOriginals(paths, host) {
2901
3822
  }
2902
3823
  }
2903
3824
  try {
2904
- (0, import_node_fs3.unlinkSync)(powerOriginalPath(paths));
3825
+ (0, import_node_fs6.unlinkSync)(powerOriginalPath(paths));
2905
3826
  } catch {
2906
3827
  }
2907
3828
  return restored;
@@ -2925,7 +3846,7 @@ function stopUnits(host, units) {
2925
3846
  if (units.some((unit) => systemdActive(host, unit))) throw new SystemError("service-active", "system services did not stop; no files were restored");
2926
3847
  }
2927
3848
  function unitsForCleanup(paths, host) {
2928
- const legacy = LEGACY_UNIT_NAMES.filter((unit) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, unit)) || systemdActive(host, unit));
3849
+ const legacy = LEGACY_UNIT_NAMES.filter((unit) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit)) || systemdActive(host, unit));
2929
3850
  return [...Object.values(UNIT_NAMES), ...legacy];
2930
3851
  }
2931
3852
  function startUnits(host, units) {
@@ -2963,7 +3884,7 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
2963
3884
  const report = runRequired(
2964
3885
  host,
2965
3886
  state.nodePath,
2966
- [(0, import_node_path4.join)(paths.prefix, "current/dist/ops.cjs"), "inspect", "--database", hubDatabasePath(paths), "--json"],
3887
+ [(0, import_node_path6.join)(paths.prefix, "current/dist/ops.cjs"), "inspect", "--database", hubDatabasePath(paths), "--json"],
2967
3888
  "activated database verification",
2968
3889
  { timeoutMs: Math.max(100, deadline - Date.now()) }
2969
3890
  );
@@ -2979,8 +3900,8 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
2979
3900
  let problem = "no sampling marker";
2980
3901
  while (Date.now() < deadline) {
2981
3902
  try {
2982
- const marker = readJsonObject((0, import_node_path4.join)(paths.stateDir, "collector/health.json"));
2983
- const lease = readJsonObject((0, import_node_path4.join)(paths.stateDir, "collector/collector-runtime.lock"));
3903
+ const marker = readJsonObject((0, import_node_path6.join)(paths.stateDir, "collector/health.json"));
3904
+ const lease = readJsonObject((0, import_node_path6.join)(paths.stateDir, "collector/collector-runtime.lock"));
2984
3905
  if (marker.pid !== lease.pid || !Number.isSafeInteger(marker.pid)) throw new Error("sampling marker belongs to another process");
2985
3906
  process.kill(Number(marker.pid), 0);
2986
3907
  if (marker.version !== state.currentVersion || !Number.isFinite(marker.sampledAt) || Date.now() - Number(marker.sampledAt) > 15e3) throw new Error("collector version or sampling freshness is wrong");
@@ -2988,32 +3909,35 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
2988
3909
  } catch (error) {
2989
3910
  problem = error instanceof Error ? error.message : String(error);
2990
3911
  }
2991
- await new Promise((resolve4) => setTimeout(resolve4, 100));
3912
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
2992
3913
  }
2993
3914
  throw new SystemError("collector-unready", `system collector did not sample: ${problem}`);
2994
3915
  }
2995
3916
  }
2996
3917
  function chownAccount(paths, owner, group, host) {
2997
3918
  for (const path of paths) {
2998
- if (!(0, import_node_fs3.existsSync)(path)) continue;
3919
+ if (!(0, import_node_fs6.existsSync)(path)) continue;
2999
3920
  runRequired(host, "chown", ["-R", `${owner}:${group}`, path], `ownership ${path}`);
3000
3921
  }
3001
3922
  }
3002
3923
  function chmodTree(path, host) {
3003
- if (!(0, import_node_fs3.existsSync)(path)) return;
3924
+ if (!(0, import_node_fs6.existsSync)(path)) return;
3004
3925
  runRequired(host, "chmod", ["-R", "u=rwX,go=rX", path], `release permissions ${path}`);
3005
3926
  }
3006
3927
  function pickRollbackBackup(state, previousSchema) {
3007
3928
  const backups = Object.values(state.backups).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
3008
3929
  if (previousSchema === null) return null;
3009
- return [...backups].reverse().find((backup) => backup.fromVersion === state.previousVersion && backup.toVersion === state.currentVersion && backup.schemaVersion !== null && backup.schemaVersion <= previousSchema) ?? null;
3930
+ return [...backups].reverse().find((backup3) => backup3.fromVersion === state.previousVersion && backup3.toVersion === state.currentVersion && backup3.schemaVersion !== null && backup3.schemaVersion <= previousSchema) ?? null;
3010
3931
  }
3011
3932
  async function recoverFailedUpgrade(options) {
3012
3933
  stopUnits(options.host, options.units);
3013
3934
  assertDirectoryStopped(options.paths.stateDir);
3014
- replaceSymlink((0, import_node_path4.join)(options.paths.prefix, "current"), options.oldCurrent, options.host);
3015
- if (options.oldPrevious === null) removeSymlink((0, import_node_path4.join)(options.paths.prefix, "previous"));
3016
- else replaceSymlink((0, import_node_path4.join)(options.paths.prefix, "previous"), options.oldPrevious, options.host);
3935
+ for (const message of undoMachineDataCleanup(systemMachineDataPaths(options.paths), options.machineData?.stagedQueues ?? null)) {
3936
+ console.warn(`rigscope system upgrade recovery: ${message}`);
3937
+ }
3938
+ replaceSymlink((0, import_node_path6.join)(options.paths.prefix, "current"), options.oldCurrent, options.host);
3939
+ if (options.oldPrevious === null) removeSymlink((0, import_node_path6.join)(options.paths.prefix, "previous"));
3940
+ else replaceSymlink((0, import_node_path6.join)(options.paths.prefix, "previous"), options.oldPrevious, options.host);
3017
3941
  const database = hubDatabasePath(options.paths);
3018
3942
  let afterSchema = null;
3019
3943
  try {
@@ -3021,13 +3945,13 @@ async function recoverFailedUpgrade(options) {
3021
3945
  } catch {
3022
3946
  afterSchema = null;
3023
3947
  }
3024
- if (options.backupPath !== null && (0, import_node_fs3.existsSync)(options.backupPath) && (afterSchema === null || options.beforeSchema === null || afterSchema > options.beforeSchema)) {
3025
- await backupDatabase(database, (0, import_node_path4.join)(options.paths.stateDir, "backups", `failed-upgrade-${newestTimestamp()}.sqlite`), options.host);
3948
+ if (options.backupPath !== null && (0, import_node_fs6.existsSync)(options.backupPath) && (afterSchema === null || options.beforeSchema === null || afterSchema > options.beforeSchema || options.machineData?.databaseCleared === true)) {
3949
+ await backupDatabase(database, (0, import_node_path6.join)(options.paths.stateDir, "backups", `failed-upgrade-${newestTimestamp()}.sqlite`), options.host);
3026
3950
  replaceDatabaseFromBackup(options.backupPath, database, null, options.host);
3027
3951
  runRequired(options.host, "chown", [`${options.state.accounts.hub}:${options.state.accounts.hub}`, database], "restored database ownership");
3028
3952
  }
3029
- options.state.previousVersion = options.oldPrevious === null ? null : (0, import_node_path4.basename)(options.oldPrevious);
3030
- options.state.currentVersion = (0, import_node_path4.basename)(options.oldCurrent);
3953
+ options.state.previousVersion = options.oldPrevious === null ? null : (0, import_node_path6.basename)(options.oldPrevious);
3954
+ options.state.currentVersion = (0, import_node_path6.basename)(options.oldCurrent);
3031
3955
  writeSystemState(options.paths, options.state);
3032
3956
  if (options.activeComponents.length > 0) {
3033
3957
  startUnits(options.host, options.units);
@@ -3036,14 +3960,14 @@ async function recoverFailedUpgrade(options) {
3036
3960
  }
3037
3961
  function assertManagedFileOwnership(paths, path) {
3038
3962
  if (!isDefaultSystemLayout(paths)) return;
3039
- if (!(0, import_node_fs3.existsSync)(path)) return;
3040
- const stat = (0, import_node_fs3.lstatSync)(path);
3963
+ if (!(0, import_node_fs6.existsSync)(path)) return;
3964
+ const stat = (0, import_node_fs6.lstatSync)(path);
3041
3965
  if (stat.isSymbolicLink() || !stat.isFile()) throw new SystemError("unsafe-path", `${path} must be a regular managed file`);
3042
3966
  if (stat.uid !== 0) throw new SystemError("insecure-ownership", `${path} must be root-owned before replacement`);
3043
3967
  if ((stat.mode & 18) !== 0) throw new SystemError("insecure-permissions", `${path} must not be group/world-writable`);
3044
3968
  }
3045
3969
  function writeUnitFile(paths, unit, content, replace) {
3046
- const path = (0, import_node_path4.join)(paths.unitsDir, unit);
3970
+ const path = (0, import_node_path6.join)(paths.unitsDir, unit);
3047
3971
  assertManagedFileOwnership(paths, path);
3048
3972
  writeManagedFile(path, content, 420, replace);
3049
3973
  }
@@ -3058,19 +3982,19 @@ function ensureComponentSetup(options) {
3058
3982
  ensureDirectory(paths.configDir, 493);
3059
3983
  ensureDirectory(paths.stateDir, 493);
3060
3984
  ensureDirectory(paths.unitsDir, 493);
3061
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
3062
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub", "run"), 448, true);
3063
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
3064
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
3985
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
3986
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub", "run"), 448, true);
3987
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
3988
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
3065
3989
  const release = currentReleaseOrThrow(paths);
3066
3990
  void release;
3067
3991
  writeSysusersFiles(paths, accounts, "local", power, host);
3068
3992
  if (component === "hub") {
3069
- if (!(0, import_node_fs3.existsSync)(hubEnvPath(paths))) {
3993
+ if (!(0, import_node_fs6.existsSync)(hubEnvPath(paths))) {
3070
3994
  const env = hubEnvTemplate(paths);
3071
3995
  writeManagedFile(hubEnvPath(paths), env, 384, false);
3072
3996
  }
3073
- if (!(0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, UNIT_NAMES.hub))) {
3997
+ if (!(0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, UNIT_NAMES.hub))) {
3074
3998
  const env = readHubEnvironment(paths);
3075
3999
  const remote = env.HUB_PUBLIC_ORIGIN !== void 0 && env.HUB_PUBLIC_ORIGIN !== "";
3076
4000
  const unit = renderSystemHubUnit({
@@ -3087,7 +4011,7 @@ function ensureComponentSetup(options) {
3087
4011
  `, false);
3088
4012
  }
3089
4013
  } else {
3090
- if (!(0, import_node_fs3.existsSync)(collectorEnvPath(paths))) {
4014
+ if (!(0, import_node_fs6.existsSync)(collectorEnvPath(paths))) {
3091
4015
  writeManagedFile(collectorEnvPath(paths), collectorEnvTemplate(), 384, false);
3092
4016
  }
3093
4017
  const unit = renderSystemCollectorUnit({
@@ -3103,17 +4027,17 @@ function ensureComponentSetup(options) {
3103
4027
  const validation = runRequired(
3104
4028
  host,
3105
4029
  "systemd-analyze",
3106
- ["verify", "--man=no", (0, import_node_path4.join)(paths.unitsDir, unitFor(component))],
4030
+ ["verify", "--man=no", (0, import_node_path6.join)(paths.unitsDir, unitFor(component))],
3107
4031
  "native systemd unit validation"
3108
4032
  );
3109
4033
  if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
3110
4034
  throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
3111
4035
  }
3112
- if (component === "hub") chownAccount([(0, import_node_path4.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
3113
- if (component === "collector") chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
4036
+ if (component === "hub") chownAccount([(0, import_node_path6.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
4037
+ if (component === "collector") chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
3114
4038
  }
3115
4039
  function ensurePowerAccess(paths, accounts, host) {
3116
- if (!(0, import_node_fs3.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
4040
+ if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
3117
4041
  writePowerRule(paths, accounts);
3118
4042
  resumePowerRule(paths, host);
3119
4043
  applyPowerAccess(paths, accounts, host);
@@ -3131,7 +4055,7 @@ function resetUnfinishedSystemState(options) {
3131
4055
  }
3132
4056
  function installPreset(options, role) {
3133
4057
  if (options.adoptFrom !== void 0) {
3134
- return componentSet(readAdoptionSetup((0, import_node_path4.resolve)(options.adoptFrom)).components);
4058
+ return componentSet(readAdoptionSetup((0, import_node_path6.resolve)(options.adoptFrom)).components);
3135
4059
  }
3136
4060
  return componentsForRole(role);
3137
4061
  }
@@ -3143,11 +4067,11 @@ function planSystemInstall(options) {
3143
4067
  const power = options.power !== false && preset.collector;
3144
4068
  const initial = options.enable === true ? preset : { hub: false, collector: false };
3145
4069
  const files = [
3146
- { path: (0, import_node_path4.join)(paths.prefix, "current"), mode: "symlink", purpose: "active root-owned release" },
4070
+ { path: (0, import_node_path6.join)(paths.prefix, "current"), mode: "symlink", purpose: "active root-owned release" },
3147
4071
  { path: hubEnvPath(paths), mode: "0600", purpose: "root-owned hub configuration" },
3148
4072
  { path: collectorEnvPath(paths), mode: "0600", purpose: "root-owned collector configuration" },
3149
4073
  { path: sysusersPath(paths), mode: "0644", purpose: "dedicated nologin accounts" },
3150
- ...COMPONENTS.map((component) => ({ path: (0, import_node_path4.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }))
4074
+ ...COMPONENTS.map((component) => ({ path: (0, import_node_path6.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }))
3151
4075
  ];
3152
4076
  if (power) {
3153
4077
  files.push({ path: raplRulePath(paths), mode: "0644", purpose: "package RAPL read rule" });
@@ -3162,7 +4086,7 @@ function planSystemInstall(options) {
3162
4086
  ];
3163
4087
  const notes = [
3164
4088
  "Nothing is changed without --apply and root.",
3165
- options.adoptFrom ? `Adopts ${(0, import_node_path4.resolve)(options.adoptFrom)} by copy only; the original user setup is preserved.` : "Existing user services are not modified.",
4089
+ options.adoptFrom ? `Adopts ${(0, import_node_path6.resolve)(options.adoptFrom)} by copy only; the original user setup is preserved.` : "Existing user services are not modified.",
3166
4090
  "One complete package: both runtimes, units and accounts are installed; --role only sets the initial enabled preset.",
3167
4091
  "Hub and collector use separate unprivileged accounts; Node gets no capabilities.",
3168
4092
  ...options.enable && preset.hub ? ["Enabling the hub creates its owner account once and prints the password once."] : [],
@@ -3195,8 +4119,8 @@ async function installSystemUnlocked(options) {
3195
4119
  `existing user service(s) detected (${detail}); stop them and use --adopt-user-state for explicit copy-based adoption`
3196
4120
  );
3197
4121
  }
3198
- const source = (0, import_node_path4.resolve)(options.source);
3199
- if (!(0, import_node_fs3.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
4122
+ const source = (0, import_node_path6.resolve)(options.source);
4123
+ if (!(0, import_node_fs6.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
3200
4124
  const port = options.port ?? 8081;
3201
4125
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-argument", "--port must be 1-65535");
3202
4126
  parseHubConfig(options.origin === void 0 ? { HUB_HOST: "127.0.0.1", HUB_PORT: String(port) } : { HUB_PUBLIC_ORIGIN: options.origin, HUB_SOCKET_PATH: hubSocketPath(paths) });
@@ -3217,14 +4141,14 @@ async function installSystemUnlocked(options) {
3217
4141
  });
3218
4142
  try {
3219
4143
  if (legacy) resetUnfinishedSystemState({ paths, host, now });
3220
- ensureDirectory((0, import_node_path4.join)(paths.prefix, "releases"), 493);
4144
+ ensureDirectory((0, import_node_path6.join)(paths.prefix, "releases"), 493);
3221
4145
  ensureDirectory(paths.stateDir, 493);
3222
4146
  ensureDirectory(paths.configDir, 493);
3223
4147
  ensureDirectory(paths.unitsDir, 493);
3224
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
3225
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
3226
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
3227
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub", "run"), 448, true);
4148
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
4149
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
4150
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
4151
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub", "run"), 448, true);
3228
4152
  const release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
3229
4153
  let adoption = null;
3230
4154
  if (options.adoptFrom) {
@@ -3257,13 +4181,13 @@ async function installSystemUnlocked(options) {
3257
4181
  `, true);
3258
4182
  writeUnitFile(paths, UNIT_NAMES.collector, `${collectorUnit}
3259
4183
  `, true);
3260
- const validation = runRequired(host, "systemd-analyze", ["verify", "--man=no", ...COMPONENTS.map((component) => (0, import_node_path4.join)(paths.unitsDir, unitFor(component)))], "native systemd unit validation");
4184
+ const validation = runRequired(host, "systemd-analyze", ["verify", "--man=no", ...COMPONENTS.map((component) => (0, import_node_path6.join)(paths.unitsDir, unitFor(component)))], "native systemd unit validation");
3261
4185
  if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
3262
4186
  throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
3263
4187
  }
3264
4188
  if (power) ensurePowerAccess(paths, accounts, host);
3265
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
3266
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
4189
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
4190
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], accounts.collector, accounts.collector, host);
3267
4191
  const state = emptyState({ paths, accounts, role, components: { hub: false, collector: false }, nodePath, now });
3268
4192
  state.currentVersion = release.version;
3269
4193
  state.releases[release.version] = {
@@ -3273,7 +4197,7 @@ async function installSystemUnlocked(options) {
3273
4197
  };
3274
4198
  state.adoption = adoption?.record ?? null;
3275
4199
  state.power = power ? { enabled: true, rulePath: raplRulePath(paths), group: accounts.powerGroup } : null;
3276
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), release.releaseDir, host);
4200
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), release.releaseDir, host);
3277
4201
  writeSystemState(paths, state);
3278
4202
  const wantsHub = preset.hub && (options.enable === true || options.start === true);
3279
4203
  const wantsCollector = preset.collector && (options.enable === true || options.start === true);
@@ -3299,6 +4223,7 @@ async function installSystemUnlocked(options) {
3299
4223
  await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, started);
3300
4224
  }
3301
4225
  }
4226
+ recordMachineDataTransition(paths.stateDir, release.version, (path, content) => atomicWrite(path, content, 384));
3302
4227
  return {
3303
4228
  operation: "install",
3304
4229
  ok: true,
@@ -3327,14 +4252,14 @@ function systemStatus(options = {}) {
3327
4252
  } catch (error) {
3328
4253
  stateError = error instanceof Error ? error.message : String(error);
3329
4254
  }
3330
- const presentUnits = [UNIT_NAMES.hub, UNIT_NAMES.collector].filter((unit) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, unit)));
4255
+ const presentUnits = [UNIT_NAMES.hub, UNIT_NAMES.collector].filter((unit) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit)));
3331
4256
  const publicRole = presentUnits.length === 2 ? "local" : presentUnits[0] === UNIT_NAMES.hub ? "hub" : presentUnits[0] === UNIT_NAMES.collector ? "collector" : null;
3332
4257
  const role = state?.role ?? publicRole ?? "local";
3333
4258
  const current = currentReleaseDir(paths);
3334
4259
  let publicVersion = null;
3335
4260
  if (current) {
3336
4261
  try {
3337
- const manifest = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path4.join)(current, "package.json"), "utf8"));
4262
+ const manifest = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(current, "package.json"), "utf8"));
3338
4263
  if (typeof manifest.version === "string") publicVersion = manifest.version;
3339
4264
  } catch {
3340
4265
  }
@@ -3345,7 +4270,7 @@ function systemStatus(options = {}) {
3345
4270
  const componentReports = {};
3346
4271
  for (const component of COMPONENTS) {
3347
4272
  const unit = unitFor(component);
3348
- const present = (0, import_node_fs3.existsSync)((0, import_node_path4.join)(paths.unitsDir, unit));
4273
+ const present = (0, import_node_fs6.existsSync)((0, import_node_path6.join)(paths.unitsDir, unit));
3349
4274
  const active = present && systemdActive(host, unit);
3350
4275
  const unitEnabled = present && systemdEnabled(host, unit);
3351
4276
  const enabled = state !== null ? state.components[component].enabled : unitEnabled;
@@ -3354,7 +4279,7 @@ function systemStatus(options = {}) {
3354
4279
  componentReports[component] = {
3355
4280
  component,
3356
4281
  unit,
3357
- path: (0, import_node_path4.join)(paths.unitsDir, unit),
4282
+ path: (0, import_node_path6.join)(paths.unitsDir, unit),
3358
4283
  present,
3359
4284
  enabled,
3360
4285
  unitEnabled,
@@ -3398,16 +4323,16 @@ function systemStatus(options = {}) {
3398
4323
  })),
3399
4324
  database: {
3400
4325
  path: database,
3401
- exists: (0, import_node_fs3.existsSync)(database),
4326
+ exists: (0, import_node_fs6.existsSync)(database),
3402
4327
  schema,
3403
- mode: (0, import_node_fs3.existsSync)(database) ? modeString((0, import_node_fs3.statSync)(database).mode) : null,
4328
+ mode: (0, import_node_fs6.existsSync)(database) ? modeString((0, import_node_fs6.statSync)(database).mode) : null,
3404
4329
  lock
3405
4330
  },
3406
- socket: { path: hubSocketPath(paths), exists: (0, import_node_fs3.existsSync)(hubSocketPath(paths)) },
4331
+ socket: { path: hubSocketPath(paths), exists: (0, import_node_fs6.existsSync)(hubSocketPath(paths)) },
3407
4332
  config: {
3408
4333
  dir: paths.configDir,
3409
- hubEnv: { path: hubEnvPath(paths), present: (0, import_node_fs3.existsSync)(hubEnvPath(paths)) },
3410
- collectorEnv: { path: collectorEnvPath(paths), present: (0, import_node_fs3.existsSync)(collectorEnvPath(paths)) }
4334
+ hubEnv: { path: hubEnvPath(paths), present: (0, import_node_fs6.existsSync)(hubEnvPath(paths)) },
4335
+ collectorEnv: { path: collectorEnvPath(paths), present: (0, import_node_fs6.existsSync)(collectorEnvPath(paths)) }
3411
4336
  },
3412
4337
  adoption: state?.adoption ?? null,
3413
4338
  power,
@@ -3576,7 +4501,7 @@ async function enableSystem(options) {
3576
4501
  ],
3577
4502
  files: [
3578
4503
  { path: component === "hub" ? hubEnvPath(paths) : collectorEnvPath(paths), mode: "0600", purpose: `${component} configuration` },
3579
- { path: (0, import_node_path4.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }
4504
+ { path: (0, import_node_path6.join)(paths.unitsDir, unitFor(component)), mode: "0644", purpose: `${component} boot unit` }
3580
4505
  ],
3581
4506
  notes: [
3582
4507
  "Enables only the requested component; the other component keeps running.",
@@ -3644,7 +4569,7 @@ function disableSystem(options) {
3644
4569
  accounts: state.accounts,
3645
4570
  commands: [`systemctl stop ${unitFor(component)}`, `systemctl disable ${unitFor(component)}`],
3646
4571
  files: [],
3647
- notes: ["Settings, credentials, history and outboxes are retained while the component is disabled."]
4572
+ notes: ["Settings, credentials and stored history are retained while the component is disabled; unacknowledged samples held in the collector's RAM delivery queue drain with the process."]
3648
4573
  };
3649
4574
  if (options.apply !== true) return { operation: "disable", ok: true, apply: false, message: "plan only", plan };
3650
4575
  assertApply(host);
@@ -3666,7 +4591,7 @@ function disableSystem(options) {
3666
4591
  function readPrivateToken(path, read) {
3667
4592
  let stat;
3668
4593
  try {
3669
- stat = (0, import_node_fs3.lstatSync)(path);
4594
+ stat = (0, import_node_fs6.lstatSync)(path);
3670
4595
  } catch (error) {
3671
4596
  if (error.code === "ENOENT") {
3672
4597
  throw new SystemError("missing-grant", `grant file does not exist: ${path}`);
@@ -3677,7 +4602,7 @@ function readPrivateToken(path, read) {
3677
4602
  if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `grant file must be private (0600): ${path}`);
3678
4603
  if (stat.size < 1 || stat.size > 4096) throw new SystemError("invalid-token", `grant file must contain 1-4096 bytes: ${path}`);
3679
4604
  if (!read) return void 0;
3680
- const token = (0, import_node_fs3.readFileSync)(path, "utf8").trim();
4605
+ const token = (0, import_node_fs6.readFileSync)(path, "utf8").trim();
3681
4606
  if (token === "" || /[\r\n\0]/.test(token) || !/^[\x21-\x7e]{1,256}$/.test(token)) {
3682
4607
  throw new SystemError("invalid-token", "grant file contains an invalid token");
3683
4608
  }
@@ -3689,7 +4614,7 @@ async function connectSystem(options) {
3689
4614
  const state = readSystemState(paths);
3690
4615
  if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
3691
4616
  const destination = connectDestinationUrl(options.url);
3692
- const tokenFile = (0, import_node_path4.resolve)(options.tokenFile);
4617
+ const tokenFile = (0, import_node_path6.resolve)(options.tokenFile);
3693
4618
  const configured = collectorConfigured(paths);
3694
4619
  if (configured.configured) {
3695
4620
  throw new SystemError(
@@ -3737,12 +4662,12 @@ async function connectSystem(options) {
3737
4662
  `collector enrollment failed: ${error instanceof Error ? error.message : String(error)}`
3738
4663
  );
3739
4664
  } finally {
3740
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
4665
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
3741
4666
  }
3742
4667
  if (!collectorConfigured(paths).configured) {
3743
4668
  throw new SystemError("enrollment-failed", "the hub accepted enrollment but no destination was saved; check private state");
3744
4669
  }
3745
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
4670
+ chownAccount([(0, import_node_path6.join)(paths.stateDir, "collector")], state.accounts.collector, state.accounts.collector, host);
3746
4671
  writeSystemState(paths, state);
3747
4672
  const enabled = state.components.collector.enabled;
3748
4673
  if (enabled || wasActive) {
@@ -3759,13 +4684,16 @@ async function connectSystem(options) {
3759
4684
  state
3760
4685
  };
3761
4686
  }
4687
+ function systemMachineDataPaths(paths) {
4688
+ return { database: hubDatabasePath(paths), queueTree: (0, import_node_path6.join)(paths.stateDir, "collector", "outbox") };
4689
+ }
3762
4690
  async function upgradeSystemUnlocked(options) {
3763
4691
  const host = options.host ?? createSystemHost();
3764
4692
  const now = options.now ?? Date.now;
3765
4693
  const paths = validateSystemPaths(options.paths);
3766
4694
  if (systemStateIsLegacy(paths)) {
3767
- const source2 = (0, import_node_path4.resolve)(options.source);
3768
- if (!(0, import_node_fs3.existsSync)(source2)) throw new SystemError("missing-source", `release source does not exist: ${source2}`);
4695
+ const source2 = (0, import_node_path6.resolve)(options.source);
4696
+ if (!(0, import_node_fs6.existsSync)(source2)) throw new SystemError("missing-source", `release source does not exist: ${source2}`);
3769
4697
  const role = legacySystemRole(paths);
3770
4698
  const preset = componentsForRole(role);
3771
4699
  const enable = options.enable ?? (options.start === true || unitNamesForComponents(preset).some((unit) => systemdEnabled(host, unit)));
@@ -3788,7 +4716,7 @@ async function upgradeSystemUnlocked(options) {
3788
4716
  ],
3789
4717
  files: [
3790
4718
  { path: stateFilePath(paths), mode: "0600", purpose: "canonical replacement state" },
3791
- { path: (0, import_node_path4.join)(paths.stateDir, "backups"), mode: "0700", purpose: "removed with unfinished history" }
4719
+ { path: (0, import_node_path6.join)(paths.stateDir, "backups"), mode: "0700", purpose: "removed with unfinished history" }
3792
4720
  ],
3793
4721
  notes: [
3794
4722
  "An unfinished installation is reset, not migrated; its history and credentials are removed.",
@@ -3821,6 +4749,7 @@ async function upgradeSystemUnlocked(options) {
3821
4749
  if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
3822
4750
  const activeComponents = COMPONENTS.filter((component) => systemdActive(host, unitFor(component)));
3823
4751
  const activeUnits = activeComponents.map(unitFor);
4752
+ const transitionPending = readMachineDataTransition(paths.stateDir) === null;
3824
4753
  const plan = {
3825
4754
  operation: "upgrade",
3826
4755
  apply: options.apply === true,
@@ -3828,19 +4757,34 @@ async function upgradeSystemUnlocked(options) {
3828
4757
  components: { hub: state.components.hub.enabled, collector: state.components.collector.enabled },
3829
4758
  paths,
3830
4759
  accounts: state.accounts,
3831
- commands: ["stage new release", ...activeUnits.map((unit) => `systemctl stop ${unit}`), "backup database", "switch current", ...activeUnits.map((unit) => `systemctl start ${unit}`)],
3832
- files: [{ path: (0, import_node_path4.join)(paths.stateDir, "backups"), mode: "0700", purpose: "pre-upgrade database backup" }],
4760
+ commands: [
4761
+ "stage new release",
4762
+ ...activeUnits.map((unit) => `systemctl stop ${unit}`),
4763
+ "backup database",
4764
+ ...transitionPending ? ["discard legacy machine data and collector queues once (irreversible after success)"] : [],
4765
+ "switch current",
4766
+ ...activeUnits.map((unit) => `systemctl start ${unit}`)
4767
+ ],
4768
+ files: [
4769
+ { path: (0, import_node_path6.join)(paths.stateDir, "backups"), mode: "0700", purpose: "pre-upgrade database backup" },
4770
+ ...transitionPending ? [{ path: (0, import_node_path6.join)(paths.stateDir, "machine-data-transition.json"), mode: "0600", purpose: "written once after the transition completes" }] : []
4771
+ ],
3833
4772
  notes: [
3834
4773
  "Staging happens before any service is stopped.",
3835
4774
  "The exact active component set is restored; --start does not start a component that was stopped.",
3836
- "A failed post-upgrade start restores the previous release and matching database backup."
4775
+ "A failed post-upgrade start restores the previous release and matching database backup.",
4776
+ ...transitionPending ? [
4777
+ "This upgrade completes the one-time RAM-first machine-data transition: telemetry rows, summaries, receipts, gaps, latest snapshots, inventory and machine-derived alert history are discarded, plus the pre-RAM-first durable collector queue. The transition's database copy restores the rows and the staged queue is renamed back if activation fails.",
4778
+ `The transition always keeps ${machineDataKeptNote()}.`,
4779
+ "Later upgrades keep the marker and preserve durable history and queues."
4780
+ ] : ["The one-time machine-data transition already completed; durable history and collector queues are preserved."]
3837
4781
  ]
3838
4782
  };
3839
4783
  if (options.apply !== true) return { operation: "upgrade", ok: true, apply: false, message: "plan only", plan };
3840
4784
  assertApply(host);
3841
4785
  assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
3842
- const source = (0, import_node_path4.resolve)(options.source);
3843
- if (!(0, import_node_fs3.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
4786
+ const source = (0, import_node_path6.resolve)(options.source);
4787
+ if (!(0, import_node_fs6.existsSync)(source)) throw new SystemError("missing-source", `release source does not exist: ${source}`);
3844
4788
  const staged = prepareStagedRelease({
3845
4789
  source,
3846
4790
  paths,
@@ -3859,21 +4803,22 @@ async function upgradeSystemUnlocked(options) {
3859
4803
  let backupPath = null;
3860
4804
  let beforeSchema = null;
3861
4805
  let release = null;
4806
+ let machineData = null;
3862
4807
  try {
3863
4808
  stopUnits(host, activeUnits);
3864
4809
  assertDirectoryStopped(paths.stateDir);
3865
4810
  const lock = systemLockState(paths);
3866
4811
  if (lock.live) throw new SystemError("duplicate-runtime", `hub did not stop cleanly; lock remains at ${lock.path}`);
3867
4812
  const database = hubDatabasePath(paths);
3868
- if ((0, import_node_fs3.existsSync)(database)) {
3869
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
4813
+ if ((0, import_node_fs6.existsSync)(database)) {
4814
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
3870
4815
  beforeSchema = databaseSchema(database);
3871
- backupPath = (0, import_node_path4.join)(paths.stateDir, "backups", `pre-upgrade-${staged.version}-${newestTimestamp()}.sqlite`);
4816
+ backupPath = (0, import_node_path6.join)(paths.stateDir, "backups", `pre-upgrade-${staged.version}-${newestTimestamp()}.sqlite`);
3872
4817
  await backupDatabase(database, backupPath, host);
3873
4818
  }
3874
4819
  release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
3875
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), oldCurrent, host);
3876
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), release.releaseDir, host);
4820
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "previous"), oldCurrent, host);
4821
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), release.releaseDir, host);
3877
4822
  state.previousVersion = state.currentVersion;
3878
4823
  state.currentVersion = release.version;
3879
4824
  state.releases[release.version] = {
@@ -3882,7 +4827,7 @@ async function upgradeSystemUnlocked(options) {
3882
4827
  installedAt: new Date(now()).toISOString()
3883
4828
  };
3884
4829
  if (backupPath !== null) {
3885
- state.backups[(0, import_node_path4.basename)(backupPath)] = {
4830
+ state.backups[(0, import_node_path6.basename)(backupPath)] = {
3886
4831
  path: backupPath,
3887
4832
  schemaVersion: beforeSchema,
3888
4833
  fromSchema: beforeSchema,
@@ -3893,6 +4838,7 @@ async function upgradeSystemUnlocked(options) {
3893
4838
  };
3894
4839
  }
3895
4840
  writeSystemState(paths, state);
4841
+ machineData = performMachineDataCleanup(paths.stateDir, systemMachineDataPaths(paths), now);
3896
4842
  if (activeComponents.length > 0) {
3897
4843
  startUnits(host, activeUnits);
3898
4844
  await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, activeComponents);
@@ -3905,6 +4851,7 @@ async function upgradeSystemUnlocked(options) {
3905
4851
  oldPrevious,
3906
4852
  beforeSchema,
3907
4853
  backupPath,
4854
+ machineData,
3908
4855
  host,
3909
4856
  activeComponents,
3910
4857
  units: activeUnits
@@ -3912,6 +4859,10 @@ async function upgradeSystemUnlocked(options) {
3912
4859
  throw error;
3913
4860
  }
3914
4861
  const upgraded = release;
4862
+ if (machineData !== null) {
4863
+ completeMachineDataCleanup(paths.stateDir, machineData, upgraded.version, (path, content) => atomicWrite(path, content, 384));
4864
+ }
4865
+ const machineHistory = machineData?.report;
3915
4866
  return {
3916
4867
  operation: "upgrade",
3917
4868
  ok: true,
@@ -3921,6 +4872,7 @@ async function upgradeSystemUnlocked(options) {
3921
4872
  current: upgraded.releaseDir,
3922
4873
  previous: oldCurrent,
3923
4874
  backupPath,
4875
+ ...machineHistory ? { machineHistory } : {},
3924
4876
  state
3925
4877
  };
3926
4878
  } finally {
@@ -3928,7 +4880,7 @@ async function upgradeSystemUnlocked(options) {
3928
4880
  }
3929
4881
  }
3930
4882
  function systemOperationLockPath(paths) {
3931
- return (0, import_node_path4.join)(paths.prefix, "run", "operation.lock");
4883
+ return (0, import_node_path6.join)(paths.prefix, "run", "operation.lock");
3932
4884
  }
3933
4885
  async function withSystemOperation(options, operation) {
3934
4886
  if (!options.apply) return operation();
@@ -3936,7 +4888,7 @@ async function withSystemOperation(options, operation) {
3936
4888
  assertApply(host);
3937
4889
  const paths = validateSystemPaths(options.paths);
3938
4890
  ensureDirectory(paths.prefix, 493);
3939
- ensureDirectory((0, import_node_path4.join)(paths.prefix, "run"), 448, true);
4891
+ ensureDirectory((0, import_node_path6.join)(paths.prefix, "run"), 448, true);
3940
4892
  const release = acquireProcessLease(systemOperationLockPath(paths));
3941
4893
  try {
3942
4894
  return await operation();
@@ -3988,24 +4940,24 @@ async function rollbackSystemUnlocked(options = {}) {
3988
4940
  try {
3989
4941
  stopUnits(host, units);
3990
4942
  assertDirectoryStopped(paths.stateDir);
3991
- if (systemLockState(paths).live || lockIsLive((0, import_node_path4.join)(paths.stateDir, "collector/collector-runtime.lock")).live) {
4943
+ if (systemLockState(paths).live || lockIsLive((0, import_node_path6.join)(paths.stateDir, "collector/collector-runtime.lock")).live) {
3992
4944
  throw new SystemError("runtime-active", "runtime lease remains after stop; rollback refused");
3993
4945
  }
3994
4946
  const database = hubDatabasePath(paths);
3995
- const currentSchema = (0, import_node_fs3.existsSync)(database) ? databaseSchema(database) : null;
4947
+ const currentSchema = (0, import_node_fs6.existsSync)(database) ? databaseSchema(database) : null;
3996
4948
  let backupToRestore = null;
3997
4949
  if (currentSchema !== null && (previousSchema === null || currentSchema > previousSchema)) {
3998
4950
  backupToRestore = pickRollbackBackup(state, previousSchema);
3999
- if (backupToRestore === null || !(0, import_node_fs3.existsSync)(backupToRestore.path)) {
4951
+ if (backupToRestore === null || !(0, import_node_fs6.existsSync)(backupToRestore.path)) {
4000
4952
  throw new SystemError(
4001
4953
  "incompatible-schema",
4002
4954
  `current database schema ${currentSchema} is newer than release ${previousVersion}; no matching pre-upgrade backup is recorded`
4003
4955
  );
4004
4956
  }
4005
4957
  }
4006
- if ((0, import_node_fs3.existsSync)(database)) {
4007
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
4008
- safetyBackup = (0, import_node_path4.join)(paths.stateDir, "backups", `pre-rollback-${newestTimestamp()}.sqlite`);
4958
+ if ((0, import_node_fs6.existsSync)(database)) {
4959
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "backups"), 448, true);
4960
+ safetyBackup = (0, import_node_path6.join)(paths.stateDir, "backups", `pre-rollback-${newestTimestamp()}.sqlite`);
4009
4961
  await backupDatabase(database, safetyBackup, host);
4010
4962
  }
4011
4963
  if (backupToRestore !== null) {
@@ -4013,8 +4965,8 @@ async function rollbackSystemUnlocked(options = {}) {
4013
4965
  restoredDatabase = true;
4014
4966
  runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, database], "restored database ownership");
4015
4967
  }
4016
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), oldCurrent, host);
4017
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), previous, host);
4968
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "previous"), oldCurrent, host);
4969
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), previous, host);
4018
4970
  state.previousVersion = state.currentVersion;
4019
4971
  state.currentVersion = previousVersion;
4020
4972
  state.updatedAt = new Date(now()).toISOString();
@@ -4036,10 +4988,10 @@ async function rollbackSystemUnlocked(options = {}) {
4036
4988
  };
4037
4989
  } catch (error) {
4038
4990
  stopUnits(host, units);
4039
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), oldCurrent, host);
4040
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), previous, host);
4991
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "current"), oldCurrent, host);
4992
+ replaceSymlink((0, import_node_path6.join)(paths.prefix, "previous"), previous, host);
4041
4993
  if (restoredDatabase && safetyBackup) {
4042
- const failed = (0, import_node_path4.join)(paths.stateDir, "backups", `failed-rollback-${newestTimestamp()}.sqlite`);
4994
+ const failed = (0, import_node_path6.join)(paths.stateDir, "backups", `failed-rollback-${newestTimestamp()}.sqlite`);
4043
4995
  await backupDatabase(hubDatabasePath(paths), failed, host);
4044
4996
  replaceDatabaseFromBackup(safetyBackup, hubDatabasePath(paths), null, host);
4045
4997
  runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, hubDatabasePath(paths)], "restored database ownership");
@@ -4096,31 +5048,31 @@ function uninstallSystem(options = {}) {
4096
5048
  assertDirectoryStopped(paths.stateDir);
4097
5049
  for (const unit of units) runMaybe(host, "systemctl", ["disable", unit]);
4098
5050
  for (const unit of units) {
4099
- const path = (0, import_node_path4.join)(paths.unitsDir, unit);
5051
+ const path = (0, import_node_path6.join)(paths.unitsDir, unit);
4100
5052
  assertManagedFileOwnership(paths, path);
4101
5053
  try {
4102
- (0, import_node_fs3.unlinkSync)(path);
5054
+ (0, import_node_fs6.unlinkSync)(path);
4103
5055
  } catch {
4104
5056
  }
4105
5057
  }
4106
5058
  runMaybe(host, "systemctl", ["daemon-reload"]);
4107
- const hadPowerAccess = state?.power?.enabled === true || (0, import_node_fs3.existsSync)(raplRulePath(paths)) || (0, import_node_fs3.existsSync)(powerOriginalPath(paths));
5059
+ const hadPowerAccess = state?.power?.enabled === true || (0, import_node_fs6.existsSync)(raplRulePath(paths)) || (0, import_node_fs6.existsSync)(powerOriginalPath(paths));
4108
5060
  if (hadPowerAccess) {
4109
5061
  try {
4110
- (0, import_node_fs3.unlinkSync)(raplRulePath(paths));
5062
+ (0, import_node_fs6.unlinkSync)(raplRulePath(paths));
4111
5063
  } catch {
4112
5064
  }
4113
5065
  try {
4114
- (0, import_node_fs3.unlinkSync)(powerSysusersPath(paths));
5066
+ (0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
4115
5067
  } catch {
4116
5068
  }
4117
5069
  restorePowerOriginals(paths, host);
4118
5070
  resumePowerRule(paths, host);
4119
5071
  }
4120
- removeSymlink((0, import_node_path4.join)(paths.prefix, "current"));
4121
- removeSymlink((0, import_node_path4.join)(paths.prefix, "previous"));
4122
- (0, import_node_fs3.rmSync)((0, import_node_path4.join)(paths.prefix, "releases"), { recursive: true, force: true });
4123
- if (options.preserveNode !== true) (0, import_node_fs3.rmSync)((0, import_node_path4.join)(paths.prefix, "node"), { recursive: true, force: true });
5072
+ removeSymlink((0, import_node_path6.join)(paths.prefix, "current"));
5073
+ removeSymlink((0, import_node_path6.join)(paths.prefix, "previous"));
5074
+ (0, import_node_fs6.rmSync)((0, import_node_path6.join)(paths.prefix, "releases"), { recursive: true, force: true });
5075
+ if (options.preserveNode !== true) (0, import_node_fs6.rmSync)((0, import_node_path6.join)(paths.prefix, "node"), { recursive: true, force: true });
4124
5076
  if (state !== null) {
4125
5077
  state.active = false;
4126
5078
  state.power = null;
@@ -4133,8 +5085,8 @@ function uninstallSystem(options = {}) {
4133
5085
  for (const group of [accounts.powerGroup, accounts.hub, accounts.collector]) runMaybe(host, "groupdel", [group]);
4134
5086
  }
4135
5087
  if (options.purgeData === true) {
4136
- (0, import_node_fs3.rmSync)(paths.stateDir, { recursive: true, force: true });
4137
- (0, import_node_fs3.rmSync)(paths.configDir, { recursive: true, force: true });
5088
+ (0, import_node_fs6.rmSync)(paths.stateDir, { recursive: true, force: true });
5089
+ (0, import_node_fs6.rmSync)(paths.configDir, { recursive: true, force: true });
4138
5090
  }
4139
5091
  return {
4140
5092
  operation: "uninstall",
@@ -4184,7 +5136,7 @@ function grantPower(options = {}) {
4184
5136
  writeManagedFile(powerSysusersPath(paths), renderPowerSysusers(paths, accounts), 420, true);
4185
5137
  runRequired(host, "systemd-sysusers", [], "power group creation");
4186
5138
  assertSystemAccounts(host, accounts, true, ["collector"]);
4187
- if (!(0, import_node_fs3.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
5139
+ if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
4188
5140
  writePowerRule(paths, accounts);
4189
5141
  resumePowerRule(paths, host);
4190
5142
  applyPowerAccess(paths, accounts, host);
@@ -4213,11 +5165,11 @@ function revokePower(options = {}) {
4213
5165
  if (options.apply !== true) return { operation: "power-revoke", ok: true, apply: false, message: "plan only", plan };
4214
5166
  assertApply(host);
4215
5167
  try {
4216
- (0, import_node_fs3.unlinkSync)(raplRulePath(paths));
5168
+ (0, import_node_fs6.unlinkSync)(raplRulePath(paths));
4217
5169
  } catch {
4218
5170
  }
4219
5171
  try {
4220
- (0, import_node_fs3.unlinkSync)(powerSysusersPath(paths));
5172
+ (0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
4221
5173
  } catch {
4222
5174
  }
4223
5175
  restorePowerOriginals(paths, host);
@@ -4433,6 +5385,12 @@ function printMutationReport(report) {
4433
5385
  console.log("then run `rigscope system connect https://<hub> --token-file <file> --apply`.");
4434
5386
  }
4435
5387
  }
5388
+ function printMachineDataOutcome(machineHistory) {
5389
+ if (machineHistory === void 0) return;
5390
+ const described = describeMachineDataReport(machineHistory);
5391
+ console.log(described.summary);
5392
+ if (described.warning) console.log(`Warning: ${described.warning}`);
5393
+ }
4436
5394
  async function runSystemCommand(argv) {
4437
5395
  const parsed = parseSystemArgs(argv);
4438
5396
  if (parsed.command === "help") {
@@ -4504,7 +5462,7 @@ ${humanPlan(report.plan)}`);
4504
5462
  if (parsed.command === "logs") {
4505
5463
  const selected = validateSystemPaths(paths);
4506
5464
  const component = componentFrom(parsed, false);
4507
- const units = (component ? [unitFor(component)] : [UNIT_NAMES.hub, UNIT_NAMES.collector]).filter((unit) => (0, import_node_fs3.existsSync)((0, import_node_path4.join)(selected.unitsDir, unit)));
5465
+ const units = (component ? [unitFor(component)] : [UNIT_NAMES.hub, UNIT_NAMES.collector]).filter((unit) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(selected.unitsDir, unit)));
4508
5466
  if (units.length === 0) throw new SystemError("not-installed", component ? `${component} logs are unavailable; its unit is not installed` : "no system services installed");
4509
5467
  const result = (0, import_node_child_process.spawnSync)("journalctl", [...units.flatMap((unit) => ["--unit", unit]), "--lines=100", "--no-pager", ...boolValue(values, "follow") ? ["--follow"] : []], { stdio: "inherit" });
4510
5468
  if (result.error) throw result.error;
@@ -4580,6 +5538,7 @@ ${humanPlan(report.plan)}`);
4580
5538
  else {
4581
5539
  printMutationReport(report);
4582
5540
  console.log(`Backup: ${report.backupPath ?? "none"}`);
5541
+ printMachineDataOutcome(report.machineHistory);
4583
5542
  }
4584
5543
  return 0;
4585
5544
  }