Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/lib/tests/the_meter_is_what_answers.rs

//! **The meter plane is live, and these are the four things that says.**
//!
//! Until the wiring lane, every meter loaded with `NoServices` — the three host
//! services refuse by name — and the metering doors counted inline. A module
//! could be installed, granted, verified and reported in the census, and still
//! learn nothing and decide nothing. "Complete but inert" is not a state a test
//! can tell from "working", which is why these exist.
//!
//! Each test here asserts a JOIN, not a shape:
//!
//! 1. the guest is what answers the usage door, and its readings say so;
//! 2. the guest's verdict decides a PUT and decides a promote, through the
//!    `GateSet` the server actually builds — not through a `WasmGate` a test
//!    made for itself;
//! 3. a refusal is a refusal, a notice is not, and an `Err` refuses;
//! 4. the grant table is still the only authority, and no service is reachable
//!    without the matching capability.

use std::sync::Arc;

use holger_plugin_host::abi::v2::{
    meters, AbiRange, Capability, Enforcement, GuestKind, Meter, MeterFacts, MeterHost, MeterScope,
    ReadingValue, WireAuditRecord, WireCallContext, WireCapacity, WireInstant, WireManifest2,
    WireMeterDecl, WireMeterReading, WireSelftest, WireVerb, WireVerdict,
};
use holger_plugin_host::{GrantTable, MeterCatalog, MeterHostServices};
use holger_server_lib::meter_services::{CallStamp, HolgerMeterServices};
use holger_server_lib::promote::{PromotedArtifact, PromotionContext};
use holger_server_lib::Holger;

// ───────────────────────── the guests these tests use ─────────────────────────

/// A meter whose verdict the test chooses and which passes the host's readings
/// through unchanged — the same shape as the open guest's `read`, so what comes
/// out of the usage door is what the SOURCE produced and not what this fixture
/// invented.
struct Passthrough {
    name: &'static str,
    verdict: Result<WireVerdict, String>,
}

impl Passthrough {
    fn allowing(name: &'static str) -> Self {
        Self {
            name,
            verdict: Ok(WireVerdict::Allow),
        }
    }
}

impl Meter for Passthrough {
    fn manifest(&self) -> WireManifest2 {
        WireManifest2 {
            abi: AbiRange::new(2, 2),
            kind: GuestKind::Meter,
            name: self.name.to_string(),
            requires: vec![Capability::UsageRead],
            handler: None,
            meter: Some(MeterFacts {
                accepts_facts: false,
            }),
            identity: None,
            unknown_optional: Vec::new(),
        }
    }
    fn meters(&self) -> Result<Vec<WireMeterDecl>, String> {
        Ok(Vec::new())
    }
    fn read(
        &self,
        host: &dyn MeterHost,
        repository: &str,
    ) -> Result<Vec<WireMeterReading>, String> {
        // The host counts; the guest never substitutes a number of its own.
        host.usage_read(repository)
    }
    fn capacity(&self, host: &dyn MeterHost) -> Result<WireCapacity, String> {
        let _ = host.usage_read(meters::PRODUCT_SCOPE)?;
        Ok(WireCapacity {
            total_bytes: 0,
            servable_bytes: 0,
            used_bytes: 0,
            source: ReadingValue::Refused {
                reason: "fixture".into(),
            },
        })
    }
    fn selftest(&self, _h: &dyn MeterHost) -> Result<WireSelftest, String> {
        Ok(WireSelftest {
            name: "fixture".into(),
            passed: true,
            detail: String::new(),
        })
    }
    fn evaluate(&self, _h: &dyn MeterHost, _c: &WireCallContext) -> Result<WireVerdict, String> {
        self.verdict.clone()
    }
}

/// A guest that asks for everything. Used only to prove the grant refuses it.
struct Greedy;

