@codemem/server 0.43.2 → 0.44.0-beta.1

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
3
- import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, finishLegacyTeamSetupActivation, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectLegacyTeamSetupActivation, inviteTokenDigest, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, requestJson, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
@@ -10,7 +10,7 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
10
10
  import { homedir, hostname, networkInterfaces } from "node:os";
11
11
  import { createHash, randomUUID } from "node:crypto";
12
12
  //#region src/middleware.ts
13
- var LOOPBACK_HOSTS = new Set([
13
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
14
14
  "127.0.0.1",
15
15
  "localhost",
16
16
  "::1"
@@ -32,7 +32,7 @@ function isLoopbackOrigin(origin) {
32
32
  return LOOPBACK_HOSTS.has(hostname);
33
33
  }
34
34
  /** HTTP methods that mutate state and require origin validation. */
35
- var UNSAFE_METHODS = new Set([
35
+ var UNSAFE_METHODS = /* @__PURE__ */ new Set([
36
36
  "POST",
37
37
  "DELETE",
38
38
  "PATCH",
@@ -167,20 +167,20 @@ function createInMemoryRequestRateLimiter(windowMs = 6e4) {
167
167
  * codemem/viewer_routes/config.py, scoped to the TS runtime's current needs.
168
168
  */
169
169
  var REDACTED_VALUE = "[redacted]";
170
- var RUNTIMES = new Set([
170
+ var RUNTIMES = /* @__PURE__ */ new Set([
171
171
  "api_http",
172
172
  "claude_sidecar",
173
173
  "codex_sidecar"
174
174
  ]);
175
- var AUTH_SOURCES = new Set([
175
+ var AUTH_SOURCES = /* @__PURE__ */ new Set([
176
176
  "auto",
177
177
  "env",
178
178
  "file",
179
179
  "command",
180
180
  "none"
181
181
  ]);
182
- var HOT_RELOAD_KEYS = new Set(["raw_events_sweeper_interval_s"]);
183
- var PROTECTED_WRITE_KEYS = new Set([
182
+ var HOT_RELOAD_KEYS = /* @__PURE__ */ new Set(["raw_events_sweeper_interval_s"]);
183
+ var PROTECTED_WRITE_KEYS = /* @__PURE__ */ new Set([
184
184
  "claude_command",
185
185
  "codex_command",
186
186
  "observer_base_url",
@@ -189,13 +189,13 @@ var PROTECTED_WRITE_KEYS = new Set([
189
189
  "observer_headers",
190
190
  "sync_coordinator_url"
191
191
  ]);
192
- var SECRET_CONFIG_KEYS = new Set([
192
+ var SECRET_CONFIG_KEYS = /* @__PURE__ */ new Set([
193
193
  "observer_auth_file",
194
194
  "observer_auth_command",
195
195
  "observer_headers",
196
196
  "sync_coordinator_admin_secret"
197
197
  ]);
198
- var REMOVED_CONFIG_KEYS = new Set(["observer_rich_openai_use_responses"]);
198
+ var REMOVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["observer_rich_openai_use_responses"]);
199
199
  var ALLOWED_KEYS = [
200
200
  "claude_command",
201
201
  "codex_command",
@@ -489,7 +489,7 @@ function configRoutes(opts = {}) {
489
489
  const effectiveChangedKeys = ALLOWED_KEYS.filter((key) => !configValuesEqual(beforeEffective[key], afterEffective[key]));
490
490
  const envOverrides = getCodememEnvOverrides();
491
491
  const ignoredByEnvKeys = savedChangedKeys.filter((key) => !effectiveChangedKeys.includes(key) && key in envOverrides);
492
- const hotReloadedKeys = applyRuntimeEffects([...new Set([
492
+ const hotReloadedKeys = applyRuntimeEffects([.../* @__PURE__ */ new Set([
493
493
  ...touchedKeys,
494
494
  ...savedChangedKeys,
495
495
  ...effectiveChangedKeys
@@ -689,7 +689,7 @@ function attachResolvedIdentityFieldsUnsafe(store, items) {
689
689
  AND TRIM(COALESCE(pinned_fingerprint, '')) <> ''`);
690
690
  const peerByDevice = new Map(peers.map((peer) => [peer.peer_device_id, peer]));
691
691
  const peerActorIds = peers.map((peer) => String(peer.actor_id ?? "").trim()).filter((actorId) => actorId.length > 0);
692
- const actorRows = batchedLookup(store, [...new Set([...actorIds, ...peerActorIds])], (placeholders) => `SELECT actor_id, display_name, status, merged_into_actor_id FROM actors
692
+ const actorRows = batchedLookup(store, [.../* @__PURE__ */ new Set([...actorIds, ...peerActorIds])], (placeholders) => `SELECT actor_id, display_name, status, merged_into_actor_id FROM actors
693
693
  WHERE actor_id IN (${placeholders})`);
694
694
  const devices = batchedLookup(store, deviceIds, (placeholders) => `SELECT device_id, display_name FROM identity_devices
695
695
  WHERE device_id IN (${placeholders}) AND status = 'active'`);
@@ -1266,7 +1266,11 @@ function observerStatusRoutes(deps) {
1266
1266
  //#endregion
1267
1267
  //#region src/routes/target-validation.ts
1268
1268
  var IDENTITY_TARGET_KEYS = new Set(VIEWER_IDENTITY_TARGET_KEYS);
1269
- var BOOLEAN_IDENTITY_TARGET_KEYS = new Set(["actor_id_present", "embedding_disabled"]);
1269
+ var BOOLEAN_IDENTITY_TARGET_KEYS = /* @__PURE__ */ new Set([
1270
+ "actor_id_present",
1271
+ "embedding_disabled",
1272
+ "embedding_offline"
1273
+ ]);
1270
1274
  function invalidRequest$1(message) {
1271
1275
  return {
1272
1276
  ok: false,
@@ -1330,12 +1334,12 @@ function validateViewerTarget(store, payload, options = {}) {
1330
1334
  }
1331
1335
  //#endregion
1332
1336
  //#region src/routes/pack.ts
1333
- var MAX_LEDGER_PAYLOAD_BYTES = 16 * 1024;
1337
+ var MAX_LEDGER_PAYLOAD_BYTES = 16384;
1334
1338
  var MAX_METADATA_FIELD_CHARS = 512;
1335
1339
  var MAX_WORKING_SET_FILES = 50;
1336
1340
  var MAX_WORKING_SET_PATH_CHARS = 512;
1337
1341
  var INVALID_JSON = Symbol("invalid-json");
1338
- var PACK_KEYS = new Set([
1342
+ var PACK_KEYS = /* @__PURE__ */ new Set([
1339
1343
  "context",
1340
1344
  "limit",
1341
1345
  "token_budget",
@@ -1349,7 +1353,7 @@ var PACK_KEYS = new Set([
1349
1353
  "identity_target",
1350
1354
  "attempt"
1351
1355
  ]);
1352
- var LEDGER_KEYS = new Set([
1356
+ var LEDGER_KEYS = /* @__PURE__ */ new Set([
1353
1357
  "action",
1354
1358
  "attempt_id",
1355
1359
  "started_at",
@@ -1366,7 +1370,7 @@ var LEDGER_KEYS = new Set([
1366
1370
  "db_path",
1367
1371
  "identity_target"
1368
1372
  ]);
1369
- var ATTEMPT_KEYS = new Set([
1373
+ var ATTEMPT_KEYS = /* @__PURE__ */ new Set([
1370
1374
  "attempt_id",
1371
1375
  "started_at",
1372
1376
  "source",
@@ -1375,7 +1379,7 @@ var ATTEMPT_KEYS = new Set([
1375
1379
  "prompt_number",
1376
1380
  "request_id"
1377
1381
  ]);
1378
- var FORBIDDEN_LEDGER_KEYS = new Set([
1382
+ var FORBIDDEN_LEDGER_KEYS = /* @__PURE__ */ new Set([
1379
1383
  "body",
1380
1384
  "context",
1381
1385
  "pack",
@@ -1698,7 +1702,7 @@ function isClaudeBoundaryEnvelope(envelope) {
1698
1702
  const hookEventName = meta.hook_event_name;
1699
1703
  return hookEventName === "SessionEnd" || hookEventName === "Stop";
1700
1704
  }
1701
- var SAFE_INGEST_VALIDATION_ERRORS = new Set([
1705
+ var SAFE_INGEST_VALIDATION_ERRORS = /* @__PURE__ */ new Set([
1702
1706
  "source must be string",
1703
1707
  "source is required",
1704
1708
  "source must use 1-64 letters, digits, dots, underscores, or hyphens",
@@ -2295,7 +2299,6 @@ var MAX_CONFIGURED_GROUPS = 25;
2295
2299
  var MAX_SCOPE_EVIDENCE_COORDINATORS = 100;
2296
2300
  var MAX_DEVICES = 500;
2297
2301
  var MAX_PROJECTS = 500;
2298
- var MAX_ACCESS_DELTA_ENTRIES = 1e4;
2299
2302
  var MAX_IDENTITY_CHOICES = 500;
2300
2303
  var MAX_COMPLETED_IDENTITY_CHOICES = MAX_DEVICES * 4;
2301
2304
  var MAX_PROJECT_MAPPING_CHOICES = 500;
@@ -2315,6 +2318,46 @@ var ATTEMPT_ID_PATTERN = /^legacy-team-attempt:[0-9a-f-]{36}$/u;
2315
2318
  var FINISH_DIGEST_PATTERN = /^legacy-team-activation-finish-v1:[0-9a-f]{64}$/u;
2316
2319
  var ACCESS_DELTA_DIGEST_PATTERN = /^legacy-team-access-delta:[0-9a-f]{64}$/u;
2317
2320
  var VIEWER_ACCESS_DELTA_DIGEST_PATTERN = /^legacy-team-viewer-access-delta-v1:[0-9a-f]{64}$/u;
2321
+ var activeCandidateMutations = /* @__PURE__ */ new WeakMap();
2322
+ function claimCandidateMutation(db, candidateRef) {
2323
+ let active = activeCandidateMutations.get(db);
2324
+ if (!active) {
2325
+ active = /* @__PURE__ */ new Map();
2326
+ activeCandidateMutations.set(db, active);
2327
+ }
2328
+ if (active.has(candidateRef)) return null;
2329
+ let settle = () => void 0;
2330
+ const settled = new Promise((resolve) => {
2331
+ settle = resolve;
2332
+ });
2333
+ active.set(candidateRef, settled);
2334
+ let released = false;
2335
+ return () => {
2336
+ if (released) return;
2337
+ released = true;
2338
+ active?.delete(candidateRef);
2339
+ settle();
2340
+ };
2341
+ }
2342
+ /** Resolves once the current holder of the candidate claim (if any) releases it. */
2343
+ function candidateMutationSettled(db, candidateRef) {
2344
+ return activeCandidateMutations.get(db)?.get(candidateRef) ?? Promise.resolve();
2345
+ }
2346
+ async function runCandidateMutation(db, candidateRef, operation) {
2347
+ const release = claimCandidateMutation(db, candidateRef);
2348
+ if (!release) return false;
2349
+ try {
2350
+ await operation();
2351
+ return true;
2352
+ } finally {
2353
+ release();
2354
+ }
2355
+ }
2356
+ var defaultCompletionDependencies = {
2357
+ create: coordinatorCreateLegacyTeamCompletionAction,
2358
+ get: coordinatorGetLegacyTeamCompletionAction,
2359
+ list: coordinatorListLegacyTeamCompletionsAction
2360
+ };
2318
2361
  var defaultSnapshotLoaderDependencies = {
2319
2362
  readConfig: readCoordinatorSyncConfig,
2320
2363
  listGroups: coordinatorListGroupsAction,
@@ -2326,19 +2369,45 @@ function safeCoordinatorError() {
2326
2369
  function isCoordinatorRosterTooLargeError(error) {
2327
2370
  return error instanceof Error && error.message === "coordinator_response_too_large";
2328
2371
  }
2372
+ function remoteCoordinatorHttpStatus(error) {
2373
+ if (!(error instanceof Error)) return null;
2374
+ const status = /Remote coordinator request failed \((\d{3})\):/u.exec(error.message)?.[1];
2375
+ return status ? Number(status) : null;
2376
+ }
2329
2377
  function isTransientCoordinatorReadError(error) {
2330
2378
  if (!(error instanceof Error)) return false;
2331
- const remoteStatus = /Remote coordinator request failed \((\d{3})\):/u.exec(error.message)?.[1];
2379
+ const remoteStatus = remoteCoordinatorHttpStatus(error);
2332
2380
  if (remoteStatus) return [
2333
- "408",
2334
- "429",
2335
- "500",
2336
- "502",
2337
- "503",
2338
- "504"
2381
+ 408,
2382
+ 429,
2383
+ 500,
2384
+ 502,
2385
+ 503,
2386
+ 504
2339
2387
  ].includes(remoteStatus);
2340
2388
  return error.name === "TimeoutError" || error.name === "AbortError" || /request_timeout|fetch failed|timed out|timeout/iu.test(error.message);
2341
2389
  }
2390
+ function isUnsupportedCompletionQuery(error) {
2391
+ const status = remoteCoordinatorHttpStatus(error);
2392
+ return status === 404 || status === 405;
2393
+ }
2394
+ /**
2395
+ * Batch completion decoding rejects the whole payload when any one record is
2396
+ * malformed or misattributed, so the failure cannot be pinned to a group.
2397
+ */
2398
+ function isUnattributableCompletionBatchError(error) {
2399
+ return error instanceof Error && (error.message === "coordinator_completion_response_malformed" || error.message === "coordinator_completion_group_mismatch");
2400
+ }
2401
+ async function listCompletionRecordsPerGroup(groupIds, list) {
2402
+ if (groupIds.length < 2) return [];
2403
+ const records = [];
2404
+ for (const groupId of groupIds) try {
2405
+ records.push(...await list([groupId]));
2406
+ } catch (error) {
2407
+ if (!isUnattributableCompletionBatchError(error)) throw error;
2408
+ }
2409
+ return records;
2410
+ }
2342
2411
  async function retryTransientCoordinatorRead(read, configuredTimeoutS, deadlineMs) {
2343
2412
  let lastError;
2344
2413
  for (let attempt = 0; attempt < 8; attempt += 1) {
@@ -2356,6 +2425,10 @@ async function retryTransientCoordinatorRead(read, configuredTimeoutS, deadlineM
2356
2425
  }
2357
2426
  throw lastError;
2358
2427
  }
2428
+ function remainingCoordinatorTimeoutS(configuredTimeoutS, deadlineMs, nowMs = Date.now()) {
2429
+ const remainingMs = deadlineMs - nowMs;
2430
+ return remainingMs <= 0 ? null : Math.max(.1, Math.min(configuredTimeoutS, remainingMs / 1e3));
2431
+ }
2359
2432
  function configuredGroupIds(groups) {
2360
2433
  const unique = [...new Set(groups.map((group) => group.trim()).filter(Boolean))];
2361
2434
  if (unique.length > MAX_CONFIGURED_GROUPS) throw safeCoordinatorError();
@@ -2456,7 +2529,7 @@ async function loadConfiguredLegacyTeamGroupSnapshotsWith(dependencies, options
2456
2529
  const groupDescriptors = legacyTeamCandidateGroupDescriptors(store, config.syncCoordinatorUrl, config.syncCoordinatorGroups);
2457
2530
  if (groupDescriptors.length === 0) throw safeCoordinatorError();
2458
2531
  const timeoutS = Math.max(1, config.syncCoordinatorTimeoutS);
2459
- const deadlineMs = Date.now() + COORDINATOR_ROSTER_READ_BUDGET_MS;
2532
+ const deadlineMs = options.deadlineMs ?? Date.now() + COORDINATOR_ROSTER_READ_BUDGET_MS;
2460
2533
  const requestedGroupDescriptors = options.candidateRef ? groupDescriptors.filter(({ coordinatorId, groupId }) => legacyTeamCandidateId(coordinatorId, groupId) === options.candidateRef) : groupDescriptors;
2461
2534
  const groups = await retryTransientCoordinatorRead((attemptTimeoutS) => dependencies.listGroups({
2462
2535
  remoteUrl,
@@ -2712,10 +2785,6 @@ function viewerSafeAccessDelta(candidateRef, delta, labels) {
2712
2785
  function viewerAccessDeltaDigest(delta) {
2713
2786
  return recipientPolicyDigest("legacy-team-viewer-access-delta-v1", delta);
2714
2787
  }
2715
- function requireBoundedAccessDelta(delta) {
2716
- const total = delta.teamChanges.length + delta.membershipChanges.length + delta.projectChanges.length + delta.recipientChanges.length + delta.deviceAccessChanges.length;
2717
- if (delta.teamChanges.length > MAX_ACCESS_DELTA_ENTRIES || delta.membershipChanges.length > MAX_ACCESS_DELTA_ENTRIES || delta.projectChanges.length > MAX_ACCESS_DELTA_ENTRIES || delta.recipientChanges.length > MAX_ACCESS_DELTA_ENTRIES || delta.deviceAccessChanges.length > MAX_ACCESS_DELTA_ENTRIES || total > MAX_ACCESS_DELTA_ENTRIES) throw new Error("legacy_team_setup_roster_too_large");
2718
- }
2719
2788
  var SAFE_CHOICE_LABEL_PATTERN = /^[\p{L}\p{N} '&,.()_-]*$/u;
2720
2789
  function normalizeChoiceLabelText(value) {
2721
2790
  return value.normalize("NFKC").replace(/\p{Cf}/gu, "").replace(/\p{Cc}/gu, " ").replace(/\s+/gu, " ").trim();
@@ -2853,10 +2922,10 @@ function projectMappingChoices(store) {
2853
2922
  excludePeerReceived: true
2854
2923
  }).filter((candidate) => !candidate.read_only && isLegacyTeamSetupProjectMappingIdentity(candidate.workspace_identity, store.db));
2855
2924
  } catch (error) {
2856
- if (error instanceof Error && (error.message === "project_scope_candidate_scan_too_large" || error.message === "project_scope_candidate_metadata_too_large")) throw new Error("legacy_team_setup_roster_too_large");
2925
+ if (error instanceof Error && (error.message === "project_scope_candidate_scan_too_large" || error.message === "project_scope_candidate_metadata_too_large")) throw new LocalProjectScanBudgetError(error);
2857
2926
  throw error;
2858
2927
  }
2859
- if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new Error("legacy_team_setup_roster_too_large");
2928
+ if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new LocalProjectScanBudgetError();
2860
2929
  return candidates.map((candidate) => ({
2861
2930
  projectIdentity: candidate.workspace_identity,
2862
2931
  sourceDisplayName: candidate.display_project
@@ -2919,7 +2988,7 @@ function viewerSafePreview(draft, safeDraft, preview) {
2919
2988
  };
2920
2989
  }
2921
2990
  function errorStatus$1(code) {
2922
- if (code === "team_setup_roster_unavailable") return 503;
2991
+ if (code === "team_setup_roster_unavailable" || code === "team_setup_completion_unavailable") return 503;
2923
2992
  if (code === "team_setup_failed") return 503;
2924
2993
  if (code === "team_setup_incomplete") return 400;
2925
2994
  return 409;
@@ -2928,6 +2997,160 @@ function apiErrorCode(error) {
2928
2997
  const code = legacyTeamSetupApiErrorCode(error);
2929
2998
  return code === "team_setup_projection_changed" ? "team_setup_conflict" : code;
2930
2999
  }
3000
+ function completionApiError(error) {
3001
+ const code = legacyTeamSetupApiErrorCode(error);
3002
+ return code === "team_setup_failed" ? "team_setup_completion_unavailable" : code;
3003
+ }
3004
+ var TeamSetupFinishError = class extends Error {
3005
+ reason;
3006
+ alreadyLogged;
3007
+ constructor(code, reason, alreadyLogged, cause) {
3008
+ super(code, cause === void 0 ? void 0 : { cause });
3009
+ this.reason = reason;
3010
+ this.alreadyLogged = alreadyLogged;
3011
+ this.name = "TeamSetupFinishError";
3012
+ }
3013
+ };
3014
+ /**
3015
+ * The generic `legacy_team_setup_roster_too_large` string is thrown by ten
3016
+ * sites for unrelated limits (coordinator roster size, draft device count,
3017
+ * local project choices). Only the local project-candidate scan is a
3018
+ * "this device's history is too large" condition, so it carries a distinct
3019
+ * type. The message is kept identical so the frozen API-code mapping and every
3020
+ * existing caller that matches on it are unchanged.
3021
+ */
3022
+ var LocalProjectScanBudgetError = class extends Error {
3023
+ constructor(cause) {
3024
+ super("legacy_team_setup_roster_too_large", cause === void 0 ? void 0 : { cause });
3025
+ this.name = "LocalProjectScanBudgetError";
3026
+ }
3027
+ };
3028
+ /**
3029
+ * Remote error codes the finish path can legitimately receive from the
3030
+ * coordinator. Anything else — including identifier-shaped values, which a
3031
+ * hostile or misconfigured upstream could use to smuggle a credential through
3032
+ * a JSON `error` field — is reduced to a fixed classification before logging.
3033
+ * A shape check is not an allowlist; only this set is echoed verbatim.
3034
+ */
3035
+ var LOGGABLE_REMOTE_ERROR_CODES = /* @__PURE__ */ new Set([
3036
+ "body_too_large",
3037
+ "completion_manifest_invalid",
3038
+ "completion_manifest_unavailable",
3039
+ "completion_not_found",
3040
+ "completion_query_invalid",
3041
+ "group_archived",
3042
+ "group_id_and_candidate_ref_required",
3043
+ "group_not_found",
3044
+ "invalid_json",
3045
+ "missing_admin_header",
3046
+ "not_found",
3047
+ "rate_limited",
3048
+ "response_too_large",
3049
+ "unauthorized"
3050
+ ]);
3051
+ function loggableRemoteCode(code) {
3052
+ if (LOGGABLE_REMOTE_ERROR_CODES.has(code)) return code;
3053
+ if (code.startsWith("non_json_response")) return "non_json_response";
3054
+ return "unclassified_remote_error";
3055
+ }
3056
+ /**
3057
+ * Local causes the finish path can produce. Same allowlist discipline as
3058
+ * remote codes: everything else, including transport messages that embed the
3059
+ * target URL, is classified rather than echoed.
3060
+ */
3061
+ var LOGGABLE_LOCAL_CAUSES = /* @__PURE__ */ new Set([
3062
+ "completion_conflict",
3063
+ "legacy_team_setup_roster_too_large",
3064
+ "project_scope_candidate_metadata_too_large",
3065
+ "project_scope_candidate_scan_too_large",
3066
+ "team_setup_completion_conflict",
3067
+ "team_setup_completion_invalid",
3068
+ "team_setup_completion_unavailable",
3069
+ "team_setup_confirmation_stale",
3070
+ "team_setup_roster_unavailable"
3071
+ ]);
3072
+ function loggableLocalCause(error) {
3073
+ if (LOGGABLE_LOCAL_CAUSES.has(error.message)) return error.message;
3074
+ if (error.name === "AbortError" || error.name === "TimeoutError") return error.name;
3075
+ if (/fetch failed/iu.test(error.message)) return "fetch_failed";
3076
+ if (/timed out|timeout/iu.test(error.message)) return "timed_out";
3077
+ return `unclassified_error:${error.name || "Error"}`;
3078
+ }
3079
+ function errorChain(error) {
3080
+ const chain = [];
3081
+ let current = error;
3082
+ while (current && !chain.includes(current)) {
3083
+ chain.push(current);
3084
+ current = current instanceof Error ? current.cause : void 0;
3085
+ }
3086
+ return chain;
3087
+ }
3088
+ function remoteFailureReason(remote, fallback) {
3089
+ if (remote.status === 404 && (remote.code === "not_found" || remote.code.startsWith("non_json"))) return "coordinator_route_missing";
3090
+ if (remote.status === 400 && remote.code === "completion_manifest_invalid") return "coordinator_rejected_manifest";
3091
+ return fallback;
3092
+ }
3093
+ function teamSetupFinishFailureDetails(error) {
3094
+ const chain = errorChain(error);
3095
+ const wrapper = chain.find((item) => item instanceof TeamSetupFinishError);
3096
+ const remote = chain.find((item) => item instanceof RemoteCoordinatorRequestError);
3097
+ if (remote) {
3098
+ const reason = remoteFailureReason(remote, wrapper?.reason);
3099
+ return {
3100
+ ...reason ? { reason } : {},
3101
+ cause: "remote coordinator request failed",
3102
+ status: remote.status,
3103
+ code: loggableRemoteCode(remote.code)
3104
+ };
3105
+ }
3106
+ if (chain.some((item) => item instanceof LegacyTeamSetupRosterCapacityError || item instanceof LocalProjectScanBudgetError)) return {
3107
+ reason: "local_candidate_scan_budget_exceeded",
3108
+ cause: "local project candidate scan exceeded budget"
3109
+ };
3110
+ const errorCause = chain.find((item) => item instanceof Error && !(item instanceof TeamSetupFinishError));
3111
+ const cause = errorCause ? loggableLocalCause(errorCause) : "unclassified_non_error";
3112
+ if (wrapper?.reason) return {
3113
+ reason: wrapper.reason,
3114
+ cause
3115
+ };
3116
+ if (errorCause && (errorCause.name === "AbortError" || errorCause.name === "TimeoutError" || /fetch failed|timed out|timeout/iu.test(errorCause.message))) return {
3117
+ reason: "coordinator_unreachable",
3118
+ cause
3119
+ };
3120
+ return { cause };
3121
+ }
3122
+ function warnTeamSetupFinishFailure(error, details, context) {
3123
+ if (errorChain(error).some((item) => item instanceof TeamSetupFinishError && item.alreadyLogged)) return;
3124
+ const safe = (value) => {
3125
+ let sanitized = String(value).replace(/[\r\n\t]+/gu, " ");
3126
+ for (const secret of context.redactValues ?? []) if (secret) sanitized = sanitized.replaceAll(secret, "[redacted]");
3127
+ return sanitized.replace(/https?:\/\/[^\s]+/giu, (value) => {
3128
+ try {
3129
+ const url = new URL(value);
3130
+ return `${url.protocol}//${url.host}`;
3131
+ } catch {
3132
+ return "[redacted-url]";
3133
+ }
3134
+ });
3135
+ };
3136
+ const fields = [
3137
+ `candidateRef=${safe(context.candidateRef)}`,
3138
+ `attemptId=${safe(context.attemptId)}`,
3139
+ `apiCode=${safe(context.apiCode)}`,
3140
+ `cause=${safe(details.cause)}`,
3141
+ ...details.reason ? [`reason=${safe(details.reason)}`] : [],
3142
+ ...details.status == null ? [] : [`status=${safe(details.status)}`],
3143
+ ...details.code == null ? [] : [`code=${safe(details.code)}`]
3144
+ ];
3145
+ console.warn(`[team-setup finish] ${fields.join(" ")}`);
3146
+ }
3147
+ function finishErrorResponse(code, details) {
3148
+ const reason = details.reason;
3149
+ return reason ? {
3150
+ error: code,
3151
+ reason
3152
+ } : { error: code };
3153
+ }
2931
3154
  async function parseBoundedJsonObject(c) {
2932
3155
  if (c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return { ok: false };
2933
3156
  const lengthHeader = c.req.header("content-length");
@@ -2991,7 +3214,7 @@ function detailResponse(store, draft, candidate = candidateFromDraft(draft)) {
2991
3214
  if (code === "team_setup_projection_changed") unavailableReason = "team_setup_conflict";
2992
3215
  else if (code !== "team_setup_incomplete") unavailableReason = code;
2993
3216
  }
2994
- if (preview) requireBoundedAccessDelta(preview.accessDelta);
3217
+ if (preview) requireLegacyTeamSetupAccessDeltaWithinLimit(preview.accessDelta);
2995
3218
  const safeDraft = viewerSafeDraft(store, draft);
2996
3219
  const safePreview = preview ? viewerSafePreview(draft, safeDraft, preview) : null;
2997
3220
  return projectLegacyTeamSetupView({
@@ -3019,9 +3242,12 @@ function finishResponse(candidateRef, result) {
3019
3242
  function teamSetupRoutes(options) {
3020
3243
  const app = new Hono();
3021
3244
  const loadSnapshots = options.loadLegacyTeamConfiguredGroupSnapshots ?? ((loadOptions) => loadConfiguredLegacyTeamGroupSnapshotsWith(options.snapshotLoaderDependencies ?? defaultSnapshotLoaderDependencies, loadOptions, options.getStore));
3022
- async function loadedSnapshots(candidateRef) {
3245
+ async function loadedSnapshots(candidateRef, deadlineMs) {
3023
3246
  try {
3024
- return await loadSnapshots(candidateRef ? { candidateRef } : void 0);
3247
+ return await loadSnapshots(candidateRef || deadlineMs != null ? {
3248
+ candidateRef,
3249
+ deadlineMs
3250
+ } : void 0);
3025
3251
  } catch (error) {
3026
3252
  if (error instanceof Error && error.message === "legacy_team_setup_roster_too_large") throw error;
3027
3253
  throw safeCoordinatorError();
@@ -3029,12 +3255,341 @@ function teamSetupRoutes(options) {
3029
3255
  }
3030
3256
  const loadedSummarySnapshots = createCachedSnapshotLoader(() => loadedSnapshots(), SUMMARY_SNAPSHOT_CACHE_TTL_MS);
3031
3257
  options.registerSummaryInvalidator?.(() => loadedSummarySnapshots.invalidate());
3258
+ const completionDependencies = options.completionDependencies === void 0 ? defaultCompletionDependencies : options.completionDependencies;
3259
+ /**
3260
+ * Reads the live coordinator configuration and the groups it currently
3261
+ * authorizes. Returns null when completion I/O is not configured or current
3262
+ * authorization cannot be proven.
3263
+ */
3264
+ function currentCompletionAuthorization() {
3265
+ const config = (options.readCoordinatorConfig ?? options.snapshotLoaderDependencies?.readConfig ?? readCoordinatorSyncConfig)();
3266
+ const remoteUrl = buildBaseUrl(config.syncCoordinatorUrl);
3267
+ if (!remoteUrl || !config.syncCoordinatorAdminSecret) return null;
3268
+ const configuredCoordinatorId = normalizedCoordinatorId(remoteUrl);
3269
+ if (!configuredCoordinatorId) return null;
3270
+ let currentGroupIds;
3271
+ try {
3272
+ currentGroupIds = new Set(configuredGroupIds(config.syncCoordinatorGroups));
3273
+ } catch {
3274
+ return null;
3275
+ }
3276
+ return {
3277
+ config,
3278
+ remoteUrl,
3279
+ configuredCoordinatorId,
3280
+ currentGroupIds
3281
+ };
3282
+ }
3283
+ function authorizesGroup(authorization, group) {
3284
+ return authorization.currentGroupIds.has(group.groupId) && normalizedCoordinatorId(group.coordinatorId) === authorization.configuredCoordinatorId;
3285
+ }
3286
+ /**
3287
+ * Configuration may change while completion-list or roster requests are in
3288
+ * flight. Re-prove authorization for the exact coordinator and group right
3289
+ * before applying canonical policy, as the finish publication path does.
3290
+ */
3291
+ function stillAuthorizesGroup(initial, group) {
3292
+ let current;
3293
+ try {
3294
+ current = currentCompletionAuthorization();
3295
+ } catch {
3296
+ return false;
3297
+ }
3298
+ return current !== null && current.configuredCoordinatorId === initial.configuredCoordinatorId && current.remoteUrl === initial.remoteUrl && current.config.syncCoordinatorAdminSecret === initial.config.syncCoordinatorAdminSecret && authorizesGroup(current, group);
3299
+ }
3300
+ function matchesGroupSnapshot(snapshot, group) {
3301
+ return snapshot.groupId === group.groupId && normalizedCoordinatorId(snapshot.coordinatorId) === normalizedCoordinatorId(group.coordinatorId);
3302
+ }
3303
+ function freshGroupKey(group) {
3304
+ return `${normalizedCoordinatorId(group.coordinatorId) ?? group.coordinatorId}\0${group.groupId}`;
3305
+ }
3306
+ /**
3307
+ * The snapshot loader keys candidates by the configured coordinator spelling,
3308
+ * while a persisted group may carry an equivalent but different spelling.
3309
+ * Ask for the roster under each spelling and match on normalized identity,
3310
+ * preserving the persisted candidate identity for everything else.
3311
+ */
3312
+ async function loadFreshGroupSnapshot(group, remoteUrl, deadlineMs) {
3313
+ const candidateRefs = [.../* @__PURE__ */ new Set([legacyTeamCandidateId(remoteUrl, group.groupId), legacyTeamCandidateId(group.coordinatorId, group.groupId)])];
3314
+ for (const candidateRef of candidateRefs) {
3315
+ const matches = (await loadedSnapshots(candidateRef, deadlineMs)).filter((snapshot) => matchesGroupSnapshot(snapshot, group));
3316
+ if (matches.length > 1) throw new Error("team_setup_completion_invalid");
3317
+ if (matches[0]) return matches[0];
3318
+ }
3319
+ }
3320
+ function completionRecordCandidateRef(record) {
3321
+ const manifest = record.manifest;
3322
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return null;
3323
+ const candidateRef = manifest.candidate_ref;
3324
+ return typeof candidateRef === "string" ? candidateRef : null;
3325
+ }
3326
+ function belongsToGroupCandidate(record, group) {
3327
+ const candidateRef = completionRecordCandidateRef(record);
3328
+ return candidateRef === null || candidateRef === legacyTeamCandidateId(group.coordinatorId, group.groupId);
3329
+ }
3330
+ /**
3331
+ * Applies a canonical manifest with authorization re-proven inside the same
3332
+ * serialized section that guards the apply. Checking before the locks would
3333
+ * let a group removed while reconciliation waited on another policy
3334
+ * mutation still be applied once the queues open.
3335
+ */
3336
+ async function applyCanonicalManifestIfStillAuthorized(store, authorization, group, manifest, freshRoster) {
3337
+ let outcome = "unauthorized";
3338
+ return await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => serializeRecipientPolicyPublicationMutation(store.db, () => serializeRecipientPolicyTeamMutation(store.db, manifest.team_id, () => serializeRecipientPolicyCoordinatorGroupMutation(store.db, group.groupId, async () => {
3339
+ if (!stillAuthorizesGroup(authorization, group)) return;
3340
+ await applyLegacyTeamSetupCompletionManifest(store.db, {
3341
+ coordinatorId: group.coordinatorId,
3342
+ groupId: group.groupId,
3343
+ manifest,
3344
+ freshRoster,
3345
+ recipientPolicyLocksHeld: {
3346
+ publication: true,
3347
+ team: true,
3348
+ coordinatorGroup: true
3349
+ }
3350
+ });
3351
+ outcome = "applied";
3352
+ })))) ? outcome : "claimed";
3353
+ }
3354
+ async function reconcileCompletions(store, groups, reconciliationOptions = {}) {
3355
+ if (!completionDependencies || groups.length === 0) return;
3356
+ let authorization;
3357
+ try {
3358
+ authorization = currentCompletionAuthorization();
3359
+ } catch (error) {
3360
+ throw new Error(completionApiError(error));
3361
+ }
3362
+ if (!authorization) return;
3363
+ const { config, remoteUrl } = authorization;
3364
+ const scopedGroups = groups.filter((group) => authorizesGroup(authorization, group));
3365
+ if (scopedGroups.length === 0) return;
3366
+ const timeoutS = Math.max(1, config.syncCoordinatorTimeoutS);
3367
+ try {
3368
+ const reconciliationDeadlineMs = Date.now() + COORDINATOR_ROSTER_READ_BUDGET_MS;
3369
+ const groupById = new Map(scopedGroups.map((group) => [group.groupId, group]));
3370
+ const listCompletionRecords = (groupIds) => retryTransientCoordinatorRead((attemptTimeoutS) => completionDependencies.list({
3371
+ groupIds,
3372
+ remoteUrl,
3373
+ adminSecret: config.syncCoordinatorAdminSecret,
3374
+ timeoutS: attemptTimeoutS
3375
+ }), timeoutS, reconciliationDeadlineMs);
3376
+ const scopedGroupIds = [...new Set(scopedGroups.map((group) => group.groupId))];
3377
+ let records;
3378
+ try {
3379
+ records = await listCompletionRecords(scopedGroupIds);
3380
+ } catch (error) {
3381
+ if (isUnsupportedCompletionQuery(error)) records = [];
3382
+ else if (reconciliationOptions.isolateApplyFailures && isUnattributableCompletionBatchError(error)) records = await listCompletionRecordsPerGroup(scopedGroupIds, listCompletionRecords);
3383
+ else throw error;
3384
+ }
3385
+ const freshGroupByKey = /* @__PURE__ */ new Map();
3386
+ if (records.length > 0) try {
3387
+ const onlyGroup = scopedGroups.length === 1 ? scopedGroups[0] : void 0;
3388
+ const freshGroups = onlyGroup ? [await loadFreshGroupSnapshot(onlyGroup, remoteUrl, reconciliationDeadlineMs)] : await loadedSnapshots(void 0, reconciliationDeadlineMs);
3389
+ for (const freshGroup of freshGroups) {
3390
+ if (!freshGroup) continue;
3391
+ const key = freshGroupKey(freshGroup);
3392
+ if (freshGroupByKey.has(key)) throw new Error("team_setup_completion_invalid");
3393
+ freshGroupByKey.set(key, freshGroup);
3394
+ }
3395
+ } catch (error) {
3396
+ for (const record of records) {
3397
+ const group = groupById.get(record.group_id);
3398
+ if (!group || !belongsToGroupCandidate(record, group)) continue;
3399
+ try {
3400
+ validateLegacyTeamSetupCompletionManifestBinding(record.manifest, {
3401
+ coordinatorId: group.coordinatorId,
3402
+ groupId: group.groupId
3403
+ });
3404
+ } catch (bindingError) {
3405
+ if (!reconciliationOptions.isolateApplyFailures) throw bindingError;
3406
+ continue;
3407
+ }
3408
+ let canonicalManifest;
3409
+ try {
3410
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest(record.manifest, {
3411
+ coordinatorId: group.coordinatorId,
3412
+ groupId: group.groupId
3413
+ });
3414
+ } catch (validationError) {
3415
+ try {
3416
+ if (!await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => containLegacyTeamSetupCompletionConflict(store.db, {
3417
+ coordinatorId: group.coordinatorId,
3418
+ groupId: group.groupId
3419
+ }))) {
3420
+ if (!reconciliationOptions.isolateApplyFailures) throw validationError;
3421
+ continue;
3422
+ }
3423
+ } catch (containmentError) {
3424
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3425
+ }
3426
+ if (!reconciliationOptions.isolateApplyFailures) throw validationError;
3427
+ continue;
3428
+ }
3429
+ try {
3430
+ const localManifest = reconstructLegacyTeamSetupCompletionManifest(store.db, { candidateRef: canonicalManifest.candidate_ref });
3431
+ if (canonicalCoordinatorLegacyTeamCompletionManifestJson(localManifest) === canonicalCoordinatorLegacyTeamCompletionManifestJson(canonicalManifest)) continue;
3432
+ if (areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible(localManifest, canonicalManifest)) continue;
3433
+ } catch {}
3434
+ try {
3435
+ await runCandidateMutation(store.db, canonicalManifest.candidate_ref, () => containLegacyTeamSetupCompletionConflict(store.db, {
3436
+ coordinatorId: group.coordinatorId,
3437
+ groupId: group.groupId
3438
+ }));
3439
+ } catch (containmentError) {
3440
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3441
+ }
3442
+ }
3443
+ if (!reconciliationOptions.isolateApplyFailures) throw error;
3444
+ return;
3445
+ }
3446
+ const coordinatorCompletionGroupIds = /* @__PURE__ */ new Set();
3447
+ for (const record of records) {
3448
+ const group = groupById.get(record.group_id);
3449
+ if (!group) throw new Error("team_setup_completion_invalid");
3450
+ if (!belongsToGroupCandidate(record, group)) continue;
3451
+ try {
3452
+ validateLegacyTeamSetupCompletionManifestBinding(record.manifest, {
3453
+ coordinatorId: group.coordinatorId,
3454
+ groupId: group.groupId
3455
+ });
3456
+ } catch (bindingError) {
3457
+ if (!reconciliationOptions.isolateApplyFailures) throw bindingError;
3458
+ continue;
3459
+ }
3460
+ let canonicalManifest;
3461
+ try {
3462
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest(record.manifest, {
3463
+ coordinatorId: group.coordinatorId,
3464
+ groupId: group.groupId
3465
+ });
3466
+ } catch (validationError) {
3467
+ try {
3468
+ await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => containLegacyTeamSetupCompletionConflict(store.db, {
3469
+ coordinatorId: group.coordinatorId,
3470
+ groupId: group.groupId
3471
+ }));
3472
+ } catch (containmentError) {
3473
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3474
+ }
3475
+ if (!reconciliationOptions.isolateApplyFailures) throw validationError;
3476
+ continue;
3477
+ }
3478
+ coordinatorCompletionGroupIds.add(record.group_id);
3479
+ try {
3480
+ let freshGroup = freshGroupByKey.get(freshGroupKey(group));
3481
+ if (!freshGroup && scopedGroups.length > 1) {
3482
+ if (remainingCoordinatorTimeoutS(timeoutS, reconciliationDeadlineMs) == null) throw new Error("team_setup_roster_unavailable");
3483
+ freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, reconciliationDeadlineMs);
3484
+ }
3485
+ if (!freshGroup) {
3486
+ if (!await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => containLegacyTeamSetupCompletionConflict(store.db, {
3487
+ coordinatorId: group.coordinatorId,
3488
+ groupId: group.groupId
3489
+ }))) {
3490
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_invalid");
3491
+ continue;
3492
+ }
3493
+ throw new Error("team_setup_completion_invalid");
3494
+ }
3495
+ if (await applyCanonicalManifestIfStillAuthorized(store, authorization, group, canonicalManifest, freshGroup.devices) !== "applied") continue;
3496
+ } catch (error) {
3497
+ if (!reconciliationOptions.isolateApplyFailures) throw error;
3498
+ }
3499
+ }
3500
+ for (const group of scopedGroups) {
3501
+ if (coordinatorCompletionGroupIds.has(group.groupId)) continue;
3502
+ const publicationTimeoutS = remainingCoordinatorTimeoutS(timeoutS, reconciliationDeadlineMs);
3503
+ if (publicationTimeoutS == null) break;
3504
+ if (!hasCompletedLegacyTeamSetup(store, legacyTeamCandidateId(group.coordinatorId, group.groupId))) continue;
3505
+ const candidateRef = legacyTeamCandidateId(group.coordinatorId, group.groupId);
3506
+ let reconstructionFailed = false;
3507
+ try {
3508
+ if (!await runCandidateMutation(store.db, candidateRef, () => serializeRecipientPolicyPublicationMutation(store.db, () => serializeRecipientPolicyTeamMutation(store.db, deterministicPolicyTeamId(candidateRef), async () => {
3509
+ let manifest;
3510
+ try {
3511
+ manifest = reconstructLegacyTeamSetupCompletionManifest(store.db, { candidateRef });
3512
+ } catch {
3513
+ reconstructionFailed = true;
3514
+ return;
3515
+ }
3516
+ if (!stillAuthorizesGroup(authorization, group)) {
3517
+ reconstructionFailed = true;
3518
+ return;
3519
+ }
3520
+ await completionDependencies.create({
3521
+ groupId: group.groupId,
3522
+ manifest,
3523
+ remoteUrl,
3524
+ adminSecret: config.syncCoordinatorAdminSecret,
3525
+ timeoutS: publicationTimeoutS
3526
+ });
3527
+ }))) || reconstructionFailed) continue;
3528
+ } catch (error) {
3529
+ if (completionApiError(error) !== "team_setup_completion_conflict") continue;
3530
+ const getCompletion = completionDependencies.get;
3531
+ let canonicalManifest = null;
3532
+ try {
3533
+ if (!getCompletion) throw new Error("team_setup_completion_conflict");
3534
+ canonicalManifest = await retryTransientCoordinatorRead((attemptTimeoutS) => getCompletion({
3535
+ groupId: group.groupId,
3536
+ candidateRef,
3537
+ remoteUrl,
3538
+ adminSecret: config.syncCoordinatorAdminSecret,
3539
+ timeoutS: attemptTimeoutS
3540
+ }), timeoutS, reconciliationDeadlineMs);
3541
+ if (!canonicalManifest) throw new Error("team_setup_completion_conflict");
3542
+ } catch {
3543
+ try {
3544
+ if (!await runCandidateMutation(store.db, candidateRef, () => containLegacyTeamSetupCompletionConflict(store.db, {
3545
+ coordinatorId: group.coordinatorId,
3546
+ groupId: group.groupId
3547
+ }))) {
3548
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict");
3549
+ continue;
3550
+ }
3551
+ } catch (containmentError) {
3552
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3553
+ continue;
3554
+ }
3555
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict");
3556
+ continue;
3557
+ }
3558
+ try {
3559
+ const freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, reconciliationDeadlineMs);
3560
+ if (!freshGroup) throw new Error("team_setup_roster_unavailable");
3561
+ if (await applyCanonicalManifestIfStillAuthorized(store, authorization, group, canonicalManifest, freshGroup.devices) !== "applied") continue;
3562
+ } catch (applyError) {
3563
+ if (applyError instanceof LegacyTeamSetupAdditiveConvergenceError) {
3564
+ if (!reconciliationOptions.isolateApplyFailures) throw applyError;
3565
+ continue;
3566
+ }
3567
+ try {
3568
+ if (!await runCandidateMutation(store.db, candidateRef, () => containLegacyTeamSetupCompletionConflict(store.db, {
3569
+ coordinatorId: group.coordinatorId,
3570
+ groupId: group.groupId
3571
+ }))) {
3572
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict", { cause: applyError });
3573
+ continue;
3574
+ }
3575
+ } catch (containmentError) {
3576
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3577
+ continue;
3578
+ }
3579
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict", { cause: applyError });
3580
+ }
3581
+ }
3582
+ }
3583
+ } catch (error) {
3584
+ throw new Error(completionApiError(error));
3585
+ }
3586
+ }
3032
3587
  async function loadedCandidateSnapshots(candidateRef, loadOptions = {}) {
3033
3588
  const allowCachedSummaryFallback = loadOptions.allowCachedSummaryFallback ?? false;
3034
3589
  const invalidateSummaryCache = loadOptions.invalidateSummaryCache ?? true;
3035
3590
  if (invalidateSummaryCache) loadedSummarySnapshots.invalidate();
3036
3591
  try {
3037
- return await loadedSnapshots(candidateRef);
3592
+ return await loadedSnapshots(candidateRef, loadOptions.deadlineMs);
3038
3593
  } catch (error) {
3039
3594
  if (!allowCachedSummaryFallback) throw error;
3040
3595
  const cachedGroups = loadedSummarySnapshots.peek();
@@ -3046,6 +3601,15 @@ function teamSetupRoutes(options) {
3046
3601
  if (invalidateSummaryCache) loadedSummarySnapshots.invalidate();
3047
3602
  }
3048
3603
  }
3604
+ function persistedCompletionGroup(store, candidateRef) {
3605
+ const row = store.db.prepare(`SELECT coordinator_id, group_id FROM legacy_team_setup_drafts
3606
+ WHERE candidate_id = ? ORDER BY rowid DESC LIMIT 1`).get(candidateRef);
3607
+ if (!row || legacyTeamCandidateId(row.coordinator_id, row.group_id) !== candidateRef) return null;
3608
+ return {
3609
+ coordinatorId: row.coordinator_id,
3610
+ groupId: row.group_id
3611
+ };
3612
+ }
3049
3613
  app.get("/api/sync/team-setup/v1", async (c) => {
3050
3614
  let groups;
3051
3615
  try {
@@ -3055,9 +3619,12 @@ function teamSetupRoutes(options) {
3055
3619
  return c.json({ error: code }, errorStatus$1(code));
3056
3620
  }
3057
3621
  try {
3622
+ const store = options.getStore();
3623
+ readPureCandidateViews(store, groups);
3624
+ await reconcileCompletions(store, groups, { isolateApplyFailures: true });
3058
3625
  const response = {
3059
3626
  version: TEAM_SETUP_VERSION,
3060
- candidates: readPureCandidateViews(options.getStore(), groups).map(candidateSummary).filter((candidate) => candidate.status !== "ready")
3627
+ candidates: readPureCandidateViews(store, groups).map(candidateSummary).filter((candidate) => candidate.status !== "ready")
3061
3628
  };
3062
3629
  return c.json(response);
3063
3630
  } catch (error) {
@@ -3072,6 +3639,7 @@ function teamSetupRoutes(options) {
3072
3639
  const store = options.getStore();
3073
3640
  let draft = getLegacyTeamSetupDraft(store.db, candidateRef);
3074
3641
  const terminal = hasCompletedLegacyTeamSetup(store, candidateRef);
3642
+ let candidateGroupsForCompletion = null;
3075
3643
  if (terminal && draft?.state !== "completed") return c.json({ error: "team_setup_confirmation_stale" }, 404);
3076
3644
  let candidate;
3077
3645
  if (draft && (draft.state !== "completed" || terminal)) candidate = candidateFromDraft(draft);
@@ -3080,6 +3648,7 @@ function teamSetupRoutes(options) {
3080
3648
  allowCachedSummaryFallback: draft == null,
3081
3649
  invalidateSummaryCache: false
3082
3650
  })).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3651
+ candidateGroupsForCompletion = candidateGroups;
3083
3652
  const postLoadDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3084
3653
  if (hasCompletedLegacyTeamSetup(store, candidateRef)) {
3085
3654
  if (postLoadDraft?.state !== "completed") return c.json({ error: "team_setup_confirmation_stale" }, 404);
@@ -3093,6 +3662,17 @@ function teamSetupRoutes(options) {
3093
3662
  }
3094
3663
  }
3095
3664
  if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3665
+ const cachedCandidateGroups = loadedSummarySnapshots.peek()?.filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3666
+ let completionGroups = candidateGroupsForCompletion ?? (cachedCandidateGroups?.length ? cachedCandidateGroups : null);
3667
+ if (!completionGroups) {
3668
+ const persistedGroup = persistedCompletionGroup(store, candidateRef);
3669
+ completionGroups = persistedGroup ? [persistedGroup] : null;
3670
+ }
3671
+ const terminalBeforeReconciliation = hasCompletedLegacyTeamSetup(store, candidateRef);
3672
+ if (completionGroups) await reconcileCompletions(store, completionGroups);
3673
+ draft = getLegacyTeamSetupDraft(store.db, candidateRef);
3674
+ if (!draft || !terminalBeforeReconciliation && hasCompletedLegacyTeamSetup(store, candidateRef)) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3675
+ if (completionGroups) candidate = candidateFromDraft(draft);
3096
3676
  return c.json(detailResponse(store, draft, candidate));
3097
3677
  } catch (error) {
3098
3678
  const code = apiErrorCode(error);
@@ -3128,12 +3708,18 @@ function teamSetupRoutes(options) {
3128
3708
  ]) && submittedExpectation.kind === "existing" && submittedExpectation.assignmentVersion === device.expectation.assignmentVersion && submittedExpectation.identityRef === requiredIdentityRef(candidateRef, device.expectation.identityId))) return c.json({ error: "team_setup_assignment_changed" }, 409);
3129
3709
  const target = identityChoices(store, candidateRef).find((choice) => choice.identityRef === targetIdentityRef);
3130
3710
  if (!target) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3131
- return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupDeviceAssignment(store.db, {
3132
- attemptId,
3133
- deviceRef,
3134
- targetIdentityId: target.identityId,
3135
- expectation: device.expectation
3136
- }), (draft) => detailResponse(store, draft)));
3711
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3712
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3713
+ try {
3714
+ return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupDeviceAssignment(store.db, {
3715
+ attemptId,
3716
+ deviceRef,
3717
+ targetIdentityId: target.identityId,
3718
+ expectation: device.expectation
3719
+ }), (draft) => detailResponse(store, draft)));
3720
+ } finally {
3721
+ releaseMutation();
3722
+ }
3137
3723
  } catch (error) {
3138
3724
  const code = apiErrorCode(error);
3139
3725
  return c.json({ error: code }, errorStatus$1(code));
@@ -3165,11 +3751,17 @@ function teamSetupRoutes(options) {
3165
3751
  const device = draft.devices.find((item) => item.deviceRef === deviceRef);
3166
3752
  if (!device) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3167
3753
  if (decision === "included" && (device.targetIdentityId === null || requiredIdentityRef(candidateRef, device.targetIdentityId) !== expectedTargetIdentityRef)) return c.json({ error: "team_setup_assignment_changed" }, 409);
3168
- return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupDeviceDecision(store.db, {
3169
- attemptId,
3170
- deviceRef,
3171
- decision
3172
- }), (draft) => detailResponse(store, draft)));
3754
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3755
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3756
+ try {
3757
+ return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupDeviceDecision(store.db, {
3758
+ attemptId,
3759
+ deviceRef,
3760
+ decision
3761
+ }), (draft) => detailResponse(store, draft)));
3762
+ } finally {
3763
+ releaseMutation();
3764
+ }
3173
3765
  } catch (error) {
3174
3766
  const code = apiErrorCode(error);
3175
3767
  return c.json({ error: code }, errorStatus$1(code));
@@ -3191,10 +3783,16 @@ function teamSetupRoutes(options) {
3191
3783
  if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3192
3784
  if (draft.state === "completed") return c.json({ error: "team_setup_confirmation_stale" }, 409);
3193
3785
  if (!draft.devices.some((item) => item.deviceRef === deviceRef)) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3194
- return c.json(projectMutationAtomically(store, () => clearLegacyTeamSetupDeviceDecision(store.db, {
3195
- attemptId,
3196
- deviceRef
3197
- }), (draft) => detailResponse(store, draft)));
3786
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3787
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3788
+ try {
3789
+ return c.json(projectMutationAtomically(store, () => clearLegacyTeamSetupDeviceDecision(store.db, {
3790
+ attemptId,
3791
+ deviceRef
3792
+ }), (draft) => detailResponse(store, draft)));
3793
+ } finally {
3794
+ releaseMutation();
3795
+ }
3198
3796
  } catch (error) {
3199
3797
  const code = apiErrorCode(error);
3200
3798
  return c.json({ error: code }, errorStatus$1(code));
@@ -3220,11 +3818,17 @@ function teamSetupRoutes(options) {
3220
3818
  if (project.resolution === "deterministic") return c.json({ error: "team_setup_incomplete" }, 400);
3221
3819
  const target = projectMappingChoices(store).find((choice) => legacyTeamResolvedProjectRef(projectRef, choice.projectIdentity) === resolvedProjectRef);
3222
3820
  if (!target) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3223
- return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupProjectMapping(store.db, {
3224
- attemptId,
3225
- projectRef,
3226
- resolvedProjectIdentity: target.projectIdentity
3227
- }), (draft) => detailResponse(store, draft)));
3821
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3822
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3823
+ try {
3824
+ return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupProjectMapping(store.db, {
3825
+ attemptId,
3826
+ projectRef,
3827
+ resolvedProjectIdentity: target.projectIdentity
3828
+ }), (draft) => detailResponse(store, draft)));
3829
+ } finally {
3830
+ releaseMutation();
3831
+ }
3228
3832
  } catch (error) {
3229
3833
  const code = apiErrorCode(error);
3230
3834
  return c.json({ error: code }, errorStatus$1(code));
@@ -3252,13 +3856,19 @@ function teamSetupRoutes(options) {
3252
3856
  }
3253
3857
  if (groups.filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef).length === 0) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3254
3858
  try {
3255
- return c.json(projectMutationAtomically(store, () => {
3256
- if (getLegacyTeamSetupDraft(store.db, candidateRef)?.state === "completed" || hasCompletedLegacyTeamSetup(store, candidateRef)) throw new Error("team_setup_confirmation_stale");
3257
- return refreshLegacyTeamCandidate(store.db, {
3258
- projection: projectionOptions(store),
3259
- groups
3260
- }, candidateRef);
3261
- }, (draft) => detailResponse(store, draft)));
3859
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3860
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3861
+ try {
3862
+ return c.json(projectMutationAtomically(store, () => {
3863
+ if (getLegacyTeamSetupDraft(store.db, candidateRef)?.state === "completed" || hasCompletedLegacyTeamSetup(store, candidateRef)) throw new Error("team_setup_confirmation_stale");
3864
+ return refreshLegacyTeamCandidate(store.db, {
3865
+ projection: projectionOptions(store),
3866
+ groups
3867
+ }, candidateRef);
3868
+ }, (draft) => detailResponse(store, draft)));
3869
+ } finally {
3870
+ releaseMutation();
3871
+ }
3262
3872
  } catch (error) {
3263
3873
  const code = apiErrorCode(error);
3264
3874
  return c.json({ error: code }, errorStatus$1(code));
@@ -3278,50 +3888,180 @@ function teamSetupRoutes(options) {
3278
3888
  if (!hasExactKeys(parsed.value, finishKeys)) return Object.keys(parsed.value).every((key) => finishKeys.includes(key)) ? c.json({ error: "team_setup_confirmation_stale" }, 409) : c.json({ error: "team_setup_incomplete" }, 400);
3279
3889
  const { attemptId, confirmedAccessDeltaDigest, confirmedViewerAccessDeltaDigest, finishDigest } = parsed.value;
3280
3890
  if (typeof attemptId !== "string" || !ATTEMPT_ID_PATTERN.test(attemptId) || typeof finishDigest !== "string" || !FINISH_DIGEST_PATTERN.test(finishDigest) || typeof confirmedAccessDeltaDigest !== "string" || !ACCESS_DELTA_DIGEST_PATTERN.test(confirmedAccessDeltaDigest) || typeof confirmedViewerAccessDeltaDigest !== "string" || !VIEWER_ACCESS_DELTA_DIGEST_PATTERN.test(confirmedViewerAccessDeltaDigest)) return c.json({ error: "team_setup_incomplete" }, 400);
3891
+ let releaseMutation;
3892
+ let releasePublicationMutation;
3893
+ let releaseActorMutations;
3894
+ let finishLogRedactValues = [];
3281
3895
  try {
3282
3896
  const store = options.getStore();
3283
3897
  const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
3284
3898
  if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3285
3899
  if (draft.state !== "completed" && hasCompletedLegacyTeamSetup(store, candidateRef)) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3286
3900
  if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3287
- if (draft.state !== "completed") {
3288
- const preview = previewLegacyTeamSetupActivation(store.db, {
3901
+ const replay = replayLegacyTeamSetupActivation(store.db, {
3902
+ candidateRef,
3903
+ attemptId,
3904
+ finishDigest,
3905
+ confirmedAccessDeltaDigest
3906
+ });
3907
+ if (replay) return c.json(finishResponse(candidateRef, replay));
3908
+ if (draft.state === "completed") return c.json({ error: "team_setup_confirmation_stale" }, 409);
3909
+ const preview = previewLegacyTeamSetupActivation(store.db, {
3910
+ candidateRef,
3911
+ attemptId: draft.attemptId
3912
+ });
3913
+ requireLegacyTeamSetupAccessDeltaWithinLimit(preview.accessDelta);
3914
+ const safeDraft = viewerSafeDraft(store, draft);
3915
+ if (viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, preview.accessDelta, {
3916
+ teamDisplayName: draft.displayName,
3917
+ ...safeDraft
3918
+ })) !== confirmedViewerAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3919
+ const matchingGroups = (await loadedCandidateSnapshots(candidateRef)).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3920
+ if (matchingGroups.length !== 1) throw safeCoordinatorError();
3921
+ const group = matchingGroups[0];
3922
+ if (!group) throw safeCoordinatorError();
3923
+ releaseMutation = claimCandidateMutation(store.db, candidateRef) ?? void 0;
3924
+ if (!releaseMutation) {
3925
+ await candidateMutationSettled(store.db, candidateRef);
3926
+ const overlappingReplay = replayLegacyTeamSetupActivation(store.db, {
3289
3927
  candidateRef,
3290
- attemptId: draft.attemptId
3928
+ attemptId,
3929
+ finishDigest,
3930
+ confirmedAccessDeltaDigest
3291
3931
  });
3292
- requireBoundedAccessDelta(preview.accessDelta);
3293
- const safeDraft = viewerSafeDraft(store, draft);
3294
- if (viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, preview.accessDelta, {
3295
- teamDisplayName: draft.displayName,
3296
- ...safeDraft
3297
- })) !== confirmedViewerAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3932
+ if (overlappingReplay) return c.json(finishResponse(candidateRef, overlappingReplay));
3933
+ return c.json({ error: "team_setup_confirmation_stale" }, 409);
3298
3934
  }
3299
- const result = await finishLegacyTeamSetupActivation(store.db, {
3935
+ const claimedReplay = replayLegacyTeamSetupActivation(store.db, {
3300
3936
  candidateRef,
3301
3937
  attemptId,
3302
3938
  finishDigest,
3303
- confirmedAccessDeltaDigest,
3304
- loadFreshRoster: async () => {
3305
- const matching = (await loadedCandidateSnapshots(candidateRef)).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3306
- if (matching.length !== 1) throw safeCoordinatorError();
3307
- return matching[0]?.devices ?? [];
3308
- },
3309
- loadProjectInventory: () => legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef),
3310
- validateLockedPreview: (lockedPreview) => {
3311
- requireBoundedAccessDelta(lockedPreview.accessDelta);
3312
- const lockedDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3313
- if (!lockedDraft || lockedDraft.attemptId !== attemptId) return false;
3314
- const safeDraft = viewerSafeDraft(store, lockedDraft);
3315
- return viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, lockedPreview.accessDelta, {
3316
- teamDisplayName: lockedDraft.displayName,
3317
- ...safeDraft
3318
- })) === confirmedViewerAccessDeltaDigest;
3319
- }
3939
+ confirmedAccessDeltaDigest
3320
3940
  });
3321
- return c.json(finishResponse(candidateRef, result));
3941
+ if (claimedReplay) return c.json(finishResponse(candidateRef, claimedReplay));
3942
+ const claimedDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3943
+ if (!claimedDraft || claimedDraft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3944
+ const publicationDeadlineMs = Date.now() + COORDINATOR_ROSTER_READ_BUDGET_MS;
3945
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
3946
+ releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, claimedDraft.devices.flatMap((device) => [...device.decision === "included" && device.targetIdentityId ? [device.targetIdentityId] : [], ...device.existingIdentityId ? [device.existingIdentityId] : []]));
3947
+ return await serializeRecipientPolicyTeamMutation(store.db, deterministicPolicyTeamId(candidateRef), () => serializeRecipientPolicyCoordinatorGroupMutation(store.db, group.groupId, async () => {
3948
+ let authorization;
3949
+ try {
3950
+ authorization = currentCompletionAuthorization();
3951
+ } catch (error) {
3952
+ throw new Error(completionApiError(error));
3953
+ }
3954
+ if (!completionDependencies || !authorization) throw new Error("team_setup_completion_unavailable");
3955
+ if (!authorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
3956
+ const { config, remoteUrl } = authorization;
3957
+ finishLogRedactValues = [config.syncCoordinatorAdminSecret];
3958
+ const freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
3959
+ if (!freshGroup) throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, /* @__PURE__ */ new Error("coordinator roster unavailable"));
3960
+ if (!stillAuthorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
3961
+ const projectInventory = legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef);
3962
+ const freshPreview = inspectFreshLegacyTeamSetupActivation(store.db, {
3963
+ candidateRef,
3964
+ attemptId,
3965
+ freshRoster: freshGroup.devices,
3966
+ projectInventory
3967
+ });
3968
+ if (freshPreview.finishDigest !== finishDigest || freshPreview.accessDeltaDigest !== confirmedAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3969
+ const freshDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3970
+ if (!freshDraft || freshDraft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3971
+ const safeFreshDraft = viewerSafeDraft(store, freshDraft);
3972
+ if (viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, freshPreview.accessDelta, {
3973
+ teamDisplayName: freshDraft.displayName,
3974
+ ...safeFreshDraft
3975
+ })) !== confirmedViewerAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3976
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3977
+ const proposedManifest = deriveLegacyTeamSetupCompletionManifest(store.db, {
3978
+ candidateRef,
3979
+ attemptId,
3980
+ completedAt
3981
+ });
3982
+ const timeoutS = Math.max(1, config.syncCoordinatorTimeoutS);
3983
+ const publicationTimeoutS = remainingCoordinatorTimeoutS(timeoutS, publicationDeadlineMs);
3984
+ if (publicationTimeoutS == null) throw new Error("team_setup_completion_unavailable");
3985
+ let canonicalManifest;
3986
+ try {
3987
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest((await completionDependencies.create({
3988
+ groupId: group.groupId,
3989
+ manifest: proposedManifest,
3990
+ remoteUrl,
3991
+ adminSecret: config.syncCoordinatorAdminSecret,
3992
+ timeoutS: publicationTimeoutS
3993
+ })).manifest, {
3994
+ coordinatorId: group.coordinatorId,
3995
+ groupId: group.groupId
3996
+ });
3997
+ if (canonicalCoordinatorLegacyTeamCompletionManifestJson(canonicalManifest) !== canonicalCoordinatorLegacyTeamCompletionManifestJson(proposedManifest)) throw new Error("team_setup_completion_conflict");
3998
+ } catch (error) {
3999
+ const code = completionApiError(error);
4000
+ const details = teamSetupFinishFailureDetails(error);
4001
+ const getCompletion = completionDependencies.get;
4002
+ if (code !== "team_setup_completion_conflict" || !getCompletion) {
4003
+ warnTeamSetupFinishFailure(error, details, {
4004
+ candidateRef,
4005
+ attemptId,
4006
+ apiCode: code,
4007
+ redactValues: finishLogRedactValues
4008
+ });
4009
+ throw new TeamSetupFinishError(code, details.reason, true, error);
4010
+ }
4011
+ try {
4012
+ const existing = await retryTransientCoordinatorRead((attemptTimeoutS) => getCompletion({
4013
+ groupId: group.groupId,
4014
+ candidateRef,
4015
+ remoteUrl,
4016
+ adminSecret: config.syncCoordinatorAdminSecret,
4017
+ timeoutS: attemptTimeoutS
4018
+ }), timeoutS, publicationDeadlineMs);
4019
+ if (!existing) throw new Error("team_setup_completion_conflict");
4020
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest(existing, {
4021
+ coordinatorId: group.coordinatorId,
4022
+ groupId: group.groupId
4023
+ });
4024
+ if (canonicalManifest.finish_digest !== proposedManifest.finish_digest) throw new Error("team_setup_completion_conflict");
4025
+ } catch (recoveryError) {
4026
+ throw new TeamSetupFinishError(completionApiError(recoveryError), teamSetupFinishFailureDetails(recoveryError).reason, false, recoveryError);
4027
+ }
4028
+ }
4029
+ let postPublicationGroup;
4030
+ try {
4031
+ postPublicationGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
4032
+ } catch (error) {
4033
+ if (legacyTeamSetupApiErrorCode(error) === "team_setup_completion_invalid") throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, error);
4034
+ throw error;
4035
+ }
4036
+ if (!postPublicationGroup) throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, /* @__PURE__ */ new Error("coordinator roster unavailable"));
4037
+ const result = await applyLegacyTeamSetupCompletionManifestAndReturnActivation(store.db, {
4038
+ coordinatorId: group.coordinatorId,
4039
+ groupId: group.groupId,
4040
+ freshRoster: postPublicationGroup.devices,
4041
+ manifest: canonicalManifest,
4042
+ expectedDraftManifest: proposedManifest,
4043
+ recipientPolicyLocksHeld: {
4044
+ publication: true,
4045
+ team: true,
4046
+ coordinatorGroup: true
4047
+ }
4048
+ });
4049
+ return c.json(finishResponse(candidateRef, result));
4050
+ }));
3322
4051
  } catch (error) {
3323
4052
  const code = apiErrorCode(error);
3324
- return c.json({ error: code }, errorStatus$1(code));
4053
+ const details = teamSetupFinishFailureDetails(error);
4054
+ warnTeamSetupFinishFailure(error, details, {
4055
+ candidateRef,
4056
+ attemptId,
4057
+ apiCode: code,
4058
+ redactValues: finishLogRedactValues
4059
+ });
4060
+ return c.json(finishErrorResponse(code, details), errorStatus$1(code));
4061
+ } finally {
4062
+ releaseActorMutations?.();
4063
+ releasePublicationMutation?.();
4064
+ releaseMutation?.();
3325
4065
  }
3326
4066
  });
3327
4067
  return app;
@@ -3670,7 +4410,7 @@ function projectInviteAcceptanceFailure(error) {
3670
4410
  }
3671
4411
  var PROJECT_INVITE_TTL_HOURS = 168;
3672
4412
  var PROJECT_INVITE_OWNER_FALLBACK = "Project owner";
3673
- var PROJECT_INVITE_BODY_KEYS = new Set([
4413
+ var PROJECT_INVITE_BODY_KEYS = /* @__PURE__ */ new Set([
3674
4414
  "teammate_name",
3675
4415
  "project_ids",
3676
4416
  "reviewed_project_set_digest"
@@ -3695,7 +4435,7 @@ function projectInviteStringList(body, key) {
3695
4435
  return items;
3696
4436
  }
3697
4437
  function assertProjectInviteBody(body, creating) {
3698
- const allowed = creating ? PROJECT_INVITE_BODY_KEYS : new Set(["teammate_name", "project_ids"]);
4438
+ const allowed = creating ? PROJECT_INVITE_BODY_KEYS : /* @__PURE__ */ new Set(["teammate_name", "project_ids"]);
3699
4439
  if (Object.keys(body).some((key) => !allowed.has(key))) throw new Error("unexpected_project_invite_fields");
3700
4440
  if (typeof body.teammate_name !== "string") throw new Error("teammate_name_must_be_string");
3701
4441
  if (creating && typeof body.reviewed_project_set_digest !== "string") throw new Error("reviewed_project_set_digest_required");
@@ -3807,7 +4547,7 @@ function recipientInviteOnboardingPreviewFromReviewedIntent(store, body, invitat
3807
4547
  ...binding
3808
4548
  });
3809
4549
  }
3810
- var SAFE_RECIPIENT_INVITE_ERRORS = new Set([
4550
+ var SAFE_RECIPIENT_INVITE_ERRORS = /* @__PURE__ */ new Set([
3811
4551
  "device_display_name_invalid",
3812
4552
  "device_display_name_required",
3813
4553
  "device_display_name_too_long",
@@ -3841,20 +4581,21 @@ async function peerSupportsSyncRequirements(store, deviceId, requirements) {
3841
4581
  const addresses = safeJsonList(row?.addresses_json ?? null);
3842
4582
  for (const address of addresses) try {
3843
4583
  const url = `${buildBaseUrl(address)}/v1/status`;
4584
+ const headers = {
4585
+ ...buildDirectPeerAuthHeaders({
4586
+ deviceId: localDeviceId,
4587
+ recipientId: deviceId,
4588
+ dbPath: store.dbPath,
4589
+ method: "GET",
4590
+ url,
4591
+ bodyBytes: Buffer.alloc(0),
4592
+ keysDir: syncKeysDir()
4593
+ }),
4594
+ [SYNC_CAPABILITY_HEADER]: LOCAL_SYNC_CAPABILITY,
4595
+ [SYNC_FEATURES_HEADER]: LOCAL_SYNC_FEATURES.join(",")
4596
+ };
3844
4597
  const [status, payload] = await requestJson("GET", url, {
3845
- headers: {
3846
- ...buildDirectPeerAuthHeaders({
3847
- deviceId: localDeviceId,
3848
- recipientId: deviceId,
3849
- dbPath: store.dbPath,
3850
- method: "GET",
3851
- url,
3852
- bodyBytes: Buffer.alloc(0),
3853
- keysDir: syncKeysDir()
3854
- }),
3855
- [SYNC_CAPABILITY_HEADER]: LOCAL_SYNC_CAPABILITY,
3856
- [SYNC_FEATURES_HEADER]: LOCAL_SYNC_FEATURES.join(",")
3857
- },
4598
+ headers,
3858
4599
  timeoutS: 5
3859
4600
  });
3860
4601
  if (status < 200 || status >= 300) continue;
@@ -4226,9 +4967,9 @@ var AUTOMATIC_SHARE_OPERATION_STATES = [
4226
4967
  "initial_sync",
4227
4968
  "waiting_for_device"
4228
4969
  ];
4229
- var AUTOMATIC_WAITING_ACCEPTANCE_RETRY_COOLDOWN_MS = 30 * 1e3;
4230
- var AUTOMATIC_WAITING_DEVICE_RETRY_COOLDOWN_MS = 300 * 1e3;
4231
- var TERMINAL_SHARE_MAINTENANCE_ERRORS = new Set([
4970
+ var AUTOMATIC_WAITING_ACCEPTANCE_RETRY_COOLDOWN_MS = 3e4;
4971
+ var AUTOMATIC_WAITING_DEVICE_RETRY_COOLDOWN_MS = 3e5;
4972
+ var TERMINAL_SHARE_MAINTENANCE_ERRORS = /* @__PURE__ */ new Set([
4232
4973
  "coordinator_not_configured",
4233
4974
  "team_sharing_not_configured",
4234
4975
  "team_selection_ambiguous",
@@ -4242,7 +4983,7 @@ var TERMINAL_SHARE_MAINTENANCE_ERRORS = new Set([
4242
4983
  "intent_conflict",
4243
4984
  "inviter_identity_conflict"
4244
4985
  ]);
4245
- var PROJECT_INVITE_OWNER_CONFLICT_ERRORS = new Set([
4986
+ var PROJECT_INVITE_OWNER_CONFLICT_ERRORS = /* @__PURE__ */ new Set([
4246
4987
  "operation_scope_mismatch",
4247
4988
  "operation_state_invalid",
4248
4989
  "operation_acceptance_invalid",
@@ -4257,7 +4998,7 @@ var PROJECT_INVITE_OWNER_CONFLICT_ERRORS = new Set([
4257
4998
  "intent_conflict",
4258
4999
  "inviter_identity_conflict"
4259
5000
  ]);
4260
- var TERMINAL_RECONCILIATION_ERRORS = new Set([
5001
+ var TERMINAL_RECONCILIATION_ERRORS = /* @__PURE__ */ new Set([
4261
5002
  "coordinator_not_configured",
4262
5003
  "team_sharing_not_configured",
4263
5004
  "team_selection_ambiguous",
@@ -4451,7 +5192,7 @@ function coordinatorEnrollmentFailureCode(error) {
4451
5192
  const message = (error instanceof Error ? error.message : String(error)).trim();
4452
5193
  const httpStatus = message.match(/\((\d{3})(?::|\))/u)?.[1];
4453
5194
  if (httpStatus) return `http_${httpStatus}`;
4454
- if (new Set([
5195
+ if ((/* @__PURE__ */ new Set([
4455
5196
  "coordinator_consumed_team_invite_invalid",
4456
5197
  "coordinator_device_list_malformed",
4457
5198
  "coordinator_group_id_invalid",
@@ -4459,7 +5200,7 @@ function coordinatorEnrollmentFailureCode(error) {
4459
5200
  "coordinator_invite_group_mismatch",
4460
5201
  "coordinator_invite_list_malformed",
4461
5202
  "reconciliation_time_invalid"
4462
- ]).has(message)) return message;
5203
+ ])).has(message)) return message;
4463
5204
  if (/timeout|timed out|abort/iu.test(message)) return "request_timeout";
4464
5205
  if (/fetch failed|network|econn|enotfound/iu.test(message)) return "network_error";
4465
5206
  return "unexpected_error";
@@ -4517,7 +5258,7 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
4517
5258
  continue;
4518
5259
  }
4519
5260
  try {
4520
- const result = reconcileSnapshot({
5261
+ const result = await reconcileSnapshot({
4521
5262
  coordinatorId: buildBaseUrl(remoteUrl),
4522
5263
  groupId,
4523
5264
  enrollments: enrollmentsResult.value,
@@ -4540,7 +5281,7 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
4540
5281
  var RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT = 10;
4541
5282
  var RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT = 3;
4542
5283
  var RECIPIENT_POLICY_MAINTENANCE_BACKOFF_MS = 6e4;
4543
- var RECIPIENT_POLICY_WAITING_ERRORS = new Set([
5284
+ var RECIPIENT_POLICY_WAITING_ERRORS = /* @__PURE__ */ new Set([
4544
5285
  "recipient_policy_capability_undetermined",
4545
5286
  "recipient_policy_parity_incomplete",
4546
5287
  "recipient_policy_snapshot_not_fresh"
@@ -5709,7 +6450,7 @@ function mapSyncAttemptRow(row, showDiag, addresses) {
5709
6450
  address: showDiag && addresses?.length ? addresses[0] : null
5710
6451
  };
5711
6452
  }
5712
- var SYNC_SCOPE_REJECTION_WINDOW_SECONDS = 1440 * 60;
6453
+ var SYNC_SCOPE_REJECTION_WINDOW_SECONDS = 86400;
5713
6454
  var CLEANUP_DIAGNOSTICS_MAX_ROWS = 250;
5714
6455
  function recentScopeRejectionsByPeer(store) {
5715
6456
  const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_SCOPE_REJECTION_WINDOW_SECONDS * 1e3)).toISOString();
@@ -6017,7 +6758,7 @@ function reassignLegacySharedReviewGroup(store, input) {
6017
6758
  legacy_shared_review: legacySharedReviewSummary(store)
6018
6759
  };
6019
6760
  }
6020
- var SYNC_DIRECTION_WINDOW_SECONDS = 1440 * 60;
6761
+ var SYNC_DIRECTION_WINDOW_SECONDS = 86400;
6021
6762
  function recentPeerOps(store) {
6022
6763
  const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_DIRECTION_WINDOW_SECONDS * 1e3)).toISOString();
6023
6764
  const rows = store.db.prepare(`SELECT peer_device_id,
@@ -6112,7 +6853,8 @@ var PEERS_QUERY = `
6112
6853
  * scope contents.
6113
6854
  */
6114
6855
  function negotiatedSyncCapability(c) {
6115
- return negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(c.req.header(SYNC_CAPABILITY_HEADER) ?? null));
6856
+ const header = c.req.header(SYNC_CAPABILITY_HEADER) ?? null;
6857
+ return negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(header));
6116
6858
  }
6117
6859
  /**
6118
6860
  * Peer-to-peer sync protocol routes (/v1/*).
@@ -6173,7 +6915,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
6173
6915
  });
6174
6916
  const [deviceId, fingerprint] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
6175
6917
  const syncReset = getSyncResetState(store.db);
6176
- const authorizedScopes = isScopedSyncCapability(negotiatedSyncCapability(c)) ? listAuthorizedScopesForPeer(store.db, {
6918
+ const negotiated = negotiatedSyncCapability(c);
6919
+ const authorizedScopes = isScopedSyncCapability(negotiated) ? listAuthorizedScopesForPeer(store.db, {
6177
6920
  localDeviceId: deviceId,
6178
6921
  peerDeviceId: auth.deviceId
6179
6922
  }) : null;
@@ -6560,7 +7303,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6560
7303
  const bindings = body.bindings.map((value) => {
6561
7304
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6562
7305
  const binding = value;
6563
- const bindingKeys = new Set([
7306
+ const bindingKeys = /* @__PURE__ */ new Set([
6564
7307
  "deviceId",
6565
7308
  "targetIdentityId",
6566
7309
  "confirmed",
@@ -6604,7 +7347,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6604
7347
  const body = parseDeviceBindingRequest(await parseViewerJsonBody(c), true);
6605
7348
  if (!body || !("reviewedInventoryDigest" in body)) return c.json({ error: "binding_request_invalid" }, 400);
6606
7349
  const store = getStore();
7350
+ let releasePublicationMutation;
6607
7351
  try {
7352
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
6608
7353
  ensureStableStoreIdentity(store);
6609
7354
  const result = commitDeviceIdentityBindings(store.db, {
6610
7355
  localActorId: store.actorId,
@@ -6620,6 +7365,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6620
7365
  return c.json({ error: "binding_commit_busy" }, 503);
6621
7366
  }
6622
7367
  return c.json({ error: "binding_commit_failed" }, 500);
7368
+ } finally {
7369
+ releasePublicationMutation?.();
6623
7370
  }
6624
7371
  });
6625
7372
  app.get("/api/sync/status", async (c) => {
@@ -6758,7 +7505,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6758
7505
  if (addrs.length) peerAddressMap.set(String(peerRow.peer_device_id ?? ""), addrs);
6759
7506
  });
6760
7507
  const attemptsItems = attemptRows.map((row) => {
6761
- return mapSyncAttemptRow(row, showDiag, showDiag ? peerAddressMap.get(String(row.peer_device_id ?? "")) : void 0);
7508
+ const addrs = showDiag ? peerAddressMap.get(String(row.peer_device_id ?? "")) : void 0;
7509
+ return mapSyncAttemptRow(row, showDiag, addrs);
6762
7510
  });
6763
7511
  const latestAttemptError = String(attemptRows[0]?.error || "").trim();
6764
7512
  const statusBlock = {
@@ -6837,7 +7585,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6837
7585
  const limit = queryInt(c.req.query("limit"), 100);
6838
7586
  if (limit <= 0) return c.json({ error: "invalid_limit" }, 400);
6839
7587
  const sinceMinutesRaw = queryInt(c.req.query("sinceMinutes"), 1440);
6840
- const sinceMinutes = Math.min(Math.max(sinceMinutesRaw, 0), 10 * 365 * 24 * 60);
7588
+ const sinceMinutes = Math.min(Math.max(sinceMinutesRaw, 0), 5256e3);
6841
7589
  const sinceIso = sinceMinutes > 0 ? (/* @__PURE__ */ new Date(Date.now() - sinceMinutes * 6e4)).toISOString() : void 0;
6842
7590
  const records = listInboundScopeRejections(store.db, {
6843
7591
  peerDeviceId,
@@ -7064,7 +7812,14 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7064
7812
  const store = getStore();
7065
7813
  const body = await parseViewerJsonBody(c);
7066
7814
  try {
7067
- const result = commitRecipientPolicyEdges(store.db, body);
7815
+ const request = parseRecipientPolicyEdgeCommitRequest(body);
7816
+ const teamIds = request ? [...new Set(request.changes.flatMap((change) => change.recipient.recipientKind === "team" ? [change.recipient.teamId] : []))].toSorted() : [];
7817
+ const commit = async (index) => {
7818
+ const teamId = teamIds[index];
7819
+ if (!teamId) return commitRecipientPolicyEdges(store.db, request ?? body);
7820
+ return serializeRecipientPolicyTeamMutation(store.db, teamId, () => commit(index + 1));
7821
+ };
7822
+ const result = await commit(0);
7068
7823
  if (result.status === "invalid") return c.json(result, 400);
7069
7824
  if (result.status === "not_found") return c.json(result, 404);
7070
7825
  if (result.status === "stale" || result.status === "conflict") return c.json(result, 409);
@@ -7187,7 +7942,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7187
7942
  const parseReviewRequest = (value) => {
7188
7943
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7189
7944
  const body = value;
7190
- const allowedKeys = new Set([
7945
+ const allowedKeys = /* @__PURE__ */ new Set([
7191
7946
  "reviewItemId",
7192
7947
  "sourceFingerprint",
7193
7948
  "decision",
@@ -7203,7 +7958,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7203
7958
  };
7204
7959
  app.post("/api/sync/recipient-policy/v1/review/resolve", async (c) => {
7205
7960
  const store = getStore();
7206
- const request = parseReviewRequest(await parseViewerJsonBody(c));
7961
+ const body = await parseViewerJsonBody(c);
7962
+ const request = parseReviewRequest(body);
7207
7963
  if (!request) return c.json({ error: "request_invalid" }, 400);
7208
7964
  ensureLocalActorRecord(store);
7209
7965
  const result = resolveRecipientPolicyReview(store.db, {
@@ -7518,9 +8274,11 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7518
8274
  const store = getStore();
7519
8275
  const body = await parseViewerJsonBody(c);
7520
8276
  if (!body) return c.json({ error: "invalid json" }, 400);
8277
+ let releasePublicationMutation;
7521
8278
  try {
7522
8279
  const confirmedGuardrailTokens = optionalViewerStringList(body, "confirmed_guardrail_tokens");
7523
8280
  const mappingInput = parseViewerProjectMappingInput(body);
8281
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
7524
8282
  const analysis = analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput);
7525
8283
  if (analysis.requested_workspace_identity?.startsWith("unmapped:")) return c.json({
7526
8284
  error: "unmapped_project_local_only",
@@ -7550,12 +8308,15 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7550
8308
  });
7551
8309
  } catch (error) {
7552
8310
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
8311
+ } finally {
8312
+ releasePublicationMutation?.();
7553
8313
  }
7554
8314
  });
7555
8315
  app.put("/api/sync/sharing-domains/project-mappings/bulk", async (c) => {
7556
8316
  const store = getStore();
7557
8317
  const body = await parseViewerJsonBody(c);
7558
8318
  if (!body) return c.json({ error: "invalid json" }, 400);
8319
+ let releasePublicationMutation;
7559
8320
  try {
7560
8321
  const rawMappings = body.mappings;
7561
8322
  if (!Array.isArray(rawMappings) || rawMappings.length === 0) return c.json({ error: "mappings must be a non-empty array" }, 400);
@@ -7563,6 +8324,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7563
8324
  if (raw == null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("each mapping must be an object");
7564
8325
  return parseViewerProjectMappingInput(raw);
7565
8326
  });
8327
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
7566
8328
  const analyses = mappingInputs.map((mappingInput) => analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput));
7567
8329
  const unmapped = analyses.find((analysis) => analysis.requested_workspace_identity?.startsWith("unmapped:"));
7568
8330
  if (unmapped) return c.json({
@@ -7593,12 +8355,16 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7593
8355
  });
7594
8356
  } catch (error) {
7595
8357
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
8358
+ } finally {
8359
+ releasePublicationMutation?.();
7596
8360
  }
7597
8361
  });
7598
- app.delete("/api/sync/sharing-domains/project-mappings/:id", (c) => {
8362
+ app.delete("/api/sync/sharing-domains/project-mappings/:id", async (c) => {
7599
8363
  const store = getStore();
7600
8364
  const id = Number(c.req.param("id"));
8365
+ let releasePublicationMutation;
7601
8366
  try {
8367
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
7602
8368
  const deleted = deleteProjectScopeSettingsMapping(store.db, id);
7603
8369
  return c.json({
7604
8370
  ok: true,
@@ -7606,6 +8372,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7606
8372
  });
7607
8373
  } catch (error) {
7608
8374
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
8375
+ } finally {
8376
+ releasePublicationMutation?.();
7609
8377
  }
7610
8378
  });
7611
8379
  app.post("/api/sync/peers/identity", async (c) => {
@@ -7734,6 +8502,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7734
8502
  if (!secondaryActorId) return c.json({ error: "secondary_actor_id required" }, 400);
7735
8503
  if (primaryActorId === secondaryActorId) return c.json({ error: "actor ids must differ" }, 400);
7736
8504
  const d = drizzle(store.db, { schema });
8505
+ const releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, [primaryActorId, secondaryActorId]);
8506
+ queueMicrotask(releaseActorMutations);
7737
8507
  const now = (/* @__PURE__ */ new Date()).toISOString();
7738
8508
  const result = store.db.transaction(() => {
7739
8509
  const primary = d.select().from(schema.actors).where(eq(schema.actors.actor_id, primaryActorId)).get();
@@ -7884,6 +8654,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7884
8654
  const actorId = String(body.actor_id ?? "").trim();
7885
8655
  if (!actorId) return c.json({ error: "actor_id required" }, 400);
7886
8656
  if (actorId === store.actorId) return c.json({ error: "cannot deactivate this device's own local actor" }, 409);
8657
+ const releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, [actorId]);
8658
+ queueMicrotask(releaseActorMutations);
7887
8659
  const d = drizzle(store.db, { schema });
7888
8660
  const actor = d.select().from(schema.actors).where(eq(schema.actors.actor_id, actorId)).get();
7889
8661
  if (!actor) return c.json({ error: "actor not found" }, 404);
@@ -9114,7 +9886,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9114
9886
  const publicKey = String(body.peer_public_key ?? "").trim();
9115
9887
  const name = String(body.name ?? "").trim() || null;
9116
9888
  const requestedFingerprint = String(body.fingerprint ?? "").trim();
9117
- const addresses = mergeAddresses([], (Array.isArray(body.peer_addresses) ? body.peer_addresses : []).map((item) => String(item ?? "").trim()).filter((item) => item.length > 0));
9889
+ const addressesInput = Array.isArray(body.peer_addresses) ? body.peer_addresses : [];
9890
+ const addresses = mergeAddresses([], addressesInput.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0));
9118
9891
  if (!peerDeviceId || !publicKey) return c.json({ error: "peer_device_id_and_public_key_required" }, 400);
9119
9892
  const fingerprint = requestedFingerprint || fingerprintPublicKey(publicKey);
9120
9893
  if (fingerprintPublicKey(publicKey) !== fingerprint) return c.json({ error: "fingerprint_mismatch" }, 400);
@@ -9203,7 +9976,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9203
9976
  error: "coordinator_not_configured",
9204
9977
  status
9205
9978
  }, 400);
9206
- const existing = getCoordinatorGroupPreference(getStore().db, coordinatorId, groupId);
9979
+ const store = getStore();
9980
+ const existing = getCoordinatorGroupPreference(store.db, coordinatorId, groupId);
9207
9981
  if (existing) return c.json({
9208
9982
  preferences: existing,
9209
9983
  status
@@ -9389,12 +10163,13 @@ function updateStatusRoutes(options = {}) {
9389
10163
  const app = new Hono();
9390
10164
  app.get("/api/update-status", async (c) => {
9391
10165
  try {
10166
+ const installKind = detectInstallKind({
10167
+ entryPath: process.argv[1] ?? "",
10168
+ env: process.env
10169
+ });
9392
10170
  const status = await getUpdateStatus$1({
9393
10171
  currentVersion: VERSION$1,
9394
- installKind: detectInstallKind({
9395
- entryPath: process.argv[1] ?? "",
9396
- env: process.env
9397
- })
10172
+ installKind
9398
10173
  });
9399
10174
  return c.json(status);
9400
10175
  } catch {
@@ -9455,6 +10230,8 @@ function createApp(opts) {
9455
10230
  app.route("/", teamSetupRoutes({
9456
10231
  getStore: storeFactory,
9457
10232
  loadLegacyTeamConfiguredGroupSnapshots: opts?.loadLegacyTeamConfiguredGroupSnapshots,
10233
+ completionDependencies: opts?.teamSetupCompletionDependencies,
10234
+ readCoordinatorConfig: opts?.readCoordinatorConfig,
9458
10235
  registerSummaryInvalidator: (invalidate) => {
9459
10236
  invalidateTeamSetupSummary = invalidate;
9460
10237
  }