@camstack/addon-export-hap 1.2.4 → 1.2.5

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.
@@ -10,7 +10,7 @@ import { networkInterfaces } from "node:os";
10
10
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
11
11
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
12
12
  //#endregion
13
- //#region ../types/dist/event-category-D4HJq7Mw.mjs
13
+ //#region ../types/dist/event-category-BLcNejAE.mjs
14
14
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  EventCategory["SystemBoot"] = "system.boot";
16
16
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -160,9 +160,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
160
160
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
161
161
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
162
162
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
163
- /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
164
- * thumb is a scrub gap the recorder's keyframe backfill covers. */
165
- EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
166
163
  /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
167
164
  * progress bar the client reconciles via `recordingExport.getExport`. */
168
165
  EventCategory["RecordingExportProgress"] = "recording.export.progress";
@@ -6827,7 +6824,6 @@ object({ deviceId: number() }), object({ deviceId: number() }), object({
6827
6824
  patch: record(string(), unknown())
6828
6825
  }), object({ success: literal(true) });
6829
6826
  object({ deviceId: number() }), unknown().nullable();
6830
- /** Shorthand to define a method schema */
6831
6827
  function method(input, output, options) {
6832
6828
  return {
6833
6829
  input,
@@ -6835,6 +6831,7 @@ function method(input, output, options) {
6835
6831
  kind: options?.kind ?? "query",
6836
6832
  auth: options?.auth ?? "protected",
6837
6833
  ...options?.access !== void 0 ? { access: options.access } : {},
6834
+ ...options?.caller !== void 0 ? { caller: options.caller } : {},
6838
6835
  timeoutMs: options?.timeoutMs
6839
6836
  };
6840
6837
  }
@@ -8242,6 +8239,59 @@ for (const l of AUDIO_MACRO_LABELS) {
8242
8239
  /** The complete taxonomy dictionary, keyed by kind. */
8243
8240
  var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8244
8241
  /**
8242
+ * Notification-Center taxonomy — the fixed vocabulary the NC rule editor
8243
+ * offers as pickers instead of free text. Derived (never hand-listed) from the
8244
+ * single `EVENT_TAXONOMY` dictionary so it stays in lockstep with every other
8245
+ * taxonomy surface (timeline, filters, event page).
8246
+ *
8247
+ * Three buckets, mapped onto the rule editor's `stringList` conditions:
8248
+ * - `videoClasses` → detection classes (person / vehicle / animal + subs)
8249
+ * for the `classes` / `classesExclude` conditions.
8250
+ * - `audioKinds` → audio-analyzer sub kinds (`audio-scream`, …) shown in
8251
+ * the same class picker, grouped under an Audio header.
8252
+ * - `labels` → sensor + control taxonomy kinds (doorbell / contact /
8253
+ * lock / …) for the `sensorKinds` device-event condition.
8254
+ *
8255
+ * Each entry carries `parentKind` so the client can group video subs under
8256
+ * their macro and sensor/control kinds under their category. This surface is
8257
+ * served ADDITIVELY on the `nc.getConditionCatalog` bridge response — no cap
8258
+ * method, no codegen — so it ships train-free with an addon deploy.
8259
+ */
8260
+ /** One selectable taxonomy value: a stable kind id + display label + parent. */
8261
+ var NcTaxonomyEntrySchema = object({
8262
+ /** Stable kind id (e.g. 'person', 'car', 'audio-scream', 'doorbell'). */
8263
+ kind: string(),
8264
+ /** English fallback label (the UI translates via the event-kind i18n key). */
8265
+ label: string(),
8266
+ /** Macro/category parent for grouping ('car' → 'vehicle'); null for a top. */
8267
+ parentKind: string().nullable()
8268
+ });
8269
+ object({
8270
+ videoClasses: array(NcTaxonomyEntrySchema),
8271
+ audioKinds: array(NcTaxonomyEntrySchema),
8272
+ labels: array(NcTaxonomyEntrySchema)
8273
+ });
8274
+ function toEntry(kind, label, parentKind) {
8275
+ return {
8276
+ kind,
8277
+ label,
8278
+ parentKind
8279
+ };
8280
+ }
8281
+ /**
8282
+ * Build the NC taxonomy from `EVENT_TAXONOMY`. Insertion order is preserved
8283
+ * (macros before their subs), which the client relies on for stable grouping.
8284
+ */
8285
+ function buildNcTaxonomy() {
8286
+ const all = Object.values(EVENT_TAXONOMY);
8287
+ return {
8288
+ videoClasses: all.filter((e) => e.category === "detection").map((e) => toEntry(e.kind, e.label, e.parentKind)),
8289
+ audioKinds: all.filter((e) => e.category === "audio" && e.level === "sub").map((e) => toEntry(e.kind, e.label, e.parentKind)),
8290
+ labels: all.filter((e) => e.category === "sensor" || e.category === "control").map((e) => toEntry(e.kind, e.label, e.parentKind))
8291
+ };
8292
+ }
8293
+ Object.freeze(buildNcTaxonomy());
8294
+ /**
8245
8295
  * Error types for the safe expression engine. Two distinct classes so callers
8246
8296
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8247
8297
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -10950,6 +11000,22 @@ var CameraMetricsSchema = object({
10950
11000
  ])
10951
11001
  });
10952
11002
  var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
11003
+ /**
11004
+ * Reference to the frame's retained NATIVE surface + the parent crop's placement
11005
+ * within the frame, so the executor can re-cut a leaf child ROI at native
11006
+ * resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
11007
+ */
11008
+ var NativeCropRefSchema = object({
11009
+ /** Handle keying the retained native surface (node-pinned to its owner). */
11010
+ handle: FrameHandleSchema,
11011
+ /** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
11012
+ cropFrameSpace: object({
11013
+ x: number(),
11014
+ y: number(),
11015
+ w: number(),
11016
+ h: number()
11017
+ })
11018
+ });
10953
11019
  var ModelFormatSchema$1 = _enum([
10954
11020
  "onnx",
10955
11021
  "coreml",
@@ -11225,7 +11291,22 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11225
11291
  * Omitted ⇒ the runner's default device (current single-engine
11226
11292
  * behaviour). Selects WHICH device pool of the node runs the call.
11227
11293
  */
11228
- deviceKey: string().optional()
11294
+ deviceKey: string().optional(),
11295
+ /**
11296
+ * Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
11297
+ * when the parent crop was resolved from the frame's retained NATIVE
11298
+ * surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
11299
+ * child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
11300
+ * resolution from that surface — the SAME quality path faces already
11301
+ * had — instead of the downscaled parent tile. `handle` keys the native
11302
+ * surface (node-pinned to its owner); `cropFrameSpace` is the parent
11303
+ * crop's padded/clamped rectangle in FRAME-space pixels, used to compose
11304
+ * the executor's crop-normalized child ROI back into frame-normalized
11305
+ * coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
11306
+ * of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
11307
+ * (today's behaviour on the fallback path).
11308
+ */
11309
+ nativeCropRef: NativeCropRefSchema.optional()
11229
11310
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
11230
11311
  engine: PipelineEngineChoiceSchema.optional(),
11231
11312
  steps: array(PipelineStepInputSchema).min(1),
@@ -11441,7 +11522,11 @@ var DetailResultSchema = object({
11441
11522
  bbox: NativeCropBboxSchema.optional(),
11442
11523
  embedding: string().optional(),
11443
11524
  label: string().optional(),
11444
- alignedCropJpeg: string().optional()
11525
+ alignedCropJpeg: string().optional(),
11526
+ /** Face short side (px) measured on the NATIVE crop surface. The `bbox`
11527
+ * above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
11528
+ * consumers MUST prefer this when present (2026-07-22 native-gate fix). */
11529
+ nativeFaceShortSidePx: number().optional()
11445
11530
  });
11446
11531
  /**
11447
11532
  * Per-camera tunable ranges + defaults. Single source of truth used
@@ -11455,6 +11540,12 @@ var motionCooldownMsField = {
11455
11540
  default: 3e4,
11456
11541
  step: 500
11457
11542
  };
11543
+ var maxSessionHoldMsField = {
11544
+ min: 0,
11545
+ max: 6e5,
11546
+ default: 12e4,
11547
+ step: 5e3
11548
+ };
11458
11549
  var motionFpsField = {
11459
11550
  min: 1,
11460
11551
  max: 30,
@@ -11602,6 +11693,19 @@ var RunnerCameraConfigSchema = object({
11602
11693
  "on-motion"
11603
11694
  ]).default("always-on"),
11604
11695
  motionCooldownMs: number().min(motionCooldownMsField.min).default(motionCooldownMsField.default),
11696
+ /**
11697
+ * Orchestrator-side on-motion session-hold cap (ms). While an on-motion
11698
+ * detection session is active and ≥1 confirmed non-stationary track is
11699
+ * still live, the orchestrator keeps the session open past
11700
+ * `motionCooldownMs` (a slowly-moving subject can stop re-triggering the
11701
+ * camera's VMD yet is still being tracked frame-to-frame) — up to this many
11702
+ * ms since the session opened, after which it closes regardless. `0`
11703
+ * disables the hold (legacy cooldown-only teardown). Not consumed by the
11704
+ * runner itself — carried here so it shares the per-camera device-settings
11705
+ * surface with `motionCooldownMs`; the orchestrator reads it off the
11706
+ * resolved `CameraDetectionConfig`.
11707
+ */
11708
+ maxSessionHoldMs: number().min(maxSessionHoldMsField.min).max(maxSessionHoldMsField.max).optional(),
11605
11709
  motionFps: number().min(motionFpsField.min).max(motionFpsField.max).default(motionFpsField.default),
11606
11710
  detectionFps: number().min(detectionFpsField.min).max(detectionFpsField.max).default(detectionFpsField.default),
11607
11711
  motionStreamId: string(),
@@ -11691,7 +11795,7 @@ var RunnerCameraConfigSchema = object({
11691
11795
  */
11692
11796
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
11693
11797
  });
11694
- motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
11798
+ motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
11695
11799
  /**
11696
11800
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
11697
11801
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -13545,94 +13649,6 @@ var EnrichedWidgetMetadataSchema = WidgetMetadataSchema.extend({
13545
13649
  bundleUrl: string()
13546
13650
  });
13547
13651
  method(_void(), array(EnrichedWidgetMetadataSchema).readonly());
13548
- var NotificationRuleConditionsSchema = object({
13549
- deviceIds: array(number()).readonly().optional(),
13550
- classNames: array(string()).readonly().optional(),
13551
- zoneIds: array(string()).readonly().optional(),
13552
- minConfidence: number().optional(),
13553
- source: _enum([
13554
- "pipeline",
13555
- "onboard",
13556
- "any"
13557
- ]).optional(),
13558
- schedule: object({
13559
- days: array(number()).readonly(),
13560
- startHour: number(),
13561
- endHour: number()
13562
- }).optional(),
13563
- cooldownSeconds: number().optional(),
13564
- minDwellSeconds: number().optional(),
13565
- /** Match against `event.data.eventType` token (e.g. `'press_long'`). When non-empty, only events
13566
- * carrying a matching `data.eventType` string pass this condition. Rules without this field are
13567
- * unaffected (back-compat). Distinct from `rule.eventTypes` which holds EventCategory strings. */
13568
- eventTypeTokens: array(string()).readonly().optional(),
13569
- /** Match detections whose CLIP image embedding is semantically similar to this free-text
13570
- * description. Requires the embedding-encoder cap to have pre-warmed the text vector.
13571
- * `minSimilarity` is the cosine similarity threshold in [0, 1]. */
13572
- clipDescription: object({
13573
- text: string().min(1),
13574
- minSimilarity: number().min(0).max(1)
13575
- }).optional(),
13576
- /** Match events whose recognized-entity label (face identity name or plate
13577
- * vehicle name, propagated onto `event.data.label`) is one of these values.
13578
- * Empty/absent → unaffected (back-compat). Enables "notify me when <named
13579
- * vehicle/person> is seen". */
13580
- labels: array(string()).readonly().optional()
13581
- });
13582
- var NotificationRuleTemplateSchema = object({
13583
- title: string(),
13584
- body: string(),
13585
- imageMode: _enum([
13586
- "crop",
13587
- "annotated",
13588
- "full",
13589
- "none"
13590
- ])
13591
- });
13592
- var NotificationRuleSchema = object({
13593
- id: string(),
13594
- name: string(),
13595
- enabled: boolean(),
13596
- eventTypes: array(string()).readonly(),
13597
- conditions: NotificationRuleConditionsSchema,
13598
- outputs: array(string()).readonly(),
13599
- template: NotificationRuleTemplateSchema.optional(),
13600
- priority: _enum([
13601
- "low",
13602
- "normal",
13603
- "high",
13604
- "critical"
13605
- ])
13606
- });
13607
- var NotificationTestResultSchema = object({
13608
- ruleId: string(),
13609
- eventId: string(),
13610
- timestamp: number(),
13611
- wouldFire: boolean(),
13612
- reason: string().optional()
13613
- });
13614
- var NotificationHistoryEntrySchema = object({
13615
- id: string(),
13616
- ruleId: string(),
13617
- ruleName: string(),
13618
- eventId: string(),
13619
- timestamp: number(),
13620
- outputs: array(string()).readonly(),
13621
- success: boolean(),
13622
- error: string().optional(),
13623
- deviceId: number().optional()
13624
- });
13625
- var NotificationHistoryFilterSchema = object({
13626
- ruleId: string().optional(),
13627
- deviceId: number().optional(),
13628
- from: number().optional(),
13629
- to: number().optional(),
13630
- limit: number().optional()
13631
- });
13632
- method(_void(), object({ rules: array(NotificationRuleSchema).readonly() })), method(object({ rule: NotificationRuleSchema }), object({ success: literal(true) }), { kind: "mutation" }), method(object({ ruleId: string() }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
13633
- ruleId: string(),
13634
- lookbackMinutes: number()
13635
- }), object({ results: array(NotificationTestResultSchema).readonly() }), { kind: "mutation" }), method(object({ filter: NotificationHistoryFilterSchema.optional() }), object({ entries: array(NotificationHistoryEntrySchema).readonly() }));
13636
13652
  /**
13637
13653
  * Alerts capability — collection-based internal alert system.
13638
13654
  *
@@ -13819,89 +13835,6 @@ method(object({
13819
13835
  password: string()
13820
13836
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
13821
13837
  /**
13822
- * `login-method` — collection cap through which auth addons contribute
13823
- * their pre-auth login surfaces to the login page. This is the SINGLE,
13824
- * generic mechanism that supersedes the dead `auth.listProviders` reader:
13825
- * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
13826
- * `login-method` provider and the PUBLIC `auth.listLoginMethods`
13827
- * procedure aggregates them for the unauthenticated login page.
13828
- *
13829
- * A contribution is a discriminated union on `kind`:
13830
- *
13831
- * - `redirect` — a declarative button. The login page renders a generic
13832
- * button that navigates to `startUrl` (an addon-owned HTTP route).
13833
- * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
13834
- * ZERO shell-side JS. A future SSO addon plugs in the same way — the
13835
- * login page needs NO change.
13836
- *
13837
- * - `widget` — a Module-Federation widget the login page mounts (via
13838
- * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
13839
- * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
13840
- * mechanism kept for future use; no shipped addon uses it on the login
13841
- * page (the passkey ceremony below runs natively in the shell instead).
13842
- *
13843
- * - `passkey` — a declarative WebAuthn ceremony the shell renders
13844
- * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
13845
- * a remotely-loaded bundle). Carries the addon's effective `rpId` /
13846
- * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
13847
- * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
13848
- * fetching any remote code pre-auth. Contribution stays unconditional —
13849
- * enrollment state is never leaked pre-auth; visibility is a shell
13850
- * decision.
13851
- *
13852
- * Every contribution carries a `stage`:
13853
- * - `primary` — shown on the first credentials screen (OIDC /
13854
- * magic-link buttons; a future usernameless passkey).
13855
- * - `second-factor` — shown AFTER the password leg, gated on the
13856
- * returned `factors` (passkey-as-2FA today).
13857
- *
13858
- * `mount: skip` — the cap is read server-side by the core auth router
13859
- * (`registry.getCollection('login-method')`), never mounted as its own
13860
- * tRPC router.
13861
- */
13862
- /** When a login method renders in the two-phase login flow. */
13863
- var LoginStageEnum = _enum(["primary", "second-factor"]);
13864
- /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
13865
- var LoginMethodContributionSchema = discriminatedUnion("kind", [
13866
- object({
13867
- kind: literal("redirect"),
13868
- /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13869
- id: string(),
13870
- /** Operator-facing button label. */
13871
- label: string(),
13872
- /** lucide-react icon name. */
13873
- icon: string().optional(),
13874
- /** Addon-owned HTTP route the button navigates to (GET). */
13875
- startUrl: string(),
13876
- stage: LoginStageEnum
13877
- }),
13878
- object({
13879
- kind: literal("widget"),
13880
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13881
- id: string(),
13882
- /** Owning addon id — drives the public bundle URL + the MF namespace. */
13883
- addonId: string(),
13884
- /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13885
- bundle: string(),
13886
- /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13887
- remote: WidgetRemoteSchema,
13888
- stage: LoginStageEnum
13889
- }),
13890
- object({
13891
- kind: literal("passkey"),
13892
- /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
13893
- id: string(),
13894
- /** Operator-facing button label. */
13895
- label: string(),
13896
- stage: LoginStageEnum,
13897
- /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
13898
- rpId: string(),
13899
- /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
13900
- origin: string().nullable()
13901
- })
13902
- ]);
13903
- method(_void(), array(LoginMethodContributionSchema).readonly());
13904
- /**
13905
13838
  * Orchestrator-side destination metadata. The orchestrator computes
13906
13839
  * `id = <addonId>:<subId>` from its provider lookup so consumers
13907
13840
  * (admin UI, restore flow) see one canonical key.
@@ -15270,48 +15203,423 @@ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({
15270
15203
  kind: "mutation",
15271
15204
  auth: "admin"
15272
15205
  });
15273
- var LogLevelSchema = _enum([
15274
- "debug",
15275
- "info",
15276
- "warn",
15277
- "error"
15206
+ /**
15207
+ * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15208
+ * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15209
+ * caps stay wire-compatible without a circular cap→cap import.
15210
+ *
15211
+ * Errors are a discriminated-union RESULT, never thrown: the shape survives
15212
+ * every transport tier structurally, and failed calls still write usage rows.
15213
+ * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15214
+ */
15215
+ var LlmUsageSchema = object({
15216
+ inputTokens: number(),
15217
+ outputTokens: number()
15218
+ });
15219
+ var LlmErrorCodeSchema = _enum([
15220
+ "timeout",
15221
+ "rate-limited",
15222
+ "auth",
15223
+ "refusal",
15224
+ "bad-request",
15225
+ "unavailable",
15226
+ "no-profile",
15227
+ "budget-exceeded",
15228
+ "adapter-error"
15278
15229
  ]);
15279
- var LogEntrySchema = object({
15280
- timestamp: date(),
15281
- level: LogLevelSchema,
15282
- scope: array(string()),
15230
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15231
+ ok: literal(true),
15232
+ text: string(),
15233
+ model: string(),
15234
+ usage: LlmUsageSchema,
15235
+ truncated: boolean(),
15236
+ latencyMs: number()
15237
+ }), object({
15238
+ ok: literal(false),
15239
+ code: LlmErrorCodeSchema,
15283
15240
  message: string(),
15284
- meta: record(string(), unknown()).optional(),
15285
- tags: record(string(), string()).optional()
15286
- });
15287
- method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
15288
- scope: array(string()).optional(),
15289
- level: LogLevelSchema.optional(),
15290
- since: date().optional(),
15291
- until: date().optional(),
15292
- limit: number().optional(),
15293
- tags: record(string(), string()).optional()
15294
- }), array(LogEntrySchema).readonly());
15295
- var CpuBreakdownSchema = object({
15296
- total: number(),
15297
- user: number(),
15298
- system: number(),
15299
- irq: number(),
15300
- nice: number(),
15301
- loadAvg: tuple([
15302
- number(),
15303
- number(),
15304
- number()
15305
- ]),
15306
- cores: number()
15241
+ retryAfterMs: number().optional()
15242
+ })]);
15243
+ /**
15244
+ * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
15245
+ * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15246
+ * notification-output.cap.ts:27-31 precedents).
15247
+ */
15248
+ var LlmImageSchema = object({
15249
+ bytes: _instanceof(Uint8Array),
15250
+ mimeType: string()
15307
15251
  });
