@opengeni/core 3.1.3-canary.38890816741001 → 3.3.0

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.
Files changed (36) hide show
  1. package/dist/access/index.d.ts +13 -0
  2. package/dist/application/site-session-cancellation.d.ts +14 -0
  3. package/dist/dependencies.d.ts +3 -0
  4. package/dist/domain/insights-usage.d.ts +24 -1
  5. package/dist/domain/vaults/external-provider-connections.d.ts +126 -0
  6. package/dist/domain/vaults/index.d.ts +2 -1
  7. package/dist/domain/vaults/mapping.d.ts +18 -2
  8. package/dist/domain/vaults/provider-registry.d.ts +4 -1
  9. package/dist/domain/vaults/provider-sync.d.ts +135 -21
  10. package/dist/domain/vaults/provider-writes.d.ts +102 -12
  11. package/dist/domain/vaults/remote-provider-runtime.d.ts +7 -0
  12. package/dist/domain/vaults/secret-resolver.d.ts +29 -1
  13. package/dist/goal-admission.d.ts +2 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +1578 -394
  16. package/dist/index.js.map +1 -1
  17. package/package.json +13 -13
  18. package/src/access/index.ts +100 -0
  19. package/src/application/site-session-cancellation.ts +47 -0
  20. package/src/dependencies.ts +3 -0
  21. package/src/domain/file-owner.ts +1 -0
  22. package/src/domain/insights-usage.ts +20 -1
  23. package/src/domain/realtime-voice-billing.ts +4 -1
  24. package/src/domain/vaults/external-provider-connections.ts +395 -0
  25. package/src/domain/vaults/index.ts +8 -0
  26. package/src/domain/vaults/mapping.ts +95 -1
  27. package/src/domain/vaults/provider-registry.ts +29 -0
  28. package/src/domain/vaults/provider-sync.ts +492 -136
  29. package/src/domain/vaults/provider-writes.ts +415 -71
  30. package/src/domain/vaults/remote-provider-runtime.ts +129 -0
  31. package/src/domain/vaults/secret-resolver.ts +203 -33
  32. package/src/domain/vaults/vault-service.ts +226 -34
  33. package/src/domain/voice-input-billing.ts +5 -1
  34. package/src/index.ts +3 -0
  35. package/src/sandbox/fleet.ts +1 -0
  36. package/src/session-authorization.ts +21 -0
