// Wire schema for soldr-daemon IPC + redb-backed persistent state
// (issue #580). The Rust types in `crates/soldr-cli/src/daemon/wire.rs`
// are hand-written `#[derive(prost::Message)]` structs that mirror
// this schema. The .proto file is the documentation source of truth;
// keep the two in sync.
//
// Wire format (issue #580 migration):
// * IPC frames carry a prost-encoded `WireRequest` or `WireResponse`.
//   Old bincode frames are rejected by the IPC layer's
//   `PROTOCOL_VERSION` check (bumped from 4 to 5 when this lands).
// * redb values are written as `[u8 tag][prost bytes]` where
//   `tag == 0x01` means "prost-encoded". The read path first tries
//   to interpret the row as tagged; on tag-byte mismatch it falls
//   back to the legacy bincode decoder for backwards-compat with
//   rows written before this migration.

syntax = "proto3";

package soldr.daemon.v1;

// =========================================================================
// Request (client -> daemon)
// =========================================================================

message WireRequest {
  oneof kind {
    WireRecordTargetTouch record_target_touch = 1;
    WireUnit status = 2;
    WireUnit shutdown = 3;
    WireBuildSessionStart build_session_start = 4;
    WireBuildSessionEnd build_session_end = 5;
    // Tag 6 (WireRecordCompile) reserved — lifecycle telemetry moved
    // onto WireCompileRequest in soldr#1537.
    WireListBuilds list_builds = 7;
    WireListSlowBuilds list_slow_builds = 8;
    // Tag 9 (WireLinkZccache link_zccache) reserved — deleted in
    // soldr#1467; proto3 disallows `reserved` inside `oneof`, so this
    // comment is the reservation.
    WireCookLookup cook_lookup = 10;
    WireCookRecord cook_record = 11;
    WireCookTouch cook_touch = 12;
    // #977 Phase 5 / #980 L1 — wrapper asks the daemon's embedded
    // zccache service to run a rustc invocation instead of forking
    // zccache.exe. v6 protocol.
    WireCompileRequest compile = 13;
    // #1286 F1 — checkpoint the embedded zccache state (artifact
    // index, depgraph snapshot, metadata cache) to disk without
    // shutting down. Replies with ack. v8 protocol.
    WireUnit flush_caches = 14;
    // soldr#1368 — read the embedded zccache service's cumulative
    // compile counters (for `soldr session start/end` stats).
    WireUnit compile_stats = 15;
    // v19 / soldr#1814 slice 2a — ask the daemon for the build-log inputs it
    // already owns, instead of the CLI opening state.redb itself.
    WireBuildLogInputsRequest build_log_inputs = 16;
    WireShouldWarnCargoDebugDefault should_warn_cargo_debug_default = 17;
    WireBuildLogHistoryUpdate attach_build_log_history = 18;
    // v22 / soldr#2251: daemon-owned target-registry snapshot and cleanup.
    WireUnit list_target_registry = 19;
    WireRemoveTargetRegistry remove_target_registry = 20;
  }
}

message WireBuildLogInputsRequest {
  uint64 session_id = 1;
}

message WireRemoveTargetRegistry {
  repeated string paths = 1;
}

message WireShouldWarnCargoDebugDefault {
  string repo_root = 1;
}

message WireBuildLogHistoryUpdate {
  uint64 session_id = 1;
  string repo_root = 2;
  int64 started_at_ms = 3;
  int64 ended_at_ms = 4;
  int32 exit_code = 5;
  bool daemon_finalized = 6;
  optional WireBuildCacheSummary cache_summary = 7;
  repeated WireBuildMissReason miss_reasons = 8;
  optional WireBuildLogPaths log_paths = 9;
}

message WireUnit {}

message WireRecordTargetTouch {
  string path = 1;
  int64 unix_seconds = 2;
}

message WireBuildSessionStart {
  uint64 session_id = 1;
  string repo_root = 2;
  int64 started_at_ms = 3;
}

