okengine 0.1.6 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +36 -29
  3. package/docs/spec/example.md +1187 -0
  4. package/docs/spec/unified-theory.md +1 -1
  5. package/package.json +20 -6
  6. package/src/cli/dev-app-runner.ts +6 -2
  7. package/src/cli/dev.ts +19 -10
  8. package/src/cli/doc-drift.ts +54 -21
  9. package/src/console/index.ts +25 -13
  10. package/src/console/server/app.ts +44 -7
  11. package/src/console/server/claim.test.ts +7 -4
  12. package/src/console/server/claim.ts +2 -7
  13. package/src/console/server/flows.ts +2 -6
  14. package/src/console/server/index.ts +2 -1
  15. package/src/console/server/lazy-panels.test.ts +27 -0
  16. package/src/console/server/panel-load.ts +28 -0
  17. package/src/console/server/plugin.ts +1 -1
  18. package/src/console/server/plugins.ts +7 -6
  19. package/src/console/server/public-flows.ts +12 -0
  20. package/src/console/server/state.ts +159 -122
  21. package/src/console/server/store.ts +13 -10
  22. package/src/console/ui/dist/assets/index-B71Yl_SS.js +10 -0
  23. package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
  24. package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
  25. package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
  26. package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
  27. package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
  28. package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
  29. package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
  30. package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
  31. package/src/console/ui/dist/assets/panel-overview-Bd48d9km.js +1 -0
  32. package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
  33. package/src/console/ui/dist/assets/panel-runs-BwsWqKeB.js +1 -0
  34. package/src/console/ui/dist/assets/panel-signals-9najbZY2.js +1 -0
  35. package/src/console/ui/dist/assets/panel-store-OHkP2pDp.js +1 -0
  36. package/src/console/ui/dist/assets/panel-traces-tn2JoY8U.js +1 -0
  37. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
  38. package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
  39. package/src/console/ui/dist/assets/style-Cnl7WLya.css +3 -0
  40. package/src/console/ui/dist/index.html +14 -0
  41. package/src/docs-origin.ts +19 -0
  42. package/src/drivers/index.ts +1 -6
  43. package/src/drivers/vault-sops.ts +20 -1
  44. package/src/kernel/app.ts +1 -1
  45. package/src/kernel/boot-bind/ai.ts +31 -0
  46. package/src/kernel/boot-bind/channel.ts +27 -0
  47. package/src/kernel/boot-bind/clock.ts +74 -0
  48. package/src/kernel/boot-bind/gate.ts +28 -0
  49. package/src/kernel/boot-bind/runs.ts +28 -0
  50. package/src/kernel/boot-bind/signal.ts +68 -0
  51. package/src/kernel/boot-bind/store.ts +47 -0
  52. package/src/kernel/boot-bind/vault.ts +36 -0
  53. package/src/kernel/boot.test.ts +85 -1
  54. package/src/kernel/boot.ts +250 -212
  55. package/src/kernel/errors.registry.test.ts +4 -2
  56. package/src/kernel/errors.ts +3 -1
  57. package/src/kernel/fx.test.ts +2 -2
  58. package/src/kernel/index.ts +2 -0
  59. package/src/mcp/data.ts +1 -0
  60. package/src/mcp/docs-index.ts +252 -0
  61. package/src/mcp/docs-mcp.test.ts +176 -0
  62. package/src/mcp/docs-server.ts +233 -0
  63. package/src/mcp/docs-tools.ts +143 -0
  64. package/src/mcp/index.ts +29 -5
  65. package/src/mcp/protocol.ts +2 -1
  66. package/src/release/exports.test.ts +71 -0
  67. package/src/release/exports.ts +156 -0
  68. package/src/release/index.ts +21 -0
  69. package/src/release/limits.ts +9 -0
  70. package/src/release/measure.exports.test.ts +82 -0
  71. package/src/release/measure.ts +297 -14
  72. package/src/release/publish.ts +14 -3
  73. package/src/release/readme.test.ts +61 -0
  74. package/src/release/readme.ts +13 -0
  75. package/src/runtime/index.ts +1 -0
  76. package/src/runtime/security.test.ts +6 -1
  77. package/src/runtime/types.ts +6 -0
  78. package/src/term.test.ts +51 -0
  79. package/src/term.ts +158 -0
  80. package/src/test/create-test-app.ts +2 -0
