@camstack/addon-post-analysis 1.2.283 → 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.
Files changed (15) hide show
  1. package/THIRD_PARTY_MODELS.md +233 -0
  2. package/dist/{dist-zp1v7cCp.mjs → dist-D2tXUMfE.mjs} +1035 -716
  3. package/dist/{dist-CT5xOY86.js → dist-ITESpHou.js} +1070 -715
  4. package/dist/embedding-encoder/index.js +1 -1
  5. package/dist/embedding-encoder/index.mjs +1 -1
  6. package/dist/pipeline-analytics/_stub.js +2 -2
  7. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-9UxXz_Yw.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-7oasHfhD.mjs} +2 -2
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CjeM5Bph.mjs +26 -0
  9. package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DWPDYuJS.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BidXbeas.mjs} +1 -1
  10. package/dist/pipeline-analytics/{hostInit-27I-ZEiW.mjs → hostInit-B6m75Rnz.mjs} +2 -2
  11. package/dist/pipeline-analytics/index.js +10141 -5360
  12. package/dist/pipeline-analytics/index.mjs +10132 -5351
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/package.json +3 -2
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CZkZhroP.mjs +0 -26
@@ -5360,7 +5360,7 @@ var ZodIssueCode = {
5360
5360
  var ZodFirstPartyTypeKind;
5361
5361
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5362
5362
  //#endregion
5363
- //#region ../types/dist/sleep-DU7mu4wb.mjs
5363
+ //#region ../types/dist/sleep-BaRu0bS5.mjs
5364
5364
  /**
5365
5365
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5366
5366
  * window to float samples (D455).
@@ -10024,7 +10024,7 @@ var ModelFormatEntrySchema = object({
10024
10024
  /** Multi-file payload (directory members or sibling files). */
10025
10025
  files: array(string()).readonly().optional(),
10026
10026
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
10027
- runtimes: array(_enum(["node", "python"])).readonly().optional()
10027
+ runtimes: array(_enum(["python"])).readonly().optional()
10028
10028
  });
10029
10029
  /**
10030
10030
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -10105,10 +10105,17 @@ var ModelCatalogEntrySchema = object({
10105
10105
  inputChannels: number().int().positive().optional(),
10106
10106
  labels: array(LabelDefinitionSchema).readonly(),
10107
10107
  inputLayout: _enum(["nchw", "nhwc"]).optional(),
10108
+ /**
10109
+ * `'scrfd'` applies InsightFace's SCRFD input contract, `(x - 127.5) / 128`
10110
+ * (upstream `insightface/model_zoo/scrfd.py`), instead of the historical
10111
+ * plain `/255`. Measured against COCO GT: 188 → 196 faces found on the
10112
+ * `scrfd-2.5g` catalog entry (2026-09-26 model-replacement spike, §2.4-2).
10113
+ */
10108
10114
  inputNormalization: _enum([
10109
10115
  "zero-one",
10110
10116
  "imagenet",
10111
- "none"
10117
+ "none",
10118
+ "scrfd"
10112
10119
  ]).optional(),
10113
10120
  /**
10114
10121
  * The model already applies softmax IN-GRAPH — its raw output is a
@@ -10210,7 +10217,8 @@ var ModelConvertMetadataSchema = object({
10210
10217
  inputNormalization: _enum([
10211
10218
  "zero-one",
10212
10219
  "imagenet",
10213
- "none"
10220
+ "none",
10221
+ "scrfd"
10214
10222
  ]).optional(),
10215
10223
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
10216
10224
  outputFormat: _enum([
@@ -18087,6 +18095,25 @@ var NcScheduleSchema = object({
18087
18095
  /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
18088
18096
  invert: boolean().optional()
18089
18097
  });
18098
+ /**
18099
+ * How a system-event rule of the `device-events` or `system` family speaks
18100
+ * (D647): at once, per event (`instant`, the only behaviour before D647), or
18101
+ * as a STATE SUMMARY at the end of each window of `schedule`.
18102
+ */
18103
+ var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
18104
+ /**
18105
+ * `summary`: the rule sends NOTHING per event. When a window of `schedule`
18106
+ * CLOSES, it sends one notification with the current state of its kinds
18107
+ * (low batteries, consumables to replace, devices offline, updates
18108
+ * available, ...), after its own filters. Nothing to report sends nothing.
18109
+ * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
18110
+ * midnight crossing); the send time is each window's END. Refused on the
18111
+ * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
18112
+ */
18113
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
18114
+ mode: literal("summary"),
18115
+ schedule: NcScheduleSchema
18116
+ })]);
18090
18117
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
18091
18118
  var NcPlateMatcherSchema = object({
18092
18119
  /**
@@ -18822,6 +18849,16 @@ var NcRuleInputSchema = object({
18822
18849
  delivery: NcDeliverySchema,
18823
18850
  conditions: NcConditionsSchema.default({}),
18824
18851
  schedule: NcScheduleSchema.optional(),
18852
+ /**
18853
+ * A `device-events` / `system` rule's delivery (D647): absent or `instant`
18854
+ * is a notification per event; `summary` is one state summary per closed
18855
+ * window of its schedule. `.optional()` and never defaulted: absent must
18856
+ * keep meaning what every rule authored before D647 meant, with no
18857
+ * migration. Top-level rather than inside `conditions.systemEvent`, so a
18858
+ * client that does not know it (an older viewer re-sending `conditions`
18859
+ * whole) leaves it standing through the store's patch merge.
18860
+ */
18861
+ systemEventDelivery: NcSystemEventDeliverySchema.optional(),
18825
18862
  /** May be empty when `targetUsers` addresses at least one user — the
18826
18863
  * "at least one addressee" invariant is enforced by the provider, because
18827
18864
  * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
@@ -19050,6 +19087,28 @@ var NcRuleSchema = NcRuleInputSchema.extend({
19050
19087
  */
19051
19088
  disabledTargetIds: array(string()).default([])
19052
19089
  });
