@narumitw/pi-sync 0.52.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/chunks/{chunk-7D6RDCVC.ts → chunk-BX3K7YW7.ts} +2 -2
- package/dist/chunks/{chunk-QHSQCK7L.ts → chunk-FHNISD7A.ts} +2 -2
- package/dist/chunks/{chunk-2CBF6SMT.ts → chunk-I5IG4WV4.ts} +2 -2
- package/dist/chunks/{chunk-WKPX2SIC.ts → chunk-NGIJQSRE.ts} +2 -2
- package/dist/chunks/{chunk-NLS2NV3K.ts → chunk-QTCGWQP3.ts} +2 -2
- package/dist/chunks/{conflict-review-4WPGCXFB.ts → conflict-review-NK5CM6UA.ts} +3 -3
- package/dist/chunks/{manager-result-dispatcher-VIDT3QBU.ts → manager-result-dispatcher-35TM3ZR3.ts} +4 -4
- package/dist/chunks/{manager-ui-HSMHS3OK.ts → manager-ui-6AYMNXUP.ts} +7 -7
- package/dist/chunks/{remote-selection-ui-SMF5CLFO.ts → remote-selection-ui-BB5SX6P5.ts} +3 -3
- package/dist/chunks/{s3-backend-DJ2NT3SZ.ts → s3-backend-O2AS3TH6.ts} +99 -9
- package/dist/chunks/s3-backend-O2AS3TH6.ts.map +7 -0
- package/dist/chunks/{sync-inspection-VBJF2FWC.ts → sync-inspection-RLIHUYDV.ts} +3 -3
- package/dist/chunks/{sync-operations-RHNECOOG.ts → sync-operations-RBUOUJWL.ts} +4 -4
- package/dist/index.ts +5 -5
- package/package.json +1 -1
- package/src/backends/s3/s3-backend.ts +23 -5
- package/src/backends/s3/s3-client.ts +33 -6
- package/src/backends/s3/s3-conditions.ts +67 -0
- package/dist/chunks/s3-backend-DJ2NT3SZ.ts.map +0 -7
- /package/dist/chunks/{chunk-7D6RDCVC.ts.map → chunk-BX3K7YW7.ts.map} +0 -0
- /package/dist/chunks/{chunk-QHSQCK7L.ts.map → chunk-FHNISD7A.ts.map} +0 -0
- /package/dist/chunks/{chunk-2CBF6SMT.ts.map → chunk-I5IG4WV4.ts.map} +0 -0
- /package/dist/chunks/{chunk-WKPX2SIC.ts.map → chunk-NGIJQSRE.ts.map} +0 -0
- /package/dist/chunks/{chunk-NLS2NV3K.ts.map → chunk-QTCGWQP3.ts.map} +0 -0
- /package/dist/chunks/{conflict-review-4WPGCXFB.ts.map → conflict-review-NK5CM6UA.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-VIDT3QBU.ts.map → manager-result-dispatcher-35TM3ZR3.ts.map} +0 -0
- /package/dist/chunks/{manager-ui-HSMHS3OK.ts.map → manager-ui-6AYMNXUP.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-SMF5CLFO.ts.map → remote-selection-ui-BB5SX6P5.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-VBJF2FWC.ts.map → sync-inspection-RLIHUYDV.ts.map} +0 -0
- /package/dist/chunks/{sync-operations-RHNECOOG.ts.map → sync-operations-RBUOUJWL.ts.map} +0 -0
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ Shutdown behavior is unchanged: when **Automatic sync** is On and sessions are i
|
|
|
166
166
|
|
|
167
167
|
**Settings → Automatic transfer at startup** controls the separate per-setup `sync.automaticTransfer` boolean, which defaults to `false`. It authorizes one conflict-free startup transfer at a validated idle boundary in TUI/RPC, including session replacement and `/reload`. Busy startup waits for `agent_settled`; foreground commands, a new agent run, replacement, and shutdown cancel and drain owned work. Changing the setting affects authorization immediately; enabling it schedules the next attempt on the next session start. There is no watcher or polling loop.
|
|
168
168
|
|
|
169
|
-
A baseline and matching authoritative content list are required. First-source choice, changed selection, missing established remote, secrets, and real conflicts remain review barriers. Automatic transfer requires conditional or lease-protected publication: Git
|
|
169
|
+
A baseline and matching authoritative content list are required. First-source choice, changed selection, missing established remote, secrets, and real conflicts remain review barriers. Automatic transfer requires conditional or lease-protected publication: Git, verified WebDAV, and R2/S3 with verified conditional writes are supported. The existing **Automatic sync** setting and shutdown policy are unchanged when this new opt-in is absent.
|
|
170
170
|
|
|
171
171
|
Successful transfers show one summary. They do not reload resources, activate extension code, or rewrite model context. After local resources change, reload or restart when ready; newly pulled conversations still require explicit resume. Headless print/JSON skip this new startup feature. Reviewed directional settings/session-root transitions record both roots and verified settings evidence so interrupted apply can recover before or after settings installation. Older pending journals without transition evidence retain their original root-ownership checks and may require reviewed recovery; preserve their backups rather than downgrading. See [merge safety and recovery](./docs/merge-implementation-audit.md) for journal boundaries, external-writer limits, and downgrade guidance.
|
|
172
172
|
|
|
@@ -264,7 +264,7 @@ Review [Settings](#-settings) for included-content privacy and [Manager, conflic
|
|
|
264
264
|
| --- | --- | --- | --- |
|
|
265
265
|
| Git | Exact expected-ref lease | Existing SSH/configured credential helper | `<branch>:<storage.path>` |
|
|
266
266
|
| WebDAV | Verified strong conditional requests | Private settings username/app password | `<url>/<storage.path>` |
|
|
267
|
-
| R2/S3 |
|
|
267
|
+
| R2/S3 | Verified strong conditional requests | Private settings credentials | `<bucket>/<storage.path>` |
|
|
268
268
|
|
|
269
269
|
Git requires Git 2.30 or newer and a SHA-1-format remote repository.
|
|
270
270
|
HTTPS userinfo, URL passwords, local paths, `file`, `git`, `ext`, and remote-helper transports are rejected.
|
|
@@ -275,8 +275,7 @@ WebDAV requires HTTPS except loopback tests.
|
|
|
275
275
|
URL credentials, query strings, fragments, unsafe redirects, weak/missing ETags, and ignored conditional headers fail closed.
|
|
276
276
|
`/sync doctor` verifies collection and conditional-write behavior with an isolated probe, then repairs a missing active-snapshot history entry.
|
|
277
277
|
|
|
278
|
-
S3/R2
|
|
279
|
-
Unlike Git/WebDAV, generic S3 does not provide an atomic compare-and-swap for `latest.json`; status review remains important for simultaneous writers.
|
|
278
|
+
S3/R2 publishes `latest.json` and history using ETag `If-Match`, or `If-None-Match: *` for first creation, then verifies the active pointer. Before each publication, an isolated `.pi-sync-probes/<random-id>` object verifies that the server rejects stale ETags and create-only writes to existing objects and rotates strong ETags. Credentials must allow read, write, and delete under the selected storage path; the probe is deleted even after cancellation. Missing/weak ETags, ignored conditions, and probe or cleanup failures stop publication. A failed cleanup reports the probe key for manual removal; bucket versioning may retain probe versions. `/sync doctor` remains read-only for S3/R2 and does not verify write support.
|
|
280
279
|
|
|
281
280
|
Snapshot recovery is guarded: new journals record before/after images, older journals are retired only when their unchanged preimages are provable, and unrecognized bytes remain untouched for manual review. Current-session targets are never restored during startup. Preserve a blocked transaction and its backup, close Pi, and review the private evidence before restoring selected paths; malformed/unsupported journals are not disposable. Pi may have loaded resources before lifecycle recovery, so reload or restart explicitly after recovery if loaded resources need to match the restored files.
|
|
282
281
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-BDB4IDM3.ts";
|
|
7
7
|
import {
|
|
8
8
|
createSyncBackend
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FHNISD7A.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-
|
|
50
|
+
//# sourceMappingURL=chunk-BX3K7YW7.ts.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
var createSyncBackend = async (config) => {
|
|
6
6
|
switch (config.backend.type) {
|
|
7
7
|
case "s3": {
|
|
8
|
-
const { S3SyncBackend } = await import("./s3-backend-
|
|
8
|
+
const { S3SyncBackend } = await import("./s3-backend-O2AS3TH6.ts");
|
|
9
9
|
return new S3SyncBackend(config.backend);
|
|
10
10
|
}
|
|
11
11
|
case "webdav": {
|
|
@@ -22,4 +22,4 @@ var createSyncBackend = async (config) => {
|
|
|
22
22
|
export {
|
|
23
23
|
createSyncBackend
|
|
24
24
|
};
|
|
25
|
-
//# sourceMappingURL=chunk-
|
|
25
|
+
//# sourceMappingURL=chunk-FHNISD7A.ts.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-V6VZ5TLG.ts";
|
|
6
6
|
import {
|
|
7
7
|
createSyncBackend
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FHNISD7A.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-
|
|
606
|
+
//# sourceMappingURL=chunk-I5IG4WV4.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
runCancellableOperation,
|
|
5
5
|
showRemoteSelectionReview
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-I5IG4WV4.ts";
|
|
7
7
|
import {
|
|
8
8
|
safeTerminalText
|
|
9
9
|
} from "./chunk-AMJRUXFT.ts";
|
|
@@ -206,4 +206,4 @@ async function dispatchManagerResult(ctx, initialResult, origin, runRoute, signa
|
|
|
206
206
|
export {
|
|
207
207
|
dispatchManagerResult
|
|
208
208
|
};
|
|
209
|
-
//# sourceMappingURL=chunk-
|
|
209
|
+
//# sourceMappingURL=chunk-NGIJQSRE.ts.map
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./chunk-V6VZ5TLG.ts";
|
|
26
26
|
import {
|
|
27
27
|
createSyncBackend
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FHNISD7A.ts";
|
|
29
29
|
import {
|
|
30
30
|
applySnapshotTransaction,
|
|
31
31
|
recoverPendingSnapshotTransactions,
|
|
@@ -1743,4 +1743,4 @@ export {
|
|
|
1743
1743
|
createSyncDecision,
|
|
1744
1744
|
mergeSync
|
|
1745
1745
|
};
|
|
1746
|
-
//# sourceMappingURL=chunk-
|
|
1746
|
+
//# sourceMappingURL=chunk-QTCGWQP3.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
confirmMergeReview,
|
|
5
5
|
mergeSync
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QTCGWQP3.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-
|
|
15
|
+
} from "./chunk-FHNISD7A.ts";
|
|
16
16
|
import "./chunk-ANWNQIR7.ts";
|
|
17
17
|
import {
|
|
18
18
|
captureMutationOwner
|
|
@@ -172,4 +172,4 @@ async function showConflicts(ctx, options, factory = createSyncBackend) {
|
|
|
172
172
|
export {
|
|
173
173
|
showConflicts
|
|
174
174
|
};
|
|
175
|
-
//# sourceMappingURL=conflict-review-
|
|
175
|
+
//# sourceMappingURL=conflict-review-NK5CM6UA.ts.map
|
package/dist/chunks/{manager-result-dispatcher-VIDT3QBU.ts → manager-result-dispatcher-35TM3ZR3.ts}
RENAMED
|
@@ -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-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-NGIJQSRE.ts";
|
|
6
|
+
import "./chunk-I5IG4WV4.ts";
|
|
7
7
|
import "./chunk-V6VZ5TLG.ts";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-FHNISD7A.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-
|
|
23
|
+
//# sourceMappingURL=manager-result-dispatcher-35TM3ZR3.ts.map
|
|
@@ -23,12 +23,12 @@ import {
|
|
|
23
23
|
} from "./chunk-4SBTAVFL.ts";
|
|
24
24
|
import {
|
|
25
25
|
dispatchManagerResult
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-NGIJQSRE.ts";
|
|
27
27
|
import {
|
|
28
28
|
runCancellableOperation
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-I5IG4WV4.ts";
|
|
30
30
|
import "./chunk-V6VZ5TLG.ts";
|
|
31
|
-
import "./chunk-
|
|
31
|
+
import "./chunk-FHNISD7A.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-
|
|
175
|
+
const { showRemoteSelectionReview } = await import("./remote-selection-ui-BB5SX6P5.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-
|
|
911
|
+
const { showRemoteSelectionReview } = await import("./remote-selection-ui-BB5SX6P5.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-
|
|
1566
|
+
const { showRemoteSelectionReview } = await import("./remote-selection-ui-BB5SX6P5.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-
|
|
1732
|
+
//# sourceMappingURL=manager-ui-6AYMNXUP.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-
|
|
5
|
+
} from "./chunk-I5IG4WV4.ts";
|
|
6
6
|
import "./chunk-V6VZ5TLG.ts";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-FHNISD7A.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-
|
|
22
|
+
//# sourceMappingURL=remote-selection-ui-BB5SX6P5.ts.map
|
|
@@ -143,16 +143,21 @@ var S3Client = class {
|
|
|
143
143
|
}
|
|
144
144
|
throw lastError;
|
|
145
145
|
}
|
|
146
|
-
async putJson(key, value) {
|
|
146
|
+
async putJson(key, value, options = {}) {
|
|
147
147
|
const body = Buffer.from(JSON.stringify(value, null, " "), "utf8");
|
|
148
|
-
await this.putBuffer(key, body, "application/json");
|
|
148
|
+
await this.putBuffer(key, body, "application/json", options);
|
|
149
149
|
}
|
|
150
150
|
async putBuffer(key, body, contentType, options = {}) {
|
|
151
151
|
const headers = { "content-type": contentType };
|
|
152
152
|
if (options.ifAbsent) headers["if-none-match"] = "*";
|
|
153
|
+
if (options.ifMatch) headers["if-match"] = options.ifMatch;
|
|
153
154
|
const response = await this.request("PUT", key, body, headers);
|
|
154
|
-
if (options.ifAbsent && response.status === 412) {
|
|
155
|
-
|
|
155
|
+
if ((options.ifAbsent || options.ifMatch) && (response.status === 412 || response.status === 409)) {
|
|
156
|
+
await response.body?.cancel();
|
|
157
|
+
if (options.ifAbsent && response.status === 412) throw new S3ObjectAlreadyExistsError(key);
|
|
158
|
+
if (options.ifAbsent || options.ifMatch) {
|
|
159
|
+
throw new S3HttpError(`S3 conditional PUT rejected (${response.status}).`, response.status);
|
|
160
|
+
}
|
|
156
161
|
}
|
|
157
162
|
if (!response.ok) {
|
|
158
163
|
throw new S3HttpError(
|
|
@@ -160,6 +165,17 @@ var S3Client = class {
|
|
|
160
165
|
response.status
|
|
161
166
|
);
|
|
162
167
|
}
|
|
168
|
+
await response.body?.cancel();
|
|
169
|
+
}
|
|
170
|
+
async delete(key) {
|
|
171
|
+
const response = await this.request("DELETE", key);
|
|
172
|
+
if (!response.ok && response.status !== 404) {
|
|
173
|
+
throw new S3HttpError(
|
|
174
|
+
`S3 DELETE failed (${response.status}): ${await this.readErrorText(response)}`,
|
|
175
|
+
response.status
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
await response.body?.cancel();
|
|
163
179
|
}
|
|
164
180
|
async request(method, key, body, extraHeaders = {}) {
|
|
165
181
|
const url = new URL(this.endpoint.toString());
|
|
@@ -331,6 +347,66 @@ function isSecurityTokenInvalidArgument(text) {
|
|
|
331
347
|
return text.includes("<Code>InvalidArgument</Code>") && text.includes("<Message>X-Amz-Security-Token</Message>");
|
|
332
348
|
}
|
|
333
349
|
|
|
350
|
+
// src/backends/s3/s3-conditions.ts
|
|
351
|
+
import { randomUUID } from "node:crypto";
|
|
352
|
+
function requireS3Etag(value) {
|
|
353
|
+
if (!value || !/^"[^"\r\n]+"$/u.test(value)) throw new Error("S3 conditional publication requires a strong ETag.");
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
356
|
+
async function verifyS3Conditions(config, signal) {
|
|
357
|
+
const key = posixJoin(
|
|
358
|
+
config.destination.prefix === "./" ? "" : config.destination.prefix,
|
|
359
|
+
".pi-sync-probes",
|
|
360
|
+
randomUUID()
|
|
361
|
+
);
|
|
362
|
+
const client = new S3Client(config, signal);
|
|
363
|
+
let failure;
|
|
364
|
+
let attempted = false;
|
|
365
|
+
try {
|
|
366
|
+
signal?.throwIfAborted();
|
|
367
|
+
attempted = true;
|
|
368
|
+
await client.putBuffer(key, Buffer.from("first"), "text/plain", { ifAbsent: true });
|
|
369
|
+
const first = await client.getBuffer(key);
|
|
370
|
+
const etag = requireS3Etag(first.etag);
|
|
371
|
+
if (!first.value?.equals(Buffer.from("first"))) throw new Error("S3 conditional probe read did not match.");
|
|
372
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifAbsent: true }));
|
|
373
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifMatch: '"pi-sync-stale"' }));
|
|
374
|
+
const unchanged = await client.getBuffer(key);
|
|
375
|
+
if (!unchanged.value?.equals(first.value) || unchanged.etag !== etag)
|
|
376
|
+
throw new Error("S3 failed precondition changed the probe.");
|
|
377
|
+
await client.putBuffer(key, Buffer.from("second"), "text/plain", { ifMatch: etag });
|
|
378
|
+
const second = await client.getBuffer(key);
|
|
379
|
+
if (!second.value?.equals(Buffer.from("second")) || requireS3Etag(second.etag) === etag) {
|
|
380
|
+
throw new Error("S3 conditional write did not rotate the probe ETag.");
|
|
381
|
+
}
|
|
382
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifMatch: etag }));
|
|
383
|
+
const final = await client.getBuffer(key);
|
|
384
|
+
if (!final.value?.equals(second.value) || final.etag !== second.etag)
|
|
385
|
+
throw new Error("S3 stale ETag changed the probe.");
|
|
386
|
+
signal?.throwIfAborted();
|
|
387
|
+
} catch (error) {
|
|
388
|
+
failure = error;
|
|
389
|
+
}
|
|
390
|
+
if (attempted) {
|
|
391
|
+
try {
|
|
392
|
+
await new S3Client(config, AbortSignal.timeout(1e4)).delete(key);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
throw new Error(`S3 probe cleanup failed; remove ${key}.`, { cause: failure ?? error });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (failure) throw failure;
|
|
398
|
+
signal?.throwIfAborted();
|
|
399
|
+
}
|
|
400
|
+
async function expectRejected(operation) {
|
|
401
|
+
try {
|
|
402
|
+
await operation;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (error instanceof S3ObjectAlreadyExistsError || error instanceof S3HttpError && error.status === 412) return;
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
throw new Error("S3 server ignored a write precondition; publication stopped for safety.");
|
|
408
|
+
}
|
|
409
|
+
|
|
334
410
|
// src/backends/s3/s3-backend.ts
|
|
335
411
|
var VERSION = 1;
|
|
336
412
|
var POST_COMMIT_TIMEOUT_MS = 3e4;
|
|
@@ -346,7 +422,7 @@ var S3SyncBackend = class {
|
|
|
346
422
|
postCommitTimeoutMs;
|
|
347
423
|
identity;
|
|
348
424
|
destination;
|
|
349
|
-
capability = "
|
|
425
|
+
capability = "conditional-required";
|
|
350
426
|
checksums = /* @__PURE__ */ new Map();
|
|
351
427
|
sameRevision(left, right) {
|
|
352
428
|
return left === right;
|
|
@@ -381,6 +457,8 @@ var S3SyncBackend = class {
|
|
|
381
457
|
async publishSnapshot(snapshot, expected, options = {}) {
|
|
382
458
|
throwIfAborted(options.signal);
|
|
383
459
|
assertSnapshotIdentity(snapshot, this.config.destination.namespace);
|
|
460
|
+
await verifyS3Conditions(this.config, options.signal);
|
|
461
|
+
throwIfAborted(options.signal);
|
|
384
462
|
const stagedKey = snapshotKey(this.config, snapshot.id);
|
|
385
463
|
const encoded = await encodeSnapshot(snapshot);
|
|
386
464
|
throwIfAborted(options.signal);
|
|
@@ -416,12 +494,18 @@ var S3SyncBackend = class {
|
|
|
416
494
|
currentHead: current
|
|
417
495
|
});
|
|
418
496
|
}
|
|
497
|
+
const condition = currentObject.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(currentObject.etag) };
|
|
419
498
|
throwIfAborted(options.signal);
|
|
420
499
|
options.onCommit?.();
|
|
421
500
|
const commitClient = new S3Client(this.config, AbortSignal.timeout(this.postCommitTimeoutMs));
|
|
422
501
|
try {
|
|
423
|
-
await commitClient.putJson(latestKey(this.config), pointer);
|
|
502
|
+
await commitClient.putJson(latestKey(this.config), pointer, condition);
|
|
424
503
|
} catch (error) {
|
|
504
|
+
if (error instanceof S3ObjectAlreadyExistsError || error instanceof S3HttpError && (error.status === 412 || error.status === 409)) {
|
|
505
|
+
throw new SyncBackendConflictError("Remote conditional publication was rejected; reread and retry.", {
|
|
506
|
+
cause: error
|
|
507
|
+
});
|
|
508
|
+
}
|
|
425
509
|
throw new SyncBackendPublicationOutcomeUnknownError(
|
|
426
510
|
`Remote publication outcome is unknown: ${errorMessage2(error)}`,
|
|
427
511
|
{ cause: error }
|
|
@@ -543,8 +627,14 @@ var S3SyncBackend = class {
|
|
|
543
627
|
async updateHistory(client, pointer) {
|
|
544
628
|
const object = await client.getJson(historyKey(this.config));
|
|
545
629
|
const snapshots = object.missing ? [] : requireHistory(object.value, this.config.destination.namespace);
|
|
546
|
-
const
|
|
547
|
-
|
|
630
|
+
const existing = snapshots.find((snapshot) => snapshot.snapshot === pointer.snapshot);
|
|
631
|
+
if (existing) {
|
|
632
|
+
if (!samePointer(existing, pointer)) throw new Error("Remote history rebound an immutable snapshot reference.");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const next = [...snapshots, pointer].slice(-100);
|
|
636
|
+
const condition = object.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(object.etag) };
|
|
637
|
+
await client.putJson(historyKey(this.config), { version: VERSION, snapshots: next }, condition);
|
|
548
638
|
}
|
|
549
639
|
};
|
|
550
640
|
function latestKey(config) {
|
|
@@ -731,4 +821,4 @@ export {
|
|
|
731
821
|
snapshotKey,
|
|
732
822
|
storageRoot
|
|
733
823
|
};
|
|
734
|
-
//# sourceMappingURL=s3-backend-
|
|
824
|
+
//# sourceMappingURL=s3-backend-O2AS3TH6.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\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 async readHead(signal?: AbortSignal): Promise<RemoteHead | 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 return remoteHead(pointer, this.identity, object.etag);\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 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-
|
|
5
|
+
} from "./chunk-BX3K7YW7.ts";
|
|
6
6
|
import "./chunk-BDB4IDM3.ts";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-FHNISD7A.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-
|
|
20
|
+
//# sourceMappingURL=sync-inspection-RLIHUYDV.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-
|
|
5
|
+
} from "./chunk-BX3K7YW7.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-
|
|
17
|
+
} from "./chunk-QTCGWQP3.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-
|
|
44
|
+
} from "./chunk-FHNISD7A.ts";
|
|
45
45
|
import "./chunk-ANWNQIR7.ts";
|
|
46
46
|
import {
|
|
47
47
|
backupLocal,
|
|
@@ -876,4 +876,4 @@ export {
|
|
|
876
876
|
status,
|
|
877
877
|
syncBoth
|
|
878
878
|
};
|
|
879
|
-
//# sourceMappingURL=sync-operations-
|
|
879
|
+
//# sourceMappingURL=sync-operations-RBUOUJWL.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-
|
|
179
|
+
const { showConflicts } = await import("./chunks/conflict-review-NK5CM6UA.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-
|
|
379
|
+
const { showSyncManager } = await import("./chunks/manager-ui-6AYMNXUP.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-
|
|
479
|
+
const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-35TM3ZR3.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-
|
|
758
|
+
inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import("./chunks/sync-inspection-RLIHUYDV.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-
|
|
762
|
+
operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-RBUOUJWL.ts")))
|
|
763
763
|
};
|
|
764
764
|
return loaders;
|
|
765
765
|
}
|
package/package.json
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
SyncBackendConflictError,
|
|
19
19
|
SyncBackendPublicationOutcomeUnknownError,
|
|
20
20
|
} from "../sync-backend.js";
|
|
21
|
-
import { S3Client, S3ObjectAlreadyExistsError } from "./s3-client.js";
|
|
21
|
+
import { S3Client, S3HttpError, S3ObjectAlreadyExistsError } from "./s3-client.js";
|
|
22
|
+
import { requireS3Etag, verifyS3Conditions } from "./s3-conditions.js";
|
|
22
23
|
|
|
23
24
|
const VERSION = 1;
|
|
24
25
|
const POST_COMMIT_TIMEOUT_MS = 30_000;
|
|
@@ -26,7 +27,7 @@ const POST_COMMIT_TIMEOUT_MS = 30_000;
|
|
|
26
27
|
export class S3SyncBackend implements SyncBackend {
|
|
27
28
|
readonly identity: string;
|
|
28
29
|
readonly destination: string;
|
|
29
|
-
readonly capability = "
|
|
30
|
+
readonly capability = "conditional-required" as const;
|
|
30
31
|
private readonly checksums = new Map<string, string>();
|
|
31
32
|
|
|
32
33
|
constructor(
|
|
@@ -78,6 +79,8 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
78
79
|
): Promise<PublishSnapshotResult> {
|
|
79
80
|
throwIfAborted(options.signal);
|
|
80
81
|
assertSnapshotIdentity(snapshot, this.config.destination.namespace);
|
|
82
|
+
await verifyS3Conditions(this.config, options.signal);
|
|
83
|
+
throwIfAborted(options.signal);
|
|
81
84
|
const stagedKey = snapshotKey(this.config, snapshot.id);
|
|
82
85
|
const encoded = await encodeSnapshot(snapshot);
|
|
83
86
|
throwIfAborted(options.signal);
|
|
@@ -113,6 +116,7 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
113
116
|
currentHead: current,
|
|
114
117
|
});
|
|
115
118
|
}
|
|
119
|
+
const condition = currentObject.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(currentObject.etag) };
|
|
116
120
|
throwIfAborted(options.signal);
|
|
117
121
|
options.onCommit?.();
|
|
118
122
|
|
|
@@ -120,8 +124,16 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
120
124
|
// verification to a user-cancellation signal after the boundary begins.
|
|
121
125
|
const commitClient = new S3Client(this.config, AbortSignal.timeout(this.postCommitTimeoutMs));
|
|
122
126
|
try {
|
|
123
|
-
await commitClient.putJson(latestKey(this.config), pointer);
|
|
127
|
+
await commitClient.putJson(latestKey(this.config), pointer, condition);
|
|
124
128
|
} catch (error) {
|
|
129
|
+
if (
|
|
130
|
+
error instanceof S3ObjectAlreadyExistsError ||
|
|
131
|
+
(error instanceof S3HttpError && (error.status === 412 || error.status === 409))
|
|
132
|
+
) {
|
|
133
|
+
throw new SyncBackendConflictError("Remote conditional publication was rejected; reread and retry.", {
|
|
134
|
+
cause: error,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
125
137
|
throw new SyncBackendPublicationOutcomeUnknownError(
|
|
126
138
|
`Remote publication outcome is unknown: ${errorMessage(error)}`,
|
|
127
139
|
{ cause: error },
|
|
@@ -256,8 +268,14 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
256
268
|
private async updateHistory(client: S3Client, pointer: LatestPointer) {
|
|
257
269
|
const object = await client.getJson<{ version: number; snapshots: LatestPointer[] }>(historyKey(this.config));
|
|
258
270
|
const snapshots = object.missing ? [] : requireHistory(object.value, this.config.destination.namespace);
|
|
259
|
-
const
|
|
260
|
-
|
|
271
|
+
const existing = snapshots.find((snapshot) => snapshot.snapshot === pointer.snapshot);
|
|
272
|
+
if (existing) {
|
|
273
|
+
if (!samePointer(existing, pointer)) throw new Error("Remote history rebound an immutable snapshot reference.");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const next = [...snapshots, pointer].slice(-100);
|
|
277
|
+
const condition = object.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(object.etag) };
|
|
278
|
+
await client.putJson(historyKey(this.config), { version: VERSION, snapshots: next }, condition);
|
|
261
279
|
}
|
|
262
280
|
}
|
|
263
281
|
|
|
@@ -132,17 +132,27 @@ export class S3Client {
|
|
|
132
132
|
throw lastError;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
async putJson(key: string, value: unknown) {
|
|
135
|
+
async putJson(key: string, value: unknown, options: { ifAbsent?: boolean; ifMatch?: string } = {}) {
|
|
136
136
|
const body = Buffer.from(JSON.stringify(value, null, "\t"), "utf8");
|
|
137
|
-
await this.putBuffer(key, body, "application/json");
|
|
137
|
+
await this.putBuffer(key, body, "application/json", options);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
async putBuffer(
|
|
140
|
+
async putBuffer(
|
|
141
|
+
key: string,
|
|
142
|
+
body: Buffer,
|
|
143
|
+
contentType: string,
|
|
144
|
+
options: { ifAbsent?: boolean; ifMatch?: string } = {},
|
|
145
|
+
) {
|
|
141
146
|
const headers: Record<string, string> = { "content-type": contentType };
|
|
142
147
|
if (options.ifAbsent) headers["if-none-match"] = "*";
|
|
148
|
+
if (options.ifMatch) headers["if-match"] = options.ifMatch;
|
|
143
149
|
const response = await this.request("PUT", key, body, headers);
|
|
144
|
-
if (options.ifAbsent && response.status === 412) {
|
|
145
|
-
|
|
150
|
+
if ((options.ifAbsent || options.ifMatch) && (response.status === 412 || response.status === 409)) {
|
|
151
|
+
await response.body?.cancel();
|
|
152
|
+
if (options.ifAbsent && response.status === 412) throw new S3ObjectAlreadyExistsError(key);
|
|
153
|
+
if (options.ifAbsent || options.ifMatch) {
|
|
154
|
+
throw new S3HttpError(`S3 conditional PUT rejected (${response.status}).`, response.status);
|
|
155
|
+
}
|
|
146
156
|
}
|
|
147
157
|
if (!response.ok) {
|
|
148
158
|
throw new S3HttpError(
|
|
@@ -150,9 +160,26 @@ export class S3Client {
|
|
|
150
160
|
response.status,
|
|
151
161
|
);
|
|
152
162
|
}
|
|
163
|
+
await response.body?.cancel();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async delete(key: string) {
|
|
167
|
+
const response = await this.request("DELETE", key);
|
|
168
|
+
if (!response.ok && response.status !== 404) {
|
|
169
|
+
throw new S3HttpError(
|
|
170
|
+
`S3 DELETE failed (${response.status}): ${await this.readErrorText(response)}`,
|
|
171
|
+
response.status,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
await response.body?.cancel();
|
|
153
175
|
}
|
|
154
176
|
|
|
155
|
-
private async request(
|
|
177
|
+
private async request(
|
|
178
|
+
method: "GET" | "PUT" | "DELETE",
|
|
179
|
+
key: string,
|
|
180
|
+
body?: Buffer,
|
|
181
|
+
extraHeaders: Record<string, string> = {},
|
|
182
|
+
) {
|
|
156
183
|
const url = new URL(this.endpoint.toString());
|
|
157
184
|
url.pathname = posixJoin(url.pathname, this.config.destination.bucket, encodeKey(key));
|
|
158
185
|
const send = async (sessionToken: string | undefined) => {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { posixJoin } from "../../paths.js";
|
|
3
|
+
import type { ResolvedS3Backend } from "../backend-types.js";
|
|
4
|
+
import { S3Client, S3HttpError, S3ObjectAlreadyExistsError } from "./s3-client.js";
|
|
5
|
+
|
|
6
|
+
export function requireS3Etag(value: string | undefined) {
|
|
7
|
+
if (!value || !/^"[^"\r\n]+"$/u.test(value)) throw new Error("S3 conditional publication requires a strong ETag.");
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Never probe the active pointer or immutable snapshots; ignored conditions must not corrupt either. */
|
|
12
|
+
export async function verifyS3Conditions(config: ResolvedS3Backend, signal?: AbortSignal) {
|
|
13
|
+
const key = posixJoin(
|
|
14
|
+
config.destination.prefix === "./" ? "" : config.destination.prefix,
|
|
15
|
+
".pi-sync-probes",
|
|
16
|
+
randomUUID(),
|
|
17
|
+
);
|
|
18
|
+
const client = new S3Client(config, signal);
|
|
19
|
+
let failure: unknown;
|
|
20
|
+
let attempted = false;
|
|
21
|
+
try {
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
attempted = true;
|
|
24
|
+
await client.putBuffer(key, Buffer.from("first"), "text/plain", { ifAbsent: true });
|
|
25
|
+
const first = await client.getBuffer(key);
|
|
26
|
+
const etag = requireS3Etag(first.etag);
|
|
27
|
+
if (!first.value?.equals(Buffer.from("first"))) throw new Error("S3 conditional probe read did not match.");
|
|
28
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifAbsent: true }));
|
|
29
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifMatch: '"pi-sync-stale"' }));
|
|
30
|
+
const unchanged = await client.getBuffer(key);
|
|
31
|
+
if (!unchanged.value?.equals(first.value) || unchanged.etag !== etag)
|
|
32
|
+
throw new Error("S3 failed precondition changed the probe.");
|
|
33
|
+
await client.putBuffer(key, Buffer.from("second"), "text/plain", { ifMatch: etag });
|
|
34
|
+
const second = await client.getBuffer(key);
|
|
35
|
+
if (!second.value?.equals(Buffer.from("second")) || requireS3Etag(second.etag) === etag) {
|
|
36
|
+
throw new Error("S3 conditional write did not rotate the probe ETag.");
|
|
37
|
+
}
|
|
38
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifMatch: etag }));
|
|
39
|
+
const final = await client.getBuffer(key);
|
|
40
|
+
if (!final.value?.equals(second.value) || final.etag !== second.etag)
|
|
41
|
+
throw new Error("S3 stale ETag changed the probe.");
|
|
42
|
+
signal?.throwIfAborted();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
failure = error;
|
|
45
|
+
}
|
|
46
|
+
// Cleanup has its own bounded deadline even when the owning session was cancelled.
|
|
47
|
+
if (attempted) {
|
|
48
|
+
try {
|
|
49
|
+
await new S3Client(config, AbortSignal.timeout(10_000)).delete(key);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`S3 probe cleanup failed; remove ${key}.`, { cause: failure ?? error });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (failure) throw failure;
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function expectRejected(operation: Promise<void>) {
|
|
59
|
+
try {
|
|
60
|
+
await operation;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// 409 is retryable contention, not proof that a failed condition was enforced.
|
|
63
|
+
if (error instanceof S3ObjectAlreadyExistsError || (error instanceof S3HttpError && error.status === 412)) return;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
throw new Error("S3 server ignored a write precondition; publication stopped for safety.");
|
|
67
|
+
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/backends/s3/s3-backend.ts", "../../src/backends/s3/s3-client.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, S3ObjectAlreadyExistsError } from \"./s3-client.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 = \"read-check-write-verify\" as const;\n private readonly checksums = new Map<string, 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 async readHead(signal?: AbortSignal): Promise<RemoteHead | 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 return remoteHead(pointer, this.identity, object.etag);\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 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 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);\n } catch (error) {\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 next = [...snapshots.filter((snapshot) => snapshot.snapshot !== pointer.snapshot), pointer].slice(-100);\n await client.putJson(historyKey(this.config), { version: VERSION, snapshots: next });\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) {\n const body = Buffer.from(JSON.stringify(value, null, \"\\t\"), \"utf8\");\n await this.putBuffer(key, body, \"application/json\");\n }\n\n async putBuffer(key: string, body: Buffer, contentType: string, options: { ifAbsent?: boolean } = {}) {\n const headers: Record<string, string> = { \"content-type\": contentType };\n if (options.ifAbsent) headers[\"if-none-match\"] = \"*\";\n const response = await this.request(\"PUT\", key, body, headers);\n if (options.ifAbsent && response.status === 412) {\n throw new S3ObjectAlreadyExistsError(key);\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 }\n\n private async request(method: \"GET\" | \"PUT\", key: string, body?: Buffer, extraHeaders: Record<string, string> = {}) {\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 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"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["createHash", "sessionToken", "sha256", "errorMessage", "createHash"]
|
|
7
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/chunks/{remote-selection-ui-SMF5CLFO.ts.map → remote-selection-ui-BB5SX6P5.ts.map}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|