@trillboards/edge-sdk 0.2.41 → 0.2.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +18 -0
  2. package/deploy/docker/connector-entrypoint.sh +1 -1
  3. package/dist/CloudSensingManager.d.ts +18 -0
  4. package/dist/CloudSensingManager.d.ts.map +1 -1
  5. package/dist/CloudSensingManager.js +101 -16
  6. package/dist/CloudSensingManager.js.map +1 -1
  7. package/dist/DurableVlmCandidateSpool.d.ts +99 -0
  8. package/dist/DurableVlmCandidateSpool.d.ts.map +1 -0
  9. package/dist/DurableVlmCandidateSpool.js +522 -0
  10. package/dist/DurableVlmCandidateSpool.js.map +1 -0
  11. package/dist/EdgeAgent.js +1 -1
  12. package/dist/LiteRtVlmProvider.d.ts +119 -2
  13. package/dist/LiteRtVlmProvider.d.ts.map +1 -1
  14. package/dist/LiteRtVlmProvider.js +79 -13
  15. package/dist/LiteRtVlmProvider.js.map +1 -1
  16. package/dist/SceneChangeDetector.d.ts +35 -5
  17. package/dist/SceneChangeDetector.d.ts.map +1 -1
  18. package/dist/SceneChangeDetector.js +19 -5
  19. package/dist/SceneChangeDetector.js.map +1 -1
  20. package/dist/VlmAdmissionGate.d.ts +63 -0
  21. package/dist/VlmAdmissionGate.d.ts.map +1 -1
  22. package/dist/VlmAdmissionGate.js +64 -0
  23. package/dist/VlmAdmissionGate.js.map +1 -1
  24. package/dist/VlmCandidateBuffer.d.ts +17 -0
  25. package/dist/VlmCandidateBuffer.d.ts.map +1 -1
  26. package/dist/VlmCandidateBuffer.js +6 -0
  27. package/dist/VlmCandidateBuffer.js.map +1 -1
  28. package/dist/config.d.ts +37 -4
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js.map +1 -1
  31. package/dist/connectorCadence.d.ts +193 -8
  32. package/dist/connectorCadence.d.ts.map +1 -1
  33. package/dist/connectorCadence.js +225 -10
  34. package/dist/connectorCadence.js.map +1 -1
  35. package/dist/deployedCadence.d.ts +18 -5
  36. package/dist/deployedCadence.d.ts.map +1 -1
  37. package/dist/deployedCadence.js +50 -13
  38. package/dist/deployedCadence.js.map +1 -1
  39. package/dist/diagnose.d.ts +1 -1
  40. package/dist/diagnose.js +1 -1
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/localVlmBench.d.ts +36 -0
  46. package/dist/localVlmBench.d.ts.map +1 -1
  47. package/dist/localVlmBench.js +48 -0
  48. package/dist/localVlmBench.js.map +1 -1
  49. package/dist/sensingCatchRate.d.ts +105 -3
  50. package/dist/sensingCatchRate.d.ts.map +1 -1
  51. package/dist/sensingCatchRate.js +98 -10
  52. package/dist/sensingCatchRate.js.map +1 -1
  53. package/dist/sensingRunMode.d.ts.map +1 -1
  54. package/dist/sensingRunMode.js +95 -4
  55. package/dist/sensingRunMode.js.map +1 -1
  56. package/package.json +1 -1
@@ -48,9 +48,18 @@
48
48
  * is both cheaper and far tighter — 6.0-7.5 s — which is a second,
49
49
  * independent reason the two products should not share a cadence.)
50
50
  *
51
- * LOOKING is free and is not the constraint: 3.6 ms median / 7.1 ms p95 to
52
- * pull a frame down to ~178x100 grey and run the motion detector.
53
- * Sixteen feeds looking every 30 s is 0.19% of one core.
51
+ * LOOKING costs almost no CPU — 3.6 ms median / 7.1 ms p95 to pull a frame
52
+ * down to ~178x100 grey and run the motion detector, so sixteen
53
+ * feeds looking every 30 s is 0.19% of one core.
54
+ *
55
+ * CORRECTED 2026-08-18: that is the cost AFTER the frame is in
56
+ * hand, and it is NOT the cost of a look. `buildCaptureArgs` spawns
57
+ * one ffmpeg per grab with `-frames:v 1`, so every look also pays a
58
+ * process start, an RTSP handshake and a wait for the next
59
+ * keyframe. Measured through the shipped provider: p50 1 999 ms at
60
+ * a 2 s keyframe interval, 4 005 ms at 4 s. Looking is CHEAP IN CPU
61
+ * and EXPENSIVE IN WALL CLOCK, and it is the wall clock that bounds
62
+ * the cadence — see MEASURED_LOOK_MS and lookIntervalMsForFeedCount.
54
63
  *
55
64
  * FLOOR on a still scene the motion gate contributes exactly ZERO
56
65
  * analyses — verified across byte-identical frames and gaussian
@@ -73,8 +82,11 @@
73
82
  * would degrade into reporting empty aisles on time while missing the incident.
74
83
  *
75
84
  * Two clamps, both from values the system already declares:
76
- * - never faster than the LOOK interval (`faceTriggeredIntervalMs`, 30 s):
77
- * you cannot analyze more often than you look.
85
+ * - never faster than MIN_ANALYZE_INTERVAL_MS (30 s). This clamp used to read
86
+ * "never faster than the LOOK interval", which was the same 30 s constant;
87
+ * the two split on 2026-08-18 and this one is the ANALYZE floor. You still
88
+ * cannot analyze more often than you look, but the look cadence is now the
89
+ * faster of the two, so it is no longer what binds here.
78
90
  * - never slower than the DOOH default (300 s): this can only make a
