@@ -830,6 +830,84 @@ def test_reconcile_blocks_for_unknown_code_policy(tmp_path: Path) -> None:
830830 assert "진행을 중단" in reconciliation_summary ["recommended_next_action" ]
831831
832832
833+ def test_reconcile_tracks_unclassified_issue_summary (tmp_path : Path ) -> None :
834+ target_dir = tmp_path / "demo"
835+ assert main (["init" , str (target_dir )]) == 0
836+
837+ source_path = target_dir / "fixtures/members.csv"
838+ source_path .write_text (
839+ "member_no,name,status\n 1001,Kim,ACTIVE\n 1002,,INACTIVE\n " ,
840+ encoding = "utf-8" ,
841+ )
842+ assert main (
843+ [
844+ "--project-dir" ,
845+ str (target_dir ),
846+ "approve-spec" ,
847+ "--spec" ,
848+ "specs/mapping-spec.yaml" ,
849+ "--approver" ,
850+ "lead01" ,
851+ "--note" ,
852+ "reviewed" ,
853+ ]
854+ ) == 0
855+
856+ run_dir = target_dir / "runs" / "20260411-unclassified-issue"
857+ assert main (
858+ [
859+ "--project-dir" ,
860+ str (target_dir ),
861+ "dry-run" ,
862+ "--source" ,
863+ "fixtures/members.csv" ,
864+ "--spec" ,
865+ "specs/mapping-spec.yaml" ,
866+ "--out-dir" ,
867+ str (run_dir ),
868+ ]
869+ ) == 0
870+
871+ rejects_path = run_dir / "rejects.csv"
872+ with rejects_path .open ("r" , encoding = "utf-8" , newline = "" ) as handle :
873+ reject_rows = list (csv .DictReader (handle ))
874+
875+ assert len (reject_rows ) == 1
876+ reject_rows [0 ]["issue_code" ] = "custom_policy_flag"
877+ reject_rows [0 ]["message" ] = "custom policy requires manual review"
878+
879+ with rejects_path .open ("w" , encoding = "utf-8" , newline = "" ) as handle :
880+ writer = csv .DictWriter (handle , fieldnames = list (reject_rows [0 ].keys ()))
881+ writer .writeheader ()
882+ writer .writerows (reject_rows )
883+
884+ report_path = target_dir / "reports" / "sign-off-20260411-unclassified-issue.md"
885+ assert main (
886+ [
887+ "--project-dir" ,
888+ str (target_dir ),
889+ "reconcile" ,
890+ "--run" ,
891+ str (run_dir ),
892+ "--out" ,
893+ str (report_path ),
894+ ]
895+ ) == 0
896+
897+ reconciliation_summary = json .loads ((run_dir / "reconciliation-summary.json" ).read_text (encoding = "utf-8" ))
898+
899+ assert reconciliation_summary ["verdict" ] == "review-needed"
900+ assert reconciliation_summary ["blocker_issue_summary" ] == {}
901+ assert reconciliation_summary ["review_issue_summary" ] == {}
902+ assert reconciliation_summary ["unclassified_issue_summary" ] == {"custom_policy_flag" : 1 }
903+ assert reconciliation_summary ["field_issue_summary" ]["full_name" ]["custom_policy_flag" ] == 1
904+ assert any (
905+ factor ["category" ] == "unclassified_issue" and factor ["code" ] == "custom_policy_flag"
906+ for factor in reconciliation_summary ["verdict_factors" ]
907+ )
908+ assert "custom_policy_flag" in reconciliation_summary ["recommended_next_action" ]
909+
910+
833911def test_dry_run_auto_advances_workflow_from_work_to_inspect (tmp_path : Path ) -> None :
834912 target_dir = tmp_path / "demo"
835913 assert main (["init" , str (target_dir )]) == 0
0 commit comments