okengine 0.10.2 → 0.10.3
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.
- package/package.json +1 -1
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/elements/ai.mdx +0 -6
- package/site/content/docs/elements/channel.mdx +0 -6
- package/site/content/docs/elements/clock.mdx +0 -6
- package/site/content/docs/elements/flow.mdx +0 -7
- package/site/content/docs/elements/gate.mdx +0 -6
- package/site/content/docs/elements/signal.mdx +0 -6
- package/site/content/docs/elements/store.mdx +8 -15
- package/site/content/docs/elements/vault.mdx +0 -6
- package/site/content/docs/get-started/basic-usage.mdx +0 -6
- package/site/content/docs/get-started/why.mdx +1 -2
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/fx.mdx +3 -3
- package/src/cli/ai-setup/ai-setup.test.ts +3 -1
- package/src/cli/build.ts +7 -8
- package/src/cli/dev.test.ts +88 -1
- package/src/cli/dev.ts +15 -13
- package/src/cli/doctor.ts +98 -5
- package/src/cli/load-config.images.test.ts +31 -1
- package/src/cli/load-config.ts +2 -2
- package/src/cli/safe-defaults.test.ts +1 -1
- package/src/compiler/extract.ts +12 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +4 -2
- package/src/compiler/generate-adopt.ts +27 -1
- package/src/config/driver-defaults.test.ts +122 -0
- package/src/config/driver-defaults.ts +186 -0
- package/src/config/index.ts +97 -7
- package/src/docker/images-config.test.ts +168 -0
- package/src/kernel/boot-bind/channel.ts +12 -4
- package/src/kernel/boot-bind/clock.ts +3 -3
- package/src/kernel/boot-bind/gate.ts +7 -4
- package/src/kernel/boot-bind/journal.ts +3 -3
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +19 -0
- package/src/kernel/boot-bind/store.ts +24 -9
- package/src/kernel/boot-bind/vault.ts +3 -3
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
package/package.json
CHANGED
|
@@ -98,7 +98,6 @@ The docs content ships inside the `okengine` package, so the index your agent se
|
|
|
98
98
|
|
|
99
99
|
## Learn more
|
|
100
100
|
|
|
101
|
-
- [Console · Access](/docs/console/access) — how operator tokens and grants work
|
|
102
101
|
- [Agent contracts](/docs/ai/skills) — what agents are taught about the system they operate
|
|
103
102
|
- [Flow](/docs/elements/flow) — the effects the MCP reads back
|
|
104
103
|
|
|
@@ -289,7 +289,6 @@ Bound it at declaration: `maxSteps` caps iterations, `budget.maxCostPerRun` caps
|
|
|
289
289
|
|
|
290
290
|
- [Flow](/docs/elements/flow) — `fx.ask` and `fx.search` inside `do`
|
|
291
291
|
- [Store](/docs/elements/store) — `store.index`, the home of embeddings
|
|
292
|
-
- [Console · AI](/docs/console/ai) — prompts, versions, cost per run
|
|
293
292
|
|
|
294
293
|
## Next
|
|
295
294
|
|
|
@@ -304,9 +303,4 @@ Bound it at declaration: `maxSteps` caps iterations, `budget.maxCostPerRun` caps
|
|
|
304
303
|
description="Eight elements overview."
|
|
305
304
|
href="/docs/get-started/introduction"
|
|
306
305
|
/>
|
|
307
|
-
<Card
|
|
308
|
-
title="Console"
|
|
309
|
-
description="Panels derived from the Manifest."
|
|
310
|
-
href="/docs/console/overview"
|
|
311
|
-
/>
|
|
312
306
|
</Cards>
|
|
@@ -326,7 +326,6 @@ Confirm a visible OS notification titled `okengine webpush interop`.
|
|
|
326
326
|
## Learn more
|
|
327
327
|
|
|
328
328
|
- [Flow](/docs/elements/flow) — `fx.send` inside `do`
|
|
329
|
-
- [Console · Channels](/docs/console/channels) — receipts, attempts, suppression
|
|
330
329
|
- [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
|
|
331
330
|
- [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
|
|
332
331
|
- [i18n](/docs/reference/i18n) — ICU / `fx.t` vs Channel `{{field}}` catalogs
|
|
@@ -341,9 +340,4 @@ Confirm a visible OS notification titled `okengine webpush interop`.
|
|
|
341
340
|
description="Eight elements overview."
|
|
342
341
|
href="/docs/get-started/introduction"
|
|
343
342
|
/>
|
|
344
|
-
<Card
|
|
345
|
-
title="Console"
|
|
346
|
-
description="Panels derived from the Manifest."
|
|
347
|
-
href="/docs/console/overview"
|
|
348
|
-
/>
|
|
349
343
|
</Cards>
|
|
@@ -195,7 +195,6 @@ They are not sharing one CronStore. In-memory leases are per process. Point ever
|
|
|
195
195
|
|
|
196
196
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
|
|
197
197
|
- [fx · Runs](/docs/reference/fx#runs-observability-read) — native SLO checkers via `on(every(…))` + `fx.runs`
|
|
198
|
-
- [Console · Clock](/docs/console/clock) — health numbers, pause / edit / wake early
|
|
199
198
|
- [Signal](/docs/elements/signal) — reacting to events instead of time
|
|
200
199
|
|
|
201
200
|
## Next
|
|
@@ -207,9 +206,4 @@ They are not sharing one CronStore. In-memory leases are per process. Point ever
|
|
|
207
206
|
description="Eight elements overview."
|
|
208
207
|
href="/docs/get-started/introduction"
|
|
209
208
|
/>
|
|
210
|
-
<Card
|
|
211
|
-
title="Console"
|
|
212
|
-
description="Panels derived from the Manifest."
|
|
213
|
-
href="/docs/console/overview"
|
|
214
|
-
/>
|
|
215
209
|
</Cards>
|
|
@@ -320,8 +320,6 @@ Only that run fails — the schedule keeps firing and the process does not exit.
|
|
|
320
320
|
- [fx](/docs/reference/fx) — full `fx` surface, including ICU `fx.t` / `fx.locale`
|
|
321
321
|
- [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
|
|
322
322
|
- [Clock](/docs/elements/clock) — schedules and durable sleep
|
|
323
|
-
- [Console · Flows](/docs/console/flows) — the Manifest-derived panel
|
|
324
|
-
- [Runs](/docs/console/runs) — how a flow execution is observed
|
|
325
323
|
|
|
326
324
|
## Next
|
|
327
325
|
|
|
@@ -332,9 +330,4 @@ Only that run fails — the schedule keeps firing and the process does not exit.
|
|
|
332
330
|
description="Eight elements overview."
|
|
333
331
|
href="/docs/get-started/introduction"
|
|
334
332
|
/>
|
|
335
|
-
<Card
|
|
336
|
-
title="Console"
|
|
337
|
-
description="Panels derived from the Manifest."
|
|
338
|
-
href="/docs/console/overview"
|
|
339
|
-
/>
|
|
340
333
|
</Cards>
|
|
@@ -308,7 +308,6 @@ Keying an authenticated endpoint by IP punishes shared NAT.
|
|
|
308
308
|
- [fx](/docs/reference/fx) — `fx.auth`, `fx.operator`, `fx.principal`
|
|
309
309
|
- [Client](/docs/reference/client) — Bearer `createClient` + `okengine/client/auth`
|
|
310
310
|
- [Plugins](/docs/plugins) — username, magic link, OTP, TOTP, passkeys
|
|
311
|
-
- [Console · Gates](/docs/console/gates) — decision audit and posture
|
|
312
311
|
- [Vault](/docs/elements/vault) — credentials your policies protect
|
|
313
312
|
|
|
314
313
|
## Next
|
|
@@ -316,9 +315,4 @@ Keying an authenticated endpoint by IP punishes shared NAT.
|
|
|
316
315
|
<Cards>
|
|
317
316
|
<Card title="Vault" description="Continue to Vault." href="/docs/elements/vault" />
|
|
318
317
|
<Card title="Plugins" description="Sign-in method plugins under /auth." href="/docs/plugins" />
|
|
319
|
-
<Card
|
|
320
|
-
title="Console"
|
|
321
|
-
description="Panels derived from the Manifest."
|
|
322
|
-
href="/docs/console/overview"
|
|
323
|
-
/>
|
|
324
318
|
</Cards>
|
|
@@ -291,7 +291,6 @@ At-least-once: crash-after-claim reclaims when the lease expires, and a handler
|
|
|
291
291
|
## Learn more
|
|
292
292
|
|
|
293
293
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and `fx.emit`
|
|
294
|
-
- [Console · Signals](/docs/console/signals) — topology, delivery stats, DLQ replay
|
|
295
294
|
- [Clock](/docs/elements/clock) — scheduled and delayed work
|
|
296
295
|
|
|
297
296
|
## Next
|
|
@@ -303,9 +302,4 @@ At-least-once: crash-after-claim reclaims when the lease expires, and a handler
|
|
|
303
302
|
description="Eight elements overview."
|
|
304
303
|
href="/docs/get-started/introduction"
|
|
305
304
|
/>
|
|
306
|
-
<Card
|
|
307
|
-
title="Console"
|
|
308
|
-
description="Panels derived from the Manifest."
|
|
309
|
-
href="/docs/console/overview"
|
|
310
|
-
/>
|
|
311
305
|
</Cards>
|
|
@@ -490,9 +490,8 @@ await fx.store(sessions).set(`sess:${token}`, { userId }, "7d");
|
|
|
490
490
|
const keys = await fx.store(sessions).list("sess:");
|
|
491
491
|
```
|
|
492
492
|
|
|
493
|
-
**Consequence:** `list`
|
|
494
|
-
|
|
495
|
-
readable.
|
|
493
|
+
**Consequence:** `list` enumerates a namespace. Prefer stable key prefixes (`user:`, `sess:`) so
|
|
494
|
+
your Flows and operator tools stay readable.
|
|
496
495
|
|
|
497
496
|
### Per-environment KV drivers
|
|
498
497
|
|
|
@@ -637,9 +636,9 @@ images: {
|
|
|
637
636
|
|
|
638
637
|
**Known limit:** `fs` is single-host. Under horizontal scale each replica sees its own filesystem (silently inconsistent object views). Boot prints a one-shot warning when `drivers.store.files` is `fs`. Use `s3` for docker/prod (create-oke templates already do).
|
|
639
638
|
|
|
640
|
-
<Callout title="
|
|
641
|
-
|
|
642
|
-
|
|
639
|
+
<Callout title="Operator tools can browse, not edit bytes">
|
|
640
|
+
Operator tools can list keys and delete them. Direct edit is **KV + SQL only** — blob bodies are
|
|
641
|
+
not patched.
|
|
643
642
|
</Callout>
|
|
644
643
|
|
|
645
644
|
### Images — `image` / `putImage`
|
|
@@ -861,9 +860,9 @@ Container images come from the `images` map — change the vendor by changing th
|
|
|
861
860
|
|
|
862
861
|
Columns tagged `.pii()` or `.sensitive()` are masked at the store boundary — flows, logs, and the Console see a mask, not the value. Revealing cleartext PII requires an explicit `pii:reveal` gate on the flow, so access is a permission, not a convention.
|
|
863
862
|
|
|
864
|
-
Optional `description` on a store (and `.describe("…")` on fields) becomes
|
|
865
|
-
|
|
866
|
-
|
|
863
|
+
Optional `description` on a store (and `.describe("…")` on fields) becomes its human title in
|
|
864
|
+
operator tools. Single-table `reads` / `writes` in the Manifest power cache invalidation and that
|
|
865
|
+
same PII masking — see the Callout under [Querying by hand](#querying-by-hand).
|
|
867
866
|
|
|
868
867
|
## Troubleshooting
|
|
869
868
|
|
|
@@ -915,7 +914,6 @@ Unlike KV/Files, unset `drivers.store.index` does **not** promote to `pgvector`
|
|
|
915
914
|
- [Flow](/docs/elements/flow) — the `fx.store` session inside `do`
|
|
916
915
|
- [AI](/docs/elements/ai) — `ai.embed` into a vector `store.index`, searched via `fx.search`
|
|
917
916
|
- [Gate](/docs/elements/gate) — `pii:reveal` and other permissions on flows
|
|
918
|
-
- [Console · Store](/docs/console/store) — browse data, cache keys, PII masking
|
|
919
917
|
- [CLI Reference](/docs/reference/cli) — `oke db push` · `generate` · `migrate`
|
|
920
918
|
- [Configuration](/docs/reference/configuration) — `drivers.store` maps and `images` pins
|
|
921
919
|
- [Environment variables](/docs/reference/environment-variables) — Redis · S3 · meilisearch URLs
|
|
@@ -929,9 +927,4 @@ Unlike KV/Files, unset `drivers.store.index` does **not** promote to `pgvector`
|
|
|
929
927
|
description="Eight elements overview."
|
|
930
928
|
href="/docs/get-started/introduction"
|
|
931
929
|
/>
|
|
932
|
-
<Card
|
|
933
|
-
title="Console"
|
|
934
|
-
description="Panels derived from the Manifest."
|
|
935
|
-
href="/docs/console/overview"
|
|
936
|
-
/>
|
|
937
930
|
</Cards>
|
|
@@ -251,7 +251,6 @@ Set the new value (`oke vault set` or the Console). The Console's rotation view
|
|
|
251
251
|
|
|
252
252
|
## Learn more
|
|
253
253
|
|
|
254
|
-
- [Console · Vault](/docs/console/vault) — fingerprints, resolution chain, rotation blast radius
|
|
255
254
|
- [Flow](/docs/elements/flow) — how `fx.vault` reads secrets inside `do`
|
|
256
255
|
- [CLI Reference](/docs/reference/cli) — `oke vault set` · `list` · `import`
|
|
257
256
|
|
|
@@ -264,9 +263,4 @@ Set the new value (`oke vault set` or the Console). The Console's rotation view
|
|
|
264
263
|
description="Eight elements overview."
|
|
265
264
|
href="/docs/get-started/introduction"
|
|
266
265
|
/>
|
|
267
|
-
<Card
|
|
268
|
-
title="Console"
|
|
269
|
-
description="Panels derived from the Manifest."
|
|
270
|
-
href="/docs/console/overview"
|
|
271
|
-
/>
|
|
272
266
|
</Cards>
|
|
@@ -146,7 +146,6 @@ bun test
|
|
|
146
146
|
- [Flow](/docs/elements/flow) — triggers, contracts, effects, and composition
|
|
147
147
|
- [Store](/docs/elements/store) — SQL, KV, files, index
|
|
148
148
|
- [fx](/docs/reference/fx) — the full effect surface
|
|
149
|
-
- [Console · Overview](/docs/console/overview) — Manifest-derived panels
|
|
150
149
|
|
|
151
150
|
## Next
|
|
152
151
|
|
|
@@ -161,9 +160,4 @@ bun test
|
|
|
161
160
|
description="SQL, KV, files, index, and declarative resources."
|
|
162
161
|
href="/docs/elements/store"
|
|
163
162
|
/>
|
|
164
|
-
<Card
|
|
165
|
-
title="Console"
|
|
166
|
-
description="Manifest-derived flows, effects, traces, and architecture."
|
|
167
|
-
href="/docs/console/overview"
|
|
168
|
-
/>
|
|
169
163
|
</Cards>
|
|
@@ -49,7 +49,7 @@ shared lifecycle, optional live DB config, nothing to re-copy.
|
|
|
49
49
|
Your observability stack learned your routes from sampled traffic. The Flow
|
|
50
50
|
you deployed an hour ago is invisible until someone wires it by hand.
|
|
51
51
|
|
|
52
|
-
**OKE derives:**
|
|
52
|
+
**OKE derives:** its operator interface reads the Manifest —
|
|
53
53
|
flows, effects, traces, architecture — current on every save, in dev and prod
|
|
54
54
|
(`:6533`).
|
|
55
55
|
|
|
@@ -118,7 +118,6 @@ ninth element — the set of eight is closed.
|
|
|
118
118
|
- [Flow](/docs/elements/flow) — how effects are recorded and inferred
|
|
119
119
|
- [Vault](/docs/elements/vault) — fail-loud secret contracts
|
|
120
120
|
- [Plugins](/docs/reference/plugins) — the official HTTP glue set
|
|
121
|
-
- [Console · Overview](/docs/console/overview) — the Manifest-derived operator UI
|
|
122
121
|
|
|
123
122
|
## Next
|
|
124
123
|
|
|
@@ -31,7 +31,6 @@ on(orderPlaced, sendReceipt);
|
|
|
31
31
|
description="Official extensions — security, ops, performance."
|
|
32
32
|
href="/docs/plugins"
|
|
33
33
|
/>
|
|
34
|
-
<Card title="Console" description="Manifest-derived operator panels." href="/docs/console" />
|
|
35
34
|
<Card
|
|
36
35
|
title="Deployment"
|
|
37
36
|
description="Compose, Swarm, Kubernetes, reverse proxy."
|
|
@@ -259,9 +259,9 @@ must log who started the call chain.
|
|
|
259
259
|
## Not on `fx`
|
|
260
260
|
|
|
261
261
|
<Callout title="No fx.metric">
|
|
262
|
-
Investigated and declined.
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
Investigated and declined. `fx.runs` already provides per-invocation observability as wide events.
|
|
263
|
+
Native alerting is `fx.runs` + Clock + Channel — not a second counter/gauge API. Optional OTLP
|
|
264
|
+
export for existing Grafana/Datadog stacks is additive and never required.
|
|
265
265
|
</Callout>
|
|
266
266
|
|
|
267
267
|
## Learn more
|
package/src/cli/build.ts
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
* `oke build --target edge` — tree-shaken kernel profile.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { writeFile } from "node:fs/promises";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
|
|
8
5
|
/** Options for {@link runBuild}. */
|
|
9
6
|
export interface BuildOptions {
|
|
10
7
|
readonly target?: "bun" | "node" | "edge";
|
|
@@ -21,8 +18,9 @@ export interface BuildOptions {
|
|
|
21
18
|
}) => Promise<{ success: boolean; logs: string }>;
|
|
22
19
|
/**
|
|
23
20
|
* Inject `.adopt()` barrel regeneration (tests). Default: real
|
|
24
|
-
* `generateAdoptBarrel` + write to `<rootDir>/src/flows/generated.ts
|
|
25
|
-
* Returns the unit names written, for the
|
|
21
|
+
* `generateAdoptBarrel` + atomic write to `<rootDir>/src/flows/generated.ts`
|
|
22
|
+
* (`generated.ts.tmp` → rename). Returns the unit names written, for the
|
|
23
|
+
* status line.
|
|
26
24
|
*/
|
|
27
25
|
readonly syncAdoptBarrel?: (rootDir: string) => Promise<readonly string[]>;
|
|
28
26
|
}
|
|
@@ -33,14 +31,15 @@ export interface BuildOptions {
|
|
|
33
31
|
* pre-step). A real file on disk, not a virtual module: identical resolution
|
|
34
32
|
* under `oke dev`'s runtime `import()` and `oke build`'s `Bun.build()`
|
|
35
33
|
* (investigated — a virtual-module Bun plugin does not resolve consistently
|
|
36
|
-
* across those two paths).
|
|
34
|
+
* across those two paths). Written atomically so a concurrent reader never
|
|
35
|
+
* sees a torn file.
|
|
37
36
|
*
|
|
38
37
|
* @param rootDir - Project root
|
|
39
38
|
*/
|
|
40
39
|
async function defaultSyncAdoptBarrel(rootDir: string): Promise<readonly string[]> {
|
|
41
|
-
const { generateAdoptBarrel } = await import("../compiler/generate-adopt.ts");
|
|
40
|
+
const { generateAdoptBarrel, writeAdoptBarrel } = await import("../compiler/generate-adopt.ts");
|
|
42
41
|
const { source, units } = await generateAdoptBarrel({ rootDir });
|
|
43
|
-
await
|
|
42
|
+
await writeAdoptBarrel(rootDir, source);
|
|
44
43
|
return units;
|
|
45
44
|
}
|
|
46
45
|
|
package/src/cli/dev.test.ts
CHANGED
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
10
|
-
import { mkdtemp } from "node:fs/promises";
|
|
10
|
+
import { mkdir, mkdtemp, readdir } from "node:fs/promises";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { join, resolve } from "node:path";
|
|
13
|
+
import {
|
|
14
|
+
ADOPT_BARREL_FILE,
|
|
15
|
+
generateAdoptBarrel,
|
|
16
|
+
writeAdoptBarrel,
|
|
17
|
+
} from "../compiler/generate-adopt.ts";
|
|
13
18
|
import type { Manifest } from "../manifest/types.ts";
|
|
14
19
|
import type { WideEvent } from "../runs/types.ts";
|
|
15
20
|
import { isDataEnvelope, MCP_DATA_KIND } from "../mcp/data.ts";
|
|
@@ -544,6 +549,88 @@ describe("oke dev hot reload", () => {
|
|
|
544
549
|
}, 60_000);
|
|
545
550
|
});
|
|
546
551
|
|
|
552
|
+
describe("oke dev syncAdoptBarrel atomic write", () => {
|
|
553
|
+
test("default path regenerates generated.ts and leaves no .tmp behind", async () => {
|
|
554
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-dev-adopt-atomic-"));
|
|
555
|
+
await mkdir(join(dir, "src/flows/notes"), { recursive: true });
|
|
556
|
+
await Bun.write(join(dir, "src/flows/notes/index.ts"), "export {};\n");
|
|
557
|
+
await Bun.write(join(dir, "src/app.ts"), "export {};\n");
|
|
558
|
+
await Bun.write(join(dir, "src/flows", ADOPT_BARREL_FILE), "// stale stub\n");
|
|
559
|
+
|
|
560
|
+
const { code } = await runDev({
|
|
561
|
+
cwd: dir,
|
|
562
|
+
dryRun: true,
|
|
563
|
+
stdinIsTTY: false,
|
|
564
|
+
write: () => {},
|
|
565
|
+
});
|
|
566
|
+
expect(code).toBe(0);
|
|
567
|
+
|
|
568
|
+
const barrelPath = join(dir, "src/flows", ADOPT_BARREL_FILE);
|
|
569
|
+
const text = await Bun.file(barrelPath).text();
|
|
570
|
+
expect(text).toContain('export * as notes from "./notes/index.ts";');
|
|
571
|
+
expect(text).not.toContain("stale stub");
|
|
572
|
+
expect(await Bun.file(`${barrelPath}.tmp`).exists()).toBe(false);
|
|
573
|
+
const leftovers = (await readdir(join(dir, "src/flows"))).filter((f) => f.includes(".tmp"));
|
|
574
|
+
expect(leftovers).toEqual([]);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("writeAdoptBarrel uses temp then rename — concurrent readers never see a torn file", async () => {
|
|
578
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-dev-adopt-race-"));
|
|
579
|
+
const flowsDir = join(dir, "src/flows");
|
|
580
|
+
await mkdir(flowsDir, { recursive: true });
|
|
581
|
+
const target = join(flowsDir, ADOPT_BARREL_FILE);
|
|
582
|
+
|
|
583
|
+
// Two complete, same-length payloads so a mid-write truncate would yield
|
|
584
|
+
// mixed or short content that fails both equality checks.
|
|
585
|
+
const markerA = "A";
|
|
586
|
+
const markerB = "B";
|
|
587
|
+
const body = (marker: string) => `// ${marker}\n` + `${marker.repeat(64)}\n`.repeat(2_000);
|
|
588
|
+
const payloadA = body(markerA);
|
|
589
|
+
const payloadB = body(markerB);
|
|
590
|
+
expect(payloadA.length).toBe(payloadB.length);
|
|
591
|
+
expect(payloadA).not.toBe(payloadB);
|
|
592
|
+
|
|
593
|
+
await writeAdoptBarrel(dir, payloadA);
|
|
594
|
+
expect(await Bun.file(target).text()).toBe(payloadA);
|
|
595
|
+
|
|
596
|
+
const reads: string[] = [];
|
|
597
|
+
let stop = false;
|
|
598
|
+
const reader = (async () => {
|
|
599
|
+
while (!stop) {
|
|
600
|
+
try {
|
|
601
|
+
reads.push(await Bun.file(target).text());
|
|
602
|
+
} catch {
|
|
603
|
+
// Target briefly missing only if rename races a delete — ignore.
|
|
604
|
+
}
|
|
605
|
+
// Yield so writers interleave; Bun.sleep(0) is enough on this loop.
|
|
606
|
+
await Bun.sleep(0);
|
|
607
|
+
}
|
|
608
|
+
})();
|
|
609
|
+
|
|
610
|
+
for (let i = 0; i < 80; i++) {
|
|
611
|
+
await writeAdoptBarrel(dir, i % 2 === 0 ? payloadB : payloadA);
|
|
612
|
+
}
|
|
613
|
+
stop = true;
|
|
614
|
+
await reader;
|
|
615
|
+
|
|
616
|
+
expect(reads.length).toBeGreaterThan(10);
|
|
617
|
+
for (const sample of reads) {
|
|
618
|
+
// Every observation is a complete prior or complete next payload —
|
|
619
|
+
// never a truncated prefix, never a mix of A/B markers.
|
|
620
|
+
const ok = sample === payloadA || sample === payloadB;
|
|
621
|
+
expect(ok).toBe(true);
|
|
622
|
+
expect(sample.length).toBe(payloadA.length);
|
|
623
|
+
expect(sample.includes(markerA) && sample.includes(markerB)).toBe(false);
|
|
624
|
+
}
|
|
625
|
+
expect(await Bun.file(`${target}.tmp`).exists()).toBe(false);
|
|
626
|
+
|
|
627
|
+
// Same helper the default `oke dev` path uses end-to-end.
|
|
628
|
+
const { source } = await generateAdoptBarrel({ rootDir: dir });
|
|
629
|
+
await writeAdoptBarrel(dir, source);
|
|
630
|
+
expect(await Bun.file(target).text()).toBe(source);
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
|
|
547
634
|
describe("oke dev mode resolution", () => {
|
|
548
635
|
test("non-TTY + unset mode → local, zero ask, zero docker, no save", async () => {
|
|
549
636
|
const dir = await mkdtemp(join(tmpdir(), "oke-dev-mode-nontty-"));
|
package/src/cli/dev.ts
CHANGED
|
@@ -178,11 +178,13 @@ export interface DevOptions {
|
|
|
178
178
|
readonly onDbAutoPush?: (filename: string) => void;
|
|
179
179
|
/**
|
|
180
180
|
* Injectable `.adopt()` barrel regeneration (tests). Default: real
|
|
181
|
-
* `generateAdoptBarrel` + write to `<cwd>/src/flows/generated.ts
|
|
182
|
-
* once per `oke dev` session, before
|
|
183
|
-
* a real file on disk, so `oke dev`'s
|
|
184
|
-
* `Bun.build()` resolve it
|
|
185
|
-
* plugin, which does not — see
|
|
181
|
+
* `generateAdoptBarrel` + atomic write to `<cwd>/src/flows/generated.ts`
|
|
182
|
+
* (`generated.ts.tmp` → rename). Runs once per `oke dev` session, before
|
|
183
|
+
* the entry is resolved/imported — a real file on disk, so `oke dev`'s
|
|
184
|
+
* runtime `import()` and `oke build`'s `Bun.build()` resolve it
|
|
185
|
+
* identically (unlike a virtual-module Bun plugin, which does not — see
|
|
186
|
+
* `compiler/generate-adopt.ts`). Atomic so a concurrent import during
|
|
187
|
+
* `bun --hot` never sees a torn mid-write file.
|
|
186
188
|
*
|
|
187
189
|
* @param cwd - Project root
|
|
188
190
|
*/
|
|
@@ -346,18 +348,18 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
|
|
|
346
348
|
const cwd = options.cwd ?? process.cwd();
|
|
347
349
|
|
|
348
350
|
// One-shot `.adopt()` barrel regen for the session — real file on disk
|
|
349
|
-
// (`src/flows/generated.ts`), written
|
|
350
|
-
// below so a freshly-added flows unit is
|
|
351
|
-
// Best-effort like `syncDevSchema` below:
|
|
352
|
-
// yet (or a synthetic test tree) never
|
|
351
|
+
// (`src/flows/generated.ts`), written atomically (tmp → rename) before the
|
|
352
|
+
// entry is resolved/imported below so a freshly-added flows unit is
|
|
353
|
+
// adoptable without a hand edit. Best-effort like `syncDevSchema` below:
|
|
354
|
+
// a project with no `src/flows` yet (or a synthetic test tree) never
|
|
355
|
+
// blocks the dev session.
|
|
353
356
|
const syncAdoptBarrel =
|
|
354
357
|
options.syncAdoptBarrel ??
|
|
355
358
|
(async (root: string) => {
|
|
356
|
-
const { generateAdoptBarrel } =
|
|
357
|
-
|
|
359
|
+
const { generateAdoptBarrel, writeAdoptBarrel } =
|
|
360
|
+
await import("../compiler/generate-adopt.ts");
|
|
358
361
|
const { source, units } = await generateAdoptBarrel({ rootDir: root });
|
|
359
|
-
await
|
|
360
|
-
await writeFile(resolve(root, "src/flows/generated.ts"), source);
|
|
362
|
+
await writeAdoptBarrel(root, source);
|
|
361
363
|
return units;
|
|
362
364
|
});
|
|
363
365
|
try {
|
package/src/cli/doctor.ts
CHANGED
|
@@ -3,17 +3,70 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
+
import type { ConfigEnv, DriverRef, DriversConfig, EnvDriverMap } from "../config/index.ts";
|
|
7
|
+
import { resolveEffectiveDrivers, type EffectiveDriversConfig } from "../config/driver-defaults.ts";
|
|
6
8
|
import type { Manifest } from "../manifest/types.ts";
|
|
7
9
|
import { APP_PORT, CONSOLE_PORT, MCP_PORT } from "../runtime/types.ts";
|
|
8
10
|
import { hasFlag, wantsJson } from "./args.ts";
|
|
9
11
|
import { checkManifestPiiAsks } from "./doctor-pii.ts";
|
|
10
12
|
import { EXIT_OK, EXIT_RUNTIME } from "./exit.ts";
|
|
11
|
-
import { loadManifest } from "./load-config.ts";
|
|
13
|
+
import { loadManifest, loadOkeConfig } from "./load-config.ts";
|
|
12
14
|
import { isPortInUse } from "./ports.ts";
|
|
13
15
|
import { schemaFingerprint, readSchemaFingerprint } from "./schema.ts";
|
|
14
16
|
|
|
15
17
|
export { isPortInUse } from "./ports.ts";
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* `label → merged EnvDriverMap` pairs, in the fixed order shown by
|
|
21
|
+
* {@link formatDriversSummary} / the `drivers` JSON key.
|
|
22
|
+
*
|
|
23
|
+
* @param drivers - Fully-resolved drivers config
|
|
24
|
+
*/
|
|
25
|
+
function flattenEffectiveDrivers(
|
|
26
|
+
drivers: EffectiveDriversConfig,
|
|
27
|
+
): ReadonlyArray<readonly [string, EnvDriverMap]> {
|
|
28
|
+
return [
|
|
29
|
+
["store.sql", drivers.store.sql],
|
|
30
|
+
["store.kv", drivers.store.kv],
|
|
31
|
+
["store.files", drivers.store.files],
|
|
32
|
+
["signal", drivers.signal],
|
|
33
|
+
["clock", drivers.clock],
|
|
34
|
+
["journal", drivers.journal],
|
|
35
|
+
["vault", drivers.vault],
|
|
36
|
+
["channel.email", drivers.channel.email],
|
|
37
|
+
["channel.sms", drivers.channel.sms],
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param ref - Driver ref (string id, rich `{ driver }` object, or unset)
|
|
43
|
+
*/
|
|
44
|
+
function driverRefId(ref: DriverRef | undefined): string {
|
|
45
|
+
if (ref === undefined) return "—";
|
|
46
|
+
return typeof ref === "string" ? ref : ref.driver;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render the fully-resolved drivers config as aligned text lines — every
|
|
51
|
+
* default plus every override, per env, for a developer whose own
|
|
52
|
+
* `oke.config.ts` only pins a handful of keys.
|
|
53
|
+
*
|
|
54
|
+
* @param drivers - Fully-resolved drivers config
|
|
55
|
+
* @param configEnv - Active env (marked in the active-driver column)
|
|
56
|
+
*/
|
|
57
|
+
function formatDriversSummary(drivers: EffectiveDriversConfig, configEnv: ConfigEnv): string {
|
|
58
|
+
const rows = flattenEffectiveDrivers(drivers);
|
|
59
|
+
const nameWidth = Math.max(...rows.map(([name]) => name.length));
|
|
60
|
+
const activeWidth = Math.max(...rows.map(([, map]) => driverRefId(map[configEnv]).length));
|
|
61
|
+
const lines = [`drivers (env=${configEnv}):`];
|
|
62
|
+
for (const [name, map] of rows) {
|
|
63
|
+
const active = driverRefId(map[configEnv]);
|
|
64
|
+
const full = `local=${driverRefId(map.local)} docker=${driverRefId(map.docker)} test=${driverRefId(map.test)} prod=${driverRefId(map.prod)}`;
|
|
65
|
+
lines.push(` ${name.padEnd(nameWidth)} ${active.padEnd(activeWidth)} { ${full} }`);
|
|
66
|
+
}
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
17
70
|
/** One doctor finding. */
|
|
18
71
|
export interface DoctorFinding {
|
|
19
72
|
readonly code:
|
|
@@ -55,6 +108,19 @@ export interface DoctorOptions {
|
|
|
55
108
|
}>;
|
|
56
109
|
/** Skip live DB drift probe (default false). */
|
|
57
110
|
readonly skipDbDrift?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Inject the `drivers` block directly (tests) — skips loading
|
|
113
|
+
* `oke.config.ts` from disk. When omitted, `oke doctor` best-effort loads
|
|
114
|
+
* `oke.config.ts` from {@link cwd}; a project without one just gets the
|
|
115
|
+
* real, untouched defaults in the drivers summary.
|
|
116
|
+
*/
|
|
117
|
+
readonly driversConfig?: DriversConfig;
|
|
118
|
+
/**
|
|
119
|
+
* Active {@link ConfigEnv} for the drivers summary. Default: `docker` when
|
|
120
|
+
* `OKE_DOCKER=1` (via {@link env}), else `local` — the same resolution
|
|
121
|
+
* `oke boot` uses without an explicit `env`.
|
|
122
|
+
*/
|
|
123
|
+
readonly configEnv?: ConfigEnv;
|
|
58
124
|
readonly write?: (text: string) => void;
|
|
59
125
|
/** Write hints / progress (defaults to stderr). */
|
|
60
126
|
readonly writeErr?: (text: string) => void;
|
|
@@ -92,6 +158,20 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
92
158
|
const env = options.env ?? ((k) => Bun.env[k] ?? process.env[k]);
|
|
93
159
|
const secretNames = options.secrets ?? (manifest?.vault ? Object.keys(manifest.vault) : []);
|
|
94
160
|
|
|
161
|
+
// Fully-resolved `drivers.*` — real defaults + every override merged, so
|
|
162
|
+
// the picture is complete even when `oke.config.ts` only pins one key.
|
|
163
|
+
let driversConfig = options.driversConfig;
|
|
164
|
+
if (driversConfig === undefined) {
|
|
165
|
+
try {
|
|
166
|
+
driversConfig = (await loadOkeConfig(cwd)).config.drivers;
|
|
167
|
+
} catch {
|
|
168
|
+
// No oke.config.ts (or it failed to load) — show real defaults only.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const configEnv: ConfigEnv =
|
|
172
|
+
options.configEnv ?? (env("OKE_DOCKER") === "1" ? "docker" : "local");
|
|
173
|
+
const effectiveDrivers = resolveEffectiveDrivers(driversConfig);
|
|
174
|
+
|
|
95
175
|
for (const name of secretNames) {
|
|
96
176
|
const value = env(name);
|
|
97
177
|
if (value === undefined || value.length === 0) {
|
|
@@ -177,7 +257,9 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
177
257
|
const code = ok ? EXIT_OK : EXIT_RUNTIME;
|
|
178
258
|
|
|
179
259
|
if (json) {
|
|
180
|
-
write(
|
|
260
|
+
write(
|
|
261
|
+
`${JSON.stringify({ ok, findings, drivers: { env: configEnv, resolved: effectiveDrivers } }, null, 2)}\n`,
|
|
262
|
+
);
|
|
181
263
|
if (!ok) {
|
|
182
264
|
writeErr("Hint: fix error-severity findings, then re-run oke doctor.\n");
|
|
183
265
|
}
|
|
@@ -186,6 +268,7 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
186
268
|
|
|
187
269
|
if (findings.length === 0) {
|
|
188
270
|
write("oke doctor: ok\n");
|
|
271
|
+
write(`${formatDriversSummary(effectiveDrivers, configEnv)}\n`);
|
|
189
272
|
return { code: EXIT_OK, findings };
|
|
190
273
|
}
|
|
191
274
|
|
|
@@ -193,6 +276,7 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
193
276
|
for (const f of findings) {
|
|
194
277
|
write(` [${f.severity}] ${f.code}: ${f.message}\n`);
|
|
195
278
|
}
|
|
279
|
+
write(`${formatDriversSummary(effectiveDrivers, configEnv)}\n`);
|
|
196
280
|
return { code, findings };
|
|
197
281
|
}
|
|
198
282
|
|
|
@@ -208,16 +292,25 @@ export async function doctorCli(args: readonly string[]): Promise<number> {
|
|
|
208
292
|
}
|
|
209
293
|
|
|
210
294
|
let manifestPath: string | undefined;
|
|
295
|
+
let configEnv: ConfigEnv | undefined;
|
|
211
296
|
const json = wantsJson(args);
|
|
212
297
|
for (let i = 0; i < args.length; i++) {
|
|
213
298
|
const a = args[i]!;
|
|
214
299
|
if (a === "--manifest" || a === "-m") manifestPath = args[++i];
|
|
215
|
-
else if (a === "--
|
|
216
|
-
|
|
300
|
+
else if (a === "--env" || a === "-e") {
|
|
301
|
+
const value = args[++i];
|
|
302
|
+
if (value === "local" || value === "docker" || value === "test" || value === "prod") {
|
|
303
|
+
configEnv = value;
|
|
304
|
+
}
|
|
305
|
+
} else if (a === "--help" || a === "-h") {
|
|
306
|
+
console.log(`oke doctor [--manifest|-m path] [--env|-e local|docker|test|prod] [--json|-j]
|
|
217
307
|
oke doctor --diff|-d [--before|-b <path> --after|-a <path>] [--base|-B <branch>]
|
|
218
308
|
|
|
219
309
|
Verify secrets, ports, schema drift, and PII→model egress before serving.
|
|
310
|
+
Also prints drivers.* fully resolved (every default + every override merged)
|
|
311
|
+
for the active env, from oke.config.ts.
|
|
220
312
|
|
|
313
|
+
--env Env to resolve drivers for (default: docker when OKE_DOCKER=1, else local).
|
|
221
314
|
--diff CI gate: block undeclared contract breaks (Manifest Diff).
|
|
222
315
|
Default baseline is git merge-base (main/master) vs the working
|
|
223
316
|
tree; pass --before/--after for an explicit comparison.
|
|
@@ -227,6 +320,6 @@ Verify secrets, ports, schema drift, and PII→model egress before serving.
|
|
|
227
320
|
return EXIT_OK;
|
|
228
321
|
}
|
|
229
322
|
}
|
|
230
|
-
const { code } = await runDoctor({ manifestPath, json });
|
|
323
|
+
const { code } = await runDoctor({ manifestPath, configEnv, json });
|
|
231
324
|
return code;
|
|
232
325
|
}
|