run402 4.40.0 → 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 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
- ### Vault-only repos (zero deploy ceremony)
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 # refuses without --force while the vault holds generations
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 create|list|delete|name` and `gitvault`'s mutating verbs (`init`, `snapshot`, `policy`, `compact`, `prune`) are CLI/OpenClaw-only by design — no MCP tool exists or will exist for them (one-shot recovery receipts, immutable generations, irreversible delete). See `run402 gitvault --help` and `run402 repos --help` for the full surface, and the CLI reference's `gitvault` / `repos` sections for the terminal-loss statement and the progressive backup warning.
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 name <name> [--project <id>]` claims the per-org-unique `<name>` half explicitly (no fee); `repos create` claims one automatically, best-effort, when the org already has a slug. Also CLI/SDK-only — no MCP tool.
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 Vault-only hosted encrypted repos, zero deploy ceremony (create, list, delete)
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 Host-blind encrypted Git remote (init/status/push/policy/compact/prune/verify/mirror/recover)
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));
@@ -255,52 +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 (host-blind encrypted Git remote) ───────────────────────────
259
- // Every verb needs a real principal keystore, an allocated vault, and (for
260
- // all but `status`) a local git working tree, so the gate runs structural
261
- // checks only an in-process behavioral run would either no-op against the
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: ["gitvault", "init"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "allocates a vault: mints key material on this machine and runs the six-stage creation journal" },
264
- { path: ["gitvault", "status"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the local principal keystore and the live vault record" },
265
- { path: ["gitvault", "snapshot"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "captures the cwd git working tree and publishes a signed head" },
266
- // D5 (repo-first-onramp task 2.5): `push` is a deprecation-warning alias
267
- // for `snapshot`, retained for exactly one release — same structural
268
- // shape, same skip reason, so it stays covered by this gate too.
269
- { path: ["gitvault", "push"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "deprecated alias for `snapshot` captures the cwd git working tree and publishes a signed head" },
270
- { path: ["gitvault", "policy"], positionals: [p("gitvault_policy")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["required"], runStyle: "sub", skipBehavioral: "owner + step-up mutation of the live project's activation policy" },
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
- { path: ["gitvault", "prune"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "materializes the live vault head to enumerate retention roots" },
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" },
294
-
295
- // ── repos (vault-only porcelain, repo-first-onramp D8, task 2.6) ────────
296
- // `create` writes real git state into cwd and allocates a vault; `list`
297
- // and `delete` read/mutate the live gitvault record — same structural-
298
- // checks-only rationale as the gitvault family just above.
299
- { path: ["repos", "create"], positionals: [p("name")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["my-notes"], runStyle: "sub", skipBehavioral: "provisions a project, allocates a vault, and scaffolds a real git remote into cwd" },
300
- { path: ["repos", "list"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "cross-references live projects against their live gitvault status, one call per project" },
301
- { path: ["repos", "delete"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [], runStyle: "sub", skipBehavioral: "irreversibly deletes a project after reading its live vault generation count" },
302
- // repo-first-onramp task 4.2 (design D6): explicit address-form name claim.
303
- { path: ["repos", "name"], positionals: [p("name")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["my-notes"], runStyle: "sub", skipBehavioral: "claims a per-org-unique repo name against a live project" },
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" },
304
290
  { path: ["errors"], positionals: [p("fingerprint_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
305
291
 
306
292
  // ── jobs ─────────────────────────────────────────────────────────────────
@@ -475,6 +461,15 @@ export const SKIPPED_FAMILIES = {
475
461
  // Split into `deliveries` / `contacts` / `subscriptions`; every subcommand
476
462
  // answers COMMAND_REMOVED naming its successor.
477
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)",
478
473
  };
479
474
 
480
475
  /**
@@ -500,4 +495,20 @@ export const RESERVED_SUBCOMMANDS = {
500
495
  "rooms:get": "moved to `messages get` — the verb acts on a message",
501
496
  "rooms:ack": "moved to `messages ack` — the verb acts on a message",
502
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",
503
514
  };
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 gitvault init' (idempotent; resolves to the existing vault), or 'run402 gitvault policy grandfathered --reason <why>' to un-gate the project.",
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 gitvault push' to drain them`);
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
@@ -616,7 +616,7 @@ export async function run(sub, args = []) {
616
616
  keystore_still_required: mirrorStatus.keystore_still_required,
617
617
  };
618
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.";
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
620
  } else if (mirrorStatus.is_current === false) {
621
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
622
  }
@@ -631,7 +631,7 @@ export async function run(sub, args = []) {
631
631
  status: gaps.length > 0 ? "warning" : "ok",
632
632
  value: gaps.length > 0 ? { ...value, gaps } : value,
633
633
  hint: gv.vault === null
634
- ? `No vault for this project (that is a normal shape). Allocate one with 'run402 gitvault init'. Keystore: ${gv.keystore.root}`
634
+ ? `No vault for this project (that is a normal shape). Allocate one with 'run402 repos create --project <id>'. Keystore: ${gv.keystore.root}`
635
635
  : `Back up ${gv.keystore.root} — whole-machine or whole-keystore loss is terminal for vault history.`,
636
636
  });
637
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 gitvault init`).
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;