okengine 0.6.0 → 0.7.0

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 (162) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +98 -71
  3. package/package.json +4 -4
  4. package/site/content/docs/console/clock.mdx +25 -5
  5. package/site/content/docs/console/signals.mdx +13 -4
  6. package/site/content/docs/elements/ai.mdx +29 -2
  7. package/site/content/docs/elements/channel.mdx +90 -20
  8. package/site/content/docs/elements/clock.mdx +56 -24
  9. package/site/content/docs/elements/flow.mdx +2 -0
  10. package/site/content/docs/elements/gate.mdx +8 -1
  11. package/site/content/docs/elements/signal.mdx +173 -29
  12. package/site/content/docs/elements/store.mdx +464 -73
  13. package/site/content/docs/elements/vault.mdx +15 -5
  14. package/site/content/docs/get-started/basic-usage.mdx +13 -6
  15. package/site/content/docs/get-started/installation.mdx +49 -13
  16. package/site/content/docs/get-started/introduction.mdx +1 -1
  17. package/site/content/docs/reference/cli.md +30 -2
  18. package/site/content/docs/reference/configuration.mdx +18 -17
  19. package/site/content/docs/reference/environment-variables.mdx +44 -18
  20. package/site/content/docs/reference/errors.mdx +12 -11
  21. package/site/content/docs/reference/fx.mdx +15 -13
  22. package/site/content/docs/reference/security.md +3 -1
  23. package/src/cli/ai-setup/ai-setup.test.ts +144 -0
  24. package/src/cli/ai-setup/apply.ts +214 -0
  25. package/src/cli/ai-setup/catalog.ts +263 -0
  26. package/src/cli/ai-setup/detect-ollama.ts +166 -0
  27. package/src/cli/ai-setup/index.ts +228 -0
  28. package/src/cli/ai-setup/prompts.ts +649 -0
  29. package/src/cli/ai-setup/recommend.test.ts +100 -0
  30. package/src/cli/ai-setup/recommend.ts +203 -0
  31. package/src/cli/ai.ts +38 -0
  32. package/src/cli/db-auto-push.test.ts +6 -1
  33. package/src/cli/db-auto-push.ts +8 -3
  34. package/src/cli/dev-db-push.test.ts +59 -0
  35. package/src/cli/dev.test.ts +40 -0
  36. package/src/cli/dev.ts +55 -1
  37. package/src/cli/docker-clean.test.ts +179 -0
  38. package/src/cli/docker-clean.ts +406 -0
  39. package/src/cli/docker.ts +16 -4
  40. package/src/cli/ensure-drizzle-config.ts +1 -1
  41. package/src/cli/hero-meta.test.ts +6 -5
  42. package/src/cli/hero-meta.ts +7 -1
  43. package/src/cli/index.ts +5 -0
  44. package/src/cli/load-config.ts +4 -4
  45. package/src/cli/openbao-bootstrap.test.ts +1 -0
  46. package/src/cli/openbao-bootstrap.ts +9 -2
  47. package/src/cli/openbao-restart.integration.test.ts +106 -97
  48. package/src/cli/registry.ts +79 -1
  49. package/src/client/live-gap.test.ts +35 -0
  50. package/src/client/transport.test.ts +21 -0
  51. package/src/client/transport.ts +27 -5
  52. package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
  53. package/src/config/define-config.test.ts +6 -6
  54. package/src/config/index.ts +1 -1
  55. package/src/console/server/console.test.ts +45 -0
  56. package/src/console/server/flows.ts +39 -9
  57. package/src/console/server/i18n.ts +41 -0
  58. package/src/console/server/vault.ts +12 -38
  59. package/src/console/ui/dist/assets/index-ClX0ZCe5.js +10 -0
  60. package/src/console/ui/dist/assets/{panel-access-BGv45snf.js → panel-access-ri1X9Otj.js} +1 -1
  61. package/src/console/ui/dist/assets/{panel-ai-B2S7LEii.js → panel-ai-DLHjRoJk.js} +1 -1
  62. package/src/console/ui/dist/assets/{panel-architecture-D7UJh91v.js → panel-architecture-DI3bQF3J.js} +1 -1
  63. package/src/console/ui/dist/assets/{panel-channels-9T3ybqRu.js → panel-channels-BDYWX29p.js} +1 -1
  64. package/src/console/ui/dist/assets/{panel-clock-Cb1UXGRQ.js → panel-clock-i-JpTjVB.js} +1 -1
  65. package/src/console/ui/dist/assets/{panel-diff-DmYbKWmN.js → panel-diff-CFuWxiXy.js} +1 -1
  66. package/src/console/ui/dist/assets/{panel-flows-PiHwT55z.js → panel-flows-B8TRhcoe.js} +1 -1
  67. package/src/console/ui/dist/assets/{panel-gates-BQGYXvjT.js → panel-gates-CfkBgco7.js} +1 -1
  68. package/src/console/ui/dist/assets/{panel-overview-BBnRO18l.js → panel-overview-DONDzBd2.js} +1 -1
  69. package/src/console/ui/dist/assets/{panel-plugins-D0PsmVw2.js → panel-plugins-DDJwTehL.js} +1 -1
  70. package/src/console/ui/dist/assets/{panel-runs-CWuRDe0r.js → panel-runs-D5zf-D9c.js} +1 -1
  71. package/src/console/ui/dist/assets/{panel-signals-Bbg4ewpP.js → panel-signals-Dsxdu_AR.js} +1 -1
  72. package/src/console/ui/dist/assets/{panel-store-CPCbsDRa.js → panel-store-hy7O8HOs.js} +1 -1
  73. package/src/console/ui/dist/assets/{panel-traces-DVAzuA_S.js → panel-traces-CBiAe5go.js} +1 -1
  74. package/src/console/ui/dist/assets/{panel-vault-D1_MvOmo.js → panel-vault-Za1GyJfM.js} +1 -1
  75. package/src/console/ui/dist/index.html +1 -1
  76. package/src/console/ui/shell/components/ui.tsx +5 -1
  77. package/src/console/ui/shell/setup/Wizard.tsx +26 -5
  78. package/src/docker/cleanup.test.ts +193 -0
  79. package/src/docker/cleanup.ts +355 -0
  80. package/src/docker/docker.test.ts +4 -0
  81. package/src/docker/dockerfile.integration.test.ts +126 -119
  82. package/src/docker/index.ts +19 -0
  83. package/src/docker/recipes/ollama.ts +10 -5
  84. package/src/docker/stack.integration.test.ts +118 -102
  85. package/src/drivers/ai-ollama-tools.integration.test.ts +8 -6
  86. package/src/drivers/ai-ollama.integration.test.ts +3 -19
  87. package/src/drivers/ai-ollama.ts +45 -2
  88. package/src/drivers/ai-providers.test.ts +31 -0
  89. package/src/drivers/channel-fcm.ts +49 -53
  90. package/src/drivers/channel-msegat.ts +61 -0
  91. package/src/drivers/channel-sently-map.ts +57 -0
  92. package/src/drivers/channel-sently.test.ts +99 -0
  93. package/src/drivers/channel-sndr.ts +28 -0
  94. package/src/drivers/channel-taqnyat.ts +57 -0
  95. package/src/drivers/channel-types.ts +79 -2
  96. package/src/drivers/channel-unifonic.ts +26 -43
  97. package/src/drivers/channel-wa-cloud.ts +33 -47
  98. package/src/drivers/channel-webpush.ts +39 -239
  99. package/src/drivers/index.ts +7 -0
  100. package/src/drivers/signal-engine.ts +98 -25
  101. package/src/drivers/signal-nats.ts +4 -4
  102. package/src/drivers/signal-postgres.ts +158 -64
  103. package/src/drivers/signal-redis.ts +4 -4
  104. package/src/drivers/signal-types.ts +61 -2
  105. package/src/drivers/vault-driver-removal.test.ts +5 -1
  106. package/src/elements/channel/costs.test.ts +2 -2
  107. package/src/elements/channel/costs.ts +14 -2
  108. package/src/elements/channel/mime.ts +11 -0
  109. package/src/elements/channel/runtime.ts +94 -0
  110. package/src/elements/channel/sndr-webhooks.test.ts +26 -0
  111. package/src/elements/channel.ts +10 -1
  112. package/src/elements/clock/chaos-child.ts +150 -0
  113. package/src/elements/clock/chaos.test.ts +204 -0
  114. package/src/elements/clock/reconcile.ts +110 -0
  115. package/src/elements/clock/runtime.ts +11 -0
  116. package/src/elements/clock.test.ts +164 -2
  117. package/src/elements/clock.ts +1 -0
  118. package/src/elements/gate/runtime.ts +6 -0
  119. package/src/elements/index.ts +9 -0
  120. package/src/elements/signal/chaos-child.ts +41 -4
  121. package/src/elements/signal/declare.ts +4 -1
  122. package/src/elements/signal/delivery-modes.test.ts +179 -0
  123. package/src/elements/signal/key-ordering.test.ts +306 -0
  124. package/src/elements/signal/lease-reclaim.test.ts +123 -0
  125. package/src/elements/signal/optional-emit.test.ts +76 -0
  126. package/src/elements/signal/order-lifecycle.test.ts +130 -0
  127. package/src/elements/signal/orphan-messages.test.ts +152 -0
  128. package/src/elements/signal/runtime.ts +11 -4
  129. package/src/elements/signal/schema-emit.test.ts +107 -0
  130. package/src/elements/signal.test.ts +89 -5
  131. package/src/elements/store/files-fx.ts +104 -0
  132. package/src/elements/store/files-image.test.ts +251 -0
  133. package/src/elements/store/files-image.ts +494 -0
  134. package/src/elements/store/runtime.ts +60 -0
  135. package/src/elements/store.ts +24 -0
  136. package/src/elements/vault/boot-chain.ts +150 -0
  137. package/src/elements/vault/runtime.ts +8 -0
  138. package/src/kernel/app.ts +3 -1
  139. package/src/kernel/boot-bind/channel.test.ts +68 -3
  140. package/src/kernel/boot-bind/channel.ts +93 -2
  141. package/src/kernel/boot-bind/clock.ts +52 -7
  142. package/src/kernel/boot-bind/gate.ts +71 -3
  143. package/src/kernel/boot-bind/honor-config.test.ts +247 -0
  144. package/src/kernel/boot-bind/signal.ts +64 -7
  145. package/src/kernel/boot-bind/vault.ts +27 -8
  146. package/src/kernel/boot.ts +10 -2
  147. package/src/kernel/errors.ts +8 -2
  148. package/src/kernel/fx.test.ts +13 -0
  149. package/src/kernel/fx.ts +27 -4
  150. package/src/plugins/auth-delivery.mailpit.integration.test.ts +10 -4
  151. package/src/release/exports.test.ts +26 -0
  152. package/src/release/exports.ts +64 -5
  153. package/src/release/index.ts +5 -0
  154. package/src/release/measure.exports.test.ts +13 -1
  155. package/src/release/measure.ts +76 -13
  156. package/src/release/official-plugins.ts +46 -0
  157. package/src/release/readme.test.ts +30 -2
  158. package/src/runtime/dev-request-log.test.ts +20 -1
  159. package/src/runtime/dev-request-log.ts +38 -0
  160. package/src/term.test.ts +15 -0
  161. package/src/term.ts +8 -3
  162. package/src/console/ui/dist/assets/index-CjxwRGVv.js +0 -10
