@trillboards/edge-sdk 0.2.100 → 0.2.102

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.
@@ -91,10 +91,44 @@ sudo systemctl enable --now trillboards-litertlm \
91
91
  *** 'waiting for the on-device model'. Check: sudo systemctl status trillboards-litertlm"
92
92
 
93
93
  # The verification pass. Idempotent; the serve is up now, so the GPU probe runs.
94
+ VERIFY_OK=1
94
95
  sudo -u trillboards HOME=/opt/trillboards bash \
95
96
  node_modules/@trillboards/edge-sdk/deploy/systemd/setup-litertlm.sh \
96
- || echo " *** Vision-backend verification did not finish. The connector still runs;
97
- *** re-run install.sh to retry."
97
+ || { VERIFY_OK=0; echo " *** Vision-backend verification did not finish. The connector still runs;
98
+ *** re-run install.sh to retry."; }
99
+
100
+ # APPLY WHAT THE VERIFICATION DECIDED. The pass above can DECIDE but it cannot
101
+ # ACT: `write_litertlm_config` succeeds as the `trillboards` user, and the
102
+ # `restart_litertlm_serve` that would make the process re-read it does not —
103
+ # same nologin / no-sudoers reason as the start. Measured on a fresh box, the
104
+ # verification pass logs exactly this and then leaves the running engine on the
105
+ # old backend:
106
+ #
107
+ # WARN: GPU vision encoder did not answer a probe inference - falling back to
108
+ # the CPU encoder
109
+ # WARN: could not restart trillboards-litertlm
110
+ # local AI healthy on the CPU vision encoder (slower, but serving)
111
+ # -> config on disk says CPU, the loaded engine is still GPU, and a real image
112
+ # call still returns 500 Failed to create conversation
113
+ #
114
+ # A config the process never re-read is a decision nobody acted on. So the whole
115
+ # sequence needs THREE root actions around one unprivileged provisioner: START
116
+ # it (so the probe can run), let it VERIFY, then RESTART it (so the verdict is
117
+ # loaded). install.sh holds root at each point; the provisioner holds it at none.
118
+ # ONLY WHEN THE VERIFICATION ACTUALLY SUCCEEDED. If that pass exited nonzero —
119
+ # `uv tool install --force` failing while repairing a drifted install is the
120
+ # realistic case — there is no verdict to apply, and on an UPGRADE the old
121
+ # in-memory engine is still serving. Restarting into a half-installed state
122
+ # turns a degraded-but-serving box into a dead one, which is a worse outcome
123
+ # than the bug this whole change fixes.
124
+ if [ "$VERIFY_OK" -eq 1 ]; then
125
+ sudo systemctl restart trillboards-litertlm \
126
+ || echo " *** Could not restart LiteRT-LM to apply the verified vision backend.
127
+ *** Run: sudo systemctl restart trillboards-litertlm"
128
+ else
129
+ echo " *** Skipping the LiteRT-LM restart: verification did not complete, so there is
130
+ *** no verified backend to apply and the running engine is left as it is."
131
+ fi
98
132
 
99
133
 
100
134
  # The on-device OBJECT DETECTOR cascade gate (RF-DETR, 127.0.0.1:9380). Its own
package/dist/EdgeAgent.js CHANGED
@@ -44,7 +44,7 @@ const edge_ads_1 = require("@trillboards/edge-ads");
44
44
  const CommandRouter_1 = require("./CommandRouter");
45
45
  const config_1 = require("./config");
46
46
  const dataDir_1 = require("./dataDir");
47
- const SDK_VERSION = '0.2.100';
47
+ const SDK_VERSION = '0.2.102';
48
48
  class EdgeAgent extends events_1.EventEmitter {
49
49
  constructor(config, deps) {
50
50
  super();
@@ -90,18 +90,22 @@
90
90
  * fire. A floor that ate the whole budget would starve them, and the connector
91
91
  * would degrade into reporting empty aisles on time while missing the incident.
92
92
  *
93
- * Two clamps, both from values the system already declares:
94
- * - never faster than MIN_ANALYZE_INTERVAL_MS (30 s). This clamp used to read
95
- * "never faster than the LOOK interval", which was the same 30 s constant;
96
- * the two split on 2026-08-18 and this one is the ANALYZE floor. You still
97
- * cannot analyze more often than you look, but the look cadence is now the
98
- * faster of the two, so it is no longer what binds here.
93
+ * Two clamps, and NEITHER is a constant any more:
94
+ * - never faster than `analyzeFloorMsForFeedCount` — the LOOK cadence, because
95
+ * a frame nobody looked at cannot be analyzed. This clamp read
96
+ * `MIN_ANALYZE_INTERVAL_MS` (a flat 30 s) until 2026-08-26; see that
97
+ * constant's obituary above for the two claims that turned out not to hold
98
+ * and for why removing it cannot saturate the admission slot.
99
99
  * - never slower than the DOOH default (300 s): this can only make a
100
100
  * connector sample MORE often than it does today, never less.
101
101
  *
102
- * Result, at the measured 7.30 s: 1-2 feeds 30 s, 4 feeds 59 s, 8 feeds 117 s,
103
- * 16 feeds 234 s, 21+ feeds pinned at 300 s. A deployment of seven or fewer
104
- * feeds per host resolves the 111-second threshold; past that the operator
102
+ * Result, on the 7.30 s BOOTSTRAP: 1 feed 14.6 s, 2 feeds 29.2 s, 4 feeds 59 s,
103
+ * 8 feeds 117 s, 16 feeds 234 s, 21+ feeds pinned at 300 s. The 4-feed row and
104
+ * everything slower are UNCHANGED by the 2026-08-26 removal — the flat floor only
105
+ * ever bound 1 and 2 feeds on the bootstrap. It bound far more on a box that had
106
+ * measured itself, which is the whole point: at the fleet's measured 2.75 s it
107
+ * bound every count up to 5, and at 0.50 s every count up to 30. A deployment of
108
+ * seven or fewer feeds per host resolves the 111-second threshold; past that the operator
105
109
  * needs a second connector host, and `getSensingHealth()` already reports the
106
110
  * honest effective interval so that shows up rather than hiding.
107
111
  *
@@ -252,28 +256,121 @@ export declare const STILL_FLOOR_SLOT_SHARE = 0.5;
252
256
  * magnitude, and is the only thing that would.
253
257
  */
254
258
  /**
255
- * The fastest a connector may ANALYZE one feed, in ms.
256
- *
257
- * SEPARATE FROM `LOOK_INTERVAL_MS` since 2026-08-18, and the separation is the
258
- * whole point. Both were 30 000 and one constant did both jobs, so nothing
259
- * anywhere distinguished "how often do we look at the scene" from "how often
260
- * may we spend an inference on it" — and the look cadence could not be lowered
261
- * without dragging the analyze floor down with it and multiplying VLM volume on
262
- * a 493/hour ceiling.
263
- *
264
- * 30 s is not a fresh judgement; it is the value the system already declares in
265
- * two independent places, now named once:
266
- * - `faceTriggeredIntervalMs` (`DEFAULT_FACE_MS`, 30 s) is the default for
267
- * `MotionGate.minAnalyzeIntervalMs` — the floor between two motion-driven
268
- * analyses of one feed.
269
- * - `vision_pacing_config.daily_limit_per_screen` is 2 880 for the connector
270
- * fleet, i.e. 86 400 / 2 880 = one analysis per 30 s, enforced server-side.
271
- *
272
- * So a connector that analyzed faster than this would be throttled by the
273
- * server anyway. Lowering the LOOK cadence must not move this number, and the
274
- * `analyzeFloorUnmovedByLookCadence` test asserts exactly that.
259
+ * ── THE 30 s ANALYZE FLOOR IS GONE (2026-08-26), AND WHY IT WAS NEVER A BOUND ──
260
+ *
261
+ * `MIN_ANALYZE_INTERVAL_MS = 30_000` used to live here and clamp
262
+ * `periodicIntervalMsForFeedCount` from below. It is deleted rather than
263
+ * lowered, because the two things it claimed to restate do not hold:
264
+ *
265
+ * 1. "`faceTriggeredIntervalMs` (`DEFAULT_FACE_MS`, 30 s) is the default for
266
+ * `MotionGate.minAnalyzeIntervalMs`." TRUE, and irrelevant to THIS
267
+ * number. `MotionGate.evaluate` resolves `heartbeatDue` FIRST and returns
268
+ * before `isRateLimited` is ever consulted (SceneChangeDetector.ts:616 vs
269
+ * :663), and `isRateLimited`'s own docblock says what it gates: "Is a
270
+ * MOTION-triggered analysis throttled right now?". The motion floor bounds
271
+ * the `motion` and `audio_onset` verdicts. It has never bounded the
272
+ * heartbeat. Restating it here made one datum live in two files and then
273
+ * let the copy govern a quantity the original does not.
274
+ *
275
+ * 2. "`vision_pacing_config.daily_limit_per_screen` is 2 880 for the
276
+ * connector fleet ... enforced server-side." FALSE for this connector,
277
+ * three ways over. `checkRateLimit()` has exactly one call site —
278
+ * `audienceVisionService.analyzeFrame`, the CLOUD path. The handler-top
279
+ * capped gate skips local frames by construction
280
+ * (`earner-misc.js`: `if (!hasPreAnalyzed && screenId)`). And
281
+ * `ingestPreAnalyzedFrame`, which is the path every `local-vlm` row is
282
+ * written by, says it in as many words: "we do NOT touch the rate limiter
283
+ * or Vertex client". The connector has been local-inference only since
284
+ * 0.2.41, so the sentence "a connector that analyzed faster than this would
285
+ * be throttled by the server anyway" described a path that no longer runs.
286
+ * (It could not have obeyed one either: there is no 429 handling anywhere
287
+ * in `AudienceAnalyzeClient`.)
288
+ *
289
+ * So the floor protected no resource that `required` does not already protect.
290
+ * What it cost is the product: at the fleet's MEASURED `local-vlm` p50 of
291
+ * 2 750 ms (`vision_call_log`, 2026-08-26) a one-feed connector can afford a
292
+ * 5 500 ms heartbeat and was pinned at 30 000 — 5.45x of hazard-persistence
293
+ * resolution thrown away against the 111-second constructive-notice bracket
294
+ * this whole file exists to resolve. On an M4 Max at 0.50 s/frame it was 30x,
295
+ * and every feed count up to 30 was pinned at the same 30 s.
296
+ *
297
+ * ── WHAT REPLACES IT: the look cadence, which is derived ────────────────────
298
+ *
299
+ * There is exactly one lower bound left that is not the slot budget, and it is
300
+ * a fact rather than a judgement: YOU CANNOT ANALYZE A FRAME YOU NEVER LOOKED
301
+ * AT. That is `lookIntervalMsForFeedCount`, already in this file, already
302
+ * derived per capture path and per feed count. See
303
+ * `analyzeFloorMsForFeedCount`.
304
+ *
305
+ * ── THE FAILURE MODE THIS MUST STILL AVOID, AND WHY IT DOES ────────────────
306
+ *
307
+ * `VlmAdmissionGate` holds ONE slot and DROPS rather than queues. On a box
308
+ * whose slot is saturated every feed is permanently heartbeat-due, so every
309
+ * look returns `analyze: true`, the motion gate stops gating anything, and the
310
+ * frames are dropped at admission having bought no measurement —
311
+ * `DetectorShadowProbe` measured that regime at 64 detector verdicts/second at
312
+ * 32 feeds.
313
+ *
314
+ * The derivation cannot enter it. `required` IS the cadence at which N feeds
315
+ * consume exactly `STILL_FLOOR_SLOT_SHARE` of the one slot, so flooring at
316
+ * `required` targets 50% utilisation by construction and can never saturate.
317
+ * Saturation comes only from a heartbeat set BELOW `required` — which is what a
318
+ * 5 s `sensing_profiles.capture_interval_ms` row asks for, and which the floor
319
+ * still refuses. What changed is only that the refusal is now computed from
320
+ * this machine instead of asserted at 30 s for every machine.
321
+ *
322
+ * Note also what the ceiling is NOT: analyses/hour for a whole connector is
323
+ * `feeds x 3600000 / required` = `3600 x SLOT_SHARE / perFrameSeconds`, which
324
+ * is INDEPENDENT of the feed count — 654/hour on the measured 2.75 s box,
325
+ * 3 600/hour on a 0.50 s one. Removing the clamp cannot produce unbounded load
326
+ * on any box; it can only let a box reach its own honest 50%.
275
327
  */
276
- export declare const MIN_ANALYZE_INTERVAL_MS = 30000;
328
+ /**
329
+ * The floor between two MOTION-triggered analyses of ONE feed, in ms.
330
+ *
331
+ * This is the HALF of the deleted `MIN_ANALYZE_INTERVAL_MS` that was always
332
+ * real, separated out and named for what it is. That constant conflated two
333
+ * quantities — the motion floor and the still-scene heartbeat floor — and the
334
+ * conflation is what let a motion-path number govern a path it has never gated
335
+ * (`MotionGate.evaluate` resolves `heartbeatDue` first and returns before
336
+ * `isRateLimited` is consulted).
337
+ *
338
+ * `CloudSensingManager.DEFAULT_FACE_MS` is the SINGLE HOME of this value: it is
339
+ * what `faceTriggeredIntervalMs` defaults to and what the manager hands
340
+ * `MotionGate` as `minAnalyzeIntervalMs`. Restated here rather than imported so
341
+ * that `connectorCadence` stays a leaf with no manager dependency — the same
342
+ * treatment `LOOK_CAPTURE_CONCURRENCY` and `MEASURED_STREAM_FRAME_INTERVAL_MS`
343
+ * already get — and pinned against the real one by `connectorCadence.test.ts`
344
+ * so the two cannot drift.
345
+ *
346
+ * NOTHING IN THIS FILE'S DERIVATION MAY READ IT. It is exported for consumers
347
+ * that genuinely mean the motion path: the duration contract's `derivedFrom`,
348
+ * and `sense-e2e-soak.js`'s zero-hold rungs, whose six shapes all produce motion
349
+ * and are therefore analyzed on the motion path rather than by the heartbeat. A
350
+ * `periodicIntervalMsForFeedCount` that floored on this again would be the exact
351
+ * hack that came out on 2026-08-26, and the source scan in
352
+ * `connectorCadence.test.ts` fails the build if it does.
353
+ */
354
+ export declare const MOTION_ANALYZE_FLOOR_MS = 30000;
355
+ /**
356
+ * The fastest a connector may ANALYZE one feed, in ms — DERIVED, not declared.
357
+ *
358
+ * A look is what puts a frame in front of the model, so the analyze cadence can
359
+ * never be finer than the look cadence; asking for one produces no extra
360
+ * analyses, just a `getSensingHealth()` that reports a cadence the connector is
361
+ * not running. `lookIntervalMsForFeedCount` already derives that per feed count
362
+ * and per capture path, so this is a rename of an existing quantity rather than
363
+ * a new bound — which is the point. There is no constant here to drift.
364
+ *
365
+ * `captureMode` defaults to `'per_grab'` because that is the SLOWER of the two
366
+ * paths (2 000 ms against a sub-millisecond ring read) and a floor should be
367
+ * conservative when the caller does not know which path a feed is on. A feed
368
+ * reaches per-grab by DEGRADING, so assuming it is the honest default.
369
+ *
370
+ * Returns `null` on an uncountable feed count, exactly as its two siblings do,
371
+ * so a caller can omit the key rather than derive a cadence from nothing.
372
+ */
373
+ export declare function analyzeFloorMsForFeedCount(feedCount: unknown, captureMode?: CaptureMode): number | null;
277
374
  /**
278
375
  * Measured wall-clock cost of ONE LOOK on the PER-GRAB capture path, in ms — a
279
376
  * real RTSP grab, not the decode-and-detect that follows it.
@@ -403,12 +500,12 @@ export declare const LOOK_CAPTURE_CONCURRENCY = 2;
403
500
  * The look cadence this connector ran until 2026-08-18, kept ONLY as the slow
404
501
  * clamp on `lookIntervalMsForFeedCount`.
405
502
  *
406
- * It is numerically equal to `MIN_ANALYZE_INTERVAL_MS` and that is a
503
+ * It is numerically equal to `MOTION_ANALYZE_FLOOR_MS` and that is a
407
504
  * coincidence of history, not a relationship: they were one constant, and the
408
- * whole point of the split is that they are free to move independently. Using
409
- * the analyze floor here would re-create exactly the conflation this change
410
- * removes — a future PR that lowers the analyze floor would silently speed up
411
- * the slowest look cadence with it.
505
+ * whole point of the split is that they are free to move independently. Using a
506
+ * motion-path number here would re-create exactly the conflation that change
507
+ * removes. The 2026-08-26 removal of the flat still-scene floor is the proof it
508
+ * was worth splitting: that number went away entirely and this one did not move.
412
509
  *
413
510
  * Its only job is a guarantee: deriving a look cadence can make a connector look
414
511
  * MORE often than it used to, never less. A 32-feed host cannot sustain even
@@ -471,10 +568,14 @@ export declare const LEGACY_LOOK_INTERVAL_MS = 30000;
471
568
  * failed: one 7.30 s slot is 493 analyses/hour for the whole connector and
472
569
  * there is no cloud path left to absorb an overflow. Three things hold that,
473
570
  * and all three are asserted rather than assumed:
474
- * - `MIN_ANALYZE_INTERVAL_MS` (30 s) is a SEPARATE constant and is what
475
- * `MotionGate.minAnalyzeIntervalMs` floors on.
476
- * - `periodicIntervalMsForFeedCount` floors on the ANALYZE floor, never on
477
- * the look cadence, so the still-scene heartbeat does not move.
571
+ * - the MOTION floor is a SEPARATE quantity (`MOTION_ANALYZE_FLOOR_MS`, 30 s,
572
+ * restating `DEFAULT_FACE_MS`) and is what `MotionGate.minAnalyzeIntervalMs`
573
+ * floors on. It is untouched by anything on the look side.
574
+ * - `periodicIntervalMsForFeedCount` floors on the SLOT BUDGET, and on the
575
+ * look cadence only where the budget is smaller still — which is no feed
576
+ * count on the bootstrap and none on any box measured to date. Since
577
+ * 2026-08-26 the look cadence IS the other term (`analyzeFloorMsForFeedCount`),
578
+ * and it is the honest one: a frame nobody looked at cannot be analyzed.
478
579
  * - a still scene contributes exactly ZERO motion-triggered analyses, which
479
580
  * is what makes the extra looks free at the model.
480
581
  * The `sensing-catch-rate` guard's volume ratchet is the mechanical version:
@@ -509,7 +610,13 @@ export declare function periodicIntervalMsForFeedCount(feedCount: unknown,
509
610
  * path: the equation was always per-machine, and until 2026-08-19 only its
510
611
  * input was global.
511
612
  */
512
- analyzeMs?: number | null): number | null;
613
+ analyzeMs?: number | null,
614
+ /**
615
+ * How this connector's feeds are acquired. Only reaches the LOOK-derived
616
+ * floor (`analyzeFloorMsForFeedCount`), and defaults conservatively — see
617
+ * that function for why `'per_grab'` is the honest default.
618
+ */
619
+ captureMode?: CaptureMode): number | null;
513
620
  /**
514
621
  * How a feed's frames are acquired. The look cadence a connector can SUSTAIN
515
622
  * depends on this and on nothing else about the box.
@@ -1 +1 @@
1
- {"version":3,"file":"connectorCadence.d.ts","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmHG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AAEH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,OAAO;AAClB;;;;;;;;;;GAUG;AACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,GAAG,IAAI,CAkCf;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,EAClB,WAAW,GAAE,WAAwB,GACpC,MAAM,GAAG,IAAI,CA0Bf;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAI1E;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuHG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AAEH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,EAClB,WAAW,GAAE,WAAwB,GACpC,MAAM,GAAG,IAAI,CAEf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,OAAO;AAClB;;;;;;;;;;GAUG;AACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;AACzB;;;;GAIG;AACH,WAAW,GAAE,WAAwB,GACpC,MAAM,GAAG,IAAI,CA+Cf;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,EAClB,WAAW,GAAE,WAAwB,GACpC,MAAM,GAAG,IAAI,CA0Bf;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAI1E;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"}
@@ -91,18 +91,22 @@
91
91
  * fire. A floor that ate the whole budget would starve them, and the connector
92
92
  * would degrade into reporting empty aisles on time while missing the incident.
93
93
  *
94
- * Two clamps, both from values the system already declares:
95
- * - never faster than MIN_ANALYZE_INTERVAL_MS (30 s). This clamp used to read
96
- * "never faster than the LOOK interval", which was the same 30 s constant;
97
- * the two split on 2026-08-18 and this one is the ANALYZE floor. You still
98
- * cannot analyze more often than you look, but the look cadence is now the
99
- * faster of the two, so it is no longer what binds here.
94
+ * Two clamps, and NEITHER is a constant any more:
95
+ * - never faster than `analyzeFloorMsForFeedCount` — the LOOK cadence, because
96
+ * a frame nobody looked at cannot be analyzed. This clamp read
97
+ * `MIN_ANALYZE_INTERVAL_MS` (a flat 30 s) until 2026-08-26; see that
98
+ * constant's obituary above for the two claims that turned out not to hold
99
+ * and for why removing it cannot saturate the admission slot.
100
100
  * - never slower than the DOOH default (300 s): this can only make a
101
101
  * connector sample MORE often than it does today, never less.
102
102
  *
103
- * Result, at the measured 7.30 s: 1-2 feeds 30 s, 4 feeds 59 s, 8 feeds 117 s,
104
- * 16 feeds 234 s, 21+ feeds pinned at 300 s. A deployment of seven or fewer
105
- * feeds per host resolves the 111-second threshold; past that the operator
103
+ * Result, on the 7.30 s BOOTSTRAP: 1 feed 14.6 s, 2 feeds 29.2 s, 4 feeds 59 s,
104
+ * 8 feeds 117 s, 16 feeds 234 s, 21+ feeds pinned at 300 s. The 4-feed row and
105
+ * everything slower are UNCHANGED by the 2026-08-26 removal — the flat floor only
106
+ * ever bound 1 and 2 feeds on the bootstrap. It bound far more on a box that had
107
+ * measured itself, which is the whole point: at the fleet's measured 2.75 s it
108
+ * bound every count up to 5, and at 0.50 s every count up to 30. A deployment of
109
+ * seven or fewer feeds per host resolves the 111-second threshold; past that the operator
106
110
  * needs a second connector host, and `getSensingHealth()` already reports the
107
111
  * honest effective interval so that shows up rather than hiding.
108
112
  *
@@ -116,7 +120,8 @@
116
120
  * what the operator wants; this says what the machine can deliver.
117
121
  */
118
122
  Object.defineProperty(exports, "__esModule", { value: true });
119
- exports.STREAM_LOOK_HEADROOM = exports.DOOH_PERIODIC_MS = exports.LOOK_INTERVAL_MS = exports.LEGACY_LOOK_INTERVAL_MS = exports.LOOK_CAPTURE_CONCURRENCY = exports.MEASURED_STREAM_FRAME_INTERVAL_MS = exports.LOOK_DETECT_CPU_SHARE = exports.MEASURED_LOOK_DETECT_MS = exports.MEASURED_STREAM_LOOK_MS = exports.MEASURED_LOOK_MS = exports.MIN_ANALYZE_INTERVAL_MS = exports.STILL_FLOOR_SLOT_SHARE = exports.MEASURED_LOCAL_ANALYZE_MS = void 0;
123
+ exports.STREAM_LOOK_HEADROOM = exports.DOOH_PERIODIC_MS = exports.LOOK_INTERVAL_MS = exports.LEGACY_LOOK_INTERVAL_MS = exports.LOOK_CAPTURE_CONCURRENCY = exports.MEASURED_STREAM_FRAME_INTERVAL_MS = exports.LOOK_DETECT_CPU_SHARE = exports.MEASURED_LOOK_DETECT_MS = exports.MEASURED_STREAM_LOOK_MS = exports.MEASURED_LOOK_MS = exports.MOTION_ANALYZE_FLOOR_MS = exports.STILL_FLOOR_SLOT_SHARE = exports.MEASURED_LOCAL_ANALYZE_MS = void 0;
124
+ exports.analyzeFloorMsForFeedCount = analyzeFloorMsForFeedCount;
120
125
  exports.periodicIntervalMsForFeedCount = periodicIntervalMsForFeedCount;
121
126
  exports.lookIntervalMsForFeedCount = lookIntervalMsForFeedCount;
122
127
  exports.streamFpsForLookIntervalMs = streamFpsForLookIntervalMs;
@@ -259,28 +264,123 @@ exports.STILL_FLOOR_SLOT_SHARE = 0.5;
259
264
  * magnitude, and is the only thing that would.
260
265
  */
261
266
  /**
262
- * The fastest a connector may ANALYZE one feed, in ms.
263
- *
264
- * SEPARATE FROM `LOOK_INTERVAL_MS` since 2026-08-18, and the separation is the
265
- * whole point. Both were 30 000 and one constant did both jobs, so nothing
266
- * anywhere distinguished "how often do we look at the scene" from "how often
267
- * may we spend an inference on it" — and the look cadence could not be lowered
268
- * without dragging the analyze floor down with it and multiplying VLM volume on
269
- * a 493/hour ceiling.
270
- *
271
- * 30 s is not a fresh judgement; it is the value the system already declares in
272
- * two independent places, now named once:
273
- * - `faceTriggeredIntervalMs` (`DEFAULT_FACE_MS`, 30 s) is the default for
274
- * `MotionGate.minAnalyzeIntervalMs` — the floor between two motion-driven
275
- * analyses of one feed.
276
- * - `vision_pacing_config.daily_limit_per_screen` is 2 880 for the connector
277
- * fleet, i.e. 86 400 / 2 880 = one analysis per 30 s, enforced server-side.
278
- *
279
- * So a connector that analyzed faster than this would be throttled by the
280
- * server anyway. Lowering the LOOK cadence must not move this number, and the
281
- * `analyzeFloorUnmovedByLookCadence` test asserts exactly that.
267
+ * ── THE 30 s ANALYZE FLOOR IS GONE (2026-08-26), AND WHY IT WAS NEVER A BOUND ──
268
+ *
269
+ * `MIN_ANALYZE_INTERVAL_MS = 30_000` used to live here and clamp
270
+ * `periodicIntervalMsForFeedCount` from below. It is deleted rather than
271
+ * lowered, because the two things it claimed to restate do not hold:
272
+ *
273
+ * 1. "`faceTriggeredIntervalMs` (`DEFAULT_FACE_MS`, 30 s) is the default for
274
+ * `MotionGate.minAnalyzeIntervalMs`." TRUE, and irrelevant to THIS
275
+ * number. `MotionGate.evaluate` resolves `heartbeatDue` FIRST and returns
276
+ * before `isRateLimited` is ever consulted (SceneChangeDetector.ts:616 vs
277
+ * :663), and `isRateLimited`'s own docblock says what it gates: "Is a
278
+ * MOTION-triggered analysis throttled right now?". The motion floor bounds
279
+ * the `motion` and `audio_onset` verdicts. It has never bounded the
280
+ * heartbeat. Restating it here made one datum live in two files and then
281
+ * let the copy govern a quantity the original does not.
282
+ *
283
+ * 2. "`vision_pacing_config.daily_limit_per_screen` is 2 880 for the
284
+ * connector fleet ... enforced server-side." FALSE for this connector,
285
+ * three ways over. `checkRateLimit()` has exactly one call site —
286
+ * `audienceVisionService.analyzeFrame`, the CLOUD path. The handler-top
287
+ * capped gate skips local frames by construction
288
+ * (`earner-misc.js`: `if (!hasPreAnalyzed && screenId)`). And
289
+ * `ingestPreAnalyzedFrame`, which is the path every `local-vlm` row is
290
+ * written by, says it in as many words: "we do NOT touch the rate limiter
291
+ * or Vertex client". The connector has been local-inference only since
292
+ * 0.2.41, so the sentence "a connector that analyzed faster than this would
293
+ * be throttled by the server anyway" described a path that no longer runs.
294
+ * (It could not have obeyed one either: there is no 429 handling anywhere
295
+ * in `AudienceAnalyzeClient`.)
296
+ *
297
+ * So the floor protected no resource that `required` does not already protect.
298
+ * What it cost is the product: at the fleet's MEASURED `local-vlm` p50 of
299
+ * 2 750 ms (`vision_call_log`, 2026-08-26) a one-feed connector can afford a
300
+ * 5 500 ms heartbeat and was pinned at 30 000 — 5.45x of hazard-persistence
301
+ * resolution thrown away against the 111-second constructive-notice bracket
302
+ * this whole file exists to resolve. On an M4 Max at 0.50 s/frame it was 30x,
303
+ * and every feed count up to 30 was pinned at the same 30 s.
304
+ *
305
+ * ── WHAT REPLACES IT: the look cadence, which is derived ────────────────────
306
+ *
307
+ * There is exactly one lower bound left that is not the slot budget, and it is
308
+ * a fact rather than a judgement: YOU CANNOT ANALYZE A FRAME YOU NEVER LOOKED
309
+ * AT. That is `lookIntervalMsForFeedCount`, already in this file, already
310
+ * derived per capture path and per feed count. See
311
+ * `analyzeFloorMsForFeedCount`.
312
+ *
313
+ * ── THE FAILURE MODE THIS MUST STILL AVOID, AND WHY IT DOES ────────────────
314
+ *
315
+ * `VlmAdmissionGate` holds ONE slot and DROPS rather than queues. On a box
316
+ * whose slot is saturated every feed is permanently heartbeat-due, so every
317
+ * look returns `analyze: true`, the motion gate stops gating anything, and the
318
+ * frames are dropped at admission having bought no measurement —
319
+ * `DetectorShadowProbe` measured that regime at 64 detector verdicts/second at
320
+ * 32 feeds.
321
+ *
322
+ * The derivation cannot enter it. `required` IS the cadence at which N feeds
323
+ * consume exactly `STILL_FLOOR_SLOT_SHARE` of the one slot, so flooring at
324
+ * `required` targets 50% utilisation by construction and can never saturate.
325
+ * Saturation comes only from a heartbeat set BELOW `required` — which is what a
326
+ * 5 s `sensing_profiles.capture_interval_ms` row asks for, and which the floor
327
+ * still refuses. What changed is only that the refusal is now computed from
328
+ * this machine instead of asserted at 30 s for every machine.
329
+ *
330
+ * Note also what the ceiling is NOT: analyses/hour for a whole connector is
331
+ * `feeds x 3600000 / required` = `3600 x SLOT_SHARE / perFrameSeconds`, which
332
+ * is INDEPENDENT of the feed count — 654/hour on the measured 2.75 s box,
333
+ * 3 600/hour on a 0.50 s one. Removing the clamp cannot produce unbounded load
334
+ * on any box; it can only let a box reach its own honest 50%.
282
335
  */
283
- exports.MIN_ANALYZE_INTERVAL_MS = 30000;
336
+ /**
337
+ * The floor between two MOTION-triggered analyses of ONE feed, in ms.
338
+ *
339
+ * This is the HALF of the deleted `MIN_ANALYZE_INTERVAL_MS` that was always
340
+ * real, separated out and named for what it is. That constant conflated two
341
+ * quantities — the motion floor and the still-scene heartbeat floor — and the
342
+ * conflation is what let a motion-path number govern a path it has never gated
343
+ * (`MotionGate.evaluate` resolves `heartbeatDue` first and returns before
344
+ * `isRateLimited` is consulted).
345
+ *
346
+ * `CloudSensingManager.DEFAULT_FACE_MS` is the SINGLE HOME of this value: it is
347
+ * what `faceTriggeredIntervalMs` defaults to and what the manager hands
348
+ * `MotionGate` as `minAnalyzeIntervalMs`. Restated here rather than imported so
349
+ * that `connectorCadence` stays a leaf with no manager dependency — the same
350
+ * treatment `LOOK_CAPTURE_CONCURRENCY` and `MEASURED_STREAM_FRAME_INTERVAL_MS`
351
+ * already get — and pinned against the real one by `connectorCadence.test.ts`
352
+ * so the two cannot drift.
353
+ *
354
+ * NOTHING IN THIS FILE'S DERIVATION MAY READ IT. It is exported for consumers
355
+ * that genuinely mean the motion path: the duration contract's `derivedFrom`,
356
+ * and `sense-e2e-soak.js`'s zero-hold rungs, whose six shapes all produce motion
357
+ * and are therefore analyzed on the motion path rather than by the heartbeat. A
358
+ * `periodicIntervalMsForFeedCount` that floored on this again would be the exact
359
+ * hack that came out on 2026-08-26, and the source scan in
360
+ * `connectorCadence.test.ts` fails the build if it does.
361
+ */
362
+ exports.MOTION_ANALYZE_FLOOR_MS = 30000;
363
+ /**
364
+ * The fastest a connector may ANALYZE one feed, in ms — DERIVED, not declared.
365
+ *
366
+ * A look is what puts a frame in front of the model, so the analyze cadence can
367
+ * never be finer than the look cadence; asking for one produces no extra
368
+ * analyses, just a `getSensingHealth()` that reports a cadence the connector is
369
+ * not running. `lookIntervalMsForFeedCount` already derives that per feed count
370
+ * and per capture path, so this is a rename of an existing quantity rather than
371
+ * a new bound — which is the point. There is no constant here to drift.
372
+ *
373
+ * `captureMode` defaults to `'per_grab'` because that is the SLOWER of the two
374
+ * paths (2 000 ms against a sub-millisecond ring read) and a floor should be
375
+ * conservative when the caller does not know which path a feed is on. A feed
376
+ * reaches per-grab by DEGRADING, so assuming it is the honest default.
377
+ *
378
+ * Returns `null` on an uncountable feed count, exactly as its two siblings do,
379
+ * so a caller can omit the key rather than derive a cadence from nothing.
380
+ */
381
+ function analyzeFloorMsForFeedCount(feedCount, captureMode = 'per_grab') {
382
+ return lookIntervalMsForFeedCount(feedCount, captureMode);
383
+ }
284
384
  /**
285
385
  * Measured wall-clock cost of ONE LOOK on the PER-GRAB capture path, in ms — a
286
386
  * real RTSP grab, not the decode-and-detect that follows it.
@@ -410,12 +510,12 @@ exports.LOOK_CAPTURE_CONCURRENCY = 2;
410
510
  * The look cadence this connector ran until 2026-08-18, kept ONLY as the slow
411
511
  * clamp on `lookIntervalMsForFeedCount`.
412
512
  *
413
- * It is numerically equal to `MIN_ANALYZE_INTERVAL_MS` and that is a
513
+ * It is numerically equal to `MOTION_ANALYZE_FLOOR_MS` and that is a
414
514
  * coincidence of history, not a relationship: they were one constant, and the
415
- * whole point of the split is that they are free to move independently. Using
416
- * the analyze floor here would re-create exactly the conflation this change
417
- * removes — a future PR that lowers the analyze floor would silently speed up
418
- * the slowest look cadence with it.
515
+ * whole point of the split is that they are free to move independently. Using a
516
+ * motion-path number here would re-create exactly the conflation that change
517
+ * removes. The 2026-08-26 removal of the flat still-scene floor is the proof it
518
+ * was worth splitting: that number went away entirely and this one did not move.
419
519
  *
420
520
  * Its only job is a guarantee: deriving a look cadence can make a connector look
421
521
  * MORE often than it used to, never less. A 32-feed host cannot sustain even
@@ -478,10 +578,14 @@ exports.LEGACY_LOOK_INTERVAL_MS = 30000;
478
578
  * failed: one 7.30 s slot is 493 analyses/hour for the whole connector and
479
579
  * there is no cloud path left to absorb an overflow. Three things hold that,
480
580
  * and all three are asserted rather than assumed:
481
- * - `MIN_ANALYZE_INTERVAL_MS` (30 s) is a SEPARATE constant and is what
482
- * `MotionGate.minAnalyzeIntervalMs` floors on.
483
- * - `periodicIntervalMsForFeedCount` floors on the ANALYZE floor, never on
484
- * the look cadence, so the still-scene heartbeat does not move.
581
+ * - the MOTION floor is a SEPARATE quantity (`MOTION_ANALYZE_FLOOR_MS`, 30 s,
582
+ * restating `DEFAULT_FACE_MS`) and is what `MotionGate.minAnalyzeIntervalMs`
583
+ * floors on. It is untouched by anything on the look side.
584
+ * - `periodicIntervalMsForFeedCount` floors on the SLOT BUDGET, and on the
585
+ * look cadence only where the budget is smaller still — which is no feed
586
+ * count on the bootstrap and none on any box measured to date. Since
587
+ * 2026-08-26 the look cadence IS the other term (`analyzeFloorMsForFeedCount`),
588
+ * and it is the honest one: a frame nobody looked at cannot be analyzed.
485
589
  * - a still scene contributes exactly ZERO motion-triggered analyses, which
486
590
  * is what makes the extra looks free at the model.
487
591
  * The `sensing-catch-rate` guard's volume ratchet is the mechanical version:
@@ -516,7 +620,13 @@ function periodicIntervalMsForFeedCount(feedCount,
516
620
  * path: the equation was always per-machine, and until 2026-08-19 only its
517
621
  * input was global.
518
622
  */
519
- analyzeMs) {
623
+ analyzeMs,
624
+ /**
625
+ * How this connector's feeds are acquired. Only reaches the LOOK-derived
626
+ * floor (`analyzeFloorMsForFeedCount`), and defaults conservatively — see
627
+ * that function for why `'per_grab'` is the honest default.
628
+ */
629
+ captureMode = 'per_grab') {
520
630
  const n = Number(feedCount);
521
631
  if (!Number.isFinite(n) || n < 1)
522
632
  return null;
@@ -545,12 +655,25 @@ analyzeMs) {
545
655
  // and the fix is a second connector host, which `getSensingHealth()` already
546
656
  // reports enough to see. Changing this constant would trade a measurable
547
657
  // alert-path regression for a cadence number that reads tidier.
548
- // Floored at the ANALYZE floor, never at the LOOK cadence. Those were the
549
- // same constant until 2026-08-18; clamping this at the look cadence now would
550
- // let a faster look cadence pull the still-scene heartbeat down with it and
551
- // double the analyses a 1-feed connector runs, which is the exact failure the
552
- // look/analyze split exists to prevent.
553
- return Math.min(exports.DOOH_PERIODIC_MS, Math.max(exports.MIN_ANALYZE_INTERVAL_MS, required));
658
+ // Floored at the LOOK cadence, which is the only lower bound left that is not
659
+ // the slot budget itself, and is a fact rather than a judgement: a frame
660
+ // nobody looked at cannot be analyzed.
661
+ //
662
+ // This read a flat `MIN_ANALYZE_INTERVAL_MS` (30 s) until 2026-08-26. The
663
+ // 2026-08-18 note that used to sit here warned that flooring on the look
664
+ // cadence would "let a faster look cadence pull the still-scene heartbeat down
665
+ // with it and double the analyses a 1-feed connector runs". That is exactly
666
+ // what this now does, deliberately, and the warning's arithmetic is what makes
667
+ // it safe rather than what makes it dangerous: the heartbeat lands at
668
+ // `required` — 50% of the one slot BY CONSTRUCTION — for every box on which
669
+ // `required` exceeds the look cadence, which is every box measured to date at
670
+ // every feed count. The look floor binds only where `required` is smaller
671
+ // still, i.e. on a machine fast enough that the CAPTURE path, not the model,
672
+ // is the scarce thing. Doubling a 1-feed connector's analyses is the product
673
+ // working: it is 5.45x of hazard-persistence resolution at the fleet's
674
+ // measured per-frame cost, against a 111-second bracket.
675
+ const lookFloor = analyzeFloorMsForFeedCount(n, captureMode) ?? 0;
676
+ return Math.min(exports.DOOH_PERIODIC_MS, Math.max(lookFloor, required));
554
677
  }
