@nylorun/runtime 0.16.0-beta → 0.19.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 +207 -0
- package/README.md +105 -63
- package/dist/adapters/execution/restate.d.ts +1 -3
- package/dist/adapters/execution/restate.js +4 -31
- package/dist/adapters/replication/pgoutput.js +1 -1
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +11 -13
- package/dist/api/ag-ui/translate.d.ts +1 -3
- package/dist/api/ag-ui/translate.js +2 -17
- package/dist/api/components.d.ts +4783 -2588
- package/dist/api/components.js +19 -21
- package/dist/api/gate/routes.d.ts +7 -8
- package/dist/api/gate/routes.js +58 -52
- package/dist/api/http/app.d.ts +6 -0
- package/dist/api/http/app.js +20 -14
- package/dist/api/http/define.d.ts +22 -13
- package/dist/api/http/define.js +43 -39
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +25 -184
- package/dist/api/http/routes/artifacts.js +3 -7
- package/dist/api/http/routes/files.d.ts +8 -0
- package/dist/api/http/routes/files.js +77 -0
- package/dist/api/http/routes/keys.d.ts +10 -0
- package/dist/api/http/routes/keys.js +64 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +48 -0
- package/dist/api/http/routes/reads.d.ts +66 -0
- package/dist/api/http/routes/reads.js +127 -0
- package/dist/api/http/routes/sandboxes.d.ts +2 -2
- package/dist/api/http/routes/sandboxes.js +8 -7
- package/dist/api/http/routes/sessions.js +31 -20
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +13 -3
- package/dist/api/http/routes/vaults.js +136 -64
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +292 -68
- package/dist/artifacts/links.js +6 -6
- package/dist/artifacts/service.js +1 -1
- package/dist/core/flow-host.d.ts +44 -23
- package/dist/core/flow-host.js +88 -106
- package/dist/core/provider.d.ts +4 -1
- package/dist/core/provider.js +6 -1
- package/dist/core/sandbox-routes.d.ts +0 -9
- package/dist/core/sandbox-routes.js +2 -21
- package/dist/execution/memory.d.ts +4 -5
- package/dist/execution/memory.js +11 -25
- package/dist/execution/types.d.ts +3 -33
- package/dist/execution/types.js +2 -12
- package/dist/gates/http-tool.d.ts +86 -0
- package/dist/gates/http-tool.js +197 -0
- package/dist/gates/mcp-handler.d.ts +3 -0
- package/dist/gates/mcp-handler.js +6 -4
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +19 -14
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +7 -15
- package/dist/gates/tool-calls.js +4 -4
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +23 -47
- package/dist/gates/tool-contract.d.ts +27 -39
- package/dist/gates/tool-contract.js +30 -21
- package/dist/gates/tool-gate.d.ts +25 -24
- package/dist/gates/tool-gate.js +11 -7
- package/dist/harness/calls.d.ts +10 -4
- package/dist/harness/calls.js +15 -6
- package/dist/harness/executors.d.ts +1 -1
- package/dist/harness/executors.js +17 -12
- package/dist/harness/main.d.ts +2 -2
- package/dist/harness/main.js +5 -13
- package/dist/harness/service.d.ts +4 -5
- package/dist/harness/service.js +9 -2
- package/dist/harness-api/in-process.js +2 -2
- package/dist/harness-api/record.d.ts +6 -3
- package/dist/harness-api/record.js +65 -72
- package/dist/harness-api/server.d.ts +1 -9
- package/dist/harness-api/server.js +19 -17
- package/dist/harness-api/start.js +1 -5
- package/dist/harness-api/workspace.js +2 -3
- package/dist/harness-api/ws-server.js +1 -1
- package/dist/host/app.d.ts +2 -10
- package/dist/host/app.js +24 -44
- package/dist/host/config-for.d.ts +5 -5
- package/dist/host/config-for.js +2 -3
- package/dist/host/config.d.ts +0 -11
- package/dist/host/create-host.d.ts +3 -40
- package/dist/host/create-host.js +18 -91
- package/dist/host/environment.d.ts +0 -3
- package/dist/host/environment.js +0 -9
- package/dist/host/gates.d.ts +4 -1
- package/dist/host/gates.js +9 -3
- package/dist/host/http.d.ts +1 -2
- package/dist/host/http.js +1 -7
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +2 -2
- package/dist/host/main.js +55 -40
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +23 -20
- package/dist/host/stack-config.js +28 -44
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +10 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +8 -27
- package/dist/mcp/pool.d.ts +6 -9
- package/dist/mcp/pool.js +8 -18
- package/dist/mcp/snapshot.d.ts +8 -2
- package/dist/mcp/snapshot.js +29 -2
- package/dist/media.d.ts +1 -1
- package/dist/media.js +1 -1
- package/dist/model/pi-model.js +3 -0
- package/dist/openapi.json +6228 -15615
- package/dist/reads/access.d.ts +4 -0
- package/dist/reads/access.js +12 -0
- package/dist/reads/cursor.d.ts +11 -0
- package/dist/reads/cursor.js +80 -0
- package/dist/reads/types.d.ts +25 -0
- package/dist/reads/types.js +1 -0
- package/dist/sandbox/manager.d.ts +16 -0
- package/dist/sandbox/manager.js +40 -0
- package/dist/sandbox/session-sandbox.js +13 -3
- package/dist/sandbox/skills.d.ts +13 -0
- package/dist/sandbox/skills.js +46 -0
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/ownership.d.ts +9 -0
- package/dist/store/ownership.js +13 -0
- package/dist/store/postgres/connect.d.ts +2 -0
- package/dist/store/postgres/connect.js +29 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
- package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
- package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
- package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/reads.d.ts +3 -0
- package/dist/store/postgres/reads.js +272 -0
- package/dist/store/postgres/schema.d.ts +423 -594
- package/dist/store/postgres/schema.js +84 -101
- package/dist/store/postgres/store.js +96 -248
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +44 -119
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -7
- package/dist/streams/control.js +0 -13
- package/dist/streams/types.d.ts +2 -20
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +24 -15
- package/dist/tenant/auth.js +44 -55
- package/dist/tenant/cause.d.ts +3 -3
- package/dist/tenant/cause.js +1 -1
- package/dist/tenant/commands.d.ts +2 -18
- package/dist/tenant/commands.js +38 -179
- package/dist/tenant/context.d.ts +33 -45
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +20 -18
- package/dist/tenant/effects.js +59 -133
- package/dist/tenant/ephemeral.d.ts +21 -8
- package/dist/tenant/ephemeral.js +11 -12
- package/dist/tenant/host-token.js +5 -5
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +44 -0
- package/dist/tenant/operator-keys.js +87 -0
- package/dist/tenant/outbound.d.ts +40 -1
- package/dist/tenant/outbound.js +167 -18
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +13 -13
- package/dist/tenant/principals.js +35 -36
- package/dist/tenant/run-token.js +6 -6
- package/dist/tenant/runtime.d.ts +5 -3
- package/dist/tenant/runtime.js +29 -15
- package/dist/tenant/sandboxes.d.ts +9 -6
- package/dist/tenant/sandboxes.js +16 -5
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +8 -11
- package/dist/tenant/session-streams.js +48 -32
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.d.ts +2 -2
- package/dist/tenant/sessions.js +17 -9
- package/dist/tenant/signing-keys.d.ts +3 -2
- package/dist/tenant/signing-keys.js +5 -5
- package/dist/tenant/skill-tool.d.ts +20 -0
- package/dist/tenant/skill-tool.js +72 -0
- package/dist/tenant/slim.d.ts +1 -1
- package/dist/tenant/slim.js +1 -1
- package/dist/tenant/status.js +7 -12
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +7 -3
- package/dist/tenant/streams.d.ts +3 -11
- package/dist/tenant/streams.js +2 -29
- package/dist/tenant/sweep.js +0 -5
- package/dist/tenant/transcript.d.ts +2 -2
- package/dist/tenant/transcript.js +2 -2
- package/dist/tenant/types.d.ts +33 -19
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +60 -0
- package/dist/vault/sources.js +196 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/dist/admin-openapi.json +0 -604
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -76
- package/dist/api/http/routes/endpoints.d.ts +0 -7
- package/dist/api/http/routes/endpoints.js +0 -100
- package/dist/host/admin-api.d.ts +0 -23
- package/dist/host/admin-api.js +0 -74
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/delivery-token.d.ts +0 -41
- package/dist/tenant/delivery-token.js +0 -104
- package/dist/tenant/delivery.d.ts +0 -62
- package/dist/tenant/delivery.js +0 -481
- package/dist/tenant/endpoints.d.ts +0 -67
- package/dist/tenant/endpoints.js +0 -90
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -187
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Plain reads and writes use Drizzle's query builder: `.set(patch)` and `and(...)` skip
|
|
10
10
|
* `undefined`, rows come back in the camelCase of `schema.ts`. Statements whose logic is SQL
|
|
11
|
-
* stay `sql` fragments inside it: the
|
|
11
|
+
* stay `sql` fragments inside it: the log-head upsert,
|
|
12
12
|
* `orphanedSessions`' ordering, `counts()`, advisory locks and array updates.
|
|
13
13
|
*
|
|
14
14
|
* ## Transactions
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* ends. To stay deadlock-free:
|
|
29
29
|
*
|
|
30
30
|
* 1. A session-scoped transaction locks its session row before writing any
|
|
31
|
-
* other row of that session (effects,
|
|
31
|
+
* other row of that session (effects, its record rows and log
|
|
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.
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
52
52
|
import { and, count, eq, getTableColumns, gte, inArray, isNotNull, isNull, lt, lte, ne, or, sql, } from "drizzle-orm";
|
|
53
53
|
import { appendEvent, appendSandboxEvent } from "../../record/index.js";
|
|
54
|
-
import { OwnershipLostError } from "../ownership.js";
|
|
54
|
+
import { OwnershipLostError, PrincipalRoleConflict } from "../ownership.js";
|
|
55
55
|
import { database, driverError } from "./db.js";
|
|
56
56
|
import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
|
|
57
57
|
import { createPostgresRecordReader } from "./record.js";
|
|
58
58
|
import { postgresRecordWriter, postgresSandboxRecordWriter } from "./record-writer.js";
|
|
59
|
-
import {
|
|
59
|
+
import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, definitions, effects, links, modelBudgets, modelUsage, principals, sandboxes, sandboxEvents, sandboxResources, sessionEvents, sessionLogHeads, sessions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, oauthPending, } from "./schema.js";
|
|
60
60
|
export function createPostgresSessionStore(options) {
|
|
61
61
|
return new PostgresSessionStore(options);
|
|
62
62
|
}
|
|
@@ -68,7 +68,6 @@ const DOCUMENTS = {
|
|
|
68
68
|
sessions: sessions,
|
|
69
69
|
commands,
|
|
70
70
|
effects: effects,
|
|
71
|
-
actions: actions,
|
|
72
71
|
sandboxes,
|
|
73
72
|
links: links,
|
|
74
73
|
};
|
|
@@ -243,10 +242,6 @@ function podColumns(patch) {
|
|
|
243
242
|
};
|
|
244
243
|
return Object.fromEntries(Object.entries(columns).filter(([, value]) => value !== undefined));
|
|
245
244
|
}
|
|
246
|
-
/** An endpoint row with its unset (null) columns left out, as `EndpointRow` has them. */
|
|
247
|
-
function endpointRow(row) {
|
|
248
|
-
return Object.fromEntries(Object.entries(row).filter(([, value]) => value !== null));
|
|
249
|
-
}
|
|
250
245
|
/** The time column a signing key's new state stamps. */
|
|
251
246
|
const SIGNING_KEY_STAMP = {
|
|
252
247
|
current: "activatedAt",
|
|
@@ -255,11 +250,9 @@ const SIGNING_KEY_STAMP = {
|
|
|
255
250
|
};
|
|
256
251
|
/** The audit columns a row has (`ord` only orders them). */
|
|
257
252
|
const { ord: _ord, ...AUDIT } = getTableColumns(vaultAudit);
|
|
258
|
-
const SESSION_TABLES = [sessions, commands, effects,
|
|
253
|
+
const SESSION_TABLES = [sessions, commands, effects, links];
|
|
259
254
|
/** The inserted row's `column` (`ON CONFLICT … DO UPDATE`). */
|
|
260
255
|
const excluded = (column) => sql.raw(`excluded.${column}`);
|
|
261
|
-
/** Keeps an endpoint's health `column` when its URL stays; a new URL starts with `fresh`. */
|
|
262
|
-
const sameUrl = (column, fresh = sql `NULL`) => sql `CASE WHEN ${endpoints.url} = excluded.url THEN ${column} ELSE ${fresh} END`;
|
|
263
256
|
class PostgresTx {
|
|
264
257
|
db;
|
|
265
258
|
tenantId;
|
|
@@ -536,56 +529,6 @@ class PostgresTx {
|
|
|
536
529
|
.limit(options.limit ?? 1000);
|
|
537
530
|
return rows.map((row) => row.body);
|
|
538
531
|
}
|
|
539
|
-
async actionBodies(where) {
|
|
540
|
-
const rows = await this.db
|
|
541
|
-
.select({ body: actions.body })
|
|
542
|
-
.from(actions)
|
|
543
|
-
.where(where)
|
|
544
|
-
.orderBy(actions.id);
|
|
545
|
-
return rows.map((row) => row.body);
|
|
546
|
-
}
|
|
547
|
-
async pendingActions(agentId) {
|
|
548
|
-
this.check();
|
|
549
|
-
return this.actionBodies(and(eq(actions.agentId, agentId), eq(actions.status, "pending")));
|
|
550
|
-
}
|
|
551
|
-
async deliveringCount(agentId) {
|
|
552
|
-
this.check();
|
|
553
|
-
const [row] = await this.db
|
|
554
|
-
.select({ n: count() })
|
|
555
|
-
.from(actions)
|
|
556
|
-
.where(and(eq(actions.agentId, agentId), eq(actions.status, "delivering")));
|
|
557
|
-
return row.n;
|
|
558
|
-
}
|
|
559
|
-
async pendingActionsWithEndpoint(limit) {
|
|
560
|
-
this.check();
|
|
561
|
-
const rows = await this.db
|
|
562
|
-
.select({ body: actions.body })
|
|
563
|
-
.from(actions)
|
|
564
|
-
.innerJoin(endpoints, eq(endpoints.agentId, actions.agentId))
|
|
565
|
-
.where(eq(actions.status, "pending"))
|
|
566
|
-
.orderBy(actions.id)
|
|
567
|
-
.limit(limit);
|
|
568
|
-
return rows.map((row) => row.body);
|
|
569
|
-
}
|
|
570
|
-
async expiredDeliveries(now, limit) {
|
|
571
|
-
this.check();
|
|
572
|
-
const deadline = sql `${actions.deadlineAt}::timestamptz`;
|
|
573
|
-
const rows = await this.db
|
|
574
|
-
.select({ body: actions.body })
|
|
575
|
-
.from(actions)
|
|
576
|
-
.where(and(eq(actions.status, "delivering"), isNotNull(actions.deadlineAt), sql `${deadline} <= ${now.toISOString()}::timestamptz`))
|
|
577
|
-
.orderBy(deadline, actions.id)
|
|
578
|
-
.limit(limit);
|
|
579
|
-
return rows.map((row) => row.body);
|
|
580
|
-
}
|
|
581
|
-
async actionsForSession(sessionId, filter = {}) {
|
|
582
|
-
this.check();
|
|
583
|
-
return this.actionBodies(and(eq(actions.sessionId, sessionId), filter.turnId === undefined ? undefined : eq(actions.turnId, filter.turnId), filter.statuses === undefined ? undefined : inArray(actions.status, filter.statuses)));
|
|
584
|
-
}
|
|
585
|
-
async actionsWithStatus(statuses, filter = {}) {
|
|
586
|
-
this.check();
|
|
587
|
-
return this.actionBodies(and(inArray(actions.status, statuses), filter.kinds === undefined ? undefined : inArray(actions.kind, filter.kinds)));
|
|
588
|
-
}
|
|
589
532
|
async effectBodies(where) {
|
|
590
533
|
const rows = await this.db
|
|
591
534
|
.select({ body: effects.body })
|
|
@@ -629,15 +572,12 @@ class PostgresTx {
|
|
|
629
572
|
(SELECT count(*) FROM ${sessions})::int AS sessions,
|
|
630
573
|
(SELECT count(*) FROM ${sessions}
|
|
631
574
|
WHERE ${sessions.status} IN ('running', 'runnable'))::int AS running,
|
|
632
|
-
(SELECT count(*) FROM ${actions}
|
|
633
|
-
WHERE ${actions.status} IN ('pending', 'delivering'))::int AS actions,
|
|
634
575
|
(SELECT count(*) FROM ${effects} WHERE ${effects.status} = 'uncertain')::int AS uncertain,
|
|
635
576
|
(SELECT count(*) FROM ${sandboxes})::int AS sandboxes,
|
|
636
577
|
(SELECT count(*) FROM ${definitions})::int AS definitions`);
|
|
637
578
|
return {
|
|
638
579
|
sessions: row.sessions,
|
|
639
580
|
runningSessions: row.running,
|
|
640
|
-
pendingActions: row.actions,
|
|
641
581
|
uncertainEffects: row.uncertain,
|
|
642
582
|
sandboxes: row.sandboxes,
|
|
643
583
|
definitions: row.definitions,
|
|
@@ -658,81 +598,6 @@ class PostgresTx {
|
|
|
658
598
|
retiredGenerations: sql `array_remove(${tenant.retiredGenerations}, ${generation}::int)`,
|
|
659
599
|
});
|
|
660
600
|
}
|
|
661
|
-
// --- Action endpoints -----------------------------------------------------
|
|
662
|
-
async listEndpoints() {
|
|
663
|
-
this.check();
|
|
664
|
-
const rows = await this.db.select().from(endpoints).orderBy(endpoints.agentId);
|
|
665
|
-
return rows.map(endpointRow);
|
|
666
|
-
}
|
|
667
|
-
async getEndpoint(agentId) {
|
|
668
|
-
this.check();
|
|
669
|
-
const [row] = await this.db.select().from(endpoints).where(eq(endpoints.agentId, agentId));
|
|
670
|
-
return row && endpointRow(row);
|
|
671
|
-
}
|
|
672
|
-
async putEndpoint(row) {
|
|
673
|
-
this.check();
|
|
674
|
-
// Health belongs to a URL: a new URL starts with none.
|
|
675
|
-
await this.db
|
|
676
|
-
.insert(endpoints)
|
|
677
|
-
.values({
|
|
678
|
-
agentId: row.agentId,
|
|
679
|
-
url: row.url,
|
|
680
|
-
implementationVersion: row.implementationVersion,
|
|
681
|
-
manifestHash: row.manifestHash ?? null,
|
|
682
|
-
timeoutMs: row.timeoutMs,
|
|
683
|
-
maxConcurrent: row.maxConcurrent,
|
|
684
|
-
principalId: row.principalId ?? null,
|
|
685
|
-
consecutiveFailures: 0,
|
|
686
|
-
createdAt: row.updatedAt,
|
|
687
|
-
updatedAt: row.updatedAt,
|
|
688
|
-
})
|
|
689
|
-
.onConflictDoUpdate({
|
|
690
|
-
target: endpoints.agentId,
|
|
691
|
-
set: {
|
|
692
|
-
url: excluded("url"),
|
|
693
|
-
implementationVersion: excluded("implementation_version"),
|
|
694
|
-
manifestHash: excluded("manifest_hash"),
|
|
695
|
-
timeoutMs: excluded("timeout_ms"),
|
|
696
|
-
maxConcurrent: excluded("max_concurrent"),
|
|
697
|
-
principalId: excluded("principal_id"),
|
|
698
|
-
updatedAt: excluded("updated_at"),
|
|
699
|
-
lastDeliveryAt: sameUrl(sql `${endpoints.lastDeliveryAt}`),
|
|
700
|
-
lastSuccessAt: sameUrl(sql `${endpoints.lastSuccessAt}`),
|
|
701
|
-
lastErrorCode: sameUrl(sql `${endpoints.lastErrorCode}`),
|
|
702
|
-
lastErrorMessage: sameUrl(sql `${endpoints.lastErrorMessage}`),
|
|
703
|
-
consecutiveFailures: sameUrl(sql `${endpoints.consecutiveFailures}`, sql `0`),
|
|
704
|
-
servedImplementationVersion: sameUrl(sql `${endpoints.servedImplementationVersion}`),
|
|
705
|
-
servedManifestHash: sameUrl(sql `${endpoints.servedManifestHash}`),
|
|
706
|
-
},
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
async deleteEndpoint(agentId) {
|
|
710
|
-
this.check();
|
|
711
|
-
await this.db.delete(endpoints).where(eq(endpoints.agentId, agentId));
|
|
712
|
-
}
|
|
713
|
-
async recordEndpointHealth(agentId, update) {
|
|
714
|
-
this.check();
|
|
715
|
-
const set = update.kind === "success"
|
|
716
|
-
? {
|
|
717
|
-
lastDeliveryAt: update.at,
|
|
718
|
-
lastSuccessAt: update.at,
|
|
719
|
-
consecutiveFailures: 0,
|
|
720
|
-
lastErrorCode: null,
|
|
721
|
-
lastErrorMessage: null,
|
|
722
|
-
}
|
|
723
|
-
: update.kind === "failure"
|
|
724
|
-
? {
|
|
725
|
-
lastDeliveryAt: update.at,
|
|
726
|
-
lastErrorCode: update.code,
|
|
727
|
-
lastErrorMessage: update.message,
|
|
728
|
-
consecutiveFailures: sql `${endpoints.consecutiveFailures} + 1`,
|
|
729
|
-
}
|
|
730
|
-
: {
|
|
731
|
-
servedImplementationVersion: update.implementationVersion,
|
|
732
|
-
servedManifestHash: update.manifestHash ?? null,
|
|
733
|
-
};
|
|
734
|
-
await this.db.update(endpoints).set(set).where(eq(endpoints.agentId, agentId));
|
|
735
|
-
}
|
|
736
601
|
// --- principals ----------------------------------------------------------
|
|
737
602
|
async insertPrincipal(row) {
|
|
738
603
|
this.check();
|
|
@@ -751,14 +616,33 @@ class PostgresTx {
|
|
|
751
616
|
const [row] = await this.db.select().from(principals).where(eq(principals.id, id));
|
|
752
617
|
return row;
|
|
753
618
|
}
|
|
754
|
-
async
|
|
619
|
+
async listPrincipals() {
|
|
620
|
+
this.check();
|
|
621
|
+
return await this.db.select().from(principals).orderBy(principals.id);
|
|
622
|
+
}
|
|
623
|
+
async putPrincipal(id, tokenHash, createdAt, role = "application") {
|
|
624
|
+
this.check();
|
|
625
|
+
const [row] = await this.db
|
|
626
|
+
.insert(principals)
|
|
627
|
+
.values({ id, role, tokenHash, idempotencyKey: null, createdAt })
|
|
628
|
+
.onConflictDoUpdate({
|
|
629
|
+
target: principals.id,
|
|
630
|
+
set: { tokenHash, createdAt, idempotencyKey: null },
|
|
631
|
+
// A key keeps its role: rotating never turns one kind of key into another.
|
|
632
|
+
setWhere: eq(principals.role, role),
|
|
633
|
+
})
|
|
634
|
+
.returning();
|
|
635
|
+
if (!row)
|
|
636
|
+
throw new PrincipalRoleConflict(id);
|
|
637
|
+
return row;
|
|
638
|
+
}
|
|
639
|
+
async deletePrincipal(id) {
|
|
755
640
|
this.check();
|
|
756
641
|
const rows = await this.db
|
|
757
|
-
.
|
|
758
|
-
.
|
|
759
|
-
.
|
|
760
|
-
|
|
761
|
-
return rows.map((row) => row.tokenHash);
|
|
642
|
+
.delete(principals)
|
|
643
|
+
.where(eq(principals.id, id))
|
|
644
|
+
.returning({ id: principals.id });
|
|
645
|
+
return rows.length > 0;
|
|
762
646
|
}
|
|
763
647
|
// --- vault ---------------------------------------------------------------
|
|
764
648
|
async insertVault(row) {
|
|
@@ -778,6 +662,14 @@ class PostgresTx {
|
|
|
778
662
|
.where(and(eq(vaults.ownerUserId, ownerUserId), eq(vaults.scope, "user")))
|
|
779
663
|
.orderBy(vaults.createdAt, vaults.id);
|
|
780
664
|
}
|
|
665
|
+
async installationVaults() {
|
|
666
|
+
this.check();
|
|
667
|
+
return this.db
|
|
668
|
+
.select()
|
|
669
|
+
.from(vaults)
|
|
670
|
+
.where(eq(vaults.scope, "installation"))
|
|
671
|
+
.orderBy(vaults.createdAt, vaults.id);
|
|
672
|
+
}
|
|
781
673
|
async updateVaultMetadata(id, metadataJson) {
|
|
782
674
|
this.check();
|
|
783
675
|
await this.db.update(vaults).set({ metadataJson }).where(eq(vaults.id, id));
|
|
@@ -858,7 +750,24 @@ class PostgresTx {
|
|
|
858
750
|
this.check();
|
|
859
751
|
await this.db.insert(vaultIdempotency).values(row);
|
|
860
752
|
}
|
|
861
|
-
|
|
753
|
+
async insertOAuthPending(row) {
|
|
754
|
+
this.check();
|
|
755
|
+
await this.db.insert(oauthPending).values(row);
|
|
756
|
+
}
|
|
757
|
+
async takeOAuthPending(stateHash) {
|
|
758
|
+
this.check();
|
|
759
|
+
const [row] = await this.db
|
|
760
|
+
.delete(oauthPending)
|
|
761
|
+
.where(eq(oauthPending.stateHash, stateHash))
|
|
762
|
+
.returning();
|
|
763
|
+
return row;
|
|
764
|
+
}
|
|
765
|
+
async deleteExpiredOAuthPending(now) {
|
|
766
|
+
this.check();
|
|
767
|
+
const result = await this.db.delete(oauthPending).where(lt(oauthPending.expiresAt, now));
|
|
768
|
+
return result.count;
|
|
769
|
+
}
|
|
770
|
+
// --- signing keys --------------------------------------------------------
|
|
862
771
|
async lockSigningKeys() {
|
|
863
772
|
this.check();
|
|
864
773
|
await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.signing_keys'))`);
|
|
@@ -895,103 +804,6 @@ class PostgresTx {
|
|
|
895
804
|
const [row] = await this.db.select({ n: count() }).from(signingKeys);
|
|
896
805
|
return row.n;
|
|
897
806
|
}
|
|
898
|
-
async subjectEpoch(subject) {
|
|
899
|
-
this.check();
|
|
900
|
-
const [row] = await this.db
|
|
901
|
-
.select({ epoch: subjectEpochs.epoch })
|
|
902
|
-
.from(subjectEpochs)
|
|
903
|
-
.where(eq(subjectEpochs.subject, subject));
|
|
904
|
-
return row?.epoch ?? 0;
|
|
905
|
-
}
|
|
906
|
-
async subjectEpochs(subjects) {
|
|
907
|
-
this.check();
|
|
908
|
-
if (subjects.length === 0)
|
|
909
|
-
return new Map();
|
|
910
|
-
const rows = await this.db
|
|
911
|
-
.select({ subject: subjectEpochs.subject, epoch: subjectEpochs.epoch })
|
|
912
|
-
.from(subjectEpochs)
|
|
913
|
-
.where(inArray(subjectEpochs.subject, subjects));
|
|
914
|
-
return new Map(rows.map((row) => [row.subject, row.epoch]));
|
|
915
|
-
}
|
|
916
|
-
async bumpSubjectEpoch(subject, at) {
|
|
917
|
-
this.check();
|
|
918
|
-
const [row] = await this.db
|
|
919
|
-
.insert(subjectEpochs)
|
|
920
|
-
.values({ subject, epoch: 1, revokedAt: at })
|
|
921
|
-
.onConflictDoUpdate({
|
|
922
|
-
target: subjectEpochs.subject,
|
|
923
|
-
set: { epoch: sql `${subjectEpochs.epoch} + 1`, revokedAt: excluded("revoked_at") },
|
|
924
|
-
})
|
|
925
|
-
.returning({ epoch: subjectEpochs.epoch });
|
|
926
|
-
return row.epoch;
|
|
927
|
-
}
|
|
928
|
-
async lockSubjectUsage(initial) {
|
|
929
|
-
this.check();
|
|
930
|
-
await this.db.insert(subjectUsage).values(initial).onConflictDoNothing();
|
|
931
|
-
const [row] = await this.db
|
|
932
|
-
.select()
|
|
933
|
-
.from(subjectUsage)
|
|
934
|
-
.where(eq(subjectUsage.subject, initial.subject))
|
|
935
|
-
.for("update");
|
|
936
|
-
return row;
|
|
937
|
-
}
|
|
938
|
-
async putSubjectUsage(row) {
|
|
939
|
-
this.check();
|
|
940
|
-
await this.db
|
|
941
|
-
.insert(subjectUsage)
|
|
942
|
-
.values(row)
|
|
943
|
-
.onConflictDoUpdate({
|
|
944
|
-
target: subjectUsage.subject,
|
|
945
|
-
set: { turnTokens: excluded("turn_tokens"), refilledAt: excluded("refilled_at") },
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
async countOwnerSessions(ownerUserId, statuses) {
|
|
949
|
-
this.check();
|
|
950
|
-
const [row] = await this.db
|
|
951
|
-
.select({ n: count() })
|
|
952
|
-
.from(sessions)
|
|
953
|
-
.where(and(eq(sessions.ownerUserId, ownerUserId), inArray(sessions.status, statuses)));
|
|
954
|
-
return row.n;
|
|
955
|
-
}
|
|
956
|
-
// --- publishable keys ----------------------------------------------------
|
|
957
|
-
async insertPublishableKey(row) {
|
|
958
|
-
this.check();
|
|
959
|
-
await this.db.insert(publishableKeys).values(row);
|
|
960
|
-
}
|
|
961
|
-
async publishableKeyByKey(key) {
|
|
962
|
-
this.check();
|
|
963
|
-
const [row] = await this.db
|
|
964
|
-
.select()
|
|
965
|
-
.from(publishableKeys)
|
|
966
|
-
.where(eq(publishableKeys.key, key));
|
|
967
|
-
return row;
|
|
968
|
-
}
|
|
969
|
-
async publishableKey(id) {
|
|
970
|
-
this.check();
|
|
971
|
-
const [row] = await this.db
|
|
972
|
-
.select()
|
|
973
|
-
.from(publishableKeys)
|
|
974
|
-
.where(eq(publishableKeys.id, id));
|
|
975
|
-
return row;
|
|
976
|
-
}
|
|
977
|
-
async publishableKeys() {
|
|
978
|
-
this.check();
|
|
979
|
-
return this.db
|
|
980
|
-
.select()
|
|
981
|
-
.from(publishableKeys)
|
|
982
|
-
.orderBy(publishableKeys.createdAt, publishableKeys.id);
|
|
983
|
-
}
|
|
984
|
-
async updatePublishableKey(id, patch) {
|
|
985
|
-
this.check();
|
|
986
|
-
if (patch.originsJson === undefined && patch.revokedAt === undefined)
|
|
987
|
-
return (await this.publishableKey(id)) !== undefined;
|
|
988
|
-
const rows = await this.db
|
|
989
|
-
.update(publishableKeys)
|
|
990
|
-
.set(patch)
|
|
991
|
-
.where(eq(publishableKeys.id, id))
|
|
992
|
-
.returning({ id: publishableKeys.id });
|
|
993
|
-
return rows.length === 1;
|
|
994
|
-
}
|
|
995
807
|
// --- model usage ---------------------------------------------------------
|
|
996
808
|
async recordModelUsage(row) {
|
|
997
809
|
this.check();
|
|
@@ -1001,8 +813,8 @@ class PostgresTx {
|
|
|
1001
813
|
...row,
|
|
1002
814
|
duplicate: sql `EXISTS (SELECT 1 FROM ${modelUsage} WHERE ${modelUsage.effectKey} = ${row.effectKey})`,
|
|
1003
815
|
})
|
|
1004
|
-
.returning(
|
|
1005
|
-
return
|
|
816
|
+
.returning();
|
|
817
|
+
return inserted;
|
|
1006
818
|
}
|
|
1007
819
|
async modelUsageTotals(query) {
|
|
1008
820
|
this.check();
|
|
@@ -1185,6 +997,42 @@ class PostgresTx {
|
|
|
1185
997
|
.where(scope === "sessions" ? isNotNull(artifacts.sessionId) : undefined);
|
|
1186
998
|
return rows.map((row) => row.blobKey);
|
|
1187
999
|
}
|
|
1000
|
+
// --- definition files -----------------------------------------------------
|
|
1001
|
+
async definitionFile(sha256) {
|
|
1002
|
+
this.check();
|
|
1003
|
+
const [row] = await this.db.select().from(definitionFiles).where(eq(definitionFiles.sha256, sha256));
|
|
1004
|
+
return row;
|
|
1005
|
+
}
|
|
1006
|
+
async insertDefinitionFile(row) {
|
|
1007
|
+
this.check();
|
|
1008
|
+
const inserted = await this.db
|
|
1009
|
+
.insert(definitionFiles)
|
|
1010
|
+
.values(row)
|
|
1011
|
+
.onConflictDoNothing()
|
|
1012
|
+
.returning({ sha256: definitionFiles.sha256 });
|
|
1013
|
+
return inserted.length === 1;
|
|
1014
|
+
}
|
|
1015
|
+
async heldDefinitionFiles(shas) {
|
|
1016
|
+
this.check();
|
|
1017
|
+
const found = new Set();
|
|
1018
|
+
for (let at = 0; at < shas.length; at += CONTENT_BATCH) {
|
|
1019
|
+
const rows = await this.db
|
|
1020
|
+
.select({ sha256: definitionFiles.sha256 })
|
|
1021
|
+
.from(definitionFiles)
|
|
1022
|
+
.where(inArray(definitionFiles.sha256, shas.slice(at, at + CONTENT_BATCH)));
|
|
1023
|
+
for (const row of rows)
|
|
1024
|
+
found.add(row.sha256);
|
|
1025
|
+
}
|
|
1026
|
+
return found;
|
|
1027
|
+
}
|
|
1028
|
+
async insertDefinitionFileUses(agentId, manifestHash, shas) {
|
|
1029
|
+
this.check();
|
|
1030
|
+
for (let at = 0; at < shas.length; at += CONTENT_BATCH)
|
|
1031
|
+
await this.db
|
|
1032
|
+
.insert(definitionFileUses)
|
|
1033
|
+
.values(shas.slice(at, at + CONTENT_BATCH).map((sha256) => ({ agentId, manifestHash, sha256 })))
|
|
1034
|
+
.onConflictDoNothing();
|
|
1035
|
+
}
|
|
1188
1036
|
// --- settings ------------------------------------------------------------
|
|
1189
1037
|
async getSetting(key) {
|
|
1190
1038
|
this.check();
|
|
@@ -1208,7 +1056,6 @@ class PostgresTx {
|
|
|
1208
1056
|
if (scope === "sessions" || scope === "all") {
|
|
1209
1057
|
for (const table of SESSION_TABLES)
|
|
1210
1058
|
await db.delete(table);
|
|
1211
|
-
await db.delete(subjectUsage);
|
|
1212
1059
|
await db.delete(toolCrossings);
|
|
1213
1060
|
// A session's artifacts go with it (their versions by cascade).
|
|
1214
1061
|
await db.delete(artifacts).where(isNotNull(artifacts.sessionId));
|
|
@@ -1228,7 +1075,8 @@ class PostgresTx {
|
|
|
1228
1075
|
}
|
|
1229
1076
|
if (scope === "all") {
|
|
1230
1077
|
await db.delete(definitions);
|
|
1231
|
-
|
|
1078
|
+
// The files stay, as their bytes do; only what used them goes.
|
|
1079
|
+
await db.delete(definitionFileUses);
|
|
1232
1080
|
await db.delete(modelUsage);
|
|
1233
1081
|
await db.delete(modelBudgets);
|
|
1234
1082
|
await db.delete(artifacts);
|
|
@@ -1,3 +1,27 @@
|
|
|
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 (the
|
|
18
|
+
* ephemeral Runtime's application key); existing ones are kept, as ordinary keys.
|
|
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 type { KeyRole } from "@nylorun/core/compatibility";
|
|
1
25
|
import type { Sql } from "postgres";
|
|
2
26
|
import { type TenantEnvelope } from "@nylorun/core/contracts";
|
|
3
27
|
import type { SessionStore } from "../types.js";
|
|
@@ -8,6 +32,14 @@ export interface InitialPrincipal {
|
|
|
8
32
|
id: string;
|
|
9
33
|
/** SHA-256 hex of the principal's key. */
|
|
10
34
|
credentialHash: string;
|
|
35
|
+
/** What the key reaches; `application` when absent. */
|
|
36
|
+
role?: KeyRole;
|
|
37
|
+
/**
|
|
38
|
+
* Its key comes from outside the database (the bootstrap file, or Studio's key derived from
|
|
39
|
+
* the admin key): on later opens a principal of the same role takes this hash. Otherwise an
|
|
40
|
+
* existing principal is kept as it is.
|
|
41
|
+
*/
|
|
42
|
+
replace?: boolean;
|
|
11
43
|
}
|
|
12
44
|
/** Who the Tenant is when the database holds none yet. */
|
|
13
45
|
export interface TenantCreation {
|
|
@@ -16,8 +48,8 @@ export interface TenantCreation {
|
|
|
16
48
|
/** Ignored when the Tenant exists. */
|
|
17
49
|
name: string;
|
|
18
50
|
/**
|
|
19
|
-
* The principals the Tenant must have, given its id
|
|
20
|
-
*
|
|
51
|
+
* The principals the Tenant must have, given its id. Created with the Tenant; on later
|
|
52
|
+
* opens, the missing ones are added.
|
|
21
53
|
*/
|
|
22
54
|
principals?(tenantId: string): readonly InitialPrincipal[];
|
|
23
55
|
}
|
|
@@ -1,27 +1,4 @@
|
|
|
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";
|
|
1
|
+
import { and, eq, ne, sql } from "drizzle-orm";
|
|
25
2
|
import { isTenantId, newTenantId } from "@nylorun/core/compatibility";
|
|
26
3
|
import { TenantEnvelopeSchema, } from "@nylorun/core/contracts";
|
|
27
4
|
import { openError, TenantOpenError } from "../../tenant/cause.js";
|
|
@@ -138,17 +115,24 @@ async function ensureTenant(tx, create, migrated, now) {
|
|
|
138
115
|
else if (migrated.from !== migrated.to) {
|
|
139
116
|
await tx.update(tenant).set({ schemaVersion: migrated.to });
|
|
140
117
|
}
|
|
141
|
-
for (const principal of create.principals?.(tenantId) ?? [])
|
|
142
|
-
|
|
143
|
-
await tx
|
|
144
|
-
.insert(principals)
|
|
145
|
-
.values({
|
|
118
|
+
for (const principal of create.principals?.(tenantId) ?? []) {
|
|
119
|
+
const insert = tx.insert(principals).values({
|
|
146
120
|
id: principal.id,
|
|
147
|
-
role: "application",
|
|
121
|
+
role: principal.role ?? "application",
|
|
148
122
|
tokenHash: principal.credentialHash,
|
|
149
123
|
idempotencyKey: null,
|
|
150
124
|
createdAt,
|
|
151
|
-
})
|
|
152
|
-
|
|
125
|
+
});
|
|
126
|
+
if (principal.replace)
|
|
127
|
+
// The bootstrap key or Studio's: a principal of the same role takes the current key.
|
|
128
|
+
await insert.onConflictDoUpdate({
|
|
129
|
+
target: principals.id,
|
|
130
|
+
set: { tokenHash: principal.credentialHash, createdAt },
|
|
131
|
+
setWhere: and(eq(principals.role, principal.role ?? "application"), ne(principals.tokenHash, principal.credentialHash)),
|
|
132
|
+
});
|
|
133
|
+
// A principal with this id or this key already exists: it is kept as it is.
|
|
134
|
+
else
|
|
135
|
+
await insert.onConflictDoNothing();
|
|
136
|
+
}
|
|
153
137
|
return existing === undefined;
|
|
154
138
|
}
|