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
@@ -27,12 +27,15 @@ import { maskPiiValue, maskWideEventForConsole, piiFieldNamesFromManifest } from
27
27
  import { ConsoleRunsQueryError, RUNS_QUERY_PII_GAP, RUNS_QUERY_TIMEOUT_MS } from "./runs-query.ts";
28
28
  import { DuckQueryTimeoutError } from "../../runs/duckdb.ts";
29
29
  import { ClockResourceNotFoundError, ScheduleNotOverridableError } from "./clock.ts";
30
+ import { isoAt } from "./iso-at.ts";
30
31
  import { createFileDiff, emitStructuralDiff } from "./structural.ts";
31
32
  import type { ConsoleState } from "./state.ts";
32
33
  import { consoleFailureMessage } from "./i18n.ts";
33
34
  import { CONSOLE_PASSWORD_POLICY } from "../password-policy.ts";
34
35
  import { PUBLIC_CONSOLE_FLOWS } from "./public-flows.ts";
35
36
  import { resolveInvokeAs } from "./invoke-user-flow.ts";
37
+ import { resolveRlsIdentity } from "../../elements/store.ts";
38
+ import type { RlsIdentity } from "../../drivers/pg-rls.ts";
36
39
  import { isKnownConsoleSqlGate, StoreFileNotFoundError, tenancyDeclared } from "./store.ts";
37
40
  import {
38
41
  PG_STAT_STATEMENTS_NOT_CREATED,
@@ -152,7 +155,7 @@ const TracesReplayOut = z.object({
152
155
  ok: z.literal(true),
153
156
  rootId: z.string(),
154
157
  dryRun: z.boolean(),
155
- at: z.number(),
158
+ at: z.string(),
156
159
  flow: z.string(),
157
160
  });
158
161
 
@@ -260,7 +263,7 @@ const SignalsReplayOut = z.object({
260
263
  messageId: z.string().optional(),
261
264
  }),
262
265
  ),
263
- at: z.number(),
266
+ at: z.string(),
264
267
  });
265
268
 