555
678
  /**
556
679
  * The fastest look cadence a connector running `feedCount` feeds can actually
@@ -1 +1 @@
1
- {"version":3,"file":"connectorCadence.js","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectorCadence.js","sourceRoot":"","sources":["../src/connectorCadence.ts"],"names":[],"mappings":"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"}
@@ -65,6 +65,7 @@
65
65
  *
66
66
  * It is a 32-bit `setTimeout` overflow guard. See its own docs below.
67
67
  */
68
+ import { type CaptureMode } from './connectorCadence';
68
69
  /**
69
70
  * Kept as a single-member type rather than deleted: it names the axis (WHICH
70
71
  * run mode is being metered) that the callers still pass, and re-introducing a
@@ -110,12 +111,16 @@ export type CadenceReason =
110
111
  /** Slowed to fit the single local inference slot. */
111
112
  | 'local_inference_budget'
112
113
  /**
113
- * Faster than the connector may ANALYZE one feed.
114
+ * Faster than the connector may ANALYZE one feed, because it is faster than
115
+ * the connector LOOKS at one — a frame nobody looked at cannot be analyzed.
114
116
  *
115
117
  * Named `look_interval` until 2026-08-18, when the look cadence and the
116
- * analyze floor stopped being the same 30 000 ms constant. The old name now
117
- * describes the wrong quantity: the connector looks every 2 s and this clamp
118
- * is about the 30 s floor between two inferences.
118
+ * analyze floor stopped being the same 30 000 ms constant, and pointed at a
119
+ * flat `MIN_ANALYZE_INTERVAL_MS = 30_000` until 2026-08-26, when that constant
120
+ * was deleted for restating a motion-gate floor that never bounded the
121
+ * heartbeat. It now means what its 2018-era name meant again: the capture path,
122
+ * not the model, is the scarce thing on this box. That is rare and it is
123
+ * REPORTED rather than assumed — `local_inference_budget` is the ordinary case.
119
124
  */
