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>
150 lines
8.4 KiB
Markdown
150 lines
8.4 KiB
Markdown
# 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, 0x010–0x3FF), `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"
|
||
```
|