impl Meter for Greedy {
    fn manifest(&self) -> WireManifest2 {
        WireManifest2 {
            abi: AbiRange::new(2, 2),
            kind: GuestKind::Meter,
            name: "greedy".into(),
            requires: vec![
                Capability::UsageRead,
                Capability::AuditWrite,
                Capability::ClockRead,
                Capability::StoreWrite,
            ],
            handler: None,
            meter: Some(MeterFacts {
                accepts_facts: false,
            }),
            identity: None,
            unknown_optional: Vec::new(),
        }
    }
    fn meters(&self) -> Result<Vec<WireMeterDecl>, String> {
        Ok(Vec::new())
    }
    fn read(&self, _h: &dyn MeterHost, _r: &str) -> Result<Vec<WireMeterReading>, String> {
        Ok(Vec::new())
    }
    fn capacity(&self, _h: &dyn MeterHost) -> Result<WireCapacity, String> {
        Err("greedy".into())
    }
    fn selftest(&self, _h: &dyn MeterHost) -> Result<WireSelftest, String> {
        Err("greedy".into())
    }
    fn evaluate(&self, _h: &dyn MeterHost, _c: &WireCallContext) -> Result<WireVerdict, String> {
        Ok(WireVerdict::Allow)
    }
}

// ───────────────────────────── the server under test ─────────────────────────

/// A holger with one writable file-backed repository, wired the way the serve
/// path wires it. `wire_holger` also builds the meter plane, so what comes back
/// is a server whose plane is REAL — the same one `ReadUsage` and the gates use.
fn server(dir: &std::path::Path) -> Holger {
    let d = dir.display();
    let ron = format!(
        r#"(
    exposed_endpoints: [ ( ron_name: "dev", ron_url: "127.0.0.1:0", ron_http_url: None ) ],
    storage_endpoints: [ ( ron_name: "store", ron_storage_type: "file", ron_path: "{d}/store" ) ],
    repositories: [
        ( ron_name: "releases", ron_repo_type: "rust", ron_data_dir: Some("{d}/releases"), ron_base_url: Some("http://x"), ron_upstreams: [], ron_in: None, ron_out: Some(( ron_storage_endpoint: "store", ron_exposed_endpoint: "dev" )) ),
    ],
)
"#
    );
    let mut holger: Holger =
        ron::de::from_reader(std::io::Cursor::new(ron.as_bytes())).expect("RON parses");
    holger.instantiate_backends().expect("the backend builds");
    holger_server_lib::wire_holger(&mut holger).expect("the config wires");
    holger
}

/// Replace the plane `wire_holger` built with one holding `meter`, granted
/// `usage.read` and nothing else — the same `add_linked` path a compiled-in
/// guest takes, held to the same grant table a loaded module is.
fn with_linked_meter(holger: &mut Holger, meter: impl Meter + Send + Sync + 'static, name: &str) {
    let grants = GrantTable::from_pairs([(name, vec!["usage.read"])]).expect("the grant parses");
    let mut catalog = MeterCatalog::new(grants);
    let services: Arc<dyn MeterHostServices> = Arc::new(HolgerMeterServices::new(
        holger.exposed_endpoints[0]
            .aggregated_routes
            .clone()
            .expect("the door has a route table"),
        Arc::new(holger_server_lib::audit::NoopAuditLog),
    ));
    catalog
        .add_linked(
            name,
            meter,
            Arc::new(holger_plugin_host::DirStore::new(dir_for_store())),
            services,
        )
        .expect("the guest loads under its own grant");
    holger.meter_plane = Some(Arc::new(catalog));
}

fn dir_for_store() -> std::path::PathBuf {
    std::env::temp_dir().join(format!("holger-meter-plane-{}", std::process::id()))
}

// ───────────────────── 1. the guest is what answers ──────────────────────────

#[test]
fn the_loaded_guest_is_what_answers_the_usage_door_and_the_reading_says_so() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    with_linked_meter(&mut holger, Passthrough::allowing("narrator"), "narrator");

    let plane = holger.meter_plane.clone().expect("the plane is built");
    let rows = holger_server_lib::meter_services::read_through_guests(plane.meters(), "releases")
        .expect("the guest answers");

    // The six meters the door has always reported, narrated by the guest.
    let names: Vec<&str> = rows.iter().map(|r| r.name.as_str()).collect();
    for expected in [
        holger_server_lib::metering::METER_ARTIFACTS,
        holger_server_lib::metering::METER_ARCHIVE_BYTES,
        holger_server_lib::metering::METER_BYTES_SERVED,
        holger_server_lib::metering::METER_REQUESTS,
        holger_server_lib::metering::METER_PROXY_UPSTREAM_HITS,
        holger_server_lib::metering::METER_REPOS,
    ] {
        assert!(
            names.contains(&expected),
            "{expected} missing from {names:?}"
        );
    }
    // Every one of them came THROUGH the module, and says which module.
    for r in &rows {
        assert_eq!(
            r.source, "meter:narrator",
            "{} was not narrated by the guest",
            r.name
        );
    }
    // `repos` is a number this server counted and the guest carried unchanged.
    let repos = rows.iter().find(|r| r.name == "repos").unwrap();
    assert!(repos.counted);
    assert_eq!(repos.value, 1);
}

