@nylorun/runtime 0.14.0-beta → 0.15.0-beta

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 (198) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +75 -33
  3. package/dist/adapters/execution/restate.js +2 -2
  4. package/dist/adapters/replication/pgoutput.d.ts +5 -0
  5. package/dist/adapters/replication/pgoutput.js +4 -3
  6. package/dist/admin-openapi.json +184 -685
  7. package/dist/api/components.d.ts +308 -68
  8. package/dist/api/components.js +4 -5
  9. package/dist/api/gate/keys.d.ts +14 -0
  10. package/dist/api/gate/keys.js +59 -0
  11. package/dist/api/gate/routes.d.ts +34 -0
  12. package/dist/api/gate/routes.js +235 -0
  13. package/dist/api/http/define.js +0 -8
  14. package/dist/api/http/routes/access.js +25 -18
  15. package/dist/api/http/routes/tenant.d.ts +2 -1
  16. package/dist/api/http/routes/tenant.js +77 -5
  17. package/dist/api/http/routes/vaults.js +2 -2
  18. package/dist/api/openapi.js +4 -5
  19. package/dist/gates/contract.d.ts +51 -0
  20. package/dist/gates/contract.js +37 -0
  21. package/dist/gates/handler.d.ts +21 -0
  22. package/dist/gates/handler.js +55 -0
  23. package/dist/gates/http-client.d.ts +12 -0
  24. package/dist/gates/http-client.js +160 -0
  25. package/dist/gates/in-process.d.ts +32 -0
  26. package/dist/gates/in-process.js +42 -0
  27. package/dist/gates/inflight.d.ts +39 -0
  28. package/dist/gates/inflight.js +70 -0
  29. package/dist/gates/mcp-handler.d.ts +35 -0
  30. package/dist/gates/mcp-handler.js +142 -0
  31. package/dist/gates/meter.d.ts +24 -0
  32. package/dist/gates/meter.js +165 -0
  33. package/dist/gates/model-gate.d.ts +45 -0
  34. package/dist/gates/model-gate.js +1 -0
  35. package/dist/gates/tenant-vaults.d.ts +64 -0
  36. package/dist/gates/tenant-vaults.js +94 -0
  37. package/dist/gates/tool-calls.d.ts +51 -0
  38. package/dist/gates/tool-calls.js +64 -0
  39. package/dist/gates/tool-client.d.ts +12 -0
  40. package/dist/gates/tool-client.js +201 -0
  41. package/dist/gates/tool-contract.d.ts +116 -0
  42. package/dist/gates/tool-contract.js +64 -0
  43. package/dist/gates/tool-gate.d.ts +45 -0
  44. package/dist/gates/tool-gate.js +10 -0
  45. package/dist/host/admin-api.d.ts +7 -6
  46. package/dist/host/admin-api.js +9 -113
  47. package/dist/host/app.d.ts +1 -1
  48. package/dist/host/app.js +20 -24
  49. package/dist/host/config-for.d.ts +1 -1
  50. package/dist/host/config-for.js +2 -2
  51. package/dist/host/create-host.d.ts +5 -5
  52. package/dist/host/create-host.js +5 -15
  53. package/dist/host/execution.d.ts +20 -18
  54. package/dist/host/execution.js +2 -2
  55. package/dist/host/gates.d.ts +58 -0
  56. package/dist/host/gates.js +127 -0
  57. package/dist/host/http.d.ts +6 -1
  58. package/dist/host/http.js +17 -0
  59. package/dist/host/index.d.ts +1 -1
  60. package/dist/host/main.js +151 -71
  61. package/dist/host/stack-config.d.ts +71 -19
  62. package/dist/host/stack-config.js +192 -18
  63. package/dist/infra/database.d.ts +2 -2
  64. package/dist/infra/execution.d.ts +5 -5
  65. package/dist/infra/execution.js +8 -6
  66. package/dist/infra/index.d.ts +1 -1
  67. package/dist/infra/workers.d.ts +17 -17
  68. package/dist/infra/workers.js +9 -9
  69. package/dist/keys/client.d.ts +9 -0
  70. package/dist/keys/client.js +70 -0
  71. package/dist/keys/contract.d.ts +24 -0
  72. package/dist/keys/contract.js +12 -0
  73. package/dist/keys/keys.d.ts +41 -0
  74. package/dist/keys/keys.js +50 -0
  75. package/dist/mcp/connect.d.ts +43 -3
  76. package/dist/mcp/connect.js +12 -5
  77. package/dist/mcp/pool.d.ts +21 -3
  78. package/dist/mcp/pool.js +15 -2
  79. package/dist/openapi.json +887 -694
  80. package/dist/record/append.d.ts +51 -0
  81. package/dist/record/append.js +20 -0
  82. package/dist/{store/event.js → record/envelope.js} +1 -1
  83. package/dist/record/index.d.ts +11 -0
  84. package/dist/record/index.js +11 -0
  85. package/dist/store/postgres/connect.d.ts +15 -12
  86. package/dist/store/postgres/connect.js +24 -12
  87. package/dist/store/postgres/db.d.ts +16 -0
  88. package/dist/store/postgres/db.js +28 -0
  89. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  90. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  91. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  92. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  93. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  94. package/dist/store/postgres/migrate.d.ts +68 -0
  95. package/dist/store/postgres/migrate.js +183 -0
  96. package/dist/store/postgres/record-writer.d.ts +4 -0
  97. package/dist/store/postgres/record-writer.js +34 -0
  98. package/dist/store/postgres/record.d.ts +2 -8
  99. package/dist/store/postgres/record.js +50 -54
  100. package/dist/store/postgres/schema.d.ts +3096 -0
  101. package/dist/store/postgres/schema.js +363 -0
  102. package/dist/store/postgres/store.d.ts +2 -4
  103. package/dist/store/postgres/store.js +481 -567
  104. package/dist/store/postgres/tenant.d.ts +56 -0
  105. package/dist/store/postgres/tenant.js +154 -0
  106. package/dist/store/types.d.ts +43 -87
  107. package/dist/store/types.js +0 -1
  108. package/dist/streams/control.d.ts +1 -1
  109. package/dist/streams/control.js +6 -2
  110. package/dist/streams/relay/types.d.ts +2 -1
  111. package/dist/streams/relay/types.js +2 -1
  112. package/dist/streams/types.d.ts +5 -0
  113. package/dist/tenant/advance.d.ts +7 -2
  114. package/dist/tenant/advance.js +95 -16
  115. package/dist/tenant/cause.d.ts +25 -0
  116. package/dist/tenant/cause.js +45 -0
  117. package/dist/tenant/commands.js +4 -2
  118. package/dist/tenant/context.d.ts +26 -3
  119. package/dist/tenant/delivery-token.js +17 -18
  120. package/dist/tenant/delivery.d.ts +1 -1
  121. package/dist/tenant/delivery.js +18 -13
  122. package/dist/tenant/effects.d.ts +15 -1
  123. package/dist/tenant/effects.js +93 -17
  124. package/dist/tenant/ephemeral.d.ts +16 -7
  125. package/dist/tenant/ephemeral.js +45 -111
  126. package/dist/tenant/history.d.ts +64 -0
  127. package/dist/tenant/history.js +120 -0
  128. package/dist/tenant/model-setting.d.ts +2 -2
  129. package/dist/tenant/module.d.ts +14 -28
  130. package/dist/tenant/module.js +131 -271
  131. package/dist/tenant/paths.d.ts +11 -16
  132. package/dist/tenant/paths.js +24 -68
  133. package/dist/tenant/pool.d.ts +1 -1
  134. package/dist/tenant/pool.js +1 -1
  135. package/dist/tenant/principals.d.ts +23 -11
  136. package/dist/tenant/principals.js +61 -49
  137. package/dist/tenant/runtime.d.ts +19 -1
  138. package/dist/tenant/runtime.js +40 -26
  139. package/dist/tenant/scheduler.d.ts +4 -2
  140. package/dist/tenant/scheduler.js +4 -4
  141. package/dist/tenant/session-streams.d.ts +2 -0
  142. package/dist/tenant/session-streams.js +8 -3
  143. package/dist/tenant/status.d.ts +3 -0
  144. package/dist/tenant/status.js +1 -1
  145. package/dist/tenant/store-pg.d.ts +9 -6
  146. package/dist/tenant/store-pg.js +60 -125
  147. package/dist/tenant/streams.d.ts +9 -25
  148. package/dist/tenant/streams.js +7 -22
  149. package/dist/tenant/tokens.js +16 -14
  150. package/dist/tenant/types.d.ts +41 -92
  151. package/dist/tenant/types.js +4 -15
  152. package/dist/tenant/worker.js +4 -4
  153. package/dist/vault/host-model.d.ts +31 -0
  154. package/dist/vault/host-model.js +57 -0
  155. package/dist/vault/service.d.ts +26 -15
  156. package/dist/vault/service.js +70 -109
  157. package/dist/version.d.ts +1 -1
  158. package/dist/version.js +1 -1
  159. package/package.json +9 -5
  160. package/dist/store/memory.d.ts +0 -69
  161. package/dist/store/memory.js +0 -810
  162. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  163. package/dist/store/postgres/migrations/001_initial.js +0 -196
  164. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  165. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  166. package/dist/store/postgres/migrations/003_subject_tokens.d.ts +0 -8
  167. package/dist/store/postgres/migrations/003_subject_tokens.js +0 -42
  168. package/dist/store/postgres/migrations/004_publishable_keys.d.ts +0 -6
  169. package/dist/store/postgres/migrations/004_publishable_keys.js +0 -18
  170. package/dist/store/postgres/migrations/005_action_endpoints.d.ts +0 -6
  171. package/dist/store/postgres/migrations/005_action_endpoints.js +0 -32
  172. package/dist/store/postgres/migrations/006_drop_executors.d.ts +0 -11
  173. package/dist/store/postgres/migrations/006_drop_executors.js +0 -32
  174. package/dist/store/postgres/migrations/007_event_time.d.ts +0 -7
  175. package/dist/store/postgres/migrations/007_event_time.js +0 -14
  176. package/dist/store/postgres/migrations/008_durable_streams.d.ts +0 -13
  177. package/dist/store/postgres/migrations/008_durable_streams.js +0 -35
  178. package/dist/store/postgres/migrations/index.d.ts +0 -53
  179. package/dist/store/postgres/migrations/index.js +0 -96
  180. package/dist/store/postgres/migrations/shared/index.d.ts +0 -31
  181. package/dist/store/postgres/migrations/shared/index.js +0 -92
  182. package/dist/store/postgres/names.d.ts +0 -11
  183. package/dist/store/postgres/names.js +0 -42
  184. package/dist/store/postgres/tenants.d.ts +0 -90
  185. package/dist/store/postgres/tenants.js +0 -228
  186. package/dist/streams/relay/memory.d.ts +0 -69
  187. package/dist/streams/relay/memory.js +0 -176
  188. package/dist/tenant/envelope.d.ts +0 -7
  189. package/dist/tenant/envelope.js +0 -22
  190. package/dist/tenant/quarantine-error.d.ts +0 -6
  191. package/dist/tenant/quarantine-error.js +0 -9
  192. package/dist/tenant/quarantine.d.ts +0 -27
  193. package/dist/tenant/quarantine.js +0 -72
  194. package/dist/tenant/store-memory.d.ts +0 -16
  195. package/dist/tenant/store-memory.js +0 -111
  196. /package/dist/{store → record}/cursor.d.ts +0 -0
  197. /package/dist/{store → record}/cursor.js +0 -0
  198. /package/dist/{store/event.d.ts → record/envelope.d.ts} +0 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The only write path into the record (Durable Streams §6, blueprint D27): the session's log
