@codemem/server 0.43.2 → 0.44.0-alpha.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, 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, 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();
@@ -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,10 @@ 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
+ }
2931
3004
  async function parseBoundedJsonObject(c) {
2932
3005
  if (c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return { ok: false };
2933
3006
  const lengthHeader = c.req.header("content-length");
@@ -2991,7 +3064,7 @@ function detailResponse(store, draft, candidate = candidateFromDraft(draft)) {
2991
3064
  if (code === "team_setup_projection_changed") unavailableReason = "team_setup_conflict";
2992
3065
  else if (code !== "team_setup_incomplete") unavailableReason = code;
2993
3066
  }
2994
- if (preview) requireBoundedAccessDelta(preview.accessDelta);
3067
+ if (preview) requireLegacyTeamSetupAccessDeltaWithinLimit(preview.accessDelta);
2995
3068
  const safeDraft = viewerSafeDraft(store, draft);
2996
3069
  const safePreview = preview ? viewerSafePreview(draft, safeDraft, preview) : null;
2997
3070
  return projectLegacyTeamSetupView({
@@ -3019,9 +3092,12 @@ function finishResponse(candidateRef, result) {
3019
3092
  function teamSetupRoutes(options) {
3020
3093
  const app = new Hono();
3021
3094
  const loadSnapshots = options.loadLegacyTeamConfiguredGroupSnapshots ?? ((loadOptions) => loadConfiguredLegacyTeamGroupSnapshotsWith(options.snapshotLoaderDependencies ?? defaultSnapshotLoaderDependencies, loadOptions, options.getStore));
3022
- async function loadedSnapshots(candidateRef) {
3095
+ async function loadedSnapshots(candidateRef, deadlineMs) {
3023
3096
  try {
3024
- return await loadSnapshots(candidateRef ? { candidateRef } : void 0);
3097
+ return await loadSnapshots(candidateRef || deadlineMs != null ? {
3098
+ candidateRef,
3099
+ deadlineMs
3100
+ } : void 0);
3025
3101
  } catch (error) {
3026
3102
  if (error instanceof Error && error.message === "legacy_team_setup_roster_too_large") throw error;
3027
3103
  throw safeCoordinatorError();
@@ -3029,12 +3105,341 @@ function teamSetupRoutes(options) {
3029
3105
  }
3030
3106
  const loadedSummarySnapshots = createCachedSnapshotLoader(() => loadedSnapshots(), SUMMARY_SNAPSHOT_CACHE_TTL_MS);
3031
3107
  options.registerSummaryInvalidator?.(() => loadedSummarySnapshots.invalidate());
3108
+ const completionDependencies = options.completionDependencies === void 0 ? defaultCompletionDependencies : options.completionDependencies;
3109
+ /**
3110
+ * Reads the live coordinator configuration and the groups it currently
3111
+ * authorizes. Returns null when completion I/O is not configured or current
3112
+ * authorization cannot be proven.
3113
+ */
3114
+ function currentCompletionAuthorization() {
3115
+ const config = (options.readCoordinatorConfig ?? options.snapshotLoaderDependencies?.readConfig ?? readCoordinatorSyncConfig)();
3116
+ const remoteUrl = buildBaseUrl(config.syncCoordinatorUrl);
3117
+ if (!remoteUrl || !config.syncCoordinatorAdminSecret) return null;
3118
+ const configuredCoordinatorId = normalizedCoordinatorId(remoteUrl);
3119
+ if (!configuredCoordinatorId) return null;
3120
+ let currentGroupIds;
3121
+ try {
3122
+ currentGroupIds = new Set(configuredGroupIds(config.syncCoordinatorGroups));
3123
+ } catch {
3124
+ return null;
3125
+ }
3126
+ return {
3127
+ config,
3128
+ remoteUrl,
3129
+ configuredCoordinatorId,
3130
+ currentGroupIds
3131
+ };
3132
+ }
3133
+ function authorizesGroup(authorization, group) {
3134
+ return authorization.currentGroupIds.has(group.groupId) && normalizedCoordinatorId(group.coordinatorId) === authorization.configuredCoordinatorId;
3135
+ }
3136
+ /**
3137
+ * Configuration may change while completion-list or roster requests are in
3138
+ * flight. Re-prove authorization for the exact coordinator and group right
3139
+ * before applying canonical policy, as the finish publication path does.
3140
+ */
3141
+ function stillAuthorizesGroup(initial, group) {
3142
+ let current;
3143
+ try {
3144
+ current = currentCompletionAuthorization();
3145
+ } catch {
3146
+ return false;
3147
+ }
3148
+ return current !== null && current.configuredCoordinatorId === initial.configuredCoordinatorId && current.remoteUrl === initial.remoteUrl && current.config.syncCoordinatorAdminSecret === initial.config.syncCoordinatorAdminSecret && authorizesGroup(current, group);
3149
+ }
3150
+ function matchesGroupSnapshot(snapshot, group) {
3151
+ return snapshot.groupId === group.groupId && normalizedCoordinatorId(snapshot.coordinatorId) === normalizedCoordinatorId(group.coordinatorId);
3152
+ }
3153
+ function freshGroupKey(group) {
3154
+ return `${normalizedCoordinatorId(group.coordinatorId) ?? group.coordinatorId}\0${group.groupId}`;
3155
+ }
3156
+ /**
3157
+ * The snapshot loader keys candidates by the configured coordinator spelling,
3158
+ * while a persisted group may carry an equivalent but different spelling.
3159
+ * Ask for the roster under each spelling and match on normalized identity,
3160
+ * preserving the persisted candidate identity for everything else.
3161
+ */
3162
+ async function loadFreshGroupSnapshot(group, remoteUrl, deadlineMs) {
3163
+ const candidateRefs = [.../* @__PURE__ */ new Set([legacyTeamCandidateId(remoteUrl, group.groupId), legacyTeamCandidateId(group.coordinatorId, group.groupId)])];
3164
+ for (const candidateRef of candidateRefs) {
3165
+ const matches = (await loadedSnapshots(candidateRef, deadlineMs)).filter((snapshot) => matchesGroupSnapshot(snapshot, group));
3166
+ if (matches.length > 1) throw new Error("team_setup_completion_invalid");
3167
+ if (matches[0]) return matches[0];
3168
+ }
3169
+ }
3170
+ function completionRecordCandidateRef(record) {
3171
+ const manifest = record.manifest;
3172
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return null;
3173
+ const candidateRef = manifest.candidate_ref;
3174
+ return typeof candidateRef === "string" ? candidateRef : null;
3175
+ }
3176
+ function belongsToGroupCandidate(record, group) {
3177
+ const candidateRef = completionRecordCandidateRef(record);
3178
+ return candidateRef === null || candidateRef === legacyTeamCandidateId(group.coordinatorId, group.groupId);
3179
+ }
3180
+ /**
3181
+ * Applies a canonical manifest with authorization re-proven inside the same
3182
+ * serialized section that guards the apply. Checking before the locks would
3183
+ * let a group removed while reconciliation waited on another policy
3184
+ * mutation still be applied once the queues open.
3185
+ */
3186
+ async function applyCanonicalManifestIfStillAuthorized(store, authorization, group, manifest, freshRoster) {
3187
+ let outcome = "unauthorized";
3188
+ 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 () => {
3189
+ if (!stillAuthorizesGroup(authorization, group)) return;
3190
+ await applyLegacyTeamSetupCompletionManifest(store.db, {
3191
+ coordinatorId: group.coordinatorId,
3192
+ groupId: group.groupId,
3193
+ manifest,
3194
+ freshRoster,
3195
+ recipientPolicyLocksHeld: {
3196
+ publication: true,
3197
+ team: true,
3198
+ coordinatorGroup: true
3199
+ }
3200
+ });
3201
+ outcome = "applied";
3202
+ })))) ? outcome : "claimed";
3203
+ }
3204
+ async function reconcileCompletions(store, groups, reconciliationOptions = {}) {
3205
+ if (!completionDependencies || groups.length === 0) return;
3206
+ let authorization;
3207
+ try {
3208
+ authorization = currentCompletionAuthorization();
3209
+ } catch (error) {
3210
+ throw new Error(completionApiError(error));
3211
+ }
3212
+ if (!authorization) return;
3213
+ const { config, remoteUrl } = authorization;
3214
+ const scopedGroups = groups.filter((group) => authorizesGroup(authorization, group));
3215
+ if (scopedGroups.length === 0) return;
3216
+ const timeoutS = Math.max(1, config.syncCoordinatorTimeoutS);
3217
+ try {
3218
+ const reconciliationDeadlineMs = Date.now() + COORDINATOR_ROSTER_READ_BUDGET_MS;
3219
+ const groupById = new Map(scopedGroups.map((group) => [group.groupId, group]));
3220
+ const listCompletionRecords = (groupIds) => retryTransientCoordinatorRead((attemptTimeoutS) => completionDependencies.list({
3221
+ groupIds,
3222
+ remoteUrl,
3223
+ adminSecret: config.syncCoordinatorAdminSecret,
3224
+ timeoutS: attemptTimeoutS
3225
+ }), timeoutS, reconciliationDeadlineMs);
3226
+ const scopedGroupIds = [...new Set(scopedGroups.map((group) => group.groupId))];
3227
+ let records;
3228
+ try {
3229
+ records = await listCompletionRecords(scopedGroupIds);
3230
+ } catch (error) {
3231
+ if (isUnsupportedCompletionQuery(error)) records = [];
3232
+ else if (reconciliationOptions.isolateApplyFailures && isUnattributableCompletionBatchError(error)) records = await listCompletionRecordsPerGroup(scopedGroupIds, listCompletionRecords);
3233
+ else throw error;
3234
+ }
3235
+ const freshGroupByKey = /* @__PURE__ */ new Map();
3236
+ if (records.length > 0) try {
3237
+ const onlyGroup = scopedGroups.length === 1 ? scopedGroups[0] : void 0;
3238
+ const freshGroups = onlyGroup ? [await loadFreshGroupSnapshot(onlyGroup, remoteUrl, reconciliationDeadlineMs)] : await loadedSnapshots(void 0, reconciliationDeadlineMs);
3239
+ for (const freshGroup of freshGroups) {
3240
+ if (!freshGroup) continue;
3241
+ const key = freshGroupKey(freshGroup);
3242
+ if (freshGroupByKey.has(key)) throw new Error("team_setup_completion_invalid");
3243
+ freshGroupByKey.set(key, freshGroup);
3244
+ }
3245
+ } catch (error) {
3246
+ for (const record of records) {
3247
+ const group = groupById.get(record.group_id);
3248
+ if (!group || !belongsToGroupCandidate(record, group)) continue;
3249
+ try {
3250
+ validateLegacyTeamSetupCompletionManifestBinding(record.manifest, {
3251
+ coordinatorId: group.coordinatorId,
3252
+ groupId: group.groupId
3253
+ });
3254
+ } catch (bindingError) {
3255
+ if (!reconciliationOptions.isolateApplyFailures) throw bindingError;
3256
+ continue;
3257
+ }
3258
+ let canonicalManifest;
3259
+ try {
3260
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest(record.manifest, {
3261
+ coordinatorId: group.coordinatorId,
3262
+ groupId: group.groupId
3263
+ });
3264
+ } catch (validationError) {
3265
+ try {
3266
+ if (!await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => containLegacyTeamSetupCompletionConflict(store.db, {
3267
+ coordinatorId: group.coordinatorId,
3268
+ groupId: group.groupId
3269
+ }))) {
3270
+ if (!reconciliationOptions.isolateApplyFailures) throw validationError;
3271
+ continue;
3272
+ }
3273
+ } catch (containmentError) {
3274
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3275
+ }
3276
+ if (!reconciliationOptions.isolateApplyFailures) throw validationError;
3277
+ continue;
3278
+ }
3279
+ try {
3280
+ const localManifest = reconstructLegacyTeamSetupCompletionManifest(store.db, { candidateRef: canonicalManifest.candidate_ref });
3281
+ if (canonicalCoordinatorLegacyTeamCompletionManifestJson(localManifest) === canonicalCoordinatorLegacyTeamCompletionManifestJson(canonicalManifest)) continue;
3282
+ if (areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible(localManifest, canonicalManifest)) continue;
3283
+ } catch {}
3284
+ try {
3285
+ await runCandidateMutation(store.db, canonicalManifest.candidate_ref, () => containLegacyTeamSetupCompletionConflict(store.db, {
3286
+ coordinatorId: group.coordinatorId,
3287
+ groupId: group.groupId
3288
+ }));
3289
+ } catch (containmentError) {
3290
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3291
+ }
3292
+ }
3293
+ if (!reconciliationOptions.isolateApplyFailures) throw error;
3294
+ return;
3295
+ }
3296
+ const coordinatorCompletionGroupIds = /* @__PURE__ */ new Set();
3297
+ for (const record of records) {
3298
+ const group = groupById.get(record.group_id);
3299
+ if (!group) throw new Error("team_setup_completion_invalid");
3300
+ if (!belongsToGroupCandidate(record, group)) continue;
3301
+ try {
3302
+ validateLegacyTeamSetupCompletionManifestBinding(record.manifest, {
3303
+ coordinatorId: group.coordinatorId,
3304
+ groupId: group.groupId
3305
+ });
3306
+ } catch (bindingError) {
3307
+ if (!reconciliationOptions.isolateApplyFailures) throw bindingError;
3308
+ continue;
3309
+ }
3310
+ let canonicalManifest;
3311
+ try {
3312
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest(record.manifest, {
3313
+ coordinatorId: group.coordinatorId,
3314
+ groupId: group.groupId
3315
+ });
3316
+ } catch (validationError) {
3317
+ try {
3318
+ await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => containLegacyTeamSetupCompletionConflict(store.db, {
3319
+ coordinatorId: group.coordinatorId,
3320
+ groupId: group.groupId
3321
+ }));
3322
+ } catch (containmentError) {
3323
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3324
+ }
3325
+ if (!reconciliationOptions.isolateApplyFailures) throw validationError;
3326
+ continue;
3327
+ }
3328
+ coordinatorCompletionGroupIds.add(record.group_id);
3329
+ try {
3330
+ let freshGroup = freshGroupByKey.get(freshGroupKey(group));
3331
+ if (!freshGroup && scopedGroups.length > 1) {
3332
+ if (remainingCoordinatorTimeoutS(timeoutS, reconciliationDeadlineMs) == null) throw new Error("team_setup_roster_unavailable");
3333
+ freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, reconciliationDeadlineMs);
3334
+ }
3335
+ if (!freshGroup) {
3336
+ if (!await runCandidateMutation(store.db, legacyTeamCandidateId(group.coordinatorId, group.groupId), () => containLegacyTeamSetupCompletionConflict(store.db, {
3337
+ coordinatorId: group.coordinatorId,
3338
+ groupId: group.groupId
3339
+ }))) {
3340
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_invalid");
3341
+ continue;
3342
+ }
3343
+ throw new Error("team_setup_completion_invalid");
3344
+ }
3345
+ if (await applyCanonicalManifestIfStillAuthorized(store, authorization, group, canonicalManifest, freshGroup.devices) !== "applied") continue;
3346
+ } catch (error) {
3347
+ if (!reconciliationOptions.isolateApplyFailures) throw error;
3348
+ }
3349
+ }
3350
+ for (const group of scopedGroups) {
3351
+ if (coordinatorCompletionGroupIds.has(group.groupId)) continue;
3352
+ const publicationTimeoutS = remainingCoordinatorTimeoutS(timeoutS, reconciliationDeadlineMs);
3353
+ if (publicationTimeoutS == null) break;
3354
+ if (!hasCompletedLegacyTeamSetup(store, legacyTeamCandidateId(group.coordinatorId, group.groupId))) continue;
3355
+ const candidateRef = legacyTeamCandidateId(group.coordinatorId, group.groupId);
3356
+ let reconstructionFailed = false;
3357
+ try {
3358
+ if (!await runCandidateMutation(store.db, candidateRef, () => serializeRecipientPolicyPublicationMutation(store.db, () => serializeRecipientPolicyTeamMutation(store.db, deterministicPolicyTeamId(candidateRef), async () => {
3359
+ let manifest;
3360
+ try {
3361
+ manifest = reconstructLegacyTeamSetupCompletionManifest(store.db, { candidateRef });
3362
+ } catch {
3363
+ reconstructionFailed = true;
3364
+ return;
3365
+ }
3366
+ if (!stillAuthorizesGroup(authorization, group)) {
3367
+ reconstructionFailed = true;
3368
+ return;
3369
+ }
3370
+ await completionDependencies.create({
3371
+ groupId: group.groupId,
3372
+ manifest,
3373
+ remoteUrl,
3374
+ adminSecret: config.syncCoordinatorAdminSecret,
3375
+ timeoutS: publicationTimeoutS
3376
+ });
3377
+ }))) || reconstructionFailed) continue;
3378
+ } catch (error) {
3379
+ if (completionApiError(error) !== "team_setup_completion_conflict") continue;
3380
+ const getCompletion = completionDependencies.get;
3381
+ let canonicalManifest = null;
3382
+ try {
3383
+ if (!getCompletion) throw new Error("team_setup_completion_conflict");
3384
+ canonicalManifest = await retryTransientCoordinatorRead((attemptTimeoutS) => getCompletion({
3385
+ groupId: group.groupId,
3386
+ candidateRef,
3387
+ remoteUrl,
3388
+ adminSecret: config.syncCoordinatorAdminSecret,
3389
+ timeoutS: attemptTimeoutS
3390
+ }), timeoutS, reconciliationDeadlineMs);
3391
+ if (!canonicalManifest) throw new Error("team_setup_completion_conflict");
3392
+ } catch {
3393
+ try {
3394
+ if (!await runCandidateMutation(store.db, candidateRef, () => containLegacyTeamSetupCompletionConflict(store.db, {
3395
+ coordinatorId: group.coordinatorId,
3396
+ groupId: group.groupId
3397
+ }))) {
3398
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict");
3399
+ continue;
3400
+ }
3401
+ } catch (containmentError) {
3402
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3403
+ continue;
3404
+ }
3405
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict");
3406
+ continue;
3407
+ }
3408
+ try {
3409
+ const freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, reconciliationDeadlineMs);
3410
+ if (!freshGroup) throw new Error("team_setup_roster_unavailable");
3411
+ if (await applyCanonicalManifestIfStillAuthorized(store, authorization, group, canonicalManifest, freshGroup.devices) !== "applied") continue;
3412
+ } catch (applyError) {
3413
+ if (applyError instanceof LegacyTeamSetupAdditiveConvergenceError) {
3414
+ if (!reconciliationOptions.isolateApplyFailures) throw applyError;
3415
+ continue;
3416
+ }
3417
+ try {
3418
+ if (!await runCandidateMutation(store.db, candidateRef, () => containLegacyTeamSetupCompletionConflict(store.db, {
3419
+ coordinatorId: group.coordinatorId,
3420
+ groupId: group.groupId
3421
+ }))) {
3422
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict", { cause: applyError });
3423
+ continue;
3424
+ }
3425
+ } catch (containmentError) {
3426
+ if (!reconciliationOptions.isolateApplyFailures) throw containmentError;
3427
+ continue;
3428
+ }
3429
+ if (!reconciliationOptions.isolateApplyFailures) throw new Error("team_setup_completion_conflict", { cause: applyError });
3430
+ }
3431
+ }
3432
+ }
3433
+ } catch (error) {
3434
+ throw new Error(completionApiError(error));
3435
+ }
3436
+ }
3032
3437
  async function loadedCandidateSnapshots(candidateRef, loadOptions = {}) {
3033
3438
  const allowCachedSummaryFallback = loadOptions.allowCachedSummaryFallback ?? false;
3034
3439
  const invalidateSummaryCache = loadOptions.invalidateSummaryCache ?? true;
3035
3440
  if (invalidateSummaryCache) loadedSummarySnapshots.invalidate();
3036
3441
  try {
3037
- return await loadedSnapshots(candidateRef);
3442
+ return await loadedSnapshots(candidateRef, loadOptions.deadlineMs);
3038
3443
  } catch (error) {
3039
3444
  if (!allowCachedSummaryFallback) throw error;
3040
3445
  const cachedGroups = loadedSummarySnapshots.peek();
@@ -3046,6 +3451,15 @@ function teamSetupRoutes(options) {
3046
3451
  if (invalidateSummaryCache) loadedSummarySnapshots.invalidate();
3047
3452
  }
3048
3453
  }
3454
+ function persistedCompletionGroup(store, candidateRef) {
3455
+ const row = store.db.prepare(`SELECT coordinator_id, group_id FROM legacy_team_setup_drafts
3456
+ WHERE candidate_id = ? ORDER BY rowid DESC LIMIT 1`).get(candidateRef);
3457
+ if (!row || legacyTeamCandidateId(row.coordinator_id, row.group_id) !== candidateRef) return null;
3458
+ return {
3459
+ coordinatorId: row.coordinator_id,
3460
+ groupId: row.group_id
3461
+ };
3462
+ }
3049
3463
  app.get("/api/sync/team-setup/v1", async (c) => {
3050
3464
  let groups;
3051
3465
  try {
@@ -3055,9 +3469,12 @@ function teamSetupRoutes(options) {
3055
3469
  return c.json({ error: code }, errorStatus$1(code));
3056
3470
  }
3057
3471
  try {
3472
+ const store = options.getStore();
3473
+ readPureCandidateViews(store, groups);
3474
+ await reconcileCompletions(store, groups, { isolateApplyFailures: true });
3058
3475
  const response = {
3059
3476
  version: TEAM_SETUP_VERSION,
3060
- candidates: readPureCandidateViews(options.getStore(), groups).map(candidateSummary).filter((candidate) => candidate.status !== "ready")
3477
+ candidates: readPureCandidateViews(store, groups).map(candidateSummary).filter((candidate) => candidate.status !== "ready")
3061
3478
  };
3062
3479
  return c.json(response);
3063
3480
  } catch (error) {
@@ -3072,6 +3489,7 @@ function teamSetupRoutes(options) {
3072
3489
  const store = options.getStore();
3073
3490
  let draft = getLegacyTeamSetupDraft(store.db, candidateRef);
3074
3491
  const terminal = hasCompletedLegacyTeamSetup(store, candidateRef);
3492
+ let candidateGroupsForCompletion = null;
3075
3493
  if (terminal && draft?.state !== "completed") return c.json({ error: "team_setup_confirmation_stale" }, 404);
3076
3494
  let candidate;
3077
3495
  if (draft && (draft.state !== "completed" || terminal)) candidate = candidateFromDraft(draft);
@@ -3080,6 +3498,7 @@ function teamSetupRoutes(options) {
3080
3498
  allowCachedSummaryFallback: draft == null,
3081
3499
  invalidateSummaryCache: false
3082
3500
  })).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3501
+ candidateGroupsForCompletion = candidateGroups;
3083
3502
  const postLoadDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3084
3503
  if (hasCompletedLegacyTeamSetup(store, candidateRef)) {
3085
3504
  if (postLoadDraft?.state !== "completed") return c.json({ error: "team_setup_confirmation_stale" }, 404);
@@ -3093,6 +3512,17 @@ function teamSetupRoutes(options) {
3093
3512
  }
3094
3513
  }
3095
3514
  if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3515
+ const cachedCandidateGroups = loadedSummarySnapshots.peek()?.filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3516
+ let completionGroups = candidateGroupsForCompletion ?? (cachedCandidateGroups?.length ? cachedCandidateGroups : null);
3517
+ if (!completionGroups) {
3518
+ const persistedGroup = persistedCompletionGroup(store, candidateRef);
3519
+ completionGroups = persistedGroup ? [persistedGroup] : null;
3520
+ }
3521
+ const terminalBeforeReconciliation = hasCompletedLegacyTeamSetup(store, candidateRef);
3522
+ if (completionGroups) await reconcileCompletions(store, completionGroups);
3523
+ draft = getLegacyTeamSetupDraft(store.db, candidateRef);
3524
+ if (!draft || !terminalBeforeReconciliation && hasCompletedLegacyTeamSetup(store, candidateRef)) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3525
+ if (completionGroups) candidate = candidateFromDraft(draft);
3096
3526
  return c.json(detailResponse(store, draft, candidate));
3097
3527
  } catch (error) {
3098
3528
  const code = apiErrorCode(error);
@@ -3128,12 +3558,18 @@ function teamSetupRoutes(options) {
3128
3558
  ]) && 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
3559
  const target = identityChoices(store, candidateRef).find((choice) => choice.identityRef === targetIdentityRef);
3130
3560
  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)));
3561
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3562
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3563
+ try {
3564
+ return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupDeviceAssignment(store.db, {
3565
+ attemptId,
3566
+ deviceRef,
3567
+ targetIdentityId: target.identityId,
3568
+ expectation: device.expectation
3569
+ }), (draft) => detailResponse(store, draft)));
3570
+ } finally {
3571
+ releaseMutation();
3572
+ }
3137
3573
  } catch (error) {
3138
3574
  const code = apiErrorCode(error);
3139
3575
  return c.json({ error: code }, errorStatus$1(code));
@@ -3165,11 +3601,17 @@ function teamSetupRoutes(options) {
3165
3601
  const device = draft.devices.find((item) => item.deviceRef === deviceRef);
3166
3602
  if (!device) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3167
3603
  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)));
