@camstack/addon-post-analysis 1.2.279 → 1.2.281

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.
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  //#region ../types/dist/event-category-BVDXG4tB.mjs
2
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
4
  EventCategory["SystemBoot"] = "system.boot";
@@ -7567,6 +7568,39 @@ function sleep(ms) {
7567
7568
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
7568
7569
  }
7569
7570
  //#endregion
7571
+ //#region ../types/dist/canonical-hash-CPK2Dy60.mjs
7572
+ /**
7573
+ * Deterministic SHA-256 hash of an arbitrary serialisable value. The
7574
+ * canonical form sorts object keys alphabetically at every depth so two
7575
+ * structurally-equal inputs with different key insertion orders produce
7576
+ * the same hash. Returns a 64-char lowercase hex digest.
7577
+ *
7578
+ * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /
7579
+ * accessory-rebuild work when the upstream shape is byte-identical to
7580
+ * the last applied state — preventing user-visible "re-discovery"
7581
+ * notifications on every addon-runner respawn. Each respawn re-fires
7582
+ * `DeviceBindingsChanged` for every cap registration, which without
7583
+ * this guard would propagate redundant pushes.
7584
+ *
7585
+ * Note: this is a SYMPTOMATIC fix layered on top of the binding-change
7586
+ * subscription. The proper fix is a single "device ready" lifecycle
7587
+ * barrier so exports react only when the full cap set has landed —
7588
+ * tracked separately for post-HA-integration work.
7589
+ */
7590
+ function canonicalHash(value) {
7591
+ const canonical = JSON.stringify(value, replaceWithSortedKeys);
7592
+ return createHash("sha256").update(canonical ?? "").digest("hex");
7593
+ }
7594
+ function replaceWithSortedKeys(_key, value) {
7595
+ if (value && typeof value === "object" && !Array.isArray(value)) {
7596
+ const obj = value;
7597
+ const out = {};
7598
+ for (const k of Object.keys(obj).toSorted()) out[k] = obj[k];
7599
+ return out;
7600
+ }
7601
+ return value;
7602
+ }
7603
+ //#endregion
7570
7604
  //#region ../types/dist/err-msg-IQTHeDzc.mjs
7571
7605
  /**
7572
7606
  import { errMsg } from '@camstack/types'
@@ -15361,18 +15395,30 @@ var LlmErrorCodeSchema = _enum([
15361
15395
  "budget-exceeded",
15362
15396
  "adapter-error"
15363
15397
  ]);
15364
- var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15398
+ var LlmGenerateOkSchema = object({
15365
15399
  ok: literal(true),
15366
15400
  text: string(),
15367
15401
  model: string(),
15368
15402
  usage: LlmUsageSchema,
15369
15403
  truncated: boolean(),
15370
15404
  latencyMs: number()
15371
- }), object({
15405
+ });
15406
+ /**
15407
+ * WHO refused, on a `refusal`. `guardrail` = a safety filter in front of the
15408
+ * model rejected the request before it answered (Apple Foundation Models
15409
+ * refuses surveillance-framed prompts in 0.1 s); `model` = the model itself
15410
+ * declined. They are different faults: a guardrail refusal is a property of
15411
+ * the BACKEND, so the provider falls back to the next profile; a model refusal
15412
+ * is an answer. Absent on every other code, and on a refusal a wire cannot
15413
+ * attribute.
15414
+ */
15415
+ var LlmRefusalSourceSchema = _enum(["guardrail", "model"]);
15416
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [LlmGenerateOkSchema, object({
15372
15417
  ok: literal(false),
15373
15418
  code: LlmErrorCodeSchema,
15374
15419
  message: string(),
15375
- retryAfterMs: number().optional()
15420
+ retryAfterMs: number().optional(),
15421
+ refusalSource: LlmRefusalSourceSchema.optional()
15376
15422
  })]);
15377
15423
  /**
15378
15424
  * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
@@ -15602,12 +15648,78 @@ var LlmRuntimeDiskUsageSchema = object({
15602
15648
  modelsBytes: number(),
15603
15649
  freeBytes: number().optional()
15604
15650
  });
15605
- method(LlmGenerateBaseInputSchema.extend({
15651
+ var LlmRuntimeCompleteInputSchema = LlmGenerateBaseInputSchema.extend({
15606
15652
  images: array(LlmImageSchema).optional(),
15607
15653
  runtime: ManagedRuntimeConfigSchema,
15608
15654
  /** The managed profile's timeout, threaded by the hub provider. */
15609
15655
  timeoutMs: number().int().positive().optional()