package/AGENTS.md CHANGED
@@ -78,9 +78,9 @@ A regression fails the build. Claims we cannot measure, we do not make.
78
78
  Published packages:
79
79
 
80
80
  - `okengine` — framework. Subpath exports: `.`, `./client`, `./test`, `./config`, `./auth`, `./plugins`, `./drivers/*`. `"sideEffects": false`. CLI binary: `oke`.
81
- - `create-oke` — scaffold CLI (`bunx create-oke@latest <name>`). Lives in `packages/create-oke` and ships one standard starter from `packages/create-oke/template`.
81
+ - `create-oke` — scaffold CLI (`bunx create-oke@latest <name>`). Lives in `packages/create-oke` and ships Notes starters from `packages/create-oke/templates/{standard,advanced}`.
82
82
 
83
- Engine: Bun `>=1.3`.
83
+ Engine: Bun `>=1.3.14`.
84
84
 
85
85
  ## Documentation authority
86
86
 
package/README.md CHANGED
@@ -1,21 +1,36 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="site/public/logo/OKE-W.svg" />
4
- <img alt="OKE" src="site/public/logo/OKE-B.svg" width="220" />
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://shieldcn.dev/header/grid.svg?title=okengine&subtitle=One+law.+Eight+elements.+Ten+exports.&logo=https://raw.githubusercontent.com/omqkhafi/okengine/main/site/public/logo/OKE-W.svg&theme=zinc&size=banner&mode=dark&font=geist&align=left"
6
+ />
7
+ <img
8
+ alt="okengine — One law. Eight elements. Ten exports."
9
+ src="https://shieldcn.dev/header/grid.svg?title=okengine&subtitle=One+law.+Eight+elements.+Ten+exports.&logo=https://raw.githubusercontent.com/omqkhafi/okengine/main/site/public/logo/OKE-B.svg&theme=zinc&size=banner&mode=light&font=geist&align=left"
10
+ width="750"
11
+ />
5
12
  </picture>
