cellos-supervisor 0.5.1

CellOS execution-cell runner — boots cells in Firecracker microVMs or gVisor, enforces narrow typed authority, emits signed CloudEvents.
Documentation
//! End-to-end fixture test for the CellOS authority-derivation pipeline.
//!
//! Exercises the full operator-facing path against committed example files:
//!
//! 1. JSON spec → `serde_json` → `ExecutionCellDocument`     (schema validation)
//! 2. Keys file → `serde_json` → `HashMap<String,String>`    (mirrors
//!    `cellos_supervisor::composition::load_authority_keys`'s on-disk format)
//! 3. `verify_authority_derivation(&spec, &token, &keys)`    (ED25519 verify)
//! 4. Tamper the signature byte-for-byte and re-verify       (negative path)
//!
//! The fixture files live at:
//! - `contracts/examples/execution-cell-ci-runner-signed.valid.json`
//! - `contracts/examples/authority-keys.example.json`
//!
//! The signature embedded in the spec is generated deterministically from
//! seed `[0x42; 32]` by the `gen_signed_ci_runner_fixture` test in
//! `cellos-core::spec_validation::tests`. If the leaf capability, role root,
//! or parentRunId in the fixture changes, regenerate with:
//!
//! ```text
//! cargo test -p cellos-core --lib gen_signed_ci_runner_fixture \
//!   -- --ignored --nocapture
//! ```
//!
//! Note on the keys-file loader: `load_authority_keys` is `pub(crate)` in
//! `cellos-supervisor::composition`, so this integration test cannot call it
//! directly. It instead deserializes the file with the same JSON shape the
//! loader expects (`HashMap<String, String>` of role-id → base64 verifying
//! key) — proving the operator-facing fixture format is correct.

use std::collections::HashMap;
use std::path::PathBuf;

use base64::engine::general_purpose::STANDARD;
use base64::Engine as _;
use cellos_core::spec_validation::{validate_execution_cell_document, verify_authority_derivation};
use cellos_core::{AuthorityDerivationToken, ExecutionCellDocument};

/// Resolve `contracts/examples/<name>` from the workspace root, regardless of
/// whether tests run from the crate dir or workspace dir.
fn fixture_path(name: &str) -> PathBuf {
    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let workspace_root = manifest_dir
        .parent()
        .and_then(|p| p.parent())
        .expect("cellos-supervisor lives under <workspace>/crates/");
    workspace_root.join("contracts").join("examples").join(name)
}

fn load_signed_fixture() -> (
    ExecutionCellDocument,
    AuthorityDerivationToken,
    HashMap<String, String>,
) {
    let spec_path = fixture_path("execution-cell-ci-runner-signed.valid.json");
    let keys_path = fixture_path("authority-keys.example.json");

    let spec_raw = std::fs::read_to_string(&spec_path)
        .unwrap_or_else(|e| panic!("read fixture {}: {e}", spec_path.display()));
    let doc: ExecutionCellDocument = serde_json::from_str(&spec_raw)
        .unwrap_or_else(|e| panic!("parse fixture {}: {e}", spec_path.display()));

    // Schema validation must pass — the fixture is asserted "valid" by name.
    validate_execution_cell_document(&doc).expect("fixture must pass schema validation");

    let token = doc
        .spec
        .authority
        .authority_derivation
        .clone()
        .expect("fixture spec must carry an authorityDerivation token");

    let keys_raw = std::fs::read_to_string(&keys_path)
        .unwrap_or_else(|e| panic!("read keys file {}: {e}", keys_path.display()));
    // Same JSON shape that load_authority_keys consumes (HashMap<role,b64>).
    let keys: HashMap<String, String> = serde_json::from_str(&keys_raw)
        .unwrap_or_else(|e| panic!("parse keys file {}: {e}", keys_path.display()));

    (doc, token, keys)
}

#[test]
fn signed_ci_runner_fixture_verifies_end_to_end() {
    let (doc, token, keys) = load_signed_fixture();

    // Sanity-check the fixture wiring before crypto: the role in the token
    // must be present in the keys file, otherwise the failure below would
    // be ambiguous (unknown-role vs. bad-signature).
    let role = token.role_root.to_string();
    assert!(
        keys.contains_key(&role),
        "keys file must publish the verifying key for {role}"
    );

    verify_authority_derivation(&doc.spec, &token, &keys)
        .expect("signed fixture must verify against the published verifying key");
}

#[test]
fn signed_ci_runner_fixture_rejects_tampered_signature() {
    let (doc, mut token, keys) = load_signed_fixture();

    // Flip the last byte of the raw signature (post-base64-decode) and
    // re-encode. ED25519 `verify_strict` must reject this.
    let mut sig_bytes = STANDARD
        .decode(token.grantor_signature.bytes.as_bytes())
        .expect("fixture signature is base64");
    let last = sig_bytes.len() - 1;
    sig_bytes[last] ^= 0x01;
    token.grantor_signature.bytes = STANDARD.encode(&sig_bytes);

    let err = verify_authority_derivation(&doc.spec, &token, &keys)
        .expect_err("tampered signature must not verify");

    // The negative path must surface as InvalidSpec("authority derivation
    // signature invalid") — NOT a structural rejection (subset check) and
    // NOT an unknown-role rejection. Asserting on the exact variant + the
    // exact message catches both wrong-error-type regressions and silent
    // re-routing of crypto failures into structural ones.
    match err {
        cellos_core::error::CellosError::InvalidSpec(msg) => {
            assert!(
                msg.contains("authority derivation signature invalid"),
                "expected signature-invalid message, got: {msg}"
            );
        }
        other => panic!("expected CellosError::InvalidSpec, got {other:?}"),
    }
}