okengine 0.3.6 → 0.5.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 (205) hide show
  1. package/AGENTS.md +2 -0
  2. package/package.json +14 -12
  3. package/site/content/docs/ai/index.mdx +24 -0
  4. package/site/content/docs/ai/llms-txt.mdx +3 -0
  5. package/site/content/docs/ai/meta.json +1 -1
  6. package/site/content/docs/ai/skills.mdx +5 -3
  7. package/site/content/docs/console/gates.mdx +46 -8
  8. package/site/content/docs/console/index.mdx +54 -0
  9. package/site/content/docs/console/meta.json +1 -0
  10. package/site/content/docs/elements/ai.mdx +2 -0
  11. package/site/content/docs/elements/channel.mdx +2 -0
  12. package/site/content/docs/elements/clock.mdx +1 -4
  13. package/site/content/docs/elements/flow.mdx +4 -10
  14. package/site/content/docs/elements/gate.mdx +189 -48
  15. package/site/content/docs/elements/index.mdx +45 -0
  16. package/site/content/docs/elements/meta.json +1 -1
  17. package/site/content/docs/elements/signal.mdx +1 -5
  18. package/site/content/docs/elements/store.mdx +27 -6
  19. package/site/content/docs/elements/vault.mdx +10 -11
  20. package/site/content/docs/get-started/basic-usage.mdx +79 -43
  21. package/site/content/docs/get-started/index.mdx +33 -0
  22. package/site/content/docs/get-started/installation.mdx +95 -43
  23. package/site/content/docs/get-started/introduction.mdx +128 -75
  24. package/site/content/docs/get-started/meta.json +1 -1
  25. package/site/content/docs/get-started/why.mdx +141 -0
  26. package/site/content/docs/index.mdx +9 -31
  27. package/site/content/docs/plugins/anonymous.mdx +95 -0
  28. package/site/content/docs/plugins/compression.mdx +2 -2
  29. package/site/content/docs/plugins/cors.mdx +2 -2
  30. package/site/content/docs/plugins/csrf.mdx +2 -2
  31. package/site/content/docs/plugins/email-otp.mdx +111 -0
  32. package/site/content/docs/plugins/{security-headers.mdx → headers.mdx} +2 -2
  33. package/site/content/docs/plugins/index.mdx +69 -0
  34. package/site/content/docs/plugins/ip-allowlist.mdx +1 -2
  35. package/site/content/docs/plugins/magic-link.mdx +112 -0
  36. package/site/content/docs/plugins/maintenance-mode.mdx +8 -8
  37. package/site/content/docs/plugins/meta.json +10 -1
  38. package/site/content/docs/plugins/passkey.mdx +128 -0
  39. package/site/content/docs/plugins/phone-number.mdx +111 -0
  40. package/site/content/docs/plugins/two-factor.mdx +116 -0
  41. package/site/content/docs/plugins/username.mdx +117 -0
  42. package/site/content/docs/reference/client.mdx +331 -0
  43. package/site/content/docs/reference/configuration.mdx +1 -1
  44. package/site/content/docs/reference/environment-variables.mdx +5 -3
  45. package/site/content/docs/reference/fx.mdx +36 -8
  46. package/site/content/docs/reference/index.mdx +45 -0
  47. package/site/content/docs/reference/meta.json +11 -1
  48. package/site/content/docs/reference/plugins.mdx +25 -14
  49. package/src/auth/auth.test.ts +20 -2
  50. package/src/auth/bindings.ts +439 -0
  51. package/src/auth/breach-check.ts +112 -0
  52. package/src/auth/config.ts +288 -0
  53. package/src/auth/cookies.ts +123 -0
  54. package/src/auth/gate-auth.test.ts +379 -0
  55. package/src/auth/identity.ts +190 -0
  56. package/src/auth/index.ts +117 -1
  57. package/src/auth/method-context.ts +33 -0
  58. package/src/auth/operator.ts +27 -1
  59. package/src/auth/password-policy.test.ts +126 -0
  60. package/src/auth/password-policy.ts +77 -0
  61. package/src/auth/plugin.ts +62 -4
  62. package/src/auth/rate.ts +45 -0
  63. package/src/auth/schema.ts +260 -0
  64. package/src/auth/secondary-storage.ts +37 -0
  65. package/src/auth/sessions.ts +58 -1
  66. package/src/auth/tables.ts +4 -0
  67. package/src/auth/verification.ts +78 -0
  68. package/src/cli/competitor-mention-removal.test.ts +117 -0
  69. package/src/cli/dev.test.ts +3 -3
  70. package/src/cli/dev.ts +20 -0
  71. package/src/cli/meilisearch-local.test.ts +69 -0
  72. package/src/cli/meilisearch-local.ts +188 -0
  73. package/src/cli/schema.ts +95 -23
  74. package/src/client/auth.ts +120 -0
  75. package/src/client-react/index.ts +93 -0
  76. package/src/compiler/aot.test.ts +2 -1
  77. package/src/compiler/extract.ts +19 -0
  78. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +5 -1
  79. package/src/compiler/response.ts +16 -2
  80. package/src/console/server/app.ts +10 -6
  81. package/src/console/server/auth-rate.test.ts +3 -3
  82. package/src/console/server/bind.ts +12 -1
  83. package/src/console/server/channels.test.ts +1 -1
  84. package/src/console/server/console-gates.ts +14 -0
  85. package/src/console/server/console.test.ts +6 -6
  86. package/src/console/server/flows-invoke.test.ts +2 -2
  87. package/src/console/server/flows.ts +2 -0
  88. package/src/console/server/gates.ts +8 -1
  89. package/src/console/server/operator-db.test.ts +4 -4
  90. package/src/console/server/operator-db.ts +22 -4
  91. package/src/console/server/security.gate.test.ts +3 -3
  92. package/src/console/server/store.test.ts +1 -1
  93. package/src/console/server/store.ts +11 -1
  94. package/src/console/ui/dist/assets/index-CjxwRGVv.js +10 -0
  95. package/src/console/ui/dist/assets/panel-access-BGv45snf.js +64 -0
  96. package/src/console/ui/dist/assets/{panel-ai-D_m6WQI8.js → panel-ai-B2S7LEii.js} +1 -1
  97. package/src/console/ui/dist/assets/{panel-architecture-CKnXFyUx.js → panel-architecture-D7UJh91v.js} +1 -1
  98. package/src/console/ui/dist/assets/{panel-channels-DCDd4WAC.js → panel-channels-9T3ybqRu.js} +1 -1
  99. package/src/console/ui/dist/assets/panel-clock-Cb1UXGRQ.js +1 -0
  100. package/src/console/ui/dist/assets/{panel-diff-cdonmH8c.js → panel-diff-DmYbKWmN.js} +1 -1
  101. package/src/console/ui/dist/assets/panel-flows-PiHwT55z.js +48 -0
  102. package/src/console/ui/dist/assets/{panel-gates-B5eTE8XH.js → panel-gates-BQGYXvjT.js} +1 -1
  103. package/src/console/ui/dist/assets/panel-overview-BBnRO18l.js +1 -0
  104. package/src/console/ui/dist/assets/{panel-plugins-Cj7DK1er.js → panel-plugins-D0PsmVw2.js} +1 -1
  105. package/src/console/ui/dist/assets/panel-runs-CWuRDe0r.js +1 -0
  106. package/src/console/ui/dist/assets/{panel-signals-whmDXIg3.js → panel-signals-Bbg4ewpP.js} +1 -1
  107. package/src/console/ui/dist/assets/{panel-store-CEMHLvaw.js → panel-store-CPCbsDRa.js} +1 -1
  108. package/src/console/ui/dist/assets/panel-traces-DVAzuA_S.js +1 -0
  109. package/src/console/ui/dist/assets/{panel-vault-C9wjbki8.js → panel-vault-D1_MvOmo.js} +1 -1
  110. package/src/console/ui/dist/assets/{rolldown-runtime-CNC7AqOf.js → rolldown-runtime-B0Z9INg1.js} +1 -1
  111. package/src/console/ui/dist/index.html +2 -2
  112. package/src/console/ui/gates/fixture.ts +4 -0
  113. package/src/console/ui/gates/types.ts +2 -0
  114. package/src/console/ui/shell/client.ts +1 -0
  115. package/src/docker/compose.ts +5 -0
  116. package/src/docker/docker.test.ts +41 -0
  117. package/src/docker/recipes/index.ts +10 -2
  118. package/src/docker/recipes/meilisearch.ts +31 -0
  119. package/src/drivers/conformance.test.ts +16 -1
  120. package/src/drivers/conformance.ts +40 -3
  121. package/src/drivers/index.ts +14 -2
  122. package/src/drivers/libsql.ts +4 -4
  123. package/src/drivers/meilisearch.integration.test.ts +77 -0
  124. package/src/drivers/meilisearch.test.ts +181 -0
  125. package/src/drivers/meilisearch.ts +208 -0
  126. package/src/drivers/memory.ts +4 -4
  127. package/src/drivers/pgvector.ts +6 -6
  128. package/src/drivers/types.ts +93 -12
  129. package/src/drivers/vault-driver-removal.test.ts +6 -0
  130. package/src/drivers/vault-types.ts +4 -4
  131. package/src/elements/ai/runtime.ts +6 -0
  132. package/src/elements/ai.test.ts +22 -0
  133. package/src/elements/gate/boot.ts +136 -0
  134. package/src/elements/gate/config.ts +69 -0
  135. package/src/elements/gate/declare.ts +51 -1
  136. package/src/elements/gate/runtime.ts +3 -1
  137. package/src/elements/gate.test.ts +77 -0
  138. package/src/elements/gate.ts +20 -1
  139. package/src/elements/index.ts +8 -0
  140. package/src/elements/store/index-boot.test.ts +49 -7
  141. package/src/elements/store/runtime.ts +50 -15
  142. package/src/elements/store.ts +2 -0
  143. package/src/elements/vault.test.ts +27 -4
  144. package/src/elements/vault.ts +1 -1
  145. package/src/index.ts +15 -0
  146. package/src/kernel/app.ts +253 -32
  147. package/src/kernel/boot-bind/store.test.ts +9 -0
  148. package/src/kernel/boot-bind/store.ts +30 -2
  149. package/src/kernel/boot.test.ts +40 -3
  150. package/src/kernel/boot.ts +8 -0
  151. package/src/kernel/call.test.ts +46 -2
  152. package/src/kernel/concurrency.test.ts +58 -0
  153. package/src/kernel/concurrency.ts +48 -0
  154. package/src/kernel/edge.test.ts +3 -3
  155. package/src/kernel/flow.test.ts +2 -2
  156. package/src/kernel/fx.test.ts +12 -2
  157. package/src/kernel/fx.ts +97 -5
  158. package/src/kernel/hooks.test.ts +4 -4
  159. package/src/kernel/index.ts +22 -1
  160. package/src/kernel/pipeline.test.ts +12 -8
  161. package/src/kernel/pipeline.ts +23 -4
  162. package/src/kernel/plugin/decorate.test.ts +3 -3
  163. package/src/kernel/plugin/scoping.test.ts +3 -3
  164. package/src/kernel/plugin-elements.test.ts +51 -0
  165. package/src/kernel/plugin-needs.test.ts +83 -0
  166. package/src/kernel/plugin-needs.ts +129 -0
  167. package/src/kernel/plugin.ts +101 -0
  168. package/src/kernel/redacted.ts +74 -0
  169. package/src/kernel/registry-isolation.test.ts +5 -5
  170. package/src/kernel/registry.ts +102 -3
  171. package/src/kernel/router.ts +3 -3
  172. package/src/manifest/types.ts +2 -0
  173. package/src/plugins/anonymous.ts +58 -0
  174. package/src/plugins/auth/shared.ts +121 -0
  175. package/src/plugins/auth-methods.test.ts +176 -0
  176. package/src/plugins/compression.test.ts +5 -5
  177. package/src/plugins/config-source.test.ts +1 -1
  178. package/src/plugins/cors.test.ts +16 -10
  179. package/src/plugins/csrf.test.ts +1 -1
  180. package/src/plugins/email-otp.ts +161 -0
  181. package/src/plugins/index.ts +31 -0
  182. package/src/plugins/ip-allowlist.test.ts +19 -9
  183. package/src/plugins/magic-link.ts +163 -0
  184. package/src/plugins/maintenance-mode.test.ts +9 -5
  185. package/src/plugins/passkey.ts +216 -0
  186. package/src/plugins/phone-number.ts +149 -0
  187. package/src/plugins/security-headers.test.ts +14 -14
  188. package/src/plugins/two-factor.ts +249 -0
  189. package/src/plugins/username.ts +148 -0
  190. package/src/runs/runs.test.ts +6 -2
  191. package/src/runtime/primitives.ts +37 -4
  192. package/src/runtime/serve.test.ts +3 -2
  193. package/src/runtime/types.ts +25 -2
  194. package/src/test/create-test-app.test.ts +1 -1
  195. package/src/test/create-test-app.ts +4 -1
  196. package/src/test/provisions.integration.test.ts +2 -2
  197. package/site/content/docs/get-started/comparison.mdx +0 -65
  198. package/src/console/ui/dist/assets/index-CrKMmO__.js +0 -10
  199. package/src/console/ui/dist/assets/panel-access-C0J2D-a2.js +0 -64
  200. package/src/console/ui/dist/assets/panel-clock-DjGGFPzr.js +0 -1
  201. package/src/console/ui/dist/assets/panel-flows-DlCU5zjA.js +0 -45
  202. package/src/console/ui/dist/assets/panel-overview-BsFvDdts.js +0 -1
  203. package/src/console/ui/dist/assets/panel-runs-C0gmnoYL.js +0 -1
  204. package/src/console/ui/dist/assets/panel-traces-BDiAuVSK.js +0 -1
  205. package/src/drivers/vault-infisical.ts +0 -57
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test, beforeEach } from "bun:test";
2
+ import { clock } from "../elements/clock.ts";
3
+ import { gate } from "../elements/gate.ts";
4
+ import { signal } from "../elements/signal.ts";
5
+ import { vault } from "../elements/vault.ts";
6
+ import { oke } from "./app.ts";
7
+ import { flow, resetFlowSeq } from "./flow.ts";
8
+ import { on, resetBindings } from "./on.ts";
9
+ import { plugin } from "./plugin.ts";
10
+ import { http } from "./triggers.ts";
11
+
12
+ beforeEach(() => {
13
+ resetBindings();
14
+ resetFlowSeq();
15
+ });
16
+
17
+ describe("plugin element contributions", () => {
18
+ test("vault / clock / signal / gate merge into boot options", async () => {
19
+ const otpSecret = vault.secret("OTP_SIGNING_KEY", {
20
+ description: "2FA HMAC",
21
+ dev: "test-otp-signing-key",
22
+ });
23
+ const expire = clock("expire-otp", { every: "1m" });
24
+ const bus = signal("otp.events", { delivery: "once" });
25
+ const member = gate.policy("plugin-member", ({ auth }) => !!auth.verified);
26
+
27
+ const twoFactor = plugin("twoFactor-scaffold", { version: "0.0.1" })
28
+ .vault(otpSecret)
29
+ .clock(expire)
30
+ .signal(bus)
31
+ .gate(member)
32
+ .table("otp_codes")
33
+ .needs("store.sql");
34
+
35
+ on(http.get("/ok").gate(gate.public), flow({ name: "ok", do: () => ({ ok: true }) }));
36
+
37
+ const app = oke({
38
+ name: "plugin-elements",
39
+ gate: { policies: [gate.public] },
40
+ vault: { allowDevFallbacks: true },
41
+ }).plug(twoFactor);
42
+
43
+ await app.boot({ env: "test", unguardedHttp: "allow" });
44
+ expect(app.booted).toBe(true);
45
+ expect(app.elements?.vault).toBeDefined();
46
+ expect(app.elements?.clock).toBeDefined();
47
+ expect(app.elements?.signal).toBeDefined();
48
+ expect(app.elements?.gate).toBeDefined();
49
+ await app.stop();
50
+ });
51
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { oke } from "./app.ts";
3
+ import { flow } from "./flow.ts";
4
+ import { on, resetBindings } from "./on.ts";
5
+ import { plugin } from "./plugin.ts";
6
+ import { assertPluginNeeds, PluginNeedsError, buildAvailableNeedTokens } from "./plugin-needs.ts";
7
+ import { http } from "./triggers.ts";
8
+ import { resetFlowSeq } from "./flow.ts";
9
+ import { gate } from "../elements/gate.ts";
10
+
11
+ describe("plugin .needs() resolution", () => {
12
+ test("plugin-name dependency fails when missing", () => {
13
+ expect(() =>
14
+ assertPluginNeeds(
15
+ {
16
+ twoFactor: {
17
+ name: "twoFactor",
18
+ version: "0.0.1",
19
+ declares: [],
20
+ intercepts: [],
21
+ needs: ["auth"],
22
+ },
23
+ },
24
+ { pluginNames: new Set(["twoFactor"]), available: new Set() },
25
+ ),
26
+ ).toThrow(PluginNeedsError);
27
+ });
28
+
29
+ test("plugin-name dependency passes when plugged", () => {
30
+ expect(() =>
31
+ assertPluginNeeds(
32
+ {
33
+ auth: {
34
+ name: "auth",
35
+ version: "0.0.1",
36
+ declares: [],
37
+ intercepts: [],
38
+ needs: [],
39
+ },
40
+ twoFactor: {
41
+ name: "twoFactor",
42
+ version: "0.0.1",
43
+ declares: [],
44
+ intercepts: [],
45
+ needs: ["auth"],
46
+ },
47
+ },
48
+ {
49
+ pluginNames: new Set(["auth", "twoFactor"]),
50
+ available: new Set(),
51
+ },
52
+ ),
53
+ ).not.toThrow();
54
+ });
55
+
56
+ test("element token store.sql from available set", () => {
57
+ const available = buildAvailableNeedTokens({ elements: { storeSql: true } });
58
+ expect(available.has("store.sql")).toBe(true);
59
+ expect(() =>
60
+ assertPluginNeeds(
61
+ {
62
+ auth: {
63
+ name: "auth",
64
+ version: "0.0.1",
65
+ declares: ["table:oke_sessions"],
66
+ intercepts: [],
67
+ needs: ["store.sql"],
68
+ },
69
+ },
70
+ { pluginNames: new Set(["auth"]), available },
71
+ ),
72
+ ).not.toThrow();
73
+ });
74
+
75
+ test("boot fails when plugin needs missing peer plugin", async () => {
76
+ resetBindings();
77
+ resetFlowSeq();
78
+ on(http.get("/x").gate(gate.public), flow({ name: "x", do: () => ({ ok: true }) }));
79
+ const dependent = plugin("needs-auth", { version: "0.0.1" }).needs("auth");
80
+ const app = oke({ name: "needs-boot", gate: { policies: [gate.public] } }).plug(dependent);
81
+ await expect(app.boot({ env: "test" })).rejects.toThrow(PluginNeedsError);
82
+ });
83
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Resolve plugin `.needs(dep)` at boot — fail loud when unmet.
3
+ *
4
+ * Tokens are either:
5
+ * - another plugged plugin name (e.g. `"auth"`)
6
+ * - an element / driver id (e.g. `"store.sql"`, `"postgres"`)
7
+ */
8
+
9
+ import type { PluginCapabilities } from "./plugin.ts";
10
+
11
+ /** Known element / facet dependency tokens. */
12
+ export const ELEMENT_NEED_TOKENS = [
13
+ "store.sql",
14
+ "store.kv",
15
+ "store.files",
16
+ "store.index",
17
+ "signal",
18
+ "clock",
19
+ "gate",
20
+ "vault",
21
+ "channel",
22
+ "ai",
23
+ "flow",
24
+ ] as const;
25
+
26
+ /** One unmet `.needs()` dependency. */
27
+ export interface PluginNeedGap {
28
+ /** Plugin that declared the need. */
29
+ readonly plugin: string;
30
+ /** Unmet dependency token. */
31
+ readonly need: string;
32
+ }
33
+
34
+ /**
35
+ * Thrown when installed plugins declare unmet `.needs()` dependencies.
36
+ */
37
+ export class PluginNeedsError extends Error {
38
+ readonly gaps: readonly PluginNeedGap[];
39
+
40
+ constructor(gaps: readonly PluginNeedGap[]) {
41
+ const lines = gaps.map((g) => ` - ${g.plugin} needs "${g.need}"`);
42
+ super(
43
+ `plugin boot failed — ${gaps.length} unmet .needs() dependenc${gaps.length === 1 ? "y" : "ies"}:\n${lines.join("\n")}`,
44
+ );
45
+ this.name = "PluginNeedsError";
46
+ this.gaps = gaps;
47
+ }
48
+ }
49
+
50
+ /** Context for resolving whether a need token is available. */
51
+ export interface PluginNeedsContext {
52
+ /** Installed plugin names. */
53
+ readonly pluginNames: ReadonlySet<string>;
54
+ /**
55
+ * Available element / driver tokens (e.g. `store.sql`, `postgres`).
56
+ * Built from boot options, declared drivers, and element needs.
57
+ */
58
+ readonly available: ReadonlySet<string>;
59
+ }
60
+
61
+ /**
62
+ * Collect unmet needs across all plugin capabilities.
63
+ *
64
+ * @param capabilities - Registry capability map
65
+ * @param ctx - Available plugins + element/driver tokens
66
+ */
67
+ export function collectUnmetPluginNeeds(
68
+ capabilities: Readonly<Record<string, PluginCapabilities>>,
69
+ ctx: PluginNeedsContext,
70
+ ): PluginNeedGap[] {
71
+ const gaps: PluginNeedGap[] = [];
72
+ for (const caps of Object.values(capabilities)) {
73
+ for (const need of caps.needs) {
74
+ if (ctx.pluginNames.has(need) || ctx.available.has(need)) continue;
75
+ gaps.push({ plugin: caps.name, need });
76
+ }
77
+ }
78
+ return gaps;
79
+ }
80
+
81
+ /**
82
+ * Assert every plugin `.needs()` is satisfied. Throws {@link PluginNeedsError}.
83
+ *
84
+ * @param capabilities - Registry capability map
85
+ * @param ctx - Available plugins + element/driver tokens
86
+ */
87
+ export function assertPluginNeeds(
88
+ capabilities: Readonly<Record<string, PluginCapabilities>>,
89
+ ctx: PluginNeedsContext,
90
+ ): void {
91
+ const gaps = collectUnmetPluginNeeds(capabilities, ctx);
92
+ if (gaps.length > 0) throw new PluginNeedsError(gaps);
93
+ }
94
+
95
+ /**
96
+ * Build the available token set from boot-time signals.
97
+ *
98
+ * @param input - Element/driver presence flags and declared driver ids
99
+ */
100
+ export function buildAvailableNeedTokens(input: {
101
+ readonly elements?: {
102
+ readonly storeSql?: boolean;
103
+ readonly storeKv?: boolean;
104
+ readonly storeFiles?: boolean;
105
+ readonly storeIndex?: boolean;
106
+ readonly signal?: boolean;
107
+ readonly clock?: boolean;
108
+ readonly gate?: boolean;
109
+ readonly vault?: boolean;
110
+ readonly channel?: boolean;
111
+ readonly ai?: boolean;
112
+ };
113
+ readonly driverIds?: readonly string[];
114
+ }): Set<string> {
115
+ const available = new Set<string>();
116
+ const e = input.elements ?? {};
117
+ if (e.storeSql) available.add("store.sql");
118
+ if (e.storeKv) available.add("store.kv");
119
+ if (e.storeFiles) available.add("store.files");
120
+ if (e.storeIndex) available.add("store.index");
121
+ if (e.signal) available.add("signal");
122
+ if (e.clock) available.add("clock");
123
+ if (e.gate) available.add("gate");
124
+ if (e.vault) available.add("vault");
125
+ if (e.channel) available.add("channel");
126
+ if (e.ai) available.add("ai");
127
+ for (const id of input.driverIds ?? []) available.add(id);
128
+ return available;
129
+ }
@@ -14,8 +14,14 @@
14
14
  * @see docs/spec/console.md §9.15
15
15
  */
16
16
 
17
+ import type { ChannelTemplateDecl } from "../elements/channel/declare.ts";
18
+ import type { ClockDecl } from "../elements/clock/declare.ts";
19
+ import type { GateDecl } from "../elements/gate/declare.ts";
20
+ import type { SignalDecl } from "../elements/signal/declare.ts";
21
+ import type { VaultSecretDecl } from "../elements/vault/declare.ts";
17
22
  import type { SchemaInput } from "../validation/standard-schema.ts";
18
23
  import type { AnyFlowDef, FlowErrorMap } from "./flow.ts";
24
+ import type { Binding } from "./on.ts";
19
25
  import type { HookFn, HookStage } from "./hooks.ts";
20
26
 
21
27
  /** Brand carrying accumulated decoration types through the builder. */
@@ -226,6 +232,13 @@ export interface PluginApi {
226
232
  columns?: Readonly<Record<string, unknown>>,
227
233
  options?: PluginTableOptions,
228
234
  ): PluginApi;
235
+ /**
236
+ * Contribute an HTTP (or other) Binding into the app router + adopted set.
237
+ * Required for auth method plugins — registry `flows[]` alone does not route.
238
+ *
239
+ * @param binding - Trigger + flow pair
240
+ */
241
+ binding(binding: Binding): PluginApi;
229
242
  /**
230
243
  * Contribute a flow.
231
244
  *
@@ -245,6 +258,36 @@ export interface PluginApi {
245
258
  * @param schema - Standard Schema
246
259
  */
247
260
  config(schema: SchemaInput): PluginApi;
261
+ /**
262
+ * Contribute a vault secret/config contract (merged into boot secrets).
263
+ *
264
+ * @param secret - Vault contract from `vault.secret(...)` / `vault.config(...)`
265
+ */
266
+ vault(secret: VaultSecretDecl): PluginApi;
267
+ /**
268
+ * Contribute a named clock schedule (merged into boot clocks).
269
+ *
270
+ * @param decl - Clock from `clock(...)`
271
+ */
272
+ clock(decl: ClockDecl): PluginApi;
273
+ /**
274
+ * Contribute a signal declaration (merged into boot signals).
275
+ *
276
+ * @param decl - Signal from `signal(...)`
277
+ */
278
+ signal(decl: SignalDecl): PluginApi;
279
+ /**
280
+ * Contribute a gate declaration (merged into boot gates).
281
+ *
282
+ * @param decl - Gate from `gate.policy` / `gate.scope` / `gate.rate` / `gate.public`
283
+ */
284
+ gate(decl: GateDecl): PluginApi;
285
+ /**
286
+ * Contribute a channel template (merged into boot channel templates).
287
+ *
288
+ * @param decl - Template from `channel.email.template(...)` etc.
289
+ */
290
+ channelTemplate(decl: ChannelTemplateDecl): PluginApi;
248
291
  }
249
292
 
250
293
  /** Captured capability lists for the Manifest. */
@@ -276,9 +319,15 @@ export interface PluginRegistration {
276
319
  readonly cli: readonly CliContribution[];
277
320
  readonly images: readonly ImageRecipeContribution[];
278
321
  readonly flows: readonly AnyFlowDef[];
322
+ readonly bindings: readonly Binding[];
279
323
  readonly errors: FlowErrorMap;
280
324
  readonly client: readonly ClientExtensionContribution[];
281
325
  readonly configSchema: SchemaInput | undefined;
326
+ readonly vaultSecrets: readonly VaultSecretDecl[];
327
+ readonly clocks: readonly ClockDecl[];
328
+ readonly signals: readonly SignalDecl[];
329
+ readonly gates: readonly GateDecl[];
330
+ readonly channelTemplates: readonly ChannelTemplateDecl[];
282
331
  }
283
332
 
284
333
  /**
@@ -383,6 +432,12 @@ export interface PluginDef<D extends Record<string, unknown> = {}> {
383
432
  columns?: Readonly<Record<string, unknown>>,
384
433
  options?: PluginTableOptions,
385
434
  ): PluginDef<D>;
435
+ /**
436
+ * Queue a Binding contribution (joins router + adopted on app.plug).
437
+ *
438
+ * @param binding - Trigger + flow
439
+ */
440
+ binding(binding: Binding): PluginDef<D>;
386
441
  /**
387
442
  * Queue a flow contribution.
388
443
  *
@@ -402,6 +457,16 @@ export interface PluginDef<D extends Record<string, unknown> = {}> {
402
457
  * @param schema - Standard Schema
403
458
  */
404
459
  config(schema: SchemaInput): PluginDef<D>;
460
+ /** Queue a vault secret/config contract. */
461
+ vault(secret: VaultSecretDecl): PluginDef<D>;
462
+ /** Queue a clock schedule. */
463
+ clock(decl: ClockDecl): PluginDef<D>;
464
+ /** Queue a signal declaration. */
465
+ signal(decl: SignalDecl): PluginDef<D>;
466
+ /** Queue a gate declaration. */
467
+ gate(decl: GateDecl): PluginDef<D>;
468
+ /** Queue a channel template. */
469
+ channelTemplate(decl: ChannelTemplateDecl): PluginDef<D>;
405
470
  }
406
471
 
407
472
  /** Extract accumulated decoration types from a {@link PluginDef}. */
@@ -505,6 +570,12 @@ export function plugin(name: string, options: PluginOptions): PluginDef {
505
570
  });
506
571
  return def;
507
572
  },
