Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion dag/gunbc/guarantee_rung_drop.dag
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,26 @@ data gitattributes_committed_emit_drift_stall: GuaranteeStall = GuaranteeStall {
next_rung_trigger: "node://adhoc-16f7520a-85f lands: identify the authority edit that introduced the drift, regenerate .gitattributes from that authority, and restore a required generated-artifact drift gate so later authority/artifact disagreement refuses"
}

// THE WALL DEADLINE STILL CHARGES A SHARED-ARTIFACT FILL TO WHICHEVER CLAIM PAID IT, and this row
// exists because a comment beside the code would go stale on the day the population changes while
// this row cannot. The 2026-08-27 attribution ruling has now been applied to three homes one at a
// time -- the completion-side CPU split, the completion-side WALL split, and the CPU evaluation
// deadline -- and each application was made only after that home's omission had cost something.
// The wall deadline is the fourth home and it is unrepaired.
//
// WHY IT IS SCOPED OUT RATHER THAN FIXED HERE: measured on run 33185280160, all 44 interruptions
// are on the `Cpu` clock, 44 of 44, so the wall arm is currently unexercised. That is a fact about
// today's population and NOT about the mechanism, which is exactly why the obligation is declared
// as a countable row rather than left as a note. The trigger is deliberately an OBSERVATION rather
// than a promise: the first wall-clock interruption to appear in the floor's ledger is the event
// that makes this reachable, and it fires without anyone remembering this row exists.
data wall_deadline_shared_fill_attribution_stall: GuaranteeStall = GuaranteeStall {
subject: "the wall evaluation deadline charges shared-artifact fill to the claim that paid it, so a wall interruption is a function of discovery order rather than of the row",
current: Mitigatable,
ceiling: StructurallyGuaranteed,
blocker: ClimbableButUnbuilt,
population: BoundedPopulation { members: Cons { head: "v1_interpreter.arm_wall_deadline", tail: Cons { head: "v1_interpreter.wall_deadline_remaining_ms", tail: Cons { head: "v1_interpreter.wall_deadline_exceeded_error", tail: Empty {} } } } },
next_rung_trigger: "the required floor's ledger reports any INTERRUPTED-BEFORE-VERDICT row whose clock is Wall, at which point the wall deadline must read a fill-netted clock exactly as v1_interpreter.budgeted_cpu_nanos does for the CPU deadline"
data heterogeneous_child_list_stall: GuaranteeStall = GuaranteeStall {
subject: "a type's child list may hold BOTH a type node and a field node, and v1.04_types child_type_node tells them apart by whether `inferred` is populated -- a stamp v1.02_parse field_to_child_node writes at PARSE TIME as Resolved, before any resolution has run. A child list that mixes the two kinds makes that accessor return the FIELD node where its caller expects the TYPE, with no diagnostic",
current: OutsideTheLadder,
Expand Down Expand Up @@ -432,7 +452,8 @@ data all_guarantee_stalls: List<GuaranteeStall> = [
self_host_candidate_generation_add_slice_stall,
non_fold_residue_roster_stall,
retained_rust_live_tree_migration_stall,
gitattributes_committed_emit_drift_stall
gitattributes_committed_emit_drift_stall,
wall_deadline_shared_fill_attribution_stall
]

// A STALL AT ITS CEILING IS NOT A STALL: the row would describe a class that already arrived, and
Expand Down
142 changes: 135 additions & 7 deletions src/v1/stage0/src/cli_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2041,10 +2041,12 @@ pub fn compile_dag_diagnostic_census(source: &str) -> CompileDiagnosticCensus {
// `run_claim_measured` as its own `[floor-shared-fill]` column — the claim's marginal and fill
// halves still sum to what it actually spent.
let fill_started = v1_interpreter::thread_cpu_nanos();
let fill_wall_started = std::time::Instant::now();
let census = compile_dag_diagnostic_census_uncached(source);
record_shared_artifact_fill_cpu(
v1_interpreter::thread_cpu_nanos().saturating_sub(fill_started),
);
record_shared_artifact_fill_wall(fill_wall_started.elapsed().as_nanos());
COMPILE_DAG_DIAGNOSTIC_CENSUS_MEMO.with(|m| m.borrow_mut().insert(memo_key, census.clone()));
census
}
Expand Down Expand Up @@ -2543,21 +2545,52 @@ thread_local! {
/// COUNTED. A cost that vanished here would be the absorbing fallback (§5) wearing an
/// accounting label — the deficit's frequency zeroed by construction — so the receipt carries
/// marginal and fill as two columns whose sum is the claim's whole measured cost.
static SHARED_ARTIFACT_FILL_CPU_NANOS: std::cell::Cell<u128> =
// (the CPU fill cell now lives in `v1_interpreter`; see the delegating fns below)

/// THE SAME FILL, ON THE OTHER CLOCK. The ruling above is about WHOSE COST A FILL IS, which
/// is a fact about attribution and not about which clock measured it — so it applies once to
/// every ceiling derived from a claim's elapsed time. It was landed on the CPU ceiling alone,
/// and the wall ceiling kept charging the whole fill to the first payer, which is how two
/// rows whose own cost is 0ms and 1ms refused a required floor at ~18000ms against a 10000ms
/// wall requirement (main run 33145062452, `test.claim.transport_script_wall_compile_red`:
/// `[floor-shared-fill]` reported `marginal_cpu_ms=0 fill_cpu_ms=18966` for one of them).
/// A one-clock accounting rule is the §3 failure the CPU comment already names — one concept
/// with two homes, one of which does not apply it — so this cell exists to close the second
/// home rather than to add a policy beside it.
static SHARED_ARTIFACT_FILL_WALL_NANOS: std::cell::Cell<u128> =
const { std::cell::Cell::new(0) };
}

/// Accumulate CPU spent filling a shared memoized artifact. Called ONLY from a memo MISS path,
/// and only under the floor guard — outside it there is no memo, so there is no shared artifact
/// and nothing to attribute.
///
/// THE CELL MOVED TO `v1_interpreter` AND THIS DELEGATES. The evaluation deadline must net the
/// same quantity WHILE a claim runs, and the interpreter cannot read a cell owned here, so one
/// counter now serves both readers rather than two counters drifting apart — which is the exact
/// defect (one accounting rule, two homes) this line of work exists to close.
fn record_shared_artifact_fill_cpu(nanos: u128) {
SHARED_ARTIFACT_FILL_CPU_NANOS.with(|c| c.set(c.get().saturating_add(nanos)));
v1_interpreter::record_shared_artifact_fill_cpu_nanos(nanos);
}

/// Accumulate WALL time spent filling a shared memoized artifact, under the same rule and from
/// the same miss paths as `record_shared_artifact_fill_cpu`. The two are recorded together at
/// every call site so a fill can never be counted on one clock and not the other — which is the
/// state that produced the defect this pair exists to close.
fn record_shared_artifact_fill_wall(nanos: u128) {
SHARED_ARTIFACT_FILL_WALL_NANOS.with(|c| c.set(c.get().saturating_add(nanos)));
}

/// Read the running total for this thread. The claim loop samples it either side of one claim;
/// the difference is that claim's fill.
pub fn shared_artifact_fill_cpu_nanos() -> u128 {
SHARED_ARTIFACT_FILL_CPU_NANOS.with(|c| c.get())
v1_interpreter::shared_artifact_fill_cpu_nanos()
}

/// Read the running wall-clock total for this thread, sampled either side of one claim exactly
/// as the CPU total is.
pub fn shared_artifact_fill_wall_nanos() -> u128 {
SHARED_ARTIFACT_FILL_WALL_NANOS.with(|c| c.get())
}

static COMPILE_DAG_RUST_EMIT_CHECK_MEMO_HITS: std::sync::atomic::AtomicU64 =
Expand Down Expand Up @@ -2645,10 +2678,12 @@ pub fn compile_dag_rust_emit_check(
// against, so the two quantities cannot drift apart, and recorded rather than subtracted here
// — the claim loop does the split, this only says how much of the cost was a fill.
let fill_started = v1_interpreter::thread_cpu_nanos();
let fill_wall_started = std::time::Instant::now();
let verdict = compile_dag_rust_emit_check_uncached(source, file_path, includes, excludes);
record_shared_artifact_fill_cpu(
v1_interpreter::thread_cpu_nanos().saturating_sub(fill_started),
);
record_shared_artifact_fill_wall(fill_wall_started.elapsed().as_nanos());
COMPILE_DAG_RUST_EMIT_CHECK_MEMO.with(|m| m.borrow_mut().insert(memo_key, verdict));
verdict
}
Expand Down Expand Up @@ -20481,11 +20516,15 @@ pub fn run_claim_measured(
let started = std::time::Instant::now();
let cpu_started_nanos = v1_interpreter::thread_cpu_nanos();
let fill_before_nanos = shared_artifact_fill_cpu_nanos();
let fill_wall_before_nanos = shared_artifact_fill_wall_nanos();
let outcome = run_claim(ctx, function);
// CPU consumed by THIS (witness-eval) thread — the budget metric, so the completion-side
// check matches the cooperative stride-poll and neither fires on cold-I/O or contention
// wall time. wall_nanos stays the measurement/receipt basis (unchanged).
// wall time.
let measured_cpu_nanos = v1_interpreter::thread_cpu_nanos().saturating_sub(cpu_started_nanos);
// Sampled here rather than after the report so the wall clock can be split by the same rule
// the CPU clock is, and so the reported line and the enforced quantity read one binding.
let measured_wall_nanos = started.elapsed().as_nanos();
// SHARED-ARTIFACT FILL IS NOT THIS CLAIM'S MARGINAL COST (operator-line ruling, 2026-08-27).
// Whatever this claim spent filling a memo is consumed by every later claim naming the same
// source — one of them measured at literally 0ms in the same run because this one paid — so
Expand All @@ -20498,7 +20537,18 @@ pub fn run_claim_measured(
// the fill is still counted, still attributed and still visible in the receipt.
let fill_cpu_nanos = shared_artifact_fill_cpu_nanos().saturating_sub(fill_before_nanos);
let cpu_nanos = measured_cpu_nanos.saturating_sub(fill_cpu_nanos);
if fill_cpu_nanos > 0 {
// THE SAME SPLIT ON THE WALL CLOCK. `wall_budget_completion_outcome` below is a
// merge-blocking ceiling, so charging it the fill made it a function of execution order in
// exactly the way the ruling above forbids — and unlike the CPU side it had no exemption
// argument, only an omission. Split, never dropped: both halves are reported and they sum to
// `measured_wall_nanos`.
let fill_wall_nanos = shared_artifact_fill_wall_nanos().saturating_sub(fill_wall_before_nanos);
let wall_nanos = marginal_wall_nanos(measured_wall_nanos, fill_wall_nanos);
// EITHER clock, not the CPU one. A fill that blocked on I/O can spend wall time while
// charging almost no CPU, and under a `fill_cpu_nanos > 0` guard that fill would be
// subtracted from the enforced wall figure and reported nowhere — a cost dropped rather
// than split, which is the one thing the ruling above forbids.
if fill_cpu_nanos > 0 || fill_wall_nanos > 0 {
// REPORTED, NOT ABSORBED. Printed on its own line, per claim, whenever a fill happened,
// so the quantity the ceiling stops charging is visible at the same grain it was measured
// — the difference between attributing a cost and losing one. `triggered_by` is this
Expand All @@ -20507,13 +20557,16 @@ pub fn run_claim_measured(
// this line names who paid.
eprintln!(
"[floor-shared-fill] claim={function} marginal_cpu_ms={} fill_cpu_ms={} \
measured_cpu_ms={} provenance=filled-shared-artifact triggered_by={function}",
measured_cpu_ms={} marginal_wall_ms={} fill_wall_ms={} measured_wall_ms={} \
provenance=filled-shared-artifact triggered_by={function}",
cpu_nanos / 1_000_000,
fill_cpu_nanos / 1_000_000,
measured_cpu_nanos / 1_000_000,
wall_nanos / 1_000_000,
fill_wall_nanos / 1_000_000,
measured_wall_nanos / 1_000_000,
);
}
let wall_nanos = started.elapsed().as_nanos();
ctx.clear_eval_deadline();
ctx.clear_wall_deadline();
v1_interpreter::eval_subject_clear();
Expand Down Expand Up @@ -20983,6 +21036,19 @@ fn budget_completion_outcome(
}
}

/// The claim's own wall cost: what it spent, less what it spent filling a shared artifact every
/// later claim naming the same source then reads free.
///
/// Named rather than written inline at the one call site because it is the quantity
/// `wall_budget_completion_outcome` enforces against, and a merge-blocking ceiling's input
/// deserves a symbol its regression control can drive directly. The subtraction is saturating for
/// the same reason the CPU side's is: the two clocks are sampled at slightly different instants,
/// so a fill measured marginally longer than the enclosing claim is an artifact of sampling, not
/// a negative cost.
fn marginal_wall_nanos(measured_wall_nanos: u128, fill_wall_nanos: u128) -> u128 {
measured_wall_nanos.saturating_sub(fill_wall_nanos)
}

/// Whole-receipt wall budget for Wet self-host receipts: emit+cargo subprocess I/O
/// counts against wall time, not CPU. A Pass over the wall budget converts to the same
/// typed refusal — silent green would fail open on the nightly falsifier lane budget.
Expand Down Expand Up @@ -21035,6 +21101,68 @@ mod budget_completion_tests {
}
}

#[test]
/// THE DISCRIMINATING RED FOR THE WALL HALF OF THE FILL-ATTRIBUTION RULING. A claim whose own
/// wall cost is 1ms, which happened to be the first to reach a shared memo and paid an 18000ms
/// fill for it, must not refuse a 10000ms wall requirement — every later claim naming the same
/// source reads that artifact free, so charging it here makes the ceiling a function of
/// execution order rather than of the tree.
///
/// The second arm is what makes this a control rather than a restatement: driving the SAME
/// ceiling with the unsplit figure must still refuse. So the test fails if the split is
/// removed AND fails if the ceiling stops firing at all, which is the pair a single assertion
/// cannot carry. The shape is the one main run 33145062452 exhibited on
/// `test.claim.transport_script_wall_compile_red`, whose two rows reported
/// `marginal_cpu_ms=0`/`1` against `fill_cpu_ms` near 19000 and refused the required floor.
fn a_shared_fill_is_not_charged_to_the_claim_that_paid_it() {
let measured_wall_nanos = 18_001_000_000u128;
let fill_wall_nanos = 18_000_000_000u128;
let budget_ms = 10_000u64;

assert!(
matches!(
wall_budget_completion_outcome(
Some(budget_ms),
ClaimOutcome::Pass,
marginal_wall_nanos(measured_wall_nanos, fill_wall_nanos),
),
ClaimOutcome::Pass
),
"a 1ms claim must not refuse a 10s ceiling because it paid an 18s shared fill"
);

match wall_budget_completion_outcome(
Some(budget_ms),
ClaimOutcome::Pass,
measured_wall_nanos,
) {
ClaimOutcome::CompletedOverBudget { kind, .. } => {
assert_eq!(kind, BudgetKind::Wall);
}
other => panic!("the unsplit figure must still refuse, got {other:?}"),
}
}

#[test]
/// The cost is SPLIT, never DROPPED (§5 — a vanished cost is the absorbing fallback wearing an
/// accounting label). Whatever the ceiling stops charging must still be recoverable, so the
/// two halves are asserted to sum to what the claim actually spent.
fn the_two_wall_halves_sum_to_the_measured_cost() {
let measured_wall_nanos = 18_001_000_000u128;
let fill_wall_nanos = 18_000_000_000u128;
assert_eq!(
marginal_wall_nanos(measured_wall_nanos, fill_wall_nanos) + fill_wall_nanos,
measured_wall_nanos
);
}

#[test]
/// A fill sampled marginally longer than the enclosing claim saturates to zero rather than
/// wrapping to an enormous cost that would refuse every ceiling.
fn a_fill_longer_than_the_claim_saturates() {
assert_eq!(marginal_wall_nanos(5, 9), 0);
}

#[test]
fn pass_under_budget_stays_pass() {
assert!(matches!(
Expand Down
Loading
Loading