run402 4.42.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.
Files changed (46) hide show
  1. package/lib/command-manifest.mjs +6 -3
  2. package/lib/deploy-v2.mjs +27 -4
  3. package/lib/next-actions.mjs +5 -2
  4. package/lib/repos.mjs +466 -117
  5. package/lib/stats.mjs +57 -0
  6. package/package.json +1 -1
  7. package/sdk/dist/index.d.ts +10 -1
  8. package/sdk/dist/index.d.ts.map +1 -1
  9. package/sdk/dist/index.js +11 -0
  10. package/sdk/dist/index.js.map +1 -1
  11. package/sdk/dist/kernel.d.ts +31 -0
  12. package/sdk/dist/kernel.d.ts.map +1 -1
  13. package/sdk/dist/kernel.js +116 -3
  14. package/sdk/dist/kernel.js.map +1 -1
  15. package/sdk/dist/namespaces/gitvault.crypto.d.ts +178 -5
  16. package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
  17. package/sdk/dist/namespaces/gitvault.crypto.js +266 -7
  18. package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
  19. package/sdk/dist/namespaces/gitvault.d.ts +103 -1
  20. package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
  21. package/sdk/dist/namespaces/gitvault.js +175 -7
  22. package/sdk/dist/namespaces/gitvault.js.map +1 -1
  23. package/sdk/dist/namespaces/gitvault.types.d.ts +184 -9
  24. package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -1
  25. package/sdk/dist/namespaces/gitvault.types.js +2 -1
  26. package/sdk/dist/namespaces/gitvault.types.js.map +1 -1
  27. package/sdk/dist/node/gitvault-apply.d.ts +8 -0
  28. package/sdk/dist/node/gitvault-apply.d.ts.map +1 -1
  29. package/sdk/dist/node/gitvault-apply.js +1 -0
  30. package/sdk/dist/node/gitvault-apply.js.map +1 -1
  31. package/sdk/dist/node/gitvault-deploy.d.ts +13 -0
  32. package/sdk/dist/node/gitvault-deploy.d.ts.map +1 -1
  33. package/sdk/dist/node/gitvault-deploy.js +28 -0
  34. package/sdk/dist/node/gitvault-deploy.js.map +1 -1
  35. package/sdk/dist/node/gitvault-keystore.d.ts +54 -4
  36. package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
  37. package/sdk/dist/node/gitvault-keystore.js +20 -0
  38. package/sdk/dist/node/gitvault-keystore.js.map +1 -1
  39. package/sdk/dist/node/gitvault-publication.d.ts +328 -4
  40. package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
  41. package/sdk/dist/node/gitvault-publication.js +560 -14
  42. package/sdk/dist/node/gitvault-publication.js.map +1 -1
  43. package/sdk/dist/node/gitvault-snapshot.d.ts +40 -7
  44. package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
  45. package/sdk/dist/node/gitvault-snapshot.js +67 -19
  46. package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
@@ -280,10 +280,13 @@ export const COMMAND_MANIFEST = [
280
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" },
281
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" },
282
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" },
283
- // `access repair` is a nested sub-verb literal — two manifest entries,
284
- // one dispatched `run("access", ...)` case in repos.mjs.
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.
285
286
  { path: ["repos", "access"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "reads the live org encryption-key directory + vault envelope recipients" },
286
- { path: ["repos", "access", "repair"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub", skipBehavioral: "always refusesgated on gitvault-human-envelopes' epoch-rotation work, not shipped yet" },
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" },
287
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" },
288
291
  { path: ["errors"], positionals: [p("fingerprint_id", { required: false })], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
289
292
 
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: getSdk(sdkOpts),
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.
@@ -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). One-time $1 fee, owner-only.
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. One-time $1, owner-only.",
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