19090
+ /** One section of a previewed state summary. `unavailable` = its state could not be read. */
19091
+ var NcStateSummaryPreviewSectionSchema = object({
19092
+ id: string(),
19093
+ status: _enum(["read", "unavailable"]),
19094
+ items: number().int().nonnegative(),
19095
+ reason: string().optional()
19096
+ });
19097
+ /**
19098
+ * The summary a rule would send now. `empty` = nothing to report and every
19099
+ * section read: such a summary is NOT sent. `dropped` counts the open states
19100
+ * the rule's own filters (or a camera mute) left out, by reason.
19101
+ * `excludedGroups` names the kind groups selected that have no state to report
19102
+ * (backups & exports).
19103
+ */
19104
+ var NcStateSummaryPreviewSchema = object({
19105
+ empty: boolean(),
19106
+ count: number().int().nonnegative(),
19107
+ text: string(),
19108
+ sections: array(NcStateSummaryPreviewSectionSchema),
19109
+ dropped: record(string(), number().int().nonnegative()),
19110
+ excludedGroups: array(string())
19111
+ });
19053
19112
  var NcTestResultSchema = object({
19054
19113
  recordId: string(),
19055
19114
  recordKind: _enum([
@@ -19141,6 +19200,13 @@ var NcTemplateVarDescriptorSchema = object({
19141
19200
  kinds: array(NcTemplateVarKindSchema).optional(),
19142
19201
  deliveries: array(NcDeliverySchema).optional(),
19143
19202
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
19203
+ /**
19204
+ * Which system-event DELIVERY has a value for it (D647). Absent = an
19205
+ * instant row only when the descriptor names `systemEventKinds` (a
19206
+ * per-event fact: a summary row has no single device), and both otherwise.
19207
+ * `['summary']` marks the state-summary vocabulary (`{{summaryText}}`, ...).
19208
+ */
19209
+ systemEventDeliveries: array(NcSystemEventDeliveryModeSchema).optional(),
19144
19210
  dynamic: object({
19145
19211
  prefix: literal("count_"),
19146
19212
  from: literal("classes")
@@ -19153,7 +19219,9 @@ var NcTemplatePreviewInputSchema = object({
19153
19219
  field: NcTemplateFieldSchema,
19154
19220
  kind: NcRuleKindSchema.optional(),
19155
19221
  delivery: NcDeliverySchema.optional(),
19156
- systemEventKinds: array(NcSystemEventKindSchema).optional()
19222
+ systemEventKinds: array(NcSystemEventKindSchema).optional(),
19223
+ /** The rule's system-event delivery (D647); absent = instant. */
19224
+ systemEventDelivery: NcSystemEventDeliveryModeSchema.optional()
19157
19225
  }),
19158
19226
  template: object({
19159
19227
  title: string().max(500).optional(),
@@ -20287,6 +20355,17 @@ var notificationRulesCapability = {
20287
20355
  auth: "admin"
20288
20356
  }),
20289
20357
  /**
20358
+ * The STATE SUMMARY a system rule would send now (D647) — for a saved rule
20359
+ * or an unsaved draft. Pure: sends nothing, stamps nothing. A MUTATION
20360
+ * only for transport (a whole rule does not belong in a GET URL). Admin,
20361
+ * like `testRule`: it names devices and nodes across the installation.
20362
+ */
20363
+ previewStateSummary: method(object({ rule: NcRuleInputSchema }), NcStateSummaryPreviewSchema, {
20364
+ kind: "mutation",
20365
+ auth: "admin",
20366
+ access: "view"
20367
+ }),
20368
+ /**
20290
20369
  * The machine-readable condition surface a rule editor renders from, plus
20291
20370
  * the picker VOCABULARY those conditions draw on.
20292
20371
  *
@@ -20731,646 +20810,6 @@ var OauthIntegrationDescriptorSchema = object({
20731
20810
  });
20732
20811
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
20733
20812
  /**
20734
- * Reference to the frame's retained NATIVE surface + the parent crop's placement
20735
- * within the frame, so the executor can re-cut a leaf child ROI at native
20736
- * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
20737
- */
20738
- var NativeCropRefSchema = object({
20739
- /** Handle keying the retained native surface (node-pinned to its owner). */
20740
- handle: FrameHandleSchema,
20741
- /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
20742
- cropFrameSpace: object({
20743
- x: number(),
20744
- y: number(),
20745
- w: number(),
20746
- h: number()
20747
- })
20748
- });
20749
- object({
20750
- crop: object({
20751
- left: number(),
20752
- top: number(),
20753
- width: number().positive(),
20754
- height: number().positive()
20755
- }).optional(),
20756
- content: object({
20757
- width: number().int().positive(),
20758
- height: number().int().positive()
20759
- }),
20760
- fit: _enum(["stretch", "contain"]),
20761
- format: _enum([
20762
- "rgb",
20763
- "gray",
20764
- "jpeg"
20765
- ])
20766
- });
20767
- /**
20768
- * Process-local frame identity. It is serializable so it can ride an in-process
20769
- * capability call, but `registryId` deliberately prevents resolution in any
20770
- * other process or execution group.
20771
- */
20772
- var FrameRefSchema = object({
20773
- registryId: string().min(1),
20774
- id: string().min(1),
20775
- width: number().int().positive(),
20776
- height: number().int().positive(),
20777
- format: _enum(["rgb", "gray"]),
20778
- timestamp: number(),
20779
- capturedAt: number().optional()
20780
- });
20781
- var ModelFormatSchema$1 = _enum([
20782
- "onnx",
20783
- "coreml",
20784
- "openvino",
20785
- "tflite",
20786
- "pt",
20787
- "gguf"
20788
- ]);
20789
- var PipelineSlotSchema = _enum([
20790
- "detector",
20791
- "cropper",
20792
- "classifier",
20793
- "refiner",
20794
- "audio-classifier"
20795
- ]);
20796
- var PipelineEngineChoiceSchema = object({
20797
- runtime: _enum(["node", "python"]),
20798
- backend: string(),
20799
- format: ModelFormatSchema$1,
20800
- device: string().optional()
20801
- });
20802
- var AvailableEngineSchema = object({
20803
- engine: PipelineEngineChoiceSchema,
20804
- devices: array(object({
20805
- id: string(),
20806
- label: string(),
20807
- description: string().optional()
20808
- })).readonly(),
20809
- defaultDevice: string()
20810
- });
20811
- var PipelineDefaultStepSchema = lazy(() => object({
20812
- addonId: string(),
20813
- addonName: string(),
20814
- slot: PipelineSlotSchema,
20815
- inputClasses: array(string()).readonly(),
20816
- outputClasses: array(string()).readonly(),
20817
- enabled: boolean(),
20818
- modelId: string(),
20819
- children: array(PipelineDefaultStepSchema).readonly(),
20820
- group: string().optional(),
20821
- settings: record(string(), unknown()).optional()
20822
- }));
20823
- var PipelineTemplateStepSchema = lazy(() => object({
20824
- addonId: string(),
20825
- enabled: boolean(),
20826
- modelId: string(),
20827
- children: array(PipelineTemplateStepSchema).readonly(),
20828
- settings: record(string(), unknown()).optional()
20829
- }));
20830
- var PipelineTemplateSchema$1 = object({
20831
- id: string(),
20832
- name: string(),
20833
- createdAt: string(),
20834
- updatedAt: string(),
20835
- engine: PipelineEngineChoiceSchema,
20836
- steps: array(PipelineTemplateStepSchema).readonly()
20837
- });
20838
- var PipelineModelOptionSchema = object({
20839
- id: string(),
20840
- name: string(),
20841
- formats: record(string(), object({
20842
- downloaded: boolean(),
20843
- sizeMB: number()
20844
- })),
20845
- group: ModelVariantGroupSchema.optional(),
20846
- legacy: boolean().optional(),
20847
- provider: ModelProviderIdSchema.optional()
20848
- });
20849
- var ConfigFieldBridge = custom();
20850
- var PipelineAddonSchemaSchema = object({
20851
- id: string(),
20852
- name: string(),
20853
- slot: PipelineSlotSchema,
20854
- inputClasses: array(string()).readonly(),
20855
- outputClasses: array(string()).readonly(),
20856
- childSlots: array(PipelineSlotSchema).readonly(),
20857
- models: array(PipelineModelOptionSchema).readonly(),
20858
- defaultModelId: string(),
20859
- defaultModelIdByFormat: record(string(), string()).optional(),
20860
- enabledByDefault: boolean().optional(),
20861
- backfillIntoExistingOverrides: boolean().optional(),
20862
- defaultConfidence: number(),
20863
- group: string().optional(),
20864
- configSchema: array(ConfigFieldBridge).readonly().optional()
20865
- });
20866
- var PipelineSlotSchemaSchema = object({
20867
- id: PipelineSlotSchema,
20868
- label: string(),
20869
- priority: number(),
20870
- parentSlot: PipelineSlotSchema.nullable(),
20871
- addons: array(PipelineAddonSchemaSchema).readonly()
20872
- });
20873
- var PipelineSchemaSchema = object({
20874
- availableEngines: array(AvailableEngineSchema).readonly(),
20875
- selectedEngine: PipelineEngineChoiceSchema,
20876
- slots: array(PipelineSlotSchemaSchema).readonly()
20877
- });
20878
- var EngineProvisioningSchema = object({
20879
- runtimeId: _enum([
20880
- "onnx",
20881
- "openvino",
20882
- "coreml",
20883
- "edgetpu"
20884
- ]).nullable(),
20885
- device: string().nullable(),
20886
- state: _enum([
20887
- "idle",
20888
- "installing",
20889
- "verifying",
20890
- "ready",
20891
- "failed"
20892
- ]),
20893
- progress: number().optional(),
20894
- error: string().optional(),
20895
- nextRetryAt: number().optional(),
20896
- /**
20897
- * Gate A (config-correctness gate at engine change): human-readable
20898
- * config issues surfaced EAGERLY when the node's engine changes — model
20899
- * substitutions ("chose X, running Y") and zero-build steps ("no model
20900
- * has a <format> build"). Additive/optional: informational only, never
20901
- * enforced here — `assertEngineReady` (readiness) still gates inference.
20902
- * Absent/empty when the node-default tree resolves cleanly.
20903
- */
20904
- configIssues: array(string()).optional()
20905
- });
20906
- var PipelineStepInputSchema = lazy(() => object({
20907
- addonId: string(),
20908
- modelId: string().optional(),
20909
- enabled: boolean().default(true),
20910
- children: array(PipelineStepInputSchema).optional(),
20911
- settings: record(string(), unknown()).optional(),
20912
- jumpDeviceKey: string().optional()
20913
- }));
20914
- var ModelSubstitutionSchema = object({
20915
- addonId: string(),
20916
- chosen: string(),
20917
- running: string(),
20918
- format: string()
20919
- });
20920
- var PipelineValidationIssueSchema = object({
20921
- addonId: string(),
20922
- kind: _enum(["unknown-addon", "no-format-build"]),
20923
- detail: string()
20924
- });
20925
- var PipelineValidationResultSchema = object({
20926
- ok: boolean(),
20927
- issues: array(PipelineValidationIssueSchema).readonly(),
20928
- substitutions: array(ModelSubstitutionSchema).readonly(),
20929
- /** The node's `currentEngine.format` this validation ran against. */
20930
- format: string()
20931
- });
20932
- var ReferenceImageEntrySchema = object({
20933
- filename: string(),
20934
- stepIds: array(string()).readonly().optional()
20935
- });
20936
- var ReferenceImageBodySchema = object({
20937
- base64: string(),
20938
- filename: string()
20939
- });
20940
- var ReferenceAudioEntrySchema = object({
20941
- filename: string(),
20942
- sizeKb: number()
20943
- });
20944
- var ReferenceAudioBodySchema = object({ base64: string() });
20945
- var AudioBackendSchema = object({
20946
- id: string(),
20947
- name: string(),
20948
- description: string(),
20949
- available: boolean(),
20950
- /**
20951
- * Raw classifier labels this backend can emit (e.g. YAMNet's
20952
- * 521-class set or Apple SoundAnalysis's 303-class set). Used by
20953
- * the benchmark UI to populate the `enabledMicroClasses` filter
20954
- * specific to the selected backend without a separate fetch.
20955
- */
20956
- rawLabels: array(string()).readonly().optional()
20957
- });
20958
- var AudioCapabilitiesSchema = object({
20959
- activeBackend: string(),
20960
- availableBackends: array(AudioBackendSchema).readonly(),
20961
- sampleRate: number(),
20962
- chunkDurationMs: number()
20963
- });
20964
- var DownloadModelResultSchema = object({
20965
- filePath: string(),
20966
- sizeMB: number(),
20967
- durationMs: number()
20968
- });
20969
- /**
20970
- * Wrapper carrying a single test run's result. Replaces the legacy
20971
- * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
20972
- * canonical `AudioResult` from the Phase 6 output rework: one
20973
- * `AudioDetection` per class above `minScore`, top-N candidates in
20974
- * `debug.alternateLabels['audio-classifier']`, per-source timings in
20975
- * `debug.stepTimings`. The outer `success`/`error` fields stay so the
20976
- * benchmark UI can still report a clean failure when the classifier
20977
- * cap isn't available.
20978
- */
20979
- var AudioTestResultSchema = object({
20980
- success: boolean(),
20981
- error: string().optional(),
20982
- frame: custom().optional()
20983
- });
20984
- var PipelineConfigBridge = custom();
20985
- var ConfigUISchemaBridge = custom();
20986
- var ConfigUISchemaNullableBridge = custom();
20987
- var InferenceCapabilitiesBridge = custom();
20988
- var ModelAvailabilityListBridge = custom();
20989
- var PipelineRunResultBridge = custom();
20990
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
20991
- modelId: string(),
20992
- settings: record(string(), unknown()).readonly()
20993
- }))), method(object({ steps: record(string(), object({
20994
- modelId: string(),
20995
- settings: record(string(), unknown()).readonly()
20996
- })) }), object({ success: literal(true) }), {
20997
- kind: "mutation",
20998
- auth: "admin"
20999
- }), method(object({ nodeId: string() }), object({
21000
- success: literal(true),
21001
- clearedDevices: number()
21002
- }), {
21003
- kind: "mutation",
21004
- auth: "admin"
21005
- }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
21006
- /** `<backend>:<device>`, e.g. `openvino:gpu`. */
21007
- deviceKey: string(),
21008
- /**
21009
- * `failed` — the per-device restart budget is exhausted; no pool
21010
- * will be spawned until an operator re-arms it or the runner
21011
- * respawns. `backoff` — under budget, waiting out the backoff (or
21012
- * a cached pool observed dead and not yet condemned).
21013
- */
21014
- state: _enum(["failed", "backoff"]),
21015
- /** Epoch ms of the death that produced this state. */
21016
- since: number(),
21017
- /** Pool deaths inside the current window. */
21018
- deaths: number(),
21019
- /** The last death's message. */
21020
- lastError: string()
21021
- })).readonly() })), method(object({
21022
- nodeId: string(),
21023
- deviceKey: string()
21024
- }), object({ rearmed: boolean() }), {
21025
- kind: "mutation",
21026
- auth: "admin"
21027
- }), 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({
21028
- name: string(),
21029
- steps: array(PipelineTemplateStepSchema).readonly(),
21030
- engine: PipelineEngineChoiceSchema
21031
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
21032
- id: string(),
21033
- name: string().optional(),
21034
- steps: array(PipelineTemplateStepSchema).readonly().optional()
21035
- }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
21036
- addonId: string(),
21037
- modelId: string(),
21038
- format: ModelFormatSchema$1
21039
- }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
21040
- addonId: string(),
21041
- modelId: string(),
21042
- format: ModelFormatSchema$1
21043
- }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
21044
- engine: PipelineEngineChoiceSchema.optional(),
21045
- steps: array(PipelineStepInputSchema).min(1),
21046
- frame: FrameInputSchema.optional(),
21047
- /**
21048
- * Process-local lazy frame. Valid only when caller and provider resolve
21049
- * in the same execution-group process; split/cross-node callers use
21050
- * `frame`/`image` inline compatibility instead.
21051
- */
21052
- frameRef: FrameRefSchema.optional(),
21053
- /**
21054
- * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
21055
- * the decoded pixels live in. One more member of the one-of
21056
- * frame/frameHandle/image/imageBase64/referenceImage group.
21057
- */
21058
- frameHandle: FrameHandleSchema.optional(),
21059
- imageBase64: string().optional(),
21060
- /**
21061
- * Binary JPEG bytes — preferred over `imageBase64` on internal
21062
- * hops (hub → forked worker via Moleculer MsgPack) because it
21063
- * skips the 33% base64 overhead + the per-call base64 decode on
21064
- * the detection-pipeline worker. Callers can pass either; exactly
21065
- * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
21066
- */
21067
- image: _instanceof(Uint8Array).optional(),
21068
- referenceImage: string().optional(),
21069
- deviceId: number().optional(),
21070
- sessionId: string().optional(),
21071
- /**
21072
- * Execution plane. 'full' (default) runs the whole tree — benchmark,
21073
- * reference-image, and detail-subtree calls. 'frame' is the live
21074
- * per-frame dispatch: ONLY root-plane steps run; crop children
21075
- * (inputClasses ≠ null) are skipped and served per-track via
21076
- * pipelineRunner.runDetailSubtree (two-plane design).
21077
- */
21078
- plane: _enum(["full", "frame"]).optional(),
21079
- /**
21080
- * Inference-device selector (Phase 2 multi-device). Format
21081
- * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
21082
- * Omitted ⇒ the runner's default device (current single-engine
21083
- * behaviour). Selects WHICH device pool of the node runs the call.
21084
- */
21085
- deviceKey: string().optional(),
21086
- /**
21087
- * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
21088
- * when the parent crop was resolved from the frame's retained NATIVE
21089
- * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
21090
- * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
21091
- * resolution from that surface — the SAME quality path faces already
21092
- * had — instead of the downscaled parent tile. `handle` keys the native
21093
- * surface (node-pinned to its owner); `cropFrameSpace` is the parent
21094
- * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
21095
- * the executor's crop-normalized child ROI back into frame-normalized
21096
- * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
21097
- * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
21098
- * (today's behaviour on the fallback path).
21099
- */
21100
- nativeCropRef: NativeCropRefSchema.optional()
21101
- }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
21102
- engine: PipelineEngineChoiceSchema.optional(),
21103
- steps: array(PipelineStepInputSchema).min(1),
21104
- frames: array(FrameInputSchema).min(1).max(255),
21105
- deviceId: number().optional(),
21106
- sessionId: string().optional(),
21107
- /**
21108
- * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
21109
- * the batch to the Python pool's bench preprocess cache
21110
- * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
21111
- * preprocessed ONCE and every later inference is a pure-inference cache
21112
- * hit — the sustained-throughput run measures inference, not
21113
- * decode+preprocess+infer. Omitted/0 for live frames (all different →
21114
- * full preprocess every call, correct). Fresh per sustained run;
21115
- * released via `uncacheFrame`.
21116
- */
21117
- frameId: number().int().nonnegative().optional(),
21118
- /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
21119
- deviceKey: string().optional()
21120
- }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
21121
- data: _instanceof(Uint8Array),
21122
- width: number().int().positive(),
21123
- height: number().int().positive(),
21124
- format: _enum([
21125
- "rgb",
21126
- "bgr",
21127
- "gray"
21128
- ])
21129
- }), object({
21130
- frameId: number(),
21131
- width: number(),
21132
- height: number()
21133
- }), { kind: "mutation" }), method(object({
21134
- stepId: string(),
21135
- frameId: number().int()
21136
- }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(_void(), object({
21137
- batchMode: string(),
21138
- windowMs: number(),
21139
- maxBatchSize: number(),
21140
- concurrency: number()
21141
- })), method(_void(), array(object({
21142
- engineKey: string(),
21143
- engine: PipelineEngineChoiceSchema,
21144
- modelsLoaded: array(string()).readonly(),
21145
- inUseByCameras: array(number()).readonly(),
21146
- /**
21147
- * Origin of this resident factory.
21148
- * - `runtime` — main camera-serving engine (no idle TTL).
21149
- * - `warm-override` — benchmark/test override held in the warm
21150
- * cache; auto-disposed after the idle TTL.
21151
- * - `device-pool` — a concurrent per-device pool (Phase 2
21152
- * multi-device, keyed by `deviceKey`) resolved
21153
- * via `resolveDeviceFactory`. Runs alongside the
21154
- * `runtime` engine on a DIFFERENT accelerator
21155
- * (NPU / iGPU / Coral) — this is how the
21156
- * Engines tab shows all pools running at once.
21157
- */
21158
- kind: _enum([
21159
- "runtime",
21160
- "warm-override",
21161
- "device-pool"
21162
- ]),
21163
- /** Native pid of the underlying Python pool (null when no pool). */
21164
- poolPid: number().nullable(),
21165
- /** ms since this factory was last used (null when not warm-tracked). */
21166
- idleMs: number().nullable(),
21167
- /** Idle TTL after which `warm-override` factories self-evict (null when not applicable). */
21168
- idleTtlMs: number().nullable()
21169
- })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
21170
- kind: "mutation",
21171
- auth: "admin"
21172
- }), method(object({
21173
- engine: PipelineEngineChoiceSchema,
21174
- force: boolean().optional()
21175
- }), object({
21176
- success: boolean(),
21177
- reason: string().optional()
21178
- }), {
21179
- kind: "mutation",
21180
- auth: "admin"
21181
- }), 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({
21182
- addonId: string(),
21183
- modelId: string(),
21184
- filename: string().optional(),
21185
- settings: record(string(), unknown()).optional()
21186
- }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
21187
- /**
21188
- * Per-stage gating mode applied to the zones a rule references.
21189
- *
21190
- * - `include`: the rule contributes to a **whitelist** for its stage.
21191
- * When at least one `include` rule fires for a stage, only entities
21192
- * inside one of those zones pass that stage.
21193
- * - `exclude`: the rule contributes to a **blacklist** for its stage.
21194
- * Entities inside one of those zones are dropped at that stage.
21195
- *
21196
- * `monitor`-style observation (count without filtering) is not a rule
21197
- * mode — zones without any matching rule are observed naturally by
21198
- * `zone-analytics` (live snapshot + history), so an "I just want to
21199
- * count, not filter" use case needs no rule at all.
21200
- */
21201
- var ZoneRuleModeEnum = _enum(["include", "exclude"]);
21202
- /**
21203
- * Per-consumer rule that references existing zones (geometry) and
21204
- * defines how a specific pipeline stage should treat them. Each
21205
- * consumer addon owns its own `ZoneRule[]` array in its per-device
21206
- * settings:
21207
- *
21208
- * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
21209
- * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
21210
- * - future: notification rules, audio gating, etc.
21211
- *
21212
- * One rule applies to N zones (`zoneIds[]`) so the operator can
21213
- * express "ignore motion in ALL of {garden, street}" with a single
21214
- * rule. `classFilter` narrows the rule to specific object classes —
21215
- * "drop person detections in the street, but keep cars" is one
21216
- * `exclude` rule with `classFilter: ['person']`.
21217
- *
21218
- * `enabled` is a soft toggle — the operator can keep the rule
21219
- * configured but inert without deleting it.
21220
- */
21221
- var ZoneRuleSchema = object({
21222
- /** Stable rule id — survives edits, used by the UI for diffing. */
21223
- id: string(),
21224
- /** Optional human-readable label rendered in the rule editor. */
21225
- name: string().optional(),
21226
- /** Zones this rule targets. The rule's `mode` applies to ALL
21227
- * listed zones (OR-set: a detection in any one of them counts).
21228
- * At least one zone id required — a rule with no targets is a
21229
- * configuration mistake and the form validator rejects it. */
21230
- zoneIds: array(string()).min(1).readonly(),
21231
- mode: ZoneRuleModeEnum,
21232
- /**
21233
- * Class names this rule applies to. Empty / undefined ⇒ rule
21234
- * applies to every class. Class strings match the `macroClass`
21235
- * field on detections (e.g. `person`, `car`, `dog`).
21236
- */
21237
- classFilter: array(string()).readonly().optional(),
21238
- /**
21239
- * Minimum bbox/mask overlap (0–1) with any of the rule's zones
21240
- * required to consider an entity "in the zone". Defaults to the
21241
- * consumer's stage default when omitted. Kept for back-compat with
21242
- * existing per-rule overrides; new operators pick the value via
21243
- * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
21244
- * set, the lower-level engine reads it as a 0–1 fraction.
21245
- */
21246
- overlapThreshold: number().min(0).max(1).optional(),
21247
- /**
21248
- * Operator-friendly version of `overlapThreshold` — the percentage
21249
- * of the detection's bbox that must lie inside the zone for the
21250
- * rule to match. Documented default is 85%; the engine substitutes
21251
- * that when the field is omitted (kept optional so existing rules
21252
- * stored without it stay valid).
21253
- *
21254
- * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
21255
- * rule, the engine prefers `bboxInclusionPct` because it's the
21256
- * field exposed in the UI. Internally both feed the same gate.
21257
- */
21258
- bboxInclusionPct: number().min(0).max(100).optional(),
21259
- /**
21260
- * When `true` and a detection has a segmentation mask, use the
21261
- * mask for overlap instead of the bbox. Detection-stage only;
21262
- * motion rules ignore this field.
21263
- */
21264
- preferMask: boolean().optional(),
21265
- /**
21266
- * Soft-toggle: `false` disables the rule without deleting it.
21267
- * Defaults to `true` so operators creating a rule via the UI
21268
- * see it active immediately.
21269
- */
21270
- enabled: boolean().default(true)
21271
- });
21272
- array(ZoneRuleSchema).readonly();
21273
- /**
21274
- * Zone — pure geometry + identity. NO filtering behaviour.
21275
- *
21276
- * Zones describe **where** in the frame the operator wants to flag
21277
- * something; consumer-owned {@link ZoneRule} arrays describe **how**
21278
- * each pipeline stage uses them. Splitting the two means a single
21279
- * polygon "Driveway" can simultaneously back a motion-exclude rule,
21280
- * a detection-include rule on `['car']`, and an occupancy aggregate
21281
- * — without three duplicated polygons.
21282
- *
21283
- * Owned by the orchestrator addon (provider) and mirrored into the
21284
- * `zones` device-state slice on every mutation. Consumers
21285
- * (motion-wasm, pipeline-executor, analytics, admin UI) read either
21286
- * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
21287
- * mirror with `onChanged`).
21288
- *
21289
- * Coordinates are normalised fractions of the frame (0–1) so zones
21290
- * survive resolution changes and stream profile switches.
21291
- *
21292
- * `kind` discriminates between full polygons (closed regions used
21293
- * for intrusion / occupancy filters) and tripwires (open 2-point
21294
- * line segments used for cross events). Onboard / firmware-reported
21295
- * zones (Reolink, ONVIF) are out of scope for now — see the deferred
21296
- * task list.
21297
- */
21298
- var ZoneKindEnum = _enum(["polygon", "tripwire"]);
21299
- /** Polygon vertex in fraction-of-frame coordinates (0–1). */
21300
- var PolygonPointSchema = object({
21301
- x: number(),
21302
- y: number()
21303
- });
21304
- /** A camera detection zone — pure geometry/identity. */
21305
- var ZoneSchema = object({
21306
- id: string(),
21307
- name: string(),
21308
- kind: ZoneKindEnum.default("polygon"),
21309
- /** Polygon vertices, fraction of frame (0–1). */
21310
- polygon: array(PolygonPointSchema).readonly(),
21311
- /** Visual color for UI rendering. */
21312
- color: string().default("#3b82f6")
21313
- });
21314
- /**
21315
- * Zones capability — per-camera CRUD over polygon detection zones.
21316
- *
21317
- * Provider lives in `addon-pipeline-orchestrator` (hub-only). Persists
21318
- * to per-device settings and mirrors into the `zones` device-state
21319
- * slice on every mutation, so downstream consumers can subscribe via
21320
- * `dev.state.zones.onChanged`.
21321
- *
21322
- * The cap surface only handles geometry + identity; filtering
21323
- * behaviour (per-class, include/exclude, threshold) lives in the
21324
- * consumer addons' rule arrays — see `ZoneRuleSchema` exported from
21325
- * `capabilities/schemas/zone-rule.js`.
21326
- */
21327
- var zonesCapability = {
21328
- name: "zones",
21329
- scope: "device",
21330
- mode: "singleton",
21331
- deviceTypes: [DeviceType.Camera],
21332
- methods: {
21333
- listZones: method(object({ deviceId: number() }), array(ZoneSchema).readonly()),
21334
- addZone: method(object({
21335
- deviceId: number(),
21336
- zone: ZoneSchema
21337
- }), _void(), {
21338
- kind: "mutation",
21339
- auth: "admin"
21340
- }),
21341
- removeZone: method(object({
21342
- deviceId: number(),
21343
- zoneId: string()
21344
- }), _void(), {
21345
- kind: "mutation",
21346
- auth: "admin"
21347
- }),
21348
- updateZone: method(object({
21349
- deviceId: number(),
21350
- zone: ZoneSchema
21351
- }), _void(), {
21352
- kind: "mutation",
21353
- auth: "admin"
21354
- })
21355
- },
21356
- /**
21357
- * Runtime-state slice — the live zone catalogue mirrored by the
21358
- * orchestrator on every CRUD mutation. Consumers read via
21359
- * `device.state.zones.value` / `.watch(...)` without round-tripping
21360
- * the cap, and the codegen DeviceProxy auto-wires the reactive
21361
- * handle. Slice shape is `{ zones: Zone[] }` so future extensions
21362
- * (e.g. zone groupings) can sit alongside the polygon list.
21363
- */
21364
- runtimeState: object({ zones: array(ZoneSchema).readonly() }),
21365
- /**
21366
- * 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.
21367
- *
21368
- * See `RuntimeStateDurability`. Enforced by
21369
- * `scripts/check-runtime-state-durability.ts`.
21370
- */
21371
- durability: "restored"
21372
- };
21373
- /**
21374
20813
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
21375
20814
  * per-frame detections emitted by the pipeline runner into tracked
21376
20815
  * objects, per-kind event collections (motion / object / audio), and
@@ -21941,6 +21380,22 @@ var TrackSchema = object({
21941
21380
  * omit it. Render nothing when absent.
21942
21381
  */
21943
21382
  previewMissReason: string().optional(),
21383
+ /**
21384
+ * The ROOT model that produced this track — the `FrameResult.rootModelId` of
21385
+ * the frame it was BORN on (D646). What a later replay compares against, so
21386
+ * "the original model" is named rather than assumed. A birth fact: never
21387
+ * rewritten by a later frame. **Absent ≠ unknown-model**: a row from before
21388
+ * the column, and a track the camera's own detector produced, carry none.
21389
+ */
21390
+ modelId: string().optional(),
21391
+ /**
21392
+ * Size (px) of the detection frame this track's `positions[].bbox` live in —
21393
+ * the frame it was born on, else the camera's latest analysis frame at
21394
+ * persist time. Divide a position by these to normalise it. Absent when the
21395
+ * size was never known.
21396
+ */
21397
+ frameWidth: number().optional(),
21398
+ frameHeight: number().optional(),
21944
21399
  ...TrackFlagFields,
21945
21400
  ...TrackRetrainFields
21946
21401
  });
@@ -22947,24 +22402,6 @@ var MediaReclaimStatusSchema = object({
22947
22402
  finishedAtMs: number().nullable(),
22948
22403
  error: string().nullable()
22949
22404
  });
22950
- var ReplayFrameInputSchema = object({
22951
- timestamp: number(),
22952
- frame: PipelineRunResultBridge
22953
- });
22954
- var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
22955
- className: string(),
22956
- firstSeenMs: number(),
22957
- lastSeenMs: number(),
22958
- /** Bbox of the track's FIRST matched detection, pixel-space in the clip's
22959
- * frame — a representative box for the diff's `(className, window, IoU)`
22960
- * pairing (`replay-diff.ts`). A replay does not need the full per-frame
22961
- * trajectory production's `Track.positions` keeps. */
22962
- bbox: BoundingBoxSchema,
22963
- /** How many of the input frames this track matched a real detection on
22964
- * (never a coasted/extrapolated frame) — the replay's own signal for "how
22965
- * solid is this track", cheaper than re-deriving it from a trajectory. */
22966
- framesMatched: number().int()
22967
- })).readonly() });
22968
22405
  /**
22969
22406
  * The long-term series a camera produces. A closed union so a typo cannot
22970
22407
  * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
@@ -23691,32 +23128,6 @@ var pipelineAnalyticsCapability = {
23691
23128
  kind: "mutation",
23692
23129
  auth: "admin"
23693
23130
  }),
23694
- /**
23695
- * The FrameProcessor pass of a replay run — see the `Replay` section
23696
- * above this capability's definition for why this is not the
23697
- * `processFrame` method this file's header says pipeline-analytics does
23698
- * not have.
23699
- *
23700
- * Constructs a FRESH `FrameProcessor` for `(deviceId, source)`, feeds it
23701
- * `frames` IN THE ORDER GIVEN (the caller is responsible for time
23702
- * ordering — this method does not sort), and returns the tracks it
23703
- * produced. Zero persistence: no `TrackStore`, no event bus, no media
23704
- * capture. `zones` / `detectionRules` are the run's OWN zone set —
23705
- * typically the camera's real zones plus an ephemeral overlay
23706
- * (`addon-benchmark`'s `replay-plan.ts`), never read from or written to
23707
- * the `zones` capability by this method itself.
23708
- */
23709
- runReplayFrameProcessor: method(object({
23710
- deviceId: number(),
23711
- source: DetectionSourceSchema,
23712
- zones: array(ZoneSchema).readonly().optional(),
23713
- detectionRules: array(ZoneRuleSchema).readonly().optional(),
23714
- zoneMembershipMinOverlap: number().min(0).max(1).optional(),
23715
- frames: array(ReplayFrameInputSchema).min(1)
23716
- }), RunReplayFrameProcessorResultSchema, {
23717
- kind: "mutation",
23718
- auth: "admin"
23719
- }),
23720
23131
  /** Every annotation on a track, oldest first. */
