@opengeni/core 3.1.2 → 3.2.2
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/dist/dependencies.d.ts +3 -0
- package/dist/domain/vaults/external-provider-connections.d.ts +126 -0
- package/dist/domain/vaults/index.d.ts +2 -1
- package/dist/domain/vaults/mapping.d.ts +18 -2
- package/dist/domain/vaults/provider-registry.d.ts +4 -1
- package/dist/domain/vaults/provider-sync.d.ts +135 -21
- package/dist/domain/vaults/provider-writes.d.ts +102 -12
- package/dist/domain/vaults/remote-provider-runtime.d.ts +7 -0
- package/dist/domain/vaults/secret-resolver.d.ts +29 -1
- package/dist/goal-admission.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1432 -375
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/src/dependencies.ts +3 -0
- package/src/domain/realtime-voice-billing.ts +4 -1
- package/src/domain/vaults/external-provider-connections.ts +395 -0
- package/src/domain/vaults/index.ts +8 -0
- package/src/domain/vaults/mapping.ts +95 -1
- package/src/domain/vaults/provider-registry.ts +29 -0
- package/src/domain/vaults/provider-sync.ts +492 -136
- package/src/domain/vaults/provider-writes.ts +415 -71
- package/src/domain/vaults/remote-provider-runtime.ts +129 -0
- package/src/domain/vaults/secret-resolver.ts +203 -33
- package/src/domain/vaults/vault-service.ts +226 -34
- package/src/domain/voice-input-billing.ts +5 -1
- package/src/index.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -1011,10 +1011,10 @@ import { requireSession as requireSession3, requireWorkspace as requireWorkspace
|
|
|
1011
1011
|
// src/domain/capabilities.ts
|
|
1012
1012
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1013
1013
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1014
|
-
import { isDeepStrictEqual } from "util";
|
|
1014
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
1015
1015
|
import { withLockedCapabilityInstallation } from "@opengeni/db/capability-reconciliation";
|
|
1016
1016
|
import {
|
|
1017
|
-
environmentsEncryptionKeyBytes,
|
|
1017
|
+
environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2,
|
|
1018
1018
|
findIntegrationsOauthClient,
|
|
1019
1019
|
parseIntegrationsOauthClientsJson
|
|
1020
1020
|
} from "@opengeni/config";
|
|
@@ -1041,7 +1041,7 @@ import {
|
|
|
1041
1041
|
encryptVariableSetValue,
|
|
1042
1042
|
getCapabilityCatalogItem,
|
|
1043
1043
|
getCapabilityInstallation,
|
|
1044
|
-
getConnectionMetadata as
|
|
1044
|
+
getConnectionMetadata as getConnectionMetadata3,
|
|
1045
1045
|
readCodexCutoverDisposition,
|
|
1046
1046
|
resolveSubscriptionCoreCodexAppsDesignation,
|
|
1047
1047
|
rlsContextForWorkspace,
|
|
@@ -1099,14 +1099,263 @@ function preferredFikenConnection(connections) {
|
|
|
1099
1099
|
|
|
1100
1100
|
// src/domain/vaults/provider-registry.ts
|
|
1101
1101
|
import {
|
|
1102
|
+
getConnectionMetadata as getConnectionMetadata2,
|
|
1103
|
+
getProviderConnectionHomeWorkspace as getProviderConnectionHomeWorkspace2,
|
|
1104
|
+
loadConnectionCredentialForBroker as loadConnectionCredentialForBroker2
|
|
1105
|
+
} from "@opengeni/db";
|
|
1106
|
+
import {
|
|
1107
|
+
createVaultProviderRegistry,
|
|
1108
|
+
VaultProviderError as VaultProviderError2
|
|
1109
|
+
} from "@opengeni/vaults";
|
|
1110
|
+
|
|
1111
|
+
// src/domain/vaults/external-provider-connections.ts
|
|
1112
|
+
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
1113
|
+
import { isDeepStrictEqual } from "util";
|
|
1114
|
+
import {
|
|
1115
|
+
ExternalVaultProviderKind,
|
|
1116
|
+
VaultProviderCredentials,
|
|
1117
|
+
VaultProviderOperations
|
|
1118
|
+
} from "@opengeni/contracts";
|
|
1119
|
+
import {
|
|
1120
|
+
encryptEnvironmentValue,
|
|
1102
1121
|
getConnectionMetadata,
|
|
1103
1122
|
getProviderConnectionHomeWorkspace,
|
|
1104
|
-
loadConnectionCredentialForBroker
|
|
1123
|
+
loadConnectionCredentialForBroker,
|
|
1124
|
+
updateConnection
|
|
1105
1125
|
} from "@opengeni/db";
|
|
1106
1126
|
import {
|
|
1107
|
-
|
|
1127
|
+
providerOperations,
|
|
1108
1128
|
VaultProviderError
|
|
1109
1129
|
} from "@opengeni/vaults";
|
|
1130
|
+
import { z } from "zod";
|
|
1131
|
+
var EXTERNAL_VAULT_CREDENTIAL_ROLE = "external_vault_provider";
|
|
1132
|
+
var ExternalVaultConnectionDetails = z.object({
|
|
1133
|
+
version: z.literal(1),
|
|
1134
|
+
provider: ExternalVaultProviderKind,
|
|
1135
|
+
ownership: z.enum(["user", "workspace", "organization"]),
|
|
1136
|
+
accountId: z.string().min(1).max(300).nullable(),
|
|
1137
|
+
accountName: z.string().max(300).nullable(),
|
|
1138
|
+
label: z.string().max(120).nullable(),
|
|
1139
|
+
verifiedAt: z.string().datetime(),
|
|
1140
|
+
operations: VaultProviderOperations,
|
|
1141
|
+
verificationStatus: z.enum(["ready", "pending"]).optional()
|
|
1142
|
+
}).strict();
|
|
1143
|
+
function externalVaultProviderDomain(provider) {
|
|
1144
|
+
return `vault:${provider}`;
|
|
1145
|
+
}
|
|
1146
|
+
function externalVaultConnectionDetails(metadata) {
|
|
1147
|
+
if (metadata?.credentialRole !== EXTERNAL_VAULT_CREDENTIAL_ROLE) return null;
|
|
1148
|
+
const result = ExternalVaultConnectionDetails.safeParse(metadata.vaultProvider);
|
|
1149
|
+
return result.success ? result.data : null;
|
|
1150
|
+
}
|
|
1151
|
+
function isExternalVaultConnection(connection) {
|
|
1152
|
+
const details = externalVaultConnectionDetails(connection.metadata);
|
|
1153
|
+
return details !== null && connection.kind === "api_key" && connection.providerDomain === externalVaultProviderDomain(details.provider) && details.ownership === "user" === (connection.subjectId !== null);
|
|
1154
|
+
}
|
|
1155
|
+
var StoredVaultProviderCredentials = z.union([
|
|
1156
|
+
VaultProviderCredentials.options[0],
|
|
1157
|
+
VaultProviderCredentials.options[1],
|
|
1158
|
+
VaultProviderCredentials.options[2],
|
|
1159
|
+
z.object({
|
|
1160
|
+
provider: z.literal("keeper"),
|
|
1161
|
+
configuration: z.record(z.string().max(100), z.string().max(65536)).refine((value) => Object.keys(value).length <= 32)
|
|
1162
|
+
}).strict()
|
|
1163
|
+
]);
|
|
1164
|
+
var PendingVaultCredentialEnvelope = z.object({
|
|
1165
|
+
vaultCredentialEnvelope: z.literal(1),
|
|
1166
|
+
current: StoredVaultProviderCredentials.nullable(),
|
|
1167
|
+
pending: StoredVaultProviderCredentials
|
|
1168
|
+
}).strict();
|
|
1169
|
+
function pendingVaultCredentialBundle(current, pending) {
|
|
1170
|
+
return { vaultCredentialEnvelope: 1, current, pending };
|
|
1171
|
+
}
|
|
1172
|
+
function externalVaultConnectionMetadata(details) {
|
|
1173
|
+
return {
|
|
1174
|
+
credentialRole: EXTERNAL_VAULT_CREDENTIAL_ROLE,
|
|
1175
|
+
vaultProvider: ExternalVaultConnectionDetails.parse(details)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function unavailable() {
|
|
1179
|
+
return new VaultProviderError(
|
|
1180
|
+
"provider_auth_invalid",
|
|
1181
|
+
"This vault connection was removed or is not available to you. Reconnect it in Vaults."
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
async function loadExternalVaultConnection(db, settings, input) {
|
|
1185
|
+
let connection = await getConnectionMetadata(
|
|
1186
|
+
db,
|
|
1187
|
+
input.workspaceId,
|
|
1188
|
+
input.connectionId,
|
|
1189
|
+
input.subjectId
|
|
1190
|
+
);
|
|
1191
|
+
if (!connection) {
|
|
1192
|
+
const home = await getProviderConnectionHomeWorkspace(
|
|
1193
|
+
db,
|
|
1194
|
+
input.workspaceId,
|
|
1195
|
+
input.connectionId,
|
|
1196
|
+
input.subjectId
|
|
1197
|
+
);
|
|
1198
|
+
if (home && home !== input.workspaceId)
|
|
1199
|
+
connection = await getConnectionMetadata(db, home, input.connectionId, input.subjectId);
|
|
1200
|
+
}
|
|
1201
|
+
if (!connection || !isExternalVaultConnection(connection)) throw unavailable();
|
|
1202
|
+
const details = externalVaultConnectionDetails(connection.metadata);
|
|
1203
|
+
if (details.provider !== input.provider) throw unavailable();
|
|
1204
|
+
if (connection.status !== "active" && !(input.allowPendingVerification && (connection.status === "needs_reauth" || connection.status === "error")))
|
|
1205
|
+
throw new VaultProviderError(
|
|
1206
|
+
"provider_auth_expired",
|
|
1207
|
+
"This vault connection needs to be reconnected."
|
|
1208
|
+
);
|
|
1209
|
+
if (connection.subjectId !== null && connection.subjectId !== input.subjectId)
|
|
1210
|
+
throw unavailable();
|
|
1211
|
+
const loaded = await loadConnectionCredentialForBroker(db, settings, {
|
|
1212
|
+
workspaceId: connection.workspaceId,
|
|
1213
|
+
connectionId: connection.id,
|
|
1214
|
+
providerDomain: externalVaultProviderDomain(input.provider),
|
|
1215
|
+
kind: "api_key",
|
|
1216
|
+
...connection.subjectId !== null ? { allowSubjectOwned: true, subjectId: input.subjectId } : {}
|
|
1217
|
+
});
|
|
1218
|
+
if (!loaded || loaded.status !== connection.status || loaded.version !== connection.version || loaded.accountId !== connection.accountId || loaded.subjectId !== connection.subjectId || loaded.providerDomain !== connection.providerDomain || loaded.kind !== "api_key" || externalVaultConnectionDetails(loaded.metadata)?.provider !== input.provider)
|
|
1219
|
+
throw unavailable();
|
|
1220
|
+
const envelope = PendingVaultCredentialEnvelope.safeParse(loaded.credential);
|
|
1221
|
+
const raw = envelope.success ? input.allowPendingVerification ? envelope.data.pending : envelope.data.current : loaded.credential;
|
|
1222
|
+
const parsed = StoredVaultProviderCredentials.safeParse(raw);
|
|
1223
|
+
if (!parsed.success || parsed.data.provider !== input.provider) throw unavailable();
|
|
1224
|
+
if (envelope.success && (envelope.data.pending.provider !== input.provider || envelope.data.current !== null && envelope.data.current.provider !== input.provider))
|
|
1225
|
+
throw unavailable();
|
|
1226
|
+
return {
|
|
1227
|
+
connection,
|
|
1228
|
+
details,
|
|
1229
|
+
credentials: parsed.data,
|
|
1230
|
+
currentCredentials: envelope.success ? envelope.data.current : parsed.data,
|
|
1231
|
+
pendingCredentials: envelope.success ? envelope.data.pending : null
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function bindVaultProviderAuthority(provider, input) {
|
|
1235
|
+
async function context(ctx) {
|
|
1236
|
+
ctx.signal?.throwIfAborted();
|
|
1237
|
+
await input.revalidate();
|
|
1238
|
+
ctx.signal?.throwIfAborted();
|
|
1239
|
+
return { ...ctx, connectionId: input.connectionId, authority: { ...input.authority } };
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
id: provider.id,
|
|
1243
|
+
capabilities: () => ({
|
|
1244
|
+
...provider.capabilities(),
|
|
1245
|
+
operations: providerOperations(provider.capabilities(), { operations: input.operations })
|
|
1246
|
+
}),
|
|
1247
|
+
validate: async (ctx) => provider.validate(await context(ctx)),
|
|
1248
|
+
listVaults: async (ctx) => provider.listVaults(await context(ctx)),
|
|
1249
|
+
listItems: async (ctx, vaultId) => provider.listItems(await context(ctx), vaultId),
|
|
1250
|
+
readFields: async (ctx, vaultId, itemId, fields) => provider.readFields(await context(ctx), vaultId, itemId, fields),
|
|
1251
|
+
writeItem: async (ctx, vaultId, item) => provider.writeItem(await context(ctx), vaultId, item),
|
|
1252
|
+
archiveItem: async (ctx, vaultId, itemId) => provider.archiveItem(await context(ctx), vaultId, itemId),
|
|
1253
|
+
...provider.listVaultPage ? { listVaultPage: async (ctx, page) => provider.listVaultPage(await context(ctx), page) } : {},
|
|
1254
|
+
...provider.listItemPage ? {
|
|
1255
|
+
listItemPage: async (ctx, vaultId, page) => provider.listItemPage(await context(ctx), vaultId, page)
|
|
1256
|
+
} : {},
|
|
1257
|
+
...provider.createVault ? { createVault: async (ctx, value) => provider.createVault(await context(ctx), value) } : {}
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
async function externalVaultProviderForConnection(input) {
|
|
1261
|
+
const loaded = await loadExternalVaultConnection(input.db, input.settings, input);
|
|
1262
|
+
let connection = loaded.connection;
|
|
1263
|
+
const prepared = await input.factory({
|
|
1264
|
+
credentials: loaded.credentials,
|
|
1265
|
+
connectionId: connection.id,
|
|
1266
|
+
signal: AbortSignal.timeout(3e4),
|
|
1267
|
+
operations: loaded.details.operations,
|
|
1268
|
+
persistCredentials: async (next) => {
|
|
1269
|
+
const parsed = StoredVaultProviderCredentials.safeParse(next);
|
|
1270
|
+
if (!parsed.success || parsed.data.provider !== loaded.details.provider) throw unavailable();
|
|
1271
|
+
const credentials = parsed.data;
|
|
1272
|
+
const key = environmentsEncryptionKeyBytes(input.settings);
|
|
1273
|
+
if (!key)
|
|
1274
|
+
throw new VaultProviderError(
|
|
1275
|
+
"provider_unavailable",
|
|
1276
|
+
"Vault credential encryption is unavailable."
|
|
1277
|
+
);
|
|
1278
|
+
const updated = await updateConnection(input.db, {
|
|
1279
|
+
workspaceId: connection.workspaceId,
|
|
1280
|
+
connectionId: connection.id,
|
|
1281
|
+
visibleToSubjectId: input.subjectId,
|
|
1282
|
+
expectedVersion: connection.version,
|
|
1283
|
+
credentialEncrypted: encryptEnvironmentValue(
|
|
1284
|
+
key,
|
|
1285
|
+
JSON.stringify(
|
|
1286
|
+
loaded.pendingCredentials ? pendingVaultCredentialBundle(credentials, loaded.pendingCredentials) : credentials
|
|
1287
|
+
)
|
|
1288
|
+
)
|
|
1289
|
+
});
|
|
1290
|
+
if (updated) {
|
|
1291
|
+
connection = updated;
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const live = await loadExternalVaultConnection(input.db, input.settings, input);
|
|
1295
|
+
if (isDeepStrictEqual(
|
|
1296
|
+
rotationConnectionIdentity(live.connection),
|
|
1297
|
+
rotationConnectionIdentity(connection)
|
|
1298
|
+
) && isDeepStrictEqual(live.currentCredentials, credentials) && isDeepStrictEqual(live.pendingCredentials, loaded.pendingCredentials)) {
|
|
1299
|
+
connection = live.connection;
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
throw new VaultProviderError(
|
|
1303
|
+
"provider_conflict",
|
|
1304
|
+
"This vault connection changed while its credentials were renewed."
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
if (prepared.provider.id !== input.provider) throw unavailable();
|
|
1309
|
+
return bindVaultProviderAuthority(prepared.provider, {
|
|
1310
|
+
connectionId: connection.id,
|
|
1311
|
+
authority: {
|
|
1312
|
+
accountId: connection.accountId,
|
|
1313
|
+
workspaceId: input.workspaceId,
|
|
1314
|
+
connectionWorkspaceId: connection.workspaceId,
|
|
1315
|
+
subjectId: input.subjectId,
|
|
1316
|
+
...input.executionContext
|
|
1317
|
+
},
|
|
1318
|
+
operations: loaded.details.operations,
|
|
1319
|
+
revalidate: async () => {
|
|
1320
|
+
if (connection.workspaceId !== input.workspaceId) {
|
|
1321
|
+
const home = await getProviderConnectionHomeWorkspace(
|
|
1322
|
+
input.db,
|
|
1323
|
+
input.workspaceId,
|
|
1324
|
+
connection.id,
|
|
1325
|
+
input.subjectId
|
|
1326
|
+
);
|
|
1327
|
+
if (home !== connection.workspaceId) throw unavailable();
|
|
1328
|
+
}
|
|
1329
|
+
const live = await getConnectionMetadata(
|
|
1330
|
+
input.db,
|
|
1331
|
+
connection.workspaceId,
|
|
1332
|
+
connection.id,
|
|
1333
|
+
input.subjectId
|
|
1334
|
+
);
|
|
1335
|
+
if (!live || !isExternalVaultConnection(live) || live.status !== "active" || live.accountId !== connection.accountId || live.subjectId !== connection.subjectId || live.providerDomain !== connection.providerDomain)
|
|
1336
|
+
throw unavailable();
|
|
1337
|
+
if (live.version !== connection.version)
|
|
1338
|
+
throw new VaultProviderError(
|
|
1339
|
+
"provider_conflict",
|
|
1340
|
+
"This vault connection changed. Reload it before starting another operation."
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
function rotationConnectionIdentity(connection) {
|
|
1346
|
+
const {
|
|
1347
|
+
version: _version,
|
|
1348
|
+
connectionAuthorityGeneration: _authorityGeneration,
|
|
1349
|
+
updatedAt: _updatedAt,
|
|
1350
|
+
updatedBySubjectId: _updatedBy,
|
|
1351
|
+
lastUsedAt: _lastUsedAt,
|
|
1352
|
+
lastRefreshAt: _lastRefreshAt,
|
|
1353
|
+
...identity
|
|
1354
|
+
} = connection;
|
|
1355
|
+
return identity;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/domain/vaults/provider-registry.ts
|
|
1110
1359
|
function vaultErrorCodeForProvider(code, operation) {
|
|
1111
1360
|
if (code === "provider_not_found") {
|
|
1112
1361
|
return operation === "read" ? "vault_field_not_found" : "vault_not_found";
|
|
@@ -1143,19 +1392,19 @@ function onePasswordCredentialBundle(token) {
|
|
|
1143
1392
|
return { token };
|
|
1144
1393
|
}
|
|
1145
1394
|
async function loadOnePasswordConnectionToken(db, settings, input) {
|
|
1146
|
-
const
|
|
1395
|
+
const unavailable2 = () => new VaultProviderError2(
|
|
1147
1396
|
"provider_auth_invalid",
|
|
1148
1397
|
"The 1Password connection for this vault was removed or is not available to you; reconnect 1Password"
|
|
1149
1398
|
);
|
|
1150
1399
|
let homeWorkspaceId = input.workspaceId;
|
|
1151
|
-
let connection = await
|
|
1400
|
+
let connection = await getConnectionMetadata2(
|
|
1152
1401
|
db,
|
|
1153
1402
|
input.workspaceId,
|
|
1154
1403
|
input.connectionId,
|
|
1155
1404
|
input.subjectId
|
|
1156
1405
|
);
|
|
1157
1406
|
if (!connection) {
|
|
1158
|
-
const home = await
|
|
1407
|
+
const home = await getProviderConnectionHomeWorkspace2(
|
|
1159
1408
|
db,
|
|
1160
1409
|
input.workspaceId,
|
|
1161
1410
|
input.connectionId,
|
|
@@ -1163,18 +1412,18 @@ async function loadOnePasswordConnectionToken(db, settings, input) {
|
|
|
1163
1412
|
);
|
|
1164
1413
|
if (home && home !== input.workspaceId) {
|
|
1165
1414
|
homeWorkspaceId = home;
|
|
1166
|
-
connection = await
|
|
1415
|
+
connection = await getConnectionMetadata2(db, home, input.connectionId, input.subjectId);
|
|
1167
1416
|
}
|
|
1168
1417
|
}
|
|
1169
|
-
if (!connection || !isOnePasswordConnection(connection)) throw
|
|
1418
|
+
if (!connection || !isOnePasswordConnection(connection)) throw unavailable2();
|
|
1170
1419
|
if (connection.status !== "active") {
|
|
1171
|
-
throw new
|
|
1420
|
+
throw new VaultProviderError2(
|
|
1172
1421
|
"provider_auth_expired",
|
|
1173
1422
|
"The 1Password connection needs a new service account token"
|
|
1174
1423
|
);
|
|
1175
1424
|
}
|
|
1176
1425
|
const personal = connection.subjectId !== null;
|
|
1177
|
-
const credential = await
|
|
1426
|
+
const credential = await loadConnectionCredentialForBroker2(db, settings, {
|
|
1178
1427
|
workspaceId: homeWorkspaceId,
|
|
1179
1428
|
connectionId: connection.id,
|
|
1180
1429
|
providerDomain: ONEPASSWORD_PROVIDER_DOMAIN,
|
|
@@ -1182,7 +1431,7 @@ async function loadOnePasswordConnectionToken(db, settings, input) {
|
|
|
1182
1431
|
...personal ? { allowSubjectOwned: true, subjectId: connection.subjectId } : {}
|
|
1183
1432
|
});
|
|
1184
1433
|
const token = credential?.credential?.token;
|
|
1185
|
-
if (!credential || typeof token !== "string" || !token) throw
|
|
1434
|
+
if (!credential || typeof token !== "string" || !token) throw unavailable2();
|
|
1186
1435
|
return { token, metadata: onePasswordConnectionMetadata(connection.metadata) };
|
|
1187
1436
|
}
|
|
1188
1437
|
function createWorkspaceVaultProviderRegistry(input) {
|
|
@@ -1192,7 +1441,7 @@ function createWorkspaceVaultProviderRegistry(input) {
|
|
|
1192
1441
|
...factory ? {
|
|
1193
1442
|
async onepassword(connectionId) {
|
|
1194
1443
|
if (!connectionId) {
|
|
1195
|
-
throw new
|
|
1444
|
+
throw new VaultProviderError2(
|
|
1196
1445
|
"provider_unavailable",
|
|
1197
1446
|
"This 1Password vault has no connection"
|
|
1198
1447
|
);
|
|
@@ -1209,7 +1458,29 @@ function createWorkspaceVaultProviderRegistry(input) {
|
|
|
1209
1458
|
});
|
|
1210
1459
|
}
|
|
1211
1460
|
} : {},
|
|
1212
|
-
...host ? { host: async () => host } : {}
|
|
1461
|
+
...host ? { host: async () => host } : {},
|
|
1462
|
+
...Object.fromEntries(
|
|
1463
|
+
Object.entries(input.externalVaultProviders ?? {}).map(([provider, providerFactory]) => [
|
|
1464
|
+
provider,
|
|
1465
|
+
async (connectionId) => {
|
|
1466
|
+
if (!connectionId || !providerFactory)
|
|
1467
|
+
throw new VaultProviderError2(
|
|
1468
|
+
"provider_unavailable",
|
|
1469
|
+
"This vault has no available connection."
|
|
1470
|
+
);
|
|
1471
|
+
return externalVaultProviderForConnection({
|
|
1472
|
+
db: input.db,
|
|
1473
|
+
settings: input.settings,
|
|
1474
|
+
workspaceId: input.workspaceId,
|
|
1475
|
+
subjectId: input.subjectId,
|
|
1476
|
+
connectionId,
|
|
1477
|
+
provider,
|
|
1478
|
+
factory: providerFactory,
|
|
1479
|
+
...input.executionContext ? { executionContext: input.executionContext } : {}
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
])
|
|
1483
|
+
)
|
|
1213
1484
|
});
|
|
1214
1485
|
}
|
|
1215
1486
|
|
|
@@ -1440,7 +1711,7 @@ async function prepareCapabilityEnable(input) {
|
|
|
1440
1711
|
item,
|
|
1441
1712
|
installationConfig
|
|
1442
1713
|
);
|
|
1443
|
-
if (!current || !
|
|
1714
|
+
if (!current || !isDeepStrictEqual2(current, unchanged)) {
|
|
1444
1715
|
throw new HTTPException5(409, {
|
|
1445
1716
|
message: "Capability installation changed; reload before reconciling"
|
|
1446
1717
|
});
|
|
@@ -1494,7 +1765,7 @@ async function validateVerifiedMcpConnection(input, item) {
|
|
|
1494
1765
|
const ref = input.payload.connectionRef;
|
|
1495
1766
|
if (!proof || !ref || item.endpointUrl !== proof.endpointUrl || ref.resource !== proof.endpointUrl)
|
|
1496
1767
|
throw new HTTPException5(409, { message: "MCP verification no longer matches the connection" });
|
|
1497
|
-
const connection = await
|
|
1768
|
+
const connection = await getConnectionMetadata3(
|
|
1498
1769
|
input.db,
|
|
1499
1770
|
input.workspaceId,
|
|
1500
1771
|
proof.id,
|
|
@@ -1521,7 +1792,7 @@ async function unchangedMcpInstallation(input, item, requestedConfig) {
|
|
|
1521
1792
|
const config = { ...requestedConfig, ...connectionRef ? { connectionRef } : {} };
|
|
1522
1793
|
const { mcpConnectivity: _storedConnectivity, ...storedMetadata } = existing.metadata;
|
|
1523
1794
|
const { mcpConnectivity: _requestedConnectivity, ...requestedMetadata } = input.payload.metadata;
|
|
1524
|
-
if (!
|
|
1795
|
+
if (!isDeepStrictEqual2(config, storedConfig) || !isDeepStrictEqual2(requestedMetadata, storedMetadata))
|
|
1525
1796
|
return null;
|
|
1526
1797
|
const provided = normalizedMcpCredentialHeaders(input.payload.headers);
|
|
1527
1798
|
if (provided) {
|
|
@@ -1529,7 +1800,7 @@ async function unchangedMcpInstallation(input, item, requestedConfig) {
|
|
|
1529
1800
|
{ ...input, payload: { ...input.payload, headers: {} } },
|
|
1530
1801
|
item
|
|
1531
1802
|
);
|
|
1532
|
-
if (!
|
|
1803
|
+
if (!isDeepStrictEqual2(provided, stored2)) return null;
|
|
1533
1804
|
}
|
|
1534
1805
|
return existing;
|
|
1535
1806
|
}
|
|
@@ -1630,7 +1901,7 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
1630
1901
|
assertNativeMcpConnectionRef(normalized);
|
|
1631
1902
|
return normalized;
|
|
1632
1903
|
}
|
|
1633
|
-
let connection = normalized.connectionId ? await
|
|
1904
|
+
let connection = normalized.connectionId ? await getConnectionMetadata3(
|
|
1634
1905
|
input.db,
|
|
1635
1906
|
input.workspaceId,
|
|
1636
1907
|
normalized.connectionId,
|
|
@@ -1712,7 +1983,7 @@ function requiredCapabilityHeaders(metadata) {
|
|
|
1712
1983
|
return value.filter((name) => typeof name === "string" && name.trim().length > 0).map((name) => name.trim());
|
|
1713
1984
|
}
|
|
1714
1985
|
function requireCapabilityHeaderEncryption(settings) {
|
|
1715
|
-
const key =
|
|
1986
|
+
const key = environmentsEncryptionKeyBytes2(settings);
|
|
1716
1987
|
if (!key) {
|
|
1717
1988
|
throw new HTTPException5(503, {
|
|
1718
1989
|
message: "MCP credential headers require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
@@ -2001,7 +2272,7 @@ function settingsWithMcpCapabilityServers(settings, enabled) {
|
|
|
2001
2272
|
if (enabled.length === 0) {
|
|
2002
2273
|
return settings;
|
|
2003
2274
|
}
|
|
2004
|
-
const encryptionKey =
|
|
2275
|
+
const encryptionKey = environmentsEncryptionKeyBytes2(settings);
|
|
2005
2276
|
const existingIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
2006
2277
|
const dynamicServers = enabled.filter((server) => !existingIds.has(server.id)).flatMap((server) => {
|
|
2007
2278
|
const headers = decryptedCapabilityHeaders(server, encryptionKey);
|
|
@@ -3040,7 +3311,7 @@ import {
|
|
|
3040
3311
|
getSessionTurnPersonalConnectionDelegations,
|
|
3041
3312
|
getSessionTurnMcpAccountBindings,
|
|
3042
3313
|
getSessionTurnConnectionAccountIntent,
|
|
3043
|
-
getConnectionMetadata as
|
|
3314
|
+
getConnectionMetadata as getConnectionMetadata4,
|
|
3044
3315
|
getSocialConnection,
|
|
3045
3316
|
listConnectionsMetadata as listConnectionsMetadata2,
|
|
3046
3317
|
listSocialConnections as listSocialConnections2,
|
|
@@ -3478,7 +3749,7 @@ async function listOwnConnectionMetadata(db, input) {
|
|
|
3478
3749
|
);
|
|
3479
3750
|
const connections = [];
|
|
3480
3751
|
for (const account of accounts) {
|
|
3481
|
-
const connection = await
|
|
3752
|
+
const connection = await getConnectionMetadata4(
|
|
3482
3753
|
db,
|
|
3483
3754
|
account.originWorkspaceId,
|
|
3484
3755
|
account.connectionId,
|
|
@@ -3941,7 +4212,7 @@ async function visibleMcpAccountConnections(db, input) {
|
|
|
3941
4212
|
async function visibleMcpAccountConnectionsByIds(db, input) {
|
|
3942
4213
|
const workspace = [];
|
|
3943
4214
|
for (const connectionId of new Set(input.workspaceConnectionIds)) {
|
|
3944
|
-
const connection = await
|
|
4215
|
+
const connection = await getConnectionMetadata4(db, input.workspaceId, connectionId, null);
|
|
3945
4216
|
if (connection && connection.subjectId === null) workspace.push(connection);
|
|
3946
4217
|
}
|
|
3947
4218
|
const personalIds = new Set(input.personalConnectionIds);
|
|
@@ -4163,7 +4434,7 @@ import {
|
|
|
4163
4434
|
resolveNamedManagedPersonalWorkspaceGrant
|
|
4164
4435
|
} from "@opengeni/db";
|
|
4165
4436
|
import { HTTPException as HTTPException8 } from "hono/http-exception";
|
|
4166
|
-
import { z } from "zod";
|
|
4437
|
+
import { z as z2 } from "zod";
|
|
4167
4438
|
|
|
4168
4439
|
// src/access/service-initiator.ts
|
|
4169
4440
|
import { ServiceTurnInitiatorContext } from "@opengeni/contracts";
|
|
@@ -4273,19 +4544,19 @@ function requireExplicitPermissionDelegation(grant, requested) {
|
|
|
4273
4544
|
}
|
|
4274
4545
|
}
|
|
4275
4546
|
var canonicalLocalHumanContexts = /* @__PURE__ */ new WeakSet();
|
|
4276
|
-
var delegatedHumanAuthorizationSchema =
|
|
4277
|
-
organizationId:
|
|
4278
|
-
subjectId:
|
|
4279
|
-
permissions:
|
|
4547
|
+
var delegatedHumanAuthorizationSchema = z2.object({
|
|
4548
|
+
organizationId: z2.string().uuid(),
|
|
4549
|
+
subjectId: z2.string().max(1024).regex(/^user:[^\s\u0000-\u001f\u007f]+$/),
|
|
4550
|
+
permissions: z2.array(Permission),
|
|
4280
4551
|
workspaceScope: OrganizationWorkspaceScope
|
|
4281
4552
|
});
|
|
4282
4553
|
var delegatedHumanRequests = /* @__PURE__ */ new WeakMap();
|
|
4283
4554
|
var delegatedHumanContexts = /* @__PURE__ */ new WeakMap();
|
|
4284
|
-
var delegatedHumanProfileSchema =
|
|
4285
|
-
id:
|
|
4286
|
-
name:
|
|
4287
|
-
email:
|
|
4288
|
-
emailVerified:
|
|
4555
|
+
var delegatedHumanProfileSchema = z2.object({
|
|
4556
|
+
id: z2.string(),
|
|
4557
|
+
name: z2.string().nullable(),
|
|
4558
|
+
email: z2.string(),
|
|
4559
|
+
emailVerified: z2.boolean().optional()
|
|
4289
4560
|
});
|
|
4290
4561
|
var delegatedHumanProfiles = /* @__PURE__ */ new WeakMap();
|
|
4291
4562
|
var verifiedDelegatedHumanAuthorizations = /* @__PURE__ */ new WeakMap();
|
|
@@ -6494,7 +6765,7 @@ function sessionUsageLimitRefusalFromError(error) {
|
|
|
6494
6765
|
}
|
|
6495
6766
|
|
|
6496
6767
|
// src/sandbox/fleet.ts
|
|
6497
|
-
import { environmentsEncryptionKeyBytes as
|
|
6768
|
+
import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
|
|
6498
6769
|
import {
|
|
6499
6770
|
agentCapabilityEnabled
|
|
6500
6771
|
} from "@opengeni/contracts";
|
|
@@ -7026,6 +7297,7 @@ import {
|
|
|
7026
7297
|
isValidTotpInput,
|
|
7027
7298
|
parseTotpInput,
|
|
7028
7299
|
referenceKey,
|
|
7300
|
+
secretNameKey,
|
|
7029
7301
|
SecretReferenceError,
|
|
7030
7302
|
VAULT_LIMITS as VAULT_LIMITS2
|
|
7031
7303
|
} from "@opengeni/contracts";
|
|
@@ -7049,14 +7321,42 @@ import {
|
|
|
7049
7321
|
} from "@opengeni/contracts";
|
|
7050
7322
|
import {
|
|
7051
7323
|
getProviderVaultSyncTarget,
|
|
7052
|
-
|
|
7053
|
-
|
|
7324
|
+
isVaultError,
|
|
7325
|
+
setVaultSyncStatus
|
|
7054
7326
|
} from "@opengeni/db";
|
|
7055
7327
|
import {
|
|
7056
7328
|
isVaultProviderError,
|
|
7057
|
-
|
|
7329
|
+
providerOperations as providerOperations2,
|
|
7330
|
+
readProviderItemInventory,
|
|
7331
|
+
readProviderVaultInventory,
|
|
7332
|
+
VaultProviderError as VaultProviderError3
|
|
7058
7333
|
} from "@opengeni/vaults";
|
|
7059
7334
|
var PROVIDER_RATE_LIMIT_DEFAULT_BACKOFF_MS = 15 * 6e4;
|
|
7335
|
+
function incompleteInventory() {
|
|
7336
|
+
return new VaultProviderError3(
|
|
7337
|
+
"provider_inventory_incomplete",
|
|
7338
|
+
`This vault has more than ${VAULT_LIMITS.itemsPerVault} items, more than Opengeni mirrors. The previous inventory was kept.`
|
|
7339
|
+
);
|
|
7340
|
+
}
|
|
7341
|
+
function oversizedItem() {
|
|
7342
|
+
return new VaultProviderError3(
|
|
7343
|
+
"provider_inventory_incomplete",
|
|
7344
|
+
`An item in this vault has more than ${VAULT_LIMITS.fieldsPerItem} fields or ${VAULT_LIMITS.websitesPerItem} websites (or a website over ${VAULT_LIMITS.websiteMaxChars} characters), more than Opengeni mirrors. The previous inventory was kept.`
|
|
7345
|
+
);
|
|
7346
|
+
}
|
|
7347
|
+
function unprovenInventory(listing) {
|
|
7348
|
+
return new VaultProviderError3(
|
|
7349
|
+
"provider_inventory_incomplete",
|
|
7350
|
+
listing === "items" ? "The provider listed this vault in several pages without a snapshot, so Opengeni cannot tell which items were removed. The previous inventory was kept." : "The provider listed its vaults in several pages without a snapshot and this vault was not among them, so Opengeni cannot confirm it was removed."
|
|
7351
|
+
);
|
|
7352
|
+
}
|
|
7353
|
+
function assertMirrorable(item) {
|
|
7354
|
+
if (!Array.isArray(item.fields) || !Array.isArray(item.websites) || item.fields.length > VAULT_LIMITS.fieldsPerItem || item.websites.length > VAULT_LIMITS.websitesPerItem || item.websites.some(
|
|
7355
|
+
(website) => typeof website !== "string" || website.length > VAULT_LIMITS.websiteMaxChars
|
|
7356
|
+
)) {
|
|
7357
|
+
throw oversizedItem();
|
|
7358
|
+
}
|
|
7359
|
+
}
|
|
7060
7360
|
var FORBIDDEN_NAME_CHARACTERS = /[/{}#\\\u0000-\u001f\u007f-\u009f]/g;
|
|
7061
7361
|
function nameKey(name) {
|
|
7062
7362
|
return name.normalize("NFC").trim().toLowerCase();
|
|
@@ -7103,15 +7403,16 @@ function providerPrimaryFieldId(item) {
|
|
|
7103
7403
|
return candidates.find((field) => field.kind === "concealed")?.externalFieldId ?? null;
|
|
7104
7404
|
}
|
|
7105
7405
|
function planProviderMirror(items, contentVersion) {
|
|
7406
|
+
if (items.length > VAULT_LIMITS.itemsPerVault) throw incompleteInventory();
|
|
7407
|
+
for (const item of items) assertMirrorable(item);
|
|
7106
7408
|
const ordered = [...items].sort(
|
|
7107
7409
|
(a, b) => nameKey(a.name).localeCompare(nameKey(b.name)) || a.externalItemId.localeCompare(b.externalItemId)
|
|
7108
7410
|
);
|
|
7109
|
-
const kept = ordered.slice(0, VAULT_LIMITS.itemsPerVault);
|
|
7110
7411
|
const takenItems = /* @__PURE__ */ new Set();
|
|
7111
|
-
const mirrored =
|
|
7412
|
+
const mirrored = ordered.map((item) => {
|
|
7112
7413
|
const name = uniqueProviderName(providerSecretName(item.name), takenItems);
|
|
7113
7414
|
const takenFields = /* @__PURE__ */ new Set();
|
|
7114
|
-
const fields = item.fields.
|
|
7415
|
+
const fields = item.fields.map((field, position) => {
|
|
7115
7416
|
let fieldName = providerSecretName(
|
|
7116
7417
|
field.name,
|
|
7117
7418
|
field.kind === "totp" ? "one-time password" : "field"
|
|
@@ -7129,7 +7430,7 @@ function planProviderMirror(items, contentVersion) {
|
|
|
7129
7430
|
name,
|
|
7130
7431
|
providerName: item.name,
|
|
7131
7432
|
type: item.type,
|
|
7132
|
-
websites: item.websites
|
|
7433
|
+
websites: [...item.websites],
|
|
7133
7434
|
fields,
|
|
7134
7435
|
primaryExternalFieldId: providerPrimaryFieldId({ type: item.type, fields: item.fields }),
|
|
7135
7436
|
hasTotp: item.fields.some((field) => field.kind === "totp"),
|
|
@@ -7137,7 +7438,32 @@ function planProviderMirror(items, contentVersion) {
|
|
|
7137
7438
|
updatedAt: item.updatedAt
|
|
7138
7439
|
};
|
|
7139
7440
|
});
|
|
7140
|
-
return { contentVersion, items: mirrored, truncated:
|
|
7441
|
+
return { contentVersion, items: mirrored, truncated: 0 };
|
|
7442
|
+
}
|
|
7443
|
+
function validTime(value) {
|
|
7444
|
+
if (typeof value !== "string" || value.length > 64) return null;
|
|
7445
|
+
const time = Date.parse(value);
|
|
7446
|
+
return Number.isFinite(time) ? time : null;
|
|
7447
|
+
}
|
|
7448
|
+
function aggregateProviderFreshness(observations, checkedAt) {
|
|
7449
|
+
if (observations.length === 0) return null;
|
|
7450
|
+
let cache = false;
|
|
7451
|
+
let unknown = false;
|
|
7452
|
+
let unknownTime = false;
|
|
7453
|
+
let oldest = null;
|
|
7454
|
+
for (const observation of observations) {
|
|
7455
|
+
const source = observation?.source;
|
|
7456
|
+
if (source === "cache") cache = true;
|
|
7457
|
+
else if (source !== "live") unknown = true;
|
|
7458
|
+
const time = validTime(observation?.sourceSyncedAt);
|
|
7459
|
+
if (time !== null) oldest = oldest === null ? time : Math.min(oldest, time);
|
|
7460
|
+
else if (source !== "live") unknownTime = true;
|
|
7461
|
+
}
|
|
7462
|
+
const syncedAt = unknownTime || oldest === null ? null : new Date(Math.min(oldest, checkedAt.getTime()));
|
|
7463
|
+
return {
|
|
7464
|
+
source: cache ? "cache" : unknown ? "unknown" : "live",
|
|
7465
|
+
sourceSyncedAt: syncedAt ? syncedAt.toISOString() : null
|
|
7466
|
+
};
|
|
7141
7467
|
}
|
|
7142
7468
|
function providerSyncFailure(error, now) {
|
|
7143
7469
|
const code = isVaultProviderError(error) ? error.code : "provider_unavailable";
|
|
@@ -7160,24 +7486,59 @@ function providerSyncFailure(error, now) {
|
|
|
7160
7486
|
lastSyncedAt: null
|
|
7161
7487
|
};
|
|
7162
7488
|
}
|
|
7489
|
+
async function providerStep(call) {
|
|
7490
|
+
try {
|
|
7491
|
+
return await call();
|
|
7492
|
+
} catch (error) {
|
|
7493
|
+
if (isVaultProviderError(error)) throw error;
|
|
7494
|
+
throw new VaultProviderError3(
|
|
7495
|
+
"provider_unavailable",
|
|
7496
|
+
"the vault provider could not be reached or gave an invalid answer"
|
|
7497
|
+
);
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7163
7500
|
async function syncProviderVault(input) {
|
|
7164
7501
|
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
7165
7502
|
const { target } = input;
|
|
7166
|
-
if (target.rateLimitedUntil && target.rateLimitedUntil.getTime() > now().getTime()
|
|
7503
|
+
if (target.rateLimitedUntil && target.rateLimitedUntil.getTime() > now().getTime()) {
|
|
7167
7504
|
return { status: "skipped_rate_limited", rateLimitedUntil: target.rateLimitedUntil };
|
|
7168
7505
|
}
|
|
7506
|
+
const observations = [];
|
|
7507
|
+
const outer = input.ctx.onFreshness;
|
|
7508
|
+
const ctx = {
|
|
7509
|
+
...input.ctx,
|
|
7510
|
+
...target.cacheEpoch !== void 0 && input.ctx.cacheEpoch === void 0 ? { cacheEpoch: target.cacheEpoch } : {},
|
|
7511
|
+
...input.force ? { forceRefresh: true } : {},
|
|
7512
|
+
onFreshness(freshness) {
|
|
7513
|
+
observations.push(freshness);
|
|
7514
|
+
outer?.(freshness);
|
|
7515
|
+
}
|
|
7516
|
+
};
|
|
7517
|
+
const succeeded = () => {
|
|
7518
|
+
const checkedAt = now();
|
|
7519
|
+
return {
|
|
7520
|
+
syncStatus: "ok",
|
|
7521
|
+
syncErrorCode: null,
|
|
7522
|
+
syncErrorMessage: null,
|
|
7523
|
+
rateLimitedUntil: null,
|
|
7524
|
+
lastSyncedAt: checkedAt,
|
|
7525
|
+
freshness: aggregateProviderFreshness(observations, checkedAt)
|
|
7526
|
+
};
|
|
7527
|
+
};
|
|
7169
7528
|
try {
|
|
7170
|
-
const vaults = await input.provider
|
|
7171
|
-
const vault = vaults.find(
|
|
7529
|
+
const vaults = await providerStep(() => readProviderVaultInventory(input.provider, ctx));
|
|
7530
|
+
const vault = vaults.items.find(
|
|
7531
|
+
(candidate) => candidate.externalVaultId === target.externalVaultId
|
|
7532
|
+
);
|
|
7172
7533
|
if (!vault) {
|
|
7173
7534
|
const failure = providerSyncFailure(
|
|
7174
|
-
new
|
|
7535
|
+
vaults.consistent ? new VaultProviderError3(
|
|
7175
7536
|
"provider_not_found",
|
|
7176
7537
|
"the linked provider vault no longer exists or is no longer shared with this connection"
|
|
7177
|
-
),
|
|
7538
|
+
) : unprovenInventory("vaults"),
|
|
7178
7539
|
now()
|
|
7179
7540
|
);
|
|
7180
|
-
await input.store.
|
|
7541
|
+
if (!await input.store.settleRefresh(failure)) return { status: "superseded" };
|
|
7181
7542
|
return {
|
|
7182
7543
|
status: "failed",
|
|
7183
7544
|
syncStatus: failure.syncStatus,
|
|
@@ -7185,26 +7546,19 @@ async function syncProviderVault(input) {
|
|
|
7185
7546
|
rateLimitedUntil: null
|
|
7186
7547
|
};
|
|
7187
7548
|
}
|
|
7549
|
+
const operations = providerOperations2(input.provider.capabilities(), vault);
|
|
7188
7550
|
if (!input.force && vault.contentVersion !== null && vault.contentVersion === target.mirroredContentVersion) {
|
|
7189
|
-
await input.store.
|
|
7190
|
-
|
|
7191
|
-
syncErrorCode: null,
|
|
7192
|
-
syncErrorMessage: null,
|
|
7193
|
-
rateLimitedUntil: null,
|
|
7194
|
-
lastSyncedAt: now()
|
|
7195
|
-
});
|
|
7551
|
+
if (!await input.store.settleRefresh({ ...succeeded(), operations }))
|
|
7552
|
+
return { status: "superseded" };
|
|
7196
7553
|
return { status: "unchanged", contentVersion: vault.contentVersion };
|
|
7197
7554
|
}
|
|
7198
|
-
const
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
rateLimitedUntil: null,
|
|
7206
|
-
lastSyncedAt: now()
|
|
7207
|
-
});
|
|
7555
|
+
const inventory = await providerStep(
|
|
7556
|
+
() => readProviderItemInventory(input.provider, ctx, target.externalVaultId)
|
|
7557
|
+
);
|
|
7558
|
+
if (!inventory.consistent) throw unprovenInventory("items");
|
|
7559
|
+
const plan = planProviderMirror(inventory.items, vault.contentVersion);
|
|
7560
|
+
if (!await input.store.settleRefresh({ ...succeeded(), operations }, plan))
|
|
7561
|
+
return { status: "superseded" };
|
|
7208
7562
|
return {
|
|
7209
7563
|
status: "synced",
|
|
7210
7564
|
contentVersion: plan.contentVersion,
|
|
@@ -7214,7 +7568,7 @@ async function syncProviderVault(input) {
|
|
|
7214
7568
|
} catch (error) {
|
|
7215
7569
|
if (!isVaultProviderError(error)) throw error;
|
|
7216
7570
|
const failure = providerSyncFailure(error, now());
|
|
7217
|
-
await input.store.
|
|
7571
|
+
if (!await input.store.settleRefresh(failure)) return { status: "superseded" };
|
|
7218
7572
|
return {
|
|
7219
7573
|
status: "failed",
|
|
7220
7574
|
syncStatus: failure.syncStatus,
|
|
@@ -7223,11 +7577,10 @@ async function syncProviderVault(input) {
|
|
|
7223
7577
|
};
|
|
7224
7578
|
}
|
|
7225
7579
|
}
|
|
7226
|
-
function databaseProviderMirrorStore(db, context, vaultId) {
|
|
7580
|
+
function databaseProviderMirrorStore(db, context, vaultId, expectedCacheEpoch) {
|
|
7227
7581
|
return {
|
|
7228
|
-
async
|
|
7229
|
-
|
|
7230
|
-
vaultId,
|
|
7582
|
+
async settleRefresh(update, plan) {
|
|
7583
|
+
const mirror = plan ? {
|
|
7231
7584
|
contentVersion: plan.contentVersion,
|
|
7232
7585
|
items: plan.items.map((item) => ({
|
|
7233
7586
|
providerItemRef: item.externalItemId,
|
|
@@ -7245,22 +7598,41 @@ function databaseProviderMirrorStore(db, context, vaultId) {
|
|
|
7245
7598
|
kind: field.kind
|
|
7246
7599
|
}))
|
|
7247
7600
|
}))
|
|
7248
|
-
}
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7601
|
+
} : void 0;
|
|
7602
|
+
try {
|
|
7603
|
+
await setVaultSyncStatus(db, context, {
|
|
7604
|
+
vaultId,
|
|
7605
|
+
expectedCacheEpoch,
|
|
7606
|
+
...mirror ? { mirror } : {},
|
|
7607
|
+
status: update.syncStatus,
|
|
7608
|
+
errorCode: update.syncErrorCode,
|
|
7609
|
+
errorMessage: update.syncErrorMessage,
|
|
7610
|
+
rateLimitedUntil: update.rateLimitedUntil?.toISOString() ?? null,
|
|
7611
|
+
...update.operations !== void 0 ? { operations: update.operations } : {},
|
|
7612
|
+
...update.freshness !== void 0 ? { freshness: update.freshness } : {}
|
|
7613
|
+
});
|
|
7614
|
+
return true;
|
|
7615
|
+
} catch (error) {
|
|
7616
|
+
if (isVaultError(error) && error.code === "provider_conflict" && error.details?.superseded === true)
|
|
7617
|
+
return false;
|
|
7618
|
+
throw error;
|
|
7619
|
+
}
|
|
7258
7620
|
}
|
|
7259
7621
|
};
|
|
7260
7622
|
}
|
|
7261
7623
|
async function syncLinkedProviderVault(input) {
|
|
7262
7624
|
const target = await getProviderVaultSyncTarget(input.db, input.context, input.vaultId);
|
|
7263
|
-
const store = databaseProviderMirrorStore(
|
|
7625
|
+
const store = databaseProviderMirrorStore(
|
|
7626
|
+
input.db,
|
|
7627
|
+
input.context,
|
|
7628
|
+
input.vaultId,
|
|
7629
|
+
target.providerCacheEpoch ?? "0"
|
|
7630
|
+
);
|
|
7631
|
+
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
7632
|
+
const rateLimitedUntil = target.rateLimitedUntil ? new Date(target.rateLimitedUntil) : null;
|
|
7633
|
+
if (rateLimitedUntil && rateLimitedUntil.getTime() > now().getTime()) {
|
|
7634
|
+
return { status: "skipped_rate_limited", rateLimitedUntil };
|
|
7635
|
+
}
|
|
7264
7636
|
let provider;
|
|
7265
7637
|
try {
|
|
7266
7638
|
provider = await input.providers.forVault({
|
|
@@ -7269,8 +7641,8 @@ async function syncLinkedProviderVault(input) {
|
|
|
7269
7641
|
});
|
|
7270
7642
|
} catch (error) {
|
|
7271
7643
|
if (!isVaultProviderError(error)) throw error;
|
|
7272
|
-
const failure = providerSyncFailure(error,
|
|
7273
|
-
await store.
|
|
7644
|
+
const failure = providerSyncFailure(error, now());
|
|
7645
|
+
if (!await store.settleRefresh(failure)) return { status: "superseded" };
|
|
7274
7646
|
return {
|
|
7275
7647
|
status: "failed",
|
|
7276
7648
|
syncStatus: failure.syncStatus,
|
|
@@ -7283,12 +7655,16 @@ async function syncLinkedProviderVault(input) {
|
|
|
7283
7655
|
ctx: {
|
|
7284
7656
|
connectionId: target.providerConnectionId,
|
|
7285
7657
|
budget: input.budget ?? { take() {
|
|
7286
|
-
} }
|
|
7658
|
+
} },
|
|
7659
|
+
cacheEpoch: target.providerCacheEpoch ?? null,
|
|
7660
|
+
...input.signal ? { signal: input.signal } : {},
|
|
7661
|
+
...input.onFreshness ? { onFreshness: input.onFreshness } : {}
|
|
7287
7662
|
},
|
|
7288
7663
|
target: {
|
|
7289
7664
|
externalVaultId: target.providerVaultRef,
|
|
7290
7665
|
mirroredContentVersion: target.providerContentVersion,
|
|
7291
|
-
rateLimitedUntil
|
|
7666
|
+
rateLimitedUntil,
|
|
7667
|
+
cacheEpoch: target.providerCacheEpoch ?? null
|
|
7292
7668
|
},
|
|
7293
7669
|
store,
|
|
7294
7670
|
...input.now ? { now: input.now } : {},
|
|
@@ -7299,6 +7675,7 @@ var LINKED_VAULT_SEARCH_MAX_AGE_MS = 10 * 6e4;
|
|
|
7299
7675
|
var LINKED_VAULT_MISS_MAX_AGE_MS = 2 * 6e4;
|
|
7300
7676
|
var LINKED_VAULT_REFRESH_WAIT_MS = 8e3;
|
|
7301
7677
|
var LINKED_VAULT_OPEN_WAIT_MS = 3e3;
|
|
7678
|
+
var LINKED_VAULT_REFRESH_TIMEOUT_MS = 6e4;
|
|
7302
7679
|
function linkedVaultNeedsRefresh(vault, maxAgeMs, now) {
|
|
7303
7680
|
if (vault.source === "opengeni") return false;
|
|
7304
7681
|
if (vault.rateLimitedUntil && new Date(vault.rateLimitedUntil).getTime() > now.getTime()) {
|
|
@@ -7308,38 +7685,124 @@ function linkedVaultNeedsRefresh(vault, maxAgeMs, now) {
|
|
|
7308
7685
|
if (!last) return true;
|
|
7309
7686
|
return now.getTime() - new Date(last).getTime() >= maxAgeMs;
|
|
7310
7687
|
}
|
|
7688
|
+
function isoOrNull(value) {
|
|
7689
|
+
const time = validTime(value);
|
|
7690
|
+
return time === null ? null : new Date(time).toISOString();
|
|
7691
|
+
}
|
|
7692
|
+
function providerVaultFreshness(vault, now, options = {}) {
|
|
7693
|
+
if (vault.source === "opengeni") return void 0;
|
|
7694
|
+
const stored2 = vault.providerFreshness ?? null;
|
|
7695
|
+
const failed = vault.syncStatus !== "ok" && vault.syncStatus !== "syncing";
|
|
7696
|
+
const base = {
|
|
7697
|
+
lastCheckedAt: isoOrNull(vault.lastSyncedAt),
|
|
7698
|
+
sourceSyncedAt: isoOrNull(stored2?.sourceSyncedAt),
|
|
7699
|
+
source: stored2?.source === "live" || stored2?.source === "cache" ? stored2.source : "unknown",
|
|
7700
|
+
errorCode: failed && vault.syncErrorCode ? vault.syncErrorCode.slice(0, 100) : null,
|
|
7701
|
+
retryAt: null
|
|
7702
|
+
};
|
|
7703
|
+
if (options.refreshing || vault.syncStatus === "syncing") return { state: "refreshing", ...base };
|
|
7704
|
+
if (vault.syncStatus === "rate_limited") {
|
|
7705
|
+
const retryAt = isoOrNull(vault.rateLimitedUntil);
|
|
7706
|
+
if (retryAt !== null && Date.parse(retryAt) > now.getTime()) {
|
|
7707
|
+
return { state: "unavailable", ...base, retryAt };
|
|
7708
|
+
}
|
|
7709
|
+
return { state: "stale", ...base };
|
|
7710
|
+
}
|
|
7711
|
+
if (failed) return { state: "unavailable", ...base };
|
|
7712
|
+
const checked = base.lastCheckedAt === null ? null : Date.parse(base.lastCheckedAt);
|
|
7713
|
+
const maxAgeMs = options.maxAgeMs ?? LINKED_VAULT_SEARCH_MAX_AGE_MS;
|
|
7714
|
+
if (checked === null || now.getTime() - checked >= maxAgeMs) return { state: "stale", ...base };
|
|
7715
|
+
return { state: "current", ...base };
|
|
7716
|
+
}
|
|
7717
|
+
function vaultRefreshScopeKey(context) {
|
|
7718
|
+
if (context.kind === "subject") {
|
|
7719
|
+
return `${context.accountId}|${context.workspaceId}|subject|${context.subjectId}`;
|
|
7720
|
+
}
|
|
7721
|
+
const principal = context.principal;
|
|
7722
|
+
switch (principal.kind) {
|
|
7723
|
+
case "attempt":
|
|
7724
|
+
return `${principal.accountId}|${principal.workspaceId}|attempt|${principal.attemptId}|${principal.executionGeneration}`;
|
|
7725
|
+
case "site_viewer":
|
|
7726
|
+
return `${principal.accountId}|${principal.workspaceId}|site|${principal.siteId}|${principal.siteVersion}|${principal.subjectId}`;
|
|
7727
|
+
case "human":
|
|
7728
|
+
return `${principal.accountId}|${principal.workspaceId}|human|${principal.subjectId}|${principal.viaApiKey ? "key" : principal.verifiedHuman ? "verified" : "unverified"}`;
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7311
7731
|
var linkedVaultRefreshes = /* @__PURE__ */ new Map();
|
|
7312
|
-
async function
|
|
7732
|
+
async function refreshLinkedVaults(input) {
|
|
7313
7733
|
const now = (input.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
7314
|
-
const
|
|
7315
|
-
|
|
7734
|
+
const maxAgeMs = input.maxAgeMs ?? LINKED_VAULT_SEARCH_MAX_AGE_MS;
|
|
7735
|
+
const scope = vaultRefreshScopeKey(input.context);
|
|
7736
|
+
const outcomes = /* @__PURE__ */ new Map();
|
|
7316
7737
|
const changed = [];
|
|
7317
|
-
const runs =
|
|
7318
|
-
|
|
7738
|
+
const runs = [];
|
|
7739
|
+
for (const vault of input.vaults) {
|
|
7740
|
+
if (outcomes.has(vault.id)) continue;
|
|
7741
|
+
const limited = vault.rateLimitedUntil !== null && new Date(vault.rateLimitedUntil).getTime() > now.getTime();
|
|
7742
|
+
const due = vault.source !== "opengeni" && !limited && (input.force || linkedVaultNeedsRefresh(vault, maxAgeMs, now));
|
|
7743
|
+
const outcome = {
|
|
7744
|
+
vaultId: vault.id,
|
|
7745
|
+
state: due ? "pending" : "not_due",
|
|
7746
|
+
result: null
|
|
7747
|
+
};
|
|
7748
|
+
outcomes.set(vault.id, outcome);
|
|
7749
|
+
if (!due) continue;
|
|
7750
|
+
const key = `${scope}|${vault.id}|${input.force ? "force" : "stale"}`;
|
|
7751
|
+
let run = linkedVaultRefreshes.get(key);
|
|
7319
7752
|
if (!run) {
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7753
|
+
let timer;
|
|
7754
|
+
run = Promise.race([
|
|
7755
|
+
syncLinkedProviderVault({
|
|
7756
|
+
db: input.db,
|
|
7757
|
+
context: input.context,
|
|
7758
|
+
vaultId: vault.id,
|
|
7759
|
+
providers: input.providers,
|
|
7760
|
+
signal: AbortSignal.timeout(LINKED_VAULT_REFRESH_TIMEOUT_MS),
|
|
7761
|
+
...input.force ? { force: true } : {},
|
|
7762
|
+
...input.now ? { now: input.now } : {}
|
|
7763
|
+
}),
|
|
7764
|
+
new Promise((resolve) => {
|
|
7765
|
+
timer = setTimeout(() => resolve(null), LINKED_VAULT_REFRESH_TIMEOUT_MS);
|
|
7766
|
+
timer.unref?.();
|
|
7767
|
+
})
|
|
7768
|
+
]).catch(() => {
|
|
7769
|
+
outcome.state = "failed";
|
|
7770
|
+
return null;
|
|
7771
|
+
}).finally(() => {
|
|
7772
|
+
if (timer) clearTimeout(timer);
|
|
7773
|
+
linkedVaultRefreshes.delete(key);
|
|
7774
|
+
});
|
|
7775
|
+
linkedVaultRefreshes.set(key, run);
|
|
7328
7776
|
}
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7777
|
+
runs.push(
|
|
7778
|
+
run.then((result) => {
|
|
7779
|
+
if (outcome.state === "pending") outcome.state = result ? "done" : "failed";
|
|
7780
|
+
outcome.result = result;
|
|
7781
|
+
if (result?.status === "synced") changed.push(vault.id);
|
|
7782
|
+
})
|
|
7783
|
+
);
|
|
7784
|
+
}
|
|
7785
|
+
if (runs.length > 0) {
|
|
7786
|
+
let timer;
|
|
7787
|
+
await Promise.race([
|
|
7788
|
+
Promise.all(runs),
|
|
7789
|
+
new Promise((resolve) => {
|
|
7790
|
+
timer = setTimeout(resolve, input.waitMs ?? LINKED_VAULT_REFRESH_WAIT_MS);
|
|
7791
|
+
timer.unref?.();
|
|
7792
|
+
})
|
|
7793
|
+
]);
|
|
7794
|
+
if (timer) clearTimeout(timer);
|
|
7795
|
+
}
|
|
7796
|
+
const list = [...outcomes.values()].map((outcome) => ({ ...outcome }));
|
|
7797
|
+
return {
|
|
7798
|
+
outcomes: list,
|
|
7799
|
+
changed: [...changed],
|
|
7800
|
+
pending: list.filter((outcome) => outcome.state === "pending").map((outcome) => outcome.vaultId)
|
|
7801
|
+
};
|
|
7802
|
+
}
|
|
7803
|
+
async function refreshStaleLinkedVaults(input) {
|
|
7804
|
+
const report = await refreshLinkedVaults(input);
|
|
7805
|
+
return report.changed;
|
|
7343
7806
|
}
|
|
7344
7807
|
|
|
7345
7808
|
// src/domain/vaults/secret-resolver.ts
|
|
@@ -7473,6 +7936,51 @@ function providerFailureCode(code) {
|
|
|
7473
7936
|
if (code === "provider_permission_denied") return "denied";
|
|
7474
7937
|
return "provider_unavailable";
|
|
7475
7938
|
}
|
|
7939
|
+
function relevantProviderVaults(reference, candidates, reachable) {
|
|
7940
|
+
const connected = reachable.filter((vault) => vault.source !== "opengeni");
|
|
7941
|
+
if (connected.length === 0) return [];
|
|
7942
|
+
const where = possibleProviderVaults(reference, reachable, connected);
|
|
7943
|
+
const live = candidates.filter((candidate) => !candidate.archived);
|
|
7944
|
+
if (live.length === 0) return where;
|
|
7945
|
+
const deciding = Math.min(...live.map((candidate) => candidate.tier));
|
|
7946
|
+
const lacking = new Set(
|
|
7947
|
+
live.filter(
|
|
7948
|
+
(candidate) => candidate.tier <= deciding && candidate.source !== "opengeni" && (candidate.missing === "field" || candidate.missing === "totp_field")
|
|
7949
|
+
).map((candidate) => candidate.vaultId)
|
|
7950
|
+
);
|
|
7951
|
+
const matched = new Set(live.map((candidate) => candidate.vaultId));
|
|
7952
|
+
const unmatched = new Set(
|
|
7953
|
+
where.filter((vault) => vault.tier <= deciding && !matched.has(vault.id)).map((vault) => vault.id)
|
|
7954
|
+
);
|
|
7955
|
+
return connected.filter((vault) => lacking.has(vault.id) || unmatched.has(vault.id));
|
|
7956
|
+
}
|
|
7957
|
+
function possibleProviderVaults(reference, reachable, connected) {
|
|
7958
|
+
if (reference.target.form !== "path") return [];
|
|
7959
|
+
const segments = reference.target.segments;
|
|
7960
|
+
if (segments.length === 1) return [...connected];
|
|
7961
|
+
const nameKey2 = secretNameKey(segments[0]);
|
|
7962
|
+
const named = reachable.filter((vault) => secretNameKey(vault.name) === nameKey2);
|
|
7963
|
+
if (segments.length === 2 && named.length === 0) return [...connected];
|
|
7964
|
+
return named.filter((vault) => vault.source !== "opengeni");
|
|
7965
|
+
}
|
|
7966
|
+
function uncheckedSourceFailure(reference, unchecked) {
|
|
7967
|
+
const authExpired = unchecked.filter(({ vault }) => vault.syncStatus === "auth_expired");
|
|
7968
|
+
const rateLimited = unchecked.filter(
|
|
7969
|
+
({ freshness }) => freshness.state === "unavailable" && freshness.retryAt !== null
|
|
7970
|
+
);
|
|
7971
|
+
const [code, named, why] = authExpired.length > 0 ? ["provider_auth_expired", authExpired, "needs to be reconnected"] : rateLimited.length > 0 ? ["provider_rate_limited", rateLimited, "is rate limited by its provider"] : [
|
|
7972
|
+
"provider_unavailable",
|
|
7973
|
+
unchecked,
|
|
7974
|
+
unchecked.every(({ freshness }) => freshness.state === "refreshing") ? "is still refreshing" : "could not be checked"
|
|
7975
|
+
];
|
|
7976
|
+
const names = named.slice(0, 3).map(({ vault }) => vault.name).join(", ");
|
|
7977
|
+
const retryAt = rateLimited.map(({ freshness }) => freshness.retryAt).sort().at(0);
|
|
7978
|
+
return {
|
|
7979
|
+
raw: reference.raw,
|
|
7980
|
+
code,
|
|
7981
|
+
message: `not found in the vaults that could be checked, but ${names} ${why}, so it may be there` + (code === "provider_rate_limited" && retryAt ? `; try again after ${retryAt}` : "; try again later")
|
|
7982
|
+
};
|
|
7983
|
+
}
|
|
7476
7984
|
function decodeBase64(value) {
|
|
7477
7985
|
return new Uint8Array(Buffer.from(value, "base64"));
|
|
7478
7986
|
}
|
|
@@ -7535,35 +8043,74 @@ function createSecretResolver(deps, principal, opts) {
|
|
|
7535
8043
|
const { meta: meta2, candidates } = await resolveSecretReferenceCandidates(deps.db, context, {
|
|
7536
8044
|
refs: references.map((reference, index) => lookupFor(reference, index))
|
|
7537
8045
|
});
|
|
7538
|
-
const
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
const outcome = chooseSecretCandidate(
|
|
7542
|
-
reference,
|
|
7543
|
-
candidates.filter((candidate) => candidate.refIndex === index),
|
|
7544
|
-
meta2
|
|
7545
|
-
);
|
|
7546
|
-
if ("failure" in outcome) failures2.push(outcome.failure);
|
|
7547
|
-
else chosen2.push({ reference, candidate: outcome.candidate });
|
|
8046
|
+
const outcomes2 = references.map((reference, index) => {
|
|
8047
|
+
const own = candidates.filter((candidate) => candidate.refIndex === index);
|
|
8048
|
+
return { reference, candidates: own, outcome: chooseSecretCandidate(reference, own, meta2) };
|
|
7548
8049
|
});
|
|
7549
|
-
return { meta: meta2,
|
|
8050
|
+
return { meta: meta2, outcomes: outcomes2 };
|
|
7550
8051
|
};
|
|
7551
|
-
let { meta,
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
if (registry) {
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
8052
|
+
let { meta, outcomes } = await choose();
|
|
8053
|
+
let registry;
|
|
8054
|
+
const providers = () => {
|
|
8055
|
+
if (registry === void 0) {
|
|
8056
|
+
registry = deps.providers ?? opts.providersForSubject?.(meta.subjectId) ?? null;
|
|
8057
|
+
}
|
|
8058
|
+
return registry;
|
|
8059
|
+
};
|
|
8060
|
+
const misses = () => outcomes.filter(
|
|
8061
|
+
(entry) => "failure" in entry.outcome && entry.outcome.failure.code === "not_found"
|
|
8062
|
+
);
|
|
8063
|
+
let reachable = null;
|
|
8064
|
+
let refreshing = /* @__PURE__ */ new Set();
|
|
8065
|
+
if (misses().length > 0) {
|
|
8066
|
+
reachable = await listVaultsForSubject(deps.db, context, {
|
|
8067
|
+
workspaceId: principal.workspaceId
|
|
8068
|
+
});
|
|
8069
|
+
const relevant = /* @__PURE__ */ new Map();
|
|
8070
|
+
for (const entry of misses()) {
|
|
8071
|
+
for (const vault of relevantProviderVaults(entry.reference, entry.candidates, reachable)) {
|
|
8072
|
+
relevant.set(vault.id, vault);
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
const refreshProviders = relevant.size > 0 ? providers() : null;
|
|
8076
|
+
if (refreshProviders) {
|
|
8077
|
+
const report = await refreshLinkedVaults({
|
|
7559
8078
|
db: deps.db,
|
|
7560
8079
|
context,
|
|
7561
|
-
vaults:
|
|
7562
|
-
providers:
|
|
8080
|
+
vaults: [...relevant.values()],
|
|
8081
|
+
providers: refreshProviders,
|
|
7563
8082
|
maxAgeMs: LINKED_VAULT_MISS_MAX_AGE_MS
|
|
7564
8083
|
});
|
|
7565
|
-
|
|
8084
|
+
refreshing = new Set(report.pending);
|
|
8085
|
+
if (report.changed.length > 0 || report.outcomes.some((outcome) => outcome.result?.status === "superseded"))
|
|
8086
|
+
({ meta, outcomes } = await choose());
|
|
8087
|
+
if (report.outcomes.some((outcome) => outcome.state === "done" || outcome.state === "failed")) {
|
|
8088
|
+
reachable = await listVaultsForSubject(deps.db, context, {
|
|
8089
|
+
workspaceId: principal.workspaceId
|
|
8090
|
+
});
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
const failures = [];
|
|
8095
|
+
const chosen = [];
|
|
8096
|
+
const checkedAt = /* @__PURE__ */ new Date();
|
|
8097
|
+
for (const entry of outcomes) {
|
|
8098
|
+
if ("candidate" in entry.outcome) {
|
|
8099
|
+
chosen.push({ reference: entry.reference, candidate: entry.outcome.candidate });
|
|
8100
|
+
continue;
|
|
7566
8101
|
}
|
|
8102
|
+
let failure = entry.outcome.failure;
|
|
8103
|
+
if (failure.code === "not_found" && reachable) {
|
|
8104
|
+
const unchecked = relevantProviderVaults(entry.reference, entry.candidates, reachable).map((vault) => ({
|
|
8105
|
+
vault,
|
|
8106
|
+
freshness: providerVaultFreshness(vault, checkedAt, {
|
|
8107
|
+
maxAgeMs: LINKED_VAULT_MISS_MAX_AGE_MS,
|
|
8108
|
+
refreshing: refreshing.has(vault.id)
|
|
8109
|
+
})
|
|
8110
|
+
})).filter(({ freshness }) => freshness.state !== "current");
|
|
8111
|
+
if (unchecked.length > 0) failure = uncheckedSourceFailure(entry.reference, unchecked);
|
|
8112
|
+
}
|
|
8113
|
+
failures.push(failure);
|
|
7567
8114
|
}
|
|
7568
8115
|
const hasActingHuman = meta.humanExpected && meta.humanValid;
|
|
7569
8116
|
if (failures.length > 0) {
|
|
@@ -7582,13 +8129,16 @@ function createSecretResolver(deps, principal, opts) {
|
|
|
7582
8129
|
for (const group of providerGroups.values()) {
|
|
7583
8130
|
const first = group[0].candidate;
|
|
7584
8131
|
try {
|
|
7585
|
-
const
|
|
7586
|
-
const provider = await registry.forVault({
|
|
8132
|
+
const provider = await (providers() ?? EMPTY_VAULT_PROVIDER_REGISTRY).forVault({
|
|
7587
8133
|
source: first.source,
|
|
7588
8134
|
providerConnectionId: first.providerConnectionId
|
|
7589
8135
|
});
|
|
7590
8136
|
const values = await provider.readFields(
|
|
7591
|
-
{
|
|
8137
|
+
{
|
|
8138
|
+
connectionId: first.providerConnectionId,
|
|
8139
|
+
budget,
|
|
8140
|
+
cacheEpoch: first.providerCacheEpoch ?? null
|
|
8141
|
+
},
|
|
7592
8142
|
first.providerVaultRef ?? "",
|
|
7593
8143
|
first.providerItemRef ?? "",
|
|
7594
8144
|
[...new Set(group.map((entry) => entry.candidate.providerFieldRef ?? ""))]
|
|
@@ -7602,7 +8152,7 @@ function createSecretResolver(deps, principal, opts) {
|
|
|
7602
8152
|
failures.push({
|
|
7603
8153
|
raw: entry.reference.raw,
|
|
7604
8154
|
code,
|
|
7605
|
-
message: code === "provider_rate_limited" ? "the connected vault is rate limited; try again later" : code === "provider_auth_expired" ? "the connected vault needs to be reconnected" : "the connected vault is unavailable"
|
|
8155
|
+
message: code === "provider_rate_limited" ? "the connected vault is rate limited; try again later" : code === "provider_auth_expired" ? "the connected vault needs to be reconnected" : code === "not_found" ? "the connected vault no longer has this item" : "the connected vault is unavailable"
|
|
7606
8156
|
});
|
|
7607
8157
|
}
|
|
7608
8158
|
}
|
|
@@ -8416,7 +8966,7 @@ async function swapActiveSandbox(services, ctx, target, workingDir) {
|
|
|
8416
8966
|
async function createRunOnSecretResolver(services, attempt, providersForSubject) {
|
|
8417
8967
|
const session = await getSession3(services.db, attempt.workspaceId, attempt.sessionId);
|
|
8418
8968
|
return createSecretResolver(
|
|
8419
|
-
{ db: services.db, encryptionKey:
|
|
8969
|
+
{ db: services.db, encryptionKey: environmentsEncryptionKeyBytes3(services.settings) ?? null },
|
|
8420
8970
|
{ kind: "attempt", ...attempt },
|
|
8421
8971
|
{
|
|
8422
8972
|
capabilityEnabled: session?.agent ? agentCapabilityEnabled(session.agent.capabilities, "secrets") : true,
|
|
@@ -10603,10 +11153,10 @@ async function validateSubmittedTimelineAnnotations(db, workspaceId, sessionId,
|
|
|
10603
11153
|
}
|
|
10604
11154
|
|
|
10605
11155
|
// src/domain/encryption-key.ts
|
|
10606
|
-
import { environmentsEncryptionKeyBytes as
|
|
11156
|
+
import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes4 } from "@opengeni/config";
|
|
10607
11157
|
import { HTTPException as HTTPException19 } from "hono/http-exception";
|
|
10608
11158
|
function requireEnvironmentEncryption(settings) {
|
|
10609
|
-
const key =
|
|
11159
|
+
const key = environmentsEncryptionKeyBytes4(settings);
|
|
10610
11160
|
if (!key) {
|
|
10611
11161
|
throw new HTTPException19(503, {
|
|
10612
11162
|
message: "secrets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
@@ -10630,7 +11180,7 @@ import {
|
|
|
10630
11180
|
areOpenGeniSlackBotScopesAccepted
|
|
10631
11181
|
} from "@opengeni/contracts";
|
|
10632
11182
|
import {
|
|
10633
|
-
getConnectionMetadata as
|
|
11183
|
+
getConnectionMetadata as getConnectionMetadata5,
|
|
10634
11184
|
availableSlackBotConnectionMetadata,
|
|
10635
11185
|
requireWorkspace as requireWorkspace4
|
|
10636
11186
|
} from "@opengeni/db";
|
|
@@ -10651,7 +11201,7 @@ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
|
|
|
10651
11201
|
);
|
|
10652
11202
|
}
|
|
10653
11203
|
async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
|
|
10654
|
-
const local = await
|
|
11204
|
+
const local = await getConnectionMetadata5(db, workspaceId, connectionId, null);
|
|
10655
11205
|
const workspace = await requireWorkspace4(db, workspaceId);
|
|
10656
11206
|
const connection = local ?? (await availableSlackBotConnectionMetadata(db, { accountId: workspace.accountId, workspaceId })).find((candidate) => candidate.id === connectionId) ?? null;
|
|
10657
11207
|
if (!connection || !isOpenGeniSlackBotConnection(connection)) {
|
|
@@ -12621,9 +13171,9 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
|
|
|
12621
13171
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
12622
13172
|
await assertSiteAgentMayChooseCompute(unresolvedDeps.db, grant, payload);
|
|
12623
13173
|
if (payload.firstPartyMcpTools) {
|
|
12624
|
-
const
|
|
13174
|
+
const unavailable2 = deploymentUnavailableFirstPartyMcpTools(unresolvedDeps.settings);
|
|
12625
13175
|
payload.firstPartyMcpTools = payload.firstPartyMcpTools.filter(
|
|
12626
|
-
(tool) => !
|
|
13176
|
+
(tool) => !unavailable2.has(tool)
|
|
12627
13177
|
);
|
|
12628
13178
|
}
|
|
12629
13179
|
const surface = resolveTurnSurface({
|
|
@@ -15710,7 +16260,10 @@ function createRealtimeVoiceBilling(deps) {
|
|
|
15710
16260
|
};
|
|
15711
16261
|
}
|
|
15712
16262
|
const attribution = input.callerSubjectId === facts.ownerSubjectId ? input.attribution : { kind: "unknown" };
|
|
15713
|
-
const cost = realtimeVoiceMinuteCreditMicros(
|
|
16263
|
+
const cost = realtimeVoiceMinuteCreditMicros(
|
|
16264
|
+
voice.pricing,
|
|
16265
|
+
deps.settings.creditMarginBpsOverride
|
|
16266
|
+
);
|
|
15714
16267
|
const minutes = facts.connections.flatMap(
|
|
15715
16268
|
(connection) => Array.from(
|
|
15716
16269
|
{ length: realtimeVoiceStartedMinutes(connection.startedAt, connection.observedUntil) },
|
|
@@ -15898,7 +16451,11 @@ function createVoiceInputBilling(deps) {
|
|
|
15898
16451
|
usage: input.usage,
|
|
15899
16452
|
trustedDurationSeconds: input.billing.trustedDurationSeconds
|
|
15900
16453
|
});
|
|
15901
|
-
const cost = calculateVoiceInputCost(
|
|
16454
|
+
const cost = calculateVoiceInputCost(
|
|
16455
|
+
input.pricing,
|
|
16456
|
+
usage,
|
|
16457
|
+
deps.settings.creditMarginBpsOverride
|
|
16458
|
+
);
|
|
15902
16459
|
return await settleVoiceInputUsage(deps.db, {
|
|
15903
16460
|
accountId: input.accountId,
|
|
15904
16461
|
workspaceId: input.workspaceId,
|
|
@@ -16106,6 +16663,121 @@ function createWebSearchBilling(deps) {
|
|
|
16106
16663
|
};
|
|
16107
16664
|
}
|
|
16108
16665
|
|
|
16666
|
+
// src/domain/vaults/remote-provider-runtime.ts
|
|
16667
|
+
import {
|
|
16668
|
+
DestinationPolicyError,
|
|
16669
|
+
ResponseBodyLimitError,
|
|
16670
|
+
isLocalTestEnvironment,
|
|
16671
|
+
pinnedFetch as pinnedFetch2,
|
|
16672
|
+
readResponseTextBounded,
|
|
16673
|
+
validateHttpUrl
|
|
16674
|
+
} from "@opengeni/network";
|
|
16675
|
+
import {
|
|
16676
|
+
RemoteVaultTransportError,
|
|
16677
|
+
VaultProviderError as VaultProviderError4,
|
|
16678
|
+
createRemoteVaultProvider
|
|
16679
|
+
} from "@opengeni/vaults";
|
|
16680
|
+
function createPinnedRemoteVaultTransport(settings, fetchImpl = pinnedFetch2) {
|
|
16681
|
+
return {
|
|
16682
|
+
async post(request) {
|
|
16683
|
+
if (request.signal?.aborted) throw new RemoteVaultTransportError("aborted", false);
|
|
16684
|
+
const controller = new AbortController();
|
|
16685
|
+
let timedOut = false;
|
|
16686
|
+
const timer = setTimeout(() => {
|
|
16687
|
+
timedOut = true;
|
|
16688
|
+
controller.abort();
|
|
16689
|
+
}, request.timeoutMs);
|
|
16690
|
+
const onAbort = () => controller.abort();
|
|
16691
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
16692
|
+
let rejectAborted;
|
|
16693
|
+
try {
|
|
16694
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
16695
|
+
rejectAborted = () => reject(new RemoteVaultTransportError(timedOut ? "timeout" : "aborted", true));
|
|
16696
|
+
controller.signal.addEventListener("abort", rejectAborted, { once: true });
|
|
16697
|
+
if (controller.signal.aborted) rejectAborted();
|
|
16698
|
+
});
|
|
16699
|
+
const exchange = async () => {
|
|
16700
|
+
const response = await fetchImpl(
|
|
16701
|
+
request.url,
|
|
16702
|
+
{
|
|
16703
|
+
method: "POST",
|
|
16704
|
+
headers: { ...request.headers },
|
|
16705
|
+
body: request.body,
|
|
16706
|
+
redirect: "manual",
|
|
16707
|
+
signal: controller.signal
|
|
16708
|
+
},
|
|
16709
|
+
settings,
|
|
16710
|
+
{ label: "Vault provider", requireHttpsOutsideLocalTest: true }
|
|
16711
|
+
);
|
|
16712
|
+
if (response.status >= 300 && response.status < 400) {
|
|
16713
|
+
await response.body?.cancel().catch(() => void 0);
|
|
16714
|
+
throw new RemoteVaultTransportError("redirect", true);
|
|
16715
|
+
}
|
|
16716
|
+
return {
|
|
16717
|
+
status: response.status,
|
|
16718
|
+
headers: response.headers,
|
|
16719
|
+
body: await readResponseTextBounded(
|
|
16720
|
+
response,
|
|
16721
|
+
request.maxResponseBytes,
|
|
16722
|
+
"Vault provider",
|
|
16723
|
+
{ signal: controller.signal }
|
|
16724
|
+
)
|
|
16725
|
+
};
|
|
16726
|
+
};
|
|
16727
|
+
return await Promise.race([exchange(), aborted]);
|
|
16728
|
+
} catch (error) {
|
|
16729
|
+
if (error instanceof RemoteVaultTransportError) throw error;
|
|
16730
|
+
if (error instanceof DestinationPolicyError)
|
|
16731
|
+
throw new RemoteVaultTransportError("refused", false);
|
|
16732
|
+
if (error instanceof ResponseBodyLimitError)
|
|
16733
|
+
throw new RemoteVaultTransportError("too_large", true);
|
|
16734
|
+
throw new RemoteVaultTransportError(
|
|
16735
|
+
timedOut ? "timeout" : controller.signal.aborted ? "aborted" : "network",
|
|
16736
|
+
true
|
|
16737
|
+
);
|
|
16738
|
+
} finally {
|
|
16739
|
+
clearTimeout(timer);
|
|
16740
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
16741
|
+
if (rejectAborted) controller.signal.removeEventListener("abort", rejectAborted);
|
|
16742
|
+
}
|
|
16743
|
+
}
|
|
16744
|
+
};
|
|
16745
|
+
}
|
|
16746
|
+
function createRemoteExternalVaultFactories(settings) {
|
|
16747
|
+
const transport = createPinnedRemoteVaultTransport(settings);
|
|
16748
|
+
const factory = async ({
|
|
16749
|
+
credentials,
|
|
16750
|
+
operations
|
|
16751
|
+
}) => {
|
|
16752
|
+
if (credentials.provider !== "external" && credentials.provider !== "bitwarden")
|
|
16753
|
+
throw new VaultProviderError4("provider_auth_invalid", "Vault provider does not match.");
|
|
16754
|
+
let endpoint2;
|
|
16755
|
+
try {
|
|
16756
|
+
endpoint2 = validateHttpUrl(credentials.endpoint, {
|
|
16757
|
+
allowLoopbackHttp: isLocalTestEnvironment(settings.environment),
|
|
16758
|
+
label: "Vault provider"
|
|
16759
|
+
});
|
|
16760
|
+
} catch {
|
|
16761
|
+
throw new VaultProviderError4(
|
|
16762
|
+
"provider_auth_invalid",
|
|
16763
|
+
"Use an HTTPS vault endpoint without URL credentials or a fragment."
|
|
16764
|
+
);
|
|
16765
|
+
}
|
|
16766
|
+
return {
|
|
16767
|
+
credentials: { ...credentials, endpoint: endpoint2 },
|
|
16768
|
+
provider: createRemoteVaultProvider({
|
|
16769
|
+
provider: credentials.provider,
|
|
16770
|
+
endpoint: endpoint2,
|
|
16771
|
+
signingSecret: credentials.signingSecret,
|
|
16772
|
+
transport,
|
|
16773
|
+
allowInsecureHttp: isLocalTestEnvironment(settings.environment),
|
|
16774
|
+
...operations ? { operations } : {}
|
|
16775
|
+
})
|
|
16776
|
+
};
|
|
16777
|
+
};
|
|
16778
|
+
return { external: factory, bitwarden: factory };
|
|
16779
|
+
}
|
|
16780
|
+
|
|
16109
16781
|
// src/workflow-wake-contract.ts
|
|
16110
16782
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
|
|
16111
16783
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
|
|
@@ -17182,7 +17854,7 @@ async function executeConnectOperation(input) {
|
|
|
17182
17854
|
|
|
17183
17855
|
// src/application/prepared-mcp-connection.ts
|
|
17184
17856
|
import { createHash as createHash3 } from "crypto";
|
|
17185
|
-
import { isDeepStrictEqual as
|
|
17857
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
17186
17858
|
import {
|
|
17187
17859
|
CapabilityCatalogItem as CapabilityCatalogItem2,
|
|
17188
17860
|
McpServerConnectionRef,
|
|
@@ -17266,7 +17938,7 @@ async function prepareMcpConnectionActivation(input) {
|
|
|
17266
17938
|
const installation = await prepared.commit(db);
|
|
17267
17939
|
const savedRef = McpServerConnectionRef.safeParse(installation.config.connectionRef);
|
|
17268
17940
|
const retainedExactPin = savedRef.success && savedRef.data.authoritySource !== "host" && savedRef.data.connectionId === connection.id && savedRef.data.providerDomain === connectionRef.providerDomain && (savedRef.data.subjectScope ?? "workspace") === connectionRef.subjectScope && (savedRef.data.kind ?? connection.kind) === connection.kind && (!savedRef.data.resource || savedRef.data.resource === endpoint2);
|
|
17269
|
-
if (installation.status !== "active" || !retainedExactPin && !
|
|
17941
|
+
if (installation.status !== "active" || !retainedExactPin && !isDeepStrictEqual3(installation.config.connectionRef, connectionRef)) {
|
|
17270
17942
|
throw new HTTPException26(409, {
|
|
17271
17943
|
message: "MCP account selection changed; review the existing connection before setup"
|
|
17272
17944
|
});
|
|
@@ -17977,7 +18649,7 @@ function sha256Hex2(bytes) {
|
|
|
17977
18649
|
}
|
|
17978
18650
|
|
|
17979
18651
|
// src/domain/skill-search.ts
|
|
17980
|
-
import { pinnedFetch as
|
|
18652
|
+
import { pinnedFetch as pinnedFetch3, readResponseJsonBounded } from "@opengeni/network";
|
|
17981
18653
|
var PublicSkillSearchError = class extends Error {
|
|
17982
18654
|
constructor(code, message, retryAfterSeconds = null) {
|
|
17983
18655
|
super(message);
|
|
@@ -17992,7 +18664,7 @@ var maxResponseBytes = 256 * 1024;
|
|
|
17992
18664
|
var maxResults = 20;
|
|
17993
18665
|
var ownerPattern = /^[a-z0-9](?:[a-z0-9-]{0,38})$/iu;
|
|
17994
18666
|
var segmentPattern = /^[a-z0-9](?:[a-z0-9._-]{0,98}[a-z0-9])?$/iu;
|
|
17995
|
-
function createPublicSkillSearchClient(settings, fetcher =
|
|
18667
|
+
function createPublicSkillSearchClient(settings, fetcher = pinnedFetch3) {
|
|
17996
18668
|
return {
|
|
17997
18669
|
async search(input) {
|
|
17998
18670
|
const query = input.query.trim();
|
|
@@ -18119,7 +18791,7 @@ function parseRetryAfter(value) {
|
|
|
18119
18791
|
|
|
18120
18792
|
// src/domain/github-skill-source.ts
|
|
18121
18793
|
import { HTTPException as HTTPException28 } from "hono/http-exception";
|
|
18122
|
-
import { pinnedFetch as
|
|
18794
|
+
import { pinnedFetch as pinnedFetch4, readResponseJsonBounded as readResponseJsonBounded2, readResponseTextBounded as readResponseTextBounded2 } from "@opengeni/network";
|
|
18123
18795
|
import {
|
|
18124
18796
|
PORTABLE_SKILL_MAX_FILES as PORTABLE_SKILL_MAX_FILES2,
|
|
18125
18797
|
PORTABLE_SKILL_MAX_FILE_BYTES as PORTABLE_SKILL_MAX_FILE_BYTES2,
|
|
@@ -18220,7 +18892,7 @@ function createGitHubSkillSourceClient(settings, requestJson = (path, maxBytes,
|
|
|
18220
18892
|
}
|
|
18221
18893
|
async function downloadSkillSnapshot(settings, owner, repository, slug) {
|
|
18222
18894
|
const signal = AbortSignal.timeout(githubRequestTimeoutMs);
|
|
18223
|
-
const response = await
|
|
18895
|
+
const response = await pinnedFetch4(
|
|
18224
18896
|
`https://skills.sh/api/download/${[owner, repository, slug].map(encodeURIComponent).join("/")}`,
|
|
18225
18897
|
{ headers: { accept: "application/json" }, credentials: "omit", redirect: "manual", signal },
|
|
18226
18898
|
settings,
|
|
@@ -18255,7 +18927,7 @@ async function githubJson(settings, path, maxBytes, label) {
|
|
|
18255
18927
|
const controller = new AbortController();
|
|
18256
18928
|
const timeout = setTimeout(() => controller.abort(), githubRequestTimeoutMs);
|
|
18257
18929
|
try {
|
|
18258
|
-
const response = await
|
|
18930
|
+
const response = await pinnedFetch4(
|
|
18259
18931
|
`${githubApiBase}${path}`,
|
|
18260
18932
|
{
|
|
18261
18933
|
method: "GET",
|
|
@@ -18270,7 +18942,7 @@ async function githubJson(settings, path, maxBytes, label) {
|
|
|
18270
18942
|
{ label, requireHttpsOutsideLocalTest: true }
|
|
18271
18943
|
);
|
|
18272
18944
|
if (!response.ok) {
|
|
18273
|
-
await
|
|
18945
|
+
await readResponseTextBounded2(response, 8192, `${label} error`).catch(() => void 0);
|
|
18274
18946
|
if (response.status === 404) throw new Error(`${label} was not found or is not public`);
|
|
18275
18947
|
if (response.status === 403 || response.status === 429) {
|
|
18276
18948
|
throw new HTTPException28(429, {
|
|
@@ -18298,20 +18970,72 @@ function stringValue(value) {
|
|
|
18298
18970
|
|
|
18299
18971
|
// src/domain/vaults/index.ts
|
|
18300
18972
|
import { buildMissingSecretAskCard as buildMissingSecretAskCard2 } from "@opengeni/contracts";
|
|
18301
|
-
import { isVaultError, VaultError as VaultError3, vaultErrorFromDatabase } from "@opengeni/db";
|
|
18973
|
+
import { isVaultError as isVaultError3, VaultError as VaultError3, vaultErrorFromDatabase } from "@opengeni/db";
|
|
18302
18974
|
|
|
18303
18975
|
// src/domain/vaults/mapping.ts
|
|
18304
18976
|
import {
|
|
18305
18977
|
formatSecretReference as formatSecretReference2,
|
|
18306
|
-
secretNameKey
|
|
18978
|
+
secretNameKey as secretNameKey2,
|
|
18979
|
+
VaultProviderOperations as VaultProviderOperations2
|
|
18307
18980
|
} from "@opengeni/contracts";
|
|
18308
18981
|
import {
|
|
18309
18982
|
decryptEnvironmentValue as decryptEnvironmentValue2
|
|
18310
18983
|
} from "@opengeni/db";
|
|
18311
|
-
function
|
|
18312
|
-
|
|
18313
|
-
|
|
18314
|
-
|
|
18984
|
+
function withoutProviderWrites(operations) {
|
|
18985
|
+
return {
|
|
18986
|
+
...operations,
|
|
18987
|
+
createItemTypes: [],
|
|
18988
|
+
updateItem: false,
|
|
18989
|
+
removeField: false,
|
|
18990
|
+
archiveItem: false,
|
|
18991
|
+
archiveKind: "none",
|
|
18992
|
+
deleteItem: false,
|
|
18993
|
+
restoreItem: false,
|
|
18994
|
+
writeFiles: false,
|
|
18995
|
+
createTotp: false,
|
|
18996
|
+
updateTotp: false,
|
|
18997
|
+
createVault: false,
|
|
18998
|
+
restoreFieldHistory: false
|
|
18999
|
+
};
|
|
19000
|
+
}
|
|
19001
|
+
function intersectProviderOperations(left, right) {
|
|
19002
|
+
const archiveItem = left.archiveItem && right.archiveItem && left.archiveKind === right.archiveKind && left.archiveKind !== "none";
|
|
19003
|
+
return {
|
|
19004
|
+
createItemTypes: left.createItemTypes.filter(
|
|
19005
|
+
(type) => type !== "passkey" && right.createItemTypes.includes(type)
|
|
19006
|
+
),
|
|
19007
|
+
updateItem: left.updateItem && right.updateItem,
|
|
19008
|
+
removeField: left.removeField && right.removeField,
|
|
19009
|
+
archiveItem,
|
|
19010
|
+
archiveKind: archiveItem ? left.archiveKind : "none",
|
|
19011
|
+
deleteItem: false,
|
|
19012
|
+
restoreItem: left.restoreItem && right.restoreItem,
|
|
19013
|
+
readFiles: left.readFiles && right.readFiles,
|
|
19014
|
+
writeFiles: left.writeFiles && right.writeFiles,
|
|
19015
|
+
createTotp: left.createTotp && right.createTotp,
|
|
19016
|
+
updateTotp: left.updateTotp && right.updateTotp,
|
|
19017
|
+
createVault: left.createVault && right.createVault,
|
|
19018
|
+
restoreFieldHistory: left.restoreFieldHistory && right.restoreFieldHistory
|
|
19019
|
+
};
|
|
19020
|
+
}
|
|
19021
|
+
function storedProviderOperations(row) {
|
|
19022
|
+
if (row.source === "opengeni" || !row.providerOperations) return null;
|
|
19023
|
+
const parsed = VaultProviderOperations2.safeParse(row.providerOperations);
|
|
19024
|
+
return parsed.success ? providerOperationsForMode(parsed.data, row.mode) : null;
|
|
19025
|
+
}
|
|
19026
|
+
function providerOperationsForMode(operations, mode) {
|
|
19027
|
+
const normalized = intersectProviderOperations(operations, operations);
|
|
19028
|
+
return mode === "read_write" ? normalized : withoutProviderWrites(normalized);
|
|
19029
|
+
}
|
|
19030
|
+
function providerAllowsAnyWrite(operations) {
|
|
19031
|
+
if (!operations) return true;
|
|
19032
|
+
return operations.createItemTypes.length > 0 || operations.updateItem || operations.archiveItem;
|
|
19033
|
+
}
|
|
19034
|
+
function vaultCanWrite(row, authority) {
|
|
19035
|
+
if (row.mode !== "read_write" || !authority.write) return false;
|
|
19036
|
+
if (!providerAllowsAnyWrite(storedProviderOperations(row))) return false;
|
|
19037
|
+
if (row.ownerKind === "user") return row.ownedByCaller;
|
|
19038
|
+
return true;
|
|
18315
19039
|
}
|
|
18316
19040
|
function vaultCanManage(row, authority) {
|
|
18317
19041
|
switch (row.ownerKind) {
|
|
@@ -18325,7 +19049,11 @@ function vaultCanManage(row, authority) {
|
|
|
18325
19049
|
return authority.write;
|
|
18326
19050
|
}
|
|
18327
19051
|
}
|
|
18328
|
-
function toVaultSummary(row, authority) {
|
|
19052
|
+
function toVaultSummary(row, authority, options = {}) {
|
|
19053
|
+
const operations = storedProviderOperations(row);
|
|
19054
|
+
const freshness = providerVaultFreshness(row, options.now ?? /* @__PURE__ */ new Date(), {
|
|
19055
|
+
...options.refreshing ? { refreshing: true } : {}
|
|
19056
|
+
});
|
|
18329
19057
|
return {
|
|
18330
19058
|
id: row.id,
|
|
18331
19059
|
workspaceId: row.workspaceId,
|
|
@@ -18342,6 +19070,8 @@ function toVaultSummary(row, authority) {
|
|
|
18342
19070
|
lastSyncedAt: row.lastSyncedAt,
|
|
18343
19071
|
rateLimitedUntil: row.rateLimitedUntil,
|
|
18344
19072
|
providerConnectionId: row.providerConnectionId,
|
|
19073
|
+
...operations ? { providerOperations: operations } : {},
|
|
19074
|
+
...freshness ? { freshness } : {},
|
|
18345
19075
|
availability: row.availability,
|
|
18346
19076
|
isDefault: row.isDefault,
|
|
18347
19077
|
itemCount: Number(row.itemCount),
|
|
@@ -18381,7 +19111,7 @@ function vaultItemReferences(item) {
|
|
|
18381
19111
|
}
|
|
18382
19112
|
function usernameOf(row, key) {
|
|
18383
19113
|
const field = row.fields.find(
|
|
18384
|
-
(candidate) => candidate.kind === "text" &&
|
|
19114
|
+
(candidate) => candidate.kind === "text" && secretNameKey2(candidate.name) === "username"
|
|
18385
19115
|
);
|
|
18386
19116
|
return field ? decryptOrNull(key, field.valueEncrypted) : null;
|
|
18387
19117
|
}
|
|
@@ -18418,7 +19148,7 @@ function toVaultItemField(item, field, key) {
|
|
|
18418
19148
|
}
|
|
18419
19149
|
function toVaultItem(row, key) {
|
|
18420
19150
|
const textField = (name) => {
|
|
18421
|
-
const field = row.fields.find((candidate) =>
|
|
19151
|
+
const field = row.fields.find((candidate) => secretNameKey2(candidate.name) === name);
|
|
18422
19152
|
return field ? decryptOrNull(key, field.valueEncrypted) : null;
|
|
18423
19153
|
};
|
|
18424
19154
|
const credentialId = row.type === "passkey" ? textField("credentialid") ?? "" : "";
|
|
@@ -18441,68 +19171,25 @@ function toVaultItem(row, key) {
|
|
|
18441
19171
|
};
|
|
18442
19172
|
}
|
|
18443
19173
|
|
|
18444
|
-
// src/domain/vaults/vault-service.ts
|
|
18445
|
-
import { createHash as createHash6 } from "crypto";
|
|
18446
|
-
import {
|
|
18447
|
-
generatePassword as generatePassword2,
|
|
18448
|
-
isValidTotpInput as isValidTotpInput3,
|
|
18449
|
-
parseSecretReference,
|
|
18450
|
-
parseTotpInput as parseTotpInput2,
|
|
18451
|
-
secretNameKey as secretNameKey3,
|
|
18452
|
-
SecretReferenceError as SecretReferenceError2,
|
|
18453
|
-
totpCodeSync,
|
|
18454
|
-
totpRemainingMs,
|
|
18455
|
-
systemSecretClock,
|
|
18456
|
-
VAULT_LIMITS as VAULT_LIMITS3
|
|
18457
|
-
} from "@opengeni/contracts";
|
|
18458
|
-
import {
|
|
18459
|
-
archiveVaultItem,
|
|
18460
|
-
createVault,
|
|
18461
|
-
decryptEnvironmentValue as decryptEnvironmentValue3,
|
|
18462
|
-
deleteVault,
|
|
18463
|
-
deleteVaultItemPermanently,
|
|
18464
|
-
encryptEnvironmentValue,
|
|
18465
|
-
ensureDefaultVault,
|
|
18466
|
-
ensureTaskVault,
|
|
18467
|
-
getSession as getSession6,
|
|
18468
|
-
getSessionSecretsView,
|
|
18469
|
-
getVault,
|
|
18470
|
-
getVaultItem,
|
|
18471
|
-
listVaultFieldVersions,
|
|
18472
|
-
listVaultItems,
|
|
18473
|
-
listVaultsForSubject as listVaultsForSubject2,
|
|
18474
|
-
vaultItemCursor,
|
|
18475
|
-
moveVaultItem,
|
|
18476
|
-
readVaultFieldCiphertext,
|
|
18477
|
-
resolveSecretReferenceCandidates as resolveSecretReferenceCandidates2,
|
|
18478
|
-
restoreVaultItem,
|
|
18479
|
-
setSessionVaultPolicy,
|
|
18480
|
-
updateVault,
|
|
18481
|
-
upsertVaultItem,
|
|
18482
|
-
VaultError as VaultError2
|
|
18483
|
-
} from "@opengeni/db";
|
|
18484
|
-
import {
|
|
18485
|
-
createProviderCallBudget as createProviderCallBudget2,
|
|
18486
|
-
EMPTY_VAULT_PROVIDER_REGISTRY as EMPTY_VAULT_PROVIDER_REGISTRY2,
|
|
18487
|
-
isVaultProviderError as isVaultProviderError4
|
|
18488
|
-
} from "@opengeni/vaults";
|
|
18489
|
-
|
|
18490
19174
|
// src/domain/vaults/provider-writes.ts
|
|
19175
|
+
import { randomUUID } from "crypto";
|
|
18491
19176
|
import {
|
|
18492
19177
|
generatePassword,
|
|
18493
19178
|
isValidTotpInput as isValidTotpInput2,
|
|
18494
|
-
secretNameKey as
|
|
19179
|
+
secretNameKey as secretNameKey3
|
|
18495
19180
|
} from "@opengeni/contracts";
|
|
18496
19181
|
import {
|
|
18497
19182
|
getProviderVaultItemRef,
|
|
18498
19183
|
VaultError
|
|
18499
19184
|
} from "@opengeni/db";
|
|
18500
19185
|
import {
|
|
18501
|
-
isVaultProviderError as isVaultProviderError3
|
|
19186
|
+
isVaultProviderError as isVaultProviderError3,
|
|
19187
|
+
providerOperations as providerOperations3,
|
|
19188
|
+
readProviderItemInventory as readProviderItemInventory2
|
|
18502
19189
|
} from "@opengeni/vaults";
|
|
18503
19190
|
var TEXT_FIELD_NAMES = ["username", "user", "email", "login", "account"];
|
|
18504
19191
|
function defaultKind(name) {
|
|
18505
|
-
return TEXT_FIELD_NAMES.includes(
|
|
19192
|
+
return TEXT_FIELD_NAMES.includes(secretNameKey3(name)) ? "text" : "concealed";
|
|
18506
19193
|
}
|
|
18507
19194
|
function assertProviderVaultWritable(vault) {
|
|
18508
19195
|
if (vault.source === "opengeni") {
|
|
@@ -18515,11 +19202,43 @@ function assertProviderVaultWritable(vault) {
|
|
|
18515
19202
|
throw new VaultError("vault_not_found", "vault not found", { vaultId: vault.id });
|
|
18516
19203
|
}
|
|
18517
19204
|
}
|
|
18518
|
-
|
|
18519
|
-
|
|
18520
|
-
|
|
19205
|
+
var NO_PROVIDER_OPERATIONS = withoutProviderWrites({
|
|
19206
|
+
createItemTypes: [],
|
|
19207
|
+
updateItem: false,
|
|
19208
|
+
removeField: false,
|
|
19209
|
+
archiveItem: false,
|
|
19210
|
+
archiveKind: "none",
|
|
19211
|
+
deleteItem: false,
|
|
19212
|
+
restoreItem: false,
|
|
19213
|
+
readFiles: false,
|
|
19214
|
+
writeFiles: false,
|
|
19215
|
+
createTotp: false,
|
|
19216
|
+
updateTotp: false,
|
|
19217
|
+
createVault: false,
|
|
19218
|
+
restoreFieldHistory: false
|
|
19219
|
+
});
|
|
19220
|
+
function effectiveProviderOperations(vault, provider) {
|
|
19221
|
+
if (vault.source === "opengeni") return NO_PROVIDER_OPERATIONS;
|
|
19222
|
+
const stored2 = storedProviderOperations(vault);
|
|
19223
|
+
const live = provider ? providerOperations3(provider.capabilities()) : null;
|
|
19224
|
+
const operations = live && stored2 ? intersectProviderOperations(live, stored2) : stored2 ?? live;
|
|
19225
|
+
return operations ? providerOperationsForMode(operations, vault.mode) : NO_PROVIDER_OPERATIONS;
|
|
19226
|
+
}
|
|
19227
|
+
function refuse(vault, operation, message, details = {}) {
|
|
19228
|
+
throw new VaultError("vault_read_only", message, { vaultId: vault.id, operation, ...details });
|
|
19229
|
+
}
|
|
19230
|
+
function refuseProviderOperation(vault, operation) {
|
|
19231
|
+
const messages = {
|
|
19232
|
+
deleteItem: `Opengeni never permanently deletes items in ${vault.name}. Archive the item, or delete it in the provider itself.`,
|
|
19233
|
+
restoreItem: `Items archived in ${vault.name} are restored in the provider itself; the restored item appears after the next sync.`,
|
|
19234
|
+
restoreFieldHistory: `${vault.name} keeps field history in the provider; restore an earlier value there.`
|
|
19235
|
+
};
|
|
19236
|
+
return refuse(vault, operation, messages[operation]);
|
|
19237
|
+
}
|
|
19238
|
+
function planFieldWrites(vault, operations, creating, fields, existing) {
|
|
19239
|
+
return fields.map((input) => {
|
|
18521
19240
|
const current = existing.find(
|
|
18522
|
-
(field) => input.fieldId !== void 0 && field.id === input.fieldId ||
|
|
19241
|
+
(field) => input.fieldId !== void 0 && field.id === input.fieldId || secretNameKey3(field.name) === secretNameKey3(input.name)
|
|
18523
19242
|
);
|
|
18524
19243
|
const kind = input.kind ?? current?.kind ?? defaultKind(input.name);
|
|
18525
19244
|
const base = {
|
|
@@ -18527,24 +19246,51 @@ async function providerFieldWrites(fields, existing, resolveReference, generated
|
|
|
18527
19246
|
name: input.name,
|
|
18528
19247
|
kind
|
|
18529
19248
|
};
|
|
18530
|
-
if (input.remove) {
|
|
18531
|
-
writes.push({ ...base, remove: true });
|
|
18532
|
-
continue;
|
|
18533
|
-
}
|
|
18534
19249
|
if (input.restoreVersion !== void 0) {
|
|
18535
|
-
|
|
18536
|
-
"provider_unavailable",
|
|
18537
|
-
"connected vaults keep their history in the provider; restore it there",
|
|
18538
|
-
{ field: input.name }
|
|
18539
|
-
);
|
|
19250
|
+
return refuseProviderOperation(vault, "restoreFieldHistory");
|
|
18540
19251
|
}
|
|
18541
|
-
if (input.fileBase64 !== void 0 || kind === "file") {
|
|
19252
|
+
if (input.fileBase64 !== void 0 || kind === "file" || current?.kind === "file") {
|
|
18542
19253
|
throw new VaultError(
|
|
18543
19254
|
"provider_unavailable",
|
|
18544
19255
|
"files in connected vaults are read-only in Opengeni",
|
|
18545
|
-
{ field: input.name }
|
|
19256
|
+
{ field: input.name, vaultId: vault.id, operation: "writeFiles" }
|
|
18546
19257
|
);
|
|
18547
19258
|
}
|
|
19259
|
+
const totp = kind === "totp" || current?.kind === "totp";
|
|
19260
|
+
if (input.remove) {
|
|
19261
|
+
if (!operations.removeField) {
|
|
19262
|
+
refuse(vault, "removeField", `${vault.name} does not allow removing fields here`, {
|
|
19263
|
+
field: input.name
|
|
19264
|
+
});
|
|
19265
|
+
}
|
|
19266
|
+
if (totp && !operations.updateTotp) {
|
|
19267
|
+
refuse(vault, "updateTotp", `${vault.name} does not allow changing one-time passwords`, {
|
|
19268
|
+
field: input.name
|
|
19269
|
+
});
|
|
19270
|
+
}
|
|
19271
|
+
return { input, base, kind };
|
|
19272
|
+
}
|
|
19273
|
+
if (totp) {
|
|
19274
|
+
const allowed = creating ? operations.createTotp : operations.updateTotp;
|
|
19275
|
+
if (!allowed) {
|
|
19276
|
+
refuse(
|
|
19277
|
+
vault,
|
|
19278
|
+
creating ? "createTotp" : "updateTotp",
|
|
19279
|
+
`${vault.name} does not allow ${creating ? "saving" : "changing"} one-time passwords`,
|
|
19280
|
+
{ field: input.name }
|
|
19281
|
+
);
|
|
19282
|
+
}
|
|
19283
|
+
}
|
|
19284
|
+
return { input, base, kind };
|
|
19285
|
+
});
|
|
19286
|
+
}
|
|
19287
|
+
async function providerFieldValues(planned, resolveReference, generated) {
|
|
19288
|
+
const writes = [];
|
|
19289
|
+
for (const { input, base, kind } of planned) {
|
|
19290
|
+
if (input.remove) {
|
|
19291
|
+
writes.push({ ...base, remove: true });
|
|
19292
|
+
continue;
|
|
19293
|
+
}
|
|
18548
19294
|
let value;
|
|
18549
19295
|
if (input.generate !== void 0) {
|
|
18550
19296
|
value = generatePassword(input.generate);
|
|
@@ -18574,6 +19320,16 @@ function providerFailure(error) {
|
|
|
18574
19320
|
}
|
|
18575
19321
|
throw error;
|
|
18576
19322
|
}
|
|
19323
|
+
function mutationFailure(vault, error, what) {
|
|
19324
|
+
if (isVaultProviderError3(error) && error.code !== "provider_outcome_unknown") {
|
|
19325
|
+
return providerFailure(error);
|
|
19326
|
+
}
|
|
19327
|
+
throw new VaultError(
|
|
19328
|
+
"provider_outcome_unknown",
|
|
19329
|
+
`${vault.name} did not confirm ${what}. It may have been applied: check the vault before trying again.`,
|
|
19330
|
+
{ vaultId: vault.id }
|
|
19331
|
+
);
|
|
19332
|
+
}
|
|
18577
19333
|
async function providerFor(deps, vault) {
|
|
18578
19334
|
try {
|
|
18579
19335
|
return await deps.providers.forVault({
|
|
@@ -18584,38 +19340,68 @@ async function providerFor(deps, vault) {
|
|
|
18584
19340
|
return providerFailure(error);
|
|
18585
19341
|
}
|
|
18586
19342
|
}
|
|
19343
|
+
function mutationContext(deps, vault) {
|
|
19344
|
+
return {
|
|
19345
|
+
connectionId: vault.providerConnectionId,
|
|
19346
|
+
budget: deps.budget ?? { take() {
|
|
19347
|
+
} },
|
|
19348
|
+
cacheEpoch: vault.providerCacheEpoch ?? null,
|
|
19349
|
+
// Read-before-write must see the provider's current item, never a cached copy.
|
|
19350
|
+
forceRefresh: true,
|
|
19351
|
+
idempotencyKey: randomUUID()
|
|
19352
|
+
};
|
|
19353
|
+
}
|
|
19354
|
+
async function refreshAfterWrite(deps, vault) {
|
|
19355
|
+
try {
|
|
19356
|
+
const result = await syncLinkedProviderVault({
|
|
19357
|
+
db: deps.db,
|
|
19358
|
+
context: deps.context,
|
|
19359
|
+
vaultId: vault.id,
|
|
19360
|
+
providers: deps.providers,
|
|
19361
|
+
force: true,
|
|
19362
|
+
...deps.budget ? { budget: deps.budget } : {}
|
|
19363
|
+
});
|
|
19364
|
+
return result.status;
|
|
19365
|
+
} catch {
|
|
19366
|
+
return "error";
|
|
19367
|
+
}
|
|
19368
|
+
}
|
|
18587
19369
|
async function refreshAndFind(deps, vault, providerItemRef) {
|
|
18588
|
-
await
|
|
18589
|
-
db: deps.db,
|
|
18590
|
-
context: deps.context,
|
|
18591
|
-
vaultId: vault.id,
|
|
18592
|
-
providers: deps.providers,
|
|
18593
|
-
force: true,
|
|
18594
|
-
...deps.budget ? { budget: deps.budget } : {}
|
|
18595
|
-
});
|
|
19370
|
+
const sync = await refreshAfterWrite(deps, vault);
|
|
18596
19371
|
try {
|
|
18597
19372
|
const ref = await getProviderVaultItemRef(deps.db, deps.context, {
|
|
18598
19373
|
vaultId: vault.id,
|
|
18599
19374
|
providerItemRef
|
|
18600
19375
|
});
|
|
18601
|
-
return ref.itemId;
|
|
19376
|
+
return { status: "listed", itemId: ref.itemId };
|
|
18602
19377
|
} catch (error) {
|
|
18603
19378
|
if (error instanceof VaultError && error.code === "vault_item_not_found") {
|
|
18604
|
-
|
|
18605
|
-
"provider_unavailable",
|
|
18606
|
-
"saved in the provider; Opengeni will show it after the next sync",
|
|
18607
|
-
{ vaultId: vault.id }
|
|
18608
|
-
);
|
|
19379
|
+
return { status: "saved_not_listed", vaultId: vault.id, sync };
|
|
18609
19380
|
}
|
|
18610
19381
|
throw error;
|
|
18611
19382
|
}
|
|
18612
19383
|
}
|
|
18613
|
-
async function
|
|
19384
|
+
async function writeProviderVaultItemOutcome(deps, input) {
|
|
18614
19385
|
const { vault, existing, change } = input;
|
|
18615
19386
|
assertProviderVaultWritable(vault);
|
|
18616
|
-
|
|
19387
|
+
const type = change.type ?? existing?.type ?? "login";
|
|
19388
|
+
if (type === "passkey") {
|
|
18617
19389
|
throw new VaultError("vault_permission_denied", "passkeys live only in Opengeni vaults");
|
|
18618
19390
|
}
|
|
19391
|
+
const provider = await providerFor(deps, vault);
|
|
19392
|
+
const operations = effectiveProviderOperations(vault, provider);
|
|
19393
|
+
if (existing) {
|
|
19394
|
+
if (!operations.updateItem) {
|
|
19395
|
+
refuse(vault, "updateItem", `${vault.name} does not allow changing items here`);
|
|
19396
|
+
}
|
|
19397
|
+
} else if (!operations.createItemTypes.includes(type)) {
|
|
19398
|
+
refuse(
|
|
19399
|
+
vault,
|
|
19400
|
+
"createItem",
|
|
19401
|
+
operations.createItemTypes.length === 0 ? `${vault.name} does not allow creating items here` : `${vault.name} cannot hold ${type} items; it accepts ${operations.createItemTypes.join(", ")}`,
|
|
19402
|
+
{ itemType: type, allowedItemTypes: operations.createItemTypes }
|
|
19403
|
+
);
|
|
19404
|
+
}
|
|
18619
19405
|
let externalItemId;
|
|
18620
19406
|
let expectedVersion;
|
|
18621
19407
|
if (existing) {
|
|
@@ -18632,60 +19418,190 @@ async function writeProviderVaultItem(deps, input) {
|
|
|
18632
19418
|
externalItemId = ref.providerItemRef;
|
|
18633
19419
|
expectedVersion = ref.providerVersion;
|
|
18634
19420
|
}
|
|
18635
|
-
const
|
|
19421
|
+
const planned = planFieldWrites(
|
|
19422
|
+
vault,
|
|
19423
|
+
operations,
|
|
19424
|
+
existing === null,
|
|
18636
19425
|
change.fields ?? [],
|
|
18637
|
-
existing?.fields ?? []
|
|
18638
|
-
deps.resolveReference,
|
|
18639
|
-
input.generated ?? []
|
|
19426
|
+
existing?.fields ?? []
|
|
18640
19427
|
);
|
|
18641
|
-
const
|
|
19428
|
+
const fields = await providerFieldValues(planned, deps.resolveReference, input.generated ?? []);
|
|
19429
|
+
const name = change.name ?? existing?.name ?? "";
|
|
18642
19430
|
let written;
|
|
18643
19431
|
try {
|
|
18644
|
-
written = await provider.writeItem(
|
|
18645
|
-
{
|
|
18646
|
-
|
|
18647
|
-
|
|
19432
|
+
written = await provider.writeItem(mutationContext(deps, vault), vault.providerVaultRef ?? "", {
|
|
19433
|
+
...externalItemId ? { externalItemId, expectedVersion: expectedVersion ?? null } : {},
|
|
19434
|
+
name,
|
|
19435
|
+
type,
|
|
19436
|
+
websites: change.websites ?? existing?.websites ?? [],
|
|
19437
|
+
fields
|
|
19438
|
+
});
|
|
19439
|
+
} catch (error) {
|
|
19440
|
+
return mutationFailure(vault, error, existing ? `the change to ${name}` : `saving ${name}`);
|
|
19441
|
+
}
|
|
19442
|
+
return await refreshAndFind(deps, vault, written.externalItemId);
|
|
19443
|
+
}
|
|
19444
|
+
function savedNotListedError(vault, itemName, outcome) {
|
|
19445
|
+
return new VaultError(
|
|
19446
|
+
"provider_inventory_incomplete",
|
|
19447
|
+
`Saved ${itemName} in ${vault.name}, but Opengeni could not list it yet. Do not save it again: it appears after the next successful sync.`,
|
|
19448
|
+
{ saved: true, vaultId: outcome.vaultId, itemName, sync: outcome.sync }
|
|
19449
|
+
);
|
|
19450
|
+
}
|
|
19451
|
+
async function writeProviderVaultItem(deps, input) {
|
|
19452
|
+
const outcome = await writeProviderVaultItemOutcome(deps, input);
|
|
19453
|
+
if (outcome.status === "listed") return outcome.itemId;
|
|
19454
|
+
throw savedNotListedError(
|
|
19455
|
+
input.vault,
|
|
19456
|
+
input.change.name ?? input.existing?.name ?? "the item",
|
|
19457
|
+
outcome
|
|
19458
|
+
);
|
|
19459
|
+
}
|
|
19460
|
+
function providerArchiveKind(vault, provider) {
|
|
19461
|
+
const operations = effectiveProviderOperations(vault, provider);
|
|
19462
|
+
if (!operations.archiveItem) return null;
|
|
19463
|
+
return operations.archiveKind === "archive" || operations.archiveKind === "trash" ? operations.archiveKind : null;
|
|
19464
|
+
}
|
|
19465
|
+
async function assertProviderVaultAccepts(deps, vault, item) {
|
|
19466
|
+
assertProviderVaultWritable(vault);
|
|
19467
|
+
if (item.type === "passkey") {
|
|
19468
|
+
throw new VaultError("vault_permission_denied", "passkeys live only in Opengeni vaults");
|
|
19469
|
+
}
|
|
19470
|
+
if (item.fields.some((field) => field.kind === "file")) {
|
|
19471
|
+
throw new VaultError(
|
|
19472
|
+
"provider_unavailable",
|
|
19473
|
+
"files in connected vaults are read-only in Opengeni; copy the item into an Opengeni vault instead",
|
|
19474
|
+
{ vaultId: vault.id, operation: "writeFiles" }
|
|
19475
|
+
);
|
|
19476
|
+
}
|
|
19477
|
+
const operations = effectiveProviderOperations(vault, await providerFor(deps, vault));
|
|
19478
|
+
if (!operations.createItemTypes.includes(item.type)) {
|
|
19479
|
+
refuse(
|
|
19480
|
+
vault,
|
|
19481
|
+
"createItem",
|
|
19482
|
+
operations.createItemTypes.length === 0 ? `${vault.name} does not allow creating items here` : `${vault.name} cannot hold ${item.type} items; it accepts ${operations.createItemTypes.join(", ")}`,
|
|
19483
|
+
{ itemType: item.type, allowedItemTypes: operations.createItemTypes }
|
|
19484
|
+
);
|
|
19485
|
+
}
|
|
19486
|
+
if (item.fields.some((field) => field.kind === "totp") && !operations.createTotp) {
|
|
19487
|
+
refuse(vault, "createTotp", `${vault.name} does not allow saving one-time passwords`);
|
|
19488
|
+
}
|
|
19489
|
+
}
|
|
19490
|
+
async function assertProviderVaultReleases(deps, vault) {
|
|
19491
|
+
assertProviderVaultWritable(vault);
|
|
19492
|
+
const archiveKind = providerArchiveKind(vault, await providerFor(deps, vault));
|
|
19493
|
+
if (archiveKind === null) {
|
|
19494
|
+
refuse(
|
|
19495
|
+
vault,
|
|
19496
|
+
"archiveItem",
|
|
19497
|
+
`${vault.name} cannot archive the original recoverably, so the item cannot be moved out of it; copy it instead`
|
|
19498
|
+
);
|
|
19499
|
+
}
|
|
19500
|
+
return archiveKind;
|
|
19501
|
+
}
|
|
19502
|
+
async function observeProviderItemVersion(deps, input) {
|
|
19503
|
+
const { vault } = input;
|
|
19504
|
+
const provider = await providerFor(deps, vault);
|
|
19505
|
+
try {
|
|
19506
|
+
const inventory = await readProviderItemInventory2(
|
|
19507
|
+
provider,
|
|
18648
19508
|
{
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
}
|
|
19509
|
+
connectionId: vault.providerConnectionId,
|
|
19510
|
+
budget: deps.budget ?? { take() {
|
|
19511
|
+
} },
|
|
19512
|
+
cacheEpoch: vault.providerCacheEpoch ?? null,
|
|
19513
|
+
forceRefresh: true
|
|
19514
|
+
},
|
|
19515
|
+
vault.providerVaultRef ?? ""
|
|
18655
19516
|
);
|
|
19517
|
+
const item = inventory.items.find((meta) => meta.externalItemId === input.providerItemRef);
|
|
19518
|
+
if (!item) return { listed: false, consistent: inventory.consistent };
|
|
19519
|
+
return {
|
|
19520
|
+
listed: true,
|
|
19521
|
+
version: typeof item.version === "string" && item.version.length > 0 ? item.version : null
|
|
19522
|
+
};
|
|
18656
19523
|
} catch (error) {
|
|
18657
|
-
|
|
19524
|
+
if (isVaultProviderError3(error)) {
|
|
19525
|
+
throw new VaultError(vaultErrorCodeForProvider(error.code, "vault"), error.message, {
|
|
19526
|
+
vaultId: vault.id,
|
|
19527
|
+
...error.retryAfterMs ? { retryAfterMs: error.retryAfterMs } : {}
|
|
19528
|
+
});
|
|
19529
|
+
}
|
|
19530
|
+
throw error;
|
|
18658
19531
|
}
|
|
18659
|
-
return await refreshAndFind(deps, vault, written.externalItemId);
|
|
18660
19532
|
}
|
|
18661
19533
|
async function archiveProviderVaultItem(deps, input) {
|
|
18662
|
-
|
|
19534
|
+
const { vault } = input;
|
|
19535
|
+
assertProviderVaultWritable(vault);
|
|
18663
19536
|
const ref = await getProviderVaultItemRef(deps.db, deps.context, {
|
|
18664
|
-
vaultId:
|
|
19537
|
+
vaultId: vault.id,
|
|
18665
19538
|
itemId: input.itemId
|
|
18666
19539
|
});
|
|
18667
|
-
const provider = await providerFor(deps,
|
|
19540
|
+
const provider = await providerFor(deps, vault);
|
|
19541
|
+
const archiveKind = providerArchiveKind(vault, provider);
|
|
19542
|
+
if (archiveKind === null) {
|
|
19543
|
+
refuse(vault, "archiveItem", `${vault.name} does not allow archiving items here`);
|
|
19544
|
+
}
|
|
18668
19545
|
try {
|
|
18669
19546
|
await provider.archiveItem(
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
input.vault.providerVaultRef ?? "",
|
|
19547
|
+
mutationContext(deps, vault),
|
|
19548
|
+
vault.providerVaultRef ?? "",
|
|
18673
19549
|
ref.providerItemRef
|
|
18674
19550
|
);
|
|
18675
19551
|
} catch (error) {
|
|
18676
|
-
|
|
19552
|
+
mutationFailure(vault, error, "archiving the item");
|
|
18677
19553
|
}
|
|
18678
|
-
await
|
|
18679
|
-
|
|
18680
|
-
context: deps.context,
|
|
18681
|
-
vaultId: input.vault.id,
|
|
18682
|
-
providers: deps.providers,
|
|
18683
|
-
force: true,
|
|
18684
|
-
...deps.budget ? { budget: deps.budget } : {}
|
|
18685
|
-
});
|
|
19554
|
+
await refreshAfterWrite(deps, vault);
|
|
19555
|
+
return { archiveKind };
|
|
18686
19556
|
}
|
|
18687
19557
|
|
|
18688
19558
|
// src/domain/vaults/vault-service.ts
|
|
19559
|
+
import { createHash as createHash6 } from "crypto";
|
|
19560
|
+
import {
|
|
19561
|
+
generatePassword as generatePassword2,
|
|
19562
|
+
isValidTotpInput as isValidTotpInput3,
|
|
19563
|
+
parseSecretReference,
|
|
19564
|
+
parseTotpInput as parseTotpInput2,
|
|
19565
|
+
secretNameKey as secretNameKey4,
|
|
19566
|
+
SecretReferenceError as SecretReferenceError2,
|
|
19567
|
+
totpCodeSync,
|
|
19568
|
+
totpRemainingMs,
|
|
19569
|
+
systemSecretClock,
|
|
19570
|
+
VAULT_LIMITS as VAULT_LIMITS3
|
|
19571
|
+
} from "@opengeni/contracts";
|
|
19572
|
+
import {
|
|
19573
|
+
archiveVaultItem,
|
|
19574
|
+
createVault,
|
|
19575
|
+
decryptEnvironmentValue as decryptEnvironmentValue3,
|
|
19576
|
+
deleteVault,
|
|
19577
|
+
deleteVaultItemPermanently,
|
|
19578
|
+
encryptEnvironmentValue as encryptEnvironmentValue2,
|
|
19579
|
+
ensureDefaultVault,
|
|
19580
|
+
ensureTaskVault,
|
|
19581
|
+
getSession as getSession6,
|
|
19582
|
+
getSessionSecretsView,
|
|
19583
|
+
getProviderVaultItemRef as getProviderVaultItemRef2,
|
|
19584
|
+
getVault,
|
|
19585
|
+
getVaultItem,
|
|
19586
|
+
isVaultError as isVaultError2,
|
|
19587
|
+
listVaultFieldVersions,
|
|
19588
|
+
listVaultItems,
|
|
19589
|
+
listVaultsForSubject as listVaultsForSubject2,
|
|
19590
|
+
vaultItemCursor,
|
|
19591
|
+
moveVaultItem,
|
|
19592
|
+
readVaultFieldCiphertext,
|
|
19593
|
+
resolveSecretReferenceCandidates as resolveSecretReferenceCandidates2,
|
|
19594
|
+
restoreVaultItem,
|
|
19595
|
+
setSessionVaultPolicy,
|
|
19596
|
+
updateVault,
|
|
19597
|
+
upsertVaultItem,
|
|
19598
|
+
VaultError as VaultError2
|
|
19599
|
+
} from "@opengeni/db";
|
|
19600
|
+
import {
|
|
19601
|
+
createProviderCallBudget as createProviderCallBudget2,
|
|
19602
|
+
EMPTY_VAULT_PROVIDER_REGISTRY as EMPTY_VAULT_PROVIDER_REGISTRY2,
|
|
19603
|
+
isVaultProviderError as isVaultProviderError4
|
|
19604
|
+
} from "@opengeni/vaults";
|
|
18689
19605
|
function subjectContext(actor, workspaceId) {
|
|
18690
19606
|
return {
|
|
18691
19607
|
kind: "subject",
|
|
@@ -18722,13 +19638,13 @@ function requireKey(deps) {
|
|
|
18722
19638
|
return deps.encryptionKey;
|
|
18723
19639
|
}
|
|
18724
19640
|
function defaultFieldKind(name) {
|
|
18725
|
-
return ["username", "user", "email", "login", "account", "rpid"].includes(
|
|
19641
|
+
return ["username", "user", "email", "login", "account", "rpid"].includes(secretNameKey4(name)) ? "text" : "concealed";
|
|
18726
19642
|
}
|
|
18727
19643
|
function createVaultService(deps) {
|
|
18728
19644
|
const clock = deps.clock ?? systemSecretClock;
|
|
18729
19645
|
async function fieldWrite(input, existing, resolveReference, generated) {
|
|
18730
19646
|
const current = existing?.fields.find(
|
|
18731
|
-
(field) => input.fieldId !== void 0 && field.id === input.fieldId ||
|
|
19647
|
+
(field) => input.fieldId !== void 0 && field.id === input.fieldId || secretNameKey4(field.name) === secretNameKey4(input.name)
|
|
18732
19648
|
);
|
|
18733
19649
|
const name = current?.name ?? input.name;
|
|
18734
19650
|
const write = { name };
|
|
@@ -18781,7 +19697,7 @@ function createVaultService(deps) {
|
|
|
18781
19697
|
return {
|
|
18782
19698
|
...write,
|
|
18783
19699
|
kind,
|
|
18784
|
-
valueEncrypted:
|
|
19700
|
+
valueEncrypted: encryptEnvironmentValue2(requireKey(deps), plaintext),
|
|
18785
19701
|
valueSize: size
|
|
18786
19702
|
};
|
|
18787
19703
|
}
|
|
@@ -18848,6 +19764,11 @@ function createVaultService(deps) {
|
|
|
18848
19764
|
function key() {
|
|
18849
19765
|
return deps.encryptionKey;
|
|
18850
19766
|
}
|
|
19767
|
+
async function refuseForConnectedItem(context, itemId, operation) {
|
|
19768
|
+
const item = await getVaultItem(deps.db, context, { itemId });
|
|
19769
|
+
if (item.vaultSource === "opengeni") return;
|
|
19770
|
+
refuseProviderOperation(await getVault(deps.db, context, { vaultId: item.vaultId }), operation);
|
|
19771
|
+
}
|
|
18851
19772
|
async function moveAcrossProviders(input) {
|
|
18852
19773
|
const source = await getVaultItem(deps.db, input.context, { itemId: input.itemId });
|
|
18853
19774
|
const target = await getVault(deps.db, input.context, { vaultId: input.targetVaultId });
|
|
@@ -18873,6 +19794,46 @@ function createVaultService(deps) {
|
|
|
18873
19794
|
}
|
|
18874
19795
|
);
|
|
18875
19796
|
}
|
|
19797
|
+
if (target.source !== "opengeni") {
|
|
19798
|
+
await assertProviderVaultAccepts(input.providerWrites, target, source);
|
|
19799
|
+
}
|
|
19800
|
+
if (input.mode === "move" && source.vaultSource !== "opengeni") {
|
|
19801
|
+
await assertProviderVaultReleases(input.providerWrites, sourceVault);
|
|
19802
|
+
}
|
|
19803
|
+
let pinned = null;
|
|
19804
|
+
if (input.mode === "move" && source.vaultSource !== "opengeni") {
|
|
19805
|
+
const mirrored = await getProviderVaultItemRef2(deps.db, input.context, {
|
|
19806
|
+
vaultId: sourceVault.id,
|
|
19807
|
+
itemId: source.id
|
|
19808
|
+
});
|
|
19809
|
+
const { providerItemRef } = mirrored;
|
|
19810
|
+
const observed = await observeProviderItemVersion(input.providerWrites, {
|
|
19811
|
+
vault: sourceVault,
|
|
19812
|
+
providerItemRef
|
|
19813
|
+
});
|
|
19814
|
+
if (!observed.listed) {
|
|
19815
|
+
throw new VaultError2(
|
|
19816
|
+
"vault_item_not_found",
|
|
19817
|
+
`${source.name} is not listed in ${sourceVault.name} right now; sync the vault and try again`,
|
|
19818
|
+
{ itemId: source.id, vaultId: sourceVault.id }
|
|
19819
|
+
);
|
|
19820
|
+
}
|
|
19821
|
+
if (observed.version !== null) {
|
|
19822
|
+
if (mirrored.providerVersion !== observed.version || mirrored.generation !== source.generation) {
|
|
19823
|
+
throw new VaultError2(
|
|
19824
|
+
"provider_conflict",
|
|
19825
|
+
`${source.name} changed in ${sourceVault.name} since Opengeni last synced it, so its fields here may be out of date. Nothing was moved: sync ${sourceVault.name}, then move it again.`,
|
|
19826
|
+
{
|
|
19827
|
+
itemId: source.id,
|
|
19828
|
+
vaultId: sourceVault.id,
|
|
19829
|
+
mirroredVersion: mirrored.providerVersion,
|
|
19830
|
+
providerVersion: observed.version
|
|
19831
|
+
}
|
|
19832
|
+
);
|
|
19833
|
+
}
|
|
19834
|
+
pinned = { providerItemRef, version: observed.version };
|
|
19835
|
+
}
|
|
19836
|
+
}
|
|
18876
19837
|
const name = input.newName ?? source.name;
|
|
18877
19838
|
const fields = [];
|
|
18878
19839
|
for (const field of source.fields) {
|
|
@@ -18899,14 +19860,19 @@ function createVaultService(deps) {
|
|
|
18899
19860
|
...source.expiresAt ? { expiresAt: source.expiresAt } : {},
|
|
18900
19861
|
fields
|
|
18901
19862
|
};
|
|
18902
|
-
let created;
|
|
19863
|
+
let created = null;
|
|
19864
|
+
let notListed = null;
|
|
18903
19865
|
if (target.source !== "opengeni") {
|
|
18904
|
-
const
|
|
19866
|
+
const outcome = await writeProviderVaultItemOutcome(input.providerWrites, {
|
|
18905
19867
|
vault: target,
|
|
18906
19868
|
existing: null,
|
|
18907
19869
|
change
|
|
18908
19870
|
});
|
|
18909
|
-
|
|
19871
|
+
if (outcome.status === "listed") {
|
|
19872
|
+
created = await getVaultItem(deps.db, input.context, { itemId: outcome.itemId });
|
|
19873
|
+
} else {
|
|
19874
|
+
notListed = outcome;
|
|
19875
|
+
}
|
|
18910
19876
|
} else {
|
|
18911
19877
|
const write = await itemWrite(
|
|
18912
19878
|
change,
|
|
@@ -18921,17 +19887,54 @@ function createVaultService(deps) {
|
|
|
18921
19887
|
});
|
|
18922
19888
|
}
|
|
18923
19889
|
if (input.mode === "move") {
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
19890
|
+
try {
|
|
19891
|
+
if (source.vaultSource !== "opengeni") {
|
|
19892
|
+
if (pinned) await assertSourceUnchanged(input.providerWrites, sourceVault, pinned);
|
|
19893
|
+
await archiveProviderVaultItem(input.providerWrites, {
|
|
19894
|
+
vault: sourceVault,
|
|
19895
|
+
itemId: source.id
|
|
19896
|
+
});
|
|
19897
|
+
} else {
|
|
19898
|
+
await archiveVaultItem(deps.db, input.context, { itemId: source.id });
|
|
19899
|
+
}
|
|
19900
|
+
} catch (error) {
|
|
19901
|
+
const changed = isVaultError2(error) && error.details?.sourceChanged === true;
|
|
19902
|
+
throw new VaultError2(
|
|
19903
|
+
isVaultError2(error) ? error.code : "provider_outcome_unknown",
|
|
19904
|
+
changed ? `Saved ${name} in ${target.name} with the values that were read, but the original in ${sourceVault.name} ${error.message}, so it was kept. Do not move it again: compare the two, then update the copy or archive the original in ${sourceVault.name}.` : `Saved ${name} in ${target.name}, but archiving the original in ${sourceVault.name} did not complete${isVaultError2(error) ? ` (${error.message})` : ""}. Do not move it again: check ${sourceVault.name} and archive the original there if it is still listed.`,
|
|
19905
|
+
{
|
|
19906
|
+
saved: true,
|
|
19907
|
+
sourceArchived: false,
|
|
19908
|
+
...changed ? { sourceChanged: true } : {},
|
|
19909
|
+
vaultId: target.id,
|
|
19910
|
+
...created ? { itemId: created.id } : {},
|
|
19911
|
+
sourceItemId: source.id,
|
|
19912
|
+
sourceVaultId: sourceVault.id
|
|
19913
|
+
}
|
|
19914
|
+
);
|
|
18931
19915
|
}
|
|
18932
19916
|
}
|
|
19917
|
+
if (notListed) {
|
|
19918
|
+
const error = savedNotListedError(target, name, notListed);
|
|
19919
|
+
throw new VaultError2(error.code, error.message, {
|
|
19920
|
+
...error.details,
|
|
19921
|
+
...input.mode === "move" ? { sourceArchived: true } : {}
|
|
19922
|
+
});
|
|
19923
|
+
}
|
|
18933
19924
|
return created;
|
|
18934
19925
|
}
|
|
19926
|
+
async function assertSourceUnchanged(providerWrites, vault, pinned) {
|
|
19927
|
+
const now = await observeProviderItemVersion(providerWrites, {
|
|
19928
|
+
vault,
|
|
19929
|
+
providerItemRef: pinned.providerItemRef
|
|
19930
|
+
});
|
|
19931
|
+
if (now.listed && now.version === pinned.version) return;
|
|
19932
|
+
throw new VaultError2(
|
|
19933
|
+
now.listed ? "vault_generation_conflict" : "vault_item_not_found",
|
|
19934
|
+
now.listed ? "changed after its values were read" : "is no longer listed at the version that was read",
|
|
19935
|
+
{ sourceChanged: true, vaultId: vault.id }
|
|
19936
|
+
);
|
|
19937
|
+
}
|
|
18935
19938
|
function toItem(row) {
|
|
18936
19939
|
return toVaultItem(row, key());
|
|
18937
19940
|
}
|
|
@@ -18951,7 +19954,12 @@ function createVaultService(deps) {
|
|
|
18951
19954
|
});
|
|
18952
19955
|
const budget = createProviderCallBudget2(VAULT_LIMITS3.providerReadsPerAttempt);
|
|
18953
19956
|
const [value] = await provider.readFields(
|
|
18954
|
-
|
|
19957
|
+
// The durable epoch invalidates provider value caches after a refresh anywhere.
|
|
19958
|
+
{
|
|
19959
|
+
connectionId: read.providerConnectionId,
|
|
19960
|
+
budget,
|
|
19961
|
+
cacheEpoch: read.providerCacheEpoch ?? null
|
|
19962
|
+
},
|
|
18955
19963
|
read.providerVaultRef ?? "",
|
|
18956
19964
|
read.providerItemRef ?? "",
|
|
18957
19965
|
[read.providerFieldRef ?? ""]
|
|
@@ -19008,9 +20016,12 @@ function createVaultService(deps) {
|
|
|
19008
20016
|
const vaults = await listVaultsForSubject2(deps.db, principalContext(principal), {
|
|
19009
20017
|
workspaceId: principal.workspaceId
|
|
19010
20018
|
});
|
|
20019
|
+
return agentVaultFrom(vaults, nameOrId);
|
|
20020
|
+
}
|
|
20021
|
+
function agentVaultFrom(vaults, nameOrId) {
|
|
19011
20022
|
const byId = vaults.find((vault) => vault.id === nameOrId.toLowerCase());
|
|
19012
20023
|
if (byId) return byId;
|
|
19013
|
-
const matches = vaults.filter((vault) =>
|
|
20024
|
+
const matches = vaults.filter((vault) => secretNameKey4(vault.name) === secretNameKey4(nameOrId));
|
|
19014
20025
|
if (matches.length === 0) {
|
|
19015
20026
|
throw new VaultError2("vault_not_found", `no reachable vault is named ${nameOrId}`);
|
|
19016
20027
|
}
|
|
@@ -19237,12 +20248,14 @@ function createVaultService(deps) {
|
|
|
19237
20248
|
await archiveVaultItem(deps.db, context, { itemId });
|
|
19238
20249
|
},
|
|
19239
20250
|
async restoreItem(actor, workspaceId, itemId) {
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
);
|
|
20251
|
+
const context = subjectContext(actor, workspaceId);
|
|
20252
|
+
await refuseForConnectedItem(context, itemId, "restoreItem");
|
|
20253
|
+
return toItem(await restoreVaultItem(deps.db, context, { itemId }));
|
|
19243
20254
|
},
|
|
19244
20255
|
async deleteItemPermanently(actor, workspaceId, itemId) {
|
|
19245
|
-
|
|
20256
|
+
const context = subjectContext(actor, workspaceId);
|
|
20257
|
+
await refuseForConnectedItem(context, itemId, "deleteItem");
|
|
20258
|
+
await deleteVaultItemPermanently(deps.db, context, { itemId });
|
|
19246
20259
|
},
|
|
19247
20260
|
async moveItem(actor, workspaceId, itemId, req) {
|
|
19248
20261
|
const crossed = await moveAcrossProviders({
|
|
@@ -19282,6 +20295,12 @@ function createVaultService(deps) {
|
|
|
19282
20295
|
async restoreFieldVersion(actor, workspaceId, itemId, fieldId, version) {
|
|
19283
20296
|
const context = subjectContext(actor, workspaceId);
|
|
19284
20297
|
const existing = await getVaultItem(deps.db, context, { itemId });
|
|
20298
|
+
if (existing.vaultSource !== "opengeni") {
|
|
20299
|
+
refuseProviderOperation(
|
|
20300
|
+
await getVault(deps.db, context, { vaultId: existing.vaultId }),
|
|
20301
|
+
"restoreFieldHistory"
|
|
20302
|
+
);
|
|
20303
|
+
}
|
|
19285
20304
|
const field = existing.fields.find((candidate) => candidate.id === fieldId);
|
|
19286
20305
|
if (!field) throw new VaultError2("vault_field_not_found", "field not found", { fieldId });
|
|
19287
20306
|
return toItem(
|
|
@@ -19318,26 +20337,37 @@ function createVaultService(deps) {
|
|
|
19318
20337
|
let reachable = await listVaultsForSubject2(deps.db, context, {
|
|
19319
20338
|
workspaceId: principal.workspaceId
|
|
19320
20339
|
});
|
|
19321
|
-
|
|
19322
|
-
|
|
20340
|
+
let scoped = input.vault !== void 0 ? [agentVaultFrom(reachable, input.vault)] : reachable;
|
|
20341
|
+
let refreshing = /* @__PURE__ */ new Set();
|
|
20342
|
+
const connected = scoped.filter((vault) => vault.source !== "opengeni");
|
|
20343
|
+
if (deps.providers && connected.length > 0) {
|
|
20344
|
+
const report = await refreshLinkedVaults({
|
|
19323
20345
|
db: deps.db,
|
|
19324
20346
|
context,
|
|
19325
|
-
vaults:
|
|
20347
|
+
vaults: connected,
|
|
19326
20348
|
providers: deps.providers,
|
|
19327
|
-
maxAgeMs: LINKED_VAULT_SEARCH_MAX_AGE_MS
|
|
20349
|
+
maxAgeMs: LINKED_VAULT_SEARCH_MAX_AGE_MS,
|
|
20350
|
+
...input.refresh && input.cursor === void 0 ? { force: true } : {}
|
|
19328
20351
|
});
|
|
19329
|
-
|
|
20352
|
+
refreshing = new Set(report.pending);
|
|
20353
|
+
if (report.outcomes.some((outcome) => outcome.state === "done" || outcome.state === "failed")) {
|
|
19330
20354
|
reachable = await listVaultsForSubject2(deps.db, context, {
|
|
19331
20355
|
workspaceId: principal.workspaceId
|
|
19332
20356
|
});
|
|
20357
|
+
scoped = scoped.map((vault) => reachable.find((row) => row.id === vault.id) ?? vault);
|
|
19333
20358
|
}
|
|
19334
20359
|
}
|
|
20360
|
+
const checkedAt = /* @__PURE__ */ new Date();
|
|
20361
|
+
const freshnessOf = (vault) => providerVaultFreshness(vault, checkedAt, { refreshing: refreshing.has(vault.id) });
|
|
20362
|
+
const sources = scoped.flatMap((vault) => {
|
|
20363
|
+
const freshness = freshnessOf(vault);
|
|
20364
|
+
return freshness ? [{ vaultId: vault.id, freshness }] : [];
|
|
20365
|
+
});
|
|
20366
|
+
const complete = sources.every((source) => source.freshness.state === "current");
|
|
19335
20367
|
const byTier = (vaults2) => [...vaults2].sort(
|
|
19336
20368
|
(left, right) => left.tier - right.tier || left.name.localeCompare(right.name)
|
|
19337
20369
|
);
|
|
19338
|
-
const vaults = byTier(
|
|
19339
|
-
input.vault !== void 0 ? [await agentVault(principal, input.vault)] : reachable
|
|
19340
|
-
);
|
|
20370
|
+
const vaults = byTier(scoped);
|
|
19341
20371
|
const limit = input.limit ?? 20;
|
|
19342
20372
|
const filters = agentSearchFilterKey(input);
|
|
19343
20373
|
let startIndex = 0;
|
|
@@ -19403,15 +20433,21 @@ function createVaultService(deps) {
|
|
|
19403
20433
|
truncated: next !== null,
|
|
19404
20434
|
nextCursor: next === null ? null : encodeAgentSearchCursor(next),
|
|
19405
20435
|
...input.cursor === void 0 ? {
|
|
19406
|
-
vaults: byTier(reachable).map((vault) =>
|
|
19407
|
-
|
|
19408
|
-
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
20436
|
+
vaults: byTier(reachable).map((vault) => {
|
|
20437
|
+
const freshness = freshnessOf(vault);
|
|
20438
|
+
return {
|
|
20439
|
+
id: vault.id,
|
|
20440
|
+
name: vault.name,
|
|
20441
|
+
ownerKind: vault.ownerKind,
|
|
20442
|
+
source: vault.source,
|
|
20443
|
+
mode: vault.mode,
|
|
20444
|
+
itemCount: vault.itemCount,
|
|
20445
|
+
...freshness ? { freshness } : {}
|
|
20446
|
+
};
|
|
20447
|
+
})
|
|
20448
|
+
} : {},
|
|
20449
|
+
complete,
|
|
20450
|
+
sources
|
|
19415
20451
|
};
|
|
19416
20452
|
function agentSearchResult(vault, row) {
|
|
19417
20453
|
const summary = toVaultItemSummary(row, key());
|
|
@@ -19446,7 +20482,7 @@ function createVaultService(deps) {
|
|
|
19446
20482
|
q: input.item,
|
|
19447
20483
|
limit: VAULT_LIMITS3.searchResultsMax
|
|
19448
20484
|
});
|
|
19449
|
-
const existing = existingPage.items.find((item) =>
|
|
20485
|
+
const existing = existingPage.items.find((item) => secretNameKey4(item.name) === secretNameKey4(input.item)) ?? null;
|
|
19450
20486
|
if (input.mode === "create" && existing) {
|
|
19451
20487
|
throw new VaultError2(
|
|
19452
20488
|
"vault_item_name_conflict",
|
|
@@ -19554,7 +20590,7 @@ function createVaultService(deps) {
|
|
|
19554
20590
|
const current = await getVaultItem(deps.db, context, { itemId: target.itemId });
|
|
19555
20591
|
if (current.vaultSource !== "opengeni") {
|
|
19556
20592
|
const vault = await getVault(deps.db, context, { vaultId: current.vaultId });
|
|
19557
|
-
await archiveProviderVaultItem(
|
|
20593
|
+
const { archiveKind } = await archiveProviderVaultItem(
|
|
19558
20594
|
{
|
|
19559
20595
|
db: deps.db,
|
|
19560
20596
|
context,
|
|
@@ -19565,7 +20601,7 @@ function createVaultService(deps) {
|
|
|
19565
20601
|
);
|
|
19566
20602
|
return {
|
|
19567
20603
|
archived: true,
|
|
19568
|
-
restoreHint: `A person can restore ${current.name} from the
|
|
20604
|
+
restoreHint: `A person can restore ${current.name} from the ${archiveKind === "trash" ? "trash" : "archive"} in ${vault.name}'s provider; it reappears here after the next sync.`
|
|
19569
20605
|
};
|
|
19570
20606
|
}
|
|
19571
20607
|
const row = await archiveVaultItem(deps.db, context, {
|
|
@@ -20040,39 +21076,39 @@ import {
|
|
|
20040
21076
|
AutomationSessionTemplate,
|
|
20041
21077
|
OPENGENI_PR_REVIEW_SESSION_ROLE
|
|
20042
21078
|
} from "@opengeni/contracts";
|
|
20043
|
-
import { z as
|
|
21079
|
+
import { z as z3 } from "zod";
|
|
20044
21080
|
var PR_REVIEW_AUTOMATION_ADAPTER_ID = "source-control.pull-request.v1";
|
|
20045
|
-
var PrReviewSourceConfiguration =
|
|
20046
|
-
provider:
|
|
20047
|
-
providerBaseUrl:
|
|
20048
|
-
registrationId:
|
|
20049
|
-
webhookUsername:
|
|
21081
|
+
var PrReviewSourceConfiguration = z3.object({
|
|
21082
|
+
provider: z3.enum(["github", "gitlab", "azure_devops"]),
|
|
21083
|
+
providerBaseUrl: z3.string().url().max(2048),
|
|
21084
|
+
registrationId: z3.string().uuid(),
|
|
21085
|
+
webhookUsername: z3.string().min(1).max(512).nullable().default(null)
|
|
20050
21086
|
}).strict();
|
|
20051
|
-
var PrReviewTriggerParameters =
|
|
20052
|
-
registrationId:
|
|
20053
|
-
repositoryBindingId:
|
|
20054
|
-
provider:
|
|
20055
|
-
repositoryUri:
|
|
20056
|
-
repositoryFullName:
|
|
20057
|
-
providerRepositoryId:
|
|
20058
|
-
installationId:
|
|
20059
|
-
projectId:
|
|
20060
|
-
model:
|
|
20061
|
-
additionalInstructions:
|
|
21087
|
+
var PrReviewTriggerParameters = z3.object({
|
|
21088
|
+
registrationId: z3.string().uuid(),
|
|
21089
|
+
repositoryBindingId: z3.string().uuid(),
|
|
21090
|
+
provider: z3.enum(["github", "gitlab", "azure_devops"]),
|
|
21091
|
+
repositoryUri: z3.string().url().max(2048),
|
|
21092
|
+
repositoryFullName: z3.string().min(1).max(1024),
|
|
21093
|
+
providerRepositoryId: z3.string().min(1).max(512),
|
|
21094
|
+
installationId: z3.string().min(1).max(512).nullable(),
|
|
21095
|
+
projectId: z3.string().min(1).max(512).nullable(),
|
|
21096
|
+
model: z3.string().min(1).max(512).nullable(),
|
|
21097
|
+
additionalInstructions: z3.string().max(16384).nullable()
|
|
20062
21098
|
}).strict();
|
|
20063
|
-
var PrReviewEventPayload =
|
|
20064
|
-
provider:
|
|
20065
|
-
eventName:
|
|
20066
|
-
action:
|
|
20067
|
-
providerRepositoryId:
|
|
20068
|
-
installationId:
|
|
20069
|
-
projectId:
|
|
20070
|
-
pullRequestId:
|
|
20071
|
-
headSha:
|
|
20072
|
-
baseSha:
|
|
20073
|
-
headRef:
|
|
20074
|
-
baseRef:
|
|
20075
|
-
ignoredReason:
|
|
21099
|
+
var PrReviewEventPayload = z3.object({
|
|
21100
|
+
provider: z3.enum(["github", "gitlab", "azure_devops"]),
|
|
21101
|
+
eventName: z3.string().max(200),
|
|
21102
|
+
action: z3.string().max(200),
|
|
21103
|
+
providerRepositoryId: z3.string().max(512).nullable(),
|
|
21104
|
+
installationId: z3.string().max(512).nullable(),
|
|
21105
|
+
projectId: z3.string().max(512).nullable(),
|
|
21106
|
+
pullRequestId: z3.string().max(512).nullable(),
|
|
21107
|
+
headSha: z3.string().regex(/^[0-9a-f]{40}$/).nullable(),
|
|
21108
|
+
baseSha: z3.string().regex(/^[0-9a-f]{40}$/).nullable(),
|
|
21109
|
+
headRef: z3.string().max(1024).nullable(),
|
|
21110
|
+
baseRef: z3.string().max(1024).nullable(),
|
|
21111
|
+
ignoredReason: z3.string().max(200).nullable()
|
|
20076
21112
|
}).strict();
|
|
20077
21113
|
var OPENGENI_PR_REVIEW_SKILL = {
|
|
20078
21114
|
name: "pr-review",
|
|
@@ -21044,7 +22080,7 @@ import {
|
|
|
21044
22080
|
saveAgentLearningSettings as saveAgentLearningSettings2,
|
|
21045
22081
|
dbSql,
|
|
21046
22082
|
deleteScheduledTask,
|
|
21047
|
-
getConnectionMetadata as
|
|
22083
|
+
getConnectionMetadata as getConnectionMetadata6,
|
|
21048
22084
|
getEnrollment as getEnrollment3,
|
|
21049
22085
|
getLiveEnrollmentConnection as getLiveEnrollmentConnection3,
|
|
21050
22086
|
getKnowledgeSourceForSyncAuthority,
|
|
@@ -21070,7 +22106,7 @@ import {
|
|
|
21070
22106
|
import { HTTPException as HTTPException32 } from "hono/http-exception";
|
|
21071
22107
|
import { GOOGLE_DRIVE_PUBLICATION_SERVER_ID as GOOGLE_DRIVE_PUBLICATION_SERVER_ID3 } from "@opengeni/contracts/google-drive";
|
|
21072
22108
|
import { PERSONAL_GITHUB_CONNECTION_SURFACE_ID as PERSONAL_GITHUB_CONNECTION_SURFACE_ID3 } from "@opengeni/contracts/personal-github";
|
|
21073
|
-
import { isDeepStrictEqual as
|
|
22109
|
+
import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
|
|
21074
22110
|
|
|
21075
22111
|
// src/domain/scheduled-task-audience.ts
|
|
21076
22112
|
import {
|
|
@@ -21185,7 +22221,7 @@ async function resolveScheduledTaskVisibility(input) {
|
|
|
21185
22221
|
|
|
21186
22222
|
// src/domain/scheduled-task-account-audit.ts
|
|
21187
22223
|
import { recordAuditEvent as recordAuditEvent2 } from "@opengeni/db";
|
|
21188
|
-
import { isDeepStrictEqual as
|
|
22224
|
+
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
21189
22225
|
var SCHEDULED_TASK_ACCOUNT_CHOICES_AUDIT_ACTION = "scheduled_task.account_choices_changed";
|
|
21190
22226
|
function choicesOf(config) {
|
|
21191
22227
|
return {
|
|
@@ -21205,7 +22241,7 @@ function scheduledTaskAccountChoiceChanges(before, after) {
|
|
|
21205
22241
|
const next = choicesOf(after);
|
|
21206
22242
|
const changes = {};
|
|
21207
22243
|
for (const key of ["pinnedAccounts", "pinnedAccountsRequired", "connectors"]) {
|
|
21208
|
-
if (!
|
|
22244
|
+
if (!isDeepStrictEqual4(prior[key], next[key]))
|
|
21209
22245
|
changes[key] = { before: prior[key], after: next[key] };
|
|
21210
22246
|
}
|
|
21211
22247
|
return Object.keys(changes).length > 0 ? changes : null;
|
|
@@ -22193,7 +23229,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
|
|
|
22193
23229
|
throw new HTTPException32(422, { message: "Source ingestion uses an ordinary agent task" });
|
|
22194
23230
|
}
|
|
22195
23231
|
const knowledgeSource = input.payload.agentConfig?.knowledgeSource ?? scheduledTaskKnowledgeSource(input.existing);
|
|
22196
|
-
if (knowledgeSource && requestedKnowledgeSource && !
|
|
23232
|
+
if (knowledgeSource && requestedKnowledgeSource && !isDeepStrictEqual5(requestedKnowledgeSource, existingKnowledgeSource)) {
|
|
22197
23233
|
await validateKnowledgeSourceSyncAction({
|
|
22198
23234
|
db: input.db,
|
|
22199
23235
|
grant: input.grant,
|
|
@@ -22330,7 +23366,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
|
|
|
22330
23366
|
const pins = nextRunMode === "existing_session" ? [] : input.payload.pinnedAccounts ?? input.existing.agentConfig.pinnedAccounts ?? [];
|
|
22331
23367
|
const required = input.payload.pinnedAccountsRequired ?? input.existing.agentConfig.pinnedAccountsRequired === true;
|
|
22332
23368
|
const next = withScheduledTaskPins(base, pins, required);
|
|
22333
|
-
if (update.agentConfig || !
|
|
23369
|
+
if (update.agentConfig || !isDeepStrictEqual5(next, input.existing.agentConfig))
|
|
22334
23370
|
update.agentConfig = next;
|
|
22335
23371
|
}
|
|
22336
23372
|
const nextAgentConfig = {
|
|
@@ -22359,7 +23395,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
|
|
|
22359
23395
|
verifySlackChannel: input.verifySlackChannel
|
|
22360
23396
|
});
|
|
22361
23397
|
const authorityTargetChanged = nextRunMode !== input.existing.runMode || nextTargetSessionId !== input.existing.targetSessionId || input.payload.variableSetId !== void 0 && input.payload.variableSetId !== input.existing.variableSetId || input.payload.rigId !== void 0 && input.payload.rigId !== input.existing.rigId;
|
|
22362
|
-
const materialExecutionChange = authorityTargetChanged || pinsProvided || !
|
|
23398
|
+
const materialExecutionChange = authorityTargetChanged || pinsProvided || !isDeepStrictEqual5(nextAgentConfig, input.existing.agentConfig) || input.payload.action !== void 0 && !isDeepStrictEqual5(input.payload.action, input.existing.action) || input.payload.schedule !== void 0 && !isDeepStrictEqual5(input.payload.schedule, input.existing.schedule) || input.payload.overlapPolicy !== void 0 && input.payload.overlapPolicy !== input.existing.overlapPolicy || input.payload.metadata !== void 0 && !isDeepStrictEqual5(input.payload.metadata, input.existing.metadata) || input.existing.status === "paused" && input.payload.status === "active";
|
|
22363
23399
|
if (materialExecutionChange && nextRunMode !== "existing_session" && (!materializedReusable || nextAgentConfig.model !== input.existing.agentConfig.model)) {
|
|
22364
23400
|
const beforeUpdateCommit = workspaceCustomModelCommitGuard({
|
|
22365
23401
|
settings: input.settings,
|
|
@@ -22512,7 +23548,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
|
|
|
22512
23548
|
rigId: input.payload.rigId !== void 0 ? input.payload.rigId : input.existing.rigId,
|
|
22513
23549
|
agentConfig: update.agentConfig ?? input.existing.agentConfig
|
|
22514
23550
|
});
|
|
22515
|
-
if (!knowledgeSource && !(materializedReusable &&
|
|
23551
|
+
if (!knowledgeSource && !(materializedReusable && isDeepStrictEqual5(nextAgentConfig.machineTarget, input.existing.agentConfig.machineTarget) && nextAgentConfig.sandboxBackend === input.existing.agentConfig.sandboxBackend)) {
|
|
22516
23552
|
await validateScheduledTaskMachineTarget({
|
|
22517
23553
|
settings: input.settings,
|
|
22518
23554
|
db: input.db,
|
|
@@ -22590,7 +23626,7 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
22590
23626
|
message: "knowledge source authority or generation changed"
|
|
22591
23627
|
});
|
|
22592
23628
|
}
|
|
22593
|
-
const connection = await
|
|
23629
|
+
const connection = await getConnectionMetadata6(
|
|
22594
23630
|
input.db,
|
|
22595
23631
|
input.grant.workspaceId,
|
|
22596
23632
|
input.action.connection.connectionId,
|
|
@@ -22681,7 +23717,7 @@ async function requireUnchangedScheduledTaskForCompensation(db, expected) {
|
|
|
22681
23717
|
);
|
|
22682
23718
|
if (!current) throw new ScheduledTaskHeadChangedError();
|
|
22683
23719
|
const { deletedAt, ...task } = current;
|
|
22684
|
-
if (deletedAt !== null || !
|
|
23720
|
+
if (deletedAt !== null || !isDeepStrictEqual5(task, expected))
|
|
22685
23721
|
throw new ScheduledTaskHeadChangedError();
|
|
22686
23722
|
}
|
|
22687
23723
|
async function syncUpdatedScheduledTask(input) {
|
|
@@ -22748,7 +23784,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
22748
23784
|
},
|
|
22749
23785
|
toolsProvided: true
|
|
22750
23786
|
};
|
|
22751
|
-
const unchangedCreationField = (key) => input.retainedCreationConfig !== void 0 &&
|
|
23787
|
+
const unchangedCreationField = (key) => input.retainedCreationConfig !== void 0 && isDeepStrictEqual5(input.payload.agentConfig[key], input.retainedCreationConfig[key]);
|
|
22752
23788
|
const actor = scheduledTaskInitiatorForGrant(input.grant).actor;
|
|
22753
23789
|
const parent = actor && !existingChat ? await getSession8(input.db, input.workspaceId, actor.sessionId) : null;
|
|
22754
23790
|
if (!existingChat && actor && (!parent || parent.accountId !== input.grant.accountId)) {
|
|
@@ -22871,7 +23907,7 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
22871
23907
|
});
|
|
22872
23908
|
}
|
|
22873
23909
|
for (const required of preflight.requiredResources) {
|
|
22874
|
-
if (!agentConfig.resources.some((selected) =>
|
|
23910
|
+
if (!agentConfig.resources.some((selected) => isDeepStrictEqual5(selected, required))) {
|
|
22875
23911
|
throw new HTTPException32(422, {
|
|
22876
23912
|
message: "incidentTelemetryPreflight.requiredResources must be exact selected resources"
|
|
22877
23913
|
});
|
|
@@ -22971,7 +24007,7 @@ import {
|
|
|
22971
24007
|
ScheduledTaskHeadChangedError as ScheduledTaskHeadChangedError2
|
|
22972
24008
|
} from "@opengeni/db";
|
|
22973
24009
|
import { HTTPException as HTTPException33 } from "hono/http-exception";
|
|
22974
|
-
import { z as
|
|
24010
|
+
import { z as z4 } from "zod";
|
|
22975
24011
|
var ALWAYS_ATTACHED_CONNECTOR = "opengeni";
|
|
22976
24012
|
var PRESERVED_ACCOUNT_SURFACES = /* @__PURE__ */ new Set([
|
|
22977
24013
|
GOOGLE_DRIVE_PUBLICATION_SERVER_ID4,
|
|
@@ -22986,10 +24022,10 @@ function uniqueById(items) {
|
|
|
22986
24022
|
return items.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true)).slice(0, SCHEDULED_TASK_ACCESS_CONNECTORS_MAX);
|
|
22987
24023
|
}
|
|
22988
24024
|
function planScheduledTaskConnectors(input) {
|
|
22989
|
-
const
|
|
24025
|
+
const unavailable2 = input.taskTools.filter(
|
|
22990
24026
|
(tool) => tool.id !== ALWAYS_ATTACHED_CONNECTOR && !input.availableServerIds.has(tool.id)
|
|
22991
24027
|
);
|
|
22992
|
-
const unavailableIds = new Set(
|
|
24028
|
+
const unavailableIds = new Set(unavailable2.map((tool) => tool.id));
|
|
22993
24029
|
const kept = input.taskTools.filter((tool) => !unavailableIds.has(tool.id));
|
|
22994
24030
|
const keptIds = new Set(kept.map((tool) => tool.id));
|
|
22995
24031
|
const missing = input.defaultTools.filter(
|
|
@@ -22998,7 +24034,7 @@ function planScheduledTaskConnectors(input) {
|
|
|
22998
24034
|
return {
|
|
22999
24035
|
tools: mergeToolRefs3([...kept], [...missing]),
|
|
23000
24036
|
missing: uniqueById(missing.map((tool) => connector(tool.id, input.names))),
|
|
23001
|
-
unavailable: uniqueById(
|
|
24037
|
+
unavailable: uniqueById(unavailable2.map((tool) => connector(tool.id, input.names)))
|
|
23002
24038
|
};
|
|
23003
24039
|
}
|
|
23004
24040
|
function planScheduledTaskPinnedAccounts(input) {
|
|
@@ -23009,7 +24045,7 @@ function planScheduledTaskPinnedAccounts(input) {
|
|
|
23009
24045
|
)
|
|
23010
24046
|
);
|
|
23011
24047
|
const kept = [];
|
|
23012
|
-
const
|
|
24048
|
+
const unavailable2 = [];
|
|
23013
24049
|
const dropped = [];
|
|
23014
24050
|
for (const pin of input.pins) {
|
|
23015
24051
|
if (PRESERVED_ACCOUNT_SURFACES.has(pin.serverId)) {
|
|
@@ -23022,11 +24058,11 @@ function planScheduledTaskPinnedAccounts(input) {
|
|
|
23022
24058
|
}
|
|
23023
24059
|
kept.push(pin);
|
|
23024
24060
|
if (!available.has(JSON.stringify([pin.serverId, pin.connectionId])))
|
|
23025
|
-
|
|
24061
|
+
unavailable2.push(connector(pin.serverId, input.names));
|
|
23026
24062
|
}
|
|
23027
24063
|
return {
|
|
23028
24064
|
kept,
|
|
23029
|
-
unavailable: uniqueById(
|
|
24065
|
+
unavailable: uniqueById(unavailable2),
|
|
23030
24066
|
dropped: uniqueById(dropped)
|
|
23031
24067
|
};
|
|
23032
24068
|
}
|
|
@@ -23081,13 +24117,13 @@ function planScheduledTaskOpenGeniTools(input) {
|
|
|
23081
24117
|
policy: unchanged ? null : { firstPartyMcpTools: tools, firstPartyMcpPermissions: permissions }
|
|
23082
24118
|
};
|
|
23083
24119
|
}
|
|
23084
|
-
var AuthNeededFact =
|
|
23085
|
-
serverId:
|
|
23086
|
-
canonicalServerId:
|
|
23087
|
-
providerDomain:
|
|
24120
|
+
var AuthNeededFact = z4.object({
|
|
24121
|
+
serverId: z4.string().min(1),
|
|
24122
|
+
canonicalServerId: z4.string().min(1).optional(),
|
|
24123
|
+
providerDomain: z4.string().min(1),
|
|
23088
24124
|
reason: ToolAuthNeededReason,
|
|
23089
|
-
capability:
|
|
23090
|
-
setupRequest:
|
|
24125
|
+
capability: z4.unknown().optional(),
|
|
24126
|
+
setupRequest: z4.unknown().optional()
|
|
23091
24127
|
});
|
|
23092
24128
|
function scheduledTaskRunAccessFailures(events, names) {
|
|
23093
24129
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -27860,7 +28896,7 @@ import { createPinnedIntegrationTransport as createPinnedIntegrationTransport2 }
|
|
|
27860
28896
|
import { mcpToolRecommendsApproval } from "@opengeni/contracts";
|
|
27861
28897
|
import {
|
|
27862
28898
|
buildConnectionTokenResolver,
|
|
27863
|
-
getConnectionMetadata as
|
|
28899
|
+
getConnectionMetadata as getConnectionMetadata7,
|
|
27864
28900
|
listEnabledMcpCapabilityServers as listEnabledMcpCapabilityServers3,
|
|
27865
28901
|
listConnectorToolPermissionPolicies,
|
|
27866
28902
|
listChangedConnectorToolPermissions,
|
|
@@ -27913,7 +28949,7 @@ async function resolveTarget2(input) {
|
|
|
27913
28949
|
const server2 = settingsWithApiIntegrationServers({ ...input.settings, mcpServers: [] }, [
|
|
27914
28950
|
integration
|
|
27915
28951
|
]).mcpServers[0];
|
|
27916
|
-
const connection2 = integration.connectionRef?.connectionId ? await
|
|
28952
|
+
const connection2 = integration.connectionRef?.connectionId ? await getConnectionMetadata7(
|
|
27917
28953
|
input.db,
|
|
27918
28954
|
input.workspaceId,
|
|
27919
28955
|
integration.connectionRef.connectionId,
|
|
@@ -28269,6 +29305,7 @@ export {
|
|
|
28269
29305
|
EDITABLE_ARTIFACT_OPERATION_PROTOCOL_VERSION,
|
|
28270
29306
|
EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES,
|
|
28271
29307
|
EXTERNAL_FIRST_USE_MEMBER_PERMISSIONS,
|
|
29308
|
+
EXTERNAL_VAULT_CREDENTIAL_ROLE,
|
|
28272
29309
|
EditableArtifactAgentApplication,
|
|
28273
29310
|
EditableArtifactApplication,
|
|
28274
29311
|
EditableArtifactCausalChainError,
|
|
@@ -28309,6 +29346,7 @@ export {
|
|
|
28309
29346
|
KnowledgeVectorQueryRejectedError,
|
|
28310
29347
|
LINKED_VAULT_MISS_MAX_AGE_MS,
|
|
28311
29348
|
LINKED_VAULT_OPEN_WAIT_MS,
|
|
29349
|
+
LINKED_VAULT_REFRESH_TIMEOUT_MS,
|
|
28312
29350
|
LINKED_VAULT_REFRESH_WAIT_MS,
|
|
28313
29351
|
LINKED_VAULT_SEARCH_MAX_AGE_MS,
|
|
28314
29352
|
MAX_CHECKS_PER_RIG,
|
|
@@ -28378,6 +29416,7 @@ export {
|
|
|
28378
29416
|
SessionTenancyManagedHumanRequiredError,
|
|
28379
29417
|
SessionTenancyPersistenceOutcomeUnknownError,
|
|
28380
29418
|
SessionUsageLimitHttpError,
|
|
29419
|
+
StoredVaultProviderCredentials,
|
|
28381
29420
|
SystemEditableArtifactClock,
|
|
28382
29421
|
SystemEditableArtifactLiveClock,
|
|
28383
29422
|
SystemEditableArtifactLiveScheduler,
|
|
@@ -28413,6 +29452,7 @@ export {
|
|
|
28413
29452
|
agentAccessPermitsCrossTreeAccess,
|
|
28414
29453
|
agentConfigParentFor,
|
|
28415
29454
|
agentRunAdmissionDenial,
|
|
29455
|
+
aggregateProviderFreshness,
|
|
28416
29456
|
apiIntegrationsMatchingDelegations,
|
|
28417
29457
|
appendArchivedSessionEventsForRequest,
|
|
28418
29458
|
appendRigSetupCommand,
|
|
@@ -28455,6 +29495,7 @@ export {
|
|
|
28455
29495
|
automationRequestDigest,
|
|
28456
29496
|
availableMcpAccountBindings,
|
|
28457
29497
|
availableToolRefs,
|
|
29498
|
+
bindVaultProviderAuthority,
|
|
28458
29499
|
buildApiIntegrationMcpServers,
|
|
28459
29500
|
buildAutomationAcceptedExecution,
|
|
28460
29501
|
buildCapabilityCatalog,
|
|
@@ -28499,8 +29540,10 @@ export {
|
|
|
28499
29540
|
createCompanyProfileAgentAdminRouter,
|
|
28500
29541
|
createCompanyProfileDurableLearningAdapter,
|
|
28501
29542
|
createGitHubSkillSourceClient,
|
|
29543
|
+
createPinnedRemoteVaultTransport,
|
|
28502
29544
|
createPublicSkillSearchClient,
|
|
28503
29545
|
createRealtimeVoiceBilling,
|
|
29546
|
+
createRemoteExternalVaultFactories,
|
|
28504
29547
|
createRigForApi,
|
|
28505
29548
|
createRigVersionForApi,
|
|
28506
29549
|
createRunOnSecretResolver,
|
|
@@ -28566,6 +29609,7 @@ export {
|
|
|
28566
29609
|
editableArtifactTransactionId,
|
|
28567
29610
|
effectiveFirstPartyMcpToolCeiling,
|
|
28568
29611
|
effectiveOrganizationPrivacy,
|
|
29612
|
+
effectiveProviderOperations,
|
|
28569
29613
|
enableCapability,
|
|
28570
29614
|
enabledCapabilityMcpToolRefs,
|
|
28571
29615
|
encodeEditableArtifactBrokerHint,
|
|
@@ -28581,6 +29625,10 @@ export {
|
|
|
28581
29625
|
externalActorContinuationForAuthorization,
|
|
28582
29626
|
externalAttributionForAuthorization,
|
|
28583
29627
|
externalContinuationCommitAuthorizer,
|
|
29628
|
+
externalVaultConnectionDetails,
|
|
29629
|
+
externalVaultConnectionMetadata,
|
|
29630
|
+
externalVaultProviderDomain,
|
|
29631
|
+
externalVaultProviderForConnection,
|
|
28584
29632
|
fikenConnectionMetadata,
|
|
28585
29633
|
fileOwnerContextForAccess,
|
|
28586
29634
|
fileOwnerContextForAgent,
|
|
@@ -28637,6 +29685,7 @@ export {
|
|
|
28637
29685
|
installSkill,
|
|
28638
29686
|
integrationKeyForConnectProvider,
|
|
28639
29687
|
integrationSourceForOrganizationPolicy,
|
|
29688
|
+
intersectProviderOperations,
|
|
28640
29689
|
isAcceptedMimeType,
|
|
28641
29690
|
isAuthenticatedPersonAuthorization,
|
|
28642
29691
|
isAuthoritativeGitHubRepositorySelectionError,
|
|
@@ -28644,6 +29693,7 @@ export {
|
|
|
28644
29693
|
isDeveloperSetupAuthorization,
|
|
28645
29694
|
isDeveloperSetupDelegatedPermissionAllowed,
|
|
28646
29695
|
isDeveloperSetupGrant,
|
|
29696
|
+
isExternalVaultConnection,
|
|
28647
29697
|
isFikenConnection,
|
|
28648
29698
|
isOnePasswordConnection,
|
|
28649
29699
|
isOpenGeniSlackBotConnection,
|
|
@@ -28651,7 +29701,7 @@ export {
|
|
|
28651
29701
|
isTerminalVideoGenerationState,
|
|
28652
29702
|
isTrustedScheduledSlackBotSession,
|
|
28653
29703
|
isUserMember,
|
|
28654
|
-
isVaultError,
|
|
29704
|
+
isVaultError3 as isVaultError,
|
|
28655
29705
|
isVerifiedDelegatedHumanAuthorization,
|
|
28656
29706
|
isVerifiedOrganizationServiceAuthorization,
|
|
28657
29707
|
isWorkspaceCustomModelId,
|
|
@@ -28677,6 +29727,7 @@ export {
|
|
|
28677
29727
|
listSkills,
|
|
28678
29728
|
loadCoreRoutedClaudeCatalogReadiness,
|
|
28679
29729
|
loadCoreRoutedXaiCatalogReadiness,
|
|
29730
|
+
loadExternalVaultConnection,
|
|
28680
29731
|
loadOnePasswordConnectionToken,
|
|
28681
29732
|
loadWorkspaceClaudeSubscriptionReadiness,
|
|
28682
29733
|
loadWorkspaceCodexCatalogReadiness,
|
|
@@ -28731,6 +29782,7 @@ export {
|
|
|
28731
29782
|
parseSkillSource,
|
|
28732
29783
|
pausePrivateScheduledTaskAsAdmin,
|
|
28733
29784
|
pauseScheduledTaskAsAdmin,
|
|
29785
|
+
pendingVaultCredentialBundle,
|
|
28734
29786
|
personalAtlassianDelegationsFromVisibleConnections,
|
|
28735
29787
|
personalConnectionDelegationForServer,
|
|
28736
29788
|
personalConnectionDelegationSourceForGrant,
|
|
@@ -28771,10 +29823,12 @@ export {
|
|
|
28771
29823
|
promoteSetupAppendChange,
|
|
28772
29824
|
promoteVerifiedDefinitionEditChangeForApi,
|
|
28773
29825
|
proposeRigChangeForApi,
|
|
29826
|
+
providerOperationsForMode,
|
|
28774
29827
|
providerPrimaryFieldId,
|
|
28775
29828
|
providerSecretName,
|
|
28776
29829
|
providerSettingsForSessionSandboxRuntime,
|
|
28777
29830
|
providerSyncFailure,
|
|
29831
|
+
providerVaultFreshness,
|
|
28778
29832
|
provisionSandbox,
|
|
28779
29833
|
publishGovernedLearningEventToSlack,
|
|
28780
29834
|
readSessionAttachmentFiles,
|
|
@@ -28790,6 +29844,7 @@ export {
|
|
|
28790
29844
|
recordUserMessageAccepted,
|
|
28791
29845
|
recordWorkspaceUsage,
|
|
28792
29846
|
refreshCoreCodexModelEntitlements,
|
|
29847
|
+
refreshLinkedVaults,
|
|
28793
29848
|
refreshScheduledTaskAccess,
|
|
28794
29849
|
refreshStaleLinkedVaults,
|
|
28795
29850
|
registerAutomationAdapter,
|
|
@@ -28972,6 +30027,7 @@ export {
|
|
|
28972
30027
|
statusForVoiceInputError,
|
|
28973
30028
|
steerAgentSession,
|
|
28974
30029
|
steerHumanQueuePrompt,
|
|
30030
|
+
storedProviderOperations,
|
|
28975
30031
|
submitComposerDraftForRequest,
|
|
28976
30032
|
suggestCapabilityCatalogItems,
|
|
28977
30033
|
swapActiveSandbox,
|
|
@@ -29028,6 +30084,7 @@ export {
|
|
|
29028
30084
|
vaultErrorCodeForProvider,
|
|
29029
30085
|
vaultErrorFromDatabase,
|
|
29030
30086
|
vaultItemReferences,
|
|
30087
|
+
vaultRefreshScopeKey,
|
|
29031
30088
|
verifiedDelegatedHumanAuthorizationForRequest,
|
|
29032
30089
|
verifyDirectModelAccess,
|
|
29033
30090
|
verifyPrReviewWebhook,
|