@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
@@ -0,0 +1,168 @@
1
+ import { z } from "zod";
2
+ import { GEO_MAX_POLYLINE_CHARS, GeoAddressSchema, GeoGeocodeInputSchema, GeoPointSchema, GeoProvenanceSchema } from "./geo";
3
+
4
+ export const GEO_ROUTE_MAX_STOPS = 26;
5
+ export const GeoRouteDistanceAllowanceSchema = z
6
+ .number()
7
+ .int()
8
+ .min(1)
9
+ .max(1_000_000_000)
10
+ .describe(
11
+ "Reference distance allowance in meters per leg; defaults to 500000. The whole chain reserves at this allowance. This is not a guaranteed provider bill ceiling. A larger actual route is charged and stops the chain.",
12
+ );
13
+ const stopId = z.string().min(1).max(100).describe("Stable stop ID, unique within this route.");
14
+ export const GeoRouteOptionsSchema = z
15
+ .object({
16
+ mode: z.enum(["car", "bike", "walk"]).optional().describe("Travel mode; defaults to car."),
17
+ avoid: z
18
+ .array(z.enum(["tolls", "ferries", "highways"]))
19
+ .max(3)
20
+ .optional()
21
+ .describe("Preferences, not prohibitions; tolls/highways require car. Each preference costs extra."),
22
+ })
23
+ .strict();
24
+ export const GeoRouteStopSchema = z
25
+ .object({
26
+ id: stopId,
27
+ label: z.string().max(300).optional().describe("Optional display label."),
28
+ point: GeoPointSchema.optional().describe("Known coordinates; choose point or address."),
29
+ address: GeoGeocodeInputSchema.optional().describe("Address to resolve; choose address or point. Ambiguity returns candidates."),
30
+ })
31
+ .strict();
32
+ export const GeoRouteResolvedStopSchema = z
33
+ .object({
34
+ id: stopId,
35
+ label: z.string().max(300).optional().describe("Optional display label."),
36
+ point: GeoPointSchema.describe("Resolved stop coordinates."),
37
+ address: GeoAddressSchema.optional().describe("Resolved address, when this stop was geocoded."),
38
+ })
39
+ .strict();
40
+ export const GeoRouteLegOptionsSchema = z
41
+ .object({
42
+ fromStopId: stopId,
43
+ toStopId: stopId,
44
+ options: GeoRouteOptionsSchema.describe("Override only the supplied options for this adjacent pair."),
45
+ })
46
+ .strict();
47
+ export const GeoRoutePlanSchema = z
48
+ .object({
49
+ distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema.optional(),
50
+ stops: z.array(GeoRouteStopSchema).min(2).max(GEO_ROUTE_MAX_STOPS).describe("2–26 stops in travel order, with unique IDs."),
51
+ options: GeoRouteOptionsSchema.optional().describe("Default options for the route."),
52
+ legOptions: z
53
+ .array(GeoRouteLegOptionsSchema)
54
+ .max(25)
55
+ .optional()
56
+ .describe("Options for adjacent stop pairs; on move/optimization each override travels with its departure stop."),
57
+ optimize: z.boolean().optional().describe("Optimize intermediate stops; start and destination stay fixed."),
58
+ })
59
+ .strict();
60
+ const geometry = z.string().min(2).max(GEO_MAX_POLYLINE_CHARS);
61
+ export const GeoRouteLegSchema = z
62
+ .object({
63
+ fromStopId: stopId,
64
+ toStopId: stopId,
65
+ options: GeoRouteOptionsSchema.describe("Travel options; mode and avoid override only supplied fields."),
66
+ distanceMeters: z.number().nonnegative().describe("Travel distance in meters."),
67
+ durationSeconds: z.number().nonnegative().describe("Estimated travel duration in seconds, without live traffic."),
68
+ polyline6: geometry.describe("Encoded route geometry at precision six."),
69
+ simplificationToleranceMeters: z.number().nonnegative().describe("Conservative geometry simplification tolerance in meters."),
70
+ provenance: GeoProvenanceSchema.describe("Provider provenance and required attribution."),
71
+ })
72
+ .strict();
73
+ export const GeoRouteSchema = z
74
+ .object({
75
+ distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema,
76
+ stops: z.array(GeoRouteResolvedStopSchema).min(2).max(GEO_ROUTE_MAX_STOPS).describe("Resolved stops in current travel order."),
77
+ options: GeoRouteOptionsSchema.describe("Travel options; mode and avoid override only supplied fields."),
78
+ legs: z.array(GeoRouteLegSchema).min(1).max(25).describe("Independent stopover legs in travel order."),
79
+ distanceMeters: z.number().nonnegative().describe("Travel distance in meters."),
80
+ durationSeconds: z.number().nonnegative().describe("Estimated travel duration in seconds, without live traffic."),
81
+ polyline6: geometry.describe("Encoded route geometry at precision six."),
82
+ simplificationToleranceMeters: z.number().nonnegative().describe("Conservative geometry simplification tolerance in meters."),
83
+ inputHash: z
84
+ .string()
85
+ .regex(/^[a-f0-9]{64}$/)
86
+ .describe("SHA-256 fingerprint of the resolved route input; preserve when updating."),
87
+ provenance: GeoProvenanceSchema.describe("Provider provenance and required attribution."),
88
+ })
89
+ .strict()
90
+ .describe("Self-contained stateless route; preserve stops, legs, options and provenance when saving.");
91
+ export const GeoRouteChangesSchema = z.discriminatedUnion("type", [
92
+ z
93
+ .object({
94
+ type: z.literal("insert").describe("Insert one stop."),
95
+ stop: GeoRouteStopSchema.describe("New stop to insert."),
96
+ afterStopId: stopId.optional().describe("Insert after this stop; omit to compare up to three best candidate gaps."),
97
+ })
98
+ .strict(),
99
+ z
100
+ .object({
101
+ type: z.literal("move").describe("Move an existing stop to a new index."),
102
+ stopId,
103
+ toIndex: z.number().int().min(0).max(25).describe("Zero-based destination index in the final stop order."),
104
+ })
105
+ .strict(),
106
+ z.object({ type: z.literal("optimize").describe("Optimize intermediate stop order while fixing start and destination.") }).strict(),
107
+ z
108
+ .object({
109
+ type: z.literal("options").describe("Change route defaults or one leg."),
110
+ options: GeoRouteOptionsSchema.describe("Travel options; mode and avoid override only supplied fields."),
111
+ leg: z
112
+ .object({ fromStopId: stopId, toStopId: stopId })
113
+ .strict()
114
+ .optional()
115
+ .describe("Change just this adjacent pair; omit to change route defaults and clear leg overrides."),
116
+ })
117
+ .strict(),
118
+ ]);
119
+ export const GeoRouteCalculateInputSchema = z.object({ plan: GeoRoutePlanSchema.describe("Route stops and travel preferences.") }).strict();
120
+ export const GeoRouteCalculateDataSchema = z.object({ route: GeoRouteSchema }).strict();
121
+ export const GeoRouteUpdateInputSchema = z
122
+ .object({
123
+ route: GeoRouteSchema,
124
+ changes: GeoRouteChangesSchema.describe("One stateless route edit to preview."),
125
+ distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema.optional(),
126
+ })
127
+ .strict();
128
+ export const GeoRouteUpdateDataSchema = z
129
+ .object({
130
+ route: GeoRouteSchema,
131
+ before: z
132
+ .object({
133
+ stopIds: z.array(stopId).min(2).max(26),
134
+ distanceMeters: z.number().nonnegative().describe("Travel distance in meters."),
135
+ durationSeconds: z.number().nonnegative(),
136
+ })
137
+ .strict(),
138
+ after: z
139
+ .object({
140
+ stopIds: z.array(stopId).min(2).max(26),
141
+ distanceMeters: z.number().nonnegative().describe("Travel distance in meters."),
142
+ durationSeconds: z.number().nonnegative(),
143
+ })
144
+ .strict(),
145
+ delta: z.object({ distanceMeters: z.number(), durationSeconds: z.number() }).strict(),
146
+ insertion: z
147
+ .object({ afterStopId: stopId, candidateGaps: z.number().int().min(1).max(3), exhaustive: z.boolean() })
148
+ .strict()
149
+ .optional()
150
+ .describe("Best insertion is a three-gap heuristic when there are more than three gaps."),
151
+ })
152
+ .strict();
153
+ export type GeoRouteOptions = z.infer<typeof GeoRouteOptionsSchema>;
154
+ export type GeoRouteStop = z.infer<typeof GeoRouteStopSchema>;
155
+ export type GeoRouteResolvedStop = z.infer<typeof GeoRouteResolvedStopSchema>;
156
+ export type GeoRouteLegOptions = z.infer<typeof GeoRouteLegOptionsSchema>;
157
+ export type GeoRoutePlan = z.infer<typeof GeoRoutePlanSchema>;
158
+ export type GeoRouteLeg = z.infer<typeof GeoRouteLegSchema>;
159
+ export type GeoRoute = z.infer<typeof GeoRouteSchema>;
160
+ export type GeoRouteChanges = z.infer<typeof GeoRouteChangesSchema>;
161
+ export type GeoRouteCalculateInput = z.infer<typeof GeoRouteCalculateInputSchema>;
162
+ export type GeoRouteCalculateData = z.infer<typeof GeoRouteCalculateDataSchema>;
163
+ export type GeoRouteUpdateInput = z.infer<typeof GeoRouteUpdateInputSchema>;
164
+ export type GeoRouteUpdateData = z.infer<typeof GeoRouteUpdateDataSchema>;
165
+ export const geoRouteContract = {
166
+ calculateRoute: { id: "route.calculate", input: GeoRouteCalculateInputSchema, data: GeoRouteCalculateDataSchema },
167
+ updateRoute: { id: "route.update", input: GeoRouteUpdateInputSchema, data: GeoRouteUpdateDataSchema },
168
+ } as const;
@@ -0,0 +1,248 @@
1
+ import { z } from "zod";
2
+
3
+ export const GeoPointSchema = z
4
+ .object({
5
+ lat: z.number().min(-90).max(90).describe("Latitude in degrees, -90 to 90."),
6
+ lon: z.number().min(-180).max(180).describe("Longitude in degrees, -180 to 180."),
7
+ })
8
+ .strict();
9
+ export const GeoProvenanceSchema = z
10
+ .object({
11
+ provider: z.literal("geoapify").describe("Source provider."),
12
+ attribution: z
13
+ .object({
14
+ text: z.literal("Geoapify · OpenStreetMap").describe("Attribution to display with this result."),
15
+ links: z.array(z.url()).length(2).describe("Provider and data-source attribution links."),
16
+ })
17
+ .strict()
18
+ .describe("Required attribution; preserve when storing or reusing the result."),
19
+ computedAt: z.iso.datetime().describe("ISO timestamp when the provider result was computed."),
20
+ })
21
+ .strict();
22
+ const text = z.string().max(300);
23
+ export const GeoAddressSchema = z
24
+ .object({
25
+ line1: text.describe("Street and house number."),
26
+ postalCode: text.describe("Postal code."),
27
+ city: text.describe("City or locality."),
28
+ stateRegion: text.describe("State or region."),
29
+ countryCode: z.string().max(2).describe("ISO alpha-2 country code, or empty when unavailable."),
30
+ point: GeoPointSchema.describe("Resolved geographic point."),
31
+ formatted: z.string().max(600).describe("Formatted provider address."),
32
+ confidence: z.number().min(0).max(1).nullable().describe("Provider match confidence, or null when unavailable."),
33
+ provenance: GeoProvenanceSchema.describe("Provider source and required attribution."),
34
+ })
35
+ .strict();
36
+
37
+ /** Verified Places categories; names belong to Cloud, provider keys to the adapter. */
38
+ export const GEO_CATEGORIES = {
39
+ museum: "entertainment.museum",
40
+ cafe: "catering.cafe",
41
+ restaurant: "catering.restaurant",
42
+ bar: "catering.bar",
43
+ pub: "catering.pub",
44
+ bakery: "commercial.food_and_drink.bakery",
45
+ supermarket: "commercial.supermarket",
46
+ pharmacy: "healthcare.pharmacy",
47
+ hotel: "accommodation.hotel",
48
+ accommodation: "accommodation",
49
+ fuel: "service.vehicle.fuel",
50
+ parking: "parking",
51
+ charging: "service.vehicle.charging_station",
52
+ toilets: "amenity.toilet",
53
+ park: "leisure.park",
54
+ viewpoint: "tourism.attraction.viewpoint",
55
+ attraction: "tourism.attraction",
56
+ } as const;
57
+ export const GeoCategorySchema = z.enum([
58
+ "museum",
59
+ "cafe",
60
+ "restaurant",
61
+ "bar",
62
+ "pub",
63
+ "bakery",
64
+ "supermarket",
65
+ "pharmacy",
66
+ "hotel",
67
+ "accommodation",
68
+ "fuel",
69
+ "parking",
70
+ "charging",
71
+ "toilets",
72
+ "park",
73
+ "viewpoint",
74
+ "attraction",
75
+ ]);
76
+ export const GeoAreaSchema = z.discriminatedUnion("type", [
77
+ z
78
+ .object({
79
+ type: z.literal("bbox").describe("Bounding box."),
80
+ west: z.number().describe("Western longitude."),
81
+ south: z.number().describe("Southern latitude."),
82
+ east: z.number().describe("Eastern longitude."),
83
+ north: z.number().describe("Northern latitude."),
84
+ })
85
+ .strict(),
86
+ z
87
+ .object({
88
+ type: z.literal("circle").describe("Circular search area."),
89
+ point: GeoPointSchema.describe("Center of the circle."),
90
+ radiusMeters: z.number().describe("Positive radius in meters."),
91
+ })
92
+ .strict(),
93
+ ]);
94
+ // Bounds are enforced by the service, after authentication/configuration, so all consumers receive GEO_LIMIT_EXCEEDED.
95
+ export const GeoAutocompleteInputSchema = z
96
+ .object({
97
+ text: z.string().describe("Partial address, 3–300 characters."),
98
+ limit: z.number().optional().describe("At most 5 suggestions; default 5."),
99
+ countryCode: z.string().optional().describe("Optional ISO alpha-2 country filter."),
100
+ type: z
101
+ .enum(["country", "state", "city", "postcode", "street", "amenity", "locality"])
102
+ .optional()
103
+ .describe("Optional address result type."),
104
+ bias: GeoPointSchema.optional().describe("Optional ranking point."),
105
+ area: GeoAreaSchema.optional().describe("Optional geographic filter."),
106
+ })
107
+ .strict();
108
+ export const GeoGeocodeInputSchema = z
109
+ .object({
110
+ text: z.string().optional().describe("Address text, at most 300 characters; use text or fields."),
111
+ fields: z
112
+ .object({
113
+ line1: z.string().optional().describe("Street and house number, at most 200 characters."),
114
+ postalCode: z.string().optional().describe("Postal code, at most 200 characters."),
115
+ city: z.string().optional().describe("City, at most 200 characters."),
116
+ stateRegion: z.string().optional().describe("State or region, at most 200 characters."),
117
+ country: z.string().optional().describe("Country name, at most 200 characters."),
118
+ })
119
+ .strict()
120
+ .optional()
121
+ .describe("Structured address, at most 200 characters per field."),
122
+ countryCode: z.string().optional().describe("Optional ISO alpha-2 country filter."),
123
+ })
124
+ .strict();
125
+ export const GeoReverseInputSchema = z.object({ point: GeoPointSchema.describe("Coordinates to reverse geocode.") }).strict();
126
+ // 102,400 ASCII bytes matches Geoapify's documented geometry budget, bounding decode memory/work before simplification.
127
+ export const GEO_MAX_POLYLINE_CHARS = 102_400;
128
+ export const GeoPlaceSearchInputSchema = z
129
+ .object({
130
+ categories: z.array(GeoCategorySchema).describe("1–5 Cloud categories."),
131
+ near: GeoPointSchema.optional().describe("Rank by distance to this point; choose near, area, or along."),
132
+ area: GeoAreaSchema.optional().describe("Optional geographic filter."),
133
+ along: z
134
+ .object({
135
+ polyline6: z.string().describe("Full route as polyline6; at most 102,400 ASCII characters."),
136
+ fromKm: z.number().describe("Start position on the full route in km."),
137
+ toKm: z.number().describe("End position; window at most 200 km."),
138
+ bufferMeters: z.number().optional().describe("100–5,000 m; default 1,000."),
139
+ })
140
+ .strict()
141
+ .optional()
142
+ .describe("Route corridor with a window of at most 200 km."),
143
+ limit: z.number().optional().describe("1–20 results; default 20."),
144
+ })
145
+ .strict();
146
+ export const GeoPlaceSchema = z
147
+ .object({
148
+ id: z.string().min(1).max(2048),
149
+ name: text,
150
+ categories: z.array(z.string().max(100)).max(20),
151
+ point: GeoPointSchema,
152
+ address: GeoAddressSchema,
153
+ distanceMeters: z.number().nonnegative().optional(),
154
+ atMeters: z.number().nonnegative().optional(),
155
+ distanceToRouteMeters: z.number().nonnegative().optional(),
156
+ openingHours: z.string().max(1000).optional(),
157
+ website: z.string().max(1000).optional(),
158
+ phone: text.optional(),
159
+ provenance: GeoProvenanceSchema,
160
+ })
161
+ .strict();
162
+ export const GeoAutocompleteDataSchema = z
163
+ .object({ suggestions: z.array(GeoAddressSchema).max(5), provenance: GeoProvenanceSchema })
164
+ .strict();
165
+ export const GeoGeocodeDataSchema = z.object({ address: GeoAddressSchema, provenance: GeoProvenanceSchema }).strict();
166
+ export const GeoReverseDataSchema = GeoGeocodeDataSchema;
167
+ export const GeoPlaceSearchDataSchema = z
168
+ .object({
169
+ places: z.array(GeoPlaceSchema).max(20),
170
+ provenance: GeoProvenanceSchema,
171
+ simplificationToleranceMeters: z.number().nonnegative().optional(),
172
+ truncated: z
173
+ .boolean()
174
+ .optional()
175
+ .describe("Candidate limit reached before filling the requested corridor results; narrow the distance window."),
176
+ })
177
+ .strict();
178
+ export const GeoAvailabilityInputSchema = z.object({}).strict();
179
+ const balance = z.object({ exhausted: z.boolean(), resetsAt: z.iso.datetime() }).strict();
180
+ export const GeoAvailabilityDataSchema = z.object({ configured: z.boolean(), person: balance, cloud: balance }).strict();
181
+ export const GeoCostsSchema = z.object({ settledCredits: z.number().nonnegative(), unknownCredits: z.number().nonnegative() }).strict();
182
+ export const GeoErrorDetailsSchema = z.discriminatedUnion("code", [
183
+ z.object({ code: z.literal("GEO_NOT_CONFIGURED") }).strict(),
184
+ z.object({ code: z.literal("GEO_FORBIDDEN") }).strict(),
185
+ z.object({ code: z.literal("GEO_UNSUPPORTED_OPTION") }).strict(),
186
+ z.object({ code: z.literal("GEO_ADDRESS_NOT_FOUND"), stopId: z.string().optional(), costs: GeoCostsSchema.optional() }).strict(),
187
+ z.object({ code: z.literal("GEO_NO_ROUTE"), costs: GeoCostsSchema.optional() }).strict(),
188
+ z
189
+ .object({
190
+ code: z.literal("GEO_COST_OVERRUN"),
191
+ distanceAllowanceMeters: z.number().positive(),
192
+ actualDistanceMeters: z.number().nonnegative(),
193
+ costs: GeoCostsSchema,
194
+ })
195
+ .strict(),
196
+ z.object({ code: z.literal("GEO_BUDGET_EXHAUSTED"), scope: z.enum(["person", "cloud"]), resetsAt: z.iso.datetime() }).strict(),
197
+ z.object({ code: z.literal("GEO_BUSY"), retryAfterSeconds: z.number().positive(), costs: GeoCostsSchema.optional() }).strict(),
198
+ z
199
+ .object({
200
+ code: z.literal("GEO_LIMIT_EXCEEDED"),
201
+ limit: z.string(),
202
+ max: z.number(),
203
+ actual: z.number(),
204
+ costs: GeoCostsSchema.optional(),
205
+ })
206
+ .strict(),
207
+ z
208
+ .object({
209
+ code: z.literal("GEO_ADDRESS_AMBIGUOUS"),
210
+ stopId: z.string().optional(),
211
+ costs: GeoCostsSchema.optional(),
212
+ candidates: z.array(GeoAddressSchema).max(5),
213
+ })
214
+ .strict(),
215
+ z
216
+ .object({
217
+ code: z.literal("GEO_PROVIDER_FAILED"),
218
+ costs: GeoCostsSchema,
219
+ adminHint: z.string().optional(),
220
+ accounting: z
221
+ .object({
222
+ reservationCredits: z.number().nonnegative(),
223
+ knownReferenceCredits: z.number().nonnegative(),
224
+ unknownReferenceCredits: z.number().nonnegative(),
225
+ })
226
+ .strict()
227
+ .optional()
228
+ .describe("Present when ledger settlement failed: retained reservation and reference costs requiring operator reconciliation."),
229
+ })
230
+ .strict(),
231
+ ]);
232
+ export type GeoPoint = z.infer<typeof GeoPointSchema>;
233
+ export type GeoProvenance = z.infer<typeof GeoProvenanceSchema>;
234
+ export type GeoAddress = z.infer<typeof GeoAddressSchema>;
235
+ export type GeoPlace = z.infer<typeof GeoPlaceSchema>;
236
+ export type GeoAutocompleteInput = z.infer<typeof GeoAutocompleteInputSchema>;
237
+ export type GeoGeocodeInput = z.infer<typeof GeoGeocodeInputSchema>;
238
+ export type GeoReverseInput = z.infer<typeof GeoReverseInputSchema>;
239
+ export type GeoPlaceSearchInput = z.infer<typeof GeoPlaceSearchInputSchema>;
240
+ export type GeoErrorCode = z.infer<typeof GeoErrorDetailsSchema>["code"];
241
+ export type GeoFunction = "autocomplete" | "geocode" | "reverse" | "places" | "route";
242
+ export const geoContract = {
243
+ autocomplete: { id: "address.autocomplete", input: GeoAutocompleteInputSchema, data: GeoAutocompleteDataSchema },
244
+ geocode: { id: "address.geocode", input: GeoGeocodeInputSchema, data: GeoGeocodeDataSchema },
245
+ reverse: { id: "address.reverse", input: GeoReverseInputSchema, data: GeoReverseDataSchema },
246
+ searchPlaces: { id: "place.search", input: GeoPlaceSearchInputSchema, data: GeoPlaceSearchDataSchema },
247
+ availability: { id: "geo.availability", input: GeoAvailabilityInputSchema, data: GeoAvailabilityDataSchema },
248
+ } as const;
@@ -7,6 +7,10 @@ export * from "./capability-compatibility";
7
7
  export * from "./commands";