79
91
  * connector sample MORE often than it does today, never less.
80
92
  *
@@ -105,10 +117,161 @@ export declare const MEASURED_LOCAL_ANALYZE_MS = 7300;
105
117
  */
106
118
  export declare const STILL_FLOOR_SLOT_SHARE = 0.5;
107
119
  /**
108
- * The connector's look cadence (`faceTriggeredIntervalMs` default, which is
109
- * also the motion gate's scan interval). A heartbeat can never be finer.
120
+ * ── Two cadences, two different machines ───────────────────────────────────
121
+ *
122
+ * After the 2026-08-18 split, the connector's two cadences are bounded by two
123
+ * DIFFERENT pieces of hardware, and that is why they cannot share a constant:
124
+ *
125
+ * ANALYZE is bounded by the BOX. `MEASURED_LOCAL_ANALYZE_MS` is one machine's
126
+ * per-frame inference cost, and it varies by ~7x across the machines
127
+ * this ships on. `periodicIntervalMsForFeedCount` derives the
128
+ * still-scene heartbeat from it, so a slower box automatically gets a
129
+ * slower heartbeat with no other change.
130
+ *
131
+ * LOOK is bounded by the RECORDER. `MEASURED_LOOK_MS` is an ffmpeg spawn
132
+ * plus an RTSP handshake plus a wait for the camera's next keyframe;
133
+ * none of that is inference and none of it moves when the box gets
134
+ * faster. `lookIntervalMsForFeedCount` derives it from the recorder's
135
+ * capture concurrency.
136
+ *
137
+ * So the look cadence below is NOT a function of the box, and does not need a
138
+ * hardware caveat. Re-measure it when the CAPTURE PATH changes — a persistent
139
+ * RTSP session instead of one process per grab would move it by an order of
140
+ * magnitude, and is the only thing that would.
141
+ */
142
+ /**
143
+ * The fastest a connector may ANALYZE one feed, in ms.
144
+ *
145
+ * SEPARATE FROM `LOOK_INTERVAL_MS` since 2026-08-18, and the separation is the
146
+ * whole point. Both were 30 000 and one constant did both jobs, so nothing
147
+ * anywhere distinguished "how often do we look at the scene" from "how often
148
+ * may we spend an inference on it" — and the look cadence could not be lowered
149
+ * without dragging the analyze floor down with it and multiplying VLM volume on
150
+ * a 493/hour ceiling.
151
+ *
152
+ * 30 s is not a fresh judgement; it is the value the system already declares in
153
+ * two independent places, now named once:
154
+ * - `faceTriggeredIntervalMs` (`DEFAULT_FACE_MS`, 30 s) is the default for
155
+ * `MotionGate.minAnalyzeIntervalMs` — the floor between two motion-driven
156
+ * analyses of one feed.
157
+ * - `vision_pacing_config.daily_limit_per_screen` is 2 880 for the connector
158
+ * fleet, i.e. 86 400 / 2 880 = one analysis per 30 s, enforced server-side.
159
+ *
160
+ * So a connector that analyzed faster than this would be throttled by the
161
+ * server anyway. Lowering the LOOK cadence must not move this number, and the
162
+ * `analyzeFloorUnmovedByLookCadence` test asserts exactly that.
163
+ */
164
+ export declare const MIN_ANALYZE_INTERVAL_MS = 30000;
165
+ /**
166
+ * Measured wall-clock cost of ONE LOOK, in ms — a real RTSP grab, not the
167
+ * decode-and-detect that follows it.
168
+ *
169
+ * The cascade budget above quotes 3.6 ms for a look. That is the cost AFTER the
170
+ * frame is in hand: pull it down to ~178x100 grey and run the detector. Getting
171
+ * it in hand is a different order of magnitude, because `buildCaptureArgs`
172
+ * spawns ONE ffmpeg per grab with `-frames:v 1` and no `-ss`, so every look pays
173
+ * a process start, a full RTSP DESCRIBE/SETUP/PLAY handshake, and a wait for the
174
+ * next keyframe before anything can be decoded.
175
+ *
176
+ * MEASURED 2026-08-18 through the shipped `RTSPCameraProvider` against mediamtx
177
+ * re-streaming a real CCTV clip at the documented degraded profile
178
+ * (640x360 / 399 kbps), one feed, no contention, p50 of 8 grabs:
179
+ *
180
+ * keyframe interval p50 grab
181
+ * 1 s 1 986 ms
182
+ * 2 s 1 999 - 2 025 ms
183
+ * 4 s 4 005 ms
184
+ *
185
+ * So the cost is `max(~2 s, keyframe interval)`. The floor is the RTSP SESSION,
186
+ * not the process: the same ffmpeg args against a local file complete in
187
+ * 20-30 ms, so ~99% of a look is DESCRIBE/SETUP/PLAY plus the wait for an IDR.
188
+ * That is a property of pulling one frame per session, not of the machine — a
189
+ * faster box does not move it, and a persistent session would remove almost all
190
+ * of it. 2 s is taken as the floor
191
+ * because it is the fastest observed and because a 1-2 s keyframe interval is
192
+ * the common CCTV default.
193
+ *
194
+ * This is why `lookIntervalMsForFeedCount` exists. Asking for a look cadence
195
+ * finer than the capture path can deliver does not produce more looks — the
196
+ * grabs queue on `CaptureAdmission` and `getSensingHealth()` reports a cadence
197
+ * the connector is not running.
110
198
  */
