@camstack/system 1.2.70 → 1.2.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/auth/auth-manager.d.ts +13 -29
  4. package/dist/auth/scope-matcher.d.ts +6 -5
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  17. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  18. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  19. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  20. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  21. package/dist/builtins/hub-forwarder/index.js +1 -1
  22. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  23. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  24. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  25. package/dist/builtins/local-auth/local-auth.addon.js +4756 -4717
  26. package/dist/builtins/local-auth/local-auth.addon.mjs +4747 -4708
  27. package/dist/builtins/local-auth/oauth-grants.d.ts +15 -0
  28. package/dist/builtins/local-network/local-network.addon.js +1 -1
  29. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  30. package/dist/builtins/loki-logging/index.js +1 -1
  31. package/dist/builtins/loki-logging/index.mjs +1 -1
  32. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  33. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  34. package/dist/builtins/platform-probe/index.js +1 -1
  35. package/dist/builtins/platform-probe/index.mjs +1 -1
  36. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  37. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  38. package/dist/builtins/snapshot/index.js +1 -1
  39. package/dist/builtins/snapshot/index.mjs +1 -1
  40. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  41. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-CnQXXnK1.mjs → dist-Daxx9CB0.mjs} +92 -17
  51. package/dist/{dist-DCLoJf2T.js → dist-I0WgqQQ-.js} +103 -16
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.js +4 -13
  54. package/dist/index.mjs +3 -13
  55. package/dist/kernel/transport/parent-unowned-call.d.ts +27 -0
  56. package/dist/{manifest-python-deps-DKrHcUZj.mjs → manifest-python-deps-BdWxKNvu.mjs} +2 -0
  57. package/dist/{manifest-python-deps-DafB-1hp.js → manifest-python-deps-BqIpg0C7.js} +2 -0
  58. package/package.json +1 -1
@@ -1,5 +1,13 @@
1
1
  import { ISsoBridgeProvider, TokenScope } from '@camstack/types';
2
2
  import { OauthSessionManager } from './oauth-session-manager.js';
