@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.
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  enumerable: true
30
30
  }) : target, mod));
31
31
  //#endregion
32
+ let node_crypto = require("node:crypto");
32
33
  //#region ../types/dist/event-category-BVDXG4tB.mjs
33
34
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
34
35
  EventCategory["SystemBoot"] = "system.boot";
@@ -7598,6 +7599,39 @@ function sleep(ms) {
7598
7599
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
7599
7600
  }
7600
7601
  //#endregion
7602
+ //#region ../types/dist/canonical-hash-CPK2Dy60.mjs
7603
+ /**
7604
+ * Deterministic SHA-256 hash of an arbitrary serialisable value. The
7605
+ * canonical form sorts object keys alphabetically at every depth so two
7606
+ * structurally-equal inputs with different key insertion orders produce
7607
+ * the same hash. Returns a 64-char lowercase hex digest.
7608
+ *
7609
+ * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /
7610
+ * accessory-rebuild work when the upstream shape is byte-identical to
7611
+ * the last applied state — preventing user-visible "re-discovery"
7612
+ * notifications on every addon-runner respawn. Each respawn re-fires
7613
+ * `DeviceBindingsChanged` for every cap registration, which without
7614
+ * this guard would propagate redundant pushes.
7615
+ *
7616
+ * Note: this is a SYMPTOMATIC fix layered on top of the binding-change
7617
+ * subscription. The proper fix is a single "device ready" lifecycle
7618
+ * barrier so exports react only when the full cap set has landed —
7619
+ * tracked separately for post-HA-integration work.
7620
+ */
7621
+ function canonicalHash(value) {
7622
+ const canonical = JSON.stringify(value, replaceWithSortedKeys);
7623
+ return (0, node_crypto.createHash)("sha256").update(canonical ?? "").digest("hex");
7624
+ }
7625
+ function replaceWithSortedKeys(_key, value) {
7626
+ if (value && typeof value === "object" && !Array.isArray(value)) {
7627
+ const obj = value;
7628
+ const out = {};
7629
+ for (const k of Object.keys(obj).toSorted()) out[k] = obj[k];
7630
+ return out;
7631
+ }
7632
+ return value;
7633
+ }
7634
+ //#endregion
7601
7635
  //#region ../types/dist/err-msg-IQTHeDzc.mjs
7602
7636
  /**
7603
7637
  import { errMsg } from '@camstack/types'
@@ -15392,18 +15426,30 @@ var LlmErrorCodeSchema = _enum([
15392
15426
  "budget-exceeded",
15393
15427
  "adapter-error"
15394
15428
  ]);
15395
- var LlmGenerateResultSchema = discriminatedUnion("ok", [object({
15429
+ var LlmGenerateOkSchema = object({
15396
15430
  ok: literal(true),
15397
15431
  text: string(),
15398
15432
  model: string(),
15399
15433
  usage: LlmUsageSchema,
15400
15434
  truncated: boolean(),
15401
15435
  latencyMs: number()
15402
- }), object({
15436
+ });
15437
+ /**
15438
+ * WHO refused, on a `refusal`. `guardrail` = a safety filter in front of the
15439
+ * model rejected the request before it answered (Apple Foundation Models
15440
+ * refuses surveillance-framed prompts in 0.1 s); `model` = the model itself
15441
+ * declined. They are different faults: a guardrail refusal is a property of
15442
+ * the BACKEND, so the provider falls back to the next profile; a model refusal
15443
+ * is an answer. Absent on every other code, and on a refusal a wire cannot
15444
+ * attribute.
15445
+ */
15446
+ var LlmRefusalSourceSchema = _enum(["guardrail", "model"]);
15447
+ var LlmGenerateResultSchema = discriminatedUnion("ok", [LlmGenerateOkSchema, object({
15403
15448
  ok: literal(false),
15404
15449
  code: LlmErrorCodeSchema,
15405
15450
  message: string(),
15406
- retryAfterMs: number().optional()
15451
+ retryAfterMs: number().optional(),
15452
+ refusalSource: LlmRefusalSourceSchema.optional()
15407
15453
  })]);
