@camstack/types 1.2.232 → 1.2.234

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-D7DPSeiv.js");
2
+ const require_sleep = require("./sleep-DImve4HM.js");
3
3
  const require_event_category = require("./event-category-BVfsrBYA.js");
4
4
  const require_canonical_hash = require("./canonical-hash-CSE4ioRi.js");
5
5
  const require_enums = require("./enums.js");
@@ -9762,6 +9762,29 @@ var cameraStreamsCapability = {
9762
9762
  };
9763
9763
  //#endregion
9764
9764
  //#region src/capabilities/cap-router-predicates.ts
9765
+ /**
9766
+ * True when a cap's generated router carries the COLLECTION `addonId`
9767
+ * selector — "which provider of this collection answers".
9768
+ *
9769
+ * `resolveCapMount` is the authority, not `mode`: since D554 a device-scoped
9770
+ * wrapper that is a collection (`videoclips`) keeps the PER-DEVICE mount, and
9771
+ * its sources are resolved from the device, never picked by an addonId the
9772
+ * caller supplies. Emitting the selector for it would advertise a routing key
9773
+ * the runtime builder does not read — the exact type/runtime disagreement
9774
+ * D552 removed from `generate-cap-mounts.ts`.
9775
+ *
9776
+ * The one exception is preserved deliberately, and it is the same one
9777
+ * `generate-cap-mounts.ts` documents: a cap declaring `mount: { kind: 'skip' }`
9778
+ * keeps its mode-derived shape. The builder never mounts it, so the emitted
9779
+ * value is never called — four inert caps (`log-channels`, `login-method`,
9780
+ * `load-contribution`, `failure-contribution`) that would otherwise change
9781
+ * shape inside a slice that is about something else.
9782
+ */
9783
+ function emitsCollectionSelector(def) {
9784
+ const kind = require_sleep.resolveCapMount(def).kind;
9785
+ if (kind === "collection") return true;
9786
+ return kind === "skip" && def.mode === "collection";
9787
+ }
9765
9788
  /** `z.void()` — input carries no data. */
