@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
@@ -1,4 +1,5 @@
1
1
  import type { z } from "zod";
2
+ import type { AppPwaActor } from "../../app-pwa/contracts";
2
3
  import type { AccessSubject, CapabilityExecutionContext, RequestActor } from "../../contracts";
3
4
  import {
4
5
  GeoAutocompleteDataSchema,
@@ -26,17 +27,27 @@ import { createGeoRouteService } from "./route-service";
26
27
  import { type GeoConfig, geoPlan, readGeoConfig } from "./settings";
27
28
 
28
29
  export type GeoContext = Partial<Pick<CapabilityExecutionContext, "actor" | "accessSubject" | "locale" | "signal" | "requestId">>;
30
+ /** Internal, narrow App-PWA autocomplete adapter; never a RequestActor or capability caller. */
31
+ export type AppPwaGeoContext = { actor: AppPwaActor; locale?: string; signal?: AbortSignal; requestId?: string };
32
+ type GeoCallContext = GeoContext | AppPwaGeoContext;
29
33
  export const geoIdentity = (context: GeoContext): { actor: RequestActor; accessSubject: AccessSubject } => {
30
34
  const { actor, accessSubject } = context;
31
35
  if (
32
36
  !actor ||
33
37
  !accessSubject ||
34
- !(actor.kind === "user" ? actor.user?.id : actor.serviceAccount?.id) ||
38
+ !(actor.kind === "user" ? actor.user?.id : actor.kind === "service_account" ? actor.serviceAccount?.id : undefined) ||
35
39
  !(accessSubject.type === "user" ? accessSubject.userId : accessSubject.serviceAccountId)
36
40
  )
37
41
  throw new GeoError("GEO_FORBIDDEN");
38
42
  return { actor, accessSubject };
39
43
  };
44
+ const geoCallIdentity = (context: GeoCallContext): { actor: RequestActor | AppPwaActor; accessSubject: AccessSubject } => {
45
+ if (context.actor?.kind === "device" || context.actor?.kind === "visit") {
46
+ if (!context.actor.ownerUserId) throw new GeoError("GEO_FORBIDDEN");
47
+ return { actor: context.actor, accessSubject: { type: "user", userId: context.actor.ownerUserId } };
48
+ }
49
+ return geoIdentity({ actor: context.actor, accessSubject: "accessSubject" in context ? context.accessSubject : undefined });
50
+ };
40
51
  const bound = (name: string, value: number, min: number, max: number, integer = false) => {
41
52
  if (!Number.isFinite(value) || value > max || value < min || (integer && !Number.isInteger(value)))
42
53
  limitExceeded(name, value < min ? min : max, value);
@@ -81,14 +92,21 @@ export const createGeoService = (
81
92
  monthlyCredits: settings.monthlyCredits,
82
93
  personDailyCredits: settings.personDailyCredits,
83
94
  });
84
- const recordTransition = async (event: "monthly_budget_exhausted" | "provider_key_rejected", timezone: string, context: GeoContext) => {
85
- const identity = geoIdentity(context);
95
+ const recordTransition = async (
96
+ event: "monthly_budget_exhausted" | "provider_key_rejected" | "route_cost_overrun",
97
+ timezone: string,
98
+ context: GeoCallContext,
99
+ metadata?: Record<string, number>,
100
+ ) => {
101
+ const identity = geoCallIdentity(context);
86
102
  try {
87
- await ledger.recordTransition(event, timezone, { ...context, ...identity }, now());
103
+ await ledger.recordTransition(event, timezone, { ...context, ...identity }, now(), metadata);
104
+ return true;
88
105
  } catch {
89
106
  // The transaction rolls back the dedup row too, allowing the next refusal to record it.
90
107
  // Preserve the original recovery details and paid/unknown costs on audit outages.
91
108
  console.error("[geo] Audit transition could not be recorded", { event });
109
+ return false;
92
110
  }
93
111
  };
94
112
  const configured = async (context: GeoContext) => {
@@ -100,7 +118,7 @@ export const createGeoService = (
100
118
  const paid = async <T>(
101
119
  fn: GeoFunction,
102
120
  input: unknown,
103
- context: GeoContext,
121
+ context: GeoCallContext,
104
122
  settings: GeoConfig,
105
123
  schema: z.ZodType<T>,
106
124
  request: (signal: AbortSignal) => Promise<T>,
@@ -108,7 +126,7 @@ export const createGeoService = (
108
126
  cacheInput = input,
109
127
  empty: (value: T) => boolean = () => false,
110
128
  ): Promise<T> => {
111
- const identity = geoIdentity(context);
129
+ const identity = geoCallIdentity(context);
112
130
  const locale = context.locale ?? "en";
113
131
  const key = geoCacheKey(fn, cacheInput, locale);
114
132
  const hit = await cache.get(key, schema);
@@ -169,29 +187,37 @@ export const createGeoService = (
169
187
  await release();
170
188
  }
171
189
  };
190
+ const autocomplete = async (input: GeoAutocompleteInput, context: GeoCallContext) => {
191
+ geoCallIdentity(context);
192
+ const settings = await config();
193
+ if (!settings.apiKey.trim()) throw new GeoError("GEO_NOT_CONFIGURED");
194
+ const value = parse(GeoAutocompleteInputSchema, input);
195
+ bound("text", value.text.length, 3, 300);
196
+ bound("text", value.text.trim().length, 3, 300);
197
+ bound("limit", value.limit ?? 5, 1, 5, true);
198
+ country(value.countryCode);
199
+ area(value.area);
200
+ return paid(
201
+ "autocomplete",
202
+ value,
203
+ context,
204
+ settings,
205
+ GeoAutocompleteDataSchema,
206
+ (signal) => adapter.autocomplete(value, settings.apiKey, context.locale ?? "en", signal),
207
+ value.limit,
208
+ { ...value, limit: value.limit ?? 5 },
209
+ (result) => !result.suggestions.length,
210
+ );
211
+ };
172
212
  const routes = createGeoRouteService({ configured, adapter, cache, bucket, ledger, now, recordTransition });
173
213
  return {
174
214
  ...routes,
175
- async autocomplete(input: GeoAutocompleteInput, context: GeoContext) {
176
- const settings = await configured(context);
177
- const value = parse(GeoAutocompleteInputSchema, input);
178
- bound("text", value.text.length, 3, 300);
179
- bound("text", value.text.trim().length, 3, 300);
180
- bound("limit", value.limit ?? 5, 1, 5, true);
181
- country(value.countryCode);
182
- area(value.area);
183
- return paid(
184
- "autocomplete",
185
- value,
186
- context,
187
- settings,
188
- GeoAutocompleteDataSchema,
189
- (signal) => adapter.autocomplete(value, settings.apiKey, context.locale ?? "en", signal),
190
- value.limit,
191
- { ...value, limit: value.limit ?? 5 },
192
- (result) => !result.suggestions.length,
193
- );
215
+ autocomplete: async (input: GeoAutocompleteInput, context: GeoContext) => {
216
+ geoIdentity(context);
217
+ return autocomplete(input, context);
194
218
  },
219
+ // Only the App-PWA service calls this after locking the pairing and optional visit authority.
220
+ autocompleteForAppPwa: (input: GeoAutocompleteInput, context: AppPwaGeoContext) => autocomplete(input, context),
195
221
  async geocode(input: GeoGeocodeInput, context: GeoContext) {
196
222
  const settings = await configured(context);
197
223
  const value = parse(GeoGeocodeInputSchema, input);
@@ -0,0 +1,12 @@
1
+ import { env } from "../../config/env";
2
+ import { publicCloudOrigin } from "../../shared/app-url";
3
+ import { get } from "../settings";
4
+
5
+ export const coreOrigin = async (internal = false): Promise<string> => {
6
+ const configured = env.CLOUD_CORE_INTERNAL_ORIGIN;
7
+ if (internal && !configured) throw new Error("CLOUD_CORE_INTERNAL_ORIGIN is required for internal Core requests");
8
+ const origin = configured || publicCloudOrigin(await get<string>("app.url"));
9
+ const url = new URL(origin);
10
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("CLOUD_CORE_INTERNAL_ORIGIN must use http or https");
11
+ return url.origin;
12
+ };
@@ -8,7 +8,7 @@ export {
8
8
  CLOUD_SESSION_TOKEN_TYPE,
9
9
  } from "./constants";
10
10
  export { type ResolvedInvocationAuthority, resolveInvocationAuthority } from "./invocation-actor";
11
- export { invocationAuthorityFromRequest } from "./invocation-authority";
11
+ export { authorityFromParentInvocation, invocationAuthorityFromRequest, matchesInvocationSubject } from "./invocation-authority";
12
12
  export { capabilityInvocationOperation, searchInvocationOperation, widgetInvocationOperation } from "./invocation-operations";
13
13
  export {
14
14
  type CloudInvocationClaims,
@@ -36,6 +36,7 @@ export {
36
36
  withActiveIdentitySigner,
37
37
  } from "./key-ring";
38
38
  export { identityMetrics } from "./metrics";
39
+ export { isNestedSourceCurrent, type NestedInvocationLease, nestedInvocationLeases } from "./nested-invocation";
39
40
  export { createIdentityPublicRoutes } from "./routes";
40
41
  export {
41
42
  type CloudSessionClaims,
@@ -3,9 +3,11 @@ import type { RequestActor } from "../../server/middleware/auth";
3
3
  import type { AccessSubject } from "../../server/services/access";
4
4
  import { isAccountCategoryAllowed } from "../account-category-policy";
5
5
  import { isAccountExpired } from "../account-model";
6
+ import { isServiceAccountOAuthAuthorityCurrent } from "../oauth-tokens";
6
7
  import type { ServiceAccount } from "../service-accounts";
7
8
  import { buildProjectedUser, loadCurrentUser, userProjectionJoin, userProjectionSql, withoutAdminRole } from "../session/user";
8
9
  import type { CloudInvocationClaims } from "./invocation-token";
10
+ import { isNestedSourceCurrent, nestedInvocationLeases } from "./nested-invocation";
9
11
  import { getIdentityRuntimeConfig } from "./runtime-config";
10
12
 
11
13
  type ServiceAccountRow = Record<string, unknown> & {
@@ -32,9 +34,13 @@ export type InvocationPrincipalClaims = Pick<
32
34
  CloudInvocationClaims,
33
35
  | "sub"
34
36
  | "principal_type"
37
+ | "access_subject_type"
38
+ | "access_subject_id"
35
39
  | "session_kind"
36
40
  | "scopes"
37
41
  | "credential_id"
42
+ | "oauth_client_id"
43
+ | "credential_expires_at"
38
44
  | "exp"
39
45
  | "act"
40
46
  | "credential_kind"
@@ -44,6 +50,7 @@ export type InvocationPrincipalClaims = Pick<
44
50
  | "mandate_revision"
45
51
  | "workload_type"
46
52
  | "workload_id"
53
+ | "parent_invocation_id"
47
54
  >;
48
55
 
49
56
  const provenance = (claims: InvocationPrincipalClaims): NonNullable<RequestActor["delegation"]> => ({
@@ -51,6 +58,7 @@ const provenance = (claims: InvocationPrincipalClaims): NonNullable<RequestActor
51
58
  callingAppId: claims.act.sub.slice("app:".length),
52
59
  credentialKind: claims.credential_kind,
53
60
  invocationId: claims.jti,
61
+ ...(claims.parent_invocation_id ? { parentInvocationId: claims.parent_invocation_id } : {}),
54
62
  requestId: claims.request_id ?? null,
55
63
  ...(claims.mandate_id
56
64
  ? {
@@ -80,10 +88,29 @@ export const resolveInvocationAuthority = async (
80
88
  query: typeof sql = sql,
81
89
  groupsAdminOverride?: string[],
82
90
  ): Promise<ResolvedInvocationAuthority | null> => {
91
+ if (claims.parent_invocation_id) {
92
+ const lease = await nestedInvocationLeases.read(claims.parent_invocation_id).catch(() => null);
93
+ if (!lease || `app:${lease.appId}` !== claims.act.sub) return null;
94
+ if (lease.proof && !(await isNestedSourceCurrent(lease.proof, claims).catch(() => false))) return null;
95
+ if (claims.mandate_id) {
96
+ // Policy was checked under Core's issuance transaction; admission also
97
+ // checks the parent's current mandate state and subject.
98
+ const [mandate] = await query<{ id: string }[]>`
99
+ SELECT id FROM auth.mandates
100
+ WHERE id = ${claims.mandate_id}::uuid AND revision = ${claims.mandate_revision}
101
+ AND state = 'active' AND confirmed_at IS NOT NULL
102
+ AND (expires_at IS NULL OR expires_at > now())
103
+ AND subject_kind = ${claims.principal_type}
104
+ AND COALESCE(subject_user_id, subject_service_account_id) = ${claims.sub}::uuid
105
+ `;
106
+ if (!mandate) return null;
107
+ }
108
+ }
83
109
  const groupsAdmin = groupsAdminOverride ?? (await getIdentityRuntimeConfig()).groupsAdmin;
84
110
  const delegation = provenance(claims);
85
111
 
86
112
  if (claims.principal_type === "user") {
113
+ if (claims.access_subject_type !== "user" || claims.access_subject_id !== claims.sub) return null;
87
114
  const current = await loadCurrentUser({ userId: claims.sub, groupsAdmin }, query);
88
115
  if (!current || isAccountExpired(current.accountExpires)) return null;
89
116
  if (!(await isAccountCategoryAllowed(current, query))) return null;
@@ -125,15 +152,36 @@ export const resolveInvocationAuthority = async (
125
152
  if (serviceAccount.kind === "user_delegated" && (!delegatedUser || isAccountExpired(delegatedUser.accountExpires))) return null;
126
153
  if (delegatedUser && !(await isAccountCategoryAllowed(delegatedUser, query))) return null;
127
154
 
128
- const actor: RequestActor = {
155
+ if (
156
+ claims.access_subject_type !== (delegatedUser ? "user" : "service_account") ||
157
+ claims.access_subject_id !== (delegatedUser?.id ?? serviceAccount.id)
158
+ )
159
+ return null;
160
+
161
+ const actor: Extract<RequestActor, { kind: "service_account" }> = {
129
162
  kind: "service_account",
130
163
  serviceAccount,
131
164
  delegatedUser,
132
165
  scopes: [...claims.scopes],
133
166
  credentialId: claims.credential_id ?? null,
134
- credentialExpiresAt: new Date(claims.exp * 1_000).toISOString(),
167
+ oauthClientId: claims.oauth_client_id ?? null,
168
+ credentialExpiresAt: new Date(
169
+ Math.min(claims.exp * 1_000, claims.credential_expires_at ? Date.parse(claims.credential_expires_at) : Infinity),
170
+ ).toISOString(),
135
171
  delegation,
136
172
  };
173
+ if (
174
+ claims.oauth_client_id &&
175
+ !(await isServiceAccountOAuthAuthorityCurrent(
176
+ {
177
+ serviceAccountId: serviceAccount.id,
178
+ clientId: claims.oauth_client_id,
179
+ expiresAt: actor.credentialExpiresAt,
180
+ },
181
+ { query, groupsAdmin },
182
+ ))
183
+ )
184
+ return null;
137
185
  const accessSubject: AccessSubject = delegatedUser
138
186
  ? { type: "user", userId: delegatedUser.id, delegatedByServiceAccountId: serviceAccount.id }
139
187
  : { type: "service_account", serviceAccountId: serviceAccount.id };
@@ -1,5 +1,5 @@
1
1
  import type { RequestAuthority } from "../../server/middleware/auth";
2
- import type { InvocationAuthority } from "./invocation-token";
2
+ import type { CloudInvocationClaims, InvocationAuthority } from "./invocation-token";
3
3
 
4
4
  export const invocationAuthorityFromRequest = (authority: RequestAuthority): InvocationAuthority => {
5
5
  if (authority.credentialKind === "invocation") {
@@ -35,6 +35,8 @@ export const invocationAuthorityFromRequest = (authority: RequestAuthority): Inv
35
35
  delegated_user_id: actor.delegatedUser.id,
36
36
  credential_kind: authority.credentialKind,
37
37
  ...(actor.credentialId ? { credential_id: actor.credentialId } : {}),
38
+ ...(actor.oauthClientId ? { oauth_client_id: actor.oauthClientId } : {}),
39
+ ...(actor.credentialExpiresAt ? { credential_expires_at: actor.credentialExpiresAt } : {}),
38
40
  scopes: [...new Set(authority.scopes)].sort(),
39
41
  };
40
42
  }
@@ -49,6 +51,41 @@ export const invocationAuthorityFromRequest = (authority: RequestAuthority): Inv
49
51
  access_subject_id: actor.serviceAccount.id,
50
52
  credential_kind: authority.credentialKind,
51
53
  ...(actor.credentialId ? { credential_id: actor.credentialId } : {}),
54
+ ...(actor.oauthClientId ? { oauth_client_id: actor.oauthClientId } : {}),
55
+ ...(actor.credentialExpiresAt ? { credential_expires_at: actor.credentialExpiresAt } : {}),
52
56
  scopes: [...new Set(authority.scopes)].sort(),
53
57
  };
54
58
  };
59
+
60
+ /** A principal or delegation change cannot turn an existing invocation into new authority. */
61
+ export const matchesInvocationSubject = (parent: CloudInvocationClaims, current: RequestAuthority): boolean => {
62
+ const actor = current.actor;
63
+ const subject = current.accessSubject;
64
+ return (
65
+ (actor.kind === "user" ? actor.user.id : actor.serviceAccount.id) === parent.sub &&
66
+ actor.kind === parent.principal_type &&
67
+ subject.type === parent.access_subject_type &&
68
+ (subject.type === "user" ? subject.userId : subject.serviceAccountId) === parent.access_subject_id
69
+ );
70
+ };
71
+
72
+ /** Copy only authority fields, preserving every ceiling. Never relabel an invocation as a session. */
73
+ export const authorityFromParentInvocation = (parent: CloudInvocationClaims): InvocationAuthority => ({
74
+ sub: parent.sub,
75
+ principal_type: parent.principal_type,
76
+ access_subject_type: parent.access_subject_type,
77
+ access_subject_id: parent.access_subject_id,
78
+ ...(parent.delegated_user_id ? { delegated_user_id: parent.delegated_user_id } : {}),
79
+ credential_kind: parent.credential_kind,
80
+ ...(parent.session_kind ? { session_kind: parent.session_kind } : {}),
81
+ ...(parent.credential_id ? { credential_id: parent.credential_id } : {}),
82
+ scopes: [...parent.scopes],
83
+ ...(parent.mandate_id
84
+ ? {
85
+ mandate_id: parent.mandate_id,
86
+ mandate_revision: parent.mandate_revision,
87
+ workload_type: parent.workload_type,
88
+ workload_id: parent.workload_id,
89
+ }
90
+ : {}),
91
+ });