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>
8.4 KiB
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/2plug (reads session)GuildhallWeb.AuthController— login/callback/logout controllerGuildhallWeb.AuthHooks— LiveViewon_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
- User fills form at
/guilds/register→ creates guild row (status: pending_approval) + ceremony viaCeremonyClient.create_guild_registration_ceremony - 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)
- Guild master visits
/guilds/:slug/schematic→ loads TOML template for guild type - Click Deploy →
FfcPipeline.deploy/2runs 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 - Pipeline pauses at
awaiting_approval— returns{:paused, state}withschematic_hashfor signing - External signer POSTs Ed25519 signature to
POST /api/approvals(bearer token auth) ApprovalCoordinatorvalidates signer + callsFfcPipeline.resume_after_approval/2: approve_rpc → publish → realize → update_db → start_poller/guilds/:slug/realizationshows 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 ratifiedHostingAgreement. Guildhouse auto-ratifies for NSP tiers.
Founding override lifecycle
- NSP templates use
founding_overrideto 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
- User visits
/guilds/:slug/join→ creates membership (pending) + ceremony for guild master approval - 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 coordinatorGuildhall.Orchestrator.RealizationPoller— GenServer, polls realization status every 5s for watched IDs, broadcasts on"realization:#{guild_slug}"PubSub topicGuildhall.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", dispatchesresume_after_approvalwhen signature received
Schematic templates
TOML files in apps/guildhall_orchestrator/priv/schematic_templates/:
msp-founding.toml— attestation tier 2, MFA, single_approvalisv-founding.toml— attestation tier 1, no MFA, single_approvalnsp-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.Stubschematic/v1/schematics.pb.ex—Schematic.V1.SchematicsService.Stubschematic/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 5005392-schematic-server-deployment.yaml+93-*-service.yaml— ClusterIP on 909170-guildhall-deployment.yaml— includes OIDC + gRPC env vars- Secrets created imperatively (see
50-guildhall-secrets-template.yaml)
Commands
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:
git -c user.email=tking@guildhouse.dev commit -s -m "message"