@capybara-hq/kernel 0.1.0-rc.12 → 0.1.0-rc.14

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 (267) hide show
  1. package/dist/admin/admin.schemas.d.ts +13 -0
  2. package/dist/agents/agent-lookup.d.ts +5 -0
  3. package/dist/agents/agent-lookup.js +15 -0
  4. package/dist/background/background.module.js +2 -2
  5. package/dist/background/launch-lock.d.ts +1 -1
  6. package/dist/background/launch-lock.js +1 -1
  7. package/dist/background/poll.job.d.ts +2 -2
  8. package/dist/background/poll.job.js +10 -10
  9. package/dist/background/stop.d.ts +1 -1
  10. package/dist/background/stop.js +3 -3
  11. package/dist/background/store.d.ts +5 -5
  12. package/dist/background/store.js +5 -5
  13. package/dist/background/tasks.service.d.ts +6 -6
  14. package/dist/background/tasks.service.js +13 -13
  15. package/dist/catalog/admin-catalog.controller.d.ts +13 -2
  16. package/dist/catalog/admin-catalog.controller.js +24 -3
  17. package/dist/catalog/admin-catalog.schemas.d.ts +10 -0
  18. package/dist/catalog/admin-catalog.schemas.js +7 -0
  19. package/dist/catalog/catalog-content.d.ts +9 -1
  20. package/dist/catalog/catalog-content.js +2 -1
  21. package/dist/catalog/catalog-files.d.ts +3 -2
  22. package/dist/catalog/catalog-files.js +8 -5
  23. package/dist/catalog/component-scope.service.d.ts +14 -1
  24. package/dist/catalog/component-scope.service.js +12 -2
  25. package/dist/catalog/marketplaces.service.d.ts +1 -1
  26. package/dist/catalog/org-catalog.controller.d.ts +5 -4
  27. package/dist/catalog/org-catalog.controller.js +5 -4
  28. package/dist/catalog/org-catalog.schemas.d.ts +18 -2
  29. package/dist/catalog/org-catalog.schemas.js +16 -2
  30. package/dist/catalog/org-catalog.service.js +8 -2
  31. package/dist/catalog/plugin-import.js +1 -1
  32. package/dist/catalog/plugin-package.js +37 -9
  33. package/dist/catalog/plugin-upgrade.d.ts +15 -1
  34. package/dist/catalog/plugin-upgrade.js +18 -5
  35. package/dist/channels/channels.module.js +2 -2
  36. package/dist/channels/connections.controller.d.ts +1 -1
  37. package/dist/channels/connections.controller.js +1 -1
  38. package/dist/channels/connections.service.d.ts +22 -5
  39. package/dist/channels/connections.service.js +56 -10
  40. package/dist/channels/inbound.d.ts +8 -8
  41. package/dist/channels/inbound.js +31 -31
  42. package/dist/channels/phrases.d.ts +27 -27
  43. package/dist/channels/phrases.js +27 -27
  44. package/dist/channels/webhook.d.ts +21 -1
  45. package/dist/channels/webhook.js +39 -4
  46. package/dist/config/docs.js +1 -1
  47. package/dist/config/schema.d.ts +2 -2
  48. package/dist/config/schema.js +21 -21
  49. package/dist/conversations/conversations.controller.d.ts +11 -11
  50. package/dist/conversations/conversations.controller.js +15 -15
  51. package/dist/conversations/conversations.module.d.ts +1 -1
  52. package/dist/conversations/conversations.module.js +7 -7
  53. package/dist/conversations/conversations.schemas.js +1 -1
  54. package/dist/conversations/conversations.service.d.ts +14 -14
  55. package/dist/conversations/conversations.service.js +19 -19
  56. package/dist/conversations/{environment-status.d.ts → device-status.d.ts} +9 -9
  57. package/dist/conversations/{environment-status.js → device-status.js} +18 -18
  58. package/dist/conversations/event-log.d.ts +1 -1
  59. package/dist/conversations/event-log.js +1 -1
  60. package/dist/conversations/events.d.ts +1 -1
  61. package/dist/conversations/events.js +2 -2
  62. package/dist/ctx/create-ctx.d.ts +26 -3
  63. package/dist/ctx/create-ctx.js +134 -26
  64. package/dist/db/kernel-schema.d.ts +37 -25
  65. package/dist/{environments → devices}/access.d.ts +19 -19
  66. package/dist/{environments → devices}/access.js +28 -28
  67. package/dist/devices/conversation-devices.d.ts +75 -0
  68. package/dist/{environments/conversation-environments.js → devices/conversation-devices.js} +71 -72
  69. package/dist/devices/devices.controller.d.ts +36 -0
  70. package/dist/{environments/environments.controller.js → devices/devices.controller.js} +37 -37
  71. package/dist/devices/devices.module.d.ts +5 -13
  72. package/dist/devices/devices.module.js +16 -29
  73. package/dist/devices/devices.schemas.d.ts +3 -0
  74. package/dist/devices/devices.schemas.js +14 -0
  75. package/dist/{environments/environments.service.d.ts → devices/devices.service.d.ts} +70 -70
  76. package/dist/{environments/environments.service.js → devices/devices.service.js} +122 -122
  77. package/dist/devices/dto.d.ts +13 -54
  78. package/dist/devices/dto.js +7 -39
  79. package/dist/devices/label.d.ts +11 -0
  80. package/dist/{environments → devices}/label.js +7 -7
  81. package/dist/{environments → devices}/legacy-cleanup.job.d.ts +3 -3
  82. package/dist/{environments → devices}/legacy-cleanup.job.js +3 -3
  83. package/dist/errors.d.ts +29 -25
  84. package/dist/errors.js +29 -25
  85. package/dist/evals/recorded-tools.d.ts +2 -2
  86. package/dist/evals/recorded-tools.js +6 -6
  87. package/dist/extensions/admin-ops.js +1 -0
  88. package/dist/extensions/admin-view.js +2 -0
  89. package/dist/extensions/effective-config.d.ts +6 -0
  90. package/dist/extensions/effective-config.js +18 -7
  91. package/dist/extensions/guide-places.d.ts +66 -0
  92. package/dist/extensions/guide-places.js +152 -0
  93. package/dist/extensions/host.d.ts +13 -2
  94. package/dist/extensions/host.js +27 -6
  95. package/dist/extensions/instance.d.ts +17 -2
  96. package/dist/extensions/instance.js +21 -0
  97. package/dist/extensions/ledger.d.ts +1 -1
  98. package/dist/extensions/permission-set.d.ts +16 -2
  99. package/dist/extensions/permission-set.js +17 -3
  100. package/dist/extensions/permission-view.d.ts +10 -1
  101. package/dist/extensions/plugin-guides.d.ts +207 -54
  102. package/dist/extensions/plugin-guides.js +436 -165
  103. package/dist/files/previewers.d.ts +1 -1
  104. package/dist/files/previewers.js +1 -1
  105. package/dist/http/api.module.js +16 -6
  106. package/dist/http/gateway-routes.d.ts +1 -1
  107. package/dist/http/gateway-routes.js +1 -1
  108. package/dist/identity/settings.d.ts +32 -0
  109. package/dist/identity/settings.js +26 -2
  110. package/dist/infra/config/kernel-config.service.js +4 -4
  111. package/dist/infra/lock/lock.service.d.ts +8 -0
  112. package/dist/infra/lock/lock.service.js +27 -0
  113. package/dist/infra/throttler/named-throttlers.d.ts +4 -1
  114. package/dist/infra/throttler/named-throttlers.js +16 -1
  115. package/dist/infra/throttler/throttler.guard.js +1 -1
  116. package/dist/kernel.module.js +3 -3
  117. package/dist/{devices → local-devices}/device-auth.controller.d.ts +6 -6
  118. package/dist/{devices → local-devices}/device-auth.controller.js +5 -5
  119. package/dist/{devices → local-devices}/device-auth.d.ts +20 -20
  120. package/dist/{devices → local-devices}/device-auth.js +51 -53
  121. package/dist/local-devices/device-auth.module.d.ts +7 -0
  122. package/dist/{devices → local-devices}/device-auth.module.js +5 -5
  123. package/dist/{devices → local-devices}/device-routes.d.ts +3 -3
  124. package/dist/{devices → local-devices}/device-routes.js +2 -2
  125. package/dist/local-devices/dto.d.ts +66 -0
  126. package/dist/local-devices/dto.js +44 -0
  127. package/dist/{devices → local-devices}/keys.d.ts +11 -11
  128. package/dist/{devices → local-devices}/keys.js +11 -11
  129. package/dist/{devices/device-gateway.d.ts → local-devices/local-device-gateway.d.ts} +14 -14
  130. package/dist/{devices/device-gateway.js → local-devices/local-device-gateway.js} +44 -44
  131. package/dist/{devices/device-provider.d.ts → local-devices/local-device-provider.d.ts} +15 -15
  132. package/dist/{devices/device-provider.js → local-devices/local-device-provider.js} +23 -23
  133. package/dist/local-devices/local-devices.module.d.ts +16 -0
  134. package/dist/local-devices/local-devices.module.js +44 -0
  135. package/dist/{devices → local-devices}/machines.d.ts +26 -26
  136. package/dist/{devices → local-devices}/machines.js +71 -71
  137. package/dist/{devices → local-devices}/relay.d.ts +15 -15
  138. package/dist/{devices → local-devices}/relay.js +55 -55
  139. package/dist/{devices → local-devices}/targets.d.ts +8 -8
  140. package/dist/{devices → local-devices}/targets.js +14 -14
  141. package/dist/orgs/offboarding.d.ts +6 -6
  142. package/dist/orgs/offboarding.js +12 -12
  143. package/dist/orgs/settings.service.d.ts +28 -0
  144. package/dist/orgs/settings.service.js +20 -1
  145. package/dist/overrides.d.ts +3 -1
  146. package/dist/overrides.js +2 -0
  147. package/dist/platform/exclusions.js +48 -8
  148. package/dist/platform/operation.js +10 -2
  149. package/dist/platform/ops/admin.ops.js +8 -2
  150. package/dist/platform/ops/connectors.ops.js +2 -0
  151. package/dist/platform/ops/conversations.ops.d.ts +3 -3
  152. package/dist/platform/ops/conversations.ops.js +9 -9
  153. package/dist/platform/ops/devices.ops.d.ts +26 -0
  154. package/dist/platform/ops/devices.ops.js +311 -0
  155. package/dist/platform/ops/org-settings.ops.js +2 -2
  156. package/dist/platform/ops/orgs.ops.js +1 -0
  157. package/dist/platform/ops/setup.ops.d.ts +24 -0
  158. package/dist/platform/ops/setup.ops.js +212 -0
  159. package/dist/platform/ops/ui.ops.d.ts +8 -0
  160. package/dist/platform/ops/ui.ops.js +41 -0
  161. package/dist/platform/platform-ops.module.d.ts +4 -2
  162. package/dist/platform/platform-ops.module.js +13 -5
  163. package/dist/platform/route-schemas.js +11 -3
  164. package/dist/roles/gateway-role.d.ts +3 -3
  165. package/dist/roles/gateway-role.js +4 -4
  166. package/dist/roles/gateway.module.d.ts +1 -1
  167. package/dist/roles/gateway.module.js +5 -5
  168. package/dist/roles/worker.module.js +1 -1
  169. package/dist/runtime/context/build.d.ts +1 -1
  170. package/dist/runtime/context/build.js +6 -6
  171. package/dist/runtime/context/compaction.js +1 -1
  172. package/dist/runtime/context/{environment-paths.d.ts → device-paths.d.ts} +11 -11
  173. package/dist/runtime/context/{environment-paths.js → device-paths.js} +13 -13
  174. package/dist/runtime/maintenance.d.ts +1 -1
  175. package/dist/sandbox/jobs.d.ts +13 -13
  176. package/dist/sandbox/jobs.js +20 -20
  177. package/dist/sandbox/leases.d.ts +9 -9
  178. package/dist/sandbox/leases.js +14 -14
  179. package/dist/sandbox/providers.d.ts +8 -8
  180. package/dist/sandbox/providers.js +8 -8
  181. package/dist/sandbox/sandbox.module.d.ts +1 -1
  182. package/dist/sandbox/sandbox.module.js +1 -1
  183. package/dist/sandbox/sandbox.service.d.ts +76 -76
  184. package/dist/sandbox/sandbox.service.js +230 -230
  185. package/dist/setup/kernel-checks.d.ts +91 -0
  186. package/dist/setup/kernel-checks.js +295 -0
  187. package/dist/setup/oauth-token-probe.d.ts +47 -0
  188. package/dist/setup/oauth-token-probe.js +108 -0
  189. package/dist/setup/redact-check.d.ts +18 -0
  190. package/dist/setup/redact-check.js +46 -0
  191. package/dist/setup/setup-check-registry.d.ts +86 -0
  192. package/dist/setup/setup-check-registry.js +75 -0
  193. package/dist/setup/setup-checks.controller.d.ts +28 -0
  194. package/dist/setup/setup-checks.controller.js +78 -0
  195. package/dist/setup/setup-checks.schemas.d.ts +38 -0
  196. package/dist/setup/setup-checks.schemas.js +43 -0
  197. package/dist/setup/setup-checks.service.d.ts +144 -0
  198. package/dist/setup/setup-checks.service.js +683 -0
  199. package/dist/setup/setup-guides.controller.d.ts +18 -0
  200. package/dist/setup/setup-guides.controller.js +48 -0
  201. package/dist/setup/setup-guides.schemas.d.ts +10 -0
  202. package/dist/setup/setup-guides.schemas.js +6 -0
  203. package/dist/setup/setup-guides.service.d.ts +126 -0
  204. package/dist/setup/setup-guides.service.js +315 -0
  205. package/dist/setup/setup.module.d.ts +8 -0
  206. package/dist/setup/setup.module.js +31 -0
  207. package/dist/tools/builtins/builtin-tools.d.ts +2 -2
  208. package/dist/tools/builtins/builtin-tools.js +5 -5
  209. package/dist/tools/builtins/platform.js +1 -1
  210. package/dist/tools/builtins/sandbox/bash.js +6 -6
  211. package/dist/tools/builtins/sandbox/common.d.ts +25 -25
  212. package/dist/tools/builtins/sandbox/common.js +51 -51
  213. package/dist/tools/builtins/sandbox/edit.js +2 -2
  214. package/dist/tools/builtins/sandbox/index.d.ts +1 -1
  215. package/dist/tools/builtins/sandbox/index.js +1 -1
  216. package/dist/tools/builtins/sandbox/task-output.d.ts +6 -6
  217. package/dist/tools/builtins/sandbox/task-output.js +10 -10
  218. package/dist/tools/builtins/sandbox/task-stop.js +2 -2
  219. package/dist/tools/builtins/sandbox/write.js +2 -2
  220. package/dist/tools/builtins/skill-load.d.ts +1 -1
  221. package/dist/tools/builtins/skill-load.js +2 -2
  222. package/dist/tools/connectors.controller.d.ts +28 -11
  223. package/dist/tools/connectors.controller.js +49 -14
  224. package/dist/tools/connectors.schemas.d.ts +4 -0
  225. package/dist/tools/connectors.schemas.js +4 -0
  226. package/dist/tools/connectors.service.d.ts +30 -2
  227. package/dist/tools/connectors.service.js +98 -3
  228. package/dist/tools/credentials.d.ts +20 -0
  229. package/dist/tools/credentials.js +90 -1
  230. package/dist/tools/oauth/tokens.d.ts +20 -0
  231. package/dist/tools/oauth/tokens.js +49 -24
  232. package/dist/tools/pipeline.js +4 -3
  233. package/dist/tools/registry.d.ts +2 -2
  234. package/dist/tools/tools.module.js +3 -3
  235. package/dist/ui/admin-ui.controller.d.ts +56 -0
  236. package/dist/ui/admin-ui.controller.js +192 -0
  237. package/dist/ui/arrangement.d.ts +74 -0
  238. package/dist/ui/arrangement.js +98 -0
  239. package/dist/ui/embed-token.d.ts +2 -2
  240. package/dist/ui/embed-token.js +30 -5
  241. package/dist/ui/registry.d.ts +29 -14
  242. package/dist/ui/registry.js +31 -10
  243. package/dist/ui/slots.controller.d.ts +11 -0
  244. package/dist/ui/slots.controller.js +25 -0
  245. package/dist/ui/ui-stream.d.ts +20 -3
  246. package/dist/ui/ui-stream.js +25 -7
  247. package/dist/ui/ui.schemas.d.ts +66 -0
  248. package/dist/ui/ui.schemas.js +65 -0
  249. package/dist/ui/views.controller.d.ts +35 -49
  250. package/dist/ui/views.controller.js +48 -70
  251. package/dist/ui/views.d.ts +84 -18
  252. package/dist/ui/views.js +201 -38
  253. package/package.json +3 -3
  254. package/src/migrations/0105_devices_rename.sql +136 -0
  255. package/src/migrations/0106_setup_guides_v2.sql +10 -0
  256. package/dist/devices/device-auth.module.d.ts +0 -7
  257. package/dist/environments/conversation-environments.d.ts +0 -75
  258. package/dist/environments/dto.d.ts +0 -25
  259. package/dist/environments/dto.js +0 -12
  260. package/dist/environments/environments.controller.d.ts +0 -36
  261. package/dist/environments/environments.module.d.ts +0 -8
  262. package/dist/environments/environments.module.js +0 -31
  263. package/dist/environments/environments.schemas.d.ts +0 -3
  264. package/dist/environments/environments.schemas.js +0 -14
  265. package/dist/environments/label.d.ts +0 -11
  266. package/dist/platform/ops/environments.ops.d.ts +0 -26
  267. package/dist/platform/ops/environments.ops.js +0 -311
