diff --git a/dag/extdeps/rust/cargo_diagnostic.dag b/dag/extdeps/rust/cargo_diagnostic.dag index c37dbbc3c06..766aebe60d5 100644 --- a/dag/extdeps/rust/cargo_diagnostic.dag +++ b/dag/extdeps/rust/cargo_diagnostic.dag @@ -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: _ } => "" diff --git a/dag/gunbc/self_host_compile_phase_frontier.dag b/dag/gunbc/self_host_compile_phase_frontier.dag index f9acbe46d0c..3b25ab0a95c 100644 --- a/dag/gunbc/self_host_compile_phase_frontier.dag +++ b/dag/gunbc/self_host_compile_phase_frontier.dag @@ -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, @@ -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 { @@ -999,16 +1002,27 @@ fn compile_phase_frontier_standing(series: List) -> } } -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 = [ "src/v2_std_cross_tree_resolution.rs:98:102 E0308 mismatched types", diff --git a/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag b/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag index 4507605a73d..6acf3b4f7f2 100644 --- a/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag +++ b/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag @@ -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( @@ -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) -> 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 + } +} diff --git a/docs/design-ledgers.md b/docs/design-ledgers.md index bf70dd98471..bc3ccaee3e4 100644 --- a/docs/design-ledgers.md +++ b/docs/design-ledgers.md @@ -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`