okengine 0.1.6 → 0.2.1

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 (52) 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 +19 -6
  6. package/src/cli/dev.ts +3 -1
  7. package/src/cli/doc-drift.ts +54 -21
  8. package/src/console/index.ts +25 -13
  9. package/src/console/server/app.ts +44 -7
  10. package/src/console/server/flows.ts +2 -6
  11. package/src/console/server/index.ts +2 -1
  12. package/src/console/server/lazy-panels.test.ts +27 -0
  13. package/src/console/server/panel-load.ts +28 -0
  14. package/src/console/server/plugin.ts +1 -1
  15. package/src/console/server/plugins.ts +7 -6
  16. package/src/console/server/public-flows.ts +12 -0
  17. package/src/console/server/state.ts +159 -122
  18. package/src/console/server/store.ts +13 -10
  19. package/src/drivers/index.ts +1 -6
  20. package/src/drivers/vault-sops.ts +20 -1
  21. package/src/kernel/app.ts +1 -1
  22. package/src/kernel/boot-bind/ai.ts +31 -0
  23. package/src/kernel/boot-bind/channel.ts +27 -0
  24. package/src/kernel/boot-bind/clock.ts +74 -0
  25. package/src/kernel/boot-bind/gate.ts +28 -0
  26. package/src/kernel/boot-bind/runs.ts +28 -0
  27. package/src/kernel/boot-bind/signal.ts +68 -0
  28. package/src/kernel/boot-bind/store.ts +47 -0
  29. package/src/kernel/boot-bind/vault.ts +36 -0
  30. package/src/kernel/boot.test.ts +85 -1
  31. package/src/kernel/boot.ts +250 -212
  32. package/src/kernel/index.ts +2 -0
  33. package/src/mcp/data.ts +1 -0
  34. package/src/mcp/docs-index.ts +252 -0
  35. package/src/mcp/docs-mcp.test.ts +176 -0
  36. package/src/mcp/docs-server.ts +233 -0
  37. package/src/mcp/docs-tools.ts +143 -0
  38. package/src/mcp/index.ts +29 -5
  39. package/src/mcp/protocol.ts +2 -1
  40. package/src/release/exports.test.ts +71 -0
  41. package/src/release/exports.ts +156 -0
  42. package/src/release/index.ts +21 -0
  43. package/src/release/limits.ts +9 -0
  44. package/src/release/measure.exports.test.ts +82 -0
  45. package/src/release/measure.ts +297 -14
  46. package/src/release/publish.ts +14 -3
  47. package/src/release/readme.test.ts +61 -0
  48. package/src/release/readme.ts +13 -0
  49. package/src/runtime/index.ts +1 -0
  50. package/src/runtime/security.test.ts +6 -1
  51. package/src/runtime/types.ts +6 -0
  52. package/src/test/create-test-app.ts +2 -0
@@ -3,15 +3,32 @@
3
3
  *
4
4
  * Pure TypeScript, in-process. No Go `sops` / `age` binary.
5
5
  * Decrypts the SOPS data key with age, then AES-256-GCM value payloads.
6
+ *
7
+ * `age-encryption` is an optional peer — loaded only when this driver runs.
6
8
  */
7
9
 
8
- import * as age from "age-encryption";
9
10
  import type {
10
11
  VaultBag,
11
12
  VaultDriver,
12
13
  VaultOpenOptions,
13
14
  } from "./vault-types.ts";
14
15
 
16
+ /** Minimal Typage surface used by this driver. */
17
+ type AgeModule = typeof import("age-encryption");
18
+
19
+ /**
20
+ * Lazy-load Typage so apps that never open a sops vault skip the dependency.
21
+ */
22
+ async function loadAge(): Promise<AgeModule> {
23
+ try {
24
+ return await import("age-encryption");
25
+ } catch {
26
+ throw new Error(
27
+ "sops vault: install optional peer `age-encryption` (bun add age-encryption)",
28
+ );
29
+ }
30
+ }
31
+
15
32
  /** SOPS metadata block. */