23721
23132
  listRetrainAnnotations: method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
23722
23133
  kind: "query",
@@ -23956,6 +23367,478 @@ var pipelineAnalyticsCapability = {
23956
23367
  }) }
23957
23368
  }
23958
23369
  };
23370
+ /**
23371
+ * Reference to the frame's retained NATIVE surface + the parent crop's placement
23372
+ * within the frame, so the executor can re-cut a leaf child ROI at native
23373
+ * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
23374
+ */
23375
+ var NativeCropRefSchema = object({
23376
+ /** Handle keying the retained native surface (node-pinned to its owner). */
23377
+ handle: FrameHandleSchema,
23378
+ /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
23379
+ cropFrameSpace: object({
23380
+ x: number(),
23381
+ y: number(),
23382
+ w: number(),
23383
+ h: number()
23384
+ })
23385
+ });
23386
+ object({
23387
+ crop: object({
23388
+ left: number(),
23389
+ top: number(),
23390
+ width: number().positive(),
23391
+ height: number().positive()
23392
+ }).optional(),
23393
+ content: object({
23394
+ width: number().int().positive(),
23395
+ height: number().int().positive()
23396
+ }),
23397
+ fit: _enum(["stretch", "contain"]),
23398
+ format: _enum([
23399
+ "rgb",
23400
+ "gray",
23401
+ "jpeg"
23402
+ ])
23403
+ });
23404
+ /**
23405
+ * Process-local frame identity. It is serializable so it can ride an in-process
23406
+ * capability call, but `registryId` deliberately prevents resolution in any
23407
+ * other process or execution group.
23408
+ */
23409
+ var FrameRefSchema = object({
23410
+ registryId: string().min(1),
23411
+ id: string().min(1),
23412
+ width: number().int().positive(),
23413
+ height: number().int().positive(),
23414
+ format: _enum(["rgb", "gray"]),
23415
+ timestamp: number(),
23416
+ capturedAt: number().optional()
23417
+ });
23418
+ var ModelFormatSchema$1 = _enum([
23419
+ "onnx",
23420
+ "coreml",
23421
+ "openvino",
23422
+ "tflite",
23423
+ "pt",
23424
+ "gguf"
23425
+ ]);
23426
+ var PipelineSlotSchema = _enum([
23427
+ "detector",
23428
+ "cropper",
23429
+ "classifier",
23430
+ "refiner",
23431
+ "audio-classifier"
23432
+ ]);
23433
+ var PipelineEngineChoiceSchema = object({
23434
+ runtime: literal("python"),
23435
+ backend: string(),
23436
+ format: ModelFormatSchema$1,
23437
+ device: string().optional()
23438
+ });
23439
+ /**
23440
+ * One pool's resolved provisioning (`getEffectiveTuning`). `numWorkers` is the
23441
+ * knob that lifts a saturated pool (`inference-devices.md`); `concurrency` is
23442
+ * per-worker in-flight requests. `deviceKey` echoes the pool asked about —
23443
+ * `null` for the node-default pool.
23444
+ */
23445
+ var EffectiveTuningSchema = object({
23446
+ deviceKey: string().nullable(),
23447
+ runtime: string(),
23448
+ batchMode: string(),
23449
+ windowMs: number(),
23450
+ maxBatchSize: number(),
23451
+ concurrency: number(),
23452
+ numWorkers: number()
23453
+ });
23454
+ var AvailableEngineSchema = object({
23455
+ engine: PipelineEngineChoiceSchema,
23456
+ devices: array(object({
23457
+ id: string(),
23458
+ label: string(),
23459
+ description: string().optional()
23460
+ })).readonly(),
23461
+ defaultDevice: string()
23462
+ });
23463
+ var PipelineDefaultStepSchema = lazy(() => object({
23464
+ addonId: string(),
23465
+ addonName: string(),
23466
+ slot: PipelineSlotSchema,
23467
+ inputClasses: array(string()).readonly(),
23468
+ outputClasses: array(string()).readonly(),
23469
+ enabled: boolean(),
23470
+ modelId: string(),
23471
+ children: array(PipelineDefaultStepSchema).readonly(),
23472
+ group: string().optional(),
23473
+ settings: record(string(), unknown()).optional()
23474
+ }));
23475
+ var PipelineTemplateStepSchema = lazy(() => object({
23476
+ addonId: string(),
23477
+ enabled: boolean(),
23478
+ modelId: string(),
23479
+ children: array(PipelineTemplateStepSchema).readonly(),
23480
+ settings: record(string(), unknown()).optional()
23481
+ }));
23482
+ var PipelineTemplateSchema$1 = object({
23483
+ id: string(),
23484
+ name: string(),
23485
+ createdAt: string(),
23486
+ updatedAt: string(),
23487
+ engine: PipelineEngineChoiceSchema,
23488
+ steps: array(PipelineTemplateStepSchema).readonly()
23489
+ });
23490
+ var PipelineModelOptionSchema = object({
23491
+ id: string(),
23492
+ name: string(),
23493
+ formats: record(string(), object({
23494
+ downloaded: boolean(),
23495
+ sizeMB: number()
23496
+ })),
23497
+ group: ModelVariantGroupSchema.optional(),
23498
+ legacy: boolean().optional(),
23499
+ provider: ModelProviderIdSchema.optional()
23500
+ });
23501
+ var ConfigFieldBridge = custom();
23502
+ var PipelineAddonSchemaSchema = object({
23503
+ id: string(),
23504
+ name: string(),
23505
+ slot: PipelineSlotSchema,
23506
+ inputClasses: array(string()).readonly(),
23507
+ outputClasses: array(string()).readonly(),
23508
+ childSlots: array(PipelineSlotSchema).readonly(),
23509
+ models: array(PipelineModelOptionSchema).readonly(),
23510
+ defaultModelId: string(),
23511
+ defaultModelIdByFormat: record(string(), string()).optional(),
23512
+ enabledByDefault: boolean().optional(),
23513
+ backfillIntoExistingOverrides: boolean().optional(),
23514
+ defaultConfidence: number(),
23515
+ group: string().optional(),
23516
+ configSchema: array(ConfigFieldBridge).readonly().optional()
23517
+ });
23518
+ var PipelineSlotSchemaSchema = object({
23519
+ id: PipelineSlotSchema,
23520
+ label: string(),
23521
+ priority: number(),
23522
+ parentSlot: PipelineSlotSchema.nullable(),
23523
+ addons: array(PipelineAddonSchemaSchema).readonly()
23524
+ });
23525
+ var PipelineSchemaSchema = object({
23526
+ availableEngines: array(AvailableEngineSchema).readonly(),
23527
+ selectedEngine: PipelineEngineChoiceSchema,
23528
+ slots: array(PipelineSlotSchemaSchema).readonly()
23529
+ });
23530
+ var EngineProvisioningSchema = object({
23531
+ runtimeId: _enum([
23532
+ "onnx",
23533
+ "openvino",
23534
+ "coreml",
23535
+ "edgetpu"
23536
+ ]).nullable(),
23537
+ device: string().nullable(),
23538
+ state: _enum([
23539
+ "idle",
23540
+ "installing",
23541
+ "verifying",
23542
+ "ready",
23543
+ "failed"
23544
+ ]),
23545
+ progress: number().optional(),
23546
+ error: string().optional(),
23547
+ nextRetryAt: number().optional(),
23548
+ /**
23549
+ * Gate A (config-correctness gate at engine change): human-readable
23550
+ * config issues surfaced EAGERLY when the node's engine changes — model
23551
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
23552
+ * has a <format> build"). Additive/optional: informational only, never
23553
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
23554
+ * Absent/empty when the node-default tree resolves cleanly.
23555
+ */
23556
+ configIssues: array(string()).optional()
23557
+ });
23558
+ var PipelineStepInputSchema = lazy(() => object({
23559
+ addonId: string(),
23560
+ modelId: string().optional(),
23561
+ enabled: boolean().default(true),
23562
+ children: array(PipelineStepInputSchema).optional(),
23563
+ settings: record(string(), unknown()).optional(),
23564
+ jumpDeviceKey: string().optional()
23565
+ }));
23566
+ var ModelSubstitutionSchema = object({
23567
+ addonId: string(),
23568
+ chosen: string(),
23569
+ running: string(),
23570
+ format: string()
23571
+ });
23572
+ var PipelineValidationIssueSchema = object({
23573
+ addonId: string(),
23574
+ kind: _enum([
23575
+ "unknown-addon",
23576
+ "no-format-build",
23577
+ "unknown-device"
23578
+ ]),
23579
+ detail: string()
23580
+ });
23581
+ var PipelineValidationResultSchema = object({
23582
+ ok: boolean(),
23583
+ issues: array(PipelineValidationIssueSchema).readonly(),
23584
+ substitutions: array(ModelSubstitutionSchema).readonly(),
23585
+ /** The node's `currentEngine.format` this validation ran against. */
23586
+ format: string()
23587
+ });
23588
+ var ReferenceImageEntrySchema = object({
23589
+ filename: string(),
23590
+ stepIds: array(string()).readonly().optional()
23591
+ });
23592
+ var ReferenceImageBodySchema = object({
23593
+ base64: string(),
23594
+ filename: string()
23595
+ });
23596
+ var ReferenceAudioEntrySchema = object({
23597
+ filename: string(),
23598
+ sizeKb: number()
23599
+ });
23600
+ var ReferenceAudioBodySchema = object({ base64: string() });
23601
+ var AudioBackendSchema = object({
23602
+ id: string(),
23603
+ name: string(),
23604
+ description: string(),
23605
+ available: boolean(),
23606
+ /**
23607
+ * Raw classifier labels this backend can emit (e.g. YAMNet's
23608
+ * 521-class set or Apple SoundAnalysis's 303-class set). Used by
23609
+ * the benchmark UI to populate the `enabledMicroClasses` filter
23610
+ * specific to the selected backend without a separate fetch.
23611
+ */
23612
+ rawLabels: array(string()).readonly().optional()
23613
+ });
23614
+ var AudioCapabilitiesSchema = object({
23615
+ activeBackend: string(),
23616
+ availableBackends: array(AudioBackendSchema).readonly(),
23617
+ sampleRate: number(),
23618
+ chunkDurationMs: number()
23619
+ });
23620
+ var DownloadModelResultSchema = object({
23621
+ filePath: string(),
23622
+ sizeMB: number(),
23623
+ durationMs: number()
23624
+ });
23625
+ /**
23626
+ * Wrapper carrying a single test run's result. Replaces the legacy
23627
+ * ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
23628
+ * canonical `AudioResult` from the Phase 6 output rework: one
23629
+ * `AudioDetection` per class above `minScore`, top-N candidates in
23630
+ * `debug.alternateLabels['audio-classifier']`, per-source timings in
23631
+ * `debug.stepTimings`. The outer `success`/`error` fields stay so the
23632
+ * benchmark UI can still report a clean failure when the classifier
23633
+ * cap isn't available.
23634
+ */
23635
+ var AudioTestResultSchema = object({
23636
+ success: boolean(),
23637
+ error: string().optional(),
23638
+ frame: custom().optional()
23639
+ });
23640
+ var PipelineConfigBridge = custom();
23641
+ var ConfigUISchemaBridge = custom();
23642
+ var ConfigUISchemaNullableBridge = custom();
23643
+ var InferenceCapabilitiesBridge = custom();
23644
+ var ModelAvailabilityListBridge = custom();
23645
+ var PipelineRunResultBridge = custom();
23646
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
23647
+ modelId: string(),
23648
+ settings: record(string(), unknown()).readonly()
23649
+ }))), method(object({ steps: record(string(), object({
23650
+ modelId: string(),
23651
+ settings: record(string(), unknown()).readonly()
23652
+ })) }), object({ success: literal(true) }), {
23653
+ kind: "mutation",
23654
+ auth: "admin"
23655
+ }), method(object({ nodeId: string() }), object({
23656
+ success: literal(true),
23657
+ clearedDevices: number()
23658
+ }), {
23659
+ kind: "mutation",
23660
+ auth: "admin"
23661
+ }), method(object({ nodeId: string() }), object({ unhealthy: array(object({
23662
+ /** `<backend>:<device>`, e.g. `openvino:gpu`. */
23663
+ deviceKey: string(),
23664
+ /**
23665
+ * `failed` — the per-device restart budget is exhausted; no pool
23666
+ * will be spawned until an operator re-arms it or the runner
23667
+ * respawns. `backoff` — under budget, waiting out the backoff (or
23668
+ * a cached pool observed dead and not yet condemned).
23669
+ */
23670
+ state: _enum(["failed", "backoff"]),
23671
+ /** Epoch ms of the death that produced this state. */
23672
+ since: number(),
23673
+ /** Pool deaths inside the current window. */
23674
+ deaths: number(),
23675
+ /** The last death's message. */
23676
+ lastError: string()
23677
+ })).readonly() })), method(object({
23678
+ nodeId: string(),
23679
+ deviceKey: string()
23680
+ }), object({ rearmed: boolean() }), {
23681
+ kind: "mutation",
23682
+ auth: "admin"
23683
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({
23684
+ steps: array(PipelineStepInputSchema),
23685
+ deviceKey: string().optional()
23686
+ }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
23687
+ name: string(),
23688
+ steps: array(PipelineTemplateStepSchema).readonly(),
23689
+ engine: PipelineEngineChoiceSchema
23690
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({
23691
+ id: string(),
23692
+ name: string().optional(),
23693
+ steps: array(PipelineTemplateStepSchema).readonly().optional()
23694
+ }), PipelineTemplateSchema$1, { kind: "mutation" }), method(object({ id: string() }), _void(), { kind: "mutation" }), method(_void(), InferenceCapabilitiesBridge), method(object({ addonId: string() }), ModelAvailabilityListBridge), method(object({
23695
+ addonId: string(),
23696
+ modelId: string(),
23697
+ format: ModelFormatSchema$1
23698
+ }), DownloadModelResultSchema, { kind: "mutation" }), method(object({
23699
+ addonId: string(),
23700
+ modelId: string(),
23701
+ format: ModelFormatSchema$1
23702
+ }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
23703
+ steps: array(PipelineStepInputSchema).min(1),
23704
+ frame: FrameInputSchema.optional(),
23705
+ /**
23706
+ * Process-local lazy frame. Valid only when caller and provider resolve
23707
+ * in the same execution-group process; split/cross-node callers use
23708
+ * `frame`/`image` inline compatibility instead.
23709
+ */
23710
+ frameRef: FrameRefSchema.optional(),
23711
+ /**
23712
+ * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
23713
+ * the decoded pixels live in. One more member of the one-of
23714
+ * frame/frameHandle/image/imageBase64/referenceImage group.
23715
+ */
23716
+ frameHandle: FrameHandleSchema.optional(),
23717
+ imageBase64: string().optional(),
23718
+ /**
23719
+ * Binary JPEG bytes — preferred over `imageBase64` on internal
23720
+ * hops (hub → forked worker via Moleculer MsgPack) because it
23721
+ * skips the 33% base64 overhead + the per-call base64 decode on
23722
+ * the detection-pipeline worker. Callers can pass either; exactly
23723
+ * one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
23724
+ */
23725
+ image: _instanceof(Uint8Array).optional(),
23726
+ referenceImage: string().optional(),
23727
+ deviceId: number().optional(),
23728
+ sessionId: string().optional(),
23729
+ /**
23730
+ * Execution plane. 'full' (default) runs the whole tree — benchmark,
23731
+ * reference-image, and detail-subtree calls. 'frame' is the live
23732
+ * per-frame dispatch: ONLY root-plane steps run; crop children
23733
+ * (inputClasses ≠ null) are skipped and served per-track via
23734
+ * pipelineRunner.runDetailSubtree (two-plane design).
23735
+ */
23736
+ plane: _enum(["full", "frame"]).optional(),
23737
+ /**
23738
+ * Inference-device selector (Phase 2 multi-device). Format
23739
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
23740
+ * Omitted ⇒ the runner's default device (current single-engine
23741
+ * behaviour). Selects WHICH device pool of the node runs the call.
23742
+ */
23743
+ deviceKey: string().optional(),
23744
+ /**
23745
+ * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
23746
+ * when the parent crop was resolved from the frame's retained NATIVE
23747
+ * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
23748
+ * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
23749
+ * resolution from that surface — the SAME quality path faces already
23750
+ * had — instead of the downscaled parent tile. `handle` keys the native
23751
+ * surface (node-pinned to its owner); `cropFrameSpace` is the parent
23752
+ * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
23753
+ * the executor's crop-normalized child ROI back into frame-normalized
23754
+ * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
23755
+ * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
23756
+ * (today's behaviour on the fallback path).
23757
+ */
23758
+ nativeCropRef: NativeCropRefSchema.optional(),
23759
+ /**
23760
+ * Offline REPLAY of stored media for `deviceId` (D56, D646): the
23761
+ * camera's zone gate and overrides apply as live, every executing
23762
+ * step runs EXACTLY the model it names on the `deviceKey` pool it
23763
+ * names or the call refuses (`model-not-servable` /
23764
+ * `device-not-servable`), and nothing reaches the camera's live
23765
+ * trace stream. Absent ⇒ unchanged behaviour.
23766
+ */
23767
+ replay: literal(true).optional()
23768
+ }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
23769
+ steps: array(PipelineStepInputSchema).min(1),
23770
+ frames: array(FrameInputSchema).min(1).max(255),
23771
+ deviceId: number().optional(),
23772
+ sessionId: string().optional(),
23773
+ /**
23774
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
23775
+ * the batch to the Python pool's bench preprocess cache
23776
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
23777
+ * preprocessed ONCE and every later inference is a pure-inference cache
23778
+ * hit — the sustained-throughput run measures inference, not
23779
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
23780
+ * full preprocess every call, correct). Fresh per sustained run;
23781
+ * released via `uncacheFrame`.
23782
+ */
23783
+ frameId: number().int().nonnegative().optional(),
23784
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
23785
+ deviceKey: string().optional()
23786
+ }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
23787
+ data: _instanceof(Uint8Array),
23788
+ width: number().int().positive(),
23789
+ height: number().int().positive(),
23790
+ format: _enum([
23791
+ "rgb",
23792
+ "bgr",
23793
+ "gray"
23794
+ ])
23795
+ }), object({
23796
+ frameId: number(),
23797
+ width: number(),
23798
+ height: number()
23799
+ }), { kind: "mutation" }), method(object({
23800
+ stepId: string(),
23801
+ frameId: number().int()
23802
+ }), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(object({ deviceKey: string().optional() }), EffectiveTuningSchema), method(_void(), array(object({
23803
+ engineKey: string(),
23804
+ engine: PipelineEngineChoiceSchema,
23805
+ modelsLoaded: array(string()).readonly(),
23806
+ inUseByCameras: array(number()).readonly(),
23807
+ /**
23808
+ * Origin of this resident factory.
23809
+ * - `runtime` — main camera-serving engine (no idle TTL).
23810
+ * - `device-pool` — a concurrent per-device pool (Phase 2
23811
+ * multi-device, keyed by `deviceKey`) resolved
23812
+ * via `resolveDeviceFactory`. Runs alongside the
23813
+ * `runtime` engine on a DIFFERENT accelerator
23814
+ * (NPU / iGPU / Coral) — this is how the
23815
+ * Engines tab shows all pools running at once.
23816
+ */
23817
+ kind: _enum(["runtime", "device-pool"]),
23818
+ /** Native pid of the underlying Python pool (null when no pool). */
23819
+ poolPid: number().nullable(),
23820
+ /** ms since this factory was last used (null when not warm-tracked). */
23821
+ idleMs: number().nullable(),
23822
+ /** Idle TTL after which the factory self-evicts (null when not applicable). */
23823
+ idleTtlMs: number().nullable()
23824
+ })).readonly()), method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
23825
+ kind: "mutation",
23826
+ auth: "admin"
23827
+ }), method(object({
23828
+ engine: PipelineEngineChoiceSchema,
23829
+ force: boolean().optional()
23830
+ }), object({
23831
+ success: boolean(),
23832
+ reason: string().optional()
23833
+ }), {
23834
+ kind: "mutation",
23835
+ auth: "admin"
23836
+ }), 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({
23837
+ addonId: string(),
23838
+ modelId: string(),
23839
+ filename: string().optional(),
23840
+ settings: record(string(), unknown()).optional()
23841
+ }), AudioTestResultSchema, { kind: "mutation" }), method(_void(), ConfigUISchemaNullableBridge);
23959
23842
  object({
23960
23843
  activeCameras: number(),
23961
23844
  throttledCameras: number(),
@@ -23981,6 +23864,106 @@ var CameraMetricsSchema = object({
23981
23864
  });
23982
23865
  var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
23983
23866
  /**
23867
+ * Zone — pure geometry + identity. NO filtering behaviour.
23868
+ *
23869
+ * Zones describe **where** in the frame the operator wants to flag
23870
+ * something; consumer-owned {@link ZoneRule} arrays describe **how**
23871
+ * each pipeline stage uses them. Splitting the two means a single
23872
+ * polygon "Driveway" can simultaneously back a motion-exclude rule,
23873
+ * a detection-include rule on `['car']`, and an occupancy aggregate
23874
+ * — without three duplicated polygons.
23875
+ *
23876
+ * Owned by the orchestrator addon (provider) and mirrored into the
23877
+ * `zones` device-state slice on every mutation. Consumers
23878
+ * (motion-wasm, pipeline-executor, analytics, admin UI) read either
23879
+ * via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
23880
+ * mirror with `onChanged`).
23881
+ *
23882
+ * Coordinates are normalised fractions of the frame (0–1) so zones
23883
+ * survive resolution changes and stream profile switches.
23884
+ *
23885
+ * `kind` discriminates between full polygons (closed regions used
23886
+ * for intrusion / occupancy filters) and tripwires (open 2-point
23887
+ * line segments used for cross events). Onboard / firmware-reported
23888
+ * zones (Reolink, ONVIF) are out of scope for now — see the deferred
23889
+ * task list.
23890
+ */
23891
+ var ZoneKindEnum = _enum(["polygon", "tripwire"]);
23892
+ /** Polygon vertex in fraction-of-frame coordinates (0–1). */
23893
+ var PolygonPointSchema = object({
23894
+ x: number(),
23895
+ y: number()
23896
+ });
23897
+ /** A camera detection zone — pure geometry/identity. */
23898
+ var ZoneSchema = object({
23899
+ id: string(),
23900
+ name: string(),
23901
+ kind: ZoneKindEnum.default("polygon"),
23902
+ /** Polygon vertices, fraction of frame (0–1). */
23903
+ polygon: array(PolygonPointSchema).readonly(),
23904
+ /** Visual color for UI rendering. */
23905
+ color: string().default("#3b82f6")
23906
+ });
23907
+ /**
23908
+ * Zones capability — per-camera CRUD over polygon detection zones.
23909
+ *
23910
+ * Provider lives in `addon-pipeline-orchestrator` (hub-only). Persists
23911
+ * to per-device settings and mirrors into the `zones` device-state
23912
+ * slice on every mutation, so downstream consumers can subscribe via
23913
+ * `dev.state.zones.onChanged`.
23914
+ *
23915
+ * The cap surface only handles geometry + identity; filtering
23916
+ * behaviour (per-class, include/exclude, threshold) lives in the
23917
+ * consumer addons' rule arrays — see `ZoneRuleSchema` exported from
23918
+ * `capabilities/schemas/zone-rule.js`.
23919
+ */
23920
+ var zonesCapability = {
23921
+ name: "zones",
23922
+ scope: "device",
23923
+ mode: "singleton",
23924
+ deviceTypes: [DeviceType.Camera],
23925
+ methods: {
23926
+ listZones: method(object({ deviceId: number() }), array(ZoneSchema).readonly()),
23927
+ addZone: method(object({
23928
+ deviceId: number(),
23929
+ zone: ZoneSchema
23930
+ }), _void(), {
23931
+ kind: "mutation",
23932
+ auth: "admin"
23933
+ }),
23934
+ removeZone: method(object({
23935
+ deviceId: number(),
23936
+ zoneId: string()
23937
+ }), _void(), {
23938
+ kind: "mutation",
23939
+ auth: "admin"
23940
+ }),
23941
+ updateZone: method(object({
23942
+ deviceId: number(),
23943
+ zone: ZoneSchema
23944
+ }), _void(), {
23945
+ kind: "mutation",
23946
+ auth: "admin"
23947
+ })
23948
+ },
23949
+ /**
23950
+ * Runtime-state slice — the live zone catalogue mirrored by the
23951
+ * orchestrator on every CRUD mutation. Consumers read via
23952
+ * `device.state.zones.value` / `.watch(...)` without round-tripping
23953
+ * the cap, and the codegen DeviceProxy auto-wires the reactive
23954
+ * handle. Slice shape is `{ zones: Zone[] }` so future extensions
23955
+ * (e.g. zone groupings) can sit alongside the polygon list.
23956
+ */
23957
+ runtimeState: object({ zones: array(ZoneSchema).readonly() }),
23958
+ /**
23959
+ * 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.
23960
+ *
23961
+ * See `RuntimeStateDurability`. Enforced by
23962
+ * `scripts/check-runtime-state-durability.ts`.
23963
+ */
23964
+ durability: "restored"
23965
+ };
23966
+ /**
23984
23967
  * A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
23985
23968
  * decode worker resolves it against the RETAINED native frame's real pixel dims,
23986
23969
  * so the caller supplies only the detection-res bbox divided by the detection
@@ -24167,7 +24150,17 @@ var DetailResultSchema = object({
24167
24150
  /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
24168
24151
  * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
24169
24152
  * consumers MUST prefer this when present (2026-07-22 native-gate fix). */
24170
- nativeFaceShortSidePx: number().optional()
24153
+ nativeFaceShortSidePx: number().optional(),
24154
+ /**
24155
+ * The five facial landmarks of a `face` detail, in the SAME frame space as
24156
+ * `bbox` (D644). Additive and optional: post-analysis stores them beside the
24157
+ * face's source frame so a future alignment can be re-derived exactly; a
24158
+ * runner that predates the field sends none and the face is stored without.
24159
+ */
24160
+ landmarks: array(object({
24161
+ x: number(),
24162
+ y: number()
24163
+ })).optional()
24171
24164
  });