111
- export declare const LOOK_INTERVAL_MS = 30000;
199
+ export declare const MEASURED_LOOK_MS = 2000;
200
+ /**
201
+ * Concurrent RTSP grabs one physical recorder will take —
202
+ * `DEFAULT_MAX_CONCURRENT_CAPTURES_PER_SOURCE` in `RTSPCameraProvider`, which
203
+ * keys its admission gate on `host:port` because 32 NVR channels are usually
204
+ * one recorder with one session budget.
205
+ *
206
+ * Restated here rather than imported because `edge-sdk` must not depend on a
207
+ * platform package, and asserted against the real constant by
208
+ * `connectorCadence.test.ts` so the two cannot drift.
209
+ *
210
+ * Raising it does NOT buy look throughput: measured at 16 feeds on one recorder,
211
+ * 2 concurrent and 8 concurrent both sustained 1.00 looks/s (the per-grab p50
212
+ * simply rose from 9 962 ms to 9 996 ms). The recorder, not the gate, is the
213
+ * bound.
214
+ */
215
+ export declare const LOOK_CAPTURE_CONCURRENCY = 2;
216
+ /**
217
+ * The look cadence this connector ran until 2026-08-18, kept ONLY as the slow
218
+ * clamp on `lookIntervalMsForFeedCount`.
219
+ *
220
+ * It is numerically equal to `MIN_ANALYZE_INTERVAL_MS` and that is a
221
+ * coincidence of history, not a relationship: they were one constant, and the
222
+ * whole point of the split is that they are free to move independently. Using
223
+ * the analyze floor here would re-create exactly the conflation this change
224
+ * removes — a future PR that lowers the analyze floor would silently speed up
225
+ * the slowest look cadence with it.
226
+ *
227
+ * Its only job is a guarantee: deriving a look cadence can make a connector look
228
+ * MORE often than it used to, never less. A 32-feed host cannot sustain even
229
+ * this (32 feeds want 1.07 looks/s against a measured 1.00), so past ~30 feeds
230
+ * the derivation is pinned here and the grabs queue on `CaptureAdmission` — a
231
+ * pre-existing capacity fact about one recorder, not something a faster look
232
+ * cadence introduces.
233
+ */
234
+ export declare const LEGACY_LOOK_INTERVAL_MS = 30000;
235
+ /**
236
+ * The connector's TARGET look cadence, in ms — how often the motion gate gets a
237
+ * chance to see anything at all.
238
+ *
239
+ * DERIVED FROM THE CATCH-RATE EVAL, not chosen. An event is catchable only if a
240
+ * LOOK lands inside it, so for an event of duration `d` against an
241
+ * event-uncorrelated look cadence `T` the ceiling on catch rate is
242
+ * `E[min(d/T,1)]`. Evaluated over the whole 75,657-event corpus
243
+ * (`eval/sensing-event-corpus.json`), event-weighted per reporting group and
244
+ * UNCAPPED:
245
+ *
246
+ * T Instant Brief Sustained State
247
+ * 30 s 8.4% 18.6% 100% 100%
248
+ * 5 s 50.2% 55.1% 100% 100%
249
+ * 2.44 s 89.1% 71.2% 100% 100%
250
+ * 2 s 95.5% 75.2% 100% 100%
251
+ * 1 s 100.0% 86.6% 100% 100%
252
+ * 0.75 s 100.0% 90.1% 100% 100%
253
+ *
254
+ * Two things fall out of that table and both are load bearing:
255
+ *
256
+ * - The Instant group — "alert me if the door opens after hours", a 2.2 s
257
+ * median event — clears the 90% target at 2.4 s and is at 8.4% today. This
258
+ * is the promise the 30 s cadence was silently failing.
259
+ *
260
+ * - The Brief group does NOT reach 90% until 0.75 s, and 0.75 s is well below
261
+ * the measured cost of a single grab (`MEASURED_LOOK_MS`). So 90% on every
262
+ * shape is NOT reachable by look cadence on the current capture
263
+ * architecture, and this constant does not pretend otherwise. Closing that
264
+ * gap needs a persistent RTSP session — one long-lived ffmpeg per feed
265
+ * instead of one per grab — which is an architecture change, not a number.
266
+ *
267
+ * 2 000 ms is therefore the fastest cadence that is BOTH an eval output and
268
+ * physically deliverable: it is the measured single-grab floor, and at it the
269
+ * Instant group clears the target. Do not lower it without first lowering
270
+ * `MEASURED_LOOK_MS`, and do not raise it without re-running the replay — the
271
+ * `sensing-catch-rate` guard re-reads this constant and fails if it drifts from
272
+ * the cadence the corpus was measured at.
273
+ */
274
+ export declare const LOOK_INTERVAL_MS = 2000;
112
275
  /** The DOOH audience default (`DEFAULT_PERIODIC_MS`). The slow end of the clamp. */
113
276
  export declare const DOOH_PERIODIC_MS = 300000;
114
277
  /**
@@ -122,6 +285,28 @@ export declare const DOOH_PERIODIC_MS = 300000;
122
285
  * the key entirely rather than write a cadence derived from a bad count.
123
286
  */
124
287
  export declare function periodicIntervalMsForFeedCount(feedCount: unknown): number | null;