// Request-response since v13 (soldr#1536): the daemon finalizes the
// session from its in-memory per-session aggregate and replies with
// ack once the BuildRecord and staged session events are durable.
message WireBuildSessionEnd {
  uint64 session_id = 1;
  int32 exit_code = 2;
  int64 ended_at_ms = 3;
}

message WireListBuilds {
  uint32 limit = 1;
  optional int64 since_ms = 2;
}

message WireListSlowBuilds {
  uint64 threshold_ms = 1;
  uint32 limit = 2;
}

message WireCookLookup {
  bytes recipe_hash = 1;  // exactly 32 bytes; validated on decode
  string target_triple = 2;
  string profile = 3;
  string channel = 4;
  string rustc_version = 5;
  optional string origin_url_normalized = 6;
  repeated string branch_lineage = 7;
}

message WireCookRecord {
  bytes recipe_hash = 1;  // exactly 32 bytes
  string target_triple = 2;
  string profile = 3;
  string channel = 4;
  string rustc_version = 5;
  bytes sha256 = 6;       // exactly 32 bytes
  uint64 size_bytes = 7;
  optional string origin_url_normalized = 8;
  string cook_cmd_summary = 9;
  optional string branch_name = 10;
}

message WireCookTouch {
  bytes sha256 = 1;       // exactly 32 bytes
}

message WireEnvEntry {
  string key = 1;
  string value = 2;
}

message WireCompileRequest {
  repeated string args = 1;
  string cwd = 2;
  repeated WireEnvEntry env = 3;
  bytes stdin = 4;
  optional WireCompileLifecycle lifecycle = 5;
  uint32 ipc_busy_retries = 6;
}

// soldr#1537: compile telemetry rides the existing compile connection.
message WireCompileLifecycle {
  uint64 session_id = 1;
  string crate_name = 2;
  string target_dir = 3;
  int64 started_at_ms = 4;
}

// =========================================================================
// Response (daemon -> client)
// =========================================================================

message WireResponse {
  oneof kind {
    WireStatusInfo status = 1;
    WireShuttingDown shutting_down = 2;
    WireBuilds builds = 3;
    string error = 4;
    WireCookHit cook_hit = 5;
    WireCookMiss cook_miss = 6;
    WireUnit ack = 7;
    // #977 Phase 5 / #980 L1 — original single-frame Compile reply.
    // Deprecated in v7 (#983 Phase 5b): the daemon no longer emits
    // this variant; the wire-format slot is reserved for one release
    // cycle so an in-flight v7-vs-v6 mismatch surfaces as a
    // version-mismatch error rather than a silent oneof mis-decode.
    // Plan to remove the tag in v8.
    WireCompileResponse compile_response = 8;
    // #983 Phase 5b — streaming Compile reply: zero or more
    // {stdout,stderr} chunk frames followed by exactly one
    // WireCompileDone frame to close the call.
    WireCompileStdoutChunk compile_stdout_chunk = 9;
    WireCompileStderrChunk compile_stderr_chunk = 10;
    WireCompileDone compile_done = 11;
    // soldr#1368 — reply to compile_stats.
    WireCompileStats compile_stats = 12;
    // soldr#1735 — bounded admission is full; retry without restart.
    WireBackpressure backpressure = 13;
    // v17 — structured result for flush_caches.
    WireCacheFlush cache_flushed = 14;
    // v19 / soldr#1814 slice 2a — build-log inputs served by the daemon so
    // the CLI stops opening the daemon tables in state.redb directly.
    WireBuildLogInputs build_log_inputs = 15;
    // v19 / soldr#1814 slice 2c — reply to should_warn_cargo_debug_default.
    // Tag 16 has existed in the Rust schema since v19; it was never recorded
    // here, so this file understated the used tag space (soldr#1838).
    WireCargoDebugWarning cargo_debug_warning = 16;
    // v20 / soldr#1838 Phase 2 — the daemon is retiring and will not serve
    // this request. Distinct from `error`, which means the daemon answered
    // and something went wrong inside it: a retiring daemon is behaving
    // correctly and the client should degrade to direct rustc rather than
    // fail the build.
    WireRetiring retiring = 17;
    // v21 / soldr#2023 — acknowledgement for BuildSessionStart carrying the
    // daemon's applied compile limit. Replaces the bare `ack` on that one
    // request so the front door learns the running daemon's limit without a
    // second round-trip on a path with a documented fixed-overhead problem
    // (#1843).
    WireBuildSessionStarted build_session_started = 18;
    // v22 / soldr#2251: target-registry rows are owned by the daemon.
    WireTargetRegistryRows target_registry_rows = 19;
    WireTargetRegistryRemoved target_registry_removed = 20;
  }
}

