@camstack/system 1.2.58 → 1.2.60
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.
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/binding-mirror.d.ts +65 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +56 -11
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +248 -26
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +248 -26
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +2 -2
- package/dist/builtins/snapshot/index.mjs +2 -2
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +94 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +142 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +142 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-UrXyJ9Zm.mjs → dist-C5XpOjbI.mjs} +841 -38
- package/dist/{dist-Chl2MvMd.js → dist-Cis_iV-u.js} +852 -37
- package/dist/index.js +187 -39
- package/dist/index.mjs +187 -39
- package/dist/kernel/addon-installer.d.ts +55 -7
- package/dist/kernel/addon-manifest.d.ts +36 -16
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as isSameAddonId, Et as createEvent, ft as errMsg, k as doorbellCapability, pt as BaseAddon, x as bareAddonId, yt as DeviceType, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -216,6 +216,65 @@ var DoorbellTriggerEngine = class {
|
|
|
216
216
|
//#endregion
|
|
217
217
|
//#region src/builtins/doorbell/binding-mirror.ts
|
|
218
218
|
/**
|
|
219
|
+
* The virtual-doorbell wrapper's in-memory mirror of ONE durable fact per
|
|
220
|
+
* camera: is this wrapper still the active `doorbell` binding for it?
|
|
221
|
+
*
|
|
222
|
+
* WHY A MIRROR AND NOT A READ. A doorbell press is a one-shot event: there
|
|
223
|
+
* is no retry, no reconcile, nothing downstream that can notice it went
|
|
224
|
+
* missing. The wrapper used to re-derive the binding on the ring path via
|
|
225
|
+
* `deviceManager.getBindings` and treat ANY answer that omitted its own
|
|
226
|
+
* activation as "the operator unbound this". On 2026-08-04/05 camera 615
|
|
227
|
+
* rang zero times out of three real presses: the binding was persisted (a
|
|
228
|
+
* same-day backup taken before the presses proves it, and the same process
|
|
229
|
+
* later returned it correctly to a tRPC query), but the store answered
|
|
230
|
+
* transiently without it — `getBindings` reads the device-manager addon
|
|
231
|
+
* store, which is exactly the surface that can silently answer `{}` (D44,
|
|
232
|
+
* `addon-registry.service.ts`). Three presses, three drops, at `debug`.
|
|
233
|
+
*
|
|
234
|
+
* WHY CORROBORATION AND NOT A BETTER DISCRIMINATOR. There isn't one. A
|
|
235
|
+
* genuinely unbound pure-wrapper cap and a store answering from an empty
|
|
236
|
+
* snapshot produce the SAME `getBindings` response — no `doorbell` entry,
|
|
237
|
+
* with the camera's native entries (snapshot, motion, …) still present in
|
|
238
|
+
* both cases because those come from the capability registry, not the
|
|
239
|
+
* store. The response cannot be graded. What CAN be graded is repetition: a
|
|
240
|
+
* real unbind is permanent and every later read agrees, a store blip does
|
|
241
|
+
* not survive the next read. So a negative read ARMS the revocation and a
|
|
242
|
+
* second consecutive negative applies it.
|
|
243
|
+
*
|
|
244
|
+
* ERRING. `undefined` (never read) and `unbind-pending` both ring. Reaching
|
|
245
|
+
* the ring path at all means the camera has a configured trigger source,
|
|
246
|
+
* and a source can only be configured through this wrapper's own
|
|
247
|
+
* binding-gated settings surface — so a configured source is itself
|
|
248
|
+
* evidence the wrapper was bound. The cost of erring open is a spurious
|
|
249
|
+
* ring on a camera the operator unbound but left configured, for at most
|
|
250
|
+
* two reload cycles. The cost of erring closed is the press.
|
|
251
|
+
*
|
|
252
|
+
* ─────────────────────────────────────────────────────────────────────
|
|
253
|
+
* 2026-08-07 (D72). The corroboration above was DEFEATED, and the reason
|
|
254
|
+
* matters more than the fix. `refreshBindingMirror` matched the binding with
|
|
255
|
+
* `entry.providerAddonId === this.ctx.id`. The two addon-context factories do
|
|
256
|
+
* not spell `ctx.id` the same way: a forked addon gets `virtual-doorbell`, a
|
|
257
|
+
* hub-main BUILTIN gets `addon:virtual-doorbell`. Every entry on the wire
|
|
258
|
+
* carries the bare form. So for this builtin the comparison was false for
|
|
259
|
+
* every entry, forever — not a blip. Corroboration only defends against a
|
|
260
|
+
* TRANSIENT bad read; two systematically-wrong reads agree with each other,
|
|
261
|
+
* and the mirror latched `unbound` with no way back except an operator
|
|
262
|
+
* opening the settings page. A real press on camera 615 was dropped.
|
|
263
|
+
*
|
|
264
|
+
* Two things follow, and both are now load-bearing here:
|
|
265
|
+
*
|
|
266
|
+
* 1. The comparison goes through `isSameAddonId` ({@link entriesBindWrapper}),
|
|
267
|
+
* never a raw `===`.
|
|
268
|
+
* 2. The DERIVED view (`deviceManager.getBindings`) is no longer the sole
|
|
269
|
+
* input. Before a negative is believed — and again on the ring path
|
|
270
|
+
* before a press is destroyed — the DURABLE authority is consulted
|
|
271
|
+
* directly ({@link readDurableWrapperBinding} over the `device-manager`
|
|
272
|
+
* addon store's `deviceBindings` map). That read carries a discriminator
|
|
273
|
+
* the derived view genuinely cannot offer: a store answering from an
|
|
274
|
+
* empty snapshot (D44) has no `deviceBindings` key at all, while a
|
|
275
|
+
* genuine unbind leaves the key present and merely omits the row.
|
|
276
|
+
*/
|
|
277
|
+
/**
|
|
219
278
|
* Fold one SUCCESSFUL bindings read into the mirror. A read that failed must
|
|
220
279
|
* never reach this function — an unanswerable store carries no information
|
|
221
280
|
* and must leave the mirror exactly as it was.
|
|
@@ -232,6 +291,52 @@ function allowsRing(state) {
|
|
|
232
291
|
function hasConfirmedBinding(state) {
|
|
233
292
|
return state === "bound";
|
|
234
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Does this list of binding entries name `ownAddonId` as the ACTIVE WRAPPER
|
|
296
|
+
* of `capName`?
|
|
297
|
+
*
|
|
298
|
+
* `isSameAddonId` is the whole point: `ownAddonId` is an `AddonContext.id`,
|
|
299
|
+
* which is prefixed (`addon:…`) in hub-main and bare in a forked runner,
|
|
300
|
+
* while `providerAddonId` is always bare. A raw `===` here is the D72 bug.
|
|
301
|
+
*/
|
|
302
|
+
function entriesBindWrapper(entries, capName, ownAddonId) {
|
|
303
|
+
return entries.some((entry) => entry.capName === capName && entry.kind === "wrapped" && isSameAddonId(entry.providerAddonId, ownAddonId));
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Render binding entries for a log line. A systematic wrong answer and a
|
|
307
|
+
* one-off blip produce the same VERDICT; only the entries themselves tell
|
|
308
|
+
* them apart, so a negative read never gets to report just its conclusion.
|
|
309
|
+
*/
|
|
310
|
+
function summariseEntries(entries) {
|
|
311
|
+
return entries.map((entry) => `${entry.capName}:${entry.kind}:${entry.providerAddonId}`);
|
|
312
|
+
}
|
|
313
|
+
function isRecord$2(value) {
|
|
314
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Read the DURABLE authority — the `device-manager` addon store's
|
|
318
|
+
* `deviceBindings` map — for "is `ownAddonId` the active wrapper of
|
|
319
|
+
* `capName` on `deviceId`".
|
|
320
|
+
*
|
|
321
|
+
* This is the input the derived `getBindings` view is not: it is the row the
|
|
322
|
+
* operator's toggle actually writes, with no registry resolution, no
|
|
323
|
+
* `defaultActive` auto-binding and no cross-node cap folding in between. It
|
|
324
|
+
* also grades its own trustworthiness, which `getBindings` cannot: the
|
|
325
|
+
* absence of the `deviceBindings` KEY is the signature of a store answering
|
|
326
|
+
* from an empty snapshot, whereas a genuine unbind leaves the key present.
|
|
327
|
+
*/
|
|
328
|
+
function readDurableWrapperBinding(record, deviceId, capName, ownAddonId) {
|
|
329
|
+
if (!isRecord$2(record)) return "unreadable";
|
|
330
|
+
const deviceBindings = record["deviceBindings"];
|
|
331
|
+
if (!isRecord$2(deviceBindings)) return "unreadable";
|
|
332
|
+
const perDevice = deviceBindings[String(deviceId)];
|
|
333
|
+
if (!isRecord$2(perDevice)) return "unbound";
|
|
334
|
+
const activation = perDevice[capName];
|
|
335
|
+
if (!isRecord$2(activation)) return "unbound";
|
|
336
|
+
const wrapperAddonId = activation["wrapperAddonId"];
|
|
337
|
+
if (typeof wrapperAddonId !== "string") return "unbound";
|
|
338
|
+
return isSameAddonId(wrapperAddonId, ownAddonId) ? "bound" : "unbound";
|
|
339
|
+
}
|
|
235
340
|
//#endregion
|
|
236
341
|
//#region src/builtins/doorbell/doorbell-settings.ts
|
|
237
342
|
/**
|
|
@@ -325,6 +430,13 @@ function applyDoorbellSourcesPatch(current, patchValue) {
|
|
|
325
430
|
}
|
|
326
431
|
//#endregion
|
|
327
432
|
//#region src/builtins/doorbell/virtual-doorbell.addon.ts
|
|
433
|
+
/**
|
|
434
|
+
* The addon whose store IS the durable authority on wrapper activations.
|
|
435
|
+
* `deviceBindings[<deviceId>][<capName>].wrapperAddonId` is the row the
|
|
436
|
+
* operator's bindings toggle writes; `deviceManager.getBindings` is a VIEW
|
|
437
|
+
* derived from it plus the capability registry.
|
|
438
|
+
*/
|
|
439
|
+
var DEVICE_MANAGER_ADDON_ID = "device-manager";
|
|
328
440
|
/** Structural narrowing helper for untrusted bus payloads. */
|
|
329
441
|
function isRecord(value) {
|
|
330
442
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -464,19 +576,14 @@ var VirtualDoorbellAddon = class extends BaseAddon {
|
|
|
464
576
|
* byte-shaped like a native firmware ring so every downstream consumer
|
|
465
577
|
* (UI toast, notifier rules, exporters) is agnostic to the origin.
|
|
466
578
|
*
|
|
467
|
-
* The binding gate here is a pure in-memory read
|
|
468
|
-
*
|
|
469
|
-
*
|
|
579
|
+
* The binding gate here is a pure in-memory read on the path that RINGS:
|
|
580
|
+
* an open mirror performs NO I/O and cannot fail. The mirror can only
|
|
581
|
+
* DESTROY a press through the one branch below, and that branch is not
|
|
582
|
+
* allowed to be the last word — see `ringsDespiteUnboundMirror`.
|
|
470
583
|
*/
|
|
471
584
|
async fireDoorbell(cameraId, timestamp) {
|
|
472
585
|
const binding = this.bindingMirror.get(cameraId);
|
|
473
|
-
if (!allowsRing(binding))
|
|
474
|
-
this.ctx.logger.warn("virtual-doorbell: ring DROPPED — the wrapper is not bound to this camera, but a trigger source is still configured for it", {
|
|
475
|
-
tags: { deviceId: cameraId },
|
|
476
|
-
meta: { bindingState: binding }
|
|
477
|
-
});
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
586
|
+
if (!allowsRing(binding) && !await this.ringsDespiteUnboundMirror(cameraId, binding)) return;
|
|
480
587
|
if (!hasConfirmedBinding(binding)) this.ctx.logger.warn("virtual-doorbell: ringing on an unconfirmed binding — the bindings store has not corroborated an unbind, and a one-shot press is never dropped on an unproven read", {
|
|
481
588
|
tags: { deviceId: cameraId },
|
|
482
589
|
meta: { bindingState: binding ?? "never-read" }
|
|
@@ -515,20 +622,28 @@ var VirtualDoorbellAddon = class extends BaseAddon {
|
|
|
515
622
|
});
|
|
516
623
|
}
|
|
517
624
|
/**
|
|
518
|
-
*
|
|
519
|
-
* assignment-reload cadence, never while firing.
|
|
625
|
+
* The addon id as the REGISTRY and the bindings store spell it.
|
|
520
626
|
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* because a genuine unbind and a store answering from an empty snapshot
|
|
526
|
-
* (D44) are indistinguishable in a single response.
|
|
627
|
+
* `ctx.id` does NOT: hub-main prefixes it (`addon:virtual-doorbell`) while
|
|
628
|
+
* a forked runner does not, and this builtin runs in hub-main. Comparing
|
|
629
|
+
* the raw `ctx.id` to a `providerAddonId` was false for every entry for
|
|
630
|
+
* twelve hours on 2026-08-07 (D72).
|
|
527
631
|
*/
|
|
528
|
-
|
|
529
|
-
|
|
632
|
+
get ownAddonId() {
|
|
633
|
+
return bareAddonId(this.ctx.id);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* One read of the DERIVED binding view. `null` when the read could not be
|
|
637
|
+
* answered at all — which carries no information and must leave the mirror
|
|
638
|
+
* untouched.
|
|
639
|
+
*/
|
|
640
|
+
async readDerivedBinding(cameraId) {
|
|
530
641
|
try {
|
|
531
|
-
|
|
642
|
+
const bindings = await this.ctx.api.deviceManager.getBindings.query({ deviceId: cameraId });
|
|
643
|
+
return {
|
|
644
|
+
bound: entriesBindWrapper(bindings.entries, doorbellCapability.name, this.ownAddonId),
|
|
645
|
+
entries: bindings.entries
|
|
646
|
+
};
|
|
532
647
|
} catch (err) {
|
|
533
648
|
this.ctx.logger.warn("virtual-doorbell: binding check failed — keeping the last known verdict, NOT treating this as an unbind", {
|
|
534
649
|
tags: { deviceId: cameraId },
|
|
@@ -537,19 +652,126 @@ var VirtualDoorbellAddon = class extends BaseAddon {
|
|
|
537
652
|
bindingState: this.bindingMirror.get(cameraId)
|
|
538
653
|
}
|
|
539
654
|
});
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* One read of the DURABLE authority: the `device-manager` addon store's
|
|
660
|
+
* `deviceBindings` map, straight from the settings DB.
|
|
661
|
+
*
|
|
662
|
+
* This is deliberately NOT `getBindings`. The derived view folds the store
|
|
663
|
+
* together with the capability registry, `defaultActive` auto-binding and
|
|
664
|
+
* the cluster native-cap feeds — any of which can make it disagree with
|
|
665
|
+
* what the operator actually persisted, and one of which (an addon-id
|
|
666
|
+
* spelling mismatch) made it disagree *systematically* while looking
|
|
667
|
+
* exactly like a genuine unbind. The raw row cannot: it is what the
|
|
668
|
+
* bindings toggle wrote, and it grades its own trustworthiness (D44).
|
|
669
|
+
*/
|
|
670
|
+
async readDurableAuthority(cameraId) {
|
|
671
|
+
try {
|
|
672
|
+
return readDurableWrapperBinding(await this.ctx.api.addonSettingsRaw.getGlobal.query({ addonId: DEVICE_MANAGER_ADDON_ID }), cameraId, doorbellCapability.name, this.ownAddonId);
|
|
673
|
+
} catch (err) {
|
|
674
|
+
this.ctx.logger.warn("virtual-doorbell: the durable bindings store could not be read — it carries no information and revokes nothing", {
|
|
675
|
+
tags: { deviceId: cameraId },
|
|
676
|
+
meta: { error: errMsg(err) }
|
|
677
|
+
});
|
|
678
|
+
return "unreadable";
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Reconcile `cameraId`'s mirror against the authority. Runs on the
|
|
683
|
+
* assignment-reload cadence, never while firing — and it runs on EVERY
|
|
684
|
+
* cycle regardless of the current verdict, so a camera sitting in
|
|
685
|
+
* `unbound` returns to `bound` on its own the moment the authority says
|
|
686
|
+
* so. There is no latch here that an operator has to clear.
|
|
687
|
+
*
|
|
688
|
+
* A positive derived read is believed immediately. A NEGATIVE one never
|
|
689
|
+
* is: the durable authority is asked, and it outranks the derived view in
|
|
690
|
+
* the direction that destroys work. Only when the authority agrees does
|
|
691
|
+
* the negative get folded, and even then `nextBindingState` still wants a
|
|
692
|
+
* second consecutive one (D49).
|
|
693
|
+
*/
|
|
694
|
+
async refreshBindingMirror(cameraId) {
|
|
695
|
+
const derived = await this.readDerivedBinding(cameraId);
|
|
696
|
+
if (derived === null) return;
|
|
697
|
+
if (derived.bound) {
|
|
698
|
+
this.bindingMirror.set(cameraId, nextBindingState(this.bindingMirror.get(cameraId), true));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const authority = await this.readDurableAuthority(cameraId);
|
|
702
|
+
const entries = summariseEntries(derived.entries);
|
|
703
|
+
if (authority === "bound") {
|
|
704
|
+
this.ctx.logger.warn("virtual-doorbell: the derived binding list omits this wrapper but the durable bindings store still names this wrapper — trusting the store", {
|
|
705
|
+
tags: { deviceId: cameraId },
|
|
706
|
+
meta: {
|
|
707
|
+
ownAddonId: this.ownAddonId,
|
|
708
|
+
entries
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
this.bindingMirror.set(cameraId, nextBindingState(this.bindingMirror.get(cameraId), true));
|
|
540
712
|
return;
|
|
541
713
|
}
|
|
542
|
-
const boundNow = entries.some((entry) => entry.capName === doorbellCapability.name && entry.kind === "wrapped" && entry.providerAddonId === this.ctx.id);
|
|
543
714
|
const previous = this.bindingMirror.get(cameraId);
|
|
544
|
-
const next = nextBindingState(previous,
|
|
715
|
+
const next = nextBindingState(previous, false);
|
|
545
716
|
this.bindingMirror.set(cameraId, next);
|
|
546
|
-
|
|
717
|
+
this.ctx.logger.warn("virtual-doorbell: bindings read did not see this wrapper", {
|
|
547
718
|
tags: { deviceId: cameraId },
|
|
548
719
|
meta: {
|
|
549
720
|
from: previous ?? "never-read",
|
|
550
|
-
to: next
|
|
721
|
+
to: next,
|
|
722
|
+
ownAddonId: this.ownAddonId,
|
|
723
|
+
durableAuthority: authority,
|
|
724
|
+
entries
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* The mirror says `unbound` and a real press just arrived. Decide, with
|
|
730
|
+
* ONE read of the durable authority, whether to destroy it.
|
|
731
|
+
*
|
|
732
|
+
* D49 forbade I/O on the ring path because the read it would have made was
|
|
733
|
+
* the same fallible derived read that caused the problem. This read is
|
|
734
|
+
* different in both directions: it is the authority rather than a view of
|
|
735
|
+
* it, and it is paid ONLY on the branch that would otherwise drop a press —
|
|
736
|
+
* a ring is rare, one query is cheap, and a dropped ring has no retry, no
|
|
737
|
+
* reconcile and nothing downstream that notices.
|
|
738
|
+
*
|
|
739
|
+
* Returns `true` when the ring must proceed.
|
|
740
|
+
*/
|
|
741
|
+
async ringsDespiteUnboundMirror(cameraId, binding) {
|
|
742
|
+
const authority = await this.readDurableAuthority(cameraId);
|
|
743
|
+
if (authority === "bound") {
|
|
744
|
+
this.bindingMirror.set(cameraId, "bound");
|
|
745
|
+
this.ctx.logger.warn("virtual-doorbell: ring RESCUED — the mirror said unbound but the durable bindings store still names this wrapper; the mirror has been repaired", {
|
|
746
|
+
tags: { deviceId: cameraId },
|
|
747
|
+
meta: {
|
|
748
|
+
bindingState: binding,
|
|
749
|
+
ownAddonId: this.ownAddonId
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
if (authority === "unreadable") {
|
|
755
|
+
this.ctx.logger.warn("virtual-doorbell: ringing on an unbound mirror — the durable bindings store could not answer, and an unanswerable store never destroys a one-shot press", {
|
|
756
|
+
tags: { deviceId: cameraId },
|
|
757
|
+
meta: {
|
|
758
|
+
bindingState: binding,
|
|
759
|
+
ownAddonId: this.ownAddonId
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
const derived = await this.readDerivedBinding(cameraId);
|
|
765
|
+
this.ctx.logger.warn("virtual-doorbell: ring DROPPED — the wrapper is not bound to this camera, but a trigger source is still configured for it", {
|
|
766
|
+
tags: { deviceId: cameraId },
|
|
767
|
+
meta: {
|
|
768
|
+
bindingState: binding,
|
|
769
|
+
ownAddonId: this.ownAddonId,
|
|
770
|
+
durableAuthority: authority,
|
|
771
|
+
entries: derived === null ? "unreadable" : summariseEntries(derived.entries)
|
|
551
772
|
}
|
|
552
773
|
});
|
|
774
|
+
return false;
|
|
553
775
|
}
|
|
554
776
|
/**
|
|
555
777
|
* Record a binding we observed FIRST-HAND. Both callers are cap methods
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
9
9
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
4
4
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
4
4
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
5
5
|
var NO_DEVICES = "liveness:no-devices";
|
|
6
6
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Et as createEvent, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
3
3
|
var NO_DEVICES = "liveness:no-devices";
|
|
4
4
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
9
9
|
let crypto$1 = require("crypto");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { ct as userManagementCapability, d as ScopedTokenSchema, p as UserRecordSchema, pt as BaseAddon, r as ApiKeyRecordSchema, y as authProviderCapability } from "../../dist-C5XpOjbI.mjs";
|
|
3
3
|
import * as crypto$1 from "node:crypto";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import nodeCrypto from "crypto";
|
|
@@ -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-
|
|
3
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
4
4
|
let node_os = require("node:os");
|
|
5
5
|
node_os = require_chunk.__toESM(node_os);
|
|
6
6
|
//#region src/builtins/local-network/local-network.addon.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { W as localNetworkCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
//#region src/builtins/local-network/local-network.addon.ts
|
|
4
4
|
/**
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
8
8
|
/**
|
|
9
9
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
3
3
|
/**
|
|
4
4
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_child_process = require("node:child_process");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Et as createEvent, J as metricsProviderCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { execFile, execFileSync } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
node_fs = require_chunk.__toESM(node_fs);
|
|
9
9
|
let node_child_process = require("node:child_process");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { A as enumerateInferenceDevices, Ot as emitReadiness, Q as platformProbeCapability, et as scoreRuntimes, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
//#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
|
|
8
8
|
/**
|
|
9
9
|
* Reconcile the durable `enabledProviders` set against authoritative
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
//#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
|
|
3
3
|
/**
|
|
4
4
|
* Reconcile the durable `enabledProviders` set against authoritative
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
//#region src/builtins/snapshot/snapshot-coalescing.ts
|
|
9
9
|
/**
|
|
@@ -604,7 +604,7 @@ var SnapshotAddon = class SnapshotAddon extends require_dist.BaseAddon {
|
|
|
604
604
|
access: "authenticated",
|
|
605
605
|
handler
|
|
606
606
|
}) ?? null;
|
|
607
|
-
this.ctx.logger.info("snapshot media data-plane served", { meta: { baseUrl: this.mediaDataPlane ? `/addon/${this.ctx.id}/media` : "(no dataPlane facility)" } });
|
|
607
|
+
this.ctx.logger.info("snapshot media data-plane served", { meta: { baseUrl: this.mediaDataPlane ? `/addon/${require_dist.bareAddonId(this.ctx.id)}/media` : "(no dataPlane facility)" } });
|
|
608
608
|
} catch (err) {
|
|
609
609
|
this.ctx.logger.warn("snapshot media data-plane failed to serve", { meta: { error: require_dist.errMsg(err) } });
|
|
610
610
|
this.mediaDataPlane = null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as nodePin, _t as DeviceFeature, ft as errMsg, i as BatteryStatusSchema, ot as streamQualityLabel, pt as BaseAddon, rt as snapshotCapability, x as bareAddonId, yt as DeviceType } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
import { execFile, spawn } from "node:child_process";
|
|
3
3
|
//#region src/builtins/snapshot/snapshot-coalescing.ts
|
|
4
4
|
/**
|
|
@@ -599,7 +599,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
|
|
|
599
599
|
access: "authenticated",
|
|
600
600
|
handler
|
|
601
601
|
}) ?? null;
|
|
602
|
-
this.ctx.logger.info("snapshot media data-plane served", { meta: { baseUrl: this.mediaDataPlane ? `/addon/${this.ctx.id}/media` : "(no dataPlane facility)" } });
|
|
602
|
+
this.ctx.logger.info("snapshot media data-plane served", { meta: { baseUrl: this.mediaDataPlane ? `/addon/${bareAddonId(this.ctx.id)}/media` : "(no dataPlane facility)" } });
|
|
603
603
|
} catch (err) {
|
|
604
604
|
this.ctx.logger.warn("snapshot media data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
605
605
|
this.mediaDataPlane = null;
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-Cis_iV-u.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as filesystemBrowseCapability, at as storageProviderCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ISettingsBackend, SettingsCollection } from '@camstack/types';
|
|
2
|
+
/** One retired-key declaration, scoped to a single store row. */
|
|
3
|
+
export interface RetiredKeySpec {
|
|
4
|
+
/** Addon id owning the store (the settings-store namespace). */
|
|
5
|
+
readonly namespace: string;
|
|
6
|
+
/** Collection inside that namespace, e.g. `addon-settings`. */
|
|
7
|
+
readonly collection: SettingsCollection;
|
|
8
|
+
/** Row id inside the collection, e.g. `root`. */
|
|
9
|
+
readonly row: string;
|
|
10
|
+
/** Keys retired verbatim. Matched exactly. */
|
|
11
|
+
readonly keys: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Keys retired in both their bare form and their per-node form
|
|
14
|
+
* (`<key>@<nodeId>`), which is how `nodeScopedKey` persists a
|
|
15
|
+
* `perNode: true` schema field.
|
|
16
|
+
*/
|
|
17
|
+
readonly perNodeKeys: readonly string[];
|
|
18
|
+
/** Why they are dead — cited in the record, not re-derived at the call site. */
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
}
|
|
21
|
+
/** What a purge pass deleted from one row. */
|
|
22
|
+
export interface RetiredKeyPurgeResult {
|
|
23
|
+
readonly addon: string;
|
|
24
|
+
readonly collection: string;
|
|
25
|
+
readonly row: string;
|
|
26
|
+
readonly keys: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
/** The store access the purge needs. Narrow on purpose — trivially fakeable. */
|
|
29
|
+
export interface RetiredKeyStore {
|
|
30
|
+
/** Current row value, or `null` when the row does not exist. Throws on fault. */
|
|
31
|
+
read(spec: RetiredKeySpec): Promise<Record<string, unknown> | null>;
|
|
32
|
+
/** Replace the row with `value`. */
|
|
33
|
+
write(spec: RetiredKeySpec, value: Record<string, unknown>): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Is THIS node the one whose settings store is the cluster's authority?
|
|
37
|
+
*
|
|
38
|
+
* Mirrors `resolveNodeRole` (server/backend `node-role.ts`) — unset or `'hub'`
|
|
39
|
+
* means hub, which is the launcher's own default. It is duplicated rather than
|
|
40
|
+
* imported because `@camstack/system` is a dependency OF the backend, not the
|
|
41
|
+
* other way round; the parse is four lines and the env var is the contract.
|
|
42
|
+
*
|
|
43
|
+
* Anything else answers `false`. Skipping where the purge should have run is a
|
|
44
|
+
* missed cleanup the next hub boot fixes; running where it should not have is a
|
|
45
|
+
* write to a store this node does not own. Only one of those is recoverable.
|
|
46
|
+
*/
|
|
47
|
+
export declare function settingsStoreIsAuthoritativeHere(env: NodeJS.ProcessEnv): boolean;
|
|
48
|
+
/** The logging surface the purge uses — a structural subset of `IScopedLogger`. */
|
|
49
|
+
export interface RetiredKeyLogger {
|
|
50
|
+
info(message: string, context?: {
|
|
51
|
+
readonly meta?: Record<string, unknown>;
|
|
52
|
+
}): void;
|
|
53
|
+
warn(message: string, context?: {
|
|
54
|
+
readonly meta?: Record<string, unknown>;
|
|
55
|
+
}): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The dead keys, per row.
|
|
59
|
+
*
|
|
60
|
+
* `detection-pipeline` / `addon-settings` / `root` — every key in this row is
|
|
61
|
+
* dead; the row itself is kept because the addon writes live keys into it
|
|
62
|
+
* (`pipelineTemplates`, `videoPipelineSteps`).
|
|
63
|
+
*
|
|
64
|
+
* - `pipelineSteps` / `pipelineEngine`: the persisted GLOBAL step + engine
|
|
65
|
+
* seed. Removed with `getGlobalSteps` becoming pure — dispatch builds steps
|
|
66
|
+
* from the orchestrator's `agentSettings` plus per-camera overrides, and
|
|
67
|
+
* never consulted the seed. See `addon-pipeline/src/detection-pipeline/provider.ts`.
|
|
68
|
+
* - `engineRuntime` / `engineBackend` / `engineDevice` / `probedBestEngine`:
|
|
69
|
+
* the per-node engine CASCADE, removed with the operator-facing engine
|
|
70
|
+
* election. Each node now runs a device-array of inference pools balanced
|
|
71
|
+
* per session, and the node-default pool derives live from the node's own
|
|
72
|
+
* hardware (`resolveAutoEngine`). See
|
|
73
|
+
* `addon-pipeline/src/detection-pipeline/engine-store-keys.ts`.
|
|
74
|
+
*/
|
|
75
|
+
export declare const RETIRED_SETTINGS_KEYS: readonly RetiredKeySpec[];
|
|
76
|
+
/**
|
|
77
|
+
* Adapt the settings backend to {@link RetiredKeyStore}.
|
|
78
|
+
*
|
|
79
|
+
* `get` answering a non-object (missing row) is a MISS, not a fault: it maps to
|
|
80
|
+
* `null` and the purge skips the row. A backend throw propagates, which is what
|
|
81
|
+
* routes it to the "read failed, row left untouched" branch.
|
|
82
|
+
*/
|
|
83
|
+
export declare function retiredKeyStoreOf(backend: ISettingsBackend): RetiredKeyStore;
|
|
84
|
+
/** Keys of `blob` that `spec` retires. Pure. */
|
|
85
|
+
export declare function planRetiredKeyPurge(blob: Readonly<Record<string, unknown>>, spec: RetiredKeySpec): readonly string[];
|
|
86
|
+
/**
|
|
87
|
+
* Delete every retired key present in the declared rows — on the hub.
|
|
88
|
+
*
|
|
89
|
+
* Off the authoritative node this is a no-op that touches nothing and says
|
|
90
|
+
* nothing (see the module header). Returns one entry per row that actually
|
|
91
|
+
* changed — empty when the store is already clean, which is the steady state
|
|
92
|
+
* after the first boot that ran it.
|
|
93
|
+
*/
|
|
94
|
+
export declare function purgeRetiredSettingsKeys(store: RetiredKeyStore, logger: RetiredKeyLogger, specs?: readonly RetiredKeySpec[], env?: NodeJS.ProcessEnv): Promise<readonly RetiredKeyPurgeResult[]>;
|