@trillboards/edge-sdk 0.2.78 → 0.2.79
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/CloudSensingManager.d.ts +39 -0
- package/dist/CloudSensingManager.d.ts.map +1 -1
- package/dist/CloudSensingManager.js +88 -0
- package/dist/CloudSensingManager.js.map +1 -1
- package/dist/DetectorShadowProbe.d.ts +381 -0
- package/dist/DetectorShadowProbe.d.ts.map +1 -0
- package/dist/DetectorShadowProbe.js +463 -0
- package/dist/DetectorShadowProbe.js.map +1 -0
- package/dist/DurableVlmCandidateSpool.d.ts.map +1 -1
- package/dist/DurableVlmCandidateSpool.js +2 -0
- package/dist/DurableVlmCandidateSpool.js.map +1 -1
- package/dist/EdgeAgent.js +1 -1
- package/dist/VlmCandidateBuffer.d.ts +13 -0
- package/dist/VlmCandidateBuffer.d.ts.map +1 -1
- package/dist/VlmCandidateBuffer.js +4 -0
- package/dist/VlmCandidateBuffer.js.map +1 -1
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/localSensing.d.ts +11 -0
- package/dist/localSensing.d.ts.map +1 -1
- package/dist/localSensing.js +4 -0
- package/dist/localSensing.js.map +1 -1
- package/dist/sensingRunMode.d.ts.map +1 -1
- package/dist/sensingRunMode.js +24 -0
- package/dist/sensingRunMode.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DetectorShadowProbe — the on-device object detector running in SHADOW MODE.
|
|
3
|
+
*
|
|
4
|
+
* It records the decision the cascade gate WOULD have made and changes nothing.
|
|
5
|
+
* Nothing downstream of it may branch on its answer: not the cadence, not
|
|
6
|
+
* admission, not whether the VLM runs.
|
|
7
|
+
*
|
|
8
|
+
* ── WHY SHADOW FIRST, AND WHY THAT IS NOT A FORMALITY ──────────────────────
|
|
9
|
+
*
|
|
10
|
+
* The detector is a cascade GATE. When it says "nobody here" the VLM call is
|
|
11
|
+
* skipped, and no alert can fire from a frame nobody looked at. The numbers
|
|
12
|
+
* that justify it — ~84.7% of VLM calls removed for ~2.1% of events — were
|
|
13
|
+
* measured on 134 hand-labelled frames drawn from ALERT EPISODES, so the eval
|
|
14
|
+
* set is 64% positive while production is ~84.7% empty
|
|
15
|
+
* (docs/runbooks/cctv-detector-tier.md §1). Those are the right numbers for
|
|
16
|
+
* choosing BETWEEN detectors and the wrong population for sizing a suppressor.
|
|
17
|
+
*
|
|
18
|
+
* So the gate is measured against real fleet traffic before it is allowed to
|
|
19
|
+
* suppress anything. At ~63 ms on top of a ~5,645 ms analyze that measurement
|
|
20
|
+
* costs about 1% — and this class is what keeps it to about 1% when the service
|
|
21
|
+
* is missing, wedged, or answering slowly.
|
|
22
|
+
*
|
|
23
|
+
* ── THREE STATES, NEVER TWO ────────────────────────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* person_present at least one accepted person-class detection
|
|
26
|
+
* empty the service ANSWERED and found nobody — this is a RESULT
|
|
27
|
+
* unavailable transport failed, non-2xx, bad envelope, or nothing installed
|
|
28
|
+
*
|
|
29
|
+
* `unavailable` must never collapse into `empty`. That collapse is the entire
|
|
30
|
+
* failure mode this feature can have: once the gate is armed, a broken detector
|
|
31
|
+
* read as a quiet venue suppresses every VLM call — and therefore every alert —
|
|
32
|
+
* while `postCount`, `sensingState:'active'` and the portal's "AI active" tile
|
|
33
|
+
* all stay green (docs/runbooks/cctv-connector-runtime-edge-cases.md). The
|
|
34
|
+
* distinction is already load-bearing one layer down: `LocalDetectorClient`
|
|
35
|
+
* throws `DetectorUnavailableError` for a service failure and returns an empty
|
|
36
|
+
* `detections` array for an empty frame, and refuses to default a missing
|
|
37
|
+
* `detections` key to `[]`. This class is the consumer that has to honour it.
|
|
38
|
+
*
|
|
39
|
+
* ── WHY THE SERVICE BEING ABSENT MUST COST NOTHING ─────────────────────────
|
|
40
|
+
*
|
|
41
|
+
* The service exists as of edge-sdk 0.2.76 and ships in all three installers,
|
|
42
|
+
* but "the service shipped" and "this box is running it" are different events.
|
|
43
|
+
* A connector below 0.2.76 has no detector at all, and a FRESH 0.2.76 install
|
|
44
|
+
* answers `/health` with **503 by design** until the OTA'd ONNX lands — so for
|
|
45
|
+
* as long as the fleet takes to upgrade, "nothing usable on 9380" is the common
|
|
46
|
+
* case and shadow mode has to be free in it, or it is a fleet-wide latency
|
|
47
|
+
* regression bought with no measurement at all.
|
|
48
|
+
*
|
|
49
|
+
* Two bounds do that, and neither is a retry policy:
|
|
50
|
+
*
|
|
51
|
+
* 1. A HARD DEADLINE. The client is constructed with a short `timeoutMs`
|
|
52
|
+
* (default 1000 ms) rather than its own 5 s default, so a wedged service
|
|
53
|
+
* can never hold a capture tick for five seconds. ECONNREFUSED — the
|
|
54
|
+
* overwhelmingly common case — settles in microseconds and never reaches
|
|
55
|
+
* it.
|
|
56
|
+
* 2. A BACKOFF ON FAILURE, exponential to a ceiling. After a failure the
|
|
57
|
+
* probe stops calling `detect()` entirely and reports `unavailable` at
|
|
58
|
+
* zero cost until the window closes, at which point ONE cheap `/health`
|
|
59
|
+
* GET decides whether to resume. So a box with no detector pays one
|
|
60
|
+
* connection refusal per backoff window, not one per frame.
|
|
61
|
+
*
|
|
62
|
+
* A skipped frame still REPORTS `unavailable`. Reporting nothing would make a
|
|
63
|
+
* fleet with no detector look like a small sample rather than what it is, and
|
|
64
|
+
* the unavailable RATE is one of the three numbers the soak exists to produce.
|
|
65
|
+
*
|
|
66
|
+
* ── WHY IT NEVER THROWS ────────────────────────────────────────────────────
|
|
67
|
+
*
|
|
68
|
+
* It sits inside `CloudSensingManager.lookThenAdmit`, on the path to the only
|
|
69
|
+
* thing that produces alerts. A shadow measurement that can fail a real capture
|
|
70
|
+
* tick is strictly worse than no measurement. Every exit returns a verdict.
|
|
71
|
+
*
|
|
72
|
+
* @reuse-audit
|
|
73
|
+
* considered: LocalDetectorClient (this package)
|
|
74
|
+
* - reused wholesale, unchanged, as the transport and the parser. Its
|
|
75
|
+
* DetectorUnavailableError-vs-empty-array distinction IS this class's
|
|
76
|
+
* three-state vocabulary; re-deriving it here would be a second opinion
|
|
77
|
+
* about the same bytes.
|
|
78
|
+
* considered: LiteRtVlmProvider's accelerator probe
|
|
79
|
+
* - rejected: it caches a CAPABILITY answered once at startup. This caches a
|
|
80
|
+
* LIVENESS verdict that must be able to return to healthy while the
|
|
81
|
+
* process runs — a detector installed by an MDM push at 3 a.m. has to be
|
|
82
|
+
* picked up without a restart, which is the same reason enterpriseTrust
|
|
83
|
+
* drops its cache on a classified failure.
|
|
84
|
+
*/
|
|
85
|
+
import { type DetectResult } from './LocalDetectorClient';
|
|
86
|
+
/** The decision the armed gate WOULD have made about one frame. */
|
|
87
|
+
export type DetectorShadowVerdict = 'person_present' | 'empty' | 'unavailable';
|
|
88
|
+
/**
|
|
89
|
+
* What travels to the server with the frame, and nothing more.
|
|
90
|
+
*
|
|
91
|
+
* Exactly four fields because exactly four columns read them
|
|
92
|
+
* (`vision_call_log.detector_shadow_verdict / _min_confidence / _inference_ms /
|
|
93
|
+
* _wall_ms`).
|
|
94
|
+
* Everything else the probe knows — why a call was unavailable, how many people
|
|
95
|
+
* were found, which artifact answered — stays in `getStats()` for the local
|
|
96
|
+
* health surface. A field on the wire with no column at the other end is the
|
|
97
|
+
* telemetry-producer-substrate defect wearing its other face.
|
|
98
|
+
*/
|
|
99
|
+
export interface DetectorShadowWire {
|
|
100
|
+
verdict: DetectorShadowVerdict;
|
|
101
|
+
/**
|
|
102
|
+
* The confidence threshold this verdict was produced at.
|
|
103
|
+
*
|
|
104
|
+
* Carried per frame, not restated server-side: a fleet mid-upgrade across two
|
|
105
|
+
* connector versions can be running two thresholds at once, and pooling those
|
|
106
|
+
* into one rate averages two different gates with nothing saying so. The soak
|
|
107
|
+
* groups on it and refuses to pool. See DETECTOR_GATE_CONFIDENCE.
|
|
108
|
+
*/
|
|
109
|
+
minConfidence: number;
|
|
110
|
+
/**
|
|
111
|
+
* Wall-clock ms the SERVICE reports it spent on inference, or null when it
|
|
112
|
+
* did not answer. Distinct from `wallMs` on purpose: the difference between
|
|
113
|
+
* them is loopback + base64 + JSON, which is the part a gate would still pay
|
|
114
|
+
* after it stopped paying for the VLM.
|
|
115
|
+
*/
|
|
116
|
+
inferenceMs: number | null;
|
|
117
|
+
/**
|
|
118
|
+
* Wall-clock ms SHADOW MODE cost this capture tick, measured by the caller
|
|
119
|
+
* that paid it. This is the number that says whether the ~1% overhead claim
|
|
120
|
+
* survived contact with real hardware, and it is 0 for a frame skipped under
|
|
121
|
+
* backoff.
|
|
122
|
+
*/
|
|
123
|
+
wallMs: number;
|
|
124
|
+
}
|
|
125
|
+
/** The full local reading, including the parts that do not go on the wire. */
|
|
126
|
+
export interface DetectorShadowObservation extends DetectorShadowWire {
|
|
127
|
+
/** Accepted person-class detections. Null when the service did not answer. */
|
|
128
|
+
personCount: number | null;
|
|
129
|
+
/** Highest accepted confidence, for calibrating a threshold later. */
|
|
130
|
+
topConfidence: number | null;
|
|
131
|
+
/** The artifact that answered, as it names itself. */
|
|
132
|
+
model: string | null;
|
|
133
|
+
/** Why `unavailable`. Null on every other verdict. */
|
|
134
|
+
unavailableReason: string | null;
|
|
135
|
+
}
|
|
136
|
+
export interface DetectorShadowStats {
|
|
137
|
+
enabled: boolean;
|
|
138
|
+
/** Frames the probe was asked about, whatever the outcome. */
|
|
139
|
+
observed: number;
|
|
140
|
+
/**
|
|
141
|
+
* Frames declined by the per-feed cadence bound WITHOUT any detector call.
|
|
142
|
+
*
|
|
143
|
+
* Non-zero means this box is starved: `analyze: true` is arriving faster than
|
|
144
|
+
* the analyze cadence the detector is scored at. See `minIntervalMs`.
|
|
145
|
+
*/
|
|
146
|
+
rateLimited: number;
|
|
147
|
+
personPresent: number;
|
|
148
|
+
empty: number;
|
|
149
|
+
unavailable: number;
|
|
150
|
+
/** Unavailable frames the backoff answered without touching the network. */
|
|
151
|
+
skippedUnderBackoff: number;
|
|
152
|
+
/** ms this probe has cost capture ticks, summed. */
|
|
153
|
+
totalWallMs: number;
|
|
154
|
+
/** Whether the probe is currently in its failure backoff. */
|
|
155
|
+
backingOff: boolean;
|
|
156
|
+
/** Most recent reason a frame came back `unavailable`. */
|
|
157
|
+
lastUnavailableReason: string | null;
|
|
158
|
+
/** The artifact that most recently answered. */
|
|
159
|
+
lastModel: string | null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Labels that count as a person.
|
|
163
|
+
*
|
|
164
|
+
* The same set the offline scoring harness accepts
|
|
165
|
+
* (`trillboard-api/scripts/vision/detector-arm-eval.mjs#PERSON_LABELS`), so a
|
|
166
|
+
* shadow verdict and an eval score mean the same thing by the same rule. Held
|
|
167
|
+
* as LABELS and never as a class index: an eval run once defaulted an absent
|
|
168
|
+
* class to index 0 and scored buses as people for a whole run, and the table
|
|
169
|
+
* looked entirely plausible (cctv-detector-tier.md §5).
|
|
170
|
+
*
|
|
171
|
+
* The SHIPPED artifact emits COCO names, so `person` is the only member it can
|
|
172
|
+
* currently produce (`deploy/detector/trillboards_detector/labels.py`). The
|
|
173
|
+
* other three are kept because they are what the SCORING harness accepts and
|
|
174
|
+
* because none of them is a COCO name — so a future artifact with a different
|
|
175
|
+
* vocabulary is graded the same way, and today's cannot be mis-classified by
|
|
176
|
+
* their presence.
|
|
177
|
+
*/
|
|
178
|
+
export declare const PERSON_LABELS: ReadonlySet<string>;
|
|
179
|
+
/**
|
|
180
|
+
* Ceiling on how long shadow mode may hold a capture tick.
|
|
181
|
+
*
|
|
182
|
+
* NOT the client's own 5 s default. `lookThenAdmit` runs this between the
|
|
183
|
+
* motion verdict and the admission gate, so every ms here is a ms the frame is
|
|
184
|
+
* older when it reaches the model. The detector's measured cost is ~63 ms, so
|
|
185
|
+
* 1000 ms is ~16x headroom for the slowest plausible answer and still ~0.02% of
|
|
186
|
+
* a 5,645 ms analyze in the worst case — and it is paid at most once per
|
|
187
|
+
* backoff window, because a breach opens the backoff.
|
|
188
|
+
*/
|
|
189
|
+
export declare const DEFAULT_SHADOW_TIMEOUT_MS = 1000;
|
|
190
|
+
/** First backoff step after a failure. */
|
|
191
|
+
export declare const DEFAULT_BACKOFF_INITIAL_MS = 30000;
|
|
192
|
+
/**
|
|
193
|
+
* Backoff ceiling.
|
|
194
|
+
*
|
|
195
|
+
* Five minutes rather than "give up": the detector service arrives by MDM push
|
|
196
|
+
* or by an installer run while the connector is already running, so a probe
|
|
197
|
+
* that latched off permanently would need a restart to notice its own product
|
|
198
|
+
* shipping. Same reasoning as the enterprise trust-store cache.
|
|
199
|
+
*/
|
|
200
|
+
export declare const DEFAULT_BACKOFF_MAX_MS = 300000;
|
|
201
|
+
/**
|
|
202
|
+
* Minimum wall time between shadow observations OF ONE FEED. 15 s.
|
|
203
|
+
*
|
|
204
|
+
* ── WHY THIS BOUND EXISTS AND WHY IT IS NOT A TUNING KNOB ──────────────────
|
|
205
|
+
*
|
|
206
|
+
* `cctv-detector-tier.md` §2 is unambiguous: **the detector gates the ANALYZE,
|
|
207
|
+
* not the LOOK.** At the 500 ms look cadence, 6 cameras never clears at p95 on
|
|
208
|
+
* any thread configuration measured; at the analyze cadence nothing binds.
|
|
209
|
+
*
|
|
210
|
+
* The call site — one observation per `analyze: true` verdict — is the right
|
|
211
|
+
* PLACE and, on a starved box, the wrong RATE. `MotionGate.evaluate` resolves
|
|
212
|
+
* `heartbeatDue` before it consults the motion verdict, and `markAnalyzed()`
|
|
213
|
+
* only fires when an inference actually LANDS. So on a connector whose single
|
|
214
|
+
* inference slot is saturated, every feed is permanently heartbeat-due and
|
|
215
|
+
* every look returns `analyze: true`. That is the LOOK cadence: measured on the
|
|
216
|
+
* shipped constants, **64 verdicts/second at 32 feeds** — ~4 CPU-seconds per
|
|
217
|
+
* wall second of detector at ~63 ms each, on the exact box the runbook already
|
|
218
|
+
* describes as starved. Worse, almost all of those frames are dropped at
|
|
219
|
+
* admission and never POST, so the calls buy no measurement at all.
|
|
220
|
+
*
|
|
221
|
+
* 15 s is `MIN_ANALYZE_INTERVAL_MS / 2`. Half, not equal, so it cannot bind on
|
|
222
|
+
* a HEALTHY feed: `MotionGate`'s own `minAnalyzeIntervalMs` floor already keeps
|
|
223
|
+
* `analyze: true` at least 30 s apart there, so every analyzed frame is
|
|
224
|
+
* observed and coverage is 100% — which is the fleet the measurement is for.
|
|
225
|
+
* On a starved feed it caps the cost at 1 call per feed per 15 s (2.13/s at 32
|
|
226
|
+
* feeds, ~13% of one core) and the frames it declines simply carry no verdict:
|
|
227
|
+
* the column's NULL already means "not reported", and the soak counts only
|
|
228
|
+
* non-null rows, so the ratio it reports is unchanged.
|
|
229
|
+
*
|
|
230
|
+
* The sampling is by TIME and never by content, so it cannot move the
|
|
231
|
+
* disagreement or savings rates. What it does cost is COVERAGE on a starved
|
|
232
|
+
* box, and that is the deliberate trade: a bounded loss of sample there against
|
|
233
|
+
* an unbounded CPU regression §2 already measured as not clearing.
|
|
234
|
+
*/
|
|
235
|
+
export declare const DEFAULT_MIN_OBSERVE_INTERVAL_MS = 15000;
|
|
236
|
+
/**
|
|
237
|
+
* The confidence threshold the QUOTED GATE RESULT was produced at. 0.2918.
|
|
238
|
+
*
|
|
239
|
+
* NOT `LocalDetectorClient`'s 0.25 default, and the difference is the whole
|
|
240
|
+
* point of measuring. The committed evaluation
|
|
241
|
+
* (`trillboard-api/scripts/ai/eval-baselines/detector-arm-results-2026-08-23.json`,
|
|
242
|
+
* arm `rfdetr-medium`) carries TWO numbers that are easy to conflate:
|
|
243
|
+
*
|
|
244
|
+
* pooled_at_0.25 precision 98.8 / recall 98.8 — a DETECTOR score
|
|
245
|
+
* cascade_gate 2.1% events lost / 84.7% saved — a GATE score, and its
|
|
246
|
+
* own `at` field says "the TRAIN-selected threshold, applied
|
|
247
|
+
* to the TEST half" = held_out.threshold_chosen_on_train
|
|
248
|
+
* = 0.2918
|
|
249
|
+
*
|
|
250
|
+
* §1 of the runbook is explicit that those are different questions and that the
|
|
251
|
+
* ranking inverts between them. A shadow run at 0.25 would therefore measure a
|
|
252
|
+
* DIFFERENT gate and hand back rates presented as comparable to 2.1%/84.7%,
|
|
253
|
+
* which is the exact "quoted under other conditions" error the shadow phase
|
|
254
|
+
* exists to stop.
|
|
255
|
+
*
|
|
256
|
+
* The value travels ON THE WIRE per frame (`minConfidence` below) rather than
|
|
257
|
+
* being restated server-side, because a fleet is routinely mid-upgrade across
|
|
258
|
+
* two connector versions and a soak that pooled two thresholds would average
|
|
259
|
+
* two different gates into one number with nothing saying so — the
|
|
260
|
+
* `agent_version` trap in docs/runbooks/ctv-agent-version-map.md, one column
|
|
261
|
+
* over.
|
|
262
|
+
*/
|
|
263
|
+
export declare const DETECTOR_GATE_CONFIDENCE = 0.2918;
|
|
264
|
+
/**
|
|
265
|
+
* Ceiling on the WHOLE observation, enforced by this class rather than by the
|
|
266
|
+
* transport. 1500 ms.
|
|
267
|
+
*
|
|
268
|
+
* `LocalDetectorClient`'s `timeoutMs` is an INACTIVITY timeout — its own header
|
|
269
|
+
* says so, and names the case it cannot cover: a server dribbling bytes slowly
|
|
270
|
+
* under a large Content-Length keeps the socket busy, the inactivity timer
|
|
271
|
+
* never fires, and the call is genuinely unbounded. That is tolerable for a
|
|
272
|
+
* client nothing calls on the hot path and NOT tolerable here, because this
|
|
273
|
+
* runs on the path to every admitted analysis — the only path that produces
|
|
274
|
+
* alerts.
|
|
275
|
+
*
|
|
276
|
+
* So the elapsed-time bound lives here, above the transport, where "how long
|
|
277
|
+
* may a shadow measurement hold a capture tick" is actually decided. Slightly
|
|
278
|
+
* above the client's own 1000 ms so the transport still wins the ordinary
|
|
279
|
+
* timeout and reports it as one.
|
|
280
|
+
*/
|
|
281
|
+
export declare const DEFAULT_OBSERVE_DEADLINE_MS = 1500;
|
|
282
|
+
export interface DetectorShadowProbeOptions {
|
|
283
|
+
/** Off entirely. Nothing is called and every observe() returns null. */
|
|
284
|
+
enabled?: boolean;
|
|
285
|
+
/** Detector service base URL. Defaults to the client's loopback default. */
|
|
286
|
+
baseUrl?: string;
|
|
287
|
+
/** Hard per-frame deadline. Default DEFAULT_SHADOW_TIMEOUT_MS. */
|
|
288
|
+
timeoutMs?: number;
|
|
289
|
+
/**
|
|
290
|
+
* Confidence floor applied AT THE SERVICE. Default DETECTOR_GATE_CONFIDENCE
|
|
291
|
+
* (0.2918) — the threshold the quoted gate result was produced at, NOT the
|
|
292
|
+
* client's 0.25 default. See that constant for why the difference matters.
|
|
293
|
+
*/
|
|
294
|
+
minConfidence?: number;
|
|
295
|
+
/**
|
|
296
|
+
* Ceiling on the whole observation. Default DEFAULT_OBSERVE_DEADLINE_MS —
|
|
297
|
+
* the transport's timeout is an INACTIVITY timeout and cannot bound a
|
|
298
|
+
* dribbling response. 0 disables it, which is only correct on a bench rig.
|
|
299
|
+
*/
|
|
300
|
+
deadlineMs?: number;
|
|
301
|
+
backoffInitialMs?: number;
|
|
302
|
+
backoffMaxMs?: number;
|
|
303
|
+
/**
|
|
304
|
+
* Minimum wall time between observations OF ONE FEED. Default 15 s — see
|
|
305
|
+
* DEFAULT_MIN_OBSERVE_INTERVAL_MS for why this is a safety bound rather than
|
|
306
|
+
* a tuning knob. 0 disables it, which is only correct on a bench rig.
|
|
307
|
+
*/
|
|
308
|
+
minIntervalMs?: number;
|
|
309
|
+
/** Clock injection for tests. Default Date.now. */
|
|
310
|
+
now?: () => number;
|
|
311
|
+
}
|
|
312
|
+
/** The slice of LocalDetectorClient this probe uses — injectable for tests. */
|
|
313
|
+
export interface DetectorLike {
|
|
314
|
+
detect(jpeg: Buffer): Promise<DetectResult>;
|
|
315
|
+
isHealthy(): Promise<boolean>;
|
|
316
|
+
}
|
|
317
|
+
export declare class DetectorShadowProbe {
|
|
318
|
+
private readonly client;
|
|
319
|
+
private readonly enabled;
|
|
320
|
+
private readonly backoffInitialMs;
|
|
321
|
+
private readonly backoffMaxMs;
|
|
322
|
+
private readonly minIntervalMs;
|
|
323
|
+
private readonly minConfidence;
|
|
324
|
+
private readonly deadlineMs;
|
|
325
|
+
private readonly now;
|
|
326
|
+
/** Last observation per feed, for the cadence bound. */
|
|
327
|
+
private readonly lastObservedAtMs;
|
|
328
|
+
/** null ⇒ not backing off. Otherwise the clock reading backoff expires at. */
|
|
329
|
+
private backoffUntilMs;
|
|
330
|
+
private backoffStepMs;
|
|
331
|
+
private observed;
|
|
332
|
+
private rateLimited;
|
|
333
|
+
private personPresent;
|
|
334
|
+
private empty;
|
|
335
|
+
private unavailable;
|
|
336
|
+
private skippedUnderBackoff;
|
|
337
|
+
private totalWallMs;
|
|
338
|
+
private lastUnavailableReason;
|
|
339
|
+
private lastModel;
|
|
340
|
+
constructor(options?: DetectorShadowProbeOptions, client?: DetectorLike);
|
|
341
|
+
isEnabled(): boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Record the decision the armed gate WOULD have made about this frame.
|
|
344
|
+
*
|
|
345
|
+
* NEVER THROWS, never returns a promise that rejects, and never returns
|
|
346
|
+
* `empty` for a service that did not answer.
|
|
347
|
+
*
|
|
348
|
+
* Returns null for the two cases that are NOT a verdict — the probe is
|
|
349
|
+
* disabled, or this feed is inside its cadence bound — so a caller can tell
|
|
350
|
+
* "no reading" from "the detector is down". Those must not share a value:
|
|
351
|
+
* `unavailable` is a claim about the detector, and null means nobody asked.
|
|
352
|
+
* The caller sends nothing for a null, and the column's NULL already means
|
|
353
|
+
* "not reported".
|
|
354
|
+
*
|
|
355
|
+
* @param jpeg the exact bytes the VLM is about to analyze
|
|
356
|
+
* @param feedKey which feed this frame came from; the cadence bound is
|
|
357
|
+
* PER FEED, because a busy camera must not spend a quiet camera's budget
|
|
358
|
+
*/
|
|
359
|
+
observe(jpeg: Buffer, feedKey?: string): Promise<DetectorShadowObservation | null>;
|
|
360
|
+
/** The wire slice — exactly the fields a column reads. */
|
|
361
|
+
static toWire(observation: DetectorShadowObservation): DetectorShadowWire;
|
|
362
|
+
getStats(): DetectorShadowStats;
|
|
363
|
+
private recordUnavailable;
|
|
364
|
+
/**
|
|
365
|
+
* Bound a detector call by ELAPSED time, which the transport cannot do.
|
|
366
|
+
*
|
|
367
|
+
* `LocalDetectorClient`'s timeout is an inactivity timer, and its own header
|
|
368
|
+
* names the hole: a server dribbling bytes under a large Content-Length keeps
|
|
369
|
+
* the socket busy forever. On the path to every admitted analysis, that is an
|
|
370
|
+
* unbounded stall on the only path that produces alerts.
|
|
371
|
+
*
|
|
372
|
+
* The loser's rejection is swallowed rather than left to become an unhandled
|
|
373
|
+
* rejection — the underlying request still settles on its own timeout and its
|
|
374
|
+
* result is simply discarded, which is the correct outcome for a measurement
|
|
375
|
+
* whose window has passed.
|
|
376
|
+
*/
|
|
377
|
+
private withDeadline;
|
|
378
|
+
private openBackoff;
|
|
379
|
+
private closeBackoff;
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=DetectorShadowProbe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DetectorShadowProbe.d.ts","sourceRoot":"","sources":["../src/DetectorShadowProbe.ts"],"names":[],"mappings":"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"}
|