@camstack/system 1.2.7 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +2 -2
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  20. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  21. package/dist/builtins/local-network/local-network.addon.js +1 -1
  22. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  23. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  24. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  25. package/dist/builtins/platform-probe/index.js +1 -1
  26. package/dist/builtins/platform-probe/index.mjs +1 -1
  27. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  28. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  29. package/dist/builtins/snapshot/index.js +1 -1
  30. package/dist/builtins/snapshot/index.mjs +1 -1
  31. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  32. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  33. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  34. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  35. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  36. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  37. package/dist/builtins/system-config/system-config.addon.js +1 -1
  38. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  39. package/dist/builtins/winston-logging/index.js +1 -1
  40. package/dist/builtins/winston-logging/index.mjs +1 -1
  41. package/dist/{dist-DEkwmyIj.js → dist-CdgScQ3H.js} +291 -1
  42. package/dist/{dist-CfcmjXSI.mjs → dist-CxM20CkF.mjs} +291 -1
  43. package/dist/index.js +56 -6
  44. package/dist/index.mjs +56 -6
  45. package/dist/kernel/cap-router-builder.d.ts +18 -1
  46. package/dist/{manifest-python-deps-DIrBiAMS.js → manifest-python-deps-CUHn5wqq.js} +1 -1
  47. package/dist/{manifest-python-deps-QqIGHGMJ.mjs → manifest-python-deps-CcW0WpfS.mjs} +1 -1
  48. package/package.json +1 -1
@@ -2702,7 +2702,6 @@ function expandCapMethods(def) {
2702
2702
  };
2703
2703
  return out;
2704
2704
  }
2705
- /** Shorthand to define a method schema */
2706
2705
  function method(input, output, options) {
2707
2706
  return {
2708
2707
  input,
@@ -2710,6 +2709,7 @@ function method(input, output, options) {
2710
2709
  kind: options?.kind ?? "query",
2711
2710
  auth: options?.auth ?? "protected",
2712
2711
  ...options?.access !== void 0 ? { access: options.access } : {},
2712
+ ...options?.caller !== void 0 ? { caller: options.caller } : {},
2713
2713
  timeoutMs: options?.timeoutMs
2714
2714
  };
2715
2715
  }
@@ -15763,6 +15763,247 @@ var notificationOutputCapability = {
15763
15763
  }
15764
15764
  };
