@k2b/cloud 0.40.0 → 0.42.0

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 (279) hide show
  1. package/package.json +9 -5
  2. package/src/_internal/bounded-json.ts +14 -2
  3. package/src/_internal/capabilities.ts +87 -8
  4. package/src/_internal/capability-reviews.ts +6 -0
  5. package/src/_internal/capability-sentences.ts +2 -1
  6. package/src/_internal/capability-timeout.ts +71 -0
  7. package/src/_internal/define-app.ts +158 -92
  8. package/src/_internal/heartbeat.ts +203 -14
  9. package/src/_internal/http-drain.ts +56 -0
  10. package/src/_internal/registry-validation.ts +3 -0
  11. package/src/_internal/registry.ts +28 -1
  12. package/src/_internal/runtime-context.ts +1 -0
  13. package/src/_internal/setup-lock.ts +103 -0
  14. package/src/_internal/status-preserving-ssr.ts +2 -0
  15. package/src/access/PermissionEditor.tsx +16 -17
  16. package/src/access/PrincipalPicker.tsx +45 -10
  17. package/src/access/messages.ts +8 -0
  18. package/src/access/principal-label.ts +18 -0
  19. package/src/access/ui.ts +1 -0
  20. package/src/account/EntitySearch.tsx +5 -4
  21. package/src/ai/action-review.ts +2 -1
  22. package/src/ai/app-skill-store.ts +1 -1
  23. package/src/ai/capabilities.ts +21 -3
  24. package/src/ai/capability-execution.ts +14 -12
  25. package/src/ai/chat/action-card.ts +76 -4
  26. package/src/ai/chat/action-messages.ts +14 -0
  27. package/src/ai/chat/action-question.browser-harness.tsx +12 -0
  28. package/src/ai/chat/action-question.tsx +214 -23
  29. package/src/ai/chat/action-text.ts +288 -0
  30. package/src/ai/chat/tool-action-words.ts +31 -0
  31. package/src/ai/chat/tool-action.tsx +1 -26
  32. package/src/ai/chat-tasks.ts +2 -1
  33. package/src/ai/client/controller.ts +150 -32
  34. package/src/ai/client/transport.ts +14 -2
  35. package/src/ai/code-capability-routes.ts +27 -23
  36. package/src/ai/code-runtime-tools.ts +14 -16
  37. package/src/ai/code-source-tools.ts +16 -18
  38. package/src/ai/executor.ts +64 -21
  39. package/src/ai/fetch-file-tool.ts +67 -76
  40. package/src/ai/firecrawl-tools.ts +15 -2
  41. package/src/ai/http.ts +1 -0
  42. package/src/ai/index.ts +1 -0
  43. package/src/ai/migrate.ts +28 -0
  44. package/src/ai/open-questions.ts +168 -0
  45. package/src/ai/projects.ts +1 -2
  46. package/src/ai/protocol.ts +2 -2
  47. package/src/ai/question-text.ts +8 -0
  48. package/src/ai/recent-resources.ts +12 -5
  49. package/src/ai/review-content.ts +77 -0
  50. package/src/ai/routes.ts +119 -35
  51. package/src/ai/runtime.ts +1 -0
  52. package/src/ai/skill-tool.ts +17 -6
  53. package/src/ai/skills-routes.ts +72 -27
  54. package/src/ai/skills.ts +139 -60
  55. package/src/ai/store.ts +184 -31
  56. package/src/ai/testing.ts +36 -0
  57. package/src/ai/tool-sentences.ts +152 -34
  58. package/src/ai/tool-wording.ts +12 -6
  59. package/src/ai/tools.ts +8 -3
  60. package/src/ai/types.ts +25 -4
  61. package/src/ai/usage.ts +2 -2
  62. package/src/api/admin-core-settings.ts +15 -2
  63. package/src/api/admin-geo.ts +54 -0
  64. package/src/api/admin-linux-identities.ts +6 -3
  65. package/src/api/admin-outgoing-mail.ts +29 -15
  66. package/src/api/app-approval-actions.ts +31 -7
  67. package/src/api/approvals-loader.ts +3 -3
  68. package/src/api/approvals.ts +51 -0
  69. package/src/api/apps.ts +4 -2
  70. package/src/api/auth/schemas.ts +3 -0
  71. package/src/api/auth.ts +54 -18
  72. package/src/api/capabilities.ts +258 -181
  73. package/src/api/capability-streams.ts +21 -13
  74. package/src/api/cli-plugins.ts +4 -1
  75. package/src/api/geo-glyphs.ts +11 -0
  76. package/src/api/geo.ts +128 -0
  77. package/src/api/help.ts +12 -4
  78. package/src/api/index.ts +5 -0
  79. package/src/api/mcp.ts +73 -41
  80. package/src/api/me-panel-consent.ts +8 -5
  81. package/src/api/me.ts +79 -3
  82. package/src/api/search.ts +21 -14
  83. package/src/api/sync-ops.ts +19 -13
  84. package/src/api/widgets.ts +42 -18
  85. package/src/approvals/Approvals.tsx +30 -66
  86. package/src/approvals/approval-card.ts +68 -0
  87. package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
  88. package/src/browser/CloudResourceSearch.tsx +3 -13
  89. package/src/browser/PanelLeaveNotice.tsx +18 -0
  90. package/src/browser/navigation-search.ts +78 -25
  91. package/src/browser/panel-leave.ts +69 -0
  92. package/src/browser/panel-messages.ts +12 -0
  93. package/src/browser/panel-slot.ts +173 -24
  94. package/src/browser/panel.ts +3 -0
  95. package/src/browser/resource-search-messages.ts +2 -0
  96. package/src/capabilities/client.ts +28 -1
  97. package/src/capabilities/executions.ts +22 -3
  98. package/src/capabilities/geo-client.ts +57 -0
  99. package/src/capabilities/response.ts +2 -1
  100. package/src/capabilities/review-storage.ts +24 -0
  101. package/src/capabilities/reviews.ts +17 -3
  102. package/src/capabilities/server.ts +36 -16
  103. package/src/cli/access.ts +15 -23
  104. package/src/cli/admin/linux.ts +13 -2
  105. package/src/cli/admin/outgoing-mail.ts +26 -4
  106. package/src/cli/capabilities.ts +19 -0
  107. package/src/config/env.ts +0 -6
  108. package/src/contracts/app-access.ts +48 -0
  109. package/src/contracts/app-approval.ts +3 -3
  110. package/src/contracts/app.ts +4 -1
  111. package/src/contracts/approvals.ts +2 -2
  112. package/src/contracts/avatar.ts +186 -0
  113. package/src/contracts/capabilities.ts +149 -55
  114. package/src/contracts/geo-admin.ts +127 -0
  115. package/src/contracts/geo-map.ts +19 -0
  116. package/src/contracts/geo-route.ts +168 -0
  117. package/src/contracts/geo.ts +248 -0
  118. package/src/contracts/index.ts +4 -0
  119. package/src/contracts/outgoing-mail.ts +17 -4
  120. package/src/contracts/registry.ts +2 -0
  121. package/src/contracts/shared.ts +3 -0
  122. package/src/events/app-access.ts +68 -0
  123. package/src/events/live-delivery.ts +128 -0
  124. package/src/events/live.ts +14 -130
  125. package/src/geo/CloudMap.tsx +95 -0
  126. package/src/geo/map-messages.ts +17 -0
  127. package/src/geo/ui.ts +1 -0
  128. package/src/index.ts +2 -0
  129. package/src/server/client-ip.ts +36 -0
  130. package/src/server/index.ts +3 -4
  131. package/src/server/middleware/app-access.ts +312 -0
  132. package/src/server/middleware/auth.ts +5 -3
  133. package/src/server/middleware/invocation.ts +11 -0
  134. package/src/server/middleware/rate-limit.ts +4 -4
  135. package/src/server/services/access.ts +13 -9
  136. package/src/server/services/index.ts +0 -2
  137. package/src/server/services/services.ts +0 -2
  138. package/src/services/accounts/app.ts +23 -3
  139. package/src/services/accounts/posix.ts +21 -7
  140. package/src/services/accounts/users.ts +12 -1
  141. package/src/services/allowed-network-fetch.ts +16 -10
  142. package/src/services/app-access-metrics.ts +24 -0
  143. package/src/services/app-access.ts +123 -0
  144. package/src/services/app-discovery.ts +27 -0
  145. package/src/services/approvals/content.ts +40 -0
  146. package/src/services/approvals/device.ts +49 -33
  147. package/src/services/approvals/index.ts +15 -6
  148. package/src/services/approvals/list.ts +6 -11
  149. package/src/services/approvals/question.ts +72 -0
  150. package/src/services/audit/index.ts +1 -0
  151. package/src/services/auth-flows/ipa.ts +39 -2
  152. package/src/services/auth-flows/password-reset.ts +32 -6
  153. package/src/services/downstream-budget.ts +87 -0
  154. package/src/services/geo/admin.ts +262 -0
  155. package/src/services/geo/bucket.ts +67 -0
  156. package/src/services/geo/cache.ts +57 -0
  157. package/src/services/geo/costs.ts +5 -0
  158. package/src/services/geo/errors.ts +41 -0
  159. package/src/services/geo/fixtures/README.md +17 -0
  160. package/src/services/geo/fixtures/along.json +355 -0
  161. package/src/services/geo/fixtures/autocomplete.json +218 -0
  162. package/src/services/geo/fixtures/geocode.json +129 -0
  163. package/src/services/geo/fixtures/places.json +355 -0
  164. package/src/services/geo/fixtures/reverse.json +61 -0
  165. package/src/services/geo/fixtures/routing.json +57 -0
  166. package/src/services/geo/geoapify-route.ts +93 -0
  167. package/src/services/geo/geoapify.ts +281 -0
  168. package/src/services/geo/ledger.ts +122 -0
  169. package/src/services/geo/map.ts +160 -0
  170. package/src/services/geo/migrate.ts +25 -0
  171. package/src/services/geo/polyline.ts +184 -0
  172. package/src/services/geo/route-chain.ts +145 -0
  173. package/src/services/geo/route-costs.ts +9 -0
  174. package/src/services/geo/route-geometry.ts +39 -0
  175. package/src/services/geo/route-plan.ts +172 -0
  176. package/src/services/geo/route-service.ts +433 -0
  177. package/src/services/geo/service.ts +300 -0
  178. package/src/services/geo/settings.ts +49 -0
  179. package/src/services/help/index.ts +16 -7
  180. package/src/services/help/types.ts +6 -1
  181. package/src/services/identity/invocation-actor.ts +20 -2
  182. package/src/services/identity/issue-invocation.ts +67 -0
  183. package/src/services/identity/key-ring.ts +5 -3
  184. package/src/services/identity/runtime-config.ts +23 -0
  185. package/src/services/index.ts +11 -0
  186. package/src/services/mandates/index.ts +40 -3
  187. package/src/services/network-security.ts +6 -3
  188. package/src/services/network-transport.ts +99 -0
  189. package/src/services/notifications/catalog.ts +3 -2
  190. package/src/services/notifications/runtime.ts +1 -0
  191. package/src/services/notifications/web-push-transport.ts +45 -38
  192. package/src/services/outgoing-mail/dispatcher.ts +6 -1
  193. package/src/services/outgoing-mail/index.ts +55 -1
  194. package/src/services/outgoing-mail/messages.ts +82 -3
  195. package/src/services/outgoing-mail/resolve-host.ts +2 -2
  196. package/src/services/outgoing-mail/retention.ts +13 -1
  197. package/src/services/outgoing-mail/store.ts +36 -6
  198. package/src/services/outgoing-mail/test-send.ts +34 -4
  199. package/src/services/outgoing-mail/transport.ts +1 -1
  200. package/src/services/pdf/gotenberg.ts +11 -38
  201. package/src/services/pdf/markdown.ts +160 -2
  202. package/src/services/pdf/template-preview.ts +1 -1
  203. package/src/services/public-http.ts +28 -40
  204. package/src/services/session/index.ts +203 -29
  205. package/src/services/settings/app.ts +4 -1
  206. package/src/services/settings/core-settings.ts +88 -0
  207. package/src/services/settings/defaults.ts +8 -2
  208. package/src/services/settings/errors.ts +11 -0
  209. package/src/services/settings/geo-budget.ts +40 -0
  210. package/src/services/settings/index.ts +4 -3
  211. package/src/services/settings/store.ts +25 -17
  212. package/src/services/weather/geo.ts +29 -111
  213. package/src/services/weather/index.ts +4 -3
  214. package/src/services/weather/locations.ts +8 -5
  215. package/src/services/weather/migrate.ts +1 -0
  216. package/src/services/weather/settings.ts +0 -16
  217. package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
  218. package/src/shared/avatar-art.ts +400 -0
  219. package/src/shared/index.ts +2 -0
  220. package/src/shared/network-address.ts +22 -3
  221. package/src/shared/template-rendering.ts +36 -4
  222. package/src/shared-text/documents.ts +1 -1
  223. package/src/shared-text/index.ts +2 -0
  224. package/src/shared-text/log.ts +51 -3
  225. package/src/shared-text/snapshots.ts +340 -0
  226. package/src/ssr/AdminLayout.tsx +4 -2
  227. package/src/ssr/CloudShell.island.tsx +6 -0
  228. package/src/ssr/HelpPanelArticle.island.tsx +308 -0
  229. package/src/ssr/HelpPanelBack.island.tsx +32 -0
  230. package/src/ssr/HelpPanelMenu.island.tsx +133 -0
  231. package/src/ssr/HelpPanelSearch.island.tsx +123 -0
  232. package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
  233. package/src/ssr/HelpPanelView.island.tsx +27 -0
  234. package/src/ssr/Layout.tsx +65 -15
  235. package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
  236. package/src/ssr/LayoutHelp.tsx +185 -542
  237. package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
  238. package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
  239. package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
  240. package/src/ssr/PanelRuntime.island.tsx +11 -7
  241. package/src/ssr/PwaLayout.tsx +2 -2
  242. package/src/ssr/RailApps.tsx +6 -0
  243. package/src/ssr/ShellRuntime.island.tsx +23 -7
  244. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
  245. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
  246. package/src/ssr/admin-navigation.ts +1 -0
  247. package/src/ssr/app-badges.ts +6 -1
  248. package/src/ssr/compact-shell.browser-harness.ts +44 -5
  249. package/src/ssr/help.ts +7 -5
  250. package/src/ssr/index.ts +2 -1
  251. package/src/ssr/islands/index.ts +3 -4
  252. package/src/ssr/layout-context.ts +2 -0
  253. package/src/ssr/layout-help-panel-controls.tsx +43 -0
  254. package/src/ssr/layout-help-panel-document.ts +67 -0
  255. package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
  256. package/src/ssr/layout-help-panel-menu.tsx +113 -0
  257. package/src/ssr/layout-help-panel-modules.ts +7 -0
  258. package/src/ssr/layout-help-panel-reader.tsx +159 -0
  259. package/src/ssr/layout-help-panel-search.tsx +69 -0
  260. package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
  261. package/src/ssr/layout-help-panel-state.ts +141 -0
  262. package/src/ssr/layout-help-panel-types.ts +19 -0
  263. package/src/ssr/layout-help-views.tsx +484 -0
  264. package/src/ssr/mobile-menu-history.ts +4 -0
  265. package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
  266. package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
  267. package/src/ssr/page-swap.browser-harness.tsx +177 -0
  268. package/src/ssr/page-swap.ts +28 -0
  269. package/src/ssr/page-trail-runtime.ts +32 -13
  270. package/src/ssr/panel-consent.ts +2 -2
  271. package/src/ssr/panel-idle.ts +9 -0
  272. package/src/ssr/panel-slot.browser-harness.tsx +3 -0
  273. package/src/ssr/platform-messages.ts +8 -0
  274. package/src/ssr/runtime.ts +17 -0
  275. package/src/ssr/shell-data.tsx +10 -2
  276. package/src/styles/approvals.css +6 -0
  277. package/src/styles/effects.css +15 -0
  278. package/src/styles/global.css +70 -6
  279. package/src/server/services/geo.ts +0 -215
