@camstack/addon-google-services 0.2.12 → 0.2.13

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.
@@ -7933,6 +7933,12 @@ function normalizeAddonInitResult(result) {
7933
7933
  if (Array.isArray(result)) return { providers: result };
7934
7934
  return result;
7935
7935
  }
7936
+ /**
7937
+ * The share-view scope kinds a `csv_*` share token can carry. The hub mints
7938
+ * and validates them (`share-token.service.ts` builds its enum from this
7939
+ * list); a data plane names the kinds it ACCEPTS in `share`.
7940
+ */
7941
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
7936
7942
  /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
7937
7943
  var PeerBytesTicketSchema = object({
7938
7944
  /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
@@ -12915,6 +12921,229 @@ var ConvertResultSchema = object({
12915
12921
  files: array(string()).readonly()
12916
12922
  })).readonly()
12917
12923
  });
12924
+ /**
12925
+ * Zod schemas for persisted record types.
12926
+ *
12927
+ * These schemas serve as the single source of truth for types that are
12928
+ * stored in the settings-store backend and retrieved via tRPC. They
12929
+ * replace the manual TypeScript interfaces with schema-derived types,
12930
+ * enabling runtime validation when reading from the backend.
12931
+ *
12932
+ * Convention: each schema is named `<Type>Schema`, the inferred TS type
12933
+ * keeps the original name for backward compatibility.
12934
+ */
12935
+ /**
12936
+ * Auth model (v2 — 2026-05-12 caps-only):
12937
+ *
12938
+ * Two principals:
12939
+ * - `isAdmin: true` — full bypass. Admin scope set is ignored.
12940
+ * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
12941
+ *
12942
+ * No role enum. There used to be `viewer / agent / scoped` and earlier
12943
+ * `super_admin` — collapsed onto the single boolean because every
12944
+ * non-admin caller is governed by the same scope-check rules; the
12945
+ * distinction was only ever used to route role-specific procedures
12946
+ * which we no longer have. CLI / webhook integrations are `cst_*` tokens
12947
+ * with `isAdmin: false` + an explicit scope list.
12948
+ */
12949
+ /** Per-method access flavour. Materialised on every cap method via codegen. */
12950
+ var MethodAccessSchema = _enum([
12951
+ "view",
12952
+ "create",
12953
+ "delete"
12954
+ ]);
12955
+ /**
12956
+ * A single scope grant. Three forms — checked in matcher with OR
12957
+ * semantics; the first match decides whether the call passes:
12958
+ *
12959
+ * - `category` — broadest. `target: 'device' | 'system'` selects every
12960
+ * cap with the matching `scope` declaration. New caps inherit grants
12961
+ * automatically — adding a device cap to the codebase makes it
12962
+ * reachable to every user with `category:device [...]` without a
12963
+ * re-grant. Use this for "family viewer" / "operator" tiers.
12964
+ *
12965
+ * - `addon` — every cap that ships inside a given addon id. Niche;
12966
+ * useful when an integration needs to interact with one specific
12967
+ * addon's surface regardless of how many caps it exports.
12968
+ *
12969
+ * - `capability` — the most precise form. Single cap name. Use for
12970
+ * integration tokens where you want to grant `addons.installPackage`
12971
+ * and nothing else.
12972
+ *
12973
+ * `access` is REQUIRED with min 1 element. `admin` bypass is the
12974
+ * "give me everything" escape; there is no wildcard scope.
12975
+ */
12976
+ var AllowedProviderSchema = union([literal("*"), array(string())]);
12977
+ var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
12978
+ var CapScopeSchema = _enum(["device", "system"]);
12979
+ /**
12980
+ * DeviceSelector (scope model v3 — 2026-08-12).
12981
+ *
12982
+ * A `device` grant no longer carries a frozen list of deviceIds. It carries
12983
+ * a SELECTOR the matcher resolves against the live fleet, so the grant can be
12984
+ * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
12985
+ * AFTER the grant was minted — no re-grant, no re-login.
12986
+ *
12987
+ * - `all` — every device in the deployment. The broad viewer/operator
12988
+ * lever without a `category` grant (a `category` grant also covers device
12989
+ * caps that carry no deviceId; `all` is specifically the device set).
12990
+ * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
12991
+ * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
12992
+ * is NOT covered until the grant is edited.
12993
+ * - `types` — every device of a `DeviceType` (e.g. every `camera`).
12994
+ * DYNAMIC. A device that changes type, or a new device of the type,
12995
+ * re-resolves on the next request.
12996
+ * - `locations` — every device whose operator-assigned `location` label is
12997
+ * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
12998
+ * null/unset location matches NO `locations` selector.
12999
+ */
13000
+ var DeviceSelectorSchema = discriminatedUnion("kind", [
13001
+ object({ kind: literal("all") }),
13002
+ object({
13003
+ kind: literal("ids"),
13004
+ ids: array(number().int()).min(1)
13005
+ }),
13006
+ object({
13007
+ kind: literal("types"),
13008
+ types: array(_enum(DeviceType)).min(1)
13009
+ }),
13010
+ object({
13011
+ kind: literal("locations"),
13012
+ locations: array(string().min(1)).min(1)
13013
+ })
13014
+ ]);
13015
+ var DeviceTokenScopeSchema = object({
13016
+ type: literal("device"),
13017
+ /** The device SET this grant covers — resolved against the live fleet. */
13018
+ selector: DeviceSelectorSchema,
13019
+ access: array(MethodAccessSchema).min(1),
13020
+ /**
13021
+ * Whether a grant on a PARENT device transparently covers its accessory
13022
+ * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
13023
+ * Direction is parent → children ONLY.
13024
+ *
13025
+ * Absent → the matcher DERIVES it from the access flavour: `view`
13026
+ * inherits (a camera viewer sees the camera's accessories), `create` /
13027
+ * `delete` do NOT (actuating/removing a child is an explicit act the
13028
+ * operator must grant on the child, not inherit from the parent). Set it
13029
+ * explicitly to override that default per grant.
13030
+ */
13031
+ includeLinked: boolean().optional()
13032
+ });
13033
+ /**
13034
+ * v2 → v3 lazy migration. A pre-v3 `device` grant carried
13035
+ * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
13036
+ * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
13037
+ * EVERY parse path — stored records AND the JWT-carried scope arrays
13038
+ * normalised at the request boundary ({@link normalizeTokenScopes} in
13039
+ * `device-selector.ts`). Chosen over a one-time DB migration because a
13040
+ * migration cannot reach a JWT already in a client's hands; parse-time
13041
+ * migration covers both without a flag day. No cast — the raw object is read
13042
+ * through `Reflect.get` (its static type is `unknown`).
13043
+ */
13044
+ function migrateLegacyTokenScope(raw) {
13045
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
13046
+ if (Reflect.get(raw, "type") !== "device") return raw;
13047
+ if (Reflect.get(raw, "selector") !== void 0) return raw;
13048
+ const targets = Reflect.get(raw, "targets");
13049
+ if (!Array.isArray(targets)) return raw;
13050
+ return {
13051
+ type: "device",
13052
+ selector: {
13053
+ kind: "ids",
13054
+ ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
13055
+ },
13056
+ access: Reflect.get(raw, "access")
13057
+ };
13058
+ }
13059
+ var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
13060
+ object({
13061
+ type: literal("category"),
13062
+ target: CapScopeSchema,
13063
+ access: array(MethodAccessSchema).min(1)
13064
+ }),
13065
+ object({
13066
+ type: literal("capability"),
13067
+ target: string(),
13068
+ access: array(MethodAccessSchema).min(1)
13069
+ }),
13070
+ object({
13071
+ type: literal("addon"),
13072
+ target: string(),
13073
+ access: array(MethodAccessSchema).min(1)
13074
+ }),
13075
+ DeviceTokenScopeSchema
13076
+ ]));
13077
+ object({
13078
+ id: string(),
13079
+ username: string(),
13080
+ passwordHash: string(),
13081
+ /**
13082
+ * Admin bypass. When true, the middleware skips the scope-access
13083
+ * check entirely. There is no other axis of privilege; the legacy
13084
+ * role enum collapsed onto this boolean in v2.
13085
+ */
13086
+ isAdmin: boolean().default(false),
13087
+ allowedProviders: AllowedProviderSchema,
13088
+ allowedDevices: AllowedDevicesSchema,
13089
+ /**
13090
+ * Scopes granted to this user. Admins bypass; their `scopes` is
13091
+ * ignored. Non-admins without scopes are locked out of every
13092
+ * protected call.
13093
+ */
13094
+ scopes: array(TokenScopeSchema).default([]),
13095
+ createdAt: number(),
13096
+ updatedAt: number()
13097
+ });
13098
+ object({
13099
+ id: string(),
13100
+ label: string(),
13101
+ isAdmin: boolean().default(false),
13102
+ allowedProviders: AllowedProviderSchema,
13103
+ allowedDevices: AllowedDevicesSchema,
13104
+ tokenHash: string(),
13105
+ tokenPrefix: string(),
13106
+ createdAt: number(),
13107
+ lastUsedAt: number().optional()
13108
+ });
13109
+ object({
13110
+ id: string(),
13111
+ userId: string(),
13112
+ name: string(),
13113
+ tokenHash: string(),
13114
+ tokenPrefix: string(),
13115
+ scopes: array(TokenScopeSchema),
13116
+ expiresAt: number().nullish(),
13117
+ lastUsedAt: number().nullish(),
13118
+ createdAt: number()
13119
+ });
13120
+ /** What a share-view grant lets its holder see. */
13121
+ var ShareViewGrantSchema = object({
13122
+ kind: _enum(SHARE_VIEW_KINDS),
13123
+ deviceIds: array(number().int().nonnegative()).min(1).max(64),
13124
+ /** Talk-back, opt-in at mint time; absent means off. */
13125
+ talk: boolean().optional()
13126
+ });
13127
+ var ShareViewScopeSchema = object({
13128
+ type: literal("share-view"),
13129
+ grant: ShareViewGrantSchema
13130
+ });
13131
+ var AddonRoutesScopeSchema = object({
13132
+ type: literal("addon-routes"),
13133
+ access: array(MethodAccessSchema).min(1)
13134
+ });
13135
+ var CredentialScopeSchema = union([
13136
+ literal("*"),
13137
+ ShareViewScopeSchema,
13138
+ AddonRoutesScopeSchema,
13139
+ TokenScopeSchema
13140
+ ]);
13141
+ object({
13142
+ kind: literal("credential"),
13143
+ sub: string().min(1),
13144
+ jti: string().min(16),
13145
+ scopes: array(CredentialScopeSchema).min(1)
13146
+ });
12918
13147
  _enum(["new-session", "step-up"]);