package/dist/index.js CHANGED
@@ -156,6 +156,7 @@ import {
156
156
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
157
157
  import {
158
158
  currentSessionRlsActorIdentityKey,
159
+ currentSiteViewerRlsContext,
159
160
  getSessionAuthorityProjection,
160
161
  getSessionAuthorizationTargetProjection,
161
162
  getSession as getSession2,
@@ -395,7 +396,8 @@ function relatedSessionAccessForAgentAttempt(actor, targetSessionId) {
395
396
  function sessionRlsActorForAuthorization(authorization) {
396
397
  return authorization.actor.kind === "agent_attempt" ? {
397
398
  subjectId: authorization.actor.subjectId,
398
- initiatingHumanSubjectId: authorization.actor.initiatingHumanSubjectId
399
+ initiatingHumanSubjectId: authorization.actor.initiatingHumanSubjectId,
400
+ agentRootSessionId: authorization.actor.callerRootSessionId
399
401
  } : { subjectId: authorization.actor.subjectId };
400
402
  }
401
403
  async function withResolvedSessionAuthorization(authorization, fn) {
@@ -426,6 +428,13 @@ async function requireSessionAuthorization(deps, grant, input) {
426
428
  if (!authority && input.surface !== "core") {
427
429
  throw new SessionAuthorizationDeniedError("not_found");
428
430
  }
431
+ if (authority?.siteAccessRootId) {
432
+ const viewer = currentSiteViewerRlsContext();
433
+ const resolved = await resolveSessionAuthorizationActor(deps.db, grant, reads);
434
+ const actor2 = resolved.actor;
435
+ const allowed = actor2.kind === "agent_attempt" ? authority.siteOwnerSubjectId !== null && authority.siteOwnerSubjectId === actor2.initiatingHumanSubjectId || actor2.callerRootSessionId === authority.rootSessionId && authority.siteAccessRootId === authority.rootSessionId : authority.siteOwnerSubjectId === actor2.subjectId || actor2.subjectId === `site:${authority.siteId}` && viewer?.siteId === authority.siteId && viewer.principal === authority.siteViewerPrincipal;
436
+ if (!allowed) throw new SessionAuthorizationDeniedError("not_found");
437
+ }
429
438
  const siteOrigin = currentSiteSessionOrigin();
430
439
  if (siteOrigin && authority && !input.operation.endsWith(".read")) {
431
440
  const session = await getSession2(deps.db, grant.workspaceId, input.sessionId);
@@ -1011,10 +1020,10 @@ import { requireSession as requireSession3, requireWorkspace as requireWorkspace
1011
1020
  // src/domain/capabilities.ts
1012
1021
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1013
1022
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1014
- import { isDeepStrictEqual } from "util";
1023
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1015
1024
  import { withLockedCapabilityInstallation } from "@opengeni/db/capability-reconciliation";
1016
1025
  import {
1017
- environmentsEncryptionKeyBytes,
1026
+ environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2,
1018
1027
  findIntegrationsOauthClient,
1019
1028
  parseIntegrationsOauthClientsJson
1020
1029
  } from "@opengeni/config";
@@ -1041,7 +1050,7 @@ import {
1041
1050
  encryptVariableSetValue,
1042
1051
  getCapabilityCatalogItem,
1043
1052
  getCapabilityInstallation,
1044
- getConnectionMetadata as getConnectionMetadata2,
1053
+ getConnectionMetadata as getConnectionMetadata3,
1045
1054
  readCodexCutoverDisposition,
1046
1055
  resolveSubscriptionCoreCodexAppsDesignation,
1047
1056
  rlsContextForWorkspace,
@@ -1099,14 +1108,263 @@ function preferredFikenConnection(connections) {
1099
1108
 
1100
1109
  // src/domain/vaults/provider-registry.ts
1101
1110
  import {
1111
+ getConnectionMetadata as getConnectionMetadata2,
1112
+ getProviderConnectionHomeWorkspace as getProviderConnectionHomeWorkspace2,
1113
+ loadConnectionCredentialForBroker as loadConnectionCredentialForBroker2
1114
+ } from "@opengeni/db";
1115
+ import {
1116
+ createVaultProviderRegistry,
1117
+ VaultProviderError as VaultProviderError2
1118
+ } from "@opengeni/vaults";
1119
+
1120
+ // src/domain/vaults/external-provider-connections.ts
1121
+ import { environmentsEncryptionKeyBytes } from "@opengeni/config";
1122
+ import { isDeepStrictEqual } from "util";
1123
+ import {
1124
+ ExternalVaultProviderKind,
1125
+ VaultProviderCredentials,
1126
+ VaultProviderOperations
1127
+ } from "@opengeni/contracts";
1128
+ import {
1129
+ encryptEnvironmentValue,
1102
1130
  getConnectionMetadata,
1103
1131
  getProviderConnectionHomeWorkspace,
1104
- loadConnectionCredentialForBroker
1132
+ loadConnectionCredentialForBroker,
1133
+ updateConnection
1105
1134
  } from "@opengeni/db";
1106
1135
  import {
1107
- createVaultProviderRegistry,
1136
+ providerOperations,
1108
1137
  VaultProviderError
1109
1138
  } from "@opengeni/vaults";
1139
+ import { z } from "zod";
1140
+ var EXTERNAL_VAULT_CREDENTIAL_ROLE = "external_vault_provider";
1141
+ var ExternalVaultConnectionDetails = z.object({
1142
+ version: z.literal(1),
1143
+ provider: ExternalVaultProviderKind,
1144
+ ownership: z.enum(["user", "workspace", "organization"]),
1145
+ accountId: z.string().min(1).max(300).nullable(),
1146
+ accountName: z.string().max(300).nullable(),
1147
+ label: z.string().max(120).nullable(),
1148
+ verifiedAt: z.string().datetime(),
1149
+ operations: VaultProviderOperations,
1150
+ verificationStatus: z.enum(["ready", "pending"]).optional()
1151
+ }).strict();
1152
+ function externalVaultProviderDomain(provider) {
1153
+ return `vault:${provider}`;
1154
+ }
1155
+ function externalVaultConnectionDetails(metadata) {
1156
+ if (metadata?.credentialRole !== EXTERNAL_VAULT_CREDENTIAL_ROLE) return null;
1157
+ const result = ExternalVaultConnectionDetails.safeParse(metadata.vaultProvider);
1158
+ return result.success ? result.data : null;
1159
+ }
1160
+ function isExternalVaultConnection(connection) {
1161
+ const details = externalVaultConnectionDetails(connection.metadata);
1162
+ return details !== null && connection.kind === "api_key" && connection.providerDomain === externalVaultProviderDomain(details.provider) && details.ownership === "user" === (connection.subjectId !== null);
1163
+ }
1164
+ var StoredVaultProviderCredentials = z.union([
1165
+ VaultProviderCredentials.options[0],
1166
+ VaultProviderCredentials.options[1],
1167
+ VaultProviderCredentials.options[2],
1168
+ z.object({
1169
+ provider: z.literal("keeper"),
1170
+ configuration: z.record(z.string().max(100), z.string().max(65536)).refine((value) => Object.keys(value).length <= 32)
1171
+ }).strict()
1172
+ ]);
1173
+ var PendingVaultCredentialEnvelope = z.object({
1174
+ vaultCredentialEnvelope: z.literal(1),
1175
+ current: StoredVaultProviderCredentials.nullable(),
1176
+ pending: StoredVaultProviderCredentials
1177
+ }).strict();
1178
+ function pendingVaultCredentialBundle(current, pending) {
1179
+ return { vaultCredentialEnvelope: 1, current, pending };
1180
+ }
1181
+ function externalVaultConnectionMetadata(details) {
1182
+ return {
1183
+ credentialRole: EXTERNAL_VAULT_CREDENTIAL_ROLE,
1184
+ vaultProvider: ExternalVaultConnectionDetails.parse(details)
1185
+ };
1186
+ }
1187
+ function unavailable() {
1188
+ return new VaultProviderError(
1189
+ "provider_auth_invalid",
1190
+ "This vault connection was removed or is not available to you. Reconnect it in Vaults."
1191
+ );
1192
+ }
1193
+ async function loadExternalVaultConnection(db, settings, input) {
1194
+ let connection = await getConnectionMetadata(
1195
+ db,
1196
+ input.workspaceId,
1197
+ input.connectionId,
1198
+ input.subjectId
1199
+ );
1200
+ if (!connection) {
1201
+ const home = await getProviderConnectionHomeWorkspace(
1202
+ db,
1203
+ input.workspaceId,
1204
+ input.connectionId,
1205
+ input.subjectId
1206
+ );
1207
+ if (home && home !== input.workspaceId)
1208
+ connection = await getConnectionMetadata(db, home, input.connectionId, input.subjectId);
1209
+ }
1210
+ if (!connection || !isExternalVaultConnection(connection)) throw unavailable();
1211
+ const details = externalVaultConnectionDetails(connection.metadata);
1212
+ if (details.provider !== input.provider) throw unavailable();
1213
+ if (connection.status !== "active" && !(input.allowPendingVerification && (connection.status === "needs_reauth" || connection.status === "error")))
1214
+ throw new VaultProviderError(
1215
+ "provider_auth_expired",
1216
+ "This vault connection needs to be reconnected."
1217
+ );
1218
+ if (connection.subjectId !== null && connection.subjectId !== input.subjectId)
1219
+ throw unavailable();
1220
+ const loaded = await loadConnectionCredentialForBroker(db, settings, {
1221
+ workspaceId: connection.workspaceId,
1222
+ connectionId: connection.id,
1223
+ providerDomain: externalVaultProviderDomain(input.provider),
1224
+ kind: "api_key",
1225
+ ...connection.subjectId !== null ? { allowSubjectOwned: true, subjectId: input.subjectId } : {}
1226
+ });
1227
+ 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)
1228
+ throw unavailable();
1229
+ const envelope = PendingVaultCredentialEnvelope.safeParse(loaded.credential);
1230
+ const raw = envelope.success ? input.allowPendingVerification ? envelope.data.pending : envelope.data.current : loaded.credential;
1231
+ const parsed = StoredVaultProviderCredentials.safeParse(raw);
1232
+ if (!parsed.success || parsed.data.provider !== input.provider) throw unavailable();
1233
+ if (envelope.success && (envelope.data.pending.provider !== input.provider || envelope.data.current !== null && envelope.data.current.provider !== input.provider))
1234
+ throw unavailable();
1235
+ return {
1236
+ connection,
1237
+ details,
1238
+ credentials: parsed.data,
1239
+ currentCredentials: envelope.success ? envelope.data.current : parsed.data,
1240
+ pendingCredentials: envelope.success ? envelope.data.pending : null
1241
+ };
1242
+ }
1243
+ function bindVaultProviderAuthority(provider, input) {
1244
+ async function context(ctx) {
1245
+ ctx.signal?.throwIfAborted();
1246
+ await input.revalidate();
1247
+ ctx.signal?.throwIfAborted();
1248
+ return { ...ctx, connectionId: input.connectionId, authority: { ...input.authority } };
1249
+ }
1250
+ return {
1251
+ id: provider.id,
1252
+ capabilities: () => ({
1253
+ ...provider.capabilities(),
1254
+ operations: providerOperations(provider.capabilities(), { operations: input.operations })
1255
+ }),
1256
+ validate: async (ctx) => provider.validate(await context(ctx)),
1257
+ listVaults: async (ctx) => provider.listVaults(await context(ctx)),
1258
+ listItems: async (ctx, vaultId) => provider.listItems(await context(ctx), vaultId),
1259
+ readFields: async (ctx, vaultId, itemId, fields) => provider.readFields(await context(ctx), vaultId, itemId, fields),
1260
+ writeItem: async (ctx, vaultId, item) => provider.writeItem(await context(ctx), vaultId, item),
1261
+ archiveItem: async (ctx, vaultId, itemId) => provider.archiveItem(await context(ctx), vaultId, itemId),
1262
+ ...provider.listVaultPage ? { listVaultPage: async (ctx, page) => provider.listVaultPage(await context(ctx), page) } : {},
1263
+ ...provider.listItemPage ? {
1264
+ listItemPage: async (ctx, vaultId, page) => provider.listItemPage(await context(ctx), vaultId, page)
1265
+ } : {},
1266
+ ...provider.createVault ? { createVault: async (ctx, value) => provider.createVault(await context(ctx), value) } : {}
1267
+ };
1268
+ }
1269
+ async function externalVaultProviderForConnection(input) {
1270
+ const loaded = await loadExternalVaultConnection(input.db, input.settings, input);
1271
+ let connection = loaded.connection;
1272
+ const prepared = await input.factory({
1273
+ credentials: loaded.credentials,
1274
+ connectionId: connection.id,
1275
+ signal: AbortSignal.timeout(3e4),
1276
+ operations: loaded.details.operations,
1277
+ persistCredentials: async (next) => {
1278
+ const parsed = StoredVaultProviderCredentials.safeParse(next);
1279
+ if (!parsed.success || parsed.data.provider !== loaded.details.provider) throw unavailable();
1280
+ const credentials = parsed.data;
1281
+ const key = environmentsEncryptionKeyBytes(input.settings);
1282
+ if (!key)
1283
+ throw new VaultProviderError(
1284
+ "provider_unavailable",
1285
+ "Vault credential encryption is unavailable."
1286
+ );
1287
+ const updated = await updateConnection(input.db, {
1288
+ workspaceId: connection.workspaceId,
1289
+ connectionId: connection.id,
1290
+ visibleToSubjectId: input.subjectId,
1291
+ expectedVersion: connection.version,
1292
+ credentialEncrypted: encryptEnvironmentValue(
1293
+ key,
1294
+ JSON.stringify(
1295
+ loaded.pendingCredentials ? pendingVaultCredentialBundle(credentials, loaded.pendingCredentials) : credentials
1296
+ )
1297
+ )
1298
+ });
1299
+ if (updated) {
1300
+ connection = updated;
1301
+ return;
1302
+ }
1303
+ const live = await loadExternalVaultConnection(input.db, input.settings, input);
1304
+ if (isDeepStrictEqual(
1305
+ rotationConnectionIdentity(live.connection),
1306
+ rotationConnectionIdentity(connection)
1307
+ ) && isDeepStrictEqual(live.currentCredentials, credentials) && isDeepStrictEqual(live.pendingCredentials, loaded.pendingCredentials)) {
1308
+ connection = live.connection;
1309
+ return;
1310
+ }
1311
+ throw new VaultProviderError(
1312
+ "provider_conflict",
1313
+ "This vault connection changed while its credentials were renewed."
1314
+ );
1315
+ }
1316
+ });
1317
+ if (prepared.provider.id !== input.provider) throw unavailable();
1318
+ return bindVaultProviderAuthority(prepared.provider, {
1319
+ connectionId: connection.id,
1320
+ authority: {
1321
+ accountId: connection.accountId,
1322
+ workspaceId: input.workspaceId,
1323
+ connectionWorkspaceId: connection.workspaceId,
1324
+ subjectId: input.subjectId,
1325
+ ...input.executionContext
1326
+ },
1327
+ operations: loaded.details.operations,
1328
+ revalidate: async () => {
1329
+ if (connection.workspaceId !== input.workspaceId) {
1330
+ const home = await getProviderConnectionHomeWorkspace(
1331
+ input.db,
1332
+ input.workspaceId,
1333
+ connection.id,
1334
+ input.subjectId
1335
+ );
1336
+ if (home !== connection.workspaceId) throw unavailable();
1337
+ }
1338
+ const live = await getConnectionMetadata(
1339
+ input.db,
1340
+ connection.workspaceId,
1341
+ connection.id,
1342
+ input.subjectId
1343
+ );
1344
+ if (!live || !isExternalVaultConnection(live) || live.status !== "active" || live.accountId !== connection.accountId || live.subjectId !== connection.subjectId || live.providerDomain !== connection.providerDomain)
1345
+ throw unavailable();
1346
+ if (live.version !== connection.version)
1347
+ throw new VaultProviderError(
1348
+ "provider_conflict",
1349
+ "This vault connection changed. Reload it before starting another operation."
1350
+ );
1351
+ }
1352
+ });
1353
+ }
1354
+ function rotationConnectionIdentity(connection) {
1355
+ const {
1356
+ version: _version,
1357
+ connectionAuthorityGeneration: _authorityGeneration,
1358
+ updatedAt: _updatedAt,
1359
+ updatedBySubjectId: _updatedBy,
1360
+ lastUsedAt: _lastUsedAt,
1361
+ lastRefreshAt: _lastRefreshAt,
1362
+ ...identity
1363
+ } = connection;
1364
+ return identity;
1365
+ }
1366
+
1367
+ // src/domain/vaults/provider-registry.ts
1110
1368
  function vaultErrorCodeForProvider(code, operation) {
1111
1369
  if (code === "provider_not_found") {
1112
1370
  return operation === "read" ? "vault_field_not_found" : "vault_not_found";
@@ -1143,19 +1401,19 @@ function onePasswordCredentialBundle(token) {
1143
1401
  return { token };
1144
1402
  }
1145
1403
  async function loadOnePasswordConnectionToken(db, settings, input) {
1146
- const unavailable = () => new VaultProviderError(
1404
+ const unavailable2 = () => new VaultProviderError2(
1147
1405
  "provider_auth_invalid",
1148
1406
  "The 1Password connection for this vault was removed or is not available to you; reconnect 1Password"
1149
1407
  );
1150
1408
  let homeWorkspaceId = input.workspaceId;
1151
- let connection = await getConnectionMetadata(
1409
+ let connection = await getConnectionMetadata2(
1152
1410
  db,
1153
1411
  input.workspaceId,
1154
1412
  input.connectionId,
1155
1413
  input.subjectId
1156
1414
  );
1157
1415
  if (!connection) {
1158
- const home = await getProviderConnectionHomeWorkspace(
1416
+ const home = await getProviderConnectionHomeWorkspace2(
1159
1417
  db,
1160
1418
  input.workspaceId,
1161
1419
  input.connectionId,
@@ -1163,18 +1421,18 @@ async function loadOnePasswordConnectionToken(db, settings, input) {
1163
1421
  );
1164
1422
  if (home && home !== input.workspaceId) {
1165
1423
  homeWorkspaceId = home;
1166
- connection = await getConnectionMetadata(db, home, input.connectionId, input.subjectId);
1424
+ connection = await getConnectionMetadata2(db, home, input.connectionId, input.subjectId);
1167
1425
  }
1168
1426
  }
1169
- if (!connection || !isOnePasswordConnection(connection)) throw unavailable();
1427
+ if (!connection || !isOnePasswordConnection(connection)) throw unavailable2();
1170
1428
  if (connection.status !== "active") {
1171
- throw new VaultProviderError(
1429
+ throw new VaultProviderError2(
1172
1430
  "provider_auth_expired",
1173
1431
  "The 1Password connection needs a new service account token"
1174
1432
  );
1175
1433
  }
1176
1434
  const personal = connection.subjectId !== null;
1177
- const credential = await loadConnectionCredentialForBroker(db, settings, {
1435
+ const credential = await loadConnectionCredentialForBroker2(db, settings, {
1178
1436
  workspaceId: homeWorkspaceId,
1179
1437
  connectionId: connection.id,
1180
1438
  providerDomain: ONEPASSWORD_PROVIDER_DOMAIN,
@@ -1182,7 +1440,7 @@ async function loadOnePasswordConnectionToken(db, settings, input) {
1182
1440
  ...personal ? { allowSubjectOwned: true, subjectId: connection.subjectId } : {}
1183
1441
  });
1184
1442
  const token = credential?.credential?.token;
1185
- if (!credential || typeof token !== "string" || !token) throw unavailable();
1443
+ if (!credential || typeof token !== "string" || !token) throw unavailable2();
1186
1444
  return { token, metadata: onePasswordConnectionMetadata(connection.metadata) };
1187
1445
  }
1188
1446
  function createWorkspaceVaultProviderRegistry(input) {
@@ -1192,7 +1450,7 @@ function createWorkspaceVaultProviderRegistry(input) {
1192
1450
  ...factory ? {
1193
1451
  async onepassword(connectionId) {
1194
1452
  if (!connectionId) {
1195
- throw new VaultProviderError(
1453
+ throw new VaultProviderError2(
1196
1454
  "provider_unavailable",
1197
1455
  "This 1Password vault has no connection"
1198
1456
  );
@@ -1209,7 +1467,29 @@ function createWorkspaceVaultProviderRegistry(input) {
1209
1467
  });
1210
1468
  }
1211
1469
  } : {},
1212
- ...host ? { host: async () => host } : {}
1470
+ ...host ? { host: async () => host } : {},
1471
+ ...Object.fromEntries(
1472
+ Object.entries(input.externalVaultProviders ?? {}).map(([provider, providerFactory]) => [
1473
+ provider,
1474
+ async (connectionId) => {
1475
+ if (!connectionId || !providerFactory)
1476
+ throw new VaultProviderError2(
1477
+ "provider_unavailable",
1478
+ "This vault has no available connection."
1479
+ );
1480
+ return externalVaultProviderForConnection({
1481
+ db: input.db,
1482
+ settings: input.settings,
1483
+ workspaceId: input.workspaceId,
1484
+ subjectId: input.subjectId,
1485
+ connectionId,
1486
+ provider,
1487
+ factory: providerFactory,
1488
+ ...input.executionContext ? { executionContext: input.executionContext } : {}
1489
+ });
1490
+ }
1491
+ ])
1492
+ )
1213
1493
  });
1214
1494
  }
1215
1495
 
@@ -1440,7 +1720,7 @@ async function prepareCapabilityEnable(input) {
1440
1720
  item,
1441
1721
  installationConfig
1442
1722
  );
1443
- if (!current || !isDeepStrictEqual(current, unchanged)) {
1723
+ if (!current || !isDeepStrictEqual2(current, unchanged)) {
1444
1724
  throw new HTTPException5(409, {
1445
1725
  message: "Capability installation changed; reload before reconciling"
1446
1726
  });
@@ -1494,7 +1774,7 @@ async function validateVerifiedMcpConnection(input, item) {
1494
1774
  const ref = input.payload.connectionRef;
1495
1775
  if (!proof || !ref || item.endpointUrl !== proof.endpointUrl || ref.resource !== proof.endpointUrl)
1496
1776
  throw new HTTPException5(409, { message: "MCP verification no longer matches the connection" });
1497
- const connection = await getConnectionMetadata2(
1777
+ const connection = await getConnectionMetadata3(
1498
1778
  input.db,
1499
1779
  input.workspaceId,
1500
1780
  proof.id,
@@ -1521,7 +1801,7 @@ async function unchangedMcpInstallation(input, item, requestedConfig) {
1521
1801
  const config = { ...requestedConfig, ...connectionRef ? { connectionRef } : {} };
1522
1802
  const { mcpConnectivity: _storedConnectivity, ...storedMetadata } = existing.metadata;
1523
1803
  const { mcpConnectivity: _requestedConnectivity, ...requestedMetadata } = input.payload.metadata;
1524
- if (!isDeepStrictEqual(config, storedConfig) || !isDeepStrictEqual(requestedMetadata, storedMetadata))
1804
+ if (!isDeepStrictEqual2(config, storedConfig) || !isDeepStrictEqual2(requestedMetadata, storedMetadata))
1525
1805
  return null;
1526
1806
  const provided = normalizedMcpCredentialHeaders(input.payload.headers);
1527
1807
  if (provided) {
@@ -1529,7 +1809,7 @@ async function unchangedMcpInstallation(input, item, requestedConfig) {
1529
1809
  { ...input, payload: { ...input.payload, headers: {} } },
1530
1810
  item
1531
1811
  );
1532
- if (!isDeepStrictEqual(provided, stored2)) return null;
1812
+ if (!isDeepStrictEqual2(provided, stored2)) return null;
1533
1813
  }
1534
1814
  return existing;
1535
1815
  }
@@ -1630,7 +1910,7 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
1630
1910
  assertNativeMcpConnectionRef(normalized);
1631
1911
  return normalized;
1632
1912
  }
1633
- let connection = normalized.connectionId ? await getConnectionMetadata2(
1913
+ let connection = normalized.connectionId ? await getConnectionMetadata3(
1634
1914
  input.db,
1635
1915
  input.workspaceId,
1636
1916
  normalized.connectionId,
@@ -1712,7 +1992,7 @@ function requiredCapabilityHeaders(metadata) {
1712
1992
  return value.filter((name) => typeof name === "string" && name.trim().length > 0).map((name) => name.trim());
1713
1993
  }
1714
1994
  function requireCapabilityHeaderEncryption(settings) {
1715
- const key = environmentsEncryptionKeyBytes(settings);
1995
+ const key = environmentsEncryptionKeyBytes2(settings);
1716
1996
  if (!key) {
1717
1997
  throw new HTTPException5(503, {
1718
1998
  message: "MCP credential headers require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
@@ -2001,7 +2281,7 @@ function settingsWithMcpCapabilityServers(settings, enabled) {
2001
2281
  if (enabled.length === 0) {
2002
2282
  return settings;
2003
2283
  }
2004
- const encryptionKey = environmentsEncryptionKeyBytes(settings);
2284
+ const encryptionKey = environmentsEncryptionKeyBytes2(settings);
2005
2285
  const existingIds = new Set(settings.mcpServers.map((server) => server.id));
2006
2286
  const dynamicServers = enabled.filter((server) => !existingIds.has(server.id)).flatMap((server) => {
2007
2287
  const headers = decryptedCapabilityHeaders(server, encryptionKey);
@@ -3040,7 +3320,7 @@ import {
3040
3320
  getSessionTurnPersonalConnectionDelegations,
3041
3321
  getSessionTurnMcpAccountBindings,
3042
3322
  getSessionTurnConnectionAccountIntent,
3043
- getConnectionMetadata as getConnectionMetadata3,
3323
+ getConnectionMetadata as getConnectionMetadata4,
3044
3324
  getSocialConnection,
3045
3325
  listConnectionsMetadata as listConnectionsMetadata2,
3046
3326
  listSocialConnections as listSocialConnections2,
@@ -3478,7 +3758,7 @@ async function listOwnConnectionMetadata(db, input) {
3478
3758
  );
3479
3759
  const connections = [];
3480
3760
  for (const account of accounts) {
3481
- const connection = await getConnectionMetadata3(
3761
+ const connection = await getConnectionMetadata4(
3482
3762
  db,
3483
3763
  account.originWorkspaceId,
3484
3764
  account.connectionId,
@@ -3941,7 +4221,7 @@ async function visibleMcpAccountConnections(db, input) {
3941
4221
  async function visibleMcpAccountConnectionsByIds(db, input) {
3942
4222
  const workspace = [];
3943
4223
  for (const connectionId of new Set(input.workspaceConnectionIds)) {
3944
- const connection = await getConnectionMetadata3(db, input.workspaceId, connectionId, null);
4224
+ const connection = await getConnectionMetadata4(db, input.workspaceId, connectionId, null);
3945
4225
  if (connection && connection.subjectId === null) workspace.push(connection);
3946
4226
  }
3947
4227
  const personalIds = new Set(input.personalConnectionIds);
@@ -4159,11 +4439,12 @@ import {
4159
4439
  listWorkspacesForSubject,
4160
4440
  findActiveApiKeyByHash,
4161
4441
  getWorkspaceGrant,
4442
+ subjectHasLiveWorkspaceAuthorityInScope,
4162
4443
  requireWorkspace,
4163
4444
  resolveNamedManagedPersonalWorkspaceGrant
4164
4445
  } from "@opengeni/db";
4165
4446
  import { HTTPException as HTTPException8 } from "hono/http-exception";
4166
- import { z } from "zod";
4447
+ import { z as z2 } from "zod";
4167
4448
 
4168
4449
  // src/access/service-initiator.ts
4169
4450
  import { ServiceTurnInitiatorContext } from "@opengeni/contracts";
@@ -4273,19 +4554,20 @@ function requireExplicitPermissionDelegation(grant, requested) {
4273
4554
  }
4274
4555
  }
4275
4556
  var canonicalLocalHumanContexts = /* @__PURE__ */ new WeakSet();
4276
- var delegatedHumanAuthorizationSchema = z.object({
4277
- organizationId: z.string().uuid(),
4278
- subjectId: z.string().max(1024).regex(/^user:[^\s\u0000-\u001f\u007f]+$/),
4279
- permissions: z.array(Permission),
4557
+ var delegatedHumanAuthorizationSchema = z2.object({
4558
+ organizationId: z2.string().uuid(),
4559
+ subjectId: z2.string().max(1024).regex(/^user:[^\s\u0000-\u001f\u007f]+$/),
4560
+ permissions: z2.array(Permission),
4280
4561
  workspaceScope: OrganizationWorkspaceScope
4281
4562
  });
4282
4563
  var delegatedHumanRequests = /* @__PURE__ */ new WeakMap();
4283
4564
  var delegatedHumanContexts = /* @__PURE__ */ new WeakMap();
4284
- var delegatedHumanProfileSchema = z.object({
4285
- id: z.string(),
4286
- name: z.string().nullable(),
4287
- email: z.string(),
4288
- emailVerified: z.boolean().optional()
4565
+ var verifiedSiteViewerProofs = /* @__PURE__ */ new WeakSet();
4566
+ var delegatedHumanProfileSchema = z2.object({
4567
+ id: z2.string(),
4568
+ name: z2.string().nullable(),
4569
+ email: z2.string(),
4570
+ emailVerified: z2.boolean().optional()
4289
4571
  });
4290
4572
  var delegatedHumanProfiles = /* @__PURE__ */ new WeakMap();
4291
4573
  var verifiedDelegatedHumanAuthorizations = /* @__PURE__ */ new WeakMap();
@@ -4310,6 +4592,27 @@ function stampDelegatedHumanAuthorization(request, input) {
4310
4592
  Object.freeze(proof.permissions);
4311
4593
  delegatedHumanRequests.set(request, Object.freeze(proof));
4312
4594
  }
4595
+ function stampSiteViewerSessionAuthorization(request, input) {
4596
+ if (input.subjectId.startsWith("user:")) {
4597
+ stampDelegatedHumanAuthorization(request, {
4598
+ ...input,
4599
+ workspaceScope: { kind: "selected", workspaceIds: [input.workspaceId] }
4600
+ });
4601
+ return;
4602
+ }
4603
+ if (delegatedHumanRequests.has(request) || accessResolvedRequests.has(request))
4604
+ throw new HTTPException8(403, { message: "request access authorization already resolved" });
4605
+ const parsed = delegatedHumanAuthorizationSchema.extend({
4606
+ subjectId: z2.string().max(1024).regex(/^external_user:[^\s\u0000-\u001f\u007f]+$/)
4607
+ }).parse({ ...input, workspaceScope: { kind: "selected", workspaceIds: [input.workspaceId] } });
4608
+ if (parsed.workspaceScope.kind !== "selected") throw new Error("Site workspace scope required");
4609
+ Object.freeze(parsed.permissions);
4610
+ Object.freeze(parsed.workspaceScope.workspaceIds);
4611
+ Object.freeze(parsed.workspaceScope);
4612
+ const proof = Object.freeze(parsed);
4613
+ verifiedSiteViewerProofs.add(proof);
4614
+ delegatedHumanRequests.set(request, proof);
4615
+ }
4313
4616
  var PERSONAL_API_KEY_TOKEN_PREFIX = "ogp_";
4314
4617
  async function stampPersonalApiKeyRequest(request, deps) {
4315
4618
  const authorization = request.headers.get("authorization");
@@ -4991,6 +5294,55 @@ async function resolveDelegatedHumanAccessContext(deps, proof) {
4991
5294
  message: "delegated humans require native organization access"
4992
5295
  });
4993
5296
  }
5297
+ if (verifiedSiteViewerProofs.has(proof)) {
5298
+ const workspaceId = proof.workspaceScope.kind === "selected" && proof.workspaceScope.workspaceIds.length === 1 ? proof.workspaceScope.workspaceIds[0] : null;
5299
+ if (!workspaceId)
5300
+ throw new HTTPException8(403, { message: "Site viewer workspace unavailable" });
5301
+ const grant = await withWorkspaceSubjectRls(
5302
+ deps.db,
5303
+ workspaceId,
5304
+ proof.subjectId,
5305
+ async (tx) => {
5306
+ if (!await subjectHasLiveWorkspaceAuthorityInScope(tx, {
5307
+ accountId: proof.organizationId,
5308
+ workspaceId,
5309
+ subjectId: proof.subjectId
5310
+ }))
5311
+ return null;
5312
+ return getWorkspaceGrant(tx, proof.subjectId, workspaceId, {
5313
+ principalKind: "human_session"
5314
+ });
5315
+ }
5316
+ );
5317
+ if (!grant || grant.accountId !== proof.organizationId)
5318
+ throw new HTTPException8(403, { message: "Site viewer membership unavailable" });
5319
+ const bounded = Object.freeze({
5320
+ ...grant,
5321
+ permissionMode: "explicit",
5322
+ permissions: Object.freeze(
5323
+ explicitPermissions(
5324
+ Permission.options.filter(
5325
+ (permission) => hasPermission(grant.permissions, permission) && hasPermission(proof.permissions, permission, "explicit")
5326
+ )
5327
+ )
5328
+ )
5329
+ });
5330
+ const accountGrant2 = Object.freeze({
5331
+ accountId: proof.organizationId,
5332
+ subjectId: proof.subjectId,
5333
+ permissions: Object.freeze([])
5334
+ });
5335
+ const context2 = Object.freeze({
5336
+ mode: "managed",
5337
+ subjectId: proof.subjectId,
5338
+ accountGrants: Object.freeze([accountGrant2]),
5339
+ workspaceGrants: Object.freeze([bounded]),
5340
+ defaultAccountId: proof.organizationId,
5341
+ defaultWorkspaceId: workspaceId
5342
+ });
5343
+ delegatedHumanContexts.set(context2, proof);
5344
+ return context2;
5345
+ }
4994
5346
  const userId = proof.subjectId.slice("user:".length);
4995
5347
  const profiles = await getManagedUserProfilesByIds(deps.db, [userId]);
4996
5348
  const parsedProfile = delegatedHumanProfileSchema.safeParse(
@@ -5525,6 +5877,7 @@ async function fileOwnerContextForAgent(deps, grant, permission) {
5525
5877
  },
5526
5878
  subjectId: grant.subjectId,
5527
5879
  initiatingHumanSubjectId: attempt.initiatingHumanSubjectId,
5880
+ agentRootSessionId: attempt.callerRootSessionId,
5528
5881
  privateFileOwnerSubjectId: snapshot.defaultScope === "personal" ? snapshot.subjectId : null
5529
5882
  };
5530
5883
  }
@@ -6494,7 +6847,7 @@ function sessionUsageLimitRefusalFromError(error) {
6494
6847
  }
6495
6848
 
6496
6849
  // src/sandbox/fleet.ts
6497
- import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
6850
+ import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
6498
6851
  import {
6499
6852
  agentCapabilityEnabled
6500
6853
  } from "@opengeni/contracts";
@@ -7026,6 +7379,7 @@ import {
7026
7379
  isValidTotpInput,
7027
7380
  parseTotpInput,
7028
7381
  referenceKey,
7382
+ secretNameKey,
7029
7383
  SecretReferenceError,
7030
7384
  VAULT_LIMITS as VAULT_LIMITS2
7031
7385
  } from "@opengeni/contracts";
@@ -7049,14 +7403,42 @@ import {
7049
7403
  } from "@opengeni/contracts";
7050
7404
  import {
7051
7405
  getProviderVaultSyncTarget,
7052
- setVaultSyncStatus,
7053
- upsertProviderVaultItems
7406
+ isVaultError,
7407
+ setVaultSyncStatus
7054
7408
  } from "@opengeni/db";
7055
7409
  import {
7056
7410
  isVaultProviderError,
7057
- VaultProviderError as VaultProviderError2
7411
+ providerOperations as providerOperations2,
7412
+ readProviderItemInventory,
7413
+ readProviderVaultInventory,
7414
+ VaultProviderError as VaultProviderError3
7058
7415
  } from "@opengeni/vaults";
7059
7416
  var PROVIDER_RATE_LIMIT_DEFAULT_BACKOFF_MS = 15 * 6e4;
7417
+ function incompleteInventory() {
7418
+ return new VaultProviderError3(
7419
+ "provider_inventory_incomplete",
7420
+ `This vault has more than ${VAULT_LIMITS.itemsPerVault} items, more than Opengeni mirrors. The previous inventory was kept.`
7421
+ );
7422
+ }
7423
+ function oversizedItem() {
7424
+ return new VaultProviderError3(
7425
+ "provider_inventory_incomplete",
7426
+ `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.`
7427
+ );
7428
+ }
7429
+ function unprovenInventory(listing) {
7430
+ return new VaultProviderError3(
7431
+ "provider_inventory_incomplete",
7432
+ 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."
7433
+ );
7434
+ }
7435
+ function assertMirrorable(item) {
7436
+ if (!Array.isArray(item.fields) || !Array.isArray(item.websites) || item.fields.length > VAULT_LIMITS.fieldsPerItem || item.websites.length > VAULT_LIMITS.websitesPerItem || item.websites.some(
7437
+ (website) => typeof website !== "string" || website.length > VAULT_LIMITS.websiteMaxChars
7438
+ )) {
7439
+ throw oversizedItem();
7440
+ }
7441
+ }
7060
7442
  var FORBIDDEN_NAME_CHARACTERS = /[/{}#\\\u0000-\u001f\u007f-\u009f]/g;
7061
7443
  function nameKey(name) {
7062
7444
  return name.normalize("NFC").trim().toLowerCase();
@@ -7103,15 +7485,16 @@ function providerPrimaryFieldId(item) {
7103
7485
  return candidates.find((field) => field.kind === "concealed")?.externalFieldId ?? null;
7104
7486
  }
7105
7487
  function planProviderMirror(items, contentVersion) {
7488
+ if (items.length > VAULT_LIMITS.itemsPerVault) throw incompleteInventory();
7489
+ for (const item of items) assertMirrorable(item);
7106
7490
  const ordered = [...items].sort(
7107
7491
  (a, b) => nameKey(a.name).localeCompare(nameKey(b.name)) || a.externalItemId.localeCompare(b.externalItemId)
7108
7492
  );
7109
- const kept = ordered.slice(0, VAULT_LIMITS.itemsPerVault);
7110
7493
  const takenItems = /* @__PURE__ */ new Set();
7111
- const mirrored = kept.map((item) => {
7494
+ const mirrored = ordered.map((item) => {
7112
7495
  const name = uniqueProviderName(providerSecretName(item.name), takenItems);
7113
7496
  const takenFields = /* @__PURE__ */ new Set();
7114
- const fields = item.fields.slice(0, VAULT_LIMITS.fieldsPerItem).map((field, position) => {
7497
+ const fields = item.fields.map((field, position) => {
7115
7498
  let fieldName = providerSecretName(
7116
7499
  field.name,
7117
7500
  field.kind === "totp" ? "one-time password" : "field"
@@ -7129,7 +7512,7 @@ function planProviderMirror(items, contentVersion) {
7129
7512
  name,
7130
7513
  providerName: item.name,
7131
7514
  type: item.type,
7132
- websites: item.websites.filter((website) => website.length <= VAULT_LIMITS.websiteMaxChars).slice(0, VAULT_LIMITS.websitesPerItem),
7515
+ websites: [...item.websites],
7133
7516
  fields,
7134
7517
  primaryExternalFieldId: providerPrimaryFieldId({ type: item.type, fields: item.fields }),
7135
7518
  hasTotp: item.fields.some((field) => field.kind === "totp"),
@@ -7137,7 +7520,32 @@ function planProviderMirror(items, contentVersion) {
7137
7520
  updatedAt: item.updatedAt
7138
7521
  };
7139
7522
  });
7140
- return { contentVersion, items: mirrored, truncated: ordered.length - kept.length };
7523
+ return { contentVersion, items: mirrored, truncated: 0 };
7524
+ }
7525
+ function validTime(value) {
7526
+ if (typeof value !== "string" || value.length > 64) return null;
7527
+ const time = Date.parse(value);
7528
+ return Number.isFinite(time) ? time : null;
7529
+ }
7530
+ function aggregateProviderFreshness(observations, checkedAt) {
7531
+ if (observations.length === 0) return null;
7532
+ let cache = false;
7533
+ let unknown = false;
7534
+ let unknownTime = false;
7535
+ let oldest = null;
7536
+ for (const observation of observations) {
7537
+ const source = observation?.source;
7538
+ if (source === "cache") cache = true;
7539
+ else if (source !== "live") unknown = true;
7540
+ const time = validTime(observation?.sourceSyncedAt);
7541
+ if (time !== null) oldest = oldest === null ? time : Math.min(oldest, time);
7542
+ else if (source !== "live") unknownTime = true;
7543
+ }
7544
+ const syncedAt = unknownTime || oldest === null ? null : new Date(Math.min(oldest, checkedAt.getTime()));
7545
+ return {
7546
+ source: cache ? "cache" : unknown ? "unknown" : "live",
7547
+ sourceSyncedAt: syncedAt ? syncedAt.toISOString() : null
7548
+ };
7141
7549
  }
7142
7550
  function providerSyncFailure(error, now) {
7143
7551
  const code = isVaultProviderError(error) ? error.code : "provider_unavailable";
@@ -7160,24 +7568,59 @@ function providerSyncFailure(error, now) {
7160
7568
  lastSyncedAt: null
7161
7569
  };
7162
7570
  }
7571
+ async function providerStep(call) {
7572
+ try {
7573
+ return await call();
7574
+ } catch (error) {
7575
+ if (isVaultProviderError(error)) throw error;
7576
+ throw new VaultProviderError3(
7577
+ "provider_unavailable",
7578
+ "the vault provider could not be reached or gave an invalid answer"
7579
+ );
7580
+ }
7581
+ }
7163
7582
  async function syncProviderVault(input) {
7164
7583
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
7165
7584
  const { target } = input;
7166
- if (target.rateLimitedUntil && target.rateLimitedUntil.getTime() > now().getTime() && !input.force) {
7585
+ if (target.rateLimitedUntil && target.rateLimitedUntil.getTime() > now().getTime()) {
7167
7586
  return { status: "skipped_rate_limited", rateLimitedUntil: target.rateLimitedUntil };
7168
7587
  }
7588
+ const observations = [];
7589
+ const outer = input.ctx.onFreshness;
7590
+ const ctx = {
7591
+ ...input.ctx,
7592
+ ...target.cacheEpoch !== void 0 && input.ctx.cacheEpoch === void 0 ? { cacheEpoch: target.cacheEpoch } : {},
7593
+ ...input.force ? { forceRefresh: true } : {},
7594
+ onFreshness(freshness) {
7595
+ observations.push(freshness);
7596
+ outer?.(freshness);
7597
+ }
7598
+ };
7599
+ const succeeded = () => {
7600
+ const checkedAt = now();
7601
+ return {
7602
+ syncStatus: "ok",
7603
+ syncErrorCode: null,
7604
+ syncErrorMessage: null,
7605
+ rateLimitedUntil: null,
7606
+ lastSyncedAt: checkedAt,
7607
+ freshness: aggregateProviderFreshness(observations, checkedAt)
7608
+ };
7609
+ };
7169
7610
  try {
7170
- const vaults = await input.provider.listVaults(input.ctx);
7171
- const vault = vaults.find((candidate) => candidate.externalVaultId === target.externalVaultId);
7611
+ const vaults = await providerStep(() => readProviderVaultInventory(input.provider, ctx));
7612
+ const vault = vaults.items.find(
7613
+ (candidate) => candidate.externalVaultId === target.externalVaultId
7614
+ );
7172
7615
  if (!vault) {
7173
7616
  const failure = providerSyncFailure(
7174
- new VaultProviderError2(
7617
+ vaults.consistent ? new VaultProviderError3(
7175
7618
  "provider_not_found",
7176
7619
  "the linked provider vault no longer exists or is no longer shared with this connection"
7177
- ),
7620
+ ) : unprovenInventory("vaults"),
7178
7621
  now()
7179
7622
  );
7180
- await input.store.setSyncStatus(failure);
7623
+ if (!await input.store.settleRefresh(failure)) return { status: "superseded" };
7181
7624
  return {
7182
7625
  status: "failed",
7183
7626
  syncStatus: failure.syncStatus,
@@ -7185,26 +7628,19 @@ async function syncProviderVault(input) {
7185
7628
  rateLimitedUntil: null
7186
7629
  };
7187
7630
  }
7631
+ const operations = providerOperations2(input.provider.capabilities(), vault);
7188
7632
  if (!input.force && vault.contentVersion !== null && vault.contentVersion === target.mirroredContentVersion) {
7189
- await input.store.setSyncStatus({
7190
- syncStatus: "ok",
7191
- syncErrorCode: null,
7192
- syncErrorMessage: null,
7193
- rateLimitedUntil: null,
7194
- lastSyncedAt: now()
7195
- });
7633
+ if (!await input.store.settleRefresh({ ...succeeded(), operations }))
7634
+ return { status: "superseded" };
7196
7635
  return { status: "unchanged", contentVersion: vault.contentVersion };
7197
7636
  }
7198
- const items = await input.provider.listItems(input.ctx, target.externalVaultId);
7199
- const plan = planProviderMirror(items, vault.contentVersion);
7200
- await input.store.replaceMirror(plan);
7201
- await input.store.setSyncStatus({
7202
- syncStatus: "ok",
7203
- syncErrorCode: null,
7204
- syncErrorMessage: null,
7205
- rateLimitedUntil: null,
7206
- lastSyncedAt: now()
7207
- });
7637
+ const inventory = await providerStep(
7638
+ () => readProviderItemInventory(input.provider, ctx, target.externalVaultId)
7639
+ );
7640
+ if (!inventory.consistent) throw unprovenInventory("items");
7641
+ const plan = planProviderMirror(inventory.items, vault.contentVersion);
7642
+ if (!await input.store.settleRefresh({ ...succeeded(), operations }, plan))
7643
+ return { status: "superseded" };
7208
7644
  return {
7209
7645
  status: "synced",
7210
7646
  contentVersion: plan.contentVersion,
@@ -7214,7 +7650,7 @@ async function syncProviderVault(input) {
7214
7650
  } catch (error) {
7215
7651
  if (!isVaultProviderError(error)) throw error;
7216
7652
  const failure = providerSyncFailure(error, now());
7217
- await input.store.setSyncStatus(failure);
7653
+ if (!await input.store.settleRefresh(failure)) return { status: "superseded" };
7218
7654
  return {
7219
7655
  status: "failed",
7220
7656
  syncStatus: failure.syncStatus,
@@ -7223,11 +7659,10 @@ async function syncProviderVault(input) {
7223
7659
  };
7224
7660
  }
7225
7661
  }
7226
- function databaseProviderMirrorStore(db, context, vaultId) {
7662
+ function databaseProviderMirrorStore(db, context, vaultId, expectedCacheEpoch) {
7227
7663
  return {
7228
- async replaceMirror(plan) {
7229
- await upsertProviderVaultItems(db, context, {
7230
- vaultId,
7664
+ async settleRefresh(update, plan) {
7665
+ const mirror = plan ? {
7231
7666
  contentVersion: plan.contentVersion,
7232
7667
  items: plan.items.map((item) => ({
7233
7668
  providerItemRef: item.externalItemId,
@@ -7245,22 +7680,41 @@ function databaseProviderMirrorStore(db, context, vaultId) {
7245
7680
  kind: field.kind
7246
7681
  }))
7247
7682
  }))
7248
- });
7249
- },
7250
- async setSyncStatus(update) {
7251
- await setVaultSyncStatus(db, context, {
7252
- vaultId,
7253
- status: update.syncStatus,
7254
- errorCode: update.syncErrorCode,
7255
- errorMessage: update.syncErrorMessage,
7256
- rateLimitedUntil: update.rateLimitedUntil?.toISOString() ?? null
7257
- });
7683
+ } : void 0;
7684
+ try {
7685
+ await setVaultSyncStatus(db, context, {
7686
+ vaultId,
7687
+ expectedCacheEpoch,
7688
+ ...mirror ? { mirror } : {},
7689
+ status: update.syncStatus,
7690
+ errorCode: update.syncErrorCode,
7691
+ errorMessage: update.syncErrorMessage,
7692
+ rateLimitedUntil: update.rateLimitedUntil?.toISOString() ?? null,
7693
+ ...update.operations !== void 0 ? { operations: update.operations } : {},
7694
+ ...update.freshness !== void 0 ? { freshness: update.freshness } : {}
7695
+ });
7696
+ return true;
7697
+ } catch (error) {
7698
+ if (isVaultError(error) && error.code === "provider_conflict" && error.details?.superseded === true)
7699
+ return false;
7700
+ throw error;
7701
+ }
7258
7702
  }
7259
7703
  };
7260
7704
  }
7261
7705
  async function syncLinkedProviderVault(input) {
7262
7706
  const target = await getProviderVaultSyncTarget(input.db, input.context, input.vaultId);
7263
- const store = databaseProviderMirrorStore(input.db, input.context, input.vaultId);
7707
+ const store = databaseProviderMirrorStore(
7708
+ input.db,
7709
+ input.context,
7710
+ input.vaultId,
7711
+ target.providerCacheEpoch ?? "0"
7712
+ );
7713
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
7714
+ const rateLimitedUntil = target.rateLimitedUntil ? new Date(target.rateLimitedUntil) : null;
7715
+ if (rateLimitedUntil && rateLimitedUntil.getTime() > now().getTime()) {
7716
+ return { status: "skipped_rate_limited", rateLimitedUntil };
7717
+ }
7264
7718
  let provider;
7265
7719
  try {
7266
7720
  provider = await input.providers.forVault({
@@ -7269,8 +7723,8 @@ async function syncLinkedProviderVault(input) {
7269
7723
  });
7270
7724
  } catch (error) {
7271
7725
  if (!isVaultProviderError(error)) throw error;
7272
- const failure = providerSyncFailure(error, (input.now ?? (() => /* @__PURE__ */ new Date()))());
7273
- await store.setSyncStatus(failure);
7726
+ const failure = providerSyncFailure(error, now());
7727
+ if (!await store.settleRefresh(failure)) return { status: "superseded" };
7274
7728
  return {
7275
7729
  status: "failed",
7276
7730
  syncStatus: failure.syncStatus,
@@ -7283,12 +7737,16 @@ async function syncLinkedProviderVault(input) {
7283
7737
  ctx: {
7284
7738
  connectionId: target.providerConnectionId,
7285
7739
  budget: input.budget ?? { take() {
7286
- } }
7740
+ } },
7741
+ cacheEpoch: target.providerCacheEpoch ?? null,
7742
+ ...input.signal ? { signal: input.signal } : {},
7743
+ ...input.onFreshness ? { onFreshness: input.onFreshness } : {}
7287
7744
  },
7288
7745
  target: {
7289
7746
  externalVaultId: target.providerVaultRef,
7290
7747
  mirroredContentVersion: target.providerContentVersion,
7291
- rateLimitedUntil: target.rateLimitedUntil ? new Date(target.rateLimitedUntil) : null
7748
+ rateLimitedUntil,
7749
+ cacheEpoch: target.providerCacheEpoch ?? null
7292
7750
  },
7293
7751
  store,
7294
7752
  ...input.now ? { now: input.now } : {},
@@ -7299,6 +7757,7 @@ var LINKED_VAULT_SEARCH_MAX_AGE_MS = 10 * 6e4;
7299
7757
  var LINKED_VAULT_MISS_MAX_AGE_MS = 2 * 6e4;
7300
7758
  var LINKED_VAULT_REFRESH_WAIT_MS = 8e3;
7301
7759
  var LINKED_VAULT_OPEN_WAIT_MS = 3e3;
7760
+ var LINKED_VAULT_REFRESH_TIMEOUT_MS = 6e4;
7302
7761
  function linkedVaultNeedsRefresh(vault, maxAgeMs, now) {
7303
7762
  if (vault.source === "opengeni") return false;
7304
7763
  if (vault.rateLimitedUntil && new Date(vault.rateLimitedUntil).getTime() > now.getTime()) {
@@ -7308,38 +7767,124 @@ function linkedVaultNeedsRefresh(vault, maxAgeMs, now) {
7308
7767
  if (!last) return true;
7309
7768
  return now.getTime() - new Date(last).getTime() >= maxAgeMs;
7310
7769
  }
7770
+ function isoOrNull(value) {
7771
+ const time = validTime(value);
7772
+ return time === null ? null : new Date(time).toISOString();
7773
+ }
7774
+ function providerVaultFreshness(vault, now, options = {}) {
7775
+ if (vault.source === "opengeni") return void 0;
7776
+ const stored2 = vault.providerFreshness ?? null;
7777
+ const failed = vault.syncStatus !== "ok" && vault.syncStatus !== "syncing";
7778
+ const base = {
7779
+ lastCheckedAt: isoOrNull(vault.lastSyncedAt),
7780
+ sourceSyncedAt: isoOrNull(stored2?.sourceSyncedAt),
7781
+ source: stored2?.source === "live" || stored2?.source === "cache" ? stored2.source : "unknown",
7782
+ errorCode: failed && vault.syncErrorCode ? vault.syncErrorCode.slice(0, 100) : null,
7783
+ retryAt: null
7784
+ };
7785
+ if (options.refreshing || vault.syncStatus === "syncing") return { state: "refreshing", ...base };
7786
+ if (vault.syncStatus === "rate_limited") {
7787
+ const retryAt = isoOrNull(vault.rateLimitedUntil);
7788
+ if (retryAt !== null && Date.parse(retryAt) > now.getTime()) {
7789
+ return { state: "unavailable", ...base, retryAt };
7790
+ }
7791
+ return { state: "stale", ...base };
7792
+ }
7793
+ if (failed) return { state: "unavailable", ...base };
7794
+ const checked = base.lastCheckedAt === null ? null : Date.parse(base.lastCheckedAt);
7795
+ const maxAgeMs = options.maxAgeMs ?? LINKED_VAULT_SEARCH_MAX_AGE_MS;
7796
+ if (checked === null || now.getTime() - checked >= maxAgeMs) return { state: "stale", ...base };
7797
+ return { state: "current", ...base };
7798
+ }
7799
+ function vaultRefreshScopeKey(context) {
7800
+ if (context.kind === "subject") {
7801
+ return `${context.accountId}|${context.workspaceId}|subject|${context.subjectId}`;
7802
+ }
7803
+ const principal = context.principal;
7804
+ switch (principal.kind) {
7805
+ case "attempt":
7806
+ return `${principal.accountId}|${principal.workspaceId}|attempt|${principal.attemptId}|${principal.executionGeneration}`;
7807
+ case "site_viewer":
7808
+ return `${principal.accountId}|${principal.workspaceId}|site|${principal.siteId}|${principal.siteVersion}|${principal.subjectId}`;
7809
+ case "human":
7810
+ return `${principal.accountId}|${principal.workspaceId}|human|${principal.subjectId}|${principal.viaApiKey ? "key" : principal.verifiedHuman ? "verified" : "unverified"}`;
7811
+ }
7812
+ }
7311
7813
  var linkedVaultRefreshes = /* @__PURE__ */ new Map();
7312
- async function refreshStaleLinkedVaults(input) {
7814
+ async function refreshLinkedVaults(input) {
7313
7815
  const now = (input.now ?? (() => /* @__PURE__ */ new Date()))();
7314
- const stale = input.vaults.filter((vault) => linkedVaultNeedsRefresh(vault, input.maxAgeMs, now));
7315
- if (stale.length === 0) return [];
7816
+ const maxAgeMs = input.maxAgeMs ?? LINKED_VAULT_SEARCH_MAX_AGE_MS;
7817
+ const scope = vaultRefreshScopeKey(input.context);
7818
+ const outcomes = /* @__PURE__ */ new Map();
7316
7819
  const changed = [];
7317
- const runs = stale.map((vault) => {
7318
- let run = linkedVaultRefreshes.get(vault.id);
7820
+ const runs = [];
7821
+ for (const vault of input.vaults) {
7822
+ if (outcomes.has(vault.id)) continue;
7823
+ const limited = vault.rateLimitedUntil !== null && new Date(vault.rateLimitedUntil).getTime() > now.getTime();
7824
+ const due = vault.source !== "opengeni" && !limited && (input.force || linkedVaultNeedsRefresh(vault, maxAgeMs, now));
7825
+ const outcome = {
7826
+ vaultId: vault.id,
7827
+ state: due ? "pending" : "not_due",
7828
+ result: null
7829
+ };
7830
+ outcomes.set(vault.id, outcome);
7831
+ if (!due) continue;
7832
+ const key = `${scope}|${vault.id}|${input.force ? "force" : "stale"}`;
7833
+ let run = linkedVaultRefreshes.get(key);
7319
7834
  if (!run) {
7320
- run = syncLinkedProviderVault({
7321
- db: input.db,
7322
- context: input.context,
7323
- vaultId: vault.id,
7324
- providers: input.providers,
7325
- ...input.now ? { now: input.now } : {}
7326
- }).catch(() => null).finally(() => linkedVaultRefreshes.delete(vault.id));
7327
- linkedVaultRefreshes.set(vault.id, run);
7835
+ let timer;
7836
+ run = Promise.race([
7837
+ syncLinkedProviderVault({
7838
+ db: input.db,
7839
+ context: input.context,
7840
+ vaultId: vault.id,
7841
+ providers: input.providers,
7842
+ signal: AbortSignal.timeout(LINKED_VAULT_REFRESH_TIMEOUT_MS),
7843
+ ...input.force ? { force: true } : {},
7844
+ ...input.now ? { now: input.now } : {}
7845
+ }),
7846
+ new Promise((resolve) => {
7847
+ timer = setTimeout(() => resolve(null), LINKED_VAULT_REFRESH_TIMEOUT_MS);
7848
+ timer.unref?.();
7849
+ })
7850
+ ]).catch(() => {
7851
+ outcome.state = "failed";
7852
+ return null;
7853
+ }).finally(() => {
7854
+ if (timer) clearTimeout(timer);
7855
+ linkedVaultRefreshes.delete(key);
7856
+ });
7857
+ linkedVaultRefreshes.set(key, run);
7328
7858
  }
7329
- return run.then((result) => {
7330
- if (result?.status === "synced") changed.push(vault.id);
7331
- });
7332
- });
7333
- let timer;
7334
- await Promise.race([
7335
- Promise.all(runs),
7336
- new Promise((resolve) => {
7337
- timer = setTimeout(resolve, input.waitMs ?? LINKED_VAULT_REFRESH_WAIT_MS);
7338
- timer.unref?.();
7339
- })
7340
- ]);
7341
- if (timer) clearTimeout(timer);
7342
- return [...changed];
7859
+ runs.push(
7860
+ run.then((result) => {
7861
+ if (outcome.state === "pending") outcome.state = result ? "done" : "failed";
7862
+ outcome.result = result;
7863
+ if (result?.status === "synced") changed.push(vault.id);
7864
+ })
7865
+ );
7866
+ }
7867
+ if (runs.length > 0) {
7868
+ let timer;
7869
+ await Promise.race([
7870
+ Promise.all(runs),
7871
+ new Promise((resolve) => {
7872
+ timer = setTimeout(resolve, input.waitMs ?? LINKED_VAULT_REFRESH_WAIT_MS);
7873
+ timer.unref?.();
7874
+ })
7875
+ ]);
7876
+ if (timer) clearTimeout(timer);
7877
+ }
7878
+ const list = [...outcomes.values()].map((outcome) => ({ ...outcome }));
7879
+ return {
7880
+ outcomes: list,
7881
+ changed: [...changed],
7882
+ pending: list.filter((outcome) => outcome.state === "pending").map((outcome) => outcome.vaultId)
7883
+ };
7884
+ }
7885
+ async function refreshStaleLinkedVaults(input) {
7886
+ const report = await refreshLinkedVaults(input);
7887
+ return report.changed;
7343
7888
  }
7344
7889
 
7345
7890
  // src/domain/vaults/secret-resolver.ts
@@ -7473,6 +8018,51 @@ function providerFailureCode(code) {
7473
8018
  if (code === "provider_permission_denied") return "denied";
7474
8019
  return "provider_unavailable";
7475
8020
  }
8021
+ function relevantProviderVaults(reference, candidates, reachable) {
8022
+ const connected = reachable.filter((vault) => vault.source !== "opengeni");
8023
+ if (connected.length === 0) return [];
8024
+ const where = possibleProviderVaults(reference, reachable, connected);
8025
+ const live = candidates.filter((candidate) => !candidate.archived);
8026
+ if (live.length === 0) return where;
8027
+ const deciding = Math.min(...live.map((candidate) => candidate.tier));
8028
+ const lacking = new Set(
8029
+ live.filter(
8030
+ (candidate) => candidate.tier <= deciding && candidate.source !== "opengeni" && (candidate.missing === "field" || candidate.missing === "totp_field")
8031
+ ).map((candidate) => candidate.vaultId)
8032
+ );
8033
+ const matched = new Set(live.map((candidate) => candidate.vaultId));
8034
+ const unmatched = new Set(
8035
+ where.filter((vault) => vault.tier <= deciding && !matched.has(vault.id)).map((vault) => vault.id)
8036
+ );
8037
+ return connected.filter((vault) => lacking.has(vault.id) || unmatched.has(vault.id));
8038
+ }
8039
+ function possibleProviderVaults(reference, reachable, connected) {
8040
+ if (reference.target.form !== "path") return [];
8041
+ const segments = reference.target.segments;
8042
+ if (segments.length === 1) return [...connected];
8043
+ const nameKey2 = secretNameKey(segments[0]);
8044
+ const named = reachable.filter((vault) => secretNameKey(vault.name) === nameKey2);
8045
+ if (segments.length === 2 && named.length === 0) return [...connected];
8046
+ return named.filter((vault) => vault.source !== "opengeni");
8047
+ }
8048
+ function uncheckedSourceFailure(reference, unchecked) {
8049
+ const authExpired = unchecked.filter(({ vault }) => vault.syncStatus === "auth_expired");
8050
+ const rateLimited = unchecked.filter(
8051
+ ({ freshness }) => freshness.state === "unavailable" && freshness.retryAt !== null
8052
+ );
8053
+ 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"] : [
8054
+ "provider_unavailable",
8055
+ unchecked,
8056
+ unchecked.every(({ freshness }) => freshness.state === "refreshing") ? "is still refreshing" : "could not be checked"
8057
+ ];
8058
+ const names = named.slice(0, 3).map(({ vault }) => vault.name).join(", ");
8059
+ const retryAt = rateLimited.map(({ freshness }) => freshness.retryAt).sort().at(0);
8060
+ return {
8061
+ raw: reference.raw,
8062
+ code,
8063
+ 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")
8064
+ };
8065
+ }
7476
8066
  function decodeBase64(value) {
7477
8067
  return new Uint8Array(Buffer.from(value, "base64"));
7478
8068
  }
@@ -7535,35 +8125,74 @@ function createSecretResolver(deps, principal, opts) {
7535
8125
  const { meta: meta2, candidates } = await resolveSecretReferenceCandidates(deps.db, context, {
7536
8126
  refs: references.map((reference, index) => lookupFor(reference, index))
7537
8127
  });
7538
- const failures2 = [];
7539
- const chosen2 = [];
7540
- references.forEach((reference, index) => {
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 });
8128
+ const outcomes2 = references.map((reference, index) => {
8129
+ const own = candidates.filter((candidate) => candidate.refIndex === index);
8130
+ return { reference, candidates: own, outcome: chooseSecretCandidate(reference, own, meta2) };
7548
8131
  });
7549
- return { meta: meta2, failures: failures2, chosen: chosen2 };
8132
+ return { meta: meta2, outcomes: outcomes2 };
7550
8133
  };
7551
- let { meta, failures, chosen } = await choose();
7552
- if (failures.some((failure) => failure.code === "not_found")) {
7553
- const registry = deps.providers ?? opts.providersForSubject?.(meta.subjectId);
7554
- if (registry) {
7555
- const reachable = await listVaultsForSubject(deps.db, context, {
7556
- workspaceId: principal.workspaceId
7557
- });
7558
- const changed = await refreshStaleLinkedVaults({
8134
+ let { meta, outcomes } = await choose();
8135
+ let registry;
8136
+ const providers = () => {
8137
+ if (registry === void 0) {
8138
+ registry = deps.providers ?? opts.providersForSubject?.(meta.subjectId) ?? null;
8139
+ }
8140
+ return registry;
8141
+ };
8142
+ const misses = () => outcomes.filter(
8143
+ (entry) => "failure" in entry.outcome && entry.outcome.failure.code === "not_found"
8144
+ );
8145
+ let reachable = null;
8146
+ let refreshing = /* @__PURE__ */ new Set();
8147
+ if (misses().length > 0) {
8148
+ reachable = await listVaultsForSubject(deps.db, context, {
8149
+ workspaceId: principal.workspaceId
8150
+ });
8151
+ const relevant = /* @__PURE__ */ new Map();
8152
+ for (const entry of misses()) {
8153
+ for (const vault of relevantProviderVaults(entry.reference, entry.candidates, reachable)) {
8154
+ relevant.set(vault.id, vault);
8155
+ }
8156
+ }
8157
+ const refreshProviders = relevant.size > 0 ? providers() : null;
8158
+ if (refreshProviders) {
8159
+ const report = await refreshLinkedVaults({
7559
8160
  db: deps.db,
7560
8161
  context,
7561
- vaults: reachable,
7562
- providers: registry,
8162
+ vaults: [...relevant.values()],
8163
+ providers: refreshProviders,
7563
8164
  maxAgeMs: LINKED_VAULT_MISS_MAX_AGE_MS
7564
8165
  });
7565
- if (changed.length > 0) ({ meta, failures, chosen } = await choose());
8166
+ refreshing = new Set(report.pending);
8167
+ if (report.changed.length > 0 || report.outcomes.some((outcome) => outcome.result?.status === "superseded"))
8168
+ ({ meta, outcomes } = await choose());
8169
+ if (report.outcomes.some((outcome) => outcome.state === "done" || outcome.state === "failed")) {
8170
+ reachable = await listVaultsForSubject(deps.db, context, {
8171
+ workspaceId: principal.workspaceId
8172
+ });
8173
+ }
8174
+ }
8175
+ }
8176
+ const failures = [];
8177
+ const chosen = [];
8178
+ const checkedAt = /* @__PURE__ */ new Date();
8179
+ for (const entry of outcomes) {
8180
+ if ("candidate" in entry.outcome) {
8181
+ chosen.push({ reference: entry.reference, candidate: entry.outcome.candidate });
8182
+ continue;
8183
+ }
8184
+ let failure = entry.outcome.failure;
8185
+ if (failure.code === "not_found" && reachable) {
8186
+ const unchecked = relevantProviderVaults(entry.reference, entry.candidates, reachable).map((vault) => ({
8187
+ vault,
8188
+ freshness: providerVaultFreshness(vault, checkedAt, {
8189
+ maxAgeMs: LINKED_VAULT_MISS_MAX_AGE_MS,
8190
+ refreshing: refreshing.has(vault.id)
8191
+ })
8192
+ })).filter(({ freshness }) => freshness.state !== "current");
8193
+ if (unchecked.length > 0) failure = uncheckedSourceFailure(entry.reference, unchecked);
7566
8194
  }
8195
+ failures.push(failure);
7567
8196
  }
7568
8197
  const hasActingHuman = meta.humanExpected && meta.humanValid;
7569
8198
  if (failures.length > 0) {
@@ -7582,13 +8211,16 @@ function createSecretResolver(deps, principal, opts) {
7582
8211
  for (const group of providerGroups.values()) {
7583
8212
  const first = group[0].candidate;
7584
8213
  try {
7585
- const registry = deps.providers ?? opts.providersForSubject?.(meta.subjectId) ?? EMPTY_VAULT_PROVIDER_REGISTRY;
7586
- const provider = await registry.forVault({
8214
+ const provider = await (providers() ?? EMPTY_VAULT_PROVIDER_REGISTRY).forVault({
7587
8215
  source: first.source,
7588
8216
  providerConnectionId: first.providerConnectionId
7589
8217
  });
7590
8218
  const values = await provider.readFields(
7591
- { connectionId: first.providerConnectionId, budget },
8219
+ {
8220
+ connectionId: first.providerConnectionId,
8221
+ budget,
8222
+ cacheEpoch: first.providerCacheEpoch ?? null
8223
+ },
7592
8224
  first.providerVaultRef ?? "",
7593
8225
  first.providerItemRef ?? "",
7594
8226
  [...new Set(group.map((entry) => entry.candidate.providerFieldRef ?? ""))]
@@ -7602,7 +8234,7 @@ function createSecretResolver(deps, principal, opts) {
7602
8234
  failures.push({
7603
8235
  raw: entry.reference.raw,
7604
8236
  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"
8237
+ 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
8238
  });
7607
8239
  }
7608
8240
  }
@@ -8416,7 +9048,7 @@ async function swapActiveSandbox(services, ctx, target, workingDir) {
8416
9048
  async function createRunOnSecretResolver(services, attempt, providersForSubject) {
8417
9049
  const session = await getSession3(services.db, attempt.workspaceId, attempt.sessionId);
8418
9050
  return createSecretResolver(
8419
- { db: services.db, encryptionKey: environmentsEncryptionKeyBytes2(services.settings) ?? null },
9051
+ { db: services.db, encryptionKey: environmentsEncryptionKeyBytes3(services.settings) ?? null },
8420
9052
  { kind: "attempt", ...attempt },
8421
9053
  {
8422
9054
  capabilityEnabled: session?.agent ? agentCapabilityEnabled(session.agent.capabilities, "secrets") : true,
@@ -8439,7 +9071,8 @@ function runOnOperationAdmission(services, enrollment) {
8439
9071
  operationResourcePolicy: enrollment.operationPolicy,
8440
9072
  operationResourcePolicySupported: enrollment.agentCapabilities.operationResourcePolicy === true,
8441
9073
  operationCpuQuotaSupported: enrollment.agentCapabilities.operationCpuQuota === true,
8442
- transactionalFsWriteSupported: enrollment.agentCapabilities.transactionalFsWrite === true
9074
+ transactionalFsWriteSupported: enrollment.agentCapabilities.transactionalFsWrite === true,
9075
+ browserControlHttpSupported: enrollment.agentCapabilities.browserControlHttp === true
8443
9076
  };
8444
9077
  }
8445
9078
  function hasSecretFileReference(carry) {
@@ -10603,10 +11236,10 @@ async function validateSubmittedTimelineAnnotations(db, workspaceId, sessionId,
10603
11236
  }
10604
11237
 
10605
11238
  // src/domain/encryption-key.ts
10606
- import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
11239
+ import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes4 } from "@opengeni/config";
10607
11240
  import { HTTPException as HTTPException19 } from "hono/http-exception";
10608
11241
  function requireEnvironmentEncryption(settings) {
10609
- const key = environmentsEncryptionKeyBytes3(settings);
11242
+ const key = environmentsEncryptionKeyBytes4(settings);
10610
11243
  if (!key) {
10611
11244
  throw new HTTPException19(503, {
10612
11245
  message: "secrets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
@@ -10630,7 +11263,7 @@ import {
10630
11263
  areOpenGeniSlackBotScopesAccepted
10631
11264
  } from "@opengeni/contracts";
10632
11265
  import {
10633
- getConnectionMetadata as getConnectionMetadata4,
11266
+ getConnectionMetadata as getConnectionMetadata5,
10634
11267
  availableSlackBotConnectionMetadata,
10635
11268
  requireWorkspace as requireWorkspace4
10636
11269
  } from "@opengeni/db";
@@ -10651,7 +11284,7 @@ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
10651
11284
  );
10652
11285
  }
10653
11286
  async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
10654
- const local = await getConnectionMetadata4(db, workspaceId, connectionId, null);
11287
+ const local = await getConnectionMetadata5(db, workspaceId, connectionId, null);
10655
11288
  const workspace = await requireWorkspace4(db, workspaceId);
10656
11289
  const connection = local ?? (await availableSlackBotConnectionMetadata(db, { accountId: workspace.accountId, workspaceId })).find((candidate) => candidate.id === connectionId) ?? null;
10657
11290
  if (!connection || !isOpenGeniSlackBotConnection(connection)) {
@@ -12621,9 +13254,9 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
12621
13254
  const payload = CreateSessionRequest.parse(rawPayload);
12622
13255
  await assertSiteAgentMayChooseCompute(unresolvedDeps.db, grant, payload);
12623
13256
  if (payload.firstPartyMcpTools) {
12624
- const unavailable = deploymentUnavailableFirstPartyMcpTools(unresolvedDeps.settings);
13257
+ const unavailable2 = deploymentUnavailableFirstPartyMcpTools(unresolvedDeps.settings);
12625
13258
  payload.firstPartyMcpTools = payload.firstPartyMcpTools.filter(
12626
- (tool) => !unavailable.has(tool)
13259
+ (tool) => !unavailable2.has(tool)
12627
13260
  );
12628
13261
  }
12629
13262
  const surface = resolveTurnSurface({
@@ -15710,7 +16343,10 @@ function createRealtimeVoiceBilling(deps) {
15710
16343
  };
15711
16344
  }
15712
16345
  const attribution = input.callerSubjectId === facts.ownerSubjectId ? input.attribution : { kind: "unknown" };
15713
- const cost = realtimeVoiceMinuteCreditMicros(voice.pricing);
16346
+ const cost = realtimeVoiceMinuteCreditMicros(
16347
+ voice.pricing,
16348
+ deps.settings.creditMarginBpsOverride
16349
+ );
15714
16350
  const minutes = facts.connections.flatMap(
15715
16351
  (connection) => Array.from(
15716
16352
  { length: realtimeVoiceStartedMinutes(connection.startedAt, connection.observedUntil) },
@@ -15898,7 +16534,11 @@ function createVoiceInputBilling(deps) {
15898
16534
  usage: input.usage,
15899
16535
  trustedDurationSeconds: input.billing.trustedDurationSeconds
15900
16536
  });
15901
- const cost = calculateVoiceInputCost(input.pricing, usage);
16537
+ const cost = calculateVoiceInputCost(
16538
+ input.pricing,
16539
+ usage,
16540
+ deps.settings.creditMarginBpsOverride
16541
+ );
15902
16542
  return await settleVoiceInputUsage(deps.db, {
15903
16543
  accountId: input.accountId,
15904
16544
  workspaceId: input.workspaceId,
@@ -16106,22 +16746,137 @@ function createWebSearchBilling(deps) {
16106
16746
  };
16107
16747
  }
16108
16748
 
16109
- // src/workflow-wake-contract.ts
16110
- var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
16111
- var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
16112
- var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_ID = "opengeni-session-workflow-wake-dispatcher";
16113
- var SESSION_WORKFLOW_WAKE_DISPATCH_REQUESTED_SIGNAL = "dispatchRequested";
16114
- var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
16115
- var TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
16116
-
16117
- // src/user-presence.ts
16118
- import { recordUserActivityPresence } from "@opengeni/db";
16119
- var USER_PRESENCE_THROTTLE_MS = 6e4;
16120
- var USER_PRESENCE_FLUSH_DELAY_MS = 5e3;
16121
- var USER_PRESENCE_MAX_TRACKED = 5e4;
16122
- var USER_PRESENCE_SUBJECT = /^user:[A-Za-z0-9_-]{8,128}$/;
16123
- var PRESENCE_FLUSH_METRIC = {
16124
- name: "opengeni_user_presence_flushes_total",
16749
+ // src/domain/vaults/remote-provider-runtime.ts
16750
+ import {
16751
+ DestinationPolicyError,
16752
+ ResponseBodyLimitError,
16753
+ isLocalTestEnvironment,
16754
+ pinnedFetch as pinnedFetch2,
16755
+ readResponseTextBounded,
16756
+ validateHttpUrl
16757
+ } from "@opengeni/network";
16758
+ import {
16759
+ RemoteVaultTransportError,
16760
+ VaultProviderError as VaultProviderError4,
16761
+ createRemoteVaultProvider
16762
+ } from "@opengeni/vaults";
16763
+ function createPinnedRemoteVaultTransport(settings, fetchImpl = pinnedFetch2) {
16764
+ return {
16765
+ async post(request) {
16766
+ if (request.signal?.aborted) throw new RemoteVaultTransportError("aborted", false);
16767
+ const controller = new AbortController();
16768
+ let timedOut = false;
16769
+ const timer = setTimeout(() => {
16770
+ timedOut = true;
16771
+ controller.abort();
16772
+ }, request.timeoutMs);
16773
+ const onAbort = () => controller.abort();
16774
+ request.signal?.addEventListener("abort", onAbort, { once: true });
16775
+ let rejectAborted;
16776
+ try {
16777
+ const aborted = new Promise((_resolve, reject) => {
16778
+ rejectAborted = () => reject(new RemoteVaultTransportError(timedOut ? "timeout" : "aborted", true));
16779
+ controller.signal.addEventListener("abort", rejectAborted, { once: true });
16780
+ if (controller.signal.aborted) rejectAborted();
16781
+ });
16782
+ const exchange = async () => {
16783
+ const response = await fetchImpl(
16784
+ request.url,
16785
+ {
16786
+ method: "POST",
16787
+ headers: { ...request.headers },
16788
+ body: request.body,
16789
+ redirect: "manual",
16790
+ signal: controller.signal
16791
+ },
16792
+ settings,
16793
+ { label: "Vault provider", requireHttpsOutsideLocalTest: true }
16794
+ );
16795
+ if (response.status >= 300 && response.status < 400) {
16796
+ await response.body?.cancel().catch(() => void 0);
16797
+ throw new RemoteVaultTransportError("redirect", true);
16798
+ }
16799
+ return {
16800
+ status: response.status,
16801
+ headers: response.headers,
16802
+ body: await readResponseTextBounded(
16803
+ response,
16804
+ request.maxResponseBytes,
16805
+ "Vault provider",
16806
+ { signal: controller.signal }
16807
+ )
16808
+ };
16809
+ };
16810
+ return await Promise.race([exchange(), aborted]);
16811
+ } catch (error) {
16812
+ if (error instanceof RemoteVaultTransportError) throw error;
16813
+ if (error instanceof DestinationPolicyError)
16814
+ throw new RemoteVaultTransportError("refused", false);
16815
+ if (error instanceof ResponseBodyLimitError)
16816
+ throw new RemoteVaultTransportError("too_large", true);
16817
+ throw new RemoteVaultTransportError(
16818
+ timedOut ? "timeout" : controller.signal.aborted ? "aborted" : "network",
16819
+ true
16820
+ );
16821
+ } finally {
16822
+ clearTimeout(timer);
16823
+ request.signal?.removeEventListener("abort", onAbort);
16824
+ if (rejectAborted) controller.signal.removeEventListener("abort", rejectAborted);
16825
+ }
16826
+ }
16827
+ };
16828
+ }
16829
+ function createRemoteExternalVaultFactories(settings) {
16830
+ const transport = createPinnedRemoteVaultTransport(settings);
16831
+ const factory = async ({
16832
+ credentials,
16833
+ operations
16834
+ }) => {
16835
+ if (credentials.provider !== "external" && credentials.provider !== "bitwarden")
16836
+ throw new VaultProviderError4("provider_auth_invalid", "Vault provider does not match.");
16837
+ let endpoint2;
16838
+ try {
16839
+ endpoint2 = validateHttpUrl(credentials.endpoint, {
16840
+ allowLoopbackHttp: isLocalTestEnvironment(settings.environment),
16841
+ label: "Vault provider"
16842
+ });
16843
+ } catch {
16844
+ throw new VaultProviderError4(
16845
+ "provider_auth_invalid",
16846
+ "Use an HTTPS vault endpoint without URL credentials or a fragment."
16847
+ );
16848
+ }
16849
+ return {
16850
+ credentials: { ...credentials, endpoint: endpoint2 },
16851
+ provider: createRemoteVaultProvider({
16852
+ provider: credentials.provider,
16853
+ endpoint: endpoint2,
16854
+ signingSecret: credentials.signingSecret,
16855
+ transport,
16856
+ allowInsecureHttp: isLocalTestEnvironment(settings.environment),
16857
+ ...operations ? { operations } : {}
16858
+ })
16859
+ };
16860
+ };
16861
+ return { external: factory, bitwarden: factory };
16862
+ }
16863
+
16864
+ // src/workflow-wake-contract.ts
16865
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
16866
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
16867
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_ID = "opengeni-session-workflow-wake-dispatcher";
16868
+ var SESSION_WORKFLOW_WAKE_DISPATCH_REQUESTED_SIGNAL = "dispatchRequested";
16869
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
16870
+ var TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
16871
+
16872
+ // src/user-presence.ts
16873
+ import { recordUserActivityPresence } from "@opengeni/db";
16874
+ var USER_PRESENCE_THROTTLE_MS = 6e4;
16875
+ var USER_PRESENCE_FLUSH_DELAY_MS = 5e3;
16876
+ var USER_PRESENCE_MAX_TRACKED = 5e4;
16877
+ var USER_PRESENCE_SUBJECT = /^user:[A-Za-z0-9_-]{8,128}$/;
16878
+ var PRESENCE_FLUSH_METRIC = {
16879
+ name: "opengeni_user_presence_flushes_total",
16125
16880
  help: "Batched user presence writes from this API process by outcome."
16126
16881
  };
16127
16882
  function createUserPresenceRecorder(options) {
@@ -16916,7 +17671,7 @@ import { RotateSessionMcpCredentialsRequest } from "@opengeni/contracts";
16916
17671
  import {
16917
17672
  encryptVariableSetValue as encryptVariableSetValue3,
16918
17673
  getWorkspaceGrant as getWorkspaceGrant3,
16919
- subjectHasLiveWorkspaceAuthorityInScope
17674
+ subjectHasLiveWorkspaceAuthorityInScope as subjectHasLiveWorkspaceAuthorityInScope2
16920
17675
  } from "@opengeni/db";
16921
17676
  import {
16922
17677
  rotateSessionMcpCredentialsAtomically,
@@ -17061,7 +17816,7 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
17061
17816
  } else if (!hasVerifiedOwningUserAuthorization(authorization)) {
17062
17817
  throw new HTTPException24(403, { message: "credential rotation authority changed" });
17063
17818
  }
17064
- if (!await subjectHasLiveWorkspaceAuthorityInScope(tx, scope)) {
17819
+ if (!await subjectHasLiveWorkspaceAuthorityInScope2(tx, scope)) {
17065
17820
  throw new HTTPException24(403, { message: "credential rotation authority changed" });
17066
17821
  }
17067
17822
  }
@@ -17182,7 +17937,7 @@ async function executeConnectOperation(input) {
17182
17937
 
17183
17938
  // src/application/prepared-mcp-connection.ts
17184
17939
  import { createHash as createHash3 } from "crypto";
17185
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
17940
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
17186
17941
  import {
17187
17942
  CapabilityCatalogItem as CapabilityCatalogItem2,
17188
17943
  McpServerConnectionRef,
@@ -17266,7 +18021,7 @@ async function prepareMcpConnectionActivation(input) {
17266
18021
  const installation = await prepared.commit(db);
17267
18022
  const savedRef = McpServerConnectionRef.safeParse(installation.config.connectionRef);
17268
18023
  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 && !isDeepStrictEqual2(installation.config.connectionRef, connectionRef)) {
18024
+ if (installation.status !== "active" || !retainedExactPin && !isDeepStrictEqual3(installation.config.connectionRef, connectionRef)) {
17270
18025
  throw new HTTPException26(409, {
17271
18026
  message: "MCP account selection changed; review the existing connection before setup"
17272
18027
  });
@@ -17977,7 +18732,7 @@ function sha256Hex2(bytes) {
17977
18732
  }
17978
18733
 
17979
18734
  // src/domain/skill-search.ts
17980
- import { pinnedFetch as pinnedFetch2, readResponseJsonBounded } from "@opengeni/network";
18735
+ import { pinnedFetch as pinnedFetch3, readResponseJsonBounded } from "@opengeni/network";
17981
18736
  var PublicSkillSearchError = class extends Error {
17982
18737
  constructor(code, message, retryAfterSeconds = null) {
17983
18738
  super(message);
@@ -17992,7 +18747,7 @@ var maxResponseBytes = 256 * 1024;
17992
18747
  var maxResults = 20;
17993
18748
  var ownerPattern = /^[a-z0-9](?:[a-z0-9-]{0,38})$/iu;
17994
18749
  var segmentPattern = /^[a-z0-9](?:[a-z0-9._-]{0,98}[a-z0-9])?$/iu;
17995
- function createPublicSkillSearchClient(settings, fetcher = pinnedFetch2) {
18750
+ function createPublicSkillSearchClient(settings, fetcher = pinnedFetch3) {
17996
18751
  return {
17997
18752
  async search(input) {
17998
18753
  const query = input.query.trim();
@@ -18119,7 +18874,7 @@ function parseRetryAfter(value) {
18119
18874
 
18120
18875
  // src/domain/github-skill-source.ts
18121
18876
  import { HTTPException as HTTPException28 } from "hono/http-exception";
18122
- import { pinnedFetch as pinnedFetch3, readResponseJsonBounded as readResponseJsonBounded2, readResponseTextBounded } from "@opengeni/network";
18877
+ import { pinnedFetch as pinnedFetch4, readResponseJsonBounded as readResponseJsonBounded2, readResponseTextBounded as readResponseTextBounded2 } from "@opengeni/network";
18123
18878
  import {
18124
18879
  PORTABLE_SKILL_MAX_FILES as PORTABLE_SKILL_MAX_FILES2,
18125
18880
  PORTABLE_SKILL_MAX_FILE_BYTES as PORTABLE_SKILL_MAX_FILE_BYTES2,
@@ -18220,7 +18975,7 @@ function createGitHubSkillSourceClient(settings, requestJson = (path, maxBytes,
18220
18975
  }
18221
18976
  async function downloadSkillSnapshot(settings, owner, repository, slug) {
18222
18977
  const signal = AbortSignal.timeout(githubRequestTimeoutMs);
18223
- const response = await pinnedFetch3(
18978
+ const response = await pinnedFetch4(
18224
18979
  `https://skills.sh/api/download/${[owner, repository, slug].map(encodeURIComponent).join("/")}`,
18225
18980
  { headers: { accept: "application/json" }, credentials: "omit", redirect: "manual", signal },
18226
18981
  settings,
@@ -18255,7 +19010,7 @@ async function githubJson(settings, path, maxBytes, label) {
18255
19010
  const controller = new AbortController();
18256
19011
  const timeout = setTimeout(() => controller.abort(), githubRequestTimeoutMs);
18257
19012
  try {
18258
- const response = await pinnedFetch3(
19013
+ const response = await pinnedFetch4(
18259
19014
  `${githubApiBase}${path}`,
18260
19015
  {
18261
19016
  method: "GET",
@@ -18270,7 +19025,7 @@ async function githubJson(settings, path, maxBytes, label) {
18270
19025
  { label, requireHttpsOutsideLocalTest: true }
18271
19026
  );
18272
19027
  if (!response.ok) {
18273
- await readResponseTextBounded(response, 8192, `${label} error`).catch(() => void 0);
19028
+ await readResponseTextBounded2(response, 8192, `${label} error`).catch(() => void 0);
18274
19029
  if (response.status === 404) throw new Error(`${label} was not found or is not public`);
18275
19030
  if (response.status === 403 || response.status === 429) {
18276
19031
  throw new HTTPException28(429, {
@@ -18298,18 +19053,70 @@ function stringValue(value) {
18298
19053
 
18299
19054
  // src/domain/vaults/index.ts
18300
19055
  import { buildMissingSecretAskCard as buildMissingSecretAskCard2 } from "@opengeni/contracts";
18301
- import { isVaultError, VaultError as VaultError3, vaultErrorFromDatabase } from "@opengeni/db";
19056
+ import { isVaultError as isVaultError3, VaultError as VaultError3, vaultErrorFromDatabase } from "@opengeni/db";
18302
19057
 
18303
19058
  // src/domain/vaults/mapping.ts
18304
19059
  import {
18305
19060
  formatSecretReference as formatSecretReference2,
18306
- secretNameKey
19061
+ secretNameKey as secretNameKey2,
19062
+ VaultProviderOperations as VaultProviderOperations2
18307
19063
  } from "@opengeni/contracts";
18308
19064
  import {
18309
19065
  decryptEnvironmentValue as decryptEnvironmentValue2
18310
19066
  } from "@opengeni/db";
19067
+ function withoutProviderWrites(operations) {
19068
+ return {
19069
+ ...operations,
19070
+ createItemTypes: [],
19071
+ updateItem: false,
19072
+ removeField: false,
19073
+ archiveItem: false,
19074
+ archiveKind: "none",
19075
+ deleteItem: false,
19076
+ restoreItem: false,
19077
+ writeFiles: false,
19078
+ createTotp: false,
19079
+ updateTotp: false,
19080
+ createVault: false,
19081
+ restoreFieldHistory: false
19082
+ };
19083
+ }
19084
+ function intersectProviderOperations(left, right) {
19085
+ const archiveItem = left.archiveItem && right.archiveItem && left.archiveKind === right.archiveKind && left.archiveKind !== "none";
19086
+ return {
19087
+ createItemTypes: left.createItemTypes.filter(
19088
+ (type) => type !== "passkey" && right.createItemTypes.includes(type)
19089
+ ),
19090
+ updateItem: left.updateItem && right.updateItem,
19091
+ removeField: left.removeField && right.removeField,
19092
+ archiveItem,
19093
+ archiveKind: archiveItem ? left.archiveKind : "none",
19094
+ deleteItem: false,
19095
+ restoreItem: left.restoreItem && right.restoreItem,
19096
+ readFiles: left.readFiles && right.readFiles,
19097
+ writeFiles: left.writeFiles && right.writeFiles,
19098
+ createTotp: left.createTotp && right.createTotp,
19099
+ updateTotp: left.updateTotp && right.updateTotp,
19100
+ createVault: left.createVault && right.createVault,
19101
+ restoreFieldHistory: left.restoreFieldHistory && right.restoreFieldHistory
19102
+ };
19103
+ }
19104
+ function storedProviderOperations(row) {
19105
+ if (row.source === "opengeni" || !row.providerOperations) return null;
19106
+ const parsed = VaultProviderOperations2.safeParse(row.providerOperations);
19107
+ return parsed.success ? providerOperationsForMode(parsed.data, row.mode) : null;
19108
+ }
19109
+ function providerOperationsForMode(operations, mode) {
19110
+ const normalized = intersectProviderOperations(operations, operations);
19111
+ return mode === "read_write" ? normalized : withoutProviderWrites(normalized);
19112
+ }
19113
+ function providerAllowsAnyWrite(operations) {
19114
+ if (!operations) return true;
19115
+ return operations.createItemTypes.length > 0 || operations.updateItem || operations.archiveItem;
19116
+ }
18311
19117
  function vaultCanWrite(row, authority) {
18312
19118
  if (row.mode !== "read_write" || !authority.write) return false;
19119
+ if (!providerAllowsAnyWrite(storedProviderOperations(row))) return false;
18313
19120
  if (row.ownerKind === "user") return row.ownedByCaller;
18314
19121
  return true;
18315
19122
  }
@@ -18325,7 +19132,11 @@ function vaultCanManage(row, authority) {
18325
19132
  return authority.write;
18326
19133
  }
18327
19134
  }
18328
- function toVaultSummary(row, authority) {
19135
+ function toVaultSummary(row, authority, options = {}) {
19136
+ const operations = storedProviderOperations(row);
19137
+ const freshness = providerVaultFreshness(row, options.now ?? /* @__PURE__ */ new Date(), {
19138
+ ...options.refreshing ? { refreshing: true } : {}
19139
+ });
18329
19140
  return {
18330
19141
  id: row.id,
18331
19142
  workspaceId: row.workspaceId,
@@ -18342,6 +19153,8 @@ function toVaultSummary(row, authority) {
18342
19153
  lastSyncedAt: row.lastSyncedAt,
18343
19154
  rateLimitedUntil: row.rateLimitedUntil,
18344
19155
  providerConnectionId: row.providerConnectionId,
19156
+ ...operations ? { providerOperations: operations } : {},
19157
+ ...freshness ? { freshness } : {},
18345
19158
  availability: row.availability,
18346
19159
  isDefault: row.isDefault,
18347
19160
  itemCount: Number(row.itemCount),
@@ -18381,7 +19194,7 @@ function vaultItemReferences(item) {
18381
19194
  }
18382
19195
  function usernameOf(row, key) {
18383
19196
  const field = row.fields.find(
18384
- (candidate) => candidate.kind === "text" && secretNameKey(candidate.name) === "username"
19197
+ (candidate) => candidate.kind === "text" && secretNameKey2(candidate.name) === "username"
18385
19198
  );
18386
19199
  return field ? decryptOrNull(key, field.valueEncrypted) : null;
18387
19200
  }
@@ -18418,7 +19231,7 @@ function toVaultItemField(item, field, key) {
18418
19231
  }
18419
19232
  function toVaultItem(row, key) {
18420
19233
  const textField = (name) => {
18421
- const field = row.fields.find((candidate) => secretNameKey(candidate.name) === name);
19234
+ const field = row.fields.find((candidate) => secretNameKey2(candidate.name) === name);
18422
19235
  return field ? decryptOrNull(key, field.valueEncrypted) : null;
18423
19236
  };
18424
19237
  const credentialId = row.type === "passkey" ? textField("credentialid") ?? "" : "";
@@ -18441,68 +19254,25 @@ function toVaultItem(row, key) {
18441
19254
  };
18442
19255
  }
18443
19256
 
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
19257
  // src/domain/vaults/provider-writes.ts
19258
+ import { randomUUID } from "crypto";
18491
19259
  import {
18492
19260
  generatePassword,
18493
19261
  isValidTotpInput as isValidTotpInput2,
18494
- secretNameKey as secretNameKey2
19262
+ secretNameKey as secretNameKey3
18495
19263
  } from "@opengeni/contracts";
18496
19264
  import {
18497
19265
  getProviderVaultItemRef,
18498
19266
  VaultError
18499
19267
  } from "@opengeni/db";
18500
19268
  import {
18501
- isVaultProviderError as isVaultProviderError3
19269
+ isVaultProviderError as isVaultProviderError3,
19270
+ providerOperations as providerOperations3,
19271
+ readProviderItemInventory as readProviderItemInventory2
18502
19272
  } from "@opengeni/vaults";
18503
19273
  var TEXT_FIELD_NAMES = ["username", "user", "email", "login", "account"];
18504
19274
  function defaultKind(name) {
18505
- return TEXT_FIELD_NAMES.includes(secretNameKey2(name)) ? "text" : "concealed";
19275
+ return TEXT_FIELD_NAMES.includes(secretNameKey3(name)) ? "text" : "concealed";
18506
19276
  }
18507
19277
  function assertProviderVaultWritable(vault) {
18508
19278
  if (vault.source === "opengeni") {
@@ -18515,11 +19285,43 @@ function assertProviderVaultWritable(vault) {
18515
19285
  throw new VaultError("vault_not_found", "vault not found", { vaultId: vault.id });
18516
19286
  }
18517
19287
  }
18518
- async function providerFieldWrites(fields, existing, resolveReference, generated) {
18519
- const writes = [];
18520
- for (const input of fields) {
19288
+ var NO_PROVIDER_OPERATIONS = withoutProviderWrites({
19289
+ createItemTypes: [],
19290
+ updateItem: false,
19291
+ removeField: false,
19292
+ archiveItem: false,
19293
+ archiveKind: "none",
19294
+ deleteItem: false,
19295
+ restoreItem: false,
19296
+ readFiles: false,
19297
+ writeFiles: false,
19298
+ createTotp: false,
19299
+ updateTotp: false,
19300
+ createVault: false,
19301
+ restoreFieldHistory: false
19302
+ });
19303
+ function effectiveProviderOperations(vault, provider) {
19304
+ if (vault.source === "opengeni") return NO_PROVIDER_OPERATIONS;
19305
+ const stored2 = storedProviderOperations(vault);
19306
+ const live = provider ? providerOperations3(provider.capabilities()) : null;
19307
+ const operations = live && stored2 ? intersectProviderOperations(live, stored2) : stored2 ?? live;
19308
+ return operations ? providerOperationsForMode(operations, vault.mode) : NO_PROVIDER_OPERATIONS;
19309
+ }
19310
+ function refuse(vault, operation, message, details = {}) {
19311
+ throw new VaultError("vault_read_only", message, { vaultId: vault.id, operation, ...details });
19312
+ }
19313
+ function refuseProviderOperation(vault, operation) {
19314
+ const messages = {
19315
+ deleteItem: `Opengeni never permanently deletes items in ${vault.name}. Archive the item, or delete it in the provider itself.`,
19316
+ restoreItem: `Items archived in ${vault.name} are restored in the provider itself; the restored item appears after the next sync.`,
19317
+ restoreFieldHistory: `${vault.name} keeps field history in the provider; restore an earlier value there.`
19318
+ };
19319
+ return refuse(vault, operation, messages[operation]);
19320
+ }
19321
+ function planFieldWrites(vault, operations, creating, fields, existing) {
19322
+ return fields.map((input) => {
18521
19323
  const current = existing.find(
18522
- (field) => input.fieldId !== void 0 && field.id === input.fieldId || secretNameKey2(field.name) === secretNameKey2(input.name)
19324
+ (field) => input.fieldId !== void 0 && field.id === input.fieldId || secretNameKey3(field.name) === secretNameKey3(input.name)
18523
19325
  );
18524
19326
  const kind = input.kind ?? current?.kind ?? defaultKind(input.name);
18525
19327
  const base = {
@@ -18527,24 +19329,51 @@ async function providerFieldWrites(fields, existing, resolveReference, generated
18527
19329
  name: input.name,
18528
19330
  kind
18529
19331
  };
18530
- if (input.remove) {
18531
- writes.push({ ...base, remove: true });
18532
- continue;
18533
- }
18534
19332
  if (input.restoreVersion !== void 0) {
18535
- throw new VaultError(
18536
- "provider_unavailable",
18537
- "connected vaults keep their history in the provider; restore it there",
18538
- { field: input.name }
18539
- );
19333
+ return refuseProviderOperation(vault, "restoreFieldHistory");
18540
19334
  }
18541
- if (input.fileBase64 !== void 0 || kind === "file") {
19335
+ if (input.fileBase64 !== void 0 || kind === "file" || current?.kind === "file") {
18542
19336
  throw new VaultError(
18543
19337
  "provider_unavailable",
18544
19338
  "files in connected vaults are read-only in Opengeni",
18545
- { field: input.name }
19339
+ { field: input.name, vaultId: vault.id, operation: "writeFiles" }
18546
19340
  );
18547
19341
  }
19342
+ const totp = kind === "totp" || current?.kind === "totp";
19343
+ if (input.remove) {
19344
+ if (!operations.removeField) {
19345
+ refuse(vault, "removeField", `${vault.name} does not allow removing fields here`, {
19346
+ field: input.name
19347
+ });
19348
+ }
19349
+ if (totp && !operations.updateTotp) {
19350
+ refuse(vault, "updateTotp", `${vault.name} does not allow changing one-time passwords`, {
19351
+ field: input.name
19352
+ });
19353
+ }
19354
+ return { input, base, kind };
19355
+ }
19356
+ if (totp) {
19357
+ const allowed = creating ? operations.createTotp : operations.updateTotp;
19358
+ if (!allowed) {
19359
+ refuse(
19360
+ vault,
19361
+ creating ? "createTotp" : "updateTotp",
19362
+ `${vault.name} does not allow ${creating ? "saving" : "changing"} one-time passwords`,
19363
+ { field: input.name }
19364
+ );
19365
+ }
19366
+ }
19367
+ return { input, base, kind };
19368
+ });
19369
+ }
19370
+ async function providerFieldValues(planned, resolveReference, generated) {
19371
+ const writes = [];
19372
+ for (const { input, base, kind } of planned) {
19373
+ if (input.remove) {
19374
+ writes.push({ ...base, remove: true });
19375
+ continue;
19376
+ }
18548
19377
  let value;
18549
19378
  if (input.generate !== void 0) {
18550
19379
  value = generatePassword(input.generate);
@@ -18574,6 +19403,16 @@ function providerFailure(error) {
18574
19403
  }
18575
19404
  throw error;
18576
19405
  }
19406
+ function mutationFailure(vault, error, what) {
19407
+ if (isVaultProviderError3(error) && error.code !== "provider_outcome_unknown") {
19408
+ return providerFailure(error);
19409
+ }
19410
+ throw new VaultError(
19411
+ "provider_outcome_unknown",
19412
+ `${vault.name} did not confirm ${what}. It may have been applied: check the vault before trying again.`,
19413
+ { vaultId: vault.id }
19414
+ );
19415
+ }
18577
19416
  async function providerFor(deps, vault) {
18578
19417
  try {
18579
19418
  return await deps.providers.forVault({
@@ -18584,38 +19423,68 @@ async function providerFor(deps, vault) {
18584
19423
  return providerFailure(error);
18585
19424
  }
18586
19425
  }
19426
+ function mutationContext(deps, vault) {
19427
+ return {
19428
+ connectionId: vault.providerConnectionId,
19429
+ budget: deps.budget ?? { take() {
19430
+ } },
19431
+ cacheEpoch: vault.providerCacheEpoch ?? null,
19432
+ // Read-before-write must see the provider's current item, never a cached copy.
19433
+ forceRefresh: true,
19434
+ idempotencyKey: randomUUID()
19435
+ };
19436
+ }
19437
+ async function refreshAfterWrite(deps, vault) {
19438
+ try {
19439
+ const result = await syncLinkedProviderVault({
19440
+ db: deps.db,
19441
+ context: deps.context,
19442
+ vaultId: vault.id,
19443
+ providers: deps.providers,
19444
+ force: true,
19445
+ ...deps.budget ? { budget: deps.budget } : {}
19446
+ });
19447
+ return result.status;
19448
+ } catch {
19449
+ return "error";
19450
+ }
19451
+ }
18587
19452
  async function refreshAndFind(deps, vault, providerItemRef) {
18588
- await syncLinkedProviderVault({
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
- });
19453
+ const sync = await refreshAfterWrite(deps, vault);
18596
19454
  try {
18597
19455
  const ref = await getProviderVaultItemRef(deps.db, deps.context, {
18598
19456
  vaultId: vault.id,
18599
19457
  providerItemRef
18600
19458
  });
18601
- return ref.itemId;
19459
+ return { status: "listed", itemId: ref.itemId };
18602
19460
  } catch (error) {
18603
19461
  if (error instanceof VaultError && error.code === "vault_item_not_found") {
18604
- throw new VaultError(
18605
- "provider_unavailable",
18606
- "saved in the provider; Opengeni will show it after the next sync",
18607
- { vaultId: vault.id }
18608
- );
19462
+ return { status: "saved_not_listed", vaultId: vault.id, sync };
18609
19463
  }
18610
19464
  throw error;
18611
19465
  }
18612
19466
  }
18613
- async function writeProviderVaultItem(deps, input) {
19467
+ async function writeProviderVaultItemOutcome(deps, input) {
18614
19468
  const { vault, existing, change } = input;
18615
19469
  assertProviderVaultWritable(vault);
18616
- if ((change.type ?? existing?.type) === "passkey") {
19470
+ const type = change.type ?? existing?.type ?? "login";
19471
+ if (type === "passkey") {
18617
19472
  throw new VaultError("vault_permission_denied", "passkeys live only in Opengeni vaults");
18618
19473
  }
19474
+ const provider = await providerFor(deps, vault);
19475
+ const operations = effectiveProviderOperations(vault, provider);
19476
+ if (existing) {
19477
+ if (!operations.updateItem) {
19478
+ refuse(vault, "updateItem", `${vault.name} does not allow changing items here`);
19479
+ }
19480
+ } else if (!operations.createItemTypes.includes(type)) {
19481
+ refuse(
19482
+ vault,
19483
+ "createItem",
19484
+ operations.createItemTypes.length === 0 ? `${vault.name} does not allow creating items here` : `${vault.name} cannot hold ${type} items; it accepts ${operations.createItemTypes.join(", ")}`,
19485
+ { itemType: type, allowedItemTypes: operations.createItemTypes }
19486
+ );
19487
+ }
18619
19488
  let externalItemId;
18620
19489
  let expectedVersion;
18621
19490
  if (existing) {
@@ -18632,60 +19501,190 @@ async function writeProviderVaultItem(deps, input) {
18632
19501
  externalItemId = ref.providerItemRef;
18633
19502
  expectedVersion = ref.providerVersion;
18634
19503
  }
18635
- const fields = await providerFieldWrites(
19504
+ const planned = planFieldWrites(
19505
+ vault,
19506
+ operations,
19507
+ existing === null,
18636
19508
  change.fields ?? [],
18637
- existing?.fields ?? [],
18638
- deps.resolveReference,
18639
- input.generated ?? []
19509
+ existing?.fields ?? []
18640
19510
  );
18641
- const provider = await providerFor(deps, vault);
19511
+ const fields = await providerFieldValues(planned, deps.resolveReference, input.generated ?? []);
19512
+ const name = change.name ?? existing?.name ?? "";
18642
19513
  let written;
18643
19514
  try {
18644
- written = await provider.writeItem(
18645
- { connectionId: vault.providerConnectionId, budget: deps.budget ?? { take() {
18646
- } } },
18647
- vault.providerVaultRef ?? "",
19515
+ written = await provider.writeItem(mutationContext(deps, vault), vault.providerVaultRef ?? "", {
19516
+ ...externalItemId ? { externalItemId, expectedVersion: expectedVersion ?? null } : {},
19517
+ name,
19518
+ type,
19519
+ websites: change.websites ?? existing?.websites ?? [],
19520
+ fields
19521
+ });
19522
+ } catch (error) {
19523
+ return mutationFailure(vault, error, existing ? `the change to ${name}` : `saving ${name}`);
19524
+ }
19525
+ return await refreshAndFind(deps, vault, written.externalItemId);
19526
+ }
19527
+ function savedNotListedError(vault, itemName, outcome) {
19528
+ return new VaultError(
19529
+ "provider_inventory_incomplete",
19530
+ `Saved ${itemName} in ${vault.name}, but Opengeni could not list it yet. Do not save it again: it appears after the next successful sync.`,
19531
+ { saved: true, vaultId: outcome.vaultId, itemName, sync: outcome.sync }
19532
+ );
19533
+ }
19534
+ async function writeProviderVaultItem(deps, input) {
19535
+ const outcome = await writeProviderVaultItemOutcome(deps, input);
19536
+ if (outcome.status === "listed") return outcome.itemId;
19537
+ throw savedNotListedError(
19538
+ input.vault,
19539
+ input.change.name ?? input.existing?.name ?? "the item",
19540
+ outcome
19541
+ );
19542
+ }
19543
+ function providerArchiveKind(vault, provider) {
19544
+ const operations = effectiveProviderOperations(vault, provider);
19545
+ if (!operations.archiveItem) return null;
19546
+ return operations.archiveKind === "archive" || operations.archiveKind === "trash" ? operations.archiveKind : null;
19547
+ }
19548
+ async function assertProviderVaultAccepts(deps, vault, item) {
19549
+ assertProviderVaultWritable(vault);
19550
+ if (item.type === "passkey") {
19551
+ throw new VaultError("vault_permission_denied", "passkeys live only in Opengeni vaults");
19552
+ }
19553
+ if (item.fields.some((field) => field.kind === "file")) {
19554
+ throw new VaultError(
19555
+ "provider_unavailable",
19556
+ "files in connected vaults are read-only in Opengeni; copy the item into an Opengeni vault instead",
19557
+ { vaultId: vault.id, operation: "writeFiles" }
19558
+ );
19559
+ }
19560
+ const operations = effectiveProviderOperations(vault, await providerFor(deps, vault));
19561
+ if (!operations.createItemTypes.includes(item.type)) {
19562
+ refuse(
19563
+ vault,
19564
+ "createItem",
19565
+ operations.createItemTypes.length === 0 ? `${vault.name} does not allow creating items here` : `${vault.name} cannot hold ${item.type} items; it accepts ${operations.createItemTypes.join(", ")}`,
19566
+ { itemType: item.type, allowedItemTypes: operations.createItemTypes }
19567
+ );
19568
+ }
19569
+ if (item.fields.some((field) => field.kind === "totp") && !operations.createTotp) {
19570
+ refuse(vault, "createTotp", `${vault.name} does not allow saving one-time passwords`);
19571
+ }
19572
+ }
19573
+ async function assertProviderVaultReleases(deps, vault) {
19574
+ assertProviderVaultWritable(vault);
19575
+ const archiveKind = providerArchiveKind(vault, await providerFor(deps, vault));
19576
+ if (archiveKind === null) {
19577
+ refuse(
19578
+ vault,
19579
+ "archiveItem",
19580
+ `${vault.name} cannot archive the original recoverably, so the item cannot be moved out of it; copy it instead`
19581
+ );
19582
+ }
19583
+ return archiveKind;
19584
+ }
19585
+ async function observeProviderItemVersion(deps, input) {
19586
+ const { vault } = input;
19587
+ const provider = await providerFor(deps, vault);
19588
+ try {
19589
+ const inventory = await readProviderItemInventory2(
19590
+ provider,
18648
19591
  {
18649
- ...externalItemId ? { externalItemId, expectedVersion: expectedVersion ?? null } : {},
18650
- name: change.name ?? existing?.name ?? "",
18651
- type: change.type ?? existing?.type ?? "login",
18652
- websites: change.websites ?? existing?.websites ?? [],
18653
- fields
18654
- }
19592
+ connectionId: vault.providerConnectionId,
19593
+ budget: deps.budget ?? { take() {
19594
+ } },
19595
+ cacheEpoch: vault.providerCacheEpoch ?? null,
19596
+ forceRefresh: true
19597
+ },
19598
+ vault.providerVaultRef ?? ""
18655
19599
  );
19600
+ const item = inventory.items.find((meta) => meta.externalItemId === input.providerItemRef);
19601
+ if (!item) return { listed: false, consistent: inventory.consistent };
19602
+ return {
19603
+ listed: true,
19604
+ version: typeof item.version === "string" && item.version.length > 0 ? item.version : null
19605
+ };
18656
19606
  } catch (error) {
18657
- return providerFailure(error);
19607
+ if (isVaultProviderError3(error)) {
19608
+ throw new VaultError(vaultErrorCodeForProvider(error.code, "vault"), error.message, {
19609
+ vaultId: vault.id,
19610
+ ...error.retryAfterMs ? { retryAfterMs: error.retryAfterMs } : {}
19611
+ });
19612
+ }
19613
+ throw error;
18658
19614
  }
18659
- return await refreshAndFind(deps, vault, written.externalItemId);
18660
19615
  }
18661
19616
  async function archiveProviderVaultItem(deps, input) {
18662
- assertProviderVaultWritable(input.vault);
19617
+ const { vault } = input;
19618
+ assertProviderVaultWritable(vault);
18663
19619
  const ref = await getProviderVaultItemRef(deps.db, deps.context, {
18664
- vaultId: input.vault.id,
19620
+ vaultId: vault.id,
18665
19621
  itemId: input.itemId
18666
19622
  });
18667
- const provider = await providerFor(deps, input.vault);
19623
+ const provider = await providerFor(deps, vault);
19624
+ const archiveKind = providerArchiveKind(vault, provider);
19625
+ if (archiveKind === null) {
19626
+ refuse(vault, "archiveItem", `${vault.name} does not allow archiving items here`);
19627
+ }
18668
19628
  try {
18669
19629
  await provider.archiveItem(
18670
- { connectionId: input.vault.providerConnectionId, budget: deps.budget ?? { take() {
18671
- } } },
18672
- input.vault.providerVaultRef ?? "",
19630
+ mutationContext(deps, vault),
19631
+ vault.providerVaultRef ?? "",
18673
19632
  ref.providerItemRef
18674
19633
  );
18675
19634
  } catch (error) {
18676
- providerFailure(error);
19635
+ mutationFailure(vault, error, "archiving the item");
18677
19636
  }
18678
- await syncLinkedProviderVault({
18679
- db: deps.db,
18680
- context: deps.context,
18681
- vaultId: input.vault.id,
18682
- providers: deps.providers,
18683
- force: true,
18684
- ...deps.budget ? { budget: deps.budget } : {}
18685
- });
19637
+ await refreshAfterWrite(deps, vault);
19638
+ return { archiveKind };
18686
19639
  }
18687
19640
 
18688
19641
  // src/domain/vaults/vault-service.ts
19642
+ import { createHash as createHash6 } from "crypto";
19643
+ import {
19644
+ generatePassword as generatePassword2,
19645
+ isValidTotpInput as isValidTotpInput3,
19646
+ parseSecretReference,
19647
+ parseTotpInput as parseTotpInput2,
19648
+ secretNameKey as secretNameKey4,
19649
+ SecretReferenceError as SecretReferenceError2,
19650
+ totpCodeSync,
19651
+ totpRemainingMs,
19652
+ systemSecretClock,
19653
+ VAULT_LIMITS as VAULT_LIMITS3
19654
+ } from "@opengeni/contracts";
19655
+ import {
19656
+ archiveVaultItem,
19657
+ createVault,
19658
+ decryptEnvironmentValue as decryptEnvironmentValue3,
19659
+ deleteVault,
19660
+ deleteVaultItemPermanently,
19661
+ encryptEnvironmentValue as encryptEnvironmentValue2,
19662
+ ensureDefaultVault,
19663
+ ensureTaskVault,
19664
+ getSession as getSession6,
19665
+ getSessionSecretsView,
19666
+ getProviderVaultItemRef as getProviderVaultItemRef2,
19667
+ getVault,
19668
+ getVaultItem,
19669
+ isVaultError as isVaultError2,
19670
+ listVaultFieldVersions,
19671
+ listVaultItems,
19672
+ listVaultsForSubject as listVaultsForSubject2,
19673
+ vaultItemCursor,
19674
+ moveVaultItem,
19675
+ readVaultFieldCiphertext,
19676
+ resolveSecretReferenceCandidates as resolveSecretReferenceCandidates2,
19677
+ restoreVaultItem,
19678
+ setSessionVaultPolicy,
19679
+ updateVault,
19680
+ upsertVaultItem,
19681
+ VaultError as VaultError2
19682
+ } from "@opengeni/db";
19683
+ import {
19684
+ createProviderCallBudget as createProviderCallBudget2,
19685
+ EMPTY_VAULT_PROVIDER_REGISTRY as EMPTY_VAULT_PROVIDER_REGISTRY2,
19686
+ isVaultProviderError as isVaultProviderError4
19687
+ } from "@opengeni/vaults";
18689
19688
  function subjectContext(actor, workspaceId) {
18690
19689
  return {
18691
19690
  kind: "subject",
@@ -18722,13 +19721,13 @@ function requireKey(deps) {
18722
19721
  return deps.encryptionKey;
18723
19722
  }
18724
19723
  function defaultFieldKind(name) {
18725
- return ["username", "user", "email", "login", "account", "rpid"].includes(secretNameKey3(name)) ? "text" : "concealed";
19724
+ return ["username", "user", "email", "login", "account", "rpid"].includes(secretNameKey4(name)) ? "text" : "concealed";
18726
19725
  }
18727
19726
  function createVaultService(deps) {
18728
19727
  const clock = deps.clock ?? systemSecretClock;
18729
19728
  async function fieldWrite(input, existing, resolveReference, generated) {
18730
19729
  const current = existing?.fields.find(
18731
- (field) => input.fieldId !== void 0 && field.id === input.fieldId || secretNameKey3(field.name) === secretNameKey3(input.name)
19730
+ (field) => input.fieldId !== void 0 && field.id === input.fieldId || secretNameKey4(field.name) === secretNameKey4(input.name)
18732
19731
  );
18733
19732
  const name = current?.name ?? input.name;
18734
19733
  const write = { name };
@@ -18781,7 +19780,7 @@ function createVaultService(deps) {
18781
19780
  return {
18782
19781
  ...write,
18783
19782
  kind,
18784
- valueEncrypted: encryptEnvironmentValue(requireKey(deps), plaintext),
19783
+ valueEncrypted: encryptEnvironmentValue2(requireKey(deps), plaintext),
18785
19784
  valueSize: size
18786
19785
  };
18787
19786
  }
@@ -18848,6 +19847,11 @@ function createVaultService(deps) {
18848
19847
  function key() {
18849
19848
  return deps.encryptionKey;
18850
19849
  }
19850
+ async function refuseForConnectedItem(context, itemId, operation) {
19851
+ const item = await getVaultItem(deps.db, context, { itemId });
19852
+ if (item.vaultSource === "opengeni") return;
19853
+ refuseProviderOperation(await getVault(deps.db, context, { vaultId: item.vaultId }), operation);
19854
+ }
18851
19855
  async function moveAcrossProviders(input) {
18852
19856
  const source = await getVaultItem(deps.db, input.context, { itemId: input.itemId });
18853
19857
  const target = await getVault(deps.db, input.context, { vaultId: input.targetVaultId });
@@ -18873,6 +19877,46 @@ function createVaultService(deps) {
18873
19877
  }
18874
19878
  );
18875
19879
  }
19880
+ if (target.source !== "opengeni") {
19881
+ await assertProviderVaultAccepts(input.providerWrites, target, source);
19882
+ }
19883
+ if (input.mode === "move" && source.vaultSource !== "opengeni") {
19884
+ await assertProviderVaultReleases(input.providerWrites, sourceVault);
19885
+ }
19886
+ let pinned = null;
19887
+ if (input.mode === "move" && source.vaultSource !== "opengeni") {
19888
+ const mirrored = await getProviderVaultItemRef2(deps.db, input.context, {
19889
+ vaultId: sourceVault.id,
19890
+ itemId: source.id
19891
+ });
19892
+ const { providerItemRef } = mirrored;
19893
+ const observed = await observeProviderItemVersion(input.providerWrites, {
19894
+ vault: sourceVault,
19895
+ providerItemRef
19896
+ });
19897
+ if (!observed.listed) {
19898
+ throw new VaultError2(
19899
+ "vault_item_not_found",
19900
+ `${source.name} is not listed in ${sourceVault.name} right now; sync the vault and try again`,
19901
+ { itemId: source.id, vaultId: sourceVault.id }
19902
+ );
19903
+ }
19904
+ if (observed.version !== null) {
19905
+ if (mirrored.providerVersion !== observed.version || mirrored.generation !== source.generation) {
19906
+ throw new VaultError2(
19907
+ "provider_conflict",
19908
+ `${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.`,
19909
+ {
19910
+ itemId: source.id,
19911
+ vaultId: sourceVault.id,
19912
+ mirroredVersion: mirrored.providerVersion,
19913
+ providerVersion: observed.version
19914
+ }
19915
+ );
19916
+ }
19917
+ pinned = { providerItemRef, version: observed.version };
19918
+ }
19919
+ }
18876
19920
  const name = input.newName ?? source.name;
18877
19921
  const fields = [];
18878
19922
  for (const field of source.fields) {
@@ -18899,14 +19943,19 @@ function createVaultService(deps) {
18899
19943
  ...source.expiresAt ? { expiresAt: source.expiresAt } : {},
18900
19944
  fields
18901
19945
  };
18902
- let created;
19946
+ let created = null;
19947
+ let notListed = null;
18903
19948
  if (target.source !== "opengeni") {
18904
- const itemId = await writeProviderVaultItem(input.providerWrites, {
19949
+ const outcome = await writeProviderVaultItemOutcome(input.providerWrites, {
18905
19950
  vault: target,
18906
19951
  existing: null,
18907
19952
  change
18908
19953
  });
18909
- created = await getVaultItem(deps.db, input.context, { itemId });
19954
+ if (outcome.status === "listed") {
19955
+ created = await getVaultItem(deps.db, input.context, { itemId: outcome.itemId });
19956
+ } else {
19957
+ notListed = outcome;
19958
+ }
18910
19959
  } else {
18911
19960
  const write = await itemWrite(
18912
19961
  change,
@@ -18921,17 +19970,54 @@ function createVaultService(deps) {
18921
19970
  });
18922
19971
  }
18923
19972
  if (input.mode === "move") {
18924
- if (source.vaultSource !== "opengeni") {
18925
- await archiveProviderVaultItem(input.providerWrites, {
18926
- vault: sourceVault,
18927
- itemId: source.id
18928
- });
18929
- } else {
18930
- await archiveVaultItem(deps.db, input.context, { itemId: source.id });
19973
+ try {
19974
+ if (source.vaultSource !== "opengeni") {
19975
+ if (pinned) await assertSourceUnchanged(input.providerWrites, sourceVault, pinned);
19976
+ await archiveProviderVaultItem(input.providerWrites, {
19977
+ vault: sourceVault,
19978
+ itemId: source.id
19979
+ });
19980
+ } else {
19981
+ await archiveVaultItem(deps.db, input.context, { itemId: source.id });
19982
+ }
19983
+ } catch (error) {
19984
+ const changed = isVaultError2(error) && error.details?.sourceChanged === true;
19985
+ throw new VaultError2(
19986
+ isVaultError2(error) ? error.code : "provider_outcome_unknown",
19987
+ 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.`,
19988
+ {
19989
+ saved: true,
19990
+ sourceArchived: false,
19991
+ ...changed ? { sourceChanged: true } : {},
19992
+ vaultId: target.id,
19993
+ ...created ? { itemId: created.id } : {},
19994
+ sourceItemId: source.id,
19995
+ sourceVaultId: sourceVault.id
19996
+ }
19997
+ );
18931
19998
  }
18932
19999
  }
20000
+ if (notListed) {
20001
+ const error = savedNotListedError(target, name, notListed);
20002
+ throw new VaultError2(error.code, error.message, {
20003
+ ...error.details,
20004
+ ...input.mode === "move" ? { sourceArchived: true } : {}
20005
+ });
20006
+ }
18933
20007
  return created;
18934
20008
  }
20009
+ async function assertSourceUnchanged(providerWrites, vault, pinned) {
20010
+ const now = await observeProviderItemVersion(providerWrites, {
20011
+ vault,
20012
+ providerItemRef: pinned.providerItemRef
20013
+ });
20014
+ if (now.listed && now.version === pinned.version) return;
20015
+ throw new VaultError2(
20016
+ now.listed ? "vault_generation_conflict" : "vault_item_not_found",
20017
+ now.listed ? "changed after its values were read" : "is no longer listed at the version that was read",
20018
+ { sourceChanged: true, vaultId: vault.id }
20019
+ );
20020
+ }
18935
20021
  function toItem(row) {
18936
20022
  return toVaultItem(row, key());
18937
20023
  }
@@ -18951,7 +20037,12 @@ function createVaultService(deps) {
18951
20037
  });
18952
20038
  const budget = createProviderCallBudget2(VAULT_LIMITS3.providerReadsPerAttempt);
18953
20039
  const [value] = await provider.readFields(
18954
- { connectionId: read.providerConnectionId, budget },
20040
+ // The durable epoch invalidates provider value caches after a refresh anywhere.
20041
+ {
20042
+ connectionId: read.providerConnectionId,
20043
+ budget,
20044
+ cacheEpoch: read.providerCacheEpoch ?? null
20045
+ },
18955
20046
  read.providerVaultRef ?? "",
18956
20047
  read.providerItemRef ?? "",
18957
20048
  [read.providerFieldRef ?? ""]
@@ -19008,9 +20099,12 @@ function createVaultService(deps) {
19008
20099
  const vaults = await listVaultsForSubject2(deps.db, principalContext(principal), {
19009
20100
  workspaceId: principal.workspaceId
19010
20101
  });
20102
+ return agentVaultFrom(vaults, nameOrId);
20103
+ }
20104
+ function agentVaultFrom(vaults, nameOrId) {
19011
20105
  const byId = vaults.find((vault) => vault.id === nameOrId.toLowerCase());
19012
20106
  if (byId) return byId;
19013
- const matches = vaults.filter((vault) => secretNameKey3(vault.name) === secretNameKey3(nameOrId));
20107
+ const matches = vaults.filter((vault) => secretNameKey4(vault.name) === secretNameKey4(nameOrId));
19014
20108
  if (matches.length === 0) {
19015
20109
  throw new VaultError2("vault_not_found", `no reachable vault is named ${nameOrId}`);
19016
20110
  }
@@ -19237,12 +20331,14 @@ function createVaultService(deps) {
19237
20331
  await archiveVaultItem(deps.db, context, { itemId });
19238
20332
  },
19239
20333
  async restoreItem(actor, workspaceId, itemId) {
19240
- return toItem(
19241
- await restoreVaultItem(deps.db, subjectContext(actor, workspaceId), { itemId })
19242
- );
20334
+ const context = subjectContext(actor, workspaceId);
20335
+ await refuseForConnectedItem(context, itemId, "restoreItem");
20336
+ return toItem(await restoreVaultItem(deps.db, context, { itemId }));
19243
20337
  },
19244
20338
  async deleteItemPermanently(actor, workspaceId, itemId) {
19245
- await deleteVaultItemPermanently(deps.db, subjectContext(actor, workspaceId), { itemId });
20339
+ const context = subjectContext(actor, workspaceId);
20340
+ await refuseForConnectedItem(context, itemId, "deleteItem");
20341
+ await deleteVaultItemPermanently(deps.db, context, { itemId });
19246
20342
  },
19247
20343
  async moveItem(actor, workspaceId, itemId, req) {
19248
20344
  const crossed = await moveAcrossProviders({
@@ -19282,6 +20378,12 @@ function createVaultService(deps) {
19282
20378
  async restoreFieldVersion(actor, workspaceId, itemId, fieldId, version) {
19283
20379
  const context = subjectContext(actor, workspaceId);
19284
20380
  const existing = await getVaultItem(deps.db, context, { itemId });
20381
+ if (existing.vaultSource !== "opengeni") {
20382
+ refuseProviderOperation(
20383
+ await getVault(deps.db, context, { vaultId: existing.vaultId }),
20384
+ "restoreFieldHistory"
20385
+ );
20386
+ }
19285
20387
  const field = existing.fields.find((candidate) => candidate.id === fieldId);
19286
20388
  if (!field) throw new VaultError2("vault_field_not_found", "field not found", { fieldId });
19287
20389
  return toItem(
@@ -19318,26 +20420,37 @@ function createVaultService(deps) {
19318
20420
  let reachable = await listVaultsForSubject2(deps.db, context, {
19319
20421
  workspaceId: principal.workspaceId
19320
20422
  });
19321
- if (deps.providers) {
19322
- const changed = await refreshStaleLinkedVaults({
20423
+ let scoped = input.vault !== void 0 ? [agentVaultFrom(reachable, input.vault)] : reachable;
20424
+ let refreshing = /* @__PURE__ */ new Set();
20425
+ const connected = scoped.filter((vault) => vault.source !== "opengeni");
20426
+ if (deps.providers && connected.length > 0) {
20427
+ const report = await refreshLinkedVaults({
19323
20428
  db: deps.db,
19324
20429
  context,
19325
- vaults: reachable,
20430
+ vaults: connected,
19326
20431
  providers: deps.providers,
19327
- maxAgeMs: LINKED_VAULT_SEARCH_MAX_AGE_MS
20432
+ maxAgeMs: LINKED_VAULT_SEARCH_MAX_AGE_MS,
20433
+ ...input.refresh && input.cursor === void 0 ? { force: true } : {}
19328
20434
  });
19329
- if (changed.length > 0) {
20435
+ refreshing = new Set(report.pending);
20436
+ if (report.outcomes.some((outcome) => outcome.state === "done" || outcome.state === "failed")) {
19330
20437
  reachable = await listVaultsForSubject2(deps.db, context, {
19331
20438
  workspaceId: principal.workspaceId
19332
20439
  });
20440
+ scoped = scoped.map((vault) => reachable.find((row) => row.id === vault.id) ?? vault);
19333
20441
  }
19334
20442
  }
20443
+ const checkedAt = /* @__PURE__ */ new Date();
20444
+ const freshnessOf = (vault) => providerVaultFreshness(vault, checkedAt, { refreshing: refreshing.has(vault.id) });
20445
+ const sources = scoped.flatMap((vault) => {
20446
+ const freshness = freshnessOf(vault);
20447
+ return freshness ? [{ vaultId: vault.id, freshness }] : [];
20448
+ });
20449
+ const complete = sources.every((source) => source.freshness.state === "current");
19335
20450
  const byTier = (vaults2) => [...vaults2].sort(
19336
20451
  (left, right) => left.tier - right.tier || left.name.localeCompare(right.name)
19337
20452
  );
19338
- const vaults = byTier(
19339
- input.vault !== void 0 ? [await agentVault(principal, input.vault)] : reachable
19340
- );
20453
+ const vaults = byTier(scoped);
19341
20454
  const limit = input.limit ?? 20;
19342
20455
  const filters = agentSearchFilterKey(input);
19343
20456
  let startIndex = 0;
@@ -19403,15 +20516,21 @@ function createVaultService(deps) {
19403
20516
  truncated: next !== null,
19404
20517
  nextCursor: next === null ? null : encodeAgentSearchCursor(next),
19405
20518
  ...input.cursor === void 0 ? {
19406
- vaults: byTier(reachable).map((vault) => ({
19407
- id: vault.id,
19408
- name: vault.name,
19409
- ownerKind: vault.ownerKind,
19410
- source: vault.source,
19411
- mode: vault.mode,
19412
- itemCount: vault.itemCount
19413
- }))
19414
- } : {}
20519
+ vaults: byTier(reachable).map((vault) => {
20520
+ const freshness = freshnessOf(vault);
20521
+ return {
20522
+ id: vault.id,
20523
+ name: vault.name,
20524
+ ownerKind: vault.ownerKind,
20525
+ source: vault.source,
20526
+ mode: vault.mode,
20527
+ itemCount: vault.itemCount,
20528
+ ...freshness ? { freshness } : {}
20529
+ };
20530
+ })
20531
+ } : {},
20532
+ complete,
20533
+ sources
19415
20534
  };
19416
20535
  function agentSearchResult(vault, row) {
19417
20536
  const summary = toVaultItemSummary(row, key());
@@ -19446,7 +20565,7 @@ function createVaultService(deps) {
19446
20565
  q: input.item,
19447
20566
  limit: VAULT_LIMITS3.searchResultsMax
19448
20567
  });
19449
- const existing = existingPage.items.find((item) => secretNameKey3(item.name) === secretNameKey3(input.item)) ?? null;
20568
+ const existing = existingPage.items.find((item) => secretNameKey4(item.name) === secretNameKey4(input.item)) ?? null;
19450
20569
  if (input.mode === "create" && existing) {
19451
20570
  throw new VaultError2(
19452
20571
  "vault_item_name_conflict",
@@ -19554,7 +20673,7 @@ function createVaultService(deps) {
19554
20673
  const current = await getVaultItem(deps.db, context, { itemId: target.itemId });
19555
20674
  if (current.vaultSource !== "opengeni") {
19556
20675
  const vault = await getVault(deps.db, context, { vaultId: current.vaultId });
19557
- await archiveProviderVaultItem(
20676
+ const { archiveKind } = await archiveProviderVaultItem(
19558
20677
  {
19559
20678
  db: deps.db,
19560
20679
  context,
@@ -19565,7 +20684,7 @@ function createVaultService(deps) {
19565
20684
  );
19566
20685
  return {
19567
20686
  archived: true,
19568
- restoreHint: `A person can restore ${current.name} from the provider's archive.`
20687
+ 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
20688
  };
19570
20689
  }
19571
20690
  const row = await archiveVaultItem(deps.db, context, {
@@ -20040,39 +21159,39 @@ import {
20040
21159
  AutomationSessionTemplate,
20041
21160
  OPENGENI_PR_REVIEW_SESSION_ROLE
20042
21161
  } from "@opengeni/contracts";
20043
- import { z as z2 } from "zod";
21162
+ import { z as z3 } from "zod";
20044
21163
  var PR_REVIEW_AUTOMATION_ADAPTER_ID = "source-control.pull-request.v1";
20045
- var PrReviewSourceConfiguration = z2.object({
20046
- provider: z2.enum(["github", "gitlab", "azure_devops"]),
20047
- providerBaseUrl: z2.string().url().max(2048),
20048
- registrationId: z2.string().uuid(),
20049
- webhookUsername: z2.string().min(1).max(512).nullable().default(null)
21164
+ var PrReviewSourceConfiguration = z3.object({
21165
+ provider: z3.enum(["github", "gitlab", "azure_devops"]),
21166
+ providerBaseUrl: z3.string().url().max(2048),
21167
+ registrationId: z3.string().uuid(),
21168
+ webhookUsername: z3.string().min(1).max(512).nullable().default(null)
20050
21169
  }).strict();
20051
- var PrReviewTriggerParameters = z2.object({
20052
- registrationId: z2.string().uuid(),
20053
- repositoryBindingId: z2.string().uuid(),
20054
- provider: z2.enum(["github", "gitlab", "azure_devops"]),
20055
- repositoryUri: z2.string().url().max(2048),
20056
- repositoryFullName: z2.string().min(1).max(1024),
20057
- providerRepositoryId: z2.string().min(1).max(512),
20058
- installationId: z2.string().min(1).max(512).nullable(),
20059
- projectId: z2.string().min(1).max(512).nullable(),
20060
- model: z2.string().min(1).max(512).nullable(),
20061
- additionalInstructions: z2.string().max(16384).nullable()
21170
+ var PrReviewTriggerParameters = z3.object({
21171
+ registrationId: z3.string().uuid(),
21172
+ repositoryBindingId: z3.string().uuid(),
21173
+ provider: z3.enum(["github", "gitlab", "azure_devops"]),
21174
+ repositoryUri: z3.string().url().max(2048),
21175
+ repositoryFullName: z3.string().min(1).max(1024),
21176
+ providerRepositoryId: z3.string().min(1).max(512),
21177
+ installationId: z3.string().min(1).max(512).nullable(),
21178
+ projectId: z3.string().min(1).max(512).nullable(),
21179
+ model: z3.string().min(1).max(512).nullable(),
21180
+ additionalInstructions: z3.string().max(16384).nullable()
20062
21181
  }).strict();
20063
- var PrReviewEventPayload = z2.object({
20064
- provider: z2.enum(["github", "gitlab", "azure_devops"]),
20065
- eventName: z2.string().max(200),
20066
- action: z2.string().max(200),
20067
- providerRepositoryId: z2.string().max(512).nullable(),
20068
- installationId: z2.string().max(512).nullable(),
20069
- projectId: z2.string().max(512).nullable(),
20070
- pullRequestId: z2.string().max(512).nullable(),
20071
- headSha: z2.string().regex(/^[0-9a-f]{40}$/).nullable(),
20072
- baseSha: z2.string().regex(/^[0-9a-f]{40}$/).nullable(),
20073
- headRef: z2.string().max(1024).nullable(),
20074
- baseRef: z2.string().max(1024).nullable(),
20075
- ignoredReason: z2.string().max(200).nullable()
21182
+ var PrReviewEventPayload = z3.object({
21183
+ provider: z3.enum(["github", "gitlab", "azure_devops"]),
21184
+ eventName: z3.string().max(200),
21185
+ action: z3.string().max(200),
21186
+ providerRepositoryId: z3.string().max(512).nullable(),
21187
+ installationId: z3.string().max(512).nullable(),
21188
+ projectId: z3.string().max(512).nullable(),
21189
+ pullRequestId: z3.string().max(512).nullable(),
21190
+ headSha: z3.string().regex(/^[0-9a-f]{40}$/).nullable(),
21191
+ baseSha: z3.string().regex(/^[0-9a-f]{40}$/).nullable(),
21192
+ headRef: z3.string().max(1024).nullable(),
21193
+ baseRef: z3.string().max(1024).nullable(),
21194
+ ignoredReason: z3.string().max(200).nullable()
20076
21195
  }).strict();
20077
21196
  var OPENGENI_PR_REVIEW_SKILL = {
20078
21197
  name: "pr-review",
@@ -21044,7 +22163,7 @@ import {
21044
22163
  saveAgentLearningSettings as saveAgentLearningSettings2,
21045
22164
  dbSql,
21046
22165
  deleteScheduledTask,
21047
- getConnectionMetadata as getConnectionMetadata5,
22166
+ getConnectionMetadata as getConnectionMetadata6,
21048
22167
  getEnrollment as getEnrollment3,
21049
22168
  getLiveEnrollmentConnection as getLiveEnrollmentConnection3,
21050
22169
  getKnowledgeSourceForSyncAuthority,
@@ -21070,7 +22189,7 @@ import {
21070
22189
  import { HTTPException as HTTPException32 } from "hono/http-exception";
21071
22190
  import { GOOGLE_DRIVE_PUBLICATION_SERVER_ID as GOOGLE_DRIVE_PUBLICATION_SERVER_ID3 } from "@opengeni/contracts/google-drive";
21072
22191
  import { PERSONAL_GITHUB_CONNECTION_SURFACE_ID as PERSONAL_GITHUB_CONNECTION_SURFACE_ID3 } from "@opengeni/contracts/personal-github";
21073
- import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
22192
+ import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
21074
22193
 
21075
22194
  // src/domain/scheduled-task-audience.ts
21076
22195
  import {
@@ -21185,7 +22304,7 @@ async function resolveScheduledTaskVisibility(input) {
21185
22304
 
21186
22305
  // src/domain/scheduled-task-account-audit.ts
21187
22306
  import { recordAuditEvent as recordAuditEvent2 } from "@opengeni/db";
21188
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
22307
+ import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
21189
22308
  var SCHEDULED_TASK_ACCOUNT_CHOICES_AUDIT_ACTION = "scheduled_task.account_choices_changed";
21190
22309
  function choicesOf(config) {
21191
22310
  return {
@@ -21205,7 +22324,7 @@ function scheduledTaskAccountChoiceChanges(before, after) {
21205
22324
  const next = choicesOf(after);
21206
22325
  const changes = {};
21207
22326
  for (const key of ["pinnedAccounts", "pinnedAccountsRequired", "connectors"]) {
21208
- if (!isDeepStrictEqual3(prior[key], next[key]))
22327
+ if (!isDeepStrictEqual4(prior[key], next[key]))
21209
22328
  changes[key] = { before: prior[key], after: next[key] };
21210
22329
  }
21211
22330
  return Object.keys(changes).length > 0 ? changes : null;
@@ -22193,7 +23312,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
22193
23312
  throw new HTTPException32(422, { message: "Source ingestion uses an ordinary agent task" });
22194
23313
  }
22195
23314
  const knowledgeSource = input.payload.agentConfig?.knowledgeSource ?? scheduledTaskKnowledgeSource(input.existing);
22196
- if (knowledgeSource && requestedKnowledgeSource && !isDeepStrictEqual4(requestedKnowledgeSource, existingKnowledgeSource)) {
23315
+ if (knowledgeSource && requestedKnowledgeSource && !isDeepStrictEqual5(requestedKnowledgeSource, existingKnowledgeSource)) {
22197
23316
  await validateKnowledgeSourceSyncAction({
22198
23317
  db: input.db,
22199
23318
  grant: input.grant,
@@ -22330,7 +23449,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
22330
23449
  const pins = nextRunMode === "existing_session" ? [] : input.payload.pinnedAccounts ?? input.existing.agentConfig.pinnedAccounts ?? [];
22331
23450
  const required = input.payload.pinnedAccountsRequired ?? input.existing.agentConfig.pinnedAccountsRequired === true;
22332
23451
  const next = withScheduledTaskPins(base, pins, required);
22333
- if (update.agentConfig || !isDeepStrictEqual4(next, input.existing.agentConfig))
23452
+ if (update.agentConfig || !isDeepStrictEqual5(next, input.existing.agentConfig))
22334
23453
  update.agentConfig = next;
22335
23454
  }
22336
23455
  const nextAgentConfig = {
@@ -22359,7 +23478,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
22359
23478
  verifySlackChannel: input.verifySlackChannel
22360
23479
  });
22361
23480
  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 || !isDeepStrictEqual4(nextAgentConfig, input.existing.agentConfig) || input.payload.action !== void 0 && !isDeepStrictEqual4(input.payload.action, input.existing.action) || input.payload.schedule !== void 0 && !isDeepStrictEqual4(input.payload.schedule, input.existing.schedule) || input.payload.overlapPolicy !== void 0 && input.payload.overlapPolicy !== input.existing.overlapPolicy || input.payload.metadata !== void 0 && !isDeepStrictEqual4(input.payload.metadata, input.existing.metadata) || input.existing.status === "paused" && input.payload.status === "active";
23481
+ 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
23482
  if (materialExecutionChange && nextRunMode !== "existing_session" && (!materializedReusable || nextAgentConfig.model !== input.existing.agentConfig.model)) {
22364
23483
  const beforeUpdateCommit = workspaceCustomModelCommitGuard({
22365
23484
  settings: input.settings,
@@ -22512,7 +23631,7 @@ async function validatedScheduledTaskUpdateWithoutAudience(input) {
22512
23631
  rigId: input.payload.rigId !== void 0 ? input.payload.rigId : input.existing.rigId,
22513
23632
  agentConfig: update.agentConfig ?? input.existing.agentConfig
22514
23633
  });
22515
- if (!knowledgeSource && !(materializedReusable && isDeepStrictEqual4(nextAgentConfig.machineTarget, input.existing.agentConfig.machineTarget) && nextAgentConfig.sandboxBackend === input.existing.agentConfig.sandboxBackend)) {
23634
+ if (!knowledgeSource && !(materializedReusable && isDeepStrictEqual5(nextAgentConfig.machineTarget, input.existing.agentConfig.machineTarget) && nextAgentConfig.sandboxBackend === input.existing.agentConfig.sandboxBackend)) {
22516
23635
  await validateScheduledTaskMachineTarget({
22517
23636
  settings: input.settings,
22518
23637
  db: input.db,
@@ -22590,7 +23709,7 @@ async function validateKnowledgeSourceSyncAction(input) {
22590
23709
  message: "knowledge source authority or generation changed"
22591
23710
  });
22592
23711
  }
22593
- const connection = await getConnectionMetadata5(
23712
+ const connection = await getConnectionMetadata6(
22594
23713
  input.db,
22595
23714
  input.grant.workspaceId,
22596
23715
  input.action.connection.connectionId,
@@ -22681,7 +23800,7 @@ async function requireUnchangedScheduledTaskForCompensation(db, expected) {
22681
23800
  );
22682
23801
  if (!current) throw new ScheduledTaskHeadChangedError();
22683
23802
  const { deletedAt, ...task } = current;
22684
- if (deletedAt !== null || !isDeepStrictEqual4(task, expected))
23803
+ if (deletedAt !== null || !isDeepStrictEqual5(task, expected))
22685
23804
  throw new ScheduledTaskHeadChangedError();
22686
23805
  }
22687
23806
  async function syncUpdatedScheduledTask(input) {
@@ -22748,7 +23867,7 @@ async function validateScheduledTaskAgentConfig(input) {
22748
23867
  },
22749
23868
  toolsProvided: true
22750
23869
  };
22751
- const unchangedCreationField = (key) => input.retainedCreationConfig !== void 0 && isDeepStrictEqual4(input.payload.agentConfig[key], input.retainedCreationConfig[key]);
23870
+ const unchangedCreationField = (key) => input.retainedCreationConfig !== void 0 && isDeepStrictEqual5(input.payload.agentConfig[key], input.retainedCreationConfig[key]);
22752
23871
  const actor = scheduledTaskInitiatorForGrant(input.grant).actor;
22753
23872
  const parent = actor && !existingChat ? await getSession8(input.db, input.workspaceId, actor.sessionId) : null;
22754
23873
  if (!existingChat && actor && (!parent || parent.accountId !== input.grant.accountId)) {
@@ -22871,7 +23990,7 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
22871
23990
  });
22872
23991
  }
22873
23992
  for (const required of preflight.requiredResources) {
22874
- if (!agentConfig.resources.some((selected) => isDeepStrictEqual4(selected, required))) {
23993
+ if (!agentConfig.resources.some((selected) => isDeepStrictEqual5(selected, required))) {
22875
23994
  throw new HTTPException32(422, {
22876
23995
  message: "incidentTelemetryPreflight.requiredResources must be exact selected resources"
22877
23996
  });
@@ -22971,7 +24090,7 @@ import {
22971
24090
  ScheduledTaskHeadChangedError as ScheduledTaskHeadChangedError2
22972
24091
  } from "@opengeni/db";
22973
24092
  import { HTTPException as HTTPException33 } from "hono/http-exception";
22974
- import { z as z3 } from "zod";
24093
+ import { z as z4 } from "zod";
22975
24094
  var ALWAYS_ATTACHED_CONNECTOR = "opengeni";
22976
24095
  var PRESERVED_ACCOUNT_SURFACES = /* @__PURE__ */ new Set([
22977
24096
  GOOGLE_DRIVE_PUBLICATION_SERVER_ID4,
@@ -22986,10 +24105,10 @@ function uniqueById(items) {
22986
24105
  return items.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true)).slice(0, SCHEDULED_TASK_ACCESS_CONNECTORS_MAX);
22987
24106
  }
22988
24107
  function planScheduledTaskConnectors(input) {
22989
- const unavailable = input.taskTools.filter(
24108
+ const unavailable2 = input.taskTools.filter(
22990
24109
  (tool) => tool.id !== ALWAYS_ATTACHED_CONNECTOR && !input.availableServerIds.has(tool.id)
22991
24110
  );
22992
- const unavailableIds = new Set(unavailable.map((tool) => tool.id));
24111
+ const unavailableIds = new Set(unavailable2.map((tool) => tool.id));
22993
24112
  const kept = input.taskTools.filter((tool) => !unavailableIds.has(tool.id));
22994
24113
  const keptIds = new Set(kept.map((tool) => tool.id));
22995
24114
  const missing = input.defaultTools.filter(
@@ -22998,7 +24117,7 @@ function planScheduledTaskConnectors(input) {
22998
24117
  return {
22999
24118
  tools: mergeToolRefs3([...kept], [...missing]),
23000
24119
  missing: uniqueById(missing.map((tool) => connector(tool.id, input.names))),
23001
- unavailable: uniqueById(unavailable.map((tool) => connector(tool.id, input.names)))
24120
+ unavailable: uniqueById(unavailable2.map((tool) => connector(tool.id, input.names)))
23002
24121
  };
23003
24122
  }
23004
24123
  function planScheduledTaskPinnedAccounts(input) {
@@ -23009,7 +24128,7 @@ function planScheduledTaskPinnedAccounts(input) {
23009
24128
  )
23010
24129
  );
23011
24130
  const kept = [];
23012
- const unavailable = [];
24131
+ const unavailable2 = [];
23013
24132
  const dropped = [];
23014
24133
  for (const pin of input.pins) {
23015
24134
  if (PRESERVED_ACCOUNT_SURFACES.has(pin.serverId)) {
@@ -23022,11 +24141,11 @@ function planScheduledTaskPinnedAccounts(input) {
23022
24141
  }
23023
24142
  kept.push(pin);
23024
24143
  if (!available.has(JSON.stringify([pin.serverId, pin.connectionId])))
23025
- unavailable.push(connector(pin.serverId, input.names));
24144
+ unavailable2.push(connector(pin.serverId, input.names));
23026
24145
  }
23027
24146
  return {
23028
24147
  kept,
23029
- unavailable: uniqueById(unavailable),
24148
+ unavailable: uniqueById(unavailable2),
23030
24149
  dropped: uniqueById(dropped)
23031
24150
  };
23032
24151
  }
@@ -23081,13 +24200,13 @@ function planScheduledTaskOpenGeniTools(input) {
23081
24200
  policy: unchanged ? null : { firstPartyMcpTools: tools, firstPartyMcpPermissions: permissions }
23082
24201
  };
23083
24202
  }
23084
- var AuthNeededFact = z3.object({
23085
- serverId: z3.string().min(1),
23086
- canonicalServerId: z3.string().min(1).optional(),
23087
- providerDomain: z3.string().min(1),
24203
+ var AuthNeededFact = z4.object({
24204
+ serverId: z4.string().min(1),
24205
+ canonicalServerId: z4.string().min(1).optional(),
24206
+ providerDomain: z4.string().min(1),
23088
24207
  reason: ToolAuthNeededReason,
23089
- capability: z3.unknown().optional(),
23090
- setupRequest: z3.unknown().optional()
24208
+ capability: z4.unknown().optional(),
24209
+ setupRequest: z4.unknown().optional()
23091
24210
  });
23092
24211
  function scheduledTaskRunAccessFailures(events, names) {
23093
24212
  const byKey = /* @__PURE__ */ new Map();
@@ -24323,7 +25442,14 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
24323
25442
  }
24324
25443
 
24325
25444
  // src/domain/insights-usage.ts
24326
- import { readInsightsCalls, readInsightsUsage } from "@opengeni/db";
25445
+ import {
25446
+ readInsightsCalls,
25447
+ readInsightsUsage,
25448
+ readInsightsSiteUsage
25449
+ } from "@opengeni/db";
25450
+ async function getInsightsSiteUsage(db, input) {
25451
+ return readInsightsSiteUsage(db, { ...input, now: input.now ?? /* @__PURE__ */ new Date() });
25452
+ }
24327
25453
  async function getInsightsUsage(db, input) {
24328
25454
  return await readInsightsUsage(db, { ...input, now: input.now ?? /* @__PURE__ */ new Date() });
24329
25455
  }
@@ -27246,6 +28372,40 @@ async function controlHumanWorkspaceTimer(deps, context, input) {
27246
28372
  ]);
27247
28373
  }
27248
28374
 
28375
+ // src/application/site-session-cancellation.ts
28376
+ import { cancelSiteRuntimeConversation } from "@opengeni/db";
28377
+ import {
28378
+ publishDurableSessionEvents as publishDurableSessionEvents4,
28379
+ publishDurableWorkspaceControlEvent as publishDurableWorkspaceControlEvent3
28380
+ } from "@opengeni/events";
28381
+ async function cancelPublishedSiteSession(deps, input) {
28382
+ const result = await cancelSiteRuntimeConversation(deps.db, input);
28383
+ if (!result) return null;
28384
+ const bySession = /* @__PURE__ */ new Map();
28385
+ for (const event of result.events) {
28386
+ const group = bySession.get(event.sessionId) ?? [];
28387
+ group.push(event);
28388
+ bySession.set(event.sessionId, group);
28389
+ }
28390
+ const effects = await Promise.allSettled([
28391
+ ...[...bySession].map(
28392
+ ([sessionId, events]) => publishDurableSessionEvents4(deps.bus, input.workspaceId, sessionId, events)
28393
+ ),
28394
+ ...result.workspaceEvent ? [publishDurableWorkspaceControlEvent3(deps.bus, input.workspaceId, result.workspaceEvent)] : [],
28395
+ ...result.wakeCount > 0 ? [deps.workflowClient.requestSessionWorkflowWakeDispatch()] : []
28396
+ ]);
28397
+ if (effects.some((effect) => effect.status === "rejected"))
28398
+ console.warn(
28399
+ "[site-lifecycle] post-commit fanout failed; durable events and wake outbox retained"
28400
+ );
28401
+ return {
28402
+ receiptId: result.receiptId,
28403
+ replay: result.replay,
28404
+ cancelledSessionCount: result.cancelledSessionCount,
28405
+ cancelledTurnCount: result.cancelledTurnCount
28406
+ };
28407
+ }
28408
+
27249
28409
  // src/application/archived-session-imports.ts
27250
28410
  import {
27251
28411
  AppendArchivedSessionEventsRequest,
@@ -27260,7 +28420,7 @@ import {
27260
28420
  importArchivedSession,
27261
28421
  withSessionRlsActorContext as withSessionRlsActorContext7
27262
28422
  } from "@opengeni/db";
27263
- import { publishDurableSessionEvents as publishDurableSessionEvents4 } from "@opengeni/events";
28423
+ import { publishDurableSessionEvents as publishDurableSessionEvents5 } from "@opengeni/events";
27264
28424
  import { HTTPException as HTTPException38 } from "hono/http-exception";
27265
28425
  import { ArchivedSessionImportError as ArchivedSessionImportError2 } from "@opengeni/db";
27266
28426
  function requireImportAuthorization(authorization, workspaceId, permission) {
@@ -27291,7 +28451,7 @@ function importerScope(authorization, workspaceId, permission, fileOwnerSubjectI
27291
28451
  async function publishImportedEvents(deps, workspaceId, sessionId, events) {
27292
28452
  if (!events.length) return;
27293
28453
  try {
27294
- await publishDurableSessionEvents4(deps.bus, workspaceId, sessionId, events);
28454
+ await publishDurableSessionEvents5(deps.bus, workspaceId, sessionId, events);
27295
28455
  } catch {
27296
28456
  console.warn("[session-import] committed timeline fanout deferred to replay", { sessionId });
27297
28457
  }
@@ -27860,7 +29020,7 @@ import { createPinnedIntegrationTransport as createPinnedIntegrationTransport2 }
27860
29020
  import { mcpToolRecommendsApproval } from "@opengeni/contracts";
27861
29021
  import {
27862
29022
  buildConnectionTokenResolver,
27863
- getConnectionMetadata as getConnectionMetadata6,
29023
+ getConnectionMetadata as getConnectionMetadata7,
27864
29024
  listEnabledMcpCapabilityServers as listEnabledMcpCapabilityServers3,
27865
29025
  listConnectorToolPermissionPolicies,
27866
29026
  listChangedConnectorToolPermissions,
@@ -27913,7 +29073,7 @@ async function resolveTarget2(input) {
27913
29073
  const server2 = settingsWithApiIntegrationServers({ ...input.settings, mcpServers: [] }, [
27914
29074
  integration
27915
29075
  ]).mcpServers[0];
27916
- const connection2 = integration.connectionRef?.connectionId ? await getConnectionMetadata6(
29076
+ const connection2 = integration.connectionRef?.connectionId ? await getConnectionMetadata7(
27917
29077
  input.db,
27918
29078
  input.workspaceId,
27919
29079
  integration.connectionRef.connectionId,
@@ -28269,6 +29429,7 @@ export {
28269
29429
  EDITABLE_ARTIFACT_OPERATION_PROTOCOL_VERSION,
28270
29430
  EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES,
28271
29431
  EXTERNAL_FIRST_USE_MEMBER_PERMISSIONS,
29432
+ EXTERNAL_VAULT_CREDENTIAL_ROLE,
28272
29433
  EditableArtifactAgentApplication,
28273
29434
  EditableArtifactApplication,
28274
29435
  EditableArtifactCausalChainError,
@@ -28309,6 +29470,7 @@ export {
28309
29470
  KnowledgeVectorQueryRejectedError,
28310
29471
  LINKED_VAULT_MISS_MAX_AGE_MS,
28311
29472
  LINKED_VAULT_OPEN_WAIT_MS,
29473
+ LINKED_VAULT_REFRESH_TIMEOUT_MS,
28312
29474
  LINKED_VAULT_REFRESH_WAIT_MS,
28313
29475
  LINKED_VAULT_SEARCH_MAX_AGE_MS,
28314
29476
  MAX_CHECKS_PER_RIG,
@@ -28378,6 +29540,7 @@ export {
28378
29540
  SessionTenancyManagedHumanRequiredError,
28379
29541
  SessionTenancyPersistenceOutcomeUnknownError,
28380
29542
  SessionUsageLimitHttpError,
29543
+ StoredVaultProviderCredentials,
28381
29544
  SystemEditableArtifactClock,
28382
29545
  SystemEditableArtifactLiveClock,
28383
29546
  SystemEditableArtifactLiveScheduler,
@@ -28413,6 +29576,7 @@ export {
28413
29576
  agentAccessPermitsCrossTreeAccess,
28414
29577
  agentConfigParentFor,
28415
29578
  agentRunAdmissionDenial,
29579
+ aggregateProviderFreshness,
28416
29580
  apiIntegrationsMatchingDelegations,
28417
29581
  appendArchivedSessionEventsForRequest,
28418
29582
  appendRigSetupCommand,
@@ -28455,12 +29619,14 @@ export {
28455
29619
  automationRequestDigest,
28456
29620
  availableMcpAccountBindings,
28457
29621
  availableToolRefs,
29622
+ bindVaultProviderAuthority,
28458
29623
  buildApiIntegrationMcpServers,
28459
29624
  buildAutomationAcceptedExecution,
28460
29625
  buildCapabilityCatalog,
28461
29626
  buildFleetContextForSession,
28462
29627
  buildMissingSecretAskCard2 as buildMissingSecretAskCard,
28463
29628
  cancelExternalWorkspaceMemberGrantForRequest,
29629
+ cancelPublishedSiteSession,
28464
29630
  canonicalConfiguredModel,
28465
29631
  canonicalConversationDeliveryCommandJson,
28466
29632
  canonicalConversationDeliveryOutcomeJson,
@@ -28499,8 +29665,10 @@ export {
28499
29665
  createCompanyProfileAgentAdminRouter,
28500
29666
  createCompanyProfileDurableLearningAdapter,
28501
29667
  createGitHubSkillSourceClient,
29668
+ createPinnedRemoteVaultTransport,
28502
29669
  createPublicSkillSearchClient,
28503
29670
  createRealtimeVoiceBilling,
29671
+ createRemoteExternalVaultFactories,
28504
29672
  createRigForApi,
28505
29673
  createRigVersionForApi,
28506
29674
  createRunOnSecretResolver,
@@ -28566,6 +29734,7 @@ export {
28566
29734
  editableArtifactTransactionId,
28567
29735
  effectiveFirstPartyMcpToolCeiling,
28568
29736
  effectiveOrganizationPrivacy,
29737
+ effectiveProviderOperations,
28569
29738
  enableCapability,
28570
29739
  enabledCapabilityMcpToolRefs,
28571
29740
  encodeEditableArtifactBrokerHint,
@@ -28581,6 +29750,10 @@ export {
28581
29750
  externalActorContinuationForAuthorization,
28582
29751
  externalAttributionForAuthorization,
28583
29752
  externalContinuationCommitAuthorizer,
29753
+ externalVaultConnectionDetails,
29754
+ externalVaultConnectionMetadata,
29755
+ externalVaultProviderDomain,
29756
+ externalVaultProviderForConnection,
28584
29757
  fikenConnectionMetadata,
28585
29758
  fileOwnerContextForAccess,
28586
29759
  fileOwnerContextForAgent,
@@ -28596,6 +29769,7 @@ export {
28596
29769
  getAutomationAdapter,
28597
29770
  getConnectorToolPermissions,
28598
29771
  getHumanComposerDraft,
29772
+ getInsightsSiteUsage,
28599
29773
  getInsightsUsage,
28600
29774
  getManagedAuthRequestActorAbortSignal,
28601
29775
  getManagedAuthRequestActorAdmissionStamp,
@@ -28637,6 +29811,7 @@ export {
28637
29811
  installSkill,
28638
29812
  integrationKeyForConnectProvider,
28639
29813
  integrationSourceForOrganizationPolicy,
29814
+ intersectProviderOperations,
28640
29815
  isAcceptedMimeType,
28641
29816
  isAuthenticatedPersonAuthorization,
28642
29817
  isAuthoritativeGitHubRepositorySelectionError,
@@ -28644,6 +29819,7 @@ export {
28644
29819
  isDeveloperSetupAuthorization,
28645
29820
  isDeveloperSetupDelegatedPermissionAllowed,
28646
29821
  isDeveloperSetupGrant,
29822
+ isExternalVaultConnection,
28647
29823
  isFikenConnection,
28648
29824
  isOnePasswordConnection,
28649
29825
  isOpenGeniSlackBotConnection,
@@ -28651,7 +29827,7 @@ export {
28651
29827
  isTerminalVideoGenerationState,
28652
29828
  isTrustedScheduledSlackBotSession,
28653
29829
  isUserMember,
28654
- isVaultError,
29830
+ isVaultError3 as isVaultError,
28655
29831
  isVerifiedDelegatedHumanAuthorization,
28656
29832
  isVerifiedOrganizationServiceAuthorization,
28657
29833
  isWorkspaceCustomModelId,
@@ -28677,6 +29853,7 @@ export {
28677
29853
  listSkills,
28678
29854
  loadCoreRoutedClaudeCatalogReadiness,
28679
29855
  loadCoreRoutedXaiCatalogReadiness,
29856
+ loadExternalVaultConnection,
28680
29857
  loadOnePasswordConnectionToken,
28681
29858
  loadWorkspaceClaudeSubscriptionReadiness,
28682
29859
  loadWorkspaceCodexCatalogReadiness,
@@ -28731,6 +29908,7 @@ export {
28731
29908
  parseSkillSource,
28732
29909
  pausePrivateScheduledTaskAsAdmin,
28733
29910
  pauseScheduledTaskAsAdmin,
29911
+ pendingVaultCredentialBundle,
28734
29912
  personalAtlassianDelegationsFromVisibleConnections,
28735
29913
  personalConnectionDelegationForServer,
28736
29914
  personalConnectionDelegationSourceForGrant,
@@ -28771,10 +29949,12 @@ export {
28771
29949
  promoteSetupAppendChange,
28772
29950
  promoteVerifiedDefinitionEditChangeForApi,
28773
29951
  proposeRigChangeForApi,
29952
+ providerOperationsForMode,
28774
29953
  providerPrimaryFieldId,
28775
29954
  providerSecretName,
28776
29955
  providerSettingsForSessionSandboxRuntime,
28777
29956
  providerSyncFailure,
29957
+ providerVaultFreshness,
28778
29958
  provisionSandbox,
28779
29959
  publishGovernedLearningEventToSlack,
28780
29960
  readSessionAttachmentFiles,
@@ -28790,6 +29970,7 @@ export {
28790
29970
  recordUserMessageAccepted,
28791
29971
  recordWorkspaceUsage,
28792
29972
  refreshCoreCodexModelEntitlements,
29973
+ refreshLinkedVaults,
28793
29974
  refreshScheduledTaskAccess,
28794
29975
  refreshStaleLinkedVaults,
28795
29976
  registerAutomationAdapter,
@@ -28969,9 +30150,11 @@ export {
28969
30150
  stableJson,
28970
30151
  stampDelegatedHumanAuthorization,
28971
30152
  stampPersonalApiKeyRequest,
30153
+ stampSiteViewerSessionAuthorization,
28972
30154
  statusForVoiceInputError,
28973
30155
  steerAgentSession,
28974
30156
  steerHumanQueuePrompt,
30157
+ storedProviderOperations,
28975
30158
  submitComposerDraftForRequest,
28976
30159
  suggestCapabilityCatalogItems,
28977
30160
  swapActiveSandbox,
@@ -29028,6 +30211,7 @@ export {
29028
30211
  vaultErrorCodeForProvider,
29029
30212
  vaultErrorFromDatabase,
29030
30213
  vaultItemReferences,
30214
+ vaultRefreshScopeKey,
29031
30215
  verifiedDelegatedHumanAuthorizationForRequest,
29032
30216
  verifyDirectModelAccess,
29033
30217
  verifyPrReviewWebhook,