Close the loop: build, sanity-check, and publish #1
13 changed files with 23 additions and 23 deletions
|
|
@ -37,7 +37,7 @@ args = ["test"]
|
|||
# orchestration too (hence its own low number) — its one pure decision
|
||||
# function (decide_tier_action) is unit tested and should stay visible in
|
||||
# this report; excluding the whole file would hide that signal along with
|
||||
# the untested parts. See ARCHITECTURE.md > "separate pure decision logic
|
||||
# the untested parts. See docs/ARCHITECTURE.md > "separate pure decision logic
|
||||
# from I/O."
|
||||
[tasks.coverage-report]
|
||||
command = "cargo"
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub struct BuildResult {
|
|||
/// artifact, then runs `makepkg` in `build_dir`.
|
||||
///
|
||||
/// Deliberately one fixed "prebuilt binary" shape, not a templating engine
|
||||
/// — see SPEC.md > Scaling > Template reuse. Covers the two shapes the two
|
||||
/// — see docs/SPEC.md > Scaling > Template reuse. Covers the two shapes the two
|
||||
/// currently-tracked packages actually need: a bare-binary download
|
||||
/// (scaleway-cli) and a tarball containing a same-named directory (uv).
|
||||
/// Extend when a third real shape shows up rather than guessing at
|
||||
|
|
@ -71,7 +71,7 @@ pub fn build(req: &BuildRequest, build_dir: &Path) -> Result<BuildResult> {
|
|||
}
|
||||
|
||||
/// Builds the PKGBUILD text for `req`, validating every upstream-controlled
|
||||
/// string first (see SPEC.md > Architecture > Builder: "strict validation
|
||||
/// string first (see docs/SPEC.md > Architecture > Builder: "strict validation
|
||||
/// on any upstream-controlled string ... never unescaped interpolation").
|
||||
/// Pure and side-effect-free so it's testable without invoking `makepkg`.
|
||||
fn generate_pkgbuild(req: &BuildRequest) -> Result<String> {
|
||||
|
|
@ -155,7 +155,7 @@ fn archive_stem(asset_name: &str) -> Option<&str> {
|
|||
/// `asset_name` (via `install_source`) and `binary_name` end up embedded
|
||||
/// so `${srcdir}`/`${pkgdir}` can expand. A single check covering both
|
||||
/// contexts is safer than trying to remember which fields land in which
|
||||
/// quoting style. See SPEC.md > Architecture > Builder ("never unescaped
|
||||
/// quoting style. See docs/SPEC.md > Architecture > Builder ("never unescaped
|
||||
/// interpolation").
|
||||
fn validate_shell_safe(field: &str, value: &str) -> Result<()> {
|
||||
if value.contains(['\'', '\n', '$', '`', '\\']) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ struct PackageFile {
|
|||
pub struct Package {
|
||||
pub repo: String,
|
||||
/// Exact GitHub release asset name (still not a glob — see
|
||||
/// SPEC.md > Architecture > Fetcher), optionally containing a
|
||||
/// docs/SPEC.md > Architecture > Fetcher), optionally containing a
|
||||
/// `{version}` placeholder for projects whose asset names embed the
|
||||
/// version (e.g. `scaleway-cli_{version}_linux_amd64`). Substituted via
|
||||
/// `checker::version_from_tag` before matching.
|
||||
|
|
@ -29,7 +29,7 @@ pub struct Package {
|
|||
/// the repo name). Defaults to the package name when omitted.
|
||||
pub binary_name: Option<String>,
|
||||
/// Post-build correctness check (not a security control — see
|
||||
/// SPEC.md > Verification trust tiers). Runs `command` against the
|
||||
/// docs/SPEC.md > Verification trust tiers). Runs `command` against the
|
||||
/// freshly built binary and confirms `version_regex`'s capture group
|
||||
/// matches the version pkgwatch believes it just built.
|
||||
pub sanity_check: Option<SanityCheck>,
|
||||
|
|
@ -53,7 +53,7 @@ pub struct SanityCheck {
|
|||
#[serde(tag = "method", rename_all = "kebab-case")]
|
||||
pub enum Verification {
|
||||
/// Tier 4: proves transport integrity only, not authorship. See
|
||||
/// SPEC.md > Verification trust tiers. `checksum_asset_pattern` may
|
||||
/// docs/SPEC.md > Verification trust tiers. `checksum_asset_pattern` may
|
||||
/// also contain a `{version}` placeholder, same as `asset_pattern`.
|
||||
SameOriginSha256 { checksum_asset_pattern: String },
|
||||
/// Tier 2: GitHub build-provenance attestation, verified via `gh
|
||||
|
|
@ -71,7 +71,7 @@ impl Verification {
|
|||
}
|
||||
|
||||
/// Loads every `*.toml` file in `dir` (the `packages.d/` layout from
|
||||
/// SPEC.md > Scaling to many packages), keyed by package name.
|
||||
/// docs/SPEC.md > Scaling to many packages), keyed by package name.
|
||||
pub fn load_packages_dir(dir: &Path) -> Result<Vec<(String, Package)>> {
|
||||
let mut out = Vec::new();
|
||||
for entry in std::fs::read_dir(dir).with_context(|| format!("reading {}", dir.display()))? {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Two functions, shared by two real callers (`verifier`, `builder`) —
|
||||
//! not a general-purpose utils dump. See ARCHITECTURE.md > "organize by
|
||||
//! not a general-purpose utils dump. See docs/ARCHITECTURE.md > "organize by
|
||||
//! pipeline stage, not by layer" for why that distinction matters.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Entry point: parses `argv` and dispatches to `pipeline`. Nothing here
|
||||
//! makes a network/subprocess call or contains a decision worth a test —
|
||||
//! see ARCHITECTURE.md > "main is a dispatcher, not the program."
|
||||
//! see docs/ARCHITECTURE.md > "main is a dispatcher, not the program."
|
||||
|
||||
mod builder;
|
||||
mod checker;
|
||||
|
|
@ -20,8 +20,8 @@ use anyhow::{Result, bail};
|
|||
|
||||
/// check -> fetch -> verify -> build -> sanity-check -> publish, for
|
||||
/// whatever is in packages.d/. Tier 1-3 passes auto-publish; tier 4-6
|
||||
/// passes queue for `pkgwatch review`. See SPEC.md > Architecture for what
|
||||
/// each stage does, and ARCHITECTURE.md for how the code implementing it
|
||||
/// passes queue for `pkgwatch review`. See docs/SPEC.md > Architecture for what
|
||||
/// each stage does, and docs/ARCHITECTURE.md for how the code implementing it
|
||||
/// is organized.
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Orchestrates one run of check -> fetch -> verify -> build ->
|
||||
//! sanity-check -> publish across every configured package, plus the
|
||||
//! `review` subcommand for tier 4-6 approvals. The only module that calls
|
||||
//! more than one other pipeline-stage module — see ARCHITECTURE.md > "main
|
||||
//! more than one other pipeline-stage module — see docs/ARCHITECTURE.md > "main
|
||||
//! is a dispatcher, not the program" for why this lives here and not in
|
||||
//! `main.rs`.
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ pub fn run_check() -> Result<()> {
|
|||
|
||||
/// What to do about a package after verification, derived purely from the
|
||||
/// verification outcome and whether this exact version is already queued
|
||||
/// for review — no I/O. See ARCHITECTURE.md > "separate pure decision
|
||||
/// for review — no I/O. See docs/ARCHITECTURE.md > "separate pure decision
|
||||
/// logic from I/O."
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum TierAction {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::process::Command;
|
||||
|
||||
/// Pacman's system-wide config — hardcoded like the rest of this tool's
|
||||
/// Arch/Manjaro-specific assumptions (see SPEC.md > Scope).
|
||||
/// Arch/Manjaro-specific assumptions (see docs/SPEC.md > Scope).
|
||||
const PACMAN_CONF: &str = "/etc/pacman.conf";
|
||||
|
||||
/// Copies the built package into `repo_dir` and runs `repo-add` against
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Post-build correctness check: runs the freshly built binary and
|
||||
//! confirms it reports the version pkgwatch believes it just built. Not a
|
||||
//! security control — see SPEC.md > Verification trust tiers.
|
||||
//! security control — see docs/SPEC.md > Verification trust tiers.
|
||||
|
||||
use crate::config::SanityCheck;
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
|
@ -14,7 +14,7 @@ use std::process::Command;
|
|||
/// whatever's already on the system. Confirms `check.version_regex`'s
|
||||
/// capture group matches `expected_version`.
|
||||
///
|
||||
/// Correctness check only, not a security control — see SPEC.md >
|
||||
/// Correctness check only, not a security control — see docs/SPEC.md >
|
||||
/// Verification trust tiers. Catches checker bugs and mangled/wrong-asset
|
||||
/// downloads, not malicious releases.
|
||||
pub fn run(check: &SanityCheck, pkg_bin_dir: &Path, expected_version: &str) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::path::Path;
|
|||
/// Last-known-published version per package, so re-runs don't re-flag a
|
||||
/// version already handled. Deliberately just one file per package for
|
||||
/// now — this is where a real review-queue persistence layer plugs in
|
||||
/// later (see SPEC.md > Architecture > Reviewer queue).
|
||||
/// later (see docs/SPEC.md > Architecture > Reviewer queue).
|
||||
pub fn load_last_version(state_dir: &Path, name: &str) -> Option<String> {
|
||||
std::fs::read_to_string(state_dir.join(format!("{name}.version")))
|
||||
.ok()
|
||||
|
|
@ -22,7 +22,7 @@ pub fn save_last_version(state_dir: &Path, name: &str, version: &str) -> Result<
|
|||
}
|
||||
|
||||
/// Tag currently awaiting human review for a tier 4-6 package (see
|
||||
/// SPEC.md > Architecture > Reviewer queue), if any. Separate from
|
||||
/// docs/SPEC.md > Architecture > Reviewer queue), if any. Separate from
|
||||
/// `load_last_version`/`save_last_version`: approving a review doesn't
|
||||
/// mean future versions auto-publish, so the two must be tracked
|
||||
/// independently.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Test-only fixture helpers shared across modules' `#[cfg(test)]` code
|
||||
//! (`publisher`, `sanity`) — not production code, and not built outside
|
||||
//! `cargo test`. See ARCHITECTURE.md > "organize by pipeline stage, not
|
||||
//! `cargo test`. See docs/ARCHITECTURE.md > "organize by pipeline stage, not
|
||||
//! by layer": this exists to remove one specific piece of duplication
|
||||
//! (two near-identical copies of "write an executable shell script"), not
|
||||
//! as a general test-utils dump.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Runs the trust-tier-specific check declared for a package against a
|
||||
//! downloaded artifact, and reports a pass/fail plus the tier it implies.
|
||||
//! The only module that knows what each `Verification::method` actually
|
||||
//! proves — see SPEC.md > Verification trust tiers.
|
||||
//! proves — see docs/SPEC.md > Verification trust tiers.
|
||||
|
||||
use crate::checker::version_from_tag;
|
||||
use crate::config::Verification;
|
||||
|
|
@ -19,7 +19,7 @@ pub struct VerificationResult {
|
|||
}
|
||||
|
||||
/// Runs the verification method declared for a package against a
|
||||
/// downloaded artifact. See SPEC.md > Verification trust tiers for what
|
||||
/// downloaded artifact. See docs/SPEC.md > Verification trust tiers for what
|
||||
/// each tier does and does not prove.
|
||||
pub fn verify(
|
||||
client: &reqwest::blocking::Client,
|
||||
|
|
@ -59,7 +59,7 @@ pub fn verify(
|
|||
passed,
|
||||
justification: if passed {
|
||||
"same-origin sha256 matched — proves transport integrity only, \
|
||||
not authorship (see tier 4 in SPEC.md)"
|
||||
not authorship (see tier 4 in docs/SPEC.md)"
|
||||
.into()
|
||||
} else {
|
||||
format!("sha256 mismatch: expected {expected}, got {actual}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue