@narumitw/pi-sync 0.53.0 → 0.53.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.
Files changed (38) hide show
  1. package/dist/chunks/{chunk-NGIJQSRE.ts → chunk-FJGNDYGC.ts} +2 -2
  2. package/dist/chunks/{chunk-QTCGWQP3.ts → chunk-QDPED3XJ.ts} +67 -8
  3. package/dist/chunks/{chunk-QTCGWQP3.ts.map → chunk-QDPED3XJ.ts.map} +3 -3
  4. package/dist/chunks/{chunk-FHNISD7A.ts → chunk-SDZCTCYQ.ts} +4 -4
  5. package/dist/chunks/{chunk-I5IG4WV4.ts → chunk-TRCSEIAQ.ts} +2 -2
  6. package/dist/chunks/{chunk-BX3K7YW7.ts → chunk-WFKUKVCP.ts} +2 -2
  7. package/dist/chunks/{chunk-UETBKTDI.ts → chunk-YDVQYUHE.ts} +1 -1
  8. package/dist/chunks/{chunk-UETBKTDI.ts.map → chunk-YDVQYUHE.ts.map} +2 -2
  9. package/dist/chunks/{conflict-review-NK5CM6UA.ts → conflict-review-JANNPKKS.ts} +4 -4
  10. package/dist/chunks/{git-backend-HLGKUQJT.ts → git-backend-FD7AVSK6.ts} +2 -2
  11. package/dist/chunks/{manager-result-dispatcher-35TM3ZR3.ts → manager-result-dispatcher-AAEAYX57.ts} +4 -4
  12. package/dist/chunks/{manager-ui-6AYMNXUP.ts → manager-ui-MHAHAKG3.ts} +7 -7
  13. package/dist/chunks/{remote-selection-ui-BB5SX6P5.ts → remote-selection-ui-Q2HUIBTL.ts} +3 -3
  14. package/dist/chunks/{s3-backend-O2AS3TH6.ts → s3-backend-AQGV6LFT.ts} +18 -4
  15. package/dist/chunks/s3-backend-AQGV6LFT.ts.map +7 -0
  16. package/dist/chunks/{sync-inspection-RLIHUYDV.ts → sync-inspection-K45QVLHB.ts} +3 -3
  17. package/dist/chunks/{sync-operations-RBUOUJWL.ts → sync-operations-Y27F5T7W.ts} +5 -5
  18. package/dist/chunks/{webdav-backend-ETQGSI6I.ts → webdav-backend-TBILVEQK.ts} +2 -2
  19. package/dist/index.ts +5 -5
  20. package/package.json +1 -1
  21. package/src/backends/s3/s3-backend.ts +22 -1
  22. package/src/backends/s3/s3-client.ts +3 -1
  23. package/src/backends/sync-backend.ts +2 -0
  24. package/src/sync/merge-transfer-error.ts +72 -0
  25. package/src/sync/merged-sync.ts +8 -5
  26. package/dist/chunks/s3-backend-O2AS3TH6.ts.map +0 -7
  27. /package/dist/chunks/{chunk-NGIJQSRE.ts.map → chunk-FJGNDYGC.ts.map} +0 -0
  28. /package/dist/chunks/{chunk-FHNISD7A.ts.map → chunk-SDZCTCYQ.ts.map} +0 -0
  29. /package/dist/chunks/{chunk-I5IG4WV4.ts.map → chunk-TRCSEIAQ.ts.map} +0 -0
  30. /package/dist/chunks/{chunk-BX3K7YW7.ts.map → chunk-WFKUKVCP.ts.map} +0 -0
  31. /package/dist/chunks/{conflict-review-NK5CM6UA.ts.map → conflict-review-JANNPKKS.ts.map} +0 -0
  32. /package/dist/chunks/{git-backend-HLGKUQJT.ts.map → git-backend-FD7AVSK6.ts.map} +0 -0
  33. /package/dist/chunks/{manager-result-dispatcher-35TM3ZR3.ts.map → manager-result-dispatcher-AAEAYX57.ts.map} +0 -0
  34. /package/dist/chunks/{manager-ui-6AYMNXUP.ts.map → manager-ui-MHAHAKG3.ts.map} +0 -0
  35. /package/dist/chunks/{remote-selection-ui-BB5SX6P5.ts.map → remote-selection-ui-Q2HUIBTL.ts.map} +0 -0
  36. /package/dist/chunks/{sync-inspection-RLIHUYDV.ts.map → sync-inspection-K45QVLHB.ts.map} +0 -0
  37. /package/dist/chunks/{sync-operations-RBUOUJWL.ts.map → sync-operations-Y27F5T7W.ts.map} +0 -0
  38. /package/dist/chunks/{webdav-backend-ETQGSI6I.ts.map → webdav-backend-TBILVEQK.ts.map} +0 -0
@@ -5,15 +5,15 @@
5
5
  var createSyncBackend = async (config) => {
6
6
  switch (config.backend.type) {
7
7
  case "s3": {
8
- const { S3SyncBackend } = await import("./s3-backend-O2AS3TH6.ts");
8
+ const { S3SyncBackend } = await import("./s3-backend-AQGV6LFT.ts");
9
9
  return new S3SyncBackend(config.backend);
10
10
  }
11
11
  case "webdav": {
12
- const { WebDavSyncBackend } = await import("./webdav-backend-ETQGSI6I.ts");
12
+ const { WebDavSyncBackend } = await import("./webdav-backend-TBILVEQK.ts");
13
13
  return new WebDavSyncBackend(config.backend);
14
14
  }
15
15
  case "git": {
16
- const { GitSyncBackend } = await import("./git-backend-HLGKUQJT.ts");
16
+ const { GitSyncBackend } = await import("./git-backend-FD7AVSK6.ts");
17
17
  return new GitSyncBackend(config.backend);
18
18
  }
19
19
  }
@@ -22,4 +22,4 @@ var createSyncBackend = async (config) => {
22
22
  export {
23
23
  createSyncBackend
24
24
  };
25
- //# sourceMappingURL=chunk-FHNISD7A.ts.map
25
+ //# sourceMappingURL=chunk-SDZCTCYQ.ts.map
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-V6VZ5TLG.ts";
6
6
  import {
7
7
  createSyncBackend
8
- } from "./chunk-FHNISD7A.ts";
8
+ } from "./chunk-SDZCTCYQ.ts";
9
9
  import {
10
10
  setSyncStatus
11
11
  } from "./chunk-K3RIEL6J.ts";
@@ -603,4 +603,4 @@ export {
603
603
  runCancellableOperation,
604
604
  showRemoteSelectionReview
605
605
  };
606
- //# sourceMappingURL=chunk-I5IG4WV4.ts.map
606
+ //# sourceMappingURL=chunk-TRCSEIAQ.ts.map
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-BDB4IDM3.ts";
7
7
  import {
8
8
  createSyncBackend
9
- } from "./chunk-FHNISD7A.ts";
9
+ } from "./chunk-SDZCTCYQ.ts";
10
10
  import {
11
11
  throwIfAborted
12
12
  } from "./chunk-PWMWOJP4.ts";
@@ -47,4 +47,4 @@ async function inspectSync(config, options, signal, factory = createSyncBackend)
47
47
  export {
48
48
  inspectSync
49
49
  };
50
- //# sourceMappingURL=chunk-BX3K7YW7.ts.map
50
+ //# sourceMappingURL=chunk-WFKUKVCP.ts.map
@@ -31,4 +31,4 @@ export {
31
31
  SyncBackendPublicationOutcomeUnknownError,
32
32
  expectedRemoteHead
33
33
  };
