camstack 1.2.47 → 1.2.48

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-CQs1b7uh.js
23637
- var require_dist_CQs1b7uh = __commonJS({
23638
- "../system/dist/dist-CQs1b7uh.js"(exports) {
23636
+ // ../system/dist/dist-D3lqzV40.js
23637
+ var require_dist_D3lqzV40 = __commonJS({
23638
+ "../system/dist/dist-D3lqzV40.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -47109,17 +47109,39 @@ var require_dist_CQs1b7uh = __commonJS({
47109
47109
  latitude: zod.z.number().min(-90).max(90),
47110
47110
  longitude: zod.z.number().min(-180).max(180)
47111
47111
  }).nullable();
47112
+ var TransportPlaneSchema = zod.z.enum([
47113
+ "http",
47114
+ "ws",
47115
+ "mesh",
47116
+ "unknown"
47117
+ ]);
47118
+ var TransportPlaneCountsSchema = zod.z.object({
47119
+ http: zod.z.number(),
47120
+ ws: zod.z.number(),
47121
+ mesh: zod.z.number(),
47122
+ unknown: zod.z.number()
47123
+ });
47112
47124
  var RequestCensusGroupSchema = zod.z.object({
47125
+ plane: TransportPlaneSchema,
47113
47126
  procedure: zod.z.string(),
47114
47127
  userAgent: zod.z.string(),
47115
47128
  ip: zod.z.string(),
47116
47129
  principal: zod.z.string(),
47117
47130
  calls: zod.z.number(),
47131
+ subscriptions: zod.z.number(),
47118
47132
  perMin: zod.z.number()
47119
47133
  });
47120
47134
  var RequestCensusProcedureSchema = zod.z.object({
47121
47135
  procedure: zod.z.string(),
47122
47136
  calls: zod.z.number(),
47137
+ /**
47138
+ * The same total, split by transport. THIS is the row that answers the
47139
+ * question the census exists for: one look at `deviceManager.listAll` says
47140
+ * which plane carried the 4 960, without joining two log lines by eye.
47141
+ */
47142
+ planes: TransportPlaneCountsSchema,
47143
+ /** Subscription STARTS on this procedure. Never folded into `calls`. */
47144
+ subscriptions: zod.z.number(),
47123
47145
  perMin: zod.z.number()
47124
47146
  });
47125
47147
  var RequestCensusStatusSchema = zod.z.object({
@@ -47139,14 +47161,45 @@ var require_dist_CQs1b7uh = __commonJS({
47139
47161
  */
47140
47162
  procedureCalls: zod.z.number(),
47141
47163
  /**
47164
+ * `procedureCalls` split by transport. The four keys sum to
47165
+ * `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
47166
+ * `planesExplainTotal` is that identity, checked rather than assumed.
47167
+ */
47168
+ planes: TransportPlaneCountsSchema,
47169
+ /**
47170
+ * True iff `planes` sums to `procedureCalls`. False means a call was counted
47171
+ * on no plane at all - which is a RESULT (a plane is missing from the
47172
+ * instrument), not a failure, and it has to be visible to be read as one.
47173
+ */
47174
+ planesExplainTotal: zod.z.boolean(),
47175
+ /**
47142
47176
  * tRPC WebSocket connections opened during the window. NOT calls - the WS
47143
- * transport resolves one context per connection - but the number that says
47144
- * whether a plane this census cannot see was busy while HTTP was quiet.
47177
+ * adapter resolves one context per connection - kept because a plane's call
47178
+ * count of zero against 37 open connections says something different from a
47179
+ * plane with no connections at all.
47145
47180
  */
47146
47181
  wsConnections: zod.z.number(),
47182
+ /**
47183
+ * Client frames the WS plane looked at. `wsMessages` far above
47184
+ * `planes.ws + subscriptions` means most traffic is not operations
47185
+ * (keepalives, connection params) - which is itself an answer.
47186
+ */
47187
+ wsMessages: zod.z.number(),
47188
+ /**
47189
+ * Subscription STARTS across every plane, excluded from `procedureCalls` on
47190
+ * purpose: one live-events stream opened at boot and held for six hours is
47191
+ * one subscription, and counting it as a call would let a quiet plane
47192
+ * masquerade as the storm.
47193
+ */
47194
+ subscriptions: zod.z.number(),
47195
+ /** `subscription.stop` frames. Starts minus stops is what is still open. */
47196
+ subscriptionStops: zod.z.number(),
47147
47197
  distinctGroups: zod.z.number(),
47148
- /** Calls counted in the totals whose group attribution was shed at the
47149
- * cardinality bound. */
47198
+ /**
47199
+ * Operations counted in the totals whose CALLER attribution was shed at the
47200
+ * cardinality bound. Unrelated to the `unknown` PLANE: these calls know
47201
+ * which transport they arrived on, they just lost their group row.
47202
+ */
47150
47203
  unattributedCalls: zod.z.number(),
47151
47204
  procedures: zod.z.array(RequestCensusProcedureSchema).readonly(),
47152
47205
  groups: zod.z.array(RequestCensusGroupSchema).readonly()
@@ -57677,7 +57730,7 @@ var require_alerts_addon = __commonJS({
57677
57730
  [Symbol.toStringTag]: { value: "Module" }
57678
57731
  });
57679
57732
  require_chunk_Cek0wNdY();
57680
- var require_dist10 = require_dist_CQs1b7uh();
57733
+ var require_dist10 = require_dist_D3lqzV40();
57681
57734
  function selectExpired(alerts, cutoffMs) {
57682
57735
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
57683
57736
  }
@@ -58496,7 +58549,7 @@ var require_console_logging = __commonJS({
58496
58549
  [Symbol.toStringTag]: { value: "Module" }
58497
58550
  });
58498
58551
  require_chunk_Cek0wNdY();
58499
- var require_dist10 = require_dist_CQs1b7uh();
58552
+ var require_dist10 = require_dist_D3lqzV40();
58500
58553
  var require_formatter = require_formatter_DqAKDlvN();
58501
58554
  var LEVEL_RANK = {
58502
58555
  debug: 0,
@@ -58590,7 +58643,7 @@ var require_core_blocks_addon = __commonJS({
58590
58643
  "use strict";
58591
58644
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
58592
58645
  var require_chunk = require_chunk_Cek0wNdY();
58593
- var require_dist10 = require_dist_CQs1b7uh();
58646
+ var require_dist10 = require_dist_D3lqzV40();
58594
58647
  var node_crypto = __require("crypto");
58595
58648
  var node_fs_promises = __require("fs/promises");
58596
58649
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -59487,11 +59540,11 @@ var require_core_blocks = __commonJS({
59487
59540
  }
59488
59541
  });
59489
59542
 
59490
- // ../system/dist/retired-settings-keys-Dvei9nhG.js
59491
- var require_retired_settings_keys_Dvei9nhG = __commonJS({
59492
- "../system/dist/retired-settings-keys-Dvei9nhG.js"(exports) {
59543
+ // ../system/dist/retired-settings-keys-OtUQtEbq.js
59544
+ var require_retired_settings_keys_OtUQtEbq = __commonJS({
59545
+ "../system/dist/retired-settings-keys-OtUQtEbq.js"(exports) {
59493
59546
  "use strict";
59494
- var require_dist10 = require_dist_CQs1b7uh();
59547
+ var require_dist10 = require_dist_D3lqzV40();
59495
59548
  function settingsStoreIsAuthoritativeHere(env) {
59496
59549
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
59497
59550
  return raw === "" || raw === "hub";
@@ -61534,8 +61587,8 @@ var require_device_manager_addon = __commonJS({
61534
61587
  [Symbol.toStringTag]: { value: "Module" }
61535
61588
  });
61536
61589
  require_chunk_Cek0wNdY();
61537
- var require_dist10 = require_dist_CQs1b7uh();
61538
- var require_retired_settings_keys = require_retired_settings_keys_Dvei9nhG();
61590
+ var require_dist10 = require_dist_D3lqzV40();
61591
+ var require_retired_settings_keys = require_retired_settings_keys_OtUQtEbq();
61539
61592
  var node_crypto = __require("crypto");
61540
61593
  var _camstack_types_node = require_node();
61541
61594
  var JOB_HISTORY = 20;
@@ -66204,7 +66257,7 @@ var require_hub_forwarder = __commonJS({
66204
66257
  [Symbol.toStringTag]: { value: "Module" }
66205
66258
  });
66206
66259
  require_chunk_Cek0wNdY();
66207
- var require_dist10 = require_dist_CQs1b7uh();
66260
+ var require_dist10 = require_dist_D3lqzV40();
66208
66261
  var require_formatter = require_formatter_DqAKDlvN();
66209
66262
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
66210
66263
  var HubForwarderDestination = class {
@@ -66341,7 +66394,7 @@ var require_liveness_monitor_addon = __commonJS({
66341
66394
  "use strict";
66342
66395
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
66343
66396
  require_chunk_Cek0wNdY();
66344
- var require_dist10 = require_dist_CQs1b7uh();
66397
+ var require_dist10 = require_dist_D3lqzV40();
66345
66398
  var NO_DEVICES = "liveness:no-devices";
66346
66399
  var ALL_OFFLINE = "liveness:all-devices-offline";
66347
66400
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -66531,7 +66584,7 @@ var require_local_auth_addon = __commonJS({
66531
66584
  [Symbol.toStringTag]: { value: "Module" }
66532
66585
  });
66533
66586
  var require_chunk = require_chunk_Cek0wNdY();
66534
- var require_dist10 = require_dist_CQs1b7uh();
66587
+ var require_dist10 = require_dist_D3lqzV40();
66535
66588
  var node_crypto = __require("crypto");
66536
66589
  node_crypto = require_chunk.__toESM(node_crypto);
66537
66590
  var crypto$1 = __require("crypto");
@@ -74215,7 +74268,7 @@ var require_loki_logging = __commonJS({
74215
74268
  [Symbol.toStringTag]: { value: "Module" }
74216
74269
  });
74217
74270
  require_chunk_Cek0wNdY();
74218
- var require_dist10 = require_dist_CQs1b7uh();
74271
+ var require_dist10 = require_dist_D3lqzV40();
74219
74272
  function sanitizeLabelName(raw) {
74220
74273
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
74221
74274
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -74780,7 +74833,7 @@ var require_native_metrics_addon = __commonJS({
74780
74833
  [Symbol.toStringTag]: { value: "Module" }
74781
74834
  });
74782
74835
  var require_chunk = require_chunk_Cek0wNdY();
74783
- var require_dist10 = require_dist_CQs1b7uh();
74836
+ var require_dist10 = require_dist_D3lqzV40();
74784
74837
  var node_child_process = __require("child_process");
74785
74838
  var node_util = __require("util");
74786
74839
  var node_os = __require("os");
@@ -75722,7 +75775,7 @@ var require_filesystem_storage_addon = __commonJS({
75722
75775
  [Symbol.toStringTag]: { value: "Module" }
75723
75776
  });
75724
75777
  var require_chunk = require_chunk_Cek0wNdY();
75725
- var require_dist10 = require_dist_CQs1b7uh();
75778
+ var require_dist10 = require_dist_D3lqzV40();
75726
75779
  var node_crypto = __require("crypto");
75727
75780
  var node_fs_promises = __require("fs/promises");
75728
75781
  var node_path = __require("path");
@@ -76838,8 +76891,8 @@ var require_sqlite_settings_addon = __commonJS({
76838
76891
  [Symbol.toStringTag]: { value: "Module" }
76839
76892
  });
76840
76893
  var require_chunk = require_chunk_Cek0wNdY();
76841
- var require_dist10 = require_dist_CQs1b7uh();
76842
- var require_retired_settings_keys = require_retired_settings_keys_Dvei9nhG();
76894
+ var require_dist10 = require_dist_D3lqzV40();
76895
+ var require_retired_settings_keys = require_retired_settings_keys_OtUQtEbq();
76843
76896
  var node_crypto = __require("crypto");
76844
76897
  var node_fs = __require("fs");
76845
76898
  var node_module = __require("module");
@@ -79033,7 +79086,7 @@ var require_storage_orchestrator_addon = __commonJS({
79033
79086
  [Symbol.toStringTag]: { value: "Module" }
79034
79087
  });
79035
79088
  var require_chunk = require_chunk_Cek0wNdY();
79036
- var require_dist10 = require_dist_CQs1b7uh();
79089
+ var require_dist10 = require_dist_D3lqzV40();
79037
79090
  var node_crypto = __require("crypto");
79038
79091
  var node_fs_promises = __require("fs/promises");
79039
79092
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -80912,7 +80965,7 @@ var require_system_config_addon = __commonJS({
80912
80965
  [Symbol.toStringTag]: { value: "Module" }
80913
80966
  });
80914
80967
  require_chunk_Cek0wNdY();
80915
- var require_dist10 = require_dist_CQs1b7uh();
80968
+ var require_dist10 = require_dist_D3lqzV40();
80916
80969
  var SECTION_TITLES = {
80917
80970
  server: "Server",
80918
80971
  auth: "Authentication"
@@ -98973,7 +99026,7 @@ var require_winston_logging = __commonJS({
98973
99026
  [Symbol.toStringTag]: { value: "Module" }
98974
99027
  });
98975
99028
  var require_chunk = require_chunk_Cek0wNdY();
98976
- var require_dist10 = require_dist_CQs1b7uh();
99029
+ var require_dist10 = require_dist_D3lqzV40();
98977
99030
  var require_formatter = require_formatter_DqAKDlvN();
98978
99031
  var node_path = __require("path");
98979
99032
  node_path = require_chunk.__toESM(node_path);
@@ -122221,7 +122274,7 @@ var require_dist3 = __commonJS({
122221
122274
  "use strict";
122222
122275
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
122223
122276
  var require_chunk = require_chunk_Cek0wNdY();
122224
- var require_dist10 = require_dist_CQs1b7uh();
122277
+ var require_dist10 = require_dist_D3lqzV40();
122225
122278
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
122226
122279
  require_alerts();
122227
122280
  var require_formatter = require_formatter_DqAKDlvN();
@@ -201048,13 +201101,22 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
201048
201101
  const actions = {};
201049
201102
  for (const { actionName, invoke } of options.actions) actions[actionName] = { handler: async (ctx) => {
201050
201103
  const raw = require_manifest_python_deps.deserializeTypedArrays(ctx.params);
201051
- return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw);
201104
+ return invoke(raw !== null && typeof raw === "object" && Object.keys(raw).length === 0 ? void 0 : raw, readOrigin(ctx));
201052
201105
  } };
201053
201106
  return {
201054
201107
  name: options.serviceName ?? "$core-caps",
201055
201108
  actions
201056
201109
  };
201057
201110
  }
201111
+ function readOrigin(ctx) {
201112
+ return {
201113
+ nodeId: asName(ctx.nodeID),
201114
+ caller: asName(ctx.caller)
201115
+ };
201116
+ }
201117
+ function asName(value) {
201118
+ return typeof value === "string" && value.length > 0 ? value : null;
201119
+ }
201058
201120
  var HubLogForwarder = class {
201059
201121
  broker;
201060
201122
  connected = false;
@@ -227086,17 +227148,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
227086
227148
  latitude: zod.z.number().min(-90).max(90),
227087
227149
  longitude: zod.z.number().min(-180).max(180)
227088
227150
  }).nullable();
227151
+ var TransportPlaneSchema = zod.z.enum([
227152
+ "http",
227153
+ "ws",
227154
+ "mesh",
227155
+ "unknown"
227156
+ ]);
227157
+ var TransportPlaneCountsSchema = zod.z.object({
227158
+ http: zod.z.number(),
227159
+ ws: zod.z.number(),
227160
+ mesh: zod.z.number(),
227161
+ unknown: zod.z.number()
227162
+ });
227089
227163
  var RequestCensusGroupSchema = zod.z.object({
227164
+ plane: TransportPlaneSchema,
227090
227165
  procedure: zod.z.string(),
227091
227166
  userAgent: zod.z.string(),
227092
227167
  ip: zod.z.string(),
227093
227168
  principal: zod.z.string(),
227094
227169
  calls: zod.z.number(),
227170
+ subscriptions: zod.z.number(),
227095
227171
  perMin: zod.z.number()
227096
227172
  });
227097
227173
  var RequestCensusProcedureSchema = zod.z.object({
227098
227174
  procedure: zod.z.string(),
227099
227175
  calls: zod.z.number(),
227176
+ /**
227177
+ * The same total, split by transport. THIS is the row that answers the
227178
+ * question the census exists for: one look at `deviceManager.listAll` says
227179
+ * which plane carried the 4 960, without joining two log lines by eye.
227180
+ */
227181
+ planes: TransportPlaneCountsSchema,
227182
+ /** Subscription STARTS on this procedure. Never folded into `calls`. */
227183
+ subscriptions: zod.z.number(),
227100
227184
  perMin: zod.z.number()
227101
227185
  });
227102
227186
  var RequestCensusSnapshotSchema = zod.z.object({
@@ -227116,14 +227200,45 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
227116
227200
  */
227117
227201
  procedureCalls: zod.z.number(),
227118
227202
  /**
227203
+ * `procedureCalls` split by transport. The four keys sum to
227204
+ * `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
227205
+ * `planesExplainTotal` is that identity, checked rather than assumed.
227206
+ */
227207
+ planes: TransportPlaneCountsSchema,
227208
+ /**
227209
+ * True iff `planes` sums to `procedureCalls`. False means a call was counted
227210
+ * on no plane at all - which is a RESULT (a plane is missing from the
227211
+ * instrument), not a failure, and it has to be visible to be read as one.
227212
+ */
227213
+ planesExplainTotal: zod.z.boolean(),
227214
+ /**
227119
227215
  * tRPC WebSocket connections opened during the window. NOT calls - the WS
227120
- * transport resolves one context per connection - but the number that says
227121
- * whether a plane this census cannot see was busy while HTTP was quiet.
227216
+ * adapter resolves one context per connection - kept because a plane's call
227217
+ * count of zero against 37 open connections says something different from a
227218
+ * plane with no connections at all.
227122
227219
  */
227123
227220
  wsConnections: zod.z.number(),
227221
+ /**
227222
+ * Client frames the WS plane looked at. `wsMessages` far above
227223
+ * `planes.ws + subscriptions` means most traffic is not operations
227224
+ * (keepalives, connection params) - which is itself an answer.
227225
+ */
227226
+ wsMessages: zod.z.number(),
227227
+ /**
227228
+ * Subscription STARTS across every plane, excluded from `procedureCalls` on
227229
+ * purpose: one live-events stream opened at boot and held for six hours is
227230
+ * one subscription, and counting it as a call would let a quiet plane
227231
+ * masquerade as the storm.
227232
+ */
227233
+ subscriptions: zod.z.number(),
227234
+ /** `subscription.stop` frames. Starts minus stops is what is still open. */
227235
+ subscriptionStops: zod.z.number(),
227124
227236
  distinctGroups: zod.z.number(),
227125
- /** Calls counted in the totals whose group attribution was shed at the
227126
- * cardinality bound. */
227237
+ /**
227238
+ * Operations counted in the totals whose CALLER attribution was shed at the
227239
+ * cardinality bound. Unrelated to the `unknown` PLANE: these calls know
227240
+ * which transport they arrived on, they just lost their group row.
227241
+ */
227127
227242
  unattributedCalls: zod.z.number(),
227128
227243
  procedures: zod.z.array(RequestCensusProcedureSchema).readonly(),
227129
227244
  groups: zod.z.array(RequestCensusGroupSchema).readonly()
@@ -244160,6 +244275,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244160
244275
  exports.TrackedDetectionSchema = TrackedDetectionSchema;
244161
244276
  exports.TrainingExportDeviceTotalsSchema = TrainingExportDeviceTotalsSchema;
244162
244277
  exports.TrainingExportSummarySchema = TrainingExportSummarySchema;
244278
+ exports.TransportPlaneCountsSchema = TransportPlaneCountsSchema;
244279
+ exports.TransportPlaneSchema = TransportPlaneSchema;
244163
244280
  exports.TurnServerSchema = TurnServerSchema;
244164
244281
  exports.UNIT_TABLE = UNIT_TABLE;
244165
244282
  exports.UnifiedBrokerInfoSchema = BrokerInfoSchema$1;
@@ -277297,6 +277414,90 @@ var require_agent_http_auth = __commonJS({
277297
277414
  }
277298
277415
  });
277299
277416
 
277417
+ // ../../server/backend/dist/agent/agent-update-routes.js
277418
+ var require_agent_update_routes = __commonJS({
277419
+ "../../server/backend/dist/agent/agent-update-routes.js"(exports) {
277420
+ "use strict";
277421
+ Object.defineProperty(exports, "__esModule", { value: true });
277422
+ exports.ADDON_UPDATE_UNAVAILABLE_REASON = exports.SERVER_UPDATE_UNAVAILABLE_REASON = exports.APPLY_ALREADY_RUNNING_MESSAGE = void 0;
277423
+ exports.createAgentServerUpdateControls = createAgentServerUpdateControls;
277424
+ exports.registerAgentUpdateRoutes = registerAgentUpdateRoutes;
277425
+ exports.APPLY_ALREADY_RUNNING_MESSAGE = "Refused: an update is already in flight on this node.";
277426
+ function createAgentServerUpdateControls(engine, onError) {
277427
+ let lastCheckError = null;
277428
+ let lastApply = null;
277429
+ let applyInFlight = false;
277430
+ return {
277431
+ getView: async () => ({
277432
+ status: await engine.getServerPackageStatus(),
277433
+ lastCheckError,
277434
+ lastApply,
277435
+ applyInFlight
277436
+ }),
277437
+ check: async () => {
277438
+ const result = await engine.checkServerUpdate();
277439
+ lastCheckError = result.error;
277440
+ return result;
277441
+ },
277442
+ applyLatest: () => {
277443
+ if (applyInFlight) {
277444
+ return { started: false, message: exports.APPLY_ALREADY_RUNNING_MESSAGE };
277445
+ }
277446
+ applyInFlight = true;
277447
+ lastApply = null;
277448
+ void engine.applyServerUpdate({}).then((result) => {
277449
+ lastApply = result;
277450
+ if (!result.accepted)
277451
+ onError?.(result.message);
277452
+ }).catch((err) => {
277453
+ const message = err instanceof Error ? err.message : String(err);
277454
+ lastApply = {
277455
+ accepted: false,
277456
+ targetVersion: null,
277457
+ restarting: false,
277458
+ message: `Update failed: ${message}`
277459
+ };
277460
+ onError?.(message);
277461
+ }).finally(() => {
277462
+ applyInFlight = false;
277463
+ });
277464
+ return { started: true, message: null };
277465
+ }
277466
+ };
277467
+ }
277468
+ exports.SERVER_UPDATE_UNAVAILABLE_REASON = "Framework updates are not available on this agent build.";
277469
+ exports.ADDON_UPDATE_UNAVAILABLE_REASON = "The addon roster is not available on this agent build.";
277470
+ function registerAgentUpdateRoutes(app, controls) {
277471
+ const server = controls.server;
277472
+ const addons = controls.addons;
277473
+ app.get("/api/agent/server-update", async () => {
277474
+ if (server === void 0) {
277475
+ return { available: false, reason: exports.SERVER_UPDATE_UNAVAILABLE_REASON };
277476
+ }
277477
+ return { available: true, ...await server.getView() };
277478
+ });
277479
+ app.post("/api/agent/server-update/check", async (_req, reply) => {
277480
+ if (server === void 0) {
277481
+ return reply.status(503).send({ error: exports.SERVER_UPDATE_UNAVAILABLE_REASON });
277482
+ }
277483
+ return server.check();
277484
+ });
277485
+ app.post("/api/agent/server-update/apply", async (_req, reply) => {
277486
+ if (server === void 0) {
277487
+ return reply.status(503).send({ error: exports.SERVER_UPDATE_UNAVAILABLE_REASON });
277488
+ }
277489
+ return server.applyLatest();
277490
+ });
277491
+ app.get("/api/agent/addon-updates", async () => {
277492
+ if (addons === void 0) {
277493
+ return { available: false, reason: exports.ADDON_UPDATE_UNAVAILABLE_REASON };
277494
+ }
277495
+ return { available: true, ...await addons.getReport() };
277496
+ });
277497
+ }
277498
+ }
277499
+ });
277500
+
277300
277501
  // ../../server/backend/dist/agent/derive-hub-url.js
277301
277502
  var require_derive_hub_url = __commonJS({
277302
277503
  "../../server/backend/dist/agent/derive-hub-url.js"(exports) {
@@ -283473,6 +283674,7 @@ var require_agent_http = __commonJS({
283473
283674
  var path = __importStar(__require("path"));
283474
283675
  var fastify_1 = __importDefault(require_fastify());
283475
283676
  var agent_http_auth_js_1 = require_agent_http_auth();
283677
+ var agent_update_routes_js_1 = require_agent_update_routes();
283476
283678
  var derive_hub_url_js_1 = require_derive_hub_url();
283477
283679
  Object.defineProperty(exports, "pickIpv4", { enumerable: true, get: function() {
283478
283680
  return derive_hub_url_js_1.pickIpv4;
@@ -283720,6 +283922,7 @@ var require_agent_http = __commonJS({
283720
283922
  });
283721
283923
  return { success: true, message: "Agent reconnecting..." };
283722
283924
  });
283925
+ (0, agent_update_routes_js_1.registerAgentUpdateRoutes)(app, config.updateControls ?? {});
283723
283926
  app.get("/api/agent/discovered-nodes", async () => {
283724
283927
  const b = getBroker();
283725
283928
  const nodes = getRegistryNodes(b);
@@ -375651,312 +375854,6 @@ var require_agent_service = __commonJS({
375651
375854
  }
375652
375855
  });
375653
375856
 
375654
- // ../../server/backend/dist/core/server-update/system-ensure-prebuilds.js
375655
- var require_system_ensure_prebuilds = __commonJS({
375656
- "../../server/backend/dist/core/server-update/system-ensure-prebuilds.js"(exports) {
375657
- "use strict";
375658
- Object.defineProperty(exports, "__esModule", { value: true });
375659
- exports.buildSystemEnsureNativePrebuilds = buildSystemEnsureNativePrebuilds;
375660
- var system_1 = require_dist3();
375661
- function buildSystemEnsureNativePrebuilds(logger) {
375662
- return async (closureDir) => {
375663
- await (0, system_1.ensureNativePrebuilds)(closureDir, { logger });
375664
- };
375665
- }
375666
- }
375667
- });
375668
-
375669
- // ../../server/backend/dist/core/server-update/system-exec-npm.js
375670
- var require_system_exec_npm = __commonJS({
375671
- "../../server/backend/dist/core/server-update/system-exec-npm.js"(exports) {
375672
- "use strict";
375673
- Object.defineProperty(exports, "__esModule", { value: true });
375674
- exports.buildSystemExecNpm = buildSystemExecNpm;
375675
- var system_1 = require_dist3();
375676
- function buildSystemExecNpm(options, runNpmFn = system_1.runNpm) {
375677
- return async (args, opts) => {
375678
- const { stdout } = await runNpmFn(args, {
375679
- cacheDir: options.cacheDir,
375680
- registry: options.registry,
375681
- logger: options.logger,
375682
- ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
375683
- timeout: opts.timeoutMs
375684
- });
375685
- return { stdout };
375686
- };
375687
- }
375688
- }
375689
- });
375690
-
375691
- // ../../server/backend/dist/core/update-availability-emitter.js
375692
- var require_update_availability_emitter = __commonJS({
375693
- "../../server/backend/dist/core/update-availability-emitter.js"(exports) {
375694
- "use strict";
375695
- Object.defineProperty(exports, "__esModule", { value: true });
375696
- exports.UpdateAvailabilityEmitter = void 0;
375697
- var types_1 = require_dist4();
375698
- var UpdateAvailabilityEmitter = class {
375699
- eventBus;
375700
- source;
375701
- store;
375702
- latestByKey = /* @__PURE__ */ new Map();
375703
- held = /* @__PURE__ */ new Map();
375704
- /** Set when in-memory state diverged from what the store last saw. */
375705
- dirty = false;
375706
- constructor(eventBus, source, store) {
375707
- this.eventBus = eventBus;
375708
- this.source = source;
375709
- this.store = store;
375710
- const persisted = store?.load() ?? null;
375711
- if (persisted === null)
375712
- return;
375713
- for (const [key, version] of Object.entries(persisted.latestByKey)) {
375714
- this.latestByKey.set(key, version);
375715
- }
375716
- for (const [key, candidate] of Object.entries(persisted.held)) {
375717
- this.held.set(key, candidate);
375718
- }
375719
- }
375720
- publishSnapshot(target, candidates, nodeId) {
375721
- const scope = nodeId ?? "hub";
375722
- const present = /* @__PURE__ */ new Set();
375723
- let latestChanged = false;
375724
- for (const candidate of candidates) {
375725
- if (candidate.target !== target)
375726
- continue;
375727
- const stamped = stampNode(candidate, nodeId);
375728
- const key = this.key(stamped);
375729
- present.add(key);
375730
- if (this.note(stamped))
375731
- latestChanged = true;
375732
- }
375733
- for (const key of [...this.held.keys()]) {
375734
- if (!key.startsWith(`${target}:${scope}:`))
375735
- continue;
375736
- if (present.has(key))
375737
- continue;
375738
- this.held.delete(key);
375739
- this.latestByKey.delete(key);
375740
- this.dirty = true;
375741
- }
375742
- this.persistIfDirty();
375743
- if (!latestChanged)
375744
- return;
375745
- this.emitList(target, scope);
375746
- }
375747
- /** Publish a partial candidate set without treating omissions as up-to-date. */
375748
- publishCandidates(candidates) {
375749
- const changed = /* @__PURE__ */ new Map();
375750
- for (const candidate of candidates) {
375751
- if (!this.note(candidate))
375752
- continue;
375753
- const scope = scopeOf(candidate);
375754
- changed.set(`${candidate.target}:${scope}`, { target: candidate.target, scope });
375755
- }
375756
- this.persistIfDirty();
375757
- for (const { target, scope } of changed.values())
375758
- this.emitList(target, scope);
375759
- }
375760
- note(candidate) {
375761
- const key = this.key(candidate);
375762
- const previous = this.latestByKey.get(key);
375763
- const previousHeld = this.held.get(key);
375764
- if (previousHeld === void 0 || previousHeld.currentVersion !== candidate.currentVersion || previousHeld.latestVersion !== candidate.latestVersion || previousHeld.nodeId !== candidate.nodeId) {
375765
- this.dirty = true;
375766
- }
375767
- this.held.set(key, candidate);
375768
- this.latestByKey.set(key, candidate.latestVersion);
375769
- return previous !== candidate.latestVersion;
375770
- }
375771
- /**
375772
- * Flush to the durable store. Called BEFORE the emit so a crash between the
375773
- * two costs a duplicate announcement, never a silent one.
375774
- */
375775
- persistIfDirty() {
375776
- if (!this.dirty)
375777
- return;
375778
- this.dirty = false;
375779
- this.store?.save({
375780
- latestByKey: Object.fromEntries(this.latestByKey),
375781
- held: Object.fromEntries(this.held)
375782
- });
375783
- }
375784
- /** Announce ONE scope's full list. `scope` is the node id, `hub` for the hub. */
375785
- emitList(target, scope) {
375786
- const list = [...this.held.values()].filter((candidate) => candidate.target === target && scopeOf(candidate) === scope);
375787
- if (list.length === 0)
375788
- return;
375789
- const packages = list.map((candidate) => ({
375790
- packageName: candidate.packageName,
375791
- currentVersion: candidate.currentVersion,
375792
- latestVersion: candidate.latestVersion,
375793
- nodeId: scope
375794
- }));
375795
- const head = list[0];
375796
- this.eventBus.emit({
375797
- id: `update.available:${target}:${scope}:${packages.map((pkg) => `${pkg.packageName}@${pkg.latestVersion}`).toSorted().join(",")}`,
375798
- timestamp: /* @__PURE__ */ new Date(),
375799
- source: this.source,
375800
- category: types_1.EventCategory.UpdateAvailable,
375801
- data: {
375802
- target,
375803
- packageName: head.packageName,
375804
- currentVersion: head.currentVersion,
375805
- latestVersion: head.latestVersion,
375806
- packages,
375807
- nodeId: scope,
375808
- nodeIds: [scope]
375809
- }
375810
- });
375811
- }
375812
- key(candidate) {
375813
- return `${candidate.target}:${scopeOf(candidate)}:${candidate.packageName}`;
375814
- }
375815
- };
375816
- exports.UpdateAvailabilityEmitter = UpdateAvailabilityEmitter;
375817
- function scopeOf(candidate) {
375818
- return candidate.nodeId ?? "hub";
375819
- }
375820
- function stampNode(candidate, nodeId) {
375821
- if (nodeId === void 0 || candidate.nodeId !== void 0)
375822
- return candidate;
375823
- return { ...candidate, nodeId };
375824
- }
375825
- }
375826
- });
375827
-
375828
- // ../../server/backend/dist/core/update-availability-store.js
375829
- var require_update_availability_store = __commonJS({
375830
- "../../server/backend/dist/core/update-availability-store.js"(exports) {
375831
- "use strict";
375832
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
375833
- if (k2 === void 0) k2 = k;
375834
- var desc = Object.getOwnPropertyDescriptor(m, k);
375835
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
375836
- desc = { enumerable: true, get: function() {
375837
- return m[k];
375838
- } };
375839
- }
375840
- Object.defineProperty(o, k2, desc);
375841
- }) : (function(o, m, k, k2) {
375842
- if (k2 === void 0) k2 = k;
375843
- o[k2] = m[k];
375844
- }));
375845
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
375846
- Object.defineProperty(o, "default", { enumerable: true, value: v });
375847
- }) : function(o, v) {
375848
- o["default"] = v;
375849
- });
375850
- var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
375851
- var ownKeys = function(o) {
375852
- ownKeys = Object.getOwnPropertyNames || function(o2) {
375853
- var ar = [];
375854
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
375855
- return ar;
375856
- };
375857
- return ownKeys(o);
375858
- };
375859
- return function(mod) {
375860
- if (mod && mod.__esModule) return mod;
375861
- var result = {};
375862
- if (mod != null) {
375863
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
375864
- }
375865
- __setModuleDefault(result, mod);
375866
- return result;
375867
- };
375868
- })();
375869
- Object.defineProperty(exports, "__esModule", { value: true });
375870
- exports.FileUpdateAvailabilityStore = exports.UPDATE_AVAILABILITY_DIR = void 0;
375871
- var fs = __importStar(__require("fs"));
375872
- var path = __importStar(__require("path"));
375873
- var types_1 = require_dist4();
375874
- exports.UPDATE_AVAILABILITY_DIR = "update-availability";
375875
- var FileUpdateAvailabilityStore = class {
375876
- scopeId;
375877
- logger;
375878
- filePath;
375879
- constructor(dataDir, scopeId, logger) {
375880
- this.scopeId = scopeId;
375881
- this.logger = logger;
375882
- this.filePath = path.join(dataDir, exports.UPDATE_AVAILABILITY_DIR, `${scopeId}.json`);
375883
- }
375884
- load() {
375885
- try {
375886
- if (!fs.existsSync(this.filePath))
375887
- return null;
375888
- const parsed = JSON.parse(fs.readFileSync(this.filePath, "utf-8"));
375889
- return parseState(parsed);
375890
- } catch (error) {
375891
- this.logger.warn("Update-availability state unreadable; starting from empty", {
375892
- meta: { scopeId: this.scopeId, path: this.filePath, error: (0, types_1.errMsg)(error) }
375893
- });
375894
- return null;
375895
- }
375896
- }
375897
- save(state) {
375898
- try {
375899
- fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
375900
- const tmp = `${this.filePath}.tmp`;
375901
- fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
375902
- fs.renameSync(tmp, this.filePath);
375903
- } catch (error) {
375904
- this.logger.warn("Failed to persist update-availability state", {
375905
- meta: { scopeId: this.scopeId, path: this.filePath, error: (0, types_1.errMsg)(error) }
375906
- });
375907
- }
375908
- }
375909
- };
375910
- exports.FileUpdateAvailabilityStore = FileUpdateAvailabilityStore;
375911
- function parseState(raw) {
375912
- if (raw === null || typeof raw !== "object")
375913
- return null;
375914
- const latestRaw = Reflect.get(raw, "latestByKey");
375915
- const heldRaw = Reflect.get(raw, "held");
375916
- if (latestRaw === null || typeof latestRaw !== "object")
375917
- return null;
375918
- if (heldRaw === null || typeof heldRaw !== "object")
375919
- return null;
375920
- const latestByKey = {};
375921
- for (const [key, value] of Object.entries(latestRaw)) {
375922
- if (typeof value === "string")
375923
- latestByKey[key] = value;
375924
- }
375925
- const held = {};
375926
- for (const [key, value] of Object.entries(heldRaw)) {
375927
- const candidate = parseCandidate(value);
375928
- if (candidate !== null)
375929
- held[key] = candidate;
375930
- }
375931
- return { latestByKey, held };
375932
- }
375933
- function parseCandidate(raw) {
375934
- if (raw === null || typeof raw !== "object")
375935
- return null;
375936
- const target = Reflect.get(raw, "target");
375937
- const packageName = Reflect.get(raw, "packageName");
375938
- const currentVersion = Reflect.get(raw, "currentVersion");
375939
- const latestVersion = Reflect.get(raw, "latestVersion");
375940
- const nodeId = Reflect.get(raw, "nodeId");
375941
- if (target !== "addon" && target !== "server")
375942
- return null;
375943
- if (typeof packageName !== "string")
375944
- return null;
375945
- if (typeof currentVersion !== "string")
375946
- return null;
375947
- if (typeof latestVersion !== "string")
375948
- return null;
375949
- return {
375950
- target,
375951
- packageName,
375952
- currentVersion,
375953
- latestVersion,
375954
- ...typeof nodeId === "string" ? { nodeId } : {}
375955
- };
375956
- }
375957
- }
375958
- });
375959
-
375960
375857
  // ../../server/backend/dist/server-root/index.js
375961
375858
  var require_server_root = __commonJS({
375962
375859
  "../../server/backend/dist/server-root/index.js"(exports, module) {
@@ -377467,6 +377364,441 @@ var require_server_root = __commonJS({
377467
377364
  }
377468
377365
  });
377469
377366
 
377367
+ // ../../server/backend/dist/agent/agent-addon-updates.js
377368
+ var require_agent_addon_updates = __commonJS({
377369
+ "../../server/backend/dist/agent/agent-addon-updates.js"(exports) {
377370
+ "use strict";
377371
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
377372
+ if (k2 === void 0) k2 = k;
377373
+ var desc = Object.getOwnPropertyDescriptor(m, k);
377374
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
377375
+ desc = { enumerable: true, get: function() {
377376
+ return m[k];
377377
+ } };
377378
+ }
377379
+ Object.defineProperty(o, k2, desc);
377380
+ }) : (function(o, m, k, k2) {
377381
+ if (k2 === void 0) k2 = k;
377382
+ o[k2] = m[k];
377383
+ }));
377384
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
377385
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
377386
+ }) : function(o, v) {
377387
+ o["default"] = v;
377388
+ });
377389
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
377390
+ var ownKeys = function(o) {
377391
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
377392
+ var ar = [];
377393
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
377394
+ return ar;
377395
+ };
377396
+ return ownKeys(o);
377397
+ };
377398
+ return function(mod) {
377399
+ if (mod && mod.__esModule) return mod;
377400
+ var result = {};
377401
+ if (mod != null) {
377402
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
377403
+ }
377404
+ __setModuleDefault(result, mod);
377405
+ return result;
377406
+ };
377407
+ })();
377408
+ Object.defineProperty(exports, "__esModule", { value: true });
377409
+ exports.DEFAULT_NPM_REGISTRY = void 0;
377410
+ exports.readInstalledAddonPackages = readInstalledAddonPackages;
377411
+ exports.buildAddonUpdateEntries = buildAddonUpdateEntries;
377412
+ exports.resolvePublishedVersion = resolvePublishedVersion;
377413
+ exports.buildAgentAddonUpdateReport = buildAgentAddonUpdateReport;
377414
+ var fs = __importStar(__require("fs"));
377415
+ var path = __importStar(__require("path"));
377416
+ var index_js_1 = require_server_root();
377417
+ var addon_scan_js_1 = require_addon_scan();
377418
+ exports.DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
377419
+ function readPackageIdentity(dir) {
377420
+ try {
377421
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
377422
+ if (typeof parsed.name !== "string" || parsed.name.length === 0)
377423
+ return null;
377424
+ return {
377425
+ packageName: parsed.name,
377426
+ installedVersion: typeof parsed.version === "string" ? parsed.version : null
377427
+ };
377428
+ } catch {
377429
+ return null;
377430
+ }
377431
+ }
377432
+ function readInstalledAddonPackages(addonsDir) {
377433
+ const found = [];
377434
+ for (const dir of (0, addon_scan_js_1.resolveAddonPackageDirs)(addonsDir)) {
377435
+ const identity = readPackageIdentity(dir);
377436
+ if (identity !== null)
377437
+ found.push(identity);
377438
+ }
377439
+ return found.sort((a, b) => a.packageName.localeCompare(b.packageName));
377440
+ }
377441
+ function buildAddonUpdateEntries(installed, published) {
377442
+ return installed.map((pkg) => {
377443
+ const latest = published.get(pkg.packageName);
377444
+ const latestVersion = latest?.latestVersion ?? null;
377445
+ const updateAvailable = latestVersion !== null && pkg.installedVersion !== null && (0, index_js_1.compareSemver)(latestVersion, pkg.installedVersion) > 0;
377446
+ return {
377447
+ packageName: pkg.packageName,
377448
+ installedVersion: pkg.installedVersion,
377449
+ latestVersion,
377450
+ updateAvailable,
377451
+ error: latest?.error ?? null
377452
+ };
377453
+ });
377454
+ }
377455
+ async function resolvePublishedVersion(packageName, registryUrl, fetchFn) {
377456
+ const base = registryUrl.replace(/\/+$/, "");
377457
+ const url = `${base}/${encodeURIComponent(packageName).replace(/^%40/, "@")}`;
377458
+ try {
377459
+ const res = await fetchFn(url);
377460
+ if (!res.ok)
377461
+ return { latestVersion: null, error: `registry GET \u2192 ${res.status}` };
377462
+ const body = await res.json();
377463
+ const latest = body["dist-tags"]?.["latest"];
377464
+ if (typeof latest !== "string" || latest.length === 0) {
377465
+ return { latestVersion: null, error: "registry returned no dist-tags.latest" };
377466
+ }
377467
+ return { latestVersion: latest, error: null };
377468
+ } catch (err) {
377469
+ return { latestVersion: null, error: err instanceof Error ? err.message : String(err) };
377470
+ }
377471
+ }
377472
+ async function buildAgentAddonUpdateReport(input) {
377473
+ const now = input.now ?? Date.now;
377474
+ const registryUrl = input.registryUrl ?? exports.DEFAULT_NPM_REGISTRY;
377475
+ const installed = readInstalledAddonPackages(input.addonsDir);
377476
+ const published = /* @__PURE__ */ new Map();
377477
+ const resolved = await Promise.all(installed.map(async (pkg) => ({
377478
+ packageName: pkg.packageName,
377479
+ result: await resolvePublishedVersion(pkg.packageName, registryUrl, input.fetchFn)
377480
+ })));
377481
+ for (const r of resolved)
377482
+ published.set(r.packageName, r.result);
377483
+ const entries = buildAddonUpdateEntries(installed, published);
377484
+ const allFailed = entries.length > 0 && entries.every((e) => e.latestVersion === null);
377485
+ const firstError = entries.find((e) => e.error !== null)?.error ?? "registry unreachable";
377486
+ return {
377487
+ entries,
377488
+ checkedAtMs: now(),
377489
+ error: allFailed ? `Could not reach ${registryUrl}: ${firstError}` : null,
377490
+ hubReachable: input.isHubReachable()
377491
+ };
377492
+ }
377493
+ }
377494
+ });
377495
+
377496
+ // ../../server/backend/dist/core/server-update/system-ensure-prebuilds.js
377497
+ var require_system_ensure_prebuilds = __commonJS({
377498
+ "../../server/backend/dist/core/server-update/system-ensure-prebuilds.js"(exports) {
377499
+ "use strict";
377500
+ Object.defineProperty(exports, "__esModule", { value: true });
377501
+ exports.buildSystemEnsureNativePrebuilds = buildSystemEnsureNativePrebuilds;
377502
+ var system_1 = require_dist3();
377503
+ function buildSystemEnsureNativePrebuilds(logger) {
377504
+ return async (closureDir) => {
377505
+ await (0, system_1.ensureNativePrebuilds)(closureDir, { logger });
377506
+ };
377507
+ }
377508
+ }
377509
+ });
377510
+
377511
+ // ../../server/backend/dist/core/server-update/system-exec-npm.js
377512
+ var require_system_exec_npm = __commonJS({
377513
+ "../../server/backend/dist/core/server-update/system-exec-npm.js"(exports) {
377514
+ "use strict";
377515
+ Object.defineProperty(exports, "__esModule", { value: true });
377516
+ exports.buildSystemExecNpm = buildSystemExecNpm;
377517
+ var system_1 = require_dist3();
377518
+ function buildSystemExecNpm(options, runNpmFn = system_1.runNpm) {
377519
+ return async (args, opts) => {
377520
+ const { stdout } = await runNpmFn(args, {
377521
+ cacheDir: options.cacheDir,
377522
+ registry: options.registry,
377523
+ logger: options.logger,
377524
+ ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
377525
+ timeout: opts.timeoutMs
377526
+ });
377527
+ return { stdout };
377528
+ };
377529
+ }
377530
+ }
377531
+ });
377532
+
377533
+ // ../../server/backend/dist/core/update-availability-emitter.js
377534
+ var require_update_availability_emitter = __commonJS({
377535
+ "../../server/backend/dist/core/update-availability-emitter.js"(exports) {
377536
+ "use strict";
377537
+ Object.defineProperty(exports, "__esModule", { value: true });
377538
+ exports.UpdateAvailabilityEmitter = void 0;
377539
+ var types_1 = require_dist4();
377540
+ var UpdateAvailabilityEmitter = class {
377541
+ eventBus;
377542
+ source;
377543
+ store;
377544
+ latestByKey = /* @__PURE__ */ new Map();
377545
+ held = /* @__PURE__ */ new Map();
377546
+ /** Set when in-memory state diverged from what the store last saw. */
377547
+ dirty = false;
377548
+ constructor(eventBus, source, store) {
377549
+ this.eventBus = eventBus;
377550
+ this.source = source;
377551
+ this.store = store;
377552
+ const persisted = store?.load() ?? null;
377553
+ if (persisted === null)
377554
+ return;
377555
+ for (const [key, version] of Object.entries(persisted.latestByKey)) {
377556
+ this.latestByKey.set(key, version);
377557
+ }
377558
+ for (const [key, candidate] of Object.entries(persisted.held)) {
377559
+ this.held.set(key, candidate);
377560
+ }
377561
+ }
377562
+ publishSnapshot(target, candidates, nodeId) {
377563
+ const scope = nodeId ?? "hub";
377564
+ const present = /* @__PURE__ */ new Set();
377565
+ let latestChanged = false;
377566
+ for (const candidate of candidates) {
377567
+ if (candidate.target !== target)
377568
+ continue;
377569
+ const stamped = stampNode(candidate, nodeId);
377570
+ const key = this.key(stamped);
377571
+ present.add(key);
377572
+ if (this.note(stamped))
377573
+ latestChanged = true;
377574
+ }
377575
+ for (const key of [...this.held.keys()]) {
377576
+ if (!key.startsWith(`${target}:${scope}:`))
377577
+ continue;
377578
+ if (present.has(key))
377579
+ continue;
377580
+ this.held.delete(key);
377581
+ this.latestByKey.delete(key);
377582
+ this.dirty = true;
377583
+ }
377584
+ this.persistIfDirty();
377585
+ if (!latestChanged)
377586
+ return;
377587
+ this.emitList(target, scope);
377588
+ }
377589
+ /** Publish a partial candidate set without treating omissions as up-to-date. */
377590
+ publishCandidates(candidates) {
377591
+ const changed = /* @__PURE__ */ new Map();
377592
+ for (const candidate of candidates) {
377593
+ if (!this.note(candidate))
377594
+ continue;
377595
+ const scope = scopeOf(candidate);
377596
+ changed.set(`${candidate.target}:${scope}`, { target: candidate.target, scope });
377597
+ }
377598
+ this.persistIfDirty();
377599
+ for (const { target, scope } of changed.values())
377600
+ this.emitList(target, scope);
377601
+ }
377602
+ note(candidate) {
377603
+ const key = this.key(candidate);
377604
+ const previous = this.latestByKey.get(key);
377605
+ const previousHeld = this.held.get(key);
377606
+ if (previousHeld === void 0 || previousHeld.currentVersion !== candidate.currentVersion || previousHeld.latestVersion !== candidate.latestVersion || previousHeld.nodeId !== candidate.nodeId) {
377607
+ this.dirty = true;
377608
+ }
377609
+ this.held.set(key, candidate);
377610
+ this.latestByKey.set(key, candidate.latestVersion);
377611
+ return previous !== candidate.latestVersion;
377612
+ }
377613
+ /**
377614
+ * Flush to the durable store. Called BEFORE the emit so a crash between the
377615
+ * two costs a duplicate announcement, never a silent one.
377616
+ */
377617
+ persistIfDirty() {
377618
+ if (!this.dirty)
377619
+ return;
377620
+ this.dirty = false;
377621
+ this.store?.save({
377622
+ latestByKey: Object.fromEntries(this.latestByKey),
377623
+ held: Object.fromEntries(this.held)
377624
+ });
377625
+ }
377626
+ /** Announce ONE scope's full list. `scope` is the node id, `hub` for the hub. */
377627
+ emitList(target, scope) {
377628
+ const list = [...this.held.values()].filter((candidate) => candidate.target === target && scopeOf(candidate) === scope);
377629
+ if (list.length === 0)
377630
+ return;
377631
+ const packages = list.map((candidate) => ({
377632
+ packageName: candidate.packageName,
377633
+ currentVersion: candidate.currentVersion,
377634
+ latestVersion: candidate.latestVersion,
377635
+ nodeId: scope
377636
+ }));
377637
+ const head = list[0];
377638
+ this.eventBus.emit({
377639
+ id: `update.available:${target}:${scope}:${packages.map((pkg) => `${pkg.packageName}@${pkg.latestVersion}`).toSorted().join(",")}`,
377640
+ timestamp: /* @__PURE__ */ new Date(),
377641
+ source: this.source,
377642
+ category: types_1.EventCategory.UpdateAvailable,
377643
+ data: {
377644
+ target,
377645
+ packageName: head.packageName,
377646
+ currentVersion: head.currentVersion,
377647
+ latestVersion: head.latestVersion,
377648
+ packages,
377649
+ nodeId: scope,
377650
+ nodeIds: [scope]
377651
+ }
377652
+ });
377653
+ }
377654
+ key(candidate) {
377655
+ return `${candidate.target}:${scopeOf(candidate)}:${candidate.packageName}`;
377656
+ }
377657
+ };
377658
+ exports.UpdateAvailabilityEmitter = UpdateAvailabilityEmitter;
377659
+ function scopeOf(candidate) {
377660
+ return candidate.nodeId ?? "hub";
377661
+ }
377662
+ function stampNode(candidate, nodeId) {
377663
+ if (nodeId === void 0 || candidate.nodeId !== void 0)
377664
+ return candidate;
377665
+ return { ...candidate, nodeId };
377666
+ }
377667
+ }
377668
+ });
377669
+
377670
+ // ../../server/backend/dist/core/update-availability-store.js
377671
+ var require_update_availability_store = __commonJS({
377672
+ "../../server/backend/dist/core/update-availability-store.js"(exports) {
377673
+ "use strict";
377674
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
377675
+ if (k2 === void 0) k2 = k;
377676
+ var desc = Object.getOwnPropertyDescriptor(m, k);
377677
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
377678
+ desc = { enumerable: true, get: function() {
377679
+ return m[k];
377680
+ } };
377681
+ }
377682
+ Object.defineProperty(o, k2, desc);
377683
+ }) : (function(o, m, k, k2) {
377684
+ if (k2 === void 0) k2 = k;
377685
+ o[k2] = m[k];
377686
+ }));
377687
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
377688
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
377689
+ }) : function(o, v) {
377690
+ o["default"] = v;
377691
+ });
377692
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
377693
+ var ownKeys = function(o) {
377694
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
377695
+ var ar = [];
377696
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
377697
+ return ar;
377698
+ };
377699
+ return ownKeys(o);
377700
+ };
377701
+ return function(mod) {
377702
+ if (mod && mod.__esModule) return mod;
377703
+ var result = {};
377704
+ if (mod != null) {
377705
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
377706
+ }
377707
+ __setModuleDefault(result, mod);
377708
+ return result;
377709
+ };
377710
+ })();
377711
+ Object.defineProperty(exports, "__esModule", { value: true });
377712
+ exports.FileUpdateAvailabilityStore = exports.UPDATE_AVAILABILITY_DIR = void 0;
377713
+ var fs = __importStar(__require("fs"));
377714
+ var path = __importStar(__require("path"));
377715
+ var types_1 = require_dist4();
377716
+ exports.UPDATE_AVAILABILITY_DIR = "update-availability";
377717
+ var FileUpdateAvailabilityStore = class {
377718
+ scopeId;
377719
+ logger;
377720
+ filePath;
377721
+ constructor(dataDir, scopeId, logger) {
377722
+ this.scopeId = scopeId;
377723
+ this.logger = logger;
377724
+ this.filePath = path.join(dataDir, exports.UPDATE_AVAILABILITY_DIR, `${scopeId}.json`);
377725
+ }
377726
+ load() {
377727
+ try {
377728
+ if (!fs.existsSync(this.filePath))
377729
+ return null;
377730
+ const parsed = JSON.parse(fs.readFileSync(this.filePath, "utf-8"));
377731
+ return parseState(parsed);
377732
+ } catch (error) {
377733
+ this.logger.warn("Update-availability state unreadable; starting from empty", {
377734
+ meta: { scopeId: this.scopeId, path: this.filePath, error: (0, types_1.errMsg)(error) }
377735
+ });
377736
+ return null;
377737
+ }
377738
+ }
377739
+ save(state) {
377740
+ try {
377741
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
377742
+ const tmp = `${this.filePath}.tmp`;
377743
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
377744
+ fs.renameSync(tmp, this.filePath);
377745
+ } catch (error) {
377746
+ this.logger.warn("Failed to persist update-availability state", {
377747
+ meta: { scopeId: this.scopeId, path: this.filePath, error: (0, types_1.errMsg)(error) }
377748
+ });
377749
+ }
377750
+ }
377751
+ };
377752
+ exports.FileUpdateAvailabilityStore = FileUpdateAvailabilityStore;
377753
+ function parseState(raw) {
377754
+ if (raw === null || typeof raw !== "object")
377755
+ return null;
377756
+ const latestRaw = Reflect.get(raw, "latestByKey");
377757
+ const heldRaw = Reflect.get(raw, "held");
377758
+ if (latestRaw === null || typeof latestRaw !== "object")
377759
+ return null;
377760
+ if (heldRaw === null || typeof heldRaw !== "object")
377761
+ return null;
377762
+ const latestByKey = {};
377763
+ for (const [key, value] of Object.entries(latestRaw)) {
377764
+ if (typeof value === "string")
377765
+ latestByKey[key] = value;
377766
+ }
377767
+ const held = {};
377768
+ for (const [key, value] of Object.entries(heldRaw)) {
377769
+ const candidate = parseCandidate(value);
377770
+ if (candidate !== null)
377771
+ held[key] = candidate;
377772
+ }
377773
+ return { latestByKey, held };
377774
+ }
377775
+ function parseCandidate(raw) {
377776
+ if (raw === null || typeof raw !== "object")
377777
+ return null;
377778
+ const target = Reflect.get(raw, "target");
377779
+ const packageName = Reflect.get(raw, "packageName");
377780
+ const currentVersion = Reflect.get(raw, "currentVersion");
377781
+ const latestVersion = Reflect.get(raw, "latestVersion");
377782
+ const nodeId = Reflect.get(raw, "nodeId");
377783
+ if (target !== "addon" && target !== "server")
377784
+ return null;
377785
+ if (typeof packageName !== "string")
377786
+ return null;
377787
+ if (typeof currentVersion !== "string")
377788
+ return null;
377789
+ if (typeof latestVersion !== "string")
377790
+ return null;
377791
+ return {
377792
+ target,
377793
+ packageName,
377794
+ currentVersion,
377795
+ latestVersion,
377796
+ ...typeof nodeId === "string" ? { nodeId } : {}
377797
+ };
377798
+ }
377799
+ }
377800
+ });
377801
+
377470
377802
  // ../../server/backend/dist/core/updates/restart-marker.js
377471
377803
  var require_restart_marker = __commonJS({
377472
377804
  "../../server/backend/dist/core/updates/restart-marker.js"(exports) {
@@ -377921,6 +378253,8 @@ var require_main2 = __commonJS({
377921
378253
  var agent_group_runner_js_1 = require_agent_group_runner();
377922
378254
  var agent_http_js_1 = require_agent_http();
377923
378255
  var agent_service_js_1 = require_agent_service();
378256
+ var agent_addon_updates_js_1 = require_agent_addon_updates();
378257
+ var agent_update_routes_js_1 = require_agent_update_routes();
377924
378258
  var agent_update_service_js_1 = require_agent_update_service();
377925
378259
  var derive_hub_url_js_1 = require_derive_hub_url();
377926
378260
  var infra_boot_guard_js_1 = require_infra_boot_guard();
@@ -378329,13 +378663,29 @@ var require_main2 = __commonJS({
378329
378663
  broker.createService((0, system_1.createReadinessServiceForRegistry)(agentReadinessRegistry, system_1.AGENT_READINESS_SERVICE_NAME));
378330
378664
  }
378331
378665
  const getBrokerFn = (() => broker);
378666
+ const updateControls = {
378667
+ server: (0, agent_update_routes_js_1.createAgentServerUpdateControls)(agentUpdateService, (message) => {
378668
+ consoleLogger.warn(`framework update not applied: ${message}`, {
378669
+ tags: { nodeId: config.nodeId }
378670
+ });
378671
+ }),
378672
+ addons: {
378673
+ getReport: () => (0, agent_addon_updates_js_1.buildAgentAddonUpdateReport)({
378674
+ addonsDir: config.addonsDir,
378675
+ ...process.env["CAMSTACK_NPM_REGISTRY"] !== void 0 ? { registryUrl: process.env["CAMSTACK_NPM_REGISTRY"] } : {},
378676
+ fetchFn: (url) => fetch(url),
378677
+ isHubReachable: () => getHubConnectionState() === "connected"
378678
+ })
378679
+ }
378680
+ };
378332
378681
  void (0, agent_http_js_1.startAgentHttpServer)(getBrokerFn, {
378333
378682
  port: config.statusPort ?? 4444,
378334
378683
  nodeId: config.nodeId,
378335
378684
  dataDir: config.dataDir,
378336
378685
  configPath: config.configPath,
378337
378686
  onReconnect: reconnect,
378338
- getHubConnectionState
378687
+ getHubConnectionState,
378688
+ updateControls
378339
378689
  });
378340
378690
  await bootCoreAddons(broker, config, capabilityRegistry, loadedAddons, loggerFactory);
378341
378691
  for (const dest of capabilityRegistry.getCollection("log-destination")) {
@@ -401027,6 +401377,7 @@ var require_http_request_census = __commonJS({
401027
401377
  "use strict";
401028
401378
  Object.defineProperty(exports, "__esModule", { value: true });
401029
401379
  exports.HttpRequestCensus = exports.UNKNOWN = exports.PROCEDURE_OVERFLOW = exports.BATCH_OVERFLOW_PROCEDURE = exports.MAX_BATCH_PROCEDURES = exports.USER_AGENT_MAX_CHARS = exports.MAX_PROCEDURES = exports.MAX_GROUPS = exports.MIN_REPORT_EVERY_MS = exports.DEFAULT_REPORT_EVERY_MS = exports.MIN_WINDOW_MS = exports.MAX_WINDOW_MS = exports.TRPC_URL_PREFIX = void 0;
401380
+ exports.zeroPlaneCounts = zeroPlaneCounts;
401030
401381
  exports.shortenUserAgent = shortenUserAgent;
401031
401382
  exports.parseTrpcProcedures = parseTrpcProcedures;
401032
401383
  exports.TRPC_URL_PREFIX = "/trpc/";
@@ -401042,6 +401393,15 @@ var require_http_request_census = __commonJS({
401042
401393
  exports.PROCEDURE_OVERFLOW = "(procedure-overflow)";
401043
401394
  exports.UNKNOWN = "unknown";
401044
401395
  var KEY_SEPARATOR = " | ";
401396
+ function zeroPlaneCounts() {
401397
+ return { http: 0, ws: 0, mesh: 0, unknown: 0 };
401398
+ }
401399
+ function freezePlaneCounts(counts) {
401400
+ return { http: counts.http, ws: counts.ws, mesh: counts.mesh, unknown: counts.unknown };
401401
+ }
401402
+ function sumPlaneCounts(counts) {
401403
+ return counts.http + counts.ws + counts.mesh + counts.unknown;
401404
+ }
401045
401405
  function defaultSchedule(fn, ms) {
401046
401406
  setTimeout(fn, ms).unref();
401047
401407
  }
@@ -401131,7 +401491,11 @@ var require_http_request_census = __commonJS({
401131
401491
  batchedRequests = 0;
401132
401492
  procedureCalls = 0;
401133
401493
  wsConnections = 0;
401494
+ wsMessages = 0;
401495
+ subscriptions = 0;
401496
+ subscriptionStops = 0;
401134
401497
  unattributedCalls = 0;
401498
+ planeCalls = zeroPlaneCounts();
401135
401499
  byProcedure = /* @__PURE__ */ new Map();
401136
401500
  byGroup = /* @__PURE__ */ new Map();
401137
401501
  logger = null;
@@ -401199,38 +401563,85 @@ var require_http_request_census = __commonJS({
401199
401563
  this.httpRequests += 1;
401200
401564
  if (observation.procedures.length > 1)
401201
401565
  this.batchedRequests += 1;
401566
+ this.observe({ ...observation, plane: "http", kind: "call" });
401567
+ }
401568
+ /**
401569
+ * Count operations on ANY plane.
401570
+ *
401571
+ * The plane is data rather than a method name so that a plane added later
401572
+ * cannot quietly reuse the HTTP bookkeeping (`httpRequests`,
401573
+ * `batchedRequests`), which counts REQUESTS and means nothing off HTTP.
401574
+ */
401575
+ observe(observation) {
401576
+ if (!this.isArmed)
401577
+ return;
401578
+ if (observation.procedures.length === 0)
401579
+ return;
401202
401580
  const userAgent = shortenUserAgent(observation.userAgent);
401203
401581
  const ip = observation.ip ?? exports.UNKNOWN;
401204
- const principal = observation.principal;
401582
+ const { plane, kind, principal } = observation;
401205
401583
  for (const procedure of observation.procedures) {
401206
- this.procedureCalls += 1;
401207
- this.countProcedure(procedure);
401208
- const key = [procedure, userAgent, ip, principal].join(KEY_SEPARATOR);
401584
+ if (kind === "subscription")
401585
+ this.subscriptions += 1;
401586
+ else {
401587
+ this.procedureCalls += 1;
401588
+ this.planeCalls[plane] += 1;
401589
+ }
401590
+ this.countProcedure(procedure, plane, kind);
401591
+ const key = [plane, procedure, userAgent, ip, principal].join(KEY_SEPARATOR);
401209
401592
  const group = this.byGroup.get(key);
401210
401593
  if (group !== void 0) {
401211
- group.calls += 1;
401594
+ if (kind === "subscription")
401595
+ group.subscriptions += 1;
401596
+ else
401597
+ group.calls += 1;
401212
401598
  continue;
401213
401599
  }
401214
401600
  if (this.byGroup.size >= exports.MAX_GROUPS) {
401215
401601
  this.unattributedCalls += 1;
401216
401602
  continue;
401217
401603
  }
401218
- this.byGroup.set(key, { procedure, userAgent, ip, principal, calls: 1 });
401604
+ this.byGroup.set(key, {
401605
+ plane,
401606
+ procedure,
401607
+ userAgent,
401608
+ ip,
401609
+ principal,
401610
+ calls: kind === "subscription" ? 0 : 1,
401611
+ subscriptions: kind === "subscription" ? 1 : 0
401612
+ });
401219
401613
  }
401220
401614
  }
401221
401615
  /**
401222
401616
  * Count one tRPC WebSocket connection opening.
401223
401617
  *
401224
- * Deliberately NOT a call count: the WS adapter resolves one context per
401225
- * connection, and per-message procedure names never reach this layer. It is
401226
- * here so a near-zero HTTP total can be read as "the traffic went somewhere
401227
- * else" rather than as "the instrument was off".
401618
+ * NOT a call count - the WS adapter resolves one context per connection.
401619
+ * Kept alongside the per-operation `ws` plane because "37 connections, 0
401620
+ * calls" and "0 connections, 0 calls" are different answers.
401228
401621
  */
401229
401622
  observeWsConnection() {
401230
401623
  if (!this.isArmed)
401231
401624
  return;
401232
401625
  this.wsConnections += 1;
401233
401626
  }
401627
+ /** Count one client frame the WS plane inspected, operation or not. */
401628
+ observeWsMessage() {
401629
+ if (!this.isArmed)
401630
+ return;
401631
+ this.wsMessages += 1;
401632
+ }
401633
+ /**
401634
+ * Count `subscription.stop` frames.
401635
+ *
401636
+ * Stops carry no procedure path on the wire (tRPC's stop message is `{ id }`
401637
+ * and nothing else), so they are a scalar rather than a group row - inventing
401638
+ * a path for them would put a name on the line that the client never sent.
401639
+ */
401640
+ observeSubscriptionStop(count = 1) {
401641
+ if (!this.isArmed)
401642
+ return;
401643
+ this.subscriptionStops += count;
401644
+ }
401234
401645
  /** Everything the window measured, busiest first. */
401235
401646
  snapshot(limit = exports.MAX_GROUPS) {
401236
401647
  const elapsedMs = this.elapsedMs();
@@ -401238,16 +401649,20 @@ var require_http_request_census = __commonJS({
401238
401649
  const procedures = [...this.byProcedure.entries()].map(([procedure, count]) => ({
401239
401650
  procedure,
401240
401651
  calls: count.calls,
401652
+ planes: freezePlaneCounts(count.planes),
401653
+ subscriptions: count.subscriptions,
401241
401654
  perMin: perMin(count.calls)
401242
401655
  })).toSorted((a, b) => b.calls - a.calls);
401243
401656
  const groups = [...this.byGroup.values()].map((group) => ({
401657
+ plane: group.plane,
401244
401658
  procedure: group.procedure,
401245
401659
  userAgent: group.userAgent,
401246
401660
  ip: group.ip,
401247
401661
  principal: group.principal,
401248
401662
  calls: group.calls,
401663
+ subscriptions: group.subscriptions,
401249
401664
  perMin: perMin(group.calls)
401250
- })).toSorted((a, b) => b.calls - a.calls);
401665
+ })).toSorted((a, b) => b.calls + b.subscriptions - (a.calls + a.subscriptions));
401251
401666
  return {
401252
401667
  armed: this.isArmed,
401253
401668
  elapsedMs,
@@ -401256,7 +401671,12 @@ var require_http_request_census = __commonJS({
401256
401671
  httpRequests: this.httpRequests,
401257
401672
  batchedRequests: this.batchedRequests,
401258
401673
  procedureCalls: this.procedureCalls,
401674
+ planes: freezePlaneCounts(this.planeCalls),
401675
+ planesExplainTotal: sumPlaneCounts(this.planeCalls) === this.procedureCalls,
401259
401676
  wsConnections: this.wsConnections,
401677
+ wsMessages: this.wsMessages,
401678
+ subscriptions: this.subscriptions,
401679
+ subscriptionStops: this.subscriptionStops,
401260
401680
  distinctGroups: this.byGroup.size,
401261
401681
  unattributedCalls: this.unattributedCalls,
401262
401682
  procedures: procedures.slice(0, limit),
@@ -401264,55 +401684,71 @@ var require_http_request_census = __commonJS({
401264
401684
  };
401265
401685
  }
401266
401686
  /**
401267
- * Emit ONE aggregated line.
401687
+ * Emit ONE aggregated line - the line that answers the question.
401268
401688
  *
401269
- * `procedures` comes first because that block is what answers "was it HTTP
401270
- * at all". `top` is the classification, and it is the part that may be cut.
401689
+ * Field order is the reading order: `planes` first (which transport), then
401690
+ * `planesExplainTotal` (is any call unaccounted for), then `procedures`
401691
+ * (which procedure, split by plane), then `top` (which caller). `top` is the
401692
+ * part that may be cut; the first three never are.
401693
+ *
401694
+ * The message is `transport census` rather than `http request census`
401695
+ * because the line now carries three planes. Grepping Loki for the old text
401696
+ * would find only pre-2026-08-27 windows, which is the honest outcome: those
401697
+ * windows really did measure one plane.
401271
401698
  */
401272
401699
  report(phase) {
401273
401700
  const logger = this.logger;
401274
401701
  if (logger === null)
401275
401702
  return;
401276
401703
  const snap = this.snapshot();
401277
- logger.info("http request census", {
401704
+ logger.info("transport census", {
401278
401705
  meta: {
401279
401706
  phase,
401280
401707
  armed: snap.armed,
401281
401708
  elapsedMs: snap.elapsedMs,
401282
401709
  windowMs: snap.windowMs,
401710
+ planes: snap.planes,
401711
+ procedureCalls: snap.procedureCalls,
401712
+ // The acceptance criterion, ON the line: false means a call was
401713
+ // counted on no plane, i.e. this instrument is missing one.
401714
+ planesExplainTotal: snap.planesExplainTotal,
401715
+ subscriptions: snap.subscriptions,
401716
+ subscriptionStops: snap.subscriptionStops,
401283
401717
  httpRequests: snap.httpRequests,
401284
401718
  batchedRequests: snap.batchedRequests,
401285
- procedureCalls: snap.procedureCalls,
401286
401719
  wsConnections: snap.wsConnections,
401720
+ wsMessages: snap.wsMessages,
401287
401721
  distinctGroups: snap.distinctGroups,
401288
401722
  unattributedCalls: snap.unattributedCalls,
401289
401723
  procedures: snap.procedures.slice(0, REPORTED_PROCEDURES),
401290
401724
  top: snap.groups.slice(0, REPORTED_GROUPS).map((group) => ({
401725
+ plane: group.plane,
401291
401726
  procedure: group.procedure,
401292
401727
  ua: group.userAgent,
401293
401728
  ip: group.ip,
401294
401729
  principal: group.principal,
401295
401730
  calls: group.calls,
401731
+ subscriptions: group.subscriptions,
401296
401732
  perMin: group.perMin
401297
401733
  }))
401298
401734
  }
401299
401735
  });
401300
401736
  }
401301
- countProcedure(procedure) {
401737
+ countProcedure(procedure, plane, kind) {
401302
401738
  const existing = this.byProcedure.get(procedure);
401303
401739
  if (existing !== void 0) {
401304
- existing.calls += 1;
401740
+ bumpCount(existing, plane, kind);
401305
401741
  return;
401306
401742
  }
401307
401743
  if (this.byProcedure.size >= exports.MAX_PROCEDURES) {
401308
- const overflow = this.byProcedure.get(exports.PROCEDURE_OVERFLOW);
401309
- if (overflow === void 0)
401310
- this.byProcedure.set(exports.PROCEDURE_OVERFLOW, { calls: 1 });
401311
- else
401312
- overflow.calls += 1;
401744
+ const overflow = this.byProcedure.get(exports.PROCEDURE_OVERFLOW) ?? newCount();
401745
+ bumpCount(overflow, plane, kind);
401746
+ this.byProcedure.set(exports.PROCEDURE_OVERFLOW, overflow);
401313
401747
  return;
401314
401748
  }
401315
- this.byProcedure.set(procedure, { calls: 1 });
401749
+ const fresh = newCount();
401750
+ bumpCount(fresh, plane, kind);
401751
+ this.byProcedure.set(procedure, fresh);
401316
401752
  }
401317
401753
  elapsedMs() {
401318
401754
  if (this.armedAt === 0)
@@ -401327,7 +401763,11 @@ var require_http_request_census = __commonJS({
401327
401763
  this.batchedRequests = 0;
401328
401764
  this.procedureCalls = 0;
401329
401765
  this.wsConnections = 0;
401766
+ this.wsMessages = 0;
401767
+ this.subscriptions = 0;
401768
+ this.subscriptionStops = 0;
401330
401769
  this.unattributedCalls = 0;
401770
+ this.planeCalls = zeroPlaneCounts();
401331
401771
  }
401332
401772
  scheduleTick(generation) {
401333
401773
  this.schedule(() => {
@@ -401345,6 +401785,17 @@ var require_http_request_census = __commonJS({
401345
401785
  exports.HttpRequestCensus = HttpRequestCensus;
401346
401786
  var REPORTED_PROCEDURES = 40;
401347
401787
  var REPORTED_GROUPS = 25;
401788
+ function newCount() {
401789
+ return { calls: 0, subscriptions: 0, planes: zeroPlaneCounts() };
401790
+ }
401791
+ function bumpCount(count, plane, kind) {
401792
+ if (kind === "subscription") {
401793
+ count.subscriptions += 1;
401794
+ return;
401795
+ }
401796
+ count.calls += 1;
401797
+ count.planes[plane] += 1;
401798
+ }
401348
401799
  function clamp(value, min, max) {
401349
401800
  if (!Number.isFinite(value))
401350
401801
  return min;
@@ -401673,7 +402124,7 @@ var require_request_census_settings = __commonJS({
401673
402124
  updatedAt: this.now()
401674
402125
  };
401675
402126
  const persisted = await this.write(record);
401676
- this.logger?.info("http request census switched", {
402127
+ this.logger?.info("transport census switched", {
401677
402128
  meta: {
401678
402129
  armed: this.census.armed,
401679
402130
  armedUntilMs: this.census.armedUntilMs,
@@ -401698,13 +402149,13 @@ var require_request_census_settings = __commonJS({
401698
402149
  return false;
401699
402150
  const remainingMs = until - this.now();
401700
402151
  if (remainingMs <= 0) {
401701
- this.logger?.info("http request census not restored - window already expired", {
402152
+ this.logger?.info("transport census not restored - window already expired", {
401702
402153
  meta: { armedUntilMs: until }
401703
402154
  });
401704
402155
  return false;
401705
402156
  }
401706
402157
  this.census.arm(remainingMs, record?.reportEveryMs ?? http_request_census_js_1.DEFAULT_REPORT_EVERY_MS);
401707
- this.logger?.info("http request census restored from system-settings", {
402158
+ this.logger?.info("transport census restored from system-settings", {
401708
402159
  meta: { remainingMs, armedUntilMs: this.census.armedUntilMs }
401709
402160
  });
401710
402161
  return true;
@@ -401722,7 +402173,7 @@ var require_request_census_settings = __commonJS({
401722
402173
  });
401723
402174
  return toRecord(raw);
401724
402175
  } catch (err) {
401725
- this.logger?.warn("http request census settings read failed", {
402176
+ this.logger?.warn("transport census settings read failed", {
401726
402177
  meta: { error: (0, types_1.errMsg)(err) }
401727
402178
  });
401728
402179
  return null;
@@ -401737,7 +402188,7 @@ var require_request_census_settings = __commonJS({
401737
402188
  });
401738
402189
  return true;
401739
402190
  } catch (err) {
401740
- this.logger?.warn("http request census armed in memory but NOT persisted - it will not survive a restart", { meta: { error: (0, types_1.errMsg)(err) } });
402191
+ this.logger?.warn("transport census armed in memory but NOT persisted - it will not survive a restart", { meta: { error: (0, types_1.errMsg)(err) } });
401741
402192
  return false;
401742
402193
  }
401743
402194
  }
@@ -403305,6 +403756,499 @@ var require_cap_providers = __commonJS({
403305
403756
  }
403306
403757
  });
403307
403758
 
403759
+ // ../../server/backend/dist/core/auth/share-token.service.js
403760
+ var require_share_token_service = __commonJS({
403761
+ "../../server/backend/dist/core/auth/share-token.service.js"(exports) {
403762
+ "use strict";
403763
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
403764
+ if (k2 === void 0) k2 = k;
403765
+ var desc = Object.getOwnPropertyDescriptor(m, k);
403766
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
403767
+ desc = { enumerable: true, get: function() {
403768
+ return m[k];
403769
+ } };
403770
+ }
403771
+ Object.defineProperty(o, k2, desc);
403772
+ }) : (function(o, m, k, k2) {
403773
+ if (k2 === void 0) k2 = k;
403774
+ o[k2] = m[k];
403775
+ }));
403776
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
403777
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
403778
+ }) : function(o, v) {
403779
+ o["default"] = v;
403780
+ });
403781
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
403782
+ var ownKeys = function(o) {
403783
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
403784
+ var ar = [];
403785
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
403786
+ return ar;
403787
+ };
403788
+ return ownKeys(o);
403789
+ };
403790
+ return function(mod) {
403791
+ if (mod && mod.__esModule) return mod;
403792
+ var result = {};
403793
+ if (mod != null) {
403794
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
403795
+ }
403796
+ __setModuleDefault(result, mod);
403797
+ return result;
403798
+ };
403799
+ })();
403800
+ Object.defineProperty(exports, "__esModule", { value: true });
403801
+ exports.ShareTokenService = exports.ShareTokenRecordSchema = exports.ShareTokenScopeSchema = exports.SHARE_TOKEN_MAX_DEVICES = exports.SHARE_TOKEN_TTL_DEFAULT_SEC = exports.SHARE_TOKEN_TTL_MAX_SEC = exports.SHARE_TOKEN_TTL_MIN_SEC = exports.SHARE_TOKEN_PREFIX = void 0;
403802
+ var crypto2 = __importStar(__require("crypto"));
403803
+ var zod_1 = require_zod();
403804
+ var SHARE_TOKENS_COLLECTION = "share_view_tokens";
403805
+ var SHARE_TOKENS_INDEXES = [
403806
+ { name: "idx_share_view_tokens_hash", columns: ["tokenHash"] },
403807
+ { name: "idx_share_view_tokens_user", columns: ["userId"] }
403808
+ ];
403809
+ exports.SHARE_TOKEN_PREFIX = "csv_";
403810
+ exports.SHARE_TOKEN_TTL_MIN_SEC = 60;
403811
+ exports.SHARE_TOKEN_TTL_MAX_SEC = 30 * 24 * 60 * 60;
403812
+ exports.SHARE_TOKEN_TTL_DEFAULT_SEC = 7 * 24 * 60 * 60;
403813
+ exports.SHARE_TOKEN_MAX_DEVICES = 64;
403814
+ exports.ShareTokenScopeSchema = zod_1.z.object({
403815
+ kind: zod_1.z.enum(["grid-view", "events-view"]),
403816
+ deviceIds: zod_1.z.array(zod_1.z.number().int().nonnegative()).min(1).max(exports.SHARE_TOKEN_MAX_DEVICES)
403817
+ });
403818
+ exports.ShareTokenRecordSchema = zod_1.z.object({
403819
+ id: zod_1.z.string(),
403820
+ /** The user that minted the share link (owner — may revoke it). */
403821
+ userId: zod_1.z.string(),
403822
+ tokenHash: zod_1.z.string(),
403823
+ /** First 12 chars of the raw token — display/audit label only. */
403824
+ tokenPrefix: zod_1.z.string(),
403825
+ scope: exports.ShareTokenScopeSchema,
403826
+ createdAt: zod_1.z.number(),
403827
+ /** Epoch ms — or `null` for a never-expiring token (explicit choice at mint). */
403828
+ expiresAt: zod_1.z.number().nullable(),
403829
+ lastUsedAt: zod_1.z.number().optional()
403830
+ });
403831
+ function parseShareToken(data) {
403832
+ const parsed = exports.ShareTokenRecordSchema.safeParse(data);
403833
+ return parsed.success ? parsed.data : null;
403834
+ }
403835
+ var ShareTokenService = class {
403836
+ getStore;
403837
+ logger;
403838
+ /**
403839
+ * Declaration is one-time per process; reset only if the backend itself
403840
+ * is swapped (it never is at runtime — the getter is lazy solely because
403841
+ * the sqlite builtin registers after this service is constructed).
403842
+ */
403843
+ collectionDeclared = false;
403844
+ constructor(getStore, logger = null) {
403845
+ this.getStore = getStore;
403846
+ this.logger = logger;
403847
+ }
403848
+ /**
403849
+ * Resolve the backend AND ensure `share_view_tokens` is declared before
403850
+ * the first operation. The structured settings backend fail-closes on
403851
+ * undeclared collections; this KV shape (`id` PK + `data` TEXT) is
403852
+ * byte-identical to what the legacy on-demand path created, so existing
403853
+ * rows keep working with no migration.
403854
+ */
403855
+ async store() {
403856
+ const store = this.getStore();
403857
+ if (!store) {
403858
+ throw new Error("Share tokens unavailable \u2014 settings backend not ready");
403859
+ }
403860
+ if (!this.collectionDeclared) {
403861
+ await store.declareCollection({
403862
+ collection: SHARE_TOKENS_COLLECTION,
403863
+ columns: [
403864
+ { name: "id", type: "TEXT", primaryKey: true, notNull: true },
403865
+ { name: "data", type: "TEXT", notNull: true }
403866
+ ],
403867
+ indexes: SHARE_TOKENS_INDEXES
403868
+ });
403869
+ this.collectionDeclared = true;
403870
+ }
403871
+ return store;
403872
+ }
403873
+ /**
403874
+ * Mint a new share token. Throws on invalid scope or out-of-bounds TTL
403875
+ * (fail fast — the router's Zod input schema should have caught both).
403876
+ * `ttlSec: 'never'` is the explicit opt-in to a never-expiring token
403877
+ * (`expiresAt: null`); omitted falls back to the 7-day default.
403878
+ * Returns the RAW token exactly once; only its hash is persisted.
403879
+ */
403880
+ async create(input) {
403881
+ const scope = exports.ShareTokenScopeSchema.parse(input.scope);
403882
+ const ttlSec = input.ttlSec ?? exports.SHARE_TOKEN_TTL_DEFAULT_SEC;
403883
+ if (ttlSec !== "never" && (!Number.isInteger(ttlSec) || ttlSec < exports.SHARE_TOKEN_TTL_MIN_SEC || ttlSec > exports.SHARE_TOKEN_TTL_MAX_SEC)) {
403884
+ throw new Error(`Share token TTL out of bounds \u2014 got ${ttlSec}s, allowed [${exports.SHARE_TOKEN_TTL_MIN_SEC}s, ${exports.SHARE_TOKEN_TTL_MAX_SEC}s] or 'never'`);
403885
+ }
403886
+ const rawToken = `${exports.SHARE_TOKEN_PREFIX}${crypto2.randomBytes(32).toString("hex")}`;
403887
+ const now = Date.now();
403888
+ const record = {
403889
+ id: crypto2.randomUUID(),
403890
+ userId: input.userId,
403891
+ tokenHash: crypto2.createHash("sha256").update(rawToken).digest("hex"),
403892
+ tokenPrefix: rawToken.slice(0, 12),
403893
+ scope,
403894
+ createdAt: now,
403895
+ expiresAt: ttlSec === "never" ? null : now + ttlSec * 1e3
403896
+ };
403897
+ await (await this.store()).insert({
403898
+ collection: SHARE_TOKENS_COLLECTION,
403899
+ record: { id: record.id, data: { ...record } }
403900
+ });
403901
+ this.logger?.info("Share token minted", {
403902
+ meta: {
403903
+ id: record.id,
403904
+ userId: record.userId,
403905
+ kind: scope.kind,
403906
+ deviceIds: scope.deviceIds,
403907
+ expiresAt: record.expiresAt
403908
+ }
403909
+ });
403910
+ return { token: rawToken, record };
403911
+ }
403912
+ /**
403913
+ * Resolve a raw `csv_*` token to its record. Returns `null` for:
403914
+ * wrong prefix, unknown token, corrupt record, or expired token.
403915
+ * Never throws on bad input — the auth boundary treats `null` as 401.
403916
+ */
403917
+ async validate(rawToken) {
403918
+ if (!rawToken.startsWith(exports.SHARE_TOKEN_PREFIX))
403919
+ return null;
403920
+ const tokenHash = crypto2.createHash("sha256").update(rawToken).digest("hex");
403921
+ const results = await (await this.store()).query({
403922
+ collection: SHARE_TOKENS_COLLECTION,
403923
+ filter: { where: { tokenHash } }
403924
+ });
403925
+ const first = results[0];
403926
+ if (!first)
403927
+ return null;
403928
+ const record = parseShareToken(first.data);
403929
+ if (!record)
403930
+ return null;
403931
+ if (record.expiresAt !== null && Date.now() > record.expiresAt)
403932
+ return null;
403933
+ this.touchLastUsed(record).catch(() => {
403934
+ });
403935
+ return record;
403936
+ }
403937
+ /**
403938
+ * Revoke (delete) a share token. Only the minting user or an admin may
403939
+ * revoke. Returns `false` when the token doesn't exist; throws on a
403940
+ * permission mismatch so the router surfaces a FORBIDDEN.
403941
+ */
403942
+ async revoke(input) {
403943
+ const results = await (await this.store()).query({
403944
+ collection: SHARE_TOKENS_COLLECTION,
403945
+ filter: { where: { id: input.id } }
403946
+ });
403947
+ const first = results[0];
403948
+ if (!first)
403949
+ return false;
403950
+ const record = parseShareToken(first.data);
403951
+ if (!record) {
403952
+ await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
403953
+ return true;
403954
+ }
403955
+ if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
403956
+ throw new Error("Only the token owner or an admin can revoke a share token");
403957
+ }
403958
+ await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
403959
+ this.logger?.info("Share token revoked", {
403960
+ meta: { id: record.id, byUserId: input.callerUserId }
403961
+ });
403962
+ return true;
403963
+ }
403964
+ /** Every share token minted by `userId`, expired ones included. */
403965
+ async listForUser(userId) {
403966
+ const results = await (await this.store()).query({
403967
+ collection: SHARE_TOKENS_COLLECTION,
403968
+ filter: { where: { userId } }
403969
+ });
403970
+ return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
403971
+ }
403972
+ /** All share tokens (admin listing). */
403973
+ async listAll() {
403974
+ const results = await (await this.store()).query({
403975
+ collection: SHARE_TOKENS_COLLECTION,
403976
+ filter: {}
403977
+ });
403978
+ return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
403979
+ }
403980
+ async touchLastUsed(record) {
403981
+ await (await this.store()).update({
403982
+ collection: SHARE_TOKENS_COLLECTION,
403983
+ id: record.id,
403984
+ data: { ...record, lastUsedAt: Date.now() }
403985
+ });
403986
+ }
403987
+ };
403988
+ exports.ShareTokenService = ShareTokenService;
403989
+ }
403990
+ });
403991
+
403992
+ // ../../server/backend/dist/api/trpc/trpc-error-principal.js
403993
+ var require_trpc_error_principal = __commonJS({
403994
+ "../../server/backend/dist/api/trpc/trpc-error-principal.js"(exports) {
403995
+ "use strict";
403996
+ Object.defineProperty(exports, "__esModule", { value: true });
403997
+ exports.readCredentialIdentity = readCredentialIdentity;
403998
+ exports.describeTrpcPrincipal = describeTrpcPrincipal;
403999
+ function readCredentialIdentity(claims) {
404000
+ const kind = Reflect.get(claims, "kind");
404001
+ if (kind !== "sso-bridge")
404002
+ return {};
404003
+ const provider = Reflect.get(claims, "provider");
404004
+ const sessionId = Reflect.get(claims, "sessionId");
404005
+ return {
404006
+ ...typeof provider === "string" && provider !== "" ? { credential: provider } : {},
404007
+ ...typeof sessionId === "string" && sessionId !== "" ? { sessionId } : {}
404008
+ };
404009
+ }
404010
+ function describeTrpcPrincipal(user) {
404011
+ if (!user)
404012
+ return "anonymous";
404013
+ const name = user.username || user.id || "unknown";
404014
+ if (user.shareView)
404015
+ return `${name} (share-view)`;
404016
+ if (user.isAdmin)
404017
+ return `${name} (admin)`;
404018
+ if (user.credential) {
404019
+ const session = user.oauthSessionId ? ` session=${user.oauthSessionId}` : "";
404020
+ return `${name} (${user.credential}${session})`;
404021
+ }
404022
+ if (user.isScoped)
404023
+ return `${name} (scoped-token)`;
404024
+ return `${name} (session)`;
404025
+ }
404026
+ }
404027
+ });
404028
+
404029
+ // ../../server/backend/dist/api/trpc/trpc.context.js
404030
+ var require_trpc_context = __commonJS({
404031
+ "../../server/backend/dist/api/trpc/trpc.context.js"(exports) {
404032
+ "use strict";
404033
+ Object.defineProperty(exports, "__esModule", { value: true });
404034
+ exports.createMeshTrpcContext = createMeshTrpcContext;
404035
+ exports.createTrpcContext = createTrpcContext;
404036
+ exports.createWsTrpcContext = createWsTrpcContext;
404037
+ var types_1 = require_dist4();
404038
+ var share_token_service_js_1 = require_share_token_service();
404039
+ var trpc_error_principal_js_1 = require_trpc_error_principal();
404040
+ var SCOPE_REFRESH_TTL_MS = 3e4;
404041
+ var SCOPE_REFRESH_MISS_TTL_MS = 5e3;
404042
+ var liveScopeMemo = /* @__PURE__ */ new Map();
404043
+ async function refreshScopesFromStore(userId, addonRegistry) {
404044
+ const cached = liveScopeMemo.get(userId);
404045
+ const now = Date.now();
404046
+ if (cached && now - cached.at < (cached.scopes === null ? SCOPE_REFRESH_MISS_TTL_MS : SCOPE_REFRESH_TTL_MS)) {
404047
+ return cached.scopes;
404048
+ }
404049
+ let scopes = null;
404050
+ try {
404051
+ const userMgmt = addonRegistry.getCapabilityRegistry().getSingleton("user-management");
404052
+ const fresh = typeof userMgmt?.listUsers === "function" ? (await userMgmt.listUsers()).find((u) => u.id === userId) : null;
404053
+ if (fresh)
404054
+ scopes = (0, types_1.normalizeTokenScopes)(fresh.scopes ?? []);
404055
+ } catch {
404056
+ scopes = null;
404057
+ }
404058
+ liveScopeMemo.set(userId, { at: now, scopes });
404059
+ if (liveScopeMemo.size > 512) {
404060
+ for (const [key, entry] of liveScopeMemo) {
404061
+ if (now - entry.at > SCOPE_REFRESH_TTL_MS * 4)
404062
+ liveScopeMemo.delete(key);
404063
+ }
404064
+ }
404065
+ return scopes;
404066
+ }
404067
+ function readQuery(req) {
404068
+ if (!("query" in req))
404069
+ return null;
404070
+ const q = Reflect.get(req, "query");
404071
+ if (q === null || typeof q !== "object" || Array.isArray(q))
404072
+ return null;
404073
+ return { ...q };
404074
+ }
404075
+ function extractTokenFromRequest(req) {
404076
+ const authHeader = req.headers.authorization;
404077
+ if (authHeader && typeof authHeader === "string") {
404078
+ const [scheme, token2] = authHeader.split(" ");
404079
+ if (scheme === "Bearer" && token2)
404080
+ return token2;
404081
+ }
404082
+ const q = readQuery(req);
404083
+ if (q && typeof q.token === "string") {
404084
+ return q.token;
404085
+ }
404086
+ const url = req.url;
404087
+ if (url) {
404088
+ const qIdx = url.indexOf("?");
404089
+ if (qIdx !== -1) {
404090
+ const t = new URLSearchParams(url.slice(qIdx + 1)).get("token");
404091
+ if (t)
404092
+ return t;
404093
+ }
404094
+ }
404095
+ return null;
404096
+ }
404097
+ async function resolveUser(token2, authService, addonRegistry, shareTokens = null, logger = null) {
404098
+ if (!token2)
404099
+ return null;
404100
+ if (token2.startsWith(share_token_service_js_1.SHARE_TOKEN_PREFIX)) {
404101
+ if (!shareTokens)
404102
+ return null;
404103
+ try {
404104
+ const record = await shareTokens.validate(token2);
404105
+ if (!record)
404106
+ return null;
404107
+ return {
404108
+ id: record.userId,
404109
+ // Display label — `share:<prefix>` keeps audit logs readable
404110
+ // without exposing the token hash.
404111
+ username: `share:${record.tokenPrefix}`,
404112
+ isAdmin: false,
404113
+ permissions: {
404114
+ isAdmin: false,
404115
+ allowedProviders: "*",
404116
+ allowedDevices: {}
404117
+ },
404118
+ isApiKey: true,
404119
+ shareView: { tokenId: record.id, scope: record.scope }
404120
+ };
404121
+ } catch {
404122
+ return null;
404123
+ }
404124
+ }
404125
+ if (token2.startsWith("cst_")) {
404126
+ try {
404127
+ const userMgmt = addonRegistry.getCapabilityRegistry().getSingleton("user-management");
404128
+ if (!userMgmt)
404129
+ return null;
404130
+ const record = await userMgmt.validateScopedToken({ token: token2 });
404131
+ if (!record)
404132
+ return null;
404133
+ return {
404134
+ id: record.userId,
404135
+ // Display label — `scoped:<prefix>` makes audit logs read
404136
+ // naturally without exposing the token hash.
404137
+ username: `scoped:${record.tokenPrefix}`,
404138
+ isAdmin: false,
404139
+ permissions: {
404140
+ isAdmin: false,
404141
+ allowedProviders: "*",
404142
+ allowedDevices: {}
404143
+ },
404144
+ isApiKey: true,
404145
+ isScoped: true,
404146
+ // Migrate any v2 `device:targets` grant to a v3 selector at the
404147
+ // boundary so the enforcement matcher only ever sees v3.
404148
+ scopes: (0, types_1.normalizeTokenScopes)(record.scopes)
404149
+ };
404150
+ } catch {
404151
+ return null;
404152
+ }
404153
+ }
404154
+ try {
404155
+ const payload = authService.verifyToken(token2);
404156
+ if (typeof payload.isAdmin !== "boolean") {
404157
+ return null;
404158
+ }
404159
+ const principal = (0, types_1.classifyBearerPrincipal)(payload);
404160
+ if (principal.kind === "not-a-credential") {
404161
+ logger?.warn("trpc: refused a bearer that is not an API credential", {
404162
+ meta: { reason: principal.reason, userId: payload.userId ?? payload.keyId ?? "unknown" }
404163
+ });
404164
+ return null;
404165
+ }
404166
+ const credential = (0, trpc_error_principal_js_1.readCredentialIdentity)(payload);
404167
+ return {
404168
+ id: payload.userId ?? payload.keyId ?? "unknown",
404169
+ username: payload.username ?? "unknown",
404170
+ isAdmin: payload.isAdmin,
404171
+ permissions: {
404172
+ isAdmin: payload.isAdmin,
404173
+ allowedProviders: payload.allowedProviders,
404174
+ allowedDevices: payload.allowedDevices
404175
+ },
404176
+ isApiKey: payload.type === "api_key",
404177
+ agentId: payload.agentId,
404178
+ // Scopes are baked into the JWT at login; the middleware uses
404179
+ // them to gate every call until the user re-logs. Normalised at the
404180
+ // boundary — a v2 `device:targets` JWT (issued before the v3 model)
404181
+ // migrates to a selector here, so a pre-v3 token keeps working.
404182
+ //
404183
+ // …but a login-time snapshot is exactly the bug: an admin editing a
404184
+ // user's scopes expects the change to APPLY, and the live session
404185
+ // kept gating on the old set until re-login (2026-08-15: a user
404186
+ // granted the whole fleet kept seeing zero cameras). For non-admin
404187
+ // SESSION principals the store's current scopes win when readable
404188
+ // (memoized — see above); the JWT's copy is the fallback when the
404189
+ // user-management cap is unreachable, so a store hiccup never
404190
+ // locks anybody out mid-request.
404191
+ //
404192
+ // `session` ONLY, and that word is the whole fix. An ACCOUNT-LINK token
404193
+ // is governed by the grant the operator consented to, not by the linking
404194
+ // user's record: it always carries `isAdmin: false` (`oauth-grants.ts`
404195
+ // → `baseClaims`) and its owner is normally an admin, whose record is
404196
+ // deliberately unscoped — so the refresh replaced a six-scope Home
404197
+ // Assistant grant with `[]` and the hub refused the component's first
404198
+ // call with `Have: (none)` (2026-08-19). The user store knows nothing
404199
+ // about a link and must not get a vote on one.
404200
+ ...payload.scopes !== void 0 ? { scopes: (0, types_1.normalizeTokenScopes)(payload.scopes) } : {},
404201
+ ...!payload.isAdmin && principal.kind === "session" ? await (async () => {
404202
+ const fresh = await refreshScopesFromStore(payload.userId ?? payload.keyId ?? "unknown", addonRegistry);
404203
+ return fresh !== null ? { scopes: fresh } : {};
404204
+ })() : {},
404205
+ ...credential.credential !== void 0 ? { credential: credential.credential } : {},
404206
+ ...credential.sessionId !== void 0 ? { oauthSessionId: credential.sessionId } : {}
404207
+ };
404208
+ } catch {
404209
+ return null;
404210
+ }
404211
+ }
404212
+ function makeDeviceScopeLookup(addonRegistry) {
404213
+ return {
404214
+ getAncestors: (deviceId) => addonRegistry.getPersistedAncestors(deviceId),
404215
+ getType: (deviceId) => addonRegistry.getPersistedType(deviceId),
404216
+ getLocation: (deviceId) => addonRegistry.getPersistedLocation(deviceId)
404217
+ };
404218
+ }
404219
+ function createMeshTrpcContext() {
404220
+ const user = {
404221
+ id: "mesh",
404222
+ username: "mesh",
404223
+ isAdmin: true,
404224
+ permissions: { isAdmin: true, allowedProviders: "*", allowedDevices: {} },
404225
+ isApiKey: true
404226
+ };
404227
+ return { user };
404228
+ }
404229
+ async function createTrpcContext(req, authService, addonRegistry, shareTokens = null, logger = null) {
404230
+ const token2 = extractTokenFromRequest(req);
404231
+ return {
404232
+ user: await resolveUser(token2, authService, addonRegistry, shareTokens, logger),
404233
+ req,
404234
+ deviceScopeLookup: makeDeviceScopeLookup(addonRegistry),
404235
+ deviceFleet: () => addonRegistry.getPersistedDeviceList()
404236
+ };
404237
+ }
404238
+ async function createWsTrpcContext(opts, authService, addonRegistry, shareTokens = null, logger = null) {
404239
+ const paramToken = opts.info.connectionParams?.["token"];
404240
+ const token2 = (typeof paramToken === "string" ? paramToken : null) ?? extractTokenFromRequest(opts.req);
404241
+ const user = await resolveUser(token2, authService, addonRegistry, shareTokens, logger);
404242
+ return {
404243
+ user,
404244
+ req: opts.req,
404245
+ deviceScopeLookup: makeDeviceScopeLookup(addonRegistry),
404246
+ deviceFleet: () => addonRegistry.getPersistedDeviceList()
404247
+ };
404248
+ }
404249
+ }
404250
+ });
404251
+
403308
404252
  // ../../node_modules/superjson/dist/double-indexed-kv.js
403309
404253
  var DoubleIndexedKV;
403310
404254
  var init_double_indexed_kv = __esm({
@@ -404812,509 +405756,18 @@ var require_trpc_middleware = __commonJS({
404812
405756
  }
404813
405757
  });
404814
405758
 
404815
- // ../../server/backend/dist/core/auth/share-token.service.js
404816
- var require_share_token_service = __commonJS({
404817
- "../../server/backend/dist/core/auth/share-token.service.js"(exports) {
404818
- "use strict";
404819
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
404820
- if (k2 === void 0) k2 = k;
404821
- var desc = Object.getOwnPropertyDescriptor(m, k);
404822
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
404823
- desc = { enumerable: true, get: function() {
404824
- return m[k];
404825
- } };
404826
- }
404827
- Object.defineProperty(o, k2, desc);
404828
- }) : (function(o, m, k, k2) {
404829
- if (k2 === void 0) k2 = k;
404830
- o[k2] = m[k];
404831
- }));
404832
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
404833
- Object.defineProperty(o, "default", { enumerable: true, value: v });
404834
- }) : function(o, v) {
404835
- o["default"] = v;
404836
- });
404837
- var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
404838
- var ownKeys = function(o) {
404839
- ownKeys = Object.getOwnPropertyNames || function(o2) {
404840
- var ar = [];
404841
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
404842
- return ar;
404843
- };
404844
- return ownKeys(o);
404845
- };
404846
- return function(mod) {
404847
- if (mod && mod.__esModule) return mod;
404848
- var result = {};
404849
- if (mod != null) {
404850
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
404851
- }
404852
- __setModuleDefault(result, mod);
404853
- return result;
404854
- };
404855
- })();
404856
- Object.defineProperty(exports, "__esModule", { value: true });
404857
- exports.ShareTokenService = exports.ShareTokenRecordSchema = exports.ShareTokenScopeSchema = exports.SHARE_TOKEN_MAX_DEVICES = exports.SHARE_TOKEN_TTL_DEFAULT_SEC = exports.SHARE_TOKEN_TTL_MAX_SEC = exports.SHARE_TOKEN_TTL_MIN_SEC = exports.SHARE_TOKEN_PREFIX = void 0;
404858
- var crypto2 = __importStar(__require("crypto"));
404859
- var zod_1 = require_zod();
404860
- var SHARE_TOKENS_COLLECTION = "share_view_tokens";
404861
- var SHARE_TOKENS_INDEXES = [
404862
- { name: "idx_share_view_tokens_hash", columns: ["tokenHash"] },
404863
- { name: "idx_share_view_tokens_user", columns: ["userId"] }
404864
- ];
404865
- exports.SHARE_TOKEN_PREFIX = "csv_";
404866
- exports.SHARE_TOKEN_TTL_MIN_SEC = 60;
404867
- exports.SHARE_TOKEN_TTL_MAX_SEC = 30 * 24 * 60 * 60;
404868
- exports.SHARE_TOKEN_TTL_DEFAULT_SEC = 7 * 24 * 60 * 60;
404869
- exports.SHARE_TOKEN_MAX_DEVICES = 64;
404870
- exports.ShareTokenScopeSchema = zod_1.z.object({
404871
- kind: zod_1.z.enum(["grid-view", "events-view"]),
404872
- deviceIds: zod_1.z.array(zod_1.z.number().int().nonnegative()).min(1).max(exports.SHARE_TOKEN_MAX_DEVICES)
404873
- });
404874
- exports.ShareTokenRecordSchema = zod_1.z.object({
404875
- id: zod_1.z.string(),
404876
- /** The user that minted the share link (owner — may revoke it). */
404877
- userId: zod_1.z.string(),
404878
- tokenHash: zod_1.z.string(),
404879
- /** First 12 chars of the raw token — display/audit label only. */
404880
- tokenPrefix: zod_1.z.string(),
404881
- scope: exports.ShareTokenScopeSchema,
404882
- createdAt: zod_1.z.number(),
404883
- /** Epoch ms — or `null` for a never-expiring token (explicit choice at mint). */
404884
- expiresAt: zod_1.z.number().nullable(),
404885
- lastUsedAt: zod_1.z.number().optional()
404886
- });
404887
- function parseShareToken(data) {
404888
- const parsed = exports.ShareTokenRecordSchema.safeParse(data);
404889
- return parsed.success ? parsed.data : null;
404890
- }
404891
- var ShareTokenService = class {
404892
- getStore;
404893
- logger;
404894
- /**
404895
- * Declaration is one-time per process; reset only if the backend itself
404896
- * is swapped (it never is at runtime — the getter is lazy solely because
404897
- * the sqlite builtin registers after this service is constructed).
404898
- */
404899
- collectionDeclared = false;
404900
- constructor(getStore, logger = null) {
404901
- this.getStore = getStore;
404902
- this.logger = logger;
404903
- }
404904
- /**
404905
- * Resolve the backend AND ensure `share_view_tokens` is declared before
404906
- * the first operation. The structured settings backend fail-closes on
404907
- * undeclared collections; this KV shape (`id` PK + `data` TEXT) is
404908
- * byte-identical to what the legacy on-demand path created, so existing
404909
- * rows keep working with no migration.
404910
- */
404911
- async store() {
404912
- const store = this.getStore();
404913
- if (!store) {
404914
- throw new Error("Share tokens unavailable \u2014 settings backend not ready");
404915
- }
404916
- if (!this.collectionDeclared) {
404917
- await store.declareCollection({
404918
- collection: SHARE_TOKENS_COLLECTION,
404919
- columns: [
404920
- { name: "id", type: "TEXT", primaryKey: true, notNull: true },
404921
- { name: "data", type: "TEXT", notNull: true }
404922
- ],
404923
- indexes: SHARE_TOKENS_INDEXES
404924
- });
404925
- this.collectionDeclared = true;
404926
- }
404927
- return store;
404928
- }
404929
- /**
404930
- * Mint a new share token. Throws on invalid scope or out-of-bounds TTL
404931
- * (fail fast — the router's Zod input schema should have caught both).
404932
- * `ttlSec: 'never'` is the explicit opt-in to a never-expiring token
404933
- * (`expiresAt: null`); omitted falls back to the 7-day default.
404934
- * Returns the RAW token exactly once; only its hash is persisted.
404935
- */
404936
- async create(input) {
404937
- const scope = exports.ShareTokenScopeSchema.parse(input.scope);
404938
- const ttlSec = input.ttlSec ?? exports.SHARE_TOKEN_TTL_DEFAULT_SEC;
404939
- if (ttlSec !== "never" && (!Number.isInteger(ttlSec) || ttlSec < exports.SHARE_TOKEN_TTL_MIN_SEC || ttlSec > exports.SHARE_TOKEN_TTL_MAX_SEC)) {
404940
- throw new Error(`Share token TTL out of bounds \u2014 got ${ttlSec}s, allowed [${exports.SHARE_TOKEN_TTL_MIN_SEC}s, ${exports.SHARE_TOKEN_TTL_MAX_SEC}s] or 'never'`);
404941
- }
404942
- const rawToken = `${exports.SHARE_TOKEN_PREFIX}${crypto2.randomBytes(32).toString("hex")}`;
404943
- const now = Date.now();
404944
- const record = {
404945
- id: crypto2.randomUUID(),
404946
- userId: input.userId,
404947
- tokenHash: crypto2.createHash("sha256").update(rawToken).digest("hex"),
404948
- tokenPrefix: rawToken.slice(0, 12),
404949
- scope,
404950
- createdAt: now,
404951
- expiresAt: ttlSec === "never" ? null : now + ttlSec * 1e3
404952
- };
404953
- await (await this.store()).insert({
404954
- collection: SHARE_TOKENS_COLLECTION,
404955
- record: { id: record.id, data: { ...record } }
404956
- });
404957
- this.logger?.info("Share token minted", {
404958
- meta: {
404959
- id: record.id,
404960
- userId: record.userId,
404961
- kind: scope.kind,
404962
- deviceIds: scope.deviceIds,
404963
- expiresAt: record.expiresAt
404964
- }
404965
- });
404966
- return { token: rawToken, record };
404967
- }
404968
- /**
404969
- * Resolve a raw `csv_*` token to its record. Returns `null` for:
404970
- * wrong prefix, unknown token, corrupt record, or expired token.
404971
- * Never throws on bad input — the auth boundary treats `null` as 401.
404972
- */
404973
- async validate(rawToken) {
404974
- if (!rawToken.startsWith(exports.SHARE_TOKEN_PREFIX))
404975
- return null;
404976
- const tokenHash = crypto2.createHash("sha256").update(rawToken).digest("hex");
404977
- const results = await (await this.store()).query({
404978
- collection: SHARE_TOKENS_COLLECTION,
404979
- filter: { where: { tokenHash } }
404980
- });
404981
- const first = results[0];
404982
- if (!first)
404983
- return null;
404984
- const record = parseShareToken(first.data);
404985
- if (!record)
404986
- return null;
404987
- if (record.expiresAt !== null && Date.now() > record.expiresAt)
404988
- return null;
404989
- this.touchLastUsed(record).catch(() => {
404990
- });
404991
- return record;
404992
- }
404993
- /**
404994
- * Revoke (delete) a share token. Only the minting user or an admin may
404995
- * revoke. Returns `false` when the token doesn't exist; throws on a
404996
- * permission mismatch so the router surfaces a FORBIDDEN.
404997
- */
404998
- async revoke(input) {
404999
- const results = await (await this.store()).query({
405000
- collection: SHARE_TOKENS_COLLECTION,
405001
- filter: { where: { id: input.id } }
405002
- });
405003
- const first = results[0];
405004
- if (!first)
405005
- return false;
405006
- const record = parseShareToken(first.data);
405007
- if (!record) {
405008
- await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
405009
- return true;
405010
- }
405011
- if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
405012
- throw new Error("Only the token owner or an admin can revoke a share token");
405013
- }
405014
- await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
405015
- this.logger?.info("Share token revoked", {
405016
- meta: { id: record.id, byUserId: input.callerUserId }
405017
- });
405018
- return true;
405019
- }
405020
- /** Every share token minted by `userId`, expired ones included. */
405021
- async listForUser(userId) {
405022
- const results = await (await this.store()).query({
405023
- collection: SHARE_TOKENS_COLLECTION,
405024
- filter: { where: { userId } }
405025
- });
405026
- return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
405027
- }
405028
- /** All share tokens (admin listing). */
405029
- async listAll() {
405030
- const results = await (await this.store()).query({
405031
- collection: SHARE_TOKENS_COLLECTION,
405032
- filter: {}
405033
- });
405034
- return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
405035
- }
405036
- async touchLastUsed(record) {
405037
- await (await this.store()).update({
405038
- collection: SHARE_TOKENS_COLLECTION,
405039
- id: record.id,
405040
- data: { ...record, lastUsedAt: Date.now() }
405041
- });
405042
- }
405043
- };
405044
- exports.ShareTokenService = ShareTokenService;
405045
- }
405046
- });
405047
-
405048
- // ../../server/backend/dist/api/trpc/trpc-error-principal.js
405049
- var require_trpc_error_principal = __commonJS({
405050
- "../../server/backend/dist/api/trpc/trpc-error-principal.js"(exports) {
405051
- "use strict";
405052
- Object.defineProperty(exports, "__esModule", { value: true });
405053
- exports.readCredentialIdentity = readCredentialIdentity;
405054
- exports.describeTrpcPrincipal = describeTrpcPrincipal;
405055
- function readCredentialIdentity(claims) {
405056
- const kind = Reflect.get(claims, "kind");
405057
- if (kind !== "sso-bridge")
405058
- return {};
405059
- const provider = Reflect.get(claims, "provider");
405060
- const sessionId = Reflect.get(claims, "sessionId");
405061
- return {
405062
- ...typeof provider === "string" && provider !== "" ? { credential: provider } : {},
405063
- ...typeof sessionId === "string" && sessionId !== "" ? { sessionId } : {}
405064
- };
405065
- }
405066
- function describeTrpcPrincipal(user) {
405067
- if (!user)
405068
- return "anonymous";
405069
- const name = user.username || user.id || "unknown";
405070
- if (user.shareView)
405071
- return `${name} (share-view)`;
405072
- if (user.isAdmin)
405073
- return `${name} (admin)`;
405074
- if (user.credential) {
405075
- const session = user.oauthSessionId ? ` session=${user.oauthSessionId}` : "";
405076
- return `${name} (${user.credential}${session})`;
405077
- }
405078
- if (user.isScoped)
405079
- return `${name} (scoped-token)`;
405080
- return `${name} (session)`;
405081
- }
405082
- }
405083
- });
405084
-
405085
- // ../../server/backend/dist/api/trpc/trpc.context.js
405086
- var require_trpc_context = __commonJS({
405087
- "../../server/backend/dist/api/trpc/trpc.context.js"(exports) {
405088
- "use strict";
405089
- Object.defineProperty(exports, "__esModule", { value: true });
405090
- exports.createMeshTrpcContext = createMeshTrpcContext;
405091
- exports.createTrpcContext = createTrpcContext;
405092
- exports.createWsTrpcContext = createWsTrpcContext;
405093
- var types_1 = require_dist4();
405094
- var share_token_service_js_1 = require_share_token_service();
405095
- var trpc_error_principal_js_1 = require_trpc_error_principal();
405096
- var SCOPE_REFRESH_TTL_MS = 3e4;
405097
- var SCOPE_REFRESH_MISS_TTL_MS = 5e3;
405098
- var liveScopeMemo = /* @__PURE__ */ new Map();
405099
- async function refreshScopesFromStore(userId, addonRegistry) {
405100
- const cached = liveScopeMemo.get(userId);
405101
- const now = Date.now();
405102
- if (cached && now - cached.at < (cached.scopes === null ? SCOPE_REFRESH_MISS_TTL_MS : SCOPE_REFRESH_TTL_MS)) {
405103
- return cached.scopes;
405104
- }
405105
- let scopes = null;
405106
- try {
405107
- const userMgmt = addonRegistry.getCapabilityRegistry().getSingleton("user-management");
405108
- const fresh = typeof userMgmt?.listUsers === "function" ? (await userMgmt.listUsers()).find((u) => u.id === userId) : null;
405109
- if (fresh)
405110
- scopes = (0, types_1.normalizeTokenScopes)(fresh.scopes ?? []);
405111
- } catch {
405112
- scopes = null;
405113
- }
405114
- liveScopeMemo.set(userId, { at: now, scopes });
405115
- if (liveScopeMemo.size > 512) {
405116
- for (const [key, entry] of liveScopeMemo) {
405117
- if (now - entry.at > SCOPE_REFRESH_TTL_MS * 4)
405118
- liveScopeMemo.delete(key);
405119
- }
405120
- }
405121
- return scopes;
405122
- }
405123
- function readQuery(req) {
405124
- if (!("query" in req))
405125
- return null;
405126
- const q = Reflect.get(req, "query");
405127
- if (q === null || typeof q !== "object" || Array.isArray(q))
405128
- return null;
405129
- return { ...q };
405130
- }
405131
- function extractTokenFromRequest(req) {
405132
- const authHeader = req.headers.authorization;
405133
- if (authHeader && typeof authHeader === "string") {
405134
- const [scheme, token2] = authHeader.split(" ");
405135
- if (scheme === "Bearer" && token2)
405136
- return token2;
405137
- }
405138
- const q = readQuery(req);
405139
- if (q && typeof q.token === "string") {
405140
- return q.token;
405141
- }
405142
- const url = req.url;
405143
- if (url) {
405144
- const qIdx = url.indexOf("?");
405145
- if (qIdx !== -1) {
405146
- const t = new URLSearchParams(url.slice(qIdx + 1)).get("token");
405147
- if (t)
405148
- return t;
405149
- }
405150
- }
405151
- return null;
405152
- }
405153
- async function resolveUser(token2, authService, addonRegistry, shareTokens = null, logger = null) {
405154
- if (!token2)
405155
- return null;
405156
- if (token2.startsWith(share_token_service_js_1.SHARE_TOKEN_PREFIX)) {
405157
- if (!shareTokens)
405158
- return null;
405159
- try {
405160
- const record = await shareTokens.validate(token2);
405161
- if (!record)
405162
- return null;
405163
- return {
405164
- id: record.userId,
405165
- // Display label — `share:<prefix>` keeps audit logs readable
405166
- // without exposing the token hash.
405167
- username: `share:${record.tokenPrefix}`,
405168
- isAdmin: false,
405169
- permissions: {
405170
- isAdmin: false,
405171
- allowedProviders: "*",
405172
- allowedDevices: {}
405173
- },
405174
- isApiKey: true,
405175
- shareView: { tokenId: record.id, scope: record.scope }
405176
- };
405177
- } catch {
405178
- return null;
405179
- }
405180
- }
405181
- if (token2.startsWith("cst_")) {
405182
- try {
405183
- const userMgmt = addonRegistry.getCapabilityRegistry().getSingleton("user-management");
405184
- if (!userMgmt)
405185
- return null;
405186
- const record = await userMgmt.validateScopedToken({ token: token2 });
405187
- if (!record)
405188
- return null;
405189
- return {
405190
- id: record.userId,
405191
- // Display label — `scoped:<prefix>` makes audit logs read
405192
- // naturally without exposing the token hash.
405193
- username: `scoped:${record.tokenPrefix}`,
405194
- isAdmin: false,
405195
- permissions: {
405196
- isAdmin: false,
405197
- allowedProviders: "*",
405198
- allowedDevices: {}
405199
- },
405200
- isApiKey: true,
405201
- isScoped: true,
405202
- // Migrate any v2 `device:targets` grant to a v3 selector at the
405203
- // boundary so the enforcement matcher only ever sees v3.
405204
- scopes: (0, types_1.normalizeTokenScopes)(record.scopes)
405205
- };
405206
- } catch {
405207
- return null;
405208
- }
405209
- }
405210
- try {
405211
- const payload = authService.verifyToken(token2);
405212
- if (typeof payload.isAdmin !== "boolean") {
405213
- return null;
405214
- }
405215
- const principal = (0, types_1.classifyBearerPrincipal)(payload);
405216
- if (principal.kind === "not-a-credential") {
405217
- logger?.warn("trpc: refused a bearer that is not an API credential", {
405218
- meta: { reason: principal.reason, userId: payload.userId ?? payload.keyId ?? "unknown" }
405219
- });
405220
- return null;
405221
- }
405222
- const credential = (0, trpc_error_principal_js_1.readCredentialIdentity)(payload);
405223
- return {
405224
- id: payload.userId ?? payload.keyId ?? "unknown",
405225
- username: payload.username ?? "unknown",
405226
- isAdmin: payload.isAdmin,
405227
- permissions: {
405228
- isAdmin: payload.isAdmin,
405229
- allowedProviders: payload.allowedProviders,
405230
- allowedDevices: payload.allowedDevices
405231
- },
405232
- isApiKey: payload.type === "api_key",
405233
- agentId: payload.agentId,
405234
- // Scopes are baked into the JWT at login; the middleware uses
405235
- // them to gate every call until the user re-logs. Normalised at the
405236
- // boundary — a v2 `device:targets` JWT (issued before the v3 model)
405237
- // migrates to a selector here, so a pre-v3 token keeps working.
405238
- //
405239
- // …but a login-time snapshot is exactly the bug: an admin editing a
405240
- // user's scopes expects the change to APPLY, and the live session
405241
- // kept gating on the old set until re-login (2026-08-15: a user
405242
- // granted the whole fleet kept seeing zero cameras). For non-admin
405243
- // SESSION principals the store's current scopes win when readable
405244
- // (memoized — see above); the JWT's copy is the fallback when the
405245
- // user-management cap is unreachable, so a store hiccup never
405246
- // locks anybody out mid-request.
405247
- //
405248
- // `session` ONLY, and that word is the whole fix. An ACCOUNT-LINK token
405249
- // is governed by the grant the operator consented to, not by the linking
405250
- // user's record: it always carries `isAdmin: false` (`oauth-grants.ts`
405251
- // → `baseClaims`) and its owner is normally an admin, whose record is
405252
- // deliberately unscoped — so the refresh replaced a six-scope Home
405253
- // Assistant grant with `[]` and the hub refused the component's first
405254
- // call with `Have: (none)` (2026-08-19). The user store knows nothing
405255
- // about a link and must not get a vote on one.
405256
- ...payload.scopes !== void 0 ? { scopes: (0, types_1.normalizeTokenScopes)(payload.scopes) } : {},
405257
- ...!payload.isAdmin && principal.kind === "session" ? await (async () => {
405258
- const fresh = await refreshScopesFromStore(payload.userId ?? payload.keyId ?? "unknown", addonRegistry);
405259
- return fresh !== null ? { scopes: fresh } : {};
405260
- })() : {},
405261
- ...credential.credential !== void 0 ? { credential: credential.credential } : {},
405262
- ...credential.sessionId !== void 0 ? { oauthSessionId: credential.sessionId } : {}
405263
- };
405264
- } catch {
405265
- return null;
405266
- }
405267
- }
405268
- function makeDeviceScopeLookup(addonRegistry) {
405269
- return {
405270
- getAncestors: (deviceId) => addonRegistry.getPersistedAncestors(deviceId),
405271
- getType: (deviceId) => addonRegistry.getPersistedType(deviceId),
405272
- getLocation: (deviceId) => addonRegistry.getPersistedLocation(deviceId)
405273
- };
405274
- }
405275
- function createMeshTrpcContext() {
405276
- const user = {
405277
- id: "mesh",
405278
- username: "mesh",
405279
- isAdmin: true,
405280
- permissions: { isAdmin: true, allowedProviders: "*", allowedDevices: {} },
405281
- isApiKey: true
405282
- };
405283
- return { user };
405284
- }
405285
- async function createTrpcContext(req, authService, addonRegistry, shareTokens = null, logger = null) {
405286
- const token2 = extractTokenFromRequest(req);
405287
- return {
405288
- user: await resolveUser(token2, authService, addonRegistry, shareTokens, logger),
405289
- req,
405290
- deviceScopeLookup: makeDeviceScopeLookup(addonRegistry),
405291
- deviceFleet: () => addonRegistry.getPersistedDeviceList()
405292
- };
405293
- }
405294
- async function createWsTrpcContext(opts, authService, addonRegistry, shareTokens = null, logger = null) {
405295
- const paramToken = opts.info.connectionParams?.["token"];
405296
- const token2 = (typeof paramToken === "string" ? paramToken : null) ?? extractTokenFromRequest(opts.req);
405297
- const user = await resolveUser(token2, authService, addonRegistry, shareTokens, logger);
405298
- return {
405299
- user,
405300
- req: opts.req,
405301
- deviceScopeLookup: makeDeviceScopeLookup(addonRegistry),
405302
- deviceFleet: () => addonRegistry.getPersistedDeviceList()
405303
- };
405304
- }
405305
- }
405306
- });
405307
-
405308
405759
  // ../../server/backend/dist/api/trpc/core-cap-bridge.js
405309
405760
  var require_core_cap_bridge = __commonJS({
405310
405761
  "../../server/backend/dist/api/trpc/core-cap-bridge.js"(exports) {
405311
405762
  "use strict";
405312
405763
  Object.defineProperty(exports, "__esModule", { value: true });
405313
405764
  exports.isCoreServiceCapName = isCoreServiceCapName;
405765
+ exports.describeMeshOrigin = describeMeshOrigin;
405314
405766
  exports.buildCoreCapService = buildCoreCapService;
405315
405767
  var system_1 = require_dist3();
405316
- var trpc_middleware_js_1 = require_trpc_middleware();
405768
+ var http_request_census_singleton_js_1 = require_http_request_census_singleton();
405317
405769
  var trpc_context_js_1 = require_trpc_context();
405770
+ var trpc_middleware_js_1 = require_trpc_middleware();
405318
405771
  var CORE_NAMESPACES = /* @__PURE__ */ new Set([
405319
405772
  // Service-backed cap routers (no addon provider).
405320
405773
  "system",
@@ -405359,6 +405812,24 @@ var require_core_cap_bridge = __commonJS({
405359
405812
  }
405360
405813
  }
405361
405814
  }
405815
+ function countMeshCall(census, path, origin) {
405816
+ if (!census.armed)
405817
+ return;
405818
+ census.observe({
405819
+ plane: "mesh",
405820
+ kind: "call",
405821
+ procedures: [path],
405822
+ // A mesh call has no socket and no user-agent. Both render as the named
405823
+ // unknown rather than as a blank, which would read as a logging defect.
405824
+ userAgent: null,
405825
+ ip: null,
405826
+ principal: describeMeshOrigin(origin)
405827
+ });
405828
+ }
405829
+ function describeMeshOrigin(origin) {
405830
+ const node = origin.nodeId ?? "unknown";
405831
+ return origin.caller === null ? `mesh:${node}` : `mesh:${node} via ${origin.caller}`;
405832
+ }
405362
405833
  function resolveInvoker(caller, path) {
405363
405834
  let node = caller;
405364
405835
  for (const segment of path.split(".")) {
@@ -405368,7 +405839,7 @@ var require_core_cap_bridge = __commonJS({
405368
405839
  }
405369
405840
  return typeof node === "function" ? node : null;
405370
405841
  }
405371
- function buildCoreCapService(appRouter) {
405842
+ function buildCoreCapService(appRouter, census = (0, http_request_census_singleton_js_1.getHttpRequestCensus)()) {
405372
405843
  const meshCaller = (0, trpc_middleware_js_1.createCallerFactory)(appRouter)((0, trpc_context_js_1.createMeshTrpcContext)());
405373
405844
  const discovered = [];
405374
405845
  collectProcedures(appRouter._def.procedures, "", discovered);
@@ -405386,7 +405857,13 @@ var require_core_cap_bridge = __commonJS({
405386
405857
  if (invoke === null)
405387
405858
  continue;
405388
405859
  const method = path.slice(dot + 1);
405389
- actions.push({ actionName: `${toKebab(namespace)}.${method}`, invoke });
405860
+ actions.push({
405861
+ actionName: `${toKebab(namespace)}.${method}`,
405862
+ invoke: (input, origin) => {
405863
+ countMeshCall(census, path, origin);
405864
+ return invoke(input);
405865
+ }
405866
+ });
405390
405867
  }
405391
405868
  return (0, system_1.createCoreCapService)({ actions });
405392
405869
  }
@@ -407757,6 +408234,202 @@ var require_trpc_router = __commonJS({
407757
408234
  }
407758
408235
  });
407759
408236
 
408237
+ // ../../server/backend/dist/api/trpc/ws-request-census.js
408238
+ var require_ws_request_census = __commonJS({
408239
+ "../../server/backend/dist/api/trpc/ws-request-census.js"(exports) {
408240
+ "use strict";
408241
+ Object.defineProperty(exports, "__esModule", { value: true });
408242
+ exports.WsCensusSession = exports.MAX_PENDING_WS_OPERATIONS = exports.WS_PATHLESS_PROCEDURE = void 0;
408243
+ exports.parseTrpcWsFrame = parseTrpcWsFrame;
408244
+ exports.attachWsCensusSession = attachWsCensusSession;
408245
+ exports.identifyWsCensusSession = identifyWsCensusSession;
408246
+ var client_ip_js_1 = require_client_ip();
408247
+ var http_request_census_js_1 = require_http_request_census();
408248
+ var KEEPALIVE_FRAMES = /* @__PURE__ */ new Set(["PING", "PONG"]);
408249
+ exports.WS_PATHLESS_PROCEDURE = "(ws-no-path)";
408250
+ exports.MAX_PENDING_WS_OPERATIONS = 128;
408251
+ var NO_OPERATIONS = { calls: [], subscriptions: [], stops: 0 };
408252
+ function parseTrpcWsFrame(raw) {
408253
+ const text = raw.trim();
408254
+ if (text.length === 0)
408255
+ return NO_OPERATIONS;
408256
+ if (KEEPALIVE_FRAMES.has(text))
408257
+ return NO_OPERATIONS;
408258
+ const parsed = parseJsonSafely(text);
408259
+ if (parsed === null)
408260
+ return NO_OPERATIONS;
408261
+ const messages = Array.isArray(parsed) ? parsed : [parsed];
408262
+ const calls = [];
408263
+ const subscriptions = [];
408264
+ let stops = 0;
408265
+ let overflowed = false;
408266
+ for (const message of messages) {
408267
+ if (typeof message !== "object" || message === null)
408268
+ continue;
408269
+ const method = Reflect.get(message, "method");
408270
+ if (method === "subscription.stop") {
408271
+ stops += 1;
408272
+ continue;
408273
+ }
408274
+ if (method !== "query" && method !== "mutation" && method !== "subscription")
408275
+ continue;
408276
+ if (calls.length + subscriptions.length >= http_request_census_js_1.MAX_BATCH_PROCEDURES) {
408277
+ overflowed = true;
408278
+ continue;
408279
+ }
408280
+ const path = readPath(Reflect.get(message, "params"));
408281
+ if (method === "subscription")
408282
+ subscriptions.push(path);
408283
+ else
408284
+ calls.push(path);
408285
+ }
408286
+ if (overflowed)
408287
+ calls.push(http_request_census_js_1.BATCH_OVERFLOW_PROCEDURE);
408288
+ return { calls, subscriptions, stops };
408289
+ }
408290
+ function parseJsonSafely(text) {
408291
+ try {
408292
+ return JSON.parse(text);
408293
+ } catch {
408294
+ return null;
408295
+ }
408296
+ }
408297
+ function readPath(params) {
408298
+ if (typeof params !== "object" || params === null)
408299
+ return exports.WS_PATHLESS_PROCEDURE;
408300
+ const path = Reflect.get(params, "path");
408301
+ return typeof path === "string" && path.length > 0 ? path : exports.WS_PATHLESS_PROCEDURE;
408302
+ }
408303
+ var WsCensusSession = class {
408304
+ census;
408305
+ userAgent;
408306
+ ip;
408307
+ /** `null` until `createWsTrpcContext` resolved the bearer. */
408308
+ principal = null;
408309
+ pending = [];
408310
+ closed = false;
408311
+ constructor(census, req) {
408312
+ this.census = census;
408313
+ this.userAgent = (0, client_ip_js_1.extractUserAgent)(req);
408314
+ this.ip = (0, client_ip_js_1.extractClientIp)(req);
408315
+ }
408316
+ /**
408317
+ * Count one raw client frame.
408318
+ *
408319
+ * The armed check is the FIRST statement. If it ever moves below a field
408320
+ * access, the hostile-frame spec fails instead of the hub paying for a
408321
+ * diagnostic nobody switched on.
408322
+ */
408323
+ observeMessage(data) {
408324
+ if (!this.census.armed)
408325
+ return;
408326
+ this.census.observeWsMessage();
408327
+ const text = frameText(data);
408328
+ if (text === null)
408329
+ return;
408330
+ const operations = parseTrpcWsFrame(text);
408331
+ if (operations.stops > 0)
408332
+ this.census.observeSubscriptionStop(operations.stops);
408333
+ if (operations.calls.length === 0 && operations.subscriptions.length === 0)
408334
+ return;
408335
+ if (this.principal !== null) {
408336
+ this.flushFrame(operations, this.principal);
408337
+ return;
408338
+ }
408339
+ if (this.pending.length >= exports.MAX_PENDING_WS_OPERATIONS) {
408340
+ this.flushFrame(operations, http_request_census_js_1.UNKNOWN);
408341
+ return;
408342
+ }
408343
+ this.pending.push(operations);
408344
+ }
408345
+ /** Name the connection, once, and release everything held for it. */
408346
+ identify(principal) {
408347
+ if (this.principal !== null || this.closed)
408348
+ return;
408349
+ this.principal = principal;
408350
+ this.drain(principal);
408351
+ }
408352
+ /** The socket went away. Anything still unnamed is released as {@link UNKNOWN}. */
408353
+ close() {
408354
+ if (this.closed)
408355
+ return;
408356
+ this.closed = true;
408357
+ this.drain(this.principal ?? http_request_census_js_1.UNKNOWN);
408358
+ }
408359
+ drain(principal) {
408360
+ if (this.pending.length === 0)
408361
+ return;
408362
+ const held = this.pending;
408363
+ this.pending = [];
408364
+ for (const frame of held)
408365
+ this.flushFrame(frame, principal);
408366
+ }
408367
+ flushFrame(operations, principal) {
408368
+ if (operations.calls.length > 0) {
408369
+ this.census.observe({
408370
+ plane: "ws",
408371
+ kind: "call",
408372
+ procedures: operations.calls,
408373
+ userAgent: this.userAgent,
408374
+ ip: this.ip,
408375
+ principal
408376
+ });
408377
+ }
408378
+ if (operations.subscriptions.length > 0) {
408379
+ this.census.observe({
408380
+ plane: "ws",
408381
+ kind: "subscription",
408382
+ procedures: operations.subscriptions,
408383
+ userAgent: this.userAgent,
408384
+ ip: this.ip,
408385
+ principal
408386
+ });
408387
+ }
408388
+ }
408389
+ };
408390
+ exports.WsCensusSession = WsCensusSession;
408391
+ var sessions = /* @__PURE__ */ new WeakMap();
408392
+ function attachWsCensusSession(census, client, req, logger) {
408393
+ census.observeWsConnection();
408394
+ const session = new WsCensusSession(census, req);
408395
+ sessions.set(client, session);
408396
+ let broken = false;
408397
+ client.on("message", (data) => {
408398
+ if (broken)
408399
+ return;
408400
+ try {
408401
+ session.observeMessage(data);
408402
+ } catch (err) {
408403
+ broken = true;
408404
+ logger?.warn("transport census: ws frames no longer counted on this socket", {
408405
+ meta: { error: err instanceof Error ? err.message : String(err) }
408406
+ });
408407
+ }
408408
+ });
408409
+ client.once("close", () => {
408410
+ session.close();
408411
+ });
408412
+ return session;
408413
+ }
408414
+ function identifyWsCensusSession(client, principal) {
408415
+ sessions.get(client)?.identify(principal);
408416
+ }
408417
+ function frameText(data) {
408418
+ if (typeof data === "string")
408419
+ return data;
408420
+ if (Buffer.isBuffer(data))
408421
+ return data.toString("utf8");
408422
+ if (data instanceof ArrayBuffer)
408423
+ return Buffer.from(data).toString("utf8");
408424
+ if (Array.isArray(data)) {
408425
+ const chunks = data.filter((chunk) => Buffer.isBuffer(chunk));
408426
+ return chunks.length === 0 ? null : Buffer.concat(chunks).toString("utf8");
408427
+ }
408428
+ return null;
408429
+ }
408430
+ }
408431
+ });
408432
+
407760
408433
  // ../../server/backend/dist/auth/addon-route-jwt-gate.js
407761
408434
  var require_addon_route_jwt_gate = __commonJS({
407762
408435
  "../../server/backend/dist/auth/addon-route-jwt-gate.js"(exports) {
@@ -415525,6 +416198,7 @@ var require_main4 = __commonJS({
415525
416198
  var trpc_context_1 = require_trpc_context();
415526
416199
  var trpc_router_1 = require_trpc_router();
415527
416200
  var trpc_error_principal_1 = require_trpc_error_principal();
416201
+ var ws_request_census_1 = require_ws_request_census();
415528
416202
  var addon_route_jwt_gate_js_1 = require_addon_route_jwt_gate();
415529
416203
  var session_cookie_js_1 = require_session_cookie();
415530
416204
  var boot_config_1 = require_boot_config();
@@ -416191,12 +416865,16 @@ var require_main4 = __commonJS({
416191
416865
  concurrencyLimit: 10
416192
416866
  }
416193
416867
  });
416868
+ wss.on("connection", (client, req) => {
416869
+ (0, ws_request_census_1.attachWsCensusSession)(httpRequestCensus, client, req, app.get(logging_service_1.LoggingService).createLogger("tRPC:ws"));
416870
+ });
416194
416871
  (0, ws_1.applyWSSHandler)({
416195
416872
  wss,
416196
416873
  router: appRouter,
416197
- createContext: (opts) => {
416198
- httpRequestCensus.observeWsConnection();
416199
- return (0, trpc_context_1.createWsTrpcContext)(opts, authService, addonRegistry, shareTokenService, app.get(logging_service_1.LoggingService).createLogger("tRPC:ws"));
416874
+ createContext: async (opts) => {
416875
+ const wsCtx = await (0, trpc_context_1.createWsTrpcContext)(opts, authService, addonRegistry, shareTokenService, app.get(logging_service_1.LoggingService).createLogger("tRPC:ws"));
416876
+ (0, ws_request_census_1.identifyWsCensusSession)(opts.res, (0, trpc_error_principal_1.describeTrpcPrincipal)(wsCtx.user));
416877
+ return wsCtx;
416200
416878
  },
416201
416879
  onError: ({ path: trpcPath, error, ctx }) => {
416202
416880
  const trpcLogger = app.get(logging_service_1.LoggingService).createLogger("tRPC:ws");