okengine 0.13.0 → 0.15.2

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 (277) hide show
  1. package/manifest.v1.schema.json +7 -1
  2. package/package.json +4 -5
  3. package/site/content/docs/ai/mcp.mdx +27 -2
  4. package/site/content/docs/elements/ai.mdx +59 -11
  5. package/site/content/docs/elements/channel.mdx +1 -0
  6. package/site/content/docs/elements/clock.mdx +30 -12
  7. package/site/content/docs/elements/flow.mdx +9 -3
  8. package/site/content/docs/elements/gate.mdx +25 -22
  9. package/site/content/docs/elements/signal.mdx +71 -25
  10. package/site/content/docs/elements/store.mdx +87 -28
  11. package/site/content/docs/elements/vault.mdx +1 -0
  12. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  13. package/site/content/docs/get-started/index.mdx +5 -0
  14. package/site/content/docs/get-started/installation.mdx +2 -2
  15. package/site/content/docs/get-started/introduction.mdx +11 -3
  16. package/site/content/docs/get-started/meta.json +1 -1
  17. package/site/content/docs/get-started/project-structure.mdx +932 -0
  18. package/site/content/docs/reference/cli.md +13 -10
  19. package/site/content/docs/reference/client.mdx +25 -21
  20. package/site/content/docs/reference/fx.mdx +27 -22
  21. package/site/content/docs/reference/security.md +1 -1
  22. package/src/auth/bindings.ts +14 -5
  23. package/src/auth/cross-plane.ts +10 -3
  24. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  25. package/src/cli/ai-setup/apply.ts +32 -1
  26. package/src/cli/ai-setup/recommend.test.ts +25 -0
  27. package/src/cli/ai-setup/recommend.ts +8 -3
  28. package/src/cli/attach-host-console.test.ts +3 -2
  29. package/src/cli/attach-host-console.ts +3 -0
  30. package/src/cli/build.test.ts +4 -2
  31. package/src/cli/db-seed.ts +48 -15
  32. package/src/cli/db.test.ts +104 -0
  33. package/src/cli/db.ts +122 -10
  34. package/src/cli/dev-controls.test.ts +20 -0
  35. package/src/cli/dev-controls.ts +13 -0
  36. package/src/cli/dev.test.ts +15 -4
  37. package/src/cli/dev.ts +102 -29
  38. package/src/cli/ensure-drizzle-config.ts +2 -1
  39. package/src/cli/tui/DevLive.tsx +12 -2
  40. package/src/cli/tui/keys.test.ts +2 -1
  41. package/src/cli/tui/keys.ts +3 -2
  42. package/src/compiler/aot.test.ts +1 -1
  43. package/src/compiler/effects-infer.ts +58 -3
  44. package/src/compiler/extract.test.ts +458 -29
  45. package/src/compiler/extract.ts +387 -69
  46. package/src/compiler/flow-path.test.ts +96 -0
  47. package/src/compiler/flow-path.ts +173 -0
  48. package/src/compiler/generate-adopt.test.ts +112 -3
  49. package/src/compiler/generate-adopt.ts +280 -26
  50. package/src/compiler/response.ts +45 -1
  51. package/src/compiler/schema-from-ast.ts +609 -0
  52. package/src/console/server/ai.ts +5 -2
  53. package/src/console/server/bind.ts +2 -2
  54. package/src/console/server/console.test.ts +3 -0
  55. package/src/console/server/flows.ts +121 -40
  56. package/src/console/server/invoke-user-flow.test.ts +3 -1
  57. package/src/console/server/invoke-user-flow.ts +17 -2
  58. package/src/console/server/iso-at.test.ts +9 -0
  59. package/src/console/server/iso-at.ts +11 -0
  60. package/src/console/server/security-headers.ts +6 -3
  61. package/src/console/server/serve.ts +17 -2
  62. package/src/console/server/spa-proxy.test.ts +1 -0
  63. package/src/console/server/sql-catalog.ts +147 -7
  64. package/src/console/server/sql-rls.test.ts +16 -0
  65. package/src/console/server/state.ts +8 -0
  66. package/src/console/server/store.test.ts +7 -4
  67. package/src/console/server/store.ts +142 -58
  68. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  69. package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
  70. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  71. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  72. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  73. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  74. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  75. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  76. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  77. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  78. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  79. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  80. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  81. package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
  82. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  83. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  84. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  85. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  86. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  87. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  88. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  89. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  90. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  91. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  92. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  93. package/src/console/ui-next/dist/index.html +6 -6
  94. package/src/console/ui-next/seed-invoke-host.ts +2 -0
  95. package/src/console/ui-next/src/client.ts +35 -7
  96. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  97. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  98. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  99. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
  100. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
  101. package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
  102. package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
  103. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
  104. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  105. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  106. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
  107. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
  108. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  109. package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
  110. package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
  111. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  112. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  113. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  114. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  115. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  116. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  117. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  118. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  119. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  120. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  121. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  122. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  123. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  124. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  125. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  126. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  127. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  128. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  129. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  130. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  131. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  132. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  133. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  134. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  135. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  137. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
  138. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  139. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  140. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  141. package/src/docker/compose-health.test.ts +27 -0
  142. package/src/docker/compose-health.ts +19 -3
  143. package/src/docker/docker.test.ts +4 -1
  144. package/src/docker/dockerfile.ts +1 -1
  145. package/src/docker/recipes/pgdog.ts +2 -0
  146. package/src/drivers/ai-anthropic.ts +5 -0
  147. package/src/drivers/ai-ollama.ts +49 -30
  148. package/src/drivers/ai-openai-compatible.ts +57 -46
  149. package/src/drivers/ai-providers.test.ts +3 -0
  150. package/src/drivers/bun-native-completeness.test.ts +7 -9
  151. package/src/drivers/clock-postgres.ts +13 -9
  152. package/src/drivers/instances-postgres.ts +4 -6
  153. package/src/drivers/journal-postgres.ts +13 -9
  154. package/src/drivers/pg-rls.test.ts +87 -0
  155. package/src/drivers/pg-rls.ts +233 -7
  156. package/src/drivers/pglite.ts +26 -3
  157. package/src/drivers/postgres.test.ts +23 -0
  158. package/src/drivers/postgres.ts +133 -9
  159. package/src/drivers/redis.ts +11 -4
  160. package/src/drivers/signal-redis.ts +24 -14
  161. package/src/drivers/signal-types.ts +2 -1
  162. package/src/drivers/types.ts +10 -0
  163. package/src/elements/ai/declare.ts +109 -0
  164. package/src/elements/ai/errors.test.ts +5 -1
  165. package/src/elements/ai/errors.ts +30 -2
  166. package/src/elements/ai/eval.ts +4 -6
  167. package/src/elements/ai/mcp-client.test.ts +206 -0
  168. package/src/elements/ai/mcp-client.ts +362 -0
  169. package/src/elements/ai/mcp-http.ts +159 -0
  170. package/src/elements/ai/mcp-mock.ts +134 -0
  171. package/src/elements/ai/mcp-protocol.ts +234 -0
  172. package/src/elements/ai/mcp-stdio.test.ts +50 -0
  173. package/src/elements/ai/mcp-stdio.ts +212 -0
  174. package/src/elements/ai/mcp-transport.ts +70 -0
  175. package/src/elements/ai/runtime.ts +159 -29
  176. package/src/elements/ai.test.ts +139 -0
  177. package/src/elements/ai.ts +16 -0
  178. package/src/elements/clock/declare.ts +26 -1
  179. package/src/elements/clock/health.test.ts +43 -0
  180. package/src/elements/clock/runtime.ts +55 -2
  181. package/src/elements/clock/schedule.ts +71 -142
  182. package/src/elements/clock.test.ts +9 -40
  183. package/src/elements/clock.ts +6 -1
  184. package/src/elements/gate/boot.ts +5 -5
  185. package/src/elements/gate/declare.ts +36 -4
  186. package/src/elements/gate/flatten.ts +1 -1
  187. package/src/elements/gate/runtime.ts +1 -1
  188. package/src/elements/index.ts +2 -0
  189. package/src/elements/signal/declare.ts +2 -1
  190. package/src/elements/signal/runtime.ts +16 -1
  191. package/src/elements/signal.ts +1 -0
  192. package/src/elements/store/cache.test.ts +2 -0
  193. package/src/elements/store/cache.ts +3 -3
  194. package/src/elements/store/declare.ts +9 -6
  195. package/src/elements/store/emit-drizzle.ts +44 -8
  196. package/src/elements/store/resource.test.ts +1 -1
  197. package/src/elements/store/rls-identity.test.ts +95 -0
  198. package/src/elements/store/rls-identity.ts +116 -0
  199. package/src/elements/store/runtime.ts +4 -0
  200. package/src/elements/store/schema-decl.test.ts +37 -0
  201. package/src/elements/store/schema-decl.ts +161 -0
  202. package/src/elements/store/seed.ts +1 -1
  203. package/src/elements/store/sql-condition.test.ts +12 -0
  204. package/src/elements/store/sql-condition.ts +26 -2
  205. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  206. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  207. package/src/elements/store/sql-session.ts +81 -3
  208. package/src/elements/store/upsert-app.test.ts +1 -1
  209. package/src/elements/store.ts +14 -0
  210. package/src/elements/vault/builtin-adapter.ts +17 -0
  211. package/src/full.ts +2 -0
  212. package/src/http.ts +20 -3
  213. package/src/index.ts +14 -1
  214. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  215. package/src/kernel/adopt-routes.ts +30 -2
  216. package/src/kernel/app.ts +227 -77
  217. package/src/kernel/auto-cache.test.ts +8 -8
  218. package/src/kernel/auto-registry.test.ts +54 -9
  219. package/src/kernel/boot-bind/ai.ts +24 -0
  220. package/src/kernel/boot-bind/clock.ts +2 -0
  221. package/src/kernel/boot.test.ts +4 -4
  222. package/src/kernel/boot.ts +5 -3
  223. package/src/kernel/effects-stamping.test.ts +3 -3
  224. package/src/kernel/element-registries.ts +15 -5
  225. package/src/kernel/errors.ts +24 -0
  226. package/src/kernel/flow-units.ts +64 -0
  227. package/src/kernel/flow.ts +29 -17
  228. package/src/kernel/fx-dead-letters.test.ts +77 -0
  229. package/src/kernel/fx.test.ts +22 -0
  230. package/src/kernel/fx.ts +135 -6
  231. package/src/kernel/horizontal-child.ts +4 -4
  232. package/src/kernel/http-path-pending.ts +20 -0
  233. package/src/kernel/http-resource.ts +4 -0
  234. package/src/kernel/http-stream.test.ts +174 -0
  235. package/src/kernel/index.ts +2 -0
  236. package/src/kernel/pipeline.test.ts +2 -2
  237. package/src/kernel/plugin-elements.test.ts +1 -1
  238. package/src/kernel/plugin-needs.test.ts +1 -1
  239. package/src/kernel/ready.test.ts +2 -2
  240. package/src/kernel/resource-mount.test.ts +14 -0
  241. package/src/kernel/run-duration.test.ts +2 -2
  242. package/src/kernel/stamp-http.test.ts +79 -0
  243. package/src/kernel/stamp-http.ts +75 -0
  244. package/src/kernel/triggers.ts +52 -27
  245. package/src/manifest/mcp-ref.ts +88 -0
  246. package/src/manifest/types.ts +41 -2
  247. package/src/manifest/validate.test.ts +20 -0
  248. package/src/mcp/docs-server.ts +1 -1
  249. package/src/mcp/server.ts +1 -1
  250. package/src/plugins/compression.test.ts +21 -0
  251. package/src/plugins/compression.ts +1 -0
  252. package/src/release/measure.ts +2 -2
  253. package/src/runtime/bun.ts +41 -4
  254. package/src/runtime/json-code-block.test.ts +2 -2
  255. package/src/runtime/serve.test.ts +4 -4
  256. package/src/term.test.ts +52 -4
  257. package/src/term.ts +125 -17
  258. package/src/test/reset-element-registries.ts +10 -3
  259. package/src/cli/ask-seed.test.ts +0 -96
  260. package/src/cli/ask-seed.ts +0 -82
  261. package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
  262. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  263. package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
  264. package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
  265. package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
  266. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  267. package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
  268. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  269. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  270. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  271. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  272. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  273. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  274. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  275. package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
  276. package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
  277. package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
