forge-pilot 0.1.0

Closed-loop orchestrator over runtime advisories, kernel oracles, and canonical Forge export/import lanes
Documentation
mod common;

use common::{
    base_loop_config, import_thin_v3_batch, import_v2_bundle_without_kernel_payload,
    open_forge_store, open_memory_store, point_config_at_dir, resources, sample_bundle, tempdir,
    write_source_file,
};
use forge_pilot::{extract_targets, observe_scope, HaltReason, LoopRunner, TargetKind};
use knowledge_runtime::Scope;

#[tokio::test]
async fn observation_degrades_honestly_when_kernel_payload_is_missing() {
    let dir = tempdir();
    let memory_store = open_memory_store(dir.path());
    let forge_store = open_forge_store(dir.path());
    let scope = Scope::new("pilot-degrade-missing");
    let mut config = base_loop_config(scope.clone());
    point_config_at_dir(&mut config, dir.path());
    write_source_file(
        dir.path(),
        "src/lib.rs",
        "pub fn missing_kernel_fixture() -> bool { true }\n",
    );

    import_v2_bundle_without_kernel_payload(
        &memory_store,
        &scope.namespace,
        &sample_bundle("missing-kernel"),
    )
    .await;

    let resources = resources(memory_store, forge_store, &config);
    let observation = observe_scope(&resources.runtime, &resources.memory_store, &config)
        .await
        .unwrap();

    assert!(observation.missing_kernel_payload());
    assert!(observation.batch.is_none());
    assert!(observation.compiled.is_none());
}

#[tokio::test]
async fn thin_export_yields_explicit_target_instead_of_invented_structure() {
    let dir = tempdir();
    let memory_store = open_memory_store(dir.path());
    let forge_store = open_forge_store(dir.path());
    let scope = Scope::new("pilot-degrade-thin");
    let mut config = base_loop_config(scope.clone());
    point_config_at_dir(&mut config, dir.path());
    write_source_file(
        dir.path(),
        "src/lib.rs",
        "pub fn thin_export_fixture() -> bool { true }\n",
    );

    import_thin_v3_batch(
        &memory_store,
        &forge_store,
        &scope.namespace,
        &sample_bundle("thin-export"),
    )
    .await;

    let resources = resources(memory_store, forge_store, &config);
    let observation = observe_scope(&resources.runtime, &resources.memory_store, &config)
        .await
        .unwrap();
    let targets = extract_targets(&observation, &config);

    assert!(observation.thin_export_active());
    assert!(targets
        .iter()
        .any(|target| matches!(target, TargetKind::ThinExport { .. })));
}

#[tokio::test]
async fn loop_runner_bounds_repeated_degraded_thin_export_cycles() {
    let dir = tempdir();
    let memory_store = open_memory_store(dir.path());
    let forge_store = open_forge_store(dir.path());
    let scope = Scope::new("pilot-degrade-loop");
    let mut config = base_loop_config(scope.clone());
    point_config_at_dir(&mut config, dir.path());
    write_source_file(
        dir.path(),
        "src/lib.rs",
        "pub fn thin_loop_fixture() -> bool { true }\n",
    );
    config.max_iterations = 3;
    config.max_retries_per_target = 2;
    let expected_iterations = config.max_iterations;

    import_thin_v3_batch(
        &memory_store,
        &forge_store,
        &scope.namespace,
        &sample_bundle("thin-loop"),
    )
    .await;

    let resources = resources(memory_store, forge_store, &config);
    let mut runner = LoopRunner::new(config, resources);
    let report = runner.run().await.unwrap();

    assert_eq!(report.iterations_completed, expected_iterations);
    assert_eq!(report.receipt.iterations_completed, expected_iterations);
    assert_eq!(report.degraded_iterations, expected_iterations);
    assert_eq!(report.receipt.degraded_iterations, expected_iterations);
    assert_eq!(report.halt_reason, HaltReason::MaxIterationsReached);
    assert_eq!(
        report.targets_investigated.len(),
        expected_iterations as usize
    );
    assert!(report.iterations.iter().all(|iteration| iteration.degraded));
}