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/common.proto

syntax = "proto3";

// gunnar's control plane. gRPC is the ONLY control plane; there is no REST and
// no web UI in this repo: gRPC is the only control plane.
//
// Two rules hold across every file in this package:
//
//   1. NAMESPACES ARE FIRST CLASS. Multi-tenancy is in from day 1. Every
//      repository is addressed as `<namespace>/<repo>`, never as a bare name,
//      never as an opaque id. `RepoPath` carries both halves and appears in
//      every repo-scoped request and every repo-scoped event.
//
//   2. NO `google.protobuf.Any`. Every payload is an explicit, named message.
//      A control plane whose wire types are opaque cannot be reviewed, and a
//      UI cannot render what it cannot name.
//
// Timestamps are explicit `int64 *_unix_ms` (milliseconds since the Unix
// epoch, UTC) rather than `google.protobuf.Timestamp`: it keeps the server's
// dependency budget one crate lighter and there is nothing a Timestamp
// buys here that a millisecond count does not.
package gunnar.v1;

// The hash function a repository's objects are named by (sha256 is the
// default for new repos; sha1 stays supported). `object-format` on the wire is
// echoed from this. Answering sha1 to a sha256 repo yields an empty response,
// a bug that has already cost one session.
enum HashKind {
  HASH_KIND_UNSPECIFIED = 0;
  HASH_KIND_SHA1 = 1;
  HASH_KIND_SHA256 = 2;
}

enum Visibility {
  VISIBILITY_UNSPECIFIED = 0;
  // Readable only by principals with an explicit grant.
  VISIBILITY_PRIVATE = 1;
  // Readable by any authenticated principal on this server.
  VISIBILITY_INTERNAL = 2;
  // Readable without a grant.
  VISIBILITY_PUBLIC = 3;
}

// gunnar owns its own identity table. There are no OS users.
enum PrincipalKind {
  PRINCIPAL_KIND_UNSPECIFIED = 0;
  PRINCIPAL_KIND_USER = 1;
  PRINCIPAL_KIND_MACHINE = 2;
  PRINCIPAL_KIND_DEPLOY = 3;
  // A group of principals (punkt 9). A group holds no keys; it is a name that
  // grants attach to, and its members inherit those grants. Membership is
  // tracked by the Access service (AddGroupMember), not on this message.
  PRINCIPAL_KIND_GROUP = 4;
}

enum GrantLevel {
  GRANT_LEVEL_UNSPECIFIED = 0;
  // git-upload-pack (clone/fetch).
  GRANT_LEVEL_READ = 1;
  // git-receive-pack (push). Implies READ.
  GRANT_LEVEL_WRITE = 2;
  // Control-plane mutation. Implies WRITE.
  GRANT_LEVEL_ADMIN = 3;
}

enum ObjectKind {
  OBJECT_KIND_UNSPECIFIED = 0;
  OBJECT_KIND_BLOB = 1;
  OBJECT_KIND_TREE = 2;
  OBJECT_KIND_COMMIT = 3;
  OBJECT_KIND_TAG = 4;
}

// The address of a repository: `<namespace>/<repo>`, matching the SSH path
// `git@host:<namespace>/<repo>.git`. Both halves are required; an empty
// namespace is INVALID_ARGUMENT, never a default.
message RepoPath {
  string namespace = 1;
  string repo = 2;
}

message Namespace {
  string name = 1;
  string description = 2;
  int64 created_at_unix_ms = 3;
  // Whose ACCOUNT this namespace is: a principal's id is the namespace it
  // intrinsically owns, and this is that principal's kind. UNSPECIFIED: no
  // principal of that id (a namespace made on its own). A reader that sells
  // or bills per namespace (monetize's gunnar plugin) leaves out the
  // accounts of MACHINE principals — the front's certify key, the console,
  // the guest, the twin, the installer's seed, monetize itself, a fed
  // reader — which are the estate's own parts, never a customer.
  PrincipalKind owner_kind = 4;
  // 5 was `bool enterprise`, a flag an operator set, on the release-3 merge
  // branch only (never released): there is no such flag. An account is an
  // enterprise account when its admin has enrolled employees (`employees`).
  reserved 5;
  reserved "enterprise";
  // The namespace is PUBLIC: shown to everyone, even while it holds no public
  // repository. False — the default for every account — is private: an
  // outsider learns of it only through a public repository in it, and one
  // holding none answers exactly as a namespace that does not exist. Only the
  // account admin changes it (Repos.SetNamespaceSettings).
  bool public = 6;
  // The account's emitted events are PUBLIC. False — the default — shows them
  // to nobody outside the account (Events.Watch). Only the account admin
  // changes it (Repos.SetNamespaceSettings).
  bool events_public = 7;
  // How many employees the account's admin has enrolled (Accounts.
  // EnrollEmployee). An account with any is an ENTERPRISE account: derived,
  // never set. Told to whoever may list the namespace.
  uint32 employees = 8;
  // The namespace's owner holds a SERVER-scope grant — the operator, the SELLER (2026-10-08):
  // the shop's own, never a customer. A selling shop gives every person's account a fact
  // (no account there is "no fact = uncapped") and leaves these out, naming them in its log.
  // Only an operator grants Scope::Server; nothing a customer does sets it.
  bool owner_server_grant = 9;
}