8
8
  export * from "./contact-directory";
9
9
  export * from "./file-provider";
10
+ export * from "./geo";
11
+ export * from "./geo-admin";
12
+ export * from "./geo-map";
13
+ export * from "./geo-route";
10
14
  export * from "./geoip";
11
15
  export * from "./notification-types";
12
16
  export * from "./outgoing-mail";
@@ -5,7 +5,9 @@ import { type RequestActor, ServiceAccountSchema, UserSchema } from "./shared";
5
5
  export const PlatformPermissionSchema = z.enum(["mail:send", "mail:read"]);
6
6
  export type PlatformPermission = z.infer<typeof PlatformPermissionSchema>;
7
7
  export const MailRetentionSchema = z.object({ contentDays: z.int().min(1).max(36500), recordDays: z.int().min(1).max(36500) }).strict();
8
- export type MailRetention = z.infer<typeof MailRetentionSchema>;
8
+ export type MailRetention = z.infer<typeof MailRetentionSchema> & {
9
+ unreadable?: Partial<Record<"outgoing_mail.content_retention_days" | "outgoing_mail.record_retention_days", boolean>>;
10
+ };
9
11
 
10
12
  export const MailProfileKeySchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/);
11
13
  const headerText = z
@@ -129,6 +131,8 @@ export const AdminMailAppSchema = z.object({
129
131
  readDeclared: z.boolean(),
130
132
  /** Other apps whose send log this app may read, besides its own. */
131
133
  logApps: z.array(z.string()),
134
+ /** Subset of logApps with explicit content access. Missing on older servers. */
135
+ logContentApps: z.array(z.string()).optional(),
132
136
  });
