okengine 0.14.1 → 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 (205) 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 +3 -0
  6. package/site/content/docs/elements/gate.mdx +25 -22
  7. package/site/content/docs/elements/signal.mdx +4 -4
  8. package/site/content/docs/elements/store.mdx +86 -27
  9. package/site/content/docs/elements/vault.mdx +1 -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 +25 -21
  17. package/site/content/docs/reference/security.md +1 -1
  18. package/src/auth/bindings.ts +14 -5
  19. package/src/auth/cross-plane.ts +10 -3
  20. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  21. package/src/cli/ai-setup/apply.ts +32 -1
  22. package/src/cli/attach-host-console.test.ts +3 -2
  23. package/src/cli/attach-host-console.ts +3 -0
  24. package/src/cli/build.test.ts +4 -2
  25. package/src/cli/db-seed.ts +48 -15
  26. package/src/cli/db.test.ts +104 -0
  27. package/src/cli/db.ts +122 -10
  28. package/src/cli/dev-controls.test.ts +20 -0
  29. package/src/cli/dev-controls.ts +13 -0
  30. package/src/cli/dev.test.ts +15 -4
  31. package/src/cli/dev.ts +102 -29
  32. package/src/cli/ensure-drizzle-config.ts +2 -1
  33. package/src/cli/tui/DevLive.tsx +12 -2
  34. package/src/cli/tui/keys.test.ts +2 -1
  35. package/src/cli/tui/keys.ts +3 -2
  36. package/src/compiler/aot.test.ts +1 -1
  37. package/src/compiler/extract.test.ts +409 -33
  38. package/src/compiler/extract.ts +318 -67
  39. package/src/compiler/flow-path.test.ts +96 -0
  40. package/src/compiler/flow-path.ts +173 -0
  41. package/src/compiler/generate-adopt.test.ts +112 -3
  42. package/src/compiler/generate-adopt.ts +280 -26
  43. package/src/compiler/schema-from-ast.ts +609 -0
  44. package/src/console/server/bind.ts +2 -2
  45. package/src/console/server/console.test.ts +3 -0
  46. package/src/console/server/flows.ts +120 -40
  47. package/src/console/server/invoke-user-flow.test.ts +3 -1
  48. package/src/console/server/invoke-user-flow.ts +17 -2
  49. package/src/console/server/iso-at.test.ts +9 -0
  50. package/src/console/server/iso-at.ts +11 -0
  51. package/src/console/server/security-headers.ts +6 -3
  52. package/src/console/server/spa-proxy.test.ts +1 -0
  53. package/src/console/server/sql-catalog.ts +147 -7
  54. package/src/console/server/sql-rls.test.ts +16 -0
  55. package/src/console/server/state.ts +8 -0
  56. package/src/console/server/store.test.ts +7 -4
  57. package/src/console/server/store.ts +142 -58
  58. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  59. package/src/console/ui-next/dist/assets/{cache-glyph-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
  60. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  61. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  62. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  63. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  64. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  65. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  66. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  67. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  68. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  69. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  70. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  71. package/src/console/ui-next/dist/assets/{observability-page-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
  72. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  73. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  74. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  75. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  76. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  77. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  78. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  79. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  80. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  81. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  82. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  83. package/src/console/ui-next/dist/index.html +6 -6
  84. package/src/console/ui-next/src/client.ts +35 -7
  85. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  86. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  87. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  88. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  89. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
  90. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  91. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  92. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  93. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  94. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  95. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  96. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  97. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  98. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  99. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  100. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  101. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  102. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  103. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  104. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  105. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  106. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  107. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  108. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  109. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  110. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  111. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  112. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  113. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  114. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  115. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  116. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  117. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  118. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  119. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  120. package/src/docker/compose-health.test.ts +27 -0
  121. package/src/docker/compose-health.ts +19 -3
  122. package/src/docker/docker.test.ts +3 -0
  123. package/src/docker/recipes/pgdog.ts +2 -0
  124. package/src/drivers/clock-postgres.ts +13 -9
  125. package/src/drivers/instances-postgres.ts +4 -6
  126. package/src/drivers/journal-postgres.ts +13 -9
  127. package/src/drivers/pg-rls.test.ts +87 -0
  128. package/src/drivers/pg-rls.ts +233 -7
  129. package/src/drivers/pglite.ts +26 -3
  130. package/src/drivers/postgres.test.ts +23 -0
  131. package/src/drivers/postgres.ts +133 -9
  132. package/src/drivers/types.ts +10 -0
  133. package/src/elements/clock/declare.ts +26 -1
  134. package/src/elements/gate/boot.ts +5 -5
  135. package/src/elements/gate/declare.ts +36 -4
  136. package/src/elements/gate/flatten.ts +1 -1
  137. package/src/elements/gate/runtime.ts +1 -1
  138. package/src/elements/store/declare.ts +9 -6
  139. package/src/elements/store/emit-drizzle.ts +44 -8
  140. package/src/elements/store/resource.test.ts +1 -1
  141. package/src/elements/store/rls-identity.test.ts +95 -0
  142. package/src/elements/store/rls-identity.ts +116 -0
  143. package/src/elements/store/runtime.ts +4 -0
  144. package/src/elements/store/schema-decl.test.ts +37 -0
  145. package/src/elements/store/schema-decl.ts +161 -0
  146. package/src/elements/store/seed.ts +1 -1
  147. package/src/elements/store/sql-condition.test.ts +12 -0
  148. package/src/elements/store/sql-condition.ts +26 -2
  149. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  150. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  151. package/src/elements/store/sql-session.ts +81 -3
  152. package/src/elements/store/upsert-app.test.ts +1 -1
  153. package/src/elements/store.ts +14 -0
  154. package/src/http.ts +20 -3
  155. package/src/index.ts +10 -1
  156. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  157. package/src/kernel/adopt-routes.ts +30 -2
  158. package/src/kernel/app.ts +131 -14
  159. package/src/kernel/auto-cache.test.ts +8 -8
  160. package/src/kernel/auto-registry.test.ts +49 -9
  161. package/src/kernel/boot.test.ts +4 -4
  162. package/src/kernel/boot.ts +2 -2
  163. package/src/kernel/effects-stamping.test.ts +3 -3
  164. package/src/kernel/element-registries.ts +12 -5
  165. package/src/kernel/errors.ts +24 -0
  166. package/src/kernel/flow-units.ts +64 -0
  167. package/src/kernel/flow.ts +29 -17
  168. package/src/kernel/fx.ts +23 -0
  169. package/src/kernel/horizontal-child.ts +4 -4
  170. package/src/kernel/http-path-pending.ts +20 -0
  171. package/src/kernel/http-resource.ts +4 -0
  172. package/src/kernel/http-stream.test.ts +4 -4
  173. package/src/kernel/pipeline.test.ts +2 -2
  174. package/src/kernel/plugin-elements.test.ts +1 -1
  175. package/src/kernel/plugin-needs.test.ts +1 -1
  176. package/src/kernel/ready.test.ts +2 -2
  177. package/src/kernel/resource-mount.test.ts +14 -0
  178. package/src/kernel/run-duration.test.ts +2 -2
  179. package/src/kernel/stamp-http.test.ts +79 -0
  180. package/src/kernel/stamp-http.ts +75 -0
  181. package/src/kernel/triggers.ts +52 -27
  182. package/src/manifest/types.ts +13 -0
  183. package/src/release/measure.ts +2 -2
  184. package/src/runtime/json-code-block.test.ts +2 -2
  185. package/src/runtime/serve.test.ts +4 -4
  186. package/src/term.test.ts +52 -4
  187. package/src/term.ts +125 -17
  188. package/src/test/reset-element-registries.ts +8 -3
  189. package/src/cli/ask-seed.test.ts +0 -96
  190. package/src/cli/ask-seed.ts +0 -82
  191. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  192. package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
  193. package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
  194. package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
  195. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  196. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  197. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  198. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  199. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  200. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  201. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  202. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  203. package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
  204. package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
  205. package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.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({
@@ -1422,6 +1432,7 @@ const StoreListOut = z.object({
1422
1432
  piiColumns: z.array(z.string()),
1423
1433
  columnDescriptions: z.record(z.string(), z.string()),
1424
1434
  rls: z.boolean().optional(),
1435
+ rlsPolicyCount: z.number().int().nonnegative().optional(),
1425
1436
  }),
1426
1437
  ),
1427
1438
  replicaLagMs: z.number().nullable(),
@@ -1445,6 +1456,17 @@ const StoreListOut = z.object({
1445
1456
  ),
1446
1457
  });
1447
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
+
1448
1470
  const StoreQueryIn = z.object({
1449
1471
  ref: z.string().min(1),
1450
1472
  child: z.string().optional(),
@@ -1455,6 +1477,7 @@ const StoreQueryIn = z.object({
1455
1477
  q: z.string().optional(),
1456
1478
  topK: z.number().min(1).max(100).optional(),
1457
1479
  revealPii: z.boolean().optional(),
1480
+ ...StoreRlsAs,
1458
1481
  });
1459
1482
 
1460
1483
  const StoreQueryOut = z.object({
@@ -1483,6 +1506,7 @@ const StoreQueryOut = z.object({
1483
1506
  .optional(),
1484
1507
  masked: z.boolean(),
1485
1508
  routedRole: z.enum(["primary", "replica"]).optional(),
1509
+ rls: StoreRlsOut.optional(),
1486
1510
  });
1487
1511
 
1488
1512
  const StoreRevealIn = z.object({
@@ -1496,7 +1520,7 @@ const StoreRevealIn = z.object({
1496
1520
  const StoreRevealOut = z.object({
1497
1521
  ok: z.literal(true),
1498
1522
  value: z.unknown(),
1499
- at: z.number(),
1523
+ at: z.string(),
1500
1524
  });
1501
1525
 
1502
1526
  const StoreFileGetIn = z.object({
@@ -1527,6 +1551,7 @@ const StoreEditIn = z.object({
1527
1551
  reason: z.string().optional(),
1528
1552
  /** When false/omitted, returns willNotFire without applying. */
1529
1553
  commit: z.boolean().optional(),
1554
+ ...StoreRlsAs,
1530
1555
  });
1531
1556
 
1532
1557
  const StoreEditOut = z.object({
@@ -1540,7 +1565,8 @@ const StoreEditOut = z.object({
1540
1565
  resource: z.string(),
1541
1566
  }),
1542
1567
  ),
1543
- at: z.number(),
1568
+ at: z.string(),
1569
+ rls: StoreRlsOut.optional(),
1544
1570
  });
1545
1571
 
1546
1572
  const StoreDeleteIn = z.object({
@@ -1556,7 +1582,7 @@ const StoreDeleteIn = z.object({
1556
1582
  const StoreDeleteOut = z.object({
1557
1583
  ok: z.literal(true),
1558
1584
  deleted: z.number(),
1559
- at: z.number(),
1585
+ at: z.string(),
1560
1586
  });
1561
1587
 
1562
1588
  const StorePurgeIn = z.object({
@@ -1568,7 +1594,7 @@ const StorePurgeIn = z.object({
1568
1594
  const StorePurgeOut = z.object({
1569
1595
  ok: z.literal(true),
1570
1596
  keys: z.array(z.string()),
1571
- at: z.number(),
1597
+ at: z.string(),
1572
1598
  });
1573
1599
 
1574
1600
  const StoreSqlIn = z.object({
@@ -1578,6 +1604,7 @@ const StoreSqlIn = z.object({
1578
1604
  allowWrite: z.boolean().optional(),
1579
1605
  revealPii: z.boolean().optional(),
1580
1606
  asGate: z.string().min(1).optional(),
1607
+ asUserId: z.string().min(1).optional(),
1581
1608
  });
1582
1609
 
1583
1610
  const StoreSqlOut = z.object({
@@ -1585,7 +1612,9 @@ const StoreSqlOut = z.object({
1585
1612
  masked: z.boolean(),
1586
1613
  routedRole: z.enum(["primary", "replica"]),
1587
1614
  asGate: z.string().nullable(),
1615
+ asUserId: z.string().nullable(),
1588
1616
  gateApplied: z.boolean(),
1617
+ rls: StoreRlsOut.optional(),
1589
1618
  });
1590
1619
 
1591
1620
  const StoreSqlStatsIn = z.object({
@@ -1749,7 +1778,7 @@ const StorePreviewOut = z.object({
1749
1778
  resource: z.string(),
1750
1779
  }),
1751
1780
  ),
1752
- at: z.number(),
1781
+ at: z.string(),
1753
1782
  });
1754
1783
 
1755
1784
  /**
@@ -2362,7 +2391,7 @@ function createTracesReplay(state: ConsoleState) {
2362
2391
  ok: true as const,
2363
2392
  rootId: input.rootId,
2364
2393
  dryRun,
2365
- at: Date.now(),
2394
+ at: isoAt(Date.now()),
2366
2395
  flow: root.flow,
2367
2396
  };
2368
2397
  },
@@ -2443,7 +2472,7 @@ function createSignalsDiscard(state: ConsoleState) {
2443
2472
  return {
2444
2473
  ok: true as const,
2445
2474
  discarded: result.discarded,
2446
- at: state.now(),
2475
+ at: isoAt(state.now()),
2447
2476
  };
2448
2477
  },
2449
2478
  });
@@ -2517,7 +2546,7 @@ async function runSignalReplay(
2517
2546
  dryRun: result.dryRun,
2518
2547
  results: [...result.results],
2519
2548
  wouldHaveFired: [...result.wouldHaveFired],
2520
- at: state.now(),
2549
+ at: isoAt(state.now()),
2521
2550
  };
2522
2551
  }
2523
2552
 
@@ -2562,7 +2591,7 @@ function createActionPing(state: ConsoleState) {
2562
2591
  operatorId: fx.operator.id,
2563
2592
  note: input.note,
2564
2593
  });
2565
- return { ok: true as const, note: input.note, at: state.now() };
2594
+ return { ok: true as const, note: input.note, at: isoAt(state.now()) };
2566
2595
  },
2567
2596
  });
2568
2597
  }
@@ -2664,6 +2693,7 @@ function createFlowsInvoke(state: ConsoleState) {
2664
2693
  ...(assumed.bypassGates ? { bypassGates: true } : {}),
2665
2694
  ...(input.reason !== undefined ? { reason: input.reason } : {}),
2666
2695
  ...(input.revealPii === true ? { revealPii: true } : {}),
2696
+ ...(assumed.rls ? { rls: assumed.rls } : {}),
2667
2697
  });
2668
2698
 
2669
2699
  const reveal = input.revealPii === true;
@@ -2716,6 +2746,11 @@ function createFlowsInvoke(state: ConsoleState) {
2716
2746
  ...(host.durationMs !== undefined ? { durationMs: host.durationMs } : {}),
2717
2747
  peakTier,
2718
2748
  auditedAt: state.now(),
2749
+ rls: {
2750
+ gate: assumed.rls?.gate ?? null,
2751
+ userId: assumed.rls?.userId ?? "",
2752
+ applied: assumed.rls !== null,
2753
+ },
2719
2754
  };
2720
2755
  },
2721
2756
  });
@@ -2806,6 +2841,32 @@ function createStoreList(state: ConsoleState) {
2806
2841
  });
2807
2842
  }
2808
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
+
2809
2870
  function createStoreQuery(state: ConsoleState) {
2810
2871
  return flow("console.store.query", {
2811
2872
  plane: "operator",
@@ -2824,6 +2885,8 @@ function createStoreQuery(state: ConsoleState) {
2824
2885
  tenant: input.tenant,
2825
2886
  });
2826
2887
  }
2888
+ const assumed = consoleStoreRls(state, input);
2889
+ if (!assumed.ok) return fail("StoreNotFound", { ref: assumed.reason });
2827
2890
  return state.queryStore({
2828
2891
  ref: input.ref as ResourceRef,
2829
2892
  child: input.child,
@@ -2834,6 +2897,9 @@ function createStoreQuery(state: ConsoleState) {
2834
2897
  q: input.q,
2835
2898
  topK: input.topK,
2836
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 } : {}),
2837
2903
  });
2838
2904
  },
2839
2905
  });
@@ -2869,7 +2935,7 @@ function createStoreReveal(state: ConsoleState) {
2869
2935
  return {
2870
2936
  ok: true as const,
2871
2937
  value: row[input.column],
2872
- at: state.now(),
2938
+ at: isoAt(state.now()),
2873
2939
  };
2874
2940
  },
2875
2941
  });
@@ -2922,6 +2988,14 @@ function createStoreEdit(state: ConsoleState) {
2922
2988
  const tenantFail = requireTenantIfDeclared(state, input.tenant);
2923
2989
  if (tenantFail) return tenantFail;
2924
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
+
2925
2999
  if (!input.commit) {
2926
3000
  // Return will-not-fire payload without applying — informational confirm.
2927
3001
  const preview = await state.editStore(
@@ -2932,6 +3006,7 @@ function createStoreEdit(state: ConsoleState) {
2932
3006
  id: input.id,
2933
3007
  key: input.key,
2934
3008
  patch: input.patch,
3009
+ ...rlsFields,
2935
3010
  },
2936
3011
  { dryRun: true },
2937
3012
  );
@@ -2941,7 +3016,8 @@ function createStoreEdit(state: ConsoleState) {
2941
3016
  applied: false,
2942
3017
  willNotFire: preview.willNotFire,
2943
3018
  wouldHaveFired: [...preview.wouldHaveFired],
2944
- at: state.now(),
3019
+ at: isoAt(state.now()),
3020
+ ...(preview.rls ? { rls: preview.rls } : {}),
2945
3021
  };
2946
3022
  }
2947
3023
 
@@ -2962,6 +3038,7 @@ function createStoreEdit(state: ConsoleState) {
2962
3038
  id: input.id,
2963
3039
  key: input.key,
2964
3040
  patch: input.patch,
3041
+ ...rlsFields,
2965
3042
  },
2966
3043
  { dryRun: false },
2967
3044
  );
@@ -2978,7 +3055,8 @@ function createStoreEdit(state: ConsoleState) {
2978
3055
  applied: result.applied,
2979
3056
  willNotFire: result.willNotFire,
2980
3057
  wouldHaveFired: [...result.wouldHaveFired],
2981
- at: state.now(),
3058
+ at: isoAt(state.now()),
3059
+ ...(result.rls ? { rls: result.rls } : {}),
2982
3060
  };
2983
3061
  },
2984
3062
  });
@@ -3018,7 +3096,7 @@ function createStoreDelete(state: ConsoleState) {
3018
3096
  return {
3019
3097
  ok: true as const,
3020
3098
  deleted: result.deleted,
3021
- at: state.now(),
3099
+ at: isoAt(state.now()),
3022
3100
  };
3023
3101
  },
3024
3102
  });
@@ -3050,7 +3128,7 @@ function createStorePurgeCache(state: ConsoleState) {
3050
3128
  return {
3051
3129
  ok: true as const,
3052
3130
  keys: [...result.keys],
3053
- at: state.now(),
3131
+ at: isoAt(state.now()),
3054
3132
  };
3055
3133
  },
3056
3134
  });
@@ -3074,9 +3152,8 @@ function createStoreSql(state: ConsoleState) {
3074
3152
  tenant: input.tenant,
3075
3153
  });
3076
3154
  }
3077
- if (input.asGate !== undefined && !isKnownConsoleSqlGate(state.manifest, input.asGate)) {
3078
- return fail("StoreNotFound", { ref: `unknown gate: ${input.asGate}` });
3079
- }
3155
+ const assumed = consoleStoreRls(state, input);
3156
+ if (!assumed.ok) return fail("StoreNotFound", { ref: assumed.reason });
3080
3157
  if (input.asGate !== undefined) {
3081
3158
  fx.log.info("console.store.sql.asGate", {
3082
3159
  operatorId: fx.operator.id,
@@ -3090,6 +3167,8 @@ function createStoreSql(state: ConsoleState) {
3090
3167
  revealPii: input.revealPii === true,
3091
3168
  tenant: input.tenant,
3092
3169
  ...(input.asGate !== undefined ? { asGate: input.asGate } : {}),
3170
+ ...(input.asUserId !== undefined ? { asUserId: input.asUserId } : {}),
3171
+ ...(assumed.rls ? { rls: assumed.rls } : {}),
3093
3172
  });
3094
3173
  fx.log.info("console.store.sql", {
3095
3174
  operatorId: fx.operator.id,
@@ -3097,6 +3176,7 @@ function createStoreSql(state: ConsoleState) {
3097
3176
  allowWrite: input.allowWrite === true,
3098
3177
  revealPii: input.revealPii === true,
3099
3178
  asGate: input.asGate ?? null,
3179
+ asUserId: input.asUserId ?? null,
3100
3180
  rowCount: result.rows.length,
3101
3181
  });
3102
3182
  return result;
@@ -3312,7 +3392,7 @@ function createStorePreview(state: ConsoleState) {
3312
3392
  dryRun: true as const,
3313
3393
  willNotFire: preview.willNotFire,
3314
3394
  wouldHaveFired: [...preview.wouldHaveFired],
3315
- at: state.now(),
3395
+ at: isoAt(state.now()),
3316
3396
  };
3317
3397
  } catch (err) {
3318
3398
  if (err instanceof DryRunWriteIsolationError) {
@@ -3368,7 +3448,7 @@ function createVaultCreate(state: ConsoleState) {
3368
3448
  ok: true as const,
3369
3449
  name: result.name,
3370
3450
  fingerprint: result.fingerprint,
3371
- at: state.now(),
3451
+ at: isoAt(state.now()),
3372
3452
  };
3373
3453
  } catch (err) {
3374
3454
  const message = err instanceof Error ? err.message : String(err);
@@ -3408,7 +3488,7 @@ function createVaultSet(state: ConsoleState) {
3408
3488
  ok: true as const,
3409
3489
  name: result.name,
3410
3490
  fingerprint: result.fingerprint,
3411
- at: state.now(),
3491
+ at: isoAt(state.now()),
3412
3492
  };
3413
3493
  } catch (err) {
3414
3494
  return fail("VaultNotFound", {
@@ -3446,7 +3526,7 @@ function createVaultRotate(state: ConsoleState) {
3446
3526
  ok: true as const,
3447
3527
  name: result.name,
3448
3528
  fingerprint: result.fingerprint,
3449
- at: state.now(),
3529
+ at: isoAt(state.now()),
3450
3530
  };
3451
3531
  } catch (err) {
3452
3532
  return fail("VaultNotFound", {
@@ -3492,7 +3572,7 @@ function createVaultRotateMaster(state: ConsoleState) {
3492
3572
  kekVersion: result.kekVersion,
3493
3573
  remaining: result.remaining,
3494
3574
  masterKey: result.masterKey,
3495
- at: state.now(),
3575
+ at: isoAt(state.now()),
3496
3576
  };
3497
3577
  } catch (err) {
3498
3578
  return mapVaultOperatorError(err);
@@ -4010,7 +4090,7 @@ function createClockRunNow(state: ConsoleState) {
4010
4090
  ok: true as const,
4011
4091
  name: input.name,
4012
4092
  ran: result.ran,
4013
- at: state.now(),
4093
+ at: isoAt(state.now()),
4014
4094
  };
4015
4095
  } catch (err) {
4016
4096
  if (err instanceof ClockResourceNotFoundError) {
@@ -4040,7 +4120,7 @@ function createClockPause(state: ConsoleState) {
4040
4120
  ok: true as const,
4041
4121
  name: result.name,
4042
4122
  status: result.status,
4043
- at: state.now(),
4123
+ at: isoAt(state.now()),
4044
4124
  };
4045
4125
  } catch (err) {
4046
4126
  if (err instanceof ClockResourceNotFoundError) {
@@ -4075,7 +4155,7 @@ function createClockEditSchedule(state: ConsoleState) {
4075
4155
  name: result.name,
4076
4156
  effectiveCron: result.effectiveCron,
4077
4157
  effectiveEvery: result.effectiveEvery,
4078
- at: state.now(),
4158
+ at: isoAt(state.now()),
4079
4159
  };
4080
4160
  } catch (err) {
4081
4161
  if (err instanceof ScheduleNotOverridableError) {
@@ -4110,7 +4190,7 @@ function createClockWakeEarly(state: ConsoleState) {
4110
4190
  runId: result.runId,
4111
4191
  wakeAt: result.wakeAt,
4112
4192
  resumed: result.resumed,
4113
- at: state.now(),
4193
+ at: isoAt(state.now()),
4114
4194
  };
4115
4195
  } catch (err) {
4116
4196
  if (err instanceof ClockResourceNotFoundError) {
@@ -4187,7 +4267,7 @@ function createChannelReveal(state: ConsoleState) {
4187
4267
  ok: true as const,
4188
4268
  id: revealed.id,
4189
4269
  to: revealed.to,
4190
- at: state.now(),
4270
+ at: isoAt(state.now()),
4191
4271
  };
4192
4272
  },
4193
4273
  });
@@ -4234,7 +4314,7 @@ function createChannelSendTest(state: ConsoleState) {
4234
4314
  });
4235
4315
  return {
4236
4316
  ...result,
4237
- at: state.now(),
4317
+ at: isoAt(state.now()),
4238
4318
  };
4239
4319
  } catch (err) {
4240
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). */