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
@@ -7,8 +7,11 @@
7
7
 
8
8
  import { flattenGateArgs, GATE_PUBLIC_NAME, type GateAllDecl } from "../elements/gate/flatten.ts";
9
9
  import type { NamedRef } from "./fx.ts";
10
+ import { HTTP_PATH_PENDING, type HttpPathPending } from "./http-path-pending.ts";
10
11
  import { lazyRequire } from "./lazy-require.ts";
11
12
 
13
+ export { HTTP_PATH_PENDING, isPendingHttpPath, type HttpPathPending } from "./http-path-pending.ts";
14
+
12
15
  /** HTTP methods accepted by {@link http}. */
13
16
  export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD" | "QUERY";
14
17
 
@@ -18,15 +21,8 @@ export type GateRef = NamedRef;
18
21
  /** One `.gate(...)` argument — a named ref, an `all` handle, or an array. */
19
22
  export type GateArg = GateRef | GateAllDecl | readonly GateArg[];
20
23
 
21
- /**
22
- * Attach function on an HTTP trigger / resource mount.
23
- * Call it with members, or use `.public` for the unauthenticated sentinel.
24
- */
25
- export interface GateAttach<T> {
26
- (...gates: GateArg[]): T;
27
- /** Attach {@link gate.public} — same as `.gate(gate.public)`. */
28
- readonly public: T;
29
- }
24
+ /** Attach gates on an HTTP trigger / resource mount. */
25
+ export type GateAttach<T> = (...gates: GateArg[]) => T;
30
26
 
31
27
  /**
32
28
  * HTTP trigger value. Method and path are literal type parameters so
@@ -44,9 +40,12 @@ export interface HttpTrigger<M extends HttpMethod = HttpMethod, P extends string
44
40
  readonly isLive: boolean;
45
41
  /**
46
42
  * Attach gates (registration order). `gate.all` handles and arrays flatten.
47
- * `.gate.public` is the unauthenticated sentinel.
48
43
  */
49
44
  readonly gate: GateAttach<HttpTrigger<M, P>>;
45
+ /**
46
+ * Attach the unauthenticated public sentinel.
47
+ */
48
+ public(): HttpTrigger<M, P>;
50
49
  /**
51
50
  * Mark the HTTP trigger as live (push result to subscribers).
52
51
  */
@@ -93,7 +92,7 @@ export type Trigger = HttpTrigger | EveryTrigger | SignalAsTrigger | CdcTrigger
93
92
  export type TriggerKind = Trigger["kind"];
94
93
 
95
94
  /**
96
- * Callable `.gate(...)` plus `.gate.public` on a trigger or resource mount.
95
+ * Callable `.gate(...)` on a trigger or resource mount.
97
96
  *
98
97
  * @param apply - Rebuild the host with the flattened gate list
99
98
  * @param current - Gates already attached
@@ -102,19 +101,11 @@ export function createGateAttach<T>(
102
101
  apply: (next: readonly GateRef[]) => T,
103
102
  current: readonly GateRef[],
104
103
  ): GateAttach<T> {
105
- const attach = ((...next: GateArg[]) =>
106
- apply([...current, ...flattenGateArgs(next)])) as GateAttach<T>;
107
- Object.defineProperty(attach, "public", {
108
- get() {
109
- return apply([...current, GATE_PUBLIC_NAME]);
110
- },
111
- enumerable: true,
112
- });
113
- return attach;
104
+ return (...next: GateArg[]) => apply([...current, ...flattenGateArgs(next)]);
114
105
  }
115
106
 
116
107
  /**
117
- * Build an HTTP trigger with `.gate` / `.live`.
108
+ * Build an HTTP trigger with `.gate` / `.public` / `.live`.
118
109
  *
119
110
  * @param method - HTTP verb
120
111
  * @param path - Route path
@@ -134,6 +125,9 @@ export function createHttpTrigger<M extends HttpMethod, P extends string>(
134
125
  gates,
135
126
  isLive,
136
127
  gate: createGateAttach((next) => createHttpTrigger(method, path, next, isLive), gates),
128
+ public() {
129
+ return createHttpTrigger(method, path, [...gates, GATE_PUBLIC_NAME], isLive);
130
+ },
137
131
  live() {
138
132
  return createHttpTrigger(method, path, gates, true);
139
133
  },
@@ -156,7 +150,7 @@ export interface ResourceFlowBag {
156
150
  /**
157
151
  * A mounted resource — the single argument to the `on(http.resource(…))`
158
152
  * overload. Branded so `on` can tell it apart from a plain trigger.
159
- * Same chain as {@link HttpTrigger}: `.gate(...)` / `.live()`.
153
+ * Same chain as {@link HttpTrigger}: `.gate(...)` / `.public()` / `.live()`.
160
154
  */