16
33
  interface SopsMeta {
17
34
  readonly age?: ReadonlyArray<{
@@ -109,6 +126,7 @@ async function decryptDataKey(
109
126
  identity: string,
110
127
  encBlock: string,
111
128
  ): Promise<Uint8Array> {
129
+ const age = await loadAge();
112
130
  const d = new age.Decrypter();
113
131
  d.addIdentity(identity.trim());
114
132
  const trimmed = encBlock.trim();
@@ -182,6 +200,7 @@ export async function buildSopsFixture(
182
200
  recipient: string,
183
201
  dataKey?: Uint8Array,
184
202
  ): Promise<{ json: string; dataKey: Uint8Array }> {
203
+ const age = await loadAge();
185
204
  const key = dataKey ?? crypto.getRandomValues(new Uint8Array(32));
186
205
  const e = new age.Encrypter();
187
206
  e.addRecipient(recipient);
package/src/kernel/app.ts CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  // `./boot.ts` pulls in every element + driver module (vault, store, signal,
23
23
  // clock, gate, channel, ai, runs) — a type-only import here keeps that whole
24
24
  // graph out of the cold-start path; `doBoot` below loads it lazily, only
25
- // when an app actually boots (AGENTS.md budget: cold start < 75 ms).
25
+ // when an app actually boots (AGENTS.md / unified-theory budget: cold start < 75 ms).
26
26
  import type { BootOptions, BootResult, ElementRuntimes } from "./boot.ts";
27
27
  import type { CapabilityToken } from "./capability.ts";
28
28
  import {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Lazy AI binder — loaded only when AI is declared.
3
+ */
4
+
5
+ import { mockAiDriver } from "../../drivers/ai-mock.ts";
6
+ import {
7
+ createAiRuntime,
8
+ type AiRuntime,
9
+ } from "../../elements/ai.ts";
10
+ import type { GateRuntime } from "../../elements/gate.ts";
11
+ import type { BootOptions } from "../boot.ts";
12
+
13
+ /**
14
+ * Construct an AI runtime (mock default; shares gate for agents).
15
+ *
16
+ * @param options - Boot options
17
+ * @param gate - Gate runtime for agent tool checks
18
+ * @param now - Clock
19
+ */
20
+ export function bindAi(
21
+ options: BootOptions,
22
+ gate: GateRuntime | undefined,
23
+ now: () => number,
24
+ ): AiRuntime {
25
+ return createAiRuntime({
26
+ ...(options.ai ?? {}),
27
+ defaultDriver: options.ai?.defaultDriver ?? mockAiDriver,
28
+ gates: options.ai?.gates ?? gate,
29
+ now,
30
+ });
31
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Lazy channel binder — loaded only when Channel is declared.
3
+ */
4
+
5
+ import { openConsoleChannel } from "../../drivers/channel-console.ts";
6
+ import {
7
+ createChannelRuntime,
8
+ type ChannelRuntime,
9
+ } from "../../elements/channel.ts";
10
+ import type { BootOptions } from "../boot.ts";
11
+
12
+ /**
13
+ * Construct a Channel runtime (console inbox default).
14
+ *
15
+ * @param options - Boot options
16
+ * @param now - Clock
17
+ */
18
+ export function bindChannel(
19
+ options: BootOptions,
20
+ now: () => number,
21
+ ): ChannelRuntime {
22
+ return createChannelRuntime({
23
+ ...(options.channel ?? {}),
24
+ drivers: options.channel?.drivers ?? [openConsoleChannel()],
25
+ now,
26
+ });
27
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Lazy clock binder — loaded only when Clock is declared.
3
+ */
4
+
5
+ import {
6
+ clock as declareClock,
7
+ createClockRuntime,
8
+ createTestClockRuntime,
9
+ type ClockDecl,
10
+ type ClockRuntime,
11
+ } from "../../elements/clock.ts";
12
+ import {
13
+ resolveDriverId,
14
+ type ConfigEnv,
15
+ } from "../../config/index.ts";
16
+ import type { BootOptions } from "../boot.ts";
17
+
18
+ /** Result of binding a clock runtime. */
19
+ export interface BindClockResult {
20
+ readonly clock: ClockRuntime;
21
+ readonly clockDecls: ReadonlyMap<string, ClockDecl>;
22
+ }
23
+
24
+ /**
25
+ * Construct / adopt a Clock runtime, register decls, reconcile.
26
+ *
27
+ * @param options - Boot options
28
+ * @param env - Active environment
29
+ * @param now - Clock
30
+ * @param prebuilt - Optional injected runtime
31
+ */
32
+ export async function bindClock(
33
+ options: BootOptions,
34
+ env: ConfigEnv,
35
+ now: () => number,
36
+ prebuilt?: ClockRuntime,
37
+ ): Promise<BindClockResult> {
38
+ const clockDriver =
39
+ resolveDriverId(options.config?.drivers?.clock, env) ??
40
+ (env === "test" ? "frozen" : "memory");
41
+ const clock =
42
+ prebuilt ??
43
+ (clockDriver === "frozen" || env === "test"
44
+ ? createTestClockRuntime(now(), { instanceId: options.instanceId })
45
+ : createClockRuntime({ instanceId: options.instanceId, now }));
46
+
47
+ const clockDecls = new Map<string, ClockDecl>();
48
+ for (const c of options.clocks ?? []) {
49
+ clockDecls.set(c.name, c);
50
+ }
51
+ for (const b of options.bindings ?? []) {
52
+ if (b.trigger.kind === "every" && !clockDecls.has(b.trigger.interval)) {
53
+ clockDecls.set(
54
+ b.trigger.interval,
55
+ declareClock(b.trigger.interval, { every: b.trigger.interval }),
56
+ );
57
+ }
58
+ }
59
+ for (const decl of clockDecls.values()) {
60
+ clock.register(decl);
61
+ }
62
+ await clock.reconcile();
63
+
64
+ if (options.onCronFire) {
65
+ const fire = options.onCronFire;
66
+ for (const name of clockDecls.keys()) {
67
+ clock.onCron(name, async () => {
68
+ await fire(name);
69
+ });
70
+ }
71
+ }
72
+
73
+ return { clock, clockDecls };
74
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Lazy gate binder — loaded only when Gate is declared (or required by AI).
3
+ */
4
+
5
+ import { memoryDrivers } from "../../drivers/memory.ts";
6
+ import {
7
+ createGateRuntime,
8
+ type GateRuntime,
9
+ } from "../../elements/gate.ts";
10
+ import type { BootOptions } from "../boot.ts";
11
+
12
+ /**
13
+ * Construct a Gate runtime backed by a memory kv namespace.
14
+ *
15
+ * @param options - Boot options
16
+ * @param now - Clock
17
+ */
18
+ export async function bindGate(
19
+ options: BootOptions,
20
+ now: () => number,
21
+ ): Promise<GateRuntime> {
22
+ const kvNs = await memoryDrivers.kv.open({ name: "oke:gates" });
23
+ return createGateRuntime({
24
+ gates: options.gates ?? [],
25
+ kv: kvNs,
26
+ now,
27
+ });
28
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Lazy runs binder — loaded only when runs are requested.
3
+ */
4
+
5
+ import {
6
+ createRunsRuntime,
7
+ memoryRunsDriver,
8
+ type CreateRunsRuntimeOptions,
9
+ type RunsRuntime,
10
+ } from "../../runs/index.ts";
11
+
12
+ /**
13
+ * Construct and open a runs runtime.
14
+ *
15
+ * @param options - Create options (when not already a runtime)
16
+ */
17
+ export async function bindRuns(
18
+ options?: CreateRunsRuntimeOptions,
19
+ ): Promise<RunsRuntime> {
20
+ const runs = createRunsRuntime({
21
+ driver: options?.driver ?? memoryRunsDriver,
22
+ ...(options ?? {}),
23
+ });
24
+ if (!runs.store) {
25
+ await runs.open();
26
+ }
27
+ return runs;
28
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Lazy signal binder — loaded only when Signal is declared.
3
+ */
4
+
5
+ import { memorySignalDriver } from "../../drivers/signal-memory.ts";
6
+ import {
7
+ createSignalRuntime,
8
+ type SignalRuntime,
9
+ } from "../../elements/signal.ts";
10
+ import {
11
+ resolveDriverId,
12
+ type ConfigEnv,
13
+ } from "../../config/index.ts";
14
+ import type { BootOptions } from "../boot.ts";
15
+
16
+ /**
17
+ * Construct a Signal runtime, register decls / binding names, start the bus.
18
+ *
19
+ * @param options - Boot options
20
+ * @param env - Active environment
21
+ * @param now - Clock
22
+ */
23
+ export async function bindSignal(
24
+ options: BootOptions,
25
+ env: ConfigEnv,
26
+ now: () => number,
27
+ ): Promise<SignalRuntime> {
28
+ const signalId =
29
+ resolveDriverId(options.config?.drivers?.signal, env) ?? "memory";
30
+ void signalId;
31
+ const signal = createSignalRuntime({
32
+ driver: memorySignalDriver,
33
+ now,
34
+ });
35
+ for (const decl of options.signals ?? []) {
36
+ signal.register(decl);
37
+ }
38
+ for (const b of options.bindings ?? []) {
39
+ if (b.trigger.kind === "signal") {
40
+ if (!signal.declarations.has(b.trigger.name)) {
41
+ signal.register({
42
+ name: b.trigger.name,
43
+ delivery: "once",
44
+ retries: 3,
45
+ deadLetter: true,
46
+ optional: true,
47
+ });
48
+ }
49
+ }
50
+ }
51
+ const bus = await signal.start();
52
+
53
+ if (options.onSignal) {
54
+ const handler = options.onSignal;
55
+ const seen = new Set<string>();
56
+ for (const b of options.bindings ?? []) {
57
+ if (b.trigger.kind !== "signal") continue;
58
+ const name = b.trigger.name;
59
+ if (seen.has(name)) continue;
60
+ seen.add(name);
61
+ await bus.subscribe(name, `oke:${name}`, async (msg) => {
62
+ await handler(name, msg.payload);
63
+ });
64
+ }
65
+ }
66
+
67
+ return signal;
68
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Lazy store binder — loaded only when Store is declared.
3
+ */
4
+
5
+ import { memoryDrivers } from "../../drivers/memory.ts";
6
+ import {
7
+ createStoreRuntime,
8
+ type StoreRuntime,
9
+ } from "../../elements/store.ts";
10
+ import {
11
+ resolveDriverId,
12
+ type ConfigEnv,
13
+ } from "../../config/index.ts";
14
+ import type { BootOptions } from "../boot.ts"; // type-only — no cycle at runtime
15
+
16
+ /**
17
+ * Construct a Store runtime and register facet declarations.
18
+ *
19
+ * @param options - Boot options
20
+ * @param env - Active environment
21
+ * @param now - Clock
22
+ */
23
+ export function bindStore(
24
+ options: BootOptions,
25
+ env: ConfigEnv,
26
+ now: () => number,
27
+ ): StoreRuntime {
28
+ const sqlId =
29
+ resolveDriverId(options.config?.drivers?.store?.sql, env) ?? "memory";
30
+ const kvId =
31
+ resolveDriverId(options.config?.drivers?.store?.kv, env) ?? "memory";
32
+ void sqlId;
33
+ void kvId;
34
+ const store = createStoreRuntime({
35
+ drivers: {
36
+ sql: memoryDrivers.sql,
37
+ kv: memoryDrivers.kv,
38
+ files: memoryDrivers.files,
39
+ index: memoryDrivers.index,
40
+ },
41
+ now,
42
+ });
43
+ for (const decl of options.stores ?? []) {
44
+ store.register?.(decl);
45
+ }
46
+ return store;
47
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Lazy vault binder — loaded only when secrets are declared.
3
+ */
4
+
5
+ import { memoryVaultDriver } from "../../drivers/vault-memory.ts";
6
+ import {
7
+ createVaultRuntime,
8
+ type VaultRuntime,
9
+ } from "../../elements/vault.ts";
10
+ import type { ConfigEnv } from "../../config/index.ts";
11
+ import type { BootOptions } from "../boot.ts";
12
+
13
+ /**
14
+ * Construct and boot a Vault runtime from BootOptions.
15
+ *
16
+ * @param options - Boot options
17
+ * @param env - Active environment
18
+ */
19
+ export async function bindVault(
20
+ options: BootOptions,
21
+ env: ConfigEnv,
22
+ ): Promise<VaultRuntime> {
23
+ const vaultSecrets = options.vault?.secrets ?? options.secrets ?? [];
24
+ const vault = createVaultRuntime({
25
+ secrets: vaultSecrets,
26
+ chain: options.vault?.chain ?? [
27
+ {
28
+ driver: memoryVaultDriver,
29
+ options: { secrets: {} },
30
+ },
31
+ ],
32
+ allowDevFallbacks: options.vault?.allowDevFallbacks ?? env !== "prod",
33
+ });
34
+ await vault.boot();
35
+ return vault;
36
+ }
@@ -7,10 +7,14 @@
7
7
  */
8
8
 
9
9
  import { afterEach, describe, expect, jest, test } from "bun:test";
10
+ import { mkdtemp, rm } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
10
13
  import { memoryVaultDriver } from "../drivers/index.ts";
11
14
  import { clock } from "../elements/clock.ts";
15
+ import { store } from "../elements/store.ts";
12
16
  import { vault, VaultBootError } from "../elements/vault.ts";
13
- import { bootApplication } from "./boot.ts";
17
+ import { bootApplication, resolveElementNeeds } from "./boot.ts";
14
18
  import { flow, resetFlowSeq } from "./flow.ts";
15
19
  import { every, http } from "./triggers.ts";
16
20
  import { oke } from "./app.ts";
@@ -42,6 +46,86 @@ describe("boot — vault gaps", () => {
42
46
  expect(boot.message).toContain("DATABASE_URL");
43
47
  }
44
48
  });
49
+
50
+ test("three missing secrets still list every gap at once", async () => {
51
+ try {
52
+ await bootApplication({
53
+ env: "prod",
54
+ secrets: [
55
+ vault("A", { description: "one" }),
56
+ vault("B", { description: "two" }),
57
+ vault("C", { description: "three" }),
58
+ ],
59
+ vault: {
60
+ allowDevFallbacks: false,
61
+ chain: [{ driver: memoryVaultDriver, options: { secrets: {} } }],
62
+ },
63
+ });
64
+ expect.unreachable("boot should fail");
65
+ } catch (err) {
66
+ expect(err).toBeInstanceOf(VaultBootError);
67
+ const boot = err as VaultBootError;
68
+ expect(boot.gaps.map((g) => g.name).sort()).toEqual(["A", "B", "C"]);
69
+ }
70
+ });
71
+ });
72
+
73
+ describe("boot — lazy element needs", () => {
74
+ test("Store-only declarations do not require AI/channel/vault", () => {
75
+ const needs = resolveElementNeeds({
76
+ stores: [store.sql("notes", { schema: {} as never })],
77
+ });
78
+ expect(needs.store).toBe(true);
79
+ expect(needs.ai).toBe(false);
80
+ expect(needs.channel).toBe(false);
81
+ expect(needs.vault).toBe(false);
82
+ expect(needs.signal).toBe(false);
83
+ });
84
+
85
+ test("oke() Store-only graph stays under the prior 41.4 kB baseline", async () => {
86
+ const dir = await mkdtemp(join(tmpdir(), "oke-store-only-"));
87
+ const entry = join(dir, "entry.ts");
88
+ const appPath = join(import.meta.dir, "app.ts");
89
+ const storePath = join(import.meta.dir, "../elements/store.ts");
90
+ await Bun.write(
91
+ entry,
92
+ `import { oke } from ${JSON.stringify(appPath)};\n` +
93
+ `import { store } from ${JSON.stringify(storePath)};\n` +
94
+ `export const app = oke({ name: "notes" });\n` +
95
+ `export { store };\n`,
96
+ );
97
+ try {
98
+ const result = await Bun.build({
99
+ entrypoints: [entry],
100
+ minify: true,
101
+ target: "bun",
102
+ format: "esm",
103
+ external: [
104
+ "@duckdb/node-api",
105
+ "@duckdb/*",
106
+ "age-encryption",
107
+ "sently",
108
+ "sently/*",
109
+ "ajv",
110
+ "ajv/*",
111
+ "ajv-formats",
112
+ "oxc-parser",
113
+ "zod",
114
+ ],
115
+ });
116
+ expect(result.success).toBe(true);
117
+ let total = 0;
118
+ for (const o of result.outputs) {
119
+ const raw = await o.arrayBuffer();
120
+ if (raw.byteLength === 0) continue;
121
+ total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
122
+ }
123
+ // Prior eager-bind baseline: ~41.4 kB for oke() alone.
124
+ expect(total).toBeLessThan(41_400);
125
+ } finally {
126
+ await rm(dir, { recursive: true, force: true });
127
+ }
128
+ });
45
129
  });
46
130
 
47
131
  describe("boot — capabilities from Manifest effects", () => {