guildhall/CLAUDE.md
Tyler J King 38cf2b7c6b feat(orchestrator): governance correctness — override revocation + bootstrap ceremony
Wire founding override enforcement (TTL guard, periodic sweep, second-
master auto-revoke, manual revocation) and replace the approve stub with
a real Ed25519 signing flow through two bootstrap modes (self-sovereign
and partner-hosted with Guildhouse as default partner).

Pipeline now pauses at awaiting_approval, returns schematic_hash for the
signer, and resumes via POST /api/approvals webhook. HostingAgreement
table + HostingCeremony module support partner-hosted onboarding with
auto-ratification for Guildhouse-as-partner.

70 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Tyler J King <tking@guildhouse.dev>
2026-05-16 12:54:55 -04:00

150 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Guildhall — Claude Context
## What this is
Phoenix umbrella app — ceremony orchestrator + governance UI for the Guildhouse platform. Onboards guilds (MSP/ISV/NSP organizations), deploys founding schematics via gRPC, and manages membership with ceremony-engine approval flows.
## Architecture
```
guildhall/
├── apps/
│ ├── guildhall_ops_db — Ecto schemas + contexts (Postgres)
│ ├── guildhall_orchestrator — gRPC clients, GenServers, template engine
│ ├── guildhall_web — Phoenix LiveView UI + OIDC auth
│ ├── guildhall_chronicle — Chronicle event consumer (stub)
│ └── guildhall_graph_bridge — Microsoft Graph reconciler (stub)
├── config/ — dev.exs, runtime.exs (env-var driven)
└── k8s/ — Kubernetes manifests (00-93, numeric order)
```
## External services (gRPC)
| Service | Port | Proto | Client module |
|---------|------|-------|---------------|
| ceremony-service (Rust) | 50053 | `ceremony.v1.CeremonyService` | `Guildhall.Orchestrator.CeremonyClient` |
| ffc-schematic-server (Rust) | 9091 | `schematic.v1.SchematicsService` + `FfcSchematicService` | `Guildhall.Orchestrator.SchematicClient` |
Env vars: `CEREMONY_SERVICE_URL`, `SCHEMATIC_SERVICE_URL`, `FFC_SCHEMATIC_SERVICE_URL`. In-cluster DNS: `<name>.guildhall.svc.cluster.local:<port>`.
## Auth
Keycloak OIDC at `auth.guildhouse.dev/realms/guildhouse`, client `guildhall-web`. Env vars: `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_REDIRECT_URI`.
- `GuildhallWeb.Plugs.Auth``fetch_current_user/2` plug (reads session)
- `GuildhallWeb.AuthController` — login/callback/logout controller
- `GuildhallWeb.AuthHooks` — LiveView `on_mount(:require_auth)`
- User stored in session as map: `%{"did" => "did:web:...", "email" => ..., "name" => ..., "sub" => ..., "preferred_username" => ...}`
- DID format: `did:web:guildhouse.dev:user:<preferred_username>`
- Hub operator DID: `did:web:guildhouse.dev:user:tking`
## Database tables (guildhall_ops_db)
Pre-existing: `governed_artifacts`, `deployment_states`, `verification_results`
New guild tables:
- **guilds** — `guild_id` (10-bit, 0x0100x3FF), `slug`, `guild_type` (msp/isv/nsp), `status` (pending_approval/approved/denied/active/suspended), `registration_ceremony_id`, `trust_domain`, `registrant_did`, `contact_did`
- **guild_schematics** — FK to guilds, `template_name`, `schematic_name`, `schematic_version`, `realization_id`, `status` (pending/draft/validated/hosting_agreed/awaiting_approval/approved/published/realizing/realized/partially_realized/failed), `realization_snapshot` (map), `bootstrap_mode` (self_sovereign/partner_hosted), `founding_override_expires_at`, `founding_override_revoked_at`, `founding_override_revocation_reason`, `approval_requested_at`, `approved_at`, `approval_metadata` (map)
- **guild_memberships** — FK to guilds, `user_did`, `role` (apprentice/journeyman/master), `status` (pending/approved/denied/active/suspended/removed), `membership_ceremony_id`, unique on (guild_id, user_did)
- **hosting_agreements** — FK to guild_schematics + guilds (partner), `hosting_did`, `terms` (map), `partner_signature` (binary, Ed25519), `status` (proposed/ratified/active/handoff_pending/completed/expired), `auto_ratified`, `expires_at`
## Key flows
### Guild registration
1. User fills form at `/guilds/register` → creates guild row (status: pending_approval) + ceremony via `CeremonyClient.create_guild_registration_ceremony`
2. Hub operator (tking) sees pending guild at `/guilds/:slug`, clicks Approve → `CeremonyClient.approve_ceremony` + `Guilds.approve_guild/1` (Ecto.Multi: updates guild to "approved" + creates registrant as guild master)
### Schematic deployment (FfcPipeline)
1. Guild master visits `/guilds/:slug/schematic` → loads TOML template for guild type
2. Click Deploy → `FfcPipeline.deploy/2` runs 11 steps: load → validate → resolve → validate_resolved → encode → create_on_server → validate_on_server → create_db_record → fetch_schematic_hash → check_founding_override → check_hosting_agreement
3. Pipeline pauses at `awaiting_approval` — returns `{:paused, state}` with `schematic_hash` for signing
4. External signer POSTs Ed25519 signature to `POST /api/approvals` (bearer token auth)
5. `ApprovalCoordinator` validates signer + calls `FfcPipeline.resume_after_approval/2`: approve_rpc → publish → realize → update_db → start_poller
6. `/guilds/:slug/realization` shows 7 reconciler sections via PubSub live updates
### Bootstrap modes
- **self_sovereign** — Guild has own FFC. Founding master signs from their own GSH.
- **partner_hosted** — Partner guild (default: Guildhouse `guildhouse-ops`) hosts FFC during bootstrap. Requires ratified `HostingAgreement`. Guildhouse auto-ratifies for NSP tiers.
### Founding override lifecycle
- NSP templates use `founding_override` to reduce multi-party quorum to 1 during bootstrap
- 90-day TTL enforced by `FoundingOverrideGuard` (inline check) + `FoundingOverrideMonitor` (hourly sweep)
- Revoked automatically when second master onboards, or manually by founding master on GuildLive.Show
### Member onboarding
1. User visits `/guilds/:slug/join` → creates membership (pending) + ceremony for guild master approval
2. Guild master at `/guilds/:slug/members` → approve/deny via ceremony engine, manage roles
## Routes
All guild routes are under authenticated `live_session :authenticated`:
```
/ DashboardLive
/guilds GuildLive.Index
/guilds/register GuildLive.Register
/guilds/:slug GuildLive.Show
/guilds/:slug/schematic GuildLive.Schematic
/guilds/:slug/realization GuildLive.Realization
/guilds/:slug/join GuildLive.Join
/guilds/:slug/members GuildLive.Members
/ceremonies CeremonyLive.Index
/artifacts ArtifactLive.Index
```
Public: `/auth/login`, `/auth/callback`, `/auth/logout`, `/health`
API (bearer token `GUILDHALL_APPROVAL_WEBHOOK_SECRET`):
```
POST /api/approvals — receive Ed25519 signature for pending schematic
POST /api/hosting-agreements/:id/ratify — partner ratifies hosting agreement
```
## Orchestrator supervision tree
- `Guildhall.Orchestrator.CeremonyOrchestrator` — existing ceremony workflow coordinator
- `Guildhall.Orchestrator.RealizationPoller` — GenServer, polls realization status every 5s for watched IDs, broadcasts on `"realization:#{guild_slug}"` PubSub topic
- `Guildhall.Orchestrator.FoundingOverrideMonitor` — GenServer, hourly sweep of expired overrides, broadcasts `{:founding_override_revoked, ...}` on `"override:#{slug}"`
- `Guildhall.Orchestrator.ApprovalCoordinator` — GenServer, tracks pending approvals by `"name:version"`, dispatches `resume_after_approval` when signature received
## Schematic templates
TOML files in `apps/guildhall_orchestrator/priv/schematic_templates/`:
- `msp-founding.toml` — attestation tier 2, MFA, single_approval
- `isv-founding.toml` — attestation tier 1, no MFA, single_approval
- `nsp-founding.toml` — attestation tier 3, MFA + hardware, multi_party quorum 2
`SchematicTemplate.render_template/2` substitutes `{{guild_slug}}`, `{{guild_name}}`, `{{trust_domain}}`, `{{registrant_did}}`.
## Generated protobuf modules
In `apps/guildhall_orchestrator/lib/guildhall/orchestrator/proto/`:
- `ceremony/v1/ceremony.pb.ex``Ceremony.V1.CeremonyService.Stub`
- `schematic/v1/schematics.pb.ex``Schematic.V1.SchematicsService.Stub`
- `schematic/v1/ffc_schematic.pb.ex``Schematic.V1.FfcSchematicService.Stub`
These were generated with `protoc --elixir_out=plugins=grpc`. Re-generate if protos change.
## K8s manifests
Files in `k8s/` applied in numeric order. Key additions:
- `90-ceremony-service-deployment.yaml` + `91-*-service.yaml` — ClusterIP on 50053
- `92-schematic-server-deployment.yaml` + `93-*-service.yaml` — ClusterIP on 9091
- `70-guildhall-deployment.yaml` — includes OIDC + gRPC env vars
- Secrets created imperatively (see `50-guildhall-secrets-template.yaml`)
## Commands
```bash
mix deps.get # fetch deps
mix ecto.migrate # run migrations
mix compile # verify compilation
mix phx.server # start dev server (localhost:4000)
mix test # run tests
```
## Commit convention
Sign commits as `tking@guildhouse.dev`:
```bash
git -c user.email=tking@guildhouse.dev commit -s -m "message"
```