15610
- }), LlmGenerateResultSchema, {
15656
+ });
15657
+ /**
15658
+ * Why Apple Foundation Models cannot run on a node that has it in principle.
15659
+ * The first three are Apple's own `UnavailableReason`s, verbatim; the rest are
15660
+ * ours: the OS predates the framework (macOS < 26), the helper binary is not
15661
+ * in the installed addon, or it crashed past the respawn bound.
15662
+ */
15663
+ var AppleFmUnavailableReasonSchema = _enum([
15664
+ "deviceNotEligible",
15665
+ "appleIntelligenceNotEnabled",
15666
+ "modelNotReady",
15667
+ "osTooOld",
15668
+ "helperMissing",
15669
+ "helperCrashed"
15670
+ ]);
15671
+ /**
15672
+ * Apple Foundation Models on ONE node — four states, never a boolean (D315).
15673
+ *
15674
+ * - `available` — the helper probed it; what it CAN do is reported
15675
+ * here (`imageInput`, `contextSize`, languages),
15676
+ * never typed by an operator.
15677
+ * - `unavailable` — a known absence, with the reason to act on.
15678
+ * - `unsupported-platform` — not a Mac; there is nothing to enable.
15679
+ * - `unknown` — the probe did not answer (not yet run, timed
15680
+ * out, helper crashed mid-probe). A failed
15681
+ * measurement is NEVER `unavailable` (D393).
15682
+ */
15683
+ var AppleFmAvailabilitySchema = discriminatedUnion("state", [
15684
+ object({
15685
+ state: literal("available"),
15686
+ osVersion: string(),
15687
+ /** Tokens, as the framework reports it. Read at runtime, never assumed. */
15688
+ contextSize: number().int().positive(),
15689
+ /** BCP-47-ish tags (`it-IT`) of the languages the model supports. */
15690
+ languages: array(string()),
15691
+ /** Whether prompts may carry images. `false` on a macOS 26 build. */
15692
+ imageInput: boolean()
15693
+ }),
15694
+ object({
15695
+ state: literal("unavailable"),
15696
+ reason: AppleFmUnavailableReasonSchema,
15697
+ osVersion: string().nullable(),
15698
+ detail: string()
15699
+ }),
15700
+ object({
15701
+ state: literal("unsupported-platform"),
15702
+ platform: string()
15703
+ }),
15704
+ object({
15705
+ state: literal("unknown"),
15706
+ error: string()
15707
+ })
15708
+ ]);
15709
+ /**
15710
+ * A generation on the node's Apple Foundation Models helper. A separate verb
15711
+ * from `complete`, whose `runtime` is a llama.cpp config: overloading it would
15712
+ * drag a GGUF model reference into a backend that has none. `images` is part
15713
+ * of the contract so vision needs no cap change later; a node whose model has
15714
+ * no image input REFUSES them (`unavailable`), never drops them.
15715
+ */
15716
+ var LlmRuntimeAppleCompleteInputSchema = LlmGenerateBaseInputSchema.extend({
15717
+ images: array(LlmImageSchema).optional(),
15718
+ /** The profile's timeout, threaded by the hub provider. */
15719
+ timeoutMs: number().int().positive().optional()
15720
+ });
15721
+ var LlmRuntimeCancelInputSchema = object({ requestId: string() });
15722
+ method(LlmRuntimeCompleteInputSchema, LlmGenerateResultSchema, {
15611
15723
  kind: "mutation",
15612
15724
  auth: "admin",
15613
15725
  timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
@@ -15625,7 +15737,14 @@ method(LlmGenerateBaseInputSchema.extend({
15625
15737
  }), method(object({ file: string() }), _void(), {
15626
15738
  kind: "mutation",
15627
15739
  auth: "admin"
15628
- }), method(object({}), array(LlmNodeModelSchema), { auth: "admin" }), method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" });
15740
+ }), method(object({}), array(LlmNodeModelSchema), { auth: "admin" }), method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" }), method(LlmRuntimeCancelInputSchema, _void(), {
15741
+ kind: "mutation",
15742
+ auth: "admin"
15743
+ }), method(object({}), AppleFmAvailabilitySchema, { auth: "admin" }), method(LlmRuntimeAppleCompleteInputSchema, LlmGenerateResultSchema, {
15744
+ kind: "mutation",
15745
+ auth: "admin",
15746
+ timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
15747
+ });
15629
15748
  /**
15630
15749
  * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15631
15750
  * methods concat-fan across providers; single-row methods route to ONE
@@ -15642,7 +15761,8 @@ var LlmProfileKindSchema = _enum([
15642
15761
  "openai",
15643
15762
  "anthropic",
15644
15763
  "google",
15645
- "managed-local"
15764
+ "managed-local",
15765
+ "apple-foundation"
15646
15766
  ]);
15647
15767
  var LlmProfileSchema = object({
15648
15768
  id: string(),
@@ -15700,7 +15820,14 @@ var LlmProfileSchema = object({
15700
15820
  toolsEnabled: boolean().default(false),
15701
15821
  extraHeaders: record(string(), string()).optional(),
15702
15822
  /** kind === 'managed-local' only (spec §4). */
15703
- runtime: ManagedRuntimeConfigSchema.optional()
15823
+ runtime: ManagedRuntimeConfigSchema.optional(),
15824
+ /**
15825
+ * kind === 'apple-foundation' only: WHERE it runs — the only thing there is
15826
+ * to configure (no key, no URL, no download; `model` is `apple-system`).
15827
+ * `supportsVision` on such a row is stamped by the provider from the node's
15828
+ * reported `imageInput`, never taken from the operator (D62).
15829
+ */
15830
+ appleFoundation: object({ nodeId: string() }).optional()
15704
15831
  });
15705
15832
  /** ConfigUISchema tree passed through untyped on the wire (the
15706
15833
  * notification-output `ConfigSchemaPassthrough` precedent at
@@ -15782,7 +15909,9 @@ var LlmRuntimeNodeSchema = object({
15782
15909
  reachable: boolean(),
15783
15910
  status: LlmRuntimeStatusSchema.optional(),
15784
15911
  disk: LlmRuntimeDiskUsageSchema.optional(),
15785
- error: string().optional()
15912
+ error: string().optional(),
15913
+ /** Absent = the node did not answer the question (render as unknown). */
15914
+ apple: AppleFmAvailabilitySchema.optional()
15786
15915
  });