133
137
  export type AdminMailApp = z.infer<typeof AdminMailAppSchema>;
134
138
  /** Application IDs as registered, like sender access; Core's own mail is never readable by apps. */
@@ -136,11 +140,15 @@ export const MailLogAppIdSchema = z.string().min(1);
136
140
  /** How many other apps' send logs an administrator can let one app read. */
137
141
  const MAIL_LOG_GRANT_LIMIT = 100;
138
142
  export const MailLogAccessSchema = z
139
- .object({ apps: z.array(MailLogAppIdSchema).max(MAIL_LOG_GRANT_LIMIT) })
143
+ .object({
144
+ apps: z.array(MailLogAppIdSchema).max(MAIL_LOG_GRANT_LIMIT),
145
+ contentApps: z.array(MailLogAppIdSchema).max(MAIL_LOG_GRANT_LIMIT).optional(),
146
+ })
140
147
  .strict()
148
+ .refine((value) => new Set(value.apps).size === value.apps.length && !value.apps.includes("core"), "Choose distinct apps other than core")
141
149
  .refine(
142
- (value) => new Set(value.apps).size === value.apps.length && !value.apps.includes("core"),
143
- "Choose distinct apps other than core",
150
+ ({ apps, contentApps = [] }) => new Set(contentApps).size === contentApps.length && contentApps.every((id) => apps.includes(id)),
151
+ "Content apps must be a distinct subset of apps",
144
152
  );
