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
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
- import { splitContractInput } from "./contract-input.ts";
6
+ import { pickSeedFields, splitCallApiInput, splitContractInput } from "./contract-input.ts";
7
7
  import { fieldsFromSchema } from "./fields-from-schema.ts";
8
8
 
9
9
  const LIST_IN = fieldsFromSchema({
@@ -45,3 +45,208 @@ describe("splitContractInput", () => {
45
45
  expect(split.fields.map((f) => f.name)).toEqual(["title"]);
46
46
  });
47
47
  });
48
+
49
+ describe("splitCallApiInput", () => {
50
+ const idOnly = fieldsFromSchema({
51
+ type: "object",
52
+ required: ["id"],
53
+ properties: { id: { type: "string", minLength: 1 } },
54
+ });
55
+ const list = LIST_IN;
56
+ const nestedList = fieldsFromSchema({
57
+ type: "object",
58
+ required: ["id"],
59
+ properties: {
60
+ id: { type: "string", minLength: 1 },
61
+ q: { type: "string" },
62
+ limit: { type: "integer" },
63
+ },
64
+ });
65
+ const create = fieldsFromSchema({
66
+ type: "object",
67
+ required: ["title"],
68
+ properties: { title: { type: "string" }, spaceKey: { type: "string" } },
69
+ });
70
+ const assign = fieldsFromSchema({
71
+ type: "object",
72
+ required: ["id", "assigneeEmail"],
73
+ properties: { id: { type: "string" }, assigneeEmail: { type: "string" } },
74
+ });
75
+ const patch = fieldsFromSchema({
76
+ type: "object",
77
+ required: ["id"],
78
+ properties: {
79
+ id: { type: "string" },
80
+ title: { type: "string" },
81
+ status: { type: "string" },
82
+ },
83
+ });
84
+ const putDraft = fieldsFromSchema({
85
+ type: "object",
86
+ required: ["id", "title"],
87
+ properties: { id: { type: "string" }, title: { type: "string" }, body: { type: "string" } },
88
+ });
89
+ const search = fieldsFromSchema({
90
+ type: "object",
91
+ required: ["q"],
92
+ properties: { q: { type: "string" }, limit: { type: "integer" } },
93
+ });
94
+
95
+ const cases: readonly {
96
+ readonly name: string;
97
+ readonly method: string;
98
+ readonly pathParams: readonly string[];
99
+ readonly fields: ReturnType<typeof fieldsFromSchema>;
100
+ readonly path: readonly string[];
101
+ readonly query: readonly string[];
102
+ readonly body: readonly string[];
103
+ }[] = [
104
+ {
105
+ name: "GET tasks.get",
106
+ method: "GET",
107
+ pathParams: ["id"],
108
+ fields: idOnly,
109
+ path: ["id"],
110
+ query: [],
111
+ body: [],
112
+ },
113
+ {
114
+ name: "GET tasks.list",
115
+ method: "GET",
116
+ pathParams: [],
117
+ fields: list,
118
+ path: [],
119
+ query: ["q", "teamKey", "limit", "offset", "cursor", "orderBy", "order"],
120
+ body: [],
121
+ },
122
+ {
123
+ name: "GET comments.list /tasks/:id/comments",
124
+ method: "GET",
125
+ pathParams: ["id"],
126
+ fields: nestedList,
127
+ path: ["id"],
128
+ query: ["q", "limit"],
129
+ body: [],
130
+ },
131
+ {
132
+ name: "HEAD main.health",
133
+ method: "HEAD",
134
+ pathParams: [],
135
+ fields: [],
136
+ path: [],
137
+ query: [],
138
+ body: [],
139
+ },
140
+ {
141
+ name: "DELETE tasks.delete",
142
+ method: "DELETE",
143
+ pathParams: ["id"],
144
+ fields: idOnly,
145
+ path: ["id"],
146
+ query: [],
147
+ body: [],
148
+ },
149
+ {
150
+ name: "POST tasks.create",
151
+ method: "POST",
152
+ pathParams: [],
153
+ fields: create,
154
+ path: [],
155
+ query: [],
156
+ body: ["title", "spaceKey"],
157
+ },
158
+ {
159
+ name: "POST tasks.assign /tasks/:id/assign",
160
+ method: "POST",
161
+ pathParams: ["id"],
162
+ fields: assign,
163
+ path: ["id"],
164
+ query: [],
165
+ body: ["assigneeEmail"],
166
+ },
167
+ {
168
+ name: "POST tasks.archive path-only",
169
+ method: "POST",
170
+ pathParams: ["id"],
171
+ fields: idOnly,
172
+ path: ["id"],
173
+ query: [],
174
+ body: [],
175
+ },
176
+ {
177
+ name: "PATCH tasks.update",
178
+ method: "PATCH",
179
+ pathParams: ["id"],
180
+ fields: patch,
181
+ path: ["id"],
182
+ query: [],
183
+ body: ["title", "status"],
184
+ },
185
+ {
186
+ name: "PUT drafts.save",
187
+ method: "PUT",
188
+ pathParams: ["id"],
189
+ fields: putDraft,
190
+ path: ["id"],
191
+ query: [],
192
+ body: ["title", "body"],
193
+ },
194
+ {
195
+ name: "QUERY search.query",
196
+ method: "QUERY",
197
+ pathParams: [],
198
+ fields: search,
199
+ path: [],
200
+ query: [],
201
+ body: ["q", "limit"],
202
+ },
203
+ {
204
+ name: "OPTIONS leftover stays body",
205
+ method: "OPTIONS",
206
+ pathParams: [],
207
+ fields: search,
208
+ path: [],
209
+ query: [],
210
+ body: ["q", "limit"],
211
+ },
212
+ ];
213
+
214
+ for (const row of cases) {
215
+ test(row.name, () => {
216
+ const split = splitCallApiInput(row.fields, {
217
+ http: true,
218
+ method: row.method,
219
+ pathParams: row.pathParams,
220
+ });
221
+ expect(split.path).toEqual([...row.path]);
222
+ expect(split.query.map((f) => f.name)).toEqual([...row.query]);
223
+ expect(split.body.map((f) => f.name)).toEqual([...row.body]);
224
+ });
225
+ }
226
+
227
+ test("non-HTTP keeps the full input as body", () => {
228
+ const split = splitCallApiInput(idOnly);
229
+ expect(split.path).toEqual([]);
230
+ expect(split.query).toEqual([]);
231
+ expect(split.body.map((f) => f.name)).toEqual(["id"]);
232
+ });
233
+
234
+ test("Signal / CDC / Call-only never grow Path params from an id field", () => {
235
+ for (const kind of ["signal", "cdc", "internal"] as const) {
236
+ const split = splitCallApiInput(idOnly, { http: false, method: kind });
237
+ expect(split.path).toEqual([]);
238
+ expect(split.query).toEqual([]);
239
+ expect(split.body.map((f) => f.name)).toEqual(["id"]);
240
+ }
241
+ });
242
+ });
243
+
244
+ describe("pickSeedFields", () => {
245
+ test("keeps only named section keys", () => {
246
+ const title = fieldsFromSchema({
247
+ type: "object",
248
+ properties: { title: { type: "string" } },
249
+ });
250
+ expect(pickSeedFields({ id: "x", title: "n" }, title)).toEqual({ title: "n" });
251
+ });
252
+ });
@@ -2,6 +2,7 @@
2
2
  * Split flow input into query/path parameters vs body / filter fields.