#[test]
fn a_refusal_the_host_made_survives_the_guest_instead_of_becoming_a_zero() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    with_linked_meter(&mut holger, Passthrough::allowing("narrator"), "narrator");
    let plane = holger.meter_plane.clone().unwrap();

    // The product scope over a server wired with NO data root: holger refuses
    // the three storage meters BY NAME, and the reason has to survive the ABI.
    // (With a root it measures them -- see
    // tests/the_storage_meters_measure_a_real_filesystem.rs.)
    let rows = holger_server_lib::meter_services::read_through_guests(
        plane.meters(),
        holger_server_lib::meter_services::product_scope(),
    )
    .expect("the guest answers");
    assert_eq!(rows.len(), 3, "{rows:?}");
    for r in &rows {
        assert!(!r.counted, "{} came back as a measurement", r.name);
        assert_eq!(
            r.value, 0,
            "the field must hold something; `counted` is what disowns it"
        );
        assert!(
            r.refusal_reason.contains("no data root"),
            "the reason was lost across the ABI: {}",
            r.refusal_reason
        );
    }
}

#[test]
fn the_guest_reads_the_same_counter_the_door_would_have_read() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    let routes = holger.exposed_endpoints[0]
        .aggregated_routes
        .clone()
        .expect("routes");
    with_linked_meter(&mut holger, Passthrough::allowing("narrator"), "narrator");
    let plane = holger.meter_plane.clone().unwrap();

    // The same inputs `HolgerMeterServices::new` hands the guest: no data root,
    // no declared storage places.
    let direct =
        holger_server_lib::metering::count(&routes, "releases", None, Some(&Default::default()))
            .expect("the door counts");
    let through =
        holger_server_lib::meter_services::read_through_guests(plane.meters(), "releases").unwrap();

    assert_eq!(
        direct.len(),
        through.len(),
        "the guest dropped or added a meter"
    );
    for d in direct {
        let t = through
            .iter()
            .find(|r| r.name == d.name)
            .expect("every meter survives");
        match d.value {
            Ok(v) => {
                assert!(t.counted, "{} lost its measurement", d.name);
                assert_eq!(t.value, v, "{} changed value across the ABI", d.name);
            }
            Err(reason) => {
                assert!(!t.counted, "{} gained a measurement it never had", d.name);
                assert_eq!(t.refusal_reason, reason, "{} lost its reason", d.name);
            }
        }
    }
}

// ────────────── 2. the verdict decides a PUT and decides a promote ───────────

fn one_artifact() -> (traits::ArtifactId, Vec<u8>) {
    (
        traits::ArtifactId {
            namespace: None,
            name: "widget".into(),
            version: "1.0.0".into(),
        },
        vec![7u8; 11],
    )
}

