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.
- package/cli.mjs +2 -2
- package/core-dist/allowance-auth.js +1 -1
- package/core-dist/allowance.js +1 -1
- package/core-dist/config.js +4 -4
- package/core-dist/keystore.js +7 -7
- package/core-dist/profiles.js +1 -1
- package/core-dist/redact.js +6 -7
- package/git-remote-run402.mjs +2 -2
- package/gitvault-surface.json +2 -1
- package/lib/agent.mjs +1 -1
- package/lib/allowance.mjs +3 -4
- package/lib/argparse.mjs +7 -9
- package/lib/billing.mjs +33 -35
- package/lib/buzz-notifications.mjs +3 -2
- package/lib/buzz.mjs +14 -8
- package/lib/buzz.test.mjs +1 -1
- package/lib/command-manifest.mjs +38 -20
- package/lib/config.mjs +1 -1
- package/lib/deploy-v2.mjs +3 -3
- package/lib/doctor-source-scan.mjs +1 -1
- package/lib/doctor.mjs +42 -25
- package/lib/email.mjs +2 -3
- package/lib/harness-context.mjs +1 -2
- package/lib/init.mjs +1 -1
- package/lib/next-actions.mjs +1 -1
- package/lib/org-context.mjs +83 -2
- package/lib/org.mjs +231 -114
- package/lib/projects.mjs +2 -2
- package/lib/remote-helper-session.mjs +26 -32
- package/lib/repos.mjs +117 -15
- package/lib/rooms-context.mjs +5 -5
- package/lib/sdk-errors.mjs +1 -2
- package/lib/status.mjs +1 -1
- package/lib/update-check.test.mjs +1 -1
- package/lib/wallet-context.mjs +10 -10
- package/lib/wallet-context.test.mjs +6 -7
- package/lib/wallets.mjs +2 -2
- package/lib/webhooks.mjs +2 -2
- package/package.json +1 -1
- package/sdk/core-dist/allowance-auth.js +1 -1
- package/sdk/core-dist/allowance.js +1 -1
- package/sdk/core-dist/config.js +4 -4
- package/sdk/core-dist/keystore.js +7 -7
- package/sdk/core-dist/profiles.js +1 -1
- package/sdk/core-dist/redact.js +6 -7
- package/sdk/dist/delegate-credentials.d.ts +1 -1
- package/sdk/dist/delegate-credentials.js +1 -1
- package/sdk/dist/errors.d.ts +10 -4
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +2 -3
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/kernel.js +1 -1
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/allowance.d.ts +1 -1
- package/sdk/dist/namespaces/allowance.js +1 -1
- package/sdk/dist/namespaces/allowance.js.map +1 -1
- package/sdk/dist/namespaces/assets.js +1 -1
- package/sdk/dist/namespaces/assets.js.map +1 -1
- package/sdk/dist/namespaces/assets.types.d.ts +1 -1
- package/sdk/dist/namespaces/ci.d.ts.map +1 -1
- package/sdk/dist/namespaces/ci.js +4 -6
- package/sdk/dist/namespaces/ci.js.map +1 -1
- package/sdk/dist/namespaces/deploy.js +7 -8
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +86 -3
- package/sdk/dist/namespaces/gitvault.crypto.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.js +102 -3
- package/sdk/dist/namespaces/gitvault.crypto.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +197 -17
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +698 -58
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.types.d.ts +91 -3
- package/sdk/dist/namespaces/gitvault.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.types.js.map +1 -1
- package/sdk/dist/namespaces/operator.d.ts +1 -1
- package/sdk/dist/namespaces/operator.js +1 -1
- package/sdk/dist/namespaces/org.d.ts +14 -1
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +15 -0
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/org.types.d.ts +30 -0
- package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +1 -1
- package/sdk/dist/namespaces/subdomains.js +1 -1
- package/sdk/dist/namespaces/subdomains.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts +3 -4
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
- package/sdk/dist/namespaces/transfers.js +1 -2
- package/sdk/dist/namespaces/transfers.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +3 -4
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +2 -2
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/gitvault-address.js +2 -2
- package/sdk/dist/node/gitvault-address.js.map +1 -1
- package/sdk/dist/node/gitvault-creation-journal.d.ts +1 -1
- package/sdk/dist/node/gitvault-creation-journal.js +2 -2
- package/sdk/dist/node/gitvault-creation-journal.js.map +1 -1
- package/sdk/dist/node/gitvault-deploy.d.ts +2 -2
- package/sdk/dist/node/gitvault-deploy.js +2 -2
- package/sdk/dist/node/gitvault-handoff.d.ts +140 -1
- package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-handoff.js +226 -22
- package/sdk/dist/node/gitvault-handoff.js.map +1 -1
- package/sdk/dist/node/gitvault-keystore.d.ts +78 -3
- package/sdk/dist/node/gitvault-keystore.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-keystore.js +46 -5
- package/sdk/dist/node/gitvault-keystore.js.map +1 -1
- package/sdk/dist/node/gitvault-mirror.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-mirror.js +3 -5
- package/sdk/dist/node/gitvault-mirror.js.map +1 -1
- package/sdk/dist/node/gitvault-profile-scan.js +1 -1
- package/sdk/dist/node/gitvault-publication.d.ts +379 -21
- package/sdk/dist/node/gitvault-publication.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-publication.js +664 -41
- package/sdk/dist/node/gitvault-publication.js.map +1 -1
- package/sdk/dist/node/gitvault-recover.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-recover.js +7 -10
- package/sdk/dist/node/gitvault-recover.js.map +1 -1
- package/sdk/dist/node/gitvault-restore.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-restore.js +5 -2
- package/sdk/dist/node/gitvault-restore.js.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.d.ts +16 -2
- package/sdk/dist/node/gitvault-snapshot.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-snapshot.js +23 -2
- package/sdk/dist/node/gitvault-snapshot.js.map +1 -1
- package/sdk/dist/node/gitvault-writer-state.d.ts +201 -0
- package/sdk/dist/node/gitvault-writer-state.d.ts.map +1 -0
- package/sdk/dist/node/gitvault-writer-state.js +357 -0
- package/sdk/dist/node/gitvault-writer-state.js.map +1 -0
- package/sdk/dist/node/index.d.ts +5 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +21 -2
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
- package/sdk/dist/node/paid-fetch.js +3 -4
- package/sdk/dist/node/paid-fetch.js.map +1 -1
- package/sdk/dist/node/sites-node.js +1 -1
- package/sdk/dist/node/sites-node.js.map +1 -1
package/lib/deploy-v2.mjs
CHANGED
|
@@ -891,7 +891,7 @@ async function mergeAstroReleaseSlice(spec, dirArg) {
|
|
|
891
891
|
* (inline bytes). Only `{ path }` entries with no `data` resolve to a disk
|
|
892
892
|
* file; inline entries are skipped (nothing to scan on disk). Returns a
|
|
893
893
|
* de-duped list of absolute paths. Existence + extension filtering is the
|
|
894
|
-
* caller's job
|
|
894
|
+
* caller's job. Defensive: tolerates missing/odd shapes silently —
|
|
895
895
|
* the manifest normalizer is the authority on shape validity, not this
|
|
896
896
|
* best-effort extractor.
|
|
897
897
|
*/
|
|
@@ -1017,7 +1017,7 @@ async function applyCmd(args) {
|
|
|
1017
1017
|
await mergeAstroReleaseSlice(spec, opts.dir);
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
//
|
|
1020
|
+
// Reject empty specs client-side. Without this guard,
|
|
1021
1021
|
// `run402 deploy apply --spec '{}'` (and `--manifest <empty>`) would silently
|
|
1022
1022
|
// send an empty ReleaseSpec to /apply/v1/plans with no signal that nothing
|
|
1023
1023
|
// was deployed.
|
|
@@ -1117,7 +1117,7 @@ async function applyCmd(args) {
|
|
|
1117
1117
|
// the scanner has a false positive that the operator has confirmed
|
|
1118
1118
|
// is fine. Hits with severity `error` fail the deploy.
|
|
1119
1119
|
//
|
|
1120
|
-
// Scope
|
|
1120
|
+
// Scope: a `--dir` (Astro SSR build) deploy walks that dir —
|
|
1121
1121
|
// it IS the artifact. A manifest/spec/stdin deploy scans ONLY the
|
|
1122
1122
|
// on-disk source files the manifest actually references, resolved
|
|
1123
1123
|
// against the manifest's baseDir. We must NOT walk cwd/src for a
|
|
@@ -425,7 +425,7 @@ export function scanSourceTree(srcDir, opts = {}) {
|
|
|
425
425
|
/** Scan an explicit list of on-disk file paths — no directory walk.
|
|
426
426
|
* Used by `run402 deploy apply` for manifest/spec/stdin deploys, where
|
|
427
427
|
* the artifact is exactly the set of files the manifest references, NOT
|
|
428
|
-
* whatever happens to live under cwd/src
|
|
428
|
+
* whatever happens to live under cwd/src. Files without a
|
|
429
429
|
* scannable extension are ignored; unreadable files become a WARN
|
|
430
430
|
* finding (never throw). Returns the combined findings list, sorted by
|
|
431
431
|
* file + line for stable output, exactly like `scanSourceTree`. */
|
package/lib/doctor.mjs
CHANGED
|
@@ -27,12 +27,12 @@ import { queueBuzzDoctorTelemetry } from "./diagnostic-telemetry.mjs";
|
|
|
27
27
|
import { fail } from "./sdk-errors.mjs";
|
|
28
28
|
import { normalizeArgv, assertKnownFlags, flagValue } from "./argparse.mjs";
|
|
29
29
|
|
|
30
|
-
/** Value-taking flags
|
|
30
|
+
/** Value-taking flags — the flag set doctor actually parses; anything else is BAD_USAGE via assertKnownFlags, never silently ignored. */
|
|
31
31
|
const DOCTOR_VALUE_FLAGS = ["--scan-dir", "--buzz-agent", "--project", "--only"];
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* The stable, complete registry of ordinary-mode check names
|
|
35
|
-
*
|
|
34
|
+
* The stable, complete registry of ordinary-mode check names. One entry
|
|
35
|
+
* per `checks.push({ name: ... })` call below,
|
|
36
36
|
* in the order each check normally runs. This is the ONE place `--only`
|
|
37
37
|
* validates its argument against and the ONE place its help text is derived
|
|
38
38
|
* from, so a check can never be selectable-but-undocumented or
|
|
@@ -195,11 +195,10 @@ export async function run(sub, args = []) {
|
|
|
195
195
|
console.log(HELP);
|
|
196
196
|
return;
|
|
197
197
|
}
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// instead of quietly doing nothing.
|
|
198
|
+
// Accepting ANY flag silently means an unrecognized one (a typo) is
|
|
199
|
+
// never looked at. Any flag doctor actually parses is listed here;
|
|
200
|
+
// anything else is a structured BAD_USAGE/UNKNOWN_FLAG rejection instead
|
|
201
|
+
// of quietly doing nothing.
|
|
203
202
|
assertKnownFlags(all, ["--verbose", "--refresh", "--no-scan", "--buzz", ...DOCTOR_VALUE_FLAGS], DOCTOR_VALUE_FLAGS);
|
|
204
203
|
const verbose = all.includes("--verbose");
|
|
205
204
|
const refresh = all.includes("--refresh");
|
|
@@ -210,11 +209,10 @@ export async function run(sub, args = []) {
|
|
|
210
209
|
// wallet/machine-wide, not per-project.
|
|
211
210
|
const projectOverride = flagValue(all, "--project");
|
|
212
211
|
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// doctor's own --human bug.
|
|
212
|
+
// --only <check>, repeatable. Validated against the stable registry
|
|
213
|
+
// ABOVE the buzz early-return, so an unknown name is BAD_USAGE regardless
|
|
214
|
+
// of which mode was also requested — the same "every accepted flag must
|
|
215
|
+
// work or BAD_USAGE" bar as every other doctor flag.
|
|
218
216
|
const onlyChecks = collectRepeatableFlag(all, "--only");
|
|
219
217
|
for (const name of onlyChecks) {
|
|
220
218
|
if (!DOCTOR_CHECK_NAMES.includes(name)) {
|
|
@@ -227,9 +225,8 @@ export async function run(sub, args = []) {
|
|
|
227
225
|
}
|
|
228
226
|
}
|
|
229
227
|
// Buzz mode is a wholly separate, always-complete report shape — an --only
|
|
230
|
-
// that named ordinary-mode checks would be silently ignored under --buzz
|
|
231
|
-
//
|
|
232
|
-
// combination instead.
|
|
228
|
+
// that named ordinary-mode checks would be silently ignored under --buzz
|
|
229
|
+
// — an accepted-but-inert flag. Reject the combination instead.
|
|
233
230
|
if (onlyChecks.length > 0 && all.includes("--buzz")) {
|
|
234
231
|
fail({
|
|
235
232
|
code: "BAD_USAGE",
|
|
@@ -594,8 +591,7 @@ export async function run(sub, args = []) {
|
|
|
594
591
|
// deployed is a first-class shape (protocol D183), so its mere absence of a
|
|
595
592
|
// deploy raises nothing.
|
|
596
593
|
//
|
|
597
|
-
// TARGETING
|
|
598
|
-
// by kychee-com/run402#566's --project half): when cwd is a repository
|
|
594
|
+
// TARGETING: when cwd is a repository
|
|
599
595
|
// with its own pinned repo id or run402/origin remote, doctor checks THAT
|
|
600
596
|
// vault, not the profile's active project — the same pin > remote >
|
|
601
597
|
// RUN402_PROJECT_ID env > active-project order every other gitvault verb
|
|
@@ -683,8 +679,30 @@ export async function run(sub, args = []) {
|
|
|
683
679
|
// to the durability sentence instead of the terminal-loss claim.
|
|
684
680
|
covering_recipients: gv.covering_recipients ?? null,
|
|
685
681
|
daemon: daemonInfo,
|
|
682
|
+
// gitvault-multi-writer (rev 47) task 6.2 — this machine's own
|
|
683
|
+
// standing on the vault's chain-verified writer set. `null` only
|
|
684
|
+
// when there is no vault at all (nothing to be a writer OF).
|
|
685
|
+
// `read_only_vault` takes priority over the caller's own standing
|
|
686
|
+
// — the D228 terminal state blocks EVERY push regardless of who
|
|
687
|
+
// is asking.
|
|
688
|
+
writer: gv.vault === null
|
|
689
|
+
? null
|
|
690
|
+
: gv.vault.read_only_terminal
|
|
691
|
+
? "read_only_vault"
|
|
692
|
+
: gv.vault.writer_set?.writers.some((w) => w.writer_key_id === gv.keystore.identity_fingerprint)
|
|
693
|
+
? "active"
|
|
694
|
+
: gv.vault.pending_writers?.some((p) => p.writer_key_id === gv.keystore.identity_fingerprint)
|
|
695
|
+
? "pending"
|
|
696
|
+
: "not_admitted",
|
|
686
697
|
};
|
|
687
698
|
const gaps = [];
|
|
699
|
+
if (value.writer === "read_only_vault") {
|
|
700
|
+
gaps.push("this vault has lost its last writer (D228 read-only terminal) — it still serves reads, but no push can be admitted until a new writer is admitted through a recovery path");
|
|
701
|
+
} else if (value.writer === "pending") {
|
|
702
|
+
gaps.push("this machine's key is an eligible writer candidate but not yet admitted — run 'run402 repos access sync' if you already hold writer standing on this vault, or ask a current writer to run any gitvault operation");
|
|
703
|
+
} else if (value.writer === "not_admitted") {
|
|
704
|
+
gaps.push("this machine's key is not an active writer on this vault — a push from here is refused GITVAULT_WRITER_NOT_ADMITTED; ask a current writer to admit you (org membership at role developer+ and a published signing key make you eligible)");
|
|
705
|
+
}
|
|
688
706
|
// The one that actually breaks the next deploy: the project demands a
|
|
689
707
|
// vaulted capture and THIS machine cannot produce one.
|
|
690
708
|
if (gv.gitvault_policy === "required" && !gv.keystore.holds_repo_key) {
|
|
@@ -698,15 +716,14 @@ export async function run(sub, args = []) {
|
|
|
698
716
|
if (gv.pending_overrides > 0) {
|
|
699
717
|
gaps.push(`${gv.pending_overrides} unvaulted-override journal(s) are still open — run 'run402 repos snapshot' to drain them`);
|
|
700
718
|
}
|
|
701
|
-
// `matches` is a TRI-STATE
|
|
719
|
+
// `matches` is a TRI-STATE: `false` alone is
|
|
702
720
|
// a real mismatch. `null` (a slug-form remote not yet resolved on
|
|
703
|
-
// this machine) is not evidence of anything wrong —
|
|
704
|
-
//
|
|
721
|
+
// this machine) is not evidence of anything wrong — a bare
|
|
722
|
+
// `!gv.remote.matches` would treat null the same as false and warn.
|
|
705
723
|
if (gv.remote && gv.remote.matches === false) {
|
|
706
724
|
gaps.push(`the '${gv.remote.name}' git remote points at a different project than ${value.project_id} (${gv.remote.url})`);
|
|
707
725
|
}
|
|
708
|
-
//
|
|
709
|
-
// bug, pointing the other way — a `kygit::` remote with no
|
|
726
|
+
// A `kygit::` remote with no
|
|
710
727
|
// `git-remote-kygit` helper on PATH means every push/clone/fetch
|
|
711
728
|
// in this checkout fails inside git with an opaque error.
|
|
712
729
|
if (gv.remote?.url?.startsWith("kygit::")) {
|
|
@@ -786,8 +803,8 @@ export async function run(sub, args = []) {
|
|
|
786
803
|
// direct mutation of internal.sessions.authz_version. Hits with severity
|
|
787
804
|
// `error` block deploy (`run402 deploy` wraps doctor and respects exit
|
|
788
805
|
// code). Skipped via --no-scan when the user wants config-only checks, and
|
|
789
|
-
// by any --only that omits it
|
|
790
|
-
//
|
|
806
|
+
// by any --only that omits it — on a monorepo this check's findings can
|
|
807
|
+
// otherwise bury the gitvault diagnosis under thousands of hits.
|
|
791
808
|
if (!skipScan && wanted("source_scan")) {
|
|
792
809
|
try {
|
|
793
810
|
const scanRoot = scanDirOverride ?? resolveScanRoot(process.cwd());
|
package/lib/email.mjs
CHANGED
|
@@ -366,9 +366,8 @@ function mailboxIdFromSelector(envelope, selector, flag) {
|
|
|
366
366
|
if (/^mbx_/.test(selector)) return selector;
|
|
367
367
|
const hit = (envelope.mailboxes ?? []).find((m) => m.mailbox_id === selector || m.slug === selector);
|
|
368
368
|
if (!hit) {
|
|
369
|
-
// A selector that matches nothing is a value we know nothing about
|
|
370
|
-
// core-dist/redact.js's doc comment
|
|
371
|
-
// so it must not be echoed verbatim.
|
|
369
|
+
// A selector that matches nothing is a value we know nothing about (see
|
|
370
|
+
// core-dist/redact.js's doc comment), so it must not be echoed verbatim.
|
|
372
371
|
fail({
|
|
373
372
|
code: "MAILBOX_NOT_FOUND",
|
|
374
373
|
message: `No mailbox matching ${JSON.stringify(describeRejectedValue(selector))} for ${flag}.`,
|
package/lib/harness-context.mjs
CHANGED
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* long its TTL has silently decayed, so a session that can re-derive its OWN
|
|
12
12
|
* key independently of any local file needs no cache at all to pick up where
|
|
13
13
|
* it left off. Two genuinely concurrent sessions must NEVER derive the same
|
|
14
|
-
* key — that
|
|
15
|
-
* run402-private's agent-presence.ts, resolvePresence's own comment) — so
|
|
14
|
+
* key — that is the regression this design exists to avoid — so
|
|
16
15
|
* every source below is either a value the harness itself guarantees is
|
|
17
16
|
* unique per session, or a value generated here and persisted only for THIS
|
|
18
17
|
* checkout.
|
package/lib/init.mjs
CHANGED
|
@@ -404,7 +404,7 @@ export async function run(args = []) {
|
|
|
404
404
|
const data = await res.json();
|
|
405
405
|
if (data.result) {
|
|
406
406
|
// Tempo faucet is "instant" on-chain, but the client RPC read can be
|
|
407
|
-
// racy relative to faucet settlement — poll up to 30s
|
|
407
|
+
// racy relative to faucet settlement — poll up to 30s, mirroring
|
|
408
408
|
// the x402 path below.
|
|
409
409
|
for (let i = 0; i < 30; i++) {
|
|
410
410
|
await new Promise(r => setTimeout(r, 1000));
|
package/lib/next-actions.mjs
CHANGED
|
@@ -70,7 +70,7 @@ 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
|
|
73
|
+
* feature at all. Owner-only. Wording deliberately
|
|
74
74
|
* omits a price: the first slug per org is free, and renames/re-claims cost
|
|
75
75
|
* $1 — a gateway change lands the fee separately, so this stays true under
|
|
76
76
|
* both the old and new pricing rule.
|
package/lib/org-context.mjs
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* different organization than the caller named).
|
|
40
40
|
*/
|
|
41
41
|
import { getSdk } from "./sdk.mjs";
|
|
42
|
-
import { flagValue } from "./argparse.mjs";
|
|
42
|
+
import { flagValue, positionalArgs } from "./argparse.mjs";
|
|
43
43
|
import { findBindingKey } from "./wallet-context.mjs";
|
|
44
44
|
import { fail } from "./sdk-errors.mjs";
|
|
45
45
|
import { nextAction } from "./next-actions.mjs";
|
|
@@ -69,7 +69,7 @@ const trimmed = (v) => (typeof v === "string" && v.trim() ? v.trim() : null);
|
|
|
69
69
|
* NEVER checked here — only that the value could be an org id at all.
|
|
70
70
|
*
|
|
71
71
|
* A rejected value is a value we know nothing about — see
|
|
72
|
-
* core-dist/redact.js's doc comment
|
|
72
|
+
* core-dist/redact.js's doc comment: the
|
|
73
73
|
* same class of mistake that put a private key into RUN402_WALLET can put
|
|
74
74
|
* one into RUN402_ORG / --org, so this must never echo the raw value.
|
|
75
75
|
*/
|
|
@@ -290,6 +290,87 @@ export async function resolveOrgId(a, opts = {}) {
|
|
|
290
290
|
return resolved ? resolved.orgId : null;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/**
|
|
294
|
+
* The org-scoped POSITIONAL contract (cli-org-context, "One Resolver Serves
|
|
295
|
+
* Every Org-Scoped Command").
|
|
296
|
+
*
|
|
297
|
+
* `<org_id>` is optional sugar on every verb that acts on an organization —
|
|
298
|
+
* `org get`, `org member add`, `billing link-wallet`, … A leading positional
|
|
299
|
+
* that IS an org id (a UUID) addresses that org; anything else is the verb's
|
|
300
|
+
* own next positional (a wallet, an email, a principal) and the org comes from
|
|
301
|
+
* the shared chain: `--org`, then `RUN402_ORG`, then the `.run402.json`
|
|
302
|
+
* binding, then `org use`. So inside a bound checkout the two-agent case reads
|
|
303
|
+
* `run402 org member add 0xB… --role developer` with nothing else to know.
|
|
304
|
+
*
|
|
305
|
+
* Naming the org twice with different values (positional AND `--org`) is
|
|
306
|
+
* `AMBIGUOUS_ORG`, never a silent pick — the same refusal the env-vs-binding
|
|
307
|
+
* pair gets. A non-org first positional with no chain answer fails
|
|
308
|
+
* `ORG_REQUIRED` and NAMES the rejected value, so `org get foo` says why `foo`
|
|
309
|
+
* did not count rather than reporting a bare "no organization".
|
|
310
|
+
*
|
|
311
|
+
* Returns the org id with provenance plus the REMAINING positionals; bound
|
|
312
|
+
* those with {@link requireRest} using the verb's own usage line. Enforced
|
|
313
|
+
* mechanically: every `orgScoped` entry in the command manifest is driven
|
|
314
|
+
* through this shape by `cli-conventions-gate.test.mjs`.
|
|
315
|
+
*
|
|
316
|
+
* @param {string[]} a normalized argv
|
|
317
|
+
* @param {string[]} valueFlags flags that take a value (must include "--org")
|
|
318
|
+
* @param {object} [opts] forwarded to {@link resolveOrg} (cmd, env, cwd)
|
|
319
|
+
*/
|
|
320
|
+
export async function takeOrgPositional(a, valueFlags = [], opts = {}) {
|
|
321
|
+
const positionals = positionalArgs(a, valueFlags);
|
|
322
|
+
const first = positionals[0];
|
|
323
|
+
if (typeof first === "string" && ORG_ID_RE.test(first)) {
|
|
324
|
+
const flag = trimmed(flagValue(a, "--org"));
|
|
325
|
+
if (flag && flag.toLowerCase() !== first.toLowerCase()) {
|
|
326
|
+
fail({
|
|
327
|
+
code: "AMBIGUOUS_ORG",
|
|
328
|
+
message: `Ambiguous organization: positional ${first} but --org ${flag}.`,
|
|
329
|
+
hint: "Name the organization once — as the leading <org_id> positional or as --org <org_id>, not both.",
|
|
330
|
+
details: {
|
|
331
|
+
candidates: [
|
|
332
|
+
{ org_id: first, source: "positional", source_detail: "<org_id>" },
|
|
333
|
+
{ org_id: flag, source: "flag", source_detail: "--org" },
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return { orgId: first, rest: positionals.slice(1), source: "positional", sourceDetail: "<org_id>" };
|
|
339
|
+
}
|
|
340
|
+
const resolved = await resolveOrg(a, { ...opts, optional: true });
|
|
341
|
+
if (resolved) return { orgId: resolved.orgId, rest: positionals, source: resolved.source, sourceDetail: resolved.sourceDetail };
|
|
342
|
+
fail({
|
|
343
|
+
code: "ORG_REQUIRED",
|
|
344
|
+
message: typeof first === "string"
|
|
345
|
+
? `No organization specified and no current organization set (${JSON.stringify(first)} is not an org_id — an org_id is a UUID).`
|
|
346
|
+
: "No organization specified and no current organization set.",
|
|
347
|
+
hint: `Pass --org <org_id> (or a leading <org_id> positional), set ${ORG_ENV}, bind this directory in .run402.json, or run: run402 org use <org_id>`,
|
|
348
|
+
...(typeof first === "string" ? { details: { rejected_positional: first } } : {}),
|
|
349
|
+
next_actions: orgRequiredActions(),
|
|
350
|
+
});
|
|
351
|
+
return null; // unreachable — fail() exits
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Bound the positionals LEFT after {@link takeOrgPositional} took the org —
|
|
356
|
+
* the same `BAD_USAGE` shapes `requirePositionalCount` emits, on an array the
|
|
357
|
+
* caller already holds.
|
|
358
|
+
*/
|
|
359
|
+
export function requireRest(rest, opts = {}) {
|
|
360
|
+
const { min = 0, max = min, command = "command", missing = "Missing required argument." } = opts;
|
|
361
|
+
if (rest.length < min) {
|
|
362
|
+
fail({ code: "BAD_USAGE", message: missing, hint: command });
|
|
363
|
+
}
|
|
364
|
+
if (rest.length > max) {
|
|
365
|
+
fail({
|
|
366
|
+
code: "BAD_USAGE",
|
|
367
|
+
message: `Unexpected argument for ${command}: ${rest[max]}`,
|
|
368
|
+
hint: `Use \`${command}\`.`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return rest;
|
|
372
|
+
}
|
|
373
|
+
|
|
293
374
|
/** Validate an org id supplied by a human, naming the origin. Throws via fail(). */
|
|
294
375
|
export function requireOrgIdShape(orgId, origin = "--org") {
|
|
295
376
|
return assertOrgIdShape(orgId, origin);
|