@camstack/addon-post-analysis 1.2.284 → 1.2.285

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.
@@ -5391,7 +5391,7 @@ var ZodIssueCode = {
5391
5391
  var ZodFirstPartyTypeKind;
5392
5392
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5393
5393
  //#endregion
5394
- //#region ../types/dist/sleep-DU7mu4wb.mjs
5394
+ //#region ../types/dist/sleep-BaRu0bS5.mjs
5395
5395
  /**
5396
5396
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5397
5397
  * window to float samples (D455).
@@ -10055,7 +10055,7 @@ var ModelFormatEntrySchema = object({
10055
10055
  /** Multi-file payload (directory members or sibling files). */
10056
10056
  files: array(string()).readonly().optional(),
10057
10057
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
10058
- runtimes: array(_enum(["node", "python"])).readonly().optional()
10058
+ runtimes: array(_enum(["python"])).readonly().optional()
10059
10059
  });
10060
10060
  /**
10061
10061
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -18126,6 +18126,25 @@ var NcScheduleSchema = object({
18126
18126
  /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
18127
18127
  invert: boolean().optional()
18128
18128
  });
18129
+ /**
18130
+ * How a system-event rule of the `device-events` or `system` family speaks
18131
+ * (D647): at once, per event (`instant`, the only behaviour before D647), or
18132
+ * as a STATE SUMMARY at the end of each window of `schedule`.
18133
+ */
18134
+ var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
18135
+ /**
18136
+ * `summary`: the rule sends NOTHING per event. When a window of `schedule`
18137
+ * CLOSES, it sends one notification with the current state of its kinds
18138
+ * (low batteries, consumables to replace, devices offline, updates
18139
+ * available, ...), after its own filters. Nothing to report sends nothing.
18140
+ * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
18141
+ * midnight crossing); the send time is each window's END. Refused on the
18142
+ * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
18143
+ */
18144
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
18145
+ mode: literal("summary"),
18146
+ schedule: NcScheduleSchema
18147
+ })]);
18129
18148
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
18130
18149
  var NcPlateMatcherSchema = object({
18131
18150
  /**
@@ -18861,6 +18880,16 @@ var NcRuleInputSchema = object({
18861
18880
  delivery: NcDeliverySchema,
18862
18881
  conditions: NcConditionsSchema.default({}),
18863
18882
  schedule: NcScheduleSchema.optional(),
18883
+ /**
18884
+ * A `device-events` / `system` rule's delivery (D647): absent or `instant`
18885
+ * is a notification per event; `summary` is one state summary per closed
18886
+ * window of its schedule. `.optional()` and never defaulted: absent must
18887
+ * keep meaning what every rule authored before D647 meant, with no
18888
+ * migration. Top-level rather than inside `conditions.systemEvent`, so a
18889
+ * client that does not know it (an older viewer re-sending `conditions`
18890
+ * whole) leaves it standing through the store's patch merge.
18891
+ */
18892
+ systemEventDelivery: NcSystemEventDeliverySchema.optional(),
18864
18893
  /** May be empty when `targetUsers` addresses at least one user — the
18865
18894
  * "at least one addressee" invariant is enforced by the provider, because
18866
18895
  * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
@@ -19089,6 +19118,28 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19089
19118
  */
19090
19119
  disabledTargetIds: array(string()).default([])
19091
19120
  });
