@camstack/addon-provider-homeassistant 1.2.146 → 1.2.148

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.
@@ -5369,7 +5369,7 @@ var ZodIssueCode = {
5369
5369
  var ZodFirstPartyTypeKind;
5370
5370
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5371
5371
  //#endregion
5372
- //#region ../types/dist/sleep-jkRQgtSc.mjs
5372
+ //#region ../types/dist/sleep-C-v96WpE.mjs
5373
5373
  /**
5374
5374
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5375
5375
  * window to float samples (D455).
@@ -6984,6 +6984,10 @@ function toNodeId(idOrRunnerId) {
6984
6984
  const head = idOrRunnerId.split("/")[0];
6985
6985
  return head === void 0 || head.length === 0 ? idOrRunnerId : head;
6986
6986
  }
6987
+ object({
6988
+ field: string().min(1),
6989
+ on: literal("advance")
6990
+ });
6987
6991
  /**
6988
6992
  * Output schema shared by the contribution + live methods.
6989
6993
  *
@@ -7467,7 +7471,45 @@ function errMsg(err) {
7467
7471
  return String(err);
7468
7472
  }
7469
7473
  //#endregion
7470
- //#region ../types/dist/composition-CPFIlFfw.mjs
7474
+ //#region ../types/dist/composition-BK-GTFYb.mjs
7475
+ /**
7476
+ * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
7477
+ * valutatore puro del suo FRONTE.
7478
+ *
7479
+ * Viveva dentro il builtin virtual-doorbell
7480
+ * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7481
+ * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7482
+ * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7483
+ * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7484
+ * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
7485
+ * SPOSTA qui e il doorbell la ri-esporta.
7486
+ *
7487
+ * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
7488
+ * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
7489
+ * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
7490
+ * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
7491
+ */
7492
+ /**
7493
+ * Known binary / switch source caps → the boolean slice field whose
7494
+ * false→true rise counts as ACTIVE. Every entry is "fire on active".
7495
+ * Sensors whose "active" reading is not a plain boolean (presence's string
7496
+ * state, connectivity's connected flag) are deliberately excluded — a
7497
+ * reconnect is not a doorbell press, and it is not a recording either.
7498
+ */
7499
+ var SOURCE_CAP_ACTIVE_FIELD = {
7500
+ contact: "entryOpen",
7501
+ binary: "on",
7502
+ switch: "on",
7503
+ motion: "detected",
7504
+ flood: "flooded",
7505
+ gas: "detected",
7506
+ smoke: "detected",
7507
+ "carbon-monoxide": "detected",
7508
+ vibration: "detected",
7509
+ tamper: "tampered"
7510
+ };
7511
+ /** Cap names whose presence in a device's bindings qualify it as a source. */
7512
+ var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
7471
7513
  /**
7472
7514
  * Error types for the safe expression engine. Two distinct classes so callers
7473
7515
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
@@ -7665,6 +7707,20 @@ var table = {
7665
7707
  const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
7666
7708
  return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
7667
7709
  }
7710
+ },
7711
+ rose: {
7712
+ minArgs: 2,
7713
+ maxArgs: 3,
7714
+ apply: () => {
7715
+ throw new ExpressionEvalError("rose: stateful evaluation context is required");
7716
+ }
7717
+ },
7718
+ count: {
7719
+ minArgs: 1,
7720
+ maxArgs: 1,
7721
+ apply: () => {
7722
+ throw new ExpressionEvalError("count: stateful evaluation context is required");
7723
+ }
7668
7724
  }
7669
7725
  };
7670
7726
  Object.freeze(Object.assign(Object.create(null), table));
@@ -8179,6 +8235,7 @@ var CompositionItemEntrySchema = object({
8179
8235
  var CompositionFeatureSchema = discriminatedUnion("kind", [object({
8180
8236
  kind: literal("fields"),
8181
8237
  cap: string().min(1),
8238
+ enabled: boolean().optional(),
8182
8239
  fields: record(string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
8183
8240
  /**
8184
8241
  * Items of the cap's item array, keyed by item key (D663). Naming `items`
@@ -8190,6 +8247,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
8190
8247
  }), object({
8191
8248
  kind: literal("passthrough"),
8192
8249
  cap: string().min(1),
8250
+ enabled: boolean().optional(),
8193
8251
  source: CompositionSourceRefSchema
8194
8252
  })]);
8195
8253
  var CompositionSchema = object({
@@ -8219,7 +8277,6 @@ var CompositionSchema = object({
8219
8277
  });
8220
8278
  new Set([
8221
8279
  "track",
8222
- "summary",
8223
8280
  "face",
8224
8281
  "identity",
8225
8282
  "plate",
@@ -13137,6 +13194,15 @@ var cameraStreamsCapability = {
13137
13194
  */
13138
13195
  durability: "session"
13139
13196
  };