34
- //# sourceMappingURL=chunk-UETBKTDI.ts.map
34
+ //# sourceMappingURL=chunk-YDVQYUHE.ts.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/backends/sync-backend.ts"],
4
- "sourcesContent": ["import type { Snapshot, SnapshotSelection } from \"../snapshot/snapshot-types.js\";\n\nexport type PublicationCapability =\n | \"read-check-write-verify\"\n | \"conditional-required\"\n | \"atomic-conditional\"\n | \"lease-protected\";\n\nexport interface RemoteHead {\n /** Backend-owned reference used only to retrieve this immutable snapshot. */\n snapshotRef: string;\n /** Backend-neutral snapshot content identity retained in local sync state. */\n snapshotId: string;\n revision: string;\n createdAt: string;\n machine: string;\n syncSessions: boolean;\n /** Validated lightweight projection; revalidate it against the immutable snapshot before adoption. */\n selection?: SnapshotSelection;\n}\n\nexport interface RemoteHistoryEntry {\n snapshotRef: string;\n snapshotId: string;\n createdAt: string;\n machine: string;\n syncSessions: boolean;\n}\n\nexport type ExpectedRemoteHead = { kind: \"missing\" } | { kind: \"revision\"; revision: string };\n\nexport interface PublishSnapshotOptions {\n signal?: AbortSignal;\n /** Called at the backend's active-head commit boundary. */\n onCommit?: () => void;\n}\n\nexport interface PublishSnapshotResult {\n head: RemoteHead;\n warnings: string[];\n}\n\nexport interface BackendDiagnostic {\n key: string;\n level: \"info\" | \"warning\" | \"error\";\n message: string;\n}\n\nexport interface SyncBackend {\n readonly identity: string;\n readonly destination: string;\n readonly capability: PublicationCapability;\n /** Compare opaque revisions produced by this backend identity. */\n sameRevision(left: string, right: string): boolean;\n readHead(signal?: AbortSignal): Promise<RemoteHead | undefined>;\n readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot>;\n publishSnapshot(\n snapshot: Snapshot,\n expected: ExpectedRemoteHead,\n options?: PublishSnapshotOptions,\n ): Promise<PublishSnapshotResult>;\n listHistory(signal?: AbortSignal): Promise<RemoteHistoryEntry[]>;\n diagnose(signal?: AbortSignal): Promise<BackendDiagnostic[]>;\n}\n\nexport class SyncBackendConflictError extends Error {\n readonly code = \"SYNC_BACKEND_CONFLICT\";\n readonly phase: \"before-commit\" | \"after-commit\";\n readonly currentHead?: RemoteHead;\n readonly candidateMayHaveBeenActive: boolean;\n\n constructor(\n message: string,\n options: ErrorOptions & {\n phase?: \"before-commit\" | \"after-commit\";\n currentHead?: RemoteHead;\n candidateMayHaveBeenActive?: boolean;\n } = {},\n ) {\n super(message, options);\n this.name = \"SyncBackendConflictError\";\n this.phase = options.phase ?? \"before-commit\";\n this.currentHead = options.currentHead;\n this.candidateMayHaveBeenActive = options.candidateMayHaveBeenActive ?? false;\n }\n}\n\nexport class SyncBackendPublicationOutcomeUnknownError extends Error {\n readonly code = \"SYNC_BACKEND_PUBLICATION_OUTCOME_UNKNOWN\";\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"SyncBackendPublicationOutcomeUnknownError\";\n }\n}\n\nexport function expectedRemoteHead(head: RemoteHead | undefined): ExpectedRemoteHead {\n return head ? { kind: \"revision\", revision: head.revision } : { kind: \"missing\" };\n}\n"],
5
- "mappings": ";;;;AAiEO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EACzC,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EAET,YACE,SACA,UAII,CAAC,GACL;AACA,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,cAAc,QAAQ;AAC3B,SAAK,6BAA6B,QAAQ,8BAA8B;AAAA,EAC1E;AACF;AAEO,IAAM,4CAAN,cAAwD,MAAM;AAAA,EAC1D,OAAO;AAAA,EAEhB,YAAY,SAAiB,SAAwB;AACnD,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,mBAAmB,MAAkD;AACnF,SAAO,OAAO,EAAE,MAAM,YAAY,UAAU,KAAK,SAAS,IAAI,EAAE,MAAM,UAAU;AAClF;",
4
+ "sourcesContent": ["import type { Snapshot, SnapshotSelection } from \"../snapshot/snapshot-types.js\";\n\nexport type PublicationCapability =\n | \"read-check-write-verify\"\n | \"conditional-required\"\n | \"atomic-conditional\"\n | \"lease-protected\";\n\nexport interface RemoteHead {\n /** Backend-owned reference used only to retrieve this immutable snapshot. */\n snapshotRef: string;\n /** Backend-neutral snapshot content identity retained in local sync state. */\n snapshotId: string;\n revision: string;\n createdAt: string;\n machine: string;\n syncSessions: boolean;\n /** Validated lightweight projection; revalidate it against the immutable snapshot before adoption. */\n selection?: SnapshotSelection;\n}\n\nexport interface RemoteHistoryEntry {\n snapshotRef: string;\n snapshotId: string;\n createdAt: string;\n machine: string;\n syncSessions: boolean;\n}\n\nexport type ExpectedRemoteHead = { kind: \"missing\" } | { kind: \"revision\"; revision: string };\n\nexport interface PublishSnapshotOptions {\n signal?: AbortSignal;\n /** Called at the backend's active-head commit boundary. */\n onCommit?: () => void;\n}\n\nexport interface PublishSnapshotResult {\n head: RemoteHead;\n warnings: string[];\n}\n\nexport interface BackendDiagnostic {\n key: string;\n level: \"info\" | \"warning\" | \"error\";\n message: string;\n}\n\nexport interface SyncBackend {\n readonly identity: string;\n readonly destination: string;\n readonly capability: PublicationCapability;\n /** Compare opaque revisions produced by this backend identity. */\n sameRevision(left: string, right: string): boolean;\n /** Recovery-only equivalence for retiring an inactive candidate; never authorizes publication or apply. */\n matchesUncommittedRecoveryHead?(current: RemoteHead, recorded: RemoteHead): boolean;\n readHead(signal?: AbortSignal): Promise<RemoteHead | undefined>;\n readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot>;\n publishSnapshot(\n snapshot: Snapshot,\n expected: ExpectedRemoteHead,\n options?: PublishSnapshotOptions,\n ): Promise<PublishSnapshotResult>;\n listHistory(signal?: AbortSignal): Promise<RemoteHistoryEntry[]>;\n diagnose(signal?: AbortSignal): Promise<BackendDiagnostic[]>;\n}\n\nexport class SyncBackendConflictError extends Error {\n readonly code = \"SYNC_BACKEND_CONFLICT\";\n readonly phase: \"before-commit\" | \"after-commit\";\n readonly currentHead?: RemoteHead;\n readonly candidateMayHaveBeenActive: boolean;\n\n constructor(\n message: string,\n options: ErrorOptions & {\n phase?: \"before-commit\" | \"after-commit\";\n currentHead?: RemoteHead;\n candidateMayHaveBeenActive?: boolean;\n } = {},\n ) {\n super(message, options);\n this.name = \"SyncBackendConflictError\";\n this.phase = options.phase ?? \"before-commit\";\n this.currentHead = options.currentHead;\n this.candidateMayHaveBeenActive = options.candidateMayHaveBeenActive ?? false;\n }\n}\n\nexport class SyncBackendPublicationOutcomeUnknownError extends Error {\n readonly code = \"SYNC_BACKEND_PUBLICATION_OUTCOME_UNKNOWN\";\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"SyncBackendPublicationOutcomeUnknownError\";\n }\n}\n\nexport function expectedRemoteHead(head: RemoteHead | undefined): ExpectedRemoteHead {\n return head ? { kind: \"revision\", revision: head.revision } : { kind: \"missing\" };\n}\n"],
5
+ "mappings": ";;;;AAmEO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EACzC,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EAET,YACE,SACA,UAII,CAAC,GACL;AACA,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,cAAc,QAAQ;AAC3B,SAAK,6BAA6B,QAAQ,8BAA8B;AAAA,EAC1E;AACF;AAEO,IAAM,4CAAN,cAAwD,MAAM;AAAA,EAC1D,OAAO;AAAA,EAEhB,YAAY,SAAiB,SAAwB;AACnD,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,mBAAmB,MAAkD;AACnF,SAAO,OAAO,EAAE,MAAM,YAAY,UAAU,KAAK,SAAS,IAAI,EAAE,MAAM,UAAU;AAClF;",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  confirmMergeReview,
5
5
  mergeSync
6
- } from "./chunk-QTCGWQP3.ts";
6
+ } from "./chunk-QDPED3XJ.ts";
7
7
  import {
8
8
  conflictArtifactFingerprint,
9
9
  readConflictArtifact
@@ -12,7 +12,7 @@ import "./chunk-BDB4IDM3.ts";
12
12
  import "./chunk-V6VZ5TLG.ts";
13
13
  import {
14
14
  createSyncBackend
15
- } from "./chunk-FHNISD7A.ts";
15
+ } from "./chunk-SDZCTCYQ.ts";
16
16
  import "./chunk-ANWNQIR7.ts";
17
17
  import {
18
18
  captureMutationOwner
@@ -34,7 +34,7 @@ import {
34
34
  } from "./chunk-3B4Z2SKV.ts";
35
35
  import "./chunk-2JZFHNLM.ts";
36
36
  import "./chunk-T2SJD3N7.ts";
37
- import "./chunk-UETBKTDI.ts";
37
+ import "./chunk-YDVQYUHE.ts";
38
38
  import "./chunk-ZX2Q6THB.ts";
39
39
  import "./chunk-7D5SF7P3.ts";
40
40
  import "./chunk-A6HU4ERM.ts";
@@ -172,4 +172,4 @@ async function showConflicts(ctx, options, factory = createSyncBackend) {
172
172
  export {
173
173
  showConflicts
174
174
  };
175
- //# sourceMappingURL=conflict-review-NK5CM6UA.ts.map
175
+ //# sourceMappingURL=conflict-review-JANNPKKS.ts.map
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  SyncBackendConflictError,
15
15
  SyncBackendPublicationOutcomeUnknownError
16
- } from "./chunk-UETBKTDI.ts";
16
+ } from "./chunk-YDVQYUHE.ts";
17
17
  import {
18
18
  syncErrorGuidance
19
19
  } from "./chunk-G4RUXRWZ.ts";
@@ -1075,4 +1075,4 @@ export {
1075
1075
  gitBackendIdentity,
1076
1076
  isSupportedGitVersion
1077
1077
  };
1078
- //# sourceMappingURL=git-backend-HLGKUQJT.ts.map
1078
+ //# sourceMappingURL=git-backend-FD7AVSK6.ts.map
@@ -2,10 +2,10 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  dispatchManagerResult
5
- } from "./chunk-NGIJQSRE.ts";
6
- import "./chunk-I5IG4WV4.ts";
5
+ } from "./chunk-FJGNDYGC.ts";
6
+ import "./chunk-TRCSEIAQ.ts";
7
7
  import "./chunk-V6VZ5TLG.ts";
