okengine 0.3.5 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/AGENTS.md +6 -0
  2. package/manifest.v1.schema.json +43 -1
  3. package/package.json +27 -12
  4. package/site/content/docs/ai/skills.mdx +11 -7
  5. package/site/content/docs/console/vault.mdx +4 -0
  6. package/site/content/docs/elements/ai.mdx +2 -0
  7. package/site/content/docs/elements/channel.mdx +7 -4
  8. package/site/content/docs/elements/clock.mdx +2 -4
  9. package/site/content/docs/elements/flow.mdx +19 -10
  10. package/site/content/docs/elements/gate.mdx +18 -11
  11. package/site/content/docs/elements/signal.mdx +9 -12
  12. package/site/content/docs/elements/store.mdx +69 -8
  13. package/site/content/docs/elements/vault.mdx +11 -12
  14. package/site/content/docs/get-started/basic-usage.mdx +76 -41
  15. package/site/content/docs/get-started/installation.mdx +95 -43
  16. package/site/content/docs/get-started/introduction.mdx +128 -75
  17. package/site/content/docs/get-started/meta.json +1 -1
  18. package/site/content/docs/get-started/why.mdx +141 -0
  19. package/site/content/docs/plugins/ip-allowlist.mdx +1 -2
  20. package/site/content/docs/plugins/security-headers.mdx +1 -1
  21. package/site/content/docs/reference/configuration.mdx +2 -2
  22. package/site/content/docs/reference/environment-variables.mdx +10 -0
  23. package/site/content/docs/reference/fx.mdx +56 -3
  24. package/site/content/docs/reference/plugins.mdx +18 -18
  25. package/src/cli/competitor-mention-removal.test.ts +117 -0
  26. package/src/cli/dev.ts +20 -0
  27. package/src/cli/meilisearch-local.test.ts +69 -0
  28. package/src/cli/meilisearch-local.ts +188 -0
  29. package/src/compiler/extract.test.ts +63 -0
  30. package/src/compiler/extract.ts +36 -15
  31. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +5 -1
  32. package/src/console/server/channels.ts +2 -0
  33. package/src/console/server/clock.ts +3 -0
  34. package/src/console/server/flows.ts +13 -0
  35. package/src/console/server/gates.ts +2 -0
  36. package/src/console/server/plugins.ts +20 -1
  37. package/src/console/server/signals.ts +5 -0
  38. package/src/console/server/store.test.ts +17 -0
  39. package/src/console/server/store.ts +35 -1
  40. package/src/console/ui/channels/types.ts +1 -0
  41. package/src/console/ui/clock/types.ts +1 -0
  42. package/src/console/ui/display.test.ts +14 -0
  43. package/src/console/ui/display.ts +9 -0
  44. package/src/console/ui/dist/assets/index-CjxwRGVv.js +10 -0
  45. package/src/console/ui/dist/assets/panel-access-BGv45snf.js +64 -0
  46. package/src/console/ui/dist/assets/{panel-ai-D_m6WQI8.js → panel-ai-B2S7LEii.js} +1 -1
  47. package/src/console/ui/dist/assets/{panel-architecture-CKnXFyUx.js → panel-architecture-D7UJh91v.js} +1 -1
  48. package/src/console/ui/dist/assets/panel-channels-9T3ybqRu.js +1 -0
  49. package/src/console/ui/dist/assets/panel-clock-Cb1UXGRQ.js +1 -0
  50. package/src/console/ui/dist/assets/{panel-diff-cdonmH8c.js → panel-diff-DmYbKWmN.js} +1 -1
  51. package/src/console/ui/dist/assets/panel-flows-PiHwT55z.js +48 -0
  52. package/src/console/ui/dist/assets/panel-gates-BQGYXvjT.js +1 -0
  53. package/src/console/ui/dist/assets/panel-overview-BBnRO18l.js +1 -0
  54. package/src/console/ui/dist/assets/panel-plugins-D0PsmVw2.js +1 -0
  55. package/src/console/ui/dist/assets/panel-runs-CWuRDe0r.js +1 -0
  56. package/src/console/ui/dist/assets/panel-signals-Bbg4ewpP.js +1 -0
  57. package/src/console/ui/dist/assets/panel-store-CPCbsDRa.js +1 -0
  58. package/src/console/ui/dist/assets/panel-traces-DVAzuA_S.js +1 -0
  59. package/src/console/ui/dist/assets/panel-vault-D1_MvOmo.js +1 -0
  60. package/src/console/ui/dist/assets/{rolldown-runtime-CNC7AqOf.js → rolldown-runtime-B0Z9INg1.js} +1 -1
  61. package/src/console/ui/dist/index.html +2 -2
  62. package/src/console/ui/gates/types.ts +1 -0
  63. package/src/console/ui/plugins/fixture.ts +7 -0
  64. package/src/console/ui/plugins/types.ts +3 -0
  65. package/src/console/ui/shell/client.ts +3 -0
  66. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +7 -2
  67. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +9 -2
  68. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +12 -5
  69. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +18 -4
  70. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +13 -2
  71. package/src/console/ui/shell/panels/store/StorePanel.tsx +11 -4
  72. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +9 -3
  73. package/src/console/ui/signals/types.ts +1 -0
  74. package/src/console/ui/store/fixture.ts +5 -0
  75. package/src/console/ui/store/types.ts +2 -0
  76. package/src/docker/compose.ts +5 -0
  77. package/src/docker/docker.test.ts +41 -0
  78. package/src/docker/recipes/index.ts +10 -2
  79. package/src/docker/recipes/meilisearch.ts +31 -0
  80. package/src/drivers/conformance.test.ts +26 -0
  81. package/src/drivers/conformance.ts +40 -3
  82. package/src/drivers/drizzle-dialect.test.ts +4 -0
  83. package/src/drivers/drizzle-dialect.ts +8 -4
  84. package/src/drivers/index.ts +18 -2
  85. package/src/drivers/libsql.ts +179 -0
  86. package/src/drivers/meilisearch.integration.test.ts +77 -0
  87. package/src/drivers/meilisearch.test.ts +181 -0
  88. package/src/drivers/meilisearch.ts +208 -0
  89. package/src/drivers/memory.ts +4 -4
  90. package/src/drivers/pglite.ts +79 -0
  91. package/src/drivers/pgvector.ts +60 -25
  92. package/src/drivers/types.ts +106 -16
  93. package/src/drivers/vault-driver-removal.test.ts +6 -0
  94. package/src/drivers/vault-types.ts +4 -4
  95. package/src/elements/ai/runtime.ts +6 -0
  96. package/src/elements/ai.test.ts +22 -0
  97. package/src/elements/channel/declare.ts +5 -0
  98. package/src/elements/clock/declare.ts +5 -0
  99. package/src/elements/clock/durable.ts +7 -1
  100. package/src/elements/gate/declare.ts +28 -5
  101. package/src/elements/gate.ts +1 -0
  102. package/src/elements/signal/declare.ts +5 -0
  103. package/src/elements/store/declare.ts +10 -2
  104. package/src/elements/store/index-boot.test.ts +299 -0
  105. package/src/elements/store/runtime.ts +110 -17
  106. package/src/elements/store/schema-decl.ts +7 -0
  107. package/src/elements/store.ts +2 -0
  108. package/src/elements/vault.test.ts +27 -4
  109. package/src/elements/vault.ts +1 -1
  110. package/src/index.ts +4 -0
  111. package/src/kernel/abort-scope.ts +116 -0
  112. package/src/kernel/app.ts +12 -2
  113. package/src/kernel/boot-bind/store.test.ts +68 -1
  114. package/src/kernel/boot-bind/store.ts +92 -2
  115. package/src/kernel/concurrency.test.ts +294 -0
  116. package/src/kernel/concurrency.ts +220 -0
  117. package/src/kernel/flow.ts +9 -0
  118. package/src/kernel/fx.test.ts +23 -2
  119. package/src/kernel/fx.ts +80 -4
  120. package/src/kernel/index.ts +23 -0
  121. package/src/kernel/journal.ts +9 -0
  122. package/src/kernel/plugin/capabilities.test.ts +18 -0
  123. package/src/kernel/plugin.ts +11 -3
  124. package/src/kernel/redacted.ts +74 -0
  125. package/src/kernel/registry.ts +29 -6
  126. package/src/kernel/router.ts +3 -3
  127. package/src/manifest/types.ts +21 -0
  128. package/src/release/measure.ts +4 -0
  129. package/src/test/provisions.integration.test.ts +1 -1
  130. package/site/content/docs/get-started/comparison.mdx +0 -65
  131. package/src/console/ui/dist/assets/index-BWo8R7NR.js +0 -10
  132. package/src/console/ui/dist/assets/panel-access-C0J2D-a2.js +0 -64
  133. package/src/console/ui/dist/assets/panel-channels-BOmQ-onL.js +0 -1
  134. package/src/console/ui/dist/assets/panel-clock-giAq0Ccv.js +0 -1
  135. package/src/console/ui/dist/assets/panel-flows-DlCU5zjA.js +0 -45
  136. package/src/console/ui/dist/assets/panel-gates-XclZxWD5.js +0 -1
  137. package/src/console/ui/dist/assets/panel-overview-BznEOTnb.js +0 -1
  138. package/src/console/ui/dist/assets/panel-plugins-CcGM1g64.js +0 -1
  139. package/src/console/ui/dist/assets/panel-runs-CGWNHLR4.js +0 -1
  140. package/src/console/ui/dist/assets/panel-signals-CNywkdak.js +0 -1
  141. package/src/console/ui/dist/assets/panel-store-KmTbFHMH.js +0 -1
  142. package/src/console/ui/dist/assets/panel-traces-DBLx2ilD.js +0 -1
  143. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +0 -1
  144. package/src/drivers/vault-infisical.ts +0 -57
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Local Meilisearch lifecycle for `oke dev` (local mode).
3
+ *
4
+ * A Meilisearch server is a fourth moving part only when the app actually
5
+ * opts into the `meilisearch` store.index driver. This module:
6
+ *
7
+ * - generates the master key once and persists it under `.oke/meilisearch/`
8
+ * (`0700` dir / `0600` file, atomic write — same material discipline as
9
+ * OpenBao, minus the init/unseal ceremony: a single static key),
10
+ * - resolves the `meilisearch` binary from `PATH` (a documented prerequisite,
11
+ * like Docker for `--docker`; we never auto-download binaries),
12
+ * - spawns it via `Bun.spawn` with its own data dir + port, polls `/health`,
13
+ * and hands back an env overlay (`OKE_STORE_INDEX_URL` / `…_KEY`) plus a
14
+ * `stop()` that kills the child alongside the app.
15
+ *
16
+ * Fail-loud: missing binary / unhealthy server / never-ready all throw
17
+ * {@link MeilisearchLocalError} — never a silent memory fallback.
18
+ */
19
+
20
+ import { randomBytes } from "node:crypto";
21
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
22
+ import { dirname, join, resolve } from "node:path";
23
+
24
+ /** Material directory relative to the project root. */
25
+ export const MEILISEARCH_STATE_DIR_REL = join(".oke", "meilisearch");
26
+ /** Default loopback port for the local server. */
27
+ export const MEILISEARCH_LOCAL_PORT = 7700;
28
+
29
+ /** Fail-loud local Meilisearch error (missing binary / never healthy). */
30
+ export class MeilisearchLocalError extends Error {
31
+ constructor(message: string) {
32
+ super(message);
33
+ this.name = "MeilisearchLocalError";
34
+ }
35
+ }
36
+
37
+ /** Options for {@link startLocalMeilisearch}. */
38
+ export interface LocalMeilisearchOptions {
39
+ readonly cwd?: string;
40
+ /** Loopback port (default {@link MEILISEARCH_LOCAL_PORT}). */
41
+ readonly port?: number;
42
+ /** Override state dir (tests). */
43
+ readonly stateDir?: string;
44
+ /** Binary name/path resolved via `Bun.which` (default `meilisearch`). */
45
+ readonly binary?: string;
46
+ /** Max ms to wait for `/health` to report available (default 30_000). */
47
+ readonly readyTimeoutMs?: number;
48
+ readonly fetch?: typeof globalThis.fetch;
49
+ }
50
+
51
+ /** A running local Meilisearch child. */
52
+ export interface LocalMeilisearchHandle {
53
+ /** Base URL the app should talk to. */
54
+ readonly url: string;
55
+ /** Master key (also persisted on disk). */
56
+ readonly masterKey: string;
57
+ /** Env overlay for the app child. */
58
+ readonly overlay: Record<string, string>;
59
+ /** Kill the child (idempotent). */
60
+ readonly stop: () => void;
61
+ }
62
+
63
+ /** Env overlay for the app child. */
64
+ export function meilisearchStackEnv(handle: LocalMeilisearchHandle): Record<string, string> {
65
+ return handle.overlay;
66
+ }
67
+
68
+ function writeMaterial(path: string, value: string): void {
69
+ mkdirSync(dirname(path), { recursive: true });
70
+ chmodSync(dirname(path), 0o700);
71
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
72
+ writeFileSync(tmp, `${value}\n`, { mode: 0o600 });
73
+ renameSync(tmp, path);
74
+ chmodSync(path, 0o600);
75
+ const back = readFileSync(path, "utf8").trim();
76
+ if (back !== value) {
77
+ throw new MeilisearchLocalError(`meilisearch local: verify failed reading ${path}`);
78
+ }
79
+ }
80
+
81
+ /** Read the persisted master key, or generate + persist a fresh one. */
82
+ export function ensureMasterKey(stateDir: string): string {
83
+ const keyPath = join(stateDir, "master.key");
84
+ if (existsSync(keyPath)) {
85
+ const key = readFileSync(keyPath, "utf8").trim();
86
+ if (key.length >= 16) return key;
87
+ throw new MeilisearchLocalError(
88
+ `meilisearch local: ${keyPath} exists but is too short — delete it to regenerate`,
89
+ );
90
+ }
91
+ const key = randomBytes(32).toString("hex");
92
+ writeMaterial(keyPath, key);
93
+ return key;
94
+ }
95
+
96
+ /**
97
+ * Start a local Meilisearch server and wait for `/health`.
98
+ *
99
+ * @param options - cwd / port / binary / injected fetch
100
+ */
101
+ export async function startLocalMeilisearch(
102
+ options: LocalMeilisearchOptions = {},
103
+ ): Promise<LocalMeilisearchHandle> {
104
+ const cwd = options.cwd ?? process.cwd();
105
+ const port = options.port ?? MEILISEARCH_LOCAL_PORT;
106
+ const stateDir = options.stateDir ?? resolve(cwd, MEILISEARCH_STATE_DIR_REL);
107
+ const dataDir = join(stateDir, "data");
108
+ const fetchFn = options.fetch ?? globalThis.fetch;
109
+ const readyTimeoutMs = options.readyTimeoutMs ?? 30_000;
110
+
111
+ const binary = options.binary ?? "meilisearch";
112
+ const resolved = Bun.which(binary);
113
+ if (!resolved) {
114
+ throw new MeilisearchLocalError(
115
+ "meilisearch local: `meilisearch` not found on PATH — install it to use the store.index FTS driver " +
116
+ "(brew install meilisearch · curl -L https://install.meilisearch.com | sh), " +
117
+ "or run `oke dev --docker` instead. OKE never auto-downloads binaries.",
118
+ );
119
+ }
120
+
121
+ const masterKey = ensureMasterKey(stateDir);
122
+ mkdirSync(dataDir, { recursive: true });
123
+
124
+ const url = `http://127.0.0.1:${port}`;
125
+ const proc = Bun.spawn(
126
+ [
127
+ resolved,
128
+ "--http-addr",
129
+ `127.0.0.1:${port}`,
130
+ "--master-key",
131
+ masterKey,
132
+ "--db-path",
133
+ dataDir,
134
+ "--env",
135
+ "development",
136
+ "--no-analytics",
137
+ ],
138
+ {
139
+ cwd,
140
+ stdout: "ignore",
141
+ stderr: "inherit",
142
+ env: { ...(process.env as Record<string, string>) },
143
+ },
144
+ );
145
+
146
+ let stopped = false;
147
+ const stop = () => {
148
+ if (stopped) return;
149
+ stopped = true;
150
+ try {
151
+ proc.kill("SIGKILL");
152
+ } catch {
153
+ // Already gone.
154
+ }
155
+ };
156
+
157
+ const deadline = Date.now() + readyTimeoutMs;
158
+ for (;;) {
159
+ if (proc.exitCode !== null) {
160
+ throw new MeilisearchLocalError(
161
+ `meilisearch local: server exited (${proc.exitCode}) before becoming healthy`,
162
+ );
163
+ }
164
+ try {
165
+ const res = await fetchFn(`${url}/health`);
166
+ if (res.ok) break;
167
+ } catch {
168
+ // Not up yet.
169
+ }
170
+ if (Date.now() > deadline) {
171
+ stop();
172
+ throw new MeilisearchLocalError(
173
+ `meilisearch local: no /health from ${url} within ${readyTimeoutMs}ms`,
174
+ );
175
+ }
176
+ await new Promise((resolve) => setTimeout(resolve, 25));
177
+ }
178
+
179
+ return {
180
+ url,
181
+ masterKey,
182
+ overlay: {
183
+ OKE_STORE_INDEX_URL: url,
184
+ OKE_STORE_INDEX_KEY: masterKey,
185
+ },
186
+ stop,
187
+ };
188
+ }
@@ -240,6 +240,69 @@ export const db = store.sql("app", { schema: { notes } });
240
240
  sqlName: "id",
