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

nordisk/holger

proto/holger-doors.proto

syntax = "proto3";

// `holger.doors.v1` (lane B3, 2026-10-26): the acts that had no gRPC door, so the
// thin client `holger-cli` could not do them. Its own package so `holger.proto`, and
// every copy of it a console vendors, does not move.
//
// * `BundleService.Export`: the signed airgap export of the approved set, the same
//   candidate `holger export` writes, streamed to the caller. Admin only, audited first
//   (no record, no export).
// * `FirstKeyService.Invite`: the link that enrols the box's FIRST admin key (a
//   passkey), for a box nobody can open a browser on yet. Admin only (the box's own
//   bootstrap credential), audited, and only while no admin key exists.
package holger.doors.v1;

service BundleService {
  // The candidate's members, in order: each member's bytes in one or more chunks that
  // name its path; the LAST chunk carries the summary and no data.
  rpc Export(ExportBundleRequest) returns (stream ExportBundleChunk);
  // The admin's authorization of a FIRST import (lanes HOL-CDS, HOL-NCSC): this vendor,
  // product and sequence may set the anti-rollback floor once. Admin only, audited, used
  // once; the same act as `holger bundle allow-first-import` and
  // `POST /-/bundle/first-import`.
  rpc AllowFirstImport(AllowFirstImportRequest) returns (AllowFirstImportReply);
}

message AllowFirstImportRequest {
  // The bundle's vendor (its `supplier`).
  string vendor = 1;
  string product = 2;
  // The sequence that establishes the floor.
  uint64 sequence = 3;
}

message AllowFirstImportReply {
  string vendor = 1;
  string product = 2;
  uint64 sequence = 3;
  // Who authorized it.
  string by = 4;
  // Unix seconds.
  int64 at = 5;
}

message ExportBundleRequest {
  string bundle_id = 1;
  // Monotonic per (supplier, product): the secure side's anti-rollback floor.
  uint64 sequence = 2;
  string supplier = 3;
  string product = 4;
  string release_version = 5;
  // 0 is the default of 30 days.
  uint32 expires_days = 6;
  // Only these artifacts (file name, name or `name@version`). Empty: every AVAILABLE
  // artifact in production.
  repeated string artifacts = 7;
}

message ExportBundleChunk {
  // The member these bytes belong to (relative to the candidate directory).
  string path = 1;
  bytes data = 2;
  // Set on the last chunk only.
  ExportBundleSummary summary = 3;
}

message ExportBundleSummary {
  string bundle_id = 1;
  uint64 sequence = 2;
  // Computed from the stored evidence, never typed in.
  string verdict = 3;
  string candidate_sha256 = 4;
  // One line per exported artifact, as `holger export` prints it.
  repeated string artifacts = 5;
  uint32 members = 6;
}

service FirstKeyService {
  // A single-use invite that enrols the first admin key. Refused once an admin key exists.
  rpc Invite(FirstKeyRequest) returns (FirstKeyReply);
}

message FirstKeyRequest {
  // The name the first admin is enrolled under. Empty: theirs to choose at enrolment.
  string display = 1;
}

message FirstKeyReply {
  // The invite secret: shown ONCE, never stored (only its hash is).
  string invite = 1;
  string id = 2;
  int64 expires = 3;
  // The groups the redeemed key holds.
  repeated string groups = 4;
  // Where to redeem it, relative to the console's origin (`/login?invite=<secret>`).
  string path = 5;
  string words = 6;
}

// `DownstreamService.Invite` (lane HOL-PRODUCTS-7, 2026-10-30): the signed invite a
// downstream of this server is handed (`holger-cli downstream invite`, the console's
// Invite button). A `via: Front` downstream gets a direct invite (schema v1: the server's
// sync door, no tunnel); a `via: Wireguard` one gets the estate's (schema v2, the link's
// pre-shared key inside the signed bytes, the front's interface). Signed with the server's
// sync key, for the downstream's key; admin only, audited.
service DownstreamService {
  rpc Invite(DownstreamInviteRequest) returns (DownstreamInviteReply);
}

message DownstreamInviteRequest {
  // The enrolled downstream's name.
  string name = 1;
  // A direct invite's door (`http(s)://host:port`). Empty: the server's own sync door.
  string door = 2;
  // A WireGuard invite's front key and endpoint. Empty: the front's own, as it offered them.
  string hub_key = 3;
  string endpoint = 4;
}

message DownstreamInviteReply {
  // The signed invite (JSON): the file the downstream's RON names in `sync.upstreams[].invite`.
  bytes invite = 1;
  string schema = 2;
  // The door the downstream reaches this server at.
  string door = 3;
  // A WireGuard downstream's address on the front's interface; empty for a direct one.
  string address = 4;
  // This server's sync name and key: what the downstream's RON trusts.
  string upstream = 5;
  string key = 6;
}