@opengeni/worker-bundle 0.29.0-canary.0 → 0.29.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activities/agent-turn/knowledge-source-tools.d.ts +9 -0
- package/dist/activities/agent-turn/knowledge-sources.d.ts +20 -0
- package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
- package/dist/activities/agent-turn/tool-environment.d.ts +1 -0
- package/dist/activities/knowledge-indexing.d.ts +10 -0
- package/dist/activities/run-input.d.ts +1 -0
- package/dist/activities/types.d.ts +10 -3
- package/dist/{activities-control-MKGEOBXT.js → activities-control-HMFGSKC5.js} +331 -3381
- package/dist/activities-control-HMFGSKC5.js.map +1 -0
- package/dist/activities-control.d.ts +12 -0
- package/dist/{activities-turn-5MZZ6HQA.js → activities-turn-AP67JADS.js} +1296 -1021
- package/dist/activities-turn-AP67JADS.js.map +1 -0
- package/dist/activities.d.ts +12 -0
- package/dist/{chunk-OA5CSY5N.js → chunk-CYITU5UH.js} +3348 -174
- package/dist/chunk-CYITU5UH.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -1
- package/dist/workflow-bundle.js +65 -35
- package/dist/workflows/knowledge-indexing.d.ts +2 -0
- package/dist/workflows.d.ts +1 -0
- package/package.json +19 -18
- package/src/activities/agent-turn/knowledge-source-tools.ts +98 -0
- package/src/activities/agent-turn/knowledge-sources.ts +86 -0
- package/src/activities/agent-turn/run.ts +1244 -1112
- package/src/activities/agent-turn/skill-install.ts +1 -1
- package/src/activities/agent-turn/skill-save.ts +1 -1
- package/src/activities/agent-turn/skill-selection.ts +2 -0
- package/src/activities/agent-turn/skill-tools.ts +5 -12
- package/src/activities/agent-turn/stream-attempt.ts +3 -0
- package/src/activities/agent-turn/tool-environment.ts +24 -0
- package/src/activities/codex-image-generation.ts +1 -2
- package/src/activities/documents.ts +17 -52
- package/src/activities/knowledge-indexing.ts +130 -0
- package/src/activities/knowledge-source-sync.ts +120 -16
- package/src/activities/mcp-credentials.ts +2 -0
- package/src/activities/run-input.ts +2 -0
- package/src/activities/scheduled-tasks.ts +25 -62
- package/src/activities/types.ts +8 -2
- package/src/activities/video-generation-reconciliation.ts +8 -1
- package/src/activities-control.ts +2 -0
- package/src/activities.ts +1 -0
- package/src/index.ts +49 -0
- package/src/pr-review-credentials.ts +2 -2
- package/src/workflows/knowledge-indexing.ts +12 -0
- package/src/workflows.ts +1 -0
- package/dist/activities-control-MKGEOBXT.js.map +0 -1
- package/dist/activities-turn-5MZZ6HQA.js.map +0 -1
- package/dist/chunk-OA5CSY5N.js.map +0 -1
|
@@ -246,7 +246,7 @@ function exactRequestedRepositories(refs, credentialBindingId, snapshots) {
|
|
|
246
246
|
// src/pr-review-credentials.ts
|
|
247
247
|
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
248
248
|
import { prReviewRegistrationIdFromCredentialBinding } from "@opengeni/core";
|
|
249
|
-
import {
|
|
249
|
+
import { createNativeRemoteMcpCredentialsPort } from "@opengeni/core/remote-mcp-credentials";
|
|
250
250
|
import { decryptVariableSetValue, resolvePrReviewGitCredential } from "@opengeni/db";
|
|
251
251
|
import {
|
|
252
252
|
createGitHubAppInstallationTokenWithExpiry,
|
|
@@ -254,7 +254,7 @@ import {
|
|
|
254
254
|
} from "@opengeni/github";
|
|
255
255
|
function createStandaloneConnectionCredentialsPort(settings, db) {
|
|
256
256
|
return {
|
|
257
|
-
...
|
|
257
|
+
...createNativeRemoteMcpCredentialsPort(settings, db),
|
|
258
258
|
gitCredentials: async (request) => await resolveStandaloneGitCredentials(settings, db, request)
|
|
259
259
|
};
|
|
260
260
|
}
|
|
@@ -1115,6 +1115,82 @@ function createCodexCapacityActivities(services) {
|
|
|
1115
1115
|
return { getCodexCapacityWait, reconcileCodexCapacityWait };
|
|
1116
1116
|
}
|
|
1117
1117
|
|
|
1118
|
+
// src/activities/documents.ts
|
|
1119
|
+
import {
|
|
1120
|
+
resolveDocumentIndexAuthority,
|
|
1121
|
+
rlsContextForWorkspace,
|
|
1122
|
+
withSessionRlsActorContext,
|
|
1123
|
+
withWorkspaceUsageLock
|
|
1124
|
+
} from "@opengeni/db";
|
|
1125
|
+
function createDocumentActivities(services, resolveDocumentServices) {
|
|
1126
|
+
return {
|
|
1127
|
+
indexDocument: async (input) => {
|
|
1128
|
+
const { db, objectStorage } = await services();
|
|
1129
|
+
if (!objectStorage) {
|
|
1130
|
+
throw new Error("object storage is not configured");
|
|
1131
|
+
}
|
|
1132
|
+
if (!resolveDocumentServices) {
|
|
1133
|
+
throw new Error("document services are not configured");
|
|
1134
|
+
}
|
|
1135
|
+
const [{ getDocumentForIndexing, indexDocumentNow }, documentServices] = await Promise.all([
|
|
1136
|
+
import("@opengeni/documents"),
|
|
1137
|
+
resolveDocumentServices()
|
|
1138
|
+
]);
|
|
1139
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
1140
|
+
if (context.accountId !== input.accountId) {
|
|
1141
|
+
throw new Error("document account/workspace authority mismatch");
|
|
1142
|
+
}
|
|
1143
|
+
return await withWorkspaceUsageLock(db, input.workspaceId, async (lockedDb) => {
|
|
1144
|
+
const storedAuthority = await resolveDocumentIndexAuthority(lockedDb, input);
|
|
1145
|
+
if (!storedAuthority) {
|
|
1146
|
+
throw new Error("document authority was not found before indexing");
|
|
1147
|
+
}
|
|
1148
|
+
const suppliedAuthorityFields = [
|
|
1149
|
+
"authorityKind",
|
|
1150
|
+
"authorityWorkspaceId",
|
|
1151
|
+
"authoritySubjectId"
|
|
1152
|
+
];
|
|
1153
|
+
const suppliedCount = suppliedAuthorityFields.filter(
|
|
1154
|
+
(field) => Object.prototype.hasOwnProperty.call(input, field)
|
|
1155
|
+
).length;
|
|
1156
|
+
const suppliedMismatch = Object.prototype.hasOwnProperty.call(input, "authorityKind") && input.authorityKind !== storedAuthority.authorityKind || Object.prototype.hasOwnProperty.call(input, "authorityWorkspaceId") && input.authorityWorkspaceId !== storedAuthority.authorityWorkspaceId || Object.prototype.hasOwnProperty.call(input, "authoritySubjectId") && input.authoritySubjectId !== storedAuthority.authoritySubjectId;
|
|
1157
|
+
if (suppliedMismatch) {
|
|
1158
|
+
throw new Error("document authority changed before indexing");
|
|
1159
|
+
}
|
|
1160
|
+
if (suppliedCount !== 0 && suppliedCount !== suppliedAuthorityFields.length) {
|
|
1161
|
+
throw new Error("document authority tuple is partial");
|
|
1162
|
+
}
|
|
1163
|
+
const claimedDocument = await getDocumentForIndexing(
|
|
1164
|
+
lockedDb,
|
|
1165
|
+
input.workspaceId,
|
|
1166
|
+
input.documentId
|
|
1167
|
+
);
|
|
1168
|
+
if (!claimedDocument || claimedDocument.authorityKind !== storedAuthority.authorityKind || claimedDocument.authorityWorkspaceId !== storedAuthority.authorityWorkspaceId || claimedDocument.authoritySubjectId !== storedAuthority.authoritySubjectId) {
|
|
1169
|
+
throw new Error("document authority changed before indexing");
|
|
1170
|
+
}
|
|
1171
|
+
const document = await withSessionRlsActorContext(
|
|
1172
|
+
{
|
|
1173
|
+
subjectId: "service:document-preparation",
|
|
1174
|
+
privateFileOwnerSubjectId: storedAuthority.authorityKind === "personal" ? storedAuthority.authoritySubjectId : null
|
|
1175
|
+
},
|
|
1176
|
+
() => indexDocumentNow(
|
|
1177
|
+
lockedDb,
|
|
1178
|
+
objectStorage,
|
|
1179
|
+
input.workspaceId,
|
|
1180
|
+
input.documentId,
|
|
1181
|
+
documentServices,
|
|
1182
|
+
{ viewerSubjectId: storedAuthority.authoritySubjectId }
|
|
1183
|
+
)
|
|
1184
|
+
);
|
|
1185
|
+
if (document.authorityKind !== storedAuthority.authorityKind || document.authorityWorkspaceId !== storedAuthority.authorityWorkspaceId || document.authoritySubjectId !== storedAuthority.authoritySubjectId) {
|
|
1186
|
+
throw new Error("document authority changed before indexing");
|
|
1187
|
+
}
|
|
1188
|
+
return document;
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1118
1194
|
// src/activities/goals.ts
|
|
1119
1195
|
import {
|
|
1120
1196
|
allowedFirstPartyMcpToolsForSession,
|
|
@@ -1226,187 +1302,3282 @@ function createGoalActivities(services) {
|
|
|
1226
1302
|
latencyMode: continuationLatencyMode,
|
|
1227
1303
|
latencyModeSource: "continuation"
|
|
1228
1304
|
});
|
|
1229
|
-
const decision = await materializeGoalContinuation(db, {
|
|
1230
|
-
accountId: input.accountId,
|
|
1231
|
-
workspaceId: input.workspaceId,
|
|
1232
|
-
sessionId: input.sessionId,
|
|
1233
|
-
workflowId: input.workflowId,
|
|
1234
|
-
defaultMaxAutoContinuations: settings.goalMaxAutoContinuations ?? null,
|
|
1235
|
-
// Pacing between consecutive no-input continuations (never a cap). The
|
|
1236
|
-
// materializer re-arms a delayed outbox wake and returns `deferred`.
|
|
1237
|
-
idleBackoff: {
|
|
1238
|
-
scheduleMs: settings.goalIdleBackoffMs,
|
|
1239
|
-
maxMs: settings.goalIdleBackoffMaxMs
|
|
1240
|
-
},
|
|
1241
|
-
// A model-policy block takes precedence: it is deterministic (a budget
|
|
1242
|
-
// pause can clear on its own; a policy pause needs a model/policy change)
|
|
1243
|
-
// and rides the same visible-pause channel.
|
|
1244
|
-
budgetBlocked: modelPolicyBlocked ?? budgetBlocked,
|
|
1245
|
-
policy: {
|
|
1246
|
-
model: continuationModel,
|
|
1247
|
-
reasoningEffort: continuationReasoningEffort,
|
|
1248
|
-
latencyMode: continuationLatencyMode,
|
|
1249
|
-
turnExecutionPolicy,
|
|
1250
|
-
tools: withFirstPartyTools(settings, session.tools),
|
|
1251
|
-
sandboxBackend: session.sandboxBackend
|
|
1252
|
-
},
|
|
1253
|
-
// Long-wait guidance is only given when `wait_for_input` is actually in this
|
|
1254
|
-
// session's effective first-party selection (the same source the worker
|
|
1255
|
-
// signs into the delegated token and the API uses to register tools), so
|
|
1256
|
-
// a pre-existing narrowed selection is never told to call a missing tool.
|
|
1257
|
-
prompt: (goal, autoContinuation, cap) => {
|
|
1258
|
-
const effectiveFirstPartyTools = allowedFirstPartyMcpToolsForSession(
|
|
1259
|
-
settings,
|
|
1260
|
-
session.firstPartyMcpTools
|
|
1261
|
-
);
|
|
1262
|
-
return goalContinuationPrompt(goal, autoContinuation, cap, {
|
|
1263
|
-
inputWaitAvailable: effectiveFirstPartyTools.includes("wait_for_input"),
|
|
1264
|
-
humanInputRespondAvailable: effectiveFirstPartyTools.includes(
|
|
1265
|
-
"session_human_input_respond"
|
|
1266
|
-
)
|
|
1267
|
-
});
|
|
1305
|
+
const decision = await materializeGoalContinuation(db, {
|
|
1306
|
+
accountId: input.accountId,
|
|
1307
|
+
workspaceId: input.workspaceId,
|
|
1308
|
+
sessionId: input.sessionId,
|
|
1309
|
+
workflowId: input.workflowId,
|
|
1310
|
+
defaultMaxAutoContinuations: settings.goalMaxAutoContinuations ?? null,
|
|
1311
|
+
// Pacing between consecutive no-input continuations (never a cap). The
|
|
1312
|
+
// materializer re-arms a delayed outbox wake and returns `deferred`.
|
|
1313
|
+
idleBackoff: {
|
|
1314
|
+
scheduleMs: settings.goalIdleBackoffMs,
|
|
1315
|
+
maxMs: settings.goalIdleBackoffMaxMs
|
|
1316
|
+
},
|
|
1317
|
+
// A model-policy block takes precedence: it is deterministic (a budget
|
|
1318
|
+
// pause can clear on its own; a policy pause needs a model/policy change)
|
|
1319
|
+
// and rides the same visible-pause channel.
|
|
1320
|
+
budgetBlocked: modelPolicyBlocked ?? budgetBlocked,
|
|
1321
|
+
policy: {
|
|
1322
|
+
model: continuationModel,
|
|
1323
|
+
reasoningEffort: continuationReasoningEffort,
|
|
1324
|
+
latencyMode: continuationLatencyMode,
|
|
1325
|
+
turnExecutionPolicy,
|
|
1326
|
+
tools: withFirstPartyTools(settings, session.tools),
|
|
1327
|
+
sandboxBackend: session.sandboxBackend
|
|
1328
|
+
},
|
|
1329
|
+
// Long-wait guidance is only given when `wait_for_input` is actually in this
|
|
1330
|
+
// session's effective first-party selection (the same source the worker
|
|
1331
|
+
// signs into the delegated token and the API uses to register tools), so
|
|
1332
|
+
// a pre-existing narrowed selection is never told to call a missing tool.
|
|
1333
|
+
prompt: (goal, autoContinuation, cap) => {
|
|
1334
|
+
const effectiveFirstPartyTools = allowedFirstPartyMcpToolsForSession(
|
|
1335
|
+
settings,
|
|
1336
|
+
session.firstPartyMcpTools
|
|
1337
|
+
);
|
|
1338
|
+
return goalContinuationPrompt(goal, autoContinuation, cap, {
|
|
1339
|
+
inputWaitAvailable: effectiveFirstPartyTools.includes("wait_for_input"),
|
|
1340
|
+
humanInputRespondAvailable: effectiveFirstPartyTools.includes(
|
|
1341
|
+
"session_human_input_respond"
|
|
1342
|
+
)
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
if (decision.events.length > 0) {
|
|
1347
|
+
await bus.publish(input.workspaceId, input.sessionId, decision.events);
|
|
1348
|
+
}
|
|
1349
|
+
return { action: decision.action };
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
enqueueGoalRetryWake,
|
|
1353
|
+
maybeContinueGoal
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function goalContinuationModelDecision(input) {
|
|
1357
|
+
const catalogSettings = input.settings.supergrokSubscriptionEnabled ? withXaiSubscriptionCatalogProvider(
|
|
1358
|
+
input.settings.codexSubscriptionEnabled ? withCodexCatalogProvider(input.settings) : input.settings
|
|
1359
|
+
) : input.settings.codexSubscriptionEnabled ? withCodexCatalogProvider(input.settings) : input.settings;
|
|
1360
|
+
const policyBlocks = (modelId) => input.workspaceModelPolicy !== null && !evaluateWorkspaceModelPolicy(input.workspaceModelPolicy, {
|
|
1361
|
+
providerId: policyProviderIdForModel(catalogSettings, modelId),
|
|
1362
|
+
modelId
|
|
1363
|
+
}).allowed;
|
|
1364
|
+
if (resolveModelProvider(catalogSettings, input.inheritedModel) && !policyBlocks(input.inheritedModel)) {
|
|
1365
|
+
return { model: input.inheritedModel, blocked: null };
|
|
1366
|
+
}
|
|
1367
|
+
if (!resolveModelProvider(catalogSettings, input.inheritedModel)) {
|
|
1368
|
+
return {
|
|
1369
|
+
model: input.inheritedModel,
|
|
1370
|
+
blocked: `model "${input.inheritedModel}" is no longer in the deployment or workspace catalog; choose an available model before resuming the goal`
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
model: input.inheritedModel,
|
|
1375
|
+
blocked: `workspace model policy blocks model "${input.inheritedModel}"; pick an allowed model or change the workspace model policy`
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function goalContinuationFundedWithoutCredits(settings, model, codexBilled) {
|
|
1379
|
+
return modelFundingForAdmission(settings, model, codexBilled).fundedWithoutCredits;
|
|
1380
|
+
}
|
|
1381
|
+
function goalContinuationPrompt(_goal, _autoContinuation, _cap, options = {}) {
|
|
1382
|
+
const waitingGuidance = options.inputWaitAvailable ? [
|
|
1383
|
+
"Waiting on child sessions, background commands, or external events:",
|
|
1384
|
+
"- When further progress depends on work already in flight, do not sleep, loop, or poll session or command state repeatedly.",
|
|
1385
|
+
"- Re-check once; if the work is still in flight and the wait is long or uncertain, call opengeni__wait_for_input with a concrete reason and timeoutSeconds, then end your turn immediately. Relevant session input or the safety deadline will start a new turn, and this goal stays active.",
|
|
1386
|
+
"- If the immediately preceding user-facing update already reported this same unchanged wait, do not restate it or produce another equivalent final answer. Call opengeni__wait_for_input and end the turn. Report only material new state or a newly discovered blocker.",
|
|
1387
|
+
"- If you are blocked on a human decision, use opengeni__goal_pause under the blocked audit below instead.",
|
|
1388
|
+
""
|
|
1389
|
+
] : [];
|
|
1390
|
+
const childNoticeGuidance = [
|
|
1391
|
+
"Child lifecycle notices:",
|
|
1392
|
+
"- A `child_requires_action` update means a worker you spawned is blocked on a question or a tool approval and will not progress until it is answered. " + (options.humanInputRespondAvailable ? "If you know the answer to its question, answer it with opengeni__session_human_input_respond (pass the worker's sessionId, the requestId from the notice, and a response). " : "") + "Tool approvals can only be decided by a human. If you cannot resolve the blocker yourself, report the exact blocker (worker session id, the question) to the user and wait or pause instead of retrying the worker.",
|
|
1393
|
+
"- `child_requires_action_resolved`, `child_paused`, `child_waiting_capacity`, and `child_progress` updates are informational: a resolved notice means the worker is moving again; a paused worker needs a human or you to resume it; a capacity wait resumes by itself; a progress note needs no action.",
|
|
1394
|
+
""
|
|
1395
|
+
];
|
|
1396
|
+
return [
|
|
1397
|
+
"Reconcile the active session goal against authoritative current state, then carry the work through to the full requested end state and verify it.",
|
|
1398
|
+
"",
|
|
1399
|
+
"Goal recovery:",
|
|
1400
|
+
"- Treat this continuation as re-entry into the full objective, not as a request to perform one step and stop.",
|
|
1401
|
+
"- Do not rely on previous assistant claims of progress or completion; use them only to locate authoritative evidence.",
|
|
1402
|
+
"- If authoritative evidence already proves the full objective, call opengeni__goal_complete instead of manufacturing more work.",
|
|
1403
|
+
"- Before repeating a state-setting action, verify whether its desired state already holds. If it does, do not repeat it; continue reconciling the overall goal.",
|
|
1404
|
+
"",
|
|
1405
|
+
"Continuation behavior:",
|
|
1406
|
+
"- This goal persists across turns. A runtime boundary can end one turn without shrinking the objective; the next continuation resumes the same full objective.",
|
|
1407
|
+
"- Keep working until the requested end state is true and verified. Do not end the turn merely because one useful action completed, and do not redefine success around a smaller or easier task.",
|
|
1408
|
+
"- An incomplete-status report is not a substitute for continuing the work. If the remaining problem can be investigated or addressed within your current authority, continue that work in this turn rather than returning another equivalent status-only final.",
|
|
1409
|
+
"- Distinguish unfinished work from a blocker that actually requires human input or external change; use the existing waiting and blocked audits only when their conditions hold.",
|
|
1410
|
+
"- Temporary rough edges are acceptable while the work is moving in the right direction. Completion still requires the requested end state to be true and verified.",
|
|
1411
|
+
"",
|
|
1412
|
+
"Work from evidence:",
|
|
1413
|
+
"Use the current workspace and external state as authoritative. Conversation context can help locate relevant work, but it is not proof of the current state. Inspect the current state before relying on it. Improve, replace, or remove existing work as needed to satisfy the actual objective.",
|
|
1414
|
+
"",
|
|
1415
|
+
"Progress visibility:",
|
|
1416
|
+
"If a planning tool is available and the next work is meaningfully multi-step, use it to show a concise plan tied to the real objective. Keep the plan current as steps complete or the next best action changes. Skip planning overhead for trivial one-step progress, and do not treat a plan update as a substitute for doing the work.",
|
|
1417
|
+
"",
|
|
1418
|
+
"Fidelity:",
|
|
1419
|
+
"- Optimize each turn for movement toward the requested end state, not for the smallest stable-looking subset or easiest passing change.",
|
|
1420
|
+
"- Do not substitute a narrower, safer, smaller, merely compatible, or easier-to-test solution because it is more likely to pass current tests.",
|
|
1421
|
+
"- Treat alignment as movement toward the requested end state. An edit is aligned only if it makes the requested final state more true; useful-looking behavior that preserves a different end state is misaligned.",
|
|
1422
|
+
"",
|
|
1423
|
+
"Completion audit:",
|
|
1424
|
+
"Before deciding that the goal is achieved, treat completion as unproven and verify it against the actual current state:",
|
|
1425
|
+
"- Derive concrete requirements from the objective and any referenced files, plans, specifications, issues, or user instructions.",
|
|
1426
|
+
"- Preserve the original scope; do not redefine success around the work that already exists.",
|
|
1427
|
+
"- For every explicit requirement, named artifact, command, test, gate, invariant, and deliverable, identify and inspect the authoritative evidence that would prove it.",
|
|
1428
|
+
"- Match verification scope to requirement scope. Treat uncertain, indirect, incomplete, or missing evidence as not achieved and continue working.",
|
|
1429
|
+
"- The audit must prove completion, not merely fail to find obvious remaining work.",
|
|
1430
|
+
"",
|
|
1431
|
+
"Do not rely on intent, partial progress, memory of earlier work, or a plausible final answer as proof of completion. Call opengeni__goal_complete with concrete evidence only when the full objective is actually achieved and no required work remains.",
|
|
1432
|
+
"",
|
|
1433
|
+
...waitingGuidance,
|
|
1434
|
+
...childNoticeGuidance,
|
|
1435
|
+
"Blocked audit:",
|
|
1436
|
+
"- Do not call opengeni__goal_pause the first time a blocker appears.",
|
|
1437
|
+
"- Pause only when the same blocking condition has repeated for at least three consecutive goal turns and meaningful progress is impossible without user input or an external-state change.",
|
|
1438
|
+
"- Do not pause merely because the work is hard, slow, uncertain, incomplete, or would benefit from clarification.",
|
|
1439
|
+
"- Once that threshold is satisfied, call opengeni__goal_pause with the concrete blocker instead of repeatedly reporting it while leaving the goal active.",
|
|
1440
|
+
"",
|
|
1441
|
+
"Do not call opengeni__goal_complete or opengeni__goal_pause unless the corresponding audit above is satisfied."
|
|
1442
|
+
].join("\n");
|
|
1443
|
+
}
|
|
1444
|
+
function withFirstPartyTools(settings, tools) {
|
|
1445
|
+
if (!settings.mcpServers.some((server) => server.id === "opengeni")) {
|
|
1446
|
+
return tools;
|
|
1447
|
+
}
|
|
1448
|
+
return mergeToolRefs(tools, [{ kind: "mcp", id: "opengeni" }]);
|
|
1449
|
+
}
|
|
1450
|
+
async function goalRunBudgetBlocked(settings, db, accountId, workspaceId, fundedWithoutCredits) {
|
|
1451
|
+
if (!fundedWithoutCredits && (settings.billingMode === "stripe" || settings.usageLimitsMode === "managed")) {
|
|
1452
|
+
const balance = await getBillingBalance(db, accountId);
|
|
1453
|
+
if (balance.balanceMicros <= 0) {
|
|
1454
|
+
return "insufficient OpenGeni credits";
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (settings.usageLimitsMode === "static" || settings.usageLimitsMode === "managed") {
|
|
1458
|
+
const limits = configuredStaticUsageLimits(settings);
|
|
1459
|
+
if (!fundedWithoutCredits && limits.maxMonthlyCostMicrosPerAccount) {
|
|
1460
|
+
const used = await sumUsageQuantity(db, {
|
|
1461
|
+
accountId,
|
|
1462
|
+
eventType: "model.cost",
|
|
1463
|
+
since: startOfUtcMonth()
|
|
1464
|
+
});
|
|
1465
|
+
if (used >= limits.maxMonthlyCostMicrosPerAccount) {
|
|
1466
|
+
return `monthly model cost limit reached (${limits.maxMonthlyCostMicrosPerAccount} micros)`;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
if (limits.maxMonthlyAgentRunsPerWorkspace) {
|
|
1470
|
+
const used = await sumUsageQuantity(db, {
|
|
1471
|
+
workspaceId,
|
|
1472
|
+
eventType: "agent_run.created",
|
|
1473
|
+
since: startOfUtcMonth()
|
|
1474
|
+
});
|
|
1475
|
+
if (used + 1 > limits.maxMonthlyAgentRunsPerWorkspace) {
|
|
1476
|
+
return `monthly agent run limit reached (${limits.maxMonthlyAgentRunsPerWorkspace})`;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
function startOfUtcMonth() {
|
|
1483
|
+
const now = /* @__PURE__ */ new Date();
|
|
1484
|
+
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// src/activities/knowledge-source-sync.ts
|
|
1488
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
1489
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1490
|
+
import { scheduledTaskKnowledgeSource } from "@opengeni/contracts";
|
|
1491
|
+
import {
|
|
1492
|
+
freezeAgentLearningPolicy,
|
|
1493
|
+
getScheduledTaskRunAcceptedExecution,
|
|
1494
|
+
ensureKnowledgeSourceSyncState,
|
|
1495
|
+
recordKnowledgeSourceSyncWake
|
|
1496
|
+
} from "@opengeni/db";
|
|
1497
|
+
import { createHash as createHash2 } from "crypto";
|
|
1498
|
+
import { heartbeat } from "@temporalio/activity";
|
|
1499
|
+
import {
|
|
1500
|
+
googleDriveProviderRetryOptions
|
|
1501
|
+
} from "@opengeni/config";
|
|
1502
|
+
import {
|
|
1503
|
+
KnowledgeSourceSyncRunSummary
|
|
1504
|
+
} from "@opengeni/contracts";
|
|
1505
|
+
import {
|
|
1506
|
+
ATLASSIAN_PROVIDER_DOMAIN,
|
|
1507
|
+
AtlassianConnectionMetadata,
|
|
1508
|
+
atlassianScopesAllowRead
|
|
1509
|
+
} from "@opengeni/contracts/atlassian";
|
|
1510
|
+
import {
|
|
1511
|
+
GoogleDriveConnectionMetadata,
|
|
1512
|
+
GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
1513
|
+
googleDriveScopesAllowCapability
|
|
1514
|
+
} from "@opengeni/contracts/google-drive";
|
|
1515
|
+
import {
|
|
1516
|
+
googleDriveKnowledgeScope,
|
|
1517
|
+
inventoryGoogleDriveSource
|
|
1518
|
+
} from "@opengeni/documents/google-drive";
|
|
1519
|
+
import {
|
|
1520
|
+
atlassianKnowledgeScope,
|
|
1521
|
+
inventoryAtlassianSource
|
|
1522
|
+
} from "@opengeni/documents/atlassian";
|
|
1523
|
+
import { addDocumentToBase, ensureDefaultBase, getDocumentBase } from "@opengeni/documents";
|
|
1524
|
+
import {
|
|
1525
|
+
appendKnowledgeDocumentVersion,
|
|
1526
|
+
appendKnowledgeSourceAclVersion,
|
|
1527
|
+
beginKnowledgeSyncRun,
|
|
1528
|
+
beginGoogleDriveObjectAclRefresh,
|
|
1529
|
+
buildConnectionTokenResolver,
|
|
1530
|
+
checkpointKnowledgeSourceSync,
|
|
1531
|
+
claimKnowledgeSourceSyncLease,
|
|
1532
|
+
compareCanonicalDecimalProviderRevisions,
|
|
1533
|
+
completeKnowledgeSyncRun,
|
|
1534
|
+
completeKnowledgeSourceSyncWakeWithoutLease,
|
|
1535
|
+
deauthorizeKnowledgeSourceRetrieval,
|
|
1536
|
+
enqueueKnowledgeSourceSyncIndexObligation,
|
|
1537
|
+
ensureKnowledgeSourceBlobFile,
|
|
1538
|
+
getConnectionMetadata as getConnectionMetadata2,
|
|
1539
|
+
getKnowledgeDocumentVersionObservationForSyncAuthority,
|
|
1540
|
+
getKnowledgeSyncRunForSyncAuthority,
|
|
1541
|
+
getKnowledgeSourceAclForSyncAuthority,
|
|
1542
|
+
getKnowledgeSourceForSyncAuthority,
|
|
1543
|
+
getKnowledgeSourceObjectForSyncAuthority,
|
|
1544
|
+
getKnowledgeSourceSyncIndexObligationForVersion,
|
|
1545
|
+
getScheduledTask,
|
|
1546
|
+
listObservedKnowledgeSourceSyncExternalObjectIds,
|
|
1547
|
+
KnowledgeSourceSyncObservationFenceError,
|
|
1548
|
+
reconcileKnowledgeSourceSyncCompleteScan,
|
|
1549
|
+
reconcileKnowledgeSourceSyncLiveGeneration,
|
|
1550
|
+
recordGoogleDriveObjectAclEvidence,
|
|
1551
|
+
recordKnowledgeSourceSyncItemOutcomes,
|
|
1552
|
+
recordKnowledgeSourceSyncObjectObservations,
|
|
1553
|
+
recordUsageEvent,
|
|
1554
|
+
releaseKnowledgeSourceSyncLeaseForRetry,
|
|
1555
|
+
restoreKnowledgeSourceObject,
|
|
1556
|
+
retryKnowledgeSourceSyncIndexObligation,
|
|
1557
|
+
scopedKnowledgeScopeKey,
|
|
1558
|
+
settleKnowledgeSourceSyncLease,
|
|
1559
|
+
settleKnowledgeSourceSyncIndexObligation,
|
|
1560
|
+
updateKnowledgeSourceDocumentObservationMetadata,
|
|
1561
|
+
updateScheduledTaskRun,
|
|
1562
|
+
upsertKnowledgeSourceObject
|
|
1563
|
+
} from "@opengeni/db";
|
|
1564
|
+
import { readResponseJsonBounded } from "@opengeni/network";
|
|
1565
|
+
|
|
1566
|
+
// src/activities/google-drive-provider.ts
|
|
1567
|
+
var GoogleDriveProviderTransportError = class extends Error {
|
|
1568
|
+
constructor() {
|
|
1569
|
+
super("google_drive_provider_transport_failed");
|
|
1570
|
+
this.name = "GoogleDriveProviderTransportError";
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
async function fetchGoogleDriveProvider(input) {
|
|
1574
|
+
const now = input.now ?? Date.now;
|
|
1575
|
+
const sleep3 = input.sleep ?? Bun.sleep;
|
|
1576
|
+
let retryDelaySpentMs = 0;
|
|
1577
|
+
for (let attempt = 1; attempt <= input.policy.attempts; attempt += 1) {
|
|
1578
|
+
let response;
|
|
1579
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "request_start" });
|
|
1580
|
+
try {
|
|
1581
|
+
response = await input.fetchImpl(input.url, {
|
|
1582
|
+
...input.init,
|
|
1583
|
+
redirect: "error",
|
|
1584
|
+
signal: AbortSignal.timeout(input.policy.requestTimeoutMs)
|
|
1585
|
+
});
|
|
1586
|
+
} catch {
|
|
1587
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "request_end" });
|
|
1588
|
+
recordRequest(input.observability, input.operation, "retryable_error");
|
|
1589
|
+
const delayMs2 = retryDelayMs(input.policy, attempt, null);
|
|
1590
|
+
if (!canRetry(input.policy, attempt, retryDelaySpentMs, delayMs2)) {
|
|
1591
|
+
throw new GoogleDriveProviderTransportError();
|
|
1592
|
+
}
|
|
1593
|
+
recordRetry(input.observability, input.operation, "transport", attempt, delayMs2);
|
|
1594
|
+
retryDelaySpentMs += delayMs2;
|
|
1595
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "retry_delay_start" });
|
|
1596
|
+
await sleep3(delayMs2);
|
|
1597
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "retry_delay_end" });
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "request_end" });
|
|
1601
|
+
if (!isRetryableStatus(response.status)) {
|
|
1602
|
+
recordRequest(
|
|
1603
|
+
input.observability,
|
|
1604
|
+
input.operation,
|
|
1605
|
+
response.ok ? "succeeded" : "terminal_error"
|
|
1606
|
+
);
|
|
1607
|
+
return response;
|
|
1608
|
+
}
|
|
1609
|
+
recordRequest(input.observability, input.operation, "retryable_error");
|
|
1610
|
+
const delayMs = retryDelayMs(
|
|
1611
|
+
input.policy,
|
|
1612
|
+
attempt,
|
|
1613
|
+
retryAfterDelayMs(response.headers.get("retry-after"), now())
|
|
1614
|
+
);
|
|
1615
|
+
if (!canRetry(input.policy, attempt, retryDelaySpentMs, delayMs)) return response;
|
|
1616
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1617
|
+
recordRetry(
|
|
1618
|
+
input.observability,
|
|
1619
|
+
input.operation,
|
|
1620
|
+
response.status === 429 ? "rate_limited" : "provider_unavailable",
|
|
1621
|
+
attempt,
|
|
1622
|
+
delayMs
|
|
1623
|
+
);
|
|
1624
|
+
retryDelaySpentMs += delayMs;
|
|
1625
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "retry_delay_start" });
|
|
1626
|
+
await sleep3(delayMs);
|
|
1627
|
+
input.heartbeat?.({ operation: input.operation, attempt, phase: "retry_delay_end" });
|
|
1628
|
+
}
|
|
1629
|
+
throw new GoogleDriveProviderTransportError();
|
|
1630
|
+
}
|
|
1631
|
+
function canRetry(policy, attempt, retryDelaySpentMs, delayMs) {
|
|
1632
|
+
return attempt < policy.attempts && retryDelaySpentMs + delayMs <= policy.budgetMs;
|
|
1633
|
+
}
|
|
1634
|
+
function retryDelayMs(policy, attempt, retryAfterMs) {
|
|
1635
|
+
const exponential = Math.min(
|
|
1636
|
+
policy.maxDelayMs,
|
|
1637
|
+
policy.initialDelayMs * 2 ** Math.max(0, attempt - 1)
|
|
1638
|
+
);
|
|
1639
|
+
return Math.min(policy.maxDelayMs, Math.max(exponential, retryAfterMs ?? 0));
|
|
1640
|
+
}
|
|
1641
|
+
function retryAfterDelayMs(value, nowMs) {
|
|
1642
|
+
if (!value) return null;
|
|
1643
|
+
const seconds = Number(value);
|
|
1644
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1e3);
|
|
1645
|
+
const timestamp = Date.parse(value);
|
|
1646
|
+
return Number.isFinite(timestamp) ? Math.max(0, timestamp - nowMs) : null;
|
|
1647
|
+
}
|
|
1648
|
+
function isRetryableStatus(status) {
|
|
1649
|
+
return status === 429 || status >= 500;
|
|
1650
|
+
}
|
|
1651
|
+
function recordRequest(observability, operation, outcome) {
|
|
1652
|
+
observability.incrementCounter({
|
|
1653
|
+
name: "opengeni_google_drive_provider_requests_total",
|
|
1654
|
+
help: "Google Drive provider request attempts by operation and bounded outcome.",
|
|
1655
|
+
labels: { operation, outcome }
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
function recordRetry(observability, operation, reason, attempt, delayMs) {
|
|
1659
|
+
observability.incrementCounter({
|
|
1660
|
+
name: "opengeni_google_drive_provider_retries_total",
|
|
1661
|
+
help: "Bounded Google Drive provider retries by operation and reason.",
|
|
1662
|
+
labels: { operation, reason }
|
|
1663
|
+
});
|
|
1664
|
+
observability.observeHistogram({
|
|
1665
|
+
name: "opengeni_google_drive_provider_retry_delay_seconds",
|
|
1666
|
+
help: "Delay applied before a bounded Google Drive provider retry.",
|
|
1667
|
+
labels: { operation, reason },
|
|
1668
|
+
value: delayMs / 1e3,
|
|
1669
|
+
buckets: [0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60]
|
|
1670
|
+
});
|
|
1671
|
+
observability.warn("Google Drive provider request retry scheduled", {
|
|
1672
|
+
provider: "google_drive",
|
|
1673
|
+
operation,
|
|
1674
|
+
outcome: "retry_scheduled",
|
|
1675
|
+
reason,
|
|
1676
|
+
attempt,
|
|
1677
|
+
delayMs
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// src/activities/google-drive-changes.ts
|
|
1682
|
+
import { createHash } from "crypto";
|
|
1683
|
+
import {
|
|
1684
|
+
GOOGLE_DRIVE_FOLDER_MIME_TYPE,
|
|
1685
|
+
planGoogleDriveTransfer
|
|
1686
|
+
} from "@opengeni/documents/google-drive";
|
|
1687
|
+
var MAX_TOKEN_CHARS = 4096;
|
|
1688
|
+
var MAX_BOUNDARY_ID_CHARS = 1024;
|
|
1689
|
+
var MAX_ANCESTOR_DEPTH = 100;
|
|
1690
|
+
function parseGoogleDriveChangesCursor(value, expected) {
|
|
1691
|
+
if (!value || value.version !== 1 || value.kind !== "google_drive_changes") return null;
|
|
1692
|
+
if (value.connectionId !== expected.connectionId || value.googlePermissionId !== expected.googlePermissionId || value.sourceId !== expected.sourceId || value.driveId !== expected.driveId || typeof value.boundaryId !== "string" || value.boundaryId.length < 1 || value.boundaryId.length > MAX_BOUNDARY_ID_CHARS || typeof value.pageToken !== "string" || value.pageToken.length < 1 || value.pageToken.length > MAX_TOKEN_CHARS || typeof value.cursorGeneration !== "number" || !Number.isSafeInteger(value.cursorGeneration) || value.cursorGeneration < 1 || typeof value.lastFullReconciliationAt !== "string" || !Number.isFinite(Date.parse(value.lastFullReconciliationAt)) || typeof value.nextFullReconciliationAt !== "string" || !Number.isFinite(Date.parse(value.nextFullReconciliationAt))) {
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
return value;
|
|
1696
|
+
}
|
|
1697
|
+
function buildGoogleDriveChangesCursor(input) {
|
|
1698
|
+
return {
|
|
1699
|
+
version: 1,
|
|
1700
|
+
kind: "google_drive_changes",
|
|
1701
|
+
connectionId: input.connectionId,
|
|
1702
|
+
googlePermissionId: input.googlePermissionId,
|
|
1703
|
+
sourceId: input.sourceId,
|
|
1704
|
+
driveId: input.driveId,
|
|
1705
|
+
boundaryId: boundedBoundaryId(input.boundaryId),
|
|
1706
|
+
pageToken: boundedToken(input.pageToken),
|
|
1707
|
+
cursorGeneration: input.previousGeneration + 1,
|
|
1708
|
+
lastFullReconciliationAt: input.reconciledAt.toISOString(),
|
|
1709
|
+
nextFullReconciliationAt: new Date(
|
|
1710
|
+
input.reconciledAt.getTime() + input.fullReconciliationIntervalMs
|
|
1711
|
+
).toISOString()
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
function advanceGoogleDriveChangesCursor(cursor, pageToken) {
|
|
1715
|
+
return {
|
|
1716
|
+
...cursor,
|
|
1717
|
+
pageToken: boundedToken(pageToken),
|
|
1718
|
+
cursorGeneration: cursor.cursorGeneration + 1
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
function googleDriveFullReconciliationDue(cursor, now) {
|
|
1722
|
+
return Date.parse(cursor.nextFullReconciliationAt) <= now.getTime();
|
|
1723
|
+
}
|
|
1724
|
+
async function resolveGoogleDriveChangesBoundaryId(input) {
|
|
1725
|
+
if (input.source.id !== "root" || input.source.driveId !== null) {
|
|
1726
|
+
return boundedBoundaryId(input.source.id);
|
|
1727
|
+
}
|
|
1728
|
+
const root = await input.getFile("root");
|
|
1729
|
+
if (!root || root.mimeType !== GOOGLE_DRIVE_FOLDER_MIME_TYPE || root.trashed) {
|
|
1730
|
+
throw new GoogleDriveChangesProtocolError("google_drive_root_boundary_invalid");
|
|
1731
|
+
}
|
|
1732
|
+
return boundedBoundaryId(root.id);
|
|
1733
|
+
}
|
|
1734
|
+
function parseGoogleDriveChangesCheckpoint(value, limits) {
|
|
1735
|
+
try {
|
|
1736
|
+
return validatedCheckpoint(value, limits);
|
|
1737
|
+
} catch {
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
async function drainGoogleDriveChanges(input) {
|
|
1742
|
+
const now = input.now ?? Date.now;
|
|
1743
|
+
const startedAt = now();
|
|
1744
|
+
const checkpoint = input.checkpoint ? validatedCheckpoint(input.checkpoint, input) : null;
|
|
1745
|
+
let pageToken = boundedToken(checkpoint?.pageToken ?? input.cursor.pageToken);
|
|
1746
|
+
let requiresFullReconciliation = checkpoint?.requiresFullReconciliation ?? false;
|
|
1747
|
+
const seenPageTokenHashes = new Set(checkpoint?.seenPageTokenHashes ?? []);
|
|
1748
|
+
const initialBudget = checkpoint?.budget ?? emptyBudget();
|
|
1749
|
+
const budget = { ...initialBudget };
|
|
1750
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1751
|
+
while (true) {
|
|
1752
|
+
const invocationElapsedMs = Math.max(0, now() - startedAt);
|
|
1753
|
+
budget.elapsedMs = initialBudget.elapsedMs + invocationElapsedMs;
|
|
1754
|
+
const stopReason = limitReason(budget, input, invocationElapsedMs);
|
|
1755
|
+
if (stopReason) {
|
|
1756
|
+
return {
|
|
1757
|
+
status: "paused",
|
|
1758
|
+
stopReason,
|
|
1759
|
+
entries: [...entries.values()],
|
|
1760
|
+
checkpoint: {
|
|
1761
|
+
version: 2,
|
|
1762
|
+
pageToken,
|
|
1763
|
+
requiresFullReconciliation,
|
|
1764
|
+
seenPageTokenHashes: [...seenPageTokenHashes],
|
|
1765
|
+
budget: { ...budget }
|
|
1766
|
+
},
|
|
1767
|
+
newStartPageToken: null,
|
|
1768
|
+
requiresFullReconciliation,
|
|
1769
|
+
providerRequests: budget.providerRequests,
|
|
1770
|
+
elapsedMs: budget.elapsedMs,
|
|
1771
|
+
budget: { ...budget },
|
|
1772
|
+
hardLimitReached: stopReason !== "elapsed_time_limit" || budget.elapsedMs >= input.maxElapsedMs
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
const pageTokenHash = createHash("sha256").update(pageToken).digest("hex");
|
|
1776
|
+
if (seenPageTokenHashes.has(pageTokenHash)) {
|
|
1777
|
+
throw new GoogleDriveChangesProtocolError("google_drive_changes_page_token_cycle");
|
|
1778
|
+
}
|
|
1779
|
+
seenPageTokenHashes.add(pageTokenHash);
|
|
1780
|
+
budget.providerRequests += 1;
|
|
1781
|
+
const pageSize = Math.min(100, input.maxItems - budget.examinedItems);
|
|
1782
|
+
let page;
|
|
1783
|
+
try {
|
|
1784
|
+
page = await input.listChanges(pageToken, pageSize);
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
budget.elapsedMs = initialBudget.elapsedMs + Math.max(0, now() - startedAt);
|
|
1787
|
+
if (error instanceof GoogleDriveCursorInvalidError) error.budget = { ...budget };
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
if (page.changes.length > pageSize) {
|
|
1791
|
+
throw new GoogleDriveChangesProtocolError("google_drive_changes_page_exceeds_item_budget");
|
|
1792
|
+
}
|
|
1793
|
+
budget.examinedItems += page.changes.length;
|
|
1794
|
+
const repairCandidates = /* @__PURE__ */ new Set();
|
|
1795
|
+
for (const change of page.changes) {
|
|
1796
|
+
if (change.removed || !change.file || change.file.trashed) {
|
|
1797
|
+
repairCandidates.add(change.fileId);
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
const membership = await isInsideSource({
|
|
1801
|
+
file: change.file,
|
|
1802
|
+
boundaryId: input.cursor.boundaryId,
|
|
1803
|
+
getFile: async (fileId) => {
|
|
1804
|
+
const ancestryElapsedMs = Math.max(0, now() - startedAt);
|
|
1805
|
+
budget.elapsedMs = initialBudget.elapsedMs + ancestryElapsedMs;
|
|
1806
|
+
if (budget.providerRequests >= input.maxProviderRequests || budget.elapsedMs >= input.maxElapsedMs || ancestryElapsedMs >= input.maxInvocationElapsedMs) {
|
|
1807
|
+
return null;
|
|
1808
|
+
}
|
|
1809
|
+
budget.providerRequests += 1;
|
|
1810
|
+
const parent = await input.getFile(fileId);
|
|
1811
|
+
budget.elapsedMs = initialBudget.elapsedMs + Math.max(0, now() - startedAt);
|
|
1812
|
+
return parent;
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
if (change.file.mimeType === GOOGLE_DRIVE_FOLDER_MIME_TYPE) {
|
|
1816
|
+
entries.delete(change.fileId);
|
|
1817
|
+
if (membership !== "outside") requiresFullReconciliation = true;
|
|
1818
|
+
else repairCandidates.add(change.fileId);
|
|
1819
|
+
continue;
|
|
1820
|
+
}
|
|
1821
|
+
if (membership === "inside") {
|
|
1822
|
+
entries.set(change.fileId, toInventoryEntry(change.file, input.source, input.maxFileBytes));
|
|
1823
|
+
} else if (membership === "unknown") {
|
|
1824
|
+
entries.delete(change.fileId);
|
|
1825
|
+
requiresFullReconciliation = true;
|
|
1826
|
+
} else {
|
|
1827
|
+
repairCandidates.add(change.fileId);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
if (repairCandidates.size > 0) {
|
|
1831
|
+
for (const fileId of repairCandidates) {
|
|
1832
|
+
if (entries.delete(fileId)) requiresFullReconciliation = true;
|
|
1833
|
+
}
|
|
1834
|
+
const observed = await input.observedExternalObjectIds([...repairCandidates]);
|
|
1835
|
+
if (observed.size > 0) requiresFullReconciliation = true;
|
|
1836
|
+
}
|
|
1837
|
+
if (page.nextPageToken) {
|
|
1838
|
+
pageToken = boundedToken(page.nextPageToken);
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (!page.newStartPageToken) {
|
|
1842
|
+
throw new GoogleDriveChangesProtocolError("google_drive_changes_terminal_token_missing");
|
|
1843
|
+
}
|
|
1844
|
+
budget.elapsedMs = initialBudget.elapsedMs + Math.max(0, now() - startedAt);
|
|
1845
|
+
return {
|
|
1846
|
+
status: "complete",
|
|
1847
|
+
stopReason: null,
|
|
1848
|
+
entries: [...entries.values()],
|
|
1849
|
+
checkpoint: null,
|
|
1850
|
+
newStartPageToken: boundedToken(page.newStartPageToken),
|
|
1851
|
+
requiresFullReconciliation,
|
|
1852
|
+
providerRequests: budget.providerRequests,
|
|
1853
|
+
elapsedMs: budget.elapsedMs,
|
|
1854
|
+
budget: { ...budget },
|
|
1855
|
+
hardLimitReached: false
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
async function isInsideSource(input) {
|
|
1860
|
+
if (input.file.id === input.boundaryId) return "inside";
|
|
1861
|
+
let parents = input.file.parents;
|
|
1862
|
+
const seen = /* @__PURE__ */ new Set([input.file.id]);
|
|
1863
|
+
for (let depth = 0; depth < MAX_ANCESTOR_DEPTH; depth += 1) {
|
|
1864
|
+
if (parents.includes(input.boundaryId)) return "inside";
|
|
1865
|
+
const next = parents.find((parent2) => !seen.has(parent2));
|
|
1866
|
+
if (!next) return "outside";
|
|
1867
|
+
seen.add(next);
|
|
1868
|
+
const parent = await input.getFile(next);
|
|
1869
|
+
if (!parent) return "unknown";
|
|
1870
|
+
parents = parent.parents;
|
|
1871
|
+
}
|
|
1872
|
+
return "unknown";
|
|
1873
|
+
}
|
|
1874
|
+
function toInventoryEntry(file, source, maxFileBytes) {
|
|
1875
|
+
return {
|
|
1876
|
+
externalObjectId: file.id,
|
|
1877
|
+
externalVersionId: file.version ?? file.modifiedTime ?? file.md5Checksum,
|
|
1878
|
+
sourceId: source.id,
|
|
1879
|
+
parentFolderId: file.parents[0] ?? source.id,
|
|
1880
|
+
driveId: file.driveId ?? source.driveId,
|
|
1881
|
+
title: file.name,
|
|
1882
|
+
mimeType: file.mimeType,
|
|
1883
|
+
modifiedTime: file.modifiedTime,
|
|
1884
|
+
createdTime: file.createdTime,
|
|
1885
|
+
sourceUri: file.webViewLink ?? `https://drive.google.com/open?id=${encodeURIComponent(file.id)}`,
|
|
1886
|
+
transfer: planGoogleDriveTransfer(file, maxFileBytes)
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
function validatedCheckpoint(value, limits) {
|
|
1890
|
+
if (!value || typeof value !== "object") {
|
|
1891
|
+
throw new Error("google_drive_changes_checkpoint_invalid");
|
|
1892
|
+
}
|
|
1893
|
+
const checkpoint = value;
|
|
1894
|
+
const budget = checkpoint.budget;
|
|
1895
|
+
if (checkpoint.version !== 2 || typeof checkpoint.requiresFullReconciliation !== "boolean" || !Array.isArray(checkpoint.seenPageTokenHashes) || checkpoint.seenPageTokenHashes.some((hash) => !/^[a-f0-9]{64}$/.test(hash)) || new Set(checkpoint.seenPageTokenHashes).size !== checkpoint.seenPageTokenHashes.length || checkpoint.seenPageTokenHashes.length > limits.maxProviderRequests || !budget || typeof budget !== "object") {
|
|
1896
|
+
throw new Error("google_drive_changes_checkpoint_invalid");
|
|
1897
|
+
}
|
|
1898
|
+
if (typeof checkpoint.pageToken !== "string") {
|
|
1899
|
+
throw new Error("google_drive_changes_checkpoint_invalid");
|
|
1900
|
+
}
|
|
1901
|
+
boundedToken(checkpoint.pageToken);
|
|
1902
|
+
const budgetRow = budget;
|
|
1903
|
+
for (const [key, maximum] of [
|
|
1904
|
+
["examinedItems", limits.maxItems],
|
|
1905
|
+
["providerRequests", limits.maxProviderRequests],
|
|
1906
|
+
["elapsedMs", limits.maxElapsedMs]
|
|
1907
|
+
]) {
|
|
1908
|
+
const budgetValue = budgetRow[key];
|
|
1909
|
+
if (!Number.isSafeInteger(budgetValue) || Number(budgetValue) < 0 || Number(budgetValue) > maximum) {
|
|
1910
|
+
throw new Error("google_drive_changes_checkpoint_budget_invalid");
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (checkpoint.seenPageTokenHashes.length > Number(budgetRow.providerRequests)) {
|
|
1914
|
+
throw new Error("google_drive_changes_checkpoint_request_history_invalid");
|
|
1915
|
+
}
|
|
1916
|
+
return value;
|
|
1917
|
+
}
|
|
1918
|
+
function limitReason(budget, input, invocationElapsedMs) {
|
|
1919
|
+
if (budget.examinedItems >= input.maxItems) return "item_limit";
|
|
1920
|
+
if (budget.providerRequests >= input.maxProviderRequests) return "api_request_limit";
|
|
1921
|
+
if (budget.elapsedMs >= input.maxElapsedMs || invocationElapsedMs >= input.maxInvocationElapsedMs) {
|
|
1922
|
+
return "elapsed_time_limit";
|
|
1923
|
+
}
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
function emptyBudget() {
|
|
1927
|
+
return { examinedItems: 0, providerRequests: 0, elapsedMs: 0 };
|
|
1928
|
+
}
|
|
1929
|
+
function boundedToken(value) {
|
|
1930
|
+
if (value.length < 1 || value.length > MAX_TOKEN_CHARS) {
|
|
1931
|
+
throw new Error("google_drive_changes_token_invalid");
|
|
1932
|
+
}
|
|
1933
|
+
return value;
|
|
1934
|
+
}
|
|
1935
|
+
function boundedBoundaryId(value) {
|
|
1936
|
+
if (value.length < 1 || value.length > MAX_BOUNDARY_ID_CHARS) {
|
|
1937
|
+
throw new Error("google_drive_changes_boundary_invalid");
|
|
1938
|
+
}
|
|
1939
|
+
return value;
|
|
1940
|
+
}
|
|
1941
|
+
var GoogleDriveCursorInvalidError = class extends Error {
|
|
1942
|
+
budget = null;
|
|
1943
|
+
constructor() {
|
|
1944
|
+
super("google_drive_changes_cursor_invalid");
|
|
1945
|
+
this.name = "GoogleDriveCursorInvalidError";
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
var GoogleDriveChangesProtocolError = class extends Error {
|
|
1949
|
+
constructor(message) {
|
|
1950
|
+
super(message);
|
|
1951
|
+
this.name = "GoogleDriveChangesProtocolError";
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
// src/activities/knowledge-source-sync.ts
|
|
1956
|
+
var sourceRequestAuthority = new AsyncLocalStorage();
|
|
1957
|
+
var sourceFetch = Object.assign(
|
|
1958
|
+
async (...args) => {
|
|
1959
|
+
await sourceRequestAuthority.getStore()?.();
|
|
1960
|
+
return fetch(...args);
|
|
1961
|
+
},
|
|
1962
|
+
{ preconnect: fetch.preconnect }
|
|
1963
|
+
);
|
|
1964
|
+
var DRIVE_API_BASE = "https://www.googleapis.com/drive/v3";
|
|
1965
|
+
var DRIVE_JSON_MAX_BYTES = 2 * 1024 * 1024;
|
|
1966
|
+
var GOOGLE_DRIVE_FULL_RECONCILIATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1967
|
+
var GOOGLE_DRIVE_SYNC_INVOCATION_SLICE_MS = 3e4;
|
|
1968
|
+
var GOOGLE_DRIVE_EXECUTION_CHECKPOINT_MAX_BYTES = 2 * 1024 * 1024;
|
|
1969
|
+
var GOOGLE_DRIVE_ACL_FRESHNESS_MS = 26 * 60 * 60 * 1e3;
|
|
1970
|
+
var GOOGLE_DRIVE_ACL_MAX_PRINCIPALS = 1e3;
|
|
1971
|
+
var ATLASSIAN_API_BASE = "https://api.atlassian.com";
|
|
1972
|
+
var ATLASSIAN_JSON_MAX_BYTES = 4 * 1024 * 1024;
|
|
1973
|
+
function knowledgeSyncObservationPolicy(kind) {
|
|
1974
|
+
return kind === "google_drive" ? { revisionOrdering: "canonical_decimal", filterWithDriveDurability: true } : { revisionOrdering: "first_observation", filterWithDriveDurability: false };
|
|
1975
|
+
}
|
|
1976
|
+
function createKnowledgeSourceSyncActivities(services, resolveDocumentServices) {
|
|
1977
|
+
const documentActivities = createDocumentActivities(services, resolveDocumentServices);
|
|
1978
|
+
return {
|
|
1979
|
+
runKnowledgeSourceSyncBatch: async (input) => {
|
|
1980
|
+
const { settings, db, objectStorage, observability } = await services();
|
|
1981
|
+
if (!objectStorage) throw new Error("object storage is not configured");
|
|
1982
|
+
if (!input.agent)
|
|
1983
|
+
return { action: "failed", bufferedWake: false, errorCode: "accepted_agent_required" };
|
|
1984
|
+
const context = {
|
|
1985
|
+
accountId: input.accountId,
|
|
1986
|
+
workspaceId: input.workspaceId,
|
|
1987
|
+
actor: input.agent
|
|
1988
|
+
};
|
|
1989
|
+
let boundSource = null;
|
|
1990
|
+
const authorize = async () => {
|
|
1991
|
+
const policy = await freezeAgentLearningPolicy(db, context);
|
|
1992
|
+
if (policy.scheduledTaskRunId !== input.scheduledTaskRunId)
|
|
1993
|
+
throw new Error("Source fetch does not belong to this accepted agent run");
|
|
1994
|
+
if (boundSource) {
|
|
1995
|
+
const [currentTask, connection] = await Promise.all([
|
|
1996
|
+
getScheduledTask(db, input.workspaceId, input.taskId),
|
|
1997
|
+
getConnectionMetadata2(
|
|
1998
|
+
db,
|
|
1999
|
+
input.workspaceId,
|
|
2000
|
+
boundSource.connection.connectionId,
|
|
2001
|
+
boundSource.initiatingSubjectId
|
|
2002
|
+
)
|
|
2003
|
+
]);
|
|
2004
|
+
const control = currentTask?.metadata.knowledgeSourceSync;
|
|
2005
|
+
if (!currentTask || currentTask.status !== "active" || control?.sourceEnabled === false || control?.connectionPaused === true || !isDeepStrictEqual2(scheduledTaskKnowledgeSource(currentTask), boundSource) || !connection || connection.status !== "active" || connection.version !== boundSource.connection.connectionVersion || connection.subjectId !== boundSource.connection.ownerSubjectId)
|
|
2006
|
+
throw new SyncFailure("authority_changed", false);
|
|
2007
|
+
}
|
|
2008
|
+
return policy;
|
|
2009
|
+
};
|
|
2010
|
+
await authorize();
|
|
2011
|
+
const accepted = await getScheduledTaskRunAcceptedExecution(db, {
|
|
2012
|
+
workspaceId: input.workspaceId,
|
|
2013
|
+
runId: input.scheduledTaskRunId
|
|
2014
|
+
});
|
|
2015
|
+
const task = accepted?.task;
|
|
2016
|
+
const action = task ? scheduledTaskKnowledgeSource(task) : null;
|
|
2017
|
+
const liveTask = await getScheduledTask(db, input.workspaceId, input.taskId);
|
|
2018
|
+
if (!task || !action || !liveTask || liveTask.status !== "active" || task.id !== input.taskId || task.accountId !== input.accountId || task.action.kind !== "agent_turn" || action.sourceId !== input.sourceId || accepted.causalHumanSubjectId !== action.initiatingSubjectId) {
|
|
2019
|
+
return { action: "failed", bufferedWake: false };
|
|
2020
|
+
}
|
|
2021
|
+
boundSource = action;
|
|
2022
|
+
await authorize();
|
|
2023
|
+
await ensureKnowledgeSourceSyncState(db, task);
|
|
2024
|
+
await recordKnowledgeSourceSyncWake(db, {
|
|
2025
|
+
accountId: input.accountId,
|
|
2026
|
+
workspaceId: input.workspaceId,
|
|
2027
|
+
sourceId: input.sourceId,
|
|
2028
|
+
scheduledTaskId: task.id,
|
|
2029
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2030
|
+
cause: "scheduled",
|
|
2031
|
+
producerKey: `agent-source:${input.scheduledTaskRunId}`,
|
|
2032
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2033
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration
|
|
2034
|
+
});
|
|
2035
|
+
const providerLabel = action.connection.providerDomain === ATLASSIAN_PROVIDER_DOMAIN ? "atlassian" : "google_drive";
|
|
2036
|
+
if (action.controlWorkspaceId !== input.workspaceId || action.sourceConfigGeneration <= 0 || !action.providerCoordinationKey) {
|
|
2037
|
+
return await failWithoutProvider("authority_changed");
|
|
2038
|
+
}
|
|
2039
|
+
const lease = await claimKnowledgeSourceSyncLease(db, {
|
|
2040
|
+
...input,
|
|
2041
|
+
overlapPolicy: input.overlapPolicy
|
|
2042
|
+
});
|
|
2043
|
+
if (lease.action !== "claimed") {
|
|
2044
|
+
observability.incrementCounter({
|
|
2045
|
+
name: "opengeni_knowledge_source_sync_runs_total",
|
|
2046
|
+
labels: { provider: providerLabel, outcome: lease.action }
|
|
2047
|
+
});
|
|
2048
|
+
return { action: lease.action };
|
|
2049
|
+
}
|
|
2050
|
+
let knowledgeRunId = null;
|
|
2051
|
+
let activeSourceSyncGeneration = null;
|
|
2052
|
+
const startedAt = Date.now();
|
|
2053
|
+
const summary = {
|
|
2054
|
+
phase: "queued",
|
|
2055
|
+
scanned: 0,
|
|
2056
|
+
imported: 0,
|
|
2057
|
+
unchanged: 0,
|
|
2058
|
+
skipped: 0,
|
|
2059
|
+
failed: 0,
|
|
2060
|
+
bytes: 0,
|
|
2061
|
+
providerRequests: 0,
|
|
2062
|
+
elapsedMs: 0,
|
|
2063
|
+
indexed: 0,
|
|
2064
|
+
aclPending: 0,
|
|
2065
|
+
retryable: false,
|
|
2066
|
+
limitReached: null,
|
|
2067
|
+
checkpointed: false,
|
|
2068
|
+
reconnectRequired: false,
|
|
2069
|
+
failures: []
|
|
2070
|
+
};
|
|
2071
|
+
observability.info("Knowledge source sync started", {
|
|
2072
|
+
provider: providerLabel,
|
|
2073
|
+
outcome: "started"
|
|
2074
|
+
});
|
|
2075
|
+
try {
|
|
2076
|
+
summary.phase = "inventory";
|
|
2077
|
+
const resolved = await getKnowledgeSourceForSyncAuthority(db, {
|
|
2078
|
+
accountId: input.accountId,
|
|
2079
|
+
workspaceId: input.workspaceId,
|
|
2080
|
+
sourceId: input.sourceId,
|
|
2081
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2082
|
+
});
|
|
2083
|
+
if (!resolved || resolved.source.lifecycleState !== "active") {
|
|
2084
|
+
throw new SyncFailure("authority_changed", false);
|
|
2085
|
+
}
|
|
2086
|
+
if (scopedKnowledgeScopeKey(resolved.source.scope) !== scopedKnowledgeScopeKey(action.destination)) {
|
|
2087
|
+
throw new SyncFailure("authority_changed", false);
|
|
2088
|
+
}
|
|
2089
|
+
const liveState = await reconcileKnowledgeSourceSyncLiveGeneration(db, {
|
|
2090
|
+
accountId: input.accountId,
|
|
2091
|
+
workspaceId: input.workspaceId,
|
|
2092
|
+
sourceId: input.sourceId,
|
|
2093
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2094
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2095
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2096
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2097
|
+
liveSourceSyncGeneration: resolved.source.syncGeneration
|
|
2098
|
+
});
|
|
2099
|
+
activeSourceSyncGeneration = resolved.source.syncGeneration;
|
|
2100
|
+
const syncOperationId = `scheduled-task-run:${input.scheduledTaskRunId}`;
|
|
2101
|
+
const replayedRun = await getKnowledgeSyncRunForSyncAuthority(db, {
|
|
2102
|
+
accountId: input.accountId,
|
|
2103
|
+
workspaceId: input.workspaceId,
|
|
2104
|
+
sourceId: input.sourceId,
|
|
2105
|
+
operationId: syncOperationId,
|
|
2106
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2107
|
+
});
|
|
2108
|
+
if (replayedRun && replayedRun.state !== "started") {
|
|
2109
|
+
const replayedSummary = KnowledgeSourceSyncRunSummary.parse(replayedRun.metadata);
|
|
2110
|
+
const settlementInput = {
|
|
2111
|
+
accountId: input.accountId,
|
|
2112
|
+
workspaceId: input.workspaceId,
|
|
2113
|
+
sourceId: input.sourceId,
|
|
2114
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2115
|
+
knowledgeSyncRunId: replayedRun.id,
|
|
2116
|
+
status: replayedRun.state === "succeeded" ? "succeeded" : "failed",
|
|
2117
|
+
summary: replayedSummary,
|
|
2118
|
+
error: replayedRun.errorCode,
|
|
2119
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2120
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2121
|
+
sourceSyncGeneration: replayedRun.inputSyncGeneration,
|
|
2122
|
+
completedSourceSyncGeneration: replayedRun.state === "succeeded" ? replayedRun.inputSyncGeneration + 1 : null,
|
|
2123
|
+
...replayedRun.state === "succeeded" && replayedRun.metadata.providerCursor && typeof replayedRun.metadata.providerCursor === "object" ? {
|
|
2124
|
+
providerCursor: replayedRun.metadata.providerCursor
|
|
2125
|
+
} : {}
|
|
2126
|
+
};
|
|
2127
|
+
const settled2 = replayedRun.state === "failed" && replayedSummary.reconnectRequired ? await deauthorizeAndSettleKnowledgeSourceSyncFailure(db, {
|
|
2128
|
+
deauthorization: {
|
|
2129
|
+
accountId: input.accountId,
|
|
2130
|
+
workspaceId: input.workspaceId,
|
|
2131
|
+
sourceId: input.sourceId,
|
|
2132
|
+
audience: action.destination,
|
|
2133
|
+
operationId: `knowledge-sync-deauthorize:${input.sourceId}:${action.connection.connectionVersion}:${replayedRun.errorCode ?? "connection_reconnect_required"}`,
|
|
2134
|
+
reasonCode: "connection_reconnect_required",
|
|
2135
|
+
actor: {
|
|
2136
|
+
kind: "service",
|
|
2137
|
+
subjectId: "knowledge-source-sync",
|
|
2138
|
+
initiatingHumanSubjectId: action.initiatingSubjectId
|
|
2139
|
+
}
|
|
2140
|
+
},
|
|
2141
|
+
settlement: settlementInput
|
|
2142
|
+
}) : await settleKnowledgeSourceSyncLease(db, settlementInput);
|
|
2143
|
+
await recordConnectorUsage("completed", replayedSummary.scanned, "item");
|
|
2144
|
+
await recordConnectorUsage("items", replayedSummary.imported, "item");
|
|
2145
|
+
await recordConnectorUsage("bytes", replayedSummary.bytes, "byte");
|
|
2146
|
+
return {
|
|
2147
|
+
action: replayedRun.state === "succeeded" ? "complete" : "failed",
|
|
2148
|
+
summary: replayedSummary,
|
|
2149
|
+
...replayedRun.errorCode ? { errorCode: replayedRun.errorCode } : {},
|
|
2150
|
+
bufferedWake: settled2.bufferedWake,
|
|
2151
|
+
bufferedScheduledTaskRunId: settled2.bufferedScheduledTaskRunId
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
const provider = await resolveKnowledgeSyncProvider({
|
|
2155
|
+
db,
|
|
2156
|
+
settings,
|
|
2157
|
+
observability,
|
|
2158
|
+
accountId: input.accountId,
|
|
2159
|
+
workspaceId: input.workspaceId,
|
|
2160
|
+
action,
|
|
2161
|
+
sourceId: input.sourceId,
|
|
2162
|
+
externalSourceId: resolved.source.externalSourceId
|
|
2163
|
+
});
|
|
2164
|
+
const selectedDestination = provider.selectedDestination;
|
|
2165
|
+
const observationPolicy = knowledgeSyncObservationPolicy(provider.kind);
|
|
2166
|
+
let aclGeneration = resolved.source.currentAclGeneration;
|
|
2167
|
+
let acl = aclGeneration ? await getKnowledgeSourceAclForSyncAuthority(db, {
|
|
2168
|
+
accountId: input.accountId,
|
|
2169
|
+
workspaceId: input.workspaceId,
|
|
2170
|
+
sourceId: input.sourceId,
|
|
2171
|
+
generation: aclGeneration,
|
|
2172
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2173
|
+
}) : null;
|
|
2174
|
+
const actor = {
|
|
2175
|
+
kind: "service",
|
|
2176
|
+
subjectId: "knowledge-source-sync",
|
|
2177
|
+
initiatingHumanSubjectId: action.initiatingSubjectId
|
|
2178
|
+
};
|
|
2179
|
+
if (!acl) {
|
|
2180
|
+
acl = await appendKnowledgeSourceAclVersion(db, {
|
|
2181
|
+
accountId: input.accountId,
|
|
2182
|
+
workspaceId: input.workspaceId,
|
|
2183
|
+
sourceId: input.sourceId,
|
|
2184
|
+
audience: action.destination,
|
|
2185
|
+
expectedSourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2186
|
+
expectedAclGeneration: 0,
|
|
2187
|
+
aclVersion: "opengeni-destination-v1",
|
|
2188
|
+
agentAccess: false,
|
|
2189
|
+
operationId: `source-initial-acl:${input.sourceId}`,
|
|
2190
|
+
reasonCode: "initial_destination_acl",
|
|
2191
|
+
actor
|
|
2192
|
+
});
|
|
2193
|
+
aclGeneration = acl.generation;
|
|
2194
|
+
}
|
|
2195
|
+
const knowledgeRun = replayedRun ?? await beginKnowledgeSyncRun(db, {
|
|
2196
|
+
accountId: input.accountId,
|
|
2197
|
+
workspaceId: input.workspaceId,
|
|
2198
|
+
sourceId: input.sourceId,
|
|
2199
|
+
expectedSourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2200
|
+
expectedSyncGeneration: resolved.source.syncGeneration,
|
|
2201
|
+
inputCursor: null,
|
|
2202
|
+
operationId: syncOperationId,
|
|
2203
|
+
actor
|
|
2204
|
+
});
|
|
2205
|
+
knowledgeRunId = knowledgeRun.id;
|
|
2206
|
+
const originalDriver = provider.driver;
|
|
2207
|
+
const readAcl = originalDriver.readAcl;
|
|
2208
|
+
const driver = {
|
|
2209
|
+
...originalDriver,
|
|
2210
|
+
inventory: (...args) => sourceRequestAuthority.run(authorize, () => originalDriver.inventory(...args)),
|
|
2211
|
+
fetchContent: (...args) => sourceRequestAuthority.run(authorize, () => originalDriver.fetchContent(...args)),
|
|
2212
|
+
...readAcl ? {
|
|
2213
|
+
readAcl: (...args) => sourceRequestAuthority.run(authorize, () => readAcl(...args))
|
|
2214
|
+
} : {}
|
|
2215
|
+
};
|
|
2216
|
+
await authorize();
|
|
2217
|
+
const inventory = await driver.inventory(
|
|
2218
|
+
lease.state.executionCheckpoint,
|
|
2219
|
+
lease.state.providerCursor
|
|
2220
|
+
);
|
|
2221
|
+
summary.providerRequests = inventory.providerRequests;
|
|
2222
|
+
summary.elapsedMs = inventory.elapsedMs;
|
|
2223
|
+
const base = selectedDestination.collectionId ? await getDocumentBase(db, input.workspaceId, selectedDestination.collectionId) : await ensureDefaultBase(db, {
|
|
2224
|
+
accountId: input.accountId,
|
|
2225
|
+
workspaceId: input.workspaceId
|
|
2226
|
+
});
|
|
2227
|
+
if (!base) throw new SyncFailure("authority_changed", false);
|
|
2228
|
+
const durableObservations = await recordKnowledgeSourceSyncObjectObservations(db, {
|
|
2229
|
+
accountId: input.accountId,
|
|
2230
|
+
workspaceId: input.workspaceId,
|
|
2231
|
+
sourceId: input.sourceId,
|
|
2232
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2233
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2234
|
+
scanGeneration: liveState.activeScanGeneration,
|
|
2235
|
+
executionCheckpointGeneration: lease.state.executionCheckpointGeneration,
|
|
2236
|
+
revisionOrdering: observationPolicy.revisionOrdering,
|
|
2237
|
+
observations: inventory.entries.map((entry) => ({
|
|
2238
|
+
externalObjectId: entry.externalObjectId,
|
|
2239
|
+
providerRevision: entry.externalVersionId,
|
|
2240
|
+
metadataHash: entryMetadataHash(entry)
|
|
2241
|
+
}))
|
|
2242
|
+
});
|
|
2243
|
+
const entriesToProcess = observationPolicy.filterWithDriveDurability ? inventory.entries.filter(
|
|
2244
|
+
(entry, index) => shouldProcessGoogleDriveDurableObservation({
|
|
2245
|
+
entry,
|
|
2246
|
+
observation: durableObservations[index],
|
|
2247
|
+
sourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2248
|
+
aclGeneration
|
|
2249
|
+
})
|
|
2250
|
+
) : inventory.entries;
|
|
2251
|
+
const observationFloorsById = /* @__PURE__ */ new Map();
|
|
2252
|
+
for (const observation of durableObservations) {
|
|
2253
|
+
observationFloorsById.set(observation.externalObjectId, {
|
|
2254
|
+
externalObjectId: observation.externalObjectId,
|
|
2255
|
+
providerRevision: observation.providerRevision,
|
|
2256
|
+
metadataHash: observation.metadataHash
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
const observationFloors = [...observationFloorsById.values()];
|
|
2260
|
+
const executionCheckpoint = mergeGoogleDriveDurableObservationFloors(
|
|
2261
|
+
inventory.checkpoint,
|
|
2262
|
+
observationFloors,
|
|
2263
|
+
action.limits.maxItems
|
|
2264
|
+
);
|
|
2265
|
+
const ensureVersionIndexed = async (details) => {
|
|
2266
|
+
if (!details.version.documentId) {
|
|
2267
|
+
throw new SyncFailure("authority_changed", false);
|
|
2268
|
+
}
|
|
2269
|
+
let obligation = details.obligation ?? await getKnowledgeSourceSyncIndexObligationForVersion(db, {
|
|
2270
|
+
accountId: input.accountId,
|
|
2271
|
+
workspaceId: input.workspaceId,
|
|
2272
|
+
knowledgeDocumentVersionId: details.version.id,
|
|
2273
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2274
|
+
});
|
|
2275
|
+
if (!obligation) {
|
|
2276
|
+
obligation = await enqueueKnowledgeSourceSyncIndexObligation(db, {
|
|
2277
|
+
accountId: input.accountId,
|
|
2278
|
+
workspaceId: input.workspaceId,
|
|
2279
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2280
|
+
sourceId: input.sourceId,
|
|
2281
|
+
sourceSyncGeneration: knowledgeRun.inputSyncGeneration,
|
|
2282
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2283
|
+
externalObjectId: details.entry.externalObjectId,
|
|
2284
|
+
knowledgeSourceObjectId: details.object.id,
|
|
2285
|
+
knowledgeDocumentVersionId: details.version.id,
|
|
2286
|
+
documentId: details.version.documentId,
|
|
2287
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2288
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2289
|
+
objectLifecycleGeneration: details.object.lifecycleGeneration,
|
|
2290
|
+
objectVersionGeneration: details.version.versionGeneration,
|
|
2291
|
+
citationLocator: driver.citationLocator(details.entry)
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
if (obligation.documentId !== details.version.documentId) {
|
|
2295
|
+
throw new SyncFailure("authority_changed", false);
|
|
2296
|
+
}
|
|
2297
|
+
if (obligation.status === "indexed" && !details.indexRequired) return obligation;
|
|
2298
|
+
if (obligation.status === "indexed" && details.indexRequired) {
|
|
2299
|
+
summary.phase = "index";
|
|
2300
|
+
try {
|
|
2301
|
+
const indexedDocument = await documentActivities.indexDocument({
|
|
2302
|
+
accountId: input.accountId,
|
|
2303
|
+
workspaceId: input.workspaceId,
|
|
2304
|
+
documentId: details.version.documentId,
|
|
2305
|
+
authorityKind: action.destination.kind,
|
|
2306
|
+
authorityWorkspaceId: action.destination.workspaceId,
|
|
2307
|
+
authoritySubjectId: action.destination.subjectId
|
|
2308
|
+
});
|
|
2309
|
+
if (indexedDocument.status !== "ready") {
|
|
2310
|
+
throw new Error("document indexing did not complete");
|
|
2311
|
+
}
|
|
2312
|
+
} catch {
|
|
2313
|
+
throw new SyncFailure("indexing_failed", true);
|
|
2314
|
+
}
|
|
2315
|
+
return obligation;
|
|
2316
|
+
}
|
|
2317
|
+
if (obligation.status === "failed") {
|
|
2318
|
+
const retried = await retryKnowledgeSourceSyncIndexObligation(db, {
|
|
2319
|
+
accountId: input.accountId,
|
|
2320
|
+
workspaceId: input.workspaceId,
|
|
2321
|
+
obligationId: obligation.id,
|
|
2322
|
+
scheduledTaskRunId: input.scheduledTaskRunId
|
|
2323
|
+
});
|
|
2324
|
+
if (retried === "invalidated") {
|
|
2325
|
+
throw new SyncFailure("authority_changed", false);
|
|
2326
|
+
}
|
|
2327
|
+
obligation = { ...obligation, status: retried };
|
|
2328
|
+
if (retried === "indexed") return obligation;
|
|
2329
|
+
}
|
|
2330
|
+
if (obligation.status !== "pending") {
|
|
2331
|
+
throw new SyncFailure("authority_changed", false);
|
|
2332
|
+
}
|
|
2333
|
+
if (details.indexRequired) {
|
|
2334
|
+
summary.phase = "index";
|
|
2335
|
+
try {
|
|
2336
|
+
const indexedDocument = await documentActivities.indexDocument({
|
|
2337
|
+
accountId: input.accountId,
|
|
2338
|
+
workspaceId: input.workspaceId,
|
|
2339
|
+
documentId: details.version.documentId,
|
|
2340
|
+
authorityKind: action.destination.kind,
|
|
2341
|
+
authorityWorkspaceId: action.destination.workspaceId,
|
|
2342
|
+
authoritySubjectId: action.destination.subjectId
|
|
2343
|
+
});
|
|
2344
|
+
if (indexedDocument.status !== "ready") {
|
|
2345
|
+
throw new Error("document indexing did not complete");
|
|
2346
|
+
}
|
|
2347
|
+
} catch {
|
|
2348
|
+
await settleKnowledgeSourceSyncIndexObligation(db, {
|
|
2349
|
+
accountId: input.accountId,
|
|
2350
|
+
workspaceId: input.workspaceId,
|
|
2351
|
+
obligationId: obligation.id,
|
|
2352
|
+
status: "failed",
|
|
2353
|
+
failureCode: "indexing_failed"
|
|
2354
|
+
}).catch(() => void 0);
|
|
2355
|
+
throw new SyncFailure("indexing_failed", true);
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
const settlement = await settleKnowledgeSourceSyncIndexObligation(db, {
|
|
2359
|
+
accountId: input.accountId,
|
|
2360
|
+
workspaceId: input.workspaceId,
|
|
2361
|
+
obligationId: obligation.id,
|
|
2362
|
+
status: "indexed"
|
|
2363
|
+
});
|
|
2364
|
+
if (settlement === "invalidated") {
|
|
2365
|
+
throw new SyncFailure("authority_changed", false);
|
|
2366
|
+
}
|
|
2367
|
+
return { ...obligation, status: "indexed" };
|
|
2368
|
+
};
|
|
2369
|
+
const recordEntryAcl = async (details) => {
|
|
2370
|
+
if (!driver.readAcl || !provider.aclAuthority) {
|
|
2371
|
+
return { aclEligibility: "pending", aclEvidence: null };
|
|
2372
|
+
}
|
|
2373
|
+
await beginGoogleDriveObjectAclRefresh(db, {
|
|
2374
|
+
accountId: input.accountId,
|
|
2375
|
+
workspaceId: input.workspaceId,
|
|
2376
|
+
obligationId: details.obligation.id,
|
|
2377
|
+
sourceSyncGeneration: details.obligation.sourceSyncGeneration,
|
|
2378
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2379
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2380
|
+
objectLifecycleGeneration: details.object.lifecycleGeneration,
|
|
2381
|
+
objectVersionGeneration: details.version.versionGeneration
|
|
2382
|
+
});
|
|
2383
|
+
const remainingProviderRequests = action.limits.maxProviderRequests - summary.providerRequests;
|
|
2384
|
+
if (remainingProviderRequests < 1) {
|
|
2385
|
+
throw new SyncFailure("resource_limit", false);
|
|
2386
|
+
}
|
|
2387
|
+
await authorize();
|
|
2388
|
+
const evidence = await driver.readAcl(details.entry, remainingProviderRequests);
|
|
2389
|
+
summary.providerRequests += evidence.providerRequests;
|
|
2390
|
+
if (summary.providerRequests > action.limits.maxProviderRequests) {
|
|
2391
|
+
throw new SyncFailure("resource_limit", false);
|
|
2392
|
+
}
|
|
2393
|
+
const recorded = await recordGoogleDriveObjectAclEvidence(db, {
|
|
2394
|
+
accountId: input.accountId,
|
|
2395
|
+
workspaceId: input.workspaceId,
|
|
2396
|
+
obligationId: details.obligation.id,
|
|
2397
|
+
connectionId: provider.aclAuthority.connectionId,
|
|
2398
|
+
connectionVersion: provider.aclAuthority.connectionVersion,
|
|
2399
|
+
sourceGooglePermissionId: provider.aclAuthority.sourceGooglePermissionId,
|
|
2400
|
+
sourceSyncGeneration: details.obligation.sourceSyncGeneration,
|
|
2401
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2402
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2403
|
+
objectLifecycleGeneration: details.object.lifecycleGeneration,
|
|
2404
|
+
objectVersionGeneration: details.version.versionGeneration,
|
|
2405
|
+
providerRevision: evidence.providerRevision,
|
|
2406
|
+
driveId: evidence.driveId,
|
|
2407
|
+
aclRevision: evidence.aclRevision,
|
|
2408
|
+
eligibility: evidence.eligibility,
|
|
2409
|
+
observedAt: evidence.observedAt,
|
|
2410
|
+
expiresAt: evidence.expiresAt,
|
|
2411
|
+
citationLocator: driver.citationLocator(details.entry),
|
|
2412
|
+
operationId: `drive-acl:${details.obligation.id}:${evidence.aclRevision}`,
|
|
2413
|
+
principals: evidence.principals
|
|
2414
|
+
});
|
|
2415
|
+
return {
|
|
2416
|
+
aclEligibility: evidence.eligibility,
|
|
2417
|
+
aclEvidence: {
|
|
2418
|
+
version: 1,
|
|
2419
|
+
provider: "google_drive",
|
|
2420
|
+
evidenceId: recorded.evidenceId,
|
|
2421
|
+
aclRevision: evidence.aclRevision,
|
|
2422
|
+
aclHash: recorded.aclHash,
|
|
2423
|
+
observedAt: evidence.observedAt,
|
|
2424
|
+
expiresAt: evidence.expiresAt
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
};
|
|
2428
|
+
const outcomes = [];
|
|
2429
|
+
for (const entry of entriesToProcess) {
|
|
2430
|
+
await authorize();
|
|
2431
|
+
heartbeat({ sourceId: input.sourceId, externalObjectId: entry.externalObjectId });
|
|
2432
|
+
const observationFloor = observationFloorsById.get(entry.externalObjectId);
|
|
2433
|
+
if (!observationFloor) throw new SyncFailure("provider_payload_invalid", false);
|
|
2434
|
+
const syncObservation = {
|
|
2435
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2436
|
+
scanGeneration: liveState.activeScanGeneration,
|
|
2437
|
+
providerRevision: observationFloor.providerRevision,
|
|
2438
|
+
metadataHash: observationFloor.metadataHash
|
|
2439
|
+
};
|
|
2440
|
+
summary.scanned += 1;
|
|
2441
|
+
if (entry.transfer.action === "skip") {
|
|
2442
|
+
summary.skipped += 1;
|
|
2443
|
+
outcomes.push({
|
|
2444
|
+
externalObjectId: entry.externalObjectId,
|
|
2445
|
+
outcome: "skipped",
|
|
2446
|
+
reasonCode: entry.transfer.reason
|
|
2447
|
+
});
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
if (entry.transfer.action === "traverse") continue;
|
|
2451
|
+
try {
|
|
2452
|
+
const existingObject = await getKnowledgeSourceObjectForSyncAuthority(db, {
|
|
2453
|
+
accountId: input.accountId,
|
|
2454
|
+
workspaceId: input.workspaceId,
|
|
2455
|
+
sourceId: input.sourceId,
|
|
2456
|
+
externalObjectId: entry.externalObjectId,
|
|
2457
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2458
|
+
});
|
|
2459
|
+
let object = existingObject ?? await upsertKnowledgeSourceObject(db, {
|
|
2460
|
+
accountId: input.accountId,
|
|
2461
|
+
workspaceId: input.workspaceId,
|
|
2462
|
+
sourceId: input.sourceId,
|
|
2463
|
+
externalObjectId: entry.externalObjectId,
|
|
2464
|
+
operationId: `source-object:${input.sourceId}:${entry.externalObjectId}`,
|
|
2465
|
+
actor
|
|
2466
|
+
});
|
|
2467
|
+
if (object.lifecycleState !== "active") {
|
|
2468
|
+
const restored = await restoreKnowledgeSourceObject(db, {
|
|
2469
|
+
accountId: input.accountId,
|
|
2470
|
+
workspaceId: input.workspaceId,
|
|
2471
|
+
targetId: object.id,
|
|
2472
|
+
expectedGeneration: object.lifecycleGeneration,
|
|
2473
|
+
operationId: `knowledge-sync-object-restore:${object.id}:${liveState.activeScanGeneration}`,
|
|
2474
|
+
reasonCode: "authoritative_scan_observed",
|
|
2475
|
+
actor
|
|
2476
|
+
});
|
|
2477
|
+
object = {
|
|
2478
|
+
...object,
|
|
2479
|
+
lifecycleState: "active",
|
|
2480
|
+
lifecycleGeneration: restored.lifecycleGeneration
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
const currentObservation = object.currentVersionId ? await getKnowledgeDocumentVersionObservationForSyncAuthority(db, {
|
|
2484
|
+
accountId: input.accountId,
|
|
2485
|
+
workspaceId: input.workspaceId,
|
|
2486
|
+
versionId: object.currentVersionId,
|
|
2487
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2488
|
+
}) : null;
|
|
2489
|
+
const currentVersion = currentObservation?.version ?? null;
|
|
2490
|
+
const metadataHash = entryMetadataHash(entry);
|
|
2491
|
+
const observedProviderRevision = typeof currentObservation?.sourceMetadata.providerRevision === "string" ? currentObservation.sourceMetadata.providerRevision : null;
|
|
2492
|
+
const observedMetadataHash = typeof currentObservation?.sourceMetadata.metadataHash === "string" ? currentObservation.sourceMetadata.metadataHash : null;
|
|
2493
|
+
const observedSourceLifecycleGeneration = Number(
|
|
2494
|
+
currentObservation?.sourceMetadata.sourceLifecycleGeneration ?? 0
|
|
2495
|
+
);
|
|
2496
|
+
const observedObjectLifecycleGeneration = Number(
|
|
2497
|
+
currentObservation?.sourceMetadata.objectLifecycleGeneration ?? 0
|
|
2498
|
+
);
|
|
2499
|
+
const currentObligation = currentVersion ? await getKnowledgeSourceSyncIndexObligationForVersion(db, {
|
|
2500
|
+
accountId: input.accountId,
|
|
2501
|
+
workspaceId: input.workspaceId,
|
|
2502
|
+
knowledgeDocumentVersionId: currentVersion.id,
|
|
2503
|
+
initiatingSubjectId: action.initiatingSubjectId
|
|
2504
|
+
}) : null;
|
|
2505
|
+
if (currentVersion && currentVersion.documentId && observedProviderRevision === entry.externalVersionId && observedMetadataHash === metadataHash && currentVersion.aclGeneration === aclGeneration && observedSourceLifecycleGeneration === resolved.source.lifecycleGeneration && observedObjectLifecycleGeneration === object.lifecycleGeneration && currentObligation?.status !== "invalidated") {
|
|
2506
|
+
await updateKnowledgeSourceDocumentObservationMetadata(db, {
|
|
2507
|
+
accountId: input.accountId,
|
|
2508
|
+
workspaceId: input.workspaceId,
|
|
2509
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2510
|
+
sourceId: input.sourceId,
|
|
2511
|
+
expectedSourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2512
|
+
objectId: object.id,
|
|
2513
|
+
expectedObjectLifecycleGeneration: object.lifecycleGeneration,
|
|
2514
|
+
versionId: currentVersion.id,
|
|
2515
|
+
documentId: currentVersion.documentId,
|
|
2516
|
+
title: entry.title,
|
|
2517
|
+
sourceUri: entry.sourceUri,
|
|
2518
|
+
sourceVersion: entry.externalVersionId ?? currentVersion.contentSha256,
|
|
2519
|
+
sourceUpdatedAt: entry.modifiedTime,
|
|
2520
|
+
syncObservation
|
|
2521
|
+
});
|
|
2522
|
+
const obligation2 = await ensureVersionIndexed({
|
|
2523
|
+
entry,
|
|
2524
|
+
object,
|
|
2525
|
+
version: currentVersion,
|
|
2526
|
+
indexRequired: (currentObservation?.documentChunkCount ?? 0) === 0,
|
|
2527
|
+
obligation: currentObligation
|
|
2528
|
+
});
|
|
2529
|
+
const objectAcl2 = await recordEntryAcl({
|
|
2530
|
+
entry,
|
|
2531
|
+
object,
|
|
2532
|
+
version: currentVersion,
|
|
2533
|
+
obligation: obligation2
|
|
2534
|
+
});
|
|
2535
|
+
summary.unchanged += 1;
|
|
2536
|
+
outcomes.push({
|
|
2537
|
+
externalObjectId: entry.externalObjectId,
|
|
2538
|
+
outcome: "unchanged",
|
|
2539
|
+
contentSha256: currentVersion.contentSha256,
|
|
2540
|
+
providerRevision: entry.externalVersionId,
|
|
2541
|
+
metadataHash,
|
|
2542
|
+
aclEligibility: objectAcl2.aclEligibility,
|
|
2543
|
+
aclEvidence: objectAcl2.aclEvidence,
|
|
2544
|
+
indexObligationId: obligation2.id
|
|
2545
|
+
});
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
summary.phase = "transfer";
|
|
2549
|
+
await authorize();
|
|
2550
|
+
const bytes = await driver.fetchContent(entry, action.limits.maxFileBytes);
|
|
2551
|
+
const contentSha256 = createHash2("sha256").update(bytes).digest("hex");
|
|
2552
|
+
const observationKey = createHash2("sha256").update(
|
|
2553
|
+
JSON.stringify({
|
|
2554
|
+
externalObjectId: entry.externalObjectId,
|
|
2555
|
+
providerRevision: entry.externalVersionId,
|
|
2556
|
+
metadataHash,
|
|
2557
|
+
contentSha256,
|
|
2558
|
+
aclGeneration,
|
|
2559
|
+
sourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2560
|
+
objectLifecycleGeneration: object.lifecycleGeneration
|
|
2561
|
+
})
|
|
2562
|
+
).digest("hex");
|
|
2563
|
+
if (currentVersion?.contentSha256 === contentSha256 && currentVersion.documentId && currentVersion.fileId) {
|
|
2564
|
+
const version2 = await appendKnowledgeDocumentVersion(db, {
|
|
2565
|
+
accountId: input.accountId,
|
|
2566
|
+
workspaceId: input.workspaceId,
|
|
2567
|
+
objectId: object.id,
|
|
2568
|
+
expectedSourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2569
|
+
expectedObjectLifecycleGeneration: object.lifecycleGeneration,
|
|
2570
|
+
expectedVersionGeneration: object.versionGeneration,
|
|
2571
|
+
externalVersionId: entry.externalVersionId ?? contentSha256,
|
|
2572
|
+
contentSha256,
|
|
2573
|
+
ingestionKey: `${entry.externalObjectId}:${observationKey}`,
|
|
2574
|
+
sourceCursor: null,
|
|
2575
|
+
sourceMetadata: {
|
|
2576
|
+
title: entry.title,
|
|
2577
|
+
mimeType: entry.mimeType,
|
|
2578
|
+
driveId: entry.driveId,
|
|
2579
|
+
providerRevision: entry.externalVersionId,
|
|
2580
|
+
metadataHash,
|
|
2581
|
+
aclEligibility: "pending",
|
|
2582
|
+
sourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2583
|
+
objectLifecycleGeneration: object.lifecycleGeneration
|
|
2584
|
+
},
|
|
2585
|
+
sourceCreatedAt: entry.createdTime,
|
|
2586
|
+
sourceUpdatedAt: entry.modifiedTime,
|
|
2587
|
+
aclVersionId: acl.id,
|
|
2588
|
+
aclGeneration,
|
|
2589
|
+
documentId: currentVersion.documentId,
|
|
2590
|
+
fileId: currentVersion.fileId,
|
|
2591
|
+
locationMetadata: { sourceUri: entry.sourceUri },
|
|
2592
|
+
documentObservationMetadata: {
|
|
2593
|
+
title: entry.title,
|
|
2594
|
+
sourceUri: entry.sourceUri,
|
|
2595
|
+
sourceVersion: entry.externalVersionId ?? contentSha256,
|
|
2596
|
+
sourceUpdatedAt: entry.modifiedTime
|
|
2597
|
+
},
|
|
2598
|
+
syncObservation,
|
|
2599
|
+
operationId: `document-observation:${object.id}:${observationKey}`,
|
|
2600
|
+
reasonCode: "source_metadata_observed",
|
|
2601
|
+
actor
|
|
2602
|
+
});
|
|
2603
|
+
await updateKnowledgeSourceDocumentObservationMetadata(db, {
|
|
2604
|
+
accountId: input.accountId,
|
|
2605
|
+
workspaceId: input.workspaceId,
|
|
2606
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2607
|
+
sourceId: input.sourceId,
|
|
2608
|
+
expectedSourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2609
|
+
objectId: object.id,
|
|
2610
|
+
expectedObjectLifecycleGeneration: object.lifecycleGeneration,
|
|
2611
|
+
versionId: version2.id,
|
|
2612
|
+
documentId: currentVersion.documentId,
|
|
2613
|
+
title: entry.title,
|
|
2614
|
+
sourceUri: entry.sourceUri,
|
|
2615
|
+
sourceVersion: entry.externalVersionId ?? contentSha256,
|
|
2616
|
+
sourceUpdatedAt: entry.modifiedTime,
|
|
2617
|
+
syncObservation
|
|
2618
|
+
});
|
|
2619
|
+
const obligation2 = await ensureVersionIndexed({
|
|
2620
|
+
entry,
|
|
2621
|
+
object,
|
|
2622
|
+
version: version2,
|
|
2623
|
+
indexRequired: (currentObservation?.documentChunkCount ?? 0) === 0
|
|
2624
|
+
});
|
|
2625
|
+
const objectAcl2 = await recordEntryAcl({ entry, object, version: version2, obligation: obligation2 });
|
|
2626
|
+
summary.unchanged += 1;
|
|
2627
|
+
summary.indexed += 1;
|
|
2628
|
+
if (objectAcl2.aclEligibility === "pending") summary.aclPending += 1;
|
|
2629
|
+
outcomes.push({
|
|
2630
|
+
externalObjectId: entry.externalObjectId,
|
|
2631
|
+
outcome: "unchanged",
|
|
2632
|
+
contentSha256,
|
|
2633
|
+
sizeBytes: bytes.byteLength,
|
|
2634
|
+
providerRevision: entry.externalVersionId,
|
|
2635
|
+
metadataHash,
|
|
2636
|
+
aclEligibility: objectAcl2.aclEligibility,
|
|
2637
|
+
aclEvidence: objectAcl2.aclEvidence,
|
|
2638
|
+
indexObligationId: obligation2.id
|
|
2639
|
+
});
|
|
2640
|
+
continue;
|
|
2641
|
+
}
|
|
2642
|
+
const fileId = deterministicUuid(
|
|
2643
|
+
`knowledge-blob:${input.accountId}:${input.workspaceId}:${contentSha256}`
|
|
2644
|
+
);
|
|
2645
|
+
const objectKey = `workspaces/${input.workspaceId}/knowledge/blobs/${contentSha256}`;
|
|
2646
|
+
await objectStorage.putObject({
|
|
2647
|
+
key: objectKey,
|
|
2648
|
+
contentType: entry.transfer.contentType,
|
|
2649
|
+
body: bytes,
|
|
2650
|
+
sha256: contentSha256
|
|
2651
|
+
});
|
|
2652
|
+
const file = await ensureKnowledgeSourceBlobFile(db, {
|
|
2653
|
+
accountId: input.accountId,
|
|
2654
|
+
workspaceId: input.workspaceId,
|
|
2655
|
+
fileId,
|
|
2656
|
+
filename: entry.transfer.filename,
|
|
2657
|
+
safeFilename: safeFilename(entry.transfer.filename),
|
|
2658
|
+
contentType: entry.transfer.contentType,
|
|
2659
|
+
sizeBytes: bytes.byteLength,
|
|
2660
|
+
sha256: contentSha256,
|
|
2661
|
+
bucket: objectStorage.bucket,
|
|
2662
|
+
objectKey
|
|
2663
|
+
});
|
|
2664
|
+
const document = await addDocumentToBase(db, {
|
|
2665
|
+
accountId: input.accountId,
|
|
2666
|
+
workspaceId: input.workspaceId,
|
|
2667
|
+
baseId: base.id,
|
|
2668
|
+
fileId: file.id,
|
|
2669
|
+
title: entry.title,
|
|
2670
|
+
sourceKind: "document",
|
|
2671
|
+
sourceUri: entry.sourceUri,
|
|
2672
|
+
sourceExternalId: entry.externalObjectId,
|
|
2673
|
+
sourceTitle: entry.title,
|
|
2674
|
+
sourceCreatedAt: entry.createdTime ?? void 0,
|
|
2675
|
+
sourceUpdatedAt: entry.modifiedTime ?? void 0,
|
|
2676
|
+
sourceVersion: entry.externalVersionId ?? contentSha256,
|
|
2677
|
+
knowledgeSourceIdentity: deterministicUuid(
|
|
2678
|
+
`knowledge-source-version:${object.id}:${observationKey}`
|
|
2679
|
+
),
|
|
2680
|
+
authorityKind: action.destination.kind,
|
|
2681
|
+
createdBy: action.initiatingSubjectId,
|
|
2682
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2683
|
+
organizationAuthorityGranted: action.destination.kind === "organization",
|
|
2684
|
+
agentAccess: false,
|
|
2685
|
+
curationStatus: "none",
|
|
2686
|
+
access: { viewerSubjectId: action.initiatingSubjectId }
|
|
2687
|
+
});
|
|
2688
|
+
const version = await appendKnowledgeDocumentVersion(db, {
|
|
2689
|
+
accountId: input.accountId,
|
|
2690
|
+
workspaceId: input.workspaceId,
|
|
2691
|
+
objectId: object.id,
|
|
2692
|
+
expectedSourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2693
|
+
expectedObjectLifecycleGeneration: object.lifecycleGeneration,
|
|
2694
|
+
expectedVersionGeneration: object.versionGeneration,
|
|
2695
|
+
externalVersionId: entry.externalVersionId ?? contentSha256,
|
|
2696
|
+
contentSha256,
|
|
2697
|
+
ingestionKey: `${entry.externalObjectId}:${observationKey}`,
|
|
2698
|
+
sourceCursor: null,
|
|
2699
|
+
sourceMetadata: {
|
|
2700
|
+
title: entry.title,
|
|
2701
|
+
mimeType: entry.mimeType,
|
|
2702
|
+
driveId: entry.driveId,
|
|
2703
|
+
providerRevision: entry.externalVersionId,
|
|
2704
|
+
metadataHash,
|
|
2705
|
+
aclEligibility: "pending",
|
|
2706
|
+
sourceLifecycleGeneration: resolved.source.lifecycleGeneration,
|
|
2707
|
+
objectLifecycleGeneration: object.lifecycleGeneration
|
|
2708
|
+
},
|
|
2709
|
+
sourceCreatedAt: entry.createdTime,
|
|
2710
|
+
sourceUpdatedAt: entry.modifiedTime,
|
|
2711
|
+
aclVersionId: acl.id,
|
|
2712
|
+
aclGeneration,
|
|
2713
|
+
documentId: document.id,
|
|
2714
|
+
fileId: file.id,
|
|
2715
|
+
locationMetadata: { sourceUri: entry.sourceUri },
|
|
2716
|
+
syncObservation,
|
|
2717
|
+
operationId: `document-version:${object.id}:${observationKey}`,
|
|
2718
|
+
reasonCode: "source_content_observed",
|
|
2719
|
+
actor
|
|
2720
|
+
});
|
|
2721
|
+
const obligation = await ensureVersionIndexed({
|
|
2722
|
+
entry,
|
|
2723
|
+
object,
|
|
2724
|
+
version,
|
|
2725
|
+
indexRequired: true
|
|
2726
|
+
});
|
|
2727
|
+
const objectAcl = await recordEntryAcl({ entry, object, version, obligation });
|
|
2728
|
+
summary.indexed += 1;
|
|
2729
|
+
summary.imported += 1;
|
|
2730
|
+
if (objectAcl.aclEligibility === "pending") summary.aclPending += 1;
|
|
2731
|
+
summary.bytes += bytes.byteLength;
|
|
2732
|
+
outcomes.push({
|
|
2733
|
+
externalObjectId: entry.externalObjectId,
|
|
2734
|
+
outcome: "imported",
|
|
2735
|
+
contentSha256,
|
|
2736
|
+
sizeBytes: bytes.byteLength,
|
|
2737
|
+
providerRevision: entry.externalVersionId,
|
|
2738
|
+
metadataHash,
|
|
2739
|
+
aclEligibility: objectAcl.aclEligibility,
|
|
2740
|
+
aclEvidence: objectAcl.aclEvidence,
|
|
2741
|
+
indexObligationId: obligation.id
|
|
2742
|
+
});
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
if (error instanceof KnowledgeSourceSyncObservationFenceError) {
|
|
2745
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
2746
|
+
}
|
|
2747
|
+
const message = boundedError(error);
|
|
2748
|
+
const itemFailure = error instanceof SyncFailure ? error : new SyncFailure("item_processing_failed", false);
|
|
2749
|
+
summary.failed += 1;
|
|
2750
|
+
summary.retryable ||= itemFailure.retryable;
|
|
2751
|
+
if (summary.failures.length < action.limits.maxFailureDetails) {
|
|
2752
|
+
summary.failures.push({
|
|
2753
|
+
externalObjectId: entry.externalObjectId,
|
|
2754
|
+
code: itemFailure.code,
|
|
2755
|
+
retryable: itemFailure.retryable,
|
|
2756
|
+
message
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
outcomes.push({
|
|
2760
|
+
externalObjectId: entry.externalObjectId,
|
|
2761
|
+
outcome: "failed",
|
|
2762
|
+
reasonCode: itemFailure.code,
|
|
2763
|
+
detail: message,
|
|
2764
|
+
providerRevision: entry.externalVersionId,
|
|
2765
|
+
metadataHash: entryMetadataHash(entry),
|
|
2766
|
+
aclEligibility: "pending"
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
await recordKnowledgeSourceSyncItemOutcomes(db, {
|
|
2771
|
+
accountId: input.accountId,
|
|
2772
|
+
workspaceId: input.workspaceId,
|
|
2773
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2774
|
+
knowledgeSyncRunId: knowledgeRun.id,
|
|
2775
|
+
sourceId: input.sourceId,
|
|
2776
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2777
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2778
|
+
outcomes
|
|
2779
|
+
});
|
|
2780
|
+
if (inventory.status === "paused" && inventory.checkpoint) {
|
|
2781
|
+
if (inventory.stopReason === "provider_error" || inventory.stopReason === "incomplete_search") {
|
|
2782
|
+
throw new SyncFailure(
|
|
2783
|
+
inventory.stopReason === "provider_error" ? "provider_unavailable" : "provider_payload_invalid",
|
|
2784
|
+
inventory.stopReason === "provider_error"
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
if (inventory.hardLimitReached) {
|
|
2788
|
+
summary.limitReached = inventoryLimit(inventory.stopReason);
|
|
2789
|
+
throw new SyncFailure("resource_limit", false);
|
|
2790
|
+
}
|
|
2791
|
+
if (inventory.stopReason !== "elapsed_time_limit") {
|
|
2792
|
+
summary.limitReached = inventoryLimit(inventory.stopReason);
|
|
2793
|
+
throw new SyncFailure("resource_limit", false);
|
|
2794
|
+
}
|
|
2795
|
+
summary.checkpointed = true;
|
|
2796
|
+
summary.phase = "checkpoint";
|
|
2797
|
+
await checkpointKnowledgeSourceSync(db, {
|
|
2798
|
+
accountId: input.accountId,
|
|
2799
|
+
workspaceId: input.workspaceId,
|
|
2800
|
+
sourceId: input.sourceId,
|
|
2801
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2802
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2803
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2804
|
+
executionCheckpoint,
|
|
2805
|
+
observationFence: {
|
|
2806
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2807
|
+
scanGeneration: liveState.activeScanGeneration,
|
|
2808
|
+
executionCheckpointGeneration: lease.state.executionCheckpointGeneration,
|
|
2809
|
+
observations: observationFloors
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
return { action: "continue", summary };
|
|
2813
|
+
}
|
|
2814
|
+
const tombstonedExternalObjectIds = inventory.authoritativeFullScan ? await reconcileKnowledgeSourceSyncCompleteScan(db, {
|
|
2815
|
+
accountId: input.accountId,
|
|
2816
|
+
workspaceId: input.workspaceId,
|
|
2817
|
+
sourceId: input.sourceId,
|
|
2818
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2819
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2820
|
+
sourceSyncGeneration: knowledgeRun.inputSyncGeneration,
|
|
2821
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2822
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2823
|
+
scanGeneration: liveState.activeScanGeneration
|
|
2824
|
+
}) : [];
|
|
2825
|
+
if (tombstonedExternalObjectIds.length > 0) {
|
|
2826
|
+
summary.skipped += tombstonedExternalObjectIds.length;
|
|
2827
|
+
await recordKnowledgeSourceSyncItemOutcomes(db, {
|
|
2828
|
+
accountId: input.accountId,
|
|
2829
|
+
workspaceId: input.workspaceId,
|
|
2830
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2831
|
+
knowledgeSyncRunId: knowledgeRun.id,
|
|
2832
|
+
sourceId: input.sourceId,
|
|
2833
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2834
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2835
|
+
outcomes: tombstonedExternalObjectIds.map((externalObjectId) => ({
|
|
2836
|
+
externalObjectId,
|
|
2837
|
+
outcome: "tombstoned",
|
|
2838
|
+
reasonCode: "authoritative_scan_absent",
|
|
2839
|
+
aclEligibility: "denied"
|
|
2840
|
+
}))
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
await completeKnowledgeSyncRun(db, {
|
|
2844
|
+
accountId: input.accountId,
|
|
2845
|
+
workspaceId: input.workspaceId,
|
|
2846
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2847
|
+
runId: knowledgeRun.id,
|
|
2848
|
+
state: "succeeded",
|
|
2849
|
+
outputCursor: null,
|
|
2850
|
+
watermark: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2851
|
+
metadata: { ...summary, providerCursor: inventory.providerCursor },
|
|
2852
|
+
reasonCode: "scheduled_source_sync"
|
|
2853
|
+
});
|
|
2854
|
+
summary.phase = "completed";
|
|
2855
|
+
summary.elapsedMs = Date.now() - startedAt;
|
|
2856
|
+
const settled = await settleKnowledgeSourceSyncLease(db, {
|
|
2857
|
+
accountId: input.accountId,
|
|
2858
|
+
workspaceId: input.workspaceId,
|
|
2859
|
+
sourceId: input.sourceId,
|
|
2860
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2861
|
+
knowledgeSyncRunId: knowledgeRun.id,
|
|
2862
|
+
status: "succeeded",
|
|
2863
|
+
summary,
|
|
2864
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2865
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2866
|
+
sourceSyncGeneration: knowledgeRun.inputSyncGeneration,
|
|
2867
|
+
completedSourceSyncGeneration: knowledgeRun.inputSyncGeneration + 1,
|
|
2868
|
+
providerCursor: inventory.providerCursor,
|
|
2869
|
+
observationFence: {
|
|
2870
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2871
|
+
scanGeneration: liveState.activeScanGeneration,
|
|
2872
|
+
executionCheckpointGeneration: lease.state.executionCheckpointGeneration,
|
|
2873
|
+
observations: observationFloors
|
|
2874
|
+
}
|
|
2875
|
+
});
|
|
2876
|
+
await recordConnectorUsage("completed", summary.scanned, "item");
|
|
2877
|
+
await recordConnectorUsage("items", summary.imported, "item");
|
|
2878
|
+
await recordConnectorUsage("bytes", summary.bytes, "byte");
|
|
2879
|
+
observability.incrementCounter({
|
|
2880
|
+
name: "opengeni_knowledge_source_sync_runs_total",
|
|
2881
|
+
labels: { provider: providerLabel, outcome: "succeeded" }
|
|
2882
|
+
});
|
|
2883
|
+
observability.incrementCounter({
|
|
2884
|
+
name: "opengeni_knowledge_source_sync_items_total",
|
|
2885
|
+
labels: { provider: providerLabel, outcome: "imported" },
|
|
2886
|
+
amount: summary.imported
|
|
2887
|
+
});
|
|
2888
|
+
for (const outcome of ["unchanged", "skipped", "failed"]) {
|
|
2889
|
+
observability.incrementCounter({
|
|
2890
|
+
name: "opengeni_knowledge_source_sync_items_total",
|
|
2891
|
+
labels: { provider: providerLabel, outcome },
|
|
2892
|
+
amount: summary[outcome]
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
observability.incrementCounter({
|
|
2896
|
+
name: "opengeni_knowledge_source_sync_bytes_total",
|
|
2897
|
+
labels: { provider: providerLabel },
|
|
2898
|
+
amount: summary.bytes
|
|
2899
|
+
});
|
|
2900
|
+
recordSyncHealthTelemetry(observability, providerLabel, summary, "succeeded");
|
|
2901
|
+
return {
|
|
2902
|
+
action: "complete",
|
|
2903
|
+
summary,
|
|
2904
|
+
bufferedWake: settled.bufferedWake,
|
|
2905
|
+
bufferedScheduledTaskRunId: settled.bufferedScheduledTaskRunId
|
|
2906
|
+
};
|
|
2907
|
+
} catch (error) {
|
|
2908
|
+
const failure = error instanceof SyncFailure ? error : new SyncFailure("internal_failure", false);
|
|
2909
|
+
summary.phase = "failed";
|
|
2910
|
+
summary.retryable ||= failure.retryable;
|
|
2911
|
+
summary.elapsedMs = Date.now() - startedAt;
|
|
2912
|
+
summary.reconnectRequired = failure.reconnectRequired;
|
|
2913
|
+
summary.failed = Math.max(summary.failed, 1);
|
|
2914
|
+
if (summary.failures.length === 0) {
|
|
2915
|
+
summary.failures.push({
|
|
2916
|
+
externalObjectId: input.sourceId,
|
|
2917
|
+
code: failure.code,
|
|
2918
|
+
retryable: failure.retryable,
|
|
2919
|
+
message: boundedError(error)
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
if (knowledgeRunId) {
|
|
2923
|
+
await completeKnowledgeSyncRun(db, {
|
|
2924
|
+
accountId: input.accountId,
|
|
2925
|
+
workspaceId: input.workspaceId,
|
|
2926
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
2927
|
+
runId: knowledgeRunId,
|
|
2928
|
+
state: "failed",
|
|
2929
|
+
metadata: summary,
|
|
2930
|
+
errorCode: failure.code,
|
|
2931
|
+
reasonCode: "scheduled_source_sync_failed"
|
|
2932
|
+
}).catch(() => void 0);
|
|
2933
|
+
}
|
|
2934
|
+
const settlementInput = {
|
|
2935
|
+
accountId: input.accountId,
|
|
2936
|
+
workspaceId: input.workspaceId,
|
|
2937
|
+
sourceId: input.sourceId,
|
|
2938
|
+
scheduledTaskRunId: input.scheduledTaskRunId,
|
|
2939
|
+
knowledgeSyncRunId: knowledgeRunId,
|
|
2940
|
+
status: "failed",
|
|
2941
|
+
summary,
|
|
2942
|
+
error: failure.code,
|
|
2943
|
+
sourceConfigGeneration: action.sourceConfigGeneration,
|
|
2944
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
2945
|
+
sourceSyncGeneration: activeSourceSyncGeneration ?? lease.state.sourceSyncGeneration,
|
|
2946
|
+
// Failed or incomplete provider walks are not authoritative scans.
|
|
2947
|
+
// A later scheduled run must restart from the root under a fresh
|
|
2948
|
+
// observation generation before it can infer object absence.
|
|
2949
|
+
executionCheckpoint: null
|
|
2950
|
+
};
|
|
2951
|
+
const settled = failure.reconnectRequired ? await deauthorizeAndSettleKnowledgeSourceSyncFailure(db, {
|
|
2952
|
+
deauthorization: {
|
|
2953
|
+
accountId: input.accountId,
|
|
2954
|
+
workspaceId: input.workspaceId,
|
|
2955
|
+
sourceId: input.sourceId,
|
|
2956
|
+
audience: action.destination,
|
|
2957
|
+
operationId: `knowledge-sync-deauthorize:${input.sourceId}:${action.connection.connectionVersion}:${failure.code}`,
|
|
2958
|
+
reasonCode: "connection_reconnect_required",
|
|
2959
|
+
actor: {
|
|
2960
|
+
kind: "service",
|
|
2961
|
+
subjectId: "knowledge-source-sync",
|
|
2962
|
+
initiatingHumanSubjectId: action.initiatingSubjectId
|
|
2963
|
+
}
|
|
2964
|
+
},
|
|
2965
|
+
settlement: settlementInput
|
|
2966
|
+
}) : await settleKnowledgeSourceSyncLease(db, settlementInput);
|
|
2967
|
+
await recordConnectorUsage("completed", summary.scanned, "item");
|
|
2968
|
+
await recordConnectorUsage("items", summary.imported, "item");
|
|
2969
|
+
await recordConnectorUsage("bytes", summary.bytes, "byte");
|
|
2970
|
+
observability.incrementCounter({
|
|
2971
|
+
name: "opengeni_knowledge_source_sync_runs_total",
|
|
2972
|
+
labels: { provider: providerLabel, outcome: "failed" }
|
|
2973
|
+
});
|
|
2974
|
+
for (const outcome of ["imported", "unchanged", "skipped", "failed"]) {
|
|
2975
|
+
observability.incrementCounter({
|
|
2976
|
+
name: "opengeni_knowledge_source_sync_items_total",
|
|
2977
|
+
labels: { provider: providerLabel, outcome },
|
|
2978
|
+
amount: summary[outcome]
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
observability.incrementCounter({
|
|
2982
|
+
name: "opengeni_knowledge_source_sync_bytes_total",
|
|
2983
|
+
labels: { provider: providerLabel },
|
|
2984
|
+
amount: summary.bytes
|
|
2985
|
+
});
|
|
2986
|
+
recordSyncHealthTelemetry(observability, providerLabel, summary, "failed", failure);
|
|
2987
|
+
return {
|
|
2988
|
+
action: "failed",
|
|
2989
|
+
summary,
|
|
2990
|
+
errorCode: failure.code,
|
|
2991
|
+
bufferedWake: settled.bufferedWake,
|
|
2992
|
+
bufferedScheduledTaskRunId: settled.bufferedScheduledTaskRunId
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
async function failWithoutProvider(code) {
|
|
2996
|
+
const terminalSummary = {
|
|
2997
|
+
phase: "failed",
|
|
2998
|
+
scanned: 0,
|
|
2999
|
+
imported: 0,
|
|
3000
|
+
unchanged: 0,
|
|
3001
|
+
skipped: 0,
|
|
3002
|
+
failed: 1,
|
|
3003
|
+
bytes: 0,
|
|
3004
|
+
providerRequests: 0,
|
|
3005
|
+
elapsedMs: 0,
|
|
3006
|
+
indexed: 0,
|
|
3007
|
+
aclPending: 0,
|
|
3008
|
+
retryable: false,
|
|
3009
|
+
limitReached: null,
|
|
3010
|
+
checkpointed: false,
|
|
3011
|
+
reconnectRequired: false,
|
|
3012
|
+
failures: [
|
|
3013
|
+
{
|
|
3014
|
+
externalObjectId: input.sourceId,
|
|
3015
|
+
code: "authority_changed",
|
|
3016
|
+
retryable: false,
|
|
3017
|
+
message: code
|
|
3018
|
+
}
|
|
3019
|
+
]
|
|
3020
|
+
};
|
|
3021
|
+
await updateScheduledTaskRun(db, input.workspaceId, input.scheduledTaskRunId, {
|
|
3022
|
+
knowledgeSummary: terminalSummary
|
|
3023
|
+
}).catch(() => void 0);
|
|
3024
|
+
await completeKnowledgeSourceSyncWakeWithoutLease(db, {
|
|
3025
|
+
accountId: input.accountId,
|
|
3026
|
+
workspaceId: input.workspaceId,
|
|
3027
|
+
sourceId: input.sourceId,
|
|
3028
|
+
scheduledTaskRunId: input.scheduledTaskRunId
|
|
3029
|
+
}).catch(() => void 0);
|
|
3030
|
+
await recordConnectorUsage("completed", 0, "item").catch(() => void 0);
|
|
3031
|
+
observability.incrementCounter({
|
|
3032
|
+
name: "opengeni_knowledge_source_sync_runs_total",
|
|
3033
|
+
labels: { provider: "unknown", outcome: "failed_preflight" }
|
|
3034
|
+
});
|
|
3035
|
+
return { action: "failed", bufferedWake: false, summary: terminalSummary, errorCode: code };
|
|
3036
|
+
}
|
|
3037
|
+
async function recordConnectorUsage(kind, quantity, unit) {
|
|
3038
|
+
if (quantity === 0 && kind !== "completed") return;
|
|
3039
|
+
await recordUsageEvent(db, {
|
|
3040
|
+
accountId: input.accountId,
|
|
3041
|
+
workspaceId: input.workspaceId,
|
|
3042
|
+
eventType: `knowledge_source_sync.${kind}`,
|
|
3043
|
+
quantity: kind === "completed" ? 1 : quantity,
|
|
3044
|
+
unit,
|
|
3045
|
+
sourceResourceType: "scheduled_task_run",
|
|
3046
|
+
sourceResourceId: input.scheduledTaskRunId,
|
|
3047
|
+
initiator: { kind: "service", subjectId: "knowledge-source-sync" },
|
|
3048
|
+
initiatorContext: {
|
|
3049
|
+
scheduledTaskId: input.taskId,
|
|
3050
|
+
scheduledTaskRunId: input.scheduledTaskRunId
|
|
3051
|
+
},
|
|
3052
|
+
origin: "scheduled_task",
|
|
3053
|
+
idempotencyKey: `usage:knowledge_source_sync.${kind}:${input.scheduledTaskRunId}`
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
async function deauthorizeAndSettleKnowledgeSourceSyncFailure(db, input) {
|
|
3060
|
+
try {
|
|
3061
|
+
await deauthorizeKnowledgeSourceRetrieval(db, input.deauthorization);
|
|
3062
|
+
} catch (error) {
|
|
3063
|
+
await releaseKnowledgeSourceSyncLeaseForRetry(db, {
|
|
3064
|
+
accountId: input.settlement.accountId,
|
|
3065
|
+
workspaceId: input.settlement.workspaceId,
|
|
3066
|
+
sourceId: input.settlement.sourceId,
|
|
3067
|
+
scheduledTaskRunId: input.settlement.scheduledTaskRunId
|
|
3068
|
+
});
|
|
3069
|
+
throw error;
|
|
3070
|
+
}
|
|
3071
|
+
return await settleKnowledgeSourceSyncLease(db, input.settlement);
|
|
3072
|
+
}
|
|
3073
|
+
async function resolveKnowledgeSyncProvider(input) {
|
|
3074
|
+
const { action } = input;
|
|
3075
|
+
const connection = await getConnectionMetadata2(
|
|
3076
|
+
input.db,
|
|
3077
|
+
input.workspaceId,
|
|
3078
|
+
action.connection.connectionId,
|
|
3079
|
+
action.initiatingSubjectId
|
|
3080
|
+
);
|
|
3081
|
+
if (!connection || connection.accountId !== input.accountId || connection.subjectId !== action.connection.ownerSubjectId || connection.providerDomain.toLowerCase() !== action.connection.providerDomain.toLowerCase() || connection.kind !== action.connection.kind || connection.version < action.connection.connectionVersion || connection.status !== "active") {
|
|
3082
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3083
|
+
}
|
|
3084
|
+
const googleInitial = connection.providerDomain === GOOGLE_DRIVE_PROVIDER_DOMAIN ? GoogleDriveConnectionMetadata.safeParse(connection.metadata) : null;
|
|
3085
|
+
const atlassianInitial = connection.providerDomain === ATLASSIAN_PROVIDER_DOMAIN ? AtlassianConnectionMetadata.safeParse(connection.metadata) : null;
|
|
3086
|
+
const initialAtlassianMetadata = atlassianInitial?.success ? atlassianInitial.data : null;
|
|
3087
|
+
if (!googleInitial?.success && !initialAtlassianMetadata) {
|
|
3088
|
+
throw new SyncFailure("provider_rejected", false);
|
|
3089
|
+
}
|
|
3090
|
+
if (googleInitial?.success && !googleDriveScopesAllowCapability(connection.grantedScopes, "recursive_source_sync")) {
|
|
3091
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3092
|
+
}
|
|
3093
|
+
if (atlassianInitial?.success && !atlassianScopesAllowRead(connection.grantedScopes)) {
|
|
3094
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3095
|
+
}
|
|
3096
|
+
const initialLifecycle = googleInitial?.success ? googleInitial.data.lifecycle : initialAtlassianMetadata.lifecycle;
|
|
3097
|
+
if (initialLifecycle?.state && initialLifecycle.state !== "active") {
|
|
3098
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3099
|
+
}
|
|
3100
|
+
const initialSource = googleInitial?.success ? selectedDriveSource(googleInitial.data, input.externalSourceId) : selectedAtlassianSource(initialAtlassianMetadata, input.externalSourceId);
|
|
3101
|
+
if (!initialSource || !initialSource.syncEnabled || initialSource.configGeneration !== action.sourceConfigGeneration || !initialSource.destination || scopedKnowledgeScopeKey(
|
|
3102
|
+
googleInitial?.success ? googleDriveKnowledgeScope(initialSource.destination) : atlassianKnowledgeScope(initialSource.destination)
|
|
3103
|
+
) !== scopedKnowledgeScopeKey(action.destination)) {
|
|
3104
|
+
throw new SyncFailure("authority_changed", false);
|
|
3105
|
+
}
|
|
3106
|
+
const token = await buildConnectionTokenResolver(
|
|
3107
|
+
input.db,
|
|
3108
|
+
input.settings
|
|
3109
|
+
)({
|
|
3110
|
+
workspaceId: input.workspaceId,
|
|
3111
|
+
subjectId: action.initiatingSubjectId,
|
|
3112
|
+
serverId: "knowledge-source-sync",
|
|
3113
|
+
connectionRef: {
|
|
3114
|
+
connectionId: connection.id,
|
|
3115
|
+
providerDomain: connection.providerDomain,
|
|
3116
|
+
kind: connection.kind,
|
|
3117
|
+
subjectScope: "subject"
|
|
3118
|
+
},
|
|
3119
|
+
destinationUrl: connection.providerDomain === ATLASSIAN_PROVIDER_DOMAIN ? ATLASSIAN_API_BASE : "https://www.googleapis.com"
|
|
3120
|
+
});
|
|
3121
|
+
if (token.status !== "ok") {
|
|
3122
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3123
|
+
}
|
|
3124
|
+
const resolvedConnection = await getConnectionMetadata2(
|
|
3125
|
+
input.db,
|
|
3126
|
+
input.workspaceId,
|
|
3127
|
+
action.connection.connectionId,
|
|
3128
|
+
action.initiatingSubjectId
|
|
3129
|
+
);
|
|
3130
|
+
if (!resolvedConnection || resolvedConnection.accountId !== input.accountId || resolvedConnection.subjectId !== action.connection.ownerSubjectId || resolvedConnection.providerDomain.toLowerCase() !== action.connection.providerDomain.toLowerCase() || resolvedConnection.kind !== action.connection.kind || resolvedConnection.version !== token.connectionVersion || resolvedConnection.status !== "active") {
|
|
3131
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3132
|
+
}
|
|
3133
|
+
const authorization = token.headers.Authorization ?? token.headers.authorization;
|
|
3134
|
+
if (resolvedConnection.providerDomain === GOOGLE_DRIVE_PROVIDER_DOMAIN) {
|
|
3135
|
+
const metadata = GoogleDriveConnectionMetadata.parse(resolvedConnection.metadata);
|
|
3136
|
+
if (!googleDriveSyncProviderAccessAllowed({
|
|
3137
|
+
connectionVersion: resolvedConnection.version,
|
|
3138
|
+
resolvedConnectionVersion: token.connectionVersion,
|
|
3139
|
+
connectionStatus: resolvedConnection.status,
|
|
3140
|
+
lifecycleState: metadata.lifecycle?.state,
|
|
3141
|
+
grantedScopes: resolvedConnection.grantedScopes
|
|
3142
|
+
})) {
|
|
3143
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3144
|
+
}
|
|
3145
|
+
const source = selectedDriveSource(metadata, input.externalSourceId);
|
|
3146
|
+
if (!validSelectedSource(source, action, googleDriveKnowledgeScope)) {
|
|
3147
|
+
throw new SyncFailure("authority_changed", false);
|
|
3148
|
+
}
|
|
3149
|
+
return {
|
|
3150
|
+
kind: "google_drive",
|
|
3151
|
+
selectedDestination: source.destination,
|
|
3152
|
+
aclAuthority: {
|
|
3153
|
+
connectionId: resolvedConnection.id,
|
|
3154
|
+
connectionVersion: resolvedConnection.version,
|
|
3155
|
+
sourceGooglePermissionId: metadata.googlePermissionId
|
|
3156
|
+
},
|
|
3157
|
+
driver: googleDriveSyncDriver({
|
|
3158
|
+
actionProviderCoordinationKey: action.providerCoordinationKey,
|
|
3159
|
+
metadata,
|
|
3160
|
+
selectedSource: source,
|
|
3161
|
+
accountId: input.accountId,
|
|
3162
|
+
workspaceId: input.workspaceId,
|
|
3163
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
3164
|
+
authorization,
|
|
3165
|
+
retry: googleDriveProviderRetryOptions(input.settings),
|
|
3166
|
+
observability: input.observability,
|
|
3167
|
+
limits: action.limits,
|
|
3168
|
+
connectionId: resolvedConnection.id,
|
|
3169
|
+
fullReconciliationIntervalMs: GOOGLE_DRIVE_FULL_RECONCILIATION_INTERVAL_MS,
|
|
3170
|
+
observedExternalObjectIds: async (externalObjectIds) => await listObservedKnowledgeSourceSyncExternalObjectIds(input.db, {
|
|
3171
|
+
accountId: input.accountId,
|
|
3172
|
+
workspaceId: input.workspaceId,
|
|
3173
|
+
sourceId: input.sourceId,
|
|
3174
|
+
externalObjectIds
|
|
3175
|
+
})
|
|
3176
|
+
})
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
if (resolvedConnection.providerDomain === ATLASSIAN_PROVIDER_DOMAIN) {
|
|
3180
|
+
const metadata = AtlassianConnectionMetadata.parse(resolvedConnection.metadata);
|
|
3181
|
+
if (metadata.lifecycle?.state !== "active" || !atlassianScopesAllowRead(resolvedConnection.grantedScopes)) {
|
|
3182
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3183
|
+
}
|
|
3184
|
+
const source = selectedAtlassianSource(metadata, input.externalSourceId);
|
|
3185
|
+
if (!validSelectedSource(source, action, atlassianKnowledgeScope)) {
|
|
3186
|
+
throw new SyncFailure("authority_changed", false);
|
|
3187
|
+
}
|
|
3188
|
+
return {
|
|
3189
|
+
kind: "atlassian",
|
|
3190
|
+
selectedDestination: source.destination,
|
|
3191
|
+
aclAuthority: null,
|
|
3192
|
+
driver: atlassianSyncDriver({
|
|
3193
|
+
actionProviderCoordinationKey: action.providerCoordinationKey,
|
|
3194
|
+
metadata,
|
|
3195
|
+
selectedSource: source,
|
|
3196
|
+
authorization,
|
|
3197
|
+
limits: action.limits
|
|
3198
|
+
})
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
throw new SyncFailure("provider_rejected", false);
|
|
3202
|
+
}
|
|
3203
|
+
function validSelectedSource(source, action, scope) {
|
|
3204
|
+
return Boolean(
|
|
3205
|
+
source && source.syncEnabled && source.configGeneration === action.sourceConfigGeneration && source.destination && scopedKnowledgeScopeKey(scope(source.destination)) === scopedKnowledgeScopeKey(action.destination)
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
function selectedAtlassianSource(metadata, externalSourceId) {
|
|
3209
|
+
return metadata.selectedSources.find((source) => source.id === externalSourceId) ?? null;
|
|
3210
|
+
}
|
|
3211
|
+
function atlassianSyncDriver(input) {
|
|
3212
|
+
return {
|
|
3213
|
+
providerKey: "atlassian",
|
|
3214
|
+
providerDomain: ATLASSIAN_PROVIDER_DOMAIN,
|
|
3215
|
+
providerCoordinationKey: input.actionProviderCoordinationKey,
|
|
3216
|
+
inventory: async (checkpoint) => {
|
|
3217
|
+
const inventory = await inventoryAtlassianSource({
|
|
3218
|
+
cloudId: input.selectedSource.cloudId,
|
|
3219
|
+
source: input.selectedSource,
|
|
3220
|
+
limits: {
|
|
3221
|
+
maxItems: input.limits.maxItems,
|
|
3222
|
+
maxApiRequests: input.limits.maxProviderRequests,
|
|
3223
|
+
maxElapsedMs: input.limits.maxElapsedSeconds * 1e3,
|
|
3224
|
+
pageSize: Math.min(input.limits.maxItems, 100)
|
|
3225
|
+
},
|
|
3226
|
+
checkpoint,
|
|
3227
|
+
listPage: async (cursor, pageSize) => await listAtlassianItems(input.selectedSource, cursor, pageSize, input.authorization)
|
|
3228
|
+
});
|
|
3229
|
+
return {
|
|
3230
|
+
...inventory,
|
|
3231
|
+
providerCursor: null,
|
|
3232
|
+
authoritativeFullScan: inventory.status === "complete",
|
|
3233
|
+
cursorInvalidated: false,
|
|
3234
|
+
hardLimitReached: inventory.status === "paused" && inventory.stopReason !== "elapsed_time_limit" && inventory.stopReason !== "provider_error"
|
|
3235
|
+
};
|
|
3236
|
+
},
|
|
3237
|
+
fetchContent: async (entry, maxBytes) => await fetchAtlassianContent(input.selectedSource, entry, input.authorization, maxBytes),
|
|
3238
|
+
citationLocator: (entry) => ({
|
|
3239
|
+
version: 1,
|
|
3240
|
+
providerKey: "atlassian",
|
|
3241
|
+
providerCoordinationKey: input.actionProviderCoordinationKey,
|
|
3242
|
+
cloudId: input.selectedSource.cloudId,
|
|
3243
|
+
sourceKind: input.selectedSource.kind,
|
|
3244
|
+
externalObjectId: entry.externalObjectId,
|
|
3245
|
+
sourceUri: entry.sourceUri
|
|
3246
|
+
})
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
function googleDriveSyncDriver(input) {
|
|
3250
|
+
const maxElapsedMs = input.limits.maxElapsedSeconds * 1e3;
|
|
3251
|
+
const provider = input.provider ?? {
|
|
3252
|
+
getStartPageToken: async (driveId) => await getDriveStartPageToken(driveId, input.authorization, input.retry, input.observability),
|
|
3253
|
+
listChanges: async (pageToken, driveId, pageSize) => await listDriveChanges(
|
|
3254
|
+
pageToken,
|
|
3255
|
+
driveId,
|
|
3256
|
+
pageSize,
|
|
3257
|
+
input.authorization,
|
|
3258
|
+
input.retry,
|
|
3259
|
+
input.observability
|
|
3260
|
+
),
|
|
3261
|
+
getFile: async (fileId) => await getDriveFile(fileId, input.authorization, input.retry, input.observability),
|
|
3262
|
+
listChildren: async (request) => await listDriveChildren(request, input.authorization, input.retry, input.observability),
|
|
3263
|
+
listPermissions: async (request) => await listDrivePermissions(request, input.authorization, input.retry, input.observability)
|
|
3264
|
+
};
|
|
3265
|
+
const clock = provider.now ?? Date.now;
|
|
3266
|
+
const budgetLimits = {
|
|
3267
|
+
maxItems: input.limits.maxItems,
|
|
3268
|
+
maxProviderRequests: input.limits.maxProviderRequests,
|
|
3269
|
+
maxElapsedMs
|
|
3270
|
+
};
|
|
3271
|
+
return {
|
|
3272
|
+
providerKey: "google_drive",
|
|
3273
|
+
providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
3274
|
+
providerCoordinationKey: input.actionProviderCoordinationKey,
|
|
3275
|
+
inventory: async (executionCheckpoint, providerCursor) => {
|
|
3276
|
+
const expectedCursor = {
|
|
3277
|
+
connectionId: input.connectionId,
|
|
3278
|
+
googlePermissionId: input.metadata.googlePermissionId,
|
|
3279
|
+
sourceId: input.selectedSource.id,
|
|
3280
|
+
driveId: input.selectedSource.driveId
|
|
3281
|
+
};
|
|
3282
|
+
const legacyFullCheckpoint = isLegacyGoogleDriveFullReconciliationCheckpoint(
|
|
3283
|
+
executionCheckpoint,
|
|
3284
|
+
expectedCursor
|
|
3285
|
+
);
|
|
3286
|
+
const cursor = parseGoogleDriveChangesCursor(providerCursor, expectedCursor);
|
|
3287
|
+
const checkpoint = parseGoogleDriveExecutionCheckpoint(
|
|
3288
|
+
executionCheckpoint,
|
|
3289
|
+
expectedCursor,
|
|
3290
|
+
budgetLimits
|
|
3291
|
+
);
|
|
3292
|
+
const now = new Date(clock());
|
|
3293
|
+
const runFullReconciliation = async (full) => {
|
|
3294
|
+
const inventoryItems = full.inventoryCheckpoint?.totals.itemCount ?? 0;
|
|
3295
|
+
const inventoryProviderRequests = full.inventoryCheckpoint?.totals.apiRequestCount ?? 0;
|
|
3296
|
+
const budgetBeforeInvocation = {
|
|
3297
|
+
examinedItems: full.budgetBeforeInventory.examinedItems + inventoryItems,
|
|
3298
|
+
providerRequests: full.budgetBeforeInventory.providerRequests + inventoryProviderRequests,
|
|
3299
|
+
elapsedMs: full.budgetBeforeInventory.elapsedMs + full.inventoryElapsedMs
|
|
3300
|
+
};
|
|
3301
|
+
const hardStopReason = googleDriveHardLimitReason(budgetBeforeInvocation, budgetLimits);
|
|
3302
|
+
const fullCheckpoint = buildGoogleDriveFullReconciliationCheckpoint({
|
|
3303
|
+
...expectedCursor,
|
|
3304
|
+
...full
|
|
3305
|
+
});
|
|
3306
|
+
if (hardStopReason) {
|
|
3307
|
+
return {
|
|
3308
|
+
status: "paused",
|
|
3309
|
+
stopReason: hardStopReason,
|
|
3310
|
+
entries: [],
|
|
3311
|
+
checkpoint: fullCheckpoint,
|
|
3312
|
+
providerCursor,
|
|
3313
|
+
authoritativeFullScan: false,
|
|
3314
|
+
cursorInvalidated: full.cursorInvalidated,
|
|
3315
|
+
providerRequests: budgetBeforeInvocation.providerRequests,
|
|
3316
|
+
elapsedMs: budgetBeforeInvocation.elapsedMs,
|
|
3317
|
+
hardLimitReached: true
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
const phaseMaxItems = input.limits.maxItems - full.budgetBeforeInventory.examinedItems;
|
|
3321
|
+
const phaseMaxProviderRequests = input.limits.maxProviderRequests - full.budgetBeforeInventory.providerRequests;
|
|
3322
|
+
const remainingElapsedMs = maxElapsedMs - budgetBeforeInvocation.elapsedMs;
|
|
3323
|
+
const inventory = await inventoryGoogleDriveSource({
|
|
3324
|
+
googlePermissionId: input.metadata.googlePermissionId,
|
|
3325
|
+
googleEmail: input.metadata.googleEmail,
|
|
3326
|
+
source: input.selectedSource,
|
|
3327
|
+
accountId: input.accountId,
|
|
3328
|
+
workspaceId: input.workspaceId,
|
|
3329
|
+
connectionSubjectId: input.initiatingSubjectId,
|
|
3330
|
+
limits: {
|
|
3331
|
+
maxItems: phaseMaxItems,
|
|
3332
|
+
maxKnownBytes: input.limits.maxBytes,
|
|
3333
|
+
maxApiRequests: phaseMaxProviderRequests,
|
|
3334
|
+
maxElapsedMs: Math.min(GOOGLE_DRIVE_SYNC_INVOCATION_SLICE_MS, remainingElapsedMs),
|
|
3335
|
+
maxFileBytes: input.limits.maxFileBytes,
|
|
3336
|
+
maxFolders: Math.min(phaseMaxItems * 2, 2e3),
|
|
3337
|
+
pageSize: Math.min(phaseMaxItems, 100)
|
|
3338
|
+
},
|
|
3339
|
+
checkpoint: full.inventoryCheckpoint,
|
|
3340
|
+
listChildren: provider.listChildren,
|
|
3341
|
+
now: clock
|
|
3342
|
+
});
|
|
3343
|
+
const inventoryElapsedMs = full.inventoryElapsedMs + inventory.run.elapsedMs;
|
|
3344
|
+
const budget = {
|
|
3345
|
+
examinedItems: full.budgetBeforeInventory.examinedItems + inventory.totals.itemCount,
|
|
3346
|
+
providerRequests: full.budgetBeforeInventory.providerRequests + inventory.totals.apiRequestCount,
|
|
3347
|
+
elapsedMs: full.budgetBeforeInventory.elapsedMs + inventoryElapsedMs
|
|
3348
|
+
};
|
|
3349
|
+
const reconciled = reconcileGoogleDriveRevisionFloors(
|
|
3350
|
+
inventory.entries,
|
|
3351
|
+
full.revisionFloors,
|
|
3352
|
+
input.limits.maxItems
|
|
3353
|
+
);
|
|
3354
|
+
if (reconciled.conflict) {
|
|
3355
|
+
return {
|
|
3356
|
+
status: "paused",
|
|
3357
|
+
stopReason: "incomplete_search",
|
|
3358
|
+
entries: [],
|
|
3359
|
+
checkpoint: fullCheckpoint,
|
|
3360
|
+
providerCursor,
|
|
3361
|
+
authoritativeFullScan: false,
|
|
3362
|
+
cursorInvalidated: full.cursorInvalidated,
|
|
3363
|
+
providerRequests: budget.providerRequests,
|
|
3364
|
+
elapsedMs: budget.elapsedMs,
|
|
3365
|
+
hardLimitReached: false
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
const nextCursor = inventory.status === "complete" ? buildGoogleDriveChangesCursor({
|
|
3369
|
+
...expectedCursor,
|
|
3370
|
+
boundaryId: full.boundaryId,
|
|
3371
|
+
pageToken: full.startPageToken,
|
|
3372
|
+
previousGeneration: cursor?.cursorGeneration ?? 0,
|
|
3373
|
+
reconciledAt: now,
|
|
3374
|
+
fullReconciliationIntervalMs: input.fullReconciliationIntervalMs
|
|
3375
|
+
}) : providerCursor;
|
|
3376
|
+
return {
|
|
3377
|
+
status: inventory.status,
|
|
3378
|
+
stopReason: inventory.stopReason,
|
|
3379
|
+
entries: reconciled.entries,
|
|
3380
|
+
checkpoint: inventory.status === "paused" && inventory.checkpoint ? buildGoogleDriveFullReconciliationCheckpoint({
|
|
3381
|
+
...expectedCursor,
|
|
3382
|
+
boundaryId: full.boundaryId,
|
|
3383
|
+
startPageToken: full.startPageToken,
|
|
3384
|
+
cursorInvalidated: full.cursorInvalidated,
|
|
3385
|
+
budgetBeforeInventory: full.budgetBeforeInventory,
|
|
3386
|
+
inventoryElapsedMs,
|
|
3387
|
+
inventoryCheckpoint: inventory.checkpoint,
|
|
3388
|
+
revisionFloors: reconciled.revisionFloors
|
|
3389
|
+
}) : null,
|
|
3390
|
+
providerCursor: nextCursor,
|
|
3391
|
+
authoritativeFullScan: inventory.status === "complete",
|
|
3392
|
+
cursorInvalidated: full.cursorInvalidated,
|
|
3393
|
+
providerRequests: budget.providerRequests,
|
|
3394
|
+
elapsedMs: budget.elapsedMs,
|
|
3395
|
+
hardLimitReached: inventory.status === "paused" && googleDriveInventoryStopIsHard(inventory.stopReason, budget, budgetLimits)
|
|
3396
|
+
};
|
|
3397
|
+
};
|
|
3398
|
+
const acquireFullReconciliation = async (details) => {
|
|
3399
|
+
const acquisitionStartedAt = clock();
|
|
3400
|
+
const initialElapsedMs = details.budget.elapsedMs;
|
|
3401
|
+
const budget = { ...details.budget };
|
|
3402
|
+
const refreshElapsed = () => {
|
|
3403
|
+
budget.elapsedMs = initialElapsedMs + Math.max(0, clock() - acquisitionStartedAt);
|
|
3404
|
+
};
|
|
3405
|
+
const requireProviderCapacity = () => {
|
|
3406
|
+
refreshElapsed();
|
|
3407
|
+
if (googleDriveHardLimitReason(budget, budgetLimits)) {
|
|
3408
|
+
throw new SyncFailure("resource_limit", false);
|
|
3409
|
+
}
|
|
3410
|
+
budget.providerRequests += 1;
|
|
3411
|
+
};
|
|
3412
|
+
requireProviderCapacity();
|
|
3413
|
+
const startPageToken = await provider.getStartPageToken(input.selectedSource.driveId);
|
|
3414
|
+
refreshElapsed();
|
|
3415
|
+
let boundaryId = details.boundaryId;
|
|
3416
|
+
if (!boundaryId) {
|
|
3417
|
+
if (input.selectedSource.id === "root" && input.selectedSource.driveId === null) {
|
|
3418
|
+
requireProviderCapacity();
|
|
3419
|
+
try {
|
|
3420
|
+
boundaryId = await resolveGoogleDriveChangesBoundaryId({
|
|
3421
|
+
source: input.selectedSource,
|
|
3422
|
+
getFile: provider.getFile
|
|
3423
|
+
});
|
|
3424
|
+
} catch (error) {
|
|
3425
|
+
if (error instanceof GoogleDriveChangesProtocolError) {
|
|
3426
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
3427
|
+
}
|
|
3428
|
+
throw error;
|
|
3429
|
+
}
|
|
3430
|
+
refreshElapsed();
|
|
3431
|
+
} else {
|
|
3432
|
+
boundaryId = input.selectedSource.id;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
return {
|
|
3436
|
+
boundaryId,
|
|
3437
|
+
startPageToken,
|
|
3438
|
+
cursorInvalidated: details.cursorInvalidated,
|
|
3439
|
+
budgetBeforeInventory: budget,
|
|
3440
|
+
inventoryElapsedMs: 0,
|
|
3441
|
+
inventoryCheckpoint: null,
|
|
3442
|
+
revisionFloors: []
|
|
3443
|
+
};
|
|
3444
|
+
};
|
|
3445
|
+
if (checkpoint?.kind === "google_drive_full_reconciliation") {
|
|
3446
|
+
return await runFullReconciliation(checkpoint);
|
|
3447
|
+
}
|
|
3448
|
+
if (!cursor) {
|
|
3449
|
+
const budget = checkpoint?.kind === "google_drive_changes" ? checkpoint.changesCheckpoint.budget : emptyGoogleDriveSyncBudget();
|
|
3450
|
+
return await runFullReconciliation(
|
|
3451
|
+
await acquireFullReconciliation({
|
|
3452
|
+
budget,
|
|
3453
|
+
boundaryId: null,
|
|
3454
|
+
cursorInvalidated: providerCursor !== null
|
|
3455
|
+
})
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
if (!checkpoint && !legacyFullCheckpoint && googleDriveFullReconciliationDue(cursor, now)) {
|
|
3459
|
+
return await runFullReconciliation(
|
|
3460
|
+
await acquireFullReconciliation({
|
|
3461
|
+
budget: emptyGoogleDriveSyncBudget(),
|
|
3462
|
+
boundaryId: cursor.boundaryId,
|
|
3463
|
+
cursorInvalidated: false
|
|
3464
|
+
})
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
try {
|
|
3468
|
+
const forceFullReconciliation = legacyFullCheckpoint || googleDriveFullReconciliationDue(cursor, now);
|
|
3469
|
+
const changesCheckpoint = checkpoint?.kind === "google_drive_changes" ? {
|
|
3470
|
+
...checkpoint.changesCheckpoint,
|
|
3471
|
+
requiresFullReconciliation: checkpoint.changesCheckpoint.requiresFullReconciliation || forceFullReconciliation
|
|
3472
|
+
} : null;
|
|
3473
|
+
const changes = await drainGoogleDriveChanges({
|
|
3474
|
+
source: input.selectedSource,
|
|
3475
|
+
cursor,
|
|
3476
|
+
checkpoint: changesCheckpoint,
|
|
3477
|
+
maxItems: input.limits.maxItems,
|
|
3478
|
+
maxProviderRequests: input.limits.maxProviderRequests,
|
|
3479
|
+
maxElapsedMs,
|
|
3480
|
+
maxInvocationElapsedMs: Math.min(GOOGLE_DRIVE_SYNC_INVOCATION_SLICE_MS, maxElapsedMs),
|
|
3481
|
+
maxFileBytes: input.limits.maxFileBytes,
|
|
3482
|
+
listChanges: async (pageToken, pageSize) => await provider.listChanges(pageToken, input.selectedSource.driveId, pageSize),
|
|
3483
|
+
getFile: provider.getFile,
|
|
3484
|
+
observedExternalObjectIds: input.observedExternalObjectIds,
|
|
3485
|
+
now: clock
|
|
3486
|
+
});
|
|
3487
|
+
if (changes.status === "paused" && changes.checkpoint) {
|
|
3488
|
+
return {
|
|
3489
|
+
status: "paused",
|
|
3490
|
+
stopReason: changes.stopReason,
|
|
3491
|
+
entries: changes.entries,
|
|
3492
|
+
checkpoint: {
|
|
3493
|
+
version: 2,
|
|
3494
|
+
kind: "google_drive_changes",
|
|
3495
|
+
...expectedCursor,
|
|
3496
|
+
changesCheckpoint: {
|
|
3497
|
+
...changes.checkpoint,
|
|
3498
|
+
requiresFullReconciliation: changes.checkpoint.requiresFullReconciliation || forceFullReconciliation
|
|
3499
|
+
}
|
|
3500
|
+
},
|
|
3501
|
+
providerCursor,
|
|
3502
|
+
authoritativeFullScan: false,
|
|
3503
|
+
cursorInvalidated: false,
|
|
3504
|
+
providerRequests: changes.providerRequests,
|
|
3505
|
+
elapsedMs: changes.elapsedMs,
|
|
3506
|
+
hardLimitReached: changes.hardLimitReached
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
if (!changes.newStartPageToken) throw new Error("google_drive_changes_cursor_missing");
|
|
3510
|
+
if (changes.requiresFullReconciliation || forceFullReconciliation) {
|
|
3511
|
+
const hardStopReason = googleDriveHardLimitReason(changes.budget, budgetLimits);
|
|
3512
|
+
return {
|
|
3513
|
+
status: "paused",
|
|
3514
|
+
stopReason: hardStopReason ?? "elapsed_time_limit",
|
|
3515
|
+
entries: changes.entries,
|
|
3516
|
+
checkpoint: buildGoogleDriveFullReconciliationCheckpoint({
|
|
3517
|
+
...expectedCursor,
|
|
3518
|
+
boundaryId: cursor.boundaryId,
|
|
3519
|
+
startPageToken: changes.newStartPageToken,
|
|
3520
|
+
cursorInvalidated: false,
|
|
3521
|
+
budgetBeforeInventory: changes.budget,
|
|
3522
|
+
inventoryElapsedMs: 0,
|
|
3523
|
+
inventoryCheckpoint: null,
|
|
3524
|
+
revisionFloors: revisionFloorsForEntries(changes.entries, input.limits.maxItems)
|
|
3525
|
+
}),
|
|
3526
|
+
providerCursor,
|
|
3527
|
+
authoritativeFullScan: false,
|
|
3528
|
+
cursorInvalidated: false,
|
|
3529
|
+
providerRequests: changes.providerRequests,
|
|
3530
|
+
elapsedMs: changes.elapsedMs,
|
|
3531
|
+
hardLimitReached: hardStopReason !== null
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
return {
|
|
3535
|
+
status: "complete",
|
|
3536
|
+
stopReason: null,
|
|
3537
|
+
entries: changes.entries,
|
|
3538
|
+
checkpoint: null,
|
|
3539
|
+
providerCursor: advanceGoogleDriveChangesCursor(cursor, changes.newStartPageToken),
|
|
3540
|
+
authoritativeFullScan: false,
|
|
3541
|
+
cursorInvalidated: false,
|
|
3542
|
+
providerRequests: changes.providerRequests,
|
|
3543
|
+
elapsedMs: changes.elapsedMs,
|
|
3544
|
+
hardLimitReached: false
|
|
3545
|
+
};
|
|
3546
|
+
} catch (error) {
|
|
3547
|
+
if (error instanceof GoogleDriveChangesProtocolError) {
|
|
3548
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
3549
|
+
}
|
|
3550
|
+
if (!(error instanceof GoogleDriveCursorInvalidError)) throw error;
|
|
3551
|
+
const full = await acquireFullReconciliation({
|
|
3552
|
+
budget: error.budget ?? emptyGoogleDriveSyncBudget(),
|
|
3553
|
+
boundaryId: cursor.boundaryId,
|
|
3554
|
+
cursorInvalidated: true
|
|
3555
|
+
});
|
|
3556
|
+
const hardStopReason = googleDriveHardLimitReason(full.budgetBeforeInventory, budgetLimits);
|
|
3557
|
+
return {
|
|
3558
|
+
status: "paused",
|
|
3559
|
+
stopReason: hardStopReason ?? "elapsed_time_limit",
|
|
3560
|
+
entries: [],
|
|
3561
|
+
checkpoint: buildGoogleDriveFullReconciliationCheckpoint({
|
|
3562
|
+
...expectedCursor,
|
|
3563
|
+
...full
|
|
3564
|
+
}),
|
|
3565
|
+
providerCursor,
|
|
3566
|
+
authoritativeFullScan: false,
|
|
3567
|
+
cursorInvalidated: true,
|
|
3568
|
+
providerRequests: full.budgetBeforeInventory.providerRequests,
|
|
3569
|
+
elapsedMs: full.budgetBeforeInventory.elapsedMs,
|
|
3570
|
+
hardLimitReached: hardStopReason !== null
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
},
|
|
3574
|
+
fetchContent: async (entry, maxBytes) => await fetchDriveBytes(entry, input.authorization, maxBytes, input.retry, input.observability),
|
|
3575
|
+
readAcl: async (entry, maxProviderRequests) => {
|
|
3576
|
+
if (!provider.listPermissions) {
|
|
3577
|
+
throw new SyncFailure("provider_rejected", false);
|
|
3578
|
+
}
|
|
3579
|
+
return await collectGoogleDriveAclEvidence({
|
|
3580
|
+
entry,
|
|
3581
|
+
maxProviderRequests,
|
|
3582
|
+
listPermissions: provider.listPermissions,
|
|
3583
|
+
getFile: provider.getFile,
|
|
3584
|
+
now: clock
|
|
3585
|
+
});
|
|
3586
|
+
},
|
|
3587
|
+
citationLocator: (entry) => ({
|
|
3588
|
+
version: 2,
|
|
3589
|
+
providerKey: "google-drive",
|
|
3590
|
+
providerCoordinationKey: input.actionProviderCoordinationKey,
|
|
3591
|
+
externalObjectId: entry.externalObjectId,
|
|
3592
|
+
providerRevision: entry.externalVersionId,
|
|
3593
|
+
driveId: entry.driveId,
|
|
3594
|
+
sourceUri: entry.sourceUri
|
|
3595
|
+
})
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
function isLegacyGoogleDriveFullReconciliationCheckpoint(value, expected) {
|
|
3599
|
+
return value?.version === 2 && value.kind === "google_drive_full_reconciliation" && value.connectionId === expected.connectionId && value.googlePermissionId === expected.googlePermissionId && value.sourceId === expected.sourceId && value.driveId === expected.driveId && encodedJsonBytes(value) <= GOOGLE_DRIVE_EXECUTION_CHECKPOINT_MAX_BYTES;
|
|
3600
|
+
}
|
|
3601
|
+
function parseGoogleDriveExecutionCheckpoint(value, expected, limits) {
|
|
3602
|
+
if (!value || value.connectionId !== expected.connectionId || value.googlePermissionId !== expected.googlePermissionId || value.sourceId !== expected.sourceId || value.driveId !== expected.driveId || encodedJsonBytes(value) > GOOGLE_DRIVE_EXECUTION_CHECKPOINT_MAX_BYTES) {
|
|
3603
|
+
return null;
|
|
3604
|
+
}
|
|
3605
|
+
if (value.version === 2 && value.kind === "google_drive_changes") {
|
|
3606
|
+
const changesCheckpoint = parseGoogleDriveChangesCheckpoint(value.changesCheckpoint, limits);
|
|
3607
|
+
return changesCheckpoint ? { ...value, changesCheckpoint } : null;
|
|
3608
|
+
}
|
|
3609
|
+
if (value.version === 3 && value.kind === "google_drive_full_reconciliation") {
|
|
3610
|
+
const budgetBeforeInventory = parseGoogleDriveSyncBudget(value.budgetBeforeInventory, limits);
|
|
3611
|
+
const revisionFloors = parseGoogleDriveRevisionFloors(value.revisionFloors, limits.maxItems);
|
|
3612
|
+
if (typeof value.boundaryId !== "string" || value.boundaryId.length < 1 || value.boundaryId.length > 1024 || typeof value.startPageToken !== "string" || value.startPageToken.length < 1 || value.startPageToken.length > 4096 || typeof value.cursorInvalidated !== "boolean" || !budgetBeforeInventory || !revisionFloors || !Number.isSafeInteger(value.inventoryElapsedMs) || Number(value.inventoryElapsedMs) < 0 || Number(value.inventoryElapsedMs) > limits.maxElapsedMs || budgetBeforeInventory.elapsedMs + Number(value.inventoryElapsedMs) > limits.maxElapsedMs || value.inventoryCheckpoint !== null && (!value.inventoryCheckpoint || typeof value.inventoryCheckpoint !== "object" || Array.isArray(value.inventoryCheckpoint))) {
|
|
3613
|
+
return null;
|
|
3614
|
+
}
|
|
3615
|
+
return {
|
|
3616
|
+
...value,
|
|
3617
|
+
budgetBeforeInventory,
|
|
3618
|
+
inventoryElapsedMs: Number(value.inventoryElapsedMs),
|
|
3619
|
+
revisionFloors
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
return null;
|
|
3623
|
+
}
|
|
3624
|
+
function buildGoogleDriveFullReconciliationCheckpoint(input) {
|
|
3625
|
+
const checkpoint = {
|
|
3626
|
+
version: 3,
|
|
3627
|
+
kind: "google_drive_full_reconciliation",
|
|
3628
|
+
...input,
|
|
3629
|
+
revisionFloors: input.revisionFloors.map((floor) => [...floor])
|
|
3630
|
+
};
|
|
3631
|
+
if (encodedJsonBytes(checkpoint) > GOOGLE_DRIVE_EXECUTION_CHECKPOINT_MAX_BYTES) {
|
|
3632
|
+
throw new SyncFailure("resource_limit", false);
|
|
3633
|
+
}
|
|
3634
|
+
return checkpoint;
|
|
3635
|
+
}
|
|
3636
|
+
function parseGoogleDriveRevisionFloors(value, maxItems) {
|
|
3637
|
+
if (!Array.isArray(value) || value.length > maxItems) return null;
|
|
3638
|
+
const floors = [];
|
|
3639
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3640
|
+
for (const floor of value) {
|
|
3641
|
+
if (!Array.isArray(floor) || floor.length !== 2 || typeof floor[0] !== "string" || floor[0].trim().length < 1 || floor[0].length > 1024 || floor[1] !== null && (typeof floor[1] !== "string" || floor[1].length < 1 || floor[1].length > 256) || seen.has(floor[0])) {
|
|
3642
|
+
return null;
|
|
3643
|
+
}
|
|
3644
|
+
seen.add(floor[0]);
|
|
3645
|
+
floors.push([floor[0], floor[1]]);
|
|
3646
|
+
}
|
|
3647
|
+
return floors;
|
|
3648
|
+
}
|
|
3649
|
+
function revisionFloorsForEntries(entries, maxItems) {
|
|
3650
|
+
const reconciled = reconcileGoogleDriveRevisionFloors(entries, [], maxItems);
|
|
3651
|
+
if (reconciled.conflict) throw new SyncFailure("provider_payload_invalid", false);
|
|
3652
|
+
return reconciled.revisionFloors;
|
|
3653
|
+
}
|
|
3654
|
+
function shouldProcessGoogleDriveDurableObservation(input) {
|
|
3655
|
+
if (input.observation.externalObjectId !== input.entry.externalObjectId) {
|
|
3656
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
3657
|
+
}
|
|
3658
|
+
if (input.observation.disposition === "accepted" || input.observation.disposition === "replayed") {
|
|
3659
|
+
return true;
|
|
3660
|
+
}
|
|
3661
|
+
if (input.observation.disposition === "conflict") {
|
|
3662
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
3663
|
+
}
|
|
3664
|
+
const current = input.observation.currentVersion;
|
|
3665
|
+
const comparison = current ? compareCanonicalDecimalProviderRevisions(
|
|
3666
|
+
current.providerRevision,
|
|
3667
|
+
input.observation.providerRevision
|
|
3668
|
+
) : null;
|
|
3669
|
+
const fullyProcessed = current !== null && current.objectLifecycleState === "active" && current.sourceLifecycleGeneration === input.sourceLifecycleGeneration && current.objectLifecycleGeneration === current.currentObjectLifecycleGeneration && current.aclGeneration === input.aclGeneration && current.indexObligationStatus === "indexed" && comparison !== null && comparison >= 0 && (comparison > 0 || current.metadataHash === input.observation.metadataHash);
|
|
3670
|
+
if (!fullyProcessed) throw new SyncFailure("provider_payload_invalid", false);
|
|
3671
|
+
return false;
|
|
3672
|
+
}
|
|
3673
|
+
function mergeGoogleDriveDurableObservationFloors(checkpoint, observations, maxItems) {
|
|
3674
|
+
if (!checkpoint || checkpoint.version !== 3 || checkpoint.kind !== "google_drive_full_reconciliation") {
|
|
3675
|
+
return checkpoint;
|
|
3676
|
+
}
|
|
3677
|
+
const parsedFloors = parseGoogleDriveRevisionFloors(checkpoint.revisionFloors, maxItems);
|
|
3678
|
+
if (!parsedFloors) throw new SyncFailure("provider_payload_invalid", false);
|
|
3679
|
+
const floors = new Map(parsedFloors);
|
|
3680
|
+
for (const observation of observations) {
|
|
3681
|
+
floors.set(observation.externalObjectId, observation.providerRevision);
|
|
3682
|
+
}
|
|
3683
|
+
if (floors.size > maxItems) throw new SyncFailure("resource_limit", false);
|
|
3684
|
+
const merged = {
|
|
3685
|
+
...checkpoint,
|
|
3686
|
+
revisionFloors: [...floors]
|
|
3687
|
+
};
|
|
3688
|
+
if (encodedJsonBytes(merged) > GOOGLE_DRIVE_EXECUTION_CHECKPOINT_MAX_BYTES) {
|
|
3689
|
+
throw new SyncFailure("resource_limit", false);
|
|
3690
|
+
}
|
|
3691
|
+
return merged;
|
|
3692
|
+
}
|
|
3693
|
+
function reconcileGoogleDriveRevisionFloors(entries, existingFloors, maxItems) {
|
|
3694
|
+
const floors = new Map(existingFloors);
|
|
3695
|
+
const accepted = [];
|
|
3696
|
+
for (const entry of entries) {
|
|
3697
|
+
if (!floors.has(entry.externalObjectId)) {
|
|
3698
|
+
floors.set(entry.externalObjectId, entry.externalVersionId);
|
|
3699
|
+
accepted.push(entry);
|
|
3700
|
+
} else {
|
|
3701
|
+
const comparison = compareCanonicalDecimalProviderRevisions(
|
|
3702
|
+
entry.externalVersionId,
|
|
3703
|
+
floors.get(entry.externalObjectId) ?? null
|
|
3704
|
+
);
|
|
3705
|
+
if (comparison === null) {
|
|
3706
|
+
return {
|
|
3707
|
+
entries: [],
|
|
3708
|
+
revisionFloors: [...floors],
|
|
3709
|
+
conflict: true
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
if (comparison > 0) {
|
|
3713
|
+
floors.set(entry.externalObjectId, entry.externalVersionId);
|
|
3714
|
+
accepted.push(entry);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
if (floors.size > maxItems) throw new SyncFailure("resource_limit", false);
|
|
3718
|
+
}
|
|
3719
|
+
return {
|
|
3720
|
+
entries: accepted,
|
|
3721
|
+
revisionFloors: [...floors],
|
|
3722
|
+
conflict: false
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
function encodedJsonBytes(value) {
|
|
3726
|
+
try {
|
|
3727
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
3728
|
+
} catch {
|
|
3729
|
+
return Number.POSITIVE_INFINITY;
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
function parseGoogleDriveSyncBudget(value, limits) {
|
|
3733
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
3734
|
+
const row = value;
|
|
3735
|
+
for (const [key, maximum] of [
|
|
3736
|
+
["examinedItems", limits.maxItems],
|
|
3737
|
+
["providerRequests", limits.maxProviderRequests],
|
|
3738
|
+
["elapsedMs", limits.maxElapsedMs]
|
|
3739
|
+
]) {
|
|
3740
|
+
const budgetValue = row[key];
|
|
3741
|
+
if (!Number.isSafeInteger(budgetValue) || Number(budgetValue) < 0 || Number(budgetValue) > maximum) {
|
|
3742
|
+
return null;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
return value;
|
|
3746
|
+
}
|
|
3747
|
+
function emptyGoogleDriveSyncBudget() {
|
|
3748
|
+
return { examinedItems: 0, providerRequests: 0, elapsedMs: 0 };
|
|
3749
|
+
}
|
|
3750
|
+
function googleDriveHardLimitReason(budget, limits) {
|
|
3751
|
+
if (budget.examinedItems >= limits.maxItems) return "item_limit";
|
|
3752
|
+
if (budget.providerRequests >= limits.maxProviderRequests) return "api_request_limit";
|
|
3753
|
+
if (budget.elapsedMs >= limits.maxElapsedMs) return "elapsed_time_limit";
|
|
3754
|
+
return null;
|
|
3755
|
+
}
|
|
3756
|
+
function googleDriveInventoryStopIsHard(reason, budget, limits) {
|
|
3757
|
+
if (reason === "api_request_limit" || reason === "item_limit" || reason === "known_byte_limit") {
|
|
3758
|
+
return true;
|
|
3759
|
+
}
|
|
3760
|
+
return reason === "elapsed_time_limit" && budget.elapsedMs >= limits.maxElapsedMs;
|
|
3761
|
+
}
|
|
3762
|
+
async function getDriveStartPageToken(driveId, authorization, retry, observability) {
|
|
3763
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3764
|
+
const url = new URL(`${DRIVE_API_BASE}/changes/startPageToken`);
|
|
3765
|
+
url.searchParams.set("supportsAllDrives", "true");
|
|
3766
|
+
if (driveId) url.searchParams.set("driveId", driveId);
|
|
3767
|
+
const response = await requestGoogleDrive(
|
|
3768
|
+
url,
|
|
3769
|
+
{ headers: { authorization, accept: "application/json" } },
|
|
3770
|
+
"start_page_token",
|
|
3771
|
+
retry,
|
|
3772
|
+
observability
|
|
3773
|
+
);
|
|
3774
|
+
if (!response.ok) throwGoogleDriveResponse(response);
|
|
3775
|
+
const payload = await readResponseJsonBounded(
|
|
3776
|
+
response,
|
|
3777
|
+
DRIVE_JSON_MAX_BYTES,
|
|
3778
|
+
"Google Drive start page token"
|
|
3779
|
+
);
|
|
3780
|
+
return requiredString(payload.startPageToken, "start_page_token");
|
|
3781
|
+
}
|
|
3782
|
+
async function listDriveChanges(pageToken, driveId, pageSize, authorization, retry, observability) {
|
|
3783
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3784
|
+
const url = new URL(`${DRIVE_API_BASE}/changes`);
|
|
3785
|
+
url.searchParams.set("pageToken", pageToken);
|
|
3786
|
+
url.searchParams.set("pageSize", String(pageSize));
|
|
3787
|
+
url.searchParams.set("supportsAllDrives", "true");
|
|
3788
|
+
url.searchParams.set("includeItemsFromAllDrives", "true");
|
|
3789
|
+
url.searchParams.set("spaces", "drive");
|
|
3790
|
+
url.searchParams.set(
|
|
3791
|
+
"fields",
|
|
3792
|
+
"nextPageToken,newStartPageToken,changes(fileId,removed,time,driveId,file(id,name,mimeType,driveId,parents,modifiedTime,createdTime,version,md5Checksum,size,webViewLink,trashed))"
|
|
3793
|
+
);
|
|
3794
|
+
if (driveId) url.searchParams.set("driveId", driveId);
|
|
3795
|
+
const response = await requestGoogleDrive(
|
|
3796
|
+
url,
|
|
3797
|
+
{ headers: { authorization, accept: "application/json" } },
|
|
3798
|
+
"list_changes",
|
|
3799
|
+
retry,
|
|
3800
|
+
observability
|
|
3801
|
+
);
|
|
3802
|
+
if (response.status === 410) throw new GoogleDriveCursorInvalidError();
|
|
3803
|
+
if (!response.ok) throwGoogleDriveResponse(response);
|
|
3804
|
+
const payload = await readResponseJsonBounded(
|
|
3805
|
+
response,
|
|
3806
|
+
DRIVE_JSON_MAX_BYTES,
|
|
3807
|
+
"Google Drive changes list"
|
|
3808
|
+
);
|
|
3809
|
+
const changes = Array.isArray(payload.changes) ? payload.changes : [];
|
|
3810
|
+
return {
|
|
3811
|
+
changes: changes.map((value) => {
|
|
3812
|
+
const row = value && typeof value === "object" ? value : {};
|
|
3813
|
+
const fileId = requiredString(row.fileId, "change_file_id");
|
|
3814
|
+
return {
|
|
3815
|
+
fileId,
|
|
3816
|
+
removed: row.removed === true,
|
|
3817
|
+
file: row.file && typeof row.file === "object" ? parseDriveProviderItem(row.file) : null
|
|
3818
|
+
};
|
|
3819
|
+
}),
|
|
3820
|
+
nextPageToken: optionalString(payload.nextPageToken),
|
|
3821
|
+
newStartPageToken: optionalString(payload.newStartPageToken)
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
async function getDriveFile(fileId, authorization, retry, observability) {
|
|
3825
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3826
|
+
const url = new URL(`${DRIVE_API_BASE}/files/${encodeURIComponent(fileId)}`);
|
|
3827
|
+
url.searchParams.set("supportsAllDrives", "true");
|
|
3828
|
+
url.searchParams.set(
|
|
3829
|
+
"fields",
|
|
3830
|
+
"id,name,mimeType,driveId,parents,modifiedTime,createdTime,version,md5Checksum,size,webViewLink,trashed"
|
|
3831
|
+
);
|
|
3832
|
+
const response = await requestGoogleDrive(
|
|
3833
|
+
url,
|
|
3834
|
+
{ headers: { authorization, accept: "application/json" } },
|
|
3835
|
+
"get_file",
|
|
3836
|
+
retry,
|
|
3837
|
+
observability
|
|
3838
|
+
);
|
|
3839
|
+
if (response.status === 404) return null;
|
|
3840
|
+
if (!response.ok) throwGoogleDriveResponse(response);
|
|
3841
|
+
return parseDriveProviderItem(
|
|
3842
|
+
await readResponseJsonBounded(response, DRIVE_JSON_MAX_BYTES, "Google Drive file metadata")
|
|
3843
|
+
);
|
|
3844
|
+
}
|
|
3845
|
+
async function listDrivePermissions(input, authorization, retry, observability) {
|
|
3846
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
3847
|
+
const url = new URL(`${DRIVE_API_BASE}/files/${encodeURIComponent(input.fileId)}/permissions`);
|
|
3848
|
+
url.searchParams.set("supportsAllDrives", "true");
|
|
3849
|
+
url.searchParams.set("pageSize", String(input.pageSize));
|
|
3850
|
+
url.searchParams.set(
|
|
3851
|
+
"fields",
|
|
3852
|
+
"nextPageToken,permissions(id,type,emailAddress,domain,role,allowFileDiscovery,expirationTime,deleted,permissionDetails(inherited))"
|
|
3853
|
+
);
|
|
3854
|
+
if (input.pageToken) url.searchParams.set("pageToken", input.pageToken);
|
|
3855
|
+
const response = await requestGoogleDrive(
|
|
3856
|
+
url,
|
|
3857
|
+
{ headers: { authorization, accept: "application/json" } },
|
|
3858
|
+
"list_permissions",
|
|
3859
|
+
retry,
|
|
3860
|
+
observability
|
|
3861
|
+
);
|
|
3862
|
+
if (response.status === 403 || response.status === 404) {
|
|
3863
|
+
return { permissions: [], nextPageToken: null, denied: true };
|
|
3864
|
+
}
|
|
3865
|
+
if (!response.ok) throwGoogleDriveResponse(response);
|
|
3866
|
+
const payload = await readResponseJsonBounded(
|
|
3867
|
+
response,
|
|
3868
|
+
DRIVE_JSON_MAX_BYTES,
|
|
3869
|
+
"Google Drive permission list"
|
|
3870
|
+
);
|
|
3871
|
+
const rawPermissions = Array.isArray(payload.permissions) ? payload.permissions : null;
|
|
3872
|
+
if (!rawPermissions) throw new SyncFailure("provider_payload_invalid", false);
|
|
3873
|
+
return {
|
|
3874
|
+
permissions: rawPermissions.map(parseDrivePermission),
|
|
3875
|
+
nextPageToken: optionalString(payload.nextPageToken),
|
|
3876
|
+
denied: false
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
async function collectGoogleDriveAclEvidence(input) {
|
|
3880
|
+
if (!Number.isSafeInteger(input.maxProviderRequests) || input.maxProviderRequests < 1) {
|
|
3881
|
+
throw new SyncFailure("resource_limit", false);
|
|
3882
|
+
}
|
|
3883
|
+
const now = input.now ?? Date.now;
|
|
3884
|
+
const observedAt = new Date(now());
|
|
3885
|
+
if (!Number.isFinite(observedAt.getTime())) {
|
|
3886
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
3887
|
+
}
|
|
3888
|
+
const permissions = [];
|
|
3889
|
+
const seenTokens = /* @__PURE__ */ new Set();
|
|
3890
|
+
let pageToken = null;
|
|
3891
|
+
let providerRequests = 0;
|
|
3892
|
+
let permissionListDenied = false;
|
|
3893
|
+
do {
|
|
3894
|
+
if (providerRequests >= input.maxProviderRequests) {
|
|
3895
|
+
throw new SyncFailure("resource_limit", false);
|
|
3896
|
+
}
|
|
3897
|
+
providerRequests += 1;
|
|
3898
|
+
const page = await input.listPermissions({
|
|
3899
|
+
fileId: input.entry.externalObjectId,
|
|
3900
|
+
pageToken,
|
|
3901
|
+
pageSize: Math.min(100, GOOGLE_DRIVE_ACL_MAX_PRINCIPALS - permissions.length)
|
|
3902
|
+
});
|
|
3903
|
+
if (page.denied) {
|
|
3904
|
+
permissionListDenied = true;
|
|
3905
|
+
break;
|
|
3906
|
+
}
|
|
3907
|
+
permissions.push(...page.permissions);
|
|
3908
|
+
if (permissions.length > GOOGLE_DRIVE_ACL_MAX_PRINCIPALS) {
|
|
3909
|
+
throw new SyncFailure("resource_limit", false);
|
|
3910
|
+
}
|
|
3911
|
+
pageToken = page.nextPageToken;
|
|
3912
|
+
if (pageToken) {
|
|
3913
|
+
if (pageToken.length > 4096 || seenTokens.has(pageToken)) {
|
|
3914
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
1268
3915
|
}
|
|
3916
|
+
seenTokens.add(pageToken);
|
|
3917
|
+
}
|
|
3918
|
+
} while (pageToken);
|
|
3919
|
+
if (permissionListDenied) {
|
|
3920
|
+
if (!input.getFile || providerRequests >= input.maxProviderRequests) {
|
|
3921
|
+
return googleDriveDeniedAclEvidence(input.entry, observedAt, providerRequests);
|
|
3922
|
+
}
|
|
3923
|
+
providerRequests += 1;
|
|
3924
|
+
const current = await input.getFile(input.entry.externalObjectId);
|
|
3925
|
+
if (!current || current.trashed) {
|
|
3926
|
+
return googleDriveDeniedAclEvidence(input.entry, observedAt, providerRequests);
|
|
3927
|
+
}
|
|
3928
|
+
const aclRevision2 = createHash2("sha256").update(
|
|
3929
|
+
JSON.stringify({
|
|
3930
|
+
version: 1,
|
|
3931
|
+
mode: "source_owner_only",
|
|
3932
|
+
externalObjectId: input.entry.externalObjectId,
|
|
3933
|
+
providerRevision: input.entry.externalVersionId
|
|
3934
|
+
})
|
|
3935
|
+
).digest("hex");
|
|
3936
|
+
return {
|
|
3937
|
+
eligibility: "eligible",
|
|
3938
|
+
providerRevision: input.entry.externalVersionId,
|
|
3939
|
+
driveId: input.entry.driveId,
|
|
3940
|
+
aclRevision: aclRevision2,
|
|
3941
|
+
observedAt: observedAt.toISOString(),
|
|
3942
|
+
expiresAt: new Date(observedAt.getTime() + GOOGLE_DRIVE_ACL_FRESHNESS_MS).toISOString(),
|
|
3943
|
+
providerRequests,
|
|
3944
|
+
principals: []
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
const canonicalPermissions = permissions.map((permission) => ({
|
|
3948
|
+
id: permission.id,
|
|
3949
|
+
type: permission.type,
|
|
3950
|
+
role: permission.role,
|
|
3951
|
+
emailAddress: permission.emailAddress,
|
|
3952
|
+
domain: permission.domain,
|
|
3953
|
+
allowFileDiscovery: permission.allowFileDiscovery,
|
|
3954
|
+
expirationTime: permission.expirationTime,
|
|
3955
|
+
inherited: permission.inherited
|
|
3956
|
+
})).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
3957
|
+
const aclRevision = createHash2("sha256").update(
|
|
3958
|
+
JSON.stringify({
|
|
3959
|
+
version: 1,
|
|
3960
|
+
externalObjectId: input.entry.externalObjectId,
|
|
3961
|
+
providerRevision: input.entry.externalVersionId,
|
|
3962
|
+
permissions: canonicalPermissions
|
|
3963
|
+
})
|
|
3964
|
+
).digest("hex");
|
|
3965
|
+
return {
|
|
3966
|
+
eligibility: "eligible",
|
|
3967
|
+
providerRevision: input.entry.externalVersionId,
|
|
3968
|
+
driveId: input.entry.driveId,
|
|
3969
|
+
aclRevision,
|
|
3970
|
+
observedAt: observedAt.toISOString(),
|
|
3971
|
+
expiresAt: new Date(observedAt.getTime() + GOOGLE_DRIVE_ACL_FRESHNESS_MS).toISOString(),
|
|
3972
|
+
providerRequests,
|
|
3973
|
+
principals: canonicalPermissions.map((permission) => ({
|
|
3974
|
+
type: permission.type,
|
|
3975
|
+
permissionId: permission.id,
|
|
3976
|
+
emailAddress: permission.emailAddress,
|
|
3977
|
+
domain: permission.domain,
|
|
3978
|
+
role: permission.role,
|
|
3979
|
+
inherited: permission.inherited,
|
|
3980
|
+
allowFileDiscovery: permission.allowFileDiscovery,
|
|
3981
|
+
expirationTime: permission.expirationTime
|
|
3982
|
+
}))
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
function googleDriveDeniedAclEvidence(entry, observedAt, providerRequests) {
|
|
3986
|
+
return {
|
|
3987
|
+
eligibility: "denied",
|
|
3988
|
+
providerRevision: entry.externalVersionId,
|
|
3989
|
+
driveId: entry.driveId,
|
|
3990
|
+
aclRevision: createHash2("sha256").update(
|
|
3991
|
+
JSON.stringify({
|
|
3992
|
+
version: 1,
|
|
3993
|
+
mode: "provider_denied",
|
|
3994
|
+
externalObjectId: entry.externalObjectId,
|
|
3995
|
+
providerRevision: entry.externalVersionId
|
|
3996
|
+
})
|
|
3997
|
+
).digest("hex"),
|
|
3998
|
+
observedAt: observedAt.toISOString(),
|
|
3999
|
+
expiresAt: new Date(observedAt.getTime() + GOOGLE_DRIVE_ACL_FRESHNESS_MS).toISOString(),
|
|
4000
|
+
providerRequests,
|
|
4001
|
+
principals: []
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
function parseDrivePermission(value) {
|
|
4005
|
+
const row = objectRecord(value);
|
|
4006
|
+
if (row.deleted === true) throw new SyncFailure("provider_payload_invalid", false);
|
|
4007
|
+
const type = requiredString(row.type, "permission.type");
|
|
4008
|
+
if (type !== "user" && type !== "group" && type !== "domain" && type !== "anyone") {
|
|
4009
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
4010
|
+
}
|
|
4011
|
+
const role = requiredString(row.role, "permission.role");
|
|
4012
|
+
if (role !== "owner" && role !== "organizer" && role !== "fileOrganizer" && role !== "writer" && role !== "commenter" && role !== "reader") {
|
|
4013
|
+
throw new SyncFailure("provider_payload_invalid", false);
|
|
4014
|
+
}
|
|
4015
|
+
const permissionDetails = Array.isArray(row.permissionDetails) ? row.permissionDetails : [];
|
|
4016
|
+
return {
|
|
4017
|
+
id: requiredString(row.id, "permission.id"),
|
|
4018
|
+
type,
|
|
4019
|
+
role,
|
|
4020
|
+
emailAddress: optionalString(row.emailAddress),
|
|
4021
|
+
domain: optionalString(row.domain),
|
|
4022
|
+
allowFileDiscovery: typeof row.allowFileDiscovery === "boolean" ? row.allowFileDiscovery : null,
|
|
4023
|
+
expirationTime: optionalExactProviderTimestamp(row.expirationTime),
|
|
4024
|
+
inherited: permissionDetails.some((detail) => objectRecord(detail).inherited === true)
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
function optionalExactProviderTimestamp(value) {
|
|
4028
|
+
const timestamp = optionalString(value);
|
|
4029
|
+
if (!timestamp) return null;
|
|
4030
|
+
const parsed = new Date(timestamp);
|
|
4031
|
+
if (!Number.isFinite(parsed.getTime())) throw new SyncFailure("provider_payload_invalid", false);
|
|
4032
|
+
return parsed.toISOString();
|
|
4033
|
+
}
|
|
4034
|
+
function throwGoogleDriveResponse(response) {
|
|
4035
|
+
if (response.status === 401 || response.status === 403) {
|
|
4036
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4037
|
+
}
|
|
4038
|
+
throw new SyncFailure(
|
|
4039
|
+
response.status >= 500 || response.status === 429 ? "provider_unavailable" : "provider_rejected",
|
|
4040
|
+
response.status >= 500 || response.status === 429
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
async function requestGoogleDrive(url, init, operation, retry, observability) {
|
|
4044
|
+
try {
|
|
4045
|
+
return await fetchGoogleDriveProvider({
|
|
4046
|
+
fetchImpl: sourceFetch,
|
|
4047
|
+
url,
|
|
4048
|
+
init,
|
|
4049
|
+
operation,
|
|
4050
|
+
policy: retry,
|
|
4051
|
+
observability,
|
|
4052
|
+
heartbeat: (details) => heartbeat({ provider: "google_drive", ...details })
|
|
1269
4053
|
});
|
|
1270
|
-
|
|
1271
|
-
|
|
4054
|
+
} catch (error) {
|
|
4055
|
+
if (error instanceof GoogleDriveProviderTransportError) {
|
|
4056
|
+
throw new SyncFailure("provider_unavailable", true);
|
|
1272
4057
|
}
|
|
1273
|
-
|
|
4058
|
+
throw error;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
function selectedDriveSource(metadata, externalSourceId) {
|
|
4062
|
+
const sources = metadata.selectedSources ?? (metadata.selectedSource ? [metadata.selectedSource] : []);
|
|
4063
|
+
return sources.find((source) => source.id === externalSourceId) ?? null;
|
|
4064
|
+
}
|
|
4065
|
+
function googleDriveSyncProviderAccessAllowed(input) {
|
|
4066
|
+
return input.resolvedConnectionVersion !== void 0 && input.connectionVersion === input.resolvedConnectionVersion && input.connectionStatus === "active" && (input.lifecycleState === void 0 || input.lifecycleState === "active") && googleDriveScopesAllowCapability(input.grantedScopes, "recursive_source_sync");
|
|
4067
|
+
}
|
|
4068
|
+
async function listDriveChildren(input, authorization, retry, observability) {
|
|
4069
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4070
|
+
const url = new URL(`${DRIVE_API_BASE}/files`);
|
|
4071
|
+
url.searchParams.set(
|
|
4072
|
+
"q",
|
|
4073
|
+
`'${input.folderId.replaceAll("'", "\\'")}' in parents and trashed = false`
|
|
4074
|
+
);
|
|
4075
|
+
url.searchParams.set("pageSize", String(input.pageSize));
|
|
4076
|
+
url.searchParams.set("supportsAllDrives", "true");
|
|
4077
|
+
url.searchParams.set("includeItemsFromAllDrives", "true");
|
|
4078
|
+
url.searchParams.set(
|
|
4079
|
+
"fields",
|
|
4080
|
+
"nextPageToken,incompleteSearch,files(id,name,mimeType,driveId,parents,modifiedTime,createdTime,version,md5Checksum,size,webViewLink,trashed)"
|
|
4081
|
+
);
|
|
4082
|
+
if (input.pageToken) url.searchParams.set("pageToken", input.pageToken);
|
|
4083
|
+
if (input.driveId) {
|
|
4084
|
+
url.searchParams.set("corpora", "drive");
|
|
4085
|
+
url.searchParams.set("driveId", input.driveId);
|
|
4086
|
+
}
|
|
4087
|
+
const response = await requestGoogleDrive(
|
|
4088
|
+
url,
|
|
4089
|
+
{ headers: { authorization, accept: "application/json" } },
|
|
4090
|
+
"list_children",
|
|
4091
|
+
retry,
|
|
4092
|
+
observability
|
|
4093
|
+
);
|
|
4094
|
+
if (!response.ok) {
|
|
4095
|
+
if (response.status === 401 || response.status === 403) {
|
|
4096
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4097
|
+
}
|
|
4098
|
+
throw new SyncFailure(
|
|
4099
|
+
"provider_unavailable",
|
|
4100
|
+
response.status >= 500 || response.status === 429
|
|
4101
|
+
);
|
|
1274
4102
|
}
|
|
4103
|
+
const payload = await readResponseJsonBounded(
|
|
4104
|
+
response,
|
|
4105
|
+
DRIVE_JSON_MAX_BYTES,
|
|
4106
|
+
"Google Drive file list"
|
|
4107
|
+
);
|
|
4108
|
+
const rawFiles = Array.isArray(payload.files) ? payload.files : [];
|
|
1275
4109
|
return {
|
|
1276
|
-
|
|
1277
|
-
|
|
4110
|
+
items: rawFiles.map(parseDriveProviderItem),
|
|
4111
|
+
nextPageToken: typeof payload.nextPageToken === "string" ? payload.nextPageToken : null,
|
|
4112
|
+
incompleteSearch: payload.incompleteSearch === true
|
|
1278
4113
|
};
|
|
1279
4114
|
}
|
|
1280
|
-
function
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
4115
|
+
function parseDriveProviderItem(value) {
|
|
4116
|
+
const row = value && typeof value === "object" ? value : {};
|
|
4117
|
+
return {
|
|
4118
|
+
id: requiredString(row.id, "id"),
|
|
4119
|
+
name: requiredString(row.name, "name"),
|
|
4120
|
+
mimeType: requiredString(row.mimeType, "mimeType"),
|
|
4121
|
+
driveId: optionalString(row.driveId),
|
|
4122
|
+
parents: Array.isArray(row.parents) ? row.parents.filter((item) => typeof item === "string") : [],
|
|
4123
|
+
modifiedTime: optionalString(row.modifiedTime),
|
|
4124
|
+
createdTime: optionalString(row.createdTime),
|
|
4125
|
+
version: optionalString(row.version),
|
|
4126
|
+
md5Checksum: optionalString(row.md5Checksum),
|
|
4127
|
+
size: optionalString(row.size),
|
|
4128
|
+
webViewLink: optionalString(row.webViewLink),
|
|
4129
|
+
trashed: row.trashed === true
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
async function fetchDriveBytes(entry, authorization, maxBytes, retry, observability) {
|
|
4133
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4134
|
+
const url = entry.transfer.action === "export" ? new URL(`${DRIVE_API_BASE}/files/${encodeURIComponent(entry.externalObjectId)}/export`) : new URL(`${DRIVE_API_BASE}/files/${encodeURIComponent(entry.externalObjectId)}`);
|
|
4135
|
+
if (entry.transfer.action === "export") {
|
|
4136
|
+
url.searchParams.set("mimeType", entry.transfer.contentType);
|
|
4137
|
+
} else {
|
|
4138
|
+
url.searchParams.set("alt", "media");
|
|
4139
|
+
url.searchParams.set("supportsAllDrives", "true");
|
|
4140
|
+
}
|
|
4141
|
+
const response = await requestGoogleDrive(
|
|
4142
|
+
url,
|
|
4143
|
+
{ headers: { authorization } },
|
|
4144
|
+
entry.transfer.action === "export" ? "export" : "download",
|
|
4145
|
+
retry,
|
|
4146
|
+
observability
|
|
4147
|
+
);
|
|
4148
|
+
if (!response.ok) {
|
|
4149
|
+
if (response.status === 401 || response.status === 403) {
|
|
4150
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4151
|
+
}
|
|
4152
|
+
throw new SyncFailure(
|
|
4153
|
+
"provider_unavailable",
|
|
4154
|
+
response.status >= 500 || response.status === 429
|
|
4155
|
+
);
|
|
1290
4156
|
}
|
|
1291
|
-
|
|
4157
|
+
const declared = Number(response.headers.get("content-length") ?? "0");
|
|
4158
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
4159
|
+
throw new SyncFailure("content_too_large", false);
|
|
4160
|
+
}
|
|
4161
|
+
if (!response.body) {
|
|
4162
|
+
const bytes2 = new Uint8Array(await response.arrayBuffer());
|
|
4163
|
+
if (bytes2.byteLength > maxBytes) throw new SyncFailure("content_too_large", false);
|
|
4164
|
+
return bytes2;
|
|
4165
|
+
}
|
|
4166
|
+
const reader = response.body.getReader();
|
|
4167
|
+
const chunks = [];
|
|
4168
|
+
let total = 0;
|
|
4169
|
+
while (true) {
|
|
4170
|
+
const { done, value } = await reader.read();
|
|
4171
|
+
if (done) break;
|
|
4172
|
+
total += value.byteLength;
|
|
4173
|
+
if (total > maxBytes) {
|
|
4174
|
+
await reader.cancel();
|
|
4175
|
+
throw new SyncFailure("content_too_large", false);
|
|
4176
|
+
}
|
|
4177
|
+
chunks.push(value);
|
|
4178
|
+
}
|
|
4179
|
+
const bytes = new Uint8Array(total);
|
|
4180
|
+
let offset = 0;
|
|
4181
|
+
for (const chunk of chunks) {
|
|
4182
|
+
bytes.set(chunk, offset);
|
|
4183
|
+
offset += chunk.byteLength;
|
|
4184
|
+
}
|
|
4185
|
+
return bytes;
|
|
4186
|
+
}
|
|
4187
|
+
async function listAtlassianItems(source, cursor, pageSize, authorization) {
|
|
4188
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4189
|
+
if (source.kind === "jira_project") {
|
|
4190
|
+
const url2 = new URL(
|
|
4191
|
+
`${ATLASSIAN_API_BASE}/ex/jira/${encodeURIComponent(source.cloudId)}/rest/api/3/search/jql`
|
|
4192
|
+
);
|
|
4193
|
+
url2.searchParams.set(
|
|
4194
|
+
"jql",
|
|
4195
|
+
`project = "${source.key.replaceAll('"', '\\"')}" ORDER BY key ASC`
|
|
4196
|
+
);
|
|
4197
|
+
url2.searchParams.set("maxResults", String(pageSize));
|
|
4198
|
+
url2.searchParams.set("fields", "summary,created,updated");
|
|
4199
|
+
if (cursor) url2.searchParams.set("nextPageToken", cursor);
|
|
4200
|
+
const payload2 = objectRecord(await atlassianJsonRequest(url2, authorization, "Jira issue list"));
|
|
4201
|
+
const issues = Array.isArray(payload2.issues) ? payload2.issues : [];
|
|
1292
4202
|
return {
|
|
1293
|
-
|
|
1294
|
-
|
|
4203
|
+
items: issues.map((raw) => {
|
|
4204
|
+
const row = objectRecord(raw);
|
|
4205
|
+
const fields = objectRecord(row.fields);
|
|
4206
|
+
const id = requiredAtlassianString(row.id, "issue.id");
|
|
4207
|
+
const key = requiredAtlassianString(row.key, "issue.key");
|
|
4208
|
+
const updatedAt = optionalString(fields.updated);
|
|
4209
|
+
return {
|
|
4210
|
+
id,
|
|
4211
|
+
key,
|
|
4212
|
+
title: requiredAtlassianString(fields.summary, "issue.summary"),
|
|
4213
|
+
version: updatedAt,
|
|
4214
|
+
createdAt: optionalString(fields.created),
|
|
4215
|
+
updatedAt,
|
|
4216
|
+
webUrl: new URL(`/browse/${encodeURIComponent(key)}`, source.siteUrl).toString()
|
|
4217
|
+
};
|
|
4218
|
+
}),
|
|
4219
|
+
nextCursor: optionalString(payload2.nextPageToken)
|
|
1295
4220
|
};
|
|
1296
4221
|
}
|
|
4222
|
+
const url = new URL(
|
|
4223
|
+
`${ATLASSIAN_API_BASE}/ex/confluence/${encodeURIComponent(source.cloudId)}/wiki/api/v2/spaces/${encodeURIComponent(source.resourceId)}/pages`
|
|
4224
|
+
);
|
|
4225
|
+
url.searchParams.set("limit", String(pageSize));
|
|
4226
|
+
url.searchParams.set("sort", "id");
|
|
4227
|
+
if (cursor) url.searchParams.set("cursor", cursor);
|
|
4228
|
+
const payload = objectRecord(
|
|
4229
|
+
await atlassianJsonRequest(url, authorization, "Confluence page list")
|
|
4230
|
+
);
|
|
4231
|
+
const pages = Array.isArray(payload.results) ? payload.results : [];
|
|
1297
4232
|
return {
|
|
1298
|
-
|
|
1299
|
-
|
|
4233
|
+
items: pages.map((raw) => {
|
|
4234
|
+
const row = objectRecord(raw);
|
|
4235
|
+
const version = objectRecord(row.version);
|
|
4236
|
+
const id = requiredAtlassianString(row.id, "page.id");
|
|
4237
|
+
const webUi = optionalString(objectRecord(row._links).webui);
|
|
4238
|
+
const versionNumber = numberValue(version.number);
|
|
4239
|
+
return {
|
|
4240
|
+
id,
|
|
4241
|
+
key: id,
|
|
4242
|
+
title: requiredAtlassianString(row.title, "page.title"),
|
|
4243
|
+
version: versionNumber === void 0 ? null : String(versionNumber),
|
|
4244
|
+
createdAt: optionalString(row.createdAt),
|
|
4245
|
+
updatedAt: optionalString(version.createdAt),
|
|
4246
|
+
webUrl: webUi ? new URL(webUi.startsWith("/wiki/") ? webUi : `/wiki${webUi}`, source.siteUrl).toString() : new URL(
|
|
4247
|
+
`/wiki/spaces/${encodeURIComponent(source.key)}/pages/${encodeURIComponent(id)}`,
|
|
4248
|
+
source.siteUrl
|
|
4249
|
+
).toString()
|
|
4250
|
+
};
|
|
4251
|
+
}),
|
|
4252
|
+
nextCursor: confluenceCursor(objectRecord(payload._links).next)
|
|
1300
4253
|
};
|
|
1301
4254
|
}
|
|
1302
|
-
function
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
"
|
|
1316
|
-
"
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
"",
|
|
1323
|
-
"Goal recovery:",
|
|
1324
|
-
"- Treat this continuation as re-entry into the full objective, not as a request to perform one step and stop.",
|
|
1325
|
-
"- Do not rely on previous assistant claims of progress or completion; use them only to locate authoritative evidence.",
|
|
1326
|
-
"- If authoritative evidence already proves the full objective, call opengeni__goal_complete instead of manufacturing more work.",
|
|
1327
|
-
"- Before repeating a state-setting action, verify whether its desired state already holds. If it does, do not repeat it; continue reconciling the overall goal.",
|
|
1328
|
-
"",
|
|
1329
|
-
"Continuation behavior:",
|
|
1330
|
-
"- This goal persists across turns. A runtime boundary can end one turn without shrinking the objective; the next continuation resumes the same full objective.",
|
|
1331
|
-
"- Keep working until the requested end state is true and verified. Do not end the turn merely because one useful action completed, and do not redefine success around a smaller or easier task.",
|
|
1332
|
-
"- An incomplete-status report is not a substitute for continuing the work. If the remaining problem can be investigated or addressed within your current authority, continue that work in this turn rather than returning another equivalent status-only final.",
|
|
1333
|
-
"- Distinguish unfinished work from a blocker that actually requires human input or external change; use the existing waiting and blocked audits only when their conditions hold.",
|
|
1334
|
-
"- Temporary rough edges are acceptable while the work is moving in the right direction. Completion still requires the requested end state to be true and verified.",
|
|
1335
|
-
"",
|
|
1336
|
-
"Work from evidence:",
|
|
1337
|
-
"Use the current workspace and external state as authoritative. Conversation context can help locate relevant work, but it is not proof of the current state. Inspect the current state before relying on it. Improve, replace, or remove existing work as needed to satisfy the actual objective.",
|
|
4255
|
+
async function fetchAtlassianContent(source, entry, authorization, maxBytes) {
|
|
4256
|
+
if (!authorization) throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4257
|
+
const objectId = entry.externalObjectId.slice(entry.externalObjectId.indexOf(":") + 1);
|
|
4258
|
+
const markdown = source.kind === "jira_project" ? await jiraIssueMarkdown(source, objectId, entry, authorization) : await confluencePageMarkdown(source, objectId, entry, authorization);
|
|
4259
|
+
const bytes = new TextEncoder().encode(markdown);
|
|
4260
|
+
if (bytes.byteLength > maxBytes) throw new SyncFailure("content_too_large", false);
|
|
4261
|
+
return bytes;
|
|
4262
|
+
}
|
|
4263
|
+
async function jiraIssueMarkdown(source, issueId, entry, authorization) {
|
|
4264
|
+
const issueUrl = new URL(
|
|
4265
|
+
`${ATLASSIAN_API_BASE}/ex/jira/${encodeURIComponent(source.cloudId)}/rest/api/3/issue/${encodeURIComponent(issueId)}`
|
|
4266
|
+
);
|
|
4267
|
+
issueUrl.searchParams.set(
|
|
4268
|
+
"fields",
|
|
4269
|
+
"summary,description,status,issuetype,priority,assignee,reporter,labels,created,updated"
|
|
4270
|
+
);
|
|
4271
|
+
const issue = objectRecord(await atlassianJsonRequest(issueUrl, authorization, "Jira issue"));
|
|
4272
|
+
const fields = objectRecord(issue.fields);
|
|
4273
|
+
const lines = [
|
|
4274
|
+
`# ${entry.title}`,
|
|
1338
4275
|
"",
|
|
1339
|
-
|
|
1340
|
-
|
|
4276
|
+
`- Key: ${optionalString(issue.key) ?? entry.title}`,
|
|
4277
|
+
`- Type: ${optionalString(objectRecord(fields.issuetype).name) ?? "Unknown"}`,
|
|
4278
|
+
`- Status: ${optionalString(objectRecord(fields.status).name) ?? "Unknown"}`,
|
|
4279
|
+
`- Priority: ${optionalString(objectRecord(fields.priority).name) ?? "None"}`,
|
|
4280
|
+
`- Assignee: ${optionalString(objectRecord(fields.assignee).displayName) ?? "Unassigned"}`,
|
|
4281
|
+
`- Reporter: ${optionalString(objectRecord(fields.reporter).displayName) ?? "Unknown"}`,
|
|
4282
|
+
`- Updated: ${optionalString(fields.updated) ?? "Unknown"}`,
|
|
4283
|
+
`- Source: ${entry.sourceUri}`,
|
|
1341
4284
|
"",
|
|
1342
|
-
"
|
|
1343
|
-
"- Optimize each turn for movement toward the requested end state, not for the smallest stable-looking subset or easiest passing change.",
|
|
1344
|
-
"- Do not substitute a narrower, safer, smaller, merely compatible, or easier-to-test solution because it is more likely to pass current tests.",
|
|
1345
|
-
"- Treat alignment as movement toward the requested end state. An edit is aligned only if it makes the requested final state more true; useful-looking behavior that preserves a different end state is misaligned.",
|
|
4285
|
+
"## Description",
|
|
1346
4286
|
"",
|
|
1347
|
-
"
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
"
|
|
4287
|
+
adfToMarkdown(fields.description) || "No description."
|
|
4288
|
+
];
|
|
4289
|
+
const labels = Array.isArray(fields.labels) ? fields.labels.filter((value) => typeof value === "string") : [];
|
|
4290
|
+
if (labels.length > 0) lines.splice(9, 0, `- Labels: ${labels.join(", ")}`);
|
|
4291
|
+
const comments = await jiraComments(source, issueId, authorization);
|
|
4292
|
+
if (comments.length > 0) {
|
|
4293
|
+
lines.push("", "## Comments", "");
|
|
4294
|
+
for (const comment of comments) {
|
|
4295
|
+
const row = objectRecord(comment);
|
|
4296
|
+
const author = optionalString(objectRecord(row.author).displayName) ?? "Unknown author";
|
|
4297
|
+
const created = optionalString(row.created) ?? "Unknown date";
|
|
4298
|
+
lines.push(`### ${author} \xB7 ${created}`, "", adfToMarkdown(row.body), "");
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return lines.join("\n");
|
|
4302
|
+
}
|
|
4303
|
+
async function jiraComments(source, issueId, authorization) {
|
|
4304
|
+
const comments = [];
|
|
4305
|
+
let startAt = 0;
|
|
4306
|
+
for (let page = 0; page < 20; page += 1) {
|
|
4307
|
+
const url = new URL(
|
|
4308
|
+
`${ATLASSIAN_API_BASE}/ex/jira/${encodeURIComponent(source.cloudId)}/rest/api/3/issue/${encodeURIComponent(issueId)}/comment`
|
|
4309
|
+
);
|
|
4310
|
+
url.searchParams.set("startAt", String(startAt));
|
|
4311
|
+
url.searchParams.set("maxResults", "100");
|
|
4312
|
+
url.searchParams.set("orderBy", "created");
|
|
4313
|
+
const payload = objectRecord(await atlassianJsonRequest(url, authorization, "Jira comments"));
|
|
4314
|
+
const pageComments = Array.isArray(payload.comments) ? payload.comments : [];
|
|
4315
|
+
comments.push(...pageComments);
|
|
4316
|
+
const total = numberValue(payload.total) ?? comments.length;
|
|
4317
|
+
if (comments.length >= total || pageComments.length === 0) break;
|
|
4318
|
+
startAt += pageComments.length;
|
|
4319
|
+
}
|
|
4320
|
+
return comments;
|
|
4321
|
+
}
|
|
4322
|
+
async function confluencePageMarkdown(source, pageId, entry, authorization) {
|
|
4323
|
+
const pageUrl = new URL(
|
|
4324
|
+
`${ATLASSIAN_API_BASE}/ex/confluence/${encodeURIComponent(source.cloudId)}/wiki/api/v2/pages/${encodeURIComponent(pageId)}`
|
|
4325
|
+
);
|
|
4326
|
+
pageUrl.searchParams.set("body-format", "storage");
|
|
4327
|
+
const page = objectRecord(await atlassianJsonRequest(pageUrl, authorization, "Confluence page"));
|
|
4328
|
+
const storage = objectRecord(objectRecord(page.body).storage);
|
|
4329
|
+
const lines = [
|
|
4330
|
+
`# ${entry.title}`,
|
|
1354
4331
|
"",
|
|
1355
|
-
|
|
4332
|
+
`- Space: ${source.name} (${source.key})`,
|
|
4333
|
+
`- Updated: ${entry.modifiedTime ?? "Unknown"}`,
|
|
4334
|
+
`- Source: ${entry.sourceUri}`,
|
|
1356
4335
|
"",
|
|
1357
|
-
|
|
1358
|
-
...childNoticeGuidance,
|
|
1359
|
-
"Blocked audit:",
|
|
1360
|
-
"- Do not call opengeni__goal_pause the first time a blocker appears.",
|
|
1361
|
-
"- Pause only when the same blocking condition has repeated for at least three consecutive goal turns and meaningful progress is impossible without user input or an external-state change.",
|
|
1362
|
-
"- Do not pause merely because the work is hard, slow, uncertain, incomplete, or would benefit from clarification.",
|
|
1363
|
-
"- Once that threshold is satisfied, call opengeni__goal_pause with the concrete blocker instead of repeatedly reporting it while leaving the goal active.",
|
|
4336
|
+
"## Content",
|
|
1364
4337
|
"",
|
|
1365
|
-
|
|
1366
|
-
]
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
4338
|
+
optionalString(storage.value) ?? "No content."
|
|
4339
|
+
];
|
|
4340
|
+
const comments = await confluenceFooterComments(source, pageId, authorization);
|
|
4341
|
+
if (comments.length > 0) {
|
|
4342
|
+
lines.push("", "## Comments", "");
|
|
4343
|
+
for (const comment of comments) {
|
|
4344
|
+
const row = objectRecord(comment);
|
|
4345
|
+
const body = objectRecord(objectRecord(row.body).storage);
|
|
4346
|
+
const version = objectRecord(row.version);
|
|
4347
|
+
lines.push(
|
|
4348
|
+
`### Comment \xB7 ${optionalString(version.createdAt) ?? "Unknown date"}`,
|
|
4349
|
+
"",
|
|
4350
|
+
optionalString(body.value) ?? "",
|
|
4351
|
+
""
|
|
4352
|
+
);
|
|
4353
|
+
}
|
|
1371
4354
|
}
|
|
1372
|
-
return
|
|
4355
|
+
return lines.join("\n");
|
|
1373
4356
|
}
|
|
1374
|
-
async function
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
4357
|
+
async function confluenceFooterComments(source, pageId, authorization) {
|
|
4358
|
+
const comments = [];
|
|
4359
|
+
let cursor = null;
|
|
4360
|
+
for (let page = 0; page < 20; page += 1) {
|
|
4361
|
+
const url = new URL(
|
|
4362
|
+
`${ATLASSIAN_API_BASE}/ex/confluence/${encodeURIComponent(source.cloudId)}/wiki/api/v2/pages/${encodeURIComponent(pageId)}/footer-comments`
|
|
4363
|
+
);
|
|
4364
|
+
url.searchParams.set("body-format", "storage");
|
|
4365
|
+
url.searchParams.set("limit", "100");
|
|
4366
|
+
if (cursor) url.searchParams.set("cursor", cursor);
|
|
4367
|
+
const payload = objectRecord(
|
|
4368
|
+
await atlassianJsonRequest(url, authorization, "Confluence page comments")
|
|
4369
|
+
);
|
|
4370
|
+
const results = Array.isArray(payload.results) ? payload.results : [];
|
|
4371
|
+
comments.push(...results);
|
|
4372
|
+
cursor = confluenceCursor(objectRecord(payload._links).next);
|
|
4373
|
+
if (!cursor) break;
|
|
4374
|
+
}
|
|
4375
|
+
return comments;
|
|
4376
|
+
}
|
|
4377
|
+
async function atlassianJsonRequest(url, authorization, label) {
|
|
4378
|
+
const response = await sourceFetch(url, {
|
|
4379
|
+
headers: { authorization, accept: "application/json" },
|
|
4380
|
+
redirect: "error",
|
|
4381
|
+
signal: AbortSignal.timeout(2e4)
|
|
4382
|
+
});
|
|
4383
|
+
if (!response.ok) {
|
|
4384
|
+
await response.body?.cancel().catch(() => void 0);
|
|
4385
|
+
if (response.status === 401 || response.status === 403) {
|
|
4386
|
+
throw new SyncFailure("connection_reconnect_required", true, true);
|
|
4387
|
+
}
|
|
4388
|
+
throw new SyncFailure(
|
|
4389
|
+
"provider_unavailable",
|
|
4390
|
+
response.status >= 500 || response.status === 429
|
|
4391
|
+
);
|
|
1380
4392
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
4393
|
+
return await readResponseJsonBounded(response, ATLASSIAN_JSON_MAX_BYTES, label);
|
|
4394
|
+
}
|
|
4395
|
+
function confluenceCursor(value) {
|
|
4396
|
+
const next = optionalString(value);
|
|
4397
|
+
if (!next) return null;
|
|
4398
|
+
const url = new URL(next, ATLASSIAN_API_BASE);
|
|
4399
|
+
const cursor = url.searchParams.get("cursor");
|
|
4400
|
+
if (!cursor || cursor.length > 4096 || /[\u0000-\u001f]/.test(cursor)) {
|
|
4401
|
+
throw new Error("invalid Confluence cursor");
|
|
4402
|
+
}
|
|
4403
|
+
return cursor;
|
|
4404
|
+
}
|
|
4405
|
+
function adfToMarkdown(value) {
|
|
4406
|
+
const node = objectRecord(value);
|
|
4407
|
+
const type = optionalString(node.type);
|
|
4408
|
+
const children = Array.isArray(node.content) ? node.content.map(adfToMarkdown).join("") : "";
|
|
4409
|
+
if (type === "text") {
|
|
4410
|
+
let text = typeof node.text === "string" ? node.text : "";
|
|
4411
|
+
const marks = Array.isArray(node.marks) ? node.marks : [];
|
|
4412
|
+
for (const rawMark of marks) {
|
|
4413
|
+
const mark = objectRecord(rawMark);
|
|
4414
|
+
const markType = optionalString(mark.type);
|
|
4415
|
+
if (markType === "code") text = `\`${text}\``;
|
|
4416
|
+
if (markType === "strong") text = `**${text}**`;
|
|
4417
|
+
if (markType === "em") text = `*${text}*`;
|
|
4418
|
+
if (markType === "strike") text = `~~${text}~~`;
|
|
4419
|
+
if (markType === "link") {
|
|
4420
|
+
const href = optionalString(objectRecord(mark.attrs).href);
|
|
4421
|
+
if (href) text = `[${text}](${href})`;
|
|
1401
4422
|
}
|
|
1402
4423
|
}
|
|
4424
|
+
return text;
|
|
4425
|
+
}
|
|
4426
|
+
if (type === "paragraph") return `${children}
|
|
4427
|
+
|
|
4428
|
+
`;
|
|
4429
|
+
if (type === "heading") {
|
|
4430
|
+
const level = Math.min(Math.max(numberValue(objectRecord(node.attrs).level) ?? 2, 1), 6);
|
|
4431
|
+
return `${"#".repeat(level)} ${children.trim()}
|
|
4432
|
+
|
|
4433
|
+
`;
|
|
4434
|
+
}
|
|
4435
|
+
if (type === "hardBreak") return " \n";
|
|
4436
|
+
if (type === "rule") return "---\n\n";
|
|
4437
|
+
if (type === "blockquote") {
|
|
4438
|
+
return `${children.trim().split("\n").map((line) => `> ${line}`).join("\n")}
|
|
4439
|
+
|
|
4440
|
+
`;
|
|
4441
|
+
}
|
|
4442
|
+
if (type === "bulletList") {
|
|
4443
|
+
return `${children.trimEnd()}
|
|
4444
|
+
`;
|
|
1403
4445
|
}
|
|
4446
|
+
if (type === "orderedList") {
|
|
4447
|
+
return `${children.trimEnd()}
|
|
4448
|
+
`;
|
|
4449
|
+
}
|
|
4450
|
+
if (type === "listItem") {
|
|
4451
|
+
return `- ${children.trim().replaceAll("\n", "\n ")}
|
|
4452
|
+
`;
|
|
4453
|
+
}
|
|
4454
|
+
if (type === "codeBlock") return `\`\`\`
|
|
4455
|
+
${children.trimEnd()}
|
|
4456
|
+
\`\`\`
|
|
4457
|
+
|
|
4458
|
+
`;
|
|
4459
|
+
if (type === "mention") return `@${optionalString(objectRecord(node.attrs).text) ?? "user"}`;
|
|
4460
|
+
if (type === "emoji") return optionalString(objectRecord(node.attrs).text) ?? "";
|
|
4461
|
+
if (type === "inlineCard") {
|
|
4462
|
+
const url = optionalString(objectRecord(node.attrs).url);
|
|
4463
|
+
return url ? `[${url}](${url})` : "";
|
|
4464
|
+
}
|
|
4465
|
+
return children;
|
|
4466
|
+
}
|
|
4467
|
+
function objectRecord(value) {
|
|
4468
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4469
|
+
}
|
|
4470
|
+
function requiredAtlassianString(value, label) {
|
|
4471
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`invalid_atlassian_${label}`);
|
|
4472
|
+
return value;
|
|
4473
|
+
}
|
|
4474
|
+
function deterministicUuid(value) {
|
|
4475
|
+
const bytes = createHash2("sha256").update(value).digest().subarray(0, 16);
|
|
4476
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
4477
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
4478
|
+
const hex = bytes.toString("hex");
|
|
4479
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
4480
|
+
}
|
|
4481
|
+
function safeFilename(value) {
|
|
4482
|
+
const normalized = value.normalize("NFKC").replace(/[\\/:*?"<>|\0-\x1f]/g, "_").trim();
|
|
4483
|
+
return (normalized || "document").slice(0, 240);
|
|
4484
|
+
}
|
|
4485
|
+
function requiredString(value, label) {
|
|
4486
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`invalid_google_drive_${label}`);
|
|
4487
|
+
return value;
|
|
4488
|
+
}
|
|
4489
|
+
function optionalString(value) {
|
|
4490
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
4491
|
+
}
|
|
4492
|
+
function numberValue(value) {
|
|
4493
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4494
|
+
}
|
|
4495
|
+
function boundedError(error) {
|
|
4496
|
+
return (error instanceof Error ? error.message : String(error)).slice(0, 1e3);
|
|
4497
|
+
}
|
|
4498
|
+
function entryMetadataHash(entry) {
|
|
4499
|
+
return createHash2("sha256").update(
|
|
4500
|
+
JSON.stringify({
|
|
4501
|
+
externalObjectId: entry.externalObjectId,
|
|
4502
|
+
externalVersionId: entry.externalVersionId,
|
|
4503
|
+
title: entry.title,
|
|
4504
|
+
mimeType: entry.mimeType,
|
|
4505
|
+
driveId: entry.driveId,
|
|
4506
|
+
modifiedTime: entry.modifiedTime,
|
|
4507
|
+
createdTime: entry.createdTime,
|
|
4508
|
+
sourceUri: entry.sourceUri
|
|
4509
|
+
})
|
|
4510
|
+
).digest("hex");
|
|
4511
|
+
}
|
|
4512
|
+
function inventoryLimit(reason) {
|
|
4513
|
+
if (reason === "api_request_limit") return "provider_requests";
|
|
4514
|
+
if (reason === "elapsed_time_limit") return "elapsed_time";
|
|
4515
|
+
if (reason === "item_limit") return "items";
|
|
4516
|
+
if (reason === "known_byte_limit") return "bytes";
|
|
1404
4517
|
return null;
|
|
1405
4518
|
}
|
|
1406
|
-
function
|
|
1407
|
-
|
|
1408
|
-
|
|
4519
|
+
function recordSyncHealthTelemetry(observability, provider, summary, outcome, failure) {
|
|
4520
|
+
observability.observeHistogram({
|
|
4521
|
+
name: "opengeni_knowledge_source_sync_terminal_batch_duration_seconds",
|
|
4522
|
+
help: "Terminal knowledge-source activity batch duration by provider and outcome.",
|
|
4523
|
+
labels: { provider, outcome },
|
|
4524
|
+
value: Math.max(0, summary.elapsedMs) / 1e3
|
|
4525
|
+
});
|
|
4526
|
+
if (summary.providerRequests > 0) {
|
|
4527
|
+
observability.incrementCounter({
|
|
4528
|
+
name: "opengeni_knowledge_source_sync_provider_requests_total",
|
|
4529
|
+
help: "Logical provider requests consumed by knowledge-source synchronization.",
|
|
4530
|
+
labels: { provider, outcome },
|
|
4531
|
+
amount: summary.providerRequests
|
|
4532
|
+
});
|
|
4533
|
+
}
|
|
4534
|
+
if (summary.limitReached) {
|
|
4535
|
+
observability.incrementCounter({
|
|
4536
|
+
name: "opengeni_knowledge_source_sync_limit_hits_total",
|
|
4537
|
+
help: "Knowledge-source synchronization runs stopped by an explicit resource budget.",
|
|
4538
|
+
labels: { provider, limit: summary.limitReached }
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
if (summary.reconnectRequired) {
|
|
4542
|
+
observability.incrementCounter({
|
|
4543
|
+
name: "opengeni_knowledge_source_sync_reconnect_required_total",
|
|
4544
|
+
help: "Knowledge-source synchronization failures requiring connector reconnection.",
|
|
4545
|
+
labels: { provider }
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
4548
|
+
if (failure) {
|
|
4549
|
+
observability.incrementCounter({
|
|
4550
|
+
name: "opengeni_knowledge_source_sync_failures_total",
|
|
4551
|
+
help: "Knowledge-source synchronization terminal failures by bounded reason.",
|
|
4552
|
+
labels: {
|
|
4553
|
+
provider,
|
|
4554
|
+
reason: failure.code,
|
|
4555
|
+
retryable: failure.retryable ? "true" : "false"
|
|
4556
|
+
}
|
|
4557
|
+
});
|
|
4558
|
+
observability.warn("Knowledge source sync failed", {
|
|
4559
|
+
provider,
|
|
4560
|
+
outcome: failure.retryable ? "retryable_failed" : "failed",
|
|
4561
|
+
reason: failure.code,
|
|
4562
|
+
durationMs: summary.elapsedMs
|
|
4563
|
+
});
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
observability.info("Knowledge source sync completed", {
|
|
4567
|
+
provider,
|
|
4568
|
+
outcome: "succeeded",
|
|
4569
|
+
durationMs: summary.elapsedMs
|
|
4570
|
+
});
|
|
1409
4571
|
}
|
|
4572
|
+
var SyncFailure = class extends Error {
|
|
4573
|
+
constructor(code, retryable, reconnectRequired = false) {
|
|
4574
|
+
super(code);
|
|
4575
|
+
this.code = code;
|
|
4576
|
+
this.retryable = retryable;
|
|
4577
|
+
this.reconnectRequired = reconnectRequired;
|
|
4578
|
+
this.name = "KnowledgeSourceSyncFailure";
|
|
4579
|
+
}
|
|
4580
|
+
};
|
|
1410
4581
|
|
|
1411
4582
|
// src/activities/sandbox-images.ts
|
|
1412
4583
|
import {
|
|
@@ -2557,18 +5728,18 @@ async function resumeBoxForTurn(services, ids, kind, holderId) {
|
|
|
2557
5728
|
void release().catch(() => void 0);
|
|
2558
5729
|
};
|
|
2559
5730
|
holderLivenessTimer = setInterval(() => {
|
|
2560
|
-
const
|
|
2561
|
-
if (
|
|
5731
|
+
const heartbeat2 = holderLeaseHeartbeat;
|
|
5732
|
+
if (heartbeat2) {
|
|
2562
5733
|
void heartbeatLeaseHolderStatus(db, {
|
|
2563
5734
|
accountId: ids.accountId,
|
|
2564
5735
|
workspaceId: ids.workspaceId,
|
|
2565
5736
|
sandboxGroupId: ids.sandboxGroupId,
|
|
2566
5737
|
kind,
|
|
2567
5738
|
holderId,
|
|
2568
|
-
expectedEpoch:
|
|
2569
|
-
leaseTtlMs:
|
|
5739
|
+
expectedEpoch: heartbeat2.expectedEpoch,
|
|
5740
|
+
leaseTtlMs: heartbeat2.leaseTtlMs
|
|
2570
5741
|
}).then((status) => {
|
|
2571
|
-
if (
|
|
5742
|
+
if (heartbeat2 !== holderLeaseHeartbeat) return;
|
|
2572
5743
|
if (!status.holderAlive) {
|
|
2573
5744
|
releaseDeadHolder(status.fence);
|
|
2574
5745
|
return;
|
|
@@ -2590,7 +5761,7 @@ async function resumeBoxForTurn(services, ids, kind, holderId) {
|
|
|
2590
5761
|
kind,
|
|
2591
5762
|
holderId
|
|
2592
5763
|
}).then((touched) => {
|
|
2593
|
-
if (
|
|
5764
|
+
if (heartbeat2 !== holderLeaseHeartbeat) return;
|
|
2594
5765
|
if (!touched) {
|
|
2595
5766
|
releaseDeadHolder("holder_gone");
|
|
2596
5767
|
return;
|
|
@@ -4390,8 +7561,8 @@ import {
|
|
|
4390
7561
|
IncidentTelemetryPreflight,
|
|
4391
7562
|
stableJson
|
|
4392
7563
|
} from "@opengeni/contracts";
|
|
4393
|
-
import { createHash } from "crypto";
|
|
4394
|
-
import { isDeepStrictEqual as
|
|
7564
|
+
import { createHash as createHash3 } from "crypto";
|
|
7565
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
4395
7566
|
var INCIDENT_TELEMETRY_AUTHORITY_FENCE_LINEAGE_KEY = "incidentTelemetryAuthorityFence";
|
|
4396
7567
|
function incidentTelemetryPreflightDeclaration(agentConfig, incidentTriggered) {
|
|
4397
7568
|
const rawConfig = agentConfig;
|
|
@@ -4424,7 +7595,7 @@ function evaluateIncidentTelemetryPreflight(input) {
|
|
|
4424
7595
|
return blocked("incident_preflight_metadata_missing");
|
|
4425
7596
|
}
|
|
4426
7597
|
if (preflight.requiredResources.some(
|
|
4427
|
-
(required) => !input.responder.resources.some((selected) =>
|
|
7598
|
+
(required) => !input.responder.resources.some((selected) => isDeepStrictEqual3(selected, required))
|
|
4428
7599
|
)) {
|
|
4429
7600
|
return blocked("incident_responder_under_capable");
|
|
4430
7601
|
}
|
|
@@ -4514,9 +7685,9 @@ function incidentTelemetryAuthorityFence(input) {
|
|
|
4514
7685
|
return {
|
|
4515
7686
|
version: 2,
|
|
4516
7687
|
toolPolicyVersion: responder.toolPolicyVersion,
|
|
4517
|
-
responderDigest:
|
|
4518
|
-
taskDigest:
|
|
4519
|
-
alertSelectorDigest:
|
|
7688
|
+
responderDigest: createHash3("sha256").update(stableJson(normalized)).digest("hex"),
|
|
7689
|
+
taskDigest: createHash3("sha256").update(stableJson(task)).digest("hex"),
|
|
7690
|
+
alertSelectorDigest: createHash3("sha256").update(stableJson(selectorEntries)).digest("hex")
|
|
4520
7691
|
};
|
|
4521
7692
|
}
|
|
4522
7693
|
function parseIncidentTelemetryAuthorityFence(lineage) {
|
|
@@ -4570,7 +7741,7 @@ import { defaultSessionMcpServerIds, resolveSessionToolPolicy } from "@opengeni/
|
|
|
4570
7741
|
import {
|
|
4571
7742
|
getRig,
|
|
4572
7743
|
getScheduledScopedRigVersionMetadata,
|
|
4573
|
-
getScheduledTaskRunAcceptedExecution,
|
|
7744
|
+
getScheduledTaskRunAcceptedExecution as getScheduledTaskRunAcceptedExecution2,
|
|
4574
7745
|
getVariableSet,
|
|
4575
7746
|
listEnabledMcpCapabilityServerIds,
|
|
4576
7747
|
listInstalledApiIntegrationServerIdsForDelegations,
|
|
@@ -4744,7 +7915,7 @@ async function validateIncidentTelemetrySystemUpdateAuthority(input) {
|
|
|
4744
7915
|
};
|
|
4745
7916
|
if (!fence || typeof taskId !== "string" || !runId) return await reject();
|
|
4746
7917
|
const session = await requireSession2(input.db, input.workspaceId, input.sessionId);
|
|
4747
|
-
const acceptedExecution = await
|
|
7918
|
+
const acceptedExecution = await getScheduledTaskRunAcceptedExecution2(input.db, {
|
|
4748
7919
|
workspaceId: input.workspaceId,
|
|
4749
7920
|
runId
|
|
4750
7921
|
});
|
|
@@ -5287,7 +8458,7 @@ async function drainAttemptOwnedSandboxWriters(input) {
|
|
|
5287
8458
|
}
|
|
5288
8459
|
async function releaseTurnSandboxAfterWriterDrain(sandbox, options = {}) {
|
|
5289
8460
|
const maxAttempts = Math.max(1, options.maxAttempts ?? 3);
|
|
5290
|
-
const
|
|
8461
|
+
const retryDelayMs2 = Math.max(0, options.retryDelayMs ?? 50);
|
|
5291
8462
|
const wait = options.wait ?? sleep2;
|
|
5292
8463
|
let lastError;
|
|
5293
8464
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
@@ -5297,7 +8468,7 @@ async function releaseTurnSandboxAfterWriterDrain(sandbox, options = {}) {
|
|
|
5297
8468
|
} catch (error) {
|
|
5298
8469
|
lastError = error;
|
|
5299
8470
|
if (attempt < maxAttempts) {
|
|
5300
|
-
await wait(
|
|
8471
|
+
await wait(retryDelayMs2 * attempt);
|
|
5301
8472
|
}
|
|
5302
8473
|
}
|
|
5303
8474
|
}
|
|
@@ -5445,7 +8616,7 @@ import {
|
|
|
5445
8616
|
FileResourceRef,
|
|
5446
8617
|
resourceMountPath
|
|
5447
8618
|
} from "@opengeni/contracts";
|
|
5448
|
-
import { createHash as
|
|
8619
|
+
import { createHash as createHash4 } from "crypto";
|
|
5449
8620
|
import {
|
|
5450
8621
|
getActiveSessionHistoryItemsPaged,
|
|
5451
8622
|
getFilesForSubject,
|
|
@@ -5563,7 +8734,7 @@ async function modelAttachmentContentForFiles(files, readFileBytes, options = {}
|
|
|
5563
8734
|
});
|
|
5564
8735
|
continue;
|
|
5565
8736
|
}
|
|
5566
|
-
if (
|
|
8737
|
+
if (createHash4("sha256").update(bytes).digest("hex") !== checksum) {
|
|
5567
8738
|
console.error("model attachment checksum did not match finalized metadata", {
|
|
5568
8739
|
fileId: file.id
|
|
5569
8740
|
});
|
|
@@ -5741,7 +8912,8 @@ async function turnInput(db, runtime, agent, trigger, options) {
|
|
|
5741
8912
|
].join("\n") : void 0,
|
|
5742
8913
|
options.unavailableSandboxFilesNote,
|
|
5743
8914
|
options.runCredentialsNote,
|
|
5744
|
-
options.mcpAvailabilityNote
|
|
8915
|
+
options.mcpAvailabilityNote,
|
|
8916
|
+
options.knowledgeSourcePreparationNote
|
|
5745
8917
|
);
|
|
5746
8918
|
if (trigger.type === "user.message") {
|
|
5747
8919
|
const payload = trigger.payload;
|
|
@@ -6669,8 +9841,10 @@ export {
|
|
|
6669
9841
|
signalPendingCodexCapacityWakeTargets,
|
|
6670
9842
|
armAndReconcileCodexCapacityWait,
|
|
6671
9843
|
createCodexCapacityActivities,
|
|
9844
|
+
createDocumentActivities,
|
|
6672
9845
|
createGoalActivities,
|
|
6673
9846
|
withFirstPartyTools,
|
|
9847
|
+
createKnowledgeSourceSyncActivities,
|
|
6674
9848
|
rigProviderImageSourceImage,
|
|
6675
9849
|
settingsWithPackSandboxImage,
|
|
6676
9850
|
resolveWorkspacePackRuntime,
|
|
@@ -6761,4 +9935,4 @@ export {
|
|
|
6761
9935
|
codexUsageLimitFailurePayload,
|
|
6762
9936
|
CODEX_USAGE_LIMIT_MAX_RESUME_MS
|
|
6763
9937
|
};
|
|
6764
|
-
//# sourceMappingURL=chunk-
|
|
9938
|
+
//# sourceMappingURL=chunk-CYITU5UH.js.map
|