run402 4.39.1 → 4.41.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/README.md +7 -4
- package/cli.mjs +10 -2
- package/lib/assets.mjs +19 -26
- package/lib/cdn.mjs +4 -6
- package/lib/command-manifest.mjs +56 -25
- package/lib/config.mjs +30 -0
- package/lib/deploy-v2.mjs +15 -0
- package/lib/doctor.mjs +36 -3
- package/lib/gitvault-scaffold.mjs +1 -1
- package/lib/gitvault.mjs +121 -819
- package/lib/init.mjs +2 -2
- package/lib/repos.mjs +1237 -248
- package/package.json +1 -1
- package/sdk/dist/errors.d.ts +5 -2
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js.map +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 +285 -5
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +369 -8
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/node/gitvault-apply.d.ts +1 -1
- package/sdk/dist/node/gitvault-apply.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-apply.js +6 -6
- package/sdk/dist/node/gitvault-apply.js.map +1 -1
- package/sdk/dist/node/gitvault-creation-journal.js +1 -1
- package/sdk/dist/node/gitvault-creation-journal.js.map +1 -1
- package/sdk/dist/node/gitvault-deploy.js +2 -2
- package/sdk/dist/node/gitvault-deploy.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 +153 -3
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-publication.js +182 -13
- 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/README.md
CHANGED
|
@@ -52,7 +52,9 @@ For app manifests with `verify.http[]`, `up` runs HTTP checks after deploy. Fres
|
|
|
52
52
|
|
|
53
53
|
For typed `run402.deploy.ts` configs, pass `--manifest` explicitly because TypeScript/JavaScript configs execute local code. Use `--check` for local-only import/normalize/file validation, `--print-spec` to inspect the normalized `ReleaseSpec`, `--plan` for a gateway-reviewed non-deploying plan, and `--require-plan <plan_id>` to apply only that reviewed intent. Warning flags are not used with `--require-plan`; the reviewed plan binds the exact warning/destructive set. Run402 Core skips Cloud allowance/tier prerequisites and fails closed when no Core project is selected.
|
|
54
54
|
|
|
55
|
-
###
|
|
55
|
+
### repos — host-blind encrypted git repos (zero deploy ceremony)
|
|
56
|
+
|
|
57
|
+
One noun, twelve verbs (repo-surface-consolidation; `repo` singular resolves identically). `run402 gitvault <verb>` is RETIRED — every old spelling now answers `COMMAND_MOVED`/`COMMAND_REMOVED`.
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
run402 init # once per machine
|
|
@@ -61,13 +63,14 @@ git push -u origin main # allocates the vault on first pus
|
|
|
61
63
|
|
|
62
64
|
# or, one call: provision + allocate + scaffold, nothing deployed
|
|
63
65
|
run402 repos create my-notes
|
|
66
|
+
run402 repos view --human
|
|
64
67
|
run402 repos list --org org_1a2b3c
|
|
65
|
-
run402 repos delete prj_xyz --force
|
|
68
|
+
run402 repos delete --project prj_xyz --force # refuses without --force while the vault holds generations
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
A hosted git remote, encrypted before it leaves the machine — no deploy, no manifest, no app. `origin` is claimed additively — an existing `origin` is never touched, the run402 remote falls back to `run402` instead. `repos
|
|
71
|
+
A hosted git remote, encrypted before it leaves the machine — no deploy, no manifest, no app. `origin` is claimed additively — an existing `origin` is never touched, the run402 remote falls back to `run402` instead. Every mutating `repos` verb (`create`, `rename`, `delete`, `snapshot`, `policy`, `mirror`, `gc`) is CLI/OpenClaw-only by design — no MCP tool exists or will exist for them (one-shot recovery receipts, immutable generations, irreversible delete). Three READ-ONLY tools do exist — `repos_view`, `repos_list_heads`, `repos_fsck` — teaching only `repos` spellings. See `run402 repos --help` for the full tiered surface (common: `create`/`view`/`list`; occasional: `snapshot`/`mirror`/`recover`; lifecycle: `rename`/`delete`; maintenance: `fsck`/`gc`/`access`/`policy`), and the CLI reference's `repos` section for the terminal-loss statement and the progressive backup warning.
|
|
69
72
|
|
|
70
|
-
**Named addressing (design D6).** `run402 org slug <slug>` (owner-only, small one-time fee) claims an org's globally-unique, address-form slug, after which `run402::<slug>/<name>` addresses any repo under it — `git push` to a name that doesn't exist yet push-to-creates it. `run402 repos
|
|
73
|
+
**Named addressing (design D6).** `run402 org slug <slug>` (owner-only, small one-time fee) claims an org's globally-unique, address-form slug, after which `run402::<slug>/<name>` addresses any repo under it — `git push` to a name that doesn't exist yet push-to-creates it. `run402 repos rename <name> [--project <id>]` claims the per-org-unique `<name>` half explicitly (no fee, absorbs the old `repos name`); `repos create` claims one automatically, best-effort, when the org already has a slug. Also CLI/SDK-only — no MCP tool.
|
|
71
74
|
|
|
72
75
|
### Allowance
|
|
73
76
|
|
package/cli.mjs
CHANGED
|
@@ -32,7 +32,7 @@ SET UP — get an agent funded and entitled
|
|
|
32
32
|
BUILD & SHIP — the app and everything it serves
|
|
33
33
|
up Provision/link/deploy the current app with SDK orchestration
|
|
34
34
|
projects Manage projects (provision, list, get, sql, delete)
|
|
35
|
-
repos
|
|
35
|
+
repos Host-blind encrypted git repos: create/view/list, snapshot/mirror/recover, fsck/gc/access/policy (repo aliases repos)
|
|
36
36
|
deploy Unified deploy operations (requires active tier)
|
|
37
37
|
apply Alias for deploy apply; supports --rehearse for migration rehearsal
|
|
38
38
|
functions Manage serverless functions (deploy, invoke, logs, list, delete)
|
|
@@ -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
|
|
87
|
+
gitvault RETIRED — moved to repos (repo-surface-consolidation); every spelling answers COMMAND_MOVED/COMMAND_REMOVED
|
|
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
|
|
@@ -227,6 +227,14 @@ switch (cmd) {
|
|
|
227
227
|
await run(sub, rest);
|
|
228
228
|
break;
|
|
229
229
|
}
|
|
230
|
+
// `repo` singular resolves identically to `repos` (design D1 — agents type
|
|
231
|
+
// what `gh repo` already taught them). Its own case block, not a
|
|
232
|
+
// fall-through, so cli-conventions-gate's per-line family scanner sees it.
|
|
233
|
+
case "repo": {
|
|
234
|
+
const { run } = await import("./lib/repos.mjs");
|
|
235
|
+
await run(sub, rest);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
230
238
|
case "apply": {
|
|
231
239
|
const { runDeployV2 } = await import("./lib/deploy-v2.mjs");
|
|
232
240
|
await runDeployV2("apply", [sub, ...rest].filter(Boolean));
|
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
|
@@ -255,32 +255,38 @@ export const COMMAND_MANIFEST = [
|
|
|
255
255
|
{ path: ["claims", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub" },
|
|
256
256
|
{ path: ["claims", "release"], positionals: [p("claim_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["clm_1"], runStyle: "sub" },
|
|
257
257
|
|
|
258
|
-
// ── gitvault (
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
258
|
+
// ── gitvault — RETIRED (repo-surface-consolidation, design D7) ──────────
|
|
259
|
+
// No manifest entries: `cli/lib/gitvault.mjs`'s dispatcher retired, and
|
|
260
|
+
// every `gitvault <verb>` now answers a structural COMMAND_MOVED (or, for
|
|
261
|
+
// `push`/`reconcile`, COMMAND_REMOVED) redirect that dispatches nothing —
|
|
262
|
+
// see RESERVED_SUBCOMMANDS below, and "gitvault" in SKIPPED_FAMILIES.
|
|
263
|
+
|
|
264
|
+
// ── repos (repo-surface-consolidation — the consolidated 12-verb family) ─
|
|
265
|
+
// Every verb needs a real principal keystore and, for most, an allocated
|
|
266
|
+
// repo and a local git working tree, so the gate runs structural checks
|
|
267
|
+
// only — an in-process behavioral run would either no-op against the
|
|
262
268
|
// universal `{}` fetch mock or touch the gate's own checkout.
|
|
263
|
-
{ path: ["
|
|
264
|
-
{ path: ["
|
|
265
|
-
{ path: ["
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
{ path: ["
|
|
270
|
-
{ path: ["
|
|
271
|
-
{ path: ["
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
{ path: ["repos", "
|
|
282
|
-
|
|
283
|
-
{ path: ["repos", "
|
|
269
|
+
{ path: ["repos", "create"], positionals: [p("name", { required: false })], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["my-notes"], runStyle: "sub", skipBehavioral: "provisions a project, allocates a repo, and scaffolds a real git remote into cwd" },
|
|
270
|
+
{ path: ["repos", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the live bulk vaults-by-org route, or falls back to a live per-project walk" },
|
|
271
|
+
{ path: ["repos", "view"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the local principal keystore and the live repo record" },
|
|
272
|
+
// repo-surface-consolidation D2 (`gh repo rename`): absorbs the old
|
|
273
|
+
// `repos name` — same claim/rename, `--repo`/`--project` addressing added.
|
|
274
|
+
{ path: ["repos", "rename"], positionals: [p("new_name")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["my-notes"], runStyle: "sub", skipBehavioral: "claims a per-org-unique repo name against a live project" },
|
|
275
|
+
{ path: ["repos", "delete"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [], runStyle: "sub", skipBehavioral: "irreversibly deletes a project after reading its live non-repo-resource state and vault generation count (design D9)" },
|
|
276
|
+
{ path: ["repos", "snapshot"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "captures the cwd git working tree and publishes a signed head" },
|
|
277
|
+
{ path: ["repos", "policy"], positionals: [p("repos_policy")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["required"], runStyle: "sub", skipBehavioral: "owner + step-up mutation of the live project's activation policy" },
|
|
278
|
+
// design D4 — ONE flag-driven verb; `<destination>` is a real attribute
|
|
279
|
+
// (not a sub-verb literal like the old `mirror set/remove/...`), so this
|
|
280
|
+
// stays a single manifest entry.
|
|
281
|
+
{ path: ["repos", "mirror"], positionals: [p("destination", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads/writes mirror destination config beside the keystore and may move real bytes into a customer-owned bucket" },
|
|
282
|
+
{ path: ["repos", "fsck"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "walks the live head chain and materializes the ref map against the keystore's local pins" },
|
|
283
|
+
{ path: ["repos", "gc"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "publishes a checkpoint under a maintenance lease and materializes the live vault head to enumerate retention roots" },
|
|
284
|
+
// `access repair` is a nested sub-verb literal (same shape the old
|
|
285
|
+
// `gitvault mirror set/remove/...` used) — two manifest entries, one
|
|
286
|
+
// dispatched `run("access", ...)` case in repos.mjs, matching precedent.
|
|
287
|
+
{ path: ["repos", "access"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the live org encryption-key directory + vault envelope recipients" },
|
|
288
|
+
{ path: ["repos", "access", "repair"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "always refuses — gated on gitvault-human-envelopes' epoch-rotation work, not shipped yet" },
|
|
289
|
+
{ path: ["repos", "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" },
|
|
284
290
|
{ path: ["errors"], positionals: [p("fingerprint_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
|
|
285
291
|
|
|
286
292
|
// ── jobs ─────────────────────────────────────────────────────────────────
|
|
@@ -455,6 +461,15 @@ export const SKIPPED_FAMILIES = {
|
|
|
455
461
|
// Split into `deliveries` / `contacts` / `subscriptions`; every subcommand
|
|
456
462
|
// answers COMMAND_REMOVED naming its successor.
|
|
457
463
|
"notifications": "reserved group; split by legible-cli-surface",
|
|
464
|
+
// repo-surface-consolidation D7: the dispatcher retired. Every
|
|
465
|
+
// `gitvault <verb>` answers COMMAND_MOVED/COMMAND_REMOVED (see
|
|
466
|
+
// RESERVED_SUBCOMMANDS below for the per-verb list) — the family stays
|
|
467
|
+
// dispatched in cli.mjs (so the redirect fires instead of UNKNOWN_COMMAND)
|
|
468
|
+
// but has zero manifest entries, since a redirect dispatches nothing.
|
|
469
|
+
"gitvault": "retired; every subcommand answers COMMAND_MOVED/COMMAND_REMOVED naming its `repos`/git successor",
|
|
470
|
+
// `repo` singular resolves identically to `repos` (design D1) — same
|
|
471
|
+
// module, same case block in cli.mjs, so it needs no manifest of its own.
|
|
472
|
+
"repo": "alias for `repos`, resolves identically (design D1)",
|
|
458
473
|
};
|
|
459
474
|
|
|
460
475
|
/**
|
|
@@ -480,4 +495,20 @@ export const RESERVED_SUBCOMMANDS = {
|
|
|
480
495
|
"rooms:get": "moved to `messages get` — the verb acts on a message",
|
|
481
496
|
"rooms:ack": "moved to `messages ack` — the verb acts on a message",
|
|
482
497
|
"escalations:contacts": "merged into `contacts` — the ladder and Telegram channels are one question",
|
|
498
|
+
// repo-surface-consolidation D7: every `gitvault <verb>` spelling. Nine
|
|
499
|
+
// answer COMMAND_MOVED naming their `repos` successor; `push` and
|
|
500
|
+
// `reconcile` answer COMMAND_REMOVED (no equivalent successor for either —
|
|
501
|
+
// `push`'s one-release alias window is over, `reconcile` was a workaround
|
|
502
|
+
// with no permanent replacement, only a read at `repos access`).
|
|
503
|
+
"gitvault:init": "moved to `repos create --project <id>`",
|
|
504
|
+
"gitvault:status": "moved to `repos view`",
|
|
505
|
+
"gitvault:snapshot": "moved to `repos snapshot`",
|
|
506
|
+
"gitvault:policy": "moved to `repos policy`",
|
|
507
|
+
"gitvault:compact": "moved to `repos gc`",
|
|
508
|
+
"gitvault:prune": "moved to `repos gc`",
|
|
509
|
+
"gitvault:verify": "moved to `repos fsck`",
|
|
510
|
+
"gitvault:mirror": "moved to `repos mirror`",
|
|
511
|
+
"gitvault:recover": "moved to `repos recover`",
|
|
512
|
+
"gitvault:push": "removed — its one-release deprecation-alias window is over; `git push` / `repos snapshot`",
|
|
513
|
+
"gitvault:reconcile": "removed — a workaround with no permanent successor; read `repos access` instead",
|
|
483
514
|
};
|
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
|
@@ -575,13 +575,13 @@ export async function run(sub, args = []) {
|
|
|
575
575
|
if (gv.gitvault_policy === "required" && !gv.keystore.holds_repo_key) {
|
|
576
576
|
gaps.push(
|
|
577
577
|
"gitvault_policy is 'required' but this machine holds no key for the vault — a deploy from here is refused with GITVAULT_CLIENT_UPGRADE_REQUIRED. " +
|
|
578
|
-
"Run 'run402
|
|
578
|
+
"Run 'run402 repos create --project <id>' (idempotent; resolves to the existing repo), or 'run402 repos policy grandfathered --reason <why>' to un-gate the project.",
|
|
579
579
|
);
|
|
580
580
|
} else if (gv.gitvault_policy === "required" && !gv.keystore.can_sign) {
|
|
581
581
|
gaps.push("gitvault_policy is 'required' and this keystore is read-only (no signing key) — it can verify but cannot publish the capture a deploy needs");
|
|
582
582
|
}
|
|
583
583
|
if (gv.pending_overrides > 0) {
|
|
584
|
-
gaps.push(`${gv.pending_overrides} unvaulted-override journal(s) are still open — run 'run402
|
|
584
|
+
gaps.push(`${gv.pending_overrides} unvaulted-override journal(s) are still open — run 'run402 repos snapshot' to drain them`);
|
|
585
585
|
}
|
|
586
586
|
// `matches` is a TRI-STATE (kychee-com/run402#562): `false` alone is
|
|
587
587
|
// a real mismatch. `null` (a slug-form remote not yet resolved on
|
|
@@ -593,12 +593,45 @@ 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 repos mirror <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",
|
|
599
632
|
value: gaps.length > 0 ? { ...value, gaps } : value,
|
|
600
633
|
hint: gv.vault === null
|
|
601
|
-
? `No vault for this project (that is a normal shape). Allocate one with 'run402
|
|
634
|
+
? `No vault for this project (that is a normal shape). Allocate one with 'run402 repos create --project <id>'. Keystore: ${gv.keystore.root}`
|
|
602
635
|
: `Back up ${gv.keystore.root} — whole-machine or whole-keystore loss is terminal for vault history.`,
|
|
603
636
|
});
|
|
604
637
|
} catch (err) {
|
|
@@ -49,7 +49,7 @@ export async function scaffoldGitvaultRemote({ repoDir = process.cwd(), projectI
|
|
|
49
49
|
const remote = await getSdk().gitvault.scaffoldRemote({ repo_dir: repoDir, org_id: resolvedOrgId, project_id: projectId });
|
|
50
50
|
// `allocated: false` is stated, not left to be inferred: this is local
|
|
51
51
|
// git only — no vault exists for the project yet (allocation happens
|
|
52
|
-
// lazily on first push, or explicitly via `run402
|
|
52
|
+
// lazily on first push, or explicitly via `run402 repos create --project <id>`).
|
|
53
53
|
out.gitvault = { ...remote, allocated: false };
|
|
54
54
|
} catch (err) {
|
|
55
55
|
out.gitvault = null;
|