120
125
  | 'analyze_floor'
121
126
  /** SPED UP to `MAX_CADENCE_MS`: the request was slower than a capture loop can express. */
@@ -150,5 +155,11 @@ export declare function resolveDeployedCadence(input: {
150
155
  * while it is still on the bootstrap constant. See `AnalyzeCostObserver`.
151
156
  */
152
157
  analyzeMs?: number | null;
158
+ /**
159
+ * How this connector's feeds are acquired, when the caller knows. Reaches
160
+ * only the LOOK-derived floor; omitted falls back to the conservative
161
+ * `'per_grab'` — see `analyzeFloorMsForFeedCount`.
162
+ */
163
+ captureMode?: CaptureMode;
153
164
  }): DeployedCadence;
154
165
  //# sourceMappingURL=deployedCadence.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployedCadence.d.ts","sourceRoot":"","sources":["../src/deployedCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;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;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,eAAe,CAoFlB"}
1
+ {"version":3,"file":"deployedCadence.d.ts","sourceRoot":"","sources":["../src/deployedCadence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAE5B;;;;;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;;;;;;;;;;;GAWG;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;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,GAAG,eAAe,CA+FlB"}
@@ -118,18 +118,21 @@ function resolveDeployedCadence(input) {
118
118
  // cadence derived from nothing.
119
119
  // `analyzeMs` is what THIS box measured for itself; undefined/null means it has
120
120
  // not measured itself yet and the conservative bootstrap constant governs.
121
- const floorMs = (0, connectorCadence_1.periodicIntervalMsForFeedCount)(input.feedCount, input.analyzeMs) ?? connectorCadence_1.DOOH_PERIODIC_MS;
122
- // A cadence finer than the connector may ANALYZE is not deliverable, so it is
123
- // a floor in every case. This read `LOOK_INTERVAL_MS` until 2026-08-18, when
124
- // the two constants split: both were 30 000 ms, so the substitution changes no
125
- // value today — but the look cadence is now 2 000 ms, and leaving this pointed
126
- // at it would let a 5 s profile row land a 5 s heartbeat and multiply the
127
- // still-scene analyses of every connector in the fleet.
121
+ const floorMs = (0, connectorCadence_1.periodicIntervalMsForFeedCount)(input.feedCount, input.analyzeMs, input.captureMode) ?? connectorCadence_1.DOOH_PERIODIC_MS;
122
+ // `periodicIntervalMsForFeedCount` already carries BOTH lower bounds — the
123
+ // slot budget and the look cadence — so there is nothing to re-floor here.
128
124
  //
129
- // The ceiling wins over the floor: honouring a floor slower than a whole day
130
- // would hand a capture loop a delay it cannot express (see MAX_CADENCE_MS) —
131
- // the one clamp that must never be negotiable.
132
- const effectiveFloor = Math.min(exports.MAX_CADENCE_MS, Math.max(floorMs, connectorCadence_1.MIN_ANALYZE_INTERVAL_MS));
125
+ // Until 2026-08-26 this line read `Math.max(floorMs, MIN_ANALYZE_INTERVAL_MS)`
126
+ // and applied a flat 30 s a second time. That was already a no-op in every
127
+ // reachable state (the derivation floored at the same constant internally, and
128
+ // its only other outcome is the 300 000 ms fallback), so deleting the constant
129
+ // changes no value on this line — but it does stop a second copy of the same
130
+ // assertion from silently becoming the binding one when the first stops being.
131
+ //
132
+ // The ceiling still wins over the floor: honouring a floor slower than a whole
133
+ // day would hand a capture loop a delay it cannot express (see MAX_CADENCE_MS)
134
+ // — the one clamp that must never be negotiable.
135
+ const effectiveFloor = Math.min(exports.MAX_CADENCE_MS, floorMs);
133
136
  if (requestedMs === null) {
134
137
  // The profile answered and asked for nothing. Fall back to the cadence this
135
138
  // machine can afford, NOT to whatever the manager happened to be built with.
@@ -178,7 +181,13 @@ function resolveDeployedCadence(input) {
178
181
  reason = 'cadence_ceiling';
179
182
  }
180
183
  else if (clamped) {
181
- if (effectiveFloor === connectorCadence_1.MIN_ANALYZE_INTERVAL_MS && floorMs <= connectorCadence_1.MIN_ANALYZE_INTERVAL_MS)
184
+ // WHICH of the two derived bounds actually bit? Both are per-box, so this is
185
+ // a real distinction and not a spelling one: `analyze_floor` means the
186
+ // CAPTURE path is the scarce thing (the box outruns its own cameras),
187
+ // `local_inference_budget` means the MODEL is. An operator who is told the
188
+ // wrong one buys the wrong hardware.
189
+ const lookFloorMs = (0, connectorCadence_1.analyzeFloorMsForFeedCount)(input.feedCount, input.captureMode);
190
+ if (lookFloorMs !== null && effectiveFloor === lookFloorMs)
182
191
  reason = 'analyze_floor';
183
192
  else
184
193
  reason = 'local_inference_budget';
@@ -1 +1 @@
1
- {"version":3,"file":"deployedCadence.js","sourceRoot":"","sources":["../src/deployedCadence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deployedCadence.js","sourceRoot":"","sources":["../src/deployedCadence.ts"],"names":[],"mappings":"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"}
@@ -31,7 +31,7 @@ export interface DiagnosticReport {
31
31
  skipped: number;
32
32
  };
33
33
  }
34
- declare const SDK_VERSION = "0.2.100";
34
+ declare const SDK_VERSION = "0.2.102";
35
35
  declare function checkNodeVersion(): CheckResult;
36
36
  declare function checkPlatform(): CheckResult;
37
37
  declare function checkMemory(): CheckResult;
package/dist/diagnose.js CHANGED
@@ -63,7 +63,7 @@ const localNetworkAccess_1 = require("./localNetworkAccess");
63
63
  // ---------------------------------------------------------------------------
64
64
  // SDK version — mirrors package.json; kept in sync by CI
65
65
  // ---------------------------------------------------------------------------
66
- const SDK_VERSION = '0.2.100';
66
+ const SDK_VERSION = '0.2.102';
67
67
  exports._SDK_VERSION = SDK_VERSION;
68
68
  // ---------------------------------------------------------------------------
69
69
  // ANSI helpers (no external deps)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trillboards/edge-sdk",
3
- "version": "0.2.100",
3
+ "version": "0.2.102",
4
4
  "description": "Composable cross-platform Edge AI SDK for audience sensing on DOOH/CTV devices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "prepublishOnly": "npm run build"
17
17
  },
18
18
  "dependencies": {
19
- "@trillboards/edge-core": "^0.4.1",
19
+ "@trillboards/edge-core": "^0.4.5",
20
20
  "@trillboards/edge-sensing": "^0.2.4",
21
21
  "@trillboards/edge-federated": "^0.2.0 || ^1.0.0",
22
22
  "@trillboards/edge-ads": "^0.2.0",