@codemem/server 0.46.0-alpha.2 → 0.46.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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, 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, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildFilterClausesWithContext, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, 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, dedupeOrderedIds, 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, 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, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveProjectRoot, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, storeVectors, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, toJson, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, validateRawEvents, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature } 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, 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";
@@ -284,7 +284,7 @@ var DEFAULTS = {
284
284
  sync_host: "0.0.0.0",
285
285
  sync_port: 7337,
286
286
  sync_interval_s: 120,
287
- sync_mdns: true,
287
+ sync_mdns: false,
288
288
  sync_coordinator_timeout_s: 3,
289
289
  sync_coordinator_presence_ttl_s: 180,
290
290
  raw_events_sweeper_interval_s: 30
@@ -303,14 +303,18 @@ function getEffectiveConfig(configData) {
303
303
  ...configData
304
304
  };
305
305
  for (const key of ["claude_command", "codex_command"]) effective[key] = coerceObserverCommand(effective[key]) ?? DEFAULTS[key];
306
- for (const [key, envVar] of Object.entries(CODEMEM_CONFIG_ENV_OVERRIDES)) {
307
- const val = process.env[envVar];
308
- if (val == null || val === "") continue;
309
- if (key === "claude_command" || key === "codex_command") effective[key] = coerceObserverCommand(val) ?? effective[key];
310
- else effective[key] = val;
311
- }
306
+ Object.assign(effective, getCodememEnvOverrideValues());
312
307
  return effective;
313
308
  }