15765
15765
  /**
15766
+ * notification-rules — the Notification Center rule surface (P1 core).
15767
+ *
15768
+ * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
15769
+ * (operator decisions D-1/D-2/D-3 are binding):
15770
+ *
15771
+ * - D-2: rule EVALUATION lives in `addon-post-analysis` (the
15772
+ * `notification-center` module), hooked on the durable persistence
15773
+ * moments (object-event insert, TrackCloser.closeExpired) with a
15774
+ * persisted outbox + retry — never the lossy telemetry bus (D8).
15775
+ * - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
15776
+ * FIRST persisted detection matching the conditions (per-track dedup,
15777
+ * `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
15778
+ * `delivery: 'track-end'` evaluates the finalized track record at close.
15779
+ * - DISPATCH stays behind `notification-output` (rules reference targets
15780
+ * by id; per-backend params are a passthrough blob capped by the
15781
+ * target kind's own caps/degrade engine).
15782
+ *
15783
+ * P1 scope: admin-authored rules only (`createdBy` stamped from the
15784
+ * server-injected caller identity — the first `caller: 'required'`
15785
+ * adopter). The P1 condition subset is: devices, classes(+exclude),
15786
+ * minConfidence, admin zones (any/all + exclude), weekly schedule
15787
+ * windows, and the optional label/identity/plate matchers. User rules,
15788
+ * private zones, per-recipient fan-out and the wider condition table are
15789
+ * P2+ (see spec §7).
15790
+ *
15791
+ * All schemas here are the single source of truth — `NcRule` etc. are
15792
+ * `z.infer` exports; no duplicate interfaces (the advanced-notifier
15793
+ * schema/interface drift is explicitly not repeated).
15794
+ */
15795
+ /** D-3: the urgency of a rule — which persistence moment evaluates it. */
15796
+ var NcDeliverySchema = zod.z.enum(["immediate", "track-end"]);
15797
+ /** One weekly activation window. `startMinute > endMinute` crosses midnight. */
15798
+ var NcScheduleWindowSchema = zod.z.object({
15799
+ /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
15800
+ days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
15801
+ startMinute: zod.z.number().int().min(0).max(1439),
15802
+ endMinute: zod.z.number().int().min(0).max(1439)
15803
+ });
15804
+ /** Weekly schedule — OR of windows; absence on the rule = always active. */
15805
+ var NcScheduleSchema = zod.z.object({
15806
+ windows: zod.z.array(NcScheduleWindowSchema).min(1),
15807
+ /** IANA timezone; default = hub host timezone. */
15808
+ timezone: zod.z.string().optional(),
15809
+ /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
15810
+ invert: zod.z.boolean().optional()
15811
+ });
15812
+ /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
15813
+ var NcPlateMatcherSchema = zod.z.object({
15814
+ values: zod.z.array(zod.z.string().min(1)).min(1),
15815
+ /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
15816
+ maxDistance: zod.z.number().int().min(0).max(3).default(1)
15817
+ });
15818
+ /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
15819
+ var NcZoneConditionSchema = zod.z.object({
15820
+ ids: zod.z.array(zod.z.string().min(1)).min(1),
15821
+ /** Quantifier over `ids` — at least one / every one visited. */
15822
+ match: zod.z.enum(["any", "all"]).default("any")
15823
+ });
15824
+ /**
15825
+ * The P1 condition set — a flat AND of groups; absent group = pass;
15826
+ * membership lists are OR within the list (spec §2.3).
15827
+ */
15828
+ var NcConditionsSchema = zod.z.object({
15829
+ /** Device scope — absent = all devices. */
15830
+ devices: zod.z.array(zod.z.number()).optional(),
15831
+ /** Detector class names (any overlap with the record's class set). */
15832
+ classes: zod.z.array(zod.z.string().min(1)).optional(),
15833
+ /** Veto classes — any overlap fails the rule. */
15834
+ classesExclude: zod.z.array(zod.z.string().min(1)).optional(),
15835
+ /** Minimum detection confidence 0–1 (fails when the record has none). */
15836
+ minConfidence: zod.z.number().min(0).max(1).optional(),
15837
+ /** Admin zone membership over event `zones` / track `zonesVisited`. */
15838
+ zones: NcZoneConditionSchema.optional(),
15839
+ /** Veto zones — any hit fails the rule. */
15840
+ zonesExclude: zod.z.array(zod.z.string().min(1)).optional(),
15841
+ /**
15842
+ * Exact (case-insensitive) match on the record's collapsed `label`
15843
+ * (identity name / plate text / subclass).
15844
+ */
15845
+ labelEquals: zod.z.array(zod.z.string().min(1)).optional(),
15846
+ /**
15847
+ * Identity matcher. P1 boundary: matched against the record's collapsed
15848
+ * `label` (the identity display name propagated by the face pipeline) —
15849
+ * identity-ID matching rides in P2 when identity ids reach the record.
15850
+ */
15851
+ identities: zod.z.array(zod.z.string().min(1)).optional(),
15852
+ /** Fuzzy plate matcher against the record's `label` (plate text). */
15853
+ plates: NcPlateMatcherSchema.optional()
15854
+ });
15855
+ /** One delivery target: a `notification-output` Target ref + passthrough params. */
15856
+ var NcRuleTargetSchema = zod.z.object({
15857
+ /** `notification-output` Target id. */
15858
+ targetId: zod.z.string().min(1),
15859
+ /**
15860
+ * Per-backend passthrough. Recognized keys are mapped onto the canonical
15861
+ * Notification (`priority`, `level`, `sound`, `clickUrl`, `ttl`); the
15862
+ * degrade engine drops what the backend can't render.
15863
+ */
15864
+ params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
15865
+ });
15866
+ /**
15867
+ * Media attachment policy (P1 still-image subset). `best` = the best
15868
+ * AVAILABLE media at dispatch time (D-3); `best-matching` (track-end
15869
+ * condition-best) is deferred — operator open point.
15870
+ */
15871
+ var NcMediaPolicySchema = zod.z.object({ attach: zod.z.enum([
15872
+ "best",
15873
+ "keyFrame",
15874
+ "none"
15875
+ ]).default("best") });
15876
+ /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
15877
+ var NcThrottleSchema = zod.z.object({
15878
+ cooldownSec: zod.z.number().int().min(0).max(86400).default(60),
15879
+ /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
15880
+ scope: zod.z.enum(["rule", "rule-device"]).default("rule-device")
15881
+ });
15882
+ /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
15883
+ var NcRuleInputSchema = zod.z.object({
15884
+ name: zod.z.string().min(1).max(200),
15885
+ enabled: zod.z.boolean().default(true),
15886
+ delivery: NcDeliverySchema,
15887
+ conditions: NcConditionsSchema.default({}),
15888
+ schedule: NcScheduleSchema.optional(),
15889
+ targets: zod.z.array(NcRuleTargetSchema).min(1),
15890
+ media: NcMediaPolicySchema.default({ attach: "best" }),
15891
+ throttle: NcThrottleSchema.default({
15892
+ cooldownSec: 60,
15893
+ scope: "rule-device"
15894
+ }),
15895
+ /** `{{var}}` templating over camera/class/label/zones/confidence/time. */
15896
+ template: zod.z.object({
15897
+ title: zod.z.string().max(500).optional(),
15898
+ body: zod.z.string().max(2e3).optional()
15899
+ }).optional(),
15900
+ /** Canonical notification priority ordinal (1..5); per-target overridable. */
15901
+ priority: zod.z.number().int().min(1).max(5).default(3)
15902
+ });
15903
+ /** Partial patch for `updateRule` — any subset of the input fields. */
15904
+ var NcRulePatchSchema = NcRuleInputSchema.partial();
15905
+ /** A persisted rule. */
15906
+ var NcRuleSchema = NcRuleInputSchema.extend({
15907
+ id: zod.z.string(),
15908
+ /** userId of the admin who created the rule (server-stamped caller). */
15909
+ createdBy: zod.z.string(),
15910
+ createdAt: zod.z.number(),
15911
+ updatedAt: zod.z.number()
15912
+ });
15913
+ var NcTestResultSchema = zod.z.object({
15914
+ recordId: zod.z.string(),
15915
+ recordKind: zod.z.enum(["object-event", "track"]),
15916
+ deviceId: zod.z.number(),
15917
+ timestamp: zod.z.number(),
15918
+ wouldFire: zod.z.boolean(),
15919
+ /** Condition id that failed (first failing group), when `wouldFire` is false. */
15920
+ failedCondition: zod.z.string().optional(),
15921
+ className: zod.z.string().optional(),
15922
+ label: zod.z.string().optional()
15923
+ });
15924
+ var NcConditionDescriptorSchema = zod.z.object({
15925
+ /** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
15926
+ id: zod.z.string(),
15927
+ group: zod.z.enum([
15928
+ "scope",
15929
+ "class",
15930
+ "zones",
15931
+ "quality",
15932
+ "label",
15933
+ "schedule"
15934
+ ]),
15935
+ label: zod.z.string(),
15936
+ /** Editor widget the UI renders — never hardcode per-condition forms. */
15937
+ valueType: zod.z.enum([
15938
+ "deviceIdList",
15939
+ "stringList",
15940
+ "number01",
15941
+ "zoneSelection",
15942
+ "zoneIdList",
15943
+ "schedule",
15944
+ "plateMatcher"
15945
+ ]),
15946
+ operator: zod.z.enum([
15947
+ "in",
15948
+ "notIn",
15949
+ "anyOf",
15950
+ "allOf",
15951
+ "gte",
15952
+ "fuzzyIn",
15953
+ "withinSchedule"
15954
+ ]),
15955
+ /** Which delivery kinds the condition applies to. */
15956
+ appliesTo: zod.z.array(NcDeliverySchema),
15957
+ phase: zod.z.string(),
15958
+ description: zod.z.string().optional()
15959
+ });
15960
+ var notificationRulesCapability = {
15961
+ name: "notification-rules",
15962
+ scope: "system",
15963
+ mode: "singleton",
15964
+ methods: {
15965
+ listRules: method(zod.z.object({}), zod.z.object({ rules: zod.z.array(NcRuleSchema) }), { auth: "admin" }),
15966
+ getRule: method(zod.z.object({ ruleId: zod.z.string() }), zod.z.object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }),
15967
+ createRule: method(zod.z.object({ rule: NcRuleInputSchema }), zod.z.object({ rule: NcRuleSchema }), {
15968
+ kind: "mutation",
15969
+ auth: "admin",
15970
+ caller: "required"
15971
+ }),
15972
+ updateRule: method(zod.z.object({
15973
+ ruleId: zod.z.string(),
15974
+ patch: NcRulePatchSchema
15975
+ }), zod.z.object({ rule: NcRuleSchema }), {
15976
+ kind: "mutation",
15977
+ auth: "admin",
15978
+ caller: "required"
15979
+ }),
15980
+ deleteRule: method(zod.z.object({ ruleId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
15981
+ kind: "mutation",
15982
+ auth: "admin"
15983
+ }),
15984
+ setRuleEnabled: method(zod.z.object({
15985
+ ruleId: zod.z.string(),
15986
+ enabled: zod.z.boolean()
15987
+ }), zod.z.object({ success: zod.z.literal(true) }), {
15988
+ kind: "mutation",
15989
+ auth: "admin"
15990
+ }),
15991
+ /**
15992
+ * Dry-run a rule against recently persisted records (object events for
15993
+ * `immediate`, closed tracks for `track-end`). Mutation kind only to
15994
+ * carry the full rule object safely; no side effects.
15995
+ */
15996
+ testRule: method(zod.z.object({
15997
+ rule: NcRuleInputSchema,
15998
+ lookbackMinutes: zod.z.number().int().min(1).max(1440).default(60)
15999
+ }), zod.z.object({ results: zod.z.array(NcTestResultSchema) }), {
16000
+ kind: "mutation",
16001
+ auth: "admin"
16002
+ }),
16003
+ getConditionCatalog: method(zod.z.object({}), zod.z.object({ catalog: zod.z.array(NcConditionDescriptorSchema) }))
16004
+ }
16005
+ };
16006
+ /**
15766
16007
  * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15767
16008
  * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15768
16009
  * caps stay wire-compatible without a circular cap→cap import.
@@ -22608,6 +22849,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
22608
22849
  networkQualityCapability,
22609
22850
  nodesCapability,
22610
22851
  notificationOutputCapability,
22852
+ notificationRulesCapability,
22611
22853
  notifierCapability,
22612
22854
  numericSensorCapability,
22613
22855
  oauthIntegrationCapability,
@@ -25349,6 +25591,54 @@ var METHOD_ACCESS_MAP = Object.freeze({
25349
25591
  addonId: null,
25350
25592
  access: "create"
25351
25593
  },
25594
+ "notificationRules.createRule": {
25595
+ capName: "notification-rules",
25596
+ capScope: "system",
25597
+ addonId: null,
25598
+ access: "create"
25599
+ },
25600
+ "notificationRules.deleteRule": {
25601
+ capName: "notification-rules",
25602
+ capScope: "system",
25603
+ addonId: null,
25604
+ access: "delete"
25605
+ },
25606
+ "notificationRules.getConditionCatalog": {
25607
+ capName: "notification-rules",
25608
+ capScope: "system",
25609
+ addonId: null,
25610
+ access: "view"
25611
+ },
25612
+ "notificationRules.getRule": {
25613
+ capName: "notification-rules",
25614
+ capScope: "system",
25615
+ addonId: null,
25616
+ access: "view"
25617
+ },
25618
+ "notificationRules.listRules": {
25619
+ capName: "notification-rules",
25620
+ capScope: "system",
25621
+ addonId: null,
25622
+ access: "view"
25623
+ },
25624
+ "notificationRules.setRuleEnabled": {
25625
+ capName: "notification-rules",
25626
+ capScope: "system",
25627
+ addonId: null,
25628
+ access: "create"
25629
+ },
25630
+ "notificationRules.testRule": {
25631
+ capName: "notification-rules",
25632
+ capScope: "system",
25633
+ addonId: null,
25634
+ access: "create"
25635
+ },
25636
+ "notificationRules.updateRule": {
25637
+ capName: "notification-rules",
25638
+ capScope: "system",
25639
+ addonId: null,
25640
+ access: "create"
25641
+ },
25352
25642
  "notifier.cancel": {
25353
25643
  capName: "notifier",
25354
25644
  capScope: "device",
@@ -2702,7 +2702,6 @@ function expandCapMethods(def) {
2702
2702
  };
2703
2703
  return out;
2704
2704
  }
2705
- /** Shorthand to define a method schema */
2706
2705
  function method(input, output, options) {
2707
2706
  return {
2708
2707
  input,
@@ -2710,6 +2709,7 @@ function method(input, output, options) {
2710
2709
  kind: options?.kind ?? "query",
2711
2710
  auth: options?.auth ?? "protected",
2712
2711
  ...options?.access !== void 0 ? { access: options.access } : {},
2712
+ ...options?.caller !== void 0 ? { caller: options.caller } : {},
2713
2713
  timeoutMs: options?.timeoutMs
2714
2714
  };
2715
2715
  }
@@ -15763,6 +15763,247 @@ var notificationOutputCapability = {
15763
15763
  }
15764
15764
  };
