@nylorun/runtime 0.16.0-beta → 0.17.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 +58 -0
- package/README.md +39 -27
- package/dist/admin-openapi.json +307 -7
- 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 +10 -12
- package/dist/api/components.d.ts +54 -116
- package/dist/api/components.js +8 -11
- package/dist/api/http/app.js +2 -0
- package/dist/api/http/define.d.ts +9 -12
- package/dist/api/http/define.js +11 -26
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +12 -172
- package/dist/api/http/routes/actions.js +0 -1
- package/dist/api/http/routes/artifacts.js +1 -3
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +45 -0
- package/dist/api/http/routes/sandboxes.d.ts +2 -2
- package/dist/api/http/routes/sandboxes.js +1 -3
- package/dist/api/http/routes/sessions.js +6 -12
- package/dist/api/http/routes/vaults.d.ts +12 -3
- package/dist/api/http/routes/vaults.js +122 -54
- package/dist/api/openapi.js +4 -10
- package/dist/artifacts/links.js +6 -6
- package/dist/artifacts/service.js +1 -1
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/tenant-vaults.d.ts +18 -9
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/host/admin-api.d.ts +12 -5
- package/dist/host/admin-api.js +72 -6
- package/dist/host/app.d.ts +0 -1
- package/dist/host/app.js +13 -23
- package/dist/host/config-for.d.ts +4 -0
- package/dist/host/config-for.js +2 -0
- package/dist/host/config.d.ts +0 -6
- package/dist/host/create-host.d.ts +3 -10
- package/dist/host/create-host.js +4 -1
- package/dist/host/gates.js +6 -1
- package/dist/host/main.js +32 -10
- package/dist/host/stack-config.d.ts +11 -8
- package/dist/host/stack-config.js +23 -12
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +8 -0
- package/dist/mcp/connect.js +2 -1
- package/dist/mcp/pool.d.ts +1 -0
- package/dist/mcp/pool.js +1 -0
- package/dist/openapi.json +2359 -4096
- 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/meta/_journal.json +21 -0
- package/dist/store/postgres/schema.d.ts +194 -170
- package/dist/store/postgres/schema.js +46 -28
- package/dist/store/postgres/store.js +51 -100
- package/dist/store/postgres/tenant.d.ts +1 -1
- package/dist/store/postgres/tenant.js +2 -2
- package/dist/store/types.d.ts +24 -30
- package/dist/streams/control.d.ts +0 -5
- package/dist/streams/control.js +0 -8
- package/dist/streams/types.d.ts +2 -11
- package/dist/streams/types.js +1 -1
- package/dist/tenant/auth.d.ts +13 -9
- package/dist/tenant/auth.js +25 -32
- package/dist/tenant/commands.js +3 -7
- package/dist/tenant/context.d.ts +20 -22
- package/dist/tenant/delivery-token.js +4 -4
- package/dist/tenant/effects.d.ts +5 -1
- package/dist/tenant/effects.js +5 -16
- package/dist/tenant/ephemeral.d.ts +14 -2
- package/dist/tenant/ephemeral.js +8 -1
- 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/operator-keys.d.ts +18 -0
- package/dist/tenant/operator-keys.js +55 -0
- package/dist/tenant/outbound.d.ts +30 -0
- package/dist/tenant/outbound.js +142 -10
- package/dist/tenant/principals.d.ts +2 -11
- package/dist/tenant/principals.js +10 -30
- package/dist/tenant/run-token.js +3 -3
- package/dist/tenant/runtime.d.ts +4 -0
- package/dist/tenant/runtime.js +14 -1
- package/dist/tenant/sandboxes.d.ts +5 -5
- package/dist/tenant/sandboxes.js +5 -5
- package/dist/tenant/session-streams.d.ts +6 -10
- package/dist/tenant/session-streams.js +9 -27
- package/dist/tenant/sessions.d.ts +2 -2
- package/dist/tenant/sessions.js +1 -1
- package/dist/tenant/signing-keys.d.ts +2 -1
- package/dist/tenant/signing-keys.js +2 -2
- package/dist/tenant/streams.d.ts +2 -3
- package/dist/tenant/streams.js +1 -5
- package/dist/tenant/types.d.ts +21 -0
- 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 +54 -0
- package/dist/vault/sources.js +185 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- 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
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* "snake_case"`, here, in `db.ts` and in `drizzle.config.ts`).
|
|
6
6
|
*
|
|
7
7
|
* - `nylorun` holds the Tenant's state: the one `tenant` row, the document tables, Action
|
|
8
|
-
* endpoints, principals, vaults and credentials, signing keys,
|
|
9
|
-
*
|
|
10
|
-
* crossings and file artifacts with their versions.
|
|
8
|
+
* endpoints, principals, vaults and credentials, signing keys, settings, the model usage
|
|
9
|
+
* ledger, the model budgets, the Tool Gate's crossings and file artifacts with their versions.
|
|
11
10
|
* - `nylorun_streams` holds the record (Durable Streams §6): `session_events`,
|
|
12
11
|
* `session_log_heads` and the relay's `relay_slots`. The relay's publication is custom SQL
|
|
13
12
|
* (`drizzle/0002_stream_relay.sql`).
|
|
@@ -160,11 +159,15 @@ export const vaults = nylorun.table("vaults", {
|
|
|
160
159
|
/** JSON text, or null. */
|
|
161
160
|
metadataJson: text(),
|
|
162
161
|
createdAt: textC().notNull(),
|
|
163
|
-
|
|
162
|
+
/**
|
|
163
|
+
* `user`: one person's (owner `ownerUserId`); `installation`: the installation's own,
|
|
164
|
+
* attachable to any session (owner `installation`); `host`: the model vault, never attached.
|
|
165
|
+
*/
|
|
166
|
+
scope: text({ enum: ["user", "installation", "host"] }).notNull().default("user"),
|
|
164
167
|
}, (t) => [
|
|
165
168
|
index("vaults_owner").on(t.ownerUserId, t.createdAt, t.id),
|
|
166
169
|
uniqueIndex("vaults_one_host").on(t.scope).where(sql `scope = 'host'`),
|
|
167
|
-
check("vaults_scope_check", sql `scope IN ('user', 'host')`),
|
|
170
|
+
check("vaults_scope_check", sql `scope IN ('user', 'installation', 'host')`),
|
|
168
171
|
]);
|
|
169
172
|
/** A credential of a vault: its binding in the clear, its secret sealed. */
|
|
170
173
|
export const vaultCredentials = nylorun.table("vault_credentials", {
|
|
@@ -207,11 +210,48 @@ export const vaultIdempotency = nylorun.table("vault_idempotency", {
|
|
|
207
210
|
/** JSON text of the original response. */
|
|
208
211
|
response: text().notNull(),
|
|
209
212
|
});
|
|
213
|
+
/**
|
|
214
|
+
* An MCP OAuth connect between its start and its callback (F9 C2): found by the SHA-256 of its
|
|
215
|
+
* `state`, used once, for ten minutes. The PKCE verifier and any client secret are sealed under
|
|
216
|
+
* the vault key (`sealBytes`), which only the keys module holds.
|
|
217
|
+
*/
|
|
218
|
+
export const oauthPending = nylorun.table("oauth_pending", {
|
|
219
|
+
stateHash: text().primaryKey(),
|
|
220
|
+
vaultId: text().notNull(),
|
|
221
|
+
/** The MCP server's name in the agent, the credential's name. */
|
|
222
|
+
server: text().notNull(),
|
|
223
|
+
/** The MCP server's URL (normalized): the credential's binding. */
|
|
224
|
+
url: text().notNull(),
|
|
225
|
+
tokenEndpoint: text().notNull(),
|
|
226
|
+
clientId: text().notNull(),
|
|
227
|
+
/** How the client authenticates at the token endpoint: `none`, `client_secret_basic` or `client_secret_post`. */
|
|
228
|
+
tokenEndpointAuth: text({ enum: ["none", "client_secret_basic", "client_secret_post"] }).notNull(),
|
|
229
|
+
/** The RFC 8707 resource indicator sent with the authorization request, if any. */
|
|
230
|
+
resource: text(),
|
|
231
|
+
kekId: text().notNull(),
|
|
232
|
+
/** Sealed; null for a public client. */
|
|
233
|
+
clientSecret: bytes(),
|
|
234
|
+
/** Sealed. */
|
|
235
|
+
codeVerifier: bytes().notNull(),
|
|
236
|
+
redirectUri: text().notNull(),
|
|
237
|
+
expiresAt: textC().notNull(),
|
|
238
|
+
createdAt: textC().notNull(),
|
|
239
|
+
}, (t) => [
|
|
240
|
+
foreignKey({
|
|
241
|
+
name: "oauth_pending_vault_id_fkey",
|
|
242
|
+
columns: [t.vaultId],
|
|
243
|
+
foreignColumns: [vaults.id],
|
|
244
|
+
}).onDelete("cascade"),
|
|
245
|
+
index("oauth_pending_expires").on(t.expiresAt),
|
|
246
|
+
]);
|
|
210
247
|
export const tenantSettings = nylorun.table("tenant_settings", {
|
|
211
248
|
key: text().primaryKey(),
|
|
212
249
|
value: text().notNull(),
|
|
213
250
|
});
|
|
214
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* The Tenant's signing keys (delivery tokens, capability links, run and host tokens): the
|
|
253
|
+
* private half sealed, the public JWK not.
|
|
254
|
+
*/
|
|
215
255
|
export const signingKeys = nylorun.table("signing_keys", {
|
|
216
256
|
id: textC().primaryKey(),
|
|
217
257
|
state: text({ enum: ["standby", "current", "previous", "revoked"] }).notNull(),
|
|
@@ -233,28 +273,6 @@ export const signingKeys = nylorun.table("signing_keys", {
|
|
|
233
273
|
uniqueIndex("signing_keys_one_current").on(t.state).where(sql `state = 'current'`),
|
|
234
274
|
uniqueIndex("signing_keys_one_previous").on(t.state).where(sql `state = 'previous'`),
|
|
235
275
|
]);
|
|
236
|
-
/** Each subject's revocation epoch. */
|
|
237
|
-
export const subjectEpochs = nylorun.table("subject_epochs", {
|
|
238
|
-
subject: textC().primaryKey(),
|
|
239
|
-
epoch: bigint({ mode: "number" }).notNull(),
|
|
240
|
-
revokedAt: text().notNull(),
|
|
241
|
-
});
|
|
242
|
-
/** Each subject's turn bucket: tokens left and when they were last refilled. */
|
|
243
|
-
export const subjectUsage = nylorun.table("subject_usage", {
|
|
244
|
-
subject: textC().primaryKey(),
|
|
245
|
-
turnTokens: doublePrecision().notNull(),
|
|
246
|
-
refilledAt: text().notNull(),
|
|
247
|
-
});
|
|
248
|
-
/** Publishable keys (Host feature `browser-access`): public by design. */
|
|
249
|
-
export const publishableKeys = nylorun.table("publishable_keys", {
|
|
250
|
-
id: textC().primaryKey(),
|
|
251
|
-
key: textC().notNull().unique("publishable_keys_key_key"),
|
|
252
|
-
name: text().notNull().unique("publishable_keys_name_key"),
|
|
253
|
-
/** JSON array of allowed origins. */
|
|
254
|
-
originsJson: text().notNull(),
|
|
255
|
-
createdAt: text().notNull(),
|
|
256
|
-
revokedAt: text(),
|
|
257
|
-
});
|
|
258
276
|
/**
|
|
259
277
|
* Action endpoints: where the Runtime delivers each agent's Actions over HTTP, and the health
|
|
260
278
|
* recent deliveries and the last ping report.
|
|
@@ -56,7 +56,7 @@ 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 { actions, artifacts, artifactVersions, artifactContent, commands, definitions, effects, endpoints, links, modelBudgets, modelUsage, principals,
|
|
59
|
+
import { actions, artifacts, artifactVersions, artifactContent, commands, definitions, effects, endpoints, 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
|
}
|
|
@@ -760,6 +760,30 @@ class PostgresTx {
|
|
|
760
760
|
.orderBy(principals.id);
|
|
761
761
|
return rows.map((row) => row.tokenHash);
|
|
762
762
|
}
|
|
763
|
+
async listPrincipals() {
|
|
764
|
+
this.check();
|
|
765
|
+
return await this.db.select().from(principals).orderBy(principals.id);
|
|
766
|
+
}
|
|
767
|
+
async putPrincipal(id, tokenHash, createdAt) {
|
|
768
|
+
this.check();
|
|
769
|
+
const [row] = await this.db
|
|
770
|
+
.insert(principals)
|
|
771
|
+
.values({ id, role: "application", tokenHash, idempotencyKey: null, createdAt })
|
|
772
|
+
.onConflictDoUpdate({
|
|
773
|
+
target: principals.id,
|
|
774
|
+
set: { tokenHash, createdAt, idempotencyKey: null },
|
|
775
|
+
})
|
|
776
|
+
.returning();
|
|
777
|
+
return row;
|
|
778
|
+
}
|
|
779
|
+
async deletePrincipal(id) {
|
|
780
|
+
this.check();
|
|
781
|
+
const rows = await this.db
|
|
782
|
+
.delete(principals)
|
|
783
|
+
.where(eq(principals.id, id))
|
|
784
|
+
.returning({ id: principals.id });
|
|
785
|
+
return rows.length > 0;
|
|
786
|
+
}
|
|
763
787
|
// --- vault ---------------------------------------------------------------
|
|
764
788
|
async insertVault(row) {
|
|
765
789
|
this.check();
|
|
@@ -778,6 +802,14 @@ class PostgresTx {
|
|
|
778
802
|
.where(and(eq(vaults.ownerUserId, ownerUserId), eq(vaults.scope, "user")))
|
|
779
803
|
.orderBy(vaults.createdAt, vaults.id);
|
|
780
804
|
}
|
|
805
|
+
async installationVaults() {
|
|
806
|
+
this.check();
|
|
807
|
+
return this.db
|
|
808
|
+
.select()
|
|
809
|
+
.from(vaults)
|
|
810
|
+
.where(eq(vaults.scope, "installation"))
|
|
811
|
+
.orderBy(vaults.createdAt, vaults.id);
|
|
812
|
+
}
|
|
781
813
|
async updateVaultMetadata(id, metadataJson) {
|
|
782
814
|
this.check();
|
|
783
815
|
await this.db.update(vaults).set({ metadataJson }).where(eq(vaults.id, id));
|
|
@@ -858,7 +890,24 @@ class PostgresTx {
|
|
|
858
890
|
this.check();
|
|
859
891
|
await this.db.insert(vaultIdempotency).values(row);
|
|
860
892
|
}
|
|
861
|
-
|
|
893
|
+
async insertOAuthPending(row) {
|
|
894
|
+
this.check();
|
|
895
|
+
await this.db.insert(oauthPending).values(row);
|
|
896
|
+
}
|
|
897
|
+
async takeOAuthPending(stateHash) {
|
|
898
|
+
this.check();
|
|
899
|
+
const [row] = await this.db
|
|
900
|
+
.delete(oauthPending)
|
|
901
|
+
.where(eq(oauthPending.stateHash, stateHash))
|
|
902
|
+
.returning();
|
|
903
|
+
return row;
|
|
904
|
+
}
|
|
905
|
+
async deleteExpiredOAuthPending(now) {
|
|
906
|
+
this.check();
|
|
907
|
+
const result = await this.db.delete(oauthPending).where(lt(oauthPending.expiresAt, now));
|
|
908
|
+
return result.count;
|
|
909
|
+
}
|
|
910
|
+
// --- signing keys --------------------------------------------------------
|
|
862
911
|
async lockSigningKeys() {
|
|
863
912
|
this.check();
|
|
864
913
|
await this.db.execute(sql `SELECT pg_advisory_xact_lock(hashtext('nylorun.signing_keys'))`);
|
|
@@ -895,103 +944,6 @@ class PostgresTx {
|
|
|
895
944
|
const [row] = await this.db.select({ n: count() }).from(signingKeys);
|
|
896
945
|
return row.n;
|
|
897
946
|
}
|
|
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
947
|
// --- model usage ---------------------------------------------------------
|
|
996
948
|
async recordModelUsage(row) {
|
|
997
949
|
this.check();
|
|
@@ -1208,7 +1160,6 @@ class PostgresTx {
|
|
|
1208
1160
|
if (scope === "sessions" || scope === "all") {
|
|
1209
1161
|
for (const table of SESSION_TABLES)
|
|
1210
1162
|
await db.delete(table);
|
|
1211
|
-
await db.delete(subjectUsage);
|
|
1212
1163
|
await db.delete(toolCrossings);
|
|
1213
1164
|
// A session's artifacts go with it (their versions by cascade).
|
|
1214
1165
|
await db.delete(artifacts).where(isNotNull(artifacts.sessionId));
|
|
@@ -16,7 +16,7 @@ export interface TenantCreation {
|
|
|
16
16
|
/** Ignored when the Tenant exists. */
|
|
17
17
|
name: string;
|
|
18
18
|
/**
|
|
19
|
-
* The principals the Tenant must have, given its id (derived
|
|
19
|
+
* The principals the Tenant must have, given its id (Studio's derived key depends on it). Created
|
|
20
20
|
* with the Tenant; on later opens, the missing ones are added.
|
|
21
21
|
*/
|
|
22
22
|
principals?(tenantId: string): readonly InitialPrincipal[];
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* 3. When `nylorun.tenant` is empty, create the Tenant: its row (id, name, created time) and
|
|
15
15
|
* its first principals. A database whose row is gone but that holds a Tenant's data
|
|
16
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 (
|
|
18
|
-
*
|
|
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
19
|
* 4. Read the envelope back (`envelope-invalid` when it does not parse), and open the store.
|
|
20
20
|
*
|
|
21
21
|
* A lost connection or an unavailable server says nothing about the Tenant: it is thrown as
|
package/dist/store/types.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ import type { RecordReader } from "../streams/relay/types.js";
|
|
|
58
58
|
import type { Action, EventPayload, EventType, LiveEvent, SandboxEvent, SandboxEventPayload, SandboxEventType, SandboxKind, SandboxPodDesired, SandboxPodObserved, SessionEventOf } from "@nylorun/core/contracts";
|
|
59
59
|
import type { SandboxManifest } from "@nylorun/core/define";
|
|
60
60
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
61
|
-
import type { ArtifactRow, ArtifactVersionRow, ModelBudgetRow, ModelUsageRow, PrincipalRow, ToolCrossingRow,
|
|
61
|
+
import type { ArtifactRow, ArtifactVersionRow, ModelBudgetRow, ModelUsageRow, PrincipalRow, ToolCrossingRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow, OAuthPendingRow } from "./postgres/schema.js";
|
|
62
62
|
/** Tables holding JSON documents keyed by `id`. */
|
|
63
63
|
export type DocTable = "definitions" | "sessions" | "commands" | "effects" | "actions" | "sandboxes" | "links";
|
|
64
64
|
export declare const DOC_TABLES: readonly DocTable[];
|
|
@@ -242,10 +242,9 @@ export type EndpointHealthUpdate =
|
|
|
242
242
|
* The rows of the typed tables, inferred from the tables Drizzle defines
|
|
243
243
|
* (`store/postgres/schema.ts`): a principal, a vault and its credentials (secrets sealed in
|
|
244
244
|
* `bytea` columns, never inside a JSON body), the vault's audit and idempotency records, the
|
|
245
|
-
* Tenant's signing keys, a
|
|
246
|
-
* ledger and a model budget.
|
|
245
|
+
* Tenant's signing keys, a model call in the usage ledger and a model budget.
|
|
247
246
|
*/
|
|
248
|
-
export type { ArtifactRow, ArtifactVersionRow, ModelBudgetRow, ModelUsageRow, ToolCrossingRow, PrincipalRow,
|
|
247
|
+
export type { ArtifactRow, ArtifactVersionRow, ModelBudgetRow, ModelUsageRow, ToolCrossingRow, PrincipalRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow, OAuthPendingRow, };
|
|
249
248
|
/** Which rows of the usage ledger a total covers. */
|
|
250
249
|
export interface ModelUsageQuery {
|
|
251
250
|
scope: "tenant" | "agent" | "turn";
|
|
@@ -480,11 +479,23 @@ export interface Tx {
|
|
|
480
479
|
principalByTokenHash(tokenHash: string): Promise<PrincipalRow | undefined>;
|
|
481
480
|
principalById(id: string): Promise<PrincipalRow | undefined>;
|
|
482
481
|
applicationTokenHashes(): Promise<string[]>;
|
|
482
|
+
/** Every principal, ordered by id. */
|
|
483
|
+
listPrincipals(): Promise<PrincipalRow[]>;
|
|
484
|
+
/**
|
|
485
|
+
* Creates application principal `id` with `tokenHash`, or gives an existing one this hash
|
|
486
|
+
* (and `createdAt`): its previous key stops authenticating. Rejects when another principal
|
|
487
|
+
* holds the hash.
|
|
488
|
+
*/
|
|
489
|
+
putPrincipal(id: string, tokenHash: string, createdAt: string): Promise<PrincipalRow>;
|
|
490
|
+
/** Deletes principal `id`; false when there was none. */
|
|
491
|
+
deletePrincipal(id: string): Promise<boolean>;
|
|
483
492
|
/** Rejects on a duplicate id, or a second `host` vault. */
|
|
484
493
|
insertVault(row: VaultRow): Promise<void>;
|
|
485
494
|
getVault(id: string): Promise<VaultRow | undefined>;
|
|
486
495
|
/** User vaults of one owner, ordered by `createdAt`, then id. */
|
|
487
496
|
vaultsByOwner(ownerUserId: string): Promise<VaultRow[]>;
|
|
497
|
+
/** Installation vaults (any session may attach them), ordered by `createdAt`, then id. */
|
|
498
|
+
installationVaults(): Promise<VaultRow[]>;
|
|
488
499
|
updateVaultMetadata(id: string, metadataJson: string | null): Promise<void>;
|
|
489
500
|
/** Deletes the vault and its credentials. */
|
|
490
501
|
deleteVault(id: string): Promise<void>;
|
|
@@ -510,6 +521,12 @@ export interface Tx {
|
|
|
510
521
|
getVaultIdempotency(id: string): Promise<VaultIdempotencyRow | undefined>;
|
|
511
522
|
/** Rejects when the id exists. */
|
|
512
523
|
insertVaultIdempotency(row: VaultIdempotencyRow): Promise<void>;
|
|
524
|
+
/** An MCP OAuth connect's start (F9 C2). Rejects on a duplicate state or an unknown vault. */
|
|
525
|
+
insertOAuthPending(row: OAuthPendingRow): Promise<void>;
|
|
526
|
+
/** Deletes and returns the pending connect of `stateHash`, expired or not: each is used once. */
|
|
527
|
+
takeOAuthPending(stateHash: string): Promise<OAuthPendingRow | undefined>;
|
|
528
|
+
/** Deletes the pending connects that expired before `now` (ISO); returns how many. */
|
|
529
|
+
deleteExpiredOAuthPending(now: string): Promise<number>;
|
|
513
530
|
/**
|
|
514
531
|
* Serializes signing key changes: held until the transaction ends. Take it before reading
|
|
515
532
|
* the keys a change depends on, so concurrent first uses or rotations do not collide.
|
|
@@ -527,28 +544,6 @@ export interface Tx {
|
|
|
527
544
|
*/
|
|
528
545
|
setSigningKeyState(id: string, from: SigningKeyRow["state"], to: SigningKeyRow["state"], at: string): Promise<boolean>;
|
|
529
546
|
countSigningKeys(): Promise<number>;
|
|
530
|
-
/** The subject's revocation epoch; 0 when it was never revoked. */
|
|
531
|
-
subjectEpoch(subject: string): Promise<number>;
|
|
532
|
-
/** The epochs of several subjects; subjects never revoked are absent. */
|
|
533
|
-
subjectEpochs(subjects: readonly string[]): Promise<Map<string, number>>;
|
|
534
|
-
/** Adds one to the subject's epoch and returns the new value. */
|
|
535
|
-
bumpSubjectEpoch(subject: string, at: string): Promise<number>;
|
|
536
|
-
/**
|
|
537
|
-
* The subject's turn bucket, created from `initial` when missing, locked until the
|
|
538
|
-
* transaction ends so concurrent commands of one subject serialize.
|
|
539
|
-
*/
|
|
540
|
-
lockSubjectUsage(initial: SubjectUsageRow): Promise<SubjectUsageRow>;
|
|
541
|
-
putSubjectUsage(row: SubjectUsageRow): Promise<void>;
|
|
542
|
-
/** How many sessions of `ownerUserId` are in one of `statuses`. */
|
|
543
|
-
countOwnerSessions(ownerUserId: string, statuses: readonly string[]): Promise<number>;
|
|
544
|
-
/** Rejects on a duplicate id, key or name. */
|
|
545
|
-
insertPublishableKey(row: PublishableKeyRow): Promise<void>;
|
|
546
|
-
publishableKeyByKey(key: string): Promise<PublishableKeyRow | undefined>;
|
|
547
|
-
publishableKey(id: string): Promise<PublishableKeyRow | undefined>;
|
|
548
|
-
/** All keys, revoked ones included, ordered by `createdAt`, then id. */
|
|
549
|
-
publishableKeys(): Promise<PublishableKeyRow[]>;
|
|
550
|
-
/** Returns false when the key does not exist. */
|
|
551
|
-
updatePublishableKey(id: string, patch: Partial<Pick<PublishableKeyRow, "originsJson" | "revokedAt">>): Promise<boolean>;
|
|
552
547
|
/** Appends a row, setting `duplicate` when one with the same `effectKey` exists; returns it. */
|
|
553
548
|
recordModelUsage(row: Omit<ModelUsageRow, "duplicate">): Promise<ModelUsageRow>;
|
|
554
549
|
modelUsageTotals(query: ModelUsageQuery): Promise<ModelUsageTotals>;
|
|
@@ -616,15 +611,14 @@ export interface Tx {
|
|
|
616
611
|
putSetting(key: string, value: string): Promise<void>;
|
|
617
612
|
/**
|
|
618
613
|
* Deletes Tenant state by scope, in this transaction:
|
|
619
|
-
* - `sessions`: sessions, commands, effects, actions, links,
|
|
620
|
-
* buckets, the artifacts of sessions (their blobs are the caller's to delete) and the
|
|
614
|
+
* - `sessions`: sessions, commands, effects, actions, links, the artifacts of sessions (their blobs are the caller's to delete) and the
|
|
621
615
|
* Tenant's record rows and log heads. The Tenant moves to the next basin
|
|
622
616
|
* generation and the current one is retired, so session ids it frees start again in an
|
|
623
617
|
* empty basin;
|
|
624
618
|
* - `sandboxes`: sandbox records, sandbox resources and their lifecycle streams;
|
|
625
619
|
* - `all`: both, plus definitions, Action endpoints, user vaults with their credentials,
|
|
626
|
-
* the model usage ledger, the model budgets and Tenant-wide artifacts. The host vault, principals, signing keys,
|
|
627
|
-
*
|
|
620
|
+
* the model usage ledger, the model budgets and Tenant-wide artifacts. The host vault, principals, signing keys,
|
|
621
|
+
* settings, audit and vault idempotency rows stay.
|
|
628
622
|
*/
|
|
629
623
|
reset(scope: ResetScope): Promise<void>;
|
|
630
624
|
}
|
|
@@ -13,8 +13,3 @@ export declare function signalActionResolved(streams: DurableStreams, basin: str
|
|
|
13
13
|
* Tenant open moves its readers to `generation` and ends the streams of deleted sessions.
|
|
14
14
|
*/
|
|
15
15
|
export declare function signalSessionsReset(streams: DurableStreams, basin: string, generation: number): Promise<void>;
|
|
16
|
-
/**
|
|
17
|
-
* Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
|
|
18
|
-
* streams opened with a token older than `epoch`.
|
|
19
|
-
*/
|
|
20
|
-
export declare function signalSubjectRevoked(streams: DurableStreams, basin: string, subject: string, epoch: number): Promise<void>;
|
package/dist/streams/control.js
CHANGED
|
@@ -28,11 +28,3 @@ export async function signalSessionsReset(streams, basin, generation) {
|
|
|
28
28
|
const signal = { type: "sessions.reset", generation };
|
|
29
29
|
await streams.append(basin, CONTROL_STREAM, [signal]);
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Appends a `subject.revoked` signal: every process with the Tenant open ends the subject's
|
|
33
|
-
* streams opened with a token older than `epoch`.
|
|
34
|
-
*/
|
|
35
|
-
export async function signalSubjectRevoked(streams, basin, subject, epoch) {
|
|
36
|
-
const signal = { type: "subject.revoked", subject, epoch };
|
|
37
|
-
await streams.append(basin, CONTROL_STREAM, [signal]);
|
|
38
|
-
}
|
package/dist/streams/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* | Stream | Contents | Written by | Read by |
|
|
8
8
|
* | --- | --- | --- | --- |
|
|
9
9
|
* | `sessions/<sessionId>` | every event of the session, in sequence | the stream relay, from the record | history and session SSE |
|
|
10
|
-
* | `tenant/control` | `session.cancel`, `sessions.reset` and `
|
|
10
|
+
* | `tenant/control` | `session.cancel`, `sessions.reset` and `action.resolved` signals | API nodes | every process with the Tenant open |
|
|
11
11
|
*
|
|
12
12
|
* **Basin generations.** A session id is unique within its Tenant's basin generation. A
|
|
13
13
|
* Tenant reset, the only path that frees ids, moves the Tenant to the next generation, so
|
|
@@ -138,15 +138,6 @@ export interface SessionsResetSignal {
|
|
|
138
138
|
type: "sessions.reset";
|
|
139
139
|
generation?: number;
|
|
140
140
|
}
|
|
141
|
-
/**
|
|
142
|
-
* A subject's tokens older than `epoch` were revoked: each process ends that subject's
|
|
143
|
-
* streams opened with them.
|
|
144
|
-
*/
|
|
145
|
-
export interface SubjectRevokedSignal {
|
|
146
|
-
type: "subject.revoked";
|
|
147
|
-
subject: string;
|
|
148
|
-
epoch: number;
|
|
149
|
-
}
|
|
150
141
|
/**
|
|
151
142
|
* An Action of `sessionId` has its outcome (F6.2): the process whose harness holds the
|
|
152
143
|
* session's run while the Action is pending passes it on (`effect.resolved`).
|
|
@@ -156,4 +147,4 @@ export interface ActionResolvedSignal {
|
|
|
156
147
|
sessionId: string;
|
|
157
148
|
actionId: string;
|
|
158
149
|
}
|
|
159
|
-
export type ControlSignal = SessionCancelSignal | SessionsResetSignal |
|
|
150
|
+
export type ControlSignal = SessionCancelSignal | SessionsResetSignal | ActionResolvedSignal;
|
package/dist/streams/types.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* | Stream | Contents | Written by | Read by |
|
|
8
8
|
* | --- | --- | --- | --- |
|
|
9
9
|
* | `sessions/<sessionId>` | every event of the session, in sequence | the stream relay, from the record | history and session SSE |
|
|
10
|
-
* | `tenant/control` | `session.cancel`, `sessions.reset` and `
|
|
10
|
+
* | `tenant/control` | `session.cancel`, `sessions.reset` and `action.resolved` signals | API nodes | every process with the Tenant open |
|
|
11
11
|
*
|
|
12
12
|
* **Basin generations.** A session id is unique within its Tenant's basin generation. A
|
|
13
13
|
* Tenant reset, the only path that frees ids, moves the Tenant to the next generation, so
|
package/dist/tenant/auth.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tenant request authentication and authorization: a bearer token resolves to an application
|
|
3
|
-
* principal, a
|
|
4
|
-
* An application principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`);
|
|
5
|
-
*
|
|
6
|
-
* allow, decided from the route alone.
|
|
3
|
+
* principal, a trusted issuer's token or a delivery token; anything else is the opaque 404
|
|
4
|
+
* (D5). An application principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`);
|
|
5
|
+
* an issuer token names its subject itself. `requireScopes` limits them to the routes their
|
|
6
|
+
* scopes allow, decided from the route alone.
|
|
7
|
+
*
|
|
8
|
+
* Browsers: a request with `Origin` may carry an issuer token (CORS is the operator's proxy's),
|
|
9
|
+
* never an application key or a delivery token, which are server secrets.
|
|
7
10
|
*/
|
|
8
11
|
import type { IncomingMessage } from "node:http";
|
|
9
12
|
import { type Action, type SubjectScope } from "@nylorun/core/contracts";
|
|
10
13
|
import type { AuthScope, SessionAccess, TenantContext } from "./context.js";
|
|
11
|
-
|
|
12
|
-
export declare function authenticate(ctx: TenantContext, request: IncomingMessage, client?: BrowserClient): Promise<AuthScope>;
|
|
14
|
+
export declare function authenticate(ctx: TenantContext, request: IncomingMessage): Promise<AuthScope>;
|
|
13
15
|
/**
|
|
14
16
|
* What a subject needs for a route: any one of the scopes, nothing grants it (`never`), or
|
|
15
17
|
* any caller may (`any`, public data such as the JWKS). Each route declares it
|
|
@@ -17,12 +19,12 @@ export declare function authenticate(ctx: TenantContext, request: IncomingMessag
|
|
|
17
19
|
*/
|
|
18
20
|
export type SubjectAccess = readonly SubjectScope[] | "never" | "any";
|
|
19
21
|
/**
|
|
20
|
-
* A subject
|
|
21
|
-
*
|
|
22
|
+
* A subject or a token caller reaches a route only with one of the scopes it declares:
|
|
23
|
+
* `403 scope_required`, decided from the route alone before anything is read.
|
|
22
24
|
*/
|
|
23
25
|
export declare function requireScopes(scope: AuthScope, access: SubjectAccess): void;
|
|
24
26
|
export declare function accessOf(scope: AuthScope): SessionAccess | undefined;
|
|
25
|
-
/** The subject whose sessions
|
|
27
|
+
/** The subject whose sessions the request is limited to, if it acts for one. */
|
|
26
28
|
export declare function ownerOf(scope: AuthScope): string | undefined;
|
|
27
29
|
/** The application principal behind the request, acting for a subject or not. */
|
|
28
30
|
export declare function requirePrincipal(scope: AuthScope): string;
|
|
@@ -31,4 +33,6 @@ export declare function requirePrincipal(scope: AuthScope): string;
|
|
|
31
33
|
* is still the Action's is checked by each callback.
|
|
32
34
|
*/
|
|
33
35
|
export declare function scoped(scope: AuthScope, action: Action): void;
|
|
36
|
+
/** True when a token caller may reach `agentId` (its issuer's allowlist). Other callers always may. */
|
|
37
|
+
export declare function mayUseAgent(scope: AuthScope, agentId: string): boolean;
|
|
34
38
|
export declare function requireApplication(scope: AuthScope): string;
|