15787
15916
  var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15788
15917
  var ProfileRefInputSchema = object({
@@ -18870,9 +18999,29 @@ var NcTestResultSchema = object({
18870
18999
  className: string().optional(),
18871
19000
  label: string().optional()
18872
19001
  });
18873
- /** The five rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts`
18874
- * is the same union; the template-vars spec pins the two together. */
19002
+ /** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is the
19003
+ * same union; the template-vars spec pins the two together. `mixed` is DERIVED
19004
+ * (a stored system rule whose kinds span families, D637) and never authored. */
18875
19005
  var NcRuleKindSchema = _enum([
19006
+ "detection",
19007
+ "sensor",
19008
+ "occupancy",
19009
+ "sound",
19010
+ "device-events",
19011
+ "system",
19012
+ "alarm",
19013
+ "mixed"
19014
+ ]);
19015
+ /**
19016
+ * The kinds a SERVED template-variable descriptor may name: the five that
19017
+ * existed before D637, frozen. `getTemplateCatalog` ships `kinds` to every
19018
+ * installed viewer, and a viewer at `@camstack/types` 1.2.25x parses them with a
19019
+ * strict five-member enum — one `device-events` in one descriptor fails the
19020
+ * whole catalog parse. So the WIRE says `system`, and the MATCH widens it:
19021
+ * `templateVarKindApplies` reads a `system` descriptor as applying to every
19022
+ * system rule kind (`device-events`, `system`, `alarm`, `mixed`).
19023
+ */
19024
+ var NcTemplateVarKindSchema = _enum([
18876
19025
  "detection",
18877
19026
  "sensor",
18878
19027
  "occupancy",
@@ -18910,7 +19059,7 @@ var NcTemplateVarGroupSchema = _enum([
18910
19059
  *
18911
19060
  * `families` / `fields` / `kinds` / `deliveries` / `systemEventKinds` say WHERE
18912
19061
  * it has a value; absent = no restriction on that axis. `systemEventKinds` is
18913
- * read only for a `system` rule. `dynamic` declares a FAMILY of names
19062
+ * read only for a system-event rule (any family, or `mixed`). `dynamic` declares a FAMILY of names
18914
19063
  * (`count_<class>`): the descriptor's `name` is the example member.
18915
19064
  */
18916
19065
  var NcTemplateVarDescriptorSchema = object({
@@ -18921,7 +19070,7 @@ var NcTemplateVarDescriptorSchema = object({
18921
19070
  group: NcTemplateVarGroupSchema,
18922
19071
  families: array(NcTemplateFamilySchema).min(1),
18923
19072
  fields: array(NcTemplateFieldSchema).optional(),
18924
- kinds: array(NcRuleKindSchema).optional(),
19073
+ kinds: array(NcTemplateVarKindSchema).optional(),
18925
19074
  deliveries: array(NcDeliverySchema).optional(),
18926
19075
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
18927
19076
  dynamic: object({
@@ -33074,7 +33223,30 @@ var RenameNodeResultSchema = object({
33074
33223
  nodeId: string(),
33075
33224
  name: string()
33076
33225
  });
33077
- method(_void(), array(TopologyNodeSchema).readonly(), { auth: "admin" }), method(NodeAddonInputSchema, SuccessSchema, {
33226
+ /**
33227
+ * The cheap node list. A consumer's rule for a STORED node id (a rule's
33228
+ * `nodeIds`) that is missing from `nodes`, whatever `complete` says: render it
33229
+ * as a removable chip labelled unknown, never drop it (D49). Even a complete
33230
+ * list omits a node that joined and left between two history captures, so a
33231
+ * missing id means "not known to the hub", never "removed". Only the operator
33232
+ * removes it.
33233
+ *
33234
+ * `complete` changes only the WORDING: `false` = no history read has ever
33235
+ * answered, so the list may be missing offline nodes the hub does know (D315).
33236
+ *
33237
+ * `name` is the history row's name (≤ 30 s behind a rename), or the id when
33238
+ * the history has none.
33239
+ */
33240
+ var NodeDirectorySchema = object({
33241
+ nodes: array(object({
33242
+ id: string(),
33243
+ name: string(),
33244
+ isHub: boolean(),
33245
+ isOnline: boolean()
33246
+ })).readonly(),
33247
+ complete: boolean()
33248
+ });
33249
+ method(_void(), array(TopologyNodeSchema).readonly(), { auth: "admin" }), method(_void(), NodeDirectorySchema, { auth: "admin" }), method(NodeAddonInputSchema, SuccessSchema, {
33078
33250
  kind: "mutation",
33079
33251
  auth: "admin"
33080
33252
  }), method(NodeAddonInputSchema, SuccessSchema, {
@@ -43904,12 +44076,30 @@ Object.freeze({
43904
44076
  addonId: null,
43905
44077
  access: "create"
43906
44078
  },
44079
+ "llmRuntime.appleAvailability": {
44080
+ capName: "llm-runtime",
44081
+ capScope: "system",
44082
+ addonId: null,
44083
+ access: "view"
44084
+ },
44085
+ "llmRuntime.cancel": {
44086
+ capName: "llm-runtime",
44087
+ capScope: "system",
44088
+ addonId: null,
44089
+ access: "create"
44090
+ },
43907
44091
  "llmRuntime.complete": {
43908
44092
  capName: "llm-runtime",
43909
44093
  capScope: "system",
43910
44094
  addonId: null,
43911
44095
  access: "create"
43912
44096
  },
44097
+ "llmRuntime.completeApple": {
44098
+ capName: "llm-runtime",
44099
+ capScope: "system",
44100
+ addonId: null,
44101
+ access: "create"
44102
+ },
43913
44103
  "llmRuntime.deleteModel": {
43914
44104
  capName: "llm-runtime",
43915
44105
  capScope: "system",
@@ -44582,6 +44772,12 @@ Object.freeze({
44582
44772
  addonId: null,
44583
44773
  access: "create"
44584
44774
  },
44775
+ "nodes.directory": {
44776
+ capName: "nodes",
44777
+ capScope: "system",
44778
+ addonId: null,
44779
+ access: "view"
44780
+ },
44585
44781
  "nodes.executeQuery": {
44586
44782
  capName: "nodes",
44587
44783
  capScope: "system",
@@ -50366,6 +50562,253 @@ function audioModeOf(value) {
50366
50562
  return null;
50367
50563
  }
50368
50564
  new Set(["devices", "classes"]);
50565
+ /** Every filter, for the callers that must cover all four (guards, tests). */
50566
+ var NC_SYSTEM_EVENT_FILTER_KEYS = [
50567
+ "deviceIds",
50568
+ "deviceTypes",
50569
+ "nodeIds",
50570
+ "packageNames"
50571
+ ];
50572
+ /**
50573
+ * Does `filter` say anything about `kind`?
50574
+ *
50575
+ * `false` means the filter is not part of this kind's vocabulary: the engine
50576
+ * ignores it and an editor hides its input. It never means "excluded".
50577
+ */
50578
+ function systemEventFilterApplies(kind, filter) {
50579
+ switch (filter) {
50580
+ case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind === "alarm-triggered";
50581
+ case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind";
50582
+ case "nodeIds": return kind.startsWith("node-") || kind === "addon-crash-loop" || kind === "addon-update-available" || kind === "addon-updated" || kind === "server-update-available" || kind === "wrapper-update-available" || kind === "server-updated";
50583
+ case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
50584
+ }
50585
+ }
50586
+ /**
50587
+ * WHICH FAMILY every system-event kind belongs to, and which narrowing filters
50588
+ * each family may carry: one table, read by the hub validator, by `ruleKindOf`
50589
+ * and by both rule editors (D637).
50590
+ *
50591
+ * A system rule used to be one editor for thirty-one kinds and five filters.
50592
+ * "Eventi di sistema" carried battery kinds under a liveness `deviceTypes` filter
50593
+ * and went silent for two weeks (D636). The three families are three questions:
50594
+ * about devices, about the installation, and about the alarm panel. A rule
50595
+ * answers one of them. The engine never needed to know, and it still does not:
50596
+ * a mixed stored rule keeps firing. Only AUTHORING is strict.
50597
+ *
50598
+ * The legacy `camera-*` tail has no family (`null`). It is parsed, never produced,
50599
+ * never offered.
50600
+ */
50601
+ var NC_SYSTEM_EVENT_FAMILIES = [
50602
+ "device-events",
50603
+ "system",
50604
+ "alarm"
50605
+ ];
50606
+ function isSystemEventFamily(value) {
50607
+ return NC_SYSTEM_EVENT_FAMILIES.some((family) => family === value);
50608
+ }
50609
+ /** Total over the enum: a kind added to the cap without a family fails tsc here. */
50610
+ var NC_SYSTEM_EVENT_FAMILY = {
50611
+ "device-online": "device-events",
50612
+ "device-offline": "device-events",
50613
+ "device-disabled": "device-events",
50614
+ "device-enabled": "device-events",
50615
+ "device-battery-low": "device-events",
50616
+ "device-battery-normal": "device-events",
50617
+ "device-consumable-low": "device-events",
50618
+ "device-consumable-normal": "device-events",
50619
+ "stream-online": "device-events",
50620
+ "stream-offline": "device-events",
50621
+ "detection-blind": "device-events",
50622
+ "node-online": "system",
50623
+ "node-offline": "system",
50624
+ "node-inference-unavailable": "system",
50625
+ "addon-crash-loop": "system",
50626
+ "addon-update-available": "system",
50627
+ "addon-updated": "system",
50628
+ "server-update-available": "system",
50629
+ "server-updated": "system",
50630
+ "wrapper-update-available": "system",
50631
+ "backup-completed": "system",
50632
+ "export-completed": "system",
50633
+ "alarm-triggered": "alarm",
50634
+ "alarm-armed": "alarm",
50635
+ "alarm-disarmed": "alarm",
50636
+ "alarm-arming": "alarm",
50637
+ "alarm-arm-refused": "alarm",
50638
+ "alarm-pending": "alarm",
50639
+ "alarm-rearmed": "alarm",
50640
+ "alarm-sensor-bypass": "alarm",
50641
+ "alarm-check-failed": "alarm",
50642
+ "camera-online": null,
50643
+ "camera-offline": null,
50644
+ "camera-disabled": null,
50645
+ "camera-enabled": null
50646
+ };
50647
+ new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50648
+ var NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = [...NC_SYSTEM_EVENT_FILTER_KEYS, "excludeDeviceIds"];
50649
+ /**
50650
+ * The filters a family may carry. The four base filters are the union of
50651
+ * `systemEventFilterApplies` over the family's kinds (the spec pins that
50652
+ * equality). `excludeDeviceIds` is a device-events filter only: an alarm is
50653
+ * narrowed to the camera that triggered it and excludes nothing.
50654
+ */
50655
+ var NC_SYSTEM_EVENT_FAMILY_FILTERS = {
50656
+ "device-events": [
50657
+ "deviceIds",
50658
+ "excludeDeviceIds",
50659
+ "deviceTypes"
50660
+ ],
50661
+ system: ["nodeIds", "packageNames"],
50662
+ alarm: ["deviceIds"]
50663
+ };
50664
+ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
50665
+ {
50666
+ id: "device-state",
50667
+ family: "device-events",
50668
+ label: "Device state",
50669
+ kinds: [
50670
+ "device-offline",
50671
+ "device-online",
50672
+ "device-disabled",
50673
+ "device-enabled"
50674
+ ]
50675
+ },
50676
+ {
50677
+ id: "streams",
50678
+ family: "device-events",
50679
+ label: "Streams",
50680
+ kinds: ["stream-offline", "stream-online"]
50681
+ },
50682
+ {
50683
+ id: "battery",
50684
+ family: "device-events",
50685
+ label: "Battery",
50686
+ kinds: ["device-battery-low", "device-battery-normal"]
50687
+ },
50688
+ {
50689
+ id: "consumables",
50690
+ family: "device-events",
50691
+ label: "Consumables",
50692
+ kinds: ["device-consumable-low", "device-consumable-normal"]
50693
+ },
50694
+ {
50695
+ id: "detection-blind",
50696
+ family: "device-events",
50697
+ label: "Detecting nothing",
50698
+ kinds: ["detection-blind"]
50699
+ },
50700
+ {
50701
+ id: "nodes",
50702
+ family: "system",
50703
+ label: "Nodes",
50704
+ kinds: [
50705
+ "node-offline",
50706
+ "node-online",
50707
+ "node-inference-unavailable"
50708
+ ]
50709
+ },
50710
+ {
50711
+ id: "addons",
50712
+ family: "system",
50713
+ label: "Addons",
50714
+ kinds: [
50715
+ "addon-crash-loop",
50716
+ "addon-update-available",
50717
+ "addon-updated"
50718
+ ]
50719
+ },
50720
+ {
50721
+ id: "updates",
50722
+ family: "system",
50723
+ label: "Server & container updates",
50724
+ kinds: [
50725
+ "server-update-available",
50726
+ "server-updated",
50727
+ "wrapper-update-available"
50728
+ ]
50729
+ },
50730
+ {
50731
+ id: "backups-exports",
50732
+ family: "system",
50733
+ label: "Backups & exports",
50734
+ kinds: ["backup-completed", "export-completed"]
50735
+ },
50736
+ {
50737
+ id: "alarm",
50738
+ family: "alarm",
50739
+ label: "Alarm",
50740
+ kinds: NC_ALARM_SYSTEM_EVENT_KINDS
50741
+ }
50742
+ ];
50743
+ function systemEventKindsOfFamily(family) {
50744
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
50745
+ }
50746
+ /**
50747
+ * Why the hub refuses this condition on a new or updated rule. Empty = accepted.
50748
+ * Families are reported in `NC_SYSTEM_EVENT_FAMILIES` order, and filters in
50749
+ * `NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS` order, so a refusal reads the same
50750
+ * every time.
50751
+ */
50752
+ function systemEventFamilyProblems(condition) {
50753
+ if (condition === void 0 || condition.kinds.length === 0) return [{ code: "no-kinds" }];
50754
+ const problems = [];
50755
+ const present = /* @__PURE__ */ new Set();
50756
+ for (const kind of condition.kinds) {
50757
+ const family = NC_SYSTEM_EVENT_FAMILY[kind];
50758
+ if (family === null) problems.push({
50759
+ code: "legacy-kind",
50760
+ kind
50761
+ });
50762
+ else present.add(family);
50763
+ }
50764
+ const families = NC_SYSTEM_EVENT_FAMILIES.filter((f) => present.has(f));
50765
+ if (families.length > 1) problems.push({
50766
+ code: "mixed-families",
50767
+ families
50768
+ });
50769
+ const [family] = families;
50770
+ if (families.length !== 1 || family === void 0) return problems;
50771
+ const allowed = NC_SYSTEM_EVENT_FAMILY_FILTERS[family];
50772
+ for (const filter of NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS) if (condition[filter] !== void 0 && !allowed.includes(filter)) problems.push({
50773
+ code: "filter-outside-family",
50774
+ filter,
50775
+ family
50776
+ });
50777
+ return problems;
50778
+ }
50779
+ function describeSystemEventFamilyProblem(problem) {
50780
+ switch (problem.code) {
50781
+ case "no-kinds": return "a system-event rule needs at least one event kind";
50782
+ case "legacy-kind": return `'${problem.kind}' is a legacy kind and cannot be authored`;
50783
+ case "mixed-families": return `event kinds span ${problem.families.join(" + ")} — one rule per family`;
50784
+ case "filter-outside-family": return `filter '${problem.filter}' is not allowed on a ${problem.family} rule`;
50785
+ }
50786
+ }
50787
+ /** Every kind a `system-event` rule can derive — the three families and `mixed`. */
50788
+ var NC_SYSTEM_RULE_KINDS = [
50789
+ "device-events",
50790
+ "system",
50791
+ "alarm",
50792
+ "mixed"
50793
+ ];
50794
+ /** Is this a kind a `system-event` rule derives (any family, or `mixed`)? */
50795
+ function isSystemRuleKind(kind) {
50796
+ return NC_SYSTEM_RULE_KINDS.some((k) => k === kind);
50797
+ }
50798
+ /** The family a kind authors, or undefined (a camera kind, or `mixed`). */
50799
+ function systemFamilyOfKind(kind) {
50800
+ return isSystemEventFamily(kind) ? kind : void 0;
50801
+ }
50802
+ /** The delivery a system rule rides — named once, compared everywhere. */
50803
+ var NC_SYSTEM_DELIVERY = "system-event";
50804
+ /**
50805
+ * Is this a SYSTEM rule — one about the installation rather than about anything
50806
+ * a camera saw? Takes a plain `string` so a rule carrying a delivery this build
50807
+ * has never heard of still gets an answer instead of a type error.
50808
+ */
50809
+ function isSystemDelivery(delivery) {
50810
+ return delivery === NC_SYSTEM_DELIVERY;
50811
+ }
50369
50812
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
50370
50813
  var WEEKDAY_TO_DAY = {
50371
50814
  Sun: 0,
@@ -51164,10 +51607,38 @@ var CAPTION_VARS = {
51164
51607
  function onAxis(declared, asked) {
51165
51608
  return declared === void 0 || asked === void 0 || declared.includes(asked);
51166
51609
  }
51167
- function onSystemAxis(d, ctx) {
51168
- if (ctx.kind !== "system" || d.systemEventKinds === void 0) return true;
51610
+ /**
51611
+ * Does a descriptor declared for `declared` kinds have a value for a rule of
51612
+ * `kind`?
51613
+ *
51614
+ * `system` on the WIRE stands for every system rule kind (D637): the served
51615
+ * catalog keeps the five pre-D637 tokens so installed viewers still parse it
51616
+ * (`NcTemplateVarKindSchema`), and the family split lives here, in the match.
51617
+ * Exported so the admin's local interpreter of the served catalog asks the
51618
+ * same question instead of restating it.
51619
+ */
51620
+ function templateVarKindApplies(declared, kind) {
51621
+ if (declared === void 0 || kind === void 0) return true;
51622
+ if (declared.some((k) => k === kind)) return true;
51623
+ return isSystemRuleKind(kind) && declared.includes("system");
51624
+ }
51625
+ /**
51626
+ * Is a descriptor that names `systemEventKinds` on the event-kind axis of a
51627
+ * system rule? Read only for a system rule kind; any other kind ignores it.
51628
+ *
51629
+ * Event kinds chosen: the descriptor must speak about one of them. None chosen
51630
+ * yet: a FAMILY kind watches its own family's event kinds (a device-events rule
51631
+ * is never offered the alarm's `{{mode}}`), and `mixed` watches all of them.
51632
+ */
51633
+ function templateVarSystemEventApplies(d, ctx) {
51634
+ if (!isSystemRuleKind(ctx.kind) || d.systemEventKinds === void 0) return true;
51635
+ const declared = d.systemEventKinds;
51169
51636
  const asked = ctx.systemEventKinds ?? [];
51170
- return asked.length === 0 || asked.some((k) => d.systemEventKinds?.includes(k) === true);
51637
+ if (asked.length > 0) return asked.some((k) => declared.includes(k));
51638
+ const family = ctx.kind === void 0 ? void 0 : systemFamilyOfKind(ctx.kind);
51639
+ if (family === void 0) return true;
51640
+ const own = systemEventKindsOfFamily(family);
51641
+ return declared.some((k) => own.includes(k));
51171
51642
  }
51172
51643
  /**
51173
51644
  * The variables that have a value in `ctx`, in catalog order. Pure.
@@ -51181,27 +51652,13 @@ function templateVarsFor(ctx) {
51181
51652
  const allowed = CAPTION_VARS[ctx.field];
51182
51653
  return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && allowed.includes(d.name) && d.dynamic === void 0);
51183
51654
  }
51184
- return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && onAxis(d.fields, ctx.field) && onAxis(d.kinds, ctx.kind) && onAxis(d.deliveries, ctx.delivery) && onSystemAxis(d, ctx));
51655
+ return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && onAxis(d.fields, ctx.field) && templateVarKindApplies(d.kinds, ctx.kind) && onAxis(d.deliveries, ctx.delivery) && templateVarSystemEventApplies(d, ctx));
51185
51656
  }
51186
51657
  /** True when `name` is supplied in `ctx`, dynamic families (`count_<class>`) included. */
51187
51658
  function isKnownTemplateVar(name, ctx) {
51188
51659
  return templateVarsFor(ctx).some((d) => d.dynamic !== void 0 ? name.startsWith(d.dynamic.prefix) && name.length > d.dynamic.prefix.length : d.name === name);
51189
51660
  }
51190
51661
  /**
51191
- * Does `filter` say anything about `kind`?
51192
- *
51193
- * `false` means the filter is not part of this kind's vocabulary: the engine
51194
- * ignores it and an editor hides its input. It never means "excluded".
51195
- */
51196
- function systemEventFilterApplies(kind, filter) {
51197
- switch (filter) {
51198
- case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind === "alarm-triggered" || kind === "export-completed";
51199
- case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind";
51200
- case "nodeIds": return kind.startsWith("node-") || kind === "addon-crash-loop" || kind === "addon-update-available" || kind === "addon-updated" || kind === "server-update-available" || kind === "wrapper-update-available" || kind === "server-updated";
51201
- case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
51202
- }
51203
- }
51204
- /**
51205
51662
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
51206
51663
  *
51207
51664
  * Spec: `docs/superpowers/specs/2026-07-24-nc-occupancy-timelapse-design.md`
@@ -52205,4 +52662,4 @@ function vectorDimFromBase64(encoded) {
52205
52662
  return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
52206
52663
  }
52207
52664
  //#endregion
52208
- export { RECORDING_SOURCE_CAMSTACK_ADDON as $, CamProfileSchema as $t, NC_SNOOZE_MAX_MINUTES as A, isScheduleActive as At, NcRuleUpdateInputSchema as B, readDeviceStateFrom as Bt, MAX_BIRTH_DECISION_RECORDS as C, faceGalleryCapability as Ct, NC_ALARM_SYSTEM_EVENT_KINDS as D, isEventOwnerType as Dt, MediaFileKindEnum as E, isDetectionMacroClass as Et, NcConditionDescriptorSchema as F, occupancyScope as Ft, NcSystemEventKindSchema as G, storageOccupancyCapability as Gt, NcSnoozeInputSchema as H, resolveLocationMode as Ht, NcRuleInputSchema as I, parseProcStatus as It, NcTemplatePreviewSchema as J, templateVarsFor as Jt, NcTaxonomySchema as K, subKindsOf as Kt, NcRulePatchSchema as L, pickClusterStepModels as Lt, NC_TEMPLATE_VARS as M, kebabToCamel as Mt, NcAlarmConfigSchema as N, mayWriteToLocation as Nt, NC_CONDITION_CATALOG as O, isKnownTemplateVar as Ot, NcAlarmSettingsPatchSchema as P, notificationRulesCapability as Pt, RECORDING_EXPORT_MAX_READ_BYTES as Q, BaseAddon as Qt, NcRuleSchema as R, pipelineAnalyticsCapability as Rt, MAX_ARCHIVED_DEBUG_NOTES as S, evictionPolicyOfLocation as St, MOTION_CLOSE_AFTER_MS as T, hfModelUrl as Tt, NcSnoozeSchema as U, resolvePoolMemoryPolicy as Ut, NcScheduleSchema as V, readTimelapseGeneratedAt as Vt, NcSnoozeSuppressedSchema as W, sceneMonitorCapability as Wt, OpsLogEntrySchema as X, zoneAnalyticsCapability as Xt, NcTemplateVarDescriptorSchema as Y, vectorDimFromBase64 as Yt, PoolMemoryWatchdog as Z, errMsg as Zt, FIRST_LEVEL_MACRO_CLASSES as _, string as _n, cosineSimilarity as _t, BirthDecisionRecordSchema as a, isDeviceScopedCap as an, SceneMonitorSchema as at, LabelAttributionSchema as b, encodeVectorBase64 as bt, CoverStateSchema as c, _enum as cn, TimelapseRulePatchSchema as ct, DEFAULT_TIMELAPSE_PREVIEW_TEXT as d, discriminatedUnion as dn, addonWidgetsSourceCapability as dt, DeviceType as en, RetrainStatusSchema as et, DETECTION_MACRO_CLASSES as f, literal as fn, alarmPanelCapability as ft, EVENT_OWNER_TYPES as g, record as gn, buildEventKindDescriptor as gt, EVENT_KIND_BY_CAP as h, partialRecord as hn, audioModeOf as ht, BaseDevice as i, hydrateSchema as in, SCENE_DIVERGED as it, NC_TAXONOMY as j, isSourceCap as jt, NC_DEFAULT_SNOOZE_MINUTES as k, isMediaOwnerType as kt, DEFAULT_EVENT_COLOR as l, array as ln, TimelapseRuleSchema as lt, DeclaredDevices as m, object as mn, audioMetricsCapability as mt, AlarmArmModeSchema as n, customAction as nn, SCENE_DEFAULT_ANCHOR_THRESHOLD as nt, CLUSTER_MODEL_SCOPED_STEPS as o, nodePin as on, TIMELAPSE_DENSE_FLOOR_SEC as ot, DETECTION_PIPELINE_CAP_NAME as p, number as pn, assertTimelapseCadences as pt, NcTemplatePreviewInputSchema as q, systemEventFilterApplies as qt, ArchivedDebugNoteSchema as r, defineCustomActions as rn, SCENE_DEFAULT_UNCOVERED_POLICY as rt, COCO_TO_MACRO as s, sleep as sn, TimelapseRuleInputSchema as st, AUDIO_MACRO_LABELS as t, createEvent as tn, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, boolean as un, TrackSourceSchema as ut, FULL_IMAGE_BBOX as v, unknown as vn, deriveRecordingMode as vt, MAX_BIRTH_LATENCY_PROXY_MS as w, failureContributionCapability as wt, LockStateSchema as x, evaluateSensorEdge as xt, FailureCounters as y, EventCategory as yn, embeddingEncoderCapability as yt, NcRuleTargetSchema as z, plateGalleryCapability as zt };
52665
+ export { RECORDING_EXPORT_MAX_READ_BYTES as $, vectorDimFromBase64 as $t, NC_SNOOZE_MAX_MINUTES as A, isKnownTemplateVar as At, NcRuleTargetSchema as B, pickClusterStepModels as Bt, MAX_BIRTH_DECISION_RECORDS as C, unknown as Cn, evaluateSensorEdge as Ct, NC_ALARM_SYSTEM_EVENT_KINDS as D, hfModelUrl as Dt, MediaFileKindEnum as E, failureContributionCapability as Et, NcConditionDescriptorSchema as F, kebabToCamel as Ft, NcSnoozeSuppressedSchema as G, resolveLocationMode as Gt, NcScheduleSchema as H, plateGalleryCapability as Ht, NcConditionsSchema as I, mayWriteToLocation as It, NcTemplatePreviewInputSchema as J, storageOccupancyCapability as Jt, NcSystemEventKindSchema as K, resolvePoolMemoryPolicy as Kt, NcRuleInputSchema as L, notificationRulesCapability as Lt, NC_TEMPLATE_VARS as M, isScheduleActive as Mt, NcAlarmConfigSchema as N, isSourceCap as Nt, NC_CONDITION_CATALOG as O, isDetectionMacroClass as Ot, NcAlarmSettingsPatchSchema as P, isSystemDelivery as Pt, PoolMemoryWatchdog as Q, templateVarsFor as Qt, NcRulePatchSchema as R, occupancyScope as Rt, MAX_ARCHIVED_DEBUG_NOTES as S, string as Sn, encodeVectorBase64 as St, MOTION_CLOSE_AFTER_MS as T, faceGalleryCapability as Tt, NcSnoozeInputSchema as U, readDeviceStateFrom as Ut, NcRuleUpdateInputSchema as V, pipelineAnalyticsCapability as Vt, NcSnoozeSchema as W, readTimelapseGeneratedAt as Wt, NcTemplateVarDescriptorSchema as X, systemEventFamilyProblems as Xt, NcTemplatePreviewSchema as Y, subKindsOf as Yt, OpsLogEntrySchema as Z, systemEventFilterApplies as Zt, FIRST_LEVEL_MACRO_CLASSES as _, literal as _n, buildEventKindDescriptor as _t, BirthDecisionRecordSchema as a, DeviceType as an, SCENE_DIVERGED as at, LabelAttributionSchema as b, partialRecord as bn, describeSystemEventFamilyProblem as bt, CoverStateSchema as c, defineCustomActions as cn, TimelapseRuleInputSchema as ct, DEFAULT_TIMELAPSE_PREVIEW_TEXT as d, nodePin as dn, TrackSourceSchema as dt, zoneAnalyticsCapability as en, RECORDING_SOURCE_CAMSTACK_ADDON as et, DETECTION_MACRO_CLASSES as f, sleep as fn, addonWidgetsSourceCapability as ft, EVENT_OWNER_TYPES as g, discriminatedUnion as gn, audioModeOf as gt, EVENT_KIND_BY_CAP as h, boolean as hn, audioMetricsCapability as ht, BaseDevice as i, CamProfileSchema as in, SCENE_DEFAULT_UNCOVERED_POLICY as it, NC_TAXONOMY as j, isMediaOwnerType as jt, NC_DEFAULT_SNOOZE_MINUTES as k, isEventOwnerType as kt, DEFAULT_EVENT_COLOR as l, hydrateSchema as ln, TimelapseRulePatchSchema as lt, DeclaredDevices as m, array as mn, assertTimelapseCadences as mt, AlarmArmModeSchema as n, canonicalHash as nn, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as nt, CLUSTER_MODEL_SCOPED_STEPS as o, createEvent as on, SceneMonitorSchema as ot, DETECTION_PIPELINE_CAP_NAME as p, _enum as pn, alarmPanelCapability as pt, NcTaxonomySchema as q, sceneMonitorCapability as qt, ArchivedDebugNoteSchema as r, BaseAddon as rn, SCENE_DEFAULT_ANCHOR_THRESHOLD as rt, COCO_TO_MACRO as s, customAction as sn, TIMELAPSE_DENSE_FLOOR_SEC as st, AUDIO_MACRO_LABELS as t, errMsg as tn, RetrainStatusSchema as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, isDeviceScopedCap as un, TimelapseRuleSchema as ut, FULL_IMAGE_BBOX as v, number as vn, cosineSimilarity as vt, MAX_BIRTH_LATENCY_PROXY_MS as w, EventCategory as wn, evictionPolicyOfLocation as wt, LockStateSchema as x, record as xn, embeddingEncoderCapability as xt, FailureCounters as y, object as yn, deriveRecordingMode as yt, NcRuleSchema as z, parseProcStatus as zt };