@codemem/server 0.42.0 → 0.43.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, 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, 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, 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, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inviteTokenDigest, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, 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, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, rejectInboundScopeFailures, requestJson, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, 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, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, 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, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, finishLegacyTeamSetupActivation, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, 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, requestJson, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, 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";
@@ -28,7 +28,8 @@ function isLoopbackOrigin(origin) {
28
28
  }
29
29
  if (url.protocol !== "http:" && url.protocol !== "https:") return false;
30
30
  if (url.username || url.password) return false;
31
- return LOOPBACK_HOSTS.has(url.hostname);
31
+ const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
32
+ return LOOPBACK_HOSTS.has(hostname);
32
33
  }
33
34
  /** HTTP methods that mutate state and require origin validation. */
34
35
  var UNSAFE_METHODS = new Set([
@@ -61,7 +62,8 @@ function isUnsafeMissingOrigin(c) {
61
62
  *
62
63
  * Ports Python's `reject_cross_origin(missing_origin_policy="reject_if_unsafe")`:
63
64
  *
64
- * - GET/HEAD/OPTIONS: allowed from any origin (viewer is local-only).
65
+ * - GET/HEAD/OPTIONS: allowed from any origin unless the caller marks a
66
+ * side-effecting GET path prefix as unsafe.
65
67
  * - POST/DELETE/PATCH/PUT:
66
68
  * - Origin present + loopback → allowed (browser on localhost)
67
69
  * - Origin present + non-loopback → rejected 403
@@ -72,20 +74,21 @@ function isUnsafeMissingOrigin(c) {
72
74
  * Access-Control-Allow-Origin is set — the browser doesn't need it.
73
75
  * For valid loopback origins, ACAO is echoed back.
74
76
  */
75
- function originGuard() {
77
+ function originGuard(options) {
76
78
  return createMiddleware(async (c, next) => {
77
79
  const origin = c.req.header("Origin");
78
80
  const method = c.req.method;
79
- if (UNSAFE_METHODS.has(method)) {
81
+ const unsafeGet = (method === "GET" || method === "HEAD") && options?.unsafeGetPathPrefixes?.some((prefix) => c.req.path.startsWith(prefix));
82
+ if (UNSAFE_METHODS.has(method) || unsafeGet) {
80
83
  if (origin) {
81
84
  if (!isLoopbackOrigin(origin)) return c.json({ error: "forbidden" }, 403);
82
85
  c.header("Access-Control-Allow-Origin", origin);
83
- c.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
86
+ c.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
84
87
  c.header("Access-Control-Allow-Headers", "Content-Type");
85
88
  } else if (isUnsafeMissingOrigin(c)) return c.json({ error: "forbidden" }, 403);
86
89
  } else if (origin && isLoopbackOrigin(origin)) {
87
90
  c.header("Access-Control-Allow-Origin", origin);
88
- c.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
91
+ c.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
89
92
  c.header("Access-Control-Allow-Headers", "Content-Type");
90
93
  }
91
94
  await next();
@@ -104,7 +107,7 @@ function preflightHandler() {
104
107
  const origin = c.req.header("Origin");
105
108
  if (origin && isLoopbackOrigin(origin)) {
106
109
  c.header("Access-Control-Allow-Origin", origin);
107
- c.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
110
+ c.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
108
111
  c.header("Access-Control-Allow-Headers", "Content-Type");
109
112
  c.header("Access-Control-Max-Age", "86400");
110
113
  return c.body(null, 204);
@@ -3124,13 +3127,13 @@ var TERMINAL_RECONCILIATION_ERRORS = new Set([
3124
3127
  "operation_not_found",
3125
3128
  ...PROJECT_INVITE_OWNER_CONFLICT_ERRORS
3126
3129
  ]);
3127
- function errorStatus(error) {
3130
+ function errorStatus$1(error) {
3128
3131
  const value = error && typeof error === "object" ? error.status : null;
3129
3132
  return typeof value === "number" && Number.isInteger(value) && value >= 400 && value <= 599 ? value : null;
3130
3133
  }
3131
3134
  function projectInviteOwnerErrorResponse(error, fallback) {
3132
3135
  const message = error instanceof Error ? error.message : "";
3133
- const status = errorStatus(error);
3136
+ const status = errorStatus$1(error);
3134
3137
  if (message === "operation_not_found" && (status == null || status === 404)) return {
3135
3138
  code: message,
3136
3139
  status: 404
@@ -3152,7 +3155,7 @@ function projectInviteOwnerErrorResponse(error, fallback) {
3152
3155
  function isRetryableReconciliationError(error) {
3153
3156
  const message = error instanceof Error ? error.message : String(error);
3154
3157
  if (TERMINAL_RECONCILIATION_ERRORS.has(message)) return false;
3155
- const status = errorStatus(error);
3158
+ const status = errorStatus$1(error);
3156
3159
  return status == null || status === 408 || status === 429 || status >= 500;
3157
3160
  }
3158
3161
  function safeReconciliationErrorCode(error) {
@@ -4409,7 +4412,8 @@ async function acceptDiscoveredPeer(store, input) {
4409
4412
  const addressesJson = JSON.stringify(mergeAddresses(nextAddresses, existingAddresses));
4410
4413
  await createCoordinatorReciprocalApproval(store, config, {
4411
4414
  groupId,
4412
- requestedDeviceId: input.peerDeviceId
4415
+ requestedDeviceId: input.peerDeviceId,
4416
+ ...input.expectedIncomingRequestId ? { expectedIncomingRequestId: input.expectedIncomingRequestId } : {}
4413
4417
  });
4414
4418
  const coordinatorUrl = config.syncCoordinatorUrl || null;
4415
4419
  let scopeInclude;
@@ -5973,10 +5977,18 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
5973
5977
  if (!value || typeof value !== "object" || Array.isArray(value)) return c.json({ error: "request_invalid" }, 400);
5974
5978
  const body = value;
5975
5979
  if (Object.keys(body).some((key) => key !== "dryRun") || Object.hasOwn(body, "dryRun") && typeof body.dryRun !== "boolean") return c.json({ error: "request_invalid" }, 400);
5976
- return c.json(migrateRecipientPolicyIntent(store.db, {
5977
- localActorId: store.actorId,
5978
- localDeviceId: store.deviceId
5979
- }, { dryRun: body.dryRun === true }));
5980
+ try {
5981
+ return c.json(migrateRecipientPolicyIntent(store.db, {
5982
+ localActorId: store.actorId,
5983
+ localDeviceId: store.deviceId
5984
+ }, { dryRun: body.dryRun === true }));
5985
+ } catch (error) {
5986
+ if (isSqliteBusy(error)) {
5987
+ c.header("Retry-After", "1");
5988
+ return c.json({ error: "migration_busy" }, 503);
5989
+ }
5990
+ return c.json({ error: "migration_failed" }, 500);
5991
+ }
5980
5992
  });
5981
5993
  const parseReviewRequest = (value) => {
5982
5994
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -6718,11 +6730,18 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6718
6730
  }
6719
6731
  const peerDeviceId = String(body.peer_device_id ?? "").trim();
6720
6732
  const fingerprint = String(body.fingerprint ?? "").trim();
6733
+ const expectedGroupId = String(body.expected_group_id ?? "").trim();
6734
+ const hasExpectedIncomingRequestId = Object.hasOwn(body, "expected_incoming_request_id");
6735
+ const expectedIncomingRequestId = String(body.expected_incoming_request_id ?? "").trim();
6721
6736
  if (!peerDeviceId) return c.json({ error: "peer_device_id required" }, 400);
6737
+ if (hasExpectedIncomingRequestId && !expectedIncomingRequestId) return c.json({ error: "expected_incoming_request_id must not be empty" }, 400);
6738
+ if (expectedIncomingRequestId && !expectedGroupId) return c.json({ error: "expected_group_id required" }, 400);
6722
6739
  try {
6723
6740
  const result = await acceptDiscoveredPeer(store, {
6724
6741
  peerDeviceId,
6725
- fingerprint: fingerprint || void 0
6742
+ fingerprint: fingerprint || void 0,
6743
+ expectedGroupId: expectedGroupId || void 0,
6744
+ expectedIncomingRequestId: expectedIncomingRequestId || void 0
6726
6745
  });
6727
6746
  if (!result.ok) return c.json({
6728
6747
  error: result.error,
@@ -6738,6 +6757,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
6738
6757
  needs_scope_review: true
6739
6758
  });
6740
6759
  } catch (error) {
6760
+ if (error instanceof CoordinatorReciprocalApprovalRequestChangedError) return c.json({
6761
+ error: error.code,
6762
+ detail: "This approval request changed before it was submitted. Refresh sync status and review the current request."
6763
+ }, { status: 409 });
6741
6764
  return c.json({
6742
6765
  error: "coordinator_lookup_failed",
6743
6766
  detail: error instanceof Error ? error.message : String(error)
@@ -8166,6 +8189,850 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
8166
8189
  return app;
8167
8190
  }
8168
8191
  //#endregion
8192
+ //#region src/routes/team-setup.ts
8193
+ var TEAM_SETUP_VERSION = 1;
8194
+ var MAX_CONFIGURED_GROUPS = 25;
8195
+ var MAX_DEVICES = 500;
8196
+ var MAX_PROJECTS = 500;
8197
+ var MAX_ACCESS_DELTA_ENTRIES = 1e4;
8198
+ var MAX_IDENTITY_CHOICES = 500;
8199
+ var MAX_COMPLETED_IDENTITY_CHOICES = MAX_DEVICES * 4;
8200
+ var MAX_PROJECT_MAPPING_CHOICES = 500;
8201
+ var MAX_TOTAL_PROJECT_MAPPING_CHOICES = 1e4;
8202
+ var MAX_PROJECT_MAPPING_SCAN_ROWS = 1e4;
8203
+ var MAX_PROJECT_MAPPING_METADATA_ROWS = 1e4;
8204
+ var MAX_MUTATION_BODY_BYTES = 8192;
8205
+ var SUMMARY_SNAPSHOT_CACHE_TTL_MS = 3e4;
8206
+ var TEAM_SETUP_ROUTE_PREFIX = "/api/sync/team-setup/v1";
8207
+ var CANDIDATE_REF_PATTERN = /^legacy-team-candidate:[0-9a-f]{32}$/u;
8208
+ var DEVICE_REF_PATTERN = /^legacy-team-device-ref-v1:[0-9a-f]{64}$/u;
8209
+ var PROJECT_REF_PATTERN = /^legacy-team-project-ref-v1:[0-9a-f]{64}$/u;
8210
+ var IDENTITY_REF_PATTERN = /^legacy-team-viewer-identity-ref-v1:[0-9a-f]{64}$/u;
8211
+ var RESOLVED_PROJECT_REF_PATTERN = /^legacy-team-resolved-project-ref-v1:[0-9a-f]{64}$/u;
8212
+ var ATTEMPT_ID_PATTERN = /^legacy-team-attempt:[0-9a-f-]{36}$/u;
8213
+ var FINISH_DIGEST_PATTERN = /^legacy-team-activation-finish-v1:[0-9a-f]{64}$/u;
8214
+ var ACCESS_DELTA_DIGEST_PATTERN = /^legacy-team-access-delta:[0-9a-f]{64}$/u;
8215
+ var VIEWER_ACCESS_DELTA_DIGEST_PATTERN = /^legacy-team-viewer-access-delta-v1:[0-9a-f]{64}$/u;
8216
+ var defaultSnapshotLoaderDependencies = {
8217
+ readConfig: readCoordinatorSyncConfig,
8218
+ listGroups: coordinatorListGroupsAction,
8219
+ listDevices: coordinatorListDevicesAction
8220
+ };
8221
+ function safeCoordinatorError() {
8222
+ return /* @__PURE__ */ new Error("team_setup_roster_unavailable");
8223
+ }
8224
+ function isCoordinatorRosterTooLargeError(error) {
8225
+ return error instanceof Error && error.message === "coordinator_response_too_large";
8226
+ }
8227
+ function configuredGroupIds(groups) {
8228
+ const unique = [...new Set(groups.map((group) => group.trim()).filter(Boolean))];
8229
+ if (unique.length > MAX_CONFIGURED_GROUPS) throw safeCoordinatorError();
8230
+ return unique;
8231
+ }
8232
+ async function loadConfiguredLegacyTeamGroupSnapshotsWith(dependencies, options = {}) {
8233
+ let config;
8234
+ try {
8235
+ config = dependencies.readConfig();
8236
+ } catch {
8237
+ throw safeCoordinatorError();
8238
+ }
8239
+ const groupIds = configuredGroupIds(config.syncCoordinatorGroups);
8240
+ const hasUrl = Boolean(config.syncCoordinatorUrl);
8241
+ const hasSecret = Boolean(config.syncCoordinatorAdminSecret);
8242
+ if (!hasUrl && !hasSecret && groupIds.length === 0) return [];
8243
+ if (!hasUrl || !hasSecret || groupIds.length === 0) throw safeCoordinatorError();
8244
+ try {
8245
+ const remoteUrl = buildBaseUrl(config.syncCoordinatorUrl);
8246
+ const coordinatorId = remoteUrl;
8247
+ const timeoutS = Math.max(1, config.syncCoordinatorTimeoutS);
8248
+ const requestedGroupIds = options.candidateRef ? groupIds.filter((groupId) => legacyTeamCandidateId(coordinatorId, groupId) === options.candidateRef) : groupIds;
8249
+ const groups = await dependencies.listGroups({
8250
+ remoteUrl,
8251
+ adminSecret: config.syncCoordinatorAdminSecret,
8252
+ includeArchived: false,
8253
+ timeoutS
8254
+ });
8255
+ const groupById = new Map(groups.map((group) => [group.group_id, group]));
8256
+ return (await Promise.all(requestedGroupIds.map(async (groupId) => {
8257
+ const group = groupById.get(groupId);
8258
+ if (!group || group.group_id !== groupId || group.display_name !== null && typeof group.display_name !== "string" || group.archived_at != null) throw safeCoordinatorError();
8259
+ let devices;
8260
+ try {
8261
+ devices = await dependencies.listDevices({
8262
+ groupId,
8263
+ includeDisabled: true,
8264
+ remoteUrl,
8265
+ adminSecret: config.syncCoordinatorAdminSecret,
8266
+ timeoutS
8267
+ });
8268
+ } catch (error) {
8269
+ if (isCoordinatorRosterTooLargeError(error)) {
8270
+ if (options.candidateRef) throw new Error("legacy_team_setup_roster_too_large");
8271
+ return null;
8272
+ }
8273
+ throw error;
8274
+ }
8275
+ if (devices.length > MAX_DEVICES) {
8276
+ if (options.candidateRef) throw new Error("legacy_team_setup_roster_too_large");
8277
+ return null;
8278
+ }
8279
+ if (devices.some((device) => fingerprintPublicKey(device.public_key) !== device.fingerprint || device.enabled !== 0 && device.enabled !== 1)) throw safeCoordinatorError();
8280
+ return {
8281
+ coordinatorId,
8282
+ groupId,
8283
+ displayName: group.display_name ?? "Legacy Team",
8284
+ devices: devices.map((device) => ({
8285
+ deviceId: device.device_id,
8286
+ fingerprint: device.fingerprint,
8287
+ displayName: device.display_name ?? "Device",
8288
+ enabled: device.enabled === 1,
8289
+ labelRedactionIds: [device.identity_id ?? "", device.public_key].filter(Boolean)
8290
+ }))
8291
+ };
8292
+ }))).filter((snapshot) => snapshot !== null);
8293
+ } catch (error) {
8294
+ if (error instanceof Error && error.message === "legacy_team_setup_roster_too_large") throw error;
8295
+ throw safeCoordinatorError();
8296
+ }
8297
+ }
8298
+ async function loadConfiguredLegacyTeamGroupSnapshots(options = {}) {
8299
+ return loadConfiguredLegacyTeamGroupSnapshotsWith(defaultSnapshotLoaderDependencies, options);
8300
+ }
8301
+ function requireBoundedSnapshots(groups) {
8302
+ if (groups.length > MAX_CONFIGURED_GROUPS) throw new Error("legacy_team_setup_roster_too_large");
8303
+ }
8304
+ function projectionOptions(store) {
8305
+ return {
8306
+ localActorId: store.actorId,
8307
+ localDeviceId: store.deviceId
8308
+ };
8309
+ }
8310
+ function discoverCandidates(store, groups) {
8311
+ requireBoundedSnapshots(groups);
8312
+ return discoverLegacyTeamCandidates(store.db, {
8313
+ projection: projectionOptions(store),
8314
+ groups
8315
+ });
8316
+ }
8317
+ function candidateSummary(candidate) {
8318
+ return {
8319
+ candidateRef: candidate.candidateRef,
8320
+ displayName: candidate.displayName,
8321
+ status: candidate.status,
8322
+ deviceCount: candidate.deviceCount,
8323
+ projectCount: candidate.projectCount,
8324
+ unresolvedDeviceCount: candidate.unresolvedDeviceCount,
8325
+ unresolvedProjectCount: candidate.unresolvedProjectCount
8326
+ };
8327
+ }
8328
+ function candidateSummaryForDraft(candidate, draft) {
8329
+ return {
8330
+ ...candidateSummary(candidate),
8331
+ status: draft.state === "completed" ? "ready" : draft.state,
8332
+ unresolvedDeviceCount: draft.unresolvedDeviceCount,
8333
+ unresolvedProjectCount: draft.unresolvedProjectCount
8334
+ };
8335
+ }
8336
+ function completedCandidateFromDraft(draft) {
8337
+ return {
8338
+ candidateRef: draft.candidateRef,
8339
+ displayName: draft.displayName,
8340
+ status: "ready",
8341
+ deviceCount: draft.devices.length,
8342
+ projectCount: draft.projects.length,
8343
+ unresolvedDeviceCount: draft.unresolvedDeviceCount,
8344
+ unresolvedProjectCount: draft.unresolvedProjectCount
8345
+ };
8346
+ }
8347
+ function identityRef(candidateRef, identityId) {
8348
+ return identityId ? recipientPolicyDigest("legacy-team-viewer-identity-ref-v1", [candidateRef, identityId]) : null;
8349
+ }
8350
+ function requiredIdentityRef(candidateRef, identityId) {
8351
+ return recipientPolicyDigest("legacy-team-viewer-identity-ref-v1", [candidateRef, identityId]);
8352
+ }
8353
+ function teamRef(candidateRef, teamId) {
8354
+ return recipientPolicyDigest("legacy-team-viewer-team-ref-v1", [candidateRef, teamId]);
8355
+ }
8356
+ function resolvedProjectRef(projectRef, projectIdentity) {
8357
+ return projectIdentity ? legacyTeamResolvedProjectRef(projectRef, projectIdentity) : null;
8358
+ }
8359
+ function viewerSafeAccessDelta(candidateRef, delta, labels) {
8360
+ const fallbackLabels = /* @__PURE__ */ new Map();
8361
+ const fallbackLabelCounts = /* @__PURE__ */ new Map();
8362
+ const reservedLabels = new Set([
8363
+ labels?.teamDisplayName,
8364
+ ...labels?.devices.map((device) => device.displayName) ?? [],
8365
+ ...labels?.projects.flatMap((project) => [project.displayName, ...project.mappingChoices.map((choice) => choice.displayName)]) ?? [],
8366
+ ...labels?.identityChoices.map((identity) => identity.displayName) ?? []
8367
+ ].filter((label) => Boolean(label)).map((label) => normalizeChoiceLabelText(label).toLowerCase()));
8368
+ const fallbackLabel = (kind, ref) => {
8369
+ const key = `${kind}:${ref}`;
8370
+ const existing = fallbackLabels.get(key);
8371
+ if (existing) return existing;
8372
+ let index = (fallbackLabelCounts.get(kind) ?? 0) + 1;
8373
+ let label = `${kind} outside this setup (${index})`;
8374
+ while (reservedLabels.has(normalizeChoiceLabelText(label).toLowerCase())) {
8375
+ index += 1;
8376
+ label = `${kind} outside this setup (${index})`;
8377
+ }
8378
+ fallbackLabelCounts.set(kind, index);
8379
+ reservedLabels.add(normalizeChoiceLabelText(label).toLowerCase());
8380
+ fallbackLabels.set(key, label);
8381
+ return label;
8382
+ };
8383
+ const projectsByRef = new Map(labels?.projects.map((project) => [project.projectRef, project]));
8384
+ const projectsByCanonicalRef = new Map(labels?.projects.flatMap((project) => project.canonicalProjectRef ? [[project.canonicalProjectRef, project]] : []));
8385
+ const identitiesByRef = new Map(labels?.identityChoices.map((identity) => [identity.identityRef, identity]));
8386
+ const devicesByRef = new Map(labels?.devices.map((device) => [device.deviceRef, device]));
8387
+ const resolvedDisplayName = (projectRef, ref) => {
8388
+ if (!ref) return null;
8389
+ const project = projectsByRef.get(projectRef);
8390
+ const choice = project?.mappingChoices.find((item) => item.resolvedProjectRef === ref);
8391
+ if (choice) return choice.displayName;
8392
+ if (project?.resolvedProjectRef === ref && project.resolution === "deterministic") return `${project.displayName} (automatic match)`;
8393
+ return fallbackLabel("Project", ref);
8394
+ };
8395
+ return {
8396
+ teamChanges: delta.teamChanges.map((change) => ({
8397
+ teamRef: teamRef(candidateRef, change.teamId),
8398
+ teamDisplayName: labels?.teamDisplayName ?? fallbackLabel("Team", teamRef(candidateRef, change.teamId)),
8399
+ change: change.change,
8400
+ fromDeviceEligibilityMode: change.fromDeviceEligibilityMode,
8401
+ toDeviceEligibilityMode: change.toDeviceEligibilityMode
8402
+ })),
8403
+ membershipChanges: delta.membershipChanges.map((change) => {
8404
+ const identityRef = requiredIdentityRef(candidateRef, change.identityId);
8405
+ const membershipTeamRef = teamRef(candidateRef, change.teamId);
8406
+ return {
8407
+ teamRef: membershipTeamRef,
8408
+ teamDisplayName: labels?.teamDisplayName ?? fallbackLabel("Team", membershipTeamRef),
8409
+ identityRef,
8410
+ identityDisplayName: identitiesByRef.get(identityRef)?.displayName ?? fallbackLabel("Person", identityRef),
8411
+ change: change.change
8412
+ };
8413
+ }),
8414
+ projectChanges: delta.projectChanges.map((change) => {
8415
+ const fromRef = resolvedProjectRef(change.projectRef, change.fromProjectIdentity);
8416
+ const toRef = resolvedProjectRef(change.projectRef, change.toProjectIdentity);
8417
+ return {
8418
+ projectRef: change.projectRef,
8419
+ projectDisplayName: projectsByRef.get(change.projectRef)?.displayName ?? fallbackLabel("Project", change.projectRef),
8420
+ fromResolvedProjectRef: fromRef,
8421
+ fromResolvedProjectDisplayName: resolvedDisplayName(change.projectRef, fromRef),
8422
+ toResolvedProjectRef: toRef,
8423
+ toResolvedProjectDisplayName: resolvedDisplayName(change.projectRef, toRef),
8424
+ change: change.change
8425
+ };
8426
+ }),
8427
+ recipientChanges: delta.recipientChanges.map((change) => {
8428
+ const canonicalProjectRef = legacyTeamCanonicalProjectRef(candidateRef, change.canonicalProjectIdentity);
8429
+ const recipientRef = teamRef(candidateRef, change.recipientId);
8430
+ return {
8431
+ canonicalProjectRef,
8432
+ canonicalProjectDisplayName: projectsByCanonicalRef.get(canonicalProjectRef)?.displayName ?? fallbackLabel("Project", canonicalProjectRef),
8433
+ recipientKind: change.recipientKind,
8434
+ recipientRef,
8435
+ recipientDisplayName: labels?.teamDisplayName ?? fallbackLabel("Team", recipientRef),
8436
+ change: change.change
8437
+ };
8438
+ }),
8439
+ deviceAccessChanges: delta.deviceAccessChanges.map((change) => {
8440
+ const canonicalProjectRef = legacyTeamCanonicalProjectRef(candidateRef, change.canonicalProjectIdentity);
8441
+ const deviceRef = legacyTeamDeviceRef(candidateRef, change.deviceId);
8442
+ return {
8443
+ canonicalProjectRef,
8444
+ canonicalProjectDisplayName: projectsByCanonicalRef.get(canonicalProjectRef)?.displayName ?? fallbackLabel("Project", canonicalProjectRef),
8445
+ deviceRef,
8446
+ deviceDisplayName: devicesByRef.get(deviceRef)?.displayName ?? fallbackLabel("Device", deviceRef),
8447
+ change: change.change
8448
+ };
8449
+ })
8450
+ };
8451
+ }
8452
+ function viewerAccessDeltaDigest(delta) {
8453
+ return recipientPolicyDigest("legacy-team-viewer-access-delta-v1", delta);
8454
+ }
8455
+ function requireBoundedAccessDelta(delta) {
8456
+ const total = delta.teamChanges.length + delta.membershipChanges.length + delta.projectChanges.length + delta.recipientChanges.length + delta.deviceAccessChanges.length;
8457
+ 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");
8458
+ }
8459
+ var SAFE_CHOICE_LABEL_PATTERN = /^[\p{L}\p{N} '&,.()_-]*$/u;
8460
+ function normalizeChoiceLabelText(value) {
8461
+ return value.normalize("NFKC").replace(/\p{Cf}/gu, "").replace(/\p{Cc}/gu, " ").replace(/\s+/gu, " ").trim();
8462
+ }
8463
+ function safeChoiceLabel(value, fallback, forbiddenIds, opaqueRef) {
8464
+ const normalized = normalizeChoiceLabelText(value.slice(0, 512));
8465
+ const label = normalized.slice(0, 120).trim();
8466
+ const forbiddenComparisons = forbiddenIds.map((forbiddenId) => normalizeChoiceLabelText(forbiddenId).toLowerCase()).filter(Boolean);
8467
+ const comparable = normalized.toLowerCase();
8468
+ if (!label || !SAFE_CHOICE_LABEL_PATTERN.test(label) || /[\p{L}\p{N}]\.[\p{L}\p{N}]/u.test(label) || /-----|\b(?:ssh|ecdsa|sk)-[\p{L}\p{N}-]+ /iu.test(label) || forbiddenComparisons.some((forbiddenComparison) => comparable.includes(forbiddenComparison))) {
8469
+ const suffix = opaqueRef?.slice(-6);
8470
+ return suffix ? `${fallback} ${suffix}` : fallback;
8471
+ }
8472
+ return label;
8473
+ }
8474
+ function disambiguateChoiceLabels(choices, choiceRef) {
8475
+ const counts = /* @__PURE__ */ new Map();
8476
+ const originalLabels = /* @__PURE__ */ new Set();
8477
+ for (const choice of choices) {
8478
+ const comparable = normalizeChoiceLabelText(choice.displayName).toLowerCase();
8479
+ counts.set(comparable, (counts.get(comparable) ?? 0) + 1);
8480
+ originalLabels.add(comparable);
8481
+ }
8482
+ const usedLabels = /* @__PURE__ */ new Set();
8483
+ return choices.map((choice, index) => {
8484
+ const comparable = normalizeChoiceLabelText(choice.displayName).toLowerCase();
8485
+ if ((counts.get(comparable) ?? 0) < 2 && !usedLabels.has(comparable)) {
8486
+ usedLabels.add(comparable);
8487
+ return choice;
8488
+ }
8489
+ const ref = choiceRef(choice);
8490
+ let suffixLength = Math.min(6, ref.length);
8491
+ let suffix = ref.slice(-suffixLength);
8492
+ const maxAttempts = ref.length + originalLabels.size + usedLabels.size + 2;
8493
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
8494
+ const baseLength = Math.max(0, 119 - suffix.length);
8495
+ const base = choice.displayName.slice(0, baseLength).trimEnd();
8496
+ const displayName = base ? `${base} ${suffix}` : suffix.slice(-120);
8497
+ const finalComparable = normalizeChoiceLabelText(displayName).toLowerCase();
8498
+ if (!usedLabels.has(finalComparable) && !originalLabels.has(finalComparable)) {
8499
+ usedLabels.add(finalComparable);
8500
+ return {
8501
+ ...choice,
8502
+ displayName
8503
+ };
8504
+ }
8505
+ if (suffixLength < ref.length) {
8506
+ suffixLength = Math.min(ref.length, suffixLength + 2);
8507
+ suffix = ref.slice(-suffixLength);
8508
+ continue;
8509
+ }
8510
+ suffix = `${ref.slice(-Math.min(96, ref.length))}-${index + 1}-${attempt + 1}`;
8511
+ }
8512
+ throw new Error("legacy_team_setup_roster_too_large");
8513
+ });
8514
+ }
8515
+ function requireCompleteMappingChoices(projectCount, choiceCount) {
8516
+ if (projectCount * choiceCount > MAX_TOTAL_PROJECT_MAPPING_CHOICES) throw new Error("legacy_team_setup_roster_too_large");
8517
+ }
8518
+ function createCachedSnapshotLoader(load, ttlMs, now = Date.now) {
8519
+ let cached = null;
8520
+ let inFlight = null;
8521
+ let generation = 0;
8522
+ const cachedLoad = () => {
8523
+ if (cached && now() < cached.expiresAt) return Promise.resolve(cached.snapshots);
8524
+ if (inFlight) return inFlight;
8525
+ const operationGeneration = generation;
8526
+ let operation;
8527
+ operation = load().then((snapshots) => {
8528
+ if (generation !== operationGeneration) return cachedLoad();
8529
+ cached = {
8530
+ expiresAt: now() + ttlMs,
8531
+ snapshots
8532
+ };
8533
+ return snapshots;
8534
+ }).finally(() => {
8535
+ if (inFlight === operation) inFlight = null;
8536
+ });
8537
+ inFlight = operation;
8538
+ return operation;
8539
+ };
8540
+ cachedLoad.invalidate = () => {
8541
+ generation += 1;
8542
+ cached = null;
8543
+ inFlight = null;
8544
+ };
8545
+ return cachedLoad;
8546
+ }
8547
+ function identityChoicesFromRows(rows, candidateRef, maxChoices = MAX_IDENTITY_CHOICES) {
8548
+ if (rows.length > maxChoices) throw new Error("legacy_team_setup_roster_too_large");
8549
+ const actorIds = rows.map((row) => row.actor_id);
8550
+ return disambiguateChoiceLabels(rows.map((row) => {
8551
+ const opaqueIdentityRef = requiredIdentityRef(candidateRef, row.actor_id);
8552
+ return {
8553
+ identityId: row.actor_id,
8554
+ identityRef: opaqueIdentityRef,
8555
+ displayName: safeChoiceLabel(row.display_name, "Person", actorIds, opaqueIdentityRef)
8556
+ };
8557
+ }), (choice) => choice.identityRef);
8558
+ }
8559
+ function identityChoices(store, candidateRef) {
8560
+ return identityChoicesFromRows(store.db.prepare(`SELECT actor_id, display_name FROM actors
8561
+ WHERE status = 'active' AND merged_into_actor_id IS NULL
8562
+ ORDER BY display_name, actor_id LIMIT ?`).all(501), candidateRef);
8563
+ }
8564
+ function completedDraftIdentityChoices(store, draft) {
8565
+ const actorIds = [...new Set(draft.devices.flatMap((device) => [
8566
+ device.existingIdentityId,
8567
+ device.suggestedIdentityId,
8568
+ device.targetIdentityId,
8569
+ device.expectation.kind === "existing" ? device.expectation.identityId : null
8570
+ ]))].filter((actorId) => actorId !== null);
8571
+ if (actorIds.length === 0) return [];
8572
+ const placeholders = actorIds.map(() => "?").join(", ");
8573
+ return identityChoicesFromRows(store.db.prepare(`SELECT actor_id, display_name FROM actors
8574
+ WHERE status = 'active' AND merged_into_actor_id IS NULL
8575
+ AND actor_id IN (${placeholders})
8576
+ ORDER BY display_name, actor_id`).all(...actorIds), draft.candidateRef, MAX_COMPLETED_IDENTITY_CHOICES);
8577
+ }
8578
+ function projectMappingChoices(store) {
8579
+ let candidates;
8580
+ try {
8581
+ candidates = listProjectScopeCandidates(store.db, {
8582
+ limit: null,
8583
+ maxScannedRows: MAX_PROJECT_MAPPING_SCAN_ROWS,
8584
+ maxMetadataRows: MAX_PROJECT_MAPPING_METADATA_ROWS,
8585
+ excludePeerReceived: true
8586
+ }).filter((candidate) => !candidate.read_only && isLegacyTeamSetupProjectMappingIdentity(candidate.workspace_identity));
8587
+ } catch (error) {
8588
+ 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");
8589
+ throw error;
8590
+ }
8591
+ if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new Error("legacy_team_setup_roster_too_large");
8592
+ return candidates.map((candidate) => ({
8593
+ projectIdentity: candidate.workspace_identity,
8594
+ sourceDisplayName: candidate.display_project
8595
+ }));
8596
+ }
8597
+ function viewerSafeDraft(store, draft) {
8598
+ if (draft.devices.length > MAX_DEVICES || draft.projects.length > MAX_PROJECTS) throw new Error("legacy_team_setup_roster_too_large");
8599
+ const completed = draft.state === "completed";
8600
+ const identities = completed ? completedDraftIdentityChoices(store, draft) : identityChoices(store, draft.candidateRef);
8601
+ const mappableProjectCount = completed ? 0 : draft.projects.filter((project) => project.resolution !== "deterministic").length;
8602
+ const mappingChoices = mappableProjectCount === 0 ? [] : projectMappingChoices(store);
8603
+ requireCompleteMappingChoices(mappableProjectCount, mappingChoices.length);
8604
+ const mappingChoiceIdentities = mappingChoices.map((choice) => choice.projectIdentity);
8605
+ return {
8606
+ devices: draft.devices.map((device) => ({
8607
+ deviceRef: device.deviceRef,
8608
+ displayName: device.displayName,
8609
+ enabled: device.enabled,
8610
+ existingIdentityRef: identityRef(draft.candidateRef, device.existingIdentityId),
8611
+ suggestedIdentityRef: identityRef(draft.candidateRef, device.suggestedIdentityId),
8612
+ verifiedEvidenceKind: device.verifiedEvidenceKind,
8613
+ decision: device.decision,
8614
+ targetIdentityRef: identityRef(draft.candidateRef, device.targetIdentityId),
8615
+ expectation: device.expectation.kind === "existing" ? {
8616
+ kind: "existing",
8617
+ assignmentVersion: device.expectation.assignmentVersion,
8618
+ identityRef: requiredIdentityRef(draft.candidateRef, device.expectation.identityId)
8619
+ } : { kind: "absent" }
8620
+ })),
8621
+ projects: draft.projects.map((project) => ({
8622
+ projectRef: project.projectRef,
8623
+ displayName: project.displayName,
8624
+ resolution: project.resolution,
8625
+ canonicalProjectRef: project.canonicalProjectRef,
8626
+ resolvedProjectRef: project.resolvedProjectRef,
8627
+ mappingChoices: project.resolution === "deterministic" ? [] : disambiguateChoiceLabels(mappingChoices.map((choice) => {
8628
+ const opaqueResolvedProjectRef = legacyTeamResolvedProjectRef(project.projectRef, choice.projectIdentity);
8629
+ return {
8630
+ resolvedProjectRef: opaqueResolvedProjectRef,
8631
+ displayName: safeChoiceLabel(choice.sourceDisplayName, "Project", mappingChoiceIdentities, opaqueResolvedProjectRef)
8632
+ };
8633
+ }), (choice) => choice.resolvedProjectRef)
8634
+ })),
8635
+ identityChoices: identities.map(({ identityRef, displayName }) => ({
8636
+ identityRef,
8637
+ displayName
8638
+ }))
8639
+ };
8640
+ }
8641
+ function errorStatus(code) {
8642
+ if (code === "team_setup_roster_unavailable") return 503;
8643
+ if (code === "team_setup_failed") return 503;
8644
+ if (code === "team_setup_incomplete") return 400;
8645
+ return 409;
8646
+ }
8647
+ function apiErrorCode(error) {
8648
+ const code = legacyTeamSetupApiErrorCode(error);
8649
+ return code === "team_setup_projection_changed" ? "team_setup_conflict" : code;
8650
+ }
8651
+ async function parseBoundedJsonObject(c) {
8652
+ if (c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return { ok: false };
8653
+ const lengthHeader = c.req.header("content-length");
8654
+ if (lengthHeader != null && !/^\d+$/u.test(lengthHeader.trim())) return { ok: false };
8655
+ const contentLength = lengthHeader == null ? null : Number(lengthHeader);
8656
+ if (contentLength != null && contentLength > MAX_MUTATION_BODY_BYTES) return { ok: false };
8657
+ const body = c.req.raw.body;
8658
+ if (!body) return { ok: false };
8659
+ const reader = body.getReader();
8660
+ const chunks = [];
8661
+ let total = 0;
8662
+ try {
8663
+ while (true) {
8664
+ const { done, value } = await reader.read();
8665
+ if (done) break;
8666
+ if (!value) continue;
8667
+ total += value.byteLength;
8668
+ if (total > MAX_MUTATION_BODY_BYTES) {
8669
+ await reader.cancel().catch(() => void 0);
8670
+ return { ok: false };
8671
+ }
8672
+ chunks.push(value);
8673
+ }
8674
+ } catch {
8675
+ return { ok: false };
8676
+ } finally {
8677
+ reader.releaseLock();
8678
+ }
8679
+ try {
8680
+ const bytes = new Uint8Array(total);
8681
+ let offset = 0;
8682
+ for (const chunk of chunks) {
8683
+ bytes.set(chunk, offset);
8684
+ offset += chunk.byteLength;
8685
+ }
8686
+ const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
8687
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? {
8688
+ ok: true,
8689
+ value: parsed
8690
+ } : { ok: false };
8691
+ } catch {
8692
+ return { ok: false };
8693
+ }
8694
+ }
8695
+ function hasExactKeys(value, keys) {
8696
+ const actual = Object.keys(value).toSorted();
8697
+ const expected = keys.toSorted();
8698
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
8699
+ }
8700
+ function mutationResponse(draft) {
8701
+ return {
8702
+ version: TEAM_SETUP_VERSION,
8703
+ candidateRef: draft.candidateRef,
8704
+ attemptId: draft.attemptId,
8705
+ draftState: draft.state,
8706
+ canFinish: draft.canFinish,
8707
+ unresolvedDeviceCount: draft.unresolvedDeviceCount,
8708
+ unresolvedProjectCount: draft.unresolvedProjectCount
8709
+ };
8710
+ }
8711
+ function finishResponse(candidateRef, result) {
8712
+ return {
8713
+ version: TEAM_SETUP_VERSION,
8714
+ status: result.status,
8715
+ teamRef: teamRef(candidateRef, result.teamId),
8716
+ attemptId: result.attemptId,
8717
+ accessDeltaDigest: result.accessDeltaDigest,
8718
+ completedAt: result.completedAt
8719
+ };
8720
+ }
8721
+ function teamSetupRoutes(options) {
8722
+ const app = new Hono();
8723
+ const loadSnapshots = options.loadLegacyTeamConfiguredGroupSnapshots ?? loadConfiguredLegacyTeamGroupSnapshots;
8724
+ async function loadedSnapshots(candidateRef) {
8725
+ try {
8726
+ return await loadSnapshots(candidateRef ? { candidateRef } : void 0);
8727
+ } catch (error) {
8728
+ if (error instanceof Error && error.message === "legacy_team_setup_roster_too_large") throw error;
8729
+ throw safeCoordinatorError();
8730
+ }
8731
+ }
8732
+ const loadedSummarySnapshots = createCachedSnapshotLoader(() => loadedSnapshots(), SUMMARY_SNAPSHOT_CACHE_TTL_MS);
8733
+ async function loadedCandidateSnapshots(candidateRef) {
8734
+ loadedSummarySnapshots.invalidate();
8735
+ try {
8736
+ return await loadedSnapshots(candidateRef);
8737
+ } finally {
8738
+ loadedSummarySnapshots.invalidate();
8739
+ }
8740
+ }
8741
+ app.get("/api/sync/team-setup/v1", async (c) => {
8742
+ let groups;
8743
+ try {
8744
+ groups = await loadedSummarySnapshots();
8745
+ } catch (error) {
8746
+ const code = apiErrorCode(error);
8747
+ return c.json({ error: code }, errorStatus(code));
8748
+ }
8749
+ try {
8750
+ const response = {
8751
+ version: TEAM_SETUP_VERSION,
8752
+ candidates: discoverCandidates(options.getStore(), groups).map(candidateSummary)
8753
+ };
8754
+ return c.json(response);
8755
+ } catch (error) {
8756
+ const code = apiErrorCode(error);
8757
+ return c.json({ error: code }, errorStatus(code));
8758
+ }
8759
+ });
8760
+ app.get("/api/sync/team-setup/v1/:candidateRef", async (c) => {
8761
+ const candidateRef = c.req.param("candidateRef");
8762
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8763
+ try {
8764
+ const store = options.getStore();
8765
+ let draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8766
+ let candidate;
8767
+ if (draft?.state === "completed" && isLegacyTeamCandidateSelectable(store.db, candidateRef, { projects: legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef) })) candidate = completedCandidateFromDraft(draft);
8768
+ else {
8769
+ const discoveredCandidate = discoverCandidates(store, (await loadedCandidateSnapshots(candidateRef)).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef)).find((item) => item.candidateRef === candidateRef);
8770
+ if (!discoveredCandidate) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8771
+ candidate = discoveredCandidate;
8772
+ draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8773
+ }
8774
+ if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8775
+ let conflictState = null;
8776
+ let preview = null;
8777
+ if (draft.state !== "completed") try {
8778
+ preview = previewLegacyTeamSetupActivation(store.db, {
8779
+ candidateRef,
8780
+ attemptId: draft.attemptId
8781
+ });
8782
+ } catch (error) {
8783
+ const code = apiErrorCode(error);
8784
+ if (code === "team_setup_failed") return c.json({ error: code }, 503);
8785
+ preview = null;
8786
+ conflictState = code;
8787
+ }
8788
+ if (preview) requireBoundedAccessDelta(preview.accessDelta);
8789
+ const viewDraft = conflictState ? getLegacyTeamSetupDraft(store.db, candidateRef) ?? draft : draft;
8790
+ const safeDraft = viewerSafeDraft(store, viewDraft);
8791
+ const responseBase = {
8792
+ version: TEAM_SETUP_VERSION,
8793
+ candidate: candidateSummaryForDraft(candidate, viewDraft),
8794
+ attemptId: viewDraft.attemptId,
8795
+ draftState: viewDraft.state,
8796
+ unresolvedDeviceCount: viewDraft.unresolvedDeviceCount,
8797
+ unresolvedProjectCount: viewDraft.unresolvedProjectCount,
8798
+ ...safeDraft
8799
+ };
8800
+ if (!preview) {
8801
+ const response = {
8802
+ ...responseBase,
8803
+ canFinish: false,
8804
+ conflictState
8805
+ };
8806
+ return c.json(response);
8807
+ }
8808
+ const accessDelta = viewerSafeAccessDelta(candidateRef, preview.accessDelta, {
8809
+ teamDisplayName: viewDraft.displayName,
8810
+ ...safeDraft
8811
+ });
8812
+ const response = {
8813
+ ...responseBase,
8814
+ canFinish: true,
8815
+ conflictState: null,
8816
+ finishDigest: preview.finishDigest,
8817
+ accessDeltaDigest: preview.accessDeltaDigest,
8818
+ viewerAccessDeltaDigest: viewerAccessDeltaDigest(accessDelta),
8819
+ accessDelta
8820
+ };
8821
+ return c.json(response);
8822
+ } catch (error) {
8823
+ const code = apiErrorCode(error);
8824
+ return c.json({ error: code }, errorStatus(code));
8825
+ }
8826
+ });
8827
+ app.put("/api/sync/team-setup/v1/:candidateRef/devices/:deviceRef/assignment", async (c) => {
8828
+ const candidateRef = c.req.param("candidateRef");
8829
+ const deviceRef = c.req.param("deviceRef");
8830
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef) || !DEVICE_REF_PATTERN.test(deviceRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8831
+ const parsed = await parseBoundedJsonObject(c);
8832
+ if (!parsed.ok || !hasExactKeys(parsed.value, [
8833
+ "attemptId",
8834
+ "expectation",
8835
+ "targetIdentityRef"
8836
+ ])) return c.json({ error: "team_setup_incomplete" }, 400);
8837
+ const { attemptId, expectation, targetIdentityRef } = parsed.value;
8838
+ if (typeof attemptId !== "string" || !ATTEMPT_ID_PATTERN.test(attemptId) || typeof targetIdentityRef !== "string" || !IDENTITY_REF_PATTERN.test(targetIdentityRef) || !expectation || typeof expectation !== "object" || Array.isArray(expectation)) return c.json({ error: "team_setup_incomplete" }, 400);
8839
+ try {
8840
+ const store = options.getStore();
8841
+ const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8842
+ if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8843
+ if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
8844
+ const device = draft.devices.find((item) => item.deviceRef === deviceRef);
8845
+ if (!device) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8846
+ const submittedExpectation = expectation;
8847
+ if (!(device.expectation.kind === "absent" ? hasExactKeys(submittedExpectation, ["kind"]) && submittedExpectation.kind === "absent" : hasExactKeys(submittedExpectation, [
8848
+ "assignmentVersion",
8849
+ "identityRef",
8850
+ "kind"
8851
+ ]) && submittedExpectation.kind === "existing" && submittedExpectation.assignmentVersion === device.expectation.assignmentVersion && submittedExpectation.identityRef === requiredIdentityRef(candidateRef, device.expectation.identityId))) return c.json({ error: "team_setup_assignment_changed" }, 409);
8852
+ const target = identityChoices(store, candidateRef).find((choice) => choice.identityRef === targetIdentityRef);
8853
+ if (!target) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8854
+ return c.json(mutationResponse(setLegacyTeamSetupDeviceAssignment(store.db, {
8855
+ attemptId,
8856
+ deviceRef,
8857
+ targetIdentityId: target.identityId,
8858
+ expectation: device.expectation
8859
+ })));
8860
+ } catch (error) {
8861
+ const code = apiErrorCode(error);
8862
+ return c.json({ error: code }, errorStatus(code));
8863
+ }
8864
+ });
8865
+ app.put("/api/sync/team-setup/v1/:candidateRef/devices/:deviceRef/decision", async (c) => {
8866
+ const candidateRef = c.req.param("candidateRef");
8867
+ const deviceRef = c.req.param("deviceRef");
8868
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef) || !DEVICE_REF_PATTERN.test(deviceRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8869
+ const parsed = await parseBoundedJsonObject(c);
8870
+ if (!parsed.ok) return c.json({ error: "team_setup_incomplete" }, 400);
8871
+ const { attemptId, decision, expectedTargetIdentityRef } = parsed.value;
8872
+ if (typeof attemptId !== "string" || !ATTEMPT_ID_PATTERN.test(attemptId) || ![
8873
+ "included",
8874
+ "excluded",
8875
+ "removed"
8876
+ ].includes(decision) || (decision === "included" ? !hasExactKeys(parsed.value, [
8877
+ "attemptId",
8878
+ "decision",
8879
+ "expectedTargetIdentityRef"
8880
+ ]) || typeof expectedTargetIdentityRef !== "string" || !IDENTITY_REF_PATTERN.test(expectedTargetIdentityRef) : !hasExactKeys(parsed.value, ["attemptId", "decision"]))) return c.json({ error: "team_setup_incomplete" }, 400);
8881
+ try {
8882
+ const store = options.getStore();
8883
+ const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8884
+ if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8885
+ if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
8886
+ const device = draft.devices.find((item) => item.deviceRef === deviceRef);
8887
+ if (!device) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8888
+ if (decision === "included" && (device.targetIdentityId === null || requiredIdentityRef(candidateRef, device.targetIdentityId) !== expectedTargetIdentityRef)) return c.json({ error: "team_setup_assignment_changed" }, 409);
8889
+ return c.json(mutationResponse(setLegacyTeamSetupDeviceDecision(store.db, {
8890
+ attemptId,
8891
+ deviceRef,
8892
+ decision
8893
+ })));
8894
+ } catch (error) {
8895
+ const code = apiErrorCode(error);
8896
+ return c.json({ error: code }, errorStatus(code));
8897
+ }
8898
+ });
8899
+ app.delete("/api/sync/team-setup/v1/:candidateRef/devices/:deviceRef/decision", async (c) => {
8900
+ const candidateRef = c.req.param("candidateRef");
8901
+ const deviceRef = c.req.param("deviceRef");
8902
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef) || !DEVICE_REF_PATTERN.test(deviceRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8903
+ const parsed = await parseBoundedJsonObject(c);
8904
+ if (!parsed.ok || !hasExactKeys(parsed.value, ["attemptId"])) return c.json({ error: "team_setup_incomplete" }, 400);
8905
+ const { attemptId } = parsed.value;
8906
+ if (typeof attemptId !== "string" || !ATTEMPT_ID_PATTERN.test(attemptId)) return c.json({ error: "team_setup_incomplete" }, 400);
8907
+ try {
8908
+ const store = options.getStore();
8909
+ const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8910
+ if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8911
+ if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
8912
+ if (!draft.devices.some((item) => item.deviceRef === deviceRef)) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8913
+ return c.json(mutationResponse(clearLegacyTeamSetupDeviceDecision(store.db, {
8914
+ attemptId,
8915
+ deviceRef
8916
+ })));
8917
+ } catch (error) {
8918
+ const code = apiErrorCode(error);
8919
+ return c.json({ error: code }, errorStatus(code));
8920
+ }
8921
+ });
8922
+ app.put("/api/sync/team-setup/v1/:candidateRef/projects/:projectRef/mapping", async (c) => {
8923
+ const candidateRef = c.req.param("candidateRef");
8924
+ const projectRef = c.req.param("projectRef");
8925
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef) || !PROJECT_REF_PATTERN.test(projectRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8926
+ const parsed = await parseBoundedJsonObject(c);
8927
+ if (!parsed.ok || !hasExactKeys(parsed.value, ["attemptId", "resolvedProjectRef"])) return c.json({ error: "team_setup_incomplete" }, 400);
8928
+ const { attemptId, resolvedProjectRef } = parsed.value;
8929
+ if (typeof attemptId !== "string" || !ATTEMPT_ID_PATTERN.test(attemptId) || typeof resolvedProjectRef !== "string" || !RESOLVED_PROJECT_REF_PATTERN.test(resolvedProjectRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8930
+ try {
8931
+ const store = options.getStore();
8932
+ const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8933
+ if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8934
+ if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
8935
+ const project = draft.projects.find((item) => item.projectRef === projectRef);
8936
+ if (!project) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8937
+ if (project.resolution === "deterministic") return c.json({ error: "team_setup_incomplete" }, 400);
8938
+ const target = projectMappingChoices(store).find((choice) => legacyTeamResolvedProjectRef(projectRef, choice.projectIdentity) === resolvedProjectRef);
8939
+ if (!target) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8940
+ return c.json(mutationResponse(setLegacyTeamSetupProjectMapping(store.db, {
8941
+ attemptId,
8942
+ projectRef,
8943
+ resolvedProjectIdentity: target.projectIdentity
8944
+ })));
8945
+ } catch (error) {
8946
+ const code = apiErrorCode(error);
8947
+ return c.json({ error: code }, errorStatus(code));
8948
+ }
8949
+ });
8950
+ app.post("/api/sync/team-setup/v1/:candidateRef/refresh", async (c) => {
8951
+ const candidateRef = c.req.param("candidateRef");
8952
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8953
+ const parsed = await parseBoundedJsonObject(c);
8954
+ if (!parsed.ok || !hasExactKeys(parsed.value, [])) return c.json({ error: "team_setup_incomplete" }, 400);
8955
+ let groups;
8956
+ try {
8957
+ groups = await loadedCandidateSnapshots(candidateRef);
8958
+ } catch (error) {
8959
+ const code = apiErrorCode(error);
8960
+ return c.json({ error: code }, errorStatus(code));
8961
+ }
8962
+ if (groups.filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef).length === 0) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8963
+ try {
8964
+ const store = options.getStore();
8965
+ return c.json(mutationResponse(refreshLegacyTeamCandidate(store.db, {
8966
+ projection: projectionOptions(store),
8967
+ groups
8968
+ }, candidateRef)));
8969
+ } catch (error) {
8970
+ const code = apiErrorCode(error);
8971
+ return c.json({ error: code }, errorStatus(code));
8972
+ }
8973
+ });
8974
+ app.post("/api/sync/team-setup/v1/:candidateRef/finish", async (c) => {
8975
+ const candidateRef = c.req.param("candidateRef");
8976
+ if (!CANDIDATE_REF_PATTERN.test(candidateRef)) return c.json({ error: "team_setup_incomplete" }, 400);
8977
+ const parsed = await parseBoundedJsonObject(c);
8978
+ if (!parsed.ok) return c.json({ error: "team_setup_incomplete" }, 400);
8979
+ const finishKeys = [
8980
+ "attemptId",
8981
+ "confirmedAccessDeltaDigest",
8982
+ "confirmedViewerAccessDeltaDigest",
8983
+ "finishDigest"
8984
+ ];
8985
+ 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);
8986
+ const { attemptId, confirmedAccessDeltaDigest, confirmedViewerAccessDeltaDigest, finishDigest } = parsed.value;
8987
+ 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);
8988
+ try {
8989
+ const store = options.getStore();
8990
+ const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
8991
+ if (!draft) return c.json({ error: "team_setup_confirmation_stale" }, 404);
8992
+ if (draft.attemptId !== attemptId) return c.json({ error: "team_setup_confirmation_stale" }, 409);
8993
+ if (draft.state !== "completed") {
8994
+ const preview = previewLegacyTeamSetupActivation(store.db, {
8995
+ candidateRef,
8996
+ attemptId: draft.attemptId
8997
+ });
8998
+ requireBoundedAccessDelta(preview.accessDelta);
8999
+ const safeDraft = viewerSafeDraft(store, draft);
9000
+ if (viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, preview.accessDelta, {
9001
+ teamDisplayName: draft.displayName,
9002
+ ...safeDraft
9003
+ })) !== confirmedViewerAccessDeltaDigest) return c.json({ error: "team_setup_confirmation_stale" }, 409);
9004
+ }
9005
+ const result = await finishLegacyTeamSetupActivation(store.db, {
9006
+ candidateRef,
9007
+ attemptId,
9008
+ finishDigest,
9009
+ confirmedAccessDeltaDigest,
9010
+ loadFreshRoster: async () => {
9011
+ const matching = (await loadedCandidateSnapshots(candidateRef)).filter((group) => legacyTeamCandidateId(group.coordinatorId, group.groupId) === candidateRef);
9012
+ if (matching.length !== 1) throw safeCoordinatorError();
9013
+ return matching[0]?.devices ?? [];
9014
+ },
9015
+ loadProjectInventory: () => legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef),
9016
+ validateLockedPreview: (lockedPreview) => {
9017
+ requireBoundedAccessDelta(lockedPreview.accessDelta);
9018
+ const lockedDraft = getLegacyTeamSetupDraft(store.db, candidateRef);
9019
+ if (!lockedDraft || lockedDraft.attemptId !== attemptId) return false;
9020
+ const safeDraft = viewerSafeDraft(store, lockedDraft);
9021
+ return viewerAccessDeltaDigest(viewerSafeAccessDelta(candidateRef, lockedPreview.accessDelta, {
9022
+ teamDisplayName: lockedDraft.displayName,
9023
+ ...safeDraft
9024
+ })) === confirmedViewerAccessDeltaDigest;
9025
+ }
9026
+ });
9027
+ return c.json(finishResponse(candidateRef, result));
9028
+ } catch (error) {
9029
+ const code = apiErrorCode(error);
9030
+ return c.json({ error: code }, errorStatus(code));
9031
+ }
9032
+ });
9033
+ return app;
9034
+ }
9035
+ //#endregion
8169
9036
  //#region src/routes/update-status.ts
8170
9037
  function updateStatusRoutes(options = {}) {
8171
9038
  const getUpdateStatus$1 = options.getUpdateStatus ?? getUpdateStatus;
@@ -8216,7 +9083,7 @@ function createApp(opts) {
8216
9083
  const getSyncRuntimeStatus = opts?.getSyncRuntimeStatus ?? (() => null);
8217
9084
  const app = new Hono();
8218
9085
  app.use("*", preflightHandler());
8219
- app.use("*", originGuard());
9086
+ app.use("*", originGuard({ unsafeGetPathPrefixes: [TEAM_SETUP_ROUTE_PREFIX] }));
8220
9087
  app.route("/", healthRoutes(storeFactory));
8221
9088
  app.route("/", statsRoutes(storeFactory));
8222
9089
  app.route("/", memoryRoutes(storeFactory));
@@ -8229,6 +9096,10 @@ function createApp(opts) {
8229
9096
  app.route("/", configRoutes({ getSweeper: () => sweeper }));
8230
9097
  app.route("/", rawEventsRoutes(storeFactory, sweeper));
8231
9098
  app.route("/", syncRoutes(storeFactory, getSyncRuntimeStatus, { loadDeviceIdentityCoordinatorEvidence: opts?.loadDeviceIdentityCoordinatorEvidence }));
9099
+ app.route("/", teamSetupRoutes({
9100
+ getStore: storeFactory,
9101
+ loadLegacyTeamConfiguredGroupSnapshots: opts?.loadLegacyTeamConfiguredGroupSnapshots
9102
+ }));
8232
9103
  app.route("/", updateStatusRoutes({ getUpdateStatus: opts?.getUpdateStatus }));
8233
9104
  const staticRoot = process.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? ".", "../static");
8234
9105
  app.use("/assets/*", async (c, next) => {