// soldr#1838. Carries no payload today; a message rather than a bare bool so
// a reason or ETA can be added later without consuming another oneof slot.
message WireRetiring {}

// soldr#2023. The compile limit the answering daemon is actually running
// with, so a client whose own resolution differs can say so.
message WireBuildSessionStarted {
  uint32 compile_jobs = 1;
  string compile_jobs_source = 2;
}

// soldr#1814 slice 2a. `record` is absent when the daemon has no row for the
// session, which is not an error — the CLI renders the log without it.
message WireBuildLogInputs {
  repeated WireEvent events = 1;
  optional WireBuildRecord record = 2;
}

message WireTargetRegistryRow {
  string path = 1;
  int64 last_used = 2;
}

message WireTargetRegistryRows {
  repeated WireTargetRegistryRow rows = 1;
}

message WireTargetRegistryRemoved {
  uint32 removed = 1;
}


message WireShuttingDown {
  uint32 pid = 1;
  // v18 — process-start generation echoed by WireStatusInfo.
  uint64 generation = 2;
}

message WireCacheFlush {
  bool complete = 1;
  bool pending_writes_drained = 2;
  bool index_writer_drained = 3;
  repeated WireCacheFlushStep steps = 4;
  uint64 artifact_entries = 5;
  uint64 metadata_entries = 6;
}

message WireCacheFlushStep {
  string step = 1;
  string status = 2;
  optional string error = 3;
}

message WireCompileStats {
  uint64 total_compilations = 1;
  uint64 cache_hits = 2;
  uint64 cache_misses = 3;
  uint64 non_cacheable = 4;
  uint64 compile_errors = 5;
  uint64 time_saved_ms = 6;
  WireStagedProfile staged_profile = 7;
}

message WireBackpressure {
  uint32 retry_after_ms = 1;
}

message WireStagedProfile {
  map<string, uint64> counters = 1;
  map<string, uint64> timings_ns = 2;
  map<string, uint64> bytes = 3;
  map<string, uint64> failures = 4;
}

message WireCompileResponse {
  int32 exit_code = 1;
  bytes stdout = 2;
  bytes stderr = 3;
  bool cached = 4;
  // 1=Hit, 2=Miss, 3=Error. Mirrors zccache::embedded::CacheOutcome.
  int32 cache_outcome = 5;
}

// #983 Phase 5b — one slice of rustc's stdout pipe. Each chunk caps at
// `CHUNK_BYTES` (64 KiB) so peers hold at most one chunk in memory.
message WireCompileStdoutChunk {
  bytes bytes = 1;
}

// #983 Phase 5b — one slice of rustc's stderr pipe; same shape and cap
// as WireCompileStdoutChunk.
message WireCompileStderrChunk {
  bytes bytes = 1;
}

// #983 Phase 5b — closes the streaming Compile reply. The wrapper-side
// reader loops on read_frame_async until it sees this variant.
message WireCompileDone {
  int32 exit_code = 1;
  bool cached = 2;
  // 1=Hit, 2=Miss, 3=Error. Mirrors zccache::embedded::CacheOutcome.
  int32 cache_outcome = 3;
  string compile_id = 4;
}

message WireBuilds {
  repeated WireBuildRecord items = 1;
}

message WireCookHit {
  bytes sha256 = 1;
  string path = 2;
  uint64 size_bytes = 3;
  optional string origin_url_normalized = 4;
  bytes matched_recipe_hash = 5;  // empty when unavailable
  bool exact_recipe_match = 6;
  optional string branch_name = 7;
}

