`joule-code-conformance` ships a complete pack verifier — `verify_pack` reads all six files of `joule-code/conformance/v1`, recomputes every derived field (each `op_id` is BLAKE3 over canonical CBOR of its `CodingOp`, each binding and receipt signature a real Ed25519 signature over the spec's preimage) and returns a FAIL count that `main()` turns into a non-zero exit. A dedicated workflow, `joule-code-conformance.yml`, already runs it. The pack is not unchecked, and this commit is not what makes it checked. It closes the two gaps that gate cannot see. **It cannot distinguish a verified pack from a skipped one.** The gate is `grep -Eq '0 failed'` over the summary line, and `verify_pack` is documented to let SKIPs pass. So `0 passed, 0 failed, 24 skipped` satisfies that grep exactly as well as `24 passed, 0 failed, 0 skipped` — verified by running the three summary strings through the gate's own expression. A pack that shrank to one vector passes it too. This pins the count and requires `skip == 0`, which is the distinction the grep cannot draw. The count is pinned rather than bounded because `>= 24` would let a case be deleted as long as another was added. **It is `paths:`-filtered to `joule-code/**`, and this crate reaches outside it.** `joule-code-conformance` depends on `jcp-schema`, `jcp-grant` and `jcp-receipt` by relative path across workspaces, and `joule-code-joulecontract` on `joulecontract-schema`. A change in `jcp` to canonical CBOR or the receipt preimage would change the `op_id`s and signatures this pack pins, and would not touch `joule-code/**` — so that workflow would not run. `standards-build.yml` carries a deliberate "NO `paths:` filter" and runs `cargo test --workspace`, confirmed by running that exact command rather than reasoning about it. The second test is the load-bearing one. A verifier reporting PASS on a pack it never read is worse than none, because it manufactures confidence — a tampered copy of `map`'s pack once still reported 15/15. So the tamper case does not trust the report: it flips one hex digit of a signature in a scratch copy and requires the verifier to notice. The offset is found by scanning to the value's opening quote, since the pack is column-aligned and the run of spaces is not something a test should depend on. Falsified, each assertion separately: corrupting a published signature fails the verify test; claiming 25 vectors fails the count; neutering the flip to a no-op fails the tamper test with "the verifier is not checking what it publishes". The pack was restored afterwards and is byte-identical to main. One honest limit: I could not force a SKIP to confirm that assertion fires, so unlike the other three it is reasoned rather than falsified. Corrects the workflow comment claiming nothing in CI validates the published `conformance/v1` files on disk. That stopped being true with the `tests/pack.rs` suites, and a missing `conformance:` key is not evidence a pack is unchecked — `joule-code` and `sandbox` are both covered without one. Co-Authored-By: Claude Opus 5 <[email protected]>
150 lines
7.1 KiB
Rust
150 lines
7.1 KiB
Rust
//! Run the published `joule-code/conformance/v1` pack from inside the test suite.
|
|
//!
|
|
//! This crate ships a complete pack verifier — `verify_pack`, reachable as
|
|
//! `joule-code-conformance verify <pack-dir>`, which returns a FAIL count that
|
|
//! `main()` turns into a non-zero exit. A dedicated workflow,
|
|
//! `.github/workflows/joule-code-conformance.yml`, already runs it. So the pack is
|
|
//! not unchecked, and this file is not what makes it checked.
|
|
//!
|
|
//! What it adds is coverage in the two places that gate misses.
|
|
//!
|
|
//! **It cannot tell a verified pack from a skipped one.** The gate is
|
|
//! `grep -Eq '0 failed'` over the summary line. `verify_pack` is documented to let
|
|
//! SKIPs pass — they mark derived fields pending regeneration — so
|
|
//! `0 passed, 0 failed, 24 skipped` satisfies that grep exactly as well as
|
|
//! `24 passed, 0 failed, 0 skipped` does, and so does a pack that shrank to one
|
|
//! vector. This file pins the count and requires `skip == 0`, which is the
|
|
//! distinction the grep cannot draw.
|
|
//!
|
|
//! **It is `paths:`-filtered to `joule-code/**`, and this crate reaches outside
|
|
//! that.** `joule-code-conformance` depends on `jcp-schema`, `jcp-grant` and
|
|
//! `jcp-receipt` by relative path across workspaces, and `joule-code-joulecontract`
|
|
//! on `joulecontract-schema`. A change in `jcp` to canonical CBOR or the receipt
|
|
//! preimage would change the `op_id`s and signatures this pack pins — and would not
|
|
//! touch `joule-code/**`, so that workflow would not run. `standards-build.yml`
|
|
//! carries a deliberate "NO `paths:` filter" and runs `cargo test --workspace`, so
|
|
//! hosting the call here covers the cross-standard case.
|
|
//!
|
|
//! Hosting it as a test also sidesteps an argument problem worth recording: the
|
|
//! other five conformance binaries in this repo take no arguments, so the shared
|
|
//! matrix step invokes them as `cargo run -p <crate> --bin <crate>`. This one
|
|
//! requires `verify <pack-dir>` and exits FAILURE given none, which is why it needs
|
|
//! its own workflow rather than a `conformance:` key.
|
|
//!
|
|
//! The second test is the one that matters most. A verifier that reports PASS on a
|
|
//! pack it never really read is worse than no verifier, because it manufactures
|
|
//! confidence — a tampered copy of another standard's pack once still reported
|
|
//! 15/15. So the tamper case does not trust `verify_pack`'s output; it corrupts a
|
|
//! copy of the pack and requires the verifier to notice.
|
|
//!
|
|
use std::path::PathBuf;
|
|
|
|
use joule_code_conformance::verify_pack;
|
|
|
|
/// Every vector in the published pack, counted so that losing one is not silent.
|
|
///
|
|
/// Pinned rather than bounded: `>= 24` would let a vector be deleted as long as
|
|
/// another was added. When you legitimately add a case, update this number — that
|
|
/// edit is the point, it makes the corpus growing a reviewed event.
|
|
const PUBLISHED_VECTORS: u32 = 24;
|
|
|
|
fn pack_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../../conformance/v1")
|
|
.canonicalize()
|
|
.expect("the conformance pack must exist next to the spec")
|
|
}
|
|
|
|
/// The published pack still passes against the current implementation.
|
|
///
|
|
/// `verify_pack` recomputes each vector's derived fields — every `op_id` is BLAKE3
|
|
/// over canonical CBOR of its `CodingOp`, every binding and receipt signature is a
|
|
/// real Ed25519 signature over the spec's preimage — so this fails if the
|
|
/// implementation drifts from the bytes a third party downloads.
|
|
#[test]
|
|
fn the_published_pack_still_verifies() {
|
|
let report = verify_pack(&pack_dir()).expect("the pack must be readable and well-formed");
|
|
|
|
assert_eq!(
|
|
report.fail,
|
|
0,
|
|
"published vectors failed against the implementation:\n{}",
|
|
report
|
|
.lines
|
|
.iter()
|
|
.filter(|l| l.contains("FAIL"))
|
|
.cloned()
|
|
.collect::<Vec<_>>()
|
|
.join("\n")
|
|
);
|
|
|
|
// A SKIP is an unverified vector. `verify_pack` is documented to let skips pass
|
|
// — they mark derived fields pending regeneration — so a skip would otherwise be
|
|
// indistinguishable from a check that ran. The pack currently has none, and
|
|
// `conformance/v1/regeneration.md` states that as a property ("all vectors pass,
|
|
// none skipped"). Pin it, so reintroducing a placeholder is a deliberate act.
|
|
assert_eq!(report.skip, 0, "a vector is skipped, so it is published but unverified");
|
|
|
|
assert_eq!(
|
|
report.pass, PUBLISHED_VECTORS,
|
|
"expected {PUBLISHED_VECTORS} vectors to be checked, {} were — a vector file or \
|
|
case was added or lost",
|
|
report.pass
|
|
);
|
|
}
|
|
|
|
/// The verifier actually reads the bytes: corrupt them and it must object.
|
|
///
|
|
/// Without this, `the_published_pack_still_verifies` proves only that `verify_pack`
|
|
/// returns `fail == 0`, which a verifier that silently skipped everything would also
|
|
/// do. Here a signature is flipped in a scratch copy — the pack on disk is never
|
|
/// touched — and the run must either fail a vector or refuse the pack outright.
|
|
#[test]
|
|
fn a_tampered_pack_is_not_reported_as_passing() {
|
|
let src = pack_dir();
|
|
let tmp = std::env::temp_dir().join(format!("jc-pack-tamper-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
std::fs::create_dir_all(&tmp).expect("scratch dir");
|
|
|
|
for entry in std::fs::read_dir(&src).expect("pack dir reads") {
|
|
let entry = entry.expect("dir entry");
|
|
if entry.path().extension().is_some_and(|e| e == "json") {
|
|
std::fs::copy(entry.path(), tmp.join(entry.file_name())).expect("copy vector file");
|
|
}
|
|
}
|
|
|
|
// Flip one hex digit of the first signature in the file. The value stays
|
|
// well-formed hex of the same length, so this is a cryptographic failure and not
|
|
// a parse error — the verifier has to actually check the signature to catch it.
|
|
//
|
|
// The offset is found by scanning to the opening quote of the value rather than
|
|
// by matching a literal `"signature": "`, because the pack is written with the
|
|
// values column-aligned and the run of spaces is not something a test should
|
|
// depend on.
|
|
let path = tmp.join("signature_vectors.json");
|
|
let text = std::fs::read_to_string(&path).expect("signature vectors read");
|
|
let key = text.find("\"signature\":").expect("pack has a signature field");
|
|
let at = 1 + key
|
|
+ text[key + "\"signature\":".len()..]
|
|
.find('"')
|
|
.expect("signature field has a value")
|
|
+ "\"signature\":".len();
|
|
let mut bytes = text.into_bytes();
|
|
assert!(bytes[at].is_ascii_hexdigit(), "did not land on the signature's hex");
|
|
bytes[at] = if bytes[at] == b'a' { b'b' } else { b'a' };
|
|
std::fs::write(&path, &bytes).expect("write tampered vectors");
|
|
|
|
// Refusing the pack outright is also a detection rather than a miss, so only the
|
|
// `Ok` case carries an assertion: what must not happen is a clean bill of health.
|
|
if let Ok(report) = verify_pack(&tmp) {
|
|
assert!(
|
|
report.fail > 0,
|
|
"a forged signature was reported as {} passed / {} failed — the verifier is \
|
|
not checking what it publishes",
|
|
report.pass,
|
|
report.fail
|
|
);
|
|
}
|
|
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
}
|