mod common;

const API_DOCS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../docs/api/cli/v1");
const PACKAGED_API_SCHEMAS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/schemas/api/cli/v1");

fn parse_json(bytes: &[u8]) -> serde_json::Value {
    serde_json::from_slice(bytes).unwrap()
}

fn read_doc_json(relative: &str) -> serde_json::Value {
    let path = std::path::Path::new(API_DOCS).join(relative);
    serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap()
}

fn read_schema_doc_bytes(relative: &str) -> Vec<u8> {
    std::fs::read(std::path::Path::new(API_DOCS).join(relative)).unwrap()
}

fn read_packaged_schema_bytes(relative: &str) -> Vec<u8> {
    std::fs::read(std::path::Path::new(PACKAGED_API_SCHEMAS).join(relative)).unwrap()
}

fn validator_for_payload_def(def_name: &str) -> jsonschema::Validator {
    let payloads = read_doc_json("payloads.schema.json");
    let schema = serde_json::json!({
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$defs": payloads["$defs"].clone(),
        "$ref": format!("#/$defs/{def_name}"),
    });
    jsonschema::validator_for(&schema).unwrap()
}

fn assert_success_schema(value: &serde_json::Value) {
    assert_eq!(value["api_version"], "rebecca.cli.v1");
    assert_eq!(value["kind"], "success");
    assert!(
        value["command"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(
        value["payload_kind"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(value["generated_at_unix_seconds"].as_u64().is_some());
    assert!(value.get("data").is_some());
}

fn external_rule_manifest(family_id: &str) -> String {
    let (platform, target) = if cfg!(target_os = "linux") {
        ("linux", "%XDG_CACHE_HOME%/pip/RebeccaExternalFixture")
    } else if cfg!(target_os = "macos") {
        ("macos", "%MACOS_CACHE_HOME%/pip/RebeccaExternalFixture")
    } else {
        ("windows", "%TEMP%\\RebeccaExternalFixture")
    };

    let target = target.replace('\\', "\\\\");

    format!(
        r#"
manifest_version = 1
id = "{family_id}"
category = "development"
name = "External cache"
safety_level = "safe"
restore_hint = "The external fixture cache can be recreated."

[provenance]
source = "reference-only"
license = "example-user-rule"
notes = "Local user-authored validation fixture; no external rule data copied."

[[platforms]]
platform = "{platform}"

[[platforms.targets]]
kind = "template"
value = "{target}"
search_kind = "file"
"#
    )
}

fn create_external_rule_target(temp: &tempfile::TempDir) {
    let target = if cfg!(target_os = "linux") {
        temp.path()
            .join("cache")
            .join("pip")
            .join("RebeccaExternalFixture")
    } else if cfg!(target_os = "macos") {
        temp.path()
            .join("Library")
            .join("Caches")
            .join("pip")
            .join("RebeccaExternalFixture")
    } else {
        temp.path().join("temp").join("RebeccaExternalFixture")
    };
    std::fs::create_dir_all(&target).unwrap();
    std::fs::write(target.join("cache.bin"), b"external-cache").unwrap();
}

fn assert_error_schema(value: &serde_json::Value) {
    assert_eq!(value["api_version"], "rebecca.cli.v1");
    assert_error_shape(value);
}

fn assert_error_shape(value: &serde_json::Value) {
    assert_eq!(value["kind"], "error");
    assert!(
        value["command"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(value["generated_at_unix_seconds"].as_u64().is_some());
    assert!(
        value["error"]["code"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(
        value["error"]["title"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(
        value["error"]["detail"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(
        value["error"]["exit_code"]
            .as_u64()
            .is_some_and(|value| value > 0)
    );
}

fn assert_event_schema(value: &serde_json::Value) {
    assert_eq!(value["api_version"], "rebecca.cli.v1");
    assert_event_shape(value);
}

fn assert_event_shape(value: &serde_json::Value) {
    assert_eq!(value["kind"], "event");
    assert!(
        value["command"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(value["sequence"].as_u64().is_some());
    assert!(
        value["event_kind"]
            .as_str()
            .is_some_and(|value| !value.is_empty())
    );
    assert!(value["generated_at_unix_seconds"].as_u64().is_some());
}

#[test]
fn clean_format_json_returns_success_envelope_without_human_text() {
    let temp = tempfile::tempdir().unwrap();
    let temp_cache = temp.path().join("temp");
    std::fs::create_dir_all(&temp_cache).unwrap();
    std::fs::write(temp_cache.join("cache.tmp"), b"cache").unwrap();

    let output = common::isolated::isolated_rebecca(&temp)
        .env("REBECCA_STEAM_DISCOVERY", "none")
        .env("TEMP", &temp_cache)
        .env("TMPDIR", &temp_cache)
        .args(["clean", "--format", "json", "--category", "system"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(!stdout.contains("Workflow:"));
    assert!(!stdout.contains("Targets:"));

    let value = parse_json(&output.stdout);
    assert_success_schema(&value);
    assert_eq!(value["api_version"], "rebecca.cli.v1");
    assert_eq!(value["kind"], "success");
    assert_eq!(value["command"], "clean");
    assert_eq!(value["payload_kind"], "cleanup-plan");
    assert!(value["generated_at_unix_seconds"].as_u64().is_some());
    assert_eq!(value["data"]["request"]["mode"], "dry-run");
    assert!(
        value["data"]["summary"]["allowed_targets"]
            .as_u64()
            .unwrap()
            >= 1
    );
}

#[test]
fn history_format_json_returns_empty_history_envelope() {
    let temp = tempfile::tempdir().unwrap();
    let output = common::isolated::isolated_rebecca(&temp)
        .args(["history", "--format", "json"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let value = parse_json(&output.stdout);
    assert_success_schema(&value);
    assert_eq!(value["api_version"], "rebecca.cli.v1");
    assert_eq!(value["kind"], "success");
    assert_eq!(value["command"], "history");
    assert_eq!(value["payload_kind"], "history-list");
    assert_eq!(value["data"].as_array().unwrap().len(), 0);
}

#[test]
fn clean_format_json_unknown_rule_returns_error_envelope_on_stderr() {
    let temp = tempfile::tempdir().unwrap();
    let output = common::isolated::isolated_rebecca(&temp)
        .args(["clean", "--format", "json", "--rule", "missing.rule"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stdout.is_empty());

    let value = parse_json(&output.stderr);
    assert_error_schema(&value);
    assert_eq!(value["api_version"], "rebecca.cli.v1");
    assert_eq!(value["kind"], "error");
    assert_eq!(value["command"], "clean");
    assert_eq!(value["error"]["code"], "invalid-rule-id");
    assert_eq!(value["error"]["exit_code"], 1);
    assert!(
        value["error"]["detail"]
            .as_str()
            .unwrap()
            .contains("missing.rule")
    );
}

#[test]
fn catalog_format_json_invalid_selector_returns_error_envelope() {
    let output = common::command::rebecca()
        .args([
            "catalog",
            "--format",
            "json",
            "--warning",
            "missing-warning",
        ])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stdout.is_empty());

    let value = parse_json(&output.stderr);
    assert_error_schema(&value);
    assert_eq!(value["command"], "catalog");
    assert_eq!(value["error"]["code"], "invalid-catalog-selector");
    assert!(
        value["error"]["detail"]
            .as_str()
            .unwrap()
            .contains("catalog selection did not match")
    );
}

#[test]
fn capabilities_format_json_reports_gui_backend_contract() {
    let output = common::command::rebecca()
        .args(["capabilities", "--format", "json"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "capabilities");
    assert_eq!(envelope["payload_kind"], "capabilities");

    let data = &envelope["data"];
    assert_eq!(data["api_version"], "rebecca.cli.v1");
    assert!(data["cli_version"].as_str().is_some());
    assert!(data["platform"]["current"].as_str().is_some());
    assert!(data["features"]["rules"].as_bool().is_some());
    assert!(data["features"]["ntfs"].as_bool().is_some());
    assert!(
        data["output_formats"]
            .as_array()
            .unwrap()
            .contains(&"json".into())
    );
    assert!(
        data["long_running_commands"]
            .as_array()
            .unwrap()
            .contains(&"clean".into())
    );
    assert!(
        data["schema_documents"]
            .as_array()
            .unwrap()
            .contains(&"config".into())
    );
    assert!(
        data["recommended_startup_commands"]
            .as_array()
            .unwrap()
            .contains(&"doctor permissions".into())
    );
    let commands = data["commands"].as_array().unwrap();
    assert!(commands.iter().any(|command| {
        command["name"] == "rules validate"
            && command["payload_kind"] == "rule-validation"
            && command["machine_readable"] == true
            && command["schema_documents"]
                .as_array()
                .unwrap()
                .contains(&"cleaner-manifest-v1".into())
    }));
    assert!(commands.iter().any(|command| {
        command["name"] == "clean"
            && command["required_execution_flag"] == "--yes"
            && command["preflight_commands"]
                .as_array()
                .unwrap()
                .contains(&"doctor permissions".into())
            && command["macos_privacy_relevant"] == true
    }));

    let validator = validator_for_payload_def("capabilities");
    validator.validate(data).unwrap();
}

#[test]
fn schema_export_format_json_returns_requested_schema_document() {
    let output = common::command::rebecca()
        .args([
            "schema",
            "export",
            "--document",
            "payloads",
            "--format",
            "json",
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "schema export");
    assert_eq!(envelope["payload_kind"], "cli-schema");
    assert_eq!(envelope["data"]["document"], "payloads");
    assert_eq!(envelope["data"]["api_version"], "rebecca.cli.v1");
    assert_eq!(
        envelope["data"]["schema"]["$id"],
        "https://rebecca.local/schemas/cli/v1/payloads.schema.json"
    );

    let validator = validator_for_payload_def("cliSchema");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn schema_export_format_json_returns_config_schema_document() {
    let output = common::command::rebecca()
        .args([
            "schema",
            "export",
            "--document",
            "config",
            "--format",
            "json",
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["data"]["document"], "config");
    assert_eq!(
        envelope["data"]["schema"]["$id"],
        "https://rebecca.dev/schemas/cli/v1/config.schema.json"
    );

    let validator = validator_for_payload_def("cliSchema");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn rules_validate_format_json_accepts_external_manifest_without_importing_it() {
    let temp = tempfile::tempdir().unwrap();
    let rule_file = temp.path().join("example-cache.toml");
    std::fs::write(
        &rule_file,
        r#"
manifest_version = 1
id = "example-cache"
category = "development"
name = "Example cache"
safety_level = "safe"
restore_hint = "Example rebuilds this cache automatically."

[provenance]
source = "reference-only"
license = "example-user-rule"
notes = "Local user-authored validation fixture; no external rule data copied."

[[platforms]]
platform = "macos"

[[platforms.targets]]
kind = "template"
value = "MACOS_CACHE_HOME/Example"
search_kind = "file"
"#,
    )
    .unwrap();

    let output = common::command::rebecca()
        .args([
            "rules",
            "validate",
            "--format",
            "json",
            "--file",
            rule_file.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "rules validate");
    assert_eq!(envelope["payload_kind"], "rule-validation");
    assert_eq!(envelope["data"]["valid"], true);
    assert_eq!(envelope["data"]["rule_count"], 1);
    assert_eq!(envelope["data"]["target_count"], 1);
    assert_eq!(envelope["data"]["rules"][0], "macos.example-cache");
    assert_eq!(envelope["data"]["enabled"], false);
    assert_eq!(envelope["data"]["summary"]["diagnostics"], 0);
    assert_eq!(
        envelope["data"]["rule_previews"][0]["rule_id"],
        "macos.example-cache"
    );
    assert_eq!(
        envelope["data"]["rule_previews"][0]["enabled_by_validation"],
        false
    );

    let validator = validator_for_payload_def("ruleValidation");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn rules_validate_format_json_missing_inputs_returns_rule_catalog_error() {
    let output = common::command::rebecca()
        .args(["rules", "validate", "--format", "json"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stderr.is_empty());
    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "rules validate");
    assert_eq!(envelope["payload_kind"], "rule-validation");
    assert_eq!(envelope["data"]["valid"], false);
    assert_eq!(
        envelope["data"]["diagnostics"][0]["code"],
        "rule-catalog-invalid"
    );
    assert!(
        envelope["data"]["diagnostics"][0]["message"]
            .as_str()
            .is_some_and(|detail| detail.contains("at least one --file or --dir"))
    );

    let validator = validator_for_payload_def("ruleValidation");
    validator.validate(&envelope["data"]).unwrap();
}

#[cfg(unix)]
#[test]
fn rules_validate_dir_does_not_follow_symlinked_directories() {
    let temp = tempfile::tempdir().unwrap();
    let rules_dir = temp.path().join("rules");
    let linked_dir = temp.path().join("linked");
    std::fs::create_dir_all(&rules_dir).unwrap();
    std::fs::create_dir_all(&linked_dir).unwrap();
    std::fs::write(
        rules_dir.join("example-cache.toml"),
        r#"
manifest_version = 1
id = "example-cache"
category = "development"
name = "Example cache"
safety_level = "safe"
restore_hint = "Example rebuilds this cache automatically."

[provenance]
source = "reference-only"
license = "example-user-rule"
notes = "Local user-authored validation fixture; no external rule data copied."

[[platforms]]
platform = "macos"

[[platforms.targets]]
kind = "template"
value = "MACOS_CACHE_HOME/Example"
search_kind = "file"
"#,
    )
    .unwrap();
    std::fs::write(
        linked_dir.join("bad.toml"),
        r#"
manifest_version = 1
id = "bad-state"
category = "development"
name = "Bad credential target"
safety_level = "safe"
restore_hint = "Do not use."

[provenance]
source = "reference-only"
license = "example-user-rule"
notes = "Local user-authored validation fixture; no external rule data copied."

[[platforms]]
platform = "macos"

[[platforms.targets]]
kind = "template"
value = "MACOS_HOME/.ssh"
"#,
    )
    .unwrap();
    std::os::unix::fs::symlink(&linked_dir, rules_dir.join("linked")).unwrap();

    let output = common::command::rebecca()
        .args([
            "rules",
            "validate",
            "--format",
            "json",
            "--dir",
            rules_dir.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["data"]["rule_count"], 1);
    assert_eq!(envelope["data"]["rules"][0], "macos.example-cache");
    assert_eq!(envelope["data"]["summary"]["diagnostics"], 0);
    assert_eq!(
        envelope["data"]["rule_previews"][0]["rule_id"],
        "macos.example-cache"
    );
    assert_eq!(
        envelope["data"]["rule_previews"][0]["enabled_by_validation"],
        false
    );
}

#[test]
fn rules_validate_format_json_rejects_protected_external_manifest() {
    let temp = tempfile::tempdir().unwrap();
    let rule_file = temp.path().join("bad.toml");
    std::fs::write(
        &rule_file,
        r#"
manifest_version = 1
id = "bad-state"
category = "development"
name = "Bad credential target"
safety_level = "safe"
restore_hint = "Do not use."

[provenance]
source = "reference-only"
license = "example-user-rule"
notes = "Local user-authored validation fixture; no external rule data copied."

[[platforms]]
platform = "macos"

[[platforms.targets]]
kind = "template"
value = "MACOS_HOME/.ssh"
"#,
    )
    .unwrap();

    let output = common::command::rebecca()
        .args([
            "rules",
            "validate",
            "--format",
            "json",
            "--file",
            rule_file.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stderr.is_empty());
    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "rules validate");
    assert_eq!(envelope["payload_kind"], "rule-validation");
    assert_eq!(envelope["data"]["valid"], false);
    assert_eq!(
        envelope["data"]["diagnostics"][0]["code"],
        "rule-catalog-invalid"
    );
    assert!(
        envelope["data"]["diagnostics"][0]["message"]
            .as_str()
            .is_some_and(|detail| detail.contains("blocked by credentials"))
    );

    let validator = validator_for_payload_def("ruleValidation");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn rules_import_lifecycle_is_disabled_by_default_and_feeds_clean_when_enabled() {
    let temp = tempfile::tempdir().unwrap();
    let rule_file = temp.path().join("external-cache.toml");
    std::fs::write(&rule_file, external_rule_manifest("external-cache")).unwrap();
    create_external_rule_target(&temp);

    let import = common::isolated::isolated_rebecca(&temp)
        .args([
            "rules",
            "import",
            "--format",
            "json",
            "--file",
            rule_file.to_str().unwrap(),
        ])
        .output()
        .unwrap();
    assert!(
        import.status.success(),
        "stderr: {}",
        common::support::stderr(&import)
    );
    let import_envelope = common::support::api_envelope(&import.stdout);
    assert_eq!(import_envelope["payload_kind"], "rule-import");
    assert_eq!(import_envelope["data"]["imported"]["enabled"], false);
    let import_id = import_envelope["data"]["imported"]["import_id"]
        .as_str()
        .unwrap()
        .to_string();
    let rule_id = common::support::current_platform_rule_id("external-cache");
    assert_eq!(import_envelope["data"]["imported"]["rule_ids"][0], rule_id);

    let disabled_clean = common::isolated::isolated_rebecca(&temp)
        .args([
            "clean",
            "--dry-run",
            "--format",
            "json",
            "--no-scan-cache",
            "--rule",
            &rule_id,
        ])
        .output()
        .unwrap();
    assert!(!disabled_clean.status.success());

    let enable = common::isolated::isolated_rebecca(&temp)
        .args(["rules", "enable", "--format", "json", &import_id])
        .output()
        .unwrap();
    assert!(
        enable.status.success(),
        "stderr: {}",
        common::support::stderr(&enable)
    );
    let enable_envelope = common::support::api_envelope(&enable.stdout);
    assert_eq!(enable_envelope["payload_kind"], "rule-import-mutation");
    assert_eq!(enable_envelope["data"]["enabled"], true);

    let list = common::isolated::isolated_rebecca(&temp)
        .args(["rules", "list", "--format", "json"])
        .output()
        .unwrap();
    assert!(
        list.status.success(),
        "stderr: {}",
        common::support::stderr(&list)
    );
    let list_envelope = common::support::api_envelope(&list.stdout);
    assert_eq!(list_envelope["payload_kind"], "rule-import-list");
    assert_eq!(list_envelope["data"]["entries"][0]["enabled"], true);

    let enabled_clean = common::isolated::isolated_rebecca(&temp)
        .args([
            "clean",
            "--dry-run",
            "--format",
            "json",
            "--no-scan-cache",
            "--rule",
            &rule_id,
        ])
        .output()
        .unwrap();
    assert!(
        enabled_clean.status.success(),
        "stderr: {}",
        common::support::stderr(&enabled_clean)
    );
    let clean_envelope = common::support::api_envelope(&enabled_clean.stdout);
    assert!(
        clean_envelope["data"]["targets"]
            .as_array()
            .unwrap()
            .iter()
            .any(|target| target["rule_id"] == rule_id && target["status"] == "allowed")
    );

    let disable = common::isolated::isolated_rebecca(&temp)
        .args(["rules", "disable", "--format", "json", &import_id])
        .output()
        .unwrap();
    assert!(disable.status.success());
    let disable_envelope = common::support::api_envelope(&disable.stdout);
    assert_eq!(disable_envelope["data"]["enabled"], false);

    let remove = common::isolated::isolated_rebecca(&temp)
        .args(["rules", "remove", "--format", "json", &import_id])
        .output()
        .unwrap();
    assert!(remove.status.success());
    let remove_envelope = common::support::api_envelope(&remove.stdout);
    assert_eq!(remove_envelope["data"]["removed"], true);

    let validator = validator_for_payload_def("ruleImport");
    validator.validate(&import_envelope["data"]).unwrap();
    let validator = validator_for_payload_def("ruleImportList");
    validator.validate(&list_envelope["data"]).unwrap();
    let validator = validator_for_payload_def("ruleImportMutation");
    validator.validate(&remove_envelope["data"]).unwrap();
}

#[test]
fn config_validate_format_json_reports_effective_config_contract() {
    let temp = tempfile::tempdir().unwrap();
    let output = common::isolated::isolated_rebecca(&temp)
        .args(["config", "validate", "--format", "json"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "config validate");
    assert_eq!(envelope["payload_kind"], "config-validation");
    assert_eq!(envelope["data"]["valid"], true);
    assert_eq!(envelope["data"]["schema_version"], 1);
    assert_eq!(envelope["data"]["summary"]["diagnostics"], 0);
    assert!(
        envelope["data"]["diagnostics"]
            .as_array()
            .unwrap()
            .is_empty()
    );

    let validator = validator_for_payload_def("configValidation");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn config_validate_format_json_missing_explicit_file_returns_read_error() {
    let temp = tempfile::tempdir().unwrap();
    let missing_config = temp.path().join("missing.toml");
    let output = common::isolated::isolated_rebecca(&temp)
        .args([
            "config",
            "validate",
            "--format",
            "json",
            "--file",
            missing_config.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stderr.is_empty());
    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "config validate");
    assert_eq!(envelope["payload_kind"], "config-validation");
    assert_eq!(envelope["data"]["valid"], false);
    assert_eq!(
        envelope["data"]["diagnostics"][0]["code"],
        "config-read-failed"
    );

    let validator = validator_for_payload_def("configValidation");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn config_show_format_json_returns_loaded_and_effective_config() {
    let temp = tempfile::tempdir().unwrap();
    let output = common::isolated::isolated_rebecca(&temp)
        .args(["config", "show", "--format", "json"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "config show");
    assert_eq!(envelope["payload_kind"], "config-view");
    assert_eq!(envelope["data"]["schema_version"], 1);
    assert!(
        envelope["data"]["config"]["scan_cache"]["directory_record_max_age_seconds"]
            .as_u64()
            .is_some()
    );
    assert!(
        envelope["data"]["runtime"]["app_paths"]["history_file"]
            .as_str()
            .unwrap()
            .contains("history.jsonl")
    );

    let validator = validator_for_payload_def("configView");
    validator.validate(&envelope["data"]).unwrap();
}

#[test]
fn config_show_format_json_malformed_explicit_file_returns_parse_error() {
    let temp = tempfile::tempdir().unwrap();
    let config_file = temp.path().join("broken.toml");
    std::fs::write(&config_file, "version = 2\n").unwrap();

    let output = common::isolated::isolated_rebecca(&temp)
        .args([
            "config",
            "show",
            "--format",
            "json",
            "--file",
            config_file.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stdout.is_empty());
    let envelope = parse_json(&output.stderr);
    assert_error_schema(&envelope);
    assert_eq!(envelope["command"], "config show");
    assert_eq!(envelope["error"]["code"], "config-parse-failed");
}

#[test]
fn invalid_format_is_rejected_by_clap() {
    let output = common::command::rebecca()
        .args(["clean", "--format", "invalid"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    let stderr = common::support::stderr(&output);
    assert!(stderr.contains("invalid value"));
    assert!(stderr.contains("json"));
    assert!(stderr.contains("ndjson"));
}

#[test]
fn parse_error_format_json_returns_error_envelope() {
    let output = common::command::rebecca()
        .args(["clean", "--format", "json", "--bogus"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stdout.is_empty());

    let value = parse_json(&output.stderr);
    assert_error_schema(&value);
    assert_eq!(value["command"], "clean");
    assert_eq!(value["error"]["code"], "invalid-arguments");
    assert_eq!(value["error"]["source"], "clap");
    assert!(
        value["error"]["detail"]
            .as_str()
            .unwrap()
            .contains("unexpected argument")
    );
}

#[test]
fn parse_error_format_ndjson_returns_error_event() {
    let output = common::command::rebecca()
        .args(["inspect", "artifacts", "--format", "ndjson", "--bogus"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stderr.is_empty());

    let events = String::from_utf8_lossy(&output.stdout)
        .lines()
        .map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
        .collect::<Vec<_>>();
    assert_eq!(events.len(), 1);
    let event = events.first().unwrap();
    assert_event_schema(event);
    assert_eq!(event["command"], "inspect artifacts");
    assert_eq!(event["event_kind"], "error");
    assert_eq!(event["error"]["code"], "invalid-arguments");
}

#[test]
fn destructive_commands_reject_dry_run_yes_in_machine_format() {
    let cases = [
        (
            ["clean", "--format", "json", "--dry-run", "--yes"].as_slice(),
            "clean",
        ),
        (
            ["apps", "clean", "--format", "json", "--dry-run", "--yes"].as_slice(),
            "apps clean",
        ),
        (
            ["purge", "--format", "json", "--dry-run", "--yes"].as_slice(),
            "purge",
        ),
        (
            ["cache", "prune", "--format", "json", "--dry-run", "--yes"].as_slice(),
            "cache prune",
        ),
        (
            ["cache", "purge", "--format", "json", "--dry-run", "--yes"].as_slice(),
            "cache purge",
        ),
    ];

    for (args, command) in cases {
        let output = common::command::rebecca().args(args).output().unwrap();

        assert!(!output.status.success(), "{command} should fail");
        assert!(
            output.stdout.is_empty(),
            "{command} should not write stdout"
        );
        let value = parse_json(&output.stderr);
        assert_error_schema(&value);
        assert_eq!(value["command"], command);
        assert_eq!(value["error"]["code"], "validation-error");
        assert!(
            value["error"]["detail"]
                .as_str()
                .unwrap()
                .contains("--dry-run cannot be combined with --yes")
        );
    }
}

#[test]
fn clean_format_ndjson_emits_lifecycle_events() {
    let temp = tempfile::tempdir().unwrap();
    let temp_cache = temp.path().join("temp");
    std::fs::create_dir_all(&temp_cache).unwrap();
    std::fs::write(temp_cache.join("cache.tmp"), b"cache").unwrap();

    let output = common::isolated::isolated_rebecca(&temp)
        .env("REBECCA_STEAM_DISCOVERY", "none")
        .env("TEMP", &temp_cache)
        .env("TMPDIR", &temp_cache)
        .args([
            "clean",
            "--format",
            "ndjson",
            "--scan-cache",
            "--category",
            "system",
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    let events = stdout
        .lines()
        .map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
        .collect::<Vec<_>>();

    assert!(
        events.len() >= 3,
        "expected started, progress, and completed events: {stdout}"
    );
    assert_eq!(events.first().unwrap()["event_kind"], "started");
    assert_eq!(events.last().unwrap()["event_kind"], "completed");
    assert!(
        events
            .iter()
            .all(|event| event["api_version"] == "rebecca.cli.v1")
    );
    assert!(events.iter().all(|event| event["kind"] == "event"));
    assert!(events.iter().all(|event| {
        assert_event_schema(event);
        true
    }));
    assert!(events.iter().all(|event| event["command"] == "clean"));
    assert!(events.windows(2).all(|pair| {
        pair[0]["sequence"].as_u64().unwrap() < pair[1]["sequence"].as_u64().unwrap()
    }));
    assert!(
        events.iter().any(|event| event["event_kind"]
            .as_str()
            .unwrap()
            .starts_with("scan-cache-"))
            || events
                .iter()
                .any(|event| event["event_kind"] == "target-finished")
    );
}

#[test]
fn clean_format_ndjson_omits_file_measured_events_by_default() {
    let temp = tempfile::tempdir().unwrap();
    let temp_cache = temp.path().join("temp");
    std::fs::create_dir_all(&temp_cache).unwrap();
    for index in 0..8 {
        std::fs::write(temp_cache.join(format!("cache-{index}.tmp")), b"cache").unwrap();
    }

    let output = common::isolated::isolated_rebecca(&temp)
        .env("TEMP", &temp_cache)
        .env("TMPDIR", &temp_cache)
        .args([
            "clean",
            "--format",
            "ndjson",
            "--no-scan-cache",
            "--rule",
            common::support::current_platform_user_temp_rule_id(),
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    let events = stdout
        .lines()
        .map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
        .collect::<Vec<_>>();

    assert!(
        events
            .iter()
            .any(|event| event["event_kind"] == "target-finished")
    );
    assert!(events.last().unwrap()["event_kind"] == "completed");
    assert!(
        events
            .iter()
            .all(|event| event["event_kind"] != "file-measured"),
        "default ndjson should not include file-level events: {stdout}"
    );
}

#[test]
fn clean_format_ndjson_file_progress_detail_emits_file_measured_events() {
    let temp = tempfile::tempdir().unwrap();
    let temp_cache = temp.path().join("temp");
    std::fs::create_dir_all(&temp_cache).unwrap();
    for index in 0..8 {
        std::fs::write(temp_cache.join(format!("cache-{index}.tmp")), b"cache").unwrap();
    }

    let output = common::isolated::isolated_rebecca(&temp)
        .env("TEMP", &temp_cache)
        .env("TMPDIR", &temp_cache)
        .args([
            "clean",
            "--format",
            "ndjson",
            "--progress-detail",
            "file",
            "--no-scan-cache",
            "--rule",
            common::support::current_platform_user_temp_rule_id(),
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    let events = stdout
        .lines()
        .map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
        .collect::<Vec<_>>();
    let file_events = events
        .iter()
        .filter(|event| event["event_kind"] == "file-measured")
        .count();

    assert_eq!(
        file_events, 8,
        "verbose ndjson should include file events: {stdout}"
    );
    assert!(events.last().unwrap()["event_kind"] == "completed");
}

#[test]
fn clean_format_ndjson_unknown_rule_terminates_with_error_event() {
    let temp = tempfile::tempdir().unwrap();
    let output = common::isolated::isolated_rebecca(&temp)
        .args(["clean", "--format", "ndjson", "--rule", "missing.rule"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stderr.is_empty());

    let stdout = String::from_utf8_lossy(&output.stdout);
    let events = stdout
        .lines()
        .map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
        .collect::<Vec<_>>();

    assert_eq!(events.first().unwrap()["event_kind"], "started");
    assert!(events.windows(2).all(|pair| {
        pair[0]["sequence"].as_u64().unwrap() < pair[1]["sequence"].as_u64().unwrap()
    }));

    let error = events.last().unwrap();
    assert_event_schema(error);
    assert_eq!(error["event_kind"], "error");
    assert_eq!(error["error"]["code"], "invalid-rule-id");
    assert_eq!(error["error"]["exit_code"], 1);
}

#[test]
fn inspect_artifacts_format_ndjson_invalid_root_returns_error_event() {
    let temp = tempfile::tempdir().unwrap();
    let missing = temp.path().join("missing-workspace");
    let output = common::isolated::isolated_rebecca(&temp)
        .args([
            "inspect",
            "artifacts",
            "--format",
            "ndjson",
            "--root",
            missing.to_str().unwrap(),
        ])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(output.stderr.is_empty());

    let stdout = String::from_utf8_lossy(&output.stdout);
    let events = stdout
        .lines()
        .map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
        .collect::<Vec<_>>();

    let error = events.last().unwrap();
    assert_event_schema(error);
    assert_eq!(error["command"], "inspect artifacts");
    assert_eq!(error["event_kind"], "error");
    assert_eq!(error["error"]["code"], "invalid-purge-root");
    assert!(
        error["error"]["detail"]
            .as_str()
            .unwrap()
            .contains("is not accessible")
    );
}

#[test]
fn doctor_permissions_format_json_returns_diagnostic_payload() {
    let output = common::command::rebecca()
        .args(["doctor", "permissions", "--format", "json"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "doctor permissions");
    assert_eq!(envelope["payload_kind"], "permissions-diagnostic");
    assert!(envelope["data"]["platform"].as_str().is_some());
    assert!(envelope["data"]["platform_supported"].as_bool().is_some());
    assert!(
        envelope["data"]["cleanup_execution_supported"]
            .as_bool()
            .is_some()
    );
    assert!(envelope["data"]["privilege_level"].as_str().is_some());
    assert!(envelope["data"]["suggested_action"].as_str().is_some());
    if cfg!(target_os = "macos") {
        let macos_privacy = envelope["data"]["macos_privacy"].as_object().unwrap();
        assert!(macos_privacy["status"].as_str().is_some());
        assert!(macos_privacy["probes"].as_array().is_some());
        assert!(macos_privacy["action_kind"].as_str().is_some());
        assert!(
            macos_privacy["full_disk_access_relevant"]
                .as_bool()
                .is_some()
        );
        assert!(
            macos_privacy["affected_cleanup_families"]
                .as_array()
                .is_some()
        );
        assert!(macos_privacy["suggested_action"].as_str().is_some());
    } else {
        assert!(envelope["data"].get("macos_privacy").is_none());
    }

    let validator = validator_for_payload_def("permissionsDiagnostic");
    assert!(
        validator.is_valid(&envelope["data"]),
        "permissions diagnostic should match schema: {:?}",
        validator
            .iter_errors(&envelope["data"])
            .map(|error| error.to_string())
            .collect::<Vec<_>>()
    );
}

#[test]
fn doctor_active_processes_format_json_returns_diagnostic_payload() {
    let output = common::command::rebecca()
        .env("REBECCA_ACTIVE_PROCESSES", "slack.exe:4242")
        .args(["doctor", "active-processes", "--format", "json"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        common::support::stderr(&output)
    );

    let envelope = common::support::api_envelope(&output.stdout);
    assert_success_schema(&envelope);
    assert_eq!(envelope["command"], "doctor active-processes");
    assert_eq!(envelope["payload_kind"], "active-process-diagnostic");

    let validator = validator_for_payload_def("activeProcessDiagnostic");
    assert!(
        validator.is_valid(&envelope["data"]),
        "active process diagnostic should match schema: {:?}",
        validator
            .iter_errors(&envelope["data"])
            .map(|error| error.to_string())
            .collect::<Vec<_>>()
    );
}

#[test]
fn cli_api_schema_documents_are_parseable_draft_2020_12() {
    for relative in [
        "envelope.schema.json",
        "error.schema.json",
        "event.schema.json",
        "payloads.schema.json",
        "config.schema.json",
        "cleaner-manifest-v1.schema.json",
    ] {
        let schema = read_doc_json(relative);
        assert_eq!(
            schema["$schema"],
            "https://json-schema.org/draft/2020-12/schema"
        );
        assert!(schema["$id"].as_str().is_some());
        assert!(schema["title"].as_str().is_some());
    }

    let payloads = read_doc_json("payloads.schema.json");
    let payload_kinds = payloads["$defs"]["payloadKind"]["enum"]
        .as_array()
        .unwrap()
        .iter()
        .map(|value| value.as_str().unwrap())
        .collect::<Vec<_>>();
    assert!(!payload_kinds.contains(&"project-artifact-insight"));
    assert!(payload_kinds.contains(&"active-process-diagnostic"));
}

#[test]
fn packaged_cli_api_schemas_match_documented_schemas() {
    for relative in [
        "envelope.schema.json",
        "error.schema.json",
        "event.schema.json",
        "payloads.schema.json",
        "config.schema.json",
        "cleaner-manifest-v1.schema.json",
    ] {
        assert_eq!(
            read_packaged_schema_bytes(relative),
            read_schema_doc_bytes(relative),
            "{relative} packaged schema should match docs/api/cli/v1"
        );
    }
}

#[test]
fn cli_api_catalog_and_inspect_payloads_are_documented_in_v1() {
    let payloads = read_doc_json("payloads.schema.json");
    let payload_kinds = payloads["$defs"]["payloadKind"]["enum"]
        .as_array()
        .unwrap()
        .iter()
        .map(|value| value.as_str().unwrap())
        .collect::<Vec<_>>();
    assert!(payload_kinds.contains(&"capabilities"));
    assert!(payload_kinds.contains(&"cli-schema"));
    assert!(payload_kinds.contains(&"config-validation"));
    assert!(payload_kinds.contains(&"config-view"));
    assert!(payload_kinds.contains(&"rule-import"));
    assert!(payload_kinds.contains(&"rule-import-list"));
    assert!(payload_kinds.contains(&"rule-import-mutation"));
    assert!(payload_kinds.contains(&"rule-validation"));
    assert!(payload_kinds.contains(&"catalog"));
    assert!(payload_kinds.contains(&"catalog-validation"));
    assert!(payload_kinds.contains(&"cache-doctor"));
    assert!(payload_kinds.contains(&"cache-inventory"));
    assert!(payload_kinds.contains(&"cache-prune-report"));
    assert!(payload_kinds.contains(&"inspect-artifacts"));
    assert!(payload_kinds.contains(&"inspect-lint"));
    assert!(payload_kinds.contains(&"inspect-map"));
    assert!(payload_kinds.contains(&"inspect-map-entry"));
    assert!(payload_kinds.contains(&"inspect-map-group"));
    assert!(payload_kinds.contains(&"inspect-progress"));
    assert!(payload_kinds.contains(&"inspect-space"));

    assert_eq!(payloads["$defs"]["capabilities"]["type"], "object");
    assert_eq!(payloads["$defs"]["cliSchema"]["type"], "object");
    assert_eq!(payloads["$defs"]["configValidation"]["type"], "object");
    assert_eq!(payloads["$defs"]["configView"]["type"], "object");
    assert_eq!(payloads["$defs"]["ruleImport"]["type"], "object");
    assert_eq!(payloads["$defs"]["ruleImportList"]["type"], "object");
    assert_eq!(payloads["$defs"]["ruleImportMutation"]["type"], "object");
    assert_eq!(payloads["$defs"]["ruleValidation"]["type"], "object");
    let catalog_item = &payloads["$defs"]["catalogItem"];
    assert_eq!(catalog_item["oneOf"].as_array().unwrap().len(), 5);
    assert_eq!(payloads["$defs"]["inspectSpace"]["type"], "object");
    assert_eq!(payloads["$defs"]["inspectArtifacts"]["type"], "object");
    assert_eq!(payloads["$defs"]["inspectLint"]["type"], "object");
    assert_eq!(payloads["$defs"]["inspectMap"]["type"], "object");
    assert_eq!(payloads["$defs"]["inspectMapEntryEvent"]["type"], "object");
    assert_eq!(payloads["$defs"]["inspectMapGroupEvent"]["type"], "object");
    assert_eq!(payloads["$defs"]["cacheInventory"]["type"], "object");
    assert_eq!(payloads["$defs"]["cacheDoctor"]["type"], "object");
    assert_eq!(payloads["$defs"]["cachePruneReport"]["type"], "object");

    let event = read_doc_json("event.schema.json");
    assert_eq!(
        event["properties"]["api_version"]["const"],
        "rebecca.cli.v1"
    );
    assert_eq!(
        event["properties"]["payload_kind"]["$ref"],
        "payloads.schema.json#/$defs/payloadKind"
    );

    let error = read_doc_json("error.schema.json");
    assert_eq!(
        error["properties"]["api_version"]["const"],
        "rebecca.cli.v1"
    );
}

#[test]
fn cli_api_examples_match_documented_envelope_shapes() {
    let error = read_doc_json("examples/error-invalid-rule.json");
    assert_error_schema(&error);
    assert_eq!(error["error"]["code"], "invalid-rule-id");

    let event = read_doc_json("examples/event-completed.json");
    assert_event_schema(&event);
    assert_eq!(event["event_kind"], "completed");

    let examples_dir = std::path::Path::new(API_DOCS).join("examples");
    let mut success_payload_kinds = std::fs::read_dir(examples_dir)
        .unwrap()
        .map(|entry| entry.unwrap().path())
        .filter(|path| {
            path.file_name()
                .and_then(|name| name.to_str())
                .is_some_and(|name| name.starts_with("success-") && name.ends_with(".json"))
        })
        .map(|path| {
            let value: serde_json::Value =
                serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap();
            assert_success_schema(&value);
            value["payload_kind"].as_str().unwrap().to_owned()
        })
        .collect::<Vec<_>>();
    success_payload_kinds.sort();

    assert!(success_payload_kinds.contains(&"cleanup-plan".to_string()));
    assert!(success_payload_kinds.contains(&"cache-doctor".to_string()));
    assert!(success_payload_kinds.contains(&"cache-inventory".to_string()));
    assert!(success_payload_kinds.contains(&"cache-prune-report".to_string()));
    assert!(success_payload_kinds.contains(&"project-artifact-cleanup-plan".to_string()));
    assert!(!success_payload_kinds.contains(&"project-artifact-insight".to_string()));
}

#[test]
fn cli_api_catalog_error_example_matches_documented_shape() {
    let error = read_doc_json("examples/error-invalid-warning.json");
    assert_error_schema(&error);
    assert_eq!(error["command"], "catalog");
    assert_eq!(error["error"]["code"], "invalid-catalog-selector");
}

#[test]
fn cli_api_catalog_example_validates_against_payload_schema() {
    let example = read_doc_json("examples/success-catalog.json");
    assert_success_schema(&example);
    assert_eq!(example["payload_kind"], "catalog");

    let validator = validator_for_payload_def("catalog");
    validator.validate(&example["data"]).unwrap();
}

#[test]
fn cli_api_inspect_examples_validate_against_payload_schema() {
    let space = read_doc_json("examples/success-inspect-space.json");
    assert_success_schema(&space);
    assert_eq!(space["payload_kind"], "inspect-space");
    validator_for_payload_def("inspectSpace")
        .validate(&space["data"])
        .unwrap();

    let artifacts = read_doc_json("examples/success-inspect-artifacts.json");
    assert_success_schema(&artifacts);
    assert_eq!(artifacts["payload_kind"], "inspect-artifacts");
    validator_for_payload_def("inspectArtifacts")
        .validate(&artifacts["data"])
        .unwrap();

    let map = read_doc_json("examples/success-inspect-map.json");
    assert_success_schema(&map);
    assert_eq!(map["payload_kind"], "inspect-map");
    validator_for_payload_def("inspectMap")
        .validate(&map["data"])
        .unwrap();

    let lint = read_doc_json("examples/success-inspect-lint.json");
    assert_success_schema(&lint);
    assert_eq!(lint["payload_kind"], "inspect-lint");
    validator_for_payload_def("inspectLint")
        .validate(&lint["data"])
        .unwrap();
}

#[test]
fn cli_api_cache_examples_validate_against_payload_schema() {
    let inspect = read_doc_json("examples/success-cache-inspect.json");
    assert_success_schema(&inspect);
    assert_eq!(inspect["payload_kind"], "cache-inventory");
    validator_for_payload_def("cacheInventory")
        .validate(&inspect["data"])
        .unwrap();

    let doctor = read_doc_json("examples/success-cache-doctor.json");
    assert_success_schema(&doctor);
    assert_eq!(doctor["payload_kind"], "cache-doctor");
    validator_for_payload_def("cacheDoctor")
        .validate(&doctor["data"])
        .unwrap();

    let prune = read_doc_json("examples/success-cache-prune.json");
    assert_success_schema(&prune);
    assert_eq!(prune["payload_kind"], "cache-prune-report");
    validator_for_payload_def("cachePruneReport")
        .validate(&prune["data"])
        .unwrap();
}

#[test]
fn cli_api_gui_backend_examples_validate_against_payload_schema() {
    let capabilities = read_doc_json("examples/success-capabilities.json");
    assert_success_schema(&capabilities);
    assert_eq!(capabilities["payload_kind"], "capabilities");
    validator_for_payload_def("capabilities")
        .validate(&capabilities["data"])
        .unwrap();

    let schema = read_doc_json("examples/success-cli-schema.json");
    assert_success_schema(&schema);
    assert_eq!(schema["payload_kind"], "cli-schema");
    validator_for_payload_def("cliSchema")
        .validate(&schema["data"])
        .unwrap();

    let config_validation = read_doc_json("examples/success-config-validation.json");
    assert_success_schema(&config_validation);
    assert_eq!(config_validation["payload_kind"], "config-validation");
    validator_for_payload_def("configValidation")
        .validate(&config_validation["data"])
        .unwrap();

    let config_view = read_doc_json("examples/success-config-view.json");
    assert_success_schema(&config_view);
    assert_eq!(config_view["payload_kind"], "config-view");
    validator_for_payload_def("configView")
        .validate(&config_view["data"])
        .unwrap();

    let rule_validation = read_doc_json("examples/success-rule-validation.json");
    assert_success_schema(&rule_validation);
    assert_eq!(rule_validation["payload_kind"], "rule-validation");
    validator_for_payload_def("ruleValidation")
        .validate(&rule_validation["data"])
        .unwrap();
}
