@camstack/system 1.2.89 → 1.2.91

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 (53) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +25 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +25 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-block-store.d.ts +11 -0
  12. package/dist/builtins/core-blocks/core-blocks.addon.js +12 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.mjs +12 -1
  14. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  22. package/dist/builtins/local-auth/auth-schema.d.ts +42 -0
  23. package/dist/builtins/local-auth/local-auth.addon.js +43 -1
  24. package/dist/builtins/local-auth/local-auth.addon.mjs +43 -1
  25. package/dist/builtins/local-network/local-network.addon.js +1 -1
  26. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  27. package/dist/builtins/loki-logging/index.js +1 -1
  28. package/dist/builtins/loki-logging/index.mjs +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +16 -5
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +16 -5
  41. package/dist/builtins/storage-orchestrator/index.mjs +1 -1
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +65 -4
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +64 -4
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +12 -0
  45. package/dist/builtins/system-config/system-config.addon.js +1 -1
  46. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  47. package/dist/builtins/winston-logging/index.js +1 -1
  48. package/dist/builtins/winston-logging/index.mjs +1 -1
  49. package/dist/{dist-Dq0ebOuS.js → dist-9Gfk4KOk.js} +500 -47
  50. package/dist/{dist-Bij6agsS.mjs → dist-DhrTd8LL.mjs} +500 -47
  51. package/dist/index.js +39 -46
  52. package/dist/index.mjs +39 -46
  53. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  let zod = require("zod");
2
- //#region ../types/dist/event-category-Cv9dO26A.mjs
2
+ //#region ../types/dist/event-category-Bxo5yJjt.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -206,6 +206,33 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
206
206
  EventCategory["PipelineCameraAssigned"] = "pipeline.camera-assigned";
207
207
  EventCategory["PipelineCameraUnassigned"] = "pipeline.camera-unassigned";
