rigscope 0.2.0 → 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 (85) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -2
  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 +2 -2
  13. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  14. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  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 +2 -2
  19. package/.next/server/app/administration.segments/_full.segment.rsc +2 -2
  20. package/.next/server/app/administration.segments/_tree.segment.rsc +1 -1
  21. package/.next/server/app/administration.segments/administration/__PAGE__.segment.rsc +2 -2
  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 +2 -2
  25. package/.next/server/app/alerts.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/alerts.segments/_tree.segment.rsc +1 -1
  27. package/.next/server/app/alerts.segments/alerts/__PAGE__.segment.rsc +2 -2
  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 +3 -3
  32. package/.next/server/app/connections.segments/_full.segment.rsc +3 -3
  33. package/.next/server/app/connections.segments/_tree.segment.rsc +1 -1
  34. package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +3 -3
  35. package/.next/server/app/history/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/history.html +1 -1
  37. package/.next/server/app/history.rsc +2 -2
  38. package/.next/server/app/history.segments/_full.segment.rsc +2 -2
  39. package/.next/server/app/history.segments/_tree.segment.rsc +1 -1
  40. package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +2 -2
  41. package/.next/server/app/index.html +1 -1
  42. package/.next/server/app/index.rsc +2 -2
  43. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  44. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  45. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  46. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/login.html +1 -1
  48. package/.next/server/app/login.rsc +2 -2
  49. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  50. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  51. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  52. package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
  53. package/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/processes.html +1 -1
  56. package/.next/server/app/processes.rsc +2 -2
  57. package/.next/server/app/processes.segments/_full.segment.rsc +2 -2
  58. package/.next/server/app/processes.segments/_tree.segment.rsc +1 -1
  59. package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +2 -2
  60. package/.next/server/app-paths-manifest.json +2 -2
  61. package/.next/server/chunks/161.js +1 -1
  62. package/.next/server/middleware-build-manifest.js +1 -1
  63. package/.next/server/pages/404.html +1 -1
  64. package/.next/server/pages/500.html +1 -1
  65. package/.next/static/chunks/939-92930fde48061e20.js +1 -0
  66. package/.next/static/chunks/app/connections/page-7d18b901f2338f20.js +1 -0
  67. package/ARCHITECTURE.md +18 -6
  68. package/CHANGELOG.md +35 -0
  69. package/dist/cli.cjs +762 -509
  70. package/dist/collector.cjs +581 -115
  71. package/dist/hub.cjs +7 -3
  72. package/dist/system.cjs +1257 -363
  73. package/docs/configuration.md +1 -0
  74. package/docs/installation.md +9 -3
  75. package/docs/monitoring.md +6 -3
  76. package/docs/operations.md +40 -13
  77. package/docs/system-installation.md +11 -4
  78. package/docs/telemetry-storage.md +80 -9
  79. package/package.json +1 -1
  80. package/packaging/collector.env.example +6 -0
  81. package/packaging/system/rigscope-collector.service.example +3 -0
  82. package/.next/static/chunks/939-514e554087419c71.js +0 -1
  83. package/.next/static/chunks/app/connections/page-75abd16fc495fcbd.js +0 -1
  84. /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → JbaLjudQSxrMUD_VHPrnD}/_buildManifest.js +0 -0
  85. /package/.next/static/{Jlbibe2_iFCljUO4-BUK7 → 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
 
@@ -1085,6 +1112,851 @@ function boundedString(value, field, message, maxLength) {
1085
1112
  return value;
1086
1113
  }
1087
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
+
1088
1960
  // src/cli/system.ts
1089
1961
  var SYSTEM_SCOPE = "system";
1090
1962
  var DEFAULT_SYSTEM_ACCOUNTS = {
@@ -1194,13 +2066,13 @@ function createSystemHost(overrides = {}) {
1194
2066
  }
1195
2067
  function defaultSystemPaths(overrides = {}) {
1196
2068
  return {
1197
- prefix: (0, import_node_path4.resolve)(overrides.prefix ?? "/opt/rigscope"),
1198
- configDir: (0, import_node_path4.resolve)(overrides.configDir ?? "/etc/rigscope"),
1199
- stateDir: (0, import_node_path4.resolve)(overrides.stateDir ?? "/var/lib/rigscope"),
1200
- unitsDir: (0, import_node_path4.resolve)(overrides.unitsDir ?? "/etc/systemd/system"),
1201
- sysusersDir: (0, import_node_path4.resolve)(overrides.sysusersDir ?? "/etc/sysusers.d"),
1202
- udevRulesDir: (0, import_node_path4.resolve)(overrides.udevRulesDir ?? "/etc/udev/rules.d"),
1203
- 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")
1204
2076
  };
1205
2077
  }
1206
2078
  function unitNamesFor(role) {
@@ -1215,11 +2087,11 @@ function rolesFor(role) {
1215
2087
  return { hub: role !== "collector", collector: role !== "hub" };
1216
2088
  }
1217
2089
  function isWithin(parent, candidate) {
1218
- const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(parent), (0, import_node_path4.resolve)(candidate));
1219
- 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);
1220
2092
  }
1221
2093
  function ensureAbsolute(path, label) {
1222
- const absolute = (0, import_node_path4.resolve)(path);
2094
+ const absolute = (0, import_node_path6.resolve)(path);
1223
2095
  if (/[\0\r\n]/.test(absolute)) throw new SystemError("unsafe-path", `${label} contains control characters`);
1224
2096
  if (absolute === "/") throw new SystemError("unsafe-path", `${label} must not be the filesystem root`);
1225
2097
  for (const forbidden of ["/home", "/root", "/proc", "/sys", "/dev", "/boot"]) {
@@ -1233,7 +2105,7 @@ function validateSystemPaths(input = {}) {
1233
2105
  const paths = defaultSystemPaths(input);
1234
2106
  for (const [label, value] of Object.entries(paths)) {
1235
2107
  if (label === "powercapRoot") {
1236
- 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) === "/") {
1237
2109
  throw new SystemError("unsafe-path", `powercap root is unsafe: ${value}`);
1238
2110
  }
1239
2111
  } else {
@@ -1288,19 +2160,19 @@ function runRequired(host, command, args, label, options) {
1288
2160
  function runMaybe(host, command, args, options) {
1289
2161
  return host.run(command, args, options);
1290
2162
  }
1291
- function sha256File(path) {
1292
- 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");
1293
2165
  }
1294
2166
  function atomicWrite(path, content, mode) {
1295
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true, mode: 493 });
1296
- 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)}`);
1297
2169
  try {
1298
- (0, import_node_fs3.writeFileSync)(temporary, content, { mode });
1299
- (0, import_node_fs3.chmodSync)(temporary, mode);
1300
- (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);
1301
2173
  } catch (error) {
1302
2174
  try {
1303
- (0, import_node_fs3.unlinkSync)(temporary);
2175
+ (0, import_node_fs6.unlinkSync)(temporary);
1304
2176
  } catch {
1305
2177
  }
1306
2178
  throw error;
@@ -1309,12 +2181,12 @@ function atomicWrite(path, content, mode) {
1309
2181
  function ensureDirectory(path, mode, privateDirectory = false) {
1310
2182
  let stat;
1311
2183
  try {
1312
- stat = (0, import_node_fs3.lstatSync)(path);
2184
+ stat = (0, import_node_fs6.lstatSync)(path);
1313
2185
  } catch (error) {
1314
2186
  if (error.code !== "ENOENT") throw error;
1315
- (0, import_node_fs3.mkdirSync)(path, { recursive: true, mode });
1316
- (0, import_node_fs3.chmodSync)(path, mode);
1317
- 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);
1318
2190
  }
1319
2191
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
1320
2192
  throw new SystemError("unsafe-path", `${path} must be a real directory`);
@@ -1329,8 +2201,8 @@ function isDefaultSystemLayout(paths) {
1329
2201
  }
1330
2202
  function assertManagedPathSafety(paths, enforceRoot) {
1331
2203
  for (const path of [paths.prefix, paths.configDir, paths.stateDir, paths.unitsDir, paths.sysusersDir, paths.udevRulesDir]) {
1332
- if (!(0, import_node_fs3.existsSync)(path)) continue;
1333
- 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);
1334
2206
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
1335
2207
  throw new SystemError("unsafe-path", `${path} must be a real directory`);
1336
2208
  }
@@ -1343,8 +2215,8 @@ function assertManagedPathSafety(paths, enforceRoot) {
1343
2215
  }
1344
2216
  }
1345
2217
  function writeManagedFile(path, content, mode, replace) {
1346
- if ((0, import_node_fs3.existsSync)(path)) {
1347
- 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);
1348
2220
  if (!stat.isFile() || stat.isSymbolicLink()) {
1349
2221
  throw new SystemError("unsafe-path", `${path} must be a regular file, not a symlink`);
1350
2222
  }
@@ -1383,7 +2255,7 @@ function parseEnvValue(value) {
1383
2255
  function readEnvDocument(path) {
1384
2256
  let raw;
1385
2257
  try {
1386
- raw = (0, import_node_fs3.readFileSync)(path, "utf8");
2258
+ raw = (0, import_node_fs6.readFileSync)(path, "utf8");
1387
2259
  } catch (error) {
1388
2260
  if (error.code === "ENOENT") return null;
1389
2261
  throw error;
@@ -1430,43 +2302,43 @@ function connectDestinationUrl(value) {
1430
2302
  return url.href;
1431
2303
  }
1432
2304
  function hubDatabasePath(paths) {
1433
- return (0, import_node_path4.join)(paths.stateDir, "hub", "telemetry.sqlite");
2305
+ return (0, import_node_path6.join)(paths.stateDir, "hub", "telemetry.sqlite");
1434
2306
  }
1435
2307
  function hubEnrollmentPath(paths) {
1436
- return (0, import_node_path4.join)(paths.stateDir, "hub", "enrollment.json");
2308
+ return (0, import_node_path6.join)(paths.stateDir, "hub", "enrollment.json");
1437
2309
  }
1438
2310
  function hubSocketPath(paths) {
1439
- 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");
1440
2312
  }
1441
2313
  function collectorStatePath(paths) {
1442
- return (0, import_node_path4.join)(paths.stateDir, "collector", "state.json");
2314
+ return (0, import_node_path6.join)(paths.stateDir, "collector", "state.json");
1443
2315
  }
1444
2316
  function hubEnvPath(paths) {
1445
- return (0, import_node_path4.join)(paths.configDir, "hub.env");
2317
+ return (0, import_node_path6.join)(paths.configDir, "hub.env");
1446
2318
  }
1447
2319
  function collectorEnvPath(paths) {
1448
- return (0, import_node_path4.join)(paths.configDir, "collector.env");
2320
+ return (0, import_node_path6.join)(paths.configDir, "collector.env");
1449
2321
  }
1450
2322
  function sysusersPath(paths) {
1451
- return (0, import_node_path4.join)(paths.sysusersDir, SYSUSERS_NAME);
2323
+ return (0, import_node_path6.join)(paths.sysusersDir, SYSUSERS_NAME);
1452
2324
  }
1453
2325
  function powerSysusersPath(paths) {
1454
- return (0, import_node_path4.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
2326
+ return (0, import_node_path6.join)(paths.sysusersDir, POWER_SYSUSERS_NAME);
1455
2327
  }
1456
2328
  function raplRulePath(paths) {
1457
- return (0, import_node_path4.join)(paths.udevRulesDir, RAPL_RULE_NAME);
2329
+ return (0, import_node_path6.join)(paths.udevRulesDir, RAPL_RULE_NAME);
1458
2330
  }
1459
2331
  function stateFilePath(paths) {
1460
- return (0, import_node_path4.join)(paths.stateDir, STATE_FILE_NAME);
2332
+ return (0, import_node_path6.join)(paths.stateDir, STATE_FILE_NAME);
1461
2333
  }
1462
2334
  function readCollectorIdentity(path) {
1463
- if (!(0, import_node_fs3.existsSync)(path)) return null;
1464
- 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);
1465
2337
  if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
1466
2338
  if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be private (0600)`);
1467
2339
  let raw;
1468
2340
  try {
1469
- raw = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
2341
+ raw = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
1470
2342
  } catch {
1471
2343
  throw new SystemError("invalid-file", `${path} is not valid JSON`);
1472
2344
  }
@@ -1500,7 +2372,7 @@ function readCollectorIdentity(path) {
1500
2372
  return { collectorId: record.collectorId, destinations };
1501
2373
  }
1502
2374
  function writeCollectorIdentity(path, identity) {
1503
- ensureDirectory((0, import_node_path4.dirname)(path), 448, true);
2375
+ ensureDirectory((0, import_node_path6.dirname)(path), 448, true);
1504
2376
  atomicWrite(path, `${JSON.stringify(identity)}
1505
2377
  `, 384);
1506
2378
  }
@@ -1540,7 +2412,7 @@ function ensureHubOwner(paths) {
1540
2412
  if (!Array.isArray(parsed)) throw new SystemError("invalid-config", "HUB_BROWSER_USERS must be a JSON array");
1541
2413
  if (parsed.length > 0) return void 0;
1542
2414
  }
1543
- const password = (0, import_node_crypto5.randomBytes)(18).toString("base64url");
2415
+ const password = (0, import_node_crypto7.randomBytes)(18).toString("base64url");
1544
2416
  const users = [{ username: "owner", passwordHash: hashPassword(password), machineIds: "*", admin: true }];
1545
2417
  writeEnvValue(hubEnvPath(paths), "HUB_BROWSER_USERS", JSON.stringify(users));
1546
2418
  return password;
@@ -1591,8 +2463,8 @@ function updateHubCollectorRegistry(paths, identity) {
1591
2463
  }
1592
2464
  function registerCollectorInHubDatabase(paths, identity, host) {
1593
2465
  const database = hubDatabasePath(paths);
1594
- if (!(0, import_node_fs3.existsSync)(database)) return { inserted: false };
1595
- 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 });
1596
2468
  try {
1597
2469
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
1598
2470
  if (!table) return { inserted: false };
@@ -1606,8 +2478,8 @@ function registerCollectorInHubDatabase(paths, identity, host) {
1606
2478
  }
1607
2479
  function findCollectorInHubDatabase(paths, collectorId) {
1608
2480
  const database = hubDatabasePath(paths);
1609
- if (!(0, import_node_fs3.existsSync)(database)) return null;
1610
- 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 });
1611
2483
  try {
1612
2484
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_registry'").get();
1613
2485
  if (!table) return null;
@@ -1619,11 +2491,11 @@ function findCollectorInHubDatabase(paths, collectorId) {
1619
2491
  }
1620
2492
  function createLocalEnrollmentGrant(paths, host) {
1621
2493
  const database = hubDatabasePath(paths);
1622
- if (!(0, import_node_fs3.existsSync)(database)) throw new SystemError("hub-unavailable", "the hub database does not exist yet");
1623
- const token = (0, import_node_crypto5.randomBytes)(32).toString("base64url");
1624
- 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");
1625
2497
  const expiresAt = host.now() + 10 * 60 * 1e3;
1626
- const db = new import_node_sqlite.DatabaseSync(database, { timeout: 5e3 });
2498
+ const db = new import_node_sqlite3.DatabaseSync(database, { timeout: 5e3 });
1627
2499
  try {
1628
2500
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='collector_grants'").get();
1629
2501
  if (!table) throw new SystemError("hub-unavailable", "the hub registry is not initialized");
@@ -1673,7 +2545,7 @@ async function pairCollectorWithHub(options) {
1673
2545
  note: "the local hub has no reachable loopback or HTTPS destination"
1674
2546
  };
1675
2547
  }
1676
- 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))) {
1677
2549
  return { paired: false, awaiting: true, hubWasActive: false, note: "the local hub unit is not installed" };
1678
2550
  }
