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
@@ -416,7 +416,7 @@ Plus `oke import express|hono|elysia` codemods for the mechanical parts. Migrati
416
416
  |---|---|
417
417
  | Kernel (edge profile) | < 15 kB |
418
418
  | Client runtime | < 3 kB |
419
- | Cold start on Bun | < 75 ms |
419
+ | Cold start on Bun | < 75 ms in CI (GitHub Actions runners are less predictable than a dev machine); measured ~25–30 ms on real hardware |
420
420
  | p99 routing overhead | < 1 ms |
421
421
 
422
422
  A regression fails OKE's own build. Claims we cannot measure, we do not make.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okengine",
3
- "version": "0.1.6",
3
+ "version": "0.2.2",
4
4
  "description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,7 +10,9 @@
10
10
  "url": "git+https://github.com/omqkhafi/okengine.git"
11
11
  },
12
12
  "workspaces": [
13
- "examples/*"
13
+ "packages/*",
14
+ "examples/*",
15
+ "site"
14
16
  ],
15
17
  "scripts": {
16
18
  "typecheck": "tsc --noEmit",
@@ -33,6 +35,7 @@
33
35
  "test:mcp": "bun test src/mcp",
34
36
  "test:console:smoke": "bunx playwright test tests/console/smoke.spec.ts",
35
37
  "build:console": "bunx vite build --config src/console/ui/vite.config.ts",
38
+ "prepack": "bun run build:console",
36
39
  "bench:cold-start": "bun test ./src/runtime/cold-start.bench.ts",
37
40
  "bench:client": "bun test src/client/budget.test.ts",
38
41
  "bench:console": "bun test src/console/budget.test.ts",
@@ -44,10 +47,13 @@
44
47
  "gate:errors": "bun test src/kernel/errors.registry.test.ts",
45
48
  "gate:codemods": "bun test src/upgrade/codemods.test.ts",
46
49
  "test:examples": "bun test examples/notes && bun test examples/linkly && bun test examples/provisions && bun test examples/skyport",
47
- "test:create-oke": "bun run --cwd packages/create-oke typecheck && bun test packages/create-oke/src && CREATE_OKE_INTEGRATION=1 bun test packages/create-oke/tests/scaffold.integration.test.ts packages/create-oke/tests/dev.integration.test.ts",
50
+ "test:create-oke": "bun run --cwd packages/create-oke typecheck && bun test packages/create-oke/src && CREATE_OKE_INTEGRATION=1 bun test --timeout 180000 packages/create-oke/tests/scaffold.integration.test.ts packages/create-oke/tests/dev.integration.test.ts",
48
51
  "test:doc-drift": "bun src/cli/doc-drift.ts",
52
+ "site:sync": "bun run --cwd site sync",
53
+ "site:dev": "bun run --cwd site dev",
54
+ "site:build": "bun run --cwd site build",
49
55
  "test:skyport-docker": "bun test examples/skyport/tests/docker.test.ts",
50
- "ci": "bun run bench:cold-start && bun run typecheck && bun run test && bun run budgets && bun run gate:doctor-diff && bun run gate:errors && bun run gate:codemods && bun run test:doc-drift && bun run test:examples && bun run test:skyport-docker && bun run test:create-oke",
56
+ "ci": "bun run bench:cold-start && bun run typecheck && bun run test && bun run budgets && bun run gate:doctor-diff && bun run gate:errors && bun run gate:codemods && bun run test:doc-drift && bun run site:build && bun run test:examples && bun run test:skyport-docker && bun run test:create-oke",
51
57
  "gate:publish": "bun test scripts/publish.gate.test.ts",
52
58
  "bump": "bun run scripts/bump-version.ts",
53
59
  "publish:all": "bun run scripts/publish.ts",
@@ -60,13 +66,13 @@
60
66
  },
61
67
  "dependencies": {
62
68
  "@duckdb/node-api": "^1.5.5-r.1",
63
- "age-encryption": "^0.3.0",
64
69
  "ajv": "^8.20.0",
65
70
  "ajv-formats": "^3.0.1",
66
71
  "oxc-parser": "^0.141.0",
67
72
  "sently": "^0.8.0"
68
73
  },
