# Governance Integration How plugins interact with GovernanceService, CeremonyService, and NotaryService. ## Services Overview | Service | Proto Package | Purpose | |---------|--------------|---------| | GovernanceService | `quartermaster.v1` | MutationIntent lifecycle (create, redeem, revoke) | | CeremonyService | `bascule.v1` | Multi-stakeholder approval workflows | | NotaryService | `quartermaster.v1` | Merkle tree anchoring for audit | All communication uses gRPC with mTLS via SPIFFE SVIDs. ## Intent Lifecycle Every credential operation follows the intent lifecycle: ``` CreateIntent ──> [authorized] ──> RedeemIntent ──> SAT ──> Operation ──> Anchor | v [ceremony_pending] ──> CeremonyService ──> [approved] ──> RedeemIntent | v [denied] ──> Abort ``` ### CreateIntent ```protobuf rpc CreateIntent(CreateIntentRequest) returns (CreateIntentResponse); ``` Fields: - `registry_type`: `"credential"` for all credential operations - `verb`: `"issue"`, `"rotate"`, or `"revoke"` - `artifact_scope`: JSON description of the credential parameters - `tenant_id`: Owning tenant UUID - `identity_claim`: OIDC token or external event claim - `ttl_seconds`: Intent lifetime (default 300) - `max_redemptions`: Always `1` for credential operations - `idempotency_key`: `"{registry_type}:{verb}:{credential_id}"` Response includes: - `intent_id`: Unique identifier for the intent - `ceremony_id`: Non-empty if a governance ceremony is required - `denied`: True if the Accord policy immediately rejects the operation ### RedeemIntent Called after the intent is authorized (immediately or after ceremony approval): ```protobuf rpc RedeemIntent(RedeemIntentRequest) returns (RedeemIntentResponse); ``` Returns a `SatToken` containing: - `sat_hash`: Cryptographic binding of the SAT contents - `bearer_svid`: SPIFFE ID authorized to perform the operation - `scopes`: `SatScopeMsg` entries defining permitted operations - `issued_at` / `expires_at`: SAT validity window ## Ceremony Flow When `CreateIntentResponse.ceremony_id` is non-empty: 1. Plugin monitors ceremony status via `CeremonyService.GetCeremony` 2. Approvers use `ApproveCeremony` or `DenyCeremony` (via Bascule shell or API) 3. On approval, GovernanceService transitions the intent to `authorized` 4. Plugin proceeds with `RedeemIntent` Ceremony types (from Accord policy): - **Autonomous**: No approval needed, intent authorized immediately - **SelfGrant**: Requestor self-approves - **SingleApproval**: One external approver required - **QuorumApproval**: Multiple approvers required (configurable quorum) - **EmergencyBreakGlass**: Proceed immediately, require post-hoc approval ## MutationEnvelope Construction After the credential operation succeeds, the plugin constructs a MutationEnvelope for audit anchoring: ### Step 1: Canonicalize Payload Serialize the credential event payload using RFC 8785 JSON Canonicalization Scheme (JCS): - Sorted keys - No whitespace - Deterministic number formatting ### Step 2: Domain-Separated Hash ``` payload_hash = SHA-256("guildhouse.credential.v1:" + jcs_bytes) ``` The domain prefix prevents cross-protocol hash collisions. ### Step 3: Build Envelope ```json { "domain": "guildhouse.credential.v1", "payload_hash": "", "timestamp": "", "actor_svid": "", "tenant_id": "", "event_type": "issue", "intent_id": "", "sat_hash": "" } ``` ### Step 4: Anchor JCS-canonicalize the envelope, SHA-256 hash it, submit as a leaf to `NotaryService.CreateAnchor`. ## Error Handling | Failure | Behavior | |---------|----------| | GovernanceService unreachable | **Fail closed** — credential operation fails | | Ceremony timeout | Treat as denial, abort operation | | NotaryService unreachable | Credential operation proceeds, queue leaf for retry | | No Accord policy match | Default to SingleApproval (fail safe) | ## Plugin Configuration All plugins share governance connection configuration: ```hcl plugin_data { governance_addr = "governance.quartermaster.svc.cluster.local:50051" ceremony_addr = "ceremony.bascule.svc.cluster.local:50052" notary_addr = "notary.quartermaster.svc.cluster.local:50051" trust_domain = "guildhouse.example.org" cluster_id = "guildhouse-prod" } ```