@camstack/addon-post-analysis 1.2.56 → 1.2.59

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.
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-41fKf-q9.mjs
1
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -14,6 +14,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
14
14
  */
15
15
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
16
16
  /**
17
+ * A newer addon or server-root package version was found by the
18
+ * authoritative registry check. Emitted once per
19
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
20
+ * polling of the same result is deduplicated by the checker.
21
+ */
22
+ EventCategory["UpdateAvailable"] = "update.available";
23
+ /**
17
24
  * Readiness transition for a capability provider. Every producer emits
18
25
  * this event on `onInitialize` completion, `onDestroy`, and
19
26
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -7285,6 +7292,104 @@ object({
7285
7292
  })
7286
7293
  });
7287
7294
  /**
7295
+ * Adoption job — the background form of `device-adoption.adopt`.
7296
+ *
7297
+ * ## Why this exists
7298
+ *
7299
+ * `adopt({childNativeIds: [...]})` materialises one CamStack device per
7300
+ * candidate PLUS every accessory child, and the whole array shares ONE UDS
7301
+ * request deadline (60s). Measured on the live hub against Home Assistant:
7302
+ * each device the kernel creates costs ~450 ms — `devices.create` pre-seeds
7303
+ * meta with up to eleven SEQUENTIAL round trips (`setName`, `setType`,
7304
+ * `setRole`, … `persistConfig`) before the class is constructed — and an
7305
+ * accessory child costs the same as its parent. So the real unit of work is
7306
+ * the CHILD, not the candidate:
7307
+ *
7308
+ * - 25 candidates averaging 6 children → ~150 devices → **>60s, times out**
7309
+ * - ONE candidate with 217 children → ~217 devices → **>60s, times out**
7310
+ *
7311
+ * That second line is why this is a job and not a smaller batch. No chunking,
7312
+ * no bounded concurrency over candidates and no per-call tuning can fix a
7313
+ * shape where **N=1 already exceeds the deadline** — the count that blows the
7314
+ * budget is the source system's accessory fan-out, which the operator does not
7315
+ * choose and cannot see. A design that only works below some N is the same bug
7316
+ * deferred.
7317
+ *
7318
+ * ## What the timeout did NOT do
7319
+ *
7320
+ * It did not stop the work. The UDS deadline ends the CALLER's wait; the
7321
+ * provider's loop runs to completion. Measured: a 25-candidate adopt that
7322
+ * "failed" at 60s had adopted 17 by 87s and all 25 by ~130s. The operator saw
7323
+ * an error and had no way to learn that. Every field below exists so that
7324
+ * question has an answer.
7325
+ *
7326
+ * ## Idempotency
7327
+ *
7328
+ * Jobs are in-RAM; a restart forgets them. That is safe here because adoption
7329
+ * is keyed by a stable id (`ha:<broker>:dev:<nativeId>` and equivalents), so
7330
+ * re-running a job re-adopts nothing: an already-adopted candidate is SKIPPED
7331
+ * by the engine before any provider call and lands in `alreadyAdopted`. It is
7332
+ * never a duplicate device, and never an error the operator has to interpret.
7333
+ */
7334
+ var AdoptionJobStateSchema = _enum([
7335
+ "running",
7336
+ "done",
7337
+ "failed",
7338
+ "cancelled"
7339
+ ]);
7340
+ /**
7341
+ * Per-candidate result. Every candidate the job was asked to adopt ends in
7342
+ * exactly one of these buckets — there is no silent drop, and the operator can
7343
+ * always answer "which of my 25 landed?".
7344
+ *
7345
+ * - `adopted` — created now by this job.
7346
+ * - `already-adopted` — a device for this candidate existed before the job
7347
+ * reached it (a re-run, or a retry after a timeout). Not an error.
7348
+ * - `failed` — the provider threw; `error` carries the message.
7349
+ * - `cancelled` — the operator cancelled before this candidate was reached.
7350
+ */
7351
+ var AdoptionOutcomeSchema = _enum([
7352
+ "adopted",
7353
+ "already-adopted",
7354
+ "failed",
7355
+ "cancelled"
7356
+ ]);
7357
+ var AdoptionCandidateResultSchema = object({
7358
+ childNativeId: string(),
7359
+ outcome: AdoptionOutcomeSchema,
7360
+ /** The materialised parent device id — null for `failed` / `cancelled`. */
7361
+ parentDeviceId: number().int().nonnegative().nullable(),
7362
+ /** Accessory children created for this candidate. */
7363
+ accessoryCount: number().int().nonnegative(),
7364
+ /** Failure message; null unless `outcome === 'failed'`. */
7365
+ error: string().nullable()
7366
+ });
7367
+ var AdoptionJobSchema = object({
7368
+ jobId: string(),
7369
+ /** The integration provider this job adopts through (the `addonId` pin). */
7370
+ addonId: string(),
7371
+ integrationId: string(),
7372
+ state: AdoptionJobStateSchema,
7373
+ /** Candidates the job was asked to adopt. Known up front, so never null. */
7374
+ total: number().int().nonnegative(),
7375
+ /** Candidates that have reached a terminal bucket. */
7376
+ processed: number().int().nonnegative(),
7377
+ adopted: number().int().nonnegative(),
7378
+ alreadyAdopted: number().int().nonnegative(),
7379
+ failed: number().int().nonnegative(),
7380
+ /** Accessory child devices created across every candidate — the real unit
7381
+ * of work, surfaced so a slow job is legible rather than mysterious. */
7382
+ accessoriesCreated: number().int().nonnegative(),
7383
+ /** The candidate currently being adopted; null when idle or finished. */
7384
+ currentChildNativeId: string().nullable(),
7385
+ /** One entry per candidate, in the order they were processed. */
7386
+ results: array(AdoptionCandidateResultSchema).readonly(),
7387
+ startedAt: number(),
7388
+ finishedAt: number().nullable(),
7389
+ /** Set only when the job itself broke (not a per-candidate failure). */
7390
+ error: string().nullable()
7391
+ });
7392
+ /**
7288
7393
  * Per-camera FUNCTION SWITCHES — the one coherent on/off surface over the
7289
7394
  * pipeline functions an operator thinks in terms of.
7290
7395
  *
@@ -12197,6 +12302,15 @@ method(object({
12197
12302
  }), method(ReleaseInputSchema.extend({ addonId: string() }), _void(), {
12198
12303
  kind: "mutation",
12199
12304
  auth: "admin"
12305
+ }), method(AdoptInputSchema.extend({ addonId: string() }), object({ jobId: string() }), {
12306
+ kind: "mutation",
12307
+ auth: "admin"
12308
+ }), method(object({
12309
+ addonId: string(),
12310
+ integrationId: string().optional()
12311
+ }), array(AdoptionJobSchema).readonly(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
12312
+ kind: "mutation",
12313
+ auth: "admin"
12200
12314
  }), method(ResyncInputSchema, ResyncResultSchema, {
12201
12315
  kind: "mutation",
12202
12316
  auth: "admin"
@@ -14149,8 +14263,35 @@ var NcDeliverySchema = _enum([
14149
14263
  "immediate",
14150
14264
  "track-end",
14151
14265
  "device-event",
14152
- "package-event"
14266
+ "package-event",
14267
+ "system-event"
14268
+ ]);
14269
+ /**
14270
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
14271
+ * Bus categories are normalized into these intent-level kinds so rules do not
14272
+ * depend on a provider's raw event name or payload shape.
14273
+ */
14274
+ var NcSystemEventKindSchema = _enum([
14275
+ "camera-online",
14276
+ "camera-offline",
14277
+ "stream-online",
14278
+ "stream-offline",
14279
+ "node-online",
14280
+ "node-offline",
14281
+ "addon-update-available",
14282
+ "server-update-available"
14153
14283
  ]);