15308
- var MemoryInfoSchema = object({
15309
- percent: number(),
15310
- totalBytes: number(),
15311
- usedBytes: number(),
15312
- availableBytes: number(),
15313
- swapUsedBytes: number(),
15314
- swapTotalBytes: number()
15252
+ var LlmGenerateBaseInputSchema = object({
15253
+ /** Collection routing (the notification-output posture). */
15254
+ addonId: string().optional(),
15255
+ /** Explicit profile; else the resolution chain (spec §3). */
15256
+ profileId: string().optional(),
15257
+ /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15258
+ consumer: string(),
15259
+ system: string().optional(),
15260
+ /** v1: single-turn. `messages[]` is a v2 additive field. */
15261
+ prompt: string(),
15262
+ /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15263
+ jsonSchema: record(string(), unknown()).optional(),
15264
+ /** Per-call override of the profile default. */
15265
+ maxTokens: number().int().positive().optional(),
15266
+ temperature: number().optional()
15267
+ });
15268
+ /**
15269
+ * `llm-runtime` — node-side managed llama.cpp executor (spec §4). Registered
15270
+ * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15271
+ * a specific node's runtime with `nodePin(profile.runtime.nodeId)` — normal
15272
+ * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15273
+ * this only through the `llm` cap's methods.
15274
+ *
15275
+ * One running llama-server child per node in v1 (models are RAM-heavy).
15276
+ * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15277
+ * watchdog — operator decision #3).
15278
+ */
15279
+ var ManagedModelRefSchema = discriminatedUnion("kind", [
15280
+ object({
15281
+ kind: literal("catalog"),
15282
+ catalogId: string()
15283
+ }),
15284
+ object({
15285
+ kind: literal("url"),
15286
+ url: string(),
15287
+ sha256: string().optional()
15288
+ }),
15289
+ object({
15290
+ kind: literal("path"),
15291
+ path: string()
15292
+ })
15293
+ ]);
15294
+ var ManagedRuntimeConfigSchema = object({
15295
+ /** WHERE the runtime lives — hub or any agent. */
15296
+ nodeId: string(),
15297
+ /** Closed for v1; 'ollama' is a v2 candidate. */
15298
+ engine: _enum(["llama-cpp"]),
15299
+ model: ManagedModelRefSchema,
15300
+ contextSize: number().int().default(4096),
15301
+ /** 0 = CPU-only. */
15302
+ gpuLayers: number().int().default(0),
15303
+ /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15304
+ threads: number().int().optional(),
15305
+ /** Concurrent slots. */
15306
+ parallel: number().int().default(1),
15307
+ /** Else lazy: first generate boots it. */
15308
+ autoStart: boolean().default(false),
15309
+ /** 0 = never; frees RAM after quiet periods. */
15310
+ idleStopMinutes: number().int().default(30)
15311
+ });
15312
+ var LlmRuntimeStatusSchema = object({
15313
+ /** Status is ALWAYS node-qualified. */
15314
+ nodeId: string(),
15315
+ state: _enum([
15316
+ "stopped",
15317
+ "downloading",
15318
+ "starting",
15319
+ "ready",
15320
+ "crashed",
15321
+ "failed"
15322
+ ]),
15323
+ pid: number().optional(),
15324
+ port: number().optional(),
15325
+ modelPath: string().optional(),
15326
+ modelId: string().optional(),
15327
+ downloadProgress: number().min(0).max(1).optional(),
15328
+ lastError: string().optional(),
15329
+ crashesInWindow: number(),
15330
+ /** Child RSS (sampled best-effort). */
15331
+ memoryBytes: number().optional(),
15332
+ vramBytes: number().optional()
15333
+ });
15334
+ var LlmNodeModelSchema = object({
15335
+ file: string(),
15336
+ sizeBytes: number(),
15337
+ catalogId: string().optional(),
15338
+ installedAt: number().optional()
15339
+ });
15340
+ var LlmRuntimeDiskUsageSchema = object({
15341
+ nodeId: string(),
15342
+ modelsBytes: number(),
15343
+ freeBytes: number().optional()
15344
+ });
15345
+ method(LlmGenerateBaseInputSchema.extend({
15346
+ images: array(LlmImageSchema).optional(),
15347
+ runtime: ManagedRuntimeConfigSchema,
15348
+ /** The managed profile's timeout, threaded by the hub provider. */
15349
+ timeoutMs: number().int().positive().optional()
15350
+ }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15351
+ kind: "mutation",
15352
+ auth: "admin"
15353
+ }), method(object({}), _void(), {
15354
+ kind: "mutation",
15355
+ auth: "admin"
15356
+ }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15357
+ kind: "mutation",
15358
+ auth: "admin"
15359
+ }), method(object({ file: string() }), _void(), {
15360
+ kind: "mutation",
15361
+ auth: "admin"
15362
+ }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15363
+ /**
15364
+ * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15365
+ * methods concat-fan across providers; single-row methods route to ONE
15366
+ * provider by the `addonId` in the call input (the notification-output
15367
+ * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15368
+ * (hub-placed); the cap stays open for future providers.
15369
+ *
15370
+ * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15371
+ * `apiKey` is a password field — providers REDACT it on read and merge on
15372
+ * write; a stored key NEVER round-trips to a client.
15373
+ */
15374
+ var LlmProfileKindSchema = _enum([
15375
+ "openai-compatible",
15376
+ "openai",
15377
+ "anthropic",
15378
+ "google",
15379
+ "managed-local"
15380
+ ]);
15381
+ var LlmProfileSchema = object({
15382
+ id: string(),
15383
+ name: string(),
15384
+ kind: LlmProfileKindSchema,
15385
+ /** Stamped by the provider — keeps the fanned catalog routable. */
15386
+ addonId: string(),
15387
+ enabled: boolean(),
15388
+ /** Vendor model id, or the managed runtime's loaded model. */
15389
+ model: string(),
15390
+ /** Required for openai-compatible; override for cloud kinds. */
15391
+ baseUrl: string().optional(),
15392
+ /** ConfigUISchema type:'password' — never round-trips (spec §5). */
15393
+ apiKey: string().optional(),
15394
+ supportsVision: boolean(),
15395
+ temperature: number().min(0).max(2).optional(),
15396
+ maxTokens: number().int().positive().optional(),
15397
+ timeoutMs: number().int().positive().default(6e4),
15398
+ extraHeaders: record(string(), string()).optional(),
15399
+ /** kind === 'managed-local' only (spec §4). */
15400
+ runtime: ManagedRuntimeConfigSchema.optional()
15401
+ });
15402
+ /** ConfigUISchema tree passed through untyped on the wire (the
15403
+ * notification-output `ConfigSchemaPassthrough` precedent at
15404
+ * notification-output.cap.ts:151); the exported TS type re-tightens it. */
15405
+ var ConfigSchemaPassthrough$1 = unknown();
15406
+ var LlmProfileKindDescriptorSchema = object({
15407
+ kind: LlmProfileKindSchema,
15408
+ label: string(),
15409
+ icon: string(),
15410
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
15411
+ addonId: string(),
15412
+ configSchema: ConfigSchemaPassthrough$1
15413
+ });
15414
+ var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
15415
+ var LlmDefaultSchema = object({
15416
+ selector: LlmDefaultSelectorSchema,
15417
+ profileId: string()
15418
+ });
15419
+ /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
15420
+ var LlmUsageRollupSchema = object({
15421
+ day: string(),
15422
+ consumer: string(),
15423
+ profileId: string(),
15424
+ calls: number(),
15425
+ okCalls: number(),
15426
+ errorCalls: number(),
15427
+ inputTokens: number(),
15428
+ outputTokens: number(),
15429
+ avgLatencyMs: number()
15430
+ });
15431
+ /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
15432
+ var ManagedModelCatalogEntrySchema = object({
15433
+ id: string(),
15434
+ label: string(),
15435
+ family: string(),
15436
+ purpose: _enum(["text", "vision"]),
15437
+ url: string(),
15438
+ sha256: string(),
15439
+ sizeBytes: number(),
15440
+ quantization: string(),
15441
+ /** Load-time guidance shown in the picker. */
15442
+ minRamBytes: number(),
15443
+ contextSizeDefault: number().int(),
15444
+ /** Vision models: companion projector file. */
15445
+ mmprojUrl: string().optional()
15446
+ });
15447
+ var LlmRuntimeNodeSchema = object({
15448
+ nodeId: string(),
15449
+ reachable: boolean(),
15450
+ status: LlmRuntimeStatusSchema.optional(),
15451
+ disk: LlmRuntimeDiskUsageSchema.optional(),
15452
+ error: string().optional()
15453
+ });
15454
+ var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15455
+ var ProfileRefInputSchema = object({
15456
+ addonId: string(),
15457
+ profileId: string()
15458
+ });
15459
+ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
15460
+ kind: "mutation",
15461
+ auth: "admin"
15462
+ }), method(ProfileRefInputSchema, _void(), {
15463
+ kind: "mutation",
15464
+ auth: "admin"
15465
+ }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
15466
+ kind: "mutation",
15467
+ auth: "admin"
15468
+ }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
15469
+ selector: LlmDefaultSelectorSchema,
15470
+ profileId: string().nullable()
15471
+ }), _void(), {
15472
+ kind: "mutation",
15473
+ auth: "admin"
15474
+ }), method(object({
15475
+ since: number().optional(),
15476
+ until: number().optional(),
15477
+ consumer: string().optional(),
15478
+ profileId: string().optional()
15479
+ }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
15480
+ nodeId: string(),
15481
+ model: ManagedModelRefSchema
15482
+ }), _void(), {
15483
+ kind: "mutation",
15484
+ auth: "admin"
15485
+ }), method(object({
15486
+ nodeId: string(),
15487
+ file: string()
15488
+ }), _void(), {
15489
+ kind: "mutation",
15490
+ auth: "admin"
15491
+ }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
15492
+ kind: "mutation",
15493
+ auth: "admin"
15494
+ }), method(ProfileRefInputSchema, _void(), {
15495
+ kind: "mutation",
15496
+ auth: "admin"
15497
+ });
15498
+ var LogLevelSchema = _enum([
15499
+ "debug",
15500
+ "info",
15501
+ "warn",
15502
+ "error"
15503
+ ]);
15504
+ var LogEntrySchema = object({
15505
+ timestamp: date(),
15506
+ level: LogLevelSchema,
15507
+ scope: array(string()),
15508
+ message: string(),
15509
+ meta: record(string(), unknown()).optional(),
15510
+ tags: record(string(), string()).optional()
15511
+ });
15512
+ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
15513
+ scope: array(string()).optional(),
15514
+ level: LogLevelSchema.optional(),
15515
+ since: date().optional(),
15516
+ until: date().optional(),
15517
+ limit: number().optional(),
15518
+ tags: record(string(), string()).optional()
15519
+ }), array(LogEntrySchema).readonly());
15520
+ /**
15521
+ * `login-method` — collection cap through which auth addons contribute
15522
+ * their pre-auth login surfaces to the login page. This is the SINGLE,
15523
+ * generic mechanism that supersedes the dead `auth.listProviders` reader:
15524
+ * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
15525
+ * `login-method` provider and the PUBLIC `auth.listLoginMethods`
15526
+ * procedure aggregates them for the unauthenticated login page.
15527
+ *
15528
+ * A contribution is a discriminated union on `kind`:
15529
+ *
15530
+ * - `redirect` — a declarative button. The login page renders a generic
15531
+ * button that navigates to `startUrl` (an addon-owned HTTP route).
15532
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
15533
+ * ZERO shell-side JS. A future SSO addon plugs in the same way — the
15534
+ * login page needs NO change.
15535
+ *
15536
+ * - `widget` — a Module-Federation widget the login page mounts (via
15537
+ * `loadRemoteBundle`) for an in-page ceremony. `auth.listLoginMethods`
15538
+ * stamps a public `bundleUrl` from `addonId` + `bundle`. Generic
15539
+ * mechanism kept for future use; no shipped addon uses it on the login
15540
+ * page (the passkey ceremony below runs natively in the shell instead).
15541
+ *
15542
+ * - `passkey` — a declarative WebAuthn ceremony the shell renders
15543
+ * natively (`@simplewebauthn/browser` lives in `addon-admin-ui`, not in
15544
+ * a remotely-loaded bundle). Carries the addon's effective `rpId` /
15545
+ * `origin` (from its `resolveRpID()` / `resolveOrigin()`) so the shell
15546
+ * can gate visibility (IP-literal origin, hostname/rpId mismatch) WITHOUT
15547
+ * fetching any remote code pre-auth. Contribution stays unconditional —
15548
+ * enrollment state is never leaked pre-auth; visibility is a shell
15549
+ * decision.
15550
+ *
15551
+ * Every contribution carries a `stage`:
15552
+ * - `primary` — shown on the first credentials screen (OIDC /
15553
+ * magic-link buttons; a future usernameless passkey).
15554
+ * - `second-factor` — shown AFTER the password leg, gated on the
15555
+ * returned `factors` (passkey-as-2FA today).
15556
+ *
15557
+ * `mount: skip` — the cap is read server-side by the core auth router
15558
+ * (`registry.getCollection('login-method')`), never mounted as its own
15559
+ * tRPC router.
15560
+ */
15561
+ /** When a login method renders in the two-phase login flow. */
15562
+ var LoginStageEnum = _enum(["primary", "second-factor"]);
15563
+ /** One login-method contribution — redirect button, pre-auth widget, or native passkey ceremony. */
15564
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [
15565
+ object({
15566
+ kind: literal("redirect"),
15567
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
15568
+ id: string(),
15569
+ /** Operator-facing button label. */
15570
+ label: string(),
15571
+ /** lucide-react icon name. */
15572
+ icon: string().optional(),
15573
+ /** Addon-owned HTTP route the button navigates to (GET). */
15574
+ startUrl: string(),
15575
+ stage: LoginStageEnum
15576
+ }),
15577
+ object({
15578
+ kind: literal("widget"),
15579
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
15580
+ id: string(),
15581
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
15582
+ addonId: string(),
15583
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
15584
+ bundle: string(),
15585
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
15586
+ remote: WidgetRemoteSchema,
15587
+ stage: LoginStageEnum
15588
+ }),
15589
+ object({
15590
+ kind: literal("passkey"),
15591
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-direct-login`). */
15592
+ id: string(),
15593
+ /** Operator-facing button label. */
15594
+ label: string(),
15595
+ stage: LoginStageEnum,
15596
+ /** Effective WebAuthn RP ID (`resolveRpID()`) — the shell gates visibility on it. */
15597
+ rpId: string(),
15598
+ /** Effective expected origin (`resolveOrigin()`), null when unconfigured. */
15599
+ origin: string().nullable()
15600
+ })
15601
+ ]);
15602
+ method(_void(), array(LoginMethodContributionSchema).readonly());
15603
+ var CpuBreakdownSchema = object({
15604
+ total: number(),
15605
+ user: number(),
15606
+ system: number(),
15607
+ irq: number(),
15608
+ nice: number(),
15609
+ loadAvg: tuple([
15610
+ number(),
15611
+ number(),
15612
+ number()
15613
+ ]),
15614
+ cores: number()
15615
+ });
15616
+ var MemoryInfoSchema = object({
15617
+ percent: number(),
15618
+ totalBytes: number(),
15619
+ usedBytes: number(),
15620
+ availableBytes: number(),
15621
+ swapUsedBytes: number(),
15622
+ swapTotalBytes: number()
15315
15623
  });
15316
15624
  var DiskIoSnapshotSchema = object({
15317
15625
  readBytes: number(),
@@ -15764,14 +16072,14 @@ var TargetKindCapsSchema = object({
15764
16072
  * the union is large and not meant for runtime validation here; the exported
15765
16073
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
15766
16074
  */
15767
- var ConfigSchemaPassthrough$1 = unknown();
16075
+ var ConfigSchemaPassthrough = unknown();
15768
16076
  var TargetKindSchema = object({
15769
16077
  kind: string(),
15770
16078
  label: string(),
15771
16079
  icon: string(),
15772
16080
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
15773
16081
  addonId: string(),
15774
- configSchema: ConfigSchemaPassthrough$1,
16082
+ configSchema: ConfigSchemaPassthrough,
15775
16083
  supportsDiscovery: boolean(),
15776
16084
  caps: TargetKindCapsSchema
15777
16085
  });
@@ -15824,297 +16132,493 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
15824
16132
  enabled: boolean()
15825
16133
  }), _void(), { kind: "mutation" });
15826
16134
  /**
15827
- * Shared LLM generate contracts imported by BOTH `llm.cap.ts` (consumer
15828
- * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15829
- * caps stay wire-compatible without a circular cap→cap import.
16135
+ * notification-rules the Notification Center rule surface (P1 core).
15830
16136
  *
15831
- * Errors are a discriminated-union RESULT, never thrown: the shape survives
15832
- * every transport tier structurally, and failed calls still write usage rows.
15833
- * Token counts only in v1 — no costUsd (operator decision, 2026-07-15).
15834
- */
15835
- var LlmUsageSchema = object({
15836
- inputTokens: number(),
15837
- outputTokens: number()
15838
- });
15839
- var LlmErrorCodeSchema = _enum([
15840
- "timeout",
15841
- "rate-limited",
15842
- "auth",
15843
- "refusal",
15844
- "bad-request",
15845
- "unavailable",
15846
- "no-profile",
15847
- "budget-exceeded",
15848
- "adapter-error"
15849
- ]);
15850
- var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15851
- ok: literal(true),
15852
- text: string(),
15853
- model: string(),
15854
- usage: LlmUsageSchema,
15855
- truncated: boolean(),
15856
- latencyMs: number()
15857
- }), object({
15858
- ok: literal(false),
15859
- code: LlmErrorCodeSchema,
15860
- message: string(),
15861
- retryAfterMs: number().optional()
15862
- })]);
16137
+ * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
16138
+ * (operator decisions D-1/D-2/D-3 are binding):
16139
+ *
16140
+ * - D-2: rule EVALUATION lives in `addon-post-analysis` (the
16141
+ * `notification-center` module), hooked on the durable persistence
16142
+ * moments (object-event insert, TrackCloser.closeExpired) with a
16143
+ * persisted outbox + retry — never the lossy telemetry bus (D8).
16144
+ * - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
16145
+ * FIRST persisted detection matching the conditions (per-track dedup,
16146
+ * `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
16147
+ * `delivery: 'track-end'` evaluates the finalized track record at close.
16148
+ * - DISPATCH stays behind `notification-output` (rules reference targets
16149
+ * by id; per-backend params are a passthrough blob capped by the
16150
+ * target kind's own caps/degrade engine).
16151
+ *
16152
+ * P1 scope: admin-authored rules only (`createdBy` stamped from the
16153
+ * server-injected caller identity — the first `caller: 'required'`
16154
+ * adopter). The P1 condition subset is: devices, classes(+exclude),
16155
+ * minConfidence, admin zones (any/all + exclude), weekly schedule
16156
+ * windows, and the optional label/identity/plate matchers. User rules,
16157
+ * private zones, per-recipient fan-out and the wider condition table are
16158
+ * P2+ (see spec §7).
16159
+ *
16160
+ * All schemas here are the single source of truth — `NcRule` etc. are
16161
+ * `z.infer` exports; no duplicate interfaces (the advanced-notifier
16162
+ * schema/interface drift is explicitly not repeated).
16163
+ */
15863
16164
  /**
15864
- * `Uint8Array` is the sanctioned binary conventionsuperjson + the UDS
15865
- * MsgPack channel round-trip typed arrays (embedding-encoder.cap.ts:29,
15866
- * notification-output.cap.ts:27-31 precedents).
16165
+ * D-3: the trigger/urgency of a rule which persistence moment evaluates it.
16166
+ * The value maps 1:1 onto the evaluated record kind:
16167
+ * - `immediate` ↔ object-event persist (lowest-latency detection burst)
16168
+ * - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
16169
+ * - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
16170
+ * change of a LINKED device, one row per linked camera)
16171
+ * - `package-event` ↔ PackageDropDetector object-event insert (a `package`
16172
+ * delivery / pick-up)
16173
+ *
16174
+ * `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
16175
+ * `package-event` are pure trigger kinds (no urgency dimension). Extending
16176
+ * this one field keeps the schema additive — a rule still declares exactly
16177
+ * one trigger.
15867
16178
  */
15868
- var LlmImageSchema = object({
15869
- bytes: _instanceof(Uint8Array),
15870
- mimeType: string()
16179
+ var NcDeliverySchema = _enum([
16180
+ "immediate",
16181
+ "track-end",
16182
+ "device-event",
16183
+ "package-event"
16184
+ ]);
16185
+ /** Weekly schedule — OR of windows; absence on the rule = always active. */
16186
+ var NcScheduleSchema = object({
16187
+ windows: array(object({
16188
+ /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
16189
+ days: array(number().int().min(0).max(6)).min(1),
16190
+ startMinute: number().int().min(0).max(1439),
16191
+ endMinute: number().int().min(0).max(1439)
16192
+ })).min(1),
16193
+ /** IANA timezone; default = hub host timezone. */
16194
+ timezone: string().optional(),
16195
+ /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
16196
+ invert: boolean().optional()
15871
16197
  });
15872
- var LlmGenerateBaseInputSchema = object({
15873
- /** Collection routing (the notification-output posture). */
15874
- addonId: string().optional(),
15875
- /** Explicit profile; else the resolution chain (spec §3). */
15876
- profileId: string().optional(),
15877
- /** MANDATORY usage tag: 'ai-summary', 'notifier-rules', 'adhoc-ui', … */
15878
- consumer: string(),
15879
- system: string().optional(),
15880
- /** v1: single-turn. `messages[]` is a v2 additive field. */
15881
- prompt: string(),
15882
- /** Structured output — adapter-mapped (response_format / forced tool / responseSchema). */
15883
- jsonSchema: record(string(), unknown()).optional(),
15884
- /** Per-call override of the profile default. */
15885
- maxTokens: number().int().positive().optional(),
15886
- temperature: number().optional()
16198
+ /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
16199
+ var NcPlateMatcherSchema = object({
16200
+ values: array(string().min(1)).min(1),
16201
+ /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
16202
+ maxDistance: number().int().min(0).max(3).default(1)
15887
16203
  });
15888
16204
  /**
15889
- * `llm-runtime` node-side managed llama.cpp executor (spec §4). Registered
15890
- * on EVERY node where `addon-ai` is installed; the hub `llm` provider reaches
15891
- * a specific node's runtime with `nodePin(profile.runtime.nodeId)` normal
15892
- * cap routing, zero bespoke plumbing. `internal: true`: the operator reaches
15893
- * this only through the `llm` cap's methods.
15894
- *
15895
- * One running llama-server child per node in v1 (models are RAM-heavy).
15896
- * Resource ceiling = llama-server flags + idleStopMinutes ONLY (no RSS
15897
- * watchdog operator decision #3).
16205
+ * Occupancy condition (DEVICE-EVENT trigger). Fires on a ZoneAnalytics
16206
+ * occupancy edge for a device optionally narrowed to a single admin
16207
+ * `zoneId` and/or object `className`. `op` selects the edge/threshold:
16208
+ * - `became-occupied` (default) count crossed 0 `count`
16209
+ * - `became-free` — count crossed `count` below it
16210
+ * - `>=` / `<=` — count is at/over or at/under `count`
16211
+ * `sustainSeconds` requires the condition hold continuously that long
16212
+ * before firing (debounces flicker; 0 = fire on the first matching edge).
16213
+ * Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
16214
+ * the condition never matches. Confirmed edge-state survives addon restarts
16215
+ * (declared SQLite collection, reseeded on boot).
15898
16216
  */
15899
- var ManagedModelRefSchema = discriminatedUnion("kind", [
15900
- object({
15901
- kind: literal("catalog"),
15902
- catalogId: string()
15903
- }),
15904
- object({
15905
- kind: literal("url"),
15906
- url: string(),
15907
- sha256: string().optional()
15908
- }),
15909
- object({
15910
- kind: literal("path"),
15911
- path: string()
15912
- })
15913
- ]);
15914
- var ManagedRuntimeConfigSchema = object({
15915
- /** WHERE the runtime lives — hub or any agent. */
15916
- nodeId: string(),
15917
- /** Closed for v1; 'ollama' is a v2 candidate. */
15918
- engine: _enum(["llama-cpp"]),
15919
- model: ManagedModelRefSchema,
15920
- contextSize: number().int().default(4096),
15921
- /** 0 = CPU-only. */
15922
- gpuLayers: number().int().default(0),
15923
- /** Default: cpus-2, clamped ≥1 (resolved node-side). */
15924
- threads: number().int().optional(),
15925
- /** Concurrent slots. */
15926
- parallel: number().int().default(1),
15927
- /** Else lazy: first generate boots it. */
15928
- autoStart: boolean().default(false),
15929
- /** 0 = never; frees RAM after quiet periods. */
15930
- idleStopMinutes: number().int().default(30)
16217
+ var NcOccupancyConditionSchema = object({
16218
+ /** Admin zone id to scope the count to; absent = whole-frame occupancy. */
16219
+ zoneId: string().optional(),
16220
+ /** Object class to count; absent = any class. */
16221
+ className: string().optional(),
16222
+ op: _enum([
16223
+ "became-occupied",
16224
+ "became-free",
16225
+ ">=",
16226
+ "<="
16227
+ ]).default("became-occupied"),
16228
+ count: number().int().min(0).default(1),
16229
+ sustainSeconds: number().int().min(0).max(3600).default(15)
16230
+ });
16231
+ /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
16232
+ var NcZoneConditionSchema = object({
16233
+ ids: array(string().min(1)).min(1),
16234
+ /** Quantifier over `ids` — at least one / every one visited. */
16235
+ match: _enum(["any", "all"]).default("any")
15931
16236
  });
15932
- var LlmRuntimeStatusSchema = object({
15933
- /** Status is ALWAYS node-qualified. */
15934
- nodeId: string(),
15935
- state: _enum([
15936
- "stopped",
15937
- "downloading",
15938
- "starting",
15939
- "ready",
15940
- "crashed",
15941
- "failed"
15942
- ]),
15943
- pid: number().optional(),
15944
- port: number().optional(),
15945
- modelPath: string().optional(),
15946
- modelId: string().optional(),
15947
- downloadProgress: number().min(0).max(1).optional(),
15948
- lastError: string().optional(),
15949
- crashesInWindow: number(),
15950
- /** Child RSS (sampled best-effort). */
15951
- memoryBytes: number().optional(),
15952
- vramBytes: number().optional()
16237
+ /**
16238
+ * The P1 condition set — a flat AND of groups; absent group = pass;
16239
+ * membership lists are OR within the list (spec §2.3).
16240
+ */
16241
+ var NcConditionsSchema = object({
16242
+ /** Device scope — absent = all devices. */
16243
+ devices: array(number()).optional(),
16244
+ /** Detector class names (any overlap with the record's class set). */
16245
+ classes: array(string().min(1)).optional(),
16246
+ /** Veto classes — any overlap fails the rule. */
16247
+ classesExclude: array(string().min(1)).optional(),
16248
+ /** Minimum detection confidence 0–1 (fails when the record has none). */
16249
+ minConfidence: number().min(0).max(1).optional(),
16250
+ /** Admin zone membership over event `zones` / track `zonesVisited`. */
16251
+ zones: NcZoneConditionSchema.optional(),
16252
+ /** Veto zones — any hit fails the rule. */
16253
+ zonesExclude: array(string().min(1)).optional(),
16254
+ /**
16255
+ * Exact (case-insensitive) match on the record's collapsed `label`
16256
+ * (identity name / plate text / subclass).
16257
+ */
16258
+ labelEquals: array(string().min(1)).optional(),
16259
+ /**
16260
+ * Identity matcher. P1 boundary: matched against the record's collapsed
16261
+ * `label` (the identity display name propagated by the face pipeline) —
16262
+ * identity-ID matching rides in P2 when identity ids reach the record.
16263
+ */
16264
+ identities: array(string().min(1)).optional(),
16265
+ /** Fuzzy plate matcher against the record's `label` (plate text). */
16266
+ plates: NcPlateMatcherSchema.optional(),
16267
+ /**
16268
+ * Identity EXCLUDE — mirror of {@link identities} with `notIn` semantics.
16269
+ * Same P1 boundary: matched against the record's collapsed `label` (the
16270
+ * identity display name). A record with NO label passes (nothing to
16271
+ * exclude), unlike the include variant which fails on an absent label.
16272
+ */
16273
+ identitiesExclude: array(string().min(1)).optional(),
16274
+ /**
16275
+ * Minimum server-computed key-event importance in [0,1] (`Track.importance`).
16276
+ * TRACK-END only: importance is scored at track close, so it does not exist
16277
+ * at immediate / object-event evaluation time (see catalog `appliesTo`). At
16278
+ * close the value is threaded via the close-time info (the `Track` clone is
16279
+ * captured before the DB row is updated, so it would otherwise read stale).
16280
+ * Fails when the record carries no importance (never guess quality — the
16281
+ * `minConfidence` precedent). MVP cut: a single scalar threshold.
16282
+ */
16283
+ minImportance: number().min(0).max(1).optional(),
16284
+ /**
16285
+ * Minimum track dwell in SECONDS — `(lastSeen − firstSeen) / 1000`.
16286
+ * TRACK-END only: an `immediate` / object-event subject has no closed
16287
+ * lifespan, so a dwell condition never matches immediate delivery
16288
+ * (documented choice — the object-event record carries no `firstSeen`,
16289
+ * so dwell cannot be computed from what the subject actually carries).
16290
+ */
16291
+ minDwellSeconds: number().min(0).optional(),
16292
+ /**
16293
+ * Detection provenance filter. `any` (default / absent) matches every
16294
+ * source; otherwise the subject's source must equal it. Legacy records
16295
+ * with no stamped source are treated as `pipeline`. The union spans both
16296
+ * record kinds — object events carry `pipeline` | `onboard`, synthetic
16297
+ * tracks carry `sensor`.
16298
+ */
16299
+ source: _enum([
16300
+ "pipeline",
16301
+ "onboard",
16302
+ "sensor",
16303
+ "any"
16304
+ ]).optional(),
16305
+ /**
16306
+ * Minimum identity / plate MATCH confidence in [0,1] — DISTINCT from the
16307
+ * detector `minConfidence` (that gates the object-detection score; this
16308
+ * gates the recognition/OCR match score). Fails when the subject carries
16309
+ * no label-match confidence (never guess). TRACK-END only: the confidence
16310
+ * lives on the recognition result and reaches the subject at track close.
16311
+ *
16312
+ * What it measures precisely (plumbed at track close — the closer threads
16313
+ * the value into `NcTrackClosedInfo.labelConfidence`, the same seam as
16314
+ * `importance`): the BEST recognition match confidence observed for the
16315
+ * label the track carries at close — for a face, the peak cosine similarity
16316
+ * of the ASSIGNED identity (`FaceMatch.score`, reset on an identity switch);
16317
+ * for a plate, the peak OCR read score of the best-held plate
16318
+ * (`plateText.confidence`). When BOTH a face and a plate were recognized on
16319
+ * one track the higher of the two is used. A track that ended with no
16320
+ * confident identity/plate match carries no value, so the condition fails
16321
+ * closed for it (an un-recognized subject).
16322
+ */
16323
+ minLabelConfidence: number().min(0).max(1).optional(),
16324
+ /**
16325
+ * DEVICE-EVENT only. Raw device event-type tokens (`EventFire.eventType`,
16326
+ * e.g. a doorbell `press` / `press_long`) — matched case-insensitively
16327
+ * against the token carried on the device-event subject (extracted from the
16328
+ * event-emitter runtime slice's `lastEvent.eventType`). Fails when the
16329
+ * subject carries no token. Doorbell-pulse / passive-sensor kinds emit no
16330
+ * eventType, so gate those with {@link sensorKinds} instead.
16331
+ */
16332
+ eventTypeTokens: array(string().min(1)).optional(),
16333
+ /**
16334
+ * DEVICE-EVENT only. Sensor/control taxonomy kinds (e.g. `doorbell`,
16335
+ * `contact`, `button`, `device-event`) — matched against the persisted
16336
+ * `SensorEvent.kind` (see `sensor-event-kinds.ts`). Membership is OR.
16337
+ */
16338
+ sensorKinds: array(string().min(1)).optional(),
16339
+ /**
16340
+ * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
16341
+ * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
16342
+ * when the subject's phase does not match (a subject always carries a phase
16343
+ * on the package-event trigger).
16344
+ */
16345
+ packagePhase: _enum([
16346
+ "delivered",
16347
+ "picked-up",
16348
+ "both"
16349
+ ]).optional(),
16350
+ /**
16351
+ * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
16352
+ * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
16353
+ * listed polygon (ZoneEngine membership semantics). Evaluated only when
16354
+ * the subject carries a bbox; absent bbox ⇒ the condition FAILS.
16355
+ */
16356
+ customZones: array(MaskPolygonShapeSchema).optional(),
16357
+ /**
16358
+ * DEVICE-EVENT only. ZoneAnalytics occupancy edge — fires when a device's
16359
+ * (optionally zone/class-scoped) occupancy count crosses the configured
16360
+ * threshold and holds for `sustainSeconds`. Fail-closed on missing
16361
+ * substrate (no snapshot / missing zone). See {@link NcOccupancyCondition}.
16362
+ */
16363
+ occupancy: NcOccupancyConditionSchema.optional()
15953
16364
  });
15954
- var LlmNodeModelSchema = object({
15955
- file: string(),
15956
- sizeBytes: number(),
15957
- catalogId: string().optional(),
15958
- installedAt: number().optional()
16365
+ /** One delivery target: a `notification-output` Target ref + passthrough params. */
16366
+ var NcRuleTargetSchema = object({
16367
+ /** `notification-output` Target id. */
16368
+ targetId: string().min(1),
16369
+ /**
16370
+ * Per-backend passthrough. Recognized keys are mapped onto the canonical
16371
+ * Notification (`priority`, `level`, `sound`, `clickUrl`, `ttl`); the
16372
+ * degrade engine drops what the backend can't render.
16373
+ */
16374
+ params: record(string(), unknown()).optional()
15959
16375
  });
15960
- var LlmRuntimeDiskUsageSchema = object({
15961
- nodeId: string(),
15962
- modelsBytes: number(),
15963
- freeBytes: number().optional()
16376
+ /**
16377
+ * Media attachment policy (P1 still-image subset).
16378
+ * - `best` — the best AVAILABLE subject image at dispatch time (D-3).
16379
+ * - `best-matching` — the media that explains WHY the rule fired: a rule
16380
+ * matched on identities attaches the subject's `faceCrop`, one matched on
16381
+ * plates attaches the `plateCrop`; a rule with no identity/plate condition
16382
+ * (or when the specific crop is missing) degrades to `best`, then
16383
+ * `keyFrame`, then no attachment — never delaying the send. The matched
16384
+ * condition summary is frozen on the outbox row at enqueue (like the rule
16385
+ * name), so the choice never drifts from the record that fired it.
16386
+ * - `keyFrame` — the clean scene frame (no subject box).
16387
+ * - `none` — no attachment.
16388
+ */
16389
+ var NcMediaPolicySchema = object({ attach: _enum([
16390
+ "best",
16391
+ "best-matching",
16392
+ "keyFrame",
16393
+ "none"
16394
+ ]).default("best") });
16395
+ /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
16396
+ var NcThrottleSchema = object({
16397
+ cooldownSec: number().int().min(0).max(86400).default(60),
16398
+ /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
16399
+ scope: _enum(["rule", "rule-device"]).default("rule-device")
16400
+ });
16401
+ /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
16402
+ var NcRuleInputSchema = object({
16403
+ name: string().min(1).max(200),
16404
+ enabled: boolean().default(true),
16405
+ delivery: NcDeliverySchema,
16406
+ conditions: NcConditionsSchema.default({}),
16407
+ schedule: NcScheduleSchema.optional(),
16408
+ targets: array(NcRuleTargetSchema).min(1),
16409
+ media: NcMediaPolicySchema.default({ attach: "best" }),
16410
+ throttle: NcThrottleSchema.default({
16411
+ cooldownSec: 60,
16412
+ scope: "rule-device"
16413
+ }),
16414
+ /** `{{var}}` templating over camera/class/label/zones/confidence/time. */
16415
+ template: object({
16416
+ title: string().max(500).optional(),
16417
+ body: string().max(2e3).optional()
16418
+ }).optional(),
16419
+ /** Canonical notification priority ordinal (1..5); per-target overridable. */
16420
+ priority: number().int().min(1).max(5).default(3),
16421
+ /**
16422
+ * Ownership/visibility key. Absent = admin/global rule (unchanged legacy
16423
+ * behaviour, visible to all, read-only in the viewer). Present = personal
16424
+ * rule owned by this userId. Server-stamped; never trusted from a client.
16425
+ */
16426
+ ownerUserId: string().optional()
15964
16427
  });
15965
- method(LlmGenerateBaseInputSchema.extend({
15966
- images: array(LlmImageSchema).optional(),
15967
- runtime: ManagedRuntimeConfigSchema,
15968
- /** The managed profile's timeout, threaded by the hub provider. */
15969
- timeoutMs: number().int().positive().optional()
15970
- }), LlmGenerateResultSchema, { kind: "mutation" }), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
15971
- kind: "mutation",
15972
- auth: "admin"
15973
- }), method(object({}), _void(), {
15974
- kind: "mutation",
15975
- auth: "admin"
15976
- }), method(object({}), LlmRuntimeStatusSchema), method(object({ model: ManagedModelRefSchema }), _void(), {
15977
- kind: "mutation",
15978
- auth: "admin"
15979
- }), method(object({ file: string() }), _void(), {
15980
- kind: "mutation",
15981
- auth: "admin"
15982
- }), method(object({}), array(LlmNodeModelSchema)), method(object({}), LlmRuntimeDiskUsageSchema);
15983
16428
  /**
15984
- * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15985
- * methods concat-fan across providers; single-row methods route to ONE
15986
- * provider by the `addonId` in the call input (the notification-output
15987
- * posture, notification-output.cap.ts:215-250). Provided by `addon-ai`
15988
- * (hub-placed); the cap stays open for future providers.
15989
- *
15990
- * Profiles are ROWS (data), not addons: one row = one usable model endpoint.
15991
- * `apiKey` is a password field — providers REDACT it on read and merge on
15992
- * write; a stored key NEVER round-trips to a client.
16429
+ * Partial patch for `updateRule` — any subset of the input fields, plus the
16430
+ * persisted-only {@link NcRuleSchema} `disabledTargetIds` set. The latter is
16431
+ * NOT a client-authored input field (it lives on the persisted rule, not the
16432
+ * input), so it is added here explicitly to let the store's per-target opt-out
16433
+ * toggle round-trip through the shared `update` path. Viewer opt-out mutations
16434
+ * still flow through `nc.setRuleTargetEnabled` (owner-checked), never a raw
16435
+ * `updateRule` patch.
15993
16436
  */
15994
- var LlmProfileKindSchema = _enum([
15995
- "openai-compatible",
15996
- "openai",
15997
- "anthropic",
15998
- "google",
15999
- "managed-local"
16000
- ]);
16001
- var LlmProfileSchema = object({
16437
+ var NcRulePatchSchema = NcRuleInputSchema.partial().extend({ disabledTargetIds: array(string()).optional() });
16438
+ /** A persisted rule. */
16439
+ var NcRuleSchema = NcRuleInputSchema.extend({
16002
16440
  id: string(),
16003
- name: string(),
16004
- kind: LlmProfileKindSchema,
16005
- /** Stamped by the provider — keeps the fanned catalog routable. */
16006
- addonId: string(),
16007
- enabled: boolean(),
16008
- /** Vendor model id, or the managed runtime's loaded model. */
16009
- model: string(),
16010
- /** Required for openai-compatible; override for cloud kinds. */
16011
- baseUrl: string().optional(),
16012
- /** ConfigUISchema type:'password' — never round-trips (spec §5). */
16013
- apiKey: string().optional(),
16014
- supportsVision: boolean(),
16015
- temperature: number().min(0).max(2).optional(),
16016
- maxTokens: number().int().positive().optional(),
16017
- timeoutMs: number().int().positive().default(6e4),
16018
- extraHeaders: record(string(), string()).optional(),
16019
- /** kind === 'managed-local' only (spec §4). */
16020
- runtime: ManagedRuntimeConfigSchema.optional()
16021
- });
16022
- /** ConfigUISchema tree passed through untyped on the wire (the
16023
- * notification-output `ConfigSchemaPassthrough` precedent at
16024
- * notification-output.cap.ts:151); the exported TS type re-tightens it. */
16025
- var ConfigSchemaPassthrough = unknown();
16026
- var LlmProfileKindDescriptorSchema = object({
16027
- kind: LlmProfileKindSchema,
16028
- label: string(),
16029
- icon: string(),
16030
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
16031
- addonId: string(),
16032
- configSchema: ConfigSchemaPassthrough
16033
- });
16034
- var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
16035
- var LlmDefaultSchema = object({
16036
- selector: LlmDefaultSelectorSchema,
16037
- profileId: string()
16441
+ /** userId of the admin who created the rule (server-stamped caller). */
16442
+ createdBy: string(),
16443
+ createdAt: number(),
16444
+ updatedAt: number(),
16445
+ /**
16446
+ * Per-target opt-out set. A targetId here is suppressed for THIS rule at
16447
+ * send time. Only a target's OWNER may add/remove its id (server-checked
16448
+ * in `nc.setRuleTargetEnabled`). Defaults to empty.
16449
+ */
16450
+ disabledTargetIds: array(string()).default([])
16038
16451
  });
16039
- /** Server-side rollup row — getUsage never dumps raw call rows (spec §6). */
16040
- var LlmUsageRollupSchema = object({
16041
- day: string(),
16042
- consumer: string(),
16043
- profileId: string(),
16044
- calls: number(),
16045
- okCalls: number(),
16046
- errorCalls: number(),
16047
- inputTokens: number(),
16048
- outputTokens: number(),
16049
- avgLatencyMs: number()
16452
+ var NcTestResultSchema = object({
16453
+ recordId: string(),
16454
+ recordKind: _enum([
16455
+ "object-event",
16456
+ "track",
16457
+ "device-event",
16458
+ "package-event"
16459
+ ]),
16460
+ deviceId: number(),
16461
+ timestamp: number(),
16462
+ wouldFire: boolean(),
16463
+ /** Condition id that failed (first failing group), when `wouldFire` is false. */
16464
+ failedCondition: string().optional(),
16465
+ className: string().optional(),
16466
+ label: string().optional()
16050
16467
  });
16051
- /** LLM-facing view over the reused ModelCatalogEntry mechanism (spec §4.2). */
16052
- var ManagedModelCatalogEntrySchema = object({
16468
+ var NcConditionDescriptorSchema = object({
16469
+ /** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
16053
16470
  id: string(),
16471
+ group: _enum([
16472
+ "scope",
16473
+ "class",
16474
+ "zones",
16475
+ "quality",
16476
+ "label",
16477
+ "schedule",
16478
+ "device",
16479
+ "package",
16480
+ "occupancy"
16481
+ ]),
16054
16482
  label: string(),
16055
- family: string(),
16056
- purpose: _enum(["text", "vision"]),
16057
- url: string(),
16058
- sha256: string(),
16059
- sizeBytes: number(),
16060
- quantization: string(),
16061
- /** Load-time guidance shown in the picker. */
16062
- minRamBytes: number(),
16063
- contextSizeDefault: number().int(),
16064
- /** Vision models: companion projector file. */
16065
- mmprojUrl: string().optional()
16483
+ /** Editor widget the UI renders — never hardcode per-condition forms. */
16484
+ valueType: _enum([
16485
+ "deviceIdList",
16486
+ "stringList",
16487
+ "number01",
16488
+ "number",
16489
+ "sourceSelect",
16490
+ "zoneSelection",
16491
+ "zoneIdList",
16492
+ "schedule",
16493
+ "plateMatcher",
16494
+ "packagePhase",
16495
+ "polygonDraw",
16496
+ "occupancy"
16497
+ ]),
16498
+ operator: _enum([
16499
+ "in",
16500
+ "notIn",
16501
+ "anyOf",
16502
+ "allOf",
16503
+ "gte",
16504
+ "fuzzyIn",
16505
+ "withinSchedule"
16506
+ ]),
16507
+ /** Which delivery kinds the condition applies to. */
16508
+ appliesTo: array(NcDeliverySchema),
16509
+ phase: string(),
16510
+ description: string().optional()
16066
16511
  });
16067
- var LlmRuntimeNodeSchema = object({
16068
- nodeId: string(),
16069
- reachable: boolean(),
16070
- status: LlmRuntimeStatusSchema.optional(),
16071
- disk: LlmRuntimeDiskUsageSchema.optional(),
16072
- error: string().optional()
16512
+ /**
16513
+ * The delivery lifecycle status of a history row — a straight read of the
16514
+ * durable outbox row's own status (single source of truth):
16515
+ * - `pending` — enqueued, in-flight or retrying with backoff
16516
+ * - `sent` — delivered (terminal)
16517
+ * - `dead` — dead-lettered after exhausting retries / a permanent
16518
+ * backend rejection / a deleted target (terminal; carries
16519
+ * the failure `error`)
16520
+ *
16521
+ * P1 has no `suppressed-quiet-hours` / `snoozed` states — those ride the P2
16522
+ * user dimension (quiet hours / snooze) and are additive when they land.
16523
+ */
16524
+ var NcHistoryStatusSchema = _enum([
16525
+ "pending",
16526
+ "sent",
16527
+ "dead"
16528
+ ]);
16529
+ /** The evaluated record kind a history row descends from (one per trigger). */
16530
+ var NcHistoryRecordKindSchema = _enum([
16531
+ "object-event",
16532
+ "track-end",
16533
+ "device-event",
16534
+ "package-event"
16535
+ ]);
16536
+ /** Subject summary frozen on the row at fire time (survives rule/record edits). */
16537
+ var NcHistorySubjectSchema = object({
16538
+ className: string(),
16539
+ label: string().optional(),
16540
+ confidence: number().optional(),
16541
+ zones: array(string()),
16542
+ timestamp: number()
16543
+ });
16544
+ /**
16545
+ * One delivery-history row. This is a read-only VIEW over the durable
16546
+ * outbox row (single source of truth — the same row the drain loop drives;
16547
+ * NO second write path, so history can never drift from delivery state).
16548
+ * The §3.2 fields map directly: `ruleId`/`targetId`/`deviceId` are columns,
16549
+ * `eventRef` is `recordKind`+`recordId`, `timestamps` are `createdAt`
16550
+ * (fire) / `updatedAt` (last transition), `status` + `error` are the
16551
+ * lifecycle. `ruleName` + `subject` are the intent snapshot frozen at
16552
+ * enqueue. `userId?` (per-recipient history) is P2 — no user dimension in
16553
+ * P1 (admin scope only).
16554
+ */
16555
+ var NcHistoryEntrySchema = object({
16556
+ /** Outbox row id — the stable dedup id `ruleId:dedupRef:targetId`. */
16557
+ id: string(),
16558
+ ruleId: string(),
16559
+ /** Rule name frozen at fire time (outlives a later rename / delete). */
16560
+ ruleName: string(),
16561
+ /** The rule urgency/trigger that produced this delivery. */
16562
+ delivery: NcDeliverySchema,
16563
+ targetId: string(),
16564
+ deviceId: number(),
16565
+ recordKind: NcHistoryRecordKindSchema,
16566
+ /** Event / track ref of the evaluated record (§3.2 `eventRef`). */
16567
+ recordId: string(),
16568
+ /** Present for track-scoped deliveries (object-event / track-end). */
16569
+ trackId: string().optional(),
16570
+ status: NcHistoryStatusSchema,
16571
+ /** Delivery attempts made so far. */
16572
+ attempts: number().int(),
16573
+ /** Fire time (outbox enqueue). */
16574
+ createdAt: number(),
16575
+ /** Last transition time (terminal for sent / dead). */
16576
+ updatedAt: number(),
16577
+ /** Failure detail — present on a `dead` row. */
16578
+ error: string().optional(),
16579
+ subject: NcHistorySubjectSchema
16073
16580
  });
16074
- var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
16075
- var ProfileRefInputSchema = object({
16076
- addonId: string(),
16077
- profileId: string()
16581
+ /**
16582
+ * Query filter for `getHistory` (spec §4.2). Every field is a narrowing
16583
+ * AND; absent = unbounded on that axis. `since`/`until` bound the fire time
16584
+ * (`createdAt`, epoch ms, inclusive). `limit` is clamped to
16585
+ * {@link NC_HISTORY_LIMIT_MAX}. `userId` (per-recipient filtering) is P2.
16586
+ */
16587
+ var NcHistoryFilterSchema = object({
16588
+ ruleId: string().optional(),
16589
+ deviceId: number().optional(),
16590
+ status: NcHistoryStatusSchema.optional(),
16591
+ since: number().optional(),
16592
+ until: number().optional(),
16593
+ limit: number().int().min(1).max(500).default(100)
16078
16594
  });
16079
- method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(GenerateVisionInputSchema, LlmGenerateResultSchema, { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
16080
- kind: "mutation",
16081
- auth: "admin"
16082
- }), method(ProfileRefInputSchema, _void(), {
16595
+ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
16083
16596
  kind: "mutation",
16084
- auth: "admin"
16085
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
16597
+ auth: "admin",
16598
+ caller: "required"
16599
+ }), method(object({
16600
+ ruleId: string(),
16601
+ patch: NcRulePatchSchema
16602
+ }), object({ rule: NcRuleSchema }), {
16086
16603
  kind: "mutation",
16087
- auth: "admin"
16088
- }), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
16089
- selector: LlmDefaultSelectorSchema,
16090
- profileId: string().nullable()
16091
- }), _void(), {
16604
+ auth: "admin",
16605
+ caller: "required"
16606
+ }), method(object({ ruleId: string() }), object({ success: literal(true) }), {
16092
16607
  kind: "mutation",
16093
16608
  auth: "admin"
16094
16609
  }), method(object({
16095
- since: number().optional(),
16096
- until: number().optional(),
16097
- consumer: string().optional(),
16098
- profileId: string().optional()
16099
- }), array(LlmUsageRollupSchema)), method(object({}), array(ManagedModelCatalogEntrySchema)), method(object({}), array(LlmRuntimeNodeSchema)), method(object({ nodeId: string() }), array(LlmNodeModelSchema)), method(object({
16100
- nodeId: string(),
16101
- model: ManagedModelRefSchema
16102
- }), _void(), {
16610
+ ruleId: string(),
16611
+ enabled: boolean()
16612
+ }), object({ success: literal(true) }), {
16103
16613
  kind: "mutation",
16104
16614
  auth: "admin"
16105
16615
  }), method(object({
16106
- nodeId: string(),
16107
- file: string()
16108
- }), _void(), {
16109
- kind: "mutation",
16110
- auth: "admin"
16111
- }), method(ProfileRefInputSchema, LlmRuntimeStatusSchema), method(ProfileRefInputSchema, LlmRuntimeStatusSchema, {
16112
- kind: "mutation",
16113
- auth: "admin"
16114
- }), method(ProfileRefInputSchema, _void(), {
16616
+ rule: NcRuleInputSchema,
16617
+ lookbackMinutes: number().int().min(1).max(1440).default(60)
16618
+ }), object({ results: array(NcTestResultSchema) }), {
16115
16619
  kind: "mutation",
16116
16620
  auth: "admin"
16117
- });
16621
+ }), method(object({}), object({ catalog: array(NcConditionDescriptorSchema) })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
16118
16622
  /**
16119
16623
  * Zod schemas for persisted record types.
16120
16624
  *
@@ -16800,7 +17304,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16800
17304
  }), method(object({
16801
17305
  eventId: string(),
16802
17306
  kind: MediaFileKindEnum.optional()
16803
- }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
17307
+ }), array(MediaFileSchema).readonly()), method(object({
17308
+ trackId: string(),
17309
+ kinds: array(MediaFileKindEnum).optional()
17310
+ }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
16804
17311
  deviceId: number(),
16805
17312
  timestamp: number(),
16806
17313
  frameWidth: number(),
@@ -16821,76 +17328,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16821
17328
  eventId: string(),
16822
17329
  timestamp: number()
16823
17330
  });
16824
- /**
16825
- * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16826
- * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16827
- * caps into per-camera event-kind descriptors.
16828
- *
16829
- * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16830
- * is NOT duplicated here — every entry is derived from the single
16831
- * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16832
- * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16833
- * control cap means adding one line here (and a taxonomy entry); the anti-
16834
- * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16835
- * eventful cap is missing.
16836
- */
16837
- /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16838
- var LEGACY_ICON = {
16839
- motion: "motion",
16840
- audio: "audio",
16841
- person: "person",
16842
- vehicle: "vehicle",
16843
- animal: "animal",
16844
- package: "package",
16845
- door: "door",
16846
- pir: "pir",
16847
- smoke: "smoke",
16848
- water: "water",
16849
- button: "button",
16850
- generic: "generic",
16851
- gas: "smoke",
16852
- vibration: "generic",
16853
- tamper: "generic",
16854
- presence: "person",
16855
- lock: "generic",
16856
- siren: "generic",
16857
- switch: "generic",
16858
- doorbell: "button"
16859
- };
16860
- function legacyIcon(iconId) {
16861
- return LEGACY_ICON[iconId] ?? "generic";
16862
- }
16863
- /**
16864
- * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16865
- * The anti-drift guard cross-checks this against the eventful caps declared
16866
- * in `packages/types/src/capabilities/*.cap.ts`.
16867
- */
16868
- var CAP_TO_KIND = {
16869
- contact: "contact",
16870
- motion: "motion-sensor",
16871
- smoke: "smoke",
16872
- flood: "flood",
16873
- gas: "gas",
16874
- "carbon-monoxide": "carbon-monoxide",
16875
- vibration: "vibration",
16876
- tamper: "tamper",
16877
- presence: "presence",
16878
- "enum-sensor": "enum-sensor",
16879
- "event-emitter": "device-event",
16880
- "lock-control": "lock",
16881
- switch: "switch",
16882
- button: "button",
16883
- doorbell: "doorbell"
16884
- };
16885
- function buildDescriptor(capName, kind) {
16886
- const t = EVENT_TAXONOMY[kind];
16887
- if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16888
- return {
16889
- ...t,
16890
- icon: legacyIcon(t.iconId)
16891
- };
16892
- }
16893
- Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16894
17331
  var CameraPipelineConfigSchema = object({
16895
17332
  engine: PipelineEngineChoiceSchema.optional(),
16896
17333
  steps: array(PipelineStepInputSchema).readonly(),
@@ -17376,6 +17813,76 @@ method(object({
17376
17813
  auth: "admin"
17377
17814
  });
17378
17815
  /**
17816
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
17817
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
17818
+ * caps into per-camera event-kind descriptors.
17819
+ *
17820
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
17821
+ * is NOT duplicated here — every entry is derived from the single
17822
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
17823
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
17824
+ * control cap means adding one line here (and a taxonomy entry); the anti-
17825
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
17826
+ * eventful cap is missing.
17827
+ */
17828
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
17829
+ var LEGACY_ICON = {
17830
+ motion: "motion",
17831
+ audio: "audio",
17832
+ person: "person",
17833
+ vehicle: "vehicle",
17834
+ animal: "animal",
17835
+ package: "package",
17836
+ door: "door",
17837
+ pir: "pir",
17838
+ smoke: "smoke",
17839
+ water: "water",
17840
+ button: "button",
17841
+ generic: "generic",
17842
+ gas: "smoke",
17843
+ vibration: "generic",
17844
+ tamper: "generic",
17845
+ presence: "person",
17846
+ lock: "generic",
17847
+ siren: "generic",
17848
+ switch: "generic",
17849
+ doorbell: "button"
17850
+ };
17851
+ function legacyIcon(iconId) {
17852
+ return LEGACY_ICON[iconId] ?? "generic";
17853
+ }
17854
+ /**
17855
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
17856
+ * The anti-drift guard cross-checks this against the eventful caps declared
17857
+ * in `packages/types/src/capabilities/*.cap.ts`.
17858
+ */
17859
+ var CAP_TO_KIND = {
17860
+ contact: "contact",
17861
+ motion: "motion-sensor",
17862
+ smoke: "smoke",
17863
+ flood: "flood",
17864
+ gas: "gas",
17865
+ "carbon-monoxide": "carbon-monoxide",
17866
+ vibration: "vibration",
17867
+ tamper: "tamper",
17868
+ presence: "presence",
17869
+ "enum-sensor": "enum-sensor",
17870
+ "event-emitter": "device-event",
17871
+ "lock-control": "lock",
17872
+ switch: "switch",
17873
+ button: "button",
17874
+ doorbell: "doorbell"
17875
+ };
17876
+ function buildDescriptor(capName, kind) {
17877
+ const t = EVENT_TAXONOMY[kind];
17878
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17879
+ return {
17880
+ ...t,
17881
+ icon: legacyIcon(t.iconId)
17882
+ };
17883
+ }
17884
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
17885
+ /**
17379
17886
  * server-management — per-NODE singleton capability for a node's ROOT
17380
17887
  * package lifecycle (runtime-updatable node packages).
17381
17888
  *
@@ -18830,7 +19337,28 @@ var FaceInfoSchema = object({
18830
19337
  * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
18831
19338
  * track produced no key frame (e.g. native/onboard source) — the UI falls
18832
19339
  * back to the inline `base64` face crop. */
18833
- keyFrameMediaKey: string().optional()
19340
+ keyFrameMediaKey: string().optional(),
19341
+ /** Winning identity-match cosine (0..1) for this face's track, when an
19342
+ * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
19343
+ * (confidence badge / low-confidence audit). Absent on legacy rows and on
19344
+ * faces that were never auto-recognized. */
19345
+ bestMatchScore: number().optional(),
19346
+ /** Native-scale face short side (px) at recognition time, when the runner
19347
+ * measured it. Lets the UI flag low-resolution auto-assignments. Absent on
19348
+ * legacy rows / runners that reported no native measure. */
19349
+ nativeFaceShortSidePx: number().optional(),
19350
+ /** SUGGESTED identity for this face — a plausible-but-not-confident match that
19351
+ * MISSED auto-assignment (cosine in the suggestion band, or above threshold
19352
+ * but blocked only by the recognition size floor). Mutually exclusive with
19353
+ * `recognizedIdentityId` (a suggestion is NEVER an assignment): the face stays
19354
+ * UNASSIGNED and everything else keeps treating it as unrecognized — the UI
19355
+ * merely offers a one-tap "is this <name>?" confirm. Absent on legacy rows and
19356
+ * on faces that were auto-assigned or below the suggestion band. (2026-07-24) */
19357
+ suggestedIdentityId: string().optional(),
19358
+ /** Peak identity-match cosine (0..1) for `suggestedIdentityId`, captured at the
19359
+ * same moment as `bestMatchScore` (track peak, at close). Lets the UI rank /
19360
+ * badge suggestion confidence. Present iff `suggestedIdentityId` is. (2026-07-24) */
19361
+ suggestedMatchScore: number().optional()
18834
19362
  });
18835
19363
  var FaceFilterEnum = _enum([
18836
19364
  "unassigned",
@@ -20873,36 +21401,6 @@ Object.freeze({
20873
21401
  addonId: null,
20874
21402
  access: "view"
20875
21403
  },
20876
- "advancedNotifier.deleteRule": {
20877
- capName: "advanced-notifier",
20878
- capScope: "system",
20879
- addonId: null,
20880
- access: "delete"
20881
- },
20882
- "advancedNotifier.getHistory": {
20883
- capName: "advanced-notifier",
20884
- capScope: "system",
20885
- addonId: null,
20886
- access: "view"
20887
- },
20888
- "advancedNotifier.getRules": {
20889
- capName: "advanced-notifier",
20890
- capScope: "system",
20891
- addonId: null,
20892
- access: "view"
20893
- },
20894
- "advancedNotifier.testRule": {
20895
- capName: "advanced-notifier",
20896
- capScope: "system",
20897
- addonId: null,
20898
- access: "create"
20899
- },
20900
- "advancedNotifier.upsertRule": {
20901
- capName: "advanced-notifier",
20902
- capScope: "system",
20903
- addonId: null,
20904
- access: "create"
20905
- },
20906
21404
  "alarmPanel.arm": {
20907
21405
  capName: "alarm-panel",
20908
21406
  capScope: "device",
@@ -23207,6 +23705,60 @@ Object.freeze({
23207
23705
  addonId: null,
23208
23706
  access: "create"
23209
23707
  },
23708
+ "notificationRules.createRule": {
23709
+ capName: "notification-rules",
23710
+ capScope: "system",
23711
+ addonId: null,
23712
+ access: "create"
23713
+ },
23714
+ "notificationRules.deleteRule": {
23715
+ capName: "notification-rules",
23716
+ capScope: "system",
23717
+ addonId: null,
23718
+ access: "delete"
23719
+ },
23720
+ "notificationRules.getConditionCatalog": {
23721
+ capName: "notification-rules",
23722
+ capScope: "system",
23723
+ addonId: null,
23724
+ access: "view"
23725
+ },
23726
+ "notificationRules.getHistory": {
23727
+ capName: "notification-rules",
23728
+ capScope: "system",
23729
+ addonId: null,
23730
+ access: "view"
23731
+ },
23732
+ "notificationRules.getRule": {
23733
+ capName: "notification-rules",
23734
+ capScope: "system",
23735
+ addonId: null,
23736
+ access: "view"
23737
+ },
23738
+ "notificationRules.listRules": {
23739
+ capName: "notification-rules",
23740
+ capScope: "system",
23741
+ addonId: null,
23742
+ access: "view"
23743
+ },
23744
+ "notificationRules.setRuleEnabled": {
23745
+ capName: "notification-rules",
23746
+ capScope: "system",
23747
+ addonId: null,
23748
+ access: "create"
23749
+ },
23750
+ "notificationRules.testRule": {
23751
+ capName: "notification-rules",
23752
+ capScope: "system",
23753
+ addonId: null,
23754
+ access: "create"
23755
+ },
23756
+ "notificationRules.updateRule": {
23757
+ capName: "notification-rules",
23758
+ capScope: "system",
23759
+ addonId: null,
23760
+ access: "create"
23761
+ },
23210
23762
  "notifier.cancel": {
23211
23763
  capName: "notifier",
23212
23764
  capScope: "device",