266
269
  const SignalsDiscardIn = z.object({
@@ -273,7 +276,7 @@ const SignalsDiscardIn = z.object({
273
276
  const SignalsDiscardOut = z.object({
274
277
  ok: z.literal(true),
275
278
  discarded: z.number(),
276
- at: z.number(),
279
+ at: z.string(),
277
280
  });
278
281
 
279
282
  const ActionPingIn = z.object({
@@ -283,7 +286,7 @@ const ActionPingIn = z.object({
283
286
  const ActionPingOut = z.object({
284
287
  ok: z.literal(true),
285
288
  note: z.string().optional(),
286
- at: z.number(),
289
+ at: z.string(),
287
290
  });
288
291
 
289
292
  const StructuralIn = z.object({
@@ -354,6 +357,13 @@ const InvokeOut = z.object({
354
357
  durationMs: z.number().optional(),
355
358
  peakTier: z.enum(["none", "reads", "writes", "emits", "external", "capabilities"]),
356
359
  auditedAt: z.number(),
360
+ rls: z
361
+ .object({
362
+ gate: z.string().nullable(),
363
+ userId: z.string(),
364
+ applied: z.boolean(),
365
+ })
366
+ .optional(),
357
367
  });
358
368
 
359
369
  const SetupClosed = z.object({ reason: z.string() });
@@ -504,7 +514,7 @@ const ChannelRevealOut = z.object({
504
514
  ok: z.literal(true),
505
515
  id: z.string(),
506
516
  to: z.string(),
507
- at: z.number(),
517
+ at: z.string(),
508
518
  });
509
519
 
510
520
  const ChannelSendTestIn = z.object({
@@ -521,7 +531,7 @@ const ChannelSendTestOut = z.object({
521
531
  messageId: z.string(),
522
532
  status: z.string(),
523
533
  chain: z.string(),
524
- at: z.number(),
534
+ at: z.string(),
525
535
  });
526
536
 
527
537
  const VaultNotFound = z.object({
@@ -634,7 +644,7 @@ const ClockRunNowOut = z.object({
634
644
  ok: z.literal(true),
635
645
  name: z.string(),
636
646
  ran: z.boolean(),
637
- at: z.number(),
647
+ at: z.string(),
638
648
  });
639
649
 
640
650
  const ClockPauseIn = z.object({ name: z.string().min(1) });
@@ -642,7 +652,7 @@ const ClockPauseOut = z.object({
642
652
  ok: z.literal(true),
643
653
  name: z.string(),
644
654
  status: z.string(),
645
- at: z.number(),
655
+ at: z.string(),
646
656
  });
647
657
 
648
658
  const ClockEditIn = z.object({
@@ -656,7 +666,7 @@ const ClockEditOut = z.object({
656
666
  name: z.string(),
657
667
  effectiveCron: z.string().optional(),
658
668
  effectiveEvery: z.string().optional(),
659
- at: z.number(),
669
+ at: z.string(),
660
670
  });
661
671
 
662
672
  const ClockWakeEarlyIn = z.object({ runId: z.string().min(1) });
@@ -665,7 +675,7 @@ const ClockWakeEarlyOut = z.object({
665
675
  runId: z.string(),
666
676
  wakeAt: z.number(),
667
677
  resumed: z.boolean(),
668
- at: z.number(),
678
+ at: z.string(),
669
679
  });
670
680
 
671
681
  const VaultResolutionStepOut = z.object({
@@ -746,7 +756,7 @@ const VaultWriteOut = z.object({
746
756
  ok: z.literal(true),
747
757
  name: z.string(),
748
758
  fingerprint: z.string().nullable(),
749
- at: z.number(),
759
+ at: z.string(),
750
760
  });
751
761
 
752
762
  const VaultAuditRowOut = z.object({
@@ -780,7 +790,7 @@ const VaultRotateMasterOut = z.object({
780
790
  kekVersion: z.number(),
781
791
  remaining: z.number(),
782
792
  masterKey: z.string().nullable(),
783
- at: z.number(),
793
+ at: z.string(),
784
794
  });
785
795
 
786
796
  const AiMetricOut = z.object({
@@ -834,6 +844,7 @@ const AiListOut = z.object({
834
844
  ok: z.number(),
835
845
  provider_error: z.number(),
836
846
  schema_invalid: z.number(),
847
+ budget_exceeded: z.number(),
837
848
  }),
838
849
  }),
839
850
  ),
@@ -1421,6 +1432,7 @@ const StoreListOut = z.object({
1421
1432
  piiColumns: z.array(z.string()),
1422
1433
  columnDescriptions: z.record(z.string(), z.string()),
1423
1434
  rls: z.boolean().optional(),
1435
+ rlsPolicyCount: z.number().int().nonnegative().optional(),
1424
1436
  }),
1425
1437
  ),
1426
1438
  replicaLagMs: z.number().nullable(),
@@ -1444,6 +1456,17 @@ const StoreListOut = z.object({
1444
1456
  ),
1445
1457
  });
1446
1458
 
1459
+ const StoreRlsAs = {
1460
+ asGate: z.string().min(1).optional(),
1461
+ asUserId: z.string().min(1).optional(),
1462
+ };
1463
+
1464
+ const StoreRlsOut = z.object({
1465
+ gate: z.string().nullable(),
1466
+ userId: z.string(),
1467
+ applied: z.boolean(),
1468
+ });
1469
+
1447
1470
  const StoreQueryIn = z.object({
1448
1471
  ref: z.string().min(1),
1449
1472
  child: z.string().optional(),
@@ -1454,6 +1477,7 @@ const StoreQueryIn = z.object({
1454
1477
  q: z.string().optional(),
1455
1478
  topK: z.number().min(1).max(100).optional(),
1456
1479
  revealPii: z.boolean().optional(),
1480
+ ...StoreRlsAs,
1457
1481
  });
1458
1482
 
1459
1483
  const StoreQueryOut = z.object({
@@ -1482,6 +1506,7 @@ const StoreQueryOut = z.object({
1482
1506
  .optional(),
1483
1507
  masked: z.boolean(),
1484
1508
  routedRole: z.enum(["primary", "replica"]).optional(),
1509
+ rls: StoreRlsOut.optional(),
1485
1510
  });
1486
1511
 
1487
1512
  const StoreRevealIn = z.object({
@@ -1495,7 +1520,7 @@ const StoreRevealIn = z.object({
1495
1520
  const StoreRevealOut = z.object({
1496
1521
  ok: z.literal(true),
1497
1522
  value: z.unknown(),
1498
- at: z.number(),
1523
+ at: z.string(),
1499
1524
  });
1500
1525
 
1501
1526
  const StoreFileGetIn = z.object({
@@ -1526,6 +1551,7 @@ const StoreEditIn = z.object({
1526
1551
  reason: z.string().optional(),
1527
1552
  /** When false/omitted, returns willNotFire without applying. */
1528
1553
  commit: z.boolean().optional(),
1554
+ ...StoreRlsAs,
1529
1555
  });
1530
1556
 
1531
1557
  const StoreEditOut = z.object({
@@ -1539,7 +1565,8 @@ const StoreEditOut = z.object({
1539
1565
  resource: z.string(),
1540
1566
  }),
1541
1567
  ),
1542
- at: z.number(),
1568
+ at: z.string(),
1569
+ rls: StoreRlsOut.optional(),
1543
1570
  });
1544
1571
 
1545
1572
  const StoreDeleteIn = z.object({
@@ -1555,7 +1582,7 @@ const StoreDeleteIn = z.object({
1555
1582
  const StoreDeleteOut = z.object({
1556
1583
  ok: z.literal(true),
1557
1584
  deleted: z.number(),
1558
- at: z.number(),
1585
+ at: z.string(),
1559
1586
  });
1560
1587
 
1561
1588
  const StorePurgeIn = z.object({
@@ -1567,7 +1594,7 @@ const StorePurgeIn = z.object({
1567
1594
  const StorePurgeOut = z.object({
1568
1595
  ok: z.literal(true),
1569
1596
  keys: z.array(z.string()),
1570
- at: z.number(),
1597
+ at: z.string(),
1571
1598
  });
1572
1599
 
1573
1600
  const StoreSqlIn = z.object({
@@ -1577,6 +1604,7 @@ const StoreSqlIn = z.object({
1577
1604
  allowWrite: z.boolean().optional(),
1578
1605
  revealPii: z.boolean().optional(),
1579
1606
  asGate: z.string().min(1).optional(),
1607
+ asUserId: z.string().min(1).optional(),
1580
1608
  });
1581
1609
 
1582
1610
  const StoreSqlOut = z.object({
@@ -1584,7 +1612,9 @@ const StoreSqlOut = z.object({
1584
1612
  masked: z.boolean(),
1585
1613
  routedRole: z.enum(["primary", "replica"]),
1586
1614
  asGate: z.string().nullable(),
1615
+ asUserId: z.string().nullable(),
1587
1616
  gateApplied: z.boolean(),
1617
+ rls: StoreRlsOut.optional(),
1588
1618
  });
1589
1619
 
1590
1620
  const StoreSqlStatsIn = z.object({
@@ -1748,7 +1778,7 @@ const StorePreviewOut = z.object({
1748
1778
  resource: z.string(),
1749
1779
  }),
1750
1780
  ),
1751
- at: z.number(),
1781
+ at: z.string(),
1752
1782
  });
1753
1783
 
1754
1784
  /**
@@ -2361,7 +2391,7 @@ function createTracesReplay(state: ConsoleState) {
2361
2391
  ok: true as const,
2362
2392
  rootId: input.rootId,
2363
2393
  dryRun,
2364
- at: Date.now(),
2394
+ at: isoAt(Date.now()),
2365
2395
  flow: root.flow,
2366
2396
  };
2367
2397
  },
@@ -2442,7 +2472,7 @@ function createSignalsDiscard(state: ConsoleState) {
2442
2472
  return {
2443
2473
  ok: true as const,
2444
2474
  discarded: result.discarded,
2445
- at: state.now(),
2475
+ at: isoAt(state.now()),
2446
2476
  };
2447
2477
  },
2448
2478
  });
@@ -2516,7 +2546,7 @@ async function runSignalReplay(
2516
2546
  dryRun: result.dryRun,
2517
2547
  results: [...result.results],
2518
2548
  wouldHaveFired: [...result.wouldHaveFired],
2519
- at: state.now(),
2549
+ at: isoAt(state.now()),
2520
2550
  };
2521
2551
  }
2522
2552
 
@@ -2561,7 +2591,7 @@ function createActionPing(state: ConsoleState) {
2561
2591
  operatorId: fx.operator.id,
2562
2592
  note: input.note,
2563
2593
  });
2564
- return { ok: true as const, note: input.note, at: state.now() };
2594
+ return { ok: true as const, note: input.note, at: isoAt(state.now()) };
2565
2595
  },
2566
2596
  });
2567
2597
  }
@@ -2663,6 +2693,7 @@ function createFlowsInvoke(state: ConsoleState) {
2663
2693
  ...(assumed.bypassGates ? { bypassGates: true } : {}),
2664
2694
  ...(input.reason !== undefined ? { reason: input.reason } : {}),
2665
2695
  ...(input.revealPii === true ? { revealPii: true } : {}),
2696
+ ...(assumed.rls ? { rls: assumed.rls } : {}),
2666
2697
  });
2667
2698
 
2668
2699
  const reveal = input.revealPii === true;
@@ -2715,6 +2746,11 @@ function createFlowsInvoke(state: ConsoleState) {
2715
2746
  ...(host.durationMs !== undefined ? { durationMs: host.durationMs } : {}),
2716
2747
  peakTier,
2717
2748
  auditedAt: state.now(),
2749
+ rls: {
2750
+ gate: assumed.rls?.gate ?? null,
2751
+ userId: assumed.rls?.userId ?? "",
2752
+ applied: assumed.rls !== null,
2753
+ },
2718
2754
  };
2719
2755
  },
2720
2756
  });
@@ -2805,6 +2841,32 @@ function createStoreList(state: ConsoleState) {
2805
2841
  });
2806
2842
  }
2807
2843
 
2844
+ function consoleStoreRls(
2845
+ state: ConsoleState,
2846
+ input: { readonly asGate?: string; readonly asUserId?: string },
2847
+ ):
2848
+ | { readonly ok: true; readonly rls: RlsIdentity | null }
2849
+ | { readonly ok: false; readonly reason: string } {
2850
+ if (input.asGate !== undefined && !isKnownConsoleSqlGate(state.manifest, input.asGate)) {
2851
+ return { ok: false, reason: `unknown gate: ${input.asGate}` };
2852
+ }
2853
+ if (input.asUserId !== undefined) {
2854
+ const identity = state.identities.find((row) => row.id === input.asUserId);
2855
+ if (!identity || identity.status === "disabled") {
2856
+ return { ok: false, reason: `unknown identity: ${input.asUserId}` };
2857
+ }
2858
+ }
2859
+ return {
2860
+ ok: true,
2861
+ rls: resolveRlsIdentity({
2862
+ asGate: input.asGate,
2863
+ asUserId: input.asUserId,
2864
+ identities: state.identities,
2865
+ manifest: state.manifest,
2866
+ }),
2867
+ };
2868
+ }
2869
+
2808
2870
  function createStoreQuery(state: ConsoleState) {
2809
2871
  return flow("console.store.query", {
2810
2872
  plane: "operator",
@@ -2823,6 +2885,8 @@ function createStoreQuery(state: ConsoleState) {
2823
2885
  tenant: input.tenant,
2824
2886
  });
2825
2887
  }
2888
+ const assumed = consoleStoreRls(state, input);
2889
+ if (!assumed.ok) return fail("StoreNotFound", { ref: assumed.reason });
2826
2890
  return state.queryStore({
2827
2891
  ref: input.ref as ResourceRef,
2828
2892
  child: input.child,
@@ -2833,6 +2897,9 @@ function createStoreQuery(state: ConsoleState) {
2833
2897
  q: input.q,
2834
2898
  topK: input.topK,
2835
2899
  revealPii: input.revealPii === true,
2900
+ ...(input.asGate !== undefined ? { asGate: input.asGate } : {}),
2901
+ ...(input.asUserId !== undefined ? { asUserId: input.asUserId } : {}),
2902
+ ...(assumed.rls ? { rls: assumed.rls } : {}),
2836
2903
  });
2837
2904
  },
2838
2905
  });
@@ -2868,7 +2935,7 @@ function createStoreReveal(state: ConsoleState) {
2868
2935
  return {
2869
2936
  ok: true as const,
2870
2937
  value: row[input.column],
2871
- at: state.now(),
2938
+ at: isoAt(state.now()),
2872
2939
  };
2873
2940
  },
2874
2941
  });
@@ -2921,6 +2988,14 @@ function createStoreEdit(state: ConsoleState) {
2921
2988
  const tenantFail = requireTenantIfDeclared(state, input.tenant);
2922
2989
  if (tenantFail) return tenantFail;
2923
2990
 
2991
+ const assumed = consoleStoreRls(state, input);
2992
+ if (!assumed.ok) return fail("StoreNotFound", { ref: assumed.reason });
2993
+ const rlsFields = {
2994
+ ...(input.asGate !== undefined ? { asGate: input.asGate } : {}),
2995
+ ...(input.asUserId !== undefined ? { asUserId: input.asUserId } : {}),
2996
+ ...(assumed.rls ? { rls: assumed.rls } : {}),
2997
+ };
2998
+
2924
2999
  if (!input.commit) {
2925
3000
  // Return will-not-fire payload without applying — informational confirm.
2926
3001
  const preview = await state.editStore(
@@ -2931,6 +3006,7 @@ function createStoreEdit(state: ConsoleState) {
2931
3006
  id: input.id,
2932
3007
  key: input.key,
2933
3008
  patch: input.patch,
3009
+ ...rlsFields,
2934
3010
  },
2935
3011
  { dryRun: true },
2936
3012
  );
@@ -2940,7 +3016,8 @@ function createStoreEdit(state: ConsoleState) {
2940
3016
  applied: false,
2941
3017
  willNotFire: preview.willNotFire,
2942
3018
  wouldHaveFired: [...preview.wouldHaveFired],
2943
- at: state.now(),
3019
+ at: isoAt(state.now()),
3020
+ ...(preview.rls ? { rls: preview.rls } : {}),
2944
3021
  };
2945
3022
  }
2946
3023
 
@@ -2961,6 +3038,7 @@ function createStoreEdit(state: ConsoleState) {
2961
3038
  id: input.id,
2962
3039
  key: input.key,
2963
3040
  patch: input.patch,
3041
+ ...rlsFields,
2964
3042
  },
2965
3043
  { dryRun: false },
2966
3044
  );
@@ -2977,7 +3055,8 @@ function createStoreEdit(state: ConsoleState) {
2977
3055
  applied: result.applied,
2978
3056
  willNotFire: result.willNotFire,
2979
3057
  wouldHaveFired: [...result.wouldHaveFired],
2980
- at: state.now(),
3058
+ at: isoAt(state.now()),
3059
+ ...(result.rls ? { rls: result.rls } : {}),
2981
3060
  };
2982
3061
  },
2983
3062
  });
@@ -3017,7 +3096,7 @@ function createStoreDelete(state: ConsoleState) {
3017
3096
  return {
3018
3097
  ok: true as const,
3019
3098
  deleted: result.deleted,
3020
- at: state.now(),
3099
+ at: isoAt(state.now()),
3021
3100
  };
3022
3101
  },
3023
3102
  });
@@ -3049,7 +3128,7 @@ function createStorePurgeCache(state: ConsoleState) {
3049
3128
  return {
3050
3129
  ok: true as const,
3051
3130
  keys: [...result.keys],
3052
- at: state.now(),
3131
+ at: isoAt(state.now()),
3053
3132
  };
3054
3133
  },
3055
3134
  });
@@ -3073,9 +3152,8 @@ function createStoreSql(state: ConsoleState) {
3073
3152
  tenant: input.tenant,
3074
3153
  });
3075
3154
  }
3076
- if (input.asGate !== undefined && !isKnownConsoleSqlGate(state.manifest, input.asGate)) {
3077
- return fail("StoreNotFound", { ref: `unknown gate: ${input.asGate}` });
3078
- }
3155
+ const assumed = consoleStoreRls(state, input);
3156
+ if (!assumed.ok) return fail("StoreNotFound", { ref: assumed.reason });
3079
3157
  if (input.asGate !== undefined) {
3080
3158
  fx.log.info("console.store.sql.asGate", {
3081
3159
  operatorId: fx.operator.id,
@@ -3089,6 +3167,8 @@ function createStoreSql(state: ConsoleState) {
3089
3167
  revealPii: input.revealPii === true,
3090
3168
  tenant: input.tenant,
3091
3169
  ...(input.asGate !== undefined ? { asGate: input.asGate } : {}),
3170
+ ...(input.asUserId !== undefined ? { asUserId: input.asUserId } : {}),
3171
+ ...(assumed.rls ? { rls: assumed.rls } : {}),
3092
3172
  });
3093
3173
  fx.log.info("console.store.sql", {
3094
3174
  operatorId: fx.operator.id,
@@ -3096,6 +3176,7 @@ function createStoreSql(state: ConsoleState) {
3096
3176
  allowWrite: input.allowWrite === true,
3097
3177
  revealPii: input.revealPii === true,
3098
3178
  asGate: input.asGate ?? null,
3179
+ asUserId: input.asUserId ?? null,
3099
3180
  rowCount: result.rows.length,
3100
3181
  });
3101
3182
  return result;
@@ -3311,7 +3392,7 @@ function createStorePreview(state: ConsoleState) {
3311
3392
  dryRun: true as const,
3312
3393
  willNotFire: preview.willNotFire,
3313
3394
  wouldHaveFired: [...preview.wouldHaveFired],
3314
- at: state.now(),
3395
+ at: isoAt(state.now()),
3315
3396
  };
3316
3397
  } catch (err) {
3317
3398
  if (err instanceof DryRunWriteIsolationError) {
@@ -3367,7 +3448,7 @@ function createVaultCreate(state: ConsoleState) {
3367
3448
  ok: true as const,
3368
3449
  name: result.name,
3369
3450
  fingerprint: result.fingerprint,
3370
- at: state.now(),
3451
+ at: isoAt(state.now()),
3371
3452
  };
3372
3453
  } catch (err) {
3373
3454
  const message = err instanceof Error ? err.message : String(err);
@@ -3407,7 +3488,7 @@ function createVaultSet(state: ConsoleState) {
3407
3488
  ok: true as const,
3408
3489
  name: result.name,
3409
3490
  fingerprint: result.fingerprint,
3410
- at: state.now(),
3491
+ at: isoAt(state.now()),
3411
3492
  };
3412
3493
  } catch (err) {
3413
3494
  return fail("VaultNotFound", {
@@ -3445,7 +3526,7 @@ function createVaultRotate(state: ConsoleState) {
3445
3526
  ok: true as const,
3446
3527
  name: result.name,
3447
3528
  fingerprint: result.fingerprint,
3448
- at: state.now(),
3529
+ at: isoAt(state.now()),
3449
3530
  };
3450
3531
  } catch (err) {
3451
3532
  return fail("VaultNotFound", {
@@ -3491,7 +3572,7 @@ function createVaultRotateMaster(state: ConsoleState) {
3491
3572
  kekVersion: result.kekVersion,
3492
3573
  remaining: result.remaining,
3493
3574
  masterKey: result.masterKey,
3494
- at: state.now(),
3575
+ at: isoAt(state.now()),
3495
3576
  };
3496
3577
  } catch (err) {
3497
3578
  return mapVaultOperatorError(err);
@@ -4009,7 +4090,7 @@ function createClockRunNow(state: ConsoleState) {
4009
4090
  ok: true as const,
4010
4091
  name: input.name,
4011
4092
  ran: result.ran,
4012
- at: state.now(),
4093
+ at: isoAt(state.now()),
4013
4094
  };
4014
4095
  } catch (err) {
4015
4096
  if (err instanceof ClockResourceNotFoundError) {
@@ -4039,7 +4120,7 @@ function createClockPause(state: ConsoleState) {
4039
4120
  ok: true as const,
4040
4121
  name: result.name,
4041
4122
  status: result.status,
4042
- at: state.now(),
4123
+ at: isoAt(state.now()),
4043
4124
  };
4044
4125
  } catch (err) {
4045
4126
  if (err instanceof ClockResourceNotFoundError) {
@@ -4074,7 +4155,7 @@ function createClockEditSchedule(state: ConsoleState) {
4074
4155
  name: result.name,
4075
4156
  effectiveCron: result.effectiveCron,
4076
4157
  effectiveEvery: result.effectiveEvery,
4077
- at: state.now(),
4158
+ at: isoAt(state.now()),
4078
4159
  };
4079
4160
  } catch (err) {
4080
4161
  if (err instanceof ScheduleNotOverridableError) {
@@ -4109,7 +4190,7 @@ function createClockWakeEarly(state: ConsoleState) {
4109
4190
  runId: result.runId,
4110
4191
  wakeAt: result.wakeAt,
4111
4192
  resumed: result.resumed,
4112
- at: state.now(),
4193
+ at: isoAt(state.now()),
4113
4194
  };
4114
4195
  } catch (err) {
4115
4196
  if (err instanceof ClockResourceNotFoundError) {
@@ -4186,7 +4267,7 @@ function createChannelReveal(state: ConsoleState) {
4186
4267
  ok: true as const,
4187
4268
  id: revealed.id,
4188
4269
  to: revealed.to,
4189
- at: state.now(),
4270
+ at: isoAt(state.now()),
4190
4271
  };
4191
4272
  },
4192
4273
  });
@@ -4233,7 +4314,7 @@ function createChannelSendTest(state: ConsoleState) {
4233
4314
  });
4234
4315
  return {
4235
4316
  ...result,
4236
- at: state.now(),
4317
+ at: isoAt(state.now()),
4237
4318
  };
4238
4319
  } catch (err) {
4239
4320
  return fail("ChannelNotFound", {
@@ -181,7 +181,9 @@ describe("resolveInvokeAs", () => {
181
181
  const resolved = resolveInvokeAs({ asGate: "member" }, IDENTITIES, MANIFEST);
182
182
  expect(resolved.ok).toBe(true);
183
183
  if (!resolved.ok) return;
184
- expect(resolved.asUserId).toBe("gate:member");
184
+ expect(resolved.asUserId).toBe("");
185
+ expect(resolved.principal.userId).toBe("");
186
+ expect(resolved.rls).toEqual({ gate: "member", userId: "", scopes: ["member"] });
185
187
  expect([...resolved.principal.scopes]).toEqual(["member"]);
186
188
  expect(resolved.principal.verified).toBe(true);
187
189
  });
@@ -14,6 +14,8 @@ import type { AnyFlowDef } from "../../kernel/flow.ts";
14
14
  import { isJsonResult } from "../../kernel/fx.ts";
15
15
  import { isFlowFailure } from "../../kernel/hooks.ts";
16
16
  import type { Trigger } from "../../kernel/triggers.ts";
17
+ import type { RlsIdentity } from "../../drivers/pg-rls.ts";
18
+ import { resolveRlsIdentity } from "../../elements/store.ts";
17
19
  import type { Manifest } from "../../manifest/types.ts";
18
20
  import type { ConsoleIdentity } from "./state.ts";
19
21
 
@@ -32,6 +34,8 @@ export interface InvokeUserFlowInput {
32
34
  * Envelope remask is skipped separately when this is true.
33
35
  */
34
36
  readonly revealPii?: boolean;
37
+ /** Same bag as SQL console / browse — stamped onto host `fx.store`. */
38
+ readonly rls?: RlsIdentity;
35
39
  }
36
40
 
37
41
  /** Resolved Call API invoke-as principal. */
@@ -42,6 +46,7 @@ export type ResolvedInvokeAs =
42
46
  readonly asUserId: string;
43
47
  readonly asGate: string | null;
44
48
  readonly bypassGates: boolean;
49
+ readonly rls: RlsIdentity | null;
45
50
  }
46
51
  | { readonly ok: false; readonly reason: string };
47
52
 
@@ -75,6 +80,12 @@ export function resolveInvokeAs(
75
80
  asUserId: identity.id,
76
81
  asGate: asGate ?? null,
77
82
  bypassGates: false,
83
+ rls: resolveRlsIdentity({
84
+ asUserId: identity.id,
85
+ asGate: asGate ?? null,
86
+ identities,
87
+ manifest,
88
+ }),
78
89
  };
79
90
  }
80
91
 
@@ -89,6 +100,7 @@ export function resolveInvokeAs(
89
100
  asUserId: "console:operator",
90
101
  asGate: null,
91
102
  bypassGates: true,
103
+ rls: null,
92
104
  };
93
105
  }
94
106
 
@@ -103,6 +115,7 @@ export function resolveInvokeAs(
103
115
  asUserId: "public",
104
116
  asGate: "public",
105
117
  bypassGates: false,
118
+ rls: resolveRlsIdentity({ asGate: "public", manifest }),
106
119
  };
107
120
  }
108
121
 
@@ -114,13 +127,14 @@ export function resolveInvokeAs(
114
127
  return {
115
128
  ok: true,
116
129
  principal: userPrincipal({
117
- userId: `gate:${asGate}`,
130
+ userId: "",
118
131
  scopes,
119
132
  verified: true,
120
133
  }),
121
- asUserId: `gate:${asGate}`,
134
+ asUserId: "",
122
135
  asGate,
123
136
  bypassGates: false,
137
+ rls: resolveRlsIdentity({ asGate, manifest }),
124
138
  };
125
139
  }
126
140
 
@@ -353,6 +367,7 @@ export function bindHostInvokeUserFlow(app: InvokeHostApp): ConsoleInvokeUserFlo
353
367
  runId,
354
368
  ...(input.bypassGates === true ? { bypassGates: true } : {}),
355
369
  ...(input.revealPii === true ? { revealPii: true } : {}),
370
+ ...(input.rls ? { rls: input.rls } : {}),
356
371
  principal: {
357
372
  plane: "user",
358
373
  userId: input.principal.userId.length > 0 ? input.principal.userId : null,
@@ -0,0 +1,9 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { isoAt } from "./iso-at.ts";
4
+
5
+ describe("isoAt", () => {
6
+ test("formats epoch ms as UTC ISO-8601", () => {
7
+ expect(isoAt(1_787_305_011_525)).toBe("2026-08-21T09:36:51.525Z");
8
+ });
9
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ISO-8601 UTC instant from a Console clock reading (epoch ms).
3
+ *
4
+ * Mutation receipts (`clock.run-now`, store edit, vault write, …) return
5
+ * this instead of a raw millisecond number so Call API JSON is readable.
6
+ *
7
+ * @param ms - Epoch milliseconds from `state.now()` / `Date.now()`
8
+ */
9
+ export function isoAt(ms: number): string {
10
+ return new Date(ms).toISOString();
11
+ }
@@ -7,14 +7,16 @@ export const CONSOLE_CSP: string = [
7
7
  "default-src 'self'",
8
8
  "script-src 'self'",
9
9
  "style-src 'self' 'unsafe-inline'",
10
- "img-src 'self' data:",
10
+ "img-src 'self' data: blob:",
11
+ "media-src 'self' blob:",
11
12
  "font-src 'self'",
12
13
  "connect-src 'self' ws: wss:",
13
14
  "object-src 'none'",
14
15
  "base-uri 'self'",
15
16
  "form-action 'self'",
16
17
  "frame-ancestors 'none'",
17
- "frame-src 'self'",
18
+ // Files preview mints blob: URLs for PDF iframes, images, and A/V.
19
+ "frame-src 'self' blob:",
18
20
  ].join("; ");
19
21
 
20
22
  /**
@@ -27,13 +29,14 @@ export const CONSOLE_VITE_DEV_CSP: string = [
27
29
  "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
28
30
  "style-src 'self' 'unsafe-inline'",
29
31
  "img-src 'self' data: blob:",
32
+ "media-src 'self' blob:",
30
33
  "font-src 'self'",
31
34
  "connect-src 'self' ws: wss: http://127.0.0.1:* ws://127.0.0.1:*",
32
35
  "object-src 'none'",
33
36
  "base-uri 'self'",
34
37
  "form-action 'self'",
35
38
  "frame-ancestors 'none'",
36
- "frame-src 'self'",
39
+ "frame-src 'self' blob:",
37
40
  ].join("; ");
38
41
 
39
42
  /** CSP for sandboxed plugin panel iframes (no operator session reach). */