@camstack/system 1.2.21 → 1.2.23

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 (61) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -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 +17 -13
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1319 -1097
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1319 -1097
  12. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +33 -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/device-manager/device-manager.addon.js +1 -1
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.d.ts +16 -0
  24. package/dist/builtins/local-network/local-network.addon.js +30 -2
  25. package/dist/builtins/local-network/local-network.addon.mjs +30 -2
  26. package/dist/builtins/loki-logging/index.js +34 -12
  27. package/dist/builtins/loki-logging/index.mjs +34 -12
  28. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +19 -0
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +7 -0
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +112 -6
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +112 -6
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +17 -1
  45. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +5 -0
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-DmQ6thmF.mjs → dist-CIek6F9X.mjs} +536 -76
  51. package/dist/{dist-OQ1yU92D.js → dist-CS0pdC8l.js} +536 -76
  52. package/dist/index.js +58 -8
  53. package/dist/index.mjs +58 -8
  54. package/dist/kernel/capability-registry.d.ts +9 -0
  55. package/dist/kernel/transport/local-child-registry.d.ts +18 -0
  56. package/dist/kernel/transport/parent-unowned-call.d.ts +22 -0
  57. package/dist/logging/log-manager.d.ts +32 -1
  58. package/dist/logging/partitioned-log-buffer.d.ts +15 -3
  59. package/dist/{manifest-python-deps-B30qVAuW.mjs → manifest-python-deps-57PA4rHm.mjs} +15 -6
  60. package/dist/{manifest-python-deps-CnRW2Utv.js → manifest-python-deps-CqDPWqEE.js} +14 -5
  61. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-OQ1yU92D.js");
3
+ const require_dist = require("./dist-CS0pdC8l.js");
4
4
  const require_model_download_service = require("./model-download-service-hf0ookyy.js");
5
- const require_manifest_python_deps = require("./manifest-python-deps-CnRW2Utv.js");
5
+ const require_manifest_python_deps = require("./manifest-python-deps-CqDPWqEE.js");
6
6
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
7
7
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
8
8
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
@@ -991,6 +991,18 @@ var PartitionedLogBuffer = class {
991
991
  this.maxTotalEntries = options.maxTotalEntries ?? null;
992
992
  this.pruneLevel = options.pruneLevel ?? "debug";
993
993
  }
994
+ /**
995
+ * Apply new limits at runtime. Shrinking takes effect as entries are pushed
996
+ * (each partition trims to the new capacity on its next write) — existing
997
+ * entries are NOT dropped eagerly: losing history the operator is currently
998
+ * reading, the instant they lower a number, is a worse surprise than a
999
+ * momentarily oversized buffer.
1000
+ */
1001
+ reconfigure(options) {
1002
+ if (options.perAddonCapacity !== void 0 && options.perAddonCapacity > 0) this.perAddonCapacity = options.perAddonCapacity;
1003
+ if (options.maxTotalEntries !== void 0) this.maxTotalEntries = options.maxTotalEntries;
1004
+ if (options.pruneLevel !== void 0) this.pruneLevel = options.pruneLevel;
1005
+ }
994
1006
  bufferFor(key) {
995
1007
  let buf = this.buffers.get(key);
996
1008
  if (buf === void 0) {
@@ -1130,11 +1142,24 @@ var ScopedLogger = class ScopedLogger {
1130
1142
  };
1131
1143
  }
1132
1144
  };