@@ -8,6 +8,7 @@ import {
8
8
  capabilityActionReviewIssues,
9
9
  capabilityQueryChecksActionOutcome,
10
10
  } from "../_internal/capability-reviews";
11
+ import { capabilityTimeoutMs, createCapabilityDeadline, withCapabilityRequestTimeout } from "../_internal/capability-timeout";
11
12
  import { getCapability, listApps } from "../_internal/registry";
12
13
  import { resolveCommandLink } from "../capabilities/command-link";
13
14
  import {
@@ -28,6 +29,7 @@ import {
28
29
  CAPABILITY_MAX_CATALOG_BYTES,
29
30
  CAPABILITY_MAX_REQUEST_BYTES,
30
31
  CAPABILITY_MAX_RESULT_BYTES,
32
+ CAPABILITY_MAX_REVIEW_RESPONSE_BYTES,
31
33
  CAPABILITY_ORIGIN_HEADER,
32
34
  CAPABILITY_PROTOCOL_VERSION,
33
35
  CapabilityActionReviewDataSchema,
@@ -42,33 +44,34 @@ import {
42
44
  import { CapabilityStreamSchema } from "../contracts/capability-streams";
43
45
  import { CommandLinkSchema, CommandRequestSchema } from "../contracts/commands";
44
46
  import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/registry";
47
+ import { getLocale, LOCALE_HEADER, preferredLocale } from "../server/locale";
45
48
  import {
46
49
  type AuthContext,
47
50
  auth,
48
- getLocale,
49
51
  jsonResponse,
50
- LOCALE_HEADER,
51
- preferredLocale,
52
52
  type RequestAuthority,
53
53
  rejectReservedWorkloadCredential,
54
54
  requiresAuth,
55
55
  v,
56
- } from "../server";
56
+ } from "../server/middleware";
57
+ import { AppAccessDeniedError, appAccessDeniedBody } from "../server/middleware/app-access";
57
58
  import { noTransform } from "../server/middleware/no-transform";
58
- import { logger, settings } from "../services";
59
+ import { appAccessAuthority, canUseApp } from "../services/app-access";
60
+ import { discoverableApps } from "../services/app-discovery";
59
61
  import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
60
62
  import { capabilityInvocationOperation } from "../services/identity/invocation-operations";
61
63
  import type { InvocationAuthority } from "../services/identity/invocation-token";
62
- import { normalizeInvocationRequestId, signInvocationToken } from "../services/identity/invocation-token";
64
+ import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
65
+ import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
63
66
  import { withActiveIdentitySigner } from "../services/identity/key-ring";
67
+ import { logger } from "../services/logging";
64
68
  import { withMandateIssueAuthority } from "../services/mandates";
69
+ import * as settings from "../services/settings";
65
70
  import { resolveAppPresentation } from "../shared/app-presentation";
66
71
  import { capabilityMessages } from "../shared/capability-messages";
67
72
  import { dispatchCapabilityStream, sealCapabilityStream } from "./capability-streams";
68
73
 
69
74
  const log = logger("capabilities");
70
- export const QUERY_TIMEOUT_MS = 15_000;
71
- const ACTION_TIMEOUT_MS = 30_000;
72
75
  const DEFAULT_CATALOG_LIMIT = 10;
73
76
  const MAX_CATALOG_LIMIT = 25;
74
77
  const MAX_SCHEMA_VALIDATORS = 512;
@@ -109,12 +112,20 @@ export type CapabilityRouteDependencies = {
109
112
  withMandateIssueAuthority?: typeof withMandateIssueAuthority;
110
113
  queryTimeoutMs?: number;
111
114
  actionTimeoutMs?: number;
115
+ reviewTimeoutMs?: number;
112
116
  appUrl?: () => Promise<string>;
113
117
  };
114
118
 
115
119
  export type CapabilityDispatchDependencies = Pick<
116
120
  CapabilityRouteDependencies,
117
- "getCapability" | "fetch" | "queryTimeoutMs" | "actionTimeoutMs" | "signInvocation" | "withActiveSigner" | "withMandateIssueAuthority"
121
+ | "getCapability"
122
+ | "fetch"
123
+ | "queryTimeoutMs"
124
+ | "actionTimeoutMs"
125
+ | "reviewTimeoutMs"
126
+ | "signInvocation"
127
+ | "withActiveSigner"
128
+ | "withMandateIssueAuthority"
118
129
  >;
119
130
 
120
131
  type MandatedCapabilityAuthority = {
@@ -148,13 +159,14 @@ export const loadCapabilityCatalogPage = async (
148
159
  query: { cursor?: string; limit: number },
149
160
  dependencies: Pick<CapabilityRouteDependencies, "listApps" | "getCapability"> = {},
150
161
  locale?: string,
162
+ authority: RequestAuthority | null = null,
151
163
  ): Promise<CapabilityCatalog> => {
152
164
  if (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > MAX_CATALOG_LIMIT) {
153
165
  throw new RangeError(`Capability catalog limit must be between 1 and ${MAX_CATALOG_LIMIT}`);
154
166
  }
155
167
  const registry = dependencies.listApps ?? listApps;
156
168
  const capabilityLookup = dependencies.getCapability ?? getCapability;
157
- const entries = await registry();
169
+ const entries = await discoverableApps(authority, await registry());
158
170
  const liveApps = entries
159
171
  .filter((entry) => entry.capabilities?.protocolVersion === CAPABILITY_PROTOCOL_VERSION)
160
172
  .sort((left, right) => left.id.localeCompare(right.id));
@@ -213,23 +225,6 @@ const capabilityJsonResponse = (body: unknown, status: number): Response =>
213
225
  headers: { "content-type": "application/json" },
214
226
  });
215
227
 
216
- const waitWithin = <T>(value: Promise<T>, signal: AbortSignal): Promise<T> =>
217
- new Promise<T>((resolve, reject) => {
218
- const aborted = () => reject(signal.reason);
219
- if (signal.aborted) return aborted();
220
- signal.addEventListener("abort", aborted, { once: true });
221
- value.then(
222
- (result) => {
223
- signal.removeEventListener("abort", aborted);
224
- resolve(result);
225
- },
226
- (error) => {
227
- signal.removeEventListener("abort", aborted);
228
- reject(error);
229
- },
230
- );
231
- });
232
-
233
228
  export type CapabilityDispatchParams = {
234
229
  /** Trusted Core continuation binding; never read from client headers. */
235
230
  continuation?: string;
@@ -272,14 +267,17 @@ const runCapabilityDispatch = async (
272
267
  params: CapabilityDispatchParams,
273
268
  requestId: string,
274
269
  observed: CapabilityDispatchObservation,
270
+ deadline: ReturnType<typeof createCapabilityDeadline>,
275
271
  ): Promise<CapabilityDispatchOutcome> => {
272
+ const { signal } = deadline;
276
273
  const messages = capabilityMessages(params.locale ?? preferredLocale(params.request.headers));
277
274
  const registryEntry = params.dependencies?.getCapability ?? getCapability;
278
275
  const fetchUpstream = params.dependencies?.fetch ?? globalThis.fetch;
279
276
  let entry: CapabilityRegistryEntry | null;
280
277
  try {
281
- entry = await registryEntry(params.appId);
278
+ entry = await deadline.wait(() => registryEntry(params.appId));
282
279
  } catch (error) {
280
+ deadline.check();
283
281
  log.warn("Capability registry unavailable", {
284
282
  appId: params.appId,
285
283
  error: error instanceof Error ? error.message : String(error),
@@ -366,21 +364,16 @@ const runCapabilityDispatch = async (
366
364
 
367
365
  const invocationOperation = capabilityInvocationOperation(params.kind, params.capabilityId, params.review);
368
366
  const mandate = params.mandate;
369
- const timeoutMs =
370
- params.kind === "queries" || params.review
371
- ? (params.dependencies?.queryTimeoutMs ?? QUERY_TIMEOUT_MS)
372
- : (params.dependencies?.actionTimeoutMs ?? ACTION_TIMEOUT_MS);
373
- const timeout = AbortSignal.timeout(timeoutMs);
374
- const signal = AbortSignal.any([params.request.signal, timeout]);
375
367
  let headers: Headers;
376
368
  try {
377
369
  headers = mandate
378
370
  ? await (async () => {
379
- const issued = await waitWithin(
371
+ const issued = await deadline.wait(() =>
380
372
  (params.dependencies?.withActiveSigner ?? withActiveIdentitySigner)(
381
373
  "invocation",
382
- (signer, db) =>
383
- (params.dependencies?.withMandateIssueAuthority ?? withMandateIssueAuthority)(
374
+ (signer, db) => {
375
+ deadline.check();
376
+ return (params.dependencies?.withMandateIssueAuthority ?? withMandateIssueAuthority)(
384
377
  {
385
378
  mandateId: mandate.mandateId,
386
379
  expectedRevision: mandate.mandateRevision,
@@ -394,24 +387,27 @@ const runCapabilityDispatch = async (
394
387
  requestId,
395
388
  },
396
389
  async (mandateAuthority) => {
397
- signal.throwIfAborted();
390
+ deadline.check();
398
391
  observed.subject = mandateAuthority.subject;
399
- return (params.dependencies?.signInvocation ?? signInvocationToken)({
400
- targetAppId: params.appId,
401
- callingAppId: mandate.ownerAppId,
402
- operation: invocationOperation,
403
- schemaHash: operation.schemaHash,
404
- authority: invocationAuthorityFromMandate(mandateAuthority),
405
- requestId,
406
- signer,
407
- issuer: signer.issuer,
408
- });
392
+ return (params.dependencies?.signInvocation ?? signInvocationToken)(
393
+ {
394
+ targetAppId: params.appId,
395
+ callingAppId: mandate.ownerAppId,
396
+ operation: invocationOperation,
397
+ schemaHash: operation.schemaHash,
398
+ authority: invocationAuthorityFromMandate(mandateAuthority),
399
+ requestId,
400
+ signer,
401
+ issuer: signer.issuer,
402
+ },
403
+ { db, groupsAdmin: signer.groupsAdmin },
404
+ );
409
405
  },
410
- { db },
411
- ),
412
- { signal, timeoutMs },
406
+ { db, groupsAdmin: signer.groupsAdmin },
407
+ );
408
+ },
409
+ { signal, timeoutMs: deadline.remainingMs() },
413
410
  ),
414
- signal,
415
411
  );
416
412
  if (!issued.ok) {
417
413
  if (issued.error.code === "INTERNAL") throw new Error("Mandate invocation issuance failed");
@@ -426,23 +422,27 @@ const runCapabilityDispatch = async (
426
422
  : await (async () => {
427
423
  if (!params.authority) throw new Error("Resolved request authority is required for Cloud invocation issuance");
428
424
  const authority = params.authority;
429
- const signed = await waitWithin(
425
+ const signed = await deadline.wait(() =>
430
426
  (params.dependencies?.withActiveSigner ?? withActiveIdentitySigner)(
431
427
  "invocation",
432
- (signer) =>
433
- (params.dependencies?.signInvocation ?? signInvocationToken)({
434
- targetAppId: params.appId,
435
- callingAppId: params.callingAppId ?? "core",
436
- operation: invocationOperation,
437
- schemaHash: operation.schemaHash,
438
- authority: invocationAuthorityFromRequest(authority),
439
- requestId,
440
- signer,
441
- issuer: signer.issuer,
442
- }),
443
- { signal, timeoutMs },
428
+ (signer, db) => {
429
+ deadline.check();
430
+ return (params.dependencies?.signInvocation ?? signInvocationToken)(
431
+ {
432
+ targetAppId: params.appId,
433
+ callingAppId: params.callingAppId ?? "core",
434
+ operation: invocationOperation,
435
+ schemaHash: operation.schemaHash,
436
+ authority: invocationAuthorityFromRequest(authority),
437
+ requestId,
438
+ signer,
439
+ issuer: signer.issuer,
440
+ },
441
+ { db, groupsAdmin: signer.groupsAdmin },
442
+ );
443
+ },
444
+ { signal, timeoutMs: deadline.remainingMs() },
444
445
  ),
445
- signal,
446
446
  );
447
447
  return new Headers({
448
448
  "content-type": "application/json",
@@ -451,6 +451,7 @@ const runCapabilityDispatch = async (
451
451
  });
452
452
  })();
453
453
  } catch (error) {
454
+ if (error instanceof AppAccessDeniedError) return { body: appAccessDeniedBody(error.app), status: 403 };
454
455
  if (error instanceof MandateDispatchError) {
455
456
  const denied = errorResponse(
456
457
  error.status === 409 ? "MANDATE_REVISION_CHANGED" : "MANDATE_FORBIDDEN",
@@ -459,7 +460,7 @@ const runCapabilityDispatch = async (
459
460
  );
460
461
  return denied;
461
462
  }
462
- if (params.request.signal.aborted || timeout.aborted) {
463
+ if (signal.aborted) {
463
464
  const cancelled = params.request.signal.aborted;
464
465
  const failure = errorResponse(
465
466
  cancelled ? CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled : CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded,
@@ -473,6 +474,7 @@ const runCapabilityDispatch = async (
473
474
  const failure = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.internal, messages.issuanceFailed, 500, { retrySafe: true });
474
475
  return failure;
475
476
  }
477
+ deadline.check();
476
478
  headers.set("x-request-id", requestId);
477
479
  headers.set(CAPABILITY_ORIGIN_HEADER, params.origin);
478
480
  for (const name of ["traceparent", "tracestate"] as const) {
@@ -555,18 +557,34 @@ const runCapabilityDispatch = async (
555
557
  return outcome;
556
558
  };
557
559
 
560
+ // Claims are mutations: await their settlement, then refuse any late upstream start.
561
+ try {
562
+ deadline.check();
563
+ } catch {
564
+ const cancelled = params.request.signal.aborted;
565
+ return settle(
566
+ errorResponse(
567
+ cancelled ? CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled : CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded,
568
+ cancelled ? messages.requestCancelled : messages.deadlineExceeded,
569
+ cancelled ? 499 : 504,
570
+ { retrySafe: true },
571
+ ),
572
+ "nothing_happened",
573
+ );
574
+ }
575
+
558
576
  let response: Response;
559
577
  try {
560
578
  params.onUpstreamAttempt?.();
561
- response = await fetchUpstream(
562
- `${entry.endpoint}/${params.kind}/${encodeURIComponent(params.capabilityId)}${params.review ? "/review" : ""}`,
563
- {
579
+ const fetchResponse = () =>
580
+ fetchUpstream(`${entry.endpoint}/${params.kind}/${encodeURIComponent(params.capabilityId)}${params.review ? "/review" : ""}`, {
564
581
  method: "POST",
565
582
  headers,
566
583
  body: requestBody,
567
584
  signal,
568
- },
569
- );
585
+ });
586
+ // A read may be released at its deadline; Action effects retain their existing uncertainty handling.
587
+ response = params.kind === "queries" || params.review ? await deadline.wait(fetchResponse) : await fetchResponse();
570
588
  } catch (error) {
571
589
  log.warn("Capability app unavailable", {
572
590
  appId: params.appId,
@@ -581,7 +599,7 @@ const runCapabilityDispatch = async (
581
599
  });
582
600
  return settle(cancelled, "uncertain");
583
601
  }
584
- if (timeout.aborted) {
602
+ if (signal.aborted) {
585
603
  const deadline = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded, messages.deadlineExceeded, 504, {
586
604
  retrySafe: params.kind === "queries" || ("idempotency" in operation && operation.idempotency === "required"),
587
605
  });
@@ -596,7 +614,24 @@ const runCapabilityDispatch = async (
596
614
  return settle(unavailable, "uncertain");
597
615
  }
598
616
 
599
- const upstreamBody = await readBoundedJson(response, CAPABILITY_MAX_RESULT_BYTES);
617
+ let upstreamBody: Awaited<ReturnType<typeof readBoundedJson>>;
618
+ try {
619
+ upstreamBody = await deadline.wait(() =>
620
+ readBoundedJson(response, params.review ? CAPABILITY_MAX_REVIEW_RESPONSE_BYTES : CAPABILITY_MAX_RESULT_BYTES, signal),
621
+ );
622
+ } catch {
623
+ if (actionWithoutRetrySafety) return settle(outcomeUnknown(), "uncertain");
624
+ const cancelled = params.request.signal.aborted;
625
+ return settle(
626
+ errorResponse(
627
+ cancelled ? CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled : CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded,
628
+ cancelled ? messages.requestCancelled : messages.deadlineExceeded,
629
+ cancelled ? 499 : 504,
630
+ { retrySafe: true },
631
+ ),
632
+ "uncertain",
633
+ );
634
+ }
600
635
  if (!upstreamBody.ok) {
601
636
  if (actionWithoutRetrySafety) return settle(outcomeUnknown(), "uncertain");
602
637
  const code =
@@ -660,6 +695,7 @@ const runCapabilityDispatch = async (
660
695
  const body = upstreamBody.data as Record<string, unknown>;
661
696
  if (!params.review && body.stream !== undefined) {
662
697
  const stream = CapabilityStreamSchema.safeParse(body.stream);
698
+ const authority = params.authority;
663
699
  if (
664
700
  !stream.success ||
665
701
  !operation.stream ||
@@ -667,25 +703,29 @@ const runCapabilityDispatch = async (
667
703
  stream.data.size > operation.stream.maxBytes ||
668
704
  Date.parse(stream.data.expiresAt) <= Date.now() ||
669
705
  Date.parse(stream.data.expiresAt) > Date.now() + 86_400_000 ||
670
- !params.authority
706
+ !authority
671
707
  )
672
708
  return settle(errorResponse("INVALID_APP_RESPONSE", "Invalid stream offer", 502), "uncertain");
673
709
  try {
674
- body.stream = await sealCapabilityStream(stream.data, {
675
- appId: params.appId,
676
- kind: params.kind,
677
- capabilityId: params.capabilityId,
678
- schemaHash: operation.schemaHash,
679
- authority: params.authority,
680
- requestId,
681
- origin: params.origin,
682
- continuation: params.continuation,
683
- });
710
+ const seal = () =>
711
+ sealCapabilityStream(stream.data, {
712
+ appId: params.appId,
713
+ kind: params.kind,
714
+ capabilityId: params.capabilityId,
715
+ schemaHash: operation.schemaHash,
716
+ authority,
717
+ requestId,
718
+ origin: params.origin,
719
+ continuation: params.continuation,
720
+ });
721
+ body.stream = params.kind === "queries" ? await deadline.wait(seal) : await seal();
684
722
  if (Buffer.byteLength(JSON.stringify(body)) > CAPABILITY_MAX_RESULT_BYTES) throw new Error("Stream result too large");
685
723
  } catch {
724
+ if (params.kind === "queries") deadline.check();
686
725
  return settle(errorResponse("INVALID_APP_RESPONSE", "Invalid stream offer", 502), "uncertain");
687
726
  }
688
727
  }
728
+ if (params.kind === "queries" || params.review) deadline.check();
689
729
  return settle({ body, status: 200 }, "succeeded");
690
730
  };
691
731
 
@@ -714,50 +754,82 @@ const resultDataMeta = (body: unknown) =>
714
754
  * read-only preview and are not executions, so they are not recorded.
715
755
  */
716
756
  export const dispatchCapability = async (params: CapabilityDispatchParams): Promise<Response> => {
717
- const requestId = normalizeInvocationRequestId(params.request.headers.get("x-request-id")) ?? crypto.randomUUID();
718
- const observed: CapabilityDispatchObservation = { subject: null, destructive: false };
719
- const startedAt = new Date();
720
- const outcome = await runCapabilityDispatch(params, requestId, observed);
721
- if (!params.review) {
722
- const actor = params.authority?.actor;
723
- const principal = dispatchPrincipal(params, observed);
724
- const accessSubject = params.authority?.accessSubject;
725
- const subject =
726
- accessSubject?.type === "user"
727
- ? ({ type: "user", id: accessSubject.userId } as const)
728
- : accessSubject?.type === "service_account"
729
- ? ({ type: "service_account", id: accessSubject.serviceAccountId } as const)
730
- : null;
731
- const errorCode = outcome.status >= 400 ? bodyErrorCode(outcome.body) : null;
732
- await recordCapabilityExecution({
733
- requestId,
734
- origin: params.origin,
735
- appId: params.appId,
736
- capability: `${params.appId}.${params.capabilityId}`,
737
- kind: params.kind === "queries" ? "query" : "action",
738
- destructive: observed.destructive,
739
- actorKind: principal?.type ?? null,
740
- actorId: principal?.id ?? null,
741
- userId: actor?.kind === "user" ? actor.user.id : (actor?.delegatedUser?.id ?? (principal?.type === "user" ? principal.id : null)),
742
- accessSubject: subject && subject.id !== principal?.id ? subject : null,
743
- status: executionStatus(outcome.status, errorCode),
744
- errorCode,
745
- inputMeta: capabilityValueMeta(params.input),
746
- outputMeta: outcome.status < 400 ? resultDataMeta(outcome.body) : null,
747
- idempotencyKey: CapabilityIdempotencyKeySchema.safeParse(params.request.headers.get("idempotency-key")).data ?? null,
748
- replayed: outcome.replayed === true,
749
- startedAt,
750
- completedAt: new Date(),
751
- }).catch((error) => {
752
- // History is operational evidence; losing a row must not fail the call.
753
- log.error("Capability execution could not be recorded", {
757
+ const deadline = createCapabilityDeadline(
758
+ capabilityTimeoutMs(params.review ? "review" : params.kind === "queries" ? "query" : "action", params.dependencies),
759
+ params.request.signal,
760
+ );
761
+ try {
762
+ const requestId = normalizeInvocationRequestId(params.request.headers.get("x-request-id")) ?? crypto.randomUUID();
763
+ const observed: CapabilityDispatchObservation = { subject: null, destructive: false };
764
+ const startedAt = new Date();
765
+ let outcome: CapabilityDispatchOutcome;
766
+ try {
767
+ outcome = await runCapabilityDispatch(params, requestId, observed, deadline);
768
+ } catch (error) {
769
+ if (!deadline.signal.aborted) throw error;
770
+ const cancelled = params.request.signal.aborted;
771
+ const messages = capabilityMessages(params.locale ?? preferredLocale(params.request.headers));
772
+ outcome = errorResponse(
773
+ cancelled ? CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled : CAPABILITY_FRAMEWORK_ERROR_CODES.deadlineExceeded,
774
+ cancelled ? messages.requestCancelled : messages.deadlineExceeded,
775
+ cancelled ? 499 : 504,
776
+ { retrySafe: true },
777
+ );
778
+ }
779
+ if (!params.review) {
780
+ const actor = params.authority?.actor;
781
+ const principal = dispatchPrincipal(params, observed);
782
+ const accessSubject = params.authority?.accessSubject;
783
+ const subject =
784
+ accessSubject?.type === "user"
785
+ ? ({ type: "user", id: accessSubject.userId } as const)
786
+ : accessSubject?.type === "service_account"
787
+ ? ({ type: "service_account", id: accessSubject.serviceAccountId } as const)
788
+ : null;
789
+ const errorCode = outcome.status >= 400 ? bodyErrorCode(outcome.body) : null;
790
+ const recording = recordCapabilityExecution({
791
+ requestId,
792
+ origin: params.origin,
754
793
  appId: params.appId,
755
- capabilityId: params.capabilityId,
756
- error: error instanceof Error ? error.message : String(error),
794
+ capability: `${params.appId}.${params.capabilityId}`,
795
+ kind: params.kind === "queries" ? "query" : "action",
796
+ destructive: observed.destructive,
797
+ actorKind: principal?.type ?? null,
798
+ actorId: principal?.id ?? null,
799
+ userId: actor?.kind === "user" ? actor.user.id : (actor?.delegatedUser?.id ?? (principal?.type === "user" ? principal.id : null)),
800
+ accessSubject: subject && subject.id !== principal?.id ? subject : null,
801
+ status: executionStatus(outcome.status, errorCode),
802
+ errorCode,
803
+ inputMeta: capabilityValueMeta(params.input),
804
+ outputMeta: outcome.status < 400 ? resultDataMeta(outcome.body) : null,
805
+ idempotencyKey: CapabilityIdempotencyKeySchema.safeParse(params.request.headers.get("idempotency-key")).data ?? null,
806
+ replayed: outcome.replayed === true,
807
+ startedAt,
808
+ completedAt: new Date(),
809
+ }).catch((error) => {
810
+ // History is operational evidence; losing a row must not fail the call.
811
+ log.error("Capability execution could not be recorded", {
812
+ appId: params.appId,
813
+ capabilityId: params.capabilityId,
814
+ error: error instanceof Error ? error.message : String(error),
815
+ });
757
816
  });
758
- });
817
+ // Operational history must not extend a Query's response budget. The store
818
+ // bounds this independent, best-effort write even for already-expired calls.
819
+ if (params.kind === "queries") {
820
+ try {
821
+ await deadline.wait(() => recording);
822
+ } catch {
823
+ // The outcome is already known; return it while the bounded record finishes.
824
+ }
825
+ } else {
826
+ await recording;
827
+ }
828
+ }
829
+ return capabilityJsonResponse(outcome.body, outcome.status);
830
+ } finally {
831
+ deadline.clear();
759
832
  }
760
- return capabilityJsonResponse(outcome.body, outcome.status);
761
833
  };
762
834
 
763
835
  class MandateDispatchError extends Error {
@@ -813,7 +885,7 @@ export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies
813
885
  const query = c.req.valid("query");
814
886
  const messages = capabilityMessages(getLocale(c));
815
887
  try {
816
- return c.json(await loadCapabilityCatalogPage(query, dependencies, getLocale(c)));
888
+ return c.json(await loadCapabilityCatalogPage(query, dependencies, getLocale(c), appAccessAuthority(c)));
817
889
  } catch (error) {
818
890
  log.warn("Capability catalog unavailable", { error: error instanceof Error ? error.message : String(error) });
819
891
  const unavailable = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable, messages.registryUnavailable, 503);
@@ -830,6 +902,7 @@ export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies
830
902
  responses: {
831
903
  200: jsonResponse(CommandLinkSchema, "Command entry URL"),
832
904
  400: jsonResponse(CapabilityErrorSchema, "Invalid command input"),
905
+ 403: jsonResponse(CapabilityErrorSchema, "Application access denied"),
833
906
  404: jsonResponse(CapabilityErrorSchema, "Command unavailable"),
834
907
  503: jsonResponse(CapabilityErrorSchema, "Route owner unavailable"),
835
908
  },
@@ -840,6 +913,9 @@ export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies
840
913
  const request = body.ok ? CommandRequestSchema.safeParse(body.data) : null;
841
914
  if (!request?.success) return c.json({ code: "VALIDATION_FAILED", message: capabilityMessages(getLocale(c)).requestBodyJson }, 400);
842
915
  const appId = c.req.param("appId");
916
+ const apps = await (dependencies.listApps ?? listApps)();
917
+ const target = apps.find((app) => app.id === appId);
918
+ if (target && !(await canUseApp(appAccessAuthority(c), target))) return c.json(appAccessDeniedBody(target), 403);
843
919
  const entry = await (dependencies.getCapability ?? getCapability)(appId);
844
920
  const command = entry?.manifest.commands.find((command) => command.localId === c.req.param("capabilityId"));
845
921
  if (!command)
@@ -850,7 +926,6 @@ export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies
850
926
  },
851
927
  404,
852
928
  );
853
- const apps = await (dependencies.listApps ?? listApps)();
854
929
  const pathname = new URL(command.path, "https://cloud.invalid").pathname;
855
930
  const owner = apps
856
931
  .flatMap((app) => app.routes.map((prefix) => ({ app, prefix })))
@@ -889,33 +964,34 @@ export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies
889
964
  },
890
965
  }),
891
966
  requireReadScope,
892
- async (c) => {
893
- const locale = getLocale(c);
894
- const messages = capabilityMessages(locale);
895
- const body = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
896
- if (!body.ok) {
897
- const message = body.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
898
- const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message, 400);
899
- return capabilityJsonResponse(error.body, error.status);
900
- }
901
- const request = CapabilityInvocationRequestSchema.safeParse(body.data);
902
- if (!request.success) {
903
- const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestInputOnly, 400);
904
- return capabilityJsonResponse(error.body, error.status);
905
- }
906
- return dispatchCapability({
907
- request: c.req.raw,
908
- kind: "actions",
909
- review: true,
910
- origin: "http",
911
- appId: c.req.param("appId") ?? "",
912
- capabilityId: c.req.param("capabilityId") ?? "",
913
- input: request.data.input,
914
- authority: auth.getAuthority(c),
915
- locale,
916
- dependencies,
917
- });
918
- },
967
+ async (c) =>
968
+ withCapabilityRequestTimeout(c, "review", async () => {
969
+ const locale = getLocale(c);
970
+ const messages = capabilityMessages(locale);
971
+ const body = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
972
+ if (!body.ok) {
973
+ const message = body.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
974
+ const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message, 400);
975
+ return capabilityJsonResponse(error.body, error.status);
976
+ }
977
+ const request = CapabilityInvocationRequestSchema.safeParse(body.data);
978
+ if (!request.success) {
979
+ const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestInputOnly, 400);
980
+ return capabilityJsonResponse(error.body, error.status);
981
+ }
982
+ return dispatchCapability({
983
+ request: c.req.raw,
984
+ kind: "actions",
985
+ review: true,
986
+ origin: "http",
987
+ appId: c.req.param("appId") ?? "",
988
+ capabilityId: c.req.param("capabilityId") ?? "",
989
+ input: request.data.input,
990
+ authority: auth.getAuthority(c),
991
+ locale,
992
+ dependencies,
993
+ });
994
+ }),
919
995
  )
920
996
  .post(
921
997
  "/capabilities/v1/:kind{queries|actions}/:appId/:capabilityId",
@@ -940,33 +1016,34 @@ export const createCapabilityRoutes = (dependencies: CapabilityRouteDependencies
940
1016
  },
941
1017
  }),
942
1018
  requireInvocationScope,
943
- async (c) => {
944
- const locale = getLocale(c);
945
- const messages = capabilityMessages(locale);
946
- const body = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
947
- if (!body.ok) {
948
- const message = body.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
949
- const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message, 400);
950
- return capabilityJsonResponse(error.body, error.status);
951
- }
952
- const request = CapabilityInvocationRequestSchema.safeParse(body.data);
953
- if (!request.success) {
954
- const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestInputOnly, 400);
955
- return capabilityJsonResponse(error.body, error.status);
956
- }
1019
+ async (c) =>
1020
+ withCapabilityRequestTimeout(c, c.req.param("kind") === "queries" ? "query" : "action", async () => {
1021
+ const locale = getLocale(c);
1022
+ const messages = capabilityMessages(locale);
1023
+ const body = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
1024
+ if (!body.ok) {
1025
+ const message = body.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
1026
+ const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message, 400);
1027
+ return capabilityJsonResponse(error.body, error.status);
1028
+ }
1029
+ const request = CapabilityInvocationRequestSchema.safeParse(body.data);
1030
+ if (!request.success) {
1031
+ const error = errorResponse(CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, messages.requestInputOnly, 400);
1032
+ return capabilityJsonResponse(error.body, error.status);
1033
+ }
957
1034
 
958
- return dispatchCapability({
959
- request: c.req.raw,
960
- kind: c.req.param("kind") as "queries" | "actions",
961
- origin: "http",
962
- appId: c.req.param("appId") ?? "",
963
- capabilityId: c.req.param("capabilityId") ?? "",
964
- input: request.data.input,
965
- authority: auth.getAuthority(c),
966
- locale,
967
- dependencies,
968
- });
969
- },
1035
+ return dispatchCapability({
1036
+ request: c.req.raw,
1037
+ kind: c.req.param("kind") as "queries" | "actions",
1038
+ origin: "http",
1039
+ appId: c.req.param("appId") ?? "",
1040
+ capabilityId: c.req.param("capabilityId") ?? "",
1041
+ input: request.data.input,
1042
+ authority: auth.getAuthority(c),
1043
+ locale,
1044
+ dependencies,
1045
+ });
1046
+ }),
970
1047
  );
971
1048
  };
972
1049