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,79 @@
1
+ /**
2
+ * `pglite` driver — binds `@electric-sql/pglite` (optional peer, dynamic import).
3
+ *
4
+ * Protocol-named transport: PGlite is Postgres compiled to WASM — same wire
5
+ * dialect as `postgres` (`?` params convert to `$n`), different transport.
6
+ * drizzle-kit dialect: `postgresql`.
7
+ *
8
+ * Honest latency trade-off (measured on `@electric-sql/pglite@0.5.4`): first
9
+ * init ~900 ms one-time (WASM instantiation); reopen of an existing datadir
10
+ * ~44 ms; warm CRUD ~45 ms vs ~3 ms for `bun:sqlite` (~15× slower). Choose
11
+ * `pglite` for dialect/pgvector parity on a laptop — never as a
12
+ * latency-competitive default. `local` stays on `sqlite`.
13
+ */
14
+
15
+ import { toPostgresParams } from "./postgres.ts";
16
+ import type { SqlConnectOptions, SqlConnection, SqlDriver, SqlRow } from "./types.ts";
17
+
18
+ /** `@electric-sql/pglite` is an optional peer — loaded only when this driver runs. */
19
+ type PgliteModule = typeof import("@electric-sql/pglite");
20
+ type PgliteVectorModule = typeof import("@electric-sql/pglite-pgvector");
21
+
22
+ async function loadPglite(): Promise<PgliteModule> {
23
+ try {
24
+ return await import("@electric-sql/pglite");
25
+ } catch {
26
+ throw new Error(
27
+ "pglite driver: install optional peer `@electric-sql/pglite` (bun add @electric-sql/pglite)",
28
+ );
29
+ }
30
+ }
31
+
32
+ async function loadPgliteVector(): Promise<PgliteVectorModule> {
33
+ try {
34
+ return await import("@electric-sql/pglite-pgvector");
35
+ } catch {
36
+ throw new Error(
37
+ "pglite driver: install optional peer `@electric-sql/pglite-pgvector` (bun add @electric-sql/pglite-pgvector)",
38
+ );
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Open a PGlite connection implementing {@link SqlConnection}.
44
+ *
45
+ * The pgvector extension is loaded at `PGlite.create` so `CREATE EXTENSION
46
+ * vector` in the pgvector index driver succeeds — same real-ANN code path as
47
+ * `postgres`.
48
+ *
49
+ * @param options - URL (`memory://` or a datadir path; default `.oke/pgdata`) / role
50
+ */
51
+ export async function connectPglite(options: SqlConnectOptions = {}): Promise<SqlConnection> {
52
+ const role = options.role ?? "primary";
53
+ const { PGlite } = await loadPglite();
54
+ const { vector } = await loadPgliteVector();
55
+ const dataDir = options.url && options.url.length > 0 ? options.url : ".oke/pgdata";
56
+ const db = await PGlite.create(dataDir, { extensions: { vector } });
57
+ return {
58
+ driverId: "pglite",
59
+ role,
60
+ async query(sql, params = []) {
61
+ const rs = await db.query<SqlRow>(toPostgresParams(sql), [...params]);
62
+ return rs.rows;
63
+ },
64
+ async exec(sql, params = []) {
65
+ const rs = await db.query<SqlRow>(toPostgresParams(sql), [...params]);
66
+ return { changes: rs.affectedRows ?? 0 };
67
+ },
68
+ async close() {
69
+ await db.close();
70
+ },
71
+ };
72
+ }
73
+
74
+ /** Protocol-named pglite driver. */
75
+ export const pgliteDriver: SqlDriver = {
76
+ id: "pglite",
77
+ facet: "sql",
78
+ connect: connectPglite,
79
+ };
@@ -1,16 +1,24 @@
1
1
  /**
2
2
  * `pgvector` driver — vector index facet over Postgres + pgvector.
3
3
  *
4
+ * Real ANN via drizzle-orm's pg vector API: `vector(dims)` column, HNSW index
5
+ * (`vector_cosine_ops`), `cosineDistance` ordering — no JS-side distance math
6
+ * on the SQL path. One implementation serves both the `postgres` and `pglite`
7
+ * sql drivers (identical wire dialect); the caller injects the already-open
8
+ * {@link SqlConnection} (shared with `store.sql`, never a second connection).
9
+ *
4
10
  * When no SQL connection is provided, falls back to an in-process cosine index
5
11
  * so the conformance suite runs without a live Postgres.
6
12
  */
7
13
 
14
+ import { cosineDistance } from "drizzle-orm";
15
+ import { PgDialect, index as pgIndex, pgTable, text, vector } from "drizzle-orm/pg-core";
8
16
  import type {
9
- IndexDriver,
10
17
  IndexHit,
11
18
  IndexOpenOptions,
12
- IndexStore,
13
19
  SqlConnection,
20
+ VectorIndexDriver,
21
+ VectorIndexStore,
14
22
  } from "./types.ts";
15
23
 
16
24
  /**
@@ -18,7 +26,7 @@ import type {
18
26
  *
19
27
  * @param options - Name / dims / SQL connection
20
28
  */
21
- export async function openPgvectorIndex(options: IndexOpenOptions): Promise<IndexStore> {
29
+ export async function openPgvectorIndex(options: IndexOpenOptions): Promise<VectorIndexStore> {
22
30
  if (options.sql) {
23
31
  return openPgvectorSql(options.name, options.dims, options.sql);
24
32
  }
@@ -26,48 +34,75 @@ export async function openPgvectorIndex(options: IndexOpenOptions): Promise<Inde
26
34
  return openPgvectorMemory(options.dims);
27
35
  }
28
36
 
37
+ const pgDialect = new PgDialect();
38
+
29
39
  async function openPgvectorSql(
30
40
  name: string,
31
41
  dims: number,
32
42
  sql: SqlConnection,
33
- ): Promise<IndexStore> {
34
- const table = `oke_idx_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
43
+ ): Promise<VectorIndexStore> {
44
+ if (sql.driverId !== "postgres" && sql.driverId !== "pglite") {
45
+ throw new Error(
46
+ `pgvector index: needs a postgres/pglite SQL connection, got "${sql.driverId}" — ` +
47
+ `set store.sql to "postgres" or "pglite" so the index shares its connection`,
48
+ );
49
+ }
50
+
51
+ const tableName = `oke_idx_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
52
+ const indexName = `${tableName}_hnsw`;
53
+ const table = pgTable(
54
+ tableName,
55
+ {
56
+ id: text("id").primaryKey(),
57
+ embedding: vector("embedding", { dimensions: dims }).notNull(),
58
+ meta: text("meta"),
59
+ },
60
+ (t) => [pgIndex(indexName).using("hnsw", t.embedding.op("vector_cosine_ops"))],
61
+ );
62
+
63
+ // Extension + DDL — fail loud when pgvector is not installed (never fall back).
64
+ await sql.exec(`CREATE EXTENSION IF NOT EXISTS vector`);
35
65
  await sql.exec(
36
- `CREATE TABLE IF NOT EXISTS "${table}" (id TEXT PRIMARY KEY, embedding TEXT NOT NULL, meta TEXT)`,
66
+ `CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT PRIMARY KEY, embedding vector(${dims}) NOT NULL, meta TEXT)`,
67
+ );
68
+ await sql.exec(
69
+ `CREATE INDEX IF NOT EXISTS "${indexName}" ON "${tableName}" USING hnsw (embedding vector_cosine_ops)`,
37
70
  );
38
71
 
39
72
  return {
40
73
  driverId: "pgvector",
41
- async upsert(id, vector, meta) {
42
- if (vector.length !== dims) {
43
- throw new Error(`vector dims ${vector.length} !== index dims ${dims}`);
74
+ async upsert(id, vec, meta) {
75
+ if (vec.length !== dims) {
76
+ throw new Error(`vector dims ${vec.length} !== index dims ${dims}`);
44
77
  }
45
- await sql.exec(`DELETE FROM "${table}" WHERE id = ?`, [id]);
46
- await sql.exec(`INSERT INTO "${table}" (id, embedding, meta) VALUES (?, ?, ?)`, [
47
- id,
48
- JSON.stringify(vector),
49
- meta ? JSON.stringify(meta) : null,
50
- ]);
78
+ await sql.exec(
79
+ `INSERT INTO "${tableName}" (id, embedding, meta) VALUES (?, ?::vector, ?) ` +
80
+ `ON CONFLICT (id) DO UPDATE SET embedding = excluded.embedding, meta = excluded.meta`,
81
+ [id, JSON.stringify(vec), meta ? JSON.stringify(meta) : null],
82
+ );
51
83
  },
52
- async search(vector, topK = 10): Promise<IndexHit[]> {
53
- const rows = await sql.query(`SELECT id, embedding, meta FROM "${table}"`);
54
- const hits: IndexHit[] = rows.map((row) => {
55
- const emb = JSON.parse(String(row.embedding)) as number[];
84
+ async search(vec, topK = 10): Promise<IndexHit[]> {
85
+ const distance = pgDialect.sqlToQuery(cosineDistance(table.embedding, [...vec]));
86
+ const distanceSql = distance.sql.replace(/\$\d+/g, "?");
87
+ const rows = await sql.query(
88
+ `SELECT "id", "meta", ${distanceSql} AS "distance" FROM "${tableName}" ` +
89
+ `ORDER BY "distance" ASC LIMIT ?`,
90
+ [...distance.params, Math.max(1, Math.floor(topK))],
91
+ );
92
+ return rows.map((row) => {
56
93
  const metaRaw = row.meta;
57
94
  return {
58
95
  id: String(row.id),
59
- score: cosine(vector, emb),
96
+ score: 1 - Number(row.distance),
60
97
  meta:
61
98
  typeof metaRaw === "string"
62
99
  ? (JSON.parse(metaRaw) as Record<string, unknown>)
63
100
  : undefined,
64
101
  };
65
102
  });
66
- hits.sort((a, b) => b.score - a.score);
67
- return hits.slice(0, topK);
68
103
  },
69
104
  async delete(id) {
70
- const result = await sql.exec(`DELETE FROM "${table}" WHERE id = ?`, [id]);
105
+ const result = await sql.exec(`DELETE FROM "${tableName}" WHERE id = ?`, [id]);
71
106
  return result.changes > 0;
72
107
  },
73
108
  async close() {
@@ -76,7 +111,7 @@ async function openPgvectorSql(
76
111
  };
77
112
  }
78
113
 
79
- function openPgvectorMemory(dims: number): IndexStore {
114
+ function openPgvectorMemory(dims: number): VectorIndexStore {
80
115
  const docs = new Map<string, { vector: number[]; meta?: Record<string, unknown> }>();
81
116
  return {
82
117
  driverId: "pgvector",
@@ -119,7 +154,7 @@ function cosine(a: readonly number[], b: readonly number[]): number {
119
154
  }
120
155
 
121
156
  /** Protocol-named pgvector driver. */
122
- export const pgvectorDriver: IndexDriver = {
157
+ export const pgvectorDriver: VectorIndexDriver = {
123
158
  id: "pgvector",
124
159
  facet: "index",
125
160
  open: openPgvectorIndex,
@@ -8,7 +8,17 @@
8
8
  import type { ColumnClassification, StoreFacet } from "../manifest/types.ts";
9
9
 
10
10
  /** Protocol ids for store drivers in scope. */
11
- export type StoreDriverId = "sqlite" | "postgres" | "memory" | "redis" | "fs" | "s3" | "pgvector";
11
+ export type StoreDriverId =
12
+ | "sqlite"
13
+ | "postgres"
14
+ | "libsql"
15
+ | "pglite"
16
+ | "memory"
17
+ | "redis"
18
+ | "fs"
19
+ | "s3"
20
+ | "pgvector"
21
+ | "meilisearch";
12
22
 
13
23
  /** Facets a driver may serve. */
14
24
  export type DriverFacet = StoreFacet;
@@ -37,7 +47,7 @@ export interface SqlConnectOptions {
37
47
  /** Low-level SQL connection used by every sql driver. */
38
48
  export interface SqlConnection {
39
49
  /** Protocol driver id. */
40
- readonly driverId: "sqlite" | "postgres" | "memory";
50
+ readonly driverId: "sqlite" | "postgres" | "libsql" | "pglite" | "memory";
41
51
  /** Primary vs replica. */
42
52
  readonly role: SqlRole;
43
53
  /**
@@ -61,7 +71,7 @@ export interface SqlConnection {
61
71
  /** SQL driver factory. */
62
72
  export interface SqlDriver {
63
73
  /** Protocol id. */
64
- readonly id: "sqlite" | "postgres" | "memory";
74
+ readonly id: "sqlite" | "postgres" | "libsql" | "pglite" | "memory";
65
75
  /** Facet this driver serves. */
66
76
  readonly facet: "sql";
67
77
  /**
@@ -239,16 +249,20 @@ export interface FilesDriver {
239
249
  open(options: FilesOpenOptions): Promise<FilesBucket>;
240
250
  }
241
251
 
242
- /** Vector index hit. */
252
+ /** Index hit — score meaning depends on the driver. */
243
253
  export interface IndexHit {
244
254
  readonly id: string;
255
+ /**
256
+ * Vector drivers: cosine similarity (higher = more similar).
257
+ * Full-text (`meilisearch`): relevance score (`_rankingScore`, higher = more relevant).
258
+ */
245
259
  readonly score: number;
246
260
  readonly meta?: Record<string, unknown>;
247
261
  }
248
262
 
249
- /** Vector index handle. */
250
- export interface IndexStore {
251
- readonly driverId: "memory" | "pgvector";
263
+ /** Vector (ANN) index handle. */
264
+ export interface VectorIndexStore {
265
+ readonly driverId: "memory" | "pgvector" | "libsql";
252
266
  /**
253
267
  * Upsert a vector.
254
268
  *
@@ -263,7 +277,7 @@ export interface IndexStore {
263
277
  * @param vector - Query embedding
264
278
  * @param topK - Max hits
265
279
  */
266
- search(vector: readonly number[], topK?: number): Promise<IndexHit[]>;
280
+ search(vector: readonly number[], topK?: number): Promise<readonly IndexHit[]>;
267
281
  /**
268
282
  * Delete a vector.
269
283
  *
@@ -274,28 +288,104 @@ export interface IndexStore {
274
288
  close(): Promise<void>;
275
289
  }
276
290
 
291
+ /** Full-text search options (meilisearch). */
292
+ export interface TextIndexSearchOptions {
293
+ /** Max hits (Meilisearch `limit`). */
294
+ readonly topK?: number;
295
+ /** Filter expression (attribute must be filterable). */
296
+ readonly filter?: string | string[];
297
+ /** Facet attributes to aggregate. */
298
+ readonly facets?: string[];
299
+ }
300
+
301
+ /** Full-text search result (meilisearch). */
302
+ export interface TextIndexSearchResult {
303
+ readonly hits: readonly IndexHit[];
304
+ /** Facet counts per attribute, when `facets` was requested. */
305
+ readonly facetDistribution?: Record<string, Record<string, number>>;
306
+ }
307
+
308
+ /** Full-text index handle. */
309
+ export interface TextIndexStore {
310
+ readonly driverId: "meilisearch";
311
+ /**
312
+ * Upsert a document.
313
+ *
314
+ * @param id - Document id
315
+ * @param document - Field map (must include the id field)
316
+ */
317
+ upsert(id: string, document: Record<string, unknown>): Promise<void>;
318
+ /**
319
+ * Full-text search.
320
+ *
321
+ * @param q - Query text (typo tolerant)
322
+ * @param opts - topK / filter / facets
323
+ */
324
+ search(q: string, opts?: TextIndexSearchOptions): Promise<TextIndexSearchResult>;
325
+ /**
326
+ * Delete a document.
327
+ *
328
+ * @param id - Document id
329
+ */
330
+ delete(id: string): Promise<boolean>;
331
+ /** Close / release. */
332
+ close(): Promise<void>;
333
+ }
334
+
335
+ /** Index handle — discriminated by `driverId`. */
336
+ export type IndexStore = VectorIndexStore | TextIndexStore;
337
+
277
338
  /** Options when opening an index. */
278
339
  export interface IndexOpenOptions {
279
340
  /** Index name. */
280
341
  readonly name: string;
281
- /** Vector dimensions. */
342
+ /** Vector dimensions (vector drivers only). */
282
343
  readonly dims: number;
283
- /** Postgres URL when using pgvector. */
344
+ /**
345
+ * Vector: SQL URL when the driver shares the sql facet's engine.
346
+ * Meilisearch: base HTTP URL.
347
+ */
284
348
  readonly url?: string;
285
- /** Injected SQL connection for pgvector tests. */
349
+ /** Injected SQL connection shared with `store.sql` at boot (vector only). */
286
350
  readonly sql?: SqlConnection;
351
+ /** Meilisearch API / master key. */
352
+ readonly apiKey?: string;
353
+ /** Injected fetch (tests). */
354
+ readonly fetch?: typeof fetch;
287
355
  }
288
356
 
289
- /** Index driver factory. */
290
- export interface IndexDriver {
291
- readonly id: "memory" | "pgvector";
357
+ /** Vector index driver factory. */
358
+ export interface VectorIndexDriver {
359
+ readonly id: "memory" | "pgvector" | "libsql";
292
360
  readonly facet: "index";
293
361
  /**
294
- * Open an index.
362
+ * Open a vector index.
295
363
  *
296
364
  * @param options - Name / dims / SQL
297
365
  */
298
- open(options: IndexOpenOptions): Promise<IndexStore>;
366
+ open(options: IndexOpenOptions): Promise<VectorIndexStore>;
367
+ }
368
+
369
+ /** Full-text index driver factory. */
370
+ export interface TextIndexDriver {
371
+ readonly id: "meilisearch";
372
+ readonly facet: "index";
373
+ /**
374
+ * Open a full-text index.
375
+ *
376
+ * @param options - Name / HTTP URL / apiKey
377
+ */
378
+ open(options: IndexOpenOptions): Promise<TextIndexStore>;
379
+ }
380
+
381
+ /** Index driver factory — discriminated by `id`. */
382
+ export type IndexDriver = VectorIndexDriver | TextIndexDriver;
383
+
384
+ /** True when the index driver shares the sql facet's engine (never memory). */
385
+ export function indexDriverNeedsSql(
386
+ driver: IndexDriver,
387
+ ): driver is VectorIndexDriver & { id: "pgvector" | "libsql" } {
388
+ return driver.id === "pgvector" || driver.id === "libsql";
299
389
  }
300
390
 
301
391
  /** Union of all store drivers. */
@@ -12,6 +12,8 @@ const ROOT = join(import.meta.dir, "../..");
12
12
 
13
13
  /** Forbidden driver id, split so this file does not match itself. */
14
14
  const FORBIDDEN_DRIVER = ["so", "ps"].join("");
15
+ /** Forbidden vault driver id, split so this file does not match itself. */
16
+ const FORBIDDEN_SECRETS_SAAS = ["infi", "sical"].join("");
15
17
  /** Forbidden optional peer, split so this file does not match itself. */
16
18
  const FORBIDDEN_AGE = ["age", "-", "encryption"].join("");
17
19
 
@@ -49,6 +51,10 @@ describe("removed vault driver gate", () => {
49
51
  assertZeroGitGrep(FORBIDDEN_DRIVER);
50
52
  });
51
53
 
54
+ test("tracked tree has zero matches for the removed secrets-saas driver id", () => {
55
+ assertZeroGitGrep(FORBIDDEN_SECRETS_SAAS, ["changelog.md:"]);
56
+ });
57
+
52
58
  test("tracked tree has zero matches for the removed age peer outside lockfiles", () => {
53
59
  assertZeroGitGrep(FORBIDDEN_AGE, LOCKFILE_PREFIXES);
54
60
  });
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Protocol-named vault driver contracts.
3
3
  *
4
- * Driver ids: `env` · `openbao` · `infisical` · `managed` · `memory`.
5
- * Never vendor names (`neon`, `infisical-cloud`, …) — vendor choice lives in `images`.
4
+ * Driver ids: `env` · `openbao` · `managed` · `memory`.
5
+ * Never vendor names (`neon`, …) — vendor choice lives in `images`.
6
6
  */
7
7
 
8
8
  /** Protocol ids for vault drivers. */
9
- export type VaultDriverId = "env" | "openbao" | "infisical" | "managed" | "memory";
9
+ export type VaultDriverId = "env" | "openbao" | "managed" | "memory";
10
10
 
11
11
  /** Options when opening a vault backend. */
12
12
  export interface VaultOpenOptions {
@@ -16,7 +16,7 @@ export interface VaultOpenOptions {
16
16
  readonly envPrefix?: string;
17
17
  /** Injected env map for tests (defaults to `process.env`). */
18
18
  readonly env?: Readonly<Record<string, string | undefined>>;
19
- /** OpenBao / Infisical / managed base URL. */
19
+ /** OpenBao / managed base URL. */
20
20
  readonly url?: string;
21
21
  /** Token / API key for remote vaults. */
22
22
  readonly token?: string;
@@ -485,6 +485,12 @@ export function createAiRuntime(options: CreateAiRuntimeOptions = {}): AiRuntime
485
485
  if (!index) {
486
486
  throw new Error(`ai: index "${decl.into}" not registered`);
487
487
  }
488
+ if (index.driverId === "meilisearch") {
489
+ throw new Error(
490
+ `ai: embed into "${decl.into}" needs a vector index (memory/pgvector/libsql) — ` +
491
+ `"${index.driverId}" is full-text; embeddings don't apply`,
492
+ );
493
+ }
488
494
  const modelName = decl.model ?? [...models.keys()][0] ?? "mock";
489
495
  const client = await clientFor(modelName);
490
496
  if (!client.embed) {
@@ -263,6 +263,28 @@ describe("embeddings into store.index", () => {
263
263
  const hits = await index.search((await client.embed!({ input: "hello world" })).vectors[0]!, 1);
264
264
  expect(hits[0]?.id).toBe("doc-1");
265
265
  });
266
+
267
+ test("embed into a meilisearch (full-text) index fails loud", async () => {
268
+ const smart = ai.model("smart", { provider: "mock" });
269
+ const embed = ai.embed("docs", { model: smart, into: "kb" });
270
+ const client = await mockAiDriver.open({ model: "mock" });
271
+ const textIndex = {
272
+ driverId: "meilisearch" as const,
273
+ upsert: async () => {},
274
+ search: async () => ({ hits: [] }),
275
+ delete: async () => true,
276
+ close: async () => {},
277
+ };
278
+ const runtime = createAiRuntime({
279
+ models: [smart],
280
+ embeds: [embed],
281
+ clients: { smart: client },
282
+ indexes: { kb: textIndex as never },
283
+ });
284
+ await expect(runtime.embed("docs", "doc-1", "hello world")).rejects.toThrow(
285
+ /needs a vector index/,
286
+ );
287
+ });
266
288
  });
267
289
 
268
290
  describe("model fallback chain", () => {
@@ -14,6 +14,8 @@ export interface ChannelMediumOptions {
14
14
 
15
15
  /** Options for {@link channel.template} / medium `.template`. */
16
16
  export interface ChannelTemplateOptions {
17
+ /** Optional human description for Console / docs (falls back to the template name). */
18
+ readonly description?: string;
17
19
  readonly medium?: ChannelMedium;
18
20
  readonly locales?: string[];
19
21
  readonly schema?: unknown;
@@ -24,6 +26,7 @@ export interface ChannelTemplateOptions {
24
26
  export interface ChannelTemplateDecl {
25
27
  readonly kind: "template";
26
28
  readonly name: string;
29
+ readonly description?: string;
27
30
  readonly medium: ChannelMedium;
28
31
  readonly locales?: string[];
29
32
  readonly schema?: unknown;
@@ -65,6 +68,7 @@ function mediumBinder(
65
68
  kind: "template",
66
69
  name,
67
70
  medium,
71
+ ...(opts.description !== undefined ? { description: opts.description } : {}),
68
72
  ...(opts.locales ? { locales: opts.locales } : {}),
69
73
  ...(opts.schema !== undefined ? { schema: opts.schema } : {}),
70
74
  ...(from !== undefined ? { from } : {}),
@@ -110,6 +114,7 @@ export const channel: ChannelNamespace = {
110
114
  kind: "template",
111
115
  name,
112
116
  medium: options.medium ?? "email",
117
+ ...(options.description !== undefined ? { description: options.description } : {}),
113
118
  ...(options.locales ? { locales: options.locales } : {}),
114
119
  ...(options.schema !== undefined ? { schema: options.schema } : {}),
115
120
  ...(options.from !== undefined ? { from: options.from } : {}),
@@ -18,6 +18,8 @@ export interface ClockOptions {
18
18
  * Without it, no override is possible (console §4.1).
19
19
  */
20
20
  readonly overridable?: boolean;
21
+ /** Optional human description for Console / docs (falls back to the clock name). */
22
+ readonly description?: string;
21
23
  }
22
24
 
23
25
  /**
@@ -34,6 +36,8 @@ export interface ClockDecl {
34
36
  readonly timezone: string;
35
37
  /** Whether Console override is allowed. */
36
38
  readonly overridable: boolean;
39
+ /** Optional human description. */
40
+ readonly description?: string;
37
41
  }
38
42
 
39
43
  /**
@@ -52,5 +56,6 @@ export function clock(name: string, options: ClockOptions = {}): ClockDecl {
52
56
  every: options.every,
53
57
  timezone: options.timezone ?? "UTC",
54
58
  overridable: options.overridable ?? false,
59
+ ...(options.description !== undefined ? { description: options.description } : {}),
55
60
  };
56
61
  }
@@ -6,6 +6,7 @@
6
6
  * step never re-runs (four-applications · Provisions).
7
7
  */
8
8
 
9
+ import { fxRetry } from "../../kernel/concurrency.ts";
9
10
  import type { AnyFlowDef } from "../../kernel/flow.ts";
10
11
  import { createFxContext, type CreateFxOptions, type Fx } from "../../kernel/fx.ts";
11
12
  import {
@@ -95,7 +96,12 @@ export async function runDurable<O = unknown>(
95
96
  });
96
97
 
97
98
  try {
98
- const output = await options.flow.do(options.input as never, fx);
99
+ const run = () => {
100
+ // Same journal session across attempts — rewind so completed steps replay.
101
+ session.rewind();
102
+ return options.flow.do(options.input as never, fx);
103
+ };
104
+ const output = await (options.flow.retry ? fxRetry(run, options.flow.retry) : run());
99
105
  await session.commit("completed", { output });
100
106
  return {
101
107
  status: "completed",
@@ -45,6 +45,16 @@ export interface RateOptions {
45
45
  * Without it, no override is possible (console §4.1).
46
46
  */
47
47
  readonly overridable?: boolean;
48
+ /** Optional human description for Console / docs (falls back to the rate name). */
49
+ readonly description?: string;
50
+ }
51
+
52
+ /** Options object form of {@link gate.policy} (keeps the two-arg check form valid). */
53
+ export interface PolicyGateOptions {
54
+ /** Predicate over {@link GatePolicyContext}. */
55
+ readonly check: (ctx: GatePolicyContext) => boolean | Promise<boolean>;
56
+ /** Optional human description for Console / docs (falls back to the policy name). */
57
+ readonly description?: string;
48
58
  }
49
59
 
50
60
  /** Policy gate declaration. */
@@ -52,6 +62,7 @@ export interface PolicyGateDecl {
52
62
  readonly kind: "policy";
53
63
  readonly name: string;
54
64
  readonly check: (ctx: GatePolicyContext) => boolean | Promise<boolean>;
65
+ readonly description?: string;
55
66
  }
56
67
 
57
68
  /** Rate gate declaration. */
@@ -63,6 +74,7 @@ export interface RateGateDecl {
63
74
  readonly per: string;
64
75
  readonly keyBy?: string;
65
76
  readonly overridable: boolean;
77
+ readonly description?: string;
66
78
  }
67
79
 
68
80
  /** Declared gate handle. */
@@ -76,11 +88,11 @@ export interface GateNamespace {
76
88
  * Declare a named ABAC / auth policy.
77
89
  *
78
90
  * @param name - Policy id (also a Module:Action when it contains `:`)
79
- * @param check - Predicate over {@link GatePolicyContext}
91
+ * @param checkOrOptions - Predicate, or `{ check, description? }`
80
92
  */
81
93
  policy(
82
94
  name: string,
83
- check: (ctx: GatePolicyContext) => boolean | Promise<boolean>,
95
+ checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
84
96
  ): PolicyGateDecl;
85
97
  /**
86
98
  * Declare a rate limit (atomic Lua on the kv driver).
@@ -98,13 +110,23 @@ export const gate: GateNamespace = {
98
110
  * Declare a named ABAC / auth policy.
99
111
  *
100
112
  * @param name - Policy id (also a Module:Action when it contains `:`)
101
- * @param check - Predicate over {@link GatePolicyContext}
113
+ * @param checkOrOptions - Predicate, or `{ check, description? }`
102
114
  */
103
115
  policy(
104
116
  name: string,
105
- check: (ctx: GatePolicyContext) => boolean | Promise<boolean>,
117
+ checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
106
118
  ): PolicyGateDecl {
107
- return { kind: "policy", name, check };
119
+ if (typeof checkOrOptions === "function") {
120
+ return { kind: "policy", name, check: checkOrOptions };
121
+ }
122
+ return {
123
+ kind: "policy",
124
+ name,
125
+ check: checkOrOptions.check,
126
+ ...(checkOrOptions.description !== undefined
127
+ ? { description: checkOrOptions.description }
128
+ : {}),
129
+ };
108
130
  },
109
131
 
110
132
  /**
@@ -129,6 +151,7 @@ export const gate: GateNamespace = {
129
151
  per: options.per,
130
152
  keyBy: options.keyBy,
131
153
  overridable: options.overridable ?? false,
154
+ ...(options.description !== undefined ? { description: options.description } : {}),
132
155
  };
133
156
  },
134
157
  };
@@ -13,6 +13,7 @@ export type {
13
13
  GateDecl,
14
14
  GatePolicyContext,
15
15
  PolicyGateDecl,
16
+ PolicyGateOptions,
16
17
  RateGateDecl,
17
18
  RateOptions,
18
19
  } from "./gate/declare.ts";