hive-c0re: full build-log capture to sqlite, drop 32-line stderr ring (#726 phase 1)
Backend half of #726. The 32-line stderr ring buffer in `lifecycle::run` and `lifecycle::prebuild_toplevel` routinely truncated the actual eval error — a single 'tried alternatives' block out of a failing module ref is often 30+ lines on its own, which pushed the real cause out of the bailout message. With this patch the full stream lands in sqlite where the dashboard can surface it; bail-outs now point at the build log id instead of an arbitrary tail. ### New module: `hive-c0re::build_logs` `BuildLogs::open(db_path)` creates a sqlite db at `<db_path>/build_logs.sqlite`. Schema: id, agent, kind, cmdline, started_at, finished_at, status, stdout, stderr — indexed for both per-agent latest-N queries and the status-driven retention sweep. API: `start / append_stdout / append_stderr / finish` for the streaming writer side (best-effort — every append handles sqlite errors via tracing::warn so a transient blip never tears down a rebuild), plus `list_recent_for_agent / get_full` for the read side (50-row cap clamped server-side). ### Process-singleton handle `build_logs::install / global()` install the `Arc<BuildLogs>` at `Coordinator::open` so `lifecycle::run` and `lifecycle::prebuild_toplevel` can write without us threading the handle through every `pub async fn` entry point in the lifecycle surface — there are 10+ call sites and the handle is the same Arc everywhere anyway. Reads via `global()` return None in early-startup / standalone-test paths so callers no-op cleanly. ### Lifecycle integration `run` derives the kind from `args[0]` (the nixos-container verb) and the agent name from `args[1]` (stripped of the `h-` agent prefix so dashboard grouping matches the bare agent name). It opens a row before spawning, pipes stdout/stderr into both tracing AND the row, then `finish`es with the terminal status. `prebuild_toplevel` does the same with kind = "prebuild" and the agent name already in scope from its caller. On failure both bail with "see build log #<id>" instead of the ring-buffer tail. ### Retention `spawn_vacuum` mirrors `stats_vacuum`/`events_vacuum` in shape — hourly tick that calls `BuildLogs::vacuum()`. Rule: failures kept 30d (operators dig into them), successes 24h (mostly noise after a day), in-flight rows never reaped regardless of age (running builds shouldn't disappear from their own log viewer mid-stream). ### Out of scope (follow-ups) - Dashboard endpoints (`GET /api/build-logs/{agent}`, `GET /api/build-logs/{id}`) — wire layer - ContainerView.build_logs field — agent-card chip data source - Side-panel viewer + SSE `build_log_appended` event — UX - Download-as-text link — operator workflow polish These all stack cleanly on top of the data layer + writer this PR ships. Filing as phase 2 PRs. ### Validation - 5 new unit tests pass (start/append/finish flow, list ordering + clamp, get_full miss, vacuum per-status rule, post-finish append fault tolerance) - 157 hive-c0re lib tests pass overall - cargo check workspace clean Refs #726.
This commit is contained in:
parent
61aed469c9
commit
f1d2063a84
5 changed files with 581 additions and 43 deletions
|
|
@ -41,6 +41,13 @@ pub struct Coordinator {
|
|||
/// internal mutex; the worker drains due rows and the manager
|
||||
/// handlers insert / cancel through the same handle.
|
||||
pub scheduled_prompts: Arc<crate::scheduled_prompts::ScheduledPrompts>,
|
||||
/// Full build-log capture. `lifecycle::run` /
|
||||
/// `lifecycle::prebuild_toplevel` `start()` a row per attempt,
|
||||
/// pipe every stdout/stderr line into it, and `finish()` it on
|
||||
/// child exit. Dashboard reads it via `list_recent_for_agent` /
|
||||
/// `get_full` for the per-card chip + side-panel viewer. See
|
||||
/// `build_logs.rs` for retention.
|
||||
pub build_logs: Arc<crate::build_logs::BuildLogs>,
|
||||
/// URL of the hyperhive flake (no fragment). Inlined into per-agent
|
||||
/// `flake.nix` files as `inputs.hyperhive.url`.
|
||||
pub hyperhive_flake: String,
|
||||
|
|
@ -213,6 +220,14 @@ impl Coordinator {
|
|||
let questions = OperatorQuestions::open(db_path).context("open operator_questions")?;
|
||||
let scheduled_prompts = crate::scheduled_prompts::ScheduledPrompts::open(db_path)
|
||||
.context("open scheduled_prompts")?;
|
||||
let build_logs = Arc::new(
|
||||
crate::build_logs::BuildLogs::open(db_path).context("open build_logs")?,
|
||||
);
|
||||
// Install the process-wide handle so `lifecycle::run` /
|
||||
// `lifecycle::prebuild_toplevel` can write without us having
|
||||
// to thread an `Arc<BuildLogs>` through every public entry
|
||||
// point in the lifecycle surface.
|
||||
crate::build_logs::install(build_logs.clone());
|
||||
let (dashboard_events, _) = broadcast::channel(DASHBOARD_CHANNEL);
|
||||
let (shutdown_tx, _) = watch::channel(false);
|
||||
Ok(Self {
|
||||
|
|
@ -220,6 +235,7 @@ impl Coordinator {
|
|||
approvals: Arc::new(approvals),
|
||||
questions: Arc::new(questions),
|
||||
scheduled_prompts: Arc::new(scheduled_prompts),
|
||||
build_logs,
|
||||
hyperhive_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
|
|
|
|||
Loading…
Reference in a new issue