288
+ /**
289
+ * The fastest look cadence a connector running `feedCount` feeds against ONE
290
+ * recorder can actually SUSTAIN, in ms.
291
+ *
292
+ * Same shape as `periodicIntervalMsForFeedCount` and for the same reason: a
293
+ * cadence is only real if the machine can deliver it. That function divides one
294
+ * inference slot among N feeds; this one divides one recorder's capture
295
+ * capacity among N feeds.
296
+ *
297
+ * N feeds x (1 look / T) <= LOOK_CAPTURE_CONCURRENCY / MEASURED_LOOK_MS
298
+ * T >= N x MEASURED_LOOK_MS / LOOK_CAPTURE_CONCURRENCY
299
+ *
300
+ * Measured against that formula at 16 feeds on one recorder, GOP 2 s: predicted
301
+ * 16.0 s/feed, observed 16.0 s/feed.
302
+ *
303
+ * Never faster than `LOOK_INTERVAL_MS` (the eval target — looking finer than
304
+ * that buys nothing the corpus can see) and never slower than
305
+ * `LEGACY_LOOK_INTERVAL_MS`, so this can only make a connector look MORE often
306
+ * than it did, never less. Returns `null` on an uncountable feed count so a caller can
307
+ * omit the key rather than derive a cadence from nothing.
308
+ */
309
+ export declare function lookIntervalMsForFeedCount(feedCount: unknown): number | null;
125
310
  /**
126
311
  * The still-scene analyses/hour a whole connector demands at a heartbeat, and
127
312
  * what fraction of the single slot that is. Exposed so the derivation is
@@ -1 +1 @@
1
- {"version":3,"file":"connectorCadence.d.ts","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKhF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,EAClB,kBAAkB,EAAE,OAAO,GAC1B;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAOhD"}
1
+ {"version":3,"file":"connectorCadence.d.ts","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CA4BhF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK5E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,EAClB,kBAAkB,EAAE,OAAO,GAC1B;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAOhD"}
@@ -49,9 +49,18 @@
49
49
  * is both cheaper and far tighter — 6.0-7.5 s — which is a second,
50
50
  * independent reason the two products should not share a cadence.)
51
51
  *
52
- * LOOKING is free and is not the constraint: 3.6 ms median / 7.1 ms p95 to
53
- * pull a frame down to ~178x100 grey and run the motion detector.
54
- * Sixteen feeds looking every 30 s is 0.19% of one core.
52
+ * LOOKING costs almost no CPU — 3.6 ms median / 7.1 ms p95 to pull a frame
53
+ * down to ~178x100 grey and run the motion detector, so sixteen
54
+ * feeds looking every 30 s is 0.19% of one core.
55
+ *
56
+ * CORRECTED 2026-08-18: that is the cost AFTER the frame is in
57
+ * hand, and it is NOT the cost of a look. `buildCaptureArgs` spawns
58
+ * one ffmpeg per grab with `-frames:v 1`, so every look also pays a
59
+ * process start, an RTSP handshake and a wait for the next
60
+ * keyframe. Measured through the shipped provider: p50 1 999 ms at
61
+ * a 2 s keyframe interval, 4 005 ms at 4 s. Looking is CHEAP IN CPU
62
+ * and EXPENSIVE IN WALL CLOCK, and it is the wall clock that bounds
63
+ * the cadence — see MEASURED_LOOK_MS and lookIntervalMsForFeedCount.
55
64
  *
56
65
  * FLOOR on a still scene the motion gate contributes exactly ZERO
57
66
  * analyses — verified across byte-identical frames and gaussian
@@ -74,8 +83,11 @@
74
83
  * would degrade into reporting empty aisles on time while missing the incident.
75
84
  *
76
85
  * Two clamps, both from values the system already declares:
77
- * - never faster than the LOOK interval (`faceTriggeredIntervalMs`, 30 s):
78
- * you cannot analyze more often than you look.
86
+ * - never faster than MIN_ANALYZE_INTERVAL_MS (30 s). This clamp used to read
87
+ * "never faster than the LOOK interval", which was the same 30 s constant;
88
+ * the two split on 2026-08-18 and this one is the ANALYZE floor. You still
89
+ * cannot analyze more often than you look, but the look cadence is now the
90
+ * faster of the two, so it is no longer what binds here.
79
91
  * - never slower than the DOOH default (300 s): this can only make a
80
92
  * connector sample MORE often than it does today, never less.
81
93
  *
@@ -95,8 +107,9 @@
95
107
  * what the operator wants; this says what the machine can deliver.
96
108
  */
97
109
  Object.defineProperty(exports, "__esModule", { value: true });
98
- exports.DOOH_PERIODIC_MS = exports.LOOK_INTERVAL_MS = exports.STILL_FLOOR_SLOT_SHARE = exports.MEASURED_LOCAL_ANALYZE_MS = void 0;
110
+ exports.DOOH_PERIODIC_MS = exports.LOOK_INTERVAL_MS = exports.LEGACY_LOOK_INTERVAL_MS = exports.LOOK_CAPTURE_CONCURRENCY = exports.MEASURED_LOOK_MS = exports.MIN_ANALYZE_INTERVAL_MS = exports.STILL_FLOOR_SLOT_SHARE = exports.MEASURED_LOCAL_ANALYZE_MS = void 0;
99
111
  exports.periodicIntervalMsForFeedCount = periodicIntervalMsForFeedCount;
