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

nordisk/holger

proto/holger-grow.proto

syntax = "proto3";

// holger.grow.v1 — the data set grows WITHOUT a restart (2026-09-27).
//
// holger's half of monetize's growth: the batcher on vm1 reads
// AdminService.Capacity (total / used / `growth`), orders a `watermark:`
// growth, and upcloud.wasm detaches → resizes → attaches the volumes while this
// door reports `unmounted | waiting-members`. The phase machine is edda's
// `appliance-grow` (gunnar's, moved AS IS); the field set below is gunnar's
// `GrowStartRequest` / `GrowStatusResponse` / `FlushReplicationResponse` field
// for field, so monetize reads the same facts from both products.
//
// Its own package, like holger.estate.v1: holger.proto — and every downstream
// copy of it — moves only by the growth verdict on CapacityReply.
package holger.grow.v1;

service GrowService {
  // Drain, unmount, wait for the members. ADMIN (on a twin too: a twin grows
  // exactly like main). Answers the status AFTER the call, never when the
  // growth has finished.
  rpc Start(GrowStartRequest) returns (GrowStatusReply);
  // The phase, NO CREDENTIAL: the actor outside the box that owns the volumes
  // must read `waiting-members` / `failed` with nothing to sign with, while the
  // stores are closed — gunnar's Bootstrap.GrowStatus, one door here.
  rpc Status(GrowStatusRequest) returns (GrowStatusReply);
  // While `waiting-members`: look again now (`give_up`: serve what is there).
  // On a growth that is already `done` it answers the status. ADMIN.
  rpc Resume(GrowResumeRequest) returns (GrowStatusReply);
  // The watermark (T11): wait up to `timeout_secs` for every enrolled twin to
  // have applied this box's head. ADMIN.
  rpc Flush(GrowFlushRequest) returns (GrowFlushReply);
  // A TWIN's word to its primary, each follow cycle: how far into the primary's
  // sequence it has applied, and how full its own disk is. The primary answers
  // its current position. ADMIN-READ (the twin's follow bearer).
  rpc TwinReport(TwinReportRequest) returns (TwinReportReply);
  // Every enrolled twin as this primary last heard from it — monetize's twin
  // sizing reads the fill. ADMIN-READ.
  rpc Twins(TwinsRequest) returns (TwinsReply);
  // A paired DOWNSTREAM holger's word to this upstream after each completed
  // mirror cycle — the twin's TwinReport, per paired repository: how far into
  // this box's sequence it has applied. Authenticated by the paired mTLS
  // certificate ALONE (the pinned fingerprint of `holger-peers/downstream-*`);
  // an unpaired caller is refused by name (holger-peer-4), a repository the
  // pairing does not list by holger-peer-2. The answer is this box's position,
  // which the downstream's next cycle starts from.
  rpc DownstreamReport(DownstreamReportRequest) returns (TwinReportReply);
  // Every paired downstream as this upstream last heard from it (kept under the
  // data root, so a restart keeps the last word). ADMIN-READ.
  rpc Downstreams(DownstreamsRequest) returns (DownstreamsReply);
}

message GrowStartRequest {
  // Per-member target in 512-byte sectors; 0 = whatever every member comes
  // back larger at.
  uint64 target_sectors_per_member = 1;
  // The go-ahead JSON ({v, signer, target_sectors, nonce, issued_unix_ms,
  // signature}); `monetize` signed under the box's monetize key (metered), or
  // `operator` on this admin door.
  bytes go_ahead = 2;
  // 0 = the box's own ceilings (600 s drain, 3600 s members).
  uint32 drain_timeout_secs = 3;
  uint32 wait_members_timeout_secs = 4;
}

message GrowStatusRequest {}

message GrowResumeRequest {
  bool give_up = 1;
}

message GrowMember {
  uint32 index = 1;
  string device = 2;
  string partition = 3;
  uint64 disk_bytes = 4;
  uint64 set_bytes = 5;
  uint64 offered_bytes = 6;
  bool larger = 7;
}

// The boot stack's grow journal (/serialdat/grow-journal), summarised.
message GrowState {
  bool engine_present = 1;
  // idle | in_progress | done | partial | aborted | failed
  string phase = 2;
  uint64 epoch = 3;
  uint64 target_sectors = 4;
  repeated string steps = 5;
  repeated uint32 partial_members = 6;
  string detail = 7;
  string journal_path = 8;
}