208
208
  /**
209
+ * A node the orchestrator would otherwise place cameras on has NO usable
210
+ * inference device: the operator enabled one or more accelerators there and
211
+ * the live probe reports every one of them unavailable. Emitted once per
212
+ * TRANSITION into that state (never per dispatch), and the node is dropped
213
+ * from the placement candidate set for as long as it holds.
214
+ *
215
+ * This exists because the state was previously invisible: little-unraid
216
+ * absorbed 283k inference errors in a day while still being handed cameras,
217
+ * and nothing in the system said so.
218
+ *
219
+ * A node with no accelerators configured at all is NOT this — its devices
220
+ * are `disabled`, not `unavailable`, and the runner's default CPU pool
221
+ * serves it exactly as before.
222
+ */
223
+ EventCategory["PipelineNodeInferenceUnavailable"] = "pipeline.node-inference-unavailable";
224
+ /**
225
+ * A camera has an OPEN detection session and has produced no detection at
226
+ * all for longer than the blind threshold — the camera is being decoded and
227
+ * inferred and is returning nothing. Emitted once per transition into blind,
228
+ * per camera.
229
+ *
230
+ * The failure it reports: a 1h43 detection blackout on the entrance camera
231
+ * that nobody noticed, because "a camera that detects nothing" and "a quiet
232
+ * camera" produce byte-identical silence.
233
+ */
234
+ EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
235
+ /**
209
236
  * Per-camera pipeline config was mutated by the orchestrator
210
237
  * (3-level settings change via `setAgentAddonDefaults` /
211
238
  * `setCameraStepToggle` / `setCameraPipelineForAgent` or a
@@ -10072,6 +10099,17 @@ var LlmImageSchema = zod.z.object({
10072
10099
  bytes: zod.z.instanceof(Uint8Array),
10073
10100
  mimeType: zod.z.string()
10074
10101
  });
10102
+ /**
10103
+ * Retry policy. `enabled: false` is NOT the same as `maxAttempts: 1` in intent —
10104
+ * the flag is what a consumer table flips, the count is what the operator tunes.
10105
+ * A retry doubles the wall time of a call, so the two gates that run inside a
10106
+ * notification's budget keep it off (see `CONSUMER_RETRY_POLICY` in addon-ai).
10107
+ */
10108
+ var LlmRetryPolicySchema = zod.z.object({
10109
+ enabled: zod.z.boolean().default(false),
10110
+ /** Total attempts INCLUDING the first. 1 = no retry. */
10111
+ maxAttempts: zod.z.number().int().min(1).max(5).default(1)
10112
+ });
10075
10113
  var LlmGenerateBaseInputSchema = zod.z.object({
10076
10114
  /** Collection routing (the notification-output posture). */
10077
10115
  addonId: zod.z.string().optional(),
@@ -10086,7 +10124,28 @@ var LlmGenerateBaseInputSchema = zod.z.object({
10086
10124
  jsonSchema: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
10087
10125
  /** Per-call override of the profile default. */
10088
10126
  maxTokens: zod.z.number().int().positive().optional(),
10089
- temperature: zod.z.number().optional()
10127
+ temperature: zod.z.number().optional(),
10128
+ /** Per-call override of the profile default (nucleus sampling). */
10129
+ topP: zod.z.number().min(0).max(1).optional(),
10130
+ /** Per-call override of the profile default (top-k sampling). */
10131
+ topK: zod.z.number().int().positive().optional(),
10132
+ /** Per-call override of `profile.timeoutMs` — the total generation bound. */
10133
+ timeoutMs: zod.z.number().int().positive().optional(),
10134
+ /** Per-call override; beats both the consumer table and the profile. */
10135
+ retry: LlmRetryPolicySchema.optional(),
10136
+ /**
10137
+ * Caller-minted id that makes this generation CANCELLABLE.
10138
+ *
10139
+ * Without it a caller that stops waiting cannot stop the work: the gates race
10140
+ * the call against 8 s and free their own slot when the timer wins, while the
10141
+ * generation upstream keeps running to `profile.timeoutMs` — 60 s by default,
10142
+ * on a single-threaded local model. The per-camera bound then counts WAITS,
10143
+ * not generations, and the real load is unbounded.
10144
+ *
10145
+ * `AbortSignal` cannot cross a process boundary; an id can. Pass one here and
10146
+ * `llm.cancel({ requestId })` tears the socket down.
10147
+ */
10148
+ requestId: zod.z.string().optional()
10090
10149
  });
10091
10150
  /**
10092
10151
  * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
@@ -10125,8 +10184,49 @@ var ManagedRuntimeConfigSchema = zod.z.object({
10125
10184
  gpuLayers: zod.z.number().int().default(0),
10126
10185
  /** Default: cpus-2, clamped ≥1 (resolved node-side). */
10127
10186
  threads: zod.z.number().int().optional(),
10128
- /** Concurrent slots. */
10187
+ /** Concurrent slots (`--parallel`). */
10129
10188
  parallel: zod.z.number().int().default(1),
10189
+ /** Logical batch size (`-b`). Larger = faster prompt ingest, more RAM. */
10190
+ batchSize: zod.z.number().int().positive().optional(),
10191
+ /** Physical batch / micro-batch (`-ub`). */
10192
+ ubatchSize: zod.z.number().int().positive().optional(),
10193
+ /**
10194
+ * `--flash-attn`. Cuts KV-cache memory on the backends that implement it and
10195
+ * is a no-op elsewhere, so it is offered rather than assumed.
10196
+ */
10197
+ flashAttention: zod.z.boolean().default(false),
10198
+ /**
10199
+ * `--mlock`. Pins the weights in RAM so the OS cannot page them out mid
10200
+ * inference. Costs the full model size in resident memory — which is exactly
10201
+ * what the RAM budget is counting.
10202
+ */
10203
+ mlock: zod.z.boolean().default(false),
10204
+ /**
10205
+ * `--no-mmap`. Reads the whole GGUF up front instead of mapping it. Slower to
10206
+ * start, but avoids the page-fault stalls a network or spinning-disk model
10207
+ * store produces on every first token.
10208
+ */
10209
+ noMmap: zod.z.boolean().default(false),
10210
+ /** `--cache-type-k` / `--cache-type-v` — quantising the KV cache is the
10211
+ * cheapest way to fit a longer context in the same RAM. */
10212
+ cacheTypeK: zod.z.enum([
10213
+ "f32",
10214
+ "f16",
10215
+ "q8_0",
10216
+ "q5_1",
10217
+ "q5_0",
10218
+ "q4_1",
10219
+ "q4_0"
10220
+ ]).optional(),
10221
+ cacheTypeV: zod.z.enum([
10222
+ "f32",
10223
+ "f16",
10224
+ "q8_0",
10225
+ "q5_1",
10226
+ "q5_0",
10227
+ "q4_1",
10228
+ "q4_0"
10229
+ ]).optional(),
10130
10230
  /** Else lazy: first generate boots it. */
10131
10231
  autoStart: zod.z.boolean().default(false),
10132
10232
  /** 0 = never; frees RAM after quiet periods. */
@@ -10229,10 +10329,44 @@ var LlmProfileSchema = zod.z.object({
10229
10329
  baseUrl: zod.z.string().optional(),
10230
10330
  /** ConfigUISchema type:'password' — never round-trips (spec §5). */
10231
10331
  apiKey: zod.z.string().optional(),
10332
+ /** Vision on/off. A vision call against a `false` profile is REFUSED, never
10333
+ * degraded to text — that shipped once and produced a confident answer to a
10334
+ * question about a picture nobody sent. */
10232
10335
  supportsVision: zod.z.boolean(),
10233
10336
  temperature: zod.z.number().min(0).max(2).optional(),
10337
+ /** Nucleus sampling. Every wire we speak has it. */
10338
+ topP: zod.z.number().min(0).max(1).optional(),
10339
+ /** Top-k sampling. Carried only by the wires that have it — NEITHER OpenAI
10340
+ * wire does, and the client drops it there (measured: the request body gets
10341
+ * `top_p` and no `top_k`). The profile editor hides the field wherever it
10342
+ * would change nothing; `KINDS_WITH_TOP_K` is the single owner of that list. */
10343
+ topK: zod.z.number().int().positive().optional(),
10234
10344
  maxTokens: zod.z.number().int().positive().optional(),
10345
+ /** Prompt context window. Advisory for cloud kinds (they enforce their own);
10346
+ * for `managed-local` it is the llama.cpp `--ctx-size` the runtime starts
10347
+ * the model with, so it is the one field that changes a PROCESS. */
10348
+ contextLength: zod.z.number().int().positive().optional(),
10349
+ /** Default system prompt. A caller's `system` REPLACES it (never appends —
10350
+ * two system prompts fighting is worse than either alone). */
10351
+ systemPrompt: zod.z.string().optional(),
10352
+ /** Total generation bound — the only one a unary call has. */
10235
10353
  timeoutMs: zod.z.number().int().positive().default(6e4),
10354
+ /** Wait for response headers only. */
10355
+ connectTimeoutMs: zod.z.number().int().positive().default(1e4),
10356
+ /** Accepted, but no output yet — a cold GPU load lives here. */
10357
+ firstTokenTimeoutMs: zod.z.number().int().positive().default(12e4),
10358
+ /** Output started then stopped. */
10359
+ idleTimeoutMs: zod.z.number().int().positive().default(6e4),
10360
+ /** Profile-level default. The per-consumer table and a per-call override
10361
+ * both beat it — see `resolveRetryPolicy`. */
10362
+ retry: LlmRetryPolicySchema.default({
10363
+ enabled: false,
10364
+ maxAttempts: 1
10365
+ }),
10366
+ /** Whether this profile may use tools. The tool-call plumbing rides the
10367
+ * library; the REGISTRY of callable tools is ours and is empty in v1, so a
10368
+ * `true` here buys the wiring, not behaviour, until tools are registered. */
10369
+ toolsEnabled: zod.z.boolean().default(false),
10236
10370
  extraHeaders: zod.z.record(zod.z.string(), zod.z.string()).optional(),
10237
10371
  /** kind === 'managed-local' only (spec §4). */
10238
10372
  runtime: ManagedRuntimeConfigSchema.optional()
@@ -10305,6 +10439,17 @@ var llmCapability = {
10305
10439
  methods: {
10306
10440
  generate: method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }),
10307
10441
  generateVision: method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }),
10442
+ /**
10443
+ * Stop a generation started with a `requestId`.
10444
+ *
10445
+ * Idempotent and always successful: cancelling an id that already finished,
10446
+ * never existed, or was cancelled a moment ago is a no-op. A caller that has
10447
+ * given up must never have to handle an error from giving up.
10448
+ */
10449
+ cancel: method(zod.z.object({
10450
+ addonId: zod.z.string().optional(),
10451
+ requestId: zod.z.string()
10452
+ }), zod.z.void(), { kind: "mutation" }),
10308
10453
  listProfileKinds: method(zod.z.object({}), zod.z.array(LlmProfileKindDescriptorSchema)),
10309
10454
  listProfiles: method(zod.z.object({}), zod.z.array(LlmProfileSchema)),
10310
10455
  upsertProfile: method(zod.z.object({ profile: LlmProfileSchema }), LlmProfileSchema, {
@@ -12171,6 +12316,8 @@ var NcSystemEventKindSchema = zod.z.enum([
12171
12316
  "stream-offline",
12172
12317
  "node-online",
12173
12318
  "node-offline",
12319
+ "node-inference-unavailable",
12320
+ "detection-blind",
12174
12321
  "addon-update-available",
12175
12322
  "server-update-available",
12176
12323
  "alarm-triggered",
@@ -12277,28 +12424,36 @@ var NcOccupancyConditionSchema = zod.z.object({
12277
12424
  /**
12278
12425
  * Audio condition (IMMEDIATE trigger) — a rule on SOUND, not on a picture.
12279
12426
  *
12280
- * Operator-approved vocabulary (2026-08-12, option A — the same one the
12281
- * reference notifier uses, so an operator moving between them re-uses what
12282
- * they already know): a rule matches when, over a sampling window of
12283
- * `samplingSeconds`, at least `hitPercent`% of the audio samples in that
12284
- * window are HITS. A sample is a hit when it satisfies BOTH present filters:
12285
- *
12286
- * - `dbThreshold` its level is at or above this many dBFS (see
12287
- * {@link NC_AUDIO_DBFS_FLOOR}: negative-going, `0` = full scale);
12288
- * - `labels` the classifier put at least one of these labels on it.
12289
- *
12290
- * Both are OPTIONAL and independent, which is the point of the shape: a
12291
- * loudness rule ("something loud at 3am") needs no model to be right, and a
12292
- * label rule ("a dog barked") needs no threshold. **Fail-closed when NEITHER
12293
- * is given** a window in which every sample is trivially a hit would fire on
12294
- * silence, so the engine refuses such a condition rather than notifying on
12295
- * nothing (the schema cannot express "at least one of" without becoming a
12296
- * ZodEffects the cap path would have to special-case).
12297
- *
12298
- * `hitPercent` is over the samples the window actually HOLDS, and the window
12299
- * must be FULL before it can match a window that has been open for two
12300
- * seconds of its ten is 100% of nothing, and firing on it would make
12301
- * `samplingSeconds` decorative.
12427
+ * **TWO EXCLUSIVE MODES** (operator decision 2026-08-14, D157). Which one a
12428
+ * rule is in is not a stored field it is WHICH FILTER the rule carries, so
12429
+ * there is no second switch that can disagree with the first and every rule
12430
+ * authored before the decision migrates for free (`audioModeOf`):
12431
+ *
12432
+ * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
12433
+ * classifier labels with one of them. No window, no percentage:
12434
+ * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
12435
+ * `throttle` cooldown is the only brake. The per-label confidence floor is
12436
+ * the analyzer's (`classificationMinScore`, per device) — a label only
12437
+ * reaches this condition if the classifier was already confident enough.
12438
+ * - **LEVEL mode `dbThreshold` present, no labels.** The sampling window IS
12439
+ * the condition: at least `hitPercent`% of the samples over
12440
+ * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
12441
+ * {@link NC_AUDIO_DBFS_FLOOR}: negative-going, `0` = full scale). The window
12442
+ * must be FULL before it can match a window open for two of its ten
12443
+ * seconds is 100% of nothing.
12444
+ *
12445
+ * **Why label mode has no window.** It had one, and it never fired: the
12446
+ * analyzer emits ~1 audio frame per second but YAMNet only LABELS one to three
12447
+ * of them per episode, even through continuous crying. The measured maximum
12448
+ * `hitPercent` over the whole live history was 40 — under the shipped default
12449
+ * of 60, so a label rule could not fire at all, ever. A percentage of frames is
12450
+ * the wrong question to ask of a sparse classifier.
12451
+ *
12452
+ * **Fail-closed when NEITHER is given** — every sample would be a trivial hit
12453
+ * and the rule would fire on silence. The schema cannot express "exactly one
12454
+ * of" without becoming a ZodEffects the cap path would have to special-case, so
12455
+ * the exclusivity is enforced where every editor writes (`patchAudio`) and a
12456
+ * legacy rule carrying both resolves to LABEL (the mode that fires).
12302
12457
  *
12303
12458
  * Labels are the audio macro classes (`AUDIO_MACRO_LABELS` / the NC taxonomy's
12304
12459
  * `audio-*` ids). Both spellings are accepted — the matcher normalizes the
@@ -12306,13 +12461,13 @@ var NcOccupancyConditionSchema = zod.z.object({
12306
12461
  * an operator who typed `dog` mean the same thing.
12307
12462
  */
12308
12463
  var NcAudioConditionSchema = zod.z.object({
12309
- /** Audio macro labels; absent = any sound (level-only rule). */
12464
+ /** LABEL MODE: audio macro labels. Present fires on the first labelled frame. */
12310
12465
  labels: zod.z.array(zod.z.string().min(1)).min(1).optional(),
12311
- /** Level floor in dBFS (negative-going, `0` = full scale); absent = any level. */
12466
+ /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
12312
12467
  dbThreshold: zod.z.number().min(-96).max(0).optional(),
12313
- /** Percentage of the window's samples that must be hits (1–100). */
12468
+ /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
12314
12469
  hitPercent: zod.z.number().int().min(1).max(100).default(60),
12315
- /** Length of the sampling window in seconds. */
12470
+ /** LEVEL MODE ONLY: length of the sampling window in seconds. */
12316
12471
  samplingSeconds: zod.z.number().int().min(1).max(300).default(10)
12317
12472
  });
12318
12473
  /**
@@ -12955,7 +13110,50 @@ var NcRuleInputSchema = zod.z.object({
12955
13110
  * tests every zone the track visited and a `crossing` condition can no longer
12956
13111
  * be satisfied, because a closed track carries no crossing.
12957
13112
  */
12958
- waitForEnhancement: zod.z.boolean().optional()
13113
+ waitForEnhancement: zod.z.boolean().optional(),
13114
+ /**
13115
+ * GROUP a burst of subjects into ONE notification that grows.
13116
+ *
13117
+ * Seconds of quiet after the last matching subject before the burst is
13118
+ * considered over. While it is open, the first subject enqueues immediately —
13119
+ * **exactly as today, with no added latency** — and every real growth (a new
13120
+ * subject, or a name confirmed on one already in it) REPLACES that
13121
+ * notification with an updated one naming everybody. The push carries the
13122
+ * group's own coalescing tag, so the phone replaces rather than stacks.
13123
+ *
13124
+ * `0` / absent = off, and off is today's behaviour byte for byte.
13125
+ *
13126
+ * ### Why an idle cutoff and not a window
13127
+ *
13128
+ * The measured seven-person arrival on device 590 spans 110 s with every
13129
+ * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
13130
+ * idle cutoff holds it as one and ends it when the arrival actually ends.
13131
+ * 30 is Frigate's shipped value for the same decision.
13132
+ *
13133
+ * ### What it replaces
13134
+ *
13135
+ * The blind cooldown, which collapses a burst by DISCARDING it. Measured on
13136
+ * device 615 / *Persona su Uscio* over six days: 116 qualifying tracks → 74
13137
+ * notifications, **44 (37.9%) suppressed outright**, 23 of them overlapping a
13138
+ * track that did fire and 7 carrying a confirmed identity nobody heard about.
13139
+ * A group collapses the same volume by MERGING, so the cooldown becomes a
13140
+ * budget over GROUPS — which is what it always meant — and a growth is never
13141
+ * throttled by the window its own first member spent.
13142
+ *
13143
+ * ### Interaction with {@link waitForEnhancement}
13144
+ *
13145
+ * They compose, and the order matters. `waitForEnhancement` defers the rule to
13146
+ * TRACK CLOSE, so with both set the group is opened by the first member to
13147
+ * CLOSE — already carrying its name — and grows as later members close. That
13148
+ * is later, and complete. With grouping alone the group opens on the first
13149
+ * object event and picks up names as they are confirmed, through the growth
13150
+ * path. Neither combination fires twice for one subject.
13151
+ *
13152
+ * `.optional()` and deliberately NOT `.default()`: a Zod default does not run
13153
+ * on the addon cap path, so absent must keep meaning what it meant before this
13154
+ * field existed.
13155
+ */
13156
+ groupIdleSec: zod.z.number().int().min(0).max(600).optional()
12959
13157
  });
12960
13158
  /**
12961
13159
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -13989,7 +14187,7 @@ var TrackEnvelopeSchema = zod.z.object({
13989
14187
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
13990
14188
  * keeps every scalar the list surfaces actually render (ids, class(es),
13991
14189
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
13992
- * zonesVisited, bestEventId, envelope, hasFace) and returns `positions` /
14190
+ * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
13993
14191
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
13994
14192
  * `getTrack`. Mirrors the event-store `projection` convention
13995
14193
  * (`getObjectEvents` et al.).
@@ -14276,6 +14474,28 @@ var TrackSchema = zod.z.object({
14276
14474
  * `=== true` and render nothing otherwise, never infer "no face".
14277
14475
  */
14278
14476
  hasFace: zod.z.boolean().optional(),
14477
+ /**
14478
+ * This subject CONTAINS a folded rider — a person the rider-pairing step
14479
+ * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
14480
+ * so the passage is tracked once and as a VEHICLE.
14481
+ *
14482
+ * It exists because the fold's record was dishonest. D34 and the code both
14483
+ * said "the person is not lost — it is reported so both entities stay on the
14484
+ * record"; in fact the pair went into a per-processor RAM field behind an
14485
+ * accessor nobody called, and every durable surface said `vehicle`, full
14486
+ * stop. This is the composition note that makes the row true.
14487
+ *
14488
+ * A COMPOSITION, never a class and never a label. "This vehicle contains a
14489
+ * person" is not an answer to "what is this" — both label tiers would refuse
14490
+ * a macro token anyway (D89), and correctly. Nothing here changes what the
14491
+ * subject IS: a cyclist stays one vehicle track, occupancy still counts one,
14492
+ * and a `person` rule still does not fire for someone cycling past.
14493
+ *
14494
+ * **Absent ≠ false**, exactly like {@link hasFace}: every row written before
14495
+ * the column, and every hub that predates the field, omits it. Test
14496
+ * `=== true` and render nothing otherwise — never infer "no rider".
14497
+ */
14498
+ hasRider: zod.z.boolean().optional(),
14279
14499
  ...TrackFlagFields,
14280
14500
  ...TrackRetrainFields
14281
14501
  });
@@ -26542,14 +26762,50 @@ var recordingExportCapability = {
26542
26762
  * thing except the comparator: `similarity` (CLIP cosine at the same ROI coords
26543
26763
  * vs condition-tagged references) and `llm` (vision-LLM judgment over the crop).
26544
26764
  *
26545
- * D14 device-config archetype (`deviceConfig.ui.kind:'widget'`) the framework
26546
- * derives the device-detail contribution; the provider carries NO hand-written
26547
- * settings-contribution methods. `status.kind:'push'` the engine pushes on
26548
- * every hysteresis flip / availability change; consumers never poll.
26549
- */
26550
- /** Extensible condition tag. Seeded 'day' | 'night'; open by design so more can
26551
- * be added without a wire break (matching falls back to any-condition refs). */
26765
+ * **No `deviceConfig`, deliberately.** This shipped as the D14 widget archetype,
26766
+ * which put a "Scenes" tab on one camera's detail page. That is the wrong shape
26767
+ * for the thing: a scene is a standing question about the property ("is the bin
26768
+ * still out"), and the operator's question is "which of my scenes have tripped",
26769
+ * across every camera at once — not "what does camera 617 think". Buried one
26770
+ * camera deep it also could not be found. The surface is now a top-level admin
26771
+ * page (`/scenes`, `pages/Scenes.tsx`) that lists every scene on every camera and
26772
+ * picks the camera inside the create flow, the same shape Events and Faces have.
26773
+ *
26774
+ * The consequence to keep in mind: `host/scene-monitor-editor` is gone from
26775
+ * `HOST_WIDGETS` too. `scripts/check-host-widget-resolves.ts` asserts BOTH
26776
+ * directions, so a registration nobody declares fails exactly as loudly as a
26777
+ * declaration nobody registers. The editor is imported directly by the page.
26778
+ *
26779
+ * `status.kind:'push'` — the engine pushes on every hysteresis flip /
26780
+ * availability change; consumers never poll.
26781
+ */
26782
+ /** Extensible condition tag. Seeded 'day' | 'ir' (the two variants the operator
26783
+ * captures) plus 'night' | 'dawn' | 'dusk' from the resolver's sun-times band.
26784
+ * Open by design so more can be added without a wire break.
26785
+ *
26786
+ * Matching does NOT fall back across conditions: cross-condition cosines are
26787
+ * not comparable, so "I have never seen this scene in this light" is reported
26788
+ * as `unknown`, never guessed. A day reference scored against an IR frame
26789
+ * collapses the cosine and would latch a false alarm every single night. */
26552
26790
  var SceneConditionSchema = zod.z.string();
26791
+ /** `matched` = the baseline is what we see; `diverged` = it demonstrably is not;
26792
+ * `unknown` = we cannot judge (no reference for this condition, encoder model
26793
+ * changed, view shifted, no snapshot). `unknown` is a real value, not a null,
26794
+ * and never counts toward hysteresis in either direction. */
26795
+ var SceneVerdictSchema = zod.z.enum([
26796
+ "matched",
26797
+ "diverged",
26798
+ "unknown"
26799
+ ]);
26800
+ /** Why a scene cannot judge. Named, because this feature's failure mode is
26801
+ * silence that reads as "nothing has happened". */
26802
+ var SceneUnavailableSchema = zod.z.enum([
26803
+ "no-reference-for-condition",
26804
+ "view-shifted",
26805
+ "no-vision-profile",
26806
+ "encoder-model-changed",
26807
+ "no-snapshot"
26808
+ ]);
26553
26809
  /** One captured reference — condition-tagged, model-version-gated. `embedding`
26554
26810
  * is `number[]` (Float32Array does NOT survive MsgPack/UDS). */
26555
26811
  var SceneReferenceSchema = zod.z.object({
@@ -26557,7 +26813,14 @@ var SceneReferenceSchema = zod.z.object({
26557
26813
  modelId: zod.z.string(),
26558
26814
  condition: SceneConditionSchema,
26559
26815
  capturedAt: zod.z.number(),
26560
- thumbnailMediaId: zod.z.string().optional()
26816
+ thumbnailMediaId: zod.z.string().optional(),
26817
+ /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
26818
+ * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
26819
+ * normalized rect frame a different piece of world, and the scene would
26820
+ * diverge forever with a perfectly plausible cosine. Checked LAZILY, only
26821
+ * when hysteresis is about to flip — one extra encode per candidate
26822
+ * transition, not per poll. */
26823
+ anchorEmbedding: zod.z.array(zod.z.number()).optional()
26561
26824
  });
26562
26825
  var SceneMonitorStateSchema = zod.z.object({
26563
26826
  id: zod.z.string(),
@@ -26579,6 +26842,25 @@ var SceneCheckSchema = zod.z.discriminatedUnion("mode", [zod.z.object({
26579
26842
  profileId: zod.z.string().optional(),
26580
26843
  hysteresisCount: zod.z.number().int().positive()
26581
26844
  })]);
26845
+ var SCENE_DEFAULT_ANCHOR_THRESHOLD = .85;
26846
+ /**
26847
+ * Vision-model adjudication of a candidate flip. Field names deliberately
26848
+ * mirror `NcConfirmSchema` so an operator meets one vocabulary, not two.
26849
+ *
26850
+ * `onTimeout` defaults to **'hold'**, the OPPOSITE of `NcConfirmGate`'s
26851
+ * fail-open: a notification suppressed is the worse error there, but a vision
26852
+ * model that timed out has not told us the bin is gone, and a latch is a
26853
+ * stateful claim that costs the operator a trip to reset.
26854
+ */
26855
+ var SceneConfirmSchema = zod.z.object({
26856
+ enabled: zod.z.boolean().default(false),
26857
+ prompt: zod.z.string().min(1).max(1e3),
26858
+ profileId: zod.z.string().optional(),
26859
+ timeoutMs: zod.z.number().int().min(1e3).max(2e4).default(8e3),
26860
+ maxImagePx: zod.z.number().int().min(64).max(2048).default(448),
26861
+ /** What a timeout / unavailable model means for the PENDING flip. */
26862
+ onTimeout: zod.z.enum(["flip", "hold"]).default("hold")
26863
+ });
26582
26864
  var SceneMonitorSchema = zod.z.object({
26583
26865
  id: zod.z.string(),
26584
26866
  label: zod.z.string(),
@@ -26597,7 +26879,41 @@ var SceneMonitorSchema = zod.z.object({
26597
26879
  lastConfidence: zod.z.number().nullable(),
26598
26880
  currentCondition: SceneConditionSchema.nullable(),
26599
26881
  availability: zod.z.enum(["ok", "unavailable"]),
26600
- unavailableReason: zod.z.string().nullable()
26882
+ unavailableReason: zod.z.string().nullable(),
26883
+ /** Which state is "the initial screen". `null` until the first capture. */
26884
+ baselineStateId: zod.z.string().nullable(),
26885
+ /** Which boolean drives notification rules and any export. */
26886
+ emit: zod.z.enum(["latched", "live"]).default("latched"),
26887
+ /** Live: does the region match the baseline RIGHT NOW. */
26888
+ verdict: SceneVerdictSchema,
26889
+ /** Has it been `diverged` at least once since `armedAt` — the operator's boolean. */
26890
+ latched: zod.z.boolean(),
26891
+ /** Last reset (or creation). */
26892
+ armedAt: zod.z.number(),
26893
+ divergedAt: zod.z.number().nullable(),
26894
+ restoredAt: zod.z.number().nullable(),
26895
+ /** A check is only COUNTED when the device has been quiet this long. Motion
26896
+ * during the window DISCARDS the observation — a car pulling up in front of
26897
+ * the bin must not be able to spend hysteresis credit. */
26898
+ quietSeconds: zod.z.number().int().min(0).max(3600).default(60),
26899
+ /** An observation only advances the pending count when it is at least this
26900
+ * far from the previously counted one, so N agreeing checks span real time
26901
+ * rather than N adjacent polls inside one occlusion. */
26902
+ minObservationSpacingSec: zod.z.number().int().min(0).max(3600).default(120),
26903
+ /** Vision-model adjudication of a candidate flip. Similarity primary only. */
26904
+ confirm: SceneConfirmSchema.optional(),
26905
+ /** Whole-frame anchor cosine below which a flip is REFUSED as `view-shifted`. */
26906
+ anchorThreshold: zod.z.number().min(0).max(1).default(SCENE_DEFAULT_ANCHOR_THRESHOLD),
26907
+ /** Clear the latch on its own when the scene matches again? Default false —
26908
+ * `restoredAt` and the `scene-restored` edge are recorded regardless, so an
26909
+ * automation can react to the bin coming back without the operator's own
26910
+ * alarm silently clearing itself. */
26911
+ autoRestore: zod.z.boolean().default(false),
26912
+ /** Named cause when `verdict === 'unknown'`. */
26913
+ unavailable: SceneUnavailableSchema.nullable(),
26914
+ /** Conditions that have at least one comparable reference — the coverage line
26915
+ * ("day ✓ · ir ✓ · dusk ✗") that turns a silent fallback into a visible fact. */
26916
+ coveredConditions: zod.z.array(SceneConditionSchema)
26601
26917
  });
26602
26918
  var SceneMonitorStatusSchema = zod.z.object({
26603
26919
  monitors: zod.z.array(SceneMonitorSchema),
@@ -26610,12 +26926,6 @@ var sceneMonitorCapability = {
26610
26926
  kind: "wrapper",
26611
26927
  defaultActive: true,
26612
26928
  deviceTypes: [DeviceType.Camera],
26613
- deviceConfig: { ui: {
26614
- kind: "widget",
26615
- widgetId: "host/scene-monitor-editor",
26616
- tab: "scenes",
26617
- label: "Scenes"
26618
- } },
26619
26929
  methods: {
26620
26930
  listScenes: method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
26621
26931
  createScene: method(zod.z.object({
@@ -26646,7 +26956,14 @@ var sceneMonitorCapability = {
26646
26956
  "both"
26647
26957
  ]).optional(),
26648
26958
  checkIntervalSec: zod.z.number().optional(),
26649
- check: SceneCheckSchema.optional()
26959
+ check: SceneCheckSchema.optional(),
26960
+ emit: zod.z.enum(["latched", "live"]).optional(),
26961
+ quietSeconds: zod.z.number().int().min(0).max(3600).optional(),
26962
+ minObservationSpacingSec: zod.z.number().int().min(0).max(3600).optional(),
26963
+ anchorThreshold: zod.z.number().min(0).max(1).optional(),
26964
+ autoRestore: zod.z.boolean().optional(),
26965
+ /** `null` clears the vision-model adjudicator. */
26966
+ confirm: SceneConfirmSchema.nullable().optional()
26650
26967
  })
26651
26968
  }), zod.z.void(), {
26652
26969
  kind: "mutation",
@@ -26687,6 +27004,26 @@ var sceneMonitorCapability = {
26687
27004
  }), zod.z.void(), {
26688
27005
  kind: "mutation",
26689
27006
  auth: "admin"
27007
+ }),
27008
+ /**
27009
+ * Clear the latch, re-arm, and — by default — RE-CAPTURE the baseline for
27010
+ * the CURRENT condition. The bin never goes back in exactly the same spot;
27011
+ * "reset" in the operator's head means *this is the new normal*, and
27012
+ * re-capture is what makes the feature self-healing against slow drift
27013
+ * instead of failing silently weeks later.
27014
+ *
27015
+ * Reachable from three surfaces on this one mutation: the scene card, a
27016
+ * notification button (an `onTrigger` sequence with a `kind:'cap'` step —
27017
+ * no new Notification-Center code at all), and tRPC for scripts.
27018
+ */
27019
+ resetScene: method(zod.z.object({
27020
+ deviceId: zod.z.number(),
27021
+ monitorId: zod.z.string(),
27022
+ /** Defaults to TRUE at the provider seam — see `SCENE_RESET_RECAPTURES`. */
27023
+ recapture: zod.z.boolean().optional()
27024
+ }), zod.z.void(), {
27025
+ kind: "mutation",
27026
+ auth: "admin"
26690
27027
  })
26691
27028
  },
26692
27029
  status: {
@@ -27447,6 +27784,61 @@ var NetworkAddressSchema = zod.z.object({
27447
27784
  family: zod.z.string(),
27448
27785
  internal: zod.z.boolean()
27449
27786
  });
27787
+ /**
27788
+ * Provenance of the site coordinates, and the whole reason this is not just two
27789
+ * numbers.
27790
+ *
27791
+ * - `operator-set` — a human typed it, or accepted a detection. Authoritative;
27792
+ * nothing overwrites it.
27793
+ * - `derived-from-ip` — the hub geolocated its own public IP once, because a
27794
+ * default that is right to a few kilometres beats the coarse UTC clock split
27795
+ * the sun-times consumers otherwise fall back to.
27796
+ *
27797
+ * The UI shows which one it is. An operator who cannot tell a guess from their
27798
+ * own input will eventually trust the guess.
27799
+ */
27800
+ var SiteLocationSourceSchema = zod.z.enum(["operator-set", "derived-from-ip"]);
27801
+ /**
27802
+ * Where the installation physically is — a fact of the SITE, not of any addon.
27803
+ *
27804
+ * It used to live in `pipeline-analytics`' global settings, which made a
27805
+ * property of the building a property of one analytics addon. Anything that
27806
+ * needs sun-times (scene condition variants today; anything solar tomorrow)
27807
+ * reads it from here.
27808
+ */
27809
+ var SiteLocationSchema = zod.z.object({
27810
+ /** WGS84 decimal degrees. */
27811
+ latitude: zod.z.number().min(-90).max(90),
27812
+ longitude: zod.z.number().min(-180).max(180),
27813
+ source: SiteLocationSourceSchema,
27814
+ /** Epoch ms the value was last written. */
27815
+ updatedAt: zod.z.number(),
27816
+ /**
27817
+ * Human-readable place the geo-IP service reported ("Napoli, IT"). Display
27818
+ * only — never parsed, never matched on. Absent for an operator-typed value.
27819
+ */
27820
+ label: zod.z.string().optional()
27821
+ });
27822
+ /**
27823
+ * The read shape: the location plus the honest state of the one-shot derivation.
27824
+ *
27825
+ * `derivationAttemptedAt` is what makes the "one call, ever" contract
27826
+ * inspectable. When it is set and `location` is null, the geo-IP lookup ran and
27827
+ * failed; the hub will NOT try again on its own — the fallback is declared
27828
+ * (consumers degrade to their own last resort) and the operator either types the
27829
+ * coordinates or presses detect.
27830
+ */
27831
+ var SiteLocationStatusSchema = zod.z.object({
27832
+ location: SiteLocationSchema.nullable(),
27833
+ derivationAttemptedAt: zod.z.number().nullable(),
27834
+ /** Why the last derivation failed, for the UI to show instead of a shrug. */
27835
+ derivationError: zod.z.string().nullable()
27836
+ });
27837
+ /** `null` clears the location and re-arms nothing — the derivation stays spent. */
27838
+ var SetSiteLocationInputSchema = zod.z.object({
27839
+ latitude: zod.z.number().min(-90).max(90),
27840
+ longitude: zod.z.number().min(-180).max(180)
27841
+ }).nullable();
27450
27842
  var systemCapability = {
27451
27843
  name: "system",
27452
27844
  scope: "system",
@@ -27464,6 +27856,32 @@ var systemCapability = {
27464
27856
  forceRetentionCleanup: method(zod.z.void(), zod.z.void(), {
27465
27857
  kind: "mutation",
27466
27858
  auth: "admin"
27859
+ }),
27860
+ /**
27861
+ * The site coordinates, deriving a default from the hub's public IP on the
27862
+ * FIRST read that finds nothing stored.
27863
+ *
27864
+ * The derivation is one-shot and bounded: one outbound request, a few
27865
+ * seconds, its outcome persisted either way. A hub with no internet pays it
27866
+ * once and never again, and neither boot nor any consumer is blocked on it —
27867
+ * the caller gets `location: null` and degrades exactly as it did before this
27868
+ * method existed.
27869
+ */
27870
+ getSiteLocation: method(zod.z.void(), SiteLocationStatusSchema),
27871
+ /** Operator input. Always lands as `source: 'operator-set'`. */
27872
+ setSiteLocation: method(SetSiteLocationInputSchema, SiteLocationStatusSchema, {
27873
+ kind: "mutation",
27874
+ auth: "admin"
27875
+ }),
27876
+ /**
27877
+ * Re-run the geo-IP derivation now. The ONLY way a spent or failed
27878
+ * derivation is retried — there is no timer, and no read path retries.
27879
+ * Overwrites an existing `derived-from-ip` value; refuses to clobber an
27880
+ * `operator-set` one.
27881
+ */
27882
+ detectSiteLocation: method(zod.z.void(), SiteLocationStatusSchema, {
27883
+ kind: "mutation",
27884
+ auth: "admin"
27467
27885
  })
27468
27886
  },
27469
27887
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
@@ -31199,6 +31617,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
31199
31617
  addonId: null,
31200
31618
  access: "create"
31201
31619
  },
31620
+ "llm.cancel": {
31621
+ capName: "llm",
31622
+ capScope: "system",
31623
+ addonId: null,
31624
+ access: "create"
31625
+ },
31202
31626
  "llm.deleteModel": {
31203
31627
  capName: "llm",
31204
31628
  capScope: "system",
@@ -33449,6 +33873,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
33449
33873
  addonId: null,
33450
33874
  access: "create"
33451
33875
  },
33876
+ "sceneMonitor.resetScene": {
33877
+ capName: "scene-monitor",
33878
+ capScope: "device",
33879
+ addonId: null,
33880
+ access: "delete"
33881
+ },
33452
33882
  "sceneMonitor.updateScene": {
33453
33883
  capName: "scene-monitor",
33454
33884
  capScope: "device",
@@ -34127,6 +34557,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
34127
34557
  addonId: null,
34128
34558
  access: "create"
34129
34559
  },
34560
+ "system.detectSiteLocation": {
34561
+ capName: "system",
34562
+ capScope: "system",
34563
+ addonId: null,
34564
+ access: "create"
34565
+ },
34130
34566
  "system.featureFlags": {
34131
34567
  capName: "system",
34132
34568
  capScope: "system",
@@ -34145,6 +34581,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
34145
34581
  addonId: null,
34146
34582
  access: "view"
34147
34583
  },
34584
+ "system.getSiteLocation": {
34585
+ capName: "system",
34586
+ capScope: "system",
34587
+ addonId: null,
34588
+ access: "view"
34589
+ },
34148
34590
  "system.health": {
34149
34591
  capName: "system",
34150
34592
  capScope: "system",
@@ -34169,6 +34611,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
34169
34611
  addonId: null,
34170
34612
  access: "create"
34171
34613
  },
34614
+ "system.setSiteLocation": {
34615
+ capName: "system",
34616
+ capScope: "system",
34617
+ addonId: null,
34618
+ access: "create"
34619
+ },
34172
34620
  "terminalSession.adoptLegacyMonitor": {
34173
34621
  capName: "terminal-session",
34174
34622
  capScope: "system",
@@ -36131,6 +36579,11 @@ Object.freeze({
36131
36579
  form: "single",
36132
36580
  optional: false
36133
36581
  }],
36582
+ "sceneMonitor.resetScene": [{
36583
+ name: "deviceId",
36584
+ form: "single",
36585
+ optional: false
36586
+ }],
36134
36587
  "sceneMonitor.updateScene": [{
36135
36588
  name: "deviceId",
36136
36589
  form: "single",