@@ -55,6 +55,7 @@ import { ToolbarTip } from "@/components/ui/toolbar-tip.tsx";
55
55
  import { buildStoreGridModel, type StoreGridRow } from "../lib/grid-model.ts";
56
56
  import { parseIndexQuery } from "../lib/index-query.ts";
57
57
  import { childCatalogKind, groupSqlChildren, isSqlCatalogChild } from "../lib/sql-catalog.ts";
58
+ import { QueryGateMenu } from "../query/query-gate-menu.tsx";
58
59
  import { TouchedByLists } from "./touched-by-section.tsx";
59
60
 
60
61
  /** Browse page sizes offered in the Rows select. */
@@ -116,6 +117,10 @@ export function BrowseSection({
116
117
  const [indexOpen, setIndexOpen] = useState(false);
117
118
  const [triggerOpen, setTriggerOpen] = useState(false);
118
119
  const [insertOpen, setInsertOpen] = useState(false);
120
+ const [rlsAs, setRlsAs] = useState<{ asGate: string | null; asUserId: string | null }>({
121
+ asGate: null,
122
+ asUserId: null,
123
+ });
119
124
 
120
125
  const debouncedIndexText = useDebouncedValue(indexText, INDEX_SEARCH_DEBOUNCE_MS);
121
126
  const indexQuery = useMemo(() => parseIndexQuery(debouncedIndexText), [debouncedIndexText]);
@@ -133,8 +138,25 @@ export function BrowseSection({
133
138
  : {}),
134
139
  ...(store.facet === "index" && indexQuery.kind === "text" ? { q: indexQuery.q, topK } : {}),
135
140
  ...(store.facet === "sql" && !piiMasked ? { revealPii: true } : {}),
141
+ ...(store.facet === "sql" && !isSqlCatalogChild(child) && rlsAs.asGate
142
+ ? { asGate: rlsAs.asGate }
143
+ : {}),
144
+ ...(store.facet === "sql" && !isSqlCatalogChild(child) && rlsAs.asUserId
145
+ ? { asUserId: rlsAs.asUserId }
146
+ : {}),
136
147
  };
137
- }, [store.ref, store.facet, child.name, tenant, tenantReady, limit, indexQuery, topK, piiMasked]);
148
+ }, [
149
+ store.ref,
150
+ store.facet,
151
+ child,
152
+ tenant,
153
+ tenantReady,
154
+ limit,
155
+ indexQuery,
156
+ topK,
157
+ piiMasked,
158
+ rlsAs,
159
+ ]);
138
160
 
