Commit the previously-untracked standard directories and catalog them in the
root README alongside proof:
- aac/ — communication cargo class: portable boards + a ten-level
offline-first cascade (Apache-2.0 code / CC-BY-4.0 spec).
- map/ — geospatial cargo class: range-requestable signed tilesets +
an F0–F4 fidelity cascade (Apache-2.0 code / CC-BY-4.0 spec).
- sandbox/— isolated, metered, attested execution across the heterogeneous
compute fabric (Apache-2.0 code / CC-BY-4.0 spec).
- jouleclaw-otel-authz, jouleclaw-trust — new jouleclaw reference crates.
Root README now lists aac, map, sandbox, and proof in the catalog, the tree,
and the contribution guide.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
256 lines
9.5 KiB
Rust
256 lines
9.5 KiB
Rust
//! Map conformance harness (SPEC §8).
|
|
//!
|
|
//! Runs five positive suites and a negative pack against deterministic,
|
|
//! real-crypto vectors. Every check runs for real — there are no placeholder
|
|
//! SKIPs. Any FAIL makes the run non-zero (CI-ready). The same vectors are
|
|
//! emitted to `conformance/v1/*.json` by the `gen-vectors` binary so a
|
|
//! third-party implementation in any language can certify against the published
|
|
//! bytes.
|
|
|
|
use ed25519_dalek::SigningKey;
|
|
use map_cascade::{resolve, CascadeError, Resolution, TileSource};
|
|
use map_receipt::{Energy, Provenance, Receipt};
|
|
use map_schema::{
|
|
verify_chain, AltBand, AltSchedule, CellKey, ContentId, DirEntry, Directory, Lod, Manifest,
|
|
ManifestBody, RevisionBody, S2CellId, SchemaError, Tier, TimeBucket,
|
|
};
|
|
|
|
pub const STEWARD: &str = "did:example:map-reference";
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct Report {
|
|
pub pass: u32,
|
|
pub fail: u32,
|
|
pub lines: Vec<String>,
|
|
}
|
|
|
|
impl Report {
|
|
fn ok(&mut self, name: &str) {
|
|
self.pass += 1;
|
|
self.lines.push(format!(" PASS {name}"));
|
|
}
|
|
fn bad(&mut self, name: &str, why: impl std::fmt::Display) {
|
|
self.fail += 1;
|
|
self.lines.push(format!(" FAIL {name}: {why}"));
|
|
}
|
|
/// `check(true, …)` passes; `check(false, …)` fails.
|
|
fn check(&mut self, name: &str, cond: bool, why: &str) {
|
|
if cond {
|
|
self.ok(name);
|
|
} else {
|
|
self.bad(name, why);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn test_key(seed: u8) -> SigningKey {
|
|
SigningKey::from_bytes(&[seed; 32])
|
|
}
|
|
|
|
fn cell(s2: u64, lod: u8) -> CellKey {
|
|
CellKey { s2: S2CellId(s2), alt: AltBand(0), time: TimeBucket::ATEMPORAL, lod: Lod(lod) }
|
|
}
|
|
|
|
/// A tier-presence map for the cascade suite.
|
|
struct FixtureSource {
|
|
present: Vec<(u64, Vec<Tier>)>,
|
|
}
|
|
impl TileSource for FixtureSource {
|
|
fn cached(&self, _c: &CellKey) -> Option<ContentId> {
|
|
None
|
|
}
|
|
fn tile(&self, c: &CellKey, tier: Tier) -> Option<ContentId> {
|
|
self.present
|
|
.iter()
|
|
.find(|(s2, _)| *s2 == c.s2.0)
|
|
.filter(|(_, tiers)| tiers.contains(&tier))
|
|
.map(|_| ContentId::of_bytes(&[c.s2.0 as u8, tier.rank()]))
|
|
}
|
|
}
|
|
|
|
/// Build the canonical sample tileset directory shared by C2/C3.
|
|
fn sample_directory() -> Directory {
|
|
Directory::new(vec![
|
|
DirEntry { key: cell(0x10, 0), offset: 0, len: 128, content_id: ContentId::of_bytes(b"a") },
|
|
DirEntry { key: cell(0x20, 0), offset: 128, len: 64, content_id: ContentId::of_bytes(b"b") },
|
|
DirEntry { key: cell(0x30, 0), offset: 256, len: 32, content_id: ContentId::of_bytes(b"c") },
|
|
])
|
|
}
|
|
|
|
fn sample_manifest(sk: &SigningKey, dir: &Directory) -> Result<Manifest, SchemaError> {
|
|
ManifestBody {
|
|
spec_version: "0.1.0".into(),
|
|
datum: "WGS84".into(),
|
|
alt_schedule: AltSchedule::default(),
|
|
dir_root: dir.root()?,
|
|
bounds: Some((cell(0x10, 0), cell(0x30, 0))),
|
|
disclaimer: None,
|
|
publisher: sk.verifying_key(),
|
|
}
|
|
.sign(sk)
|
|
}
|
|
|
|
/// Run all suites. Returns the report; caller decides exit code.
|
|
pub fn run() -> Report {
|
|
let mut r = Report::default();
|
|
c1_cell_key(&mut r);
|
|
c2_directory(&mut r);
|
|
c3_manifest_revision(&mut r);
|
|
c4_cascade(&mut r);
|
|
c5_receipt(&mut r);
|
|
c_neg(&mut r);
|
|
r
|
|
}
|
|
|
|
/// C1 — cell key canonical serialization + content id is deterministic.
|
|
fn c1_cell_key(r: &mut Report) {
|
|
let k = cell(0x1234_0000_0000_0001, 5);
|
|
match (ContentId::of(&k), ContentId::of(&k)) {
|
|
(Ok(a), Ok(b)) => r.check("C1.cell_key_deterministic", a == b, "content id not stable"),
|
|
_ => r.bad("C1.cell_key_deterministic", "serialization failed"),
|
|
}
|
|
// Distinct axes -> distinct ids (the time axis is first-class).
|
|
let t1 = CellKey { time: TimeBucket { start_tai_ms: 1, width_ms: 3_600_000 }, ..k };
|
|
let t2 = CellKey { time: TimeBucket { start_tai_ms: 2, width_ms: 3_600_000 }, ..k };
|
|
r.check(
|
|
"C1.time_axis_distinguishes",
|
|
ContentId::of(&t1).ok() != ContentId::of(&t2).ok(),
|
|
"different time buckets hashed equal",
|
|
);
|
|
}
|
|
|
|
/// C2 — directory sorts, coalesces ranges, and a reader can verify payloads.
|
|
fn c2_directory(r: &mut Report) {
|
|
let dir = sample_directory();
|
|
r.check("C2.directory_sorted", dir.is_sorted(), "directory not Hilbert-sorted");
|
|
// Two adjacent cells -> one coalesced byte span.
|
|
let ranges = dir.ranges_for(&[cell(0x10, 0), cell(0x20, 0)]);
|
|
r.check("C2.range_coalesced", ranges == vec![(0, 192)], "range planner did not coalesce");
|
|
// Payload hash check: the directory's content_id must match the bytes.
|
|
let entry = dir.locate(&cell(0x10, 0)).expect("located");
|
|
r.check(
|
|
"C2.payload_hash_verifies",
|
|
entry.content_id == ContentId::of_bytes(b"a"),
|
|
"payload hash mismatch",
|
|
);
|
|
}
|
|
|
|
/// C3 — manifest signs/verifies; revision chain validates; tamper fails.
|
|
fn c3_manifest_revision(r: &mut Report) {
|
|
let sk = test_key(7);
|
|
let dir = sample_directory();
|
|
let m = match sample_manifest(&sk, &dir) {
|
|
Ok(m) => m,
|
|
Err(e) => return r.bad("C3.manifest_sign", e),
|
|
};
|
|
r.check("C3.manifest_verifies", m.verify().is_ok(), "good manifest failed to verify");
|
|
|
|
// A revision chains onto the manifest root.
|
|
let root0 = m.root().unwrap();
|
|
let root1 = ContentId::of_bytes(b"next-cycle-root");
|
|
let rev = RevisionBody { prev_root: root0, new_root: root1, timestamp_tai_ms: 1, publisher: sk.verifying_key() }
|
|
.sign(&sk)
|
|
.unwrap();
|
|
match verify_chain(root0, &[rev]) {
|
|
Ok(head) => r.check("C3.revision_chain", head == root1, "chain head wrong"),
|
|
Err(e) => r.bad("C3.revision_chain", e),
|
|
}
|
|
}
|
|
|
|
/// C4 — cascade closes at the right tier and falls back to F1.
|
|
fn c4_cascade(r: &mut Report) {
|
|
let src = FixtureSource {
|
|
present: vec![
|
|
(0xAA, vec![Tier::Base, Tier::Field, Tier::Feature, Tier::Photoreal]),
|
|
(0xBB, vec![Tier::Base]), // floor only -> must fall back
|
|
],
|
|
};
|
|
let close = |s2, req| resolve(&src, &cell(s2, 0), req);
|
|
match close(0xAA, Tier::Field) {
|
|
Ok(Resolution { tier, fell_back, .. }) => {
|
|
r.check("C4.respects_ceiling", tier == Tier::Field && !fell_back, "did not stop at ceiling");
|
|
}
|
|
Err(e) => r.bad("C4.respects_ceiling", e),
|
|
}
|
|
match close(0xBB, Tier::Photoreal) {
|
|
Ok(Resolution { tier, fell_back, .. }) => {
|
|
r.check("C4.falls_back_to_floor", tier == Tier::Base && fell_back, "did not fall back to F1");
|
|
}
|
|
Err(e) => r.bad("C4.falls_back_to_floor", e),
|
|
}
|
|
}
|
|
|
|
/// C5 — receipts seal, verify, and enforce the provenance coupling.
|
|
fn c5_receipt(r: &mut Report) {
|
|
let sk = test_key(9);
|
|
let rcpt = Receipt {
|
|
cell: cell(0x42, 0),
|
|
content_id: ContentId::of_bytes(b"tile"),
|
|
tier: Tier::Photoreal,
|
|
energy: Energy::estimate_tier(Tier::Photoreal),
|
|
sequence: 1,
|
|
nonce: [0, 0, 0, 0, 0, 0, 0, 1],
|
|
};
|
|
match rcpt.seal(&sk, STEWARD) {
|
|
Ok(env) => {
|
|
let ok = rcpt.verify_envelope(&env, STEWARD, &sk.verifying_key().to_bytes()).is_ok();
|
|
r.check("C5.receipt_roundtrip", ok, "sealed receipt failed to verify");
|
|
let wrong = rcpt.verify_envelope(&env, "did:example:other", &sk.verifying_key().to_bytes()).is_err();
|
|
r.check("C5.receipt_rejects_wrong_steward", wrong, "verified under wrong steward");
|
|
}
|
|
Err(e) => r.bad("C5.receipt_roundtrip", e),
|
|
}
|
|
}
|
|
|
|
/// C-neg — the negative pack: tampered directory root, broken chain, dishonest receipt.
|
|
fn c_neg(r: &mut Report) {
|
|
let sk = test_key(7);
|
|
let dir = sample_directory();
|
|
let mut m = sample_manifest(&sk, &dir).unwrap();
|
|
m.body.dir_root = ContentId::of_bytes(b"tampered");
|
|
r.check("Cneg.tampered_manifest_fails", m.verify().is_err(), "tampered manifest verified");
|
|
|
|
// Revision signed by an unendorsed key.
|
|
let attacker = test_key(66);
|
|
let root0 = ContentId::of_bytes(b"r0");
|
|
let bad_rev = RevisionBody { prev_root: root0, new_root: ContentId::of_bytes(b"r1"), timestamp_tai_ms: 1, publisher: sk.verifying_key() }
|
|
.sign(&attacker) // signed by the wrong key
|
|
.unwrap();
|
|
r.check("Cneg.unendorsed_revision_fails", verify_chain(root0, &[bad_rev]).is_err(), "unendorsed revision verified");
|
|
|
|
// Receipt claiming HwShunt with no measured figure.
|
|
let rcpt = Receipt {
|
|
cell: cell(1, 0),
|
|
content_id: ContentId::of_bytes(b"x"),
|
|
tier: Tier::Base,
|
|
energy: Energy { measured_uj: None, estimated_uj: 1, provenance: Provenance::HwShunt },
|
|
sequence: 1,
|
|
nonce: [0; 8],
|
|
};
|
|
r.check("Cneg.hwshunt_without_measured_fails", rcpt.seal(&test_key(9), STEWARD).is_err(), "dishonest receipt sealed");
|
|
|
|
// An out-of-order directory must refuse to produce a root.
|
|
let unsorted = Directory {
|
|
entries: vec![
|
|
DirEntry { key: cell(0x30, 0), offset: 0, len: 1, content_id: ContentId::of_bytes(b"c") },
|
|
DirEntry { key: cell(0x10, 0), offset: 1, len: 1, content_id: ContentId::of_bytes(b"a") },
|
|
],
|
|
};
|
|
r.check(
|
|
"Cneg.unsorted_directory_refuses_root",
|
|
matches!(unsorted.root(), Err(SchemaError::Unsorted)),
|
|
"unsorted directory produced a root",
|
|
);
|
|
let _ = CascadeError::UncoveredRegion; // referenced for completeness
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
#[test]
|
|
fn all_suites_pass() {
|
|
let report = run();
|
|
assert_eq!(report.fail, 0, "conformance failures:\n{}", report.lines.join("\n"));
|
|
assert!(report.pass >= 12, "expected the full suite to run, got {} checks", report.pass);
|
|
}
|
|
}
|