@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.
- package/CHANGELOG.md +104 -0
- package/README.md +75 -33
- package/dist/adapters/execution/restate.js +2 -2
- package/dist/adapters/replication/pgoutput.d.ts +5 -0
- package/dist/adapters/replication/pgoutput.js +4 -3
- package/dist/admin-openapi.json +184 -685
- package/dist/api/components.d.ts +308 -68
- package/dist/api/components.js +4 -5
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -0
- package/dist/api/http/define.js +0 -8
- package/dist/api/http/routes/access.js +25 -18
- package/dist/api/http/routes/tenant.d.ts +2 -1
- package/dist/api/http/routes/tenant.js +77 -5
- package/dist/api/http/routes/vaults.js +2 -2
- package/dist/api/openapi.js +4 -5
- package/dist/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +7 -6
- package/dist/host/admin-api.js +9 -113
- package/dist/host/app.d.ts +1 -1
- package/dist/host/app.js +20 -24
- package/dist/host/config-for.d.ts +1 -1
- package/dist/host/config-for.js +2 -2
- package/dist/host/create-host.d.ts +5 -5
- package/dist/host/create-host.js +5 -15
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +6 -1
- package/dist/host/http.js +17 -0
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +151 -71
- package/dist/host/stack-config.d.ts +71 -19
- package/dist/host/stack-config.js +192 -18
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +21 -3
- package/dist/mcp/pool.js +15 -2
- package/dist/openapi.json +887 -694
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/{store/event.js → record/envelope.js} +1 -1
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -0
- package/dist/store/postgres/connect.d.ts +15 -12
- package/dist/store/postgres/connect.js +24 -12
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +2 -8
- package/dist/store/postgres/record.js +50 -54
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +481 -567
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +43 -87
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +1 -1
- package/dist/streams/control.js +6 -2
- package/dist/streams/relay/types.d.ts +2 -1
- package/dist/streams/relay/types.js +2 -1
- package/dist/streams/types.d.ts +5 -0
- package/dist/tenant/advance.d.ts +7 -2
- package/dist/tenant/advance.js +95 -16
- package/dist/tenant/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.js +4 -2
- package/dist/tenant/context.d.ts +26 -3
- package/dist/tenant/delivery-token.js +17 -18
- package/dist/tenant/delivery.d.ts +1 -1
- package/dist/tenant/delivery.js +18 -13
- package/dist/tenant/effects.d.ts +15 -1
- package/dist/tenant/effects.js +93 -17
- package/dist/tenant/ephemeral.d.ts +16 -7
- package/dist/tenant/ephemeral.js +45 -111
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- package/dist/tenant/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +14 -28
- package/dist/tenant/module.js +131 -271
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -1
- package/dist/tenant/pool.js +1 -1
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/runtime.d.ts +19 -1
- package/dist/tenant/runtime.js +40 -26
- package/dist/tenant/scheduler.d.ts +4 -2
- package/dist/tenant/scheduler.js +4 -4
- package/dist/tenant/session-streams.d.ts +2 -0
- package/dist/tenant/session-streams.js +8 -3
- package/dist/tenant/status.d.ts +3 -0
- package/dist/tenant/status.js +1 -1
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +9 -25
- package/dist/tenant/streams.js +7 -22
- package/dist/tenant/tokens.js +16 -14
- package/dist/tenant/types.d.ts +41 -92
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.js +4 -4
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +26 -15
- package/dist/vault/service.js +70 -109
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -5
- package/dist/store/memory.d.ts +0 -69
- package/dist/store/memory.js +0 -810
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +0 -8
- package/dist/store/postgres/migrations/003_subject_tokens.js +0 -42
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +0 -6
- package/dist/store/postgres/migrations/004_publishable_keys.js +0 -18
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +0 -6
- package/dist/store/postgres/migrations/005_action_endpoints.js +0 -32
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +0 -11
- package/dist/store/postgres/migrations/006_drop_executors.js +0 -32
- package/dist/store/postgres/migrations/007_event_time.d.ts +0 -7
- package/dist/store/postgres/migrations/007_event_time.js +0 -14
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +0 -13
- package/dist/store/postgres/migrations/008_durable_streams.js +0 -35
- package/dist/store/postgres/migrations/index.d.ts +0 -53
- package/dist/store/postgres/migrations/index.js +0 -96
- package/dist/store/postgres/migrations/shared/index.d.ts +0 -31
- package/dist/store/postgres/migrations/shared/index.js +0 -92
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -228
- package/dist/streams/relay/memory.d.ts +0 -69
- package/dist/streams/relay/memory.js +0 -176
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- package/dist/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
- /package/dist/{store/event.d.ts → record/envelope.d.ts} +0 -0
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* The first Postgres Tenant schema.
|
|
4
|
-
*
|
|
5
|
-
* - `tenant` holds the Tenant envelope (one row).
|
|
6
|
-
* - Document tables keep `id text primary key, body json`. The fields typed
|
|
7
|
-
* queries filter on are stored generated columns with indexes. Ids and
|
|
8
|
-
* timestamps compared as text use `COLLATE "C"`, so ordering is by code
|
|
9
|
-
* point, like the in-memory fake, whatever the database collation.
|
|
10
|
-
* - Bodies are `json`, not `jsonb`, so every string round-trips exactly as
|
|
11
|
-
* `JSON.stringify` wrote it. `jsonb` rejects the escapes `\u0000` (`22P05`)
|
|
12
|
-
* and unpaired surrogates (`\ud800`), which tool output can contain and
|
|
13
|
-
* which the in-memory store accepts. `json` stores the text
|
|
14
|
-
* verbatim and checks only its syntax. The generated columns read the body
|
|
15
|
-
* through `doc(body)`, which casts to `jsonb` after replacing those two
|
|
16
|
-
* escapes with `\ufffd`: the stored body is untouched, and only an indexed
|
|
17
|
-
* field that itself contained one (never an id, status or timestamp) would
|
|
18
|
-
* see U+FFFD. Bodies with no `\u` escape at all take the plain cast. Queries
|
|
19
|
-
* never apply `->`/`->>` to a `json` body (Postgres de-escapes the whole text
|
|
20
|
-
* and fails on the same escapes); they read generated columns.
|
|
21
|
-
* - `sessions` carries the store-managed `next_event_seq` and ownership
|
|
22
|
-
* columns (`owner`, `epoch`, `owner_expires_at`), which are never in `body`.
|
|
23
|
-
* - `outbox(session_id, seq, body)` holds committed, unrelayed events.
|
|
24
|
-
* - Executors, principals, vault and settings keep typed columns. Vault
|
|
25
|
-
* ciphertext is `bytea` columns, never inside a JSON body.
|
|
26
|
-
*/
|
|
27
|
-
export declare const initial: Migration;
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The first Postgres Tenant schema.
|
|
3
|
-
*
|
|
4
|
-
* - `tenant` holds the Tenant envelope (one row).
|
|
5
|
-
* - Document tables keep `id text primary key, body json`. The fields typed
|
|
6
|
-
* queries filter on are stored generated columns with indexes. Ids and
|
|
7
|
-
* timestamps compared as text use `COLLATE "C"`, so ordering is by code
|
|
8
|
-
* point, like the in-memory fake, whatever the database collation.
|
|
9
|
-
* - Bodies are `json`, not `jsonb`, so every string round-trips exactly as
|
|
10
|
-
* `JSON.stringify` wrote it. `jsonb` rejects the escapes `\u0000` (`22P05`)
|
|
11
|
-
* and unpaired surrogates (`\ud800`), which tool output can contain and
|
|
12
|
-
* which the in-memory store accepts. `json` stores the text
|
|
13
|
-
* verbatim and checks only its syntax. The generated columns read the body
|
|
14
|
-
* through `doc(body)`, which casts to `jsonb` after replacing those two
|
|
15
|
-
* escapes with `\ufffd`: the stored body is untouched, and only an indexed
|
|
16
|
-
* field that itself contained one (never an id, status or timestamp) would
|
|
17
|
-
* see U+FFFD. Bodies with no `\u` escape at all take the plain cast. Queries
|
|
18
|
-
* never apply `->`/`->>` to a `json` body (Postgres de-escapes the whole text
|
|
19
|
-
* and fails on the same escapes); they read generated columns.
|
|
20
|
-
* - `sessions` carries the store-managed `next_event_seq` and ownership
|
|
21
|
-
* columns (`owner`, `epoch`, `owner_expires_at`), which are never in `body`.
|
|
22
|
-
* - `outbox(session_id, seq, body)` holds committed, unrelayed events.
|
|
23
|
-
* - Executors, principals, vault and settings keep typed columns. Vault
|
|
24
|
-
* ciphertext is `bytea` columns, never inside a JSON body.
|
|
25
|
-
*/
|
|
26
|
-
export const initial = {
|
|
27
|
-
version: 1,
|
|
28
|
-
name: "initial",
|
|
29
|
-
up: (s) => `
|
|
30
|
-
CREATE FUNCTION ${s}.doc(body json) RETURNS jsonb
|
|
31
|
-
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS $$
|
|
32
|
-
SELECT CASE WHEN strpos(body::text, '\\u') = 0 THEN body::jsonb
|
|
33
|
-
ELSE regexp_replace(
|
|
34
|
-
body::text,
|
|
35
|
-
'(?<!\\\\)((?:\\\\\\\\)*)\\\\u(0000|[dD][89a-fA-F][0-9a-fA-F]{2})',
|
|
36
|
-
'\\1\\\\ufffd',
|
|
37
|
-
'g'
|
|
38
|
-
)::jsonb
|
|
39
|
-
END
|
|
40
|
-
$$;
|
|
41
|
-
|
|
42
|
-
CREATE TABLE ${s}.tenant (
|
|
43
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
44
|
-
name text NOT NULL,
|
|
45
|
-
created_at text NOT NULL,
|
|
46
|
-
updated_at text NOT NULL,
|
|
47
|
-
schema_version integer NOT NULL,
|
|
48
|
-
singleton boolean NOT NULL DEFAULT true UNIQUE CHECK (singleton)
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
CREATE TABLE ${s}.definitions (
|
|
52
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
53
|
-
body json NOT NULL
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
CREATE TABLE ${s}.sessions (
|
|
57
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
58
|
-
body json NOT NULL,
|
|
59
|
-
status text GENERATED ALWAYS AS (${s}.doc(body)->>'status') STORED,
|
|
60
|
-
agent_id text GENERATED ALWAYS AS (${s}.doc(body)->>'agentId') STORED,
|
|
61
|
-
stream_incarnation text GENERATED ALWAYS AS (${s}.doc(body)->>'streamIncarnation') STORED,
|
|
62
|
-
next_event_seq bigint NOT NULL DEFAULT 0,
|
|
63
|
-
owner text,
|
|
64
|
-
epoch bigint NOT NULL DEFAULT 0,
|
|
65
|
-
owner_expires_at timestamptz
|
|
66
|
-
);
|
|
67
|
-
CREATE INDEX sessions_status ON ${s}.sessions (status, owner_expires_at);
|
|
68
|
-
CREATE INDEX sessions_agent ON ${s}.sessions (agent_id);
|
|
69
|
-
|
|
70
|
-
CREATE TABLE ${s}.commands (
|
|
71
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
72
|
-
body json NOT NULL
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
CREATE TABLE ${s}.checkpoints (
|
|
76
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
77
|
-
body json NOT NULL
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
CREATE TABLE ${s}.effects (
|
|
81
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
82
|
-
body json NOT NULL,
|
|
83
|
-
session_id text GENERATED ALWAYS AS (${s}.doc(body)->'request'->>'sessionId') STORED,
|
|
84
|
-
turn_id text GENERATED ALWAYS AS (${s}.doc(body)->'request'->>'turnId') STORED,
|
|
85
|
-
kind text GENERATED ALWAYS AS (${s}.doc(body)->'request'->>'kind') STORED,
|
|
86
|
-
status text GENERATED ALWAYS AS (${s}.doc(body)->>'status') STORED
|
|
87
|
-
);
|
|
88
|
-
CREATE INDEX effects_session ON ${s}.effects (session_id, turn_id, status);
|
|
89
|
-
CREATE INDEX effects_status ON ${s}.effects (status, kind);
|
|
90
|
-
|
|
91
|
-
CREATE TABLE ${s}.actions (
|
|
92
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
93
|
-
body json NOT NULL,
|
|
94
|
-
session_id text GENERATED ALWAYS AS (${s}.doc(body)->>'sessionId') STORED,
|
|
95
|
-
turn_id text GENERATED ALWAYS AS (${s}.doc(body)->>'turnId') STORED,
|
|
96
|
-
agent_id text GENERATED ALWAYS AS (${s}.doc(body)->>'agentId') STORED,
|
|
97
|
-
status text GENERATED ALWAYS AS (${s}.doc(body)->>'status') STORED,
|
|
98
|
-
kind text GENERATED ALWAYS AS (${s}.doc(body)->>'kind') STORED,
|
|
99
|
-
lease_expires_at text GENERATED ALWAYS AS (${s}.doc(body)->>'leaseExpiresAt') STORED
|
|
100
|
-
);
|
|
101
|
-
CREATE INDEX actions_session ON ${s}.actions (session_id, turn_id, status);
|
|
102
|
-
CREATE INDEX actions_agent ON ${s}.actions (agent_id, status);
|
|
103
|
-
CREATE INDEX actions_status ON ${s}.actions (status, kind);
|
|
104
|
-
CREATE INDEX actions_lease ON ${s}.actions (status, lease_expires_at);
|
|
105
|
-
|
|
106
|
-
CREATE TABLE ${s}.sandboxes (
|
|
107
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
108
|
-
body json NOT NULL
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
CREATE TABLE ${s}.links (
|
|
112
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
113
|
-
body json NOT NULL,
|
|
114
|
-
workflow_session_id text GENERATED ALWAYS AS (${s}.doc(body)->>'workflowSessionId') STORED
|
|
115
|
-
);
|
|
116
|
-
CREATE INDEX links_workflow ON ${s}.links (workflow_session_id);
|
|
117
|
-
|
|
118
|
-
CREATE TABLE ${s}.outbox (
|
|
119
|
-
session_id text COLLATE "C" NOT NULL,
|
|
120
|
-
seq bigint NOT NULL,
|
|
121
|
-
body json NOT NULL,
|
|
122
|
-
created_at text COLLATE "C" GENERATED ALWAYS AS (${s}.doc(body)->>'createdAt') STORED,
|
|
123
|
-
PRIMARY KEY (session_id, seq)
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
CREATE TABLE ${s}.executors (
|
|
127
|
-
agent_id text COLLATE "C" PRIMARY KEY,
|
|
128
|
-
token_hash text NOT NULL UNIQUE,
|
|
129
|
-
implementation_version text NOT NULL,
|
|
130
|
-
manifest_hash text,
|
|
131
|
-
principal_id text,
|
|
132
|
-
created_at text NOT NULL,
|
|
133
|
-
updated_at text NOT NULL
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
CREATE TABLE ${s}.principals (
|
|
137
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
138
|
-
role text NOT NULL,
|
|
139
|
-
token_hash text NOT NULL UNIQUE,
|
|
140
|
-
idempotency_key text,
|
|
141
|
-
created_at text NOT NULL
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
CREATE TABLE ${s}.vaults (
|
|
145
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
146
|
-
name text NOT NULL,
|
|
147
|
-
owner_user_id text NOT NULL,
|
|
148
|
-
metadata_json text,
|
|
149
|
-
created_at text COLLATE "C" NOT NULL,
|
|
150
|
-
scope text NOT NULL DEFAULT 'user' CHECK (scope IN ('user', 'host'))
|
|
151
|
-
);
|
|
152
|
-
CREATE INDEX vaults_owner ON ${s}.vaults (owner_user_id, created_at, id);
|
|
153
|
-
CREATE UNIQUE INDEX vaults_one_host ON ${s}.vaults (scope) WHERE scope = 'host';
|
|
154
|
-
|
|
155
|
-
CREATE TABLE ${s}.vault_credentials (
|
|
156
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
157
|
-
vault_id text NOT NULL REFERENCES ${s}.vaults (id) ON DELETE CASCADE,
|
|
158
|
-
name text NOT NULL,
|
|
159
|
-
type text NOT NULL,
|
|
160
|
-
binding_json text NOT NULL,
|
|
161
|
-
expires_at text,
|
|
162
|
-
created_at text COLLATE "C" NOT NULL,
|
|
163
|
-
rotated_at text,
|
|
164
|
-
kek_id text NOT NULL,
|
|
165
|
-
nonce bytea NOT NULL,
|
|
166
|
-
ciphertext bytea NOT NULL,
|
|
167
|
-
wrapped_dek bytea NOT NULL
|
|
168
|
-
);
|
|
169
|
-
CREATE INDEX vault_credentials_vault ON ${s}.vault_credentials (vault_id, created_at, id);
|
|
170
|
-
|
|
171
|
-
CREATE TABLE ${s}.vault_audit (
|
|
172
|
-
ord bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
173
|
-
id text NOT NULL UNIQUE,
|
|
174
|
-
at text NOT NULL,
|
|
175
|
-
actor text NOT NULL,
|
|
176
|
-
action text NOT NULL,
|
|
177
|
-
vault_id text,
|
|
178
|
-
credential_id text,
|
|
179
|
-
session_id text,
|
|
180
|
-
target text,
|
|
181
|
-
outcome text NOT NULL
|
|
182
|
-
);
|
|
183
|
-
CREATE INDEX vault_audit_vault ON ${s}.vault_audit (vault_id, ord);
|
|
184
|
-
|
|
185
|
-
CREATE TABLE ${s}.vault_idempotency (
|
|
186
|
-
id text PRIMARY KEY,
|
|
187
|
-
body_hash text NOT NULL,
|
|
188
|
-
response text NOT NULL
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
CREATE TABLE ${s}.tenant_settings (
|
|
192
|
-
key text PRIMARY KEY,
|
|
193
|
-
value text NOT NULL
|
|
194
|
-
);
|
|
195
|
-
`,
|
|
196
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* The session owner (`ownerUserId`) as a stored generated column with an index, so a request
|
|
4
|
-
* acting for a subject lists only that subject's sessions. Not the lease column `owner`.
|
|
5
|
-
*/
|
|
6
|
-
export declare const sessionOwner: Migration;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The session owner (`ownerUserId`) as a stored generated column with an index, so a request
|
|
3
|
-
* acting for a subject lists only that subject's sessions. Not the lease column `owner`.
|
|
4
|
-
*/
|
|
5
|
-
export const sessionOwner = {
|
|
6
|
-
version: 2,
|
|
7
|
-
name: "session_owner",
|
|
8
|
-
up: (s) => `
|
|
9
|
-
ALTER TABLE ${s}.sessions
|
|
10
|
-
ADD COLUMN owner_user_id text GENERATED ALWAYS AS (${s}.doc(body)->>'ownerUserId') STORED;
|
|
11
|
-
CREATE INDEX sessions_owner_user ON ${s}.sessions (owner_user_id);
|
|
12
|
-
`,
|
|
13
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Subject tokens (Host feature `subject-tokens`): the Tenant's signing keys (private halves
|
|
4
|
-
* sealed with the vault KEK, like credentials), each subject's revocation epoch, each
|
|
5
|
-
* subject's turn bucket. Counting a subject's sessions by status uses the owner index of
|
|
6
|
-
* migration 2; a second index on status would be written on every session transition.
|
|
7
|
-
*/
|
|
8
|
-
export declare const subjectTokens: Migration;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subject tokens (Host feature `subject-tokens`): the Tenant's signing keys (private halves
|
|
3
|
-
* sealed with the vault KEK, like credentials), each subject's revocation epoch, each
|
|
4
|
-
* subject's turn bucket. Counting a subject's sessions by status uses the owner index of
|
|
5
|
-
* migration 2; a second index on status would be written on every session transition.
|
|
6
|
-
*/
|
|
7
|
-
export const subjectTokens = {
|
|
8
|
-
version: 3,
|
|
9
|
-
name: "subject_tokens",
|
|
10
|
-
up: (s) => `
|
|
11
|
-
CREATE TABLE ${s}.signing_keys (
|
|
12
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
13
|
-
state text NOT NULL CHECK (state IN ('standby', 'current', 'previous', 'revoked')),
|
|
14
|
-
alg text NOT NULL CHECK (alg = 'ES256'),
|
|
15
|
-
public_jwk text NOT NULL,
|
|
16
|
-
kek_id text NOT NULL,
|
|
17
|
-
nonce bytea NOT NULL,
|
|
18
|
-
ciphertext bytea NOT NULL,
|
|
19
|
-
wrapped_dek bytea NOT NULL,
|
|
20
|
-
created_at text NOT NULL,
|
|
21
|
-
activated_at text,
|
|
22
|
-
retired_at text,
|
|
23
|
-
revoked_at text
|
|
24
|
-
);
|
|
25
|
-
CREATE UNIQUE INDEX signing_keys_one_standby ON ${s}.signing_keys (state) WHERE state = 'standby';
|
|
26
|
-
CREATE UNIQUE INDEX signing_keys_one_current ON ${s}.signing_keys (state) WHERE state = 'current';
|
|
27
|
-
CREATE UNIQUE INDEX signing_keys_one_previous ON ${s}.signing_keys (state) WHERE state = 'previous';
|
|
28
|
-
|
|
29
|
-
CREATE TABLE ${s}.subject_epochs (
|
|
30
|
-
subject text COLLATE "C" PRIMARY KEY,
|
|
31
|
-
epoch bigint NOT NULL,
|
|
32
|
-
revoked_at text NOT NULL
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
CREATE TABLE ${s}.subject_usage (
|
|
36
|
-
subject text COLLATE "C" PRIMARY KEY,
|
|
37
|
-
turn_tokens double precision NOT NULL,
|
|
38
|
-
refilled_at text NOT NULL
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
`,
|
|
42
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Publishable keys (Host feature `browser-access`): public by design, so stored as they are,
|
|
3
|
-
* with the origins a browser may use each from.
|
|
4
|
-
*/
|
|
5
|
-
export const publishableKeys = {
|
|
6
|
-
version: 4,
|
|
7
|
-
name: "publishable_keys",
|
|
8
|
-
up: (s) => `
|
|
9
|
-
CREATE TABLE ${s}.publishable_keys (
|
|
10
|
-
id text COLLATE "C" PRIMARY KEY,
|
|
11
|
-
key text COLLATE "C" NOT NULL UNIQUE,
|
|
12
|
-
name text NOT NULL UNIQUE,
|
|
13
|
-
origins_json text NOT NULL,
|
|
14
|
-
created_at text NOT NULL,
|
|
15
|
-
revoked_at text
|
|
16
|
-
);
|
|
17
|
-
`,
|
|
18
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Action endpoints: where the Runtime delivers each agent's Actions over HTTP, with the health
|
|
4
|
-
* recent deliveries report, and the deadline of an Action being delivered.
|
|
5
|
-
*/
|
|
6
|
-
export declare const actionEndpoints: Migration;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Action endpoints: where the Runtime delivers each agent's Actions over HTTP, with the health
|
|
3
|
-
* recent deliveries report, and the deadline of an Action being delivered.
|
|
4
|
-
*/
|
|
5
|
-
export const actionEndpoints = {
|
|
6
|
-
version: 5,
|
|
7
|
-
name: "action_endpoints",
|
|
8
|
-
up: (s) => `
|
|
9
|
-
CREATE TABLE ${s}.endpoints (
|
|
10
|
-
agent_id text COLLATE "C" PRIMARY KEY,
|
|
11
|
-
url text NOT NULL,
|
|
12
|
-
implementation_version text NOT NULL,
|
|
13
|
-
manifest_hash text,
|
|
14
|
-
timeout_ms integer NOT NULL,
|
|
15
|
-
max_concurrent integer NOT NULL,
|
|
16
|
-
principal_id text,
|
|
17
|
-
last_delivery_at text,
|
|
18
|
-
last_success_at text,
|
|
19
|
-
last_error_code text,
|
|
20
|
-
last_error_message text,
|
|
21
|
-
consecutive_failures integer NOT NULL DEFAULT 0,
|
|
22
|
-
served_implementation_version text,
|
|
23
|
-
served_manifest_hash text,
|
|
24
|
-
created_at text NOT NULL,
|
|
25
|
-
updated_at text NOT NULL
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
ALTER TABLE ${s}.actions
|
|
29
|
-
ADD COLUMN deadline_at text GENERATED ALWAYS AS (${s}.doc(body)->>'deadlineAt') STORED;
|
|
30
|
-
CREATE INDEX actions_deadline ON ${s}.actions (status, deadline_at);
|
|
31
|
-
`,
|
|
32
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Protocol 3 removes executors: Actions are only delivered to Action endpoints.
|
|
4
|
-
*
|
|
5
|
-
* An Action an executor had claimed becomes `delivering` with a deadline that has already
|
|
6
|
-
* passed, so the next sweep applies the delivery loss rule to it (`delivery.ts` `loseAction`):
|
|
7
|
-
* a tool becomes `uncertain` with its `action.uncertain` event, and a hook, `fn` or `verify` is
|
|
8
|
-
* delivered again. Open Actions lose the executor-only `claimId` and `leaseExpiresAt`; settled
|
|
9
|
-
* ones are never delivered again and keep them.
|
|
10
|
-
*/
|
|
11
|
-
export declare const dropExecutors: Migration;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Protocol 3 removes executors: Actions are only delivered to Action endpoints.
|
|
3
|
-
*
|
|
4
|
-
* An Action an executor had claimed becomes `delivering` with a deadline that has already
|
|
5
|
-
* passed, so the next sweep applies the delivery loss rule to it (`delivery.ts` `loseAction`):
|
|
6
|
-
* a tool becomes `uncertain` with its `action.uncertain` event, and a hook, `fn` or `verify` is
|
|
7
|
-
* delivered again. Open Actions lose the executor-only `claimId` and `leaseExpiresAt`; settled
|
|
8
|
-
* ones are never delivered again and keep them.
|
|
9
|
-
*/
|
|
10
|
-
export const dropExecutors = {
|
|
11
|
-
version: 6,
|
|
12
|
-
name: "drop_executors",
|
|
13
|
-
up: (s) => `
|
|
14
|
-
DROP INDEX ${s}.actions_lease;
|
|
15
|
-
ALTER TABLE ${s}.actions DROP COLUMN lease_expires_at;
|
|
16
|
-
|
|
17
|
-
UPDATE ${s}.actions
|
|
18
|
-
SET body = (
|
|
19
|
-
${s}.doc(body) - 'claimId' - 'leaseExpiresAt'
|
|
20
|
-
|| jsonb_build_object(
|
|
21
|
-
'status', 'delivering',
|
|
22
|
-
'deadlineAt', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
23
|
-
)
|
|
24
|
-
)::json
|
|
25
|
-
WHERE status = 'claimed';
|
|
26
|
-
UPDATE ${s}.actions
|
|
27
|
-
SET body = (${s}.doc(body) - 'claimId' - 'leaseExpiresAt')::json
|
|
28
|
-
WHERE status IN ('pending', 'delivering');
|
|
29
|
-
|
|
30
|
-
DROP TABLE ${s}.executors;
|
|
31
|
-
`,
|
|
32
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Protocol 4 puts events on the `nylorun.event/2` envelope, where `createdAt` is `time`. The
|
|
4
|
-
* outbox's `created_at` (its age, for relay lag) is generated from either, so rows written
|
|
5
|
-
* before the upgrade keep theirs.
|
|
6
|
-
*/
|
|
7
|
-
export declare const eventTime: Migration;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Protocol 4 puts events on the `nylorun.event/2` envelope, where `createdAt` is `time`. The
|
|
3
|
-
* outbox's `created_at` (its age, for relay lag) is generated from either, so rows written
|
|
4
|
-
* before the upgrade keep theirs.
|
|
5
|
-
*/
|
|
6
|
-
export const eventTime = {
|
|
7
|
-
version: 7,
|
|
8
|
-
name: "event_time",
|
|
9
|
-
up: (s) => `
|
|
10
|
-
ALTER TABLE ${s}.outbox DROP COLUMN created_at;
|
|
11
|
-
ALTER TABLE ${s}.outbox ADD COLUMN created_at text COLLATE "C"
|
|
12
|
-
GENERATED ALWAYS AS (coalesce(${s}.doc(body)->>'time', ${s}.doc(body)->>'createdAt')) STORED;
|
|
13
|
-
`,
|
|
14
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Migration } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Durable Streams v1 (Durable Streams §18.1): session events move from the Tenant's outbox to
|
|
4
|
-
* the shared record (`nylorun_streams`), and stream names lose their incarnation.
|
|
5
|
-
*
|
|
6
|
-
* **A fresh start for session data.** A Tenant that has sessions loses them, with their
|
|
7
|
-
* commands, checkpoints, effects, Actions, links, outbox and turn buckets, as a
|
|
8
|
-
* sessions-scope reset does; settings, agents, Action endpoints, keys, policy and vaults stay.
|
|
9
|
-
* It moves to basin generation 1, and generation 0 (the basin holding the old streams) is
|
|
10
|
-
* retired, so the Runtime deletes it when the Tenant opens. A Tenant without sessions stays at
|
|
11
|
-
* generation 0: its basin holds no session stream it could collide with.
|
|
12
|
-
*/
|
|
13
|
-
export declare const durableStreams: Migration;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable Streams v1 (Durable Streams §18.1): session events move from the Tenant's outbox to
|
|
3
|
-
* the shared record (`nylorun_streams`), and stream names lose their incarnation.
|
|
4
|
-
*
|
|
5
|
-
* **A fresh start for session data.** A Tenant that has sessions loses them, with their
|
|
6
|
-
* commands, checkpoints, effects, Actions, links, outbox and turn buckets, as a
|
|
7
|
-
* sessions-scope reset does; settings, agents, Action endpoints, keys, policy and vaults stay.
|
|
8
|
-
* It moves to basin generation 1, and generation 0 (the basin holding the old streams) is
|
|
9
|
-
* retired, so the Runtime deletes it when the Tenant opens. A Tenant without sessions stays at
|
|
10
|
-
* generation 0: its basin holds no session stream it could collide with.
|
|
11
|
-
*/
|
|
12
|
-
export const durableStreams = {
|
|
13
|
-
version: 8,
|
|
14
|
-
name: "durable_streams",
|
|
15
|
-
up: (s) => `
|
|
16
|
-
ALTER TABLE ${s}.tenant
|
|
17
|
-
ADD COLUMN basin_generation integer NOT NULL DEFAULT 0,
|
|
18
|
-
ADD COLUMN retired_generations integer[] NOT NULL DEFAULT '{}';
|
|
19
|
-
UPDATE ${s}.tenant SET basin_generation = 1, retired_generations = '{0}'
|
|
20
|
-
WHERE EXISTS (SELECT 1 FROM ${s}.sessions);
|
|
21
|
-
|
|
22
|
-
DELETE FROM ${s}.links;
|
|
23
|
-
DELETE FROM ${s}.actions;
|
|
24
|
-
DELETE FROM ${s}.effects;
|
|
25
|
-
DELETE FROM ${s}.checkpoints;
|
|
26
|
-
DELETE FROM ${s}.commands;
|
|
27
|
-
DELETE FROM ${s}.sessions;
|
|
28
|
-
DELETE FROM ${s}.subject_usage;
|
|
29
|
-
DROP TABLE ${s}.outbox;
|
|
30
|
-
|
|
31
|
-
ALTER TABLE ${s}.sessions
|
|
32
|
-
DROP COLUMN stream_incarnation,
|
|
33
|
-
DROP COLUMN next_event_seq;
|
|
34
|
-
`,
|
|
35
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Forward-only, numbered migrations of one Tenant schema (architecture §14.6).
|
|
3
|
-
*
|
|
4
|
-
* Each schema records what it has applied in `<schema>.schema_version`, one
|
|
5
|
-
* row per migration; its version is the highest row. Migrating runs every
|
|
6
|
-
* missing step in order, in the caller's transaction, under a transaction-level
|
|
7
|
-
* advisory lock on the schema so two processes never migrate the same schema
|
|
8
|
-
* at once. A schema newer than the Runtime is never touched: migrating it
|
|
9
|
-
* throws a `schema-too-new` quarantine and the Tenant is quarantined.
|
|
10
|
-
*
|
|
11
|
-
* Adding a migration: append `NNN_name.ts` with the next version to
|
|
12
|
-
* `MIGRATIONS`. Never edit or reorder an applied migration.
|
|
13
|
-
*/
|
|
14
|
-
import type { Sql, TransactionSql } from "postgres";
|
|
15
|
-
export interface Migration {
|
|
16
|
-
/** 1, 2, 3, … without gaps. */
|
|
17
|
-
version: number;
|
|
18
|
-
name: string;
|
|
19
|
-
/** DDL for the quoted schema name `s` (for example `"tenant_tn_…"`). */
|
|
20
|
-
up(s: string): string;
|
|
21
|
-
}
|
|
22
|
-
export declare const MIGRATIONS: readonly Migration[];
|
|
23
|
-
/** The schema version this Runtime writes and expects. */
|
|
24
|
-
export declare const POSTGRES_SCHEMA_VERSION: number;
|
|
25
|
-
/** Checks that versions run 1, 2, 3, … Throws otherwise. */
|
|
26
|
-
export declare function assertMigrations(migrations: readonly Migration[]): void;
|
|
27
|
-
/**
|
|
28
|
-
* Takes the schema's migration lock until the transaction ends. Waiting for it, and the
|
|
29
|
-
* migrations or deletion done under it, may outlast the pool's statement timeout: it is lifted
|
|
30
|
-
* for the transaction, since a migration that times out would quarantine the Tenant.
|
|
31
|
-
*/
|
|
32
|
-
export declare function lockSchema(tx: TransactionSql, schema: string): Promise<void>;
|
|
33
|
-
export declare function schemaExists(sql: Sql | TransactionSql, schema: string): Promise<boolean>;
|
|
34
|
-
/**
|
|
35
|
-
* The schema's version: 0 when it exists without a `schema_version` table,
|
|
36
|
-
* undefined when the schema does not exist.
|
|
37
|
-
*/
|
|
38
|
-
export declare function readSchemaVersion(sql: Sql | TransactionSql, schema: string): Promise<number | undefined>;
|
|
39
|
-
/**
|
|
40
|
-
* Creates the schema when missing and applies every missing migration, inside
|
|
41
|
-
* `tx`. Call `lockSchema` first when other processes may migrate concurrently
|
|
42
|
-
* (`migrateSchema` does). Throws a `schema-too-new` `QuarantineError` when the
|
|
43
|
-
* schema is ahead of `migrations`.
|
|
44
|
-
*/
|
|
45
|
-
export declare function migrateSchemaInTx(tx: TransactionSql, schema: string, migrations?: readonly Migration[]): Promise<{
|
|
46
|
-
from: number;
|
|
47
|
-
to: number;
|
|
48
|
-
}>;
|
|
49
|
-
/** `migrateSchemaInTx` in its own transaction, under the schema's migration lock. */
|
|
50
|
-
export declare function migrateSchema(sql: Sql, schema: string, migrations?: readonly Migration[]): Promise<{
|
|
51
|
-
from: number;
|
|
52
|
-
to: number;
|
|
53
|
-
}>;
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { quarantine } from "../../../tenant/quarantine.js";
|
|
2
|
-
import { quoteIdentifier, tenantIdFromSchema } from "../names.js";
|
|
3
|
-
import { initial } from "./001_initial.js";
|
|
4
|
-
import { sessionOwner } from "./002_session_owner.js";
|
|
5
|
-
import { subjectTokens } from "./003_subject_tokens.js";
|
|
6
|
-
import { publishableKeys } from "./004_publishable_keys.js";
|
|
7
|
-
import { actionEndpoints } from "./005_action_endpoints.js";
|
|
8
|
-
import { dropExecutors } from "./006_drop_executors.js";
|
|
9
|
-
import { eventTime } from "./007_event_time.js";
|
|
10
|
-
import { durableStreams } from "./008_durable_streams.js";
|
|
11
|
-
export const MIGRATIONS = [
|
|
12
|
-
initial,
|
|
13
|
-
sessionOwner,
|
|
14
|
-
subjectTokens,
|
|
15
|
-
publishableKeys,
|
|
16
|
-
actionEndpoints,
|
|
17
|
-
dropExecutors,
|
|
18
|
-
eventTime,
|
|
19
|
-
durableStreams,
|
|
20
|
-
];
|
|
21
|
-
/** The schema version this Runtime writes and expects. */
|
|
22
|
-
export const POSTGRES_SCHEMA_VERSION = MIGRATIONS.at(-1).version;
|
|
23
|
-
/** Checks that versions run 1, 2, 3, … Throws otherwise. */
|
|
24
|
-
export function assertMigrations(migrations) {
|
|
25
|
-
migrations.forEach((migration, index) => {
|
|
26
|
-
if (migration.version !== index + 1)
|
|
27
|
-
throw new Error(`Migration ${migration.name} has version ${migration.version}, expected ${index + 1}`);
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Takes the schema's migration lock until the transaction ends. Waiting for it, and the
|
|
32
|
-
* migrations or deletion done under it, may outlast the pool's statement timeout: it is lifted
|
|
33
|
-
* for the transaction, since a migration that times out would quarantine the Tenant.
|
|
34
|
-
*/
|
|
35
|
-
export async function lockSchema(tx, schema) {
|
|
36
|
-
await tx `SET LOCAL statement_timeout = 0`;
|
|
37
|
-
await tx `SELECT pg_advisory_xact_lock(hashtextextended(${`nylorun.schema:${schema}`}, 0))`;
|
|
38
|
-
}
|
|
39
|
-
export async function schemaExists(sql, schema) {
|
|
40
|
-
const rows = await sql `SELECT 1 FROM pg_namespace WHERE nspname = ${schema}`;
|
|
41
|
-
return rows.length > 0;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* The schema's version: 0 when it exists without a `schema_version` table,
|
|
45
|
-
* undefined when the schema does not exist.
|
|
46
|
-
*/
|
|
47
|
-
export async function readSchemaVersion(sql, schema) {
|
|
48
|
-
const [row] = await sql `
|
|
49
|
-
SELECT
|
|
50
|
-
EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = ${schema}) AS has_schema,
|
|
51
|
-
to_regclass(${`${quoteIdentifier(schema)}.schema_version`}) IS NOT NULL AS has_table`;
|
|
52
|
-
if (!row.has_schema)
|
|
53
|
-
return undefined;
|
|
54
|
-
if (!row.has_table)
|
|
55
|
-
return 0;
|
|
56
|
-
const [version] = await sql `
|
|
57
|
-
SELECT max(version)::int AS version FROM ${sql(`${schema}.schema_version`)}`;
|
|
58
|
-
return version?.version ?? 0;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Creates the schema when missing and applies every missing migration, inside
|
|
62
|
-
* `tx`. Call `lockSchema` first when other processes may migrate concurrently
|
|
63
|
-
* (`migrateSchema` does). Throws a `schema-too-new` `QuarantineError` when the
|
|
64
|
-
* schema is ahead of `migrations`.
|
|
65
|
-
*/
|
|
66
|
-
export async function migrateSchemaInTx(tx, schema, migrations = MIGRATIONS) {
|
|
67
|
-
assertMigrations(migrations);
|
|
68
|
-
const s = quoteIdentifier(schema);
|
|
69
|
-
const latest = migrations.length;
|
|
70
|
-
await tx.unsafe(`
|
|
71
|
-
CREATE SCHEMA IF NOT EXISTS ${s};
|
|
72
|
-
CREATE TABLE IF NOT EXISTS ${s}.schema_version (
|
|
73
|
-
version integer PRIMARY KEY,
|
|
74
|
-
name text NOT NULL,
|
|
75
|
-
applied_at timestamptz NOT NULL DEFAULT now()
|
|
76
|
-
);`);
|
|
77
|
-
const from = (await readSchemaVersion(tx, schema)) ?? 0;
|
|
78
|
-
if (from > latest)
|
|
79
|
-
throw quarantine("schema-too-new", `Tenant schema version ${from} is newer than this Runtime's ${latest}`, { tenantId: tenantIdFromSchema(schema) ?? schema });
|
|
80
|
-
for (const migration of migrations.slice(from)) {
|
|
81
|
-
await tx.unsafe(migration.up(s));
|
|
82
|
-
await tx `INSERT INTO ${tx(`${schema}.schema_version`)} (version, name)
|
|
83
|
-
VALUES (${migration.version}, ${migration.name})`;
|
|
84
|
-
}
|
|
85
|
-
if (from < latest)
|
|
86
|
-
await tx `UPDATE ${tx(`${schema}.tenant`)} SET schema_version = ${latest}`;
|
|
87
|
-
return { from, to: latest };
|
|
88
|
-
}
|
|
89
|
-
/** `migrateSchemaInTx` in its own transaction, under the schema's migration lock. */
|
|
90
|
-
export async function migrateSchema(sql, schema, migrations = MIGRATIONS) {
|
|
91
|
-
const result = await sql.begin(async (tx) => {
|
|
92
|
-
await lockSchema(tx, schema);
|
|
93
|
-
return { result: await migrateSchemaInTx(tx, schema, migrations) };
|
|
94
|
-
});
|
|
95
|
-
return result.result;
|
|
96
|
-
}
|