Skip to content

Commit aac1461

Browse files
willemnealclaude
andcommitted
docs: registry IssueOps operator runbook
Document the bootstrap and day-to-day usage of the registry IssueOps pipeline: - Architecture summary with the issue → label → vote → env-gated submit flow. - One-time org setup: `registry-pilots` team, `READ_ORG_MEMBERS_PAT` fine-grained PAT, optional pre-creation of labels. - Per-network bootstrap: CI key generation, `set_manager(<CI_PUBKEY>)` by the registry admin, GitHub Environment creation with required reviewers, secret/var layout. - Submitter, pilot, and operator workflows. - Key rotation procedure. - Explicit out-of-scope list (set_admin, set_manager, remove_manager, registry-self upgrade — all remain offline-only because they require registry admin auth, not the manager / CI key). - Future migration to the Tansu factory pattern: the IssueOps shape stays put, only the signing step changes once a per-org contract becomes the registry's manager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2c8c6db commit aac1461

1 file changed

Lines changed: 178 additions & 0 deletions

File tree

docs/ci-publishing.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Registry IssueOps — operator runbook
2+
3+
This repo runs a GitHub IssueOps pipeline for the on-chain Stellar Registry. It mirrors the pattern used by [SCF Public Goods Maintenance](https://github.com/SCF-Public-Goods-Maintenance/scf-public-goods-maintenance.github.io): structured issue forms drive a label-based state machine, a pilot team votes via reactions, and an accepted intake fires a GitHub-environment-gated workflow that signs the on-chain transaction with a CI key.
4+
5+
## What lives where
6+
7+
| File | What it does |
8+
|---|---|
9+
| `.github/ISSUE_TEMPLATE/registry-{publish,register,deploy,admin}.yml` | Four issue forms — one per registry method family. The form sets the `registry-intake` parent label and a `registry-intake:<kind>` subtype. |
10+
| `.github/validator/config.yml` + `validate-*.js` | Field-level validation (Stellar strkeys, semver, wasm hashes, contract names, https URLs). |
11+
| `.github/registry-quorum.yml` | Per-kind quorum policy: pilot team slug, `min_voters`, `require_unanimous`. Edit via PR. |
12+
| `.github/workflows/validate-registry-intake.yml` | Runs on issue open/edit/reopen. Parses + validates the form; on success labels `:in-review`, on failure labels `issueops:validation-error` and posts a friendly comment. |
13+
| `.github/workflows/registry-quorum-check.yml` | Runs on `.quorum-check` comments. Tallies pilot 👍/👎, applies the policy, dispatches the on-chain submit workflow on accept (or closes the issue on reject). |
14+
| `.github/workflows/registry-onchain-submit.yml` | `workflow_dispatch` only. Re-fetches and re-validates the issue body, parses to CLI args, runs `stellar-registry-cli` from a protected GitHub Environment with the CI signer secret. |
15+
| `.github/scripts/parse_intake_to_args.py` | Single source of truth for the kind → CLI subcommand → flags mapping. Edit via PR. |
16+
| `.github/scripts/render_validation_failure.py` | Pretty-prints validator errors into an issue comment. |
17+
18+
## Architecture at a glance
19+
20+
```
21+
issue-form-submit ─▶ validate-registry-intake.yml ─▶ :in-review
22+
│ │
23+
│ └─ on fail ▶ issueops:validation-error + comment
24+
25+
pilots react 👍/👎
26+
.quorum-check comment ─▶ registry-quorum-check.yml
27+
│ │
28+
│ ┌──── reject path ────┐
29+
│ ▼ │
30+
│ :rejected + close+lock │
31+
│ │
32+
└────── accept path ─────────────────┘
33+
34+
35+
:accepted + dispatch
36+
37+
38+
registry-onchain-submit.yml (workflow_dispatch)
39+
40+
─────────┴───────── GitHub Environment gate (required reviewers)
41+
42+
43+
stellar-registry-cli <method> --source ci ...
44+
45+
46+
:submitted + tx hash comment ─OR─ :submission-failed
47+
```
48+
49+
The CI key is set as the registry contract's **manager** (see `contracts/registry/src/lib.rs:80-82`). When a manager is set, `publish`, `register_contract`, `deploy`, `update_*`, `rename_contract`, and `flag_contract` all accept manager auth in lieu of the original author/admin/owner. That is the contract-side hook that lets one CI key cover all of v1.
50+
51+
## Bootstrapping (one-time per network)
52+
53+
Do these in order. All steps that touch the registry contract are signed locally by the registry **admin**, not by CI.
54+
55+
### 1. Create the pilots team
56+
57+
In the GitHub org that owns this repo, create a team named `registry-pilots`. Add at least 3 members (mirroring `defaults.min_voters` in `.github/registry-quorum.yml`). Members of this team are the only accounts whose 👍/👎 reactions count toward quorum, and only members may run `.quorum-check`.
58+
59+
### 2. Provision the org-read PAT
60+
61+
The `registry-quorum-check.yml` workflow needs to enumerate `registry-pilots` members. The default `GITHUB_TOKEN` cannot read org teams, so create a fine-grained PAT:
62+
63+
- Resource owner: the org
64+
- Repository access: this repo only
65+
- Organization permissions: **Members → Read**
66+
67+
Store as repo secret `READ_ORG_MEMBERS_PAT`. Mirrors pg-atlas's setup.
68+
69+
### 3. Generate the CI key
70+
71+
Offline (developer workstation), per network:
72+
73+
```sh
74+
stellar keys generate ci-publisher # testnet auto-funds; for mainnet, fund out-of-band
75+
stellar keys public-key ci-publisher # → CI_PUBKEY
76+
stellar keys show ci-publisher # → CI_SECRET (handle with care)
77+
```
78+
79+
Treat the mainnet secret as you would any production credential — keep the offline copy in a password manager / hardware wallet only, and ensure no one can recover it from `STELLAR_SECRET_KEY` once it lands in GitHub.
80+
81+
### 4. Set the CI key as the registry manager
82+
83+
The registry **admin** (whoever holds the admin keypair from registry deployment) runs once per network:
84+
85+
```sh
86+
stellar contract invoke \
87+
--id "$STELLAR_REGISTRY_CONTRACT_ID" \
88+
--source admin \
89+
--network <testnet|mainnet> \
90+
-- set_manager --new_manager "$CI_PUBKEY"
91+
```
92+
93+
Verify:
94+
95+
```sh
96+
stellar contract invoke --id "$STELLAR_REGISTRY_CONTRACT_ID" --network <> -- manager
97+
# → "$CI_PUBKEY"
98+
```
99+
100+
### 5. Create GitHub Environments
101+
102+
In repo settings → Environments, create two:
103+
104+
- `registry-testnet`
105+
- `registry-mainnet`
106+
107+
For each, attach **required reviewers** (recommend the same set as `registry-pilots` for testnet; a stricter ops subset for mainnet). The env reviewer prompt is the second gate after the pilot quorum vote.
108+
109+
For each environment set:
110+
111+
| Kind | Name | Value |
112+
|---|---|---|
113+
| Secret | `STELLAR_SECRET_KEY` | The CI key's secret seed (`SC…`). |
114+
| Secret | `STELLAR_NETWORK_PASSPHRASE` | `Test SDF Network ; September 2015` (testnet) or `Public Global Stellar Network ; September 2015` (mainnet). |
115+
| Variable | `STELLAR_RPC_URL` | The RPC endpoint for that network. |
116+
| Variable | `STELLAR_REGISTRY_CONTRACT_ID` | The registry contract's `C…` address on that network. |
117+
118+
## Day-to-day usage
119+
120+
### Submitter workflow
121+
122+
1. Click **New issue** in this repo. Pick one of the four "Registry: …" forms.
123+
2. Fill in the fields. The form's `validations: required: true` blocks empty submission for must-have fields; the workflow re-validates kind-specific cross-field rules (e.g. `update_contract_owner` requires `new_owner`).
124+
3. On submit, the validator workflow runs. If it labels you `issueops:validation-error`, fix the fields and edit the issue — the workflow re-runs on edit.
125+
4. Once labeled `registry-intake:in-review`, wait for pilot votes.
126+
127+
### Pilot workflow
128+
129+
1. Read the issue, including the **Justification** section.
130+
2. React with 👍 to approve or 👎 to reject. (One reaction of each kind per pilot — the API enforces this.)
131+
3. When you believe the quorum threshold has been reached, comment exactly `.quorum-check`. Only `registry-pilots` members can trigger this; the action checks via the org PAT.
132+
4. The workflow tallies according to `.github/registry-quorum.yml`:
133+
- **Default formula:** `accepted iff ups ≥ (min_voters + 2 × downs)`. So 3 ups beats 0 downs; 5 ups beats 1 down.
134+
- **`require_unanimous: true`** (admin kind): any 👎 blocks acceptance regardless of 👍 count.
135+
5. On accept, the on-chain submit workflow is dispatched. It will pause at the env gate; an environment reviewer must click **Approve and deploy** before the CI key signs.
136+
6. On reject, the issue is closed and locked.
137+
138+
### Where to find the result
139+
140+
A successful submission posts a comment on the issue with the tx hash, links to the workflow run, and applies the `registry-intake:submitted` label. A failed submission applies `registry-intake:submission-failed` and links to the run logs; a maintainer can re-trigger via the workflow_dispatch UI after fixing the underlying problem.
141+
142+
## Key rotation
143+
144+
```
145+
Generate new CI key offline
146+
147+
admin: set_manager(<NEW_PUBKEY>)
148+
149+
Update each Environment's STELLAR_SECRET_KEY secret to the new SC…
150+
151+
(Optional, hardens the gap) admin: remove_manager + set_manager(<NEW_PUBKEY>)
152+
153+
Old key now has no on-chain authority; can be retired
154+
```
155+
156+
There is intentionally no "rotate from CI" path — rotation is an admin-only ceremony.
157+
158+
## Out of scope for v1 (offline-only, admin-signed)
159+
160+
The following operations require **registry admin** auth, not the manager / CI key. They are not exposed as IssueOps forms. To perform them, sign locally with the registry admin keypair.
161+
162+
| Operation | Why offline |
163+
|---|---|
164+
| `set_admin` | Changing who can change the manager. Highest blast radius. |
165+
| `set_manager` | Including initial bootstrap and key rotation. Admin-signed by design. |
166+
| `remove_manager` | Same. |
167+
| `upgrade` (registry self-upgrade) | Replaces the registry contract itself. Out of scope for IssueOps. |
168+
169+
## Future migration: Tansu factory pattern
170+
171+
When per-org contracts land:
172+
173+
1. Deploy the org contract `O` with multi-sig logic via Soroban `__check_auth`.
174+
2. Admin runs `set_manager(O)`.
175+
3. Update `registry-onchain-submit.yml`: instead of submitting a tx fully signed by the CI key, submit a tx whose auth entry references `O`. `O.__check_auth` reads quorum signatures from its own bag of signers.
176+
4. The CI key is retained as one of N quorum signers, or retired entirely if all signers are humans / hardware wallets.
177+
178+
The IssueOps pipeline (forms, validation, pilot vote, env gate) stays unchanged. It now governs **one** of N signatures rather than the sole authority. This is the explicit reason to invest in IssueOps now: the durable shape of human-mediated approvals doesn't change when the contract-side authority model becomes more sophisticated.

0 commit comments

Comments
 (0)