112
+ exports.lookIntervalMsForFeedCount = lookIntervalMsForFeedCount;
100
113
  exports.stillSceneLoad = stillSceneLoad;
101
114
  /**
102
115
  * Measured median seconds per on-device analysis, in ms. See the header for the
@@ -110,10 +123,161 @@ exports.MEASURED_LOCAL_ANALYZE_MS = 7300;
110
123
  */
111
124
  exports.STILL_FLOOR_SLOT_SHARE = 0.5;
112
125
  /**
113
- * The connector's look cadence (`faceTriggeredIntervalMs` default, which is
114
- * also the motion gate's scan interval). A heartbeat can never be finer.
126
+ * ── Two cadences, two different machines ───────────────────────────────────
127
+ *
128
+ * After the 2026-08-18 split, the connector's two cadences are bounded by two
129
+ * DIFFERENT pieces of hardware, and that is why they cannot share a constant:
130
+ *
131
+ * ANALYZE is bounded by the BOX. `MEASURED_LOCAL_ANALYZE_MS` is one machine's
132
+ * per-frame inference cost, and it varies by ~7x across the machines
133
+ * this ships on. `periodicIntervalMsForFeedCount` derives the
134
+ * still-scene heartbeat from it, so a slower box automatically gets a
135
+ * slower heartbeat with no other change.
136
+ *
137
+ * LOOK is bounded by the RECORDER. `MEASURED_LOOK_MS` is an ffmpeg spawn
138
+ * plus an RTSP handshake plus a wait for the camera's next keyframe;
139
+ * none of that is inference and none of it moves when the box gets
140
+ * faster. `lookIntervalMsForFeedCount` derives it from the recorder's
141
+ * capture concurrency.
142
+ *
143
+ * So the look cadence below is NOT a function of the box, and does not need a
144
+ * hardware caveat. Re-measure it when the CAPTURE PATH changes — a persistent
145
+ * RTSP session instead of one process per grab would move it by an order of
146
+ * magnitude, and is the only thing that would.
147
+ */
148
+ /**
149
+ * The fastest a connector may ANALYZE one feed, in ms.
150
+ *
151
+ * SEPARATE FROM `LOOK_INTERVAL_MS` since 2026-08-18, and the separation is the
152
+ * whole point. Both were 30 000 and one constant did both jobs, so nothing
153
+ * anywhere distinguished "how often do we look at the scene" from "how often
154
+ * may we spend an inference on it" — and the look cadence could not be lowered
155
+ * without dragging the analyze floor down with it and multiplying VLM volume on
156
+ * a 493/hour ceiling.
157
+ *
158
+ * 30 s is not a fresh judgement; it is the value the system already declares in
159
+ * two independent places, now named once:
160
+ * - `faceTriggeredIntervalMs` (`DEFAULT_FACE_MS`, 30 s) is the default for
161
+ * `MotionGate.minAnalyzeIntervalMs` — the floor between two motion-driven
162
+ * analyses of one feed.
163
+ * - `vision_pacing_config.daily_limit_per_screen` is 2 880 for the connector
164
+ * fleet, i.e. 86 400 / 2 880 = one analysis per 30 s, enforced server-side.
165
+ *
166
+ * So a connector that analyzed faster than this would be throttled by the
167
+ * server anyway. Lowering the LOOK cadence must not move this number, and the
168
+ * `analyzeFloorUnmovedByLookCadence` test asserts exactly that.
169
+ */
170
+ exports.MIN_ANALYZE_INTERVAL_MS = 30000;
171
+ /**
172
+ * Measured wall-clock cost of ONE LOOK, in ms — a real RTSP grab, not the
173
+ * decode-and-detect that follows it.
174
+ *
175
+ * The cascade budget above quotes 3.6 ms for a look. That is the cost AFTER the
176
+ * frame is in hand: pull it down to ~178x100 grey and run the detector. Getting
177
+ * it in hand is a different order of magnitude, because `buildCaptureArgs`
178
+ * spawns ONE ffmpeg per grab with `-frames:v 1` and no `-ss`, so every look pays
179
+ * a process start, a full RTSP DESCRIBE/SETUP/PLAY handshake, and a wait for the
180
+ * next keyframe before anything can be decoded.
181
+ *
182
+ * MEASURED 2026-08-18 through the shipped `RTSPCameraProvider` against mediamtx
183
+ * re-streaming a real CCTV clip at the documented degraded profile
184
+ * (640x360 / 399 kbps), one feed, no contention, p50 of 8 grabs:
185
+ *
186
+ * keyframe interval p50 grab
187
+ * 1 s 1 986 ms
188
+ * 2 s 1 999 - 2 025 ms
189
+ * 4 s 4 005 ms
190
+ *
191
+ * So the cost is `max(~2 s, keyframe interval)`. The floor is the RTSP SESSION,
192
+ * not the process: the same ffmpeg args against a local file complete in
193
+ * 20-30 ms, so ~99% of a look is DESCRIBE/SETUP/PLAY plus the wait for an IDR.
194
+ * That is a property of pulling one frame per session, not of the machine — a
195
+ * faster box does not move it, and a persistent session would remove almost all
196
+ * of it. 2 s is taken as the floor
197
+ * because it is the fastest observed and because a 1-2 s keyframe interval is
198
+ * the common CCTV default.
199
+ *
200
+ * This is why `lookIntervalMsForFeedCount` exists. Asking for a look cadence
201
+ * finer than the capture path can deliver does not produce more looks — the
202
+ * grabs queue on `CaptureAdmission` and `getSensingHealth()` reports a cadence
203
+ * the connector is not running.
115
204
  */