13197
+ EventCategory.DoorbellOnPressed;
13198
+ var CompositionEventPlanSchema = object({
13199
+ featureIndex: number().int().nonnegative(),
13200
+ cap: string(),
13201
+ eventName: string(),
13202
+ category: _enum(EventCategory),
13203
+ stateField: string(),
13204
+ fieldKey: string()
13205
+ });
13140
13206
  /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
13141
13207
  var CompositionProblemSchema = object({
13142
13208
  code: _enum([
@@ -13186,12 +13252,14 @@ var CompositionUnavailableModeSchema = _enum([
13186
13252
  ]);
13187
13253
  /**
13188
13254
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
13189
- * field of its cap changes value · `constant` reads nothing, computed once.
13255
+ * field of its cap changes value · `constant` reads nothing, computed once ·
13256
+ * `accumulator` advances from the target's seed/current output.
13190
13257
  */
13191
13258
  var CompositionFieldRoleSchema = _enum([
13192
13259
  "derived",
13193
13260
  "clock",
13194
- "constant"
13261
+ "constant",
13262
+ "accumulator"
13195
13263
  ]);
13196
13264
  /** The item a planned field belongs to; its `path` is then the dotted path INSIDE the item. */
13197
13265
  var CompositionFieldItemSchema = object({
@@ -13227,6 +13295,7 @@ var CompositionFeatureModeSchema = _enum([
13227
13295
  "replace"
13228
13296
  ]);
13229
13297
  var CompositionFeaturePlanSchema = object({
13298
+ /** Index in the stored feature array; disabled features are omitted rather than re-indexed. */
13230
13299
  featureIndex: number().int().nonnegative(),
13231
13300
  cap: string(),
13232
13301
  mode: CompositionFeatureModeSchema
@@ -13235,7 +13304,8 @@ var CompositionValidationSchema = object({
13235
13304
  ok: boolean(),
13236
13305
  problems: array(CompositionProblemSchema),
13237
13306
  fields: array(CompositionFieldPlanSchema),
13238
- features: array(CompositionFeaturePlanSchema)
13307
+ features: array(CompositionFeaturePlanSchema),
13308
+ events: array(CompositionEventPlanSchema)
13239
13309
  });
13240
13310
  /**
13241
13311
  * Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts`. `degraded`:
@@ -15252,6 +15322,34 @@ var DeviceBindingsForDeviceSchema = object({
15252
15322
  nativeAddonId: string()
15253
15323
  }))
15254
15324
  });
15325
+ /**
15326
+ * The STORED wrapper activation for one `(device, capability)` pair — the row
15327
+ * `setWrapperActive` writes, read back three-way (D675):
15328
+ *
15329
+ * - `value` — the store answered and holds an activation: the bound wrapper's
15330
+ * addon id, or `null`, the operator's explicit "off" marker (D62);
15331
+ * - `missing` — the store answered, its activation map exists, and it holds
15332
+ * nothing for this pair: the operator never activated or deactivated a
15333
+ * wrapper here;
15334
+ * - `failed` — the store could not be read, holds no activation map at all
15335
+ * (an empty or unready snapshot, D44), or holds something that is not an
15336
+ * activation. It carries NO information: a consumer must never read it as
15337
+ * `missing` (D315, D393).
15338
+ *
15339
+ * This is the persisted INPUT, not the derived `getBindings` view — no registry
15340
+ * resolution, no `defaultActive` auto-binding, no cluster native-cap folding.
15341
+ */
15342
+ var WrapperActivationReadSchema = discriminatedUnion("kind", [
15343
+ object({
15344
+ kind: literal("value"),
15345
+ wrapperAddonId: string().nullable()
15346
+ }),
15347
+ object({ kind: literal("missing") }),
15348
+ object({
15349
+ kind: literal("failed"),
15350
+ error: string()
15351
+ })
15352
+ ]);
15255
15353
  var SavedDeviceRowSchema = object({
15256
15354
  /** Numeric id reserved at allocateDeviceId time. */
15257
15355
  id: number(),
@@ -15602,7 +15700,10 @@ method(object({
15602
15700
  }), _void(), {
15603
15701
  kind: "mutation",
15604
15702
  auth: "admin"
15605
- }), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
15703
+ }), method(object({
15704
+ deviceId: number(),
15705
+ capName: string().min(1)
15706
+ }), WrapperActivationReadSchema), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
15606
15707
  capName: string(),
15607
15708
  wrappers: array(string())
15608
15709
  }))), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), object({
@@ -20781,6 +20882,102 @@ var oauthIntegrationCapability = {
20781
20882
  getDescriptor: method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" }) }
20782
20883
  };
20783
20884
  /**
20885
+ * What started a camera's onboard motion (D672). `camera` is the camera's own
20886
+ * firmware; `sensor` is a device the operator ATTACHED to it. Absent on the
20887
+ * wire means the camera: only the camera-sensors bridge ever says `sensor`.
20888
+ */
20889
+ var MotionTriggeredBySchema = discriminatedUnion("kind", [object({ kind: literal("camera") }), object({
20890
+ kind: literal("sensor"),
20891
+ deviceId: number().int().positive()
20892
+ })]);
20893
+ Object.freeze({ kind: "camera" });
20894
+ var CameraSensorRefSchema = object({
20895
+ addonId: string().min(1),
20896
+ stableId: string().min(1)
20897
+ });
20898
+ function cameraSensorRefKey(ref) {
20899
+ return `${ref.addonId}/${ref.stableId}`;
20900
+ }
20901
+ /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
20902
+ var CameraSensorsModeSchema = _enum(["in-addition"]);
20903
+ object({
20904
+ mode: CameraSensorsModeSchema,
20905
+ sensors: array(CameraSensorRefSchema).min(1).max(8)
20906
+ }).superRefine((value, ctx) => {
20907
+ const seen = /* @__PURE__ */ new Set();
20908
+ value.sensors.forEach((ref, index) => {
20909
+ const key = cameraSensorRefKey(ref);
20910
+ if (seen.has(key)) ctx.addIssue({
20911
+ code: "custom",
20912
+ path: ["sensors", index],
20913
+ message: `duplicate camera sensor ${key}`
20914
+ });
20915
+ seen.add(key);
20916
+ });
20917
+ });
20918
+ var RefFields = {
20919
+ addonId: string(),
20920
+ stableId: string()
20921
+ };
20922
+ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
20923
+ object({
20924
+ ...RefFields,
20925
+ state: literal("ready"),
20926
+ deviceId: number(),
20927
+ name: string(),
20928
+ caps: array(string()).readonly()
20929
+ }),
20930
+ object({
20931
+ ...RefFields,
20932
+ state: literal("offline"),
20933
+ deviceId: number(),
20934
+ name: string(),
20935
+ reason: _enum(["offline", "disabled"])
20936
+ }),
20937
+ object({
20938
+ ...RefFields,
20939
+ state: literal("missing"),
20940
+ reason: string()
20941
+ }),
20942
+ object({
20943
+ ...RefFields,
20944
+ state: literal("unsupported"),
20945
+ deviceId: number(),
20946
+ name: string(),
20947
+ reason: string()
20948
+ }),
20949
+ object({
20950
+ ...RefFields,
20951
+ state: literal("unreadable"),
20952
+ reason: string()
20953
+ })
20954
+ ]);
20955
+ /**
20956
+ * Why a configured camera forwards NOTHING from its sensors, however ready they
20957
+ * are (D672): it does not list `onboard` in `motionSources` (attaching a sensor
20958
+ * does not add it), or it has no active detection config at all (the bridge
20959
+ * reads the active config). Absent = forwarded, or an older node that does not
20960
+ * say.
20961
+ */
20962
+ var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
20963
+ var CameraSensorsStatusSchema = discriminatedUnion("state", [
20964
+ object({ state: literal("not-configured") }),
20965
+ object({
20966
+ state: literal("unreadable"),
20967
+ reason: string()
20968
+ }),
20969
+ object({
20970
+ state: literal("configured"),
20971
+ mode: CameraSensorsModeSchema,
20972
+ sensors: array(CameraSensorAttachmentSchema).readonly(),
20973
+ notForwarded: CameraSensorsNotForwardedSchema.optional()
20974
+ })
20975
+ ]);
20976
+ Object.freeze({
20977
+ state: "unreadable",
20978
+ reason: "camera sensor mappings not loaded"
20979
+ });
20980
+ /**
20784
20981
  * A model choice has a SCOPE, and a scope change has a workflow.
20785
20982
  *
20786
20983
  * ## The two scopes
@@ -21529,6 +21726,12 @@ var MotionEventSchema = object({
21529
21726
  /** Populated by B5 (recording playback URL for this event). */
21530
21727
  mediaUrl: string().optional(),
21531
21728
  /**
21729
+ * The FIRST trigger of an `onboard` episode, written once at open (D672).
21730
+ * Absent = not recorded (a row written before D672, or an analyzer /
21731
+ * device-activity episode) — never read as "the camera" (D393).
21732
+ */
21733
+ triggeredBy: MotionTriggeredBySchema.optional(),
21734
+ /**
21532
21735
  * One row per motion EPISODE, not one per push (D475). `null` while the
21533
21736
  * episode is still open — a further rising edge extends it in place rather
21534
21737
  * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
@@ -21687,6 +21890,21 @@ var ObjectEventSchema = object({
21687
21890
  keyFrameMediaId: number().int().optional(),
21688
21891
  /** Populated by B5 (recording playback URL for this event). */
21689
21892
  mediaUrl: string().optional(),
21893
+ /**
21894
+ * When the change this row records HAPPENED, epoch ms, when that is earlier
21895
+ * than {@link timestamp} and the producer could date it (D684).
21896
+ *
21897
+ * Set on a package PICK-UP row only: its `timestamp` is when the departure
21898
+ * was CONCLUDED — five minutes of unseen observed time after the parcel went
21899
+ * — and a notification's footage has to be cut around the pick-up, not the
21900
+ * empty doormat after it (D682). Stored so every path that rebuilds a
21901
+ * notification subject from the row (the boot reconcile, a dry-run) anchors
21902
+ * on the same instant the live hook did.
21903
+ *
21904
+ * `timestamp` keeps its meaning. Absent on every other row and on every
21905
+ * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
21906
+ */
21907
+ observedAt: number().optional(),
21690
21908
  /** The parent track's key-event importance [0,1], propagated to every object
21691
21909
  * event of the track (so an event row can be sorted by importance without a
21692
21910
  * track join). Absent on legacy rows / before the track was scored. */
@@ -22022,131 +22240,6 @@ var RecentTracksQueryInput = object({
22022
22240
  /** See {@link ExcludeSourcesDoc}. */
22023
22241
  excludeSources: array(TrackSourceSchema).optional()
22024
22242
  });
22025
- /**
22026
- * A Summary (D359): the per-camera session envelope that references tracks.
22027
- * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
22028
- * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
22029
- * first frame with a member visible has been materialised (D482 — it was the
22030
- * row's composite key, which embedded the owner and could name another table's
22031
- * row once event ids became per-table rowids).
22032
- */
22033
- var SummarySchema = object({
22034
- id: string(),
22035
- deviceId: number(),
22036
- openedAt: number(),
22037
- lastActiveAt: number(),
22038
- sealedAt: number().nullable(),
22039
- memberCount: number().int().nonnegative(),
22040
- groupShotMediaId: number().int().nullable()
22041
- });
22042
- var SummariesQueryInput = object({
22043
- deviceIds: array(number()).min(1),
22044
- since: number().optional(),
22045
- until: number().optional(),
22046
- limit: number().int().min(1).max(200).default(50),
22047
- /** The same member-wise filter {@link listRecentSummaries} takes — one
22048
- * meaning of "dog" on every summary surface. */
22049
- classes: array(string().min(1).max(60)).max(40).optional()
22050
- });
22051
- var SummaryDetailSchema = object({
22052
- summary: SummarySchema,
22053
- /** Members in absorption order, oldest join first. */
22054
- members: array(object({
22055
- trackId: string(),
22056
- /**
22057
- * The track's class at the time it JOINED, or `null` when it was unresolved
22058
- * and the track no longer exists to fill it.
22059
- *
22060
- * The join, not the latest opinion: class voting can re-decide a track's
22061
- * class afterwards, and a group's composition is the history of who came
22062
- * together. A consumer counts these to badge a thumbnail — "two people and a
22063
- * dog" — rather than being handed a second, denormalised tally that would
22064
- * drift from the members it claims to describe.
22065
- */
22066
- className: string().nullable(),
22067
- /** `primary` is the one member that carries the group's identity. */
22068
- role: _enum([
22069
- "primary",
22070
- "sibling",
22071
- "child"
22072
- ]).nullable(),
22073
- joinedAt: number(),
22074
- /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
22075
- * at read time. Absent when nothing resolved, never an empty string. */
22076
- label: string().optional(),
22077
- /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
22078
- * recognised nobody, and absent for a member whose track has been pruned —
22079
- * a thumbnail that names the wrong person is worse than one naming nobody. */
22080
- subLabel: string().optional()
22081
- })).readonly()
22082
- });
22083
- /**
22084
- * What a LIST of groups carries: the envelope plus what it is MADE OF.
22085
- *
22086
- * Coarse on purpose — the operator's rule is "only the rough data on the list,
22087
- * we extend when we notice we need more". Enough to draw the card ("two people
22088
- * and a dog") and no more. The MEMBERS, their names and the per-member links
22089
- * are on {@link getSummary}: shipping every member of every group so a client
22090
- * can count them turns a page of two hundred groups into a thousand records to
22091
- * draw two hundred badges.
22092
- */
22093
- var SummaryListItemSchema = object({
22094
- summary: SummarySchema,
22095
- /** Class counts, most numerous first; `className: null` is a member whose
22096
- * class never resolved, counted so the counts add up to `memberCount`. */
22097
- classes: array(object({
22098
- className: string().nullable(),
22099
- count: number().int().positive()
22100
- })).readonly()
22101
- });
22102
- var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
22103
- /**
22104
- * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
22105
- *
22106
- * The reel on the cameras page and the Events gallery both ride a cursor, so a
22107
- * summaries feed that could only answer "the last N for these cameras"
22108
- * (`listSummaries`) would make groups available in the timeline and nowhere
22109
- * else. Two feeds with different ordering contracts would be two ideas of what
22110
- * "newest" means, on one screen.
22111
- */
22112
- var RecentSummariesQueryInput = object({
22113
- /** Devices to merge. An empty array yields an empty page. */
22114
- deviceIds: array(number()),
22115
- /** Window lower bound on `lastActiveAt` (inclusive). */
22116
- since: number().optional(),
22117
- /** Window upper bound on `lastActiveAt` (inclusive). */
22118
- until: number().optional(),
22119
- limit: number().int().min(1).max(500).default(100),
22120
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
22121
- cursor: string().optional(),
22122
- /**
22123
- * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
22124
- * deliberately the same name: the events filter tree emits one list of ticked
22125
- * taxonomy nodes, and a summaries feed that called it something else would
22126
- * make the operator's one filter component produce two shapes.
22127
- *
22128
- * A question about the MEMBERS: a group matches when ANY of them does. "Show
22129
- * me the arrivals with a dog" has to return the group of four people who had
22130
- * one with them; a filter on the group's own fields could only ever answer
22131
- * about its primary.
22132
- *
22133
- * Matched against all THREE tiers of a member — the class (`animal`), the
22134
- * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
22135
- * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
22136
- * an event kind in the tree and a tier-1 label on an `animal` track; a filter
22137
- * that read only the class would answer "animal" and never "dog".
22138
- *
22139
- * ABSENT or EMPTY means no filter — absent is not a selection of none.
22140
- */
22141
- classes: array(string().min(1).max(60)).max(40).optional()
22142
- });
22143
- var RecentSummariesPageSchema = object({
22144
- /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
22145
- * {@link listSummaries} — one list shape, so a card renders the same
22146
- * whichever feed drew it. */
22147
- summaries: array(SummaryListItemSchema).readonly(),
22148
- nextCursor: string().nullable()
22149
- });
22150
22243
  var RecentTracksPageSchema = object({
22151
22244
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
22152
22245
  tracks: array(TrackSchema).readonly(),
@@ -22658,7 +22751,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
22658
22751
  classes: array(string()).optional(),
22659
22752
  /** See {@link ExcludeSourcesDoc}. */
22660
22753
  excludeSources: array(TrackSourceSchema).optional()
22661
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
22754
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
22662
22755
  kind: "mutation",
22663
22756
  auth: "admin"
22664
22757
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -23878,6 +23971,23 @@ var MotionSourceEnum = _enum([
23878
23971
  */
23879
23972
  var MotionSourcesSchema = array(MotionSourceEnum);
23880
23973
  /**
23974
+ * The operator-facing name of each MOTION source (D672: `onboard` = the
23975
+ * camera's sensors — its own motion plus any sensor attached to it). The wire
23976
+ * value stays `onboard`; only the label changed. `detectionSources: 'onboard'`
23977
+ * ("Camera onboard AI") and the notification source filter keep their labels.
23978
+ */
23979
+ var MOTION_SOURCE_LABELS = {
23980
+ analyzer: "Frame-diff Analyzer (motion addon)",
23981
+ onboard: "Camera sensors",
23982
+ "device-activity": "Device activity (the device says it is working)"
23983
+ };
23984
+ /** The order the motion-source picker offers them in (not the enum's order). */
23985
+ var MOTION_SOURCE_PICKER_ORDER = [
23986
+ "analyzer",
23987
+ "onboard",
23988
+ "device-activity"
23989
+ ];
23990
+ /**
23881
23991
  * Which root detector a camera runs — EXACTLY ONE.
23882
23992
  *
23883
23993
  * Deliberately the SAME vocabulary post-analysis already tags every detection
@@ -24224,7 +24334,14 @@ var RunnerCameraConfigSchema = object({
24224
24334
  */
24225
24335
  inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
24226
24336
  });
24227
- motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, preRollInferenceFramesField.min, preRollInferenceFramesField.max, preRollInferenceFramesField.step, preRollInferenceFramesField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
24337
+ MOTION_SOURCE_PICKER_ORDER.map((value) => value === "onboard" ? {
24338
+ value,
24339
+ label: MOTION_SOURCE_LABELS[value],
24340
+ description: "The camera's own motion, plus any sensor attached to it"
24341
+ } : {
24342
+ value,
24343
+ label: MOTION_SOURCE_LABELS[value]
24344
+ }), motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, preRollInferenceFramesField.min, preRollInferenceFramesField.max, preRollInferenceFramesField.step, preRollInferenceFramesField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
24228
24345
  /**
24229
24346
  * Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
24230
24347
  * load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
@@ -24798,6 +24915,11 @@ var CameraStatusSchema = object({
24798
24915
  */
24799
24916
  switchedOff: array(CameraSwitchIdSchema).readonly(),
24800
24917
  /**
24918
+ * The camera's attached sensors (D672). Optional only for a status composed
24919
+ * by an orchestrator older than D672; this orchestrator always sets it.
24920
+ */
24921
+ cameraSensors: CameraSensorsStatusSchema.optional(),
24922
+ /**
24801
24923
  * Stages of the bounded fan-out that were CUT SHORT — a timeout or a
24802
24924
  * rejection — and whose block is therefore `null` because we could not
24803
24925
  * READ it, not because there is nothing there.
@@ -27250,44 +27372,6 @@ DeviceType.Camera, method(object({
27250
27372
  auth: "protected"
27251
27373
  });
27252
27374
  /**
27253
- * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
27254
- * valutatore puro del suo FRONTE.
27255
- *
27256
- * Viveva dentro il builtin virtual-doorbell
27257
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
27258
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
27259
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
27260
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
27261
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
27262
- * SPOSTA qui e il doorbell la ri-esporta.
27263
- *
27264
- * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
27265
- * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
27266
- * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
27267
- * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
27268
- */
27269
- /**
27270
- * Known binary / switch source caps → the boolean slice field whose
27271
- * false→true rise counts as ACTIVE. Every entry is "fire on active".
27272
- * Sensors whose "active" reading is not a plain boolean (presence's string
27273
- * state, connectivity's connected flag) are deliberately excluded — a
27274
- * reconnect is not a doorbell press, and it is not a recording either.
27275
- */
27276
- var SOURCE_CAP_ACTIVE_FIELD = {
27277
- contact: "entryOpen",
27278
- binary: "on",
27279
- switch: "on",
27280
- motion: "detected",
27281
- flood: "flooded",
27282
- gas: "detected",
27283
- smoke: "detected",
27284
- "carbon-monoxide": "detected",
27285
- vibration: "detected",
27286
- tamper: "tampered"
27287
- };
27288
- /** Cap names whose presence in a device's bindings qualify it as a source. */
27289
- var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
27290
- /**
27291
27375
  * Doorbell button cap. Two kinds of providers coexist behind this cap
27292
27376
  * name (same pattern as `snapshot`):
27293
27377
  *
@@ -27360,7 +27444,13 @@ var doorbellCapability = {
27360
27444
  * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
27361
27445
  * release/duration — it's a pulse.
27362
27446
  */
27363
- onPressed: { data: DoorbellPressEventSchema } },
27447
+ onPressed: {
27448
+ data: DoorbellPressEventSchema,
27449
+ fromState: {
27450
+ field: "lastPressedAt",
27451
+ on: "advance"
27452
+ }
27453
+ } },
27364
27454
  status: {
27365
27455
  schema: DoorbellStatusSchema,
27366
27456
  kind: "push"
@@ -32257,7 +32347,9 @@ var MotionOnMotionChangedDataSchema = object({
32257
32347
  detected: boolean(),
32258
32348
  timestamp: number(),
32259
32349
  source: MotionSourceEnum,
32260
- regions: array(MotionRegionSchema).readonly().optional()
32350
+ regions: array(MotionRegionSchema).readonly().optional(),
32351
+ /** Who started it (D672). Absent = the camera itself. Only `onboard` carries `sensor`. */
32352
+ triggeredBy: MotionTriggeredBySchema.optional()
32261
32353
  });
32262
32354
  var motionCapability = {
32263
32355
  name: "motion",
@@ -35247,12 +35339,21 @@ var RecordingStorageUsageSchema = object({
35247
35339
  */
35248
35340
  devicesRead: _enum(["archive", "unavailable"]).optional(),
35249
35341
  /**
35342
+ * Is the `archive` answer WHOLE (D674): the hour ledger loaded without
35343
+ * hitting its row cap, and at least one location resolved? Only then may a
35344
+ * camera ABSENT from `devices[]` be read as "no footage" — and only then is a
35345
+ * row's `oldestMs` the archive's oldest rather than the oldest of a fraction.
35346
+ * OPTIONAL: a recorder predating D674 omits it, and a consumer must read the
35347
+ * absence as `false`. A retention floor is a delete; it never guesses.
35348
+ */
35349
+ complete: boolean().optional(),
35350
+ /**
35250
35351
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
35251
- * of the payload without it: the hub serves this method through a BAKED
35252
- * router, so a hub whose framework train predates a change to this array
35253
- * strips it out of an answer a newer recorder happily produced. A UI that
35254
- * dereferences it unconditionally crashes the whole Storage page over a
35255
- * missing disk bar.
35352
+ * of the payload without it: a recorder that could not build it omits it. A
35353
+ * UI that dereferences it unconditionally crashes the whole Storage page over
35354
+ * a missing disk bar. (The hub's cap routers pass output through unvalidated
35355
+ * since 2026-07-11 — `cap-router-builder.ts` — so a newer recorder's fields
35356
+ * are not stripped by an older hub.)
35256
35357
  */
35257
35358
  locations: array(RecordingLocationUsageSchema).optional()
35258
35359
  });
@@ -38771,7 +38872,12 @@ Object.freeze({
38771
38872
  number: {
38772
38873
  kind: "fixed",
38773
38874
  result: ["number", "null"]
38774
- }
38875
+ },
38876
+ rose: {
38877
+ kind: "fixed",
38878
+ result: ["number", "null"]
38879
+ },
38880
+ count: NUMBER
38775
38881
  });
38776
38882
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
38777
38883
  /**
@@ -39812,6 +39918,7 @@ var BaseDeviceProvider = class extends BaseAddon {
39812
39918
  }];
39813
39919
  }
39814
39920
  async onShutdown() {
39921
+ this._restoreShuttingDown = true;
39815
39922
  this.cancelRestoreRetries();
39816
39923
  const devices = await this.ctx.kernel.devices?.getAll() ?? [];
39817
39924
  for (const device of devices) try {
@@ -39929,6 +40036,7 @@ var BaseDeviceProvider = class extends BaseAddon {
39929
40036
  };
39930
40037
  }
39931
40038
  async restoreDevices(savedDevices) {
40039
+ this._restoreShuttingDown = false;
39932
40040
  const report = await this.onRestoreDevices(savedDevices);
39933
40041
  if (savedDevices.length === 0) return;
39934
40042
  if (report && report.failedCount > 0) {
@@ -39943,8 +40051,13 @@ var BaseDeviceProvider = class extends BaseAddon {
39943
40051
  /** Retry lane width. See `device-restore-retry.ts` for why retries
39944
40052
  * never re-stampede full-width while the initial pass does (D167). */
39945
40053
  restoreRetryConcurrency = 4;
39946
- _restoreRetryScheduler = null;
40054
+ /** Every bounded retry this process started — the default pass's, and each
40055
+ * `restoreWithBoundedRetry` of a provider's own rows. Cancelled together. */
40056
+ _restoreRetrySchedulers = [];
39947
40057
  _restoreRetryCompletion = null;
40058
+ /** Set by `onShutdown` BEFORE the retries are cancelled: a restore pass still
40059
+ * in flight must not start a scheduler after the cancel ran. */
40060
+ _restoreShuttingDown = false;
39948
40061
  _permanentRestoreFailures = /* @__PURE__ */ new Map();
39949
40062
  /** Settles when the background retry rounds finish (or `null` when
39950
40063
  * nothing failed). Exposed for tests and subclass diagnostics —
@@ -40013,8 +40126,8 @@ var BaseDeviceProvider = class extends BaseAddon {
40013
40126
  });
40014
40127
  }
40015
40128
  cancelRestoreRetries() {
40016
- this._restoreRetryScheduler?.cancel();
40017
- this._restoreRetryScheduler = null;
40129
+ for (const scheduler of this._restoreRetrySchedulers) scheduler.cancel();
40130
+ this._restoreRetrySchedulers = [];
40018
40131
  }
40019
40132
  recordPermanentRestoreFailure(failure) {
40020
40133
  this._permanentRestoreFailures.set(failure.deviceId, failure);
@@ -40031,6 +40144,13 @@ var BaseDeviceProvider = class extends BaseAddon {
40031
40144
  });
40032
40145
  }
40033
40146
  scheduleRestoreRetries(failures, attempt) {
40147
+ if (this._restoreShuttingDown) {
40148
+ this.ctx.logger.info("Restore retries not scheduled — the provider is shutting down", { meta: {
40149
+ rows: failures.length,
40150
+ deviceIds: failures.map((f) => f.saved.id)
40151
+ } });
40152
+ return;
40153
+ }
40034
40154
  const scheduler = new DeviceRestoreRetryScheduler({
40035
40155
  logger: this.ctx.logger,
40036
40156
  delaysMs: this.restoreRetryDelaysMs,
@@ -40038,10 +40158,54 @@ var BaseDeviceProvider = class extends BaseAddon {
40038
40158
  attempt,
40039
40159
  onPermanentFailure: (failure) => this.recordPermanentRestoreFailure(failure)
40040
40160
  });
40041
- this._restoreRetryScheduler = scheduler;
40042
- this._restoreRetryCompletion = scheduler.run(failures).then(() => void 0).catch((err) => {
40161
+ this._restoreRetrySchedulers = [...this._restoreRetrySchedulers, scheduler];
40162
+ const run = scheduler.run(failures).then(() => void 0).catch((err) => {
40043
40163
  this.ctx.logger.error("Restore retry scheduler crashed", { meta: { error: err instanceof Error ? err.message : String(err) } });
40044
40164
  });
40165
+ const prior = this._restoreRetryCompletion;
40166
+ this._restoreRetryCompletion = prior === null ? run : Promise.all([prior, run]).then(() => void 0);
40167
+ }
40168
+ /**
40169
+ * Restore rows the default pass does not handle (a provider's own restore
40170
+ * loop) under the SAME bounded retry (D347): one attempt each, in order; a
40171
+ * row that throws is logged with `tags.deviceId` and retried in the
40172
+ * background rounds, then marked permanently failed like any other.
40173
+ *
40174
+ * A restore is a device build, and a build whose runtime-state load did not
40175
+ * answer is REFUSED rather than started on cold defaults (D677) — so a
40176
+ * transient hub read must cost a retry, never the device until the next
40177
+ * runner restart. An ANSWERED `{}` is the device's true state and never
40178
+ * refuses.
40179
+ */
40180
+ async restoreWithBoundedRetry(rows, attempt) {
40181
+ const failures = [];
40182
+ for (const saved of rows) {
40183
+ if (this.isUnrestorableShell(saved)) {
40184
+ this.quarantineShell(saved);
40185
+ continue;
40186
+ }
40187
+ try {
40188
+ await attempt(saved);
40189
+ } catch (err) {
40190
+ const error = err instanceof Error ? err.message : String(err);
40191
+ this.ctx.logger.warn("Failed to restore device — bounded retry scheduled", {
40192
+ tags: {
40193
+ deviceId: saved.id,
40194
+ stableId: saved.stableId
40195
+ },
40196
+ meta: {
40197
+ type: saved.type,
40198
+ attempt: 1,
40199
+ error
40200
+ }
40201
+ });
40202
+ failures.push({
40203
+ saved,
40204
+ error
40205
+ });
40206
+ }
40207
+ }
40208
+ if (failures.length > 0) this.scheduleRestoreRetries(failures, attempt);
40045
40209
  }
40046
40210
  /**
40047
40211
  * Tear down and reconstruct ONE device from its persisted rows — the
@@ -43351,6 +43515,12 @@ Object.freeze({
43351
43515
  addonId: null,
43352
43516
  access: "view"
43353
43517
  },
43518
+ "deviceManager.getWrapperActivation": {
43519
+ capName: "device-manager",
43520
+ capScope: "system",
43521
+ addonId: null,
43522
+ access: "view"
43523
+ },
43354
43524
  "deviceManager.listAll": {
43355
43525
  capName: "device-manager",
43356
43526
  capScope: "system",
@@ -45625,12 +45795,6 @@ Object.freeze({
45625
45795
  addonId: null,
45626
45796
  access: "view"
45627
45797
  },
45628
- "pipelineAnalytics.getSummary": {
45629
- capName: "pipeline-analytics",
45630
- capScope: "device",
45631
- addonId: null,
45632
- access: "view"
45633
- },
45634
45798
  "pipelineAnalytics.getTrack": {
45635
45799
  capName: "pipeline-analytics",
45636
45800
  capScope: "device",
@@ -45691,12 +45855,6 @@ Object.freeze({
45691
45855
  addonId: null,
45692
45856
  access: "view"
45693
45857
  },
45694
- "pipelineAnalytics.listRecentSummaries": {
45695
- capName: "pipeline-analytics",
45696
- capScope: "device",
45697
- addonId: null,
45698
- access: "view"
45699
- },
45700
45858
  "pipelineAnalytics.listRecentTracks": {
45701
45859
  capName: "pipeline-analytics",
45702
45860
  capScope: "device",
@@ -45727,12 +45885,6 @@ Object.freeze({
45727
45885
  addonId: null,
45728
45886
  access: "view"
45729
45887
  },
45730
- "pipelineAnalytics.listSummaries": {
45731
- capName: "pipeline-analytics",
45732
- capScope: "device",
45733
- addonId: null,
45734
- access: "view"
45735
- },
45736
45888
  "pipelineAnalytics.listTrackMedia": {
45737
45889
  capName: "pipeline-analytics",
45738
45890
  capScope: "device",
@@ -49160,6 +49312,11 @@ Object.freeze({
49160
49312
  form: "single",
49161
49313
  optional: false
49162
49314
  }],
49315
+ "deviceManager.getWrapperActivation": [{
49316
+ name: "deviceId",
49317
+ form: "single",
49318
+ optional: false
49319
+ }],
49163
49320
  "deviceManager.listAll": [{
49164
49321
  name: "deviceIds",
49165
49322
  form: "array",
@@ -49897,11 +50054,6 @@ Object.freeze({
49897
50054
  form: "single",
49898
50055
  optional: true
49899
50056
  }],
49900
- "pipelineAnalytics.listRecentSummaries": [{
49901
- name: "deviceIds",
49902
- form: "array",
49903
- optional: false
49904
- }],
49905
50057
  "pipelineAnalytics.listRecentTracks": [{
49906
50058
  name: "deviceIds",
49907
50059
  form: "array",
@@ -49912,11 +50064,6 @@ Object.freeze({
49912
50064
  form: "array",
49913
50065
  optional: true
49914
50066
  }],
49915
- "pipelineAnalytics.listSummaries": [{
49916
- name: "deviceIds",
49917
- form: "array",
49918
- optional: false
49919
- }],
49920
50067
  "pipelineAnalytics.listTrackMedia": [{
49921
50068
  name: "deviceId",
49922
50069
  form: "single",