Skip to content

Slice 1: emit per-shard roster_closure — the missing CI-floor calibration datum - #6425

Merged
briansrls merged 9 commits into
mainfrom
session/merry-owl-649
Jul 10, 2026
Merged

briansrls merged 9 commits into
mainfrom
session/merry-owl-649

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

What

Emit roster_closure_nodes — the distinct modules typechecked in a shard's union closure (typecheck_compute_count(), the once-per-node receipt), max-merged across shards — on the [measurement] discovery corpus line. Every floor run now records the pair (roster_closure, per_shard_rss).

Why

The floor's per-shard memory authority is a hand constant (gunbc_ci_floor_per_shard_peak_max, 4.56 GB) that is wrong in two independent ways: it is a width-1 whole-process misattribution (the entire process RSS charged to a single shard), and it was measured on the pre-enrollment-flip ~1000-witness corpus, not the current 8-witness enrolled floor. One constant can't be right because per-shard memory is a function of input size — and today no run records that input size. Per-shard RSS is already emitted in two bases; the node count (the missing half of the calibration pair) was not.

This is Slice 1 of flipping the floor's memory model from predict-a-peak-and-hope to declare-a-limit-and-construct (design: docs/plans/could-we-plan-out-*.md). It ships the sensor everything downstream needs — the decomposed space fit, the contended host→slot→swap budget tree, and admission-by-construction — and it turns "how does contention behave under saturation" from a guess into recorded data.

Safety (DESIGN §5)

Additive and behavior-preserving. No constant is lowered — lowering per-shard memory is the OOM-risk direction, so it waits on a trustworthy current-corpus reading from this sensor plus the Slice-2 fit, never a guess. The stale 4.56 GB constant is re-annotated on its own carrier as debt (gunbc_ci_floor_per_shard_rss_source) with the explicit re-grounding path.

Discriminating RED

merge_discovery_summaries_takes_max_roster_closure — fails if the merge sums the field (would double-count the shared std/spec prefix), drops the merge line (stays 0), or reverts the carrier. Max-merge is correct because parallel shards share the prefix and the heaviest single shard's closure governs the per-shard peak.

Note on the failing checks

The earlier CI red (42bfc38) was an auto-committed mid-implementation snapshot — the DiscoverySummary struct field landed before its three constructor initializers, so the seed didn't compile. The complete change (da3f0ac575) adds the initializers, the per-shard count read, the merge, the emit, and the test; it builds, tests green, and is fmt-clean locally.

@gunbai-bot gunbai-bot Bot changed the title ci floor measurement Slice 1: emit per-shard roster_closure — the missing CI-floor calibration datum Jul 9, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review July 9, 2026 22:34
@gunbai-bot
gunbai-bot Bot marked this pull request as draft July 9, 2026 23:00
@gunbai-bot

gunbai-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

This PR is intentionally held in draft at the operator's request — not abandoned.

It is awaiting sign-off on a model reconciliation for the operator-signed bounded_input_cost_envelope_scheduling.dag (predicted→allowable ceiling; one authored size dimension; std.measure as the concrete byte evaluator; decomposed per-shard fit base + W·shared_prefix + bytes_per_node·roster), plus the Slice 2 implementation that retires the hand-calculated gunbc_ci_floor_per_shard_peak_max constant.

It will be flipped ready once the reconciled doc and Slice 2 land together as one coherent unit ('fully done'). Slice 1 (the roster_closure sensor) is complete and already approved on this branch.

— sent from merry-owl-649

@briansrls
briansrls marked this pull request as ready for review July 10, 2026 12:17
briansrls and others added 5 commits July 10, 2026 12:25
A temporary red probe (union_resolve_receipts_test.rs) used to characterize the
width==1 typecheck-compute-counter semantics was captured by the auto-committer
in 533e473 while it sat in the working tree. It asserts a deliberate RED, so it
fails rust_tests. It was never intended to land.

Restores the file to its state at 674381f.

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

`typecheck_compute_count()` counts typecheck cache MISSES on the current thread and
is never reset in production (only tests reset it), so it equals a closure size only
from a cold start. The measurement window cannot assume that:

  - width == 1 returns `run_discovery_rows` on the discovery thread, which has already
    resolved every changed file via `floor_diff_edits_from_line_ranges` plus both prefix
    entries. The reported count was therefore |closure(changed files) u closure(prefix)
    u closure(rows)| -- a function of the diff under test, not of the roster.
  - `floor_skip_discovery_witness` calls run_discovery_corpus three times on one thread,
    so the 2nd and 3rd calls read a counter warmed by the 1st.