24172
24165
  /**
24173
24166
  * Why an executing node REFUSED a stateless step run (`runStatelessStep`).
@@ -34344,7 +34337,7 @@ var HardwareInfoSchema = object({
34344
34337
  coral: CoralInfoSchema.nullable().optional()
34345
34338
  });
34346
34339
  var PlatformScoreSchema = object({
34347
- runtime: _enum(["node", "python"]),
34340
+ runtime: literal("python"),
34348
34341
  backend: string(),
34349
34342
  format: _enum([
34350
34343
  "onnx",
@@ -34381,7 +34374,7 @@ var ModelRequirementSchema = object({
34381
34374
  });
34382
34375
  var ResolvedInferenceConfigSchema = object({
34383
34376
  modelId: string(),
34384
- runtime: _enum(["node", "python"]),
34377
+ runtime: literal("python"),
34385
34378
  backend: string(),
34386
34379
  format: ModelFormatSchema,
34387
34380
  reason: string()
@@ -36976,6 +36969,92 @@ var sceneMonitorCapability = {
36976
36969
  durability: "session"
36977
36970
  };
36978
36971
  /**
36972
+ * Per-stage gating mode applied to the zones a rule references.
36973
+ *
36974
+ * - `include`: the rule contributes to a **whitelist** for its stage.
36975
+ * When at least one `include` rule fires for a stage, only entities
36976
+ * inside one of those zones pass that stage.
36977
+ * - `exclude`: the rule contributes to a **blacklist** for its stage.
36978
+ * Entities inside one of those zones are dropped at that stage.
36979
+ *
36980
+ * `monitor`-style observation (count without filtering) is not a rule
36981
+ * mode — zones without any matching rule are observed naturally by
36982
+ * `zone-analytics` (live snapshot + history), so an "I just want to
36983
+ * count, not filter" use case needs no rule at all.
36984
+ */
36985
+ var ZoneRuleModeEnum = _enum(["include", "exclude"]);
36986
+ /**
36987
+ * Per-consumer rule that references existing zones (geometry) and
36988
+ * defines how a specific pipeline stage should treat them. Each
36989
+ * consumer addon owns its own `ZoneRule[]` array in its per-device
36990
+ * settings:
36991
+ *
36992
+ * - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
36993
+ * - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
36994
+ * - future: notification rules, audio gating, etc.
36995
+ *
36996
+ * One rule applies to N zones (`zoneIds[]`) so the operator can
36997
+ * express "ignore motion in ALL of {garden, street}" with a single
36998
+ * rule. `classFilter` narrows the rule to specific object classes —
36999
+ * "drop person detections in the street, but keep cars" is one
37000
+ * `exclude` rule with `classFilter: ['person']`.
37001
+ *
37002
+ * `enabled` is a soft toggle — the operator can keep the rule
37003
+ * configured but inert without deleting it.
37004
+ */
37005
+ var ZoneRuleSchema = object({
37006
+ /** Stable rule id — survives edits, used by the UI for diffing. */
37007
+ id: string(),
37008
+ /** Optional human-readable label rendered in the rule editor. */
37009
+ name: string().optional(),
37010
+ /** Zones this rule targets. The rule's `mode` applies to ALL
37011
+ * listed zones (OR-set: a detection in any one of them counts).
37012
+ * At least one zone id required — a rule with no targets is a
37013
+ * configuration mistake and the form validator rejects it. */
37014
+ zoneIds: array(string()).min(1).readonly(),
37015
+ mode: ZoneRuleModeEnum,
37016
+ /**
37017
+ * Class names this rule applies to. Empty / undefined ⇒ rule
37018
+ * applies to every class. Class strings match the `macroClass`
37019
+ * field on detections (e.g. `person`, `car`, `dog`).
37020
+ */
37021
+ classFilter: array(string()).readonly().optional(),
37022
+ /**
37023
+ * Minimum bbox/mask overlap (0–1) with any of the rule's zones
37024
+ * required to consider an entity "in the zone". Defaults to the
37025
+ * consumer's stage default when omitted. Kept for back-compat with
37026
+ * existing per-rule overrides; new operators pick the value via
37027
+ * `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
37028
+ * set, the lower-level engine reads it as a 0–1 fraction.
37029
+ */
37030
+ overlapThreshold: number().min(0).max(1).optional(),
37031
+ /**
37032
+ * Operator-friendly version of `overlapThreshold` — the percentage
37033
+ * of the detection's bbox that must lie inside the zone for the
37034
+ * rule to match. Documented default is 85%; the engine substitutes
37035
+ * that when the field is omitted (kept optional so existing rules
37036
+ * stored without it stay valid).
37037
+ *
37038
+ * When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
37039
+ * rule, the engine prefers `bboxInclusionPct` because it's the
37040
+ * field exposed in the UI. Internally both feed the same gate.
37041
+ */
37042
+ bboxInclusionPct: number().min(0).max(100).optional(),
37043
+ /**
37044
+ * When `true` and a detection has a segmentation mask, use the
37045
+ * mask for overlap instead of the bbox. Detection-stage only;
37046
+ * motion rules ignore this field.
37047
+ */
37048
+ preferMask: boolean().optional(),
37049
+ /**
37050
+ * Soft-toggle: `false` disables the rule without deleting it.
37051
+ * Defaults to `true` so operators creating a rule via the UI
37052
+ * see it active immediately.
37053
+ */
37054
+ enabled: boolean().default(true)
37055
+ });
37056
+ array(ZoneRuleSchema).readonly();
37057
+ /**
36979
37058
  * Script-runner cap. Models HA `script.*` entities on
36980
37059
  * `DeviceType.Script`. A Script is a pre-recorded action sequence
36981
37060
  * that can be invoked imperatively — optionally with a variables
@@ -45147,6 +45226,12 @@ Object.freeze({
45147
45226
  addonId: null,
45148
45227
  access: "view"
45149
45228
  },
45229
+ "notificationRules.previewStateSummary": {
45230
+ capName: "notification-rules",
45231
+ capScope: "system",
45232
+ addonId: null,
45233
+ access: "view"
45234
+ },
45150
45235
  "notificationRules.previewTemplate": {
45151
45236
  capName: "notification-rules",
45152
45237
  capScope: "system",
@@ -45723,12 +45808,6 @@ Object.freeze({
45723
45808
  addonId: null,
45724
45809
  access: "create"
45725
45810
  },
45726
- "pipelineAnalytics.runReplayFrameProcessor": {
45727
- capName: "pipeline-analytics",
45728
- capScope: "device",
45729
- addonId: null,
45730
- access: "create"
45731
- },
45732
45811
  "pipelineAnalytics.saveRetrainAnnotations": {
45733
45812
  capName: "pipeline-analytics",
45734
45813
  capScope: "device",
@@ -49826,11 +49905,6 @@ Object.freeze({
49826
49905
  form: "single",
49827
49906
  optional: false
49828
49907
  }],
49829
- "pipelineAnalytics.runReplayFrameProcessor": [{
49830
- name: "deviceId",
49831
- form: "single",
49832
- optional: false
49833
- }],
49834
49908
  "pipelineAnalytics.saveRetrainAnnotations": [{
49835
49909
  name: "deviceId",
49836
49910
  form: "single",
@@ -50868,7 +50942,8 @@ var NC_SYSTEM_EVENT_FAMILY = {
50868
50942
  "camera-disabled": null,
50869
50943
  "camera-enabled": null
50870
50944
  };
50871
- new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50945
+ /** Read by NAME, for kinds that arrive as plain strings (a newer hub, the viewer's mirror). */
50946
+ var FAMILY_BY_NAME = new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50872
50947
  var NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = [...NC_SYSTEM_EVENT_FILTER_KEYS, "excludeDeviceIds"];
