cellos-cortex 0.1.0

Bridge between CellOS execution cells and the Cortex doctrine layer — DoctrineAuthorityPolicy, CortexCellRunner, CellosLedgerEmitter.
//! Full Cortex→CellOS pipeline integration test.
//!
//! Tests the path: ContextPack → translate (apply_policy) → ExecutionCellDocument
//! → validate_execution_cell_document → admit.
//!
//! This exercises the *real* surface of `cellos-cortex` against the *real*
//! validator in `cellos-core`, so any drift between the runner's output and
//! what the supervisor's admission gate will accept is caught here rather
//! than at runtime.

use std::sync::Arc;

use async_trait::async_trait;
use cellos_core::{validate_execution_cell_document, SecretDeliveryMode};
use cellos_cortex::{
    context::ContextPack,
    runner::{CellSubmissionOutcome, CellSubmitter, CortexCellRunner},
};

/// A submitter that never actually submits — these tests are about the
/// translation surface, not the dispatch path. The runner still requires
/// *some* submitter to be constructible.
struct InertSubmitter;

#[async_trait]
impl CellSubmitter for InertSubmitter {
    async fn submit(
        &self,
        document: &cellos_core::types::ExecutionCellDocument,
    ) -> Result<CellSubmissionOutcome, anyhow::Error> {
        Ok(CellSubmissionOutcome {
            cell_id: document.spec.id.clone(),
            exit_code: Some(0),
            lifecycle_events: Vec::new(),
        })
    }
}

fn runner() -> CortexCellRunner {
    CortexCellRunner::new(Arc::new(InertSubmitter), vec!["agent".into()])
}

#[test]
fn full_pipeline_d5_context_pack_produces_valid_300s_spec() {
    // A ContextPack with D5 doctrine + an agent task.
    let pack = ContextPack {
        memory_digest: "sha256:abc".to_string(),
        doctrine_refs: vec!["D5".to_string()],
        task: "Summarize the provided document".to_string(),
        expires_at: None,
    };

    // The runner translates pack → document with policy applied.
    let translation = runner().translate(&pack);

    // D5: TTL must be ≤ 300s (the built-in D5 rule clamps to 300).
    assert!(
        translation.document.spec.lifetime.ttl_seconds <= 300,
        "D5 requires TTL ≤ 300s, got {}",
        translation.document.spec.lifetime.ttl_seconds
    );

    // The resulting spec must pass CellOS admission validation.
    validate_execution_cell_document(&translation.document)
        .expect("D5 spec must pass cellos-core validation");
}

#[test]
fn full_pipeline_d1_context_pack_uses_runtime_broker() {
    let pack = ContextPack {
        memory_digest: "sha256:abc".to_string(),
        doctrine_refs: vec!["D1".to_string()],
        task: "Process this data".to_string(),
        expires_at: None,
    };
    let translation = runner().translate(&pack);

    let run = translation
        .document
        .spec
        .run
        .as_ref()
        .expect("runner always populates RunSpec");
    assert!(
        matches!(
            run.secret_delivery,
            SecretDeliveryMode::RuntimeLeasedBroker | SecretDeliveryMode::RuntimeBroker
        ),
        "D1 requires runtime broker secret delivery, got {:?}",
        run.secret_delivery
    );

    validate_execution_cell_document(&translation.document)
        .expect("D1 spec must pass cellos-core validation");
}

#[test]
fn full_pipeline_empty_doctrine_produces_valid_default_spec() {
    let pack = ContextPack {
        memory_digest: "sha256:abc".to_string(),
        doctrine_refs: vec![],
        task: "Do some work".to_string(),
        expires_at: None,
    };
    let translation = runner().translate(&pack);

    validate_execution_cell_document(&translation.document)
        .expect("default (no doctrine) spec must be valid");
}