/// Run `f` over a promotion context. An EMPTY `from_repo` is the ingest PUT
/// boundary; a named one is a promotion hop. That is the one field that differs.
fn over_context<R>(from_repo: &str, f: impl FnOnce(&PromotionContext<'_>) -> R) -> R {
    let (id, bytes) = one_artifact();
    let digest =
        holger_server_lib::attest::ArtifactDigest::parse("sha256", &"ab".repeat(32)).unwrap();
    let set = [PromotedArtifact {
        id: &id,
        ecosystem: "raw",
        digest,
        bytes: &bytes,
        attestations: &[],
    }];
    let ctx = PromotionContext {
        principal: "sub-123",
        from_repo,
        to_repo: "releases",
        hop: None,
        set: &set,
    };
    f(&ctx)
}

#[test]
fn the_guest_is_in_the_ingest_set_the_server_builds_and_it_runs_on_a_put() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    with_linked_meter(
        &mut holger,
        Passthrough {
            name: "capper",
            verdict: Ok(WireVerdict::Refuse {
                code: "holger-metered-6".into(),
                reason: "the ceiling is reached".into(),
                url: Some("https://example.invalid/renew".into()),
            }),
        },
        "capper",
    );

    // THE SERVER'S OWN SET — not a `WasmGate` this test built. This config has
    // no `promotion:` block at all, so before the wiring it was empty.
    let set = holger.ingest_gates().expect("the ingest set builds");
    assert!(
        set.names().contains(&"capper"),
        "the meter is not in the set: {:?}",
        set.names()
    );

    let decision = over_context("", |ctx| set.evaluate(ctx));
    assert!(
        !decision.is_allowed(),
        "a refusing meter let a PUT through: {decision:?}"
    );
    let text = format!("{decision:?}");
    assert!(text.contains("holger-metered-6"), "{text}");
    assert!(text.contains("the ceiling is reached"), "{text}");
}

#[test]
fn the_guest_is_on_the_promotion_engine_and_it_runs_on_a_promote() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    with_linked_meter(&mut holger, Passthrough::allowing("capper"), "capper");

    // Again with no `promotion:` block: a box with no rungs is not a box whose
    // metering plane was switched off.
    let engine = holger.promotion_engine().expect("the engine builds");
    assert!(engine.has_gate(), "the promotion engine carries no gate");

    // And the gate sees a PROMOTE there and a PUT at ingest — the one field that
    // differs between the two boundaries.
    let gates = holger.meter_gates();
    assert_eq!(gates.len(), 1);
    assert_eq!(gates[0].name(), "capper");
    for (from_repo, _expected) in [("", WireVerb::Put), ("dev", WireVerb::Promote)] {
        let v = over_context(from_repo, |ctx| gates[0].check(ctx));
        assert!(v.is_ok(), "the allowing guest refused a {from_repo:?} hop");
    }
}

#[test]
fn a_build_with_no_meter_loaded_gates_exactly_as_it_always_did() {
    let dir = tempfile::tempdir().unwrap();
    let holger = server(dir.path());
    // The plane exists and is empty. Empty is not a gate.
    assert!(holger.meter_plane.as_ref().unwrap().is_empty());
    assert!(holger.meter_gates().is_empty());
    assert!(holger.ingest_gates().unwrap().is_empty());
    assert!(!holger.promotion_engine().unwrap().has_gate());
}

// ───────────── 3. a refusal is a refusal; a notice is not; Err refuses ───────

#[test]
fn a_notice_is_not_a_refusal_because_data_is_never_held_hostage() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    with_linked_meter(
        &mut holger,
        Passthrough {
            name: "noticer",
            verdict: Ok(WireVerdict::AllowWithNotice {
                line: "80% of the ceiling".into(),
            }),
        },
        "noticer",
    );
    let set = holger.ingest_gates().unwrap();
    let decision = over_context("", |ctx| set.evaluate(ctx));
    assert!(
        decision.is_allowed(),
        "a NOTICE was applied as a refusal: {decision:?}"
    );
}

#[test]
fn a_guest_that_cannot_answer_refuses_the_hop_rather_than_waving_it_through() {
    let dir = tempfile::tempdir().unwrap();
    let mut holger = server(dir.path());
    with_linked_meter(
        &mut holger,
        Passthrough {
            name: "trapper",
            verdict: Err("the guest trapped".into()),
        },
        "trapper",
    );
    let set = holger.ingest_gates().unwrap();
    let decision = over_context("", |ctx| set.evaluate(ctx));
    assert!(
        !decision.is_allowed(),
        "an Err from a metering guest became an allow: {decision:?}"
    );
    assert!(
        format!("{decision:?}").contains("trapped"),
        "the reason was swallowed: {decision:?}"
    );
}

// ───────────────── 4. the grant table is still the authority ─────────────────

