@camstack/system 1.2.157 → 1.2.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/addon-runner.js +17 -16
  2. package/dist/addon-runner.mjs +2 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +10 -7
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +50 -12
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +50 -12
  12. package/dist/builtins/backup-orchestrator/write-targets.d.ts +63 -0
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  17. package/dist/builtins/device-manager/device-bindings-store.d.ts +56 -10
  18. package/dist/builtins/device-manager/device-manager.addon.d.ts +11 -0
  19. package/dist/builtins/device-manager/device-manager.addon.js +216 -81
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +216 -81
  21. package/dist/builtins/device-manager/device-meta-actions.d.ts +9 -3
  22. package/dist/builtins/device-manager/device-meta-store.d.ts +32 -5
  23. package/dist/builtins/device-manager/device-queries.d.ts +7 -3
  24. package/dist/builtins/device-manager/write-lock.d.ts +17 -0
  25. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  26. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  27. package/dist/builtins/hub-forwarder/index.js +1 -1
  28. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  30. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  32. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  33. package/dist/builtins/local-network/local-network.addon.js +1 -1
  34. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  35. package/dist/builtins/loki-logging/index.js +1 -1
  36. package/dist/builtins/loki-logging/index.mjs +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  38. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  39. package/dist/builtins/platform-probe/index.js +1 -1
  40. package/dist/builtins/platform-probe/index.mjs +1 -1
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/snapshot/index.js +1 -1
  44. package/dist/builtins/snapshot/index.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  47. package/dist/builtins/sqlite-storage/filter-compiler.d.ts +41 -2
  48. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +15 -7
  49. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  50. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  51. package/dist/builtins/sqlite-storage/vector-index-shared.d.ts +30 -5
  52. package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +28 -6
  53. package/dist/builtins/storage-orchestrator/access-guards.d.ts +32 -0
  54. package/dist/builtins/storage-orchestrator/location-secrets.d.ts +33 -0
  55. package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +74 -3
  56. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +26 -0
  57. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +552 -15
  58. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +552 -15
  59. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +56 -1
  60. package/dist/builtins/system-config/system-config.addon.js +1 -1
  61. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  62. package/dist/builtins/winston-logging/index.js +1 -1
  63. package/dist/builtins/winston-logging/index.mjs +1 -1
  64. package/dist/{dist-CDgIzo82.js → dist-CcvXUhHK.js} +537 -17
  65. package/dist/{dist-LvKus-mT.mjs → dist-DKs---Vx.mjs} +520 -18
  66. package/dist/index.js +141 -139
  67. package/dist/index.mjs +3 -3
  68. package/dist/kernel/deps/manifest-system-deps.d.ts +15 -0
  69. package/dist/kernel/index.d.ts +4 -2
  70. package/dist/kernel/moleculer/cap-usage-registry.d.ts +132 -7
  71. package/dist/kernel/storage-location-registry.d.ts +16 -1
  72. package/dist/kernel/transport/local-child-registry.d.ts +57 -0
  73. package/dist/{manifest-python-deps-CD14Pv0-.mjs → manifest-system-deps-DnoPpLGp.mjs} +466 -83
  74. package/dist/{manifest-python-deps-DVODn-qc.js → manifest-system-deps-oJMkWSX-.js} +477 -82
  75. package/dist/{retired-settings-keys-CV4QVdXj.mjs → retired-settings-keys-C3eONTvV.mjs} +1 -1
  76. package/dist/{retired-settings-keys-BfAzWvPC.js → retired-settings-keys-DXZ2xe7C.js} +1 -1
  77. package/package.json +2 -1
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- require("./dist-CDgIzo82.js");
2
+ require("./dist-CcvXUhHK.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  node_crypto = require_chunk.__toESM(node_crypto);
5
5
  let _camstack_types_node = require("@camstack/types/node");
@@ -1607,7 +1607,7 @@ function createAddonDataPlaneFacility(args) {
1607
1607
  * Failures THROW with an actionable message — a caller must never proceed as
1608
1608
  * if dependencies were installed when they were not.
1609
1609
  */
1610
- var execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
1610
+ var execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
1611
1611
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
1612
1612
  /** Where the bootstrap lands inside `cacheDir` (npm tgz carries a `package/` prefix). */
1613
1613
  function bootstrappedCliPath(cacheDir) {
@@ -1615,7 +1615,7 @@ function bootstrappedCliPath(cacheDir) {
1615
1615
  }
1616
1616
  async function defaultProbeSystemNpm() {
1617
1617
  try {
1618
- await execFileAsync$1("npm", ["--version"], { timeout: 15e3 });
1618
+ await execFileAsync$2("npm", ["--version"], { timeout: 15e3 });
1619
1619
  return true;
1620
1620
  } catch {
1621
1621
  return false;
@@ -1633,7 +1633,7 @@ async function defaultDownloadTarball(url, destTgz) {
1633
1633
  await node_fs.promises.writeFile(destTgz, bytes);
1634
1634
  }
1635
1635
  async function defaultExtractTarball(tgzPath, destDir) {
1636
- await execFileAsync$1("tar", [
1636
+ await execFileAsync$2("tar", [
1637
1637
  "-xzf",
1638
1638
  tgzPath,
1639
1639
  "-C",
@@ -1672,7 +1672,7 @@ async function runNpm(args, options) {
1672
1672
  registry: options.registry,
1673
1673
  logger: options.logger
1674
1674
  });
1675
- return execFileAsync$1(invocation.command, [...invocation.argsPrefix, ...args], {
1675
+ return execFileAsync$2(invocation.command, [...invocation.argsPrefix, ...args], {
1676
1676
  ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
1677
1677
  timeout: options.timeout ?? 3e5
1678
1678
  });
@@ -1752,7 +1752,7 @@ function resolveAddonClass(mod) {
1752
1752
  }
1753
1753
  //#endregion
1754
1754
  //#region src/kernel/deps/manifest-native-deps.ts
1755
- var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
1755
+ var execFileAsync$1 = (0, node_util.promisify)(node_child_process.execFile);
1756
1756
  /**
1757
1757
  * Fallback bootstrap-cache location when the caller doesn't thread one
1758
1758
  * through (the forked addon-runner's spawn-time install). tmpdir survives the
@@ -2061,7 +2061,7 @@ async function rebuildNativeDeps(addonDir, packageNames, logger, npmCacheDir, re
2061
2061
  return;
2062
2062
  }
2063
2063
  logger.warn("@electron/rebuild not available — falling back to npx", { meta: { addonDir } });
2064
- await execFileAsync("npx", [
2064
+ await execFileAsync$1("npx", [
2065
2065
  "--yes",
2066
2066
  "electron-rebuild",
2067
2067
  "-m",
@@ -5550,6 +5550,246 @@ function createLocalTransport() {
5550
5550
  };
5551
5551
  }
5552
5552
  //#endregion
5553
+ //#region src/kernel/moleculer/cap-usage-registry.ts
5554
+ /**
5555
+ * In-memory observation of tRPC cap calls between addons — the store behind
5556
+ * `nodes.getCapUsageGraph`.
5557
+ *
5558
+ * Storage: `Map<callerAddonId, Map<providerAddonId, Map<capName, RollingWindow>>>`.
5559
+ * `RollingWindow` is a fixed-size ring of 1-second buckets — default 300
5560
+ * buckets = 5 minutes retention. Queries narrower than the retention window
5561
+ * filter buckets at read time.
5562
+ *
5563
+ * ── The ring ages, and that is not free decoration ─────────────────────────
5564
+ * Buckets are indexed `second % retention`, so a bucket written at second S is
5565
+ * hit again at S + retention·k forever. Until 2026-08-30 nothing ever zeroed
5566
+ * them: the `lastCallAtMs` guard hid it while a triple was fully idle, but ONE
5567
+ * fresh call re-armed the entire stale ring. A triple that made 1000 calls and
5568
+ * then one call five minutes later reported **1001 calls/min instead of 1**.
5569
+ * An over-counted edge is worse than the empty graph this instrument shipped
5570
+ * with — silence invites doubt, a confident wrong number sends the next
5571
+ * investigation at the wrong addon.
5572
+ *
5573
+ * The fix is clear-forward: on each record, zero the buckets the ring has
5574
+ * ADVANCED over since the previous write, and never read past the last written
5575
+ * second. Amortised O(1) — a busy triple clears 0–1 buckets per call, and an
5576
+ * idle one clears at most `retention` once, when it wakes.
5577
+ *
5578
+ * ── Cardinality is bounded, because this map lives on hub-main ─────────────
5579
+ * The previous docblock claimed "~22 MB worst case … under 200 KB at ~10 active
5580
+ * triples". Both numbers were written for a smaller machine and the second was
5581
+ * an assumption, not a bound: the key space is caller × provider × cap, and on
5582
+ * the live hub that is 64 addons × 64 runners × ~150 caps. Nothing capped it.
5583
+ *
5584
+ * Today a ring is one `Uint32Array(retention)` — 1.2 KB of ArrayBuffer at the
5585
+ * default — plus the three Map entries that reach it. MEASURED (`heapUsed` +
5586
+ * `arrayBuffers`, node 24, `--expose-gc`): **1.7 KB per triple**, so ≈1.0 MB at
5587
+ * the realistic hub figure (64 callers × a handful of caps each, ≈600 triples)
5588
+ * and **6.3 MB at the {@link DEFAULT_MAX_TRIPLES} ceiling**. `recordCall` costs
5589
+ * ≈46 ns (1e6 calls in 46 ms) — at the 39 717 calls/min that started this
5590
+ * investigation, ≈30 µs of hub-main per second.
5591
+ *
5592
+ * Reclamation is what keeps it at the low end: a triple whose ring has entirely
5593
+ * aged out is dead data and is dropped on the next admission sweep. The hard
5594
+ * cap only exists so that a pathological key space (a caller id derived from
5595
+ * something unbounded) cannot take hub-main's heap with it — and when it bites,
5596
+ * {@link CapUsageRegistry.getStats} says how often, because an instrument that
5597
+ * truncates silently is the defect this whole file exists to fix (D237).
5598
+ */
5599
+ /** Ring length in seconds when the caller does not pick one. */
5600
+ var DEFAULT_RETENTION_SECONDS = 300;
5601
+ /**
5602
+ * Ceiling on live `(caller, provider, cap)` triples. ≈1.5 KB each → ≈6 MB.
5603
+ * Roughly 6× the realistic hub figure, so it is a backstop against an
5604
+ * unbounded key space, not a working limit.
5605
+ */
5606
+ var DEFAULT_MAX_TRIPLES = 4e3;
5607
+ /**
5608
+ * Provider placeholder for a call the PARENT routed rather than short-
5609
+ * circuiting to a sibling runner: hub-in-process (`settings-store`,
5610
+ * `device-manager`, the `$core-caps` bridge), or a remote node. At the layer
5611
+ * that records — the parent's `cap-call-out` handler — the sibling id IS the
5612
+ * provider and everything else has not been resolved yet.
5613
+ *
5614
+ * It is a NAMED unknown, not a dropped observation, for the reason D237 was
5615
+ * written: the caller is always known, and "who is hammering `settings-store`"
5616
+ * is the question this graph exists to answer. Sibling to
5617
+ * `UNRESOLVED_PROVIDER_ADDON_ID` in `trpc-links.ts`, which names the same kind
5618
+ * of unknown one layer up.
5619
+ */
5620
+ var PARENT_ROUTED_PROVIDER_ADDON_ID = "(unresolved: parent-routed)";
5621
+ var CapUsageRegistry = class {
5622
+ retentionSeconds;
5623
+ maxTriples;
5624
+ map = /* @__PURE__ */ new Map();
5625
+ triples = 0;
5626
+ droppedTriples = 0;
5627
+ constructor(opts) {
5628
+ this.retentionSeconds = Math.max(1, opts?.retentionSeconds ?? DEFAULT_RETENTION_SECONDS);
5629
+ this.maxTriples = Math.max(1, opts?.maxTriples ?? DEFAULT_MAX_TRIPLES);
5630
+ }
5631
+ /**
5632
+ * Record one observed cap call.
5633
+ *
5634
+ * On the hot path: this runs once per cross-process cap call on hub-main's
5635
+ * event loop, which is the cluster's only queue (D181). Three Map lookups, an
5636
+ * integer increment, and — only when the wall clock has moved on — a bounded
5637
+ * clear. No allocation once a triple exists, and it NEVER throws into the
5638
+ * call it observes.
5639
+ */
5640
+ recordCall(rec) {
5641
+ try {
5642
+ if (rec.callerAddonId === "" || rec.providerAddonId === "" || rec.capName === "") return;
5643
+ if (!Number.isFinite(rec.atMs) || rec.atMs < 0) return;
5644
+ const window = this.windowFor(rec);
5645
+ if (window === null) return;
5646
+ const sec = Math.floor(rec.atMs / 1e3);
5647
+ if (window.lastCallAtMs !== 0) {
5648
+ const lastSec = Math.floor(window.lastCallAtMs / 1e3);
5649
+ const advanced = Math.min(sec - lastSec, this.retentionSeconds);
5650
+ for (let k = 1; k <= advanced; k++) window.buckets[(lastSec + k) % this.retentionSeconds] = 0;
5651
+ }
5652
+ const idx = sec % this.retentionSeconds;
5653
+ window.buckets[idx] = (window.buckets[idx] ?? 0) + 1;
5654
+ if (rec.atMs > window.lastCallAtMs) window.lastCallAtMs = rec.atMs;
5655
+ } catch {}
5656
+ }
5657
+ getGraph(opts) {
5658
+ const windowSeconds = Math.max(1, Math.min(opts.windowSeconds, this.retentionSeconds));
5659
+ const minMs = opts.nowMs - windowSeconds * 1e3;
5660
+ const nowSec = Math.floor(opts.nowMs / 1e3);
5661
+ const out = [];
5662
+ for (const [caller, byProvider] of this.map) for (const [provider, byCap] of byProvider) for (const [capName, window] of byCap) {
5663
+ if (window.lastCallAtMs < minMs) continue;
5664
+ const count = this.sumWindow(window, nowSec, windowSeconds);
5665
+ if (count === 0) continue;
5666
+ const callsPerMin = count / windowSeconds * 60;
5667
+ out.push({
5668
+ callerAddonId: caller,
5669
+ providerAddonId: provider,
5670
+ capName,
5671
+ callsPerMin,
5672
+ lastCallAtMs: window.lastCallAtMs
5673
+ });
5674
+ }
5675
+ return out;
5676
+ }
5677
+ /** See {@link CapUsageStats}. Cheap — three counters, no scan. */
5678
+ getStats() {
5679
+ return {
5680
+ triples: this.triples,
5681
+ maxTriples: this.maxTriples,
5682
+ droppedTriples: this.droppedTriples
5683
+ };
5684
+ }
5685
+ /** Test / diagnostic helper — drops all recorded calls. */
5686
+ clear() {
5687
+ this.map.clear();
5688
+ this.triples = 0;
5689
+ this.droppedTriples = 0;
5690
+ }
5691
+ /**
5692
+ * Sum the buckets covering `(nowSec - windowSeconds, nowSec]`.
5693
+ *
5694
+ * The scan starts at `lastSec`, never at `nowSec`. `recordCall` clears the
5695
+ * buckets the ring has advanced OVER, which by definition stops at the last
5696
+ * write: the buckets for the seconds since then still hold whatever the
5697
+ * previous pass left in them, and reading `nowSec` down would count it. A
5698
+ * triple that went quiet for a couple of minutes and then made one call would
5699
+ * report the burst it made five minutes earlier.
5700
+ *
5701
+ * No lower bound is needed beyond the window itself: every bucket at or below
5702
+ * `lastSec - retention` was cleared by the advance that reached `lastSec`, so
5703
+ * it reads zero anyway. The loop is bounded by `windowSeconds ≤ retention`.
5704
+ */
5705
+ sumWindow(window, nowSec, windowSeconds) {
5706
+ const lastSec = Math.floor(window.lastCallAtMs / 1e3);
5707
+ const from = Math.min(nowSec, lastSec);
5708
+ const floorSec = nowSec - windowSeconds;
5709
+ let count = 0;
5710
+ for (let s = from; s > floorSec; s--) count += window.buckets[s % this.retentionSeconds] ?? 0;
5711
+ return count;
5712
+ }
5713
+ /**
5714
+ * The ring for this triple, creating it if the ceiling allows. `null` means
5715
+ * the observation is dropped — counted, never silent (see
5716
+ * {@link CapUsageStats.droppedTriples}).
5717
+ */
5718
+ windowFor(rec) {
5719
+ let byProvider = this.map.get(rec.callerAddonId);
5720
+ if (!byProvider) {
5721
+ byProvider = /* @__PURE__ */ new Map();
5722
+ this.map.set(rec.callerAddonId, byProvider);
5723
+ }
5724
+ let byCap = byProvider.get(rec.providerAddonId);
5725
+ if (!byCap) {
5726
+ byCap = /* @__PURE__ */ new Map();
5727
+ byProvider.set(rec.providerAddonId, byCap);
5728
+ }
5729
+ const existing = byCap.get(rec.capName);
5730
+ if (existing) return existing;
5731
+ if (this.triples >= this.maxTriples) {
5732
+ this.reclaimExpired(rec.atMs);
5733
+ if (this.triples >= this.maxTriples) {
5734
+ this.droppedTriples++;
5735
+ return null;
5736
+ }
5737
+ byProvider = this.map.get(rec.callerAddonId);
5738
+ if (!byProvider) {
5739
+ byProvider = /* @__PURE__ */ new Map();
5740
+ this.map.set(rec.callerAddonId, byProvider);
5741
+ }
5742
+ byCap = byProvider.get(rec.providerAddonId);
5743
+ if (!byCap) {
5744
+ byCap = /* @__PURE__ */ new Map();
5745
+ byProvider.set(rec.providerAddonId, byCap);
5746
+ }
5747
+ }
5748
+ const created = {
5749
+ buckets: new Uint32Array(this.retentionSeconds),
5750
+ lastCallAtMs: 0
5751
+ };
5752
+ byCap.set(rec.capName, created);
5753
+ this.triples++;
5754
+ return created;
5755
+ }
5756
+ /**
5757
+ * Drop every triple whose whole ring has aged out — its last call is older
5758
+ * than the retention window, so it can contribute to no query. O(triples),
5759
+ * and only reachable when the ceiling is full, so it is amortised away by the
5760
+ * admissions it enables.
5761
+ */
5762
+ reclaimExpired(nowMs) {
5763
+ const cutoff = nowMs - this.retentionSeconds * 1e3;
5764
+ for (const [caller, byProvider] of this.map) {
5765
+ for (const [provider, byCap] of byProvider) {
5766
+ for (const [capName, window] of byCap) {
5767
+ if (window.lastCallAtMs >= cutoff) continue;
5768
+ byCap.delete(capName);
5769
+ this.triples--;
5770
+ }
5771
+ if (byCap.size === 0) byProvider.delete(provider);
5772
+ }
5773
+ if (byProvider.size === 0) this.map.delete(caller);
5774
+ }
5775
+ }
5776
+ };
5777
+ var singleton = null;
5778
+ /**
5779
+ * The registry for THIS process. Deliberately per-process: the reader
5780
+ * (`nodes.getCapUsageGraph` → `cap-providers.ts`) runs on hub-main, so only
5781
+ * writes made on hub-main are ever visible. See the blind-spot list on
5782
+ * `LocalChildRegistryOptions.capUsageObserver` for what that excludes.
5783
+ */
5784
+ function getCapUsageRegistry() {
5785
+ if (!singleton) singleton = new CapUsageRegistry();
5786
+ return singleton;
5787
+ }
5788
+ /** Test helper — clears the singleton (kept module-private elsewhere). */
5789
+ function __resetCapUsageRegistryForTests() {
5790
+ singleton = null;
5791
+ }
5792
+ //#endregion
5553
5793
  //#region src/kernel/moleculer/event-bus-core.ts
5554
5794
  /**
5555
5795
  * @param retainRecent - `true` ONLY for the hub main broker bus (it serves
@@ -6241,6 +6481,8 @@ var LocalChildRegistry = class {
6241
6481
  isAddonPinnedCall;
6242
6482
  /** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
6243
6483
  capTimeoutMs;
6484
+ /** See {@link LocalChildRegistryOptions.capUsageObserver}. */
6485
+ capUsageObserver;
6244
6486
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
6245
6487
  egressRoutedCaps = /* @__PURE__ */ new Set();
6246
6488
  /** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
@@ -6272,6 +6514,7 @@ var LocalChildRegistry = class {
6272
6514
  this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
6273
6515
  this.isAddonPinnedCall = opts.isAddonPinnedCall;
6274
6516
  this.capTimeoutMs = opts.capTimeoutMs;
6517
+ this.capUsageObserver = opts.capUsageObserver;
6275
6518
  } else {
6276
6519
  this.server = serverOrOptions;
6277
6520
  this.onUnownedCall = onUnownedCallArg;
@@ -6438,6 +6681,29 @@ var LocalChildRegistry = class {
6438
6681
  }
6439
6682
  return candidates[0];
6440
6683
  }
6684
+ /**
6685
+ * Publish one cap-usage observation, if a sink is wired.
6686
+ *
6687
+ * Cost discipline — this is on the hot path (D181: hub-main's event loop is
6688
+ * the cluster's only queue): no sink means one undefined check; with a sink
6689
+ * it is one object literal and one `Date.now()`, and the sink itself is O(1).
6690
+ * Wrapped so a broken observer can never fail the call it observes — the
6691
+ * registry swallows too, and BOTH matter: this catch also covers a sink that
6692
+ * is not the registry.
6693
+ */
6694
+ recordCapUsage(callerChildId, providerChildId, capName, methodName) {
6695
+ const sink = this.capUsageObserver;
6696
+ if (sink === void 0 || callerChildId === null) return;
6697
+ try {
6698
+ sink({
6699
+ callerAddonId: callerChildId,
6700
+ providerAddonId: providerChildId ?? "(unresolved: parent-routed)",
6701
+ capName,
6702
+ methodName,
6703
+ atMs: Date.now()
6704
+ });
6705
+ } catch {}
6706
+ }
6441
6707
  /** First child whose cap manifest contains a descriptor matching `predicate`. */
6442
6708
  findChildId(predicate) {
6443
6709
  for (const entry of this.children.values()) if (entry.caps.some(predicate)) return entry.childId;
@@ -6707,7 +6973,9 @@ var LocalChildRegistry = class {
6707
6973
  const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
6708
6974
  const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
6709
6975
  const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, out.args) === true;
6710
- if ((!(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
6976
+ const target = !(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null;
6977
+ this.recordCapUsage(childId, target, out.capName, out.method);
6978
+ if (target !== null) {
6711
6979
  if (!this.egressRoutedCaps.has(out.capName)) {
6712
6980
  this.egressRoutedCaps.add(out.capName);
6713
6981
  this.logger?.info("routed child egress over UDS", { capName: out.capName });
@@ -7859,80 +8127,6 @@ function buildLinkChain(target, localResolver, hookup) {
7859
8127
  return links;
7860
8128
  }
7861
8129
  //#endregion
7862
- //#region src/kernel/moleculer/cap-usage-registry.ts
7863
- var CapUsageRegistry = class {
7864
- retentionSeconds;
7865
- map = /* @__PURE__ */ new Map();
7866
- constructor(opts) {
7867
- this.retentionSeconds = Math.max(1, opts?.retentionSeconds ?? 300);
7868
- }
7869
- recordCall(rec) {
7870
- try {
7871
- if (rec.callerAddonId === "" || rec.providerAddonId === "" || rec.capName === "") return;
7872
- if (!Number.isFinite(rec.atMs) || rec.atMs < 0) return;
7873
- let byProvider = this.map.get(rec.callerAddonId);
7874
- if (!byProvider) {
7875
- byProvider = /* @__PURE__ */ new Map();
7876
- this.map.set(rec.callerAddonId, byProvider);
7877
- }
7878
- let byCap = byProvider.get(rec.providerAddonId);
7879
- if (!byCap) {
7880
- byCap = /* @__PURE__ */ new Map();
7881
- byProvider.set(rec.providerAddonId, byCap);
7882
- }
7883
- let window = byCap.get(rec.capName);
7884
- if (!window) {
7885
- window = {
7886
- buckets: Array.from({ length: this.retentionSeconds }, () => 0),
7887
- lastCallAtMs: 0
7888
- };
7889
- byCap.set(rec.capName, window);
7890
- }
7891
- const bucketIdx = Math.floor(rec.atMs / 1e3) % this.retentionSeconds;
7892
- window.buckets[bucketIdx] = (window.buckets[bucketIdx] ?? 0) + 1;
7893
- if (rec.atMs > window.lastCallAtMs) window.lastCallAtMs = rec.atMs;
7894
- } catch {}
7895
- }
7896
- getGraph(opts) {
7897
- const windowSeconds = Math.max(1, Math.min(opts.windowSeconds, this.retentionSeconds));
7898
- const minMs = opts.nowMs - windowSeconds * 1e3;
7899
- const out = [];
7900
- for (const [caller, byProvider] of this.map) for (const [provider, byCap] of byProvider) for (const [capName, window] of byCap) {
7901
- if (window.lastCallAtMs < minMs) continue;
7902
- const nowBucket = Math.floor(opts.nowMs / 1e3);
7903
- let count = 0;
7904
- for (let i = 0; i < windowSeconds; i++) {
7905
- const b = (nowBucket - i + this.retentionSeconds * 1e6) % this.retentionSeconds;
7906
- count += window.buckets[b] ?? 0;
7907
- }
7908
- if (count === 0) continue;
7909
- const callsPerMin = count / windowSeconds * 60;
7910
- out.push({
7911
- callerAddonId: caller,
7912
- providerAddonId: provider,
7913
- capName,
7914
- callsPerMin,
7915
- lastCallAtMs: window.lastCallAtMs
7916
- });
7917
- }
7918
- return out;
7919
- }
7920
- /** Test / diagnostic helper — drops all recorded calls. */
7921
- clear() {
7922
- this.map.clear();
7923
- }
7924
- };
7925
- var singleton = null;
7926
- /** Hub-side singleton. The first caller seeds it; subsequent calls return the same. */
7927
- function getCapUsageRegistry() {
7928
- if (!singleton) singleton = new CapUsageRegistry();
7929
- return singleton;
7930
- }
7931
- /** Test helper — clears the singleton (kept module-private elsewhere). */
7932
- function __resetCapUsageRegistryForTests() {
7933
- singleton = null;
7934
- }
7935
- //#endregion
7936
8130
  //#region src/kernel/hwaccel/hwaccel-resolver.ts
7937
8131
  /**
7938
8132
  * Platform-probe for hardware video decode acceleration.
@@ -9504,6 +9698,195 @@ async function installManifestPythonDeps(declaration, addonDir, deps, logger) {
9504
9698
  await deps.installPythonRequirements(reqAbs);
9505
9699
  }
9506
9700
  //#endregion
9701
+ //#region src/kernel/deps/manifest-system-deps.ts
9702
+ /**
9703
+ * Auto-install the host binaries declared in an addon's manifest
9704
+ * (`camstack.addons[].systemDependencies`) before `onInitialize()` runs.
9705
+ *
9706
+ * ── Why this is framework and not addon-local ──────────────────────────────
9707
+ * The Python precedent (`camstack.addons[].python.requirements` →
9708
+ * `installManifestPythonDeps`, called from the same place in
9709
+ * `addon-runner.ts`) already answers "this addon needs something installed,
9710
+ * satisfy it before its caps register". A binary is the same question with a
9711
+ * different package manager, so it gets the same mechanism rather than a
9712
+ * second one per addon — CLAUDE.md forbids a parallel answer where one exists,
9713
+ * and the next addon that needs `rclone` or `mediainfo` inherits this for
9714
+ * free instead of copying a startup routine.
9715
+ *
9716
+ * ── The four things this has to get right ──────────────────────────────────
9717
+ * 1. **Cheap on the common path.** The binary is probed first; the package
9718
+ * manager is only invoked when it is genuinely absent. Runners respawn
9719
+ * routinely (deploys, updates, crash recovery) and a network round-trip on
9720
+ * every boot is a cost the Python mechanism does not impose either.
9721
+ * 2. **Runs every boot, not once.** The install lands INSIDE the container
9722
+ * and is lost on every image update. That is expected. "Already
9723
+ * installed" is a success, not an error.
9724
+ * 3. **Honest, not fatal.** A failure warns and returns. The addon still
9725
+ * loads; the capability that wraps the binary is responsible for refusing
9726
+ * loudly on its own surface. Aborting the runner here would take the
9727
+ * addon's SIBLINGS down with it (they share `execution.group`).
9728
+ * 4. **Platform reality is stated, not assumed.** An unsupported host logs
9729
+ * which manager it looked for and continues. Silence would read as
9730
+ * "installed".
9731
+ *
9732
+ * Every branch that declines to install SAYS SO. A dependency step that
9733
+ * produced no line at all is indistinguishable from one that never ran, and
9734
+ * the failure it precedes ("smbclient: ENOENT") arrives minutes later in a
9735
+ * different addon's logs.
9736
+ */
9737
+ var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
9738
+ /** How long a single package-manager invocation may take. */
9739
+ var INSTALL_TIMEOUT_MS = 300 * 1e3;
9740
+ /** How long the `command -v <binary>` presence probe may take. */
9741
+ var PROBE_TIMEOUT_MS = 1e4;
9742
+ /**
9743
+ * Satisfy every `systemDependencies` entry the declaration carries.
9744
+ * Resolves regardless of outcome — see the docblock.
9745
+ */
9746
+ async function installManifestSystemDeps(declaration, logger, options = {}) {
9747
+ const deps = declaration.systemDependencies;
9748
+ if (!deps || deps.length === 0) return;
9749
+ const hasBinary = options.hasBinary ?? defaultHasBinary;
9750
+ const detectManager = options.detectManager ?? defaultDetectManager;
9751
+ const run = options.run ?? defaultRun;
9752
+ const platform = options.platform ?? process.platform;
9753
+ for (const dep of deps) try {
9754
+ await satisfyOne(dep, declaration.id, {
9755
+ hasBinary,
9756
+ detectManager,
9757
+ run,
9758
+ platform
9759
+ }, logger);
9760
+ } catch (err) {
9761
+ logger.warn("system dependency step failed", { meta: {
9762
+ addonId: declaration.id,
9763
+ binary: dep.binary,
9764
+ error: err instanceof Error ? err.message : String(err)
9765
+ } });
9766
+ }
9767
+ }
9768
+ async function satisfyOne(dep, addonId, rt, logger) {
9769
+ if (await rt.hasBinary(dep.binary)) {
9770
+ logger.debug("system dependency already present", { meta: {
9771
+ addonId,
9772
+ binary: dep.binary
9773
+ } });
9774
+ return;
9775
+ }
9776
+ const manager = await rt.detectManager();
9777
+ if (manager === null) {
9778
+ logger.warn("system dependency missing and no supported package manager on this host", { meta: {
9779
+ addonId,
9780
+ binary: dep.binary,
9781
+ platform: rt.platform,
9782
+ ...dep.reason !== void 0 ? { reason: dep.reason } : {}
9783
+ } });
9784
+ return;
9785
+ }
9786
+ const packageName = dep.packages[manager];
9787
+ if (packageName === void 0 || packageName.length === 0) {
9788
+ logger.warn("system dependency missing and the addon declares no package for this manager", { meta: {
9789
+ addonId,
9790
+ binary: dep.binary,
9791
+ manager,
9792
+ ...dep.reason !== void 0 ? { reason: dep.reason } : {}
9793
+ } });
9794
+ return;
9795
+ }
9796
+ const plan = planFor(manager, packageName);
9797
+ logger.info("installing system dependency", { meta: {
9798
+ addonId,
9799
+ binary: dep.binary,
9800
+ manager,
9801
+ package: packageName
9802
+ } });
9803
+ try {
9804
+ for (const [command, args] of plan.steps) await rt.run(command, args);
9805
+ } catch (err) {
9806
+ logger.warn("system dependency install failed — the addon loads, its capability must refuse", { meta: {
9807
+ addonId,
9808
+ binary: dep.binary,
9809
+ manager,
9810
+ package: packageName,
9811
+ error: err instanceof Error ? err.message : String(err),
9812
+ ...dep.reason !== void 0 ? { reason: dep.reason } : {}
9813
+ } });
9814
+ return;
9815
+ }
9816
+ if (!await rt.hasBinary(dep.binary)) {
9817
+ logger.warn("system dependency installed but the binary still does not resolve", { meta: {
9818
+ addonId,
9819
+ binary: dep.binary,
9820
+ manager,
9821
+ package: packageName
9822
+ } });
9823
+ return;
9824
+ }
9825
+ logger.info("system dependency installed", { meta: {
9826
+ addonId,
9827
+ binary: dep.binary,
9828
+ manager,
9829
+ package: packageName
9830
+ } });
9831
+ }
9832
+ function planFor(manager, packageName) {
9833
+ switch (manager) {
9834
+ case "apt": return {
9835
+ manager,
9836
+ steps: [["apt-get", ["update"]], ["apt-get", [
9837
+ "install",
9838
+ "-y",
9839
+ "--no-install-recommends",
9840
+ packageName
9841
+ ]]]
9842
+ };
9843
+ case "apk": return {
9844
+ manager,
9845
+ steps: [["apk", [
9846
+ "add",
9847
+ "--no-cache",
9848
+ packageName
9849
+ ]]]
9850
+ };
9851
+ case "dnf": return {
9852
+ manager,
9853
+ steps: [["dnf", [
9854
+ "install",
9855
+ "-y",
9856
+ packageName
9857
+ ]]]
9858
+ };
9859
+ case "brew": return {
9860
+ manager,
9861
+ steps: [["brew", ["install", packageName]]]
9862
+ };
9863
+ }
9864
+ }
9865
+ async function defaultHasBinary(binary) {
9866
+ try {
9867
+ await execFileAsync("command", ["-v", binary], {
9868
+ timeout: PROBE_TIMEOUT_MS,
9869
+ shell: process.platform === "win32" ? false : "/bin/sh"
9870
+ });
9871
+ return true;
9872
+ } catch {
9873
+ return false;
9874
+ }
9875
+ }
9876
+ async function defaultDetectManager() {
9877
+ const candidates = node_os.platform() === "darwin" ? ["brew"] : [
9878
+ "apt",
9879
+ "apk",
9880
+ "dnf",
9881
+ "brew"
9882
+ ];
9883
+ for (const candidate of candidates) if (await defaultHasBinary(candidate === "apt" ? "apt-get" : candidate)) return candidate;
9884
+ return null;
9885
+ }
9886
+ async function defaultRun(command, args) {
9887
+ await execFileAsync(command, [...args], { timeout: INSTALL_TIMEOUT_MS });
9888
+ }
9889
+ //#endregion
9507
9890
  Object.defineProperty(exports, "AGENT_CAP_FWD_ACTION", {
9508
9891
  enumerable: true,
9509
9892
  get: function() {
@@ -9678,6 +10061,12 @@ Object.defineProperty(exports, "NATIVE_PROVIDER_SERVICE_INFIX", {
9678
10061
  return NATIVE_PROVIDER_SERVICE_INFIX;
9679
10062
  }
9680
10063
  });
10064
+ Object.defineProperty(exports, "PARENT_ROUTED_PROVIDER_ADDON_ID", {
10065
+ enumerable: true,
10066
+ get: function() {
10067
+ return PARENT_ROUTED_PROVIDER_ADDON_ID;
10068
+ }
10069
+ });
9681
10070
  Object.defineProperty(exports, "RSS_BUDGET_REANNOUNCE_MIN_MS", {
9682
10071
  enumerable: true,
9683
10072
  get: function() {
@@ -10098,6 +10487,12 @@ Object.defineProperty(exports, "installManifestPythonDeps", {
10098
10487
  return installManifestPythonDeps;
10099
10488
  }
10100
10489
  });
10490
+ Object.defineProperty(exports, "installManifestSystemDeps", {
10491
+ enumerable: true,
10492
+ get: function() {
10493
+ return installManifestSystemDeps;
10494
+ }
10495
+ });
10101
10496
  Object.defineProperty(exports, "ipcParentLink", {
10102
10497
  enumerable: true,
10103
10498
  get: function() {
@@ -1,4 +1,4 @@
1
- import { zt as asJsonObject } from "./dist-LvKus-mT.mjs";
1
+ import { Ht as asJsonObject } from "./dist-DKs---Vx.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CDgIzo82.js");
1
+ const require_dist = require("./dist-CcvXUhHK.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.157",
3
+ "version": "1.2.160",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",
@@ -135,6 +135,7 @@
135
135
  "displayName": "Backups",
136
136
  "description": "Backup archives.",
137
137
  "cardinality": "multi",
138
+ "access": "cap-mediated",
138
139
  "defaultRoot": "backup"
139
140
  }
140
141
  ]