6
13
  </p>
7
14
 
8
- # okengine
9
-
10
- _"Stop gluing APIs, jobs, and queues into one backend. One law collapses them — client, Console, and infra come free. Yours to host."_
15
+ <p align="center">
16
+ <a href="https://www.npmjs.com/package/okengine"><img alt="npm" src="https://shieldcn.dev/npm/okengine.svg?size=sm&variant=outline" /></a>
17
+ <a href="https://jsr.io/@omqkhafi/okengine"><img alt="JSR" src="https://shieldcn.dev/jsr/@omqkhafi/okengine.svg?size=sm&variant=outline" /></a>
18
+ <a href="https://opensource.org/licenses/MIT"><img alt="MIT" src="https://shieldcn.dev/npm/license/okengine.svg?size=sm&variant=outline" /></a>
19
+ <a href="https://bun.sh"><img alt="Bun >=1.3.14" src="https://shieldcn.dev/badge/Bun-%3E%3D1.3.14.svg?logo=bun&size=sm&variant=outline" /></a>
20
+ <a href="https://github.com/omqkhafi/okengine/actions"><img alt="CI" src="https://shieldcn.dev/github/ci/omqkhafi/okengine.svg?size=sm&variant=outline" /></a>
21
+ <a href="https://github.com/omqkhafi/okengine/commits"><img alt="last commit" src="https://shieldcn.dev/github/last-commit/omqkhafi/okengine.svg?size=sm&variant=outline" /></a>
22
+ </p>
11
23
 