#[test]
fn a_guest_asking_for_more_than_the_grant_gives_is_refused_by_name() {
    let dir = tempfile::tempdir().unwrap();
    let holger = server(dir.path());
    let routes = holger.exposed_endpoints[0]
        .aggregated_routes
        .clone()
        .unwrap();
    let grants = GrantTable::from_pairs([("greedy", vec!["usage.read"])]).unwrap();
    let mut catalog = MeterCatalog::new(grants);
    let services: Arc<dyn MeterHostServices> = Arc::new(HolgerMeterServices::new(
        routes,
        Arc::new(holger_server_lib::audit::NoopAuditLog),
    ));
    let err = catalog
        .add_linked(
            "greedy",
            Greedy,
            Arc::new(holger_plugin_host::DirStore::new(dir_for_store())),
            services,
        )
        .expect_err("the grant stops short of what the manifest declares");
    // Named, not silently reduced: a typo that grants less produces a module
    // that refuses at runtime for a reason nobody can find.
    assert!(err.contains("audit.write"), "{err}");
    assert!(
        catalog.is_empty(),
        "a refused module still reached the plane"
    );
}

#[test]
fn the_wired_services_are_unreachable_without_the_matching_capability() {
    // The bridge, with the open guest's grant. `usage.read` and `store.list`
    // answer; the four that make a metering plugin dangerous do not — and none
    // of them was made reachable by wiring a real implementation behind them.
    let dir = tempfile::tempdir().unwrap();
    let holger = server(dir.path());
    let routes = holger.exposed_endpoints[0]
        .aggregated_routes
        .clone()
        .unwrap();
    let services: Arc<dyn MeterHostServices> = Arc::new(HolgerMeterServices::new(
        routes,
        Arc::new(holger_server_lib::audit::NoopAuditLog),
    ));
    let host = holger_plugin_host::GrantedHost::new(
        Arc::new(holger_plugin_host::DirStore::new(dir_for_store())),
        services,
        &[Capability::UsageRead, Capability::StoreList],
    );
    assert!(
        host.usage_read("releases").is_ok(),
        "the usage source is wired"
    );
    assert!(host.store_list("").is_ok(), "the store listing is wired");
    assert!(host
        .audit_write(&WireAuditRecord::default())
        .unwrap_err()
        .contains("audit.write"));
    assert!(host.clock().unwrap_err().contains("clock.read"));
    assert!(host.store_get("k").unwrap_err().contains("store.read"));
    // The one that would let a meter move its own number.
    assert!(host
        .store_put("k", b"v")
        .unwrap_err()
        .contains("store.write"));
}

#[test]
fn a_granted_guest_reaches_all_three_services_and_the_host_stamps_the_call() {
    let dir = tempfile::tempdir().unwrap();
    let holger = server(dir.path());
    let routes = holger.exposed_endpoints[0]
        .aggregated_routes
        .clone()
        .unwrap();
    let services: Arc<dyn MeterHostServices> = Arc::new(HolgerMeterServices::new(
        routes,
        Arc::new(holger_server_lib::audit::NoopAuditLog),
    ));
    let host = holger_plugin_host::GrantedHost::new(
        Arc::new(holger_plugin_host::DirStore::new(dir_for_store())),
        services,
        &[
            Capability::UsageRead,
            Capability::AuditWrite,
            Capability::ClockRead,
        ],
    );
    // All three answer — which is the whole difference from `NoServices`, whose
    // three answers are "this holger has no … wired".
    let rows: Vec<WireMeterReading> = host.usage_read("releases").expect("the usage source");
    assert!(rows
        .iter()
        .any(|r| r.meter == "repos" && r.scope == MeterScope::Product));
    assert!(rows.iter().all(|r| r.enforcement == Enforcement::Measured));

    let t: WireInstant = host.clock().expect("the clock");
    assert!(t.unix_nanos > 0);
    assert_eq!(
        t.trusted_until_unix_nanos, 0,
        "no signed time source, so no horizon"
    );

    holger_server_lib::meter_services::in_call(
        CallStamp {
            ident: "sub-7".into(),
            source_ip: "10.0.0.2:443".into(),
        },
        || {
            host.audit_write(&WireAuditRecord::default())
                .expect("the audit sink")
        },
    );
}