@@ -0,0 +1,683 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { createHmac, randomUUID } from "node:crypto";
11
+ import { SETUP_CHECK_LIMITS, validateSetupCheckResult, } from "@capybara-hq/plugin-sdk";
12
+ import { Inject, Injectable, Optional } from "@nestjs/common";
13
+ import { ChannelConnections } from "../channels/connections.service.js";
14
+ import { secretValues } from "../config/fields.js";
15
+ import { ApiError } from "../errors.js";
16
+ import { EffectiveConfigs } from "../extensions/effective-config.js";
17
+ import { isExtensionEnabled } from "../extensions/enabled.js";
18
+ import { deepFreeze } from "../extensions/hooks.js";
19
+ import { OrgExtensionConfigs } from "../extensions/org-config.js";
20
+ import { PluginGuides } from "../extensions/plugin-guides.js";
21
+ import { ExtensionRegistry } from "../extensions/registry.js";
22
+ import { KernelDb } from "../infra/database/kernel-db.js";
23
+ import { LockService } from "../infra/lock/lock.service.js";
24
+ import { KERNEL_LOGGER, KEYRING, REDIS } from "../infra/tokens.js";
25
+ import { readOrgSettings } from "../orgs/settings.service.js";
26
+ import { Credentials } from "../tools/credentials.js";
27
+ import { checkRedactor, describeCheckError, redactCheckResult } from "./redact-check.js";
28
+ import { SetupCheckRegistry, } from "./setup-check-registry.js";
29
+ /** Optional provider: the per-run time limit (ms) instead of `SETUP_CHECK_LIMITS.timeoutMs` (tests). */
30
+ export const SETUP_CHECK_TIMEOUT = Symbol("SETUP_CHECK_TIMEOUT");
31
+ /** The single-flight lock outlives the run's time limit, so it never expires under a live run. */
32
+ const LOCK_TTL_MS = 15_000;
33
+ /** A slot of the org's concurrency semaphore frees itself after this (a crashed replica's). */
34
+ const SLOT_TTL_MS = 15_000;
35
+ /** Runs per account and window, the HTTP route and the `Platform` tool together (spec §10.3). */
36
+ const RUN_LIMIT = 30;
37
+ const RUN_WINDOW_MS = 60_000;
38
+ /**
39
+ * One atomic step of the per-account run counter: at the limit the attempt is refused and not
40
+ * counted (the window ends on schedule); otherwise the counter is incremented and always carries
41
+ * a TTL. KEYS[1] the counter; ARGV: window ms, limit. Returns 1 when allowed.
42
+ */
43
+ const RUN_RATE_LUA = `local n = tonumber(redis.call('GET', KEYS[1]) or '0')
44
+ if n >= tonumber(ARGV[2]) then
45
+ if redis.call('PTTL', KEYS[1]) < 0 then redis.call('PEXPIRE', KEYS[1], ARGV[1]) end
46
+ return 0
47
+ end
48
+ redis.call('INCR', KEYS[1])
49
+ if redis.call('PTTL', KEYS[1]) < 0 then redis.call('PEXPIRE', KEYS[1], ARGV[1]) end
50
+ return 1`;
51
+ const text = (en, zh) => ({ en, zh });
52
+ /** The kernel's own reasons (results the kernel answers instead of a check; never extension text). */
53
+ const REASONS = {
54
+ unavailable: text("Checks are temporarily unavailable; retry later.", "检查暂时不可用,请稍后重试。"),
55
+ notRunning: (plugin) => text(`The ${plugin} plugin is not running on this server right now; retry later.`, `插件 ${plugin} 此刻没有在这台服务器上运行,请稍后重试。`),
56
+ disabled: (plugin) => text(`The ${plugin} plugin is not enabled in this organization; an org admin enables it under Plugins.`, `插件 ${plugin} 没有在本组织启用;组织管理员可以在「插件」里启用。`),
57
+ needsConfig: (plugin) => text(`The ${plugin} plugin needs configuration in this organization; an org admin configures it under Plugins.`, `插件 ${plugin} 需要在本组织完成配置;组织管理员可以在「插件」里配置。`),
58
+ unregistered: text("This check needs a newer version of the plugin.", "这项检查需要更新版本的插件。"),
59
+ otherPlace: text("This check does not apply on this page.", "这项检查不适用于本页面。"),
60
+ failed: text("The check failed to run; retry later.", "检查运行失败,请稍后重试。"),
61
+ timedOut: text("The check timed out.", "检查超时。"),
62
+ invalid: text("The check returned an invalid result; retry later or update the plugin.", "检查返回的结果无效;请稍后重试或更新插件。"),
63
+ targetGone: text("The connection's stored settings could not be read; save them again.", "无法读取这个连接已保存的设置,请重新保存。"),
64
+ };
65
+ const unknownState = (reason) => ({ status: "unknown", reason });
66
+ /**
67
+ * Whether a check's cache keys and fingerprint carry the caller's account: every check of the
68
+ * member audience, and every kernel check (it gets the caller's account whatever the audience,
69
+ * so one admin's answer is never another admin's).
70
+ */
71
+ const accountScoped = (p, checkId) => p.perAccount || checkId.startsWith("capybara:");
72
+ /** The fixed fields of a state: nothing else leaves the kernel. */
73
+ function stateOf(r, checkedAt, cooldownUntil) {
74
+ return {
75
+ status: r.status,
76
+ ...(r.reason !== undefined && { reason: copyText(r.reason) }),
77
+ ...(r.fix !== undefined && { fix: copyText(r.fix) }),
78
+ ...(checkedAt !== undefined && { checkedAt }),
79
+ ...(cooldownUntil !== undefined && { cooldownUntil }),
80
+ };
81
+ }
82
+ const copyText = (t) => ({
83
+ en: t.en,
84
+ ...(t.zh !== undefined && { zh: t.zh }),
85
+ });
86
+ const isValid = (r) => !("problem" in r);
87
+ /**
88
+ * Setup checks (spec 2026-11-09-setup-guides-v2-design.md §10): reads the cached state of a
89
+ * guide's checks and runs one on request. Authorization is the guide route's (catalog visibility,
90
+ * then the place and audience action, then the target, all through `PluginGuides.render`) before
91
+ * any target is loaded; the check must be in the guide rendered for that mode and audience.
92
+ *
93
+ * Multi-replica (§10.7): results, cooldowns, the single-flight lock and the org's concurrency
94
+ * semaphore live in Redis, every key with a TTL, none touched inside a transaction; nothing is in
95
+ * process memory. A run takes `capk:setup-check-run:<key>` (`LockService.lease`, 15 s, longer than
96
+ * the 10 s time limit) and a slot of `capk:setup-check-org:<orgId>` (8, self-expiring), and frees
97
+ * both on every path. Runs are limited per account (`capk:setup-check-rate:<accountId>`, 30 a
98
+ * minute, 429 `rate_limited`) here, so the HTTP route and the `Platform` tool share one budget.
99
+ * A check runs on the replica that took the lock, in the extension loaded
100
+ * there, raced against `AbortSignal.any([timeout, ctx.signal])` so one that ignores its signal
101
+ * still answers `unknown` at the limit (its late settle is dropped).
102
+ *
103
+ * Results never leak secrets (§10.8): a result is validated, redacted by every secret value the
104
+ * kernel knows for the target and the extension's config (the plugin's own, the connector's
105
+ * stored credentials it never got, the platform and the org's effective deployment config) and
106
+ * validated again; only that output is cached and returned. A throw, a timeout or an invalid
107
+ * result is `unknown` with a kernel reason; only the redacted problem or error message is logged.
108
+ */
109
+ let SetupChecksService = class SetupChecksService {
110
+ db;
111
+ redis;
112
+ locks;
113
+ keyring;
114
+ guides;
115
+ registry;
116
+ extensions;
117
+ effective;
118
+ orgConfigs;
119
+ channels;
120
+ credentials;
121
+ log;
122
+ timeoutMs;
123
+ constructor(db, redis, locks, keyring, guides, registry, extensions, effective, orgConfigs, channels, credentials, log, timeoutMs = SETUP_CHECK_LIMITS.timeoutMs) {
124
+ this.db = db;
125
+ this.redis = redis;
126
+ this.locks = locks;
127
+ this.keyring = keyring;
128
+ this.guides = guides;
129
+ this.registry = registry;
130
+ this.extensions = extensions;
131
+ this.effective = effective;
132
+ this.orgConfigs = orgConfigs;
133
+ this.channels = channels;
134
+ this.credentials = credentials;
135
+ this.log = log;
136
+ this.timeoutMs = timeoutMs;
137
+ }
138
+ /**
139
+ * The cached state of every check of the rendered guide (`GET …/checks`), by check id; nothing
140
+ * runs. `not_run` without a fresh result for the current input fingerprint; `running` while a
141
+ * run holds the lock. Redis down: every row `unknown` ("temporarily unavailable").
142
+ */
143
+ async states(caller, itemId, guideId, q) {
144
+ const p = await this.prepare(caller, itemId, guideId, q);
145
+ const ids = [...new Set(p.checks)];
146
+ const out = {};
147
+ if (ids.length === 0)
148
+ return out;
149
+ // at most two fingerprints: the shared one and the caller's own (account-scoped checks)
150
+ const prints = new Map();
151
+ const fingerprints = [];
152
+ for (const id of ids) {
153
+ const scoped = accountScoped(p, id);
154
+ let fp = prints.get(scoped);
155
+ if (fp === undefined) {
156
+ fp = this.fingerprint(p, await this.revision(p, scoped));
157
+ prints.set(scoped, fp);
158
+ }
159
+ fingerprints.push(fp);
160
+ }
161
+ const keys = ids.map((id) => this.keys(p, id));
162
+ let cached;
163
+ let locked;
164
+ let cooldowns;
165
+ try {
166
+ cached = await this.redis.mget(...keys.map((k) => k.result));
167
+ locked = await Promise.all(keys.map((k) => this.redis.exists(k.lock)));
168
+ cooldowns = await Promise.all(keys.map((k) => this.redis.pttl(k.cooldown)));
169
+ }
170
+ catch (err) {
171
+ this.log.warn("setup checks: Redis unavailable", { err: describeCheckError(err, (s) => s) });
172
+ for (const id of ids)
173
+ out[id] = unknownState(REASONS.unavailable);
174
+ return out;
175
+ }
176
+ ids.forEach((id, i) => {
177
+ const c = parseCached(cached[i] ?? null);
178
+ const cooldown = cooldowns[i] ?? -2;
179
+ const until = cooldown > 0 ? new Date(Date.now() + cooldown).toISOString() : undefined;
180
+ if (locked[i] === 1)
181
+ out[id] = { status: "running" };
182
+ else if (c && c.fingerprint === fingerprints[i]) {
183
+ out[id] = stateOf(c.result, c.checkedAt, until);
184
+ }
185
+ else
186
+ out[id] = { status: "not_run" };
187
+ });
188
+ return out;
189
+ }
190
+ /**
191
+ * Runs one check (`POST …/checks/:checkId/run`) unless a fresh result for the current input
192
+ * fingerprint is cached (or `force`, refused within 10 s of the last run: the cached state with
193
+ * `cooldownUntil`). Another run in progress (any replica) or the org's 8 concurrent runs taken:
194
+ * `running`. Kernel reasons (`unknown`) when the check cannot run here.
195
+ */
196
+ async run(caller, itemId, guideId, checkId, body) {
197
+ const p = await this.prepare(caller, itemId, guideId, body, checkId);
198
+ // the per-account budget, for the HTTP route and the Platform tool alike (an answer from the
199
+ // cache counts too); Redis down: nothing runs
200
+ let allowed;
201
+ try {
202
+ allowed = await this.allowRun(caller.accountId);
203
+ }
204
+ catch (err) {
205
+ this.log.warn("setup checks: Redis unavailable", { err: describeCheckError(err, (s) => s) });
206
+ return unknownState(REASONS.unavailable);
207
+ }
208
+ if (!allowed) {
209
+ throw new ApiError("rate_limited", "Too many setup check runs; try again in a minute");
210
+ }
211
+ // a plugin check: the extension must run here, be on in the org and have a valid effective
212
+ // config before its registration is even looked at (nothing is called otherwise)
213
+ let config;
214
+ if (!checkId.startsWith("capybara:")) {
215
+ const gate = await this.pluginGate(caller.orgId, p.plugin);
216
+ if ("state" in gate)
217
+ return gate.state;
218
+ config = gate.config;
219
+ }
220
+ const check = this.registry.resolve(p.plugin, checkId);
221
+ if (!check)
222
+ return unknownState(REASONS.unregistered);
223
+ if (check.place !== "any" && check.place !== p.place)
224
+ return unknownState(REASONS.otherPlace);
225
+ const keys = this.keys(p, checkId);
226
+ const revision = await this.revision(p, accountScoped(p, checkId));
227
+ const fingerprint = this.fingerprint(p, revision);
228
+ let cached;
229
+ let cooldownMs;
230
+ try {
231
+ cached = parseCached(await this.redis.get(keys.result));
232
+ cooldownMs = body.force ? await this.redis.pttl(keys.cooldown) : -2;
233
+ }
234
+ catch (err) {
235
+ this.log.warn("setup checks: Redis unavailable", { err: describeCheckError(err, (s) => s) });
236
+ return unknownState(REASONS.unavailable);
237
+ }
238
+ const fresh = cached !== null && cached.fingerprint === fingerprint ? cached : null;
239
+ if (fresh && !body.force)
240
+ return stateOf(fresh.result, fresh.checkedAt);
241
+ if (fresh && cooldownMs > 0) {
242
+ return stateOf(fresh.result, fresh.checkedAt, new Date(Date.now() + cooldownMs).toISOString());
243
+ }
244
+ const token = randomUUID();
245
+ const orgSlots = `capk:setup-check-org:${caller.orgId}`;
246
+ let locked = false;
247
+ let slot = false;
248
+ try {
249
+ locked = await this.locks.lease(keys.lock, token, LOCK_TTL_MS);
250
+ if (!locked)
251
+ return { status: "running" };
252
+ // a run that finished between the read above and the lock: its answer, not a second call
253
+ const settled = await this.settledWhileLocking(keys, fingerprint, fresh, body.force === true);
254
+ if (settled)
255
+ return settled;
256
+ slot = await this.locks.takeSlot(orgSlots, token, SETUP_CHECK_LIMITS.orgConcurrency, SLOT_TTL_MS);
257
+ if (!slot)
258
+ return { status: "running" };
259
+ const result = await this.execute(p, check, config);
260
+ const checkedAt = new Date().toISOString();
261
+ const cooldownUntil = new Date(Date.now() + SETUP_CHECK_LIMITS.cooldownMs).toISOString();
262
+ await this.store(keys, { result, fingerprint, checkedAt });
263
+ return stateOf(result, checkedAt, cooldownUntil);
264
+ }
265
+ catch (err) {
266
+ if (err instanceof ApiError)
267
+ throw err;
268
+ // Redis, or reading the target's stored material; never the check itself (execute catches)
269
+ this.log.warn("setup check could not run", { err: describeCheckError(err, (s) => s) });
270
+ return unknownState(REASONS.unavailable);
271
+ }
272
+ finally {
273
+ if (slot)
274
+ await this.locks.releaseSlot(orgSlots, token).catch(() => { });
275
+ if (locked)
276
+ await this.locks.releaseIf(keys.lock, token).catch(() => { });
277
+ }
278
+ }
279
+ /**
280
+ * Authorization and the guide (the guide route's rules, `PluginGuides.render`: catalog
281
+ * visibility, then the place and audience action, then the target), then the check's presence
282
+ * in the guide rendered for that mode and audience (404 `catalog.check_not_found`), then the
283
+ * target (400: checks need a saved connection or connector).
284
+ */
285
+ async prepare(caller, itemId, guideId, q, checkId) {
286
+ const guide = await this.guides.render({ orgId: caller.orgId, role: caller.role }, itemId, guideId, {
287
+ mode: q.mode,
288
+ audience: q.audience,
289
+ connectionId: q.connectionId,
290
+ connectorId: q.connectorId,
291
+ locale: "en",
292
+ });
293
+ const checks = guide.checks.map((c) => c.id);
294
+ if (checkId !== undefined && !checks.includes(checkId)) {
295
+ throw new ApiError("catalog.check_not_found", "The check is not in this setup guide for that mode and audience");
296
+ }
297
+ if (guide.target === null) {
298
+ const path = guide.place === "channel-connection" ? "connectionId" : "connectorId";
299
+ throw new ApiError("validation_failed", "Request validation failed", [
300
+ {
301
+ path,
302
+ message: guide.place === "channel-connection"
303
+ ? "checks need a saved connection"
304
+ : "checks need a connector made from the template",
305
+ },
306
+ ]);
307
+ }
308
+ return {
309
+ caller,
310
+ plugin: guide.plugin,
311
+ itemId: guide.itemId,
312
+ version: guide.version,
313
+ place: guide.place,
314
+ template: guide.template,
315
+ mode: guide.mode,
316
+ audience: guide.audience,
317
+ target: { kind: guide.target.kind, id: guide.target.id },
318
+ checks,
319
+ perAccount: guide.audience === "member",
320
+ };
321
+ }
322
+ /**
323
+ * A plugin check runs only where its extension runs on this replica, is enabled in the org and
324
+ * has a valid effective config there (nothing is called otherwise): the effective config, or the
325
+ * kernel's answer.
326
+ */
327
+ async pluginGate(orgId, plugin) {
328
+ if (!this.extensions.current(plugin))
329
+ return { state: unknownState(REASONS.notRunning(plugin)) };
330
+ if (!(await isExtensionEnabled(this.db.db, orgId, plugin))) {
331
+ return { state: unknownState(REASONS.disabled(plugin)) };
332
+ }
333
+ const eff = await this.effective.get(orgId, plugin);
334
+ if (!eff.ok) {
335
+ return {
336
+ state: unknownState(eff.notRunning ? REASONS.notRunning(plugin) : REASONS.needsConfig(plugin)),
337
+ };
338
+ }
339
+ return { config: eff.config };
340
+ }
341
+ /** The Redis keys of a check of the prepared target (spec §10.7). */
342
+ keys(p, checkId) {
343
+ const base = [
344
+ p.caller.orgId,
345
+ p.target.kind,
346
+ p.target.id,
347
+ p.plugin,
348
+ checkId,
349
+ p.mode ?? "-",
350
+ ...(accountScoped(p, checkId) ? [p.caller.accountId] : []),
351
+ ].join(":");
352
+ return {
353
+ result: `capk:setup-check:${base}`,
354
+ cooldown: `capk:setup-check:${base}:cooldown`,
355
+ lock: `capk:setup-check-run:${base}`,
356
+ };
357
+ }
358
+ /**
359
+ * The input revision of a prepared target (spec §10.4): the target's and its credentials'
360
+ * `updated_at`, the org's extension config and copy revisions; never a value.
361
+ */
362
+ async revision(p, scoped) {
363
+ const target = p.target.kind === "channel-connection"
364
+ ? await this.channelRevision(p)
365
+ : await this.connectorRevision(p, scoped);
366
+ const org = await this.db
367
+ .selectFrom("extension_org_configs")
368
+ .select("updated_at")
369
+ .where("org_id", "=", p.caller.orgId)
370
+ .where("plugin", "=", p.plugin)
371
+ .executeTakeFirst();
372
+ const copy = await this.db
373
+ .selectFrom("extension_org_overrides")
374
+ .select("updated_at")
375
+ .where("org_id", "=", p.caller.orgId)
376
+ .where("plugin", "=", p.plugin)
377
+ .executeTakeFirst();
378
+ const iso = (d) => d === undefined ? "-" : new Date(d).toISOString();
379
+ return [target, iso(org?.updated_at), iso(copy?.updated_at)].join("|");
380
+ }
381
+ channelRevision(p) {
382
+ return this.channels.checkRevision(p.caller.orgId, p.plugin, p.target.id);
383
+ }
384
+ async connectorRevision(p, scoped) {
385
+ const row = await this.db
386
+ .selectFrom("connectors")
387
+ .select("updated_at")
388
+ .where("id", "=", p.target.id)
389
+ .where("org_id", "=", p.caller.orgId)
390
+ .executeTakeFirst();
391
+ // the client and org credential rows, and the caller's own row for account-scoped checks
392
+ const creds = await this.credentials.checkRevision(p.caller.orgId, p.target.id, scoped ? p.caller.accountId : null);
393
+ return `${row ? new Date(row.updated_at).toISOString() : "-"}|${creds}`;
394
+ }
395
+ /**
396
+ * The input fingerprint (spec §10.4): an HMAC with a key derived from the kernel's master key
397
+ * over revisions only (the target and credentials revision, the org config revisions, the
398
+ * plugin version, the loaded extension version and the mode), never a value, so it cannot be
399
+ * reversed to a secret.
400
+ */
401
+ fingerprint(p, revision) {
402
+ const inst = this.extensions.current(p.plugin);
403
+ // the loaded deployment config's digest: a platform config change (PG, env, hot reload) is a
404
+ // new input like the org's copy is
405
+ const loaded = inst === undefined ? ["-", "-"] : [inst.version, inst.configHash];
406
+ return createHmac("sha256", this.keyring.deriveKey("setup-check-fingerprint").current)
407
+ .update(JSON.stringify([revision, p.version, ...loaded, p.mode ?? "-"]))
408
+ .digest("hex");
409
+ }
410
+ /**
411
+ * After the lock is taken: the cache read again. A fresh result written since the first read
412
+ * (another run settled in between) is answered as is, with its cooldown when this was a forced
413
+ * run; null when this run must call the check.
414
+ */
415
+ async settledWhileLocking(keys, fingerprint, seen, force) {
416
+ const again = parseCached(await this.redis.get(keys.result));
417
+ if (again === null || again.fingerprint !== fingerprint)
418
+ return null;
419
+ if (force && seen !== null && again.checkedAt === seen.checkedAt)
420
+ return null;
421
+ if (!force)
422
+ return stateOf(again.result, again.checkedAt);
423
+ const cooldownMs = await this.redis.pttl(keys.cooldown);
424
+ return stateOf(again.result, again.checkedAt, cooldownMs > 0 ? new Date(Date.now() + cooldownMs).toISOString() : undefined);
425
+ }
426
+ /** Counts one run of the account in its window; false at the limit (nothing counted). */
427
+ async allowRun(accountId) {
428
+ const r = await this.redis.eval(RUN_RATE_LUA, 1, `capk:setup-check-rate:${accountId}`, String(RUN_WINDOW_MS), String(RUN_LIMIT));
429
+ return Number(r) === 1;
430
+ }
431
+ /** Writes a result (TTL 5 min) and the force cooldown (10 s), outside any transaction. */
432
+ async store(keys, value) {
433
+ await this.redis.set(keys.result, JSON.stringify(value), "PX", SETUP_CHECK_LIMITS.freshMs);
434
+ await this.redis.set(keys.cooldown, "1", "PX", SETUP_CHECK_LIMITS.cooldownMs);
435
+ }
436
+ /**
437
+ * Loads the target (built field by field), runs the check against the time limit and turns what
438
+ * it answers into a validated, redacted, re-validated result. Never throws for anything the
439
+ * check does: a throw, a timeout or an invalid result is an `unknown` result.
440
+ */
441
+ async execute(p, check, config) {
442
+ const material = await this.material(p, check);
443
+ if ("result" in material)
444
+ return material.result;
445
+ const secrets = [
446
+ ...material.secrets,
447
+ ...this.configSecrets(p.plugin, config),
448
+ ...(await this.orgConfigSecrets(p.caller.orgId, p.plugin)),
449
+ ];
450
+ const redact = checkRedactor(secrets);
451
+ const fields = { plugin: p.plugin, checkId: check.id, orgId: p.caller.orgId };
452
+ const timeout = AbortSignal.timeout(this.timeoutMs);
453
+ let answer;
454
+ if (check.source === "plugin") {
455
+ const signal = AbortSignal.any([timeout, check.signal]);
456
+ // the org's language: a result is cached for every viewer of the org
457
+ const { locale } = await readOrgSettings(this.db.db, p.caller.orgId);
458
+ answer = await raceSignal(signal,
459
+ // the in-flight tracker merges the instance's abort into the signal the check gets
460
+ () => check.inflight.run(signal, (s) => check.run(this.pluginArgs(p, material, config, locale, s))), () => (check.signal.aborted ? "unloaded" : "timeout"));
461
+ }
462
+ else {
463
+ const signal = timeout;
464
+ const args = Object.freeze({
465
+ orgId: p.caller.orgId,
466
+ accountId: p.caller.accountId,
467
+ mode: p.mode,
468
+ target: kernelTarget(p, material),
469
+ signal,
470
+ // every value the result is redacted by: a check that cuts a text (connection-active's
471
+ // last_error) redacts first, so no partial secret survives the later whole-value redaction
472
+ secrets: Object.freeze([...secrets]),
473
+ });
474
+ answer = await raceSignal(signal, () => check.run(args), () => "timeout");
475
+ }
476
+ if (answer === "timeout") {
477
+ this.log.warn("setup check timed out", { ...fields, timeoutMs: this.timeoutMs });
478
+ return { status: "unknown", reason: REASONS.timedOut };
479
+ }
480
+ if (answer === "unloaded")
481
+ return { status: "unknown", reason: REASONS.notRunning(p.plugin) };
482
+ if ("error" in answer) {
483
+ this.log.warn("setup check failed", {
484
+ ...fields,
485
+ error: describeCheckError(answer.error, redact),
486
+ });
487
+ return { status: "unknown", reason: REASONS.failed };
488
+ }
489
+ const first = validateSetupCheckResult(answer.value);
490
+ if (!isValid(first)) {
491
+ this.log.warn("setup check result invalid", { ...fields, problem: redact(first.problem) });
492
+ return { status: "unknown", reason: REASONS.invalid };
493
+ }
494
+ const second = validateSetupCheckResult(redactCheckResult(first, redact));
495
+ if (!isValid(second)) {
496
+ this.log.warn("setup check result invalid after redaction", {
497
+ ...fields,
498
+ problem: redact(second.problem),
499
+ });
500
+ return { status: "unknown", reason: REASONS.invalid };
501
+ }
502
+ return second;
503
+ }
504
+ /**
505
+ * The target material: for a channel connection its revision and stored secret values (and, for
506
+ * a plugin check, the connection config decrypted exactly as `channel@1` verify gets it); for a
507
+ * connector its credentials' values (redaction only), the manual OAuth client id and the
508
+ * template id. A `result` when the material cannot be read (the check is not called).
509
+ */
510
+ async material(p, check) {
511
+ if (p.target.kind === "channel-connection") {
512
+ const stored = await this.channels.checkSecrets(p.caller.orgId, p.plugin, p.target.id);
513
+ if (check.source === "kernel")
514
+ return { secrets: stored };
515
+ let rt;
516
+ try {
517
+ rt = await this.channels.runtime(p.target.id);
518
+ }
519
+ catch (err) {
520
+ if (err instanceof ApiError && err.code === "extension.config_invalid") {
521
+ return { result: { status: "unknown", reason: REASONS.targetGone } };
522
+ }
523
+ if (err instanceof ApiError && err.code === "extension.not_enabled") {
524
+ return { result: { status: "unknown", reason: REASONS.disabled(p.plugin) } };
525
+ }
526
+ if (err instanceof ApiError && err.code === "extension.not_found") {
527
+ return { result: { status: "unknown", reason: REASONS.notRunning(p.plugin) } };
528
+ }
529
+ throw err;
530
+ }
531
+ // the connection of this org on the check's plugin only (render checked it; checked again)
532
+ if (rt.row.orgId !== p.caller.orgId || rt.row.plugin !== p.plugin) {
533
+ throw new ApiError("channel.not_found", "Channel connection not found");
534
+ }
535
+ return {
536
+ secrets: [...stored, ...secretValues(rt.channel.connectionSchema, rt.config)],
537
+ // a frozen copy: what the check gets cannot change the kernel's object or another call's
538
+ connectionConfig: deepFreeze(structuredClone(rt.config)),
539
+ };
540
+ }
541
+ // a live connector of the org made from the guide's own template, a live platform component
542
+ if (p.template === null)
543
+ throw new ApiError("connector.not_found", "Connector not found");
544
+ const row = await this.db
545
+ .selectFrom("connectors as c")
546
+ .innerJoin("platform_connector_templates as t", "t.id", "c.platform_template_id")
547
+ .select(["c.auth", "t.template_id"])
548
+ .where("c.id", "=", p.target.id)
549
+ .where("c.org_id", "=", p.caller.orgId)
550
+ .where("c.deleted_at", "is", null)
551
+ .where("t.catalog_item_id", "=", p.itemId)
552
+ .where("t.template_id", "=", p.template)
553
+ .where("t.deleted_at", "is", null)
554
+ .executeTakeFirst();
555
+ if (!row)
556
+ throw new ApiError("connector.not_found", "Connector not found");
557
+ const auth = row.auth;
558
+ const m = await this.credentials.checkMaterial(p.caller.orgId, p.target.id);
559
+ const manual = auth.type === "oauth_code" && auth.client === "manual";
560
+ return {
561
+ secrets: m.secrets,
562
+ clientId: manual ? m.clientId : null,
563
+ templateId: row.template_id,
564
+ };
565
+ }
566
+ /** The secret values of the extension's platform and effective deployment config. */
567
+ configSecrets(plugin, effective) {
568
+ const inst = this.extensions.current(plugin);
569
+ const schema = inst?.manifest.config?.deployment;
570
+ if (!inst || !schema)
571
+ return [];
572
+ return [
573
+ ...secretValues(schema, inst.config),
574
+ ...(effective === undefined ? [] : secretValues(schema, effective)),
575
+ ];
576
+ }
577
+ /**
578
+ * The secret values of the extension's org-level config (`config.org`) in the org: the check
579
+ * never gets them, but the extension holds them, so a result is redacted by them too. Nothing
580
+ * when the plugin has no org config; a read that fails throws (the check is not run: a result
581
+ * is never redacted by fewer secrets than the extension holds).
582
+ */
583
+ async orgConfigSecrets(orgId, plugin) {
584
+ const schema = this.extensions.current(plugin)?.manifest.config?.org;
585
+ if (!schema)
586
+ return [];
587
+ const c = await this.orgConfigs.get(orgId, plugin);
588
+ return c?.config == null ? [] : secretValues(schema, c.config);
589
+ }
590
+ /** The frozen arguments of a plugin check (the target built field by field). */
591
+ pluginArgs(p, m, config, locale, signal) {
592
+ const target = p.target.kind === "channel-connection"
593
+ ? Object.freeze({
594
+ kind: "channel-connection",
595
+ connectionId: p.target.id,
596
+ config: m.connectionConfig,
597
+ })
598
+ : Object.freeze({
599
+ kind: "connector",
600
+ connectorId: p.target.id,
601
+ templateId: m.templateId ?? "",
602
+ clientId: m.clientId ?? null,
603
+ accountId: p.perAccount ? p.caller.accountId : null,
604
+ });
605
+ return Object.freeze({
606
+ orgId: p.caller.orgId,
607
+ config,
608
+ locale,
609
+ mode: p.mode,
610
+ target,
611
+ signal,
612
+ });
613
+ }
614
+ };
615
+ SetupChecksService = __decorate([
616
+ Injectable(),
617
+ __param(0, Inject(KernelDb)),
618
+ __param(1, Inject(REDIS)),
619
+ __param(2, Inject(LockService)),
620
+ __param(3, Inject(KEYRING)),
621
+ __param(4, Inject(PluginGuides)),
622
+ __param(5, Inject(SetupCheckRegistry)),
623
+ __param(6, Inject(ExtensionRegistry)),
624
+ __param(7, Inject(EffectiveConfigs)),
625
+ __param(8, Inject(OrgExtensionConfigs)),
626
+ __param(9, Inject(ChannelConnections)),
627
+ __param(10, Inject(Credentials)),
628
+ __param(11, Inject(KERNEL_LOGGER)),
629
+ __param(12, Optional()),
630
+ __param(12, Inject(SETUP_CHECK_TIMEOUT))
631
+ ], SetupChecksService);
632
+ export { SetupChecksService };
633
+ /** The kernel check's target: ids and the plugin only. */
634
+ function kernelTarget(p, m) {
635
+ return p.target.kind === "channel-connection"
636
+ ? Object.freeze({ kind: "channel-connection", connectionId: p.target.id, plugin: p.plugin })
637
+ : Object.freeze({
638
+ kind: "connector",
639
+ connectorId: p.target.id,
640
+ templateId: m.templateId ?? "",
641
+ plugin: p.plugin,
642
+ });
643
+ }
644
+ /**
645
+ * Runs `fn` raced against `signal`: its value or error, or `onAbort()` when the signal aborts
646
+ * first (a late settle of `fn` is swallowed). `fn` throwing synchronously counts as an error.
647
+ */
648
+ async function raceSignal(signal, fn, onAbort) {
649
+ let settle;
650
+ const aborted = new Promise((resolve) => {
651
+ if (signal.aborted) {
652
+ resolve(onAbort());
653
+ return;
654
+ }
655
+ settle = () => resolve(onAbort());
656
+ signal.addEventListener("abort", settle, { once: true });
657
+ });
658
+ const call = Promise.resolve()
659
+ .then(fn)
660
+ .then((value) => ({ value }), (error) => ({ error }));
661
+ try {
662
+ return await Promise.race([call, aborted]);
663
+ }
664
+ finally {
665
+ if (settle)
666
+ signal.removeEventListener("abort", settle);
667
+ }
668
+ }
669
+ /** A cached value, or null when absent or not of the stored shape (re-validated). */
670
+ function parseCached(raw) {
671
+ if (raw === null)
672
+ return null;
673
+ try {
674
+ const v = JSON.parse(raw);
675
+ if (typeof v.fingerprint !== "string" || typeof v.checkedAt !== "string")
676
+ return null;
677
+ const r = validateSetupCheckResult(v.result);
678
+ return isValid(r) ? { result: r, fingerprint: v.fingerprint, checkedAt: v.checkedAt } : null;
679
+ }
680
+ catch {
681
+ return null;
682
+ }
683
+ }