message GrowStatusReply {
  // idle | draining | unmounted | waiting-members | growing | mounting | done | failed
  // `unmounted` is said only once /data is unmounted AND the array stopped.
  string phase = 1;
  string why = 2;
  uint64 epoch = 3;
  uint64 target_sectors_per_member = 4;
  repeated GrowMember members = 5;
  string members_verdict = 6;
  GrowState journal = 7;
  bool engine_present = 8;
  bool hotplug = 9;
  // /data is mounted and the stores are open — true ONLY then. monetize orders
  // a recharge on a failed run that reads `serving: false`.
  bool serving = 10;
  bool identity_in_ram = 11;
  uint64 in_flight = 12;
  int64 since_unix_ms = 13;
  int64 server_unix_ms = 14;
  string detail = 15;
  string go_ahead_signer = 16;
  string set_uuid = 17;
  uint64 total_bytes = 18;
  // This box is a twin (it grows like main; its drain proves no standby).
  bool twin = 19;
}

message GrowFlushRequest {
  uint32 timeout_secs = 1;
}

message GrowFlushReply {
  bool caught_up = 1;
  uint64 in_flight = 2;
  uint64 pending = 3;
  uint64 bus_gaps = 4;
  bool needs_full_resync = 5;
  int64 last_success_unix_ms = 6;
  int64 flushed_at_unix_ms = 7;
  uint64 waited_ms = 8;
  uint64 catalog_repos = 9;
  uint64 standbys = 10;
  string verdict = 11;
}

message TwinReportRequest {
  // Who the twin is (its principal on the primary's roster).
  string principal = 1;
  // Where the twin serves (for the operator; the primary never dials it).
  string address = 2;
  // The primary boot the position below belongs to; a position from another
  // boot says nothing about this one.
  uint64 applied_boot = 3;
  // The primary's head at the START of the twin's last complete follow cycle.
  uint64 applied_seq = 4;
  // The twin's own data volume, statvfs.
  uint64 disk_total_bytes = 5;
  uint64 disk_used_bytes = 6;
  // WHAT the twin holds, as it proved it at the end of its last WHOLE follow
  // cycle (2026-09-28, the estate's read of its twin — gunnar's pattern: a twin
  // publishes no door outside, its PRIMARY keeps what the twin says of itself,
  // and the front carries the primary's door): the root of the primary's estate
  // manifest the twin pulled and holds blake3-identical
  // (`estate_wire::manifest_root`), empty until one whole cycle;
  string estate_root = 7;
  // how many files that manifest named;
  uint64 estate_files = 8;
  // the artifacts the twin wrote most recently, with the sha256 of the bytes it
  // wrote (a bounded ring, oldest first).
  repeated HeldArtifact held = 9;
}

// One artifact a twin wrote into its own repository.
message HeldArtifact {
  string repository = 1;
  // The mirror coordinate (`namespace/name@version`).
  string coordinate = 2;
  // Lowercase hex sha256 of the bytes the twin wrote.
  string sha256 = 3;
  uint64 size = 4;
}

message TwinReportReply {
  uint64 boot = 1;
  uint64 head_seq = 2;
}

message TwinsRequest {}

message TwinRow {
  string principal = 1;
  string address = 2;
  // ok | behind | stalled | silent — stalled: still reporting, the applied
  // position unchanged with the head ahead for longer than TWIN_STALLED_AFTER_MS
  string state = 3;
  int64 last_seen_unix_ms = 4;
  uint64 lag_entries = 5;
  uint64 disk_total_bytes = 6;
  uint64 disk_used_bytes = 7;
  uint32 fill_permille = 8;
  // The twin's applied position and this primary's head — `lag_entries` is
  // computed from them (applied 0: a position from another boot, or none).
  uint64 applied_seq = 9;
  uint64 head_seq = 10;
  uint64 seen_ago_ms = 11;
  // What the twin last said it holds (TwinReportRequest 7-9).
  string estate_root = 12;
  uint64 estate_files = 13;
  repeated HeldArtifact held = 14;
  // Today's pulls (UTC day, since this process started) and the last one —
  // DownstreamRow 10/12's words: a twin reports once at the end of each whole
  // follow cycle, so each report is one pull. The last synced point is
  // applied_seq above, judged by the one follow_point rule.
  uint64 pulls_today = 15;
  int64 last_pull_unix = 16;
}

message TwinsReply {
  repeated TwinRow twins = 1;
}

