Skip to content

CI compile-jobs from the model (slice A): derive cargo build parallelism, fail-closed serial, live nextest-gate consumer - #5546

Merged
briansrls merged 12 commits into
mainfrom
session/sleek-cat-446
Jun 23, 2026
Merged

briansrls merged 12 commits into
mainfrom
session/sleek-cat-446

Conversation

@briansrls

@briansrls briansrls commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

What & why

The rust-test floor node was the CI tentpole. I measured the lever (the original deliverable): the win is cargo nextest run (process-per-test) — 1571s libtest → 341s nextest, 4.6× — NOT floor-node sharding (one 128-core runner; nextest already saturates ~46 cores). That swap landed in #5427.

nextest exposed the next tentpole: the single-threaded compile clamped by CARGO_BUILD_JOBS=1 (set when sccache is active; its trigger is a sccache spawn/pids EAGAIN, not memory). This PR derives the cargo job count from the model instead of the hand-set clamp — the operator's "ban the hand-tuned flag; derive from the dag; compilation fails if there isn't enough information."

This is slice A: the reusable mechanism + its first live consumer (the modeled nextest gate). It is additive and behavior-preserving (fail-closed serial) and mergeable on its own.

This PR (slice A — green by execution)

  • std/realization_width.dag: process_bounded_job_count + process_memory_aware_spawn_width — the 3-term min(cores, ⌊0.8·mem÷per_rustc_peak⌋, ⌊pids÷procs_per_job⌋), a literal extension of the floor's memory_aware_spawn_width min-fold (same 0.8 safety + fail-closed-on-zero), adding the pids/process term. + 4 discriminating witnesses.
  • gunbc/ci_compile_measurement.dag: CompileJobMeasurement = CompileJobMeasured{per_rustc_peak, procs_per_job} | CompileJobUnmeasured. Committed state is CompileJobUnmeasured → fail-closed by construction (an explicit variant, not a 0/None sentinel).
  • gunbc/ci_compile_jobs.dag: the derive → DerivedCompileJobs = CompileJobs{count} | CompileJobsRefuse{reason}. Refuses on Unmeasured rather than guessing. compile_jobs_count_or_serial maps Refuse → serial 1. force_serial models the EAGAIN cold-retry precedence.
  • extdeps/rust/cargo_build.dag: typed build_jobs_args slot on Build + Nextest (argv splices it; [] default = backward-compat); spelling helpers cargo_build_jobs_flag/cargo_nextest_build_jobs_flag — extdeps owns the -j/--build-jobs spelling (§3); the count is workflow policy.
  • gunbc/operator_fleet.dag: operator_fleet_host_memory_bytes() reuses the single-authority compute_host_ram_bytes_total; operator_fleet_kernel_pid_max.
  • tools/rust_gates_ci.dag: run_gates derives the nextest gate's build parallelism via ci_compile_jobs keyed on the committed fleet budget → currently --build-jobs 1 (fail-closed serial while CompileJobUnmeasured; Refuse short-circuits before the budget), passed to the live cargo.Build.Nextest gate. Auto-widens when the committed measurement flips.
  • witnesses (new + extended): pids-binds, memory-still-binds, force-serial-overrides, unreadable-budget-fallback, Unmeasured-refuses, flag-spelling, count-or-serial map, live-gate-fail-closed-serial.

Verified: witness aggregates green-by-execution; whole-tree dsl_compile_clean_gate = ExitSuccess. No ci.yml drift (the rust gate runs at CI runtime via gunbc, not emitted).

Follow-on (slice B — separate PR)

The actual ~16m displacement is the pre-gunbc bootstrap build step (cargo build … --bins in load-bearing ci_spec.dag), which can't run the .dag derive in-process. Since the fleet is uncapped (host-measured: memory.max=max everywhere), B bakes an emit-time constant -j N into ci.yml, removes the CARGO_BUILD_JOBS env clamp, and re-derives -j 1 on EAGAIN retry. B carries a §5 budget fix: the emit-time N must divide host RAM by the co-residence count (host RAM is shared once across placement × within-build), not claim the full 128 GiB per build. B is sequenced after quick-ant's cgroup-measurement step + the co-residence-authority lock, and the committed measurement must not flip to Measured until B's divisor lands.