116
- exports.LOOK_INTERVAL_MS = 30000;
205
+ exports.MEASURED_LOOK_MS = 2000;
206
+ /**
207
+ * Concurrent RTSP grabs one physical recorder will take —
208
+ * `DEFAULT_MAX_CONCURRENT_CAPTURES_PER_SOURCE` in `RTSPCameraProvider`, which
209
+ * keys its admission gate on `host:port` because 32 NVR channels are usually
210
+ * one recorder with one session budget.
211
+ *
212
+ * Restated here rather than imported because `edge-sdk` must not depend on a
213
+ * platform package, and asserted against the real constant by
214
+ * `connectorCadence.test.ts` so the two cannot drift.
215
+ *
216
+ * Raising it does NOT buy look throughput: measured at 16 feeds on one recorder,
217
+ * 2 concurrent and 8 concurrent both sustained 1.00 looks/s (the per-grab p50
218
+ * simply rose from 9 962 ms to 9 996 ms). The recorder, not the gate, is the
219
+ * bound.
220
+ */
221
+ exports.LOOK_CAPTURE_CONCURRENCY = 2;
222
+ /**
223
+ * The look cadence this connector ran until 2026-08-18, kept ONLY as the slow
224
+ * clamp on `lookIntervalMsForFeedCount`.
225
+ *
226
+ * It is numerically equal to `MIN_ANALYZE_INTERVAL_MS` and that is a
227
+ * coincidence of history, not a relationship: they were one constant, and the
228
+ * whole point of the split is that they are free to move independently. Using
229
+ * the analyze floor here would re-create exactly the conflation this change
230
+ * removes — a future PR that lowers the analyze floor would silently speed up
231
+ * the slowest look cadence with it.
232
+ *
233
+ * Its only job is a guarantee: deriving a look cadence can make a connector look
234
+ * MORE often than it used to, never less. A 32-feed host cannot sustain even
235
+ * this (32 feeds want 1.07 looks/s against a measured 1.00), so past ~30 feeds
236
+ * the derivation is pinned here and the grabs queue on `CaptureAdmission` — a
237
+ * pre-existing capacity fact about one recorder, not something a faster look
238
+ * cadence introduces.
239
+ */
240
+ exports.LEGACY_LOOK_INTERVAL_MS = 30000;
241
+ /**
242
+ * The connector's TARGET look cadence, in ms — how often the motion gate gets a
243
+ * chance to see anything at all.
244
+ *
245
+ * DERIVED FROM THE CATCH-RATE EVAL, not chosen. An event is catchable only if a
246
+ * LOOK lands inside it, so for an event of duration `d` against an
247
+ * event-uncorrelated look cadence `T` the ceiling on catch rate is
248
+ * `E[min(d/T,1)]`. Evaluated over the whole 75,657-event corpus
249
+ * (`eval/sensing-event-corpus.json`), event-weighted per reporting group and
250
+ * UNCAPPED:
251
+ *
252
+ * T Instant Brief Sustained State
253
+ * 30 s 8.4% 18.6% 100% 100%
254
+ * 5 s 50.2% 55.1% 100% 100%
255
+ * 2.44 s 89.1% 71.2% 100% 100%
256
+ * 2 s 95.5% 75.2% 100% 100%
257
+ * 1 s 100.0% 86.6% 100% 100%
258
+ * 0.75 s 100.0% 90.1% 100% 100%
259
+ *
260
+ * Two things fall out of that table and both are load bearing:
261
+ *
262
+ * - The Instant group — "alert me if the door opens after hours", a 2.2 s
263
+ * median event — clears the 90% target at 2.4 s and is at 8.4% today. This
264
+ * is the promise the 30 s cadence was silently failing.
265
+ *
266
+ * - The Brief group does NOT reach 90% until 0.75 s, and 0.75 s is well below
267
+ * the measured cost of a single grab (`MEASURED_LOOK_MS`). So 90% on every
268
+ * shape is NOT reachable by look cadence on the current capture
269
+ * architecture, and this constant does not pretend otherwise. Closing that
270
+ * gap needs a persistent RTSP session — one long-lived ffmpeg per feed
271
+ * instead of one per grab — which is an architecture change, not a number.
272
+ *
273
+ * 2 000 ms is therefore the fastest cadence that is BOTH an eval output and
274
+ * physically deliverable: it is the measured single-grab floor, and at it the
275
+ * Instant group clears the target. Do not lower it without first lowering
276
+ * `MEASURED_LOOK_MS`, and do not raise it without re-running the replay — the
277
+ * `sensing-catch-rate` guard re-reads this constant and fails if it drifts from
278
+ * the cadence the corpus was measured at.
279
+ */
280
+ exports.LOOK_INTERVAL_MS = 2000;
117
281
  /** The DOOH audience default (`DEFAULT_PERIODIC_MS`). The slow end of the clamp. */
118
282
  exports.DOOH_PERIODIC_MS = 300000;
