okengine 0.14.1 → 0.16.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 (285) hide show
  1. package/package.json +3 -4
  2. package/site/content/docs/elements/ai.mdx +2 -1
  3. package/site/content/docs/elements/channel.mdx +1 -0
  4. package/site/content/docs/elements/clock.mdx +3 -0
  5. package/site/content/docs/elements/flow.mdx +9 -0
  6. package/site/content/docs/elements/gate.mdx +80 -22
  7. package/site/content/docs/elements/signal.mdx +4 -4
  8. package/site/content/docs/elements/store.mdx +84 -27
  9. package/site/content/docs/elements/vault.mdx +4 -0
  10. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  11. package/site/content/docs/get-started/index.mdx +5 -0
  12. package/site/content/docs/get-started/introduction.mdx +11 -3
  13. package/site/content/docs/get-started/meta.json +1 -1
  14. package/site/content/docs/get-started/project-structure.mdx +932 -0
  15. package/site/content/docs/reference/cli.md +13 -10
  16. package/site/content/docs/reference/client.mdx +28 -21
  17. package/site/content/docs/reference/fx.mdx +29 -18
  18. package/site/content/docs/reference/security.md +2 -2
  19. package/src/auth/api-key-sql.test.ts +47 -0
  20. package/src/auth/api-key-sql.ts +254 -0
  21. package/src/auth/api-keys.ts +273 -14
  22. package/src/auth/auth.test.ts +56 -0
  23. package/src/auth/bindings.ts +14 -5
  24. package/src/auth/config.ts +5 -5
  25. package/src/auth/cross-plane.ts +10 -3
  26. package/src/auth/index.ts +25 -1
  27. package/src/auth/schema.ts +8 -0
  28. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  29. package/src/cli/ai-setup/apply.ts +32 -1
  30. package/src/cli/attach-host-console.test.ts +40 -2
  31. package/src/cli/attach-host-console.ts +9 -0
  32. package/src/cli/build.test.ts +4 -2
  33. package/src/cli/db-seed.ts +48 -15
  34. package/src/cli/db.test.ts +104 -0
  35. package/src/cli/db.ts +122 -10
  36. package/src/cli/dev-controls.test.ts +20 -0
  37. package/src/cli/dev-controls.ts +13 -0
  38. package/src/cli/dev.test.ts +15 -4
  39. package/src/cli/dev.ts +102 -29
  40. package/src/cli/ensure-drizzle-config.ts +2 -1
  41. package/src/cli/tui/DevLive.tsx +12 -2
  42. package/src/cli/tui/keys.test.ts +2 -1
  43. package/src/cli/tui/keys.ts +3 -2
  44. package/src/compiler/aot.test.ts +1 -1
  45. package/src/compiler/effects-infer.ts +17 -2
  46. package/src/compiler/extract.test.ts +409 -33
  47. package/src/compiler/extract.ts +318 -67
  48. package/src/compiler/flow-path.test.ts +96 -0
  49. package/src/compiler/flow-path.ts +173 -0
  50. package/src/compiler/generate-adopt.test.ts +112 -3
  51. package/src/compiler/generate-adopt.ts +280 -26
  52. package/src/compiler/schema-from-ast.ts +609 -0
  53. package/src/console/server/access.test.ts +116 -8
  54. package/src/console/server/access.ts +70 -23
  55. package/src/console/server/app.ts +1 -0
  56. package/src/console/server/bind.ts +2 -2
  57. package/src/console/server/console.test.ts +3 -0
  58. package/src/console/server/dev-identities.test.ts +127 -0
  59. package/src/console/server/dev-identities.ts +229 -0
  60. package/src/console/server/flows-invoke.test.ts +21 -4
  61. package/src/console/server/flows.ts +233 -98
  62. package/src/console/server/gates.test.ts +24 -0
  63. package/src/console/server/gates.ts +8 -21
  64. package/src/console/server/index.ts +1 -0
  65. package/src/console/server/invoke-user-flow.test.ts +3 -1
  66. package/src/console/server/invoke-user-flow.ts +17 -2
  67. package/src/console/server/iso-at.test.ts +9 -0
  68. package/src/console/server/iso-at.ts +11 -0
  69. package/src/console/server/live.test.ts +26 -1
  70. package/src/console/server/live.ts +4 -2
  71. package/src/console/server/operator-db.ts +2 -0
  72. package/src/console/server/security-headers.ts +6 -3
  73. package/src/console/server/spa-proxy.test.ts +1 -0
  74. package/src/console/server/sql-catalog.ts +147 -7
  75. package/src/console/server/sql-rls.test.ts +16 -0
  76. package/src/console/server/state.ts +37 -55
  77. package/src/console/server/store.test.ts +7 -4
  78. package/src/console/server/store.ts +142 -58
  79. package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
  80. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
  81. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
  82. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
  83. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
  84. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
  85. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
  86. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
  87. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  88. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
  89. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
  90. package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
  91. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-Awq7gYdu.js} +1 -1
  92. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
  93. package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
  94. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
  95. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
  96. package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
  97. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  98. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
  99. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
  100. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
  101. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
  102. package/src/console/ui-next/dist/assets/{skeleton-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
  103. package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
  104. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
  105. package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
  106. package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
  107. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-uk4WVboC.js} +1 -1
  108. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
  109. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
  110. package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
  111. package/src/console/ui-next/dist/index.html +12 -10
  112. package/src/console/ui-next/index.html +3 -3
  113. package/src/console/ui-next/src/client.ts +202 -7
  114. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  115. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  116. package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
  117. package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
  118. package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
  119. package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
  120. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  121. package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
  122. package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
  123. package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
  124. package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
  125. package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
  126. package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
  127. package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
  128. package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
  129. package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
  130. package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
  131. package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
  132. package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
  133. package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
  134. package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
  135. package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
  136. package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
  137. package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
  138. package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
  139. package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
  140. package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
  141. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  142. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
  143. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  144. package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
  145. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  146. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  147. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  148. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  149. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  150. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  151. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  152. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  153. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  154. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  155. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  156. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  157. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  158. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  159. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  160. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  161. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  162. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  163. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  164. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  165. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  166. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  167. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  168. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  169. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  170. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  171. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  172. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  173. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  174. package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
  175. package/src/console/ui-next/src/lib/document-meta.ts +2 -1
  176. package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
  177. package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
  178. package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
  179. package/src/console/ui-next/src/lib/shortcut.ts +3 -0
  180. package/src/console/ui-next/src/router.tsx +10 -1
  181. package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
  182. package/src/console/ui-next/ui-next-seed-store.ts +43 -15
  183. package/src/console/xss.gate.test.ts +1 -0
  184. package/src/docker/compose-health.test.ts +27 -0
  185. package/src/docker/compose-health.ts +19 -3
  186. package/src/docker/docker.test.ts +3 -0
  187. package/src/docker/recipes/pgdog.ts +2 -0
  188. package/src/drivers/clock-postgres.ts +13 -9
  189. package/src/drivers/instances-postgres.ts +4 -6
  190. package/src/drivers/journal-postgres.ts +13 -9
  191. package/src/drivers/pg-rls.test.ts +87 -0
  192. package/src/drivers/pg-rls.ts +233 -7
  193. package/src/drivers/pglite.ts +26 -3
  194. package/src/drivers/postgres.test.ts +23 -0
  195. package/src/drivers/postgres.ts +133 -9
  196. package/src/drivers/types.ts +10 -0
  197. package/src/elements/clock/declare.ts +26 -1
  198. package/src/elements/gate/boot.ts +5 -5
  199. package/src/elements/gate/declare.ts +38 -4
  200. package/src/elements/gate/flatten.ts +1 -1
  201. package/src/elements/gate/runtime.ts +1 -1
  202. package/src/elements/store/cache.ts +1 -1
  203. package/src/elements/store/declare.ts +9 -6
  204. package/src/elements/store/emit-drizzle.ts +44 -8
  205. package/src/elements/store/resource.test.ts +1 -1
  206. package/src/elements/store/rls-identity.test.ts +95 -0
  207. package/src/elements/store/rls-identity.ts +116 -0
  208. package/src/elements/store/runtime.ts +18 -0
  209. package/src/elements/store/schema-decl.test.ts +37 -0
  210. package/src/elements/store/schema-decl.ts +260 -41
  211. package/src/elements/store/seed.ts +1 -1
  212. package/src/elements/store/sql-condition.test.ts +12 -0
  213. package/src/elements/store/sql-condition.ts +26 -2
  214. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  215. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  216. package/src/elements/store/sql-select.types.test.ts +71 -0
  217. package/src/elements/store/sql-session.ts +135 -35
  218. package/src/elements/store/upsert-app.test.ts +1 -1
  219. package/src/elements/store.ts +17 -0
  220. package/src/http.ts +20 -3
  221. package/src/index.ts +11 -1
  222. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  223. package/src/kernel/adopt-routes.ts +30 -2
  224. package/src/kernel/api-key-host-persist.test.ts +143 -0
  225. package/src/kernel/app.ts +145 -16
  226. package/src/kernel/auth-resolve.ts +10 -5
  227. package/src/kernel/auto-cache.test.ts +8 -8
  228. package/src/kernel/auto-registry.test.ts +49 -9
  229. package/src/kernel/boot.test.ts +5 -5
  230. package/src/kernel/boot.ts +2 -2
  231. package/src/kernel/effects-stamping.test.ts +3 -3
  232. package/src/kernel/element-registries.ts +12 -5
  233. package/src/kernel/errors.ts +24 -0
  234. package/src/kernel/flow-units.ts +64 -0
  235. package/src/kernel/flow.ts +29 -17
  236. package/src/kernel/fx-auth-keys.test.ts +88 -0
  237. package/src/kernel/fx-auth-keys.ts +198 -0
  238. package/src/kernel/fx.test.ts +17 -1
  239. package/src/kernel/fx.ts +59 -18
  240. package/src/kernel/horizontal-child.ts +4 -4
  241. package/src/kernel/http-path-pending.ts +20 -0
  242. package/src/kernel/http-resource.ts +4 -0
  243. package/src/kernel/http-stream.test.ts +4 -4
  244. package/src/kernel/pipeline-api-key.test.ts +192 -7
  245. package/src/kernel/pipeline.test.ts +2 -2
  246. package/src/kernel/pipeline.ts +4 -2
  247. package/src/kernel/plugin-elements.test.ts +1 -1
  248. package/src/kernel/plugin-needs.test.ts +1 -1
  249. package/src/kernel/ready.test.ts +2 -2
  250. package/src/kernel/resource-mount.test.ts +14 -0
  251. package/src/kernel/run-duration.test.ts +2 -2
  252. package/src/kernel/stamp-http.test.ts +79 -0
  253. package/src/kernel/stamp-http.ts +75 -0
  254. package/src/kernel/triggers.ts +52 -27
  255. package/src/manifest/types.ts +22 -3
  256. package/src/release/build-lib.ts +1 -0
  257. package/src/release/measure.ts +2 -2
  258. package/src/runtime/json-code-block.test.ts +2 -2
  259. package/src/runtime/serve.test.ts +4 -4
  260. package/src/term.test.ts +52 -4
  261. package/src/term.ts +125 -17
  262. package/src/test/reset-element-registries.ts +8 -3
  263. package/src/cli/ask-seed.test.ts +0 -96
  264. package/src/cli/ask-seed.ts +0 -82
  265. package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
  266. package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
  267. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  268. package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
  269. package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
  270. package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
  271. package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
  272. package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
  273. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  274. package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
  275. package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
  276. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  277. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  278. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  279. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  280. package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
  281. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  282. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  283. package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
  284. package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
  285. package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `fx.auth` key-management methods — session-only, attenuated, one store.
