run402 4.41.0 → 4.43.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 +2 -2
- package/lib/argparse.mjs +1 -1
- package/lib/assets.mjs +10 -14
- package/lib/command-manifest.mjs +28 -27
- package/lib/deploy-v2.mjs +34 -14
- package/lib/doctor.mjs +8 -1
- package/lib/gitvault-target.mjs +7 -9
- package/lib/gitvault.mjs +7 -8
- package/lib/next-actions.mjs +5 -2
- package/lib/notifications.mjs +4 -8
- package/lib/operator.mjs +1 -1
- package/lib/org.mjs +3 -3
- package/lib/repos.mjs +536 -164
- package/lib/rooms-context.mjs +2 -3
- package/lib/rooms.mjs +11 -14
- package/lib/stats.mjs +57 -0
- package/lib/update-check.mjs +6 -8
- package/package.json +1 -1
- package/sdk/dist/index.d.ts +10 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +11 -0
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts +31 -0
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +116 -3
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/admin.d.ts +1 -1
- package/sdk/dist/namespaces/admin.js +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +2 -2
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +188 -5
- package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.js +277 -8
- package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +239 -44
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +327 -49
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.types.d.ts +184 -9
- package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.types.js +2 -1
- package/sdk/dist/namespaces/gitvault.types.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +1 -1
- package/sdk/dist/namespaces/projects.js +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-apply.d.ts +8 -0
- package/sdk/dist/node/gitvault-apply.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-apply.js +1 -0
- package/sdk/dist/node/gitvault-apply.js.map +1 -1
- package/sdk/dist/node/gitvault-deploy.d.ts +13 -0
- package/sdk/dist/node/gitvault-deploy.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-deploy.js +28 -0
- package/sdk/dist/node/gitvault-deploy.js.map +1 -1
- package/sdk/dist/node/gitvault-keystore.d.ts +54 -4
- package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-keystore.js +20 -0
- package/sdk/dist/node/gitvault-keystore.js.map +1 -1
- package/sdk/dist/node/gitvault-publication.d.ts +376 -21
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-publication.js +567 -19
- package/sdk/dist/node/gitvault-publication.js.map +1 -1
- package/sdk/dist/node/gitvault-recover.d.ts +15 -1
- package/sdk/dist/node/gitvault-recover.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-recover.js +8 -4
- package/sdk/dist/node/gitvault-recover.js.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.d.ts +40 -7
- package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.js +67 -19
- package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
- package/sdk/dist/scoped.d.ts +2 -2
- package/sdk/dist/scoped.js +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ For typed `run402.deploy.ts` configs, pass `--manifest` explicitly because TypeS
|
|
|
54
54
|
|
|
55
55
|
### repos — host-blind encrypted git repos (zero deploy ceremony)
|
|
56
56
|
|
|
57
|
-
One noun, twelve verbs (
|
|
57
|
+
One noun, twelve verbs (`repo` singular resolves identically). `run402 gitvault <verb>` answers `COMMAND_MOVED`/`COMMAND_REMOVED`.
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
60
|
run402 init # once per machine
|
|
@@ -70,7 +70,7 @@ run402 repos delete --project prj_xyz --force # refuses without --force while th
|
|
|
70
70
|
|
|
71
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.
|
|
72
72
|
|
|
73
|
-
**Named addressing
|
|
73
|
+
**Named addressing.** `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); `repos create` claims one automatically, best-effort, when the org already has a slug. Also CLI/SDK-only — no MCP tool.
|
|
74
74
|
|
|
75
75
|
### Allowance
|
|
76
76
|
|
package/lib/argparse.mjs
CHANGED
|
@@ -272,7 +272,7 @@ export function requirePositionalCount(args = [], flagsWithValues = [], opts = {
|
|
|
272
272
|
return pos;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
// Resolve a positional project_id argument with active-project fallback
|
|
275
|
+
// Resolve a positional project_id argument with active-project fallback.
|
|
276
276
|
// If the first positional starts with "prj_", treat it as the project id and
|
|
277
277
|
// strip it from the rest. Otherwise, fall through to the active project from
|
|
278
278
|
// the keystore. Callers can tighten the legacy shorthand when a bare non-prj
|
package/lib/assets.mjs
CHANGED
|
@@ -265,7 +265,7 @@ function parseArgs(rawArgs) {
|
|
|
265
265
|
else if (a === "--limit") out.limit = parseIntegerFlag("--limit", args[++i], { min: 1, max: 1000 });
|
|
266
266
|
else if (a === "--output" || a === "-o") out.output = args[++i];
|
|
267
267
|
else if (a === "--ttl") out.ttl = parseIntegerFlag("--ttl", args[++i], { min: 60, max: 604800 });
|
|
268
|
-
//
|
|
268
|
+
// Repeatable --meta key=value (number-coerce on pure digits,
|
|
269
269
|
// comma-split into string[] when value contains ',', "true"/"false"
|
|
270
270
|
// → boolean, else string). Multiple --meta flags accumulate into a
|
|
271
271
|
// single flat metadata object.
|
|
@@ -274,7 +274,7 @@ function parseArgs(rawArgs) {
|
|
|
274
274
|
applyMetaFlag(out.metadata, args[++i]);
|
|
275
275
|
}
|
|
276
276
|
else if (a === "--exif-policy") out.exifPolicy = parseExifPolicyFlag(args[++i]);
|
|
277
|
-
//
|
|
277
|
+
// ls --sort and repeatable --filter k=v.
|
|
278
278
|
else if (a === "--sort") out.sort = parseSortFlag(args[++i]);
|
|
279
279
|
else if (a === "--filter") {
|
|
280
280
|
if (out.filter === null) out.filter = {};
|
|
@@ -285,7 +285,7 @@ function parseArgs(rawArgs) {
|
|
|
285
285
|
return out;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
/**
|
|
288
|
+
/** Parses a single `--meta key=value` token. Coerces value:
|
|
289
289
|
* - numeric digits (with optional leading `-` / decimal) → number
|
|
290
290
|
* - "true" / "false" → boolean
|
|
291
291
|
* - commas → string[] (each segment stays a string; agents wanting
|
|
@@ -436,23 +436,19 @@ async function putOne(projectId, filePath, opts) {
|
|
|
436
436
|
}
|
|
437
437
|
const destKey = computeDestKey(filePath, opts.key);
|
|
438
438
|
|
|
439
|
-
//
|
|
440
|
-
// now delegates to `sdk.assets.put`, which routes through the
|
|
439
|
+
// The CLI delegates to `sdk.assets.put`, which routes through the
|
|
441
440
|
// unified-apply hero (apply/v1/plans -> content/v1/plans -> S3 PUT ->
|
|
442
|
-
// commit).
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
// at the apply-plan level (24h plan TTL); a future CLI redesign can
|
|
447
|
-
// expose that. The --concurrency and --no-resume flags are accepted but
|
|
448
|
-
// ignored — the SDK upload paths handle parallelism internally.
|
|
441
|
+
// commit). Resume semantics live at the apply-plan level (24h plan TTL);
|
|
442
|
+
// a future CLI redesign can expose that. The --concurrency and --no-resume
|
|
443
|
+
// flags are accepted but ignored — the SDK upload paths handle parallelism
|
|
444
|
+
// internally.
|
|
449
445
|
log(opts, { event: "start", key: destKey, size_bytes: stat.size });
|
|
450
446
|
const bytes = new Uint8Array(readFileSync(filePath));
|
|
451
447
|
const result = await getSdk().assets.put(projectId, destKey, { bytes }, {
|
|
452
448
|
contentType: opts.contentType ?? guessContentType(destKey),
|
|
453
449
|
visibility: opts.private ? "private" : "public",
|
|
454
450
|
immutable: opts.immutable,
|
|
455
|
-
//
|
|
451
|
+
// Threads caller-supplied metadata + EXIF policy through. The
|
|
456
452
|
// SDK validates each value with the same error code (INVALID_ASSET_
|
|
457
453
|
// METADATA / INVALID_EXIF_POLICY) the gateway returns, so a bad
|
|
458
454
|
// --meta / --exif-policy fails fast without an HTTP roundtrip.
|
|
@@ -532,7 +528,7 @@ async function ls(argv) {
|
|
|
532
528
|
const data = await getSdk().assets.ls(resolvedId, {
|
|
533
529
|
prefix: opts.prefix ?? undefined,
|
|
534
530
|
limit: opts.limit ?? undefined,
|
|
535
|
-
//
|
|
531
|
+
// Forward sort + filter to the SDK. Cursor stays absent for
|
|
536
532
|
// first-page calls; agents pass `--cursor` for follow-ups but the
|
|
537
533
|
// cursor is sort-pinned by the gateway so it doesn't accept a
|
|
538
534
|
// separate `--cursor` flag here yet.
|
package/lib/command-manifest.mjs
CHANGED
|
@@ -197,7 +197,7 @@ export const COMMAND_MANIFEST = [
|
|
|
197
197
|
{ path: ["org", "get"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1"] },
|
|
198
198
|
{ path: ["org", "rename"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--name", "Gate"] },
|
|
199
199
|
{ path: ["org", "payout-wallet"], positionals: [p("org_id")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["org_gate1", "--wallet", "0x1111111111111111111111111111111111111111"] },
|
|
200
|
-
//
|
|
200
|
+
// `--org` (not a positional org_id
|
|
201
201
|
// like rename/payout-wallet above) goes through resolveOrg's SHAPE
|
|
202
202
|
// validation (a real UUID) — "org_gate1" fails that locally, so this needs
|
|
203
203
|
// the same UUID-shaped fixture `org use` below already established.
|
|
@@ -255,13 +255,13 @@ 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
|
-
//
|
|
262
|
-
//
|
|
258
|
+
// ── gitvault — no manifest entries ──────────────────────
|
|
259
|
+
// `cli/lib/gitvault.mjs`'s dispatcher handles every `gitvault <verb>` and
|
|
260
|
+
// answers a structural COMMAND_MOVED (or, for `push`/`reconcile`,
|
|
261
|
+
// COMMAND_REMOVED) redirect that dispatches nothing — see
|
|
262
|
+
// RESERVED_SUBCOMMANDS below, and "gitvault" in SKIPPED_FAMILIES.
|
|
263
263
|
|
|
264
|
-
// ── repos (
|
|
264
|
+
// ── repos (the consolidated 12-verb family) ─
|
|
265
265
|
// Every verb needs a real principal keystore and, for most, an allocated
|
|
266
266
|
// repo and a local git working tree, so the gate runs structural checks
|
|
267
267
|
// only — an in-process behavioral run would either no-op against the
|
|
@@ -269,23 +269,24 @@ export const COMMAND_MANIFEST = [
|
|
|
269
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
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
271
|
{ path: ["repos", "view"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the local principal keystore and the live repo record" },
|
|
272
|
-
//
|
|
273
|
-
//
|
|
272
|
+
// (`gh repo rename`): claims/renames the per-org-unique address-form
|
|
273
|
+
// name, with `--repo`/`--project` addressing.
|
|
274
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
|
|
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" },
|
|
276
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
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
|
-
//
|
|
279
|
-
//
|
|
280
|
-
// stays a single manifest entry.
|
|
278
|
+
// ONE flag-driven verb; `<destination>` is a real attribute (not a
|
|
279
|
+
// sub-verb literal), so this stays a single manifest entry.
|
|
281
280
|
{ 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
281
|
{ 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
282
|
{ 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`
|
|
285
|
-
//
|
|
286
|
-
//
|
|
283
|
+
// `access repair`/`revoke-key`/`declare-exposure` are nested sub-verb
|
|
284
|
+
// literals — one manifest entry each, all dispatched through the ONE
|
|
285
|
+
// `run("access", ...)` case in repos.mjs.
|
|
287
286
|
{ 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: "
|
|
287
|
+
{ path: ["repos", "access", "repair"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["--recipient-state-version", "0", "--recipient-revocation-version", "0"], runStyle: "sub", skipBehavioral: "owner+step-up-gated epoch rotation (D193-D203, rev 42) — samples a fresh epoch key and re-seals a live vault's recipients" },
|
|
288
|
+
{ path: ["repos", "access", "revoke-key"], positionals: [p("principal_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["prin_00000000000000000000000000000000"], runStyle: "sub", skipBehavioral: "owner+step-up-gated: declares a recipient's key revoked (org-scoped watermark) and drives a real epoch rotation off it" },
|
|
289
|
+
{ path: ["repos", "access", "declare-exposure"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "owner+step-up-gated: declares this vault's epoch secret exposed, forcing every subsequent ordinary push to refuse until a rotation lands" },
|
|
289
290
|
{ 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" },
|
|
290
291
|
{ path: ["errors"], positionals: [p("fingerprint_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
|
|
291
292
|
|
|
@@ -439,7 +440,7 @@ export const COMMAND_MANIFEST = [
|
|
|
439
440
|
{ path: ["service", "health"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: [] },
|
|
440
441
|
{ path: ["cache", "inspect"], positionals: [p("url")], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["https://example.com/"] },
|
|
441
442
|
{ path: ["cache", "invalidate"], positionals: [p("url", { required: false })], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--all", "--host", "example.com"] },
|
|
442
|
-
// projectScoped
|
|
443
|
+
// projectScoped: --project targets the gitvault
|
|
443
444
|
// check only (see doctor.mjs's own HELP) — every other check stays
|
|
444
445
|
// wallet/machine-wide, but the gate's contract is "accepts --project
|
|
445
446
|
// without rejecting it," which this satisfies.
|
|
@@ -461,13 +462,13 @@ export const SKIPPED_FAMILIES = {
|
|
|
461
462
|
// Split into `deliveries` / `contacts` / `subscriptions`; every subcommand
|
|
462
463
|
// answers COMMAND_REMOVED naming its successor.
|
|
463
464
|
"notifications": "reserved group; split by legible-cli-surface",
|
|
464
|
-
//
|
|
465
|
-
// `gitvault <verb>`
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
465
|
+
// The gitvault dispatcher answers COMMAND_MOVED/COMMAND_REMOVED for every
|
|
466
|
+
// `gitvault <verb>` (see RESERVED_SUBCOMMANDS below for the per-verb list)
|
|
467
|
+
// — the family stays dispatched in cli.mjs (so the redirect fires instead
|
|
468
|
+
// of UNKNOWN_COMMAND) but has zero manifest entries, since a redirect
|
|
469
|
+
// dispatches nothing.
|
|
469
470
|
"gitvault": "retired; every subcommand answers COMMAND_MOVED/COMMAND_REMOVED naming its `repos`/git successor",
|
|
470
|
-
// `repo` singular resolves identically to `repos`
|
|
471
|
+
// `repo` singular resolves identically to `repos` — same
|
|
471
472
|
// module, same case block in cli.mjs, so it needs no manifest of its own.
|
|
472
473
|
"repo": "alias for `repos`, resolves identically (design D1)",
|
|
473
474
|
};
|
|
@@ -481,8 +482,8 @@ export const SKIPPED_FAMILIES = {
|
|
|
481
482
|
* belongs in no capability mapping, so the inventory gate must not demand one.
|
|
482
483
|
*
|
|
483
484
|
* The family-level equivalent is SKIPPED_FAMILIES above. This exists because
|
|
484
|
-
*
|
|
485
|
-
*
|
|
485
|
+
* some spellings are retired INSIDE a family rather than as a whole family,
|
|
486
|
+
* which the family list cannot express.
|
|
486
487
|
*/
|
|
487
488
|
export const RESERVED_SUBCOMMANDS = {
|
|
488
489
|
"rooms:who": "renamed to `rooms join` — an interrogative must not name a write",
|
|
@@ -495,7 +496,7 @@ export const RESERVED_SUBCOMMANDS = {
|
|
|
495
496
|
"rooms:get": "moved to `messages get` — the verb acts on a message",
|
|
496
497
|
"rooms:ack": "moved to `messages ack` — the verb acts on a message",
|
|
497
498
|
"escalations:contacts": "merged into `contacts` — the ladder and Telegram channels are one question",
|
|
498
|
-
//
|
|
499
|
+
// Every `gitvault <verb>` spelling. Nine
|
|
499
500
|
// answer COMMAND_MOVED naming their `repos` successor; `push` and
|
|
500
501
|
// `reconcile` answer COMMAND_REMOVED (no equivalent successor for either —
|
|
501
502
|
// `push`'s one-release alias window is over, `reconcile` was a workaround
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -41,6 +41,7 @@ import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.
|
|
|
41
41
|
import { withAutoApprove } from "./operator.mjs";
|
|
42
42
|
import { editRequestAction, nextAction, retryAction } from "./next-actions.mjs";
|
|
43
43
|
import { createUpdateCheckScheduler, emitUpdateNotice } from "./update-check.mjs";
|
|
44
|
+
import { sdkStats, printVerboseStats } from "./stats.mjs";
|
|
44
45
|
|
|
45
46
|
const APPLY_HELP = `run402 deploy apply — Unified deploy primitive (v1.34+)
|
|
46
47
|
|
|
@@ -106,9 +107,16 @@ Options:
|
|
|
106
107
|
--final-only Alias for --quiet; final success/error envelope is still preserved
|
|
107
108
|
--allow-warning <code> Continue past this reviewed warning code (repeatable)
|
|
108
109
|
--allow-warnings Continue past plan warnings that require confirmation
|
|
110
|
+
--allow-dirty gitvault-required projects only: capture a dirty
|
|
111
|
+
work tree as-is instead of refusing
|
|
112
|
+
SNAPSHOT_DIRTY_TREE. The result discloses exactly
|
|
113
|
+
what was swept in, printed to stderr too — even
|
|
114
|
+
this override never captures silently.
|
|
115
|
+
-v, --verbose Print one stderr summary line of this call's
|
|
116
|
+
request stats (round trips, wire time, bytes)
|
|
109
117
|
|
|
110
118
|
Output:
|
|
111
|
-
stdout: { "release_id": "rel_...", "operation_id": "op_...", "urls": {...}, "warnings": [...] }
|
|
119
|
+
stdout: { "release_id": "rel_...", "operation_id": "op_...", "urls": {...}, "warnings": [...], "stats": {...} }
|
|
112
120
|
stderr: one JSON event per line (suppressed with --quiet or --final-only)
|
|
113
121
|
|
|
114
122
|
Secrets:
|
|
@@ -654,8 +662,10 @@ function parseApplyArgs(args) {
|
|
|
654
662
|
planFingerprint: null,
|
|
655
663
|
rehearsalTeardown: "keep",
|
|
656
664
|
commitAfterRehearse: false,
|
|
665
|
+
allowDirty: false,
|
|
666
|
+
verbose: false,
|
|
657
667
|
};
|
|
658
|
-
const allowedFlags = ["--manifest", "--spec", "--dir", "--project", "--quiet", "--final-only", "--json", "--allow-warning", "--allow-warnings", "--check", "--print-spec", "--plan", "--rehearse", "--teardown", "--commit", "--require-plan", "--plan-fingerprint", "--help", "-h"];
|
|
668
|
+
const allowedFlags = ["--manifest", "--spec", "--dir", "--project", "--quiet", "--final-only", "--json", "--allow-warning", "--allow-warnings", "--check", "--print-spec", "--plan", "--rehearse", "--teardown", "--commit", "--require-plan", "--plan-fingerprint", "--allow-dirty", "-v", "--verbose", "--help", "-h"];
|
|
659
669
|
|
|
660
670
|
for (let i = 0; i < args.length; i++) {
|
|
661
671
|
const arg = args[i];
|
|
@@ -723,6 +733,8 @@ function parseApplyArgs(args) {
|
|
|
723
733
|
if (arg === "--quiet" || arg === "--final-only") { opts.quiet = true; continue; }
|
|
724
734
|
if (arg === "--json") { continue; }
|
|
725
735
|
if (arg === "--allow-warnings") { opts.allowWarnings = true; continue; }
|
|
736
|
+
if (arg === "--allow-dirty") { opts.allowDirty = true; continue; }
|
|
737
|
+
if (arg === "-v" || arg === "--verbose") { opts.verbose = true; continue; }
|
|
726
738
|
if (arg === "--check") { setApplyMode(opts, "check", "--check"); continue; }
|
|
727
739
|
if (arg === "--print-spec") { setApplyMode(opts, "printSpec", "--print-spec"); continue; }
|
|
728
740
|
if (arg === "--plan") { setApplyMode(opts, "plan", "--plan"); continue; }
|
|
@@ -1247,9 +1259,10 @@ async function applyCmd(args) {
|
|
|
1247
1259
|
// not `required` runs the same `apply()` this line always ran, with the
|
|
1248
1260
|
// same options object; a `required` one captures, publishes, mints an
|
|
1249
1261
|
// activation token, and commits with it.
|
|
1262
|
+
const sdk = getSdk(sdkOpts);
|
|
1250
1263
|
const outcome = await withAutoApprove(() =>
|
|
1251
1264
|
applyWithGitvault({
|
|
1252
|
-
sdk
|
|
1265
|
+
sdk,
|
|
1253
1266
|
spec: releaseSpec,
|
|
1254
1267
|
apply: {
|
|
1255
1268
|
onEvent: makeStderrEventWriter(opts.quiet),
|
|
@@ -1259,13 +1272,15 @@ async function applyCmd(args) {
|
|
|
1259
1272
|
...(requiredPlan ? { requiredPlan } : {}),
|
|
1260
1273
|
},
|
|
1261
1274
|
target: isCoreApiTarget() ? "core" : "cloud",
|
|
1275
|
+
...(opts.allowDirty ? { allowDirty: true } : {}),
|
|
1262
1276
|
// The `gitvault_commit` line is printed ALWAYS, and to stderr, so
|
|
1263
1277
|
// `run402 deploy apply | jq` stays clean (the pipe contract).
|
|
1264
1278
|
onCommitLine: (line) => { if (!opts.quiet) process.stderr.write(`${line}\n`); },
|
|
1265
1279
|
}),
|
|
1266
1280
|
);
|
|
1267
1281
|
if (!outcome.gitvault) {
|
|
1268
|
-
console.log(JSON.stringify(outcome.deploy, null, 2));
|
|
1282
|
+
console.log(JSON.stringify({ ...outcome.deploy, stats: sdkStats(sdk) }, null, 2));
|
|
1283
|
+
printVerboseStats(opts.verbose, sdk);
|
|
1269
1284
|
return;
|
|
1270
1285
|
}
|
|
1271
1286
|
const vaulted = outcome.gitvault;
|
|
@@ -1290,6 +1305,7 @@ async function applyCmd(args) {
|
|
|
1290
1305
|
...("reconcile_recipients" in vaulted ? { reconcile_recipients: vaulted.reconcile_recipients } : {}),
|
|
1291
1306
|
next_actions: vaulted.next_actions,
|
|
1292
1307
|
deploy: outcome.deploy,
|
|
1308
|
+
stats: sdkStats(sdk),
|
|
1293
1309
|
}, null, 2));
|
|
1294
1310
|
// Design D6: the mirror result is reported BESIDE the vault outcome
|
|
1295
1311
|
// above, on its own stderr line — a mirror failure never blocks the
|
|
@@ -1299,6 +1315,13 @@ async function applyCmd(args) {
|
|
|
1299
1315
|
} else if (vaulted.mirror_push?.outcome === "failed") {
|
|
1300
1316
|
console.error(`mirror: dual-push FAILED (deploy is unaffected) — ${vaulted.mirror_push.error ?? "see mirror_push.summary.errors"}`);
|
|
1301
1317
|
}
|
|
1318
|
+
// Dirty-tree disclosure (Tal's decision, "help people not make
|
|
1319
|
+
// mistakes"): even an explicit --allow-dirty override never captures
|
|
1320
|
+
// silently — name every modified/staged and untracked-not-ignored path
|
|
1321
|
+
// that got swept into this deploy's capture.
|
|
1322
|
+
for (const p of vaulted.snapshot?.modified_captured ?? []) console.error(`captured (modified): ${p}`);
|
|
1323
|
+
for (const p of vaulted.snapshot?.untracked_captured ?? []) console.error(`captured (untracked): ${p}`);
|
|
1324
|
+
printVerboseStats(opts.verbose, sdk);
|
|
1302
1325
|
// A non-activating outcome is a failed deploy even though it resolved
|
|
1303
1326
|
// rather than threw: the five outcomes are a result type, not an error
|
|
1304
1327
|
// channel, so the exit code has to carry the verdict.
|
|
@@ -1400,16 +1423,13 @@ const CI_DEPLOY_ERROR_GUIDANCE = {
|
|
|
1400
1423
|
};
|
|
1401
1424
|
|
|
1402
1425
|
/**
|
|
1403
|
-
* The
|
|
1404
|
-
*
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
* `grandfather_policy` — is now TRUE as written, so it is relayed untouched;
|
|
1411
|
-
* re-authoring it here would be the client lying in the opposite direction.
|
|
1412
|
-
* `cli-deploy-gitvault-lane.test.mjs` is the successor gate.
|
|
1426
|
+
* The gateway's envelope for gitvault deploy errors — `upgrade_client` first,
|
|
1427
|
+
* then `grandfather_policy` — is relayed untouched: `applyCmd` deploys
|
|
1428
|
+
* through `applyWithGitvault`, which declares `{capture_id,
|
|
1429
|
+
* snapshot_oid_hmac}` at plan time and presents an activation token at
|
|
1430
|
+
* commit, satisfying `gitvault_policy: required`, so no client-side rewrite
|
|
1431
|
+
* of the envelope is needed. `cli-deploy-gitvault-lane.test.mjs` is the gate
|
|
1432
|
+
* for this behavior.
|
|
1413
1433
|
*/
|
|
1414
1434
|
function reportDeployApplyError(err, useGithubActionsOidc) {
|
|
1415
1435
|
const warningEnhanced = enhanceDeployWarningError(err);
|
package/lib/doctor.mjs
CHANGED
|
@@ -568,6 +568,11 @@ export async function run(sub, args = []) {
|
|
|
568
568
|
pending_overrides: gv.pending_overrides,
|
|
569
569
|
pins: gv.pins,
|
|
570
570
|
remote: gv.remote,
|
|
571
|
+
// dogfood item 2: `null` (unknown) or <= 1 means the single-principal
|
|
572
|
+
// V0-A terminal-loss statement below is honest; >= 2 means the SDK
|
|
573
|
+
// already proved a second covering recipient, so the hint switches
|
|
574
|
+
// to the durability sentence instead of the terminal-loss claim.
|
|
575
|
+
covering_recipients: gv.covering_recipients ?? null,
|
|
571
576
|
};
|
|
572
577
|
const gaps = [];
|
|
573
578
|
// The one that actually breaks the next deploy: the project demands a
|
|
@@ -632,7 +637,9 @@ export async function run(sub, args = []) {
|
|
|
632
637
|
value: gaps.length > 0 ? { ...value, gaps } : value,
|
|
633
638
|
hint: gv.vault === null
|
|
634
639
|
? `No vault for this project (that is a normal shape). Allocate one with 'run402 repos create --project <id>'. Keystore: ${gv.keystore.root}`
|
|
635
|
-
:
|
|
640
|
+
: gv.durability_statement
|
|
641
|
+
? `Back up ${gv.keystore.root} anyway — ${gv.durability_statement} (covering_recipients: ${gv.covering_recipients})`
|
|
642
|
+
: `Back up ${gv.keystore.root} — whole-machine or whole-keystore loss is terminal for vault history.`,
|
|
636
643
|
});
|
|
637
644
|
} catch (err) {
|
|
638
645
|
// A gateway without gitvault, an unreachable API, or a project this
|
package/lib/gitvault-target.mjs
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared "which vault does this repo mean" resolution for `run402 gitvault`
|
|
3
|
-
* and `run402 doctor`'s gitvault check
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* repo, and a stale active-project pointer silently targeted a DIFFERENT
|
|
9
|
-
* one instead (GITVAULT_ACCESS_DENIED, or worse, a silently WRONG vault).
|
|
3
|
+
* and `run402 doctor`'s gitvault check. When a verb runs standing inside a
|
|
4
|
+
* repository that already names its own vault via a pinned repo id or a
|
|
5
|
+
* run402/origin remote, targeting prefers that repo over the active-project
|
|
6
|
+
* pointer — git muscle memory says a command run inside a repo acts on THAT
|
|
7
|
+
* repo.
|
|
10
8
|
*
|
|
11
9
|
* Targeting order for a verb run standing inside a git repository, highest
|
|
12
10
|
* first:
|
|
13
11
|
* 1. an explicit --repo/--project flag (owned by each call site — this
|
|
14
12
|
* module supplies only the fallback chain beneath it, plus the
|
|
15
13
|
* mismatch warning against tier 3)
|
|
16
|
-
* 2. the
|
|
14
|
+
* 2. the pinned `r402.repoId` in local git config — addresses the
|
|
17
15
|
* vault by repo_id directly, no network read at all
|
|
18
16
|
* 3. the repo's run402/origin remote address — id-form is parsed
|
|
19
17
|
* directly out of the address string (free, no network); slug-form is
|
|
@@ -24,7 +22,7 @@
|
|
|
24
22
|
* 5. the profile's active project
|
|
25
23
|
*
|
|
26
24
|
* Outside a repository (or when repo detection itself fails), only tiers 4
|
|
27
|
-
* and 5 apply
|
|
25
|
+
* and 5 apply.
|
|
28
26
|
*
|
|
29
27
|
* ARCHITECTURAL NOTE: this is CLI-edge policy (which flag/env/file wins),
|
|
30
28
|
* not gitvault protocol behavior — the same class of concern
|
package/lib/gitvault.mjs
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `run402 gitvault` —
|
|
2
|
+
* `run402 gitvault` — retired from the CLI; every spelling answers a
|
|
3
|
+
* structured error naming its `repos`/git successor.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* spelling now answers a proper stderr JSON error envelope: typed
|
|
5
|
+
* Every spelling answers a proper stderr JSON error envelope: typed
|
|
6
6
|
* `next_actions`, non-zero exit, EMPTY stdout — never silence, never new
|
|
7
7
|
* behavior. Renamed verbs answer `COMMAND_MOVED` naming their `repos`
|
|
8
8
|
* successor; verbs with no behavioral successor answer `COMMAND_REMOVED`
|
|
9
|
-
* (never a `COMMAND_MOVED` that would lie about equivalence
|
|
10
|
-
*
|
|
11
|
-
* re-pointed at different behavior — the `rooms`→`messages` precedent.
|
|
9
|
+
* (never a `COMMAND_MOVED` that would lie about equivalence). The freed
|
|
10
|
+
* spellings are never re-pointed at different behavior.
|
|
12
11
|
*
|
|
13
12
|
* `gitvault` itself survives only as protocol/infrastructure language: the
|
|
14
|
-
* SDK keeps `r.gitvault.*` (
|
|
15
|
-
*
|
|
13
|
+
* SDK keeps `r.gitvault.*` ("gitvault is what the thing IS; repos is what
|
|
14
|
+
* the user HAS"). This module's one surviving export,
|
|
16
15
|
* `printKeystoreLocation`, is a shared print helper `cli/lib/repos.mjs`
|
|
17
16
|
* still composes — moving it there instead would make repos.mjs restate
|
|
18
17
|
* logic this file already owns for zero benefit.
|
package/lib/next-actions.mjs
CHANGED
|
@@ -70,12 +70,15 @@ export function deployAction() {
|
|
|
70
70
|
/**
|
|
71
71
|
* `repos create` (and `gitvault init`) on an org with no claimed slug: the
|
|
72
72
|
* response's `address: null` had no pointer to WHY, or to the named-addressing
|
|
73
|
-
* feature at all (kychee-com/run402#560).
|
|
73
|
+
* feature at all (kychee-com/run402#560). Owner-only. Wording deliberately
|
|
74
|
+
* omits a price: the first slug per org is free, and renames/re-claims cost
|
|
75
|
+
* $1 — a gateway change lands the fee separately, so this stays true under
|
|
76
|
+
* both the old and new pricing rule.
|
|
74
77
|
*/
|
|
75
78
|
export function claimOrgSlugAction() {
|
|
76
79
|
return nextAction("claim_org_slug", {
|
|
77
80
|
command: "run402 org slug <slug>",
|
|
78
|
-
why: "This organization has no claimed slug yet, so its repos have no run402::<slug>/<name> address.
|
|
81
|
+
why: "This organization has no claimed slug yet, so its repos have no run402::<slug>/<name> address. Owner-only.",
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
84
|
|
package/lib/notifications.mjs
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `run402 notifications` —
|
|
3
|
-
*
|
|
4
|
-
* The group was two nouns in one coat: `list`/`get` read delivery RECORDS
|
|
5
|
-
* while `preferences`/`channels`/`rules` were the CONFIGURATION producing
|
|
6
|
-
* them. It has been split along that line:
|
|
2
|
+
* `run402 notifications` — tombstone dispatcher. Delivery RECORDS and the
|
|
3
|
+
* CONFIGURATION producing them live under separate nouns:
|
|
7
4
|
*
|
|
8
5
|
* notifications list|get -> deliveries list|get
|
|
9
6
|
* notifications channels -> contacts (merged with escalations contacts)
|
|
10
7
|
* notifications rules -> subscriptions
|
|
11
8
|
* notifications preferences -> contacts preferences
|
|
12
9
|
*
|
|
13
|
-
* Reserved, not aliased
|
|
14
|
-
*
|
|
15
|
-
* one forever.
|
|
10
|
+
* Reserved, not aliased: each subcommand names its successor so one failed
|
|
11
|
+
* call teaches the new model, where an alias would teach the old one forever.
|
|
16
12
|
*/
|
|
17
13
|
import { fail } from "./sdk-errors.mjs";
|
|
18
14
|
import { hasHelp } from "./argparse.mjs";
|
package/lib/operator.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Agent-first: JSON to stdout. `login` additionally prints the verification URL
|
|
16
16
|
* + user code to stderr (human-in-the-loop) and degrades gracefully when not a
|
|
17
|
-
* TTY. Gated on the gateway device-auth bridge
|
|
17
|
+
* TTY. Gated on the gateway device-auth bridge.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { setTimeout as sleep } from "node:timers/promises";
|
package/lib/org.mjs
CHANGED
|
@@ -438,9 +438,9 @@ async function payoutWallet(args) {
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
/**
|
|
441
|
-
* `run402 org slug <slug>` — claim or rename the org's address-form slug
|
|
442
|
-
*
|
|
443
|
-
*
|
|
441
|
+
* `run402 org slug <slug>` — claim or rename the org's address-form slug.
|
|
442
|
+
* Owner-only, and a genesis claim spends a one-time claim fee — this is a
|
|
443
|
+
* PAID, side-effecting mutation, so it
|
|
444
444
|
* requires `Idempotency-Key`; the SDK generates one client-side when
|
|
445
445
|
* `--idempotency-key` is omitted, so a retried call after a dropped response
|
|
446
446
|
* can never double-bill.
|