1679
2551
  const hubWasActive = systemdActive(host, UNIT_NAMES.hub);
@@ -1686,7 +2558,7 @@ async function pairCollectorWithHub(options) {
1686
2558
  destinations: [{ url: destination.url, token: registered.token, sharing: { ...SYSTEM_DEFAULT_SHARING } }]
1687
2559
  };
1688
2560
  writeCollectorIdentity(collectorStatePath(paths), adopted);
1689
- 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);
1690
2562
  return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
1691
2563
  }
1692
2564
  }
@@ -1694,7 +2566,7 @@ async function pairCollectorWithHub(options) {
1694
2566
  try {
1695
2567
  grant = createLocalEnrollmentGrant(paths, host);
1696
2568
  } catch (error) {
1697
- 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);
1698
2570
  return {
1699
2571
  paired: false,
1700
2572
  awaiting: true,
@@ -1712,18 +2584,18 @@ async function pairCollectorWithHub(options) {
1712
2584
  note: `enrollment through the live hub failed: ${error instanceof Error ? error.message : String(error)}`
1713
2585
  };
1714
2586
  } finally {
1715
- 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);
1716
2588
  }
1717
2589
  if (!collectorConfigured(paths).configured) {
1718
2590
  return { paired: false, awaiting: true, hubWasActive, note: "the hub accepted enrollment but no destination was saved" };
1719
2591
  }
1720
- 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);
1721
2593
  return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
1722
2594
  }
1723
2595
  const identity = {
1724
- collectorId: existing?.collectorId ?? (0, import_node_crypto5.randomUUID)(),
1725
- machineId: (0, import_node_crypto5.randomUUID)(),
1726
- 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")
1727
2599
  };
1728
2600
  const registry = registerCollectorInHubDatabase(paths, identity, host);
1729
2601
  let token = identity.token;
@@ -1750,7 +2622,7 @@ async function pairCollectorWithHub(options) {
1750
2622
  }
1751
2623
  ];
1752
2624
  writeCollectorIdentity(collectorStatePath(paths), next);
1753
- 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);
1754
2626
  return { paired: true, awaiting: false, hubWasActive, destination: destination.url };
1755
2627
  }
1756
2628
  function unitCommon(name, description, paths, account, envPath, execStart, extraService = []) {
@@ -1767,7 +2639,7 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
1767
2639
  "Type=simple",
1768
2640
  `User=${account}`,
1769
2641
  `Group=${account}`,
1770
- `WorkingDirectory=${(0, import_node_path4.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
2642
+ `WorkingDirectory=${(0, import_node_path6.join)(paths.prefix, "current").replace(/%/g, "%%")}`,
1771
2643
  `EnvironmentFile=${envPath.replace(/%/g, "%%")}`,
1772
2644
  "Environment=NODE_ENV=production",
1773
2645
  "Restart=on-failure",
@@ -1802,13 +2674,13 @@ function unitCommon(name, description, paths, account, envPath, execStart, extra
1802
2674
  }
1803
2675
  function renderSystemHubUnit(options) {
1804
2676
  const { paths, accounts } = options;
1805
- 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");
1806
2678
  const extra = [
1807
2679
  `Environment=${systemdQuote(`HUB_DATABASE_PATH=${options.database}`)}`,
1808
2680
  `Environment=${systemdQuote(`HUB_STATE_FILE=${options.enrollment}`)}`,
1809
2681
  ...options.remote ? [`Environment=${systemdQuote(`HUB_SOCKET_PATH=${options.socket}`)}`] : [],
1810
- `ReadWritePaths=${systemdQuote((0, import_node_path4.join)(paths.stateDir, "hub"))}`,
1811
- `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"))}`,
1812
2684
  "PrivateDevices=yes",
1813
2685
  "TimeoutStopSec=15s"
1814
2686
  ];
@@ -1827,14 +2699,14 @@ function renderSystemHubUnit(options) {
1827
2699
  }
