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

nordisk/gunnar

crates/gunnar-grpc/tests/census.rs

//! **The census of authorised RPCs is derived from the protos, not counted by
//! hand.**
//!
//! `auth::METHODS` is the list every deny-by-default sweep iterates, and the
//! only thing that had ever kept it complete was somebody remembering to add a
//! line. That works until it does not. On 2026-09-11 the list held 71 names,
//! its own doc comment said "the 37 RPCs", and the `.proto` files declared 89 —
//! so **twelve** RPCs were served and censused nowhere: the four
//! `Access.*Group*` verbs, `Forge.GetIssue`/`ListIssues`,
//! `Mirror.SetCredential`/`ResyncReplication`, `Usage.Activity`, and all three
//! `Webhooks.*`.
//!
//! **None of them was an open door.** Every one was read, and every one calls
//! `require` or `authorize` in its handler. What was missing is the thing that
//! would notice if one stopped — the sweep in `tests/auth.rs` drives
//! `audited_methods()`, so an RPC absent from the list is an RPC the sweep has
//! never once driven. That is the same hole the `PullRequests.*` note in
//! `auth.rs` records being found in on 2026-08-30, five RPCs earlier, and
//! finding it twice is what makes the hand-kept list the defect rather than the
//! omissions.
//!
//! So the expected set is READ OUT OF THE PROTOS here. A new RPC is a failing
//! test in this file until it is declared, and no number anywhere has to be
//! re-counted.
//!
//! # There is a second census in `tests/auth.rs`, and it is not a duplicate
//!
//! `tests/auth.rs::declared_rpcs` reads the same `.proto` files through
//! `include_str!` — the bytes that were COMPILED INTO `gunnar-proto` — and
//! compares them against `audited_methods() + UNAUTHENTICATED_METHODS`, which
//! is the set its deny-by-default sweep actually drives. This file reads the
//! files ON DISK and compares against that set plus `UNIMPLEMENTED_METHODS`,
//! because "declared absent" is a declaration too. The two answer different
//! questions — what was built, and what is in the tree — and either one going
//! red is a different fact about the repository, so both stay.
//!
//! # This is a scanner, not a protobuf parser
//!
//! The same deliberate limit `xtask`'s manifest checks record: a `service X {`
//! line opens a service and an `rpc Y(` line inside one names a method. That is
//! weaker than parsing and can be evaded by writing the same proto a different
//! way. It is still worth having, because the failure it catches is somebody
//! adding an `rpc` line — and `the_scanner_finds_what_is_really_there` below is
//! what keeps a scanner that stopped matching from reading as a clean census.

use std::collections::BTreeSet;

/// Where the protos live, from this crate.
fn proto_dir() -> std::path::PathBuf {
    std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("crates/")
        .join("gunnar-proto/proto/gunnar/v1")
}

/// Every `Service.Method` the `.proto` files declare.
fn declared_rpcs() -> BTreeSet<String> {
    let mut out = BTreeSet::new();
    let dir = proto_dir();
    let entries =
        std::fs::read_dir(&dir).unwrap_or_else(|err| panic!("reading {}: {err}", dir.display()));
    for entry in entries.flatten() {
        let path = entry.path();
        if path.extension().and_then(|e| e.to_str()) != Some("proto") {
            continue;
        }
        let text = std::fs::read_to_string(&path)
            .unwrap_or_else(|err| panic!("reading {}: {err}", path.display()));
        let mut service: Option<String> = None;
        for line in text.lines() {
            let line = line.trim();
            if let Some(rest) = line.strip_prefix("service ") {
                service = rest
                    .split_whitespace()
                    .next()
                    .map(|name| name.trim_end_matches('{').to_string());
                continue;
            }
            if let (Some(service), Some(rest)) = (service.as_ref(), line.strip_prefix("rpc ")) {
                if let Some(method) = rest.split('(').next() {
                    out.insert(format!("{service}.{}", method.trim()));
                }
            }
        }
    }
    out
}

