@codemem/server 0.40.0-alpha.1 → 0.40.0

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.d.ts CHANGED
@@ -11,8 +11,8 @@ import type { ObserverClient } from "@codemem/core";
11
11
  import { MemoryStore, type RawEventSweeper, VERSION } from "@codemem/core";
12
12
  import { Hono } from "hono";
13
13
  import { type InMemoryRequestRateLimiter } from "./request-rate-limit.js";
14
- export type { AdvancePendingProjectSharesResult, AdvanceProjectShareOperationResult, RecipientPolicyReconciliationReadModel, RecipientPolicyReconciliationReadState, ReconcileRecipientPolicyProjectsResult, } from "./routes/sync.js";
15
- export { advancePendingProjectShares, advanceProjectShareOperation, createRecipientPolicyReconcilerEffects, listRecipientPolicyReconciliationStatus, recipientPolicyCapabilityFromStatus, reconcileRecipientPolicyProjects, } from "./routes/sync.js";
14
+ export type { AdvancePendingProjectSharesResult, AdvanceProjectShareOperationResult, RecipientPolicyReconciliationReadModel, RecipientPolicyReconciliationReadState, ReconcileConfiguredCoordinatorEnrollmentResult, ReconcileRecipientPolicyProjectsResult, } from "./routes/sync.js";
15
+ export { advancePendingProjectShares, advanceProjectShareOperation, createRecipientPolicyReconcilerEffects, listRecipientPolicyReconciliationStatus, recipientPolicyCapabilityFromStatus, reconcileConfiguredCoordinatorEnrollment, reconcileRecipientPolicyProjects, } from "./routes/sync.js";
16
16
  export { VERSION };
17
17
  /** Get (or create) the shared store instance. Exported so the sweeper can share it. */
18
18
  export declare function getStore(): MemoryStore;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,yBAAyB,CAAC;AAQjC,YAAY,EACX,iCAAiC,EACjC,kCAAkC,EAClC,sCAAsC,EACtC,sCAAsC,EACtC,sCAAsC,GACtC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,2BAA2B,EAC3B,4BAA4B,EAC5B,sCAAsC,EACtC,uCAAuC,EACvC,mCAAmC,EACnC,gCAAgC,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,CAAC;AAKnB,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,WAAW,CAKtC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,YAAY,CAAC,EAAE,MAAM,WAAW,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE;QACtB,OAAO,CAAC,EAAE,0BAA0B,CAAC;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,4BAA4B,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IACF,oBAAoB,CAAC,EAAE,MAAM;QAC5B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;QACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,IAAI,CAAC;CACT;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8D1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8B9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,yBAAyB,CAAC;AAQjC,YAAY,EACX,iCAAiC,EACjC,kCAAkC,EAClC,sCAAsC,EACtC,sCAAsC,EACtC,8CAA8C,EAC9C,sCAAsC,GACtC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,2BAA2B,EAC3B,4BAA4B,EAC5B,sCAAsC,EACtC,uCAAuC,EACvC,mCAAmC,EACnC,wCAAwC,EACxC,gCAAgC,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,CAAC;AAKnB,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,WAAW,CAKtC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,YAAY,CAAC,EAAE,MAAM,WAAW,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE;QACtB,OAAO,CAAC,EAAE,0BAA0B,CAAC;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,4BAA4B,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IACF,oBAAoB,CAAC,EAAE,MAAM;QAC5B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;QACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,IAAI,CAAC;CACT;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8D1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8B9C"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, MemoryStore, RecipientPolicyEdgeRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildAuthHeaders, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coerceObserverCommand, commitRecipientPolicyEdges, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, inviteTokenDigest, isProjectSyncEnablementError, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parseReassignScopePayload, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientReviewedIntentDigest, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, recordNonce, refreshConfiguredScopeMembershipCache, rejectInboundScopeFailures, requestJson, resolveDbPath, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, stripPrivateObj, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, MemoryStore, RecipientPolicyEdgeRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildAuthHeaders, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coerceObserverCommand, commitRecipientPolicyEdges, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, inviteTokenDigest, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parseReassignScopePayload, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, recordNonce, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, rejectInboundScopeFailures, requestJson, resolveDbPath, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, stripPrivateObj, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