15408
15454
  /**
15409
15455
  * `Uint8Array` is the sanctioned binary convention — superjson + the UDS
@@ -15633,12 +15679,78 @@ var LlmRuntimeDiskUsageSchema = object({
15633
15679
  modelsBytes: number(),
15634
15680
  freeBytes: number().optional()
15635
15681
  });
15636
- method(LlmGenerateBaseInputSchema.extend({
15682
+ var LlmRuntimeCompleteInputSchema = LlmGenerateBaseInputSchema.extend({
15637
15683
  images: array(LlmImageSchema).optional(),
15638
15684
  runtime: ManagedRuntimeConfigSchema,
15639
15685
  /** The managed profile's timeout, threaded by the hub provider. */
15640
15686
  timeoutMs: number().int().positive().optional()
15641
- }), LlmGenerateResultSchema, {
15687
+ });
15688
+ /**
15689
+ * Why Apple Foundation Models cannot run on a node that has it in principle.
15690
+ * The first three are Apple's own `UnavailableReason`s, verbatim; the rest are
15691
+ * ours: the OS predates the framework (macOS < 26), the helper binary is not
15692
+ * in the installed addon, or it crashed past the respawn bound.
15693
+ */
15694
+ var AppleFmUnavailableReasonSchema = _enum([
15695
+ "deviceNotEligible",
15696
+ "appleIntelligenceNotEnabled",
15697
+ "modelNotReady",
15698
+ "osTooOld",
15699
+ "helperMissing",
15700
+ "helperCrashed"
15701
+ ]);
15702
+ /**
15703
+ * Apple Foundation Models on ONE node — four states, never a boolean (D315).
15704
+ *
15705
+ * - `available` — the helper probed it; what it CAN do is reported
15706
+ * here (`imageInput`, `contextSize`, languages),
15707
+ * never typed by an operator.
15708
+ * - `unavailable` — a known absence, with the reason to act on.
15709
+ * - `unsupported-platform` — not a Mac; there is nothing to enable.
15710
+ * - `unknown` — the probe did not answer (not yet run, timed
15711
+ * out, helper crashed mid-probe). A failed
15712
+ * measurement is NEVER `unavailable` (D393).
15713
+ */
15714
+ var AppleFmAvailabilitySchema = discriminatedUnion("state", [
15715
+ object({
15716
+ state: literal("available"),
15717
+ osVersion: string(),
15718
+ /** Tokens, as the framework reports it. Read at runtime, never assumed. */
15719
+ contextSize: number().int().positive(),
15720
+ /** BCP-47-ish tags (`it-IT`) of the languages the model supports. */
15721
+ languages: array(string()),
15722
+ /** Whether prompts may carry images. `false` on a macOS 26 build. */
15723
+ imageInput: boolean()
15724
+ }),
15725
+ object({
15726
+ state: literal("unavailable"),
15727
+ reason: AppleFmUnavailableReasonSchema,
15728
+ osVersion: string().nullable(),
15729
+ detail: string()
15730
+ }),
15731
+ object({
15732
+ state: literal("unsupported-platform"),
15733
+ platform: string()
15734
+ }),
15735
+ object({
15736
+ state: literal("unknown"),
15737
+ error: string()
15738
+ })
15739
+ ]);
15740
+ /**
15741
+ * A generation on the node's Apple Foundation Models helper. A separate verb
15742
+ * from `complete`, whose `runtime` is a llama.cpp config: overloading it would
15743
+ * drag a GGUF model reference into a backend that has none. `images` is part
15744
+ * of the contract so vision needs no cap change later; a node whose model has
15745
+ * no image input REFUSES them (`unavailable`), never drops them.
15746
+ */
15747
+ var LlmRuntimeAppleCompleteInputSchema = LlmGenerateBaseInputSchema.extend({
15748
+ images: array(LlmImageSchema).optional(),
15749
+ /** The profile's timeout, threaded by the hub provider. */
15750
+ timeoutMs: number().int().positive().optional()
15751
+ });
15752
+ var LlmRuntimeCancelInputSchema = object({ requestId: string() });
15753
+ method(LlmRuntimeCompleteInputSchema, LlmGenerateResultSchema, {
15642
15754
  kind: "mutation",
15643
15755
  auth: "admin",
15644
15756
  timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
@@ -15656,7 +15768,14 @@ method(LlmGenerateBaseInputSchema.extend({
15656
15768
  }), method(object({ file: string() }), _void(), {
15657
15769
  kind: "mutation",
15658
15770
  auth: "admin"
15659
- }), method(object({}), array(LlmNodeModelSchema), { auth: "admin" }), method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" });
15771
+ }), method(object({}), array(LlmNodeModelSchema), { auth: "admin" }), method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" }), method(LlmRuntimeCancelInputSchema, _void(), {
15772
+ kind: "mutation",
15773
+ auth: "admin"
15774
+ }), method(object({}), AppleFmAvailabilitySchema, { auth: "admin" }), method(LlmRuntimeAppleCompleteInputSchema, LlmGenerateResultSchema, {
15775
+ kind: "mutation",
15776
+ auth: "admin",
15777
+ timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
15778
+ });
15660
15779
  /**
15661
15780
  * `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
15662
15781
  * methods concat-fan across providers; single-row methods route to ONE
@@ -15673,7 +15792,8 @@ var LlmProfileKindSchema = _enum([
15673
15792
  "openai",
15674
15793
  "anthropic",
15675
15794
  "google",
15676
- "managed-local"
15795
+ "managed-local",
15796
+ "apple-foundation"
15677
15797
  ]);
15678
15798
  var LlmProfileSchema = object({
15679
15799
  id: string(),
@@ -15731,7 +15851,14 @@ var LlmProfileSchema = object({
15731
15851
  toolsEnabled: boolean().default(false),
15732
15852
  extraHeaders: record(string(), string()).optional(),
15733
15853
  /** kind === 'managed-local' only (spec §4). */
