Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

crates/gunnar-ui-server/proto/gunnar/v1/policy.proto

syntax = "proto3";

package gunnar.v1;

import "gunnar/v1/common.proto";

// The pre-receive gate's configuration. It is what makes gunnar a forge
// rather than a bare repo over SSH.
//
// Gates are in-process `Gate` impls, never shell hooks: there is no shell on
// the server. This service configures them; `gunnar-policy` runs them
// between "pack is indexed" and "refs are applied".
service Policy {
  rpc GetProtection(GetProtectionRequest) returns (GetProtectionResponse);
  rpc SetProtection(SetProtectionRequest) returns (SetProtectionResponse);
  rpc DeleteProtection(DeleteProtectionRequest) returns (DeleteProtectionResponse);

  rpc GetRefNamePolicy(GetRefNamePolicyRequest) returns (GetRefNamePolicyResponse);
  rpc SetRefNamePolicy(SetRefNamePolicyRequest) returns (SetRefNamePolicyResponse);

  rpc ListGates(ListGatesRequest) returns (ListGatesResponse);
}

// Branch protection, keyed by ref pattern. The pattern is a git-style glob
// over full ref names: "refs/heads/main", "refs/heads/release/*".
message BranchProtection {
  string ref_pattern = 1;
  // A non-fast-forward update. Default (false) rejects force-pushes.
  bool allow_force_push = 2;
  // Default (false) rejects deletions of matching refs.
  bool allow_delete = 3;
  // Every new commit must carry a valid ssh-sig from a key registered to its
  // pusher (the differentiator).
  bool require_signature = 4;
  // Empty means "any principal with WRITE".
  repeated string allowed_principal_ids = 5;
  // 0 means no limit. Rejects the 4 GB blob BEFORE it lands.
  uint64 max_object_bytes = 6;
  // 0 means no limit.
  uint64 max_objects_per_push = 7;
  // `PullRequests.Merge` into a matching ref refuses to count the pull
  // request AUTHOR's own approval. Default (false) keeps the solo-operator
  // posture: an appliance with one human must stay mergeable.
  bool forbid_self_approval = 8;
  // `PullRequests.Merge` into a matching ref needs this many DISTINCT
  // verified approvers of the exact source tip. 0 means the baseline that
  // always holds: one verified approval. Approvals by the same principal
  // count once.
  uint32 required_approvals = 9;
}

// A pattern allowlist/denylist over ref names, per repo. Deny wins.
message RefNamePolicy {
  repeated string allow_patterns = 1;
  repeated string deny_patterns = 2;
}

message GateInfo {
  string name = 1;
  string description = 2;
  bool enabled = 3;
}

message GetProtectionRequest {
  RepoPath repo = 1;
  // Empty returns every protection rule on the repo.
  string ref_pattern = 2;
}
message GetProtectionResponse {
  repeated BranchProtection protections = 1;
}

message SetProtectionRequest {
  RepoPath repo = 1;
  // Upserted on `protection.ref_pattern`.
  BranchProtection protection = 2;
}
message SetProtectionResponse {
  BranchProtection protection = 1;
}

message DeleteProtectionRequest {
  RepoPath repo = 1;
  string ref_pattern = 2;
}
message DeleteProtectionResponse {}

message GetRefNamePolicyRequest {
  RepoPath repo = 1;
}
message GetRefNamePolicyResponse {
  RefNamePolicy policy = 1;
}

message SetRefNamePolicyRequest {
  RepoPath repo = 1;
  RefNamePolicy policy = 2;
}
message SetRefNamePolicyResponse {
  RefNamePolicy policy = 1;
}

message ListGatesRequest {
  RepoPath repo = 1;
}
message ListGatesResponse {
  repeated GateInfo gates = 1;
}