@camstack/addon-pipeline 1.2.305 → 1.2.307

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 (33) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-BduwEjht.mjs → default-detection-model-BV9bIuOo.mjs} +1 -1
  4. package/dist/{default-detection-model-BsXfdMCG.js → default-detection-model-DgdiQtQb.js} +1 -1
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-D4lbkRUh.js → dist-CyJWk-_f.js} +298 -160
  8. package/dist/{dist-BdYgTuWJ.mjs → dist-DdVMFlJ6.mjs} +281 -161
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-DTG5A-3e.mjs → node-COu_tHy4.mjs} +1 -1
  12. package/dist/{node-BMmRQfH_.js → node-HsWG5fGp.js} +1 -1
  13. package/dist/pipeline-runner/index.js +7 -6
  14. package/dist/pipeline-runner/index.mjs +7 -6
  15. package/dist/{process-memory-RnWS9Et5.mjs → process-memory-Bz3oIMzl.mjs} +1 -1
  16. package/dist/{process-memory-DJrgIn1c.js → process-memory-TQXG7qrJ.js} +1 -1
  17. package/dist/recorder/index.js +2 -4
  18. package/dist/recorder/index.mjs +2 -4
  19. package/dist/{segment-demux-js-BI9zEgk6.js → segment-demux-js-BrKa7tpr.js} +1 -1
  20. package/dist/{segment-demux-js-CsJMEslt.mjs → segment-demux-js-CEJn4BR0.mjs} +1 -1
  21. package/dist/stream-broker/_stub.js +2 -2
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Vcuvgik_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DdYUVQSA.mjs} +2 -2
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DPHv0v3Q.mjs +26 -0
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CdKbuxqG.mjs +26 -0
  25. package/dist/stream-broker/demux-worker-child.js +1 -1
  26. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  27. package/dist/stream-broker/{hostInit-C3s1ZFbg.mjs → hostInit-D--a9rVM.mjs} +2 -2
  28. package/dist/stream-broker/index.js +100 -151
  29. package/dist/stream-broker/index.mjs +97 -148
  30. package/dist/stream-broker/remoteEntry.js +1 -1
  31. package/package.json +1 -1
  32. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-X_j7TiDp.mjs +0 -26
  33. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CR9IIPsz.mjs +0 -26
