run402 4.68.3 → 4.69.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/cli.mjs +2 -2
- package/core-dist/allowance-auth.js +1 -1
- package/core-dist/allowance.js +1 -1
- package/core-dist/config.js +4 -4
- package/core-dist/keystore.js +7 -7
- package/core-dist/profiles.js +1 -1
- package/core-dist/redact.js +6 -7
- package/git-remote-run402.mjs +2 -2
- package/gitvault-surface.json +2 -1
- package/lib/agent.mjs +1 -1
- package/lib/allowance.mjs +3 -4
- package/lib/argparse.mjs +7 -9
- package/lib/billing.mjs +33 -35
- package/lib/buzz-notifications.mjs +3 -2
- package/lib/buzz.mjs +14 -8
- package/lib/buzz.test.mjs +1 -1
- package/lib/command-manifest.mjs +38 -20
- package/lib/config.mjs +1 -1
- package/lib/deploy-v2.mjs +3 -3
- package/lib/doctor-source-scan.mjs +1 -1
- package/lib/doctor.mjs +42 -25
- package/lib/email.mjs +2 -3
- package/lib/harness-context.mjs +1 -2
- package/lib/init.mjs +1 -1
- package/lib/next-actions.mjs +1 -1
- package/lib/org-context.mjs +83 -2
- package/lib/org.mjs +231 -114
- package/lib/projects.mjs +2 -2
- package/lib/remote-helper-session.mjs +26 -32
- package/lib/repos.mjs +117 -15
- package/lib/rooms-context.mjs +5 -5
- package/lib/sdk-errors.mjs +1 -2
- package/lib/status.mjs +1 -1
- package/lib/update-check.test.mjs +1 -1
- package/lib/wallet-context.mjs +10 -10
- package/lib/wallet-context.test.mjs +6 -7
- package/lib/wallets.mjs +2 -2
- package/lib/webhooks.mjs +2 -2
- package/package.json +1 -1
- package/sdk/core-dist/allowance-auth.js +1 -1
- package/sdk/core-dist/allowance.js +1 -1
- package/sdk/core-dist/config.js +4 -4
- package/sdk/core-dist/keystore.js +7 -7
- package/sdk/core-dist/profiles.js +1 -1
- package/sdk/core-dist/redact.js +6 -7
- package/sdk/dist/delegate-credentials.d.ts +1 -1
- package/sdk/dist/delegate-credentials.js +1 -1
- package/sdk/dist/errors.d.ts +10 -4
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +2 -3
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/kernel.js +1 -1
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/allowance.d.ts +1 -1
- package/sdk/dist/namespaces/allowance.js +1 -1
- package/sdk/dist/namespaces/allowance.js.map +1 -1
- package/sdk/dist/namespaces/assets.js +1 -1
- package/sdk/dist/namespaces/assets.js.map +1 -1
- package/sdk/dist/namespaces/assets.types.d.ts +1 -1
- package/sdk/dist/namespaces/ci.d.ts.map +1 -1
- package/sdk/dist/namespaces/ci.js +4 -6
- package/sdk/dist/namespaces/ci.js.map +1 -1
- package/sdk/dist/namespaces/deploy.js +7 -8
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +86 -3
- package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.js +102 -3
- package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +197 -17
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +698 -58
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.types.d.ts +91 -3
- package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.types.js.map +1 -1
- package/sdk/dist/namespaces/operator.d.ts +1 -1
- package/sdk/dist/namespaces/operator.js +1 -1
- package/sdk/dist/namespaces/org.d.ts +14 -1
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +15 -0
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/org.types.d.ts +30 -0
- package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +1 -1
- package/sdk/dist/namespaces/subdomains.js +1 -1
- package/sdk/dist/namespaces/subdomains.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts +3 -4
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
- package/sdk/dist/namespaces/transfers.js +1 -2
- package/sdk/dist/namespaces/transfers.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +3 -4
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +2 -2
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/gitvault-address.js +2 -2
- package/sdk/dist/node/gitvault-address.js.map +1 -1
- package/sdk/dist/node/gitvault-creation-journal.d.ts +1 -1
- package/sdk/dist/node/gitvault-creation-journal.js +2 -2
- package/sdk/dist/node/gitvault-creation-journal.js.map +1 -1
- package/sdk/dist/node/gitvault-deploy.d.ts +2 -2
- package/sdk/dist/node/gitvault-deploy.js +2 -2
- package/sdk/dist/node/gitvault-handoff.d.ts +140 -1
- package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-handoff.js +226 -22
- package/sdk/dist/node/gitvault-handoff.js.map +1 -1
- package/sdk/dist/node/gitvault-keystore.d.ts +78 -3
- package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-keystore.js +46 -5
- package/sdk/dist/node/gitvault-keystore.js.map +1 -1
- package/sdk/dist/node/gitvault-mirror.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-mirror.js +3 -5
- package/sdk/dist/node/gitvault-mirror.js.map +1 -1
- package/sdk/dist/node/gitvault-profile-scan.js +1 -1
- package/sdk/dist/node/gitvault-publication.d.ts +379 -21
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-publication.js +664 -41
- package/sdk/dist/node/gitvault-publication.js.map +1 -1
- package/sdk/dist/node/gitvault-recover.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-recover.js +7 -10
- package/sdk/dist/node/gitvault-recover.js.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.d.ts +1 -2
- package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.js +1 -2
- package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
- package/sdk/dist/node/gitvault-writer-state.d.ts +201 -0
- package/sdk/dist/node/gitvault-writer-state.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-writer-state.js +357 -0
- package/sdk/dist/node/gitvault-writer-state.js.map +1 -0
- package/sdk/dist/node/index.d.ts +4 -0
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +20 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
- package/sdk/dist/node/paid-fetch.js +3 -4
- package/sdk/dist/node/paid-fetch.js.map +1 -1
- package/sdk/dist/node/sites-node.js +1 -1
- package/sdk/dist/node/sites-node.js.map +1 -1
|
@@ -84,12 +84,12 @@
|
|
|
84
84
|
* no per-ref object selection, so one batch = one full restore. That is a
|
|
85
85
|
* superset of what git asked for (git writes the refs itself from `list`),
|
|
86
86
|
* never a subset — but it is not incremental.
|
|
87
|
-
* - `push` REPAIRS a DANGLING vault HEAD and otherwise never moves it
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* `git clone`
|
|
92
|
-
* out an EMPTY tree — publishing
|
|
87
|
+
* - `push` REPAIRS a DANGLING vault HEAD and otherwise never moves it.
|
|
88
|
+
* A fresh vault defaults its HEAD symref to `refs/heads/main`. Without
|
|
89
|
+
* the repair, a first push of any OTHER branch would leave that symref
|
|
90
|
+
* naming a ref that never exists, and the first
|
|
91
|
+
* `git clone` warns "remote HEAD refers to nonexistent ref" and checks
|
|
92
|
+
* out an EMPTY tree — publishing lands, but nothing is reachable from
|
|
93
93
|
* it. Now: when the vault's current HEAD target is unset, or is a
|
|
94
94
|
* symref naming a ref this push's own batch does not leave present, the
|
|
95
95
|
* helper points it at one of the branches THIS push is publishing — the
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
* which and why (see `chooseGitvaultHeadTargetForPush`). A HEALTHY HEAD
|
|
99
99
|
* (one that already names a ref this push leaves present) is NEVER
|
|
100
100
|
* touched — push moving history never means push moving HEAD.
|
|
101
|
-
* - `option dry-run true`
|
|
101
|
+
* - `option dry-run true` runs the REAL local
|
|
102
102
|
* pipeline — pack building, encryption sizing, via `vault.planPush` — and
|
|
103
103
|
* reports the per-ref `ok` lines a real push would, plus a stderr summary
|
|
104
104
|
* (objects, encrypted bytes, refs, the generation it would admit as,
|
|
@@ -210,12 +210,11 @@ const endBlock = () => process.stdout.write("\n");
|
|
|
210
210
|
const note = (line) => process.stderr.write(`git-remote-run402: ${line}\n`);
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
|
-
* Wallet selection (
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* `run402 repos create` used the WRONG wallet's (usually empty) allowance.
|
|
213
|
+
* Wallet selection. Calling `getSdk()` directly here would run NO wallet
|
|
214
|
+
* selection at all — a `.run402.json` binding, and even the global
|
|
215
|
+
* `wallets use` default, would silently never reach it; only the
|
|
216
|
+
* `RUN402_WALLET` env layer would work, so a bound checkout's `git push
|
|
217
|
+
* run402 main` would use the WRONG wallet's (usually empty) allowance.
|
|
219
218
|
*
|
|
220
219
|
* Shares `resolveWalletCore`/`enforceWalletExistsCore` with the CLI
|
|
221
220
|
* (`cli/lib/wallet-context.mjs`) — ONE implementation, minus the CLI's
|
|
@@ -261,7 +260,7 @@ function walletSourceLabel(resolved) {
|
|
|
261
260
|
* an env var or a binding DID name one, the remedy is actively harmful —
|
|
262
261
|
* `run402 init` recreates the DEFAULT wallet's allowance, a DIFFERENT
|
|
263
262
|
* wallet than the one that was actually resolved and whose allowance is
|
|
264
|
-
* actually missing/broken
|
|
263
|
+
* actually missing/broken. Replace
|
|
265
264
|
* it with the resolved wallet's name and how selection works, so the fix is
|
|
266
265
|
* "correct the selection" rather than "recreate the wrong wallet".
|
|
267
266
|
*/
|
|
@@ -358,7 +357,7 @@ export function partitionProtectedRefPushes(specs) {
|
|
|
358
357
|
|
|
359
358
|
/**
|
|
360
359
|
* Decide whether THIS push must repair a DANGLING vault HEAD, and to what
|
|
361
|
-
*
|
|
360
|
+
* — the first-clone empty-tree hazard. The rule:
|
|
362
361
|
*
|
|
363
362
|
* WHEN the vault's current materialized HEAD target is absent, OR is a
|
|
364
363
|
* symref naming a ref this push's own batch does not leave present, set
|
|
@@ -432,11 +431,10 @@ export function shouldRunAutoGc(threshold, generationsSinceCheckpoint) {
|
|
|
432
431
|
* `process.cwd()` only in CALLER convention, never inside the SDK's own
|
|
433
432
|
* `open()` (which sets it ONLY when `options.repo_dir !== undefined`).
|
|
434
433
|
* Building the auto-gc target from `{ ...target }` alone — omitting
|
|
435
|
-
* `repo_dir` —
|
|
434
|
+
* `repo_dir` — makes every real post-push auto-gc cycle fail closed with
|
|
436
435
|
* `GITVAULT_REPO_DIR_REQUIRED`, silently degrading to the advisory on
|
|
437
|
-
* EVERY push
|
|
438
|
-
*
|
|
439
|
-
* wrapper passes repo_dir explicitly and never hit this).
|
|
436
|
+
* EVERY push. `repos gc`'s CLI wrapper passes repo_dir explicitly, so it
|
|
437
|
+
* never exercises this path.
|
|
440
438
|
*/
|
|
441
439
|
export function buildAutoGcCompactionTarget(target, repoDir) {
|
|
442
440
|
return { ...target, repo_dir: repoDir };
|
|
@@ -462,10 +460,8 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
462
460
|
const addressForm = gitvaultRemoteAddressForm(address);
|
|
463
461
|
const target = { project_id: address.project_id, org_id: address.org_id };
|
|
464
462
|
let verbosity = 1;
|
|
465
|
-
//
|
|
466
|
-
// `
|
|
467
|
-
// reporting a fake success would be worse than refusing). It now IS
|
|
468
|
-
// real — see `handleOption`'s `dry-run` case and `runPush` below.
|
|
463
|
+
// `option dry-run true` is a REAL dry run — see `handleOption`'s
|
|
464
|
+
// `dry-run` case and `runPush` below. It never reports a fake success.
|
|
469
465
|
let dryRun = false;
|
|
470
466
|
|
|
471
467
|
/**
|
|
@@ -533,8 +529,8 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
533
529
|
}
|
|
534
530
|
|
|
535
531
|
/**
|
|
536
|
-
* What to tell a human when we refuse. `git clone` is the case that
|
|
537
|
-
*
|
|
532
|
+
* What to tell a human when we refuse. `git clone` is the case that
|
|
533
|
+
* fails here; naming the working alternative beats a bare error.
|
|
538
534
|
*/
|
|
539
535
|
function repoRefusalNote(err) {
|
|
540
536
|
note(describeError(err));
|
|
@@ -757,7 +753,7 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
757
753
|
for (const ref of Object.keys(refs).sort()) out(`${refs[ref]} ${ref}`);
|
|
758
754
|
// A snapshot-only vault holds protocol refs but no branch heads, so a
|
|
759
755
|
// plain `git clone` prints "cloned an empty repository" with no hint the
|
|
760
|
-
// history exists
|
|
756
|
+
// history exists. Say where it is.
|
|
761
757
|
const refNames = Object.keys(refs);
|
|
762
758
|
if (refNames.length > 0 && !refNames.some((r) => r.startsWith("refs/heads/"))) {
|
|
763
759
|
note(`this vault has no branch heads yet — its history lives on ${refNames.sort()[0]}`);
|
|
@@ -898,9 +894,7 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
898
894
|
// convention, not an SDK fallback (`open()` only sets repo_dir when
|
|
899
895
|
// `options.repo_dir !== undefined`). Omitting it here made every real
|
|
900
896
|
// post-push auto-gc cycle fail closed with GITVAULT_REPO_DIR_REQUIRED,
|
|
901
|
-
// silently degrading to the advisory on every push
|
|
902
|
-
// 2026-08-31, via a push-latency guard bench that finally drove the
|
|
903
|
-
// ACTUAL trigger path instead of a synthetic `repos gc` invocation).
|
|
897
|
+
// silently degrading to the advisory on every push.
|
|
904
898
|
const compactionTarget = buildAutoGcCompactionTarget(target, repoDir);
|
|
905
899
|
const runCycle = async () => {
|
|
906
900
|
const sdk = getCachedSdk();
|
|
@@ -966,7 +960,7 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
966
960
|
}
|
|
967
961
|
|
|
968
962
|
if (dryRun) {
|
|
969
|
-
//
|
|
963
|
+
// READ-ONLY resolution — `openVault`, never
|
|
970
964
|
// `openOrCreateVault` — so a push-to-create dry run allocates
|
|
971
965
|
// NOTHING. An unresolved vault means there is nothing to preview a
|
|
972
966
|
// push against yet (no repo_id ⇒ no encryption key ⇒ sizing is
|
|
@@ -1034,7 +1028,7 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
1034
1028
|
force: spec.force && expectedOld !== null,
|
|
1035
1029
|
});
|
|
1036
1030
|
}
|
|
1037
|
-
// Repair a DANGLING HEAD from this batch's own branches
|
|
1031
|
+
// Repair a DANGLING HEAD from this batch's own branches — see
|
|
1038
1032
|
// `chooseGitvaultHeadTargetForPush`'s own doc comment for the exact
|
|
1039
1033
|
// rule. `localHeadRef` is read from THIS repository (never cwd, same
|
|
1040
1034
|
// fail-closed resolution as everything else in this function).
|
|
@@ -1114,7 +1108,7 @@ async function main(argv, { onBackgroundWork } = {}) {
|
|
|
1114
1108
|
out("ok");
|
|
1115
1109
|
return;
|
|
1116
1110
|
case "dry-run":
|
|
1117
|
-
//
|
|
1111
|
+
// a REAL dry run — `runPush` runs the actual
|
|
1118
1112
|
// local pipeline (pack building, encryption sizing) and stops before
|
|
1119
1113
|
// the two network mutations. `value` is git's own boolean spelling
|
|
1120
1114
|
// ("true"/"false"); anything else is refused rather than guessed.
|
package/lib/repos.mjs
CHANGED
|
@@ -95,6 +95,7 @@ Maintenance:
|
|
|
95
95
|
run402 repos access repair [--project <id>] [--repo <repo_id>] --recipient-state-version <n> --recipient-revocation-version <n>
|
|
96
96
|
run402 repos access revoke-key <principal_id> [--project <id>] [--repo <repo_id>]
|
|
97
97
|
run402 repos access declare-exposure [--project <id>] [--repo <repo_id>]
|
|
98
|
+
run402 repos access repin [--project <id>] [--repo <repo_id>] --principal <principal_id> --fingerprint <ek_fingerprint>
|
|
98
99
|
run402 repos policy <required|grandfathered> [--project <id>] [--repo <repo_id>] [--reason <why>]
|
|
99
100
|
run402 repos policy auto-gc [<generations>|off] (local, per-checkout — no --project/--repo)
|
|
100
101
|
|
|
@@ -407,6 +408,10 @@ Options:
|
|
|
407
408
|
verified prefix, so a budget-exceeded run resumes; a
|
|
408
409
|
--no-write run does not, since nothing was persisted)
|
|
409
410
|
--mirror fsck: also run the keyless mirror integrity probe
|
|
411
|
+
--no-reconcile view/access: defer the session-start envelope fulfilment
|
|
412
|
+
(a key-holder normally wraps every pending member on its
|
|
413
|
+
first ordinary read); reported as deferred_by_local_policy,
|
|
414
|
+
never as coverage. fsck/recover never wrap regardless.
|
|
410
415
|
--no-write fsck: audit mode — compute and report the real answer,
|
|
411
416
|
persist neither local trust pin
|
|
412
417
|
--submit gc: submit the planned prune intent. Requires
|
|
@@ -629,6 +634,29 @@ async function formatRepoHuman(s, mirror) {
|
|
|
629
634
|
: "can decrypt (read-only — no signing key)";
|
|
630
635
|
lines.push(`This machine: ${decryptPart}. Policy: ${s.gitvault_policy ?? "(none)"}`);
|
|
631
636
|
|
|
637
|
+
// gitvault-multi-writer (rev 47) task 6.1 — the writer roster, one line
|
|
638
|
+
// per writer: `writer_set` is the chain-verified set exactly as `verify`/
|
|
639
|
+
// `fsck` would derive it (never re-verified here — this is `view`'s own
|
|
640
|
+
// side-effect-free read of the vault RECORD's already-verified pointer).
|
|
641
|
+
// `pending_writers` is the reverse direction (eligible org members not
|
|
642
|
+
// yet admitted); `read_only_terminal` (D228) is the forced sole-writer-
|
|
643
|
+
// removal terminal — surfaced prominently since a vault in that state
|
|
644
|
+
// still serves reads but can never accept another push until recovered.
|
|
645
|
+
const writerSet = s.vault.writer_set;
|
|
646
|
+
if (writerSet) {
|
|
647
|
+
lines.push(`Writers (${writerSet.writers.length}):`);
|
|
648
|
+
for (const w of writerSet.writers) {
|
|
649
|
+
lines.push(` ${w.writer_key_id} — admitted generation ${decimal(w.admitted_generation)} (${w.authorization_kind})`);
|
|
650
|
+
}
|
|
651
|
+
if (s.vault.read_only_terminal) {
|
|
652
|
+
lines.push(" ⚠ read-only terminal: the last writer was removed — this vault serves reads but cannot accept a push until a new writer is admitted through a recovery path.");
|
|
653
|
+
}
|
|
654
|
+
const pendingWriters = s.vault.pending_writers ?? [];
|
|
655
|
+
if (pendingWriters.length > 0) {
|
|
656
|
+
lines.push(`Pending writers (${pendingWriters.length}, eligible but not yet admitted): ${pendingWriters.map((p) => p.writer_key_id).join(", ")} — run 'run402 repos access sync' if this machine is already a writer.`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
632
660
|
// gitvault-mirror-default: the SDK-computed vault_unmirrored finding is
|
|
633
661
|
// echoed verbatim (never rephrased here) — informational, never blocking.
|
|
634
662
|
if (mirror?.finding) {
|
|
@@ -855,10 +883,9 @@ async function createProvision(name, dir, a) {
|
|
|
855
883
|
|
|
856
884
|
// A genuinely bare machine has no allowance file at all, and the NO_ALLOWANCE
|
|
857
885
|
// precheck below would refuse before the provision call could ever answer
|
|
858
|
-
// NO_ACTIVE_TIER
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
// the bare refusal.
|
|
886
|
+
// NO_ACTIVE_TIER, so the cold-start fold would never reach the one case it
|
|
887
|
+
// exists for. Fold first when there is no wallet; `--no-init` keeps the
|
|
888
|
+
// bare refusal.
|
|
862
889
|
if (!isCoreApiTarget() && !loadLiveControlPlaneSession() && !readAllowance() && !a.includes("--no-init")) {
|
|
863
890
|
console.error("no wallet on this machine — folding the cold-start chain (allowance -> faucet -> prototype tier)");
|
|
864
891
|
try {
|
|
@@ -1066,7 +1093,7 @@ async function list(args) {
|
|
|
1066
1093
|
|
|
1067
1094
|
async function view(args) {
|
|
1068
1095
|
const a = normalizeArgv(args);
|
|
1069
|
-
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "--human", "-v", "--verbose", "--help", "-h"], COMMON_VALUE_FLAGS);
|
|
1096
|
+
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "--human", "--no-reconcile", "-v", "--verbose", "--help", "-h"], COMMON_VALUE_FLAGS);
|
|
1070
1097
|
requirePositionalCount(a, COMMON_VALUE_FLAGS, { min: 0, max: 0, command: "run402 repos view", missing: "" });
|
|
1071
1098
|
const human = a.includes("--human");
|
|
1072
1099
|
if (human && a.includes("--json")) {
|
|
@@ -1077,7 +1104,7 @@ async function view(args) {
|
|
|
1077
1104
|
try {
|
|
1078
1105
|
// Design D3: `view` NEVER passes `refs: true` — it is side-effect-free
|
|
1079
1106
|
// by construction, not by convention. Materialization belongs to `fsck`.
|
|
1080
|
-
const s = await sdk.gitvault.status(target);
|
|
1107
|
+
const s = await sdk.gitvault.status({ ...target, ...(a.includes("--no-reconcile") ? { reconcile: "deferred" } : {}) });
|
|
1081
1108
|
const isByo = s.vault?.storage_profile === "byo";
|
|
1082
1109
|
let mirror = null;
|
|
1083
1110
|
if (s.repo_id) {
|
|
@@ -1493,8 +1520,7 @@ async function snapshot(args) {
|
|
|
1493
1520
|
if (manifestOutPath != null) writeManifestOut(manifestOutPath, result);
|
|
1494
1521
|
// Snapshot-only vault (no branch head published): a plain `git clone`
|
|
1495
1522
|
// of this vault prints "cloned an empty repository" with no hint the
|
|
1496
|
-
// snapshot exists
|
|
1497
|
-
// restore path in the result itself.
|
|
1523
|
+
// snapshot exists. Name the restore path in the result itself.
|
|
1498
1524
|
const resultRefNames = Object.keys(result.refs ?? {});
|
|
1499
1525
|
const snapshotOnly = resultRefNames.length > 0 && !resultRefNames.some((r) => r.startsWith("refs/heads/"));
|
|
1500
1526
|
const payload = summarizeSnapshotPayload(result, { verbose, manifestPath: manifestOutPath });
|
|
@@ -1674,6 +1700,12 @@ async function handoff(args) {
|
|
|
1674
1700
|
console.error(w.message ?? `${w.code}`);
|
|
1675
1701
|
}
|
|
1676
1702
|
console.error(`handoff minted: role ${result.minted_role}, expires ${result.expires_at}`);
|
|
1703
|
+
// gitvault-multi-writer (rev 47) task 6.4 — a handoff is now also a
|
|
1704
|
+
// WRITER admission, not just a checkout pass: the recipient signs its
|
|
1705
|
+
// own future pushes with a NEW key this vault's chain recognizes as a
|
|
1706
|
+
// writer the moment `resume` claims it (design D4 — a grant minted here,
|
|
1707
|
+
// a two-signature acceptance the recipient's own resume completes).
|
|
1708
|
+
console.error(`the recipient becomes a WRITER on this vault the moment they resume — their own key signs future pushes, not yours.`);
|
|
1677
1709
|
console.error(`recipient runs: kygit resume <key printed below>`);
|
|
1678
1710
|
if (asJson) {
|
|
1679
1711
|
printJson(sdk, result);
|
|
@@ -1781,8 +1813,7 @@ async function resume(args) {
|
|
|
1781
1813
|
key = (await readStdinText()).trim();
|
|
1782
1814
|
if (!key) fail({ code: "BAD_USAGE", message: "Missing the Handoff Key on stdin.", hint: "Pipe the kgh1_… key, or pass it as a positional argument." });
|
|
1783
1815
|
}
|
|
1784
|
-
//
|
|
1785
|
-
// resume"): a resumed agent is a NEW run402 wallet, and the loop is the
|
|
1816
|
+
// A resumed agent is a NEW run402 wallet, and the loop is the
|
|
1786
1817
|
// point — so on a wallet with no active tier `resume` folds the same
|
|
1787
1818
|
// cold-start chain `create` does (allowance → faucet → one x402
|
|
1788
1819
|
// prototype payment, announced) BEFORE the claim. The claim itself needs
|
|
@@ -1804,6 +1835,13 @@ async function resume(args) {
|
|
|
1804
1835
|
}
|
|
1805
1836
|
console.error("");
|
|
1806
1837
|
console.error(`resumed into ${result.restored.dir} (branch ${result.restored.branch})`);
|
|
1838
|
+
// gitvault-multi-writer (rev 47) task 6.4 — this checkout's own writer
|
|
1839
|
+
// activation (design D5): the outcome is "active" either way, whether
|
|
1840
|
+
// this call submitted a fresh activation head or a prior attempt's
|
|
1841
|
+
// already landed (crash-resumable, task 5.6's own idempotent-skip).
|
|
1842
|
+
if (result.writer_activation) {
|
|
1843
|
+
console.error(`writer: ${result.writer_activation.outcome} (generation ${result.writer_activation.generation})`);
|
|
1844
|
+
}
|
|
1807
1845
|
if (result.deduplicated) console.error("note: this key was already claimed by this same principal — the ORIGINAL envelope was reused (safe replay)");
|
|
1808
1846
|
for (const na of result.next_actions ?? []) {
|
|
1809
1847
|
if (na.command) console.error(`next: ${na.command}${na.why ? ` — ${na.why}` : ""}`);
|
|
@@ -2355,7 +2393,7 @@ function printMemberCustodySummary(mc) {
|
|
|
2355
2393
|
|
|
2356
2394
|
async function accessRead(args) {
|
|
2357
2395
|
const a = normalizeArgv(args);
|
|
2358
|
-
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "--human", "-v", "--verbose", "--help", "-h"], COMMON_VALUE_FLAGS);
|
|
2396
|
+
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "--human", "--no-reconcile", "-v", "--verbose", "--help", "-h"], COMMON_VALUE_FLAGS);
|
|
2359
2397
|
requirePositionalCount(a, COMMON_VALUE_FLAGS, { min: 0, max: 0, command: "run402 repos access", missing: "" });
|
|
2360
2398
|
const human = a.includes("--human");
|
|
2361
2399
|
if (human && a.includes("--json")) {
|
|
@@ -2364,7 +2402,7 @@ async function accessRead(args) {
|
|
|
2364
2402
|
const sdk = getSdk();
|
|
2365
2403
|
const target = await vaultTarget(a);
|
|
2366
2404
|
try {
|
|
2367
|
-
const result = await sdk.gitvault.access(target);
|
|
2405
|
+
const result = await sdk.gitvault.access({ ...target, ...(a.includes("--no-reconcile") ? { reconcile: "deferred" } : {}) });
|
|
2368
2406
|
// gitvault-recovery-custody — the "you" block: YOUR OWN wrapper custody
|
|
2369
2407
|
// (kind/state per wrapper, custody scheme), rendered inside the family's
|
|
2370
2408
|
// custody roster read. Principal-scoped, so it needs your control-plane
|
|
@@ -2523,8 +2561,8 @@ async function accessRevokeKey(args) {
|
|
|
2523
2561
|
* self-contained; the FOLLOW-UP rotation it authorizes is NOT auto-run
|
|
2524
2562
|
* here, because — same confirmed gap as `access repair` — the D194
|
|
2525
2563
|
* counters it must be fenced against have no client-visible read for this
|
|
2526
|
-
* reason value either. This is the rekey remedy
|
|
2527
|
-
*
|
|
2564
|
+
* reason value either. This is the rekey remedy for an exposed key:
|
|
2565
|
+
* declare here, then rotate (via `--recipient-state-version`/
|
|
2528
2566
|
* `--recipient-revocation-version` once known, e.g. from platform staff).
|
|
2529
2567
|
*
|
|
2530
2568
|
* **If a `/confirm`/`/repin` receipt is already pending** (a directory
|
|
@@ -2532,7 +2570,7 @@ async function accessRevokeKey(args) {
|
|
|
2532
2570
|
* the rotation is outstanding), do NOT call `publishPinManifestUpdate`
|
|
2533
2571
|
* separately — that call is itself an ORDINARY admission and is itself
|
|
2534
2572
|
* refused `EPOCH_ROTATION_REQUIRED` for as long as this declaration stays
|
|
2535
|
-
* outstanding
|
|
2573
|
+
* outstanding. Pass the receipt
|
|
2536
2574
|
* to `r.gitvault.rotateEpoch({..., pending_confirmations: [{principal_id,
|
|
2537
2575
|
* ek_fingerprint, receipt}]})` instead — it rides the SAME head as the
|
|
2538
2576
|
* rotation this declaration requires, publishing durably without needing a
|
|
@@ -2541,6 +2579,33 @@ async function accessRevokeKey(args) {
|
|
|
2541
2579
|
* excluded from THIS rotation's own envelope set (D196) and becomes
|
|
2542
2580
|
* eligible starting at the NEXT rotation.
|
|
2543
2581
|
*/
|
|
2582
|
+
/**
|
|
2583
|
+
* `repos access repin --principal <id> --fingerprint <ek_…>` — a KEY-HOLDER
|
|
2584
|
+
* explicitly accepts a recipient's CHANGED key (gitvault-agent-envelopes D3).
|
|
2585
|
+
* The session-start reconcile refuses `pinned_key_mismatch` and never
|
|
2586
|
+
* bypasses it, not even after an owner's revoke — acceptance names the new
|
|
2587
|
+
* fingerprint (the out-of-band verification point) and moves the local pin.
|
|
2588
|
+
* Adapter only: `sdk.gitvault.acceptRecipientKeyChange`.
|
|
2589
|
+
*/
|
|
2590
|
+
async function accessRepin(args) {
|
|
2591
|
+
const a = normalizeArgv(args);
|
|
2592
|
+
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "--principal", "--fingerprint", "--help", "-h"], [...COMMON_VALUE_FLAGS, "--principal", "--fingerprint"]);
|
|
2593
|
+
requirePositionalCount(a, [...COMMON_VALUE_FLAGS, "--principal", "--fingerprint"], { min: 0, max: 0, command: "run402 repos access repin --principal <principal_id> --fingerprint <ek_fingerprint>", missing: "" });
|
|
2594
|
+
const principalId = flagValue(a, "--principal");
|
|
2595
|
+
const fingerprint = flagValue(a, "--fingerprint");
|
|
2596
|
+
if (!principalId || !fingerprint) {
|
|
2597
|
+
fail({ code: "BAD_USAGE", message: "repin needs --principal <principal_id> and --fingerprint <ek_fingerprint> (read the fingerprint back with the recipient — it is public data).", hint: "run402 repos access repin --principal <principal_id> --fingerprint <ek_fingerprint>" });
|
|
2598
|
+
}
|
|
2599
|
+
const sdk = getSdk();
|
|
2600
|
+
const target = await vaultTarget(a);
|
|
2601
|
+
try {
|
|
2602
|
+
const result = await sdk.gitvault.acceptRecipientKeyChange({ ...target, principal_id: principalId, new_fingerprint: fingerprint });
|
|
2603
|
+
console.log(JSON.stringify({ ...result, next_actions: [{ type: "retry", command: "run402 repos view", why: "the next ordinary gitvault operation on this machine wraps the vault to the accepted key" }] }, null, 2));
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
reportSdkError(err);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2544
2609
|
async function accessDeclareExposure(args) {
|
|
2545
2610
|
const a = normalizeArgv(args);
|
|
2546
2611
|
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "-v", "--verbose", "--help", "-h"], COMMON_VALUE_FLAGS);
|
|
@@ -2561,11 +2626,48 @@ async function accessDeclareExposure(args) {
|
|
|
2561
2626
|
}
|
|
2562
2627
|
}
|
|
2563
2628
|
|
|
2629
|
+
// gitvault-multi-writer (rev 47) task 6.4 — an on-demand tail on the
|
|
2630
|
+
// existing `access` family: `r.gitvault.reconcile()` admits every eligible
|
|
2631
|
+
// `pending_writers[]` candidate right now, rather than waiting for this
|
|
2632
|
+
// machine's next push/deploy to do it as a side effect. Same shape as
|
|
2633
|
+
// `accessRepin` above — one SDK call, echo the result, a next_action
|
|
2634
|
+
// pointing at the normal follow-up.
|
|
2635
|
+
async function accessSync(args) {
|
|
2636
|
+
const a = normalizeArgv(args);
|
|
2637
|
+
assertKnownFlags(a, [...COMMON_VALUE_FLAGS, "--human", "-v", "--verbose", "--help", "-h"], COMMON_VALUE_FLAGS);
|
|
2638
|
+
requirePositionalCount(a, COMMON_VALUE_FLAGS, { min: 0, max: 0, command: "run402 repos access sync", missing: "" });
|
|
2639
|
+
const human = a.includes("--human");
|
|
2640
|
+
if (human && a.includes("--json")) {
|
|
2641
|
+
fail({ code: "BAD_USAGE", message: "--human cannot be combined with --json.", details: { flags: a.filter((arg) => arg === "--human" || arg === "--json") } });
|
|
2642
|
+
}
|
|
2643
|
+
const sdk = getSdk();
|
|
2644
|
+
const target = await vaultTarget(a);
|
|
2645
|
+
try {
|
|
2646
|
+
const result = await sdk.gitvault.reconcile(target);
|
|
2647
|
+
if (!human) console.log(JSON.stringify(result, null, 2));
|
|
2648
|
+
if (!result.eligible) {
|
|
2649
|
+
console.error("this machine's key is not an active writer on this vault — nothing to sync. Ask a current writer to admit you (org membership at role developer+ and a published signing key make you eligible).");
|
|
2650
|
+
} else if (result.admitted.length === 0) {
|
|
2651
|
+
console.error("no pending writer candidates — nothing to sync.");
|
|
2652
|
+
} else {
|
|
2653
|
+
for (const w of result.admitted) console.error(`admitted ${w.writer_key_id} (${w.principal_id}) at generation ${w.generation}.`);
|
|
2654
|
+
}
|
|
2655
|
+
if (result.skipped.length > 0) {
|
|
2656
|
+
for (const s of result.skipped) console.error(`skipped ${s.writer_key_id} (${s.principal_id}): ${s.reason}`);
|
|
2657
|
+
}
|
|
2658
|
+
printVerboseStats(a, sdk);
|
|
2659
|
+
} catch (err) {
|
|
2660
|
+
reportSdkError(err);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2564
2664
|
async function access(args) {
|
|
2565
2665
|
const a = normalizeArgv(args);
|
|
2566
2666
|
if (a[0] === "repair") return accessRepair(a.slice(1));
|
|
2567
2667
|
if (a[0] === "revoke-key") return accessRevokeKey(a.slice(1));
|
|
2568
2668
|
if (a[0] === "declare-exposure") return accessDeclareExposure(a.slice(1));
|
|
2669
|
+
if (a[0] === "repin") return accessRepin(a.slice(1));
|
|
2670
|
+
if (a[0] === "sync") return accessSync(a.slice(1));
|
|
2569
2671
|
return accessRead(a);
|
|
2570
2672
|
}
|
|
2571
2673
|
|
package/lib/rooms-context.mjs
CHANGED
|
@@ -54,7 +54,7 @@ const STATE_FILE = "messaging.json";
|
|
|
54
54
|
/**
|
|
55
55
|
* Resolve the addressed room to { orgId, roomKey } (one SDK lookup at most).
|
|
56
56
|
*
|
|
57
|
-
* A room is a PAIR, and this function owns the pair.
|
|
57
|
+
* A room is a PAIR, and this function owns the pair. It does NOT own
|
|
58
58
|
* the org half of its own fallback: below the explicit forms, the org comes
|
|
59
59
|
* from the shared chain in `org-context.mjs` (flag → env → binding → profile
|
|
60
60
|
* selection → active project) while the room key comes from `--room`, the
|
|
@@ -71,8 +71,8 @@ export async function resolveRoom({ org, room, project } = {}) {
|
|
|
71
71
|
if (envRoom) {
|
|
72
72
|
const slash = envRoom.indexOf("/");
|
|
73
73
|
if (slash <= 0 || slash === envRoom.length - 1) {
|
|
74
|
-
//
|
|
75
|
-
//
|
|
74
|
+
// A malformed RUN402_ROOM is a value we know nothing about (a leaked
|
|
75
|
+
// secret included), so it must not be echoed raw.
|
|
76
76
|
fail({
|
|
77
77
|
code: "BAD_USAGE",
|
|
78
78
|
message: `${ROOM_ENV} must be "<org_id>/<room_key>".`,
|
|
@@ -239,8 +239,8 @@ export async function registerFreshPresence(orgId, roomKey, { name, task } = {})
|
|
|
239
239
|
* REGISTER a replacement, and retry ONCE with it. The registration is
|
|
240
240
|
* explicit on purpose: a bare call cannot mean "me" from the credential
|
|
241
241
|
* alone, because the server cannot tell one credential's concurrent sessions
|
|
242
|
-
* apart — it would either guess (adopting a sibling session's presence
|
|
243
|
-
*
|
|
242
|
+
* apart — it would either guess (adopting a sibling session's presence)
|
|
243
|
+
* or refuse. Asking — by session identity when we have
|
|
244
244
|
* one, by explicit (re-)registration when we don't — is unambiguous.
|
|
245
245
|
*/
|
|
246
246
|
export async function withPresenceRetry(orgId, roomKey, call, { name, task } = {}) {
|
package/lib/sdk-errors.mjs
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* `process.exit(1)`. Preserves specific behaviors:
|
|
7
7
|
* - `ProjectCredentialNotFound` → preserves
|
|
8
8
|
* `code: "PROJECT_CREDENTIAL_NOT_FOUND"` and local-cache provenance.
|
|
9
|
-
* - HTML / non-JSON error bodies → `body_preview` field (first 500 chars)
|
|
10
|
-
* matching GH-84 behavior.
|
|
9
|
+
* - HTML / non-JSON error bodies → `body_preview` field (first 500 chars).
|
|
11
10
|
* - Network errors → `{status: "error", message: "..."}`.
|
|
12
11
|
*
|
|
13
12
|
* For client-side validation failures (missing flags, bad JSON, no-op
|
package/lib/status.mjs
CHANGED
|
@@ -137,7 +137,7 @@ export async function run(args = []) {
|
|
|
137
137
|
const walletName = getActiveProfile();
|
|
138
138
|
const walletMeta = readMeta(walletName);
|
|
139
139
|
|
|
140
|
-
//
|
|
140
|
+
// Balances are grouped under one object so the on-chain and
|
|
141
141
|
// prepaid-credit numbers are unambiguous and rail-legible.
|
|
142
142
|
// - on_chain_usd_micros / on_chain_token: on-chain USDC (x402) or pathUSD
|
|
143
143
|
// (mpp), null if the RPC read failed
|
|
@@ -448,7 +448,7 @@ describe("CLI update notices and scheduler", () => {
|
|
|
448
448
|
assert.equal(check.status, "skipped");
|
|
449
449
|
}));
|
|
450
450
|
|
|
451
|
-
// ─── the real TTL, and what happens at every edge of it
|
|
451
|
+
// ─── the real TTL, and what happens at every edge of it ──
|
|
452
452
|
//
|
|
453
453
|
// THE DEFECT (grok dogfood, 4.38.0 installed): `run402 doctor` reported
|
|
454
454
|
// the latest CLI as 4.17.5 from a cache dated Aug 1 — three minor versions
|
package/lib/wallet-context.mjs
CHANGED
|
@@ -133,10 +133,10 @@ export class WalletSelectionError extends Error {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// A rejected name is a value we know nothing about — the most likely
|
|
136
|
-
// mistake is a typo, but
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
136
|
+
// mistake is a typo, but a live Base-mainnet private key pasted into
|
|
137
|
+
// RUN402_WALLET (a NAME field, not a key field) is a demonstrated case, and
|
|
138
|
+
// echoing the raw value prints it to a terminal, a log, and a session
|
|
139
|
+
// transcript. Route it
|
|
140
140
|
// through describeRejectedValue() so a short/plain typo still shows in
|
|
141
141
|
// full (that's what makes the error useful) while anything long or
|
|
142
142
|
// hex-shaped enough to be a secret is redacted instead.
|
|
@@ -167,11 +167,11 @@ function assertValidName(name, origin) {
|
|
|
167
167
|
* Shared verbatim by `resolveWallet` below (the CLI wrapper: `--wallet` flag
|
|
168
168
|
* layered on top, failures routed through `fail()`) AND by
|
|
169
169
|
* `git-remote-run402` (no flag layer — the helper has no argv flags at all
|
|
170
|
-
* — failures routed through its own protocol-safe error reporting).
|
|
171
|
-
*
|
|
172
|
-
* `
|
|
173
|
-
*
|
|
174
|
-
*
|
|
170
|
+
* — failures routed through its own protocol-safe error reporting). Calling
|
|
171
|
+
* `getSdk()` directly instead would run no wallet selection at all, so a
|
|
172
|
+
* `.run402.json` binding — and even the global `wallets use` default —
|
|
173
|
+
* would silently never reach the helper; only the `RUN402_WALLET` env layer
|
|
174
|
+
* would work.
|
|
175
175
|
*
|
|
176
176
|
* Precedence beneath an optional flag (highest first): `RUN402_WALLET` /
|
|
177
177
|
* `RUN402_PROFILE` env > nearest `.run402.local.json`/`.run402.json` binding
|
|
@@ -197,7 +197,7 @@ export function resolveWalletCore({ walletFlag, env = {}, cwd = process.cwd(), c
|
|
|
197
197
|
|
|
198
198
|
if (envName && binding && envName !== binding.wallet && !CONFLICT_EXEMPT.has(cmd)) {
|
|
199
199
|
// This conflict check runs BEFORE assertValidNameCore below, so an
|
|
200
|
-
// unvalidated (possibly secret-shaped
|
|
200
|
+
// unvalidated (possibly secret-shaped)
|
|
201
201
|
// RUN402_WALLET reaches here first whenever a directory binding exists
|
|
202
202
|
// (routine per this repo's own fleet-coordination convention). Redact
|
|
203
203
|
// the env side the same way the format check would; `binding.wallet`
|
|
@@ -175,10 +175,9 @@ describe("resolveWallet — conflict + validation", () => {
|
|
|
175
175
|
// The WALLET_SELECTION_CONFLICT check runs BEFORE assertValidNameCore, so a
|
|
176
176
|
// secret-shaped RUN402_WALLET reaches it unvalidated whenever a directory
|
|
177
177
|
// binding also exists — routine for any checkout following this repo's own
|
|
178
|
-
// fleet-coordination convention (`.run402.json`).
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
// too, not just at assertValidNameCore.
|
|
178
|
+
// fleet-coordination convention (`.run402.json`). No BAD_WALLET_NAME
|
|
179
|
+
// format check ever gets a chance to run first, so the redaction has to
|
|
180
|
+
// happen at this site too, not just at assertValidNameCore.
|
|
182
181
|
it("never echoes a secret-shaped RUN402_WALLET via the conflict path", () => {
|
|
183
182
|
const dir = bindingDir("client-a"); // a real, differently-named binding
|
|
184
183
|
const privateKey = "0x" + "22a3f0".repeat(11); // 66 chars
|
|
@@ -201,7 +200,7 @@ describe("resolveWallet — conflict + validation", () => {
|
|
|
201
200
|
rmSync(dir, { recursive: true, force: true });
|
|
202
201
|
});
|
|
203
202
|
|
|
204
|
-
//
|
|
203
|
+
// a live Base-mainnet private key was pasted
|
|
205
204
|
// into RUN402_WALLET (a NAME field, not a key field), failed this exact
|
|
206
205
|
// check, and got echoed verbatim into a terminal, a log, and a session
|
|
207
206
|
// transcript. A value that fails validation is a value the CLI knows
|
|
@@ -254,8 +253,8 @@ describe("enforceWalletExists — fail closed", () => {
|
|
|
254
253
|
// A bare (no "0x" prefix) 64-char lowercase-hex private key satisfies the
|
|
255
254
|
// wallet-name CHARSET (lowercase letters/digits/'_'/'-'), so it sails past
|
|
256
255
|
// assertValidNameCore's format check and only fails HERE, on existence —
|
|
257
|
-
// the second, easy-to-miss half of
|
|
258
|
-
//
|
|
256
|
+
// the second, easy-to-miss half of the blast radius. It must still never
|
|
257
|
+
// be echoed.
|
|
259
258
|
it("redacts a bare-hex value that passes the name format check but not existence", () => {
|
|
260
259
|
const bareKey = "22a3f0".repeat(10) + "aabb"; // 64 lowercase-hex chars
|
|
261
260
|
assert.match(bareKey, /^[a-z0-9][a-z0-9_-]{0,63}$/); // sanity: really does pass the name regex
|
package/lib/wallets.mjs
CHANGED
|
@@ -72,7 +72,7 @@ function out(obj) {
|
|
|
72
72
|
console.log(JSON.stringify(obj, null, 2));
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// See core-dist/redact.js's doc comment
|
|
75
|
+
// See core-dist/redact.js's doc comment: a
|
|
76
76
|
// value that fails this check may be a secret pasted into a name argument
|
|
77
77
|
// by mistake, so it must never be echoed verbatim — describeRejectedValue()
|
|
78
78
|
// still shows a short/plain typo in full.
|
|
@@ -176,7 +176,7 @@ function cmdUse(args) {
|
|
|
176
176
|
if (name !== DEFAULT && !profileExists(name)) {
|
|
177
177
|
// `name` already passed requireName's charset check, but a bare (no
|
|
178
178
|
// "0x") 64-hex private key satisfies that charset too — describeRejectedValue()
|
|
179
|
-
// is the backstop against echoing it here
|
|
179
|
+
// is the backstop against echoing it here.
|
|
180
180
|
fail({ code: "WALLET_NOT_FOUND", message: `No local wallet named '${describeRejectedValue(name)}'.`, hint: "run402 wallets list", details: { name: describeRejectedValue(name) } });
|
|
181
181
|
}
|
|
182
182
|
setDefaultWallet(name);
|
package/lib/webhooks.mjs
CHANGED
|
@@ -152,7 +152,7 @@ async function update(args) {
|
|
|
152
152
|
if (!url && !eventsRaw) {
|
|
153
153
|
fail({ code: "BAD_USAGE", message: "Provide at least --url or --events" });
|
|
154
154
|
}
|
|
155
|
-
//
|
|
155
|
+
// scheme-only local validation. Server-side SSRF defenses are out
|
|
156
156
|
// of scope for the CLI (private-IP / DNS rebinding / IMDS belongs on the
|
|
157
157
|
// gateway). `validateWebhookUrl` is a no-op when `url` is null/undefined,
|
|
158
158
|
// so partial updates that change only `--events` still work.
|
|
@@ -186,7 +186,7 @@ async function register(args) {
|
|
|
186
186
|
hint: "run402 email webhooks register --url <url> --events <e1,e2>",
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
|
-
//
|
|
189
|
+
// validate scheme locally before any network call. Catches
|
|
190
190
|
// javascript:/file:/http:/data: schemes that the gateway would reject
|
|
191
191
|
// anyway, but with a friendlier round-trip-free error.
|
|
192
192
|
validateWebhookUrl(url, "--url");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.69.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": {
|
|
@@ -115,7 +115,7 @@ export function buildSIWxAuthHeaders(opts) {
|
|
|
115
115
|
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
116
116
|
*/
|
|
117
117
|
export function getAllowanceAuthHeaders(path, allowancePath) {
|
|
118
|
-
//
|
|
118
|
+
// readAllowance throws on a malformed-shape allowance file. The
|
|
119
119
|
// CLI's higher-level readAllowance wrapper surfaces this as a structured
|
|
120
120
|
// BAD_ALLOWANCE_FILE envelope; here we preserve the public contract that
|
|
121
121
|
// this helper returns SIWxAuthHeaders | null. Re-throw so callers above
|
|
@@ -14,7 +14,7 @@ const PRIVATE_KEY_RE = /^0x[a-fA-F0-9]{64}$/;
|
|
|
14
14
|
* - the file exists but is not parseable JSON (preserve existing UX —
|
|
15
15
|
* consumers print "no_allowance" and tell the user to run init)
|
|
16
16
|
*
|
|
17
|
-
* Throws a structured `Error`
|
|
17
|
+
* Throws a structured `Error` when the file parses as JSON but the
|
|
18
18
|
* shape is wrong (missing/wrong-type/wrong-length fields). Without this guard
|
|
19
19
|
* downstream callers crash with raw stack traces:
|
|
20
20
|
* - `cli/lib/status.mjs` reaches for `allowance.address.toLowerCase()`
|
package/sdk/core-dist/config.js
CHANGED
|
@@ -83,10 +83,10 @@ function assertSafeProfileName(name) {
|
|
|
83
83
|
return;
|
|
84
84
|
if (!isValidProfileName(name)) {
|
|
85
85
|
// A value that failed this check is a value we know nothing about — the
|
|
86
|
-
// most likely mistake is a typo, but
|
|
87
|
-
//
|
|
88
|
-
// the raw value
|
|
89
|
-
//
|
|
86
|
+
// most likely mistake is a typo, but a Base-mainnet private key pasted
|
|
87
|
+
// into RUN402_WALLET (a NAME field) is a demonstrated case, and echoing
|
|
88
|
+
// the raw value propagates it into terminals, logs, and session
|
|
89
|
+
// transcripts.
|
|
90
90
|
// describeRejectedValue() shows short/plain values in full (useful for
|
|
91
91
|
// an actual typo) and redacts anything long or hex-shaped enough to be
|
|
92
92
|
// a secret instead of a name.
|