3
+ */
4
+
5
+ import {
6
+ AUTH_API_KEYS_RESOURCE,
7
+ createApiKey,
8
+ getApiKey,
9
+ listApiKeys,
10
+ revokeApiKey,
11
+ rotateApiKey,
12
+ toApiKeyPublicRow,
13
+ updateApiKey,
14
+ type ApiKeyPublicRow,
15
+ type ApiKeyStore,
16
+ type CreatedApiKey,
17
+ } from "../auth/api-keys.ts";
18
+ import { parseDurationMs } from "../elements/clock/duration.ts";
19
+ import type { EffectKind } from "./effects.ts";
20
+ import { fail, type FlowFailure } from "./errors.ts";
21
+
22
+ /**
23
+ * Identity bag on {@link FxAuth} before key-management methods attach.
24
+ * Passed into {@link createFx} / {@link attach}.
25
+ */
26
+ export interface FxAuthIdentity {
27
+ /** User-plane subject id, or null when unauthenticated. */
28
+ readonly userId: string | null;
29
+ /** Granted scopes for the live principal. */
30
+ readonly scopes: ReadonlySet<string>;
31
+ /** Whether the identity has completed verification (email / MFA). */
32
+ readonly verified?: boolean;
33
+ /** Authenticating API key id when Bearer was a key secret; otherwise null. */
34
+ readonly apiKeyId?: string | null;
35
+ }
36
+
37
+ /** Options for {@link FxAuth.createApiKey}. */
38
+ export interface FxCreateApiKeyInput {
39
+ readonly name: string;
40
+ readonly scopes: readonly string[];
41
+ readonly expiresIn?: string;
42
+ readonly ipAllowlist?: readonly string[];
43
+ readonly rateLimit?: { max: number; per: string } | null;
44
+ }
45
+
46
+ /** Options for {@link FxAuth.updateApiKey}. */
47
+ export interface FxUpdateApiKeyInput {
48
+ readonly name?: string;
49
+ readonly scopes?: readonly string[];
50
+ readonly expiresIn?: string;
51
+ readonly ipAllowlist?: readonly string[];
52
+ readonly rateLimit?: { max: number; per: string } | null;
53
+ }
54
+
55
+ /** Create result for `fx.auth.createApiKey` / `rotateApiKey`. */
56
+ export interface FxCreatedApiKey {
57
+ readonly key: ApiKeyPublicRow;
58
+ readonly secret: string;
59
+ }
60
+
61
+ /**
62
+ * Session-only key management attached onto {@link FxAuthIdentity}.
63
+ * Methods throw {@link FlowFailure} values (`Unauthorized` / `Forbidden`).
64
+ */
65
+ export interface FxAuthKeyMethods {
66
+ createApiKey(input: FxCreateApiKeyInput): Promise<FxCreatedApiKey>;
67
+ listApiKeys(): Promise<readonly ApiKeyPublicRow[]>;
68
+ revokeApiKey(id: string): Promise<ApiKeyPublicRow>;
69
+ rotateApiKey(id: string): Promise<FxCreatedApiKey>;
70
+ updateApiKey(id: string, input: FxUpdateApiKeyInput): Promise<ApiKeyPublicRow>;
71
+ }
72
+
73
+ /** Dependencies for {@link attach}. */
74
+ export interface AttachAuthKeyMethodsOptions {
75
+ readonly auth: FxAuthIdentity;
76
+ readonly store: ApiKeyStore | undefined;
77
+ readonly now: () => number;
78
+ readonly gated: <T>(kind: EffectKind, resource: string, body: () => T | Promise<T>) => Promise<T>;
79
+ }
80
+
81
+ function sessionOnly(auth: FxAuthIdentity): FlowFailure | null {
82
+ if (auth.apiKeyId) {
83
+ return fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "session_only" });
84
+ }
85
+ if (!auth.userId) {
86
+ return fail("Unauthorized", {});
87
+ }
88
+ return null;
89
+ }
90
+
91
+ function requireStore(store: ApiKeyStore | undefined): ApiKeyStore {
92
+ if (!store) {
93
+ throw fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "no_store" });
94
+ }
95
+ return store;
96
+ }
97
+
98
+ function requireOwned(
99
+ store: ApiKeyStore,
100
+ id: string,
101
+ userId: string,
102
+ ): ReturnType<typeof getApiKey> {
103
+ const row = getApiKey(store, id);
104
+ if (!row || row.creatorId !== userId) {
105
+ throw fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "not_owner" });
106
+ }
107
+ return row;
108
+ }
109
+
110
+ function expiresAtFrom(expiresIn: string | undefined, now: () => number): number | undefined {
111
+ if (expiresIn === undefined) return undefined;
112
+ return now() + parseDurationMs(expiresIn);
113
+ }
114
+
115
+ /**
116
+ * Attach create / list / revoke / rotate / update onto a live identity bag.
117
+ *
118
+ * @param options - Auth bag + store + capability gate
119
+ */
120
+ export function attach(options: AttachAuthKeyMethodsOptions): FxAuthIdentity & FxAuthKeyMethods {
121
+ const { auth, now, gated } = options;
122
+
123
+ const methods: FxAuthKeyMethods = {
124
+ createApiKey(input) {
125
+ return gated("write", AUTH_API_KEYS_RESOURCE, async () => {
126
+ const denied = sessionOnly(auth);
127
+ if (denied) throw denied;
128
+ const store = requireStore(options.store);
129
+ const created: CreatedApiKey = await createApiKey(store, {
130
+ plane: "user",
131
+ name: input.name,
132
+ scopes: input.scopes,
133
+ creatorId: auth.userId!,
134
+ creatorScopes: auth.scopes,
135
+ expiresAt: expiresAtFrom(input.expiresIn, now) ?? null,
136
+ rateLimit: input.rateLimit ?? null,
137
+ ipAllowlist: input.ipAllowlist,
138
+ now,
139
+ });
140
+ return { key: toApiKeyPublicRow(created.row), secret: created.secret };
141
+ });
142
+ },
143
+ listApiKeys() {
144
+ return gated("read", AUTH_API_KEYS_RESOURCE, async () => {
145
+ const denied = sessionOnly(auth);
146
+ if (denied) throw denied;
147
+ const store = requireStore(options.store);
148
+ return listApiKeys(store, auth.userId!).map(toApiKeyPublicRow);
149
+ });
150
+ },
151
+ revokeApiKey(id) {
152
+ return gated("write", AUTH_API_KEYS_RESOURCE, async () => {
153
+ const denied = sessionOnly(auth);
154
+ if (denied) throw denied;
155
+ const store = requireStore(options.store);
156
+ requireOwned(store, id, auth.userId!);
157
+ const row = revokeApiKey(store, id, now);
158
+ if (!row) throw fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "not_owner" });
159
+ return toApiKeyPublicRow(row);
160
+ });
161
+ },
162
+ rotateApiKey(id) {
163
+ return gated("write", AUTH_API_KEYS_RESOURCE, async () => {
164
+ const denied = sessionOnly(auth);
165
+ if (denied) throw denied;
166
+ const store = requireStore(options.store);
167
+ requireOwned(store, id, auth.userId!);
168
+ const rotated = await rotateApiKey(store, id, { now });
169
+ if (!rotated)
170
+ throw fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "not_owner" });
171
+ return { key: toApiKeyPublicRow(rotated.row), secret: rotated.secret };
172
+ });
173
+ },
174
+ updateApiKey(id, input) {
175
+ return gated("write", AUTH_API_KEYS_RESOURCE, async () => {
176
+ const denied = sessionOnly(auth);
177
+ if (denied) throw denied;
178
+ const store = requireStore(options.store);
179
+ const existing = requireOwned(store, id, auth.userId!);
180
+ if (!existing)
181
+ throw fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "not_owner" });
182
+ const ceiling = new Set([...existing.creatorScopes].filter((s) => auth.scopes.has(s)));
183
+ const row = updateApiKey(store, id, {
184
+ ceiling,
185
+ name: input.name,
186
+ scopes: input.scopes,
187
+ expiresAt: expiresAtFrom(input.expiresIn, now),
188
+ rateLimit: input.rateLimit,
189
+ ipAllowlist: input.ipAllowlist,
190
+ });
191
+ if (!row) throw fail("Forbidden", { gate: AUTH_API_KEYS_RESOURCE, reason: "not_owner" });
192
+ return toApiKeyPublicRow(row);
193
+ });
194
+ },
195
+ };
196
+
197
+ return Object.assign(auth, methods);
198
+ }
@@ -432,7 +432,23 @@ describe("fx — wholesale swap", () => {
432
432
  t: (key) => key,
433
433
  locale: "en",
434
434
  id: () => "fixed-id",
435
- auth: { userId: "u1", scopes: new Set(["a"]) },
435
+ auth: {
436
+ userId: "u1",
437
+ scopes: new Set(["a"]),
438
+ createApiKey: async () => {
439
+ throw new Error("auth.createApiKey must not be used");
440
+ },
441
+ listApiKeys: async () => [],
442
+ revokeApiKey: async () => {
443
+ throw new Error("auth.revokeApiKey must not be used");
444
+ },
445
+ rotateApiKey: async () => {
446
+ throw new Error("auth.rotateApiKey must not be used");
447
+ },
448
+ updateApiKey: async () => {
449
+ throw new Error("auth.updateApiKey must not be used");
450
+ },
451
+ },
436
452
  operator: { id: null },
437
453
  principal: { userId: "u1", operatorId: null, scopes: new Set(["a"]) },
438
454
  tenant: { id: "t1" },
package/src/kernel/fx.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  IndexStoreFxHandle,
21
21
  KvStoreDecl,
22
22
  KvStoreFxHandle,
23
+ SelectFromBuilder,
23
24
  SelectOrderBuilder,
24
25
  SqlStoreDecl,
25
26
  StoreDecl,
@@ -27,6 +28,8 @@ import type {
27
28
  StoreRuntime,
28
29
  SqlStoreHandle,
29
30
  } from "../elements/store.ts";
31
+ import { rlsIdentityFromAuth } from "../elements/store.ts";
32
+ import type { RlsIdentity } from "../drivers/pg-rls.ts";
30
33
  import type { SqlRow } from "../drivers/types.ts";
31
34
  import type { SignalDecl, SignalRuntime } from "../elements/signal.ts";
32
35
  import type { DeadLetter, SignalEmitOptions } from "../drivers/signal-types.ts";
@@ -34,6 +37,8 @@ import type { VaultActor, VaultAdapter, VaultRuntime } from "../elements/vault.t
34
37
  import type { ChannelRuntime } from "../elements/channel.ts";
35
38
  import type { AiRuntime } from "../elements/ai.ts";
36
39
  import { parseDurationMs } from "../elements/clock/duration.ts";
40
+ import type { ApiKeyStore } from "../auth/api-keys.ts";
41
+ import type { FxAuthIdentity, FxAuthKeyMethods } from "./fx-auth-keys.ts";
37
42
  import { createCapabilityToken, type CapabilityToken } from "./capability.ts";
38
43
  import { createEffectLedger, recordEffect, reversibilityOf, type EffectLedger } from "./effects.ts";
39
44
  import { resolveDurationMs } from "./elapsed.ts";
@@ -74,6 +79,13 @@ async function loadRunsWindow(): Promise<typeof import("../runs/window.ts")> {
74
79
  /** Resource ref Flows declare to read the Runs store via {@link Fx.runs}. */
75
80
  export const RUNS_RESOURCE = "runs";
76
81
 
82
+ /**
83
+ * Resource ref Flows declare for {@link Fx.auth} key management.
84
+ * Same string as `src/auth/api-keys.ts` — defined here so the HMAC / key-store
85
+ * module stays off the edge and Store-only graphs.
86
+ */
87
+ export const AUTH_API_KEYS_RESOURCE = "auth:api-keys";
88
+
77
89
  /**
78
90
  * Capability ref for {@link Fx.deadLetters} — `signal:<name>`, never a store facet.
79
91
  *
@@ -101,13 +113,10 @@ export function resolveStoreRef(ref: NamedRef | { readonly ref: ResourceRef }):
101
113
  return ref.name as ResourceRef;
102
114
  }
103
115
 
116
+ export type { FxAuthIdentity } from "./fx-auth-keys.ts";
117
+
104
118
  /** Auth principal on the user plane. */
105
- export interface FxAuth {
106
- readonly userId: string | null;
107
- readonly scopes: ReadonlySet<string>;
108
- /** Whether the identity has completed verification (email / MFA). */
109
- readonly verified?: boolean;
110
- }
119
+ export interface FxAuth extends FxAuthIdentity, FxAuthKeyMethods {}
111
120
 
112
121
  /** Operator principal on the Console plane. */
113
122
  export interface FxOperator {
@@ -835,8 +844,10 @@ export interface CreateFxOptions {
835
844
  readonly capability?: CapabilityToken;
836
845
  /** Injectable clock for timestamps / `fx.clock.now`. */
837
846
  readonly now?: () => number;
838
- /** Auth principal. */
839
- readonly auth?: FxAuth;
847
+ /** Auth principal bag (key methods attach at create time). */
848
+ readonly auth?: FxAuthIdentity;
849
+ /** Shared API key store for {@link Fx.auth} key methods. */
850
+ readonly apiKeyStore?: ApiKeyStore;
840
851
  /** Operator principal. */
841
852
  readonly operator?: FxOperator;
842
853
  /**
@@ -911,6 +922,12 @@ export interface CreateFxOptions {
911
922
  readonly runId?: string;
912
923
  /** Reveal PII through the store runtime (requires `pii:reveal` upstream). */
913
924
  readonly revealPii?: boolean;
925
+ /** Trigger gate names for RLS (`oke.gate` = first policy/public). */
926
+ readonly rlsGateNames?: readonly string[];
927
+ /** Skip RLS stamp (Operator / `bypassGates` / operator-plane flows). */
928
+ readonly rlsBypass?: boolean;
929
+ /** Forced bag (Console / Call API). Wins over {@link rlsGateNames}. */
930
+ readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
914
931
  /**
915
932
  * Active journal session when the flow is durable. Every `fx` call is
916
933
  * recorded; `step` / `sleep` replay from the journal on resume.
@@ -993,10 +1010,16 @@ export function createFxContext(options: CreateFxOptions): FxContext {
993
1010
  }
994
1011
  const cacheStore = new Map<string, unknown>();
995
1012
 
996
- const auth: FxAuth = options.auth ?? {
997
- userId: null,
998
- scopes: new Set(),
999
- };
1013
+ // Computed stem a static import would pin HMAC / api-keys on every createFx.
1014
+ const auth: FxAuth = lazyRequire<typeof import("./fx-auth-keys.ts")>(
1015
+ import.meta.dir,
1016
+ ["fx", "auth", "keys"].join("-"),
1017
+ ).attach({
1018
+ auth: options.auth ?? { userId: null, scopes: new Set() },
1019
+ store: options.apiKeyStore,
1020
+ now,
1021
+ gated,
1022
+ });
1000
1023
  const operator: FxOperator = options.operator ?? { id: null };
1001
1024
  const tenant: FxTenant = options.tenant ?? { id: null };
1002
1025
  const defaultLocale = options.i18n?.defaultLocale ?? "en";
@@ -1025,6 +1048,19 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1025
1048
  } satisfies FxPrincipal);
1026
1049
  const journal = options.journal;
1027
1050
 
1051
+ function rlsInvokeContext(): { rls?: RlsIdentity } {
1052
+ if (options.rlsBypass === true) return {};
1053
+ if (options.rls) return { rls: options.rls };
1054
+ const identity = rlsIdentityFromAuth({
1055
+ userId: auth.userId,
1056
+ scopes: auth.scopes,
1057
+ gateNames: options.rlsGateNames ?? [],
1058
+ bypass: false,
1059
+ operator: false,
1060
+ });
1061
+ return identity ? { rls: identity } : {};
1062
+ }
1063
+
1028
1064
  async function gated<T>(
1029
1065
  kind: Parameters<CapabilityToken["assert"]>[0],
1030
1066
  resource: string,
@@ -1150,9 +1186,9 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1150
1186
  get driverId() {
1151
1187
  return cached?.driverId ?? "memory";
1152
1188
  },
1153
- select(columns?) {
1189
+ select: ((columns?: unknown) => {
1154
1190
  return {
1155
- from(table) {
1191
+ from(table: unknown) {
1156
1192
  const run = (plan: {
1157
1193
  where?: unknown;
1158
1194
  orders?: readonly unknown[];
@@ -1161,7 +1197,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1161
1197
  }): Promise<SqlRow[]> =>
1162
1198
  gatedTable("read", table, async () => {
1163
1199
  const h = await ensure();
1164
- const from = h.select(columns).from(table);
1200
+ const from = h.select(columns).from(table) as SelectFromBuilder;
1165
1201
  const filtered = plan.where === undefined ? from : from.where(plan.where);
1166
1202
  const ordered =
1167
1203
  plan.orders === undefined ? filtered : filtered.orderBy(...plan.orders);
@@ -1185,7 +1221,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1185
1221
  });
1186
1222
 
1187
1223
  return {
1188
- where(where) {
1224
+ where(where: unknown) {
1189
1225
  return {
1190
1226
  ...tail({ where }),
1191
1227
  orderBy: (...orders: readonly unknown[]) => tail({ where, orders }),
@@ -1198,13 +1234,16 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1198
1234
  offset(n: number) {
1199
1235
  return run({ offset: n });
1200
1236
  },
1201
- then(onfulfilled, onrejected) {
1237
+ then(
1238
+ onfulfilled: (value: SqlRow[]) => unknown,
1239
+ onrejected?: (reason: unknown) => unknown,
1240
+ ) {
1202
1241
  return run({}).then(onfulfilled, onrejected);
1203
1242
  },
1204
1243
  };
1205
1244
  },
1206
1245
  };
1207
- },
1246
+ }) as SqlStoreHandle["select"],
1208
1247
  insert(table) {
1209
1248
  return {
1210
1249
  values(row) {
@@ -1342,6 +1381,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1342
1381
  cache.handle = await runtime.open(decl, {
1343
1382
  effects: options.effects ?? {},
1344
1383
  revealPii: options.revealPii,
1384
+ ...rlsInvokeContext(),
1345
1385
  });
1346
1386
  }
1347
1387
  return cache.handle;
@@ -1361,6 +1401,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1361
1401
  {
1362
1402
  effects: options.effects ?? {},
1363
1403
  revealPii: options.revealPii,
1404
+ ...rlsInvokeContext(),
1364
1405
  },
1365
1406
  {
1366
1407
  gate: gated,
@@ -190,11 +190,11 @@ const charge = flow("horizontal.charge", {
190
190
  });
191
191
 
192
192
  const bindings: Binding[] = [
193
- { trigger: http.get("/ping").gate.public, flow: ping as AnyFlowDef },
193
+ { trigger: http.get("/ping").public(), flow: ping as AnyFlowDef },
194
194
  { trigger: http.get("/rate").gate(rateGate), flow: rate as AnyFlowDef },
195
- { trigger: http.post("/write").gate.public, flow: write as AnyFlowDef },
196
- { trigger: http.post("/emit").gate.public, flow: emit as AnyFlowDef },
197
- { trigger: http.post("/charge").gate.public, flow: charge as AnyFlowDef },
195
+ { trigger: http.post("/write").public(), flow: write as AnyFlowDef },
196
+ { trigger: http.post("/emit").public(), flow: emit as AnyFlowDef },
197
+ { trigger: http.post("/charge").public(), flow: charge as AnyFlowDef },
198
198
  ];
199
199
 
200
200
  const app = oke({
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Branded sentinel for `http.get()` with no path — never `"/"`.
3
+ * Lives in a leaf module so `oke()` can refuse leftovers without pulling
4
+ * the HTTP trigger constructors onto a Store-only graph.
5
+ */
6
+
7
+ /** Unresolved path token. {@link stampHttpPath} replaces it from the file tree. */
8
+ export const HTTP_PATH_PENDING = "__oke_http_path_pending__" as const;
9
+
10
+ /** Type of {@link HTTP_PATH_PENDING}. */
11
+ export type HttpPathPending = typeof HTTP_PATH_PENDING;
12
+
13
+ /**
14
+ * True when an HTTP trigger still carries the unresolved path sentinel.
15
+ *
16
+ * @param path - Trigger path
17
+ */
18
+ export function isPendingHttpPath(path: string): boolean {
19
+ return path === HTTP_PATH_PENDING;
20
+ }
@@ -3,6 +3,7 @@
3
3
  * apps do not pay for the five-verb CRUD helper.
4
4
  */
5
5
 
6
+ import { GATE_PUBLIC_NAME } from "../elements/gate/flatten.ts";
6
7
  import {
7
8
  createGateAttach,
8
9
  createHttpTrigger,
@@ -47,6 +48,9 @@ export function httpResource<P extends string>(
47
48
  { trigger: verb("DELETE", id, false), flow: ops.remove },
48
49
  ],
49
50
  gate: createGateAttach((next) => httpResource(path, ops, next, isLive), gates),
51
+ public() {
52
+ return httpResource(path, ops, [...gates, GATE_PUBLIC_NAME], isLive);
53
+ },
50
54
  live() {
51
55
  return httpResource(path, ops, gates, true);
52
56
  },
@@ -83,12 +83,12 @@ describe("fx.json.stream HTTP SSE", () => {
83
83
  test("encodes token chunks as SSE and leaves JSON flows buffered", async () => {
84
84
  const smart = ai.model("smart");
85
85
  on(
86
- http.get("/chat").gate.public,
86
+ http.get("/chat").public(),
87
87
  flow("chat.stream", {
88
88
  do: (_input, fx) => fx.json.stream(fx.stream(smart, { prompt: "hi" })),
89
89
  }),
90
90
  );
91
- on(http.get("/ping").gate.public, flow("ping", { do: () => ({ ok: true as const }) }));
91
+ on(http.get("/ping").public(), flow("ping", { do: () => ({ ok: true as const }) }));
92
92
 
93
93
  const app = oke({
94
94
  name: "http-stream",
@@ -111,7 +111,7 @@ describe("fx.json.stream HTTP SSE", () => {
111
111
  let providerAborted = false;
112
112
  const smart = ai.model("smart");
113
113
  on(
114
- http.get("/slow").gate.public,
114
+ http.get("/slow").public(),
115
115
  flow("chat.slow", {
116
116
  do: (_input, fx) => fx.json.stream(fx.stream(smart, { prompt: "long" })),
117
117
  }),
@@ -139,7 +139,7 @@ describe("fx.json.stream HTTP SSE", () => {
139
139
  await runs.open();
140
140
  const smart = ai.model("smart");
141
141
  on(
142
- http.get("/chat").gate.public,
142
+ http.get("/chat").public(),
143
143
  flow("chat.stream", {
144
144
  do: (_input, fx) => fx.json.stream(fx.stream(smart, { prompt: "hi" })),
145
145
  }),