309
+ function observerRuntimeMetadata(configData) {
310
+ return {
311
+ resolved_observer_runtime: resolveObserverRuntime(configData),
312
+ observer_runtime_by_auth_source: Object.fromEntries([...AUTH_SOURCES].map((source) => [source, resolveObserverRuntime({
313
+ ...configData,
314
+ observer_auth_source: source
315
+ })]))
316
+ };
317
+ }
314
318
  function redactConfigValue(key, value) {
315
319
  if (value == null || !SECRET_CONFIG_KEYS.has(key)) return value;
316
320
  if (Array.isArray(value)) return value.length > 0 ? REDACTED_VALUE : [];
@@ -530,6 +534,7 @@ function viewerConfigSavePayload(saved, opts) {
530
534
  path: savedPath,
531
535
  config: sanitizeConfigForResponse(nextConfig),
532
536
  effective: sanitizeConfigForResponse(afterEffective),
537
+ ...observerRuntimeMetadata(nextConfig),
533
538
  protected_keys: [...PROTECTED_WRITE_KEYS].sort(),
534
539
  effects: {
535
540
  saved_keys: savedChangedKeys,
@@ -563,6 +568,7 @@ function configRoutes(opts = {}) {
563
568
  config: sanitizeConfigForResponse(configData),
564
569
  defaults: DEFAULTS,
565
570
  effective: sanitizeConfigForResponse(effective),
571
+ ...observerRuntimeMetadata(configData),
566
572
  env_overrides: getCodememEnvOverrides(),
567
573
  protected_keys: [...PROTECTED_WRITE_KEYS].sort(),
568
574
  providers: loadProviderOptions()
@@ -1594,7 +1600,7 @@ function memoryRoutes(getStore) {
1594
1600
  }
1595
1601
  const memoryId = parseStrictInteger(typeof body.memory_id === "string" ? body.memory_id : String(body.memory_id ?? ""));
1596
1602
  if (memoryId == null || memoryId <= 0) return c.json({ error: "memory_id must be int" }, 400);
1597
- if (!store.get(memoryId)) return c.json({ error: "memory not found" }, 404);
1603
+ if (!forgetTargetExists(store, memoryId, forgetSafetyFilters(body))) return c.json(...forgetNotFound(forgetSafetyFilters(body)));
1598
1604
  const row = drizzle(store.db, { schema }).select().from(schema.memoryItems).where(eq(schema.memoryItems.id, memoryId)).get();
1599
1605
  if (!row) return c.json({ error: "memory not found" }, 404);
1600
1606
  if (!store.memoryOwnedBySelf(row)) return c.json({ error: "memory not owned by this device" }, 403);
@@ -1612,6 +1618,19 @@ function memoryRoutes(getStore) {
1612
1618
  });
1613
1619
  return app;
1614
1620
  }
1621
+ function forgetNotFound(filters) {
1622
+ return [{ error: Object.keys(filters).length > 0 ? "not_found" : "memory not found" }, 404];
1623
+ }
1624
+ function forgetSafetyFilters(body) {
1625
+ const filters = {};
1626
+ if (typeof body.kind === "string" && body.kind.trim()) filters.kind = body.kind.trim();
1627
+ if (typeof body.project === "string" && body.project.trim()) filters.project = body.project.trim();
1628
+ return filters;
1629
+ }
1630
+ function forgetTargetExists(store, memoryId, filters) {
1631
+ if (Object.keys(filters).length > 0) return store.timeline(null, memoryId, 0, 0, filters).some((row) => row.id === memoryId);
1632
+ return store.get(memoryId) != null;
1633
+ }
1615
1634
  //#endregion
1616
1635
  //#region src/routes/memory-tools.ts
1617
1636
  /**
@@ -2129,6 +2148,15 @@ function buildFailureImpact(latestFailure, queueTotals, authBackoff) {
2129
2148
  if (queueTotals.pending > 0) return `${queueTotals.pending} queued raw events across ${queueTotals.sessions} session(s) are waiting on a successful flush.`;
2130
2149
  return "Failed flush batches are pending retry.";
2131
2150
  }
2151
+ function readCaptureEnabled() {
2152
+ const value = process.env.CODEMEM_RAW_EVENTS?.trim().toLowerCase();
2153
+ if (!value) return null;
2154
+ return ![
2155
+ "0",
2156
+ "false",
2157
+ "off"
2158
+ ].includes(value);
2159
+ }
2132
2160
  function observerStatusRoutes(deps) {
2133
2161
  const app = new Hono();
2134
2162
  app.get("/api/observer-status", (c) => {
@@ -2138,6 +2166,7 @@ function observerStatusRoutes(deps) {
2138
2166
  if (!store || typeof store.rawEventBacklogTotals !== "function") return c.json({
2139
2167
  active: null,
2140
2168
  available_credentials: {},
2169
+ capture_enabled: readCaptureEnabled(),
2141
2170
  latest_failure: null,
2142
2171
  queue: {
2143
2172
  pending: 0,
@@ -2161,6 +2190,7 @@ function observerStatusRoutes(deps) {
2161
2190
  return c.json({
2162
2191
  active,
2163
2192
  available_credentials: availableCredentials,
2193
+ capture_enabled: readCaptureEnabled(),
2164
2194
  latest_failure: failureWithImpact,
2165
2195
  queue: {
2166
2196
  ...queueTotals,
@@ -2240,6 +2270,34 @@ function validateViewerTarget(store, payload, options = {}) {
2240
2270
  if ((options.requireCurrentIdentity || payload.identity_target != null) && !store.hasCurrentIdentity()) return conflict("viewer_identity_mismatch", "viewer identity does not match request");
2241
2271
  return { ok: true };
2242
2272
  }
2273
+ /**
2274
+ * Middleware read of db_path / identity_target from the query string.
2275
+ * Native tools (pi extension) send targets as query params so a pre-handler
2276
+ * guard can reject a foreign viewer without consuming POST bodies. Lenient:
2277
+ * clients that omit targets (UI, MCP, curl) are unaffected.
2278
+ */
2279
+ function viewerTargetQueryGuard(getStore) {
2280
+ return async (c, next) => {
2281
+ const dbPath = c.req.query("db_path");
2282
+ const rawIdentity = c.req.query("identity_target");
2283
+ if (dbPath == null && rawIdentity == null) return next();
2284
+ let identityTarget;
2285
+ if (rawIdentity != null) try {
2286
+ identityTarget = JSON.parse(rawIdentity);
2287
+ } catch {
2288
+ return c.json({ error: {
2289
+ code: "invalid_request",
2290
+ message: "identity_target must be JSON"
2291
+ } }, 400);
2292
+ }
2293
+ const target = validateViewerTarget(getStore(), {
2294
+ db_path: dbPath ?? void 0,
2295
+ identity_target: identityTarget
2296
+ });
2297
+ if (!target.ok) return c.json(target.body, target.status);
2298
+ return next();
2299
+ };
2300
+ }
2243
2301
  //#endregion
2244
2302
  //#region src/routes/pack.ts
2245
2303
  var MAX_LEDGER_PAYLOAD_BYTES = 16384;
@@ -3789,6 +3847,8 @@ var AUTOMATIC_WAITING_ACCEPTANCE_RETRY_COOLDOWN_MS = 3e4;
3789
3847
  var AUTOMATIC_WAITING_DEVICE_RETRY_COOLDOWN_MS = 3e5;
3790
3848
  var TERMINAL_SHARE_MAINTENANCE_ERRORS = /* @__PURE__ */ new Set([
3791
3849
  "coordinator_not_configured",
3850
+ "conflicting_repository_mappings",
3851
+ "project_mapping_conflict",
3792
3852
  "team_sharing_not_configured",
3793
3853
  "team_selection_ambiguous",
3794
3854
  "initiating_device_not_reviewed",
@@ -3990,25 +4050,69 @@ async function advancePendingProjectSharesOperation(store, options) {
3990
4050
  var RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT = 10;
3991
4051
  var RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT = 3;
3992
4052
  var RECIPIENT_POLICY_MAINTENANCE_BACKOFF_MS = 6e4;
4053
+ function recipientPolicyMaintenanceCandidates(store, retryBefore, limit) {
4054
+ const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
4055
+ const canonicalize = (projectId) => canonicalRepositoryProjectIdentity(repositoryIdentities, projectId);
4056
+ const projectIds = store.db.prepare(`SELECT canonical_project_identity FROM project_recipients
4057
+ UNION SELECT canonical_project_identity FROM recipient_policy_authority_states`).all();
4058
+ const authorityRows = store.db.prepare(`SELECT authority_state, canonical_project_identity, last_attempt_at, safe_error_code
4059
+ FROM recipient_policy_authority_states`).all();
4060
+ const authorityByProject = /* @__PURE__ */ new Map();
4061
+ for (const row of authorityRows) {
4062
+ const projectId = canonicalize(row.canonical_project_identity);
4063
+ const current = authorityByProject.get(projectId);
4064
+ if (!current) {
4065
+ authorityByProject.set(projectId, row);
4066
+ continue;
4067
+ }
4068
+ if (current.canonical_project_identity === projectId) continue;
4069
+ if (row.canonical_project_identity === projectId) {
4070
+ authorityByProject.set(projectId, row);
4071
+ continue;
4072
+ }
4073
+ if (String(row.last_attempt_at ?? "") > String(current.last_attempt_at ?? "")) authorityByProject.set(projectId, row);
4074
+ }
4075
+ return [...new Set(projectIds.map((row) => canonicalize(row.canonical_project_identity)))].filter((projectId) => {
4076
+ const authority = authorityByProject.get(projectId);
4077
+ return authority == null || authority.last_attempt_at == null || authority.safe_error_code == null && authority.authority_state !== "active" || authority.last_attempt_at <= retryBefore;
4078
+ }).toSorted((left, right) => {
4079
+ const leftAttempt = authorityByProject.get(left)?.last_attempt_at;
4080
+ const rightAttempt = authorityByProject.get(right)?.last_attempt_at;
4081
+ if (leftAttempt == null && rightAttempt != null) return -1;
4082
+ if (leftAttempt != null && rightAttempt == null) return 1;
4083
+ return String(leftAttempt ?? "").localeCompare(String(rightAttempt ?? "")) || left.localeCompare(right);
4084
+ }).slice(0, limit);
4085
+ }
4086
+ function recordRecipientPolicyMaintenanceFailure(store, projectId, attemptedAt) {
4087
+ store.db.prepare(`INSERT INTO recipient_policy_authority_states(
4088
+ canonical_project_identity, state_changed_at, created_at, updated_at,
4089
+ attempt_count, last_attempt_at, safe_error_code, last_error_at
4090
+ ) VALUES (?, ?, ?, ?, 1, ?, 'recipient_policy_reconciliation_failed', ?)
4091
+ ON CONFLICT(canonical_project_identity) DO UPDATE SET
4092
+ attempt_count = attempt_count + 1, last_attempt_at = excluded.last_attempt_at,
4093
+ safe_error_code = excluded.safe_error_code, last_error_at = excluded.last_error_at,
4094
+ updated_at = excluded.updated_at`).run(projectId, attemptedAt, attemptedAt, attemptedAt, attemptedAt, attemptedAt);
4095
+ }
4096
+ function isSqliteBusy$1(error) {
4097
+ if (!error || typeof error !== "object" || !("code" in error)) return false;
4098
+ const code = error.code;
4099
+ return typeof code === "string" && (code.startsWith("SQLITE_BUSY") || code.startsWith("SQLITE_LOCKED"));
4100
+ }
4101
+ function recordMaintenanceFailureIfAvailable(store, projectId, attemptedAt, error) {
4102
+ if (isSqliteBusy$1(error)) return;
4103
+ try {
4104
+ recordRecipientPolicyMaintenanceFailure(store, projectId, attemptedAt);
4105
+ } catch (bookkeepingError) {
4106
+ if (!isSqliteBusy$1(bookkeepingError)) throw bookkeepingError;
4107
+ }
4108
+ }
3993
4109
  async function reconcileRecipientPolicyProjectsOperation(store, options) {
3994
4110
  const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT), RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT));
3995
4111
  const maintenanceNow = options.now ?? /* @__PURE__ */ new Date();
3996
4112
  const backoffMs = Math.max(0, Math.trunc(options.backoffMs ?? RECIPIENT_POLICY_MAINTENANCE_BACKOFF_MS));
3997
4113
  const retryBefore = new Date(maintenanceNow.getTime() - backoffMs).toISOString();
3998
- const rows = store.db.prepare(`WITH projects AS (
3999
- SELECT DISTINCT canonical_project_identity FROM project_recipients
4000
- UNION
4001
- SELECT canonical_project_identity FROM recipient_policy_authority_states
4002
- )
4003
- SELECT projects.canonical_project_identity
4004
- FROM projects
4005
- LEFT JOIN recipient_policy_authority_states authority
4006
- ON authority.canonical_project_identity = projects.canonical_project_identity
4007
- WHERE authority.safe_error_code IS NULL OR authority.last_attempt_at IS NULL
4008
- OR authority.last_attempt_at <= ?
4009
- ORDER BY CASE WHEN authority.last_attempt_at IS NULL THEN 0 ELSE 1 END,
4010
- authority.last_attempt_at, projects.canonical_project_identity
4011
- LIMIT ?`).all(retryBefore, limit);
4114
+ const attemptedAt = maintenanceNow.toISOString();
4115
+ const projectIds = recipientPolicyMaintenanceCandidates(store, retryBefore, limit);
4012
4116
  const reconcileProject = options.reconcileProject ?? reconcileRecipientPolicyProject;
4013
4117
  const result = {
4014
4118
  processed: 0,
@@ -4018,25 +4122,26 @@ async function reconcileRecipientPolicyProjectsOperation(store, options) {
4018
4122
  failed: 0,
4019
4123
  items: []
4020
4124
  };
4021
- for (const row of rows) {
4125
+ for (const projectId of projectIds) {
4022
4126
  result.processed += 1;
4023
4127
  try {
4024
4128
  const outcome = await reconcileProject(store.db, {
4025
- canonicalProjectIdentity: row.canonical_project_identity,
4129
+ canonicalProjectIdentity: projectId,
4026
4130
  leaseOwner: options.leaseOwner ?? `recipient-policy-maintenance:${store.deviceId || process.pid}`
4027
4131
  }, options.effects);
4028
4132
  if (outcome.status === "active") result.active += 1;
4029
4133
  else if (outcome.status === "needs_attention") result.attention += 1;
4030
4134
  else result.waiting += 1;
4031
4135
  result.items.push({
4032
- canonicalProjectIdentity: row.canonical_project_identity,
4136
+ canonicalProjectIdentity: projectId,
4033
4137
  status: outcome.status,
4034
4138
  safeErrorCode: outcome.safeErrorCode
4035
4139
  });
4036
- } catch {
4140
+ } catch (error) {
4141
+ recordMaintenanceFailureIfAvailable(store, projectId, attemptedAt, error);
4037
4142
  result.failed += 1;
4038
4143
  result.items.push({
4039
- canonicalProjectIdentity: row.canonical_project_identity,
4144
+ canonicalProjectIdentity: projectId,
4040
4145
  status: "failed",
4041
4146
  safeErrorCode: "recipient_policy_reconciliation_failed"
4042
4147
  });
@@ -4319,6 +4424,40 @@ var PEERS_QUERY$1 = `
4319
4424
  ORDER BY name, peer_device_id
4320
4425
  `;
4321
4426
  //#endregion
4427
+ //#region src/snapshot-source.ts
4428
+ function snapshotGeneration(raw) {
4429
+ if (!raw?.trim()) return null;
4430
+ return Number.parseInt(raw, 10);
4431
+ }
4432
+ function snapshotSourceDevice(requested, localDeviceId) {
4433
+ if (requested !== void 0 && requested !== localDeviceId) throw new Error("retirement_snapshot_source_required");
4434
+ return requested;
4435
+ }
4436
+ function snapshotErrorStatus(message) {
4437
+ if ([
4438
+ "retirement_snapshot_source_required",
4439
+ "missing_generation",
4440
+ "missing_snapshot_id"
4441
+ ].includes(message)) return 400;
4442
+ if (message === "generation_mismatch" || message === "boundary_mismatch") return 409;
4443
+ return null;
4444
+ }
4445
+ function snapshotPageRequest(query, localDeviceId) {
4446
+ const generation = snapshotGeneration(query("generation"));
4447
+ const snapshotId = query("snapshot_id");
4448
+ if (generation === null || !Number.isFinite(generation)) throw new Error("missing_generation");
4449
+ if (!snapshotId) throw new Error("missing_snapshot_id");
4450
+ const rawLimit = Number.parseInt(query("limit") ?? "200", 10);
4451
+ return {
4452
+ generation,
4453
+ snapshotId,
4454
+ baselineCursor: query("baseline_cursor") ?? null,
4455
+ pageToken: query("page_token") ?? null,
4456
+ sourceDeviceId: snapshotSourceDevice(query("source_device_id"), localDeviceId),
4457
+ limit: Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 5e3)) : 200
4458
+ };
4459
+ }
4460
+ //#endregion
4322
4461
  //#region src/routes/team-setup-view.ts
4323
4462
  var enabled = {
4324
4463
  enabled: true,
@@ -6264,6 +6403,26 @@ function tryParsePairingPayload(value) {
6264
6403
  addresses
6265
6404
  };
6266
6405
  }
6406
+ async function inspectPairingInvite(c, next) {
6407
+ const body = await parseViewerJsonBody(c);
6408
+ if (typeof body?.invite !== "string") return c.json({ error: "invite_invalid" }, 400);
6409
+ try {
6410
+ const pairing = tryParsePairingPayload(body.invite.match(/echo\s+['"]([A-Za-z0-9+/=]+)['"]\s*\|\s*base64\s+-d\s*\|\s*codemem/)?.[1]?.trim() || body.invite.trim());
6411
+ if (pairing?.kind === "invalid-pair") return c.json({ error: pairing.error }, 400);
6412
+ if (pairing?.kind !== "pair") {
6413
+ decodeInvitePayload(extractInvitePayload(body.invite));
6414
+ return next();
6415
+ }
6416
+ return c.json({
6417
+ kind: "pair",
6418
+ device_id: pairing.device_id,
6419
+ fingerprint: pairing.fingerprint,
6420
+ addresses: pairing.addresses
6421
+ });
6422
+ } catch (error) {
6423
+ return c.json({ error: safeRecipientInviteError(error) }, 400);
6424
+ }
6425
+ }
6267
6426
  function coordinatorAdminStatusPayload(config = readCoordinatorSyncConfig()) {
6268
6427
  const groups = config.syncCoordinatorGroups;
6269
6428
  const activeGroup = groups[0] ?? null;
@@ -6482,6 +6641,14 @@ function missingGuardrailConfirmations(warnings, confirmedTokens) {
6482
6641
  const confirmed = new Set(confirmedTokens);
6483
6642
  return warnings.filter((warning) => warning.requires_confirmation && (!warning.confirmation_token || !confirmed.has(warning.confirmation_token)));
6484
6643
  }
6644
+ function mappingGuardrailChallenge(warnings, missing) {
6645
+ return {
6646
+ error: "guardrail_confirmation_required",
6647
+ required_guardrails: [...new Set(missing.map((warning) => warning.code))],
6648
+ required_guardrail_tokens: [...new Set(missing.map((warning) => warning.confirmation_token))].filter((token) => typeof token === "string" && token.length > 0),
6649
+ guardrail_warnings: warnings
6650
+ };
6651
+ }
6485
6652
  function parseViewerProjectMappingInput(body) {
6486
6653
  const id = optionalViewerInteger(body, "id");
6487
6654
  const priority = optionalViewerInteger(body, "priority");
@@ -6496,6 +6663,14 @@ function parseViewerProjectMappingInput(body) {
6496
6663
  source: optionalViewerStrictString(body, "source") ?? "user"
6497
6664
  };
6498
6665
  }
6666
+ function parseViewerProjectMappingInputs(body) {
6667
+ const rawMappings = body.mappings;
6668
+ if (!Array.isArray(rawMappings) || rawMappings.length === 0) throw new Error("mappings must be a non-empty array");
6669
+ return rawMappings.map((raw) => {
6670
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("each mapping must be an object");
6671
+ return parseViewerProjectMappingInput(raw);
6672
+ });
6673
+ }
6499
6674
  function coordinatorAdminMutationStatus(message) {
6500
6675
  if (message.includes("scope_not_found") || message.includes("membership_not_found") || message.includes("group_not_found") || message.includes("Group not found") || message.includes("Scope not found") || message.includes("device_not_enrolled_for_scope_group") || message.includes("device must be enrolled")) return 404;
6501
6676
  if (message.includes("not active") || message.includes("scope_not_active") || message.includes("scope_membership_effect_conflict") || message.includes("group_archived") || message.includes("Group is archived")) return 409;
@@ -7453,13 +7628,25 @@ function recipientPolicyReadCopy(state) {
7453
7628
  explanation: "Legacy scope enforcement remains in control while reconciliation is pending."
7454
7629
  };
7455
7630
  }
7631
+ function recipientPolicyAuthorityForAliases(store, canonicalProjectIdentity, aliases) {
7632
+ const canonical = getRecipientPolicyAuthorityState(store.db, canonicalProjectIdentity);
7633
+ if (canonical) return canonical;
7634
+ return aliases.filter((identity) => identity !== canonicalProjectIdentity).map((identity) => getRecipientPolicyAuthorityState(store.db, identity)).filter((state) => state != null).toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0] ?? null;
7635
+ }
7456
7636
  function listRecipientPolicyReconciliationStatus(store) {
7637
+ const projectIds = store.db.prepare(`SELECT DISTINCT canonical_project_identity FROM project_recipients WHERE status = 'active'
7638
+ UNION SELECT canonical_project_identity FROM recipient_policy_authority_states
7639
+ ORDER BY canonical_project_identity`).all().map((row) => row.canonical_project_identity);
7640
+ const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
7641
+ const aliasesByProject = /* @__PURE__ */ new Map();
7642
+ for (const projectId of projectIds) {
7643
+ const canonical = canonicalRepositoryProjectIdentity(repositoryIdentities, projectId);
7644
+ aliasesByProject.set(canonical, [...aliasesByProject.get(canonical) ?? [], projectId]);
7645
+ }
7457
7646
  return {
7458
7647
  version: 1,
7459
- items: store.db.prepare(`SELECT DISTINCT canonical_project_identity FROM project_recipients WHERE status = 'active'
7460
- UNION SELECT canonical_project_identity FROM recipient_policy_authority_states
7461
- ORDER BY canonical_project_identity`).all().map((row) => row.canonical_project_identity).map((canonicalProjectIdentity) => {
7462
- const state = recipientPolicyReadState(getRecipientPolicyAuthorityState(store.db, canonicalProjectIdentity));
7648
+ items: [...aliasesByProject.keys()].toSorted().map((canonicalProjectIdentity) => {
7649
+ const state = recipientPolicyReadState(recipientPolicyAuthorityForAliases(store, canonicalProjectIdentity, aliasesByProject.get(canonicalProjectIdentity) ?? []));
7463
7650
  return {
7464
7651
  canonicalProjectIdentity,
7465
7652
  state,
@@ -8453,6 +8640,7 @@ function addLegacySharedReviewRow(groups, row, identity, candidate, isOwnedBySel
8453
8640
  }
8454
8641
  function collectLegacySharedReviewGroups(store) {
8455
8642
  const ownedBySelf = store.buildOwnershipPredicate();
8643
+ const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
8456
8644
  const candidatesByIdentity = new Map(listProjectScopeCandidates(store.db, { limit: null }).map((candidate) => [candidate.workspace_identity, candidate]));
8457
8645
  const groups = /* @__PURE__ */ new Map();
8458
8646
  let memoryCount = 0;
@@ -8464,6 +8652,11 @@ function collectLegacySharedReviewGroups(store) {
8464
8652
  cwd: row.cwd,
8465
8653
  gitBranch: row.git_branch,
8466
8654
  gitRemote: row.git_remote,
8655
+ repositoryIdentity: repositoryIdentityForWorkspace(repositoryIdentities, {
8656
+ cwd: row.cwd,
8657
+ gitRemote: row.git_remote,
8658
+ metadataJson: row.session_metadata_json
8659
+ }),
8467
8660
  project: row.project,
8468
8661
  workspaceId: row.workspace_id
8469
8662
  });
@@ -8538,7 +8731,8 @@ function legacySharedReviewSummaryRows(store) {
8538
8731
  m.workspace_id,
8539
8732
  m.actor_id,
8540
8733
  m.origin_device_id,
8541
- m.metadata_json
8734
+ m.metadata_json,
8735
+ s.metadata_json AS session_metadata_json
8542
8736
  FROM memory_items m
8543
8737
  LEFT JOIN sessions s ON s.id = m.session_id
8544
8738
  WHERE m.scope_id = ?
@@ -8578,7 +8772,8 @@ function legacySharedReviewRows(store) {
8578
8772
  m.workspace_id,
8579
8773
  m.actor_id,
8580
8774
  m.origin_device_id,
8581
- m.metadata_json
8775
+ m.metadata_json,
8776
+ s.metadata_json AS session_metadata_json
8582
8777
  FROM memory_items m
8583
8778
  LEFT JOIN sessions s ON s.id = m.session_id
8584
8779
  WHERE m.scope_id = ?
@@ -8595,7 +8790,7 @@ function legacySharedReviewConfirmationToken(input) {
8595
8790
  return Buffer.from(`${input.workspaceIdentity}|${input.scopeId}|${payload}`, "utf8").toString("base64url");
8596
8791
  }
8597
8792
  function projectInventoryRowsForWorkspace(store, workspaceIdentity) {
8598
- return store.db.prepare(`SELECT m.id,
8793
+ const rows = store.db.prepare(`SELECT m.id,
8599
8794
  m.rev,
8600
8795
  s.project,
8601
8796
  s.cwd,
@@ -8604,15 +8799,23 @@ function projectInventoryRowsForWorkspace(store, workspaceIdentity) {
8604
8799
  m.workspace_id,
8605
8800
  m.actor_id,
8606
8801
  m.origin_device_id,
8607
- m.metadata_json
8802
+ m.metadata_json,
8803
+ s.metadata_json AS session_metadata_json
8608
8804
  FROM memory_items m
8609
8805
  LEFT JOIN sessions s ON s.id = m.session_id
8610
8806
  WHERE m.active = 1
8611
- AND m.deleted_at IS NULL`).all().filter((row) => {
8807
+ AND m.deleted_at IS NULL`).all();
8808
+ const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
8809
+ return rows.filter((row) => {
8612
8810
  return canonicalWorkspaceIdentity({
8613
8811
  cwd: row.cwd,
8614
8812
  gitBranch: row.git_branch,
8615
8813
  gitRemote: row.git_remote,
8814
+ repositoryIdentity: repositoryIdentityForWorkspace(repositoryIdentities, {
8815
+ cwd: row.cwd,
8816
+ gitRemote: row.git_remote,
8817
+ metadataJson: row.session_metadata_json
8818
+ }),
8616
8819
  project: row.project,
8617
8820
  workspaceId: row.workspace_id
8618
8821
  }).value === workspaceIdentity;
@@ -8666,11 +8869,17 @@ function assertLocalDeviceScopeMembership(store, scopeId) {
8666
8869
  LIMIT 1`).get(scopeId, store.deviceId)) throw new Error(`local device is not a member of Sharing domain ${scopeId}`);
8667
8870
  }
8668
8871
  function legacySharedReviewRowsForWorkspace(store, workspaceIdentity) {
8872
+ const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
8669
8873
  return legacySharedReviewRows(store).filter((row) => {
8670
8874
  return canonicalWorkspaceIdentity({
8671
8875
  cwd: row.cwd,
8672
8876
  gitBranch: row.git_branch,
8673
8877
  gitRemote: row.git_remote,
8878
+ repositoryIdentity: repositoryIdentityForWorkspace(repositoryIdentities, {
8879
+ cwd: row.cwd,
8880
+ gitRemote: row.git_remote,
8881
+ metadataJson: row.session_metadata_json
8882
+ }),
8674
8883
  project: row.project,
8675
8884
  workspaceId: row.workspace_id
8676
8885
  }).value === workspaceIdentity;
@@ -8838,6 +9047,42 @@ function negotiatedSyncCapability(c) {
8838
9047
  * network-accessible. All requests are auth-gated via signature
8839
9048
  * verification so unauthenticated callers are rejected.
8840
9049
  */
9050
+ function serveAuthorizedSnapshot(c, store, peerDeviceId) {
9051
+ try {
9052
+ const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
9053
+ const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
9054
+ const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0, {
9055
+ db: store.db,
9056
+ localDeviceId,
9057
+ negotiatedCapability: negotiatedSyncCapability(c),
9058
+ peerDeviceId
9059
+ });
9060
+ if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
9061
+ const pageRequest = snapshotPageRequest((name) => c.req.query(name), localDeviceId);
9062
+ const result = loadMemorySnapshotPageForPeer(store.db, {
9063
+ ...pageRequest,
9064
+ peerDeviceId,
9065
+ scopeId: scopeRequest.mode === "scoped" ? scopeRequest.scope_id : void 0
9066
+ });
9067
+ return c.json({
9068
+ source_device_id: pageRequest.sourceDeviceId,
9069
+ scope_id: scopeRequest.scope_id,
9070
+ generation: result.boundary.generation,
9071
+ snapshot_id: result.boundary.snapshot_id,
9072
+ baseline_cursor: result.boundary.baseline_cursor,
9073
+ retained_floor_cursor: result.boundary.retained_floor_cursor,
9074
+ sync_capability: LOCAL_SYNC_CAPABILITY,
9075
+ items: result.items,
9076
+ next_page_token: result.nextPageToken,
9077
+ has_more: result.hasMore
9078
+ });
9079
+ } catch (err) {
9080
+ const message = err instanceof Error ? err.message : "";
9081
+ const status = snapshotErrorStatus(message);
9082
+ if (status) return c.json({ error: message }, status);
9083
+ return c.json({ error: "internal_error" }, 500);
9084
+ }
9085
+ }
8841
9086
  function syncProtocolRoutes(getStore, opts = {}) {
8842
9087
  const app = new Hono();
8843
9088
  const routeRateLimit = opts.routeRateLimit ?? null;
@@ -9004,51 +9249,7 @@ function syncProtocolRoutes(getStore, opts = {}) {
9004
9249
  }
9005
9250
  const limited = rateLimitedResponse(c, auth.deviceId, true);
9006
9251
  if (limited) return limited;
9007
- try {
9008
- const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
9009
- const negotiated = negotiatedSyncCapability(c);
9010
- const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
9011
- const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0, {
9012
- db: store.db,
9013
- localDeviceId,
9014
- negotiatedCapability: negotiated,
9015
- peerDeviceId: auth.deviceId
9016
- });
9017
- if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
9018
- const rawGeneration = c.req.query("generation");
9019
- const generation = rawGeneration != null && rawGeneration.trim().length > 0 ? Number.parseInt(rawGeneration, 10) : null;
9020
- const snapshotId = c.req.query("snapshot_id") ?? null;
9021
- const baselineCursor = c.req.query("baseline_cursor") ?? null;
9022
- const pageToken = c.req.query("page_token") ?? null;
9023
- const rawLimit = Number.parseInt(c.req.query("limit") ?? "200", 10);
9024
- const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 5e3)) : 200;
9025
- if (generation == null || !Number.isFinite(generation)) return c.json({ error: "missing_generation" }, 400);
9026
- if (!snapshotId) return c.json({ error: "missing_snapshot_id" }, 400);
9027
- const result = loadMemorySnapshotPageForPeer(store.db, {
9028
- generation,
9029
- snapshotId,
9030
- baselineCursor,
9031
- pageToken,
9032
- limit,
9033
- peerDeviceId: auth.deviceId,
9034
- scopeId: scopeRequest.mode === "scoped" ? scopeRequest.scope_id : void 0
9035
- });
9036
- return c.json({
9037
- scope_id: scopeRequest.scope_id,
9038
- generation: result.boundary.generation,
9039
- snapshot_id: result.boundary.snapshot_id,
9040
- baseline_cursor: result.boundary.baseline_cursor,
9041
- retained_floor_cursor: result.boundary.retained_floor_cursor,
9042
- sync_capability: LOCAL_SYNC_CAPABILITY,
9043
- items: result.items,
9044
- next_page_token: result.nextPageToken,
9045
- has_more: result.hasMore
9046
- });
9047
- } catch (err) {
9048
- const message = err instanceof Error ? err.message : "";
9049
- if (message === "generation_mismatch" || message === "boundary_mismatch") return c.json({ error: message }, 409);
9050
- return c.json({ error: "internal_error" }, 500);
9051
- }
9252
+ return serveAuthorizedSnapshot(c, store, auth.deviceId);
9052
9253
  })();
9053
9254
  });
9054
9255
  app.post("/v1/ops", async (c) => {
@@ -9126,7 +9327,7 @@ function syncProtocolRoutes(getStore, opts = {}) {
9126
9327
  }
9127
9328
  async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9128
9329
  const config = readCoordinatorSyncConfig();
9129
- const hasCoordinatorConfiguration = config.syncCoordinatorUrl.length > 0 || config.syncCoordinatorGroups.length > 0;
9330
+ const hasCoordinatorConfiguration = config.syncCoordinatorUrl.length > 0 || config.syncCoordinatorGroups.length > 0 || config.syncCoordinatorAdminSecret.length > 0;
9130
9331
  const remoteUrl = config.syncCoordinatorUrl.trim();
9131
9332
  const adminSecret = config.syncCoordinatorAdminSecret.trim();
9132
9333
  const groupIds = [...new Set(config.syncCoordinatorGroups.map((groupId) => groupId.trim()))].filter(Boolean);
@@ -9137,7 +9338,7 @@ async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9137
9338
  };
9138
9339
  if (!remoteUrl || !adminSecret || groupIds.length === 0) return {
9139
9340
  availability: "unavailable",
9140
- safeErrorCode: "coordinator_unavailable",
9341
+ safeErrorCode: "coordinator_configuration_required",
9141
9342
  enrollments: []
9142
9343
  };
9143
9344
  if (groupIds.length > 25) return {
@@ -9170,6 +9371,174 @@ async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9170
9371
  };
9171
9372
  }
9172
9373
  }
9374
+ var ORIGIN_DEVICE_NAME_LOOKUP_BATCH_SIZE = 400;
9375
+ function originDeviceNameKey(value) {
9376
+ return value.normalize("NFKC").toLowerCase().toUpperCase().toLowerCase();
9377
+ }
9378
+ function originDeviceDisplayName(value, deviceId) {
9379
+ if (typeof value !== "string") return null;
9380
+ try {
9381
+ const displayName = normalizeHumanPresentationName(value, "display_name");
9382
+ let normalizedDeviceId = deviceId;
9383
+ try {
9384
+ normalizedDeviceId = normalizeHumanPresentationName(deviceId, "device_id");
9385
+ } catch {}
9386
+ return originDeviceNameKey(displayName) === originDeviceNameKey(normalizedDeviceId) ? null : displayName;
9387
+ } catch {
9388
+ return null;
9389
+ }
9390
+ }
9391
+ function resolveOriginDeviceDisplayNames(store, projects) {
9392
+ const deviceIds = [...new Set(projects.flatMap((project) => project.origin_devices.map((device) => device.device_id)))];
9393
+ const names = /* @__PURE__ */ new Map();
9394
+ for (let offset = 0; offset < deviceIds.length; offset += ORIGIN_DEVICE_NAME_LOOKUP_BATCH_SIZE) {
9395
+ const batch = deviceIds.slice(offset, offset + ORIGIN_DEVICE_NAME_LOOKUP_BATCH_SIZE);
9396
+ const placeholders = batch.map(() => "?").join(", ");
9397
+ const peers = store.db.prepare(`SELECT peer_device_id AS device_id, name AS display_name
9398
+ FROM sync_peers
9399
+ WHERE peer_device_id IN (${placeholders})
9400
+ AND TRIM(COALESCE(pinned_fingerprint, '')) <> ''`).all(...batch);
9401
+ for (const peer of peers) {
9402
+ const displayName = originDeviceDisplayName(peer.display_name, peer.device_id);
9403
+ if (displayName) names.set(peer.device_id, displayName);
9404
+ }
9405
+ const devices = store.db.prepare(`SELECT device_id, display_name
9406
+ FROM identity_devices
9407
+ WHERE device_id IN (${placeholders}) AND status = 'active'`).all(...batch);
9408
+ for (const device of devices) {
9409
+ const displayName = originDeviceDisplayName(device.display_name, device.device_id);
9410
+ if (displayName) names.set(device.device_id, displayName);
9411
+ }
9412
+ }
9413
+ return names;
9414
+ }
9415
+ function serializeProjectScopeInventory(store, inventory, sharingByProject) {
9416
+ const originDeviceNames = resolveOriginDeviceDisplayNames(store, inventory.projects);
9417
+ return {
9418
+ ...inventory,
9419
+ projects: inventory.projects.map((project) => ({
9420
+ ...project,
9421
+ origin_devices: project.origin_devices.map(({ device_id }) => ({
9422
+ device_id,
9423
+ display_name: originDeviceNames.get(device_id) ?? null
9424
+ })),
9425
+ sharing: sharingByProject.get(project.workspace_identity) ?? []
9426
+ }))
9427
+ };
9428
+ }
9429
+ async function deleteProjectScopeMappingRoute(c, store) {
9430
+ const id = Number(c.req.param("id"));
9431
+ let releasePublicationMutation;
9432
+ try {
9433
+ const body = c.req.header("content-type")?.includes("application/json") ? await parseViewerJsonBody(c) : {};
9434
+ if (!body) return c.json({ error: "invalid json" }, 400);
9435
+ const confirmedGuardrailTokens = optionalViewerStringList(body, "confirmed_guardrail_tokens");
9436
+ releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
9437
+ const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
9438
+ const warnings = analyzeProjectScopeMappingDeletionGuardrails(store.db, id, deviceId);
9439
+ const missing = missingGuardrailConfirmations(warnings, confirmedGuardrailTokens);
9440
+ if (missing.length > 0) return c.json({
9441
+ error: "guardrail_confirmation_required",
9442
+ required_guardrails: [...new Set(missing.map((warning) => warning.code))],
9443
+ required_guardrail_tokens: missing.map((warning) => warning.confirmation_token),
9444
+ guardrail_warnings: warnings
9445
+ }, 409);
9446
+ const deleted = deleteProjectMappingAndWakePolicies(store, id, deviceId, confirmedGuardrailTokens);
9447
+ return c.json({
9448
+ ok: true,
9449
+ deleted
9450
+ });
9451
+ } catch (error) {
9452
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
9453
+ } finally {
9454
+ releasePublicationMutation?.();
9455
+ }
9456
+ }
9457
+ function projectSharingByRepository(store, operations) {
9458
+ const operationById = new Map(operations.map((operation) => [operation.operation_id, operation]));
9459
+ const sharingByProject = /* @__PURE__ */ new Map();
9460
+ const mappings = store.db.prepare("SELECT workspace_identity, project_pattern FROM project_scope_mappings").all();
9461
+ const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db, { knownRepositoryIdentities: mappings.flatMap((mapping) => [mapping.workspace_identity, mapping.project_pattern]) });
9462
+ const seenOperationProjects = /* @__PURE__ */ new Set();
9463
+ const reviewed = store.db.prepare(`SELECT p.operation_id, p.canonical_project_identity
9464
+ FROM share_operation_projects p
9465
+ JOIN share_operations o ON o.operation_id = p.operation_id
9466
+ WHERE o.inviter_actor_id = ? ORDER BY o.created_at, p.ordinal`).all(store.actorId);
9467
+ for (const item of reviewed) {
9468
+ const operation = operationById.get(item.operation_id);
9469
+ if (!operation) continue;
9470
+ const projectId = canonicalRepositoryProjectIdentity(repositoryIdentities, item.canonical_project_identity);
9471
+ const operationProjectKey = `${item.operation_id}\u0000${projectId}`;
9472
+ if (seenOperationProjects.has(operationProjectKey)) continue;
9473
+ seenOperationProjects.add(operationProjectKey);
9474
+ const current = sharingByProject.get(projectId) ?? [];
9475
+ current.push({
9476
+ person: operation.person,
9477
+ lifecycle: {
9478
+ state: operation.lifecycle.state,
9479
+ label: operation.lifecycle.label,
9480
+ explanation: operation.lifecycle.explanation
9481
+ }
9482
+ });
9483
+ sharingByProject.set(projectId, current);
9484
+ }
9485
+ return sharingByProject;
9486
+ }
9487
+ function deactivateActorAndWakePolicies(store, actorId, now) {
9488
+ store.db.transaction(() => {
9489
+ store.db.prepare("UPDATE actors SET status = 'deactivated', updated_at = ? WHERE actor_id = ?").run(now, actorId);
9490
+ store.db.prepare("UPDATE sync_peers SET actor_id = NULL WHERE actor_id = ?").run(actorId);
9491
+ wakeRecipientPoliciesForIdentities(store.db, [actorId], now);
9492
+ }).immediate();
9493
+ }
9494
+ function finishActorMerge(store, primaryActorId, secondaryActorId, now) {
9495
+ store.db.prepare("UPDATE actors SET status = 'merged', merged_into_actor_id = ?, updated_at = ? WHERE actor_id = ?").run(primaryActorId, now, secondaryActorId);
9496
+ wakeRecipientPoliciesForIdentities(store.db, [primaryActorId, secondaryActorId], now);
9497
+ }
9498
+ function policyWakeMappings(store) {
9499
+ return store.db.prepare("SELECT id, scope_id, workspace_identity FROM project_scope_mappings ORDER BY id").all();
9500
+ }
9501
+ function existingRequestedMappings(mappings, inputs) {
9502
+ const requestedIds = new Set(inputs.map((input) => input.id).filter((id) => id != null));
9503
+ const requestedIdentities = new Set(inputs.map((input) => input.workspace_identity).filter((identity) => identity != null).map((identity) => canonicalWorkspaceIdentity({ cwd: identity }).value));
9504
+ return mappings.filter((mapping) => {
9505
+ if (requestedIds.has(mapping.id)) return true;
9506
+ if (!mapping.workspace_identity) return false;
9507
+ return requestedIdentities.has(canonicalWorkspaceIdentity({ cwd: mapping.workspace_identity }).value);
9508
+ });
9509
+ }
9510
+ function mappedProjectsInScopes(mappings, scopeIds) {
9511
+ return mappings.filter((mapping) => scopeIds.has(mapping.scope_id)).map((mapping) => mapping.workspace_identity).filter((identity) => identity != null);
9512
+ }
9513
+ function wakeRecipientPolicyProjects(store, projectIdentities, now) {
9514
+ wakeRecipientPoliciesForProjectIdentities(store.db, [...new Set(projectIdentities)], now);
9515
+ }
9516
+ function saveProjectMappingsAndWakePolicies(store, deviceId, mappingInputs) {
9517
+ return store.db.transaction(() => {
9518
+ const before = policyWakeMappings(store);
9519
+ const previousMappings = existingRequestedMappings(before, mappingInputs);
9520
+ const mappings = upsertProjectScopeSettingsMappings(store.db, mappingInputs, { deviceId });
9521
+ const after = policyWakeMappings(store);
9522
+ const changedScopeIds = /* @__PURE__ */ new Set([...previousMappings.map((mapping) => mapping.scope_id), ...mappings.map((mapping) => mapping.scope_id)]);
9523
+ wakeRecipientPolicyProjects(store, [...mappedProjectsInScopes(before, changedScopeIds), ...mappedProjectsInScopes(after, changedScopeIds)], (/* @__PURE__ */ new Date()).toISOString());
9524
+ return mappings;
9525
+ }).immediate();
9526
+ }
9527
+ function deleteProjectMappingAndWakePolicies(store, mappingId, deviceId, confirmedGuardrailTokens) {
9528
+ return store.db.transaction(() => {
9529
+ const before = policyWakeMappings(store);
9530
+ const removed = before.find((mapping) => mapping.id === mappingId);
9531
+ const deleted = deleteProjectScopeSettingsMapping(store.db, mappingId, {
9532
+ deviceId,
9533
+ confirmedGuardrailTokens
9534
+ });
9535
+ if (deleted && removed) {
9536
+ const changedScopeIds = /* @__PURE__ */ new Set([removed.scope_id]);
9537
+ wakeRecipientPolicyProjects(store, [...mappedProjectsInScopes(before, changedScopeIds), ...mappedProjectsInScopes(policyWakeMappings(store), changedScopeIds)], (/* @__PURE__ */ new Date()).toISOString());
9538
+ }
9539
+ return deleted;
9540
+ }).immediate();
9541
+ }
9173
9542
  /**
9174
9543
  * Viewer-facing sync management routes (/api/sync/*).
9175
9544
  *
@@ -9178,7 +9547,7 @@ async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9178
9547
  * local viewer.
9179
9548
  */
9180
9549
  function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9181
- const app = new Hono();
9550
+ const app = new Hono().post("/api/sync/invites/inspect", inspectPairingInvite);
9182
9551
  const readCoordinatorConfig = options.readCoordinatorConfig ?? readCoordinatorSyncConfig;
9183
9552
  const renameCoordinatorGroup = options.renameCoordinatorGroup ?? coordinatorRenameGroupAction;
9184
9553
  const loadCoordinatorEvidence = options.loadDeviceIdentityCoordinatorEvidence ?? loadConfiguredDeviceIdentityCoordinatorEvidence;
@@ -9802,34 +10171,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9802
10171
  scopeId: c.req.query("scope_id"),
9803
10172
  status: c.req.query("status")
9804
10173
  });
9805
- const operations = await shareOperationReadModels(store, void 0, false);
9806
- const operationById = new Map(operations.map((operation) => [operation.operation_id, operation]));
9807
- const sharingByProject = /* @__PURE__ */ new Map();
9808
- const reviewed = store.db.prepare(`SELECT p.operation_id, p.canonical_project_identity
9809
- FROM share_operation_projects p
9810
- JOIN share_operations o ON o.operation_id = p.operation_id
9811
- WHERE o.inviter_actor_id = ? ORDER BY o.created_at, p.ordinal`).all(store.actorId);
9812
- for (const item of reviewed) {
9813
- const operation = operationById.get(item.operation_id);
9814
- if (!operation) continue;
9815
- const current = sharingByProject.get(item.canonical_project_identity) ?? [];
9816
- current.push({
9817
- person: operation.person,
9818
- lifecycle: {
9819
- state: operation.lifecycle.state,
9820
- label: operation.lifecycle.label,
9821
- explanation: operation.lifecycle.explanation
9822
- }
9823
- });
9824
- sharingByProject.set(item.canonical_project_identity, current);
9825
- }
9826
- return c.json({
9827
- ...inventory,
9828
- projects: inventory.projects.map((project) => ({
9829
- ...project,
9830
- sharing: sharingByProject.get(project.workspace_identity) ?? []
9831
- }))
9832
- });
10174
+ const sharingByProject = projectSharingByRepository(store, await shareOperationReadModels(store, void 0, false));
10175
+ return c.json(serializeProjectScopeInventory(store, inventory, sharingByProject));
9833
10176
  });
9834
10177
  app.post("/api/sync/projects/reassign-project", async (c) => {
9835
10178
  const store = getStore();
@@ -10085,28 +10428,19 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10085
10428
  const confirmedGuardrailTokens = optionalViewerStringList(body, "confirmed_guardrail_tokens");
10086
10429
  const mappingInput = parseViewerProjectMappingInput(body);
10087
10430
  releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
10088
- const analysis = analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput);
10431
+ const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
10432
+ const analysis = analyzeProjectScopeMappingChangeGuardrails(store.db, {
10433
+ ...mappingInput,
10434
+ deviceId
10435
+ });
10089
10436
  if (analysis.requested_workspace_identity?.startsWith("unmapped:")) return c.json({
10090
10437
  error: "unmapped_project_local_only",
10091
10438
  message: "Unmapped projects need a stable path, git remote, or workspace id before assigning a Sharing domain.",
10092
10439
  guardrail_warnings: analysis.warnings
10093
10440
  }, 400);
10094
10441
  const missingConfirmations = missingGuardrailConfirmations(analysis.warnings, confirmedGuardrailTokens);
10095
- if (missingConfirmations.length > 0) {
10096
- const missingCodes = [...new Set(missingConfirmations.map((warning) => warning.code))];
10097
- const missingTokens = [...new Set(missingConfirmations.map((warning) => warning.confirmation_token))].filter((token) => typeof token === "string" && token.length > 0);
10098
- return c.json({
10099
- error: "guardrail_confirmation_required",
10100
- required_guardrails: missingCodes,
10101
- required_guardrail_tokens: missingTokens,
10102
- guardrail_warnings: analysis.warnings
10103
- }, 409);
10104
- }
10105
- const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
10106
- const mapping = upsertProjectScopeSettingsMapping(store.db, {
10107
- deviceId,
10108
- ...mappingInput
10109
- });
10442
+ if (missingConfirmations.length > 0) return c.json(mappingGuardrailChallenge(analysis.warnings, missingConfirmations), 409);
10443
+ const [mapping] = saveProjectMappingsAndWakePolicies(store, deviceId, [mappingInput]);
10110
10444
  return c.json({
10111
10445
  ok: true,
10112
10446
  mapping,
@@ -10124,14 +10458,13 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10124
10458
  if (!body) return c.json({ error: "invalid json" }, 400);
10125
10459
  let releasePublicationMutation;
10126
10460
  try {
10127
- const rawMappings = body.mappings;
10128
- if (!Array.isArray(rawMappings) || rawMappings.length === 0) return c.json({ error: "mappings must be a non-empty array" }, 400);
10129
- const mappingInputs = rawMappings.map((raw) => {
10130
- if (raw == null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("each mapping must be an object");
10131
- return parseViewerProjectMappingInput(raw);
10132
- });
10461
+ const mappingInputs = parseViewerProjectMappingInputs(body);
10133
10462
  releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
10134
- const analyses = mappingInputs.map((mappingInput) => analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput));
10463
+ const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
10464
+ const analyses = analyzeProjectScopeMappingChangesGuardrails(store.db, mappingInputs.map((input) => ({
10465
+ ...input,
10466
+ deviceId
10467
+ })));
10135
10468
  const unmapped = analyses.find((analysis) => analysis.requested_workspace_identity?.startsWith("unmapped:"));
10136
10469
  if (unmapped) return c.json({
10137
10470
  error: "unmapped_project_local_only",
@@ -10139,24 +10472,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10139
10472
  guardrail_warnings: unmapped.warnings
10140
10473
  }, 400);
10141
10474
  const missingConfirmations = analyses.flatMap((analysis) => missingGuardrailConfirmations(analysis.warnings, []));
10142
- if (missingConfirmations.length > 0) {
10143
- const missingCodes = [...new Set(missingConfirmations.map((warning) => warning.code))];
10144
- const missingTokens = [...new Set(missingConfirmations.map((warning) => warning.confirmation_token))].filter((token) => typeof token === "string" && token.length > 0);
10145
- return c.json({
10146
- error: "guardrail_confirmation_required",
10147
- required_guardrails: missingCodes,
10148
- required_guardrail_tokens: missingTokens,
10149
- guardrail_warnings: analyses.flatMap((analysis) => analysis.warnings)
10150
- }, 409);
10151
- }
10152
- const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
10153
- const saveMappings = store.db.transaction(() => mappingInputs.map((mappingInput) => upsertProjectScopeSettingsMapping(store.db, {
10154
- deviceId,
10155
- ...mappingInput
10156
- })));
10475
+ if (missingConfirmations.length > 0) return c.json(mappingGuardrailChallenge(analyses.flatMap((analysis) => analysis.warnings), missingConfirmations), 409);
10157
10476
  return c.json({
10158
10477
  ok: true,
10159
- mappings: saveMappings(),
10478
+ mappings: saveProjectMappingsAndWakePolicies(store, deviceId, mappingInputs),
10160
10479
  guardrail_warnings: analyses.flatMap((analysis) => analysis.warnings)
10161
10480
  });
10162
10481
  } catch (error) {
@@ -10165,23 +10484,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10165
10484
  releasePublicationMutation?.();
10166
10485
  }
10167
10486
  });
10168
- app.delete("/api/sync/sharing-domains/project-mappings/:id", async (c) => {
10169
- const store = getStore();
10170
- const id = Number(c.req.param("id"));
10171
- let releasePublicationMutation;
10172
- try {
10173
- releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
10174
- const deleted = deleteProjectScopeSettingsMapping(store.db, id);
10175
- return c.json({
10176
- ok: true,
10177
- deleted
10178
- });
10179
- } catch (error) {
10180
- return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
10181
- } finally {
10182
- releasePublicationMutation?.();
10183
- }
10184
- });
10487
+ app.delete("/api/sync/sharing-domains/project-mappings/:id", (c) => deleteProjectScopeMappingRoute(c, getStore()));
10185
10488
  app.post("/api/sync/peers/identity", async (c) => {
10186
10489
  const store = getStore();
10187
10490
  ensureLocalActorRecord(store);
@@ -10439,11 +10742,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10439
10742
  WHERE inviter_actor_id = ? OR recipient_actor_id = ? OR person_id = ?`).run(secondaryActorId, primaryActorId, secondaryActorId, primaryActorId, secondaryActorId, primaryActorId, now, secondaryActorId, secondaryActorId, secondaryActorId);
10440
10743
  const peerUpdate = d.update(schema.syncPeers).set({ actor_id: primaryActorId }).where(eq(schema.syncPeers.actor_id, secondaryActorId)).run();
10441
10744
  if (primary.is_local) d.update(schema.syncPeers).set({ claimed_local_actor: 1 }).where(eq(schema.syncPeers.actor_id, primaryActorId)).run();
10442
- d.update(schema.actors).set({
10443
- status: "merged",
10444
- merged_into_actor_id: primaryActorId,
10445
- updated_at: now
10446
- }).where(eq(schema.actors.actor_id, secondaryActorId)).run();
10745
+ finishActorMerge(store, primaryActorId, secondaryActorId, now);
10447
10746
  return { mergedCount: Number(peerUpdate.changes ?? 0) };
10448
10747
  }).immediate();
10449
10748
  if ("error" in result) return c.json({ error: result.error }, result.status);
@@ -10462,16 +10761,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
10462
10761
  if (actorId === store.actorId) return c.json({ error: "cannot deactivate this device's own local actor" }, 409);
10463
10762
  const releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, [actorId]);
10464
10763
  queueMicrotask(releaseActorMutations);
10465
- const d = drizzle(store.db, { schema });
10466
- const actor = d.select().from(schema.actors).where(eq(schema.actors.actor_id, actorId)).get();
10764
+ const actor = drizzle(store.db, { schema }).select().from(schema.actors).where(eq(schema.actors.actor_id, actorId)).get();
10467
10765
  if (!actor) return c.json({ error: "actor not found" }, 404);
10468
10766
  if (actor.status !== "active") return c.json({ error: "actor not active" }, 409);
10469
- const now = (/* @__PURE__ */ new Date()).toISOString();
10470
- d.update(schema.actors).set({
10471
- status: "deactivated",
10472
- updated_at: now
10473
- }).where(eq(schema.actors.actor_id, actorId)).run();
10474
- d.update(schema.syncPeers).set({ actor_id: null }).where(eq(schema.syncPeers.actor_id, actorId)).run();
10767
+ deactivateActorAndWakePolicies(store, actorId, (/* @__PURE__ */ new Date()).toISOString());
10475
10768
  return c.json({ ok: true });
10476
10769
  });
10477
10770
  app.post("/api/sync/legacy-devices/claim", async (c) => {
@@ -12028,6 +12321,9 @@ function createApp(opts) {
12028
12321
  const app = new Hono();
12029
12322
  app.use("*", preflightHandler());
12030
12323
  app.use("*", originGuard({ unsafeGetPathPrefixes: [TEAM_SETUP_ROUTE_PREFIX] }));
12324
+ const viewerTargetGuard = viewerTargetQueryGuard(storeFactory);
12325
+ app.use("/api/memories/*", viewerTargetGuard);
12326
+ app.use("/api/memory", viewerTargetGuard);
12031
12327
  app.route("/", healthRoutes(storeFactory));
12032
12328
  app.route("/", diagnosticsRoutes(storeFactory));
12033
12329
  app.route("/", statsRoutes(storeFactory));