@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
|
@@ -4,6 +4,16 @@ import { MigrationRefusedError } from './migration-refused.js';
|
|
|
4
4
|
import { RuntimeStatePolicyLookup } from './runtime-state-persist-gate.js';
|
|
5
5
|
/** A device's row as it may reach disk: cap name → persistable slice (plus `$owned`). */
|
|
6
6
|
export type PersistableRow = Record<string, Record<string, unknown>>;
|
|
7
|
+
/** Which path wrote (or tried to write) a row — named on every line it logs. */
|
|
8
|
+
export type RowWritePath = 'debounced' | 'shutdown-flush' | 'drain' | 'claim' | 'release';
|
|
9
|
+
/**
|
|
10
|
+
* A write-through refused because the device's row could not be read first
|
|
11
|
+
* (D677). Nothing was written; the caller rolls its memory change back.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RuntimeStateRowUnreadError extends Error {
|
|
14
|
+
readonly deviceId: number;
|
|
15
|
+
constructor(deviceId: number);
|
|
16
|
+
}
|
|
7
17
|
/**
|
|
8
18
|
* The drain before a migration could not vouch for a device's row. Nothing has
|
|
9
19
|
* moved yet, so it is a {@link MigrationRefusedError}: the migration refuses
|
|
@@ -25,10 +35,21 @@ export interface MirrorRowWriterDeps {
|
|
|
25
35
|
readonly policyFor: RuntimeStatePolicyLookup;
|
|
26
36
|
/** The device's row as memory holds it NOW — taken when a write RUNS. */
|
|
27
37
|
readonly persistableFor: (deviceId: number) => PersistableRow;
|
|
38
|
+
/** The device's row was read into memory in this process (D677). */
|
|
39
|
+
readonly isRowRead: (deviceId: number) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Read the row into memory — single-flight, `fill` (memory wins per cap).
|
|
42
|
+
* Rejects when the row cannot be read; memory is then unchanged.
|
|
43
|
+
*/
|
|
44
|
+
readonly readRowFirst: (deviceId: number, settings: DeviceManagerSettings) => Promise<void>;
|
|
28
45
|
}
|
|
29
46
|
export declare class MirrorRowWriter {
|
|
30
47
|
private readonly deps;
|
|
31
48
|
static readonly RUNTIME_STATE_DEBOUNCE_MS = 1000;
|
|
49
|
+
/** Ceiling of the re-arm backoff while a device's row cannot be read (D677, D3). */
|
|
50
|
+
static readonly UNREAD_REARM_MAX_MS = 60000;
|
|
51
|
+
/** The re-arm delay after `refusals` consecutive unread refusals: 1 s, 2 s, 4 s … capped at 60 s. */
|
|
52
|
+
static unreadRearmDelayMs(refusals: number): number;
|
|
32
53
|
/**
|
|
33
54
|
* Per-device disk-write debouncer for runtime-state. `setCapSlice`
|
|
34
55
|
* updates the in-memory mirror synchronously and emits the change
|
|
@@ -57,6 +78,8 @@ export declare class MirrorRowWriter {
|
|
|
57
78
|
* that drops work silently reads as "never happened".
|
|
58
79
|
*/
|
|
59
80
|
private readonly skippedSinceWrite;
|
|
81
|
+
/** Devices whose unreadable-row refusal is already logged — once per episode. */
|
|
82
|
+
private readonly rowUnreadLogged;
|
|
60
83
|
constructor(deps: MirrorRowWriterDeps);
|
|
61
84
|
/** What is believed to be on the device's row; undefined before any read or write. */
|
|
62
85
|
persisted(deviceId: number): PersistableRow | undefined;
|
|
@@ -92,8 +115,43 @@ export declare class MirrorRowWriter {
|
|
|
92
115
|
schedule(deviceId: number, settings: DeviceManagerSettings, changedCap: string): void;
|
|
93
116
|
/** Arm the debounce timer; at fire, queue a write of the live blob unless disk already has it. */
|
|
94
117
|
private armDiskWrite;
|
|
95
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Forget a removed device: cancel its armed write, await one in flight, and
|
|
120
|
+
* drop its baseline and counters — nothing writes its row after this.
|
|
121
|
+
*/
|
|
122
|
+
forget(deviceId: number): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* The row is in memory, or is read now (D677). False when the read failed:
|
|
125
|
+
* the caller writes nothing. Logged once per episode, tagged; the episode
|
|
126
|
+
* ends at the next successful read.
|
|
127
|
+
*/
|
|
128
|
+
private rowReadOrRefuse;
|
|
129
|
+
/**
|
|
130
|
+
* The ONE call of `writeDeviceRuntimeState`. A blob that lacks a cap the
|
|
131
|
+
* stored row holds is written (memory is the authority once the row was
|
|
132
|
+
* read) but never silently: the dropped caps are named at warn (D677).
|
|
133
|
+
* `$owned` is excluded — a release that empties it is the claim path's own
|
|
134
|
+
* logged business.
|
|
135
|
+
*/
|
|
136
|
+
private writeRow;
|
|
137
|
+
/**
|
|
138
|
+
* A claim's write-through (D663): queued behind the device's writes, the row
|
|
139
|
+
* read first, the blob taken when it runs. Rejects with
|
|
140
|
+
* {@link RuntimeStateRowUnreadError} when the row cannot be read, or with the
|
|
141
|
+
* write's own error; resolves to what landed.
|
|
142
|
+
*/
|
|
143
|
+
writeThroughRow(deviceId: number, settings: DeviceManagerSettings, path: RowWritePath): Promise<PersistableRow>;
|
|
144
|
+
/**
|
|
145
|
+
* The debounced write itself: the row read first (D677), then the blob as
|
|
146
|
+
* memory holds it NOW, behind the effective-change gate. Never rejects. An
|
|
147
|
+
* unreadable row writes nothing; the debounced path re-arms with a backoff
|
|
148
|
+
* (1 s doubling to 60 s — never a 1 Hz loop, D3), and the armed timer is
|
|
149
|
+
* what a migration drain sees as owed, so a row that has become readable
|
|
150
|
+
* drains normally. The shutdown flush does not re-arm.
|
|
151
|
+
*/
|
|
96
152
|
private writeLiveBlob;
|
|
153
|
+
/** Re-arm an unread refusal with its backoff, or remember it for `release` while held. */
|
|
154
|
+
private rearmAfterUnread;
|
|
97
155
|
/**
|
|
98
156
|
* Hold the debounced writer off these devices' rows while a migration swaps
|
|
99
157
|
* them (D663 re-review N-6). Resolves once each device is DRAINED: a write
|
|
@@ -1,68 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The virtual-doorbell wrapper's in-memory mirror of ONE durable fact per
|
|
3
|
-
* camera: is this wrapper still the active `doorbell` binding for it?
|
|
4
|
-
*
|
|
5
|
-
* WHY A MIRROR AND NOT A READ. A doorbell press is a one-shot event: there
|
|
6
|
-
* is no retry, no reconcile, nothing downstream that can notice it went
|
|
7
|
-
* missing. The wrapper used to re-derive the binding on the ring path via
|
|
8
|
-
* `deviceManager.getBindings` and treat ANY answer that omitted its own
|
|
9
|
-
* activation as "the operator unbound this". On 2026-08-04/05 camera 615
|
|
10
|
-
* rang zero times out of three real presses: the binding was persisted (a
|
|
11
|
-
* same-day backup taken before the presses proves it, and the same process
|
|
12
|
-
* later returned it correctly to a tRPC query), but the store answered
|
|
13
|
-
* transiently without it — `getBindings` reads the device-manager addon
|
|
14
|
-
* store, which is exactly the surface that can silently answer `{}` (D44,
|
|
15
|
-
* `addon-registry.service.ts`). Three presses, three drops, at `debug`.
|
|
16
|
-
*
|
|
17
|
-
* WHY CORROBORATION AND NOT A BETTER DISCRIMINATOR. There isn't one. A
|
|
18
|
-
* genuinely unbound pure-wrapper cap and a store answering from an empty
|
|
19
|
-
* snapshot produce the SAME `getBindings` response — no `doorbell` entry,
|
|
20
|
-
* with the camera's native entries (snapshot, motion, …) still present in
|
|
21
|
-
* both cases because those come from the capability registry, not the
|
|
22
|
-
* store. The response cannot be graded. What CAN be graded is repetition: a
|
|
23
|
-
* real unbind is permanent and every later read agrees, a store blip does
|
|
24
|
-
* not survive the next read. So a negative read ARMS the revocation and a
|
|
25
|
-
* second consecutive negative applies it.
|
|
26
|
-
*
|
|
27
|
-
* ERRING. `undefined` (never read) and `unbind-pending` both ring. The cost
|
|
28
|
-
* of erring open is a spurious ring on a camera whose wrapper is not bound
|
|
29
|
-
* but which has trigger sources, for at most two reload cycles. The cost of
|
|
30
|
-
* erring closed is the press.
|
|
31
|
-
*
|
|
32
|
-
* D655: a configured source is NO LONGER evidence of the binding. D49 leaned
|
|
33
|
-
* on "a source can only be configured through this wrapper's own
|
|
34
|
-
* binding-gated settings surface"; that surface is retired, and the
|
|
35
|
-
* `doorbell-source` extension slot that replaced it is offered on every
|
|
36
|
-
* camera, bound or not (D648). The erring-open window above is therefore
|
|
37
|
-
* reachable by configuring sources on a never-bound camera, not only by
|
|
38
|
-
* unbinding a configured one — still bounded by two reload cycles, and the
|
|
39
|
-
* slot's card says `inactive` once the reads agree.
|
|
40
|
-
*
|
|
41
|
-
* ─────────────────────────────────────────────────────────────────────
|
|
42
|
-
* 2026-08-07 (D72). The corroboration above was DEFEATED, and the reason
|
|
43
|
-
* matters more than the fix. `refreshBindingMirror` matched the binding with
|
|
44
|
-
* `entry.providerAddonId === this.ctx.id`. The two addon-context factories do
|
|
45
|
-
* not spell `ctx.id` the same way: a forked addon gets `virtual-doorbell`, a
|
|
46
|
-
* hub-main BUILTIN gets `addon:virtual-doorbell`. Every entry on the wire
|
|
47
|
-
* carries the bare form. So for this builtin the comparison was false for
|
|
48
|
-
* every entry, forever — not a blip. Corroboration only defends against a
|
|
49
|
-
* TRANSIENT bad read; two systematically-wrong reads agree with each other,
|
|
50
|
-
* and the mirror latched `unbound` with no way back except an operator
|
|
51
|
-
* opening the settings page. A real press on camera 615 was dropped.
|
|
52
|
-
*
|
|
53
|
-
* Two things follow, and both are now load-bearing here:
|
|
54
|
-
*
|
|
55
|
-
* 1. The comparison goes through `isSameAddonId` ({@link entriesBindWrapper}),
|
|
56
|
-
* never a raw `===`.
|
|
57
|
-
* 2. The DERIVED view (`deviceManager.getBindings`) is no longer the sole
|
|
58
|
-
* input. Before a negative is believed — and again on the ring path
|
|
59
|
-
* before a press is destroyed — the DURABLE authority is consulted
|
|
60
|
-
* directly ({@link readDurableWrapperBinding} over the `device-manager`
|
|
61
|
-
* addon store's `deviceBindings` map). That read carries a discriminator
|
|
62
|
-
* the derived view genuinely cannot offer: a store answering from an
|
|
63
|
-
* empty snapshot (D44) has no `deviceBindings` key at all, while a
|
|
64
|
-
* genuine unbind leaves the key present and merely omits the row.
|
|
65
|
-
*/
|
|
1
|
+
import { WrapperActivationRead } from '@camstack/types';
|
|
66
2
|
/**
|
|
67
3
|
* `bound` — a read saw the wrapper's activation.
|
|
68
4
|
* `unbind-pending` — one read did not; NOT yet believed, still rings.
|
|
@@ -75,7 +11,10 @@ export type DoorbellBindingState = 'bound' | 'unbind-pending' | 'unbound';
|
|
|
75
11
|
* and must leave the mirror exactly as it was.
|
|
76
12
|
*/
|
|
77
13
|
export declare function nextBindingState(previous: DoorbellBindingState | undefined, boundNow: boolean): DoorbellBindingState;
|
|
78
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* May a ring proceed on the mirror STATE alone? Only a corroborated unbind
|
|
16
|
+
* stops one. {@link needsDurableRead} adds the served-elsewhere case on top.
|
|
17
|
+
*/
|
|
79
18
|
export declare function allowsRing(state: DoorbellBindingState | undefined): boolean;
|
|
80
19
|
/** True only when a read has positively confirmed the binding. */
|
|
81
20
|
export declare function hasConfirmedBinding(state: DoorbellBindingState | undefined): boolean;
|
|
@@ -108,18 +47,24 @@ export declare function summariseEntries(entries: ReadonlyArray<BindingEntryView
|
|
|
108
47
|
*/
|
|
109
48
|
export type DurableBindingVerdict = 'bound' | 'unbound' | 'unreadable';
|
|
110
49
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* `capName` on `deviceId`".
|
|
50
|
+
* The durable verdict for `ownAddonId`, from device-manager's STORED
|
|
51
|
+
* activation of this cap on this device (`deviceManager.getWrapperActivation`).
|
|
114
52
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
53
|
+
* - `value` naming this wrapper → `bound`.
|
|
54
|
+
* - `value` naming another wrapper, or `null` — the operator's explicit "off"
|
|
55
|
+
* marker (D62) — → `unbound`.
|
|
56
|
+
* - `missing` → `unbound`: the store ANSWERED and holds no activation, and the
|
|
57
|
+
* doorbell cap is `defaultActive: false`, so a wrapper nobody activated is
|
|
58
|
+
* not bound.
|
|
59
|
+
* - `failed` → `unreadable`: it carries no information and destroys nothing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function durableVerdictOf(read: WrapperActivationRead, ownAddonId: string): DurableBindingVerdict;
|
|
62
|
+
/**
|
|
63
|
+
* Who, other than this wrapper, the derived view says serves `capName` —
|
|
64
|
+
* `composer` on a camera whose doorbell was migrated to a composition (D671).
|
|
65
|
+
* Non-empty means a press this wrapper does not ring is still somebody's ring.
|
|
121
66
|
*/
|
|
122
|
-
export declare function
|
|
67
|
+
export declare function otherProvidersOf(entries: ReadonlyArray<BindingEntryView>, capName: string, ownAddonId: string): string[];
|
|
123
68
|
/**
|
|
124
69
|
* What a press on this camera would do right now.
|
|
125
70
|
*
|
|
@@ -132,15 +77,34 @@ export declare function readDurableWrapperBinding(record: unknown, deviceId: num
|
|
|
132
77
|
* store agrees with an unbound mirror: the only verdict that drops a press.
|
|
133
78
|
*/
|
|
134
79
|
export type RingVerdict = 'rings' | 'rings-pending' | 'rings-unconfirmed' | 'unknown' | 'silent';
|
|
135
|
-
/** Does this mirror state need the durable authority before a verdict? Only `unbound` does. */
|
|
136
|
-
export declare function needsDurableRead(state: DoorbellBindingState | undefined): boolean;
|
|
137
80
|
/**
|
|
138
|
-
*
|
|
81
|
+
* What the ring gate and the card know about one camera without I/O: the
|
|
82
|
+
* mirror's state, and which OTHER providers the last successful bindings read
|
|
83
|
+
* said serve `doorbell` there (empty when none, or when no read has answered).
|
|
84
|
+
*/
|
|
85
|
+
export interface DoorbellMirrorRead {
|
|
86
|
+
readonly state: DoorbellBindingState | undefined;
|
|
87
|
+
readonly servedBy: readonly string[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Does this camera need the durable authority before a verdict?
|
|
91
|
+
*
|
|
92
|
+
* An `unbound` mirror does (D72). So does any mirror short of `bound` on a
|
|
93
|
+
* camera whose doorbell another provider serves — the D671 cutover, where
|
|
94
|
+
* erring open means ringing ON TOP of the composer: after every restart the
|
|
95
|
+
* mirror starts `undefined` and spends a reload cycle at `unbind-pending`, and
|
|
96
|
+
* each press in that window was a duplicate ring. Two independent reads
|
|
97
|
+
* agreeing — the derived view naming another provider, the store naming no
|
|
98
|
+
* wrapper — is the D49 "second read agrees"; an unreadable store still rings.
|
|
99
|
+
*/
|
|
100
|
+
export declare function needsDurableRead(mirror: DoorbellMirrorRead): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* THE decision — the ring gate (`fireDoorbell` / `ringsAfterDurableRead`)
|
|
139
103
|
* and the Extensions card's activation both call it, so what the card says
|
|
140
104
|
* and what a press does cannot disagree. `durable` is `null` when it was not
|
|
141
|
-
* read, which {@link needsDurableRead} allows
|
|
105
|
+
* read, which {@link needsDurableRead} allows only where it says so.
|
|
142
106
|
* Pure: it reads nothing and writes nothing.
|
|
143
107
|
*/
|
|
144
|
-
export declare function ringVerdict(
|
|
108
|
+
export declare function ringVerdict(mirror: DoorbellMirrorRead, durable: DurableBindingVerdict | null): RingVerdict;
|
|
145
109
|
/** Whether a press proceeds under this verdict. */
|
|
146
110
|
export declare function verdictRings(verdict: RingVerdict): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExtensionSlotActivation, IDeviceExtensionProvider, IScopedLogger } from '@camstack/types';
|
|
2
|
-
import {
|
|
2
|
+
import { DoorbellMirrorRead, DurableBindingVerdict, RingVerdict } from './binding-mirror.js';
|
|
3
3
|
import { DoorbellSource } from './doorbell-settings.js';
|
|
4
4
|
/** The routing prefix of this owner's `slotRef`s. */
|
|
5
5
|
export declare const VIRTUAL_DOORBELL_EXTENSION_SOURCE = "virtual-doorbell";
|
|
@@ -27,7 +27,7 @@ export interface DoorbellSlotDeps {
|
|
|
27
27
|
/** Several devices in ONE read. Ids no device has are simply absent. */
|
|
28
28
|
readonly listDevices: (deviceIds: readonly number[]) => Promise<readonly DoorbellSlotDevice[]>;
|
|
29
29
|
/** The D49 mirror's verdict for this camera, or undefined when it has none. */
|
|
30
|
-
readonly readBindingMirror: (deviceId: number) =>
|
|
30
|
+
readonly readBindingMirror: (deviceId: number) => DoorbellMirrorRead;
|
|
31
31
|
/**
|
|
32
32
|
* The DURABLE authority, read-only — the same read the ring gate makes
|
|
33
33
|
* before it drops a press. Never throws: a store that cannot answer is
|
|
@@ -47,7 +47,8 @@ export declare function activationFromVerdict(verdict: RingVerdict): ExtensionSl
|
|
|
47
47
|
* never writes it: the mirror gates the ring, and a view that could move it
|
|
48
48
|
* would let a page refetch during a store blip silence a doorbell. The only
|
|
49
49
|
* read the card may make is the durable authority, and only where the ring
|
|
50
|
-
* gate would make it too (`needsDurableRead`: an `unbound` mirror
|
|
50
|
+
* gate would make it too (`needsDurableRead`: an `unbound` mirror, or one
|
|
51
|
+
* short of `bound` whose doorbell another provider serves). A camera
|
|
51
52
|
* the mirror has never seen costs nothing — the ring path errs open there and
|
|
52
53
|
* so does the card.
|
|
53
54
|
*/
|
|
@@ -53,6 +53,24 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
53
53
|
* `binding-mirror.ts` for why a single negative read is not believed.
|
|
54
54
|
*/
|
|
55
55
|
private readonly bindingMirror;
|
|
56
|
+
/**
|
|
57
|
+
* Per camera, the last NEGATIVE refresh this addon reported — its state,
|
|
58
|
+
* the authority's verdict and who else serves the doorbell. A refresh runs
|
|
59
|
+
* every reload cycle (about once a minute); the same answer repeated is not
|
|
60
|
+
* news, so it is reported once per change and forgotten on a positive read.
|
|
61
|
+
* Camera 615 logged the same WARN 607 times in 14 h after D671.
|
|
62
|
+
*/
|
|
63
|
+
private readonly reportedNegative;
|
|
64
|
+
/** Cameras whose last durable-authority read THREW — reported once per outage. */
|
|
65
|
+
private readonly durableReadFailing;
|
|
66
|
+
/**
|
|
67
|
+
* Per camera, the OTHER providers of `doorbell` the last successful derived
|
|
68
|
+
* read named (the composer after D671). Written off the ring path with the
|
|
69
|
+
* mirror; read on it, to decide whether erring open would double a ring.
|
|
70
|
+
*/
|
|
71
|
+
private readonly servedElsewhere;
|
|
72
|
+
/** Cameras whose store-over-derived disagreement has been reported, until a derived read agrees. */
|
|
73
|
+
private readonly reportedStoreOverride;
|
|
56
74
|
constructor();
|
|
57
75
|
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
58
76
|
protected onShutdown(): Promise<void>;
|
|
@@ -76,9 +94,9 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
76
94
|
* (UI toast, notifier rules, exporters) is agnostic to the origin.
|
|
77
95
|
*
|
|
78
96
|
* The binding gate here is a pure in-memory read on the path that RINGS:
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
97
|
+
* a `bound` mirror, or an open one on a camera nobody else serves, performs
|
|
98
|
+
* NO I/O and cannot fail. Every other case asks the durable authority once
|
|
99
|
+
* before deciding — see `needsDurableRead` and `ringsAfterDurableRead`.
|
|
82
100
|
*
|
|
83
101
|
* `sourceDeviceId`/`capName` are the source that produced this rise —
|
|
84
102
|
* carried through purely so the terminal `pressed` line can name it. Every
|
|
@@ -104,8 +122,10 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
104
122
|
*/
|
|
105
123
|
private readDerivedBinding;
|
|
106
124
|
/**
|
|
107
|
-
* One read of the DURABLE authority: the
|
|
108
|
-
*
|
|
125
|
+
* One read of the DURABLE authority: the wrapper activation device-manager
|
|
126
|
+
* STORED for this camera (`deviceManager.getWrapperActivation`, D675) — the
|
|
127
|
+
* row the operator's bindings toggle writes. `deviceManager.getBindings` is
|
|
128
|
+
* a VIEW derived from it plus the capability registry.
|
|
109
129
|
*
|
|
110
130
|
* This is deliberately NOT `getBindings`. The derived view folds the store
|
|
111
131
|
* together with the capability registry, `defaultActive` auto-binding and
|
|
@@ -114,6 +134,9 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
114
134
|
* spelling mismatch) made it disagree *systematically* while looking
|
|
115
135
|
* exactly like a genuine unbind. The raw row cannot: it is what the
|
|
116
136
|
* bindings toggle wrote, and it grades its own trustworthiness (D44).
|
|
137
|
+
*
|
|
138
|
+
* A read that throws is reported when the outage STARTS and when it ends —
|
|
139
|
+
* never per read: this runs on every reload cycle.
|
|
117
140
|
*/
|
|
118
141
|
private readDurableAuthority;
|
|
119
142
|
/**
|
|
@@ -130,20 +153,39 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
130
153
|
* second consecutive one (D49).
|
|
131
154
|
*/
|
|
132
155
|
private refreshBindingMirror;
|
|
156
|
+
/** The ring gate's in-memory input for one camera — no I/O. */
|
|
157
|
+
private mirrorRead;
|
|
158
|
+
/**
|
|
159
|
+
* Report a negative refresh ONCE per change of what it says (D391: a
|
|
160
|
+
* repeated identical line is noise that buries the next real one).
|
|
161
|
+
*
|
|
162
|
+
* The level follows what the answer MEANS:
|
|
163
|
+
* - the durable store could not grade it (`unreadable`) — WARN: the
|
|
164
|
+
* camera's ring now depends on erring open, and nobody can say whether
|
|
165
|
+
* that is right;
|
|
166
|
+
* - the store CONFIRMS the wrapper is not bound — an operator's unbind or
|
|
167
|
+
* the D671 cutover to a composition. That is a state, not an anomaly:
|
|
168
|
+
* INFO when it is believed (`unbound`), DEBUG for the first,
|
|
169
|
+
* not-yet-believed read.
|
|
170
|
+
*/
|
|
171
|
+
private reportNegativeRefresh;
|
|
133
172
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
173
|
+
* A real press arrived on a camera the in-memory gate cannot clear on its
|
|
174
|
+
* own: the mirror is `unbound`, or it is short of `bound` while another
|
|
175
|
+
* provider serves the doorbell. Decide, with ONE read of the durable
|
|
176
|
+
* authority, whether this wrapper rings.
|
|
136
177
|
*
|
|
137
178
|
* D49 forbade I/O on the ring path because the read it would have made was
|
|
138
179
|
* the same fallible derived read that caused the problem. This read is
|
|
139
180
|
* different in both directions: it is the authority rather than a view of
|
|
140
|
-
* it, and it is paid ONLY
|
|
141
|
-
*
|
|
142
|
-
*
|
|
181
|
+
* it, and it is paid ONLY where erring open could be wrong — a dropped ring
|
|
182
|
+
* (an `unbound` mirror) or a DOUBLED one (another provider rings this
|
|
183
|
+
* camera). A ring is rare and one query is cheap. An unreadable store still
|
|
184
|
+
* rings: it carries no information.
|
|
143
185
|
*
|
|
144
186
|
* Returns `true` when the ring must proceed.
|
|
145
187
|
*/
|
|
146
|
-
private
|
|
188
|
+
private ringsAfterDurableRead;
|
|
147
189
|
/**
|
|
148
190
|
* Lazily (re)build the camera→source assignment set from every
|
|
149
191
|
* camera's device store. TTL-bounded so devices/settings appearing
|