message Repo {
  RepoPath path = 1;
  HashKind hash_kind = 2;
  // e.g. "refs/heads/main", a full ref name, not a short branch name.
  string default_branch = 3;
  Visibility visibility = 4;
  int64 created_at_unix_ms = 5;
  string description = 6;
  // An ENCRYPTED repository: the server refuses any push that would put
  // plaintext on it, and holds no key. Only the account admin sets it
  // (`Repos.SetEncrypted`). See gunnar's `.nornir/encrypted-repos.md`.
  bool encrypted = 7;
}

// A resolved ref. `oid` is the hex object id the ref points at; for a symbolic
// ref (HEAD) `symbolic_target` carries the ref it points to. `peeled_oid` is
// set only for annotated tags, and is the commit the tag ultimately names.
message RefTarget {
  string ref_name = 1;
  string oid = 2;
  string symbolic_target = 3;
  string peeled_oid = 4;
}

// One ref moving. Shared by receive-pack reporting, the event stream,
// attestation and mirror sync: one shape, four consumers (one
// implementation). A deletion is new_oid = all-zeroes; a creation is old_oid =
// all-zeroes.
message RefUpdate {
  string ref_name = 1;
  string old_oid = 2;
  string new_oid = 3;
}

message Principal {
  string id = 1;
  string name = 2;
  PrincipalKind kind = 3;
  int64 created_at_unix_ms = 4;
}

// Identity comes from the KEY, not from the SSH username.
message PublicKey {
  // "SHA256:...", the OpenSSH-format sha256 fingerprint. The primary key.
  string fingerprint = 1;
  // "ssh-ed25519", "ecdsa-sha2-nistp256", ...
  string algo = 2;
  string principal_id = 3;
  string comment = 4;
  int64 added_at_unix_ms = 5;
  optional int64 last_used_unix_ms = 6;
  optional int64 expires_at_unix_ms = 7;
}

// A grant scopes to a NAMESPACE or to a single REPO. An empty `repo`
// means the grant covers the whole namespace.
message Grant {
  string principal_id = 1;
  string namespace = 2;
  string repo = 3;
  GrantLevel level = 4;
  int64 granted_at_unix_ms = 5;
}

// git's author/committer identity line.
message Signature {
  string name = 1;
  string email = 2;
  int64 time_unix_seconds = 3;
  int32 tz_offset_minutes = 4;
}

message Commit {
  string oid = 1;
  repeated string parent_oids = 2;
  string tree_oid = 3;
  Signature author = 4;
  Signature committer = 5;
  string message = 6;
}

// One clock-sync attempt (`gunnar_clock::report::Report`), served on
// `Admin.Health` and `Bootstrap.Status`. Unset when no attempt has run in this
// process.
message ClockSync {
  // Why it ran: "boot" | "skew-refusal" | "timer".
  string trigger = 1;
  // This server's clock when the attempt finished (after any step).
  int64 at_unix_ms = 2;
  // "not-configured" | "off" | "misconfigured" | "stepped" | "slewed" |
  // "already-right" | "no-quorum" | "disagree" | "apply-failed"
  string outcome = 3;
  // The decided correction (source minus this clock); 0 when none was decided.
  int64 offset_ms = 4;
  // One sentence for a human.
  string detail = 5;
  repeated ClockSource sources = 6;
}

// One configured source's part in a ClockSync.
message ClockSource {
  // As configured: "peer://10.13.8.99:50123", "https://…", "ntp://…".
  string source = 1;
  bool answered = 2;
  // Source minus this clock, when it answered.
  int64 offset_ms = 3;
  // Why it contributed nothing, when it did not.
  string error = 4;
}