@camstack/system 1.2.59 → 1.2.61

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 (55) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  23. package/dist/builtins/local-network/local-network.addon.js +1 -1
  24. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  25. package/dist/builtins/loki-logging/index.js +1 -1
  26. package/dist/builtins/loki-logging/index.mjs +1 -1
  27. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  29. package/dist/builtins/platform-probe/index.js +1 -1
  30. package/dist/builtins/platform-probe/index.mjs +1 -1
  31. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  33. package/dist/builtins/snapshot/index.js +1 -1
  34. package/dist/builtins/snapshot/index.mjs +1 -1
  35. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/prefix-range.d.ts +50 -0
  38. package/dist/builtins/sqlite-storage/query-bounds.d.ts +116 -0
  39. package/dist/builtins/sqlite-storage/sqlite-pragmas.d.ts +12 -0
  40. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +38 -2
  41. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +281 -19
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +281 -19
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  45. package/dist/builtins/system-config/system-config.addon.js +1 -1
  46. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  47. package/dist/builtins/winston-logging/index.js +1 -1
  48. package/dist/builtins/winston-logging/index.mjs +1 -1
  49. package/dist/{dist-CV6XTApk.js → dist-BN681PgO.js} +974 -18
  50. package/dist/{dist-BqJJWCS8.mjs → dist-L6QPJWsr.mjs} +974 -18
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.js +131 -9
  53. package/dist/index.mjs +126 -9
  54. package/dist/kernel/heap-watch.d.ts +48 -1
  55. package/package.json +1 -1
@@ -1814,7 +1814,20 @@ var BrokerStatsSchema = zod.z.object({
1814
1814
  sampleRate: zod.z.number(),
1815
1815
  channels: zod.z.number(),
1816
1816
  supported: zod.z.boolean()
1817
- }).nullable().optional()
1817
+ }).nullable().optional(),
1818
+ /**
1819
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
1820
+ * distributing none of the device's audio, on live or recording.
1821
+ *
1822
+ * Present so a silent camera can be told apart from a broken one on the
1823
+ * stream panel itself, without cross-referencing the switch group: a
1824
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
1825
+ * working exactly as asked. `audioMutedDropped` counts the audio units
1826
+ * thrown away since the current dial — it is how you confirm from stats
1827
+ * alone that the mute is on the packet path and not merely persisted.
1828
+ */
1829
+ audioMuted: zod.z.boolean().optional(),
1830
+ audioMutedDropped: zod.z.number().optional()
1818
1831
  });
1819
1832
  /**
1820
1833
  * Exporter-facing "profile restream" entry. Returned by
@@ -3206,6 +3219,19 @@ zod.z.object({
3206
3219
  * | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
3207
3220
  * | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
3208
3221
  * | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
3222
+ * | `broker-audio` | `streamBroker.setDeviceAudioMute` → `DeviceOverride.audioMuted` | `StreamBroker.setAudioMuted` drops the audio plane at the source: no `type:'audio'` packet leaves `fanOutEncoded`, no RTP reaches the restreamer, and the restreamer serves the video-only SDP |
3223
+ *
3224
+ * ## `device-audio` and `broker-audio` are two functions, not two knobs
3225
+ *
3226
+ * They look adjacent and they are not the same control ([D83](../../../../docs/decisions/adr-0083.md)):
3227
+ * `device-audio` writes the CAMERA, so it is hardware privacy — the microphone
3228
+ * genuinely stops, it survives CamStack entirely, and it costs a multi-second
3229
+ * encoder restart on every flip. `broker-audio` writes THIS server, so it is
3230
+ * instant, vendor-independent and reversible without touching the camera, and
3231
+ * a camera that ignores or lacks the ISAPI/Reolink control is still silenced.
3232
+ * D62 forbids a second switch that *disagrees* with the first; these two
3233
+ * cannot disagree, because neither reads the other's store — the camera holds
3234
+ * one, the broker holds the other, and each reports its own fact.
3209
3235
  *
3210
3236
  * ## The two switches whose authority is not on this server
3211
3237
  *
@@ -3267,6 +3293,7 @@ var CameraSwitchIdSchema = zod.z.enum([
3267
3293
  "object-detection",
3268
3294
  "privacy-mask",
3269
3295
  "device-audio",
3296
+ "broker-audio",
3270
3297
  "audio-analysis",
3271
3298
  "recording",
3272
3299
  "notifications"
@@ -3292,7 +3319,8 @@ var CameraSwitchAuthoritySchema = zod.z.discriminatedUnion("kind", [
3292
3319
  zod.z.object({
3293
3320
  kind: zod.z.literal("camera-mask"),
3294
3321
  capName: zod.z.string()
3295
- })
3322
+ }),
3323
+ zod.z.object({ kind: zod.z.literal("broker-audio-mute") })
3296
3324
  ]);
3297
3325
  /**
3298
3326
  * Why a switch is not offered for this camera. Rendered instead of the
@@ -6031,7 +6059,44 @@ var streamBrokerCapability = {
6031
6059
  kind: "mutation",
6032
6060
  auth: "admin"
6033
6061
  }),
6034
- isRtspEnabled: method(zod.z.object({ brokerId: zod.z.string() }), zod.z.boolean())
6062
+ isRtspEnabled: method(zod.z.object({ brokerId: zod.z.string() }), zod.z.boolean()),
6063
+ /**
6064
+ * ── Per-device audio-plane policy (D83) ───────────────────────────
6065
+ *
6066
+ * The BROKER-side mute: while `muted`, this node distributes none of
6067
+ * the device's audio, on any plane it serves — live (WebRTC / encoded
6068
+ * subscribers) AND recording (the RTSP restreamer the recorder pulls,
6069
+ * which additionally serves the video-only SDP so the recorder's ffmpeg
6070
+ * never declares an audio stream it will not receive).
6071
+ *
6072
+ * Keyed by `deviceId`, not `brokerId`: every one of a camera's streams
6073
+ * carries the same microphone, and a per-stream answer would let main
6074
+ * and sub disagree about whether the camera is silent.
6075
+ *
6076
+ * The state lives in the broker's existing `DeviceOverride` blob — no
6077
+ * new store — and the mute is applied to a fresh broker at creation, so
6078
+ * a restart, a re-dial or a catalog republish never un-mutes a camera.
6079
+ */
6080
+ getDeviceAudioMute: method(zod.z.object({ deviceId: zod.z.number().int() }), zod.z.object({
6081
+ muted: zod.z.boolean(),
6082
+ /**
6083
+ * How many live non-derived brokers currently hold the mute. Purely
6084
+ * diagnostic: `muted` is the policy and is authoritative on its own
6085
+ * (it applies to brokers that do not exist yet), while this says
6086
+ * whether anything is presently being silenced.
6087
+ */
6088
+ appliedBrokers: zod.z.number().int().nonnegative()
6089
+ })),
6090
+ setDeviceAudioMute: method(zod.z.object({
6091
+ deviceId: zod.z.number().int(),
6092
+ muted: zod.z.boolean()
6093
+ }), zod.z.object({
6094
+ muted: zod.z.boolean(),
6095
+ appliedBrokers: zod.z.number().int().nonnegative()
6096
+ }), {
6097
+ kind: "mutation",
6098
+ auth: "admin"
6099
+ })
6035
6100
  },