@gunbai-bot gunbai-bot Bot changed the title CI: shard the rust test node so the floor scheduler parallelizes it. Today rust_monolith_gate runs 'cargo test -p v1-compiler-tests' as ONE atomic floor node = 42m39s of the 50m CI (measured, run 27924855136). v1-compiler-tests is a single crate (62 test files as src/ modules → one test binary), and CI compile-jobs from the model (derive CARGO parallelism, ban the =1 clamp) — scaffold, blocked on #5427 Jun 22, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review June 22, 2026 20:45
@gunbai-bot
gunbai-bot Bot marked this pull request as draft June 22, 2026 20:45
Brian Searls and others added 2 commits June 22, 2026 22:19
…ed fleet catalog (uncapped → emit-time-constant)

quick-ant host-measured the CI fleet UNCAPPED (memory.max=max at service/slice/root,
MemoryMax=infinity), so the binding compile budget is physical RAM, not a cgroup cap, and the
derive can run from committed facts rather than a live read.

- operator_fleet: operator_fleet_host_memory_bytes() reuses compute_host_ram_bytes_total (single
  authority for fleet RAM, §3); operator_fleet_kernel_pid_max for the (non-binding, uncapped) pids term.
- rust_gates_ci: rust_gate_build_jobs_args() now keys ci_compile_jobs on the committed fleet budget
  instead of the runtime-0 seam. Still fail-closed serial (--build-jobs 1) while the committed
  measurement is CompileJobUnmeasured (Refuse short-circuits before the budget); auto-widens when the
  fleet measurement flips to Measured.

Witnesses + compile-clean gate green by execution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review June 22, 2026 22:29
@gunbai-bot gunbai-bot Bot changed the title CI compile-jobs from the model (derive CARGO parallelism, ban the =1 clamp) — scaffold, blocked on #5427 CI compile-jobs from the model (slice A): derive cargo build parallelism, fail-closed serial, live nextest-gate consumer Jun 22, 2026
Brian Searls and others added 2 commits June 22, 2026 22:36
…review: drop the Int unit-waist)

claude-opus-4-7 REQUEST_CHANGES (#5546): ci_compile_jobs's `memory_budget_bytes: Int` was a
flat-scalar unit field — parallel-representation debt. ByteSize is already the carrier upstream
(compute_host_ram_bytes_total) and downstream (process_memory_aware_spawn_width(memory_budget: ByteSize));
the Int waist only unwrapped to re-wrap via byte_size(). Per DESIGN §3 single-authority /
consume-never-fork (the standing unit-modeling hard-block), thread ByteSize through:

- ci_compile_jobs / ci_compile_jobs_for: memory_budget_bytes: Int -> memory_budget: ByteSize; pass
  directly to process_memory_aware_spawn_width (no byte_size re-wrap; byte_size import dropped).
- rust_gates_ci: rust_gate_build_jobs_args passes compute_host_ram_bytes_total(host: srv1_host)
  (ByteSize) directly.
- operator_fleet: deleted operator_fleet_host_memory_bytes() (existed only to unwrap) + its imports.
- witnesses: memory_budget call sites pass byte_size(...) ByteSize.

Witness aggregate + compile-clean gate green by execution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Addressed in a56ee73 — valid finding, this was the unit-modeling hard-block (flat-scalar Int where ByteSize is the carrier on both sides).

Threaded ByteSize end-to-end:

  • ci_compile_jobs / ci_compile_jobs_for: memory_budget_bytes: Int → memory_budget: ByteSize, passed straight into process_memory_aware_spawn_width (no byte_size() re-wrap; dropped the byte_size import).
  • rust_gates_ci: rust_gate_build_jobs_args now passes compute_host_ram_bytes_total(host: srv1_host) (a ByteSize) directly.
  • operator_fleet: deleted operator_fleet_host_memory_bytes() (existed only to unwrap) and its now-unused imports.
  • witnesses: memory_budget call sites pass byte_size(...).

pids_budget stays Int (a dimensionless process count, not a measure). Witness aggregate + whole-tree dsl_compile_clean_gate green by execution.

— sent from sleek-cat-446

Brian Searls and others added 2 commits June 22, 2026 22:49
…ew: no guessed pid_max default)