3
3
  */
4
4
 
5
+ import { httpMethodHasBody } from "@/features/flows/traces/http-method.ts";
5
6
  import type { FormField } from "./fields-from-schema.ts";
6
7
 
7
8
  /** List / page / sort keys — query parameters, not resource fields. */
@@ -66,3 +67,57 @@ function pathParamField(name: string): FormField {
66
67
  required: true,
67
68
  };
68
69
  }
70
+
71
+ /** Call API request sections — path vs query vs JSON body. */
72
+ export type CallApiInputSplit = {
73
+ readonly path: readonly string[];
74
+ readonly query: readonly FormField[];
75
+ readonly body: readonly FormField[];
76
+ };
77
+
78
+ /**
79
+ * Partition `flow.in` the way the typed client puts fields on the wire.
80
+ *
81
+ * Path tokens never appear in query or body. `GET` / `HEAD` / `DELETE`
82
+ * leftover fields are query. Other HTTP verbs and non-HTTP triggers keep a
83
+ * JSON body (minus path tokens).
84
+ *
85
+ * @param input - `flow.in` fields
86
+ * @param options - Route tokens + HTTP method
87
+ */
88
+ export function splitCallApiInput(
89
+ input: readonly FormField[],
90
+ options: {
91
+ readonly pathParams?: readonly string[];
92
+ readonly method?: string | null;
93
+ readonly http?: boolean;
94
+ } = {},
95
+ ): CallApiInputSplit {
96
+ const pathParams = options.pathParams ?? [];
97
+ const pathSet = new Set(pathParams);
98
+ if (options.http !== true) {
99
+ return { path: [], query: [], body: input };
100
+ }
101
+ const leftover = input.filter((field) => !pathSet.has(field.name));
102
+ if (!httpMethodHasBody(options.method)) {
103
+ return { path: pathParams, query: leftover, body: [] };
104
+ }
105
+ return { path: pathParams, query: [], body: leftover };
106
+ }
107
+
108
+ /**
109
+ * Keep schema seed keys that belong to a Call API section.
110
+ *
111
+ * @param seed - Full `flow.in` seed
112
+ * @param fields - Section fields
113
+ */
114
+ export function pickSeedFields(
115
+ seed: Record<string, unknown>,
116
+ fields: readonly FormField[],
117
+ ): Record<string, unknown> {
118
+ const out: Record<string, unknown> = {};
119
+ for (const field of fields) {
120
+ if (Object.hasOwn(seed, field.name)) out[field.name] = seed[field.name];
121
+ }
122
+ return out;
123
+ }
@@ -110,5 +110,5 @@ export interface VaultRotateMasterResult {
110
110
  readonly kekVersion: number;
111
111
  readonly remaining: number;
112
112
  readonly masterKey: string | null;
113
- readonly at: number;
113
+ readonly at: string;
114
114
  }
