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
package/gitvault-surface.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"surface_version": "4.
|
|
2
|
+
"surface_version": "4.64.0",
|
|
3
3
|
"verbs": [
|
|
4
4
|
"repos create",
|
|
5
5
|
"repos list",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"revocation_live": true,
|
|
69
69
|
"human_envelope_add_live": true,
|
|
70
70
|
"mirror_live": true,
|
|
71
|
+
"byo_live": true,
|
|
71
72
|
"recover_live": true,
|
|
72
73
|
"allocation": "create_immediate",
|
|
73
74
|
"snapshot_dirty_default": "refuse"
|
package/lib/doctor.mjs
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { existsSync, statSync } from "node:fs";
|
|
15
|
+
import { GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT } from "#sdk";
|
|
15
16
|
import { configDir, readAllowance, loadKeyStore } from "./config.mjs";
|
|
16
17
|
import { resolveGitvaultTarget } from "./gitvault-target.mjs";
|
|
17
18
|
import { getSdk } from "./sdk.mjs";
|
|
@@ -665,6 +666,10 @@ export async function run(sub, args = []) {
|
|
|
665
666
|
project_id: gv.project_id ?? projectId,
|
|
666
667
|
repo_id: gv.repo_id,
|
|
667
668
|
vault: gv.vault === null ? null : "allocated",
|
|
669
|
+
// gitvault-byo-primary-bucket task 3.5 — absent-or-"managed" is
|
|
670
|
+
// byte-identical to before this fold for every non-BYO vault.
|
|
671
|
+
storage_profile: gv.vault?.storage_profile ?? null,
|
|
672
|
+
byo_destination: gv.vault?.byo_destination ?? null,
|
|
668
673
|
gitvault_policy: gv.gitvault_policy,
|
|
669
674
|
keystore_root: gv.keystore.root,
|
|
670
675
|
can_sign: gv.keystore.can_sign,
|
|
@@ -704,30 +709,34 @@ export async function run(sub, args = []) {
|
|
|
704
709
|
// doctor-persistent `grandfathered` advisory it owns.
|
|
705
710
|
for (const w of gv.warnings ?? []) gaps.push(`${w.kind}: ${w.message}`);
|
|
706
711
|
|
|
707
|
-
// gitvault-mirror-and-recover task 4.3
|
|
708
|
-
// ALONGSIDE (never in place of) the
|
|
709
|
-
// never blocking `run402 deploy`'s
|
|
710
|
-
// outcome is unaffected regardless of
|
|
711
|
-
// `mirror_currency` mirrors `mirror
|
|
712
|
-
// `current` / `stale` / `unknown` (mirror
|
|
713
|
-
// unread)
|
|
714
|
-
//
|
|
715
|
-
//
|
|
712
|
+
// gitvault-mirror-and-recover task 4.3 + gitvault-mirror-default:
|
|
713
|
+
// mirror currency, reported ALONGSIDE (never in place of) the
|
|
714
|
+
// deploy-related gaps above, and never blocking `run402 deploy`'s
|
|
715
|
+
// own gate — the vault lane's outcome is unaffected regardless of
|
|
716
|
+
// mirror state (design D6). `mirror_currency` mirrors `mirror
|
|
717
|
+
// status`'s own tri-state: `current` / `stale` / `unknown` (mirror
|
|
718
|
+
// unreachable or vault unread). Only STALE is actionable enough to
|
|
719
|
+
// become a warning gap; a vault with no successful mirror copy yet
|
|
720
|
+
// carries the SDK-computed `vault_unmirrored` finding — named and
|
|
721
|
+
// standing (gitvault-mirror-default supersedes the old anonymous
|
|
722
|
+
// `advisory` string), echoed verbatim, and deliberately NOT pushed
|
|
723
|
+
// into `gaps`: informational, never blocking, computed client-side
|
|
724
|
+
// only, cleared by the first successful mirror write or sync.
|
|
716
725
|
if (gv.vault !== null && value.repo_id) {
|
|
717
726
|
try {
|
|
718
|
-
const mirrorStatus = await getSdk().gitvault.mirrorStatus({ repo_id: value.repo_id });
|
|
727
|
+
const mirrorStatus = await getSdk().gitvault.mirrorStatus({ repo_id: value.repo_id, is_byo: value.storage_profile === "byo" });
|
|
719
728
|
value.gitvault_mirror = {
|
|
720
729
|
configured: mirrorStatus.configured,
|
|
721
730
|
destination: mirrorStatus.destination,
|
|
722
731
|
mirrored_generation: mirrorStatus.mirrored_generation,
|
|
723
732
|
newest_generation: mirrorStatus.newest_generation,
|
|
724
733
|
is_current: mirrorStatus.is_current,
|
|
734
|
+
last_success_at: mirrorStatus.last_success_at,
|
|
735
|
+
finding: mirrorStatus.finding,
|
|
725
736
|
validity_not_freshness: mirrorStatus.validity_not_freshness,
|
|
726
737
|
keystore_still_required: mirrorStatus.keystore_still_required,
|
|
727
738
|
};
|
|
728
|
-
if (
|
|
729
|
-
value.gitvault_mirror.advisory = "no ciphertext mirror is configured for this vault — the exit ramp is opt-in; 'run402 repos mirror <destination>' to configure one.";
|
|
730
|
-
} else if (mirrorStatus.is_current === false) {
|
|
739
|
+
if (mirrorStatus.is_current === false) {
|
|
731
740
|
gaps.push(`the ciphertext mirror at ${mirrorStatus.destination} is STALE (mirrored generation ${mirrorStatus.mirrored_generation ?? "(none)"}, vault newest ${mirrorStatus.newest_generation ?? "(none)"}) — ${mirrorStatus.closing_command}`);
|
|
732
741
|
}
|
|
733
742
|
} catch {
|
|
@@ -736,15 +745,19 @@ export async function run(sub, args = []) {
|
|
|
736
745
|
}
|
|
737
746
|
}
|
|
738
747
|
|
|
748
|
+
// gitvault-byo-primary-bucket task 3.5 — unconditional, independent
|
|
749
|
+
// of mirror status (D7); imported from the canonical constants, never
|
|
750
|
+
// paraphrased.
|
|
751
|
+
const byoDisclosure = value.storage_profile === "byo" ? ` Storage: byo (${value.byo_destination ?? "(unknown)"}) — ${GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT}` : "";
|
|
739
752
|
checks.push({
|
|
740
753
|
name: "gitvault",
|
|
741
754
|
status: gaps.length > 0 ? "warning" : "ok",
|
|
742
755
|
value: gaps.length > 0 ? { ...value, gaps } : value,
|
|
743
|
-
hint: gv.vault === null
|
|
756
|
+
hint: (gv.vault === null
|
|
744
757
|
? `No vault for this project (that is a normal shape). Allocate one with 'run402 repos create --project <id>'. Keystore: ${gv.keystore.root}`
|
|
745
758
|
: gv.durability_statement
|
|
746
759
|
? `Back up ${gv.keystore.root} anyway — ${gv.durability_statement} (covering_recipients: ${gv.covering_recipients})`
|
|
747
|
-
: `Back up ${gv.keystore.root} — whole-machine or whole-keystore loss is terminal for vault history
|
|
760
|
+
: `Back up ${gv.keystore.root} — whole-machine or whole-keystore loss is terminal for vault history.`) + byoDisclosure,
|
|
748
761
|
});
|
|
749
762
|
} catch (err) {
|
|
750
763
|
// A gateway without gitvault, an unreachable API, or a project this
|
|
@@ -28,6 +28,14 @@ export const GITVAULT_CAPABILITIES = {
|
|
|
28
28
|
// `repos mirror` reads/writes a mirror destination beside the keystore and
|
|
29
29
|
// moves real bytes into a customer-owned bucket.
|
|
30
30
|
mirror_live: true,
|
|
31
|
+
// `repos create --byo <s3://bucket/prefix>` allocates a
|
|
32
|
+
// `storage_profile: "byo"` vault whose payload ciphertext is written by
|
|
33
|
+
// the client straight to the customer's own bucket (create-only probe
|
|
34
|
+
// fail-closed before allocation; chain-copy dual-write; attested finalize
|
|
35
|
+
// with `storage_verification: "client_attested"`; degraded reads from the
|
|
36
|
+
// destination; fsck absence check) — shipped end to end in the same
|
|
37
|
+
// change that flips this flag (gitvault-byo-primary-bucket, Phase 3).
|
|
38
|
+
byo_live: true,
|
|
31
39
|
// `repos recover` materializes a git repository from a mirror source,
|
|
32
40
|
// offline, with no server call.
|
|
33
41
|
recover_live: true,
|
|
@@ -112,6 +112,23 @@
|
|
|
112
112
|
* is refused rather than silently pointed at the current directory. Only
|
|
113
113
|
* `capabilities`, `option` and `list` work without one, which is exactly
|
|
114
114
|
* the set `git ls-remote <url>` outside a checkout needs.
|
|
115
|
+
*
|
|
116
|
+
* DEGRADED READ MODE (gitvault-byo-primary-bucket, design D4). `list` and
|
|
117
|
+
* `fetch` each wrap their own live gateway read in
|
|
118
|
+
* `getSdk().gitvault.withDegradedRead` — on a NETWORK-CLASS failure (never a
|
|
119
|
+
* 401/403/404 or any other 4xx; one bounded retry runs first so a transient
|
|
120
|
+
* blip does not flap between sources) with a mirror configured for this
|
|
121
|
+
* vault, the read is served from it instead via the SAME `r402s-recover`
|
|
122
|
+
* engine `run402 repos recover` uses — `git fetch`/`clone` keep working
|
|
123
|
+
* while run402 is down. The result is marked `degraded: true` with source
|
|
124
|
+
* provenance, and exactly one stderr line names the fallback and its source
|
|
125
|
+
* (`gitvaultDegradedReadNote`). Local trust pins never advance past what the
|
|
126
|
+
* mirror copy itself chain-verifies (the fallback engine never touches the
|
|
127
|
+
* live vault's own keystore pins at all — see `gitvault-degraded-read.ts`'s
|
|
128
|
+
* own doc comment). A vault with no mirror configured is BYTE-IDENTICAL to
|
|
129
|
+
* today: the original gateway error surfaces unchanged. `push` is UNTOUCHED
|
|
130
|
+
* by this — writes are never rerouted; admission always requires the
|
|
131
|
+
* gateway.
|
|
115
132
|
*/
|
|
116
133
|
|
|
117
134
|
import { createInterface } from "node:readline";
|
|
@@ -127,7 +144,7 @@ const nodeModP = import("#sdk/node");
|
|
|
127
144
|
const configModP = import("./config.mjs");
|
|
128
145
|
const { getSdk } = await sdkModP;
|
|
129
146
|
const { resolveWalletCore, enforceWalletExistsCore, WalletSelectionError } = await walletModP;
|
|
130
|
-
const { gitvaultRemoteAddressForm, gitvaultSlugReleasedInfo, parseGitvaultRemoteUrl } = await isoModP;
|
|
147
|
+
const { gitvaultRemoteAddressForm, gitvaultSlugReleasedInfo, parseGitvaultRemoteUrl, gitvaultDegradedReadNote } = await isoModP;
|
|
131
148
|
const { GITVAULT_R402_REF_NAMESPACE, hardenedGit, resolveGitInvocationRepo, readPinnedGitvaultRepo, pinGitvaultRepo, readGitvaultRestoreMarker, readGitvaultAutoGcThreshold } = await nodeModP;
|
|
132
149
|
const { allowanceFile, projectCredentialsFile, profileStateFile } = await configModP;
|
|
133
150
|
|
|
@@ -524,7 +541,7 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
524
541
|
*/
|
|
525
542
|
const openVault = async (repoDir) => {
|
|
526
543
|
const result = await getCachedSdk().gitvault.resolveOrCreateAddress({ address, allow_create: false, ...(repoDir ? { repo_dir: repoDir } : {}) });
|
|
527
|
-
return { vault: result.handle.vault, resolution: result.resolution };
|
|
544
|
+
return { vault: result.handle.vault, keystore: result.handle.keystore, repo_id: result.handle.repo_id, resolution: result.resolution };
|
|
528
545
|
};
|
|
529
546
|
|
|
530
547
|
/**
|
|
@@ -569,7 +586,10 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
569
586
|
note(`vault ${result.handle.repo_id} allocated (genesis ${result.created.genesis_sha256}) — one-shot recovery receipt, keep many copies:`);
|
|
570
587
|
note(JSON.stringify(result.created.recovery_receipt));
|
|
571
588
|
try {
|
|
572
|
-
const { getGitvaultKeystoreRoot } = await import("#sdk/node");
|
|
589
|
+
const { getGitvaultKeystoreRoot, GITVAULT_MIRROR_SETUP_HINT } = await import("#sdk/node");
|
|
590
|
+
// gitvault-mirror-default: every allocation teaches the mirror door
|
|
591
|
+
// beside the recovery receipt, lazy push-to-create included.
|
|
592
|
+
note(GITVAULT_MIRROR_SETUP_HINT);
|
|
573
593
|
note(`keystore: ${getGitvaultKeystoreRoot()} — back this up; whole-machine or whole-keystore loss is terminal for vault history until human envelopes ship`);
|
|
574
594
|
} catch {
|
|
575
595
|
// Never let a diagnostic line fail a push that already allocated successfully.
|
|
@@ -613,13 +633,38 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
613
633
|
}
|
|
614
634
|
}
|
|
615
635
|
const materializeOpts = fetchMarker ? { deltaSince: fetchMarker.generation } : {};
|
|
616
|
-
let
|
|
617
|
-
let state;
|
|
636
|
+
let opened;
|
|
618
637
|
try {
|
|
619
|
-
|
|
620
|
-
|
|
638
|
+
opened = await openVault(repoDir ?? undefined);
|
|
639
|
+
} catch (err) {
|
|
640
|
+
// An unallocated vault is not an error here: `list` is the read half of
|
|
641
|
+
// the protocol dance and must never create anything on its own (D2
|
|
642
|
+
// scopes lazy creation to `push`). Reporting it as an EMPTY ref set is
|
|
643
|
+
// exactly what a fresh repository looks like to git, and `push` still
|
|
644
|
+
// runs `list` first either way — this is what lets a first push land in
|
|
645
|
+
// one command instead of `list` failing the whole exchange before
|
|
646
|
+
// `push` ever gets a turn. Nothing to share with `push` either
|
|
647
|
+
// (design D7): an unallocated vault has no base to reuse.
|
|
648
|
+
if (isVaultNotFound(err)) {
|
|
649
|
+
endBlock();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
throw err;
|
|
653
|
+
}
|
|
654
|
+
let vault = opened.vault;
|
|
655
|
+
|
|
656
|
+
// gitvault-byo-primary-bucket (design D4, task 3.4 — mirror half): the
|
|
657
|
+
// live materialize (with its own stale-pin retry, unchanged from before
|
|
658
|
+
// this change) is the "attemptLive" `withDegradedRead` wraps — on a
|
|
659
|
+
// NETWORK-CLASS failure (never 4xx) with a mirror configured for this
|
|
660
|
+
// vault, it falls back to `r402s-recover`'s engine against it instead of
|
|
661
|
+
// failing the whole `list`/`clone`. `repoDir` is required for the
|
|
662
|
+
// fallback to materialize into (`out_dir`); a repo-free `list` (bare
|
|
663
|
+
// `git ls-remote`) simply has none, so the fallback never runs and the
|
|
664
|
+
// original error surfaces exactly as it always did.
|
|
665
|
+
const attemptLive = async () => {
|
|
621
666
|
try {
|
|
622
|
-
|
|
667
|
+
return await vault.materialize(materializeOpts);
|
|
623
668
|
} catch (err) {
|
|
624
669
|
// An OFFLINE (id-carrying pin) resolution discovers a stale pin on
|
|
625
670
|
// its FIRST repo-scoped read (client-surface spec, id-pinning
|
|
@@ -634,30 +679,54 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
634
679
|
if (!recovered) throw err;
|
|
635
680
|
note(`pinned vault ${opened.resolution.repo_id} no longer resolves — re-resolved to ${recovered.resolution.repo_id}, retrying`);
|
|
636
681
|
vault = recovered.handle.vault;
|
|
637
|
-
|
|
682
|
+
opened = { ...opened, vault: recovered.handle.vault, keystore: recovered.handle.keystore, repo_id: recovered.handle.repo_id, resolution: recovered.resolution };
|
|
683
|
+
return await vault.materialize(materializeOpts);
|
|
638
684
|
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
let outcome;
|
|
688
|
+
try {
|
|
689
|
+
outcome = await getCachedSdk().gitvault.withDegradedRead({ attemptLive, keystore: opened.keystore, repo_id: opened.repo_id, out_dir: repoDir });
|
|
639
690
|
} catch (err) {
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
// one command instead of `list` failing the whole exchange before
|
|
646
|
-
// `push` ever gets a turn. Nothing to share with `push` either
|
|
647
|
-
// (design D7): an unallocated vault has no base to reuse.
|
|
691
|
+
// Same "an unallocated vault is not an error" reasoning as above — a
|
|
692
|
+
// 404 discovered only once materialize actually runs takes this same
|
|
693
|
+
// empty-ref-set path (network-class errors never reach here at all:
|
|
694
|
+
// `withDegradedRead` only rethrows them after exhausting its own
|
|
695
|
+
// fallback, and a 404 is never network-class).
|
|
648
696
|
if (isVaultNotFound(err)) {
|
|
649
697
|
endBlock();
|
|
650
698
|
return;
|
|
651
699
|
}
|
|
652
700
|
throw err;
|
|
653
701
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
702
|
+
|
|
703
|
+
let refs;
|
|
704
|
+
let head;
|
|
705
|
+
if (outcome.degraded) {
|
|
706
|
+
// Exactly ONE stderr line naming the degraded read and its source
|
|
707
|
+
// (design D4) — the validity-not-freshness limit rides inside the
|
|
708
|
+
// canonical statement this composes, verbatim.
|
|
709
|
+
note(gitvaultDegradedReadNote(outcome.result.source));
|
|
710
|
+
refs = outcome.result.refs ?? {};
|
|
711
|
+
head = outcome.result.head_target;
|
|
712
|
+
// No live vault instance materialized this pass — nothing safe to
|
|
713
|
+
// share with a following `push` (design D7's own "unallocated vault"
|
|
714
|
+
// reasoning applies equally to a degraded one: writes are never
|
|
715
|
+
// rerouted, so `push` must resolve and materialize live on its own).
|
|
716
|
+
} else {
|
|
717
|
+
const state = outcome.live;
|
|
718
|
+
refs = state.refs ?? {};
|
|
719
|
+
head = state.head_target;
|
|
720
|
+
// gitvault-session-state-reuse design D1: `fetchState`/`fetchMarker`
|
|
721
|
+
// extend the SAME session-scoped handoff `push` already reuses (`base`)
|
|
722
|
+
// — the `fetch` phase of THIS session reuses this response instead of
|
|
723
|
+
// issuing its own state read. Session-scoped only: dropped the moment a
|
|
724
|
+
// push admits in this same session (see `runPush`'s reset below).
|
|
725
|
+
// `keystore`/`repo_id` ride along so `fetch`'s own degraded-read wrap
|
|
726
|
+
// can fall back without re-resolving.
|
|
727
|
+
if (repoDir) sharedListSession = { repoDir, walletName: resolvedWallet?.name ?? null, vault, keystore: opened.keystore, repo_id: opened.repo_id, base: state, fetchMarker, fetchState: state };
|
|
728
|
+
}
|
|
729
|
+
|
|
661
730
|
for (const ref of Object.keys(refs).sort()) out(`${refs[ref]} ${ref}`);
|
|
662
731
|
// A snapshot-only vault holds protocol refs but no branch heads, so a
|
|
663
732
|
// plain `git clone` prints "cloned an empty repository" with no hint the
|
|
@@ -667,7 +736,6 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
667
736
|
note(`this vault has no branch heads yet — its history lives on ${refNames.sort()[0]}`);
|
|
668
737
|
note(`fetch it with: git fetch <remote> '+${refNames.sort()[0]}:${refNames.sort()[0]}' && git checkout -b restored ${refNames.sort()[0]}`);
|
|
669
738
|
}
|
|
670
|
-
const head = state.head_target;
|
|
671
739
|
// A symref is only advertised when its target is actually present:
|
|
672
740
|
// pointing HEAD at a ref that does not exist is what an empty repository
|
|
673
741
|
// looks like, and git reads the empty list correctly on its own.
|
|
@@ -700,15 +768,55 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
700
768
|
// different repository/wallet, or a push that already admitted in this
|
|
701
769
|
// same session — see `runPush`'s reset) falls back to the vault's own
|
|
702
770
|
// read, unchanged.
|
|
771
|
+
//
|
|
772
|
+
// gitvault-byo-primary-bucket (design D4, task 3.4 — mirror half):
|
|
773
|
+
// either path's actual chain/payload read is the "attemptLive" that
|
|
774
|
+
// `withDegradedRead` wraps — `gitvault.restore(...)` is exactly `open()`
|
|
775
|
+
// followed by `handle.vault.restoreObjectsInto(target_dir)` (see that
|
|
776
|
+
// method's own one-line body), decomposed here so the SECOND half can be
|
|
777
|
+
// wrapped, with byte-identical resolution to today on the live-success
|
|
778
|
+
// path. On a NETWORK-CLASS failure (never 4xx) with a mirror configured
|
|
779
|
+
// for this vault, the fetch is served from it instead of failing the
|
|
780
|
+
// whole clone; the destination it materialized into is the SAME
|
|
781
|
+
// `repoDir` git already prepared, so `git fetch`/`clone` completes
|
|
782
|
+
// exactly as if the live path had run.
|
|
703
783
|
const shared = sharedListSession && sharedListSession.repoDir === repoDir && sharedListSession.walletName === (resolvedWallet?.name ?? null) ? sharedListSession : null;
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
784
|
+
let attemptLive;
|
|
785
|
+
let degradedKeystore;
|
|
786
|
+
let degradedRepoId;
|
|
787
|
+
if (shared) {
|
|
788
|
+
attemptLive = () => shared.vault.restoreObjectsInto(repoDir, { marker: shared.fetchMarker, state: shared.fetchState });
|
|
789
|
+
degradedKeystore = shared.keystore;
|
|
790
|
+
degradedRepoId = shared.repo_id;
|
|
791
|
+
} else {
|
|
792
|
+
const handle = await getCachedSdk().gitvault.open({ ...target, repo_dir: repoDir });
|
|
793
|
+
attemptLive = () => handle.vault.restoreObjectsInto(repoDir);
|
|
794
|
+
degradedKeystore = handle.keystore;
|
|
795
|
+
degradedRepoId = handle.repo_id;
|
|
796
|
+
}
|
|
797
|
+
const outcome = await getCachedSdk().gitvault.withDegradedRead({
|
|
798
|
+
attemptLive,
|
|
799
|
+
keystore: degradedKeystore,
|
|
800
|
+
repo_id: degradedRepoId,
|
|
801
|
+
out_dir: repoDir,
|
|
802
|
+
});
|
|
803
|
+
let restored;
|
|
804
|
+
if (outcome.degraded) {
|
|
805
|
+
// Exactly ONE stderr line naming the degraded read and its source
|
|
806
|
+
// (design D4) — the validity-not-freshness limit rides inside the
|
|
807
|
+
// canonical statement this composes, verbatim. A later `git push`
|
|
808
|
+
// still requires the gateway unchanged — this function only ever
|
|
809
|
+
// serves reads; `runPush` below is untouched by this change.
|
|
810
|
+
note(gitvaultDegradedReadNote(outcome.result.source));
|
|
811
|
+
restored = { generation: outcome.result.generation, retained_refs: outcome.result.retained_refs };
|
|
812
|
+
} else {
|
|
813
|
+
restored = { generation: outcome.live.generation, retained_refs: outcome.live.retained_refs };
|
|
814
|
+
}
|
|
707
815
|
if (verbosity >= 1) note(`restored generation ${restored.generation}`);
|
|
708
816
|
// clone-installs-retained-refs D3: a bookkeeping failure here degrades to
|
|
709
817
|
// exactly today's (pre-change) behavior — one stderr note, fetch still
|
|
710
|
-
// completes. `restored.retained_refs` is never absent (the
|
|
711
|
-
//
|
|
818
|
+
// completes. `restored.retained_refs` is never absent (both the live and
|
|
819
|
+
// the degraded path always return a result, never throw for this step).
|
|
712
820
|
if (restored.retained_refs?.warning) note(restored.retained_refs.warning);
|
|
713
821
|
else if (verbosity >= 1 && (restored.retained_refs?.written.length > 0 || restored.retained_refs?.deleted.length > 0)) {
|
|
714
822
|
note(`refs/r402/retain: +${restored.retained_refs.written.length} -${restored.retained_refs.deleted.length} (${restored.retained_refs.retained_count} retained tip(s) total)`);
|
package/lib/repos.mjs
CHANGED
|
@@ -32,7 +32,13 @@ import { resolveOrgId, resolveOwningOrgId } from "./org-context.mjs";
|
|
|
32
32
|
import { resolveGitvaultTarget } from "./gitvault-target.mjs";
|
|
33
33
|
import { nextAction, claimOrgSlugAction, claimRepoNameAction } from "./next-actions.mjs";
|
|
34
34
|
import { printKeystoreLocation } from "./gitvault.mjs";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
GITVAULT_BYO_HEADLINE_STATEMENT,
|
|
37
|
+
GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT,
|
|
38
|
+
GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT,
|
|
39
|
+
GITVAULT_MIRROR_SETUP_HINT,
|
|
40
|
+
gitvaultRemoteUrlForRepo,
|
|
41
|
+
} from "#sdk";
|
|
36
42
|
import { sdkStats, printVerboseStats, isVerbose } from "./stats.mjs";
|
|
37
43
|
import {
|
|
38
44
|
normalizeArgv,
|
|
@@ -55,6 +61,7 @@ Usage:
|
|
|
55
61
|
|
|
56
62
|
Common:
|
|
57
63
|
run402 repos create [name] [--org <org_id>] [--dir <path>] [--tier <tier>] [--project <id>]
|
|
64
|
+
[--byo <s3://bucket/prefix>] [--profile <name> | --ambient] [--region <r>] [--endpoint <url>]
|
|
58
65
|
run402 repos view [--project <id>] [--repo <repo_id>] [--human]
|
|
59
66
|
run402 repos list [--org <org_id>] [--human]
|
|
60
67
|
|
|
@@ -99,7 +106,15 @@ Subcommands:
|
|
|
99
106
|
nothing usable can be derived, this is a structured error naming
|
|
100
107
|
exactly one next_action, never a guess. The response's next_action
|
|
101
108
|
is the exact \`git push\` to run. Nothing is deployed, ever, unless
|
|
102
|
-
you separately choose to.
|
|
109
|
+
you separately choose to. \`--byo <s3://bucket/prefix>\` allocates a
|
|
110
|
+
BYO (bring-your-own-bucket) vault: source ciphertext is written
|
|
111
|
+
ONLY to your own bucket, never run402's — run402 holds the small
|
|
112
|
+
signed chain only. The destination is PROBED before anything is
|
|
113
|
+
allocated (create-only writes honored, versioning disabled, write
|
|
114
|
+
permitted) and refuses closed on any failed property. Fewer
|
|
115
|
+
copies than a managed vault by construction (the platform holds
|
|
116
|
+
no payload copy at all); \`run402 repos mirror <destination>\`
|
|
117
|
+
still works unchanged as your second customer-held location.
|
|
103
118
|
view Side-effect-free: what this machine and the control plane each
|
|
104
119
|
believe about the repo — allocation, policy, whether this keystore
|
|
105
120
|
can sign, the authenticated and materialized pins, the mirror
|
|
@@ -206,6 +221,14 @@ Subcommands:
|
|
|
206
221
|
normal writing mode, so a budget-exceeded run resumes). \`--mirror\`
|
|
207
222
|
additionally runs the keyless mirror integrity probe — it proves
|
|
208
223
|
the mirror's VALIDITY, never its FRESHNESS, and says so.
|
|
224
|
+
For a BYO vault (\`repos create --byo\`), fsck ALSO adjudicates
|
|
225
|
+
the customer's own bucket against run402's signed chain —
|
|
226
|
+
automatic, no flag needed. No local BYO credentials on this
|
|
227
|
+
machine reports an explicit NOT CHECKED line, never a failure;
|
|
228
|
+
a confirmed absence FAILS fsck with GITVAULT_BYO_OBJECT_MISSING
|
|
229
|
+
naming exactly what's missing. Runs under \`--no-write\` too (a
|
|
230
|
+
pure HEAD-check read). See the result's \`byo_presence\` block
|
|
231
|
+
(also on \`--human\`); absent entirely for a managed vault.
|
|
209
232
|
In write mode (not \`--no-write\`), when this keystore holds a
|
|
210
233
|
local encryption identity, fsck ALSO submits its own
|
|
211
234
|
chain-verified/decryptable generations as a proof-of-open receipt
|
|
@@ -557,6 +580,13 @@ async function formatRepoHuman(s, mirror) {
|
|
|
557
580
|
const objectCount = storage?.objects ? Object.values(storage.objects).reduce((sum, n) => sum + Number(n), 0) : null;
|
|
558
581
|
lines.push(storage ? `Storage: ${storage.source_bytes} byte(s)${objectCount != null ? ` across ${objectCount} object(s)` : ""}` : "Storage: unknown");
|
|
559
582
|
|
|
583
|
+
// gitvault-byo-primary-bucket task 3.5 — unconditional, independent of
|
|
584
|
+
// mirror status (D7).
|
|
585
|
+
if (s.vault.storage_profile === "byo") {
|
|
586
|
+
lines.push(`Storage profile: byo (${s.vault.byo_destination ?? "(unknown)"}) — ${GITVAULT_BYO_HEADLINE_STATEMENT}`);
|
|
587
|
+
lines.push(GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT);
|
|
588
|
+
}
|
|
589
|
+
|
|
560
590
|
const decryptPart = !s.keystore.holds_repo_key
|
|
561
591
|
? "CANNOT decrypt (no key in this machine's keystore)"
|
|
562
592
|
: s.keystore.can_sign
|
|
@@ -564,7 +594,11 @@ async function formatRepoHuman(s, mirror) {
|
|
|
564
594
|
: "can decrypt (read-only — no signing key)";
|
|
565
595
|
lines.push(`This machine: ${decryptPart}. Policy: ${s.gitvault_policy ?? "(none)"}`);
|
|
566
596
|
|
|
567
|
-
|
|
597
|
+
// gitvault-mirror-default: the SDK-computed vault_unmirrored finding is
|
|
598
|
+
// echoed verbatim (never rephrased here) — informational, never blocking.
|
|
599
|
+
if (mirror?.finding) {
|
|
600
|
+
lines.push(`Mirror (${mirror.finding.kind}): ${mirror.finding.message} — ${mirror.finding.setup_command}`);
|
|
601
|
+
} else if (mirror?.configured) {
|
|
568
602
|
const currency = mirror.is_current === true ? "current" : mirror.is_current === false ? "STALE" : "unknown";
|
|
569
603
|
lines.push(`Mirror: ${mirror.destination} (${currency})`);
|
|
570
604
|
}
|
|
@@ -665,7 +699,22 @@ async function inferRepoName(dir) {
|
|
|
665
699
|
|
|
666
700
|
// ─── create ─────────────────────────────────────────────────────────────────
|
|
667
701
|
|
|
668
|
-
const CREATE_VALUE_FLAGS = ["--org", "--dir", "--tier", "--idempotency-key", "--project"];
|
|
702
|
+
const CREATE_VALUE_FLAGS = ["--org", "--dir", "--tier", "--idempotency-key", "--project", "--byo", "--profile", "--region", "--endpoint"];
|
|
703
|
+
|
|
704
|
+
/** gitvault-byo-primary-bucket task 3.5 — `--byo <destination>` + the SAME credential/region/endpoint flags `repos mirror` already uses. `undefined` when `--byo` was not passed (byte-identical to today). */
|
|
705
|
+
function resolveByoOption(a) {
|
|
706
|
+
const destinationUrl = flagValue(a, "--byo");
|
|
707
|
+
if (destinationUrl == null) return undefined;
|
|
708
|
+
const credential = resolveMirrorCredential(a);
|
|
709
|
+
const region = flagValue(a, "--region");
|
|
710
|
+
const endpoint = flagValue(a, "--endpoint");
|
|
711
|
+
return {
|
|
712
|
+
destination_url: destinationUrl,
|
|
713
|
+
...(credential ? { credential } : {}),
|
|
714
|
+
...(region != null ? { region } : {}),
|
|
715
|
+
...(endpoint != null ? { endpoint } : {}),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
669
718
|
|
|
670
719
|
async function printCreateResult({ sdk, projectId, vault, adopted, name, verboseArgv }) {
|
|
671
720
|
let address = null;
|
|
@@ -689,7 +738,15 @@ async function printCreateResult({ sdk, projectId, vault, adopted, name, verbose
|
|
|
689
738
|
? nextAction("push_repo", { command: `git push -u ${vault.remote.name} HEAD`, why: "Publish the current branch to the encrypted Run402 remote." })
|
|
690
739
|
: null;
|
|
691
740
|
const claimAction = address ? null : orgSlug ? claimRepoNameAction(projectId) : claimOrgSlugAction();
|
|
692
|
-
|
|
741
|
+
// gitvault-byo-primary-bucket task 3.5: a BYO vault's "add a copy" remedy
|
|
742
|
+
// names a SECOND customer-held location (D7) — the plain mirror hint
|
|
743
|
+
// frames the mirror as the FIRST custody-held copy, which is false once
|
|
744
|
+
// the vault's own primary bucket already is one.
|
|
745
|
+
const isByo = vault.storage_profile === "byo";
|
|
746
|
+
const mirrorAction = isByo
|
|
747
|
+
? nextAction("configure_mirror", { command: "run402 repos mirror <destination>", why: GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT })
|
|
748
|
+
: nextAction("configure_mirror", { command: "run402 repos mirror <destination>", why: GITVAULT_MIRROR_SETUP_HINT });
|
|
749
|
+
const nextActions = [pushAction, mirrorAction, claimAction].filter(Boolean);
|
|
693
750
|
|
|
694
751
|
// Secret-bearing (recovery_receipt): built fresh every call, printed once,
|
|
695
752
|
// and never spilled into any cache path — see spillIfLarge's own doc
|
|
@@ -703,6 +760,8 @@ async function printCreateResult({ sdk, projectId, vault, adopted, name, verbose
|
|
|
703
760
|
genesis_sha256: vault.genesis_sha256,
|
|
704
761
|
recovery_receipt: vault.recovery_receipt,
|
|
705
762
|
terminal_loss_statement: vault.terminal_loss_statement,
|
|
763
|
+
storage_profile: vault.storage_profile,
|
|
764
|
+
byo_destination: vault.byo_destination,
|
|
706
765
|
deployed: false,
|
|
707
766
|
next_actions: nextActions,
|
|
708
767
|
};
|
|
@@ -716,6 +775,13 @@ async function printCreateResult({ sdk, projectId, vault, adopted, name, verbose
|
|
|
716
775
|
else console.error(`no address claimed — run 'run402 repos rename <name> --project ${projectId}' to claim one`);
|
|
717
776
|
if (vault.remote) console.error(`remote '${vault.remote.name}' -> ${vault.remote.url} (${vault.remote.reason})`);
|
|
718
777
|
if (pushAction) console.error(`next: ${pushAction.command}`);
|
|
778
|
+
if (isByo) {
|
|
779
|
+
console.error(`storage: byo (${vault.byo_destination}) — ${GITVAULT_BYO_HEADLINE_STATEMENT}`);
|
|
780
|
+
console.error(GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT);
|
|
781
|
+
console.error(GITVAULT_BYO_UNMIRRORED_REMEDY_STATEMENT);
|
|
782
|
+
} else {
|
|
783
|
+
console.error(GITVAULT_MIRROR_SETUP_HINT);
|
|
784
|
+
}
|
|
719
785
|
console.error("");
|
|
720
786
|
console.error(vault.terminal_loss_statement);
|
|
721
787
|
await printKeystoreLocation();
|
|
@@ -736,7 +802,8 @@ async function createAdopt(projectId, dir, a) {
|
|
|
736
802
|
});
|
|
737
803
|
}
|
|
738
804
|
try {
|
|
739
|
-
const
|
|
805
|
+
const byo = resolveByoOption(a);
|
|
806
|
+
const vault = await sdk.gitvault.init({ org_id: orgId, project_id: projectId, repo_dir: dir, ...(byo ? { byo } : {}) });
|
|
740
807
|
await printCreateResult({ sdk, projectId, vault, adopted: true, name: null, verboseArgv: a });
|
|
741
808
|
} catch (err) {
|
|
742
809
|
reportSdkError(err);
|
|
@@ -775,7 +842,8 @@ async function createProvision(name, dir, a) {
|
|
|
775
842
|
}
|
|
776
843
|
|
|
777
844
|
try {
|
|
778
|
-
const
|
|
845
|
+
const byo = resolveByoOption(a);
|
|
846
|
+
const vault = await sdk.gitvault.init({ org_id: effectiveOrgId, project_id: provisioned.project_id, repo_dir: dir, ...(byo ? { byo } : {}) });
|
|
779
847
|
await printCreateResult({ sdk, projectId: provisioned.project_id, vault, adopted: false, name, verboseArgv: a });
|
|
780
848
|
} catch (err) {
|
|
781
849
|
reportSdkError(err);
|
|
@@ -784,7 +852,7 @@ async function createProvision(name, dir, a) {
|
|
|
784
852
|
|
|
785
853
|
async function create(args) {
|
|
786
854
|
const a = normalizeArgv(args);
|
|
787
|
-
assertKnownFlags(a, [...CREATE_VALUE_FLAGS, "--help", "-h", "-v", "--verbose"], CREATE_VALUE_FLAGS);
|
|
855
|
+
assertKnownFlags(a, [...CREATE_VALUE_FLAGS, "--ambient", "--help", "-h", "-v", "--verbose"], CREATE_VALUE_FLAGS);
|
|
788
856
|
const positionals = requirePositionalCount(a, CREATE_VALUE_FLAGS, {
|
|
789
857
|
min: 0, max: 1, command: "run402 repos create [name]", missing: "",
|
|
790
858
|
});
|
|
@@ -936,10 +1004,11 @@ async function view(args) {
|
|
|
936
1004
|
// Design D3: `view` NEVER passes `refs: true` — it is side-effect-free
|
|
937
1005
|
// by construction, not by convention. Materialization belongs to `fsck`.
|
|
938
1006
|
const s = await sdk.gitvault.status(target);
|
|
1007
|
+
const isByo = s.vault?.storage_profile === "byo";
|
|
939
1008
|
let mirror = null;
|
|
940
1009
|
if (s.repo_id) {
|
|
941
1010
|
try {
|
|
942
|
-
mirror = await sdk.gitvault.mirrorStatus({ ...target, repo_id: s.repo_id });
|
|
1011
|
+
mirror = await sdk.gitvault.mirrorStatus({ ...target, repo_id: s.repo_id, is_byo: isByo });
|
|
943
1012
|
} catch {
|
|
944
1013
|
// best-effort — a mirror read failure never fails `view`
|
|
945
1014
|
}
|
|
@@ -969,10 +1038,22 @@ async function view(args) {
|
|
|
969
1038
|
if (s.pinned) {
|
|
970
1039
|
console.error(`pinned: repo_id ${s.pinned.repo_id}` + (s.pinned.resolved_from ? ` (resolved from run402::${s.pinned.resolved_from.org_slug}/${s.pinned.resolved_from.repo_name})` : ""));
|
|
971
1040
|
}
|
|
1041
|
+
// gitvault-byo-primary-bucket task 3.5: the no-payload-copy disclosure —
|
|
1042
|
+
// unconditional and independent of mirror status (D7), never folded
|
|
1043
|
+
// into the mirror finding below (that's a SEPARATE fact: "is there a
|
|
1044
|
+
// second copy", not "is there any platform-held copy at all").
|
|
1045
|
+
if (isByo) {
|
|
1046
|
+
console.error(`storage: byo (${s.vault?.byo_destination ?? "(unknown)"}) — ${GITVAULT_BYO_HEADLINE_STATEMENT}`);
|
|
1047
|
+
console.error(GITVAULT_BYO_NO_PAYLOAD_COPY_STATEMENT);
|
|
1048
|
+
}
|
|
972
1049
|
if (mirror?.configured) {
|
|
973
1050
|
const currency = mirror.is_current === true ? "current" : mirror.is_current === false ? `STALE — ${mirror.closing_command}` : "unknown (mirror unreachable or vault unread)";
|
|
974
1051
|
console.error(`mirror ${mirror.destination}: mirrored generation ${mirror.mirrored_generation ?? "(none)"}, vault newest ${mirror.newest_generation ?? "(none)"} — ${currency}`);
|
|
975
1052
|
}
|
|
1053
|
+
// gitvault-mirror-default: echoed verbatim from the SDK, exactly like the
|
|
1054
|
+
// vault warnings below — informational, never blocking, and it clears on
|
|
1055
|
+
// the first successful mirror write or sync.
|
|
1056
|
+
if (mirror?.finding) console.error(`finding (${mirror.finding.kind}): ${mirror.finding.message} — ${mirror.finding.setup_command}`);
|
|
976
1057
|
for (const w of s.warnings) console.error(`warning (${w.kind}): ${w.message}`);
|
|
977
1058
|
for (const n of combinedNextActions) console.error(`next: ${n.why ?? n.action ?? n.type}${n.command ? ` — ${n.command}` : ""}`);
|
|
978
1059
|
printVerboseStats(a, sdk);
|
|
@@ -1292,6 +1373,9 @@ async function snapshot(args) {
|
|
|
1292
1373
|
console.error("");
|
|
1293
1374
|
console.error(`repo allocated (genesis ${created.genesis_sha256}) — one-shot recovery receipt, keep many copies:`);
|
|
1294
1375
|
console.error(JSON.stringify(created.recovery_receipt));
|
|
1376
|
+
// gitvault-mirror-default: lazy allocation is a birth too — the mirror
|
|
1377
|
+
// one-liner rides beside the recovery receipt here as well.
|
|
1378
|
+
console.error(GITVAULT_MIRROR_SETUP_HINT);
|
|
1295
1379
|
await printKeystoreLocation();
|
|
1296
1380
|
console.error("");
|
|
1297
1381
|
},
|
|
@@ -1597,6 +1681,25 @@ function formatOpenProofLine(openProof) {
|
|
|
1597
1681
|
return `proof-of-open: not submitted — ${openProof.error.code}: ${openProof.error.message}`;
|
|
1598
1682
|
}
|
|
1599
1683
|
|
|
1684
|
+
/**
|
|
1685
|
+
* gitvault-byo-primary-bucket task 3.3: one line reporting `fsck`'s BYO
|
|
1686
|
+
* presence check — always present for a BYO vault (never blank), absent for
|
|
1687
|
+
* a managed one (`result.byo_presence` is `undefined` there, so this
|
|
1688
|
+
* returns `null` and no line prints — zero output change for managed
|
|
1689
|
+
* vaults). A MISSING-object verdict never reaches here: it throws
|
|
1690
|
+
* `GITVAULT_BYO_OBJECT_MISSING` before `fsck` returns a result at all, so
|
|
1691
|
+
* this line only ever reports the two non-failure outcomes — checked-clean
|
|
1692
|
+
* or explicitly not-checked. Shared by `--human` and the default JSON-mode
|
|
1693
|
+
* stderr summary, same convention as `formatOpenProofLine` above.
|
|
1694
|
+
*/
|
|
1695
|
+
function formatByoPresenceLine(byoPresence) {
|
|
1696
|
+
if (!byoPresence) return null;
|
|
1697
|
+
if (!byoPresence.verified) {
|
|
1698
|
+
return `BYO storage: NOT CHECKED — ${byoPresence.not_checked_reason}`;
|
|
1699
|
+
}
|
|
1700
|
+
return `BYO storage: verified ${byoPresence.checked_count} object(s) present at ${byoPresence.destination}.`;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1600
1703
|
/** `repos fsck --human`: the same verdict the stderr lines already carry, condensed into one block. */
|
|
1601
1704
|
function formatFsckHuman(result, mirrorRequested) {
|
|
1602
1705
|
const lines = [`Repo: ${result.repo_id}`];
|
|
@@ -1633,6 +1736,8 @@ function formatFsckHuman(result, mirrorRequested) {
|
|
|
1633
1736
|
}
|
|
1634
1737
|
const openProofLine = formatOpenProofLine(result.open_proof);
|
|
1635
1738
|
if (openProofLine) lines.push(openProofLine);
|
|
1739
|
+
const byoPresenceLine = formatByoPresenceLine(result.byo_presence);
|
|
1740
|
+
if (byoPresenceLine) lines.push(byoPresenceLine);
|
|
1636
1741
|
return lines.join("\n");
|
|
1637
1742
|
}
|
|
1638
1743
|
|
|
@@ -1690,6 +1795,14 @@ async function fsck(args) {
|
|
|
1690
1795
|
// status as a mirror probe's failure would be).
|
|
1691
1796
|
const openProofLine = formatOpenProofLine(result.open_proof);
|
|
1692
1797
|
if (openProofLine) console.error(openProofLine);
|
|
1798
|
+
// gitvault-byo-primary-bucket task 3.3: absent (formats to `null`) for a
|
|
1799
|
+
// managed vault — `result.byo_presence` is `undefined` there, so this
|
|
1800
|
+
// line never prints and JSON-mode output for a managed vault is
|
|
1801
|
+
// unchanged. A missing-object verdict never reaches this line at all —
|
|
1802
|
+
// it throws `GITVAULT_BYO_OBJECT_MISSING` before `fsck` returns, caught
|
|
1803
|
+
// by this function's own `reportSdkError(err)` below.
|
|
1804
|
+
const byoPresenceLine = formatByoPresenceLine(result.byo_presence);
|
|
1805
|
+
if (byoPresenceLine) console.error(byoPresenceLine);
|
|
1693
1806
|
if (mirrorRequested && result.mirror) {
|
|
1694
1807
|
console.error(`mirror: recoverable generation ${result.mirror.recovered_generation}${result.mirror.chain_break ? ` (chain break at ${result.mirror.chain_break.generation}: ${result.mirror.chain_break.reason})` : ""}.`);
|
|
1695
1808
|
if (result.mirror.data_loss_detected) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.64.0",
|
|
4
4
|
"description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|