12919
13148
  /** The languages the hub's app landing pages are written in (D858). */
12920
13149
  var AppPageLangSchema = _enum(["en", "it"]);
@@ -44120,202 +44349,6 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
44120
44349
  auth: "admin"
44121
44350
  }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
44122
44351
  /**
44123
- * Zod schemas for persisted record types.
44124
- *
44125
- * These schemas serve as the single source of truth for types that are
44126
- * stored in the settings-store backend and retrieved via tRPC. They
44127
- * replace the manual TypeScript interfaces with schema-derived types,
44128
- * enabling runtime validation when reading from the backend.
44129
- *
44130
- * Convention: each schema is named `<Type>Schema`, the inferred TS type
44131
- * keeps the original name for backward compatibility.
44132
- */
44133
- /**
44134
- * Auth model (v2 — 2026-05-12 caps-only):
44135
- *
44136
- * Two principals:
44137
- * - `isAdmin: true` — full bypass. Admin scope set is ignored.
44138
- * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
44139
- *
44140
- * No role enum. There used to be `viewer / agent / scoped` and earlier
44141
- * `super_admin` — collapsed onto the single boolean because every
44142
- * non-admin caller is governed by the same scope-check rules; the
44143
- * distinction was only ever used to route role-specific procedures
44144
- * which we no longer have. CLI / webhook integrations are `cst_*` tokens
44145
- * with `isAdmin: false` + an explicit scope list.
44146
- */
44147
- /** Per-method access flavour. Materialised on every cap method via codegen. */
44148
- var MethodAccessSchema = _enum([
44149
- "view",
44150
- "create",
44151
- "delete"
44152
- ]);
44153
- /**
44154
- * A single scope grant. Three forms — checked in matcher with OR
44155
- * semantics; the first match decides whether the call passes:
44156
- *
44157
- * - `category` — broadest. `target: 'device' | 'system'` selects every
44158
- * cap with the matching `scope` declaration. New caps inherit grants
44159
- * automatically — adding a device cap to the codebase makes it
44160
- * reachable to every user with `category:device [...]` without a
44161
- * re-grant. Use this for "family viewer" / "operator" tiers.
44162
- *
44163
- * - `addon` — every cap that ships inside a given addon id. Niche;
44164
- * useful when an integration needs to interact with one specific
44165
- * addon's surface regardless of how many caps it exports.
44166
- *
44167
- * - `capability` — the most precise form. Single cap name. Use for
44168
- * integration tokens where you want to grant `addons.installPackage`
44169
- * and nothing else.
44170
- *
44171
- * `access` is REQUIRED with min 1 element. `admin` bypass is the
44172
- * "give me everything" escape; there is no wildcard scope.
44173
- */
44174
- var AllowedProviderSchema = union([literal("*"), array(string())]);
44175
- var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
44176
- var CapScopeSchema = _enum(["device", "system"]);
44177
- /**
44178
- * DeviceSelector (scope model v3 — 2026-08-12).
44179
- *
44180
- * A `device` grant no longer carries a frozen list of deviceIds. It carries
44181
- * a SELECTOR the matcher resolves against the live fleet, so the grant can be
44182
- * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
44183
- * AFTER the grant was minted — no re-grant, no re-login.
44184
- *
44185
- * - `all` — every device in the deployment. The broad viewer/operator
44186
- * lever without a `category` grant (a `category` grant also covers device
44187
- * caps that carry no deviceId; `all` is specifically the device set).
44188
- * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
44189
- * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
44190
- * is NOT covered until the grant is edited.
44191
- * - `types` — every device of a `DeviceType` (e.g. every `camera`).
44192
- * DYNAMIC. A device that changes type, or a new device of the type,
44193
- * re-resolves on the next request.
44194
- * - `locations` — every device whose operator-assigned `location` label is
44195
- * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
44196
- * null/unset location matches NO `locations` selector.
44197
- */
44198
- var DeviceSelectorSchema = discriminatedUnion("kind", [
44199
- object({ kind: literal("all") }),
44200
- object({
44201
- kind: literal("ids"),
44202
- ids: array(number().int()).min(1)
44203
- }),
44204
- object({
44205
- kind: literal("types"),
44206
- types: array(_enum(DeviceType)).min(1)
44207
- }),
44208
- object({
44209
- kind: literal("locations"),
44210
- locations: array(string().min(1)).min(1)
44211
- })
44212
- ]);
44213
- var DeviceTokenScopeSchema = object({
44214
- type: literal("device"),
44215
- /** The device SET this grant covers — resolved against the live fleet. */
44216
- selector: DeviceSelectorSchema,
44217
- access: array(MethodAccessSchema).min(1),
44218
- /**
44219
- * Whether a grant on a PARENT device transparently covers its accessory
44220
- * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
44221
- * Direction is parent → children ONLY.
44222
- *
44223
- * Absent → the matcher DERIVES it from the access flavour: `view`
44224
- * inherits (a camera viewer sees the camera's accessories), `create` /
44225
- * `delete` do NOT (actuating/removing a child is an explicit act the
44226
- * operator must grant on the child, not inherit from the parent). Set it
44227
- * explicitly to override that default per grant.
44228
- */
44229
- includeLinked: boolean().optional()
44230
- });
44231
- /**
44232
- * v2 → v3 lazy migration. A pre-v3 `device` grant carried
44233
- * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
44234
- * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
44235
- * EVERY parse path — stored records AND the JWT-carried scope arrays
44236
- * normalised at the request boundary ({@link normalizeTokenScopes} in
44237
- * `device-selector.ts`). Chosen over a one-time DB migration because a
44238
- * migration cannot reach a JWT already in a client's hands; parse-time
44239
- * migration covers both without a flag day. No cast — the raw object is read
44240
- * through `Reflect.get` (its static type is `unknown`).
44241
- */
44242
- function migrateLegacyTokenScope(raw) {
44243
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
44244
- if (Reflect.get(raw, "type") !== "device") return raw;
44245
- if (Reflect.get(raw, "selector") !== void 0) return raw;
44246
- const targets = Reflect.get(raw, "targets");
44247
- if (!Array.isArray(targets)) return raw;
44248
- return {
44249
- type: "device",
44250
- selector: {
44251
- kind: "ids",
44252
- ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
44253
- },
44254
- access: Reflect.get(raw, "access")
44255
- };
44256
- }
44257
- var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
44258
- object({
44259
- type: literal("category"),
44260
- target: CapScopeSchema,
44261
- access: array(MethodAccessSchema).min(1)
44262
- }),
44263
- object({
44264
- type: literal("capability"),
44265
- target: string(),
44266
- access: array(MethodAccessSchema).min(1)
44267
- }),
44268
- object({
44269
- type: literal("addon"),
44270
- target: string(),
44271
- access: array(MethodAccessSchema).min(1)
44272
- }),
44273
- DeviceTokenScopeSchema
44274
- ]));
44275
- object({
44276
- id: string(),
44277
- username: string(),
44278
- passwordHash: string(),
44279
- /**
44280
- * Admin bypass. When true, the middleware skips the scope-access
44281
- * check entirely. There is no other axis of privilege; the legacy
44282
- * role enum collapsed onto this boolean in v2.
44283
- */
44284
- isAdmin: boolean().default(false),
44285
- allowedProviders: AllowedProviderSchema,
44286
- allowedDevices: AllowedDevicesSchema,
44287
- /**
44288
- * Scopes granted to this user. Admins bypass; their `scopes` is
44289
- * ignored. Non-admins without scopes are locked out of every
44290
- * protected call.
44291
- */
44292
- scopes: array(TokenScopeSchema).default([]),
44293
- createdAt: number(),
44294
- updatedAt: number()
44295
- });
44296
- object({
44297
- id: string(),
44298
- label: string(),
44299
- isAdmin: boolean().default(false),
44300
- allowedProviders: AllowedProviderSchema,
44301
- allowedDevices: AllowedDevicesSchema,
44302
- tokenHash: string(),
44303
- tokenPrefix: string(),
44304
- createdAt: number(),
44305
- lastUsedAt: number().optional()
44306
- });
44307
- object({
44308
- id: string(),
44309
- userId: string(),
44310
- name: string(),
44311
- tokenHash: string(),
44312
- tokenPrefix: string(),
44313
- scopes: array(TokenScopeSchema),
44314
- expiresAt: number().nullish(),
44315
- lastUsedAt: number().nullish(),
44316
- createdAt: number()
44317
- });
44318
- /**
44319
44352
  * `oauth-integration` — a collection cap by which an addon declares
44320
44353
  * itself a linkable OAuth client of the hub's core `/oauth2/*` server.
44321
44354
  * Each provider returns a static descriptor; the core enumerates them
@@ -7929,6 +7929,12 @@ function normalizeAddonInitResult(result) {
7929
7929
  if (Array.isArray(result)) return { providers: result };
7930
7930
  return result;
7931
7931
  }
7932
+ /**
7933
+ * The share-view scope kinds a `csv_*` share token can carry. The hub mints
7934
+ * and validates them (`share-token.service.ts` builds its enum from this
7935
+ * list); a data plane names the kinds it ACCEPTS in `share`.
7936
+ */
7937
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
7932
7938
  /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
