use std::collections::BTreeSet;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DataflowGraphLayoutConsumer {
Dominators,
Callgraph,
Ifds,
Slicing,
RangePropagation,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DataflowGraphLayoutCheck {
StableHash,
EdgeEncodingFamily,
Normalization,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DataflowGraphLayoutRecord<'a> {
pub consumer: DataflowGraphLayoutConsumer,
pub check: DataflowGraphLayoutCheck,
pub command: &'a str,
pub evidence: &'a str,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DataflowGraphLayoutCoverageProof {
pub consumer_count: usize,
pub record_count: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DataflowGraphLayoutCoverageError {
EmptyRecords,
EmptyMetadata {
consumer: DataflowGraphLayoutConsumer,
field: &'static str,
},
CommandDoesNotUseCargoFull {
consumer: DataflowGraphLayoutConsumer,
command: String,
},
MissingConsumer {
consumer: DataflowGraphLayoutConsumer,
},
MissingCheck {
consumer: DataflowGraphLayoutConsumer,
check: DataflowGraphLayoutCheck,
},
}
impl std::fmt::Display for DataflowGraphLayoutCoverageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyRecords => write!(
f,
"Dataflow graph-layout coverage is empty. Fix: prove dominators, callgraph, IFDS, slicing, and range propagation use shared layout checks."
),
Self::EmptyMetadata { consumer, field } => write!(
f,
"Dataflow graph-layout record {consumer:?} has empty {field}. Fix: every record needs command and evidence."
),
Self::CommandDoesNotUseCargoFull { consumer, command } => write!(
f,
"Dataflow graph-layout record {consumer:?} uses `{command}` instead of ./cargo_full. Fix: run graph-layout checks through cargo_full."
),
Self::MissingConsumer { consumer } => write!(
f,
"Dataflow graph-layout coverage is missing {consumer:?}. Fix: route that consumer through the shared graph-layout contract."
),
Self::MissingCheck { consumer, check } => write!(
f,
"Dataflow graph-layout coverage {consumer:?} is missing {check:?}. Fix: add that compatibility check."
),
}
}
}
impl std::error::Error for DataflowGraphLayoutCoverageError {}
const REQUIRED_CONSUMERS: &[DataflowGraphLayoutConsumer] = &[
DataflowGraphLayoutConsumer::Dominators,
DataflowGraphLayoutConsumer::Callgraph,
DataflowGraphLayoutConsumer::Ifds,
DataflowGraphLayoutConsumer::Slicing,
DataflowGraphLayoutConsumer::RangePropagation,
];
const REQUIRED_CHECKS: &[DataflowGraphLayoutCheck] = &[
DataflowGraphLayoutCheck::StableHash,
DataflowGraphLayoutCheck::EdgeEncodingFamily,
DataflowGraphLayoutCheck::Normalization,
];
pub fn validate_graph_layout_coverage(
records: &[DataflowGraphLayoutRecord<'_>],
) -> Result<DataflowGraphLayoutCoverageProof, DataflowGraphLayoutCoverageError> {
if records.is_empty() {
return Err(DataflowGraphLayoutCoverageError::EmptyRecords);
}
let mut seen_consumers = BTreeSet::new();
let mut pairs = BTreeSet::new();
for record in records {
for (field, value) in [("command", record.command), ("evidence", record.evidence)] {
if value.trim().is_empty() {
return Err(DataflowGraphLayoutCoverageError::EmptyMetadata {
consumer: record.consumer,
field,
});
}
}
if !record.command.trim_start().starts_with("./cargo_full ") {
return Err(
DataflowGraphLayoutCoverageError::CommandDoesNotUseCargoFull {
consumer: record.consumer,
command: record.command.to_owned(),
},
);
}
seen_consumers.insert(record.consumer);
pairs.insert((record.consumer, record.check));
}
for consumer in REQUIRED_CONSUMERS {
if !seen_consumers.contains(consumer) {
return Err(DataflowGraphLayoutCoverageError::MissingConsumer {
consumer: *consumer,
});
}
for check in REQUIRED_CHECKS {
if !pairs.contains(&(*consumer, *check)) {
return Err(DataflowGraphLayoutCoverageError::MissingCheck {
consumer: *consumer,
check: *check,
});
}
}
}
Ok(DataflowGraphLayoutCoverageProof {
consumer_count: seen_consumers.len(),
record_count: records.len(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn graph_layout_coverage_accepts_all_consumers_and_checks() {
let proof = validate_graph_layout_coverage(&records())
.expect("Fix: complete graph-layout coverage should pass");
assert_eq!(proof.consumer_count, 5);
assert_eq!(proof.record_count, 15);
}
#[test]
fn graph_layout_coverage_rejects_missing_range_propagation() {
let records: Vec<_> = records()
.into_iter()
.filter(|record| record.consumer != DataflowGraphLayoutConsumer::RangePropagation)
.collect();
assert_eq!(
validate_graph_layout_coverage(&records)
.expect_err("missing range propagation should fail"),
DataflowGraphLayoutCoverageError::MissingConsumer {
consumer: DataflowGraphLayoutConsumer::RangePropagation,
}
);
}
#[test]
fn graph_layout_coverage_rejects_missing_check_and_raw_cargo() {
let mut missing_check_records = records();
missing_check_records.retain(|record| {
!(record.consumer == DataflowGraphLayoutConsumer::Ifds
&& record.check == DataflowGraphLayoutCheck::Normalization)
});
assert_eq!(
validate_graph_layout_coverage(&missing_check_records)
.expect_err("missing normalization should fail"),
DataflowGraphLayoutCoverageError::MissingCheck {
consumer: DataflowGraphLayoutConsumer::Ifds,
check: DataflowGraphLayoutCheck::Normalization,
}
);
let mut raw_cargo_records = records();
raw_cargo_records[0].command = "cargo test";
assert_eq!(
validate_graph_layout_coverage(&raw_cargo_records).expect_err("raw cargo should fail"),
DataflowGraphLayoutCoverageError::CommandDoesNotUseCargoFull {
consumer: DataflowGraphLayoutConsumer::Dominators,
command: "cargo test".to_owned(),
}
);
}
fn records() -> Vec<DataflowGraphLayoutRecord<'static>> {
let mut records = Vec::new();
for consumer in REQUIRED_CONSUMERS {
for check in REQUIRED_CHECKS {
records.push(DataflowGraphLayoutRecord {
consumer: *consumer,
check: *check,
command: "./cargo_full test -j1 -p dataflow",
evidence: "release/dataflow/graph-layout.md",
});
}
}
records
}
}