119
283
  /**
@@ -131,7 +295,58 @@ function periodicIntervalMsForFeedCount(feedCount) {
131
295
  if (!Number.isFinite(n) || n < 1)
132
296
  return null;
133
297
  const required = Math.ceil((Math.trunc(n) * exports.MEASURED_LOCAL_ANALYZE_MS) / exports.STILL_FLOOR_SLOT_SHARE);
134
- return Math.min(exports.DOOH_PERIODIC_MS, Math.max(exports.LOOK_INTERVAL_MS, required));
298
+ // MEASURED 2026-08-18, and left as it is ON PURPOSE — see below.
299
+ //
300
+ // The `DOOH_PERIODIC_MS` cap stops honouring `STILL_FLOOR_SLOT_SHARE` past
301
+ // ~20 feeds. `required` crosses 300 s at 21 feeds, and from there the clamp
302
+ // returns a heartbeat FASTER than the budget asks for, so the still-scene
303
+ // floor takes more than its declared half of the single slot and keeps
304
+ // taking: 51.1% at 21 feeds, 77.9% at 32 (replay-catch-rate.mjs
305
+ // --sweep-admission, .github/contracts/sensing-admission-sweep.json).
306
+ //
307
+ // Honouring the share above 20 feeds would mean returning 400 s, 600 s, and
308
+ // so on — which is strictly WORSE for the thing the heartbeat exists to do.
309
+ // `absence` catch rate is E[min(d/H,1)]: at 300 s it is 66.3%, and a slower
310
+ // heartbeat drives it down, on the exact class whose whole point is placing a
311
+ // hazard against a 111-second constructive-notice line. So the clamp is not a
312
+ // bug to fix here; it is the honest edge of what ONE 7.30 s slot can serve,
313
+ // and the fix is a second connector host, which `getSensingHealth()` already
314
+ // reports enough to see. Changing this constant would trade a measurable
315
+ // alert-path regression for a cadence number that reads tidier.
316
+ // Floored at the ANALYZE floor, never at the LOOK cadence. Those were the
317
+ // same constant until 2026-08-18; clamping this at the look cadence now would
318
+ // let a faster look cadence pull the still-scene heartbeat down with it and
319
+ // double the analyses a 1-feed connector runs, which is the exact failure the
320
+ // look/analyze split exists to prevent.
321
+ return Math.min(exports.DOOH_PERIODIC_MS, Math.max(exports.MIN_ANALYZE_INTERVAL_MS, required));
322
+ }
323
+ /**
324
+ * The fastest look cadence a connector running `feedCount` feeds against ONE
325
+ * recorder can actually SUSTAIN, in ms.
326
+ *
327
+ * Same shape as `periodicIntervalMsForFeedCount` and for the same reason: a
328
+ * cadence is only real if the machine can deliver it. That function divides one
329
+ * inference slot among N feeds; this one divides one recorder's capture
330
+ * capacity among N feeds.
331
+ *
332
+ * N feeds x (1 look / T) <= LOOK_CAPTURE_CONCURRENCY / MEASURED_LOOK_MS
333
+ * T >= N x MEASURED_LOOK_MS / LOOK_CAPTURE_CONCURRENCY
334
+ *
335
+ * Measured against that formula at 16 feeds on one recorder, GOP 2 s: predicted
336
+ * 16.0 s/feed, observed 16.0 s/feed.
337
+ *
338
+ * Never faster than `LOOK_INTERVAL_MS` (the eval target — looking finer than
339
+ * that buys nothing the corpus can see) and never slower than
340
+ * `LEGACY_LOOK_INTERVAL_MS`, so this can only make a connector look MORE often
341
+ * than it did, never less. Returns `null` on an uncountable feed count so a caller can
342
+ * omit the key rather than derive a cadence from nothing.
343
+ */
344
+ function lookIntervalMsForFeedCount(feedCount) {
345
+ const n = Number(feedCount);
346
+ if (!Number.isFinite(n) || n < 1)
347
+ return null;
348
+ const sustainable = Math.ceil((Math.trunc(n) * exports.MEASURED_LOOK_MS) / exports.LOOK_CAPTURE_CONCURRENCY);
349
+ return Math.min(exports.LEGACY_LOOK_INTERVAL_MS, Math.max(exports.LOOK_INTERVAL_MS, sustainable));
135
350
  }
