Skip to content
Open
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
278 changes: 136 additions & 142 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions ci/workflow-jobs.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ workflow job role
.github/workflows/ci.yml miri planned
.github/workflows/ci.yml plan_ci static-ci
.github/workflows/ci.yml run-git-hooks static-ci
.github/workflows/ci.yml semver planned
.github/workflows/ci.yml zizmor security
.github/workflows/dependency-review.yml dependency-review security
.github/workflows/docs.yml build documentation
Expand Down
22 changes: 18 additions & 4 deletions tools/zc/src/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
//! [`CiInputs`] until the policy is valid, its references agree with live Cargo
//! metadata and repository files, every workflow job has an exact reviewed
//! role, the handwritten matrix jobs exactly publish and consume typed plans,
//! independently recorded legacy baseline parses canonically, and the typed
//! execution model exactly reproduces that legacy evidence. Planners
//! therefore consume checked data rather than remembering which validation
//! passes must precede which lookups.
//! the complete standalone semver job consumes its typed target matrix and
//! exactly implements policy, every independently recorded legacy baseline
//! parses canonically, and the typed execution model exactly reproduces that
//! legacy evidence. Planners therefore consume checked data rather than
//! remembering which validation passes must precede which lookups.

use std::{
collections::HashMap,
Expand All @@ -32,6 +33,7 @@ use crate::{
planned_adapter::{audit_planned_adapter, PlannedAdapterAuditError},
policy::{Baselines, Policy, ReadPolicyError},
repository_file::{self, OpenRepositoryFileError, OpenedRepositoryFile},
semver_adapter::{audit_semver_adapter, SemverAdapterAuditError},
workflow::{
audit_workflows, ReviewedWorkflowJobs, WorkflowAuditError, WorkflowRegistryError,
WORKFLOW_REGISTRY_PATH,
Expand Down Expand Up @@ -108,6 +110,15 @@ impl CiInputs {
.map_err(LoadCiError::Inventory)?;
audit_planned_adapter(&repository_root, workflow_source, &workflow_jobs, &repository)
.map_err(LoadCiError::PlannedAdapter)?;
// `audit_workflows` deliberately recognizes jobs, not arbitrary YAML
// steps. GitHub requires the semver action reference to remain literal,
// so check the complete standalone job only after policy and Cargo
// inventory are trustworthy. The preceding planned-adapter audit has
// already established exact planner publication and reviewed ownership;
// this focused audit checks semver's target-only matrix consumer, fresh
// runner boundary, checkout, preparation, and literal action.
audit_semver_adapter(workflow_source, &policy, &repository)
.map_err(LoadCiError::SemverAdapter)?;
let baseline_files = OpenLegacyBaselineFiles::open(&repository_root, policy.baselines())?;
let paths = baseline_files.paths();
// Policy validation rejects two fields with the same lexical path.
Expand Down Expand Up @@ -354,6 +365,9 @@ pub enum LoadCiError {
/// The planned-job workflow bridge did not publish or execute plans exactly.
#[error(transparent)]
PlannedAdapter(PlannedAdapterAuditError),
/// The standalone literal semver job did not implement policy.
#[error(transparent)]
SemverAdapter(SemverAdapterAuditError),
/// The frozen legacy evidence was unreadable or noncanonical.
#[error(transparent)]
Baseline(BaselineError),
Expand Down
46 changes: 34 additions & 12 deletions tools/zc/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::{
github::{GitHubProjection, ProjectionError, ProjectionWriteError},
plan::{
BuildPlanCell, ExecutionMode, FeatureSelection, MiriPlanCell, Plan, PlanError,
PlanExplanation,
PlanExplanation, SemverPlanCell,
},
workflow_protocol::{
CELL_FEATURE_PROFILE_OPTION, CELL_MIRI_MODEL_OPTION, CELL_PACKAGE_OPTION,
Expand Down Expand Up @@ -376,11 +376,12 @@ fn audit(inputs: &CiInputs, output: &mut impl Write) -> Result<(), CliError> {
for plan in plans {
writeln!(
output,
"{}: {} coverage; {} build cells; {} Miri cells",
"{}: {} coverage; {} build cells; {} Miri cells; {} semver cells",
plan.event(),
plan.class(),
plan.builds().len(),
plan.miri().len(),
plan.semver().len(),
)?;
}
Ok(())
Expand All @@ -392,12 +393,16 @@ fn print_plan(inputs: &CiInputs, event: &str, output: &mut impl Write) -> Result
writeln!(output, "coverage: {}", plan.class())?;
writeln!(output, "build cells: {}", plan.builds().len())?;
writeln!(output, "Miri cells: {}", plan.miri().len())?;
writeln!(output, "semver cells: {}", plan.semver().len())?;
for cell in plan.builds() {
print_build_cell(output, cell)?;
}
for cell in plan.miri() {
print_miri_cell(output, cell)?;
}
for cell in plan.semver() {
print_semver_cell(output, cell)?;
}
Ok(())
}

Expand Down Expand Up @@ -441,6 +446,20 @@ fn print_miri_cell(output: &mut impl Write, cell: &MiriPlanCell) -> io::Result<(
writeln!(output, "]")
}

fn print_semver_cell(output: &mut impl Write, cell: &SemverPlanCell) -> io::Result<()> {
write!(
output,
"semver: package={} manifest={} toolchain={} version={} profile={} features=",
cell.package().id(),
cell.package().manifest().display(),
cell.toolchain().id(),
cell.toolchain().version(),
cell.features().profile(),
)?;
print_feature_selection(output, cell.features().selection())?;
writeln!(output, " target={}", cell.target().triple())
}

fn print_feature_selection(
output: &mut impl Write,
selection: &FeatureSelection,
Expand Down Expand Up @@ -506,9 +525,6 @@ fn print_execution_report(
for step in report.executed_steps() {
writeln!(output, "executed: {step}")?;
}
for step in report.workflow_owned_steps() {
writeln!(output, "skipped workflow-owned step: {step}")?;
}
Ok(())
}

Expand Down Expand Up @@ -662,7 +678,10 @@ mod tests {
use super::{run, CliError, Command};
use crate::{
execution::{BuildCellSelector, MiriCellSelector},
workflow_protocol::{BUILD_MATRIX_OUTPUT, MIRI_ENABLED_OUTPUT, MIRI_MATRIX_OUTPUT},
workflow_protocol::{
BUILD_MATRIX_OUTPUT, MIRI_ENABLED_OUTPUT, MIRI_MATRIX_OUTPUT, SEMVER_ENABLED_OUTPUT,
SEMVER_MATRIX_OUTPUT,
},
};

fn strings(args: &[&str]) -> Vec<String> {
Expand Down Expand Up @@ -887,10 +906,10 @@ mod tests {
String::from_utf8(output).unwrap(),
concat!(
"CI audit passed\n",
"merge_group: full coverage; 182 build cells; 64 Miri cells\n",
"pull_request: reduced coverage; 60 build cells; 0 Miri cells\n",
"push: full coverage; 182 build cells; 64 Miri cells\n",
"workflow_dispatch: full coverage; 182 build cells; 64 Miri cells\n",
"merge_group: full coverage; 182 build cells; 64 Miri cells; 9 semver cells\n",
"pull_request: reduced coverage; 60 build cells; 0 Miri cells; 3 semver cells\n",
"push: full coverage; 182 build cells; 64 Miri cells; 9 semver cells\n",
"workflow_dispatch: full coverage; 182 build cells; 64 Miri cells; 9 semver cells\n",
)
);
}
Expand All @@ -901,10 +920,11 @@ mod tests {
run(repository_root(), strings(&["plan", "--event", "pull_request"]), &mut output).unwrap();
let output = String::from_utf8(output).unwrap();
assert!(output.starts_with(
"event: pull_request\ncoverage: reduced\nbuild cells: 60\nMiri cells: 0\n"
"event: pull_request\ncoverage: reduced\nbuild cells: 60\nMiri cells: 0\nsemver cells: 3\n"
));
assert_eq!(output.lines().filter(|line| line.starts_with("build: ")).count(), 60);
assert!(!output.lines().any(|line| line.starts_with("miri: ")));
assert_eq!(output.lines().filter(|line| line.starts_with("semver: ")).count(), 3);
}

#[test]
Expand Down Expand Up @@ -974,7 +994,9 @@ mod tests {
let job_outputs = fs::read_to_string(github_output).unwrap();
assert!(job_outputs.starts_with(&format!("{BUILD_MATRIX_OUTPUT}={{\"include\":[")));
assert!(job_outputs.contains(&format!("{MIRI_MATRIX_OUTPUT}={{\"include\":[]}}\n")));
assert!(job_outputs.ends_with(&format!("{MIRI_ENABLED_OUTPUT}=false\n")));
assert!(job_outputs.contains(&format!("{MIRI_ENABLED_OUTPUT}=false\n")));
assert!(job_outputs.contains(&format!("{SEMVER_MATRIX_OUTPUT}={{\"include\":[")));
assert!(job_outputs.ends_with(&format!("{SEMVER_ENABLED_OUTPUT}=true\n")));
let artifact_json: serde_json::Value =
serde_json::from_slice(&fs::read(artifact).unwrap()).unwrap();
assert_eq!(artifact_json["event"], "pull_request");
Expand Down
Loading