@@ -5370,7 +5370,7 @@ var ZodIssueCode = {
5370
5370
  var ZodFirstPartyTypeKind;
5371
5371
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5372
5372
  //#endregion
5373
- //#region ../types/dist/sleep-E6eD4yQS.mjs
5373
+ //#region ../types/dist/sleep-C-v96WpE.mjs
5374
5374
  /**
5375
5375
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5376
5376
  * window to float samples (D455).
@@ -7684,7 +7684,7 @@ function errMsg(err) {
7684
7684
  return String(err);
7685
7685
  }
7686
7686
  //#endregion
7687
- //#region ../types/dist/composition-D5BQywfe.mjs
7687
+ //#region ../types/dist/composition-BK-GTFYb.mjs
7688
7688
  var AUDIO_ENCODER_BY_CODEC = {
7689
7689
  opus: "libopus",
7690
7690
  aac: "aac",
@@ -9324,7 +9324,6 @@ var CompositionSchema = object({
9324
9324
  });
9325
9325
  new Set([
9326
9326
  "track",
9327
- "summary",
9328
9327
  "face",
9329
9328
  "identity",
9330
9329
  "plate",
@@ -9607,6 +9606,90 @@ async function resolveEgressDecodeHwAccel(deps) {
9607
9606
  return resolved;
9608
9607
  }
9609
9608
  /**
9609
+ * The notification GIF's filter chain and argv — ONE definition for every
9610
+ * process that derives a gif from a notification video.
9611
+ *
9612
+ * ## Why this lives in `@camstack/types`
9613
+ *
9614
+ * Two addons derive a notification gif from an mp4, and they may never import
9615
+ * each other:
9616
+ *
9617
+ * - the stream broker (`addon-pipeline`), from the clip it just cut out of its
9618
+ * own ring (`gif-from-mp4.ts`);
9619
+ * - the notification centre (`addon-post-analysis`), from a clip cut out of
9620
+ * the RECORDINGS when the moment is older than any ring holds
9621
+ * ([D682](../../../../docs/decisions/adr-0682-a-pick-ups-footage-is-cut-around-the-pick-up.md)).
9622
+ *
9623
+ * Two copies of this chain would be two gifs of the same kind of moment that
9624
+ * look different depending on how old the moment was — the class of defect
9625
+ * D85/D91 already paid for once. So the chain is here, pure and dependency
9626
+ * free, and each caller owns only its own process lifecycle.
9627
+ *
9628
+ * ## The palette was never the problem
9629
+ *
9630
+ * The `split` + `palettegen(stats_mode=diff)` + `paletteuse(bayer)` pair is
9631
+ * right and stays. The measured cause of "the gif looks de-pixellated" was a
9632
+ * 640×360 substream scaled DOWN to 480 px, upstream of the palette. The `split`
9633
+ * form is a single READ of the input, not a lesser two-pass.
9634
+ */
9635
+ /**
9636
+ * Format a frame rate for an ffmpeg filter argument.
9637
+ *
9638
+ * `12 / 4` must reach the filter as `3` and `15 / 4` as `3.75`, not as a float
9639
+ * tail that `av_parse_video_rate` then has to guess at.
9640
+ */
9641
+ function ffmpegRate(rate) {
9642
+ return String(Math.round(rate * 1e3) / 1e3);
9643
+ }
9644
+ /**
9645
+ * The factor the derive must apply ON TOP of the speed the mp4 already carries.
9646
+ *
9647
+ * The gif is derived from a muxed mp4, so if that mux already ran `setpts` at
9648
+ * the caller's `speed`, applying the absolute gif rate again would compound
9649
+ * them — a 4× gif beside a 2× video would come out at 8×. Discounting here is
9650
+ * what keeps the gif rate ABSOLUTE against real time whatever the video does.
9651
+ */
9652
+ function gifDeriveSpeed(gifSpeed, videoSpeed) {
9653
+ if (!Number.isFinite(videoSpeed) || videoSpeed <= 0) return gifSpeed;
9654
+ return gifSpeed / videoSpeed;
9655
+ }
9656
+ /**
9657
+ * The filter chain. Extracted so a test can assert the palette pair without
9658
+ * spawning ffmpeg — an argv test cannot see quality, but it can see a filter
9659
+ * that somebody deleted.
9660
+ *
9661
+ * `fps` BEFORE `setpts`: decimating an already sped-up timeline yields `speed`
9662
+ * times the frames asked for, and a gif pays for every one. So the decimation
9663
+ * runs at `fps / speed` in SOURCE time and `setpts` multiplies what survived.
9664
+ * The trailing `fps` re-declares the rate, which is NOT redundant: `setpts`
9665
+ * leaves the filter link advertising the sampled rate and the gif muxer
9666
+ * believes the link — measured on 615 as a 3.01 s gif of ten frames.
9667
+ */
9668
+ function gifFilterComplex(input) {
9669
+ const speed = input.speed > 0 ? input.speed : 1;
9670
+ if (speed === 1) return `[0:v]fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
9671
+ return `[0:v]fps=${ffmpegRate(input.fps / speed)},setpts=PTS/${speed},fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
9672
+ }
9673
+ /** The full argv: mp4 in, gif on stdout. */
9674
+ function gifFromMp4Args(input) {
9675
+ return [
9676
+ "-y",
9677
+ "-v",
9678
+ "error",
9679
+ "-f",
9680
+ "mp4",
9681
+ "-i",
9682
+ input.input ?? "pipe:0",
9683
+ "-filter_complex",
9684
+ gifFilterComplex(input),
9685
+ "-loop",
9686
+ "0",
9687
+ "-f",
9688
+ "gif",
9689
+ "pipe:1"
9690
+ ];
9691
+ }
9692
+ /**
9610
9693
  * The sharing key for `streamBroker.acquireEgressTranscode`.
9611
9694
  *
9612
9695
  * **Two requesters asking for exactly the same argument set reach the same
@@ -17066,6 +17149,34 @@ var DeviceBindingsForDeviceSchema = object({
17066
17149
  nativeAddonId: string()
17067
17150
  }))
17068
17151
  });
17152
+ /**
17153
+ * The STORED wrapper activation for one `(device, capability)` pair — the row
17154
+ * `setWrapperActive` writes, read back three-way (D675):
17155
+ *
17156
+ * - `value` — the store answered and holds an activation: the bound wrapper's
17157
+ * addon id, or `null`, the operator's explicit "off" marker (D62);
17158
+ * - `missing` — the store answered, its activation map exists, and it holds
17159
+ * nothing for this pair: the operator never activated or deactivated a
17160
+ * wrapper here;
17161
+ * - `failed` — the store could not be read, holds no activation map at all
17162
+ * (an empty or unready snapshot, D44), or holds something that is not an
17163
+ * activation. It carries NO information: a consumer must never read it as
17164
+ * `missing` (D315, D393).
17165
+ *
17166
+ * This is the persisted INPUT, not the derived `getBindings` view — no registry
17167
+ * resolution, no `defaultActive` auto-binding, no cluster native-cap folding.
17168
+ */
17169
+ var WrapperActivationReadSchema = discriminatedUnion("kind", [
17170
+ object({
17171
+ kind: literal("value"),
17172
+ wrapperAddonId: string().nullable()
17173
+ }),
17174
+ object({ kind: literal("missing") }),
17175
+ object({
17176
+ kind: literal("failed"),
17177
+ error: string()
17178
+ })
17179
+ ]);
17069
17180
  var SavedDeviceRowSchema = object({
17070
17181
  /** Numeric id reserved at allocateDeviceId time. */
17071
17182
  id: number(),
@@ -17416,7 +17527,10 @@ method(object({
17416
17527
  }), _void(), {
17417
17528
  kind: "mutation",
17418
17529
  auth: "admin"
17419
- }), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
17530
+ }), method(object({
17531
+ deviceId: number(),
17532
+ capName: string().min(1)
17533
+ }), WrapperActivationReadSchema), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
17420
17534
  capName: string(),
17421
17535
  wrappers: array(string())
17422
17536
  }))), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), object({
@@ -22924,6 +23038,102 @@ var OauthIntegrationDescriptorSchema = object({
22924
23038
  });
22925
23039
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
22926
23040
  /**
23041
+ * What started a camera's onboard motion (D672). `camera` is the camera's own
23042
+ * firmware; `sensor` is a device the operator ATTACHED to it. Absent on the
23043
+ * wire means the camera: only the camera-sensors bridge ever says `sensor`.
23044
+ */
23045
+ var MotionTriggeredBySchema = discriminatedUnion("kind", [object({ kind: literal("camera") }), object({
23046
+ kind: literal("sensor"),
23047
+ deviceId: number().int().positive()
23048
+ })]);
23049
+ Object.freeze({ kind: "camera" });
23050
+ var CameraSensorRefSchema = object({
23051
+ addonId: string().min(1),
23052
+ stableId: string().min(1)
23053
+ });
23054
+ function cameraSensorRefKey(ref) {
23055
+ return `${ref.addonId}/${ref.stableId}`;
23056
+ }
23057
+ /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
23058
+ var CameraSensorsModeSchema = _enum(["in-addition"]);
23059
+ object({
23060
+ mode: CameraSensorsModeSchema,
23061
+ sensors: array(CameraSensorRefSchema).min(1).max(8)
23062
+ }).superRefine((value, ctx) => {
23063
+ const seen = /* @__PURE__ */ new Set();
23064
+ value.sensors.forEach((ref, index) => {
23065
+ const key = cameraSensorRefKey(ref);
23066
+ if (seen.has(key)) ctx.addIssue({
23067
+ code: "custom",
23068
+ path: ["sensors", index],
23069
+ message: `duplicate camera sensor ${key}`
23070
+ });
23071
+ seen.add(key);
23072
+ });
23073
+ });
23074
+ var RefFields = {
23075
+ addonId: string(),
23076
+ stableId: string()
23077
+ };
23078
+ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
23079
+ object({
23080
+ ...RefFields,
23081
+ state: literal("ready"),
23082
+ deviceId: number(),
23083
+ name: string(),
23084
+ caps: array(string()).readonly()
23085
+ }),
23086
+ object({
23087
+ ...RefFields,
23088
+ state: literal("offline"),
23089
+ deviceId: number(),
23090
+ name: string(),
23091
+ reason: _enum(["offline", "disabled"])
23092
+ }),
23093
+ object({
23094
+ ...RefFields,
23095
+ state: literal("missing"),
23096
+ reason: string()
23097
+ }),
23098
+ object({
23099
+ ...RefFields,
23100
+ state: literal("unsupported"),
23101
+ deviceId: number(),
23102
+ name: string(),
23103
+ reason: string()
23104
+ }),
23105
+ object({
23106
+ ...RefFields,
23107
+ state: literal("unreadable"),
23108
+ reason: string()
23109
+ })
23110
+ ]);
23111
+ /**
23112
+ * Why a configured camera forwards NOTHING from its sensors, however ready they
23113
+ * are (D672): it does not list `onboard` in `motionSources` (attaching a sensor
23114
+ * does not add it), or it has no active detection config at all (the bridge
23115
+ * reads the active config). Absent = forwarded, or an older node that does not
23116
+ * say.
23117
+ */
23118
+ var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
23119
+ var CameraSensorsStatusSchema = discriminatedUnion("state", [
23120
+ object({ state: literal("not-configured") }),
23121
+ object({
23122
+ state: literal("unreadable"),
23123
+ reason: string()
23124
+ }),
23125
+ object({
23126
+ state: literal("configured"),
23127
+ mode: CameraSensorsModeSchema,
23128
+ sensors: array(CameraSensorAttachmentSchema).readonly(),
23129
+ notForwarded: CameraSensorsNotForwardedSchema.optional()
23130
+ })
23131
+ ]);
23132
+ Object.freeze({
23133
+ state: "unreadable",
23134
+ reason: "camera sensor mappings not loaded"
23135
+ });
23136
+ /**
22927
23137
  * A model choice has a SCOPE, and a scope change has a workflow.
22928
23138
  *
22929
23139
  * ## The two scopes
@@ -23719,6 +23929,12 @@ var MotionEventSchema = object({
23719
23929
  /** Populated by B5 (recording playback URL for this event). */
