@codemem/server 0.40.0-alpha.1 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +236 -24
- package/dist/index.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/sync.d.ts +28 -2
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +35 -35
- package/static/app.js.br +0 -0
- package/static/app.js.gz +0 -0
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;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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 (
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
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
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
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
|
|
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
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
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
|