9766
9789
  function isVoidInput(schema) {
9767
9790
  const def = schema._def;
@@ -9871,6 +9894,33 @@ function objectInputDeclaresAddonId(schema) {
9871
9894
  const shape = direct !== void 0 && direct !== null ? direct : resolveDefShape(schema._def?.shape);
9872
9895
  return shape !== void 0 && shape !== null && typeof shape === "object" && Object.prototype.hasOwnProperty.call(shape, "addonId");
9873
9896
  }
9897
+ /**
9898
+ * True when an OBJECT input schema declares a top-level, REQUIRED, numeric
9899
+ * `deviceId` — i.e. the method names exactly one device on every call.
9900
+ *
9901
+ * This is the question the per-device dispatcher asks at runtime
9902
+ * (`requireDeviceScoped`: `typeof input.deviceId !== 'number'` → BAD_REQUEST),
9903
+ * asked once at build time instead. A `device-scoped` cap's method that cannot
9904
+ * answer it has no device to resolve for and therefore resolves through the
9905
+ * cap's SYSTEM provider (`cap-router-builder.ts`) — the same destination
9906
+ * `systemOnly` names explicitly. Without this, flipping the mount would 400
9907
+ * every batch (`deviceIds`), fleet-wide (`z.void()`) and job-keyed method on
9908
+ * the eight device-scoped wrapper caps.
9909
+ *
9910
+ * Structural, never a name heuristic over source text: the field must resolve
9911
+ * to `z.number()` and must NOT be wrapped in `optional` / `nullable` /
9912
+ * `default` — an absent value is exactly the input the dispatcher rejects.
9913
+ */
9914
+ function objectInputDeclaresRequiredDeviceId(schema) {
9915
+ if (schema === null || typeof schema !== "object" && typeof schema !== "function") return false;
9916
+ const direct = schema.shape;
9917
+ const shape = direct !== void 0 && direct !== null ? direct : resolveDefShape(schema._def?.shape);
9918
+ if (shape === void 0 || shape === null || typeof shape !== "object") return false;
9919
+ const field = shape["deviceId"];
9920
+ if (field === void 0 || field === null) return false;
9921
+ const def = field._def;
9922
+ return field.constructor?.name === "ZodNumber" || def?.type === "number" || def?.typeName === "ZodNumber";
9923
+ }
9874
9924
  /** `_def.shape` may be a thunk (Zod defers object-shape evaluation) or a plain record. */
9875
9925
  function resolveDefShape(defShape) {
9876
9926
  return typeof defShape === "function" ? defShape() : defShape;
@@ -12889,14 +12939,30 @@ var deviceManagerCapability = {
12889
12939
  */
12890
12940
  getAllBindings: require_sleep.method(zod.z.object({}), zod.z.array(DeviceBindingsForDeviceSchema)),
12891
12941
  /**
12892
- * Activate (or deactivate) a wrapper addon for a (device, cap) pair.
12893
- * Persists the binding via ctx.settings. active=false clears the wrapper
12894
- * (falls back to native if registered).
12942
+ * Activate (or deactivate) a wrapper addon — or ONE of its sources — for a
12943
+ * (device, cap) pair.
12944
+ *
12945
+ * Persists the binding via ctx.settings; `active: false` on a SINGLETON cap
12946
+ * clears the wrapper (falling back to native if one is registered). On a
12947
+ * COLLECTION cap it adds to the per-device DENY set instead, and the
12948
+ * wrapper binding is left exactly as it was — a cap with several sources
12949
+ * has no "which one" for `getBindings` to answer (D556).
12950
+ *
12951
+ * `sourceId` is what makes the switch match its label. One addon can serve
12952
+ * several of a device's sources (`addon-provider-reolink` answers a hub
12953
+ * child with `native:reolink:onboard` AND `native:reolink:hub`), so a
12954
+ * toggle keyed by addon would turn off both while saying it turned off one
12955
+ * — the D62 defect with the polarity reversed (D557). Naming the source
12956
+ * denies exactly that source. OMITTING it keeps the cap-wide meaning
12957
+ * D556 shipped: every source this addon serves for this device, which is
12958
+ * also how a deny entry persisted before the re-key is still read.
12895
12959
  */
12896
12960
  setWrapperActive: require_sleep.method(zod.z.object({
12897
12961
  deviceId: zod.z.number(),
12898
12962
  capName: zod.z.string(),
12899
12963
  wrapperAddonId: zod.z.string(),
12964
+ /** The `ClipSource.source` id to toggle. Absent = all of the addon's. */
12965
+ sourceId: zod.z.string().optional(),
12900
12966
  active: zod.z.boolean()
12901
12967
  }), zod.z.void(), {
12902
12968
  kind: "mutation",
@@ -25121,10 +25187,9 @@ var ServerBootModeSchema = zod.z.enum([
25121
25187
  * - `idle` / `checking` / `staging` — steady / in-flight registry work.
25122
25188
  * - `pending-restart` — a version is staged and the node has NOT yet
25123
25189
  * restarted onto it (still running the OLD version).
25124
- * - `awaiting-confirmation` — the node HAS restarted onto the staged version
25125
- * (it is the active probation boot) and is waiting to confirm boot-health.
25126
- * Apply/rollback are refused in this state and the node must NOT be
25127
- * manually restarted, or the probation boot auto-rolls-back.
25190
+ * - `awaiting-confirmation` — RETIRED with the probation boot (single-copy
25191
+ * collapse, 2026-07-18). Kept in the enum so an older client still parses;
25192
+ * no provider emits it.
25128
25193
  */
25129
25194
  var ServerUpdateStateSchema = zod.z.enum([
25130
25195
  "idle",
@@ -25281,12 +25346,35 @@ var ServerRollbackInfoSchema = zod.z.object({
25281
25346
  var ServerPackageStatusSchema = zod.z.object({
25282
25347
  /** Root package name (`@camstack/server` on the hub). */
25283
25348
  packageName: zod.z.string(),
25284
- /** Version of the code the running process ACTUALLY loaded. */
25349
+ /**
25350
+ * Version of the code THIS PROCESS loaded — captured once, at boot, from the
25351
+ * package.json of the copy the code was loaded from, and never re-read. A
25352
+ * copy replaced on disk under a live process must not move this number;
25353
+ * that divergence is what `installedVersion` is for. Before 2026-09-20 this
25354
+ * was a lazy disk read that would have followed such a swap silently.
25355
+ */
25285
25356
  runningVersion: zod.z.string().nullable(),
25286
25357
  /** Node.js runtime version the node's process runs on (`process.versions.node`). */
25287
25358
  nodeRuntimeVersion: zod.z.string().nullable(),
25288
25359
  /** Active data-dir root version; null when booted from seed/workspace. */
25289
25360
  activeVersion: zod.z.string().nullable(),
25361
+ /**
25362
+ * Version of the root package in the data-root `current/` copy RIGHT NOW —
25363
+ * what the next boot would load if nothing is staged. Normally equal to
25364
+ * `runningVersion`; differs only when the copy on disk changed under the
25365
+ * live process. `null` when there is no data-root copy (baked seed or
25366
+ * workspace boot). Optional for version skew: an older provider omits it.
25367
+ */
25368
+ installedVersion: zod.z.string().nullable().optional(),
25369
+ /**
25370
+ * True when the code that would run after a restart is NOT the code running
25371
+ * now: a version is staged (`pendingVersion`), or `installedVersion` differs
25372
+ * from `runningVersion`. Derived on every read, never stored. This is the
25373
+ * one field that lets an operator or a script say "1.2.353 is installed and
25374
+ * will run after a restart" instead of reading it off a number that means
25375
+ * "installed". Optional for version skew.
25376
+ */
25377
+ restartRequired: zod.z.boolean().optional(),
25290
25378
  /** N-1 version kept for rollback; null when no previous version exists. */
25291
25379
  previousVersion: zod.z.string().nullable(),
25292
25380
  /** Version of the immutable baked seed closure (image fallback). */
@@ -25321,19 +25409,71 @@ var ServerPackageStatusSchema = zod.z.object({
25321
25409
  });
25322
25410
  var ServerUpdateCheckResultSchema = zod.z.object({
25323
25411
  packageName: zod.z.string(),
25412
+ /** Same meaning as on the status payload: what THIS process loaded. */
25324
25413
  runningVersion: zod.z.string().nullable(),
25325
25414
  latestVersion: zod.z.string().nullable(),
25415
+ /**
25416
+ * `latestVersion` is newer than `runningVersion` AND is not the version
25417
+ * already staged on this node. A staged-and-waiting version is not "an
25418
+ * update available" — applying it again is refused (`already-staged`), and
25419
+ * a button that announced "updating…" on this boolean then got refused
25420
+ * would be lying. It is reported through `pendingVersion` instead.
25421
+ */
25326
25422
  updateAvailable: zod.z.boolean(),
25327
25423
  checkedAtMs: zod.z.number(),
25328
25424
  /** Non-null when the registry lookup failed (offline, bad registry, …). */
25329
- error: zod.z.string().nullable()
25425
+ error: zod.z.string().nullable(),
25426
+ /**
25427
+ * Version staged on this node and awaiting its restart, when one is. Before
25428
+ * 2026-09-20 a check answered `{runningVersion, updateAvailable:false}` for a
25429
+ * node whose staged closure had never booted, and the caller had no way to
25430
+ * see the difference. Optional for version skew.
25431
+ */
25432
+ pendingVersion: zod.z.string().nullable().optional(),
25433
+ /** See the status payload: staged, or installed-on-disk ≠ running. Optional for skew. */
25434
+ restartRequired: zod.z.boolean().optional()
25330
25435
  });
25436
+ /**
25437
+ * What an action DID — the discriminant `accepted` cannot carry. Before
25438
+ * 2026-09-20 `applyServerUpdate` answered `accepted:false` both for a refusal
25439
+ * where nothing was wrong (already current, already staged) and for a staging
25440
+ * install that died on `npm error code ECONNRESET`; the only difference was
25441
+ * prose in `message`, and an operator restarted a container on the strength of
25442
+ * it, booting the previous version.
25443
+ *
25444
+ * - `staged` — the closure is installed and validated; the node
25445
+ * restarts to apply it (`restarting:true`). NOTHING
25446
+ * runs the new code until that restart completes.
25447
+ * - `restart-scheduled` — a plain, version-preserving restart was accepted.
25448
+ * - `already-current` — the target IS the running version; nothing done.
25449
+ * - `refused` — dropped by a gate; `reason` names which.
25450
+ * - `failed` — attempted and did not land; `error` says why.
25451
+ */
25452
+ var ServerUpdateOutcomeSchema = zod.z.enum([
25453
+ "staged",
25454
+ "restart-scheduled",
25455
+ "already-current",
25456
+ "refused",
25457
+ "failed"
25458
+ ]);
25331
25459
  var ServerUpdateActionResultSchema = zod.z.object({
25332
25460
  accepted: zod.z.boolean(),
25333
25461
  targetVersion: zod.z.string().nullable(),
25334
25462
  /** True when a graceful restart was scheduled to apply the change. */
25335
25463
  restarting: zod.z.boolean(),
25336
- message: zod.z.string()
25464
+ message: zod.z.string(),
25465
+ /** See {@link ServerUpdateOutcomeSchema}. Optional for version skew. */
25466
+ outcome: ServerUpdateOutcomeSchema.optional(),
25467
+ /**
25468
+ * Stable, greppable cause for `refused` / `failed` (the engine's
25469
+ * `UpdateRefusalReason`, `staging-failed`, …). `null` on success.
25470
+ */
25471
+ reason: zod.z.string().nullable().optional(),
25472
+ /**
25473
+ * The failure itself, verbatim enough to act on (the npm output for a
25474
+ * staging install). Non-null only for `failed`.
25475
+ */
25476
+ error: zod.z.string().nullable().optional()
25337
25477
  });
25338
25478
  var serverManagementCapability = {
25339
25479
  name: "server-management",
@@ -26979,13 +27119,24 @@ var vectorStoreCapability = {
26979
27119
  /**
26980
27120
  * `videoclips` — the unified, navigable-clip surface for a camera.
26981
27121
  *
26982
- * A device-scoped WRAPPER cap (like `pipeline-analytics`): exactly one active
26983
- * provider per device, substitutable. The DEFAULT provider (registered by
27122
+ * A device-scoped wrapper COLLECTION (D549 decision 2, D554): every provider a
27123
+ * camera has is a SOURCE, and they are listed beside each other — never one
27124
+ * substituted for another. The DEFAULT provider (registered by
26984
27125
  * `addon-post-analysis`, `defaultActive: true`) composes the analytics event
26985
27126
  * log (markers + thumbnails) with the recorder's `getPlaybackManifest` — a clip
26986
27127
  * is a time-WINDOW over existing footage, never a separate file. A camera that
26987
- * exposes NATIVE onboard clips (Reolink/Hikvision NVR) can later substitute the
26988
- * wrapper provider for its device and serve its own clip catalog + URLs.
27128
+ * exposes NATIVE onboard clips (Reolink SD card, a hub's storage, HKSV) adds
27129
+ * its own catalog ALONGSIDE that one.
27130
+ *
27131
+ * The mount stays per-device (`resolveCapMount` → `device-scoped`): the fan-out
27132
+ * is over the DEVICE's sources, resolved from the registry's per-device
27133
+ * collection, never over every provider in the cluster. `listClips` is the
27134
+ * union of those sources, in newest-first order, with NO dedup and no
27135
+ * best-source pick — a clip held in two storages appears twice, once per
27136
+ * source, and every row's {@link ClipSchema.source} says which. That is the
27137
+ * operator's decision, stated so it is not "fixed" later.
27138
+ * {@link videoclipsCapability.methods.getClipPlayback} dispatches by the
27139
+ * source namespace inside the clip id, which is why ids are self-contained.
26989
27140
  *
26990
27141
  * A `Clip` is purely time-based (subtree-blind): playback resolves segments by
26991
27142
  * temporal overlap, so the API never decides `continuous` vs `events`.
@@ -27073,41 +27224,299 @@ var ClipSchema = zod.z.object({
27073
27224
  holes: zod.z.array(zod.z.object({
27074
27225
  startMs: zod.z.number(),
27075
27226
  endMs: zod.z.number()
27076
- })).optional()
27227
+ })).optional(),
27228
+ /**
27229
+ * A URL that MAY produce this clip's still, asked only for rows actually on
27230
+ * screen. The opposite of {@link ClipSchema.thumbnail}: that one is VOUCHED
27231
+ * (a JPEG is already on disk), this one is an offer. The route answers 200
27232
+ * with the image, or 204 with `x-camstack-reason` when it could not mint one
27233
+ * — a surface latches that refusal to the instant rather than retrying.
27234
+ *
27235
+ * Never both: a clip with a vouched `thumbnail` needs no mint.
27236
+ */
27237
+ thumbnailMint: zod.z.string().optional(),
27238
+ /**
27239
+ * Why no still will be produced for this clip RIGHT NOW — set when the
27240
+ * provider already knows, so the surface draws the glyph and the reason
27241
+ * instead of firing a mint that cannot succeed.
27242
+ *
27243
+ * `sleeping` — a standalone battery camera; a read would be a wake (D549 4).
27244
+ * `camera-refused` — the camera answered the CoverPreview with a refusal.
27245
+ * `no-keyframe` — the window holds no decodable I-frame (an event that sits
27246
+ * inside no file is the measured case).
27247
+ * `unsupported` — this source cannot mint stills at all.
27248
+ */
27249
+ thumbnailUnavailable: zod.z.object({ reason: zod.z.enum([
27250
+ "sleeping",
27251
+ "camera-refused",
27252
+ "no-keyframe",
27253
+ "unsupported"
27254
+ ]) }).optional(),
27255
+ /**
27256
+ * When the catalog this row came from was last CONFIRMED against the device.
27257
+ * Absent means "this row was read live". A persisted catalog served while a
27258
+ * camera sleeps carries the age it really has — a cached list is never drawn
27259
+ * as current (D549 13).
27260
+ */
27261
+ catalogAsOf: zod.z.number().optional(),
27262
+ /**
27263
+ * The camera's OWN type strings for this clip, all of them, unmapped
27264
+ * (`md`, `people`, `dog_cat`, `sched`, …). Kept beside {@link labels}
27265
+ * because a firmware inventing a type must not vanish: the mapping into our
27266
+ * filter vocabulary is lossy on purpose and this is the lossless copy
27267
+ * (D549 20).
27268
+ */
27269
+ nativeTypes: zod.z.array(zod.z.string()).optional(),
27270
+ /**
27271
+ * What the subject DID — `crossline`, `intrude`, `loitering`. A behaviour
27272
+ * travels BESIDE a class, never instead of one, and the class filter ignores
27273
+ * it: "a person crossed a line" is still a person (D549 20).
27274
+ */
27275
+ behaviours: zod.z.array(zod.z.string()).optional(),
27276
+ /**
27277
+ * The same recording as two files — the sub twin (what the row and its
27278
+ * thumbnail are) and its main twin, matched at listing time so a quality
27279
+ * change never re-searches the camera (D549 15). `getClipPlayback`'s
27280
+ * `profile` picks between them: `low | mid` → sub, `high` → main.
27281
+ */
27282
+ streams: zod.z.object({
27283
+ sub: zod.z.object({
27284
+ id: zod.z.string(),
27285
+ bytes: zod.z.number().optional()
27286
+ }).optional(),
27287
+ main: zod.z.object({
27288
+ id: zod.z.string(),
27289
+ bytes: zod.z.number().optional()
27290
+ }).optional()
27291
+ }).optional(),
27292
+ /** The camera says it holds a sub-stream copy of this recording. */
27293
+ supportSub: zod.z.boolean().optional(),
27294
+ /**
27295
+ * Whether this row has bytes behind it. ABSENT means yes — every clip that
27296
+ * IS a file is playable, and only a source that lists EVENTS can produce a
27297
+ * row with nothing to play (the measured case: 1 of 29 hub events on 3628
27298
+ * fell inside no file). Such a row is shown, never dropped and never offered
27299
+ * as playable-then-failing (D549 19).
27300
+ */
27301
+ playable: zod.z.boolean().optional(),
27302
+ /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
27303
+ unplayableReason: zod.z.string().optional()
27077
27304
  });
27078
27305
  var ClipPlaybackSchema = zod.z.object({
27079
- /** HLS master URL through the hub data-plane (Range + token in path). */
27306
+ /**
27307
+ * A media URL through the hub data-plane. {@link ClipPlaybackSchema.format}
27308
+ * says what kind — an HLS master playlist for a recording-derived clip, a
27309
+ * progressive MP4 for a native file the vendor addon muxed and serves with
27310
+ * `Range`. The URL is NOT a credential: both planes are registered
27311
+ * `access: 'authenticated'` and the hub's `/addon/<id>/<prefix>` proxy takes
27312
+ * the session cookie, so nothing is appended to it (D549 22).
27313
+ */
27080
27314
  playbackUrl: zod.z.string(),
27315
+ /**
27316
+ * How to play {@link playbackUrl}. Absent means `hls` — the shape every
27317
+ * existing consumer already assumes. A player that branches on this is the
27318
+ * one change a native clip needs; a player that ignores it will hand an MP4
27319
+ * to hls.js and fail parsing it as a manifest.
27320
+ */
27321
+ format: zod.z.enum(["hls", "mp4"]).optional(),
27322
+ /**
27323
+ * Which twin was actually served. A clip that holds only one stream answers
27324
+ * with the one it has, and the surface SAYS so — a missing main twin is
27325
+ * never served silently as if it were the asked-for quality (D549 15).
27326
+ */
27327
+ served: require_sleep.CamProfileSchema.optional(),
27081
27328
  /** Optional LAN/remote alternates for the same clip. */
27082
27329
  playbackEndpoints: zod.z.array(zod.z.string()).optional(),
27083
27330
  token: zod.z.string().optional()
27084
27331
  });
27332
+ /**
27333
+ * Why a source cannot answer right now — per SOURCE, never fleet-wide.
27334
+ *
27335
+ * `ok` is the only state whose clip list may be read as complete. The other
27336
+ * three exist because an empty list from a sleeping camera reads as "this
27337
+ * camera has no recordings", which is the defect this whole line of work is
27338
+ * about: a battery camera nobody woke (`sleeping`), a camera that could not be
27339
+ * reached at all (`unreachable`, also what a provider that THREW reports), a
27340
+ * camera whose SD card is not mounted (`no-storage`, measured on 640 —
27341
+ * `HddInfo mount 0`, honestly nothing to list rather than "no clips"), and a
27342
+ * camera whose OWN index disagrees with its OWN calendar (`index-empty`,
27343
+ * measured on 618: the calendar marks 3–20 September, days 6–20 list zero
27344
+ * files on both streams and both filters, with 177 GB free). That last one is
27345
+ * a defect ON THE CAMERA, and the only honest thing a provider can do is say
27346
+ * which days it asked for and got nothing — "no clips" would be a lie about a
27347
+ * card that is full of them.
27348
+ */
27349
+ var ClipSourceAvailabilitySchema = zod.z.object({
27350
+ state: zod.z.enum([
27351
+ "ok",
27352
+ "sleeping",
27353
+ "unreachable",
27354
+ "no-storage",
27355
+ "index-empty"
27356
+ ]),
27357
+ /** Free text, shown verbatim. Names the camera's own refusal when there is one. */
27358
+ reason: zod.z.string().optional(),
27359
+ /** When this source's catalog was last CONFIRMED. A cached list is never
27360
+ * drawn as current: the surface shows the age whenever it is older than the
27361
+ * refresh interval. */
27362
+ catalogAsOf: zod.z.number().optional()
27363
+ });
27364
+ /**
27365
+ * One SOURCE of clips for a camera — a row of the picker, and the namespace
27366
+ * every clip id from it is prefixed with (`analytics`,
27367
+ * `native:reolink:onboard`, `hksv`, …).
27368
+ *
27369
+ * A provider lists the sources IT serves for that device, and answers for each
27370
+ * of them whether it can answer at all. A provider with nothing to offer on a
27371
+ * camera returns `[]` — it is not that camera's business.
27372
+ */
27373
+ var ClipSourceSchema = zod.z.object({
27374
+ /** The value this source stamps on {@link ClipSchema.source}, and the prefix
27375
+ * of every clip id it mints. `getClipPlayback` routes on it. */
27376
+ source: zod.z.string(),
27377
+ /** Operator-facing name of the source ("CamStack recordings", "SD card"). */
27378
+ label: zod.z.string(),
27379
+ /**
27380
+ * The addon that SERVES this row.
27381
+ *
27382
+ * A surface cannot otherwise resolve a source to who answers it, and the
27383
+ * alternative — a `native:reolink:* → provider-reolink` table inside the
27384
+ * widget — is a second authority on provider identity living in the one
27385
+ * package with no business knowing it, wrong the day a third source appears
27386
+ * (D557). One addon may serve SEVERAL sources: `addon-provider-reolink`
27387
+ * answers a hub child with both `native:reolink:onboard` and
27388
+ * `native:reolink:hub`, which is why the per-device switch is keyed by
27389
+ * SOURCE and not by this (D555).
27390
+ *
27391
+ * Optional for version skew only. The collection dispatcher stamps it from
27392
+ * the registry, so a row that travelled through the fan-out carries the
27393
+ * authoritative id whatever the provider filled in.
27394
+ */
27395
+ addonId: zod.z.string().optional(),
27396
+ /**
27397
+ * Which API this source resolved to FOR THIS CAMERA, when it has a choice.
27398
+ *
27399
+ * A source may cover one store through more than one surface — the Reolink
27400
+ * provider reads a hub child through the parent's event log and a standalone
27401
+ * through its own file list, because that is what each camera answers. The
27402
+ * CHOICE is the provider's, made from what the camera is, and is never a row
27403
+ * the operator has to understand; but it is REPORTED, because a source that
27404
+ * silently reads a different API on two cameras and then behaves differently
27405
+ * is the thing nobody can debug later. Absent when the source has only one
27406
+ * way to read its store.
27407
+ */
27408
+ via: zod.z.string().optional(),
27409
+ availability: ClipSourceAvailabilitySchema,
27410
+ /**
27411
+ * The operator switched this source OFF for this camera.
27412
+ *
27413
+ * Deliberately NOT a member of {@link ClipSourceAvailabilitySchema}'s
27414
+ * vocabulary. That enum models what the source CAN do — a sleeping camera, an
27415
+ * unmounted card, an index that disagrees with its own calendar — and a
27416
+ * switched-off source could answer perfectly well; the operator decided it
27417
+ * should not. Folding the choice in is how `disabled` and `broken` stop being
27418
+ * distinguishable, which is the D62 rule this repo has already paid for twice:
27419
+ * an off switch is REPORTED off (`CameraStatus.switchedOff`, the same word),
27420
+ * and disabled must never look like broken. It is also what lets every
27421
+ * exhaustive consumer of the availability enum keep compiling.
27422
+ *
27423
+ * A switched-off source contributes NO clips (`listClips` never calls it) and
27424
+ * its row carries no `catalogAsOf`: nothing confirms a catalog it is not
27425
+ * allowed to serve, and a frozen age that can only grow draws a stalling
27426
+ * source rather than an off switch.
27427
+ *
27428
+ * The row survives BECAUSE it is the control the operator switches the source
27429
+ * back on from — D554 decision 4's rule ("a source that cannot answer
27430
+ * produces a ROW, not an absence") applied to the one case D556 carved out of
27431
+ * it, and D557's own kept property ("a source is never hidden, only its
27432
+ * rows"). Absent means on.
27433
+ *
27434
+ * A provider never sets this — like {@link ClipSourceSchema.addonId} it is
27435
+ * stamped by the collection dispatcher, which holds the registry's projection
27436
+ * of the persisted authority (D556) and is the only place that knows it.
27437
+ */
27438
+ switchedOff: zod.z.boolean().optional()
27439
+ });
27085
27440
  var videoclipsCapability = {
27086
27441
  name: "videoclips",
27087
27442
  scope: "device",
27088
- mode: "singleton",
27443
+ mode: "collection",
27089
27444
  kind: "wrapper",
27090
27445
  defaultActive: true,
27091
27446
  /** A clip is a window over a camera's footage — the cap is meaningless on a
27092
27447
  * sensor, a button or an event emitter, and the `defaultActive` auto-bind
27093
27448
  * reads this to decide which devices it may claim. */
27094
27449
  deviceTypes: [require_sleep.DeviceType.Camera],
27450
+ /**
27451
+ * The Clips section of a camera's device details is FRAMEWORK-DERIVED (D14):
27452
+ * the aggregator turns this declaration into the `type:'widget'` section and
27453
+ * `DeviceDetail.tsx` is never edited. `videoclips` is the first WRAPPER cap
27454
+ * to declare one — every previous `host/` widget cap is `deviceNative` — so
27455
+ * `device-config-widget-wrapped-binding.spec.ts` pins that a `kind:'wrapped'`
27456
+ * binding entry derives the same section a native one does.
27457
+ *
27458
+ * `topTab` because the browser owns the whole pane (a day's clips, a source
27459
+ * list and a player), exactly as `ptz` does; the widget itself is
27460
+ * `host/clips-browser` in ui-library's `HOST_WIDGETS`.
27461
+ */
27462
+ deviceConfig: { ui: {
27463
+ kind: "widget",
27464
+ widgetId: "host/clips-browser",
27465
+ tab: "clips",
27466
+ topTab: true,
27467
+ label: "Clips",
27468
+ order: 0
27469
+ } },
27095
27470
  methods: {
27096
27471
  listClips: require_sleep.method(zod.z.object({
27097
27472
  deviceId: zod.z.number(),
27098
27473
  since: zod.z.number(),
27099
27474
  until: zod.z.number(),
27100
- limit: zod.z.number().int().positive().optional()
27475
+ limit: zod.z.number().int().positive().optional(),
27476
+ /**
27477
+ * View filter over {@link ClipSourceSchema.source} values — the
27478
+ * picker's selection, forwarded so a provider need not list what
27479
+ * nobody is looking at. ABSENT means every source this camera has,
27480
+ * which is the honest default for a surface whose whole point is that
27481
+ * nothing is hidden (D554 3). A provider with one source ignores it.
27482
+ */
27483
+ sources: zod.z.array(zod.z.string()).optional()
27101
27484
  }), zod.z.array(ClipSchema).readonly(), {
27102
27485
  kind: "query",
27103
- auth: "admin"
27486
+ auth: "protected"
27487
+ }),
27488
+ /**
27489
+ * The sources this camera has, WITH the reason any of them cannot answer.
27490
+ *
27491
+ * Asked separately from `listClips` because an empty clip list is
27492
+ * ambiguous and this is the only place the ambiguity is resolved: every
27493
+ * bound provider contributes its own rows, and a provider that could not
27494
+ * be reached at all still produces one row saying so. A surface that draws
27495
+ * "no clips" without reading this is drawing a guess.
27496
+ */
27497
+ listSources: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.array(ClipSourceSchema).readonly(), {
27498
+ kind: "query",
27499
+ auth: "protected"
27104
27500
  }),
27105
27501
  getClipPlayback: require_sleep.method(zod.z.object({
27106
27502
  deviceId: zod.z.number(),
27107
- clipId: zod.z.string()
27503
+ clipId: zod.z.string(),
27504
+ /**
27505
+ * Which twin to serve, on the ONE quality scale the system already has
27506
+ * (`CamProfileSchema`). `low | mid` → the sub file, `high` → the main
27507
+ * twin; both ids are already on the row so this never re-searches the
27508
+ * camera. Absent means the provider's own default (the sub file, which
27509
+ * every source is measured to hold).
27510
+ *
27511
+ * `auto` is deliberately NOT accepted here: a stored file has no
27512
+ * broker session, so the adaptive tier cannot be resolved for it. The
27513
+ * viewer resolves `auto` to a profile the same way live does, before
27514
+ * it calls (D549 15).
27515
+ */
27516
+ profile: require_sleep.CamProfileSchema.optional()
27108
27517
  }), ClipPlaybackSchema, {
27109
27518
  kind: "query",
27110
- auth: "admin"
27519
+ auth: "protected"
27111
27520
  })
27112
27521
  }
27113
27522
  };
@@ -50586,6 +50995,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
50586
50995
  addonId: null,
50587
50996
  access: "view"
50588
50997
  },
50998
+ "videoclips.listSources": {
50999
+ capName: "videoclips",
51000
+ capScope: "device",
51001
+ addonId: null,
51002
+ access: "view"
51003
+ },
50589
51004
  "viewerUi.getStaticDir": {
50590
51005
  capName: "viewer-ui",
50591
51006
  capScope: "system",
@@ -52867,6 +53282,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
52867
53282
  form: "single",
52868
53283
  optional: false
52869
53284
  }],
53285
+ "videoclips.listSources": [{
53286
+ name: "deviceId",
53287
+ form: "single",
53288
+ optional: false
53289
+ }],
52870
53290
  "waterHeater.setAway": [{
52871
53291
  name: "deviceId",
52872
53292
  form: "single",
@@ -58530,6 +58950,8 @@ exports.ClientNetworkStatsSchema = ClientNetworkStatsSchema;
58530
58950
  exports.ClimateControlStatusSchema = ClimateControlStatusSchema;
58531
58951
  exports.ClipPlaybackSchema = ClipPlaybackSchema;
58532
58952
  exports.ClipSchema = ClipSchema;
58953
+ exports.ClipSourceAvailabilitySchema = ClipSourceAvailabilitySchema;
58954
+ exports.ClipSourceSchema = ClipSourceSchema;
58533
58955
  exports.ClusterAddonNodeDeploymentSchema = ClusterAddonNodeDeploymentSchema;
58534
58956
  exports.ClusterAddonStatusEntrySchema = ClusterAddonStatusEntrySchema;
58535
58957
  exports.CollectionColumnSchema = CollectionColumnSchema;
@@ -59280,6 +59702,7 @@ exports.ServerPackageStatusSchema = ServerPackageStatusSchema;
59280
59702
  exports.ServerRollbackInfoSchema = ServerRollbackInfoSchema;
59281
59703
  exports.ServerUpdateActionResultSchema = ServerUpdateActionResultSchema;
59282
59704
  exports.ServerUpdateCheckResultSchema = ServerUpdateCheckResultSchema;
59705
+ exports.ServerUpdateOutcomeSchema = ServerUpdateOutcomeSchema;
59283
59706
  exports.ServerUpdateStateSchema = ServerUpdateStateSchema;
59284
59707
  exports.SetLoggingSettingsInputSchema = SetLoggingSettingsInputSchema;
59285
59708
  exports.SetSiteLocationInputSchema = SetSiteLocationInputSchema;
@@ -59620,6 +60043,7 @@ exports.egressTransportFromRequest = egressTransportFromRequest;
59620
60043
  exports.embeddingEncoderCapability = embeddingEncoderCapability;
59621
60044
  exports.emitDownForOwnedCaps = require_sleep.emitDownForOwnedCaps;
59622
60045
  exports.emitReadiness = require_sleep.emitReadiness;
60046
+ exports.emitsCollectionSelector = emitsCollectionSelector;
59623
60047
  exports.encodeProfileFromStreamShape = encodeProfileFromStreamShape;
59624
60048
  exports.encodeVectorBase64 = encodeVectorBase64;
59625
60049
  exports.enumSensorCapability = enumSensorCapability;
@@ -59771,6 +60195,7 @@ exports.notifierCapability = notifierCapability;
59771
60195
  exports.numericSensorCapability = numericSensorCapability;
59772
60196
  exports.oauthIntegrationCapability = oauthIntegrationCapability;
59773
60197
  exports.objectInputDeclaresAddonId = objectInputDeclaresAddonId;
60198
+ exports.objectInputDeclaresRequiredDeviceId = objectInputDeclaresRequiredDeviceId;
59774
60199
  exports.occupancyScope = occupancyScope;
59775
60200
  exports.osdCapability = osdCapability;
59776
60201
  exports.osdManagerCapability = osdManagerCapability;