run402 4.63.1 → 4.64.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/gitvault-surface.json +2 -1
- package/lib/doctor.mjs +28 -15
- package/lib/gitvault-capabilities.mjs +8 -0
- package/lib/remote-helper-session.mjs +138 -30
- package/lib/repos.mjs +122 -9
- package/package.json +1 -1
- package/sdk/dist/errors.d.ts +4 -2
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +1 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +1 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +80 -0
- package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.js +80 -0
- package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +219 -3
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +282 -5
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/node/gitvault-byo-config.d.ts +29 -0
- package/sdk/dist/node/gitvault-byo-config.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-byo-config.js +100 -0
- package/sdk/dist/node/gitvault-byo-config.js.map +1 -0
- package/sdk/dist/node/gitvault-byo-probe.d.ts +20 -0
- package/sdk/dist/node/gitvault-byo-probe.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-byo-probe.js +57 -0
- package/sdk/dist/node/gitvault-byo-probe.js.map +1 -0
- package/sdk/dist/node/gitvault-creation-journal.d.ts +43 -0
- package/sdk/dist/node/gitvault-creation-journal.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-creation-journal.js +34 -3
- package/sdk/dist/node/gitvault-creation-journal.js.map +1 -1
- package/sdk/dist/node/gitvault-degraded-read.d.ts +120 -0
- package/sdk/dist/node/gitvault-degraded-read.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-degraded-read.js +164 -0
- package/sdk/dist/node/gitvault-degraded-read.js.map +1 -0
- package/sdk/dist/node/gitvault-mirror-backend.d.ts +73 -0
- package/sdk/dist/node/gitvault-mirror-backend.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-mirror-backend.js +114 -8
- package/sdk/dist/node/gitvault-mirror-backend.js.map +1 -1
- package/sdk/dist/node/gitvault-mirror-config.d.ts +17 -1
- package/sdk/dist/node/gitvault-mirror-config.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-mirror-config.js +21 -0
- package/sdk/dist/node/gitvault-mirror-config.js.map +1 -1
- package/sdk/dist/node/gitvault-mirror.d.ts +42 -0
- package/sdk/dist/node/gitvault-mirror.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-mirror.js +162 -1
- package/sdk/dist/node/gitvault-mirror.js.map +1 -1
- package/sdk/dist/node/gitvault-publication.d.ts +51 -1
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-publication.js +88 -6
- package/sdk/dist/node/gitvault-publication.js.map +1 -1
|
@@ -38,6 +38,7 @@ import { tmpdir } from "node:os";
|
|
|
38
38
|
import { join } from "node:path";
|
|
39
39
|
import { LocalError, isRun402Error } from "../errors.js";
|
|
40
40
|
import { fetchGitvaultObjectBytes } from "./gitvault-edge-fetch.js";
|
|
41
|
+
import { openGitvaultDestinationBackend } from "./gitvault-mirror-backend.js";
|
|
41
42
|
import { GITVAULT_FORMAT, GITVAULT_GENESIS_EPOCH, GITVAULT_GENESIS_GENERATION, GITVAULT_HEX16_RE, GITVAULT_OID40_RE, GITVAULT_SUITE, attemptKeyCommitment, bytesToHex, checkFreshEpochKeyAgainstPriorKeys, checkHPartition, computeRotationId, computeTargetPartitionDigest, deriveDigestKey, deriveObjectKey, ekFingerprint, epochRotationKeyCommitment, formatGitvaultTimestamp, fromBase64url, hexToBytes, jcs, keyEnvelopeLedgerId, keyedCommitment, newGitvaultId, newHex32, nextEpoch, objectsetContent, openBindingPreimage, openEpochRotationForRecipient, openFrame, parseGitvaultStrict, parseRotateEpochPayload, pinManifestLedgerId, randomBytes, sealFrame, sealKeyEnvelope, sha256Hex, signGitvaultObject, storedBytes, storedBytesSha256, toBase64url, verifyGitvaultObject, } from "../namespaces/gitvault.crypto.js";
|
|
42
43
|
import { GITVAULT_ZERO_SHA256_SENTINEL } from "../namespaces/gitvault.types.js";
|
|
43
44
|
import { gitvaultCheckpointStaleness } from "../namespaces/gitvault.js";
|
|
@@ -961,14 +962,20 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
961
962
|
return { path: o.path, object_id: o.object_id, sha256: r.ciphertext_sha256 ?? r.stored_bytes_sha256 ?? "", size_bytes: r.size_bytes };
|
|
962
963
|
});
|
|
963
964
|
}
|
|
964
|
-
async function upload(repoId, objects, resourceBinding) {
|
|
965
|
+
async function upload(repoId, objects, resourceBinding, byo) {
|
|
965
966
|
if (objects.length === 0)
|
|
966
967
|
return [];
|
|
967
968
|
// The manifest is closed-key: `path` is client-local and MUST NOT ride the
|
|
968
969
|
// wire — the control plane derives the bucket key itself and refuses an
|
|
969
970
|
// entry carrying an unexpected member.
|
|
970
971
|
const entries = objects.map((o) => gitvaultManifestEntry(o));
|
|
971
|
-
|
|
972
|
+
// gitvault-byo-primary-bucket task 3.2: the inline (bytes-in-body) fast
|
|
973
|
+
// path is NEVER used for a BYO vault, even when the payload is small
|
|
974
|
+
// enough to be eligible — that transport writes bytes into run402's own
|
|
975
|
+
// bucket, exactly what the credential model forbids (the gateway
|
|
976
|
+
// refuses it too, VALIDATION_FAILED, so skipping it here is purely a
|
|
977
|
+
// wasted-round-trip avoidance, not a correctness dependency).
|
|
978
|
+
if (!byo && gitvaultInlineUploadEligible(objects)) {
|
|
972
979
|
// gitvault-composite-state-read design D2: every object fits under the
|
|
973
980
|
// caps — one POST, bytes verified + written server-side, and the
|
|
974
981
|
// response IS the finalize response (no session, no PUTs, no separate
|
|
@@ -987,6 +994,14 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
987
994
|
context: "opening gitvault upload session",
|
|
988
995
|
});
|
|
989
996
|
const issued = new Map(session.objects.map((u) => [gitvaultLedgerId(u), u]));
|
|
997
|
+
// gitvault-byo-primary-bucket task 3.2 — one backend, opened ONCE, for
|
|
998
|
+
// every BYO payload object this session names; `null` `put` never
|
|
999
|
+
// appears in the session response unless `byo` was supplied (the
|
|
1000
|
+
// gateway only mints `put: null` for a vault whose storage_profile is
|
|
1001
|
+
// "byo", which is exactly when THIS caller must have supplied `byo`
|
|
1002
|
+
// too — see the resolution in `GitvaultVault.uploadAll`).
|
|
1003
|
+
const byoBackend = byo ? openGitvaultDestinationBackend(byo.destination, byo.credential) : null;
|
|
1004
|
+
const byoAttestations = [];
|
|
990
1005
|
// Independent create-only PUTs within one session (design D2) — bounded
|
|
991
1006
|
// concurrency, same limit as the batched object reads below.
|
|
992
1007
|
await mapBounded(objects, GITVAULT_TRANSPORT_CONCURRENCY, async (o, i) => {
|
|
@@ -994,6 +1009,36 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
994
1009
|
const target = issued.get(id);
|
|
995
1010
|
if (!target)
|
|
996
1011
|
fail("GITVAULT_UPLOAD_SESSION_INVALID", `the session issued no upload for ${id}`, "uploading gitvault objects", { object_id: id, path: o.path });
|
|
1012
|
+
if (target.put === null) {
|
|
1013
|
+
// BYO payload object (task 2.2's GITVAULT_BYO_PAYLOAD_KINDS
|
|
1014
|
+
// partition, mirrored client-side by the server's own `put: null` +
|
|
1015
|
+
// `key` shape) — the client writes DIRECTLY to its own bucket. No
|
|
1016
|
+
// request to run402 ever carries a customer-bucket credential; no
|
|
1017
|
+
// request to run402 ever carries these bytes either.
|
|
1018
|
+
if (!byoBackend || !target.key) {
|
|
1019
|
+
fail("GITVAULT_BYO_BUCKET_WRITE_REFUSED", `the session named ${o.path} as a BYO-written object but no BYO write target was resolved`, "uploading gitvault objects", { path: o.path, object_id: id });
|
|
1020
|
+
}
|
|
1021
|
+
let put;
|
|
1022
|
+
try {
|
|
1023
|
+
put = await byoBackend.putCreateOnly(target.key, o.bytes);
|
|
1024
|
+
}
|
|
1025
|
+
catch (e) {
|
|
1026
|
+
fail("GITVAULT_BYO_BUCKET_WRITE_REFUSED", `writing ${o.path} to the BYO destination failed: ${e instanceof Error ? e.message : String(e)}`, "uploading gitvault objects", { path: o.path, key: target.key });
|
|
1027
|
+
}
|
|
1028
|
+
if (!put.created) {
|
|
1029
|
+
// create-only: the key already exists — legal ONLY if it is
|
|
1030
|
+
// byte-identical (an idempotent retry of THIS SAME write; a
|
|
1031
|
+
// permanent object id is never legitimately reused for
|
|
1032
|
+
// different bytes — matches the managed presigned-PUT read-and-
|
|
1033
|
+
// compare branch below, and the gateway's own attestation rule).
|
|
1034
|
+
const existing = await byoBackend.get(target.key);
|
|
1035
|
+
if (!existing || sha256Hex(existing) !== o.sha256) {
|
|
1036
|
+
fail("GITVAULT_BYO_BUCKET_WRITE_REFUSED", `${o.path} already exists in the BYO destination with different bytes`, "uploading gitvault objects", { path: o.path, key: target.key });
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
byoAttestations.push({ key: target.key, sha256: o.sha256, size_bytes: o.size_bytes, create_only_result: "created" });
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
997
1042
|
// `If-None-Match: *` (create-only) and the FULL_OBJECT `x-amz-checksum-sha256`
|
|
998
1043
|
// are SIGNED INTO the presigned URL, so they must go out exactly as the
|
|
999
1044
|
// server issued them — a dropped or altered header is a signature
|
|
@@ -1015,7 +1060,13 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
1015
1060
|
if (!r.ok)
|
|
1016
1061
|
fail("GITVAULT_UPLOAD_FAILED", `presigned PUT failed (HTTP ${r.status}) for ${o.path}`, "uploading gitvault objects", { path: o.path, status: r.status });
|
|
1017
1062
|
});
|
|
1018
|
-
const fin = await client.request(`${base(repoId)}/upload-sessions/${encodeURIComponent(session.upload_session_id)}/finalize`, {
|
|
1063
|
+
const fin = await client.request(`${base(repoId)}/upload-sessions/${encodeURIComponent(session.upload_session_id)}/finalize`, {
|
|
1064
|
+
method: "POST",
|
|
1065
|
+
// Only a BYO session ever carries attestations — an ordinary managed
|
|
1066
|
+
// session's finalize body stays `{}`, byte-identical to today.
|
|
1067
|
+
body: byo ? { attestations: byoAttestations } : {},
|
|
1068
|
+
context: "finalizing gitvault upload session",
|
|
1069
|
+
});
|
|
1019
1070
|
return receiptsFromFinalize(objects, entries, fin);
|
|
1020
1071
|
}
|
|
1021
1072
|
async function admit(repoId, generation, bytes, hash, extra = {}) {
|
|
@@ -1046,7 +1097,7 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
1046
1097
|
return res.allocation ?? res;
|
|
1047
1098
|
},
|
|
1048
1099
|
async putObject(request) {
|
|
1049
|
-
const [r] = await upload(request.repo_id, [{ path: request.path, object_kind: "key_envelope", object_id: null, bytes: request.bytes, sha256: request.expected_sha256, size_bytes: request.expected_size_bytes }]);
|
|
1100
|
+
const [r] = await upload(request.repo_id, [{ path: request.path, object_kind: "key_envelope", object_id: null, bytes: request.bytes, sha256: request.expected_sha256, size_bytes: request.expected_size_bytes }], undefined, request.byo);
|
|
1050
1101
|
return { stored_bytes_sha256: r.sha256, size_bytes: r.size_bytes };
|
|
1051
1102
|
},
|
|
1052
1103
|
getObject: ({ repo_id, path }) => getObjectBytes(repo_id, path),
|
|
@@ -1075,7 +1126,7 @@ export function createGitvaultHttpTransport(client, options = {}) {
|
|
|
1075
1126
|
qs.set("cursor", request.cursor);
|
|
1076
1127
|
return client.request(`${base(request.repo_id)}/heads?${qs.toString()}`, { context: "listing gitvault heads" });
|
|
1077
1128
|
},
|
|
1078
|
-
uploadObjects: ({ repo_id, objects, resource_binding }) => upload(repo_id, objects, resource_binding),
|
|
1129
|
+
uploadObjects: ({ repo_id, objects, resource_binding, byo }) => upload(repo_id, objects, resource_binding, byo),
|
|
1079
1130
|
admitHead: (r) => admit(r.repo_id, r.generation, r.stored_bytes, r.stored_bytes_sha256),
|
|
1080
1131
|
// Both routes SHIPPED (`routes/gitvault-admission.ts`). `retention-cutoffs`
|
|
1081
1132
|
// answers `{ticket, receipt, next_actions}`, which IS
|
|
@@ -1413,6 +1464,16 @@ export class GitvaultVault {
|
|
|
1413
1464
|
retries;
|
|
1414
1465
|
servicePublicKey;
|
|
1415
1466
|
genesisCache = null;
|
|
1467
|
+
/**
|
|
1468
|
+
* gitvault-byo-primary-bucket task 3.2 — this vault's resolved BYO write
|
|
1469
|
+
* target, cached after the first resolution (storage_profile/
|
|
1470
|
+
* byo_destination are immutable-at-allocation in v1, so a single
|
|
1471
|
+
* read-once-per-instance is safe). `undefined` = not yet resolved;
|
|
1472
|
+
* `null` = this machine has no LOCAL BYO write config for this repo
|
|
1473
|
+
* (either an ordinary managed vault, or a BYO vault this machine has not
|
|
1474
|
+
* been configured to write — see {@link resolveByoWriteTarget}).
|
|
1475
|
+
*/
|
|
1476
|
+
byoResolution = undefined;
|
|
1416
1477
|
constructor(options) {
|
|
1417
1478
|
this.keystore = options.keystore;
|
|
1418
1479
|
this.transport = options.transport;
|
|
@@ -2379,12 +2440,14 @@ export class GitvaultVault {
|
|
|
2379
2440
|
created_at: formatGitvaultTimestamp(this.now()),
|
|
2380
2441
|
});
|
|
2381
2442
|
try {
|
|
2443
|
+
const byo = await this.resolveByoWriteTarget();
|
|
2382
2444
|
await this.transport.putObject({
|
|
2383
2445
|
repo_id: this.repoId,
|
|
2384
2446
|
path: gitvaultPaths.envelope(epoch, sealed.receipt.recipient_fingerprint),
|
|
2385
2447
|
bytes: sealed.stored_bytes,
|
|
2386
2448
|
expected_sha256: sealed.stored_bytes_sha256,
|
|
2387
2449
|
expected_size_bytes: sealed.size_bytes,
|
|
2450
|
+
...(byo ? { byo } : {}),
|
|
2388
2451
|
});
|
|
2389
2452
|
wrapped.push({ principal_id: entry.principal_id, ek_fingerprint: entry.ek_fingerprint });
|
|
2390
2453
|
}
|
|
@@ -2751,8 +2814,27 @@ export class GitvaultVault {
|
|
|
2751
2814
|
return out;
|
|
2752
2815
|
}
|
|
2753
2816
|
// ── upload with receipt-compare ──
|
|
2817
|
+
/**
|
|
2818
|
+
* gitvault-byo-primary-bucket task 3.2 — resolve (once, cached) this
|
|
2819
|
+
* machine's local BYO write config for THIS vault. `null` when none is
|
|
2820
|
+
* configured locally — either because this is an ordinary managed vault
|
|
2821
|
+
* (the common case; deliberately never confirmed with a network call, so
|
|
2822
|
+
* a managed vault's push pays zero extra round trips), or because this
|
|
2823
|
+
* machine has not been configured to write a BYO vault it nonetheless
|
|
2824
|
+
* belongs to (surfaced downstream as `GITVAULT_BYO_BUCKET_WRITE_REFUSED`
|
|
2825
|
+
* when the session actually names a `put: null` object with no target).
|
|
2826
|
+
*/
|
|
2827
|
+
async resolveByoWriteTarget() {
|
|
2828
|
+
if (this.byoResolution !== undefined)
|
|
2829
|
+
return this.byoResolution;
|
|
2830
|
+
const { readByoConfig } = await import("./gitvault-byo-config.js");
|
|
2831
|
+
const local = readByoConfig(this.keystore, this.repoId);
|
|
2832
|
+
this.byoResolution = local ? { destination: local.destination, ...(local.credential ? { credential: local.credential } : {}) } : null;
|
|
2833
|
+
return this.byoResolution;
|
|
2834
|
+
}
|
|
2754
2835
|
async uploadAll(objects) {
|
|
2755
|
-
const
|
|
2836
|
+
const byo = await this.resolveByoWriteTarget();
|
|
2837
|
+
const receipts = await this.transport.uploadObjects({ repo_id: this.repoId, objects, ...(byo ? { byo } : {}) });
|
|
2756
2838
|
if (receipts.length !== objects.length)
|
|
2757
2839
|
fail("GITVAULT_RECEIPT_MISMATCH", `${receipts.length} receipts for ${objects.length} objects`, "comparing finalization receipts");
|
|
2758
2840
|
const mismatches = objects.filter((o, i) => receipts[i].path !== o.path || receipts[i].sha256 !== o.sha256 || receipts[i].size_bytes !== o.size_bytes);
|