241
241
  });
242
242
  });
243
+
244
+ test("extracts optional description fields additively", async () => {
245
+ const source = `
246
+ import { store, field, signal, channel, clock, gate, vault } from "okengine";
247
+
248
+ export const notes = store.schema.table("notes", {
249
+ title: field.text().notNull().describe("Note title"),
250
+ });
251
+
252
+ export const db = store.sql("app", {
253
+ description: "Primary app database",
254
+ schema: { notes },
255
+ });
256
+
257
+ export const embeddings = store.index("embeddings", {
258
+ description: "Document embeddings",
259
+ dims: 3,
260
+ });
261
+
262
+ export const sessions = store.kv("sessions", { description: "Session cache" });
263
+
264
+ export const orderPlaced = signal("order-placed", {
265
+ delivery: "once",
266
+ description: "Order placed event",
267
+ });
268
+
269
+ export const bookingConfirmed = channel.template("booking-confirmed", {
270
+ medium: "email",
271
+ description: "Booking confirmation email",
272
+ });
273
+
274
+ export const expireHolds = clock("expire-holds", {
275
+ every: "10m",
276
+ description: "Expire unpaid holds",
277
+ });
278
+
279
+ export const member = gate.policy("member", {
280
+ description: "Verified members only",
281
+ check: ({ auth }) => !!auth?.verified,
282
+ });
283
+
284
+ export const stripeKey = vault.secret("STRIPE_KEY", {
285
+ description: "Payments gateway key",
286
+ });
287
+ `;
288
+ const manifest = await extractFromSources({
289
+ "src/described.ts": source,
290
+ });
291
+
292
+ expect(manifest.stores?.app?.description).toBe("Primary app database");
293
+ expect(manifest.stores?.app?.tables?.notes?.columns?.title).toMatchObject({
294
+ description: "Note title",
295
+ });
296
+ expect(manifest.stores?.embeddings?.description).toBe("Document embeddings");
297
+ expect(manifest.stores?.sessions?.description).toBe("Session cache");
298
+ expect(manifest.signals?.["order-placed"]?.description).toBe("Order placed event");
299
+ expect(manifest.channels?.["booking-confirmed"]?.description).toBe(
300
+ "Booking confirmation email",
301
+ );
302
+ expect(manifest.clocks?.["expire-holds"]?.description).toBe("Expire unpaid holds");
303
+ expect(manifest.gates?.member?.description).toBe("Verified members only");
304
+ expect(manifest.vault?.STRIPE_KEY?.description).toBe("Payments gateway key");
305
+ });
243
306
  });
