@camstack/system 1.2.239 → 1.2.241

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 (57) 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.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  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/liveness-monitor/liveness-monitor.addon.js +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  25. package/dist/builtins/local-network/local-network.addon.js +1 -1
  26. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  27. package/dist/builtins/loki-logging/index.js +1 -1
  28. package/dist/builtins/loki-logging/index.mjs +1 -1
  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 +0 -0
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/system-config/system-config.addon.js +1 -1
  44. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  45. package/dist/builtins/winston-logging/index.js +1 -1
  46. package/dist/builtins/winston-logging/index.mjs +1 -1
  47. package/dist/{dist-DGLUnOm4.mjs → dist-BjSYm1hH.mjs} +113 -9
  48. package/dist/{dist-B3ELrqnI.js → dist-C6JIhqS6.js} +113 -9
  49. package/dist/index.js +2 -2
  50. package/dist/index.mjs +2 -2
  51. package/dist/kernel/moleculer/device-event-ring.d.ts +113 -0
  52. package/dist/kernel/moleculer/event-bus-core.d.ts +18 -0
  53. package/dist/{manifest-system-deps-BEj9Dpu1.mjs → manifest-system-deps-CfR7Z90J.mjs} +136 -2
  54. package/dist/{manifest-system-deps-CrSP0Kkp.js → manifest-system-deps-CzyjTMcO.js} +136 -2
  55. package/dist/{retired-settings-keys-B3j3_sD9.js → retired-settings-keys-C53Y2Q1C.js} +1 -1
  56. package/dist/{retired-settings-keys-DBy2ObM_.mjs → retired-settings-keys-MpSfaPEY.mjs} +1 -1
  57. package/package.json +2 -2
@@ -690,7 +690,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
690
690
  return EventCategory;
691
691
  }({});
692
692
  //#endregion
693
- //#region ../types/dist/sleep-e0RweZzn.mjs
693
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
694
694
  /**
695
695
  * The audio chunk plane's byte format, and the ONE expansion from a coded
696
696
  * window to float samples (D455).
@@ -16998,6 +16998,48 @@ var NcAlarmConfigSchema = zod.z.object({
16998
16998
  settings: NcAlarmSettingsSchema,
16999
16999
  coverage: zod.z.array(NcAlarmModeCoverageSchema)
17000
17000
  });
17001
+ /**
17002
+ * ONE rule's demand on ONE camera's clip ring.
17003
+ *
17004
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
17005
+ * camera, so the answer stays a faithful description of the rules and the
17006
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
17007
+ * lives. A camera that appears in no ask is being told **nothing** — which is
17008
+ * a different thing from never having been told, and only the envelope
17009
+ * (`rulesLoaded`) can tell those apart.
17010
+ */
17011
+ var NcClipRetentionAskSchema = zod.z.object({
17012
+ /**
17013
+ * The camera this ask is about. **Absent = every camera**, which is what a
17014
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
17015
+ * asking everywhere.
17016
+ */
17017
+ deviceId: zod.z.number().int().nonnegative().optional(),
17018
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
17019
+ * needs nothing produces no ask at all. */
17020
+ seconds: zod.z.number().min(0).max(60),
17021
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
17022
+ * assigned, which is what the cut defaults to. */
17023
+ profile: CamProfileSchema.optional(),
17024
+ /** Who is asking — so a retention the operator did not expect names a rule. */
17025
+ ruleId: zod.z.string(),
17026
+ ruleName: zod.z.string(),
17027
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
17028
+ reason: zod.z.enum(["window", "occupancy"])
17029
+ });
17030
+ /**
17031
+ * The whole answer, with the one bit that keeps absence from reading as zero.
17032
+ *
17033
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
17034
+ * once. A centre that is up but has not read its rules yet answers with an
17035
+ * empty ask list that means nothing at all — applying it would drop every
17036
+ * camera's ring to zero for the first events after a restart, which is the
17037
+ * footage this mechanism exists to keep.
17038
+ */
17039
+ var NcClipRetentionPlanSchema = zod.z.object({
17040
+ rulesLoaded: zod.z.boolean(),
17041
+ asks: zod.z.array(NcClipRetentionAskSchema).readonly()
17042
+ });
17001
17043
  var notificationRulesCapability = {
17002
17044
  name: "notification-rules",
17003
17045
  scope: "system",
@@ -17030,6 +17072,23 @@ var notificationRulesCapability = {
17030
17072
  auth: "admin"
17031
17073
  }),