@@ -202,6 +202,8 @@ var ALLOWED_KEYS = [
202
202
  "observer_tier_routing_enabled",
203
203
  "observer_simple_model",
204
204
  "observer_simple_temperature",
205
+ "observer_reasoning_effort",
206
+ "observer_reasoning_summary",
205
207
  "observer_rich_model",
206
208
  "observer_rich_temperature",
207
209
  "observer_rich_reasoning_effort",
@@ -403,7 +405,7 @@ function validateAndApplyUpdate(configData, key, value, providers) {
403
405
  configData[key] = value;
404
406
  return null;
405
407
  }
406
- if (key === "observer_base_url" || key === "observer_model" || key === "observer_simple_model" || key === "observer_rich_model" || key === "observer_rich_reasoning_effort" || key === "observer_rich_reasoning_summary" || key === "observer_auth_file" || key === "sync_host" || key === "sync_coordinator_url" || key === "sync_coordinator_group") {
408
+ if (key === "observer_base_url" || key === "observer_model" || key === "observer_simple_model" || key === "observer_reasoning_effort" || key === "observer_reasoning_summary" || key === "observer_rich_model" || key === "observer_rich_reasoning_effort" || key === "observer_rich_reasoning_summary" || key === "observer_auth_file" || key === "sync_host" || key === "sync_coordinator_url" || key === "sync_coordinator_group") {
407
409
  if (typeof value !== "string") return `${key} must be string`;
408
410
  const trimmed = value.trim();
409
411
  if (!trimmed) delete configData[key];
@@ -2416,7 +2418,8 @@ async function shareOperationReadModels(store, operationId, includeInviteLinks =
2416
2418
  o.recipient_actor_id, o.recipient_display_name, o.recipient_device_id,
2417
2419
  o.recipient_device_display_name, o.invite_expires_at, o.acceptance_consumed_at,
2418
2420
  o.created_at, o.updated_at, a.display_name AS actor_display_name,
2419
- p.name AS peer_display_name, p.last_sync_at AS device_last_seen_at
2421
+ p.name AS peer_display_name, p.last_seen_at AS device_last_seen_at,
2422
+ p.last_sync_at AS device_last_reached_at
2420
2423
  FROM share_operations o
2421
2424
  LEFT JOIN actors a ON a.actor_id = o.person_id
2422
2425
  LEFT JOIN sync_peers p ON p.peer_device_id = o.recipient_device_id
@@ -2448,6 +2451,7 @@ async function shareOperationReadModels(store, operationId, includeInviteLinks =
2448
2451
  const deviceName = row.recipient_device_display_name || row.peer_display_name;
2449
2452
  const lifecycle = projectShareLifecycle({
2450
2453
  deviceLastSeenAt: row.device_last_seen_at,
2454
+ deviceLastReachedAt: row.device_last_reached_at,
2451
2455
  deviceName,
2452
2456
  inviteLink,
2453
2457
  now,
@@ -2674,15 +2678,44 @@ async function advancePendingProjectShares(store, options = {}) {
2674
2678
  const maintenanceNow = options.now ?? /* @__PURE__ */ new Date();
2675
2679
  const waitingAcceptanceRetryBefore = (/* @__PURE__ */ new Date(maintenanceNow.getTime() - AUTOMATIC_WAITING_ACCEPTANCE_RETRY_COOLDOWN_MS)).toISOString();
2676
2680
  const waitingRetryBefore = (/* @__PURE__ */ new Date(maintenanceNow.getTime() - AUTOMATIC_WAITING_DEVICE_RETRY_COOLDOWN_MS)).toISOString();
2677
- const rows = store.db.prepare(`SELECT operation_id FROM share_operations
2678
- WHERE inviter_actor_id = ?
2679
- AND state IN (${placeholders})
2680
- AND (state <> 'waiting_for_acceptance' OR updated_at <= ?)
2681
- AND (state <> 'waiting_for_device' OR updated_at <= ?)
2682
- ORDER BY CASE WHEN state IN ('accepted', 'provisioning', 'initial_sync') THEN 0 ELSE 1 END,
2683
- CASE WHEN state IN ('waiting_for_acceptance', 'waiting_for_device')
2684
- THEN updated_at ELSE created_at END ASC,
2685
- created_at ASC, operation_id ASC
2681
+ const rows = store.db.prepare(`SELECT operation.operation_id FROM share_operations AS operation
2682
+ WHERE operation.inviter_actor_id = ?
2683
+ AND operation.state IN (${placeholders})
2684
+ AND (operation.state <> 'waiting_for_acceptance' OR operation.updated_at <= ?)
2685
+ AND (
2686
+ operation.state <> 'waiting_for_device'
2687
+ OR operation.updated_at <= ?
2688
+ OR (
2689
+ NOT EXISTS (
2690
+ SELECT 1 FROM share_operation_steps AS step
2691
+ WHERE step.operation_id = operation.operation_id
2692
+ AND step.step_key = 'capability_preflight'
2693
+ AND step.status <> 'completed'
2694
+ AND (
2695
+ step.status = 'running'
2696
+ OR step.safe_error_code IN (
2697
+ 'waiting_for_device', 'device_offline', 'recipient_offline'
2698
+ )
2699
+ )
2700
+ )
2701
+ AND EXISTS (
2702
+ SELECT 1 FROM sync_attempts AS attempt
2703
+ WHERE attempt.id = (
2704
+ SELECT latest.id FROM sync_attempts AS latest
2705
+ WHERE latest.peer_device_id = operation.recipient_device_id
2706
+ ORDER BY latest.started_at DESC, latest.id DESC
2707
+ LIMIT 1
2708
+ )
2709
+ AND attempt.ok = 1
2710
+ AND julianday(COALESCE(attempt.finished_at, attempt.started_at)) >
2711
+ julianday(operation.updated_at)
2712
+ )
2713
+ )
2714
+ )
2715
+ ORDER BY CASE WHEN operation.state IN ('accepted', 'provisioning', 'initial_sync') THEN 0 ELSE 1 END,
2716
+ CASE WHEN operation.state IN ('waiting_for_acceptance', 'waiting_for_device')
2717
+ THEN operation.updated_at ELSE operation.created_at END ASC,
2718
+ operation.created_at ASC, operation.operation_id ASC
2686
2719
  LIMIT ?`).all(store.actorId, ...AUTOMATIC_SHARE_OPERATION_STATES, waitingAcceptanceRetryBefore, waitingRetryBefore, limit);
2687
2720
  const advanceOperation = options.advanceOperation ?? advanceProjectShareOperation;
2688
2721
  const result = {
@@ -2776,6 +2809,61 @@ async function advancePendingProjectShares(store, options = {}) {
2776
2809
  }
2777
2810
  return result;
2778
2811
  }
2812
+ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
2813
+ const config = options.config ?? readCoordinatorSyncConfig();
2814
+ const remoteUrl = config.syncCoordinatorUrl.trim();
2815
+ const adminSecret = config.syncCoordinatorAdminSecret.trim();
2816
+ if (!remoteUrl || !adminSecret || config.syncCoordinatorGroups.length === 0) return {
2817
+ skipped: true,
2818
+ groupsProcessed: 0,
2819
+ failedGroups: 0,
2820
+ devicesAdded: 0,
2821
+ membershipsAdded: 0,
2822
+ identitiesAdded: 0,
2823
+ unchanged: 0,
2824
+ issues: 0
2825
+ };
2826
+ const listDevices = options.listDevices ?? coordinatorListDevicesAction;
2827
+ const listConsumedTeamInvites = options.listConsumedTeamInvites ?? coordinatorListConsumedTeamInvitesAction;
2828
+ const reconcileSnapshot = options.reconcileSnapshot ?? ((input) => reconcileCoordinatorEnrollmentSnapshot(store.db, input));
2829
+ const total = {
2830
+ skipped: false,
2831
+ groupsProcessed: 0,
2832
+ failedGroups: 0,
2833
+ devicesAdded: 0,
2834
+ membershipsAdded: 0,
2835
+ identitiesAdded: 0,
2836
+ unchanged: 0,
2837
+ issues: 0
2838
+ };
2839
+ for (const groupId of config.syncCoordinatorGroups) try {
2840
+ const [enrollments, consumedTeamInvites] = await Promise.all([listDevices({
2841
+ groupId,
2842
+ remoteUrl,
2843
+ adminSecret
2844
+ }), listConsumedTeamInvites({
2845
+ groupId,
2846
+ remoteUrl,
2847
+ adminSecret
2848
+ })]);
2849
+ const result = reconcileSnapshot({
2850
+ coordinatorId: buildBaseUrl(remoteUrl),
2851
+ groupId,
2852
+ enrollments,
2853
+ consumedTeamInvites,
2854
+ localDeviceId: store.deviceId
2855
+ });
2856
+ total.groupsProcessed += 1;
2857
+ total.devicesAdded += result.devicesAdded;
2858
+ total.membershipsAdded += result.membershipsAdded;
2859
+ total.identitiesAdded += result.identitiesAdded;
2860
+ total.unchanged += result.unchanged;
2861
+ total.issues += result.issues.length;
2862
+ } catch {
2863
+ total.failedGroups += 1;
2864
+ }
2865
+ return total;
2866
+ }
2779
2867
  var RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT = 10;
2780
2868
  var RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT = 3;
2781
2869
  var RECIPIENT_POLICY_MAINTENANCE_BACKOFF_MS = 6e4;
@@ -2802,9 +2890,37 @@ function recipientPolicySnapshotFingerprint(scopeId, scopeMembershipEpoch, membe
2802
2890
  const canonical = memberships.toSorted((left, right) => left.deviceId.localeCompare(right.deviceId) || left.status.localeCompare(right.status) || left.membershipEpoch - right.membershipEpoch).map((membership) => `${membership.deviceId}\u0000${membership.status}\u0000${membership.membershipEpoch}`).join("");
2803
2891
  return `recipient-policy-coordinator-snapshot-v2:${createHash("sha256").update(`${scopeId}\u0000${scopeMembershipEpoch}\u0000${canonical}`).digest("hex")}`;
2804
2892
  }
2893
+ function recipientPolicyPresenceCapabilityExpiresAt(enrollment, observedAt) {
2894
+ const expiresAt = enrollment.presence_expires_at;
2895
+ const capabilities = enrollment.presence_capabilities;
2896
+ if (typeof expiresAt !== "string" || !capabilities) return null;
2897
+ const expiresAtMs = Date.parse(expiresAt);
2898
+ const observedAtMs = Date.parse(observedAt);
2899
+ if (!Number.isFinite(expiresAtMs) || !Number.isFinite(observedAtMs) || new Date(expiresAtMs).toISOString() !== expiresAt || expiresAtMs <= observedAtMs || capabilities.sync_capability !== "scoped" || !Array.isArray(capabilities.sync_features) || !capabilities.sync_features.every((feature) => typeof feature === "string") || !capabilities.sync_features.includes("reassign_scope")) return null;
2900
+ return expiresAtMs;
2901
+ }
2902
+ function recipientPolicyEnrollmentValueIsValid(value) {
2903
+ return typeof value === "string" && value.length > 0 && value === value.trim();
2904
+ }
2805
2905
  function createRecipientPolicyReconcilerEffects(store, options = {}) {
2806
2906
  const config = options.config ?? readCoordinatorSyncConfig();
2807
2907
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2908
+ const listDevices = options.listDevices ?? coordinatorListDevicesAction;
2909
+ const listReviewedRecipientInviteEvidence = options.listReviewedRecipientInviteEvidence ?? coordinatorListReviewedRecipientInviteEvidenceAction;
2910
+ const reviewedRecipientInviteEvidence = /* @__PURE__ */ new Map();
2911
+ const loadReviewedRecipientInviteEvidence = (groupId) => {
2912
+ let evidence = reviewedRecipientInviteEvidence.get(groupId);
2913
+ if (!evidence) {
2914
+ evidence = listReviewedRecipientInviteEvidence({
2915
+ groupId,
2916
+ remoteUrl: config.syncCoordinatorUrl || null,
2917
+ adminSecret: config.syncCoordinatorAdminSecret || null
2918
+ });
2919
+ reviewedRecipientInviteEvidence.set(groupId, evidence);
2920
+ }
2921
+ return evidence;
2922
+ };
2923
+ const boundaryEnrollments = /* @__PURE__ */ new Map();
2808
2924
  const target = (scopeId) => recipientPolicyCoordinatorBoundary(store, scopeId);
2809
2925
  const coordinatorOptions = (scopeId) => {
2810
2926
  const boundary = target(scopeId);
@@ -2836,7 +2952,7 @@ function createRecipientPolicyReconcilerEffects(store, options = {}) {
2836
2952
  }).catch(() => {
2837
2953
  throw new Error("recipient_policy_snapshot_not_fresh");
2838
2954
  })).map((membership) => {
2839
- if (membership.status !== "active" && membership.status !== "revoked") throw new Error("recipient_policy_snapshot_invalid");
2955
+ if (typeof membership.device_id !== "string" || membership.status !== "active" && membership.status !== "revoked") throw new Error("recipient_policy_snapshot_invalid");
2840
2956
  const status = membership.status;
2841
2957
  return {
2842
2958
  deviceId: membership.device_id,
@@ -2853,10 +2969,56 @@ function createRecipientPolicyReconcilerEffects(store, options = {}) {
2853
2969
  memberships: snapshotMemberships
2854
2970
  };
2855
2971
  },
2856
- probeCapability: (deviceId) => peerSupportsSyncRequirements(store, deviceId, {
2857
- scoped: true,
2858
- reassignScope: true
2859
- }),
2972
+ listBoundaryEnrollments: async ({ scopeId }) => {
2973
+ const targetOptions = coordinatorOptions(scopeId);
2974
+ const observedAt = now();
2975
+ const enrollments = await listDevices({
2976
+ groupId: targetOptions.groupId,
2977
+ includeDisabled: true,
2978
+ remoteUrl: targetOptions.remoteUrl,
2979
+ adminSecret: targetOptions.adminSecret
2980
+ }).catch(() => {
2981
+ throw new Error("recipient_policy_snapshot_not_fresh");
2982
+ });
2983
+ const presenceCapabilityExpiries = new Map(enrollments.map((enrollment) => [enrollment.device_id, recipientPolicyPresenceCapabilityExpiresAt(enrollment, observedAt)]));
2984
+ const mapped = enrollments.map((enrollment) => {
2985
+ if (enrollment.group_id !== targetOptions.groupId || !recipientPolicyEnrollmentValueIsValid(enrollment.device_id) || !recipientPolicyEnrollmentValueIsValid(enrollment.public_key) || !recipientPolicyEnrollmentValueIsValid(enrollment.fingerprint) || fingerprintPublicKey(enrollment.public_key) !== enrollment.fingerprint || enrollment.enabled !== 0 && enrollment.enabled !== 1 || enrollment.identity_id !== null && !recipientPolicyEnrollmentValueIsValid(enrollment.identity_id)) throw new Error("recipient_policy_snapshot_invalid");
2986
+ return {
2987
+ deviceId: enrollment.device_id,
2988
+ identityId: enrollment.identity_id,
2989
+ publicKey: enrollment.public_key,
2990
+ fingerprint: enrollment.fingerprint,
2991
+ enabled: enrollment.enabled === 1
2992
+ };
2993
+ });
2994
+ boundaryEnrollments.set(scopeId, new Map(mapped.flatMap((enrollment) => enrollment.enabled && enrollment.identityId !== null ? [[enrollment.deviceId, {
2995
+ identityId: enrollment.identityId,
2996
+ publicKey: enrollment.publicKey,
2997
+ fingerprint: enrollment.fingerprint,
2998
+ presenceCapabilityExpiresAtMs: presenceCapabilityExpiries.get(enrollment.deviceId) ?? null
2999
+ }]] : [])));
3000
+ return mapped;
3001
+ },
3002
+ probeCapability: async ({ deviceId, scopeId }) => {
3003
+ if (deviceId === store.deviceId) return "supported";
3004
+ const targetOptions = coordinatorOptions(scopeId);
3005
+ const enrollment = boundaryEnrollments.get(scopeId)?.get(deviceId);
3006
+ if (!enrollment) return "undetermined";
3007
+ const peer = store.db.prepare("SELECT pinned_fingerprint, public_key FROM sync_peers WHERE peer_device_id = ?").get(deviceId);
3008
+ if (peer) {
3009
+ const peerPublicKey = String(peer.public_key ?? "").trim();
3010
+ const peerFingerprint = String(peer.pinned_fingerprint ?? "").trim() || (peerPublicKey ? fingerprintPublicKey(peerPublicKey) : "");
3011
+ if ((peerFingerprint || peerPublicKey) && (peerFingerprint !== enrollment.fingerprint || peerPublicKey && peerPublicKey !== enrollment.publicKey)) return "undetermined";
3012
+ }
3013
+ const observed = await peerSupportsSyncRequirements(store, deviceId, {
3014
+ scoped: true,
3015
+ reassignScope: true
3016
+ });
3017
+ if (observed !== "undetermined") return observed;
3018
+ const observedAtMs = Date.parse(now());
3019
+ if (enrollment.presenceCapabilityExpiresAtMs == null || !Number.isFinite(observedAtMs) || enrollment.presenceCapabilityExpiresAtMs <= observedAtMs) return "undetermined";
3020
+ return (await loadReviewedRecipientInviteEvidence(targetOptions.groupId).catch(() => [])).some((invite) => invite.group_id === targetOptions.groupId && invite.bound_device_id === deviceId && invite.recipient_actor_id === enrollment.identityId && invite.bound_public_key === enrollment.publicKey && invite.bound_fingerprint === enrollment.fingerprint) ? "supported" : "undetermined";
3021
+ },
2860
3022
  revoke: async (input) => {
2861
3023
  const targetOptions = coordinatorOptions(input.scopeId);
2862
3024
  await coordinatorRevokeScopeMembershipAction({
@@ -2896,6 +3058,7 @@ function createRecipientPolicyReconcilerEffects(store, options = {}) {
2896
3058
  refresh: async ({ scopeId }) => {
2897
3059
  const boundary = target(scopeId);
2898
3060
  if ((await refreshConfiguredScopeMembershipCache(store.db, config, { keysDir: syncKeysDir() })).groups.find((item) => item.groupId === boundary.groupId)?.status !== "refreshed") throw new Error("recipient_policy_effect_failed");
3061
+ await refreshAuthorizedCoordinatorPeerTrust(store, config);
2899
3062
  }
2900
3063
  };
2901
3064
  }
@@ -3199,7 +3362,7 @@ async function authorizeBootstrapGrantRequest(store, request, body) {
3199
3362
  deviceId
3200
3363
  };
3201
3364
  const config = readCoordinatorSyncConfig();
3202
- if (!config.syncCoordinatorUrl || !config.syncCoordinatorAdminSecret) return {
3365
+ if (!config.syncCoordinatorUrl) return {
3203
3366
  ok: false,
3204
3367
  reason: "bootstrap_grant_coordinator_not_configured",
3205
3368
  deviceId
@@ -3207,11 +3370,31 @@ async function authorizeBootstrapGrantRequest(store, request, body) {
3207
3370
  const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
3208
3371
  let verification;
3209
3372
  try {
3210
- const [status, payload] = await requestJson("GET", `${buildBaseUrl(config.syncCoordinatorUrl)}/v1/admin/bootstrap-grants/${encodeURIComponent(grantId)}`, {
3211
- headers: { "X-Codemem-Coordinator-Admin": config.syncCoordinatorAdminSecret },
3212
- timeoutS: Math.max(1, config.syncCoordinatorTimeoutS)
3213
- });
3214
- if (status !== 200 || !payload) return {
3373
+ let payload = null;
3374
+ if (config.syncCoordinatorAdminSecret) {
3375
+ const [status, adminPayload] = await requestJson("GET", `${buildBaseUrl(config.syncCoordinatorUrl)}/v1/admin/bootstrap-grants/${encodeURIComponent(grantId)}`, {
3376
+ headers: { "X-Codemem-Coordinator-Admin": config.syncCoordinatorAdminSecret },
3377
+ timeoutS: Math.max(1, config.syncCoordinatorTimeoutS)
3378
+ });
3379
+ if (status === 200) payload = adminPayload;
3380
+ } else for (const groupId of config.syncCoordinatorGroups) {
3381
+ const lookupUrl = `${buildBaseUrl(config.syncCoordinatorUrl)}/v1/bootstrap-grants/${encodeURIComponent(grantId)}?group_id=${encodeURIComponent(groupId)}`;
3382
+ const [status, signedPayload] = await requestJson("GET", lookupUrl, {
3383
+ headers: buildAuthHeaders({
3384
+ deviceId: localDeviceId,
3385
+ method: "GET",
3386
+ url: lookupUrl,
3387
+ bodyBytes: Buffer.alloc(0),
3388
+ keysDir: syncKeysDir()
3389
+ }),
3390
+ timeoutS: Math.max(1, config.syncCoordinatorTimeoutS)
3391
+ });
3392
+ if (status === 200) {
3393
+ payload = signedPayload;
3394
+ break;
3395
+ }
3396
+ }
3397
+ if (!payload) return {
3215
3398
  ok: false,
3216
3399
  reason: "bootstrap_grant_lookup_failed",
3217
3400
  deviceId
@@ -3231,6 +3414,11 @@ async function authorizeBootstrapGrantRequest(store, request, body) {
3231
3414
  reason: "bootstrap_grant_invalid_payload",
3232
3415
  deviceId
3233
3416
  };
3417
+ if (config.syncCoordinatorGroups.length > 0 && !config.syncCoordinatorGroups.includes(String(grant.group_id))) return {
3418
+ ok: false,
3419
+ reason: "bootstrap_grant_group_mismatch",
3420
+ deviceId
3421
+ };
3234
3422
  if (grant.revoked_at) return {
3235
3423
  ok: false,
3236
3424
  reason: "bootstrap_grant_revoked",
@@ -3297,6 +3485,11 @@ async function authorizeBootstrapGrantRequest(store, request, body) {
3297
3485
  reason: "bootstrap_grant_invalid_signature",
3298
3486
  deviceId
3299
3487
  };
3488
+ if (!isPeerTrustBindingCompatible(store.db, deviceId, workerPublicKey, workerFingerprint)) return {
3489
+ ok: false,
3490
+ reason: "bootstrap_grant_peer_trust_conflict",
3491
+ deviceId
3492
+ };
3300
3493
  const nonceResult = recordSyncAuthNonce(store, deviceId, nonce, (/* @__PURE__ */ new Date()).toISOString());
3301
3494
  if (nonceResult) return nonceResult;
3302
3495
  updatePeerAddresses(store.db, deviceId, [], {
@@ -4486,6 +4679,23 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4486
4679
  }
4487
4680
  const lastSyncRow = traceSync("lastSyncRow", () => d.select({ last_sync_at: max(schema.syncPeers.last_sync_at) }).from(schema.syncPeers).get());
4488
4681
  const semanticIndex = traceSync("semanticIndex", () => redactSemanticIndexDiagnostics(getSemanticIndexDiagnostics(store.db), showDiag));
4682
+ const enrollmentIssueSummary = traceSync("coordinatorEnrollmentIssues", () => getCoordinatorEnrollmentReconciliationIssueSummary(store.db));
4683
+ const enrollmentIssueStatus = {
4684
+ counts: enrollmentIssueSummary.counts,
4685
+ ...showDiag ? { issues: enrollmentIssueSummary.issues.map((issue) => ({
4686
+ coordinator_id: issue.coordinatorId,
4687
+ group_id: issue.groupId,
4688
+ kind: issue.kind,
4689
+ reference_id: issue.referenceId,
4690
+ code: issue.code,
4691
+ status: issue.status,
4692
+ first_seen_at: issue.firstSeenAt,
4693
+ last_seen_at: issue.lastSeenAt,
4694
+ resolved_at: issue.resolvedAt,
4695
+ occurrence_count: issue.occurrenceCount,
4696
+ updated_at: issue.updatedAt
4697
+ })) } : {}
4698
+ };
4489
4699
  const lastError = daemonState?.last_error;
4490
4700
  const lastErrorAt = daemonState?.last_error_at;
4491
4701
  const lastOkAt = daemonState?.last_ok_at;
@@ -4513,6 +4723,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4513
4723
  last_error_at: retentionState?.last_error_at ?? null
4514
4724
  },
4515
4725
  semantic_index: semanticIndex,
4726
+ coordinator_enrollment_reconciliation_issues: enrollmentIssueStatus,
4516
4727
  peer_count: Number(peerCountRow?.total ?? 0),
4517
4728
  last_sync_at: lastSyncRow?.last_sync_at ?? null,
4518
4729
  daemon_state: daemonStateValue,
@@ -4948,6 +5159,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4948
5159
  inviteKind: kind,
4949
5160
  policyTeamId: kind === "team_member" ? targetId : null,
4950
5161
  targetIdentityId: kind === "add_device" ? targetId : null,
5162
+ inviterDeviceId: kind === "add_device" ? preview.binding.deviceId : null,
4951
5163
  reviewedPreviewDigest,
4952
5164
  reviewedIntent
4953
5165
  }) : await coordinatorCreateAddDeviceInviteAction({
@@ -7123,6 +7335,6 @@ function createSyncApp(opts) {
7123
7335
  return app;
7124
7336
  }
7125
7337
  //#endregion
7126
- export { VERSION, advancePendingProjectShares, advanceProjectShareOperation, closeStore, createApp, createRecipientPolicyReconcilerEffects, createSyncApp, getStore, listRecipientPolicyReconciliationStatus, recipientPolicyCapabilityFromStatus, reconcileRecipientPolicyProjects };
7338
+ export { VERSION, advancePendingProjectShares, advanceProjectShareOperation, closeStore, createApp, createRecipientPolicyReconcilerEffects, createSyncApp, getStore, listRecipientPolicyReconciliationStatus, recipientPolicyCapabilityFromStatus, reconcileConfiguredCoordinatorEnrollment, reconcileRecipientPolicyProjects };
7127
7339
 
7128
7340
  //# sourceMappingURL=index.js.map