@codemem/server 0.46.0-rc.1 → 0.46.0-rc.3

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, CodememConfigMutationError, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MEMORY_FILTER_FIELD_TYPES, MEMORY_FILTER_NAMES, MEMORY_KIND_DESCRIPTIONS, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REMEMBER_MEMORY_KINDS, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, analyzeProjectScopeMappingChangesGuardrails, analyzeProjectScopeMappingDeletionGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildFilterClausesWithContext, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalRepositoryProjectIdentity, 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, dedupeOrderedIds, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrideValues, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isAutomaticRecallMeasurement, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, judgeDistillReport, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, memoryFilterValueMatchesType, mergeAddresses, migrateRecipientPolicyIntent, mutateCodememConfigFile, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectMatchesFilter, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordAutomaticRecall, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, repositoryIdentitiesByWorkspace, repositoryIdentityForWorkspace, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveObserverRuntime, resolveProject, resolveProjectRoot, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, storeVectors, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, toJson, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMappings, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, validateRawEvents, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, wakeRecipientPoliciesForIdentities, wakeRecipientPoliciesForProjectIdentities } from "@codemem/core";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CodememConfigMutationError, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MEMORY_FILTER_FIELD_TYPES, MEMORY_FILTER_NAMES, MEMORY_KIND_DESCRIPTIONS, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REMEMBER_MEMORY_KINDS, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, analyzeProjectScopeMappingChangesGuardrails, analyzeProjectScopeMappingDeletionGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildFilterClausesWithContext, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalRepositoryProjectIdentity, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, classifyRecordedSyncFailure, 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, dedupeOrderedIds, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrideValues, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isAutomaticRecallMeasurement, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, judgeDistillReport, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, memoryFilterValueMatchesType, mergeAddresses, migrateRecipientPolicyIntent, mutateCodememConfigFile, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectMatchesFilter, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordAutomaticRecall, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, repositoryIdentitiesByWorkspace, repositoryIdentityForWorkspace, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveObserverRuntime, resolveProject, resolveProjectRoot, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, storeVectors, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, toJson, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMappings, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, validateRawEvents, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, wakeRecipientPoliciesForIdentities, wakeRecipientPoliciesForProjectIdentities } 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";
@@ -671,14 +671,9 @@ function syncSelect(options) {
671
671
  ${SYNC_OCCURRED_AT_SQL} AS occurred_at,
672
672
  CASE WHEN ok <> 0 THEN 'succeeded' ELSE 'failed' END AS status,
673
673
  ops_in AS metric_a, ops_out AS metric_b,
674
- CASE
675
- WHEN lower(error) LIKE '%auth%' OR lower(error) LIKE '%unauthorized%' THEN 'authentication'
676
- WHEN lower(error) LIKE '%timeout%' OR lower(error) LIKE '%timed out%' THEN 'timeout'
677
- WHEN lower(error) LIKE '%version%' OR lower(error) LIKE '%capability%' THEN 'compatibility'
678
- WHEN lower(error) LIKE '%connect%' OR lower(error) LIKE '%network%'
679
- OR lower(error) LIKE '%dns%' THEN 'connectivity'
680
- ELSE 'unspecified'
681
- END AS category
674
+ -- Raw error text; syncEvent classifies it server-side and never returns it.
675
+ error AS category,
676
+ failure_category AS stored_category
682
677
  FROM sync_attempts ${predicates.length ? `WHERE ${predicates.join(" AND ")}` : ""}
683
678
  ORDER BY ${SYNC_OCCURRED_AT_SQL} DESC, id DESC
684
679
  LIMIT @sourceLimit`;
@@ -712,7 +707,8 @@ function observerSelect(options) {
712
707
  WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%schema%'
713
708
  OR lower(COALESCE(observer_error_code, error_type)) LIKE '%parse%' THEN 'response format'
714
709
  ELSE 'unspecified'
715
- END AS category
710
+ END AS category,
711
+ NULL AS stored_category
716
712
  FROM raw_event_flush_batches
717
713
  WHERE status = '${status}' ${providerFilter} ${cursorFilter}
718
714
  ORDER BY updated_at DESC, id DESC
@@ -729,7 +725,7 @@ function maintenanceSelect(options) {
729
725
  return `SELECT 'maintenance' AS source_type, kind AS source_id,
730
726
  'm:' || hex(kind) AS order_key, updated_at AS occurred_at,
731
727
  status, progress_current AS metric_a, progress_total AS metric_b,
732
- NULL AS category
728
+ NULL AS category, NULL AS stored_category
733
729
  FROM maintenance_jobs WHERE status IN (${statuses.join(", ")})`;
734
730
  }
735
731
  function backlogSelect(options) {
@@ -763,7 +759,8 @@ function backlogSelect(options) {
763
759
  'b:' || printf('%020d', 0) AS order_key, occurred_at,
764
760
  CASE WHEN pending >= 1000 THEN 'error' ELSE 'warning' END AS status,
765
761
  pending AS metric_a, sessions AS metric_b,
766
- CASE WHEN candidate_count > 1000 THEN 'capped' ELSE NULL END AS category
762
+ CASE WHEN candidate_count > 1000 THEN 'capped' ELSE NULL END AS category,
763
+ NULL AS stored_category
767
764
  FROM (
768
765
  SELECT MAX(updated_at) AS occurred_at,
769
766
  SUM(pending) AS pending,
@@ -805,11 +802,28 @@ function loadSourceRows(store, options) {
805
802
  ORDER BY occurred_at DESC, order_key DESC
806
803
  LIMIT @resultLimit`).all(params);