139
161
  const browse = useStoreQuery(queryInput, tenantReady);
140
162
  const deleteMutation = useStoreDelete();
@@ -191,6 +213,8 @@ export function BrowseSection({
191
213
  c.key === "with_check" ||
192
214
  c.key === "command" ||
193
215
  c.key === "permissive",
216
+ ...(c.key === "origin" ? { label: "Origin" } : {}),
217
+ ...(c.key === "code" ? { label: "Code" } : {}),
194
218
  })),
195
219
  };
196
220
  }
@@ -269,6 +293,9 @@ export function BrowseSection({
269
293
 
270
294
  const toolbarExtras = (
271
295
  <>
296
+ {store.facet === "sql" && !isSqlCatalogChild(child) ? (
297
+ <QueryGateMenu manifest={manifest} value={rlsAs} onChange={setRlsAs} />
298
+ ) : null}
272
299
  <Tooltip>
273
300
  <TooltipTrigger
274
301
  render={(props) => (
@@ -576,9 +603,26 @@ export function BrowseSection({
576
603
  }
577
604
  insertOpen={insertOpen}
578
605
  onInsertOpenChange={setInsertOpen}
606
+ asGate={rlsAs.asGate}
607
+ asUserId={rlsAs.asUserId}
579
608
  onDeleteRows={
580
609
  authReadOnly || extensionCatalog ? undefined : (rows) => setDeleting(rows)
581
610
  }
611
+ onViewportContextMenu={
612
+ store.ref === "sql:oke_console"
613
+ ? undefined
614
+ : policyCatalog
615
+ ? () => setPolicyOpen(true)
616
+ : functionCatalog
617
+ ? () => setFunctionOpen(true)
618
+ : indexCatalog
619
+ ? () => setIndexOpen(true)
620
+ : triggerCatalog
621
+ ? () => setTriggerOpen(true)
622
+ : store.facet === "sql" && !isSqlCatalogChild(child)
623
+ ? () => setInsertOpen(true)
624
+ : undefined
625
+ }
582
626
  />
583
627
  </div>
584
628
  ) : null}
@@ -9,6 +9,8 @@ import {
9
9
  CheckmarkCircle02Icon,
10
10
  Folder01Icon,
11
11
  SecurityCheckIcon,
12
+ ShieldEnergyIcon,
13
+ ShieldOff,
12
14
  Timer01Icon,
13
15
  } from "@hugeicons/core-free-icons";
14
16
  import { HugeiconsIcon } from "@hugeicons/react";
@@ -24,8 +26,13 @@ import { formatDuration } from "@/features/flows/traces/format-duration.ts";
24
26
  import { cn } from "@/lib/utils";
25
27
  import { filesDriverLabel, filesDriverOrigin } from "../lib/files-origin.ts";
26
28
  import { latestReplicaLagFromRuns } from "../lib/replica-lag.ts";
27
- import { isSqlCatalogChild, storeChildLabel } from "../lib/sql-catalog.ts";
28
- import { STORE_FACET_SPECS } from "../lib/store-tree.ts";
29
+ import {
30
+ isSqlCatalogChild,
31
+ storeChildLabel,
32
+ storeChildRlsPolicyCount,
33
+ storeChildShowsRls,
34
+ } from "../lib/sql-catalog.ts";
35
+ import { resourceHeaderChildRef, STORE_FACET_SPECS } from "../lib/store-tree.ts";
29
36
  import { BrowseSection } from "./browse-section.tsx";
30
37
 
31
38
  /** Props for {@link ResourcePanel}. */
@@ -77,7 +84,12 @@ export function ResourcePanel({
77
84
  const lagTone = lagMs != null ? durationTone(lagMs) : null;
78
85
 
79
86
  const files = store.facet === "files";
80
- const showStatus = Boolean(drift || lagMs !== null || store.contentAddressed || piiCount > 0);
87
+ const showsRls = storeChildShowsRls(child);
88
+ const rlsOn = child.rls === true;
89
+ const rlsCount = storeChildRlsPolicyCount(child, manifest, store.name);
90
+ const showStatus = Boolean(
91
+ drift || lagMs !== null || store.contentAddressed || piiCount > 0 || showsRls,
92
+ );
81
93
 
82
94
  return (
83
95
  <div className="flex h-full min-h-0 flex-col overflow-hidden" data-slot="resource-panel">
@@ -124,18 +136,7 @@ export function ResourcePanel({
124
136
  <CopyInlineButton value={child.effectRef} label="Copy effect ref" />
125
137
  </>
126
138
  ) : (
127
- <>
128
- <span className="shrink-0 font-mono text-[11px] leading-none text-muted-foreground">
129
- {store.ref}
130
- </span>
131
- <span aria-hidden className="text-border">
132
- ·
133
- </span>
134
- <code className="min-w-0 truncate font-mono text-[11px] leading-none text-foreground/70">
135
- {child.effectRef}
136
- </code>
137
- <CopyInlineButton value={child.effectRef} label="Copy effect ref" />
138
- </>
139
+ <ResourceRefSubtitle storeRef={store.ref} effectRef={child.effectRef} />
139
140
  )}
140
141
  </>
141
142
  }
@@ -154,6 +155,30 @@ export function ResourcePanel({
154
155
  </StatusChip>
155
156
  ) : null}
156
157
  {store.contentAddressed ? <StatusChip>content-addressed</StatusChip> : null}
158
+ {showsRls ? (
159
+ <StatusChip
160
+ tone={rlsOn ? "emerald" : "neutral"}
161
+ icon={
162
+ <HugeiconsIcon
163
+ icon={rlsOn ? ShieldEnergyIcon : ShieldOff}
164
+ className="size-3"
165
+ aria-hidden
166
+ />
167
+ }
168
+ title={
169
+ rlsOn
170
+ ? rlsCount > 0
171
+ ? `Row-level security enabled · ${rlsCount} ${rlsCount === 1 ? "policy" : "policies"}`
172
+ : "Row-level security enabled"
173
+ : "Row-level security disabled"
174
+ }
175
+ role="status"
176
+ data-slot="resource-rls"
177
+ data-rls={rlsOn ? "on" : "off"}
178
+ >
179
+ {rlsOn && rlsCount > 0 ? `${rlsCount} RLS` : "RLS"}
180
+ </StatusChip>
181
+ ) : null}
157
182
  {piiCount > 0 ? (
158
183
  <StatusChip
159
184
  tone={piiMasked ? "sky" : "amber"}
@@ -233,6 +258,34 @@ export function ResourcePanel({
233
258
  );
234
259
  }
235
260
 
261
+ function ResourceRefSubtitle({
262
+ storeRef,
263
+ effectRef,
264
+ }: {
265
+ readonly storeRef: string;
266
+ readonly effectRef: string;
267
+ }): JSX.Element {
268
+ const childRef = resourceHeaderChildRef(storeRef, effectRef);
269
+ return (
270
+ <>
271
+ <span className="shrink-0 font-mono text-[11px] leading-none text-muted-foreground">
272
+ {storeRef}
273
+ </span>
274
+ {childRef ? (
275
+ <>
276
+ <span aria-hidden className="text-border">
277
+ ·
278
+ </span>
279
+ <code className="min-w-0 truncate font-mono text-[11px] leading-none text-foreground/70">
280
+ {childRef}
281
+ </code>
282
+ </>
283
+ ) : null}
284
+ <CopyInlineButton value={effectRef} label="Copy effect ref" />
285
+ </>
286
+ );
287
+ }
288
+
236
289
  type ChipTone = "neutral" | "emerald" | "amber" | "sky";
237
290
 
238
291
  const CHIP_TONES: Record<ChipTone, string> = {
@@ -251,6 +304,7 @@ interface StatusChipProps {
251
304
  readonly role?: string;
252
305
  readonly "data-slot"?: string;
253
306
  readonly "data-drifted"?: string;
307
+ readonly "data-rls"?: string;
254
308
  readonly pressed?: boolean;
255
309
  readonly onClick?: () => void;
256
310
  readonly ariaLabel?: string;
@@ -270,6 +324,7 @@ function StatusChip({
270
324
  role,
271
325
  "data-slot": dataSlot,
272
326
  "data-drifted": dataDrifted,
327
+ "data-rls": dataRls,
273
328
  pressed,
274
329
  onClick,
275
330
  ariaLabel,
@@ -290,13 +345,20 @@ function StatusChip({
290
345
  aria-label={ariaLabel}
291
346
  data-slot={dataSlot}
292
347
  data-drifted={dataDrifted}
348
+ data-rls={dataRls}
293
349
  onClick={onClick}
294
350
  >
295
351
  {icon}
296
352
  {children}
297
353
  </button>
298
354
  ) : (
299
- <span className={className} role={role} data-slot={dataSlot} data-drifted={dataDrifted}>
355
+ <span
356
+ className={className}
357
+ role={role}
358
+ data-slot={dataSlot}
359
+ data-drifted={dataDrifted}
360
+ data-rls={dataRls}
361
+ >
300
362
  {icon}
301
363
  {children}
302
364
  </span>
@@ -1,12 +1,19 @@
1
1
  /**
2
- * Store row detail Sheet — Fields / JSON tabs + fixed destructive footer.
2
+ * Store row detail Sheet — Fields / JSON tabs, in-sheet edit, delete.
3
3
  */
4
4
 
5
5
  import { useEffect, useMemo, useState, type JSX } from "react";
6
- import { Delete02Icon, PencilEdit01Icon } from "@hugeicons/core-free-icons";
6
+ import {
7
+ Cancel01Icon,
8
+ Delete02Icon,
9
+ PencilEdit01Icon,
10
+ Tick02Icon,
11
+ } from "@hugeicons/core-free-icons";
7
12
  import { HugeiconsIcon } from "@hugeicons/react";
8
13
  import { HighlightedJson } from "@/components/highlighted-json";
9
14
  import { Button } from "@/components/ui/button";
15
+ import { Input } from "@/components/ui/input";
16
+ import { SHEET_CONTROL, SheetError } from "@/components/ui/sheet-form.tsx";
10
17
  import {
11
18
  Sheet,
12
19
  SheetContent,
@@ -15,9 +22,15 @@ import {
15
22
  SheetTitle,
16
23
  } from "@/components/ui/sheet";
17
24
  import { cn } from "@/lib/utils";
25
+ import { useStoreEdit } from "../data/use-store-edit.ts";
18
26
  import { RevealCell } from "./reveal-cell.tsx";
19
- import { formatGridCell, type StoreGridModel, type StoreGridRow } from "../lib/grid-model.ts";
20
- import { isStorePiiMask } from "../lib/patch.ts";
27
+ import {
28
+ formatGridCell,
29
+ type StoreGridColumn,
30
+ type StoreGridModel,
31
+ type StoreGridRow,
32
+ } from "../lib/grid-model.ts";
33
+ import { isStorePiiMask, parseStoreCellDraft, sanitizeStorePatch } from "../lib/patch.ts";
21
34
  import { isRtlText } from "../lib/rtl.ts";
22
35
 
23
36
  /** Props for {@link StoreRowDetailSheet}. */
@@ -31,12 +44,13 @@ export interface StoreRowDetailSheetProps {
31
44
  readonly childName: string;
32
45
  readonly tenant?: string | null;
33
46
  readonly masked?: boolean;
34
- readonly onEditRow?: (row: StoreGridRow) => void;
47
+ readonly asGate?: string | null;
48
+ readonly asUserId?: string | null;
35
49
  readonly onDeleteRow?: (row: StoreGridRow) => void;
36
50
  }
37
51
 
38
52
  /**
39
- * Right-side detail for one grid row — Fields (Reveal/edit) and full JSON.
53
+ * Right-side detail for one grid row — Fields (Reveal / edit) and full JSON.
40
54
  *
41
55
  * @param props - Selected row + facet + mutation callbacks
42
56
  */
@@ -50,13 +64,28 @@ export function StoreRowDetailSheet({
50
64
  childName,
51
65
  tenant,
52
66
  masked = false,
53
- onEditRow,
67
+ asGate = null,
68
+ asUserId = null,
54
69
  onDeleteRow,
55
70
  }: StoreRowDetailSheetProps): JSX.Element {
71
+ const edit = useStoreEdit();
56
72
  const [tab, setTab] = useState<"fields" | "json">("fields");
73
+ const [editing, setEditing] = useState(false);
74
+ const [draft, setDraft] = useState<Record<string, string>>({});
75
+ const [error, setError] = useState<string | null>(null);
76
+
77
+ const editableColumns = useMemo(
78
+ () => model.columns.filter((col) => col.editable),
79
+ [model.columns],
80
+ );
81
+ const canEdit =
82
+ model.editable && (facet === "sql" || facet === "kv") && editableColumns.length > 0;
57
83
 
58
84
  useEffect(() => {
59
- if (open) setTab("fields");
85
+ if (!open) return;
86
+ setTab("fields");
87
+ setEditing(false);
88
+ setError(null);
60
89
  }, [open, row]);
61
90
 
62
91
  const json = useMemo(() => {
@@ -70,7 +99,45 @@ export function StoreRowDetailSheet({
70
99
 
71
100
  if (!row) return <></>;
72
101
 
73
- const canEdit = model.editable && (facet === "sql" || facet === "kv") && onEditRow !== undefined;
102
+ const startEdit = (): void => {
103
+ const next: Record<string, string> = {};
104
+ for (const col of editableColumns) {
105
+ next[col.key] = draftFromCell(col.type, row.cells[col.key]);
106
+ }
107
+ setDraft(next);
108
+ setError(null);
109
+ setEditing(true);
110
+ setTab("fields");
111
+ };
112
+
113
+ const save = async (): Promise<void> => {
114
+ setError(null);
115
+ const patch: Record<string, unknown> = {};
116
+ for (const col of editableColumns) {
117
+ const text = draft[col.key] ?? "";
118
+ if (col.pii && text.trim() === "") continue;
119
+ if (text === draftFromCell(col.type, row.cells[col.key])) continue;
120
+ patch[col.key] = parseStoreCellDraft(col.type, text);
121
+ }
122
+ if (Object.keys(patch).length === 0) {
123
+ setEditing(false);
124
+ return;
125
+ }
126
+ try {
127
+ await edit.mutateAsync({
128
+ ref: storeRef,
129
+ ...(facet === "sql" ? { child: childName, id: row.id } : { key: row.id }),
130
+ ...(tenant ? { tenant } : {}),
131
+ ...(asGate ? { asGate } : {}),
132
+ ...(asUserId ? { asUserId } : {}),
133
+ patch: sanitizeStorePatch(patch),
134
+ commit: true,
135
+ });
136
+ setEditing(false);
137
+ } catch (err) {
138
+ setError(err instanceof Error ? err.message : String(err));
139
+ }
140
+ };
74
141
 
75
142
  return (
76
143
  <Sheet open={open} onOpenChange={onOpenChange}>
@@ -79,11 +146,12 @@ export function StoreRowDetailSheet({
79
146
  showOverlay={false}
80
147
  className="gap-0 p-0 sm:max-w-md"
81
148
  data-slot="store-row-detail-sheet"
149
+ data-editing={editing ? "true" : "false"}
82
150
  >
83
151
  <SheetHeader className="gap-2 border-b border-border/50">
84
152
  <div className="flex flex-col gap-0.5 pr-8">
85
153
  <SheetTitle className="text-sm">
86
- Row <span className="font-mono">{row.id}</span>
154
+ {editing ? "Edit" : "Row"} <span className="font-mono">{row.id}</span>
87
155
  </SheetTitle>
88
156
  <SheetDescription className="font-mono text-[11px]">
89
157
  {storeRef}
@@ -106,6 +174,7 @@ export function StoreRowDetailSheet({
106
174
  {model.columns.map((col) => {
107
175
  const value = row.cells[col.key];
108
176
  const maskedPii = col.pii && masked && isStorePiiMask(value);
177
+ const fieldEditable = editing && col.editable;
109
178
  return (
110
179
  <li key={col.key} className="flex items-baseline gap-3 py-1.5">
111
180
  <span className="flex w-32 shrink-0 items-center gap-1.5">
@@ -130,7 +199,30 @@ export function StoreRowDetailSheet({
130
199
  </span>
131
200
  ) : null}
132
201
  </span>
133
- {maskedPii ? (
202
+ {fieldEditable ? (
203
+ col.type === "json" ? (
204
+ <textarea
205
+ value={draft[col.key] ?? ""}
206
+ onChange={(event) =>
207
+ setDraft((prev) => ({ ...prev, [col.key]: event.target.value }))
208
+ }
209
+ aria-label={col.key}
210
+ rows={3}
211
+ className="min-h-16 min-w-0 flex-1 rounded-none border-0 bg-transparent font-mono text-[11px] outline-none"
212
+ />
213
+ ) : (
214
+ <Input
215
+ value={draft[col.key] ?? ""}
216
+ onChange={(event) =>
217
+ setDraft((prev) => ({ ...prev, [col.key]: event.target.value }))
218
+ }
219
+ type={col.type === "integer" || col.type === "number" ? "number" : "text"}
220
+ aria-label={col.key}
221
+ flat
222
+ className={cn(SHEET_CONTROL, "min-w-0 flex-1 px-0 font-mono")}
223
+ />
224
+ )
225
+ ) : maskedPii ? (
134
226
  <RevealCell
135
227
  refName={storeRef}
136
228
  child={childName}
@@ -162,13 +254,42 @@ export function StoreRowDetailSheet({
162
254
  )}
163
255
  </div>
164
256
 
257
+ {error ? <SheetError slot="row-detail-error">{error}</SheetError> : null}
258
+
165
259
  <div className="mt-auto flex items-center justify-between gap-2 border-t border-border/50 px-4 py-2.5">
166
- {canEdit ? (
260
+ {editing ? (
261
+ <div className="flex items-center gap-2">
262
+ <Button
263
+ type="button"
264
+ variant="outline"
265
+ size="sm"
266
+ onClick={() => {
267
+ setEditing(false);
268
+ setError(null);
269
+ }}
270
+ disabled={edit.isPending}
271
+ data-slot="row-detail-cancel"
272
+ >
273
+ <HugeiconsIcon icon={Cancel01Icon} className="size-3.5" aria-hidden />
274
+ Cancel
275
+ </Button>
276
+ <Button
277
+ type="button"
278
+ size="sm"
279
+ onClick={() => void save()}
280
+ disabled={edit.isPending}
281
+ data-slot="row-detail-save"
282
+ >
283
+ <HugeiconsIcon icon={Tick02Icon} className="size-3.5" aria-hidden />
284
+ {edit.isPending ? "Saving…" : "Save"}
285
+ </Button>
286
+ </div>
287
+ ) : canEdit ? (
167
288
  <Button
168
289
  type="button"
169
290
  variant="outline"
170
291
  size="sm"
171
- onClick={() => onEditRow(row)}
292
+ onClick={startEdit}
172
293
  data-slot="row-detail-edit"
173
294
  >
174
295
  <HugeiconsIcon icon={PencilEdit01Icon} className="size-3.5" aria-hidden />
@@ -177,7 +298,7 @@ export function StoreRowDetailSheet({
177
298
  ) : (
178
299
  <span />
179
300
  )}
180
- {onDeleteRow ? (
301
+ {!editing && onDeleteRow ? (
181
302
  <Button
182
303
  type="button"
183
304
  variant="destructive"
@@ -195,6 +316,26 @@ export function StoreRowDetailSheet({
195
316
  );
196
317
  }
197
318
 
319
+ /** Seed / compare an edit draft without turning `null` into the display em dash. */
320
+ function draftFromCell(type: StoreGridColumn["type"], raw: unknown): string {
321
+ if (raw === null || raw === undefined) return "";
322
+ if (isStorePiiMask(raw)) return "";
323
+ if (type === "json") {
324
+ try {
325
+ return JSON.stringify(raw, null, 2);
326
+ } catch {
327
+ return "";
328
+ }
329
+ }
330
+ if (typeof raw === "string") return raw;
331
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
332
+ try {
333
+ return JSON.stringify(raw);
334
+ } catch {
335
+ return "";
336
+ }
337
+ }
338
+
198
339
  function TabButton({
199
340
  active,
200
341
  onClick,