15734
- runtime: ManagedRuntimeConfigSchema.optional()
15854
+ runtime: ManagedRuntimeConfigSchema.optional(),
15855
+ /**
15856
+ * kind === 'apple-foundation' only: WHERE it runs — the only thing there is
15857
+ * to configure (no key, no URL, no download; `model` is `apple-system`).
15858
+ * `supportsVision` on such a row is stamped by the provider from the node's
15859
+ * reported `imageInput`, never taken from the operator (D62).
15860
+ */
15861
+ appleFoundation: object({ nodeId: string() }).optional()
15735
15862
  });
15736
15863
  /** ConfigUISchema tree passed through untyped on the wire (the
15737
15864
  * notification-output `ConfigSchemaPassthrough` precedent at
@@ -15813,7 +15940,9 @@ var LlmRuntimeNodeSchema = object({
15813
15940
  reachable: boolean(),
15814
15941
  status: LlmRuntimeStatusSchema.optional(),
15815
15942
  disk: LlmRuntimeDiskUsageSchema.optional(),
15816
- error: string().optional()
15943
+ error: string().optional(),
15944
+ /** Absent = the node did not answer the question (render as unknown). */
15945
+ apple: AppleFmAvailabilitySchema.optional()
15817
15946
  });
15818
15947
  var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