573
+ binding(binding) {
574
+ steps.push((api) => {
575
+ api.binding(binding);
576
+ });
577
+ return def;
578
+ },
508
579
  flow(flowDef) {
509
580
  steps.push((api) => {
510
581
  api.flow(flowDef);
@@ -523,6 +594,36 @@ export function plugin(name: string, options: PluginOptions): PluginDef {
523
594
  });
524
595
  return def;
525
596
  },
597
+ vault(secret) {
598
+ steps.push((api) => {
599
+ api.vault(secret);
600
+ });
601
+ return def;
602
+ },
603
+ clock(decl) {
604
+ steps.push((api) => {
605
+ api.clock(decl);
606
+ });
607
+ return def;
608
+ },
609
+ signal(decl) {
610
+ steps.push((api) => {
611
+ api.signal(decl);
612
+ });
613
+ return def;
614
+ },
615
+ gate(decl) {
616
+ steps.push((api) => {
617
+ api.gate(decl);
618
+ });
619
+ return def;
620
+ },
621
+ channelTemplate(decl) {
622
+ steps.push((api) => {
623
+ api.channelTemplate(decl);
624
+ });
625
+ return def;
626
+ },
526
627
  };
527
628
 
528
629
  return def;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `Redacted<T>` — a plain value wrapper that keeps a secret value out of