1828
2700
  function renderSystemCollectorUnit(options) {
1829
2701
  const { paths, accounts } = options;
1830
- 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");
1831
2703
  const extra = [
1832
2704
  ...options.powerAccess ? [`SupplementaryGroups=${accounts.powerGroup}`] : [],
1833
2705
  `Environment=${systemdQuote(`COLLECTOR_MODE=real`)}`,
1834
2706
  `Environment=${systemdQuote(`COLLECTOR_STATE_FILE=${options.collectorState}`)}`,
1835
- `Environment=${systemdQuote(`COLLECTOR_HEALTH_FILE=${(0, import_node_path4.join)(paths.stateDir, "collector", "health.json")}`)}`,
1836
- `ReadWritePaths=${systemdQuote((0, import_node_path4.join)(paths.stateDir, "collector"))}`,
1837
- `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"))}`,
1838
2710
  "TimeoutStopSec=10s"
1839
2711
  ];
1840
2712
  return [
@@ -1907,11 +2779,15 @@ function collectorEnvTemplate() {
1907
2779
  "# COLLECTOR_ENROLL_URL=ws://127.0.0.1:8081/api/collector/connect",
1908
2780
  "# COLLECTOR_ENROLL_TOKEN=<one-use grant>",
1909
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",
1910
2786
  ""
1911
2787
  ].join("\n");
1912
2788
  }
1913
2789
  function assertPrivateSourceFile(path, label) {
1914
- const stat = (0, import_node_fs3.lstatSync)(path);
2790
+ const stat = (0, import_node_fs6.lstatSync)(path);
1915
2791
  if (!stat.isFile() || stat.isSymbolicLink()) {
1916
2792
  throw new SystemError("unsafe-adoption", `${label} must be a regular file: ${path}`);
1917
2793
  }
@@ -1922,7 +2798,7 @@ function assertPrivateSourceFile(path, label) {
1922
2798
  function readJsonObject(path) {
1923
2799
  let raw;
1924
2800
  try {
1925
- raw = (0, import_node_fs3.readFileSync)(path, "utf8");
2801
+ raw = (0, import_node_fs6.readFileSync)(path, "utf8");
1926
2802
  } catch (error) {
1927
2803
  throw new SystemError("missing-file", `cannot read ${path}: ${error.message}`);
1928
2804
  }
@@ -1945,8 +2821,8 @@ function isCanonicalComponents(value) {
1945
2821
  }
1946
2822
  function readSystemState(paths) {
1947
2823
  const path = stateFilePath(paths);
1948
- if (!(0, import_node_fs3.existsSync)(path)) return null;
1949
- 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);
1950
2826
  if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("unsafe-path", `${path} must be a regular file`);
1951
2827
  if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `${path} must be 0600`);
1952
2828
  const raw = readJsonObject(path);
@@ -1978,7 +2854,7 @@ function readSystemState(paths) {
1978
2854
  }
1979
2855
  function systemStateIsLegacy(paths) {
1980
2856
  const path = stateFilePath(paths);
1981
- if (!(0, import_node_fs3.existsSync)(path)) return false;
2857
+ if (!(0, import_node_fs6.existsSync)(path)) return false;
1982
2858
  let raw;
1983
2859
  try {
1984
2860
  raw = readJsonObject(path);
@@ -2029,24 +2905,24 @@ function validateVersion(value) {
2029
2905
  return value;
2030
2906
  }
2031
2907
  function releaseVersion(root) {
2032
- const metadata = readJsonObject((0, import_node_path4.join)(root, "package.json"));
2908
+ const metadata = readJsonObject((0, import_node_path6.join)(root, "package.json"));
2033
2909
  if (Object.hasOwn(metadata, "rigscopeConfigVersion")) {
2034
2910
  throw new SystemError("invalid-release", "release uses unfinished configuration metadata; select a canonical release");
2035
2911
  }
2036
2912
  return validateVersion(metadata.version);
2037
2913
  }
2038
2914
  function releaseSchemaVersion(root) {
2039
- const manifestPath = (0, import_node_path4.join)(root, "manifest.json");
2040
- 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;
2041
2917
  const manifest = readJsonObject(manifestPath);
2042
2918
  return Number.isSafeInteger(manifest.schemaVersion) ? Number(manifest.schemaVersion) : null;
2043
2919
  }
2044
2920
  function assertReleaseArtifacts(root) {
2045
2921
  for (const artifact of REQUIRED_ARTIFACTS) {
2046
- const path = (0, import_node_path4.join)(root, artifact);
2922
+ const path = (0, import_node_path6.join)(root, artifact);
2047
2923
  let stat;
2048
2924
  try {
2049
- stat = (0, import_node_fs3.lstatSync)(path);
2925
+ stat = (0, import_node_fs6.lstatSync)(path);
2050
2926
  } catch {
2051
2927
  throw new SystemError("missing-artifact", `release is missing ${artifact}`);
2052
2928
  }
@@ -2054,12 +2930,12 @@ function assertReleaseArtifacts(root) {
2054
2930
  throw new SystemError("invalid-artifact", `release artifact is not a non-empty regular file: ${artifact}`);
2055
2931
  }
2056
2932
  }
2057
- 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();
2058
2934
  if (buildId === "" || /\s/.test(buildId)) throw new SystemError("invalid-artifact", ".next/BUILD_ID is invalid");
2059
2935
  for (const directory of [".next/server", ".next/static"]) {
2060
2936
  let stat;
2061
2937
  try {
2062
- 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));
2063
2939
  } catch {
2064
2940
  throw new SystemError("missing-artifact", `release is missing ${directory}/`);
2065
2941
  }
@@ -2067,10 +2943,10 @@ function assertReleaseArtifacts(root) {
2067
2943
  }
2068
2944
  }
2069
2945
  function verifyReleaseManifest(root) {
2070
- const manifestPath = (0, import_node_path4.join)(root, "manifest.json");
2071
- 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;
2072
2948
  const manifest = readJsonObject(manifestPath);
2073
- const packageJson = readJsonObject((0, import_node_path4.join)(root, "package.json"));
2949
+ const packageJson = readJsonObject((0, import_node_path6.join)(root, "package.json"));
2074
2950
  if (manifest.name !== void 0 && manifest.name !== packageJson.name) {
2075
2951
  throw new SystemError("manifest-mismatch", "release manifest name does not match package.json");
2076
2952
  }
@@ -2089,17 +2965,17 @@ function verifyReleaseManifest(root) {
2089
2965
  if (typeof relativePath !== "string" || relativePath === "" || relativePath.startsWith("/") || relativePath.includes("\\") || relativePath.split("/").includes("..")) {
2090
2966
  throw new SystemError("invalid-manifest", `release manifest contains an unsafe path: ${String(relativePath)}`);
2091
2967
  }
2092
- const absolute = (0, import_node_path4.join)(root, relativePath);
2093
- 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);
2094
2970
  if (!stat.isFile() || stat.isSymbolicLink()) throw new SystemError("invalid-manifest", `release artifact is not a regular file: ${relativePath}`);
2095
2971
  if (entry.bytes !== stat.size) throw new SystemError("artifact-mismatch", `release artifact size changed: ${relativePath}`);
2096
- 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}`);
2097
2973
  listed.add(relativePath);
2098
2974
  }
2099
2975
  const visit = (current) => {
2100
- for (const entry of (0, import_node_fs3.readdirSync)(current, { withFileTypes: true })) {
2101
- const absolute = (0, import_node_path4.join)(current, entry.name);
2102
- 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("/");
2103
2979
  if (rel === "node_modules" || rel.startsWith("node_modules/") || rel === ".next/cache" || rel.startsWith(".next/cache/")) continue;
2104
2980
  if (entry.isSymbolicLink()) throw new SystemError("unsafe-release", `release contains a symlink: ${rel}`);
2105
2981
  if (entry.isDirectory()) {
@@ -2114,49 +2990,49 @@ function verifyReleaseManifest(root) {
2114
2990
  }
2115
2991
  function assertNoSymlinks(root) {
2116
2992
  const visit = (path) => {
2117
- const stat = (0, import_node_fs3.lstatSync)(path);
2118
- 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)}`);
2119
2995
  if (!stat.isDirectory()) return;
2120
- 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));
2121
2997
  };
2122
2998
  visit(root);
2123
2999
  }
2124
3000
  function copyReleaseTree(source, destination) {
2125
3001
  const visit = (current, target) => {
2126
- const stat = (0, import_node_fs3.lstatSync)(current);
2127
- 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)}`);
2128
3004
  if (stat.isDirectory()) {
2129
- (0, import_node_fs3.mkdirSync)(target, { recursive: true, mode: 493 });
2130
- for (const entry of (0, import_node_fs3.readdirSync)(current)) {
2131
- const child = (0, import_node_path4.join)(current, entry);
2132
- 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);
2133
3009
  if (rel === ".git" || rel === ".next-dev" || rel === ".next/cache" || rel === "node_modules" || rel.startsWith("node_modules/")) {
2134
3010
  continue;
2135
3011
  }
2136
- visit(child, (0, import_node_path4.join)(target, entry));
3012
+ visit(child, (0, import_node_path6.join)(target, entry));
2137
3013
  }
2138
3014
  return;
2139
3015
  }
2140
- if (!stat.isFile()) throw new SystemError("unsafe-release", `source contains a non-regular file: ${(0, import_node_path4.relative)(source, current)}`);
2141
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(target), { recursive: true, mode: 493 });
2142
- (0, import_node_fs3.copyFileSync)(current, target);
2143
- (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);
2144
3020
  };
2145
3021
  visit(source, destination);
2146
3022
  }
2147
3023
  function copyReleaseAllowlisted(source, destination) {
2148
- for (const entry of (0, import_node_fs3.readdirSync)(source)) {
3024
+ for (const entry of (0, import_node_fs6.readdirSync)(source)) {
2149
3025
  if (!PACKAGE_TOP_LEVEL.has(entry)) continue;
2150
3026
  if (entry === "node_modules") continue;
2151
- const child = (0, import_node_path4.join)(source, entry);
2152
- const target = (0, import_node_path4.join)(destination, entry);
2153
- 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);
2154
3030
  if (stat.isSymbolicLink()) throw new SystemError("unsafe-release", `source contains a symlink: ${entry}`);
2155
3031
  if (stat.isDirectory()) {
2156
3032
  copyReleaseTree(child, target);
2157
3033
  } else if (stat.isFile()) {
2158
- (0, import_node_fs3.copyFileSync)(child, target);
2159
- (0, import_node_fs3.chmodSync)(target, 420);
3034
+ (0, import_node_fs6.copyFileSync)(child, target);
3035
+ (0, import_node_fs6.chmodSync)(target, 420);
2160
3036
  }
2161
3037
  }
2162
3038
  }
@@ -2191,28 +3067,28 @@ function extractArchive(archive, stage, host) {
2191
3067
  }
2192
3068
  function discardStagedRelease(staged) {
2193
3069
  if (staged === null) return;
2194
- (0, import_node_fs3.rmSync)(staged.stageDir, { recursive: true, force: true });
3070
+ (0, import_node_fs6.rmSync)(staged.stageDir, { recursive: true, force: true });
2195
3071
  }
2196
3072
  function releaseTargetDir(paths, version) {
2197
- return (0, import_node_path4.join)(paths.prefix, "releases", version);
3073
+ return (0, import_node_path6.join)(paths.prefix, "releases", version);
2198
3074
  }
2199
3075
  function assertReleaseTargetAvailable(paths, releaseDir, replaceRelease) {
2200
- if (!(0, import_node_fs3.existsSync)(releaseDir)) return;
3076
+ if (!(0, import_node_fs6.existsSync)(releaseDir)) return;
2201
3077
  const current = currentReleaseDir(paths);
2202
3078
  const previous = previousReleaseDir(paths);
2203
3079
  if (!replaceRelease || current === releaseDir || previous === releaseDir) {
2204
3080
  throw new SystemError(
2205
3081
  "release-exists",
2206
- `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`
2207
3083
  );
2208
3084
  }
2209
3085
  }
2210
3086
  function stageRelease(options) {
2211
- const source = (0, import_node_fs3.realpathSync)((0, import_node_path4.resolve)(options.source));
2212
- 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);
2213
3089
  ensureDirectory(options.paths.prefix, 493);
2214
- const stageDir = (0, import_node_path4.join)(options.paths.prefix, `.stage-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2215
- (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 });
2216
3092
  try {
2217
3093
  if (stat.isDirectory() && !stat.isSymbolicLink()) {
2218
3094
  copyReleaseAllowlisted(source, stageDir);
@@ -2230,7 +3106,7 @@ function stageRelease(options) {
2230
3106
  artifacts: [...REQUIRED_ARTIFACTS]
2231
3107
  };
2232
3108
  } catch (error) {
2233
- (0, import_node_fs3.rmSync)(stageDir, { recursive: true, force: true });
3109
+ (0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
2234
3110
  throw error;
2235
3111
  }
2236
3112
  }
@@ -2256,13 +3132,13 @@ function prepareStagedRelease(options) {
2256
3132
  }
2257
3133
  }
2258
3134
  function commitStagedRelease(options) {
2259
- const releasesDir = (0, import_node_path4.join)(options.paths.prefix, "releases");
3135
+ const releasesDir = (0, import_node_path6.join)(options.paths.prefix, "releases");
2260
3136
  ensureDirectory(releasesDir, 493);
2261
3137
  const releaseDir = releaseTargetDir(options.paths, options.staged.version);
2262
3138
  assertReleaseTargetAvailable(options.paths, releaseDir, options.replaceRelease);
2263
- if ((0, import_node_fs3.existsSync)(releaseDir)) (0, import_node_fs3.rmSync)(releaseDir, { recursive: true, force: true });
2264
- (0, import_node_fs3.renameSync)(options.staged.stageDir, releaseDir);
2265
- 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);
2266
3142
  return {
2267
3143
  version: options.staged.version,
2268
3144
  releaseDir,
@@ -2271,20 +3147,20 @@ function commitStagedRelease(options) {
2271
3147
  };
2272
3148
  }
2273
3149
  function dependencyNames(root) {
2274
- const pkg = readJsonObject((0, import_node_path4.join)(root, "package.json"));
3150
+ const pkg = readJsonObject((0, import_node_path6.join)(root, "package.json"));
2275
3151
  const dependencies = pkg.dependencies;
2276
3152
  if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) return [];
2277
3153
  return Object.keys(dependencies).filter((name) => /^[A-Za-z0-9@/._-]+$/.test(name));
2278
3154
  }
2279
3155
  function missingDependencies(root) {
2280
- 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")));
2281
3157
  }
2282
3158
  function installDependencies(root, host, command = "npm") {
2283
- 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"));
2284
3160
  const args = hasLock ? ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"];
2285
- const siblingNpm = (0, import_node_path4.join)((0, import_node_path4.dirname)(process.execPath), "npm");
2286
- const executable = command === "npm" && (0, import_node_fs3.existsSync)(siblingNpm) ? siblingNpm : command;
2287
- 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);
2288
3164
  const path = [nodeBin, process.env.PATH ?? ""].filter(Boolean).join(":");
2289
3165
  runRequired(host, executable, args, "production dependency installation", {
2290
3166
  cwd: root,
@@ -2293,21 +3169,21 @@ function installDependencies(root, host, command = "npm") {
2293
3169
  });
2294
3170
  }