69
74
  "devDependencies": {
75
+ "age-encryption": "^0.3.0",
70
76
  "@base-ui/react": "^1.6.0",
71
77
  "@codemirror/commands": "^6.10.4",
72
78
  "@codemirror/lang-json": "^6.0.2",
@@ -103,11 +109,16 @@
103
109
  "zod": "^4.4.3"
104
110
  },
105
111
  "peerDependencies": {
112
+ "age-encryption": "^0.3.0",
106
113
  "drizzle-kit": ">=1.0.0-rc.0",
107
114
  "drizzle-orm": ">=1.0.0-rc.0",
108
- "drizzle-seed": ">=0.3.0"
115
+ "drizzle-seed": ">=0.3.0",
116
+ "zod": ">=3.23.0"
109
117
  },
110
118
  "peerDependenciesMeta": {
119
+ "age-encryption": {
120
+ "optional": true
121
+ },
111
122
  "drizzle-kit": {
112
123
  "optional": true
113
124
  },
@@ -116,6 +127,9 @@
116
127
  },
117
128
  "drizzle-seed": {
118
129
  "optional": true
130
+ },
131
+ "zod": {
132
+ "optional": true
119
133
  }
120
134
  },
121
135
  "engines": {
@@ -17,6 +17,7 @@ import { resolve } from "node:path";
17
17
  import { pathToFileURL } from "node:url";
18
18
  import { createBunRuntime } from "../runtime/bun.ts";
19
19
  import { APP_PORT, type FetchApp } from "../runtime/types.ts";
20
+ import { formatAppReadyLine } from "../term.ts";
20
21
 
21
22
  /** Stable Bun.serve id so `--hot` reuses the socket across soft reloads. */
22
23
  export const DEV_APP_SERVE_ID = "oke-dev-app";
@@ -59,8 +60,11 @@ const handle = createBunRuntime().serve(mod.app, {
59
60
  id: DEV_APP_SERVE_ID,
60
61
  });
61
62
 
62
- const line = `oke app http://${formatHostForUrl(hostname)}:${handle.port}\n`;
63
- process.stdout.write(line);
63
+ process.stdout.write(
64
+ formatAppReadyLine(
65
+ `http://${formatHostForUrl(hostname)}:${handle.port}`,
66
+ ),
67
+ );
64
68
 
65
69
  if (readyPath !== undefined && readyPath.length > 0) {
66
70
  await Bun.write(readyPath, `${handle.port}\n`);
package/src/cli/dev.ts CHANGED
@@ -23,6 +23,11 @@ import {
23
23
  CONSOLE_PORT,
24
24
  MCP_PORT,
25
25
  } from "../runtime/types.ts";
26
+ import {
27
+ formatDevBanner,
28
+ formatServiceLine,
29
+ formatStatusLine,
30
+ } from "../term.ts";
26
31
  import { clientAdd } from "./client-add.ts";
27
32
  import { loadManifest, loadOkeConfig, resolveImages } from "./load-config.ts";
28
33
  import { mcpContextFromConsole } from "./mcp-from-console.ts";
@@ -258,7 +263,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
258
263
  }
259
264
  });
260
265
  await up(composeFiles, cwd);
261
- write(`oke dev: stack up (${stackRoles.join(", ")})\n`);
266
+ write(formatStatusLine(`stack up (${stackRoles.join(", ")})`));
262
267
  }
263
268
  }
264
269
 
@@ -272,10 +277,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
272
277
  stackEnv,
273
278
  };
274
279
 
275
- write(
276
- `oke dev: app :${appPort} · Console :${consolePort} · MCP :${mcpPort}\n`,
277
- );
278
- write("oke dev: watching — client types regenerate on save\n");
280
+ write(formatDevBanner());
279
281
 
280
282
  if (options.dryRun) return { code: 0, plan };
281
283
 
@@ -312,7 +314,12 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
312
314
  ? { manifest: seedManifest }
