@nylorun/runtime 0.22.0-beta → 0.24.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 +161 -0
- package/README.md +95 -30
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +78 -84
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +3 -22
- package/dist/core/flow-host.js +11 -37
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +11 -9
- package/dist/execution/types.js +5 -7
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +64 -21
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +70 -0
- package/dist/host/stack-config.js +67 -1
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +46 -20
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +21 -15
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +1213 -779
- package/dist/store/postgres/schema.js +136 -76
- package/dist/store/postgres/store.js +283 -117
- package/dist/store/types.d.ts +206 -88
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +121 -49
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +94 -14
- package/dist/tenant/context.d.ts +67 -33
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +30 -4
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +168 -63
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +14 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -122
- package/dist/tenant/sandboxes.js +0 -562
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Every `tx` is one READ COMMITTED transaction on a pooled connection (Drizzle's
|
|
17
17
|
* `transaction` on postgres.js `begin`). Tables are addressed by schema-qualified names,
|
|
18
|
-
* never through `search_path`. Commit listeners and
|
|
18
|
+
* never through `search_path`. Commit listeners and outbox deliveries run after `COMMIT`
|
|
19
19
|
* returns and never reject the committed `tx`. Nested `tx` calls are detected with
|
|
20
20
|
* `AsyncLocalStorage` and rejected, and a `Tx` rejects every call once its callback settles.
|
|
21
21
|
* A failed statement rejects `tx` with the driver's error (`driverError`), not Drizzle's
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* head).
|
|
33
33
|
* 2. A transaction that touches several sessions locks them in ascending id
|
|
34
34
|
* order, with `lockSessions` (`./locking.ts`), before writing any of them.
|
|
35
|
-
* 3.
|
|
35
|
+
* 3. An Environment's row (`environment(id, { lock: true })`, which orders its
|
|
36
36
|
* lifecycle stream) is locked after every session row the transaction locks, never
|
|
37
37
|
* before one.
|
|
38
38
|
*
|
|
@@ -50,15 +50,15 @@
|
|
|
50
50
|
*/
|
|
51
51
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
52
52
|
import { randomUUID } from "node:crypto";
|
|
53
|
-
import { and, count, eq, getTableColumns, gte, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
|
|
54
|
-
import {
|
|
53
|
+
import { and, count, eq, getTableColumns, gte, gt, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
|
|
54
|
+
import { appendEnvironmentEvent, appendEvent } from "../../record/index.js";
|
|
55
55
|
import { OwnershipLostError, PrincipalRoleConflict } from "../ownership.js";
|
|
56
56
|
import { followControlSignals, pruneSignals, writeSignal } from "./control.js";
|
|
57
57
|
import { database, driverError } from "./db.js";
|
|
58
58
|
import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
|
|
59
59
|
import { createPostgresRecordReader } from "./record.js";
|
|
60
|
-
import {
|
|
61
|
-
import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes,
|
|
60
|
+
import { postgresEnvironmentRecordWriter, postgresRecordWriter } from "./record-writer.js";
|
|
61
|
+
import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, environmentEvents, environments, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, sandboxSignals, wakes, } from "./schema.js";
|
|
62
62
|
export function createPostgresSessionStore(options) {
|
|
63
63
|
return new PostgresSessionStore(options);
|
|
64
64
|
}
|
|
@@ -88,6 +88,9 @@ class PostgresSessionStore {
|
|
|
88
88
|
active = new AsyncLocalStorage();
|
|
89
89
|
listeners = new Set();
|
|
90
90
|
inflight = new Set();
|
|
91
|
+
/** Deliveries of committed outbox requests still running (`deliverTo`). */
|
|
92
|
+
deliveries = new Set();
|
|
93
|
+
deliver;
|
|
91
94
|
now;
|
|
92
95
|
onError;
|
|
93
96
|
closed = false;
|
|
@@ -111,7 +114,7 @@ class PostgresSessionStore {
|
|
|
111
114
|
let t;
|
|
112
115
|
// Wrapped so `begin` does not treat an array result as queries to await.
|
|
113
116
|
const run = this.db.transaction(async (db) => {
|
|
114
|
-
t = new PostgresTx(db, this.tenantId, this.now
|
|
117
|
+
t = new PostgresTx(db, this.tenantId, this.now);
|
|
115
118
|
try {
|
|
116
119
|
return { value: await this.active.run(this, () => fn(t)) };
|
|
117
120
|
}
|
|
@@ -144,20 +147,51 @@ class PostgresSessionStore {
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
await callback();
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
this.onError(error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
150
|
+
if (t.outbox.length > 0)
|
|
151
|
+
this.send(t.outbox);
|
|
155
152
|
return result.value;
|
|
156
153
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
deliverTo(deliver) {
|
|
155
|
+
this.deliver = deliver;
|
|
156
|
+
}
|
|
157
|
+
async delivered() {
|
|
158
|
+
while (this.deliveries.size > 0)
|
|
159
|
+
await Promise.allSettled([...this.deliveries]);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Hands a commit's outbox requests to the delivery, one after the other, without the caller
|
|
163
|
+
* waiting: a delivered one's row is deleted, and one that was not is due at the sweep's next
|
|
164
|
+
* pass.
|
|
165
|
+
*/
|
|
166
|
+
send(committed) {
|
|
167
|
+
const deliver = this.deliver;
|
|
168
|
+
if (!deliver)
|
|
169
|
+
return;
|
|
170
|
+
const run = (async () => {
|
|
171
|
+
for (const { kind, id, request } of committed) {
|
|
172
|
+
let sent = false;
|
|
173
|
+
try {
|
|
174
|
+
sent = await deliver(request);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
this.onError(error);
|
|
178
|
+
}
|
|
179
|
+
const table = OUTBOX[kind];
|
|
180
|
+
try {
|
|
181
|
+
if (sent === false)
|
|
182
|
+
await this.db.update(table).set({ retryAt: sql `clock_timestamp()` }).where(eq(table.id, id));
|
|
183
|
+
else
|
|
184
|
+
await this.db.delete(table).where(eq(table.id, id));
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
// The row stays as it was: the sweep sends it again, under the same key.
|
|
188
|
+
this.onError(error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
192
|
+
this.deliveries.add(run);
|
|
193
|
+
void run.finally(() => this.deliveries.delete(run));
|
|
194
|
+
}
|
|
161
195
|
onCommit(listener) {
|
|
162
196
|
this.listeners.add(listener);
|
|
163
197
|
return () => this.listeners.delete(listener);
|
|
@@ -186,10 +220,14 @@ class PostgresSessionStore {
|
|
|
186
220
|
throw new Error("SessionStore is closed");
|
|
187
221
|
return followControlSignals(this.sql, this.db, onSignal, options);
|
|
188
222
|
}
|
|
189
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* Rejects new transactions and waits for running ones, and for the deliveries of committed
|
|
225
|
+
* outbox requests. Does not end the pool.
|
|
226
|
+
*/
|
|
190
227
|
async close() {
|
|
191
228
|
this.closed = true;
|
|
192
229
|
await Promise.allSettled([...this.inflight]);
|
|
230
|
+
await this.delivered();
|
|
193
231
|
}
|
|
194
232
|
}
|
|
195
233
|
function storedSession(row) {
|
|
@@ -202,14 +240,21 @@ function ownership(row) {
|
|
|
202
240
|
ownerExpiresAt: row.ownerExpiresAt?.toISOString() ?? null,
|
|
203
241
|
};
|
|
204
242
|
}
|
|
205
|
-
function
|
|
243
|
+
function environmentOf(row) {
|
|
206
244
|
return {
|
|
207
245
|
id: row.id,
|
|
208
246
|
kind: row.kind,
|
|
209
|
-
spec: row.spec,
|
|
210
247
|
labels: row.labels,
|
|
248
|
+
recipe: row.recipe,
|
|
249
|
+
applied: row.applied,
|
|
250
|
+
version: row.version,
|
|
251
|
+
appliedVersion: row.appliedVersion,
|
|
252
|
+
...(row.ownerUserId !== null ? { ownerUserId: row.ownerUserId } : {}),
|
|
253
|
+
lastUsedAt: new Date(row.lastUsedAt).toISOString(),
|
|
211
254
|
createdAt: row.createdAt,
|
|
212
255
|
updatedAt: row.updatedAt,
|
|
256
|
+
...(row.diskOp ? { diskOp: row.diskOp } : {}),
|
|
257
|
+
...(row.diskSeed ? { diskSeed: row.diskSeed } : {}),
|
|
213
258
|
...(row.k8sName !== null && row.desired !== null && row.observed !== null
|
|
214
259
|
? {
|
|
215
260
|
pod: {
|
|
@@ -222,7 +267,6 @@ function sandboxResourceOf(row) {
|
|
|
222
267
|
...(row.joinTokenHash !== null ? { joinTokenHash: row.joinTokenHash } : {}),
|
|
223
268
|
rev: row.rev,
|
|
224
269
|
...(row.lastActiveAt ? { lastActiveAt: row.lastActiveAt.toISOString() } : {}),
|
|
225
|
-
...(row.expiresAt ? { expiresAt: row.expiresAt.toISOString() } : {}),
|
|
226
270
|
...(row.startedAt ? { startedAt: row.startedAt.toISOString() } : {}),
|
|
227
271
|
...(row.reason !== null ? { reason: row.reason } : {}),
|
|
228
272
|
...(row.retiring !== null ? { retiring: row.retiring } : {}),
|
|
@@ -245,7 +289,6 @@ function podColumns(patch) {
|
|
|
245
289
|
joinTokenHash: patch.joinTokenHash,
|
|
246
290
|
rev: patch.rev,
|
|
247
291
|
lastActiveAt: date(patch.lastActiveAt),
|
|
248
|
-
expiresAt: date(patch.expiresAt),
|
|
249
292
|
startedAt: date(patch.startedAt),
|
|
250
293
|
reason: patch.reason,
|
|
251
294
|
retiring: patch.retiring,
|
|
@@ -262,26 +305,49 @@ const SIGNING_KEY_STAMP = {
|
|
|
262
305
|
/** The audit columns a row has (`ord` only orders them). */
|
|
263
306
|
const { ord: _ord, ...AUDIT } = getTableColumns(vaultAudit);
|
|
264
307
|
const SESSION_TABLES = [sessions, commands, effects, links, wakes];
|
|
265
|
-
/**
|
|
308
|
+
/**
|
|
309
|
+
* The outbox's tables, by the kind of request each holds. Their delivery columns (`id`,
|
|
310
|
+
* `created_at`, `attempts`, `retry_at`, `parked_at`) are the same.
|
|
311
|
+
*/
|
|
312
|
+
const OUTBOX = {
|
|
313
|
+
wake: wakes,
|
|
314
|
+
sandbox: sandboxSignals,
|
|
315
|
+
};
|
|
316
|
+
/** A wake outbox row as it is delivered: its own dedupe key, or one made from the row id. */
|
|
266
317
|
function outboxWake(id, reason, dedupeKey) {
|
|
267
318
|
return { reason, dedupeKey: dedupeKey ?? `wake:${id}` };
|
|
268
319
|
}
|
|
320
|
+
/** A sandbox signal outbox row as it is delivered: a reconcile is keyed by the row. */
|
|
321
|
+
function outboxSignal(id, row) {
|
|
322
|
+
return row.kind === "arm"
|
|
323
|
+
? { kind: "arm", timer: row.timer, at: row.at }
|
|
324
|
+
: { kind: "reconcile", key: `signal:${id}` };
|
|
325
|
+
}
|
|
326
|
+
/** When an outbox row of `table` is due, after a grace of `graceMs` (the database's clock). */
|
|
327
|
+
function dueAt(table, graceMs) {
|
|
328
|
+
const grace = sql `make_interval(secs => ${Math.max(0, graceMs) / 1000})`;
|
|
329
|
+
return sql `coalesce(${table.retryAt}, ${table.createdAt} + ${grace})`;
|
|
330
|
+
}
|
|
331
|
+
/** Due rows of `table`, soonest first: their epoch seconds, to merge two tables' rows. */
|
|
332
|
+
const DUE = (table, graceMs) => ({
|
|
333
|
+
due: dueAt(table, graceMs),
|
|
334
|
+
dueSeconds: sql `extract(epoch from ${dueAt(table, graceMs)})::float8`,
|
|
335
|
+
});
|
|
269
336
|
/** The inserted row's `column` (`ON CONFLICT … DO UPDATE`). */
|
|
270
337
|
const excluded = (column) => sql.raw(`excluded.${column}`);
|
|
271
338
|
class PostgresTx {
|
|
272
339
|
db;
|
|
273
340
|
tenantId;
|
|
274
341
|
now;
|
|
275
|
-
deleteWake;
|
|
276
342
|
closed = false;
|
|
277
343
|
events = [];
|
|
278
344
|
generations = [];
|
|
279
|
-
|
|
280
|
-
|
|
345
|
+
/** The outbox requests this transaction wrote, delivered after it commits. */
|
|
346
|
+
outbox = [];
|
|
347
|
+
constructor(db, tenantId, now) {
|
|
281
348
|
this.db = db;
|
|
282
349
|
this.tenantId = tenantId;
|
|
283
350
|
this.now = now;
|
|
284
|
-
this.deleteWake = deleteWake;
|
|
285
351
|
}
|
|
286
352
|
check() {
|
|
287
353
|
if (this.closed)
|
|
@@ -328,7 +394,7 @@ class PostgresTx {
|
|
|
328
394
|
const [row] = await this.sessions().where(eq(sessions.id, id)).for("update");
|
|
329
395
|
return row && storedSession(row);
|
|
330
396
|
}
|
|
331
|
-
async event(sessionId, turnId, type, payload) {
|
|
397
|
+
async event(sessionId, turnId, type, payload, writer) {
|
|
332
398
|
this.check();
|
|
333
399
|
const db = this.db;
|
|
334
400
|
// The session row lock orders the session's events; the log head allocates under it.
|
|
@@ -347,17 +413,14 @@ class PostgresTx {
|
|
|
347
413
|
time: this.now(),
|
|
348
414
|
type,
|
|
349
415
|
payload,
|
|
416
|
+
...(writer ? { writer } : {}),
|
|
350
417
|
});
|
|
351
418
|
this.events.push(event);
|
|
352
419
|
this.generations.push(generation);
|
|
353
420
|
return structuredClone(event);
|
|
354
421
|
}
|
|
355
|
-
afterCommit(fn) {
|
|
356
|
-
this.check();
|
|
357
|
-
this.callbacks.push(fn);
|
|
358
|
-
}
|
|
359
422
|
// --- wake outbox ---------------------------------------------------------
|
|
360
|
-
async wake(sessionId, wake
|
|
423
|
+
async wake(sessionId, wake) {
|
|
361
424
|
this.check();
|
|
362
425
|
const id = randomUUID();
|
|
363
426
|
await this.db.insert(wakes).values({
|
|
@@ -366,34 +429,90 @@ class PostgresTx {
|
|
|
366
429
|
reason: wake.reason,
|
|
367
430
|
dedupeKey: wake.dedupeKey ?? null,
|
|
368
431
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return;
|
|
374
|
-
await this.deleteWake(id);
|
|
432
|
+
this.outbox.push({
|
|
433
|
+
kind: "wake",
|
|
434
|
+
id,
|
|
435
|
+
request: { kind: "wake", sessionId, wake: outboxWake(id, wake.reason, wake.dedupeKey ?? null) },
|
|
375
436
|
});
|
|
376
437
|
}
|
|
377
|
-
async
|
|
438
|
+
async signalSandbox(sandboxId, signal) {
|
|
378
439
|
this.check();
|
|
379
|
-
const
|
|
380
|
-
|
|
440
|
+
const id = randomUUID();
|
|
441
|
+
const row = {
|
|
442
|
+
kind: signal.kind,
|
|
443
|
+
timer: signal.kind === "arm" ? signal.timer : null,
|
|
444
|
+
at: signal.kind === "arm" ? signal.at : null,
|
|
445
|
+
};
|
|
446
|
+
await this.db.insert(sandboxSignals).values({ id, sandboxId, ...row });
|
|
447
|
+
this.outbox.push({
|
|
448
|
+
kind: "sandbox",
|
|
449
|
+
id,
|
|
450
|
+
request: { kind: "sandbox", sandboxId, signal: outboxSignal(id, row) },
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
async pendingOutbox(graceMs, limit) {
|
|
454
|
+
this.check();
|
|
455
|
+
// Compared on the database's clock, which wrote `created_at` and `retry_at`.
|
|
456
|
+
const now = sql `clock_timestamp()`;
|
|
457
|
+
const wake = DUE(wakes, graceMs);
|
|
458
|
+
const signal = DUE(OUTBOX.sandbox, graceMs);
|
|
459
|
+
const wakeRows = await this.db
|
|
460
|
+
.select({ ...getTableColumns(wakes), dueSeconds: wake.dueSeconds })
|
|
381
461
|
.from(wakes)
|
|
382
|
-
.where(
|
|
383
|
-
.orderBy(
|
|
462
|
+
.where(and(isNull(wakes.parkedAt), lte(wake.due, now)))
|
|
463
|
+
.orderBy(wake.due, wakes.id)
|
|
384
464
|
.limit(limit);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
465
|
+
const signalRows = await this.db
|
|
466
|
+
.select({ ...getTableColumns(sandboxSignals), dueSeconds: signal.dueSeconds })
|
|
467
|
+
.from(sandboxSignals)
|
|
468
|
+
.where(and(isNull(sandboxSignals.parkedAt), lte(signal.due, now)))
|
|
469
|
+
.orderBy(signal.due, sandboxSignals.id)
|
|
470
|
+
.limit(limit);
|
|
471
|
+
const rows = [
|
|
472
|
+
...wakeRows.map((row) => ({
|
|
473
|
+
kind: "wake",
|
|
474
|
+
id: row.id,
|
|
475
|
+
request: {
|
|
476
|
+
kind: "wake",
|
|
477
|
+
sessionId: row.sessionId,
|
|
478
|
+
wake: outboxWake(row.id, row.reason, row.dedupeKey),
|
|
479
|
+
},
|
|
480
|
+
createdAt: row.createdAt.toISOString(),
|
|
481
|
+
attempts: row.attempts,
|
|
482
|
+
due: row.dueSeconds,
|
|
483
|
+
})),
|
|
484
|
+
...signalRows.map((row) => ({
|
|
485
|
+
kind: "sandbox",
|
|
486
|
+
id: row.id,
|
|
487
|
+
request: { kind: "sandbox", sandboxId: row.sandboxId, signal: outboxSignal(row.id, row) },
|
|
488
|
+
createdAt: row.createdAt.toISOString(),
|
|
489
|
+
attempts: row.attempts,
|
|
490
|
+
due: row.dueSeconds,
|
|
491
|
+
})),
|
|
492
|
+
];
|
|
493
|
+
rows.sort((a, b) => a.due - b.due || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
494
|
+
return rows.slice(0, limit).map(({ due: _due, ...row }) => row);
|
|
495
|
+
}
|
|
496
|
+
async deleteOutbox(rows) {
|
|
497
|
+
this.check();
|
|
498
|
+
for (const kind of ["wake", "sandbox"]) {
|
|
499
|
+
const ids = rows.filter((row) => row.kind === kind).map((row) => row.id);
|
|
500
|
+
if (ids.length > 0)
|
|
501
|
+
await this.db.delete(OUTBOX[kind]).where(inArray(OUTBOX[kind].id, ids));
|
|
502
|
+
}
|
|
391
503
|
}
|
|
392
|
-
async
|
|
504
|
+
async failOutbox(row, retry) {
|
|
393
505
|
this.check();
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
await this.db
|
|
506
|
+
const table = OUTBOX[row.kind];
|
|
507
|
+
const secs = Math.max(0, retry.retryInMs) / 1000;
|
|
508
|
+
await this.db
|
|
509
|
+
.update(table)
|
|
510
|
+
.set({
|
|
511
|
+
attempts: sql `${table.attempts} + 1`,
|
|
512
|
+
retryAt: sql `clock_timestamp() + make_interval(secs => ${secs})`,
|
|
513
|
+
...(retry.park ? { parkedAt: sql `clock_timestamp()` } : {}),
|
|
514
|
+
})
|
|
515
|
+
.where(eq(table.id, row.id));
|
|
397
516
|
}
|
|
398
517
|
// --- ownership -----------------------------------------------------------
|
|
399
518
|
async takeOwnership(sessionId, claim) {
|
|
@@ -470,12 +589,11 @@ class PostgresTx {
|
|
|
470
589
|
.orderBy(sessions.id);
|
|
471
590
|
return rows.map((row) => storedSession(row));
|
|
472
591
|
}
|
|
473
|
-
async orphanedSessions(now, limit) {
|
|
592
|
+
async orphanedSessions(now, limit, after) {
|
|
474
593
|
this.check();
|
|
475
594
|
const rows = await this.sessions()
|
|
476
|
-
.where(and(inArray(sessions.status, OPEN_SESSION), or(isNull(sessions.owner), isNull(sessions.ownerExpiresAt), lte(sessions.ownerExpiresAt, now))))
|
|
477
|
-
|
|
478
|
-
.orderBy(sql `CASE WHEN ${sessions.owner} IS NULL THEN NULL ELSE ${sessions.ownerExpiresAt} END ASC NULLS FIRST`, sessions.id)
|
|
595
|
+
.where(and(inArray(sessions.status, OPEN_SESSION), or(isNull(sessions.owner), isNull(sessions.ownerExpiresAt), lte(sessions.ownerExpiresAt, now)), after === undefined ? undefined : gt(sessions.id, after)))
|
|
596
|
+
.orderBy(sessions.id)
|
|
479
597
|
.limit(limit);
|
|
480
598
|
return rows.map((row) => storedSession(row));
|
|
481
599
|
}
|
|
@@ -500,88 +618,92 @@ class PostgresTx {
|
|
|
500
618
|
this.check();
|
|
501
619
|
return this.bodies(sandboxes);
|
|
502
620
|
}
|
|
503
|
-
// ---
|
|
504
|
-
async
|
|
621
|
+
// --- Environments ----------------------------------------------------------
|
|
622
|
+
async environment(id, options = {}) {
|
|
505
623
|
this.check();
|
|
506
|
-
const query = this.db.select().from(
|
|
624
|
+
const query = this.db.select().from(environments).where(eq(environments.id, id));
|
|
507
625
|
const [row] = options.lock ? await query.for("update") : await query;
|
|
508
|
-
return row &&
|
|
626
|
+
return row && environmentOf(row);
|
|
509
627
|
}
|
|
510
|
-
async
|
|
628
|
+
async createEnvironment(row, limit) {
|
|
511
629
|
this.check();
|
|
512
630
|
// Every create takes this lock, so two never both see room for one more.
|
|
513
|
-
await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.
|
|
631
|
+
await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.environments'))`);
|
|
514
632
|
const [found] = await this.db
|
|
515
|
-
.select({ id:
|
|
516
|
-
.from(
|
|
517
|
-
.where(eq(
|
|
633
|
+
.select({ id: environments.id })
|
|
634
|
+
.from(environments)
|
|
635
|
+
.where(eq(environments.id, row.id));
|
|
518
636
|
if (found)
|
|
519
637
|
return "exists";
|
|
520
|
-
const [counted] = await this.db.select({ n: count() }).from(
|
|
638
|
+
const [counted] = await this.db.select({ n: count() }).from(environments);
|
|
521
639
|
if ((counted?.n ?? 0) >= limit)
|
|
522
640
|
return "limit";
|
|
523
|
-
const { pod, ...
|
|
524
|
-
await this.db.insert(
|
|
641
|
+
const { pod, ownerUserId, diskOp, diskSeed, ...environment } = row;
|
|
642
|
+
await this.db.insert(environments).values({
|
|
643
|
+
...environment,
|
|
644
|
+
ownerUserId: ownerUserId ?? null,
|
|
645
|
+
diskOp: diskOp ?? null,
|
|
646
|
+
diskSeed: diskSeed ?? null,
|
|
647
|
+
...(pod ? podColumns(pod) : {}),
|
|
648
|
+
});
|
|
525
649
|
return "created";
|
|
526
650
|
}
|
|
527
|
-
async
|
|
528
|
-
this.check();
|
|
529
|
-
await this.db.update(sandboxResources).set({ spec, updatedAt }).where(eq(sandboxResources.id, id));
|
|
530
|
-
}
|
|
531
|
-
async updateSandboxPod(id, patch, updatedAt) {
|
|
651
|
+
async updateEnvironment(id, patch, updatedAt) {
|
|
532
652
|
this.check();
|
|
653
|
+
const columns = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
|
|
533
654
|
await this.db
|
|
534
|
-
.update(
|
|
535
|
-
.set({ ...
|
|
536
|
-
.where(eq(
|
|
655
|
+
.update(environments)
|
|
656
|
+
.set({ ...columns, updatedAt })
|
|
657
|
+
.where(eq(environments.id, id));
|
|
537
658
|
}
|
|
538
|
-
async
|
|
659
|
+
async updateEnvironmentPod(id, patch, updatedAt) {
|
|
539
660
|
this.check();
|
|
540
661
|
await this.db
|
|
541
|
-
.update(
|
|
542
|
-
.set({
|
|
543
|
-
.where(eq(
|
|
662
|
+
.update(environments)
|
|
663
|
+
.set({ ...podColumns(patch), updatedAt })
|
|
664
|
+
.where(eq(environments.id, id));
|
|
544
665
|
}
|
|
545
|
-
async
|
|
666
|
+
async deleteEnvironment(id) {
|
|
546
667
|
this.check();
|
|
547
|
-
|
|
668
|
+
// Its vault and credentials go with it (ON DELETE CASCADE).
|
|
669
|
+
await this.db.delete(environments).where(eq(environments.id, id));
|
|
548
670
|
}
|
|
549
|
-
async
|
|
671
|
+
async listEnvironments(filter = {}) {
|
|
550
672
|
this.check();
|
|
551
673
|
const labels = filter.labels;
|
|
552
674
|
const rows = await this.db
|
|
553
675
|
.select()
|
|
554
|
-
.from(
|
|
676
|
+
.from(environments)
|
|
555
677
|
.where(labels === undefined || Object.keys(labels).length === 0
|
|
556
678
|
? undefined
|
|
557
|
-
: sql `nylorun.doc(${
|
|
558
|
-
.orderBy(
|
|
559
|
-
return rows.map(
|
|
679
|
+
: sql `nylorun.doc(${environments.labels}) @> ${toJson(labels)}::jsonb`)
|
|
680
|
+
.orderBy(environments.id);
|
|
681
|
+
return rows.map(environmentOf);
|
|
560
682
|
}
|
|
561
|
-
async
|
|
683
|
+
async sessionsOnEnvironment(environmentId) {
|
|
562
684
|
this.check();
|
|
563
685
|
const rows = await this.sessions()
|
|
564
|
-
.where(eq(sessions.
|
|
686
|
+
.where(eq(sessions.environmentId, environmentId))
|
|
565
687
|
.orderBy(sessions.id);
|
|
566
688
|
return rows.map((row) => storedSession(row));
|
|
567
689
|
}
|
|
568
|
-
async
|
|
690
|
+
async environmentEvent(environmentId, type, payload) {
|
|
569
691
|
this.check();
|
|
570
|
-
return
|
|
692
|
+
return appendEnvironmentEvent(postgresEnvironmentRecordWriter(this.db), {
|
|
571
693
|
tenantId: this.tenantId,
|
|
572
|
-
|
|
694
|
+
environmentId,
|
|
573
695
|
time: this.now(),
|
|
574
696
|
type,
|
|
575
697
|
payload,
|
|
576
698
|
});
|
|
577
699
|
}
|
|
578
|
-
async
|
|
700
|
+
async environmentEvents(environmentId, options = {}) {
|
|
579
701
|
this.check();
|
|
580
702
|
const rows = await this.db
|
|
581
|
-
.select({ body:
|
|
582
|
-
.from(
|
|
583
|
-
.where(and(eq(
|
|
584
|
-
.orderBy(
|
|
703
|
+
.select({ body: environmentEvents.body })
|
|
704
|
+
.from(environmentEvents)
|
|
705
|
+
.where(and(eq(environmentEvents.environmentId, environmentId), options.fromSeq === undefined ? undefined : gte(environmentEvents.seq, options.fromSeq)))
|
|
706
|
+
.orderBy(environmentEvents.seq)
|
|
585
707
|
.limit(options.limit ?? 1000);
|
|
586
708
|
return rows.map((row) => row.body);
|
|
587
709
|
}
|
|
@@ -719,21 +841,13 @@ class PostgresTx {
|
|
|
719
841
|
const [row] = await this.db.select().from(vaults).where(eq(vaults.id, id));
|
|
720
842
|
return row;
|
|
721
843
|
}
|
|
722
|
-
async
|
|
723
|
-
this.check();
|
|
724
|
-
return this.db
|
|
725
|
-
.select()
|
|
726
|
-
.from(vaults)
|
|
727
|
-
.where(and(eq(vaults.ownerUserId, ownerUserId), eq(vaults.scope, "user")))
|
|
728
|
-
.orderBy(vaults.createdAt, vaults.id);
|
|
729
|
-
}
|
|
730
|
-
async installationVaults() {
|
|
844
|
+
async vaultOfEnvironment(environmentId) {
|
|
731
845
|
this.check();
|
|
732
|
-
|
|
846
|
+
const [row] = await this.db
|
|
733
847
|
.select()
|
|
734
848
|
.from(vaults)
|
|
735
|
-
.where(eq(vaults.
|
|
736
|
-
|
|
849
|
+
.where(eq(vaults.environmentId, environmentId));
|
|
850
|
+
return row;
|
|
737
851
|
}
|
|
738
852
|
async updateVaultMetadata(id, metadataJson) {
|
|
739
853
|
this.check();
|
|
@@ -756,6 +870,14 @@ class PostgresTx {
|
|
|
756
870
|
.where(eq(vaultCredentials.id, id));
|
|
757
871
|
return row;
|
|
758
872
|
}
|
|
873
|
+
async credentialByName(vaultId, name) {
|
|
874
|
+
this.check();
|
|
875
|
+
const [row] = await this.db
|
|
876
|
+
.select()
|
|
877
|
+
.from(vaultCredentials)
|
|
878
|
+
.where(and(eq(vaultCredentials.vaultId, vaultId), eq(vaultCredentials.name, name)));
|
|
879
|
+
return row;
|
|
880
|
+
}
|
|
759
881
|
async credentialsForVault(vaultId, filter = {}) {
|
|
760
882
|
this.check();
|
|
761
883
|
return this.db
|
|
@@ -864,6 +986,16 @@ class PostgresTx {
|
|
|
864
986
|
.returning();
|
|
865
987
|
return inserted;
|
|
866
988
|
}
|
|
989
|
+
async modelUsageOf(effectKey) {
|
|
990
|
+
this.check();
|
|
991
|
+
const [row] = await this.db
|
|
992
|
+
.select()
|
|
993
|
+
.from(modelUsage)
|
|
994
|
+
.where(and(eq(modelUsage.effectKey, effectKey), eq(modelUsage.duplicate, false)))
|
|
995
|
+
.orderBy(modelUsage.createdAt, modelUsage.id)
|
|
996
|
+
.limit(1);
|
|
997
|
+
return row;
|
|
998
|
+
}
|
|
867
999
|
async modelUsageTotals(query) {
|
|
868
1000
|
this.check();
|
|
869
1001
|
const [row] = await this.db
|
|
@@ -904,6 +1036,15 @@ class PostgresTx {
|
|
|
904
1036
|
.returning({ key: toolCrossings.key });
|
|
905
1037
|
return inserted.length === 1;
|
|
906
1038
|
}
|
|
1039
|
+
async restartToolCrossing(row) {
|
|
1040
|
+
this.check();
|
|
1041
|
+
const restarted = await this.db
|
|
1042
|
+
.update(toolCrossings)
|
|
1043
|
+
.set({ startedAt: row.startedAt })
|
|
1044
|
+
.where(and(eq(toolCrossings.key, row.key), eq(toolCrossings.hash, row.hash), isNull(toolCrossings.settledAt)))
|
|
1045
|
+
.returning({ key: toolCrossings.key });
|
|
1046
|
+
return restarted.length === 1;
|
|
1047
|
+
}
|
|
907
1048
|
async settleToolCrossing(key, answer, settledAt) {
|
|
908
1049
|
this.check();
|
|
909
1050
|
await this.db
|
|
@@ -926,6 +1067,12 @@ class PostgresTx {
|
|
|
926
1067
|
const [row] = options.lock ? await query.for("update") : await query;
|
|
927
1068
|
return row;
|
|
928
1069
|
}
|
|
1070
|
+
async diskArtifact(environmentId, options = {}) {
|
|
1071
|
+
this.check();
|
|
1072
|
+
const query = this.db.select().from(artifacts).where(eq(artifacts.environmentId, environmentId));
|
|
1073
|
+
const [row] = options.lock ? await query.for("update") : await query;
|
|
1074
|
+
return row;
|
|
1075
|
+
}
|
|
929
1076
|
async listArtifacts(filter = {}) {
|
|
930
1077
|
this.check();
|
|
931
1078
|
return this.db
|
|
@@ -976,6 +1123,24 @@ class PostgresTx {
|
|
|
976
1123
|
await this.db.delete(artifacts).where(eq(artifacts.id, id));
|
|
977
1124
|
return deleted.map((row) => row.blobKey);
|
|
978
1125
|
}
|
|
1126
|
+
async deleteArtifactVersion(artifactId, version) {
|
|
1127
|
+
this.check();
|
|
1128
|
+
const [deleted] = await this.db
|
|
1129
|
+
.delete(artifactVersions)
|
|
1130
|
+
.where(and(eq(artifactVersions.artifactId, artifactId), eq(artifactVersions.version, version)))
|
|
1131
|
+
.returning({ blobKey: artifactVersions.blobKey });
|
|
1132
|
+
if (!deleted)
|
|
1133
|
+
return undefined;
|
|
1134
|
+
const [left] = await this.db
|
|
1135
|
+
.select({ latest: sql `max(${artifactVersions.version})` })
|
|
1136
|
+
.from(artifactVersions)
|
|
1137
|
+
.where(eq(artifactVersions.artifactId, artifactId));
|
|
1138
|
+
await this.db
|
|
1139
|
+
.update(artifacts)
|
|
1140
|
+
.set({ latestVersion: Number(left?.latest ?? 0), updatedAt: new Date().toISOString() })
|
|
1141
|
+
.where(eq(artifacts.id, artifactId));
|
|
1142
|
+
return deleted.blobKey;
|
|
1143
|
+
}
|
|
979
1144
|
async lockArtifactQuota() {
|
|
980
1145
|
this.check();
|
|
981
1146
|
await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.artifacts'))`);
|
|
@@ -1116,10 +1281,12 @@ class PostgresTx {
|
|
|
1116
1281
|
basinGeneration: sql `${tenant.basinGeneration} + 1`,
|
|
1117
1282
|
});
|
|
1118
1283
|
}
|
|
1119
|
-
if (scope === "
|
|
1284
|
+
if (scope === "environments" || scope === "all") {
|
|
1120
1285
|
await db.delete(sandboxes);
|
|
1121
|
-
|
|
1122
|
-
await db.delete(
|
|
1286
|
+
// Their vaults and credentials go with them (ON DELETE CASCADE).
|
|
1287
|
+
await db.delete(environments);
|
|
1288
|
+
await db.delete(environmentEvents);
|
|
1289
|
+
await db.delete(sandboxSignals);
|
|
1123
1290
|
}
|
|
1124
1291
|
if (scope === "all") {
|
|
1125
1292
|
await db.delete(definitions);
|
|
@@ -1128,7 +1295,6 @@ class PostgresTx {
|
|
|
1128
1295
|
await db.delete(modelUsage);
|
|
1129
1296
|
await db.delete(modelBudgets);
|
|
1130
1297
|
await db.delete(artifacts);
|
|
1131
|
-
await db.delete(vaults).where(ne(vaults.scope, "host"));
|
|
1132
1298
|
}
|
|
1133
1299
|
}
|
|
1134
1300
|
}
|