23720
23930
  mediaUrl: string().optional(),
23721
23931
  /**
23932
+ * The FIRST trigger of an `onboard` episode, written once at open (D672).
23933
+ * Absent = not recorded (a row written before D672, or an analyzer /
23934
+ * device-activity episode) — never read as "the camera" (D393).
23935
+ */
23936
+ triggeredBy: MotionTriggeredBySchema.optional(),
23937
+ /**
23722
23938
  * One row per motion EPISODE, not one per push (D475). `null` while the
23723
23939
  * episode is still open — a further rising edge extends it in place rather
23724
23940
  * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
@@ -23877,6 +24093,21 @@ var ObjectEventSchema = object({
23877
24093
  keyFrameMediaId: number().int().optional(),
23878
24094
  /** Populated by B5 (recording playback URL for this event). */
23879
24095
  mediaUrl: string().optional(),
24096
+ /**
24097
+ * When the change this row records HAPPENED, epoch ms, when that is earlier
24098
+ * than {@link timestamp} and the producer could date it (D684).
24099
+ *
24100
+ * Set on a package PICK-UP row only: its `timestamp` is when the departure
24101
+ * was CONCLUDED — five minutes of unseen observed time after the parcel went
24102
+ * — and a notification's footage has to be cut around the pick-up, not the
24103
+ * empty doormat after it (D682). Stored so every path that rebuilds a
24104
+ * notification subject from the row (the boot reconcile, a dry-run) anchors
24105
+ * on the same instant the live hook did.
24106
+ *
24107
+ * `timestamp` keeps its meaning. Absent on every other row and on every
24108
+ * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
24109
+ */
24110
+ observedAt: number().optional(),
23880
24111
  /** The parent track's key-event importance [0,1], propagated to every object
23881
24112
  * event of the track (so an event row can be sorted by importance without a
23882
24113
  * track join). Absent on legacy rows / before the track was scored. */