19121
+ /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19122
+ var NcStateSummaryPreviewSectionSchema = object({
19123
+ id: string(),
19124
+ status: _enum(["read", "unavailable"]),
19125
+ items: number().int().nonnegative(),
19126
+ reason: string().optional()
19127
+ });
19128
+ /**
19129
+ * The summary a rule would send now. `empty` = nothing to report and every
19130
+ * section read: such a summary is NOT sent. `dropped` counts the open states
19131
+ * the rule's own filters (or a camera mute) left out, by reason.
19132
+ * `excludedGroups` names the kind groups selected that have no state to report
19133
+ * (backups & exports).
19134
+ */
19135
+ var NcStateSummaryPreviewSchema = object({
19136
+ empty: boolean(),
19137
+ count: number().int().nonnegative(),
19138
+ text: string(),
19139
+ sections: array(NcStateSummaryPreviewSectionSchema),
19140
+ dropped: record(string(), number().int().nonnegative()),
19141
+ excludedGroups: array(string())
19142
+ });
19092
19143
  var NcTestResultSchema = object({
19093
19144
  recordId: string(),
19094
19145
  recordKind: _enum([
@@ -19180,6 +19231,13 @@ var NcTemplateVarDescriptorSchema = object({
19180
19231
  kinds: array(NcTemplateVarKindSchema).optional(),
19181
19232
  deliveries: array(NcDeliverySchema).optional(),
19182
19233
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
19234
+ /**
19235
+ * Which system-event DELIVERY has a value for it (D647). Absent = an
19236
+ * instant row only when the descriptor names `systemEventKinds` (a
19237
+ * per-event fact: a summary row has no single device), and both otherwise.
19238
+ * `['summary']` marks the state-summary vocabulary (`{{summaryText}}`, ...).
19239
+ */
19240
+ systemEventDeliveries: array(NcSystemEventDeliveryModeSchema).optional(),
19183
19241
  dynamic: object({
19184
19242
  prefix: literal("count_"),
19185
19243
  from: literal("classes")
@@ -19192,7 +19250,9 @@ var NcTemplatePreviewInputSchema = object({
19192
19250
  field: NcTemplateFieldSchema,
19193
19251
  kind: NcRuleKindSchema.optional(),
19194
19252
  delivery: NcDeliverySchema.optional(),
19195
- systemEventKinds: array(NcSystemEventKindSchema).optional()
19253
+ systemEventKinds: array(NcSystemEventKindSchema).optional(),
19254
+ /** The rule's system-event delivery (D647); absent = instant. */
19255
+ systemEventDelivery: NcSystemEventDeliveryModeSchema.optional()
19196
19256
  }),
19197
19257
  template: object({
19198
19258
  title: string().max(500).optional(),
@@ -20326,6 +20386,17 @@ var notificationRulesCapability = {
20326
20386
  auth: "admin"
20327
20387
  }),
20328
20388
  /**
20389
+ * The STATE SUMMARY a system rule would send now (D647) — for a saved rule
20390
+ * or an unsaved draft. Pure: sends nothing, stamps nothing. A MUTATION
20391
+ * only for transport (a whole rule does not belong in a GET URL). Admin,
20392
+ * like `testRule`: it names devices and nodes across the installation.
20393
+ */
20394
+ previewStateSummary: method(object({ rule: NcRuleInputSchema }), NcStateSummaryPreviewSchema, {
20395
+ kind: "mutation",
20396
+ auth: "admin",
20397
+ access: "view"
20398
+ }),
20399
+ /**
20329
20400
  * The machine-readable condition surface a rule editor renders from, plus
20330
20401
  * the picker VOCABULARY those conditions draw on.
20331
20402
  *
@@ -20770,646 +20841,6 @@ var OauthIntegrationDescriptorSchema = object({
20770
20841
  });
20771
20842
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
20772
20843
  /**
20773
- * Reference to the frame's retained NATIVE surface + the parent crop's placement
20774
- * within the frame, so the executor can re-cut a leaf child ROI at native
20775
- * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
20776
- */
20777
- var NativeCropRefSchema = object({
20778
- /** Handle keying the retained native surface (node-pinned to its owner). */
20779
- handle: FrameHandleSchema,
20780
- /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
20781
- cropFrameSpace: object({
20782
- x: number(),
20783
- y: number(),
20784
- w: number(),
20785
- h: number()
20786
- })
20787
- });
20788
- object({
20789
- crop: object({
20790
- left: number(),
20791
- top: number(),
20792
- width: number().positive(),
20793
- height: number().positive()
20794
- }).optional(),
20795
- content: object({
20796
- width: number().int().positive(),
20797
- height: number().int().positive()
20798
- }),
20799
- fit: _enum(["stretch", "contain"]),
20800
- format: _enum([
20801
- "rgb",
20802
- "gray",
20803
- "jpeg"
20804
- ])
20805
- });
20806
- /**
20807
- * Process-local frame identity. It is serializable so it can ride an in-process
20808
- * capability call, but `registryId` deliberately prevents resolution in any
20809
- * other process or execution group.
20810
- */
20811
- var FrameRefSchema = object({
20812
- registryId: string().min(1),
20813
- id: string().min(1),
20814
- width: number().int().positive(),
20815
- height: number().int().positive(),
20816
- format: _enum(["rgb", "gray"]),
20817
- timestamp: number(),
20818
- capturedAt: number().optional()
20819
- });
20820
- var ModelFormatSchema$1 = _enum([
20821
- "onnx",
20822
- "coreml",
20823
- "openvino",
20824
- "tflite",
20825
- "pt",
20826
- "gguf"
20827
- ]);
20828
- var PipelineSlotSchema = _enum([
20829
- "detector",
20830
- "cropper",
20831
- "classifier",
20832
- "refiner",
20833
- "audio-classifier"
20834
- ]);
20835
- var PipelineEngineChoiceSchema = object({
20836
- runtime: _enum(["node", "python"]),
20837
- backend: string(),
20838
- format: ModelFormatSchema$1,
20839
- device: string().optional()
20840
- });
20841
- var AvailableEngineSchema = object({
20842
- engine: PipelineEngineChoiceSchema,
20843
- devices: array(object({
20844
- id: string(),
20845
- label: string(),
20846
- description: string().optional()
20847
- })).readonly(),
20848
- defaultDevice: string()
20849
- });
20850
- var PipelineDefaultStepSchema = lazy(() => object({
20851
- addonId: string(),
20852
- addonName: string(),
20853
- slot: PipelineSlotSchema,
20854
- inputClasses: array(string()).readonly(),
20855
- outputClasses: array(string()).readonly(),
20856
- enabled: boolean(),
20857
- modelId: string(),
20858
- children: array(PipelineDefaultStepSchema).readonly(),
20859
- group: string().optional(),
20860
- settings: record(string(), unknown()).optional()
20861
- }));
20862
- var PipelineTemplateStepSchema = lazy(() => object({
20863
- addonId: string(),
20864
- enabled: boolean(),
20865
- modelId: string(),
20866
- children: array(PipelineTemplateStepSchema).readonly(),
20867
- settings: record(string(), unknown()).optional()
20868
- }));
20869
- var PipelineTemplateSchema$1 = object({
20870
- id: string(),
20871
- name: string(),
20872
- createdAt: string(),
20873
- updatedAt: string(),
20874
- engine: PipelineEngineChoiceSchema,
20875
- steps: array(PipelineTemplateStepSchema).readonly()
20876
- });
20877
- var PipelineModelOptionSchema = object({
20878
- id: string(),
20879
- name: string(),
20880
- formats: record(string(), object({
20881
- downloaded: boolean(),
20882
- sizeMB: number()
20883
- })),
20884
- group: ModelVariantGroupSchema.optional(),
20885
- legacy: boolean().optional(),
20886
- provider: ModelProviderIdSchema.optional()
20887
- });
20888
- var ConfigFieldBridge = custom();
20889
- var PipelineAddonSchemaSchema = object({
20890
- id: string(),
20891
- name: string(),
20892
- slot: PipelineSlotSchema,
20893
- inputClasses: array(string()).readonly(),
20894
- outputClasses: array(string()).readonly(),
20895
- childSlots: array(PipelineSlotSchema).readonly(),
20896
- models: array(PipelineModelOptionSchema).readonly(),
20897
- defaultModelId: string(),
20898
- defaultModelIdByFormat: record(string(), string()).optional(),
20899
- enabledByDefault: boolean().optional(),
20900
- backfillIntoExistingOverrides: boolean().optional(),
20901
- defaultConfidence: number(),
20902
- group: string().optional(),
20903
- configSchema: array(ConfigFieldBridge).readonly().optional()
20904
- });
20905
- var PipelineSlotSchemaSchema = object({
20906
- id: PipelineSlotSchema,
20907
- label: string(),
20908
- priority: number(),
20909
- parentSlot: PipelineSlotSchema.nullable(),
20910
- addons: array(PipelineAddonSchemaSchema).readonly()
20911
- });
20912
- var PipelineSchemaSchema = object({
20913
- availableEngines: array(AvailableEngineSchema).readonly(),
20914
- selectedEngine: PipelineEngineChoiceSchema,
20915
- slots: array(PipelineSlotSchemaSchema).readonly()
20916
- });
20917
- var EngineProvisioningSchema = object({
20918
- runtimeId: _enum([
20919
- "onnx",
20920
- "openvino",
20921
- "coreml",
20922
- "edgetpu"
20923
- ]).nullable(),
20924
- device: string().nullable(),
20925
- state: _enum([
20926
- "idle",
20927
- "installing",
20928
- "verifying",
20929
- "ready",
20930
- "failed"
20931
- ]),
20932
- progress: number().optional(),
20933
- error: string().optional(),
20934
- nextRetryAt: number().optional(),
20935
- /**
20936
- * Gate A (config-correctness gate at engine change): human-readable
20937
- * config issues surfaced EAGERLY when the node's engine changes — model
20938
- * substitutions ("chose X, running Y") and zero-build steps ("no model
20939
- * has a <format> build"). Additive/optional: informational only, never
20940
- * enforced here — `assertEngineReady` (readiness) still gates inference.
20941
- * Absent/empty when the node-default tree resolves cleanly.
20942
- */
20943
- configIssues: array(string()).optional()
20944
- });
20945
- var PipelineStepInputSchema = lazy(() => object({
20946
- addonId: string(),
20947
- modelId: string().optional(),
20948
- enabled: boolean().default(true),
20949
- children: array(PipelineStepInputSchema).optional(),
20950
- settings: record(string(), unknown()).optional(),
20951
- jumpDeviceKey: string().optional()
20952
- }));
20953
- var ModelSubstitutionSchema = object({
20954
- addonId: string(),
20955
- chosen: string(),
20956
- running: string(),
20957
- format: string()
20958
- });
20959
- var PipelineValidationIssueSchema = object({
20960
- addonId: string(),
20961
- kind: _enum(["unknown-addon", "no-format-build"]),
20962
- detail: string()
20963
- });
20964
- var PipelineValidationResultSchema = object({
20965
- ok: boolean(),
20966
- issues: array(PipelineValidationIssueSchema).readonly(),
20967
- substitutions: array(ModelSubstitutionSchema).readonly(),
20968
- /** The node's `currentEngine.format` this validation ran against. */
20969
- format: string()
20970
- });
20971
- var ReferenceImageEntrySchema = object({
20972
- filename: string(),
20973
- stepIds: array(string()).readonly().optional()
20974
- });
20975
- var ReferenceImageBodySchema = object({
20976
- base64: string(),
20977
- filename: string()
20978
- });
20979
- var ReferenceAudioEntrySchema = object({
20980
- filename: string(),
20981
- sizeKb: number()
20982
- });
20983
- var ReferenceAudioBodySchema = object({ base64: string() });
20984
- var AudioBackendSchema = object({
20985
- id: string(),
20986
- name: string(),
20987
- description: string(),
20988
- available: boolean(),
20989
- /**
20990
- * Raw classifier labels this backend can emit (e.g. YAMNet's
20991
- * 521-class set or Apple SoundAnalysis's 303-class set). Used by
20992
- * the benchmark UI to populate the `enabledMicroClasses` filter
20993
- * specific to the selected backend without a separate fetch.
20994
- */
20995
- rawLabels: array(string()).readonly().optional()
20996
- });
20997
- var AudioCapabilitiesSchema = object({
20998
- activeBackend: string(),
20999
- availableBackends: array(AudioBackendSchema).readonly(),
21000
- sampleRate: number(),
21001
- chunkDurationMs: number()
21002
- });
21003
- var DownloadModelResultSchema = object({
21004
- filePath: string(),
21005
- sizeMB: number(),
21006
- durationMs: number()
21007
- });
21008
- /**
21009
- * Wrapper carrying a single test run's result. Replaces the legacy
21010
- * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
21011
- * canonical `AudioResult` from the Phase 6 output rework: one
21012
- * `AudioDetection` per class above `minScore`, top-N candidates in
21013
- * `debug.alternateLabels['audio-classifier']`, per-source timings in
21014
- * `debug.stepTimings`. The outer `success`/`error` fields stay so the
21015
- * benchmark UI can still report a clean failure when the classifier
21016
- * cap isn't available.
21017
- */
21018
- var AudioTestResultSchema = object({
21019
- success: boolean(),
21020
- error: string().optional(),
21021
- frame: custom().optional()
21022
- });
21023
- var PipelineConfigBridge = custom();
21024
- var ConfigUISchemaBridge = custom();
21025
- var ConfigUISchemaNullableBridge = custom();
21026
- var InferenceCapabilitiesBridge = custom();
21027
- var ModelAvailabilityListBridge = custom();
21028
- var PipelineRunResultBridge = custom();
21029
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
21030
- modelId: string(),
21031
- settings: record(string(), unknown()).readonly()
21032
- }))), method(object({ steps: record(string(), object({
21033
- modelId: string(),
21034
- settings: record(string(), unknown()).readonly()
21035
- })) }), object({ success: literal(true) }), {
21036
- kind: "mutation",
21037
- auth: "admin"
21038
- }), method(object({ nodeId: string() }), object({
21039
- success: literal(true),
21040
- clearedDevices: number()
21041
- }), {
21042
- kind: "mutation",
21043
- auth: "admin"
21044
- }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
21045
- /** `<backend>:<device>`, e.g. `openvino:gpu`. */
21046
- deviceKey: string(),
21047
- /**
21048
- * `failed` — the per-device restart budget is exhausted; no pool
21049
- * will be spawned until an operator re-arms it or the runner
21050
- * respawns. `backoff` — under budget, waiting out the backoff (or
21051
- * a cached pool observed dead and not yet condemned).
21052
- */
21053
- state: _enum(["failed", "backoff"]),
21054
- /** Epoch ms of the death that produced this state. */
21055
- since: number(),
21056
- /** Pool deaths inside the current window. */
21057
- deaths: number(),
21058
- /** The last death's message. */
21059
- lastError: string()
21060
- })).readonly() })), method(object({
21061
- nodeId: string(),
21062
- deviceKey: string()
21063
- }), object({ rearmed: boolean() }), {
21064
- kind: "mutation",
21065
- auth: "admin"
21066
- }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({ steps: array(PipelineStepInputSchema) }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
21067
- name: string(),
21068
- steps: array(PipelineTemplateStepSchema).readonly(),
21069
- engine: PipelineEngineChoiceSchema
21070
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
21071
- id: string(),
21072
- name: string().optional(),
21073
- steps: array(PipelineTemplateStepSchema).readonly().optional()
21074
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
21075
- addonId: string(),
21076
- modelId: string(),
21077
- format: ModelFormatSchema$1
21078
- }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
21079
- addonId: string(),
21080
- modelId: string(),
21081
- format: ModelFormatSchema$1
21082
- }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
21083
- engine: PipelineEngineChoiceSchema.optional(),
21084
- steps: array(PipelineStepInputSchema).min(1),
21085
- frame: FrameInputSchema.optional(),
21086
- /**
21087
- * Process-local lazy frame. Valid only when caller and provider resolve
21088
- * in the same execution-group process; split/cross-node callers use
21089
- * `frame`/`image` inline compatibility instead.
21090
- */
21091
- frameRef: FrameRefSchema.optional(),
21092
- /**
21093
- * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
21094
- * the decoded pixels live in. One more member of the one-of
21095
- * frame/frameHandle/image/imageBase64/referenceImage group.
21096
- */
21097
- frameHandle: FrameHandleSchema.optional(),
21098
- imageBase64: string().optional(),
21099
- /**
21100
- * Binary JPEG bytes — preferred over `imageBase64` on internal
21101
- * hops (hub → forked worker via Moleculer MsgPack) because it
21102
- * skips the 33% base64 overhead + the per-call base64 decode on
21103
- * the detection-pipeline worker. Callers can pass either; exactly
21104
- * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
21105
- */
21106
- image: _instanceof(Uint8Array).optional(),
21107
- referenceImage: string().optional(),
21108
- deviceId: number().optional(),
21109
- sessionId: string().optional(),
21110
- /**
21111
- * Execution plane. 'full' (default) runs the whole tree — benchmark,
21112
- * reference-image, and detail-subtree calls. 'frame' is the live
21113
- * per-frame dispatch: ONLY root-plane steps run; crop children
21114
- * (inputClasses ≠ null) are skipped and served per-track via
21115
- * pipelineRunner.runDetailSubtree (two-plane design).
21116
- */
21117
- plane: _enum(["full", "frame"]).optional(),
21118
- /**
21119
- * Inference-device selector (Phase 2 multi-device). Format
21120
- * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
21121
- * Omitted ⇒ the runner's default device (current single-engine
21122
- * behaviour). Selects WHICH device pool of the node runs the call.
21123
- */
21124
- deviceKey: string().optional(),
21125
- /**
21126
- * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
21127
- * when the parent crop was resolved from the frame's retained NATIVE
21128
- * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
21129
- * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
21130
- * resolution from that surface — the SAME quality path faces already
21131
- * had — instead of the downscaled parent tile. `handle` keys the native
21132
- * surface (node-pinned to its owner); `cropFrameSpace` is the parent
21133
- * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
21134
- * the executor's crop-normalized child ROI back into frame-normalized
21135
- * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
21136
- * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
21137
- * (today's behaviour on the fallback path).
21138
- */
21139
- nativeCropRef: NativeCropRefSchema.optional()
21140
- }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
21141
- engine: PipelineEngineChoiceSchema.optional(),
21142
- steps: array(PipelineStepInputSchema).min(1),
21143
- frames: array(FrameInputSchema).min(1).max(255),
21144
- deviceId: number().optional(),
21145
- sessionId: string().optional(),
21146
- /**
21147
- * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
21148
- * the batch to the Python pool's bench preprocess cache
21149
- * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
21150
- * preprocessed ONCE and every later inference is a pure-inference cache
21151
- * hit — the sustained-throughput run measures inference, not
21152
- * decode+preprocess+infer. Omitted/0 for live frames (all different →
21153
- * full preprocess every call, correct). Fresh per sustained run;
21154
- * released via `uncacheFrame`.
21155
- */
21156
- frameId: number().int().nonnegative().optional(),
21157
- /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
21158
- deviceKey: string().optional()
21159
- }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
21160
- data: _instanceof(Uint8Array),
21161
- width: number().int().positive(),
21162
- height: number().int().positive(),
21163
- format: _enum([
21164
- "rgb",
21165
- "bgr",
21166
- "gray"
21167
- ])
21168
- }), object({
21169
- frameId: number(),
21170
- width: number(),
21171
- height: number()
21172
- }), { kind: "mutation" }), method(object({
21173
- stepId: string(),
21174
- frameId: number().int()
21175
- }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(_void(), object({
21176
- batchMode: string(),
21177
- windowMs: number(),
21178
- maxBatchSize: number(),
21179
- concurrency: number()
21180
- })), method(_void(), array(object({
21181
- engineKey: string(),
21182
- engine: PipelineEngineChoiceSchema,
21183
- modelsLoaded: array(string()).readonly(),
21184
- inUseByCameras: array(number()).readonly(),
21185
- /**
21186
- * Origin of this resident factory.
21187
- * - `runtime` — main camera-serving engine (no idle TTL).
21188
- * - `warm-override` — benchmark/test override held in the warm
21189
- * cache; auto-disposed after the idle TTL.
21190
- * - `device-pool` — a concurrent per-device pool (Phase 2
21191
- * multi-device, keyed by `deviceKey`) resolved
21192
- * via `resolveDeviceFactory`. Runs alongside the
21193
- * `runtime` engine on a DIFFERENT accelerator
21194
- * (NPU / iGPU / Coral) — this is how the
21195
- * Engines tab shows all pools running at once.
21196
- */
21197
- kind: _enum([
21198
- "runtime",
21199
- "warm-override",
21200
- "device-pool"
21201
- ]),
21202
- /** Native pid of the underlying Python pool (null when no pool). */
21203
- poolPid: number().nullable(),
21204
- /** ms since this factory was last used (null when not warm-tracked). */
21205
- idleMs: number().nullable(),
21206
- /** Idle TTL after which `warm-override` factories self-evict (null when not applicable). */
21207
- idleTtlMs: number().nullable()
21208
- })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
21209
- kind: "mutation",
21210
- auth: "admin"
21211
- }), method(object({
21212
- engine: PipelineEngineChoiceSchema,
21213
- force: boolean().optional()
21214
- }), object({
21215
- success: boolean(),
21216
- reason: string().optional()
21217
- }), {
21218
- kind: "mutation",
21219
- auth: "admin"
21220
- }), method(_void(), array(ReferenceImageEntrySchema).readonly()), method(object({ filename: string() }), ReferenceImageBodySchema.nullable()), method(_void(), array(ReferenceAudioEntrySchema).readonly()), method(object({ filename: string() }), ReferenceAudioBodySchema.nullable()), method(_void(), AudioCapabilitiesSchema), method(object({
21221
- addonId: string(),
21222
- modelId: string(),
21223
- filename: string().optional(),
21224
- settings: record(string(), unknown()).optional()
21225
- }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
21226
- /**
21227
- * Per-stage gating mode applied to the zones a rule references.
21228
- *
21229
- * - `include`: the rule contributes to a **whitelist** for its stage.
21230
- * When at least one `include` rule fires for a stage, only entities
21231
- * inside one of those zones pass that stage.
21232
- * - `exclude`: the rule contributes to a **blacklist** for its stage.
21233
- * Entities inside one of those zones are dropped at that stage.
21234
- *
21235
- * `monitor`-style observation (count without filtering) is not a rule
21236
- * mode — zones without any matching rule are observed naturally by
21237
- * `zone-analytics` (live snapshot + history), so an "I just want to
21238
- * count, not filter" use case needs no rule at all.
21239
- */
21240
- var ZoneRuleModeEnum = _enum(["include", "exclude"]);
21241
- /**
21242
- * Per-consumer rule that references existing zones (geometry) and
21243
- * defines how a specific pipeline stage should treat them. Each
21244
- * consumer addon owns its own `ZoneRule[]` array in its per-device
21245
- * settings:
21246
- *
21247
- * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
21248
- * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
21249
- * - future: notification rules, audio gating, etc.
21250
- *
21251
- * One rule applies to N zones (`zoneIds[]`) so the operator can
21252
- * express "ignore motion in ALL of {garden, street}" with a single
21253
- * rule. `classFilter` narrows the rule to specific object classes —
21254
- * "drop person detections in the street, but keep cars" is one
21255
- * `exclude` rule with `classFilter: ['person']`.
21256
- *
21257
- * `enabled` is a soft toggle — the operator can keep the rule
21258
- * configured but inert without deleting it.
21259
- */
21260
- var ZoneRuleSchema = object({
21261
- /** Stable rule id — survives edits, used by the UI for diffing. */
21262
- id: string(),
21263
- /** Optional human-readable label rendered in the rule editor. */
21264
- name: string().optional(),
21265
- /** Zones this rule targets. The rule's `mode` applies to ALL
21266
- * listed zones (OR-set: a detection in any one of them counts).
21267
- * At least one zone id required — a rule with no targets is a
21268
- * configuration mistake and the form validator rejects it. */
21269
- zoneIds: array(string()).min(1).readonly(),
21270
- mode: ZoneRuleModeEnum,
21271
- /**
21272
- * Class names this rule applies to. Empty / undefined ⇒ rule
21273
- * applies to every class. Class strings match the `macroClass`
21274
- * field on detections (e.g. `person`, `car`, `dog`).
21275
- */
21276
- classFilter: array(string()).readonly().optional(),
21277
- /**
21278
- * Minimum bbox/mask overlap (0–1) with any of the rule's zones
21279
- * required to consider an entity "in the zone". Defaults to the
21280
- * consumer's stage default when omitted. Kept for back-compat with
21281
- * existing per-rule overrides; new operators pick the value via
21282
- * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
21283
- * set, the lower-level engine reads it as a 0–1 fraction.
21284
- */
21285
- overlapThreshold: number().min(0).max(1).optional(),
21286
- /**
21287
- * Operator-friendly version of `overlapThreshold` — the percentage
21288
- * of the detection's bbox that must lie inside the zone for the
21289
- * rule to match. Documented default is 85%; the engine substitutes
21290
- * that when the field is omitted (kept optional so existing rules
21291
- * stored without it stay valid).
21292
- *
21293
- * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
21294
- * rule, the engine prefers `bboxInclusionPct` because it's the
21295
- * field exposed in the UI. Internally both feed the same gate.
21296
- */
21297
- bboxInclusionPct: number().min(0).max(100).optional(),
21298
- /**
21299
- * When `true` and a detection has a segmentation mask, use the
21300
- * mask for overlap instead of the bbox. Detection-stage only;
21301
- * motion rules ignore this field.
21302
- */
21303
- preferMask: boolean().optional(),
21304
- /**
21305
- * Soft-toggle: `false` disables the rule without deleting it.
21306
- * Defaults to `true` so operators creating a rule via the UI
21307
- * see it active immediately.
21308
- */
21309
- enabled: boolean().default(true)
21310
- });
21311
- array(ZoneRuleSchema).readonly();
21312
- /**
21313
- * Zone — pure geometry + identity. NO filtering behaviour.
21314
- *
21315
- * Zones describe **where** in the frame the operator wants to flag
21316
- * something; consumer-owned {@link ZoneRule} arrays describe **how**
21317
- * each pipeline stage uses them. Splitting the two means a single
21318
- * polygon "Driveway" can simultaneously back a motion-exclude rule,
21319
- * a detection-include rule on `['car']`, and an occupancy aggregate
21320
- * — without three duplicated polygons.
21321
- *
21322
- * Owned by the orchestrator addon (provider) and mirrored into the
21323
- * `zones` device-state slice on every mutation. Consumers
21324
- * (motion-wasm, pipeline-executor, analytics, admin UI) read either
21325
- * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
21326
- * mirror with `onChanged`).
21327
- *
21328
- * Coordinates are normalised fractions of the frame (0–1) so zones
21329
- * survive resolution changes and stream profile switches.
21330
- *
21331
- * `kind` discriminates between full polygons (closed regions used
21332
- * for intrusion / occupancy filters) and tripwires (open 2-point
21333
- * line segments used for cross events). Onboard / firmware-reported
21334
- * zones (Reolink, ONVIF) are out of scope for now — see the deferred
21335
- * task list.
21336
- */
21337
- var ZoneKindEnum = _enum(["polygon", "tripwire"]);
21338
- /** Polygon vertex in fraction-of-frame coordinates (0–1). */
21339
- var PolygonPointSchema = object({
21340
- x: number(),
21341
- y: number()
21342
- });
21343
- /** A camera detection zone — pure geometry/identity. */
21344
- var ZoneSchema = object({
21345
- id: string(),
21346
- name: string(),
21347
- kind: ZoneKindEnum.default("polygon"),
21348
- /** Polygon vertices, fraction of frame (0–1). */
21349
- polygon: array(PolygonPointSchema).readonly(),
21350
- /** Visual color for UI rendering. */
21351
- color: string().default("#3b82f6")
21352
- });
21353
- /**
21354
- * Zones capability — per-camera CRUD over polygon detection zones.
21355
- *
21356
- * Provider lives in `addon-pipeline-orchestrator` (hub-only). Persists
21357
- * to per-device settings and mirrors into the `zones` device-state
21358
- * slice on every mutation, so downstream consumers can subscribe via
21359
- * `dev.state.zones.onChanged`.
21360
- *
21361
- * The cap surface only handles geometry + identity; filtering
21362
- * behaviour (per-class, include/exclude, threshold) lives in the
21363
- * consumer addons' rule arrays — see `ZoneRuleSchema` exported from
21364
- * `capabilities/schemas/zone-rule.js`.
21365
- */
21366
- var zonesCapability = {
21367
- name: "zones",
21368
- scope: "device",
21369
- mode: "singleton",
21370
- deviceTypes: [DeviceType.Camera],
21371
- methods: {
21372
- listZones: method(object({ deviceId: number() }), array(ZoneSchema).readonly()),
21373
- addZone: method(object({
21374
- deviceId: number(),
21375
- zone: ZoneSchema
21376
- }), _void(), {
21377
- kind: "mutation",
21378
- auth: "admin"
21379
- }),
21380
- removeZone: method(object({
21381
- deviceId: number(),
21382
- zoneId: string()
21383
- }), _void(), {
21384
- kind: "mutation",
21385
- auth: "admin"
21386
- }),
21387
- updateZone: method(object({
21388
- deviceId: number(),
21389
- zone: ZoneSchema
21390
- }), _void(), {
21391
- kind: "mutation",
21392
- auth: "admin"
21393
- })
21394
- },
21395
- /**
21396
- * Runtime-state slice — the live zone catalogue mirrored by the
21397
- * orchestrator on every CRUD mutation. Consumers read via
21398
- * `device.state.zones.value` / `.watch(...)` without round-tripping
21399
- * the cap, and the codegen DeviceProxy auto-wires the reactive
21400
- * handle. Slice shape is `{ zones: Zone[] }` so future extensions
21401
- * (e.g. zone groupings) can sit alongside the polygon list.
21402
- */
21403
- runtimeState: object({ zones: array(ZoneSchema).readonly() }),
21404
- /**
21405
- * Runtime-state durability: **restored** — written only on operator mutation, so a camera that never had one has nothing to re-derive from. This is the slice `zone-mirror-hydration.ts` exists to paper over.
21406
- *
21407
- * See `RuntimeStateDurability`. Enforced by
21408
- * `scripts/check-runtime-state-durability.ts`.
21409
- */
21410
- durability: "restored"
21411
- };
21412
- /**
21413
20844
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
21414
20845
  * per-frame detections emitted by the pipeline runner into tracked
21415
20846
  * objects, per-kind event collections (motion / object / audio), and
@@ -21980,6 +21411,22 @@ var TrackSchema = object({
21980
21411
  * omit it. Render nothing when absent.
21981
21412
  */
21982
21413
  previewMissReason: string().optional(),
21414
+ /**
21415
+ * The ROOT model that produced this track — the `FrameResult.rootModelId` of
21416
+ * the frame it was BORN on (D646). What a later replay compares against, so
21417
+ * "the original model" is named rather than assumed. A birth fact: never
21418
+ * rewritten by a later frame. **Absent ≠ unknown-model**: a row from before
21419
+ * the column, and a track the camera's own detector produced, carry none.
21420
+ */
21421
+ modelId: string().optional(),
21422
+ /**
21423
+ * Size (px) of the detection frame this track's `positions[].bbox` live in —
21424
+ * the frame it was born on, else the camera's latest analysis frame at
21425
+ * persist time. Divide a position by these to normalise it. Absent when the
21426
+ * size was never known.
21427
+ */
21428
+ frameWidth: number().optional(),
21429
+ frameHeight: number().optional(),
21983
21430
  ...TrackFlagFields,
21984
21431
  ...TrackRetrainFields
21985
21432
  });
@@ -22986,24 +22433,6 @@ var MediaReclaimStatusSchema = object({
22986
22433
  finishedAtMs: number().nullable(),
22987
22434
  error: string().nullable()
22988
22435
  });
22989
- var ReplayFrameInputSchema = object({
22990
- timestamp: number(),
22991
- frame: PipelineRunResultBridge
22992
- });
22993
- var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
22994
- className: string(),
22995
- firstSeenMs: number(),
22996
- lastSeenMs: number(),
22997
- /** Bbox of the track's FIRST matched detection, pixel-space in the clip's
22998
- * frame — a representative box for the diff's `(className, window, IoU)`
22999
- * pairing (`replay-diff.ts`). A replay does not need the full per-frame
23000
- * trajectory production's `Track.positions` keeps. */
23001
- bbox: BoundingBoxSchema,
23002
- /** How many of the input frames this track matched a real detection on
23003
- * (never a coasted/extrapolated frame) — the replay's own signal for "how
23004
- * solid is this track", cheaper than re-deriving it from a trajectory. */
23005
- framesMatched: number().int()
23006
- })).readonly() });
23007
22436
  /**
23008
22437
  * The long-term series a camera produces. A closed union so a typo cannot
23009
22438
  * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
@@ -23730,32 +23159,6 @@ var pipelineAnalyticsCapability = {
23730
23159
  kind: "mutation",
23731
23160
  auth: "admin"
23732
23161
  }),
23733
- /**
23734
- * The FrameProcessor pass of a replay run — see the `Replay` section
23735
- * above this capability's definition for why this is not the
23736
- * `processFrame` method this file's header says pipeline-analytics does
23737
- * not have.
23738
- *
23739
- * Constructs a FRESH `FrameProcessor` for `(deviceId, source)`, feeds it
23740
- * `frames` IN THE ORDER GIVEN (the caller is responsible for time
23741
- * ordering — this method does not sort), and returns the tracks it
23742
- * produced. Zero persistence: no `TrackStore`, no event bus, no media
23743
- * capture. `zones` / `detectionRules` are the run's OWN zone set —
23744
- * typically the camera's real zones plus an ephemeral overlay
23745
- * (`addon-benchmark`'s `replay-plan.ts`), never read from or written to
23746
- * the `zones` capability by this method itself.
23747
- */
23748
- runReplayFrameProcessor: method(object({
23749
- deviceId: number(),
23750
- source: DetectionSourceSchema,
23751
- zones: array(ZoneSchema).readonly().optional(),
23752
- detectionRules: array(ZoneRuleSchema).readonly().optional(),
23753
- zoneMembershipMinOverlap: number().min(0).max(1).optional(),
23754
- frames: array(ReplayFrameInputSchema).min(1)
23755
- }), RunReplayFrameProcessorResultSchema, {
23756
- kind: "mutation",
23757
- auth: "admin"
23758
- }),
23759
23162
  /** Every annotation on a track, oldest first. */