8
- import "./chunk-FHNISD7A.ts";
8
+ import "./chunk-SDZCTCYQ.ts";
9
9
  import "./chunk-K3RIEL6J.ts";
10
10
  import "./chunk-AMJRUXFT.ts";
11
11
  import "./chunk-CHSIAC4N.ts";
@@ -20,4 +20,4 @@ import "./chunk-HUZ5CR4W.ts";
20
20
  export {
21
21
  dispatchManagerResult
22
22
  };
23
- //# sourceMappingURL=manager-result-dispatcher-35TM3ZR3.ts.map
23
+ //# sourceMappingURL=manager-result-dispatcher-AAEAYX57.ts.map
@@ -23,12 +23,12 @@ import {
23
23
  } from "./chunk-4SBTAVFL.ts";
24
24
  import {
25
25
  dispatchManagerResult
26
- } from "./chunk-NGIJQSRE.ts";
26
+ } from "./chunk-FJGNDYGC.ts";
27
27
  import {
28
28
  runCancellableOperation
29
- } from "./chunk-I5IG4WV4.ts";
29
+ } from "./chunk-TRCSEIAQ.ts";
30
30
  import "./chunk-V6VZ5TLG.ts";
31
- import "./chunk-FHNISD7A.ts";
31
+ import "./chunk-SDZCTCYQ.ts";
32
32
  import {
33
33
  SETUP_SWITCH_ACTION_OPTIONS,
34
34
  saveOnSwitch,
@@ -172,7 +172,7 @@ function blockedSyncMenuItem(label, manager) {
172
172
  };
173
173
  }