@@ -24212,131 +24443,6 @@ var RecentTracksQueryInput = object({
24212
24443
  /** See {@link ExcludeSourcesDoc}. */
24213
24444
  excludeSources: array(TrackSourceSchema).optional()
24214
24445
  });
24215
- /**
24216
- * A Summary (D359): the per-camera session envelope that references tracks.
24217
- * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
24218
- * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
24219
- * first frame with a member visible has been materialised (D482 — it was the
24220
- * row's composite key, which embedded the owner and could name another table's
24221
- * row once event ids became per-table rowids).
24222
- */
24223
- var SummarySchema = object({
24224
- id: string(),
24225
- deviceId: number(),
24226
- openedAt: number(),
24227
- lastActiveAt: number(),
24228
- sealedAt: number().nullable(),
24229
- memberCount: number().int().nonnegative(),
24230
- groupShotMediaId: number().int().nullable()
24231
- });
24232
- var SummariesQueryInput = object({
24233
- deviceIds: array(number()).min(1),
24234
- since: number().optional(),
24235
- until: number().optional(),
24236
- limit: number().int().min(1).max(200).default(50),
24237
- /** The same member-wise filter {@link listRecentSummaries} takes — one
24238
- * meaning of "dog" on every summary surface. */
24239
- classes: array(string().min(1).max(60)).max(40).optional()
24240
- });
24241
- var SummaryDetailSchema = object({
24242
- summary: SummarySchema,
24243
- /** Members in absorption order, oldest join first. */
24244
- members: array(object({
24245
- trackId: string(),
24246
- /**
24247
- * The track's class at the time it JOINED, or `null` when it was unresolved
24248
- * and the track no longer exists to fill it.
24249
- *
24250
- * The join, not the latest opinion: class voting can re-decide a track's
24251
- * class afterwards, and a group's composition is the history of who came
24252
- * together. A consumer counts these to badge a thumbnail — "two people and a
24253
- * dog" — rather than being handed a second, denormalised tally that would
24254
- * drift from the members it claims to describe.
24255
- */
24256
- className: string().nullable(),
24257
- /** `primary` is the one member that carries the group's identity. */
24258
- role: _enum([
24259
- "primary",
24260
- "sibling",
24261
- "child"
24262
- ]).nullable(),
24263
- joinedAt: number(),
24264
- /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
24265
- * at read time. Absent when nothing resolved, never an empty string. */
24266
- label: string().optional(),
24267
- /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
24268
- * recognised nobody, and absent for a member whose track has been pruned —
24269
- * a thumbnail that names the wrong person is worse than one naming nobody. */
24270
- subLabel: string().optional()
24271
- })).readonly()
24272
- });
24273
- /**
24274
- * What a LIST of groups carries: the envelope plus what it is MADE OF.
24275
- *
24276
- * Coarse on purpose — the operator's rule is "only the rough data on the list,
24277
- * we extend when we notice we need more". Enough to draw the card ("two people
24278
- * and a dog") and no more. The MEMBERS, their names and the per-member links
24279
- * are on {@link getSummary}: shipping every member of every group so a client
24280
- * can count them turns a page of two hundred groups into a thousand records to
24281
- * draw two hundred badges.
24282
- */
24283
- var SummaryListItemSchema = object({
24284
- summary: SummarySchema,
24285
- /** Class counts, most numerous first; `className: null` is a member whose
24286
- * class never resolved, counted so the counts add up to `memberCount`. */
24287
- classes: array(object({
24288
- className: string().nullable(),
24289
- count: number().int().positive()
24290
- })).readonly()
24291
- });
24292
- var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
24293
- /**
24294
- * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
24295
- *
24296
- * The reel on the cameras page and the Events gallery both ride a cursor, so a
24297
- * summaries feed that could only answer "the last N for these cameras"
24298
- * (`listSummaries`) would make groups available in the timeline and nowhere
24299
- * else. Two feeds with different ordering contracts would be two ideas of what
24300
- * "newest" means, on one screen.
24301
- */
24302
- var RecentSummariesQueryInput = object({
24303
- /** Devices to merge. An empty array yields an empty page. */
24304
- deviceIds: array(number()),
24305
- /** Window lower bound on `lastActiveAt` (inclusive). */
24306
- since: number().optional(),
24307
- /** Window upper bound on `lastActiveAt` (inclusive). */
24308
- until: number().optional(),
24309
- limit: number().int().min(1).max(500).default(100),
24310
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
24311
- cursor: string().optional(),
24312
- /**
24313
- * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
24314
- * deliberately the same name: the events filter tree emits one list of ticked
24315
- * taxonomy nodes, and a summaries feed that called it something else would
24316
- * make the operator's one filter component produce two shapes.
24317
- *
24318
- * A question about the MEMBERS: a group matches when ANY of them does. "Show
24319
- * me the arrivals with a dog" has to return the group of four people who had
24320
- * one with them; a filter on the group's own fields could only ever answer
24321
- * about its primary.
24322
- *
24323
- * Matched against all THREE tiers of a member — the class (`animal`), the
24324
- * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
24325
- * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
24326
- * an event kind in the tree and a tier-1 label on an `animal` track; a filter
24327
- * that read only the class would answer "animal" and never "dog".
24328
- *
24329
- * ABSENT or EMPTY means no filter — absent is not a selection of none.
24330
- */
24331
- classes: array(string().min(1).max(60)).max(40).optional()
24332
- });
24333
- var RecentSummariesPageSchema = object({
24334
- /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
24335
- * {@link listSummaries} — one list shape, so a card renders the same
24336
- * whichever feed drew it. */
24337
- summaries: array(SummaryListItemSchema).readonly(),
24338
- nextCursor: string().nullable()
24339
- });
24340
24446
  var RecentTracksPageSchema = object({
24341
24447
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
24342
24448
  tracks: array(TrackSchema).readonly(),
@@ -24848,7 +24954,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
24848
24954
  classes: array(string()).optional(),
24849
24955
  /** See {@link ExcludeSourcesDoc}. */
24850
24956
  excludeSources: array(TrackSourceSchema).optional()
24851
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
24957
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
24852
24958
  kind: "mutation",
24853
24959
  auth: "admin"
24854
24960
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -26224,6 +26330,23 @@ var MotionSourceEnum = _enum([
26224
26330
  */
26225
26331
  var MotionSourcesSchema = array(MotionSourceEnum);
26226
26332
  /**
26333
+ * The operator-facing name of each MOTION source (D672: `onboard` = the
26334
+ * camera's sensors — its own motion plus any sensor attached to it). The wire
26335
+ * value stays `onboard`; only the label changed. `detectionSources: 'onboard'`
26336
+ * ("Camera onboard AI") and the notification source filter keep their labels.
26337
+ */
26338
+ var MOTION_SOURCE_LABELS = {
26339
+ analyzer: "Frame-diff Analyzer (motion addon)",
26340
+ onboard: "Camera sensors",
26341
+ "device-activity": "Device activity (the device says it is working)"
26342
+ };
26343
+ /** The order the motion-source picker offers them in (not the enum's order). */
26344
+ var MOTION_SOURCE_PICKER_ORDER = [
26345
+ "analyzer",
26346
+ "onboard",
26347
+ "device-activity"
26348
+ ];
26349
+ /**
26227
26350
  * Which root detector a camera runs — EXACTLY ONE.
26228
26351
  *
26229
26352
  * Deliberately the SAME vocabulary post-analysis already tags every detection
@@ -26570,7 +26693,14 @@ var RunnerCameraConfigSchema = object({
26570
26693
  */
26571
26694
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
26572
26695
  });
26573
- motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, preRollInferenceFramesField.min, preRollInferenceFramesField.max, preRollInferenceFramesField.step, preRollInferenceFramesField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
26696
+ MOTION_SOURCE_PICKER_ORDER.map((value) => value === "onboard" ? {
26697
+ value,
26698
+ label: MOTION_SOURCE_LABELS[value],
26699
+ description: "The camera's own motion, plus any sensor attached to it"
26700
+ } : {
26701
+ value,
26702
+ label: MOTION_SOURCE_LABELS[value]
26703
+ }), motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, preRollInferenceFramesField.min, preRollInferenceFramesField.max, preRollInferenceFramesField.step, preRollInferenceFramesField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
26574
26704
  /**
26575
26705
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
26576
26706
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -27336,6 +27466,11 @@ var CameraStatusSchema = object({
27336
27466
  */
27337
27467
  switchedOff: array(CameraSwitchIdSchema).readonly(),
27338
27468
  /**
27469
+ * The camera's attached sensors (D672). Optional only for a status composed
27470
+ * by an orchestrator older than D672; this orchestrator always sets it.
27471
+ */
27472
+ cameraSensors: CameraSensorsStatusSchema.optional(),
27473
+ /**
27339
27474
  * Stages of the bounded fan-out that were CUT SHORT — a timeout or a
27340
27475
  * rejection — and whose block is therefore `null` because we could not
27341
27476
  * READ it, not because there is nothing there.
@@ -33907,7 +34042,9 @@ object({
33907
34042
  detected: boolean(),
33908
34043
  timestamp: number(),
33909
34044
  source: MotionSourceEnum,
33910
- regions: array(MotionRegionSchema).readonly().optional()
34045
+ regions: array(MotionRegionSchema).readonly().optional(),
34046
+ /** Who started it (D672). Absent = the camera itself. Only `onboard` carries `sensor`. */
34047
+ triggeredBy: MotionTriggeredBySchema.optional()
33911
34048
  });