@@ -25,6 +25,7 @@ describe("consoleDocumentTitle", () => {
25
25
  `Observability · ${CONSOLE_DOCUMENT_TITLE}`,
26
26
  );
27
27
  expect(consoleDocumentTitle("/vault")).toBe(`Vault · ${CONSOLE_DOCUMENT_TITLE}`);
28
+ expect(consoleDocumentTitle("/access")).toBe(`Access · ${CONSOLE_DOCUMENT_TITLE}`);
28
29
  expect(consoleDocumentTitle("/monitoring")).toBe(`Observability · ${CONSOLE_DOCUMENT_TITLE}`);
29
30
  });
30
31
 
@@ -7,7 +7,7 @@ export const CONSOLE_DOCUMENT_TITLE = "okengine Console";
7
7
 
8
8
  /** Operator-facing `meta name="description"` / Open Graph copy. */
9
9
  export const CONSOLE_DOCUMENT_DESCRIPTION =
10
- "Operator Console for okengine — Flows, Store, Observability, and Vault.";
10
+ "Operator Console for okengine — Flows, Store, Observability, Vault, and Access.";
11
11
 
12
12
  const PAGE_TITLES: Record<string, string> = {
13
13
  "/overview": "Overview",
@@ -15,6 +15,7 @@ const PAGE_TITLES: Record<string, string> = {
15
15
  "/store": "Store",
16
16
  "/observability": "Observability",
17
17
  "/vault": "Vault",
18
+ "/access": "Access",
18
19
  "/monitoring": "Observability",
19
20
  };
20
21
 
@@ -12,6 +12,7 @@ describe("last-module-search", () => {
12
12
  expect(isConsoleModulePath("/overview")).toBe(true);
13
13
  expect(isConsoleModulePath("/flows")).toBe(true);
14
14
  expect(isConsoleModulePath("/observability")).toBe(true);
15
+ expect(isConsoleModulePath("/access")).toBe(true);
15
16
  expect(isConsoleModulePath("/monitoring")).toBe(false);
16
17
  expect(isConsoleModulePath("/units")).toBe(false);
17
18
  expect(isConsoleModulePath("/")).toBe(false);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Remember each Console module's last URL search so sidebar hops
3
- * restore Overview / Flows / Store / Observability / Vault instead of wiping them.
3
+ * restore Overview / Flows / Store / Observability / Vault / Access instead of wiping them.
4
4
  */
5
5
 
6
6
  /** Authenticated Console modules that own URL search. */
@@ -10,6 +10,7 @@ export const CONSOLE_MODULE_PATHS = [
10
10
  "/store",
11
11
  "/observability",
12
12
  "/vault",
13
+ "/access",
13
14
  ] as const;
14
15
 
15
16
  /** Pathname for a Console module. */
@@ -21,6 +21,7 @@ describe("consoleShortcut", () => {
21
21
  expect(consoleShortcut("store")).toEqual(modChord("3"));
22
22
  expect(consoleShortcut("observability")).toEqual(modChord("4"));
23
23
  expect(consoleShortcut("vault")).toEqual(modChord("5"));
24
+ expect(consoleShortcut("access")).toEqual(modChord("6"));
24
25
  });
25
26
 
26
27
  test("digits map to module paths", () => {
@@ -29,6 +30,7 @@ describe("consoleShortcut", () => {
29
30
  expect(MODULE_DIGIT_PATH["3"]).toBe("/store");
30
31
  expect(MODULE_DIGIT_PATH["4"]).toBe("/observability");
31
32
  expect(MODULE_DIGIT_PATH["5"]).toBe("/vault");
33
+ expect(MODULE_DIGIT_PATH["6"]).toBe("/access");
32
34
  });
33
35
  });
34
36
 
@@ -63,6 +63,7 @@ export type ConsoleShortcutId =
63
63
  | "flows"
64
64
  | "store"
65
65
  | "vault"
66
+ | "access"
66
67
  | "observability";
67
68
 
68
69
  const MODULE_DIGIT: Readonly<
@@ -73,6 +74,7 @@ const MODULE_DIGIT: Readonly<
73
74
  store: "3",
74
75
  observability: "4",
75
76
  vault: "5",
77
+ access: "6",
76
78
  };
77
79
 
78
80
  /**
@@ -94,6 +96,7 @@ export const MODULE_DIGIT_PATH: Readonly<Record<string, ConsoleModulePath>> = {
94
96
  "3": "/store",
95
97
  "4": "/observability",
96
98
  "5": "/vault",
99
+ "6": "/access",
97
100
  };
98
101
 
99
102
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Console router — code-based TanStack Router (Vite SPA).
3
- * Pre-auth `/` and authenticated shell `/overview` | `/flows` | `/store` | `/vault` | `/observability`.
3
+ * Pre-auth `/` and authenticated shell `/overview` | `/flows` | `/store` | `/vault` | `/access` | `/observability`.
4
4
  * `/monitoring` is a one-shot search-preserving redirect to `/observability`.
5
5
  * Authenticated pages are `lazyRouteComponent` so Vite splits them out of the entry.
6
6
  * Any other path is a 404 — no legacy rewrites.
@@ -30,6 +30,7 @@ import { validateStoreSearch } from "./features/store/state/store-selection.ts";
30
30
  import { validateUnitsSearch } from "./features/units/state/units-selection.ts";
31
31
  import { validateObservabilitySearch } from "./features/observability/state/observability-selection.ts";
32
32
  import { validateVaultSearch } from "./features/vault/state/vault-selection.ts";
33
+ import { validateAccessSearch } from "./features/access/state/access-selection.ts";
33
34
  import { DocumentTitle } from "./components/document-title.tsx";
34
35
  import { ShellLayout } from "./components/shell/shell-layout.tsx";
35
36
 
@@ -120,6 +121,13 @@ const vaultRoute = createRoute({
120
121
  component: lazyRouteComponent(() => import("./features/vault/vault-page.tsx"), "VaultPage"),
121
122
  });
122
123
 
124
+ const accessRoute = createRoute({
125
+ getParentRoute: () => authenticatedRoute,
126
+ path: "/access",
127
+ validateSearch: validateAccessSearch,
128
+ component: lazyRouteComponent(() => import("./features/access/access-page.tsx"), "AccessPage"),
129
+ });
130
+
123
131
  const observabilityRoute = createRoute({
124
132
  getParentRoute: () => authenticatedRoute,
125
133
  path: "/observability",
@@ -146,6 +154,7 @@ const routeTree = rootRoute.addChildren([
146
154
  flowsRoute,
147
155
  storeRoute,
148
156
  vaultRoute,
157
+ accessRoute,
149
158
  observabilityRoute,
150
159
  monitoringRedirectRoute,
151
160
  ]),
@@ -7,6 +7,7 @@
7
7
  import { AUTH_TABLES } from "../../auth/tables.ts";
8
8
  import { resolveAuthSchema } from "../../auth/schema.ts";
9
9
  import type { DeclaredColumn, Table } from "../../manifest/types.ts";
10
+ import { KEEL_LADDER_GRANTS, KEEL_SCOPES } from "../server/dev-identities.ts";
10
11
 
11
12
  const PII_COLUMNS = new Set([
12
13
  "email",
@@ -163,9 +164,63 @@ export const UI_NEXT_SEED_APP_SYSTEM_TABLES: Record<string, Table> = {
163
164
  const NOW = 1_723_622_400_000;
164
165
  const LATER = NOW + 30 * 24 * 60 * 60 * 1000;
165
166
 
166
- const PEOPLE = [
167
- { id: "user_demo", email: "demo@example.com", name: "Demo User", status: "active" },
168
- { id: "user_member", email: "member@example.com", name: "Member", status: "active" },
167
+ /** Ten-rung owner → guest ladder (demo + member keep historic ids). */
168
+ const LADDER = [
169
+ {
170
+ id: "user_demo",
171
+ email: "demo@example.com",
172
+ name: "Demo User",
173
+ status: "active",
174
+ role: "owner",
175
+ },
176
+ { id: "user_admin", email: "admin@example.com", name: "Admin", status: "active", role: "admin" },
177
+ {
178
+ id: "user_workspace",
179
+ email: "workspace@example.com",
180
+ name: "Workspace Admin",
181
+ status: "active",
182
+ role: "workspace_admin",
183
+ },
184
+ {
185
+ id: "user_pm",
186
+ email: "pm@example.com",
187
+ name: "Project Manager",
188
+ status: "active",
189
+ role: "project_manager",
190
+ },
191
+ { id: "user_lead", email: "lead@example.com", name: "Lead", status: "active", role: "lead" },
192
+ {
193
+ id: "user_dev",
194
+ email: "developer@example.com",
195
+ name: "Developer",
196
+ status: "active",
197
+ role: "developer",
198
+ },
199
+ {
200
+ id: "user_writer",
201
+ email: "writer@example.com",
202
+ name: "Contributor",
203
+ status: "active",
204
+ role: "contributor",
205
+ },
206
+ {
207
+ id: "user_member",
208
+ email: "member@example.com",
209
+ name: "Member",
210
+ status: "active",
211
+ role: "member",
212
+ },
213
+ {
214
+ id: "user_commenter",
215
+ email: "commenter@example.com",
216
+ name: "Commenter",
217
+ status: "active",
218
+ role: "commenter",
219
+ },
220
+ { id: "user_guest", email: "guest@example.com", name: "Guest", status: "active", role: "guest" },
221
+ ] as const;
222
+
223
+ const STORY = [
169
224
  { id: "idn_aria", email: "aria@keel.dev", name: "Aria Chen", status: "active" },
170
225
  { id: "idn_ben", email: "ben@keel.dev", name: "Ben Okonkwo", status: "active" },
171
226
  { id: "idn_cai", email: "cai@keel.dev", name: "Cai Moreno", status: "active" },
@@ -185,6 +240,8 @@ const PEOPLE = [
185
240
  { id: "idn_zio", email: "zio@keel.dev", name: "Zio Hart", status: "active" },
186
241
  ] as const;
187
242
 
243
+ const PEOPLE = [...LADDER, ...STORY];
244
+
188
245
  /**
189
246
  * Seeded rows for app-plane system tables (never secret values).
190
247
  */
@@ -209,49 +266,43 @@ export const UI_NEXT_SEED_APP_SYSTEM_ROWS: Readonly<Record<string, readonly obje
209
266
  updated_at: NOW,
210
267
  })),
