@toon-protocol/rig 4.2.0 → 4.3.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
@@ -15,7 +15,7 @@ lives in NIP-34 Nostr events and the objects live on Arweave.
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,188 @@ 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.
381
+ - The **Runner** is behind one interface. The first implementation is act on the
382
+ host's Docker; a TOON-lease runner is a later class behind the same seam.
383
+
384
+ ### Maintainer side
385
+
386
+ ```sh
387
+ rig ci request <coordinator> # authorize <coordinator> (npub or hex) for this repo — paid (9843)
388
+ rig ci stop <coordinator> # revoke it — paid (9844)
389
+ rig ci trigger <coordinator> --workflow .github/workflows/ci.yml [--commit <sha>] [--ref refs/heads/main]
390
+ # re-run / run one exact workflow on one commit — paid (9840)
391
+ rig ci secret set <coordinator> DEPLOY_TOKEN=… # CI secrets, encrypted to the coordinator — paid (29846)
392
+ rig ci secret set <coordinator> NPM_TOKEN # bare NAME reads the value from stdin
393
+ rig ci secret remove <coordinator> OLD_TOKEN
394
+ rig ci status <sha> [--json] [--require-ci-trust maintainer-directed] [--workflow <path>] # free
395
+ ```
396
+
397
+ The paid verbs take the same flags as `rig issue`/`rig pr` (`--repo-id`,
398
+ `--owner`, `--remote`, `--relay`, `--yes`, `--json`): they quote the fee, ask for
399
+ confirmation (`--yes` skips; `--json` without `--yes` is a free estimate), and
400
+ publish exactly one event to the repo's `origin` relay. `rig ci trigger` reads
401
+ the workflow file at `<commit>` from the local repository and puts its path
402
+ **and SHA-256** in the event, so the coordinator can prove which file ran.
403
+
404
+ Secrets are NIP-44 v2 encrypted to the `secrets-key` the coordinator advertises
405
+ (kind:19843) with a **fresh sender key per update**: the relay sees who sent an
406
+ update and which repo/coordinator it addresses, never the names or values. The
407
+ coordinator injects secrets only into runs a maintainer caused (pushes, manual
408
+ triggers) and gives a third-party pull request an **empty** secret set, so a
409
+ contributor cannot exfiltrate them. Values are never echoed by rig, in any mode.
410
+
411
+ `rig ci status <commit>` is the merge gate: one line per run (conclusion,
412
+ workflow, trigger reason, coordinator, trust level) and a summary; with `--json`
413
+ exactly one document (`ok`, `runs[]` with per-job conclusions, log and artifact
414
+ URLs, `summary`). It exits **0** only when at least one run counted and every
415
+ counted run concluded green (`success`/`neutral`/`skipped`); **1** when a run is
416
+ red, still queued/in progress, or nothing was found. `--require-ci-trust <level>`
417
+ drops runs below that level before counting, so a passing run from an unknown
418
+ coordinator does not count as green.
419
+
420
+ **Trust levels** are derived client-side from Service Requests and the repo's
421
+ declared maintainers, strongest first:
422
+
423
+ | level | meaning |
424
+ | --- | --- |
425
+ | `maintainer-directed` | the run quotes a Service Request (or Manual Trigger) signed by the owner or a declared maintainer |
426
+ | `operationally-associated` | no quote on the run, but a maintainer's Request for that coordinator was in force when it ran |
427
+ | `seen-in-network` | the coordinator is itself a maintainer, or someone (not a maintainer) requested it for this repo |
428
+ | `no-known-context` | nothing links the coordinator to the repo — e.g. a contributor's own coordinator checking their branch |
429
+
430
+ ### Running a coordinator
431
+
432
+ ```sh
433
+ # on the coordinator host, with Docker available
434
+ export TOON_CLIENT_HOME=~/.rig-ci # keep the coordinator's identity + state apart from yours
435
+ rig identity create # a SEPARATE phrase — never the maintainer's
436
+ rig entry https://<connector> --relay wss://<relay>
437
+ rig fund # devnet; elsewhere fund the printed address — its balance IS the CI budget
438
+ rig ci serve --relay wss://<relay> --repo <owner-npub>/<repo-id> [--repo …]
439
+ ```
440
+
441
+ `rig ci serve` flags:
442
+
443
+ | flag | default | what it does |
444
+ | --- | --- | --- |
445
+ | `--relay <url>` | (required) | the ONE relay to subscribe to and publish on |
446
+ | `--repo <owner>/<repo-id>` | (required, repeatable) | repos to watch; each is served only while a maintainer's Service Request is in force |
447
+ | `--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 |
448
+ | `--concurrency <n>` | `1` | bounded run slots — more pushes queue (`queue` rounds are published on the progress event) |
449
+ | `--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` |
450
+ | `--timeout <secs>` | `1800` | wall clock per run; a hung run concludes `timed_out` |
451
+ | `--gateway <url>` | rig's preferred Arweave gateway | prefix for log/artifact URLs (`<gateway>/raw/<txId>`) |
452
+ | `--act-bin <path>` | `act` on PATH (or `RIG_ACT_BIN`) | the act binary |
453
+ | `--platform <label>=<image>` | `ubuntu-latest=catthehacker/ubuntu:act-latest` | `runs-on` label → Docker image (repeatable) |
454
+ | `--workdir <dir>` | `<state-dir>/work` | where commits are materialized |
455
+ | `--json` | | one JSON document with the coordinator, relay, repos and state dir on start |
456
+
457
+ On start the coordinator publishes an **Advertisement** (19843: runner family
458
+ `act`, its selectors, admission `maintainer-request`, execution
459
+ `request-required`, billing `out-of-band`, and a fresh `secrets-key`) and renews
460
+ it before it expires. State — the per-repo cursor of the last processed event and
461
+ the decrypted secret inventory — lives under
462
+ `$TOON_CLIENT_HOME/rig-ci/<coordinator-pubkey>/`, so a restart resumes where it
463
+ left off and a relay blip loses no runs. The secrets key itself is never
464
+ persisted and rotates every start (NIP-C1), so re-send secrets after a restart.
465
+ When the wallet cannot pay for the writes a run needs, the coordinator says so on
466
+ stderr and starts no run rather than half-publishing one.
467
+
468
+ Each run publishes, in order: `39842 queued` → `39842 in_progress` (with the
469
+ frozen Service Request quote) → one `9841` per job (log tail in the content, full
470
+ log and artifacts as store URLs) → `9842` → `39842 concluded`. Every event carries
471
+ the repo address, the commit, the workflow path + SHA-256 (`w`), and the
472
+ normalized trigger reason (`o`), so a consumer can verify what ran.
473
+
474
+ **Departures from ngit's reference coordinator**, all deliberate: logs and
475
+ artifacts go to the **TOON store** and are referenced by gateway URL (TOON has no
476
+ Blossom); the Advertisement's billing policy is `out-of-band` (the operator funds
477
+ the coordinator); kind:1617 patches are accepted as pull-request triggers (the
478
+ coordinator applies the patch on its declared parent) because that is what
479
+ `rig pr create` publishes; and the coordinator's inbox relay is the repo relay
480
+ (`--relay`), so `rig ci secret` publishes to the same place as everything else.
481
+ Not in this slice: renting runners from a TOON provider, kinds 19844/39844,
482
+ schedule triggers, and encrypting logs.
483
+
484
+ ### Packaging
485
+
486
+ `packages/rig/Dockerfile` builds an image with rig, act and a Docker client
487
+ (`docker build -f packages/rig/Dockerfile -t rig-ci .` from the workspace root).
488
+ Run it with the host's Docker socket and a state volume mounted:
489
+
490
+ ```sh
491
+ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v rig-ci-state:/state \
492
+ -e RIG_MNEMONIC='<coordinator phrase>' -e TOON_CONNECTOR=https://<connector> \
493
+ rig-ci ci serve --relay wss://<relay> --repo <owner>/<repo-id>
494
+ ```
495
+
496
+ This is the shape a TOON workload will later spawn.
497
+
498
+ ### Dogfooding: rig's own `ci.yml` on the sandbox
499
+
500
+ The [TOON sandbox](https://github.com/toon-protocol/infra/tree/main/sandbox)
501
+ (full profile — the store and gateway are needed for uploads, so `make up`, not
502
+ `make up-payments`) is a complete network on one machine: relay
503
+ `ws://localhost:7100`, hub `http://localhost:3200`, store edge
504
+ `http://localhost:3210`, gateway `http://localhost:3000`. Two identities, both
505
+ settling on Solana:
506
+
507
+ ```sh
508
+ # common env for BOTH shells (each identity gets its own TOON_CLIENT_HOME)
509
+ export TOON_CONNECTOR=http://localhost:3200 TOON_CLIENT_RELAY_URL=ws://localhost:7100
510
+ export TOON_CLIENT_CHAIN=solana TOON_CLIENT_RPC_URL=http://localhost:8899
511
+ export TOON_CLIENT_STORE_SEAL_TO=http://localhost:3210 RIG_ARWEAVE_GATEWAY=http://localhost:3000
512
+
513
+ # maintainer shell
514
+ 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)
515
+ cd rig && rig init && rig remote add origin ws://localhost:7100 && rig push --yes
516
+
517
+ # coordinator shell
518
+ export TOON_CLIENT_HOME=/tmp/rig-coordinator; rig identity create; rig fund # fund this one the same way
519
+ rig ci serve --relay ws://localhost:7100 --repo <maintainer-npub>/rig --gateway http://localhost:3000
520
+
521
+ # maintainer shell again
522
+ rig ci request <coordinator-npub> --yes
523
+ git commit --allow-empty -m 'ci: dogfood' && rig push --yes
524
+ rig ci status HEAD --json | jq .summary
525
+ ```
526
+
527
+ The same flow, end to end and asserted, is
528
+ `src/__integration__/ci-sandbox.integration.test.ts`; it runs only with
529
+ `RIG_CI_SANDBOX=1` and skips itself unless the sandbox's relay, hub, store edge
530
+ and gateway all answer. The act-on-Docker runner test (`src/ci/act-runner.test.ts`)
531
+ is likewise opt-in: set `RIG_ACT_BIN` to an act binary with Docker reachable.
532
+
533
+ ---
534
+
348
535
  ## Pointing at a node
349
536
 
350
537
  A TOON connector is a paid reverse proxy in front of an ordinary app (a relay, a