15765
15765
  /**
15766
+ * notification-rules — the Notification Center rule surface (P1 core).
15767
+ *
15768
+ * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
15769
+ * (operator decisions D-1/D-2/D-3 are binding):
15770
+ *
15771
+ * - D-2: rule EVALUATION lives in `addon-post-analysis` (the
15772
+ * `notification-center` module), hooked on the durable persistence
15773
+ * moments (object-event insert, TrackCloser.closeExpired) with a
15774
+ * persisted outbox + retry — never the lossy telemetry bus (D8).
15775
+ * - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
15776
+ * FIRST persisted detection matching the conditions (per-track dedup,
15777
+ * `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
15778
+ * `delivery: 'track-end'` evaluates the finalized track record at close.
15779
+ * - DISPATCH stays behind `notification-output` (rules reference targets
15780
+ * by id; per-backend params are a passthrough blob capped by the
15781
+ * target kind's own caps/degrade engine).
15782
+ *
15783
+ * P1 scope: admin-authored rules only (`createdBy` stamped from the
15784
+ * server-injected caller identity — the first `caller: 'required'`
15785
+ * adopter). The P1 condition subset is: devices, classes(+exclude),
15786
+ * minConfidence, admin zones (any/all + exclude), weekly schedule
15787
+ * windows, and the optional label/identity/plate matchers. User rules,
15788
+ * private zones, per-recipient fan-out and the wider condition table are
15789
+ * P2+ (see spec §7).
15790
+ *
15791
+ * All schemas here are the single source of truth — `NcRule` etc. are
15792
+ * `z.infer` exports; no duplicate interfaces (the advanced-notifier
15793
+ * schema/interface drift is explicitly not repeated).
15794
+ */
15795
+ /** D-3: the urgency of a rule — which persistence moment evaluates it. */
15796
+ var NcDeliverySchema = z.enum(["immediate", "track-end"]);
15797
+ /** One weekly activation window. `startMinute > endMinute` crosses midnight. */
15798
+ var NcScheduleWindowSchema = z.object({
15799
+ /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
15800
+ days: z.array(z.number().int().min(0).max(6)).min(1),
15801
+ startMinute: z.number().int().min(0).max(1439),
15802
+ endMinute: z.number().int().min(0).max(1439)
15803
+ });
15804
+ /** Weekly schedule — OR of windows; absence on the rule = always active. */
15805
+ var NcScheduleSchema = z.object({
15806
+ windows: z.array(NcScheduleWindowSchema).min(1),
15807
+ /** IANA timezone; default = hub host timezone. */
15808
+ timezone: z.string().optional(),
15809
+ /** Active OUTSIDE the windows (e.g. "only outside business hours"). */
15810
+ invert: z.boolean().optional()
15811
+ });
15812
+ /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
15813
+ var NcPlateMatcherSchema = z.object({
15814
+ values: z.array(z.string().min(1)).min(1),
15815
+ /** Max Levenshtein distance after normalization (uppercase alphanumeric). */
15816
+ maxDistance: z.number().int().min(0).max(3).default(1)
15817
+ });
15818
+ /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
15819
+ var NcZoneConditionSchema = z.object({
15820
+ ids: z.array(z.string().min(1)).min(1),
15821
+ /** Quantifier over `ids` — at least one / every one visited. */
15822
+ match: z.enum(["any", "all"]).default("any")
15823
+ });
15824
+ /**
15825
+ * The P1 condition set — a flat AND of groups; absent group = pass;
15826
+ * membership lists are OR within the list (spec §2.3).
15827
+ */
15828
+ var NcConditionsSchema = z.object({
15829
+ /** Device scope — absent = all devices. */
15830
+ devices: z.array(z.number()).optional(),
15831
+ /** Detector class names (any overlap with the record's class set). */
15832
+ classes: z.array(z.string().min(1)).optional(),
15833
+ /** Veto classes — any overlap fails the rule. */
15834
+ classesExclude: z.array(z.string().min(1)).optional(),
15835
+ /** Minimum detection confidence 0–1 (fails when the record has none). */
15836
+ minConfidence: z.number().min(0).max(1).optional(),
15837
+ /** Admin zone membership over event `zones` / track `zonesVisited`. */
15838
+ zones: NcZoneConditionSchema.optional(),
15839
+ /** Veto zones — any hit fails the rule. */
15840
+ zonesExclude: z.array(z.string().min(1)).optional(),
15841
+ /**
15842
+ * Exact (case-insensitive) match on the record's collapsed `label`
15843
+ * (identity name / plate text / subclass).
15844
+ */
15845
+ labelEquals: z.array(z.string().min(1)).optional(),
15846
+ /**
15847
+ * Identity matcher. P1 boundary: matched against the record's collapsed
15848
+ * `label` (the identity display name propagated by the face pipeline) —
15849
+ * identity-ID matching rides in P2 when identity ids reach the record.
15850
+ */
15851
+ identities: z.array(z.string().min(1)).optional(),
15852
+ /** Fuzzy plate matcher against the record's `label` (plate text). */
15853
+ plates: NcPlateMatcherSchema.optional()
15854
+ });
15855
+ /** One delivery target: a `notification-output` Target ref + passthrough params. */
15856
+ var NcRuleTargetSchema = z.object({
15857
+ /** `notification-output` Target id. */
15858
+ targetId: z.string().min(1),
15859
+ /**
15860
+ * Per-backend passthrough. Recognized keys are mapped onto the canonical
15861
+ * Notification (`priority`, `level`, `sound`, `clickUrl`, `ttl`); the
15862
+ * degrade engine drops what the backend can't render.
15863
+ */
15864
+ params: z.record(z.string(), z.unknown()).optional()
15865
+ });
15866
+ /**
15867
+ * Media attachment policy (P1 still-image subset). `best` = the best
15868
+ * AVAILABLE media at dispatch time (D-3); `best-matching` (track-end
15869
+ * condition-best) is deferred — operator open point.
15870
+ */
15871
+ var NcMediaPolicySchema = z.object({ attach: z.enum([
15872
+ "best",
15873
+ "keyFrame",
15874
+ "none"
15875
+ ]).default("best") });
15876
+ /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
15877
+ var NcThrottleSchema = z.object({
15878
+ cooldownSec: z.number().int().min(0).max(86400).default(60),
15879
+ /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
15880
+ scope: z.enum(["rule", "rule-device"]).default("rule-device")
15881
+ });
15882
+ /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
15883
+ var NcRuleInputSchema = z.object({
15884
+ name: z.string().min(1).max(200),
15885
+ enabled: z.boolean().default(true),
15886
+ delivery: NcDeliverySchema,
15887
+ conditions: NcConditionsSchema.default({}),
15888
+ schedule: NcScheduleSchema.optional(),
15889
+ targets: z.array(NcRuleTargetSchema).min(1),
15890
+ media: NcMediaPolicySchema.default({ attach: "best" }),
15891
+ throttle: NcThrottleSchema.default({
15892
+ cooldownSec: 60,
15893
+ scope: "rule-device"
15894
+ }),
15895
+ /** `{{var}}` templating over camera/class/label/zones/confidence/time. */
15896
+ template: z.object({
15897
+ title: z.string().max(500).optional(),
15898
+ body: z.string().max(2e3).optional()
15899
+ }).optional(),
15900
+ /** Canonical notification priority ordinal (1..5); per-target overridable. */
15901
+ priority: z.number().int().min(1).max(5).default(3)
15902
+ });
15903
+ /** Partial patch for `updateRule` — any subset of the input fields. */
15904
+ var NcRulePatchSchema = NcRuleInputSchema.partial();
15905
+ /** A persisted rule. */
15906
+ var NcRuleSchema = NcRuleInputSchema.extend({
15907
+ id: z.string(),
15908
+ /** userId of the admin who created the rule (server-stamped caller). */
15909
+ createdBy: z.string(),
15910
+ createdAt: z.number(),
15911
+ updatedAt: z.number()
15912
+ });
15913
+ var NcTestResultSchema = z.object({
15914
+ recordId: z.string(),
15915
+ recordKind: z.enum(["object-event", "track"]),
15916
+ deviceId: z.number(),
15917
+ timestamp: z.number(),
15918
+ wouldFire: z.boolean(),
15919
+ /** Condition id that failed (first failing group), when `wouldFire` is false. */
15920
+ failedCondition: z.string().optional(),
15921
+ className: z.string().optional(),
15922
+ label: z.string().optional()
15923
+ });
15924
+ var NcConditionDescriptorSchema = z.object({
15925
+ /** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
15926
+ id: z.string(),
15927
+ group: z.enum([
15928
+ "scope",
15929
+ "class",
15930
+ "zones",
15931
+ "quality",
15932
+ "label",
15933
+ "schedule"
15934
+ ]),
15935
+ label: z.string(),
15936
+ /** Editor widget the UI renders — never hardcode per-condition forms. */
15937
+ valueType: z.enum([
15938
+ "deviceIdList",
15939
+ "stringList",
15940
+ "number01",
15941
+ "zoneSelection",
15942
+ "zoneIdList",
15943
+ "schedule",
15944
+ "plateMatcher"
15945
+ ]),
15946
+ operator: z.enum([
15947
+ "in",
15948
+ "notIn",
15949
+ "anyOf",
15950
+ "allOf",
15951
+ "gte",
15952
+ "fuzzyIn",
15953
+ "withinSchedule"
15954
+ ]),
15955
+ /** Which delivery kinds the condition applies to. */
15956
+ appliesTo: z.array(NcDeliverySchema),
15957
+ phase: z.string(),
15958
+ description: z.string().optional()
15959
+ });
15960
+ var notificationRulesCapability = {
15961
+ name: "notification-rules",
15962
+ scope: "system",
15963
+ mode: "singleton",
15964
+ methods: {
15965
+ listRules: method(z.object({}), z.object({ rules: z.array(NcRuleSchema) }), { auth: "admin" }),
15966
+ getRule: method(z.object({ ruleId: z.string() }), z.object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }),
15967
+ createRule: method(z.object({ rule: NcRuleInputSchema }), z.object({ rule: NcRuleSchema }), {
15968
+ kind: "mutation",
15969
+ auth: "admin",
15970
+ caller: "required"
15971
+ }),
15972
+ updateRule: method(z.object({
15973
+ ruleId: z.string(),
15974
+ patch: NcRulePatchSchema
15975
+ }), z.object({ rule: NcRuleSchema }), {
15976
+ kind: "mutation",
15977
+ auth: "admin",
15978
+ caller: "required"
15979
+ }),
15980
+ deleteRule: method(z.object({ ruleId: z.string() }), z.object({ success: z.literal(true) }), {
15981
+ kind: "mutation",
15982
+ auth: "admin"
15983
+ }),
15984
+ setRuleEnabled: method(z.object({
15985
+ ruleId: z.string(),
15986
+ enabled: z.boolean()
15987
+ }), z.object({ success: z.literal(true) }), {
15988
+ kind: "mutation",
15989
+ auth: "admin"
15990
+ }),
15991
+ /**
15992
+ * Dry-run a rule against recently persisted records (object events for
15993
+ * `immediate`, closed tracks for `track-end`). Mutation kind only to
15994
+ * carry the full rule object safely; no side effects.
15995
+ */
15996
+ testRule: method(z.object({
15997
+ rule: NcRuleInputSchema,
15998
+ lookbackMinutes: z.number().int().min(1).max(1440).default(60)
15999
+ }), z.object({ results: z.array(NcTestResultSchema) }), {
16000
+ kind: "mutation",
16001
+ auth: "admin"
16002
+ }),
16003
+ getConditionCatalog: method(z.object({}), z.object({ catalog: z.array(NcConditionDescriptorSchema) }))
16004
+ }
16005
+ };
16006
+ /**
15766
16007
  * Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
15767
16008
  * surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
15768
16009
  * caps stay wire-compatible without a circular cap→cap import.
@@ -22608,6 +22849,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
22608
22849
  networkQualityCapability,
22609
22850
  nodesCapability,
22610
22851
  notificationOutputCapability,
22852
+ notificationRulesCapability,
22611
22853
  notifierCapability,
22612
22854
  numericSensorCapability,
22613
22855
  oauthIntegrationCapability,
@@ -25349,6 +25591,54 @@ var METHOD_ACCESS_MAP = Object.freeze({
25349
25591
  addonId: null,
25350
25592
  access: "create"
25351
25593
  },
25594
+ "notificationRules.createRule": {
25595
+ capName: "notification-rules",
25596
+ capScope: "system",
25597
+ addonId: null,
25598
+ access: "create"
25599
+ },
25600
+ "notificationRules.deleteRule": {
25601
+ capName: "notification-rules",
25602
+ capScope: "system",
25603
+ addonId: null,
25604
+ access: "delete"
25605
+ },
25606
+ "notificationRules.getConditionCatalog": {
25607
+ capName: "notification-rules",
25608
+ capScope: "system",
25609
+ addonId: null,
25610
+ access: "view"
25611
+ },
25612
+ "notificationRules.getRule": {
25613
+ capName: "notification-rules",
25614
+ capScope: "system",
25615
+ addonId: null,
25616
+ access: "view"
25617
+ },
25618
+ "notificationRules.listRules": {
25619
+ capName: "notification-rules",
25620
+ capScope: "system",
25621
+ addonId: null,
25622
+ access: "view"
25623
+ },
25624
+ "notificationRules.setRuleEnabled": {
25625
+ capName: "notification-rules",
25626
+ capScope: "system",
25627
+ addonId: null,
25628
+ access: "create"
25629
+ },
25630
+ "notificationRules.testRule": {
25631
+ capName: "notification-rules",
25632
+ capScope: "system",
25633
+ addonId: null,
25634
+ access: "create"
25635
+ },
25636
+ "notificationRules.updateRule": {
25637
+ capName: "notification-rules",
25638
+ capScope: "system",
25639
+ addonId: null,
25640
+ access: "create"
25641
+ },
25352
25642
  "notifier.cancel": {
25353
25643
  capName: "notifier",
25354
25644
  capScope: "device",