3
+ /**
4
+ * The refresh lifetime for an integration whose descriptor declares none.
5
+ * 30 days — the value every link used before `refreshTokenTtlSec` existed, kept
6
+ * as the default so an integration that says nothing is unchanged.
7
+ */
8
+ export declare const DEFAULT_REFRESH_TTL_SEC = 2592000;
9
+ /** Seconds, or `'never'` for a refresh token minted with no `exp` claim. */
10
+ export type RefreshTtl = number | 'never';
3
11
  interface IssueCodeInput {
4
12
  integrationId: string;
5
13
  userId: string;
@@ -8,6 +16,13 @@ interface IssueCodeInput {
8
16
  redirectUri: string;
9
17
  hubUrl: string;
10
18
  codeChallenge?: string;
19
+ /**
20
+ * From the integration's `OauthIntegrationDescriptor.refreshTokenTtlSec`.
21
+ * Baked into the CODE so it travels with the link: re-reading the descriptor
22
+ * at refresh time would let an edit retroactively change the lifetime of a
23
+ * grant the operator already consented to.
24
+ */
25
+ refreshTtlSec?: RefreshTtl;
11
26
  }
12
27
  /**
13
28
  * PKCE S256 check (RFC 7636 §4.6): `BASE64URL(SHA256(verifier)) === challenge`.
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DCLoJf2T.js");
3
+ const require_dist = require("../../dist-I0WgqQQ-.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { K as localNetworkCapability, Rt as EventCategory, ft as BaseAddon } from "../../dist-CnQXXnK1.mjs";
1
+ import { Bt as EventCategory, K as localNetworkCapability, mt as BaseAddon } from "../../dist-Daxx9CB0.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { ft as BaseAddon, q as logDestinationCapability } from "../../dist-CnQXXnK1.mjs";
1
+ import { mt as BaseAddon, q as logDestinationCapability } from "../../dist-Daxx9CB0.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,4 +1,4 @@
1
- import { Rt as EventCategory, Tt as createEvent, X as metricsProviderCapability, ft as BaseAddon } from "../../dist-CnQXXnK1.mjs";
1
+ import { Bt as EventCategory, Dt as createEvent, X as metricsProviderCapability, mt as BaseAddon } from "../../dist-Daxx9CB0.mjs";
2
2
  import { execFile, execFileSync } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_util = require("node:util");
9
9
  let node_os = require("node:os");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Dt as emitReadiness, P as enumerateInferenceDevices, Rt as EventCategory, dt as errMsg, et as platformProbeCapability, ft as BaseAddon, nt as scoreRuntimes } from "../../dist-CnQXXnK1.mjs";
2
+ import { Bt as EventCategory, P as enumerateInferenceDevices, et as platformProbeCapability, it as scoreRuntimes, kt as emitReadiness, mt as BaseAddon, pt as errMsg } from "../../dist-Daxx9CB0.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Rt as EventCategory, ft as BaseAddon } from "../../dist-CnQXXnK1.mjs";
1
+ import { Bt as EventCategory, mt as BaseAddon } from "../../dist-Daxx9CB0.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  let zod = require("zod");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
@@ -1,4 +1,4 @@
1
- import { C as bareAddonId, dt as errMsg, ft as BaseAddon, gt as DeviceFeature, i as BatteryStatusSchema, it as snapshotCapability, jt as nodePin, st as streamQualityLabel, vt as DeviceType } from "../../dist-CnQXXnK1.mjs";
1
+ import { C as bareAddonId, Nt as nodePin, bt as DeviceType, i as BatteryStatusSchema, lt as streamQualityLabel, mt as BaseAddon, ot as snapshotCapability, pt as errMsg, vt as DeviceFeature } from "../../dist-Daxx9CB0.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { R as filesystemBrowseCapability, ft as BaseAddon, ot as storageProviderCapability } from "../../dist-CnQXXnK1.mjs";
1
+ import { R as filesystemBrowseCapability, ct as storageProviderCapability, mt as BaseAddon } from "../../dist-Daxx9CB0.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -1,4 +1,4 @@
1
- import { C as bareAddonId, Nt as parseJsonUnknown, O as dataStoreProviderCapability, St as asJsonObject, dt as errMsg, f as RUNTIME_DEFAULTS, ft as BaseAddon, k as decodeVectorBase64, lt as vectorDimFromBase64, ut as vectorStoreCapability } from "../../dist-CnQXXnK1.mjs";
1
+ import { C as bareAddonId, Ft as parseJsonUnknown, O as dataStoreProviderCapability, dt as vectorDimFromBase64, f as RUNTIME_DEFAULTS, ft as vectorStoreCapability, k as decodeVectorBase64, mt as BaseAddon, pt as errMsg, wt as asJsonObject } from "../../dist-Daxx9CB0.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { statSync } from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  let node_fs_promises = require("node:fs/promises");
8
8
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Mt as parseJsonObject, at as storageCapability, ft as BaseAddon, h as StorageLocationTypeSchema, rt as settingsStoreCapability } from "../../dist-CnQXXnK1.mjs";
1
+ import { Pt as parseJsonObject, at as settingsStoreCapability, h as StorageLocationTypeSchema, mt as BaseAddon, st as storageCapability } from "../../dist-Daxx9CB0.mjs";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path$1 from "node:path";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { dt as errMsg, ft as BaseAddon, kt as hydrateSchema } from "../../dist-CnQXXnK1.mjs";
1
+ import { jt as hydrateSchema, mt as BaseAddon, pt as errMsg } from "../../dist-Daxx9CB0.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DCLoJf2T.js");
6
+ const require_dist = require("../../dist-I0WgqQQ-.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { ft as BaseAddon, q as logDestinationCapability } from "../../dist-CnQXXnK1.mjs";
1
+ import { mt as BaseAddon, q as logDestinationCapability } from "../../dist-Daxx9CB0.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -4148,6 +4148,24 @@ var ConvertResultSchema = z.object({
4148
4148
  artifacts: z.array(ConvertArtifactSchema).readonly()
4149
4149
  });
4150
4150
  /**
4151
+ * True if the scope set grants `access` on device-scoped capabilities.
4152
+ * A `category:device` grant covers every device cap (the broad grant).
4153
+ */
4154
+ function scopesAllowDeviceCap(scopes, access) {
4155
+ return scopes.some((s) => s.type === "category" && s.target === "device" && s.access.includes(access));
4156
+ }
4157
+ /**
4158
+ * True if the scope set names this addon with at least `access`.
4159
+ *
4160
+ * Deliberately NOT satisfied by `category:device`: a grant over per-camera
4161
+ * capabilities says nothing about an addon's own HTTP surface, and conflating
4162
+ * the two is precisely the confusion that let an Alexa token drive Home
4163
+ * Assistant's command route.
4164
+ */
4165
+ function scopesAllowAddon(scopes, addonId, access) {
4166
+ return scopes.some((s) => s.type === "addon" && s.target === addonId && s.access.includes(access));
4167
+ }
4168
+ /**
4151
4169
  * Error types for the safe expression engine. Two distinct classes so callers
4152
4170
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
4153
4171
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -13056,21 +13074,32 @@ var ScopedTokenSchema = z.object({
13056
13074
  * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
13057
13075
  * `/api/oauth2/integrations` are built from this collection alone.
13058
13076
  *
13059
- * **Scopes.** `requestedScopes` is baked into every token this integration is
13060
- * ever issued and the operator consents to it once. Derive it from the tRPC
13061
- * paths the client calls **with that token**, against `METHOD_ACCESS_MAP`, and
13062
- * prefer a narrow `capability:` scope to a `category:` one unless the client
13063
- * genuinely needs a whole family. A category scope grants every future member
13077
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
13078
+ * NEEDS to function.** Not a blast radius, not a conservative
13079
+ * under-declaration, not a description of some other path the addon happens to
13080
+ * have. Derive it from what the client actually calls **with this token** —
13081
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
13082
+ * addon HTTP route it posts to — and write the call that justifies each entry
13083
+ * next to it. Two integrations once used this field to mean two different
13084
+ * things; the operator ruled there is one meaning, and any third integration
13085
+ * inherits it (2026-08-09).
13086
+ *
13087
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
13088
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
13089
+ * checks an integration token's grant before letting it reach an
13090
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
13091
+ * that stops working** — a missing `addon:` entry means `403 Token scope
13092
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
13093
+ * honestly rather than weakening a check to make a route pass.
13094
+ *
13095
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
13096
+ * genuinely needs a whole family; a category scope grants every future member
13064
13097
  * of that category too. `category:system [create]` has been rejected once and
13065
13098
  * should stay rejected: it hands `addons.installPackage` to an integration.
13066
13099
  *
13067
- * What it does NOT cover: calls the ADDON makes over `ctx.api`, which run as
13068
- * the addon and are not scope-checked. Alexa's descriptor is narrower than
13069
- * Home Assistant's for exactly that reason its Lambda posts directives and
13070
- * the addon does the work, while the Home Assistant component calls tRPC
13071
- * directly with the token. So `requestedScopes` describes the blast radius of
13072
- * the GRANT, not the reach of the integration; do not widen one to describe the
13073
- * other.
13100
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
13101
+ * scope-checked, so they are not what this field describes but reaching the
13102
+ * addon's route in the first place IS, and that is the entry to declare.
13074
13103
  *
13075
13104
  * **The boot window.** An addon registers its provider after its runner forks
13076
13105
  * and initialises, so between hub start and that moment this collection is
@@ -13111,7 +13140,30 @@ var OauthIntegrationDescriptorSchema = z.object({
13111
13140
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
13112
13141
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
13113
13142
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
13114
- hubUrl: z.string().optional()
13143
+ hubUrl: z.string().optional(),
13144
+ /**
13145
+ * How long a REFRESH token issued for this integration lives — seconds, or
13146
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
13147
+ * 30-day default, which is what every link used before this field existed.
13148
+ *
13149
+ * Declared here for the same reason `requestedScopes` is: the integration
13150
+ * knows what it needs. Amazon's account linking and a Home Assistant config
13151
+ * entry are both meant to survive indefinitely, and re-linking is a manual
13152
+ * user action, so a 30-day expiry silently unlinks a working integration.
13153
+ *
13154
+ * **The security posture, stated so it is owned deliberately.** A refresh
13155
+ * token that never expires is permanent access if it leaks. What bounds it is
13156
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
13157
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
13158
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
13159
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
13160
+ * revoking a link takes effect there only after its remaining hour. That hour
13161
+ * is why the access TTL is not configurable.
13162
+ *
13163
+ * The value is baked into the authorization code at `/authorize` and travels
13164
+ * on the tokens, so editing this field changes FUTURE links only.
13165
+ */
13166
+ refreshTokenTtlSec: z.union([z.number().int().positive(), z.literal("never")]).optional()
13115
13167
  });
13116
13168
  var oauthIntegrationCapability = {
13117
13169
  name: "oauth-integration",
@@ -17416,7 +17468,16 @@ var SsoBridgeClaimsSchema = z.object({
17416
17468
  codeChallenge: z.string().optional(),
17417
17469
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
17418
17470
  * tokens so the verify path can check the session is not revoked. */
17419
- sessionId: z.string().optional()
17471
+ sessionId: z.string().optional(),
17472
+ /**
17473
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
17474
+ * Baked into the code at `/authorize` from the integration's descriptor and
17475
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
17476
+ * on the token rather than being re-read from the descriptor on purpose:
17477
+ * editing a descriptor must not retroactively extend or shorten a link the
17478
+ * operator already consented to.
17479
+ */
17480
+ refreshTtl: z.union([z.number().int().positive(), z.literal("never")]).optional()
17420
17481
  });
17421
17482
  var ssoBridgeCapability = {
17422
17483
  name: "sso-bridge",
@@ -17426,7 +17487,16 @@ var ssoBridgeCapability = {
17426
17487
  methods: {
17427
17488
  signBridgeToken: method(z.object({
17428
17489
  claims: SsoBridgeClaimsSchema,
17429
- ttlSec: z.number().int().positive().optional()
17490
+ /**
17491
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
17492
+ *
17493
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
17494
+ * this field already means "the 5-minute SSO hand-off default", and
17495
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
17496
+ * went through the numeric path would fail at mint time and break
17497
+ * linking rather than produce an eternal token.
17498
+ */
17499
+ ttlSec: z.union([z.number().int().positive(), z.literal("never")]).optional()
17430
17500
  }), z.object({ token: z.string() })),
17431
17501
  verifyBridgeToken: method(z.object({ token: z.string() }), SsoBridgeClaimsSchema.nullable())
17432
17502
  }
@@ -26222,7 +26292,12 @@ var userManagementCapability = {
26222
26292
  hubUrl: z.string(),
26223
26293
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
26224
26294
  * that carries one can ONLY be exchanged with the matching verifier. */
26225
- codeChallenge: z.string().optional()
26295
+ codeChallenge: z.string().optional(),
26296
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
26297
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
26298
+ * code so the link carries its own lifetime; omit for the 30-day
26299
+ * default. */
26300
+ refreshTtlSec: z.union([z.number().int().positive(), z.literal("never")]).optional()
26226
26301
  }), z.object({ code: z.string() }), {
26227
26302
  kind: "mutation",
26228
26303
  access: "create"
@@ -33238,4 +33313,4 @@ function enumerateInferenceDevices(hw) {
33238
33313
  return out;
33239
33314
  }
33240
33315
  //#endregion
33241
- export { parseStreamParamsFormPatch as $, deviceManagerCapability as A, isDeviceConfigCap as At, isCollectionArrayMethod as B, bareAddonId as C, asNumber as Ct, coreBlocksCapability as D, emitReadiness as Dt, coreBlockIdFromAddonId as E, emitDownForOwnedCaps as Et, enumerateItemArrayFields as F, resolveCapMount as Ft, lifecycleJobSchema as G, isSameAddonId as H, enumerateSchemaFields as I, scopeKey as It, logLevelAtMost as J, localNetworkCapability as K, extractNestedAddonId as L, sleep as Lt, deviceStatusCapability as M, parseJsonObject as Mt, doorbellCapability as N, parseJsonUnknown as Nt, dataStoreProviderCapability as O, expandCapMethods as Ot, enumerateInferenceDevices as P, readinessKey as Pt, objectInputDeclaresAddonId as Q, filesystemBrowseCapability as R, EventCategory as Rt, backupCapability as S, asJsonObject as St, coreBlockAddonId as T, createEvent as Tt, isVoidInput as U, isObjectInput as V, kebabToCamel as W, metricsProviderCapability as X, looseSchema as Y, normalizeUnit as Z, addonPagesCapability as _, DeviceRole as _t, CAP_NAMES_WITH_STATUS as a, storageCapability as at, alertsCapability as b, ReadinessTimeoutError as bt, CoreBlockSchema as c, userManagementCapability as ct, METHOD_ACCESS_MAP as d, errMsg as dt, platformProbeCapability as et, RUNTIME_DEFAULTS as f, BaseAddon as ft, UserRecordSchema as g, DeviceFeature as gt, StorageLocationTypeSchema as h, DEVICE_STATUS_METHOD as ht, BatteryStatusSchema as i, snapshotCapability as it, deviceStateCapability as j, nodePin as jt, decodeVectorBase64 as k, hydrateSchema as kt, DeclaredDevices as l, vectorDimFromBase64 as lt, ScopedTokenSchema as m, DEVICE_SETTINGS_CONTRIBUTION_METHODS as mt, AlertSchema as n, scoreRuntimes as nt, CORE_BLOCKS_ADDON_ID as o, storageProviderCapability as ot, STREAM_PROFILE_META as p, DATAPLANE_SECRET_HEADER as pt, logDestinationCapability as q, ApiKeyRecordSchema as r, settingsStoreCapability as rt, CORE_BLOCK_ADDON_PREFIX as s, streamQualityLabel as st, ALL_CAPABILITY_DEFINITIONS as t, procedureAuthKey as tt, DeviceStatusSchema as u, vectorStoreCapability as ut, addonSettingsCapability as v, DeviceType as vt, buildStreamParamsConfigSchema as w, asString as wt, authProviderCapability as x, WELL_KNOWN_TAB_MAP as xt, addonWidgetsCapability as y, ReadinessRegistry as yt, isArrayOutputSchema as z };
33316
+ export { parseStreamParamsFormPatch as $, deviceManagerCapability as A, expandCapMethods as At, isCollectionArrayMethod as B, EventCategory as Bt, bareAddonId as C, WELL_KNOWN_TAB_MAP as Ct, coreBlocksCapability as D, createEvent as Dt, coreBlockIdFromAddonId as E, asString as Et, enumerateItemArrayFields as F, parseJsonUnknown as Ft, lifecycleJobSchema as G, isSameAddonId as H, enumerateSchemaFields as I, readinessKey as It, logLevelAtMost as J, localNetworkCapability as K, extractNestedAddonId as L, resolveCapMount as Lt, deviceStatusCapability as M, isDeviceConfigCap as Mt, doorbellCapability as N, nodePin as Nt, dataStoreProviderCapability as O, emitDownForOwnedCaps as Ot, enumerateInferenceDevices as P, parseJsonObject as Pt, objectInputDeclaresAddonId as Q, filesystemBrowseCapability as R, scopeKey as Rt, backupCapability as S, ReadinessTimeoutError as St, coreBlockAddonId as T, asNumber as Tt, isVoidInput as U, isObjectInput as V, kebabToCamel as W, metricsProviderCapability as X, looseSchema as Y, normalizeUnit as Z, addonPagesCapability as _, DEVICE_STATUS_METHOD as _t, CAP_NAMES_WITH_STATUS as a, settingsStoreCapability as at, alertsCapability as b, DeviceType as bt, CoreBlockSchema as c, storageProviderCapability as ct, METHOD_ACCESS_MAP as d, vectorDimFromBase64 as dt, platformProbeCapability as et, RUNTIME_DEFAULTS as f, vectorStoreCapability as ft, UserRecordSchema as g, DEVICE_SETTINGS_CONTRIBUTION_METHODS as gt, StorageLocationTypeSchema as h, DATAPLANE_SECRET_HEADER as ht, BatteryStatusSchema as i, scoreRuntimes as it, deviceStateCapability as j, hydrateSchema as jt, decodeVectorBase64 as k, emitReadiness as kt, DeclaredDevices as l, streamQualityLabel as lt, ScopedTokenSchema as m, BaseAddon as mt, AlertSchema as n, scopesAllowAddon as nt, CORE_BLOCKS_ADDON_ID as o, snapshotCapability as ot, STREAM_PROFILE_META as p, errMsg as pt, logDestinationCapability as q, ApiKeyRecordSchema as r, scopesAllowDeviceCap as rt, CORE_BLOCK_ADDON_PREFIX as s, storageCapability as st, ALL_CAPABILITY_DEFINITIONS as t, procedureAuthKey as tt, DeviceStatusSchema as u, userManagementCapability as ut, addonSettingsCapability as v, DeviceFeature as vt, buildStreamParamsConfigSchema as w, asJsonObject as wt, authProviderCapability as x, ReadinessRegistry as xt, addonWidgetsCapability as y, DeviceRole as yt, isArrayOutputSchema as z, sleep as zt };
@@ -4148,6 +4148,24 @@ var ConvertResultSchema = zod.z.object({
4148
4148
  artifacts: zod.z.array(ConvertArtifactSchema).readonly()
4149
4149
  });
4150
4150
  /**
4151
+ * True if the scope set grants `access` on device-scoped capabilities.
4152
+ * A `category:device` grant covers every device cap (the broad grant).
4153
+ */
4154
+ function scopesAllowDeviceCap(scopes, access) {
4155
+ return scopes.some((s) => s.type === "category" && s.target === "device" && s.access.includes(access));
4156
+ }
4157
+ /**
4158
+ * True if the scope set names this addon with at least `access`.
4159
+ *
4160
+ * Deliberately NOT satisfied by `category:device`: a grant over per-camera
4161
+ * capabilities says nothing about an addon's own HTTP surface, and conflating
4162
+ * the two is precisely the confusion that let an Alexa token drive Home
4163
+ * Assistant's command route.
4164
+ */
4165
+ function scopesAllowAddon(scopes, addonId, access) {
4166
+ return scopes.some((s) => s.type === "addon" && s.target === addonId && s.access.includes(access));
4167
+ }
4168
+ /**
4151
4169
  * Error types for the safe expression engine. Two distinct classes so callers
4152
4170
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
4153
4171
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -13056,21 +13074,32 @@ var ScopedTokenSchema = zod.z.object({
13056
13074
  * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
13057
13075
  * `/api/oauth2/integrations` are built from this collection alone.
13058
13076
  *
13059
- * **Scopes.** `requestedScopes` is baked into every token this integration is
13060
- * ever issued and the operator consents to it once. Derive it from the tRPC
13061
- * paths the client calls **with that token**, against `METHOD_ACCESS_MAP`, and
13062
- * prefer a narrow `capability:` scope to a `category:` one unless the client
13063
- * genuinely needs a whole family. A category scope grants every future member
13077
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
13078
+ * NEEDS to function.** Not a blast radius, not a conservative
13079
+ * under-declaration, not a description of some other path the addon happens to
13080
+ * have. Derive it from what the client actually calls **with this token** —
13081
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
13082
+ * addon HTTP route it posts to — and write the call that justifies each entry
13083
+ * next to it. Two integrations once used this field to mean two different
13084
+ * things; the operator ruled there is one meaning, and any third integration
13085
+ * inherits it (2026-08-09).
13086
+ *
13087
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
13088
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
13089
+ * checks an integration token's grant before letting it reach an
13090
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
13091
+ * that stops working** — a missing `addon:` entry means `403 Token scope
13092
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
13093
+ * honestly rather than weakening a check to make a route pass.
13094
+ *
13095
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
13096
+ * genuinely needs a whole family; a category scope grants every future member
13064
13097
  * of that category too. `category:system [create]` has been rejected once and
13065
13098
  * should stay rejected: it hands `addons.installPackage` to an integration.
13066
13099
  *
13067
- * What it does NOT cover: calls the ADDON makes over `ctx.api`, which run as
13068
- * the addon and are not scope-checked. Alexa's descriptor is narrower than
13069
- * Home Assistant's for exactly that reason its Lambda posts directives and
13070
- * the addon does the work, while the Home Assistant component calls tRPC
13071
- * directly with the token. So `requestedScopes` describes the blast radius of
13072
- * the GRANT, not the reach of the integration; do not widen one to describe the
13073
- * other.
13100
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
13101
+ * scope-checked, so they are not what this field describes but reaching the
13102
+ * addon's route in the first place IS, and that is the entry to declare.
13074
13103
  *
13075
13104
  * **The boot window.** An addon registers its provider after its runner forks
13076
13105
  * and initialises, so between hub start and that moment this collection is
@@ -13111,7 +13140,30 @@ var OauthIntegrationDescriptorSchema = zod.z.object({
13111
13140
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
13112
13141
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
13113
13142
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
13114
- hubUrl: zod.z.string().optional()
13143
+ hubUrl: zod.z.string().optional(),
13144
+ /**
13145
+ * How long a REFRESH token issued for this integration lives — seconds, or
13146
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
13147
+ * 30-day default, which is what every link used before this field existed.
13148
+ *
13149
+ * Declared here for the same reason `requestedScopes` is: the integration
13150
+ * knows what it needs. Amazon's account linking and a Home Assistant config
13151
+ * entry are both meant to survive indefinitely, and re-linking is a manual
13152
+ * user action, so a 30-day expiry silently unlinks a working integration.
13153
+ *
13154
+ * **The security posture, stated so it is owned deliberately.** A refresh
13155
+ * token that never expires is permanent access if it leaks. What bounds it is
13156
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
13157
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
13158
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
13159
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
13160
+ * revoking a link takes effect there only after its remaining hour. That hour
13161
+ * is why the access TTL is not configurable.
13162
+ *
13163
+ * The value is baked into the authorization code at `/authorize` and travels
13164
+ * on the tokens, so editing this field changes FUTURE links only.
13165
+ */
13166
+ refreshTokenTtlSec: zod.z.union([zod.z.number().int().positive(), zod.z.literal("never")]).optional()
13115
13167
  });
13116
13168
  var oauthIntegrationCapability = {
13117
13169
  name: "oauth-integration",
@@ -17416,7 +17468,16 @@ var SsoBridgeClaimsSchema = zod.z.object({
17416
17468
  codeChallenge: zod.z.string().optional(),
17417
17469
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
17418
17470
  * tokens so the verify path can check the session is not revoked. */
17419
- sessionId: zod.z.string().optional()
17471
+ sessionId: zod.z.string().optional(),
17472
+ /**
17473
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
17474
+ * Baked into the code at `/authorize` from the integration's descriptor and
17475
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
17476
+ * on the token rather than being re-read from the descriptor on purpose:
17477
+ * editing a descriptor must not retroactively extend or shorten a link the
17478
+ * operator already consented to.
17479
+ */
17480
+ refreshTtl: zod.z.union([zod.z.number().int().positive(), zod.z.literal("never")]).optional()
17420
17481
  });
17421
17482
  var ssoBridgeCapability = {
17422
17483
  name: "sso-bridge",
@@ -17426,7 +17487,16 @@ var ssoBridgeCapability = {
17426
17487
  methods: {
17427
17488
  signBridgeToken: method(zod.z.object({
17428
17489
  claims: SsoBridgeClaimsSchema,
17429
- ttlSec: zod.z.number().int().positive().optional()
17490
+ /**
17491
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
17492
+ *
17493
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
17494
+ * this field already means "the 5-minute SSO hand-off default", and
17495
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
17496
+ * went through the numeric path would fail at mint time and break
17497
+ * linking rather than produce an eternal token.
17498
+ */
17499
+ ttlSec: zod.z.union([zod.z.number().int().positive(), zod.z.literal("never")]).optional()
17430
17500
  }), zod.z.object({ token: zod.z.string() })),
17431
17501
  verifyBridgeToken: method(zod.z.object({ token: zod.z.string() }), SsoBridgeClaimsSchema.nullable())
17432
17502
  }
@@ -26222,7 +26292,12 @@ var userManagementCapability = {
26222
26292
  hubUrl: zod.z.string(),
26223
26293
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
26224
26294
  * that carries one can ONLY be exchanged with the matching verifier. */
26225
- codeChallenge: zod.z.string().optional()
26295
+ codeChallenge: zod.z.string().optional(),
26296
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
26297
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
26298
+ * code so the link carries its own lifetime; omit for the 30-day
26299
+ * default. */
26300
+ refreshTtlSec: zod.z.union([zod.z.number().int().positive(), zod.z.literal("never")]).optional()
26226
26301
  }), zod.z.object({ code: zod.z.string() }), {
26227
26302
  kind: "mutation",
26228
26303
  access: "create"
@@ -33730,6 +33805,18 @@ Object.defineProperty(exports, "scopeKey", {
33730
33805
  return scopeKey;
33731
33806
  }
33732
33807
  });
33808
+ Object.defineProperty(exports, "scopesAllowAddon", {
33809
+ enumerable: true,
33810
+ get: function() {
33811
+ return scopesAllowAddon;
33812
+ }
33813
+ });
33814
+ Object.defineProperty(exports, "scopesAllowDeviceCap", {
33815
+ enumerable: true,
33816
+ get: function() {
33817
+ return scopesAllowDeviceCap;
33818
+ }
33819
+ });
33733
33820
  Object.defineProperty(exports, "scoreRuntimes", {
33734
33821
  enumerable: true,
33735
33822
  get: function() {
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export type { ApiKeyRecord, ApiKeyStorageAccess } from './auth/api-key-manager.j
8
8
  export { ApiKeyManager } from './auth/api-key-manager.js';
9
9
  export type { AuthConfigReader, TokenPayload } from './auth/auth-manager.js';
10
10
  export { AuthManager } from './auth/auth-manager.js';
11
- export { scopesAllowDeviceCap } from './auth/scope-matcher.js';
11
+ export { scopesAllowAddon, scopesAllowDeviceCap } from './auth/scope-matcher.js';
12
12
  export { ScopedTokenManager } from './auth/scoped-token-manager.js';
13
13
  export type { UserConfigReader, UserRecord, UserStorageAccess } from './auth/user-manager.js';
14
14
  export { UserManager } from './auth/user-manager.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-DCLoJf2T.js");
3
+ const require_dist = require("./dist-I0WgqQQ-.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -25,7 +25,7 @@ const require_builtins_system_config_system_config_addon = require("./builtins/s
25
25
  require("./builtins/system-config/index.js");
26
26
  const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
27
27
  const require_file_data_plane = require("./file-data-plane-DUHPHa-Y.js");
28
- const require_manifest_python_deps = require("./manifest-python-deps-DafB-1hp.js");
28
+ const require_manifest_python_deps = require("./manifest-python-deps-BqIpg0C7.js");
29
29
  const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
30
30
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
31
31
  let zod = require("zod");
@@ -183,16 +183,6 @@ function matchPath(pattern, path) {
183
183
  return params;
184
184
  }
185
185
  //#endregion
186
- //#region src/auth/scope-matcher.ts
187
- /**
188
- * True if the scope set grants `access` on device-scoped capabilities.
189
- * A `category:device` grant covers every device cap (the broad grant).
190
- * Mirrors the OR-semantics of the existing token scope matcher.
191
- */
192
- function scopesAllowDeviceCap(scopes, access) {
193
- return scopes.some((s) => s.type === "category" && s.target === "device" && s.access.includes(access));
194
- }
195
- //#endregion
196
186
  //#region src/events/event-bus.ts
197
187
  var EventBus = class {
198
188
  listeners = /* @__PURE__ */ new Map();
@@ -95239,7 +95229,8 @@ exports.resolveNpmInvocation = require_manifest_python_deps.resolveNpmInvocation
95239
95229
  exports.runNpm = require_manifest_python_deps.runNpm;
95240
95230
  exports.scheduleSelfRestart = scheduleSelfRestart;
95241
95231
  exports.scopeKey = require_dist.scopeKey;
95242
- exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
95232
+ exports.scopesAllowAddon = require_dist.scopesAllowAddon;
95233
+ exports.scopesAllowDeviceCap = require_dist.scopesAllowDeviceCap;
95243
95234
  exports.selectAddonResidue = selectAddonResidue;
95244
95235
  exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
95245
95236
  exports.setHubConnected = require_manifest_python_deps.setHubConnected;