2295
3171
  function currentReleaseDir(paths) {
2296
- const link = (0, import_node_path4.join)(paths.prefix, "current");
3172
+ const link = (0, import_node_path6.join)(paths.prefix, "current");
2297
3173
  try {
2298
- const target = (0, import_node_fs3.readlinkSync)(link);
2299
- const absolute = (0, import_node_path4.isAbsolute)(target) ? target : (0, import_node_path4.resolve)(paths.prefix, target);
2300
- 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;
2301
3177
  } catch {
2302
3178
  return null;
2303
3179
  }
2304
3180
  }
2305
3181
  function previousReleaseDir(paths) {
2306
- const link = (0, import_node_path4.join)(paths.prefix, "previous");
3182
+ const link = (0, import_node_path6.join)(paths.prefix, "previous");
2307
3183
  try {
2308
- const target = (0, import_node_fs3.readlinkSync)(link);
2309
- const absolute = (0, import_node_path4.isAbsolute)(target) ? target : (0, import_node_path4.resolve)(paths.prefix, target);
2310
- 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;
2311
3187
  } catch {
2312
3188
  return null;
2313
3189
  }
@@ -2315,11 +3191,11 @@ function previousReleaseDir(paths) {
2315
3191
  function replaceSymlink(link, target, host) {
2316
3192
  const temporary = `${link}.tmp-${process.pid}-${Date.now()}`;
2317
3193
  try {
2318
- (0, import_node_fs3.symlinkSync)(target, temporary);
2319
- (0, import_node_fs3.renameSync)(temporary, link);
3194
+ (0, import_node_fs6.symlinkSync)(target, temporary);
3195
+ (0, import_node_fs6.renameSync)(temporary, link);
2320
3196
  } catch (error) {
2321
3197
  try {
2322
- (0, import_node_fs3.unlinkSync)(temporary);
3198
+ (0, import_node_fs6.unlinkSync)(temporary);
2323
3199
  } catch {
2324
3200
  }
2325
3201
  throw error;
@@ -2328,8 +3204,8 @@ function replaceSymlink(link, target, host) {
2328
3204
  }
2329
3205
  function removeSymlink(link) {
2330
3206
  try {
2331
- const stat = (0, import_node_fs3.lstatSync)(link);
2332
- 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);
2333
3209
  } catch {
2334
3210
  }
2335
3211
  }
@@ -2342,18 +3218,18 @@ function nodeVersion(host, nodePath) {
2342
3218
  return output;
2343
3219
  }
2344
3220
  function ensureRootOwnedNode(options) {
2345
- const selected = (0, import_node_path4.resolve)(options.nodePath ?? process.execPath);
2346
- 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);
2347
3223
  nodeVersion(options.host, real);
2348
- const stat = (0, import_node_fs3.statSync)(real);
2349
- 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);
2350
3226
  const unsafe = stat.uid !== 0 || (stat.mode & 18) !== 0 || options.copyNode === true || insideRelease;
2351
3227
  if (!unsafe || options.trustedNode === true) return real;
2352
- 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");
2353
3229
  if (!options.apply) return target;
2354
- ensureDirectory((0, import_node_path4.join)(options.paths.prefix, "node"), 493);
2355
- (0, import_node_fs3.copyFileSync)(real, target);
2356
- (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);
2357
3233
  runRequired(options.host, "chown", ["root:root", target], "node ownership");
2358
3234
  nodeVersion(options.host, target);
2359
3235
  return target;
@@ -2382,8 +3258,8 @@ function validatedOrigin(value) {
2382
3258
  return value;
2383
3259
  }
2384
3260
  function readAdoptionSetup(source) {
2385
- const configPath = (0, import_node_path4.join)(source, "config.json");
2386
- 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)) {
2387
3263
  throw new SystemError("invalid-adoption", "adopted installation has no config.json");
2388
3264
  }
2389
3265
  assertPrivateSourceFile(configPath, "adopted setup");
@@ -2431,35 +3307,35 @@ function detectUserInstallations(options = {}) {
2431
3307
  const roots = options.homeRoots ?? ["/home", "/root"];
2432
3308
  const found = [];
2433
3309
  for (const homeRoot of roots) {
2434
- if (!(0, import_node_fs3.existsSync)(homeRoot)) continue;
2435
- 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));
2436
3312
  for (const home of homes) {
2437
3313
  let stat;
2438
3314
  try {
2439
- stat = (0, import_node_fs3.lstatSync)(home);
3315
+ stat = (0, import_node_fs6.lstatSync)(home);
2440
3316
  } catch {
2441
3317
  continue;
2442
3318
  }
2443
3319
  if (!stat.isDirectory()) continue;
2444
- const unitDir = (0, import_node_path4.join)(home, ".config", "systemd", "user");
3320
+ const unitDir = (0, import_node_path6.join)(home, ".config", "systemd", "user");
2445
3321
  let units = [];
2446
3322
  try {
2447
- units = (0, import_node_fs3.readdirSync)(unitDir);
3323
+ units = (0, import_node_fs6.readdirSync)(unitDir);
2448
3324
  } catch {
2449
3325
  units = [];
2450
3326
  }
2451
3327
  for (const unitName of units) {
2452
3328
  if (!/^rigscope(?:-[0-9a-f]{12})?(?:-(?:hub|collector))?\.service$/.test(unitName)) continue;
2453
- const unitPath = (0, import_node_path4.join)(unitDir, unitName);
3329
+ const unitPath = (0, import_node_path6.join)(unitDir, unitName);
2454
3330
  let execStart = null;
2455
3331
  try {
2456
- 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="));
2457
3333
  execStart = line ? line.slice("ExecStart=".length) : null;
2458
3334
  } catch {
2459
3335
  }
2460
- const stateCandidates = [(0, import_node_path4.join)(home, ".local", "state", "rigscope")];
2461
- const stateDir = stateCandidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate)) ?? null;
2462
- 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 });
2463
3339
  }
2464
3340
  }
2465
3341
  }
@@ -2467,9 +3343,9 @@ function detectUserInstallations(options = {}) {
2467
3343
  }
2468
3344
  function lockIsLive(path) {
2469
3345
  try {
2470
- const stat = (0, import_node_fs3.lstatSync)(path);
3346
+ const stat = (0, import_node_fs6.lstatSync)(path);
2471
3347
  if (!stat.isFile() || stat.size > 4096) return { exists: true, live: false, pid: null, ambiguous: true };
2472
- const owner = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
3348
+ const owner = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
2473
3349
  const pid = owner.pid;
2474
3350
  if (!Number.isSafeInteger(pid) || pid < 1) return { exists: true, live: false, pid: null, ambiguous: true };
2475
3351
  try {
@@ -2487,9 +3363,9 @@ function lockIsLive(path) {
2487
3363
  }
2488
3364
  function directoryLocks(directory) {
2489
3365
  return [
2490
- (0, import_node_path4.join)(directory, "hub", "telemetry.sqlite.hub.lock"),
2491
- (0, import_node_path4.join)(directory, "run", "rigscope.lock"),
2492
- (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")
2493
3369
  ];
2494
3370
  }
2495
3371
  function assertDirectoryStopped(directory, scope = "local") {
@@ -2520,13 +3396,13 @@ function assertSystemStartAllowed(paths, components) {
2520
3396
  }
2521
3397
  function detectPowerDriver(moduleRoot = "/sys/module") {
2522
3398
  const candidates = ["intel_rapl_msr", "intel_rapl_common", "intel_rapl_tpmi", "intel_rapl", "amd_energy"];
2523
- 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)));
2524
3400
  }
2525
3401
  function readPasswd(path = "/etc/passwd") {
2526
3402
  const users = /* @__PURE__ */ new Map();
2527
3403
  let lines;
2528
3404
  try {
2529
- lines = (0, import_node_fs3.readFileSync)(path, "utf8").split("\n");
3405
+ lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
2530
3406
  } catch {
2531
3407
  return users;
2532
3408
  }
@@ -2543,7 +3419,7 @@ function readGroups(path = "/etc/group") {
2543
3419
  const groups = /* @__PURE__ */ new Map();
2544
3420
  let lines;
2545
3421
  try {
2546
- lines = (0, import_node_fs3.readFileSync)(path, "utf8").split("\n");
3422
+ lines = (0, import_node_fs6.readFileSync)(path, "utf8").split("\n");
2547
3423
  } catch {
2548
3424
  return groups;
2549
3425
  }
@@ -2571,7 +3447,7 @@ function inspectPowerAccess(options = {}) {
2571
3447
  const sysusers = options.sysusersPath ?? powerSysusersPath(paths);
2572
3448
  let ruleText = null;
2573
3449
  try {
2574
- ruleText = (0, import_node_fs3.readFileSync)(rulePath, "utf8");
3450
+ ruleText = (0, import_node_fs6.readFileSync)(rulePath, "utf8");
2575
3451
  } catch {
2576
3452
  ruleText = null;
2577
3453
  }
@@ -2588,27 +3464,27 @@ function inspectPowerAccess(options = {}) {
2588
3464
  const domains = [];
2589
3465
  let entries = [];
2590
3466
  try {
2591
- entries = (0, import_node_fs3.readdirSync)(paths.powercapRoot);
3467
+ entries = (0, import_node_fs6.readdirSync)(paths.powercapRoot);
2592
3468
  } catch {
2593
3469
  entries = [];
2594
3470
  }
2595
3471
  for (const entry of entries.filter((name) => /^intel-rapl:\d+$/.test(name)).sort()) {
2596
- const domainPath = (0, import_node_path4.join)(paths.powercapRoot, entry);
3472
+ const domainPath = (0, import_node_path6.join)(paths.powercapRoot, entry);
2597
3473
  let name = entry;
2598
3474
  try {
2599
- 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;
2600
3476
  } catch {
2601
3477
  }
2602
3478
  if (!/^package/.test(name)) continue;
2603
- const energyPath = (0, import_node_path4.join)(domainPath, "energy_uj");
2604
- 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");
2605
3481
  const readStat = (path) => {
2606
3482
  if (options.statOverride) {
2607
3483
  const override = options.statOverride(path);
2608
3484
  return override === null ? null : { mode: modeString(override.mode), uid: override.uid, gid: override.gid };
2609
3485
  }
2610
3486
  try {
2611
- const stat = (0, import_node_fs3.statSync)(path);
3487
+ const stat = (0, import_node_fs6.statSync)(path);
2612
3488
  return { mode: modeString(stat.mode), uid: stat.uid, gid: stat.gid };
2613
3489
  } catch {
2614
3490
  return null;
@@ -2642,7 +3518,7 @@ function inspectPowerAccess(options = {}) {
2642
3518
  problems.push(`collector account ${accounts.collector} is not a member of ${accounts.powerGroup}`);
2643
3519
  }
2644
3520
  if (!compliant) problems.push(`persistent udev rule is missing or not compliant: ${rulePath}`);
2645
- 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}`);
2646
3522
  for (const domain of domains) {
2647
3523
  if (!domain.energyExists) problems.push(`${domain.name}: energy_uj is unavailable`);
2648
3524
  else if (domain.energy?.worldReadable) problems.push(`${domain.name}: energy_uj is world-readable`);
@@ -2661,7 +3537,7 @@ function inspectPowerAccess(options = {}) {
2661
3537
  collectorGids,
2662
3538
  group: { name: accounts.powerGroup, exists: group !== null, gid: group?.gid ?? null, members: group?.members ?? [] },
2663
3539
  rule: { path: rulePath, present: ruleText !== null, compliant },
2664
- sysusers: { path: sysusers, present: (0, import_node_fs3.existsSync)(sysusers) },
3540
+ sysusers: { path: sysusers, present: (0, import_node_fs6.existsSync)(sysusers) },
2665
3541
  collectorCanRead,
2666
3542
  persistent: compliant && group !== null && collector !== null && group.members.includes(accounts.collector),
2667
3543
  worldReadable,
@@ -2670,15 +3546,15 @@ function inspectPowerAccess(options = {}) {
2670
3546
  };
2671
3547
  }
2672
3548
  async function backupDatabase(source, output, host) {
2673
- ensureDirectory((0, import_node_path4.dirname)(output), 448, true);
2674
- 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 });
2675
3551
  try {
2676
- await (0, import_node_sqlite.backup)(db, output);
3552
+ await (0, import_node_sqlite3.backup)(db, output);
2677
3553
  } finally {
2678
3554
  db.close();
2679
3555
  }
2680
- (0, import_node_fs3.chmodSync)(output, 384);
2681
- 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 });
2682
3558
  try {
2683
3559
  const result = check.prepare("PRAGMA integrity_check").get();
2684
3560
  if (!result || Object.values(result)[0] !== "ok") throw new SystemError("invalid-backup", `backup integrity check failed: ${output}`);
@@ -2688,8 +3564,8 @@ async function backupDatabase(source, output, host) {
2688
3564
  void host;
2689
3565
  }
2690
3566
  function databaseSchema(path) {
2691
- if (!(0, import_node_fs3.existsSync)(path)) return null;
2692
- 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 });
2693
3569
  try {
2694
3570
  const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'").get();
2695
3571
  if (!table) return null;
@@ -2701,34 +3577,34 @@ function databaseSchema(path) {
2701
3577
  }
2702
3578
  function replaceDatabaseFromBackup(backupPath, target, safetyBackup, host) {
2703
3579
  assertPrivateSourceFile(backupPath, "database backup");
2704
- const backup = new import_node_sqlite.DatabaseSync(backupPath, { readOnly: true });
3580
+ const backup3 = new import_node_sqlite3.DatabaseSync(backupPath, { readOnly: true });
2705
3581
  try {
2706
- 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");
2707
3583
  } finally {
2708
- backup.close();
3584
+ backup3.close();
2709
3585
  }
2710
- ensureDirectory((0, import_node_path4.dirname)(target), 448, true);
2711
- 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)) {
2712
3588
  void host;
2713
3589
  }
2714
- const temporary = (0, import_node_path4.join)((0, import_node_path4.dirname)(target), `.restore-${process.pid}-${Date.now()}.sqlite`);
2715
- (0, import_node_fs3.copyFileSync)(backupPath, temporary);
2716
- (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);
2717
3593
  for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
2718
3594
  try {
2719
- (0, import_node_fs3.unlinkSync)(sidecar);
3595
+ (0, import_node_fs6.unlinkSync)(sidecar);
2720
3596
  } catch {
2721
3597
  }
2722
3598
  }
2723
- (0, import_node_fs3.renameSync)(temporary, target);
2724
- (0, import_node_fs3.chmodSync)(target, 384);
3599
+ (0, import_node_fs6.renameSync)(temporary, target);
3600
+ (0, import_node_fs6.chmodSync)(target, 384);
2725
3601
  }
2726
3602
  function newestTimestamp() {
2727
3603
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2728
3604
  }
2729
3605
  function adoptUserState(options) {
2730
- const source = (0, import_node_path4.resolve)(options.source);
2731
- 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}`);
2732
3608
  if (isWithin(options.paths.prefix, source) || isWithin(options.paths.stateDir, source) || isWithin(source, options.paths.stateDir)) {
2733
3609
  throw new SystemError("invalid-adoption", "adoption source and system paths must not overlap");
2734
3610
  }
@@ -2738,10 +3614,10 @@ function adoptUserState(options) {
2738
3614
  const { port, origin } = setup;
2739
3615
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-adoption", "adopted hub port is invalid");
2740
3616
  if (options.role !== "collector") {
2741
- const sourceDatabase = (0, import_node_path4.join)(source, "hub", "telemetry.sqlite");
2742
- 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)) {
2743
3619
  assertPrivateSourceFile(sourceDatabase, "adopted database");
2744
- const database = new import_node_sqlite.DatabaseSync(sourceDatabase, { readOnly: true });
3620
+ const database = new import_node_sqlite3.DatabaseSync(sourceDatabase, { readOnly: true });
2745
3621
  try {
2746
3622
  const row = database.prepare("PRAGMA integrity_check").get();
2747
3623
  if (!row || Object.values(row)[0] !== "ok") throw new SystemError("invalid-adoption", "adopted database failed its integrity check");
@@ -2749,12 +3625,12 @@ function adoptUserState(options) {
2749
3625
  database.close();
2750
3626
  }
2751
3627
  }
2752
- const sourceEnrollment = (0, import_node_path4.join)(source, "hub", "enrollment.json");
2753
- 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");
2754
3630
  }
2755
3631
  if (options.role !== "hub") {
2756
- const sourceCollectorState = (0, import_node_path4.join)(source, "collector", "state.json");
2757
- 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)) {
2758
3634
  assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
2759
3635
  readCollectorIdentity(sourceCollectorState);
2760
3636
  }
@@ -2763,7 +3639,7 @@ function adoptUserState(options) {
2763
3639
  const warnings = [];
2764
3640
  const targetDatabase = hubDatabasePath(options.paths);
2765
3641
  const targetCollector = collectorStatePath(options.paths);
2766
- if (options.apply && (0, import_node_fs3.existsSync)(targetDatabase)) {
3642
+ if (options.apply && (0, import_node_fs6.existsSync)(targetDatabase)) {
2767
3643
  throw new SystemError("destination-exists", `refusing to overwrite ${targetDatabase}`);
2768
3644
  }
2769
3645
  const hubEnv = options.role === "collector" ? null : [
@@ -2798,62 +3674,42 @@ function adoptUserState(options) {
2798
3674
  port
2799
3675
  };
2800
3676
  }
2801
- function copyAdoptedTree(source, target, copied, skipLocks = true) {
2802
- const visit = (current, destination) => {
2803
- const stat = (0, import_node_fs3.lstatSync)(current);
2804
- if (stat.isSymbolicLink()) return;
2805
- if (stat.isDirectory()) {
2806
- (0, import_node_fs3.mkdirSync)(destination, { recursive: true, mode: 448 });
2807
- 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));
2808
- return;
2809
- }
2810
- if (!stat.isFile()) return;
2811
- if (skipLocks && /\.(lock|sock)$/.test(current)) return;
2812
- (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(destination), { recursive: true, mode: 448 });
2813
- (0, import_node_fs3.copyFileSync)(current, destination);
2814
- (0, import_node_fs3.chmodSync)(destination, 384);
2815
- copied.push({ source: current, target: destination, sha256: sha256File(destination), bytes: stat.size });
2816
- };
2817
- visit(source, target);
2818
- }
2819
3677
  async function performAdoptionCopyAsync(options) {
2820
3678
  const { source, paths, role, host, copy } = options;
2821
3679
  assertDirectoryStopped(source, role);
2822
3680
  const targetDatabase = hubDatabasePath(paths);
2823
- const sourceDatabase = (0, import_node_path4.join)(source, "hub", "telemetry.sqlite");
2824
- 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)) {
2825
3683
  assertPrivateSourceFile(sourceDatabase, "adopted database");
2826
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
3684
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "hub"), 448, true);
2827
3685
  await backupDatabase(sourceDatabase, targetDatabase, host);
2828
3686
  copy.record.files.push({
2829
3687
  source: sourceDatabase,
2830
3688
  target: targetDatabase,
2831
- sha256: sha256File(targetDatabase),
2832
- bytes: (0, import_node_fs3.statSync)(targetDatabase).size
3689
+ sha256: sha256File2(targetDatabase),
3690
+ bytes: (0, import_node_fs6.statSync)(targetDatabase).size
2833
3691
  });
2834
3692
  }
2835
- const sourceCollectorState = (0, import_node_path4.join)(source, "collector", "state.json");
2836
- 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)) {
2837
3695
  assertPrivateSourceFile(sourceCollectorState, "adopted collector state");
2838
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
3696
+ ensureDirectory((0, import_node_path6.join)(paths.stateDir, "collector"), 448, true);
2839
3697
  const targetCollector = collectorStatePath(paths);
2840
- (0, import_node_fs3.copyFileSync)(sourceCollectorState, targetCollector);
2841
- (0, import_node_fs3.chmodSync)(targetCollector, 384);
3698
+ (0, import_node_fs6.copyFileSync)(sourceCollectorState, targetCollector);
3699
+ (0, import_node_fs6.chmodSync)(targetCollector, 384);
2842
3700
  copy.record.files.push({
2843
3701
  source: sourceCollectorState,
2844
3702
  target: targetCollector,
2845
- sha256: sha256File(targetCollector),
2846
- bytes: (0, import_node_fs3.statSync)(targetCollector).size
3703
+ sha256: sha256File2(targetCollector),
3704
+ bytes: (0, import_node_fs6.statSync)(targetCollector).size
2847
3705
  });
2848
- const outbox = (0, import_node_path4.join)(source, "collector", "outbox");
2849
- if ((0, import_node_fs3.existsSync)(outbox)) copyAdoptedTree(outbox, (0, import_node_path4.join)(paths.stateDir, "collector", "outbox"), copy.record.files);
2850
3706
  }
2851
- const enrollment = (0, import_node_path4.join)(source, "hub", "enrollment.json");
2852
- 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)) {
2853
3709
  const target = hubEnrollmentPath(paths);
2854
- (0, import_node_fs3.copyFileSync)(enrollment, target);
2855
- (0, import_node_fs3.chmodSync)(target, 384);
2856
- 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 });
2857
3713
  }
2858
3714
  }
2859
3715
  function writeSysusersFiles(paths, accounts, role, power, host) {
@@ -2893,15 +3749,15 @@ function writePowerRule(paths, accounts) {
2893
3749
  writeManagedFile(raplRulePath(paths), renderRaplRule(accounts), 420, true);
2894
3750
  }
2895
3751
  function powerOriginalPath(paths) {
2896
- 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);
2897
3753
  }
2898
3754
  function capturePowerOriginals(paths) {
2899
3755
  const files = {};
2900
3756
  for (const domain of packageDomainPaths(paths.powercapRoot)) {
2901
3757
  for (const name of ["energy_uj", "max_energy_range_uj"]) {
2902
- const path = (0, import_node_path4.join)(domain, name);
3758
+ const path = (0, import_node_path6.join)(domain, name);
2903
3759
  try {
2904
- const stat = (0, import_node_fs3.statSync)(path);
3760
+ const stat = (0, import_node_fs6.statSync)(path);
2905
3761
  files[path] = { mode: stat.mode & 511, uid: stat.uid, gid: stat.gid };
2906
3762
  } catch {
2907
3763
  }
@@ -2913,8 +3769,8 @@ function capturePowerOriginals(paths) {
2913
3769
  function applyPowerAccess(paths, accounts, host) {
2914
3770
  for (const domain of packageDomainPaths(paths.powercapRoot)) {
2915
3771
  for (const name of ["energy_uj", "max_energy_range_uj"]) {
2916
- const path = (0, import_node_path4.join)(domain, name);
2917
- 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;
2918
3774
  runRequired(host, "chgrp", [accounts.powerGroup, path], `power counter group ${path}`);
2919
3775
  runRequired(host, "chmod", ["0440", path], `power counter mode ${path}`);
2920
3776
  }
@@ -2927,9 +3783,9 @@ function resumePowerRule(paths, host) {
2927
3783
  }
2928
3784
  function packageDomainPaths(root) {
2929
3785
  try {
2930
- 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) => {
2931
3787
  try {
2932
- 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());
2933
3789
  } catch {
2934
3790
  return false;
2935
3791
  }
@@ -2949,8 +3805,8 @@ function restorePowerOriginals(paths, host) {
2949
3805
  const files = { ...originals?.files ?? {} };
2950
3806
  for (const domain of packageDomainPaths(paths.powercapRoot)) {
2951
3807
  for (const name of ["energy_uj", "max_energy_range_uj"]) {
2952
- const path = (0, import_node_path4.join)(domain, name);
2953
- 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;
2954
3810
  const original = files[path];
2955
3811
  if (original) {
2956
3812
  runRequired(host, "chown", [`${original.uid}:${original.gid}`, path], "restore counter ownership");
@@ -2966,7 +3822,7 @@ function restorePowerOriginals(paths, host) {
2966
3822
  }
2967
3823
  }
2968
3824
  try {
2969
- (0, import_node_fs3.unlinkSync)(powerOriginalPath(paths));
3825
+ (0, import_node_fs6.unlinkSync)(powerOriginalPath(paths));
2970
3826
  } catch {
2971
3827
  }
2972
3828
  return restored;
@@ -2990,7 +3846,7 @@ function stopUnits(host, units) {
2990
3846
  if (units.some((unit) => systemdActive(host, unit))) throw new SystemError("service-active", "system services did not stop; no files were restored");
2991
3847
  }
2992
3848
  function unitsForCleanup(paths, host) {
2993
- 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));
2994
3850
  return [...Object.values(UNIT_NAMES), ...legacy];
2995
3851
  }
2996
3852
  function startUnits(host, units) {
@@ -3028,7 +3884,7 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
3028
3884
  const report = runRequired(
3029
3885
  host,
3030
3886
  state.nodePath,
3031
- [(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"],
3032
3888
  "activated database verification",
3033
3889
  { timeoutMs: Math.max(100, deadline - Date.now()) }
3034
3890
  );
@@ -3044,8 +3900,8 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
3044
3900
  let problem = "no sampling marker";
3045
3901
  while (Date.now() < deadline) {
3046
3902
  try {
3047
- const marker = readJsonObject((0, import_node_path4.join)(paths.stateDir, "collector/health.json"));
3048
- 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"));
3049
3905
  if (marker.pid !== lease.pid || !Number.isSafeInteger(marker.pid)) throw new Error("sampling marker belongs to another process");
3050
3906
  process.kill(Number(marker.pid), 0);
3051
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");
@@ -3053,32 +3909,35 @@ async function verifySystemHealth(paths, state, host, timeoutMs, components = en
3053
3909
  } catch (error) {
3054
3910
  problem = error instanceof Error ? error.message : String(error);
3055
3911
  }
3056
- await new Promise((resolve4) => setTimeout(resolve4, 100));
3912
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
3057
3913
  }
3058
3914
  throw new SystemError("collector-unready", `system collector did not sample: ${problem}`);
3059
3915
  }
3060
3916
  }
3061
3917
  function chownAccount(paths, owner, group, host) {
3062
3918
  for (const path of paths) {
3063
- if (!(0, import_node_fs3.existsSync)(path)) continue;
3919
+ if (!(0, import_node_fs6.existsSync)(path)) continue;
3064
3920
  runRequired(host, "chown", ["-R", `${owner}:${group}`, path], `ownership ${path}`);
3065
3921
  }
3066
3922
  }
3067
3923
  function chmodTree(path, host) {
3068
- if (!(0, import_node_fs3.existsSync)(path)) return;
3924
+ if (!(0, import_node_fs6.existsSync)(path)) return;
3069
3925
  runRequired(host, "chmod", ["-R", "u=rwX,go=rX", path], `release permissions ${path}`);
3070
3926
  }
3071
3927
  function pickRollbackBackup(state, previousSchema) {
3072
3928
  const backups = Object.values(state.backups).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
3073
3929
  if (previousSchema === null) return null;
3074
- 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;
3075
3931
  }
3076
3932
  async function recoverFailedUpgrade(options) {
3077
3933
  stopUnits(options.host, options.units);
3078
3934
  assertDirectoryStopped(options.paths.stateDir);
3079
- replaceSymlink((0, import_node_path4.join)(options.paths.prefix, "current"), options.oldCurrent, options.host);
3080
- if (options.oldPrevious === null) removeSymlink((0, import_node_path4.join)(options.paths.prefix, "previous"));
3081
- 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);
3082
3941
  const database = hubDatabasePath(options.paths);
3083
3942
  let afterSchema = null;
3084
3943
  try {
@@ -3086,13 +3945,13 @@ async function recoverFailedUpgrade(options) {
3086
3945
  } catch {
3087
3946
  afterSchema = null;
3088
3947
  }
3089
- if (options.backupPath !== null && (0, import_node_fs3.existsSync)(options.backupPath) && (afterSchema === null || options.beforeSchema === null || afterSchema > options.beforeSchema)) {
3090
- 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);
3091
3950
  replaceDatabaseFromBackup(options.backupPath, database, null, options.host);
3092
3951
  runRequired(options.host, "chown", [`${options.state.accounts.hub}:${options.state.accounts.hub}`, database], "restored database ownership");
3093
3952
  }
3094
- options.state.previousVersion = options.oldPrevious === null ? null : (0, import_node_path4.basename)(options.oldPrevious);
3095
- 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);
3096
3955
  writeSystemState(options.paths, options.state);
3097
3956
  if (options.activeComponents.length > 0) {
3098
3957
  startUnits(options.host, options.units);
@@ -3101,14 +3960,14 @@ async function recoverFailedUpgrade(options) {
3101
3960
  }
3102
3961
  function assertManagedFileOwnership(paths, path) {
3103
3962
  if (!isDefaultSystemLayout(paths)) return;
3104
- if (!(0, import_node_fs3.existsSync)(path)) return;
3105
- 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);
3106
3965
  if (stat.isSymbolicLink() || !stat.isFile()) throw new SystemError("unsafe-path", `${path} must be a regular managed file`);
3107
3966
  if (stat.uid !== 0) throw new SystemError("insecure-ownership", `${path} must be root-owned before replacement`);
3108
3967
  if ((stat.mode & 18) !== 0) throw new SystemError("insecure-permissions", `${path} must not be group/world-writable`);
3109
3968
  }
3110
3969
  function writeUnitFile(paths, unit, content, replace) {
3111
- const path = (0, import_node_path4.join)(paths.unitsDir, unit);
3970
+ const path = (0, import_node_path6.join)(paths.unitsDir, unit);
3112
3971
  assertManagedFileOwnership(paths, path);
3113
3972
  writeManagedFile(path, content, 420, replace);
3114
3973
  }
@@ -3123,19 +3982,19 @@ function ensureComponentSetup(options) {
3123
3982
  ensureDirectory(paths.configDir, 493);
3124
3983
  ensureDirectory(paths.stateDir, 493);
3125
3984
  ensureDirectory(paths.unitsDir, 493);
3126
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
3127
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub", "run"), 448, true);
3128
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
3129
- 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);
3130
3989
  const release = currentReleaseOrThrow(paths);
3131
3990
  void release;
3132
3991
  writeSysusersFiles(paths, accounts, "local", power, host);
3133
3992
  if (component === "hub") {
3134
- if (!(0, import_node_fs3.existsSync)(hubEnvPath(paths))) {
3993
+ if (!(0, import_node_fs6.existsSync)(hubEnvPath(paths))) {
3135
3994
  const env = hubEnvTemplate(paths);
3136
3995
  writeManagedFile(hubEnvPath(paths), env, 384, false);
3137
3996
  }
3138
- 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))) {
3139
3998
  const env = readHubEnvironment(paths);
3140
3999
  const remote = env.HUB_PUBLIC_ORIGIN !== void 0 && env.HUB_PUBLIC_ORIGIN !== "";
3141
4000
  const unit = renderSystemHubUnit({
@@ -3152,7 +4011,7 @@ function ensureComponentSetup(options) {
3152
4011
  `, false);
3153
4012
  }
3154
4013
  } else {
3155
- if (!(0, import_node_fs3.existsSync)(collectorEnvPath(paths))) {
4014
+ if (!(0, import_node_fs6.existsSync)(collectorEnvPath(paths))) {
3156
4015
  writeManagedFile(collectorEnvPath(paths), collectorEnvTemplate(), 384, false);
3157
4016
  }
3158
4017
  const unit = renderSystemCollectorUnit({
@@ -3168,17 +4027,17 @@ function ensureComponentSetup(options) {
3168
4027
  const validation = runRequired(
3169
4028
  host,
3170
4029
  "systemd-analyze",
3171
- ["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))],
3172
4031
  "native systemd unit validation"
3173
4032
  );
3174
4033
  if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
3175
4034
  throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
3176
4035
  }
3177
- if (component === "hub") chownAccount([(0, import_node_path4.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
3178
- 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);
3179
4038
  }
3180
4039
  function ensurePowerAccess(paths, accounts, host) {
3181
- if (!(0, import_node_fs3.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
4040
+ if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
3182
4041
  writePowerRule(paths, accounts);
3183
4042
  resumePowerRule(paths, host);
3184
4043
  applyPowerAccess(paths, accounts, host);
@@ -3196,7 +4055,7 @@ function resetUnfinishedSystemState(options) {
3196
4055
  }
3197
4056
  function installPreset(options, role) {
3198
4057
  if (options.adoptFrom !== void 0) {
3199
- return componentSet(readAdoptionSetup((0, import_node_path4.resolve)(options.adoptFrom)).components);
4058
+ return componentSet(readAdoptionSetup((0, import_node_path6.resolve)(options.adoptFrom)).components);
3200
4059
  }
3201
4060
  return componentsForRole(role);
3202
4061
  }
@@ -3208,11 +4067,11 @@ function planSystemInstall(options) {
3208
4067
  const power = options.power !== false && preset.collector;
3209
4068
  const initial = options.enable === true ? preset : { hub: false, collector: false };
3210
4069
  const files = [
3211
- { 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" },
3212
4071
  { path: hubEnvPath(paths), mode: "0600", purpose: "root-owned hub configuration" },
3213
4072
  { path: collectorEnvPath(paths), mode: "0600", purpose: "root-owned collector configuration" },
3214
4073
  { path: sysusersPath(paths), mode: "0644", purpose: "dedicated nologin accounts" },
3215
- ...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` }))
3216
4075
  ];
3217
4076
  if (power) {
3218
4077
  files.push({ path: raplRulePath(paths), mode: "0644", purpose: "package RAPL read rule" });
@@ -3227,7 +4086,7 @@ function planSystemInstall(options) {
3227
4086
  ];
3228
4087
  const notes = [
3229
4088
  "Nothing is changed without --apply and root.",
3230
- 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.",
3231
4090
  "One complete package: both runtimes, units and accounts are installed; --role only sets the initial enabled preset.",
3232
4091
  "Hub and collector use separate unprivileged accounts; Node gets no capabilities.",
3233
4092
  ...options.enable && preset.hub ? ["Enabling the hub creates its owner account once and prints the password once."] : [],
@@ -3260,8 +4119,8 @@ async function installSystemUnlocked(options) {
3260
4119
  `existing user service(s) detected (${detail}); stop them and use --adopt-user-state for explicit copy-based adoption`
3261
4120
  );
3262
4121
  }
3263
- const source = (0, import_node_path4.resolve)(options.source);
3264
- 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}`);
3265
4124
  const port = options.port ?? 8081;
3266
4125
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new SystemError("invalid-argument", "--port must be 1-65535");
3267
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) });
@@ -3282,14 +4141,14 @@ async function installSystemUnlocked(options) {
3282
4141
  });
3283
4142
  try {
3284
4143
  if (legacy) resetUnfinishedSystemState({ paths, host, now });
3285
- ensureDirectory((0, import_node_path4.join)(paths.prefix, "releases"), 493);
4144
+ ensureDirectory((0, import_node_path6.join)(paths.prefix, "releases"), 493);
3286
4145
  ensureDirectory(paths.stateDir, 493);
3287
4146
  ensureDirectory(paths.configDir, 493);
3288
4147
  ensureDirectory(paths.unitsDir, 493);
3289
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "hub"), 448, true);
3290
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "collector"), 448, true);
3291
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
3292
- 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);
3293
4152
  const release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
3294
4153
  let adoption = null;
3295
4154
  if (options.adoptFrom) {
@@ -3322,13 +4181,13 @@ async function installSystemUnlocked(options) {
3322
4181
  `, true);
3323
4182
  writeUnitFile(paths, UNIT_NAMES.collector, `${collectorUnit}
3324
4183
  `, true);
3325
- 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");
3326
4185
  if (/path is not absolute|bad unit file|fatal error|unknown (?:key|lvalue)|ignoring/i.test(validation.stderr)) {
3327
4186
  throw new SystemError("invalid-unit", "native systemd validation rejected a generated directive");
3328
4187
  }
3329
4188
  if (power) ensurePowerAccess(paths, accounts, host);
3330
- chownAccount([(0, import_node_path4.join)(paths.stateDir, "hub")], accounts.hub, accounts.hub, host);
3331
- 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);
3332
4191
  const state = emptyState({ paths, accounts, role, components: { hub: false, collector: false }, nodePath, now });
3333
4192
  state.currentVersion = release.version;
3334
4193
  state.releases[release.version] = {
@@ -3338,7 +4197,7 @@ async function installSystemUnlocked(options) {
3338
4197
  };
3339
4198
  state.adoption = adoption?.record ?? null;
3340
4199
  state.power = power ? { enabled: true, rulePath: raplRulePath(paths), group: accounts.powerGroup } : null;
3341
- 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);
3342
4201
  writeSystemState(paths, state);
3343
4202
  const wantsHub = preset.hub && (options.enable === true || options.start === true);
3344
4203
  const wantsCollector = preset.collector && (options.enable === true || options.start === true);
@@ -3364,6 +4223,7 @@ async function installSystemUnlocked(options) {
3364
4223
  await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, started);
3365
4224
  }
3366
4225
  }
4226
+ recordMachineDataTransition(paths.stateDir, release.version, (path, content) => atomicWrite(path, content, 384));
3367
4227
  return {
3368
4228
  operation: "install",
3369
4229
  ok: true,
@@ -3392,14 +4252,14 @@ function systemStatus(options = {}) {
3392
4252
  } catch (error) {
3393
4253
  stateError = error instanceof Error ? error.message : String(error);
3394
4254
  }
3395
- 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)));
3396
4256
  const publicRole = presentUnits.length === 2 ? "local" : presentUnits[0] === UNIT_NAMES.hub ? "hub" : presentUnits[0] === UNIT_NAMES.collector ? "collector" : null;
3397
4257
  const role = state?.role ?? publicRole ?? "local";
3398
4258
  const current = currentReleaseDir(paths);
3399
4259
  let publicVersion = null;
3400
4260
  if (current) {
3401
4261
  try {
3402
- 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"));
3403
4263
  if (typeof manifest.version === "string") publicVersion = manifest.version;
3404
4264
  } catch {
3405
4265
  }
@@ -3410,7 +4270,7 @@ function systemStatus(options = {}) {
3410
4270
  const componentReports = {};
3411
4271
  for (const component of COMPONENTS) {
3412
4272
  const unit = unitFor(component);
3413
- 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));
3414
4274
  const active = present && systemdActive(host, unit);
3415
4275
  const unitEnabled = present && systemdEnabled(host, unit);
3416
4276
  const enabled = state !== null ? state.components[component].enabled : unitEnabled;
@@ -3419,7 +4279,7 @@ function systemStatus(options = {}) {
3419
4279
  componentReports[component] = {
3420
4280
  component,
3421
4281
  unit,
3422
- path: (0, import_node_path4.join)(paths.unitsDir, unit),
4282
+ path: (0, import_node_path6.join)(paths.unitsDir, unit),
3423
4283
  present,
3424
4284
  enabled,
3425
4285
  unitEnabled,
@@ -3463,16 +4323,16 @@ function systemStatus(options = {}) {
3463
4323
  })),
3464
4324
  database: {
3465
4325
  path: database,
3466
- exists: (0, import_node_fs3.existsSync)(database),
4326
+ exists: (0, import_node_fs6.existsSync)(database),
3467
4327
  schema,
3468
- 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,
3469
4329
  lock
3470
4330
  },
3471
- 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)) },
3472
4332
  config: {
3473
4333
  dir: paths.configDir,
3474
- hubEnv: { path: hubEnvPath(paths), present: (0, import_node_fs3.existsSync)(hubEnvPath(paths)) },
3475
- 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)) }
3476
4336
  },