161
155
  export interface ResourceMount {
162
156
  readonly [resourceMountBrand]: true;
@@ -166,9 +160,12 @@ export interface ResourceMount {
166
160
  readonly isLive: boolean;
167
161
  /**
168
162
  * Attach gates to every verb (registration order). `gate.all` and arrays flatten.
169
- * `.gate.public` is the unauthenticated sentinel.
170
163
  */
171
164
  readonly gate: GateAttach<ResourceMount>;
165
+ /**
166
+ * Attach the unauthenticated public sentinel to every verb.
167
+ */
168
+ public(): ResourceMount;
172
169
  /**
173
170
  * Mark list and get as live (GET mounts). Mutations stay request/response.
174
171
  */
@@ -200,34 +197,55 @@ function loadHttpResource(): typeof import("./http-resource.ts") {
200
197
  * types for param extraction.
201
198
  */
202
199
  export interface HttpTriggerNamespace {
200
+ /**
201
+ * Pathless — file-tree stamp fills the URL. Unresolved sentinel fails boot.
202
+ */
203
+ get(): HttpTrigger<"GET", HttpPathPending>;
203
204
  /**
204
205
  * @param path - Route path (`/:id` params supported)
205
206
  */
206
207
  get<P extends string>(path: P): HttpTrigger<"GET", P>;
208
+ /** Pathless — file-tree stamp fills the URL. */
209
+ post(): HttpTrigger<"POST", HttpPathPending>;
207
210
  /**
208
211
  * @param path - Route path
209
212
  */
210
213
  post<P extends string>(path: P): HttpTrigger<"POST", P>;
214
+ /** Pathless — file-tree stamp fills the URL. */
215
+ put(): HttpTrigger<"PUT", HttpPathPending>;
211
216
  /**
212
217
  * @param path - Route path
213
218
  */
214
219
  put<P extends string>(path: P): HttpTrigger<"PUT", P>;
220
+ /** Pathless — file-tree stamp fills the URL. */
221
+ patch(): HttpTrigger<"PATCH", HttpPathPending>;
215
222
  /**
216
223
  * @param path - Route path
217
224
  */
218
225
  patch<P extends string>(path: P): HttpTrigger<"PATCH", P>;
226
+ /** Pathless — file-tree stamp fills the URL. */
227
+ delete(): HttpTrigger<"DELETE", HttpPathPending>;
219
228
  /**
220
229
  * @param path - Route path
221
230
  */
222
231
  delete<P extends string>(path: P): HttpTrigger<"DELETE", P>;
232
+ /** Pathless — file-tree stamp fills the URL. */
233
+ options(): HttpTrigger<"OPTIONS", HttpPathPending>;
223
234
  /**
224
235
  * @param path - Route path
225
236
  */
226
237
  options<P extends string>(path: P): HttpTrigger<"OPTIONS", P>;
238
+ /** Pathless — file-tree stamp fills the URL. */
239
+ head(): HttpTrigger<"HEAD", HttpPathPending>;
227
240
  /**
228
241
  * @param path - Route path
229
242
  */
230
243
  head<P extends string>(path: P): HttpTrigger<"HEAD", P>;
244
+ /**
245
+ * Safe, idempotent read that carries a JSON body (RFC 10008).
246
+ * Pathless — file-tree stamp fills the URL.
247
+ */
248
+ query(): HttpTrigger<"QUERY", HttpPathPending>;
231
249
  /**
232
250
  * Safe, idempotent read that carries a JSON body (RFC 10008).
233
251
  *
@@ -236,8 +254,8 @@ export interface HttpTriggerNamespace {
236
254
  query<P extends string>(path: P): HttpTrigger<"QUERY", P>;
237
255
  /**
238
256
  * Mount a CRUD resource (list/create on `path`, get/update/remove on
239
- * `path/:id`) for the `on(http.resource(…))` overload. Chain `.gate(...)`
240
- * and `.live()` like {@link HttpTrigger}.
257
+ * `path/:id`) for the `on(http.resource(…))` overload. Chain `.gate(...)`,
258
+ * `.public()`, and `.live()` like {@link HttpTrigger}.
241
259
  */
242
260
  resource<P extends string>(path: P, ops: ResourceFlowBag): ResourceMount;
243
261
  }
@@ -245,8 +263,15 @@ export interface HttpTriggerNamespace {
245
263
  /** Bind an HTTP verb constructor (`http.get`, `http.query`, …). */
246
264
  function httpVerb<M extends HttpMethod>(
247
265
  method: M,
248
- ): <P extends string>(path: P) => HttpTrigger<M, P> {
249
- return (path) => createHttpTrigger(method, path);
266
+ ): {
267
+ (): HttpTrigger<M, HttpPathPending>;
268
+ <P extends string>(path: P): HttpTrigger<M, P>;
269
+ } {
270
+ return ((path?: string) =>
271
+ createHttpTrigger(method, path === undefined ? HTTP_PATH_PENDING : path)) as {
272
+ (): HttpTrigger<M, HttpPathPending>;
273
+ <P extends string>(path: P): HttpTrigger<M, P>;
274
+ };
250
275
  }
251
276
 
252
277
  /**
@@ -224,10 +224,23 @@ export interface DeclaredColumn extends ColumnClassification {
224
224
  /** Field classification value forms. */
225
225
  export type ClassificationValue = string | string[] | ColumnClassification;
226
226
 
227
+ /** Declared Postgres RLS policy on a {@link Table}. */
228
+ export interface TablePolicy {
229
+ as?: "permissive" | "restrictive";
230
+ to?: string | string[];
231
+ for?: "all" | "select" | "insert" | "update" | "delete";
232
+ using?: string;
233
+ withCheck?: string;
234
+ }
235
+
227
236
  /** SQL table metadata. */
228
237
  export interface Table {
229
238
  columns?: Record<string, DeclaredColumn | ColumnClassification>;
230
239
  classifications?: Record<string, ClassificationValue>;
240
+ /** RLS enabled (`pgTable.withRLS` or any policy). */
241
+ rls?: boolean;
242
+ /** Policy name → Drizzle-shaped spec. */
243
+ policies?: Record<string, TablePolicy>;
231
244
  }
232
245
 
233
246
  /** One Store declaration. */
@@ -201,7 +201,7 @@ export async function measureHttpPingAppBytes(): Promise<HttpPingBudgetSample> {
201
201
  await Bun.write(
202
202
  anchor,
203
203
  `import { on, flow, http, gate, oke, createBunRuntime } from ${JSON.stringify(httpEntry)};\n` +
204
- `on(http.get("/").gate.public, flow("ping", { do: () => "Hi" }));\n` +
204
+ `on(http.get("/").public(), flow("ping", { do: () => "Hi" }));\n` +
205
205
  `export const app = oke({ name: "ping" });\n` +
206
206
  `export const rt = createBunRuntime;\n`,
207
207
  );
@@ -362,7 +362,7 @@ const t0 = performance.now();
362
362
  const { createBunRuntime, oke, on, flow, http, gate } = await import(${JSON.stringify(httpEntry)});
363
363
  const { resetBindings } = await import(${JSON.stringify(`${import.meta.dir}/../kernel/on.ts`)});
364
364
  resetBindings();
365
- on(http.get("/ping").gate.public, flow("ping", { do: () => ({ ok: true }) }));
365
+ on(http.get("/ping").public(), flow("ping", { do: () => ({ ok: true }) }));
366
366
  const app = oke({ name: "cold-start" });
367
367
  const rt = createBunRuntime();
368
368
  const server = rt.serve(app, { port: 0, hostname: "127.0.0.1" });
@@ -255,7 +255,7 @@ describe("httpGetNavPaths", () => {
255
255
  describe("asBrowserJsonCodeBlock — HTTP", () => {
256
256
  test("Chrome GET / becomes the code block; curl stays JSON", async () => {
257
257
  on(
258
- http.get("/").gate.public,
258
+ http.get("/").public(),
259
259
  flow("main.root", {
260
260
  do: () => ({ ok: true as const, app: "keel" }),
261
261
  }),
@@ -286,7 +286,7 @@ describe("asBrowserJsonCodeBlock — HTTP", () => {
286
286
  });
287
287
 
288
288
  test("?raw=1 stays on the traces page; format=json and Accept stay the envelope", async () => {
289
- on(http.get("/health").gate.public, flow("main.health", { do: () => ({ ok: true as const }) }));
289
+ on(http.get("/health").public(), flow("main.health", { do: () => ({ ok: true as const }) }));
290
290
  const app = oke({ autoBoot: false, name: "keel" });
291
291
 
292
292
  const raw = await app.fetch(
@@ -55,13 +55,13 @@ function rawHttp(port: number, payload: string): Promise<string> {
55
55
 
56
56
  function buildApp() {
57
57
  on(
58
- http.get("/ping").gate.public,
58
+ http.get("/ping").public(),
59
59
  flow("ping", {
60
60
  do: () => ({ ok: true as const, n: 1 }),
61
61
  }),
62
62
  );
63
63
  on(
64
- http.get("/notes/:id").gate.public,
64
+ http.get("/notes/:id").public(),
65
65
  flow("notes.get", {
66
66
  do: ({ id }: { id: string }) => ({ id }),
67
67
  }),
@@ -157,13 +157,13 @@ describe("Bun.serve — real HTTP", () => {
157
157
 
158
158
  test("QUERY is omitted from native routes so Bun.serve still boots", async () => {
159
159
  on(
160
- http.query("/search").gate.public,
160
+ http.query("/search").public(),
161
161
  flow("search.query", {
162
162
  do: () => ({ hits: 1 }),
163
163
  }),
164
164
  );
165
165
  on(
166
- http.get("/search/suggest").gate.public,
166
+ http.get("/search/suggest").public(),
167
167
  flow("search.suggest", {
168
168
  do: () => ({ hits: 0 }),
169
169
  }),
package/src/term.test.ts CHANGED
@@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test";
6
6
  import {
7
7
  formatAppReadyLine,
8
8
  formatBootWarn,
9
+ formatBoundSurfaces,
9
10
  formatClaimNote,
10
11
  formatDevNote,
11
12
  formatCliChrome,
@@ -14,6 +15,7 @@ import {
14
15
  formatDevLogSeparator,
15
16
  formatOkeWordmark,
16
17
  formatServiceLine,
18
+ formatSurfaceBox,
17
19
  formatRequestLine,
18
20
  formatStackSummary,
19
21
  countTermLines,
@@ -61,8 +63,8 @@ describe("term", () => {
61
63
  expect(out).toContain("elements");
62
64
  expect(out).toContain("store");
63
65
  expect(out).toContain("postgres");
64
- expect(out).toContain("● flow");
65
- expect(out).toContain("● ai");
66
+ expect(out).toContain("● flow");
67
+ expect(out).toContain("● ai");
66
68
  expect(out).toContain("Starting");
67
69
  expect(out).not.toContain("Ready");
68
70
  expect(out).not.toContain("on(Trigger)");
@@ -134,6 +136,7 @@ describe("term", () => {
134
136
  appUrl: "http://127.0.0.1:6530",
135
137
  consoleUrl: "http://127.0.0.1:6533",
136
138
  mcpUrl: "http://127.0.0.1:6535",
139
+ docsMcpUrl: "http://127.0.0.1:6536",
137
140
  profile: "local",
138
141
  runtimeEnv: "local",
139
142
  system: "darwin 25.4.0 · bun 1.3.14",
@@ -145,11 +148,38 @@ describe("term", () => {
145
148
  expect(out).toContain("Backend");
146
149
  expect(out).toContain("Console");
147
150
  expect(out).toContain("MCP");
151
+ expect(out).toContain("Docs MCP");
148
152
  expect(out).toContain("http://127.0.0.1:6530");
153
+ expect(out).toContain("Your flows, served from the Manifest.");
149
154
  expect(out).toContain("└");
150
155
  expect(out).toContain("Logs");
151
156
  });
152
157
 
158
+ test("formatSurfaceBox prints url and purpose", () => {
159
+ const out = formatSurfaceBox({
160
+ label: "Backend",
161
+ url: "http://127.0.0.1:6530",
162
+ color: false,
163
+ });
164
+ expect(out).toContain("Backend");
165
+ expect(out).toContain("http://127.0.0.1:6530");
166
+ expect(out).toContain("Your flows, served from the Manifest.");
167
+ expect(out).toMatch(/● {2}Backend\n│ {2}http:\/\/127\.0\.0\.1:6530\n│ {2}Your flows/);
168
+ });
169
+
170
+ test("formatBoundSurfaces skips unbound MCP and Docs MCP", () => {
171
+ const out = formatBoundSurfaces({
172
+ appUrl: "http://127.0.0.1:6530",
173
+ consoleUrl: "http://127.0.0.1:6533",
174
+ color: false,
175
+ });
176
+ expect(out).toContain("Backend");
177
+ expect(out).toContain("Console");
178
+ expect(out).toContain("Panels, traces, and the effect graph.");
179
+ expect(out).not.toContain("Docs MCP");
180
+ expect(out).not.toContain("The same Manifest, for agents.");
181
+ });
182
+
153
183
  test("formatDevLogSeparator closes hero and titles Logs", () => {
154
184
  const out = formatDevLogSeparator(false);
155
185
  expect(out).toContain("│");
@@ -280,10 +310,28 @@ describe("term", () => {
280
310
  expect(out).toContain("Docker");
281
311
  expect(out).toContain(":15975");
282
312
  expect(out).toContain("gemma4:e4b-q4_K_M");
283
- expect(out).toContain("● postgres");
284
- expect(out).toContain("● ai");
313
+ expect(out).toContain("● postgres");
314
+ expect(out).toContain("● ai");
285
315
  // Blank │ row separates the Docker header from whatever sits above.
286
316
  expect(out).toMatch(/│\n◇ {2}Docker/);
287
317
  expect(out).not.toMatch(/\u001b\[/);
288
318
  });
319
+
320
+ test("formatStackSummary aligns name and port columns", () => {
321
+ const out = formatStackSummary({
322
+ project: "oke-dev-a3f791",
323
+ services: [
324
+ { label: "ai", hostPort: 23850, status: "ready" },
325
+ { label: "store.index", hostPort: 8550, status: "ready" },
326
+ { label: "postgres", hostPort: 15850, status: "ready" },
327
+ ],
328
+ color: false,
329
+ });
330
+ const portCols = out
331
+ .split("\n")
332
+ .filter((line) => /●\s+\S/.test(line) && line.includes(":"))
333
+ .map((line) => line.search(/:\d/));
334
+ expect(portCols.length).toBe(3);
335
+ expect(new Set(portCols).size).toBe(1);
336
+ });
289
337
  });
package/src/term.ts CHANGED
@@ -112,6 +112,32 @@ export type DevHeroElement = {
112
112
  readonly status?: DevStatus;
113
113
  };
114
114
 
115
+ /** Name column for Ready / Docker / bind rows — fits `store.index`. */
116
+ const HERO_NAME_MIN = 12;
117
+
118
+ /**
119
+ * Widest name column that still fits every label (never below {@link HERO_NAME_MIN}).
120
+ *
121
+ * @param names - Row labels in the table
122
+ */
123
+ function heroNameWidth(names: readonly string[]): number {
124
+ let width = HERO_NAME_MIN;
125
+ for (const name of names) {
126
+ if (name.length > width) width = name.length;
127
+ }
128
+ return width;
129
+ }
130
+
131
+ /**
132
+ * Left-align a table cell.
133
+ *
134
+ * @param value - Cell text
135
+ * @param width - Column width
136
+ */
137
+ function padHeroCol(value: string, width: number): string {
138
+ return value.padEnd(width);
139
+ }
140
+
115
141
  /** Shared options for the `oke dev` hero / banner. */
116
142
  export type DevHeroMeta = {
117
143
  /** `local` · `docker` · `test` · `production` */
@@ -158,18 +184,24 @@ export function formatOkeWordmark(color: boolean = termColorEnabled()): string {
158
184
  export function formatDevHeroDetails(options: DevHeroMeta = {}): string {
159
185
  const s = termStyle(options.color ?? termColorEnabled());
160
186
  const bar = `${s.dim}│${s.reset}`;
161
- const label = (name: string) => `${s.dim}${name.padEnd(9)}${s.reset}`;
187
+ const elements = options.elements ?? [];
188
+ const nameWidth = heroNameWidth([
189
+ ...(options.profile ? ["profile"] : []),
190
+ ...(options.runtimeEnv ? ["env"] : []),
191
+ ...(options.system ? ["system"] : []),
192
+ ...elements.map((row) => row.element),
193
+ ]);
194
+ const label = (name: string) => `${s.dim}${padHeroCol(name, nameWidth)}${s.reset}`;
162
195
  const lines: string[] = [];
163
196
  if (options.profile) {
164
- lines.push(`${bar} ${label("profile")} ${s.cyan}${options.profile}${s.reset}`);
197
+ lines.push(`${bar} ${label("profile")} ${s.cyan}${options.profile}${s.reset}`);
165
198
  }
166
199
  if (options.runtimeEnv) {
167
- lines.push(`${bar} ${label("env")} ${s.cyan}${options.runtimeEnv}${s.reset}`);
200
+ lines.push(`${bar} ${label("env")} ${s.cyan}${options.runtimeEnv}${s.reset}`);
168
201
  }
169
202
  if (options.system) {
170
- lines.push(`${bar} ${label("system")} ${s.dim}${options.system}${s.reset}`);
203
+ lines.push(`${bar} ${label("system")} ${s.dim}${options.system}${s.reset}`);
171
204
  }
172
- const elements = options.elements ?? [];
173
205
  if (elements.length > 0) {
174
206
  lines.push(`${bar} ${s.dim}elements${s.reset}`);
175
207
  const color = options.color ?? termColorEnabled();
@@ -182,8 +214,10 @@ export function formatDevHeroDetails(options: DevHeroMeta = {}): string {
182
214
  ? ""
183
215
  : `${s.dim}—${s.reset}`
184
216
  : `${s.cyan}${row.detail}${s.reset}`;
185
- const detailPart = detail ? ` ${detail}` : "";
186
- lines.push(`${bar} ${dot} ${s.dim}${row.element.padEnd(9)}${s.reset}${detailPart}`);
217
+ const detailPart = detail ? ` ${detail}` : "";
218
+ lines.push(
219
+ `${bar} ${dot} ${s.dim}${padHeroCol(row.element, nameWidth)}${s.reset}${detailPart}`,
220
+ );
187
221
  }
188
222
  }
189
223
  return lines.length > 0 ? `${lines.join("\n")}\n` : "";
@@ -454,7 +488,19 @@ export function createAnchoredBoard(
454
488
  }
455
489
 
456
490
  /**
457
- * One service URL line (Console / MCP after bind).
491
+ * One-line purpose for each `oke dev` bind surface.
492
+ *
493
+ * Same wording as the site PORTS mnemonic — keep them in lockstep.
494
+ */
495
+ export const DEV_SURFACE_DETAILS: Readonly<Record<string, string>> = {
496
+ Backend: "Your flows, served from the Manifest.",
497
+ Console: "Panels, traces, and the effect graph.",
498
+ MCP: "The same Manifest, for agents.",
499
+ "Docs MCP": "Read-only docs search for agents.",
500
+ };
501
+
502
+ /**
503
+ * One service URL line (Console / MCP after bind, before the ready board).
458
504
  *
459
505
  * @param label - Surface name
460
506
  * @param url - Absolute URL
@@ -466,19 +512,73 @@ export function formatServiceLine(
466
512
  color: boolean = termColorEnabled(),
467
513
  ): string {
468
514
  const s = termStyle(color);
469
- const pad = label.padEnd(7);
515
+ const pad = padHeroCol(label, HERO_NAME_MIN);
470
516
  return `${s.dim}│${s.reset} ${s.dim}${pad}${s.reset} ${s.cyan}${url}${s.reset}\n`;
471
517
  }
472
518
 
473
519
  /**
474
520
  * Backend child ready line (`bun --hot` soft reload).
475
521
  *
522
+ * Stays one line so Logs do not fill with surface boxes on every reload.
523
+ *
476
524
  * @param url - Backend base URL
477
525
  * @param color - Color on/off
478
526
  */
479
527
  export function formatAppReadyLine(url: string, color: boolean = termColorEnabled()): string {
480
528
  const s = termStyle(color);
481
- return `${s.green}●${s.reset} ${s.dim}${"Backend".padEnd(7)}${s.reset} ${s.cyan}${url}${s.reset}\n`;
529
+ return `${s.green}●${s.reset} ${s.dim}${padHeroCol("Backend", HERO_NAME_MIN)}${s.reset} ${s.cyan}${url}${s.reset}\n`;
530
+ }
531
+
532
+ /**
533
+ * One bound-surface box on the ready board — title, URL, purpose.
534
+ *
535
+ * @param options - Label, URL, optional detail override
536
+ */
537
+ export function formatSurfaceBox(options: {
538
+ readonly label: string;
539
+ readonly url: string;
540
+ readonly detail?: string;
541
+ readonly color?: boolean;
542
+ }): string {
543
+ const s = termStyle(options.color ?? termColorEnabled());
544
+ const bar = `${s.dim}│${s.reset}`;
545
+ const detail = (options.detail ?? DEV_SURFACE_DETAILS[options.label] ?? "").trim();
546
+ const lines = [
547
+ `${s.green}●${s.reset} ${s.bold}${options.label}${s.reset}`,
548
+ `${bar} ${s.cyan}${options.url}${s.reset}`,
549
+ ];
550
+ if (detail.length > 0) {
551
+ lines.push(`${bar} ${s.dim}${detail}${s.reset}`);
552
+ }
553
+ lines.push(bar);
554
+ return `${lines.join("\n")}\n`;
555
+ }
556
+
557
+ /**
558
+ * Backend · Console · MCP · Docs MCP boxes after bind.
559
+ *
560
+ * Pass `mcpUrl` / `docsMcpUrl` only when that surface came up — omitted
561
+ * URLs skip the box.
562
+ *
563
+ * @param options - Bound surface URLs
564
+ */
565
+ export function formatBoundSurfaces(options: {
566
+ readonly appUrl: string;
567
+ readonly consoleUrl: string;
568
+ readonly mcpUrl?: string | null;
569
+ readonly docsMcpUrl?: string | null;
570
+ readonly color?: boolean;
571
+ }): string {
572
+ const color = options.color ?? termColorEnabled();
573
+ let out = formatSurfaceBox({ label: "Backend", url: options.appUrl, color });
574
+ out += formatSurfaceBox({ label: "Console", url: options.consoleUrl, color });
575
+ if (options.mcpUrl) {
576
+ out += formatSurfaceBox({ label: "MCP", url: options.mcpUrl, color });
577
+ }
578
+ if (options.docsMcpUrl) {
579
+ out += formatSurfaceBox({ label: "Docs MCP", url: options.docsMcpUrl, color });
580
+ }
581
+ return out;
482
582
  }
483
583
 
484
584
  /**
@@ -499,6 +599,7 @@ export function formatDevHero(
499
599
  readonly appUrl: string;
500
600
  readonly consoleUrl: string;
501
601
  readonly mcpUrl: string;
602
+ readonly docsMcpUrl?: string;
502
603
  },
503
604
  ): string {
504
605
  const color = options.color ?? termColorEnabled();
@@ -511,9 +612,13 @@ export function formatDevHero(
511
612
  `\n${bar}\n` +
512
613
  formatDevHeroDetails({ ...options, color }) +
513
614
  `${bar}\n` +
514
- formatAppReadyLine(options.appUrl, color) +
515
- formatServiceLine("Console", options.consoleUrl, color) +
516
- formatServiceLine("MCP", options.mcpUrl, color) +
615
+ formatBoundSurfaces({
616
+ appUrl: options.appUrl,
617
+ consoleUrl: options.consoleUrl,
618
+ mcpUrl: options.mcpUrl,
619
+ docsMcpUrl: options.docsMcpUrl,
620
+ color,
621
+ }) +
517
622
  formatDevLogSeparator(color)
518
623
  );
519
624
  }
@@ -767,25 +872,28 @@ export function formatStackSummary(options: {
767
872
  }): string {
768
873
  const s = termStyle(options.color ?? termColorEnabled());
769
874
  const bar = `${s.dim}│${s.reset}`;
770
- const pad = (label: string) => label.padEnd(8);
875
+ const nameWidth = heroNameWidth(["Docker", "app", ...options.services.map((svc) => svc.label)]);
876
+ const portWidth = Math.max(6, ...options.services.map((svc) => `:${svc.hostPort}`.length));
877
+ const pad = (label: string) => padHeroCol(label, nameWidth);
771
878
  const color = options.color ?? termColorEnabled();
772
879
  const lines: string[] = [
773
880
  `${s.dim}│${s.reset}`,
774
- `${s.green}◇${s.reset} ${s.bold}Docker${s.reset} ${s.cyan}${options.project}${s.reset}`,
881
+ `${s.green}◇${s.reset} ${s.bold}${pad("Docker")}${s.reset} ${s.cyan}${options.project}${s.reset}`,
775
882
  ];
776
883
  for (const svc of options.services) {
777
884
  const detail = svc.detail?.trim();
778
885
  const status = svc.status ?? "pending";
779
886
  const dot = formatStatusDot(status, color);
887
+ const port = `:${svc.hostPort}`.padEnd(portWidth);
780
888
  lines.push(
781
- `${bar} ${dot} ${s.dim}${pad(svc.label)}${s.reset} ${s.cyan}:${svc.hostPort}${s.reset}` +
889
+ `${bar} ${dot} ${s.dim}${pad(svc.label)}${s.reset} ${s.cyan}${port}${s.reset}` +
782
890
  (detail ? ` ${s.dim}${detail}${s.reset}` : ""),
783
891
  );
784
892
  }
785
893
  const drivers = options.appDrivers ?? [];
786
894
  const appDetail = drivers.length > 0 ? `host Bun · ${drivers.join(" + ")}` : "host Bun";
787
895
  const appDot = formatStatusDot("ready", color);
788
- lines.push(`${bar} ${appDot} ${s.dim}${pad("app")}${s.reset} ${s.dim}${appDetail}${s.reset}`);
896
+ lines.push(`${bar} ${appDot} ${s.dim}${pad("app")}${s.reset} ${s.dim}${appDetail}${s.reset}`);
789
897
  lines.push(bar);
790
898
  return `${lines.join("\n")}\n`;
791
899
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Global safety net for the `store.sql` / `store.files` / `vault.secret` /
3
- * `signal()` / `channel.<medium>().template()` / `ai.model`·prompt·embed·agent
4
- * auto-registries (`src/kernel/element-registries.ts`).
2
+ * Global safety net for the `store.*` / `vault.secret` / `signal()` /
3
+ * `clock()` / `gate.policy`·scope·rate / `channel.<medium>().template()` /
4
+ * `ai.model`·prompt·embed·agent auto-registries
5
+ * (`src/kernel/element-registries.ts`).
5
6
  *
6
7
  * Unlike `on()` bindings — created almost exclusively to wire a real app —
7
8
  * these factories are called throughout the suite as bare value constructors,
@@ -23,6 +24,8 @@ import {
23
24
  aiModelRegistry,
24
25
  aiPromptRegistry,
25
26
  channelTemplateRegistry,
27
+ clockRegistry,
28
+ gateRegistry,
26
29
  requiredEnvRegistry,
27
30
  secretRegistry,
28
31
  signalRegistry,
@@ -34,6 +37,8 @@ afterEach(() => {
34
37
  secretRegistry.length = 0;
35
38
  requiredEnvRegistry.length = 0;
36
39
  signalRegistry.length = 0;
40
+ clockRegistry.length = 0;
41
+ gateRegistry.length = 0;
37
42
  channelTemplateRegistry.length = 0;
38
43
  aiModelRegistry.length = 0;
39
44
  aiPromptRegistry.length = 0;