guildhouse-spire-plugins/docs/governance-integration.md

139 lines
4.4 KiB
Markdown

# 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": "<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:
```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"
}
```