harn-cli 0.10.52

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
use super::*;

#[test]
fn apply_preserves_separate_narrow_capability_parameters() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("split_capabilities.harn");
    let source = "fn read_key(secrets: HarnessSecrets) {\n  secrets.read(\"app-jwt\", nil)\n}\n\npub fn mint_app_jwt(clock: HarnessClock, secrets: HarnessSecrets, config) {\n  const issued_at = clock.now_ms()\n  return {issued_at: issued_at, key: read_key(secrets), config: config}\n}\n\nfn main(harness: Harness) {\n  mint_app_jwt(harness.clock, harness.secrets, {})\n}\n";
    fs::write(&script, source).unwrap();

    for pass in 1..=2 {
        let result = apply_repairs_with_options(
            &script,
            RepairSafety::SurfaceChanging,
            false,
            FixOptions {
                capability_migrations_only: true,
            },
        )
        .unwrap();
        assert!(result.applied.is_empty(), "pass {pass}: {result:#?}");
        assert_eq!(
            fs::read_to_string(&script).unwrap(),
            source,
            "pass {pass} must preserve an already-split capability boundary"
        );
    }
}

#[test]
fn apply_rewrites_ambient_call_through_root_first_split_boundary() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("root_first_split.harn");
    fs::write(
        &script,
        "fn helper(harness: Harness, clock: HarnessClock) {\n  println(\"hi\")\n  now_ms()\n}\n\nfn main(harness: Harness) {\n  helper(harness, harness.clock)\n}\n",
    )
    .unwrap();

    let result = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(!result.applied.is_empty(), "{result:#?}");

    let updated = fs::read_to_string(&script).unwrap();
    assert!(
        updated.contains("fn helper(harness: Harness, clock: HarnessClock)"),
        "the split signature must remain intact: {updated}"
    );
    assert!(
        updated.contains("harness.stdio.println(\"hi\")"),
        "the root carrier should own the ambient rewrite: {updated}"
    );
    assert!(
        updated.contains("clock.now_ms()") && !updated.contains("harness.clock.now_ms()"),
        "an existing narrow carrier should take precedence over root authority: {updated}"
    );
}

#[test]
fn apply_extends_narrow_first_split_boundary_with_missing_handle() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("narrow_first_split.harn");
    fs::write(
        &script,
        "fn helper(clock: HarnessClock, secrets: HarnessSecrets, config) {\n  println(\"hi\")\n  return {time: clock.now_ms(), key: secrets.read(\"key\", nil), config: config}\n}\n\nfn main(harness: Harness) {\n  helper(harness.clock, harness.secrets, {})\n}\n",
    )
    .unwrap();

    let first = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(!first.applied.is_empty(), "{first:#?}");

    let updated = fs::read_to_string(&script).unwrap();
    assert!(
        updated.contains(
            "fn helper(clock: HarnessClock, secrets: HarnessSecrets, stdio: HarnessStdio, config)"
        ),
        "the split boundary should gain only the missing handle: {updated}"
    );
    assert!(
        updated.contains("stdio.println(\"hi\")"),
        "the new narrow handle should own the ambient rewrite: {updated}"
    );
    assert!(
        updated.contains("helper(harness.clock, harness.secrets, harness.stdio, {})"),
        "the caller should project the added handle at the matching position: {updated}"
    );

    let second = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(second.applied.is_empty(), "{second:#?}");
}

#[test]
fn apply_preserves_lone_narrow_handle_when_adding_another_capability() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("lone_narrow_handle.harn");
    fs::write(
        &script,
        "fn helper(clock: HarnessClock, value) {\n  const day = clock.date_iso()\n  const token = harness.secrets.get(\"token\")\n  return [day, token, value]\n}\n\nfn main(harness: Harness) {\n  helper(harness.clock, \"input\")\n}\n",
    )
    .unwrap();

    let first = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(!first.applied.is_empty(), "{first:#?}");

    let updated = fs::read_to_string(&script).unwrap();
    assert_eq!(
        updated,
        "fn helper(clock: HarnessClock, secrets: HarnessSecrets, value) {\n  const day = clock.date_iso()\n  const token = secrets.get(\"token\")\n  return [day, token, value]\n}\n\nfn main(harness: Harness) {\n  helper(harness.clock, harness.secrets, \"input\")\n}\n",
    );

    let second = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(second.applied.is_empty(), "{second:#?}");
}