50873
50948
  /**
50874
50949
  * The filters a family may carry. The base filters are the union of
@@ -50979,6 +51054,24 @@ function systemEventKindsOfFamily(family) {
50979
51054
  return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
50980
51055
  }
50981
51056
  /**
51057
+ * One family, or `mixed`. Mixed covers kinds that span families, a legacy kind,
51058
+ * a kind this build does not know (it fails toward read-only and never toward
51059
+ * editable), and an empty selection (nothing to decide on).
51060
+ */
51061
+ function systemEventFamilyOf(kindNames) {
51062
+ const families = /* @__PURE__ */ new Set();
51063
+ for (const name of kindNames) {
51064
+ const family = FAMILY_BY_NAME.get(name);
51065
+ if (family === void 0 || family === null) return { kind: "mixed" };
51066
+ families.add(family);
51067
+ }
51068
+ const [only] = [...families];
51069
+ return families.size === 1 && only !== void 0 ? {
51070
+ kind: "family",
51071
+ family: only
51072
+ } : { kind: "mixed" };
51073
+ }
51074
+ /**
50982
51075
  * Why the hub refuses this condition on a new or updated rule. Empty = accepted.
50983
51076
  * Families are reported in `NC_SYSTEM_EVENT_FAMILIES` order, and filters in
50984
51077
  * `NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS` order, so a refusal reads the same
@@ -51054,18 +51147,38 @@ var WEEKDAY_TO_DAY = {
51054
51147
  Fri: 5,
51055
51148
  Sat: 6
51056
51149
  };
51057
- /** Resolve (weekday, minute-of-day) of `atMs` in the schedule's timezone.
51058
- * An invalid/unknown IANA name falls back to the host timezone. */
51059
- function localDayMinute(atMs, timezone) {
51060
- const d = new Date(atMs);
51061
- if (timezone !== void 0) try {
51062
- const parts = new Intl.DateTimeFormat("en-US", {
51150
+ /**
51151
+ * One formatter per timezone, built once (D647 review I4). Constructing an
51152
+ * `Intl.DateTimeFormat` is the expensive part of this function, and a caller
51153
+ * that asks about many instants — a window scan — used to build one per
51154
+ * instant. `null` = an invalid IANA name (the host timezone is used).
51155
+ */
51156
+ var FORMATTERS = /* @__PURE__ */ new Map();
51157
+ function formatterFor(timezone) {
51158
+ const held = FORMATTERS.get(timezone);
51159
+ if (held !== void 0) return held;
51160
+ let made;
51161
+ try {
51162
+ made = new Intl.DateTimeFormat("en-US", {
51063
51163
  timeZone: timezone,
51064
51164
  weekday: "short",
51065
51165
  hour: "numeric",
51066
51166
  minute: "numeric",
51067
51167
  hourCycle: "h23"
51068
- }).formatToParts(d);
51168
+ });
51169
+ } catch {
51170
+ made = null;
51171
+ }
51172
+ FORMATTERS.set(timezone, made);
51173
+ return made;
51174
+ }
51175
+ /** Resolve (weekday, minute-of-day) of `atMs` in the schedule's timezone.
51176
+ * An invalid/unknown IANA name falls back to the host timezone. */
51177
+ function localDayMinute(atMs, timezone) {
51178
+ const d = new Date(atMs);
51179
+ const formatter = timezone !== void 0 ? formatterFor(timezone) : null;
51180
+ if (formatter !== null) try {
51181
+ const parts = formatter.formatToParts(d);
51069
51182
  let weekday;
51070
51183
  let hour;
51071
51184
  let minute;
@@ -51205,6 +51318,119 @@ var DEVICE_EVENTS = [
51205
51318
  "detection-blind",
51206
51319
  ...ALARM
51207
51320
  ];
51321
+ /** Every kind a summary may speak about — the two summarisable families. */
51322
+ var SUMMARISABLE = [...systemEventKindsOfFamily("device-events"), ...systemEventKindsOfFamily("system")];
51323
+ /** One per summary SECTION (`NC_STATE_SUMMARY_SECTIONS` in the notification
51324
+ * centre): the section's lines, offered when one of its kinds is selected. */
51325
+ var STATE_SUMMARY_SECTION_VARS = [
51326
+ {
51327
+ name: "summaryDevices",
51328
+ label: "Summary: devices offline / switched off",
51329
+ example: "Garage — offline since 07:12",
51330
+ kinds: [
51331
+ "device-offline",
51332
+ "device-online",
51333
+ "device-disabled",
51334
+ "device-enabled"
51335
+ ]
51336
+ },
51337
+ {
51338
+ name: "summaryStreams",
51339
+ label: "Summary: streams offline",
51340
+ example: "Ingresso (high) — since 06:40",
51341
+ kinds: ["stream-offline", "stream-online"]
51342
+ },
51343
+ {
51344
+ name: "summaryBattery",
51345
+ label: "Summary: low batteries",
51346
+ example: "Sensore porta — 8 %",
51347
+ kinds: BATTERY
51348
+ },
51349
+ {
51350
+ name: "summaryConsumables",
51351
+ label: "Summary: consumables",
51352
+ example: "Robot — filter 10 %",
51353
+ kinds: CONSUMABLE
51354
+ },
51355
+ {
51356
+ name: "summaryTanks",
51357
+ label: "Summary: tanks",
51358
+ example: "Robot — clean water low",
51359
+ kinds: TANK
51360
+ },
51361
+ {
51362
+ name: "summaryDetection",
51363
+ label: "Summary: cameras detecting nothing",
51364
+ example: "Ingresso — since 05:10",
51365
+ kinds: ["detection-blind"]
51366
+ },
51367
+ {
51368
+ name: "summaryNodes",
51369
+ label: "Summary: nodes",
51370
+ example: "little-unraid — offline since 03:00",
51371
+ kinds: [
51372
+ "node-offline",
51373
+ "node-online",
51374
+ "node-inference-unavailable"
51375
+ ]
51376
+ },
51377
+ {
51378
+ name: "summaryAddons",
51379
+ label: "Summary: addons",
51380
+ example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
51381
+ kinds: [
51382
+ "addon-crash-loop",
51383
+ "addon-update-available",
51384
+ "addon-updated"
51385
+ ]
51386
+ },
51387
+ {
51388
+ name: "summaryUpdates",
51389
+ label: "Summary: server & container updates",
51390
+ example: "server 1.2.377 → 1.2.378",
51391
+ kinds: [
51392
+ "server-update-available",
51393
+ "server-updated",
51394
+ "wrapper-update-available"
51395
+ ]
51396
+ }
51397
+ ];
51398
+ var STATE_SUMMARY_VARS = [
51399
+ {
51400
+ name: "summaryText",
51401
+ label: "Summary: every section",
51402
+ description: "One block per section with something to report, one line per item.",
51403
+ example: "Batterie scariche: Sensore porta — 8 %",
51404
+ group: "system",
51405
+ families: ["rule"],
51406
+ fields: [...RULE_TEXT],
51407
+ kinds: ["system"],
51408
+ systemEventKinds: [...SUMMARISABLE],
51409
+ systemEventDeliveries: ["summary"]
51410
+ },
51411
+ {
51412
+ name: "summaryCount",
51413
+ label: "Summary: items reported",
51414
+ example: "3",
51415
+ group: "system",
51416
+ families: ["rule"],
51417
+ fields: [...RULE_TEXT],
51418
+ kinds: ["system"],
51419
+ systemEventKinds: [...SUMMARISABLE],
51420
+ systemEventDeliveries: ["summary"]
51421
+ },
51422
+ ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
51423
+ name: v.name,
51424
+ label: v.label,
51425
+ example: v.example,
51426
+ group: "system",
51427
+ families: ["rule"],
51428
+ fields: [...RULE_TEXT],
51429
+ kinds: ["system"],
51430
+ systemEventKinds: [...v.kinds],
51431
+ systemEventDeliveries: ["summary"]
51432
+ }))
51433
+ ];
51208
51434
  var NC_TEMPLATE_VARS = [
51209
51435
  {
51210
51436
  name: "camera",
@@ -51455,7 +51681,8 @@ var NC_TEMPLATE_VARS = [
51455
51681
  group: "system",
51456
51682
  families: ["rule"],
51457
51683
  fields: [...RULE_TEXT],
51458
- kinds: ["system"]
51684
+ kinds: ["system"],
51685
+ systemEventDeliveries: ["instant"]
51459
51686
  },
51460
51687
  {
51461
51688
  name: "deviceName",
@@ -51898,7 +52125,8 @@ var NC_TEMPLATE_VARS = [
51898
52125
  group: "digest",
51899
52126
  families: ["summary"],
51900
52127
  fields: [...RULE_TEXT]
51901
- }
52128
+ },
52129
+ ...STATE_SUMMARY_VARS
51902
52130
  ];
51903
52131
  /**
51904
52132
  * System-event kinds the SERVED catalog holds back (D642): the kinds the hub
@@ -51927,11 +52155,32 @@ var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
51927
52155
  * rule an installed viewer can author, which is exactly true.
51928
52156
  */
51929
52157
  function servedTemplateVars() {
51930
- return NC_TEMPLATE_VARS.map((d) => d.systemEventKinds === void 0 ? d : {
52158
+ return NC_TEMPLATE_VARS.filter((d) => !isHeldBackTemplateVar(d)).map((d) => d.systemEventKinds === void 0 ? d : {
51931
52159
  ...d,
51932
52160
  systemEventKinds: d.systemEventKinds.filter((kind) => !NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS.includes(kind))
51933
52161
  });
51934
52162
  }
52163
+ /**
52164
+ * Is this descriptor held back from the SERVED catalog as a whole (D647)?
52165
+ * A summary-only variable is: an installed viewer does not read the delivery
52166
+ * axis, and would offer it on every instant system rule. The admin, built
52167
+ * from the workspace, restores it from its own `NC_TEMPLATE_VARS`.
52168
+ */
52169
+ function isHeldBackTemplateVar(d) {
52170
+ return d.systemEventDeliveries !== void 0 && !d.systemEventDeliveries.includes("instant");
52171
+ }
52172
+ /**
52173
+ * Does a descriptor have a value on a system rule of this DELIVERY (D647)?
52174
+ * Read only for a system rule kind. An absent axis is `instant` for a
52175
+ * descriptor that names `systemEventKinds` (a per-event fact: a summary row
52176
+ * has no one device, node or package) and BOTH for one that does not
52177
+ * (`{{rule}}`, `{{time}}`).
52178
+ */
52179
+ function templateVarDeliveryApplies(d, ctx) {
52180
+ if (!isSystemRuleKind(ctx.kind)) return true;
52181
+ const mode = ctx.systemEventDelivery ?? "instant";
52182
+ return (d.systemEventDeliveries ?? (d.systemEventKinds !== void 0 ? ["instant"] : [mode])).includes(mode);
52183
+ }
51935
52184
  /** The caption fields see a fixed, smaller vocabulary (`timelapsePreviewText`,
51936
52185
  * `summaryCaptionText`). Declared here rather than as `fields` on every
51937
52186
  * descriptor so the whole rule stays in one line of code. */
@@ -51996,12 +52245,82 @@ function templateVarsFor(ctx) {
51996
52245
  const allowed = CAPTION_VARS[ctx.field];
51997
52246
  return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && allowed.includes(d.name) && d.dynamic === void 0);
51998
52247
  }
51999
- 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));
52248
+ 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));
52000
52249
  }
