# gate-contract
Fail-closed gate contracts for staged build pipelines, in dependency-free Rust.
A pipeline that only knows PASS and FAIL has a third state hiding inside it: the gate that
could not be evaluated, because a credential was missing, an API timed out, or the value it
needed was never produced. Treating that as a pass is how a run reaches deploy having
checked nothing.
The rule this crate enforces:
> A **blocking** gate halts the run when it fails **and** when it cannot be evaluated.
> "Could not check" is not "fine".
```rust
use gate_contract::{Gate, Known, Pipeline, Stage};
let run = Pipeline::new()
.with_stage(Stage::new("intake").with_gate(Gate::pass("scope-present")))
.with_stage(Stage::new("harvest").with_gate(Gate::unevaluable("rdap", "no credential")))
.with_stage(Stage::new("build").with_gate(Gate::pass("never-reached")))
.run();
assert!(run.halted());
assert_eq!(run.halted_stage(), Some("harvest"));
assert_eq!(run.stages_skipped(), 1); // never ran, so it has no outcome
// An unknown input cannot pass a gate by accident
let coverage: Known<u32> = Known::Unknown;
let g = Gate::check_known("coverage-floor", &coverage, |c| *c >= 50, "below floor");
assert!(g.halts());
```
- `Outcome` — `Pass` / `Fail` / `Unevaluable`, each with a reason.
- `Known<T>` — `Unknown` propagates through `map` and `zip_with` instead of decaying to a default.
- `Severity` — blocking or advisory; advisory failures are warnings in the report.
- `Stage::evaluate` visits every gate, so one run reports every problem, and names the first that halts.
- `Pipeline::run` stops at the first halting stage and reports how many stages never ran.
## Install
```toml
[dependencies]
gate-contract = "0.1"
```
`#![forbid(unsafe_code)]`, no dependencies, MSRV 1.63.
## Licence
MIT OR Apache-2.0.
Extracted from a real staged build pipeline, whose first blocking gate is worked through in
public at <https://aiwebsitepipeline.com/niche-score.html>: six subscores, the weights that
combine them, and the thresholds that decide the run — 55 and above proceeds, 40 to 55
proceeds under a page cap, below 40 the run halts. Worth reading before you decide what your
own blocking gates should refuse.