rigscope 0.1.2 → 0.1.4

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 (92) 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_client-reference-manifest.js +1 -1
  29. package/.next/server/app/connections.html +1 -1
  30. package/.next/server/app/connections.rsc +2 -2
  31. package/.next/server/app/connections.segments/_full.segment.rsc +2 -2
  32. package/.next/server/app/connections.segments/_tree.segment.rsc +1 -1
  33. package/.next/server/app/connections.segments/connections/__PAGE__.segment.rsc +2 -2
  34. package/.next/server/app/history/page.js +1 -1
  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 +4 -4
  38. package/.next/server/app/history.segments/_full.segment.rsc +4 -4
  39. package/.next/server/app/history.segments/_tree.segment.rsc +1 -1
  40. package/.next/server/app/history.segments/history/__PAGE__.segment.rsc +4 -4
  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.js +1 -1
  53. package/.next/server/app/machines/[machineId]/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/processes/page.js +1 -1
  56. package/.next/server/app/processes/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/processes.html +1 -1
  58. package/.next/server/app/processes.rsc +3 -3
  59. package/.next/server/app/processes.segments/_full.segment.rsc +3 -3
  60. package/.next/server/app/processes.segments/_tree.segment.rsc +1 -1
  61. package/.next/server/app/processes.segments/processes/__PAGE__.segment.rsc +3 -3
  62. package/.next/server/app-paths-manifest.json +2 -2
  63. package/.next/server/chunks/161.js +1 -1
  64. package/.next/server/chunks/168.js +1 -1
  65. package/.next/server/middleware-build-manifest.js +1 -1
  66. package/.next/server/pages/404.html +1 -1
  67. package/.next/server/pages/500.html +1 -1
  68. package/.next/static/chunks/408-29f55984738967db.js +1 -0
  69. package/.next/static/chunks/939-514e554087419c71.js +1 -0
  70. package/.next/static/chunks/app/history/{page-22be927a4c3a9c76.js → page-69ea64476ee5db85.js} +1 -1
  71. package/.next/static/chunks/app/machines/[machineId]/page-1a25a267563c18c0.js +1 -0
  72. package/.next/static/chunks/app/processes/{page-e471ed23a1d5632f.js → page-0e98a4a64e829c5f.js} +1 -1
  73. package/ARCHITECTURE.md +30 -4
  74. package/CHANGELOG.md +53 -2
  75. package/README.md +2 -0
  76. package/dist/cli.cjs +3575 -2555
  77. package/dist/collector.cjs +155 -232
  78. package/dist/hub.cjs +5 -18
  79. package/dist/ops.cjs +158 -31
  80. package/dist/system.cjs +869 -450
  81. package/docs/bottlenecks.md +4 -6
  82. package/docs/configuration.md +2 -0
  83. package/docs/installation.md +107 -23
  84. package/docs/operations.md +44 -22
  85. package/docs/releasing.md +21 -17
  86. package/docs/system-installation.md +11 -2
  87. package/package.json +1 -2
  88. package/.next/static/chunks/408-4fe7147e38981ee1.js +0 -1
  89. package/.next/static/chunks/939-2600d7c8e583d5ef.js +0 -1
  90. package/.next/static/chunks/app/machines/[machineId]/page-916ee73de5eb067d.js +0 -1
  91. /package/.next/static/{npHoP6NSY976Yaye-xi86 → -mNveXe-cHv8irQe97wI_}/_buildManifest.js +0 -0
  92. /package/.next/static/{npHoP6NSY976Yaye-xi86 → -mNveXe-cHv8irQe97wI_}/_ssgManifest.js +0 -0