52001
52250
  /** True when `name` is supplied in `ctx`, dynamic families (`count_<class>`) included. */
52002
52251
  function isKnownTemplateVar(name, ctx) {
52003
52252
  return templateVarsFor(ctx).some((d) => d.dynamic !== void 0 ? name.startsWith(d.dynamic.prefix) && name.length > d.dynamic.prefix.length : d.name === name);
52004
52253
  }
52254
+ /** The families a rule may summarise. The alarm is not one of them. */
52255
+ var NC_SYSTEM_EVENT_SUMMARY_FAMILIES = ["device-events", "system"];
52256
+ /** The summary's schedule, or `null` for any rule that is not a summary system rule. */
52257
+ function systemEventSummaryScheduleOf(rule) {
52258
+ if (!isSystemDelivery(rule.delivery)) return null;
52259
+ const delivery = rule.systemEventDelivery;
52260
+ return delivery?.mode === "summary" ? delivery.schedule : null;
52261
+ }
52262
+ /** Absent and `instant` are the same answer: a notification per event. */
52263
+ function isSystemEventSummaryRule(rule) {
52264
+ return systemEventSummaryScheduleOf(rule) !== null;
52265
+ }
52266
+ function actionsAreSet(actions) {
52267
+ return (actions?.onTrigger?.length ?? 0) > 0 || (actions?.buttons?.length ?? 0) > 0;
52268
+ }
52269
+ /**
52270
+ * Why the hub refuses this rule's delivery. Empty = accepted. An instant rule
52271
+ * (absent or `{ mode: 'instant' }`) is never refused here.
52272
+ *
52273
+ * Beside a summary, two fields are refused because they would do nothing:
52274
+ * the rule's own `schedule` (the summary's schedule is the send time; a second
52275
+ * active-hours gate would silently empty it) and `actions` (a summary is not a
52276
+ * match, so nothing would ever run them).
52277
+ */
52278
+ function systemEventDeliveryProblems(rule) {
52279
+ if (rule.systemEventDelivery?.mode !== "summary") return [];
52280
+ if (!isSystemDelivery(rule.delivery)) return [{
52281
+ code: "not-a-system-rule",
52282
+ delivery: rule.delivery
52283
+ }];
52284
+ const problems = [];
52285
+ const verdict = systemEventFamilyOf(rule.conditions.systemEvent?.kinds ?? []);
52286
+ const family = verdict.kind === "family" ? verdict.family : "mixed";
52287
+ if (family === "mixed" || !NC_SYSTEM_EVENT_SUMMARY_FAMILIES.includes(family)) problems.push({
52288
+ code: "family-not-summarisable",
52289
+ family
52290
+ });
52291
+ const windows = rule.systemEventDelivery.schedule.windows;
52292
+ if (windows.length === 0) problems.push({ code: "empty-schedule" });
52293
+ windows.forEach((window, index) => {
52294
+ if (window.days.length === 0 || window.startMinute === window.endMinute) problems.push({
52295
+ code: "degenerate-window",
52296
+ index
52297
+ });
52298
+ });
52299
+ if (rule.schedule !== void 0) problems.push({ code: "schedule-beside-summary" });
52300
+ if (actionsAreSet(rule.actions)) problems.push({ code: "actions-beside-summary" });
52301
+ return problems;
52302
+ }
52303
+ function describeSystemEventDeliveryProblem(problem) {
52304
+ switch (problem.code) {
52305
+ case "not-a-system-rule": return `a '${problem.delivery}' rule has no summary delivery — only device-events and system rules do`;
52306
+ 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`;
52307
+ 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`;
52308
+ case "empty-schedule": return "a summary rule needs at least one send time — its schedule has no window";
52309
+ case "schedule-beside-summary": return "a summary rule is sent on its summary schedule — remove the active-hours schedule";
52310
+ case "actions-beside-summary": return "a summary rule never matches an event, so its actions would never run — remove them";
52311
+ }
52312
+ }
52313
+ var SUMMARY_CONTENT_VAR = /\{\{\s*summary(?:Text|Devices|Streams|Battery|Consumables|Tanks|Detection|Nodes|Addons|Updates)\s*\}\}/;
52314
+ /**
52315
+ * Does a custom BODY carry the summary's content (`{{summaryText}}` or a
52316
+ * section variable)? A body that does not would send a summary with nothing
52317
+ * in it; the hub then appends `{{summaryText}}` and the admin warns (review
52318
+ * M3). An absent or blank body uses the hub's own wording, which does.
52319
+ */
52320
+ function templateCarriesSummary(body) {
52321
+ if (body === void 0 || body.trim().length === 0) return true;
52322
+ return SUMMARY_CONTENT_VAR.test(body);
52323
+ }
52005
52324
  /**
52006
52325
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
52007
52326
  *
@@ -53006,4 +53325,4 @@ function vectorDimFromBase64(encoded) {
53006
53325
  return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
53007
53326
  }
53008
53327
  //#endregion
53009
- export { NcTemplateVarDescriptorSchema as $, sensorRaisedOf as $t, NC_DEFAULT_SNOOZE_MINUTES as A, unknown as An, failureContributionCapability as At, NcRuleInputSchema as B, mayWriteToLocation as Bt, MAX_BIRTH_DECISION_RECORDS as C, discriminatedUnion as Cn, deriveRecordingMode as Ct, NC_ALARM_SYSTEM_EVENT_KINDS as D, partialRecord as Dn, evaluateSensorEdge as Dt, MediaFileKindEnum as E, object as En, encodeVectorBase64 as Et, NC_TEMPLATE_VARS as F, isMediaOwnerType as Ft, NcScheduleSchema as G, pipelineAnalyticsCapability as Gt, NcRuleSchema as H, occupancyScope as Ht, NcAlarmConfigSchema as I, isScheduleActive as It, NcSnoozeSuppressedSchema as J, readTimelapseGeneratedAt as Jt, NcSnoozeInputSchema as K, plateGalleryCapability as Kt, NcAlarmSettingsPatchSchema as L, isSourceCap as Lt, NC_SNOOZE_MAX_MINUTES as M, isDetectionMacroClass as Mt, NC_TANK_SYSTEM_EVENT_KINDS as N, isEventOwnerType as Nt, NC_CONDITION_CATALOG as O, record as On, evictionPolicyOfLocation as Ot, NC_TAXONOMY as P, isKnownTemplateVar as Pt, NcTemplatePreviewSchema as Q, sceneMonitorCapability as Qt, NcConditionDescriptorSchema as R, isSystemDelivery as Rt, MAX_ARCHIVED_DEBUG_NOTES as S, boolean as Sn, cosineSimilarity as St, MOTION_CLOSE_AFTER_MS as T, number as Tn, embeddingEncoderCapability as Tt, NcRuleTargetSchema as U, parseProcStatus as Ut, NcRulePatchSchema as V, notificationRulesCapability as Vt, NcRuleUpdateInputSchema as W, pickClusterStepModels as Wt, NcTaxonomySchema as X, resolvePoolMemoryPolicy as Xt, NcSystemEventKindSchema as Y, resolveLocationMode as Yt, NcTemplatePreviewInputSchema as Z, ruleBelongsToAlarm as Zt, FIRST_LEVEL_MACRO_CLASSES as _, isDeviceScopedCap as _n, assertTimelapseCadences as _t, BirthDecisionRecordSchema as a, templateVarsFor as an, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as at, LabelAttributionSchema as b, _enum as bn, buildEventKindDescriptor as bt, CoverStateSchema as c, errMsg as cn, SCENE_DIVERGED as ct, DEFAULT_TIMELAPSE_PREVIEW_TEXT as d, CamProfileSchema as dn, TimelapseRuleInputSchema as dt, servedTemplateVars as en, OpsLogEntrySchema as et, DETECTION_MACRO_CLASSES as f, DeviceType as fn, TimelapseRulePatchSchema as ft, EVENT_OWNER_TYPES as g, hydrateSchema as gn, alarmPanelCapability as gt, EVENT_KIND_BY_CAP as h, defineCustomActions as hn, addonWidgetsSourceCapability as ht, BaseDevice as i, systemEventFilterApplies as in, RetrainStatusSchema as it, NC_SENSOR_TRANSITION_KINDS as j, EventCategory as jn, hfModelUrl as jt, NC_CONSUMABLE_SYSTEM_EVENT_KINDS as k, string as kn, faceGalleryCapability as kt, DEFAULT_EVENT_COLOR as l, canonicalHash as ln, SceneMonitorSchema as lt, DeclaredDevices as m, customAction as mn, TrackSourceSchema as mt, AlarmArmModeSchema as n, subKindsOf as nn, RECORDING_EXPORT_MAX_READ_BYTES as nt, CLUSTER_MODEL_SCOPED_STEPS as o, vectorDimFromBase64 as on, SCENE_DEFAULT_ANCHOR_THRESHOLD as ot, DETECTION_PIPELINE_CAP_NAME as p, createEvent as pn, TimelapseRuleSchema as pt, NcSnoozeSchema as q, readDeviceStateFrom as qt, ArchivedDebugNoteSchema as r, systemEventFamilyProblems as rn, RECORDING_SOURCE_CAMSTACK_ADDON as rt, COCO_TO_MACRO as s, zoneAnalyticsCapability as sn, SCENE_DEFAULT_UNCOVERED_POLICY as st, AUDIO_MACRO_LABELS as t, storageOccupancyCapability as tn, PoolMemoryWatchdog as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, BaseAddon as un, TIMELAPSE_DENSE_FLOOR_SEC as ut, FULL_IMAGE_BBOX as v, nodePin as vn, audioMetricsCapability as vt, MAX_BIRTH_LATENCY_PROXY_MS as w, literal as wn, describeSystemEventFamilyProblem as wt, LockStateSchema as x, array as xn, consumableLowPercentOf as xt, FailureCounters as y, sleep as yn, audioModeOf as yt, NcConditionsSchema as z, kebabToCamel as zt };
53328
+ export { NcTemplatePreviewSchema as $, resolvePoolMemoryPolicy as $t, NC_DEFAULT_SNOOZE_MINUTES as A, literal as An, evictionPolicyOfLocation as At, NcConditionsSchema as B, isSystemDelivery as Bt, MAX_BIRTH_DECISION_RECORDS as C, isDeviceScopedCap as Cn, cosineSimilarity as Ct, NC_ALARM_SYSTEM_EVENT_KINDS as D, array as Dn, embeddingEncoderCapability as Dt, MediaFileKindEnum as E, _enum as En, describeSystemEventFamilyProblem as Et, NC_TAXONOMY as F, string as Fn, isEventOwnerType as Ft, NcRuleUpdateInputSchema as G, occupancyScope as Gt, NcRulePatchSchema as H, kebabToCamel as Ht, NC_TEMPLATE_VARS as I, unknown as In, isKnownTemplateVar as It, NcSnoozeSchema as J, pipelineAnalyticsCapability as Jt, NcScheduleSchema as K, parseProcStatus as Kt, NcAlarmConfigSchema as L, EventCategory as Ln, isMediaOwnerType as Lt, NC_SNOOZE_MAX_MINUTES as M, object as Mn, failureContributionCapability as Mt, NC_SYSTEM_EVENT_KIND_GROUP_SPECS as N, partialRecord as Nn, hfModelUrl as Nt, NC_CONDITION_CATALOG as O, boolean as On, encodeVectorBase64 as Ot, NC_TANK_SYSTEM_EVENT_KINDS as P, record as Pn, isDetectionMacroClass as Pt, NcTemplatePreviewInputSchema as Q, resolveLocationMode as Qt, NcAlarmSettingsPatchSchema as R, isScheduleActive as Rt, MAX_ARCHIVED_DEBUG_NOTES as S, hydrateSchema as Sn, consumableLowPercentOf as St, MOTION_CLOSE_AFTER_MS as T, sleep as Tn, describeSystemEventDeliveryProblem as Tt, NcRuleSchema as U, mayWriteToLocation as Ut, NcRuleInputSchema as V, isSystemEventSummaryRule as Vt, NcRuleTargetSchema as W, notificationRulesCapability as Wt, NcSystemEventKindSchema as X, readDeviceStateFrom as Xt, NcSnoozeSuppressedSchema as Y, plateGalleryCapability as Yt, NcTaxonomySchema as Z, readTimelapseGeneratedAt as Zt, FIRST_LEVEL_MACRO_CLASSES as _, CamProfileSchema as _n, alarmPanelCapability as _t, BirthDecisionRecordSchema as a, subKindsOf as an, RetrainStatusSchema as at, LabelAttributionSchema as b, customAction as bn, audioModeOf as bt, CoverStateSchema as c, systemEventFilterApplies as cn, SCENE_DEFAULT_UNCOVERED_POLICY as ct, DEFAULT_TIMELAPSE_PREVIEW_TEXT as d, templateVarsFor as dn, TIMELAPSE_DENSE_FLOOR_SEC as dt, ruleBelongsToAlarm as en, NcTemplateVarDescriptorSchema as et, DETECTION_MACRO_CLASSES as f, vectorDimFromBase64 as fn, TimelapseRuleInputSchema as ft, EVENT_OWNER_TYPES as g, BaseAddon as gn, addonWidgetsSourceCapability as gt, EVENT_KIND_BY_CAP as h, canonicalHash as hn, TrackSourceSchema as ht, BaseDevice as i, storageOccupancyCapability as in, RECORDING_SOURCE_CAMSTACK_ADDON as it, NC_SENSOR_TRANSITION_KINDS as j, number as jn, faceGalleryCapability as jt, NC_CONSUMABLE_SYSTEM_EVENT_KINDS as k, discriminatedUnion as kn, evaluateSensorEdge as kt, DEFAULT_EVENT_COLOR as l, systemEventSummaryScheduleOf as ln, SCENE_DIVERGED as lt, DeclaredDevices as m, errMsg as mn, TimelapseRuleSchema as mt, AlarmArmModeSchema as n, sensorRaisedOf as nn, PoolMemoryWatchdog as nt, CLUSTER_MODEL_SCOPED_STEPS as o, systemEventDeliveryProblems as on, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as ot, DETECTION_PIPELINE_CAP_NAME as p, zoneAnalyticsCapability as pn, TimelapseRulePatchSchema as pt, NcSnoozeInputSchema as q, pickClusterStepModels as qt, ArchivedDebugNoteSchema as r, servedTemplateVars as rn, RECORDING_EXPORT_MAX_READ_BYTES as rt, COCO_TO_MACRO as s, systemEventFamilyProblems as sn, SCENE_DEFAULT_ANCHOR_THRESHOLD as st, AUDIO_MACRO_LABELS as t, sceneMonitorCapability as tn, OpsLogEntrySchema as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, templateCarriesSummary as un, SceneMonitorSchema as ut, FULL_IMAGE_BBOX as v, DeviceType as vn, assertTimelapseCadences as vt, MAX_BIRTH_LATENCY_PROXY_MS as w, nodePin as wn, deriveRecordingMode as wt, LockStateSchema as x, defineCustomActions as xn, buildEventKindDescriptor as xt, FailureCounters as y, createEvent as yn, audioMetricsCapability as yt, NcConditionDescriptorSchema as z, isSourceCap as zt };