@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,56 @@
1
+ import type { Sql } from "postgres";
2
+ import { type TenantEnvelope } from "@nylorun/core/contracts";
3
+ import type { SessionStore } from "../types.js";
4
+ import { type Queryable } from "./db.js";
5
+ import { type Migration } from "./migrate.js";
6
+ /** An application principal the Tenant is created with (or given later). */
7
+ export interface InitialPrincipal {
8
+ id: string;
9
+ /** SHA-256 hex of the principal's key. */
10
+ credentialHash: string;
11
+ }
12
+ /** Who the Tenant is when the database holds none yet. */
13
+ export interface TenantCreation {
14
+ /** Default: a new id (`newTenantId()`). Ignored when the Tenant exists. */
15
+ tenantId?: string;
16
+ /** Ignored when the Tenant exists. */
17
+ name: string;
18
+ /**
19
+ * The principals the Tenant must have, given its id (derived keys depend on it). Created
20
+ * with the Tenant; on later opens, the missing ones are added.
21
+ */
22
+ principals?(tenantId: string): readonly InitialPrincipal[];
23
+ }
24
+ export interface OpenTenantDatabaseOptions {
25
+ /** The pool on the Tenant's database. The store never ends it. */
26
+ sql: Sql;
27
+ create: TenantCreation;
28
+ /** Clock for the envelope and the principals' `createdAt`, and the store's. */
29
+ now?: () => Date;
30
+ /** Passed to the store (post-commit failures). */
31
+ onError?: (error: unknown) => void;
32
+ /** Tests only: the migrations this Runtime knows. Default: the shipped ones. */
33
+ migrations?: readonly Migration[];
34
+ }
35
+ export interface OpenedTenantDatabase {
36
+ store: SessionStore;
37
+ envelope: TenantEnvelope;
38
+ /** This call created the Tenant. */
39
+ created: boolean;
40
+ /** The schema versions (applied migrations) before and after. */
41
+ migrated: {
42
+ from: number;
43
+ to: number;
44
+ };
45
+ }
46
+ /**
47
+ * Migrates the database, creates its Tenant when it has none, and opens its Session Store.
48
+ * Throws a `TenantOpenError` with the cause when the Tenant cannot be opened (see above).
49
+ */
50
+ export declare function openTenantDatabase(options: OpenTenantDatabaseOptions): Promise<OpenedTenantDatabase>;
51
+ /**
52
+ * The Tenant's envelope, or undefined when the database holds no Tenant yet (no `nylorun`
53
+ * schema, or an empty `tenant` table). Throws `envelope-invalid` when the row does not parse.
54
+ * Takes the pool or a Drizzle database or transaction on it.
55
+ */
56
+ export declare function readTenantEnvelope(q: Sql | Queryable): Promise<TenantEnvelope | undefined>;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The Tenant a database holds (tenancy.md §1, §4; session-store.md §2). One database is one
3
+ * Tenant: its state in the schema `nylorun`, its record in `nylorun_streams`, and its
4
+ * envelope in the one row of `nylorun.tenant`. There is no catalog.
5
+ *
6
+ * `openTenantDatabase` runs in one transaction, under the database's migration lock
7
+ * (`migrate.ts`), so several processes starting at once migrate and create once:
8
+ *
9
+ * 1. Refuse an old layout (`database-layout-old`): `tenant_<id>` schemas or a record keyed by
10
+ * Tenant (a Runtime from before one Tenant per installation), or the `schema_version`
11
+ * tables of a pre-release build of it. It is never touched: this release starts fresh.
12
+ * 2. Apply the missing migrations (`migrate.ts`). A database with migrations this Runtime
13
+ * does not know is `schema-too-new`; a migration Postgres rejects is `migration-failed`.
14
+ * 3. When `nylorun.tenant` is empty, create the Tenant: its row (id, name, created time) and
15
+ * its first principals. A database whose row is gone but that holds a Tenant's data
16
+ * (principals, sessions, agents, vaults, keys) is `envelope-invalid`, never given a new
17
+ * Tenant. Principals given later that the Tenant does not have yet are added (a derived
18
+ * principal configured after the first start); existing ones are kept.
19
+ * 4. Read the envelope back (`envelope-invalid` when it does not parse), and open the store.
20
+ *
21
+ * A lost connection or an unavailable server says nothing about the Tenant: it is thrown as
22
+ * it is, never as a cause.
23
+ */
24
+ import { sql } from "drizzle-orm";
25
+ import { isTenantId, newTenantId } from "@nylorun/core/compatibility";
26
+ import { TenantEnvelopeSchema, } from "@nylorun/core/contracts";
27
+ import { openError, TenantOpenError } from "../../tenant/cause.js";
28
+ import { database, driverError } from "./db.js";
29
+ import { applyMigrations, assertCurrentLayout, isStatementError, lockMigrations, shippedMigrations, } from "./migrate.js";
30
+ import { definitions, principals, sessions, signingKeys, TENANT_SCHEMA, tenant, vaults, } from "./schema.js";
31
+ import { createPostgresSessionStore } from "./store.js";
32
+ /**
33
+ * Migrates the database, creates its Tenant when it has none, and opens its Session Store.
34
+ * Throws a `TenantOpenError` with the cause when the Tenant cannot be opened (see above).
35
+ */
36
+ export async function openTenantDatabase(options) {
37
+ const { sql: pool } = options;
38
+ const db = database(pool);
39
+ const now = options.now ?? (() => new Date());
40
+ const migrations = options.migrations ?? shippedMigrations();
41
+ let outcome;
42
+ try {
43
+ outcome = await db.transaction(async (tx) => {
44
+ await lockMigrations(tx);
45
+ await assertCurrentLayout(tx);
46
+ const migrated = await applyMigrations(tx, migrations);
47
+ const created = await ensureTenant(tx, options.create, migrated, now);
48
+ return { created, migrated };
49
+ });
50
+ }
51
+ catch (thrown) {
52
+ const error = driverError(thrown);
53
+ if (!(error instanceof TenantOpenError) && !isStatementError(error))
54
+ throw error;
55
+ // A statement of the bootstrap itself (the layout check, the Tenant row) was rejected.
56
+ const failure = error instanceof TenantOpenError
57
+ ? error
58
+ : openError("open-failed", `Opening the Tenant database failed: ${error.message}`);
59
+ // Name the Tenant when its row reads (a database newer than this Runtime, say).
60
+ if (failure.code !== "database-layout-old")
61
+ failure.envelope ??= await readTenantEnvelope(db).catch(() => undefined);
62
+ throw failure;
63
+ }
64
+ const envelope = await readTenantEnvelope(db);
65
+ if (!envelope)
66
+ throw openError("envelope-invalid", "The Tenant row is missing");
67
+ const store = createPostgresSessionStore({
68
+ sql: pool,
69
+ tenantId: envelope.id,
70
+ schemaVersion: migrations.length,
71
+ ...(options.now ? { now: options.now } : {}),
72
+ ...(options.onError ? { onError: options.onError } : {}),
73
+ });
74
+ return { store, envelope, ...outcome };
75
+ }
76
+ /**
77
+ * The Tenant's envelope, or undefined when the database holds no Tenant yet (no `nylorun`
78
+ * schema, or an empty `tenant` table). Throws `envelope-invalid` when the row does not parse.
79
+ * Takes the pool or a Drizzle database or transaction on it.
80
+ */
81
+ export async function readTenantEnvelope(q) {
82
+ const db = typeof q === "function" ? database(q) : q;
83
+ try {
84
+ const [table] = await db.execute(sql `
85
+ SELECT to_regclass(${`${TENANT_SCHEMA}.tenant`}) IS NOT NULL AS exists`);
86
+ if (!table?.exists)
87
+ return undefined;
88
+ const [row] = await db
89
+ .select({
90
+ id: tenant.id,
91
+ name: tenant.name,
92
+ createdAt: tenant.createdAt,
93
+ updatedAt: tenant.updatedAt,
94
+ schemaVersion: tenant.schemaVersion,
95
+ })
96
+ .from(tenant);
97
+ if (!row)
98
+ return undefined;
99
+ const parsed = TenantEnvelopeSchema.safeParse(row);
100
+ if (!parsed.success || !isTenantId(parsed.data.id))
101
+ throw openError("envelope-invalid", "The Tenant row is not valid");
102
+ return parsed.data;
103
+ }
104
+ catch (error) {
105
+ throw driverError(error);
106
+ }
107
+ }
108
+ /**
109
+ * Creates the Tenant when there is none, and adds the principals it is missing. Records the
110
+ * schema version on the Tenant row when this transaction migrated.
111
+ */
112
+ async function ensureTenant(tx, create, migrated, now) {
113
+ const [existing] = await tx.select({ id: tenant.id }).from(tenant);
114
+ const createdAt = now().toISOString();
115
+ let tenantId = existing?.id;
116
+ if (tenantId === undefined) {
117
+ const [data] = await tx.execute(sql `
118
+ SELECT EXISTS (SELECT 1 FROM ${principals})
119
+ OR EXISTS (SELECT 1 FROM ${sessions})
120
+ OR EXISTS (SELECT 1 FROM ${definitions})
121
+ OR EXISTS (SELECT 1 FROM ${vaults})
122
+ OR EXISTS (SELECT 1 FROM ${signingKeys}) AS held`);
123
+ if (data?.held)
124
+ throw openError("envelope-invalid", "The Tenant row is missing from a database that holds a Tenant's data: restore it from a backup");
125
+ tenantId = create.tenantId ?? newTenantId();
126
+ if (!isTenantId(tenantId))
127
+ throw new Error(`Invalid Tenant id: ${tenantId}`);
128
+ if (!create.name)
129
+ throw new Error("A Tenant needs a name");
130
+ await tx.insert(tenant).values({
131
+ id: tenantId,
132
+ name: create.name,
133
+ createdAt,
134
+ updatedAt: createdAt,
135
+ schemaVersion: migrated.to,
136
+ });
137
+ }
138
+ else if (migrated.from !== migrated.to) {
139
+ await tx.update(tenant).set({ schemaVersion: migrated.to });
140
+ }
141
+ for (const principal of create.principals?.(tenantId) ?? [])
142
+ // A principal with this id or this key already exists: it is kept as it is.
143
+ await tx
144
+ .insert(principals)
145
+ .values({
146
+ id: principal.id,
147
+ role: "application",
148
+ tokenHash: principal.credentialHash,
149
+ idempotencyKey: null,
150
+ createdAt,
151
+ })
152
+ .onConflictDoNothing();
153
+ return existing === undefined;
154
+ }
@@ -2,8 +2,8 @@
2
2
  * The Session Store seam (architecture §12.2).