#[test]
fn apply_does_not_guess_domain_argument_after_lone_narrow_handle() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("lone_narrow_domain_gap.harn");
    fs::write(
        &script,
        "fn helper(clock: HarnessClock, value) {\n  const token = harness.secrets.get(\"token\")\n  return [clock.date_iso(), token, value]\n}\n\nfn main(harness: Harness) {\n  helper(harness.clock)\n}\n",
    )
    .unwrap();

    let result = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(!result.applied.is_empty(), "{result:#?}");

    assert_eq!(
        fs::read_to_string(&script).unwrap(),
        "fn helper(clock: HarnessClock, secrets: HarnessSecrets, value) {\n  const token = secrets.get(\"token\")\n  return [clock.date_iso(), token, value]\n}\n\nfn main(harness: Harness) {\n  helper(harness.clock, harness.secrets)\n}\n",
        "the fixer must migrate authority without inventing the missing domain value",
    );
}

#[test]
fn apply_completes_split_capability_prefix_before_extension() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("partial_split_call.harn");
    fs::write(
        &script,
        "fn mint(clock: HarnessClock, secrets: HarnessSecrets) {\n  println(\"hi\")\n  return {time: clock.now_ms(), key: secrets.read(\"key\", nil)}\n}\n\nfn main(harness: Harness) {\n  mint(harness.clock)\n}\n",
    )
    .unwrap();

    let first = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(!first.applied.is_empty(), "{first:#?}");

    let updated = fs::read_to_string(&script).unwrap();
    assert!(
        updated
            .contains("fn mint(clock: HarnessClock, secrets: HarnessSecrets, stdio: HarnessStdio)"),
        "the split signature should gain the missing handle: {updated}"
    );
    assert!(
        updated.contains("mint(harness.clock, harness.secrets, harness.stdio)"),
        "the partial call should converge in parameter order: {updated}"
    );

    let second = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(second.applied.is_empty(), "{second:#?}");
}

#[test]
fn apply_defers_extension_across_missing_ordinary_parameter() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("ordinary_gap.harn");
    fs::write(
        &script,
        "fn mint(clock: HarnessClock, config, secrets: HarnessSecrets) {\n  println(\"hi\")\n  return {time: clock.now_ms(), key: secrets.read(\"key\", nil), config: config}\n}\n\nfn main(harness: Harness) {\n  mint(harness.clock)\n}\n",
    )
    .unwrap();

    let result = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(!result.applied.is_empty(), "{result:#?}");

    let updated = fs::read_to_string(&script).unwrap();
    assert_eq!(
        updated,
        "fn mint(clock: HarnessClock, config, secrets: HarnessSecrets, stdio: HarnessStdio) {\n  stdio.println(\"hi\")\n  return {time: clock.now_ms(), key: secrets.read(\"key\", nil), config: config}\n}\n\nfn main(harness: Harness) {\n  mint(harness.clock)\n}\n",
        "the signature and ambient call should migrate without guessing the missing config value"
    );
}

#[test]
fn apply_does_not_widen_narrow_caller_of_split_boundary() {
    let temp = tempfile::TempDir::new().unwrap();
    let script = temp.path().join("split_callee.harn");
    let source = "fn mint(clock: HarnessClock, secrets: HarnessSecrets) {\n  return {time: clock.now_ms(), key: secrets.read(\"key\", nil)}\n}\n\nfn wrap(clock: HarnessClock, secrets) {\n  mint(clock, secrets)\n}\n\nfn main(harness: Harness) {\n  wrap(harness.clock, harness.secrets)\n}\n";
    fs::write(&script, source).unwrap();

    let result = apply_repairs_with_options(
        &script,
        RepairSafety::SurfaceChanging,
        false,
        FixOptions {
            capability_migrations_only: true,
        },
    )
    .unwrap();
    assert!(result.applied.is_empty(), "{result:#?}");
    assert_eq!(fs::read_to_string(&script).unwrap(), source);
}