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