17032
17074
  /**
17075
+ * WHAT THE RULES ACTUALLY ASK the stream broker to retain, per camera.
17076
+ *
17077
+ * The notification centre owns the rules, so it is the only thing that can
17078
+ * say how much history a camera's notifications will ever reach back for.
17079
+ * Before this the broker guessed — a flat minute on every camera — and
17080
+ * learned the PROFILE reactively, after the first clip had already been cut
17081
+ * from the wrong one. This is the same ask stated once, in front.
17082
+ *
17083
+ * A QUERY, pulled by each node's broker, not a push: brokers are per node
17084
+ * (a camera's ring lives wherever its stream is ingested) while this cap is
17085
+ * a singleton, so a push would have to enumerate the cluster. The pull also
17086
+ * makes the failure mode right — a broker that cannot reach the centre
17087
+ * keeps the plan it has, and a plan that has never arrived is UNKNOWN
17088
+ * rather than empty (D49: a read that fails changes nothing).
17089
+ */
17090
+ getClipRetentionAsks: method(zod.z.object({}), NcClipRetentionPlanSchema, { auth: "admin" }),
17091
+ /**
17033
17092
  * PERMANENT per-camera mute — the notifications half of the per-camera
17034
17093
  * function switch group ([D61](../../../../docs/decisions/adr-0067.md)).
17035
17094
  *
@@ -18520,13 +18579,26 @@ var TrackAudioLabelSchema = zod.z.object({
18520
18579
  * - `audio` — an audio event on the camera itself that was anomalous for
18521
18580
  * THAT camera, loud, and heard while nothing visual was happening (D62).
18522
18581
  *
18582
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
18583
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
18584
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
18585
+ * detector.
18586
+ *
18587
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
18588
+ * a persisted onboard track failed `safeParse` on read and came back with NO
18589
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
18590
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
18591
+ * could not name.
18592
+ *
18523
18593
  * The spatial subsystems (tracker association, occupancy count, re-id /
18524
18594
  * embedding, resurrection) MUST skip every synthetic source. Test for that
18525
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
18526
- * check silently readmits every source added after it was written.
18595
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
18596
+ * `!== 'sensor'` check silently readmits every source added after it was
18597
+ * written.
18527
18598
  */
18528
18599
  var TrackSourceSchema = zod.z.enum([
18529
18600
  "pipeline",
18601
+ "onboard",
18530
18602
  "sensor",
18531
18603
  "audio"
18532
18604
  ]);