33912
34049
  DeviceType.Camera, DeviceType.Sensor, method(object({ deviceId: number() }), boolean());
33913
34050
  object({
@@ -42673,6 +42810,12 @@ Object.freeze({
42673
42810
  addonId: null,
42674
42811
  access: "view"
42675
42812
  },
42813
+ "deviceManager.getWrapperActivation": {
42814
+ capName: "device-manager",
42815
+ capScope: "system",
42816
+ addonId: null,
42817
+ access: "view"
42818
+ },
42676
42819
  "deviceManager.listAll": {
42677
42820
  capName: "device-manager",
42678
42821
  capScope: "system",
@@ -44947,12 +45090,6 @@ Object.freeze({
44947
45090
  addonId: null,
44948
45091
  access: "view"
44949
45092
  },
44950
- "pipelineAnalytics.getSummary": {
44951
- capName: "pipeline-analytics",
44952
- capScope: "device",
44953
- addonId: null,
44954
- access: "view"
44955
- },
44956
45093
  "pipelineAnalytics.getTrack": {
44957
45094
  capName: "pipeline-analytics",
44958
45095
  capScope: "device",
@@ -45013,12 +45150,6 @@ Object.freeze({
45013
45150
  addonId: null,
45014
45151
  access: "view"
45015
45152
  },
45016
- "pipelineAnalytics.listRecentSummaries": {
45017
- capName: "pipeline-analytics",
45018
- capScope: "device",
45019
- addonId: null,
45020
- access: "view"
45021
- },
45022
45153
  "pipelineAnalytics.listRecentTracks": {
45023
45154
  capName: "pipeline-analytics",
45024
45155
  capScope: "device",
@@ -45049,12 +45180,6 @@ Object.freeze({
45049
45180
  addonId: null,
45050
45181
  access: "view"
45051
45182
  },
45052
- "pipelineAnalytics.listSummaries": {
45053
- capName: "pipeline-analytics",
45054
- capScope: "device",
45055
- addonId: null,
45056
- access: "view"
45057
- },
45058
45183
  "pipelineAnalytics.listTrackMedia": {
45059
45184
  capName: "pipeline-analytics",
45060
45185
  capScope: "device",
@@ -48482,6 +48607,11 @@ Object.freeze({
48482
48607
  form: "single",
48483
48608
  optional: false
48484
48609
  }],
48610
+ "deviceManager.getWrapperActivation": [{
48611
+ name: "deviceId",
48612
+ form: "single",
48613
+ optional: false
48614
+ }],
48485
48615
  "deviceManager.listAll": [{
48486
48616
  name: "deviceIds",
48487
48617
  form: "array",
@@ -49219,11 +49349,6 @@ Object.freeze({
49219
49349
  form: "single",
49220
49350
  optional: true
49221
49351
  }],
49222
- "pipelineAnalytics.listRecentSummaries": [{
49223
- name: "deviceIds",
49224
- form: "array",
49225
- optional: false
49226
- }],
49227
49352
  "pipelineAnalytics.listRecentTracks": [{
49228
49353
  name: "deviceIds",
49229
49354
  form: "array",
@@ -49234,11 +49359,6 @@ Object.freeze({
49234
49359
  form: "array",
49235
49360
  optional: true
49236
49361
  }],
49237
- "pipelineAnalytics.listSummaries": [{
49238
- name: "deviceIds",
49239
- form: "array",
49240
- optional: false
49241
- }],
49242
49362
  "pipelineAnalytics.listTrackMedia": [{
49243
49363
  name: "deviceId",
49244
49364
  form: "single",
@@ -52112,4 +52232,4 @@ function enumerateInferenceDevices(hw) {
52112
52232
  return out;
52113
52233
  }
52114
52234
  //#endregion
52115
- export { defaultDeviceFor as $, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as $t, PoolMemoryWatchdog as A, lazy as An, pipelineExecutorCapability as At, VISIT_MERGE_GAP_MS as B, resolveLocationMode as Bt, FailureCounters as C, parseProfileBrokerId as Cn, modeMayWrite as Ct, NativeLeaseAdmissionSchema as D, array as Dn, pickClusterStepSettings as Dt, MOTION_CLOSE_AFTER_MS as E, _enum as En, parseProcStatus as Et, RECORDING_SOURCE_CAMSTACK_ADDON as F, record as Fn, recordingExportCapability as Ft, audioKindId as G, storageEvictableCapability as Gt, addonWidgetsSourceCapability as H, resolveRecordingProfiles as Ht, RecordingConfigSchema as I, string as In, recordingSignalCapability as It, clipExportFailure as J, stripRetiredBandPreBufferSec as Jt, batteryCapability as K, storageOccupancyCapability as Kt, RecordingSignalStatusSchema as L, union as Ln, resolveClusterStepModelId as Lt, RATE_CONTROL_TIGHT as M, number as Mn, rankStreams as Mt, RECORDING_EXPORT_MAX_READ_BYTES as N, object as Nn, recordingArchiveCapability as Nt, NativeLeaseSettingsSchema as O, boolean as On, pickDetailCropConvention as Ot, RECORDING_SOURCE_CAMSTACK as P, preprocess as Pn, recordingCapability as Pt, declareLogChannel as Q, AUDIO_PRESETS as Qt, RingBuffer as R, EventCategory as Rn, resolveCreateExportSubject as Rt, ExportRecordSchema as S, parseJsonUnknown as Sn, migrateLegacyDeviceSignalConfig as St, HF_BASE_URL as T, sleep as Tn, overlayClusterStepSettings as Tt, audioAnalysisCapability as U, runtimeDevices as Ut, YAMNET_TO_MACRO as V, resolvePoolMemoryPolicy as Vt, audioAnalyzerCapability as W, stepModelRunsOnFormat as Wt, createHwAccelCache as X, videoclipsCapability as Xt, clipExportFailureOf as Y, supportedRuntimes as Yt, createLogChannelsProvider as Z, webrtcSessionCapability as Zt, DEFAULT_NATIVE_LEASE_SETTINGS as _, hydrateSchema as _n, logChannelsCapability as _t, BatteryStatusSchema as a, DeviceFeature as an, egressTransportFromRequest as at, EVENT_PAD_MS as b, makeSourceBrokerId as bn, mayReadLocation as bt, CLIP_STILL_REASON_HEADER as c, BOOT_RECOVERY_BACKOFF_MS as cn, evictionPolicyForMode as ct, COCO_80_LABELS as d, CamProfileSchema as dn, failureContributionCapability as dt, Fmp4BoxSplitter as en, deriveBatteryPresence as et, COCO_TO_MACRO as f, audioChunkBytesPerSample as fn, gbToBytes as ft, DEFAULT_EVENTS_BAND_BUFFER_SEC as g, expandAudioChunkToF32le as gn, loadContributionCapability as gt, DEFAULT_DETAIL_CROP_CONVENTION as h, defineCustomActions as hn, isFirstLevelMacroClass as ht, BackgroundRunRegistry as i, isSoftwareDecode as in, egressTranscodeSharingKey as it, RATE_CONTROL_RELAXED as j, literal as jn, pipelineRunnerCapability as jt, OpsLogEntrySchema as k, discriminatedUnion as kn, pickNativeLeaseOverride as kt, CLIP_STREAM_TRANSPORT_HEADER as l, BaseAddon as ln, evictionPolicyOfLocation as lt, DEFAULT_CLUSTER_STEP_SETTINGS as m, customAction as mn, inferModelProvider as mt, AUDIO_BACKEND_CHOICES as n, evaluateSensorEdge as nn, deriveRecordingMode as nt, CLIP_BROKER_PACED_RATES as o, DeviceType as on, enumerateInferenceDevices as ot, DEFAULT_AUDIO_ANALYZER_CONFIG as p, createEvent as pn, hfModelUrl as pt, cameraStreamsCapability as q, streamBrokerCapability as qt, AUDIO_MACRO_LABELS as r, invocationFromEncodeProfile as rn, detectionPipelineCapability as rt, CLIP_STILL_REASON_CODE_HEADER as s, errMsg as sn, evaluateZoneRules as st, APPLE_SA_TO_MACRO as t, buildFfmpegArgs as tn, deriveDetailCropRect as tt, CLUSTER_MODEL_SCOPED_STEPS as u, CAM_PROFILE_ORDER as un, exportSubjectOf as ut, DEVICE_BACKEND_TO_FORMAT as v, isEvent as vn, mapAudioLabelToMacro as vt, G711_SCALE_CORRECTION_DB as w, selectAssignedProfileSlots as wn, motionDetectionCapability as wt, EncodeProfileSchema as x, nodePin as xn, mayWriteToLocation as xt, EVENT_OWNER_TYPES as y, makeProfileBrokerId as yn, maskUrlCredentials as yt, VIDEOCLIPS_MAX_OFFER_BYTES as z, resolveEgressDecodeHwAccel as zt };
52235
+ export { defaultDeviceFor as $, videoclipsCapability as $t, PoolMemoryWatchdog as A, array as An, pickClusterStepSettings as At, VISIT_MERGE_GAP_MS as B, union as Bn, resolveClusterStepModelId as Bt, FailureCounters as C, makeSourceBrokerId as Cn, mayReadLocation as Ct, NativeLeaseAdmissionSchema as D, selectAssignedProfileSlots as Dn, motionDetectionCapability as Dt, MOTION_CLOSE_AFTER_MS as E, parseProfileBrokerId as En, modeMayWrite as Et, RECORDING_SOURCE_CAMSTACK_ADDON as F, number as Fn, rankStreams as Ft, audioKindId as G, resolveRecordingProfiles as Gt, addonWidgetsSourceCapability as H, resolveEgressDecodeHwAccel as Ht, RecordingConfigSchema as I, object as In, recordingArchiveCapability as It, clipExportFailure as J, storageEvictableCapability as Jt, batteryCapability as K, runtimeDevices as Kt, RecordingSignalStatusSchema as L, preprocess as Ln, recordingCapability as Lt, RATE_CONTROL_TIGHT as M, discriminatedUnion as Mn, pickNativeLeaseOverride as Mt, RECORDING_EXPORT_MAX_READ_BYTES as N, lazy as Nn, pipelineExecutorCapability as Nt, NativeLeaseSettingsSchema as O, sleep as On, overlayClusterStepSettings as Ot, RECORDING_SOURCE_CAMSTACK as P, literal as Pn, pipelineRunnerCapability as Pt, declareLogChannel as Q, supportedRuntimes as Qt, RingBuffer as R, record as Rn, recordingExportCapability as Rt, ExportRecordSchema as S, makeProfileBrokerId as Sn, maskUrlCredentials as St, HF_BASE_URL as T, parseJsonUnknown as Tn, migrateLegacyDeviceSignalConfig as Tt, audioAnalysisCapability as U, resolveLocationMode as Ut, YAMNET_TO_MACRO as V, EventCategory as Vn, resolveCreateExportSubject as Vt, audioAnalyzerCapability as W, resolvePoolMemoryPolicy as Wt, createHwAccelCache as X, streamBrokerCapability as Xt, clipExportFailureOf as Y, storageOccupancyCapability as Yt, createLogChannelsProvider as Z, stripRetiredBandPreBufferSec as Zt, DEFAULT_NATIVE_LEASE_SETTINGS as _, customAction as _n, inferModelProvider as _t, BatteryStatusSchema as a, evaluateSensorEdge as an, egressTransportFromRequest as at, EVENT_PAD_MS as b, hydrateSchema as bn, logChannelsCapability as bt, CLIP_STILL_REASON_HEADER as c, DeviceFeature as cn, evictionPolicyForMode as ct, COCO_80_LABELS as d, BOOT_RECOVERY_BACKOFF_MS as dn, failureContributionCapability as dt, webrtcSessionCapability as en, deriveBatteryPresence as et, COCO_TO_MACRO as f, BaseAddon as fn, ffmpegRate as ft, DEFAULT_EVENTS_BAND_BUFFER_SEC as g, createEvent as gn, hfModelUrl as gt, DEFAULT_DETAIL_CROP_CONVENTION as h, audioChunkBytesPerSample as hn, gifFromMp4Args as ht, BackgroundRunRegistry as i, buildFfmpegArgs as in, egressTranscodeSharingKey as it, RATE_CONTROL_RELAXED as j, boolean as jn, pickDetailCropConvention as jt, OpsLogEntrySchema as k, _enum as kn, parseProcStatus as kt, CLIP_STREAM_TRANSPORT_HEADER as l, DeviceType as ln, evictionPolicyOfLocation as lt, DEFAULT_CLUSTER_STEP_SETTINGS as m, CamProfileSchema as mn, gifDeriveSpeed as mt, AUDIO_BACKEND_CHOICES as n, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as nn, deriveRecordingMode as nt, CLIP_BROKER_PACED_RATES as o, invocationFromEncodeProfile as on, enumerateInferenceDevices as ot, DEFAULT_AUDIO_ANALYZER_CONFIG as p, CAM_PROFILE_ORDER as pn, gbToBytes as pt, cameraStreamsCapability as q, stepModelRunsOnFormat as qt, AUDIO_MACRO_LABELS as r, Fmp4BoxSplitter as rn, detectionPipelineCapability as rt, CLIP_STILL_REASON_CODE_HEADER as s, isSoftwareDecode as sn, evaluateZoneRules as st, APPLE_SA_TO_MACRO as t, AUDIO_PRESETS as tn, deriveDetailCropRect as tt, CLUSTER_MODEL_SCOPED_STEPS as u, errMsg as un, exportSubjectOf as ut, DEVICE_BACKEND_TO_FORMAT as v, defineCustomActions as vn, isFirstLevelMacroClass as vt, G711_SCALE_CORRECTION_DB as w, nodePin as wn, mayWriteToLocation as wt, EncodeProfileSchema as x, isEvent as xn, mapAudioLabelToMacro as xt, EVENT_OWNER_TYPES as y, expandAudioChunkToF32le as yn, loadContributionCapability as yt, VIDEOCLIPS_MAX_OFFER_BYTES as z, string as zn, recordingSignalCapability as zt };
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-D4lbkRUh.js");
5
+ const require_dist = require("../dist-CyJWk-_f.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { _n as hydrateSchema, ln as BaseAddon, on as DeviceType, st as evaluateZoneRules, wt as motionDetectionCapability } from "../dist-BdYgTuWJ.mjs";
1
+ import { Dt as motionDetectionCapability, bn as hydrateSchema, fn as BaseAddon, ln as DeviceType, st as evaluateZoneRules } from "../dist-DdVMFlJ6.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -1,4 +1,4 @@
1
- import { en as Fmp4BoxSplitter, in as isSoftwareDecode, sn as errMsg, tn as buildFfmpegArgs } from "./dist-BdYgTuWJ.mjs";
1
+ import { in as buildFfmpegArgs, rn as Fmp4BoxSplitter, sn as isSoftwareDecode, un as errMsg } from "./dist-DdVMFlJ6.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-D4lbkRUh.js");
2
+ const require_dist = require("./dist-CyJWk-_f.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs, 1);
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-D4lbkRUh.js");
7
- const require_node = require("../node-BMmRQfH_.js");
8
- const require_default_detection_model = require("../default-detection-model-BsXfdMCG.js");
6
+ const require_dist = require("../dist-CyJWk-_f.js");
7
+ const require_node = require("../node-HsWG5fGp.js");
8
+ const require_default_detection_model = require("../default-detection-model-DgdiQtQb.js");
9
9
  const require_worker_protocol = require("../worker-protocol-C-G8qmye.js");
10
10
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
11
11
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
@@ -893,9 +893,10 @@ async function withRetainedReader(store, handle, read) {
893
893
  *
894
894
  * The real defect is caller-side and is not fixed here: a ~1 Hz
895
895
  * `TrackDetailDispatcher` re-asks a frozen handle for the life of a coasted
896
- * track, and `persistKeyFrames` / `offerGroupShot` are the only capture sites
897
- * that omit `staleAfterMs`. ADR-0419 named both as open; until they are fixed
898
- * a tighter bound cannot be told apart from a broken one.
896
+ * track. ADR-0419 also named the by-handle key-frame and group-shot captures
897
+ * as omitting `staleAfterMs`; both are gone (the key frame reads a parked
898
+ * parcel since D422, the group shot is deleted by D680). Until the dispatcher
899
+ * is fixed a tighter bound cannot be told apart from a broken one.
899
900
  */
900
901
  var NATIVE_CROP_LEASE_MAX_AGE_MS = 3e4;
901
902
  function classifyNativeCropLeaseAge(ageMs, maxAgeMs = NATIVE_CROP_LEASE_MAX_AGE_MS) {