okengine 0.3.5 → 0.3.6

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 (98) hide show
  1. package/AGENTS.md +4 -0
  2. package/manifest.v1.schema.json +43 -1
  3. package/package.json +16 -1
  4. package/site/content/docs/ai/skills.mdx +9 -7
  5. package/site/content/docs/console/vault.mdx +4 -0
  6. package/site/content/docs/elements/channel.mdx +5 -4
  7. package/site/content/docs/elements/clock.mdx +1 -0
  8. package/site/content/docs/elements/flow.mdx +15 -0
  9. package/site/content/docs/elements/gate.mdx +16 -11
  10. package/site/content/docs/elements/signal.mdx +8 -7
  11. package/site/content/docs/elements/store.mdx +48 -8
  12. package/site/content/docs/elements/vault.mdx +1 -1
  13. package/site/content/docs/plugins/ip-allowlist.mdx +2 -2
  14. package/site/content/docs/reference/configuration.mdx +2 -2
  15. package/site/content/docs/reference/environment-variables.mdx +8 -0
  16. package/site/content/docs/reference/fx.mdx +40 -0
  17. package/site/content/docs/reference/plugins.mdx +18 -18
  18. package/src/compiler/extract.test.ts +63 -0
  19. package/src/compiler/extract.ts +36 -15
  20. package/src/console/server/channels.ts +2 -0
  21. package/src/console/server/clock.ts +3 -0
  22. package/src/console/server/flows.ts +13 -0
  23. package/src/console/server/gates.ts +2 -0
  24. package/src/console/server/plugins.ts +20 -1
  25. package/src/console/server/signals.ts +5 -0
  26. package/src/console/server/store.test.ts +17 -0
  27. package/src/console/server/store.ts +24 -0
  28. package/src/console/ui/channels/types.ts +1 -0
  29. package/src/console/ui/clock/types.ts +1 -0
  30. package/src/console/ui/display.test.ts +14 -0
  31. package/src/console/ui/display.ts +9 -0
  32. package/src/console/ui/dist/assets/{index-BWo8R7NR.js → index-CrKMmO__.js} +2 -2
  33. package/src/console/ui/dist/assets/panel-channels-DCDd4WAC.js +1 -0
  34. package/src/console/ui/dist/assets/panel-clock-DjGGFPzr.js +1 -0
  35. package/src/console/ui/dist/assets/panel-gates-B5eTE8XH.js +1 -0
  36. package/src/console/ui/dist/assets/{panel-overview-BznEOTnb.js → panel-overview-BsFvDdts.js} +1 -1
  37. package/src/console/ui/dist/assets/panel-plugins-Cj7DK1er.js +1 -0
  38. package/src/console/ui/dist/assets/{panel-runs-CGWNHLR4.js → panel-runs-C0gmnoYL.js} +1 -1
  39. package/src/console/ui/dist/assets/panel-signals-whmDXIg3.js +1 -0
  40. package/src/console/ui/dist/assets/panel-store-CEMHLvaw.js +1 -0
  41. package/src/console/ui/dist/assets/{panel-traces-DBLx2ilD.js → panel-traces-BDiAuVSK.js} +1 -1
  42. package/src/console/ui/dist/assets/panel-vault-C9wjbki8.js +1 -0
  43. package/src/console/ui/dist/index.html +1 -1
  44. package/src/console/ui/gates/types.ts +1 -0
  45. package/src/console/ui/plugins/fixture.ts +7 -0
  46. package/src/console/ui/plugins/types.ts +3 -0
  47. package/src/console/ui/shell/client.ts +3 -0
  48. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +7 -2
  49. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +9 -2
  50. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +12 -5
  51. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +18 -4
  52. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +13 -2
  53. package/src/console/ui/shell/panels/store/StorePanel.tsx +11 -4
  54. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +9 -3
  55. package/src/console/ui/signals/types.ts +1 -0
  56. package/src/console/ui/store/fixture.ts +5 -0
  57. package/src/console/ui/store/types.ts +2 -0
  58. package/src/drivers/conformance.test.ts +11 -0
  59. package/src/drivers/drizzle-dialect.test.ts +4 -0
  60. package/src/drivers/drizzle-dialect.ts +8 -4
  61. package/src/drivers/index.ts +4 -0
  62. package/src/drivers/libsql.ts +179 -0
  63. package/src/drivers/pglite.ts +79 -0
  64. package/src/drivers/pgvector.ts +54 -19
  65. package/src/drivers/types.ts +16 -7
  66. package/src/elements/channel/declare.ts +5 -0
  67. package/src/elements/clock/declare.ts +5 -0
  68. package/src/elements/clock/durable.ts +7 -1
  69. package/src/elements/gate/declare.ts +28 -5
  70. package/src/elements/gate.ts +1 -0
  71. package/src/elements/signal/declare.ts +5 -0
  72. package/src/elements/store/declare.ts +10 -2
  73. package/src/elements/store/index-boot.test.ts +257 -0
  74. package/src/elements/store/runtime.ts +67 -9
  75. package/src/elements/store/schema-decl.ts +7 -0
  76. package/src/kernel/abort-scope.ts +116 -0
  77. package/src/kernel/app.ts +12 -2
  78. package/src/kernel/boot-bind/store.test.ts +59 -1
  79. package/src/kernel/boot-bind/store.ts +64 -2
  80. package/src/kernel/concurrency.test.ts +236 -0
  81. package/src/kernel/concurrency.ts +172 -0
  82. package/src/kernel/flow.ts +9 -0
  83. package/src/kernel/fx.test.ts +12 -0
  84. package/src/kernel/fx.ts +44 -0
  85. package/src/kernel/index.ts +14 -0
  86. package/src/kernel/journal.ts +9 -0
  87. package/src/kernel/plugin/capabilities.test.ts +18 -0
  88. package/src/kernel/plugin.ts +11 -3
  89. package/src/kernel/registry.ts +29 -6
  90. package/src/manifest/types.ts +21 -0
  91. package/src/release/measure.ts +4 -0
  92. package/src/console/ui/dist/assets/panel-channels-BOmQ-onL.js +0 -1
  93. package/src/console/ui/dist/assets/panel-clock-giAq0Ccv.js +0 -1
  94. package/src/console/ui/dist/assets/panel-gates-XclZxWD5.js +0 -1
  95. package/src/console/ui/dist/assets/panel-plugins-CcGM1g64.js +0 -1
  96. package/src/console/ui/dist/assets/panel-signals-CNywkdak.js +0 -1
  97. package/src/console/ui/dist/assets/panel-store-KmTbFHMH.js +0 -1
  98. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +0 -1