@@ -21263,13 +21335,39 @@ var MotionSourceEnum = zod.z.enum([
21263
21335
  */
21264
21336
  var MotionSourcesSchema = zod.z.array(MotionSourceEnum);
21265
21337
  /**
21266
- * Which root detectors a camera runs. Deliberately the SAME vocabulary
21267
- * post-analysis already tags every detection with (`DetectionSource`) rather
21268
- * than a second spelling of the same two ideas — the value an operator picks
21269
- * here is the value that comes back on the track, the overlay and the debug
21270
- * row.
21338
+ * Which root detector a camera runs — EXACTLY ONE.
21339
+ *
21340
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21341
+ * with (`DetectionSource`) rather than a second spelling of the same two
21342
+ * ideas: the value an operator picks here is the value that comes back on the
21343
+ * track, the overlay and the debug row.
21344
+ *
21345
+ * ## Why one, and why it is still an array
21346
+ *
21347
+ * Two roots on one camera is a capability nobody asked for and the operator has
21348
+ * since ruled out. It was never free: both planes feed the SAME per-device
21349
+ * stationary registry, both can promote the same parked object, and the two
21350
+ * detectors disagree about the same box by construction — which is the
21351
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21352
+ *
21353
+ * The ARRAY shape survives because the stored settings and the attach payload
21354
+ * already speak it on every camera of every fleet, and a cap input that
21355
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21356
+ * in the one direction that costs an operator their cameras. So the wire stays
21357
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21358
+ * element rather than refused, and `max(1)` is what every consumer can then
21359
+ * rely on.
21360
+ *
21361
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21362
+ * a camera an operator left with no root detector — and `planDetectionSources`
21363
+ * has always said so: inventing a root for them is how a default acts without
21364
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21365
+ * suite caught it.
21366
+ *
21367
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21368
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21271
21369
  */
21272
- var DetectionSourcesSchema = zod.z.array(DetectionSourceSchema);
21370
+ var DetectionSourcesSchema = zod.z.preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, zod.z.array(DetectionSourceSchema).max(1));
21273
21371
  /**
21274
21372
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21275
21373
  * so cap-side consumers (the orchestrator forward, the runner addon's
@@ -40447,6 +40545,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
40447
40545
  addonId: null,
40448
40546
  access: "view"
40449
40547
  },
40548
+ "notificationRules.getClipRetentionAsks": {
40549
+ capName: "notification-rules",
40550
+ capScope: "system",
40551
+ addonId: null,
40552
+ access: "view"
40553
+ },
40450
40554
  "notificationRules.getConditionCatalog": {
40451
40555
  capName: "notification-rules",
40452
40556
  capScope: "system",
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-B3ELrqnI.js");
3
+ const require_dist = require("./dist-C6JIhqS6.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -27,7 +27,7 @@ require("./builtins/system-config/index.js");
27
27
  const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
28
28
  const require_event_loop_stall_monitor = require("./event-loop-stall-monitor-CAsHdMwP.js");
29
29
  const require_tls$1 = require("./tls-BFs4PzoW.js");
30
- const require_manifest_system_deps = require("./manifest-system-deps-CrSP0Kkp.js");
30
+ const require_manifest_system_deps = require("./manifest-system-deps-CzyjTMcO.js");
31
31
  const require_resource_monitor = require("./resource-monitor-LVE1BoLs.js");
32
32
  const require_custom_action_registry = require("./custom-action-registry-acBY3mHA.js");
33
33
  let zod = require("zod");
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
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 { A as addonSettingsCapability, Ct as objectInputDeclaresAddonId, Et as procedureAuthKey, Nt as scopesAllowAddon, Pt as scopesAllowDeviceCap, Xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Yt as DATAPLANE_SECRET_HEADER$1, Zt as DEVICE_STATUS_METHOD, _ as RUNTIME_DEFAULTS, _n as resolveCapMount, _t as looseSchema, an as asNumber, at as isCollectionArrayMethod, bn as EventCategory$1, cn as emitDownForOwnedCaps, dt as kebabToCamel, et as extractNestedAddonId, gn as readinessKey, gt as logLevelAtMost, h as METHOD_ACCESS_MAP, hn as parseJsonUnknown, in as asJsonObject$1, it as isArrayOutputSchema, jt as resolveMethodAuth, mn as parseJsonObject, nn as ReadinessTimeoutError, on as asString$1, p as LOG_LEVEL_RANK, pt as lifecycleJobSchema, qt as errMsg$1, sn as createEvent, st as isObjectInput, t as ALL_CAPABILITY_DEFINITIONS, tn as ReadinessRegistry, un as expandCapMethods, ut as isVoidInput, vn as scopeKey } from "./dist-DGLUnOm4.mjs";
2
+ import { A as addonSettingsCapability, Ct as objectInputDeclaresAddonId, Et as procedureAuthKey, Nt as scopesAllowAddon, Pt as scopesAllowDeviceCap, Xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Yt as DATAPLANE_SECRET_HEADER$1, Zt as DEVICE_STATUS_METHOD, _ as RUNTIME_DEFAULTS, _n as resolveCapMount, _t as looseSchema, an as asNumber, at as isCollectionArrayMethod, bn as EventCategory$1, cn as emitDownForOwnedCaps, dt as kebabToCamel, et as extractNestedAddonId, gn as readinessKey, gt as logLevelAtMost, h as METHOD_ACCESS_MAP, hn as parseJsonUnknown, in as asJsonObject$1, it as isArrayOutputSchema, jt as resolveMethodAuth, mn as parseJsonObject, nn as ReadinessTimeoutError, on as asString$1, p as LOG_LEVEL_RANK, pt as lifecycleJobSchema, qt as errMsg$1, sn as createEvent, st as isObjectInput, t as ALL_CAPABILITY_DEFINITIONS, tn as ReadinessRegistry, un as expandCapMethods, ut as isVoidInput, vn as scopeKey } from "./dist-BjSYm1hH.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
@@ -26,7 +26,7 @@ import "./builtins/system-config/index.mjs";
26
26
  import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
27
27
  import { _ as fetchJson, a as createFileDataPlaneHandler, c as parseRangeHeader, d as ModelDownloadService, f as collectModelFiles, g as ensureModel, h as downloadModel, i as startEventLoopStallMonitor, l as parseTokenizedUrl, m as downloadFile, n as STALL_TICK_MS, o as contentTypeFor, p as deleteModelFromDisk, r as attributeStall, s as createAuthenticatedFileServer, t as STALL_REPORT_MS, u as resolveFilePath, v as getModelFilePath, y as isModelDownloaded } from "./event-loop-stall-monitor-GPrVvx14.mjs";
28
28
  import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-2xbgg78V.mjs";
29
- import { $ as buildUdsNativeCapProxy, $t as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, A as createUdsLoggerWithControl, An as recordSocketFrame, At as HUB_MAIN_HEAP_WATCH_LABEL, B as readMoleculerFanoutMode, Bn as diffCapActionCensus, Bt as createLoopDelayMeter, C as ipcParentLink, Cn as createSocketPlaneMeter, Ct as runNpm, D as createUdsEventBus, Dn as readSocketQueues, Dt as HEAP_RECLAIM_TRIGGER_MB, E as createUdsEventBridge, En as formatSocketPlane, Et as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, F as CapRouteError, Fn as CAP_ACTION_UNLABELLED, Ft as RUNNER_HEAP_WATCH_INTERVAL_MS, G as createLocalTransport, Gn as formatEventPlane, Gt as parseRssBudgetMb, H as PARENT_ROUTED_PROVIDER_ADDON_ID, Hn as createEventPlaneMeter, Ht as describeRss, I as classifyCapRoute, In as EMPTY_CAP_ACTION_READING, It as RUNNER_RSS_BUDGET_ENV, J as SocketChannel, Jt as startHeapWatch, K as UdsLocalTransportClient, Kt as reclaimIntervalMs, L as LocalChildClient, Ln as MAX_CAP_ACTIONS_PER_PEER, Lt as ZERO_LOOP_DELAY, M as AGENT_CAP_FWD_SERVICE, Mn as socketDirectionBytes, Mt as HUB_RSS_BUDGET_ENV, N as CapRouteResolver, Nn as socketDirectionMessages, Nt as RSS_BUDGET_REANNOUNCE_MIN_MS, O as udsChildLogToWorkerEntry, On as EMPTY_SOCKET_DIRECTION, Ot as HEAP_WATCH_INTERVAL_MS, P as callWithServiceDiscovery, Pn as CAP_ACTION_OVERFLOW_LABEL, Pt as RSS_BUDGET_RELEASE_RATIO, Q as buildNativeCapProxy, Qt as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, R as LocalChildRegistry, Rn as capActionLabel, Rt as buildHeapSample, S as buildLinkChain, Sn as SOCKET_QUEUE_TOP_N, St as resolveNpmInvocation, T as createParentUnownedCallHandler, Tn as diffSocketPlane, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, U as __resetCapUsageRegistryForTests, Un as createEventPlaneReader, Ut as hubMainRssBudget, V as CapUsageRegistry, Vn as EVENT_PLANE_TOP_N, Vt as createV8Reclaimer, W as getCapUsageRegistry, Wn as diffEventPlane, Wt as nextRssBudgetState, X as FrameDecoder, Xt as strandedMb, Y as localEndpointPath, Yt as startRunnerHeapWatch, Z as encodeFrame, Zt as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, _ as AddonDepsManager, _n as MESH_QUEUE_TOP_N, _t as CapabilityUnavailableError, a as createUdsAddonContext, an as DECISIVE_HEAP_SPACES, at as createAddonService, b as brokerCallForCap, bn as EMPTY_SOCKET_PLANE_BASELINE, bt as resolveAddonClass, c as getOrInitReadinessRegistryForClient, cn as formatHeapSpaces, ct as capActionName, d as clusterEventTopic, dn as selectReportedSpaces, dt as capServiceName, en as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, et as createBrokerDeviceManagerApi, f as getBrokerEventBus, fn as OUTBOUND_QUEUE_COOLDOWN_MS, ft as parseCapAction, g as subscribePassthrough, gn as formatOutboundQueueVerdict, gt as CapabilityHandle, h as setNodeEventInterest, hn as createOutboundQueueGate, ht as DeviceRegistry, i as createAddonContext, in as isArrayBufferCensusReport, j as AGENT_CAP_FWD_ACTION, jn as sampleSocketDirection, jt as HUB_MAIN_RSS_BUDGET_MB, k as createUdsLogger, kn as createSocketDirectionCounters, kt as HEAP_WATCH_WARN_RATIO, l as setHubConnected, ln as heapSpaceField, lt as capActionSuffix, m as registerEventBusService, mn as OUTBOUND_QUEUE_THRESHOLD_BYTES, mt as serializeTypedArrays, n as installManifestPythonDeps, nn as createInspectorArrayBufferCensus, o as getWorkerDeviceRegistry, on as HEAP_SPACE_REPORT_MIN_MB, ot as validateProviderRegistrations, p as getMoleculerEventStats, pn as OUTBOUND_QUEUE_SUSTAIN_MS, pt as deserializeTypedArrays, q as UdsLocalTransportServer, qt as shouldReclaim, r as adaptBrokerToCluster, rn as formatArrayBufferCensus, rt as mountNativeCapService, s as getOrInitReadinessRegistry, sn as emitHeapDiagnosticReport, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestSystemDeps, tn as createArrayBufferCensusGate, u as EVENT_TOPIC_PREFIX, un as readHeapSpaces, ut as capBareAction, v as createKernelHwAccel, vn as createMeshQueueReader, vt as copyBundledNativeModules, w as localProviderLink, wn as createSocketPlaneReader, wt as createAddonDataPlaneFacility, x as brokerTransportLink, xn as SOCKET_PLANE_TOP_N, xt as buildNpmChildEnv, y as resolveHwAccel, yn as formatMeshQueue, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX, zn as createCapActionCensus, zt as createDeferredHeapWatchSink } from "./manifest-system-deps-BEj9Dpu1.mjs";
29
+ import { $ as buildUdsNativeCapProxy, $t as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, A as createUdsLoggerWithControl, An as recordSocketFrame, At as HUB_MAIN_HEAP_WATCH_LABEL, B as readMoleculerFanoutMode, Bn as diffCapActionCensus, Bt as createLoopDelayMeter, C as ipcParentLink, Cn as createSocketPlaneMeter, Ct as runNpm, D as createUdsEventBus, Dn as readSocketQueues, Dt as HEAP_RECLAIM_TRIGGER_MB, E as createUdsEventBridge, En as formatSocketPlane, Et as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, F as CapRouteError, Fn as CAP_ACTION_UNLABELLED, Ft as RUNNER_HEAP_WATCH_INTERVAL_MS, G as createLocalTransport, Gn as formatEventPlane, Gt as parseRssBudgetMb, H as PARENT_ROUTED_PROVIDER_ADDON_ID, Hn as createEventPlaneMeter, Ht as describeRss, I as classifyCapRoute, In as EMPTY_CAP_ACTION_READING, It as RUNNER_RSS_BUDGET_ENV, J as SocketChannel, Jt as startHeapWatch, K as UdsLocalTransportClient, Kt as reclaimIntervalMs, L as LocalChildClient, Ln as MAX_CAP_ACTIONS_PER_PEER, Lt as ZERO_LOOP_DELAY, M as AGENT_CAP_FWD_SERVICE, Mn as socketDirectionBytes, Mt as HUB_RSS_BUDGET_ENV, N as CapRouteResolver, Nn as socketDirectionMessages, Nt as RSS_BUDGET_REANNOUNCE_MIN_MS, O as udsChildLogToWorkerEntry, On as EMPTY_SOCKET_DIRECTION, Ot as HEAP_WATCH_INTERVAL_MS, P as callWithServiceDiscovery, Pn as CAP_ACTION_OVERFLOW_LABEL, Pt as RSS_BUDGET_RELEASE_RATIO, Q as buildNativeCapProxy, Qt as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, R as LocalChildRegistry, Rn as capActionLabel, Rt as buildHeapSample, S as buildLinkChain, Sn as SOCKET_QUEUE_TOP_N, St as resolveNpmInvocation, T as createParentUnownedCallHandler, Tn as diffSocketPlane, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, U as __resetCapUsageRegistryForTests, Un as createEventPlaneReader, Ut as hubMainRssBudget, V as CapUsageRegistry, Vn as EVENT_PLANE_TOP_N, Vt as createV8Reclaimer, W as getCapUsageRegistry, Wn as diffEventPlane, Wt as nextRssBudgetState, X as FrameDecoder, Xt as strandedMb, Y as localEndpointPath, Yt as startRunnerHeapWatch, Z as encodeFrame, Zt as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, _ as AddonDepsManager, _n as MESH_QUEUE_TOP_N, _t as CapabilityUnavailableError, a as createUdsAddonContext, an as DECISIVE_HEAP_SPACES, at as createAddonService, b as brokerCallForCap, bn as EMPTY_SOCKET_PLANE_BASELINE, bt as resolveAddonClass, c as getOrInitReadinessRegistryForClient, cn as formatHeapSpaces, ct as capActionName, d as clusterEventTopic, dn as selectReportedSpaces, dt as capServiceName, en as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, et as createBrokerDeviceManagerApi, f as getBrokerEventBus, fn as OUTBOUND_QUEUE_COOLDOWN_MS, ft as parseCapAction, g as subscribePassthrough, gn as formatOutboundQueueVerdict, gt as CapabilityHandle, h as setNodeEventInterest, hn as createOutboundQueueGate, ht as DeviceRegistry, i as createAddonContext, in as isArrayBufferCensusReport, j as AGENT_CAP_FWD_ACTION, jn as sampleSocketDirection, jt as HUB_MAIN_RSS_BUDGET_MB, k as createUdsLogger, kn as createSocketDirectionCounters, kt as HEAP_WATCH_WARN_RATIO, l as setHubConnected, ln as heapSpaceField, lt as capActionSuffix, m as registerEventBusService, mn as OUTBOUND_QUEUE_THRESHOLD_BYTES, mt as serializeTypedArrays, n as installManifestPythonDeps, nn as createInspectorArrayBufferCensus, o as getWorkerDeviceRegistry, on as HEAP_SPACE_REPORT_MIN_MB, ot as validateProviderRegistrations, p as getMoleculerEventStats, pn as OUTBOUND_QUEUE_SUSTAIN_MS, pt as deserializeTypedArrays, q as UdsLocalTransportServer, qt as shouldReclaim, r as adaptBrokerToCluster, rn as formatArrayBufferCensus, rt as mountNativeCapService, s as getOrInitReadinessRegistry, sn as emitHeapDiagnosticReport, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestSystemDeps, tn as createArrayBufferCensusGate, u as EVENT_TOPIC_PREFIX, un as readHeapSpaces, ut as capBareAction, v as createKernelHwAccel, vn as createMeshQueueReader, vt as copyBundledNativeModules, w as localProviderLink, wn as createSocketPlaneReader, wt as createAddonDataPlaneFacility, x as brokerTransportLink, xn as SOCKET_PLANE_TOP_N, xt as buildNpmChildEnv, y as resolveHwAccel, yn as formatMeshQueue, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX, zn as createCapActionCensus, zt as createDeferredHeapWatchSink } from "./manifest-system-deps-CfR7Z90J.mjs";
30
30
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Vp0bPV3U.mjs";
31
31
  import { t as CustomActionRegistry } from "./custom-action-registry-B4Cf4O_X.mjs";
32
32
  import { z } from "zod";
@@ -0,0 +1,113 @@
1
+ import { SystemEvent } from '@camstack/types';
2
+ /** One declared mirror category and the reason it may be collapsed. */
3
+ export interface MirrorCategoryDeclaration {
4
+ /** Exact event category. Not a glob — a mirror is declared one by one. */
5
+ readonly category: string;
6
+ /** Why the penultimate value of this category carries no information. */
7
+ readonly reason: string;
8
+ }
9
+ /**
10
+ * The categories whose per-device history is ONE slot: the latest value.
11
+ *
12
+ * Declared here, with a reason each, on purpose — the alternative is `if`s
13
+ * scattered over the delivery path, and nobody can then answer "what does this
14
+ * system collapse, and who decided?".
15
+ *
16
+ * Adding an entry DELETES history for that category. Add one only when the
17
+ * category is genuinely a state mirror (a snapshot of a current value), never
18
+ * because it is merely frequent — a frequent EDGE (a motion start, a crash)
19
+ * belongs in `RING_BUFFER_DENY_PATTERNS` or nowhere, not here.
20
+ */
21
+ export declare const MIRROR_CATEGORIES: readonly MirrorCategoryDeclaration[];
22
+ /** The declared reason this category is a mirror, or `undefined` if unclassified. */
23
+ export declare function mirrorReason(category: string): string | undefined;
24
+ /**
25
+ * Is this category declared a mirror? An unclassified category is NOT a mirror
26
+ * — the safe default is to keep its history.
27
+ */
28
+ export declare function isMirrorCategory(category: string): boolean;
29
+ /**
30
+ * Non-mirror events retained per device. ~1.1 events/s fleet-wide after
31
+ * coalescing ⇒ ~3 000/day/camera ⇒ 2 000 ≈ 16 h.
32
+ */
33
+ export declare const MAX_DEVICE_RING_EVENTS = 2000;
34
+ /**
35
+ * Cap on the retained `data` payload, in UTF-8 bytes. The COUNT is not what
36
+ * costs memory — 32 devices × 2 000 events × 817 B (measured average) ≈ 52 MB.
37
+ * Capping the payload brings the worst case to ≈ 26 MB and the realistic case
38
+ * far below it, since most events are already under the cap.
39
+ */
40
+ export declare const MAX_RETAINED_DATA_BYTES = 256;
41
+ /** Bytes of the original JSON kept as a human-readable preview of a cut payload. */
42
+ export declare const TRUNCATED_PREVIEW_BYTES = 160;
43
+ /**
44
+ * Devices tracked at once. Far above the fleet (32) — it exists so a bad
45
+ * deviceId stream cannot grow the map without bound. Eviction is
46
+ * least-recently-written.
47
+ */
48
+ export declare const MAX_TRACKED_DEVICES = 512;
49
+ /** Marker key present on — and only on — a payload this module cut. */
50
+ export declare const TRUNCATED_DATA_KEY = "__truncated";
51
+ /**
52
+ * What replaces a payload above {@link MAX_RETAINED_DATA_BYTES}. A consumer
53
+ * MUST be able to tell a short payload from a cut one, so the replacement says
54
+ * so explicitly rather than silently shipping a prefix.
55
+ */
56
+ export interface TruncatedEventData extends Record<string, unknown> {
57
+ readonly __truncated: true;
58
+ /** Size of the original JSON payload, or `null` when it could not be measured. */
59
+ readonly __originalBytes: number | null;
60
+ /** First {@link TRUNCATED_PREVIEW_BYTES} characters of the original JSON. */
61
+ readonly __preview: string;
62
+ }
63
+ /** Is this the marked replacement of a cut payload? */
64
+ export declare function isTruncatedEventData(data: Record<string, unknown>): data is TruncatedEventData;
65
+ /**
66
+ * The event as it should be RETAINED: unchanged when its payload is within the
67
+ * cap, otherwise a NEW event whose `data` is the marked replacement. The
68
+ * argument is never mutated — the same object is still live on the fan-out path
69
+ * and in the global ring.
70
+ */
71
+ export declare function capEventData(event: SystemEvent): SystemEvent;
72
+ /**
73
+ * The device this event is about, or `undefined` when it is about no device.
74
+ *
75
+ * Same rule as `matchesEventFilter` — `source.deviceId`, or `source.id` when
76
+ * the source IS a device — so a read filtered by `deviceId` and a read served
77
+ * from that device's ring agree on which events belong to it. A non-numeric
78
+ * device source id yields `undefined` rather than `NaN`: an unattributable
79
+ * event must never become a bucket of its own.
80
+ */
81
+ export declare function eventDeviceId(event: SystemEvent): number | undefined;
82
+ interface DeviceRing {
83
+ /** Non-mirror events, oldest first, bounded by {@link MAX_DEVICE_RING_EVENTS}. */
84
+ readonly history: SystemEvent[];
85
+ /** Latest event per declared-mirror category. */
86
+ readonly mirrors: Map<string, SystemEvent>;
87
+ }
88
+ /**
89
+ * Every device's ring. Insertion order is least-recently-written first, which
90
+ * is what {@link MAX_TRACKED_DEVICES} evicts by.
91
+ */
92
+ export interface DeviceEventRings {
93
+ readonly byDevice: Map<number, DeviceRing>;
94
+ }
95
+ export declare function createDeviceEventRings(): DeviceEventRings;
96
+ /**
97
+ * Retain `event` in its device's ring, with its payload capped.
98
+ *
99
+ * Returns the device it was filed under, or `undefined` when the event names no
100
+ * device — the caller has already retained it in the global ring and nothing
101
+ * more happens here. No device is fabricated.
102
+ */
103
+ export declare function recordDeviceEvent(rings: DeviceEventRings, event: SystemEvent): number | undefined;
104
+ /**
105
+ * Everything retained for one device, oldest first — history merged with the
106
+ * current value of each mirror, ordered by timestamp so the caller's `-limit`
107
+ * slice means the same thing it means on the global ring.
108
+ *
109
+ * An unknown device yields `[]`: unknown is the truth about a camera that has
110
+ * emitted nothing since this process started.
111
+ */
112
+ export declare function getDeviceRecent(rings: DeviceEventRings, deviceId: number): readonly SystemEvent[];
113
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { EventFilter, SystemEvent } from '@camstack/types';
2
+ import { DeviceEventRings } from './device-event-ring.js';
2
3
  export interface SubscriberEntry {
3
4
  /** Original filter — kept whole so non-category dimensions are honoured per-event. */
4
5
  readonly filter: EventFilter | string | undefined;
@@ -29,6 +30,23 @@ export interface SharedBusState {
29
30
  * Cleared on server restart (intentional — events are volatile by design).
30
31
  */
31
32
  readonly recent: SystemEvent[];
33
+ /**
34
+ * Per-device rings BESIDE `recent[]` — not a replacement for it.
35
+ *
36
+ * `recent[]` is one global, count-bounded ring: a busy fleet evicts a quiet
37
+ * camera's events out of it long before an operator opens that camera's
38
+ * panel, and filtering a global ring by device cannot bring them back. A
39
+ * per-device ask is therefore answered from here instead
40
+ * (`getBrokerEventBus().getRecent({ deviceId })`), while an ask that names no
41
+ * device is still answered from `recent[]`.
42
+ *
43
+ * Populated under exactly the same conditions as `recent[]` (retaining bus,
44
+ * category not on the high-frequency deny-list), with mirror categories
45
+ * coalesced and payloads capped — see `device-event-ring.ts`. An event that
46
+ * names no device is retained in `recent[]` ONLY; no device is invented for
47
+ * it.
48
+ */
49
+ readonly deviceRings: DeviceEventRings;
32
50
  /**
33
51
  * Whether this bus retains events in `recent[]` to back the
34
52
  * `getRecent`/audit capability.
@@ -6971,6 +6971,137 @@ function getCapUsageRegistry() {
6971
6971
  function __resetCapUsageRegistryForTests() {
6972
6972
  singleton = null;
6973
6973
  }
6974
+ var MIRROR_REASON_BY_CATEGORY = new Map([
6975
+ {
6976
+ category: "device.state-changed",
6977
+ reason: "Device runtime-state mirror, 69.1 % of fleet traffic. Each event is a snapshot of the slice AFTER the change; the superseded snapshot describes a state the device is no longer in, and every consumer (SystemMirror, DeviceStateHandle) reads the latest one only."
6978
+ },
6979
+ {
6980
+ category: "camera-streams.onCamStreamsChanged",
6981
+ reason: "Full stream-list snapshot per camera, 20.1 % of fleet traffic. The payload is the complete current list, not a delta, so an older list is strictly less true than the newer one."
6982
+ },
6983
+ {
6984
+ category: "system.ready-state",
6985
+ reason: "Readiness is a LEVEL, not an edge: the event reports whether a capability is ready right now. Declared for the case where it carries a deviceId; when it does not it stays in the global ring like any other unattributed event."
6986
+ }
6987
+ ].map((m) => [m.category, m.reason]));
6988
+ /**
6989
+ * Is this category declared a mirror? An unclassified category is NOT a mirror
6990
+ * — the safe default is to keep its history.
6991
+ */
6992
+ function isMirrorCategory(category) {
6993
+ return MIRROR_REASON_BY_CATEGORY.has(category);
6994
+ }
6995
+ /**
6996
+ * Non-mirror events retained per device. ~1.1 events/s fleet-wide after
6997
+ * coalescing ⇒ ~3 000/day/camera ⇒ 2 000 ≈ 16 h.
6998
+ */
6999
+ var MAX_DEVICE_RING_EVENTS = 2e3;
7000
+ function serializePayload(data) {
7001
+ try {
7002
+ const json = JSON.stringify(data);
7003
+ if (typeof json !== "string") return null;
7004
+ return {
7005
+ json,
7006
+ bytes: Buffer.byteLength(json, "utf8")
7007
+ };
7008
+ } catch {
7009
+ return null;
7010
+ }
7011
+ }
7012
+ /**
7013
+ * The event as it should be RETAINED: unchanged when its payload is within the
7014
+ * cap, otherwise a NEW event whose `data` is the marked replacement. The
7015
+ * argument is never mutated — the same object is still live on the fan-out path
7016
+ * and in the global ring.
7017
+ */
7018
+ function capEventData(event) {
7019
+ const serialized = serializePayload(event.data);
7020
+ if (serialized !== null && serialized.bytes <= 256) return event;
7021
+ const truncated = {
7022
+ __truncated: true,
7023
+ __originalBytes: serialized?.bytes ?? null,
7024
+ __preview: serialized === null ? "" : serialized.json.slice(0, 160)
7025
+ };
7026
+ return {
7027
+ ...event,
7028
+ data: truncated
7029
+ };
7030
+ }
7031
+ /**
7032
+ * The device this event is about, or `undefined` when it is about no device.
7033
+ *
7034
+ * Same rule as `matchesEventFilter` — `source.deviceId`, or `source.id` when
7035
+ * the source IS a device — so a read filtered by `deviceId` and a read served
7036
+ * from that device's ring agree on which events belong to it. A non-numeric
7037
+ * device source id yields `undefined` rather than `NaN`: an unattributable
7038
+ * event must never become a bucket of its own.
7039
+ */
7040
+ function eventDeviceId(event) {
7041
+ const explicit = event.source.deviceId;
7042
+ if (explicit !== void 0) return Number.isFinite(explicit) ? explicit : void 0;
7043
+ if (event.source.type !== "device") return void 0;
7044
+ const asNumber = Number(event.source.id);
7045
+ return Number.isFinite(asNumber) ? asNumber : void 0;
7046
+ }
7047
+ function createDeviceEventRings() {
7048
+ return { byDevice: /* @__PURE__ */ new Map() };
7049
+ }
7050
+ function ringFor(rings, deviceId) {
7051
+ const existing = rings.byDevice.get(deviceId);
7052
+ if (existing !== void 0) {
7053
+ rings.byDevice.delete(deviceId);
7054
+ rings.byDevice.set(deviceId, existing);
7055
+ return existing;
7056
+ }
7057
+ const created = {
7058
+ history: [],
7059
+ mirrors: /* @__PURE__ */ new Map()
7060
+ };
7061
+ rings.byDevice.set(deviceId, created);
7062
+ while (rings.byDevice.size > 512) {
7063
+ const oldest = rings.byDevice.keys().next();
7064
+ if (oldest.done === true) break;
7065
+ rings.byDevice.delete(oldest.value);
7066
+ }
7067
+ return created;
7068
+ }
7069
+ /**
7070
+ * Retain `event` in its device's ring, with its payload capped.
7071
+ *
7072
+ * Returns the device it was filed under, or `undefined` when the event names no
7073
+ * device — the caller has already retained it in the global ring and nothing
7074
+ * more happens here. No device is fabricated.
7075
+ */
7076
+ function recordDeviceEvent(rings, event) {
7077
+ const deviceId = eventDeviceId(event);
7078
+ if (deviceId === void 0) return void 0;
7079
+ const ring = ringFor(rings, deviceId);
7080
+ const retained = capEventData(event);
7081
+ if (isMirrorCategory(event.category)) {
7082
+ ring.mirrors.set(event.category, retained);
7083
+ return deviceId;
7084
+ }
7085
+ ring.history.push(retained);
7086
+ if (ring.history.length > 2e3) ring.history.splice(0, ring.history.length - MAX_DEVICE_RING_EVENTS);
7087
+ return deviceId;
7088
+ }
7089
+ /**
7090
+ * Everything retained for one device, oldest first — history merged with the
7091
+ * current value of each mirror, ordered by timestamp so the caller's `-limit`
7092
+ * slice means the same thing it means on the global ring.
7093
+ *
7094
+ * An unknown device yields `[]`: unknown is the truth about a camera that has
7095
+ * emitted nothing since this process started.
7096
+ */
7097
+ function getDeviceRecent(rings, deviceId) {
7098
+ const ring = rings.byDevice.get(deviceId);
7099
+ if (ring === void 0) return [];
7100
+ if (ring.mirrors.size === 0) return ring.history;
7101
+ const merged = [...ring.history, ...ring.mirrors.values()];
7102
+ merged.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
7103
+ return merged;
7104
+ }
6974
7105
  //#endregion
6975
7106
  //#region src/kernel/moleculer/event-bus-core.ts
6976
7107
  /**
@@ -6982,6 +7113,7 @@ function createSharedBusState(retainRecent = false) {
6982
7113
  return {
6983
7114
  handlers: /* @__PURE__ */ new Map(),
6984
7115
  recent: [],
7116
+ deviceRings: createDeviceEventRings(),
6985
7117
  retainRecent
6986
7118
  };
6987
7119
  }
@@ -7165,6 +7297,7 @@ function deliverShared(state, event) {
7165
7297
  if (state.retainRecent && !isHighFrequencyCategory(event.category)) {
7166
7298
  state.recent.push(event);
7167
7299
  if (state.recent.length > 1e3) state.recent.splice(0, state.recent.length - MAX_RECENT_EVENTS);
7300
+ recordDeviceEvent(state.deviceRings, event);
7168
7301
  }
7169
7302
  const delivered = /* @__PURE__ */ new Set();
7170
7303
  for (const [pattern, set] of state.handlers) {
@@ -10130,14 +10263,15 @@ function getBrokerEventBus(broker, options) {
10130
10263
  });
10131
10264
  },
10132
10265
  getRecent(filter, limit) {
10133
- const filtered = filter ? state.recent.filter((e) => {
10266
+ const source = filter?.deviceId !== void 0 ? getDeviceRecent(state.deviceRings, filter.deviceId) : state.recent;
10267
+ const filtered = filter ? source.filter((e) => {
10134
10268
  const cat = filter.category;
10135
10269
  if (cat) {
10136
10270
  const categories = typeof cat === "string" ? [cat] : Array.isArray(cat) ? cat : [];
10137
10271
  if (categories.length > 0 && !categories.some((c) => matchesPattern(String(c), e.category))) return false;
10138
10272
  }
10139
10273
  return matchesEventFilter(e, filter);
10140
- }) : state.recent;
10274
+ }) : source;
10141
10275
  return limit !== void 0 ? filtered.slice(-limit) : filtered;
10142
10276
  }
10143
10277
  };