@toon-protocol/rig 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,13 +9,13 @@ lives in NIP-34 Nostr events and the objects live on Arweave.
9
9
  - **Writes are paid** — pushing objects and publishing events spends from a
10
10
  payment channel funded by your wallet. Writes are permanent and non-refundable.
11
11
  - **One node URL is the whole network configuration** — `rig` embeds its own
12
- payment client (`@toon-protocol/client` 2.x) built from your seed phrase and
12
+ payment client (`@toon-protocol/client` 3.x) built from your seed phrase and
13
13
  pays the TOON connector you name with `rig entry <url>`. The node describes
14
14
  itself on `GET /ilp`; there is nothing to discover (see
15
15
  [Pointing at a node](#pointing-at-a-node)).
16
16
 
17
17
  `rig` owns a handful of TOON verbs (`init`, `remote`, `clone`, `fetch`, `push`,
18
- `issue`, `pr`, `comment`, `identity`, `fund`, `balance`, `channel`). **Every other
18
+ `issue`, `pr`, `comment`, `identity`, `fund`, `balance`, `channel`, `ci`). **Every other
19
19
  command passes through to system `git` verbatim** — `rig status`, `rig add -p`,
20
20
  `rig commit`, `rig rebase -i` all behave exactly like git.
21
21
 
@@ -219,6 +219,11 @@ rig pr status <event-id> applied
219
219
  | `rig site url [ref]` | free | print the last-published site URL for a ref |
220
220
  | `rig name status <name>` | free | an ArNS name's registry record, ANT process, and current target txId |
221
221
  | `rig name buy <name>` / `rig name set <name> <txId>` | **paid¹** | buy an ArNS name / point it at an Arweave txId. On the devnet, buy/set default to brokering through the deployed store DVM (`--direct` opts out). ¹Paid in mARIO on Solana via the ar.io registry — **not** ILP; needs the optional `@ar.io/sdk` dep |
222
+ | `rig ci request <coordinator>` / `rig ci stop <coordinator>` | **paid** | authorize / revoke a CI coordinator for this repo (kind:9843 / 9844) |
223
+ | `rig ci trigger <coordinator> --workflow <path>` | **paid** | run one exact workflow (path + SHA-256) on one commit (kind:9840) |
224
+ | `rig ci secret set\|remove <coordinator> NAME[=value]` | **paid** | CI secrets, NIP-44 encrypted to the coordinator (kind:29846); values never echoed |
225
+ | `rig ci status <commit>` | free | every run + job for a commit with trust levels; `--json` is one document; exits non-zero unless all green |
226
+ | `rig ci serve --relay <url> --repo <owner>/<id>` | **paid** (its own wallet) | run a coordinator: watch the relay, run workflows with act on Docker, publish NIP-C1 results |
222
227
  | `rig channel list/open/close/settle` | free / **paid** | inspect or manage the payment channels paid commands hold (`rig channels` = `rig channel list`) |
223
228
  | `rig chain [set <c>\|unset]` | free | pin which chain/USDC settles paid writes: evm \| sol (default: the first chain the node settles on that your identity holds a key for) |
224
229
  | `rig entry [<connector-url>\|clear]` | free | name the TOON connector paid writes go to (`--relay <wss-url>` records its free-read relay too); bare `rig entry` shows what is in effect |
@@ -345,6 +350,197 @@ the human-readable snapshot (verified live 2026-07-17), useful when auditing
345
350
 
346
351
  ---
347
352
 
353
+ ## Continuous integration: `rig ci`
354
+
355
+ A repo published with `rig` gets CI without leaving TOON. The **relay is the
356
+ only control plane**: every command that changes a coordinator's behaviour is a
357
+ signed, paid relay event, every observation is a free relay read, and there is no
358
+ HTTP between a maintainer and a coordinator. The wire contract is
359
+ [NIP-C1 (Nostr CI)](../../docs/specs/nip-c1.md), adopted verbatim so ngit tooling
360
+ can read rig's events.
361
+
362
+ **Roles** (the words are `CONTEXT.md`'s):
363
+
364
+ - A **Coordinator** is an ordinary rig identity — its own BIP-39 phrase, wallet and
365
+ payment channel — that runs `rig ci serve`. It watches the repos it serves on the
366
+ relay, materializes each triggering commit from the relay + Arweave with the
367
+ free read path (no GitHub, no git server), runs the matching
368
+ GitHub-Actions-syntax **Workflows** from `.github/workflows/` (and
369
+ `.ngit/act/workflows/`) with [`act`](https://github.com/nektos/act) on Docker,
370
+ uploads logs and artifacts to the TOON store, and publishes NIP-C1 progress
371
+ (kind:39842), **Job** results (9841) and **Run** results (9842) as paid writes
372
+ from its own channel. Its budget is its wallet balance; the maintainer's phrase
373
+ is never on the coordinator host.
374
+ - A **maintainer** authorizes a coordinator with a **Service Request** (9843) and
375
+ revokes it with a Service Stop (9844). rig's coordinator is *request-required*:
376
+ it never runs a repo nobody asked it to, and it only honours Requests signed by
377
+ the repo owner or a declared maintainer (`rig maintainers`).
378
+ - **Triggers**: a push (a new kind:30618 whose refs moved), a pull request or PR
379
+ update (kinds 1617/1618/1619 — rig's own `rig pr create` patches included),
380
+ or a Manual Trigger (9840) from a maintainer. A pull request runs the
381
+ workflows with `on: pull_request`; its events carry NIP-22 tags back to the PR
382
+ (`E`/`K`/`P` root, `e`/`k`/`p` for the update that supplied the commit) and
383
+ no git ref. A PR update supersedes the previous tip: the earlier run for that
384
+ PR, queued or in progress, concludes `cancelled` before the new one starts.
385
+ `rig ci status` shows a PR run's PR event id and whether its author is the
386
+ repo owner or a declared maintainer (`pr.authorIsMaintainer` in `--json`).
387
+ - The **Runner** is behind one interface. The first implementation is act on the
388
+ host's Docker; a TOON-lease runner is a later class behind the same seam.
389
+
390
+ ### Maintainer side
391
+
392
+ ```sh
393
+ rig ci request <coordinator> # authorize <coordinator> (npub or hex) for this repo — paid (9843)
394
+ rig ci stop <coordinator> # revoke it — paid (9844)
395
+ rig ci trigger <coordinator> --workflow .github/workflows/ci.yml [--commit <sha>] [--ref refs/heads/main]
396
+ # re-run / run one exact workflow on one commit — paid (9840)
397
+ rig ci secret set <coordinator> DEPLOY_TOKEN=… # CI secrets, encrypted to the coordinator — paid (29846)
398
+ rig ci secret set <coordinator> NPM_TOKEN # bare NAME reads the value from stdin
399
+ rig ci secret remove <coordinator> OLD_TOKEN
400
+ rig ci status <sha> [--json] [--require-ci-trust maintainer-directed] [--workflow <path>] # free
401
+ ```
402
+
403
+ The paid verbs take the same flags as `rig issue`/`rig pr` (`--repo-id`,
404
+ `--owner`, `--remote`, `--relay`, `--yes`, `--json`): they quote the fee, ask for
405
+ confirmation (`--yes` skips; `--json` without `--yes` is a free estimate), and
406
+ publish exactly one event to the repo's `origin` relay. `rig ci trigger` reads
407
+ the workflow file at `<commit>` from the local repository and puts its path
408
+ **and SHA-256** in the event, so the coordinator can prove which file ran.
409
+
410
+ Secrets are NIP-44 v2 encrypted to the `secrets-key` the coordinator advertises
411
+ (kind:19843) with a **fresh sender key per update**: the relay sees who sent an
412
+ update and which repo/coordinator it addresses, never the names or values. The
413
+ coordinator injects secrets only into runs a maintainer caused (pushes, manual
414
+ triggers) and gives a third-party pull request an **empty** secret set, so a
415
+ contributor cannot exfiltrate them. Values are never echoed by rig, in any mode.
416
+
417
+ `rig ci status <commit>` is the merge gate: one line per run (conclusion,
418
+ workflow, trigger reason, coordinator, trust level, wall-clock time once
419
+ concluded), one indented line per job (conclusion, exit code, duration) and a
420
+ summary; with `--json` exactly one document (`ok`, `runs[]` with each run's and
421
+ job's event id, conclusion, timings — `queuedAt`/`startedAt`/`concludedAt` —
422
+ log and artifact URLs, `summary`). It exits **0** only when at least one run
423
+ counted and every counted run concluded green (`success`/`neutral`/`skipped`);
424
+ **1** when a run is red, still queued/in progress, or nothing was found.
425
+ `--require-ci-trust <level>` drops runs below that level before counting, so a
426
+ passing run from an unknown coordinator does not count as green.
427
+
428
+ **Trust levels** are derived client-side from Service Requests and the repo's
429
+ declared maintainers, strongest first:
430
+
431
+ | level | meaning |
432
+ | --- | --- |
433
+ | `maintainer-directed` | the run quotes a Service Request (or Manual Trigger) signed by the owner or a declared maintainer |
434
+ | `operationally-associated` | no quote on the run, but a maintainer's Request for that coordinator was in force when it ran |
435
+ | `seen-in-network` | the coordinator is itself a maintainer, or someone (not a maintainer) requested it for this repo |
436
+ | `no-known-context` | nothing links the coordinator to the repo — e.g. a contributor's own coordinator checking their branch |
437
+
438
+ ### Running a coordinator
439
+
440
+ ```sh
441
+ # on the coordinator host, with Docker available
442
+ export TOON_CLIENT_HOME=~/.rig-ci # keep the coordinator's identity + state apart from yours
443
+ rig identity create # a SEPARATE phrase — never the maintainer's
444
+ rig entry https://<connector> --relay wss://<relay>
445
+ rig fund # devnet; elsewhere fund the printed address — its balance IS the CI budget
446
+ rig ci serve --relay wss://<relay> --repo <owner-npub>/<repo-id> [--repo …]
447
+ ```
448
+
449
+ `rig ci serve` flags:
450
+
451
+ | flag | default | what it does |
452
+ | --- | --- | --- |
453
+ | `--relay <url>` | (required) | the ONE relay to subscribe to and publish on |
454
+ | `--repo <owner>/<repo-id>` | (required, repeatable) | repos to watch; each is served only while a maintainer's Service Request is in force |
455
+ | `--requester <pubkey>` | (none) | also accept Service Requests from this pubkey (repeatable) — a contributor's own coordinator for a repo they do not maintain; its runs show as lower trust |
456
+ | `--concurrency <n>` | `1` | bounded run slots — more pushes queue (`queue` rounds are published on the progress event) |
457
+ | `--requester <npub\|hex>` | (none) | also accept Service Requests from this pubkey (NIP-C1 operator policy; repeatable) — for running your own coordinator against a repo you do not maintain; such runs show as `seen-in-network` |
458
+ | `--timeout <secs>` | `1800` | wall clock per run; a hung run concludes `timed_out` |
459
+ | `--gateway <url>` | rig's preferred Arweave gateway | prefix for log/artifact URLs (`<gateway>/raw/<txId>`) |
460
+ | `--act-bin <path>` | `act` on PATH (or `RIG_ACT_BIN`) | the act binary |
461
+ | `--platform <label>=<image>` | `ubuntu-latest=catthehacker/ubuntu:act-latest` | `runs-on` label → Docker image (repeatable) |
462
+ | `--workdir <dir>` | `<state-dir>/work` | where commits are materialized |
463
+ | `--once` | | stop after the first run concludes (its 9842 and final 39842 are on the relay) — one `rig ci trigger` answered by one bounded serve; ignored or refused triggers do not count |
464
+ | `--json` | | one JSON document with the coordinator, relay, repos and state dir on start |
465
+
466
+ On start the coordinator publishes an **Advertisement** (19843: runner family
467
+ `act`, its selectors, admission `maintainer-request`, execution
468
+ `request-required`, billing `out-of-band`, and a fresh `secrets-key`) and renews
469
+ it before it expires. State — the per-repo cursor of the last processed event and
470
+ the decrypted secret inventory — lives under
471
+ `$TOON_CLIENT_HOME/rig-ci/<coordinator-pubkey>/`, so a restart resumes where it
472
+ left off and a relay blip loses no runs. The secrets key itself is never
473
+ persisted and rotates every start (NIP-C1), so re-send secrets after a restart.
474
+ When the wallet cannot pay for the writes a run needs, the coordinator says so on
475
+ stderr and starts no run rather than half-publishing one.
476
+
477
+ Each run publishes, in order: `39842 queued` → `39842 in_progress` (with the
478
+ frozen Service Request quote) → one `9841` per job (log tail in the content, full
479
+ log and artifacts as store URLs) → `9842` → `39842 concluded`. Every event carries
480
+ the repo address, the commit, the workflow path + SHA-256 (`w`), and the
481
+ normalized trigger reason (`o`), so a consumer can verify what ran.
482
+
483
+ **Departures from ngit's reference coordinator**, all deliberate: logs and
484
+ artifacts go to the **TOON store** and are referenced by gateway URL (TOON has no
485
+ Blossom); the Advertisement's billing policy is `out-of-band` (the operator funds
486
+ the coordinator); kind:1617 patches are accepted as pull-request triggers (the
487
+ coordinator applies the patch on its declared parent) because that is what
488
+ `rig pr create` publishes; and the coordinator's inbox relay is the repo relay
489
+ (`--relay`), so `rig ci secret` publishes to the same place as everything else.
490
+ Not in this slice: renting runners from a TOON provider, kinds 19844/39844,
491
+ schedule triggers, and encrypting logs.
492
+
493
+ ### Packaging
494
+
495
+ `packages/rig/Dockerfile` builds an image with rig, act and a Docker client
496
+ (`docker build -f packages/rig/Dockerfile -t rig-ci .` from the workspace root).
497
+ Run it with the host's Docker socket and a state volume mounted:
498
+
499
+ ```sh
500
+ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v rig-ci-state:/state \
501
+ -e RIG_MNEMONIC='<coordinator phrase>' -e TOON_CONNECTOR=https://<connector> \
502
+ rig-ci ci serve --relay wss://<relay> --repo <owner>/<repo-id>
503
+ ```
504
+
505
+ This is the shape a TOON workload will later spawn.
506
+
507
+ ### Dogfooding: rig's own `ci.yml` on the sandbox
508
+
509
+ The [TOON sandbox](https://github.com/toon-protocol/infra/tree/main/sandbox)
510
+ (full profile — the store and gateway are needed for uploads, so `make up`, not
511
+ `make up-payments`) is a complete network on one machine: relay
512
+ `ws://localhost:7100`, hub `http://localhost:3200`, store edge
513
+ `http://localhost:3210`, gateway `http://localhost:3000`. Two identities, both
514
+ settling on Solana:
515
+
516
+ ```sh
517
+ # common env for BOTH shells (each identity gets its own TOON_CLIENT_HOME)
518
+ export TOON_CONNECTOR=http://localhost:3200 TOON_CLIENT_RELAY_URL=ws://localhost:7100
519
+ export TOON_CLIENT_CHAIN=solana TOON_CLIENT_RPC_URL=http://localhost:8899
520
+ export TOON_CLIENT_STORE_SEAL_TO=http://localhost:3210 RIG_ARWEAVE_GATEWAY=http://localhost:3000
521
+
522
+ # maintainer shell
523
+ export TOON_CLIENT_HOME=/tmp/rig-maintainer; rig identity create; rig fund # prints the Solana address: airdrop SOL + mint mock USDC to it (sandbox keys/toon/usdc-authority.json)
524
+ cd rig && rig init && rig remote add origin ws://localhost:7100 && rig push --yes
525
+
526
+ # coordinator shell
527
+ export TOON_CLIENT_HOME=/tmp/rig-coordinator; rig identity create; rig fund # fund this one the same way
528
+ rig ci serve --relay ws://localhost:7100 --repo <maintainer-npub>/rig --gateway http://localhost:3000
529
+
530
+ # maintainer shell again
531
+ rig ci request <coordinator-npub> --yes
532
+ git commit --allow-empty -m 'ci: dogfood' && rig push --yes
533
+ rig ci status HEAD --json | jq .summary
534
+ ```
535
+
536
+ The same flow, end to end and asserted, is
537
+ `src/__integration__/ci-sandbox.integration.test.ts`; it runs only with
538
+ `RIG_CI_SANDBOX=1` and skips itself unless the sandbox's relay, hub, store edge
539
+ and gateway all answer. The act-on-Docker runner test (`src/ci/act-runner.test.ts`)
540
+ is likewise opt-in: set `RIG_ACT_BIN` to an act binary with Docker reachable.
541
+
542
+ ---
543
+
348
544
  ## Pointing at a node
349
545
 
350
546
  A TOON connector is a paid reverse proxy in front of an ordinary app (a relay, a
@@ -577,7 +773,7 @@ it — that stays behind the `Publisher` seam:
577
773
  split, and `JobDeliveryPort` is the injected seam for the per-increment
578
774
  encrypt/pay leg. The concrete port and the buyer-side payment helpers that
579
775
  rode the 0.x client's serve-side job API were removed with the move to
580
- `@toon-protocol/client` 2.x; the protocol pieces stay so a port can be
581
- written against the 2.x client.
776
+ `@toon-protocol/client` 2.x (now 3.x); the protocol pieces stay so a port can be
777
+ written against the current client.
582
778
 
583
779
  [epic #246](https://github.com/toon-protocol/toon-client/issues/246).