okengine 0.10.1 → 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/README.md +1 -1
- 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 +2 -8
- package/site/content/docs/elements/flow.mdx +10 -17
- package/site/content/docs/elements/gate.mdx +4 -10
- package/site/content/docs/elements/signal.mdx +4 -13
- package/site/content/docs/elements/store.mdx +9 -16
- package/site/content/docs/elements/vault.mdx +1 -7
- package/site/content/docs/get-started/basic-usage.mdx +1 -9
- package/site/content/docs/get-started/introduction.mdx +1 -1
- 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/cli.md +12 -12
- package/site/content/docs/reference/fx.mdx +6 -7
- package/src/auth/auth.test.ts +1 -2
- package/src/auth/bindings.ts +5 -15
- package/src/auth/gate-auth.test.ts +2 -2
- package/src/cli/ai-setup/ai-setup.test.ts +3 -1
- package/src/cli/ai-setup/apply.ts +13 -0
- package/src/cli/build.test.ts +67 -0
- package/src/cli/build.ts +33 -0
- package/src/cli/db-seed.ts +1 -1
- package/src/cli/dev-controls.test.ts +43 -48
- package/src/cli/dev-controls.ts +23 -172
- package/src/cli/dev.test.ts +90 -5
- package/src/cli/dev.ts +39 -16
- 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/client/notes-contract.test.ts +3 -4
- package/src/compiler/aot.test.ts +1 -2
- package/src/compiler/extract.test.ts +76 -21
- package/src/compiler/extract.ts +15 -3
- package/src/compiler/fixtures/raw/raw-unannotated.ts +2 -4
- package/src/compiler/fixtures/skyport/oke.config.ts +4 -2
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +4 -8
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -2
- package/src/compiler/fixtures/skyport/src/flows/support/index.ts +1 -2
- package/src/compiler/fixtures/skyport.expected.json +3 -3
- package/src/compiler/fixtures/triggers/five-triggers.ts +5 -10
- package/src/compiler/generate-adopt.test.ts +85 -0
- package/src/compiler/generate-adopt.ts +111 -0
- 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/console/server/dry-run.audit.test.ts +2 -2
- package/src/console/server/flows.ts +50 -150
- package/src/docker/compose.ts +13 -3
- package/src/docker/images-config.test.ts +168 -0
- package/src/docker/recipes/llama-cpp.ts +10 -1
- package/src/docker/stack-id.ts +1 -0
- package/src/elements/channel/declare.ts +27 -1
- package/src/elements/clock/chaos-child.ts +3 -6
- package/src/elements/clock/chaos.test.ts +1 -2
- package/src/elements/clock.test.ts +2 -4
- package/src/elements/signal/declare.ts +25 -1
- package/src/elements/store/declare.ts +27 -1
- package/src/elements/store/resource.ts +6 -11
- package/src/elements/store/upsert-app.test.ts +1 -2
- package/src/elements/vault/declare.ts +29 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +103 -0
- package/src/kernel/app.ts +66 -9
- package/src/kernel/auto-registry.test.ts +198 -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/honor-config.test.ts +5 -5
- 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/src/kernel/boot.test.ts +19 -15
- package/src/kernel/boot.ts +81 -1
- package/src/kernel/call.test.ts +5 -10
- package/src/kernel/compensate.test.ts +3 -6
- package/src/kernel/concurrency.test.ts +3 -5
- package/src/kernel/correlation.test.ts +4 -8
- package/src/kernel/edge.test.ts +1 -1
- package/src/kernel/effects-stamping.test.ts +4 -7
- package/src/kernel/element-registries.ts +27 -0
- package/src/kernel/errors.ts +11 -0
- package/src/kernel/flow.test.ts +8 -15
- package/src/kernel/flow.ts +12 -14
- package/src/kernel/hook-timing.test.ts +2 -2
- package/src/kernel/hooks.test.ts +5 -10
- package/src/kernel/horizontal-child.ts +5 -10
- package/src/kernel/journal-boot.test.ts +2 -4
- package/src/kernel/pipeline.test.ts +6 -12
- package/src/kernel/plugin/capabilities.test.ts +1 -1
- package/src/kernel/plugin/decorate.test.ts +3 -8
- package/src/kernel/plugin/scoping.test.ts +6 -16
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +1 -2
- package/src/kernel/registry-isolation.test.ts +5 -5
- package/src/kernel/triggers.ts +1 -1
- package/src/plugins/anonymous.ts +1 -3
- package/src/plugins/compression.test.ts +6 -8
- package/src/plugins/config-source.test.ts +1 -1
- package/src/plugins/config-source.ts +1 -2
- package/src/plugins/cors.test.ts +10 -10
- package/src/plugins/csrf.test.ts +2 -2
- package/src/plugins/headers.test.ts +14 -15
- package/src/plugins/ip-allowlist.test.ts +9 -9
- package/src/plugins/magic-link.ts +2 -6
- package/src/plugins/maintenance-mode.test.ts +6 -6
- package/src/plugins/otp.ts +5 -15
- package/src/plugins/passkey.ts +4 -12
- package/src/plugins/two-factor.ts +3 -9
- package/src/plugins/username.ts +2 -6
- package/src/release/measure.ts +1 -1
- package/src/runs/runs.test.ts +3 -5
- package/src/runtime/serve.test.ts +2 -4
- package/src/test/create-test-app.test.ts +3 -7
- package/src/test/provisions.integration.test.ts +4 -9
- package/src/test/reset-element-registries.ts +31 -0
- 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
|
@@ -25,7 +25,7 @@ function get(ip?: string): Request {
|
|
|
25
25
|
|
|
26
26
|
describe("ipAllowlist plugin", () => {
|
|
27
27
|
test("allow list: listed IPs pass, others get 403 Forbidden", async () => {
|
|
28
|
-
on(http.get("/x"), flow(
|
|
28
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
29
29
|
const app = oke({ autoBoot: false, name: "ips" }).plug(ipAllowlist({ allow: ["203.0.113.7"] }));
|
|
30
30
|
|
|
31
31
|
const ok = await app.fetch(get("203.0.113.7"));
|
|
@@ -39,7 +39,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
test("deny list: blocked IPs get 403 ip_denied, others pass", async () => {
|
|
42
|
-
on(http.get("/x"), flow(
|
|
42
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
43
43
|
const app = oke({ autoBoot: false, name: "ips-deny" }).plug(
|
|
44
44
|
ipAllowlist({ deny: ["198.51.100.9"] }),
|
|
45
45
|
);
|
|
@@ -54,7 +54,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test("deny wins over allow on overlap", async () => {
|
|
57
|
-
on(http.get("/x"), flow(
|
|
57
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
58
58
|
const app = oke({ autoBoot: false, name: "ips-both" }).plug(
|
|
59
59
|
ipAllowlist({ allow: ["203.0.113.7"], deny: ["203.0.113.7"] }),
|
|
60
60
|
);
|
|
@@ -64,7 +64,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("XFF last hop is the client; a spoofed first hop cannot bypass allow", async () => {
|
|
67
|
-
on(http.get("/x"), flow(
|
|
67
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
68
68
|
const app = oke({ autoBoot: false, name: "ips-xff" }).plug(
|
|
69
69
|
ipAllowlist({ allow: ["203.0.113.7"] }),
|
|
70
70
|
);
|
|
@@ -78,7 +78,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test("spoofed first hop cannot bypass a deny rule", async () => {
|
|
81
|
-
on(http.get("/x"), flow(
|
|
81
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
82
82
|
const app = oke({ autoBoot: false, name: "ips-xff-deny" }).plug(
|
|
83
83
|
ipAllowlist({ deny: ["198.51.100.9"] }),
|
|
84
84
|
);
|
|
@@ -92,7 +92,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
test("trustedProxyDepth selects the hop behind N trusted proxies", async () => {
|
|
95
|
-
on(http.get("/x"), flow(
|
|
95
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
96
96
|
// Chain: spoofed, real-client, cdn-egress — depth 2 skips the nearest proxy hop.
|
|
97
97
|
const app = oke({ autoBoot: false, name: "ips-depth" }).plug(
|
|
98
98
|
ipAllowlist({ allow: ["203.0.113.7"], trustedProxyDepth: 2 }),
|
|
@@ -113,7 +113,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("missing header: denied when allow is set, permitted for deny-only", async () => {
|
|
116
|
-
on(http.get("/x"), flow(
|
|
116
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
117
117
|
const strict = oke({ autoBoot: false, name: "ips-missing-allow" }).plug(
|
|
118
118
|
ipAllowlist({ allow: ["203.0.113.7"] }),
|
|
119
119
|
);
|
|
@@ -123,7 +123,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
123
123
|
|
|
124
124
|
resetBindings();
|
|
125
125
|
resetFlowSeq();
|
|
126
|
-
on(http.get("/x"), flow(
|
|
126
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
127
127
|
|
|
128
128
|
const lax = oke({ autoBoot: false, name: "ips-missing-deny" }).plug(
|
|
129
129
|
ipAllowlist({ deny: ["198.51.100.9"] }),
|
|
@@ -133,7 +133,7 @@ describe("ipAllowlist plugin", () => {
|
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test("custom header name is honored", async () => {
|
|
136
|
-
on(http.get("/x"), flow(
|
|
136
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
137
137
|
const app = oke({ autoBoot: false, name: "ips-custom" }).plug(
|
|
138
138
|
ipAllowlist({ allow: ["203.0.113.7"], header: "x-real-ip" }),
|
|
139
139
|
);
|
|
@@ -107,9 +107,7 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
107
107
|
})
|
|
108
108
|
: magicLinkTemplate;
|
|
109
109
|
|
|
110
|
-
const request = flow({
|
|
111
|
-
name: "auth.requestMagicLink",
|
|
112
|
-
unit: "auth",
|
|
110
|
+
const request = flow("auth.requestMagicLink", {
|
|
113
111
|
plane: "user",
|
|
114
112
|
in: z.object({ email: z.string().min(3) }),
|
|
115
113
|
out: z.object({
|
|
@@ -144,9 +142,7 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
144
142
|
},
|
|
145
143
|
});
|
|
146
144
|
|
|
147
|
-
const verify = flow({
|
|
148
|
-
name: "auth.verifyMagicLink",
|
|
149
|
-
unit: "auth",
|
|
145
|
+
const verify = flow("auth.verifyMagicLink", {
|
|
150
146
|
plane: "user",
|
|
151
147
|
in: z.object({ token: z.string().min(1) }),
|
|
152
148
|
out: SessionTokensOut,
|
|
@@ -17,7 +17,7 @@ beforeEach(() => {
|
|
|
17
17
|
|
|
18
18
|
describe("maintenanceMode plugin", () => {
|
|
19
19
|
test("returns 503 with ServiceUnavailable envelope and Retry-After", async () => {
|
|
20
|
-
on(http.get("/x"), flow(
|
|
20
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
21
21
|
const app = oke({ autoBoot: false, name: "maint" }).plug(maintenanceMode({ retryAfter: 120 }));
|
|
22
22
|
|
|
23
23
|
const res = await app.fetch(new Request("http://localhost/x"));
|
|
@@ -35,7 +35,7 @@ describe("maintenanceMode plugin", () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
test("enabled: false passes traffic through", async () => {
|
|
38
|
-
on(http.get("/x"), flow(
|
|
38
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
39
39
|
const app = oke({ autoBoot: false, name: "maint-off" }).plug(
|
|
40
40
|
maintenanceMode({ enabled: false }),
|
|
41
41
|
);
|
|
@@ -46,8 +46,8 @@ describe("maintenanceMode plugin", () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test("allowPaths prefixes keep serving", async () => {
|
|
49
|
-
on(http.get("/health"), flow(
|
|
50
|
-
on(http.get("/x"), flow(
|
|
49
|
+
on(http.get("/health"), flow("health.get", { do: () => ({ up: true }) }));
|
|
50
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
51
51
|
const app = oke({ autoBoot: false, name: "maint-allow" }).plug(
|
|
52
52
|
maintenanceMode({ allowPaths: ["/health"] }),
|
|
53
53
|
);
|
|
@@ -60,7 +60,7 @@ describe("maintenanceMode plugin", () => {
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test("bypass header lets operators through (any non-empty value)", async () => {
|
|
63
|
-
on(http.get("/x"), flow(
|
|
63
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
64
64
|
const app = oke({ autoBoot: false, name: "maint-bypass" }).plug(
|
|
65
65
|
maintenanceMode({ bypassHeader: "x-ops-token" }),
|
|
66
66
|
);
|
|
@@ -75,7 +75,7 @@ describe("maintenanceMode plugin", () => {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test("the 503 still flows through onResponse (other plugins stamp it)", async () => {
|
|
78
|
-
on(http.get("/x"), flow(
|
|
78
|
+
on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
|
|
79
79
|
const app = oke({ autoBoot: false, name: "maint-stamp" }).plug(maintenanceMode());
|
|
80
80
|
app.hook("onResponse", (ctx) => {
|
|
81
81
|
if (!ctx.response) return;
|
package/src/plugins/otp.ts
CHANGED
|
@@ -236,9 +236,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
if (opts.mode === "provider") {
|
|
239
|
-
const request = flow({
|
|
240
|
-
name: "auth.requestOtp",
|
|
241
|
-
unit: "auth",
|
|
239
|
+
const request = flow("auth.requestOtp", {
|
|
242
240
|
plane: "user",
|
|
243
241
|
in: z.object({
|
|
244
242
|
phone: z.string().min(8),
|
|
@@ -277,9 +275,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
277
275
|
},
|
|
278
276
|
});
|
|
279
277
|
|
|
280
|
-
const verify = flow({
|
|
281
|
-
name: "auth.verifyOtp",
|
|
282
|
-
unit: "auth",
|
|
278
|
+
const verify = flow("auth.verifyOtp", {
|
|
283
279
|
plane: "user",
|
|
284
280
|
in: z.object({
|
|
285
281
|
phone: z.string().min(8),
|
|
@@ -347,9 +343,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
347
343
|
}
|
|
348
344
|
|
|
349
345
|
// ── App mode ───────────────────────────────────────────────────────────
|
|
350
|
-
const request = flow({
|
|
351
|
-
name: "auth.requestOtp",
|
|
352
|
-
unit: "auth",
|
|
346
|
+
const request = flow("auth.requestOtp", {
|
|
353
347
|
plane: "user",
|
|
354
348
|
in: z.object({
|
|
355
349
|
email: z.string().min(3).optional(),
|
|
@@ -415,9 +409,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
415
409
|
},
|
|
416
410
|
});
|
|
417
411
|
|
|
418
|
-
const resend = flow({
|
|
419
|
-
name: "auth.resendOtp",
|
|
420
|
-
unit: "auth",
|
|
412
|
+
const resend = flow("auth.resendOtp", {
|
|
421
413
|
plane: "user",
|
|
422
414
|
in: z.object({
|
|
423
415
|
email: z.string().min(3).optional(),
|
|
@@ -486,9 +478,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
486
478
|
},
|
|
487
479
|
});
|
|
488
480
|
|
|
489
|
-
const verify = flow({
|
|
490
|
-
name: "auth.verifyOtp",
|
|
491
|
-
unit: "auth",
|
|
481
|
+
const verify = flow("auth.verifyOtp", {
|
|
492
482
|
plane: "user",
|
|
493
483
|
in: z.object({
|
|
494
484
|
email: z.string().min(3).optional(),
|
package/src/plugins/passkey.ts
CHANGED
|
@@ -87,9 +87,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
87
87
|
const rpId = opts.rpId ?? "localhost";
|
|
88
88
|
const origins = opts.origins ?? ["http://localhost", "https://localhost"];
|
|
89
89
|
|
|
90
|
-
const registerOptions = flow({
|
|
91
|
-
name: "auth.passkeyRegisterOptions",
|
|
92
|
-
unit: "auth",
|
|
90
|
+
const registerOptions = flow("auth.passkeyRegisterOptions", {
|
|
93
91
|
plane: "user",
|
|
94
92
|
out: z.object({
|
|
95
93
|
challenge: z.string(),
|
|
@@ -115,9 +113,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
115
113
|
},
|
|
116
114
|
});
|
|
117
115
|
|
|
118
|
-
const register = flow({
|
|
119
|
-
name: "auth.passkeyRegister",
|
|
120
|
-
unit: "auth",
|
|
116
|
+
const register = flow("auth.passkeyRegister", {
|
|
121
117
|
plane: "user",
|
|
122
118
|
in: CeremonyIn.extend({
|
|
123
119
|
publicKey: z.string().min(1),
|
|
@@ -171,9 +167,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
171
167
|
},
|
|
172
168
|
});
|
|
173
169
|
|
|
174
|
-
const authenticateOptions = flow({
|
|
175
|
-
name: "auth.passkeyAuthenticateOptions",
|
|
176
|
-
unit: "auth",
|
|
170
|
+
const authenticateOptions = flow("auth.passkeyAuthenticateOptions", {
|
|
177
171
|
plane: "user",
|
|
178
172
|
in: z.object({ email: z.string().optional() }),
|
|
179
173
|
out: z.object({
|
|
@@ -199,9 +193,7 @@ export function passkey(opts: PasskeyOptions = {}): PluginDef {
|
|
|
199
193
|
},
|
|
200
194
|
});
|
|
201
195
|
|
|
202
|
-
const authenticate = flow({
|
|
203
|
-
name: "auth.passkeyAuthenticate",
|
|
204
|
-
unit: "auth",
|
|
196
|
+
const authenticate = flow("auth.passkeyAuthenticate", {
|
|
205
197
|
plane: "user",
|
|
206
198
|
in: CeremonyIn.extend({
|
|
207
199
|
challenge: z.string().min(1),
|
|
@@ -59,9 +59,7 @@ export function twoFactor(opts: TwoFactorOptions = {}): PluginDef {
|
|
|
59
59
|
const factors = opts.factors ?? createTwoFactorStore();
|
|
60
60
|
const issuer = opts.issuer ?? "oke";
|
|
61
61
|
|
|
62
|
-
const enable = flow({
|
|
63
|
-
name: "auth.twoFactorEnable",
|
|
64
|
-
unit: "auth",
|
|
62
|
+
const enable = flow("auth.twoFactorEnable", {
|
|
65
63
|
plane: "user",
|
|
66
64
|
out: z.object({
|
|
67
65
|
secret: z.string(),
|
|
@@ -91,9 +89,7 @@ export function twoFactor(opts: TwoFactorOptions = {}): PluginDef {
|
|
|
91
89
|
},
|
|
92
90
|
});
|
|
93
91
|
|
|
94
|
-
const verify = flow({
|
|
95
|
-
name: "auth.twoFactorVerify",
|
|
96
|
-
unit: "auth",
|
|
92
|
+
const verify = flow("auth.twoFactorVerify", {
|
|
97
93
|
plane: "user",
|
|
98
94
|
in: z.object({
|
|
99
95
|
userId: z.string().min(1),
|
|
@@ -127,9 +123,7 @@ export function twoFactor(opts: TwoFactorOptions = {}): PluginDef {
|
|
|
127
123
|
},
|
|
128
124
|
});
|
|
129
125
|
|
|
130
|
-
const disable = flow({
|
|
131
|
-
name: "auth.twoFactorDisable",
|
|
132
|
-
unit: "auth",
|
|
126
|
+
const disable = flow("auth.twoFactorDisable", {
|
|
133
127
|
plane: "user",
|
|
134
128
|
out: z.object({ ok: z.literal(true) }),
|
|
135
129
|
errors: { AuthFailed },
|
package/src/plugins/username.ts
CHANGED
|
@@ -319,9 +319,7 @@ export function username(opts: UsernamePluginOptions = {}): PluginDef {
|
|
|
319
319
|
resolveUsernamePolicy(usernamePolicy);
|
|
320
320
|
const crypto = createBunCrypto();
|
|
321
321
|
|
|
322
|
-
const signUp = flow({
|
|
323
|
-
name: "auth.signUpUsername",
|
|
324
|
-
unit: "auth",
|
|
322
|
+
const signUp = flow("auth.signUpUsername", {
|
|
325
323
|
plane: "user",
|
|
326
324
|
in: UsernameIn,
|
|
327
325
|
out: SessionTokensOut,
|
|
@@ -385,9 +383,7 @@ export function username(opts: UsernamePluginOptions = {}): PluginDef {
|
|
|
385
383
|
},
|
|
386
384
|
});
|
|
387
385
|
|
|
388
|
-
const signIn = flow({
|
|
389
|
-
name: "auth.signInUsername",
|
|
390
|
-
unit: "auth",
|
|
386
|
+
const signIn = flow("auth.signInUsername", {
|
|
391
387
|
plane: "user",
|
|
392
388
|
in: UsernameIn,
|
|
393
389
|
out: SessionTokensOut,
|
package/src/release/measure.ts
CHANGED
|
@@ -302,7 +302,7 @@ const { flow } = await import(${JSON.stringify(paths.flow)});
|
|
|
302
302
|
const { on, resetBindings } = await import(${JSON.stringify(paths.on)});
|
|
303
303
|
const { http } = await import(${JSON.stringify(paths.http)});
|
|
304
304
|
resetBindings();
|
|
305
|
-
on(http.get("/ping"), flow(
|
|
305
|
+
on(http.get("/ping"), flow("ping", { do: () => ({ ok: true }) }));
|
|
306
306
|
const app = oke({ name: "cold-start" });
|
|
307
307
|
const rt = createBunRuntime();
|
|
308
308
|
const server = rt.serve(app, { port: 0, hostname: "127.0.0.1" });
|
package/src/runs/runs.test.ts
CHANGED
|
@@ -59,9 +59,7 @@ describe("zero-instrumentation dimensions", () => {
|
|
|
59
59
|
|
|
60
60
|
on(
|
|
61
61
|
http.get("/book").gate(member),
|
|
62
|
-
flow({
|
|
63
|
-
name: "book.create",
|
|
64
|
-
unit: "bookings",
|
|
62
|
+
flow("bookings.create", {
|
|
65
63
|
plane: "user",
|
|
66
64
|
effects: { reads: ["sql:bookings"] },
|
|
67
65
|
do: async (_input, fx) => {
|
|
@@ -92,7 +90,7 @@ describe("zero-instrumentation dimensions", () => {
|
|
|
92
90
|
const events = await runs.all();
|
|
93
91
|
expect(events.length).toBe(1);
|
|
94
92
|
const e = events[0]!;
|
|
95
|
-
expect(e.flow).toBe("
|
|
93
|
+
expect(e.flow).toBe("bookings.create");
|
|
96
94
|
expect(e.unit).toBe("bookings");
|
|
97
95
|
expect(e.trigger).toBe("http");
|
|
98
96
|
expect(e.plane).toBe("user");
|
|
@@ -103,7 +101,7 @@ describe("zero-instrumentation dimensions", () => {
|
|
|
103
101
|
expect(e.buildVersion).toBe("1.2.3");
|
|
104
102
|
expect(e.logs.length).toBe(1);
|
|
105
103
|
expect(e.logs[0]!.message).toBe("booking started");
|
|
106
|
-
expect(e.dimensions.flow).toBe("
|
|
104
|
+
expect(e.dimensions.flow).toBe("bookings.create");
|
|
107
105
|
expect(e.dimensions.tenant).toBe("org_a41");
|
|
108
106
|
expect(e.dimensions.cache).toBe("hit");
|
|
109
107
|
|
|
@@ -57,15 +57,13 @@ function rawHttp(port: number, payload: string): Promise<string> {
|
|
|
57
57
|
function buildApp() {
|
|
58
58
|
on(
|
|
59
59
|
http.get("/ping").gate(gate.public),
|
|
60
|
-
flow({
|
|
61
|
-
name: "ping",
|
|
60
|
+
flow("ping", {
|
|
62
61
|
do: () => ({ ok: true as const, n: 1 }),
|
|
63
62
|
}),
|
|
64
63
|
);
|
|
65
64
|
on(
|
|
66
65
|
http.get("/notes/:id").gate(gate.public),
|
|
67
|
-
flow({
|
|
68
|
-
name: "notes.get",
|
|
66
|
+
flow("notes.get", {
|
|
69
67
|
do: ({ id }: { id: string }) => ({ id }),
|
|
70
68
|
}),
|
|
71
69
|
);
|
|
@@ -37,9 +37,7 @@ describe("createTestApp — four-applications surface", () => {
|
|
|
37
37
|
|
|
38
38
|
on(
|
|
39
39
|
http.post("/orders").gate(member),
|
|
40
|
-
flow({
|
|
41
|
-
name: "orders.create",
|
|
42
|
-
unit: "orders",
|
|
40
|
+
flow("orders.create", {
|
|
43
41
|
in: z.object({ sku: z.string(), qty: z.number() }),
|
|
44
42
|
out: z.object({ id: z.string() }),
|
|
45
43
|
effects: { emits: ["order-placed"] },
|
|
@@ -53,8 +51,7 @@ describe("createTestApp — four-applications surface", () => {
|
|
|
53
51
|
|
|
54
52
|
on(
|
|
55
53
|
orderPlaced,
|
|
56
|
-
flow({
|
|
57
|
-
name: "orders.onPlaced",
|
|
54
|
+
flow("orders.onPlaced", {
|
|
58
55
|
effects: { sends: ["order-confirmed"] },
|
|
59
56
|
do: async ({ orderId }, fx) => {
|
|
60
57
|
await fx.send(orderConfirmed, {
|
|
@@ -67,8 +64,7 @@ describe("createTestApp — four-applications surface", () => {
|
|
|
67
64
|
|
|
68
65
|
on(
|
|
69
66
|
every("1h"),
|
|
70
|
-
flow({
|
|
71
|
-
name: "orders.expire",
|
|
67
|
+
flow("orders.expire", {
|
|
72
68
|
do: (_i, fx) => {
|
|
73
69
|
fx.log.info("expire");
|
|
74
70
|
},
|
|
@@ -48,8 +48,7 @@ describe("Provisions integration", () => {
|
|
|
48
48
|
}),
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
const chargeOrder = flow({
|
|
52
|
-
name: "payments.chargeOrder",
|
|
51
|
+
const chargeOrder = flow("payments.chargeOrder", {
|
|
53
52
|
durable: true,
|
|
54
53
|
in: z.object({ orderId: z.string() }),
|
|
55
54
|
out: z.boolean(),
|
|
@@ -69,9 +68,7 @@ describe("Provisions integration", () => {
|
|
|
69
68
|
|
|
70
69
|
on(
|
|
71
70
|
http.post("/orders").gate(member, canOrder),
|
|
72
|
-
flow({
|
|
73
|
-
name: "orders.create",
|
|
74
|
-
unit: "orders",
|
|
71
|
+
flow("orders.create", {
|
|
75
72
|
in: z.object({ sku: z.string(), qty: z.number() }),
|
|
76
73
|
out: z.object({ id: z.string() }),
|
|
77
74
|
effects: { emits: ["order-placed"], calls: ["payments.chargeOrder"] },
|
|
@@ -85,8 +82,7 @@ describe("Provisions integration", () => {
|
|
|
85
82
|
|
|
86
83
|
on(
|
|
87
84
|
orderPlaced,
|
|
88
|
-
flow({
|
|
89
|
-
name: "orders.onPlaced",
|
|
85
|
+
flow("orders.onPlaced", {
|
|
90
86
|
effects: { calls: ["payments.chargeOrder"] },
|
|
91
87
|
do: async ({ orderId }, fx) => {
|
|
92
88
|
await fx.call(chargeOrder, { orderId });
|
|
@@ -96,8 +92,7 @@ describe("Provisions integration", () => {
|
|
|
96
92
|
|
|
97
93
|
on(
|
|
98
94
|
orderNews,
|
|
99
|
-
flow({
|
|
100
|
-
name: "notifications.onNews",
|
|
95
|
+
flow("notifications.onNews", {
|
|
101
96
|
effects: { sends: ["order-confirmed"] },
|
|
102
97
|
do: async ({ orderId, status }, fx) => {
|
|
103
98
|
if (status !== "confirmed") return;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global safety net for the `store.sql` / `store.files` / `vault.secret` /
|
|
3
|
+
* `signal()` / `channel.<medium>().template()` auto-registries
|
|
4
|
+
* (`src/kernel/element-registries.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Unlike `on()` bindings — created almost exclusively to wire a real app —
|
|
7
|
+
* these four factories are called throughout the suite as bare value
|
|
8
|
+
* constructors, completely unrelated to booting an app (hundreds of call
|
|
9
|
+
* sites across `src/elements/*.test.ts`). Since the registries are plain
|
|
10
|
+
* module-level arrays shared by every test file in one `bun test` process,
|
|
11
|
+
* leaving cleanup to per-file discipline (the convention `on.ts` relies on —
|
|
12
|
+
* see `resetBindings()` / `registry: "ignore"`) would let stray decls from
|
|
13
|
+
* one file silently reach a default-registry `oke()` call in a completely
|
|
14
|
+
* unrelated file. Reset after every test, globally, via `bunfig.toml`
|
|
15
|
+
* `[test].preload`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { afterEach } from "bun:test";
|
|
19
|
+
import {
|
|
20
|
+
channelTemplateRegistry,
|
|
21
|
+
secretRegistry,
|
|
22
|
+
signalRegistry,
|
|
23
|
+
storeRegistry,
|
|
24
|
+
} from "../kernel/element-registries.ts";
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
storeRegistry.length = 0;
|
|
28
|
+
secretRegistry.length = 0;
|
|
29
|
+
signalRegistry.length = 0;
|
|
30
|
+
channelTemplateRegistry.length = 0;
|
|
31
|
+
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Access"
|
|
3
|
-
description: "Identities, roles, API keys."
|
|
4
|
-
icon: "Users"
|
|
5
|
-
source: "docs/spec/console.md"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Answers: **identities, roles, API keys**
|
|
9
|
-
|
|
10
|
-
<Callout title="Governing rule">
|
|
11
|
-
Granting an application scope to an operator is impossible in the UI — taught by absence, not
|
|
12
|
-
refusal.
|
|
13
|
-
</Callout>
|
|
14
|
-
|
|
15
|
-
## What this panel shows
|
|
16
|
-
|
|
17
|
-
<Cards>
|
|
18
|
-
<Card title="Two planes" description="Operators and application users never mix." />
|
|
19
|
-
<Card title="Key creation" description="Value shown once; attenuation to creator scopes only." />
|
|
20
|
-
<Card title="Revocation" description="Blast radius plus honest JWT validity delay." />
|
|
21
|
-
<Card title="Hygiene" description="Unused keys, never-signed-in operators, expired invites." />
|
|
22
|
-
</Cards>
|
|
23
|
-
|
|
24
|
-
## Catalog
|
|
25
|
-
|
|
26
|
-
<Cards>
|
|
27
|
-
<Card title="Dev" description="✓" />
|
|
28
|
-
<Card title="Prod" description="✓ (admin)" />
|
|
29
|
-
</Cards>
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "AI"
|
|
3
|
-
description: "Prompt versions, eval scores, cost, agent runs."
|
|
4
|
-
icon: "Sparkles"
|
|
5
|
-
source: "docs/spec/console.md"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Answers: **prompt versions, eval scores, cost, agent runs**
|
|
9
|
-
|
|
10
|
-
<Callout title="Governing rule">
|
|
11
|
-
Everything else in the Console is deterministic; this panel is built on distributions, not single
|
|
12
|
-
values.
|
|
13
|
-
</Callout>
|
|
14
|
-
|
|
15
|
-
## What this panel shows
|
|
16
|
-
|
|
17
|
-
<Cards>
|
|
18
|
-
<Card title="Prompt versions" description="A version bump is Manifest Diff blast radius." />
|
|
19
|
-
<Card title="Eval + shape" description="Schema-validation failure is its own class." />
|
|
20
|
-
<Card
|
|
21
|
-
title="Agent tools"
|
|
22
|
-
description="Tools are the app’s own flows — undeclared reach is denied and shown."
|
|
23
|
-
/>
|
|
24
|
-
<Card
|
|
25
|
-
title="Budgets"
|
|
26
|
-
description="Cost, semantic cache, and `allowPii` as standing review surfaces."
|
|
27
|
-
/>
|
|
28
|
-
</Cards>
|
|
29
|
-
|
|
30
|
-
## Catalog
|
|
31
|
-
|
|
32
|
-
<Cards>
|
|
33
|
-
<Card title="Dev" description="full" />
|
|
34
|
-
<Card title="Prod" description="read + budgets" />
|
|
35
|
-
</Cards>
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Architecture"
|
|
3
|
-
description: "How it all connects — the diagram that is the code."
|
|
4
|
-
icon: "Network"
|
|
5
|
-
source: "docs/spec/console.md"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Answers: **how it all connects — the diagram that _is_ the code**
|
|
9
|
-
|
|
10
|
-
<Callout title="Governing rule">
|
|
11
|
-
Flows answers “which one”; Architecture answers “what shape”. Never shows the whole system by
|
|
12
|
-
default.
|
|
13
|
-
</Callout>
|
|
14
|
-
|
|
15
|
-
## What this panel shows
|
|
16
|
-
|
|
17
|
-
<Cards>
|
|
18
|
-
<Card title="Unit clusters" description="Default aggregated edges; focus one or two hops." />
|
|
19
|
-
<Card title="Element layers" description="Toggle data, messaging, time, external edges." />
|
|
20
|
-
<Card
|
|
21
|
-
title="Live traffic"
|
|
22
|
-
description="Edge thickness from real traffic; dashed = never traversed."
|
|
23
|
-
/>
|
|
24
|
-
<Card
|
|
25
|
-
title="Pathologies"
|
|
26
|
-
description="Cycles, god nodes, orphan signals, SPOFs — computed from the graph."
|
|
27
|
-
/>
|
|
28
|
-
</Cards>
|
|
29
|
-
|
|
30
|
-
## Catalog
|
|
31
|
-
|
|
32
|
-
<Cards>
|
|
33
|
-
<Card title="Dev" description="✓" />
|
|
34
|
-
<Card title="Prod" description="✓" />
|
|
35
|
-
</Cards>
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Channels"
|
|
3
|
-
description: "Templates, delivery receipts, bounces, opt-outs."
|
|
4
|
-
icon: "Mailbox"
|
|
5
|
-
source: "docs/spec/console.md"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Answers: **templates, delivery receipts, bounces, opt-outs, deliverability (SPF/DKIM/DMARC)**
|
|
9
|
-
|
|
10
|
-
<Callout title="Governing rule">
|
|
11
|
-
Suppression is not failure — the taxonomy of “did not arrive” has seven states with verdicts.
|
|
12
|
-
</Callout>
|
|
13
|
-
|
|
14
|
-
## What this panel shows
|
|
15
|
-
|
|
16
|
-
<Cards>
|
|
17
|
-
<Card title="Dev inbox" description="`console` driver lands every medium locally." />
|
|
18
|
-
<Card
|
|
19
|
-
title="Deliverability"
|
|
20
|
-
description="SPF/DKIM/DMARC, bounces, complaints, suppression list."
|
|
21
|
-
/>
|
|
22
|
-
<Card
|
|
23
|
-
title="Fallback chains"
|
|
24
|
-
description="Recorded as chains (e.g. WhatsApp → SMS), not single outcomes."
|
|
25
|
-
/>
|
|
26
|
-
<Card
|
|
27
|
-
title="Locale previews"
|
|
28
|
-
description="Template data bound to schema; RTL-accurate previews."
|
|
29
|
-
/>
|
|
30
|
-
</Cards>
|
|
31
|
-
|
|
32
|
-
## Catalog
|
|
33
|
-
|
|
34
|
-
<Cards>
|
|
35
|
-
<Card title="Dev" description="full + console inbox" />
|
|
36
|
-
<Card title="Prod" description="send test, inspect delivery" />
|
|
37
|
-
</Cards>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Clock"
|
|
3
|
-
description: "Upcoming crons, sleeping durable flows, journal."
|
|
4
|
-
icon: "Clock"
|
|
5
|
-
source: "docs/spec/console.md"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Answers: **upcoming crons, sleeping durable flows, journal**
|
|
9
|
-
|
|
10
|
-
<Callout title="Governing rule">
|
|
11
|
-
Looks like time — a forward timeline for schedules and pending wakes, not a flat grid.
|
|
12
|
-
</Callout>
|
|
13
|
-
|
|
14
|
-
## What this panel shows
|
|
15
|
-
|
|
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." />
|
|
19
|
-
<Card
|
|
20
|
-
title="DST warnings"
|
|
21
|
-
description="Gap or overlap badge — detect only, no schedule rewrite."
|
|
22
|
-
/>
|
|
23
|
-
<Card title="Actions" description="Run now, wake early, pause; edit when overridable." />
|
|
24
|
-
</Cards>
|
|
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
|
-
|
|
46
|
-
## Catalog
|
|
47
|
-
|
|
48
|
-
<Cards>
|
|
49
|
-
<Card title="Dev" description="full" />
|
|
50
|
-
<Card title="Prod" description="trigger/pause; edit if `overridable`" />
|
|
51
|
-
</Cards>
|