145
153
  export type MailLogAccess = z.infer<typeof MailLogAccessSchema>;
146
154
 
@@ -315,3 +323,8 @@ export type MailServiceError = ServiceError<MailErrorCode> & {
315
323
  used?: number;
316
324
  requested?: number;
317
325
  };
326
+
327
+ /** One audited read; no HTML, headers, or attachment bytes. */
328
+ export type MailContent = { purged: true; contentPurgedAt: string } | { purged: false; text: string | null; attachmentNames: string[] };
329
+ /** Pass a currently authenticated server context, never browser JSON or a stored actor snapshot. */
330
+ export type MailContentContext = { actor: RequestActor; requestId?: string };
@@ -1,4 +1,5 @@
1
1
  import type { AppAdminNavigationGroup, AppAppearance, AppAppearanceColor, AppPanel, AppPresentationCatalog, AppSearchLink } from "./app";
2
+ import type { AppAccessRule } from "./app-access";
2
3
  import type { CapabilityManifest, CapabilityPresentationCatalog } from "./capabilities";
3
4
  import type { PlatformPermission } from "./outgoing-mail";
4
5
  import type { Role } from "./shared";
@@ -77,6 +78,7 @@ export type AppRuntimeMetadata = {
77
78
  };
78
79
 
79
80
  export type AppRegistryEntry = {
81
+ access?: AppAccessRule;
80
82
  platformPermissions?: readonly PlatformPermission[];
81
83
  id: string;
82
84
  name: string;
@@ -206,6 +206,9 @@ export const MessageResponseSchema = z.object({
206
206
  });
207
207
  export type MessageResponse = z.infer<typeof MessageResponseSchema>;
208
208
 
209
+ export const SignOutOthersResponseSchema = MessageResponseSchema.extend({ signInAgain: z.boolean() });
210
+ export type SignOutOthersResponse = z.infer<typeof SignOutOthersResponseSchema>;
211
+
209
212
  export type MutationResult<T = void> = { ok: true; data: T } | { ok: false; error: string; status: 400 | 401 | 403 | 404 | 409 | 500 };
210
213
 
211
214
  export const PermissionLevelSchema = z.enum(["none", "read", "write", "admin"]);
@@ -0,0 +1,68 @@
1
+ import type { SQL } from "bun";
2
+ import { z } from "zod";
3
+ import { logger } from "../services/logging";
4
+ import { superviseRuntimeTask } from "../services/runtime-lifecycle";
5
+ import { defineLivePublisher, liveTopic } from "./live-delivery";
6
+
7
+ const change = z.object({ type: z.literal("app.access.changed"), appId: z.string() });
8
+ // Core already owns and publishes this topic and outbox; no second delivery mechanism.
9
+ const changes = defineLivePublisher({ appId: "core", event: change });
10
+
11
+ /** Call in the grant/state mutation transaction; rolled-back changes publish nothing. */
12
+ export const publishAppAccessChanged = (tx: SQL, appId: string): Promise<void> =>
13
+ changes.publish(tx, { key: `app.access:${appId}`, data: { type: "app.access.changed", appId }, access: true });
14
+ export const wakeAppAccessChanged = (): void => changes.wake();
15
+
16
+ const listeners = new Map<string, Set<() => void>>();
17
+ let active: AbortController | undefined;
18
+
19
+ /** One follower per process, released as soon as the last live connection ends. */
20
+ export const subscribeAppAccessChanged = (appId: string, changed: () => void): (() => void) => {
21
+ let callbacks = listeners.get(appId);
22
+ if (!callbacks) {
23
+ callbacks = new Set();
24
+ listeners.set(appId, callbacks);
25
+ }
26
+ callbacks.add(changed);
27
+ if (!active) {
28
+ const controller = new AbortController();
29
+ active = controller;
30
+ const notifyAll = () => {
31
+ for (const callbacks of listeners.values()) for (const callback of callbacks) callback();
32
+ };
33
+ void superviseRuntimeTask({
34
+ name: "App access invalidations",
35
+ signal: controller.signal,
36
+ run: async (signal) => {
37
+ const source = liveTopic("core");
38
+ const after = await source.head();
39
+ if (signal.aborted) return;
40
+ // Recheck after capturing the cursor to cover changes during subscription startup/reconnect.
41
+ notifyAll();
42
+ for await (const event of source.follow({ after, signal })) {
43
+ const parsed = change.safeParse(event.data.d);
44
+ if (parsed.success) for (const callback of listeners.get(parsed.data.appId) ?? []) callback();
45
+ }
46
+ },
47
+ onError: ({ error }) => {
48
+ notifyAll();
49
+ logger("app-access").warn("App access invalidations failed; reconnecting", {
50
+ error: error instanceof Error ? error.message : String(error),
51
+ });
52
+ },
53
+ }).finally(() => {
54
+ if (active === controller) active = undefined;
55
+ });
56
+ }
57
+ let subscribed = true;
58
+ return () => {
59
+ if (!subscribed) return;
60
+ subscribed = false;
61
+ callbacks.delete(changed);
62
+ if (callbacks.size === 0) listeners.delete(appId);
63
+ if (listeners.size === 0) {
64
+ active?.abort();
65
+ active = undefined;
66
+ }
67
+ };
68
+ };