The Web3 Alliance member-application lifecycle service. Receives applications from partners applying to join the W3A federation, moves them through review and committee voting, and emits state-change events to sponsoring members.
This is the canonical onboarding backend referenced by
Web3_Alliance.tex §8 (Governance and Coordination) and the
member-application taxonomy in members/REGISTRY.md.
┌───────────────────────┐
│ Hanzo IAM (JWKS) │
└───────────┬───────────┘
│ RS256 reviewer tokens
▼
applicant ──HMAC─► ┌──────────────────────┐ ──CEK seal──► ┌────────────┐
│ onboardingd (ZIP) │ │ Hanzo KMS │
reviewer ──Bearer─► │ /v1/onboarding/* │ ◄──KEK unwrap──│ (KEK) │
└──────────┬───────────┘ └────────────┘
│
▼ append-only
┌──────────────────────┐
│ SQLite + WAL + │
│ Litestream replica │
└──────────────────────┘
- HTTP edge:
github.com/hanzoai/zip(Sinatra-style on Fiber v3 / fasthttp). Nonet/http, no chi / gin / echo, nonginx/caddy. - Storage: SQLite via
modernc.org/sqlitewith the canonical Hanzo Base pragma list (WAL,synchronous=NORMAL,busy_timeout=10000,foreign_keys=ON,temp_store=MEMORY,cache_size=-32000). Litestream is configured at the orchestration layer (replica URL via env), not implemented in-process. - Auth: Hanzo IAM JWT bearer; JWKS fetched once at boot, refreshed
on a fixed cadence. Two tiers:
applicant(HS256, service-issued at submit time) — read-only on the application's own status; can withdraw.reviewer(RS256, IAM-issued) — can review, vote, and (withgc_override) drive manual transitions and register webhooks.
- KMS: Application material is sealed under a random per-application AES-256-GCM CEK, which is in turn wrapped under the W3A KEK held by Hanzo KMS. Storage rows hold opaque ciphertext + wrapped CEK + AAD; decryption requires an auditable KMS unwrap call.
- Audit: Every state transition, review, vote, manual transition, withdrawal, KMS unwrap, and webhook registration writes one immutable row.
SUBMITTED → UNDER_REVIEW → REVIEW_COMPLETE → COMMITTEE_VOTING → APPROVED → ONBOARDING → ACTIVE
↓
REJECTED (terminal)
WITHDRAWN (applicant; terminal)
13 enumerated transitions; the table is the single source of truth
(pkg/statemachine/statemachine.go). Review quorum is 2 distinct
reviewers; vote quorum is 3 committee ballots.
All routes under /v1/onboarding. Authentication is Authorization: Bearer <jwt>.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/applications |
HMAC-signed (anon) | Submit a new application; returns id + applicant-tier JWT. |
GET |
/applications/:id |
applicant or reviewer | Read the application's current state. |
POST |
/applications/:id/withdraw |
applicant | Applicant-initiated terminal exit. |
GET |
/applications |
reviewer | List + filter (state, jurisdiction, contribution_category). |
POST |
/applications/:id/review |
reviewer | Submit one review. Two reviews → COMMITTEE_VOTING. |
POST |
/applications/:id/vote |
reviewer + committee | Cast committee ballot. Quorum → APPROVED/REJECTED. |
POST |
/applications/:id/transition |
reviewer + gc_override | Manual GC override transition. |
GET |
/applications/:id/audit-log |
applicant (own) or reviewer | Full audit history. |
POST |
/webhooks/state-change |
reviewer + gc_override | Register outbound webhook URL for a sponsor. |
GET |
/health |
none | K8s liveness. |
GET |
/ready |
none | K8s readiness (store + JWKS). |
| Name | Default | Description |
|---|---|---|
ONBOARDING_LISTEN |
:8080 |
HTTP listen address. |
ONBOARDING_DB_PATH |
./data/onboarding.db |
SQLite file path. |
ONBOARDING_IAM_ISSUER |
https://iam.w3a.foundation |
JWT iss claim. |
ONBOARDING_IAM_AUDIENCE |
w3a-onboarding |
JWT aud claim. |
ONBOARDING_IAM_JWKS_URL |
derived | JWKS endpoint. |
ONBOARDING_HMAC_SECRET |
required | base64 ≥32 bytes; applicant-token signing. |
ONBOARDING_SUBMIT_HMAC |
required | base64 ≥32 bytes; public submit HMAC. |
ONBOARDING_KMS_ENDPOINT |
_required_¹ | Hanzo KMS URL. |
ONBOARDING_KMS_IAM_URL |
_required_¹ | Hanzo IAM URL for KMS svc-auth. |
ONBOARDING_KMS_CLIENT_ID |
_required_¹ | KMS svc-account client_id. |
ONBOARDING_KMS_SECRET |
_required_¹ | KMS svc-account client_secret. |
ONBOARDING_KMS_ORG |
w3a |
KMS org slug. |
ONBOARDING_KEK_VERSION |
1 |
KEK rotation marker. |
ONBOARDING_LOCAL_KEK_B64 |
_unset_² | base64 32-byte KEK for local dev. |
ONBOARDING_SKIP_JWKS_FETCH |
_unset_² | 1 to disable JWKS refresh (local dev). |
¹ Required unless ONBOARDING_LOCAL_KEK_B64 is set.
² Local-dev only; never set in production.
docker compose up --build
curl -s http://localhost:8080/v1/onboarding/health | jq .go test ./... -racego build ./...
go vet ./...
docker build -t ghcr.io/w3a-foundation/onboarding:dev ..github/workflows/ci.yml runs go build, go vet, and go test -race on every push. On tag v* it cross-builds static linux/amd64
and linux/arm64 binaries (matrix), then builds the multi-arch image
on native runners (ubuntu-24.04 for amd64; ubuntu-24.04-arm for
arm64) and pushes ghcr.io/w3a-foundation/onboarding:VERSION +
:latest with a unified manifest. SLSA build-provenance attestations
are pushed to GHCR alongside the image.
Native arm64 runners are the canonical path — they're meaningfully
faster than QEMU emulation. If ubuntu-24.04-arm is unavailable on
your GitHub plan, swap that matrix entry back to ubuntu-24.04 and add
docker/setup-qemu-action@v3 before buildx; the build still produces
the same multi-arch manifest, just slower.
No CI secrets beyond the default GITHUB_TOKEN are required —
packages: write lets the GITHUB_TOKEN push to ghcr.io under the
repository's namespace automatically.
cmd/onboardingd/ # service entrypoint + Hanzo KMS HTTP adapter
pkg/types/ # domain types (Application, Review, Vote, State, …)
pkg/store/ # Store interface + BaseStore (SQLite) + MemoryStore
pkg/store/migrations/ # embedded *.sql schema
pkg/handlers/ # ZIP route handlers (applications, reviews, votes, webhooks, health)
pkg/auth/ # JWT verifier + applicant/reviewer token issuance
pkg/kms/ # narrow KMSGetter + envelope seal/open
pkg/audit/ # immutable append-only audit writer
pkg/statemachine/ # transition table + quorum constants
pkg/webhook/ # outbound delivery with HMAC + retry
- One way to do everything. No alternate transports, no alternate storage engines, no alternate HTTP frameworks.
- Every line owned by this repo or a pinned upstream — no vendored deltas, no forks.
- Hash and sign — never store plaintext credentials, never store plaintext application material at rest.
- Audit every action. The audit log is the system of record for who did what when.