3
+ * head allocates the next sequence, the event is built on the envelope and checked against the
4
+ * catalog, and its row is written, both in the caller's transaction. The database holds one
5
+ * Tenant, so rows are keyed by session alone; the Tenant id is the caller's and goes into the
6
+ * envelope only. The caller holds the session row lock, which orders the session's appends,
7
+ * and passes the epoch it read under that lock.
8
+ *
9
+ * The two statements run through `RecordWriter`, which the store implements on its
10
+ * transaction (`store/postgres/record-writer.ts`): the database driver stays behind
11
+ * `store/postgres/` (session-store.md §3), and this module decides what is written.
12
+ */
13
+ import type { EventPayload, EventType, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
14
+ /** The record's two statements, in the caller's transaction. */
15
+ export interface RecordWriter {
16
+ /**
17
+ * Advances the session's log head: the seq its next event takes, and the basin generation
18
+ * its events go to (a session's first event takes the Tenant's current one).
19
+ */
20
+ advance(sessionId: string): Promise<{
21
+ seq: number;
22
+ generation: number;
23
+ }>;
24
+ /** Writes the event's row. */
25
+ insert(row: {
26
+ sessionId: string;
27
+ seq: number;
28
+ generation: number;
29
+ type: string;
30
+ body: LiveEvent;
31
+ }): Promise<void>;
32
+ }
33
+ export interface AppendInput<T extends EventType> {
34
+ /** The database's one Tenant, for the envelope. */
35
+ tenantId: string;
36
+ sessionId: string;
37
+ turnId: string | null;
38
+ /** The session's ownership epoch, read under the caller's session row lock. */
39
+ epoch: number;
40
+ time: Date;
41
+ type: T;
42
+ payload: EventPayload<T>;
43
+ }
44
+ /**
45
+ * Appends one event through `writer`. An event the catalog does not describe throws
46
+ * `InvalidEventError` before its row is written; the caller's transaction then rolls back.
47
+ */
48
+ export declare function appendEvent<T extends EventType>(writer: RecordWriter, input: AppendInput<T>): Promise<{
49
+ event: SessionEventOf<T>;
50
+ generation: number;
51
+ }>;
@@ -0,0 +1,20 @@
1
+ import { buildEvent } from "./envelope.js";
2
+ /**
3
+ * Appends one event through `writer`. An event the catalog does not describe throws
4
+ * `InvalidEventError` before its row is written; the caller's transaction then rolls back.
5
+ */
6
+ export async function appendEvent(writer, input) {
7
+ const { seq, generation } = await writer.advance(input.sessionId);
8
+ const event = buildEvent({
9
+ tenantId: input.tenantId,
10
+ sessionId: input.sessionId,
11
+ turnId: input.turnId,
12
+ seq,
13
+ epoch: input.epoch,
14
+ time: input.time,
15
+ type: input.type,
16
+ payload: input.payload,
17
+ });
18
+ await writer.insert({ sessionId: input.sessionId, seq, generation, type: input.type, body: event });
19
+ return { event, generation };
20
+ }
@@ -37,7 +37,7 @@ export function buildEvent(input) {
37
37
  schemaVersion: entry.version,
38
38
  source: { kind: entry.source, id: "runtime" },
39
39
  evidence: "observed",
40
- visibility: "public",
40
+ visibility: entry.visibility ?? "public",
41
41
  retention: "full",
42
42
  type,
43
43
  payload: input.payload,
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The record module (blueprint D27): the one write path into the record. It builds each event
3
+ * on the `nylorun.event/2` envelope, checks it against the event catalog, and holds the only
4
+ * insert into `session_events` and `session_log_heads`, whose Postgres statements it runs
5
+ * through the store's `RecordWriter` (`store/postgres/record-writer.ts`;
6
+ * `scripts/check-boundaries.mjs` refuses an insert anywhere else). Other folders import this
7
+ * file, never the module's own files.
8
+ */
9
+ export { buildEvent, InvalidEventError, type EventInput } from "./envelope.js";
10
+ export { decodeCursor, encodeCursor } from "./cursor.js";
11
+ export { appendEvent, type AppendInput, type RecordWriter } from "./append.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The record module (blueprint D27): the one write path into the record. It builds each event
3
+ * on the `nylorun.event/2` envelope, checks it against the event catalog, and holds the only
4
+ * insert into `session_events` and `session_log_heads`, whose Postgres statements it runs
5
+ * through the store's `RecordWriter` (`store/postgres/record-writer.ts`;
6
+ * `scripts/check-boundaries.mjs` refuses an insert anywhere else). Other folders import this
7
+ * file, never the module's own files.
8
+ */
9
+ export { buildEvent, InvalidEventError } from "./envelope.js";
10
+ export { decodeCursor, encodeCursor } from "./cursor.js";
11
+ export { appendEvent } from "./append.js";
@@ -11,7 +11,7 @@ export interface PostgresClientOptions {
11
11
  applicationName?: string;
12
12
  /**
13
13
  * `statement_timeout`: a statement running or waiting for a lock longer fails. Default
14
- * 60 s; 0 turns it off. Schema migrations lift it (`lockSchema`).
14
+ * 60 s; 0 turns it off. Migrations lift it (`lockMigrations` in `migrate.ts`).
15
15
  */
16
16
  statementTimeoutMs?: number;
17
17
  /**
@@ -21,18 +21,21 @@ export interface PostgresClientOptions {
21
21
  idleInTransactionTimeoutMs?: number;
22
22
  }
23
23
  /**
24
- * Opens a connection pool for Session Stores and the Tenant catalog. One pool
25
- * serves every Tenant schema of a Host; stores never end it, so the caller
26
- * calls `client.end()` at shutdown. This module and its siblings are the only
27
- * code that imports the Postgres driver (seam rule 3).
24
+ * Opens a connection pool on the Tenant's database, for its Session Store and the Tenant
25
+ * bootstrap (`tenant.ts`). Stores never end it, so the caller calls `client.end()` at
26
+ * shutdown. This module and its siblings are the only code that imports the Postgres driver
27
+ * and Drizzle (seam rule 3, `scripts/check-boundaries.mjs`); Drizzle runs on this pool
28
+ * (`db.ts`).
28
29
  *
29
- * Statements are not prepared (`prepare: false`). Every statement names its
30
- * Tenant's schema, so the same query is a different statement per Tenant, and
31
- * named prepared statements would pile up on every pooled connection with the
32
- * number of Tenants a Host serves. Unnamed statements still go in one round
33
- * trip; the queries are simple enough that re-planning them costs little.
30
+ * Statements are prepared: one database holds one Tenant in fixed schemas, so every statement
31
+ * is the same for the whole pool (session-store.md §2).
34
32
  *
35
- * The pool is shared by every Tenant, so a statement or transaction that hangs must not hold
36
- * a connection forever: both are bounded by default.
33
+ * The pool serves every request of the Host, so a statement or transaction that hangs must
34
+ * not hold a connection forever: both are bounded by default.
37
35
  */
38
36
  export declare function createPostgresClient(url: string, options?: PostgresClientOptions): PostgresClient;
37
+ /**
38
+ * Throws, naming the setting, when Postgres cannot run the stream relay: logical decoding
39
+ * needs `wal_level = logical` (a restart) and a role allowed to replicate.
40
+ */
41
+ export declare function assertLogicalReplication(sql: Sql): Promise<void>;
@@ -1,25 +1,22 @@
1
1
  import postgres, {} from "postgres";
2
2
  /**
3
- * Opens a connection pool for Session Stores and the Tenant catalog. One pool
4
- * serves every Tenant schema of a Host; stores never end it, so the caller
5
- * calls `client.end()` at shutdown. This module and its siblings are the only
6
- * code that imports the Postgres driver (seam rule 3).
3
+ * Opens a connection pool on the Tenant's database, for its Session Store and the Tenant
4
+ * bootstrap (`tenant.ts`). Stores never end it, so the caller calls `client.end()` at
5
+ * shutdown. This module and its siblings are the only code that imports the Postgres driver
6
+ * and Drizzle (seam rule 3, `scripts/check-boundaries.mjs`); Drizzle runs on this pool
7
+ * (`db.ts`).
7
8
  *
8
- * Statements are not prepared (`prepare: false`). Every statement names its
9
- * Tenant's schema, so the same query is a different statement per Tenant, and
10
- * named prepared statements would pile up on every pooled connection with the
11
- * number of Tenants a Host serves. Unnamed statements still go in one round
12
- * trip; the queries are simple enough that re-planning them costs little.
9
+ * Statements are prepared: one database holds one Tenant in fixed schemas, so every statement
10
+ * is the same for the whole pool (session-store.md §2).
13
11
  *
14
- * The pool is shared by every Tenant, so a statement or transaction that hangs must not hold
15
- * a connection forever: both are bounded by default.
12
+ * The pool serves every request of the Host, so a statement or transaction that hangs must
13
+ * not hold a connection forever: both are bounded by default.
16
14
  */
17
15
  export function createPostgresClient(url, options = {}) {
18
16
  return postgres(url, {
19
17
  max: options.max ?? 10,
20
18
  idle_timeout: options.idleTimeoutSeconds ?? 30,
21
19
  connect_timeout: options.connectTimeoutSeconds ?? 10,
22
- prepare: false,
23
20
  onnotice: () => { },
24
21
  connection: {
25
22
  application_name: options.applicationName ?? "nylorun-runtime",
@@ -28,3 +25,18 @@ export function createPostgresClient(url, options = {}) {
28
25
  },
29
26
  });
30
27
  }
28
+ /**
29
+ * Throws, naming the setting, when Postgres cannot run the stream relay: logical decoding
30
+ * needs `wal_level = logical` (a restart) and a role allowed to replicate.
31
+ */
32
+ export async function assertLogicalReplication(sql) {
33
+ const [row] = await sql `
34
+ SELECT current_setting('wal_level') AS wal_level,
35
+ (SELECT rolreplication OR rolsuper FROM pg_roles WHERE rolname = current_user) AS replicates`;
36
+ if (row?.wal_level !== "logical")
37
+ throw new Error(`Postgres has wal_level = ${row?.wal_level ?? "unknown"}; the stream relay needs logical replication. ` +
38
+ "Set wal_level = logical and restart Postgres (`nylorun start` does this for the local stack; " +
39
+ "on a managed Postgres, turn on its logical replication option). See DEPLOYMENT.md.");
40
+ if (!row.replicates)
41
+ throw new Error("The Runtime's Postgres role cannot replicate: grant it REPLICATION (ALTER ROLE … REPLICATION). See DEPLOYMENT.md.");
42
+ }
@@ -0,0 +1,16 @@
1
+ import { type PostgresJsDatabase, type PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js";
2
+ import type { PgDatabase } from "drizzle-orm/pg-core";
3
+ import type { Sql } from "postgres";
4
+ export type Database = PostgresJsDatabase<Record<string, never>>;
5
+ /** A database or one of its transactions: anything a query can run on. */
6
+ export type Queryable = PgDatabase<PostgresJsQueryResultHKT, Record<string, never>>;
7
+ /** A transaction of `Database` (`db.transaction`). */
8
+ export type Transaction = Parameters<Parameters<Database["transaction"]>[0]>[0];
9
+ /** The Drizzle database on `sql`. */
10
+ export declare function database(sql: Sql): Database;
11
+ /**
12
+ * The driver's error behind a Drizzle query error. Drizzle wraps every failed query in a
13
+ * `DrizzleQueryError` whose message holds the query and its parameters (secrets among them);
14
+ * the store surfaces the postgres.js error with its SQLSTATE `code`, as callers expect.
15
+ */
16
+ export declare function driverError(error: unknown): unknown;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Drizzle on the Tenant database's pool (session-store.md §3): `drizzle-orm/postgres-js` over
3
+ * the postgres.js pool `connect.ts` opens, with the snake_case column names `schema.ts`
4
+ * defines. One Drizzle instance per pool.
5
+ *
6
+ * Drizzle hands timestamps to its own column types: on the pool it is given, postgres.js stops
7
+ * parsing date and time values, so a raw query on that pool reads a `timestamptz` as text.
8
+ */
9
+ import { DrizzleQueryError } from "drizzle-orm";
10
+ import { drizzle, } from "drizzle-orm/postgres-js";
11
+ const databases = new WeakMap();
12
+ /** The Drizzle database on `sql`. */
13
+ export function database(sql) {
14
+ let db = databases.get(sql);
15
+ if (!db) {
16
+ db = drizzle({ client: sql, casing: "snake_case" });
17
+ databases.set(sql, db);
18
+ }
19
+ return db;
20
+ }
21
+ /**
22
+ * The driver's error behind a Drizzle query error. Drizzle wraps every failed query in a
23
+ * `DrizzleQueryError` whose message holds the query and its parameters (secrets among them);
24
+ * the store surfaces the postgres.js error with its SQLSTATE `code`, as callers expect.
25
+ */
26
+ export function driverError(error) {
27
+ return error instanceof DrizzleQueryError && error.cause !== undefined ? error.cause : error;
28
+ }
@@ -0,0 +1,22 @@
1
+ -- Custom SQL (drizzle-kit does not model it): the two schemas and `nylorun.doc(body)`, which
2
+ -- the generated columns of the baseline (0001) read. Its snapshot declares the schemas, so
3
+ -- the baseline does not create them again.
4
+ --
5
+ -- doc(body) casts a `json` body to `jsonb` for the generated columns. `jsonb` rejects the
6
+ -- escapes \u0000 and unpaired surrogates, which `json` keeps verbatim: they are replaced with
7
+ -- \ufffd first (the stored body is untouched). A body with no \u escape takes the plain cast.
8
+ CREATE SCHEMA IF NOT EXISTS nylorun;
9
+ --> statement-breakpoint
10
+ CREATE SCHEMA nylorun_streams;
11
+ --> statement-breakpoint
12
+ CREATE FUNCTION nylorun.doc(body json) RETURNS jsonb
13
+ LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$
14
+ SELECT CASE WHEN strpos(body::text, '\u') = 0 THEN body::jsonb
15
+ ELSE regexp_replace(
16
+ body::text,
17
+ '(?<!\\)((?:\\\\)*)\\u(0000|[dD][89a-fA-F][0-9a-fA-F]{2})',
18
+ '\1\\ufffd',
19
+ 'g'
20
+ )::jsonb
21
+ END
22
+ $$;
@@ -0,0 +1,255 @@
1
+ CREATE TABLE "nylorun"."actions" (
2
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
3
+ "body" json NOT NULL,
4
+ "session_id" text GENERATED ALWAYS AS (nylorun.doc(body)->>'sessionId') STORED,
5
+ "turn_id" text GENERATED ALWAYS AS (nylorun.doc(body)->>'turnId') STORED,
6
+ "agent_id" text GENERATED ALWAYS AS (nylorun.doc(body)->>'agentId') STORED,
7
+ "status" text GENERATED ALWAYS AS (nylorun.doc(body)->>'status') STORED,
8
+ "kind" text GENERATED ALWAYS AS (nylorun.doc(body)->>'kind') STORED,
9
+ "deadline_at" text GENERATED ALWAYS AS (nylorun.doc(body)->>'deadlineAt') STORED
10
+ );
11
+ --> statement-breakpoint
12
+ CREATE TABLE "nylorun"."commands" (
13
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
14
+ "body" json NOT NULL
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE "nylorun"."definitions" (
18
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
19
+ "body" json NOT NULL
20
+ );
21
+ --> statement-breakpoint
22
+ CREATE TABLE "nylorun"."effects" (
23
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
24
+ "body" json NOT NULL,
25
+ "session_id" text GENERATED ALWAYS AS (nylorun.doc(body)->'request'->>'sessionId') STORED,
26
+ "turn_id" text GENERATED ALWAYS AS (nylorun.doc(body)->'request'->>'turnId') STORED,
27
+ "kind" text GENERATED ALWAYS AS (nylorun.doc(body)->'request'->>'kind') STORED,
28
+ "status" text GENERATED ALWAYS AS (nylorun.doc(body)->>'status') STORED
29
+ );
30
+ --> statement-breakpoint
31
+ CREATE TABLE "nylorun"."endpoints" (
32
+ "agent_id" text COLLATE "C" PRIMARY KEY NOT NULL,
33
+ "url" text NOT NULL,
34
+ "implementation_version" text NOT NULL,
35
+ "manifest_hash" text,
36
+ "timeout_ms" integer NOT NULL,
37
+ "max_concurrent" integer NOT NULL,
38
+ "principal_id" text,
39
+ "last_delivery_at" text,
40
+ "last_success_at" text,
41
+ "last_error_code" text,
42
+ "last_error_message" text,
43
+ "consecutive_failures" integer DEFAULT 0 NOT NULL,
44
+ "served_implementation_version" text,
45
+ "served_manifest_hash" text,
46
+ "created_at" text NOT NULL,
47
+ "updated_at" text NOT NULL
48
+ );
49
+ --> statement-breakpoint
50
+ CREATE TABLE "nylorun"."links" (
51
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
52
+ "body" json NOT NULL,
53
+ "workflow_session_id" text GENERATED ALWAYS AS (nylorun.doc(body)->>'workflowSessionId') STORED
54
+ );
55
+ --> statement-breakpoint
56
+ CREATE TABLE "nylorun"."model_budgets" (
57
+ "scope" text NOT NULL,
58
+ "scope_id" text COLLATE "C" NOT NULL,
59
+ "period" text,
60
+ "limit_usd" double precision,
61
+ "limit_tokens" bigint,
62
+ "updated_at" text NOT NULL,
63
+ CONSTRAINT "model_budgets_pkey" PRIMARY KEY("scope","scope_id")
64
+ );
65
+ --> statement-breakpoint
66
+ CREATE TABLE "nylorun"."model_usage" (
67
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
68
+ "effect_key" text COLLATE "C" NOT NULL,
69
+ "session_id" text COLLATE "C" NOT NULL,
70
+ "turn_id" text COLLATE "C" NOT NULL,
71
+ "agent_id" text COLLATE "C" NOT NULL,
72
+ "provider" text,
73
+ "model" text,
74
+ "input_tokens" integer NOT NULL,
75
+ "output_tokens" integer NOT NULL,
76
+ "total_tokens" integer NOT NULL,
77
+ "cached_tokens" integer NOT NULL,
78
+ "cache_write_tokens" integer NOT NULL,
79
+ "reasoning_tokens" integer NOT NULL,
80
+ "cost_usd" double precision NOT NULL,
81
+ "duplicate" boolean NOT NULL,
82
+ "created_at" text NOT NULL
83
+ );
84
+ --> statement-breakpoint
85
+ CREATE TABLE "nylorun"."principals" (
86
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
87
+ "role" text NOT NULL,
88
+ "token_hash" text NOT NULL,
89
+ "idempotency_key" text,
90
+ "created_at" text NOT NULL,
91
+ CONSTRAINT "principals_token_hash_key" UNIQUE("token_hash")
92
+ );
93
+ --> statement-breakpoint
94
+ CREATE TABLE "nylorun"."publishable_keys" (
95
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
96
+ "key" text COLLATE "C" NOT NULL,
97
+ "name" text NOT NULL,
98
+ "origins_json" text NOT NULL,
99
+ "created_at" text NOT NULL,
100
+ "revoked_at" text,
101
+ CONSTRAINT "publishable_keys_key_key" UNIQUE("key"),
102
+ CONSTRAINT "publishable_keys_name_key" UNIQUE("name")
103
+ );
104
+ --> statement-breakpoint
105
+ CREATE TABLE "nylorun_streams"."relay_slots" (
106
+ "slot_name" text PRIMARY KEY NOT NULL,
107
+ "reconcile_pending" boolean NOT NULL
108
+ );
109
+ --> statement-breakpoint
110
+ CREATE TABLE "nylorun"."sandboxes" (
111
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
112
+ "body" json NOT NULL
113
+ );
114
+ --> statement-breakpoint
115
+ CREATE TABLE "nylorun_streams"."session_events" (
116
+ "session_id" text COLLATE "C" NOT NULL,
117
+ "seq" bigint NOT NULL,
118
+ "generation" integer NOT NULL,
119
+ "type" text NOT NULL,
120
+ "body" json NOT NULL,
121
+ "committed_at" timestamp with time zone DEFAULT now() NOT NULL,
122
+ CONSTRAINT "session_events_pkey" PRIMARY KEY("session_id","seq")
123
+ );
124
+ --> statement-breakpoint
125
+ CREATE TABLE "nylorun_streams"."session_log_heads" (
126
+ "session_id" text COLLATE "C" PRIMARY KEY NOT NULL,
127
+ "generation" integer NOT NULL,
128
+ "head" bigint DEFAULT 0 NOT NULL
129
+ );
130
+ --> statement-breakpoint
131
+ CREATE TABLE "nylorun"."sessions" (
132
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
133
+ "body" json NOT NULL,
134
+ "status" text GENERATED ALWAYS AS (nylorun.doc(body)->>'status') STORED,
135
+ "agent_id" text GENERATED ALWAYS AS (nylorun.doc(body)->>'agentId') STORED,
136
+ "owner" text,
137
+ "epoch" bigint DEFAULT 0 NOT NULL,
138
+ "owner_expires_at" timestamp with time zone,
139
+ "owner_user_id" text GENERATED ALWAYS AS (nylorun.doc(body)->>'ownerUserId') STORED
140
+ );
141
+ --> statement-breakpoint
142
+ CREATE TABLE "nylorun"."signing_keys" (
143
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
144
+ "state" text NOT NULL,
145
+ "alg" text NOT NULL,
146
+ "public_jwk" text NOT NULL,
147
+ "kek_id" text NOT NULL,
148
+ "nonce" "bytea" NOT NULL,
149
+ "ciphertext" "bytea" NOT NULL,
150
+ "wrapped_dek" "bytea" NOT NULL,
151
+ "created_at" text NOT NULL,
152
+ "activated_at" text,
153
+ "retired_at" text,
154
+ "revoked_at" text,
155
+ CONSTRAINT "signing_keys_state_check" CHECK (state IN ('standby', 'current', 'previous', 'revoked')),
156
+ CONSTRAINT "signing_keys_alg_check" CHECK (alg = 'ES256')
157
+ );
158
+ --> statement-breakpoint
159
+ CREATE TABLE "nylorun"."subject_epochs" (
160
+ "subject" text COLLATE "C" PRIMARY KEY NOT NULL,
161
+ "epoch" bigint NOT NULL,
162
+ "revoked_at" text NOT NULL
163
+ );
164
+ --> statement-breakpoint
165
+ CREATE TABLE "nylorun"."subject_usage" (
166
+ "subject" text COLLATE "C" PRIMARY KEY NOT NULL,
167
+ "turn_tokens" double precision NOT NULL,
168
+ "refilled_at" text NOT NULL
169
+ );
170
+ --> statement-breakpoint
171
+ CREATE TABLE "nylorun"."tenant" (
172
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
173
+ "name" text NOT NULL,
174
+ "created_at" text NOT NULL,
175
+ "updated_at" text NOT NULL,
176
+ "schema_version" integer NOT NULL,
177
+ "singleton" boolean DEFAULT true NOT NULL,
178
+ "basin_generation" integer DEFAULT 0 NOT NULL,
179
+ "retired_generations" integer[] DEFAULT '{}' NOT NULL,
180
+ CONSTRAINT "tenant_singleton_key" UNIQUE("singleton"),
181
+ CONSTRAINT "tenant_singleton_check" CHECK (singleton)
182
+ );
183
+ --> statement-breakpoint
184
+ CREATE TABLE "nylorun"."tenant_settings" (
185
+ "key" text PRIMARY KEY NOT NULL,
186
+ "value" text NOT NULL
187
+ );
188
+ --> statement-breakpoint
189
+ CREATE TABLE "nylorun"."vault_audit" (
190
+ "ord" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "nylorun"."vault_audit_ord_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
191
+ "id" text NOT NULL,
192
+ "at" text NOT NULL,
193
+ "actor" text NOT NULL,
194
+ "action" text NOT NULL,
195
+ "vault_id" text,
196
+ "credential_id" text,
197
+ "session_id" text,
198
+ "target" text,
199
+ "outcome" text NOT NULL,
200
+ CONSTRAINT "vault_audit_id_key" UNIQUE("id")
201
+ );
202
+ --> statement-breakpoint
203
+ CREATE TABLE "nylorun"."vault_credentials" (
204
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
205
+ "vault_id" text NOT NULL,
206
+ "name" text NOT NULL,
207
+ "type" text NOT NULL,
208
+ "binding_json" text NOT NULL,
209
+ "expires_at" text,
210
+ "created_at" text COLLATE "C" NOT NULL,
211
+ "rotated_at" text,
212
+ "kek_id" text NOT NULL,
213
+ "nonce" "bytea" NOT NULL,
214
+ "ciphertext" "bytea" NOT NULL,
215
+ "wrapped_dek" "bytea" NOT NULL
216
+ );
217
+ --> statement-breakpoint
218
+ CREATE TABLE "nylorun"."vault_idempotency" (
219
+ "id" text PRIMARY KEY NOT NULL,
220
+ "body_hash" text NOT NULL,
221
+ "response" text NOT NULL
222
+ );
223
+ --> statement-breakpoint
224
+ CREATE TABLE "nylorun"."vaults" (
225
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
226
+ "name" text NOT NULL,
227
+ "owner_user_id" text NOT NULL,
228
+ "metadata_json" text,
229
+ "created_at" text COLLATE "C" NOT NULL,
230
+ "scope" text DEFAULT 'user' NOT NULL,
231
+ CONSTRAINT "vaults_scope_check" CHECK (scope IN ('user', 'host'))
232
+ );
233
+ --> statement-breakpoint
234
+ ALTER TABLE "nylorun"."vault_credentials" ADD CONSTRAINT "vault_credentials_vault_id_fkey" FOREIGN KEY ("vault_id") REFERENCES "nylorun"."vaults"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
235
+ CREATE INDEX "actions_session" ON "nylorun"."actions" USING btree ("session_id","turn_id","status");--> statement-breakpoint
236
+ CREATE INDEX "actions_agent" ON "nylorun"."actions" USING btree ("agent_id","status");--> statement-breakpoint
237
+ CREATE INDEX "actions_status" ON "nylorun"."actions" USING btree ("status","kind");--> statement-breakpoint
238
+ CREATE INDEX "actions_deadline" ON "nylorun"."actions" USING btree ("status","deadline_at");--> statement-breakpoint
239
+ CREATE INDEX "effects_session" ON "nylorun"."effects" USING btree ("session_id","turn_id","status");--> statement-breakpoint
240
+ CREATE INDEX "effects_status" ON "nylorun"."effects" USING btree ("status","kind");--> statement-breakpoint
241
+ CREATE INDEX "links_workflow" ON "nylorun"."links" USING btree ("workflow_session_id");--> statement-breakpoint
242
+ CREATE INDEX "model_usage_effect" ON "nylorun"."model_usage" USING btree ("effect_key");--> statement-breakpoint
243
+ CREATE INDEX "model_usage_agent" ON "nylorun"."model_usage" USING btree ("agent_id","created_at");--> statement-breakpoint
244
+ CREATE INDEX "model_usage_turn" ON "nylorun"."model_usage" USING btree ("turn_id");--> statement-breakpoint
245
+ CREATE INDEX "model_usage_created" ON "nylorun"."model_usage" USING btree ("created_at");--> statement-breakpoint
246
+ CREATE INDEX "sessions_status" ON "nylorun"."sessions" USING btree ("status","owner_expires_at");--> statement-breakpoint
247
+ CREATE INDEX "sessions_agent" ON "nylorun"."sessions" USING btree ("agent_id");--> statement-breakpoint
248
+ CREATE INDEX "sessions_owner_user" ON "nylorun"."sessions" USING btree ("owner_user_id");--> statement-breakpoint
249
+ CREATE UNIQUE INDEX "signing_keys_one_standby" ON "nylorun"."signing_keys" USING btree ("state") WHERE state = 'standby';--> statement-breakpoint
250
+ CREATE UNIQUE INDEX "signing_keys_one_current" ON "nylorun"."signing_keys" USING btree ("state") WHERE state = 'current';--> statement-breakpoint
251
+ CREATE UNIQUE INDEX "signing_keys_one_previous" ON "nylorun"."signing_keys" USING btree ("state") WHERE state = 'previous';--> statement-breakpoint
252
+ CREATE INDEX "vault_audit_vault" ON "nylorun"."vault_audit" USING btree ("vault_id","ord");--> statement-breakpoint
253
+ CREATE INDEX "vault_credentials_vault" ON "nylorun"."vault_credentials" USING btree ("vault_id","created_at","id");--> statement-breakpoint
254
+ CREATE INDEX "vaults_owner" ON "nylorun"."vaults" USING btree ("owner_user_id","created_at","id");--> statement-breakpoint
255
+ CREATE UNIQUE INDEX "vaults_one_host" ON "nylorun"."vaults" USING btree ("scope") WHERE scope = 'host';
@@ -0,0 +1,4 @@
1
+ -- Custom SQL (drizzle-kit does not model publications): the publication the stream relay
2
+ -- subscribes to (Durable Streams §7). Only inserts: the record is append-only.
3
+ CREATE PUBLICATION nylorun_stream_relay
4
+ FOR TABLE nylorun_streams.session_events WITH (publish = 'insert');
@@ -0,0 +1,9 @@
1
+ CREATE TABLE "nylorun"."tool_crossings" (
2
+ "key" text COLLATE "C" PRIMARY KEY NOT NULL,
3
+ "hash" text NOT NULL,
4
+ "started_at" text COLLATE "C" NOT NULL,
5
+ "settled_at" text COLLATE "C",
6
+ "answer" json
7
+ );
8
+ --> statement-breakpoint
9
+ CREATE INDEX "tool_crossings_settled" ON "nylorun"."tool_crossings" USING btree ("settled_at");
@@ -0,0 +1,34 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1790943952307,
9
+ "tag": "0000_foundation",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "7",
15
+ "when": 1790943980707,
16
+ "tag": "0001_baseline",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1790943989041,
23
+ "tag": "0002_stream_relay",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "7",
29
+ "when": 1790956444115,
30
+ "tag": "0003_tool_crossings",
31
+ "breakpoints": true
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,68 @@
1
+ import type { Sql } from "postgres";
2
+ import { type Queryable, type Transaction } from "./db.js";
3
+ /** Where the migrations are, next to this module (`src/` in development, `dist/` built). */
4
+ export declare const MIGRATIONS_FOLDER: string;
5
+ /** The journal of applied migrations: `nylorun.__drizzle_migrations`. */
6
+ export declare const MIGRATIONS_SCHEMA = "nylorun";
7
+ export declare const MIGRATIONS_TABLE = "__drizzle_migrations";
8
+ /**
9
+ * What schemas were called when one database held several Tenants (`"tenant_<id>"`). A
10
+ * database that still has one was written by an older Runtime and is refused.
11
+ */
12
+ export declare const OLD_TENANT_SCHEMA_PREFIX = "tenant_";
13
+ export interface Migration {
14
+ /** The journal tag, `0001_baseline`. */
15
+ tag: string;
16
+ /** SHA-256 of the SQL file, as Drizzle records it. */
17
+ hash: string;
18
+ /** The journal's `when` (ms), recorded as `created_at`. */
19
+ when: number;
20
+ /** The file's statements, split at `--> statement-breakpoint`. */
21
+ statements: readonly string[];
22
+ }
23
+ /** The migrations this Runtime ships, in order. */
24
+ export declare function shippedMigrations(): readonly Migration[];
25
+ /** The schema version this Runtime writes and expects: the number of shipped migrations. */
26
+ export declare function expectedSchemaVersion(): number;
27
+ /**
28
+ * Lifts `statement_timeout` for the transaction and takes the database's migration lock
29
+ * until it ends. Waiting for the lock, and the migrations done under it, may outlast the
30
+ * pool's statement timeout; a migration that timed out would fail the Host's readiness.
31
+ */
32
+ export declare function lockMigrations(tx: Transaction): Promise<void>;
33
+ /**
34
+ * Throws `database-layout-old` when the database was written by an older Runtime: one that
35
+ * kept several Tenants in one database (`tenant_<id>` schemas, a record keyed by Tenant), or a
36
+ * pre-release build of one Tenant per database whose hand-written migrations recorded
37
+ * themselves in `schema_version` tables.
38
+ */
39
+ export declare function assertCurrentLayout(q: Queryable): Promise<void>;
40
+ /**
41
+ * The database's schema version: the number of migrations its journal records, or undefined
42
+ * when it has no journal (a database never migrated).
43
+ */
44
+ export declare function readSchemaVersion(q: Queryable): Promise<number | undefined>;
45
+ /**
46
+ * Applies every migration of `migrations` the journal does not record, in `tx`. Take
47
+ * `lockMigrations` first (`migrateDatabase` does). Throws `schema-too-new` when the journal
48
+ * records a migration `migrations` does not have, and `migration-failed` when Postgres
49
+ * rejects a migration.
50
+ */
51
+ export declare function applyMigrations(tx: Transaction, migrations?: readonly Migration[]): Promise<{
52
+ from: number;
53
+ to: number;
54
+ }>;
55
+ /**
56
+ * Refuses an old layout and applies the missing migrations, in one transaction under the
57
+ * migration lock. For tests and tools; the Host migrates through `openTenantDatabase`.
58
+ */
59
+ export declare function migrateDatabase(pool: Sql, migrations?: readonly Migration[]): Promise<{
60
+ from: number;
61
+ to: number;
62
+ }>;
63
+ /**
64
+ * Whether Postgres rejected a statement (a SQLSTATE), rather than the connection or the
65
+ * server failing (classes 08, 53, 57, and the driver's own connection errors). Only the
66
+ * first says something about the Tenant's database.
67
+ */
68
+ export declare function isStatementError(error: unknown): boolean;