@camstack/system 1.2.316 → 1.2.318
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/auth/auth-manager.d.ts +11 -0
- 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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.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/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +347 -181
- package/dist/builtins/composer/composer.addon.mjs +347 -181
- package/dist/builtins/composer/composition-evaluation.d.ts +37 -0
- package/dist/builtins/composer/composition-runtime.d.ts +11 -52
- package/dist/builtins/composer/composition-target.d.ts +50 -0
- package/dist/builtins/composer/existing-target.d.ts +19 -1
- package/dist/builtins/composer/own-field-reads.d.ts +19 -0
- 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 +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +265 -29
- package/dist/builtins/device-manager/device-manager.addon.mjs +265 -29
- package/dist/builtins/device-manager/device-provider-context.d.ts +6 -0
- package/dist/builtins/device-manager/device-source-activation.d.ts +26 -1
- package/dist/builtins/device-manager/device-state-mirror.d.ts +41 -9
- package/dist/builtins/device-manager/mirror-row-writer.d.ts +59 -1
- package/dist/builtins/doorbell/binding-mirror.d.ts +45 -81
- package/dist/builtins/doorbell/doorbell-extension-slot.d.ts +4 -3
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +53 -11
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +207 -82
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +207 -82
- 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 +47 -3
- package/dist/builtins/local-auth/local-auth.addon.mjs +46 -4
- 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 +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- 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/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- 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 +4 -4
- package/dist/builtins/system-config/system-config.addon.mjs +4 -4
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-AAltfBUH.mjs → child-cap-dispatch-C2VINHu1.mjs} +70 -18
- package/dist/{child-cap-dispatch-DzcUd_ct.js → child-cap-dispatch-C8uts1P4.js} +70 -18
- package/dist/{composition-sources-BwE1POnF.js → composition-sources-BjRPhWtU.js} +1 -1
- package/dist/{composition-sources-fUyF4z39.mjs → composition-sources-Cg2MP-qf.mjs} +1 -1
- package/dist/{dist-BPvku43i.mjs → dist-CQ9vXSUh.mjs} +220 -8
- package/dist/{dist-RAl10EfG.js → dist-Dcy0gVHu.js} +220 -8
- package/dist/index.js +739 -350
- package/dist/index.mjs +737 -352
- package/dist/kernel/bootstrap-config.d.ts +8 -0
- package/dist/kernel/capability-registry.d.ts +30 -0
- package/dist/kernel/config-manager.d.ts +75 -129
- package/dist/kernel/index.d.ts +5 -2
- package/dist/kernel/moleculer/runtime-state-load.d.ts +19 -0
- package/dist/kernel/settings-door-view.d.ts +24 -1
- package/dist/kernel/settings-read-error.d.ts +37 -0
- package/dist/kernel/settings-view-types.d.ts +105 -0
- package/dist/kernel/store-settings-view.d.ts +17 -0
- package/dist/kernel/system-settings-mirror.d.ts +34 -0
- package/dist/kernel/system-settings-policy.d.ts +47 -0
- package/dist/{retired-settings-keys-DqVWSrVw.mjs → retired-settings-keys-DNIbyfTR.mjs} +1 -1
- package/dist/{retired-settings-keys-D3rGTGq5.js → retired-settings-keys-pzlrLUbA.js} +1 -1
- package/package.json +1 -1
|
@@ -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-Dcy0gVHu.js");
|
|
7
7
|
const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
|
|
8
8
|
let zod = require("zod");
|
|
9
9
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
@@ -201,11 +201,18 @@ var DoorbellTriggerEngine = class {
|
|
|
201
201
|
* 2. The DERIVED view (`deviceManager.getBindings`) is no longer the sole
|
|
202
202
|
* input. Before a negative is believed — and again on the ring path
|
|
203
203
|
* before a press is destroyed — the DURABLE authority is consulted
|
|
204
|
-
* directly ({@link
|
|
205
|
-
*
|
|
206
|
-
* the derived view genuinely cannot offer:
|
|
207
|
-
*
|
|
208
|
-
*
|
|
204
|
+
* directly ({@link durableVerdictOf} over
|
|
205
|
+
* `deviceManager.getWrapperActivation`). That read carries a discriminator
|
|
206
|
+
* the derived view genuinely cannot offer: it is three-way, so a store
|
|
207
|
+
* that could not answer (`failed`) is never mistaken for a store that
|
|
208
|
+
* answered "nothing here" (`missing`).
|
|
209
|
+
*
|
|
210
|
+
* 2026-09-29 (D675). Until then the durable read was
|
|
211
|
+
* `addonSettingsRaw.getGlobal({ addonId: 'device-manager' })`, whose backend
|
|
212
|
+
* (`ConfigManager.getAddonConfig`) has had no store on the hub since D231
|
|
213
|
+
* isolated the engine: it answered `{}` on every call, so every durable read
|
|
214
|
+
* was `unreadable`, an operator's explicit "off" (camera 615,
|
|
215
|
+
* `wrapperAddonId: null`) was never honoured, and a press rang anyway.
|
|
209
216
|
*/
|
|
210
217
|
/**
|
|
211
218
|
* Fold one SUCCESSFUL bindings read into the mirror. A read that failed must
|
|
@@ -216,9 +223,12 @@ function nextBindingState(previous, boundNow) {
|
|
|
216
223
|
if (boundNow) return "bound";
|
|
217
224
|
return previous === "unbind-pending" || previous === "unbound" ? "unbound" : "unbind-pending";
|
|
218
225
|
}
|
|
219
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* May a ring proceed on the mirror STATE alone? Only a corroborated unbind
|
|
228
|
+
* stops one. {@link needsDurableRead} adds the served-elsewhere case on top.
|
|
229
|
+
*/
|
|
220
230
|
function allowsRing(state) {
|
|
221
|
-
return
|
|
231
|
+
return state !== "unbound";
|
|
222
232
|
}
|
|
223
233
|
/** True only when a read has positively confirmed the binding. */
|
|
224
234
|
function hasConfirmedBinding(state) {
|
|
@@ -243,46 +253,62 @@ function entriesBindWrapper(entries, capName, ownAddonId) {
|
|
|
243
253
|
function summariseEntries(entries) {
|
|
244
254
|
return entries.map((entry) => `${entry.capName}:${entry.kind}:${entry.providerAddonId}`);
|
|
245
255
|
}
|
|
246
|
-
function isRecord$2(value) {
|
|
247
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
248
|
-
}
|
|
249
256
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* `capName` on `deviceId`".
|
|
257
|
+
* The durable verdict for `ownAddonId`, from device-manager's STORED
|
|
258
|
+
* activation of this cap on this device (`deviceManager.getWrapperActivation`).
|
|
253
259
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
+
* - `value` naming this wrapper → `bound`.
|
|
261
|
+
* - `value` naming another wrapper, or `null` — the operator's explicit "off"
|
|
262
|
+
* marker (D62) — → `unbound`.
|
|
263
|
+
* - `missing` → `unbound`: the store ANSWERED and holds no activation, and the
|
|
264
|
+
* doorbell cap is `defaultActive: false`, so a wrapper nobody activated is
|
|
265
|
+
* not bound.
|
|
266
|
+
* - `failed` → `unreadable`: it carries no information and destroys nothing.
|
|
267
|
+
*/
|
|
268
|
+
function durableVerdictOf(read, ownAddonId) {
|
|
269
|
+
switch (read.kind) {
|
|
270
|
+
case "failed": return "unreadable";
|
|
271
|
+
case "missing": return "unbound";
|
|
272
|
+
case "value": return read.wrapperAddonId !== null && require_dist.isSameAddonId(read.wrapperAddonId, ownAddonId) ? "bound" : "unbound";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Who, other than this wrapper, the derived view says serves `capName` —
|
|
277
|
+
* `composer` on a camera whose doorbell was migrated to a composition (D671).
|
|
278
|
+
* Non-empty means a press this wrapper does not ring is still somebody's ring.
|
|
260
279
|
*/
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
const deviceBindings = record["deviceBindings"];
|
|
264
|
-
if (!isRecord$2(deviceBindings)) return "unreadable";
|
|
265
|
-
const perDevice = deviceBindings[String(deviceId)];
|
|
266
|
-
if (!isRecord$2(perDevice)) return "unbound";
|
|
267
|
-
const activation = perDevice[capName];
|
|
268
|
-
if (!isRecord$2(activation)) return "unbound";
|
|
269
|
-
const wrapperAddonId = activation["wrapperAddonId"];
|
|
270
|
-
if (typeof wrapperAddonId !== "string") return "unbound";
|
|
271
|
-
return require_dist.isSameAddonId(wrapperAddonId, ownAddonId) ? "bound" : "unbound";
|
|
280
|
+
function otherProvidersOf(entries, capName, ownAddonId) {
|
|
281
|
+
return entries.filter((entry) => entry.capName === capName && !require_dist.isSameAddonId(entry.providerAddonId, ownAddonId)).map((entry) => entry.providerAddonId);
|
|
272
282
|
}
|
|
273
|
-
/**
|
|
274
|
-
|
|
275
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Does this camera need the durable authority before a verdict?
|
|
285
|
+
*
|
|
286
|
+
* An `unbound` mirror does (D72). So does any mirror short of `bound` on a
|
|
287
|
+
* camera whose doorbell another provider serves — the D671 cutover, where
|
|
288
|
+
* erring open means ringing ON TOP of the composer: after every restart the
|
|
289
|
+
* mirror starts `undefined` and spends a reload cycle at `unbind-pending`, and
|
|
290
|
+
* each press in that window was a duplicate ring. Two independent reads
|
|
291
|
+
* agreeing — the derived view naming another provider, the store naming no
|
|
292
|
+
* wrapper — is the D49 "second read agrees"; an unreadable store still rings.
|
|
293
|
+
*/
|
|
294
|
+
function needsDurableRead(mirror) {
|
|
295
|
+
if (!allowsRing(mirror.state)) return true;
|
|
296
|
+
return mirror.servedBy.length > 0 && mirror.state !== "bound";
|
|
276
297
|
}
|
|
277
298
|
/**
|
|
278
|
-
* THE decision — the ring gate (`fireDoorbell` / `
|
|
299
|
+
* THE decision — the ring gate (`fireDoorbell` / `ringsAfterDurableRead`)
|
|
279
300
|
* and the Extensions card's activation both call it, so what the card says
|
|
280
301
|
* and what a press does cannot disagree. `durable` is `null` when it was not
|
|
281
|
-
* read, which {@link needsDurableRead} allows
|
|
302
|
+
* read, which {@link needsDurableRead} allows only where it says so.
|
|
282
303
|
* Pure: it reads nothing and writes nothing.
|
|
283
304
|
*/
|
|
284
|
-
function ringVerdict(
|
|
305
|
+
function ringVerdict(mirror, durable) {
|
|
306
|
+
const state = mirror.state;
|
|
285
307
|
if (state === "bound") return "rings";
|
|
308
|
+
if (mirror.servedBy.length > 0) {
|
|
309
|
+
if (durable === "unbound") return "silent";
|
|
310
|
+
if (durable === "bound") return "rings";
|
|
311
|
+
}
|
|
286
312
|
if (state === "unbind-pending") return "rings-pending";
|
|
287
313
|
if (state === void 0) return "rings-unconfirmed";
|
|
288
314
|
if (durable === "bound") return "rings";
|
|
@@ -853,13 +879,6 @@ function errorText(err) {
|
|
|
853
879
|
}
|
|
854
880
|
//#endregion
|
|
855
881
|
//#region src/builtins/doorbell/virtual-doorbell.addon.ts
|
|
856
|
-
/**
|
|
857
|
-
* The addon whose store IS the durable authority on wrapper activations.
|
|
858
|
-
* `deviceBindings[<deviceId>][<capName>].wrapperAddonId` is the row the
|
|
859
|
-
* operator's bindings toggle writes; `deviceManager.getBindings` is a VIEW
|
|
860
|
-
* derived from it plus the capability registry.
|
|
861
|
-
*/
|
|
862
|
-
var DEVICE_MANAGER_ADDON_ID = "device-manager";
|
|
863
882
|
/** Narrow a raw `doorbell` runtime-state slice to counters (defaults on absence). */
|
|
864
883
|
function parseDoorbellSlice(slice) {
|
|
865
884
|
const lastPressedAtRaw = slice?.["lastPressedAt"];
|
|
@@ -912,6 +931,24 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
912
931
|
* `binding-mirror.ts` for why a single negative read is not believed.
|
|
913
932
|
*/
|
|
914
933
|
bindingMirror = /* @__PURE__ */ new Map();
|
|
934
|
+
/**
|
|
935
|
+
* Per camera, the last NEGATIVE refresh this addon reported — its state,
|
|
936
|
+
* the authority's verdict and who else serves the doorbell. A refresh runs
|
|
937
|
+
* every reload cycle (about once a minute); the same answer repeated is not
|
|
938
|
+
* news, so it is reported once per change and forgotten on a positive read.
|
|
939
|
+
* Camera 615 logged the same WARN 607 times in 14 h after D671.
|
|
940
|
+
*/
|
|
941
|
+
reportedNegative = /* @__PURE__ */ new Map();
|
|
942
|
+
/** Cameras whose last durable-authority read THREW — reported once per outage. */
|
|
943
|
+
durableReadFailing = /* @__PURE__ */ new Set();
|
|
944
|
+
/**
|
|
945
|
+
* Per camera, the OTHER providers of `doorbell` the last successful derived
|
|
946
|
+
* read named (the composer after D671). Written off the ring path with the
|
|
947
|
+
* mirror; read on it, to decide whether erring open would double a ring.
|
|
948
|
+
*/
|
|
949
|
+
servedElsewhere = /* @__PURE__ */ new Map();
|
|
950
|
+
/** Cameras whose store-over-derived disagreement has been reported, until a derived read agrees. */
|
|
951
|
+
reportedStoreOverride = /* @__PURE__ */ new Set();
|
|
915
952
|
constructor() {
|
|
916
953
|
super({
|
|
917
954
|
debounceMs: 2e3,
|
|
@@ -992,7 +1029,7 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
992
1029
|
projection: "slim",
|
|
993
1030
|
deviceIds: [...deviceIds]
|
|
994
1031
|
})).map(toSlotDevice),
|
|
995
|
-
readBindingMirror: (deviceId) => this.
|
|
1032
|
+
readBindingMirror: (deviceId) => this.mirrorRead(deviceId),
|
|
996
1033
|
readDurableBinding: (deviceId) => this.readDurableAuthority(deviceId),
|
|
997
1034
|
sourceCapableDeviceIds: () => this.ctx.api.deviceManager.listDeviceIdsByCaps.query({ capNames: [...require_dist.SOURCE_CAPS] }),
|
|
998
1035
|
listFleet: async () => (await this.ctx.api.deviceManager.listAll.query({ projection: "slim" })).map(toSlotDevice)
|
|
@@ -1041,9 +1078,9 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
1041
1078
|
* (UI toast, notifier rules, exporters) is agnostic to the origin.
|
|
1042
1079
|
*
|
|
1043
1080
|
* The binding gate here is a pure in-memory read on the path that RINGS:
|
|
1044
|
-
*
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1081
|
+
* a `bound` mirror, or an open one on a camera nobody else serves, performs
|
|
1082
|
+
* NO I/O and cannot fail. Every other case asks the durable authority once
|
|
1083
|
+
* before deciding — see `needsDurableRead` and `ringsAfterDurableRead`.
|
|
1047
1084
|
*
|
|
1048
1085
|
* `sourceDeviceId`/`capName` are the source that produced this rise —
|
|
1049
1086
|
* carried through purely so the terminal `pressed` line can name it. Every
|
|
@@ -1053,8 +1090,9 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
1053
1090
|
* timestamp against provider logs (proven live 2026-08-25, camera 615).
|
|
1054
1091
|
*/
|
|
1055
1092
|
async fireDoorbell(cameraId, timestamp, sourceDeviceId, capName) {
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1093
|
+
const mirror = this.mirrorRead(cameraId);
|
|
1094
|
+
const binding = mirror.state;
|
|
1095
|
+
if (needsDurableRead(mirror) && !await this.ringsAfterDurableRead(cameraId, mirror)) return;
|
|
1058
1096
|
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", {
|
|
1059
1097
|
tags: { deviceId: cameraId },
|
|
1060
1098
|
meta: { bindingState: binding ?? "never-read" }
|
|
@@ -1131,8 +1169,10 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
1131
1169
|
}
|
|
1132
1170
|
}
|
|
1133
1171
|
/**
|
|
1134
|
-
* One read of the DURABLE authority: the
|
|
1135
|
-
*
|
|
1172
|
+
* One read of the DURABLE authority: the wrapper activation device-manager
|
|
1173
|
+
* STORED for this camera (`deviceManager.getWrapperActivation`, D675) — the
|
|
1174
|
+
* row the operator's bindings toggle writes. `deviceManager.getBindings` is
|
|
1175
|
+
* a VIEW derived from it plus the capability registry.
|
|
1136
1176
|
*
|
|
1137
1177
|
* This is deliberately NOT `getBindings`. The derived view folds the store
|
|
1138
1178
|
* together with the capability registry, `defaultActive` auto-binding and
|
|
@@ -1141,17 +1181,35 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
1141
1181
|
* spelling mismatch) made it disagree *systematically* while looking
|
|
1142
1182
|
* exactly like a genuine unbind. The raw row cannot: it is what the
|
|
1143
1183
|
* bindings toggle wrote, and it grades its own trustworthiness (D44).
|
|
1184
|
+
*
|
|
1185
|
+
* A read that throws is reported when the outage STARTS and when it ends —
|
|
1186
|
+
* never per read: this runs on every reload cycle.
|
|
1144
1187
|
*/
|
|
1145
1188
|
async readDurableAuthority(cameraId) {
|
|
1189
|
+
let read;
|
|
1146
1190
|
try {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
tags: { deviceId: cameraId },
|
|
1151
|
-
meta: { error: require_dist.errMsg(err) }
|
|
1191
|
+
read = await this.ctx.api.deviceManager.getWrapperActivation.query({
|
|
1192
|
+
deviceId: cameraId,
|
|
1193
|
+
capName: require_dist.doorbellCapability.name
|
|
1152
1194
|
});
|
|
1195
|
+
} catch (err) {
|
|
1196
|
+
read = {
|
|
1197
|
+
kind: "failed",
|
|
1198
|
+
error: require_dist.errMsg(err)
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
if (read.kind === "failed") {
|
|
1202
|
+
if (!this.durableReadFailing.has(cameraId)) {
|
|
1203
|
+
this.durableReadFailing.add(cameraId);
|
|
1204
|
+
this.ctx.logger.warn("virtual-doorbell: the durable bindings store could not be read — it carries no information and revokes nothing (reported once until it answers again)", {
|
|
1205
|
+
tags: { deviceId: cameraId },
|
|
1206
|
+
meta: { error: read.error }
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1153
1209
|
return "unreadable";
|
|
1154
1210
|
}
|
|
1211
|
+
if (this.durableReadFailing.delete(cameraId)) this.ctx.logger.info("virtual-doorbell: the durable bindings store answers again", { tags: { deviceId: cameraId } });
|
|
1212
|
+
return durableVerdictOf(read, this.ownAddonId);
|
|
1155
1213
|
}
|
|
1156
1214
|
/**
|
|
1157
1215
|
* Reconcile `cameraId`'s mirror against the authority. Runs on the
|
|
@@ -1169,60 +1227,114 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
1169
1227
|
async refreshBindingMirror(cameraId) {
|
|
1170
1228
|
const derived = await this.readDerivedBinding(cameraId);
|
|
1171
1229
|
if (derived === null) return;
|
|
1230
|
+
const servedBy = otherProvidersOf(derived.entries, require_dist.doorbellCapability.name, this.ownAddonId);
|
|
1231
|
+
this.servedElsewhere.set(cameraId, servedBy);
|
|
1172
1232
|
if (derived.bound) {
|
|
1173
1233
|
this.bindingMirror.set(cameraId, nextBindingState(this.bindingMirror.get(cameraId), true));
|
|
1234
|
+
this.reportedNegative.delete(cameraId);
|
|
1235
|
+
this.reportedStoreOverride.delete(cameraId);
|
|
1174
1236
|
return;
|
|
1175
1237
|
}
|
|
1176
1238
|
const authority = await this.readDurableAuthority(cameraId);
|
|
1177
1239
|
const entries = summariseEntries(derived.entries);
|
|
1178
1240
|
if (authority === "bound") {
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1241
|
+
this.reportedNegative.delete(cameraId);
|
|
1242
|
+
if (!this.reportedStoreOverride.has(cameraId)) {
|
|
1243
|
+
this.reportedStoreOverride.add(cameraId);
|
|
1244
|
+
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 (reported once until they agree)", {
|
|
1245
|
+
tags: { deviceId: cameraId },
|
|
1246
|
+
meta: {
|
|
1247
|
+
ownAddonId: this.ownAddonId,
|
|
1248
|
+
entries
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1186
1252
|
this.bindingMirror.set(cameraId, nextBindingState(this.bindingMirror.get(cameraId), true));
|
|
1187
1253
|
return;
|
|
1188
1254
|
}
|
|
1255
|
+
this.reportedStoreOverride.delete(cameraId);
|
|
1189
1256
|
const previous = this.bindingMirror.get(cameraId);
|
|
1190
1257
|
const next = nextBindingState(previous, false);
|
|
1191
1258
|
this.bindingMirror.set(cameraId, next);
|
|
1192
|
-
this.
|
|
1259
|
+
this.reportNegativeRefresh(cameraId, {
|
|
1260
|
+
from: previous ?? "never-read",
|
|
1261
|
+
to: next,
|
|
1262
|
+
authority,
|
|
1263
|
+
servedBy,
|
|
1264
|
+
entries
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
/** The ring gate's in-memory input for one camera — no I/O. */
|
|
1268
|
+
mirrorRead(cameraId) {
|
|
1269
|
+
return {
|
|
1270
|
+
state: this.bindingMirror.get(cameraId),
|
|
1271
|
+
servedBy: this.servedElsewhere.get(cameraId) ?? []
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Report a negative refresh ONCE per change of what it says (D391: a
|
|
1276
|
+
* repeated identical line is noise that buries the next real one).
|
|
1277
|
+
*
|
|
1278
|
+
* The level follows what the answer MEANS:
|
|
1279
|
+
* - the durable store could not grade it (`unreadable`) — WARN: the
|
|
1280
|
+
* camera's ring now depends on erring open, and nobody can say whether
|
|
1281
|
+
* that is right;
|
|
1282
|
+
* - the store CONFIRMS the wrapper is not bound — an operator's unbind or
|
|
1283
|
+
* the D671 cutover to a composition. That is a state, not an anomaly:
|
|
1284
|
+
* INFO when it is believed (`unbound`), DEBUG for the first,
|
|
1285
|
+
* not-yet-believed read.
|
|
1286
|
+
*/
|
|
1287
|
+
reportNegativeRefresh(cameraId, report) {
|
|
1288
|
+
const key = `${report.to}|${report.authority}|${report.servedBy.join(",")}`;
|
|
1289
|
+
if (this.reportedNegative.get(cameraId) === key) return;
|
|
1290
|
+
this.reportedNegative.set(cameraId, key);
|
|
1291
|
+
const options = {
|
|
1193
1292
|
tags: { deviceId: cameraId },
|
|
1194
1293
|
meta: {
|
|
1195
|
-
from:
|
|
1196
|
-
to:
|
|
1294
|
+
from: report.from,
|
|
1295
|
+
to: report.to,
|
|
1197
1296
|
ownAddonId: this.ownAddonId,
|
|
1198
|
-
durableAuthority: authority,
|
|
1199
|
-
|
|
1297
|
+
durableAuthority: report.authority,
|
|
1298
|
+
doorbellServedBy: report.servedBy,
|
|
1299
|
+
entries: report.entries
|
|
1200
1300
|
}
|
|
1201
|
-
}
|
|
1301
|
+
};
|
|
1302
|
+
if (report.authority === "unreadable") {
|
|
1303
|
+
this.ctx.logger.warn("virtual-doorbell: bindings read did not see this wrapper and the durable store cannot confirm it — a press still rings", options);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const message = report.servedBy.length > 0 ? `virtual-doorbell: wrapper deactivated on this camera — its doorbell is served by ${report.servedBy.join(", ")}` : "virtual-doorbell: wrapper deactivated on this camera — the bindings store confirms it";
|
|
1307
|
+
if (report.to === "unbound") this.ctx.logger.info(message, options);
|
|
1308
|
+
else this.ctx.logger.debug(message, options);
|
|
1202
1309
|
}
|
|
1203
1310
|
/**
|
|
1204
|
-
*
|
|
1205
|
-
*
|
|
1311
|
+
* A real press arrived on a camera the in-memory gate cannot clear on its
|
|
1312
|
+
* own: the mirror is `unbound`, or it is short of `bound` while another
|
|
1313
|
+
* provider serves the doorbell. Decide, with ONE read of the durable
|
|
1314
|
+
* authority, whether this wrapper rings.
|
|
1206
1315
|
*
|
|
1207
1316
|
* D49 forbade I/O on the ring path because the read it would have made was
|
|
1208
1317
|
* the same fallible derived read that caused the problem. This read is
|
|
1209
1318
|
* different in both directions: it is the authority rather than a view of
|
|
1210
|
-
* it, and it is paid ONLY
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1319
|
+
* it, and it is paid ONLY where erring open could be wrong — a dropped ring
|
|
1320
|
+
* (an `unbound` mirror) or a DOUBLED one (another provider rings this
|
|
1321
|
+
* camera). A ring is rare and one query is cheap. An unreadable store still
|
|
1322
|
+
* rings: it carries no information.
|
|
1213
1323
|
*
|
|
1214
1324
|
* Returns `true` when the ring must proceed.
|
|
1215
1325
|
*/
|
|
1216
|
-
async
|
|
1326
|
+
async ringsAfterDurableRead(cameraId, mirror) {
|
|
1327
|
+
const binding = mirror.state;
|
|
1217
1328
|
const authority = await this.readDurableAuthority(cameraId);
|
|
1218
|
-
const verdict = ringVerdict(
|
|
1329
|
+
const verdict = ringVerdict(mirror, authority);
|
|
1219
1330
|
switch (verdict) {
|
|
1220
1331
|
case "rings":
|
|
1221
1332
|
this.bindingMirror.set(cameraId, "bound");
|
|
1222
|
-
this.ctx.logger.warn("virtual-doorbell: ring RESCUED — the mirror
|
|
1333
|
+
this.ctx.logger.warn("virtual-doorbell: ring RESCUED — the mirror did not confirm this wrapper but the durable bindings store still names it; the mirror has been repaired", {
|
|
1223
1334
|
tags: { deviceId: cameraId },
|
|
1224
1335
|
meta: {
|
|
1225
|
-
bindingState: binding,
|
|
1336
|
+
bindingState: binding ?? "never-read",
|
|
1337
|
+
doorbellServedBy: mirror.servedBy,
|
|
1226
1338
|
ownAddonId: this.ownAddonId
|
|
1227
1339
|
}
|
|
1228
1340
|
});
|
|
@@ -1238,19 +1350,32 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
1238
1350
|
return verdictRings(verdict);
|
|
1239
1351
|
case "silent": {
|
|
1240
1352
|
const derived = await this.readDerivedBinding(cameraId);
|
|
1241
|
-
|
|
1353
|
+
const servedBy = derived === null ? mirror.servedBy : otherProvidersOf(derived.entries, require_dist.doorbellCapability.name, this.ownAddonId);
|
|
1354
|
+
const options = {
|
|
1242
1355
|
tags: { deviceId: cameraId },
|
|
1243
1356
|
meta: {
|
|
1244
1357
|
bindingState: binding,
|
|
1245
1358
|
ownAddonId: this.ownAddonId,
|
|
1246
1359
|
durableAuthority: authority,
|
|
1360
|
+
doorbellServedBy: servedBy,
|
|
1247
1361
|
entries: derived === null ? "unreadable" : summariseEntries(derived.entries)
|
|
1248
1362
|
}
|
|
1249
|
-
}
|
|
1363
|
+
};
|
|
1364
|
+
if (servedBy.length > 0) this.ctx.logger.info(`virtual-doorbell: press left to ${servedBy.join(", ")} — this wrapper is deactivated on this camera`, options);
|
|
1365
|
+
else 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", options);
|
|
1250
1366
|
return verdictRings(verdict);
|
|
1251
1367
|
}
|
|
1252
1368
|
case "rings-pending":
|
|
1253
|
-
case "rings-unconfirmed":
|
|
1369
|
+
case "rings-unconfirmed":
|
|
1370
|
+
this.ctx.logger.warn(`virtual-doorbell: ringing although ${mirror.servedBy.join(", ")} serves this doorbell — the durable bindings store could not confirm this wrapper was deactivated`, {
|
|
1371
|
+
tags: { deviceId: cameraId },
|
|
1372
|
+
meta: {
|
|
1373
|
+
bindingState: binding ?? "never-read",
|
|
1374
|
+
durableAuthority: authority,
|
|
1375
|
+
ownAddonId: this.ownAddonId
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
return verdictRings(verdict);
|
|
1254
1379
|
}
|
|
1255
1380
|
}
|
|
1256
1381
|
/**
|