@camstack/system 1.2.238 → 1.2.240

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-D5hpgLSL.mjs → dist-CDDG1zn3.mjs} +133 -1
  48. package/dist/{dist-CHclfj0J.js → dist-Dze0l7UM.js} +133 -1
  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-DRKmYztE.js → retired-settings-keys-1GWw9mup.js} +1 -1
  56. package/dist/{retired-settings-keys-bJE3qjAT.mjs → retired-settings-keys-Cbt4-K_M.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-BfJ2k1jm.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
  *
@@ -21645,6 +21704,18 @@ var RunnerLocalMetricsSchema = zod.z.object({
21645
21704
  * runner has zero knowledge of other agents, no global state, and never
21646
21705
  * makes assignment decisions itself.
21647
21706
  */
21707
+ /**
21708
+ * Why a runner declined to look at a camera out of band. Named rather than a
21709
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
21710
+ * already mid-session and one already bursting are four different facts, and a
21711
+ * caller told only "no" cannot tell a protection from a bug.
21712
+ */
21713
+ var OccupancyBurstRefusalSchema = zod.z.enum([
21714
+ "not-attached",
21715
+ "battery-asleep",
21716
+ "not-watching",
21717
+ "already-bursting"
21718
+ ]);
21648
21719
  var pipelineRunnerCapability = {
21649
21720
  name: "pipeline-runner",
21650
21721
  scope: "system",
@@ -21681,6 +21752,50 @@ var pipelineRunnerCapability = {
21681
21752
  /** List the deviceIds currently attached to this runner. */
21682
21753
  getLocalCameras: method(zod.z.void(), zod.z.array(zod.z.number()).readonly()),
21683
21754
  /**
21755
+ * Look at a camera NOW, out of band, because something just happened that
21756
+ * only a fresh look can settle.
21757
+ *
21758
+ * The periodic recheck (`occupancyRecheckSec`, 300 s) exists to watch a
21759
+ * still scene. It is the wrong instrument for a QUESTION that has just been
21760
+ * asked: on device 617 a parked car's departure was observed, a departure
21761
+ * strike was armed — and then the motion session ended, the strike decayed
21762
+ * after its 30 s TTL, and the next periodic look arrived up to five minutes
21763
+ * later with nothing left to confirm. Every one of those departures was
21764
+ * settled by the entry ageing out 6 to 10 minutes of observed time later,
21765
+ * and the notification's clip showed an empty space with nothing leaving
21766
+ * it.
21767
+ *
21768
+ * Two numbers make the ask specific, and a caller that ignores them wastes
21769
+ * the burst: a confirmation must land at least
21770
+ * `DEPARTURE_CONFIRM_MIN_GAP_MS` (3 s) after the read that armed the
21771
+ * strike — before that it is the same optical event and is refused — and
21772
+ * `DEPARTURE_ABSENCE_MIN_READS` (5) consecutive unassociated frames are
21773
+ * needed, while `occupancyRecheckFrames` defaults to FOUR. A burst of four
21774
+ * can never confirm an absence; that is why two periodic looks were always
21775
+ * required.
21776
+ *
21777
+ * The runner answers whether it actually started one. A refusal is NAMED,
21778
+ * never a silent `false`: a camera that is asleep on battery (D173 — this
21779
+ * must not become a self-initiated wake), detached, mid-session or already
21780
+ * bursting are four different facts, and a caller told only "no" cannot
21781
+ * tell a protection from a bug.
21782
+ */
21783
+ requestOccupancyBurst: method(zod.z.object({
21784
+ deviceId: zod.z.number(),
21785
+ /** How many frames to collect. The caller sizes this against what its
21786
+ * own confirmation needs; the runner clamps it to the field's range. */
21787
+ frames: zod.z.number().int().positive().optional(),
21788
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
21789
+ * defend — the periodic one at least has a timer to point at. */
21790
+ reason: zod.z.string()
21791
+ }), zod.z.object({
21792
+ started: zod.z.boolean(),
21793
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
21794
+ }), {
21795
+ auth: "admin",
21796
+ kind: "mutation"
21797
+ }),
21798
+ /**
21684
21799
  * Best-effort NATIVE-resolution crop of a retention-ring frame. Given the
21685
21800
  * `FrameHandle` that rode an inference-result event and a normalized `bbox`,
21686
21801
  * the runner asks the decode worker still holding that frame's NATIVE
@@ -40391,6 +40506,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
40391
40506
  addonId: null,
40392
40507
  access: "view"
40393
40508
  },
40509
+ "notificationRules.getClipRetentionAsks": {
40510
+ capName: "notification-rules",
40511
+ capScope: "system",
40512
+ addonId: null,
40513
+ access: "view"
40514
+ },
40394
40515
  "notificationRules.getConditionCatalog": {
40395
40516
  capName: "notification-rules",
40396
40517
  capScope: "system",
@@ -41579,6 +41700,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
41579
41700
  addonId: null,
41580
41701
  access: "create"
41581
41702
  },
41703
+ "pipelineRunner.requestOccupancyBurst": {
41704
+ capName: "pipeline-runner",
41705
+ capScope: "system",
41706
+ addonId: null,
41707
+ access: "create"
41708
+ },
41582
41709
  "pipelineRunner.runDetailSubtree": {
41583
41710
  capName: "pipeline-runner",
41584
41711
  capScope: "system",
@@ -44918,6 +45045,11 @@ Object.freeze({
44918
45045
  form: "single",
44919
45046
  optional: false
44920
45047
  }],
45048
+ "pipelineRunner.requestOccupancyBurst": [{
45049
+ name: "deviceId",
45050
+ form: "single",
45051
+ optional: false
45052
+ }],
44921
45053
  "pipelineRunner.runDetailSubtree": [{
44922
45054
  name: "deviceId",
44923
45055
  form: "single",
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-CHclfj0J.js");
3
+ const require_dist = require("./dist-Dze0l7UM.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-D5hpgLSL.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-CDDG1zn3.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
  };