@k2b/cloud 0.42.0 → 0.44.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 (352) hide show
  1. package/package.json +13 -5
  2. package/scripts/app-favicon.ts +7 -6
  3. package/scripts/build.ts +3 -0
  4. package/scripts/installable-assets.ts +61 -0
  5. package/scripts/preload.ts +3 -0
  6. package/src/_internal/app-access-path.ts +12 -0
  7. package/src/_internal/app-access-request.ts +13 -0
  8. package/src/_internal/app-access-resources.ts +141 -0
  9. package/src/_internal/app-event-catalog.ts +40 -0
  10. package/src/_internal/app-event-schemas.ts +127 -0
  11. package/src/_internal/application-package.ts +40 -0
  12. package/src/_internal/built-in-app-ids.json +27 -0
  13. package/src/_internal/capabilities.ts +185 -12
  14. package/src/_internal/capability-origin.ts +4 -0
  15. package/src/_internal/capability-streams.ts +10 -5
  16. package/src/_internal/define-app.ts +159 -45
  17. package/src/_internal/installable-assets.ts +90 -0
  18. package/src/_internal/installable-head.ts +22 -0
  19. package/src/_internal/object-store-area.ts +9 -0
  20. package/src/_internal/object-store-config.ts +101 -0
  21. package/src/_internal/object-store-key.ts +14 -0
  22. package/src/_internal/page-responses.ts +34 -3
  23. package/src/_internal/process-identity.ts +7 -2
  24. package/src/_internal/registry-validation.ts +15 -1
  25. package/src/_internal/registry.ts +44 -1
  26. package/src/_internal/runtime-context.ts +9 -1
  27. package/src/_internal/runtime-watcher.ts +8 -0
  28. package/src/_internal/status-preserving-ssr.ts +12 -1
  29. package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
  30. package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
  31. package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
  32. package/src/access/AppAccessEditor.tsx +380 -0
  33. package/src/access/PermissionEditor.browser-harness.tsx +22 -2
  34. package/src/access/PermissionEditor.tsx +386 -77
  35. package/src/access/PrincipalAvatar.tsx +63 -0
  36. package/src/access/PrincipalPicker.tsx +29 -8
  37. package/src/access/ResourceApiKeys.tsx +3 -4
  38. package/src/access/app-access-adapter.ts +51 -0
  39. package/src/access/app-access-messages.ts +172 -0
  40. package/src/access/messages.ts +14 -0
  41. package/src/access/ui.ts +4 -1
  42. package/src/account/Avatar.tsx +21 -3
  43. package/src/account/ProfilePictureDialog.tsx +699 -0
  44. package/src/account/profile-picture-messages.ts +269 -0
  45. package/src/account/ui.ts +8 -1
  46. package/src/ai/admin.ts +3 -0
  47. package/src/ai/app-frame.ts +12 -0
  48. package/src/ai/application-tasks.ts +101 -0
  49. package/src/ai/assistant-models.ts +126 -64
  50. package/src/ai/capabilities.ts +19 -4
  51. package/src/ai/capability-execution.ts +5 -2
  52. package/src/ai/chart-block.ts +63 -30
  53. package/src/ai/chat/chart-block.tsx +12 -3
  54. package/src/ai/chat/composer-adapter.ts +8 -7
  55. package/src/ai/chat/message-actions.tsx +1 -1
  56. package/src/ai/chat/message-utils.ts +2 -2
  57. package/src/ai/chat/presentation.tsx +22 -1
  58. package/src/ai/chat-quotas.ts +5 -4
  59. package/src/ai/chat-tasks.ts +2 -2
  60. package/src/ai/client/controller.ts +11 -10
  61. package/src/ai/client/projection.ts +3 -3
  62. package/src/ai/code-capability-routes.ts +1 -0
  63. package/src/ai/compaction.ts +3 -3
  64. package/src/ai/credentials.ts +13 -114
  65. package/src/ai/default-tool-contracts.ts +4 -3
  66. package/src/ai/dictation-runtime.ts +4 -4
  67. package/src/ai/enrich.ts +2 -2
  68. package/src/ai/executor.ts +38 -27
  69. package/src/ai/http.ts +17 -29
  70. package/src/ai/index.ts +11 -5
  71. package/src/ai/inference-calls.ts +6 -4
  72. package/src/ai/maintenance.ts +1 -1
  73. package/src/ai/memory-learning-runs.ts +5 -5
  74. package/src/ai/memory-learning.ts +3 -3
  75. package/src/ai/message-queue.ts +1 -2
  76. package/src/ai/migrate.ts +75 -42
  77. package/src/ai/model-access-migrate.ts +3 -17
  78. package/src/ai/model-access.ts +44 -85
  79. package/src/ai/model-card.tsx +134 -0
  80. package/src/ai/model-catalog.ts +359 -0
  81. package/src/ai/model-chooser-messages.ts +88 -0
  82. package/src/ai/model-chooser.tsx +577 -0
  83. package/src/ai/model-delete.ts +234 -0
  84. package/src/ai/model-fixture.ts +39 -0
  85. package/src/ai/model-icon.tsx +36 -0
  86. package/src/ai/model-overview.ts +53 -0
  87. package/src/ai/model-usage.ts +45 -0
  88. package/src/ai/openai-api-messages.ts +0 -4
  89. package/src/ai/openai-api-slots.ts +10 -2
  90. package/src/ai/openai-api.ts +83 -16
  91. package/src/ai/personal-agent.ts +1 -1
  92. package/src/ai/prefs.ts +12 -10
  93. package/src/ai/projects-routes.ts +2 -1
  94. package/src/ai/projects.ts +13 -12
  95. package/src/ai/protocol.ts +2 -2
  96. package/src/ai/provider-connections.ts +161 -0
  97. package/src/ai/provider-errors.ts +138 -0
  98. package/src/ai/provider-fetch.ts +4 -0
  99. package/src/ai/provider-retry.ts +1 -1
  100. package/src/ai/provider.ts +2 -1
  101. package/src/ai/quota-limits.ts +36 -0
  102. package/src/ai/quota-provider.ts +11 -2
  103. package/src/ai/quota-report.ts +7 -4
  104. package/src/ai/quotas-migrate.ts +1 -0
  105. package/src/ai/quotas.ts +19 -8
  106. package/src/ai/recent-resources.ts +6 -0
  107. package/src/ai/request-headers.ts +14 -87
  108. package/src/ai/resource-refs.ts +6 -2
  109. package/src/ai/routes.ts +17 -18
  110. package/src/ai/runtime.ts +50 -22
  111. package/src/ai/settings.ts +93 -580
  112. package/src/ai/store.ts +45 -23
  113. package/src/ai/stream.ts +1 -1
  114. package/src/ai/structured-runs.ts +2 -2
  115. package/src/ai/structured.ts +29 -46
  116. package/src/ai/task-contracts.ts +25 -5
  117. package/src/ai/task-execution.ts +15 -6
  118. package/src/ai/task-model.ts +9 -0
  119. package/src/ai/transcription.ts +37 -21
  120. package/src/ai/turn-failure.ts +24 -2
  121. package/src/ai/types.ts +50 -62
  122. package/src/ai/ui.tsx +13 -0
  123. package/src/ai/usage.ts +8 -8
  124. package/src/ai/validate.ts +8 -7
  125. package/src/ai/view-block.ts +2 -2
  126. package/src/api/accounts-entities.ts +40 -16
  127. package/src/api/admin-ai-quotas.ts +34 -56
  128. package/src/api/admin-ai-usage.ts +1 -1
  129. package/src/api/admin-ai.ts +173 -0
  130. package/src/api/admin-core-settings.ts +68 -244
  131. package/src/api/admin-directory-shares.ts +55 -0
  132. package/src/api/admin-identity.ts +81 -2
  133. package/src/api/app-access.ts +85 -0
  134. package/src/api/app-approval.ts +3 -1
  135. package/src/api/app-event-delivery.ts +51 -0
  136. package/src/api/app-events.ts +123 -0
  137. package/src/api/app-resource-access.ts +48 -0
  138. package/src/api/auth/account-mismatch.ts +24 -0
  139. package/src/api/auth.ts +26 -3
  140. package/src/api/capabilities.ts +127 -15
  141. package/src/api/capability-streams.ts +2 -5
  142. package/src/api/identity-child-invocation.ts +145 -0
  143. package/src/api/index.ts +11 -2
  144. package/src/api/mcp.ts +180 -5
  145. package/src/api/me.ts +38 -17
  146. package/src/api/pwa-phone.ts +87 -48
  147. package/src/api/pwa.ts +34 -8
  148. package/src/api/resource-links.ts +83 -6
  149. package/src/api/resource-resolve.ts +50 -0
  150. package/src/api/search/schemas.ts +3 -1
  151. package/src/api/search.ts +9 -7
  152. package/src/api/widgets.ts +1 -0
  153. package/src/app-pwa/authority.ts +75 -0
  154. package/src/app-pwa/contracts.ts +177 -0
  155. package/src/app-pwa/core-query-error.ts +20 -0
  156. package/src/app-pwa/index.ts +23 -0
  157. package/src/app-pwa/protocol.ts +27 -0
  158. package/src/app-pwa/schema.ts +59 -0
  159. package/src/app-pwa/service.ts +861 -0
  160. package/src/app-pwa/transaction.ts +338 -0
  161. package/src/app-pwa/transport-input.ts +57 -0
  162. package/src/app-pwa/transport.ts +235 -0
  163. package/src/approvals/messages.ts +2 -2
  164. package/src/browser/app-access-recovery.ts +44 -0
  165. package/src/browser/app-session.ts +14 -3
  166. package/src/browser/cloud-id.ts +86 -0
  167. package/src/browser/live-websocket.ts +5 -0
  168. package/src/browser/pwa-drafts.ts +57 -0
  169. package/src/browser/resource-clipboard.ts +4 -0
  170. package/src/browser/resource-links.ts +52 -0
  171. package/src/browser/resource-picker.tsx +3 -2
  172. package/src/capabilities/calendar-sources.ts +302 -0
  173. package/src/capabilities/executions.ts +10 -3
  174. package/src/capabilities/migrate.ts +4 -0
  175. package/src/capabilities/nested.ts +43 -0
  176. package/src/capabilities/server-invoke.ts +202 -0
  177. package/src/capabilities/server.ts +26 -187
  178. package/src/capabilities/testing.ts +5 -2
  179. package/src/cli/access.ts +6 -5
  180. package/src/cli/account.ts +57 -0
  181. package/src/cli/admin/agents.ts +66 -0
  182. package/src/cli/admin/ai-models.ts +199 -0
  183. package/src/cli/admin/ai-quotas.ts +6 -102
  184. package/src/cli/admin/ai-usage.ts +4 -4
  185. package/src/cli/admin/app-credentials.ts +5 -3
  186. package/src/cli/admin/index.ts +7 -3
  187. package/src/cli/app-access.ts +60 -0
  188. package/src/cli/apps.ts +89 -26
  189. package/src/cli/avatar.ts +64 -0
  190. package/src/cli/capabilities.ts +22 -1
  191. package/src/cli/events.ts +320 -0
  192. package/src/cli/index.ts +1 -1
  193. package/src/config/env.ts +40 -0
  194. package/src/contracts/app-access.ts +52 -5
  195. package/src/contracts/app-approval.ts +8 -1
  196. package/src/contracts/app-details.ts +89 -0
  197. package/src/contracts/app-events.ts +51 -0
  198. package/src/contracts/app.ts +24 -5
  199. package/src/contracts/calendar-source.ts +274 -0
  200. package/src/contracts/calendar-view.ts +35 -0
  201. package/src/contracts/capabilities.ts +164 -3
  202. package/src/contracts/geo-admin.ts +2 -0
  203. package/src/contracts/geo-route.ts +0 -11
  204. package/src/contracts/geo.ts +14 -2
  205. package/src/contracts/index.ts +6 -0
  206. package/src/contracts/installable.ts +40 -0
  207. package/src/contracts/notification-types.ts +36 -9
  208. package/src/contracts/profile.ts +32 -0
  209. package/src/contracts/pwa-builds.ts +14 -0
  210. package/src/contracts/pwa-location.ts +38 -0
  211. package/src/contracts/pwa.ts +24 -3
  212. package/src/contracts/registry.ts +8 -0
  213. package/src/contracts/resource-clipboard.ts +35 -0
  214. package/src/contracts/resource-pages.ts +140 -0
  215. package/src/contracts/resource-resolution.ts +13 -0
  216. package/src/contracts/shared.ts +4 -0
  217. package/src/contracts/user-notifications.ts +2 -0
  218. package/src/events/app-event-delivery.ts +100 -0
  219. package/src/events/app-events.testing.ts +65 -0
  220. package/src/events/app-events.ts +134 -0
  221. package/src/events/core-user-created.ts +24 -0
  222. package/src/events/index.ts +9 -0
  223. package/src/events/testing.ts +2 -0
  224. package/src/index.ts +2 -1
  225. package/src/server/api/respond.ts +8 -3
  226. package/src/server/index.ts +1 -0
  227. package/src/server/middleware/app-access.ts +7 -6
  228. package/src/server/middleware/auth.ts +168 -66
  229. package/src/server/middleware/index.ts +1 -0
  230. package/src/server/middleware/invocation.ts +4 -2
  231. package/src/server/middleware/workload.ts +3 -3
  232. package/src/server/oauth-scope.ts +10 -0
  233. package/src/server/public-storage.ts +106 -0
  234. package/src/server/services/access.ts +8 -0
  235. package/src/services/accounts/app.ts +35 -3
  236. package/src/services/accounts/avatar.ts +342 -35
  237. package/src/services/accounts/base-group.ts +4 -5
  238. package/src/services/accounts/directory-shares.ts +157 -0
  239. package/src/services/accounts/entities.ts +50 -6
  240. package/src/services/accounts/groups.ts +3 -1
  241. package/src/services/accounts/identities.ts +64 -12
  242. package/src/services/accounts/local-groups.ts +1 -1
  243. package/src/services/accounts/users.ts +23 -15
  244. package/src/services/app-access-management.ts +519 -0
  245. package/src/services/app-access.ts +25 -7
  246. package/src/services/app-approval.ts +15 -4
  247. package/src/services/app-details.ts +224 -0
  248. package/src/services/app-discovery.ts +2 -1
  249. package/src/services/app-event-shapes.ts +139 -0
  250. package/src/services/app-events/accept.ts +116 -0
  251. package/src/services/app-events/admin.ts +250 -0
  252. package/src/services/app-events/declarations.ts +187 -0
  253. package/src/services/app-events/deliver.ts +218 -0
  254. package/src/services/app-events.ts +92 -0
  255. package/src/services/config-conflict.ts +11 -0
  256. package/src/services/core-user-events.ts +1 -0
  257. package/src/services/freeipa-config.ts +9 -0
  258. package/src/services/geo/admin.ts +4 -1
  259. package/src/services/geo/ledger.ts +47 -14
  260. package/src/services/geo/route-chain.ts +40 -18
  261. package/src/services/geo/route-estimate.ts +19 -0
  262. package/src/services/geo/route-plan.ts +0 -2
  263. package/src/services/geo/route-service.ts +57 -39
  264. package/src/services/geo/service.ts +51 -25
  265. package/src/services/identity/core-origin.ts +12 -0
  266. package/src/services/identity/index.ts +2 -1
  267. package/src/services/identity/invocation-actor.ts +50 -2
  268. package/src/services/identity/invocation-authority.ts +38 -1
  269. package/src/services/identity/invocation-token.ts +255 -50
  270. package/src/services/identity/issue-invocation.ts +9 -1
  271. package/src/services/identity/models-only-auth.ts +13 -4
  272. package/src/services/identity/nested-invocation.ts +128 -0
  273. package/src/services/image-metadata.ts +65 -1
  274. package/src/services/index.ts +24 -1
  275. package/src/services/ipa/sync.ts +6 -2
  276. package/src/services/ipa/users.ts +4 -1
  277. package/src/services/mandates/policy.ts +19 -12
  278. package/src/services/notifications/browser.ts +4 -0
  279. package/src/services/notifications/catalog.ts +5 -2
  280. package/src/services/notifications/platform.ts +7 -2
  281. package/src/services/notifications/user.ts +18 -7
  282. package/src/services/oauth-tokens.ts +28 -2
  283. package/src/services/object-store.ts +217 -0
  284. package/src/services/providers/local/users.ts +5 -0
  285. package/src/services/pwa-devices.ts +147 -37
  286. package/src/services/service-account-credentials.ts +5 -2
  287. package/src/services/session/index.ts +14 -0
  288. package/src/services/settings/core-settings.ts +91 -47
  289. package/src/services/settings/index.ts +8 -0
  290. package/src/services/settings/versions.ts +26 -0
  291. package/src/shared/ai-models.ts +274 -0
  292. package/src/shared/ai-quotas.ts +2 -2
  293. package/src/shared/ai-usage.ts +2 -2
  294. package/src/shared/index.ts +2 -0
  295. package/src/shared-text/documents.ts +25 -8
  296. package/src/shared-text/log.ts +1 -1
  297. package/src/shared-text/text-diff.ts +75 -0
  298. package/src/ssr/AdminArea.tsx +131 -0
  299. package/src/ssr/AdminLayout.tsx +11 -3
  300. package/src/ssr/AdminSidebar.tsx +7 -7
  301. package/src/ssr/AppAccessDenied.tsx +114 -0
  302. package/src/ssr/AppBadge.tsx +14 -0
  303. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  304. package/src/ssr/AppPwaRuntime.island.tsx +82 -0
  305. package/src/ssr/CloudShell.island.tsx +25 -4
  306. package/src/ssr/Layout.tsx +15 -7
  307. package/src/ssr/PwaLayout.tsx +73 -8
  308. package/src/ssr/PwaPartCount.island.tsx +23 -0
  309. package/src/ssr/PwaRuntime.island.tsx +85 -6
  310. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  311. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  312. package/src/ssr/ShellRuntime.island.tsx +5 -0
  313. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  314. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
  315. package/src/ssr/admin-active-link.ts +16 -0
  316. package/src/ssr/admin-navigation.ts +26 -20
  317. package/src/ssr/app-badges.ts +31 -7
  318. package/src/ssr/app-pwa-document.tsx +16 -0
  319. package/src/ssr/app-pwa-messages.ts +17 -0
  320. package/src/ssr/index.ts +8 -0
  321. package/src/ssr/layout-context.ts +1 -1
  322. package/src/ssr/page-trail-runtime.ts +3 -1
  323. package/src/ssr/platform-messages.ts +9 -1
  324. package/src/ssr/pwa-messages.ts +10 -2
  325. package/src/ssr/rail-navigation.ts +5 -1
  326. package/src/ssr/settings-draft.ts +153 -0
  327. package/src/ssr/shell-data.tsx +2 -0
  328. package/src/styles/global.css +1 -0
  329. package/src/styles/profile-picture.css +270 -0
  330. package/src/styles/utilities-navigation.css +6 -0
  331. package/src/workflows/ai/policy.ts +49 -0
  332. package/src/workflows/ai/runtime.ts +131 -21
  333. package/src/workflows/ai/store.ts +283 -35
  334. package/src/workflows/ai/types.ts +8 -2
  335. package/src/workflows/ai-actions.ts +148 -120
  336. package/src/workflows/authoring.ts +31 -0
  337. package/src/workflows/contracts.ts +1 -0
  338. package/src/workflows/definition.ts +2 -0
  339. package/src/workflows/editor.ts +15 -0
  340. package/src/workflows/index.ts +2 -1
  341. package/src/workflows/language/compiler.ts +34 -3
  342. package/src/workflows/runtime/executor.ts +132 -33
  343. package/src/workflows/runtime/index.ts +2 -0
  344. package/src/workflows/runtime/ports.ts +5 -1
  345. package/src/workflows/store/actions.ts +1 -1
  346. package/src/workflows/store/definitions.ts +5 -0
  347. package/src/workflows/store/runs.ts +5 -3
  348. package/src/workflows/store/worker.ts +7 -2
  349. package/src/ai/model-pricing.ts +0 -43
  350. package/src/ai/model-request-settings.ts +0 -98
  351. package/src/ai/settings-messages.ts +0 -129
  352. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -54,6 +54,9 @@ export const capabilityValueMeta = (value: unknown): CapabilityValueMeta => {
54
54
  export type CapabilityExecutionInput = {
55
55
  /** Correlation id shared with the invocation token, the app audit row, and request logs. */
56
56
  requestId: string;
57
+ invocationId?: string | null;
58
+ parentInvocationId?: string | null;
59
+ viaAppId?: string | null;
57
60
  origin: CapabilityOrigin;
58
61
  appId: string;
59
62
  /** Fully qualified capability name, such as `contacts.list`. */
@@ -79,6 +82,9 @@ export type CapabilityExecutionInput = {
79
82
  export type CapabilityExecution = {
80
83
  id: string;
81
84
  requestId: string;
85
+ invocationId?: string | null;
86
+ parentInvocationId?: string | null;
87
+ viaAppId?: string | null;
82
88
  origin: CapabilityOrigin;
83
89
  appId: string;
84
90
  capability: string;
@@ -115,12 +121,12 @@ export const recordCapabilityExecution = async (input: CapabilityExecutionInput)
115
121
  deadline.check();
116
122
  const [row] = await db<{ id: string }[]>`
117
123
  INSERT INTO capabilities.executions (
118
- request_id, origin, app_id, capability, kind, destructive,
124
+ request_id, invocation_id, parent_invocation_id, via_app_id, origin, app_id, capability, kind, destructive,
119
125
  actor_kind, actor_id, user_id, access_subject_type, access_subject_id,
120
126
  status, error_code, input_meta, output_meta, idempotency_key, replayed,
121
127
  started_at, completed_at, duration_ms
122
128
  ) VALUES (
123
- ${input.requestId}, ${input.origin}, ${input.appId}, ${input.capability}, ${input.kind}, ${input.destructive},
129
+ ${input.requestId}, ${input.invocationId ?? null}, ${input.parentInvocationId ?? null}, ${input.viaAppId ?? null}, ${input.origin}, ${input.appId}, ${input.capability}, ${input.kind}, ${input.destructive},
124
130
  ${input.actorKind ?? null}, ${input.actorId ?? null}, ${input.userId ?? null},
125
131
  ${input.accessSubject?.type ?? null}, ${input.accessSubject?.id ?? null},
126
132
  ${input.status}, ${input.errorCode ?? null},
@@ -168,7 +174,8 @@ const filterSql = (filter: CapabilityExecutionFilter) => sql`
168
174
  `;
169
175
 
170
176
  const columns = () => sql`
171
- execution.id::text AS id, execution.request_id AS "requestId", execution.origin, execution.app_id AS "appId",
177
+ execution.id::text AS id, execution.request_id AS "requestId",
178
+ execution.invocation_id::text AS "invocationId", execution.parent_invocation_id::text AS "parentInvocationId", execution.via_app_id AS "viaAppId", execution.origin, execution.app_id AS "appId",
172
179
  execution.capability, execution.kind, execution.destructive,
173
180
  execution.actor_kind AS "actorKind", execution.actor_id::text AS "actorId", execution.user_id::text AS "userId",
174
181
  execution.access_subject_type AS "accessSubjectType", execution.access_subject_id::text AS "accessSubjectId",
@@ -55,6 +55,10 @@ export const migrateCloudCapabilities = async (): Promise<void> => {
55
55
 
56
56
  await sql`ALTER TABLE capabilities.executions ADD COLUMN IF NOT EXISTS replayed BOOLEAN NOT NULL DEFAULT false`.simple();
57
57
 
58
+ await sql`ALTER TABLE capabilities.executions ADD COLUMN IF NOT EXISTS invocation_id UUID`.simple();
59
+ await sql`ALTER TABLE capabilities.executions ADD COLUMN IF NOT EXISTS parent_invocation_id UUID`.simple();
60
+ await sql`ALTER TABLE capabilities.executions ADD COLUMN IF NOT EXISTS via_app_id TEXT`.simple();
61
+
58
62
  // Platform-owned idempotency claims. One row per
59
63
  // (app, capability, principal, key); the dispatcher claims before it
60
64
  // forwards and resolves the row once the outcome is known. Response bodies
@@ -0,0 +1,43 @@
1
+ import { env } from "../config/env";
2
+ import type { CapabilityExecutionContext } from "../contracts/capabilities";
3
+ import { capabilityMessages } from "../shared/capability-messages";
4
+ import { invokeCapabilityWithDataSchema } from "./server";
5
+
6
+ /** Framework-owned closure: the invocation credential never becomes handler data. */
7
+ export const createNestedCapabilityQuery =
8
+ (source: Request): NonNullable<CapabilityExecutionContext["query"]> =>
9
+ async (invocation, schema) => {
10
+ if (!env.CLOUD_CORE_INTERNAL_ORIGIN)
11
+ return {
12
+ ok: false,
13
+ error: {
14
+ code: "APP_UNAVAILABLE",
15
+ message: capabilityMessages(source.headers.get("x-cloud-locale") ?? undefined).cloudUnavailable,
16
+ status: 503,
17
+ },
18
+ };
19
+ return invokeCapabilityWithDataSchema({ ...invocation, kind: "query" }, schema, {
20
+ signal: source.signal,
21
+ locale: source.headers.get("x-cloud-locale"),
22
+ transport: async (request) => {
23
+ const match = new URL(request.url).pathname.match(/^\/api\/capabilities\/v1\/queries\/([^/]+)\/([^/]+)$/);
24
+ if (!match) throw new Error("Only a nested Query is supported");
25
+ const { input } = await request.json();
26
+ const headers = new Headers({ "content-type": "application/json" });
27
+ const token = source.headers.get("authorization");
28
+ if (!token) throw new Error("A parent invocation is required");
29
+ headers.set("authorization", token);
30
+ for (const name of ["x-cloud-locale", "traceparent", "tracestate"]) {
31
+ const value = source.headers.get(name);
32
+ if (value) headers.set(name, value);
33
+ }
34
+ return fetch(new URL("/api/_internal/identity/v1/invoke-child", request.url), {
35
+ method: "POST",
36
+ headers,
37
+ signal: request.signal,
38
+ redirect: "error",
39
+ body: JSON.stringify({ targetApp: decodeURIComponent(match[1]!), capabilityId: decodeURIComponent(match[2]!), input }),
40
+ });
41
+ },
42
+ });
43
+ };
@@ -0,0 +1,202 @@
1
+ import { z } from "zod";
2
+ import { dispatchCapability, loadCapabilityCatalogPage } from "../api/capabilities";
3
+ import {
4
+ CAPABILITY_FRAMEWORK_ERROR_CODES,
5
+ CAPABILITY_MAX_CATALOG_BYTES,
6
+ CapabilityCatalogSchema,
7
+ type CapabilityOrigin,
8
+ capabilityResultSchema,
9
+ } from "../contracts/capabilities";
10
+ import type { RequestAuthority } from "../server";
11
+ import { coreOrigin } from "../services/identity/core-origin";
12
+ import { capabilityMessages } from "../shared/capability-messages";
13
+ import { LOCALE_HEADER } from "../shared/locale";
14
+ import { readCapabilityResponse } from "./response";
15
+ import { combineCapabilitySignals } from "./signals";
16
+ import type { CapabilityCatalogClientResult, CapabilityClientError, CapabilityClientResult, CapabilityInvocation } from "./types";
17
+
18
+ /**
19
+ * The server capability client: the caller's credentials, the request to Core, invocation, and the catalog. Its own
20
+ * module, so readers built on it, such as the calendar reader, and the public `capabilities/server` entry share it.
21
+ */
22
+
23
+ export type CapabilityCaller = {
24
+ /** Core-only in-process authority; other applications must use a Core transport. */
25
+ authority?: RequestAuthority;
26
+ /** Trusted server transport to Core. Never supplied by a browser or sandbox. */
27
+ transport?: (request: Request) => Promise<Response>;
28
+ origin?: CapabilityOrigin;
29
+
30
+ authorization?: string | null;
31
+ cookie?: string | null;
32
+ requestId?: string | null;
33
+ traceparent?: string | null;
34
+ tracestate?: string | null;
35
+ /** BCP 47 locale of the originating request, forwarded as invocation metadata. */
36
+ locale?: string | null;
37
+ signal?: AbortSignal;
38
+ /** Durable background authority. `authorization` must be the owning app's workload credential. */
39
+ mandate?: { id: string; revision: number; callingAppId: string };
40
+ };
41
+
42
+ export { coreOrigin } from "../services/identity/core-origin";
43
+
44
+ export const callerRequest = async (
45
+ caller: CapabilityCaller,
46
+ path: string,
47
+ idempotencyKey?: string,
48
+ invocationSignal?: AbortSignal,
49
+ ): Promise<Request> => {
50
+ const headers = new Headers();
51
+ if (caller.authorization) headers.set("authorization", caller.authorization);
52
+ if (!caller.mandate) {
53
+ if (caller.cookie) headers.set("cookie", caller.cookie);
54
+ if (caller.requestId) headers.set("x-request-id", caller.requestId);
55
+ if (caller.traceparent) headers.set("traceparent", caller.traceparent);
56
+ if (caller.tracestate) headers.set("tracestate", caller.tracestate);
57
+ if (caller.locale) headers.set(LOCALE_HEADER, caller.locale);
58
+ if (idempotencyKey) headers.set("idempotency-key", idempotencyKey);
59
+ } else {
60
+ headers.set("x-cloud-app-id", caller.mandate.callingAppId);
61
+ }
62
+ const signal = combineCapabilitySignals(caller.signal, invocationSignal);
63
+ return new Request(new URL(path, await coreOrigin(caller.mandate !== undefined)), { method: "POST", headers, signal });
64
+ };
65
+
66
+ export const unavailable = (
67
+ cause: unknown,
68
+ invocation?: Pick<CapabilityInvocation, "kind" | "idempotencyKey">,
69
+ locale?: string,
70
+ ): { ok: false; error: CapabilityClientError } => ({
71
+ ok: false,
72
+ error:
73
+ invocation?.kind === "action" && !invocation.idempotencyKey
74
+ ? {
75
+ code: CAPABILITY_FRAMEWORK_ERROR_CODES.actionOutcomeUnknown,
76
+ message: capabilityMessages(locale).actionOutcomeUnknown,
77
+ status: 502,
78
+ details: { retrySafe: false },
79
+ }
80
+ : cause instanceof Error && cause.name === "AbortError"
81
+ ? {
82
+ code: CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled,
83
+ message: capabilityMessages(locale).requestCancelled,
84
+ status: 499,
85
+ }
86
+ : {
87
+ code: CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable,
88
+ message: capabilityMessages(locale).cloudUnavailable,
89
+ status: 503,
90
+ },
91
+ });
92
+
93
+ const invokeCapabilityWithResultSchema = async <TDataSchema extends z.ZodType, TInput = unknown>(
94
+ invocation: CapabilityInvocation<TInput>,
95
+ dataSchema: TDataSchema,
96
+ caller: CapabilityCaller,
97
+ ): Promise<CapabilityClientResult<z.output<TDataSchema>>> => {
98
+ try {
99
+ const path = caller.mandate
100
+ ? "/api/_internal/identity/v1/invoke"
101
+ : `/api/capabilities/v1/${invocation.kind === "query" ? "queries" : "actions"}/${encodeURIComponent(invocation.appId)}/${encodeURIComponent(invocation.capabilityId)}`;
102
+ const request = await callerRequest(caller, path, invocation.idempotencyKey, invocation.signal);
103
+ if (caller.authority) {
104
+ if (caller.authorization || caller.cookie || caller.mandate || caller.transport)
105
+ throw new Error("Use exactly one capability authority source");
106
+ const outcome = await dispatchCapability({
107
+ request,
108
+ kind: invocation.kind === "query" ? "queries" : "actions",
109
+ appId: invocation.appId,
110
+ capabilityId: invocation.capabilityId,
111
+ input: invocation.input,
112
+ authority: caller.authority,
113
+ locale: caller.locale ?? undefined,
114
+ origin: caller.origin ?? "app",
115
+ });
116
+ return readCapabilityResponse(outcome, capabilityResultSchema(dataSchema, { consumer: true }));
117
+ }
118
+ const response = await (caller.transport ?? fetch)(
119
+ new Request(request, {
120
+ body: JSON.stringify(
121
+ caller.mandate
122
+ ? {
123
+ kind: invocation.kind,
124
+ targetApp: invocation.appId,
125
+ capabilityId: invocation.capabilityId,
126
+ input: invocation.input,
127
+ mandateId: caller.mandate.id,
128
+ mandateRevision: caller.mandate.revision,
129
+ ...(invocation.idempotencyKey ? { idempotencyKey: invocation.idempotencyKey } : {}),
130
+ ...(caller.requestId || caller.traceparent || caller.tracestate || caller.locale
131
+ ? {
132
+ metadata: {
133
+ ...(caller.requestId ? { requestId: caller.requestId } : {}),
134
+ ...(caller.traceparent ? { traceparent: caller.traceparent } : {}),
135
+ ...(caller.tracestate ? { tracestate: caller.tracestate } : {}),
136
+ ...(caller.locale ? { locale: caller.locale } : {}),
137
+ },
138
+ }
139
+ : {}),
140
+ }
141
+ : { input: invocation.input },
142
+ ),
143
+ }),
144
+ );
145
+ return readCapabilityResponse(response, capabilityResultSchema(dataSchema, { consumer: true }));
146
+ } catch (cause) {
147
+ return unavailable(cause, invocation, caller.locale ?? undefined);
148
+ }
149
+ };
150
+
151
+ export const invokeCapability = <TInput = unknown>(
152
+ invocation: CapabilityInvocation<TInput>,
153
+ caller: CapabilityCaller,
154
+ ): Promise<CapabilityClientResult<unknown>> => invokeCapabilityWithResultSchema(invocation, z.unknown(), caller);
155
+
156
+ export const invokeCapabilityWithDataSchema = <TDataSchema extends z.ZodType, TInput = unknown>(
157
+ invocation: CapabilityInvocation<TInput>,
158
+ dataSchema: TDataSchema,
159
+ caller: CapabilityCaller,
160
+ ): Promise<CapabilityClientResult<z.output<TDataSchema>>> => invokeCapabilityWithResultSchema(invocation, dataSchema, caller);
161
+
162
+ export const listCapabilityCatalog = async (
163
+ options: { cursor?: string; limit?: number; locale?: string; authority?: RequestAuthority | null; caller?: CapabilityCaller } = {},
164
+ ): Promise<CapabilityCatalogClientResult> => {
165
+ const limit = options.limit ?? 25;
166
+ const messages = capabilityMessages(options.locale);
167
+ if (!Number.isInteger(limit) || limit < 1 || limit > 25) {
168
+ return {
169
+ ok: false,
170
+ error: { code: "VALIDATION_FAILED", message: messages.catalogLimit({ max: 25 }), status: 400 },
171
+ };
172
+ }
173
+ try {
174
+ if (options.caller) {
175
+ const caller = options.caller;
176
+ if (options.authority) throw new Error("Use one catalog authority source");
177
+ if (caller.authority && (caller.authorization || caller.cookie || caller.mandate || caller.transport))
178
+ throw new Error("Use exactly one capability authority source");
179
+ if (caller.authority)
180
+ return {
181
+ ok: true,
182
+ data: await loadCapabilityCatalogPage({ cursor: options.cursor, limit }, {}, caller.locale ?? options.locale, caller.authority),
183
+ };
184
+ if (caller.mandate) throw new Error("Calendar discovery requires request credentials");
185
+ const query = new URLSearchParams({ limit: String(limit) });
186
+ if (options.cursor) query.set("cursor", options.cursor);
187
+ const request = new Request(await callerRequest(caller, `/api/capabilities/v1/catalog?${query}`), { method: "GET" });
188
+ return await readCapabilityResponse(
189
+ await (caller.transport ?? fetch)(request),
190
+ CapabilityCatalogSchema,
191
+ CAPABILITY_MAX_CATALOG_BYTES,
192
+ caller.signal,
193
+ );
194
+ }
195
+ return {
196
+ ok: true,
197
+ data: await loadCapabilityCatalogPage({ cursor: options.cursor, limit }, {}, options.locale, options.authority ?? null),
198
+ };
199
+ } catch {
200
+ return { ok: false, error: { code: "APP_UNAVAILABLE", message: messages.cloudUnavailable, status: 503 } };
201
+ }
202
+ };
@@ -1,35 +1,25 @@
1
- import { z } from "zod";
2
1
  import { resolveCapabilityActionWording, resolveCapabilityManifestPresentation } from "../_internal/capabilities";
3
2
  import type { CapabilityActionWording } from "../_internal/capability-sentences";
4
3
  import { getApp, getCapability } from "../_internal/registry";
5
- import { dispatchCapability, loadCapabilityCatalogPage } from "../api/capabilities";
6
- import { env } from "../config/env";
7
- import {
8
- CAPABILITY_FRAMEWORK_ERROR_CODES,
9
- CAPABILITY_MAX_REVIEW_RESPONSE_BYTES,
10
- CapabilityActionReviewDataSchema,
11
- type CapabilityOrigin,
12
- capabilityResultSchema,
13
- } from "../contracts/capabilities";
4
+ import { dispatchCapability } from "../api/capabilities";
5
+ import { CAPABILITY_MAX_REVIEW_RESPONSE_BYTES, CapabilityActionReviewDataSchema } from "../contracts/capabilities";
14
6
  import type { RequestAuthority } from "../server";
15
7
  import { canUseApp } from "../services/app-access";
16
- import { get } from "../services/settings";
17
8
  import { resolveAppPresentation } from "../shared/app-presentation";
18
- import { publicCloudOrigin } from "../shared/app-url";
19
9
  import { capabilityMessages } from "../shared/capability-messages";
20
10
  import { DEFAULT_LOCALE, LOCALE_HEADER } from "../shared/locale";
21
11
  import { readCapabilityResponse } from "./response";
12
+ import { type CapabilityCaller, callerRequest, coreOrigin, unavailable } from "./server-invoke";
22
13
  import { combineCapabilitySignals } from "./signals";
23
- import type {
24
- CapabilityCatalogAppClientResult,
25
- CapabilityCatalogClientResult,
26
- CapabilityClientError,
27
- CapabilityClientResult,
28
- CapabilityInvocation,
29
- CapabilityReviewClientResult,
30
- } from "./types";
14
+ import type { CapabilityCatalogAppClientResult, CapabilityInvocation, CapabilityReviewClientResult } from "./types";
31
15
 
32
16
  export type { CapabilityActionWording } from "../_internal/capability-sentences";
17
+ export {
18
+ type CapabilityCaller,
19
+ invokeCapability,
20
+ invokeCapabilityWithDataSchema,
21
+ listCapabilityCatalog,
22
+ } from "./server-invoke";
33
23
  export type {
34
24
  CapabilityCatalogApp,
35
25
  CapabilityCatalogAppClientResult,
@@ -39,173 +29,6 @@ export type {
39
29
  CapabilityReviewClientResult,
40
30
  } from "./types";
41
31
 
42
- export type CapabilityCaller = {
43
- /** Core-only in-process authority; other applications must use a Core transport. */
44
- authority?: RequestAuthority;
45
- /** Trusted server transport to Core. Never supplied by a browser or sandbox. */
46
- transport?: (request: Request) => Promise<Response>;
47
- origin?: CapabilityOrigin;
48
-
49
- authorization?: string | null;
50
- cookie?: string | null;
51
- requestId?: string | null;
52
- traceparent?: string | null;
53
- tracestate?: string | null;
54
- /** BCP 47 locale of the originating request, forwarded as invocation metadata. */
55
- locale?: string | null;
56
- signal?: AbortSignal;
57
- /** Durable background authority. `authorization` must be the owning app's workload credential. */
58
- mandate?: { id: string; revision: number; callingAppId: string };
59
- };
60
-
61
- const coreOrigin = async (internal = false): Promise<string> => {
62
- const configured = env.CLOUD_CORE_INTERNAL_ORIGIN;
63
- if (internal && !configured) throw new Error("CLOUD_CORE_INTERNAL_ORIGIN is required for mandate-backed invocation");
64
- const origin = configured || publicCloudOrigin(await get<string>("app.url"));
65
- const url = new URL(origin);
66
- if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("CLOUD_CORE_INTERNAL_ORIGIN must use http or https");
67
- return url.origin;
68
- };
69
-
70
- const callerRequest = async (
71
- caller: CapabilityCaller,
72
- path: string,
73
- idempotencyKey?: string,
74
- invocationSignal?: AbortSignal,
75
- ): Promise<Request> => {
76
- const headers = new Headers();
77
- if (caller.authorization) headers.set("authorization", caller.authorization);
78
- if (!caller.mandate) {
79
- if (caller.cookie) headers.set("cookie", caller.cookie);
80
- if (caller.requestId) headers.set("x-request-id", caller.requestId);
81
- if (caller.traceparent) headers.set("traceparent", caller.traceparent);
82
- if (caller.tracestate) headers.set("tracestate", caller.tracestate);
83
- if (caller.locale) headers.set(LOCALE_HEADER, caller.locale);
84
- if (idempotencyKey) headers.set("idempotency-key", idempotencyKey);
85
- } else {
86
- headers.set("x-cloud-app-id", caller.mandate.callingAppId);
87
- }
88
- const signal = combineCapabilitySignals(caller.signal, invocationSignal);
89
- return new Request(new URL(path, await coreOrigin(caller.mandate !== undefined)), { method: "POST", headers, signal });
90
- };
91
-
92
- const unavailable = (
93
- cause: unknown,
94
- invocation?: Pick<CapabilityInvocation, "kind" | "idempotencyKey">,
95
- locale?: string,
96
- ): { ok: false; error: CapabilityClientError } => ({
97
- ok: false,
98
- error:
99
- invocation?.kind === "action" && !invocation.idempotencyKey
100
- ? {
101
- code: CAPABILITY_FRAMEWORK_ERROR_CODES.actionOutcomeUnknown,
102
- message: capabilityMessages(locale).actionOutcomeUnknown,
103
- status: 502,
104
- details: { retrySafe: false },
105
- }
106
- : cause instanceof Error && cause.name === "AbortError"
107
- ? {
108
- code: CAPABILITY_FRAMEWORK_ERROR_CODES.requestCancelled,
109
- message: capabilityMessages(locale).requestCancelled,
110
- status: 499,
111
- }
112
- : {
113
- code: CAPABILITY_FRAMEWORK_ERROR_CODES.appUnavailable,
114
- message: capabilityMessages(locale).cloudUnavailable,
115
- status: 503,
116
- },
117
- });
118
-
119
- const invokeCapabilityWithResultSchema = async <TDataSchema extends z.ZodType, TInput = unknown>(
120
- invocation: CapabilityInvocation<TInput>,
121
- dataSchema: TDataSchema,
122
- caller: CapabilityCaller,
123
- ): Promise<CapabilityClientResult<z.output<TDataSchema>>> => {
124
- try {
125
- const path = caller.mandate
126
- ? "/api/_internal/identity/v1/invoke"
127
- : `/api/capabilities/v1/${invocation.kind === "query" ? "queries" : "actions"}/${encodeURIComponent(invocation.appId)}/${encodeURIComponent(invocation.capabilityId)}`;
128
- const request = await callerRequest(caller, path, invocation.idempotencyKey, invocation.signal);
129
- if (caller.authority) {
130
- if (caller.authorization || caller.cookie || caller.mandate || caller.transport)
131
- throw new Error("Use exactly one capability authority source");
132
- const outcome = await dispatchCapability({
133
- request,
134
- kind: invocation.kind === "query" ? "queries" : "actions",
135
- appId: invocation.appId,
136
- capabilityId: invocation.capabilityId,
137
- input: invocation.input,
138
- authority: caller.authority,
139
- locale: caller.locale ?? undefined,
140
- origin: caller.origin ?? "app",
141
- });
142
- return readCapabilityResponse(outcome, capabilityResultSchema(dataSchema, { consumer: true }));
143
- }
144
- const response = await (caller.transport ?? fetch)(
145
- new Request(request, {
146
- body: JSON.stringify(
147
- caller.mandate
148
- ? {
149
- kind: invocation.kind,
150
- targetApp: invocation.appId,
151
- capabilityId: invocation.capabilityId,
152
- input: invocation.input,
153
- mandateId: caller.mandate.id,
154
- mandateRevision: caller.mandate.revision,
155
- ...(invocation.idempotencyKey ? { idempotencyKey: invocation.idempotencyKey } : {}),
156
- ...(caller.requestId || caller.traceparent || caller.tracestate || caller.locale
157
- ? {
158
- metadata: {
159
- ...(caller.requestId ? { requestId: caller.requestId } : {}),
160
- ...(caller.traceparent ? { traceparent: caller.traceparent } : {}),
161
- ...(caller.tracestate ? { tracestate: caller.tracestate } : {}),
162
- ...(caller.locale ? { locale: caller.locale } : {}),
163
- },
164
- }
165
- : {}),
166
- }
167
- : { input: invocation.input },
168
- ),
169
- }),
170
- );
171
- return readCapabilityResponse(response, capabilityResultSchema(dataSchema, { consumer: true }));
172
- } catch (cause) {
173
- return unavailable(cause, invocation, caller.locale ?? undefined);
174
- }
175
- };
176
-
177
- export const invokeCapability = <TInput = unknown>(
178
- invocation: CapabilityInvocation<TInput>,
179
- caller: CapabilityCaller,
180
- ): Promise<CapabilityClientResult<unknown>> => invokeCapabilityWithResultSchema(invocation, z.unknown(), caller);
181
-
182
- export const invokeCapabilityWithDataSchema = <TDataSchema extends z.ZodType, TInput = unknown>(
183
- invocation: CapabilityInvocation<TInput>,
184
- dataSchema: TDataSchema,
185
- caller: CapabilityCaller,
186
- ): Promise<CapabilityClientResult<z.output<TDataSchema>>> => invokeCapabilityWithResultSchema(invocation, dataSchema, caller);
187
-
188
- export const listCapabilityCatalog = async (
189
- options: { cursor?: string; limit?: number; locale?: string; authority?: RequestAuthority | null } = {},
190
- ): Promise<CapabilityCatalogClientResult> => {
191
- const limit = options.limit ?? 25;
192
- const messages = capabilityMessages(options.locale);
193
- if (!Number.isInteger(limit) || limit < 1 || limit > 25) {
194
- return {
195
- ok: false,
196
- error: { code: "VALIDATION_FAILED", message: messages.catalogLimit({ max: 25 }), status: 400 },
197
- };
198
- }
199
- try {
200
- return {
201
- ok: true,
202
- data: await loadCapabilityCatalogPage({ cursor: options.cursor, limit }, {}, options.locale, options.authority ?? null),
203
- };
204
- } catch {
205
- return { ok: false, error: { code: "APP_UNAVAILABLE", message: messages.cloudUnavailable, status: 503 } };
206
- }
207
- };
208
-
209
32
  export const getCapabilityCatalogApp = async (
210
33
  appId: string,
211
34
  locale?: string,
@@ -333,6 +156,22 @@ export async function transferCapabilityStream(
333
156
  return caller.transport ? caller.transport(request) : fetch(request, { redirect: "error" });
334
157
  }
335
158
 
159
+ export {
160
+ CALENDAR_VIEW_MAX_RANGE_MS,
161
+ type CalendarReadResult,
162
+ CalendarReadResultSchema,
163
+ type CalendarSelection,
164
+ CalendarSourceErrorSchema,
165
+ type CalendarSourceListEntry,
166
+ CalendarSourceListEntrySchema,
167
+ type CalendarSourceState,
168
+ CalendarSourceStateSchema,
169
+ type CalendarSourceSummary,
170
+ CalendarSourceSummarySchema,
171
+ CalendarViewRangeSchema,
172
+ listCalendarSources,
173
+ readCalendarEvents,
174
+ } from "./calendar-sources";
336
175
  export {
337
176
  actionReview,
338
177
  type CapabilityReviewForms,
@@ -17,8 +17,11 @@ import {
17
17
  } from "../contracts/capabilities";
18
18
 
19
19
  /** Compile and validate the public manifest an app would register at startup. */
20
- export const compileCapabilityManifest = (appId: string, definitions: CapabilityDefinitions): CapabilityManifest =>
21
- compileCapabilities(appId, definitions).manifest;
20
+ export const compileCapabilityManifest = (
21
+ appId: string,
22
+ definitions: CapabilityDefinitions,
23
+ routes?: readonly string[],
24
+ ): CapabilityManifest => compileCapabilities(appId, definitions, routes).manifest;
22
25
 
23
26
  /** Fail a provider compatibility test when a published local id changed incompatibly. */
24
27
  export const assertCapabilityManifestEvolution = (previous: CapabilityManifest, next: CapabilityManifest): void => {
package/src/cli/access.ts CHANGED
@@ -49,6 +49,7 @@ export type AccessCommandAdapter<TResource extends AccessResource> = {
49
49
  scopes?: readonly [string, ...string[]];
50
50
  allowPublic?: boolean;
51
51
  allowAuthenticated?: boolean;
52
+ /** Defaults to true; false preserves an explicitly restricted resource contract. */
52
53
  allowServiceAccounts?: boolean;
53
54
  examples?: {
54
55
  list?: readonly string[];
@@ -217,7 +218,7 @@ export const resolveAccessPrincipal = async (
217
218
  "--group",
218
219
  ...(options.allowAuthenticated === false ? [] : ["--authenticated"]),
219
220
  ...(options.allowPublic === true ? ["--public"] : []),
220
- ...(options.allowServiceAccounts === true ? ["--service-account"] : []),
221
+ ...(options.allowServiceAccounts !== false ? ["--service-account"] : []),
221
222
  ];
222
223
  throw new Error(`Pass exactly one principal flag: ${allowedFlags.join(", ")}.`);
223
224
  }
@@ -225,8 +226,8 @@ export const resolveAccessPrincipal = async (
225
226
  if (flags.authenticated && options.allowAuthenticated === false) {
226
227
  throw new Error("This resource does not allow authenticated-audience grants.");
227
228
  }
228
- if (flags.serviceAccount && options.allowServiceAccounts !== true) {
229
- throw new Error("Service-account grants are hidden by default. Enable allowServiceAccounts for this access command.");
229
+ if (flags.serviceAccount && options.allowServiceAccounts === false) {
230
+ throw new Error("This resource does not allow service-account access grants.");
230
231
  }
231
232
  if (flags.authenticated) return { type: "authenticated" };
232
233
  if (flags.public) return { type: "public" };
@@ -348,11 +349,11 @@ const accessIdFlag = {
348
349
 
349
350
  export const createAccessCommands = <TResource extends AccessResource>(adapter: AccessCommandAdapter<TResource>) => {
350
351
  const allowed = normalizeAllowedPermissions(adapter.allowedPermissions);
351
- const searchableKinds = adapter.allowServiceAccounts ? PRINCIPAL_KINDS : (["user", "group"] as const);
352
+ const searchableKinds = adapter.allowServiceAccounts !== false ? PRINCIPAL_KINDS : (["user", "group"] as const);
352
353
  const principalFlagOptions = {
353
354
  includePublicFlag: adapter.allowPublic === true,
354
355
  includeAuthenticatedFlag: adapter.allowAuthenticated !== false,
355
- includeServiceAccountFlag: adapter.allowServiceAccounts === true,
356
+ includeServiceAccountFlag: adapter.allowServiceAccounts !== false,
356
357
  };
357
358
  const resolve = (ctx: CloudCliContext, args: string[]) => adapter.resolveResource(ctx, args);
358
359
  const scopeFlag: { scope?: ReturnType<typeof flag.string> } = adapter.scopes