23760
23163
  listRetrainAnnotations: method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
23761
23164
  kind: "query",
@@ -23995,6 +23398,478 @@ var pipelineAnalyticsCapability = {
23995
23398
  }) }
23996
23399
  }
23997
23400
  };
23401
+ /**
23402
+ * Reference to the frame's retained NATIVE surface + the parent crop's placement
23403
+ * within the frame, so the executor can re-cut a leaf child ROI at native
23404
+ * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
23405
+ */
23406
+ var NativeCropRefSchema = object({
23407
+ /** Handle keying the retained native surface (node-pinned to its owner). */
23408
+ handle: FrameHandleSchema,
23409
+ /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
23410
+ cropFrameSpace: object({
23411
+ x: number(),
23412
+ y: number(),
23413
+ w: number(),
23414
+ h: number()
23415
+ })
23416
+ });
23417
+ object({
23418
+ crop: object({
23419
+ left: number(),
23420
+ top: number(),
23421
+ width: number().positive(),
23422
+ height: number().positive()
23423
+ }).optional(),
23424
+ content: object({
23425
+ width: number().int().positive(),
23426
+ height: number().int().positive()
23427
+ }),
23428
+ fit: _enum(["stretch", "contain"]),
23429
+ format: _enum([
23430
+ "rgb",
23431
+ "gray",
23432
+ "jpeg"
23433
+ ])
23434
+ });
23435
+ /**
23436
+ * Process-local frame identity. It is serializable so it can ride an in-process
23437
+ * capability call, but `registryId` deliberately prevents resolution in any
23438
+ * other process or execution group.
23439
+ */
23440
+ var FrameRefSchema = object({
23441
+ registryId: string().min(1),
23442
+ id: string().min(1),
23443
+ width: number().int().positive(),
23444
+ height: number().int().positive(),
23445
+ format: _enum(["rgb", "gray"]),
23446
+ timestamp: number(),
23447
+ capturedAt: number().optional()
23448
+ });
23449
+ var ModelFormatSchema$1 = _enum([
23450
+ "onnx",
23451
+ "coreml",
23452
+ "openvino",
23453
+ "tflite",
23454
+ "pt",
23455
+ "gguf"
23456
+ ]);
23457
+ var PipelineSlotSchema = _enum([
23458
+ "detector",
23459
+ "cropper",
23460
+ "classifier",
23461
+ "refiner",
23462
+ "audio-classifier"
23463
+ ]);
23464
+ var PipelineEngineChoiceSchema = object({
23465
+ runtime: literal("python"),
23466
+ backend: string(),
23467
+ format: ModelFormatSchema$1,
23468
+ device: string().optional()
23469
+ });
23470
+ /**
23471
+ * One pool's resolved provisioning (`getEffectiveTuning`). `numWorkers` is the
23472
+ * knob that lifts a saturated pool (`inference-devices.md`); `concurrency` is
23473
+ * per-worker in-flight requests. `deviceKey` echoes the pool asked about —
23474
+ * `null` for the node-default pool.
23475
+ */
23476
+ var EffectiveTuningSchema = object({
23477
+ deviceKey: string().nullable(),
23478
+ runtime: string(),
23479
+ batchMode: string(),
23480
+ windowMs: number(),
23481
+ maxBatchSize: number(),
23482
+ concurrency: number(),
23483
+ numWorkers: number()
23484
+ });
23485
+ var AvailableEngineSchema = object({
23486
+ engine: PipelineEngineChoiceSchema,
23487
+ devices: array(object({
23488
+ id: string(),
23489
+ label: string(),
23490
+ description: string().optional()
23491
+ })).readonly(),
23492
+ defaultDevice: string()
23493
+ });
23494
+ var PipelineDefaultStepSchema = lazy(() => object({
23495
+ addonId: string(),
23496
+ addonName: string(),
23497
+ slot: PipelineSlotSchema,
23498
+ inputClasses: array(string()).readonly(),
23499
+ outputClasses: array(string()).readonly(),
23500
+ enabled: boolean(),
23501
+ modelId: string(),
23502
+ children: array(PipelineDefaultStepSchema).readonly(),
23503
+ group: string().optional(),
23504
+ settings: record(string(), unknown()).optional()
23505
+ }));
23506
+ var PipelineTemplateStepSchema = lazy(() => object({
23507
+ addonId: string(),
23508
+ enabled: boolean(),
23509
+ modelId: string(),
23510
+ children: array(PipelineTemplateStepSchema).readonly(),
23511
+ settings: record(string(), unknown()).optional()
23512
+ }));
23513
+ var PipelineTemplateSchema$1 = object({
23514
+ id: string(),
23515
+ name: string(),
23516
+ createdAt: string(),
23517
+ updatedAt: string(),
23518
+ engine: PipelineEngineChoiceSchema,
23519
+ steps: array(PipelineTemplateStepSchema).readonly()
23520
+ });
23521
+ var PipelineModelOptionSchema = object({
23522
+ id: string(),
23523
+ name: string(),
23524
+ formats: record(string(), object({
23525
+ downloaded: boolean(),
23526
+ sizeMB: number()
23527
+ })),
23528
+ group: ModelVariantGroupSchema.optional(),
23529
+ legacy: boolean().optional(),
23530
+ provider: ModelProviderIdSchema.optional()
23531
+ });
23532
+ var ConfigFieldBridge = custom();
23533
+ var PipelineAddonSchemaSchema = object({
23534
+ id: string(),
23535
+ name: string(),
23536
+ slot: PipelineSlotSchema,
23537
+ inputClasses: array(string()).readonly(),
23538
+ outputClasses: array(string()).readonly(),
23539
+ childSlots: array(PipelineSlotSchema).readonly(),
23540
+ models: array(PipelineModelOptionSchema).readonly(),
23541
+ defaultModelId: string(),
23542
+ defaultModelIdByFormat: record(string(), string()).optional(),
23543
+ enabledByDefault: boolean().optional(),
23544
+ backfillIntoExistingOverrides: boolean().optional(),
23545
+ defaultConfidence: number(),
23546
+ group: string().optional(),
23547
+ configSchema: array(ConfigFieldBridge).readonly().optional()
23548
+ });
23549
+ var PipelineSlotSchemaSchema = object({
23550
+ id: PipelineSlotSchema,
23551
+ label: string(),
23552
+ priority: number(),
23553
+ parentSlot: PipelineSlotSchema.nullable(),
23554
+ addons: array(PipelineAddonSchemaSchema).readonly()
23555
+ });
23556
+ var PipelineSchemaSchema = object({
23557
+ availableEngines: array(AvailableEngineSchema).readonly(),
23558
+ selectedEngine: PipelineEngineChoiceSchema,
23559
+ slots: array(PipelineSlotSchemaSchema).readonly()
23560
+ });
23561
+ var EngineProvisioningSchema = object({
23562
+ runtimeId: _enum([
23563
+ "onnx",
23564
+ "openvino",
23565
+ "coreml",
23566
+ "edgetpu"
23567
+ ]).nullable(),
23568
+ device: string().nullable(),
23569
+ state: _enum([
23570
+ "idle",
23571
+ "installing",
23572
+ "verifying",
23573
+ "ready",
23574
+ "failed"
23575
+ ]),
23576
+ progress: number().optional(),
23577
+ error: string().optional(),
23578
+ nextRetryAt: number().optional(),
23579
+ /**
23580
+ * Gate A (config-correctness gate at engine change): human-readable
23581
+ * config issues surfaced EAGERLY when the node's engine changes — model
23582
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
23583
+ * has a <format> build"). Additive/optional: informational only, never
23584
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
23585
+ * Absent/empty when the node-default tree resolves cleanly.
23586
+ */
23587
+ configIssues: array(string()).optional()
23588
+ });
23589
+ var PipelineStepInputSchema = lazy(() => object({
23590
+ addonId: string(),
23591
+ modelId: string().optional(),
23592
+ enabled: boolean().default(true),
23593
+ children: array(PipelineStepInputSchema).optional(),
23594
+ settings: record(string(), unknown()).optional(),
23595
+ jumpDeviceKey: string().optional()
23596
+ }));
23597
+ var ModelSubstitutionSchema = object({
23598
+ addonId: string(),
23599
+ chosen: string(),
23600
+ running: string(),
23601
+ format: string()
23602
+ });
23603
+ var PipelineValidationIssueSchema = object({
23604
+ addonId: string(),
23605
+ kind: _enum([
23606
+ "unknown-addon",
23607
+ "no-format-build",
23608
+ "unknown-device"
23609
+ ]),
23610
+ detail: string()
23611
+ });
23612
+ var PipelineValidationResultSchema = object({
23613
+ ok: boolean(),
23614
+ issues: array(PipelineValidationIssueSchema).readonly(),
23615
+ substitutions: array(ModelSubstitutionSchema).readonly(),
23616
+ /** The node's `currentEngine.format` this validation ran against. */
23617
+ format: string()
23618
+ });
23619
+ var ReferenceImageEntrySchema = object({
23620
+ filename: string(),
23621
+ stepIds: array(string()).readonly().optional()
23622
+ });
23623
+ var ReferenceImageBodySchema = object({
23624
+ base64: string(),
23625
+ filename: string()
23626
+ });
23627
+ var ReferenceAudioEntrySchema = object({
23628
+ filename: string(),
23629
+ sizeKb: number()
23630
+ });
23631
+ var ReferenceAudioBodySchema = object({ base64: string() });
23632
+ var AudioBackendSchema = object({
23633
+ id: string(),
23634
+ name: string(),
23635
+ description: string(),
23636
+ available: boolean(),
23637
+ /**
23638
+ * Raw classifier labels this backend can emit (e.g. YAMNet's
23639
+ * 521-class set or Apple SoundAnalysis's 303-class set). Used by
23640
+ * the benchmark UI to populate the `enabledMicroClasses` filter
23641
+ * specific to the selected backend without a separate fetch.
23642
+ */
23643
+ rawLabels: array(string()).readonly().optional()
23644
+ });
23645
+ var AudioCapabilitiesSchema = object({
23646
+ activeBackend: string(),
23647
+ availableBackends: array(AudioBackendSchema).readonly(),
23648
+ sampleRate: number(),
23649
+ chunkDurationMs: number()
23650
+ });
23651
+ var DownloadModelResultSchema = object({
23652
+ filePath: string(),
23653
+ sizeMB: number(),
23654
+ durationMs: number()
23655
+ });
23656
+ /**
23657
+ * Wrapper carrying a single test run's result. Replaces the legacy
23658
+ * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
23659
+ * canonical `AudioResult` from the Phase 6 output rework: one
23660
+ * `AudioDetection` per class above `minScore`, top-N candidates in
23661
+ * `debug.alternateLabels['audio-classifier']`, per-source timings in
23662
+ * `debug.stepTimings`. The outer `success`/`error` fields stay so the
23663
+ * benchmark UI can still report a clean failure when the classifier
23664
+ * cap isn't available.
23665
+ */
23666
+ var AudioTestResultSchema = object({
23667
+ success: boolean(),
23668
+ error: string().optional(),
23669
+ frame: custom().optional()
23670
+ });
23671
+ var PipelineConfigBridge = custom();
23672
+ var ConfigUISchemaBridge = custom();
23673
+ var ConfigUISchemaNullableBridge = custom();
23674
+ var InferenceCapabilitiesBridge = custom();
23675
+ var ModelAvailabilityListBridge = custom();
23676
+ var PipelineRunResultBridge = custom();
23677
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
23678
+ modelId: string(),
23679
+ settings: record(string(), unknown()).readonly()
23680
+ }))), method(object({ steps: record(string(), object({
23681
+ modelId: string(),
23682
+ settings: record(string(), unknown()).readonly()
23683
+ })) }), object({ success: literal(true) }), {
23684
+ kind: "mutation",
23685
+ auth: "admin"
23686
+ }), method(object({ nodeId: string() }), object({
23687
+ success: literal(true),
23688
+ clearedDevices: number()
23689
+ }), {
23690
+ kind: "mutation",
23691
+ auth: "admin"
23692
+ }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
23693
+ /** `<backend>:<device>`, e.g. `openvino:gpu`. */
23694
+ deviceKey: string(),
23695
+ /**
23696
+ * `failed` — the per-device restart budget is exhausted; no pool
23697
+ * will be spawned until an operator re-arms it or the runner
23698
+ * respawns. `backoff` — under budget, waiting out the backoff (or
23699
+ * a cached pool observed dead and not yet condemned).
23700
+ */
23701
+ state: _enum(["failed", "backoff"]),
23702
+ /** Epoch ms of the death that produced this state. */
23703
+ since: number(),
23704
+ /** Pool deaths inside the current window. */
23705
+ deaths: number(),
23706
+ /** The last death's message. */
23707
+ lastError: string()
23708
+ })).readonly() })), method(object({
23709
+ nodeId: string(),
23710
+ deviceKey: string()
23711
+ }), object({ rearmed: boolean() }), {
23712
+ kind: "mutation",
23713
+ auth: "admin"
23714
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({
23715
+ steps: array(PipelineStepInputSchema),
23716
+ deviceKey: string().optional()
23717
+ }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
23718
+ name: string(),
23719
+ steps: array(PipelineTemplateStepSchema).readonly(),
23720
+ engine: PipelineEngineChoiceSchema
23721
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
23722
+ id: string(),
23723
+ name: string().optional(),
23724
+ steps: array(PipelineTemplateStepSchema).readonly().optional()
23725
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
23726
+ addonId: string(),
23727
+ modelId: string(),
23728
+ format: ModelFormatSchema$1
23729
+ }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
23730
+ addonId: string(),
23731
+ modelId: string(),
23732
+ format: ModelFormatSchema$1
23733
+ }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
23734
+ steps: array(PipelineStepInputSchema).min(1),
23735
+ frame: FrameInputSchema.optional(),
23736
+ /**
23737
+ * Process-local lazy frame. Valid only when caller and provider resolve
23738
+ * in the same execution-group process; split/cross-node callers use
23739
+ * `frame`/`image` inline compatibility instead.
23740
+ */
23741
+ frameRef: FrameRefSchema.optional(),
23742
+ /**
23743
+ * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
23744
+ * the decoded pixels live in. One more member of the one-of
23745
+ * frame/frameHandle/image/imageBase64/referenceImage group.
23746
+ */
23747
+ frameHandle: FrameHandleSchema.optional(),
23748
+ imageBase64: string().optional(),
23749
+ /**
23750
+ * Binary JPEG bytes — preferred over `imageBase64` on internal
23751
+ * hops (hub → forked worker via Moleculer MsgPack) because it
23752
+ * skips the 33% base64 overhead + the per-call base64 decode on
23753
+ * the detection-pipeline worker. Callers can pass either; exactly
23754
+ * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
23755
+ */
23756
+ image: _instanceof(Uint8Array).optional(),
23757
+ referenceImage: string().optional(),
23758
+ deviceId: number().optional(),
23759
+ sessionId: string().optional(),
23760
+ /**
23761
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
23762
+ * reference-image, and detail-subtree calls. 'frame' is the live
23763
+ * per-frame dispatch: ONLY root-plane steps run; crop children
23764
+ * (inputClasses ≠ null) are skipped and served per-track via
23765
+ * pipelineRunner.runDetailSubtree (two-plane design).
23766
+ */
23767
+ plane: _enum(["full", "frame"]).optional(),
23768
+ /**
23769
+ * Inference-device selector (Phase 2 multi-device). Format
23770
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
23771
+ * Omitted ⇒ the runner's default device (current single-engine
23772
+ * behaviour). Selects WHICH device pool of the node runs the call.
23773
+ */
23774
+ deviceKey: string().optional(),
23775
+ /**
23776
+ * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
23777
+ * when the parent crop was resolved from the frame's retained NATIVE
23778
+ * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
23779
+ * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
23780
+ * resolution from that surface — the SAME quality path faces already
23781
+ * had — instead of the downscaled parent tile. `handle` keys the native
23782
+ * surface (node-pinned to its owner); `cropFrameSpace` is the parent
23783
+ * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
23784
+ * the executor's crop-normalized child ROI back into frame-normalized
23785
+ * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
23786
+ * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
23787
+ * (today's behaviour on the fallback path).
23788
+ */
23789
+ nativeCropRef: NativeCropRefSchema.optional(),
23790
+ /**
23791
+ * Offline REPLAY of stored media for `deviceId` (D56, D646): the
23792
+ * camera's zone gate and overrides apply as live, every executing
23793
+ * step runs EXACTLY the model it names on the `deviceKey` pool it
23794
+ * names or the call refuses (`model-not-servable` /
23795
+ * `device-not-servable`), and nothing reaches the camera's live
23796
+ * trace stream. Absent ⇒ unchanged behaviour.
23797
+ */
23798
+ replay: literal(true).optional()
23799
+ }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
23800
+ steps: array(PipelineStepInputSchema).min(1),
23801
+ frames: array(FrameInputSchema).min(1).max(255),
23802
+ deviceId: number().optional(),
23803
+ sessionId: string().optional(),
23804
+ /**
23805
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
23806
+ * the batch to the Python pool's bench preprocess cache
23807
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
23808
+ * preprocessed ONCE and every later inference is a pure-inference cache
23809
+ * hit — the sustained-throughput run measures inference, not
23810
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
23811
+ * full preprocess every call, correct). Fresh per sustained run;
23812
+ * released via `uncacheFrame`.
23813
+ */
23814
+ frameId: number().int().nonnegative().optional(),
23815
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
23816
+ deviceKey: string().optional()
23817
+ }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
23818
+ data: _instanceof(Uint8Array),
23819
+ width: number().int().positive(),
23820
+ height: number().int().positive(),
23821
+ format: _enum([
23822
+ "rgb",
23823
+ "bgr",
23824
+ "gray"
23825
+ ])
23826
+ }), object({
23827
+ frameId: number(),
23828
+ width: number(),
23829
+ height: number()
23830
+ }), { kind: "mutation" }), method(object({
23831
+ stepId: string(),
23832
+ frameId: number().int()
23833
+ }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(object({ deviceKey: string().optional() }), EffectiveTuningSchema), method(_void(), array(object({
23834
+ engineKey: string(),
23835
+ engine: PipelineEngineChoiceSchema,
23836
+ modelsLoaded: array(string()).readonly(),
23837
+ inUseByCameras: array(number()).readonly(),
23838
+ /**
23839
+ * Origin of this resident factory.
23840
+ * - `runtime` — main camera-serving engine (no idle TTL).
23841
+ * - `device-pool` — a concurrent per-device pool (Phase 2
23842
+ * multi-device, keyed by `deviceKey`) resolved
23843
+ * via `resolveDeviceFactory`. Runs alongside the
23844
+ * `runtime` engine on a DIFFERENT accelerator
23845
+ * (NPU / iGPU / Coral) — this is how the
23846
+ * Engines tab shows all pools running at once.
23847
+ */
23848
+ kind: _enum(["runtime", "device-pool"]),
23849
+ /** Native pid of the underlying Python pool (null when no pool). */
23850
+ poolPid: number().nullable(),
23851
+ /** ms since this factory was last used (null when not warm-tracked). */
23852
+ idleMs: number().nullable(),
23853
+ /** Idle TTL after which the factory self-evicts (null when not applicable). */
23854
+ idleTtlMs: number().nullable()
23855
+ })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
23856
+ kind: "mutation",
23857
+ auth: "admin"
23858
+ }), method(object({
23859
+ engine: PipelineEngineChoiceSchema,
23860
+ force: boolean().optional()
23861
+ }), object({
23862
+ success: boolean(),
23863
+ reason: string().optional()
23864
+ }), {
23865
+ kind: "mutation",
23866
+ auth: "admin"
23867
+ }), method(_void(), array(ReferenceImageEntrySchema).readonly()), method(object({ filename: string() }), ReferenceImageBodySchema.nullable()), method(_void(), array(ReferenceAudioEntrySchema).readonly()), method(object({ filename: string() }), ReferenceAudioBodySchema.nullable()), method(_void(), AudioCapabilitiesSchema), method(object({
23868
+ addonId: string(),
23869
+ modelId: string(),
23870
+ filename: string().optional(),
23871
+ settings: record(string(), unknown()).optional()
23872
+ }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
23998
23873
  object({
23999
23874
  activeCameras: number(),
24000
23875
  throttledCameras: number(),
@@ -24020,6 +23895,106 @@ var CameraMetricsSchema = object({
24020
23895
  });
24021
23896
  var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
24022
23897
  /**
23898
+ * Zone — pure geometry + identity. NO filtering behaviour.
23899
+ *
23900
+ * Zones describe **where** in the frame the operator wants to flag
23901
+ * something; consumer-owned {@link ZoneRule} arrays describe **how**
23902
+ * each pipeline stage uses them. Splitting the two means a single
23903
+ * polygon "Driveway" can simultaneously back a motion-exclude rule,
23904
+ * a detection-include rule on `['car']`, and an occupancy aggregate
23905
+ * — without three duplicated polygons.
23906
+ *
23907
+ * Owned by the orchestrator addon (provider) and mirrored into the
23908
+ * `zones` device-state slice on every mutation. Consumers
23909
+ * (motion-wasm, pipeline-executor, analytics, admin UI) read either
23910
+ * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
23911
+ * mirror with `onChanged`).
23912
+ *
23913
+ * Coordinates are normalised fractions of the frame (0–1) so zones
23914
+ * survive resolution changes and stream profile switches.
23915
+ *
23916
+ * `kind` discriminates between full polygons (closed regions used
23917
+ * for intrusion / occupancy filters) and tripwires (open 2-point
23918
+ * line segments used for cross events). Onboard / firmware-reported
23919
+ * zones (Reolink, ONVIF) are out of scope for now — see the deferred
23920
+ * task list.
23921
+ */
23922
+ var ZoneKindEnum = _enum(["polygon", "tripwire"]);
23923
+ /** Polygon vertex in fraction-of-frame coordinates (0–1). */
23924
+ var PolygonPointSchema = object({
23925
+ x: number(),
23926
+ y: number()
23927
+ });
23928
+ /** A camera detection zone — pure geometry/identity. */
23929
+ var ZoneSchema = object({
23930
+ id: string(),
23931
+ name: string(),
23932
+ kind: ZoneKindEnum.default("polygon"),
23933
+ /** Polygon vertices, fraction of frame (0–1). */
23934
+ polygon: array(PolygonPointSchema).readonly(),
23935
+ /** Visual color for UI rendering. */
23936
+ color: string().default("#3b82f6")
23937
+ });
23938
+ /**
23939
+ * Zones capability — per-camera CRUD over polygon detection zones.
23940
+ *
23941
+ * Provider lives in `addon-pipeline-orchestrator` (hub-only). Persists
23942
+ * to per-device settings and mirrors into the `zones` device-state
23943
+ * slice on every mutation, so downstream consumers can subscribe via
23944
+ * `dev.state.zones.onChanged`.
23945
+ *
23946
+ * The cap surface only handles geometry + identity; filtering
23947
+ * behaviour (per-class, include/exclude, threshold) lives in the
23948
+ * consumer addons' rule arrays — see `ZoneRuleSchema` exported from
23949
+ * `capabilities/schemas/zone-rule.js`.
23950
+ */
23951
+ var zonesCapability = {
23952
+ name: "zones",
23953
+ scope: "device",
23954
+ mode: "singleton",
23955
+ deviceTypes: [DeviceType.Camera],
23956
+ methods: {
23957
+ listZones: method(object({ deviceId: number() }), array(ZoneSchema).readonly()),
23958
+ addZone: method(object({
23959
+ deviceId: number(),
23960
+ zone: ZoneSchema
23961
+ }), _void(), {
23962
+ kind: "mutation",
23963
+ auth: "admin"
23964
+ }),
23965
+ removeZone: method(object({
23966
+ deviceId: number(),
23967
+ zoneId: string()
23968
+ }), _void(), {
23969
+ kind: "mutation",
23970
+ auth: "admin"
23971
+ }),
23972
+ updateZone: method(object({
23973
+ deviceId: number(),
23974
+ zone: ZoneSchema
23975
+ }), _void(), {
23976
+ kind: "mutation",
23977
+ auth: "admin"
23978
+ })
23979
+ },
23980
+ /**
23981
+ * Runtime-state slice — the live zone catalogue mirrored by the
23982
+ * orchestrator on every CRUD mutation. Consumers read via
23983
+ * `device.state.zones.value` / `.watch(...)` without round-tripping
23984
+ * the cap, and the codegen DeviceProxy auto-wires the reactive
23985
+ * handle. Slice shape is `{ zones: Zone[] }` so future extensions
23986
+ * (e.g. zone groupings) can sit alongside the polygon list.
23987
+ */
23988
+ runtimeState: object({ zones: array(ZoneSchema).readonly() }),
23989
+ /**
23990
+ * Runtime-state durability: **restored** — written only on operator mutation, so a camera that never had one has nothing to re-derive from. This is the slice `zone-mirror-hydration.ts` exists to paper over.
23991
+ *
23992
+ * See `RuntimeStateDurability`. Enforced by
23993
+ * `scripts/check-runtime-state-durability.ts`.
23994
+ */
23995
+ durability: "restored"
23996
+ };
23997
+ /**
24023
23998
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
24024
23999
  * decode worker resolves it against the RETAINED native frame's real pixel dims,
24025
24000
  * so the caller supplies only the detection-res bbox divided by the detection
@@ -34393,7 +34368,7 @@ var HardwareInfoSchema = object({
34393
34368
  coral: CoralInfoSchema.nullable().optional()
34394
34369
  });
34395
34370
  var PlatformScoreSchema = object({
34396
- runtime: _enum(["node", "python"]),
34371
+ runtime: literal("python"),
34397
34372
  backend: string(),
34398
34373
  format: _enum([
34399
34374
  "onnx",
@@ -34430,7 +34405,7 @@ var ModelRequirementSchema = object({
34430
34405
  });
34431
34406
  var ResolvedInferenceConfigSchema = object({
34432
34407
  modelId: string(),
34433
- runtime: _enum(["node", "python"]),
34408
+ runtime: literal("python"),
34434
34409
  backend: string(),
34435
34410
  format: ModelFormatSchema,
34436
34411
  reason: string()
@@ -37025,6 +37000,92 @@ var sceneMonitorCapability = {
37025
37000
  durability: "session"
37026
37001
  };
37027
37002
  /**
37003
+ * Per-stage gating mode applied to the zones a rule references.
37004
+ *
37005
+ * - `include`: the rule contributes to a **whitelist** for its stage.
37006
+ * When at least one `include` rule fires for a stage, only entities
37007
+ * inside one of those zones pass that stage.
37008
+ * - `exclude`: the rule contributes to a **blacklist** for its stage.
37009
+ * Entities inside one of those zones are dropped at that stage.
37010
+ *
37011
+ * `monitor`-style observation (count without filtering) is not a rule
37012
+ * mode — zones without any matching rule are observed naturally by
37013
+ * `zone-analytics` (live snapshot + history), so an "I just want to
37014
+ * count, not filter" use case needs no rule at all.
37015
+ */
37016
+ var ZoneRuleModeEnum = _enum(["include", "exclude"]);
37017
+ /**
37018
+ * Per-consumer rule that references existing zones (geometry) and
37019
+ * defines how a specific pipeline stage should treat them. Each
37020
+ * consumer addon owns its own `ZoneRule[]` array in its per-device
37021
+ * settings:
37022
+ *
37023
+ * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
37024
+ * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
37025
+ * - future: notification rules, audio gating, etc.
37026
+ *
37027
+ * One rule applies to N zones (`zoneIds[]`) so the operator can
37028
+ * express "ignore motion in ALL of {garden, street}" with a single
37029
+ * rule. `classFilter` narrows the rule to specific object classes —
37030
+ * "drop person detections in the street, but keep cars" is one
37031
+ * `exclude` rule with `classFilter: ['person']`.
37032
+ *
37033
+ * `enabled` is a soft toggle — the operator can keep the rule
37034
+ * configured but inert without deleting it.
37035
+ */
37036
+ var ZoneRuleSchema = object({
37037
+ /** Stable rule id — survives edits, used by the UI for diffing. */
37038
+ id: string(),
37039
+ /** Optional human-readable label rendered in the rule editor. */
37040
+ name: string().optional(),
37041
+ /** Zones this rule targets. The rule's `mode` applies to ALL
37042
+ * listed zones (OR-set: a detection in any one of them counts).
37043
+ * At least one zone id required — a rule with no targets is a
37044
+ * configuration mistake and the form validator rejects it. */
37045
+ zoneIds: array(string()).min(1).readonly(),
37046
+ mode: ZoneRuleModeEnum,
37047
+ /**
37048
+ * Class names this rule applies to. Empty / undefined ⇒ rule
37049
+ * applies to every class. Class strings match the `macroClass`
37050
+ * field on detections (e.g. `person`, `car`, `dog`).
37051
+ */
37052
+ classFilter: array(string()).readonly().optional(),
37053
+ /**
37054
+ * Minimum bbox/mask overlap (0–1) with any of the rule's zones
37055
+ * required to consider an entity "in the zone". Defaults to the
37056
+ * consumer's stage default when omitted. Kept for back-compat with
37057
+ * existing per-rule overrides; new operators pick the value via
37058
+ * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
37059
+ * set, the lower-level engine reads it as a 0–1 fraction.
37060
+ */
37061
+ overlapThreshold: number().min(0).max(1).optional(),
37062
+ /**
37063
+ * Operator-friendly version of `overlapThreshold` — the percentage
37064
+ * of the detection's bbox that must lie inside the zone for the
37065
+ * rule to match. Documented default is 85%; the engine substitutes
37066
+ * that when the field is omitted (kept optional so existing rules
37067
+ * stored without it stay valid).
37068
+ *
37069
+ * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
37070
+ * rule, the engine prefers `bboxInclusionPct` because it's the
37071
+ * field exposed in the UI. Internally both feed the same gate.
37072
+ */
37073
+ bboxInclusionPct: number().min(0).max(100).optional(),
37074
+ /**
37075
+ * When `true` and a detection has a segmentation mask, use the
37076
+ * mask for overlap instead of the bbox. Detection-stage only;
37077
+ * motion rules ignore this field.
37078
+ */
37079
+ preferMask: boolean().optional(),
37080
+ /**
37081
+ * Soft-toggle: `false` disables the rule without deleting it.
37082
+ * Defaults to `true` so operators creating a rule via the UI
37083
+ * see it active immediately.
37084
+ */
37085
+ enabled: boolean().default(true)
37086
+ });
37087
+ array(ZoneRuleSchema).readonly();
37088
+ /**
37028
37089
  * Script-runner cap. Models HA `script.*` entities on
37029
37090
  * `DeviceType.Script`. A Script is a pre-recorded action sequence
37030
37091
  * that can be invoked imperatively — optionally with a variables
@@ -45196,6 +45257,12 @@ Object.freeze({
45196
45257
  addonId: null,
45197
45258
  access: "view"
45198
45259
  },
45260
+ "notificationRules.previewStateSummary": {
45261
+ capName: "notification-rules",
45262
+ capScope: "system",
45263
+ addonId: null,
45264
+ access: "view"
45265
+ },
45199
45266
  "notificationRules.previewTemplate": {
45200
45267
  capName: "notification-rules",
45201
45268
  capScope: "system",
@@ -45772,12 +45839,6 @@ Object.freeze({
45772
45839
  addonId: null,
45773
45840
  access: "create"
45774
45841
  },
45775
- "pipelineAnalytics.runReplayFrameProcessor": {
45776
- capName: "pipeline-analytics",
45777
- capScope: "device",
45778
- addonId: null,
45779
- access: "create"
45780
- },
45781
45842
  "pipelineAnalytics.saveRetrainAnnotations": {
45782
45843
  capName: "pipeline-analytics",
45783
45844
  capScope: "device",
@@ -49875,11 +49936,6 @@ Object.freeze({
49875
49936
  form: "single",
49876
49937
  optional: false
49877
49938
  }],
49878
- "pipelineAnalytics.runReplayFrameProcessor": [{
49879
- name: "deviceId",
49880
- form: "single",
49881
- optional: false
49882
- }],
49883
49939
  "pipelineAnalytics.saveRetrainAnnotations": [{
49884
49940
  name: "deviceId",
49885
49941
  form: "single",
@@ -50917,7 +50973,8 @@ var NC_SYSTEM_EVENT_FAMILY = {
50917
50973
  "camera-disabled": null,
50918
50974
  "camera-enabled": null
50919
50975
  };
50920
- new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50976
+ /** Read by NAME, for kinds that arrive as plain strings (a newer hub, the viewer's mirror). */
50977
+ var FAMILY_BY_NAME = new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50921
50978
  var NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = [...NC_SYSTEM_EVENT_FILTER_KEYS, "excludeDeviceIds"];
50922
50979
  /**
50923
50980
  * The filters a family may carry. The base filters are the union of
@@ -51028,6 +51085,24 @@ function systemEventKindsOfFamily(family) {
51028
51085
  return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
51029
51086
  }
51030
51087
  /**
51088
+ * One family, or `mixed`. Mixed covers kinds that span families, a legacy kind,
51089
+ * a kind this build does not know (it fails toward read-only and never toward
51090
+ * editable), and an empty selection (nothing to decide on).
51091
+ */
51092
+ function systemEventFamilyOf(kindNames) {
51093
+ const families = /* @__PURE__ */ new Set();
51094
+ for (const name of kindNames) {
51095
+ const family = FAMILY_BY_NAME.get(name);
51096
+ if (family === void 0 || family === null) return { kind: "mixed" };
51097
+ families.add(family);
51098
+ }
51099
+ const [only] = [...families];
51100
+ return families.size === 1 && only !== void 0 ? {
51101
+ kind: "family",
51102
+ family: only
51103
+ } : { kind: "mixed" };
51104
+ }
51105
+ /**
51031
51106
  * Why the hub refuses this condition on a new or updated rule. Empty = accepted.
51032
51107
  * Families are reported in `NC_SYSTEM_EVENT_FAMILIES` order, and filters in
51033
51108
  * `NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS` order, so a refusal reads the same
@@ -51103,18 +51178,38 @@ var WEEKDAY_TO_DAY = {
51103
51178
  Fri: 5,
51104
51179
  Sat: 6
51105
51180
  };
51106
- /** Resolve (weekday, minute-of-day) of `atMs` in the schedule's timezone.
51107
- * An invalid/unknown IANA name falls back to the host timezone. */
51108
- function localDayMinute(atMs, timezone) {
51109
- const d = new Date(atMs);
51110
- if (timezone !== void 0) try {
51111
- const parts = new Intl.DateTimeFormat("en-US", {
51181
+ /**
51182
+ * One formatter per timezone, built once (D647 review I4). Constructing an
51183
+ * `Intl.DateTimeFormat` is the expensive part of this function, and a caller
51184
+ * that asks about many instants — a window scan — used to build one per
51185
+ * instant. `null` = an invalid IANA name (the host timezone is used).
51186
+ */
51187
+ var FORMATTERS = /* @__PURE__ */ new Map();
51188
+ function formatterFor(timezone) {
51189
+ const held = FORMATTERS.get(timezone);
51190
+ if (held !== void 0) return held;
51191
+ let made;
51192
+ try {
51193
+ made = new Intl.DateTimeFormat("en-US", {
51112
51194
  timeZone: timezone,
51113
51195
  weekday: "short",
51114
51196
  hour: "numeric",
51115
51197
  minute: "numeric",
51116
51198
  hourCycle: "h23"
51117
- }).formatToParts(d);
51199
+ });
51200
+ } catch {
51201
+ made = null;
51202
+ }
51203
+ FORMATTERS.set(timezone, made);
51204
+ return made;
51205
+ }
51206
+ /** Resolve (weekday, minute-of-day) of `atMs` in the schedule's timezone.
51207
+ * An invalid/unknown IANA name falls back to the host timezone. */
51208
+ function localDayMinute(atMs, timezone) {
51209
+ const d = new Date(atMs);
51210
+ const formatter = timezone !== void 0 ? formatterFor(timezone) : null;
51211
+ if (formatter !== null) try {
51212
+ const parts = formatter.formatToParts(d);
51118
51213
  let weekday;
51119
51214
  let hour;
51120
51215
  let minute;
@@ -51254,6 +51349,119 @@ var DEVICE_EVENTS = [
51254
51349
  "detection-blind",
51255
51350
  ...ALARM
51256
51351
  ];
51352
+ /** Every kind a summary may speak about — the two summarisable families. */
51353
+ var SUMMARISABLE = [...systemEventKindsOfFamily("device-events"), ...systemEventKindsOfFamily("system")];
51354
+ /** One per summary SECTION (`NC_STATE_SUMMARY_SECTIONS` in the notification
51355
+ * centre): the section's lines, offered when one of its kinds is selected. */
51356
+ var STATE_SUMMARY_SECTION_VARS = [
51357
+ {
51358
+ name: "summaryDevices",
51359
+ label: "Summary: devices offline / switched off",
51360
+ example: "Garage — offline since 07:12",
51361
+ kinds: [
51362
+ "device-offline",
51363
+ "device-online",
51364
+ "device-disabled",
51365
+ "device-enabled"
51366
+ ]
51367
+ },
51368
+ {
51369
+ name: "summaryStreams",
51370
+ label: "Summary: streams offline",
51371
+ example: "Ingresso (high) — since 06:40",
51372
+ kinds: ["stream-offline", "stream-online"]
51373
+ },
51374
+ {
51375
+ name: "summaryBattery",
51376
+ label: "Summary: low batteries",
51377
+ example: "Sensore porta — 8 %",
51378
+ kinds: BATTERY
51379
+ },
51380
+ {
51381
+ name: "summaryConsumables",
51382
+ label: "Summary: consumables",
51383
+ example: "Robot — filter 10 %",
51384
+ kinds: CONSUMABLE
51385
+ },
51386
+ {
51387
+ name: "summaryTanks",
51388
+ label: "Summary: tanks",
51389
+ example: "Robot — clean water low",
51390
+ kinds: TANK
51391
+ },
51392
+ {
51393
+ name: "summaryDetection",
51394
+ label: "Summary: cameras detecting nothing",
51395
+ example: "Ingresso — since 05:10",
51396
+ kinds: ["detection-blind"]
51397
+ },
51398
+ {
51399
+ name: "summaryNodes",
51400
+ label: "Summary: nodes",
51401
+ example: "little-unraid — offline since 03:00",
51402
+ kinds: [
51403
+ "node-offline",
51404
+ "node-online",
51405
+ "node-inference-unavailable"
51406
+ ]
51407
+ },
51408
+ {
51409
+ name: "summaryAddons",
51410
+ label: "Summary: addons",
51411
+ example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
51412
+ kinds: [
51413
+ "addon-crash-loop",
51414
+ "addon-update-available",
51415
+ "addon-updated"
51416
+ ]
51417
+ },
51418
+ {
51419
+ name: "summaryUpdates",
51420
+ label: "Summary: server & container updates",
51421
+ example: "server 1.2.377 → 1.2.378",
51422
+ kinds: [
51423
+ "server-update-available",
51424
+ "server-updated",
51425
+ "wrapper-update-available"
51426
+ ]
51427
+ }
51428
+ ];
51429
+ var STATE_SUMMARY_VARS = [
51430
+ {
51431
+ name: "summaryText",
51432
+ label: "Summary: every section",
51433
+ description: "One block per section with something to report, one line per item.",
51434
+ example: "Batterie scariche: Sensore porta — 8 %",
51435
+ group: "system",
51436
+ families: ["rule"],
51437
+ fields: [...RULE_TEXT],
51438
+ kinds: ["system"],
51439
+ systemEventKinds: [...SUMMARISABLE],
51440
+ systemEventDeliveries: ["summary"]
51441
+ },
51442
+ {
51443
+ name: "summaryCount",
51444
+ label: "Summary: items reported",
51445
+ example: "3",
51446
+ group: "system",
51447
+ families: ["rule"],
51448
+ fields: [...RULE_TEXT],
51449
+ kinds: ["system"],
51450
+ systemEventKinds: [...SUMMARISABLE],
51451
+ systemEventDeliveries: ["summary"]
51452
+ },
51453
+ ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
51454
+ name: v.name,
51455
+ label: v.label,
51456
+ example: v.example,
51457
+ group: "system",
51458
+ families: ["rule"],
51459
+ fields: [...RULE_TEXT],
51460
+ kinds: ["system"],
51461
+ systemEventKinds: [...v.kinds],
51462
+ systemEventDeliveries: ["summary"]
51463
+ }))
51464
+ ];
51257
51465
  var NC_TEMPLATE_VARS = [
51258
51466
  {
51259
51467
  name: "camera",
@@ -51504,7 +51712,8 @@ var NC_TEMPLATE_VARS = [
51504
51712
  group: "system",
51505
51713
  families: ["rule"],
51506
51714
  fields: [...RULE_TEXT],
51507
- kinds: ["system"]
51715
+ kinds: ["system"],
51716
+ systemEventDeliveries: ["instant"]
51508
51717
  },
51509
51718
  {
51510
51719
  name: "deviceName",
@@ -51947,7 +52156,8 @@ var NC_TEMPLATE_VARS = [
51947
52156
  group: "digest",
51948
52157
  families: ["summary"],
51949
52158
  fields: [...RULE_TEXT]
51950
- }
52159
+ },
52160
+ ...STATE_SUMMARY_VARS
51951
52161
  ];
51952
52162
  /**
51953
52163
  * System-event kinds the SERVED catalog holds back (D642): the kinds the hub
@@ -51976,11 +52186,32 @@ var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
51976
52186
  * rule an installed viewer can author, which is exactly true.
51977
52187
  */
51978
52188
  function servedTemplateVars() {
51979
- return NC_TEMPLATE_VARS.map((d) => d.systemEventKinds === void 0 ? d : {
52189
+ return NC_TEMPLATE_VARS.filter((d) => !isHeldBackTemplateVar(d)).map((d) => d.systemEventKinds === void 0 ? d : {
51980
52190
  ...d,
51981
52191
  systemEventKinds: d.systemEventKinds.filter((kind) => !NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS.includes(kind))
51982
52192
  });
51983
52193
  }
52194
+ /**
52195
+ * Is this descriptor held back from the SERVED catalog as a whole (D647)?
52196
+ * A summary-only variable is: an installed viewer does not read the delivery
52197
+ * axis, and would offer it on every instant system rule. The admin, built
52198
+ * from the workspace, restores it from its own `NC_TEMPLATE_VARS`.
52199
+ */
52200
+ function isHeldBackTemplateVar(d) {
52201
+ return d.systemEventDeliveries !== void 0 && !d.systemEventDeliveries.includes("instant");
52202
+ }
52203
+ /**
52204
+ * Does a descriptor have a value on a system rule of this DELIVERY (D647)?
52205
+ * Read only for a system rule kind. An absent axis is `instant` for a
52206
+ * descriptor that names `systemEventKinds` (a per-event fact: a summary row
52207
+ * has no one device, node or package) and BOTH for one that does not
52208
+ * (`{{rule}}`, `{{time}}`).
52209
+ */
52210
+ function templateVarDeliveryApplies(d, ctx) {
52211
+ if (!isSystemRuleKind(ctx.kind)) return true;
52212
+ const mode = ctx.systemEventDelivery ?? "instant";
52213
+ return (d.systemEventDeliveries ?? (d.systemEventKinds !== void 0 ? ["instant"] : [mode])).includes(mode);
52214
+ }
51984
52215
  /** The caption fields see a fixed, smaller vocabulary (`timelapsePreviewText`,
51985
52216
  * `summaryCaptionText`). Declared here rather than as `fields` on every
51986
52217
  * descriptor so the whole rule stays in one line of code. */
@@ -52045,12 +52276,82 @@ function templateVarsFor(ctx) {
52045
52276
  const allowed = CAPTION_VARS[ctx.field];
52046
52277
  return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && allowed.includes(d.name) && d.dynamic === void 0);
52047
52278
  }
52048
- return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && onAxis(d.fields, ctx.field) && templateVarKindApplies(d.kinds, ctx.kind) && onAxis(d.deliveries, ctx.delivery) && templateVarSystemEventApplies(d, ctx));
52279
+ return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && onAxis(d.fields, ctx.field) && templateVarKindApplies(d.kinds, ctx.kind) && onAxis(d.deliveries, ctx.delivery) && templateVarSystemEventApplies(d, ctx) && templateVarDeliveryApplies(d, ctx));
52049
52280
  }
52050
52281
  /** True when `name` is supplied in `ctx`, dynamic families (`count_<class>`) included. */
52051
52282
  function isKnownTemplateVar(name, ctx) {
52052
52283
  return templateVarsFor(ctx).some((d) => d.dynamic !== void 0 ? name.startsWith(d.dynamic.prefix) && name.length > d.dynamic.prefix.length : d.name === name);
52053
52284
  }
52285
+ /** The families a rule may summarise. The alarm is not one of them. */
52286
+ var NC_SYSTEM_EVENT_SUMMARY_FAMILIES = ["device-events", "system"];
52287
+ /** The summary's schedule, or `null` for any rule that is not a summary system rule. */
52288
+ function systemEventSummaryScheduleOf(rule) {
52289
+ if (!isSystemDelivery(rule.delivery)) return null;
52290
+ const delivery = rule.systemEventDelivery;
52291
+ return delivery?.mode === "summary" ? delivery.schedule : null;
52292
+ }
52293
+ /** Absent and `instant` are the same answer: a notification per event. */
52294
+ function isSystemEventSummaryRule(rule) {
52295
+ return systemEventSummaryScheduleOf(rule) !== null;
52296
+ }
52297
+ function actionsAreSet(actions) {
52298
+ return (actions?.onTrigger?.length ?? 0) > 0 || (actions?.buttons?.length ?? 0) > 0;
52299
+ }
52300
+ /**
52301
+ * Why the hub refuses this rule's delivery. Empty = accepted. An instant rule
52302
+ * (absent or `{ mode: 'instant' }`) is never refused here.
52303
+ *
52304
+ * Beside a summary, two fields are refused because they would do nothing:
52305
+ * the rule's own `schedule` (the summary's schedule is the send time; a second
52306
+ * active-hours gate would silently empty it) and `actions` (a summary is not a
52307
+ * match, so nothing would ever run them).
52308
+ */
52309
+ function systemEventDeliveryProblems(rule) {
52310
+ if (rule.systemEventDelivery?.mode !== "summary") return [];
52311
+ if (!isSystemDelivery(rule.delivery)) return [{
52312
+ code: "not-a-system-rule",
52313
+ delivery: rule.delivery
52314
+ }];
52315
+ const problems = [];
52316
+ const verdict = systemEventFamilyOf(rule.conditions.systemEvent?.kinds ?? []);
52317
+ const family = verdict.kind === "family" ? verdict.family : "mixed";
52318
+ if (family === "mixed" || !NC_SYSTEM_EVENT_SUMMARY_FAMILIES.includes(family)) problems.push({
52319
+ code: "family-not-summarisable",
52320
+ family
52321
+ });
52322
+ const windows = rule.systemEventDelivery.schedule.windows;
52323
+ if (windows.length === 0) problems.push({ code: "empty-schedule" });
52324
+ windows.forEach((window, index) => {
52325
+ if (window.days.length === 0 || window.startMinute === window.endMinute) problems.push({
52326
+ code: "degenerate-window",
52327
+ index
52328
+ });
52329
+ });
52330
+ if (rule.schedule !== void 0) problems.push({ code: "schedule-beside-summary" });
52331
+ if (actionsAreSet(rule.actions)) problems.push({ code: "actions-beside-summary" });
52332
+ return problems;
52333
+ }
52334
+ function describeSystemEventDeliveryProblem(problem) {
52335
+ switch (problem.code) {
52336
+ case "not-a-system-rule": return `a '${problem.delivery}' rule has no summary delivery — only device-events and system rules do`;
52337
+ case "family-not-summarisable": return problem.family === "alarm" ? "an alarm rule cannot be a scheduled summary — the alarm stays instant" : `a ${problem.family} rule cannot be a scheduled summary — only device-events and system rules`;
52338
+ case "degenerate-window": return `send time ${String(problem.index + 1)} has no day, or starts and ends on the same minute — it never closes, so nothing would be sent`;
52339
+ case "empty-schedule": return "a summary rule needs at least one send time — its schedule has no window";
52340
+ case "schedule-beside-summary": return "a summary rule is sent on its summary schedule — remove the active-hours schedule";
52341
+ case "actions-beside-summary": return "a summary rule never matches an event, so its actions would never run — remove them";
52342
+ }
52343
+ }
52344
+ var SUMMARY_CONTENT_VAR = /\{\{\s*summary(?:Text|Devices|Streams|Battery|Consumables|Tanks|Detection|Nodes|Addons|Updates)\s*\}\}/;
52345
+ /**
52346
+ * Does a custom BODY carry the summary's content (`{{summaryText}}` or a
52347
+ * section variable)? A body that does not would send a summary with nothing
52348
+ * in it; the hub then appends `{{summaryText}}` and the admin warns (review
52349
+ * M3). An absent or blank body uses the hub's own wording, which does.
52350
+ */
52351
+ function templateCarriesSummary(body) {
52352
+ if (body === void 0 || body.trim().length === 0) return true;
52353
+ return SUMMARY_CONTENT_VAR.test(body);
52354
+ }
52054
52355
  /**
52055
52356
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
52056
52357
  *
@@ -53271,6 +53572,12 @@ Object.defineProperty(exports, "NC_SNOOZE_MAX_MINUTES", {
53271
53572
  return NC_SNOOZE_MAX_MINUTES;
53272
53573
  }
53273
53574
  });
53575
+ Object.defineProperty(exports, "NC_SYSTEM_EVENT_KIND_GROUP_SPECS", {
53576
+ enumerable: true,
53577
+ get: function() {
53578
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS;
53579
+ }
53580
+ });
53274
53581
  Object.defineProperty(exports, "NC_TANK_SYSTEM_EVENT_KINDS", {
53275
53582
  enumerable: true,
53276
53583
  get: function() {
@@ -53595,6 +53902,12 @@ Object.defineProperty(exports, "deriveRecordingMode", {
53595
53902
  return deriveRecordingMode;
53596
53903
  }
53597
53904
  });
53905
+ Object.defineProperty(exports, "describeSystemEventDeliveryProblem", {
53906
+ enumerable: true,
53907
+ get: function() {
53908
+ return describeSystemEventDeliveryProblem;
53909
+ }
53910
+ });
53598
53911
  Object.defineProperty(exports, "describeSystemEventFamilyProblem", {
53599
53912
  enumerable: true,
53600
53913
  get: function() {
@@ -53709,6 +54022,12 @@ Object.defineProperty(exports, "isSystemDelivery", {
53709
54022
  return isSystemDelivery;
53710
54023
  }
53711
54024
  });
54025
+ Object.defineProperty(exports, "isSystemEventSummaryRule", {
54026
+ enumerable: true,
54027
+ get: function() {
54028
+ return isSystemEventSummaryRule;
54029
+ }
54030
+ });
53712
54031
  Object.defineProperty(exports, "kebabToCamel", {
53713
54032
  enumerable: true,
53714
54033
  get: function() {
@@ -53865,6 +54184,12 @@ Object.defineProperty(exports, "subKindsOf", {
53865
54184
  return subKindsOf;
53866
54185
  }
53867
54186
  });
54187
+ Object.defineProperty(exports, "systemEventDeliveryProblems", {
54188
+ enumerable: true,
54189
+ get: function() {
54190
+ return systemEventDeliveryProblems;
54191
+ }
54192
+ });
53868
54193
  Object.defineProperty(exports, "systemEventFamilyProblems", {
53869
54194
  enumerable: true,
53870
54195
  get: function() {
@@ -53877,6 +54202,18 @@ Object.defineProperty(exports, "systemEventFilterApplies", {
53877
54202
  return systemEventFilterApplies;
53878
54203
  }
53879
54204
  });
54205
+ Object.defineProperty(exports, "systemEventSummaryScheduleOf", {
54206
+ enumerable: true,
54207
+ get: function() {
54208
+ return systemEventSummaryScheduleOf;
54209
+ }
54210
+ });
54211
+ Object.defineProperty(exports, "templateCarriesSummary", {
54212
+ enumerable: true,
54213
+ get: function() {
54214
+ return templateCarriesSummary;
54215
+ }
54216
+ });
53880
54217
  Object.defineProperty(exports, "templateVarsFor", {
53881
54218
  enumerable: true,
53882
54219
  get: function() {