run402 4.39.1 → 4.40.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 +1 -1
- package/lib/assets.mjs +19 -26
- package/lib/cdn.mjs +4 -6
- package/lib/command-manifest.mjs +20 -0
- package/lib/config.mjs +30 -0
- package/lib/deploy-v2.mjs +15 -0
- package/lib/doctor.mjs +33 -0
- package/lib/gitvault.mjs +348 -1
- package/package.json +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +10 -0
- package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.js +10 -0
- package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +141 -4
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +226 -3
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/node/gitvault-keystore.d.ts +11 -1
- package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-keystore.js.map +1 -1
- package/sdk/dist/node/gitvault-mirror-backend.d.ts +88 -0
- package/sdk/dist/node/gitvault-mirror-backend.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-mirror-backend.js +374 -0
- package/sdk/dist/node/gitvault-mirror-backend.js.map +1 -0
- package/sdk/dist/node/gitvault-mirror-config.d.ts +53 -0
- package/sdk/dist/node/gitvault-mirror-config.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-mirror-config.js +112 -0
- package/sdk/dist/node/gitvault-mirror-config.js.map +1 -0
- package/sdk/dist/node/gitvault-mirror.d.ts +120 -0
- package/sdk/dist/node/gitvault-mirror.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-mirror.js +464 -0
- package/sdk/dist/node/gitvault-mirror.js.map +1 -0
- package/sdk/dist/node/gitvault-publication.d.ts +131 -0
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-publication.js +145 -1
- package/sdk/dist/node/gitvault-publication.js.map +1 -1
- package/sdk/dist/node/gitvault-recover.d.ts +136 -0
- package/sdk/dist/node/gitvault-recover.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-recover.js +412 -0
- package/sdk/dist/node/gitvault-recover.js.map +1 -0
- package/sdk/dist/node/gitvault-snapshot.d.ts +8 -0
- package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.js +11 -0
- package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
package/cli.mjs
CHANGED
|
@@ -84,7 +84,7 @@ PLATFORM — everything else, and the things still finding a home
|
|
|
84
84
|
transfer Two-party project transfer (init, preview, list, accept, cancel)
|
|
85
85
|
cloud Cloud portability archive export (archives create/download/status)
|
|
86
86
|
archives Inspect and verify portable project archives locally
|
|
87
|
-
gitvault Host-blind encrypted Git remote (init/status/push/policy/compact/prune/verify)
|
|
87
|
+
gitvault Host-blind encrypted Git remote (init/status/push/policy/compact/prune/verify/mirror/recover)
|
|
88
88
|
buzz Buzz human/community/agent control-plane workflows
|
|
89
89
|
apps Browse and manage the app marketplace
|
|
90
90
|
ai AI translation and moderation tools
|
package/lib/assets.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { basename, dirname, resolve as resolvePath } from "node:path";
|
|
29
29
|
import { pipeline } from "node:stream/promises";
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import { resolveProjectIdAllowingLegacyEnv } from "./config.mjs";
|
|
32
32
|
import { getSdk } from "./sdk.mjs";
|
|
33
33
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
34
34
|
import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag, failUnknownSubcommand } from "./argparse.mjs";
|
|
@@ -472,10 +472,9 @@ function computeDestKey(filePath, keyOpt) {
|
|
|
472
472
|
return keyOpt;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
async function put(
|
|
475
|
+
async function put(argv) {
|
|
476
476
|
const opts = parseArgs(argv);
|
|
477
|
-
|
|
478
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
477
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
479
478
|
|
|
480
479
|
if (opts.positional.length === 0) die("At least one file path is required");
|
|
481
480
|
if (opts.positional.length > 1 && opts.key && !opts.key.endsWith("/")) {
|
|
@@ -499,10 +498,9 @@ async function put(projectId, argv) {
|
|
|
499
498
|
// get
|
|
500
499
|
// ---------------------------------------------------------------------------
|
|
501
500
|
|
|
502
|
-
async function get(
|
|
501
|
+
async function get(argv) {
|
|
503
502
|
const opts = parseArgs(argv);
|
|
504
|
-
|
|
505
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
503
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
506
504
|
if (opts.positional.length === 0) die("Key required");
|
|
507
505
|
if (opts.positional.length > 1) die("blob get expects exactly one key");
|
|
508
506
|
if (!opts.output) die("--output <file> required");
|
|
@@ -526,10 +524,9 @@ async function get(projectId, argv) {
|
|
|
526
524
|
// ls
|
|
527
525
|
// ---------------------------------------------------------------------------
|
|
528
526
|
|
|
529
|
-
async function ls(
|
|
527
|
+
async function ls(argv) {
|
|
530
528
|
const opts = parseArgs(argv);
|
|
531
|
-
|
|
532
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
529
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
533
530
|
|
|
534
531
|
try {
|
|
535
532
|
const data = await getSdk().assets.ls(resolvedId, {
|
|
@@ -552,10 +549,9 @@ async function ls(projectId, argv) {
|
|
|
552
549
|
// rm
|
|
553
550
|
// ---------------------------------------------------------------------------
|
|
554
551
|
|
|
555
|
-
async function rm(
|
|
552
|
+
async function rm(argv) {
|
|
556
553
|
const opts = parseArgs(argv);
|
|
557
|
-
|
|
558
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
554
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
559
555
|
if (opts.positional.length === 0) die("Key required");
|
|
560
556
|
if (opts.positional.length > 1) die("blob rm expects exactly one key");
|
|
561
557
|
const key = opts.positional[0];
|
|
@@ -572,10 +568,9 @@ async function rm(projectId, argv) {
|
|
|
572
568
|
// sign
|
|
573
569
|
// ---------------------------------------------------------------------------
|
|
574
570
|
|
|
575
|
-
async function diagnose(
|
|
571
|
+
async function diagnose(argv) {
|
|
576
572
|
const opts = parseArgs(argv);
|
|
577
|
-
|
|
578
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
573
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
579
574
|
if (opts.positional.length === 0) die("URL required");
|
|
580
575
|
if (opts.positional.length > 1) die("blob diagnose expects exactly one URL");
|
|
581
576
|
const url = opts.positional[0];
|
|
@@ -623,10 +618,9 @@ function toCliDiagnoseEnvelope(env) {
|
|
|
623
618
|
};
|
|
624
619
|
}
|
|
625
620
|
|
|
626
|
-
async function sign(
|
|
621
|
+
async function sign(argv) {
|
|
627
622
|
const opts = parseArgs(argv);
|
|
628
|
-
|
|
629
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
623
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
630
624
|
if (opts.positional.length === 0) die("Key required");
|
|
631
625
|
if (opts.positional.length > 1) die("blob sign expects exactly one key");
|
|
632
626
|
const key = opts.positional[0];
|
|
@@ -676,14 +670,13 @@ export async function run(sub, args) {
|
|
|
676
670
|
console.log(SUB_HELP[sub] || HELP);
|
|
677
671
|
process.exit(0);
|
|
678
672
|
}
|
|
679
|
-
const defaultProject = process.env.RUN402_PROJECT ?? null;
|
|
680
673
|
switch (sub) {
|
|
681
|
-
case "put": await put(
|
|
682
|
-
case "get": await get(
|
|
683
|
-
case "ls": await ls(
|
|
684
|
-
case "rm": await rm(
|
|
685
|
-
case "sign": await sign(
|
|
686
|
-
case "diagnose": await diagnose(
|
|
674
|
+
case "put": await put(args); break;
|
|
675
|
+
case "get": await get(args); break;
|
|
676
|
+
case "ls": await ls(args); break;
|
|
677
|
+
case "rm": await rm(args); break;
|
|
678
|
+
case "sign": await sign(args); break;
|
|
679
|
+
case "diagnose": await diagnose(args); break;
|
|
687
680
|
default:
|
|
688
681
|
failUnknownSubcommand("assets", sub);
|
|
689
682
|
}
|
package/lib/cdn.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* are bound to a SHA at upload time and never previously cached.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { resolveProjectIdAllowingLegacyEnv } from "./config.mjs";
|
|
17
17
|
import { getSdk } from "./sdk.mjs";
|
|
18
18
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
19
19
|
import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs, failUnknownSubcommand } from "./argparse.mjs";
|
|
@@ -91,10 +91,9 @@ function parseArgs(args) {
|
|
|
91
91
|
return opts;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
async function waitFresh(
|
|
94
|
+
async function waitFresh(argv) {
|
|
95
95
|
const opts = parseArgs(argv);
|
|
96
|
-
|
|
97
|
-
const resolvedId = resolveProjectId(opts.project);
|
|
96
|
+
const resolvedId = resolveProjectIdAllowingLegacyEnv(opts.project);
|
|
98
97
|
if (opts.positional.length === 0) die("URL required");
|
|
99
98
|
const url = opts.positional[0];
|
|
100
99
|
if (!opts.sha) die("--sha is required");
|
|
@@ -136,10 +135,9 @@ export async function run(sub, args) {
|
|
|
136
135
|
console.log(SUB_HELP[sub] || HELP);
|
|
137
136
|
process.exit(0);
|
|
138
137
|
}
|
|
139
|
-
const defaultProject = process.env.RUN402_PROJECT ?? null;
|
|
140
138
|
switch (sub) {
|
|
141
139
|
case "wait-fresh":
|
|
142
|
-
await waitFresh(
|
|
140
|
+
await waitFresh(args);
|
|
143
141
|
break;
|
|
144
142
|
default:
|
|
145
143
|
failUnknownSubcommand("cdn", sub);
|
package/lib/command-manifest.mjs
CHANGED
|
@@ -271,6 +271,26 @@ export const COMMAND_MANIFEST = [
|
|
|
271
271
|
{ path: ["gitvault", "compact"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "takes a maintenance lease and builds a checkpoint from the local repository" },
|
|
272
272
|
{ path: ["gitvault", "prune"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "materializes the live vault head to enumerate retention roots" },
|
|
273
273
|
{ path: ["gitvault", "verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "walks the live head chain against the keystore's authenticated pin" },
|
|
274
|
+
{ path: ["gitvault", "reconcile"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the live org encryption-key directory + vault envelope recipients, and may publish new key_envelope objects" },
|
|
275
|
+
|
|
276
|
+
// ── gitvault mirror (gitvault-mirror-and-recover) ────────────────────────
|
|
277
|
+
// The customer-owned ciphertext mirror — client-side only, never a server
|
|
278
|
+
// call except `sync` (lists the live vault's objects) and `status`/`verify`
|
|
279
|
+
// (a keyless read against the mirror + one live vault-record read). All
|
|
280
|
+
// five need a real keystore + (for anything but `remove`) a configured
|
|
281
|
+
// mirror destination, so — same as the gitvault family above — the gate
|
|
282
|
+
// runs structural checks only.
|
|
283
|
+
{ path: ["gitvault", "mirror", "set"], positionals: [p("destination")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["s3://example-mirror-bucket"], runStyle: "sub", skipBehavioral: "writes mirror destination config beside the keystore (client-side only)" },
|
|
284
|
+
{ path: ["gitvault", "mirror", "remove"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "removes mirror destination config beside the keystore (client-side only)" },
|
|
285
|
+
{ path: ["gitvault", "mirror", "status"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the configured mirror + the live vault record's newest_generation" },
|
|
286
|
+
{ path: ["gitvault", "mirror", "sync"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "lists the live vault's stored objects and reconciles them against the configured mirror" },
|
|
287
|
+
{ path: ["gitvault", "mirror", "verify"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "keyless discovery + chain verification against the configured mirror; touches no key material" },
|
|
288
|
+
|
|
289
|
+
// ── gitvault recover (gitvault-mirror-and-recover, design D4) ────────────
|
|
290
|
+
// `r402s-recover`: offline, no server call at all — reads only from the
|
|
291
|
+
// mirror source named on the command line. Not project-scoped (the source
|
|
292
|
+
// URL, not the active project, addresses the vault to recover).
|
|
293
|
+
{ path: ["gitvault", "recover"], positionals: [p("source")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["s3://example-mirror-bucket", "--out", "__SCRATCH_DIR__/recover-out"], runStyle: "sub", skipBehavioral: "materializes a git repository from a mirror source, offline, with no server call" },
|
|
274
294
|
|
|
275
295
|
// ── repos (vault-only porcelain, repo-first-onramp D8, task 2.6) ────────
|
|
276
296
|
// `create` writes real git state into cwd and allocates a vault; `list`
|
package/lib/config.mjs
CHANGED
|
@@ -139,6 +139,36 @@ export function resolveProjectId(id) {
|
|
|
139
139
|
return projectId;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* `resolveProjectId`, but also honors the deprecated `RUN402_PROJECT` alias
|
|
144
|
+
* as a last-resort fallback.
|
|
145
|
+
*
|
|
146
|
+
* `RUN402_PROJECT_ID` is the canonical env var everywhere in the CLI — it's
|
|
147
|
+
* what `resolveProjectId`, `run402 doctor`, `org-context.mjs`, and every
|
|
148
|
+
* other project-scoped command read. Two commands (`cdn wait-fresh` and
|
|
149
|
+
* every `assets` subcommand) historically read the DIFFERENT, undocumented
|
|
150
|
+
* `RUN402_PROJECT` instead, so exporting the canonical `RUN402_PROJECT_ID`
|
|
151
|
+
* and running one of those two commands was a silent no-op: the export did
|
|
152
|
+
* nothing, and resolution quietly fell through to the active project.
|
|
153
|
+
*
|
|
154
|
+
* Precedence, highest first: an explicit `id` (e.g. `--project`) >
|
|
155
|
+
* `RUN402_PROJECT_ID` > the deprecated `RUN402_PROJECT` alias > the active
|
|
156
|
+
* project. The alias is only ever consulted when `RUN402_PROJECT_ID` is
|
|
157
|
+
* unset AND no explicit id was given — so it never silently overrides the
|
|
158
|
+
* canonical var or a flag. When the alias is what actually resolves the
|
|
159
|
+
* project, exactly one deprecation line goes to stderr (never stdout — the
|
|
160
|
+
* pipe contract keeps stdout pure JSON).
|
|
161
|
+
*/
|
|
162
|
+
export function resolveProjectIdAllowingLegacyEnv(id) {
|
|
163
|
+
if (!id && !process.env.RUN402_PROJECT_ID && process.env.RUN402_PROJECT) {
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
"warning: RUN402_PROJECT is deprecated and will be removed; set RUN402_PROJECT_ID instead.\n",
|
|
166
|
+
);
|
|
167
|
+
return resolveProjectId(process.env.RUN402_PROJECT);
|
|
168
|
+
}
|
|
169
|
+
return resolveProjectId(id);
|
|
170
|
+
}
|
|
171
|
+
|
|
142
172
|
// Re-export core keystore functions for direct use
|
|
143
173
|
export {
|
|
144
174
|
configureApiBase,
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -1281,9 +1281,24 @@ async function applyCmd(args) {
|
|
|
1281
1281
|
...("generation" in vaulted ? { generation: vaulted.generation } : {}),
|
|
1282
1282
|
...("push_error" in vaulted ? { push_error: vaulted.push_error } : {}),
|
|
1283
1283
|
...("deploy_error" in vaulted ? { deploy_error: vaulted.deploy_error } : {}),
|
|
1284
|
+
// Design D5/D6 (gitvault-human-envelopes task 4.1 + gitvault-mirror):
|
|
1285
|
+
// present only when this deploy landed a new generation — the SDK
|
|
1286
|
+
// omits both fields entirely on the other three outcomes rather than
|
|
1287
|
+
// faking a `skipped_*` value for something that never had a chance to
|
|
1288
|
+
// run. See `Gitvault.deploy`'s doc comment (sdk/src/namespaces/gitvault.ts).
|
|
1289
|
+
...("mirror_push" in vaulted ? { mirror_push: vaulted.mirror_push } : {}),
|
|
1290
|
+
...("reconcile_recipients" in vaulted ? { reconcile_recipients: vaulted.reconcile_recipients } : {}),
|
|
1284
1291
|
next_actions: vaulted.next_actions,
|
|
1285
1292
|
deploy: outcome.deploy,
|
|
1286
1293
|
}, null, 2));
|
|
1294
|
+
// Design D6: the mirror result is reported BESIDE the vault outcome
|
|
1295
|
+
// above, on its own stderr line — a mirror failure never blocks the
|
|
1296
|
+
// deploy (mirrors `run402 gitvault snapshot`'s reporting).
|
|
1297
|
+
if (vaulted.mirror_push?.outcome === "pushed") {
|
|
1298
|
+
console.error(`mirror: pushed generation ${vaulted.generation} (${vaulted.mirror_push.summary?.objects_copied ?? 0} object(s) copied)`);
|
|
1299
|
+
} else if (vaulted.mirror_push?.outcome === "failed") {
|
|
1300
|
+
console.error(`mirror: dual-push FAILED (deploy is unaffected) — ${vaulted.mirror_push.error ?? "see mirror_push.summary.errors"}`);
|
|
1301
|
+
}
|
|
1287
1302
|
// A non-activating outcome is a failed deploy even though it resolved
|
|
1288
1303
|
// rather than threw: the five outcomes are a result type, not an error
|
|
1289
1304
|
// channel, so the exit code has to carry the verdict.
|
package/lib/doctor.mjs
CHANGED
|
@@ -593,6 +593,39 @@ export async function run(sub, args = []) {
|
|
|
593
593
|
// Echoed exactly as the SDK reported them — including the
|
|
594
594
|
// doctor-persistent `grandfathered` advisory it owns.
|
|
595
595
|
for (const w of gv.warnings ?? []) gaps.push(`${w.kind}: ${w.message}`);
|
|
596
|
+
|
|
597
|
+
// gitvault-mirror-and-recover task 4.3: mirror currency, reported
|
|
598
|
+
// ALONGSIDE (never in place of) the deploy-related gaps above, and
|
|
599
|
+
// never blocking `run402 deploy`'s own gate — the vault lane's
|
|
600
|
+
// outcome is unaffected regardless of mirror state (design D6).
|
|
601
|
+
// `mirror_currency` mirrors `mirror status`'s own tri-state:
|
|
602
|
+
// `current` / `stale` / `unknown` (mirror unreachable or vault
|
|
603
|
+
// unread) only STALE is actionable enough to become a warning; NO
|
|
604
|
+
// mirror configured is a purely informational, ungated advisory —
|
|
605
|
+
// most vaults have never opted in, and that is a normal shape.
|
|
606
|
+
if (gv.vault !== null && value.repo_id) {
|
|
607
|
+
try {
|
|
608
|
+
const mirrorStatus = await getSdk().gitvault.mirrorStatus({ repo_id: value.repo_id });
|
|
609
|
+
value.gitvault_mirror = {
|
|
610
|
+
configured: mirrorStatus.configured,
|
|
611
|
+
destination: mirrorStatus.destination,
|
|
612
|
+
mirrored_generation: mirrorStatus.mirrored_generation,
|
|
613
|
+
newest_generation: mirrorStatus.newest_generation,
|
|
614
|
+
is_current: mirrorStatus.is_current,
|
|
615
|
+
validity_not_freshness: mirrorStatus.validity_not_freshness,
|
|
616
|
+
keystore_still_required: mirrorStatus.keystore_still_required,
|
|
617
|
+
};
|
|
618
|
+
if (!mirrorStatus.configured) {
|
|
619
|
+
value.gitvault_mirror.advisory = "no ciphertext mirror is configured for this vault — the exit ramp is opt-in; 'run402 gitvault mirror set <destination>' to configure one.";
|
|
620
|
+
} else if (mirrorStatus.is_current === false) {
|
|
621
|
+
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}`);
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
624
|
+
// Best-effort: a mirror status read failing is never a doctor
|
|
625
|
+
// failure, and never touches the deploy-related gaps above.
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
596
629
|
checks.push({
|
|
597
630
|
name: "gitvault",
|
|
598
631
|
status: gaps.length > 0 ? "warning" : "ok",
|