3
3
  *
4
4
  * The Session Store records what happened. It is async, transactional and
5
- * tenant-scoped: one `SessionStore` per Tenant. Postgres is the supported
6
- * implementation; `store/memory.ts` is the in-memory fake for unit tests.
5
+ * tenant-scoped: one `SessionStore` per Tenant. Postgres is the
6
+ * implementation (`store/postgres/store.ts`); tests run on it too.
7
7
  *
8
8
  * ## Invariants every implementation keeps
9
9
  *
@@ -23,7 +23,7 @@
23
23
  * committed transactions. The sequence is also the S2 sequence number of the
24
24
  * event in the session's stream (`sessions/<id>` in the Tenant's basin
25
25
  * generation, `streams/basin.ts`), and the cursor is
26
- * `base64url("<sessionId>:<seq>")` (see `store/cursor.ts`).
26
+ * `base64url("<sessionId>:<seq>")` (see `record/cursor.ts`).
27
27
  * 4. **No external I/O inside `fn`.** No model, tool, MCP, sandbox, Restate or
28
28
  * S2 call, and no `fetch`, runs inside a transaction. Wakes and deliveries go
29
29
  * through `afterCommit`, and events are delivered to commit listeners after
@@ -57,8 +57,9 @@
57
57
  import type { RecordReader } from "../streams/relay/types.js";
