@camstack/addon-post-analysis 1.2.64 → 1.2.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-BQ-M7jn9.js → dist-DKsqvmSl.js} +255 -17
- package/dist/{dist-CSTtfEOC.mjs → dist-DPzTTt9R.mjs} +232 -18
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-lSMqbbY-.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BdNH8u64.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-EHTrRb5u.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DO3ZGk1V.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-D5rpu5pY.mjs → hostInit-D8a3hjwW.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +1205 -14
- package/dist/pipeline-analytics/index.mjs +1205 -14
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -7421,8 +7421,31 @@ var AdoptionJobSchema = object({
|
|
|
7421
7421
|
error: string().nullable()
|
|
7422
7422
|
});
|
|
7423
7423
|
/**
|
|
7424
|
-
* Per-camera FUNCTION SWITCHES
|
|
7425
|
-
*
|
|
7424
|
+
* Per-camera FUNCTION SWITCHES.
|
|
7425
|
+
*
|
|
7426
|
+
* ## The aggregate group is being withdrawn — the BADGE is not (D113)
|
|
7427
|
+
*
|
|
7428
|
+
* This file shipped as "the one coherent on/off surface over the pipeline
|
|
7429
|
+
* functions an operator thinks in terms of". The operator's verdict on
|
|
7430
|
+
* 2026-08-12 was that the coherent surface bought complexity and no clarity:
|
|
7431
|
+
* every function already had a settings page of its own, and a second place to
|
|
7432
|
+
* turn it off is a second place to look. Each switch is going back to its own
|
|
7433
|
+
* component's original options — detection to the detection-pipeline wrapper
|
|
7434
|
+
* binding, audio analysis to its own, recording to `RecordingConfig.enabled`
|
|
7435
|
+
* (which was always first-class; the switch was a veneer over
|
|
7436
|
+
* `recording.setDeviceConfig`), notifications to a notification-center
|
|
7437
|
+
* per-device setting, the two camera planes to their own components.
|
|
7438
|
+
*
|
|
7439
|
+
* What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
|
|
7440
|
+
* thing that lets a status surface say DISABLED instead of BROKEN, recomposed
|
|
7441
|
+
* straight from the authorities with no group in the middle. That rule was
|
|
7442
|
+
* never about a control panel.
|
|
7443
|
+
*
|
|
7444
|
+
* Everything else here — {@link CAMERA_SWITCH_CATALOG}, {@link CameraSwitch},
|
|
7445
|
+
* {@link deriveCameraSwitches}, the `pipelineOrchestrator.getCameraSwitches` /
|
|
7446
|
+
* `setCameraSwitch` pair — is a COMPATIBILITY surface for as long as deployed
|
|
7447
|
+
* viewers (v1.0.305) and the admin UI still call it. It is deleted when they
|
|
7448
|
+
* stop; nothing new may be built on it.
|
|
7426
7449
|
*
|
|
7427
7450
|
* ## This file adds no state
|
|
7428
7451
|
*
|
|
@@ -7784,6 +7807,22 @@ var RecordingConfigSchema = object({
|
|
|
7784
7807
|
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7785
7808
|
}).strict();
|
|
7786
7809
|
/**
|
|
7810
|
+
* Derive the {@link RecordingStorageModeSchema} summary from the authoritative
|
|
7811
|
+
* bands: `continuous` when any band records continuously, `events` when a band
|
|
7812
|
+
* exists at all, else `off`. Pure.
|
|
7813
|
+
*
|
|
7814
|
+
* THE one definition: the recorder stamps `config.mode` with it on save and
|
|
7815
|
+
* reports it as the cap's `activeMode`, and the settings UI reads it back to
|
|
7816
|
+
* decide which authoring tab a config opens in. Two copies would drift, and the
|
|
7817
|
+
* symptom (a status dot disagreeing with what is being recorded) is exactly the
|
|
7818
|
+
* bug this field was added to fix.
|
|
7819
|
+
*/
|
|
7820
|
+
function deriveRecordingMode(config) {
|
|
7821
|
+
if (!config.enabled || config.bands.length === 0) return "off";
|
|
7822
|
+
if (config.bands.some((band) => band.mode === "continuous")) return "continuous";
|
|
7823
|
+
return "events";
|
|
7824
|
+
}
|
|
7825
|
+
/**
|
|
7787
7826
|
* Entity-relocation job state (storage entity-routing spec, Phase 4).
|
|
7788
7827
|
*
|
|
7789
7828
|
* One shape shared by the recorder and pipeline-analytics internal movers.
|
|
@@ -7962,6 +8001,21 @@ var StorageLocationSchema = object({
|
|
|
7962
8001
|
nodeId: string().optional(),
|
|
7963
8002
|
isDefault: boolean().default(false),
|
|
7964
8003
|
isSystem: boolean().default(false),
|
|
8004
|
+
/**
|
|
8005
|
+
* Operator opt-in: whether consumers that BALANCE across several locations
|
|
8006
|
+
* of a type may write here. Recordings reads it today; event media and
|
|
8007
|
+
* backups are the next consumers, which is why the flag lives on the
|
|
8008
|
+
* location rather than in any one addon's store — nothing has to be
|
|
8009
|
+
* extended to add the next consumer.
|
|
8010
|
+
*
|
|
8011
|
+
* OPTIONAL, and ABSENT MEANS ACTIVE. Every location persisted before the
|
|
8012
|
+
* flag existed reads back with no flag and keeps working exactly as before;
|
|
8013
|
+
* that is the whole compat story, and it is why no migration ships with it.
|
|
8014
|
+
* A newly CREATED sibling is stamped `false` by the orchestrator (creating a
|
|
8015
|
+
* disk must not silently start writing to it); the default of a type is
|
|
8016
|
+
* always stamped `true`.
|
|
8017
|
+
*/
|
|
8018
|
+
enabled: boolean().optional(),
|
|
7965
8019
|
/** COMPUTED at read time by the orchestrator (statfs of the backing volume
|
|
7966
8020
|
* for node-local locations it can reach) — never persisted, absent when the
|
|
7967
8021
|
* volume is remote/unreachable. The single capacity truth every UI reads. */
|
|
@@ -12501,7 +12555,8 @@ var embeddingEncoderCapability = {
|
|
|
12501
12555
|
};
|
|
12502
12556
|
/**
|
|
12503
12557
|
* filesystem-browse — per-node capability for browsing the node's local
|
|
12504
|
-
* filesystem
|
|
12558
|
+
* filesystem. Reads are unconfined (whole filesystem, from `/` down); WRITES
|
|
12559
|
+
* are sandboxed to operator-configured allowed roots (D115). Used by the
|
|
12505
12560
|
* admin "Add filesystem location" flow to pick a node + path. `mode:'per-node'`
|
|
12506
12561
|
* (one provider per node); the hub calls it with `{nodeId}` so the codegen
|
|
12507
12562
|
* routes to that exact node (default `nodeIdMode:'routing'`).
|
|
@@ -18913,9 +18968,16 @@ var CameraStatusSchema = object({
|
|
|
18913
18968
|
audio: CameraAudioStatusSchema.nullable(),
|
|
18914
18969
|
recording: CameraRecordingStatusSchema.nullable(),
|
|
18915
18970
|
/**
|
|
18916
|
-
* Per-camera
|
|
18971
|
+
* Per-camera functions an OPERATOR has turned off
|
|
18917
18972
|
* ([D61](../../../../docs/decisions/adr-0067.md)).
|
|
18918
18973
|
*
|
|
18974
|
+
* Composed from the AUTHORITIES themselves — the wrapper bindings,
|
|
18975
|
+
* `RecordingConfig.enabled`, the notification mute, the broker's audio
|
|
18976
|
+
* policy, the camera's own microphone — via `composeSwitchedOff`, not from
|
|
18977
|
+
* the deprecated `getCameraSwitches` group ([D113](../../../../docs/decisions/adr-0113.md)).
|
|
18978
|
+
* The badge outlives the control panel: the panel was a convenience, this is
|
|
18979
|
+
* the difference between a camera being off and a camera being dead.
|
|
18980
|
+
*
|
|
18919
18981
|
* This is the difference between DISABLED and BROKEN. A camera whose
|
|
18920
18982
|
* `detection` block reports zero fps and whose `switchedOff` contains
|
|
18921
18983
|
* `'object-detection'` was switched off by a person; the same camera with an
|
|
@@ -25953,7 +26015,7 @@ method(object({
|
|
|
25953
26015
|
auth: "admin"
|
|
25954
26016
|
});
|
|
25955
26017
|
/**
|
|
25956
|
-
* `
|
|
26018
|
+
* `recording-export` cap — render a footage time range into a single downloadable
|
|
25957
26019
|
* MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
|
|
25958
26020
|
* bounded lifetime with a durable history, auto-expiry, and optional
|
|
25959
26021
|
* delete-after-download.
|
|
@@ -25968,10 +26030,42 @@ method(object({
|
|
|
25968
26030
|
*/
|
|
25969
26031
|
/** Playback-speed multiplier for the render (1 = realtime). */
|
|
25970
26032
|
var ExportSpeedSchema = number().min(.25).max(32);
|
|
26033
|
+
/**
|
|
26034
|
+
* One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
|
|
26035
|
+
*
|
|
26036
|
+
* Relative and not absolute epoch on purpose: the renderer's frame-select
|
|
26037
|
+
* expression sees ffmpeg's `t`, which starts at 0 for the export's source
|
|
26038
|
+
* playlist. Handing it absolute epochs would make every call site responsible
|
|
26039
|
+
* for the same subtraction, and the one that forgot would emit a filter that
|
|
26040
|
+
* selects nothing — silently, as a uniform timelapse.
|
|
26041
|
+
*/
|
|
26042
|
+
var ExportDenseRangeSchema = object({
|
|
26043
|
+
fromSec: number().nonnegative(),
|
|
26044
|
+
toSec: number().nonnegative()
|
|
26045
|
+
}).refine((r) => r.toSec > r.fromSec, { message: "dense range must have toSec > fromSec" });
|
|
26046
|
+
/**
|
|
26047
|
+
* Dense-interval overlay for a timelapse: sample at `dense.everyMs` INSIDE the
|
|
26048
|
+
* listed ranges and at the base `everyMs` everywhere else.
|
|
26049
|
+
*
|
|
26050
|
+
* `everyMs` must be strictly smaller than the base cadence — a dense rate that
|
|
26051
|
+
* is not denser renders a uniform timelapse the operator believes is two-rate.
|
|
26052
|
+
*/
|
|
26053
|
+
var ExportDenseSchema = object({
|
|
26054
|
+
everyMs: number().int().positive(),
|
|
26055
|
+
ranges: array(ExportDenseRangeSchema).min(1).max(200)
|
|
26056
|
+
});
|
|
25971
26057
|
/** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
|
|
25972
26058
|
var ExportTimelapseSchema = object({
|
|
25973
26059
|
everyMs: number().int().positive(),
|
|
25974
|
-
outputFps: number().int().min(1).max(60).optional()
|
|
26060
|
+
outputFps: number().int().min(1).max(60).optional(),
|
|
26061
|
+
/** Optional second, FASTER rate over the intervals that matter. */
|
|
26062
|
+
dense: ExportDenseSchema.optional()
|
|
26063
|
+
}).superRefine((v, ctx) => {
|
|
26064
|
+
if (v.dense !== void 0 && v.dense.everyMs >= v.everyMs) ctx.addIssue({
|
|
26065
|
+
code: ZodIssueCode.custom,
|
|
26066
|
+
message: "dense.everyMs must be strictly smaller than the base everyMs",
|
|
26067
|
+
path: ["dense", "everyMs"]
|
|
26068
|
+
});
|
|
25975
26069
|
});
|
|
25976
26070
|
/**
|
|
25977
26071
|
* Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
|
|
@@ -26029,6 +26123,38 @@ var ExportDownloadSchema = object({
|
|
|
26029
26123
|
url: string(),
|
|
26030
26124
|
endpoints: array(string())
|
|
26031
26125
|
});
|
|
26126
|
+
/**
|
|
26127
|
+
* Hard ceiling on ONE {@link recordingExportCapability} byte read — 50 MiB.
|
|
26128
|
+
*
|
|
26129
|
+
* Two independent reasons land on the same number, which is why it is this one
|
|
26130
|
+
* and not a rounder guess:
|
|
26131
|
+
*
|
|
26132
|
+
* - **Nobody would accept more.** The roomiest byte cap any notifier backend
|
|
26133
|
+
* declares is telegram's 50 MiB, and the degrade engine DROPS an over-cap
|
|
26134
|
+
* attachment outright rather than degrading it to a link. Bytes above this
|
|
26135
|
+
* are read, encoded and moved to be thrown away at the last step.
|
|
26136
|
+
* - **The envelope is unary.** A base64 payload is held whole, ~1.33× its
|
|
26137
|
+
* size, in the provider AND in the caller — on a hub this repo has already
|
|
26138
|
+
* OOM'd once (D9/D18). A bounded on-demand read at human speed is the shape
|
|
26139
|
+
* those records permit; an unbounded one is the shape they forbid.
|
|
26140
|
+
*
|
|
26141
|
+
* Above it the provider REFUSES with a log line rather than truncating: half a
|
|
26142
|
+
* video is worse than a notification that says there is no attachment.
|
|
26143
|
+
*/
|
|
26144
|
+
var RECORDING_EXPORT_MAX_READ_BYTES = 50 * 1024 * 1024;
|
|
26145
|
+
/**
|
|
26146
|
+
* A finished export's bytes, inline.
|
|
26147
|
+
*
|
|
26148
|
+
* `bytes` is the DECODED length — the number the caller bounds and logs
|
|
26149
|
+
* against, so nobody has to infer it from the base64 length.
|
|
26150
|
+
*/
|
|
26151
|
+
var ExportBytesSchema = object({
|
|
26152
|
+
base64: string(),
|
|
26153
|
+
contentType: string(),
|
|
26154
|
+
/** Suggested filename, extension included. */
|
|
26155
|
+
name: string(),
|
|
26156
|
+
bytes: number().int().nonnegative()
|
|
26157
|
+
});
|
|
26032
26158
|
method(object({
|
|
26033
26159
|
deviceId: number(),
|
|
26034
26160
|
profile: string(),
|
|
@@ -26053,6 +26179,9 @@ method(object({
|
|
|
26053
26179
|
}), method(object({ exportId: string() }), ExportDownloadSchema, {
|
|
26054
26180
|
kind: "query",
|
|
26055
26181
|
auth: "protected"
|
|
26182
|
+
}), method(object({ exportId: string() }), ExportBytesSchema, {
|
|
26183
|
+
kind: "query",
|
|
26184
|
+
auth: "protected"
|
|
26056
26185
|
});
|
|
26057
26186
|
/**
|
|
26058
26187
|
* scene-monitor — device-scoped reference-region state cap. An operator marks
|
|
@@ -28519,9 +28648,10 @@ var DeclaredDevices = class {
|
|
|
28519
28648
|
}
|
|
28520
28649
|
const integrationId = spec.integrationId ?? await this.ensureIntegration(spec.integrationName);
|
|
28521
28650
|
const index = await this.readIndex();
|
|
28651
|
+
const live = await this.readLiveByStableId();
|
|
28522
28652
|
const outcomes = [];
|
|
28523
28653
|
for (const declaration of spec.devices) {
|
|
28524
|
-
const outcome = await this.applyDeclaration(declaration, integrationId, index);
|
|
28654
|
+
const outcome = await this.applyDeclaration(declaration, integrationId, index, live);
|
|
28525
28655
|
if (outcome !== null) outcomes.push(outcome);
|
|
28526
28656
|
}
|
|
28527
28657
|
return {
|
|
@@ -28567,6 +28697,26 @@ var DeclaredDevices = class {
|
|
|
28567
28697
|
return new Map(rows.map((row) => [row.stableId, row]));
|
|
28568
28698
|
}
|
|
28569
28699
|
/**
|
|
28700
|
+
* Devices this kernel already has CONSTRUCTED, by stableId.
|
|
28701
|
+
*
|
|
28702
|
+
* Distinct from {@link readIndex}, and the distinction is the bug: the index
|
|
28703
|
+
* is persisted rows, this is live objects. A row without an object must be
|
|
28704
|
+
* adopted; an object must be left exactly as it is.
|
|
28705
|
+
*
|
|
28706
|
+
* Failure is non-fatal and deliberately so — an empty map degrades to the
|
|
28707
|
+
* previous behaviour (attempt the adopt) rather than skipping a device that
|
|
28708
|
+
* genuinely needs bringing up.
|
|
28709
|
+
*/
|
|
28710
|
+
async readLiveByStableId() {
|
|
28711
|
+
try {
|
|
28712
|
+
const devices = await this.ports.devices.getAll();
|
|
28713
|
+
return new Map(devices.map((device) => [device.stableId, device]));
|
|
28714
|
+
} catch (err) {
|
|
28715
|
+
this.ports.logger.warn("could not read live devices — falling back to adopt-by-row", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
28716
|
+
return /* @__PURE__ */ new Map();
|
|
28717
|
+
}
|
|
28718
|
+
}
|
|
28719
|
+
/**
|
|
28570
28720
|
* One declaration: adopt what exists, create what does not.
|
|
28571
28721
|
*
|
|
28572
28722
|
* The create branch is the destructive one — it seeds `initialMeta`, and
|
|
@@ -28575,8 +28725,15 @@ var DeclaredDevices = class {
|
|
|
28575
28725
|
* the declared name over the operator's rename. D49: that branch needs a
|
|
28576
28726
|
* second read to agree.
|
|
28577
28727
|
*/
|
|
28578
|
-
async applyDeclaration(declaration, integrationId, index) {
|
|
28728
|
+
async applyDeclaration(declaration, integrationId, index, live) {
|
|
28579
28729
|
try {
|
|
28730
|
+
const alreadyLive = live.get(declaration.stableId);
|
|
28731
|
+
if (alreadyLive !== void 0) return {
|
|
28732
|
+
stableId: declaration.stableId,
|
|
28733
|
+
deviceId: alreadyLive.id,
|
|
28734
|
+
device: alreadyLive,
|
|
28735
|
+
created: false
|
|
28736
|
+
};
|
|
28580
28737
|
let existing = index.get(declaration.stableId);
|
|
28581
28738
|
if (existing === void 0) {
|
|
28582
28739
|
existing = (await this.readIndex()).get(declaration.stableId);
|
|
@@ -32882,37 +33039,43 @@ Object.freeze({
|
|
|
32882
33039
|
access: "create"
|
|
32883
33040
|
},
|
|
32884
33041
|
"recordingExport.cancelExport": {
|
|
32885
|
-
capName: "
|
|
33042
|
+
capName: "recording-export",
|
|
32886
33043
|
capScope: "system",
|
|
32887
33044
|
addonId: null,
|
|
32888
33045
|
access: "create"
|
|
32889
33046
|
},
|
|
32890
33047
|
"recordingExport.createExport": {
|
|
32891
|
-
capName: "
|
|
33048
|
+
capName: "recording-export",
|
|
32892
33049
|
capScope: "system",
|
|
32893
33050
|
addonId: null,
|
|
32894
33051
|
access: "create"
|
|
32895
33052
|
},
|
|
32896
33053
|
"recordingExport.deleteExport": {
|
|
32897
|
-
capName: "
|
|
33054
|
+
capName: "recording-export",
|
|
32898
33055
|
capScope: "system",
|
|
32899
33056
|
addonId: null,
|
|
32900
33057
|
access: "delete"
|
|
32901
33058
|
},
|
|
32902
33059
|
"recordingExport.getDownloadUrl": {
|
|
32903
|
-
capName: "
|
|
33060
|
+
capName: "recording-export",
|
|
32904
33061
|
capScope: "system",
|
|
32905
33062
|
addonId: null,
|
|
32906
33063
|
access: "view"
|
|
32907
33064
|
},
|
|
32908
33065
|
"recordingExport.getExport": {
|
|
32909
|
-
capName: "
|
|
33066
|
+
capName: "recording-export",
|
|
32910
33067
|
capScope: "system",
|
|
32911
33068
|
addonId: null,
|
|
32912
33069
|
access: "view"
|
|
32913
33070
|
},
|
|
32914
33071
|
"recordingExport.listExports": {
|
|
32915
|
-
capName: "
|
|
33072
|
+
capName: "recording-export",
|
|
33073
|
+
capScope: "system",
|
|
33074
|
+
addonId: null,
|
|
33075
|
+
access: "view"
|
|
33076
|
+
},
|
|
33077
|
+
"recordingExport.readExportBytes": {
|
|
33078
|
+
capName: "recording-export",
|
|
32916
33079
|
capScope: "system",
|
|
32917
33080
|
addonId: null,
|
|
32918
33081
|
access: "view"
|
|
@@ -34374,7 +34537,23 @@ var TimelapseRuleInputSchema = object({
|
|
|
34374
34537
|
/** Canonical notification priority ordinal (1..5); per-target overridable. */
|
|
34375
34538
|
priority: PriorityField.default(3)
|
|
34376
34539
|
});
|
|
34377
|
-
|
|
34540
|
+
/**
|
|
34541
|
+
* Partial patch for an update — any subset of the INPUT fields, with NO
|
|
34542
|
+
* defaults (an absent key means "leave unchanged", never "reset to default").
|
|
34543
|
+
* Provenance and ownership are absent by construction: a patch can rename or
|
|
34544
|
+
* retune a rule, never re-own it or forge its generation state.
|
|
34545
|
+
*
|
|
34546
|
+
* CLEAR SIGNAL: `template` is the one OPTIONAL field an editor can REMOVE, so
|
|
34547
|
+
* it is `.nullable()` here and NOWHERE else. Wire representation:
|
|
34548
|
+
* - key absent → leave the template unchanged
|
|
34549
|
+
* - `"template": null` → CLEAR it (the persisted rule loses the key)
|
|
34550
|
+
* - `"template": {...}` → replace it
|
|
34551
|
+
* `undefined` is deliberately NOT the clear signal: it does not survive
|
|
34552
|
+
* `JSON.stringify`, so a viewer editor could not express "remove". The
|
|
34553
|
+
* PERSISTED rule never carries `null` — the store drops the key (see
|
|
34554
|
+
* `TimelapseStore.update`).
|
|
34555
|
+
*/
|
|
34556
|
+
var TimelapseRulePatchSchema = object({
|
|
34378
34557
|
name: NameField.optional(),
|
|
34379
34558
|
enabled: boolean().optional(),
|
|
34380
34559
|
deviceIds: DeviceIdsField.optional(),
|
|
@@ -34395,15 +34574,50 @@ var TimelapseRuleSchema = TimelapseRuleInputSchema.extend({
|
|
|
34395
34574
|
*/
|
|
34396
34575
|
ownerUserId: string().optional(),
|
|
34397
34576
|
/**
|
|
34398
|
-
* Epoch-ms of the
|
|
34399
|
-
*
|
|
34577
|
+
* Epoch-ms of the NEWEST successful generation across every camera of this
|
|
34578
|
+
* rule. What a UI shows, and the compatibility floor for
|
|
34579
|
+
* {@link readTimelapseGeneratedAt}. Absent = never generated.
|
|
34400
34580
|
*/
|
|
34401
34581
|
lastGeneratedAt: number().optional(),
|
|
34582
|
+
/**
|
|
34583
|
+
* PER-CAMERA generation state, keyed by `String(deviceId)` — the
|
|
34584
|
+
* re-generation guard's real durable state.
|
|
34585
|
+
*
|
|
34586
|
+
* One rule covers several cameras and each renders its own video, so a rule
|
|
34587
|
+
* -wide stamp is wrong in the direction that DESTROYS work: camera A
|
|
34588
|
+
* succeeding at 06:05 tells camera B, whose render failed, that it is
|
|
34589
|
+
* already done — and B's night is gone for good, because the window will not
|
|
34590
|
+
* come back.
|
|
34591
|
+
*
|
|
34592
|
+
* ADDITIVE, so the migration is free: a row written before this field simply
|
|
34593
|
+
* has no map, and {@link readTimelapseGeneratedAt} falls back to
|
|
34594
|
+
* {@link TimelapseRuleSchema.shape.lastGeneratedAt}. Reading an old row as
|
|
34595
|
+
* "never generated" would re-render and re-notify every camera of every rule
|
|
34596
|
+
* once, on the deploy that shipped the map.
|
|
34597
|
+
*/
|
|
34598
|
+
generatedByDevice: record(string(), number()).optional(),
|
|
34402
34599
|
/** userId of the caller who created the rule (server-stamped). */
|
|
34403
34600
|
createdBy: string(),
|
|
34404
34601
|
createdAt: number(),
|
|
34405
34602
|
updatedAt: number()
|
|
34406
34603
|
});
|
|
34604
|
+
/**
|
|
34605
|
+
* The last successful generation for ONE camera of a rule, epoch-ms.
|
|
34606
|
+
*
|
|
34607
|
+
* The per-device map wins; a rule with no map falls back to the rule-wide
|
|
34608
|
+
* `lastGeneratedAt` (the compatible-migration path — see
|
|
34609
|
+
* {@link TimelapseRuleSchema}); a rule with neither returns 0, which every
|
|
34610
|
+
* guard reads as "never generated".
|
|
34611
|
+
*
|
|
34612
|
+
* Read through this helper and never off the field directly: the fallback is
|
|
34613
|
+
* the whole migration, and a call site that forgot it would re-render an
|
|
34614
|
+
* entire rule set once.
|
|
34615
|
+
*/
|
|
34616
|
+
function readTimelapseGeneratedAt(rule, deviceId) {
|
|
34617
|
+
const map = rule.generatedByDevice;
|
|
34618
|
+
if (map !== void 0) return map[String(deviceId)] ?? 0;
|
|
34619
|
+
return rule.lastGeneratedAt ?? 0;
|
|
34620
|
+
}
|
|
34407
34621
|
object({
|
|
34408
34622
|
/**
|
|
34409
34623
|
* Fraction of the box's own size added on EACH side before cutting.
|
|
@@ -34682,6 +34896,12 @@ Object.defineProperty(exports, "OpsLogEntrySchema", {
|
|
|
34682
34896
|
return OpsLogEntrySchema;
|
|
34683
34897
|
}
|
|
34684
34898
|
});
|
|
34899
|
+
Object.defineProperty(exports, "RECORDING_EXPORT_MAX_READ_BYTES", {
|
|
34900
|
+
enumerable: true,
|
|
34901
|
+
get: function() {
|
|
34902
|
+
return RECORDING_EXPORT_MAX_READ_BYTES;
|
|
34903
|
+
}
|
|
34904
|
+
});
|
|
34685
34905
|
Object.defineProperty(exports, "RetrainStatusSchema", {
|
|
34686
34906
|
enumerable: true,
|
|
34687
34907
|
get: function() {
|
|
@@ -34694,6 +34914,12 @@ Object.defineProperty(exports, "TimelapseRuleInputSchema", {
|
|
|
34694
34914
|
return TimelapseRuleInputSchema;
|
|
34695
34915
|
}
|
|
34696
34916
|
});
|
|
34917
|
+
Object.defineProperty(exports, "TimelapseRulePatchSchema", {
|
|
34918
|
+
enumerable: true,
|
|
34919
|
+
get: function() {
|
|
34920
|
+
return TimelapseRulePatchSchema;
|
|
34921
|
+
}
|
|
34922
|
+
});
|
|
34697
34923
|
Object.defineProperty(exports, "TimelapseRuleSchema", {
|
|
34698
34924
|
enumerable: true,
|
|
34699
34925
|
get: function() {
|
|
@@ -34784,6 +35010,12 @@ Object.defineProperty(exports, "defineCustomActions", {
|
|
|
34784
35010
|
return defineCustomActions;
|
|
34785
35011
|
}
|
|
34786
35012
|
});
|
|
35013
|
+
Object.defineProperty(exports, "deriveRecordingMode", {
|
|
35014
|
+
enumerable: true,
|
|
35015
|
+
get: function() {
|
|
35016
|
+
return deriveRecordingMode;
|
|
35017
|
+
}
|
|
35018
|
+
});
|
|
34787
35019
|
Object.defineProperty(exports, "embeddingEncoderCapability", {
|
|
34788
35020
|
enumerable: true,
|
|
34789
35021
|
get: function() {
|
|
@@ -34886,6 +35118,12 @@ Object.defineProperty(exports, "readDeviceStateFrom", {
|
|
|
34886
35118
|
return readDeviceStateFrom;
|
|
34887
35119
|
}
|
|
34888
35120
|
});
|
|
35121
|
+
Object.defineProperty(exports, "readTimelapseGeneratedAt", {
|
|
35122
|
+
enumerable: true,
|
|
35123
|
+
get: function() {
|
|
35124
|
+
return readTimelapseGeneratedAt;
|
|
35125
|
+
}
|
|
35126
|
+
});
|
|
34889
35127
|
Object.defineProperty(exports, "record", {
|
|
34890
35128
|
enumerable: true,
|
|
34891
35129
|
get: function() {
|