1133
- //#endregion
1134
- //#region src/logging/log-manager.ts
1135
1145
  var LogManager = class {
1136
1146
  ringBuffer;
1147
+ /**
1148
+ * Destinations, each tagged with the addon that provides it (when known) so
1149
+ * the fan-out can honour the operator's per-destination toggle.
1150
+ */
1137
1151
  destinations = [];
1152
+ /**
1153
+ * "Is this destination enabled?", injected by the wiring side (which owns
1154
+ * the capability registry). Absent ⇒ everything is enabled.
1155
+ *
1156
+ * WHY: the orchestrator's Enable/Disable toggle only added the addon to
1157
+ * `disabledProviders` and NOTHING consulted it for `log-destination` — so
1158
+ * console and winston could not be turned off at all and the badge in the UI
1159
+ * lied, while Loki appeared to work only because it carried a SECOND,
1160
+ * private `enabled` setting of its own (audit 2026-07-30).
1161
+ */
1162
+ isDestinationEnabled;
1138
1163
  subscribers = /* @__PURE__ */ new Map();
1139
1164
  deviceNameLookup = null;
1140
1165
  /**
@@ -1169,8 +1194,11 @@ var LogManager = class {
1169
1194
  createLogger(scope) {
1170
1195
  return new ScopedLogger(scope, (entry) => {
1171
1196
  const enriched = this.enrichDeviceName(entry);
1172
- this.ringBuffer.push(enriched);
1173
- for (const dest of this.destinations) dest.write(enriched);
1197
+ if (this.isDestinationEnabled?.("log-buffer") !== false) this.ringBuffer.push(enriched);
1198
+ for (const entry of this.destinations) {
1199
+ if (entry.addonId !== void 0 && this.isDestinationEnabled?.(entry.addonId) === false) continue;
1200
+ entry.dest.write(enriched);
1201
+ }
1174
1202
  for (const [, sub] of this.subscribers) if (this.matchesFilter(enriched, sub.filter)) try {
1175
1203
  sub.callback(enriched);
1176
1204
  } catch {}
@@ -1228,15 +1256,26 @@ var LogManager = class {
1228
1256
  * traffic starts. Lets a destination added mid-boot
1229
1257
  * still receive boot-time log entries.
1230
1258
  */
1259
+ /** Apply new in-memory buffer limits at runtime (see PartitionedLogBuffer). */
1260
+ reconfigureBuffer(options) {
1261
+ this.ringBuffer.reconfigure(options);
1262
+ }
1263
+ /** Set the per-destination enablement check (see {@link isDestinationEnabled}). */
1264
+ setDestinationEnabledCheck(check) {
1265
+ this.isDestinationEnabled = check ?? void 0;
1266
+ }
1231
1267
  addDestination(dest, opts = {}) {
1232
1268
  const replay = opts.replay ?? true;
1233
- this.destinations.push(dest);
1269
+ this.destinations.push(opts.addonId === void 0 ? { dest } : {
1270
+ dest,
1271
+ addonId: opts.addonId
1272
+ });
1234
1273
  if (replay) for (const entry of this.ringBuffer.query({})) try {
1235
1274
  dest.write(entry);
1236
1275
  } catch {}
1237
1276
  }
1238
1277
  removeDestination(dest) {
1239
- const index = this.destinations.indexOf(dest);
1278
+ const index = this.destinations.findIndex((d) => d.dest === dest);
1240
1279
  if (index !== -1) this.destinations.splice(index, 1);
1241
1280
  }
1242
1281
  query(filter) {
@@ -5168,6 +5207,17 @@ var CapabilityRegistry = class CapabilityRegistry {
5168
5207
  if (!state || state.definition.mode !== "collection") return [];
5169
5208
  return [...state.providers.entries()].filter(([addonId]) => !state.disabledProviders.has(addonId));
5170
5209
  }
5210
+ /**
5211
+ * Is `addonId` currently DISABLED as a provider of `capability`?
5212
+ *
5213
+ * Exposed because the disabled set was write-only for `log-destination`:
5214
+ * the orchestrator toggle added the addon here and nothing ever read it, so
5215
+ * console and winston could not be turned off at all while the UI badge said
5216
+ * they were (audit 2026-07-30).
5217
+ */
5218
+ isProviderDisabled(capability, addonId) {
5219
+ return this.capabilities.get(capability)?.disabledProviders.has(addonId) === true;
5220
+ }
5171
5221
  /** Get the full CapabilityDefinition for a declared capability. */
5172
5222
  getDefinition(capability) {
5173
5223
  return this.capabilities.get(capability)?.definition;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { B as logLevelAtMost, Dt as readinessKey, Et as parseJsonUnknown$1, F as isVoidInput, I as kebabToCamel, L as lifecycleJobSchema, M as isArrayOutputSchema, N as isCollectionArrayMethod, Ot as resolveCapMount, P as isObjectInput, Tt as parseJsonObject, V as looseSchema, W as objectInputDeclaresAddonId, _t as asString$1, c as RUNTIME_DEFAULTS, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, jt as EventCategory$1, k as extractNestedAddonId, kt as scopeKey, m as addonSettingsCapability, ot as DATAPLANE_SECRET_HEADER$1, pt as ReadinessTimeoutError, q as procedureAuthKey, s as METHOD_ACCESS_MAP, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, xt as expandCapMethods, yt as emitDownForOwnedCaps } from "./dist-DmQ6thmF.mjs";
2
+ import { B as logLevelAtMost, Dt as readinessKey, Et as parseJsonUnknown$1, F as isVoidInput, I as kebabToCamel, L as lifecycleJobSchema, M as isArrayOutputSchema, N as isCollectionArrayMethod, Ot as resolveCapMount, P as isObjectInput, Tt as parseJsonObject, V as looseSchema, W as objectInputDeclaresAddonId, _t as asString$1, c as RUNTIME_DEFAULTS, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, jt as EventCategory$1, k as extractNestedAddonId, kt as scopeKey, m as addonSettingsCapability, ot as DATAPLANE_SECRET_HEADER$1, pt as ReadinessTimeoutError, q as procedureAuthKey, s as METHOD_ACCESS_MAP, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, xt as expandCapMethods, yt as emitDownForOwnedCaps } from "./dist-CIek6F9X.mjs";
3
3
  import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-Cp9f4dk6.mjs";
4
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-B30qVAuW.mjs";
4
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-57PA4rHm.mjs";
5
5
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
6
6
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
7
7
  import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
@@ -984,6 +984,18 @@ var PartitionedLogBuffer = class {
984
984
  this.maxTotalEntries = options.maxTotalEntries ?? null;
985
985
  this.pruneLevel = options.pruneLevel ?? "debug";
986
986
  }
987
+ /**
988
+ * Apply new limits at runtime. Shrinking takes effect as entries are pushed
989
+ * (each partition trims to the new capacity on its next write) — existing
990
+ * entries are NOT dropped eagerly: losing history the operator is currently
991
+ * reading, the instant they lower a number, is a worse surprise than a
992
+ * momentarily oversized buffer.
993
+ */
994
+ reconfigure(options) {
995
+ if (options.perAddonCapacity !== void 0 && options.perAddonCapacity > 0) this.perAddonCapacity = options.perAddonCapacity;
996
+ if (options.maxTotalEntries !== void 0) this.maxTotalEntries = options.maxTotalEntries;
997
+ if (options.pruneLevel !== void 0) this.pruneLevel = options.pruneLevel;
998
+ }
987
999
  bufferFor(key) {
988
1000
  let buf = this.buffers.get(key);
989
1001
  if (buf === void 0) {
@@ -1123,11 +1135,24 @@ var ScopedLogger = class ScopedLogger {
1123
1135
  };
1124
1136
  }
1125
1137
  };
1126
- //#endregion
1127
- //#region src/logging/log-manager.ts
1128
1138
  var LogManager = class {
1129
1139
  ringBuffer;
1140
+ /**
1141
+ * Destinations, each tagged with the addon that provides it (when known) so
1142
+ * the fan-out can honour the operator's per-destination toggle.
1143
+ */
1130
1144
  destinations = [];
1145
+ /**
1146
+ * "Is this destination enabled?", injected by the wiring side (which owns
1147
+ * the capability registry). Absent ⇒ everything is enabled.
1148
+ *
1149
+ * WHY: the orchestrator's Enable/Disable toggle only added the addon to
1150
+ * `disabledProviders` and NOTHING consulted it for `log-destination` — so
1151
+ * console and winston could not be turned off at all and the badge in the UI
1152
+ * lied, while Loki appeared to work only because it carried a SECOND,
1153
+ * private `enabled` setting of its own (audit 2026-07-30).
1154
+ */
1155
+ isDestinationEnabled;
1131
1156
  subscribers = /* @__PURE__ */ new Map();
1132
1157
  deviceNameLookup = null;
1133
1158
  /**
@@ -1162,8 +1187,11 @@ var LogManager = class {
1162
1187
  createLogger(scope) {
1163
1188
  return new ScopedLogger(scope, (entry) => {
1164
1189
  const enriched = this.enrichDeviceName(entry);
1165
- this.ringBuffer.push(enriched);
1166
- for (const dest of this.destinations) dest.write(enriched);
1190
+ if (this.isDestinationEnabled?.("log-buffer") !== false) this.ringBuffer.push(enriched);
1191
+ for (const entry of this.destinations) {
1192
+ if (entry.addonId !== void 0 && this.isDestinationEnabled?.(entry.addonId) === false) continue;
1193
+ entry.dest.write(enriched);
1194
+ }
1167
1195
  for (const [, sub] of this.subscribers) if (this.matchesFilter(enriched, sub.filter)) try {
1168
1196
  sub.callback(enriched);
1169
1197
  } catch {}
@@ -1221,15 +1249,26 @@ var LogManager = class {
1221
1249
  * traffic starts. Lets a destination added mid-boot
1222
1250
  * still receive boot-time log entries.
1223
1251
  */
1252
+ /** Apply new in-memory buffer limits at runtime (see PartitionedLogBuffer). */
1253
+ reconfigureBuffer(options) {
1254
+ this.ringBuffer.reconfigure(options);
1255
+ }
1256
+ /** Set the per-destination enablement check (see {@link isDestinationEnabled}). */
1257
+ setDestinationEnabledCheck(check) {
1258
+ this.isDestinationEnabled = check ?? void 0;
1259
+ }
1224
1260
  addDestination(dest, opts = {}) {
1225
1261
  const replay = opts.replay ?? true;
1226
- this.destinations.push(dest);
1262
+ this.destinations.push(opts.addonId === void 0 ? { dest } : {
1263
+ dest,
1264
+ addonId: opts.addonId
1265
+ });
1227
1266
  if (replay) for (const entry of this.ringBuffer.query({})) try {
1228
1267
  dest.write(entry);
1229
1268
  } catch {}
1230
1269
  }
1231
1270
  removeDestination(dest) {
1232
- const index = this.destinations.indexOf(dest);
1271
+ const index = this.destinations.findIndex((d) => d.dest === dest);
1233
1272
  if (index !== -1) this.destinations.splice(index, 1);
1234
1273
  }
1235
1274
  query(filter) {
@@ -5161,6 +5200,17 @@ var CapabilityRegistry = class CapabilityRegistry {
5161
5200
  if (!state || state.definition.mode !== "collection") return [];
5162
5201
  return [...state.providers.entries()].filter(([addonId]) => !state.disabledProviders.has(addonId));
5163
5202
  }
5203
+ /**
5204
+ * Is `addonId` currently DISABLED as a provider of `capability`?
5205
+ *
5206
+ * Exposed because the disabled set was write-only for `log-destination`:
5207
+ * the orchestrator toggle added the addon here and nothing ever read it, so
5208
+ * console and winston could not be turned off at all while the UI badge said
5209
+ * they were (audit 2026-07-30).
5210
+ */
5211
+ isProviderDisabled(capability, addonId) {
5212
+ return this.capabilities.get(capability)?.disabledProviders.has(addonId) === true;
5213
+ }
5164
5214
  /** Get the full CapabilityDefinition for a declared capability. */
5165
5215
  getDefinition(capability) {
5166
5216
  return this.capabilities.get(capability)?.definition;
@@ -311,6 +311,15 @@ export declare class CapabilityRegistry {
311
311
  */
312
312
  getCollectionEntries<K extends keyof CollectionCapabilityMap>(capability: K): readonly (readonly [string, CollectionCapabilityMap[K]])[];
313
313
  getCollectionEntries<T = unknown>(capability: string): readonly (readonly [string, T])[];
314
+ /**
315
+ * Is `addonId` currently DISABLED as a provider of `capability`?
316
+ *
317
+ * Exposed because the disabled set was write-only for `log-destination`:
318
+ * the orchestrator toggle added the addon here and nothing ever read it, so
319
+ * console and winston could not be turned off at all while the UI badge said
320
+ * they were (audit 2026-07-30).
321
+ */
322
+ isProviderDisabled(capability: string, addonId: string): boolean;
314
323
  /** Get the full CapabilityDefinition for a declared capability. */
315
324
  getDefinition(capability: string): CapabilityDefinition | undefined;
316
325
  /** Get the mode declared for a capability. */
@@ -79,6 +79,22 @@ export interface LocalChildRegistryOptions {
79
79
  * distinguish a pin-to-self from a pin-to-another-node.
80
80
  */
81
81
  readonly ownNodeId?: string;
82
+ /**
83
+ * Does `(capName, method)` name a `mode:'collection'` array method whose
84
+ * contract is the UNION across every provider? Such a call must NOT be
85
+ * short-circuited to a local sibling: `resolveChildId` picks ONE provider
86
+ * (first-registered, or the singleton preference — meaningless here), so the
87
+ * caller would see a fraction of the catalog the hub's own tRPC router
88
+ * returns. It goes to `onUnownedCall`, which owns the aggregate over local
89
+ * siblings, in-process providers and remote nodes alike.
90
+ *
91
+ * `notification-output` has three provider siblings; the Notification Center
92
+ * landed on one holding no targets, read the empty list as "target deleted"
93
+ * and permanently dead-lettered every notification it ever produced
94
+ * (2026-07-30). Omitted ⇒ prior behaviour exactly (sibling short-circuit for
95
+ * everything). The kernel has no cap registry, so the wiring side injects it.
96
+ */
97
+ readonly isAggregatedCollectionMethod?: (capName: string, method: string) => boolean;
82
98
  }
83
99
  /**
84
100
  * Parent-side authority for local addon-runners reachable over a
@@ -100,6 +116,8 @@ export declare class LocalChildRegistry {
100
116
  private readonly resolveChildIdForAddon?;
101
117
  /** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
102
118
  private readonly ownNodeId?;
119
+ /** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
120
+ private readonly isAggregatedCollectionMethod?;
103
121
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
104
122
  private readonly egressRoutedCaps;
105
123
  /** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
@@ -110,6 +110,28 @@ export interface ParentUnownedCallDeps {
110
110
  * `CapabilityDefinition`s and the registry) injects this.
111
111
  */
112
112
  readonly resolveEmptyCollection?: (capName: string, method: string) => readonly unknown[] | null;
113
+ /**
114
+ * Optional collection FAN-OUT. Returns the union of `method` across every
115
+ * registered provider of a `mode:'collection'` cap, or `null` when the
116
+ * (cap, method) pair is not an aggregated collection array-method.
117
+ *
118
+ * Why: routing resolves ONE provider (`resolveChildId` picks the
119
+ * first-registered child, or the singleton preference — meaningless for a
120
+ * collection). The hub's tRPC cap-router instead fans array methods across
121
+ * every provider (`concatArrayMethod`). A forked addon calling the same cap
122
+ * through `ctx.api` therefore saw a DIFFERENT, smaller answer than the hub's
123
+ * own router — and for `notification-output`, whose three providers include
124
+ * two that hold no targets, it saw `[]`. The Notification Center read that
125
+ * empty catalog as "the target was deleted" and permanently dead-lettered
126
+ * every notification it ever produced (2026-07-30).
127
+ *
128
+ * Skipped when the call carries an explicit `nodeId` pin — a pin must route
129
+ * to its node, never be answered by a cluster-wide union. The kernel layer
130
+ * has no cap registry, so the wiring side injects this (same shape as
131
+ * {@link resolveEmptyCollection}, and it shares that path's aggregation code
132
+ * so the two answers cannot drift).
133
+ */
134
+ readonly callCollectionAggregate?: (capName: string, method: string, args: unknown) => Promise<readonly unknown[] | null>;
113
135
  /**
114
136
  * Optional predicate — returns `true` when `capName` is a HUB-CORE `$`-service
115
137
  * cap (`system`, `addons`, `nodes`, `capabilities`, `stream-probe`, …): served
@@ -1,4 +1,4 @@
1
- import { LogEntry, LogFilter } from './log-ring-buffer.js';
1
+ import { LogEntry, LogFilter, LogLevel } from './log-ring-buffer.js';
2
2
  import { PartitionedLogBufferOptions } from './partitioned-log-buffer.js';
3
3
  import { IScopedLogger } from './scoped-logger.js';
4
4
  export interface ILogDestination {
@@ -7,9 +7,31 @@ export interface ILogDestination {
7
7
  write(entry: LogEntry): void;
8
8
  query?(filter: LogFilter): Promise<readonly LogEntry[]>;
9
9
  }
10
+ /**
11
+ * The reserved destination id of the in-memory buffer. Not an addon: the
12
+ * buffer must exist before any addon loads (boot lines) and it backs
13
+ * `logs.query`/`subscribe`, so it lives in the kernel — but it answers to the
14
+ * SAME orchestrator toggle as every other destination.
15
+ */
16
+ export declare const LOG_BUFFER_DESTINATION_ID = "log-buffer";
10
17
  export declare class LogManager {
11
18
  private readonly ringBuffer;
19
+ /**
20
+ * Destinations, each tagged with the addon that provides it (when known) so
21
+ * the fan-out can honour the operator's per-destination toggle.
22
+ */
12
23
  private readonly destinations;
24
+ /**
25
+ * "Is this destination enabled?", injected by the wiring side (which owns
26
+ * the capability registry). Absent ⇒ everything is enabled.
27
+ *
28
+ * WHY: the orchestrator's Enable/Disable toggle only added the addon to
29
+ * `disabledProviders` and NOTHING consulted it for `log-destination` — so
30
+ * console and winston could not be turned off at all and the badge in the UI
31
+ * lied, while Loki appeared to work only because it carried a SECOND,
32
+ * private `enabled` setting of its own (audit 2026-07-30).
33
+ */
34
+ private isDestinationEnabled?;
13
35
  private readonly subscribers;
14
36
  private deviceNameLookup;
15
37
  /**
@@ -48,8 +70,17 @@ export declare class LogManager {
48
70
  * traffic starts. Lets a destination added mid-boot
49
71
  * still receive boot-time log entries.
50
72
  */
73
+ /** Apply new in-memory buffer limits at runtime (see PartitionedLogBuffer). */
74
+ reconfigureBuffer(options: {
75
+ readonly perAddonCapacity?: number;
76
+ readonly maxTotalEntries?: number | null;
77
+ readonly pruneLevel?: LogLevel;
78
+ }): void;
79
+ /** Set the per-destination enablement check (see {@link isDestinationEnabled}). */
80
+ setDestinationEnabledCheck(check: ((addonId: string) => boolean) | null): void;
51
81
  addDestination(dest: ILogDestination, opts?: {
52
82
  replay?: boolean;
83
+ addonId?: string;
53
84
  }): void;
54
85
  removeDestination(dest: ILogDestination): void;
55
86
  query(filter: LogFilter): LogEntry[];
@@ -42,12 +42,24 @@ export interface PartitionedLogBufferOptions {
42
42
  */
43
43
  export declare class PartitionedLogBuffer {
44
44
  private readonly buffers;
45
- private readonly perAddonCapacity;
46
- private readonly maxTotalEntries;
47
- private readonly pruneLevel;
45
+ private perAddonCapacity;
46
+ private maxTotalEntries;
47
+ private pruneLevel;
48
48
  /** Running total, so `push` does not walk every bucket to decide. */
49
49
  private totalEntries;
50
50
  constructor(perAddonCapacity?: number, options?: PartitionedLogBufferOptions);
51
+ /**
52
+ * Apply new limits at runtime. Shrinking takes effect as entries are pushed
53
+ * (each partition trims to the new capacity on its next write) — existing
54
+ * entries are NOT dropped eagerly: losing history the operator is currently
55
+ * reading, the instant they lower a number, is a worse surprise than a
56
+ * momentarily oversized buffer.
57
+ */
58
+ reconfigure(options: {
59
+ readonly perAddonCapacity?: number;
60
+ readonly maxTotalEntries?: number | null;
61
+ readonly pruneLevel?: LogLevel;
62
+ }): void;
51
63
  private bufferFor;
52
64
  push(entry: LogEntry): void;
53
65
  /**
@@ -5,7 +5,7 @@ import * as path$1 from "node:path";
5
5
  import { isAbsolute, join } from "node:path";
6
6
  import * as crypto$1 from "node:crypto";
7
7
  import { randomBytes, randomUUID } from "node:crypto";
8
- import { DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep } from "@camstack/types/addon";
8
+ import { DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
9
9
  import { execFile } from "node:child_process";
10
10
  import { promisify } from "node:util";
11
11
  import * as os from "node:os";
@@ -4301,6 +4301,8 @@ var LocalChildRegistry = class {
4301
4301
  resolveChildIdForAddon;
4302
4302
  /** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
4303
4303
  ownNodeId;
4304
+ /** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
4305
+ isAggregatedCollectionMethod;
4304
4306
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
4305
4307
  egressRoutedCaps = /* @__PURE__ */ new Set();
4306
4308
  /** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
@@ -4329,6 +4331,7 @@ var LocalChildRegistry = class {
4329
4331
  this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
4330
4332
  this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
4331
4333
  this.ownNodeId = opts.ownNodeId;
4334
+ this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
4332
4335
  } else {
4333
4336
  this.server = serverOrOptions;
4334
4337
  this.onUnownedCall = onUnownedCallArg;
@@ -4692,7 +4695,7 @@ var LocalChildRegistry = class {
4692
4695
  };
4693
4696
  const pinnedNodeId = out.nodeId ?? extractNodeId(out.args);
4694
4697
  const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
4695
- if ((pinnedNodeId === void 0 || pinTargetsThisNode ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
4698
+ if ((!(pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true) && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
4696
4699
  if (!this.egressRoutedCaps.has(out.capName)) {
4697
4700
  this.egressRoutedCaps.add(out.capName);
4698
4701
  this.logger?.info("routed child egress over UDS", { capName: out.capName });
@@ -5248,6 +5251,8 @@ function createParentUnownedCallHandler(deps) {
5248
5251
  if (nodeId === void 0) {
5249
5252
  const empty = deps.resolveEmptyCollection?.(input.capName, input.method);
5250
5253
  if (empty != null) return empty;
5254
+ const aggregate = await deps.callCollectionAggregate?.(input.capName, input.method, input.args);
5255
+ if (aggregate != null) return aggregate;
5251
5256
  }
5252
5257
  const resolver = deps.getResolver();
5253
5258
  if (resolver !== null) try {
@@ -5415,14 +5420,18 @@ function localProviderLink(resolver, observerOpts) {
5415
5420
  return ({ op, next }) => {
5416
5421
  const parsed = parsePath(op.path);
5417
5422
  if (!parsed) return next(op);
5423
+ const opWithDefaults = op.input === void 0 ? op : {
5424
+ ...op,
5425
+ input: withCapInputDefaults(parsed.capName, parsed.method, op.input)
5426
+ };
5418
5427
  if (localBase !== void 0) {
5419
5428
  const pinnedNodeId = readNodePin(op.context);
5420
- if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(op);
5429
+ if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(opWithDefaults);
5421
5430
  }
5422
5431
  const provider = resolver.getByName(parsed.capName);
5423
- if (!provider || typeof provider !== "object") return next(op);
5432
+ if (!provider || typeof provider !== "object") return next(opWithDefaults);
5424
5433
  const fn = Reflect.get(provider, parsed.method);
5425
- if (typeof fn !== "function") return next(op);
5434
+ if (typeof fn !== "function") return next(opWithDefaults);
5426
5435
  if (observerOpts) try {
5427
5436
  const providerAddonId = observerOpts.providerAddonIdForCap(parsed.capName);
5428
5437
  if (providerAddonId) observerOpts.observer({
@@ -5434,7 +5443,7 @@ function localProviderLink(resolver, observerOpts) {
5434
5443
  });
5435
5444
  } catch {}
5436
5445
  return observable((observer) => {
5437
- Promise.resolve(fn.call(provider, op.input)).then((data) => {
5446
+ Promise.resolve(fn.call(provider, opWithDefaults.input)).then((data) => {
5438
5447
  observer.next({ result: {
5439
5448
  type: "data",
5440
5449
  data
@@ -4303,6 +4303,8 @@ var LocalChildRegistry = class {
4303
4303
  resolveChildIdForAddon;
4304
4304
  /** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
4305
4305
  ownNodeId;
4306
+ /** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
4307
+ isAggregatedCollectionMethod;
4306
4308
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
4307
4309
  egressRoutedCaps = /* @__PURE__ */ new Set();
4308
4310
  /** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
@@ -4331,6 +4333,7 @@ var LocalChildRegistry = class {
4331
4333
  this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
4332
4334
  this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
4333
4335
  this.ownNodeId = opts.ownNodeId;
4336
+ this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
4334
4337
  } else {
4335
4338
  this.server = serverOrOptions;
4336
4339
  this.onUnownedCall = onUnownedCallArg;
@@ -4694,7 +4697,7 @@ var LocalChildRegistry = class {
4694
4697
  };
4695
4698
  const pinnedNodeId = out.nodeId ?? extractNodeId(out.args);
4696
4699
  const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
4697
- if ((pinnedNodeId === void 0 || pinTargetsThisNode ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
4700
+ if ((!(pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true) && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
4698
4701
  if (!this.egressRoutedCaps.has(out.capName)) {
4699
4702
  this.egressRoutedCaps.add(out.capName);
4700
4703
  this.logger?.info("routed child egress over UDS", { capName: out.capName });
@@ -5250,6 +5253,8 @@ function createParentUnownedCallHandler(deps) {
5250
5253
  if (nodeId === void 0) {
5251
5254
  const empty = deps.resolveEmptyCollection?.(input.capName, input.method);
5252
5255
  if (empty != null) return empty;
5256
+ const aggregate = await deps.callCollectionAggregate?.(input.capName, input.method, input.args);
5257
+ if (aggregate != null) return aggregate;
5253
5258
  }
5254
5259
  const resolver = deps.getResolver();
5255
5260
  if (resolver !== null) try {
@@ -5417,14 +5422,18 @@ function localProviderLink(resolver, observerOpts) {
5417
5422
  return ({ op, next }) => {
5418
5423
  const parsed = parsePath(op.path);
5419
5424
  if (!parsed) return next(op);
5425
+ const opWithDefaults = op.input === void 0 ? op : {
5426
+ ...op,
5427
+ input: (0, _camstack_types_addon.withCapInputDefaults)(parsed.capName, parsed.method, op.input)
5428
+ };
5420
5429
  if (localBase !== void 0) {
5421
5430
  const pinnedNodeId = (0, _camstack_types_addon.readNodePin)(op.context);
5422
- if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(op);
5431
+ if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(opWithDefaults);
5423
5432
  }
5424
5433
  const provider = resolver.getByName(parsed.capName);
5425
- if (!provider || typeof provider !== "object") return next(op);
5434
+ if (!provider || typeof provider !== "object") return next(opWithDefaults);
5426
5435
  const fn = Reflect.get(provider, parsed.method);
5427
- if (typeof fn !== "function") return next(op);
5436
+ if (typeof fn !== "function") return next(opWithDefaults);
5428
5437
  if (observerOpts) try {
5429
5438
  const providerAddonId = observerOpts.providerAddonIdForCap(parsed.capName);
5430
5439
  if (providerAddonId) observerOpts.observer({
@@ -5436,7 +5445,7 @@ function localProviderLink(resolver, observerOpts) {
5436
5445
  });
5437
5446
  } catch {}
5438
5447
  return observable((observer) => {
5439
- Promise.resolve(fn.call(provider, op.input)).then((data) => {
5448
+ Promise.resolve(fn.call(provider, opWithDefaults.input)).then((data) => {
5440
5449
  observer.next({ result: {
5441
5450
  type: "data",
5442
5451
  data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.21",
3
+ "version": "1.2.23",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",
@@ -125,7 +125,8 @@
125
125
  "id": "backups",
126
126
  "displayName": "Backups",
127
127
  "description": "Backup archives.",
128
- "cardinality": "multi"
128
+ "cardinality": "multi",
129
+ "defaultRoot": "backup"
129
130
  }
130
131
  ]
131
132
  },