174
174
  async function showManagerAttention(ctx, attention, runRoute, signal, onSelectionResolved) {
175
- const { showRemoteSelectionReview } = await import("./remote-selection-ui-BB5SX6P5.ts");
175
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui-Q2HUIBTL.ts");
176
176
  if (signal?.aborted) return "close";
177
177
  const review = await showRemoteSelectionReview(ctx, attention.decision.setupName, signal, void 0, {
178
178
  decision: attention.decision,
@@ -908,7 +908,7 @@ async function showSyncSettings(ctx, runRoute, signal) {
908
908
  },
909
909
  "remote-include": async ({ signal: actionSignal }) => {
910
910
  const reviewSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
911
- const { showRemoteSelectionReview } = await import("./remote-selection-ui-BB5SX6P5.ts");
911
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui-Q2HUIBTL.ts");
912
912
  if (reviewSignal.aborted) return { kind: "rejected" };
913
913
  const review = await showRemoteSelectionReview(ctx, setupName, reviewSignal, void 0, {
914
914
  origin: "settings",
@@ -1563,7 +1563,7 @@ async function showSyncManager(ctx, runRoute, sessionSignal, options = {}) {
1563
1563
  if (!attention) {
1564
1564
  const observation = options.getObservation?.();
1565
1565
  if (!observation) return { kind: "stay" };
1566
- const { showRemoteSelectionReview } = await import("./remote-selection-ui-BB5SX6P5.ts");
1566
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui-Q2HUIBTL.ts");
1567
1567
  if (sessionSignal?.aborted) return { kind: "close" };
1568
1568
  const config = await loadConfig();
1569
1569
  if (sessionSignal?.aborted) return { kind: "close" };
@@ -1729,4 +1729,4 @@ function syncMainMenuItem(label) {
1729
1729
  export {
1730
1730
  showSyncManager
1731
1731
  };
1732
- //# sourceMappingURL=manager-ui-6AYMNXUP.ts.map
1732
+ //# sourceMappingURL=manager-ui-MHAHAKG3.ts.map
@@ -2,9 +2,9 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  showRemoteSelectionReview
5
- } from "./chunk-I5IG4WV4.ts";
5
+ } from "./chunk-TRCSEIAQ.ts";
6
6
  import "./chunk-V6VZ5TLG.ts";
7
- import "./chunk-FHNISD7A.ts";
7
+ import "./chunk-SDZCTCYQ.ts";
8
8
  import "./chunk-K3RIEL6J.ts";
9
9
  import "./chunk-AMJRUXFT.ts";
10
10
  import "./chunk-CHSIAC4N.ts";
@@ -19,4 +19,4 @@ import "./chunk-HUZ5CR4W.ts";
19
19
  export {
20
20
  showRemoteSelectionReview
21
21
  };
22
- //# sourceMappingURL=remote-selection-ui-BB5SX6P5.ts.map
22
+ //# sourceMappingURL=remote-selection-ui-Q2HUIBTL.ts.map
@@ -8,7 +8,7 @@ import "./chunk-T2SJD3N7.ts";
8
8
  import {
9
9
  SyncBackendConflictError,
10
10
  SyncBackendPublicationOutcomeUnknownError
11
- } from "./chunk-UETBKTDI.ts";
11
+ } from "./chunk-YDVQYUHE.ts";
12
12
  import {
13
13
  decodeSnapshot,
14
14
  encodeSnapshot
@@ -186,7 +186,9 @@ var S3Client = class {
186
186
  method,
187
187
  url,
188
188
  body,
189
- extraHeaders,
189
+ // Transfer compression can weaken an object's ETag (R2 JSON responses).
190
+ // Read the original representation for exact checksums and strong If-Match tokens.
191
+ extraHeaders: method === "GET" ? { ...extraHeaders, "accept-encoding": "identity" } : extraHeaders,
190
192
  accessKeyId: this.config.profile.accessKeyId,
191
193
  secretAccessKey: this.config.profile.secretAccessKey,
192
194
  sessionToken: sessionToken2,
@@ -424,10 +426,15 @@ var S3SyncBackend = class {
424
426
  destination;
425
427
  capability = "conditional-required";
426
428
  checksums = /* @__PURE__ */ new Map();
429
+ recoveryHead;
427
430
  sameRevision(left, right) {
428
431
  return left === right;
429
432
  }
433
+ matchesUncommittedRecoveryHead(current, recorded) {
434
+ return this.sameRevision(current.revision, recorded.revision) || current.snapshotId === recorded.snapshotId && current.snapshotRef === recorded.snapshotRef && current.revision === this.recoveryHead?.current && recorded.revision === this.recoveryHead?.legacy;
435
+ }
430
436
  async readHead(signal) {
437
+ this.recoveryHead = void 0;
431
438
  const object = await new S3Client(this.config, signal).getJson(latestKey(this.config));
432
439
  throwIfAborted(signal);
433
440
  if (object.missing) return void 0;
@@ -437,7 +444,14 @@ var S3SyncBackend = class {
437
444
  this.config.destination.namespace
438
445
  );
439
446
  this.registerChecksum(pointer.snapshot, pointer.sha256);
440
- return remoteHead(pointer, this.identity, object.etag);
447
+ const head = remoteHead(pointer, this.identity, object.etag);
448
+ if (object.etag && /^"[^"\r\n]+"$/u.test(object.etag)) {
449
+ this.recoveryHead = {
450
+ current: head.revision,
451
+ legacy: remoteHead(pointer, this.identity, `W/${object.etag}`).revision
452
+ };
453
+ }
454
+ return head;
441
455
  }
442
456
  async readSnapshot(reference, signal) {
443
457
  const expectedChecksum = this.checksums.get(reference) ?? await this.resolveChecksum(reference, signal);
@@ -821,4 +835,4 @@ export {
821
835
  snapshotKey,
822
836
  storageRoot
823
837
  };
824
- //# sourceMappingURL=s3-backend-O2AS3TH6.ts.map
838
+ //# sourceMappingURL=s3-backend-AQGV6LFT.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/backends/s3/s3-backend.ts", "../../src/backends/s3/s3-client.ts", "../../src/backends/s3/s3-conditions.ts"],
4
+ "sourcesContent": ["import { createHash } from \"node:crypto\";\nimport { encodeKey, posixJoin } from \"../../paths.js\";\nimport { sessionTokenWarnings } from \"../../settings/config.js\";\nimport { decodeSnapshot, encodeSnapshot } from \"../../snapshot/snapshot-codec.js\";\nimport type { Snapshot } from \"../../snapshot/snapshot-types.js\";\nimport { validatePortableSnapshot } from \"../../sync/local-fields.js\";\nimport { syncErrorGuidance } from \"../../sync/sync-error-guidance.js\";\nimport { portableSnapshotSelection } from \"../../sync/sync-policy.js\";\nimport type { LatestPointer, RemoteObject, ResolvedS3Backend } from \"../backend-types.js\";\nimport {\n type BackendDiagnostic,\n type ExpectedRemoteHead,\n type PublishSnapshotOptions,\n type PublishSnapshotResult,\n type RemoteHead,\n type RemoteHistoryEntry,\n type SyncBackend,\n SyncBackendConflictError,\n SyncBackendPublicationOutcomeUnknownError,\n} from \"../sync-backend.js\";\nimport { S3Client, S3HttpError, S3ObjectAlreadyExistsError } from \"./s3-client.js\";\nimport { requireS3Etag, verifyS3Conditions } from \"./s3-conditions.js\";\n\nconst VERSION = 1;\nconst POST_COMMIT_TIMEOUT_MS = 30_000;\n\nexport class S3SyncBackend implements SyncBackend {\n readonly identity: string;\n readonly destination: string;\n readonly capability = \"conditional-required\" as const;\n private readonly checksums = new Map<string, string>();\n private recoveryHead?: { current: string; legacy: string };\n\n constructor(\n private readonly config: ResolvedS3Backend,\n private readonly postCommitTimeoutMs = POST_COMMIT_TIMEOUT_MS,\n ) {\n assertSafeDestination(config);\n this.identity = s3BackendIdentity(config);\n this.destination = s3Destination(config);\n }\n\n sameRevision(left: string, right: string) {\n return left === right;\n }\n\n matchesUncommittedRecoveryHead(current: RemoteHead, recorded: RemoteHead) {\n return (\n this.sameRevision(current.revision, recorded.revision) ||\n (current.snapshotId === recorded.snapshotId &&\n current.snapshotRef === recorded.snapshotRef &&\n current.revision === this.recoveryHead?.current &&\n recorded.revision === this.recoveryHead?.legacy)\n );\n }\n\n async readHead(signal?: AbortSignal): Promise<RemoteHead | undefined> {\n this.recoveryHead = undefined;\n const object = await new S3Client(this.config, signal).getJson<LatestPointer>(latestKey(this.config));\n throwIfAborted(signal);\n if (object.missing) return undefined;\n const pointer = requirePointer(\n object.value,\n \"Remote latest pointer is malformed.\",\n this.config.destination.namespace,\n );\n this.registerChecksum(pointer.snapshot, pointer.sha256);\n const head = remoteHead(pointer, this.identity, object.etag);\n if (object.etag && /^\"[^\"\\r\\n]+\"$/u.test(object.etag)) {\n // Old clients hashed R2's compression-weakened ETag. Bind the alias to the\n // exact current pointer and strong ETag, solely for inactive-journal retirement.\n this.recoveryHead = {\n current: head.revision,\n legacy: remoteHead(pointer, this.identity, `W/${object.etag}`).revision,\n };\n }\n return head;\n }\n\n async readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot> {\n const expectedChecksum = this.checksums.get(reference) ?? (await this.resolveChecksum(reference, signal));\n const object = await new S3Client(this.config, signal).getBuffer(snapshotKey(this.config, reference));\n throwIfAborted(signal);\n if (!object.value) throw new Error(`Snapshot not found: ${reference}`);\n if (expectedChecksum && sha256(object.value) !== expectedChecksum) {\n throw new Error(\"Remote snapshot checksum mismatch.\");\n }\n const snapshot = await decodeSnapshot(object.value, { signal });\n if (snapshot.id !== reference || snapshot.profile !== this.config.destination.namespace) {\n throw new Error(\"Remote snapshot identity mismatch.\");\n }\n validateSnapshotBundle(snapshot);\n return snapshot;\n }\n\n async publishSnapshot(\n snapshot: Snapshot,\n expected: ExpectedRemoteHead,\n options: PublishSnapshotOptions = {},\n ): Promise<PublishSnapshotResult> {\n throwIfAborted(options.signal);\n assertSnapshotIdentity(snapshot, this.config.destination.namespace);\n await verifyS3Conditions(this.config, options.signal);\n throwIfAborted(options.signal);\n const stagedKey = snapshotKey(this.config, snapshot.id);\n const encoded = await encodeSnapshot(snapshot);\n throwIfAborted(options.signal);\n const pointer = pointerFor(this.config, snapshot, sha256(encoded));\n const cancellableClient = new S3Client(this.config, options.signal);\n try {\n await cancellableClient.putBuffer(stagedKey, encoded, \"application/gzip\", {\n ifAbsent: true,\n });\n } catch (error) {\n if (!(error instanceof S3ObjectAlreadyExistsError)) throw error;\n const existing = await cancellableClient.getBuffer(stagedKey);\n if (!existing.value || sha256(existing.value) !== pointer.sha256) {\n throw new SyncBackendConflictError(\n `Immutable snapshot id already exists with different content: ${snapshot.id}`,\n );\n }\n }\n const currentObject = await cancellableClient.getJson<LatestPointer>(latestKey(this.config));\n throwIfAborted(options.signal);\n let current: RemoteHead | undefined;\n if (!currentObject.missing) {\n const currentPointer = requirePointer(\n currentObject.value,\n \"Remote latest pointer is malformed.\",\n this.config.destination.namespace,\n );\n this.registerChecksum(currentPointer.snapshot, currentPointer.sha256);\n current = remoteHead(currentPointer, this.identity, currentObject.etag);\n }\n if (!matchesExpected(current, expected)) {\n throw new SyncBackendConflictError(\"Remote changed while pushing. Run /sync pull first, then retry.\", {\n currentHead: current,\n });\n }\n const condition = currentObject.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(currentObject.etag) };\n throwIfAborted(options.signal);\n options.onCommit?.();\n\n // The active-head PUT is the publication boundary. Do not bind it or its\n // verification to a user-cancellation signal after the boundary begins.\n const commitClient = new S3Client(this.config, AbortSignal.timeout(this.postCommitTimeoutMs));\n try {\n await commitClient.putJson(latestKey(this.config), pointer, condition);\n } catch (error) {\n if (\n error instanceof S3ObjectAlreadyExistsError ||\n (error instanceof S3HttpError && (error.status === 412 || error.status === 409))\n ) {\n throw new SyncBackendConflictError(\"Remote conditional publication was rejected; reread and retry.\", {\n cause: error,\n });\n }\n throw new SyncBackendPublicationOutcomeUnknownError(\n `Remote publication outcome is unknown: ${errorMessage(error)}`,\n { cause: error },\n );\n }\n\n let verifiedObject: RemoteObject<LatestPointer>;\n try {\n verifiedObject = await commitClient.getJson<LatestPointer>(latestKey(this.config));\n } catch (error) {\n throw new SyncBackendPublicationOutcomeUnknownError(\n `Remote snapshot may be active, but publication could not be verified: ${errorMessage(error)}`,\n { cause: error },\n );\n }\n let verifiedPointer: LatestPointer;\n try {\n verifiedPointer = requirePointer(\n verifiedObject.value,\n \"Remote latest pointer is malformed after publication.\",\n this.config.destination.namespace,\n );\n } catch (error) {\n throw new SyncBackendPublicationOutcomeUnknownError(\n `Remote snapshot may be active, but publication verification was malformed: ${errorMessage(error)}`,\n { cause: error },\n );\n }\n if (!samePointer(verifiedPointer, pointer)) {\n throw new SyncBackendConflictError(\n \"Remote latest changed immediately after push. Run /sync status before continuing.\",\n {\n phase: \"after-commit\",\n currentHead: remoteHead(verifiedPointer, this.identity, verifiedObject.etag),\n candidateMayHaveBeenActive: true,\n },\n );\n }\n this.registerChecksum(verifiedPointer.snapshot, verifiedPointer.sha256);\n const head = remoteHead(verifiedPointer, this.identity, verifiedObject.etag);\n const warning = await this.updateHistorySafely(commitClient, pointer);\n return { head, warnings: warning ? [warning] : [] };\n }\n\n async listHistory(signal?: AbortSignal): Promise<RemoteHistoryEntry[]> {\n const object = await new S3Client(this.config, signal).getJson<{\n version: number;\n snapshots: LatestPointer[];\n }>(historyKey(this.config));\n throwIfAborted(signal);\n if (object.missing) return [];\n const pointers = requireHistory(object.value, this.config.destination.namespace);\n for (const pointer of pointers) {\n const known = this.checksums.get(pointer.snapshot);\n if (known && known !== pointer.sha256) {\n throw new Error(\"Remote history conflicts with an already observed snapshot checksum.\");\n }\n }\n for (const pointer of pointers) this.registerChecksum(pointer.snapshot, pointer.sha256);\n return pointers.map(remoteHistoryEntry);\n }\n\n async diagnose(signal?: AbortSignal): Promise<BackendDiagnostic[]> {\n throwIfAborted(signal);\n const diagnostics: BackendDiagnostic[] = [\n {\n key: \"s3-config\",\n level: \"info\",\n message: `s3 config: ok (${this.config.destination.bucket}/${storageRoot(this.config)})`,\n },\n ...sessionTokenWarnings(this.config.profile).map((message) => ({\n key: \"s3-session-token\",\n level: \"warning\" as const,\n message,\n })),\n ];\n const deadline = AbortSignal.timeout(10_000);\n const probeSignal = signal ? AbortSignal.any([signal, deadline]) : deadline;\n try {\n const result = await new S3Client(this.config, probeSignal).diagnoseRead(latestKey(this.config));\n throwIfAborted(probeSignal);\n const messages = {\n readable: \"s3 remote read: HTTP success. Snapshot contents and write access are not tested.\",\n \"missing-key\":\n \"s3 remote read: no snapshot at this path (NoSuchKey). Write access is not tested; verify the bucket and path before first sync.\",\n \"missing-bucket\":\n \"s3 remote read: bucket not found (NoSuchBucket). Create the bucket with your provider or edit the setup's bucket name.\",\n \"unknown-404\":\n \"s3 remote read: HTTP 404; cannot confirm whether the bucket or snapshot is missing. Verify the endpoint, bucket, and path with your provider. Write access is not tested.\",\n };\n diagnostics.push({\n key: \"s3-read\",\n level: result === \"readable\" ? \"info\" : \"warning\",\n message: messages[result],\n });\n } catch (error) {\n throwIfAborted(signal);\n diagnostics.push({\n key: \"s3-read\",\n level: \"warning\",\n message: syncErrorGuidance(deadline.aborted ? new Error(\"S3 diagnostic request timed out.\") : error),\n });\n }\n return diagnostics;\n }\n\n private async resolveChecksum(reference: string, signal?: AbortSignal) {\n await this.readHead(signal);\n const headChecksum = this.checksums.get(reference);\n if (headChecksum) return headChecksum;\n await this.listHistory(signal);\n return this.checksums.get(reference);\n }\n\n private registerChecksum(reference: string, checksum: string) {\n const known = this.checksums.get(reference);\n if (known && known !== checksum) {\n throw new Error(\"Remote snapshot reference was rebound to a different checksum.\");\n }\n this.checksums.set(reference, checksum);\n }\n\n private async updateHistorySafely(client: S3Client, pointer: LatestPointer) {\n try {\n await this.updateHistory(client, pointer);\n return undefined;\n } catch (error) {\n return `Remote snapshot is active, but history could not be updated: ${errorMessage(error)}. Run /sync doctor before relying on history.`;\n }\n }\n\n private async updateHistory(client: S3Client, pointer: LatestPointer) {\n const object = await client.getJson<{ version: number; snapshots: LatestPointer[] }>(historyKey(this.config));\n const snapshots = object.missing ? [] : requireHistory(object.value, this.config.destination.namespace);\n const existing = snapshots.find((snapshot) => snapshot.snapshot === pointer.snapshot);\n if (existing) {\n if (!samePointer(existing, pointer)) throw new Error(\"Remote history rebound an immutable snapshot reference.\");\n return;\n }\n const next = [...snapshots, pointer].slice(-100);\n const condition = object.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(object.etag) };\n await client.putJson(historyKey(this.config), { version: VERSION, snapshots: next }, condition);\n }\n}\n\nexport function latestKey(config: ResolvedS3Backend) {\n return posixJoin(storageRoot(config), \"latest.json\");\n}\n\nexport function historyKey(config: ResolvedS3Backend) {\n return posixJoin(storageRoot(config), \"history.json\");\n}\n\nexport function snapshotKey(config: ResolvedS3Backend, id: string) {\n requireSnapshotReference(id);\n return posixJoin(storageRoot(config), \"snapshots\", `${id}.json.gz`);\n}\n\nexport function storageRoot(config: ResolvedS3Backend) {\n return config.destination.prefix === \"./\" ? \"\" : config.destination.prefix;\n}\n\nexport function pointerFor(config: ResolvedS3Backend, snapshot: Snapshot, checksum: string): LatestPointer {\n return {\n version: VERSION,\n profile: config.destination.namespace,\n snapshot: snapshot.id,\n sha256: checksum,\n createdAt: snapshot.createdAt,\n machine: snapshot.machine,\n syncSessions: snapshot.syncSessions === true || snapshot.files.some((file) => file.path.startsWith(\"sessions/\")),\n ...(snapshot.selection === undefined ? {} : { selection: snapshot.selection }),\n };\n}\n\nexport function s3BackendIdentity(config: ResolvedS3Backend) {\n const destination = JSON.stringify([\n secretFreeEndpoint(config.profile.endpoint),\n trimSlashes(config.destination.bucket),\n trimSlashes(config.destination.prefix),\n ]);\n return `s3:${sha256(Buffer.from(destination))}`;\n}\n\nfunction s3Destination(config: ResolvedS3Backend) {\n let host = secretFreeEndpoint(config.profile.endpoint);\n try {\n host = new URL(host).hostname;\n } catch {\n host = \"invalid S3 endpoint\";\n }\n return `${host} \u00B7 ${config.destination.bucket}/${storageRoot(config)}`;\n}\n\nfunction secretFreeEndpoint(value: string) {\n const normalized = value.trim();\n try {\n const url = new URL(normalized);\n url.username = \"\";\n url.password = \"\";\n return url.toString();\n } catch {\n return normalized.replace(/\\/\\/[^/@\\s]+@/u, \"//\");\n }\n}\n\nfunction trimSlashes(value: string) {\n return value.replace(/^\\/+|\\/+$/g, \"\");\n}\n\nfunction remoteHistoryEntry(pointer: LatestPointer): RemoteHistoryEntry {\n return {\n snapshotRef: pointer.snapshot,\n snapshotId: pointer.snapshot,\n createdAt: pointer.createdAt,\n machine: pointer.machine,\n syncSessions: pointer.syncSessions === true,\n };\n}\n\nfunction remoteHead(pointer: LatestPointer, identity: string, etag?: string): RemoteHead {\n return {\n snapshotRef: pointer.snapshot,\n snapshotId: pointer.snapshot,\n revision: `s3:${sha256(Buffer.from(canonicalJson([identity, etag ?? null, pointer])))}`,\n createdAt: pointer.createdAt,\n machine: pointer.machine,\n syncSessions: pointer.syncSessions === true,\n ...(pointer.selection === undefined ? {} : { selection: pointer.selection }),\n };\n}\n\nfunction samePointer(left: LatestPointer, right: LatestPointer) {\n return canonicalJson(left) === canonicalJson(right);\n}\n\nfunction canonicalJson(value: unknown) {\n return JSON.stringify(canonicalValue(value));\n}\n\nfunction canonicalValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalValue);\n if (!value || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [key, canonicalValue((value as Record<string, unknown>)[key])]),\n );\n}\n\nfunction matchesExpected(head: RemoteHead | undefined, expected: ExpectedRemoteHead) {\n if (expected.kind === \"missing\") return head === undefined;\n return head?.revision === expected.revision;\n}\n\nfunction requirePointer(value: LatestPointer | undefined, message: string, expectedProfile: string) {\n if (\n !value ||\n value.version !== VERSION ||\n value.profile !== expectedProfile ||\n typeof value.snapshot !== \"string\" ||\n !isSafeSnapshotReference(value.snapshot) ||\n typeof value.sha256 !== \"string\" ||\n !/^[0-9a-f]{64}$/.test(value.sha256) ||\n typeof value.createdAt !== \"string\" ||\n !isSafeMetadata(value.createdAt, 64) ||\n typeof value.machine !== \"string\" ||\n !isSafeMetadata(value.machine, 256) ||\n (value.syncSessions !== undefined && typeof value.syncSessions !== \"boolean\")\n ) {\n throw new Error(message);\n }\n if (value.selection !== undefined) portableSnapshotSelection(value.selection);\n return value;\n}\n\nfunction requireHistory(value: { version: number; snapshots: LatestPointer[] } | undefined, expectedProfile: string) {\n if (!value || value.version !== VERSION || !Array.isArray(value.snapshots)) {\n throw new Error(\"Remote history is malformed.\");\n }\n const pointers = value.snapshots.map((pointer) =>\n requirePointer(pointer, \"Remote history entry is malformed.\", expectedProfile),\n );\n const references = new Set<string>();\n for (const pointer of pointers) {\n if (references.has(pointer.snapshot)) {\n throw new Error(\"Remote history contains duplicate snapshot references.\");\n }\n references.add(pointer.snapshot);\n }\n return pointers;\n}\n\nfunction assertSnapshotIdentity(snapshot: Snapshot, expectedProfile: string) {\n validatePortableSnapshot(snapshot);\n if (\n snapshot.profile !== expectedProfile ||\n !isSafeSnapshotReference(snapshot.id) ||\n !isSafeMetadata(snapshot.createdAt, 64) ||\n !isSafeMetadata(snapshot.machine, 256) ||\n !Array.isArray(snapshot.files)\n ) {\n throw new Error(\"Invalid snapshot identity for S3 publication.\");\n }\n}\n\nfunction validateSnapshotBundle(snapshot: Snapshot) {\n const paths = new Set<string>();\n for (const file of snapshot.files) {\n if (\n !file ||\n typeof file.path !== \"string\" ||\n typeof file.contentBase64 !== \"string\" ||\n typeof file.sha256 !== \"string\" ||\n !/^[0-9a-f]{64}$/.test(file.sha256) ||\n paths.has(file.path)\n ) {\n throw new Error(\"Remote snapshot bundle is malformed.\");\n }\n const content = Buffer.from(file.contentBase64, \"base64\");\n if (content.toString(\"base64\") !== file.contentBase64 || sha256(content) !== file.sha256) {\n throw new Error(\"Remote snapshot file checksum mismatch.\");\n }\n paths.add(file.path);\n }\n}\n\nfunction assertSafeDestination(config: ResolvedS3Backend) {\n for (const [label, value, allowEmpty] of [\n [\"bucket\", config.destination.bucket, false],\n [\"prefix\", config.destination.prefix, true],\n [\"namespace\", config.destination.namespace, false],\n ] as const) {\n if (label === \"prefix\" && value === \"./\") continue;\n if (\n (!allowEmpty && value.length === 0) ||\n value.includes(\"\\\\\") ||\n hasControlCharacter(value) ||\n value.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")\n ) {\n throw new Error(`Invalid S3 storage location ${label}.`);\n }\n }\n}\n\nfunction requireSnapshotReference(reference: string) {\n if (!isSafeSnapshotReference(reference)) {\n throw new Error(\"Invalid S3 snapshot reference.\");\n }\n}\n\nfunction isSafeSnapshotReference(reference: string) {\n return (\n reference.length > 0 &&\n reference.length <= 512 &&\n reference !== \".\" &&\n reference !== \"..\" &&\n !reference.includes(\"/\") &&\n !reference.includes(\"\\\\\") &&\n !hasControlCharacter(reference)\n );\n}\n\nfunction isSafeMetadata(value: string, maxLength: number) {\n return value.length > 0 && value.length <= maxLength && !hasControlCharacter(value);\n}\n\nfunction hasControlCharacter(value: string) {\n return [...value].some((character) => {\n const code = character.codePointAt(0) ?? 0;\n return code < 0x20 || code === 0x7f;\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n if (!signal?.aborted) return;\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n\nfunction sha256(value: Buffer) {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport { encodeKey };\n", "import { createHash, createHmac } from \"node:crypto\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { encodeKey, posixJoin } from \"../../paths.js\";\nimport type { RemoteObject, ResolvedS3Backend } from \"../backend-types.js\";\n\nconst MAX_JSON_RESPONSE_BYTES = 1024 * 1024;\nconst MAX_SNAPSHOT_RESPONSE_BYTES = 256 * 1024 * 1024;\nconst MAX_ERROR_RESPONSE_BYTES = 64 * 1024;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\nfunction iso8601Basic(date: Date) {\n return date.toISOString().replace(/[:-]|\\.\\d{3}/g, \"\");\n}\n\nfunction isCloudflareR2Endpoint(endpoint: string | undefined) {\n const value = endpoint?.trim();\n if (!value) return false;\n try {\n const hostname = new URL(value).hostname.toLowerCase();\n return hostname === \"r2.cloudflarestorage.com\" || hostname.endsWith(\".r2.cloudflarestorage.com\");\n } catch {\n return false;\n }\n}\n\nexport class S3ObjectAlreadyExistsError extends Error {\n constructor(readonly key: string) {\n super(`S3 object already exists: ${key}`);\n this.name = \"S3ObjectAlreadyExistsError\";\n }\n}\n\nexport class S3HttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n this.name = \"S3HttpError\";\n }\n}\n\nexport class S3Client {\n private config: ResolvedS3Backend;\n private endpoint: URL;\n private signal?: AbortSignal;\n private requestTimeoutMs: number;\n private omitSessionTokenAfterRejection = false;\n\n constructor(config: ResolvedS3Backend, signal?: AbortSignal, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {\n this.config = config;\n this.endpoint = new URL(config.profile.endpoint);\n this.signal = signal;\n this.requestTimeoutMs = requestTimeoutMs;\n }\n\n /** Read-only access probe. A bare 404 cannot distinguish a missing key from a missing bucket. */\n async diagnoseRead(key: string): Promise<\"readable\" | \"missing-key\" | \"missing-bucket\" | \"unknown-404\"> {\n this.signal?.throwIfAborted();\n const response = await this.request(\"GET\", key);\n if (response.ok) {\n await response.body?.cancel();\n this.signal?.throwIfAborted();\n return \"readable\";\n }\n const detail = await this.readErrorText(response);\n this.signal?.throwIfAborted();\n if (response.status === 404) {\n const code = /<Code>\\s*([A-Za-z]+)\\s*<\\/Code>/u.exec(detail)?.[1];\n if (code === \"NoSuchBucket\") return \"missing-bucket\";\n if (code === \"NoSuchKey\") return \"missing-key\";\n return \"unknown-404\";\n }\n throw new S3HttpError(`S3 remote read failed (HTTP ${response.status}).`, response.status);\n }\n\n async getJson<T>(key: string): Promise<RemoteObject<T>> {\n const maxAttempts = 3;\n let lastError: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const object = await this.request(\"GET\", key);\n if (object.status === 404) return { missing: true };\n if (!object.ok) {\n throw new S3HttpError(`S3 GET failed (${object.status}): ${await this.readErrorText(object)}`, object.status);\n }\n const body = await readBoundedText(object, MAX_JSON_RESPONSE_BYTES, \"S3 JSON response\");\n // R2 can intermittently return an empty 200 body (read-after-write\n // inconsistency on a long-lived keep-alive connection), which makes\n // response.json() throw \"JSON Parse error: Unexpected EOF\" under Bun.\n // Retry so the transient blip is absorbed instead of surfacing as a\n // \"pi-sync auto sync skipped\" warning on every session start.\n if (body.length > 0) {\n return {\n value: JSON.parse(body) as T,\n etag: normalizeEtag(object.headers.get(\"etag\")),\n missing: false,\n };\n }\n lastError = new Error(`S3 GET returned an empty body for ${key}`);\n if (attempt < maxAttempts) {\n await sleep(250 * attempt, undefined, { signal: this.signal });\n }\n }\n throw lastError;\n }\n\n async getBuffer(key: string): Promise<RemoteObject<Buffer>> {\n const maxAttempts = 3;\n let lastError: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const object = await this.request(\"GET\", key);\n if (object.status === 404) return { missing: true };\n if (!object.ok) {\n throw new S3HttpError(`S3 GET failed (${object.status}): ${await this.readErrorText(object)}`, object.status);\n }\n const buffer = await readBoundedBuffer(object, MAX_SNAPSHOT_RESPONSE_BYTES, \"S3 snapshot response\");\n // R2 can intermittently return an empty 200 body on a long-lived\n // keep-alive connection (same root cause as the getJson retry\n // above). getBuffer is only used for snapshot .json.gz payloads,\n // which are always non-empty, so an empty body is a transient\n // blip, not a legitimate response. Retry so the checksum guard\n // downstream doesn't surface it as \"Remote snapshot checksum\n // mismatch\".\n if (buffer.length > 0) {\n return { value: buffer, etag: normalizeEtag(object.headers.get(\"etag\")), missing: false };\n }\n lastError = new Error(`S3 GET returned an empty body for ${key}`);\n if (attempt < maxAttempts) {\n await sleep(250 * attempt, undefined, { signal: this.signal });\n }\n }\n throw lastError;\n }\n\n async putJson(key: string, value: unknown, options: { ifAbsent?: boolean; ifMatch?: string } = {}) {\n const body = Buffer.from(JSON.stringify(value, null, \"\\t\"), \"utf8\");\n await this.putBuffer(key, body, \"application/json\", options);\n }\n\n async putBuffer(\n key: string,\n body: Buffer,\n contentType: string,\n options: { ifAbsent?: boolean; ifMatch?: string } = {},\n ) {\n const headers: Record<string, string> = { \"content-type\": contentType };\n if (options.ifAbsent) headers[\"if-none-match\"] = \"*\";\n if (options.ifMatch) headers[\"if-match\"] = options.ifMatch;\n const response = await this.request(\"PUT\", key, body, headers);\n if ((options.ifAbsent || options.ifMatch) && (response.status === 412 || response.status === 409)) {\n await response.body?.cancel();\n if (options.ifAbsent && response.status === 412) throw new S3ObjectAlreadyExistsError(key);\n if (options.ifAbsent || options.ifMatch) {\n throw new S3HttpError(`S3 conditional PUT rejected (${response.status}).`, response.status);\n }\n }\n if (!response.ok) {\n throw new S3HttpError(\n `S3 PUT failed (${response.status}): ${await this.readErrorText(response)}`,\n response.status,\n );\n }\n await response.body?.cancel();\n }\n\n async delete(key: string) {\n const response = await this.request(\"DELETE\", key);\n if (!response.ok && response.status !== 404) {\n throw new S3HttpError(\n `S3 DELETE failed (${response.status}): ${await this.readErrorText(response)}`,\n response.status,\n );\n }\n await response.body?.cancel();\n }\n\n private async request(\n method: \"GET\" | \"PUT\" | \"DELETE\",\n key: string,\n body?: Buffer,\n extraHeaders: Record<string, string> = {},\n ) {\n const url = new URL(this.endpoint.toString());\n url.pathname = posixJoin(url.pathname, this.config.destination.bucket, encodeKey(key));\n const send = async (sessionToken: string | undefined) => {\n const transportSignal = this.transportSignal();\n const headers = await signedHeaders({\n method,\n url,\n body,\n // Transfer compression can weaken an object's ETag (R2 JSON responses).\n // Read the original representation for exact checksums and strong If-Match tokens.\n extraHeaders: method === \"GET\" ? { ...extraHeaders, \"accept-encoding\": \"identity\" } : extraHeaders,\n accessKeyId: this.config.profile.accessKeyId,\n secretAccessKey: this.config.profile.secretAccessKey,\n sessionToken,\n region: this.config.profile.region,\n });\n try {\n return await fetch(url, {\n method,\n headers,\n body: body ? new Uint8Array(body) : undefined,\n signal: transportSignal,\n });\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") throw error;\n if (error instanceof Error && error.name === \"TimeoutError\") {\n throw new Error(\"S3 request timed out.\", { cause: error });\n }\n throw new Error(`S3 request failed: ${this.redact(errorMessage(error))}`, { cause: error });\n }\n };\n const sessionToken = this.omitSessionTokenAfterRejection ? undefined : this.config.profile.sessionToken;\n const response = await send(sessionToken);\n try {\n if (!(await this.shouldRetryWithoutSessionToken(response, sessionToken))) return response;\n } catch (error) {\n await response.body?.cancel().catch(() => undefined);\n throw error;\n }\n await response.body?.cancel();\n const retry = await send(undefined);\n if (retry.ok || retry.status === 404) this.omitSessionTokenAfterRejection = true;\n return retry;\n }\n\n private transportSignal() {\n const timeout = AbortSignal.timeout(this.requestTimeoutMs);\n return this.signal ? AbortSignal.any([this.signal, timeout]) : timeout;\n }\n\n private async shouldRetryWithoutSessionToken(response: Response, sessionToken: string | undefined) {\n if (\n !sessionToken ||\n !isCloudflareR2Endpoint(this.config.profile.endpoint) ||\n response.ok ||\n response.status !== 400\n ) {\n return false;\n }\n return isSecurityTokenInvalidArgument(\n await readBoundedText(response.clone(), MAX_ERROR_RESPONSE_BYTES, \"S3 error response\"),\n );\n }\n\n private async readErrorText(response: Response) {\n try {\n return this.redact(await readBoundedText(response, MAX_ERROR_RESPONSE_BYTES, \"S3 error response\"));\n } catch (error) {\n return this.redact(errorMessage(error));\n }\n }\n\n private redact(value: string) {\n let redacted = value;\n let endpointUsername: string | undefined;\n let endpointPassword: string | undefined;\n let endpointQueryValues: string[] = [];\n try {\n const endpoint = new URL(this.config.profile.endpoint);\n endpointUsername = endpoint.username;\n endpointPassword = endpoint.password;\n endpointQueryValues = [...endpoint.searchParams.values()];\n } catch {\n // The constructor already validates normal runtime endpoints.\n }\n for (const secret of [\n this.config.profile.accessKeyId,\n this.config.profile.secretAccessKey,\n this.config.profile.sessionToken,\n endpointUsername,\n endpointPassword,\n ...endpointQueryValues,\n ]) {\n if (secret) redacted = redacted.replaceAll(secret, \"[REDACTED]\");\n }\n return redacted;\n }\n}\n\nasync function readBoundedText(response: Response, limit: number, label: string) {\n return (await readBoundedBuffer(response, limit, label)).toString(\"utf8\");\n}\n\nasync function readBoundedBuffer(response: Response, limit: number, label: string) {\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > limit) {\n // Do not await cancellation of a cloned stream before its sibling can be released.\n void response.body?.cancel().catch(() => undefined);\n throw new Error(`${label} exceeds the ${limit}-byte limit.`);\n }\n if (!response.body) return Buffer.alloc(0);\n const reader = response.body.getReader();\n const chunks: Buffer[] = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > limit) {\n void reader.cancel().catch(() => undefined);\n throw new Error(`${label} exceeds the ${limit}-byte limit.`);\n }\n chunks.push(Buffer.from(value));\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, total);\n}\n\nfunction errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function signedHeaders(input: {\n method: string;\n url: URL;\n body?: Buffer;\n extraHeaders: Record<string, string>;\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n region: string;\n}) {\n const now = new Date();\n const amzDate = iso8601Basic(now);\n const dateStamp = amzDate.slice(0, 8);\n const payloadHash = sha256(input.body ?? Buffer.alloc(0));\n const headers: Record<string, string> = {\n ...lowercaseKeys(input.extraHeaders),\n host: input.url.host,\n \"x-amz-content-sha256\": payloadHash,\n \"x-amz-date\": amzDate,\n };\n if (input.sessionToken) headers[\"x-amz-security-token\"] = input.sessionToken;\n const signedHeaderNames = Object.keys(headers).sort();\n const canonicalHeaders = signedHeaderNames.map((name) => `${name}:${headers[name]?.trim()}\\n`).join(\"\");\n const canonicalRequest = [\n input.method,\n input.url.pathname,\n input.url.searchParams.toString(),\n canonicalHeaders,\n signedHeaderNames.join(\";\"),\n payloadHash,\n ].join(\"\\n\");\n const scope = `${dateStamp}/${input.region}/s3/aws4_request`;\n const stringToSign = [\"AWS4-HMAC-SHA256\", amzDate, scope, sha256(Buffer.from(canonicalRequest))].join(\"\\n\");\n const signingKey = hmac(\n hmac(hmac(hmac(Buffer.from(`AWS4${input.secretAccessKey}`), dateStamp), input.region), \"s3\"),\n \"aws4_request\",\n );\n const signature = createHmac(\"sha256\", signingKey).update(stringToSign).digest(\"hex\");\n return {\n ...headers,\n authorization: `AWS4-HMAC-SHA256 Credential=${input.accessKeyId}/${scope}, SignedHeaders=${signedHeaderNames.join(\";\")}, Signature=${signature}`,\n };\n}\n\nfunction hmac(key: Buffer, value: string) {\n return createHmac(\"sha256\", key).update(value).digest();\n}\n\nfunction sha256(value: Buffer) {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction lowercaseKeys(value: Record<string, string>) {\n return Object.fromEntries(Object.entries(value).map(([key, item]) => [key.toLowerCase(), item]));\n}\n\nfunction normalizeEtag(value: string | null) {\n return value ?? undefined;\n}\n\nfunction isSecurityTokenInvalidArgument(text: string) {\n return text.includes(\"<Code>InvalidArgument</Code>\") && text.includes(\"<Message>X-Amz-Security-Token</Message>\");\n}\n", "import { randomUUID } from \"node:crypto\";\nimport { posixJoin } from \"../../paths.js\";\nimport type { ResolvedS3Backend } from \"../backend-types.js\";\nimport { S3Client, S3HttpError, S3ObjectAlreadyExistsError } from \"./s3-client.js\";\n\nexport function requireS3Etag(value: string | undefined) {\n if (!value || !/^\"[^\"\\r\\n]+\"$/u.test(value)) throw new Error(\"S3 conditional publication requires a strong ETag.\");\n return value;\n}\n\n/** Never probe the active pointer or immutable snapshots; ignored conditions must not corrupt either. */\nexport async function verifyS3Conditions(config: ResolvedS3Backend, signal?: AbortSignal) {\n const key = posixJoin(\n config.destination.prefix === \"./\" ? \"\" : config.destination.prefix,\n \".pi-sync-probes\",\n randomUUID(),\n );\n const client = new S3Client(config, signal);\n let failure: unknown;\n let attempted = false;\n try {\n signal?.throwIfAborted();\n attempted = true;\n await client.putBuffer(key, Buffer.from(\"first\"), \"text/plain\", { ifAbsent: true });\n const first = await client.getBuffer(key);\n const etag = requireS3Etag(first.etag);\n if (!first.value?.equals(Buffer.from(\"first\"))) throw new Error(\"S3 conditional probe read did not match.\");\n await expectRejected(client.putBuffer(key, Buffer.from(\"ignored\"), \"text/plain\", { ifAbsent: true }));\n await expectRejected(client.putBuffer(key, Buffer.from(\"ignored\"), \"text/plain\", { ifMatch: '\"pi-sync-stale\"' }));\n const unchanged = await client.getBuffer(key);\n if (!unchanged.value?.equals(first.value) || unchanged.etag !== etag)\n throw new Error(\"S3 failed precondition changed the probe.\");\n await client.putBuffer(key, Buffer.from(\"second\"), \"text/plain\", { ifMatch: etag });\n const second = await client.getBuffer(key);\n if (!second.value?.equals(Buffer.from(\"second\")) || requireS3Etag(second.etag) === etag) {\n throw new Error(\"S3 conditional write did not rotate the probe ETag.\");\n }\n await expectRejected(client.putBuffer(key, Buffer.from(\"ignored\"), \"text/plain\", { ifMatch: etag }));\n const final = await client.getBuffer(key);\n if (!final.value?.equals(second.value) || final.etag !== second.etag)\n throw new Error(\"S3 stale ETag changed the probe.\");\n signal?.throwIfAborted();\n } catch (error) {\n failure = error;\n }\n // Cleanup has its own bounded deadline even when the owning session was cancelled.\n if (attempted) {\n try {\n await new S3Client(config, AbortSignal.timeout(10_000)).delete(key);\n } catch (error) {\n throw new Error(`S3 probe cleanup failed; remove ${key}.`, { cause: failure ?? error });\n }\n }\n if (failure) throw failure;\n signal?.throwIfAborted();\n}\n\nasync function expectRejected(operation: Promise<void>) {\n try {\n await operation;\n } catch (error) {\n // 409 is retryable contention, not proof that a failed condition was enforced.\n if (error instanceof S3ObjectAlreadyExistsError || (error instanceof S3HttpError && error.status === 412)) return;\n throw error;\n }\n throw new Error(\"S3 server ignored a write precondition; publication stopped for safety.\");\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["createHash", "sessionToken", "sha256", "errorMessage", "createHash"]
7
+ }
@@ -2,9 +2,9 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  inspectSync
5
- } from "./chunk-BX3K7YW7.ts";
5
+ } from "./chunk-WFKUKVCP.ts";
6
6
  import "./chunk-BDB4IDM3.ts";
7
- import "./chunk-FHNISD7A.ts";
7
+ import "./chunk-SDZCTCYQ.ts";
8
8
  import "./chunk-PWMWOJP4.ts";
9
9
  import "./chunk-4QVPTIZB.ts";
10
10
  import "./chunk-BVVP33U5.ts";
@@ -17,4 +17,4 @@ import "./chunk-HUZ5CR4W.ts";
17
17
  export {
18
18
  inspectSync
19
19
  };
20
- //# sourceMappingURL=sync-inspection-RLIHUYDV.ts.map
20
+ //# sourceMappingURL=sync-inspection-K45QVLHB.ts.map
@@ -2,7 +2,7 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  inspectSync
5
- } from "./chunk-BX3K7YW7.ts";
5
+ } from "./chunk-WFKUKVCP.ts";
6
6
  import {
7
7
  applySnapshot,
8
8
  countPreservedRemoteFiles,
@@ -14,7 +14,7 @@ import {
14
14
  formatSnapshotOnlyDiff,
15
15
  mergeSync,
16
16
  publicationCapabilityDescription
17
- } from "./chunk-QTCGWQP3.ts";
17
+ } from "./chunk-QDPED3XJ.ts";
18
18
  import {
19
19
  readMergeJournal,
20
20
  requireNoMergeJournal,
@@ -41,7 +41,7 @@ import {
41
41
  } from "./chunk-V6VZ5TLG.ts";
42
42
  import {
43
43
  createSyncBackend
44
- } from "./chunk-FHNISD7A.ts";
44
+ } from "./chunk-SDZCTCYQ.ts";
45
45
  import "./chunk-ANWNQIR7.ts";
46
46
  import {
47
47
  backupLocal,
@@ -79,7 +79,7 @@ import "./chunk-2JZFHNLM.ts";
79
79
  import "./chunk-T2SJD3N7.ts";
80
80
  import {
81
81
  expectedRemoteHead
82
- } from "./chunk-UETBKTDI.ts";
82
+ } from "./chunk-YDVQYUHE.ts";
83
83
  import "./chunk-ZX2Q6THB.ts";
84
84
  import {
85
85
  errorMessage
@@ -876,4 +876,4 @@ export {
876
876
  status,
877
877
  syncBoth
878
878
  };
879
- //# sourceMappingURL=sync-operations-RBUOUJWL.ts.map
879
+ //# sourceMappingURL=sync-operations-Y27F5T7W.ts.map
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  SyncBackendConflictError,
5
5
  SyncBackendPublicationOutcomeUnknownError
6
- } from "./chunk-UETBKTDI.ts";
6
+ } from "./chunk-YDVQYUHE.ts";
7
7
  import {
8
8
  decodeSnapshot,
9
9
  encodeSnapshot
@@ -854,4 +854,4 @@ export {
854
854
  snapshotsPath,
855
855
  webDavBackendIdentity
856
856
  };
857
- //# sourceMappingURL=webdav-backend-ETQGSI6I.ts.map
857
+ //# sourceMappingURL=webdav-backend-TBILVEQK.ts.map
package/dist/index.ts CHANGED
@@ -176,7 +176,7 @@ async function executeCommand(rawArgs, ctx, signal, loaders, onCommit, setup) {
176
176
  return { kind: "completed" };
177
177
  }
178
178
  case "conflicts": {
179
- const { showConflicts } = await import("./chunks/conflict-review-NK5CM6UA.ts");
179
+ const { showConflicts } = await import("./chunks/conflict-review-JANNPKKS.ts");
180
180
  throwIfAborted(options.signal);
181
181
  await withLock("sync", () => showConflicts(ctx, options));
182
182
  return { kind: "completed" };
@@ -376,7 +376,7 @@ async function handleCommand(rawArgs, ctx, sessionSignal, loaders, attention) {
376
376
  );
377
377
  if (!rawArgs.trim()) {
378
378
  try {
379
- const { showSyncManager } = await import("./chunks/manager-ui-6AYMNXUP.ts");
379
+ const { showSyncManager } = await import("./chunks/manager-ui-MHAHAKG3.ts");
380
380
  if (sessionSignal.aborted) return;
381
381
  await showSyncManager(ctx, run, sessionSignal, {
382
382
  getAttention: () => attention.current(),
@@ -476,7 +476,7 @@ function directSelectionOrigin(rawArgs) {
476
476
  async function resolveSelectionAttention(ctx, attention, signal, loaders, options = {}) {
477
477
  const current = attention.current();
478
478
  if (!current || signal.aborted) return;
479
- const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-35TM3ZR3.ts");
479
+ const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-AAEAYX57.ts");
480
480
  if (signal.aborted || attention.current() !== current) return;
481
481
  await dispatchManagerResult(
482
482
  ctx,
@@ -755,11 +755,11 @@ function createStartupCheck(loaders, attention, timeoutMs = 3e4) {
755
755
  // src/sync/sync-loaders.ts
756
756
  function createSyncLoaders(dependencies) {
757
757
  const loaders = {
758
- inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import("./chunks/sync-inspection-RLIHUYDV.ts"))),
758
+ inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import("./chunks/sync-inspection-K45QVLHB.ts"))),
759
759
  setupSwitch: cachedModuleLoader(dependencies.loadSetupSwitch ?? (() => import("./chunks/setup-switch-KTUWL6DE.ts"))),
760
760
  snapshot: cachedModuleLoader(dependencies.loadSnapshot ?? (() => import("./chunks/snapshot-THS5EY2L.ts"))),
761
761
  syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import("./chunks/sync-state-CFL4RRRB.ts"))),
762
- operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-RBUOUJWL.ts")))
762
+ operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-Y27F5T7W.ts")))
763
763
  };
764
764
  return loaders;
765
765
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-sync",
3
- "version": "0.53.0",
3
+ "version": "0.53.1",
4
4
  "description": "Pi extension that syncs Pi configuration through Git, WebDAV, Cloudflare R2, or S3-compatible storage.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,7 @@ export class S3SyncBackend implements SyncBackend {
29
29
  readonly destination: string;
30
30
  readonly capability = "conditional-required" as const;
31
31
  private readonly checksums = new Map<string, string>();
32
+ private recoveryHead?: { current: string; legacy: string };
32
33
 
33
34
  constructor(
34
35
  private readonly config: ResolvedS3Backend,
@@ -43,7 +44,18 @@ export class S3SyncBackend implements SyncBackend {
43
44
  return left === right;
44
45
  }
45
46
 
47
+ matchesUncommittedRecoveryHead(current: RemoteHead, recorded: RemoteHead) {
48
+ return (
49
+ this.sameRevision(current.revision, recorded.revision) ||
50
+ (current.snapshotId === recorded.snapshotId &&
51
+ current.snapshotRef === recorded.snapshotRef &&
52
+ current.revision === this.recoveryHead?.current &&
53
+ recorded.revision === this.recoveryHead?.legacy)
54
+ );
55
+ }
56
+
46
57
  async readHead(signal?: AbortSignal): Promise<RemoteHead | undefined> {
58
+ this.recoveryHead = undefined;
47
59
  const object = await new S3Client(this.config, signal).getJson<LatestPointer>(latestKey(this.config));
48
60
  throwIfAborted(signal);
49
61
  if (object.missing) return undefined;
@@ -53,7 +65,16 @@ export class S3SyncBackend implements SyncBackend {
53
65
  this.config.destination.namespace,
54
66
  );
55
67
  this.registerChecksum(pointer.snapshot, pointer.sha256);
56
- return remoteHead(pointer, this.identity, object.etag);
68
+ const head = remoteHead(pointer, this.identity, object.etag);
69
+ if (object.etag && /^"[^"\r\n]+"$/u.test(object.etag)) {
70
+ // Old clients hashed R2's compression-weakened ETag. Bind the alias to the
71
+ // exact current pointer and strong ETag, solely for inactive-journal retirement.
72
+ this.recoveryHead = {
73
+ current: head.revision,
74
+ legacy: remoteHead(pointer, this.identity, `W/${object.etag}`).revision,
75
+ };
76
+ }
77
+ return head;
57
78
  }
58
79
 
59
80
  async readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot> {
@@ -188,7 +188,9 @@ export class S3Client {
188
188
  method,
189
189
  url,
190
190
  body,
191
- extraHeaders,
191
+ // Transfer compression can weaken an object's ETag (R2 JSON responses).
192
+ // Read the original representation for exact checksums and strong If-Match tokens.
193
+ extraHeaders: method === "GET" ? { ...extraHeaders, "accept-encoding": "identity" } : extraHeaders,
192
194
  accessKeyId: this.config.profile.accessKeyId,
193
195
  secretAccessKey: this.config.profile.secretAccessKey,
194
196
  sessionToken,
@@ -52,6 +52,8 @@ export interface SyncBackend {
52
52
  readonly capability: PublicationCapability;
53
53
  /** Compare opaque revisions produced by this backend identity. */
54
54
  sameRevision(left: string, right: string): boolean;
55
+ /** Recovery-only equivalence for retiring an inactive candidate; never authorizes publication or apply. */
56
+ matchesUncommittedRecoveryHead?(current: RemoteHead, recorded: RemoteHead): boolean;
55
57
  readHead(signal?: AbortSignal): Promise<RemoteHead | undefined>;
56
58
  readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot>;
57
59
  publishSnapshot(