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

nordisk/edda

.nornir/build-info-design.md

build-info — the second half of a build (schema + emitter)

A build produces two things at once: the thing (artifact bytes, hashed by BuildProvenance and — a later slice — stored in holger's znippy CAS) and the build info (the columnar record ABOUT the build). This note wires the second half in nornir-build-thing: the §f3 Arrow schema, a flat dep-light row, and a pluggable BuildInfoSink with an in-process default. The full constellation design (Flight seam, at-rest seal, jera DAG) is workspace_nordisk/.nornir/ build-info-flight-stream-design.md; this is the build-thing-local slice 2.

The schema (§f3) — one row, six kinds

Build info is inherently record-shaped, so it IS columnar. One row shape (build_info::BuildInfoRow), discriminated by the kind column, carries the whole stream — the Artifactory-parity manifest rows (provenance / dep / artifact, derived from BuildProvenance) AND the live feed (log / metric / step). Columns, in declared order:

build_id (Utf8) · step_id (Utf8) · ts_micros (Int64) · kind (Utf8) · level (Utf8) · name (Utf8) · value (Utf8) · content_id (Utf8) · blob (Binary, the only nullable column).

Dep-light by construction

The rows + the sink are pure std + serde — a leaf repo's xtask gets the emitter for free. The Arrow RecordBatch binding (build_info_schema() / build_info_batch()) is gated behind the build-info-arrow feature, so the default crate keeps its no-heavy-deps promise. The feature reuses the arrow builder plumbing (arrow-array / arrow-schema, the crates + version znippy pins) rather than re-rolling arrow; znippy's own build_metadata_batch builds a different schema (blob-index), so only the plumbing is shared, not the shape.

The sink seam

BuildInfoSink is transport-agnostic (emit(&[BuildInfoRow])):

The emitter

build_variant_with_info(set, name, repo_root, sink) runs the same build as build_variant (which now just routes to a NullBuildInfoSink, so callers are unchanged) and emits: the live feed (step/log/metric for the cargo-build step) as it finishes, then the manifest rows from the captured BuildProvenance. Emission is best-effort — a sink error never fails the build.

Tested (RED-when-broken)

Slice 3 — native warehouse landing + at-rest seal (shipped)

The build-info feed no longer rides test_results as an interim; it lands in its OWN warehouse table with its OWN submit path, and seals at rest:

Guarded: submit_build_info_and_provenance_land_natively (tests/data_submit_rpcs.rs) submits real rows to an ephemeral server and reads the exact values back over Warehouse.Scan; warehouse::build_info unit tests round-trip the manifest + the nullable blob; seal_build_info_writes_znippy_ beside_artifact proves the seal. The root build-info-arrow feature forwards to nornir-build-thing/build-info-arrow, and tests/cargo_features_guard.rs fails RED if any feature ever references an undeclared dep:.

Slice 4 — jera pipeline-DAG sub-jobs (§f5, shipped)

A build/pipeline is not one job — it is a root jera job whose body is a DAG, and every step is a sub-job. Modelled in nornir::build::pipeline:

Guarded (src/build/pipeline.rs, RED-when-broken): three_node_dag_makes_root_plus_three_subjobs_with_needs_and_status (a fetch → compile → test DAG creates 1 root + 3 sub-jobs on the ledger with the right needs edges + per-step done), failed_step_blocks_dependents_and_fails_root (compile fails → test blocked → root failed), single_step_is_a_one_node_dag_additive, build_info_stream_filters_per_step_id_to_each_subjob, cyclic_pipeline_is_rejected, diamond_dag_reuses_funnel_core_critical_path. Emits nornir_testmatrix::functional_status rows for the three headline surfaces.

Slice 5 — the Flight-backed sink + consumer (§f1/§f2/§f5, shipped)

The arc closes: the same build-info stream is now in-process (default) OR Flight (remote) behind the ONE BuildInfoSink trait, exactly as the design says.

Measured against the tree, both sides are arrow 59: nornir-flight/Cargo.toml:42-44 declares arrow-array/arrow-schema/ arrow-flight all "59" on tonic 0.14, nornir-build-thing:109-110 declares arrow-array/arrow-schema "59", and edda's Cargo.lock carries exactly one arrow family — 59.2.0, arrow-flight 59.2.0 included. The tonic 0.13/0.14 split that forced the gap resolved itself when nornir-service moved to 0.14.

rows_to_batch / batch_to_rows stay, and are not dead code: converting BuildInfoRow to a RecordBatch is the serialization layer, needed at any arrow version. What changed is the REASON — they are no longer a bridge across incompatible majors, so nobody should preserve them as one or go looking for the arrow-56 half. The test pinning their column shape against the canonical build_info_schema still earns its keep: it is now a same-major drift guard.

Guarded (src/build/flight_sink.rs, RED-when-broken): flight_schema_matches_canonical_f3_columns (the §f3 shape + no-drift), batch_round_trips_rows_byte_identical (arrow-56 columnize/decode lossless incl. the NULL-vs-present blob), flight_sink_ships_and_consumer_receives_byte_identical (a mixed manifest+feed stream shipped over do_put, consumed per route key over do_get, byte-identical), auth_is_enforced_on_the_build_info_wire (no bearer ⇒ do_put/do_get rejected), and emits_functional_rows_for_flight_build_info (feature testmatrix — honest do_put/do_get matrix rows).

Slice 5 tail — the viz build-info LIVE-TAIL pane (shipped)

The arc's last open thread — surfacing the received stream in the viz — is now wired: a build-info live-tail pane in the 📡 Live Run area subscribes the §f3 stream per sub-job step_id and shows the rows arriving as they land.

Guarded (RED-when-broken): ingest_grows_tail_and_tracks_status + manifest_rows_counted_not_tailed + auto_refresh_drains_inbox_and_advances_ticks (a simulated interval pull GROWS the tail + ticks poll_count, no re-inject) + empty_interval_pull_is_not_a_tick (src/viz/build_info_live.rs); build_info_pane_tails_feed_per_subjob + build_info_pane_auto_updates_on_poll_timer (the REAL app auto-folds delivered batches across egui frames) + poller_pulls_live_flight_rows_across_intervals (feature build-info-flight — the poller re-pulls a live edda/nornir-flight server across intervals, deduped by the high-water mark) + a wgpu snapshot (viz_snapshot_build_info_live) (tests/viz_build_info_live.rs). Emits nornir/viz/build-info-live functional_status rows (feature testmatrix).

Deferred (later slices)

dep rows currently carry the enabled features (the consumed inputs present on BuildProvenance); a resolved crate-graph dependency row is a later slice. (Continuous auto-refresh of the live-tail pane — the timer that re-pulls each ticket while the pane is open — is now shipped; see the "Slice 5 tail" section above.) run_on_ledger runs steps sequentially in topo order today (each wave's dependents wait on the prior); parallel intra-wave execution of independent sub-jobs is a later refinement (the PlanDag already exposes the waves via depths()).