Count the union of authored module names across the graphs the shard actually resolves
(prefix contexts + each roster entry) instead. That is a property of the source closure:
independent of cache warmth, resolve order, and the diff. Prefix modules stay counted --
they are resident for the shard's lifetime, so they belong in the memory pairing.

Note the parallel path was never the problem: run_walk dispatches DiscoveryBatch via
thread::spawn, so shards do start cold. The defect was the pre-row work on the discovery
thread, which shards never repeat.

Adds a discriminating control: a prior same-thread resolve moves the compute counter
(asserted, so the control stays discriminating) but must not move the closure count.

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

gunbai-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 5838dba (and the commits it builds on). The core finding was right and I've taken it: roster_closure_nodes is no longer read from typecheck_compute_count(). It is now the union of authored module names across the graphs each shard actually resolves — its prefix contexts plus each roster entry — via collect_typed_module_names. That is a property of the source closure, so it is independent of cache warmth, resolve order, and the diff under test.

Three corrections to the diagnosis, because they change what the fix has to be.

The stated mechanism doesn't hold. Finding 1 says the width == 1 path "runs on the same executor thread that already paid typechecks for earlier floor batches (compile-clean gate, single-claim gates, etc.)". It doesn't. run_walk (claim_executor.rs:1419–1452) partitions batch units: only SharedClaims go to the main-thread memo path, and everything else — including BatchUnit::Discovery — is dispatched with thread::spawn. The compile-clean gate is a memo unit on the main thread; discovery gets a fresh thread with a cold counter and a cold thread-local typed cache. I chased this exact hypothesis first and falsified it.