@@ -12,65 +12,48 @@
12
12
  * 8. Caps — mint per-flow capability tokens from the Manifest / effects
13
13
  * ```
14
14
  *
15
+ * Element runtimes are loaded via `new URL("./boot-bind/"+name+".ts", …)` so
16
+ * unused binders stay out of the `oke()` bundle (semantic tree-shaking).
17
+ * Vault still loads whenever secrets are declared and lists every gap in one
18
+ * failure; capability minting uses flow effect refs only — no element modules.
19
+ *
15
20
  * The scheduler reads the effective state from the Store after reconciliation,
16
21
  * never the code directly (console §5).
17
22
  */
18
23
 
19
- import {
20
- createAiRuntime,
21
- type AiRuntime,
22
- type CreateAiRuntimeOptions,
24
+ import type { ConfigEnv, OkeConfig } from "../config/index.ts";
25
+ import type {
26
+ AiRuntime,
27
+ CreateAiRuntimeOptions,
23
28
  } from "../elements/ai.ts";
24
- import {
25
- createChannelRuntime,
26
- type ChannelRuntime,
27
- type CreateChannelRuntimeOptions,
29
+ import type {
30
+ ChannelRuntime,
31
+ CreateChannelRuntimeOptions,
28
32
  } from "../elements/channel.ts";
29
- import {
30
- clock as declareClock,
31
- createClockRuntime,
32
- createTestClockRuntime,
33
- type ClockDecl,
34
- type ClockRuntime,
33
+ import type {
34
+ ClockDecl,
35
+ ClockRuntime,
35
36
  } from "../elements/clock.ts";
36
- import {
37
- createGateRuntime,
38
- type GateDecl,
39
- type GateRuntime,
37
+ import type {
38
+ GateDecl,
39
+ GateRuntime,
40
40
  } from "../elements/gate.ts";
41
- import {
42
- createSignalRuntime,
43
- type SignalDecl,
44
- type SignalRuntime,
41
+ import type {
42
+ SignalDecl,
43
+ SignalRuntime,
45
44
  } from "../elements/signal.ts";
46
- import {
47
- createStoreRuntime,
48
- type StoreDecl,
49
- type StoreRuntime,
45
+ import type {
46
+ StoreDecl,
47
+ StoreRuntime,
50
48
  } from "../elements/store.ts";
51
- import {
52
- createVaultRuntime,
53
- type CreateVaultRuntimeOptions,
54
- type VaultRuntime,
55
- type VaultSecretDecl,
49
+ import type {
50
+ CreateVaultRuntimeOptions,
51
+ VaultRuntime,
52
+ VaultSecretDecl,
56
53
  } from "../elements/vault.ts";
57
- import {
58
- memoryDrivers,
59
- memorySignalDriver,
60
- memoryVaultDriver,
61
- openConsoleChannel,
62
- mockAiDriver,
63
- } from "../drivers/index.ts";
64
- import {
65
- resolveDriverId,
66
- type ConfigEnv,
67
- type OkeConfig,
68
- } from "../config/index.ts";
69
- import {
70
- createRunsRuntime,
71
- memoryRunsDriver,
72
- type CreateRunsRuntimeOptions,
73
- type RunsRuntime,
54
+ import type {
55
+ CreateRunsRuntimeOptions,
56
+ RunsRuntime,
74
57
  } from "../runs/index.ts";
75
58
  import {
76
59
  createCapabilityToken,
@@ -171,6 +154,88 @@ export interface BootResult {
171
154
  close(): Promise<void>;
172
155
  }
173
156
 
157
+ /** Which element runtimes a boot must construct / import. */
158
+ export interface ElementNeeds {
159
+ readonly vault: boolean;
160
+ readonly store: boolean;
161
+ readonly signal: boolean;
162
+ readonly clock: boolean;
163
+ readonly gate: boolean;
164
+ readonly channel: boolean;
165
+ readonly ai: boolean;
166
+ readonly runs: boolean;
167
+ }
168
+
169
+ /**
170
+ * Decide which element runtimes are required from BootOptions alone —
171
+ * declaration arrays, pre-built runtimes, flow effects, and bindings.
172
+ * Does not import any element runtime module.
173
+ *
174
+ * @param options - Boot declarations
175
+ */
176
+ export function resolveElementNeeds(options: BootOptions): ElementNeeds {
177
+ const pre = options.elements ?? {};
178
+ let vault =
179
+ pre.vault !== undefined ||
180
+ (options.vault?.secrets ?? options.secrets ?? []).length > 0;
181
+ let store = pre.store !== undefined || (options.stores?.length ?? 0) > 0;
182
+ let signal = pre.signal !== undefined || (options.signals?.length ?? 0) > 0;
183
+ let clock = pre.clock !== undefined || (options.clocks?.length ?? 0) > 0;
184
+ let gate = pre.gate !== undefined || (options.gates?.length ?? 0) > 0;
185
+ let channel = pre.channel !== undefined || options.channel !== undefined;
186
+ let ai = pre.ai !== undefined || options.ai !== undefined;
187
+ let runs = pre.runs !== undefined || options.runs !== undefined;
188
+
189
+ const considerFlow = (f: AnyFlowDef): void => {
190
+ const e = f.effects;
191
+ if ((e?.reads?.length ?? 0) > 0 || (e?.writes?.length ?? 0) > 0) {
192
+ store = true;
193
+ }
194
+ if ((e?.emits?.length ?? 0) > 0) signal = true;
195
+ if ((e?.secrets?.length ?? 0) > 0) vault = true;
196
+ if ((e?.sends?.length ?? 0) > 0) channel = true;
197
+ if ((e?.asks?.length ?? 0) > 0) ai = true;
198
+ for (const t of f.triggers) {
199
+ if (t.kind === "every") clock = true;
200
+ if (t.kind === "signal") signal = true;
201
+ if (t.kind === "cdc") store = true;
202
+ if (t.kind === "http" && t.gates.length > 0) gate = true;
203
+ }
204
+ };
205
+
206
+ for (const f of options.flows ?? []) considerFlow(f);
207
+ for (const b of options.bindings ?? []) {
208
+ considerFlow(b.flow);
209
+ const t = b.trigger;
210
+ if (t.kind === "every") clock = true;
211
+ if (t.kind === "signal") signal = true;
212
+ if (t.kind === "cdc") store = true;
213
+ if (t.kind === "http" && t.gates.length > 0) gate = true;
214
+ }
215
+
216
+ // AI agents share the gate runtime for tool checks.
217
+ if (ai) gate = true;
218
+ if (
219
+ options.onSignal &&
220
+ (options.bindings ?? []).some((b) => b.trigger.kind === "signal")
221
+ ) {
222
+ signal = true;
223
+ }
224
+
225
+ return { vault, store, signal, clock, gate, channel, ai, runs };
226
+ }
227
+
228
+ /**
229
+ * Dynamically load a boot-bind module without pulling it into the bundler
230
+ * graph (expression `new URL` — Bun resolves at runtime).
231
+ *
232
+ * @param name - Binder file stem (`store`, `vault`, …)
233
+ */
234
+ async function loadBind<T>(name: string): Promise<T> {
235
+ const url = new URL(`./boot-bind/${name}.ts`, import.meta.url);
236
+ return import(url.href) as Promise<T>;
237
+ }
238
+
174
239
  /**
175
240
  * Run the ordered boot sequence. Fails fast — a missing secret throws
176
241
  * before store/signal/… open, so no request can be served.
@@ -183,204 +248,177 @@ export async function bootApplication(
183
248
  const env: ConfigEnv = options.env ?? "dev";
184
249
  const pre = options.elements ?? {};
185
250
  const now = options.now ?? (() => Date.now());
251
+ const needs = resolveElementNeeds(options);
186
252
 
187
- // 1. Vault boot resolve every declared secret; list all gaps at once.
188
- const vaultSecrets =
189
- options.vault?.secrets ?? options.secrets ?? [];
253
+ // 1. Vault — module loads only when secrets (or a pre-built vault) exist.
190
254
  let vault = pre.vault;
191
- if (!vault && vaultSecrets.length > 0) {
192
- vault = createVaultRuntime({
193
- secrets: vaultSecrets,
194
- chain: options.vault?.chain ?? [
195
- {
196
- driver: memoryVaultDriver,
197
- options: { secrets: {} },
198
- },
199
- ],
200
- allowDevFallbacks:
201
- options.vault?.allowDevFallbacks ?? env !== "prod",
202
- });
203
- }
204
- if (vault && !vault.booted) {
205
- await vault.boot();
255
+ if (needs.vault) {
256
+ if (!vault) {
257
+ const { bindVault } = await loadBind<
258
+ typeof import("./boot-bind/vault.ts")
259
+ >("vault");
260
+ vault = await bindVault(options, env);
261
+ } else if (!vault.booted) {
262
+ await vault.boot();
263
+ }
206
264
  }
207
265
 
208
- // 2. Bind store drivers per environment; open connections.
209
- let store = pre.store;
210
- if (!store) {
211
- const sqlId =
212
- resolveDriverId(options.config?.drivers?.store?.sql, env) ?? "memory";
213
- const kvId =
214
- resolveDriverId(options.config?.drivers?.store?.kv, env) ?? "memory";
215
- // Protocol ids memory bundle for test/dev when not otherwise injected.
216
- void sqlId;
217
- void kvId;
218
- store = createStoreRuntime({
219
- drivers: {
220
- sql: memoryDrivers.sql,
221
- kv: memoryDrivers.kv,
222
- files: memoryDrivers.files,
223
- index: memoryDrivers.index,
224
- },
225
- now,
226
- });
266
+ // Parallel-load remaining binders after Vault fail-fast.
267
+ const binderLoads: Promise<unknown>[] = [];
268
+ type StoreBind = typeof import("./boot-bind/store.ts");
269
+ type SignalBind = typeof import("./boot-bind/signal.ts");
270
+ type ClockBind = typeof import("./boot-bind/clock.ts");
271
+ type GateBind = typeof import("./boot-bind/gate.ts");
272
+ type ChannelBind = typeof import("./boot-bind/channel.ts");
273
+ type AiBind = typeof import("./boot-bind/ai.ts");
274
+ type RunsBind = typeof import("./boot-bind/runs.ts");
275
+
276
+ let storeBind: StoreBind | undefined;
277
+ let signalBind: SignalBind | undefined;
278
+ let clockBind: ClockBind | undefined;
279
+ let gateBind: GateBind | undefined;
280
+ let channelBind: ChannelBind | undefined;
281
+ let aiBind: AiBind | undefined;
282
+ let runsBind: RunsBind | undefined;
283
+
284
+ if (needs.store && !pre.store) {
285
+ binderLoads.push(
286
+ loadBind<StoreBind>("store").then((m) => {
287
+ storeBind = m;
288
+ }),
289
+ );
227
290
  }
228
- for (const decl of options.stores ?? []) {
229
- store.register?.(decl);
291
+ if (needs.signal && !pre.signal) {
292
+ binderLoads.push(
293
+ loadBind<SignalBind>("signal").then((m) => {
294
+ signalBind = m;
295
+ }),
296
+ );
230
297
  }
231
-
232
- // 3. Register signals; start consumers.
233
- let signal = pre.signal;
234
- if (!signal) {
235
- const signalId =
236
- resolveDriverId(options.config?.drivers?.signal, env) ?? "memory";
237
- void signalId;
238
- signal = createSignalRuntime({
239
- driver: memorySignalDriver,
240
- now,
241
- });
298
+ if (needs.clock) {
299
+ binderLoads.push(
300
+ loadBind<ClockBind>("clock").then((m) => {
301
+ clockBind = m;
302
+ }),
303
+ );
242
304
  }
243
- for (const decl of options.signals ?? []) {
244
- signal.register(decl);
305
+ if (needs.gate && !pre.gate) {
306
+ binderLoads.push(
307
+ loadBind<GateBind>("gate").then((m) => {
308
+ gateBind = m;
309
+ }),
310
+ );
245
311
  }
246
- // Also register signal-as-trigger names from bindings.
247
- for (const b of options.bindings ?? []) {
248
- if (b.trigger.kind === "signal") {
249
- if (!signal.declarations.has(b.trigger.name)) {
250
- // Minimal decl so the bus knows the name; optional to avoid orphan fail.
251
- signal.register({
252
- name: b.trigger.name,
253
- delivery: "once",
254
- retries: 3,
255
- deadLetter: true,
256
- optional: true,
257
- });
258
- }
259
- }
312
+ if (needs.channel && !pre.channel) {
313
+ binderLoads.push(
314
+ loadBind<ChannelBind>("channel").then((m) => {
315
+ channelBind = m;
316
+ }),
317
+ );
260
318
  }
261
- const bus = await signal.start();
262
-
263
- if (options.onSignal) {
264
- const handler = options.onSignal;
265
- const seen = new Set<string>();
266
- for (const b of options.bindings ?? []) {
267
- if (b.trigger.kind !== "signal") continue;
268
- const name = b.trigger.name;
269
- if (seen.has(name)) continue;
270
- seen.add(name);
271
- await bus.subscribe(name, `oke:${name}`, async (msg) => {
272
- await handler(name, msg.payload);
273
- });
274
- }
319
+ if (needs.ai && !pre.ai) {
320
+ binderLoads.push(
321
+ loadBind<AiBind>("ai").then((m) => {
322
+ aiBind = m;
323
+ }),
324
+ );
275
325
  }
276
-
277
- // 4. Reconcile clocks into the store; start scheduler with leader election.
278
- const clockDriver =
279
- resolveDriverId(options.config?.drivers?.clock, env) ??
280
- (env === "test" ? "frozen" : "memory");
281
- let clock = pre.clock;
282
- if (!clock) {
283
- clock =
284
- clockDriver === "frozen" || env === "test"
285
- ? createTestClockRuntime(now(), { instanceId: options.instanceId })
286
- : createClockRuntime({ instanceId: options.instanceId, now });
326
+ if (
327
+ needs.runs &&
328
+ !pre.runs &&
329
+ !(options.runs && isRunsRuntime(options.runs))
330
+ ) {
331
+ binderLoads.push(
332
+ loadBind<RunsBind>("runs").then((m) => {
333
+ runsBind = m;
334
+ }),
335
+ );
287
336
  }
337
+ await Promise.all(binderLoads);
288
338
 
289
- const clockDecls = new Map<string, ClockDecl>();
290
- for (const c of options.clocks ?? []) {
291
- clockDecls.set(c.name, c);
292
- }
293
- // every("1h") bindings → named clocks the scheduler can fire.
294
- for (const b of options.bindings ?? []) {
295
- if (b.trigger.kind === "every" && !clockDecls.has(b.trigger.interval)) {
296
- clockDecls.set(
297
- b.trigger.interval,
298
- declareClock(b.trigger.interval, { every: b.trigger.interval }),
299
- );
339
+ // 2. Store
340
+ let store = pre.store;
341
+ if (needs.store) {
342
+ if (!store) {
343
+ store = storeBind!.bindStore(options, env, now);
344
+ } else {
345
+ for (const decl of options.stores ?? []) {
346
+ store.register?.(decl);
347
+ }
300
348
  }
301
349
  }
302
- for (const decl of clockDecls.values()) {
303
- clock.register(decl);
304
- }
305
- await clock.reconcile();
306
350
 
307
- if (options.onCronFire) {
308
- const fire = options.onCronFire;
309
- for (const name of clockDecls.keys()) {
310
- clock.onCron(name, async () => {
311
- await fire(name);
312
- });
351
+ // 3. Signals
352
+ let signal = pre.signal;
353
+ if (needs.signal) {
354
+ if (!signal) {
355
+ signal = await signalBind!.bindSignal(options, env, now);
356
+ } else {
357
+ // Pre-built: still register decls / start if needed.
358
+ for (const decl of options.signals ?? []) {
359
+ signal.register(decl);
360
+ }
361
+ if (!signal.bus) {
362
+ await signal.start();
363
+ }
313
364
  }
314
365
  }
315
366
 
316
- // Default ON outside test — a declared every()/cron must fire with zero
317
- // further application calls (Clock element's core promise).
318
- const startScheduler =
319
- options.startScheduler ?? (env !== "test");
367
+ // 4. Clocks + optional scheduler
368
+ let clock = pre.clock;
320
369
  let schedulerTimer: ReturnType<typeof setInterval> | undefined;
321
- if (startScheduler) {
322
- const period = options.schedulerIntervalMs ?? 1000;
323
- schedulerTimer = setInterval(() => {
324
- void clock!.tick();
325
- }, period);
326
- // Don't keep the process alive solely for the scheduler in tests.
327
- schedulerTimer.unref?.();
370
+ if (needs.clock) {
371
+ const bound = await clockBind!.bindClock(options, env, now, clock);
372
+ clock = bound.clock;
373
+ const startScheduler = options.startScheduler ?? env !== "test";
374
+ if (startScheduler) {
375
+ const period = options.schedulerIntervalMs ?? 1000;
376
+ const clockRt = clock;
377
+ schedulerTimer = setInterval(() => {
378
+ void clockRt.tick();
379
+ }, period);
380
+ schedulerTimer.unref?.();
381
+ }
328
382
  }
329
383
 
330
- // Gate runtime (needs kv from store drivers) — used by the request
331
- // pipeline and AI agents. Constructed before channel/AI bind.
384
+ // Gate (before AI)
332
385
  let gate = pre.gate;
333
- if (!gate) {
334
- const kvNs = await memoryDrivers.kv.open({ name: "oke:gates" });
335
- gate = createGateRuntime({
336
- gates: options.gates ?? [],
337
- kv: kvNs,
338
- now,
339
- });
386
+ if (needs.gate && !gate) {
387
+ gate = await gateBind!.bindGate(options, now);
340
388
  }
341
389
 
342
- // 5. Bind channel runtime.
390
+ // 5. Channel
343
391
  let channel = pre.channel;
344
- if (!channel) {
345
- channel = createChannelRuntime({
346
- ...(options.channel ?? {}),
347
- drivers: options.channel?.drivers ?? [openConsoleChannel()],
348
- now,
349
- });
392
+ if (needs.channel && !channel) {
393
+ channel = channelBind!.bindChannel(options, now);
350
394
  }
351
395
 
352
- // 6. Bind AI runtime (shares the gate runtime for agent tool checks).
396
+ // 6. AI
353
397
  let ai = pre.ai;
354
- if (!ai) {
355
- ai = createAiRuntime({
356
- ...(options.ai ?? {}),
357
- defaultDriver: options.ai?.defaultDriver ?? mockAiDriver,
358
- gates: options.ai?.gates ?? gate,
359
- now,
360
- });
398
+ if (needs.ai && !ai) {
399
+ ai = aiBind!.bindAi(options, gate, now);
361
400
  }
362
401
 
363
- // 7. Open the runs store.
402
+ // 7. Runs
364
403
  let runs = pre.runs;
365
- if (!runs) {
366
- if (options.runs && isRunsRuntime(options.runs)) {
367
- runs = options.runs;
368
- } else {
369
- const runsOpts =
370
- options.runs && !isRunsRuntime(options.runs)
371
- ? options.runs
372
- : undefined;
373
- runs = createRunsRuntime({
374
- driver: runsOpts?.driver ?? memoryRunsDriver,
375
- ...(runsOpts ?? {}),
376
- });
404
+ if (needs.runs) {
405
+ if (!runs) {
406
+ if (options.runs && isRunsRuntime(options.runs)) {
407
+ runs = options.runs;
408
+ if (!runs.store) await runs.open();
409
+ } else {
410
+ const runsOpts =
411
+ options.runs && !isRunsRuntime(options.runs)
412
+ ? options.runs
413
+ : undefined;
414
+ runs = await runsBind!.bindRuns(runsOpts);
415
+ }
416
+ } else if (!runs.store) {
417
+ await runs.open();
377
418
  }
378
419
  }
379
- if (runs && !runs.store) {
380
- await runs.open();
381
- }
382
420
 
383
- // 8. Mint per-flow capability tokens from declared effects (Manifest).
421
+ // 8. Caps effect refs only; no element modules.
384
422
  const capabilities = mintCapabilities(options.flows ?? []);
385
423
 
386
424
  return {
@@ -27,10 +27,12 @@ describe("OKE error-code registry", () => {
27
27
  }
28
28
  });
29
29
 
30
- test("OkeError docsUrl matches https://oke.dev/e/{code}", () => {
30
+ test("OkeError docsUrl matches docs origin /e/{code}", () => {
31
31
  for (const def of Object.values(OKE_ERRORS) as OkeErrorDefinition[]) {
32
32
  const err = new OkeError(def);
33
- expect(err.docsUrl).toBe(`https://oke.dev/e/${def.code}`);
33
+ expect(err.docsUrl).toBe(
34
+ `https://okengine.vercel.app/e/${def.code}`,
35
+ );
34
36
  expect(err.message).toContain(err.docsUrl);
