@camstack/addon-post-analysis 1.2.50 → 1.2.52

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.
@@ -9156,6 +9156,69 @@ var StreamFormatSchema = _enum([
9156
9156
  "mjpeg",
9157
9157
  "rtsp"
9158
9158
  ]);
9159
+ /** A container `produceEventMedia` can emit. */
9160
+ var EventMediaKindSchema = _enum(["mp4", "gif"]);
9161
+ /**
9162
+ * One produced artifact, referenced by HANDLE.
9163
+ *
9164
+ * Never inline bytes: a produced clip is 200 KB–5 MB and every consumer of this
9165
+ * method is in another runner ([D9](../../../../docs/decisions/adr-0009.md),
9166
+ * [D18](../../../../docs/decisions/adr-0018.md) — cross-process media is fetched
9167
+ * on demand, compressed, by handle). `bytes` is here so a caller can decide
9168
+ * whether it wants the fetch at all.
9169
+ */
9170
+ var EventMediaArtifactSchema = object({
9171
+ kind: EventMediaKindSchema,
9172
+ /** Opaque, single-camera, short-lived. Redeem with `fetchEventMedia`. */
9173
+ handle: string(),
9174
+ /**
9175
+ * The node holding the bytes — the ROUTING key for `fetchEventMedia`.
9176
+ *
9177
+ * `stream-broker` is a singleton cap and an unpinned call never leaves the
9178
+ * hub, so a handle produced on an agent's broker would be redeemed against
9179
+ * the hub's store and come back `null`. Same contract, same field name and
9180
+ * the same reason as `FrameHandleSchema.nodeId`: the producer stamps where it
9181
+ * lives and the consumer pins to it.
9182
+ */
9183
+ nodeId: string(),
9184
+ mime: string(),
9185
+ bytes: number().int(),
9186
+ width: number().int(),
9187
+ height: number().int()
9188
+ });
9189
+ /**
9190
+ * What a production actually covered — the answer to the only question an
9191
+ * operator asks about a notification clip.
9192
+ *
9193
+ * `fromTs`/`toTs` are WALL CLOCK, derived from the ring's own packet timeline,
9194
+ * so a caller can state "this clip starts 4.1 s before the event" instead of
9195
+ * inferring it from a duration. A production whose `fromTs` is later than the
9196
+ * event is a production with no pre-roll, and that is exactly the defect this
9197
+ * method exists to make visible rather than plausible.
9198
+ */
9199
+ var EventMediaCoverageSchema = object({
9200
+ fromTs: number(),
9201
+ toTs: number(),
9202
+ /** Encoded packets in the muxed window. */
9203
+ packets: number().int()
9204
+ });
9205
+ /**
9206
+ * The result of ONE cut, in every container the caller asked for.
9207
+ *
9208
+ * Every artifact in `media` came out of the SAME window of the SAME rendition —
9209
+ * that is the whole reason this is one method rather than one call per format.
9210
+ * A consumer attaching a gif and a video can no longer show two different
9211
+ * moments, because it never chose two sources.
9212
+ */
9213
+ var EventMediaProductionSchema = object({
9214
+ media: array(EventMediaArtifactSchema).readonly(),
9215
+ coverage: EventMediaCoverageSchema,
9216
+ /** The rendition actually cut from — what the default or the fallback chose. */
9217
+ profile: CamProfileSchema,
9218
+ /** `copy` = the camera's own H.264, untouched. `encode` = re-encoded (H.265
9219
+ * source, a downscale, or a playback rate other than 1). */
9220
+ video: _enum(["copy", "encode"])
9221
+ });
9159
9222
  var RtspRestreamEntrySchema = object({
9160
9223
  brokerId: string(),
9161
9224
  url: string(),
@@ -9555,6 +9618,34 @@ method(object({
9555
9618
  }), {
9556
9619
  kind: "mutation",
9557
9620
  auth: "admin"
9621
+ }), method(object({
9622
+ deviceId: number(),
9623
+ /** Absent = the largest H.264 rendition at or below 1080p, which is
9624
+ * also the one that can be copied. Falls back to whatever the ring
9625
+ * actually retained, and the answer says which. */
9626
+ profile: CamProfileSchema.optional(),
9627
+ aroundMs: number(),
9628
+ preSeconds: number().min(0).max(20).default(4),
9629
+ postSeconds: number().min(0).max(20).default(6),
9630
+ kinds: array(EventMediaKindSchema).min(1).default(["mp4"]),
9631
+ /** GIF geometry. The video keeps the source's own. */
9632
+ gifMaxWidth: number().int().min(120).max(1280).default(640),
9633
+ gifFps: number().int().min(1).max(15).default(8),
9634
+ /**
9635
+ * Playback rate, applied to EVERY container so they stay one clip.
9636
+ * `1` is real time and is what allows the copy branch.
9637
+ */
9638
+ speed: number().min(1).max(8).default(1)
9639
+ }), EventMediaProductionSchema, {
9640
+ kind: "mutation",
9641
+ auth: "admin"
9642
+ }), method(object({ handle: string() }), object({
9643
+ base64: string(),
9644
+ mime: string(),
9645
+ bytes: number().int()
9646
+ }).nullable(), {
9647
+ kind: "mutation",
9648
+ auth: "admin"
9558
9649
  }), method(_void(), array(CameraStreamSchema).readonly()), method(_void(), array(ProfileSlotSchema).readonly()), method(object({ brokerId: string() }), BrokerStatsSchema), method(object({ brokerId: string() }), object({
9559
9650
  probed: boolean(),
9560
9651
  summary: string()
@@ -15679,6 +15770,60 @@ var TrackFlagsSchema = object({
15679
15770
  * `trained` without a re-fetch. */
15680
15771
  retrainStatus: RetrainStatusSchema
15681
15772
  });
15773
+ union([literal(1), literal(2)]);
15774
+ /**
15775
+ * WHO decided a label, and when. Carried per tier so a value can be traced to
15776
+ * the step and model that produced it — which is what makes the write rule
15777
+ * arguable after the fact ("why is 592's label `dog` and not `Canis lupus`?")
15778
+ * and what lets a migrated, UNATTRIBUTED value be told apart from a real one.
15779
+ *
15780
+ * `stepId` is the pipeline step id (`animal-classifier`, `bird-classifier`,
15781
+ * `plate-ocr`, `face-embedding`, `object-detection`), or the sentinel
15782
+ * `migration:4g` for a value the 4g migration moved from the single-slot era —
15783
+ * that value has no provenance, and the write rule lets ANY properly-attributed
15784
+ * write of the same tier replace it regardless of score.
15785
+ */
15786
+ var LabelAttributionSchema = object({
15787
+ stepId: string(),
15788
+ modelId: string().optional(),
15789
+ decidedAt: number()
15790
+ });
15791
+ /**
15792
+ * The TIERED label model (roadmap 4g), spread into `TrackSchema` and
15793
+ * `ObjectEventSchema` from ONE place so the two surfaces cannot drift — a
15794
+ * track and its events always answer the same question the same way.
15795
+ *
15796
+ * Two scalar columns, not an array: every consumer wants "the coarse one" or
15797
+ * "the fine one", and an array made both a scan. `label` is tier 1, `subLabel`
15798
+ * is tier 2, and each carries its own score + attribution.
15799
+ *
15800
+ * **Reading it.** What a human should be shown is `subLabel ?? label` — the
15801
+ * finest thing known. Before 4g the single `label` column held the finest
15802
+ * value, so a consumer that has not been updated reads the tier-1 slot and
15803
+ * shows nothing on a species-only row; that is why the migration puts every
15804
+ * pre-4g value in tier 2 (it cannot regress a display that reads the fallback)
15805
+ * and why the read surfaces were changed in the same train.
15806
+ *
15807
+ * **Writing it.** The slots are independent, which is the whole point: a
15808
+ * tier-1 write (`bird`) can never overwrite a tier-2 value (`Turdus
15809
+ * migratorius`), so fineness cannot regress by construction. Within a tier the
15810
+ * higher score wins. One rule, one implementation — see
15811
+ * `pipeline/label-tier.ts` in addon-post-analysis.
15812
+ */
15813
+ var TieredLabelFields = {
15814
+ /** Tier 1 — the sub-class. See {@link LabelTierSchema}. */
15815
+ label: string().optional(),
15816
+ /** Confidence of the tier-1 value, as reported by the deciding step. */
15817
+ labelScore: number().optional(),
15818
+ /** Provenance of the tier-1 value. See {@link LabelAttributionSchema}. */
15819
+ labelMeta: LabelAttributionSchema.optional(),
15820
+ /** Tier 2 — the instance. See {@link LabelTierSchema}. */
15821
+ subLabel: string().optional(),
15822
+ /** Confidence of the tier-2 value, as reported by the deciding step. */
15823
+ subLabelScore: number().optional(),
15824
+ /** Provenance of the tier-2 value. See {@link LabelAttributionSchema}. */
15825
+ subLabelMeta: LabelAttributionSchema.optional()
15826
+ };
15682
15827
  /** Per-camera slice of a training-export estimate. */
15683
15828
  var TrainingExportDeviceTotalsSchema = object({
15684
15829
  deviceId: number(),
@@ -15703,7 +15848,7 @@ var TrackSchema = object({
15703
15848
  trackId: string(),
15704
15849
  deviceId: number(),
15705
15850
  className: string(),
15706
- label: string().optional(),
15851
+ ...TieredLabelFields,
15707
15852
  producingDeviceName: string().optional(),
15708
15853
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
15709
15854
  source: TrackSourceSchema.optional(),
@@ -15816,7 +15961,7 @@ var ObjectEventSchema = object({
15816
15961
  /** Omitted in slim projection. */
15817
15962
  trackId: string().optional(),
15818
15963
  className: string(),
15819
- label: string().optional(),
15964
+ ...TieredLabelFields,
15820
15965
  /** Omitted in slim projection. */
15821
15966
  confidence: number().optional(),
15822
15967
  /** Heavy JSON — omitted in slim projection. */
@@ -15897,6 +16042,173 @@ var MediaFileSchema = object({
15897
16042
  * stored blob and a `?variant=thumb` rendering without fetching either.
15898
16043
  */
15899
16044
  var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
16045
+ /**
16046
+ * The MACRO tier of an annotation — a CLOSED set.
16047
+ *
16048
+ * This is what the exported detector predicts, so a typo here is a new class
16049
+ * with one example in it. `label` and `subLabel` are open strings by contrast:
16050
+ * the whole point of the page is teaching the model things it does not know
16051
+ * yet, and constraining that vocabulary would make it useless.
16052
+ *
16053
+ * A macro class is NEVER a label. The provider refuses a write whose `label` or
16054
+ * `subLabel` is one of these values, in any casing, because once `person`
16055
+ * exists in both tiers "every person box" stops being answerable without
16056
+ * knowing every string anyone ever typed — and the damage is retroactive.
16057
+ */
16058
+ var RetrainMacroClassSchema = _enum([
16059
+ "person",
16060
+ "vehicle",
16061
+ "animal",
16062
+ "package",
16063
+ "face",
16064
+ "plate"
16065
+ ]);
16066
+ /** A subject to learn, or a phantom to unlearn (taught by OMISSION). */
16067
+ var RetrainAnnotationKindSchema = _enum(["subject", "model_error"]);
16068
+ /** Did a human draw this box, or did the assist propose it? */
16069
+ var RetrainAnnotationSourceSchema = _enum(["operator", "assist"]);
16070
+ /** Normalised `[0,1]` rectangle against the FULL frame — the canonical form. */
16071
+ var RetrainBboxSchema = object({
16072
+ x: number(),
16073
+ y: number(),
16074
+ w: number(),
16075
+ h: number()
16076
+ });
16077
+ /**
16078
+ * One annotated subject.
16079
+ *
16080
+ * `bbox` is normalised against the full frame, ALWAYS. The per-model shapes
16081
+ * (letterboxed root / zone-cropped package / subject-cropped classifier) are
16082
+ * derived from it at export and never stored — storing them is how one feature
16083
+ * space ends up holding two crops of the same subject (D52).
16084
+ */
16085
+ var RetrainAnnotationSchema = object({
16086
+ id: string(),
16087
+ trackId: string(),
16088
+ deviceId: number(),
16089
+ /** The COPY in retrain storage — never the source track's media key. */
16090
+ mediaKey: string(),
16091
+ bbox: RetrainBboxSchema,
16092
+ macroClass: RetrainMacroClassSchema,
16093
+ label: string().optional(),
16094
+ subLabel: string().optional(),
16095
+ kind: RetrainAnnotationKindSchema,
16096
+ source: RetrainAnnotationSourceSchema,
16097
+ /** Which model proposed this box — or, on a `model_error`, drew the phantom. */
16098
+ assistModelId: string().optional(),
16099
+ assistScore: number().optional(),
16100
+ exportedInBatch: string().optional(),
16101
+ createdAt: number()
16102
+ });
16103
+ /** The write form — the server owns `id`, `createdAt` and the frame binding. */
16104
+ var RetrainAnnotationDraftSchema = RetrainAnnotationSchema.omit({
16105
+ id: true,
16106
+ trackId: true,
16107
+ deviceId: true,
16108
+ mediaKey: true,
16109
+ createdAt: true,
16110
+ exportedInBatch: true
16111
+ });
16112
+ /** A track sitting in `staging`, with everything the worklist needs to rank it. */
16113
+ var RetrainTrackSchema = object({
16114
+ trackId: string(),
16115
+ deviceId: number(),
16116
+ className: string(),
16117
+ label: string().optional(),
16118
+ firstSeen: number(),
16119
+ lastSeen: number(),
16120
+ /** How many frames the dataset already holds from this track. */
16121
+ frameCount: number().int(),
16122
+ /** How many subjects have been annotated on those frames. `0` with
16123
+ * `frameCount: 0` is exactly "staging, still to work". */
16124
+ annotationCount: number().int()
16125
+ });
16126
+ /** A frame the picker may offer — an index row, no blob was read to produce it. */
16127
+ var RetrainFrameCandidateSchema = object({
16128
+ mediaKey: string(),
16129
+ kind: MediaFileKindEnum,
16130
+ timestamp: number(),
16131
+ sizeBytes: number().int(),
16132
+ /** A copy of this original already exists — selecting it is free and cannot
16133
+ * fail, whatever became of the original. */
16134
+ copied: boolean()
16135
+ });
16136
+ /** A frame the dataset OWNS: bytes copied at selection time. */
16137
+ var RetrainFrameSchema = object({
16138
+ frameId: string(),
16139
+ deviceId: number(),
16140
+ trackId: string(),
16141
+ /** Provenance only. It may already point at nothing — that is expected. */
16142
+ sourceMediaKey: string(),
16143
+ sourceKind: MediaFileKindEnum,
16144
+ sizeBytes: number().int(),
16145
+ width: number().int(),
16146
+ height: number().int(),
16147
+ copiedAt: number()
16148
+ });
16149
+ /** Why a copy-on-select could not be honoured — named, never a silent skip. */
16150
+ var RetrainCopyRefusalSchema = _enum([
16151
+ "source-missing",
16152
+ "unreadable-image",
16153
+ "write-failed"
16154
+ ]);
16155
+ var RetrainFrameSelectionSchema = object({
16156
+ copied: array(RetrainFrameSchema).readonly(),
16157
+ refused: array(object({
16158
+ sourceMediaKey: string(),
16159
+ reason: RetrainCopyRefusalSchema
16160
+ })).readonly()
16161
+ });
16162
+ var RetrainFrameListSchema = object({
16163
+ candidates: array(RetrainFrameCandidateSchema).readonly(),
16164
+ copies: array(RetrainFrameSchema).readonly(),
16165
+ /** What the page pre-selects — the native key frame when one survives. */
16166
+ autoPickMediaKey: string().optional()
16167
+ });
16168
+ /** What the operator asked the assist to look for. */
16169
+ var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
16170
+ kind: literal("package"),
16171
+ zone: RetrainBboxSchema.optional()
16172
+ }), object({
16173
+ kind: literal("objects"),
16174
+ modelId: string(),
16175
+ minScore: number().optional()
16176
+ })]);
16177
+ /**
16178
+ * The assist's answer — a discriminated union, because "the model saw nothing"
16179
+ * and "this node cannot run that model" lead to different next moves and a
16180
+ * nullable result cannot tell them apart.
16181
+ */
16182
+ var RetrainAssistResultSchema = discriminatedUnion("kind", [object({
16183
+ kind: literal("proposed"),
16184
+ modelId: string(),
16185
+ stepId: string(),
16186
+ minScore: number(),
16187
+ /** Drafts, ready to edit. `source: 'assist'` until the operator touches one. */
16188
+ proposals: array(RetrainAnnotationDraftSchema).readonly(),
16189
+ /** Returned by the runner but removed by the threshold. */
16190
+ belowThreshold: number().int()
16191
+ }), object({
16192
+ kind: literal("refused"),
16193
+ /** `no-zone` is ours; the rest are the runner's own refusal vocabulary. */
16194
+ reason: string(),
16195
+ detail: string().optional()
16196
+ })]);
16197
+ /** The outcome of a lifecycle move owned by the retrain page. */
16198
+ var RetrainTransitionResultSchema = object({
16199
+ trackId: string(),
16200
+ /** Where the track ended up, whatever happened. */
16201
+ retrainStatus: RetrainStatusSchema,
16202
+ /** `false` ⇒ the move was refused or was a no-op; `reason` says which. */
16203
+ changed: boolean(),
16204
+ reason: _enum([
16205
+ "unknown-track",
16206
+ "no-frames-copied",
16207
+ "not-staging",
16208
+ "not-trained",
16209
+ "unchanged"
16210
+ ]).optional()
16211
+ });
15900
16212
  var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
15901
16213
  var MAX_EVENT_QUERY_LIMIT = 5e3;
15902
16214
  var DeviceEventQueryInput = object({
@@ -15951,7 +16263,7 @@ var KeyEventSchema = object({
15951
16263
  /** Track start time (firstSeen). */
15952
16264
  timestamp: number(),
15953
16265
  className: string(),
15954
- label: string().optional(),
16266
+ ...TieredLabelFields,
15955
16267
  importance: number(),
15956
16268
  /** Highest-confidence ObjectEvent id for the track (empty when none). */
15957
16269
  bestEventId: string(),
@@ -16442,6 +16754,201 @@ var pipelineAnalyticsCapability = {
16442
16754
  kind: "query",
16443
16755
  auth: "admin"
16444
16756
  }),
16757
+ /**
16758
+ * The staging worklist for one camera, or for every camera that has one.
16759
+ *
16760
+ * Fetched ON DEMAND, over the staging set only — the page never scans
16761
+ * history, because making the working set small is the entire purpose of
16762
+ * the mark. Each row carries how many frames the dataset already holds from
16763
+ * the track and how many subjects were annotated on them, so
16764
+ * `frameCount: 0` reads as "still to work" without a second call per track.
16765
+ *
16766
+ * `auth: 'admin'`, unlike the viewer-level mark itself: marking a track is
16767
+ * curation you do while looking at it, but building the training set the
16768
+ * fleet's models are fine-tuned on is not.
16769
+ */
16770
+ listRetrainStaging: method(object({
16771
+ /** Empty ⇒ every camera that has staging tracks. A LIST, not a single
16772
+ * `deviceId`, deliberately: `deviceId` would make this device-bound and
16773
+ * route it at one camera's owner, and "every camera" would stop being
16774
+ * expressible at all. */
16775
+ deviceIds: array(number()).optional(),
16776
+ limit: number().int().min(1).max(500).optional()
16777
+ }), array(RetrainTrackSchema).readonly(), {
16778
+ kind: "query",
16779
+ auth: "admin"
16780
+ }),
16781
+ /**
16782
+ * What a track can contribute, and what it already has.
16783
+ *
16784
+ * `candidates` are the track's whole, unannotated frames — index rows only,
16785
+ * so this is cheap. `copies` are the frames already inside the dataset, and
16786
+ * a candidate whose copy exists is marked `copied: true`: selecting it again
16787
+ * is free and CANNOT fail, whatever became of the original.
16788
+ *
16789
+ * A crop, a thumbnail and `fullFrameBoxed` are never candidates. The last
16790
+ * one matters most: it has the model's own rectangle burned into the pixels,
16791
+ * and a detector trained on it learns to find a green line.
16792
+ */
16793
+ listRetrainFrames: method(object({ trackId: string() }), RetrainFrameListSchema, {
16794
+ kind: "query",
16795
+ auth: "admin"
16796
+ }),
16797
+ /**
16798
+ * COPY-ON-SELECT — the write that makes `trained` safe to evict.
16799
+ *
16800
+ * Selecting a frame copies its bytes into retrain storage immediately: not
16801
+ * a reference, not a lease. Once the copy exists the dataset no longer
16802
+ * depends on the track's media, which is exactly what lets D81 hand a
16803
+ * `trained` track back to retention.
16804
+ *
16805
+ * The order inside is load-bearing and is pinned by a test: an EXISTING
16806
+ * copy is returned without touching the source, so an original that
16807
+ * evaporated blocks the selection of THAT ORIGINAL and never the copy
16808
+ * already taken. Every refusal comes back named — a dropped selection is
16809
+ * never silent, on the wire or in the log.
16810
+ */
16811
+ selectRetrainFrames: method(object({
16812
+ deviceId: number(),
16813
+ trackId: string(),
16814
+ mediaKeys: array(string()).min(1)
16815
+ }), RetrainFrameSelectionSchema, {
16816
+ kind: "mutation",
16817
+ auth: "admin"
16818
+ }),
16819
+ /** Un-select a frame: its annotations go first, then the copy and its blob.
16820
+ * Deliberately destructive and deliberately explicit — it is the only way
16821
+ * a frame leaves the dataset before export. */
16822
+ deselectRetrainFrame: method(object({
16823
+ deviceId: number(),
16824
+ trackId: string(),
16825
+ frameId: string()
16826
+ }), object({
16827
+ removed: boolean(),
16828
+ removedAnnotations: number().int()
16829
+ }), {
16830
+ kind: "mutation",
16831
+ auth: "admin"
16832
+ }),
16833
+ /**
16834
+ * The pixels of ONE copied frame, base64.
16835
+ *
16836
+ * Through the cap rather than a data plane because it is genuinely one
16837
+ * frame at a time, on demand, at human speed — the shape D9/D18 permit
16838
+ * (what they forbid is frames crossing a boundary at frame RATE). The
16839
+ * annotation canvas needs the image and its exact dimensions in the same
16840
+ * answer: a canvas that places a normalised box against a size it guessed
16841
+ * draws every box in the wrong place.
16842
+ */
16843
+ getRetrainFrameImage: method(object({ frameId: string() }), object({
16844
+ base64: string(),
16845
+ width: number().int(),
16846
+ height: number().int()
16847
+ }), {
16848
+ kind: "query",
16849
+ auth: "admin"
16850
+ }),
16851
+ /**
16852
+ * Ask the pipeline what it sees, as a PROPOSAL.
16853
+ *
16854
+ * Runs through `pipelineRunner.runStatelessStep` on the COPIED frame, and
16855
+ * every box comes back as a draft with `source: 'assist'` plus the model and
16856
+ * score that produced it. The operator confirms, edits, adds and deletes;
16857
+ * nothing is stored until `saveRetrainAnnotations`.
16858
+ *
16859
+ * For packages the request is `rfdetr-package` on the ZONE CROP at 0.35 —
16860
+ * never the whole frame, where a package detector at that threshold proposes
16861
+ * furniture. A package request with no zone is REFUSED rather than widened,
16862
+ * because the silent widening would look like a bad model for as long as
16863
+ * nobody checked which rectangle it ran on.
16864
+ */
16865
+ proposeRetrainAnnotations: method(object({
16866
+ deviceId: number(),
16867
+ trackId: string(),
16868
+ frameId: string(),
16869
+ subject: RetrainAssistSubjectSchema,
16870
+ /** Which node runs it. Absent ⇒ wherever an unowned call lands. */
16871
+ nodeId: string().optional()
16872
+ }), RetrainAssistResultSchema, {
16873
+ kind: "mutation",
16874
+ auth: "admin"
16875
+ }),
16876
+ /** Every annotation on a track, oldest first. */
16877
+ listRetrainAnnotations: method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
16878
+ kind: "query",
16879
+ auth: "admin"
16880
+ }),
16881
+ /**
16882
+ * Replace EVERY annotation on one frame with the supplied set.
16883
+ *
16884
+ * Whole-frame replacement, not per-box upsert: the unit of ground truth is
16885
+ * the frame, and "the operator deleted a box" must be the same durable
16886
+ * outcome as "the operator never drew it". A per-box patch would let a frame
16887
+ * keep a box the operator removed on a surface that only knew about the
16888
+ * boxes it sent.
16889
+ *
16890
+ * Refuses a macro class typed into `label` or `subLabel` — the tiers are
16891
+ * separate and the guard is at the WRITE, because a mixed taxonomy cannot
16892
+ * be un-mixed by reading it.
16893
+ */
16894
+ saveRetrainAnnotations: method(object({
16895
+ deviceId: number(),
16896
+ trackId: string(),
16897
+ frameId: string(),
16898
+ annotations: array(RetrainAnnotationDraftSchema)
16899
+ }), array(RetrainAnnotationSchema).readonly(), {
16900
+ kind: "mutation",
16901
+ auth: "admin"
16902
+ }),
16903
+ /**
16904
+ * Finish with a track: `staging → trained`. **The only writer of that
16905
+ * state** — D81 shipped the column with it deliberately unreachable.
16906
+ *
16907
+ * Refuses a track the dataset holds no copies from. `trained` un-pins the
16908
+ * track's media, so completing without a copy is a delete order for material
16909
+ * nothing ever extracted anything from; that refusal IS the safety argument
16910
+ * of D81, expressed as a precondition.
16911
+ */
16912
+ completeRetrainTrack: method(object({
16913
+ deviceId: number(),
16914
+ trackId: string()
16915
+ }), RetrainTransitionResultSchema, {
16916
+ kind: "mutation",
16917
+ auth: "admin"
16918
+ }),
16919
+ /**
16920
+ * The deliberate return: `trained → staging`, for the rare case.
16921
+ *
16922
+ * The generic `setTrackFlags` toggle refuses this in both directions by
16923
+ * design (D81) — re-staging from a checkbox is how the same material gets
16924
+ * annotated twice under two ground truths. Doing it here means the operator
16925
+ * is looking at the annotations that already exist while they decide, and
16926
+ * those annotations are LEFT ALONE: "put this back" must not be a
16927
+ * destructive act wearing a navigational name.
16928
+ */
16929
+ restageRetrainTrack: method(object({
16930
+ deviceId: number(),
16931
+ trackId: string()
16932
+ }), RetrainTransitionResultSchema, {
16933
+ kind: "mutation",
16934
+ auth: "admin"
16935
+ }),
16936
+ /**
16937
+ * Where to download the ANNOTATED dataset.
16938
+ *
16939
+ * The sibling of `getTrainingExportUrl` and deliberately not the same
16940
+ * archive: that one streams a marked track's stored media verbatim, this one
16941
+ * streams the retrain COPIES plus an `annotations.json` carrying, for every
16942
+ * subject, the canonical full-frame box AND the geometry derived for each
16943
+ * model shape (letterboxed root / zone-cropped package / subject-cropped
16944
+ * classifier). Derived at export, never stored — one box in, three shapes
16945
+ * out, so two crops of the same subject can never end up in one feature
16946
+ * space (D52).
16947
+ */
16948
+ getRetrainExportUrl: method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
16949
+ kind: "query",
16950
+ auth: "admin"
16951
+ }),
16445
16952
  getEventMedia: method(object({
16446
16953
  eventId: string(),
16447
16954
  kind: MediaFileKindEnum.optional()
@@ -17106,6 +17613,22 @@ var DetailResultSchema = object({
17106
17613
  bbox: NativeCropBboxSchema.optional(),
17107
17614
  embedding: string().optional(),
17108
17615
  label: string().optional(),
17616
+ /**
17617
+ * The tier `label` occupies, copied VERBATIM from the producing step's
17618
+ * `StepDefinition.labelTier` (roadmap 4g). Present only when `label` is.
17619
+ *
17620
+ * It rides the wire rather than being resolved by the consumer because the
17621
+ * declaration lives with the step definition, which only the executing node
17622
+ * has: post-analysis holds no step registry, and re-deriving the tier from
17623
+ * `className` there would be exactly the inference this model exists to
17624
+ * forbid. A `label` that arrives WITHOUT this field is refused by the write
17625
+ * rule and logged (`label tier undeclared`) — an older runner therefore
17626
+ * stops enriching rather than guessing, which is why addon-pipeline is
17627
+ * deployed BEFORE addon-post-analysis.
17628
+ */
17629
+ labelTier: union([literal(1), literal(2)]).optional(),
17630
+ /** Model that produced `label` — carried into the tier's attribution. */
17631
+ labelModelId: string().optional(),
17109
17632
  alignedCropJpeg: string().optional(),
17110
17633
  /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
17111
17634
  * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
@@ -30055,6 +30578,12 @@ Object.freeze({
30055
30578
  addonId: null,
30056
30579
  access: "delete"
30057
30580
  },
30581
+ "pipelineAnalytics.completeRetrainTrack": {
30582
+ capName: "pipeline-analytics",
30583
+ capScope: "device",
30584
+ addonId: null,
30585
+ access: "create"
30586
+ },
30058
30587
  "pipelineAnalytics.deleteDeviceEvents": {
30059
30588
  capName: "pipeline-analytics",
30060
30589
  capScope: "device",
@@ -30067,6 +30596,12 @@ Object.freeze({
30067
30596
  addonId: null,
30068
30597
  access: "delete"
30069
30598
  },
30599
+ "pipelineAnalytics.deselectRetrainFrame": {
30600
+ capName: "pipeline-analytics",
30601
+ capScope: "device",
30602
+ addonId: null,
30603
+ access: "create"
30604
+ },
30070
30605
  "pipelineAnalytics.getActiveTracks": {
30071
30606
  capName: "pipeline-analytics",
30072
30607
  capScope: "device",
@@ -30127,6 +30662,18 @@ Object.freeze({
30127
30662
  addonId: null,
30128
30663
  access: "view"
30129
30664
  },
30665
+ "pipelineAnalytics.getRetrainExportUrl": {
30666
+ capName: "pipeline-analytics",
30667
+ capScope: "device",
30668
+ addonId: null,
30669
+ access: "view"
30670
+ },
30671
+ "pipelineAnalytics.getRetrainFrameImage": {
30672
+ capName: "pipeline-analytics",
30673
+ capScope: "device",
30674
+ addonId: null,
30675
+ access: "view"
30676
+ },
30130
30677
  "pipelineAnalytics.getSensorEvents": {
30131
30678
  capName: "pipeline-analytics",
30132
30679
  capScope: "device",
@@ -30181,6 +30728,24 @@ Object.freeze({
30181
30728
  addonId: null,
30182
30729
  access: "view"
30183
30730
  },
30731
+ "pipelineAnalytics.listRetrainAnnotations": {
30732
+ capName: "pipeline-analytics",
30733
+ capScope: "device",
30734
+ addonId: null,
30735
+ access: "view"
30736
+ },
30737
+ "pipelineAnalytics.listRetrainFrames": {
30738
+ capName: "pipeline-analytics",
30739
+ capScope: "device",
30740
+ addonId: null,
30741
+ access: "view"
30742
+ },
30743
+ "pipelineAnalytics.listRetrainStaging": {
30744
+ capName: "pipeline-analytics",
30745
+ capScope: "device",
30746
+ addonId: null,
30747
+ access: "view"
30748
+ },
30184
30749
  "pipelineAnalytics.listTrackMedia": {
30185
30750
  capName: "pipeline-analytics",
30186
30751
  capScope: "device",
@@ -30193,6 +30758,12 @@ Object.freeze({
30193
30758
  addonId: null,
30194
30759
  access: "view"
30195
30760
  },
30761
+ "pipelineAnalytics.proposeRetrainAnnotations": {
30762
+ capName: "pipeline-analytics",
30763
+ capScope: "device",
30764
+ addonId: null,
30765
+ access: "create"
30766
+ },
30196
30767
  "pipelineAnalytics.pruneEvents": {
30197
30768
  capName: "pipeline-analytics",
30198
30769
  capScope: "device",
@@ -30223,12 +30794,30 @@ Object.freeze({
30223
30794
  addonId: null,
30224
30795
  access: "create"
30225
30796
  },
30797
+ "pipelineAnalytics.restageRetrainTrack": {
30798
+ capName: "pipeline-analytics",
30799
+ capScope: "device",
30800
+ addonId: null,
30801
+ access: "create"
30802
+ },
30803
+ "pipelineAnalytics.saveRetrainAnnotations": {
30804
+ capName: "pipeline-analytics",
30805
+ capScope: "device",
30806
+ addonId: null,
30807
+ access: "create"
30808
+ },
30226
30809
  "pipelineAnalytics.searchObjectEvents": {
30227
30810
  capName: "pipeline-analytics",
30228
30811
  capScope: "device",
30229
30812
  addonId: null,
30230
30813
  access: "view"
30231
30814
  },
30815
+ "pipelineAnalytics.selectRetrainFrames": {
30816
+ capName: "pipeline-analytics",
30817
+ capScope: "device",
30818
+ addonId: null,
30819
+ access: "create"
30820
+ },
30232
30821
  "pipelineAnalytics.setTrackFlags": {
30233
30822
  capName: "pipeline-analytics",
30234
30823
  capScope: "device",
@@ -31621,6 +32210,12 @@ Object.freeze({
31621
32210
  addonId: null,
31622
32211
  access: "create"
31623
32212
  },
32213
+ "streamBroker.fetchEventMedia": {
32214
+ capName: "stream-broker",
32215
+ capScope: "system",
32216
+ addonId: null,
32217
+ access: "create"
32218
+ },
31624
32219
  "streamBroker.getAllRtspEntries": {
31625
32220
  capName: "stream-broker",
31626
32221
  capScope: "system",
@@ -31705,6 +32300,12 @@ Object.freeze({
31705
32300
  addonId: null,
31706
32301
  access: "create"
31707
32302
  },
32303
+ "streamBroker.produceEventMedia": {
32304
+ capName: "stream-broker",
32305
+ capScope: "system",
32306
+ addonId: null,
32307
+ access: "create"
32308
+ },
31708
32309
  "streamBroker.publishCameraStream": {
31709
32310
  capName: "stream-broker",
31710
32311
  capScope: "system",
@@ -32714,4 +33315,4 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
32714
33315
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
32715
33316
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
32716
33317
  //#endregion
32717
- export { literal as $, faceGalleryCapability as A, videoclipsCapability as B, audioMetricsCapability as C, defineCustomActions as D, customAction as E, pipelineAnalyticsCapability as F, createEvent as G, errMsg as H, plateGalleryCapability as I, nodePin as J, hydrateSchema as K, readDeviceStateFrom as L, isScheduleActive as M, kebabToCamel as N, embeddingEncoderCapability as O, notificationRulesCapability as P, boolean as Q, subKindsOf as R, alarmPanelCapability as S, cosineSimilarity as T, BaseAddon as U, zoneAnalyticsCapability as V, DeviceType as W, _enum as X, sleep as Y, array as Z, RetrainStatusSchema as _, MACRO_LABELS as a, EventCategory as at, TrackSourceSchema as b, NcConditionDescriptorSchema as c, NcRuleSchema as d, number as et, NcSnoozeInputSchema as f, OpsLogEntrySchema as g, NcTaxonomySchema as h, EVENT_PAD_MS as i, unknown as it, hfModelUrl as j, encodeVectorBase64 as k, NcRuleInputSchema as l, NcSnoozeSuppressedSchema as m, DEFAULT_EVENT_COLOR as n, record as nt, NC_CONDITION_CATALOG as o, NcSnoozeSchema as p, isDeviceScopedCap as q, EVENT_KIND_BY_CAP as r, string as rt, NC_TAXONOMY as s, BaseDevice as t, object as tt, NcRulePatchSchema as u, TimelapseRuleInputSchema as v, buildEventKindDescriptor as w, addonWidgetsSourceCapability as x, TimelapseRuleSchema as y, vectorDimFromBase64 as z };
33318
+ export { boolean as $, encodeVectorBase64 as A, vectorDimFromBase64 as B, alarmPanelCapability as C, customAction as D, cosineSimilarity as E, notificationRulesCapability as F, DeviceType as G, zoneAnalyticsCapability as H, pipelineAnalyticsCapability as I, isDeviceScopedCap as J, createEvent as K, plateGalleryCapability as L, hfModelUrl as M, isScheduleActive as N, defineCustomActions as O, kebabToCamel as P, array as Q, readDeviceStateFrom as R, addonWidgetsSourceCapability as S, buildEventKindDescriptor as T, errMsg as U, videoclipsCapability as V, BaseAddon as W, sleep as X, nodePin as Y, _enum as Z, OpsLogEntrySchema as _, LabelAttributionSchema as a, unknown as at, TimelapseRuleSchema as b, NC_TAXONOMY as c, NcRulePatchSchema as d, literal as et, NcRuleSchema as f, NcTaxonomySchema as g, NcSnoozeSuppressedSchema as h, EVENT_PAD_MS as i, string as it, faceGalleryCapability as j, embeddingEncoderCapability as k, NcConditionDescriptorSchema as l, NcSnoozeSchema as m, DEFAULT_EVENT_COLOR as n, object as nt, MACRO_LABELS as o, EventCategory as ot, NcSnoozeInputSchema as p, hydrateSchema as q, EVENT_KIND_BY_CAP as r, record as rt, NC_CONDITION_CATALOG as s, BaseDevice as t, number as tt, NcRuleInputSchema as u, RetrainStatusSchema as v, audioMetricsCapability as w, TrackSourceSchema as x, TimelapseRuleInputSchema as y, subKindsOf as z };