3477
4337
  adoption: state?.adoption ?? null,
3478
4338
  power,
@@ -3641,7 +4501,7 @@ async function enableSystem(options) {
3641
4501
  ],
3642
4502
  files: [
3643
4503
  { path: component === "hub" ? hubEnvPath(paths) : collectorEnvPath(paths), mode: "0600", purpose: `${component} configuration` },
3644
- { 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` }
3645
4505
  ],
3646
4506
  notes: [
3647
4507
  "Enables only the requested component; the other component keeps running.",
@@ -3709,7 +4569,7 @@ function disableSystem(options) {
3709
4569
  accounts: state.accounts,
3710
4570
  commands: [`systemctl stop ${unitFor(component)}`, `systemctl disable ${unitFor(component)}`],
3711
4571
  files: [],
3712
- 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."]
3713
4573
  };
3714
4574
  if (options.apply !== true) return { operation: "disable", ok: true, apply: false, message: "plan only", plan };
3715
4575
  assertApply(host);
@@ -3731,7 +4591,7 @@ function disableSystem(options) {
3731
4591
  function readPrivateToken(path, read) {
3732
4592
  let stat;
3733
4593
  try {
3734
- stat = (0, import_node_fs3.lstatSync)(path);
4594
+ stat = (0, import_node_fs6.lstatSync)(path);
3735
4595
  } catch (error) {
3736
4596
  if (error.code === "ENOENT") {
3737
4597
  throw new SystemError("missing-grant", `grant file does not exist: ${path}`);
@@ -3742,7 +4602,7 @@ function readPrivateToken(path, read) {
3742
4602
  if ((stat.mode & 63) !== 0) throw new SystemError("insecure-permissions", `grant file must be private (0600): ${path}`);
3743
4603
  if (stat.size < 1 || stat.size > 4096) throw new SystemError("invalid-token", `grant file must contain 1-4096 bytes: ${path}`);
3744
4604
  if (!read) return void 0;
3745
- const token = (0, import_node_fs3.readFileSync)(path, "utf8").trim();
4605
+ const token = (0, import_node_fs6.readFileSync)(path, "utf8").trim();
3746
4606
  if (token === "" || /[\r\n\0]/.test(token) || !/^[\x21-\x7e]{1,256}$/.test(token)) {
3747
4607
  throw new SystemError("invalid-token", "grant file contains an invalid token");
3748
4608
  }
@@ -3754,7 +4614,7 @@ async function connectSystem(options) {
3754
4614
  const state = readSystemState(paths);
3755
4615
  if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
3756
4616
  const destination = connectDestinationUrl(options.url);
3757
- const tokenFile = (0, import_node_path4.resolve)(options.tokenFile);
4617
+ const tokenFile = (0, import_node_path6.resolve)(options.tokenFile);
3758
4618
  const configured = collectorConfigured(paths);
3759
4619
  if (configured.configured) {
3760
4620
  throw new SystemError(
@@ -3802,12 +4662,12 @@ async function connectSystem(options) {
3802
4662
  `collector enrollment failed: ${error instanceof Error ? error.message : String(error)}`
3803
4663
  );
3804
4664
  } finally {
3805
- 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);
3806
4666
  }
3807
4667
  if (!collectorConfigured(paths).configured) {
3808
4668
  throw new SystemError("enrollment-failed", "the hub accepted enrollment but no destination was saved; check private state");
3809
4669
  }
3810
- 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);
3811
4671
  writeSystemState(paths, state);
3812
4672
  const enabled = state.components.collector.enabled;
3813
4673
  if (enabled || wasActive) {
@@ -3824,13 +4684,16 @@ async function connectSystem(options) {
3824
4684
  state
3825
4685
  };
3826
4686
  }
4687
+ function systemMachineDataPaths(paths) {
4688
+ return { database: hubDatabasePath(paths), queueTree: (0, import_node_path6.join)(paths.stateDir, "collector", "outbox") };
4689
+ }
3827
4690
  async function upgradeSystemUnlocked(options) {
3828
4691
  const host = options.host ?? createSystemHost();
3829
4692
  const now = options.now ?? Date.now;
3830
4693
  const paths = validateSystemPaths(options.paths);
3831
4694
  if (systemStateIsLegacy(paths)) {
3832
- const source2 = (0, import_node_path4.resolve)(options.source);
3833
- 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}`);
3834
4697
  const role = legacySystemRole(paths);
3835
4698
  const preset = componentsForRole(role);
3836
4699
  const enable = options.enable ?? (options.start === true || unitNamesForComponents(preset).some((unit) => systemdEnabled(host, unit)));
@@ -3853,7 +4716,7 @@ async function upgradeSystemUnlocked(options) {
3853
4716
  ],
3854
4717
  files: [
3855
4718
  { path: stateFilePath(paths), mode: "0600", purpose: "canonical replacement state" },
3856
- { 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" }
3857
4720
  ],
3858
4721
  notes: [
3859
4722
  "An unfinished installation is reset, not migrated; its history and credentials are removed.",
@@ -3886,6 +4749,7 @@ async function upgradeSystemUnlocked(options) {
3886
4749
  if (state === null || !state.active) throw new SystemError("not-installed", "no active system installation; run install first");
3887
4750
  const activeComponents = COMPONENTS.filter((component) => systemdActive(host, unitFor(component)));
3888
4751
  const activeUnits = activeComponents.map(unitFor);
4752
+ const transitionPending = readMachineDataTransition(paths.stateDir) === null;
3889
4753
  const plan = {
3890
4754
  operation: "upgrade",
3891
4755
  apply: options.apply === true,
@@ -3893,19 +4757,34 @@ async function upgradeSystemUnlocked(options) {
3893
4757
  components: { hub: state.components.hub.enabled, collector: state.components.collector.enabled },
3894
4758
  paths,
3895
4759
  accounts: state.accounts,
3896
- commands: ["stage new release", ...activeUnits.map((unit) => `systemctl stop ${unit}`), "backup database", "switch current", ...activeUnits.map((unit) => `systemctl start ${unit}`)],
3897
- 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
+ ],
3898
4772
  notes: [
3899
4773
  "Staging happens before any service is stopped.",
3900
4774
  "The exact active component set is restored; --start does not start a component that was stopped.",
3901
- "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."]
3902
4781
  ]
3903
4782
  };
3904
4783
  if (options.apply !== true) return { operation: "upgrade", ok: true, apply: false, message: "plan only", plan };
3905
4784
  assertApply(host);
3906
4785
  assertManagedPathSafety(paths, isDefaultSystemLayout(paths));
3907
- const source = (0, import_node_path4.resolve)(options.source);
3908
- 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}`);
3909
4788
  const staged = prepareStagedRelease({
3910
4789
  source,
3911
4790
  paths,
@@ -3924,21 +4803,22 @@ async function upgradeSystemUnlocked(options) {
3924
4803
  let backupPath = null;
3925
4804
  let beforeSchema = null;
3926
4805
  let release = null;
4806
+ let machineData = null;
3927
4807
  try {
3928
4808
  stopUnits(host, activeUnits);
3929
4809
  assertDirectoryStopped(paths.stateDir);
3930
4810
  const lock = systemLockState(paths);
3931
4811
  if (lock.live) throw new SystemError("duplicate-runtime", `hub did not stop cleanly; lock remains at ${lock.path}`);
3932
4812
  const database = hubDatabasePath(paths);
3933
- if ((0, import_node_fs3.existsSync)(database)) {
3934
- 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);
3935
4815
  beforeSchema = databaseSchema(database);
3936
- 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`);
3937
4817
  await backupDatabase(database, backupPath, host);
3938
4818
  }