211
268
  [AUTH_TABLES.identityRoles]: [
212
- ...PEOPLE.map((p) => ({
269
+ ...LADDER.filter((p) => p.role !== "guest").map((p) => ({
270
+ id: `ir_${p.id}_${p.role}`,
271
+ identity_id: p.id,
272
+ role_id: `role_${p.role}`,
273
+ })),
274
+ ...STORY.map((p) => ({
213
275
  id: `ir_${p.id}_member`,
214
276
  identity_id: p.id,
215
277
  role_id: "role_member",
216
278
  })),
217
- { id: "ir_idn_aria_staff", identity_id: "idn_aria", role_id: "role_staff" },
218
- { id: "ir_user_demo_staff", identity_id: "user_demo", role_id: "role_staff" },
219
- ],
220
- [AUTH_TABLES.roles]: [
221
- { id: "role_member", name: "member", plane: "user", description: "Signed-in workspace member" },
222
- { id: "role_staff", name: "staff", plane: "user", description: "May accept triage" },
223
- {
224
- id: "role_issue_write",
225
- name: "issue:write",
226
- plane: "user",
227
- description: "May create and update issues",
228
- },
229
- {
230
- id: "role_team_admin",
231
- name: "team:admin",
232
- plane: "user",
233
- description: "May create teams and close cycles",
234
- },
235
- ],
236
- [AUTH_TABLES.roleGrants]: [
237
- { id: "rg_1", role_id: "role_member", action: "member" },
238
- { id: "rg_2", role_id: "role_staff", action: "member" },
239
- { id: "rg_3", role_id: "role_staff", action: "triage:accept" },
240
- { id: "rg_4", role_id: "role_issue_write", action: "issue:write" },
241
- { id: "rg_5", role_id: "role_issue_write", action: "project:admin" },
242
- { id: "rg_6", role_id: "role_issue_write", action: "comment:write" },
243
- { id: "rg_7", role_id: "role_issue_write", action: "files:write" },
244
- { id: "rg_8", role_id: "role_team_admin", action: "team:admin" },
245
- { id: "rg_9", role_id: "role_team_admin", action: "member:admin" },
246
- { id: "rg_10", role_id: "role_team_admin", action: "webhook:admin" },
279
+ { id: "ir_idn_aria_pm", identity_id: "idn_aria", role_id: "role_project_manager" },
247
280
  ],
281
+ [AUTH_TABLES.roles]: Object.keys(KEEL_LADDER_GRANTS).map((role) => ({
282
+ id: `role_${role}`,
283
+ name: role,
284
+ plane: "user",
285
+ description:
286
+ role === "owner"
287
+ ? "Full workspace authorization"
288
+ : role === "guest"
289
+ ? "Least authorization — no write scopes"
290
+ : `Keel ${role.replaceAll("_", " ")}`,
291
+ })),
292
+ [AUTH_TABLES.roleGrants]: Object.entries(KEEL_LADDER_GRANTS).flatMap(([role, actions]) =>
293
+ actions.map((action, i) => ({
294
+ id: `rg_${role}_${i + 1}`,
295
+ role_id: `role_${role}`,
296
+ action,
297
+ })),
298
+ ),
248
299
  [AUTH_TABLES.apiKeys]: [
249
300
  {
250
301
  id: "key_keel_ci",
251
302
  plane: "user",
252
303
  hash: "sha256$keel_ci",
253
304
  name: "Keel CI",
254
- scopes: "member,issue:write",
305
+ scopes: "member,task:write",
255
306
  expires_at: null,
256
307
  created_at: NOW,
257
308
  last_used_at: NOW - 3_600_000,
@@ -279,7 +330,7 @@ export const UI_NEXT_SEED_APP_SYSTEM_ROWS: Readonly<Record<string, readonly obje
279
330
  created_at: NOW,
280
331
  expires_at: LATER,
281
332
  last_active_at: NOW,
282
- scopes: "member,issue:write",
333
+ scopes: KEEL_LADDER_GRANTS.project_manager.join(","),
283
334
  audience: "oke-app",
284
335
  },
285
336
  {
@@ -291,7 +342,7 @@ export const UI_NEXT_SEED_APP_SYSTEM_ROWS: Readonly<Record<string, readonly obje
291
342
  created_at: NOW,
292
343
  expires_at: LATER,
293
344
  last_active_at: NOW,
294
- scopes: "member,issue:write",
345
+ scopes: KEEL_SCOPES.join(","),
295
346
  audience: "oke-app",
296
347
  },
297
348
  ],