244
307
 
245
308
  describe("extractManifest — on(http.resource(...))", () => {
@@ -420,6 +420,7 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
420
420
  const chain: string[] = [];
421
421
  let sqlType: "text" | "integer" | undefined;
422
422
  let sqlName: string | undefined;
423
+ let description: string | undefined;
423
424
  let defaultValue: string | number | boolean | null | undefined;
424
425
  let hasDefault = false;
425
426
  let cur: AstNode | undefined = node;
@@ -435,6 +436,9 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
435
436
  if (method === "as") {
436
437
  sqlName = stringArg(call.arguments[0]) ?? sqlName;
437
438
  }
439
+ if (method === "describe") {
440
+ description = stringArg(call.arguments[0]) ?? description;
441
+ }
438
442
  if (method === "default") {
439
443
  const lit = call.arguments[0];
440
444
  if (lit && lit.type === "Literal") {
@@ -504,6 +508,7 @@ function parseFieldChain(node: AstNode, key: string): DeclaredColumn | undefined
504
508
  ...(methods.has("primaryKey") ? { primaryKey: true } : {}),
505
509
  ...(methods.has("unique") ? { unique: true } : {}),
506
510
  ...(hasDefault ? { default: defaultValue ?? null } : {}),
511
+ ...(description !== undefined ? { description } : {}),
507
512
  ...(methods.has("pii") ? { pii: true } : {}),
508
513
  ...(methods.has("sensitive") ? { sensitive: true } : {}),
509
514
  };
@@ -597,7 +602,12 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
597
602
  const storeName = stringArg(call.arguments[0]) ?? "store";
598
603
  const ref = `${facet}:${storeName}` as const;
599
604
  const bindingName = enclosingConstName(call, program);
605
+ const storeOpts = objectArg(call.arguments[1]);
606
+ const description = stringProp(storeOpts, "description");
600
607
  scope.stores[storeName] = scope.stores[storeName] ?? { facet };
608
+ if (description) {
609
+ scope.stores[storeName]!.description = description;
610
+ }
601
611
  if (facet === "sql") {
602
612
  attachSchemaOption(call.arguments[1], scope.stores[storeName]!, scope);
603
613
  }
@@ -643,7 +653,13 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
643
653
  if (obj === "gate" && prop === "policy") {
644
654
  const policyName = stringArg(call.arguments[0]);
645
655
  if (policyName) {
646
- scope.gates[policyName] = { kind: "policy", roles: [policyName] };
656
+ const opts = objectArg(call.arguments[1]);
657
+ const description = stringProp(opts, "description");
658
+ scope.gates[policyName] = {
659
+ kind: "policy",
660
+ roles: [policyName],
661
+ ...(description ? { description } : {}),
662
+ };
647
663
  const bindingName = enclosingConstName(call, program);
648
664
  if (bindingName) {
649
665
  scope.gateIds.set(bindingName, policyName);
@@ -661,26 +677,23 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
661
677
  const max = numberProp(opts, "max");
662
678
  const per = stringProp(opts, "per");
663
679
  const keyBy = stringProp(opts, "keyBy");
680
+ const description = stringProp(opts, "description");
664
681
  if (max !== undefined && per) {
665
682
  const expr = `rate:${strategy}:${max}/${per}`;
666
683
  const bindingName = enclosingConstName(call, program);
684
+ const rateGate = {
685
+ kind: "rate" as const,
686
+ strategy,
687
+ max,
688
+ per,
689
+ ...(keyBy ? { keyBy } : {}),
690
+ ...(description ? { description } : {}),
691
+ };
667
692
  if (bindingName) {
668
- scope.gates[bindingName] = {
669
- kind: "rate",
670
- strategy,
671
- max,
672
- per,
673
- ...(keyBy ? { keyBy } : {}),
674
- };
693
+ scope.gates[bindingName] = rateGate;
675
694
  scope.gateIds.set(bindingName, expr);
676
695
  } else {
677
- scope.gates[expr] = {
678
- kind: "rate",
679
- strategy,
680
- max,
681
- per,
682
- ...(keyBy ? { keyBy } : {}),
683
- };
696
+ scope.gates[expr] = rateGate;
684
697
  }
685
698
  }
686
699
  }
@@ -691,9 +704,11 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
691
704
  if (templateName) {
692
705
  const medium = stringProp(opts, "medium");
693
706
  const locales = stringArrayProp(opts, "locales");
707
+ const description = stringProp(opts, "description");
694
708
  scope.channels[templateName] = {
695
709
  ...(medium ? { medium: medium as Channel["medium"] } : { medium: "email" }),
696
710
  ...(locales ? { locales } : {}),
711
+ ...(description ? { description } : {}),
697
712
  };
698
713
  const bindingName = enclosingConstName(call, program);
699
714
  if (bindingName) {
@@ -809,6 +824,9 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
809
824
  if (delivery) {
810
825
  const signal: Signal = {
811
826
  delivery,
827
+ ...(stringProp(opts, "description")
828
+ ? { description: stringProp(opts, "description") }
829
+ : {}),
812
830
  ...(numberProp(opts, "retries") !== undefined
813
831
  ? { retries: numberProp(opts, "retries") }
814
832
  : {}),
@@ -839,6 +857,9 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
839
857
  ...(boolProp(opts, "overridable") !== undefined
840
858
  ? { overridable: boolProp(opts, "overridable") }
841
859
  : {}),
860
+ ...(stringProp(opts, "description")
861
+ ? { description: stringProp(opts, "description") }
862
+ : {}),
842
863
  };
843
864
  }
844
865
  }
@@ -5,7 +5,11 @@ export const chargeBooking = flow({
5
5
  name: "payments.chargeBooking",
6
6
  durable: true,
7
7
  do: async ({ orderId }, fx) => {
8
- const intent = await fx.step("create-intent", () => fx.vault(stripeKey));
8
+ // Journal only serializable step values a Redacted is not journaled.
9
+ const intent = await fx.step("create-intent", async () => ({
10
+ id: `pi_${orderId}`,
11
+ key: fx.vault(stripeKey).reveal(),
12
+ }));
9
13
  await fx.clock.sleep("verify-window", "2m");
10
14
  return fx.step("confirm", () => intent);
11
15
  },
@@ -41,6 +41,7 @@ export type ChannelsFace = "inbox" | "deliverability";
41
41
  /** One Manifest template row. */
42
42
  export interface ConsoleChannelTemplate {
43
43
  readonly name: string;
44
+ readonly description?: string;
44
45
  readonly medium: string;
45
46
  readonly locales: readonly string[];
46
47
  readonly from: string | null;
@@ -214,6 +215,7 @@ export function projectTemplates(manifest: Manifest | null): readonly ConsoleCha
214
215
  return Object.entries(manifest?.channels ?? {})
215
216
  .map(([name, c]) => ({
216
217
  name,
218
+ ...(c.description !== undefined ? { description: c.description } : {}),
217
219
  medium: c.medium ?? "email",
218
220
  locales: c.locales ?? [],
219
221
  from: c.from ?? null,
@@ -28,6 +28,7 @@ import {
28
28
  /** One cron row in `console.clock.list`. */
29
29
  export interface ConsoleClockRow {
30
30
  readonly name: string;
31
+ readonly description?: string;
31
32
  readonly status: CronRow["status"];
32
33
  readonly timezone: string;
33
34
  readonly overridable: boolean;
@@ -110,8 +111,10 @@ export async function projectClocksList(options: ProjectClocksOptions): Promise<
110
111
  .map((row) => {
111
112
  const flowIds = flowIdsForCronRow(row, options.manifest);
112
113
  const external = flowIds.some((id) => isExternal(flows[id]));
114
+ const description = options.manifest?.clocks?.[row.name]?.description;
113
115
  return {
114
116
  name: row.name,
117
+ ...(description !== undefined ? { description } : {}),
115
118
  status: row.status,
116
119
  timezone: row.timezone,
117
120
  overridable: row.overridable,
@@ -158,6 +158,7 @@ const SignalsListOut = z.object({
158
158
  signals: z.array(
159
159
  z.object({
160
160
  name: z.string(),
161
+ description: z.string().optional(),
161
162
  delivery: z.enum(["once", "broadcast", "live"]),
162
163
  retries: z.number(),
163
164
  deadLetterEnabled: z.boolean(),
@@ -336,6 +337,7 @@ const ChannelsListOut = z.object({
336
337
  templates: z.array(
337
338
  z.object({
338
339
  name: z.string(),
340
+ description: z.string().optional(),
339
341
  medium: z.string(),
340
342
  locales: z.array(z.string()),
341
343
  from: z.string().nullable(),
@@ -465,6 +467,7 @@ const CronHealthOut = z.object({
465
467
 
466
468
  const ClockCronOut = z.object({
467
469
  name: z.string(),
470
+ description: z.string().optional(),
468
471
  status: z.enum(["active", "paused", "orphaned"]),
469
472
  timezone: z.string(),
470
473
  overridable: z.boolean(),
@@ -753,6 +756,7 @@ const GatesListOut = z.object({
753
756
  gates: z.array(
754
757
  z.object({
755
758
  name: z.string(),
759
+ description: z.string().optional(),
756
760
  kind: z.enum(["policy", "rate"]),
757
761
  scopes: z.array(z.string()),
758
762
  roles: z.array(z.string()),
@@ -853,6 +857,13 @@ const PluginsListOut = z.object({
853
857
  }),
854
858
  ),
855
859
  declares: z.array(z.string()),
860
+ tables: z.record(
861
+ z.string(),
862
+ z.object({
863
+ plane: z.string().optional(),
864
+ description: z.string().optional(),
865
+ }),
866
+ ),
856
867
  intercepts: z.array(
857
868
  z.object({
858
869
  stage: z.string(),
@@ -1216,6 +1227,7 @@ const StoreListOut = z.object({
1216
1227
  ref: z.string(),
1217
1228
  facet: z.enum(["sql", "kv", "files", "index"]),
1218
1229
  name: z.string(),
1230
+ description: z.string().optional(),
1219
1231
  children: z.array(
1220
1232
  z.object({
1221
1233
  name: z.string(),
@@ -1229,6 +1241,7 @@ const StoreListOut = z.object({
1229
1241
  }),
1230
1242
  willNotFire: WillNotFireOut,
1231
1243
  piiColumns: z.array(z.string()),
1244
+ columnDescriptions: z.record(z.string(), z.string()),
1232
1245
  }),
1233
1246
  ),
1234
1247
  replicaLagMs: z.number().nullable(),
@@ -48,6 +48,7 @@ export type GatePrincipalKind = "role" | "key" | "user";
48
48
  /** One Module:Action / flow / gate surface row. */
49
49
  export interface ConsoleGateDefRow {
50
50
  readonly name: string;
51
+ readonly description?: string;
51
52
  readonly kind: "policy" | "rate";
52
53
  readonly scopes: readonly string[];
53
54
  readonly roles: readonly string[];
@@ -477,6 +478,7 @@ function projectGateDefs(
477
478
  def.kind === "rate" || name.startsWith("rate:") ? "rate" : "policy";
478
479
  rows.push({
479
480
  name,
481
+ ...(def.description !== undefined ? { description: def.description } : {}),
480
482
  kind,
481
483
  scopes: [...(def.scopes ?? [])],
482
484
  roles: [...(def.roles ?? [])],
@@ -8,7 +8,13 @@
8
8
 
9
9
  import { allHookCostSummaries, type HookCostSummary } from "../../kernel/hook-timing.ts";
10
10
  import type { PluginRegistry } from "../../kernel/registry.ts";
11
- import type { Manifest, ManifestChange, Plugin, PluginOrigin } from "../../manifest/types.ts";
11
+ import type {
12
+ Manifest,
13
+ ManifestChange,
14
+ Plugin,
15
+ PluginOrigin,
16
+ PluginTable,
17
+ } from "../../manifest/types.ts";
12
18
  import { CORE_PLUGINS, isCorePluginOn, type PluginConfigProbe } from "../../plugins/catalogue.ts";
13
19
  import type { PackageJsonProbe, SupplyChainSignals } from "../../plugins/supply-chain.ts";
14
20
  import type { ScanSourceFile } from "../../plugins/node-import-scan.ts";
@@ -46,6 +52,8 @@ export interface ConsolePluginRow {
46
52
  readonly summary: string | null;
47
53
  readonly scopes: readonly PluginScopeView[];
48
54
  readonly declares: readonly string[];
55
+ /** Optional metadata for `table:*` declares. */
56
+ readonly tables: Readonly<Record<string, PluginTable>>;
49
57
  readonly intercepts: readonly PluginInterceptView[];
50
58
  readonly hookCost: {
51
59
  readonly count: number;
@@ -133,6 +141,7 @@ export async function projectPluginsList(
133
141
  version: meta?.version ?? manifestPlugin?.version ?? null,
134
142
  summary: spec.summary,
135
143
  declares: meta?.declares ?? manifestPlugin?.declares ?? [],
144
+ tables: meta?.tables ?? manifestPlugin?.tables ?? {},
136
145
  interceptStages: meta?.intercepts ?? manifestPlugin?.intercepts ?? [],
137
146
  scopes: meta?.scopes ?? [],
138
147
  costs,
@@ -155,6 +164,7 @@ export async function projectPluginsList(
155
164
  version: meta.version,
156
165
  summary: null,
157
166
  declares: meta.declares,
167
+ tables: meta.tables,
158
168
  interceptStages: meta.intercepts,
159
169
  scopes: meta.scopes,
160
170
  costs,
@@ -184,6 +194,7 @@ interface PluggedMeta {
184
194
  readonly version: string | null;
185
195
  readonly declares: readonly string[];
186
196
  readonly intercepts: readonly string[];
197
+ readonly tables: Readonly<Record<string, PluginTable>>;
187
198
  readonly scopes: readonly PluginScopeView[];
188
199
  readonly origin?: PluginOrigin;
189
200
  }
@@ -199,6 +210,7 @@ function collectPlugged(
199
210
  version: p.version ?? null,
200
211
  declares: p.declares ?? [],
201
212
  intercepts: p.intercepts ?? [],
213
+ tables: p.tables ?? {},
202
214
  scopes: [],
203
215
  origin: p.origin,
204
216
  });
@@ -214,10 +226,15 @@ function collectPlugged(
214
226
  ? { kind: "app" }
215
227
  : { kind: entry.scope.kind, name: entry.scope.name };
216
228
  const scopes = [...(prev?.scopes ?? []), scope];
229
+ const tables = {
230
+ ...(prev?.tables ?? {}),
231
+ ...(caps.tables ?? {}),
232
+ };
217
233
  out.set(id, {
218
234
  version: caps.version ?? prev?.version ?? null,
219
235
  declares: caps.declares.length ? [...caps.declares] : (prev?.declares ?? []),
220
236
  intercepts: caps.intercepts.length ? [...caps.intercepts] : (prev?.intercepts ?? []),
237
+ tables,
221
238
  scopes,
222
239
  origin: prev?.origin,
223
240
  });
@@ -243,6 +260,7 @@ async function buildRow(input: {
243
260
  readonly version: string | null;
244
261
  readonly summary: string | null;
245
262
  readonly declares: readonly string[];
263
+ readonly tables: Readonly<Record<string, PluginTable>>;
246
264
  readonly interceptStages: readonly string[];
247
265
  readonly scopes: readonly PluginScopeView[];
248
266
  readonly costs: Readonly<Record<string, HookCostSummary>>;
@@ -286,6 +304,7 @@ async function buildRow(input: {
286
304
  summary: input.summary,
287
305
  scopes: input.scopes,
288
306
  declares: [...input.declares],
307
+ tables: { ...input.tables },
289
308
  intercepts,
290
309
  hookCost: cost
291
310
  ? {
@@ -28,6 +28,7 @@ export interface SignalEndpoint {
28
28
  /** One row in `console.signals.list`. */
29
29
  export interface ConsoleSignalRow {
30
30
  readonly name: string;
31
+ readonly description?: string;
31
32
  readonly delivery: "once" | "broadcast" | "live";
32
33
  readonly retries: number;
33
34
  readonly deadLetterEnabled: boolean;
@@ -112,8 +113,10 @@ export async function projectSignalsList(
112
113
  withCause(dl, cfg.name, options.runs),
113
114
  );
114
115
 
116
+ const description = options.manifest?.signals?.[cfg.name]?.description;
115
117
  rows.push({
116
118
  name: cfg.name,
119
+ ...(description !== undefined ? { description } : {}),
117
120
  delivery: cfg.delivery,
118
121
  retries: stats?.retries ?? cfg.retries,
119
122
  deadLetterEnabled: stats?.deadLetterEnabled ?? cfg.deadLetter,
@@ -142,8 +145,10 @@ export async function projectSignalsList(
142
145
  if (rows.some((r) => r.name === name)) continue;
143
146
  const producers = producersOf(name, flows);
144
147
  const consumers = consumersOf(name, flows);
148
+ const description = options.manifest?.signals?.[name]?.description;
145
149
  rows.push({
146
150
  name,
151
+ ...(description !== undefined ? { description } : {}),
147
152
  delivery: stats.delivery,
148
153
  retries: stats.retries,
149
154
  deadLetterEnabled: stats.deadLetterEnabled,
@@ -130,6 +130,23 @@ describe("willNotFireFor", () => {
130
130
  });
131
131
  });
132
132
 
133
+ describe("index driver resolution", () => {
134
+ test("console manifest runtime uses the shared boot switch (memory default)", async () => {
135
+ const runtime = await createManifestStoreRuntime(MANIFEST);
136
+ const decl = runtime.declarations.get("index:docs");
137
+ expect(decl).toBeDefined();
138
+ const handle = (await runtime.open(decl!, {
139
+ effects: { reads: ["index:docs"], writes: ["index:docs"] },
140
+ })) as import("../../elements/store.ts").VectorIndexStoreFxHandle;
141
+ expect(handle.driverId).toBe("memory");
142
+ await handle.upsert("d1", [1, 0, 0], { t: 1 });
143
+ const hits = await handle.search([1, 0, 0], 1);
144
+ expect(hits[0]?.id).toBe("d1");
145
+ expect(hits[0]?.meta).toEqual({ t: 1 });
146
+ await runtime.close();
147
+ });
148
+ });
149
+
133
150
  describe("PII masking survives SELECT *", () => {
134
151
  test("masks classified columns; reveal returns cleartext", async () => {
135
152
  const runtime = await createManifestStoreRuntime(MANIFEST);