commit 2720a631b8936fac7fa834912b543ec91e3d1ad805aecad7a2185341c561a69e Author: Tyler King Date: Thu Feb 26 10:29:06 2026 -0500 Initial: Guildhouse protobuf definitions 14 proto files across 5 gRPC service domains: - quartermaster/v1 (6): governance, registry, notary, credentials, capabilities, pipelines - bascule/v1 (4): session, command, gateway, ceremony - workspace/v1 (1): workspace management - runner/v1 (2): pipeline execution - schematic/v1 (1): composite meta-artifacts Consumed as a git submodule by guildhouse-platform (Rust) and guildhouse-dashboard (Python). Co-Authored-By: Claude Opus 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a99dbd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Generated code (language-specific stubs live in consumer repos) +*_pb2.py +*_pb2_grpc.py +*_pb2.pyi +*.rs + +# Editor +.idea/ +.vscode/ +*.swp +*~ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9496406 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,27 @@ +# CLAUDE.md -- Guildhouse Proto + +## Project Identity + +Canonical protobuf definitions for all Guildhouse gRPC services. +Consumed by: +- **guildhouse-platform** (Rust/tonic-build) via git submodule +- **guildhouse-dashboard** (Python/grpcio-tools) via git submodule + +## Domains + +| Domain | Directory | Files | Purpose | +|--------|-----------|-------|---------| +| Quartermaster | `quartermaster/v1/` | 6 | Governance, registry, notary, credentials, capabilities, pipelines | +| Bascule | `bascule/v1/` | 4 | Session, command, gateway, ceremony | +| Workspace | `workspace/v1/` | 1 | Workspace management | +| Runner | `runner/v1/` | 2 | Pipeline execution | +| Schematic | `schematic/v1/` | 1 | Composite meta-artifacts | + +## Versioning + +Proto files use domain-versioned paths (e.g., `quartermaster/v1/`). Breaking changes require a new version directory (e.g., `quartermaster/v2/`). + +## Related Repos + +- `~/projects/guildhouse-platform/` -- Rust services that implement these APIs +- `~/projects/guildhouse-dashboard/` -- Django dashboard that calls these APIs diff --git a/bascule/v1/ceremony.proto b/bascule/v1/ceremony.proto new file mode 100644 index 0000000..5573e7d --- /dev/null +++ b/bascule/v1/ceremony.proto @@ -0,0 +1,156 @@ +syntax = "proto3"; +package bascule.v1; + +import "google/protobuf/timestamp.proto"; + +// Governance Ceremony Service — multi-stakeholder approval flows +// triggered by Accord policy when a mutation requires human sign-off. +service CeremonyService { + // Create a new governance ceremony. + rpc CreateCeremony (CreateCeremonyRequest) returns (CreateCeremonyResponse); + + // Record an approval or denial on a pending ceremony. + rpc ApproveCeremony (ApproveCeremonyRequest) returns (ApproveCeremonyResponse); + + // Deny a pending ceremony. + rpc DenyCeremony (DenyCeremonyRequest) returns (DenyCeremonyResponse); + + // Cancel a pending ceremony (requestor or admin). + rpc CancelCeremony (CancelCeremonyRequest) returns (CancelCeremonyResponse); + + // Get the current status of a ceremony. + rpc GetCeremony (GetCeremonyRequest) returns (GetCeremonyResponse); + + // List pending ceremonies, optionally filtered. + rpc ListPendingCeremonies (ListPendingCeremoniesRequest) returns (ListPendingCeremoniesResponse); + + // Get the resolution proof for a completed ceremony. + rpc GetCeremonyProof (GetCeremonyProofRequest) returns (GetCeremonyProofResponse); +} + +// --- Create --- + +message CreateCeremonyRequest { + string ceremony_type = 1; // "single_approval", "quorum_approval", etc. + CeremonySubjectMsg subject = 2; + uint32 required_approvals = 3; + repeated string approver_roles = 4; + uint32 ttl_hours = 5; // 0 = default (24h) + string intent_id = 6; // optional linked MutationIntent + string run_id = 7; // optional linked pipeline run + uint64 pr_number = 8; // optional linked PR + string remote_name = 9; // optional remote name +} + +message CreateCeremonyResponse { + string ceremony_id = 1; + string status = 2; // "pending" or "approved" (for self-grant) + google.protobuf.Timestamp expires_at = 3; + string error = 4; +} + +// --- Approve --- + +message ApproveCeremonyRequest { + string ceremony_id = 1; + string approver_identity = 2; + string approver_role = 3; + string comment = 4; +} + +message ApproveCeremonyResponse { + bool success = 1; + string status = 2; // updated status after approval + string error = 3; +} + +// --- Deny --- + +message DenyCeremonyRequest { + string ceremony_id = 1; + string approver_identity = 2; + string approver_role = 3; + string comment = 4; +} + +message DenyCeremonyResponse { + bool success = 1; + string status = 2; + string error = 3; +} + +// --- Cancel --- + +message CancelCeremonyRequest { + string ceremony_id = 1; +} + +message CancelCeremonyResponse { + bool success = 1; + string error = 2; +} + +// --- Get --- + +message GetCeremonyRequest { + string ceremony_id = 1; +} + +message GetCeremonyResponse { + string ceremony_id = 1; + string ceremony_type = 2; + CeremonySubjectMsg subject = 3; + string status = 4; + uint32 required_approvals = 5; + uint32 current_approvals = 6; + repeated CeremonyApprovalMsg approvals = 7; + google.protobuf.Timestamp created_at = 8; + google.protobuf.Timestamp expires_at = 9; + string intent_id = 10; + string run_id = 11; + uint64 pr_number = 12; + string remote_name = 13; + string error = 14; +} + +// --- List Pending --- + +message ListPendingCeremoniesRequest { + string intent_id = 1; // optional filter +} + +message ListPendingCeremoniesResponse { + repeated GetCeremonyResponse ceremonies = 1; +} + +// --- Proof --- + +message GetCeremonyProofRequest { + string ceremony_id = 1; +} + +message GetCeremonyProofResponse { + string ceremony_id = 1; + string status = 2; + string proof_hash = 3; + repeated CeremonyApprovalMsg approvals = 4; + google.protobuf.Timestamp resolved_at = 5; + string error = 6; +} + +// --- Shared messages --- + +message CeremonySubjectMsg { + string subject_type = 1; // "mutation_intent", "pipeline_merge", "schematic_publish", "custom" + string reference_id = 2; // intent_id, run_id, "name:version", or custom ref + string description = 3; // human-readable label + map metadata = 4; // extra fields +} + +message CeremonyApprovalMsg { + string approver_identity = 1; + string approver_role = 2; + string decision = 3; // "approve" or "deny" + string comment = 4; + google.protobuf.Timestamp decided_at = 5; +} diff --git a/bascule/v1/command.proto b/bascule/v1/command.proto new file mode 100644 index 0000000..c1a204b --- /dev/null +++ b/bascule/v1/command.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; +package bascule.v1; + +import "google/protobuf/struct.proto"; + +// --- Command execution --- + +message ExecuteCommandRequest { + string session_id = 1; + string verb = 2; + optional string namespace = 3; + optional string resource_type = 4; + optional string resource_name = 5; + google.protobuf.Struct parameters = 6; + string output_format = 7; +} + +message ExecuteCommandResponse { + bool allowed = 1; + string denied_reason = 2; + oneof result { + CommandResult success = 3; + CommandError error = 4; + } + AuditRef audit = 5; +} + +message CommandResult { + string output = 1; + uint32 resources_affected = 2; + bool session_expired_warning = 3; +} + +message CommandError { + string message = 1; + string code = 2; +} + +// --- Streaming --- + +message CommandStreamChunk { + oneof chunk { + string output_line = 1; + string error_line = 2; + bool eof = 3; + } +} + +// --- Audit reference --- + +message AuditRef { + string event_id = 1; + string classification = 2; + bool notarized = 3; +} + +// --- Command discovery --- + +message DiscoverCommandsRequest { + string session_id = 1; +} + +message DiscoverCommandsResponse { + repeated CommandDescriptor commands = 1; +} + +message CommandDescriptor { + string verb = 1; + string description = 2; + string classification = 3; + repeated ParameterDescriptor parameters = 4; + bool requires_namespace = 5; + bool requires_resource = 6; + bool streaming = 7; +} + +message ParameterDescriptor { + string name = 1; + string description = 2; + string param_type = 3; + bool required = 4; +} diff --git a/bascule/v1/gateway.proto b/bascule/v1/gateway.proto new file mode 100644 index 0000000..2a9ca60 --- /dev/null +++ b/bascule/v1/gateway.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package bascule.v1; + +import "bascule/v1/session.proto"; +import "bascule/v1/command.proto"; + +service BasculeGateway { + // Session lifecycle + rpc RequestSession (RequestSessionRequest) returns (RequestSessionResponse); + rpc GetSessionStatus (GetSessionStatusRequest) returns (GetSessionStatusResponse); + rpc EndSession (EndSessionRequest) returns (EndSessionResponse); + rpc GetCeremonyStatus (GetCeremonyStatusRequest) returns (GetCeremonyStatusResponse); + + // Command execution + rpc ExecuteCommand (ExecuteCommandRequest) returns (ExecuteCommandResponse); + rpc StreamCommand (ExecuteCommandRequest) returns (stream CommandStreamChunk); + + // Discovery + rpc DiscoverCommands (DiscoverCommandsRequest) returns (DiscoverCommandsResponse); +} diff --git a/bascule/v1/session.proto b/bascule/v1/session.proto new file mode 100644 index 0000000..a49f98b --- /dev/null +++ b/bascule/v1/session.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; +package bascule.v1; + +import "google/protobuf/timestamp.proto"; + +// --- Session request / response --- + +message RequestSessionRequest { + string ceremony_type = 1; + SessionScope requested_scope = 2; + repeated EvidenceItem evidence = 3; +} + +message RequestSessionResponse { + oneof result { + SessionGranted granted = 1; + CeremonyPending pending = 2; + CeremonyDenied denied = 3; + } +} + +message SessionGranted { + string session_id = 1; + SessionScope granted_scope = 2; + google.protobuf.Timestamp expires_at = 3; + string ceremony_id = 4; +} + +message CeremonyPending { + string ceremony_id = 1; + string message = 2; + google.protobuf.Timestamp timeout_at = 3; +} + +message CeremonyDenied { + string reason = 1; +} + +// --- Scope model --- + +message SessionScope { + repeated NamespaceScope namespaces = 1; + GlobalScope global = 2; + repeated string pathways = 3; + optional uint32 mutation_budget = 4; + bool can_delegate = 5; +} + +message NamespaceScope { + string namespace = 1; + repeated ScopeRule rules = 2; + repeated string workload_profiles = 3; + repeated string denied_capabilities = 4; +} + +message ScopeRule { + repeated string api_groups = 1; + repeated string resources = 2; + repeated string verbs = 3; +} + +message GlobalScope { + bool can_view_audit_trail = 1; + bool can_view_profiles = 2; + bool can_view_topology = 3; +} + +message EvidenceItem { + string evidence_type = 1; + string reference = 2; +} + +// --- Session status --- + +message GetSessionStatusRequest { + string session_id = 1; +} + +message GetSessionStatusResponse { + string session_id = 1; + string state = 2; + SessionScope scope = 3; + google.protobuf.Timestamp expires_at = 4; + uint32 mutations_used = 5; + optional uint32 mutation_budget = 6; +} + +// --- Session end --- + +message EndSessionRequest { + string session_id = 1; +} + +message EndSessionResponse { + bool success = 1; + uint32 total_commands = 2; + uint32 total_mutations = 3; +} + +// --- Ceremony status --- + +message GetCeremonyStatusRequest { + string ceremony_id = 1; +} + +message GetCeremonyStatusResponse { + string ceremony_id = 1; + string status = 2; + optional SessionGranted session = 3; +} diff --git a/quartermaster/v1/capabilities.proto b/quartermaster/v1/capabilities.proto new file mode 100644 index 0000000..8ed2241 --- /dev/null +++ b/quartermaster/v1/capabilities.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; +package quartermaster.v1; + +import "google/protobuf/timestamp.proto"; + +service QuartermasterCapabilities { + rpc SubmitProfile (SubmitProfileRequest) returns (SubmitProfileResponse); + rpc GetProfile (GetProfileRequest) returns (GetProfileResponse); + rpc ListProfiles (ListProfilesRequest) returns (ListProfilesResponse); + rpc VerifyProfile (VerifyProfileRequest) returns (VerifyProfileResponse); + rpc FlushAnchor (FlushAnchorRequest) returns (FlushAnchorResponse); +} + +message SubmitProfileRequest { + string cluster_id = 1; + string image_digest = 2; + bytes profile_canonical = 3; + bytes profile_hash = 4; + string miner_version = 5; + string source_repo = 6; + string source_commit = 7; +} + +message SubmitProfileResponse { + string capability_id = 1; + google.protobuf.Timestamp accepted_at = 2; + bool anchored = 3; + string anchor_id = 4; +} + +message GetProfileRequest { + bytes manifest_hash = 1; +} + +message GetProfileResponse { + string capability_id = 1; + google.protobuf.Timestamp time = 2; + string cluster_id = 3; + bytes manifest_hash = 4; + string image_digest = 5; + string miner_version = 6; + string source_repo = 7; + string source_commit = 8; + bool anchored = 9; + string anchor_id = 10; + int32 leaf_index = 11; +} + +message ListProfilesRequest { + string cluster_id = 1; + int64 limit = 2; +} + +message ListProfilesResponse { + repeated GetProfileResponse profiles = 1; +} + +message VerifyProfileRequest { + bytes manifest_hash = 1; +} + +message ProofStepMsg { + enum Direction { + LEFT = 0; + RIGHT = 1; + } + Direction direction = 1; + bytes hash = 2; +} + +message VerifyProfileResponse { + bool verified = 1; + string anchor_id = 2; + bytes merkle_root = 3; + repeated ProofStepMsg proof = 4; + string error = 5; +} + +message FlushAnchorRequest { + string cluster_id = 1; +} + +message FlushAnchorResponse { + string anchor_id = 1; + bytes merkle_root = 2; + int32 leaf_count = 3; +} diff --git a/quartermaster/v1/credentials.proto b/quartermaster/v1/credentials.proto new file mode 100644 index 0000000..9ec0d70 --- /dev/null +++ b/quartermaster/v1/credentials.proto @@ -0,0 +1,121 @@ +syntax = "proto3"; +package quartermaster.v1; + +import "google/protobuf/timestamp.proto"; + +service QuartermasterCredentials { + rpc ProvisionDatabase (ProvisionDatabaseRequest) returns (ProvisionDatabaseResponse); + rpc RotateCredential (RotateCredentialRequest) returns (RotateCredentialResponse); + rpc RevokeCredential (RevokeCredentialRequest) returns (RevokeCredentialResponse); + rpc GetCredentialRef (GetCredentialRefRequest) returns (GetCredentialRefResponse); + rpc ListCredentials (ListCredentialsRequest) returns (ListCredentialsResponse); + + // Phase 8: Cloud credential provisioning via STS AssumeRole. + rpc ProvisionCloudCredential (ProvisionCloudCredentialRequest) returns (ProvisionCloudCredentialResponse); + rpc RevokeCloudCredential (RevokeCloudCredentialRequest) returns (RevokeCloudCredentialResponse); + rpc GetCloudCredential (GetCloudCredentialRequest) returns (GetCloudCredentialResponse); +} + +message ProvisionDatabaseRequest { + string cluster_id = 1; + string service_name = 2; + string database_name = 3; +} + +message ProvisionDatabaseResponse { + string credential_id = 1; + string secret_ref = 2; + string secret_namespace = 3; + google.protobuf.Timestamp issued_at = 4; + bytes merkle_leaf = 5; +} + +message RotateCredentialRequest { + string credential_id = 1; +} + +message RotateCredentialResponse { + string new_credential_id = 1; + string secret_ref = 2; + google.protobuf.Timestamp issued_at = 3; + bytes merkle_leaf = 4; +} + +message RevokeCredentialRequest { + string credential_id = 1; +} + +message RevokeCredentialResponse { + google.protobuf.Timestamp revoked_at = 1; +} + +message GetCredentialRefRequest { + string credential_id = 1; +} + +message GetCredentialRefResponse { + string credential_id = 1; + string cluster_id = 2; + string service_name = 3; + string credential_type = 4; + string username = 5; + string database_name = 6; + string secret_ref = 7; + string secret_namespace = 8; + google.protobuf.Timestamp issued_at = 9; + google.protobuf.Timestamp expires_at = 10; + bool revoked = 11; +} + +message ListCredentialsRequest { + string cluster_id = 1; +} + +message ListCredentialsResponse { + repeated GetCredentialRefResponse credentials = 1; +} + +// Phase 8: Cloud credential provisioning messages. + +message ProvisionCloudCredentialRequest { + string tenant_id = 1; + string workspace_name = 2; + string operation_id = 3; + string provider_type = 4; + string role_arn = 5; + string session_policy = 6; + uint32 duration_seconds = 7; + string external_id = 8; +} + +message ProvisionCloudCredentialResponse { + string credential_ref_id = 1; + map credentials = 2; + google.protobuf.Timestamp expires_at = 3; + string session_name = 4; + bytes merkle_leaf = 5; +} + +message RevokeCloudCredentialRequest { + string credential_ref_id = 1; +} + +message RevokeCloudCredentialResponse { + google.protobuf.Timestamp revoked_at = 1; +} + +message GetCloudCredentialRequest { + string credential_ref_id = 1; +} + +message GetCloudCredentialResponse { + string credential_ref_id = 1; + string tenant_id = 2; + string provider_type = 3; + string role_arn = 4; + string session_name = 5; + google.protobuf.Timestamp issued_at = 6; + google.protobuf.Timestamp expires_at = 7; + bool revoked = 8; + bytes merkle_leaf = 9; +} diff --git a/quartermaster/v1/governance.proto b/quartermaster/v1/governance.proto new file mode 100644 index 0000000..16ff7d3 --- /dev/null +++ b/quartermaster/v1/governance.proto @@ -0,0 +1,119 @@ +syntax = "proto3"; + +package quartermaster.v1; + +import "google/protobuf/timestamp.proto"; + +// Governance service for intent lifecycle and SAT issuance. +service GovernanceService { + // Create a MutationIntent — called by application at user-request time. + rpc CreateIntent(CreateIntentRequest) returns (CreateIntentResponse); + + // Redeem a MutationIntent — called by worker at execution time. + rpc RedeemIntent(RedeemIntentRequest) returns (RedeemIntentResponse); + + // Revoke a MutationIntent — called to cancel pending authorization. + rpc RevokeIntent(RevokeIntentRequest) returns (RevokeIntentResponse); + + // Query intents for a tenant (admin/audit use). + rpc ListIntents(ListIntentsRequest) returns (ListIntentsResponse); +} + +message CreateIntentRequest { + string registry_type = 1; + string verb = 2; + string artifact_scope = 3; + string tenant_id = 4; + + // Identity claim — one of these should be set. + oneof identity_claim { + string oidc_token = 5; + ExternalEventClaim external_event = 6; + } + + uint32 ttl_seconds = 7; + uint32 max_redemptions = 8; + string idempotency_key = 9; +} + +message ExternalEventClaim { + string source = 1; + string event_id = 2; + string event_type = 3; + string verification = 4; +} + +message CreateIntentResponse { + string intent_id = 1; + google.protobuf.Timestamp expires_at = 2; + bytes intent_hash = 3; + string error = 4; + bool denied = 5; + string denial_reason = 6; + // If a governance ceremony is required, this field contains the + // ceremony ID. The intent status is "ceremony_pending" and cannot + // be redeemed until the ceremony resolves. + string ceremony_id = 7; +} + +message RedeemIntentRequest { + string intent_id = 1; +} + +message RedeemIntentResponse { + bool success = 1; + SatToken sat = 2; + int32 remaining_redemptions = 3; + string status = 4; + string error = 5; +} + +message SatToken { + bytes sat_hash = 1; + string bearer_svid = 2; + repeated SatScopeMsg scopes = 3; + google.protobuf.Timestamp issued_at = 4; + google.protobuf.Timestamp expires_at = 5; + bytes signature = 6; + bytes sat_bytes = 7; +} + +message SatScopeMsg { + string registry_type = 1; + repeated string verbs = 2; + string resource_pattern = 3; +} + +message RevokeIntentRequest { + string intent_id = 1; +} + +message RevokeIntentResponse { + bool success = 1; + string error = 2; +} + +message ListIntentsRequest { + string tenant_id = 1; + string status_filter = 2; + int32 limit = 3; +} + +message ListIntentsResponse { + repeated IntentSummary intents = 1; +} + +message IntentSummary { + string intent_id = 1; + string registry_type = 2; + string verb = 3; + string artifact_scope = 4; + string tenant_id = 5; + string claim_type = 6; + string claim_subject = 7; + string status = 8; + int32 max_redemptions = 9; + int32 redeemed_count = 10; + google.protobuf.Timestamp authorized_at = 11; + google.protobuf.Timestamp expires_at = 12; +} diff --git a/quartermaster/v1/notary.proto b/quartermaster/v1/notary.proto new file mode 100644 index 0000000..cbabee1 --- /dev/null +++ b/quartermaster/v1/notary.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; +package quartermaster.v1; + +import "google/protobuf/timestamp.proto"; + +service QuartermasterNotary { + rpc CreateAnchor (CreateAnchorRequest) returns (CreateAnchorResponse); + rpc GetLatestAnchor (GetLatestAnchorRequest) returns (GetLatestAnchorResponse); + rpc VerifyInclusion (VerifyInclusionRequest) returns (VerifyInclusionResponse); +} + +message CreateAnchorRequest { + string cluster_id = 1; + repeated bytes leaves = 2; + int64 etcd_revision = 3; // 0 means not set +} + +message CreateAnchorResponse { + string anchor_id = 1; + bytes merkle_root = 2; + bytes previous_root = 3; + int32 leaf_count = 4; + google.protobuf.Timestamp time = 5; +} + +message GetLatestAnchorRequest { + string cluster_id = 1; +} + +message GetLatestAnchorResponse { + string anchor_id = 1; + string cluster_id = 2; + bytes merkle_root = 3; + bytes previous_root = 4; + int64 etcd_revision = 5; + int32 leaf_count = 6; + google.protobuf.Timestamp time = 7; +} + +message VerifyInclusionRequest { + string anchor_id = 1; + bytes leaf = 2; + repeated bytes proof = 3; +} + +message VerifyInclusionResponse { + bool valid = 1; +} diff --git a/quartermaster/v1/pipelines.proto b/quartermaster/v1/pipelines.proto new file mode 100644 index 0000000..63b7d34 --- /dev/null +++ b/quartermaster/v1/pipelines.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; +package quartermaster.v1; + +import "google/protobuf/timestamp.proto"; +import "quartermaster/v1/capabilities.proto"; + +service QuartermasterPipelines { + // Notarize a completed pipeline run result. + rpc NotarizePipelineResult (NotarizePipelineResultRequest) returns (NotarizePipelineResultResponse); + + // Get the merkle inclusion proof for a notarized pipeline result. + rpc GetPipelineProof (GetPipelineProofRequest) returns (GetPipelineProofResponse); + + // Query notarized pipeline results (insurer/auditor API path). + rpc QueryPipelineAttestations (QueryPipelineAttestationsRequest) returns (QueryPipelineAttestationsResponse); + + // Get the current merkle root for a cluster. + rpc GetMerkleRoot (GetMerkleRootRequest) returns (GetMerkleRootResponse); +} + +// ── NotarizePipelineResult ── + +message NotarizePipelineResultRequest { + string cluster_id = 1; + string run_id = 2; + bytes result_canonical = 3; // RFC 8785 canonical JSON + bytes result_hash = 4; // hash_leaf(result_canonical) + string pipeline_name = 5; + string commit_hash = 6; + string status = 7; // "succeeded", "failed", "error" + string runner_svid = 8; + int32 step_count = 9; + int32 steps_passed = 10; + int32 steps_failed = 11; +} + +message NotarizePipelineResultResponse { + bool accepted = 1; + string run_id = 2; + bool anchored = 3; + string anchor_id = 4; + int32 leaf_index = 5; + bytes merkle_root = 6; + google.protobuf.Timestamp notarized_at = 7; +} + +// ── GetPipelineProof ── + +message GetPipelineProofRequest { + bytes result_hash = 1; // Look up by hash + string run_id = 2; // Or look up by run_id +} + +message GetPipelineProofResponse { + bool verified = 1; + string anchor_id = 2; + bytes merkle_root = 3; + repeated ProofStepMsg proof = 4; // Reused from capabilities.proto + string run_id = 5; + string pipeline_name = 6; + string commit_hash = 7; + string status = 8; + string error = 9; +} + +// ── QueryPipelineAttestations ── + +message QueryPipelineAttestationsRequest { + string commit_hash = 1; + string pipeline_name = 2; + string status = 3; + int32 limit = 4; // Default: 20 +} + +message QueryPipelineAttestationsResponse { + repeated PipelineAttestationSummary attestations = 1; +} + +message PipelineAttestationSummary { + string run_id = 1; + string pipeline_name = 2; + string commit_hash = 3; + string status = 4; + bytes result_hash = 5; + bool anchored = 6; + string anchor_id = 7; + int32 leaf_index = 8; + google.protobuf.Timestamp notarized_at = 9; +} + +// ── GetMerkleRoot ── + +message GetMerkleRootRequest { + string cluster_id = 1; +} + +message GetMerkleRootResponse { + string anchor_id = 1; + bytes root_hash = 2; + int32 leaf_count = 3; + google.protobuf.Timestamp computed_at = 4; +} diff --git a/quartermaster/v1/registry.proto b/quartermaster/v1/registry.proto new file mode 100644 index 0000000..54c2cf1 --- /dev/null +++ b/quartermaster/v1/registry.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; +package quartermaster.v1; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/struct.proto"; + +service QuartermasterRegistry { + rpc RegisterCluster (RegisterClusterRequest) returns (RegisterClusterResponse); + rpc GetCluster (GetClusterRequest) returns (GetClusterResponse); + rpc ListClusters (ListClustersRequest) returns (ListClustersResponse); + rpc UpdateClusterMetadata (UpdateClusterMetadataRequest) returns (UpdateClusterMetadataResponse); +} + +message RegisterClusterRequest { + string cluster_name = 1; + string trust_domain = 2; + string role = 3; // "authority", "delegate", "peer" + string provisioned_by = 4; // optional cluster_id +} + +message RegisterClusterResponse { + string cluster_id = 1; + string cluster_name = 2; + string trust_domain = 3; + string role = 4; + google.protobuf.Timestamp provisioned_at = 5; +} + +message GetClusterRequest { + string cluster_id = 1; +} + +message GetClusterResponse { + string cluster_id = 1; + string cluster_name = 2; + string trust_domain = 3; + string role = 4; + string provisioned_by = 5; + google.protobuf.Timestamp provisioned_at = 6; + string rmm_endpoint = 7; + google.protobuf.Struct metadata = 8; +} + +message ListClustersRequest {} + +message ListClustersResponse { + repeated GetClusterResponse clusters = 1; +} + +message UpdateClusterMetadataRequest { + string cluster_id = 1; + google.protobuf.Struct metadata = 2; +} + +message UpdateClusterMetadataResponse { + string cluster_id = 1; + google.protobuf.Struct metadata = 2; +} diff --git a/runner/v1/runner.proto b/runner/v1/runner.proto new file mode 100644 index 0000000..0fc22c8 --- /dev/null +++ b/runner/v1/runner.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; +package runner.v1; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +// ── Enums ── + +enum TriggerType { + TRIGGER_TYPE_UNSPECIFIED = 0; + TRIGGER_TYPE_MERGE = 1; + TRIGGER_TYPE_TAG = 2; + TRIGGER_TYPE_MANUAL = 3; + TRIGGER_TYPE_SCHEDULE = 4; + TRIGGER_TYPE_WEBHOOK = 5; + TRIGGER_TYPE_AGENT = 6; +} + +enum StepStatus { + STEP_STATUS_UNSPECIFIED = 0; + STEP_STATUS_PENDING = 1; + STEP_STATUS_RUNNING = 2; + STEP_STATUS_SUCCEEDED = 3; + STEP_STATUS_FAILED = 4; + STEP_STATUS_SKIPPED = 5; + STEP_STATUS_CANCELLED = 6; +} + +enum RunPhase { + RUN_PHASE_UNSPECIFIED = 0; + RUN_PHASE_PENDING = 1; + RUN_PHASE_RUNNING = 2; + RUN_PHASE_SUCCEEDED = 3; + RUN_PHASE_FAILED = 4; + RUN_PHASE_CANCELLED = 5; + RUN_PHASE_NOTARIZED = 6; +} + +// ── Messages ── + +message TriggerEvent { + TriggerType trigger_type = 1; + string commit_hash = 2; + string branch = 3; + string tag = 4; + string requested_by = 5; + string merge_session_id = 6; + google.protobuf.Timestamp triggered_at = 7; +} + +message StepResult { + string name = 1; + string step_type = 2; + StepStatus status = 3; + int32 exit_code = 4; + google.protobuf.Duration duration = 5; + bytes stdout_hash = 6; + bytes stderr_hash = 7; + repeated ArtifactRef artifacts = 8; + string error_message = 9; + uint32 attempt = 10; + google.protobuf.Timestamp started_at = 11; + google.protobuf.Timestamp completed_at = 12; +} + +message ArtifactRef { + string name = 1; + string path = 2; + bytes content_hash = 3; + int64 size_bytes = 4; + string media_type = 5; + string oci_digest = 6; +} + +message PipelineResult { + string run_id = 1; + string pipeline_name = 2; + TriggerEvent trigger = 3; + RunPhase phase = 4; + repeated StepResult steps = 5; + google.protobuf.Timestamp started_at = 6; + google.protobuf.Timestamp completed_at = 7; + google.protobuf.Duration total_duration = 8; + string runner_svid = 9; + bytes pipeline_yaml_hash = 10; + string cluster_id = 11; + AttestationInfo attestation = 12; +} + +message AttestationInfo { + bool anchored = 1; + string anchor_id = 2; + bytes merkle_leaf = 3; + bytes merkle_root = 4; + int32 leaf_index = 5; +} + +message LogEntry { + string step_name = 1; + string stream = 2; + string line = 3; + google.protobuf.Timestamp timestamp = 4; + uint64 sequence = 5; +} + +message PipelineRunSummary { + string run_id = 1; + string pipeline_name = 2; + RunPhase phase = 3; + TriggerType trigger_type = 4; + string commit_hash = 5; + google.protobuf.Timestamp started_at = 6; + google.protobuf.Timestamp completed_at = 7; + uint32 steps_total = 8; + uint32 steps_succeeded = 9; + uint32 steps_failed = 10; + bool attested = 11; +} diff --git a/runner/v1/service.proto b/runner/v1/service.proto new file mode 100644 index 0000000..59e2316 --- /dev/null +++ b/runner/v1/service.proto @@ -0,0 +1,100 @@ +syntax = "proto3"; +package runner.v1; + +import "runner/v1/runner.proto"; +import "google/protobuf/timestamp.proto"; + +service RunnerService { + rpc TriggerRun (TriggerRunRequest) returns (TriggerRunResponse); + rpc GetRunStatus (GetRunStatusRequest) returns (GetRunStatusResponse); + rpc StreamLogs (StreamLogsRequest) returns (stream LogEntry); + rpc CancelRun (CancelRunRequest) returns (CancelRunResponse); + rpc ListRuns (ListRunsRequest) returns (ListRunsResponse); + rpc GetPipelineSpec (GetPipelineSpecRequest) returns (GetPipelineSpecResponse); + rpc GetRunAttestation (GetRunAttestationRequest) returns (GetRunAttestationResponse); +} + +message TriggerRunRequest { + string pipeline_name = 1; + string commit_hash = 2; + TriggerType trigger_type = 3; + string requested_by = 4; + map parameters = 5; + // Branch name (for inbound sync triggers, e.g. "inbound/github-origin/pull/42/head") + string branch = 6; + // Name of the inbound remote that triggered this run (empty for non-inbound triggers) + string inbound_remote = 7; +} + +message TriggerRunResponse { + string run_id = 1; + RunPhase phase = 2; + string message = 3; + bool deduplicated = 4; + string dedup_run_id = 5; +} + +message GetRunStatusRequest { + string run_id = 1; +} + +message GetRunStatusResponse { + PipelineResult result = 1; +} + +message StreamLogsRequest { + string run_id = 1; + string step_name = 2; + bool follow = 3; + uint64 since_sequence = 4; +} + +message CancelRunRequest { + string run_id = 1; + string reason = 2; +} + +message CancelRunResponse { + bool success = 1; + string message = 2; +} + +message ListRunsRequest { + string pipeline_name = 1; + RunPhase phase_filter = 2; + TriggerType trigger_filter = 3; + uint32 limit = 4; + string commit_hash = 5; +} + +message ListRunsResponse { + repeated PipelineRunSummary runs = 1; +} + +message GetPipelineSpecRequest { + string commit_hash = 1; + string pipeline_name = 2; +} + +message GetPipelineSpecResponse { + string pipeline_yaml = 1; + string commit_hash = 2; + bytes pipeline_hash = 3; +} + +message GetRunAttestationRequest { + string run_id = 1; +} + +message GetRunAttestationResponse { + string run_id = 1; + bytes result_hash = 2; + string anchor_id = 3; + bytes merkle_root = 4; + int32 leaf_index = 5; + string runner_svid = 6; + string commit_hash = 7; + string pipeline_name = 8; + string status = 9; + google.protobuf.Timestamp notarized_at = 10; +} diff --git a/schematic/v1/schematics.proto b/schematic/v1/schematics.proto new file mode 100644 index 0000000..7570f3f --- /dev/null +++ b/schematic/v1/schematics.proto @@ -0,0 +1,261 @@ +syntax = "proto3"; +package schematic.v1; + +import "google/protobuf/timestamp.proto"; + +service SchematicsService { + // Create a new schematic from a file tree + rpc CreateSchematic(CreateSchematicRequest) returns (CreateSchematicResponse); + + // Get a schematic version + rpc GetSchematic(GetSchematicRequest) returns (GetSchematicResponse); + + // List all versions of a schematic + rpc ListVersions(ListVersionsRequest) returns (ListVersionsResponse); + + // Update a draft schematic's file tree + rpc UpdateSchematic(UpdateSchematicRequest) returns (UpdateSchematicResponse); + + // Validate a schematic against all validators + rpc ValidateSchematic(ValidateSchematicRequest) returns (ValidateSchematicResponse); + + // Record a stakeholder approval + rpc ApproveSchematic(ApproveSchematicRequest) returns (ApproveSchematicResponse); + + // Get approval status + rpc GetApprovalStatus(GetApprovalStatusRequest) returns (GetApprovalStatusResponse); + + // Publish an approved schematic as an immutable snapshot + rpc PublishSchematic(PublishSchematicRequest) returns (PublishSchematicResponse); + + // Create a new version from a published schematic + rpc CreateNextVersion(CreateNextVersionRequest) returns (CreateNextVersionResponse); + + // Fork a schematic with template operations + rpc ForkSchematic(ForkSchematicRequest) returns (ForkSchematicResponse); + + // Create a deployment binding + rpc CreateDeploymentBinding(CreateDeploymentBindingRequest) returns (CreateDeploymentBindingResponse); + + // Get deployment binding status + rpc GetDeploymentBinding(GetDeploymentBindingRequest) returns (GetDeploymentBindingResponse); +} + +// --- Create --- + +message CreateSchematicRequest { + string name = 1; + string version = 2; + // YAML content of schematic.yaml manifest + bytes manifest_yaml = 3; + // Files as path:content pairs + repeated SchematicFile files = 4; +} + +message SchematicFile { + string path = 1; + bytes content = 2; +} + +message CreateSchematicResponse { + string name = 1; + string version = 2; + string tree_hash = 3; + string status = 4; +} + +// --- Get --- + +message GetSchematicRequest { + string name = 1; + string version = 2; +} + +message GetSchematicResponse { + string name = 1; + string version = 2; + string tree_hash = 3; + string status = 4; + string parent_hash = 5; + repeated StakeholderInfo stakeholders = 6; + google.protobuf.Timestamp created_at = 7; +} + +message StakeholderInfo { + string role = 1; + string identity = 2; + bool required = 3; +} + +// --- List Versions --- + +message ListVersionsRequest { + string name = 1; +} + +message ListVersionsResponse { + repeated VersionInfo versions = 1; +} + +message VersionInfo { + string version = 1; + string tree_hash = 2; + string status = 3; + string parent_hash = 4; + google.protobuf.Timestamp created_at = 5; +} + +// --- Update --- + +message UpdateSchematicRequest { + string name = 1; + string version = 2; + repeated SchematicFile files = 3; +} + +message UpdateSchematicResponse { + string tree_hash = 1; + string status = 2; +} + +// --- Validate --- + +message ValidateSchematicRequest { + string name = 1; + string version = 2; +} + +message ValidateSchematicResponse { + bool valid = 1; + int32 error_count = 2; + int32 warning_count = 3; + repeated ValidationResultProto results = 4; +} + +message ValidationResultProto { + string validator = 1; + bool passed = 2; + string message = 3; + string severity = 4; +} + +// --- Approve --- + +message ApproveSchematicRequest { + string name = 1; + string version = 2; + string role = 3; + string identity = 4; + string tree_hash = 5; + string comment = 6; +} + +message ApproveSchematicResponse { + bool accepted = 1; + string approval_status = 2; + string message = 3; +} + +// --- Approval Status --- + +message GetApprovalStatusRequest { + string name = 1; + string version = 2; +} + +message GetApprovalStatusResponse { + string status = 1; + repeated string approved_roles = 2; + repeated string remaining_roles = 3; + repeated ApprovalInfo approvals = 4; +} + +message ApprovalInfo { + string role = 1; + string identity = 2; + google.protobuf.Timestamp approved_at = 3; + string comment = 4; +} + +// --- Publish --- + +message PublishSchematicRequest { + string name = 1; + string version = 2; +} + +message PublishSchematicResponse { + string tree_hash = 1; + string status = 2; +} + +// --- Create Next Version --- + +message CreateNextVersionRequest { + string name = 1; + string from_version = 2; + // "major", "minor", or "patch" + string bump = 3; +} + +message CreateNextVersionResponse { + string version = 1; + string tree_hash = 2; + string parent_hash = 3; + string status = 4; +} + +// --- Fork --- + +message ForkSchematicRequest { + string source_name = 1; + string source_version = 2; + string new_name = 3; + string new_version = 4; + repeated TemplateOperation operations = 5; +} + +message TemplateOperation { + // "replace", "add", "remove", "remove_section" + string op_type = 1; + string path = 2; + bytes content = 3; +} + +message ForkSchematicResponse { + string name = 1; + string version = 2; + string tree_hash = 3; + string status = 4; +} + +// --- Deployment Binding --- + +message CreateDeploymentBindingRequest { + string schematic_name = 1; + string schematic_version = 2; + string pipeline_name = 3; + string target_env = 4; +} + +message CreateDeploymentBindingResponse { + string binding_id = 1; + string status = 2; +} + +message GetDeploymentBindingRequest { + string binding_id = 1; +} + +message GetDeploymentBindingResponse { + string binding_id = 1; + string schematic_name = 2; + string schematic_version = 3; + string tree_hash = 4; + string pipeline_name = 5; + string target_env = 6; + string status = 7; + string run_id = 8; + google.protobuf.Timestamp created_at = 9; + google.protobuf.Timestamp deployed_at = 10; +} diff --git a/workspace/v1/workspace.proto b/workspace/v1/workspace.proto new file mode 100644 index 0000000..62290f9 --- /dev/null +++ b/workspace/v1/workspace.proto @@ -0,0 +1,424 @@ +syntax = "proto3"; +package workspace.v1; + +import "google/protobuf/timestamp.proto"; + +service WorkspaceController { + // Initialize the canonical repository (first-time setup) + rpc InitCanonical(InitCanonicalRequest) returns (InitCanonicalResponse); + + // Get canonical repo status + rpc GetCanonicalHead(GetCanonicalHeadRequest) returns (GetCanonicalHeadResponse); + + // Session workspace lifecycle + rpc CreateWorkspace(CreateWorkspaceRequest) returns (CreateWorkspaceResponse); + rpc GetWorkspaceStatus(GetWorkspaceStatusRequest) returns (GetWorkspaceStatusResponse); + rpc DestroyWorkspace(DestroyWorkspaceRequest) returns (DestroyWorkspaceResponse); + + // Diff and commit + rpc GetDiff(GetDiffRequest) returns (GetDiffResponse); + rpc StageFiles(StageFilesRequest) returns (StageFilesResponse); + rpc Commit(CommitRequest) returns (CommitResponse); + rpc GetLog(GetLogRequest) returns (GetLogResponse); + + // Abandon (discard all changes) + rpc Abandon(AbandonRequest) returns (AbandonResponse); + + // List active workspaces + rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse); + + // Get a tar archive of the canonical repo at a specific commit + rpc GetArchive(GetArchiveRequest) returns (stream ArchiveChunk); + + // --- Remote sync (Phase 3) --- + + // Push canonical branch to a configured remote + rpc PushToRemote(PushToRemoteRequest) returns (PushToRemoteResponse); + + // Fetch refs from a configured remote into inbound/ branches + rpc FetchFromRemote(FetchFromRemoteRequest) returns (FetchFromRemoteResponse); + + // List configured remotes from .guildhouse/remotes.yaml + rpc ListRemotes(ListRemotesRequest) returns (ListRemotesResponse); + + // Get sync status between canonical and a remote + rpc GetRemoteSyncStatus(GetRemoteSyncStatusRequest) returns (GetRemoteSyncStatusResponse); + + // Report commit status check to a remote provider (GitHub/Forgejo/GitLab) + rpc ReportStatus(ReportStatusRequest) returns (ReportStatusResponse); + + // --- Merge operations (Phase 4) --- + + // Merge an inbound branch into canonical main + rpc MergeToCanonical(MergeToCanonicalRequest) returns (MergeToCanonicalResponse); + + // Clean up an inbound branch after merge or rejection + rpc CleanupInboundBranch(CleanupInboundBranchRequest) returns (CleanupInboundBranchResponse); + + // List inbound branches (fetched from remotes) + rpc ListInboundBranches(ListInboundBranchesRequest) returns (ListInboundBranchesResponse); + + // Lightweight remote HEAD check (ls-remote equivalent — no objects fetched) + rpc LsRemote(LsRemoteRequest) returns (LsRemoteResponse); +} + +// --- Canonical --- + +message InitCanonicalRequest { + // Path to a directory of manifests to import as the initial commit. + // If empty, creates an empty repo with just .guildhouse/ structure. + string import_path = 1; + // Commit author + string author = 2; +} + +message InitCanonicalResponse { + string commit_hash = 1; + string message = 2; +} + +message GetCanonicalHeadRequest {} + +message GetCanonicalHeadResponse { + string commit_hash = 1; + string message = 2; + string author = 3; + google.protobuf.Timestamp timestamp = 4; + uint32 total_files = 5; +} + +// --- Session workspace lifecycle --- + +message CreateWorkspaceRequest { + string session_id = 1; + // Branch to base the workspace on (default: "main") + string base_branch = 2; + // Git author for commits in this workspace + string author_name = 3; + string author_email = 4; + // If true, workspace is read-only (no branch created, just a checkout) + bool read_only = 5; +} + +message CreateWorkspaceResponse { + // Filesystem path to the workspace root + string workspace_path = 1; + // Git branch name (empty if read_only) + string branch_name = 2; + // SHA of the base commit + string base_commit = 3; +} + +message GetWorkspaceStatusRequest { + string session_id = 1; +} + +message GetWorkspaceStatusResponse { + string session_id = 1; + string branch_name = 2; + string base_commit = 3; + string head_commit = 4; + uint32 commits_ahead = 5; + uint32 files_changed = 6; + uint32 insertions = 7; + uint32 deletions = 8; + bool has_uncommitted_changes = 9; + bool read_only = 10; +} + +message DestroyWorkspaceRequest { + string session_id = 1; +} + +message DestroyWorkspaceResponse { + bool success = 1; + string message = 2; +} + +// --- Diff and commit --- + +message GetDiffRequest { + string session_id = 1; + // If true: diff uncommitted changes only (workspace vs HEAD) + // If false: diff entire branch vs base (session branch vs main) + bool uncommitted_only = 2; +} + +message GetDiffResponse { + repeated FileDiff files = 1; + string base_ref = 2; + string head_ref = 3; + uint32 total_insertions = 4; + uint32 total_deletions = 5; +} + +message FileDiff { + string path = 1; + // "added", "modified", "deleted", "renamed" + string status = 2; + // Unified diff text (empty for binary files) + string diff_text = 3; + uint32 insertions = 4; + uint32 deletions = 5; + // For renames: the old path + string old_path = 6; +} + +message StageFilesRequest { + string session_id = 1; + // File paths to stage (relative to workspace root). + // Empty = stage all changes (git add -A). + repeated string paths = 2; +} + +message StageFilesResponse { + uint32 files_staged = 1; +} + +message CommitRequest { + string session_id = 1; + string message = 2; + // Override author (if empty, uses the author from CreateWorkspace) + string author_name = 3; + string author_email = 4; +} + +message CommitResponse { + string commit_hash = 1; + uint32 files_committed = 2; + string message = 3; +} + +message GetLogRequest { + string session_id = 1; + // Max number of commits to return (default: 20) + uint32 limit = 2; +} + +message GetLogResponse { + repeated CommitEntry commits = 1; +} + +message CommitEntry { + string hash = 1; + string short_hash = 2; + string message = 3; + string author_name = 4; + string author_email = 5; + google.protobuf.Timestamp timestamp = 6; + uint32 files_changed = 7; +} + +// --- Abandon --- + +message AbandonRequest { + string session_id = 1; +} + +message AbandonResponse { + bool success = 1; + uint32 commits_discarded = 2; + string message = 3; +} + +// --- List workspaces --- + +message ListWorkspacesRequest {} + +message ListWorkspacesResponse { + repeated WorkspaceSummary workspaces = 1; +} + +message WorkspaceSummary { + string session_id = 1; + string branch_name = 2; + string base_commit = 3; + string head_commit = 4; + uint32 commits_ahead = 5; + bool has_uncommitted_changes = 6; + bool read_only = 7; + google.protobuf.Timestamp created_at = 8; +} + +// --- Archive --- + +message GetArchiveRequest { + // Commit hash to archive (empty = HEAD) + string commit_hash = 1; + // Subset of paths to include (empty = all files) + repeated string paths = 2; +} + +message ArchiveChunk { + bytes data = 1; +} + +// --- Remote sync --- + +message PushToRemoteRequest { + // Name of the remote (must exist in .guildhouse/remotes.yaml) + string remote_name = 1; + // Local branch to push (default: "main") + string branch = 2; + // Force push — always false in Phase 3 + bool force = 3; +} + +message PushToRemoteResponse { + bool success = 1; + // Remote ref that was updated (e.g. "refs/heads/main") + string remote_ref = 2; + // Commit hash that was pushed + string commit_hash = 3; + string error = 4; +} + +message FetchFromRemoteRequest { + // Name of the remote (must exist in .guildhouse/remotes.yaml) + string remote_name = 1; + // Ref patterns to fetch (e.g. "refs/pull/*/head"). + // If empty, uses fetch_refs from remotes.yaml config. + repeated string ref_patterns = 2; +} + +message FetchFromRemoteResponse { + repeated FetchedRef refs_fetched = 1; + string error = 2; +} + +message FetchedRef { + // Local ref name (e.g. "inbound/github-origin/pull/42/head") + string ref_name = 1; + // Commit hash at the tip of this ref + string commit_hash = 2; + // True if this is a newly fetched ref (not previously seen) + bool is_new = 3; +} + +message ListRemotesRequest {} + +message ListRemotesResponse { + repeated RemoteInfo remotes = 1; +} + +message RemoteInfo { + string name = 1; + string url = 2; + // "github", "forgejo", or "gitlab" + string provider = 3; + string push_branch = 4; + // Last commit hash successfully pushed to this remote (empty if never pushed) + string last_push_hash = 5; + // Last time a fetch was performed from this remote + google.protobuf.Timestamp last_fetch_time = 6; +} + +message GetRemoteSyncStatusRequest { + string remote_name = 1; +} + +message GetRemoteSyncStatusResponse { + string remote_name = 1; + string canonical_head = 2; + string remote_head = 3; + uint32 commits_ahead = 4; + uint32 commits_behind = 5; + google.protobuf.Timestamp last_push = 6; + google.protobuf.Timestamp last_fetch = 7; +} + +message ReportStatusRequest { + // Name of the remote (determines provider + credentials) + string remote_name = 1; + // Commit SHA to report status for + string commit_hash = 2; + // Status state: "pending", "success", "failure", "error" + string state = 3; + // Context label (e.g. "guildhouse/pipeline") + string context = 4; + // Human-readable description + string description = 5; + // URL to link to (e.g. pipeline run URL) + string target_url = 6; + // Attestation hash (hex-encoded) to include in description + string attestation_hash = 7; +} + +message ReportStatusResponse { + bool success = 1; + // URL of the created status check (provider-specific) + string url = 2; + string error = 3; +} + +// --- Merge operations --- + +message MergeToCanonicalRequest { + // Branch name to merge (e.g. "inbound/github-origin/pull/42/head") + string branch_name = 1; + // Merge commit message + string merge_message = 2; + // Author for the merge commit + string author_name = 3; + string author_email = 4; + // Ceremony ID if the merge was approved via Accord + string ceremony_id = 5; +} + +message MergeToCanonicalResponse { + bool success = 1; + // New HEAD commit hash after merge + string new_head = 2; + // "fast_forward" or "merge_commit" + string merge_type = 3; + // Error message (empty on success) + string error = 4; + // Number of files changed in the merge + uint32 files_changed = 5; +} + +message CleanupInboundBranchRequest { + // Branch name to clean up + string branch_name = 1; +} + +message CleanupInboundBranchResponse { + bool success = 1; + string message = 2; +} + +message ListInboundBranchesRequest { + // Optional: filter by remote name + string remote_name = 1; +} + +message ListInboundBranchesResponse { + repeated InboundBranchInfo branches = 1; +} + +message InboundBranchInfo { + string branch_name = 1; + string commit_hash = 2; + string remote_name = 3; + // Original ref (e.g. "pull/42/head") + string original_ref = 4; +} + +// --- Lightweight remote ref check --- + +message LsRemoteRequest { + // Name of the remote + string remote_name = 1; + // Ref patterns to query (e.g. "refs/heads/main") + repeated string refs = 2; +} + +message LsRemoteResponse { + repeated RemoteRef refs = 1; + string error = 2; +} + +message RemoteRef { + string ref_name = 1; + string commit_hash = 2; +}