@run402/sdk 4.57.0 → 4.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/namespaces/gitvault.crypto.d.ts +22 -0
- package/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
- package/dist/namespaces/gitvault.crypto.js +14 -0
- package/dist/namespaces/gitvault.crypto.js.map +1 -1
- package/dist/node/gitvault-native-crypto.d.ts +19 -1
- package/dist/node/gitvault-native-crypto.d.ts.map +1 -1
- package/dist/node/gitvault-native-crypto.js +45 -2
- package/dist/node/gitvault-native-crypto.js.map +1 -1
- package/dist/node/gitvault-publication.d.ts +17 -0
- package/dist/node/gitvault-publication.d.ts.map +1 -1
- package/dist/node/gitvault-publication.js +115 -29
- package/dist/node/gitvault-publication.js.map +1 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +4 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -761,6 +761,22 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
761
761
|
async function getObjectsBytes(repoId, paths) {
|
|
762
762
|
if (paths.length === 0)
|
|
763
763
|
return [];
|
|
764
|
+
const targets = await presignObjectBatch(repoId, paths);
|
|
765
|
+
if (!targets)
|
|
766
|
+
return paths.map(() => null);
|
|
767
|
+
return mapBounded(targets, GITVAULT_TRANSPORT_CONCURRENCY, async (target, i) => {
|
|
768
|
+
if (!target)
|
|
769
|
+
return null;
|
|
770
|
+
const r = await fetchGitvaultObjectBytes(client, target);
|
|
771
|
+
if (r.status === 404)
|
|
772
|
+
return null;
|
|
773
|
+
if (!r.ok)
|
|
774
|
+
fail("GITVAULT_OBJECT_READ_FAILED", `object GET failed (HTTP ${r.status}) for ${paths[i]}`, "reading gitvault object", { path: paths[i], status: r.status });
|
|
775
|
+
return new Uint8Array(await r.arrayBuffer());
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
/** The shared presign step of the batched read: ONE `object-reads` POST; `null` means the not-found shapes `getObjects` maps to an all-absent result. */
|
|
779
|
+
async function presignObjectBatch(repoId, paths) {
|
|
764
780
|
const refs = paths.map((path) => {
|
|
765
781
|
const ref = gitvaultWireRefForPath(path);
|
|
766
782
|
if (!ref || ref.kind !== "object")
|
|
@@ -773,23 +789,67 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
773
789
|
}
|
|
774
790
|
catch (e) {
|
|
775
791
|
if (isRun402Error(e) && e.status === 404)
|
|
776
|
-
return
|
|
792
|
+
return null;
|
|
777
793
|
if (isRun402Error(e) && e.code === "RESOURCE_NOT_FOUND")
|
|
778
|
-
return
|
|
794
|
+
return null;
|
|
779
795
|
throw e;
|
|
780
796
|
}
|
|
781
797
|
const byLedgerId = new Map(presigned.reads.map((r) => [gitvaultLedgerId(r), r]));
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
798
|
+
return refs.map((r) => byLedgerId.get(gitvaultLedgerId(r)) ?? null);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Per-object settlement over the same batch (gitvault-pipelined-restore
|
|
802
|
+
* D2): identical presign + bounded GETs, but each index's promise settles
|
|
803
|
+
* when ITS bytes land. Failure semantics per index match `getObjects`'s
|
|
804
|
+
* per-element behavior (absent → null, a failed GET → the same
|
|
805
|
+
* GITVAULT_OBJECT_READ_FAILED); every promise is pre-marked handled so an
|
|
806
|
+
* abandoned tail never becomes an unhandled rejection.
|
|
807
|
+
*/
|
|
808
|
+
async function getObjectsSettledBytes(repoId, paths) {
|
|
809
|
+
if (paths.length === 0)
|
|
810
|
+
return [];
|
|
811
|
+
const targets = await presignObjectBatch(repoId, paths);
|
|
812
|
+
if (!targets)
|
|
813
|
+
return paths.map(() => Promise.resolve(null));
|
|
814
|
+
const deferreds = targets.map(() => {
|
|
815
|
+
let resolve;
|
|
816
|
+
let reject;
|
|
817
|
+
const promise = new Promise((res, rej) => {
|
|
818
|
+
resolve = res;
|
|
819
|
+
reject = rej;
|
|
820
|
+
});
|
|
821
|
+
void promise.catch(() => { });
|
|
822
|
+
return { promise, resolve, reject };
|
|
792
823
|
});
|
|
824
|
+
let next = 0;
|
|
825
|
+
const worker = async () => {
|
|
826
|
+
for (;;) {
|
|
827
|
+
const i = next;
|
|
828
|
+
next += 1;
|
|
829
|
+
if (i >= targets.length)
|
|
830
|
+
return;
|
|
831
|
+
try {
|
|
832
|
+
const target = targets[i];
|
|
833
|
+
if (!target) {
|
|
834
|
+
deferreds[i].resolve(null);
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
const r = await fetchGitvaultObjectBytes(client, target);
|
|
838
|
+
if (r.status === 404) {
|
|
839
|
+
deferreds[i].resolve(null);
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (!r.ok)
|
|
843
|
+
fail("GITVAULT_OBJECT_READ_FAILED", `object GET failed (HTTP ${r.status}) for ${paths[i]}`, "reading gitvault object", { path: paths[i], status: r.status });
|
|
844
|
+
deferreds[i].resolve(new Uint8Array(await r.arrayBuffer()));
|
|
845
|
+
}
|
|
846
|
+
catch (e) {
|
|
847
|
+
deferreds[i].reject(e);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
void Promise.all(Array.from({ length: Math.min(GITVAULT_TRANSPORT_CONCURRENCY, targets.length) }, () => worker()));
|
|
852
|
+
return deferreds.map((d) => d.promise);
|
|
793
853
|
}
|
|
794
854
|
/**
|
|
795
855
|
* `POST …/head-reads` — many generation-addressed heads' EXACT stored bytes
|
|
@@ -973,6 +1033,7 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
973
1033
|
},
|
|
974
1034
|
getObject: ({ repo_id, path }) => getObjectBytes(repo_id, path),
|
|
975
1035
|
getObjects: ({ repo_id, paths }) => getObjectsBytes(repo_id, paths),
|
|
1036
|
+
getObjectsSettled: ({ repo_id, paths }) => getObjectsSettledBytes(repo_id, paths),
|
|
976
1037
|
getHeads: ({ repo_id, generations }) => getHeadsBytes(repo_id, generations),
|
|
977
1038
|
async admitGenesis(request) {
|
|
978
1039
|
try {
|
|
@@ -3608,6 +3669,34 @@ export class GitvaultVault {
|
|
|
3608
3669
|
return hexToBytes(hex);
|
|
3609
3670
|
};
|
|
3610
3671
|
const first = heads[0];
|
|
3672
|
+
// gitvault-pipelined-restore: per-object settlement when the transport
|
|
3673
|
+
// offers it, the `getObjects` barrier otherwise — pipelining is a
|
|
3674
|
+
// wall-clock property, never a correctness dependency, so a transport
|
|
3675
|
+
// without the method reproduces today's serial-after-barrier behavior
|
|
3676
|
+
// exactly (each per-index promise settles when the whole batch does).
|
|
3677
|
+
const settled = async (paths) => {
|
|
3678
|
+
if (this.transport.getObjectsSettled)
|
|
3679
|
+
return this.transport.getObjectsSettled({ repo_id: this.repoId, paths });
|
|
3680
|
+
const all = this.transport.getObjects({ repo_id: this.repoId, paths });
|
|
3681
|
+
const perIndex = paths.map((_, i) => all.then((frames) => frames[i] ?? null));
|
|
3682
|
+
// Mark every derived promise handled — the consumer awaits them in
|
|
3683
|
+
// order and stops at the first failure, abandoning the tail.
|
|
3684
|
+
for (const p of perIndex)
|
|
3685
|
+
void p.catch(() => { });
|
|
3686
|
+
return perIndex;
|
|
3687
|
+
};
|
|
3688
|
+
// gitvault-pipelined-restore D3: the WAL entry list derives from the
|
|
3689
|
+
// already-verified head walk, so its batched download is INITIATED here —
|
|
3690
|
+
// before the checkpoint branch — and its (small, many) objects land while
|
|
3691
|
+
// the checkpoint downloads, decrypts, and indexes. APPLICATION of WAL
|
|
3692
|
+
// packs still begins only after the checkpoint class completes, in the
|
|
3693
|
+
// same chain order as always. Each entry decrypts under its OWN carrying
|
|
3694
|
+
// head's epoch (D194) — the flattened list keeps that pairing so a
|
|
3695
|
+
// rotation-spanning restore never reuses one head's epoch for another's
|
|
3696
|
+
// pack.
|
|
3697
|
+
const walEntries = heads.flatMap((h) => h.wal_entries.map((w) => ({ w, epoch: h.epoch })));
|
|
3698
|
+
const walFramesP = settled(walEntries.map(({ w }) => gitvaultPaths.wal(w.object_id)));
|
|
3699
|
+
void walFramesP.catch(() => { });
|
|
3611
3700
|
if (first.checkpoint) {
|
|
3612
3701
|
const claimBytes = await this.transport.getObject({ repo_id: this.repoId, path: gitvaultPaths.claimSet(first.checkpoint.claim_set.object_id) });
|
|
3613
3702
|
if (!claimBytes || sha256Hex(claimBytes) !== first.checkpoint.claim_set.stored_bytes_sha256)
|
|
@@ -3617,14 +3706,16 @@ export class GitvaultVault {
|
|
|
3617
3706
|
fail("CHECKPOINT_INCOMPLETE", "claim set signature fails", "restoring gitvault objects");
|
|
3618
3707
|
const manifest = await this.openCarrier("checkpoint_manifest", claimSet.manifest_receipt, gitvaultPaths.checkpointManifest(claimSet.manifest_receipt.object_id), writerKey, { epoch: first.epoch, k_repo: kRepoForEpoch(first.epoch) });
|
|
3619
3708
|
checkClaimSetEquality(claimSet, manifest, first.checkpoint.covers_through_generation);
|
|
3620
|
-
// Design D2: every checkpoint pack is
|
|
3621
|
-
// presign for all of them,
|
|
3622
|
-
//
|
|
3623
|
-
//
|
|
3624
|
-
|
|
3709
|
+
// Design D2 + gitvault-pipelined-restore D1: every checkpoint pack is
|
|
3710
|
+
// independent — one batched presign for all of them, applied via
|
|
3711
|
+
// index-pack strictly in manifest order, PIPELINED: apply(i) awaits
|
|
3712
|
+
// bytes(i), so decrypt/verify/index of an early pack overlaps the
|
|
3713
|
+
// later packs' downloads. Per-pack verification (AEAD open + plaintext
|
|
3714
|
+
// hash) still completes before any byte reaches git.
|
|
3715
|
+
const frames = await settled(manifest.packs.map((p) => gitvaultPaths.checkpointPack(p.object_id)));
|
|
3625
3716
|
for (let i = 0; i < manifest.packs.length; i++) {
|
|
3626
3717
|
const p = manifest.packs[i];
|
|
3627
|
-
const frame = frames[i] ?? null;
|
|
3718
|
+
const frame = (await frames[i]) ?? null;
|
|
3628
3719
|
if (!frame)
|
|
3629
3720
|
fail("CHECKPOINT_INCOMPLETE", `checkpoint pack ${p.object_id} absent`, "restoring gitvault objects");
|
|
3630
3721
|
const plain = openFrame({ k_obj: deriveObjectKey(kRepoForEpoch(first.epoch), this.repoId, first.epoch, "checkpoint_pack", p.object_id), repo_id: this.repoId, object_kind: "checkpoint_pack", object_id: p.object_id, epoch: first.epoch, frame, expected_ciphertext_sha256: p.ciphertext_sha256 });
|
|
@@ -3634,19 +3725,14 @@ export class GitvaultVault {
|
|
|
3634
3725
|
}
|
|
3635
3726
|
}
|
|
3636
3727
|
// Design D2: every WAL pack across every head in this restore's range is
|
|
3637
|
-
// independent — one batched presign for the whole set (
|
|
3638
|
-
//
|
|
3639
|
-
//
|
|
3640
|
-
//
|
|
3641
|
-
|
|
3642
|
-
// Each entry decrypts under its OWN carrying head's epoch (D194) — the
|
|
3643
|
-
// flattened list keeps that pairing so a rotation-spanning restore never
|
|
3644
|
-
// reuses one head's epoch for another's pack.
|
|
3645
|
-
const walEntries = heads.flatMap((h) => h.wal_entries.map((w) => ({ w, epoch: h.epoch })));
|
|
3646
|
-
const walFrames = await this.transport.getObjects({ repo_id: this.repoId, paths: walEntries.map(({ w }) => gitvaultPaths.wal(w.object_id)) });
|
|
3728
|
+
// independent — one batched presign for the whole set (initiated above,
|
|
3729
|
+
// before the checkpoint branch), applied via index-pack in the SAME
|
|
3730
|
+
// chain order the wholesale path always used, pipelined the same way:
|
|
3731
|
+
// apply(i) awaits bytes(i) while later packs finish downloading.
|
|
3732
|
+
const walFrames = await walFramesP;
|
|
3647
3733
|
for (let i = 0; i < walEntries.length; i++) {
|
|
3648
3734
|
const { w, epoch } = walEntries[i];
|
|
3649
|
-
const frame = walFrames[i] ?? null;
|
|
3735
|
+
const frame = (await walFrames[i]) ?? null;
|
|
3650
3736
|
if (!frame)
|
|
3651
3737
|
fail("CHAIN_UNUSABLE", `WAL pack ${w.object_id} absent`, "restoring gitvault objects");
|
|
3652
3738
|
const plain = openFrame({ k_obj: deriveObjectKey(kRepoForEpoch(epoch), this.repoId, epoch, "wal_pack", w.object_id), repo_id: this.repoId, object_kind: "wal_pack", object_id: w.object_id, epoch, frame, expected_ciphertext_sha256: w.ciphertext_sha256 });
|