3
+ * logs, traces, and accidental serialization.
4
+ *
5
+ * Not an effect type: effect tracking only records which secret *names* a
6
+ * flow touches. This is value hygiene inside a flow body.
7
+ */
8
+
9
+ /** Placeholder used for any representation of a {@link Redacted}. */
10
+ export const REDACTED_PLACEHOLDER = "[redacted]";
11
+
12
+ /**
13
+ * Wrap a value so printing / logging / JSON serialization never yields the
14
+ * real value. The only way to read the wrapped value is {@link reveal}.
15
+ */
16
+ export class Redacted<T> {
17
+ readonly #value: T;
18
+
19
+ constructor(value: T) {
20
+ this.#value = value;
21
+ }
22
+
23
+ /** The one explicit way to get the real value. */
24
+ reveal(): T {
25
+ return this.#value;
26
+ }
27
+
28
+ toString(): string {
29
+ return REDACTED_PLACEHOLDER;
30
+ }
31
+
32
+ toJSON(): string {
33
+ return REDACTED_PLACEHOLDER;
34
+ }
35
+
36
+ [Symbol.toPrimitive](): string {
37
+ return REDACTED_PLACEHOLDER;
38
+ }
39
+
40
+ /** `util.inspect` / `console.log` — never the real value. */
41
+ [Symbol.for("nodejs.util.inspect.custom")](): string {
42
+ return REDACTED_PLACEHOLDER;
43
+ }
44
+ }
45
+
46
+ /** Type guard for {@link Redacted}. */
47
+ export function isRedacted(value: unknown): value is Redacted<unknown> {
48
+ return value instanceof Redacted;
49
+ }
50
+
51
+ /**
52
+ * Deep-walk a log/trace payload: replace every {@link Redacted} with the
53
+ * placeholder (never the wrapped value); leave everything else untouched.
54
+ *
55
+ * @param value - Arbitrary structured data
56
+ */
57
+ export function maskRedactedDeep<T>(value: T): T {
58
+ return maskRedactedValue(value) as T;
59
+ }
60
+
61
+ function maskRedactedValue(value: unknown): unknown {
62
+ if (isRedacted(value)) return REDACTED_PLACEHOLDER;
63
+ if (value === null || value === undefined) return value;
64
+ if (Array.isArray(value)) return value.map(maskRedactedValue);
65
+ if (typeof value === "object") {
66
+ const obj = value as Record<string, unknown>;
67
+ const out: Record<string, unknown> = {};
68
+ for (const [k, v] of Object.entries(obj)) {
69
+ out[k] = maskRedactedValue(v);
70
+ }
71
+ return out;
72
+ }
73
+ return value;
74
+ }
@@ -22,11 +22,11 @@ afterEach(() => {
22
22
  describe("registry isolation", () => {
23
23
  test("consume (default): App B does not serve App A routes", async () => {
24
24
  on(http.get("/a"), flow({ name: "app-a.ping", do: () => "a" }));
25
- const appA = oke({ name: "app-a" });
25
+ const appA = oke({ autoBoot: false, name: "app-a" });
26
26
 
27
27
  // No resetBindings() between the apps — the pre-fix leak scenario.
28
28
  on(http.get("/b"), flow({ name: "app-b.ping", do: () => "b" }));
29
- const appB = oke({ name: "app-b" });
29
+ const appB = oke({ autoBoot: false, name: "app-b" });
30
30
 
31
31
  // Pre-fix this was 200: App B adopted App A's leftover binding.
32
32
  expect((await appB.fetch(new Request("http://localhost/a"))).status).toBe(404);
@@ -46,10 +46,10 @@ describe("registry isolation", () => {
46
46
 
47
47
  test("keep: today's behavior — registry survives construction", async () => {
48
48
  on(http.get("/a"), flow({ name: "kept.ping", do: () => "a" }));
49
- oke({ name: "app-a", registry: "keep" });
49
+ oke({ autoBoot: false, name: "app-a", registry: "keep" });
50
50
  expect(listBindings()).toHaveLength(1);
51
51
 
52
- const appB = oke({ name: "app-b", registry: "keep" });
52
+ const appB = oke({ autoBoot: false, name: "app-b", registry: "keep" });
53
53
  expect((await appB.fetch(new Request("http://localhost/a"))).status).toBe(200);
54
54
  });
55
55
 
@@ -60,7 +60,7 @@ describe("registry isolation", () => {
60
60
  trigger: http.get("/b"),
61
61
  flow: flow({ name: "only-b.ping", do: () => "b" }),
62
62
  };
63
- const app = oke({ name: "app-b", registry: "ignore", bindings: [onlyB] });
63
+ const app = oke({ autoBoot: false, name: "app-b", registry: "ignore", bindings: [onlyB] });
64
64
 
65
65
  expect((await app.fetch(new Request("http://localhost/stray"))).status).toBe(404);
66
66