run402 4.68.3 → 4.69.1

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 (141) hide show
  1. package/cli.mjs +2 -2
  2. package/core-dist/allowance-auth.js +1 -1
  3. package/core-dist/allowance.js +1 -1
  4. package/core-dist/config.js +4 -4
  5. package/core-dist/keystore.js +7 -7
  6. package/core-dist/profiles.js +1 -1
  7. package/core-dist/redact.js +6 -7
  8. package/git-remote-run402.mjs +2 -2
  9. package/gitvault-surface.json +2 -1
  10. package/lib/agent.mjs +1 -1
  11. package/lib/allowance.mjs +3 -4
  12. package/lib/argparse.mjs +7 -9
  13. package/lib/billing.mjs +33 -35
  14. package/lib/buzz-notifications.mjs +3 -2
  15. package/lib/buzz.mjs +14 -8
  16. package/lib/buzz.test.mjs +1 -1
  17. package/lib/command-manifest.mjs +38 -20
  18. package/lib/config.mjs +1 -1
  19. package/lib/deploy-v2.mjs +3 -3
  20. package/lib/doctor-source-scan.mjs +1 -1
  21. package/lib/doctor.mjs +42 -25
  22. package/lib/email.mjs +2 -3
  23. package/lib/harness-context.mjs +1 -2
  24. package/lib/init.mjs +1 -1
  25. package/lib/next-actions.mjs +1 -1
  26. package/lib/org-context.mjs +83 -2
  27. package/lib/org.mjs +231 -114
  28. package/lib/projects.mjs +2 -2
  29. package/lib/remote-helper-session.mjs +26 -32
  30. package/lib/repos.mjs +117 -15
  31. package/lib/rooms-context.mjs +5 -5
  32. package/lib/sdk-errors.mjs +1 -2
  33. package/lib/status.mjs +1 -1
  34. package/lib/update-check.test.mjs +1 -1
  35. package/lib/wallet-context.mjs +10 -10
  36. package/lib/wallet-context.test.mjs +6 -7
  37. package/lib/wallets.mjs +2 -2
  38. package/lib/webhooks.mjs +2 -2
  39. package/package.json +1 -1
  40. package/sdk/core-dist/allowance-auth.js +1 -1
  41. package/sdk/core-dist/allowance.js +1 -1
  42. package/sdk/core-dist/config.js +4 -4
  43. package/sdk/core-dist/keystore.js +7 -7
  44. package/sdk/core-dist/profiles.js +1 -1
  45. package/sdk/core-dist/redact.js +6 -7
  46. package/sdk/dist/delegate-credentials.d.ts +1 -1
  47. package/sdk/dist/delegate-credentials.js +1 -1
  48. package/sdk/dist/errors.d.ts +10 -4
  49. package/sdk/dist/errors.d.ts.map +1 -1
  50. package/sdk/dist/errors.js +2 -3
  51. package/sdk/dist/errors.js.map +1 -1
  52. package/sdk/dist/kernel.js +1 -1
  53. package/sdk/dist/kernel.js.map +1 -1
  54. package/sdk/dist/namespaces/allowance.d.ts +1 -1
  55. package/sdk/dist/namespaces/allowance.js +1 -1
  56. package/sdk/dist/namespaces/allowance.js.map +1 -1
  57. package/sdk/dist/namespaces/assets.js +1 -1
  58. package/sdk/dist/namespaces/assets.js.map +1 -1
  59. package/sdk/dist/namespaces/assets.types.d.ts +1 -1
  60. package/sdk/dist/namespaces/ci.d.ts.map +1 -1
  61. package/sdk/dist/namespaces/ci.js +4 -6
  62. package/sdk/dist/namespaces/ci.js.map +1 -1
  63. package/sdk/dist/namespaces/deploy.js +7 -8
  64. package/sdk/dist/namespaces/deploy.js.map +1 -1
  65. package/sdk/dist/namespaces/gitvault.crypto.d.ts +86 -3
  66. package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
  67. package/sdk/dist/namespaces/gitvault.crypto.js +102 -3
  68. package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
  69. package/sdk/dist/namespaces/gitvault.d.ts +197 -17
  70. package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
  71. package/sdk/dist/namespaces/gitvault.js +698 -58
  72. package/sdk/dist/namespaces/gitvault.js.map +1 -1
  73. package/sdk/dist/namespaces/gitvault.types.d.ts +91 -3
  74. package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -1
  75. package/sdk/dist/namespaces/gitvault.types.js.map +1 -1
  76. package/sdk/dist/namespaces/operator.d.ts +1 -1
  77. package/sdk/dist/namespaces/operator.js +1 -1
  78. package/sdk/dist/namespaces/org.d.ts +14 -1
  79. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  80. package/sdk/dist/namespaces/org.js +15 -0
  81. package/sdk/dist/namespaces/org.js.map +1 -1
  82. package/sdk/dist/namespaces/org.types.d.ts +30 -0
  83. package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
  84. package/sdk/dist/namespaces/projects.types.d.ts +1 -1
  85. package/sdk/dist/namespaces/subdomains.js +1 -1
  86. package/sdk/dist/namespaces/subdomains.js.map +1 -1
  87. package/sdk/dist/namespaces/transfers.d.ts +3 -4
  88. package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
  89. package/sdk/dist/namespaces/transfers.js +1 -2
  90. package/sdk/dist/namespaces/transfers.js.map +1 -1
  91. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  92. package/sdk/dist/node/actions-node.js +3 -4
  93. package/sdk/dist/node/actions-node.js.map +1 -1
  94. package/sdk/dist/node/deploy-manifest.js +2 -2
  95. package/sdk/dist/node/deploy-manifest.js.map +1 -1
  96. package/sdk/dist/node/gitvault-address.js +2 -2
  97. package/sdk/dist/node/gitvault-address.js.map +1 -1
  98. package/sdk/dist/node/gitvault-creation-journal.d.ts +1 -1
  99. package/sdk/dist/node/gitvault-creation-journal.js +2 -2
  100. package/sdk/dist/node/gitvault-creation-journal.js.map +1 -1
  101. package/sdk/dist/node/gitvault-deploy.d.ts +2 -2
  102. package/sdk/dist/node/gitvault-deploy.js +2 -2
  103. package/sdk/dist/node/gitvault-handoff.d.ts +140 -1
  104. package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
  105. package/sdk/dist/node/gitvault-handoff.js +226 -22
  106. package/sdk/dist/node/gitvault-handoff.js.map +1 -1
  107. package/sdk/dist/node/gitvault-keystore.d.ts +78 -3
  108. package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
  109. package/sdk/dist/node/gitvault-keystore.js +46 -5
  110. package/sdk/dist/node/gitvault-keystore.js.map +1 -1
  111. package/sdk/dist/node/gitvault-mirror.d.ts.map +1 -1
  112. package/sdk/dist/node/gitvault-mirror.js +3 -5
  113. package/sdk/dist/node/gitvault-mirror.js.map +1 -1
  114. package/sdk/dist/node/gitvault-profile-scan.js +1 -1
  115. package/sdk/dist/node/gitvault-publication.d.ts +379 -21
  116. package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
  117. package/sdk/dist/node/gitvault-publication.js +664 -41
  118. package/sdk/dist/node/gitvault-publication.js.map +1 -1
  119. package/sdk/dist/node/gitvault-recover.d.ts.map +1 -1
  120. package/sdk/dist/node/gitvault-recover.js +7 -10
  121. package/sdk/dist/node/gitvault-recover.js.map +1 -1
  122. package/sdk/dist/node/gitvault-restore.d.ts.map +1 -1
  123. package/sdk/dist/node/gitvault-restore.js +5 -2
  124. package/sdk/dist/node/gitvault-restore.js.map +1 -1
  125. package/sdk/dist/node/gitvault-snapshot.d.ts +16 -2
  126. package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
  127. package/sdk/dist/node/gitvault-snapshot.js +23 -2
  128. package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
  129. package/sdk/dist/node/gitvault-writer-state.d.ts +201 -0
  130. package/sdk/dist/node/gitvault-writer-state.d.ts.map +1 -0
  131. package/sdk/dist/node/gitvault-writer-state.js +357 -0
  132. package/sdk/dist/node/gitvault-writer-state.js.map +1 -0
  133. package/sdk/dist/node/index.d.ts +5 -1
  134. package/sdk/dist/node/index.d.ts.map +1 -1
  135. package/sdk/dist/node/index.js +21 -2
  136. package/sdk/dist/node/index.js.map +1 -1
  137. package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
  138. package/sdk/dist/node/paid-fetch.js +3 -4
  139. package/sdk/dist/node/paid-fetch.js.map +1 -1
  140. package/sdk/dist/node/sites-node.js +1 -1
  141. package/sdk/dist/node/sites-node.js.map +1 -1
