use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
fn ev() -> Command {
Command::new(env!("CARGO_BIN_EXE_ev"))
}
fn repo() -> std::path::PathBuf {
static N: AtomicU64 = AtomicU64::new(0);
let p = std::env::temp_dir().join(format!(
"ev-migrate-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::Relaxed)
));
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).unwrap();
assert!(ev()
.arg("init")
.current_dir(&p)
.output()
.unwrap()
.status
.success());
p
}
fn run(repo: &std::path::Path, args: &[&str]) -> std::process::Output {
ev().args(args).current_dir(repo).output().unwrap()
}
fn write_source(repo: &std::path::Path, kind: &str, name: &str, body: &str) -> String {
let path = repo.join(name);
std::fs::write(&path, body).unwrap();
format!("{kind}:{}", path.display())
}
fn write_map(repo: &std::path::Path, name: &str, body: &str) -> String {
let path = repo.join(name);
std::fs::write(&path, body).unwrap();
path.display().to_string()
}
fn tick_count(repo: &std::path::Path) -> usize {
std::fs::read_dir(repo.join(".evolving/ticks"))
.unwrap()
.filter(|e| e.as_ref().unwrap().path().is_file())
.count()
}
const TWO_ROUNDS: &str = "\
## R2289 restore-safety counter DB-backed
- rejected: Redis: would add a new infra dependency
## R2290 ship the cross-pod drain
";
#[test]
fn migrate_should_skip_every_record_when_run_twice() {
let r = repo();
let src = write_source(&r, "gitlog", "chat-room.md", TWO_ROUNDS);
let first = run(&r, &["migrate", "--source", &src, "--blame", "Wang Yu"]);
assert!(
first.status.success(),
"stderr: {}",
String::from_utf8_lossy(&first.stderr)
);
let after_first = tick_count(&r);
assert_eq!(after_first, 2, "first import writes both records");
let second = run(&r, &["migrate", "--source", &src, "--blame", "Wang Yu"]);
assert!(second.status.success());
assert_eq!(tick_count(&r), after_first, "a re-run writes no new ticks");
let out = String::from_utf8_lossy(&second.stdout);
assert!(
out.contains("imported 0") && out.contains("skipped 2"),
"summary was {out:?}"
);
}
#[test]
fn migrate_should_report_the_relinked_count_when_a_record_is_back_dated() {
let r = repo();
let later = write_source(
&r,
"gitlog",
"later.md",
"## R2290 ship the cross-pod drain\n",
);
assert!(
run(&r, &["migrate", "--source", &later, "--blame", "Wang Yu"])
.status
.success()
);
let both = write_source(
&r,
"gitlog",
"both.md",
"## R2289 restore-safety counter DB-backed\n## R2290 ship the cross-pod drain\n",
);
let out = run(&r, &["migrate", "--source", &both, "--blame", "Wang Yu"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("imported 1") && s.contains("re-linked 1"),
"summary was {s:?}"
);
}
#[test]
fn migrate_reconcile_should_surface_a_source_only_ruling_as_a_gap() {
let r = repo();
let seed = write_source(
&r,
"gitlog",
"seed.md",
"## R2289 restore-safety counter DB-backed\n",
);
assert!(
run(&r, &["migrate", "--source", &seed, "--blame", "Wang Yu"])
.status
.success()
);
let against = write_source(
&r,
"gitlog",
"against.md",
"## R2289 restore-safety counter DB-backed\n## R9999 a ruling never captured\n",
);
let out = run(&r, &["migrate", "--reconcile", "--against", &against]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains("in-both 1"), "summary was {s:?}");
assert!(s.contains("source-only 1"), "summary was {s:?}");
}
#[test]
fn migrate_should_require_a_blame_fallback_when_a_source_lacks_authors() {
let r = repo();
let src = write_source(&r, "gitlog", "no-authors.md", TWO_ROUNDS);
let out = run(&r, &["migrate", "--source", &src]);
assert!(out.status.success(), "a surfaced gap is not a hard failure");
assert_eq!(tick_count(&r), 0, "no tick is written without an author");
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("source-only gap") || s.contains("source-only"),
"the gap must be surfaced; summary was {s:?}"
);
}
#[test]
fn migrate_dry_run_should_write_no_tick_when_asked_to_preview() {
let r = repo();
let src = write_source(&r, "gitlog", "chat-room.md", TWO_ROUNDS);
let out = run(
&r,
&[
"migrate",
"--source",
&src,
"--blame",
"Wang Yu",
"--dry-run",
],
);
assert!(out.status.success());
assert_eq!(tick_count(&r), 0, "--dry-run writes no ticks");
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("imported 2"),
"preview should count both; was {s:?}"
);
}
#[test]
fn migrate_bind_check_should_print_a_harvested_check_when_a_selector_is_given() {
let r = repo();
let out = run(
&r,
&[
"migrate",
"--bind-check",
"pytest tests/test_invariant_no_redis.py",
"--on-platform",
"linux-ci",
"--triggered-by",
"pyproject.toml",
"--surface",
"pyproject-deps",
"--verified-at-sha",
"d308afac1b2c3d4e5f60718293a4b5c6d7e8f901",
],
);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains("harvested"), "output was {s:?}");
assert!(
s.contains("pytest tests/test_invariant_no_redis.py"),
"output was {s:?}"
);
}
#[test]
fn migrate_should_tag_an_imported_decision_from_the_jurisdiction_map() {
let r = repo();
let src = write_source(
&r,
"gitlog",
"chat-room.md",
"## R2289 restore-safety counter DB-backed\n",
);
let map = write_map(&r, "jurisdiction.map", "# round -> bucket\nR2289 C\n");
let out = run(
&r,
&[
"migrate",
"--source",
&src,
"--blame",
"Wang Yu",
"--jurisdiction-map",
&map,
],
);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let list = run(&r, &["list"]);
let l = String::from_utf8_lossy(&list.stdout);
assert!(
l.contains("jurisdiction=C"),
"list did not render the imported jurisdiction: {l:?}"
);
let id = l
.lines()
.find(|line| line.contains("jurisdiction=C"))
.and_then(|line| line.split('\t').next())
.expect("a row with the tagged decision");
let show = run(&r, &["show", id]);
assert!(
String::from_utf8_lossy(&show.stdout).contains("jurisdiction: C"),
"show did not render jurisdiction: {}",
String::from_utf8_lossy(&show.stdout)
);
}
#[test]
fn migrate_should_leave_a_decision_untagged_when_its_key_is_absent_from_the_map() {
let r = repo();
let src = write_source(
&r,
"gitlog",
"chat-room.md",
"## R2290 ship the cross-pod drain\n",
);
let map = write_map(&r, "jurisdiction.map", "R9999 C\n");
let out = run(
&r,
&[
"migrate",
"--source",
&src,
"--blame",
"Wang Yu",
"--jurisdiction-map",
&map,
],
);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let list = run(&r, &["list"]);
assert!(
!String::from_utf8_lossy(&list.stdout).contains("jurisdiction="),
"an unmapped key must import untagged: {}",
String::from_utf8_lossy(&list.stdout)
);
}
#[test]
fn migrate_should_reject_an_out_of_vocab_bucket_in_the_jurisdiction_map() {
let r = repo();
let src = write_source(
&r,
"gitlog",
"chat-room.md",
"## R2289 restore-safety counter DB-backed\n",
);
let map = write_map(&r, "jurisdiction.map", "R2289 Z\n");
let out = run(
&r,
&[
"migrate",
"--source",
&src,
"--blame",
"Wang Yu",
"--jurisdiction-map",
&map,
],
);
assert!(!out.status.success(), "an out-of-vocab bucket must fail");
assert_eq!(tick_count(&r), 0, "no tick is written on a bad map");
let e = String::from_utf8_lossy(&out.stderr);
assert!(
e.contains("R2289 Z") || e.contains("R2289"),
"the error should name the offending line: {e:?}"
);
}
#[test]
fn migrate_should_still_skip_a_tagged_record_on_a_re_run_because_jurisdiction_is_non_hashed() {
let r = repo();
let src = write_source(
&r,
"gitlog",
"chat-room.md",
"## R2289 restore-safety counter DB-backed\n",
);
let map = write_map(&r, "jurisdiction.map", "R2289 C\n");
let args = [
"migrate",
"--source",
&src,
"--blame",
"Wang Yu",
"--jurisdiction-map",
&map,
];
assert!(run(&r, &args).status.success());
assert_eq!(tick_count(&r), 1, "first import writes the record");
let second = run(&r, &args);
assert!(second.status.success());
assert_eq!(tick_count(&r), 1, "a re-run writes no new ticks");
let s = String::from_utf8_lossy(&second.stdout);
assert!(
s.contains("imported 0") && s.contains("skipped 1"),
"summary was {s:?}"
);
}
const CANONICAL_RULING: &str = "{\"kind\":\"ev-decision-intake\",\
\"decision\":\"rate-limit lives at the edge proxy\",\
\"grounds\":[{\"claim\":\"the edge sees every request first\",\"supports\":\"chosen\"},\
{\"claim\":\"the app tier double-counts\",\"supports\":\"rejected:app-tier\"}],\
\"blame\":\"Wang Yu\",\"authority\":\"user-ruled\",\"source_ref\":\"R1043\"}\n";
#[test]
fn migrate_should_ingest_a_canonical_jsonl_source_through_the_shared_backfill() {
let r = repo();
let src = write_source(&r, "canonical", "intake.jsonl", CANONICAL_RULING);
let out = run(&r, &["migrate", "--source", &src]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(tick_count(&r), 1, "the canonical record is imported");
let v = run(&r, &["verify"]);
assert!(
v.status.success(),
"the imported canonical chain must verify clean: {}",
String::from_utf8_lossy(&v.stderr)
);
}
#[test]
fn migrate_canonical_should_be_idempotent_on_source_ref_when_a_line_is_reingested() {
let r = repo();
let src = write_source(&r, "canonical", "intake.jsonl", CANONICAL_RULING);
assert!(run(&r, &["migrate", "--source", &src]).status.success());
assert_eq!(tick_count(&r), 1);
let second = run(&r, &["migrate", "--source", &src]);
assert!(second.status.success());
assert_eq!(tick_count(&r), 1, "a re-run writes no new ticks");
let s = String::from_utf8_lossy(&second.stdout);
assert!(
s.contains("imported 0") && s.contains("skipped 1"),
"summary was {s:?}"
);
}
#[test]
fn migrate_canonical_should_stamp_authority_user_ruled_so_the_ruling_surfaces_in_brief() {
let r = repo();
let src = write_source(&r, "canonical", "intake.jsonl", CANONICAL_RULING);
assert!(
run(&r, &["migrate", "--source", &src]).status.success(),
"the canonical ruling imports"
);
let brief = run(&r, &["brief"]);
assert!(brief.status.success());
let b = String::from_utf8_lossy(&brief.stdout);
assert!(
b.contains("rate-limit lives at the edge proxy") && b.contains("[user-ruled]"),
"the imported user ruling must surface in brief; was {b:?}"
);
}
#[test]
fn migrate_canonical_should_report_a_source_only_gap_when_a_line_has_no_blame_and_no_fallback() {
let r = repo();
let no_author = "{\"kind\":\"ev-decision-intake\",\"decision\":\"x\",\"grounds\":[],\"source_ref\":\"R7\"}\n";
let src = write_source(&r, "canonical", "intake.jsonl", no_author);
let out = run(&r, &["migrate", "--source", &src]);
assert!(out.status.success(), "a surfaced gap is not a hard failure");
assert_eq!(tick_count(&r), 0, "no tick is written without an author");
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("source-only"),
"the gap must be surfaced; was {s:?}"
);
}
#[test]
fn migrate_should_reject_a_canonical_line_with_an_unknown_kind() {
let r = repo();
let bad = "{\"kind\":\"notes\",\"decision\":\"x\",\"grounds\":[]}\n";
let src = write_source(&r, "canonical", "intake.jsonl", bad);
let out = run(&r, &["migrate", "--source", &src]);
assert!(!out.status.success(), "an unknown kind must fail loudly");
assert_eq!(tick_count(&r), 0, "nothing is written on a rejected source");
}
fn canonical_with_test(extra_tags: &str, counter: &str) -> String {
format!(
"{{\"kind\":\"ev-decision-intake\",\"decision\":\"keep the schema frozen\",\
\"grounds\":[{{\"claim\":\"the frozen schema still holds\",\"supports\":\"chosen\",\
\"check\":{{\"by\":\"test\",\"ref\":\"pytest test_schema.py\",\
\"verified_at_sha\":\"d308afac1b2c3d4e5f60718293a4b5c6d7e8f901\"{counter},\
\"liveness\":{{\"platforms\":[\"linux-ci\"],\"triggered_by\":[\"schema.sql\"],\"surfaces\":[\"schema-ddl\"]}}}}}}],\
\"blame\":\"Wang Yu\",\"source_ref\":\"R5\"{extra_tags}}}\n"
)
}
#[test]
fn ingest_should_accept_a_harvested_check_when_provenance_is_imported() {
let r = repo();
let body = canonical_with_test(",\"provenance\":\"imported\"", "");
let src = write_source(&r, "canonical", "intake.jsonl", &body);
let out = run(&r, &["migrate", "--source", &src]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(tick_count(&r), 1);
}
#[test]
fn ingest_should_refuse_a_harvested_check_when_provenance_is_agent_proposed() {
let r = repo();
let body = canonical_with_test(",\"provenance\":\"agent-proposed\"", "");
let src = write_source(&r, "canonical", "intake.jsonl", &body);
let out = run(&r, &["migrate", "--source", &src]);
assert!(!out.status.success(), "an agent-proposed harvest must fail");
assert_eq!(tick_count(&r), 0, "nothing is written on a refused record");
}
#[test]
fn ingest_should_accept_an_agent_proposed_binding_when_it_carries_a_counter_test() {
let r = repo();
let body = canonical_with_test(
",\"provenance\":\"agent-proposed\"",
",\"counter_test\":\"pytest test_schema.py::test_change_flips_red\"",
);
let src = write_source(&r, "canonical", "intake.jsonl", &body);
let out = run(&r, &["migrate", "--source", &src]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(tick_count(&r), 1);
}
#[test]
fn ingest_should_refuse_a_c_jurisdiction_record_that_carries_a_test_check() {
let r = repo();
let body = canonical_with_test(
",\"jurisdiction\":\"C\",\"provenance\":\"imported\"",
",\"counter_test\":\"pytest test_schema.py::test_change_flips_red\"",
);
let src = write_source(&r, "canonical", "intake.jsonl", &body);
let out = run(&r, &["migrate", "--source", &src]);
assert!(!out.status.success(), "a C/D test binding must fail");
assert_eq!(tick_count(&r), 0, "nothing is written on a refused record");
}
#[test]
fn migrate_canonical_should_default_provenance_to_imported_when_a_line_omits_it() {
let r = repo();
let src = write_source(&r, "canonical", "intake.jsonl", CANONICAL_RULING);
assert!(run(&r, &["migrate", "--source", &src]).status.success());
let id = std::fs::read_dir(r.join(".evolving/ticks"))
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().into_string().unwrap())
.find(|n| n.len() == 12)
.expect("one imported tick");
let raw = std::fs::read_to_string(r.join(".evolving/ticks").join(&id)).unwrap();
let v: serde_json::Value = serde_json::from_str(&raw).unwrap();
assert_eq!(
v.get("provenance").and_then(|x| x.as_str()),
Some("imported"),
"an undeclared canonical import defaults to imported; tick was {v}"
);
}
#[test]
fn reconcile_should_accept_a_canonical_source_and_report_the_capture_gap() {
let r = repo();
let seed = write_source(&r, "canonical", "seed.jsonl", CANONICAL_RULING);
assert!(run(&r, &["migrate", "--source", &seed]).status.success());
let extra = "{\"kind\":\"ev-decision-intake\",\"decision\":\"a ruling never captured\",\
\"grounds\":[],\"blame\":\"Wang Yu\",\"source_ref\":\"R9999\"}\n";
let against = write_source(
&r,
"canonical",
"against.jsonl",
&format!("{CANONICAL_RULING}{extra}"),
);
let out = run(&r, &["migrate", "--reconcile", "--against", &against]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains("in-both 1"), "summary was {s:?}");
assert!(s.contains("source-only 1"), "summary was {s:?}");
}
#[test]
fn ingest_should_error_when_an_inline_jurisdiction_conflicts_with_the_jurisdiction_map() {
let r = repo();
let body = "{\"kind\":\"ev-decision-intake\",\"decision\":\"x\",\"grounds\":[],\
\"blame\":\"Wang Yu\",\"jurisdiction\":\"C\",\"source_ref\":\"R1043\"}\n";
let src = write_source(&r, "canonical", "intake.jsonl", body);
let map = write_map(&r, "jurisdiction.map", "R1043 D\n");
let out = run(
&r,
&["migrate", "--source", &src, "--jurisdiction-map", &map],
);
assert!(!out.status.success(), "a jurisdiction conflict must fail");
assert_eq!(tick_count(&r), 0, "nothing is written on a conflict");
let e = String::from_utf8_lossy(&out.stderr);
assert!(
e.contains("conflicts"),
"the error should name the conflict: {e:?}"
);
}
#[test]
fn ingest_should_let_an_inline_jurisdiction_agree_with_a_matching_map_entry() {
let r = repo();
let body = "{\"kind\":\"ev-decision-intake\",\"decision\":\"x\",\"grounds\":[],\
\"blame\":\"Wang Yu\",\"jurisdiction\":\"C\",\"source_ref\":\"R1043\"}\n";
let src = write_source(&r, "canonical", "intake.jsonl", body);
let map = write_map(&r, "jurisdiction.map", "R1043 C\n");
let out = run(
&r,
&["migrate", "--source", &src, "--jurisdiction-map", &map],
);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let list = run(&r, &["list"]);
assert!(
String::from_utf8_lossy(&list.stdout).contains("jurisdiction=C"),
"list did not render the agreed jurisdiction"
);
}
#[test]
fn canonical_ingest_of_the_genesis_payload_with_non_hashed_extras_still_computes_the_frozen_id() {
let r = repo();
let genesis = "{\"kind\":\"ev-decision-intake\",\
\"decision\":\"freeze the retrieval schema for v2\",\
\"observe\":\"evaluating retrieval backend\",\
\"grounds\":[{\"claim\":\"team still wants a frozen schema\",\"supports\":\"chosen\",\
\"check\":{\"by\":\"person\",\"ref\":\"Q3 infra review\"}},\
{\"claim\":\"pgvector would lock our schema\",\"supports\":\"rejected:pgvector\"}],\
\"blame\":\"Wang Yu\",\"source_ref\":\"R-genesis\",\"provenance\":\"imported\",\"jurisdiction\":\"C\"}\n";
let src = write_source(&r, "canonical", "genesis.jsonl", genesis);
let out = run(&r, &["migrate", "--source", &src]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
r.join(".evolving/ticks/e2b337f53a1f").exists(),
"the canonical path must compute the same frozen genesis id; ticks: {:?}",
std::fs::read_dir(r.join(".evolving/ticks"))
.unwrap()
.map(|e| e.unwrap().file_name())
.collect::<Vec<_>>()
);
}
const RULING_NO_AUTH: &str = "{\"kind\":\"ev-decision-intake\",\"decision\":\"#247/#1458 Insights scope\",\"grounds\":[],\"blame\":\"Mac\",\"source_ref\":\"#247/#1458\",\"provenance\":\"imported\"}\n";
const RULING_USER_RULED: &str = "{\"kind\":\"ev-decision-intake\",\"decision\":\"#247/#1458 Insights scope\",\"grounds\":[],\"blame\":\"Mac\",\"authority\":\"user-ruled\",\"source_ref\":\"#247/#1458\",\"provenance\":\"imported\"}\n";
fn single_tick_id(repo: &std::path::Path) -> String {
std::fs::read_dir(repo.join(".evolving/ticks"))
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().into_string().unwrap())
.find(|n| n.len() == 12)
.expect("exactly one tick")
}
#[test]
fn migrate_should_report_a_discrepancy_when_a_re_import_corrects_a_tag_but_never_apply_it() {
let r = repo();
let p1 = write_source(&r, "canonical", "p1.jsonl", RULING_NO_AUTH);
assert!(run(&r, &["migrate", "--source", &p1]).status.success());
let id = single_tick_id(&r);
let before = std::fs::read_to_string(r.join(".evolving/ticks").join(&id)).unwrap();
let p2 = write_source(&r, "canonical", "p2.jsonl", RULING_USER_RULED);
let out = run(&r, &["migrate", "--source", &p2]);
assert!(out.status.success());
let said = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
said.contains("discrepancy") && said.contains("authority"),
"the corrected authority must surface as a discrepancy; was {said:?}"
);
assert!(said.contains("imported 0") && said.contains("skipped 1"));
let after = std::fs::read_to_string(r.join(".evolving/ticks").join(&id)).unwrap();
assert_eq!(before, after, "migrate must never rewrite a tick in place");
assert_eq!(
tick_count(&r),
1,
"no new tick is written on a discrepancy-skip"
);
}
#[test]
fn migrate_should_not_report_a_discrepancy_when_a_re_import_is_identical() {
let r = repo();
let p1 = write_source(&r, "canonical", "p1.jsonl", RULING_USER_RULED);
assert!(run(&r, &["migrate", "--source", &p1]).status.success());
let out = run(&r, &["migrate", "--source", &p1]);
let said = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
!said.contains("discrepancy"),
"an identical re-import is clean; was {said:?}"
);
}
#[test]
fn migrate_should_report_a_discrepancy_on_the_dry_run_path_too() {
let r = repo();
let p1 = write_source(&r, "canonical", "p1.jsonl", RULING_NO_AUTH);
assert!(run(&r, &["migrate", "--source", &p1]).status.success());
let p2 = write_source(&r, "canonical", "p2.jsonl", RULING_USER_RULED);
let out = run(&r, &["migrate", "--source", &p2, "--dry-run"]);
let said = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
said.contains("discrepancy"),
"dry-run must surface the pending discrepancy; was {said:?}"
);
}
#[test]
fn migrate_should_not_silently_double_import_a_within_pass_duplicate_source_key() {
let r = repo();
let body = "{\"kind\":\"ev-decision-intake\",\"decision\":\"R555 ruling\",\"grounds\":[],\"blame\":\"Mac\",\"source_ref\":\"R555\"}\n\
{\"kind\":\"ev-decision-intake\",\"decision\":\"R555 ruling\",\"grounds\":[],\"blame\":\"Mac\",\"authority\":\"user-ruled\",\"source_ref\":\"R555\"}\n";
let src = write_source(&r, "canonical", "dup.jsonl", body);
let out = run(&r, &["migrate", "--source", &src]);
assert!(out.status.success());
assert_eq!(
tick_count(&r),
1,
"a within-pass duplicate key must not double-import"
);
let said = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
said.contains("imported 1") && said.contains("skipped 1") && said.contains("discrepancy"),
"the within-pass collision must surface, not silently double-import; was {said:?}"
);
}
#[test]
fn migrate_should_round_trip_clean_through_verify_when_records_are_imported() {
let r = repo();
let src = write_source(&r, "gitlog", "chat-room.md", TWO_ROUNDS);
assert!(
run(&r, &["migrate", "--source", &src, "--blame", "Wang Yu"])
.status
.success()
);
let v = run(&r, &["verify"]);
assert!(
v.status.success(),
"verify failed: {}",
String::from_utf8_lossy(&v.stderr)
);
}