@codemem/server 0.46.0-beta.1 → 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/application/coordinator-maintenance.d.ts.map +1 -1
- package/dist/index.js +259 -119
- package/dist/index.js.map +1 -1
- package/dist/routes/sync.d.ts +14 -0
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +37 -48
- package/static/app.js.br +0 -0
- package/static/app.js.gz +0 -0
- package/static/index.html +152 -63
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordinator-maintenance.d.ts","sourceRoot":"","sources":["../../src/application/coordinator-maintenance.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"coordinator-maintenance.d.ts","sourceRoot":"","sources":["../../src/application/coordinator-maintenance.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,WAAW,EAChB,KAAK,8BAA8B,EACnC,KAAK,gCAAgC,EACrC,+BAA+B,EAE/B,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,kCAAkC;IAClD,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,QAAQ,GAAG,wBAAwB,GAAG,WAAW,CAAC;CACzD;AAED,MAAM,WAAW,iCAAiC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EACJ,UAAU,GACV,wBAAwB,GACxB,oBAAoB,GACpB,iBAAiB,GACjB,iBAAiB,GACjB,YAAY,GACZ,QAAQ,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACH;AAED,MAAM,MAAM,4BAA4B,GAAG,CAC1C,KAAK,EAAE,WAAW,EAClB,WAAW,EAAE,MAAM,KACf,OAAO,CAAC,kCAAkC,CAAC,CAAC;AA6BjD,eAAO,MAAM,oCAAoC,aAc/C,CAAC;AAwLH,wBAAsB,oCAAoC,CACzD,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,gBAAgB,EAAE,4BAA4B,CAAA;CAAE,GACrF,OAAO,CAAC,iCAAiC,CAAC,CAuB5C;AAED,MAAM,WAAW,sCAAsC;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;QACZ,wBAAwB,EAAE,MAAM,CAAC;QACjC,MAAM,EAAE,8BAA8B,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;QAC5D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,CAAC,CAAC;CACH;AAkHD,wBAAsB,yCAAyC,CAC9D,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE;IACR,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gCAAgC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,OAAO,+BAA+B,CAAC;CAC1D,GACC,OAAO,CAAC,sCAAsC,CAAC,CAwDjD"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
3
|
-
import { ACCESS_CLEANUP_OP_TYPE, CodememConfigMutationError, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MEMORY_FILTER_FIELD_TYPES, MEMORY_FILTER_NAMES, MEMORY_KIND_DESCRIPTIONS, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REMEMBER_MEMORY_KINDS, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, 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, 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, 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,
|
|
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";
|
|
@@ -3847,6 +3847,8 @@ var AUTOMATIC_WAITING_ACCEPTANCE_RETRY_COOLDOWN_MS = 3e4;
|
|
|
3847
3847
|
var AUTOMATIC_WAITING_DEVICE_RETRY_COOLDOWN_MS = 3e5;
|
|
3848
3848
|
var TERMINAL_SHARE_MAINTENANCE_ERRORS = /* @__PURE__ */ new Set([
|
|
3849
3849
|
"coordinator_not_configured",
|
|
3850
|
+
"conflicting_repository_mappings",
|
|
3851
|
+
"project_mapping_conflict",
|
|
3850
3852
|
"team_sharing_not_configured",
|
|
3851
3853
|
"team_selection_ambiguous",
|
|
3852
3854
|
"initiating_device_not_reviewed",
|
|
@@ -4048,25 +4050,69 @@ async function advancePendingProjectSharesOperation(store, options) {
|
|
|
4048
4050
|
var RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT = 10;
|
|
4049
4051
|
var RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT = 3;
|
|
4050
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
|
+
}
|
|
4051
4109
|
async function reconcileRecipientPolicyProjectsOperation(store, options) {
|
|
4052
4110
|
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? RECIPIENT_POLICY_MAINTENANCE_DEFAULT_LIMIT), RECIPIENT_POLICY_MAINTENANCE_MAX_LIMIT));
|
|
4053
4111
|
const maintenanceNow = options.now ?? /* @__PURE__ */ new Date();
|
|
4054
4112
|
const backoffMs = Math.max(0, Math.trunc(options.backoffMs ?? RECIPIENT_POLICY_MAINTENANCE_BACKOFF_MS));
|
|
4055
4113
|
const retryBefore = new Date(maintenanceNow.getTime() - backoffMs).toISOString();
|
|
4056
|
-
const
|
|
4057
|
-
|
|
4058
|
-
UNION
|
|
4059
|
-
SELECT canonical_project_identity FROM recipient_policy_authority_states
|
|
4060
|
-
)
|
|
4061
|
-
SELECT projects.canonical_project_identity
|
|
4062
|
-
FROM projects
|
|
4063
|
-
LEFT JOIN recipient_policy_authority_states authority
|
|
4064
|
-
ON authority.canonical_project_identity = projects.canonical_project_identity
|
|
4065
|
-
WHERE authority.safe_error_code IS NULL OR authority.last_attempt_at IS NULL
|
|
4066
|
-
OR authority.last_attempt_at <= ?
|
|
4067
|
-
ORDER BY CASE WHEN authority.last_attempt_at IS NULL THEN 0 ELSE 1 END,
|
|
4068
|
-
authority.last_attempt_at, projects.canonical_project_identity
|
|
4069
|
-
LIMIT ?`).all(retryBefore, limit);
|
|
4114
|
+
const attemptedAt = maintenanceNow.toISOString();
|
|
4115
|
+
const projectIds = recipientPolicyMaintenanceCandidates(store, retryBefore, limit);
|
|
4070
4116
|
const reconcileProject = options.reconcileProject ?? reconcileRecipientPolicyProject;
|
|
4071
4117
|
const result = {
|
|
4072
4118
|
processed: 0,
|
|
@@ -4076,25 +4122,26 @@ async function reconcileRecipientPolicyProjectsOperation(store, options) {
|
|
|
4076
4122
|
failed: 0,
|
|
4077
4123
|
items: []
|
|
4078
4124
|
};
|
|
4079
|
-
for (const
|
|
4125
|
+
for (const projectId of projectIds) {
|
|
4080
4126
|
result.processed += 1;
|
|
4081
4127
|
try {
|
|
4082
4128
|
const outcome = await reconcileProject(store.db, {
|
|
4083
|
-
canonicalProjectIdentity:
|
|
4129
|
+
canonicalProjectIdentity: projectId,
|
|
4084
4130
|
leaseOwner: options.leaseOwner ?? `recipient-policy-maintenance:${store.deviceId || process.pid}`
|
|
4085
4131
|
}, options.effects);
|
|
4086
4132
|
if (outcome.status === "active") result.active += 1;
|
|
4087
4133
|
else if (outcome.status === "needs_attention") result.attention += 1;
|
|
4088
4134
|
else result.waiting += 1;
|
|
4089
4135
|
result.items.push({
|
|
4090
|
-
canonicalProjectIdentity:
|
|
4136
|
+
canonicalProjectIdentity: projectId,
|
|
4091
4137
|
status: outcome.status,
|
|
4092
4138
|
safeErrorCode: outcome.safeErrorCode
|
|
4093
4139
|
});
|
|
4094
|
-
} catch {
|
|
4140
|
+
} catch (error) {
|
|
4141
|
+
recordMaintenanceFailureIfAvailable(store, projectId, attemptedAt, error);
|
|
4095
4142
|
result.failed += 1;
|
|
4096
4143
|
result.items.push({
|
|
4097
|
-
canonicalProjectIdentity:
|
|
4144
|
+
canonicalProjectIdentity: projectId,
|
|
4098
4145
|
status: "failed",
|
|
4099
4146
|
safeErrorCode: "recipient_policy_reconciliation_failed"
|
|
4100
4147
|
});
|
|
@@ -6594,6 +6641,14 @@ function missingGuardrailConfirmations(warnings, confirmedTokens) {
|
|
|
6594
6641
|
const confirmed = new Set(confirmedTokens);
|
|
6595
6642
|
return warnings.filter((warning) => warning.requires_confirmation && (!warning.confirmation_token || !confirmed.has(warning.confirmation_token)));
|
|
6596
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
|
+
}
|
|
6597
6652
|
function parseViewerProjectMappingInput(body) {
|
|
6598
6653
|
const id = optionalViewerInteger(body, "id");
|
|
6599
6654
|
const priority = optionalViewerInteger(body, "priority");
|
|
@@ -6608,6 +6663,14 @@ function parseViewerProjectMappingInput(body) {
|
|
|
6608
6663
|
source: optionalViewerStrictString(body, "source") ?? "user"
|
|
6609
6664
|
};
|
|
6610
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
|
+
}
|
|
6611
6674
|
function coordinatorAdminMutationStatus(message) {
|
|
6612
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;
|
|
6613
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;
|
|
@@ -7565,13 +7628,25 @@ function recipientPolicyReadCopy(state) {
|
|
|
7565
7628
|
explanation: "Legacy scope enforcement remains in control while reconciliation is pending."
|
|
7566
7629
|
};
|
|
7567
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
|
+
}
|
|
7568
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
|
+
}
|
|
7569
7646
|
return {
|
|
7570
7647
|
version: 1,
|
|
7571
|
-
items:
|
|
7572
|
-
|
|
7573
|
-
ORDER BY canonical_project_identity`).all().map((row) => row.canonical_project_identity).map((canonicalProjectIdentity) => {
|
|
7574
|
-
const state = recipientPolicyReadState(getRecipientPolicyAuthorityState(store.db, canonicalProjectIdentity));
|
|
7648
|
+
items: [...aliasesByProject.keys()].toSorted().map((canonicalProjectIdentity) => {
|
|
7649
|
+
const state = recipientPolicyReadState(recipientPolicyAuthorityForAliases(store, canonicalProjectIdentity, aliasesByProject.get(canonicalProjectIdentity) ?? []));
|
|
7575
7650
|
return {
|
|
7576
7651
|
canonicalProjectIdentity,
|
|
7577
7652
|
state,
|
|
@@ -8565,6 +8640,7 @@ function addLegacySharedReviewRow(groups, row, identity, candidate, isOwnedBySel
|
|
|
8565
8640
|
}
|
|
8566
8641
|
function collectLegacySharedReviewGroups(store) {
|
|
8567
8642
|
const ownedBySelf = store.buildOwnershipPredicate();
|
|
8643
|
+
const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
|
|
8568
8644
|
const candidatesByIdentity = new Map(listProjectScopeCandidates(store.db, { limit: null }).map((candidate) => [candidate.workspace_identity, candidate]));
|
|
8569
8645
|
const groups = /* @__PURE__ */ new Map();
|
|
8570
8646
|
let memoryCount = 0;
|
|
@@ -8576,6 +8652,11 @@ function collectLegacySharedReviewGroups(store) {
|
|
|
8576
8652
|
cwd: row.cwd,
|
|
8577
8653
|
gitBranch: row.git_branch,
|
|
8578
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
|
+
}),
|
|
8579
8660
|
project: row.project,
|
|
8580
8661
|
workspaceId: row.workspace_id
|
|
8581
8662
|
});
|
|
@@ -8650,7 +8731,8 @@ function legacySharedReviewSummaryRows(store) {
|
|
|
8650
8731
|
m.workspace_id,
|
|
8651
8732
|
m.actor_id,
|
|
8652
8733
|
m.origin_device_id,
|
|
8653
|
-
m.metadata_json
|
|
8734
|
+
m.metadata_json,
|
|
8735
|
+
s.metadata_json AS session_metadata_json
|
|
8654
8736
|
FROM memory_items m
|
|
8655
8737
|
LEFT JOIN sessions s ON s.id = m.session_id
|
|
8656
8738
|
WHERE m.scope_id = ?
|
|
@@ -8690,7 +8772,8 @@ function legacySharedReviewRows(store) {
|
|
|
8690
8772
|
m.workspace_id,
|
|
8691
8773
|
m.actor_id,
|
|
8692
8774
|
m.origin_device_id,
|
|
8693
|
-
m.metadata_json
|
|
8775
|
+
m.metadata_json,
|
|
8776
|
+
s.metadata_json AS session_metadata_json
|
|
8694
8777
|
FROM memory_items m
|
|
8695
8778
|
LEFT JOIN sessions s ON s.id = m.session_id
|
|
8696
8779
|
WHERE m.scope_id = ?
|
|
@@ -8707,7 +8790,7 @@ function legacySharedReviewConfirmationToken(input) {
|
|
|
8707
8790
|
return Buffer.from(`${input.workspaceIdentity}|${input.scopeId}|${payload}`, "utf8").toString("base64url");
|
|
8708
8791
|
}
|
|
8709
8792
|
function projectInventoryRowsForWorkspace(store, workspaceIdentity) {
|
|
8710
|
-
|
|
8793
|
+
const rows = store.db.prepare(`SELECT m.id,
|
|
8711
8794
|
m.rev,
|
|
8712
8795
|
s.project,
|
|
8713
8796
|
s.cwd,
|
|
@@ -8716,15 +8799,23 @@ function projectInventoryRowsForWorkspace(store, workspaceIdentity) {
|
|
|
8716
8799
|
m.workspace_id,
|
|
8717
8800
|
m.actor_id,
|
|
8718
8801
|
m.origin_device_id,
|
|
8719
|
-
m.metadata_json
|
|
8802
|
+
m.metadata_json,
|
|
8803
|
+
s.metadata_json AS session_metadata_json
|
|
8720
8804
|
FROM memory_items m
|
|
8721
8805
|
LEFT JOIN sessions s ON s.id = m.session_id
|
|
8722
8806
|
WHERE m.active = 1
|
|
8723
|
-
AND m.deleted_at IS NULL`).all()
|
|
8807
|
+
AND m.deleted_at IS NULL`).all();
|
|
8808
|
+
const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
|
|
8809
|
+
return rows.filter((row) => {
|
|
8724
8810
|
return canonicalWorkspaceIdentity({
|
|
8725
8811
|
cwd: row.cwd,
|
|
8726
8812
|
gitBranch: row.git_branch,
|
|
8727
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
|
+
}),
|
|
8728
8819
|
project: row.project,
|
|
8729
8820
|
workspaceId: row.workspace_id
|
|
8730
8821
|
}).value === workspaceIdentity;
|
|
@@ -8778,11 +8869,17 @@ function assertLocalDeviceScopeMembership(store, scopeId) {
|
|
|
8778
8869
|
LIMIT 1`).get(scopeId, store.deviceId)) throw new Error(`local device is not a member of Sharing domain ${scopeId}`);
|
|
8779
8870
|
}
|
|
8780
8871
|
function legacySharedReviewRowsForWorkspace(store, workspaceIdentity) {
|
|
8872
|
+
const repositoryIdentities = repositoryIdentitiesByWorkspace(store.db);
|
|
8781
8873
|
return legacySharedReviewRows(store).filter((row) => {
|
|
8782
8874
|
return canonicalWorkspaceIdentity({
|
|
8783
8875
|
cwd: row.cwd,
|
|
8784
8876
|
gitBranch: row.git_branch,
|
|
8785
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
|
+
}),
|
|
8786
8883
|
project: row.project,
|
|
8787
8884
|
workspaceId: row.workspace_id
|
|
8788
8885
|
}).value === workspaceIdentity;
|
|
@@ -9329,6 +9426,119 @@ function serializeProjectScopeInventory(store, inventory, sharingByProject) {
|
|
|
9329
9426
|
}))
|
|
9330
9427
|
};
|
|
9331
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
|
+
}
|
|
9332
9542
|
/**
|
|
9333
9543
|
* Viewer-facing sync management routes (/api/sync/*).
|
|
9334
9544
|
*
|
|
@@ -9961,27 +10171,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
9961
10171
|
scopeId: c.req.query("scope_id"),
|
|
9962
10172
|
status: c.req.query("status")
|
|
9963
10173
|
});
|
|
9964
|
-
const
|
|
9965
|
-
const operationById = new Map(operations.map((operation) => [operation.operation_id, operation]));
|
|
9966
|
-
const sharingByProject = /* @__PURE__ */ new Map();
|
|
9967
|
-
const reviewed = store.db.prepare(`SELECT p.operation_id, p.canonical_project_identity
|
|
9968
|
-
FROM share_operation_projects p
|
|
9969
|
-
JOIN share_operations o ON o.operation_id = p.operation_id
|
|
9970
|
-
WHERE o.inviter_actor_id = ? ORDER BY o.created_at, p.ordinal`).all(store.actorId);
|
|
9971
|
-
for (const item of reviewed) {
|
|
9972
|
-
const operation = operationById.get(item.operation_id);
|
|
9973
|
-
if (!operation) continue;
|
|
9974
|
-
const current = sharingByProject.get(item.canonical_project_identity) ?? [];
|
|
9975
|
-
current.push({
|
|
9976
|
-
person: operation.person,
|
|
9977
|
-
lifecycle: {
|
|
9978
|
-
state: operation.lifecycle.state,
|
|
9979
|
-
label: operation.lifecycle.label,
|
|
9980
|
-
explanation: operation.lifecycle.explanation
|
|
9981
|
-
}
|
|
9982
|
-
});
|
|
9983
|
-
sharingByProject.set(item.canonical_project_identity, current);
|
|
9984
|
-
}
|
|
10174
|
+
const sharingByProject = projectSharingByRepository(store, await shareOperationReadModels(store, void 0, false));
|
|
9985
10175
|
return c.json(serializeProjectScopeInventory(store, inventory, sharingByProject));
|
|
9986
10176
|
});
|
|
9987
10177
|
app.post("/api/sync/projects/reassign-project", async (c) => {
|
|
@@ -10238,28 +10428,19 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
10238
10428
|
const confirmedGuardrailTokens = optionalViewerStringList(body, "confirmed_guardrail_tokens");
|
|
10239
10429
|
const mappingInput = parseViewerProjectMappingInput(body);
|
|
10240
10430
|
releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
|
|
10241
|
-
const
|
|
10431
|
+
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
10432
|
+
const analysis = analyzeProjectScopeMappingChangeGuardrails(store.db, {
|
|
10433
|
+
...mappingInput,
|
|
10434
|
+
deviceId
|
|
10435
|
+
});
|
|
10242
10436
|
if (analysis.requested_workspace_identity?.startsWith("unmapped:")) return c.json({
|
|
10243
10437
|
error: "unmapped_project_local_only",
|
|
10244
10438
|
message: "Unmapped projects need a stable path, git remote, or workspace id before assigning a Sharing domain.",
|
|
10245
10439
|
guardrail_warnings: analysis.warnings
|
|
10246
10440
|
}, 400);
|
|
10247
10441
|
const missingConfirmations = missingGuardrailConfirmations(analysis.warnings, confirmedGuardrailTokens);
|
|
10248
|
-
if (missingConfirmations.length > 0)
|
|
10249
|
-
|
|
10250
|
-
const missingTokens = [...new Set(missingConfirmations.map((warning) => warning.confirmation_token))].filter((token) => typeof token === "string" && token.length > 0);
|
|
10251
|
-
return c.json({
|
|
10252
|
-
error: "guardrail_confirmation_required",
|
|
10253
|
-
required_guardrails: missingCodes,
|
|
10254
|
-
required_guardrail_tokens: missingTokens,
|
|
10255
|
-
guardrail_warnings: analysis.warnings
|
|
10256
|
-
}, 409);
|
|
10257
|
-
}
|
|
10258
|
-
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
10259
|
-
const mapping = upsertProjectScopeSettingsMapping(store.db, {
|
|
10260
|
-
deviceId,
|
|
10261
|
-
...mappingInput
|
|
10262
|
-
});
|
|
10442
|
+
if (missingConfirmations.length > 0) return c.json(mappingGuardrailChallenge(analysis.warnings, missingConfirmations), 409);
|
|
10443
|
+
const [mapping] = saveProjectMappingsAndWakePolicies(store, deviceId, [mappingInput]);
|
|
10263
10444
|
return c.json({
|
|
10264
10445
|
ok: true,
|
|
10265
10446
|
mapping,
|
|
@@ -10277,14 +10458,13 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
10277
10458
|
if (!body) return c.json({ error: "invalid json" }, 400);
|
|
10278
10459
|
let releasePublicationMutation;
|
|
10279
10460
|
try {
|
|
10280
|
-
const
|
|
10281
|
-
if (!Array.isArray(rawMappings) || rawMappings.length === 0) return c.json({ error: "mappings must be a non-empty array" }, 400);
|
|
10282
|
-
const mappingInputs = rawMappings.map((raw) => {
|
|
10283
|
-
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("each mapping must be an object");
|
|
10284
|
-
return parseViewerProjectMappingInput(raw);
|
|
10285
|
-
});
|
|
10461
|
+
const mappingInputs = parseViewerProjectMappingInputs(body);
|
|
10286
10462
|
releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
|
|
10287
|
-
const
|
|
10463
|
+
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
10464
|
+
const analyses = analyzeProjectScopeMappingChangesGuardrails(store.db, mappingInputs.map((input) => ({
|
|
10465
|
+
...input,
|
|
10466
|
+
deviceId
|
|
10467
|
+
})));
|
|
10288
10468
|
const unmapped = analyses.find((analysis) => analysis.requested_workspace_identity?.startsWith("unmapped:"));
|
|
10289
10469
|
if (unmapped) return c.json({
|
|
10290
10470
|
error: "unmapped_project_local_only",
|
|
@@ -10292,24 +10472,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
10292
10472
|
guardrail_warnings: unmapped.warnings
|
|
10293
10473
|
}, 400);
|
|
10294
10474
|
const missingConfirmations = analyses.flatMap((analysis) => missingGuardrailConfirmations(analysis.warnings, []));
|
|
10295
|
-
if (missingConfirmations.length > 0)
|
|
10296
|
-
const missingCodes = [...new Set(missingConfirmations.map((warning) => warning.code))];
|
|
10297
|
-
const missingTokens = [...new Set(missingConfirmations.map((warning) => warning.confirmation_token))].filter((token) => typeof token === "string" && token.length > 0);
|
|
10298
|
-
return c.json({
|
|
10299
|
-
error: "guardrail_confirmation_required",
|
|
10300
|
-
required_guardrails: missingCodes,
|
|
10301
|
-
required_guardrail_tokens: missingTokens,
|
|
10302
|
-
guardrail_warnings: analyses.flatMap((analysis) => analysis.warnings)
|
|
10303
|
-
}, 409);
|
|
10304
|
-
}
|
|
10305
|
-
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
10306
|
-
const saveMappings = store.db.transaction(() => mappingInputs.map((mappingInput) => upsertProjectScopeSettingsMapping(store.db, {
|
|
10307
|
-
deviceId,
|
|
10308
|
-
...mappingInput
|
|
10309
|
-
})));
|
|
10475
|
+
if (missingConfirmations.length > 0) return c.json(mappingGuardrailChallenge(analyses.flatMap((analysis) => analysis.warnings), missingConfirmations), 409);
|
|
10310
10476
|
return c.json({
|
|
10311
10477
|
ok: true,
|
|
10312
|
-
mappings:
|
|
10478
|
+
mappings: saveProjectMappingsAndWakePolicies(store, deviceId, mappingInputs),
|
|
10313
10479
|
guardrail_warnings: analyses.flatMap((analysis) => analysis.warnings)
|
|
10314
10480
|
});
|
|
10315
10481
|
} catch (error) {
|
|
@@ -10318,23 +10484,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
10318
10484
|
releasePublicationMutation?.();
|
|
10319
10485
|
}
|
|
10320
10486
|
});
|
|
10321
|
-
app.delete("/api/sync/sharing-domains/project-mappings/:id",
|
|
10322
|
-
const store = getStore();
|
|
10323
|
-
const id = Number(c.req.param("id"));
|
|
10324
|
-
let releasePublicationMutation;
|
|
10325
|
-
try {
|
|
10326
|
-
releasePublicationMutation = await claimRecipientPolicyPublicationMutation(store.db);
|
|
10327
|
-
const deleted = deleteProjectScopeSettingsMapping(store.db, id);
|
|
10328
|
-
return c.json({
|
|
10329
|
-
ok: true,
|
|
10330
|
-
deleted
|
|
10331
|
-
});
|
|
10332
|
-
} catch (error) {
|
|
10333
|
-
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
10334
|
-
} finally {
|
|
10335
|
-
releasePublicationMutation?.();
|
|
10336
|
-
}
|
|
10337
|
-
});
|
|
10487
|
+
app.delete("/api/sync/sharing-domains/project-mappings/:id", (c) => deleteProjectScopeMappingRoute(c, getStore()));
|
|
10338
10488
|
app.post("/api/sync/peers/identity", async (c) => {
|
|
10339
10489
|
const store = getStore();
|
|
10340
10490
|
ensureLocalActorRecord(store);
|
|
@@ -10592,11 +10742,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
10592
10742
|
WHERE inviter_actor_id = ? OR recipient_actor_id = ? OR person_id = ?`).run(secondaryActorId, primaryActorId, secondaryActorId, primaryActorId, secondaryActorId, primaryActorId, now, secondaryActorId, secondaryActorId, secondaryActorId);
|
|
10593
10743
|
const peerUpdate = d.update(schema.syncPeers).set({ actor_id: primaryActorId }).where(eq(schema.syncPeers.actor_id, secondaryActorId)).run();
|
|
10594
10744
|
if (primary.is_local) d.update(schema.syncPeers).set({ claimed_local_actor: 1 }).where(eq(schema.syncPeers.actor_id, primaryActorId)).run();
|
|
10595
|
-
|
|
10596
|
-
status: "merged",
|
|
10597
|
-
merged_into_actor_id: primaryActorId,
|
|
10598
|
-
updated_at: now
|
|
10599
|
-
}).where(eq(schema.actors.actor_id, secondaryActorId)).run();
|
|
10745
|
+
finishActorMerge(store, primaryActorId, secondaryActorId, now);
|
|
10600
10746
|
return { mergedCount: Number(peerUpdate.changes ?? 0) };
|
|
10601
10747
|
}).immediate();
|
|
10602
10748
|
if ("error" in result) return c.json({ error: result.error }, result.status);
|
|
@@ -10615,16 +10761,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
|
|
|
10615
10761
|
if (actorId === store.actorId) return c.json({ error: "cannot deactivate this device's own local actor" }, 409);
|
|
10616
10762
|
const releaseActorMutations = await claimRecipientPolicyActorMutations(store.db, [actorId]);
|
|
10617
10763
|
queueMicrotask(releaseActorMutations);
|
|
10618
|
-
const
|
|
10619
|
-
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();
|
|
10620
10765
|
if (!actor) return c.json({ error: "actor not found" }, 404);
|
|
10621
10766
|
if (actor.status !== "active") return c.json({ error: "actor not active" }, 409);
|
|
10622
|
-
|
|
10623
|
-
d.update(schema.actors).set({
|
|
10624
|
-
status: "deactivated",
|
|
10625
|
-
updated_at: now
|
|
10626
|
-
}).where(eq(schema.actors.actor_id, actorId)).run();
|
|
10627
|
-
d.update(schema.syncPeers).set({ actor_id: null }).where(eq(schema.syncPeers.actor_id, actorId)).run();
|
|
10767
|
+
deactivateActorAndWakePolicies(store, actorId, (/* @__PURE__ */ new Date()).toISOString());
|
|
10628
10768
|
return c.json({ ok: true });
|
|
10629
10769
|
});
|
|
10630
10770
|
app.post("/api/sync/legacy-devices/claim", async (c) => {
|