3939
4819
  release = commitStagedRelease({ staged, paths, replaceRelease: options.replaceRelease });
3940
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), oldCurrent, host);
3941
- 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);
3942
4822
  state.previousVersion = state.currentVersion;
3943
4823
  state.currentVersion = release.version;
3944
4824
  state.releases[release.version] = {
@@ -3947,7 +4827,7 @@ async function upgradeSystemUnlocked(options) {
3947
4827
  installedAt: new Date(now()).toISOString()
3948
4828
  };
3949
4829
  if (backupPath !== null) {
3950
- state.backups[(0, import_node_path4.basename)(backupPath)] = {
4830
+ state.backups[(0, import_node_path6.basename)(backupPath)] = {
3951
4831
  path: backupPath,
3952
4832
  schemaVersion: beforeSchema,
3953
4833
  fromSchema: beforeSchema,
@@ -3958,6 +4838,7 @@ async function upgradeSystemUnlocked(options) {
3958
4838
  };
3959
4839
  }
3960
4840
  writeSystemState(paths, state);
4841
+ machineData = performMachineDataCleanup(paths.stateDir, systemMachineDataPaths(paths), now);
3961
4842
  if (activeComponents.length > 0) {
3962
4843
  startUnits(host, activeUnits);
3963
4844
  await verifySystemHealth(paths, state, host, options.startupTimeoutMs ?? 2e4, activeComponents);
@@ -3970,6 +4851,7 @@ async function upgradeSystemUnlocked(options) {
3970
4851
  oldPrevious,
3971
4852
  beforeSchema,
3972
4853
  backupPath,
4854
+ machineData,
3973
4855
  host,
3974
4856
  activeComponents,
3975
4857
  units: activeUnits
@@ -3977,6 +4859,10 @@ async function upgradeSystemUnlocked(options) {
3977
4859
  throw error;
3978
4860
  }
3979
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;
3980
4866
  return {
3981
4867
  operation: "upgrade",
3982
4868
  ok: true,
@@ -3986,6 +4872,7 @@ async function upgradeSystemUnlocked(options) {
3986
4872
  current: upgraded.releaseDir,
3987
4873
  previous: oldCurrent,
3988
4874
  backupPath,
4875
+ ...machineHistory ? { machineHistory } : {},
3989
4876
  state
3990
4877
  };
3991
4878
  } finally {
@@ -3993,7 +4880,7 @@ async function upgradeSystemUnlocked(options) {
3993
4880
  }
3994
4881
  }
3995
4882
  function systemOperationLockPath(paths) {
3996
- return (0, import_node_path4.join)(paths.prefix, "run", "operation.lock");
4883
+ return (0, import_node_path6.join)(paths.prefix, "run", "operation.lock");
3997
4884
  }
3998
4885
  async function withSystemOperation(options, operation) {
3999
4886
  if (!options.apply) return operation();
@@ -4001,7 +4888,7 @@ async function withSystemOperation(options, operation) {
4001
4888
  assertApply(host);
4002
4889
  const paths = validateSystemPaths(options.paths);
4003
4890
  ensureDirectory(paths.prefix, 493);
4004
- ensureDirectory((0, import_node_path4.join)(paths.prefix, "run"), 448, true);
4891
+ ensureDirectory((0, import_node_path6.join)(paths.prefix, "run"), 448, true);
4005
4892
  const release = acquireProcessLease(systemOperationLockPath(paths));
4006
4893
  try {
4007
4894
  return await operation();
@@ -4053,24 +4940,24 @@ async function rollbackSystemUnlocked(options = {}) {
4053
4940
  try {
4054
4941
  stopUnits(host, units);
4055
4942
  assertDirectoryStopped(paths.stateDir);
4056
- 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) {
4057
4944
  throw new SystemError("runtime-active", "runtime lease remains after stop; rollback refused");
4058
4945
  }
4059
4946
  const database = hubDatabasePath(paths);
4060
- const currentSchema = (0, import_node_fs3.existsSync)(database) ? databaseSchema(database) : null;
4947
+ const currentSchema = (0, import_node_fs6.existsSync)(database) ? databaseSchema(database) : null;
4061
4948
  let backupToRestore = null;
4062
4949
  if (currentSchema !== null && (previousSchema === null || currentSchema > previousSchema)) {
4063
4950
  backupToRestore = pickRollbackBackup(state, previousSchema);
4064
- if (backupToRestore === null || !(0, import_node_fs3.existsSync)(backupToRestore.path)) {
4951
+ if (backupToRestore === null || !(0, import_node_fs6.existsSync)(backupToRestore.path)) {
4065
4952
  throw new SystemError(
4066
4953
  "incompatible-schema",
4067
4954
  `current database schema ${currentSchema} is newer than release ${previousVersion}; no matching pre-upgrade backup is recorded`
4068
4955
  );
4069
4956
  }
4070
4957
  }
4071
- if ((0, import_node_fs3.existsSync)(database)) {
4072
- ensureDirectory((0, import_node_path4.join)(paths.stateDir, "backups"), 448, true);
4073
- 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`);
4074
4961
  await backupDatabase(database, safetyBackup, host);
4075
4962
  }
4076
4963
  if (backupToRestore !== null) {
@@ -4078,8 +4965,8 @@ async function rollbackSystemUnlocked(options = {}) {
4078
4965
  restoredDatabase = true;
4079
4966
  runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, database], "restored database ownership");
4080
4967
  }
4081
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "previous"), oldCurrent, host);
4082
- 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);
4083
4970
  state.previousVersion = state.currentVersion;
4084
4971
  state.currentVersion = previousVersion;
4085
4972
  state.updatedAt = new Date(now()).toISOString();
@@ -4101,10 +4988,10 @@ async function rollbackSystemUnlocked(options = {}) {
4101
4988
  };
4102
4989
  } catch (error) {
4103
4990
  stopUnits(host, units);
4104
- replaceSymlink((0, import_node_path4.join)(paths.prefix, "current"), oldCurrent, host);
4105
- 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);
4106
4993
  if (restoredDatabase && safetyBackup) {
4107
- 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`);
4108
4995
  await backupDatabase(hubDatabasePath(paths), failed, host);
4109
4996
  replaceDatabaseFromBackup(safetyBackup, hubDatabasePath(paths), null, host);
4110
4997
  runRequired(host, "chown", [`${state.accounts.hub}:${state.accounts.hub}`, hubDatabasePath(paths)], "restored database ownership");
@@ -4161,31 +5048,31 @@ function uninstallSystem(options = {}) {
4161
5048
  assertDirectoryStopped(paths.stateDir);
4162
5049
  for (const unit of units) runMaybe(host, "systemctl", ["disable", unit]);
4163
5050
  for (const unit of units) {
4164
- const path = (0, import_node_path4.join)(paths.unitsDir, unit);
5051
+ const path = (0, import_node_path6.join)(paths.unitsDir, unit);
4165
5052
  assertManagedFileOwnership(paths, path);
4166
5053
  try {
4167
- (0, import_node_fs3.unlinkSync)(path);
5054
+ (0, import_node_fs6.unlinkSync)(path);
4168
5055
  } catch {
4169
5056
  }
4170
5057
  }
4171
5058
  runMaybe(host, "systemctl", ["daemon-reload"]);
4172
- 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));
4173
5060
  if (hadPowerAccess) {
4174
5061
  try {
4175
- (0, import_node_fs3.unlinkSync)(raplRulePath(paths));
5062
+ (0, import_node_fs6.unlinkSync)(raplRulePath(paths));
4176
5063
  } catch {
4177
5064
  }
4178
5065
  try {
4179
- (0, import_node_fs3.unlinkSync)(powerSysusersPath(paths));
5066
+ (0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
4180
5067
  } catch {
4181
5068
  }
4182
5069
  restorePowerOriginals(paths, host);
4183
5070
  resumePowerRule(paths, host);
4184
5071
  }
4185
- removeSymlink((0, import_node_path4.join)(paths.prefix, "current"));
4186
- removeSymlink((0, import_node_path4.join)(paths.prefix, "previous"));
4187
- (0, import_node_fs3.rmSync)((0, import_node_path4.join)(paths.prefix, "releases"), { recursive: true, force: true });
4188
- 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 });
4189
5076
  if (state !== null) {
4190
5077
  state.active = false;
4191
5078
  state.power = null;
@@ -4198,8 +5085,8 @@ function uninstallSystem(options = {}) {
4198
5085
  for (const group of [accounts.powerGroup, accounts.hub, accounts.collector]) runMaybe(host, "groupdel", [group]);
4199
5086
  }
4200
5087
  if (options.purgeData === true) {
4201
- (0, import_node_fs3.rmSync)(paths.stateDir, { recursive: true, force: true });
4202
- (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 });
4203
5090
  }
4204
5091
  return {
4205
5092
  operation: "uninstall",
@@ -4249,7 +5136,7 @@ function grantPower(options = {}) {
4249
5136
  writeManagedFile(powerSysusersPath(paths), renderPowerSysusers(paths, accounts), 420, true);
4250
5137
  runRequired(host, "systemd-sysusers", [], "power group creation");
4251
5138
  assertSystemAccounts(host, accounts, true, ["collector"]);
4252
- if (!(0, import_node_fs3.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
5139
+ if (!(0, import_node_fs6.existsSync)(powerOriginalPath(paths))) capturePowerOriginals(paths);
4253
5140
  writePowerRule(paths, accounts);
4254
5141
  resumePowerRule(paths, host);
4255
5142
  applyPowerAccess(paths, accounts, host);
@@ -4278,11 +5165,11 @@ function revokePower(options = {}) {
4278
5165
  if (options.apply !== true) return { operation: "power-revoke", ok: true, apply: false, message: "plan only", plan };
4279
5166
  assertApply(host);
4280
5167
  try {
4281
- (0, import_node_fs3.unlinkSync)(raplRulePath(paths));
5168
+ (0, import_node_fs6.unlinkSync)(raplRulePath(paths));
4282
5169
  } catch {
4283
5170
  }
4284
5171
  try {
4285
- (0, import_node_fs3.unlinkSync)(powerSysusersPath(paths));
5172
+ (0, import_node_fs6.unlinkSync)(powerSysusersPath(paths));
4286
5173
  } catch {
4287
5174
  }
4288
5175
  restorePowerOriginals(paths, host);
@@ -4498,6 +5385,12 @@ function printMutationReport(report) {
4498
5385
  console.log("then run `rigscope system connect https://<hub> --token-file <file> --apply`.");
4499
5386
  }
4500
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
+ }
4501
5394
  async function runSystemCommand(argv) {
4502
5395
  const parsed = parseSystemArgs(argv);
4503
5396
  if (parsed.command === "help") {
@@ -4569,7 +5462,7 @@ ${humanPlan(report.plan)}`);
4569
5462
  if (parsed.command === "logs") {
4570
5463
  const selected = validateSystemPaths(paths);
4571
5464
  const component = componentFrom(parsed, false);
4572
- 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)));
4573
5466
  if (units.length === 0) throw new SystemError("not-installed", component ? `${component} logs are unavailable; its unit is not installed` : "no system services installed");
4574
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" });
4575
5468
  if (result.error) throw result.error;
@@ -4645,6 +5538,7 @@ ${humanPlan(report.plan)}`);
4645
5538
  else {
4646
5539
  printMutationReport(report);
4647
5540
  console.log(`Backup: ${report.backupPath ?? "none"}`);
5541
+ printMachineDataOutcome(report.machineHistory);
4648
5542
  }
4649
5543
  return 0;
4650
5544
  }