807
804
  }
805
+ var SYNC_FAILURE_MESSAGES = {
806
+ connectivity: "Sync could not reach a paired device, or the device did not respond in time.",
807
+ trust: "Sync stopped because the pairing or identity check between two devices failed.",
808
+ scope: "Sync stopped because two devices disagree about access to a shared Space.",
809
+ compatibility: "Sync stopped because two devices run incompatible Codemem versions."
810
+ };
811
+ var SPECIFIC_STORED_SYNC_CATEGORIES = /* @__PURE__ */ new Set([
812
+ "trust",
813
+ "scope",
814
+ "connectivity",
815
+ "compatibility"
816
+ ]);
817
+ function syncFailureCategory(row) {
818
+ const stored = row.stored_category;
819
+ if (stored && SPECIFIC_STORED_SYNC_CATEGORIES.has(stored)) return stored;
820
+ return classifyRecordedSyncFailure(row.category);
821
+ }
808
822
  function syncEvent(row, includeTechnical) {
809
823
  const succeeded = row.status === "succeeded";
810
824
  const opsIn = Number(row.metric_a ?? 0);
811
825
  const opsOut = Number(row.metric_b ?? 0);
812
- const category = row.category ?? "unspecified";
826
+ const category = succeeded ? "other" : syncFailureCategory(row);
813
827
  return {
814
828
  id: opaqueId("sync-attempt", row.source_id),
815
829
  orderKey: row.order_key,
@@ -817,7 +831,7 @@ function syncEvent(row, includeTechnical) {
817
831
  severity: succeeded ? "info" : "error",
818
832
  subsystem: "sync",
819
833
  code: succeeded ? "sync_attempt_succeeded" : "sync_attempt_failed",
820
- message: succeeded ? "A sync attempt completed." : "A sync attempt failed before all work completed.",
834
+ message: succeeded ? "A sync attempt completed." : SYNC_FAILURE_MESSAGES[category] ?? "A sync attempt failed before all work completed.",
821
835
  recovery: succeeded ? void 0 : {
822
836
  label: "Open advanced sync diagnostics",
823
837
  href: "#advanced/sync/diagnostics"
@@ -4496,6 +4510,126 @@ function snapshotPageRequest(query, localDeviceId) {
4496
4510
  };
4497
4511
  }
4498
4512
  //#endregion
4513
+ //#region src/routes/device-rename.ts
4514
+ async function enrolledGroups(config, deviceId) {
4515
+ const remoteUrl = config.syncCoordinatorUrl || null;
4516
+ const adminSecret = config.syncCoordinatorAdminSecret || null;
4517
+ if (!remoteUrl || !adminSecret) throw new Error("coordinator_unavailable");
4518
+ const groups = await coordinatorListGroupsAction({
4519
+ remoteUrl,
4520
+ adminSecret
4521
+ });
4522
+ if (groups.length > 25) throw new Error("coordinator_evidence_too_large");
4523
+ const devices = (await Promise.all(groups.map((group) => coordinatorListDevicesAction({
4524
+ groupId: group.group_id,
4525
+ includeDisabled: true,
4526
+ remoteUrl,
4527
+ adminSecret
4528
+ })))).flat();
4529
+ if (devices.length > 500) throw new Error("coordinator_evidence_too_large");
4530
+ const matches = devices.filter((device) => device.device_id === deviceId);
4531
+ if (new Set(matches.map((device) => `${device.public_key}:${device.fingerprint}`)).size > 1) throw new Error("device_enrollment_conflict");
4532
+ return [...new Set(matches.map((device) => device.group_id))].sort();
4533
+ }
4534
+ async function renameEnrolledGroups(config, groups, deviceId, name) {
4535
+ let renamed = 0;
4536
+ for (const groupId of groups) try {
4537
+ if (!await coordinatorRenameDeviceAction({
4538
+ groupId,
4539
+ deviceId,
4540
+ displayName: name,
4541
+ remoteUrl: config.syncCoordinatorUrl || null,
4542
+ adminSecret: config.syncCoordinatorAdminSecret || null
4543
+ })) break;
4544
+ renamed += 1;
4545
+ } catch {
4546
+ break;
4547
+ }
4548
+ return renamed;
4549
+ }
4550
+ async function renameDevice(store, deviceId, name, provenance) {
4551
+ const config = readCoordinatorSyncConfig();
4552
+ if (provenance === "coordinator_enrollment" && !config.syncCoordinatorUrl) return {
4553
+ ok: false,
4554
+ error: "coordinator_device_names_unavailable",
4555
+ status: 503
4556
+ };
4557
+ let groups = [];
4558
+ if (config.syncCoordinatorUrl) {
4559
+ try {
4560
+ groups = await enrolledGroups(config, deviceId);
4561
+ } catch (error) {
4562
+ if (error instanceof Error && error.message === "device_enrollment_conflict") return {
4563
+ ok: false,
4564
+ error: "device_enrollment_conflict",
4565
+ status: 409
4566
+ };
4567
+ return {
4568
+ ok: false,
4569
+ error: "coordinator_device_names_unavailable",
4570
+ status: 503
4571
+ };
4572
+ }
4573
+ if (provenance === "coordinator_enrollment" && groups.length === 0) return {
4574
+ ok: false,
4575
+ error: "device_enrollment_unavailable",
4576
+ status: 409
4577
+ };
4578
+ }
4579
+ const renamedGroupCount = await renameEnrolledGroups(config, groups, deviceId, name);
4580
+ if (renamedGroupCount !== groups.length) return {
4581
+ ok: false,
4582
+ error: "coordinator_device_rename_incomplete",
4583
+ status: 503,
4584
+ renamedGroupCount
4585
+ };
4586
+ if (!store.db.transaction(() => {
4587
+ const result = store.db.prepare("UPDATE identity_devices SET display_name = ?, updated_at = ? WHERE device_id = ? AND status = 'active'").run(name, (/* @__PURE__ */ new Date()).toISOString(), deviceId);
4588
+ if (result.changes) store.db.prepare("UPDATE sync_peers SET name = ? WHERE peer_device_id = ?").run(name, deviceId);
4589
+ return result.changes;
4590
+ })()) return {
4591
+ ok: false,
4592
+ error: "device_not_found",
4593
+ status: 409
4594
+ };
4595
+ return {
4596
+ ok: true,
4597
+ display_name: name
4598
+ };
4599
+ }
4600
+ function deviceRenameRoutes(getStore) {
4601
+ const pendingDevices = /* @__PURE__ */ new Set();
4602
+ return new Hono().post("/api/sync/recipient-policy/v1/devices/:device_id/rename", async (c) => {
4603
+ const store = getStore();
4604
+ const deviceId = String(c.req.param("device_id") ?? "").trim();
4605
+ const binding = store.db.prepare("SELECT provenance FROM identity_devices WHERE device_id = ? AND status = 'active'").get(deviceId);
4606
+ if (!binding) return c.json({ error: "device_not_found" }, 404);
4607
+ let body;
4608
+ try {
4609
+ body = await c.req.json();
4610
+ } catch {
4611
+ return c.json({ error: "invalid_json" }, 400);
4612
+ }
4613
+ let name;
4614
+ try {
4615
+ name = normalizeHumanPresentationName(String(body.display_name ?? ""), "display_name");
4616
+ } catch {
4617
+ return c.json({ error: "display_name_invalid" }, 400);
4618
+ }
4619
+ if (pendingDevices.has(deviceId)) return c.json({ error: "device_rename_busy" }, 409);
4620
+ pendingDevices.add(deviceId);
4621
+ try {
4622
+ const result = await renameDevice(store, deviceId, name, binding.provenance);
4623
+ if (!result.ok) return c.json(result, result.status);
4624
+ return c.json(result);
4625
+ } catch {
4626
+ return c.json({ error: "device_rename_failed" }, 503);
4627
+ } finally {
4628
+ pendingDevices.delete(deviceId);
4629
+ }
4630
+ });
4631
+ }
4632
+ //#endregion
4499
4633
  //#region src/routes/team-setup-view.ts
4500
4634
  var enabled = {
4501
4635
  enabled: true,
@@ -10008,7 +10142,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10008
10142
  localDeviceId: store.deviceId
10009
10143
  }));
10010
10144
  });
10011
- app.get("/api/sync/recipient-policy/v1/intent", (c) => {
10145
+ app.route("/", deviceRenameRoutes(getStore)).get("/api/sync/recipient-policy/v1/intent", (c) => {
10012
10146
  const store = getStore();
10013
10147
  return c.json(listRecipientPolicyIntent(store.db));
10014
10148
  });