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
8 changes: 8 additions & 0 deletions dag/extdeps/rust/cargo_diagnostic.dag
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,14 @@ fn rustc_diagnostic_row(d: RustcCodedDiagnostic) -> String {
)
}

// The inverse of `rustc_diagnostic_row`, and it lives beside it because the field order is ONE
// fact: a reader that hard-codes its own index is a second authority for that order, and it stays
// silently wrong -- an out-of-range index yields Absent, which every consumer reads as "this
// diagnostic carries no code" rather than "this reader cannot parse the row".
fn rustc_diagnostic_row_code(row: String) -> String? {
get(xs: split(s: row, sep: "\t"), index: 1)
}

fn cargo_diagnostic_scan_refusal_reason(scan: CargoDiagnosticScan) -> String {
match scan {
CargoDiagnosticsRead { diagnostics: _, lines_scanned: _, build_succeeded: _, terminal_seen: _ } => ""
Expand Down
24 changes: 19 additions & 5 deletions dag/gunbc/self_host_compile_phase_frontier.dag
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import extdeps.cargo_diagnostic {
RustcDiagnosticIdentity,
RustcCoded,
RustcUncoded,
rustc_diagnostic_row
rustc_diagnostic_row,
rustc_diagnostic_row_code
}
import extdeps.languages.rust.compiler_phases {
RustcPhase,
Expand Down Expand Up @@ -224,8 +225,10 @@ fn self_host_compile_phase_frontier(entry: String) -> RustcPhaseBoardRead {
}
}

// The row layout is owned by `rustc_diagnostic_row`, which produced these identities; this asks
// that authority for the code rather than re-deriving a field index here.
fn census_identity_code(identity: String) -> String? {
get(xs: split(s: identity, sep: "\t"), index: 3)
rustc_diagnostic_row_code(row: identity)
}

fn census_identity_phase(identity: String) -> RustcErrorCodePhase {
Expand Down Expand Up @@ -999,16 +1002,27 @@ fn compile_phase_frontier_standing(series: List<CompilePhaseFrontierReceipt>) ->
}
}

fn render_phase_standing(row: RustcPhaseBoardRow) -> String {
// PASSES is a claim about the phase, not about this row's arithmetic. A zero count means only that
// no ATTRIBUTED identity landed in the phase, so while the census still holds identities no phase
// row accounts for, every one of those unattributed identities is a candidate member of this phase
// and the zero establishes nothing. Rendering PASSES there is the absorbing arm of DESIGN section
// 5 pointed at the compiler's own self-description: a reader cannot tell "this phase passed" from
// "the census could not be partitioned". The honest arm names the shortfall instead of hiding it.
fn render_phase_standing(row: RustcPhaseBoardRow, partition_complete: Bool) -> String {
match row.standing {
PhaseNotYetReached => concat(rustc_phase_name(phase: row.phase), " not yet reached")
PhaseCount { count: 0, evidence: _ } => concat(rustc_phase_name(phase: row.phase), " PASSES")
PhaseCount { count: 0, evidence: _ } =>
if partition_complete {
concat(rustc_phase_name(phase: row.phase), " PASSES")
} else {
concat(rustc_phase_name(phase: row.phase), " 0 attributed (census partition incomplete)")
}
PhaseCount { count: n, evidence: _ } => concat(concat(rustc_phase_name(phase: row.phase), " "), concat(to_string(n), " remaining"))
}
}

fn render_phase_board(board: RustcPhaseBoard) -> String {
board.rows |> map(r => render_phase_standing(row: r)) |> join(separator: " / ")
board.rows |> map(r => render_phase_standing(row: r, partition_complete: board.partition_complete)) |> join(separator: " / ")
}
data genesis_5006ddc6_raw_error_diagnostic_identities: List<String> = [
"src/v2_std_cross_tree_resolution.rs:98:102 E0308 mismatched types",
Expand Down
108 changes: 108 additions & 0 deletions dag/test/claim/self_host_compile_phase_frontier_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,23 @@ import gunbc.self_host_compile_phase_frontier {
PhaseBoardSeriesHeld,
phase_board_series_ratchet,
self_host_compile_phase_frontier_receipts,
PersistedPhaseBoardFold,
phase_board_from_census_identities,
board_row,
CompilePhaseFrontierValidated,
compile_phase_frontier_standing,
genesis_5006ddc6_parse_observation,
genesis_5006ddc6_raw_error_diagnostic_identities,
render_phase_board
}
import extdeps.cargo_diagnostic {
RustcCodedDiagnostic,
RustcCoded,
RustcUncoded,
RustcSpan,
rustc_diagnostic_row
}
import tools.emission_entry_instrument { RustfmtParseObservation }

test fn an_epoch_change_without_reclassification_cannot_fabricate_identity_regressions() -> Bool {
match regression_comparison_predecessor(
Expand Down Expand Up @@ -287,3 +302,96 @@ test fn milestone_render_never_calls_an_unreached_phase_zero() -> Bool {
render_phase_board(board: fixture_board(resolve_ids: [], typeck_ids: ["a", "b"]))
== "parse PASSES / expand PASSES / resolve PASSES / typeck 2 remaining / borrowck not yet reached"
}


// ---------------------------------------------------------------------------
// The census fold, which is the path the published projection actually reads.
//
// Every test above builds its board by hand. That grain is real but it is not the grain the ledger
// renders: a persisted receipt carries RAW IDENTITY ROWS and the board is re-derived from them by
// `phase_board_from_census_identities`. That fold owned an unshared copy of the row layout and read
// the wrong field, so every identity came back unattributable, every checked phase counted zero,
// and the projection published "typeck PASSES / borrowck PASSES" over a census of typeck errors.
// The rows below are produced by `rustc_diagnostic_row` itself, so the serializer and the census
// reader are held to one layout by execution rather than by a comment.

fn census_probe_row(code: String, message: String) -> String {
rustc_diagnostic_row(d: RustcCodedDiagnostic {
identity: RustcCoded { code: code },
level: "error",
location: RustcSpan { file: "src/probe.rs", line: 1, column: 1 },
message: message
})
}

fn clean_parse_observation() -> RustfmtParseObservation {
RustfmtParseObservation { producer: "fixture", files_observed: 1, refused_files: [] }
}

fn census_fold(raw: List<String>) -> PersistedPhaseBoardFold {
phase_board_from_census_identities(parse_observation: clean_parse_observation(), raw_identities: raw)
}

// THE DISCRIMINATING RED. A census carrying one typeck identity must attribute it to typeck, and
// the rendered milestone line must not call that phase passed. Reading any other field of the row
// as the error code makes this identity unattributable, which renders "typeck PASSES".
test fn a_typeck_census_identity_is_attributed_to_typeck_and_never_renders_passes() -> Bool {
let fold = census_fold(raw: [census_probe_row(code: "E0308", message: "mismatched types")])
render_phase_board(board: fold.board)
== "parse PASSES / expand PASSES / resolve PASSES / typeck 1 remaining / borrowck not yet reached"
&& fold.unplaced_identities == []
&& fold.board.partition_complete
}

// The accepted positive control: a census with nothing in it is the one state in which every phase
// may honestly say PASSES, so the wall above is not simply "never say PASSES".
test fn an_empty_census_still_renders_every_phase_as_passing() -> Bool {
let fold = census_fold(raw: [])
render_phase_board(board: fold.board)
== "parse PASSES / expand PASSES / resolve PASSES / typeck PASSES / borrowck PASSES"
&& fold.board.partition_complete
}

// The second arm of the same defect: an identity the error index does not place is not evidence
// that the phases are clean. While any identity is unattributed, a zero count establishes nothing
// about the phase and must not be rendered as a pass.
test fn an_unattributed_census_identity_forbids_every_phase_from_claiming_passes() -> Bool {
let fold = census_fold(raw: [census_probe_row(code: "E9999", message: "not in the error index")])
!fold.board.partition_complete
&& count(fold.unplaced_identities) == 1
&& !string_contains(s: render_phase_board(board: fold.board), pattern: "PASSES")
&& string_contains(s: render_phase_board(board: fold.board), pattern: "census partition incomplete")
}

// The published board itself, over the persisted genesis census rather than a fixture. Its whole
// population is attributed, and the count is joined to the persisted identity list rather than
// compared to a number copied out of a run.
test fn the_persisted_genesis_census_is_fully_attributed_to_its_phases() -> Bool {
let fold = census_fold(raw: genesis_5006ddc6_raw_error_diagnostic_identities)
match board_row(board: fold.board, phase: Typeck) {
Present { value: typeck } => match typeck.standing {
PhaseCount { count: n, evidence: _ } =>
fold.board.partition_complete
&& fold.unplaced_identities == []
&& n == count(genesis_5006ddc6_raw_error_diagnostic_identities)
&& fold.board.total_error_diagnostics == n
standing => false
}
Absent => false
}
}

// The end-to-end claim, asserted on the exact string the ledger projection publishes: the standing
// the document renders may not say the emitted compiler clears typeck or borrowck while the latest
// validated receipt's census says otherwise.
test fn the_published_frontier_standing_does_not_claim_typeck_or_borrowck_passed() -> Bool {
match compile_phase_frontier_standing(series: self_host_compile_phase_frontier_receipts) {
CompilePhaseFrontierValidated { latest: latest } => {
let rendered = render_phase_board(board: latest.board)
!string_contains(s: rendered, pattern: "typeck PASSES")
&& !string_contains(s: rendered, pattern: "borrowck PASSES")
&& string_contains(s: rendered, pattern: "borrowck not yet reached")
}
standing => false
}
}
2 changes: 1 addition & 1 deletion docs/design-ledgers.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one c

### Whole-corpus compile-phase frontier receipts — latest validated row

Milestones: parse PASSES / expand PASSES / resolve PASSES / typeck PASSES / borrowck PASSES
Milestones: parse PASSES / expand PASSES / resolve PASSES / typeck 233 remaining / borrowck not yet reached

Tree: `sha1:1c7c79fbc01aa488abb541513fc8ea44367406ad`

Expand Down
Loading