14284
+ /**
14285
+ * One coherent system-event condition. `kinds` is the required opt-in safety
14286
+ * gate; the remaining lists are optional narrowing filters relevant to the
14287
+ * selected kinds.
14288
+ */
14289
+ var NcSystemEventConditionSchema = object({
14290
+ kinds: array(NcSystemEventKindSchema).min(1),
14291
+ deviceIds: array(number().int()).min(1).optional(),
14292
+ nodeIds: array(string().min(1)).min(1).optional(),
14293
+ packageNames: array(string().min(1)).min(1).optional()
14294
+ });
14154
14295
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14155
14296
  var NcScheduleSchema = object({
14156
14297
  windows: array(object({
@@ -14455,6 +14596,8 @@ var NcConditionsSchema = object({
14455
14596
  "picked-up",
14456
14597
  "both"
14457
14598
  ]).optional(),
14599
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
14600
+ systemEvent: NcSystemEventConditionSchema.optional(),
14458
14601
  /**
14459
14602
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
14460
14603
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -14674,7 +14817,8 @@ var NcTestResultSchema = object({
14674
14817
  "object-event",
14675
14818
  "track",
14676
14819
  "device-event",
14677
- "package-event"
14820
+ "package-event",
14821
+ "system-event"
14678
14822
  ]),
14679
14823
  deviceId: number(),
14680
14824
  timestamp: number(),
@@ -14696,7 +14840,8 @@ var NcConditionDescriptorSchema = object({
14696
14840
  "schedule",
14697
14841
  "device",
14698
14842
  "package",
14699
- "occupancy"
14843
+ "occupancy",
14844
+ "system"
14700
14845
  ]),
14701
14846
  label: string(),
14702
14847
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -14714,7 +14859,8 @@ var NcConditionDescriptorSchema = object({
14714
14859
  "crossingSelect",
14715
14860
  "polygonDraw",
14716
14861
  "occupancy",
14717
- "deviceState"
14862
+ "deviceState",
14863
+ "systemEvent"
14718
14864
  ]),
14719
14865
  operator: _enum([
14720
14866
  "in",
@@ -14756,6 +14902,50 @@ var NcConditionDescriptorSchema = object({
14756
14902
  * rule editors render from the catalog, not hardcoded forms (spec §4.2).
14757
14903
  */
14758
14904
  var NC_CONDITION_CATALOG = [
14905
+ {
14906
+ id: "systemEvent",
14907
+ group: "system",
14908
+ label: "System event",
14909
+ valueType: "systemEvent",
14910
+ options: [
14911
+ {
14912
+ value: "camera-online",
14913
+ label: "Camera online"
14914
+ },
14915
+ {
14916
+ value: "camera-offline",
14917
+ label: "Camera offline"
14918
+ },
14919
+ {
14920
+ value: "stream-online",
14921
+ label: "Stream online"
14922
+ },
14923
+ {
14924
+ value: "stream-offline",
14925
+ label: "Stream offline"
14926
+ },
14927
+ {
14928
+ value: "node-online",
14929
+ label: "Node online"
14930
+ },
14931
+ {
14932
+ value: "node-offline",
14933
+ label: "Node offline"
14934
+ },
14935
+ {
14936
+ value: "addon-update-available",
14937
+ label: "Addon update available"
14938
+ },
14939
+ {
14940
+ value: "server-update-available",
14941
+ label: "Server update available"
14942
+ }
14943
+ ],
14944
+ operator: "in",
14945
+ appliesTo: ["system-event"],
14946
+ phase: "P1",
14947
+ description: "Infrastructure and update events. Optionally narrow camera/stream events by device, node events by node id, and addon updates by package name."
14948
+ },
14759
14949
  {
14760
14950
  id: "devices",
14761
14951
  group: "scope",
@@ -15070,7 +15260,8 @@ var NC_CONDITION_CATALOG = [
15070
15260
  "immediate",
15071
15261
  "track-end",
15072
15262
  "device-event",
15073
- "package-event"
15263
+ "package-event",
15264
+ "system-event"
15074
15265
  ],
15075
15266
  phase: "P1",
15076
15267
  description: "Weekly activation windows (invertible); absent = always active."
@@ -15098,7 +15289,8 @@ var NcHistoryRecordKindSchema = _enum([
15098
15289
  "object-event",
15099
15290
  "track-end",
15100
15291
  "device-event",
15101
- "package-event"
15292
+ "package-event",
15293
+ "system-event"
15102
15294
  ]);
15103
15295
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
15104
15296
  var NcHistorySubjectSchema = object({
@@ -15106,7 +15298,14 @@ var NcHistorySubjectSchema = object({
15106
15298
  label: string().optional(),
15107
15299
  confidence: number().optional(),
15108
15300
  zones: array(string()),
15109
- timestamp: number()
15301
+ timestamp: number(),
15302
+ systemEvent: object({
15303
+ kind: NcSystemEventKindSchema,
15304
+ subject: string(),
15305
+ title: string(),
15306
+ body: string(),
15307
+ data: record(string(), unknown())
15308
+ }).optional()
15110
15309
  });
15111
15310
  /**
15112
15311
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -15569,6 +15768,76 @@ object({
15569
15768
  * Each provider returns a static descriptor; the core enumerates them
15570
15769
  * to validate the `integration=` query param and resolve the consent
15571
15770
  * label + the scopes baked into the issued token.
15771
+ *
15772
+ * ## Declaring one
15773
+ *
15774
+ * An OAuth client is integration-specific knowledge — who the client is, what
15775
+ * it may ask for, where it may be sent — so it is declared by the ADDON that
15776
+ * owns the integration, never by the kernel and never as a branch inside
15777
+ * `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
15778
+ * steps, no others:
15779
+ *
15780
+ * 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
15781
+ * manifest entry. This is also what tells the hub, at addon-LOAD time, that
15782
+ * a descriptor is owed — see "the boot window" below.
15783
+ * 2. Return a provider from `onInitialize()`:
15784
+ *
15785
+ * ```ts
15786
+ * const provider: IOauthIntegrationProvider = {
15787
+ * getDescriptor: async () => ({
15788
+ * integrationId: 'my-thing', // the `integration=` query param
15789
+ * displayName: 'My Thing',
15790
+ * requestedScopes: [ … ], // see below
15791
+ * allowedRedirectPrefixes: ['https://callback.example/'],
15792
+ * }),
15793
+ * }
15794
+ * return [{ capability: oauthIntegrationCapability, provider }]
15795
+ * ```
15796
+ *
15797
+ * The descriptor must be **static** — it is read on the authorize path, so
15798
+ * never put an await on network or disk behind it, and never register it
15799
+ * behind one either (a provider is registered only once `onInitialize`
15800
+ * RETURNS, so anything awaited before the return delays linking).
15801
+ * 3. Nothing else. There is no allow-list to join, no id to register with the
15802
+ * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
15803
+ * `/api/oauth2/integrations` are built from this collection alone.
15804
+ *
15805
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
15806
+ * NEEDS to function.** Not a blast radius, not a conservative
15807
+ * under-declaration, not a description of some other path the addon happens to
15808
+ * have. Derive it from what the client actually calls **with this token** —
15809
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
15810
+ * addon HTTP route it posts to — and write the call that justifies each entry
15811
+ * next to it. Two integrations once used this field to mean two different
15812
+ * things; the operator ruled there is one meaning, and any third integration
15813
+ * inherits it (2026-08-09).
15814
+ *
15815
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
15816
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
15817
+ * checks an integration token's grant before letting it reach an
15818
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
15819
+ * that stops working** — a missing `addon:` entry means `403 Token scope
15820
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
15821
+ * honestly rather than weakening a check to make a route pass.
15822
+ *
15823
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
15824
+ * genuinely needs a whole family; a category scope grants every future member
15825
+ * of that category too. `category:system [create]` has been rejected once and
15826
+ * should stay rejected: it hands `addons.installPackage` to an integration.
15827
+ *
15828
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
15829
+ * scope-checked, so they are not what this field describes — but reaching the
15830
+ * addon's route in the first place IS, and that is the entry to declare.
15831
+ *
15832
+ * **The boot window.** An addon registers its provider after its runner forks
15833
+ * and initialises, so between hub start and that moment this collection is
15834
+ * incomplete and an `integrationId` can be legitimately absent. The core does
15835
+ * not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
15836
+ * it compares the manifest declarers against the registered providers and
15837
+ * answers `503 temporarily_unavailable` (with `Retry-After` and the pending
15838
+ * addon ids) instead of `400 unknown integration`, and reports
15839
+ * `complete: false` on `GET /api/oauth2/integrations`. A client should retry
15840
+ * while the list is incomplete rather than conclude the hub cannot do OAuth.
15572
15841
  */
15573
15842
  var OauthIntegrationDescriptorSchema = object({
15574
15843
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
@@ -15599,7 +15868,30 @@ var OauthIntegrationDescriptorSchema = object({
15599
15868
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
15600
15869
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
15601
15870
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
15602
- hubUrl: string().optional()
15871
+ hubUrl: string().optional(),
15872
+ /**
15873
+ * How long a REFRESH token issued for this integration lives — seconds, or
15874
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
15875
+ * 30-day default, which is what every link used before this field existed.
15876
+ *
15877
+ * Declared here for the same reason `requestedScopes` is: the integration
15878
+ * knows what it needs. Amazon's account linking and a Home Assistant config
15879
+ * entry are both meant to survive indefinitely, and re-linking is a manual
15880
+ * user action, so a 30-day expiry silently unlinks a working integration.
15881
+ *
15882
+ * **The security posture, stated so it is owned deliberately.** A refresh
15883
+ * token that never expires is permanent access if it leaks. What bounds it is
15884
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
15885
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
15886
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
15887
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
15888
+ * revoking a link takes effect there only after its remaining hour. That hour
15889
+ * is why the access TTL is not configurable.
15890
+ *
15891
+ * The value is baked into the authorization code at `/authorize` and travels
15892
+ * on the tokens, so editing this field changes FUTURE links only.
15893
+ */
15894
+ refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
15603
15895
  });
15604
15896
  method(_void(), OauthIntegrationDescriptorSchema);
15605
15897
  /**
@@ -19157,11 +19449,29 @@ var SsoBridgeClaimsSchema = object({
19157
19449
  codeChallenge: string().optional(),
19158
19450
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
19159
19451
  * tokens so the verify path can check the session is not revoked. */
19160
- sessionId: string().optional()
19452
+ sessionId: string().optional(),
19453
+ /**
19454
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
19455
+ * Baked into the code at `/authorize` from the integration's descriptor and
19456
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
19457
+ * on the token rather than being re-read from the descriptor on purpose:
19458
+ * editing a descriptor must not retroactively extend or shorten a link the
19459
+ * operator already consented to.
19460
+ */
19461
+ refreshTtl: union([number().int().positive(), literal("never")]).optional()
19161
19462
  });
19162
19463
  method(object({
19163
19464
  claims: SsoBridgeClaimsSchema,
19164
- ttlSec: number().int().positive().optional()
19465
+ /**
19466
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
19467
+ *
19468
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
19469
+ * this field already means "the 5-minute SSO hand-off default", and
19470
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
19471
+ * went through the numeric path would fail at mint time and break
19472
+ * linking rather than produce an eternal token.
19473
+ */
19474
+ ttlSec: union([number().int().positive(), literal("never")]).optional()
19165
19475
  }), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
19166
19476
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
19167
19477
  providerId: string().min(1),
@@ -21156,6 +21466,37 @@ onColorChanged: { data: object({
21156
21466
  */
21157
21467
  runtimeState: ColorStatusSchema
21158
21468
  };
21469
+ var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
21470
+ object({
21471
+ outcome: literal("validated"),
21472
+ /** Round-trip of the sign-in, when the provider measured it. */
21473
+ latencyMs: number().nonnegative().optional(),
21474
+ /** Optional human detail worth showing next to the tick
21475
+ * ("3 devices visible on this account"). */
21476
+ detail: string().optional()
21477
+ }).strict(),
21478
+ object({
21479
+ outcome: literal("rejected"),
21480
+ error: string()
21481
+ }).strict(),
21482
+ object({
21483
+ outcome: literal("inconclusive"),
21484
+ error: string()
21485
+ }).strict()
21486
+ ]);
21487
+ var ConnectionTestInputSchema = object({
21488
+ /** Candidate integration settings, exactly as the create form collected them. */
21489
+ settings: record(string(), unknown()) });
21490
+ /**
21491
+ * What the provider's test actually DOES, so the UI can say it in words before
21492
+ * the operator presses the button ("Signs in to the Dreo cloud"). Purely
21493
+ * descriptive — it never changes routing.
21494
+ */
21495
+ var ConnectionTestDescriptorSchema = object({ label: string() });
21496
+ method(ConnectionTestInputSchema, ConnectionTestOutcomeSchema, {
21497
+ kind: "mutation",
21498
+ auth: "admin"
21499
+ }), method(_void(), ConnectionTestDescriptorSchema, { auth: "admin" });
21159
21500
  /**
21160
21501
  * Upstream-system connectivity sensor — distinct from `device-status`,
21161
21502
  * which is the kernel-managed online/offline flag for the device's
@@ -22543,15 +22884,57 @@ var AvailableIntegrationTypeSchema = object({
22543
22884
  * flow can import (e.g. HA areas). Drives the adopt modal's "import
22544
22885
  * locations" checkbox. Provider-declared in the addon manifest. */
22545
22886
  supportsLocationImport: boolean(),
22887
+ /**
22888
+ * True when this integration DECLARES a pre-creation test (the
22889
+ * `connection-test` cap, or a broker whose settings it stores). Drives the
22890
+ * Test button: an integration that cannot be tested must say so up front
22891
+ * rather than offering a button that always answers the same nonsense.
22892
+ */
22893
+ canTest: boolean(),
22546
22894
  existingInstances: array(object({
22547
22895
  id: string(),
22548
22896
  name: string()
22549
22897
  })),
22550
22898
  canAdd: boolean()
22551
22899
  });
22900
+ /**
22901
+ * Why a test could not be answered as a plain boolean.
22902
+ *
22903
+ * `success` alone collapsed four different situations into one red box, and the
22904
+ * one that mattered most — "nobody ever asked the remote anything" — looked
22905
+ * exactly like "the remote said no". The status is the discriminator:
22906
+ *
22907
+ * - `validated` — a provider-declared test ran and the remote ACCEPTED.
22908
+ * - `rejected` — a provider-declared test ran and the remote REFUSED.
22909
+ * The only status that blocks `integrations.create`.
22910
+ * - `inconclusive` — a test IS declared but could not complete (timeout,
22911
+ * DNS, 5xx). Nothing was observed; not a failure.
22912
+ * - `unsupported` — this integration declares NO test. Nothing was
22913
+ * observed either; not a failure, and not a pass.
22914
+ *
22915
+ * `unsupported` and `inconclusive` both carry `success: false` so an older
22916
+ * client can never read them as a green tick, and both carry an `error` string
22917
+ * that SAYS the test did not run rather than inventing a failure.
22918
+ */
22919
+ var TestConnectionStatusEnum = _enum([
22920
+ "validated",
22921
+ "rejected",
22922
+ "inconclusive",
22923
+ "unsupported"
22924
+ ]);
22552
22925
  var TestConnectionResultSchema$1 = object({
22926
+ /** True ONLY for `validated`. Never true for a test that did not run. */
22553
22927
  success: boolean(),
22554
- error: string().optional()
22928
+ error: string().optional(),
22929
+ /** Optional for wire back-compat with clients built before the tri-state;
22930
+ * the server always sets it. */
22931
+ status: TestConnectionStatusEnum.optional(),
22932
+ /** Addon id whose declared test answered — `null` when none did. Lets the UI
22933
+ * attribute a result instead of blaming "the integration". */
22934
+ testedBy: string().nullable().optional(),
22935
+ latencyMs: number().nonnegative().optional(),
22936
+ /** Human detail from a `validated` result ("3 devices on this account"). */
22937
+ detail: string().optional()
22555
22938
  });
22556
22939
  var CreateIntegrationInputSchema = object({
22557
22940
  addonId: string(),
@@ -26354,7 +26737,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
26354
26737
  hubUrl: string(),
26355
26738
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
26356
26739
  * that carries one can ONLY be exchanged with the matching verifier. */
26357
- codeChallenge: string().optional()
26740
+ codeChallenge: string().optional(),
26741
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
26742
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
26743
+ * code so the link carries its own lifetime; omit for the 30-day
26744
+ * default. */
26745
+ refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
26358
26746
  }), object({ code: string() }), {
26359
26747
  kind: "mutation",
26360
26748
  access: "create"
@@ -28833,6 +29221,18 @@ Object.freeze({
28833
29221
  addonId: null,
28834
29222
  access: "create"
28835
29223
  },
29224
+ "connectionTest.describeTest": {
29225
+ capName: "connection-test",
29226
+ capScope: "system",
29227
+ addonId: null,
29228
+ access: "view"
29229
+ },
29230
+ "connectionTest.testSettings": {
29231
+ capName: "connection-test",
29232
+ capScope: "system",
29233
+ addonId: null,
29234
+ access: "create"
29235
+ },
28836
29236
  "consumables.reset": {
28837
29237
  capName: "consumables",
28838
29238
  capScope: "device",
@@ -29223,6 +29623,12 @@ Object.freeze({
29223
29623
  addonId: null,
29224
29624
  access: "create"
29225
29625
  },
29626
+ "deviceManager.adoptionCancelJob": {
29627
+ capName: "device-manager",
29628
+ capScope: "system",
29629
+ addonId: null,
29630
+ access: "create"
29631
+ },
29226
29632
  "deviceManager.adoptionListCandidateFilters": {
29227
29633
  capName: "device-manager",
29228
29634
  capScope: "system",
@@ -29235,6 +29641,12 @@ Object.freeze({
29235
29641
  addonId: null,
29236
29642
  access: "view"
29237
29643
  },
29644
+ "deviceManager.adoptionListJobs": {
29645
+ capName: "device-manager",
29646
+ capScope: "system",
29647
+ addonId: null,
29648
+ access: "view"
29649
+ },
29238
29650
  "deviceManager.adoptionRefresh": {
29239
29651
  capName: "device-manager",
29240
29652
  capScope: "system",
@@ -29253,6 +29665,12 @@ Object.freeze({
29253
29665
  addonId: null,
29254
29666
  access: "create"
29255
29667
  },
29668
+ "deviceManager.adoptionStartJob": {
29669
+ capName: "device-manager",
29670
+ capScope: "system",
29671
+ addonId: null,
29672
+ access: "create"
29673
+ },
29256
29674
  "deviceManager.allocateDeviceId": {
29257
29675
  capName: "device-manager",
29258
29676
  capScope: "system",