136
351
  /**
137
352
  * The still-scene analyses/hour a whole connector demands at a heartbeat, and
@@ -1 +1 @@
1
- {"version":3,"file":"connectorCadence.js","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;;;AAkCH,wEAKC;AAQD,wCAUC;AAvDD;;;;GAIG;AACU,QAAA,yBAAyB,GAAG,IAAK,CAAC;AAE/C;;;GAGG;AACU,QAAA,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;GAGG;AACU,QAAA,gBAAgB,GAAG,KAAM,CAAC;AAEvC,oFAAoF;AACvE,QAAA,gBAAgB,GAAG,MAAO,CAAC;AAExC;;;;;;;;;GASG;AACH,SAAgB,8BAA8B,CAAC,SAAkB;IAC/D,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,iCAAyB,CAAC,GAAG,8BAAsB,CAAC,CAAC;IACjG,OAAO,IAAI,CAAC,GAAG,CAAC,wBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,wBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,SAAkB,EAClB,kBAA2B;IAE3B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAC/E,MAAM,eAAe,GAAG,CAAC,CAAC,GAAG,OAAS,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,cAAc,GAAG,OAAS,GAAG,iCAAyB,CAAC;IAC7D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAAG,cAAc,EAAE,CAAC;AAC1E,CAAC"}
1
+ {"version":3,"file":"connectorCadence.js","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;;;AA8LH,wEA4BC;AAuBD,gEAKC;AAQD,wCAUC;AAtQD;;;;GAIG;AACU,QAAA,yBAAyB,GAAG,IAAK,CAAC;AAE/C;;;GAGG;AACU,QAAA,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACU,QAAA,uBAAuB,GAAG,KAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACU,QAAA,gBAAgB,GAAG,IAAK,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACU,QAAA,wBAAwB,GAAG,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,uBAAuB,GAAG,KAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACU,QAAA,gBAAgB,GAAG,IAAK,CAAC;AAEtC,oFAAoF;AACvE,QAAA,gBAAgB,GAAG,MAAO,CAAC;AAExC;;;;;;;;;GASG;AACH,SAAgB,8BAA8B,CAAC,SAAkB;IAC/D,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,iCAAyB,CAAC,GAAG,8BAAsB,CAAC,CAAC;IACjG,iEAAiE;IACjE,EAAE;IACF,2EAA2E;IAC3E,4EAA4E;IAC5E,0EAA0E;IAC1E,uEAAuE;IACvE,gEAAgE;IAChE,sEAAsE;IACtE,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,yEAAyE;IACzE,gEAAgE;IAChE,0EAA0E;IAC1E,8EAA8E;IAC9E,4EAA4E;IAC5E,8EAA8E;IAC9E,wCAAwC;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC,wBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,+BAAuB,EAAE,QAAQ,CAAC,CAAC,CAAC;AACjF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,0BAA0B,CAAC,SAAkB;IAC3D,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,wBAAgB,CAAC,GAAG,gCAAwB,CAAC,CAAC;IAC7F,OAAO,IAAI,CAAC,GAAG,CAAC,+BAAuB,EAAE,IAAI,CAAC,GAAG,CAAC,wBAAgB,EAAE,WAAW,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,SAAkB,EAClB,kBAA2B;IAE3B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAC/E,MAAM,eAAe,GAAG,CAAC,CAAC,GAAG,OAAS,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,cAAc,GAAG,OAAS,GAAG,iCAAyB,CAAC;IAC7D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,eAAe,GAAG,cAAc,EAAE,CAAC;AAC1E,CAAC"}
@@ -93,12 +93,24 @@ export declare const MAX_CADENCE_MS: number;
93
93
  export type CadenceReason =
94
94
  /** No deployed cadence to honour — the caller keeps exactly what it had. */
95
95
  'no_request'
96
+ /**
97
+ * The profile asked for nothing, so the cadence this machine can AFFORD is
98
+ * used instead of whatever default the manager was constructed with.
99
+ */
100
+ | 'derived_default'
96
101
  /** The operator's request was affordable and is being run verbatim. */
97
102
  | 'operator_request'
98
103
  /** Slowed to fit the single local inference slot. */
99
104
  | 'local_inference_budget'
100
- /** Faster than the connector ever looks — a cadence finer than a look is meaningless. */
101
- | 'look_interval'
105
+ /**
106
+ * Faster than the connector may ANALYZE one feed.
107
+ *
108
+ * Named `look_interval` until 2026-08-18, when the look cadence and the
109
+ * analyze floor stopped being the same 30 000 ms constant. The old name now
110
+ * describes the wrong quantity: the connector looks every 2 s and this clamp
111
+ * is about the 30 s floor between two inferences.
112
+ */
113
+ | 'analyze_floor'
102
114
  /** SPED UP to `MAX_CADENCE_MS`: the request was slower than a capture loop can express. */
103
115
  | 'cadence_ceiling';
104
116
  export interface DeployedCadence {
@@ -117,9 +129,10 @@ export interface DeployedCadence {
117
129
  /**
118
130
  * Reconcile a deployed cadence with what this mode can afford.
119
131
  *
120
- * Returns `intervalMs: null` when the profile requested nothing — the caller
121
- * MUST then leave its current cadence alone, so a connector whose server says
122
- * nothing behaves exactly as it does today.
132
+ * When the profile requested nothing, returns the cadence this machine can
133
+ * AFFORD (`reason: 'derived_default'`) rather than null — see the branch for
134
+ * the measurement. A bare null from `deployedCadenceSource` still means "nobody
135
+ * answered", and that still leaves a running connector alone.
123
136
  */
124
137
  export declare function resolveDeployedCadence(input: {
125
138
  requestedMs: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"deployedCadence.d.ts","sourceRoot":"","sources":["../src/deployedCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAQH;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,cAAc,QAAmB,CAAC;AAE/C,MAAM,MAAM,aAAa;AACvB,4EAA4E;AAC1E,YAAY;AACd,uEAAuE;GACrE,kBAAkB;AACpB,qDAAqD;GACnD,wBAAwB;AAC1B,yFAAyF;GACvF,eAAe;AACjB,2FAA2F;GACzF,iBAAiB,CAAC;AAEtB,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mFAAmF;IACnF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AASD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,eAAe,CA8ClB"}
1
+ {"version":3,"file":"deployedCadence.d.ts","sourceRoot":"","sources":["../src/deployedCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAQH;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,cAAc,QAAmB,CAAC;AAE/C,MAAM,MAAM,aAAa;AACvB,4EAA4E;AAC1E,YAAY;AACd;;;GAGG;GACD,iBAAiB;AACnB,uEAAuE;GACrE,kBAAkB;AACpB,qDAAqD;GACnD,wBAAwB;AAC1B;;;;;;;GAOG;GACD,eAAe;AACjB,2FAA2F;GACzF,iBAAiB,CAAC;AAEtB,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mFAAmF;IACnF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AASD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,eAAe,CAkFlB"}