7933
7939
  var PeerBytesTicketSchema = object({
7934
7940
  /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
@@ -12911,6 +12917,229 @@ var ConvertResultSchema = object({
12911
12917
  files: array(string()).readonly()
12912
12918
  })).readonly()
12913
12919
  });
12920
+ /**
12921
+ * Zod schemas for persisted record types.
12922
+ *
12923
+ * These schemas serve as the single source of truth for types that are
12924
+ * stored in the settings-store backend and retrieved via tRPC. They
12925
+ * replace the manual TypeScript interfaces with schema-derived types,
12926
+ * enabling runtime validation when reading from the backend.
12927
+ *
12928
+ * Convention: each schema is named `<Type>Schema`, the inferred TS type
12929
+ * keeps the original name for backward compatibility.
12930
+ */
12931
+ /**
12932
+ * Auth model (v2 — 2026-05-12 caps-only):
12933
+ *
12934
+ * Two principals:
12935
+ * - `isAdmin: true` — full bypass. Admin scope set is ignored.
12936
+ * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
12937
+ *
12938
+ * No role enum. There used to be `viewer / agent / scoped` and earlier
12939
+ * `super_admin` — collapsed onto the single boolean because every
12940
+ * non-admin caller is governed by the same scope-check rules; the
12941
+ * distinction was only ever used to route role-specific procedures
12942
+ * which we no longer have. CLI / webhook integrations are `cst_*` tokens
12943
+ * with `isAdmin: false` + an explicit scope list.
12944
+ */
12945
+ /** Per-method access flavour. Materialised on every cap method via codegen. */
12946
+ var MethodAccessSchema = _enum([
12947
+ "view",
12948
+ "create",
12949
+ "delete"
12950
+ ]);
12951
+ /**
12952
+ * A single scope grant. Three forms — checked in matcher with OR
12953
+ * semantics; the first match decides whether the call passes:
12954
+ *
12955
+ * - `category` — broadest. `target: 'device' | 'system'` selects every
12956
+ * cap with the matching `scope` declaration. New caps inherit grants
12957
+ * automatically — adding a device cap to the codebase makes it
12958
+ * reachable to every user with `category:device [...]` without a
12959
+ * re-grant. Use this for "family viewer" / "operator" tiers.
12960
+ *
12961
+ * - `addon` — every cap that ships inside a given addon id. Niche;
12962
+ * useful when an integration needs to interact with one specific
12963
+ * addon's surface regardless of how many caps it exports.
12964
+ *
12965
+ * - `capability` — the most precise form. Single cap name. Use for
12966
+ * integration tokens where you want to grant `addons.installPackage`
12967
+ * and nothing else.
12968
+ *
12969
+ * `access` is REQUIRED with min 1 element. `admin` bypass is the
12970
+ * "give me everything" escape; there is no wildcard scope.
12971
+ */
12972
+ var AllowedProviderSchema = union([literal("*"), array(string())]);
12973
+ var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
12974
+ var CapScopeSchema = _enum(["device", "system"]);
12975
+ /**
12976
+ * DeviceSelector (scope model v3 — 2026-08-12).
12977
+ *
12978
+ * A `device` grant no longer carries a frozen list of deviceIds. It carries
12979
+ * a SELECTOR the matcher resolves against the live fleet, so the grant can be
12980
+ * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
12981
+ * AFTER the grant was minted — no re-grant, no re-login.
12982
+ *
12983
+ * - `all` — every device in the deployment. The broad viewer/operator
12984
+ * lever without a `category` grant (a `category` grant also covers device
12985
+ * caps that carry no deviceId; `all` is specifically the device set).
12986
+ * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
12987
+ * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
12988
+ * is NOT covered until the grant is edited.
12989
+ * - `types` — every device of a `DeviceType` (e.g. every `camera`).
12990
+ * DYNAMIC. A device that changes type, or a new device of the type,
12991
+ * re-resolves on the next request.
12992
+ * - `locations` — every device whose operator-assigned `location` label is
12993
+ * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
12994
+ * null/unset location matches NO `locations` selector.
12995
+ */
12996
+ var DeviceSelectorSchema = discriminatedUnion("kind", [
12997
+ object({ kind: literal("all") }),
12998
+ object({
12999
+ kind: literal("ids"),
13000
+ ids: array(number().int()).min(1)
13001
+ }),
13002
+ object({
13003
+ kind: literal("types"),
13004
+ types: array(_enum(DeviceType)).min(1)
13005
+ }),
13006
+ object({
13007
+ kind: literal("locations"),
13008
+ locations: array(string().min(1)).min(1)
13009
+ })
13010
+ ]);
13011
+ var DeviceTokenScopeSchema = object({
13012
+ type: literal("device"),
13013
+ /** The device SET this grant covers — resolved against the live fleet. */
13014
+ selector: DeviceSelectorSchema,
13015
+ access: array(MethodAccessSchema).min(1),
13016
+ /**
13017
+ * Whether a grant on a PARENT device transparently covers its accessory
13018
+ * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
13019
+ * Direction is parent → children ONLY.
13020
+ *
13021
+ * Absent → the matcher DERIVES it from the access flavour: `view`
13022
+ * inherits (a camera viewer sees the camera's accessories), `create` /
13023
+ * `delete` do NOT (actuating/removing a child is an explicit act the
13024
+ * operator must grant on the child, not inherit from the parent). Set it
13025
+ * explicitly to override that default per grant.
13026
+ */
13027
+ includeLinked: boolean().optional()
13028
+ });
13029
+ /**
13030
+ * v2 → v3 lazy migration. A pre-v3 `device` grant carried
13031
+ * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
13032
+ * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
13033
+ * EVERY parse path — stored records AND the JWT-carried scope arrays
13034
+ * normalised at the request boundary ({@link normalizeTokenScopes} in
13035
+ * `device-selector.ts`). Chosen over a one-time DB migration because a
13036
+ * migration cannot reach a JWT already in a client's hands; parse-time
13037
+ * migration covers both without a flag day. No cast — the raw object is read
13038
+ * through `Reflect.get` (its static type is `unknown`).
13039
+ */
13040
+ function migrateLegacyTokenScope(raw) {
13041
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
13042
+ if (Reflect.get(raw, "type") !== "device") return raw;
13043
+ if (Reflect.get(raw, "selector") !== void 0) return raw;
13044
+ const targets = Reflect.get(raw, "targets");
13045
+ if (!Array.isArray(targets)) return raw;
13046
+ return {
13047
+ type: "device",
13048
+ selector: {
13049
+ kind: "ids",
13050
+ ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
13051
+ },
13052
+ access: Reflect.get(raw, "access")
13053
+ };
13054
+ }
13055
+ var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
13056
+ object({
13057
+ type: literal("category"),
13058
+ target: CapScopeSchema,
13059
+ access: array(MethodAccessSchema).min(1)
13060
+ }),
13061
+ object({
13062
+ type: literal("capability"),
13063
+ target: string(),
13064
+ access: array(MethodAccessSchema).min(1)
13065
+ }),
13066
+ object({
13067
+ type: literal("addon"),
13068
+ target: string(),
13069
+ access: array(MethodAccessSchema).min(1)
13070
+ }),
13071
+ DeviceTokenScopeSchema
13072
+ ]));
13073
+ object({
13074
+ id: string(),
13075
+ username: string(),
13076
+ passwordHash: string(),
13077
+ /**
13078
+ * Admin bypass. When true, the middleware skips the scope-access
13079
+ * check entirely. There is no other axis of privilege; the legacy
13080
+ * role enum collapsed onto this boolean in v2.
13081
+ */
13082
+ isAdmin: boolean().default(false),
13083
+ allowedProviders: AllowedProviderSchema,
13084
+ allowedDevices: AllowedDevicesSchema,
13085
+ /**
13086
+ * Scopes granted to this user. Admins bypass; their `scopes` is
13087
+ * ignored. Non-admins without scopes are locked out of every
13088
+ * protected call.
13089
+ */
13090
+ scopes: array(TokenScopeSchema).default([]),
13091
+ createdAt: number(),
13092
+ updatedAt: number()
13093
+ });
13094
+ object({
13095
+ id: string(),
13096
+ label: string(),
13097
+ isAdmin: boolean().default(false),
13098
+ allowedProviders: AllowedProviderSchema,
13099
+ allowedDevices: AllowedDevicesSchema,
13100
+ tokenHash: string(),
13101
+ tokenPrefix: string(),
13102
+ createdAt: number(),
13103
+ lastUsedAt: number().optional()
13104
+ });
13105
+ object({
13106
+ id: string(),
13107
+ userId: string(),
13108
+ name: string(),
13109
+ tokenHash: string(),
13110
+ tokenPrefix: string(),
13111
+ scopes: array(TokenScopeSchema),
13112
+ expiresAt: number().nullish(),
13113
+ lastUsedAt: number().nullish(),
13114
+ createdAt: number()
13115
+ });
13116
+ /** What a share-view grant lets its holder see. */
13117
+ var ShareViewGrantSchema = object({
13118
+ kind: _enum(SHARE_VIEW_KINDS),
13119
+ deviceIds: array(number().int().nonnegative()).min(1).max(64),
13120
+ /** Talk-back, opt-in at mint time; absent means off. */
13121
+ talk: boolean().optional()
13122
+ });
13123
+ var ShareViewScopeSchema = object({
13124
+ type: literal("share-view"),
13125
+ grant: ShareViewGrantSchema
13126
+ });
13127
+ var AddonRoutesScopeSchema = object({
13128
+ type: literal("addon-routes"),
13129
+ access: array(MethodAccessSchema).min(1)
13130
+ });
13131
+ var CredentialScopeSchema = union([
13132
+ literal("*"),
13133
+ ShareViewScopeSchema,
13134
+ AddonRoutesScopeSchema,
13135
+ TokenScopeSchema
13136
+ ]);
13137
+ object({
13138
+ kind: literal("credential"),
13139
+ sub: string().min(1),
13140
+ jti: string().min(16),
13141
+ scopes: array(CredentialScopeSchema).min(1)
13142
+ });
12914
13143
  _enum(["new-session", "step-up"]);