6036
6101
  events: {
6037
6102
  onCamStreamDemand: event(zod.z.object({
@@ -12883,6 +12948,30 @@ var TrackSourceSchema = zod.z.enum([
12883
12948
  "audio"
12884
12949
  ]);
12885
12950
  /**
12951
+ * Where a track sits in the RETRAIN lifecycle (D81).
12952
+ *
12953
+ * - `none` — never marked, or un-marked. Evictable.
12954
+ * - `staging` — the operator wants this track as training material and has not
12955
+ * finished with it. **This is the only state retention holds**: the track and
12956
+ * everything it owns (object events, crops, keyframes, CLIP vector) survive
12957
+ * the device's age window.
12958
+ * - `trained` — the retrain page has taken what it needed. The frames it chose
12959
+ * were COPIED into the retrain dataset at selection time, so the dataset no
12960
+ * longer depends on the track's media and the track becomes EVICTABLE again.
12961
+ * Terminal for the plain `markForTrain` toggle: returning it to `staging` is
12962
+ * a deliberate action of the retrain page, not a side effect of a checkbox.
12963
+ *
12964
+ * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
12965
+ * the store's filter language has only positive equality and `whereIn` — no
12966
+ * negation, no IS NULL — so a NULL would be unselectable by ANY predicate and
12967
+ * would make the entire pre-column history immortal in one deploy.
12968
+ */
12969
+ var RetrainStatusSchema = zod.z.enum([
12970
+ "none",
12971
+ "staging",
12972
+ "trained"
12973
+ ]);
12974
+ /**
12886
12975
  * Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
12887
12976
  * by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
12888
12977
  * so the two surfaces cannot drift.
@@ -12892,18 +12981,31 @@ var TrackSourceSchema = zod.z.enum([
12892
12981
  * columns existed read as absent, and a consumer that needs a boolean should say
12893
12982
  * `flag === true`, not `flag !== false`.
12894
12983
  *
12895
- * What the flags DO is deliberately UNDEFINED at the time of writing: they are
12896
- * operator curation, and the behaviour they drive will be specified separately.
12897
- * In particular a `markForTrain` track is NOT pinned against retention — see
12898
- * `docs/decisions/adr-0059.md` for why that is a store-level change, not a flag.
12984
+ * `markForTrain` is the WIRE FACE of {@link RetrainStatusSchema}, not a column:
12985
+ * it is exactly `retrainStatus === 'staging'`, in both directions. Writing
12986
+ * `true` moves `none → staging`, writing `false` moves `staging none`, and a
12987
+ * `trained` track reports `false` while refusing both writes. The boolean is
12988
+ * kept because three surfaces drive a toggle off it; anything that needs to tell
12989
+ * "never marked" from "already trained" must read `retrainStatus`.
12990
+ *
12991
+ * `debug` does NOT pin; it is attention, not durability.
12899
12992
  */
12900
12993
  var TrackFlagFields = {
12901
- /** Operator marked this track as training material. */
12994
+ /** Operator marked this track as training material — i.e. `retrainStatus` is
12995
+ * `'staging'`. */
12902
12996
  markForTrain: zod.z.boolean().optional(),
12903
12997
  /** Operator marked this track for diagnostic attention. */
12904
12998
  debug: zod.z.boolean().optional()
12905
12999
  };
12906
13000
  /**
13001
+ * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
13002
+ * Deliberately NOT part of {@link TrackFlagFields}: that group also builds the
13003
+ * write patch, and the status is not something the toggle sets — it is what the
13004
+ * toggle's boolean is derived from. Absent on an in-RAM track never touched;
13005
+ * always present on a persisted row (the column default materialises `'none'`).
13006
+ */
13007
+ var TrackRetrainFields = { retrainStatus: RetrainStatusSchema.optional() };
13008
+ /**
12907
13009
  * The write half: a PARTIAL patch. An omitted key is left untouched, so setting
12908
13010
  * one flag can never clear the other — the toggles are independent and are
12909
13011
  * driven from three surfaces that do not know about each other.
@@ -12917,13 +13019,92 @@ var TrackFlagsPatchSchema = zod.z.object(TrackFlagFields);
12917
13019
  var TrackFlagsSchema = zod.z.object({
12918
13020
  trackId: zod.z.string(),
12919
13021
  markForTrain: zod.z.boolean(),
12920
- debug: zod.z.boolean()
13022
+ debug: zod.z.boolean(),
13023
+ /** The lifecycle state the boolean was derived from. Required here (unlike on
13024
+ * a track row) because this shape is only ever produced by the write body,
13025
+ * which always knows it — and a surface that has just written needs to render
13026
+ * `trained` without a re-fetch. */
13027
+ retrainStatus: RetrainStatusSchema
13028
+ });
13029
+ zod.z.union([zod.z.literal(1), zod.z.literal(2)]);
13030
+ /**
13031
+ * WHO decided a label, and when. Carried per tier so a value can be traced to
13032
+ * the step and model that produced it — which is what makes the write rule
13033
+ * arguable after the fact ("why is 592's label `dog` and not `Canis lupus`?")
13034
+ * and what lets a migrated, UNATTRIBUTED value be told apart from a real one.
13035
+ *
13036
+ * `stepId` is the pipeline step id (`animal-classifier`, `bird-classifier`,
13037
+ * `plate-ocr`, `face-embedding`, `object-detection`), or the sentinel
13038
+ * `migration:4g` for a value the 4g migration moved from the single-slot era —
13039
+ * that value has no provenance, and the write rule lets ANY properly-attributed
13040
+ * write of the same tier replace it regardless of score.
13041
+ */
13042
+ var LabelAttributionSchema = zod.z.object({
13043
+ stepId: zod.z.string(),
13044
+ modelId: zod.z.string().optional(),
13045
+ decidedAt: zod.z.number()
13046
+ });
13047
+ /**
13048
+ * The TIERED label model (roadmap 4g), spread into `TrackSchema` and
13049
+ * `ObjectEventSchema` from ONE place so the two surfaces cannot drift — a
13050
+ * track and its events always answer the same question the same way.
13051
+ *
13052
+ * Two scalar columns, not an array: every consumer wants "the coarse one" or
13053
+ * "the fine one", and an array made both a scan. `label` is tier 1, `subLabel`
13054
+ * is tier 2, and each carries its own score + attribution.
13055
+ *
13056
+ * **Reading it.** What a human should be shown is `subLabel ?? label` — the
13057
+ * finest thing known. Before 4g the single `label` column held the finest
13058
+ * value, so a consumer that has not been updated reads the tier-1 slot and
13059
+ * shows nothing on a species-only row; that is why the migration puts every
13060
+ * pre-4g value in tier 2 (it cannot regress a display that reads the fallback)
13061
+ * and why the read surfaces were changed in the same train.
13062
+ *
13063
+ * **Writing it.** The slots are independent, which is the whole point: a
13064
+ * tier-1 write (`bird`) can never overwrite a tier-2 value (`Turdus
13065
+ * migratorius`), so fineness cannot regress by construction. Within a tier the
13066
+ * higher score wins. One rule, one implementation — see
13067
+ * `pipeline/label-tier.ts` in addon-post-analysis.
13068
+ */
13069
+ var TieredLabelFields = {
13070
+ /** Tier 1 — the sub-class. See {@link LabelTierSchema}. */
13071
+ label: zod.z.string().optional(),
13072
+ /** Confidence of the tier-1 value, as reported by the deciding step. */
13073
+ labelScore: zod.z.number().optional(),
13074
+ /** Provenance of the tier-1 value. See {@link LabelAttributionSchema}. */
13075
+ labelMeta: LabelAttributionSchema.optional(),
13076
+ /** Tier 2 — the instance. See {@link LabelTierSchema}. */
13077
+ subLabel: zod.z.string().optional(),
13078
+ /** Confidence of the tier-2 value, as reported by the deciding step. */
13079
+ subLabelScore: zod.z.number().optional(),
13080
+ /** Provenance of the tier-2 value. See {@link LabelAttributionSchema}. */
13081
+ subLabelMeta: LabelAttributionSchema.optional()
13082
+ };
13083
+ /** Per-camera slice of a training-export estimate. */
13084
+ var TrainingExportDeviceTotalsSchema = zod.z.object({
13085
+ deviceId: zod.z.number(),
13086
+ tracks: zod.z.number().int(),
13087
+ files: zod.z.number().int(),
13088
+ bytes: zod.z.number().int()
13089
+ });
13090
+ /**
13091
+ * What a training export WOULD contain. Computed from media index rows only —
13092
+ * no blob is read to produce this.
13093
+ */
13094
+ var TrainingExportSummarySchema = zod.z.object({
13095
+ generatedAt: zod.z.number(),
13096
+ trackCount: zod.z.number().int(),
13097
+ fileCount: zod.z.number().int(),
13098
+ byteCount: zod.z.number().int(),
13099
+ /** More marked tracks exist than a single pass carries. */
13100
+ truncated: zod.z.boolean(),
13101
+ devices: zod.z.array(TrainingExportDeviceTotalsSchema).readonly()
12921
13102
  });
12922
13103
  var TrackSchema = zod.z.object({
12923
13104
  trackId: zod.z.string(),
12924
13105
  deviceId: zod.z.number(),
12925
13106
  className: zod.z.string(),
12926
- label: zod.z.string().optional(),
13107
+ ...TieredLabelFields,
12927
13108
  producingDeviceName: zod.z.string().optional(),
12928
13109
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
12929
13110
  source: TrackSourceSchema.optional(),
@@ -12962,7 +13143,8 @@ var TrackSchema = zod.z.object({
12962
13143
  * Populated from the persisted envelope columns on historical reads;
12963
13144
  * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
12964
13145
  envelope: TrackEnvelopeSchema.optional(),
12965
- ...TrackFlagFields
13146
+ ...TrackFlagFields,
13147
+ ...TrackRetrainFields
12966
13148
  });
12967
13149
  var BaseEventFields = {
12968
13150
  id: zod.z.string(),
@@ -13043,7 +13225,7 @@ var ObjectEventSchema = zod.z.object({
13043
13225
  /** Omitted in slim projection. */
13044
13226
  trackId: zod.z.string().optional(),
13045
13227
  className: zod.z.string(),
13046
- label: zod.z.string().optional(),
13228
+ ...TieredLabelFields,
13047
13229
  /** Omitted in slim projection. */
13048
13230
  confidence: zod.z.number().optional(),
13049
13231
  /** Heavy JSON — omitted in slim projection. */
@@ -13124,6 +13306,173 @@ var MediaFileSchema = zod.z.object({
13124
13306
  * stored blob and a `?variant=thumb` rendering without fetching either.
13125
13307
  */
13126
13308
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
13309
+ /**
13310
+ * The MACRO tier of an annotation — a CLOSED set.
13311
+ *
13312
+ * This is what the exported detector predicts, so a typo here is a new class
13313
+ * with one example in it. `label` and `subLabel` are open strings by contrast:
13314
+ * the whole point of the page is teaching the model things it does not know
13315
+ * yet, and constraining that vocabulary would make it useless.
13316
+ *
13317
+ * A macro class is NEVER a label. The provider refuses a write whose `label` or
13318
+ * `subLabel` is one of these values, in any casing, because once `person`
13319
+ * exists in both tiers "every person box" stops being answerable without
13320
+ * knowing every string anyone ever typed — and the damage is retroactive.
13321
+ */
13322
+ var RetrainMacroClassSchema = zod.z.enum([
13323
+ "person",
13324
+ "vehicle",
13325
+ "animal",
13326
+ "package",
13327
+ "face",
13328
+ "plate"
13329
+ ]);
13330
+ /** A subject to learn, or a phantom to unlearn (taught by OMISSION). */
13331
+ var RetrainAnnotationKindSchema = zod.z.enum(["subject", "model_error"]);
13332
+ /** Did a human draw this box, or did the assist propose it? */
13333
+ var RetrainAnnotationSourceSchema = zod.z.enum(["operator", "assist"]);
13334
+ /** Normalised `[0,1]` rectangle against the FULL frame — the canonical form. */
13335
+ var RetrainBboxSchema = zod.z.object({
13336
+ x: zod.z.number(),
13337
+ y: zod.z.number(),
13338
+ w: zod.z.number(),
13339
+ h: zod.z.number()
13340
+ });
13341
+ /**
13342
+ * One annotated subject.
13343
+ *
13344
+ * `bbox` is normalised against the full frame, ALWAYS. The per-model shapes
13345
+ * (letterboxed root / zone-cropped package / subject-cropped classifier) are
13346
+ * derived from it at export and never stored — storing them is how one feature
13347
+ * space ends up holding two crops of the same subject (D52).
13348
+ */
13349
+ var RetrainAnnotationSchema = zod.z.object({
13350
+ id: zod.z.string(),
13351
+ trackId: zod.z.string(),
13352
+ deviceId: zod.z.number(),
13353
+ /** The COPY in retrain storage — never the source track's media key. */
13354
+ mediaKey: zod.z.string(),
13355
+ bbox: RetrainBboxSchema,
13356
+ macroClass: RetrainMacroClassSchema,
13357
+ label: zod.z.string().optional(),
13358
+ subLabel: zod.z.string().optional(),
13359
+ kind: RetrainAnnotationKindSchema,
13360
+ source: RetrainAnnotationSourceSchema,
13361
+ /** Which model proposed this box — or, on a `model_error`, drew the phantom. */
13362
+ assistModelId: zod.z.string().optional(),
13363
+ assistScore: zod.z.number().optional(),
13364
+ exportedInBatch: zod.z.string().optional(),
13365
+ createdAt: zod.z.number()
13366
+ });
13367
+ /** The write form — the server owns `id`, `createdAt` and the frame binding. */
13368
+ var RetrainAnnotationDraftSchema = RetrainAnnotationSchema.omit({
13369
+ id: true,
13370
+ trackId: true,
13371
+ deviceId: true,
13372
+ mediaKey: true,
13373
+ createdAt: true,
13374
+ exportedInBatch: true
13375
+ });
13376
+ /** A track sitting in `staging`, with everything the worklist needs to rank it. */
13377
+ var RetrainTrackSchema = zod.z.object({
13378
+ trackId: zod.z.string(),
13379
+ deviceId: zod.z.number(),
13380
+ className: zod.z.string(),
13381
+ label: zod.z.string().optional(),
13382
+ firstSeen: zod.z.number(),
13383
+ lastSeen: zod.z.number(),
13384
+ /** How many frames the dataset already holds from this track. */
13385
+ frameCount: zod.z.number().int(),
13386
+ /** How many subjects have been annotated on those frames. `0` with
13387
+ * `frameCount: 0` is exactly "staging, still to work". */
13388
+ annotationCount: zod.z.number().int()
13389
+ });
13390
+ /** A frame the picker may offer — an index row, no blob was read to produce it. */
13391
+ var RetrainFrameCandidateSchema = zod.z.object({
13392
+ mediaKey: zod.z.string(),
13393
+ kind: MediaFileKindEnum,
13394
+ timestamp: zod.z.number(),
13395
+ sizeBytes: zod.z.number().int(),
13396
+ /** A copy of this original already exists — selecting it is free and cannot
13397
+ * fail, whatever became of the original. */
13398
+ copied: zod.z.boolean()
13399
+ });
13400
+ /** A frame the dataset OWNS: bytes copied at selection time. */
13401
+ var RetrainFrameSchema = zod.z.object({
13402
+ frameId: zod.z.string(),
13403
+ deviceId: zod.z.number(),
13404
+ trackId: zod.z.string(),
13405
+ /** Provenance only. It may already point at nothing — that is expected. */
13406
+ sourceMediaKey: zod.z.string(),
13407
+ sourceKind: MediaFileKindEnum,
13408
+ sizeBytes: zod.z.number().int(),
13409
+ width: zod.z.number().int(),
13410
+ height: zod.z.number().int(),
13411
+ copiedAt: zod.z.number()
13412
+ });
13413
+ /** Why a copy-on-select could not be honoured — named, never a silent skip. */
13414
+ var RetrainCopyRefusalSchema = zod.z.enum([
13415
+ "source-missing",
13416
+ "unreadable-image",
13417
+ "write-failed"
13418
+ ]);
13419
+ var RetrainFrameSelectionSchema = zod.z.object({
13420
+ copied: zod.z.array(RetrainFrameSchema).readonly(),
13421
+ refused: zod.z.array(zod.z.object({
13422
+ sourceMediaKey: zod.z.string(),
13423
+ reason: RetrainCopyRefusalSchema
13424
+ })).readonly()
13425
+ });
13426
+ var RetrainFrameListSchema = zod.z.object({
13427
+ candidates: zod.z.array(RetrainFrameCandidateSchema).readonly(),
13428
+ copies: zod.z.array(RetrainFrameSchema).readonly(),
13429
+ /** What the page pre-selects — the native key frame when one survives. */
13430
+ autoPickMediaKey: zod.z.string().optional()
13431
+ });
13432
+ /** What the operator asked the assist to look for. */
13433
+ var RetrainAssistSubjectSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
13434
+ kind: zod.z.literal("package"),
13435
+ zone: RetrainBboxSchema.optional()
13436
+ }), zod.z.object({
13437
+ kind: zod.z.literal("objects"),
13438
+ modelId: zod.z.string(),
13439
+ minScore: zod.z.number().optional()
13440
+ })]);
13441
+ /**
13442
+ * The assist's answer — a discriminated union, because "the model saw nothing"
13443
+ * and "this node cannot run that model" lead to different next moves and a
13444
+ * nullable result cannot tell them apart.
13445
+ */
13446
+ var RetrainAssistResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
13447
+ kind: zod.z.literal("proposed"),
13448
+ modelId: zod.z.string(),
13449
+ stepId: zod.z.string(),
13450
+ minScore: zod.z.number(),
13451
+ /** Drafts, ready to edit. `source: 'assist'` until the operator touches one. */
13452
+ proposals: zod.z.array(RetrainAnnotationDraftSchema).readonly(),
13453
+ /** Returned by the runner but removed by the threshold. */
13454
+ belowThreshold: zod.z.number().int()
13455
+ }), zod.z.object({
13456
+ kind: zod.z.literal("refused"),
13457
+ /** `no-zone` is ours; the rest are the runner's own refusal vocabulary. */
13458
+ reason: zod.z.string(),
13459
+ detail: zod.z.string().optional()
13460
+ })]);
13461
+ /** The outcome of a lifecycle move owned by the retrain page. */
13462
+ var RetrainTransitionResultSchema = zod.z.object({
13463
+ trackId: zod.z.string(),
13464
+ /** Where the track ended up, whatever happened. */
13465
+ retrainStatus: RetrainStatusSchema,
13466
+ /** `false` ⇒ the move was refused or was a no-op; `reason` says which. */
13467
+ changed: zod.z.boolean(),
13468
+ reason: zod.z.enum([
13469
+ "unknown-track",
13470
+ "no-frames-copied",
13471
+ "not-staging",
13472
+ "not-trained",
13473
+ "unchanged"
13474
+ ]).optional()
13475
+ });
13127
13476
  var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
13128
13477
  var MAX_EVENT_QUERY_LIMIT = 5e3;
13129
13478
  var DeviceEventQueryInput = zod.z.object({
@@ -13178,13 +13527,14 @@ var KeyEventSchema = zod.z.object({
13178
13527
  /** Track start time (firstSeen). */
13179
13528
  timestamp: zod.z.number(),
13180
13529
  className: zod.z.string(),
13181
- label: zod.z.string().optional(),
13530
+ ...TieredLabelFields,
13182
13531
  importance: zod.z.number(),
13183
13532
  /** Highest-confidence ObjectEvent id for the track (empty when none). */
13184
13533
  bestEventId: zod.z.string(),
13185
13534
  /** Track lifetime in ms (lastSeen - firstSeen). */
13186
13535
  windowMs: zod.z.number().optional(),
13187
- ...TrackFlagFields
13536
+ ...TrackFlagFields,
13537
+ ...TrackRetrainFields
13188
13538
  });
13189
13539
  zod.z.object({
13190
13540
  trackId: zod.z.string(),
@@ -13546,11 +13896,29 @@ var pipelineAnalyticsCapability = {
13546
13896
  *
13547
13897
  * `auth: 'protected'` (the default), NOT `admin`: the viewer is an
13548
13898
  * authenticated non-admin surface and two of the three call sites are
13549
- * there. Revisit if a flag ever gains an effect that costs storage
13550
- * `deleteTracks` next door is admin for exactly that reason.
13899
+ * there. The note that used to sit here said to revisit this the day a flag
13900
+ * gained an effect that costs storage, and D81 is that day — `markForTrain`
13901
+ * now pins. It STAYS protected, and the reason is that the alternative
13902
+ * makes the feature pointless: marking a track is something you do while
13903
+ * looking at it, on the surface you were already looking at it on, and that
13904
+ * surface is the viewer. What the storage cost gets instead is a BOUND — a
13905
+ * per-device pin budget enforced in the body, refusing a new pin past the
13906
+ * limit while always allowing un-marking. `deleteTracks` next door is still
13907
+ * admin, because destroying evidence and preserving it are not symmetric.
13551
13908
  *
13552
- * Returns the RESOLVED state of both flags (absent `false`) so a caller
13553
- * can drive its toggle without a re-fetch. Rejects an unknown track.
13909
+ * `markForTrain` writes the retrain LIFECYCLE, not a boolean column: `true`
13910
+ * is `none staging`, `false` is `staging none`. A track already
13911
+ * `trained` refuses BOTH — its frames are copies inside the retrain dataset
13912
+ * and re-staging it from a generic toggle is how the same material gets
13913
+ * annotated twice under two ground truths. Returning a trained track to
13914
+ * staging is a deliberate action of the retrain page, which is also the only
13915
+ * thing that produces `trained` in the first place.
13916
+ *
13917
+ * Returns the RESOLVED state of both flags (absent → `false`) plus the
13918
+ * `retrainStatus` they were derived from, so a caller can drive its toggle
13919
+ * — and render a `trained` badge — without a re-fetch. Rejects an unknown
13920
+ * track, a new staging mark on a device already holding its full budget, and
13921
+ * any `markForTrain` write against a trained track.
13554
13922
  */
13555
13923
  setTrackFlags: method(zod.z.object({
13556
13924
  /** Log/audit scope only — the trackId is globally unique on its own. */
@@ -13614,6 +13982,237 @@ var pipelineAnalyticsCapability = {
13614
13982
  kind: "query",
13615
13983
  auth: "admin"
13616
13984
  }),
13985
+ /**
13986
+ * The CHEAP QUESTION, asked before any media moves: how big is the dataset
13987
+ * the marked (`markForTrain`) tracks would produce?
13988
+ *
13989
+ * Answered from media INDEX rows only — key, kind, size, timestamp — so it
13990
+ * costs ~2 KB of reads per track and no blob reads at all. The measured harm
13991
+ * behind D56 was a bulk pass that read and base64'd every blob a track owned
13992
+ * before deciding anything, taking hub-main to 82 s busy out of 120; an
13993
+ * export is that same I/O shape, so it inherits the same discipline: know
13994
+ * the size, then decide.
13995
+ *
13996
+ * `truncated` reports that more marked tracks exist than one pass carries.
13997
+ * Empty `deviceIds` ⇒ every device that has marked tracks.
13998
+ */
13999
+ getTrainingExportSummary: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).optional() }), TrainingExportSummarySchema, {
14000
+ kind: "query",
14001
+ auth: "admin"
14002
+ }),
14003
+ /**
14004
+ * Where to download the dataset archive.
14005
+ *
14006
+ * The BYTES do not come back through this cap — they come from the returned
14007
+ * data-plane URL, which streams a tar built entry by entry. A multi-gigabyte
14008
+ * archive base64'd through a unary RPC envelope would be held whole in
14009
+ * memory twice on a hub this repo has already OOM'd once (D9/D18 are the
14010
+ * same lesson about frames). `getDownloadUrl` on `recordingExport` is the
14011
+ * precedent, and this follows it deliberately.
14012
+ *
14013
+ * The archive contains a `manifest.json` FIRST, then the stored media
14014
+ * VERBATIM under `tracks/<deviceId>/<trackId>/…`. No crop is derived and no
14015
+ * model is run: a training set's pixels must be the pixels the pipeline saw.
14016
+ */
14017
+ getTrainingExportUrl: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).optional() }), zod.z.object({ url: zod.z.string() }), {
14018
+ kind: "query",
14019
+ auth: "admin"
14020
+ }),
14021
+ /**
14022
+ * The staging worklist for one camera, or for every camera that has one.
14023
+ *
14024
+ * Fetched ON DEMAND, over the staging set only — the page never scans
14025
+ * history, because making the working set small is the entire purpose of
14026
+ * the mark. Each row carries how many frames the dataset already holds from
14027
+ * the track and how many subjects were annotated on them, so
14028
+ * `frameCount: 0` reads as "still to work" without a second call per track.
14029
+ *
14030
+ * `auth: 'admin'`, unlike the viewer-level mark itself: marking a track is
14031
+ * curation you do while looking at it, but building the training set the
14032
+ * fleet's models are fine-tuned on is not.
14033
+ */
14034
+ listRetrainStaging: method(zod.z.object({
14035
+ /** Empty ⇒ every camera that has staging tracks. A LIST, not a single
14036
+ * `deviceId`, deliberately: `deviceId` would make this device-bound and
14037
+ * route it at one camera's owner, and "every camera" would stop being
14038
+ * expressible at all. */
14039
+ deviceIds: zod.z.array(zod.z.number()).optional(),
14040
+ limit: zod.z.number().int().min(1).max(500).optional()
14041
+ }), zod.z.array(RetrainTrackSchema).readonly(), {
14042
+ kind: "query",
14043
+ auth: "admin"
14044
+ }),
14045
+ /**
14046
+ * What a track can contribute, and what it already has.
14047
+ *
14048
+ * `candidates` are the track's whole, unannotated frames — index rows only,
14049
+ * so this is cheap. `copies` are the frames already inside the dataset, and
14050
+ * a candidate whose copy exists is marked `copied: true`: selecting it again
14051
+ * is free and CANNOT fail, whatever became of the original.
14052
+ *
14053
+ * A crop, a thumbnail and `fullFrameBoxed` are never candidates. The last
14054
+ * one matters most: it has the model's own rectangle burned into the pixels,
14055
+ * and a detector trained on it learns to find a green line.
14056
+ */
14057
+ listRetrainFrames: method(zod.z.object({ trackId: zod.z.string() }), RetrainFrameListSchema, {
14058
+ kind: "query",
14059
+ auth: "admin"
14060
+ }),
14061
+ /**
14062
+ * COPY-ON-SELECT — the write that makes `trained` safe to evict.
14063
+ *
14064
+ * Selecting a frame copies its bytes into retrain storage immediately: not
14065
+ * a reference, not a lease. Once the copy exists the dataset no longer
14066
+ * depends on the track's media, which is exactly what lets D81 hand a
14067
+ * `trained` track back to retention.
14068
+ *
14069
+ * The order inside is load-bearing and is pinned by a test: an EXISTING
14070
+ * copy is returned without touching the source, so an original that
14071
+ * evaporated blocks the selection of THAT ORIGINAL and never the copy
14072
+ * already taken. Every refusal comes back named — a dropped selection is
14073
+ * never silent, on the wire or in the log.
14074
+ */
14075
+ selectRetrainFrames: method(zod.z.object({
14076
+ deviceId: zod.z.number(),
14077
+ trackId: zod.z.string(),
14078
+ mediaKeys: zod.z.array(zod.z.string()).min(1)
14079
+ }), RetrainFrameSelectionSchema, {
14080
+ kind: "mutation",
14081
+ auth: "admin"
14082
+ }),
14083
+ /** Un-select a frame: its annotations go first, then the copy and its blob.
14084
+ * Deliberately destructive and deliberately explicit — it is the only way
14085
+ * a frame leaves the dataset before export. */
14086
+ deselectRetrainFrame: method(zod.z.object({
14087
+ deviceId: zod.z.number(),
14088
+ trackId: zod.z.string(),
14089
+ frameId: zod.z.string()
14090
+ }), zod.z.object({
14091
+ removed: zod.z.boolean(),
14092
+ removedAnnotations: zod.z.number().int()
14093
+ }), {
14094
+ kind: "mutation",
14095
+ auth: "admin"
14096
+ }),
14097
+ /**
14098
+ * The pixels of ONE copied frame, base64.
14099
+ *
14100
+ * Through the cap rather than a data plane because it is genuinely one
14101
+ * frame at a time, on demand, at human speed — the shape D9/D18 permit
14102
+ * (what they forbid is frames crossing a boundary at frame RATE). The
14103
+ * annotation canvas needs the image and its exact dimensions in the same
14104
+ * answer: a canvas that places a normalised box against a size it guessed
14105
+ * draws every box in the wrong place.
14106
+ */
14107
+ getRetrainFrameImage: method(zod.z.object({ frameId: zod.z.string() }), zod.z.object({
14108
+ base64: zod.z.string(),
14109
+ width: zod.z.number().int(),
14110
+ height: zod.z.number().int()
14111
+ }), {
14112
+ kind: "query",
14113
+ auth: "admin"
14114
+ }),
14115
+ /**
14116
+ * Ask the pipeline what it sees, as a PROPOSAL.
14117
+ *
14118
+ * Runs through `pipelineRunner.runStatelessStep` on the COPIED frame, and
14119
+ * every box comes back as a draft with `source: 'assist'` plus the model and
14120
+ * score that produced it. The operator confirms, edits, adds and deletes;
14121
+ * nothing is stored until `saveRetrainAnnotations`.
14122
+ *
14123
+ * For packages the request is `rfdetr-package` on the ZONE CROP at 0.35 —
14124
+ * never the whole frame, where a package detector at that threshold proposes
14125
+ * furniture. A package request with no zone is REFUSED rather than widened,
14126
+ * because the silent widening would look like a bad model for as long as
14127
+ * nobody checked which rectangle it ran on.
14128
+ */
14129
+ proposeRetrainAnnotations: method(zod.z.object({
14130
+ deviceId: zod.z.number(),
14131
+ trackId: zod.z.string(),
14132
+ frameId: zod.z.string(),
14133
+ subject: RetrainAssistSubjectSchema,
14134
+ /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
14135
+ nodeId: zod.z.string().optional()
14136
+ }), RetrainAssistResultSchema, {
14137
+ kind: "mutation",
14138
+ auth: "admin"
14139
+ }),
14140
+ /** Every annotation on a track, oldest first. */
14141
+ listRetrainAnnotations: method(zod.z.object({ trackId: zod.z.string() }), zod.z.array(RetrainAnnotationSchema).readonly(), {
14142
+ kind: "query",
14143
+ auth: "admin"
14144
+ }),
14145
+ /**
14146
+ * Replace EVERY annotation on one frame with the supplied set.
14147
+ *
14148
+ * Whole-frame replacement, not per-box upsert: the unit of ground truth is
14149
+ * the frame, and "the operator deleted a box" must be the same durable
14150
+ * outcome as "the operator never drew it". A per-box patch would let a frame
14151
+ * keep a box the operator removed on a surface that only knew about the
14152
+ * boxes it sent.
14153
+ *
14154
+ * Refuses a macro class typed into `label` or `subLabel` — the tiers are
14155
+ * separate and the guard is at the WRITE, because a mixed taxonomy cannot
14156
+ * be un-mixed by reading it.
14157
+ */
14158
+ saveRetrainAnnotations: method(zod.z.object({
14159
+ deviceId: zod.z.number(),
14160
+ trackId: zod.z.string(),
14161
+ frameId: zod.z.string(),
14162
+ annotations: zod.z.array(RetrainAnnotationDraftSchema)
14163
+ }), zod.z.array(RetrainAnnotationSchema).readonly(), {
14164
+ kind: "mutation",
14165
+ auth: "admin"
14166
+ }),
14167
+ /**
14168
+ * Finish with a track: `staging → trained`. **The only writer of that
14169
+ * state** — D81 shipped the column with it deliberately unreachable.
14170
+ *
14171
+ * Refuses a track the dataset holds no copies from. `trained` un-pins the
14172
+ * track's media, so completing without a copy is a delete order for material
14173
+ * nothing ever extracted anything from; that refusal IS the safety argument
14174
+ * of D81, expressed as a precondition.
14175
+ */
14176
+ completeRetrainTrack: method(zod.z.object({
14177
+ deviceId: zod.z.number(),
14178
+ trackId: zod.z.string()
14179
+ }), RetrainTransitionResultSchema, {
14180
+ kind: "mutation",
14181
+ auth: "admin"
14182
+ }),
14183
+ /**
14184
+ * The deliberate return: `trained → staging`, for the rare case.
14185
+ *
14186
+ * The generic `setTrackFlags` toggle refuses this in both directions by
14187
+ * design (D81) — re-staging from a checkbox is how the same material gets
14188
+ * annotated twice under two ground truths. Doing it here means the operator
14189
+ * is looking at the annotations that already exist while they decide, and
14190
+ * those annotations are LEFT ALONE: "put this back" must not be a
14191
+ * destructive act wearing a navigational name.
14192
+ */
14193
+ restageRetrainTrack: method(zod.z.object({
14194
+ deviceId: zod.z.number(),
14195
+ trackId: zod.z.string()
14196
+ }), RetrainTransitionResultSchema, {
14197
+ kind: "mutation",
14198
+ auth: "admin"
14199
+ }),
14200
+ /**
14201
+ * Where to download the ANNOTATED dataset.
14202
+ *
14203
+ * The sibling of `getTrainingExportUrl` and deliberately not the same
14204
+ * archive: that one streams a marked track's stored media verbatim, this one
14205
+ * streams the retrain COPIES plus an `annotations.json` carrying, for every
14206
+ * subject, the canonical full-frame box AND the geometry derived for each
14207
+ * model shape (letterboxed root / zone-cropped package / subject-cropped
14208
+ * classifier). Derived at export, never stored — one box in, three shapes
14209
+ * out, so two crops of the same subject can never end up in one feature
14210
+ * space (D52).
14211
+ */
14212
+ getRetrainExportUrl: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).optional() }), zod.z.object({ url: zod.z.string() }), {
14213
+ kind: "query",
14214
+ auth: "admin"
14215
+ }),
13617
14216
  getEventMedia: method(zod.z.object({
13618
14217
  eventId: zod.z.string(),
13619
14218
  kind: MediaFileKindEnum.optional()
@@ -14431,6 +15030,22 @@ var DetailResultSchema = zod.z.object({
14431
15030
  bbox: NativeCropBboxSchema.optional(),
14432
15031
  embedding: zod.z.string().optional(),
14433
15032
  label: zod.z.string().optional(),
15033
+ /**
15034
+ * The tier `label` occupies, copied VERBATIM from the producing step's
15035
+ * `StepDefinition.labelTier` (roadmap 4g). Present only when `label` is.
15036
+ *
15037
+ * It rides the wire rather than being resolved by the consumer because the
15038
+ * declaration lives with the step definition, which only the executing node
15039
+ * has: post-analysis holds no step registry, and re-deriving the tier from
15040
+ * `className` there would be exactly the inference this model exists to
15041
+ * forbid. A `label` that arrives WITHOUT this field is refused by the write
15042
+ * rule and logged (`label tier undeclared`) — an older runner therefore
15043
+ * stops enriching rather than guessing, which is why addon-pipeline is
15044
+ * deployed BEFORE addon-post-analysis.
15045
+ */
15046
+ labelTier: zod.z.union([zod.z.literal(1), zod.z.literal(2)]).optional(),
15047
+ /** Model that produced `label` — carried into the tier's attribution. */
15048
+ labelModelId: zod.z.string().optional(),
14434
15049
  alignedCropJpeg: zod.z.string().optional(),
14435
15050
  /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
14436
15051
  * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
@@ -22066,6 +22681,214 @@ setOverlay: method(zod.z.object({
22066
22681
  }] }
22067
22682
  };
22068
22683
  /**
22684
+ * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
22685
+ *
22686
+ * The `osd` cap is the firmware contract: it probes a camera's overlay
22687
+ * SLOTS and writes literal text into one. It has no idea WHERE that text
22688
+ * comes from, and it must not — a driver that grew a "show the temperature
22689
+ * here" feature would grow it once per vendor.
22690
+ *
22691
+ * This cap owns the other half: a per-(camera, slot) BINDING that says
22692
+ * which value feeds the slot, how it is formatted, and under which
22693
+ * conditions it is shown at all. One addon renders every binding on every
22694
+ * camera, so a new source costs zero driver code.
22695
+ *
22696
+ * Three deliberate choices, each with a rejected alternative:
22697
+ *
22698
+ * 1. A source is `(capName, valuePath)` over the kernel's device
22699
+ * runtime-state mirror — NOT a closed enum of source kinds. Every
22700
+ * cap-keyed slice a device publishes is bindable the day the cap
22701
+ * ships. The rejected alternative (one enum member per source, with
22702
+ * a resolver branch each) is what makes "add the humidity too" a
22703
+ * code change.
22704
+ * 2. The display gate reuses `NcConditionsSchema` verbatim — the
22705
+ * notification centre's condition vocabulary — rather than a parallel
22706
+ * model. An operator who has learned one condition editor has learned
22707
+ * both.
22708
+ * 3. Because the renderer's facts are device STATE and not a detection
22709
+ * record, only a SUBSET of that vocabulary can be answered here.
22710
+ * `setSlotBinding` REJECTS the rest at write time (see
22711
+ * `getConditionSupport`). It does not accept-then-fail-closed: a
22712
+ * condition that can never be true renders a permanently blank
22713
+ * overlay, and a blank overlay looks exactly like a broken camera.
22714
+ */
22715
+ /** Where a slot's value comes from. */
22716
+ var OsdSourceSchema = zod.z.discriminatedUnion("kind", [
22717
+ zod.z.object({
22718
+ kind: zod.z.literal("static"),
22719
+ text: zod.z.string().max(64)
22720
+ }),
22721
+ zod.z.object({
22722
+ kind: zod.z.literal("clock"),
22723
+ /** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
22724
+ pattern: zod.z.string().min(1).max(32).default("HH:mm"),
22725
+ /** IANA zone. Omitted = the server's zone. */
22726
+ timezone: zod.z.string().min(1).max(64).optional()
22727
+ }),
22728
+ zod.z.object({
22729
+ kind: zod.z.literal("device-state"),
22730
+ deviceId: zod.z.number().int().optional(),
22731
+ capName: zod.z.string().min(1).max(64),
22732
+ /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
22733
+ valuePath: zod.z.string().min(1).max(64)
22734
+ })
22735
+ ]);
22736
+ var OsdSlotBindingSchema = zod.z.object({
22737
+ /** Off = the manager stops driving this slot. It does NOT clear it. */
22738
+ enabled: zod.z.boolean().default(true),
22739
+ source: OsdSourceSchema,
22740
+ /** `${value}` and `${unit}` are substituted; every occurrence. */
22741
+ template: zod.z.string().max(96).default("${value}"),
22742
+ /** Truncate with an ellipsis past this length. Absent = no limit. */
22743
+ maxCharacters: zod.z.number().int().min(4).max(64).optional(),
22744
+ /**
22745
+ * Decimal places for a numeric value. `0` yields an integer — the
22746
+ * documented workaround for firmwares that reject `.` in overlay text.
22747
+ */
22748
+ maxDecimals: zod.z.number().int().min(0).max(4).default(1),
22749
+ /** Appended via `${unit}`. The state mirror does not carry units. */
22750
+ unitLabel: zod.z.string().max(8).optional(),
22751
+ /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
22752
+ valueMap: zod.z.record(zod.z.string(), zod.z.string()).optional(),
22753
+ /** Time windows in which the slot is shown. Absent = always. */
22754
+ schedule: NcScheduleSchema.optional(),
22755
+ /**
22756
+ * Display gate, in the notification centre's condition vocabulary.
22757
+ * Only the keys reported by `getConditionSupport` are accepted.
22758
+ */
22759
+ conditions: NcConditionsSchema.optional(),
22760
+ /** Rendered when the gate is closed or the value unreadable. Empty = hide. */
22761
+ fallbackText: zod.z.string().max(64).default("")
22762
+ });
22763
+ /** One camera slot, as the operator sees it: firmware truth + our binding. */
22764
+ var OsdSlotViewSchema = zod.z.object({
22765
+ slotId: zod.z.string(),
22766
+ kind: OsdOverlayKindEnum,
22767
+ /** Firmware refuses text edits (a timestamp, the channel name). */
22768
+ readOnly: zod.z.boolean(),
22769
+ cameraEnabled: zod.z.boolean(),
22770
+ cameraText: zod.z.string().optional(),
22771
+ binding: OsdSlotBindingSchema.nullable()
22772
+ });
22773
+ /**
22774
+ * What happened to one slot on one render pass. `unchanged` exists so the
22775
+ * operator can tell "we are driving this and the value is steady" from
22776
+ * "we never got there" — and so the loop can prove it is not rewriting
22777
+ * identical text to the camera every tick.
22778
+ */
22779
+ var OsdRenderOutcomeEnum = zod.z.enum([
22780
+ "written",
22781
+ "unchanged",
22782
+ "gated",
22783
+ "unreadable",
22784
+ "disabled",
22785
+ "unbound",
22786
+ "failed"
22787
+ ]);
22788
+ var OsdRenderResultSchema = zod.z.object({
22789
+ slotId: zod.z.string(),
22790
+ outcome: OsdRenderOutcomeEnum,
22791
+ /** The text the slot should carry. Empty = the slot is switched off. */
22792
+ text: zod.z.string(),
22793
+ /** Why, whenever the outcome is not a plain write. Never silent. */
22794
+ reason: zod.z.string().optional()
22795
+ });
22796
+ var OsdSourceValueTypeEnum = zod.z.enum([
22797
+ "number",
22798
+ "boolean",
22799
+ "string",
22800
+ "enum"
22801
+ ]);
22802
+ /**
22803
+ * One bindable value, derived from a cap's `runtimeState` schema — never
22804
+ * hand-listed. The editor renders from this, so a cap that ships a new
22805
+ * state field becomes bindable with no UI change.
22806
+ */
22807
+ var OsdSourceOptionSchema = zod.z.object({
22808
+ deviceId: zod.z.number().int(),
22809
+ deviceName: zod.z.string(),
22810
+ capName: zod.z.string(),
22811
+ valuePath: zod.z.string(),
22812
+ label: zod.z.string(),
22813
+ valueType: OsdSourceValueTypeEnum,
22814
+ /** Present for `enum`; the editor offers these as `valueMap` keys. */
22815
+ enumValues: zod.z.array(zod.z.string()).readonly().optional()
22816
+ });
22817
+ var osdManagerCapability = {
22818
+ name: "osd-manager",
22819
+ scope: "system",
22820
+ mode: "singleton",
22821
+ methods: {
22822
+ /**
22823
+ * The camera's slots, each with its binding. `supported: false` when
22824
+ * the camera publishes no `osd` cap — the page then says so instead
22825
+ * of rendering an empty list that reads as "no overlays configured".
22826
+ */
22827
+ getDeviceOsd: method(zod.z.object({ deviceId: zod.z.number().int() }), zod.z.object({
22828
+ supported: zod.z.boolean(),
22829
+ slots: zod.z.array(OsdSlotViewSchema)
22830
+ }), { auth: "admin" }),
22831
+ /**
22832
+ * Every value bindable on this camera: its own state leaves first,
22833
+ * then every other device's. Derived from the cap definitions, so it
22834
+ * cannot drift from what the resolver can actually read.
22835
+ */
22836
+ getSourceCatalog: method(zod.z.object({ deviceId: zod.z.number().int() }), zod.z.object({ sources: zod.z.array(OsdSourceOptionSchema) }), { auth: "admin" }),
22837
+ /**
22838
+ * The condition ids this gate can answer, alongside the FULL notification
22839
+ * condition catalog. Both, in one call, on purpose: an editor that showed
22840
+ * the catalog without the supported set would offer conditions that
22841
+ * `setSlotBinding` then rejects.
22842
+ */
22843
+ getConditionSupport: method(zod.z.object({}), zod.z.object({
22844
+ supported: zod.z.array(zod.z.string()),
22845
+ catalog: zod.z.array(NcConditionDescriptorSchema)
22846
+ }), { auth: "admin" }),
22847
+ /**
22848
+ * Persist a binding and render it immediately, returning the slot as
22849
+ * it now stands. Throws when the binding names an unsupported
22850
+ * condition, an unknown cap, or a value path that cap does not have.
22851
+ */
22852
+ setSlotBinding: method(zod.z.object({
22853
+ deviceId: zod.z.number().int(),
22854
+ slotId: zod.z.string().min(1),
22855
+ binding: OsdSlotBindingSchema
22856
+ }), zod.z.object({
22857
+ slot: OsdSlotViewSchema,
22858
+ render: OsdRenderResultSchema
22859
+ }), {
22860
+ kind: "mutation",
22861
+ auth: "admin"
22862
+ }),
22863
+ /** Forget the binding. The slot keeps whatever text it last carried. */
22864
+ clearSlotBinding: method(zod.z.object({
22865
+ deviceId: zod.z.number().int(),
22866
+ slotId: zod.z.string().min(1)
22867
+ }), zod.z.object({ success: zod.z.literal(true) }), {
22868
+ kind: "mutation",
22869
+ auth: "admin"
22870
+ }),
22871
+ /**
22872
+ * Render without writing. `binding` overrides the stored one so an
22873
+ * editor can preview an unsaved change. Mutation kind only to carry
22874
+ * the binding object safely; no side effects.
22875
+ */
22876
+ previewSlot: method(zod.z.object({
22877
+ deviceId: zod.z.number().int(),
22878
+ slotId: zod.z.string().min(1),
22879
+ binding: OsdSlotBindingSchema.optional()
22880
+ }), OsdRenderResultSchema, {
22881
+ kind: "mutation",
22882
+ auth: "admin"
22883
+ }),
22884
+ /** Force a render pass over one camera now, instead of at the next tick. */
22885
+ renderDevice: method(zod.z.object({ deviceId: zod.z.number().int() }), zod.z.object({ results: zod.z.array(OsdRenderResultSchema) }), {
22886
+ kind: "mutation",
22887
+ auth: "admin"
22888
+ })
22889
+ }
22890
+ };
22891
+ /**
22069
22892
  * Feeder connectivity / power status — mirrors the HA petkit device-status
22070
22893
  * enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
22071
22894
  * `on_batteries` (running on battery backup). `null` until first reported.
@@ -25749,6 +26572,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
25749
26572
  numericSensorCapability,
25750
26573
  oauthIntegrationCapability,
25751
26574
  osdCapability,
26575
+ osdManagerCapability,
25752
26576
  petFeederCapability,
25753
26577
  pipelineAnalyticsCapability,
25754
26578
  pipelineExecutorCapability,
@@ -28665,6 +29489,48 @@ var METHOD_ACCESS_MAP = Object.freeze({
28665
29489
  addonId: null,
28666
29490
  access: "create"
28667
29491
  },
29492
+ "osdManager.clearSlotBinding": {
29493
+ capName: "osd-manager",
29494
+ capScope: "system",
29495
+ addonId: null,
29496
+ access: "delete"
29497
+ },
29498
+ "osdManager.getConditionSupport": {
29499
+ capName: "osd-manager",
29500
+ capScope: "system",
29501
+ addonId: null,
29502
+ access: "view"
29503
+ },
29504
+ "osdManager.getDeviceOsd": {
29505
+ capName: "osd-manager",
29506
+ capScope: "system",
29507
+ addonId: null,
29508
+ access: "view"
29509
+ },
29510
+ "osdManager.getSourceCatalog": {
29511
+ capName: "osd-manager",
29512
+ capScope: "system",
29513
+ addonId: null,
29514
+ access: "view"
29515
+ },
29516
+ "osdManager.previewSlot": {
29517
+ capName: "osd-manager",
29518
+ capScope: "system",
29519
+ addonId: null,
29520
+ access: "create"
29521
+ },
29522
+ "osdManager.renderDevice": {
29523
+ capName: "osd-manager",
29524
+ capScope: "system",
29525
+ addonId: null,
29526
+ access: "create"
29527
+ },
29528
+ "osdManager.setSlotBinding": {
29529
+ capName: "osd-manager",
29530
+ capScope: "system",
29531
+ addonId: null,
29532
+ access: "create"
29533
+ },
28668
29534
  "petFeeder.callPet": {
28669
29535
  capName: "pet-feeder",
28670
29536
  capScope: "device",
@@ -28737,6 +29603,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28737
29603
  addonId: null,
28738
29604
  access: "delete"
28739
29605
  },
29606
+ "pipelineAnalytics.completeRetrainTrack": {
29607
+ capName: "pipeline-analytics",
29608
+ capScope: "device",
29609
+ addonId: null,
29610
+ access: "create"
29611
+ },
28740
29612
  "pipelineAnalytics.deleteDeviceEvents": {
28741
29613
  capName: "pipeline-analytics",
28742
29614
  capScope: "device",
@@ -28749,6 +29621,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28749
29621
  addonId: null,
28750
29622
  access: "delete"
28751
29623
  },
29624
+ "pipelineAnalytics.deselectRetrainFrame": {
29625
+ capName: "pipeline-analytics",
29626
+ capScope: "device",
29627
+ addonId: null,
29628
+ access: "create"
29629
+ },
28752
29630
  "pipelineAnalytics.getActiveTracks": {
28753
29631
  capName: "pipeline-analytics",
28754
29632
  capScope: "device",
@@ -28809,6 +29687,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
28809
29687
  addonId: null,
28810
29688
  access: "view"
28811
29689
  },
29690
+ "pipelineAnalytics.getRetrainExportUrl": {
29691
+ capName: "pipeline-analytics",
29692
+ capScope: "device",
29693
+ addonId: null,
29694
+ access: "view"
29695
+ },
29696
+ "pipelineAnalytics.getRetrainFrameImage": {
29697
+ capName: "pipeline-analytics",
29698
+ capScope: "device",
29699
+ addonId: null,
29700
+ access: "view"
29701
+ },
28812
29702
  "pipelineAnalytics.getSensorEvents": {
28813
29703
  capName: "pipeline-analytics",
28814
29704
  capScope: "device",
@@ -28827,6 +29717,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
28827
29717
  addonId: null,
28828
29718
  access: "view"
28829
29719
  },
29720
+ "pipelineAnalytics.getTrainingExportSummary": {
29721
+ capName: "pipeline-analytics",
29722
+ capScope: "device",
29723
+ addonId: null,
29724
+ access: "view"
29725
+ },
29726
+ "pipelineAnalytics.getTrainingExportUrl": {
29727
+ capName: "pipeline-analytics",
29728
+ capScope: "device",
29729
+ addonId: null,
29730
+ access: "view"
29731
+ },
28830
29732
  "pipelineAnalytics.listEventKinds": {
28831
29733
  capName: "pipeline-analytics",
28832
29734
  capScope: "device",
@@ -28851,6 +29753,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
28851
29753
  addonId: null,
28852
29754
  access: "view"
28853
29755
  },
29756
+ "pipelineAnalytics.listRetrainAnnotations": {
29757
+ capName: "pipeline-analytics",
29758
+ capScope: "device",
29759
+ addonId: null,
29760
+ access: "view"
29761
+ },
29762
+ "pipelineAnalytics.listRetrainFrames": {
29763
+ capName: "pipeline-analytics",
29764
+ capScope: "device",
29765
+ addonId: null,
29766
+ access: "view"
29767
+ },
29768
+ "pipelineAnalytics.listRetrainStaging": {
29769
+ capName: "pipeline-analytics",
29770
+ capScope: "device",
29771
+ addonId: null,
29772
+ access: "view"
29773
+ },
28854
29774
  "pipelineAnalytics.listTrackMedia": {
28855
29775
  capName: "pipeline-analytics",
28856
29776
  capScope: "device",
@@ -28863,6 +29783,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
28863
29783
  addonId: null,
28864
29784
  access: "view"
28865
29785
  },
29786
+ "pipelineAnalytics.proposeRetrainAnnotations": {
29787
+ capName: "pipeline-analytics",
29788
+ capScope: "device",
29789
+ addonId: null,
29790
+ access: "create"
29791
+ },
28866
29792
  "pipelineAnalytics.pruneEvents": {
28867
29793
  capName: "pipeline-analytics",
28868
29794
  capScope: "device",
@@ -28893,12 +29819,30 @@ var METHOD_ACCESS_MAP = Object.freeze({
28893
29819
  addonId: null,
28894
29820
  access: "create"
28895
29821
  },
29822
+ "pipelineAnalytics.restageRetrainTrack": {
29823
+ capName: "pipeline-analytics",
29824
+ capScope: "device",
29825
+ addonId: null,
29826
+ access: "create"
29827
+ },
29828
+ "pipelineAnalytics.saveRetrainAnnotations": {
29829
+ capName: "pipeline-analytics",
29830
+ capScope: "device",
29831
+ addonId: null,
29832
+ access: "create"
29833
+ },
28896
29834
  "pipelineAnalytics.searchObjectEvents": {
28897
29835
  capName: "pipeline-analytics",
28898
29836
  capScope: "device",
28899
29837
  addonId: null,
28900
29838
  access: "view"
28901
29839
  },
29840
+ "pipelineAnalytics.selectRetrainFrames": {
29841
+ capName: "pipeline-analytics",
29842
+ capScope: "device",
29843
+ addonId: null,
29844
+ access: "create"
29845
+ },
28902
29846
  "pipelineAnalytics.setTrackFlags": {
28903
29847
  capName: "pipeline-analytics",
28904
29848
  capScope: "device",
@@ -30303,6 +31247,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
30303
31247
  addonId: null,
30304
31248
  access: "view"
30305
31249
  },
31250
+ "streamBroker.getDeviceAudioMute": {
31251
+ capName: "stream-broker",
31252
+ capScope: "system",
31253
+ addonId: null,
31254
+ access: "view"
31255
+ },
30306
31256
  "streamBroker.getPreBufferInfo": {
30307
31257
  capName: "stream-broker",
30308
31258
  capScope: "system",
@@ -30423,6 +31373,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
30423
31373
  addonId: null,
30424
31374
  access: "create"
30425
31375
  },
31376
+ "streamBroker.setDeviceAudioMute": {
31377
+ capName: "stream-broker",
31378
+ capScope: "system",
31379
+ addonId: null,
31380
+ access: "create"
31381
+ },
30426
31382
  "streamBroker.setPreBufferDuration": {
30427
31383
  capName: "stream-broker",
30428
31384
  capScope: "system",