12
- [![npm](https://img.shields.io/npm/v/okengine.svg)](https://www.npmjs.com/package/okengine)
13
- [![JSR](https://jsr.io/badges/@omqkhafi/okengine)](https://jsr.io/@omqkhafi/okengine)
14
- [![npm](https://img.shields.io/npm/v/create-oke.svg)](https://www.npmjs.com/package/create-oke)
15
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
16
- [![Bun >=1.3](https://img.shields.io/badge/Bun-%3E%3D1.3-black.svg)](https://bun.sh)
24
+ <p align="center">
25
+ <em>Stop gluing APIs, jobs, and queues into one backend. One law collapses them — client, Console, and infra come free. Yours to host.</em>
26
+ </p>
17
27
 
18
- **Docs:** [oke.omqkhafi.dev](https://oke.omqkhafi.dev) · **Handbook:** [/docs](https://oke.omqkhafi.dev/docs) · **CLI:** `oke` · **Scaffold:** [`create-oke`](https://www.npmjs.com/package/create-oke)
28
+ <p align="center">
29
+ <a href="https://oke.omqkhafi.dev"><strong>Docs</strong></a> ·
30
+ <a href="https://oke.omqkhafi.dev/docs"><strong>Handbook</strong></a> ·
31
+ <a href="https://oke.omqkhafi.dev/llms.txt"><code>llms.txt</code></a> ·
32
+ <a href="https://www.npmjs.com/package/create-oke"><code>create-oke</code></a>
33
+ </p>
19
34
 
20
35
  > [!WARNING]
21
36
  > **Early development (`v0.x`) — not production-ready.**
@@ -24,16 +39,19 @@ _"Stop gluing APIs, jobs, and queues into one backend. One law collapses them
24
39
 
25
40
  ## Install
26
41
 
42
+ Requires [Bun](https://bun.sh) `>=1.3.14`.
43
+
27
44
  ```bash
45
+ bunx create-oke@latest my-app # scaffold (recommended)
28
46
  bun add okengine # framework + `oke` CLI
47
+ bun install -g okengine # global `oke` on PATH
29
48
  bunx jsr add @omqkhafi/okengine # JSR — library API only
30
- bunx create-oke@latest my-app --template hello # scaffold
31
49
  ```
32
50
 
33
51
  ## Quick start
34
52
 
35
53
  ```bash
36
- bunx create-oke@latest my-app --template hello
54
+ bunx create-oke@latest my-app
37
55
  cd my-app
38
56
  oke dev
39
57
  # or: oke mode docker && oke dev # compose infra · app on host Bun
@@ -48,73 +66,82 @@ oke dev
48
66
 
49
67
  Mnemonic: **O·K·E = 6·5·3**.
50
68
 
51
- Every backend behavior is a Flow: `on(Trigger) Effects`. World access goes through `fx`.
69
+ The starter already exports a health Flow change it and save; App and Console update from the same Manifest:
52
70
 
53
- Store apps sync schema with `oke db push|generate|migrate` (local `oke dev`
54
- auto-pushes). Prefer `store.schema.table` + `field.*` or keep a
55
- hand-written Drizzle `schema.ts`.
71
+ ```typescript
72
+ import { on, flow, http, gate } from "okengine";
73
+ import { z } from "zod";
74
+
75
+ export const health = on(
76
+ http.get("/health").gate(gate.public),
77
+ flow({
78
+ out: z.object({ ok: z.literal(true) }),
79
+ do: () => ({ ok: true as const }),
80
+ }),
81
+ );
82
+ ```
56
83
 
57
- ### Resource Flow
84
+ Call it with a typed client (contracts inferred — no separate codegen):
58
85
 
59
86
  ```typescript
60
- import { on, http, store } from "okengine";
61
- import { createInsertSchema, createSelectSchema } from "drizzle-zod";
62
- import { db } from "../../core";
63
- import { notes as notesTable } from "../../schema";
64
-
65
- // Contracts derived from the schema — one source of truth.
66
- const NewNote = createInsertSchema(notesTable, { title: (s) => s.min(1).max(120) }).omit({
67
- id: true,
68
- createdAt: true,
69
- });
70
- const Note = createSelectSchema(notesTable);
71
-
72
- // One declarative resource: list (cursor + search + filters + order +
73
- // select), create (201), get / update / remove with typed NotFound.
74
- const notesR = store.resource(db, notesTable, {
75
- in: NewNote,
76
- out: Note,
77
- update: NewNote.partial(),
78
- list: {
79
- cursor: [notesTable.createdAt, notesTable.id],
80
- direction: "desc",
81
- limit: 20,
82
- maxLimit: 100,
83
- search: [notesTable.title],
84
- filter: "all",
85
- order: "all",
86
- },
87
- unit: "notes",
88
- breaking: true,
89
- });
90
-
91
- // Mount all five verbs: list/create on /notes, get/update/remove on /notes/:id.
92
- // `on(http.resource(...))` returns the ops bag; spread it into named exports
93
- // so `adopt({ notes })` registers each flow under the unit.
94
- const mounted = on(http.resource("/notes", notesR.all()));
95
-
96
- export const list = mounted.list;
97
- export const create = mounted.create;
98
- export const get = mounted.get;
99
- export const update = mounted.update;
100
- export const remove = mounted.remove;
87
+ import { createClient } from "okengine/client";
88
+ import type { App } from "./app";
89
+
90
+ const api = createClient<App>("http://localhost:6530");
91
+ const { data, error } = await api.main.health({});
101
92
  ```
102
93
 
103
94
  Full walkthrough: [Basic usage](https://oke.omqkhafi.dev/docs/get-started/basic-usage).
104
95
 
96
+ ## One law
97
+
98
+ Every backend behavior is a Flow: `on(Trigger) → Effects`. Endpoints, jobs, consumers, and webhooks are the same species — only the trigger changes.
99
+
100
+ ```typescript
101
+ import { on, flow, signal, store, clock, gate, vault, channel, ai, plugin } from "okengine";
102
+ ```
103
+
104
+ That is the entire public vocabulary. World access goes through `fx` — effects are inferred, so the Manifest can derive the typed client, Console, and MCP without a second source of truth.
105
+
106
+ | Element | Essence |
107
+ | ----------- | ----------------------------- |
108
+ | **Flow** | behavior |
109
+ | **Signal** | data in motion |
110
+ | **Store** | data at rest |
111
+ | **Clock** | time |
112
+ | **Gate** | permission to act |
113
+ | **Vault** | protected knowledge |
114
+ | **Channel** | reaching humans |
115
+ | **AI** | reaching machine intelligence |
116
+
117
+ Theory: [Introduction](https://oke.omqkhafi.dev/docs/get-started/introduction) · [Elements](https://oke.omqkhafi.dev/docs/elements) · [Why OKE](https://oke.omqkhafi.dev/docs/get-started/why).
118
+
119
+ ## Next
120
+
121
+ | Want… | Go to |
122
+ | --------------------------- | ---------------------------------------------------------------------- |
123
+ | Scaffold details & modes | [Installation](https://oke.omqkhafi.dev/docs/get-started/installation) |
124
+ | Declarative CRUD | [`store.resource`](https://oke.omqkhafi.dev/docs/elements/store) |
125
+ | Auth, rate limits, sessions | [Gate](https://oke.omqkhafi.dev/docs/elements/gate) |
126
+ | Queues & live events | [Signal](https://oke.omqkhafi.dev/docs/elements/signal) |
127
+ | Email / SMS / push | [Channel](https://oke.omqkhafi.dev/docs/elements/channel) |
128
+ | `oke` commands | [CLI](https://oke.omqkhafi.dev/docs/reference/cli) |
129
+
130
+ Store apps sync schema with `oke db push|generate|migrate` (local `oke dev` auto-pushes). Prefer `store.schema.table` + `field.*`, or keep a hand-written Drizzle `schema.ts`.
131
+
105
132
  ## Documentation
106
133
 
107
- Everything beyond this page lives in the site handbook:
134
+ | Resource | Link |
135
+ | ------------ | -------------------------------------------------------------- |
136
+ | Docs site | [oke.omqkhafi.dev](https://oke.omqkhafi.dev) |
137
+ | Get started | [/docs/get-started](https://oke.omqkhafi.dev/docs/get-started) |
138
+ | Handbook | [/docs](https://oke.omqkhafi.dev/docs) |
139
+ | Changelog | [/changelog](https://oke.omqkhafi.dev/changelog) |
140
+ | Agents index | [/llms.txt](https://oke.omqkhafi.dev/llms.txt) |
141
+ | Manifest | [`manifest.v1.schema.json`](manifest.v1.schema.json) |
142
+ | Budgets | [`BUDGETS.md`](BUDGETS.md) |
143
+ | Contributing | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
108
144
 
109
- | Resource | Link |
110
- | ------------- | ------------------------------------------------------------------ |
111
- | Docs site | [oke.omqkhafi.dev](https://oke.omqkhafi.dev) |
112
- | Handbook | [/docs](https://oke.omqkhafi.dev/docs) |
113
- | Changelog | [/changelog](https://oke.omqkhafi.dev/changelog) |
114
- | Agents index | [/llms.txt](https://oke.omqkhafi.dev/llms.txt) |
115
- | CLI reference | [/docs/reference/cli](https://oke.omqkhafi.dev/docs/reference/cli) |
116
- | Manifest | [`manifest.v1.schema.json`](manifest.v1.schema.json) |
117
- | Budgets | [`BUDGETS.md`](BUDGETS.md) |
118
- | Contributing | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
145
+ ## License
119
146
 
120
- Pre-1.0. Published on [npm](https://www.npmjs.com/package/okengine) and [JSR](https://jsr.io/@omqkhafi/okengine). MIT.
147
+ Pre-1.0. Published on [npm](https://www.npmjs.com/package/okengine) and [JSR](https://jsr.io/@omqkhafi/okengine). [MIT](https://opensource.org/licenses/MIT).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okengine",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -58,7 +58,7 @@
58
58
  "prepack": "bun run build",
59
59
  "bench": "bun test ./src/runtime/cold-start.bench.ts src/client/budget.test.ts src/console/budget.test.ts src/kernel/budget.test.ts src/kernel/routing-budget.test.ts src/runs/bench.test.ts",
60
60
  "budgets": "bun src/release/publish.ts",
61
- "gate": "bun test src/cli/doc-staleness.test.ts src/cli/competitor-mention-removal.test.ts src/drivers/vault-driver-removal.test.ts src/kernel/errors.registry.test.ts src/upgrade/codemods.test.ts",
61
+ "gate": "PUBLISH_GATE=1 bun test src/cli/doc-staleness.test.ts src/cli/competitor-mention-removal.test.ts src/drivers/vault-driver-removal.test.ts src/kernel/errors.registry.test.ts src/upgrade/codemods.test.ts scripts/publish.gate.test.ts",
62
62
  "dev": "bun run --cwd site dev",
63
63
  "site:build": "bun run --cwd site build",
64
64
  "ci": "bun scripts/ci.ts",
@@ -73,7 +73,7 @@
73
73
  "ajv-formats": "^3.0.1",
74
74
  "intl-messageformat": "^11.2.13",
75
75
  "oxc-parser": "^0.142.0",
76
- "sently": "^0.8.0"
76
+ "sently": "1.0.0"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@base-ui/react": "^1.6.0",
@@ -150,6 +150,6 @@
150
150
  }
151
151
  },
152
152
  "engines": {
153
- "bun": ">=1.3"
153
+ "bun": ">=1.3.14"
154
154
  }
155
155
  }
@@ -14,15 +14,35 @@ Answers: **upcoming crons, sleeping durable flows, journal**
14
14
  ## What this panel shows
15
15
 
16
16
  <Cards>
17
+ <Card title="Schedules" description="Drift, overdue, missed runs · catch-up one, lease holder." />
18
+ <Card title="Pending wakes" description="Every sleeping durable flow and when it wakes." />
17
19
  <Card
18
- title="Schedules"
19
- description="Recurring crons with drift, overdue, catch-up, lease holder."
20
+ title="DST warnings"
21
+ description="Gap or overlap badge detect only, no schedule rewrite."
20
22
  />
21
- <Card title="Pending wakes" description="Every sleeping durable flow and when it wakes." />
22
- <Card title="DST warnings" description="Only when the schedule falls in an ambiguous window." />
23
- <Card title="Actions" description="Run now, wake early, pause, edit when overridable." />
23
+ <Card title="Actions" description="Run now, wake early, pause; edit when overridable." />
24
24
  </Cards>
25
25
 
26
+ ## Health numbers
27
+
28
+ Each cron row projects four numbers plus the lease holder:
29
+
30
+ | Field | Meaning |
31
+ | --------------- | ----------------------------------------------------------------- |
32
+ | Drift | How far the last run sat from the expected slot |
33
+ | Overdue | Whether the schedule is past due |
34
+ | Missed + policy | Slot count while down · always catch-up `"one"` (one fire, not N) |
35
+ | Lease | Which instance holds the leader lease (empty when none) |
36
+
37
+ ## Actions
38
+
39
+ | Action | When |
40
+ | ------------- | ---------------------------------------------------------------------- |
41
+ | Pause | Any cron — not gated by `overridable` |
42
+ | Edit schedule | Only when the declaration has `overridable: true`; else loud refuse |
43
+ | Wake early | Sleeping durable run — advances `wakeAt` and can resume the journal |
44
+ | Run now | Still requires the leader lease across replicas that share a CronStore |
45
+
26
46
  ## Catalog
27
47
 
28
48
  <Cards>
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: "Signals"
3
- description: "Queue depth, in-flight, DLQ, live monitors."
3
+ description: "Queue depth, in-flight, DLQ, live monitors, orphaned config."
4
4
  icon: "Radio"
5
5
  source: "docs/spec/console.md"
6
6
  ---
7
7
 
8
- Answers: **queue depth, in-flight, DLQ, live monitors**
8
+ Answers: **queue depth, in-flight, DLQ, live monitors, orphaned config**
9
9
 
10
10
  <Callout title="Governing rule">
11
11
  One list grouped by delivery physics — not three tabs that re-split the element.
@@ -14,15 +14,24 @@ Answers: **queue depth, in-flight, DLQ, live monitors**
14
14
  ## What this panel shows
15
15
 
16
16
  <Cards>
17
- <Card title="once" description="Pending, in-flight, DLQ, retry policy." />
17
+ <Card title="once" description="Pending, in-flight (leased), DLQ, retry policy." />
18
18
  <Card title="broadcast" description="Per-subscriber lag and targeted replay." />
19
- <Card title="live" description="Connection count, throughput, payload monitor." />
19
+ <Card
20
+ title="live"
21
+ description="Connection count, throughput, newest-50 payload monitor (full history still retained)."
22
+ />
20
23
  <Card
21
24
  title="DLQ repair"
22
25
  description="Schema form, typed errors, causal chain, dry-run bulk replay."
23
26
  />
27
+ <Card
28
+ title="Orphaned"
29
+ description="Config still listed after the declaration left code — messages kept; dry-run refused."
30
+ />
24
31
  </Cards>
25
32
 
33
+ Dry-run is refused for orphaned signals and for signals with no Manifest consumer — unknown handler shapes are not invoked unsafely. Restore the `signal(…)` declaration (and a consumer) before replaying DLQ rows. Lease / at-least-once physics live on [Signal](/docs/elements/signal).
34
+
26
35
  ## Catalog
27
36
 
28
37
  <Cards>
@@ -77,6 +77,8 @@ export const triage = smart.prompt("ticket-triage", {
77
77
  | `ai.embed(name, opts)` | Embedding pipeline into a `store.index` (searched via `fx.search`) |
78
78
  | `ai.agent(name, opts)` | Bounded agent whose tools are **your own flows** |
79
79
 
80
+ <AiBlocks />
81
+
80
82
  ### Prompt options
81
83
 
82
84
  | Option | Type | Meaning |
@@ -86,6 +88,10 @@ export const triage = smart.prompt("ticket-triage", {
86
88
  | `evals` | string | Path to a `.jsonl` eval set, regression-gated via `oke eval` |
87
89
  | `budget` | object | `maxCostPerCall` — cost is a first-class dimension |
88
90
 
91
+ ## Declared guardrails
92
+
93
+ Cost, egress, and model choice are contracts — not runtime hope. Each guardrail is declared up front; violating one fails the build or the run.
94
+
89
95
  <AiGuardrails />
90
96
 
91
97
  ## Tools are flows (same `fx.call` path)
@@ -136,6 +142,8 @@ Use `keyBy: "ip"` on public unauthenticated AI edges. Cost caps stay on prompt/a
136
142
 
137
143
  At build time, OKE checks which fields each `fx.ask` sends against your store classifications. If a PII field (`.pii()` in your schema) would reach a third-party model, the build fails:
138
144
 
145
+ <AiPiiEgress />
146
+
139
147
  ```text
140
148
  build failed: flow "ticket-triage" sends pii field(s) [email] to a third-party model without allowPii
141
149
  ```
@@ -159,9 +167,28 @@ Because `fx.ask` is nondeterministic, the runtime adjusts around it: journaling
159
167
 
160
168
  There is deliberately **no production default**: `prod` must name a driver, which keeps the model choice visible in `oke.config.ts` where review can see it.
161
169
 
170
+ ### Setup wizard
171
+
172
+ ```bash title="Terminal"
173
+ oke ai setup
174
+ oke ai setup --provider ollama --yes
175
+ ```
176
+
177
+ Writes `drivers.ai`, `.env.local`, and `src/ai.ts`.
178
+
179
+ Ollama: short quiz (use case · speed/quality · vision) → detect installed models
180
+ → picks that fit your **machine tier** (~4GB OS/IDE headroom). Labels say
181
+ **≈16GB-class**, not “download 16GB” — pull size is usually much smaller than RAM.
182
+
183
+ Cloud: short chat list + **Other…** for any model id. **← Back** returns a step.
184
+ `create-oke` runs the same model wizard **before** install when you enable AI
185
+ (customize or `--ai`). Standalone `oke ai setup` remains for existing apps.
186
+
162
187
  ### Local Ollama
163
188
 
164
- `ollama` is a local-model driver — any model you have pulled works. Pin it in `drivers.ai`; optionally pin `images.ai` for compose.
189
+ `ollama` is a local-model driver — any pulled model works. Pin `drivers.ai`;
190
+ optionally pin `images.ai` for compose. Native `complete`, `stream`, and
191
+ `embed` (`POST /api/embeddings`) — no OpenAI shim required for local RAG.
165
192
 
166
193
  ```typescript title="oke.config.ts"
167
194
  import { defineConfig } from "okengine/config";
@@ -221,7 +248,7 @@ The one you declared. There is no fallback or guess — if `prod` has no AI driv
221
248
  </Accordion>
222
249
  <Accordion title="oke boot: ollama driver needs OKE_AI_URL">
223
250
 
224
- Docker mode expects `OKE_AI_URL` from `docker/.env.docker` (written when `images.ai` is pinned). Locally, the default is `http://127.0.0.1:11434`. If asks fail with `OllamaUnavailableError`, confirm the server answers and that you have pulled the model you named (`ollama pull qwen3.5:9b`, or `qwen3.5:9b-mlx` on Apple Silicon, or your `OKE_AI_MODEL`).
251
+ Docker mode expects `OKE_AI_URL` from `docker/.env.docker` (written when `images.ai` is pinned). Locally, the default is `http://127.0.0.1:11434`. If asks fail with `OllamaUnavailableError`, confirm the server answers and that you have pulled the model you named (`ollama pull qwen3.5:9b`, or `qwen3.5:9b-mlx` on Apple Silicon, or your `OKE_AI_MODEL`). Prefer `oke ai setup` to wire driver, env, and `src/ai.ts` in one pass.
225
252
 
226
253
  </Accordion>
227
254
  <Accordion title="A prompt edit made answers worse — how do I catch that pre-merge?">
@@ -5,7 +5,7 @@ icon: "Mail"
5
5
  source: "docs/spec/unified-theory.md"
6
6
  ---
7
7
 
8
- Channel is how your app **reaches humans**: the order-confirmation email, the OTP text, the WhatsApp notification. Reaching a person is not the same problem as moving data between machines — it needs consent (did they opt out?), locale (which language?), receipts (did it land?), and fallback (email failed, try SMS). Those physics are built into the element, so every flow gets them for free.
8
+ Channel is how your app **reaches humans**: the order-confirmation email, the OTP text, the WhatsApp notification. Reaching a person is not the same problem as moving data between machines — it needs consent (did they opt out?), locale (which language?), receipts (did it land?), and fallback (first driver failed, try the next). Those physics are built into the element, so every flow gets them for free.
9
9
 
10
10
  <Callout title="The one rule">
11
11
  Sends go through `fx.send` and declared templates — never through a raw SMTP or provider client
@@ -83,49 +83,84 @@ Locally the `console` driver captures mail into an inbox instead of sending; in
83
83
 
84
84
  ### Consent is checked before sending
85
85
 
86
- Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt says so. You never hand-roll "did they unsubscribe?" checks.
86
+ Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt status is `suppressed/opted-out`. You never hand-roll "did they unsubscribe?" checks.
87
87
 
88
- ### Locale resolves through a chain
88
+ ### Locale resolves, then the catalog
89
89
 
90
- Templates render per recipient locale, falling back through your configured chain
91
- (`ar` → default `en`) instead of failing when a translation is missing.
90
+ `fx.send` picks a locale: explicit `locale` `profileLocale` first
91
+ `Accept-Language` tag → `defaultLocale`. The body is the catalog entry for
92
+ that exact key, else the default locale, else `en`.
92
93
 
93
94
  Locales come from `oke.config.ts` `i18n`. Catalog bodies use `{{field}}` — not
94
95
  ICU ([i18n](/docs/reference/i18n) / `fx.t` owns flow copy). Pass `locale` /
95
96
  `profileLocale` / `acceptLanguage` on `fx.send`, or omit them for `fx.locale`.
96
97
 
97
- ### Fallback chains are explicit
98
+ ### Fallback chains are same-medium
98
99
 
99
- `via` orders the mediums to try — first success wins, and **every** attempt is recorded:
100
+ `via` orders **driver ids** for the template's medium — first success wins,
101
+ and **every** attempt is recorded:
100
102
 
101
103
  ```typescript
102
- await fx.send(otpCode, { to: user.phone, data: { code }, via: [sms, wa] });
103
- // sms fails → whatsapp tried → receipt status "fallback" with both attempts
104
+ await fx.send(orderConfirmed, {
105
+ to: user.email,
106
+ data: { name, orderId, total },
107
+ via: ["smtp", "resend"],
108
+ });
109
+ // smtp fails → resend tried → receipt status "fallback" with both attempts
104
110
  ```
105
111
 
106
112
  ### Receipts for everything
107
113
 
108
- Each send records its attempts — driver, ok/error, timestamp, message id — so "did the user actually get it?" is a Console query, not a guess. In a dry run, sends are recorded as _would have fired_ and never contact a real provider.
114
+ Each send records its attempts — driver, ok/error, timestamp, message id — so "did the user actually get it?" is a Console query, not a guess. In a dry run, the kernel records a would-have-fired send and never opens a provider — no Channel receipt is written.
109
115
 
110
116
  ## Per-environment drivers
111
117
 
118
+ Boot opens the email driver from `drivers.channel.email`, and appends an SMS
119
+ driver when `drivers.channel.sms` is set (other than `console`). WhatsApp and
120
+ push drivers are passed via `BootOptions.channel.drivers` when you need them.
121
+
112
122
  ```typescript title="oke.config.ts"
113
123
  drivers: {
114
124
  channel: {
115
125
  email: { local: "console", docker: "smtp", test: "console", prod: "smtp" },
126
+ // optional — omit until you need SMS at boot
127
+ sms: { local: "console", docker: "taqnyat", test: "console", prod: "taqnyat" },
116
128
  },
117
129
  },
118
130
  images: {
119
- "channel.email": "axllent/mailpit:v1.22.3", // SMTP catcher for the docker stack
131
+ "channel.email": "axllent/mailpit:v1.22.3",
120
132
  },
121
133
  ```
122
134
 
123
- | Driver | Medium | Behavior |
124
- | ---------- | ------ | ---------------------------------------------------- |
125
- | `console` | any | Captures into a readable inbox — local + tests |
126
- | `smtp` | email | Real SMTP — Mailpit in docker, your provider in prod |
127
- | `resend` | email | Resend API |
128
- | `unifonic` | sms | Unifonic SMS API |
135
+ | Driver | Medium | Behavior |
136
+ | ---------- | -------- | ------------------------------------------------------------------- |
137
+ | `console` | any | Captures into a readable inbox — local + tests |
138
+ | `smtp` | email | Real SMTP — Mailpit in docker, your provider in prod |
139
+ | `resend` | email | Resend HTTP API (`RESEND_API_KEY`) |
140
+ | `sndr` | email | SNDR HTTP API (`SNDR_API_KEY`) |
141
+ | `taqnyat` | sms | Taqnyat SMS (`TAQNYAT_BEARER_TOKEN` + `TAQNYAT_SENDER`) |
142
+ | `msegat` | sms | Msegat SMS (`MSEGAT_USERNAME` + `MSEGAT_API_KEY` + `MSEGAT_SENDER`) |
143
+ | `unifonic` | sms | Unifonic el.cloud SMS (`UNIFONIC_APPSID`) |
144
+ | `wa-cloud` | whatsapp | Meta WhatsApp Cloud API |
145
+ | `webpush` | push | Web Push + VAPID (`createPushSender` → `WebPushTransport`) |
146
+ | `fcm` | push | Firebase Cloud Messaging HTTP v1 |
147
+
148
+ Email fallback uses ordered email transports; SMS with multiple SMS drivers uses
149
+ the same failover path. Credentials for boot-opened drivers are listed under
150
+ [Environment Variables](/docs/reference/environment-variables).
151
+
152
+ `webpush` needs VAPID keys — open it yourself and include it in
153
+ `BootOptions.channel.drivers` (boot does not open push from env):
154
+
155
+ ```typescript
156
+ import { openWebPushChannel } from "okengine/drivers/channel-webpush";
157
+
158
+ openWebPushChannel({
159
+ vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,
160
+ vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,
161
+ vapidSubject: process.env.VAPID_SUBJECT,
162
+ });
163
+ ```
129
164
 
130
165
  ## Troubleshooting
131
166
 
@@ -142,12 +177,45 @@ Check consent first: if they opted out, sends to them are suppressed by design.
142
177
  </Accordion>
143
178
  <Accordion title="The Arabic version didn't render">
144
179
 
145
- Locale resolution falls back through the chain to your default locale when a translation is missing the send still succeeds with the fallback body. Check that the template declares `locales: ["en", "ar"]` and that the Arabic body exists in the catalog.
180
+ The send still succeeds: the body falls back to your default locale, then `en`, when the catalog misses that exact key. Check that an Arabic entry exists under the same tag you send (`ar` vs `ar-SA`) and that the template lists it in `locales`.
181
+
182
+ </Accordion>
183
+ <Accordion title="How do I ingest SNDR / Unifonic delivery webhooks?">
184
+
185
+ ```typescript
186
+ import {
187
+ parseSndrWebhook,
188
+ verifySndrSignature,
189
+ parseUnifonicWebhook,
190
+ toDeliveryEvent,
191
+ } from "okengine/channel";
192
+ ```
193
+
194
+ Verify the signature when the provider sends one, parse the body, then call
195
+ `ingestOutcome` on your channel runtime with the bounce/complaint fields.
196
+ Console reads the receipt ledger — never raw webhook JSON.
197
+
198
+ </Accordion>
199
+ <Accordion title="How do I know which driver finally delivered?">
200
+
201
+ The receipt keeps every attempt in order with its outcome. A send that succeeded on a later driver reports status `fallback` — you can see the full chain in Console → Channels.
146
202
 
147
203
  </Accordion>
148
- <Accordion title="How do I know which medium finally delivered?">
204
+ <Accordion title="How do I verify Web Push against a real browser?">
205
+
206
+ Subscribe in a real browser with sently’s `examples/webpush-interop` page
207
+ (paste the same `VAPID_PUBLIC_KEY`), copy the `PushSubscription` JSON, then:
208
+
209
+ ```sh
210
+ export VAPID_PUBLIC_KEY='…'
211
+ export VAPID_PRIVATE_KEY='…'
212
+ bun scripts/webpush-interop.ts /tmp/push-sub.json
213
+ ```
214
+
215
+ That path is Channel → `webpush` → sently `createPushSender` →
216
+ `WebPushTransport`, and it prints the push-service HTTP response.
149
217
 
150
- The receipt keeps every attempt in order with its outcome. A send that succeeded on a later medium reports status `fallback` you can see the full chain in Console → Channels.
218
+ Confirm a visible OS notification titled `okengine webpush interop`.
151
219
 
152
220
  </Accordion>
153
221
  </Accordions>
@@ -156,6 +224,8 @@ The receipt keeps every attempt in order with its outcome. A send that succeeded
156
224
 
157
225
  - [Flow](/docs/elements/flow) — `fx.send` inside `do`
158
226
  - [Console · Channels](/docs/console/channels) — receipts, attempts, suppression
227
+ - [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
228
+ - [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
159
229
  - [Signal](/docs/elements/signal) — machine-to-machine messaging, the other side of the line
160
230
 
161
231
  ## Next