35
37
  expect(err.message).toContain(`OKE${def.code}`);
36
38
  expect(err.message).toContain("→");
@@ -6,6 +6,8 @@
6
6
  * permanent from the moment they enter this registry.
7
7
  */
8
8
 
9
+ import { docsUrl as absoluteDocsUrl } from "../docs-origin.ts";
10
+
9
11
  /** Numeric OKE error code (permanent once published). */
10
12
  export type OkeErrorCode = number;
11
13
 
@@ -45,7 +47,7 @@ export class OkeError extends Error {
45
47
  constructor(definition: OkeErrorDefinition, params: OkeErrorParams = {}) {
46
48
  const causeText = interpolate(definition.cause, params);
47
49
  const fix = interpolate(definition.fix, params);
48
- const docsUrl = `https://oke.dev/e/${definition.code}`;
50
+ const docsUrl = absoluteDocsUrl(`/e/${definition.code}`);
49
51
  const message = formatOkeMessage(definition.code, causeText, fix, docsUrl);
50
52
  super(message);
51
53
  this.name = "OkeError";
@@ -43,7 +43,7 @@ describe("fx — capability enforcement", () => {
43
43
  expect(oke.fix).toBe(
44
44
  'Add "sql:users" to this flow\'s effects.reads.',
45
45
  );
46
- expect(oke.docsUrl).toBe("https://oke.dev/e/1001");
46
+ expect(oke.docsUrl).toBe("https://okengine.vercel.app/e/1001");
47
47
  expect(oke.message).toBe(
48
48
  formatOkeMessage(1001, oke.causeText, oke.fix, oke.docsUrl),
49
49
  );
@@ -218,7 +218,7 @@ describe("errors — registry", () => {
218
218
  });
219
219
  expect(err.message).toContain("OKE1042");
220
220
  expect(err.message).toContain("no subscriber");
221
- expect(err.message).toContain("https://oke.dev/e/1042");
221
+ expect(err.message).toContain("https://okengine.vercel.app/e/1042");
222
222
  expect(err.message).toContain("→");
223
223
  });
224
224
 
@@ -14,8 +14,10 @@ export {
14
14
  export {
15
15
  bootApplication,
16
16
  mintCapabilities,
17
+ resolveElementNeeds,
17
18
  type BootOptions,
18
19
  type BootResult,
20
+ type ElementNeeds,
19
21
  type ElementRuntimes,
20
22
  } from "./boot.ts";
21
23
 
package/src/mcp/data.ts CHANGED
@@ -19,6 +19,7 @@ export type McpDataProvenance =
19
19
  | "store-record"
20
20
  | "action-result"
21
21
  | "catalog"
22
+ | "docs"
22
23
  | "error";
23
24
 
24
25
  /**