// One paired repository's sync point, as the downstream says it.
message DownstreamPoint {
  // The repository on THIS (the upstream) box — the pairing's name for it.
  string repository = 1;
  // The upstream boot the position below belongs to (TwinReportRequest 3).
  uint64 applied_boot = 2;
  // The upstream's head at the START of the downstream's last complete cycle
  // of this repository (TwinReportRequest 4); 0 = none yet.
  uint64 applied_seq = 3;
  // When that cycle completed; 0 = never.
  int64 last_complete_unix_ms = 4;
}

message DownstreamReportRequest {
  repeated DownstreamPoint points = 1;
  // The downstream's declared cycle (`ron_every_secs`): the upstream reads
  // `silent` after 3 of them and `stalled` after 5 (the twin's rule). 0 = the
  // mirror default.
  uint32 every_secs = 2;
  // What the downstream is, from its own RON (HOL-FLOW-SMART): the upstream's flow
  // unfolds it to this. Absent from an older downstream. NOT in the text the report's
  // proof signs (that text is the same in every holger version, HOL-SYNC-SKEW): a sync
  // downstream proves it with `summary_proof`, a paired one by its certificate.
  DownstreamSummary summary = 3;
  // The summary's own proof (HOL-SYNC-SKEW): base64 of the signed envelope over the
  // summary's text, which holds the report's text too, so it belongs to this report only.
  // An older upstream does not know this field and skips it, with the summary.
  string summary_proof = 4;
  // The proof of the summary's `console_url` (HOL-FLOW-FRONT): base64 of the signed envelope
  // over its own text (the report's text and the address), so an upstream that checks the
  // summary as `summary_proof` signs it (holger 3.1.0) still opens it, and a newer upstream
  // keeps the address only when this proof holds.
  string console_proof = 5;
}

// ★ **A downstream's own RON, summarised** (HOL-FLOW-SMART): names, words and folders,
// never a key, token or secret.
message DownstreamSummary {
  // `OneWay` or `Fetch`.
  string boundary = 1;
  // Its repositories, by name.
  repeated string repositories = 2;
  // Its one-way inbox folder; empty when it fetches.
  string inbox = 3;
  // Who allows a vendor's first import (`admin`).
  string first_import = 4;
  // Its security domain; empty when it declares none.
  string domain = 5;
  // How many downstreams it declares itself (it is upstream of N).
  uint32 downstreams = 6;
  // Its console's address (an https URL: the site's www name), as its own front reported it
  // (HOL-FLOW-FRONT): the upstream's flow links the downstream's box to that server's own
  // flow. Empty when it knows none. Not in `summary_proof`'s text: `console_proof` proves it.
  string console_url = 7;
}

message DownstreamsRequest {}

message DownstreamRepoRow {
  string repository = 1;
  // ok | behind | stalled | silent — TwinRow.state's words and rule.
  string state = 2;
  // Writes this upstream admitted since the reported point (head − applied; the
  // whole head for a point from another boot). The upstream's sequence counts
  // every write, so this is an upper bound for one repository.
  uint64 lag_entries = 3;
  uint64 applied_seq = 4;
  uint64 head_seq = 5;
  int64 last_complete_unix_ms = 6;
}

message DownstreamRow {
  string name = 1;
  // `sha256:<64 hex>` of the paired client certificate.
  string fingerprint = 2;
  // The worst repository's state; `silent` when never heard.
  string state = 3;
  bool stalled = 4;
  // The largest repository lag.
  uint64 lag_entries = 5;
  int64 last_seen_unix_ms = 6;
  uint64 seen_ago_ms = 7;
  uint32 every_secs = 8;
  repeated DownstreamRepoRow repositories = 9;
  // Today's pulls (UTC day, since this process started) and the last one.
  uint64 requests_today = 10;
  uint64 bytes_today = 11;
  int64 last_pull_unix = 12;
  // **Packages this downstream has fetched, ever** (lane A13, 2026-10-23): one
  // per artifact delivered to its paired certificate, and their bytes. Kept in
  // its durable sync record (`holger-peers/sync-<name>.ron`), so a restart
  // keeps the count; pulls in the last minute before a crash may be lost.
  uint64 fetched_total = 13;
  uint64 bytes_total = 14;
  // Artifacts delivered to it today (UTC day, since this process started).
  uint64 fetched_today = 15;
  // What it said of its own RON in its last report (HOL-FLOW-SMART); absent when it
  // said nothing.
  DownstreamSummary summary = 16;
}

message DownstreamsReply {
  repeated DownstreamRow downstreams = 1;
  uint64 boot = 2;
  uint64 head_seq = 3;
}