15819
15948
  var ProfileRefInputSchema = object({
@@ -18901,9 +19030,29 @@ var NcTestResultSchema = object({
18901
19030
  className: string().optional(),
18902
19031
  label: string().optional()
18903
19032
  });
18904
- /** The five rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts`
18905
- * is the same union; the template-vars spec pins the two together. */
19033
+ /** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is the
19034
+ * same union; the template-vars spec pins the two together. `mixed` is DERIVED
19035
+ * (a stored system rule whose kinds span families, D637) and never authored. */
18906
19036
  var NcRuleKindSchema = _enum([
19037
+ "detection",
19038
+ "sensor",
19039
+ "occupancy",
19040
+ "sound",
19041
+ "device-events",
19042
+ "system",
19043
+ "alarm",
19044
+ "mixed"
19045
+ ]);
19046
+ /**
19047
+ * The kinds a SERVED template-variable descriptor may name: the five that
19048
+ * existed before D637, frozen. `getTemplateCatalog` ships `kinds` to every
19049
+ * installed viewer, and a viewer at `@camstack/types` 1.2.25x parses them with a
19050
+ * strict five-member enum — one `device-events` in one descriptor fails the
19051
+ * whole catalog parse. So the WIRE says `system`, and the MATCH widens it:
19052
+ * `templateVarKindApplies` reads a `system` descriptor as applying to every
19053
+ * system rule kind (`device-events`, `system`, `alarm`, `mixed`).
19054
+ */
19055
+ var NcTemplateVarKindSchema = _enum([
18907
19056
  "detection",
18908
19057
  "sensor",
18909
19058
  "occupancy",
@@ -18941,7 +19090,7 @@ var NcTemplateVarGroupSchema = _enum([
18941
19090
  *
18942
19091
  * `families` / `fields` / `kinds` / `deliveries` / `systemEventKinds` say WHERE
18943
19092
  * it has a value; absent = no restriction on that axis. `systemEventKinds` is
18944
- * read only for a `system` rule. `dynamic` declares a FAMILY of names
19093
+ * read only for a system-event rule (any family, or `mixed`). `dynamic` declares a FAMILY of names
18945
19094
  * (`count_<class>`): the descriptor's `name` is the example member.
18946
19095
  */
18947
19096
  var NcTemplateVarDescriptorSchema = object({
@@ -18952,7 +19101,7 @@ var NcTemplateVarDescriptorSchema = object({
18952
19101
  group: NcTemplateVarGroupSchema,
18953
19102
  families: array(NcTemplateFamilySchema).min(1),
18954
19103
  fields: array(NcTemplateFieldSchema).optional(),
18955
- kinds: array(NcRuleKindSchema).optional(),
19104
+ kinds: array(NcTemplateVarKindSchema).optional(),
18956
19105
  deliveries: array(NcDeliverySchema).optional(),
18957
19106
  systemEventKinds: array(NcSystemEventKindSchema).optional(),
18958
19107
  dynamic: object({
@@ -33105,7 +33254,30 @@ var RenameNodeResultSchema = object({
33105
33254
  nodeId: string(),
33106
33255
  name: string()
33107
33256
  });
33108
- method(_void(), array(TopologyNodeSchema).readonly(), { auth: "admin" }), method(NodeAddonInputSchema, SuccessSchema, {
33257
+ /**
33258
+ * The cheap node list. A consumer's rule for a STORED node id (a rule's
33259
+ * `nodeIds`) that is missing from `nodes`, whatever `complete` says: render it
33260
+ * as a removable chip labelled unknown, never drop it (D49). Even a complete
33261
+ * list omits a node that joined and left between two history captures, so a
33262
+ * missing id means "not known to the hub", never "removed". Only the operator
33263
+ * removes it.
33264
+ *
33265
+ * `complete` changes only the WORDING: `false` = no history read has ever
33266
+ * answered, so the list may be missing offline nodes the hub does know (D315).
33267
+ *
33268
+ * `name` is the history row's name (≤ 30 s behind a rename), or the id when
33269
+ * the history has none.
33270
+ */
33271
+ var NodeDirectorySchema = object({
33272
+ nodes: array(object({
33273
+ id: string(),
33274
+ name: string(),
33275
+ isHub: boolean(),
33276
+ isOnline: boolean()
33277
+ })).readonly(),
33278
+ complete: boolean()
33279
+ });
33280
+ method(_void(), array(TopologyNodeSchema).readonly(), { auth: "admin" }), method(_void(), NodeDirectorySchema, { auth: "admin" }), method(NodeAddonInputSchema, SuccessSchema, {
33109
33281
  kind: "mutation",
33110
33282
  auth: "admin"
33111
33283
  }), method(NodeAddonInputSchema, SuccessSchema, {
@@ -43935,12 +44107,30 @@ Object.freeze({
43935
44107
  addonId: null,
43936
44108
  access: "create"
43937
44109
  },
44110
+ "llmRuntime.appleAvailability": {
44111
+ capName: "llm-runtime",
44112
+ capScope: "system",
44113
+ addonId: null,
44114
+ access: "view"
44115
+ },
44116
+ "llmRuntime.cancel": {
44117
+ capName: "llm-runtime",
44118
+ capScope: "system",
44119
+ addonId: null,
44120
+ access: "create"
44121
+ },
43938
44122
  "llmRuntime.complete": {
43939
44123
  capName: "llm-runtime",
43940
44124
  capScope: "system",
43941
44125
  addonId: null,
43942
44126
  access: "create"
43943
44127
  },
44128
+ "llmRuntime.completeApple": {
44129
+ capName: "llm-runtime",
44130
+ capScope: "system",
44131
+ addonId: null,
44132
+ access: "create"
44133
+ },
43944
44134
  "llmRuntime.deleteModel": {
43945
44135
  capName: "llm-runtime",
43946
44136
  capScope: "system",
@@ -44613,6 +44803,12 @@ Object.freeze({
44613
44803
  addonId: null,
44614
44804
  access: "create"
44615
44805
  },
44806
+ "nodes.directory": {
44807
+ capName: "nodes",
44808
+ capScope: "system",
44809
+ addonId: null,
44810
+ access: "view"
44811
+ },
44616
44812
  "nodes.executeQuery": {
44617
44813
  capName: "nodes",
44618
44814
  capScope: "system",
@@ -50397,6 +50593,253 @@ function audioModeOf(value) {
50397
50593
  return null;
50398
50594
  }
50399
50595
  new Set(["devices", "classes"]);
50596
+ /** Every filter, for the callers that must cover all four (guards, tests). */
50597
+ var NC_SYSTEM_EVENT_FILTER_KEYS = [
50598
+ "deviceIds",
50599
+ "deviceTypes",
50600
+ "nodeIds",
50601
+ "packageNames"
50602
+ ];
50603
+ /**
50604
+ * Does `filter` say anything about `kind`?
50605
+ *
50606
+ * `false` means the filter is not part of this kind's vocabulary: the engine
50607
+ * ignores it and an editor hides its input. It never means "excluded".
50608
+ */
50609
+ function systemEventFilterApplies(kind, filter) {
50610
+ switch (filter) {
50611
+ case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind === "alarm-triggered";
50612
+ case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind";
50613
+ 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";
50614
+ case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
50615
+ }
50616
+ }
50617
+ /**
50618
+ * WHICH FAMILY every system-event kind belongs to, and which narrowing filters
50619
+ * each family may carry: one table, read by the hub validator, by `ruleKindOf`
50620
+ * and by both rule editors (D637).
50621
+ *
50622
+ * A system rule used to be one editor for thirty-one kinds and five filters.
50623
+ * "Eventi di sistema" carried battery kinds under a liveness `deviceTypes` filter
50624
+ * and went silent for two weeks (D636). The three families are three questions:
50625
+ * about devices, about the installation, and about the alarm panel. A rule
50626
+ * answers one of them. The engine never needed to know, and it still does not:
50627
+ * a mixed stored rule keeps firing. Only AUTHORING is strict.
50628
+ *
50629
+ * The legacy `camera-*` tail has no family (`null`). It is parsed, never produced,
50630
+ * never offered.
50631
+ */
50632
+ var NC_SYSTEM_EVENT_FAMILIES = [
50633
+ "device-events",
50634
+ "system",
50635
+ "alarm"
50636
+ ];
50637
+ function isSystemEventFamily(value) {
50638
+ return NC_SYSTEM_EVENT_FAMILIES.some((family) => family === value);
50639
+ }
50640
+ /** Total over the enum: a kind added to the cap without a family fails tsc here. */
50641
+ var NC_SYSTEM_EVENT_FAMILY = {
50642
+ "device-online": "device-events",
50643
+ "device-offline": "device-events",
50644
+ "device-disabled": "device-events",
50645
+ "device-enabled": "device-events",
50646
+ "device-battery-low": "device-events",
50647
+ "device-battery-normal": "device-events",
50648
+ "device-consumable-low": "device-events",
50649
+ "device-consumable-normal": "device-events",
50650
+ "stream-online": "device-events",
50651
+ "stream-offline": "device-events",
50652
+ "detection-blind": "device-events",
50653
+ "node-online": "system",
50654
+ "node-offline": "system",
50655
+ "node-inference-unavailable": "system",
50656
+ "addon-crash-loop": "system",
50657
+ "addon-update-available": "system",
50658
+ "addon-updated": "system",
50659
+ "server-update-available": "system",
50660
+ "server-updated": "system",
50661
+ "wrapper-update-available": "system",
50662
+ "backup-completed": "system",
50663
+ "export-completed": "system",
50664
+ "alarm-triggered": "alarm",
50665
+ "alarm-armed": "alarm",
50666
+ "alarm-disarmed": "alarm",
50667
+ "alarm-arming": "alarm",
50668
+ "alarm-arm-refused": "alarm",
50669
+ "alarm-pending": "alarm",
50670
+ "alarm-rearmed": "alarm",
50671
+ "alarm-sensor-bypass": "alarm",
50672
+ "alarm-check-failed": "alarm",
50673
+ "camera-online": null,
50674
+ "camera-offline": null,
50675
+ "camera-disabled": null,
50676
+ "camera-enabled": null
50677
+ };
50678
+ new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50679
+ var NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = [...NC_SYSTEM_EVENT_FILTER_KEYS, "excludeDeviceIds"];
50680
+ /**
50681
+ * The filters a family may carry. The four base filters are the union of
50682
+ * `systemEventFilterApplies` over the family's kinds (the spec pins that
50683
+ * equality). `excludeDeviceIds` is a device-events filter only: an alarm is
50684
+ * narrowed to the camera that triggered it and excludes nothing.
50685
+ */
50686
+ var NC_SYSTEM_EVENT_FAMILY_FILTERS = {
50687
+ "device-events": [
50688
+ "deviceIds",
50689
+ "excludeDeviceIds",
50690
+ "deviceTypes"
50691
+ ],
50692
+ system: ["nodeIds", "packageNames"],
50693
+ alarm: ["deviceIds"]
50694
+ };
50695
+ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
50696
+ {
50697
+ id: "device-state",
50698
+ family: "device-events",
50699
+ label: "Device state",
50700
+ kinds: [
50701
+ "device-offline",
50702
+ "device-online",
50703
+ "device-disabled",
50704
+ "device-enabled"
50705
+ ]
50706
+ },
50707
+ {
50708
+ id: "streams",
50709
+ family: "device-events",
50710
+ label: "Streams",
50711
+ kinds: ["stream-offline", "stream-online"]
50712
+ },
50713
+ {
50714
+ id: "battery",
50715
+ family: "device-events",
50716
+ label: "Battery",
50717
+ kinds: ["device-battery-low", "device-battery-normal"]
50718
+ },
50719
+ {
50720
+ id: "consumables",
50721
+ family: "device-events",
50722
+ label: "Consumables",
50723
+ kinds: ["device-consumable-low", "device-consumable-normal"]
50724
+ },
50725
+ {
50726
+ id: "detection-blind",
50727
+ family: "device-events",
50728
+ label: "Detecting nothing",
50729
+ kinds: ["detection-blind"]
50730
+ },
50731
+ {
50732
+ id: "nodes",
50733
+ family: "system",
50734
+ label: "Nodes",
50735
+ kinds: [
50736
+ "node-offline",
50737
+ "node-online",
50738
+ "node-inference-unavailable"
50739
+ ]
50740
+ },
50741
+ {
50742
+ id: "addons",
50743
+ family: "system",
50744
+ label: "Addons",
50745
+ kinds: [
50746
+ "addon-crash-loop",
50747
+ "addon-update-available",
50748
+ "addon-updated"
50749
+ ]
50750
+ },
50751
+ {
50752
+ id: "updates",
50753
+ family: "system",
50754
+ label: "Server & container updates",
50755
+ kinds: [
50756
+ "server-update-available",
50757
+ "server-updated",
50758
+ "wrapper-update-available"
50759
+ ]
50760
+ },
50761
+ {
50762
+ id: "backups-exports",
50763
+ family: "system",
50764
+ label: "Backups & exports",
50765
+ kinds: ["backup-completed", "export-completed"]
50766
+ },
50767
+ {
50768
+ id: "alarm",
50769
+ family: "alarm",
50770
+ label: "Alarm",
50771
+ kinds: NC_ALARM_SYSTEM_EVENT_KINDS
50772
+ }
50773
+ ];
50774
+ function systemEventKindsOfFamily(family) {
50775
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
50776
+ }
50777
+ /**
50778
+ * Why the hub refuses this condition on a new or updated rule. Empty = accepted.
50779
+ * Families are reported in `NC_SYSTEM_EVENT_FAMILIES` order, and filters in
50780
+ * `NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS` order, so a refusal reads the same
50781
+ * every time.
50782
+ */
50783
+ function systemEventFamilyProblems(condition) {
50784
+ if (condition === void 0 || condition.kinds.length === 0) return [{ code: "no-kinds" }];
50785
+ const problems = [];
50786
+ const present = /* @__PURE__ */ new Set();
50787
+ for (const kind of condition.kinds) {
50788
+ const family = NC_SYSTEM_EVENT_FAMILY[kind];
50789
+ if (family === null) problems.push({
50790
+ code: "legacy-kind",
50791
+ kind
50792
+ });
50793
+ else present.add(family);
50794
+ }
50795
+ const families = NC_SYSTEM_EVENT_FAMILIES.filter((f) => present.has(f));
50796
+ if (families.length > 1) problems.push({
50797
+ code: "mixed-families",
50798
+ families
50799
+ });
50800
+ const [family] = families;
50801
+ if (families.length !== 1 || family === void 0) return problems;
50802
+ const allowed = NC_SYSTEM_EVENT_FAMILY_FILTERS[family];
50803
+ for (const filter of NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS) if (condition[filter] !== void 0 && !allowed.includes(filter)) problems.push({
50804
+ code: "filter-outside-family",
50805
+ filter,
50806
+ family
50807
+ });
50808
+ return problems;
50809
+ }
50810
+ function describeSystemEventFamilyProblem(problem) {
50811
+ switch (problem.code) {
50812
+ case "no-kinds": return "a system-event rule needs at least one event kind";
50813
+ case "legacy-kind": return `'${problem.kind}' is a legacy kind and cannot be authored`;
50814
+ case "mixed-families": return `event kinds span ${problem.families.join(" + ")} — one rule per family`;
50815
+ case "filter-outside-family": return `filter '${problem.filter}' is not allowed on a ${problem.family} rule`;
50816
+ }
50817
+ }
50818
+ /** Every kind a `system-event` rule can derive — the three families and `mixed`. */
50819
+ var NC_SYSTEM_RULE_KINDS = [
50820
+ "device-events",
50821
+ "system",
50822
+ "alarm",
50823
+ "mixed"
50824
+ ];
50825
+ /** Is this a kind a `system-event` rule derives (any family, or `mixed`)? */
50826
+ function isSystemRuleKind(kind) {
50827
+ return NC_SYSTEM_RULE_KINDS.some((k) => k === kind);
50828
+ }
50829
+ /** The family a kind authors, or undefined (a camera kind, or `mixed`). */
50830
+ function systemFamilyOfKind(kind) {
50831
+ return isSystemEventFamily(kind) ? kind : void 0;
50832
+ }
50833
+ /** The delivery a system rule rides — named once, compared everywhere. */
50834
+ var NC_SYSTEM_DELIVERY = "system-event";
50835
+ /**
50836
+ * Is this a SYSTEM rule — one about the installation rather than about anything
50837
+ * a camera saw? Takes a plain `string` so a rule carrying a delivery this build
50838
+ * has never heard of still gets an answer instead of a type error.
50839
+ */
50840
+ function isSystemDelivery(delivery) {
50841
+ return delivery === NC_SYSTEM_DELIVERY;
50842
+ }
50400
50843
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
50401
50844
  var WEEKDAY_TO_DAY = {
50402
50845
  Sun: 0,
@@ -51195,10 +51638,38 @@ var CAPTION_VARS = {
51195
51638
  function onAxis(declared, asked) {
51196
51639
  return declared === void 0 || asked === void 0 || declared.includes(asked);
51197
51640
  }
51198
- function onSystemAxis(d, ctx) {
51199
- if (ctx.kind !== "system" || d.systemEventKinds === void 0) return true;
51641
+ /**
51642
+ * Does a descriptor declared for `declared` kinds have a value for a rule of
51643
+ * `kind`?
51644
+ *
51645
+ * `system` on the WIRE stands for every system rule kind (D637): the served
51646
+ * catalog keeps the five pre-D637 tokens so installed viewers still parse it
51647
+ * (`NcTemplateVarKindSchema`), and the family split lives here, in the match.
51648
+ * Exported so the admin's local interpreter of the served catalog asks the
51649
+ * same question instead of restating it.
51650
+ */
51651
+ function templateVarKindApplies(declared, kind) {
51652
+ if (declared === void 0 || kind === void 0) return true;
51653
+ if (declared.some((k) => k === kind)) return true;
51654
+ return isSystemRuleKind(kind) && declared.includes("system");
51655
+ }
51656
+ /**
51657
+ * Is a descriptor that names `systemEventKinds` on the event-kind axis of a
51658
+ * system rule? Read only for a system rule kind; any other kind ignores it.
51659
+ *
51660
+ * Event kinds chosen: the descriptor must speak about one of them. None chosen
51661
+ * yet: a FAMILY kind watches its own family's event kinds (a device-events rule
51662
+ * is never offered the alarm's `{{mode}}`), and `mixed` watches all of them.
51663
+ */
51664
+ function templateVarSystemEventApplies(d, ctx) {
51665
+ if (!isSystemRuleKind(ctx.kind) || d.systemEventKinds === void 0) return true;
51666
+ const declared = d.systemEventKinds;
51200
51667
  const asked = ctx.systemEventKinds ?? [];
51201
- return asked.length === 0 || asked.some((k) => d.systemEventKinds?.includes(k) === true);
51668
+ if (asked.length > 0) return asked.some((k) => declared.includes(k));
51669
+ const family = ctx.kind === void 0 ? void 0 : systemFamilyOfKind(ctx.kind);
51670
+ if (family === void 0) return true;
51671
+ const own = systemEventKindsOfFamily(family);
51672
+ return declared.some((k) => own.includes(k));
51202
51673
  }
51203
51674
  /**
51204
51675
  * The variables that have a value in `ctx`, in catalog order. Pure.
@@ -51212,27 +51683,13 @@ function templateVarsFor(ctx) {
51212
51683
  const allowed = CAPTION_VARS[ctx.field];
51213
51684
  return NC_TEMPLATE_VARS.filter((d) => d.families.includes(ctx.family) && allowed.includes(d.name) && d.dynamic === void 0);
51214
51685
  }
51215
- 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));
51686
+ 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));
51216
51687
  }
51217
51688
  /** True when `name` is supplied in `ctx`, dynamic families (`count_<class>`) included. */
51218
51689
  function isKnownTemplateVar(name, ctx) {
51219
51690
  return templateVarsFor(ctx).some((d) => d.dynamic !== void 0 ? name.startsWith(d.dynamic.prefix) && name.length > d.dynamic.prefix.length : d.name === name);
51220
51691
  }
51221
51692
  /**
51222
- * Does `filter` say anything about `kind`?
51223
- *
51224
- * `false` means the filter is not part of this kind's vocabulary: the engine
51225
- * ignores it and an editor hides its input. It never means "excluded".
51226
- */
51227
- function systemEventFilterApplies(kind, filter) {
51228
- switch (filter) {
51229
- case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind === "alarm-triggered" || kind === "export-completed";
51230
- case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind";
51231
- 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";
51232
- case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
51233
- }
51234
- }
51235
- /**
51236
51693
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
51237
51694
  *
51238
51695
  * Spec: `docs/superpowers/specs/2026-07-24-nc-occupancy-timelapse-design.md`
@@ -52470,6 +52927,12 @@ Object.defineProperty(exports, "NcConditionDescriptorSchema", {
52470
52927
  return NcConditionDescriptorSchema;
52471
52928
  }
52472
52929
  });
52930
+ Object.defineProperty(exports, "NcConditionsSchema", {
52931
+ enumerable: true,
52932
+ get: function() {
52933
+ return NcConditionsSchema;
52934
+ }
52935
+ });
52473
52936
  Object.defineProperty(exports, "NcRuleInputSchema", {
52474
52937
  enumerable: true,
52475
52938
  get: function() {
@@ -52710,6 +53173,12 @@ Object.defineProperty(exports, "buildEventKindDescriptor", {
52710
53173
  return buildEventKindDescriptor;
52711
53174
  }
52712
53175
  });
53176
+ Object.defineProperty(exports, "canonicalHash", {
53177
+ enumerable: true,
53178
+ get: function() {
53179
+ return canonicalHash;
53180
+ }
53181
+ });
52713
53182
  Object.defineProperty(exports, "cosineSimilarity", {
52714
53183
  enumerable: true,
52715
53184
  get: function() {
@@ -52740,6 +53209,12 @@ Object.defineProperty(exports, "deriveRecordingMode", {
52740
53209
  return deriveRecordingMode;
52741
53210
  }
52742
53211
  });
53212
+ Object.defineProperty(exports, "describeSystemEventFamilyProblem", {
53213
+ enumerable: true,
53214
+ get: function() {
53215
+ return describeSystemEventFamilyProblem;
53216
+ }
53217
+ });
52743
53218
  Object.defineProperty(exports, "discriminatedUnion", {
52744
53219
  enumerable: true,
52745
53220
  get: function() {
@@ -52842,6 +53317,12 @@ Object.defineProperty(exports, "isSourceCap", {
52842
53317
  return isSourceCap;
52843
53318
  }
52844
53319
  });
53320
+ Object.defineProperty(exports, "isSystemDelivery", {
53321
+ enumerable: true,
53322
+ get: function() {
53323
+ return isSystemDelivery;
53324
+ }
53325
+ });
52845
53326
  Object.defineProperty(exports, "kebabToCamel", {
52846
53327
  enumerable: true,
52847
53328
  get: function() {
@@ -52980,6 +53461,12 @@ Object.defineProperty(exports, "subKindsOf", {
52980
53461
  return subKindsOf;
52981
53462
  }
52982
53463
  });
53464
+ Object.defineProperty(exports, "systemEventFamilyProblems", {
53465
+ enumerable: true,
53466
+ get: function() {
53467
+ return systemEventFamilyProblems;
53468
+ }
53469
+ });
52983
53470
  Object.defineProperty(exports, "systemEventFilterApplies", {
52984
53471
  enumerable: true,
52985
53472
  get: function() {