/// The set the scanner is compared against: everything gunnar declares about
/// an RPC, in any of its four tables.
///
/// `UNIMPLEMENTED_METHODS` is in here because "declared absent" is a
/// declaration too — an RPC that is deliberately not served is censused, it is
/// just censused as not served.
fn censused() -> BTreeSet<String> {
    let mut out: BTreeSet<String> = gunnar_grpc::auth::audited_methods()
        .into_iter()
        .map(str::to_owned)
        .collect();
    out.extend(
        gunnar_grpc::UNAUTHENTICATED_METHODS
            .iter()
            .map(|m| (*m).to_owned()),
    );
    out.extend(
        gunnar_grpc::UNIMPLEMENTED_METHODS
            .iter()
            .map(|m| (*m).to_owned()),
    );
    out
}

/// The two METERED families — `Entitlement.*` and `Billing.*` — are compiled
/// only with the `metered` feature, and their `.proto` files are on disk either
/// way.
///
/// Absent code has no refusal to check, so the community build is held to the
/// community surface and the metered build to its own — rather than to a union
/// that would let one hide inside the other.
///
/// **This is a prefix list and not a wildcard, on purpose.** Every name in it
/// is a family this census stops checking in the community build, so adding one
/// is the act of saying "the community appliance does not serve these" — and
/// `gunnar-cli/tests/feature_split.rs` is what holds that claim to the resolved
/// graph. A rule like "anything the community build happens not to mount" would
/// make an RPC that quietly stopped being mounted disappear from the census
/// instead of failing it.
const METERED_FAMILIES: &[&str] = &["Entitlement.", "Billing.", "Sealed."];

fn expected() -> BTreeSet<String> {
    declared_rpcs()
        .into_iter()
        .filter(|rpc| {
            cfg!(feature = "metered")
                || !METERED_FAMILIES
                    .iter()
                    .any(|family| rpc.starts_with(family))
        })
        .collect()
}

/// **Every RPC the protos declare is censused, and nothing else is.**
///
/// Both directions. The first is the finding: an RPC nobody wrote down is an
/// RPC the deny-by-default sweep has never driven. The second is what stops the
/// list rotting the other way, into names of RPCs that no longer exist and
/// whose refusals nothing checks because there is nothing to refuse.
#[test]
fn every_rpc_the_protos_declare_is_censused_and_nothing_else_is() {
    let expected = expected();
    let censused = censused();

    let uncensused: Vec<&String> = expected.difference(&censused).collect();
    assert!(
        uncensused.is_empty(),
        "these RPCs are served and appear in no table, so the deny-by-default sweep has \
         never driven them and nothing would notice if one stopped authorising: \
         {uncensused:?}"
    );

    let phantom: Vec<&String> = censused.difference(&expected).collect();
    assert!(
        phantom.is_empty(),
        "these names are censused and no proto declares them, so their entry is checking a \
         refusal nothing can make: {phantom:?}"
    );
}

/// The identity-value guard for the test above.
///
/// A scanner that matched nothing would make both assertions above pass
/// vacuously in one direction and fail loudly in the other — but a scanner that
/// found the services and missed some METHODS would quietly shrink the expected
/// set until the census "covered" it. So the scan is checked against a handful
/// of RPCs written out here by hand, chosen across four different proto files
/// and including one of the twelve that were missing.
#[test]
fn the_scanner_finds_what_is_really_there() {
    let declared = declared_rpcs();
    assert!(
        declared.len() >= 80,
        "the scanner found only {} RPCs across {}; it has stopped matching and every \
         census below it is vacuous",
        declared.len(),
        proto_dir().display()
    );
    for rpc in [
        "Bootstrap.Enroll",
        "Repos.Create",
        "Refs.ReadLfsObject",
        "Mirror.SetCredential",
        "Webhooks.AddWebhook",
        "Entitlement.Set",
    ] {
        assert!(
            declared.contains(rpc),
            "the scanner did not find {rpc}, which is in the protos on disk"
        );
    }
    assert!(
        !declared.contains("Repos.ThisDoesNotExist"),
        "the scanner reports RPCs that are not declared anywhere"
    );
}