diff --git a/crates/conceptweave-zotero/tests/review_contract.rs b/crates/conceptweave-zotero/tests/review_contract.rs index e0890264..1ba1c785 100644 --- a/crates/conceptweave-zotero/tests/review_contract.rs +++ b/crates/conceptweave-zotero/tests/review_contract.rs @@ -20,34 +20,43 @@ fn item(key: &str, title: &str, doi: &str) -> ZoteroItem { #[test] fn steward_abstention_reason_is_explicit_and_deterministic() { let blank = item("A", "", ""); - let multilingual = item("B", "온톨로지 정렬", ""); - let unmatched = item("C", "Other evidence", ""); - let matched = item("D", "Ontology alignment", ""); + let multilingual = [ + ("B", "온톨로지 정렬"), + ("C", "オントロジーアラインメント"), + ("D", "本体对齐"), + ("E", "căn chỉnh bản thể"), + ("F", "alineación de ontologías"), + ("G", "Ontologie-Ausrichtung für Wissensgraphen"), + ("H", "alignement d’ontologies sémantiques"), + ] + .map(|(key, title)| item(key, title, "")); + let unmatched = item("I", "Other evidence", ""); + let matched = item("J", "Ontology alignment", ""); - let report = classify_snapshot( - "9.0.6".into(), - None, - 42, - vec![blank, multilingual, unmatched, matched], - ); + let report = classify_snapshot("9.0.6".into(), None, 42, { + let mut items = vec![blank]; + items.extend(multilingual); + items.extend([unmatched, matched]); + items + }); assert_eq!( report.classified_items[0].abstention_reason, Some(AbstentionReason::MissingClassificationMetadata) ); + assert!(report.classified_items[1..=7].iter().all(|item| { + item.proposed_disposition == Disposition::NeedsStewardReview + && item.abstention_reason == Some(AbstentionReason::UnsupportedRuleVocabulary) + })); assert_eq!( - report.classified_items[1].abstention_reason, - Some(AbstentionReason::UnsupportedRuleVocabulary) - ); - assert_eq!( - report.classified_items[2].abstention_reason, + report.classified_items[8].abstention_reason, Some(AbstentionReason::NoDeterministicRuleMatch) ); assert_eq!( - report.classified_items[3].proposed_disposition, + report.classified_items[9].proposed_disposition, Disposition::AlignmentVersioning ); - assert_eq!(report.classified_items[3].abstention_reason, None); + assert_eq!(report.classified_items[9].abstention_reason, None); } #[test] diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 01db27c8..a2274451 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -89,6 +89,26 @@ Remaining work: mandatory adoption by restoration, worksheet, duplicate and writ ## DDD fitness constraints +### PR #16 multilingual successor verification (2026-09-06) + +Baseline `044018cef4e5d3e919b278a1cfebe56857863601` passed 87 tests/19 suites. +Normal merge `4f3ac52a27ff1090b94c63ff807ddb5bd9ce872f` retains that entire delta +and parent `42ddd81adf8d994f67a30f0c6b8383d637073c72`. Production source is identical +to the repaired parent; `review_contract.rs` is identical to the prior PR #16. +An independent static review confirmed all seven non-English cases, English +match, blank metadata and unmatched metadata remain without reduced assertions. +This is abstention safety, not multilingual classification or completed research. + +Exact merged source passed 109 tests/19 result suites including three doctests, +strict all-target Clippy, warnings-denied rustdoc, format, CI-contract and diff +checks, plus the unchanged coverage gate: 185/185 functions, 1770/1770 normalized +regions and 320/320 normalized branches. Raw LLVM remains 1998/2050 lines, +2934/3014 regions and 280/320 branches, not 100%. Logs use the +`/tmp/conceptweave-pr16-scope-` prefix (`baseline`, `final`, `clippy`, `rustdoc`, +`coverage`, each with `.log`). No new production code, live write, paper decision, +approval, protected merge or release is claimed. Next owner is PR #17 authenticated +transport; it must preserve required bindings and indeterminate-request semantics. + ### PR #15 execution-scope and uncertainty repair (2026-09-06) Baseline `45e9c493` passed 87 tests/19 result suites; normal merge `2887f70`