@@ -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
  }
@@ -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").IndexStoreFxHandle;
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);
@@ -69,6 +69,8 @@ export interface ConsoleStoreChild {
69
69
  readonly cache: ConsoleStoreCacheView;
70
70
  readonly willNotFire: ConsoleWillNotFire;
71
71
  readonly piiColumns: readonly string[];
72
+ /** Column key → optional human description (SQL tables). */
73
+ readonly columnDescriptions: Readonly<Record<string, string>>;
72
74
  }
73
75
 
74
76
  /** One row in `console.store.list`. */
@@ -76,6 +78,7 @@ export interface ConsoleStoreRow {
76
78
  readonly ref: ResourceRef;
77
79
  readonly facet: StoreFacet;
78
80
  readonly name: string;
81
+ readonly description?: string;
79
82
  readonly children: readonly ConsoleStoreChild[];
80
83
  readonly replicaLagMs: number | null;
81
84
  readonly migrationDrift: ConsoleMigrationDrift | null;
@@ -152,6 +155,7 @@ export async function projectStoresList(options: ProjectStoresOptions): Promise<
152
155
  ref,
153
156
  facet,
154
157
  name,
158
+ ...(store.description !== undefined ? { description: store.description } : {}),
155
159
  children,
156
160
  replicaLagMs,
157
161
  migrationDrift: facet === "sql" ? drift : null,
@@ -219,10 +223,27 @@ function childrenOf(
219
223
  },
220
224
  willNotFire,
221
225
  piiColumns,
226
+ columnDescriptions: columnDescriptionsFor(store, childName),
222
227
  };
223
228
  });
224
229
  }
225
230
 
231
+ function columnDescriptionsFor(
232
+ store: NonNullable<Manifest["stores"]>[string],
233
+ tableName: string,
234
+ ): Readonly<Record<string, string>> {
235
+ const cols = store.tables?.[tableName]?.columns;
236
+ if (!cols) return {};
237
+ const out: Record<string, string> = {};
238
+ for (const [key, col] of Object.entries(cols)) {
239
+ if (col && typeof col === "object" && "description" in col) {
240
+ const d = (col as { description?: string }).description;
241
+ if (typeof d === "string" && d.length > 0) out[key] = d;
242
+ }
243
+ }
244
+ return out;
245
+ }
246
+
226
247
  function flowsTouching(manifest: Manifest, ref: ResourceRef, kind: "reads" | "writes"): string[] {
227
248
  const out: string[] = [];
228
249
  for (const [flowId, flow] of Object.entries(manifest.flows ?? {})) {
@@ -779,6 +800,9 @@ export function cacheKeyInvalidatedBy(key: string, writeRef: ResourceRef): boole
779
800
  /**
780
801
  * Open a memory StoreRuntime seeded from Manifest stores (Console default).
781
802
  *
803
+ * The Console Manifest sandbox uses memory drivers for every facet. Real app
804
+ * boot resolves configured drivers before the Console binds to live runtimes.
805
+ *
782
806
  * @param manifest - Manifest snapshot
783
807
  * @param now - Clock
784
808
  */
@@ -29,6 +29,7 @@ export interface OutcomeRow {
29
29
  /** Template row. */
30
30
  export interface ChannelTemplate {
31
31
  readonly name: string;
32
+ readonly description?: string;
32
33
  readonly medium: string;
33
34
  readonly locales: readonly string[];
34
35
  readonly from: string | null;
@@ -29,6 +29,7 @@ export interface DstAmbiguityView {
29
29
  /** One cron row from `console.clock.list`. */
30
30
  export interface ClockCronRecord {
31
31
  readonly name: string;
32
+ readonly description?: string;
32
33
  readonly status: CronStatus;
33
34
  readonly timezone: string;
34
35
  readonly overridable: boolean;
@@ -0,0 +1,14 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { displayLabel } from "./display.ts";
3
+
4
+ describe("displayLabel", () => {
5
+ test("falls back to the raw key when description is absent", () => {
6
+ expect(displayLabel("DATABASE_URL")).toBe("DATABASE_URL");
7
+ expect(displayLabel("DATABASE_URL", undefined)).toBe("DATABASE_URL");
8
+ expect(displayLabel("DATABASE_URL", "")).toBe("DATABASE_URL");
9
+ });
10
+
11
+ test("prefers a non-empty description", () => {
12
+ expect(displayLabel("DATABASE_URL", "Primary database URL")).toBe("Primary database URL");
13
+ });
14
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Human-facing label for a technical key when an optional description exists.
3
+ *
4
+ * @param key - Technical identifier (Manifest map key / declaration name)
5
+ * @param description - Optional human description
6
+ */
7
+ export function displayLabel(key: string, description?: string | null): string {
8
+ return description && description.length > 0 ? description : key;
9
+ }