58
58
  import type { Action, EventPayload, EventType, LiveEvent, SessionEventOf } from "@nylorun/core/contracts";
59
59
  import type { HostEffect } from "@nylorun/harness/run";
60
+ import type { ModelBudgetRow, ModelUsageRow, PrincipalRow, ToolCrossingRow, PublishableKeyRow, SigningKeyRow, SubjectUsageRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow } from "./postgres/schema.js";
60
61
  /** Tables holding JSON documents keyed by `id`. */
61
- export type DocTable = "definitions" | "sessions" | "commands" | "checkpoints" | "effects" | "actions" | "sandboxes" | "links";
62
+ export type DocTable = "definitions" | "sessions" | "commands" | "effects" | "actions" | "sandboxes" | "links";
62
63
  export declare const DOC_TABLES: readonly DocTable[];
63
64
  /** Session lifecycle (architecture §10.2). */
64
65
  export type SessionStatus = "idle" | "runnable" | "running" | "waiting" | "paused" | "uncertain" | "completed" | "failed" | "cancelled";
@@ -186,87 +187,29 @@ export type EndpointHealthUpdate =
186
187
  implementationVersion: string;
187
188
  manifestHash?: string;
188
189
  };
189
- export interface PrincipalRow {
190
- id: string;
191
- role: "application" | (string & {});
192
- tokenHash: string;
193
- idempotencyKey: string | null;
194
- createdAt: string;
195
- }
196
- export interface VaultRow {
197
- id: string;
198
- name: string;
199
- ownerUserId: string;
200
- /** JSON text, or null. */
201
- metadataJson: string | null;
202
- createdAt: string;
203
- scope: "user" | "host";
204
- }
205
- /** Envelope-encrypted secret material. Always in columns, never inside a JSON body. */
206
- export interface SealedSecret {
207
- kekId: string;
208
- nonce: Uint8Array;
209
- ciphertext: Uint8Array;
210
- wrappedDek: Uint8Array;
211
- }
212
- /** A Tenant signing key (subject tokens). The private key is sealed; the public JWK is not. */
213
- export interface SigningKeyRow extends SealedSecret {
214
- id: string;
215
- state: "standby" | "current" | "previous" | "revoked";
216
- alg: "ES256";
217
- /** JSON text of the public JWK. */
218
- publicJwk: string;
219
- createdAt: string;
220
- activatedAt: string | null;
221
- retiredAt: string | null;
222
- revokedAt: string | null;
223
- }
224
- /** A publishable key (Host feature `browser-access`). Public by design. */
225
- export interface PublishableKeyRow {
226
- id: string;
227
- key: string;
228
- name: string;
229
- /** JSON array of allowed origins. */
230
- originsJson: string;
231
- createdAt: string;
232
- revokedAt: string | null;
233
- }
234
- /** A subject's turn bucket (subject limits): tokens left and when they were last refilled. */
235
- export interface SubjectUsageRow {
236
- subject: string;
237
- turnTokens: number;
238
- refilledAt: string;
190
+ /**
191
+ * The rows of the typed tables, inferred from the tables Drizzle defines
192
+ * (`store/postgres/schema.ts`): a principal, a vault and its credentials (secrets sealed in
193
+ * `bytea` columns, never inside a JSON body), the vault's audit and idempotency records, the
194
+ * Tenant's signing keys, a subject's turn bucket, a publishable key, a model call in the usage
195
+ * ledger and a model budget.
196
+ */
197
+ export type { ModelBudgetRow, ModelUsageRow, ToolCrossingRow, PrincipalRow, PublishableKeyRow, SigningKeyRow, SubjectUsageRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow, };
198
+ /** Which rows of the usage ledger a total covers. */
199
+ export interface ModelUsageQuery {
200
+ scope: "tenant" | "agent" | "turn";
201
+ /** The agent or turn id; ignored for `tenant`. */
202
+ id?: string;
203
+ /** Only rows created at or after this ISO time. */
204
+ since?: string;
239
205
  }
240
- export interface VaultCredentialRow extends SealedSecret {
241
- id: string;
242
- vaultId: string;
243
- name: string;
244
- type: "bearer" | "oauth" | "model";
245
- /** JSON text of the non-secret binding (url, or provider and model). */
246
- bindingJson: string;
247
- expiresAt: string | null;
248
- createdAt: string;
249
- rotatedAt: string | null;
206
+ export interface ModelUsageTotals {
207
+ calls: number;
208
+ tokens: number;
209
+ costUsd: number;
250
210
  }
251
211
  /** Fields `updateCredential` may change. */
252
212
  export type VaultCredentialPatch = Partial<Omit<VaultCredentialRow, "id" | "vaultId" | "createdAt">>;
253
- export interface VaultAuditRow {
254
- id: string;
255
- at: string;
256
- actor: string;
257
- action: string;
258
- vaultId: string | null;
259
- credentialId: string | null;
260
- sessionId: string | null;
261
- target: string | null;
262
- outcome: string;
263
- }
264
- export interface VaultIdempotencyRow {
265
- id: string;
266
- bodyHash: string;
267
- /** JSON text of the original response. */
268
- response: string;
269
- }
270
213
  export interface SessionEffectFilter {
271
214
  /** Only this turn. Absent means every turn. */
272
215
  turnId?: string;
@@ -356,7 +299,7 @@ export interface Tx {
356
299
  * Locking twice in one transaction is a no-op.
357
300
  *
358
301
  * Lock order, so concurrent transactions cannot deadlock (Postgres takes row
359
- * locks in statement order; the in-memory fake serializes whole transactions):
302
+ * locks in statement order):
360
303
  * - a linked agent (child) session is locked before its workflow (parent)
361
304
  * session, never after it. `t.event` on a session takes its lock, so an
362
305
  * event on a child after the parent is locked breaks the rule too. Work
@@ -519,19 +462,32 @@ export interface Tx {
519
462
  publishableKeys(): Promise<PublishableKeyRow[]>;
520
463
  /** Returns false when the key does not exist. */
521
464
  updatePublishableKey(id: string, patch: Partial<Pick<PublishableKeyRow, "originsJson" | "revokedAt">>): Promise<boolean>;
465
+ /** Appends a row, setting `duplicate` when one with the same `effectKey` exists; returns it. */
466
+ recordModelUsage(row: Omit<ModelUsageRow, "duplicate">): Promise<ModelUsageRow>;
467
+ modelUsageTotals(query: ModelUsageQuery): Promise<ModelUsageTotals>;
468
+ /** Every budget, ordered by scope, then scope id. */
469
+ listModelBudgets(): Promise<ModelBudgetRow[]>;
470
+ /** Replaces every budget with `rows`. */
471
+ putModelBudgets(rows: readonly ModelBudgetRow[]): Promise<void>;
472
+ toolCrossing(key: string): Promise<ToolCrossingRow | undefined>;
473
+ /** Inserts the running call's row; false when a row with `key` already exists. */
474
+ startToolCrossing(row: Pick<ToolCrossingRow, "key" | "hash" | "startedAt">): Promise<boolean>;
475
+ /** Records the call's answer. */
476
+ settleToolCrossing(key: string, answer: unknown, settledAt: string): Promise<void>;
477
+ /** Deletes rows that settled before `before`; returns how many. */
478
+ pruneToolCrossings(before: string): Promise<number>;
522
479
  getSetting(key: string): Promise<string | undefined>;
523
480
  putSetting(key: string, value: string): Promise<void>;
524
481
  /**
525
482
  * Deletes Tenant state by scope, in this transaction:
526
- * - `sessions`: sessions, commands, checkpoints, effects, actions, links, subject turn
483
+ * - `sessions`: sessions, commands, effects, actions, links, subject turn
527
484
  * buckets and the Tenant's record rows and log heads. The Tenant moves to the next basin
528
485
  * generation and the current one is retired, so session ids it frees start again in an
529
486
  * empty basin;
530
487
  * - `sandboxes`: sandbox records;
531
- * - `all`: both, plus definitions, Action endpoints and user vaults with their
532
- * credentials. The host vault, principals, signing keys, subject epochs, publishable
533
- * keys, settings,
534
- * audit and vault idempotency rows stay.
488
+ * - `all`: both, plus definitions, Action endpoints, user vaults with their credentials,
489
+ * the model usage ledger and the model budgets. The host vault, principals, signing keys, subject epochs,
490
+ * publishable keys, settings, audit and vault idempotency rows stay.
535
491
  */
536
492
  reset(scope: ResetScope): Promise<void>;
537
493
  }
@@ -2,7 +2,6 @@ export const DOC_TABLES = [
2
2
  "definitions",
3
3
  "sessions",
4
4
  "commands",
5
- "checkpoints",
6
5
  "effects",
7
6
  "actions",
8
7
  "sandboxes",
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { type DurableStreams } from "./types.js";
7
7
  /** Appends a `session.cancel` signal: the process running the session's advance aborts it. */
8
- export declare function signalCancel(streams: DurableStreams, basin: string, sessionId: string): Promise<void>;
8
+ export declare function signalCancel(streams: DurableStreams, basin: string, sessionId: string, turnId?: string): Promise<void>;
9
9
  /**
10
10
  * Appends a `sessions.reset` signal to the old generation's basin: every process with the
11
11
  * Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
@@ -5,10 +5,14 @@
5
5
  */
6
6
  import { CONTROL_STREAM } from "./types.js";
7
7
  /** Appends a `session.cancel` signal: the process running the session's advance aborts it. */
8
- export async function signalCancel(streams, basin, sessionId) {
8
+ export async function signalCancel(streams, basin, sessionId, turnId) {
9
9
  if (!sessionId)
10
10
  throw new Error("sessionId is required");
11
- const signal = { type: "session.cancel", sessionId };
11
+ const signal = {
12
+ type: "session.cancel",
13
+ sessionId,
14
+ ...(turnId ? { turnId } : {}),
15
+ };
12
16
  await streams.append(basin, CONTROL_STREAM, [signal]);
13
17
  }
14
18
  /**
@@ -2,7 +2,8 @@
2
2
  * Contracts of the stream relay (Durable Streams §7): what it reads committed events from,
3
3
  * and how it reads the record back to refill and reconcile. Postgres implements both with
4
4
  * logical replication (`adapters/replication/pgoutput.ts`) and the record tables
5
- * (`store/postgres/record.ts`); `streams/relay/memory.ts` is the in-memory pair.
5
+ * (`store/postgres/record.ts`); the relay core's unit tests use an in-memory pair
6
+ * (`test/support/relay-record.ts`).
6
7
  */
7
8
  /** One committed row of the record: a session event and the basin generation it goes to. */
8
9
  export interface RecordRow {
@@ -2,6 +2,7 @@
2
2
  * Contracts of the stream relay (Durable Streams §7): what it reads committed events from,
3
3
  * and how it reads the record back to refill and reconcile. Postgres implements both with
4
4
  * logical replication (`adapters/replication/pgoutput.ts`) and the record tables
5
- * (`store/postgres/record.ts`); `streams/relay/memory.ts` is the in-memory pair.
5
+ * (`store/postgres/record.ts`); the relay core's unit tests use an in-memory pair
6
+ * (`test/support/relay-record.ts`).
6
7
  */
7
8
  export {};
@@ -123,6 +123,11 @@ export declare function sessionStream(sessionId: string): string;
123
123
  export interface SessionCancelSignal {
124
124
  type: "session.cancel";
125
125
  sessionId: string;
126
+ /**
127
+ * The cancelled turn. Only an advance of this turn stops, so a signal delivered late (an
128
+ * append retried after S2 returns) never stops a later turn. Absent: any advance stops.
129
+ */
130
+ turnId?: string;
126
131
  }
127
132
  /**
128
133
  * The Tenant was reset and moved to basin generation `generation`. Appended to the old
@@ -8,8 +8,13 @@ import type { Session, TenantContext } from "./context.js";
8
8
  export declare function advance(ctx: TenantContext, id: string, signal: AbortSignal): Promise<AdvanceResult>;
9
9
  /**
10
10
  * Takeover (§10.5 step 2, §11.4), inside the transaction that took ownership: the dead
11
- * owner's `invoking` effects become `uncertain` and are never invoked again. When one belongs
11
+ * owner's `invoking` effects become `uncertain` and are never invoked again, except model
12
+ * calls when the gate recovers them (P1.2) and remote MCP calls when the Tool Gate does (F4.1
13
+ * G3): those stay `invoking`, and the replay re-sends them. When one belongs
12
14
  * to the active turn, the session becomes `uncertain` too, with an `effect.uncertain` event.
13
15
  * Mutates and writes `s`. Returns the effect ids it marked.
14
16
  */
15
- export declare function takeOver(t: Tx, s: Session): Promise<string[]>;
17
+ export declare function takeOver(t: Tx, s: Session, options?: {
18
+ recoversModelCalls?: boolean;
19
+ recoversMcpCalls?: boolean;
20
+ }): Promise<string[]>;