12915
13144
  /** The languages the hub's app landing pages are written in (D858). */
12916
13145
  var AppPageLangSchema = _enum(["en", "it"]);
@@ -44116,202 +44345,6 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
44116
44345
  auth: "admin"
44117
44346
  }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
44118
44347
  /**
44119
- * Zod schemas for persisted record types.
44120
- *
44121
- * These schemas serve as the single source of truth for types that are
44122
- * stored in the settings-store backend and retrieved via tRPC. They
44123
- * replace the manual TypeScript interfaces with schema-derived types,
44124
- * enabling runtime validation when reading from the backend.
44125
- *
44126
- * Convention: each schema is named `<Type>Schema`, the inferred TS type
44127
- * keeps the original name for backward compatibility.
44128
- */
44129
- /**
44130
- * Auth model (v2 — 2026-05-12 caps-only):
44131
- *
44132
- * Two principals:
44133
- * - `isAdmin: true` — full bypass. Admin scope set is ignored.
44134
- * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
44135
- *
44136
- * No role enum. There used to be `viewer / agent / scoped` and earlier
44137
- * `super_admin` — collapsed onto the single boolean because every
44138
- * non-admin caller is governed by the same scope-check rules; the
44139
- * distinction was only ever used to route role-specific procedures
44140
- * which we no longer have. CLI / webhook integrations are `cst_*` tokens
44141
- * with `isAdmin: false` + an explicit scope list.
44142
- */
44143
- /** Per-method access flavour. Materialised on every cap method via codegen. */
44144
- var MethodAccessSchema = _enum([
44145
- "view",
44146
- "create",
44147
- "delete"
44148
- ]);
44149
- /**
44150
- * A single scope grant. Three forms — checked in matcher with OR
44151
- * semantics; the first match decides whether the call passes:
44152
- *
44153
- * - `category` — broadest. `target: 'device' | 'system'` selects every
44154
- * cap with the matching `scope` declaration. New caps inherit grants
44155
- * automatically — adding a device cap to the codebase makes it
44156
- * reachable to every user with `category:device [...]` without a
44157
- * re-grant. Use this for "family viewer" / "operator" tiers.
44158
- *
44159
- * - `addon` — every cap that ships inside a given addon id. Niche;
44160
- * useful when an integration needs to interact with one specific
44161
- * addon's surface regardless of how many caps it exports.
44162
- *
44163
- * - `capability` — the most precise form. Single cap name. Use for
44164
- * integration tokens where you want to grant `addons.installPackage`
44165
- * and nothing else.
44166
- *
44167
- * `access` is REQUIRED with min 1 element. `admin` bypass is the
44168
- * "give me everything" escape; there is no wildcard scope.
44169
- */
44170
- var AllowedProviderSchema = union([literal("*"), array(string())]);
44171
- var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
44172
- var CapScopeSchema = _enum(["device", "system"]);
44173
- /**
44174
- * DeviceSelector (scope model v3 — 2026-08-12).
44175
- *
44176
- * A `device` grant no longer carries a frozen list of deviceIds. It carries
44177
- * a SELECTOR the matcher resolves against the live fleet, so the grant can be
44178
- * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
44179
- * AFTER the grant was minted — no re-grant, no re-login.
44180
- *
44181
- * - `all` — every device in the deployment. The broad viewer/operator
44182
- * lever without a `category` grant (a `category` grant also covers device
44183
- * caps that carry no deviceId; `all` is specifically the device set).
44184
- * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
44185
- * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
44186
- * is NOT covered until the grant is edited.
44187
- * - `types` — every device of a `DeviceType` (e.g. every `camera`).
44188
- * DYNAMIC. A device that changes type, or a new device of the type,
44189
- * re-resolves on the next request.
44190
- * - `locations` — every device whose operator-assigned `location` label is
44191
- * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
44192
- * null/unset location matches NO `locations` selector.
44193
- */
44194
- var DeviceSelectorSchema = discriminatedUnion("kind", [
44195
- object({ kind: literal("all") }),
44196
- object({
44197
- kind: literal("ids"),
44198
- ids: array(number().int()).min(1)
44199
- }),
44200
- object({
44201
- kind: literal("types"),
44202
- types: array(_enum(DeviceType)).min(1)
44203
- }),
44204
- object({
44205
- kind: literal("locations"),
44206
- locations: array(string().min(1)).min(1)
44207
- })
44208
- ]);
44209
- var DeviceTokenScopeSchema = object({
44210
- type: literal("device"),
44211
- /** The device SET this grant covers — resolved against the live fleet. */
44212
- selector: DeviceSelectorSchema,
44213
- access: array(MethodAccessSchema).min(1),
44214
- /**
44215
- * Whether a grant on a PARENT device transparently covers its accessory
44216
- * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
44217
- * Direction is parent → children ONLY.
44218
- *
44219
- * Absent → the matcher DERIVES it from the access flavour: `view`
44220
- * inherits (a camera viewer sees the camera's accessories), `create` /
44221
- * `delete` do NOT (actuating/removing a child is an explicit act the
44222
- * operator must grant on the child, not inherit from the parent). Set it
44223
- * explicitly to override that default per grant.
44224
- */
44225
- includeLinked: boolean().optional()
44226
- });
44227
- /**
44228
- * v2 → v3 lazy migration. A pre-v3 `device` grant carried
44229
- * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
44230
- * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
44231
- * EVERY parse path — stored records AND the JWT-carried scope arrays
44232
- * normalised at the request boundary ({@link normalizeTokenScopes} in
44233
- * `device-selector.ts`). Chosen over a one-time DB migration because a
44234
- * migration cannot reach a JWT already in a client's hands; parse-time
44235
- * migration covers both without a flag day. No cast — the raw object is read
44236
- * through `Reflect.get` (its static type is `unknown`).
44237
- */
44238
- function migrateLegacyTokenScope(raw) {
44239
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
44240
- if (Reflect.get(raw, "type") !== "device") return raw;
44241
- if (Reflect.get(raw, "selector") !== void 0) return raw;
44242
- const targets = Reflect.get(raw, "targets");
44243
- if (!Array.isArray(targets)) return raw;
44244
- return {
44245
- type: "device",
44246
- selector: {
44247
- kind: "ids",
44248
- ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
44249
- },
44250
- access: Reflect.get(raw, "access")
44251
- };
44252
- }
44253
- var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
44254
- object({
44255
- type: literal("category"),
44256
- target: CapScopeSchema,
44257
- access: array(MethodAccessSchema).min(1)
44258
- }),
44259
- object({
44260
- type: literal("capability"),
44261
- target: string(),
44262
- access: array(MethodAccessSchema).min(1)
44263
- }),
44264
- object({
44265
- type: literal("addon"),
44266
- target: string(),
44267
- access: array(MethodAccessSchema).min(1)
44268
- }),
44269
- DeviceTokenScopeSchema
44270
- ]));
44271
- object({
44272
- id: string(),
44273
- username: string(),
44274
- passwordHash: string(),
44275
- /**
44276
- * Admin bypass. When true, the middleware skips the scope-access
44277
- * check entirely. There is no other axis of privilege; the legacy
44278
- * role enum collapsed onto this boolean in v2.
44279
- */
44280
- isAdmin: boolean().default(false),
44281
- allowedProviders: AllowedProviderSchema,
44282
- allowedDevices: AllowedDevicesSchema,
44283
- /**
44284
- * Scopes granted to this user. Admins bypass; their `scopes` is
44285
- * ignored. Non-admins without scopes are locked out of every
44286
- * protected call.
44287
- */
44288
- scopes: array(TokenScopeSchema).default([]),
44289
- createdAt: number(),
44290
- updatedAt: number()
44291
- });
44292
- object({
44293
- id: string(),
44294
- label: string(),
44295
- isAdmin: boolean().default(false),
44296
- allowedProviders: AllowedProviderSchema,
44297
- allowedDevices: AllowedDevicesSchema,
44298
- tokenHash: string(),
44299
- tokenPrefix: string(),
44300
- createdAt: number(),
44301
- lastUsedAt: number().optional()
44302
- });
44303
- object({
44304
- id: string(),
44305
- userId: string(),
44306
- name: string(),
44307
- tokenHash: string(),
44308
- tokenPrefix: string(),
44309
- scopes: array(TokenScopeSchema),
44310
- expiresAt: number().nullish(),
44311
- lastUsedAt: number().nullish(),
44312
- createdAt: number()
44313
- });
44314
- /**
44315
44348
  * `oauth-integration` — a collection cap by which an addon declares
44316
44349
  * itself a linkable OAuth client of the hub's core `/oauth2/*` server.
44317
44350
  * Each provider returns a static descriptor; the core enumerates them
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-google-services",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Google Services — Google brokers (OAuth2): Calendar as calendar-source, Gmail send as smtp-provider.",
5
5
  "keywords": [
6
6
  "camstack",