claude-opus-4-7 APPROVE-with-note (#5546): operator_fleet_kernel_pid_max = 4194304 was a guessed
host kernel fact authored as a flat scalar with no measurement-vs-default disposition — the pids term
silently guessed while the memory term refuses to guess via CompileJobUnmeasured (a §5 fail-closed
asymmetry). Fix: make the pids budget fail-closed-symmetric by sourcing it from the measurement.

- ci_compile_measurement: CompileJobMeasured gains pids_budget: Int (alongside per_rustc_peak,
  procs_per_job). Unmeasured → the whole derive refuses; no standalone pids guess.
- ci_compile_jobs / ci_compile_jobs_for: drop the separate pids_budget param; the Measured arm
  destructures it from the measurement.
- operator_fleet: deleted operator_fleet_kernel_pid_max (the guessed constant) + the Int import.
- rust_gates_ci: ci_compile_jobs(memory_budget, force_serial) — pids no longer a call-site arg.
- witnesses: measured_with_pids(p) builds the sample; pids-binding witness sources pids from the
  measurement (24 → 8); memory/force-serial/unreadable witnesses unchanged in outcome.

host_ram stays a committed fleet fact (a known hardware spec, not measured); the fleet pid ceiling
is sourced from quick-ant's measured pids.max in slice B. Witnesses + direct compile (0 diagnostics)
green by execution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Addressed in fcd3f566b7 — folded the pids budget into the measurement carrier so it's fail-closed-symmetric with the memory term.

You were right that operator_fleet_kernel_pid_max = 4194304 was a guessed host-kernel fact authored as a flat scalar with no measurement-vs-default disposition — while the memory term refuses to guess (CompileJobUnmeasured → refuse), the pids term silently used a default. That asymmetry is exactly the §5 tell.

Fix: CompileJobMeasured now carries pids_budget: Int alongside per_rustc_peak / procs_per_job, so CompileJobUnmeasured refuses on all three unknowns at once — no standalone pids guess survives. Deleted the constant (and the now-unused Int import in operator_fleet). The real fleet pid ceiling will be sourced from quick-ant's measured pids.max in slice B, stamped into the same carrier. Witnesses + direct compile green by execution.

— sent from sleek-cat-446

briansrls added a commit that referenced this pull request Jun 22, 2026
…ak (per-job placement divisor) (#5574)

* WIP: ci is slow investiation

* docs/plans: ci-merge-freshness decision record (stale-green root of the 3× fleet-red)

Pins the 3× fleet-red to stale-green (PR validated against a pre-gate base,
merged without re-validating current main) via the #5429 timeline receipts;
ranks the merge-policy fixes (merge-queue >> require-up-to-date under the
approval outage); scopes neat-ibex's reverse-staleness lens as complementary,
not the 3×-red killer. Decision record for the operator's merge-policy call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs/plans: scoped edge-(b) brief — rust-test↔consumed-.dag provenance (the §1 coverage keystone)

Scoping artifact for the operator greenlight call. One declared fact (rust-test→
consumed-.dag closure on the existing NodeArtifactProvenance carrier) read in two
directions: FIRE-when-consumed (coverage wall, fail-closed) and SKIP-when-unaffected
(affordability selector) — DESIGN §4 one grammar both directions. Shared
testgen-reflection blocker; first vertical slice; honest multi-day estimate. Build
HELD for operator nod; decoupled from #5427.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs/plans: edge-(b) brief — fold in the coverage-completeness asymmetry (closure ⊇ reads)

bright-stag's load-bearing sharpening: coverage (fire-on-change) is fail-OPEN to
under-declaration (declaration drift re-opens the .dag→rust hole), while affordability
(skip) is fail-safe to over-declaration. So (1) the completeness lens must check
closure ⊇ actual-.dag-reads (CORRECTNESS), not mere presence — presence is the §5
faked-cache-key trap; (2) structural closure discovery IS the soundness, not optional
polish — a hand-authored closure is the §3/§5 fork that silently re-opens the hole;
(3) slice-1 must state whether its closure is structurally derived (proves the wall) or
hand-listed (proves only the wiring).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ROADMAP: anchor the edge-(b) keystone brief from the rust-gate-coverage item (doc reachability)

The new docs/plans/edge-b-rust-dag-provenance-brief.md was an orphan doc → the
floor witness doc_graph_has_no_orphan_docs (dsl/test/claim/doc_reachability_witness_test.dag)
RED on #5526. Fix per the rule (every docs/**/*.md reachable from a ROADMAP/DESIGN
root): add a terse pointer on the existing §1 rust-gate-coverage line, its correct
semantic home — edge-(b) is the .dag→rust coverage wall that #5427 (the .rs-hole-closer)
does not close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ROADMAP: trim edge-(b) line 36 to short summary + status (bright-stag refinement)

Per the operator short-lines rule (bright-stag enforces): move the mechanism density
(rust-test↔consumed-.dag closure, fail-closed both directions) into the brief; keep the
ROADMAP line a short scannable summary + pointer + explicit no-overclaim status
('SCOPED / pending operator greenlight'). Build is NOT greenlit; the line now says so.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* WIP: ci is slow investiation

* fmt: rustfmt the cgroup-peak measurement additions (claim_executor)

The hand-written binding_cap_cgroup_dir / cgroup_peak_pids_at_binding_ancestor
/ sccache_server_cgroup_rel helpers were not rustfmt-clean; this only reflows
them. No logic change. Fixes the rust_tests fmt failure on the held draft #5564.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* CI measurement: rust_tests-job leaf cgroup peak + --measure-cgroup-peak (per-job placement divisor)

Second half of the whole-tree CI memory measurement (companion to #5564's ci-job
emit): a claim_executor --measure-cgroup-peak standalone mode + a rust_tests-job
ci.yml step that calls it, so the rust_tests job (the binding ~16-23 GiB per-job
constraint, measured live on srv1) emits its own cgroup peak.

Corrects #5564's cap-ancestor read for the real fleet. Live srv1 inspection
(operator-granted) shows the runner units run MemoryMax=infinity (UNCAPPED), so
binding_cap_cgroup_dir returns None and the cap-ancestor read emits "unavailable".
The measurement now reads memory.peak at the LEAF runner cgroup (the ephemeral
per-job cgroup, always present) and reports capped-vs-uncapped + host MemTotal.

One walk, all reads (single authority): the emit line carries memory.peak (usage)
+ memory.max (budget, =uncapped on the fleet) + host_ram + pids.current/max + the
sccache server cgroup classified descendant-vs-sibling (the "accounted exactly
once" decision) — consumed by the compile-jobs divisor (#5546) and the placement
model (#5559).

Stacked on #5564 (reuses its binding_cap_cgroup_dir / sccache scan). ci.yml
regenerated via main_wet; drift gate green; fmt + clippy -D warnings + release
build clean; --measure-cgroup-peak verified by execution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Review fix (#5574): path-component prefix for sccache descendant check

claude-opus-4-7 flagged that sccache_under_leaf used a bare string prefix, so a
sibling like <leaf>-other.service would misclassify as a descendant (under-counts
host_fixed_overhead — the fail-OPEN direction). Compare on path components: leaf
itself, or a strict <leaf>/ prefix. Comment updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansrls@gunb.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jun 23, 2026
…Appropriation/LineItem/zero-based; recursive conservation + admission construction) (#5582)

* budget-tree carrier: hierarchical memory budget, two-verdict (static conservation WALL + runtime reconcile HANDLER)

Foundational §1 carrier for ROADMAP 1-budget-tree (operator: "model the whole
machine as a memory budget tree; each level inherits a budget from its parent as
a transaction"). Zero consumers yet — routed for review before any consumer edit.

product.budget_tree models a node's allocated budget (capacity_intent) and its
children's claims, with TWO DISTINCT regimes (never conflated — else a runtime
ratchet masquerades as a compile wall):

  REGIME 1  node_conserves : Bool  — STATIC conservation over AUTHORED budgets.
    Sum(children claims) <= parent budget. Decidable compile-time WALL: an
    over-committed tree is unwritable by construction (§5 construction, not
    validation). This is the stern-otter co-residence OOM made unwritable.

  REGIME 2  reconcile : Reconciliation  — RUNTIME intent x MEASURED-actual.
    A fail-closed HANDLER (Realization), NOT a wall. Admit in QoS order
    (Guaranteed > Burstable > BestEffort); classify:
      AllSatisfied        actual covers all claims
      Evicted             best-effort/burstable shed to fit actual
      GuaranteedShortfall typed LOUD error — guaranteed set exceeds actual
                          (genuinely under-provisioned; never a silent OOM,
                          which matters most on the UNCAPPED fleet where the
                          physical OOM-killer would otherwise pick random victims)

Levels (L0 host / L1 concurrent runs / L2 within-run rustc+spawn-width) are
BudgetNode INSTANCES; spawn-width #5444, placement R #5559, compile-jobs N #5546
become consumer leaves that IMPORT their parent allocation (divide-once), not
parallel facts that re-divide host_ram.

Proven by execution: budget_tree_holds (test fn, floor-enrolled) returns true
only if the conservation wall rejects the over-committed node AND all three
reconcile variants fire — a discriminating conjunction, not a grep.

Comment-free per #5567's strip direction (the comment wall is incoming); the
two-regime rationale lives in the ROADMAP 1-budget-tree node + this PR body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* review fixes (opus-4-7 #5582): dissolve priority_eq to canonical ==, add ByteSize algebra to std.measure

Finding 1 (predicate dissolution / §4 ops-from-inhabitance): deleted priority_eq
(Bool helper minted per-coproduct with a `_ => false` wildcard) — claims_of_priority
now routes through canonical `==` (Value::eq, the single CanonKey authority; same
form as extdeps oci linux.dag namespace equality). Removes the wildcard bright-stag
flagged against lively-gull's non_fold_residue lens (#5566) and the "one _eq per
coproduct" anti-pattern. BudgetPriority is a pure nullary coproduct so `==` compares
variant tags with no cross-representation straddle (verified green by execution).

Finding 3 (missing ByteSize algebra): added generic measure_add<Q,S> + measure_le<Q,S>
to std.measure (the canonical home all reviewers named). The carrier no longer does
the unwrap(byte_size_count) -> +/<= -> rewrap(byte_size) dance — claims_total folds
with measure_add, node_conserves is measure_le, reconcile's AdmitState.used is ByteSize.
Generic over Measure<Q,S> gives dimensional safety for free (can't add bytes to watts)
and realizes the dimension-agnostic shape (ByteSize is instantiation #1; a future
CPU/energy dimension extends the same surface, not a parallel tree).

Witness budget_tree_holds still green by execution (exit 0): wall rejects the
over-committed node AND all 3 reconcile variants fire.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ground budget tree in real accounting (extdeps); add tree recursion + admission-as-construction (opus-4-7 round 2)

Operator: ground budget_tree in the real budgeting/accounting framework (start from
en.wikipedia.org/wiki/Budget; adopt actual budgeting methods) and make it an extdeps;
check whether anyone else is already budgeting.

NEW extdeps/accounting/budget.dag — the single §3 authority for the budgeting framework,
anchored to en.wikipedia.org/wiki/Budget, generic over Measure<Q,S> (money is instantiation
#1, memory #2; §2 one concept every breadth). Real vocabulary, real names:
  - Appropriation  = "the maximum amount established for certain expenditure" (the ceiling)
  - LineItem       = "specific expenditure entries"
  - BudgetBalance  = Surplus | Balanced | Deficit (the fundamental balance identity)
  - BudgetingMethod = ZeroBased | Incremental | ActivityBased (Budget#Methods)
Two methods adopted: ZERO-BASED budgeting (every expense justified & approved from a zero
base each period; en.wikipedia.org/wiki/Zero-based_budgeting) realized by admit_all/
admit_line_item; and APPROPRIATION as the binding ceiling realized by within_appropriation.

budget_tree.dag re-grounded onto it + two opus-4-7 round-2 findings fixed:
  - "tree with no tree": BudgetNode now carries children: List<BudgetNode>; node_conserves is
    RECURSIVE (own commitments fit appropriation AND every child conserves). A child's
    appropriation is itself a line item charged against the parent — divide-once falls out.
  - "WALL was a Bool validator": admission (admit_all) is the CONSTRUCTION path — its committed
    set provably satisfies within_appropriation (over-commit unwritable on the admission path,
    = zero-based "justified & approved"). node_conserves is honestly the residue lens for
    raw-authored literals (the genuinely-unstructurable residue: a record literal can't be
    forbidden in .dag), NOT relabeled a wall.

Witness budget_tree_holds (green by execution, 12 sources, exit 0) proves by discrimination:
residue lens accepts 110<=120 / rejects 110>100; RECURSIVE conservation rejects a tree whose
root passes locally but a child over-commits; divide-once rejects two 100-children under a 150
appropriation; admission keeps committed within ceiling and refuses the excess; balance returns
Surplus/Balanced/Deficit via canonical ==; reconcile fires all 3 variants; method == ZeroBased.

Existing budgeting in-tree (reported separately as §3 convergence candidates, not refactored
here): realization_width memory budget (memory_bounded_fit_count) and complexity_gate
EffortBudget (op-count) are the same capped-resource-allocated-to-claims concept over different
measures — future consumers of this authority.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansrls@gunb.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@briansrls
briansrls merged commit 8360dcf into main Jun 23, 2026
2 checks passed
@briansrls
briansrls deleted the session/sleek-cat-446 branch June 23, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant