@toon-protocol/rig 5.3.0 → 5.4.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.
@@ -4,6 +4,7 @@ import {
4
4
  CI_SERVICE_REQUEST_KIND,
5
5
  CI_SERVICE_STOP_KIND,
6
6
  CI_STATUS_USAGE,
7
+ CI_TRUST_ORDER,
7
8
  CI_WORKFLOW_PROGRESS_KIND,
8
9
  CI_WORKFLOW_RESULT_KIND,
9
10
  GitRepoReader,
@@ -27,11 +28,13 @@ import {
27
28
  earliestUniqueCommit,
28
29
  emitCliError,
29
30
  encryptSecretUpdate,
31
+ evaluateCiGate,
30
32
  executePush,
31
33
  generateSecretsKey,
32
34
  getGitRemoteUrls,
33
35
  hexToNpub,
34
36
  initGitRepository,
37
+ isCiTrustLevel,
35
38
  isRelayUrl,
36
39
  isSafeRefname,
37
40
  listGitRemotes,
@@ -58,13 +61,13 @@ import {
58
61
  updateRef,
59
62
  writeGitObjects,
60
63
  writeToonConfig
61
- } from "./chunk-DGF7WCRW.js";
64
+ } from "./chunk-YBJNZS5X.js";
62
65
  import {
63
66
  PREFERRED_GATEWAY,
64
67
  mirrorGatewaysFor,
65
68
  readGateways,
66
69
  shaResolverFor
67
- } from "./chunk-UXRWMKXV.js";
70
+ } from "./chunk-4OAMSMXA.js";
68
71
  import {
69
72
  COMMENT_KIND,
70
73
  DEFAULT_KEYSTORE_PASSWORD,
@@ -93,7 +96,7 @@ import {
93
96
  resolveIdentity,
94
97
  revisionRootId,
95
98
  selectRevision
96
- } from "./chunk-OVZJ5P6E.js";
99
+ } from "./chunk-7LB352DM.js";
97
100
  import {
98
101
  ChannelMapStore,
99
102
  channelStatus,
@@ -307,6 +310,9 @@ function writeRigPointerRecord(env, record) {
307
310
  }
308
311
 
309
312
  // src/cli/push.ts
313
+ function foreignOwnerOf(owner, pubkey) {
314
+ return owner && owner.toLowerCase() !== pubkey.toLowerCase() ? owner : void 0;
315
+ }
310
316
  function withForcedStandalone(deps) {
311
317
  return { ...deps, env: { ...deps.env, [RIG_STANDALONE_ENV]: "1" } };
312
318
  }
@@ -322,7 +328,7 @@ function loadPaidSession(deps, relayUrl) {
322
328
  });
323
329
  }
324
330
  var defaultLoadStandalone = async (options) => {
325
- const mod = await import("./standalone-mode-E7TNMWGO.js");
331
+ const mod = await import("./standalone-mode-TWOZA5TL.js");
326
332
  return mod.createStandaloneContext(options);
327
333
  };
328
334
  function identityReport(ctx) {
@@ -567,7 +573,17 @@ async function runPush(args, deps) {
567
573
  io.err(singleRelayRefusal(resolved, "Nothing was uploaded or paid."));
568
574
  return 1;
569
575
  }
570
- const session = await loadPaidSession(deps, relaysUsed[0]);
576
+ let session = await loadPaidSession(deps, relaysUsed[0]);
577
+ const daemonForeignOwner = session.path === "daemon" ? foreignOwnerOf(toonConfig.owner, session.identity.pubkey) : void 0;
578
+ if (daemonForeignOwner) {
579
+ io.err(
580
+ `rig: toon.owner (${daemonForeignOwner.slice(0, 8)}\u2026) is another identity's repo \u2014 running standalone instead of the daemon to read the owner's repo state (the daemon reads only its own).`
581
+ );
582
+ session = await loadPaidSession(
583
+ withForcedStandalone(deps),
584
+ relaysUsed[0]
585
+ );
586
+ }
571
587
  const path = session.path;
572
588
  let identity;
573
589
  if (session.path === "standalone") {
@@ -576,7 +592,7 @@ async function runPush(args, deps) {
576
592
  } else {
577
593
  identity = session.identity;
578
594
  }
579
- const foreignOwner = toonConfig.owner && toonConfig.owner.toLowerCase() !== identity.pubkey.toLowerCase() ? toonConfig.owner : void 0;
595
+ const foreignOwner = foreignOwnerOf(toonConfig.owner, identity.pubkey);
580
596
  let maintainerRemote;
581
597
  if (session.path === "standalone" && foreignOwner) {
582
598
  const ownerState = await session.ctx.fetchRemote({
@@ -2520,7 +2536,7 @@ async function runCi(args, deps) {
2520
2536
  case "status":
2521
2537
  return runCiStatus(rest, deps);
2522
2538
  case "serve":
2523
- return (await import("./ci-serve-R7ZQ7GJQ.js")).runCiServe(rest, deps);
2539
+ return (await import("./ci-serve-OOMHKKGG.js")).runCiServe(rest, deps);
2524
2540
  case "--help":
2525
2541
  case "-h":
2526
2542
  case "help":
@@ -2796,7 +2812,7 @@ import {
2796
2812
  PATCH_KIND as PATCH_KIND3,
2797
2813
  REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND3,
2798
2814
  STATUS_APPLIED_KIND as STATUS_APPLIED_KIND3,
2799
- STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2,
2815
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND3,
2800
2816
  STATUS_DRAFT_KIND as STATUS_DRAFT_KIND2,
2801
2817
  STATUS_OPEN_KIND as STATUS_OPEN_KIND2
2802
2818
  } from "@toon-protocol/core/nip34";
@@ -2811,7 +2827,8 @@ import { promisify } from "util";
2811
2827
  import {
2812
2828
  PATCH_KIND as PATCH_KIND2,
2813
2829
  REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND2,
2814
- STATUS_APPLIED_KIND as STATUS_APPLIED_KIND2
2830
+ STATUS_APPLIED_KIND as STATUS_APPLIED_KIND2,
2831
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2
2815
2832
  } from "@toon-protocol/core/nip34";
2816
2833
 
2817
2834
  // src/cli/fetch.ts
@@ -3649,6 +3666,12 @@ reported as a partial success; retry with \`rig pr status <patch-id> applied\`.
3649
3666
  Options:
3650
3667
  --target <branch> branch to merge onto (default: the patch's branch-name
3651
3668
  tag, else the repo's default branch)
3669
+ --require-ci-trust <level>
3670
+ refuse the merge, before paying, unless the patch
3671
+ series' head commit has a concluded green CI run at or
3672
+ above this trust level (same gate as \`rig ci status\`)
3673
+ --closes <issue-id> after a successful merge, publish closed (kind:1632)
3674
+ for this issue; repeatable
3652
3675
  --yes skip the confirmation prompt
3653
3676
  --json machine-readable output (without --yes: estimate only)
3654
3677
  --relay <url> ad-hoc relay override (bypasses git remotes)
@@ -3666,6 +3689,8 @@ function parseMergeArgs(args) {
3666
3689
  remote: { type: "string" },
3667
3690
  "repo-id": { type: "string" },
3668
3691
  owner: { type: "string" },
3692
+ "require-ci-trust": { type: "string" },
3693
+ closes: { type: "string", multiple: true },
3669
3694
  standalone: { type: "boolean", default: false },
3670
3695
  "no-daemon": { type: "boolean", default: false },
3671
3696
  help: { type: "boolean", short: "h", default: false }
@@ -3687,8 +3712,23 @@ function parseMergeArgs(args) {
3687
3712
  patchId,
3688
3713
  yes: values.yes === true,
3689
3714
  json: values.json === true,
3690
- relay: values.relay ?? []
3715
+ relay: values.relay ?? [],
3716
+ closes: [...new Set(values.closes ?? [])]
3691
3717
  };
3718
+ for (const id of flags.closes) {
3719
+ if (!HEX64_RE3.test(id)) {
3720
+ throw new Error("--closes must be a 64-character lowercase hex issue id");
3721
+ }
3722
+ }
3723
+ const level = values["require-ci-trust"];
3724
+ if (level !== void 0) {
3725
+ if (!isCiTrustLevel(level)) {
3726
+ throw new Error(
3727
+ `--require-ci-trust must be one of ${CI_TRUST_ORDER.join(" | ")} (got ${JSON.stringify(level)})`
3728
+ );
3729
+ }
3730
+ flags.requireTrust = level;
3731
+ }
3692
3732
  if (values.target !== void 0) flags.target = values.target;
3693
3733
  if (values.remote !== void 0) flags.remote = values.remote;
3694
3734
  if (values["repo-id"] !== void 0) flags.repoId = values["repo-id"];
@@ -3735,6 +3775,49 @@ var OverlayReader = class extends GitRepoReader {
3735
3775
  return { ...base, refs };
3736
3776
  }
3737
3777
  };
3778
+ var MAX_SERIES_DEPTH = 256;
3779
+ function replyParentId(event) {
3780
+ const eTags = event.tags.filter((t) => t[0] === "e" && t[1] !== void 0);
3781
+ return (eTags.find((t) => t[3] === "reply") ?? eTags.find((t) => t[3] === "root") ?? eTags.at(-1))?.[1];
3782
+ }
3783
+ async function fetchSeriesFollowOns(relays, head, authorized, webSocketFactory) {
3784
+ const allowed = withTargetAuthor(authorized, head.pubkey);
3785
+ const found = /* @__PURE__ */ new Map();
3786
+ let frontier = [head.id];
3787
+ for (let depth = 1; frontier.length > 0 && depth <= MAX_SERIES_DEPTH; depth++) {
3788
+ const replies = await queryAll(
3789
+ relays,
3790
+ [{ kinds: [PATCH_KIND2], "#e": frontier }],
3791
+ webSocketFactory
3792
+ );
3793
+ frontier = [];
3794
+ for (const e of replies.values()) {
3795
+ if (e.kind !== PATCH_KIND2 || e.id === head.id || found.has(e.id) || isPatchRevision(e) || !allowed.has(e.pubkey.toLowerCase())) {
3796
+ continue;
3797
+ }
3798
+ found.set(e.id, e);
3799
+ frontier.push(e.id);
3800
+ }
3801
+ }
3802
+ const children = /* @__PURE__ */ new Map();
3803
+ for (const e of found.values()) {
3804
+ const parent = replyParentId(e);
3805
+ if (parent === void 0) continue;
3806
+ children.set(parent, [...children.get(parent) ?? [], e]);
3807
+ }
3808
+ const ordered = [];
3809
+ const visit = (id) => {
3810
+ const kids = (children.get(id) ?? []).sort(
3811
+ (a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : 1)
3812
+ );
3813
+ for (const kid of kids) {
3814
+ ordered.push(kid);
3815
+ visit(kid.id);
3816
+ }
3817
+ };
3818
+ visit(head.id);
3819
+ return ordered;
3820
+ }
3738
3821
  async function applyInIsolation(repoRoot, tip, patchText, committer) {
3739
3822
  const scratch = await mkdtemp2(join4(tmpdir(), "rig-merge-"));
3740
3823
  const worktree = join4(scratch, "wt");
@@ -3869,6 +3952,15 @@ async function runPrMerge(args, deps) {
3869
3952
  `only the owner or a declared maintainer can merge \u2014 ${identity.pubkey.slice(0, 8)}\u2026 is neither for ${aTag}. Readers would ignore the pushed state. Nothing was pushed or paid.`
3870
3953
  );
3871
3954
  }
3955
+ const followOns = await fetchSeriesFollowOns(
3956
+ relays,
3957
+ latest,
3958
+ authorized,
3959
+ webSocketFactory
3960
+ );
3961
+ const series = [latest, ...followOns];
3962
+ const seriesText = series.map((e) => e.content.endsWith("\n") ? e.content : `${e.content}
3963
+ `).join("");
3872
3964
  if (!remote.announced) {
3873
3965
  throw new PrMergeError(
3874
3966
  "target_not_found",
@@ -3909,12 +4001,29 @@ async function runPrMerge(args, deps) {
3909
4001
  const applied = await applyInIsolation(
3910
4002
  repoRoot,
3911
4003
  tip,
3912
- latest.content,
4004
+ seriesText,
3913
4005
  hasCommitter ? void 0 : {
3914
4006
  name: hexToNpub(identity.pubkey),
3915
4007
  email: `${hexToNpub(identity.pubkey)}@nostr`
3916
4008
  }
3917
4009
  );
4010
+ if (flags.requireTrust !== void 0) {
4011
+ const headCommit = (series.at(-1)?.tags.filter((t) => t[0] === "commit").at(-1)?.[1] ?? applied.newHead).toLowerCase();
4012
+ const gate = await evaluateCiGate({
4013
+ relays,
4014
+ owner,
4015
+ repoId,
4016
+ commit: headCommit,
4017
+ webSocketFactory,
4018
+ requireTrust: flags.requireTrust
4019
+ });
4020
+ if (!gate.ok) {
4021
+ throw new PrMergeError(
4022
+ "ci_gate_failed",
4023
+ `no qualifying green CI run (trust >= ${flags.requireTrust}) for ${headCommit.slice(0, 12)}: ${gate.reason ?? "not green"}. Nothing was pushed, published, or paid.`
4024
+ );
4025
+ }
4026
+ }
3918
4027
  const overlay = new OverlayReader(repoRoot, {
3919
4028
  refname,
3920
4029
  sha: applied.newHead
@@ -3929,12 +4038,17 @@ async function runPrMerge(args, deps) {
3929
4038
  force: false
3930
4039
  });
3931
4040
  const plan = serializePushPlan(pushPlan);
3932
- const total = BigInt(plan.estimate.totalFee) + feeRates.eventFee;
4041
+ const total = BigInt(plan.estimate.totalFee) + feeRates.eventFee * BigInt(1 + flags.closes.length);
3933
4042
  if (!flags.json) {
3934
4043
  for (const line of renderPlan(plan)) io.out(line);
3935
4044
  io.out(
3936
4045
  ` status kind:1631 applied (${applied.commits.length} commit${applied.commits.length === 1 ? "" : "s"}) ${feeRates.eventFee}`
3937
4046
  );
4047
+ if (flags.closes.length > 0) {
4048
+ io.out(
4049
+ ` closes kind:1632 closed (${flags.closes.length} issue${flags.closes.length === 1 ? "" : "s"}) ${feeRates.eventFee * BigInt(flags.closes.length)}`
4050
+ );
4051
+ }
3938
4052
  io.out(` total with status ${total}`);
3939
4053
  io.out(renderIdentityLine(identity));
3940
4054
  }
@@ -3999,6 +4113,25 @@ async function runPrMerge(args, deps) {
3999
4113
  } catch (err) {
4000
4114
  statusError = err instanceof Error ? err.message : String(err);
4001
4115
  }
4116
+ const closed = [];
4117
+ const closeErrors = [];
4118
+ for (const issueId of flags.closes) {
4119
+ try {
4120
+ await ctx.publisher.publishEvent(
4121
+ buildStatus(owner, repoId, issueId, STATUS_CLOSED_KIND2),
4122
+ relays
4123
+ );
4124
+ closed.push(issueId);
4125
+ } catch (err) {
4126
+ closeErrors.push({
4127
+ issueId,
4128
+ error: err instanceof Error ? err.message : String(err)
4129
+ });
4130
+ }
4131
+ }
4132
+ const closeRetryHints = closeErrors.map(
4133
+ (e) => `rig issue close ${e.issueId}`
4134
+ );
4002
4135
  const retryHint = `rig pr status ${root.id} applied`;
4003
4136
  if (flags.json) {
4004
4137
  io.emitJson({
@@ -4010,6 +4143,8 @@ async function runPrMerge(args, deps) {
4010
4143
  revisionId: latest.id,
4011
4144
  target: refname,
4012
4145
  mergedShas: applied.commits,
4146
+ closed,
4147
+ ...closeErrors.length > 0 ? { closeErrors } : {},
4013
4148
  result,
4014
4149
  ...statusEventId !== void 0 ? { statusEventId } : {},
4015
4150
  ...statusError !== void 0 ? {
@@ -4017,6 +4152,11 @@ async function runPrMerge(args, deps) {
4017
4152
  error: "status_publish_failed",
4018
4153
  detail: statusError,
4019
4154
  hint: `the push succeeded but the applied status was not published \u2014 retry with \`${retryHint}\``
4155
+ } : closeErrors.length > 0 ? {
4156
+ partial: true,
4157
+ error: "close_publish_failed",
4158
+ detail: closeErrors.map((e) => `${e.issueId}: ${e.error}`).join("; "),
4159
+ hint: `the push succeeded but some issues were not closed \u2014 retry with ${closeRetryHints.map((h) => `\`${h}\``).join(", ")}`
4020
4160
  } : {}
4021
4161
  });
4022
4162
  } else {
@@ -4027,14 +4167,24 @@ async function runPrMerge(args, deps) {
4027
4167
  );
4028
4168
  }
4029
4169
  }
4170
+ if (!flags.json && closed.length > 0) {
4171
+ io.out(`Closed ${closed.length} issue(s): ${closed.join(", ")}`);
4172
+ }
4030
4173
  if (statusError !== void 0) {
4031
4174
  io.err(
4032
4175
  `partial success: the push succeeded but publishing the applied status failed: ${statusError}`
4033
4176
  );
4034
4177
  io.err(`retry the status with: ${retryHint}`);
4035
- return 1;
4036
4178
  }
4037
- return 0;
4179
+ if (closeErrors.length > 0) {
4180
+ for (const e of closeErrors) {
4181
+ io.err(
4182
+ `partial success: the push succeeded but closing issue ${e.issueId} failed: ${e.error}`
4183
+ );
4184
+ }
4185
+ for (const h of closeRetryHints) io.err(`retry the close with: ${h}`);
4186
+ }
4187
+ return statusError !== void 0 || closeErrors.length > 0 ? 1 : 0;
4038
4188
  } catch (err) {
4039
4189
  return emitCliError(io, flags.json, "pr merge", err);
4040
4190
  } finally {
@@ -4819,7 +4969,7 @@ async function runPrCreate(rest, deps) {
4819
4969
  var STATUS_KIND_BY_VALUE = {
4820
4970
  open: STATUS_OPEN_KIND2,
4821
4971
  applied: STATUS_APPLIED_KIND3,
4822
- closed: STATUS_CLOSED_KIND2,
4972
+ closed: STATUS_CLOSED_KIND3,
4823
4973
  draft: STATUS_DRAFT_KIND2
4824
4974
  };
4825
4975
  function isStatusValue(value) {
@@ -5202,11 +5352,15 @@ Options:
5202
5352
  --json machine-readable envelope (per-chain results array)
5203
5353
  -h, --help show this help`;
5204
5354
  var SHARED_DEVNET_SUFFIX = ".devnet.toonprotocol.dev";
5355
+ var SHARED_DEVNET_HOSTS = /* @__PURE__ */ new Set([
5356
+ "toonprotocol.dev",
5357
+ "relay.toonprotocol.dev"
5358
+ ]);
5205
5359
  function devnetHost(url) {
5206
5360
  if (!url) return void 0;
5207
5361
  try {
5208
5362
  const { hostname } = new URL(url);
5209
- if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1)) {
5363
+ if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1) || SHARED_DEVNET_HOSTS.has(hostname)) {
5210
5364
  return url;
5211
5365
  }
5212
5366
  } catch {
@@ -5400,7 +5554,8 @@ async function runFund(args, deps) {
5400
5554
  }
5401
5555
  const timeoutEnv = env["TOON_CLIENT_FAUCET_TIMEOUT_MS"];
5402
5556
  const timeoutFor = () => {
5403
- if (timeoutEnv && Number.isFinite(Number(timeoutEnv))) return Number(timeoutEnv);
5557
+ if (timeoutEnv && Number.isFinite(Number(timeoutEnv)))
5558
+ return Number(timeoutEnv);
5404
5559
  if (file.faucetTimeoutMs !== void 0) return file.faucetTimeoutMs;
5405
5560
  return 9e4;
5406
5561
  };
@@ -5428,10 +5583,15 @@ async function runFund(args, deps) {
5428
5583
  }
5429
5584
  const started = Date.now();
5430
5585
  try {
5431
- const { response } = await client.fundWallet(faucetUrl, address, chain, {
5432
- timeout: timeoutFor(),
5433
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
5434
- });
5586
+ const { response } = await client.fundWallet(
5587
+ faucetUrl,
5588
+ address,
5589
+ chain,
5590
+ {
5591
+ timeout: timeoutFor(),
5592
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
5593
+ }
5594
+ );
5435
5595
  return {
5436
5596
  chain,
5437
5597
  funded: true,
@@ -5477,7 +5637,9 @@ async function runFund(args, deps) {
5477
5637
  }
5478
5638
  }
5479
5639
  io.out(renderIdentityLine(identity));
5480
- io.out("Re-check with `rig balance` (a drip can take a few blocks to land).");
5640
+ io.out(
5641
+ "Re-check with `rig balance` (a drip can take a few blocks to land)."
5642
+ );
5481
5643
  return allFunded ? 0 : 1;
5482
5644
  } catch (err) {
5483
5645
  return emitCliError(io, json, "fund", err);
@@ -5929,7 +6091,7 @@ import {
5929
6091
  PATCH_KIND as PATCH_KIND4,
5930
6092
  REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND4,
5931
6093
  STATUS_APPLIED_KIND as STATUS_APPLIED_KIND4,
5932
- STATUS_CLOSED_KIND as STATUS_CLOSED_KIND3,
6094
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND4,
5933
6095
  STATUS_DRAFT_KIND as STATUS_DRAFT_KIND3,
5934
6096
  STATUS_OPEN_KIND as STATUS_OPEN_KIND3
5935
6097
  } from "@toon-protocol/core/nip34";
@@ -5990,6 +6152,11 @@ function connectOnce(opts, onSubscribed) {
5990
6152
  } else if (msg[0] === "EOSE" && !subscribed && subIds.includes(msg[1])) {
5991
6153
  subscribed = true;
5992
6154
  onSubscribed();
6155
+ } else if (msg[0] === "CLOSED" && subIds.includes(msg[1])) {
6156
+ const why = typeof msg[2] === "string" && msg[2] ? msg[2] : "no reason";
6157
+ finish(`${opts.url} closed the subscription: ${why}`);
6158
+ } else if (msg[0] === "NOTICE") {
6159
+ opts.onError(`notice from ${opts.url}: ${String(msg[1])}`);
5993
6160
  }
5994
6161
  } catch {
5995
6162
  }
@@ -6047,7 +6214,7 @@ Options:
6047
6214
  var STATUS_KINDS2 = [
6048
6215
  STATUS_OPEN_KIND3,
6049
6216
  STATUS_APPLIED_KIND4,
6050
- STATUS_CLOSED_KIND3,
6217
+ STATUS_CLOSED_KIND4,
6051
6218
  STATUS_DRAFT_KIND3
6052
6219
  ];
6053
6220
  var CI_KINDS = [
@@ -9253,4 +9420,4 @@ export {
9253
9420
  rigVersion,
9254
9421
  dispatch
9255
9422
  };
9256
- //# sourceMappingURL=chunk-KQ4NQV7T.js.map
9423
+ //# sourceMappingURL=chunk-OJ372S6M.js.map