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
@@ -26,6 +26,11 @@ export {
26
26
  schema,
27
27
  schemaTable,
28
28
  schemaRelations,
29
+ schemaRls,
30
+ schemaPolicy,
31
+ schemaPolicyGate,
32
+ schemaPolicyOwner,
33
+ schemaPolicyScope,
29
34
  isSchemaTableDecl,
30
35
  isSchemaColumnDecl,
31
36
  isSchemaRelationsDecl,
@@ -43,6 +48,8 @@ export type {
43
48
  SchemaTableDecl,
44
49
  SchemaTableWithColumns,
45
50
  SchemaRelationsDecl,
51
+ SchemaPolicyDecl,
52
+ SchemaTableExtra,
46
53
  SchemaRelationEntry,
47
54
  SchemaRelationOne,
48
55
  SchemaRelationMany,
@@ -123,6 +130,13 @@ export type {
123
130
  WhereMap,
124
131
  } from "./store/sql-session.ts";
125
132
 
133
+ export {
134
+ resolveRlsIdentity,
135
+ rlsIdentityFromAuth,
136
+ firstPolicyOrPublic,
137
+ policyGateForScopes,
138
+ } from "./store/rls-identity.ts";
139
+ export type { RlsIdentityRow, ResolveRlsIdentityInput } from "./store/rls-identity.ts";
126
140
  export { createStoreRuntime, putTier2 } from "./store/runtime.ts";
127
141
  export type {
128
142
  StoreRuntime,
package/src/http.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * ```ts
9
9
  * import { on, flow, http, gate, oke, createBunRuntime } from "okengine/http";
10
10
  *
11
- * on(http.get("/").gate.public, flow("ping", { do: () => "Hi" }));
11
+ * on(http.get("/").public(), flow("ping", { do: () => "Hi" }));
12
12
  * const app = oke({ name: "ping" });
13
13
  * createBunRuntime().serve(app);
14
14
  * ```
@@ -24,15 +24,32 @@ export { fail, type FlowFailure } from "./kernel/errors.ts";
24
24
  export { isFlowFailure } from "./kernel/hooks.ts";
25
25
  export { plugin, isPlugin, type PluginDef } from "./kernel/plugin.ts";
26
26
  export { createBunRuntime, APP_PORT, type Runtime, type ServeOptions } from "./runtime/index.ts";
27
- export type { OkeApp, OkeOptions, ReadyState } from "./kernel/app.ts";
27
+ export type {
28
+ OkeApp,
29
+ OkeOptions,
30
+ ReadyState,
31
+ RegisteredFlowUnits,
32
+ RoutesFromRegisteredUnits,
33
+ } from "./kernel/app.ts";
34
+ export { registerFlowUnits } from "./kernel/flow-units.ts";
35
+ export { stampFlowName, stampHttpPath } from "./kernel/stamp-http.ts";
28
36
 
29
- import { oke as okeCore, type OkeApp, type OkeOptions } from "./kernel/app.ts";
37
+ import {
38
+ oke as okeCore,
39
+ type OkeApp,
40
+ type OkeOptions,
41
+ type RoutesFromRegisteredUnits,
42
+ } from "./kernel/app.ts";
30
43
 
31
44
  /**
32
45
  * Create an HTTP-oriented app — defaults `router` to `"edge"` when omitted.
33
46
  *
34
47
  * @param options - Application options
35
48
  */
49
+ export function oke(
50
+ options: OkeOptions & { readonly registry: "ignore" },
51
+ ): OkeApp<Record<string, never>, Record<string, never>>;
52
+ export function oke(options: OkeOptions): OkeApp<{}, RoutesFromRegisteredUnits>;
36
53
  export function oke(options: OkeOptions): OkeApp {
37
54
  return okeCore({
38
55
  ...options,
package/src/index.ts CHANGED
@@ -14,10 +14,19 @@
14
14
  * @module
15
15
  */
16
16
 
17
- export { oke, type OkeApp, type OkeOptions, type ReadyState } from "./kernel/app.ts";
17
+ export {
18
+ oke,
19
+ type OkeApp,
20
+ type OkeOptions,
21
+ type ReadyState,
22
+ type RegisteredFlowUnits,
23
+ type RoutesFromRegisteredUnits,
24
+ } from "./kernel/app.ts";
18
25
  export { on, type Binding } from "./kernel/on.ts";
19
26
  export { flow, isFlow, type FlowDef } from "./kernel/flow.ts";
20
27
  export { http, every, internal, table } from "./kernel/triggers.ts";
28
+ export { registerFlowUnits } from "./kernel/flow-units.ts";
29
+ export { stampFlowName, stampHttpPath } from "./kernel/stamp-http.ts";
21
30
  export { fail, type FlowFailure, type FlowErrorValue } from "./kernel/errors.ts";
22
31
  export { isFlowFailure } from "./kernel/hooks.ts";
23
32
  export { plugin, isPlugin, type PluginDef, type PluginCapabilities } from "./kernel/plugin.ts";
@@ -89,7 +89,7 @@ describe("boot-level: stale barrel through a real oke() boot", () => {
89
89
  effects: {},
90
90
  do: () => ({ ok: true as const }),
91
91
  });
92
- on(http.get("/health").gate.public, mainFlow);
92
+ on(http.get("/health").public(), mainFlow);
93
93
  const app = oke({ name: "stale-barrel", gate: { unguardedHttp: "allow" } });
94
94
 
95
95
  await expect(
@@ -39,10 +39,20 @@ export type AppFlowRoute<
39
39
  readonly path?: undefined;
40
40
  };
41
41
 
42
- /** Unit → flow → contract. */
42
+ /**
43
+ * Unit → flow → contract. Leaf is the HTTP / call-only union — do not
44
+ * index with `AppFlowRoute<any, any, any, any, any>` (`any extends string`
45
+ * makes `method`/`path` required and rejects cron / signal flows).
46
+ */
43
47
  export type AppRouteMap = {
44
48
  readonly [unit: string]: {
45
- readonly [flow: string]: AppFlowRoute<any, any, any, any, any>;
49
+ readonly [flow: string]: {
50
+ readonly in?: unknown;
51
+ readonly out?: unknown;
52
+ readonly errors?: unknown;
53
+ readonly method?: string;
54
+ readonly path?: string;
55
+ };
46
56
  };
47
57
  };
48
58
 
@@ -170,6 +180,7 @@ export function accumulateAdoptArgs(args: readonly unknown[], into: RuntimeRoute
170
180
  const unitBag = into[unit] ?? (into[unit] = {});
171
181
  for (const [flowName, flowDef] of Object.entries(value as Record<string, unknown>)) {
172
182
  if (!isFlow(flowDef)) continue;
183
+ stampAdoptedFlow(flowDef, unit, flowName);
173
184
  found.push(flowDef);
174
185
  unitBag[flowName] = runtimeRouteFromFlow(flowDef);
175
186
  }
@@ -178,3 +189,20 @@ export function accumulateAdoptArgs(args: readonly unknown[], into: RuntimeRoute
178
189
 
179
190
  return found;
180
191
  }
192
+
193
+ /**
194
+ * Stamp `unit.export` on nameless flows — same rule as {@link unit}.
195
+ *
196
+ * @param flowDef - Flow to stamp
197
+ * @param unit - Client unit (folder / adopt key)
198
+ * @param exportName - Namespace export
199
+ */
200
+ function stampAdoptedFlow(flowDef: AnyFlowDef, unit: string, exportName: string): void {
201
+ const f = flowDef as { name: string; unit: string | undefined };
202
+ if (!f.name || f.name.startsWith("flow_")) {
203
+ f.name = `${unit}.${exportName}`;
204
+ }
205
+ if (!f.unit) {
206
+ f.unit = unit;
207
+ }
208
+ }
package/src/kernel/app.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  accumulateAdoptArgs,
15
15
  type AppRouteMap,
16
16
  type RoutesFromAdoptArgs,
17
+ type RoutesFromNamespace,
17
18
  type RuntimeRouteMap,
18
19
  } from "./adopt-routes.ts";
19
20
  // `./boot.ts` pulls in every element + driver module (vault, store, signal,
@@ -59,7 +60,8 @@ import {
59
60
  } from "../runtime/dev-request-log.ts";
60
61
  import { asBrowserJsonCodeBlock, httpNavGroups } from "../runtime/json-code-block.ts";
61
62
  import { resolveDurationMs } from "./elapsed.ts";
62
- import { fail } from "./errors.ts";
63
+ import { fail, throwOke } from "./errors.ts";
64
+ import { consumeRegisteredFlowUnits, type FlowUnitBag } from "./flow-units.ts";
63
65
  import {
64
66
  isFlowFailure,
65
67
  mergeHooks,
@@ -91,6 +93,8 @@ import {
91
93
  aiModelRegistry,
92
94
  aiPromptRegistry,
93
95
  channelTemplateRegistry,
96
+ clockRegistry,
97
+ gateRegistry,
94
98
  requiredEnvRegistry,
95
99
  secretRegistry,
96
100
  signalRegistry,
@@ -99,6 +103,7 @@ import {
99
103
  import {
100
104
  applyPrincipal,
101
105
  createElementPipelineHooks,
106
+ gateNamesOf,
102
107
  type PrincipalBag,
103
108
  type ResolvedPrincipal,
104
109
  } from "./pipeline.ts";
@@ -118,6 +123,7 @@ import {
118
123
  type RouterPreset,
119
124
  type SmartRouter,
120
125
  } from "./router.ts";
126
+ import { isPendingHttpPath } from "./http-path-pending.ts";
121
127
  import type {
122
128
  CdcTrigger,
123
129
  EveryTrigger,
@@ -299,6 +305,21 @@ export interface UnitHooks<D extends Record<string, unknown> = {}> {
299
305
  plug<P extends PluginDef>(pluginDef: P): UnitHooks<AccumulateDecorations<D, P>>;
300
306
  }
301
307
 
308
+ /**
309
+ * Module-augmentation slot filled by `src/flows/generated.ts`.
310
+ * Kernel tests do not import an app generated file, so this stays `{}`.
311
+ */
312
+ export interface RegisteredFlowUnits {}
313
+
314
+ /**
315
+ * `$routes` derived from {@link RegisteredFlowUnits} after `import generated`.
316
+ *
317
+ * @typeParam U - Augmented unit map
318
+ */
319
+ export type RoutesFromRegisteredUnits<U = RegisteredFlowUnits> = {
320
+ [K in keyof U]: U[K] extends Record<string, unknown> ? RoutesFromNamespace<U[K]> : never;
321
+ };
322
+
302
323
  /**
303
324
  * Application instance — adopts flows, routes HTTP, runs the pipeline.
304
325
  *
@@ -435,6 +456,8 @@ export interface OkeApp<D extends Record<string, unknown> = {}, R extends AppRou
435
456
  * Takes effect only with {@link trustedInvoke}.
436
457
  */
437
458
  readonly revealPii?: boolean;
459
+ /** Forced RLS bag (Console / Call API). Operator omit = no stamp. */
460
+ readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
438
461
  /** Parent WideEvent id when this execution was caused by another. */
439
462
  readonly parentId?: string;
440
463
  /** Explicit run / WideEvent id (defaults to a new UUID). */
@@ -562,11 +585,57 @@ function mergeAiOptions(
562
585
  };
563
586
  }
564
587
 
588
+ /**
589
+ * Refuse an unresolved path sentinel or a nameless HTTP flow.
590
+ *
591
+ * @param binding - HTTP binding
592
+ */
593
+ function assertHttpBindingReady(binding: Binding): void {
594
+ const trigger = binding.trigger;
595
+ if (trigger.kind !== "http") return;
596
+ if (isPendingHttpPath(trigger.path)) {
597
+ throwOke("HTTP_PATH_UNRESOLVED", {
598
+ flow: binding.flow.name || "(unnamed)",
599
+ method: trigger.method,
600
+ });
601
+ }
602
+ if (!binding.flow.name || binding.flow.name.startsWith("flow_")) {
603
+ throwOke("HTTP_FLOW_UNNAMED", {
604
+ method: trigger.method,
605
+ path: trigger.path,
606
+ });
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Fold `generated.ts` units into `$routes` and `flowsByName`.
612
+ *
613
+ * @param units - Drained {@link registerFlowUnits} bag
614
+ * @param routes - Runtime `$routes`
615
+ * @param registerFlow - App flow index
616
+ */
617
+ function drainGeneratedUnits(
618
+ units: Record<string, FlowUnitBag>,
619
+ routes: RuntimeRouteMap,
620
+ registerFlow: (flowDef: AnyFlowDef) => void,
621
+ ): void {
622
+ if (Object.keys(units).length === 0) return;
623
+ const flows = accumulateAdoptArgs([units], routes);
624
+ for (const flowDef of flows) {
625
+ registerFlow(flowDef);
626
+ }
627
+ }
628
+
565
629
  /**
566
630
  * Create an application. Adopts bindings registered via {@link on}.
631
+ * Drains {@link registerFlowUnits} into `$routes` unless `registry: "ignore"`.
567
632
  *
568
633
  * @param options - App name and router preset
569
634
  */
635
+ export function oke(
636
+ options: OkeOptions & { readonly registry: "ignore" },
637
+ ): OkeApp<Record<string, never>, Record<string, never>>;
638
+ export function oke(options: OkeOptions): OkeApp<{}, RoutesFromRegisteredUnits>;
570
639
  export function oke(options: OkeOptions): OkeApp {
571
640
  const aot = options.aot !== false;
572
641
  const registry = options.registry ?? "consume";
@@ -576,11 +645,12 @@ export function oke(options: OkeOptions): OkeApp {
576
645
  : [...listBindings(), ...(options.bindings ?? [])];
577
646
  if (registry === "consume") resetBindings();
578
647
 
579
- // Same registry mode drains store.sql/store.files, vault.secret, signal(),
580
- // channel.<medium>().template(), and ai.model/prompt/embed/agent/mcpServer —
581
- // module-evaluation registries that mirror `on`'s trigger drain
582
- // (`listBindings`/`resetBindings` above). Explicit `options.stores` /
583
- // `secrets` / `signals` / `channel.templates` / `ai` are additive, never
648
+ // Same registry mode drains store.*, vault.secret, signal(), clock(),
649
+ // gate.policy/scope/rate, channel.<medium>().template(), and
650
+ // ai.model/prompt/embed/agent/mcpServer — module-evaluation registries
651
+ // that mirror `on`'s trigger drain (`listBindings`/`resetBindings` above).
652
+ // Explicit `options.stores` / `secrets` / `signals` / `clocks` /
653
+ // `gate.policies` / `channel.templates` / `ai` are additive, never
584
654
  // silently ignored — deduped by reference so an explicitly-passed decl
585
655
  // that is also in the registry is not doubled.
586
656
  const registrySnapshot =
@@ -590,6 +660,8 @@ export function oke(options: OkeOptions): OkeApp {
590
660
  secrets: [],
591
661
  requiredEnv: [],
592
662
  signals: [],
663
+ clocks: [],
664
+ gates: [],
593
665
  channelTemplates: [],
594
666
  aiModels: [],
595
667
  aiPrompts: [],
@@ -602,6 +674,8 @@ export function oke(options: OkeOptions): OkeApp {
602
674
  secrets: secretRegistry.slice(),
603
675
  requiredEnv: requiredEnvRegistry.slice(),
604
676
  signals: signalRegistry.slice(),
677
+ clocks: clockRegistry.slice(),
678
+ gates: gateRegistry.slice(),
605
679
  channelTemplates: channelTemplateRegistry.slice(),
606
680
  aiModels: aiModelRegistry.slice(),
607
681
  aiPrompts: aiPromptRegistry.slice(),
@@ -614,6 +688,8 @@ export function oke(options: OkeOptions): OkeApp {
614
688
  secretRegistry.length = 0;
615
689
  requiredEnvRegistry.length = 0;
616
690
  signalRegistry.length = 0;
691
+ clockRegistry.length = 0;
692
+ gateRegistry.length = 0;
617
693
  channelTemplateRegistry.length = 0;
618
694
  aiModelRegistry.length = 0;
619
695
  aiPromptRegistry.length = 0;
@@ -624,6 +700,8 @@ export function oke(options: OkeOptions): OkeApp {
624
700
  const effectiveStores = mergeUnique(options.stores, registrySnapshot.stores);
625
701
  const effectiveSecrets = mergeUnique(options.secrets, registrySnapshot.secrets);
626
702
  const effectiveSignals = mergeUnique(options.signals, registrySnapshot.signals);
703
+ const effectiveClocks = mergeUnique(options.clocks, registrySnapshot.clocks);
704
+ const effectivePolicies = mergeUnique(options.gate?.policies, registrySnapshot.gates);
627
705
  /**
628
706
  * Fold registered `vault.env.required` names into the vault boot options so
629
707
  * boot reports missing env vars in the same gap list as missing secrets.
@@ -658,16 +736,23 @@ export function oke(options: OkeOptions): OkeApp {
658
736
 
659
737
  // Resolve Gate bag early so auth HTTP Bindings join `adopted` + the router
660
738
  // before posture audit (same ensureBoot → doBoot path — never a side channel).
739
+ const mergedGate: GateOptions | undefined =
740
+ effectivePolicies.length > 0 ? { ...options.gate, policies: effectivePolicies } : options.gate;
661
741
  const gateConfig: ResolvedGateConfig = resolveGateConfig({
662
- gate: options.gate,
742
+ gate: mergedGate,
663
743
  env: options.env,
664
744
  });
665
745
 
666
- /** Retained for test harness / boot merges (includes auto-drained AI decls). */
667
- const $options: OkeOptions =
668
- effectiveAi === undefined || options.ai === effectiveAi
669
- ? options
670
- : { ...options, ai: effectiveAi };
746
+ /** Retained for test harness / boot merges (includes auto-drained decls). */
747
+ const $options: OkeOptions = {
748
+ ...options,
749
+ ...(effectiveAi !== undefined ? { ai: effectiveAi } : {}),
750
+ ...(effectiveStores.length > 0 ? { stores: effectiveStores } : {}),
751
+ ...(effectiveSecrets.length > 0 ? { secrets: effectiveSecrets } : {}),
752
+ ...(effectiveSignals.length > 0 ? { signals: effectiveSignals } : {}),
753
+ ...(effectiveClocks.length > 0 ? { clocks: effectiveClocks } : {}),
754
+ ...(mergedGate !== undefined ? { gate: mergedGate } : {}),
755
+ };
671
756
 
672
757
  const appHooks: HookMap = {};
673
758
  const unitHooks = new Map<string, HookMap>();
@@ -701,8 +786,19 @@ export function oke(options: OkeOptions): OkeApp {
701
786
  }
702
787
 
703
788
  const smart = createRouter<Binding>(options.router ?? "default");
789
+ const seenHttpRoutes = new Set<string>();
704
790
  for (const b of adopted) {
705
791
  if (b.trigger.kind === "http") {
792
+ assertHttpBindingReady(b);
793
+ const key = `${b.trigger.method} ${b.trigger.path}`;
794
+ if (seenHttpRoutes.has(key)) {
795
+ throwOke("HTTP_ROUTE_DUPLICATE", {
796
+ method: b.trigger.method,
797
+ path: b.trigger.path,
798
+ flow: b.flow.name || "(unnamed)",
799
+ });
800
+ }
801
+ seenHttpRoutes.add(key);
706
802
  smart.add(b.trigger.method, b.trigger.path, b);
707
803
  compiled.set(b, compileHttpBinding(b, aot));
708
804
  }
@@ -715,6 +811,16 @@ export function oke(options: OkeOptions): OkeApp {
715
811
  adopted.push(b);
716
812
  registerFlow(b.flow);
717
813
  if (b.trigger.kind === "http") {
814
+ assertHttpBindingReady(b);
815
+ const key = `${b.trigger.method} ${b.trigger.path}`;
816
+ if (seenHttpRoutes.has(key)) {
817
+ throwOke("HTTP_ROUTE_DUPLICATE", {
818
+ method: b.trigger.method,
819
+ path: b.trigger.path,
820
+ flow: b.flow.name || "(unnamed)",
821
+ });
822
+ }
823
+ seenHttpRoutes.add(key);
718
824
  smart.add(b.trigger.method, b.trigger.path, b);
719
825
  compiled.set(b, compileHttpBinding(b, aot));
720
826
  }
@@ -788,6 +894,11 @@ export function oke(options: OkeOptions): OkeApp {
788
894
  registerFlow(b.flow);
789
895
  }
790
896
 
897
+ if (registry !== "ignore") {
898
+ const generatedUnits = consumeRegisteredFlowUnits();
899
+ drainGeneratedUnits(generatedUnits, routes, registerFlow);
900
+ }
901
+
791
902
  // --- boot (vault → store → signal → clock → channel → AI → runs → caps) ---
792
903
  let bootResult: BootResult | undefined;
793
904
  let bootPromise: Promise<BootResult> | undefined;
@@ -963,7 +1074,7 @@ export function oke(options: OkeOptions): OkeApp {
963
1074
  vault: withRequiredEnv(overrides?.vault ?? options.vault),
964
1075
  gates: [...baseGates, ...authGates],
965
1076
  signals: overrides?.signals ?? effectiveSignals,
966
- clocks: overrides?.clocks ?? options.clocks,
1077
+ clocks: overrides?.clocks ?? effectiveClocks,
967
1078
  stores: overrides?.stores ?? effectiveStores,
968
1079
  channel: overrides?.channel ?? effectiveChannel,
969
1080
  ai: overrides?.ai ?? effectiveAi,
@@ -1019,7 +1130,7 @@ export function oke(options: OkeOptions): OkeApp {
1019
1130
 
1020
1131
  const baseSecrets = overrides?.secrets ?? effectiveSecrets;
1021
1132
  const baseSignals = overrides?.signals ?? effectiveSignals;
1022
- const baseClocks = overrides?.clocks ?? options.clocks ?? [];
1133
+ const baseClocks = overrides?.clocks ?? effectiveClocks;
1023
1134
  const baseChannel = overrides?.channel ?? effectiveChannel;
1024
1135
  const mergedCatalog = mergeTemplateCatalogs(baseChannel?.catalog, ...pluginChannelCatalogs);
1025
1136
 
@@ -1138,6 +1249,8 @@ export function oke(options: OkeOptions): OkeApp {
1138
1249
  readonly bypassGates?: boolean;
1139
1250
  /** Console Call API — cleartext store PII. Requires {@link trustedInvoke}. */
1140
1251
  readonly revealPii?: boolean;
1252
+ /** Forced RLS bag (Console / Call API). Operator omit = no stamp. */
1253
+ readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
1141
1254
  /** Frozen origin identity for {@link Fx.principal} across `fx.call`. */
1142
1255
  readonly originPrincipal?: FxPrincipal;
1143
1256
  /** Explicit locale override for {@link Fx.t} / channel sends. */
@@ -1189,6 +1302,7 @@ export function oke(options: OkeOptions): OkeApp {
1189
1302
  readonly trustedInvoke?: boolean;
1190
1303
  readonly bypassGates?: boolean;
1191
1304
  readonly revealPii?: boolean;
1305
+ readonly rls?: import("../drivers/pg-rls.ts").RlsIdentity;
1192
1306
  readonly originPrincipal?: FxPrincipal;
1193
1307
  readonly locale?: string;
1194
1308
  readonly parentId?: string;
@@ -1339,6 +1453,9 @@ export function oke(options: OkeOptions): OkeApp {
1339
1453
  ...(principals ? { auth: principals.auth as FxAuth, operator: principals.operator } : {}),
1340
1454
  ...(extras?.originPrincipal ? { principal: extras.originPrincipal } : {}),
1341
1455
  ...(extras?.trustedInvoke === true && extras.revealPii === true ? { revealPii: true } : {}),
1456
+ rlsGateNames: gateNamesOf(trigger),
1457
+ rlsBypass: extras?.bypassGates === true || flowDef.plane === "operator",
1458
+ ...(extras?.rls ? { rls: extras.rls } : {}),
1342
1459
  ...(capability ? { capability } : {}),
1343
1460
  ...(booted
1344
1461
  ? {
@@ -44,7 +44,7 @@ describe("automatic tier-1 cache from effects", () => {
44
44
  const db = store.sql("app", { schema: { notes } });
45
45
  let lists = 0;
46
46
  const list = on(
47
- http.get("/notes").gate.public,
47
+ http.get("/notes").public(),
48
48
  flow("notes.list", {
49
49
  out: z.array(NoteOut),
50
50
  effects: { reads: ["sql:notes"] },
@@ -80,7 +80,7 @@ describe("automatic tier-1 cache from effects", () => {
80
80
  resetFlowSeq();
81
81
  const db = store.sql("app", { schema: { notes } });
82
82
  const list = on(
83
- http.get("/notes").gate.public,
83
+ http.get("/notes").public(),
84
84
  flow("notes.list", {
85
85
  out: z.array(NoteOut),
86
86
  effects: { reads: ["sql:notes"] },
@@ -115,7 +115,7 @@ describe("automatic tier-1 cache from effects", () => {
115
115
  const db = store.sql("app", { schema: { notes } });
116
116
  let lists = 0;
117
117
  const list = on(
118
- http.get("/notes").gate.public,
118
+ http.get("/notes").public(),
119
119
  flow("notes.list", {
120
120
  cache: false,
121
121
  effects: { reads: ["sql:notes"] },
@@ -146,7 +146,7 @@ describe("automatic tier-1 cache from effects", () => {
146
146
  const db = store.sql("app", { schema: { notes } });
147
147
  let lists = 0;
148
148
  const list = on(
149
- http.get("/notes").gate.public,
149
+ http.get("/notes").public(),
150
150
  flow("notes.list", {
151
151
  out: z.array(NoteOut),
152
152
  do: async (_input, fx) => {
@@ -191,7 +191,7 @@ describe("automatic tier-1 cache from effects", () => {
191
191
  const db = store.sql("app", { schema: { notes } });
192
192
  let lists = 0;
193
193
  const list = on(
194
- http.get("/notes").gate.public,
194
+ http.get("/notes").public(),
195
195
  flow("notes.list", {
196
196
  effects: { reads: ["sql:notes"] },
197
197
  do: () => {
@@ -201,7 +201,7 @@ describe("automatic tier-1 cache from effects", () => {
201
201
  }),
202
202
  );
203
203
  const create = on(
204
- http.post("/notes").gate.public,
204
+ http.post("/notes").public(),
205
205
  flow("notes.create", {
206
206
  in: z.object({ title: z.string() }),
207
207
  effects: { writes: ["sql:notes"] },
@@ -231,7 +231,7 @@ describe("automatic tier-1 cache from effects", () => {
231
231
  const db = store.sql("app", { schema: { notes } });
232
232
  let lists = 0;
233
233
  const list = on(
234
- http.get("/notes").gate.public,
234
+ http.get("/notes").public(),
235
235
  flow("notes.list", {
236
236
  out: z.array(NoteOut),
237
237
  do: async (_input, fx) => {
@@ -242,7 +242,7 @@ describe("automatic tier-1 cache from effects", () => {
242
242
  }),
243
243
  );
244
244
  const create = on(
245
- http.post("/notes").gate.public,
245
+ http.post("/notes").public(),
246
246
  flow("notes.create", {
247
247
  in: z.object({ title: z.string() }),
248
248
  do: async (input, fx) => {
@@ -1,17 +1,21 @@
1
1
  /**
2
- * `oke({...})` auto-populates `stores` / `secrets` / `signals` /
3
- * `channel.templates` from the same kind of module-evaluation registry
4
- * already proven for `on()`'s trigger drain (`registry-isolation.test.ts`).
2
+ * `oke({...})` auto-populates `stores` / `secrets` / `signals` / `clocks` /
3
+ * `gate.policies` / `channel.templates` from the same kind of
4
+ * module-evaluation registry already proven for `on()`'s trigger drain
5
+ * (`registry-isolation.test.ts`).
5
6
  *
6
- * `store.sql` / `store.files`, `vault.secret`, `signal()`, and medium-binder
7
- * `.template()` push into a shared registry (`src/kernel/element-registries.ts`)
8
- * at call time — zero change to how developers call them. `oke()` drains it
9
- * at construction under the same `registry: "consume" | "keep" | "ignore"`
10
- * switch that already governs bindings.
7
+ * `store.*`, `vault.secret`, `signal()`, `clock()`, `gate.policy` / `.scope`
8
+ * / `.rate`, and medium-binder `.template()` push into a shared registry
9
+ * (`src/kernel/element-registries.ts`) at call time — zero change to how
10
+ * developers call them. `oke()` drains it at construction under the same
11
+ * `registry: "consume" | "keep" | "ignore"` switch that already governs
12
+ * bindings.
11
13
  */
12
14
 
13
15
  import { describe, expect, test } from "bun:test";
14
16
  import { channel } from "../elements/channel/declare.ts";
17
+ import { clock } from "../elements/clock/declare.ts";
18
+ import { gate } from "../elements/gate/declare.ts";
15
19
  import { signal } from "../elements/signal/declare.ts";
16
20
  import { store } from "../elements/store/declare.ts";
17
21
  import { vault } from "../elements/vault/declare.ts";
@@ -20,7 +24,7 @@ import { flow } from "./flow.ts";
20
24
  import { on, resetBindings } from "./on.ts";
21
25
  import { http } from "./triggers.ts";
22
26
 
23
- describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai", () => {
27
+ describe("oke() auto-registry — stores/secrets/signals/clocks/gates/channel.templates/ai", () => {
24
28
  test("before/after: zero explicit arrays boots identically to the explicit-array form", async () => {
25
29
  resetBindings();
26
30
 
@@ -161,6 +165,42 @@ describe("oke() auto-registry — stores/secrets/signals/channel.templates/ai",
161
165
  expect(app.bootResult?.vault?.contracts.has("IGNORED_SECRET")).toBeFalsy();
162
166
  });
163
167
 
168
+ test("store.kv / store.index / clock() / gate.policy auto-drain — no explicit oke() arrays", async () => {
169
+ resetBindings();
170
+ store.kv("drafts", { description: "Compose drafts" });
171
+ store.index("tasks", { description: "Task search" });
172
+ clock("daily-digest", { cron: "0 8 * * *", every: "1d", timezone: "UTC" });
173
+ const member = gate.policy("member", ({ auth }) => !!auth.verified);
174
+ gate.scope("task:write");
175
+ gate.rate({ max: 60, per: "1m", keyBy: "user" });
176
+
177
+ const ping = flow("auto.ping", {
178
+ do: () => ({ ok: true as const }),
179
+ });
180
+ on(http.get("/auto-ping").gate(member), ping);
181
+
182
+ const app = oke({
183
+ name: "auto-kv-clock-gate",
184
+ autoBoot: false,
185
+ startScheduler: false,
186
+ });
187
+
188
+ expect(app.$options.stores?.some((s) => s.ref === "kv:drafts")).toBe(true);
189
+ expect(app.$options.stores?.some((s) => s.ref === "index:tasks")).toBe(true);
190
+ expect(app.$options.clocks?.some((c) => c.name === "daily-digest")).toBe(true);
191
+ expect(app.$options.gate?.policies?.some((g) => "name" in g && g.name === "member")).toBe(true);
192
+ expect(app.$options.gate?.policies?.some((g) => "name" in g && g.name === "task:write")).toBe(
193
+ true,
194
+ );
195
+
196
+ await app.boot({ env: "test" });
197
+ expect(app.bootResult?.store?.declarations.has("kv:drafts")).toBe(true);
198
+ expect(app.bootResult?.store?.declarations.has("index:tasks")).toBe(true);
199
+ expect(app.bootResult?.clock?.declarations.has("daily-digest")).toBe(true);
200
+ expect(app.bootResult?.gate?.gates.has("member")).toBe(true);
201
+ expect(app.bootResult?.gate?.gates.has("task:write")).toBe(true);
202
+ });
203
+
164
204
  test('registry: "consume" (default) drains stores/secrets/signals/channel.templates — a later app does not inherit them', async () => {
165
205
  store.sql("leak-app", { schema: {} });
166
206
  vault.secret("LEAK_SECRET", { dev: "dev-leak" });
@@ -116,9 +116,9 @@ describe("boot — lazy element needs", () => {
116
116
  if (raw.byteLength === 0) continue;
117
117
  total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
118
118
  }
119
- // Rebased after 0.12.0 store/http graph growth
120
- // (~47.6 kB gzip with export externals).
121
- expect(total).toBeLessThan(48_000);
119
+ // Rebased after clock / gate / kv / index auto-drain on `oke()`.
120
+ // (~48.3 kB gzip with export externals).
121
+ expect(total).toBeLessThan(49_000);
122
122
  } finally {
123
123
  await rm(dir, { recursive: true, force: true });
124
124
  }
@@ -227,7 +227,7 @@ describe("boot — HTTP gate wiring on the default path", () => {
227
227
  resetBindings();
228
228
  resetFlowSeq();
229
229
  on(
230
- http.get("/ping").gate.public,
230
+ http.get("/ping").public(),
231
231
  flow("ping-public", {
232
232
  do: () => ({ ok: true }),
233
233
  }),
@@ -101,10 +101,10 @@ export interface BootOptions {
101
101
  readonly gates?: readonly GateDecl[];
102
102
  /**
103
103
  * HTTP auth-posture enforcement at boot.
104
- * - `"deny"` (default) — every HTTP trigger must carry a gate or `.gate.public`
104
+ * - `"deny"` (default) — every HTTP trigger must carry a gate or `.public()`
105
105
  * - `"allow"` — skips the audit **only** when {@link env} is `"test"`;
106
106
  * ignored in local/prod/docker (never a production-wide bypass).
107
- * Migrate real apps with per-trigger `.gate.public`.
107
+ * Migrate real apps with per-trigger `.public()`.
108
108
  */
109
109
  readonly unguardedHttp?: "deny" | "allow";
110
110
  /** Signal declarations. */