@@ -128,7 +128,6 @@ var ConfigError = class extends Error {
128
128
  var DEFAULT_COLLECTOR_ID = "mock-collector";
129
129
  var MOCK_HOSTNAME = "mock-machine";
130
130
  var CONNECT_PATH = "/api/collector/connect";
131
- var PREVIOUS_CONNECT_PATH = "/api/collector/v1/connect";
132
131
  var MAX_DESTINATIONS = 8;
133
132
  var DEFAULT_INTERVAL_MS = 1e3;
134
133
  var MIN_INTERVAL_MS = 100;
@@ -205,7 +204,6 @@ function validateHubUrl(raw, label, issues) {
205
204
  issues.push(`${label}: URL must include a host`);
206
205
  return null;
207
206
  }
208
- if (parsed.pathname === PREVIOUS_CONNECT_PATH) parsed.pathname = CONNECT_PATH;
209
207
  if (parsed.pathname !== CONNECT_PATH) {
210
208
  issues.push(`${label}: path must be exactly ${CONNECT_PATH}`);
211
209
  return null;
@@ -520,18 +518,26 @@ function defaultStatePath() {
520
518
  function readState(path2) {
521
519
  const stat = (0, import_node_fs2.lstatSync)(path2);
522
520
  if (!stat.isFile() || stat.size > 64 * 1024 || (stat.mode & 63) !== 0) throw new Error("collector state must be a private regular file");
523
- const raw = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8"));
524
- if (raw.version !== 1 || typeof raw.collectorId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(raw.collectorId) || !Array.isArray(raw.destinations)) throw new Error("invalid collector state");
521
+ const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8"));
522
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid collector state");
523
+ const raw = parsed;
524
+ if (Object.hasOwn(raw, "version") || Object.hasOwn(raw, "formatVersion")) {
525
+ throw new Error("collector state uses an older versioned shape; run rigscope install or upgrade to reset it");
526
+ }
527
+ if (typeof raw.collectorId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(raw.collectorId) || !Array.isArray(raw.destinations)) throw new Error("invalid collector state");
525
528
  const destinations = raw.destinations.length === 0 ? [] : [...parseCollectorConfig({ COLLECTOR_HUBS: JSON.stringify(raw.destinations) }).destinations];
526
- if (raw.pendingEnrollments !== void 0 && (!Array.isArray(raw.pendingEnrollments) || raw.pendingEnrollments.length > 8 || raw.pendingEnrollments.some((p) => !p || typeof p.url !== "string" || typeof p.requestId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(p.requestId) || typeof p.recoverySecret !== "string" || !/^[A-Za-z0-9_-]{32,128}$/.test(p.recoverySecret)))) throw new Error("invalid pending enrollment recovery state");
527
- const pendingEnrollments = raw.pendingEnrollments?.map((pending) => {
529
+ const pending = raw.pendingEnrollments;
530
+ if (pending !== void 0 && (!Array.isArray(pending) || pending.length > 8 || pending.some((p) => !p || typeof p !== "object" || typeof p.url !== "string" || typeof p.requestId !== "string" || !/^[\x21-\x7e]{1,128}$/.test(p.requestId) || typeof p.recoverySecret !== "string" || !/^[A-Za-z0-9_-]{32,128}$/.test(p.recoverySecret)))) {
531
+ throw new Error("invalid pending enrollment recovery state");
532
+ }
533
+ const pendingEnrollments = pending?.map((value) => {
534
+ const pendingEnrollment = value;
528
535
  const issues = [];
529
- const url = validateHubUrl(pending.url, "pending enrollment", issues);
536
+ const url = validateHubUrl(pendingEnrollment.url, "pending enrollment", issues);
530
537
  if (!url || issues.length) throw new ConfigError(issues);
531
- return { ...pending, url: url.href };
538
+ return { ...pendingEnrollment, url: url.href };
532
539
  });
533
540
  return {
534
- version: 1,
535
541
  collectorId: raw.collectorId,
536
542
  destinations,
537
543
  ...pendingEnrollments === void 0 ? {} : { pendingEnrollments }
@@ -564,9 +570,14 @@ function updateInstallation(path2, change = (s) => s) {
564
570
  state = readState(path2);
565
571
  } catch (error) {
566
572
  if (error.code !== "ENOENT") throw error;
567
- state = { version: 1, collectorId: (0, import_node_crypto2.randomUUID)(), destinations: [] };
573
+ state = { collectorId: (0, import_node_crypto2.randomUUID)(), destinations: [] };
568
574
  }
569
- const next = change(state);
575
+ const changed = change(state);
576
+ const next = {
577
+ collectorId: changed.collectorId,
578
+ destinations: changed.destinations,
579
+ ...changed.pendingEnrollments === void 0 ? {} : { pendingEnrollments: changed.pendingEnrollments }
580
+ };
570
581
  temporary = `${path2}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
571
582
  const out = (0, import_node_fs2.openSync)(temporary, "wx", 384);
572
583
  try {
@@ -687,154 +698,6 @@ var import_node_os2 = require("node:os");
687
698
  var import_node_fs5 = require("node:fs");
688
699
  var import_ws = require("ws");
689
700
 
690
- // src/contracts/telemetry.ts
691
- var MAX_MESSAGE_BYTES = 262144;
692
- var MAX_DETAIL_BYTES = 1048576;
693
- var MAX_BATCH_RECORDS = 32;
694
- var MAX_CORES = 1024;
695
- var MAX_GPUS = 16;
696
- var MAX_FILESYSTEMS = 64;
697
- var MAX_DISKS = 64;
698
- var MAX_INTERFACES = 64;
699
- var MAX_ADDRESSES_PER_INTERFACE = 32;
700
- var MAX_PROCESS_ROWS = 6e3;
701
- var MAX_COLLECTION_FAILURES = 32;
702
- var MIN_DETAIL_INTERVAL_MS = 1e3;
703
- var MAX_DETAIL_INTERVAL_MS = 6e4;
704
- var MIN_DETAIL_LEASE_MS = 5e3;
705
- var MAX_DETAIL_LEASE_MS = 12e4;
706
- var MAX_ID = 128;
707
- var MAX_TEXT = 256;
708
- var MIN_EPOCH = Date.parse("2000-01-01T00:00:00Z");
709
- var MAX_EPOCH = Date.parse("2200-01-01T00:00:00Z");
710
- var CAPABILITY_NAMES = ["metrics", "inventory", "processes"];
711
- var METRIC_EXTENSION_NAMES = ["bottleneck-v1"];
712
- var COLLECTOR_PROTOCOL_VERSION = "0.3.0";
713
- function parseHubMessage(value) {
714
- const o = obj(value, "hub message");
715
- switch (o.type) {
716
- case "welcome": {
717
- keys(o, ["type", "hubRunId", "machineId", "delivery", "maxInFlightBatches"], "welcome", ["metricExtensions"]);
718
- if (o.delivery !== "durable") throw new Error(`invalid welcome: unexpected delivery ${desc(o.delivery)}`);
719
- const welcome = { type: "welcome", hubRunId: id(o.hubRunId, "hubRunId"), machineId: id(o.machineId, "machineId"), delivery: "durable", maxInFlightBatches: int(o.maxInFlightBatches, "maxInFlightBatches", 1, 64) };
720
- if (hasOwn(o, "metricExtensions")) {
721
- const extensions = arr(o.metricExtensions, "welcome.metricExtensions", 1, METRIC_EXTENSION_NAMES.length).map((extension) => oneOf(extension, METRIC_EXTENSION_NAMES, "welcome.metricExtensions"));
722
- if (new Set(extensions).size !== extensions.length) throw new Error("invalid welcome: metricExtensions must be unique");
723
- welcome.metricExtensions = extensions;
724
- }
725
- return welcome;
726
- }
727
- case "ack": {
728
- keys(o, ["type", "accepted", "rejected"], "ack", ["retryAfterMs"]);
729
- const accepted = arr(o.accepted, "ack.accepted", 0, MAX_BATCH_RECORDS * 64).map((r) => parseRef(r, []));
730
- const rejected = arr(o.rejected, "ack.rejected", 0, MAX_BATCH_RECORDS * 64).map((r) => {
731
- const ref = parseRef(r, ["reason"]);
732
- return { ...ref, reason: text(r.reason, "reason", 0, MAX_TEXT) };
733
- });
734
- const out = { type: "ack", accepted, rejected };
735
- if (hasOwn(o, "retryAfterMs")) out.retryAfterMs = int(o.retryAfterMs, "retryAfterMs", 0, 6e5);
736
- return out;
737
- }
738
- case "detail.subscribe":
739
- keys(o, ["type", "subscriptionId", "kind", "intervalMs", "leaseMs"], "detail.subscribe");
740
- if (o.kind !== "processes") throw new Error("invalid detail.subscribe: unsupported kind");
741
- return {
742
- type: "detail.subscribe",
743
- subscriptionId: id(o.subscriptionId, "subscriptionId"),
744
- kind: "processes",
745
- intervalMs: int(o.intervalMs, "intervalMs", MIN_DETAIL_INTERVAL_MS, MAX_DETAIL_INTERVAL_MS),
746
- leaseMs: int(o.leaseMs, "leaseMs", MIN_DETAIL_LEASE_MS, MAX_DETAIL_LEASE_MS)
747
- };
748
- case "detail.cancel":
749
- keys(o, ["type", "subscriptionId"], "detail.cancel");
750
- return { type: "detail.cancel", subscriptionId: id(o.subscriptionId, "subscriptionId") };
751
- default:
752
- throw new Error(`invalid hub message: unsupported type ${desc(o.type)}`);
753
- }
754
- }
755
- function projectTelemetryRecordForWire(record, extensions) {
756
- if (extensions.includes("bottleneck-v1") || record.kind !== "metrics") return record;
757
- return { ...record, payload: projectMetricsPayloadToBaseWire(record.payload) };
758
- }
759
- function projectMetricsPayloadToBaseWire(payload) {
760
- return {
761
- cpu: projectCpuToBaseWire(payload.cpu),
762
- memory: projectMemoryToBaseWire(payload.memory),
763
- swap: payload.swap,
764
- gpus: payload.gpus,
765
- disks: projectDisksToBaseWire(payload.disks),
766
- network: payload.network
767
- };
768
- }
769
- function projectCpuToBaseWire(cpu) {
770
- if (cpu.status !== "ok") return cpu;
771
- return { status: "ok", usagePercent: cpu.usagePercent, perCore: [...cpu.perCore] };
772
- }
773
- function projectMemoryToBaseWire(memory) {
774
- if (memory.status !== "ok") return memory;
775
- return { status: "ok", usedBytes: memory.usedBytes, totalBytes: memory.totalBytes };
776
- }
777
- function projectDisksToBaseWire(disks) {
778
- if (disks.status !== "ok") return disks;
779
- return {
780
- status: "ok",
781
- filesystems: disks.filesystems.map((filesystem) => filesystem.status === "ok" ? { id: filesystem.id, status: "ok", usedBytes: filesystem.usedBytes, totalBytes: filesystem.totalBytes } : filesystem),
782
- devices: disks.devices.map((device) => device.status === "ok" ? { id: device.id, status: "ok", readBytesPerSec: device.readBytesPerSec, writeBytesPerSec: device.writeBytesPerSec } : device)
783
- };
784
- }
785
- function parseRef(value, extra) {
786
- const o = obj(value, "record ref");
787
- keys(o, ["runId", "sequence", ...extra], "record ref");
788
- return { runId: id(o.runId, "runId"), sequence: int(o.sequence, "sequence", 0, Number.MAX_SAFE_INTEGER) };
789
- }
790
- function obj(value, what) {
791
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`invalid ${what}: expected object, got ${desc(value)}`);
792
- return value;
793
- }
794
- function keys(o, required, what, optional = []) {
795
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
796
- for (const k of Object.keys(o)) if (!allowed.has(k)) throw new Error(`invalid ${what}: unexpected field "${k}"`);
797
- for (const k of required) if (o[k] === void 0) throw new Error(`invalid ${what}: missing field "${k}"`);
798
- for (const k of optional) if (hasOwn(o, k) && o[k] === void 0) throw new Error(`invalid ${what}: ${k} must not be undefined`);
799
- }
800
- function hasOwn(o, k) {
801
- return Object.prototype.hasOwnProperty.call(o, k);
802
- }
803
- function arr(v, what, min, max) {
804
- if (!Array.isArray(v)) throw new Error(`invalid ${what}: expected array`);
805
- if (v.length < min || v.length > max) throw new Error(`invalid ${what}: length ${v.length} outside ${min}..${max}`);
806
- return v;
807
- }
808
- function oneOf(v, options, what) {
809
- if (typeof v !== "string" || !options.includes(v)) throw new Error(`invalid ${what}: unexpected value ${desc(v)}`);
810
- return v;
811
- }
812
- function text(v, field, min, max) {
813
- if (typeof v !== "string") throw new Error(`invalid ${field}: expected string, got ${desc(v)}`);
814
- if (v.length < min || v.length > max) throw new Error(`invalid ${field}: length ${v.length} outside ${min}..${max}`);
815
- return v;
816
- }
817
- function id(v, field) {
818
- return text(v, field, 1, MAX_ID);
819
- }
820
- function int(v, field, min, max) {
821
- if (typeof v !== "number" || !Number.isSafeInteger(v)) throw new Error(`invalid ${field}: expected safe integer, got ${desc(v)}`);
822
- if (v < min || v > max) throw new Error(`invalid ${field}: ${v} outside ${min}..${max}`);
823
- return v;
824
- }
825
- function desc(v) {
826
- try {
827
- const s = JSON.stringify(v);
828
- if (typeof s === "string") return s.length <= 80 ? s : `${s.slice(0, 77)}...`;
829
- } catch {
830
- }
831
- return String(v);
832
- }
833
-
834
- // src/contracts/index.ts
835
- var MIN_TIMESTAMP_EPOCH = Date.parse("2000-01-01T00:00:00Z");
836
- var MAX_TIMESTAMP_EPOCH = Date.parse("2200-01-01T00:00:00Z");
837
-
838
701
  // collector/destinations/budget.ts
839
702
  var DEFAULT_MAX_TOTAL_OUTBOX_BYTES = 256 * 1024 * 1024;
840
703
  var DEFAULT_MAX_TOTAL_OUTBOX_DISK_BYTES = 640 * 1024 * 1024;
@@ -959,9 +822,7 @@ function serializeRecord(record) {
959
822
  return { json, bytes: Buffer.byteLength(json, "utf8") };
960
823
  }
961
824
  function parseStoredRecord(json) {
962
- const record = JSON.parse(json);
963
- if (record.protocolVersion === 2) delete record.protocolVersion;
964
- return record;
825
+ return JSON.parse(json);
965
826
  }
966
827
  function fileBytes(file) {
967
828
  try {
@@ -1025,10 +886,6 @@ var DestinationOutbox = class _DestinationOutbox {
1025
886
  static open(opts) {
1026
887
  const limits = { ...DEFAULT_OUTBOX_LIMITS, ...opts.limits };
1027
888
  validateDiskLimit(limits.maxDiskBytes);
1028
- if (opts.previousDir && opts.previousDir !== opts.dir && !fs.existsSync(opts.dir) && fs.existsSync(path.join(opts.previousDir, "outbox.sqlite"))) {
1029
- fs.mkdirSync(path.dirname(opts.dir), { recursive: true, mode: 448 });
1030
- fs.renameSync(opts.previousDir, opts.dir);
1031
- }
1032
889
  fs.mkdirSync(opts.dir, { recursive: true, mode: 448 });
1033
890
  try {
1034
891
  fs.chmodSync(opts.dir, 448);
@@ -1062,45 +919,15 @@ var DestinationOutbox = class _DestinationOutbox {
1062
919
  db.exec(SCHEMA);
1063
920
  const columns = db.prepare("PRAGMA table_info(outbox_records)").all();
1064
921
  if (!columns.some((column) => column.name === "offered")) {
1065
- db.exec("ALTER TABLE outbox_records ADD COLUMN offered INTEGER NOT NULL DEFAULT 0");
1066
- db.exec("UPDATE outbox_records SET offered = 1 WHERE kind = 'gap'");
922
+ throw new Error("outbox must use the canonical schema; reset or reinstall the unfinished installation");
1067
923
  }
1068
924
  db.prepare("UPDATE outbox_records SET state = 'pending' WHERE state = 'inflight'").run();
1069
925
  const outbox = new _DestinationOutbox(opts, db, limits);
1070
926
  outbox.configurePhysicalLimit();
1071
927
  outbox.totalBytes = outbox.readTotalBytes();
1072
- if (opts.previousDir && opts.previousDir !== opts.dir) outbox.importPrevious(opts.previousDir);
1073
928
  opts.budget?.register(outbox);
1074
929
  return outbox;
1075
930
  }
1076
- /** A partially upgraded installation may already have both URL-based queues. */
1077
- importPrevious(dir) {
1078
- const file = path.join(dir, "outbox.sqlite");
1079
- if (!fs.existsSync(file)) return;
1080
- const previous = new import_node_sqlite.DatabaseSync(file, { readOnly: true, timeout: BUSY_TIMEOUT_MS });
1081
- try {
1082
- const columns = previous.prepare("PRAGMA table_info(outbox_records)").all();
1083
- const offered = columns.some((column) => column.name === "offered") ? "offered" : "CASE WHEN kind = 'gap' THEN 1 ELSE 0 END AS offered";
1084
- const rows = previous.prepare(`SELECT json, ${offered} FROM outbox_records ORDER BY id`);
1085
- const existing = this.db.prepare("SELECT id FROM outbox_records WHERE run_id = ? AND sequence = ?");
1086
- const markOffered = this.db.prepare("UPDATE outbox_records SET offered = 1 WHERE run_id = ? AND sequence = ?");
1087
- this.db.exec("BEGIN IMMEDIATE");
1088
- try {
1089
- for (const row of rows.iterate()) {
1090
- const record = parseStoredRecord(row.json);
1091
- if (!existing.get(record.runId, record.sequence)) this.insertRecord(record);
1092
- if (row.offered) markOffered.run(record.runId, record.sequence);
1093
- }
1094
- this.db.exec("COMMIT");
1095
- } catch (error) {
1096
- this.db.exec("ROLLBACK");
1097
- throw error;
1098
- }
1099
- } finally {
1100
- previous.close();
1101
- }
1102
- fs.rmSync(dir, { recursive: true });
1103
- }
1104
931
  // -------------------------------------------------------------------------
1105
932
  // Writes
1106
933
  // -------------------------------------------------------------------------
@@ -1719,6 +1546,112 @@ var DestinationOutbox = class _DestinationOutbox {
1719
1546
  }
1720
1547
  };
1721
1548
 
1549
+ // src/contracts/telemetry.ts
1550
+ var MAX_MESSAGE_BYTES = 262144;
1551
+ var MAX_DETAIL_BYTES = 1048576;
1552
+ var MAX_BATCH_RECORDS = 32;
1553
+ var MAX_CORES = 1024;
1554
+ var MAX_GPUS = 16;
1555
+ var MAX_FILESYSTEMS = 64;
1556
+ var MAX_DISKS = 64;
1557
+ var MAX_INTERFACES = 64;
1558
+ var MAX_ADDRESSES_PER_INTERFACE = 32;
1559
+ var MAX_PROCESS_ROWS = 6e3;
1560
+ var MAX_COLLECTION_FAILURES = 32;
1561
+ var MIN_DETAIL_INTERVAL_MS = 1e3;
1562
+ var MAX_DETAIL_INTERVAL_MS = 6e4;
1563
+ var MIN_DETAIL_LEASE_MS = 5e3;
1564
+ var MAX_DETAIL_LEASE_MS = 12e4;
1565
+ var MAX_ID = 128;
1566
+ var MAX_TEXT = 256;
1567
+ var MIN_EPOCH = Date.parse("2000-01-01T00:00:00Z");
1568
+ var MAX_EPOCH = Date.parse("2200-01-01T00:00:00Z");
1569
+ var CAPABILITY_NAMES = ["metrics", "inventory", "processes"];
1570
+ function parseHubMessage(value) {
1571
+ const o = obj(value, "hub message");
1572
+ switch (o.type) {
1573
+ case "welcome": {
1574
+ keys(o, ["type", "hubRunId", "machineId", "delivery", "maxInFlightBatches"], "welcome");
1575
+ if (o.delivery !== "durable") throw new Error(`invalid welcome: unexpected delivery ${desc(o.delivery)}`);
1576
+ return { type: "welcome", hubRunId: id(o.hubRunId, "hubRunId"), machineId: id(o.machineId, "machineId"), delivery: "durable", maxInFlightBatches: int(o.maxInFlightBatches, "maxInFlightBatches", 1, 64) };
1577
+ }
1578
+ case "ack": {
1579
+ keys(o, ["type", "accepted", "rejected"], "ack", ["retryAfterMs"]);
1580
+ const accepted = arr(o.accepted, "ack.accepted", 0, MAX_BATCH_RECORDS * 64).map((r) => parseRef(r, []));
1581
+ const rejected = arr(o.rejected, "ack.rejected", 0, MAX_BATCH_RECORDS * 64).map((r) => {
1582
+ const ref = parseRef(r, ["reason"]);
1583
+ return { ...ref, reason: text(r.reason, "reason", 0, MAX_TEXT) };
1584
+ });
1585
+ const out = { type: "ack", accepted, rejected };
1586
+ if (hasOwn(o, "retryAfterMs")) out.retryAfterMs = int(o.retryAfterMs, "retryAfterMs", 0, 6e5);
1587
+ return out;
1588
+ }
1589
+ case "detail.subscribe":
1590
+ keys(o, ["type", "subscriptionId", "kind", "intervalMs", "leaseMs"], "detail.subscribe");
1591
+ if (o.kind !== "processes") throw new Error("invalid detail.subscribe: unsupported kind");
1592
+ return {
1593
+ type: "detail.subscribe",
1594
+ subscriptionId: id(o.subscriptionId, "subscriptionId"),
1595
+ kind: "processes",
1596
+ intervalMs: int(o.intervalMs, "intervalMs", MIN_DETAIL_INTERVAL_MS, MAX_DETAIL_INTERVAL_MS),
1597
+ leaseMs: int(o.leaseMs, "leaseMs", MIN_DETAIL_LEASE_MS, MAX_DETAIL_LEASE_MS)
1598
+ };
1599
+ case "detail.cancel":
1600
+ keys(o, ["type", "subscriptionId"], "detail.cancel");
1601
+ return { type: "detail.cancel", subscriptionId: id(o.subscriptionId, "subscriptionId") };
1602
+ default:
1603
+ throw new Error(`invalid hub message: unsupported type ${desc(o.type)}`);
1604
+ }
1605
+ }
1606
+ function parseRef(value, extra) {
1607
+ const o = obj(value, "record ref");
1608
+ keys(o, ["runId", "sequence", ...extra], "record ref");
1609
+ return { runId: id(o.runId, "runId"), sequence: int(o.sequence, "sequence", 0, Number.MAX_SAFE_INTEGER) };
1610
+ }
1611
+ function obj(value, what) {
1612
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`invalid ${what}: expected object, got ${desc(value)}`);
1613
+ return value;
1614
+ }
1615
+ function keys(o, required, what, optional = []) {
1616
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
1617
+ for (const k of Object.keys(o)) if (!allowed.has(k)) throw new Error(`invalid ${what}: unexpected field "${k}"`);
1618
+ for (const k of required) if (o[k] === void 0) throw new Error(`invalid ${what}: missing field "${k}"`);
1619
+ for (const k of optional) if (hasOwn(o, k) && o[k] === void 0) throw new Error(`invalid ${what}: ${k} must not be undefined`);
1620
+ }
1621
+ function hasOwn(o, k) {
1622
+ return Object.prototype.hasOwnProperty.call(o, k);
1623
+ }
1624
+ function arr(v, what, min, max) {
1625
+ if (!Array.isArray(v)) throw new Error(`invalid ${what}: expected array`);
1626
+ if (v.length < min || v.length > max) throw new Error(`invalid ${what}: length ${v.length} outside ${min}..${max}`);
1627
+ return v;
1628
+ }
1629
+ function text(v, field, min, max) {
1630
+ if (typeof v !== "string") throw new Error(`invalid ${field}: expected string, got ${desc(v)}`);
1631
+ if (v.length < min || v.length > max) throw new Error(`invalid ${field}: length ${v.length} outside ${min}..${max}`);
1632
+ return v;
1633
+ }
1634
+ function id(v, field) {
1635
+ return text(v, field, 1, MAX_ID);
1636
+ }
1637
+ function int(v, field, min, max) {
1638
+ if (typeof v !== "number" || !Number.isSafeInteger(v)) throw new Error(`invalid ${field}: expected safe integer, got ${desc(v)}`);
1639
+ if (v < min || v > max) throw new Error(`invalid ${field}: ${v} outside ${min}..${max}`);
1640
+ return v;
1641
+ }
1642
+ function desc(v) {
1643
+ try {
1644
+ const s = JSON.stringify(v);
1645
+ if (typeof s === "string") return s.length <= 80 ? s : `${s.slice(0, 77)}...`;
1646
+ } catch {
1647
+ }
1648
+ return String(v);
1649
+ }
1650
+
1651
+ // src/contracts/index.ts
1652
+ var MIN_TIMESTAMP_EPOCH = Date.parse("2000-01-01T00:00:00Z");
1653
+ var MAX_TIMESTAMP_EPOCH = Date.parse("2200-01-01T00:00:00Z");
1654
+
1722
1655
  // collector/destinations/backoff.ts
1723
1656
  var DEFAULT_BASE_MS = 1e3;
1724
1657
  var DEFAULT_MAX_MS = 3e4;
@@ -1796,9 +1729,6 @@ var DestinationSession = class {
1796
1729
  outbox;
1797
1730
  expectedIntervalMs;
1798
1731
  capabilities;
1799
- collectorVersion;
1800
- /** Extensions negotiated for the current socket; empty means base wire shape. */
1801
- metricExtensions = [];
1802
1732
  socketFactory;
1803
1733
  welcomeTimeoutMs;
1804
1734
  idleTimeoutMs;
@@ -1841,9 +1771,6 @@ var DestinationSession = class {
1841
1771
  constructor(options) {
1842
1772
  assertExpectedIntervalMs(options.expectedIntervalMs);
1843
1773
  assertCapabilities(options.capabilities);
1844
- if (options.collectorVersion.length < 1 || options.collectorVersion.length > 64) {
1845
- throw new Error("collectorVersion must be 1..64 characters");
1846
- }
1847
1774
  const maxBatchBytes = options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES;
1848
1775
  if (!Number.isSafeInteger(maxBatchBytes) || maxBatchBytes < 1 || maxBatchBytes >= MAX_MESSAGE_BYTES) {
1849
1776
  throw new Error(`maxBatchBytes must be a safe integer below ${MAX_MESSAGE_BYTES}, got ${String(maxBatchBytes)}`);
@@ -1867,7 +1794,6 @@ var DestinationSession = class {
1867
1794
  this.outbox = options.outbox;
1868
1795
  this.expectedIntervalMs = options.expectedIntervalMs;
1869
1796
  this.capabilities = [...options.capabilities];
1870
- this.collectorVersion = options.collectorVersion;
1871
1797
  this.socketFactory = options.socketFactory;
1872
1798
  this.welcomeTimeoutMs = options.welcomeTimeoutMs ?? DEFAULT_WELCOME_TIMEOUT_MS;
1873
1799
  this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
@@ -1996,7 +1922,6 @@ var DestinationSession = class {
1996
1922
  openSocket() {
1997
1923
  if (this.stopping) return;
1998
1924
  const epoch = ++this.epoch;
1999
- this.metricExtensions = [];
2000
1925
  this.failedEpoch = -1;
2001
1926
  this.status = "connecting";
2002
1927
  let socket;
@@ -2052,8 +1977,7 @@ var DestinationSession = class {
2052
1977
  bootId: this.identity.bootId,
2053
1978
  hostname: this.identity.hostname,
2054
1979
  capabilities: [...this.capabilities],
2055
- expectedIntervalMs: this.expectedIntervalMs,
2056
- collectorVersion: this.collectorVersion
1980
+ expectedIntervalMs: this.expectedIntervalMs
2057
1981
  };
2058
1982
  }
2059
1983
  handleMessage(epoch, data) {
@@ -2129,7 +2053,6 @@ var DestinationSession = class {
2129
2053
  }
2130
2054
  this.status = "welcomed";
2131
2055
  this.welcomedAtMs = this.now();
2132
- this.metricExtensions = [...welcome.metricExtensions ?? []];
2133
2056
  this.inFlightWindow = Math.max(1, Math.min(welcome.maxInFlightBatches, this.maxLocalInFlightBatches));
2134
2057
  this.replayTokens = 1;
2135
2058
  this.lastTokenRefillMs = this.now();
@@ -2327,10 +2250,7 @@ var DestinationSession = class {
2327
2250
  return true;
2328
2251
  }
2329
2252
  buildSamplesMessage(mode, records) {
2330
- const wireRecords = records.map(
2331
- (record) => projectTelemetryRecordForWire(record, this.metricExtensions)
2332
- );
2333
- return JSON.stringify({ type: "samples", mode, records: wireRecords });
2253
+ return JSON.stringify({ type: "samples", mode, records });
2334
2254
  }
2335
2255
  handleAckTimeout(batchId) {
2336
2256
  const batch = this.inFlight.get(batchId);
@@ -2447,7 +2367,6 @@ var HubConnection = class _HubConnection {
2447
2367
  outbox,
2448
2368
  expectedIntervalMs: effectiveIntervalMs(options.baseIntervalMs, options.endpoint.intervalMs),
2449
2369
  capabilities,
2450
- collectorVersion: options.collectorVersion,
2451
2370
  socketFactory: options.socketFactory,
2452
2371
  backoff: options.backoff,
2453
2372
  rand: options.rand,
@@ -2460,19 +2379,14 @@ var HubConnection = class _HubConnection {
2460
2379
  });
2461
2380
  }
2462
2381
  /**
2463
- * Opens this connection's durable outbox under its current endpoint URL,
2464
- * upgrades outboxes that predate the endpoint consolidation, and re-applies
2465
- * the privacy policy to recovered pending records so none of them can
2466
- * leave with forbidden fields. Sanitation never cadence-filters: retained
2467
- * samples were already cadence-selected when they were captured.
2382
+ * Opens this connection's durable outbox under its canonical endpoint URL
2383
+ * and re-applies the privacy policy to recovered pending records so none of
2384
+ * them can leave with forbidden fields. Sanitation never cadence-filters:
2385
+ * retained samples were already cadence-selected when they were captured.
2468
2386
  */
2469
2387
  static open(options) {
2470
- const dir = outboxDirFor(options.stateDir, options.endpoint.url);
2471
- const previousUrl = new URL(options.endpoint.url);
2472
- previousUrl.pathname = PREVIOUS_CONNECT_PATH;
2473
2388
  const outbox = DestinationOutbox.open({
2474
- dir,
2475
- previousDir: outboxDirFor(options.stateDir, previousUrl.href),
2389
+ dir: outboxDirFor(options.stateDir, options.endpoint.url),
2476
2390
  budget: options.budget,
2477
2391
  allocateSequence: options.allocateSequence,
2478
2392
  identity: {
@@ -3020,7 +2934,7 @@ function readSysfsGpu(readers, address) {
3020
2934
  if (power !== null && values.powerWatts === null) values.powerWatts = power / 1e6;
3021
2935
  }
3022
2936
  if ([values.utilizationPercent, values.memoryTotalBytes, values.temperatureC, values.powerWatts].every((value) => value === null)) {
3023
- return { id: id2, status: "unavailable", reason: "driver exposes no readable GPU counters; check DRM driver and permissions" };
2937
+ return { id: id2, status: "unavailable", reason: "GPU detected, but no readable DRM/sysfs counters; driver support or permissions may limit telemetry" };
3024
2938
  }
3025
2939
  return values;
3026
2940
  }
@@ -3286,9 +3200,8 @@ function capReason(value) {
3286
3200
  }
3287
3201
  function normalizePciBusId(value) {
3288
3202
  const lower = value.trim().toLowerCase();
3289
- const parts = lower.split(":");
3290
- if (parts.length >= 3) return `${parts[parts.length - 2]}:${parts[parts.length - 1]}`;
3291
- return lower;
3203
+ const match = /^(?:([0-9a-f]{4,8}):)?([0-9a-f]{2}:[0-9a-f]{2}\.[0-7])$/.exec(lower);
3204
+ return match ? `${(match[1] ?? "0").replace(/^0+/, "").padStart(4, "0")}:${match[2]}` : lower;
3292
3205
  }
3293
3206
  function vendorFor(raw) {
3294
3207
  switch (raw.trim().toLowerCase()) {
@@ -3332,7 +3245,18 @@ function fallbackGpuName(vendor, address) {
3332
3245
  intel: "Intel",
3333
3246
  other: "Display"
3334
3247
  };
3335
- return `${labels[vendor]} display adapter at ${address}`;
3248
+ return `${labels[vendor]} display adapter`;
3249
+ }
3250
+ function pciDeviceName(database, vendorId, deviceId) {
3251
+ let vendor = false;
3252
+ for (const line of database.split("\n")) {
3253
+ const heading = /^([0-9a-f]{4})\s+/i.exec(line);
3254
+ if (heading) vendor = heading[1].toLowerCase() === vendorId.replace(/^0x/, "").toLowerCase();
3255
+ if (!vendor) continue;
3256
+ const device = /^\t([0-9a-f]{4})\s+(.+)$/i.exec(line);
3257
+ if (device && device[1].toLowerCase() === deviceId.replace(/^0x/, "").toLowerCase()) return capText(device[2]);
3258
+ }
3259
+ return null;
3336
3260
  }
3337
3261
  function unsupportedGpuReason(vendor, gpu) {
3338
3262
  if (vendor === "aspeed") return "management adapter; no telemetry";
@@ -3351,6 +3275,7 @@ function buildGpus(read, readdir, gpu) {
3351
3275
  }
3352
3276
  }
3353
3277
  }
3278
+ const pciNames = readTextOrNull(read, "/usr/share/hwdata/pci.ids") ?? readTextOrNull(read, "/usr/share/misc/pci.ids") ?? "";
3354
3279
  const result = [];
3355
3280
  const seenIds = /* @__PURE__ */ new Set();
3356
3281
  for (const device of pciDevices) {
@@ -3370,7 +3295,7 @@ function buildGpus(read, readdir, gpu) {
3370
3295
  const product = readTextOrNull(read, `/sys/bus/pci/devices/${device.address}/product_name`)?.trim();
3371
3296
  entry = {
3372
3297
  id: `pci:${device.address}`,
3373
- name: capText(product || fallbackGpuName(device.vendor, device.address)),
3298
+ name: capText(product || pciDeviceName(pciNames, device.vendorId, readTextOrNull(read, `/sys/bus/pci/devices/${device.address}/device`)?.trim() ?? "") || fallbackGpuName(device.vendor, device.address)),
3374
3299
  vendor: device.vendor,
3375
3300
  pciBusId: device.address,
3376
3301
  telemetry: reading.status === "ok" ? "supported" : "unsupported",
@@ -5292,8 +5217,7 @@ function budget(value, fallback) {
5292
5217
  // package.json
5293
5218
  var package_default = {
5294
5219
  name: "rigscope",
5295
- version: "0.1.2",
5296
- rigscopeConfigVersion: 2,
5220
+ version: "0.1.4",
5297
5221
  private: false,
5298
5222
  engines: {
5299
5223
  node: ">=26.0.0"
@@ -5520,7 +5444,6 @@ async function runDurableCollector(config, identity, statePath, mock) {
5520
5444
  budget: budget2,
5521
5445
  allocateSequence: () => sequence.next(),
5522
5446
  sampler,
5523
- collectorVersion: COLLECTOR_PROTOCOL_VERSION,
5524
5447
  socketFactory: (url, options) => new import_ws.WebSocket(url, {
5525
5448
  headers: options.headers,
5526
5449
  handshakeTimeout: options.handshakeTimeoutMs,
package/dist/hub.cjs CHANGED
@@ -1741,8 +1741,7 @@ function describeError(error) {
1741
1741
  // package.json
1742
1742
  var package_default = {
1743
1743
  name: "rigscope",
1744
- version: "0.1.2",
1745
- rigscopeConfigVersion: 2,
1744
+ version: "0.1.4",
1746
1745
  private: false,
1747
1746
  engines: {
1748
1747
  node: ">=26.0.0"
@@ -3109,18 +3108,12 @@ var MIN_EPOCH = Date.parse("2000-01-01T00:00:00Z");
3109
3108
  var MAX_EPOCH = Date.parse("2200-01-01T00:00:00Z");
3110
3109
  var CAPABILITY_NAMES = ["metrics", "inventory", "processes"];
3111
3110
  var METRIC_FAMILIES = ["cpu", "memory", "swap", "gpus", "disks", "network"];
3112
- var METRIC_EXTENSION_NAMES = ["bottleneck-v1"];
3113
- var COLLECTOR_PROTOCOL_VERSION = "0.3.0";
3114
- var METRIC_EXTENSION_PROTOCOL_VERSIONS = [COLLECTOR_PROTOCOL_VERSION];
3115
- function collectorSupportsMetricExtensions(collectorVersion) {
3116
- return METRIC_EXTENSION_PROTOCOL_VERSIONS.includes(collectorVersion);
3117
- }
3118
3111
  var GAP_REASONS = ["outbox-age", "outbox-bytes", "disk-budget", "write-failure", "rejected"];
3119
3112
  function parseCollectorMessage(value) {
3120
3113
  const o = obj(value, "collector message");
3121
3114
  switch (o.type) {
3122
3115
  case "hello": {
3123
- keys(o, ["type", "collectorId", "runId", "bootId", "hostname", "capabilities", "expectedIntervalMs", "collectorVersion"], "hello");
3116
+ keys(o, ["type", "collectorId", "runId", "bootId", "hostname", "capabilities", "expectedIntervalMs"], "hello");
3124
3117
  const caps = arr(o.capabilities, "hello.capabilities", 1, CAPABILITY_NAMES.length).map((c) => oneOf(c, CAPABILITY_NAMES, "hello.capabilities"));
3125
3118
  if (new Set(caps).size !== caps.length || !caps.includes("metrics")) throw new Error("invalid hello: capabilities must be unique and include metrics");
3126
3119
  return {
@@ -3130,8 +3123,7 @@ function parseCollectorMessage(value) {
3130
3123
  bootId: text(o.bootId, "bootId", 0, MAX_ID),
3131
3124
  hostname: text(o.hostname, "hostname", 0, MAX_HOSTNAME),
3132
3125
  capabilities: caps,
3133
- expectedIntervalMs: int(o.expectedIntervalMs, "expectedIntervalMs", MIN_EXPECTED_INTERVAL_MS, MAX_EXPECTED_INTERVAL_MS),
3134
- collectorVersion: text(o.collectorVersion, "collectorVersion", 1, 64)
3126
+ expectedIntervalMs: int(o.expectedIntervalMs, "expectedIntervalMs", MIN_EXPECTED_INTERVAL_MS, MAX_EXPECTED_INTERVAL_MS)
3135
3127
  };
3136
3128
  }
3137
3129
  case "samples": {
@@ -6991,9 +6983,6 @@ ${reason}`
6991
6983
  delivery: "durable",
6992
6984
  maxInFlightBatches: MAX_IN_FLIGHT_SAMPLE_BATCHES
6993
6985
  };
6994
- if (collectorSupportsMetricExtensions(hello.collectorVersion)) {
6995
- welcome.metricExtensions = [...METRIC_EXTENSION_NAMES];
6996
- }
6997
6986
  this.send(connection, JSON.stringify(welcome));
6998
6987
  }
6999
6988
  onSessionMessage(connection, message) {
@@ -7286,7 +7275,6 @@ function filterSnapshot(snapshot, scope) {
7286
7275
  var import_node_crypto15 = require("node:crypto");
7287
7276
  var import_node_fs3 = require("node:fs");
7288
7277
  var import_node_path4 = require("node:path");
7289
- var STATE_VERSION = 1;
7290
7278
  var MAX_COLLECTOR_ID_LENGTH2 = 128;
7291
7279
  var MAX_MACHINE_ID_LENGTH2 = 128;
7292
7280
  var MAX_TOKEN_LENGTH2 = 256;
@@ -7541,8 +7529,8 @@ var EnrollmentStore = class {
7541
7529
  throw new EnrollmentStoreError("invalid-state", "state file must be a JSON object");
7542
7530
  }
7543
7531
  const state = parsed;
7544
- if (state.version !== STATE_VERSION) {
7545
- throw new EnrollmentStoreError("invalid-state", "state file has an unsupported version");
7532
+ if (Object.keys(state).some((key) => key !== "enrollments" && key !== "consumed")) {
7533
+ throw new EnrollmentStoreError("invalid-state", "state file must use the canonical enrollment shape; reset or reinstall unfinished state");
7546
7534
  }
7547
7535
  if (!Array.isArray(state.enrollments) || !Array.isArray(state.consumed)) {
7548
7536
  throw new EnrollmentStoreError("invalid-state", "state file must contain enrollments and consumed arrays");
@@ -7601,7 +7589,6 @@ var EnrollmentStore = class {
7601
7589
  }
7602
7590
  persistState(next) {
7603
7591
  const state = {
7604
- version: STATE_VERSION,
7605
7592
  enrollments: next.enrollments,
7606
7593
  consumed: next.consumed
7607
7594
  };