The real contamination is upstream of the width branch, on the discovery thread itself. With selection_enabled (CI's plan declares node_frontier_selection: SelectionApplied), floor_diff_edits_from_line_ranges resolves every changed file in the diff as an entry (cli_run.rs:5852), and each entry resolve bumps the counter. At width == 1, run_discovery_rows then returns on that same thread, so the reported value was |closure(changed files) ∪ closure(prefix) ∪ closure(rows)|. The damaging property isn't that it over-counts — it's that the count became a function of the PR's diff: the same roster reported a different number depending on which files the PR touched. There's a second live instance the review missed: floor_skip_discovery_witness calls run_discovery_corpus_with_options three times on one thread, so its 2nd and 3rd reads sit on a counter warmed by the 1st.

The proposed remedy would not have fixed it. "Reset at the start of run_discovery_rows (or snapshot a delta) before witness resolves" fails on set semantics. A reset-or-delta at that boundary counts misses during the row window, and any module already typechecked by the diff-attribution resolves is a cache hit during that window, so it is excluded. The count stays diff-dependent — it just flips from an over-count to an under-count, and it additionally drops the prefix modules, which are resident for the shard's lifetime and therefore belong in the pairing against per-shard RSS. Any counter-derived reading is warmth-dependent by construction; the closure has to be read off the graphs.

On finding 2: it's factually right that each shard resolves the floor runner and entry-selection entries before run_discovery_rows, but I don't think that's over-counting. Those graphs stay resident for the shard's lifetime, so their modules are part of the memory roster_closure_nodes is paired against. The fix keeps them counted — deliberately — by seeding each shard's closure with the prefix contexts.

Receipts: roster_closure_count_is_independent_of_thread_cache_warmth (union_resolve_receipts_test.rs) is the discriminating control — a prior same-thread resolve moves the compute counter (asserted, so the control cannot silently stop discriminating) while the graph-derived closure is unchanged. merge_discovery_summaries_takes_max_roster_closure still guards the max-vs-sum merge. cargo check --workspace --all-targets is clean.

One gap I'm naming rather than hiding: both controls prove the primitive and the merge, not the wiring — that run_discovery_rows reads closure_modules rather than the counter is verified by inspection, not by execution. The end-to-end control is to call run_discovery_corpus_with_options twice on one thread and assert the reported count doesn't collapse on the second call (it would go to ~0 under the old counter). That belongs with Slice 2, which is the first consumer of this datum and where the fit will actually depend on it.

— sent from merry-owl-649

@gunbai-bot

gunbai-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

CI is green on 5838dba2a6 (ci, ci_regen, rust_tests), and the floor run exercised the new code path. The emitted datum, from run 29094764979:

[measurement] discovery corpus: 79 witness(es) (69 skipped), resolve 9204.725ms,
  evalu 89030.495ms, ..., roster-closure 274 nodes (max shard)
[calibration] max-per-shard-peak-rss: 1632723968 bytes at spawn_width=4

So Slice 2 now has a real (roster_closure_nodes, per_shard_rss) pair at spawn_width=4: 274 nodes ↔ 1.52 GiB/shard. 274 is a sane per-shard closure — neither collapsed toward zero nor the whole ~600-module tree.

Being precise about what this receipt does and does not establish, since I named the wiring gap above: it confirms run_discovery_rows emits a plausible closure count on the shipped path, but it is not discriminating between the closure and the old counter. This run is spawn_width=4, and shards genuinely start cold (run_walk spawns them), so the counter would have reported roughly the same 274 here. The two readings only diverge on the width == 1 path and on repeat same-thread calls. The discriminating end-to-end control — invoke run_discovery_corpus_with_options twice on one thread and assert the second call doesn't collapse toward zero — still belongs with Slice 2, which is the first consumer whose fit actually depends on the datum.

Also worth flagging for Slice 2's fit: the RSS half here is VmHWM / spawn_width, a whole-process peak divided by width. At width == 1 that divisor makes the RSS reading whole-process (base included), while the node reading is one shard's closure. The two halves are on different bases at width == 1, which is independent of the counter bug this PR fixes and is exactly what the STALE-PENDING-RECALIBRATION marker in ci_floor_measurement.dag warns about. Fitting base + bytes_per_node·roster across width == 1 and width > 1 without reconciling that will push the base term into the slope.

— sent from merry-owl-649

@briansrls
briansrls merged commit b9fdf01 into main Jul 10, 2026
3 checks passed
@briansrls
briansrls deleted the session/merry-owl-649 branch July 10, 2026 16:40
gunbai-bot Bot pushed a commit that referenced this pull request Jul 10, 2026
…measured cost, 103 -> 67 check fns (operator ruling 2026-07-10)

Measured on run 29108981678 (ci job 1h0m50s): batch-2's 103 witnesses cost 15m06s, of which ONE witness (dag_collect_fingerprint_witness_keystone_holds) was 8m38s, five heavies ~2.7m, and the complexity-lens ~12s-eval family ~3.5m; the ~80-witness tail costs under a minute combined (p50=294ms). Cut by cost, not count:

- unenroll dag_collect_fingerprint_witness_test (518s keystone, #6425 lane)
- drop parse_table_memo_warm_equals_cold_content_hash (36s; the row's 4 governance REDs stay)
- trim the five complexity/body-lowering suites (mine) from 39 fns to RED control + flagship positive each (10 fns)
- KEPT: enforcement_live_witness_test (84s) and budget_roster_completeness (17s) - the section-6 lenses-must-be-live backstops that keep every other lens honest

Every trimmed fn stays in its witness file and still runs on the local discovery path (claim_batch --roster-from-discovery); existence != enrollment. Receipts: main_wet regen zero artifact drift (witness entries are projected from the roster at floor runtime, not baked into ci.yml), drift gate PASS, kept-witness sanity run PASS.

Expected effect: batch-2 15m -> ~2.5m. Remaining non-witness costs flagged for their own lanes: release-bin build (26m52s on any PR carrying .rs changes) and batch-4 emit_determinism gate (13m31s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jul 10, 2026
… cost (103 -> 67 check fns) (#6437)

* WIP: complexity

* CI witness cost backout: trim the floor's enrolled witness roster by measured cost, 103 -> 67 check fns (operator ruling 2026-07-10)

Measured on run 29108981678 (ci job 1h0m50s): batch-2's 103 witnesses cost 15m06s, of which ONE witness (dag_collect_fingerprint_witness_keystone_holds) was 8m38s, five heavies ~2.7m, and the complexity-lens ~12s-eval family ~3.5m; the ~80-witness tail costs under a minute combined (p50=294ms). Cut by cost, not count:

- unenroll dag_collect_fingerprint_witness_test (518s keystone, #6425 lane)
- drop parse_table_memo_warm_equals_cold_content_hash (36s; the row's 4 governance REDs stay)
- trim the five complexity/body-lowering suites (mine) from 39 fns to RED control + flagship positive each (10 fns)
- KEPT: enforcement_live_witness_test (84s) and budget_roster_completeness (17s) - the section-6 lenses-must-be-live backstops that keep every other lens honest

Every trimmed fn stays in its witness file and still runs on the local discovery path (claim_batch --roster-from-discovery); existence != enrollment. Receipts: main_wet regen zero artifact drift (witness entries are projected from the roster at floor runtime, not baked into ci.yml), drift gate PASS, kept-witness sanity run PASS.

Expected effect: batch-2 15m -> ~2.5m. Remaining non-witness costs flagged for their own lanes: release-bin build (26m52s on any PR carrying .rs changes) and batch-4 emit_determinism gate (13m31s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jul 11, 2026
Keep main's space-lens design doc with interim #6425 calibration appendix; retain S2a discovery-flip imports (ci_spec_with_discovery_scan_dirs) and drop removed RustMonolithGate.

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls added a commit that referenced this pull request Jul 11, 2026
…-identical plan receipt) (#6444)

* WIP: Re-land affected-set CI enrollment flip once shard resolve footprint is

* WIP: Re-land affected-set CI enrollment flip once shard resolve footprint is

* Fix CI OOM: pin discovery corpus spawn_width_cap to 1.

Run 28999086030 OOM-killed at width=2 on the 24GiB live slot during the
discovery-flip corpus batch. Gate workloads still fit at width=2; only the
tree-wide discovery batch serializes via spawn_width_cap=1, with a receipt
witness and dissolve-on note.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix type error in corpus discovery spawn width cap helper.

Both if-branches must return Nat (hardware_thread_count_value), not a
bare Int literal.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Merge affected-set enrollment flip (takeover of PR #6403 from session/gentle-stag-677-flip)

Conflict resolutions onto post-#6422/#6431/#6432/#6435 main:
- ci_witness_optin_inversion: main's typed Scaffold Disposition -> Terminal (dissolve fired at the flip; roster stays as explicit-entry home, exclusivity dissolved)
- floor step timeout: 45 (main) vs 60 (flip) -> 60 with provenance note
- ci_spec notes: kept flip notes, stale fixed-8 phrasing dated
- falsifier width note: + RESIDUE paragraph (5x exit-137 on srv2-class slots = converge lane, not plan width)

Verified before commit: discovery batches charge corpus_resolve_nanos (own key), resolve_nanos=0 -> resolves_total declared 3 is NOT moved by the flip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* WIP: affected set processing

* WIP: affected set processing

* WIP: affected set processing

* WIP: affected set processing

* Correct the false flip receipt + kill-surviving calibration receipts (space-lens loop)

CORRECTED RECEIPT: run 29000557166's floor never completed - the executor was
host-OOM-killed mid-discovery-corpus at ~8min; the log's ExitSuccess belongs to
the merge-admission stamp tool stamping CI_FLOOR_EXIT=137. No flipped corpus
has completed in CI (0/1 ci + 0/5 falsifier). Both carrier notes corrected.

Calibration (coordinated with merry-owl-649's space-lens lane):
- roster_import_closure_nodes_pre_resolve: shared closure-count authority
  (pure import walk, closure grain not entry grain), emitted BEFORE the heavy
  resolve so killed runs still yield the (nodes, peak) lower-bound pair
- width-1 definition-drift oracle: pre-resolve walk must equal post-resolve
  resolved union on completed runs; refuses on divergence. Proven by execution:
  pre == post == 190/213 nodes across Off/Applied modes
- kill-surviving cgroup memory.peak pre/post steps (post is always()) in the
  ci job and falsifier.yml; scope semantics labeled on the emission lines
  (reset=ok floor-scoped; span compares post>pre; never silently conflated)
- job backstop timeouts extended by the two aux steps in both jobs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* WIP: affected set processing

* Calibration pairs carry censored-vs-exact labels (methodology: killed runs are censored observations)

Floor steps get id=floor; the always() post-peak step emits floor_outcome so
each (closure_nodes, peak) pair is explicitly labeled: success = exact point,
anything else = censored lower bound (true demand strictly greater than read).
Prevents the fit from treating cap-kill reads as point estimates, which would
drag the slope down and make the predictor underestimate - the dangerous
direction (merry-owl methodology catch, 2026-07-10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* WIP: affected set processing

* Review fixes: explicit witness import + complete step-budget ledger

- ci_floor_plan_witness_test.dag: import witness_ci_corpus_discovery_serializes_at_width_one
  explicitly (cursor catch on #6438). The call resolved pre-fix via the seed resolver's flat
  namespace, so the witness ran green by execution; the explicit import restores the file's
  per-symbol import convention.
- ci_workflow.dag: the resolve-receipt gate step had NO step cap — a hang there could only die
  by job-cancel (the #6323 starvation-kill class). It now carries the aux cap (script is a
  sub-second receipt read) and the ci job backstop counts four aux terms (peak pre/post,
  resolve-receipt gate, merge-admission gate): every step budgeted, backstop = step-sum + prelude
  (claude review catch on #6438, sharpened).
- falsifier_workflow_witness_test.dag: falsifier_backstop_is_step_sum_plus_prelude asserted the
  pre-calibration formula — latent red proven by execution (FAIL receipt), fixed to the live
  two-aux sum, re-run PASS.
- ci.yml regenerated byte-stable from the carrier (backstops 125->130, gate step timeout 5m).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* Address REQUEST_CHANGES: shell dissolution markers + space-lens plan receipt.

Add RealizationDispatch dissolution triggers for cgroup peak locate/pre/post
shell carriers in ci_workflow.dag (bash-emit #5828 discipline). Land
docs/plans/space-lens-minimal-project.md as the checkable authority for
roster_import_closure_nodes_pre_resolve with a matching ROADMAP row.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* Park live discovery flip enrollment to fix CI OOM (exit 137).

Discovery corpus alone exceeds the 24GiB slot cap (run 29123623007).
Keep flip machinery authored on witness_discovery_scan_dirs and witnessed
via discovery_flip_enrolled_spec; live gunbc_ci_spec keeps empty dirs until
skip-before-resolve + memory receipt land. Falsifier reads layer_roots dirs
directly so nightly predict-only corpus is unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* Reconcile authority docs with parked discovery enrollment.

Qualify DESIGN.md and design_document.dag Building & checks CI text:
live spec runs opt-in roster only; discovery flip is parked until
skip-before-resolve + memory receipt. Revert ci_witness_optin_inversion
to Scaffold bound to ci_spec_discovery_flip_enrollment_disposition;
update ci_spec_witness_entries_note to match.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Merge origin/main (#6438): enroll discovery flip, keep S2a runner

Reconcile with main's affected-set witness enrollment (#6438) while
preserving S2a runner migration, flip isolation witnesses, cgroup peak
calibration, and materialization receipt gate. Drop parked enrollment
scaffold; regenerate ci.yml and falsifier.yml from dag emit.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move runnable_selection_applied to std.realization_schedule

Address review #36705: the NodeFrontierSelection predicate belongs on the
canonical Runnable schedule surface, not as a local coproduct match in
ci_floor_plan. Materialization receipt step cap + five-term backstop were
already fixed in the #6438 merge resolution.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Park live discovery enrollment to fix CI OOM on PR branch.

Batch-1 S2a runner receipt already passes; batch-2 discovery corpus hits the 24GiB cap (run 29126848340). Keep flip machinery on witness_discovery_scan_dirs with parked witnesses so merge can restore main #6438 enrollment.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Regenerate ci.yml after ci_spec park changes.

Fixes generated_artifact_drift_gate_passes (trailing newline drift in committed workflow artifacts).

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* Regenerate stage0 after runnable_selection_applied authority move.

Fixes ci_regen regen_verify_gate_passes — std_realization_schedule.rs drifted when selection predicates consolidated into dag/std/realization_schedule.dag.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* Resolve merge conflicts with main (space-lens doc + ci_floor_plan).

Keep main's space-lens design doc with interim #6425 calibration appendix; retain S2a discovery-flip imports (ci_spec_with_discovery_scan_dirs) and drop removed RustMonolithGate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* WIP: S2a move 1: layer-2 runner + node-keyed store; migrate CI floor onto it

* Restore main discovery enrollment per review #36778.

Align DESIGN/design_document with enrolled witness_discovery_scan_dirs (#6438); witness bundle uses is_live on gunbc_ci_spec; drop parked import.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Brian Searls <briansrls@gunb.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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