3604
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3605
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3606
+ try {
3607
+ return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupDeviceDecision(store.db, {
3608
+ attemptId,
3609
+ deviceRef,
3610
+ decision
3611
+ }), (draft) => detailResponse(store, draft)));
3612
+ } finally {
3613
+ releaseMutation();
3614
+ }
3173
3615
  } catch (error) {
3174
3616
  const code = apiErrorCode(error);
3175
3617
  return c.json({ error: code }, errorStatus$1(code));
@@ -3191,10 +3633,16 @@ function teamSetupRoutes(options) {
3191
3633
  if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3192
3634
  if (draft.state === "completed") return c.json({ error: "team_setup_confirmation_stale" }, 409);
3193
3635
  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)));
3636
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3637
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3638
+ try {
3639
+ return c.json(projectMutationAtomically(store, () => clearLegacyTeamSetupDeviceDecision(store.db, {
3640
+ attemptId,
3641
+ deviceRef
3642
+ }), (draft) => detailResponse(store, draft)));
3643
+ } finally {
3644
+ releaseMutation();
3645
+ }
3198
3646
  } catch (error) {
3199
3647
  const code = apiErrorCode(error);
3200
3648
  return c.json({ error: code }, errorStatus$1(code));
@@ -3220,11 +3668,17 @@ function teamSetupRoutes(options) {
3220
3668
  if (project.resolution === "deterministic") return c.json({ error: "team_setup_incomplete" }, 400);
3221
3669
  const target = projectMappingChoices(store).find((choice) => legacyTeamResolvedProjectRef(projectRef, choice.projectIdentity) === resolvedProjectRef);
3222
3670
  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)));
3671
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3672
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3673
+ try {
3674
+ return c.json(projectMutationAtomically(store, () => setLegacyTeamSetupProjectMapping(store.db, {
3675
+ attemptId,
3676
+ projectRef,
3677
+ resolvedProjectIdentity: target.projectIdentity
3678
+ }), (draft) => detailResponse(store, draft)));
3679
+ } finally {
3680
+ releaseMutation();
3681
+ }
3228
3682
  } catch (error) {
3229
3683
  const code = apiErrorCode(error);
3230
3684
  return c.json({ error: code }, errorStatus$1(code));
@@ -3252,13 +3706,19 @@ function teamSetupRoutes(options) {
3252
3706
  }
3253
3707
  if (groups.filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef).length === 0) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3254
3708
  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)));
3709
+ const releaseMutation = claimCandidateMutation(store.db, candidateRef);
3710
+ if (!releaseMutation) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3711
+ try {
3712
+ return c.json(projectMutationAtomically(store, () => {
3713
+ if (getLegacyTeamSetupDraft(store.db, candidateRef)?.state === "completed" || hasCompletedLegacyTeamSetup(store, candidateRef)) throw new Error("team_setup_confirmation_stale");
3714
+ return refreshLegacyTeamCandidate(store.db, {
3715
+ projection: projectionOptions(store),
3716
+ groups
3717
+ }, candidateRef);
3718
+ }, (draft) => detailResponse(store, draft)));
3719
+ } finally {
3720
+ releaseMutation();
3721
+ }
3262
3722
  } catch (error) {
3263
3723
  const code = apiErrorCode(error);
3264
3724
  return c.json({ error: code }, errorStatus$1(code));
@@ -3278,50 +3738,162 @@ function teamSetupRoutes(options) {
3278
3738
  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
3739
  const { attemptId, confirmedAccessDeltaDigest, confirmedViewerAccessDeltaDigest, finishDigest } = parsed.value;
3280
3740
  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);
3741
+ let releaseMutation;
3742
+ let releasePublicationMutation;
3743
+ let releaseActorMutations;
3281
3744
  try {
3282
3745
  const store = options.getStore();
3283
3746
  const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
3284
3747
  if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
3285
3748
  if (draft.state !== "completed" && hasCompletedLegacyTeamSetup(store, candidateRef)) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3286
3749
  if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3287
- if (draft.state !== "completed") {
3288
- const preview = previewLegacyTeamSetupActivation(store.db, {
3750
+ const replay = replayLegacyTeamSetupActivation(store.db, {
3751
+ candidateRef,
3752
+ attemptId,
3753
+ finishDigest,
3754
+ confirmedAccessDeltaDigest
3755
+ });
3756
+ if (replay) return c.json(finishResponse(candidateRef, replay));
3757
+ if (draft.state === "completed") return c.json({ error: "team_setup_confirmation_stale" }, 409);
3758
+ const preview = previewLegacyTeamSetupActivation(store.db, {
3759
+ candidateRef,
3760
+ attemptId: draft.attemptId
3761
+ });
3762
+ requireLegacyTeamSetupAccessDeltaWithinLimit(preview.accessDelta);
3763
+ const safeDraft = viewerSafeDraft(store, draft);
3764
+ if (viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, preview.accessDelta, {
3765
+ teamDisplayName: draft.displayName,
3766
+ ...safeDraft
3767
+ })) !== confirmedViewerAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3768
+ const matchingGroups = (await loadedCandidateSnapshots(candidateRef)).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
3769
+ if (matchingGroups.length !== 1) throw safeCoordinatorError();
3770
+ const group = matchingGroups[0];
3771
+ if (!group) throw safeCoordinatorError();
3772
+ releaseMutation = claimCandidateMutation(store.db, candidateRef) ?? void 0;
3773
+ if (!releaseMutation) {
3774
+ await candidateMutationSettled(store.db, candidateRef);
3775
+ const overlappingReplay = replayLegacyTeamSetupActivation(store.db, {
3289
3776
  candidateRef,
3290
- attemptId: draft.attemptId
3777
+ attemptId,
3778
+ finishDigest,
3779
+ confirmedAccessDeltaDigest
3291
3780
  });
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);
3781
+ if (overlappingReplay) return c.json(finishResponse(candidateRef, overlappingReplay));
3782
+ return c.json({ error: "team_setup_confirmation_stale" }, 409);
3298
3783
  }
3299
- const result = await finishLegacyTeamSetupActivation(store.db, {
3784
+ const claimedReplay = replayLegacyTeamSetupActivation(store.db, {
3300
3785
  candidateRef,
3301
3786
  attemptId,
3302
3787
  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
- }
3788
+ confirmedAccessDeltaDigest
3320
3789
  });
3321
- return c.json(finishResponse(candidateRef, result));
3790
+ if (claimedReplay) return c.json(finishResponse(candidateRef, claimedReplay));
3791
+ const claimedDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3792
+ if (!claimedDraft || claimedDraft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3793
+ const publicationDeadlineMs = Date.now() + COORDINATOR_ROSTER_READ_BUDGET_MS;
3794
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
3795
+ releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, claimedDraft.devices.flatMap((device) => [...device.decision === "included" && device.targetIdentityId ? [device.targetIdentityId] : [], ...device.existingIdentityId ? [device.existingIdentityId] : []]));
3796
+ return await serializeRecipientPolicyTeamMutation(store.db, deterministicPolicyTeamId(candidateRef), () => serializeRecipientPolicyCoordinatorGroupMutation(store.db, group.groupId, async () => {
3797
+ let authorization;
3798
+ try {
3799
+ authorization = currentCompletionAuthorization();
3800
+ } catch (error) {
3801
+ throw new Error(completionApiError(error));
3802
+ }
3803
+ if (!completionDependencies || !authorization) throw new Error("team_setup_completion_unavailable");
3804
+ if (!authorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
3805
+ const { config, remoteUrl } = authorization;
3806
+ const freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
3807
+ if (!freshGroup) throw new Error("team_setup_roster_unavailable");
3808
+ if (!stillAuthorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
3809
+ const projectInventory = legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef);
3810
+ const freshPreview = inspectFreshLegacyTeamSetupActivation(store.db, {
3811
+ candidateRef,
3812
+ attemptId,
3813
+ freshRoster: freshGroup.devices,
3814
+ projectInventory
3815
+ });
3816
+ if (freshPreview.finishDigest !== finishDigest || freshPreview.accessDeltaDigest !== confirmedAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3817
+ const freshDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
3818
+ if (!freshDraft || freshDraft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3819
+ const safeFreshDraft = viewerSafeDraft(store, freshDraft);
3820
+ if (viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, freshPreview.accessDelta, {
3821
+ teamDisplayName: freshDraft.displayName,
3822
+ ...safeFreshDraft
3823
+ })) !== confirmedViewerAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
3824
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3825
+ const proposedManifest = deriveLegacyTeamSetupCompletionManifest(store.db, {
3826
+ candidateRef,
3827
+ attemptId,
3828
+ completedAt
3829
+ });
3830
+ const timeoutS = Math.max(1, config.syncCoordinatorTimeoutS);
3831
+ const publicationTimeoutS = remainingCoordinatorTimeoutS(timeoutS, publicationDeadlineMs);
3832
+ if (publicationTimeoutS == null) throw new Error("team_setup_completion_unavailable");
3833
+ let canonicalManifest;
3834
+ try {
3835
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest((await completionDependencies.create({
3836
+ groupId: group.groupId,
3837
+ manifest: proposedManifest,
3838
+ remoteUrl,
3839
+ adminSecret: config.syncCoordinatorAdminSecret,
3840
+ timeoutS: publicationTimeoutS
3841
+ })).manifest, {
3842
+ coordinatorId: group.coordinatorId,
3843
+ groupId: group.groupId
3844
+ });
3845
+ if (canonicalCoordinatorLegacyTeamCompletionManifestJson(canonicalManifest) !== canonicalCoordinatorLegacyTeamCompletionManifestJson(proposedManifest)) throw new Error("team_setup_completion_conflict");
3846
+ } catch (error) {
3847
+ const code = completionApiError(error);
3848
+ const getCompletion = completionDependencies.get;
3849
+ if (code !== "team_setup_completion_conflict" || !getCompletion) throw new Error(code);
3850
+ try {
3851
+ const existing = await retryTransientCoordinatorRead((attemptTimeoutS) => getCompletion({
3852
+ groupId: group.groupId,
3853
+ candidateRef,
3854
+ remoteUrl,
3855
+ adminSecret: config.syncCoordinatorAdminSecret,
3856
+ timeoutS: attemptTimeoutS
3857
+ }), timeoutS, publicationDeadlineMs);
3858
+ if (!existing) throw new Error("team_setup_completion_conflict");
3859
+ canonicalManifest = validateLegacyTeamSetupCompletionManifest(existing, {
3860
+ coordinatorId: group.coordinatorId,
3861
+ groupId: group.groupId
3862
+ });
3863
+ if (canonicalManifest.finish_digest !== proposedManifest.finish_digest) throw new Error("team_setup_completion_conflict");
3864
+ } catch (recoveryError) {
3865
+ throw new Error(completionApiError(recoveryError));
3866
+ }
3867
+ }
3868
+ let postPublicationGroup;
3869
+ try {
3870
+ postPublicationGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
3871
+ } catch (error) {
3872
+ if (legacyTeamSetupApiErrorCode(error) === "team_setup_completion_invalid") throw new Error("team_setup_roster_unavailable");
3873
+ throw error;
3874
+ }
3875
+ if (!postPublicationGroup) throw new Error("team_setup_roster_unavailable");
3876
+ const result = await applyLegacyTeamSetupCompletionManifestAndReturnActivation(store.db, {
3877
+ coordinatorId: group.coordinatorId,
3878
+ groupId: group.groupId,
3879
+ freshRoster: postPublicationGroup.devices,
3880
+ manifest: canonicalManifest,
3881
+ expectedDraftManifest: proposedManifest,
3882
+ recipientPolicyLocksHeld: {
3883
+ publication: true,
3884
+ team: true,
3885
+ coordinatorGroup: true
3886
+ }
3887
+ });
3888
+ return c.json(finishResponse(candidateRef, result));
3889
+ }));
3322
3890
  } catch (error) {
3323
3891
  const code = apiErrorCode(error);
3324
3892
  return c.json({ error: code }, errorStatus$1(code));
3893
+ } finally {
3894
+ releaseActorMutations?.();
3895
+ releasePublicationMutation?.();
3896
+ releaseMutation?.();
3325
3897
  }
3326
3898
  });
3327
3899
  return app;
@@ -3670,7 +4242,7 @@ function projectInviteAcceptanceFailure(error) {
3670
4242
  }
3671
4243
  var PROJECT_INVITE_TTL_HOURS = 168;
3672
4244
  var PROJECT_INVITE_OWNER_FALLBACK = "Project owner";
3673
- var PROJECT_INVITE_BODY_KEYS = new Set([
4245
+ var PROJECT_INVITE_BODY_KEYS = /* @__PURE__ */ new Set([
3674
4246
  "teammate_name",
3675
4247
  "project_ids",
3676
4248
  "reviewed_project_set_digest"
@@ -3695,7 +4267,7 @@ function projectInviteStringList(body, key) {
3695
4267
  return items;
3696
4268
  }
3697
4269
  function assertProjectInviteBody(body, creating) {
3698
- const allowed = creating ? PROJECT_INVITE_BODY_KEYS : new Set(["teammate_name", "project_ids"]);
4270
+ const allowed = creating ? PROJECT_INVITE_BODY_KEYS : /* @__PURE__ */ new Set(["teammate_name", "project_ids"]);
3699
4271
  if (Object.keys(body).some((key) => !allowed.has(key))) throw new Error("unexpected_project_invite_fields");
3700
4272
  if (typeof body.teammate_name !== "string") throw new Error("teammate_name_must_be_string");
3701
4273
  if (creating && typeof body.reviewed_project_set_digest !== "string") throw new Error("reviewed_project_set_digest_required");
@@ -3807,7 +4379,7 @@ function recipientInviteOnboardingPreviewFromReviewedIntent(store, body, invitat
3807
4379
  ...binding
3808
4380
  });
3809
4381
  }
3810
- var SAFE_RECIPIENT_INVITE_ERRORS = new Set([
4382
+ var SAFE_RECIPIENT_INVITE_ERRORS = /* @__PURE__ */ new Set([
3811
4383
  "device_display_name_invalid",
3812
4384
  "device_display_name_required",
3813
4385
  "device_display_name_too_long",
@@ -3841,20 +4413,21 @@ async function peerSupportsSyncRequirements(store, deviceId, requirements) {
3841
4413
  const addresses = safeJsonList(row?.addresses_json ?? null);
3842
4414
  for (const address of addresses) try {
3843
4415
  const url = `${buildBaseUrl(address)}/v1/status`;
4416
+ const headers = {
4417
+ ...buildDirectPeerAuthHeaders({
4418
+ deviceId: localDeviceId,
4419
+ recipientId: deviceId,
4420
+ dbPath: store.dbPath,
4421
+ method: "GET",
4422
+ url,
4423
+ bodyBytes: Buffer.alloc(0),
4424
+ keysDir: syncKeysDir()
4425
+ }),
4426
+ [SYNC_CAPABILITY_HEADER]: LOCAL_SYNC_CAPABILITY,
4427
+ [SYNC_FEATURES_HEADER]: LOCAL_SYNC_FEATURES.join(",")
4428
+ };
3844
4429
  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
- },
4430
+ headers,
3858
4431
  timeoutS: 5
3859
4432
  });
3860
4433
  if (status < 200 || status >= 300) continue;
@@ -4226,9 +4799,9 @@ var AUTOMATIC_SHARE_OPERATION_STATES = [
4226
4799
  "initial_sync",
4227
4800
  "waiting_for_device"
4228
4801
  ];
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([
4802
+ var AUTOMATIC_WAITING_ACCEPTANCE_RETRY_COOLDOWN_MS = 3e4;
4803
+ var AUTOMATIC_WAITING_DEVICE_RETRY_COOLDOWN_MS = 3e5;
4804
+ var TERMINAL_SHARE_MAINTENANCE_ERRORS = /* @__PURE__ */ new Set([
4232
4805
  "coordinator_not_configured",
4233
4806
  "team_sharing_not_configured",
4234
4807
  "team_selection_ambiguous",
@@ -4242,7 +4815,7 @@ var TERMINAL_SHARE_MAINTENANCE_ERRORS = new Set([
4242
4815
  "intent_conflict",
4243
4816
  "inviter_identity_conflict"
4244
4817
  ]);
4245
- var PROJECT_INVITE_OWNER_CONFLICT_ERRORS = new Set([
4818
+ var PROJECT_INVITE_OWNER_CONFLICT_ERRORS = /* @__PURE__ */ new Set([
4246
4819
  "operation_scope_mismatch",
4247
4820
  "operation_state_invalid",
4248
4821
  "operation_acceptance_invalid",
@@ -4257,7 +4830,7 @@ var PROJECT_INVITE_OWNER_CONFLICT_ERRORS = new Set([
4257
4830
  "intent_conflict",
4258
4831
  "inviter_identity_conflict"
4259
4832
  ]);
4260
- var TERMINAL_RECONCILIATION_ERRORS = new Set([
4833
+ var TERMINAL_RECONCILIATION_ERRORS = /* @__PURE__ */ new Set([
4261
4834
  "coordinator_not_configured",
4262
4835
  "team_sharing_not_configured",
4263
4836
  "team_selection_ambiguous",
@@ -4451,7 +5024,7 @@ function coordinatorEnrollmentFailureCode(error) {
4451
5024
  const message = (error instanceof Error ? error.message : String(error)).trim();
4452
5025
  const httpStatus = message.match(/\((\d{3})(?::|\))/u)?.[1];
4453
5026
  if (httpStatus) return `http_${httpStatus}`;
4454
- if (new Set([
5027
+ if ((/* @__PURE__ */ new Set([
4455
5028
  "coordinator_consumed_team_invite_invalid",
4456
5029
  "coordinator_device_list_malformed",
4457
5030
  "coordinator_group_id_invalid",
@@ -4459,7 +5032,7 @@ function coordinatorEnrollmentFailureCode(error) {
4459
5032
  "coordinator_invite_group_mismatch",
4460
5033
  "coordinator_invite_list_malformed",
4461
5034
  "reconciliation_time_invalid"
4462
- ]).has(message)) return message;
5035
+ ])).has(message)) return message;
4463
5036
  if (/timeout|timed out|abort/iu.test(message)) return "request_timeout";
4464
5037
  if (/fetch failed|network|econn|enotfound/iu.test(message)) return "network_error";
4465
5038
  return "unexpected_error";
@@ -4517,7 +5090,7 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
4517
5090
  continue;
4518
5091
  }
4519
5092
  try {
4520
- const result = reconcileSnapshot({
5093
+ const result = await reconcileSnapshot({
4521
5094
  coordinatorId: buildBaseUrl(remoteUrl),
4522
5095
  groupId,
4523
5096
  enrollments: enrollmentsResult.value,
@@ -4540,7 +5113,7 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
4540
5113
  var RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT = 10;
4541
5114
  var RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT = 3;
4542
5115
  var RECIPIENT_POLICY_MAINTENANCE_BACKOFF_MS = 6e4;
4543
- var RECIPIENT_POLICY_WAITING_ERRORS = new Set([
5116
+ var RECIPIENT_POLICY_WAITING_ERRORS = /* @__PURE__ */ new Set([
4544
5117
  "recipient_policy_capability_undetermined",
4545
5118
  "recipient_policy_parity_incomplete",
4546
5119
  "recipient_policy_snapshot_not_fresh"
@@ -5709,7 +6282,7 @@ function mapSyncAttemptRow(row, showDiag, addresses) {
5709
6282
  address: showDiag && addresses?.length ? addresses[0] : null
5710
6283
  };
5711
6284
  }
5712
- var SYNC_SCOPE_REJECTION_WINDOW_SECONDS = 1440 * 60;
6285
+ var SYNC_SCOPE_REJECTION_WINDOW_SECONDS = 86400;
5713
6286
  var CLEANUP_DIAGNOSTICS_MAX_ROWS = 250;
5714
6287
  function recentScopeRejectionsByPeer(store) {
5715
6288
  const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_SCOPE_REJECTION_WINDOW_SECONDS * 1e3)).toISOString();
@@ -6017,7 +6590,7 @@ function reassignLegacySharedReviewGroup(store, input) {
6017
6590
  legacy_shared_review: legacySharedReviewSummary(store)
6018
6591
  };
6019
6592
  }
6020
- var SYNC_DIRECTION_WINDOW_SECONDS = 1440 * 60;
6593
+ var SYNC_DIRECTION_WINDOW_SECONDS = 86400;
6021
6594
  function recentPeerOps(store) {
6022
6595
  const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_DIRECTION_WINDOW_SECONDS * 1e3)).toISOString();
6023
6596
  const rows = store.db.prepare(`SELECT peer_device_id,
@@ -6112,7 +6685,8 @@ var PEERS_QUERY = `
6112
6685
  * scope contents.
6113
6686
  */
6114
6687
  function negotiatedSyncCapability(c) {
6115
- return negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(c.req.header(SYNC_CAPABILITY_HEADER) ?? null));
6688
+ const header = c.req.header(SYNC_CAPABILITY_HEADER) ?? null;
6689
+ return negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(header));
6116
6690
  }
6117
6691
  /**
6118
6692
  * Peer-to-peer sync protocol routes (/v1/*).
@@ -6173,7 +6747,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
6173
6747
  });
6174
6748
  const [deviceId, fingerprint] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
6175
6749
  const syncReset = getSyncResetState(store.db);
6176
- const authorizedScopes = isScopedSyncCapability(negotiatedSyncCapability(c)) ? listAuthorizedScopesForPeer(store.db, {
6750
+ const negotiated = negotiatedSyncCapability(c);
6751
+ const authorizedScopes = isScopedSyncCapability(negotiated) ? listAuthorizedScopesForPeer(store.db, {
6177
6752
  localDeviceId: deviceId,
6178
6753
  peerDeviceId: auth.deviceId
6179
6754
  }) : null;
@@ -6560,7 +7135,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6560
7135
  const bindings = body.bindings.map((value) => {
6561
7136
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6562
7137
  const binding = value;
6563
- const bindingKeys = new Set([
7138
+ const bindingKeys = /* @__PURE__ */ new Set([
6564
7139
  "deviceId",
6565
7140
  "targetIdentityId",
6566
7141
  "confirmed",
@@ -6604,7 +7179,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6604
7179
  const body = parseDeviceBindingRequest(await parseViewerJsonBody(c), true);
6605
7180
  if (!body || !("reviewedInventoryDigest" in body)) return c.json({ error: "binding_request_invalid" }, 400);
6606
7181
  const store = getStore();
7182
+ let releasePublicationMutation;
6607
7183
  try {
7184
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
6608
7185
  ensureStableStoreIdentity(store);
6609
7186
  const result = commitDeviceIdentityBindings(store.db, {
6610
7187
  localActorId: store.actorId,
@@ -6620,6 +7197,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6620
7197
  return c.json({ error: "binding_commit_busy" }, 503);
6621
7198
  }
6622
7199
  return c.json({ error: "binding_commit_failed" }, 500);
7200
+ } finally {
7201
+ releasePublicationMutation?.();
6623
7202
  }
6624
7203
  });
6625
7204
  app.get("/api/sync/status", async (c) => {
@@ -6758,7 +7337,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6758
7337
  if (addrs.length) peerAddressMap.set(String(peerRow.peer_device_id ?? ""), addrs);
6759
7338
  });
6760
7339
  const attemptsItems = attemptRows.map((row) => {
6761
- return mapSyncAttemptRow(row, showDiag, showDiag ? peerAddressMap.get(String(row.peer_device_id ?? "")) : void 0);
7340
+ const addrs = showDiag ? peerAddressMap.get(String(row.peer_device_id ?? "")) : void 0;
7341
+ return mapSyncAttemptRow(row, showDiag, addrs);
6762
7342
  });
6763
7343
  const latestAttemptError = String(attemptRows[0]?.error || "").trim();
6764
7344
  const statusBlock = {
@@ -6837,7 +7417,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6837
7417
  const limit = queryInt(c.req.query("limit"), 100);
6838
7418
  if (limit <= 0) return c.json({ error: "invalid_limit" }, 400);
6839
7419
  const sinceMinutesRaw = queryInt(c.req.query("sinceMinutes"), 1440);
6840
- const sinceMinutes = Math.min(Math.max(sinceMinutesRaw, 0), 10 * 365 * 24 * 60);
7420
+ const sinceMinutes = Math.min(Math.max(sinceMinutesRaw, 0), 5256e3);
6841
7421
  const sinceIso = sinceMinutes > 0 ? (/* @__PURE__ */ new Date(Date.now() - sinceMinutes * 6e4)).toISOString() : void 0;
6842
7422
  const records = listInboundScopeRejections(store.db, {
6843
7423
  peerDeviceId,
@@ -7064,7 +7644,14 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7064
7644
  const store = getStore();
7065
7645
  const body = await parseViewerJsonBody(c);
7066
7646
  try {
7067
- const result = commitRecipientPolicyEdges(store.db, body);
7647
+ const request = parseRecipientPolicyEdgeCommitRequest(body);
7648
+ const teamIds = request ? [...new Set(request.changes.flatMap((change) => change.recipient.recipientKind === "team" ? [change.recipient.teamId] : []))].toSorted() : [];
7649
+ const commit = async (index) => {
7650
+ const teamId = teamIds[index];
7651
+ if (!teamId) return commitRecipientPolicyEdges(store.db, request ?? body);
7652
+ return serializeRecipientPolicyTeamMutation(store.db, teamId, () => commit(index + 1));
7653
+ };
7654
+ const result = await commit(0);
7068
7655
  if (result.status === "invalid") return c.json(result, 400);
7069
7656
  if (result.status === "not_found") return c.json(result, 404);
7070
7657
  if (result.status === "stale" || result.status === "conflict") return c.json(result, 409);
@@ -7187,7 +7774,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7187
7774
  const parseReviewRequest = (value) => {
7188
7775
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7189
7776
  const body = value;
7190
- const allowedKeys = new Set([
7777
+ const allowedKeys = /* @__PURE__ */ new Set([
7191
7778
  "reviewItemId",
7192
7779
  "sourceFingerprint",
7193
7780
  "decision",
@@ -7203,7 +7790,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7203
7790
  };
7204
7791
  app.post("/api/sync/recipient-policy/v1/review/resolve", async (c) => {
7205
7792
  const store = getStore();
7206
- const request = parseReviewRequest(await parseViewerJsonBody(c));
7793
+ const body = await parseViewerJsonBody(c);
7794
+ const request = parseReviewRequest(body);
7207
7795
  if (!request) return c.json({ error: "request_invalid" }, 400);
7208
7796
  ensureLocalActorRecord(store);
7209
7797
  const result = resolveRecipientPolicyReview(store.db, {
@@ -7518,9 +8106,11 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7518
8106
  const store = getStore();
7519
8107
  const body = await parseViewerJsonBody(c);
7520
8108
  if (!body) return c.json({ error: "invalid json" }, 400);
8109
+ let releasePublicationMutation;
7521
8110
  try {
7522
8111
  const confirmedGuardrailTokens = optionalViewerStringList(body, "confirmed_guardrail_tokens");
7523
8112
  const mappingInput = parseViewerProjectMappingInput(body);
8113
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
7524
8114
  const analysis = analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput);
7525
8115
  if (analysis.requested_workspace_identity?.startsWith("unmapped:")) return c.json({
7526
8116
  error: "unmapped_project_local_only",
@@ -7550,12 +8140,15 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7550
8140
  });
7551
8141
  } catch (error) {
7552
8142
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
8143
+ } finally {
8144
+ releasePublicationMutation?.();
7553
8145
  }
7554
8146
  });
7555
8147
  app.put("/api/sync/sharing-domains/project-mappings/bulk", async (c) => {
7556
8148
  const store = getStore();
7557
8149
  const body = await parseViewerJsonBody(c);
7558
8150
  if (!body) return c.json({ error: "invalid json" }, 400);
8151
+ let releasePublicationMutation;
7559
8152
  try {
7560
8153
  const rawMappings = body.mappings;
7561
8154
  if (!Array.isArray(rawMappings) || rawMappings.length === 0) return c.json({ error: "mappings must be a non-empty array" }, 400);
@@ -7563,6 +8156,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7563
8156
  if (raw == null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("each mapping must be an object");
7564
8157
  return parseViewerProjectMappingInput(raw);
7565
8158
  });
8159
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
7566
8160
  const analyses = mappingInputs.map((mappingInput) => analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput));
7567
8161
  const unmapped = analyses.find((analysis) => analysis.requested_workspace_identity?.startsWith("unmapped:"));
7568
8162
  if (unmapped) return c.json({
@@ -7593,12 +8187,16 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7593
8187
  });
7594
8188
  } catch (error) {
7595
8189
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
8190
+ } finally {
8191
+ releasePublicationMutation?.();
7596
8192
  }
7597
8193
  });
7598
- app.delete("/api/sync/sharing-domains/project-mappings/:id", (c) => {
8194
+ app.delete("/api/sync/sharing-domains/project-mappings/:id", async (c) => {
7599
8195
  const store = getStore();
7600
8196
  const id = Number(c.req.param("id"));
8197
+ let releasePublicationMutation;
7601
8198
  try {
8199
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
7602
8200
  const deleted = deleteProjectScopeSettingsMapping(store.db, id);
7603
8201
  return c.json({
7604
8202
  ok: true,
@@ -7606,6 +8204,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7606
8204
  });
7607
8205
  } catch (error) {
7608
8206
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
8207
+ } finally {
8208
+ releasePublicationMutation?.();
7609
8209
  }
7610
8210
  });
7611
8211
  app.post("/api/sync/peers/identity", async (c) => {
@@ -7734,6 +8334,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7734
8334
  if (!secondaryActorId) return c.json({ error: "secondary_actor_id required" }, 400);
7735
8335
  if (primaryActorId === secondaryActorId) return c.json({ error: "actor ids must differ" }, 400);
7736
8336
  const d = drizzle(store.db, { schema });
8337
+ const releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, [primaryActorId, secondaryActorId]);
8338
+ queueMicrotask(releaseActorMutations);
7737
8339
  const now = (/* @__PURE__ */ new Date()).toISOString();
7738
8340
  const result = store.db.transaction(() => {
7739
8341
  const primary = d.select().from(schema.actors).where(eq(schema.actors.actor_id, primaryActorId)).get();
@@ -7884,6 +8486,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
7884
8486
  const actorId = String(body.actor_id ?? "").trim();
7885
8487
  if (!actorId) return c.json({ error: "actor_id required" }, 400);
7886
8488
  if (actorId === store.actorId) return c.json({ error: "cannot deactivate this device's own local actor" }, 409);
8489
+ const releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, [actorId]);
8490
+ queueMicrotask(releaseActorMutations);
7887
8491
  const d = drizzle(store.db, { schema });
7888
8492
  const actor = d.select().from(schema.actors).where(eq(schema.actors.actor_id, actorId)).get();
7889
8493
  if (!actor) return c.json({ error: "actor not found" }, 404);
@@ -9114,7 +9718,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9114
9718
  const publicKey = String(body.peer_public_key ?? "").trim();
9115
9719
  const name = String(body.name ?? "").trim() || null;
9116
9720
  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));
9721
+ const addressesInput = Array.isArray(body.peer_addresses) ? body.peer_addresses : [];
9722
+ const addresses = mergeAddresses([], addressesInput.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0));
9118
9723
  if (!peerDeviceId || !publicKey) return c.json({ error: "peer_device_id_and_public_key_required" }, 400);
9119
9724
  const fingerprint = requestedFingerprint || fingerprintPublicKey(publicKey);
9120
9725
  if (fingerprintPublicKey(publicKey) !== fingerprint) return c.json({ error: "fingerprint_mismatch" }, 400);
@@ -9203,7 +9808,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9203
9808
  error: "coordinator_not_configured",
9204
9809
  status
9205
9810
  }, 400);
9206
- const existing = getCoordinatorGroupPreference(getStore().db, coordinatorId, groupId);
9811
+ const store = getStore();
9812
+ const existing = getCoordinatorGroupPreference(store.db, coordinatorId, groupId);
9207
9813
  if (existing) return c.json({
9208
9814
  preferences: existing,
9209
9815
  status
@@ -9389,12 +9995,13 @@ function updateStatusRoutes(options = {}) {
9389
9995
  const app = new Hono();
9390
9996
  app.get("/api/update-status", async (c) => {
9391
9997
  try {
9998
+ const installKind = detectInstallKind({
9999
+ entryPath: process.argv[1] ?? "",
10000
+ env: process.env
10001
+ });
9392
10002
  const status = await getUpdateStatus$1({
9393
10003
  currentVersion: VERSION$1,
9394
- installKind: detectInstallKind({
9395
- entryPath: process.argv[1] ?? "",
9396
- env: process.env
9397
- })
10004
+ installKind
9398
10005
  });
9399
10006
  return c.json(status);
9400
10007
  } catch {
@@ -9455,6 +10062,8 @@ function createApp(opts) {
9455
10062
  app.route("/", teamSetupRoutes({
9456
10063
  getStore: storeFactory,
9457
10064
  loadLegacyTeamConfiguredGroupSnapshots: opts?.loadLegacyTeamConfiguredGroupSnapshots,
10065
+ completionDependencies: opts?.teamSetupCompletionDependencies,
10066
+ readCoordinatorConfig: opts?.readCoordinatorConfig,
9458
10067
  registerSummaryInvalidator: (invalidate) => {
9459
10068
  invalidateTeamSetupSummary = invalidate;
9460
10069
  }