4.4 KiB
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
rpc CreateIntent(CreateIntentRequest) returns (CreateIntentResponse);
Fields:
registry_type:"credential"for all credential operationsverb:"issue","rotate", or"revoke"artifact_scope: JSON description of the credential parameterstenant_id: Owning tenant UUIDidentity_claim: OIDC token or external event claimttl_seconds: Intent lifetime (default 300)max_redemptions: Always1for credential operationsidempotency_key:"{registry_type}:{verb}:{credential_id}"
Response includes:
intent_id: Unique identifier for the intentceremony_id: Non-empty if a governance ceremony is requireddenied: True if the Accord policy immediately rejects the operation
RedeemIntent
Called after the intent is authorized (immediately or after ceremony approval):
rpc RedeemIntent(RedeemIntentRequest) returns (RedeemIntentResponse);
Returns a SatToken containing:
sat_hash: Cryptographic binding of the SAT contentsbearer_svid: SPIFFE ID authorized to perform the operationscopes:SatScopeMsgentries defining permitted operationsissued_at/expires_at: SAT validity window
Ceremony Flow
When CreateIntentResponse.ceremony_id is non-empty:
- Plugin monitors ceremony status via
CeremonyService.GetCeremony - Approvers use
ApproveCeremonyorDenyCeremony(via Bascule shell or API) - On approval, GovernanceService transitions the intent to
authorized - 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
{
"domain": "guildhouse.credential.v1",
"payload_hash": "<hex-sha256>",
"timestamp": "<rfc3339>",
"actor_svid": "<spiffe-id>",
"tenant_id": "<uuid>",
"event_type": "issue",
"intent_id": "<intent-id>",
"sat_hash": "<hex-sha256>"
}
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:
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"
}