package/lib/org.mjs CHANGED
@@ -5,6 +5,8 @@ import { nextAction } from "./next-actions.mjs";
5
5
  import {
6
6
  requireOrgIdShape,
7
7
  resolveOrg,
8
+ takeOrgPositional,
9
+ requireRest,
8
10
  orgProvenance,
9
11
  getSelectedOrgId,
10
12
  setSelectedOrgId,
@@ -27,9 +29,9 @@ const HELP = `run402 org — organizations: create, label, membership, invites
27
29
  Usage:
28
30
  run402 org create [--name <label>]
29
31
  run402 org list
30
- run402 org get <org_id>
31
- run402 org rename <org_id> --name <display_name> (or: --clear to remove the label)
32
- run402 org payout-wallet <org_id> --wallet <wallet_address> (or: --clear to remove the explicit default)
32
+ run402 org get [<org_id>]
33
+ run402 org rename [<org_id>] --name <display_name> (or: --clear to remove the label)
34
+ run402 org payout-wallet [<org_id>] <wallet_address> (or: --clear to remove the explicit default)
33
35
  run402 org slug <slug> [--org <org_id>]
34
36
  run402 org whoami
35
37
  run402 org use <org_id>
@@ -37,17 +39,22 @@ Usage:
37
39
  run402 org clear
38
40
  run402 org bind [--org <org_id>] [--room <key>]
39
41
  run402 org unbind
40
- run402 org audit <org_id> [--limit N] [--after <cursor>] [--before <cursor>]
41
- run402 org member list <org_id>
42
- run402 org member add <org_id> --wallet <wallet_address> [--role <role>]
43
- run402 org member role <org_id> --principal <principal_id> --role <role>
44
- run402 org member rm <org_id> --principal <principal_id>
45
- run402 org invite list <org_id>
46
- run402 org invite create <org_id> --email <email> [--role <role>] [--ttl-hours N]
47
- run402 org invite rm <org_id> --principal <principal_id>
48
-
49
- Legacy (still supported): the second attribute may also be passed positionally,
50
- e.g. run402 org member add <org_id> <wallet_address>.
42
+ run402 org audit [<org_id>] [--limit N] [--after <cursor>] [--before <cursor>]
43
+ run402 org member list [<org_id>]
44
+ run402 org member add [<org_id>] <wallet_address> [--role <role>]
45
+ run402 org member role [<org_id>] --principal <principal_id> --role <role>
46
+ run402 org member rm [<org_id>] --principal <principal_id>
47
+ run402 org member revoke-key [<org_id>] --principal <principal_id> [--reason <why>] (owner + step-up; revokes the member's gitvault encryption key — its next gitvault operation enrolls afresh)
48
+ run402 org invite list [<org_id>]
49
+ run402 org invite create [<org_id>] --email <email> [--role <role>] [--ttl-hours N]
50
+ run402 org invite rm [<org_id>] --principal <principal_id>
51
+
52
+ <org_id> is optional everywhere: a leading UUID positional addresses that org;
53
+ omit it and the org comes from --org, then RUN402_ORG, then the .run402.json
54
+ binding, then 'run402 org use'. Inside a bound checkout two agents add each
55
+ other with nothing to look up: run402 org member add <wallet_address> --role developer.
56
+ A developer-or-above add REFUSES (GITVAULT_WRITER_NOT_ADMITTED) unless this session's key is an admitted writer on every vault of the org, so the new member can push at once; a current writer admits your key with run402 repos access sync.
57
+ The second attribute may also be passed positionally (run402 org member add <wallet_address>).
51
58
 
52
59
  Subcommands:
53
60
  create Create an empty org on the prototype tier (you become owner)
@@ -78,14 +85,16 @@ Notes:
78
85
  Examples:
79
86
  run402 org create --name "Kychee"
80
87
  run402 org list
81
- run402 org get org_abc
82
- run402 org rename org_abc "New Name"
83
- run402 org rename org_abc --clear
84
- run402 org payout-wallet org_abc 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
85
- run402 org payout-wallet org_abc --clear
86
- run402 org member add org_abc 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --role admin
87
- run402 org invite create org_abc dev@example.com --role developer
88
- run402 org audit org_abc --limit 50
88
+ run402 org get # the current org (binding / org use)
89
+ run402 org get 8f14e45f-ceea-4b7a-9d3c-0b2a6e6f1c2d
90
+ run402 org rename --name "New Name"
91
+ run402 org rename --clear
92
+ run402 org payout-wallet 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
93
+ run402 org payout-wallet --clear
94
+ run402 org member add 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --role admin
95
+ run402 org member add 8f14e45f-ceea-4b7a-9d3c-0b2a6e6f1c2d 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --role admin
96
+ run402 org invite create dev@example.com --role developer
97
+ run402 org audit --limit 50
89
98
  `;
90
99
 
91
100
  const SUB_HELP = {
@@ -108,7 +117,7 @@ Usage:
108
117
  get: `run402 org get — read one org (label + tier/lease + your role)
109
118
 
110
119
  Usage:
111
- run402 org get <org_id>
120
+ run402 org get [<org_id>]
112
121
 
113
122
  Any active member may read. A non-member (including a guessed id) gets the same
114
123
  non-revealing 403.
@@ -116,11 +125,11 @@ non-revealing 403.
116
125
  rename: `run402 org rename — set or clear an org's display label (owner-only)
117
126
 
118
127
  Usage:
119
- run402 org rename <org_id> --name <display_name>
120
- run402 org rename <org_id> --clear
128
+ run402 org rename [<org_id>] --name <display_name>
129
+ run402 org rename [<org_id>] --clear
121
130
 
122
131
  Legacy (still supported):
123
- run402 org rename <org_id> <display_name>
132
+ run402 org rename [<org_id>] <display_name>
124
133
 
125
134
  Owner-only + step-up gated. Pass --clear (or an empty display_name) to remove
126
135
  the label. Output includes the updated tier and lease timestamps.
@@ -148,11 +157,11 @@ call after a dropped response can never double-bill.
148
157
  "payout-wallet": `run402 org payout-wallet — set or clear the tenant route payout wallet
149
158
 
150
159
  Usage:
151
- run402 org payout-wallet <org_id> --wallet <wallet_address>
152
- run402 org payout-wallet <org_id> --clear
160
+ run402 org payout-wallet [<org_id>] <wallet_address>
161
+ run402 org payout-wallet [<org_id>] --clear
153
162
 
154
163
  Legacy (still supported):
155
- run402 org payout-wallet <org_id> <wallet_address>
164
+ run402 org payout-wallet [<org_id>] <wallet_address>
156
165
 
157
166
  Admin/owner-only + step-up gated. The wallet must already be active and linked
158
167
  to the same org. This wallet receives x402 settlement for function web routes
@@ -174,10 +183,11 @@ for local wallet/profile state use 'run402 status'.
174
183
  member: `run402 org member — manage org members
175
184
 
176
185
  Usage:
177
- run402 org member list <org_id>
178
- run402 org member add <org_id> --wallet <wallet_address> [--role <role>]
179
- run402 org member role <org_id> --principal <principal_id> --role <role>
180
- run402 org member rm <org_id> --principal <principal_id>
186
+ run402 org member list [<org_id>]
187
+ run402 org member add [<org_id>] <wallet_address> [--role <role>]
188
+ run402 org member role [<org_id>] --principal <principal_id> --role <role>
189
+ run402 org member rm [<org_id>] --principal <principal_id>
190
+ run402 org member revoke-key [<org_id>] --principal <principal_id> [--reason <why>] (owner + step-up; revokes the member's gitvault encryption key — its next gitvault operation enrolls afresh)
181
191
 
182
192
  Roles: ${ROLE_LIST} (add defaults to developer). Mutations require an active owner.
183
193
  Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
@@ -185,12 +195,16 @@ Demoting/removing the org's only active owner fails with 409 LAST_OWNER.
185
195
  invite: `run402 org invite — manage email invites
186
196
 
187
197
  Usage:
188
- run402 org invite list <org_id>
189
- run402 org invite create <org_id> --email <email> [--role <role>] [--ttl-hours N]
190
- run402 org invite rm <org_id> --principal <principal_id>
198
+ run402 org invite list [<org_id>]
199
+ run402 org invite create [<org_id>] --email <email> [--role <role>] [--ttl-hours N]
200
+ run402 org invite rm [<org_id>] --principal <principal_id>
191
201
 
192
- Legacy (still supported): the second attribute may also be passed positionally,
193
- e.g. run402 org member add <org_id> <wallet_address>.
202
+ <org_id> is optional everywhere: a leading UUID positional addresses that org;
203
+ omit it and the org comes from --org, then RUN402_ORG, then the .run402.json
204
+ binding, then 'run402 org use'. Inside a bound checkout two agents add each
205
+ other with nothing to look up: run402 org member add <wallet_address> --role developer.
206
+ A developer-or-above add REFUSES (GITVAULT_WRITER_NOT_ADMITTED) unless this session's key is an admitted writer on every vault of the org, so the new member can push at once; a current writer admits your key with run402 repos access sync.
207
+ The second attribute may also be passed positionally (run402 org member add <wallet_address>).
194
208
 
195
209
  An invite is claimed at the recipient's first login. Mutations require an active owner
196
210
  (plus step-up when driven by a control-plane session).
@@ -198,7 +212,7 @@ An invite is claimed at the recipient's first login. Mutations require an active
198
212
  audit: `run402 org audit — control-plane audit trail
199
213
 
200
214
  Usage:
201
- run402 org audit <org_id> [--limit N] [--after <cursor>] [--before <cursor>]
215
+ run402 org audit [<org_id>] [--limit N] [--after <cursor>] [--before <cursor>]
202
216
 
203
217
  Requires an admin+ membership on the org. Newest-first. Page forward with --after
204
218
  (next_cursor from a prior page); --before is the legacy cursor. Returns
@@ -208,6 +222,50 @@ Requires an admin+ membership on the org. Newest-first. Page forward with --afte
208
222
 
209
223
  // ── Top-level: create / list / get / rename / whoami / audit ────────────────────
210
224
 
225
+
226
+ /**
227
+ * gitvault-multi-writer (rev 47) task 6.3 / design D3 — decided 2026-09-03
228
+ * (Tal): `org member add` REFUSES a writer-eligible add (developer or
229
+ * above) when this session's own key is not an admitted writer on EVERY
230
+ * vault of the org, because a developer who cannot push is not the member
231
+ * the caller meant, and a half-usable membership is exactly the silent
232
+ * failure the multi-writer change exists to end. No warn path, no escape
233
+ * flag: the fix is for a current writer of the named vault(s) to admit this
234
+ * key (`run402 repos access sync`, or any push) and re-run — or to run
235
+ * the add themselves. Viewer/billing adds never reach this gate; an org
236
+ * with no vaults has nothing to admit; a vault in the read-only terminal
237
+ * state (its last writer gone) is skipped, since nobody can admit there.
238
+ * Every read failure REFUSES too (an unverifiable gate is not a passed one).
239
+ */
240
+ async function assertCallerCanAdmitWritersEverywhere(sdk, orgId, effectiveRole) {
241
+ const listing = await sdk.gitvault.listByOrg(orgId);
242
+ const vaults = listing?.vaults ?? [];
243
+ const blocked = [];
244
+ for (const v of vaults) {
245
+ const st = await sdk.gitvault.status({ repo_id: v.repo_id, reconcile: "forbidden" });
246
+ const vault = st?.vault ?? null;
247
+ if (!vault || vault.read_only_terminal) continue;
248
+ const me = st?.keystore?.identity_fingerprint ?? null;
249
+ const writers = vault.writer_set?.writers ?? [];
250
+ if (!me || !writers.some((w) => w.writer_key_id === me)) blocked.push({ repo_id: v.repo_id, repo_name: v.repo_name ?? null, project_id: v.project_id ?? null });
251
+ }
252
+ if (blocked.length > 0) {
253
+ fail({
254
+ code: "GITVAULT_WRITER_NOT_ADMITTED",
255
+ message: `refusing to add a ${effectiveRole}: this session's key is not an admitted writer on ${blocked.length} of this org's ${vaults.length} vault(s), so the new member could not push there`,
256
+ details: { role: effectiveRole, vaults: blocked, checked: vaults.length },
257
+ next_actions: [
258
+ {
259
+ type: "request_writer_sync",
260
+ why: "A current writer of the named vault(s) admits YOUR key by running `run402 repos access sync` there (any push does it too); then re-run this command — or have that writer run it.",
261
+ safe_to_auto_execute: false,
262
+ },
263
+ ],
264
+ });
265
+ }
266
+ return { checked: vaults.length };
267
+ }
268
+
211
269
  async function create(args) {
212
270
  const a = normalizeArgv(args);
213
271
  const valueFlags = ["--name"];
@@ -384,10 +442,9 @@ async function current(args) {
384
442
 
385
443
  async function get(args) {
386
444
  const a = normalizeArgv(args);
387
- assertKnownFlags(a, ["--help", "-h"]);
388
- const [org] = requirePositionalCount(a, [], {
389
- min: 1, max: 1, command: "run402 org get <org_id>", missing: "Missing <org_id>.",
390
- });
445
+ assertKnownFlags(a, ["--org", "--help", "-h"], ["--org"]);
446
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org"], { cmd: "org" });
447
+ requireRest(pos, { max: 0, command: "run402 org get [<org_id>]" });
391
448
  try {
392
449
  console.log(JSON.stringify(await getSdk().org(org).get(), null, 2));
393
450
  } catch (err) {
@@ -397,18 +454,18 @@ async function get(args) {
397
454
 
398
455
  async function rename(args) {
399
456
  const a = normalizeArgv(args);
400
- assertKnownFlags(a, ["--name", "--clear", "--help", "-h"], ["--name"]);
457
+ assertKnownFlags(a, ["--org", "--name", "--clear", "--help", "-h"], ["--org", "--name"]);
401
458
  const clear = a.includes("--clear");
402
459
  const nameFlag = flagValue(a, "--name");
403
460
  const single = clear || nameFlag !== null;
404
- const positionals = requirePositionalCount(a, ["--name"], {
405
- min: single ? 1 : 2,
406
- max: single ? 1 : 2,
407
- command: "run402 org rename <org_id> --name <display_name>",
408
- missing: single ? "Missing <org_id>." : "Missing <org_id> and/or <display_name> (use --name, or pass --clear).",
461
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--name"], { cmd: "org" });
462
+ requireRest(pos, {
463
+ min: single ? 0 : 1,
464
+ max: single ? 0 : 1,
465
+ command: "run402 org rename [<org_id>] --name <display_name>",
466
+ missing: "Missing <display_name> (use --name, or pass --clear).",
409
467
  });
410
- const org = positionals[0];
411
- const displayName = clear ? null : (nameFlag ?? positionals[1]);
468
+ const displayName = clear ? null : (nameFlag ?? pos[0]);
412
469
  try {
413
470
  console.log(JSON.stringify(await getSdk().org(org).rename(displayName), null, 2));
414
471
  } catch (err) {
@@ -418,18 +475,18 @@ async function rename(args) {
418
475
 
419
476
  async function payoutWallet(args) {
420
477
  const a = normalizeArgv(args);
421
- assertKnownFlags(a, ["--wallet", "--clear", "--help", "-h"], ["--wallet"]);
478
+ assertKnownFlags(a, ["--org", "--address", "--clear", "--help", "-h"], ["--org", "--address"]);
422
479
  const clear = a.includes("--clear");
423
- const walletFlag = flagValue(a, "--wallet");
480
+ const walletFlag = flagValue(a, "--address");
424
481
  const single = clear || walletFlag !== null;
425
- const positionals = requirePositionalCount(a, ["--wallet"], {
426
- min: single ? 1 : 2,
427
- max: single ? 1 : 2,
428
- command: "run402 org payout-wallet <org_id> --wallet <wallet_address>",
429
- missing: single ? "Missing <org_id>." : "Missing <org_id> and/or <wallet_address> (use --wallet, or pass --clear).",
482
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--address"], { cmd: "org" });
483
+ requireRest(pos, {
484
+ min: single ? 0 : 1,
485
+ max: single ? 0 : 1,
486
+ command: "run402 org payout-wallet [<org_id>] --address <wallet_address>",
487
+ missing: "Missing <wallet_address> (use --address, or pass --clear).",
430
488
  });
431
- const org = positionals[0];
432
- const walletAddress = clear ? null : (walletFlag ?? positionals[1]);
489
+ const walletAddress = clear ? null : (walletFlag ?? pos[0]);
433
490
  try {
434
491
  console.log(JSON.stringify(await getSdk().org(org).setPayoutWallet({ walletAddress }), null, 2));
435
492
  } catch (err) {
@@ -484,14 +541,10 @@ async function slug(args) {
484
541
 
485
542
  async function audit(args) {
486
543
  const a = normalizeArgv(args);
487
- const valueFlags = ["--limit", "--after", "--before"];
544
+ const valueFlags = ["--org", "--limit", "--after", "--before"];
488
545
  assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
489
- const [org] = requirePositionalCount(a, valueFlags, {
490
- min: 1,
491
- max: 1,
492
- command: "run402 org audit <org_id>",
493
- missing: "Missing <org_id>.",
494
- });
546
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, valueFlags, { cmd: "org" });
547
+ requireRest(pos, { max: 0, command: "run402 org audit [<org_id>]" });
495
548
  const limitFlag = flagValue(a, "--limit");
496
549
  const after = flagValue(a, "--after");
497
550
  const before = flagValue(a, "--before");
@@ -524,10 +577,9 @@ async function runMember(args) {
524
577
 
525
578
  if (memberAction === "list") {
526
579
  const a = normalizeArgv(rest);
527
- assertKnownFlags(a, ["--help", "-h"]);
528
- const [org] = requirePositionalCount(a, [], {
529
- min: 1, max: 1, command: "run402 org member list <org_id>", missing: "Missing <org_id>.",
530
- });
580
+ assertKnownFlags(a, ["--org", "--help", "-h"], ["--org"]);
581
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org"], { cmd: "org" });
582
+ requireRest(pos, { max: 0, command: "run402 org member list [<org_id>]" });
531
583
  try {
532
584
  console.log(JSON.stringify({ members: await getSdk().org(org).members.list() }, null, 2));
533
585
  } catch (err) {
@@ -538,19 +590,85 @@ async function runMember(args) {
538
590
 
539
591
  if (memberAction === "add") {
540
592
  const a = normalizeArgv(rest);
541
- assertKnownFlags(a, ["--role", "--wallet", "--help", "-h"], ["--role", "--wallet"]);
593
+ assertKnownFlags(a, ["--org", "--role", "--address", "--help", "-h"], ["--org", "--role", "--address"]);
542
594
  const role = flagValue(a, "--role");
543
- const walletFlag = flagValue(a, "--wallet");
544
- const count = walletFlag ? 1 : 2;
545
- const pos = requirePositionalCount(a, ["--role", "--wallet"], {
546
- min: count, max: count, command: "run402 org member add <org_id> --wallet <wallet_address> [--role <role>]",
547
- missing: walletFlag ? "Missing <org_id>." : "Missing <org_id> and/or <wallet_address> (use --wallet).",
595
+ const walletFlag = flagValue(a, "--address");
596
+ const count = walletFlag ? 0 : 1;
597
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--role", "--address"], { cmd: "org" });
598
+ requireRest(pos, {
599
+ min: count, max: count, command: "run402 org member add [<org_id>] <wallet_address> [--role <role>]",
600
+ missing: "Missing <wallet_address> (positional, or --address).",
548
601
  });
549
- const org = pos[0];
550
- const wallet = walletFlag ?? pos[1];
602
+ const wallet = walletFlag ?? pos[0];
551
603
  try {
552
- const res = await getSdk().org(org).members.add({ wallet, role: role || undefined });
553
- console.log(JSON.stringify(res, null, 2));
604
+ const sdk = getSdk();
605
+ // The gateway's own default role for a wallet add is developer — a
606
+ // writer-eligible role — so an add with no --role is gated too.
607
+ const effectiveRole = role || "developer";
608
+ if (["owner", "admin", "developer"].includes(effectiveRole)) {
609
+ await assertCallerCanAdmitWritersEverywhere(sdk, org, effectiveRole);
610
+ }
611
+ const res = await sdk.org(org).members.add({ wallet, role: role || undefined });
612
+ // gitvault-multi-writer (rev 47) task 6.3 — the gateway names which of
613
+ // this org's vaults now have a pending writer candidate (D3: there is
614
+ // no server-side writer admission — the client holds the keys) via a
615
+ // `sync_writers` next_action carrying `vault_ids[]`. Run the ACTUAL
616
+ // reconcile against each one now, so a single `org member add` finishes
617
+ // the whole job when the caller can. `reconcile()` is ALREADY a
618
+ // fast, network-free no-op per vault when this session's own key
619
+ // isn't a writer there (task 5.7's own `eligible` gate). For a
620
+ // writer-eligible add the gate above already proved this session IS
621
+ // a writer on every vault, so `not_eligible` here can only name a
622
+ // vault that changed under us in the meantime — reported, never
623
+ // silently dropped.
624
+ const syncTarget = res.next_actions?.find((n) => n?.type === "sync_writers");
625
+ let writerSync = null;
626
+ if (syncTarget?.vault_ids?.length > 0) {
627
+ const admitted = [];
628
+ const notEligible = [];
629
+ const errors = [];
630
+ for (const vaultId of syncTarget.vault_ids) {
631
+ try {
632
+ const r = await sdk.gitvault.reconcile({ repo_id: vaultId });
633
+ if (!r.eligible) notEligible.push(vaultId);
634
+ else if (r.admitted.length > 0) admitted.push({ repo_id: vaultId, admitted: r.admitted });
635
+ } catch (err) {
636
+ errors.push({ repo_id: vaultId, error: err?.message ?? String(err) });
637
+ }
638
+ }
639
+ writerSync = { attempted: syncTarget.vault_ids.length, admitted, not_eligible: notEligible, errors };
640
+ }
641
+ console.log(JSON.stringify(writerSync ? { ...res, writer_sync: writerSync } : res, null, 2));
642
+ if (writerSync?.not_eligible.length > 0) {
643
+ console.error(`writer sync: this session is not (yet) a writer on ${writerSync.not_eligible.length} vault(s) — the new member is a pending writer candidate there until a CURRENT writer's next gitvault operation admits them.`);
644
+ }
645
+ if (writerSync?.errors.length > 0) {
646
+ for (const e of writerSync.errors) console.error(`writer sync: ${e.repo_id} — ${e.error}`);
647
+ }
648
+ } catch (err) {
649
+ reportSdkError(err);
650
+ }
651
+ return;
652
+ }
653
+
654
+ if (memberAction === "revoke-key") {
655
+ // gitvault-agent-envelopes D3: owner + step-up revokes a member's current
656
+ // gitvault encryption key — the ONLY rotation path for a member that shares
657
+ // an org with another custodian (a lost/rebuilt keystore fails
658
+ // GITVAULT_KEY_ROTATION_REQUIRED and points here). Adapter only.
659
+ const a = normalizeArgv(rest);
660
+ assertKnownFlags(a, ["--org", "--principal", "--reason", "--help", "-h"], ["--org", "--principal", "--reason"]);
661
+ const principalFlag = flagValue(a, "--principal");
662
+ const reason = flagValue(a, "--reason");
663
+ const count = principalFlag ? 0 : 1;
664
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--principal", "--reason"], { cmd: "org" });
665
+ requireRest(pos, {
666
+ min: count, max: count, command: "run402 org member revoke-key [<org_id>] --principal <principal_id> [--reason <why>]",
667
+ missing: "Missing <principal_id> (--principal).",
668
+ });
669
+ const principalId = principalFlag ?? pos[0];
670
+ try {
671
+ console.log(JSON.stringify(await getSdk().org(org).members.revokeEncryptionKey(principalId, reason ? { reason } : {}), null, 2));
554
672
  } catch (err) {
555
673
  reportSdkError(err);
556
674
  }
@@ -559,17 +677,17 @@ async function runMember(args) {
559
677
 
560
678
  if (memberAction === "role") {
561
679
  const a = normalizeArgv(rest);
562
- assertKnownFlags(a, ["--principal", "--role", "--help", "-h"], ["--principal", "--role"]);
680
+ assertKnownFlags(a, ["--org", "--principal", "--role", "--help", "-h"], ["--org", "--principal", "--role"]);
563
681
  const principalFlag = flagValue(a, "--principal");
564
682
  const roleFlag = flagValue(a, "--role");
565
- const count = 3 - (principalFlag ? 1 : 0) - (roleFlag ? 1 : 0);
566
- const pos = requirePositionalCount(a, ["--principal", "--role"], {
567
- min: count, max: count, command: "run402 org member role <org_id> --principal <principal_id> --role <role>",
568
- missing: "Missing <org_id>, <principal_id> (--principal), and/or <role> (--role).",
683
+ const count = 2 - (principalFlag ? 1 : 0) - (roleFlag ? 1 : 0);
684
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--principal", "--role"], { cmd: "org" });
685
+ requireRest(pos, {
686
+ min: count, max: count, command: "run402 org member role [<org_id>] --principal <principal_id> --role <role>",
687
+ missing: "Missing <principal_id> (--principal) and/or <role> (--role).",
569
688
  });
570
- const org = pos[0];
571
- const principalId = principalFlag ?? pos[1];
572
- const role = roleFlag ?? (principalFlag ? pos[1] : pos[2]);
689
+ const principalId = principalFlag ?? pos[0];
690
+ const role = roleFlag ?? (principalFlag ? pos[0] : pos[1]);
573
691
  try {
574
692
  console.log(JSON.stringify(await getSdk().org(org).members.setRole(principalId, { role }), null, 2));
575
693
  } catch (err) {
@@ -580,15 +698,15 @@ async function runMember(args) {
580
698
 
581
699
  if (memberAction === "rm") {
582
700
  const a = normalizeArgv(rest);
583
- assertKnownFlags(a, ["--principal", "--help", "-h"], ["--principal"]);
701
+ assertKnownFlags(a, ["--org", "--principal", "--help", "-h"], ["--org", "--principal"]);
584
702
  const principalFlag = flagValue(a, "--principal");
585
- const count = principalFlag ? 1 : 2;
586
- const pos = requirePositionalCount(a, ["--principal"], {
587
- min: count, max: count, command: "run402 org member rm <org_id> --principal <principal_id>",
588
- missing: principalFlag ? "Missing <org_id>." : "Missing <org_id> and/or <principal_id> (use --principal).",
703
+ const count = principalFlag ? 0 : 1;
704
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--principal"], { cmd: "org" });
705
+ requireRest(pos, {
706
+ min: count, max: count, command: "run402 org member rm [<org_id>] --principal <principal_id>",
707
+ missing: "Missing <principal_id> (use --principal).",
589
708
  });
590
- const org = pos[0];
591
- const principalId = principalFlag ?? pos[1];
709
+ const principalId = principalFlag ?? pos[0];
592
710
  try {
593
711
  console.log(JSON.stringify(await getSdk().org(org).members.revoke(principalId), null, 2));
594
712
  } catch (err) {
@@ -616,10 +734,9 @@ async function runInvite(args) {
616
734
 
617
735
  if (inviteAction === "list") {
618
736
  const a = normalizeArgv(rest);
619
- assertKnownFlags(a, ["--help", "-h"]);
620
- const [org] = requirePositionalCount(a, [], {
621
- min: 1, max: 1, command: "run402 org invite list <org_id>", missing: "Missing <org_id>.",
622
- });
737
+ assertKnownFlags(a, ["--org", "--help", "-h"], ["--org"]);
738
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org"], { cmd: "org" });
739
+ requireRest(pos, { max: 0, command: "run402 org invite list [<org_id>]" });
623
740
  try {
624
741
  console.log(JSON.stringify({ invites: await getSdk().org(org).invites.list() }, null, 2));
625
742
  } catch (err) {
@@ -630,18 +747,18 @@ async function runInvite(args) {
630
747
 
631
748
  if (inviteAction === "create") {
632
749
  const a = normalizeArgv(rest);
633
- const valueFlags = ["--role", "--ttl-hours", "--email"];
750
+ const valueFlags = ["--org", "--role", "--ttl-hours", "--email"];
634
751
  assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
635
752
  const role = flagValue(a, "--role");
636
753
  const ttlFlag = flagValue(a, "--ttl-hours");
637
754
  const emailFlag = flagValue(a, "--email");
638
- const count = emailFlag ? 1 : 2;
639
- const pos = requirePositionalCount(a, valueFlags, {
640
- min: count, max: count, command: "run402 org invite create <org_id> --email <email> [--role <role>]",
641
- missing: emailFlag ? "Missing <org_id>." : "Missing <org_id> and/or <email> (use --email).",
755
+ const count = emailFlag ? 0 : 1;
756
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, valueFlags, { cmd: "org" });
757
+ requireRest(pos, {
758
+ min: count, max: count, command: "run402 org invite create [<org_id>] --email <email> [--role <role>]",
759
+ missing: "Missing <email> (use --email).",
642
760
  });
643
- const org = pos[0];
644
- const email = emailFlag ?? pos[1];
761
+ const email = emailFlag ?? pos[0];
645
762
  const inviteTtlHours = ttlFlag === null ? undefined : parseIntegerFlag("--ttl-hours", ttlFlag, { min: 1, max: 8760 });
646
763
  try {
647
764
  const res = await getSdk().org(org).invites.create({ email, role: role || "developer", inviteTtlHours });
@@ -654,15 +771,15 @@ async function runInvite(args) {
654
771
 
655
772
  if (inviteAction === "rm") {
656
773
  const a = normalizeArgv(rest);
657
- assertKnownFlags(a, ["--principal", "--help", "-h"], ["--principal"]);
774
+ assertKnownFlags(a, ["--org", "--principal", "--help", "-h"], ["--org", "--principal"]);
658
775
  const principalFlag = flagValue(a, "--principal");
659
- const count = principalFlag ? 1 : 2;
660
- const pos = requirePositionalCount(a, ["--principal"], {
661
- min: count, max: count, command: "run402 org invite rm <org_id> --principal <principal_id>",
662
- missing: principalFlag ? "Missing <org_id>." : "Missing <org_id> and/or <principal_id> (use --principal).",
776
+ const count = principalFlag ? 0 : 1;
777
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--principal"], { cmd: "org" });
778
+ requireRest(pos, {
779
+ min: count, max: count, command: "run402 org invite rm [<org_id>] --principal <principal_id>",
780
+ missing: "Missing <principal_id> (use --principal).",
663
781
  });
664
- const org = pos[0];
665
- const principalId = principalFlag ?? pos[1];
782
+ const principalId = principalFlag ?? pos[0];
666
783
  try {
667
784
  console.log(JSON.stringify(await getSdk().org(org).invites.revoke(principalId), null, 2));
668
785
  } catch (err) {
package/lib/projects.mjs CHANGED
@@ -319,7 +319,7 @@ async function provision(args) {
319
319
  for (let i = 0; i < args.length; i++) {
320
320
  if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
321
321
  // Use !== undefined so an empty-string value is captured (and rejected
322
- // below) rather than silently dropped by the falsy-check pattern (GH-176).
322
+ // below) rather than silently dropped by the falsy-check pattern.
323
323
  if (args[i] === "--name" && args[i + 1] !== undefined) opts.name = args[++i];
324
324
  // --org targets an EXISTING org (v1.82); caller needs developer+ on it.
325
325
  // Omitted = cold-start. Tier is org-governed, so --tier is irrelevant here
@@ -345,7 +345,7 @@ async function provision(args) {
345
345
  });
346
346
  }
347
347
  // Validate --name when provided. Omitted --name lets the server pick a
348
- // default. The same envelope should also be enforced server-side (GH-176).
348
+ // default. The same envelope should also be enforced server-side.
349
349
  if (opts.name !== undefined) {
350
350
  if (opts.name === "") {
351
351
  fail({