313
315
  : {}),
314
316
  });
315
- write(`oke Console http://127.0.0.1:${server.port}\n`);
317
+ write(
318
+ formatServiceLine(
319
+ "Console",
320
+ `http://127.0.0.1:${server.port}`,
321
+ ),
322
+ );
316
323
  return {
317
324
  console: server.console,
318
325
  port: server.port,
@@ -334,7 +341,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
334
341
  context: mcpOptions.context,
335
342
  now: mcpOptions.now,
336
343
  });
337
- write(`oke MCP http://127.0.0.1:${server.port}\n`);
344
+ write(formatServiceLine("MCP", `http://127.0.0.1:${server.port}`));
338
345
  return {
339
346
  port: server.port,
340
347
  url: server.url,
@@ -352,7 +359,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
352
359
  url: appUrl,
353
360
  out: resolve(cwd, "oke-client.d.ts"),
354
361
  });
355
- write("oke dev: regenerated oke-client.d.ts\n");
362
+ write(formatStatusLine("regenerated oke-client.d.ts"));
356
363
  } catch {
357
364
  // App may not be ready yet — ignore until next save.
358
365
  }
@@ -500,7 +507,9 @@ async function startAppHot(
500
507
  if (stopped) return;
501
508
  stopped = true;
502
509
  try {
503
- proc.kill();
510
+ // SIGKILL so `bun --hot` cannot linger and hold the project directory
511
+ // open (create-oke afterEach `rmSync` otherwise races the child exit).
512
+ proc.kill("SIGKILL");
504
513
  } catch {
505
514
  // already exited
506
515
  }
@@ -513,7 +522,7 @@ async function startAppHot(
513
522
  proc,
514
523
  APP_READY_TIMEOUT_MS,
515
524
  );
516
- // Child prints `oke app http://…` (again on each soft reload).
525
+ // Child prints the App ready line (again on each soft reload).
517
526
  const url = new URL(`http://${hostname}:${boundPort}/`);
518
527
  return { stop, port: boundPort, url };
519
528
  } catch (err) {
@@ -14,6 +14,10 @@
14
14
  * Usage:
15
15
  * bun src/cli/doc-drift.ts
16
16
  * bun src/cli/doc-drift.ts docs/spec/four-applications.md README.md
17
+ * bun src/cli/doc-drift.ts site/content/docs/learn/notes.md
18
+ *
19
+ * Default paths: four-applications.md, README.md, and every markdown/MDX file
20
+ * under site/content/docs (claimed fences only — prose-only pages skip).
17
21
  */
18
22
 
19
23
  import { resolve } from "node:path";
@@ -21,8 +25,27 @@ import { Window } from "happy-dom";
21
25
 
22
26
  const ROOT = resolve(import.meta.dir, "../..");
23
27
 
24
- /** Default docs checked when no CLI paths are passed. */
25
- const DEFAULT_DOCS: readonly string[] = [
28
+ /**
29
+ * Discover site content markdown that may contain claimed fences.
30
+ *
31
+ * @param root - Repo root
32
+ */
33
+ async function discoverSiteDocs(root: string): Promise<string[]> {
34
+ const docsDir = resolve(root, "site/content/docs");
35
+ const out: string[] = [];
36
+ try {
37
+ const glob = new Bun.Glob("**/*.{md,mdx}");
38
+ for await (const rel of glob.scan({ cwd: docsDir, onlyFiles: true })) {
39
+ out.push(resolve(docsDir, rel));
40
+ }
41
+ } catch {
42
+ // site/ not present yet — skip
43
+ }
44
+ return out.sort();
45
+ }
46
+
47
+ /** Core docs that must contain claimed fences. */
48
+ const REQUIRED_DOCS: readonly string[] = [
26
49
  resolve(ROOT, "docs/spec/four-applications.md"),
27
50
  resolve(ROOT, "README.md"),
28
51
  ];
@@ -315,6 +338,9 @@ export async function checkDocDrift(
315
338
  /**
316
339
  * Run doc-drift over one or more markdown files.
317
340
  *
341
+ * Site content under `site/` may have zero claimed fences (prose-only pages);
342
+ * only `docs/spec/four-applications.md` and `README.md` require at least one.
343
+ *
318
344
  * @param paths - Absolute or repo-relative markdown paths
319
345
  */
320
346
  export async function runDocDrift(
@@ -324,32 +350,36 @@ export async function runDocDrift(
324
350
  let ok = true;
325
351
  let total = 0;
326
352
  let mermaidTotal = 0;
353
+ let checkedFiles = 0;
327
354
 
328
355
  for (const path of paths) {
329
356
  const abs = resolve(ROOT, path);
330
357
  const label = abs.startsWith(ROOT + "/")
331
358
  ? abs.slice(ROOT.length + 1)
332
359
  : abs;
360
+ const requireClaimed = !label.startsWith("site/");
333
361
  const markdown = await Bun.file(abs).text();
334
362
  const fences = parseClaimedFences(markdown);
335
363
  if (fences.length === 0) {
336
- messages.push(`doc-drift: no claimed fences found in ${label}`);
337
- ok = false;
338
- continue;
339
- }
340
-
341
- const result = await checkDocDrift(fences);
342
- total += fences.length;
343
- if (!result.ok) {
344
- ok = false;
345
- messages.push(
346
- `doc-drift: ${result.failures.length} failure(s) in ${label}`,
347
- );
348
- for (const f of result.failures) messages.push(` · ${f}`);
364
+ if (requireClaimed) {
365
+ messages.push(`doc-drift: no claimed fences found in ${label}`);
366
+ ok = false;
367
+ }
349
368
  } else {
350
- messages.push(
351
- `doc-drift: ok ${fences.length} claimed fence(s) in ${label}`,
352
- );
369
+ checkedFiles++;
370
+ const result = await checkDocDrift(fences);
371
+ total += fences.length;
372
+ if (!result.ok) {
373
+ ok = false;
374
+ messages.push(
375
+ `doc-drift: ${result.failures.length} failure(s) in ${label}`,
376
+ );
377
+ for (const f of result.failures) messages.push(` · ${f}`);
378
+ } else {
379
+ messages.push(
380
+ `doc-drift: ok — ${fences.length} claimed fence(s) in ${label}`,
381
+ );
382
+ }
353
383
  }
354
384
 
355
385
  const mermaidFences = parseMermaidFences(markdown);
@@ -370,11 +400,11 @@ export async function runDocDrift(
370
400
  }
371
401
  }
372
402
 
373
- if (ok && paths.length > 1) {
403
+ if (ok && checkedFiles > 1) {
374
404
  const mermaidNote =
375
405
  mermaidTotal > 0 ? ` · ${mermaidTotal} mermaid fence(s)` : "";
376
406
  messages.push(
377
- `doc-drift: ok — ${total} claimed fence(s) across ${paths.length} file(s)${mermaidNote}`,
407
+ `doc-drift: ok — ${total} claimed fence(s) across ${checkedFiles} file(s)${mermaidNote}`,
378
408
  );
379
409
  }
380
410
 
@@ -383,7 +413,10 @@ export async function runDocDrift(
383
413
 
384
414
  async function main(): Promise<number> {
385
415
  const args = process.argv.slice(2);
386
- const paths = args.length > 0 ? args : DEFAULT_DOCS;
416
+ const paths =
417
+ args.length > 0
418
+ ? args
419
+ : [...REQUIRED_DOCS, ...(await discoverSiteDocs(ROOT))];
387
420
  const { ok, messages } = await runDocDrift(paths);
388
421
  for (const m of messages) {
389
422
  if (ok) console.log(m);
@@ -3,29 +3,41 @@
3
3
  *
4
4
  * Built on `createClient<ConsoleApp>`. Every action is a real flow through `fx`;
5
5
  * the audit log is the trace (docs/spec/console.md).
6
+ *
7
+ * Imports leaf modules (not `server/index`) so unused panel projectors stay out
8
+ * of the `okengine/console` graph when tree-shaken.
9
+ *
6
10
  * @module
7
11
  */
8
12
 
9
13
  export {
10
14
  bootConsoleApp,
15
+ createConsoleApp,
16
+ type ConsoleApp,
17
+ type ConsoleAppHandle,
18
+ type CreateConsoleAppOptions,
19
+ } from "./server/app.ts";
20
+ export {
11
21
  CLAIM_TTL_MS,
22
+ mintClaimCode,
23
+ printClaimCodeOnce,
24
+ verifyClaimCode,
25
+ } from "./server/claim.ts";
26
+ export { feedManifest } from "./server/live.ts";
27
+ export { consolePlugin } from "./server/plugin.ts";
28
+ export {
12
29
  CONSOLE_COOKIES,
13
30
  CONSOLE_CSP,
14
31
  PLUGIN_IFRAME_SANDBOX,
15
- consolePlugin,
16
- createConsoleApp,
17
- createConsoleState,
18
- feedManifest,
19
- mintClaimCode,
20
- printClaimCodeOnce,
32
+ withConsoleSecurityHeaders,
33
+ } from "./server/security-headers.ts";
34
+ export {
21
35
  serveConsole,
22
36
  startConsoleApp,
23
- verifyClaimCode,
24
- withConsoleSecurityHeaders,
25
- type ConsoleApp,
26
- type ConsoleAppHandle,
27
37
  type ConsoleServerHandle,
28
- type ConsoleState,
29
- type CreateConsoleAppOptions,
30
38
  type ServeConsoleOptions,
31
- } from "./server/index.ts";
39
+ } from "./server/serve.ts";
40
+ export {
41
+ createConsoleState,
42
+ type ConsoleState,
43
+ } from "./server/state.ts";
@@ -140,16 +140,53 @@ export async function bootConsoleApp(
140
140
  if (!runs) return [];
141
141
  return runs.all();
142
142
  };
143
- await bindManifestSignalBus(handle.state);
144
- await bindManifestStoreRuntime(handle.state);
145
- await bindManifestVaultRuntime(handle.state);
146
- await bindManifestGateRuntime(handle.state);
147
- await bindManifestClockRuntime(handle.state);
148
- bindManifestAiRuntime(handle.state);
149
- bindManifestChannelRuntime(handle.state);
143
+ // Element runtimes bind lazily on first panel access (see ensure* below
144
+ // via list* methods / bindManifest* callers) — not all seventeen at boot.
150
145
  return handle.app;
151
146
  }
152
147
 
148
+ /**
149
+ * Ensure Manifest-backed element runtimes for a panel are bound (idempotent).
150
+ *
151
+ * @param state - Console state
152
+ * @param panel - Panel id that was visited
153
+ */
154
+ export async function ensureConsolePanelRuntimes(
155
+ state: ConsoleState,
156
+ panel:
157
+ | "signals"
158
+ | "store"
159
+ | "vault"
160
+ | "gates"
161
+ | "clock"
162
+ | "ai"
163
+ | "channels",
164
+ ): Promise<void> {
165
+ switch (panel) {
166
+ case "signals":
167
+ await bindManifestSignalBus(state);
168
+ break;
169
+ case "store":
170
+ await bindManifestStoreRuntime(state);
171
+ break;
172
+ case "vault":
173
+ await bindManifestVaultRuntime(state);
174
+ break;
175
+ case "gates":
176
+ await bindManifestGateRuntime(state);
177
+ break;
178
+ case "clock":
179
+ await bindManifestClockRuntime(state);
180
+ break;
181
+ case "ai":
182
+ bindManifestAiRuntime(state);
183
+ break;
184
+ case "channels":
185
+ bindManifestChannelRuntime(state);
186
+ break;
187
+ }
188
+ }
189
+
153
190
  /**
154
191
  * Bind a ChannelRuntime + console inbox from the Manifest when no host
155
192
  * runtime is attached — Console surfaces real receipts / inbox, not mocks.
@@ -52,10 +52,13 @@ describe("claim code", () => {
52
52
 
53
53
  test("prints to boot log exactly once", () => {
54
54
  const state = mintClaimCode(() => 1);
55
- const lines: string[] = [];
56
- printClaimCodeOnce(state, (l) => lines.push(l));
57
- printClaimCodeOnce(state, (l) => lines.push(l));
58
- expect(lines.filter((l) => l.includes(state.code)).length).toBe(1);
55
+ const chunks: string[] = [];
56
+ printClaimCodeOnce(state, (l) => chunks.push(l));
57
+ printClaimCodeOnce(state, (l) => chunks.push(l));
58
+ const joined = chunks.join("");
59
+ expect(joined).toContain(state.code);
60
+ expect(joined).toContain("Claim code");
61
+ expect(chunks.length).toBe(1);
59
62
  });
60
63
 
61
64
  test("constantTimeEqual is length-safe", () => {
@@ -5,6 +5,7 @@
5
5
  * and rate-limited (console §2.5 · §10.4).
6
6
  */
7
7
 
8
+ import { formatClaimNote } from "../../term.ts";
8
9
  import {
9
10
  AUTH_RATE_LIMIT,
10
11
  AUTH_RATE_WINDOW_MS,
@@ -67,13 +68,7 @@ export function printClaimCodeOnce(
67
68
  ): void {
68
69
  if (state.printed) return;
69
70
  state.printed = true;
70
- write("");
71
- write("┌─────────────────────────────────────────────────────────┐");
72
- write("│ oke Console — first-admin claim code (expires 30 min) │");
73
- write(`│ ${state.code} │`);
74
- write("│ Whoever can read this log already owns the server. │");
75
- write("└─────────────────────────────────────────────────────────┘");
76
- write("");
71
+ write(formatClaimNote(state.code));
77
72
  }
78
73
 
79
74
  /** Result of {@link verifyClaimCode}. */
@@ -30,14 +30,10 @@ import {
30
30
  } from "./clock.ts";
31
31
  import { createFileDiff, emitStructuralDiff } from "./structural.ts";
32
32
  import type { ConsoleState } from "./state.ts";
33
+ import { PUBLIC_CONSOLE_FLOWS } from "./public-flows.ts";
33
34
  import { tenancyDeclared } from "./store.ts";
34
35
 
35
- /** Flows that may run without an operator session. */
36
- export const PUBLIC_CONSOLE_FLOWS = new Set([
37
- "console.setup.status",
38
- "console.setup.claim",
39
- "console.session.login",
40
- ]);
36
+ export { PUBLIC_CONSOLE_FLOWS };
41
37
 
42
38
  const SetupStatusOut = z.object({
43
39
  setupClosed: z.boolean(),
@@ -30,6 +30,7 @@ export {
30
30
  bindManifestVaultRuntime,
31
31
  bootConsoleApp,
32
32
  createConsoleApp,
33
+ ensureConsolePanelRuntimes,
33
34
  type ConsoleApp,
34
35
  type ConsoleAppHandle,
35
36
  type CreateConsoleAppOptions,
@@ -72,8 +73,8 @@ export {
72
73
  } from "./claim.ts";
73
74
  export {
74
75
  createConsoleBindings,
75
- PUBLIC_CONSOLE_FLOWS,
76
76
  } from "./flows.ts";
77
+ export { PUBLIC_CONSOLE_FLOWS } from "./public-flows.ts";
77
78
  export {
78
79
  createLiveWebsocket,
79
80
  feedManifest,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Console per-panel backends construct lazily on first access.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { createConsoleState } from "./state.ts";
7
+
8
+ describe("console — lazy panel construction", () => {
9
+ test("createConsoleState constructs no panel backends", () => {
10
+ const state = createConsoleState({ silentClaim: true, secret: "x" });
11
+ expect([...state.constructedPanels]).toEqual([]);
12
+ });
13
+
14
+ test("only the visited panel is constructed", async () => {
15
+ const state = createConsoleState({ silentClaim: true, secret: "x" });
16
+ expect([...state.constructedPanels]).toEqual([]);
17
+
18
+ await state.listVault();
19
+ expect([...state.constructedPanels]).toEqual(["vault"]);
20
+
21
+ await state.listStores();
22
+ expect([...state.constructedPanels].sort()).toEqual(["store", "vault"]);
23
+
24
+ await state.listVault();
25
+ expect([...state.constructedPanels].sort()).toEqual(["store", "vault"]);
26
+ });
27
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Lazy Console panel module loader — expression `new URL` keeps panel
3
+ * backends out of the createConsoleState / createConsoleApp graph until
4
+ * first access (mirrors kernel boot-bind).
5
+ */
6
+
7
+ /** Panel ids that own server-side projection / mutation modules. */
8
+ export type ConsolePanelId =
9
+ | "access"
10
+ | "gates"
11
+ | "signals"
12
+ | "store"
13
+ | "clock"
14
+ | "vault"
15
+ | "ai"
16
+ | "channels"
17
+ | "plugins"
18
+ | "diff";
19
+
20
+ /**
21
+ * Load a Console panel module by id without bundling it into the parent.
22
+ *
23
+ * @param id - Panel module stem (`store`, `vault`, …)
24
+ */
25
+ export async function loadConsolePanel<T>(id: ConsolePanelId): Promise<T> {
26
+ const url = new URL(`./${id}.ts`, import.meta.url);
27
+ return import(url.href) as Promise<T>;
28
+ }
@@ -10,7 +10,7 @@ import {
10
10
  type Fx,
11
11
  type PluginDef,
12
12
  } from "../../kernel/index.ts";
13
- import { PUBLIC_CONSOLE_FLOWS } from "./flows.ts";
13
+ import { PUBLIC_CONSOLE_FLOWS } from "./public-flows.ts";
14
14
 
15
15
  /**
16
16
  * Builtin Console plugin.
@@ -6,7 +6,6 @@
6
6
  * Never installs — hands a `bun add …` command for community packages.
7
7
  */
8
8
 
9
- import { runDoctorDiff } from "../../cli/doctor-diff.ts";
10
9
  import {
11
10
  allHookCostSummaries,
12
11
  type HookCostSummary,
@@ -23,11 +22,9 @@ import {
23
22
  isCorePluginOn,
24
23
  type PluginConfigProbe,
25
24
  } from "../../plugins/catalogue.ts";
26
- import {
27
- projectSupplyChain,
28
- projectSupplyChainSync,
29
- type PackageJsonProbe,
30
- type SupplyChainSignals,
25
+ import type {
26
+ PackageJsonProbe,
27
+ SupplyChainSignals,
31
28
  } from "../../plugins/supply-chain.ts";
32
29
  import type { ScanSourceFile } from "../../plugins/node-import-scan.ts";
33
30
 
@@ -308,6 +305,9 @@ async function buildRow(input: {
308
305
  fetchNpm: input.options.fetchNpm,
309
306
  };
310
307
 
308
+ const { projectSupplyChain, projectSupplyChainSync } = await import(
309
+ "../../plugins/supply-chain.ts"
310
+ );
311
311
  const supplyChain = input.options.syncSupplyChain
312
312
  ? projectSupplyChainSync(supplyOpts)
313
313
  : await projectSupplyChain(supplyOpts);
@@ -357,6 +357,7 @@ export async function loadCapabilityDiffByPlugin(
357
357
  manifest: Manifest | null,
358
358
  ): Promise<Readonly<Record<string, readonly ManifestChange[]>>> {
359
359
  try {
360
+ const { runDoctorDiff } = await import("../../cli/doctor-diff.ts");
360
361
  const result = await runDoctorDiff({
361
362
  cwd,
362
363
  ...(manifest ? { after: manifest } : {}),
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Console flows that may run without an operator session.
3
+ *
4
+ * Kept out of `flows.ts` so `consolePlugin` does not pull the full flow graph.
5
+ */
6
+
7
+ /** Public setup / session entry flows (no operator session required). */
8
+ export const PUBLIC_CONSOLE_FLOWS = new Set([
9
+ "console.setup.status",
10
+ "console.setup.claim",
11
+ "console.session.login",
12
+ ]);