message WireCookMiss {
  // Each element is exactly 32 bytes.
  repeated bytes previous_origin_recipe_hashes = 1;
}

// =========================================================================
// Shared persistent + status structures
// =========================================================================

message WireStatusInfo {
  uint32 version = 1;
  uint32 pid = 2;
  uint64 uptime_secs = 3;
  uint64 request_count = 4;
  // Field 5 (linked_zccache) deleted in soldr#1467.
  reserved 5;
  optional WireCookStats cook_stats = 6;
  // #977 / #980 L1 — always "embedded". The legacy "wrapped"
  // fork-zccache.exe backend was removed in #980 L1's second pass;
  // field retained for telemetry stability.
  string compile_backend = 7;
  optional WireIpcBurstStats ipc_burst_stats = 8;
  // v18 — process-start generation shared with WireShuttingDown.
  uint64 generation = 9;
  // v21 (soldr#2023) — the compile-concurrency limit this daemon actually
  // applied at startup, and the precedence tier it came from. Reported
  // rather than re-resolved: the point is to expose a limit that has
  // drifted from what the environment now resolves to.
  uint32 compile_jobs = 10;
  string compile_jobs_source = 11;
}

message WireIpcBurstStats {
  uint64 accepted = 1;
  uint64 queued = 2;
  uint64 backpressured = 3;
  uint64 busy_retries = 4;
  uint64 queue_high_water = 5;
}

message WireCookStats {
  uint64 entries = 1;
  uint64 total_bytes = 2;
  uint64 hits_this_session = 3;
}

message WireBuildRecord {
  uint64 session_id = 1;
  string repo_root = 2;
  int64 started_at_ms = 3;
  optional int64 ended_at_ms = 4;
  optional int32 exit_code = 5;
  optional uint64 total_wall_ms = 6;
  uint32 crate_count = 7;
  optional uint64 slowest_crate_us = 8;
  optional string slowest_crate_name = 9;
  optional WireBuildCacheSummary cache_summary = 10;
  optional WireBuildLogPaths log_paths = 11;
  repeated WireBuildMissReason miss_reasons = 12;
}

message WireBuildCacheSummary {
  uint64 hits = 1;
  uint64 misses = 2;
  uint64 non_cacheable = 3;
  uint64 errors = 4;
  uint64 compilations = 5;
  uint64 time_saved_ms = 6;
}

message WireBuildLogPaths {
  optional string zccache_session_id = 1;
  optional string cache_dir = 2;
  optional string session_log_path = 3;
  optional string journal_path = 4;
  optional string session_stats_path = 5;
  optional string compile_journal_path = 6;
  optional string archived_session_log_path = 7;
  optional string archived_journal_path = 8;
  optional string archived_session_stats_path = 9;
  optional string archived_compile_journal_path = 10;
  optional string private_daemon_name = 11;
}

message WireBuildMissReason {
  string reason = 1;
  uint64 count = 2;
}

// =========================================================================
// Redb-only persistent rows (issue #580 tagged-byte migration target)
// =========================================================================

message WireEvent {
  int64 ts_ms = 1;
  optional uint64 session_id = 2;
  // 0=SessionStart, 1=SessionEnd, 2=CompileStart, 3=CompileEnd.
  uint32 kind = 3;
  optional string crate_name = 4;
  optional uint64 duration_us = 5;
  optional string target_dir = 6;
  optional int32 exit_code = 7;
}

message WireCookKey {
  bytes recipe_hash = 1;  // exactly 32 bytes
  string target_triple = 2;
  string profile = 3;
  string channel = 4;
  string rustc_version = 5;
}

message WireCookEntry {
  bytes sha256 = 1;       // exactly 32 bytes
  uint64 size_bytes = 2;
  uint64 created_unix_ms = 3;
  uint64 last_used_unix_ms = 4;
  optional string origin_url_normalized = 5;
  string cook_cmd_summary = 6;
  optional string branch_name = 7;
}
