aidens-app-kit 0.1.0

Safe app builder and profile expansion with 5 built-in profiles
Documentation
use aidens_app_kit::{AiDENsApp, AiDENsProfile};
use aidens_contracts::{
    CapabilityGateOutcomeV1, StopRuleV1, ToolCallSourceV1, ToolLifecycleStateV1, TurnFinalStateV1,
    TurnModeV1,
};
use aidens_receipts::{CanonicalEventLog, CanonicalEventLogConfig};
use std::time::{SystemTime, UNIX_EPOCH};

#[tokio::test]
async fn phase_06_config_to_runner_mock_tool_receipts_audit_slice() {
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let dir = std::env::temp_dir().join(format!(
        "aidens-phase06-vertical-slice-{}-{nonce}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();
    std::fs::write(dir.join("README.md"), "phase06 fixture content\n").unwrap();
    let cfg = dir.join("aidens.toml");
    let config = include_str!("../../../tests/fixtures/p06/runner_vertical_slice_aidens.toml")
        .replace("__SANDBOX_ROOT__", &dir.display().to_string());
    std::fs::write(&cfg, config).unwrap();

    let app = AiDENsApp::builder()
        .name("ignored")
        .profile(AiDENsProfile::CodingAgent)
        .config_file(cfg.to_str().unwrap())
        .build()
        .await
        .expect("phase06 app builds from config");

    let config_receipt = app
        .config_apply_receipt()
        .expect("config apply receipt is emitted");
    assert!(config_receipt.applied);
    assert_eq!(config_receipt.app_id, "phase06-runner-slice");
    assert!(config_receipt
        .reason_codes
        .iter()
        .any(|reason| reason.contains("canonical-receipt-log")));

    let output = app
        .run_once("read the phase06 README through the runner")
        .await
        .expect("runner vertical slice succeeds");

    assert!(
        output
            .text
            .contains("phase06 final response saw: phase06 fixture content"),
        "actual output: {}",
        output.text
    );
    assert_eq!(output.turn_receipt.mode, TurnModeV1::ParserFallback);
    assert_eq!(
        output.turn_receipt.final_state,
        TurnFinalStateV1::FinalOutput
    );
    assert!(output.turn_receipt.degraded);
    assert_eq!(output.receipt.tool_exposure_ids.len(), 1);
    assert_eq!(
        output.receipt.tool_exposure_ids[0],
        output.tool_exposure.exposure_id
    );
    assert_eq!(output.receipt.tool_call_requests.len(), 1);
    assert_eq!(
        output.receipt.tool_call_requests[0].source,
        ToolCallSourceV1::ParserFallback
    );
    assert!(output.receipt.tool_call_requests[0].degraded);
    assert!(output.receipt.boundary_repair_receipts.is_empty());
    assert_eq!(output.receipt.tool_invocation_receipts.len(), 1);
    let invocation = &output.receipt.tool_invocation_receipts[0];
    assert_eq!(invocation.tool_id, "aidens:repo-read:1");
    assert!(invocation.succeeded);
    assert!(invocation.run_id.is_some());
    assert!(invocation.attempt_id.is_some());
    assert!(invocation.output_digest.is_some());
    assert!(output
        .receipt
        .stop_rule_receipts
        .iter()
        .any(|stop| stop.rule == StopRuleV1::FinalOutput));

    let repo_read_gate = output
        .tool_exposure
        .decisions
        .iter()
        .find(|decision| decision.capability_id == "aidens:repo-read:1")
        .expect("repo-read exposure decision");
    assert_eq!(&repo_read_gate.outcome, &CapabilityGateOutcomeV1::Exposed);
    assert!(!repo_read_gate.permit_required);
    assert!(repo_read_gate.executable_this_turn);
    assert!(repo_read_gate
        .lifecycle
        .contains(&ToolLifecycleStateV1::ExposedThisTurn));
    assert!(output
        .tool_exposure
        .exposed_tool_ids
        .contains(&"aidens:repo-read:1".into()));

    let patch_apply_gate = output
        .tool_exposure
        .decisions
        .iter()
        .find(|decision| decision.capability_id == "aidens:patch-apply:1")
        .expect("patch-apply permit decision");
    assert_eq!(&patch_apply_gate.outcome, &CapabilityGateOutcomeV1::Blocked);
    assert!(patch_apply_gate.permit_required);
    assert!(patch_apply_gate.approval_request.is_some());
    assert!(patch_apply_gate
        .reason_codes
        .iter()
        .any(|reason| reason == "permit-required:write"));

    let log = CanonicalEventLog::open(CanonicalEventLogConfig::for_root(dir.join("receipts")))
        .expect("canonical log reopens");
    let records = log.list_records().expect("event log records list");
    assert_eq!(records.len(), 5);
    assert!(records.iter().all(|record| record.verify_digest()));
    assert!(records.iter().any(|record| {
        record.owner_crate == "aidens-orchestration"
            && record.schema_name == "tool-exposure-plan-v1"
            && record.body.to_string().contains("aidens:repo-read:1")
    }));
    assert!(records.iter().any(|record| {
        record.owner_crate == "verification-control"
            && record.schema_name == "control-receipt"
            && record.body.to_string().contains("final-output-produced")
    }));
    assert!(records.iter().any(|record| {
        record.owner_crate == "aidens-agency-kit" && record.schema_name == "agency-policy-report-v1"
    }));
    assert!(!output.receipt.agency_receipt_ids.is_empty());
    let run_record = records
        .iter()
        .find(|record| record.schema_name == "run-report-v1")
        .expect("run report persisted as audit report");
    assert_eq!(run_record.receipt_id, output.receipt.receipt_id.as_ref());
    assert!(run_record
        .body
        .to_string()
        .contains("phase06 fixture content"));
    assert_eq!(output.durable_receipt_records.len(), records.len());

    let _ = std::fs::remove_dir_all(&dir);
}