Skip to content

Commit f9ad110

Browse files
committed
Add unclassified issue reconcile baseline
1 parent 9040471 commit f9ad110

5 files changed

Lines changed: 109 additions & 0 deletions

File tree

docs/adapter-provider-backlog.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,39 @@
5656
- Anthropic Messages API를 `draft-spec` adapter로 붙이는 후보다.
5757
- 기대 가치:
5858
- OpenAI 계열 외 provider contract를 처음 검증할 수 있다.
59+
- 후보 상태:
60+
- 다음 cut 승격 검토 가능
61+
- 구현 착수 전 contract 초안 필요
5962
- 필요한 일:
6063
- API request/response parsing 추가
6164
- `ANTHROPIC_API_KEY` 등 env contract 정의
6265
- message content parsing fail-closed 규칙 추가
6366
- stub harness 확장
67+
- contract 초안:
68+
- adapter id:
69+
- `anthropic-messages:<model-name>`
70+
- 필수 env:
71+
- `ANTHROPIC_API_KEY`
72+
- 선택 env:
73+
- `ANTHROPIC_BASE_URL`
74+
- `ANTHROPIC_HTTP_TIMEOUT`
75+
- `ANTHROPIC_HTTP_MAX_ATTEMPTS`
76+
- request surface:
77+
- `messages`
78+
- deterministic JSON-only prompt
79+
- bounded timeout/retry
80+
- response expectations:
81+
- 첫 text block에서 JSON 문자열을 추출
82+
- text block 부재, invalid JSON, schema mismatch는 fail-closed
83+
- evidence contract:
84+
- 기존 `adapter_id`, `model_label`, `model_resolution_source`, `requested_model` 유지
85+
- 최소 회귀 기준:
86+
- success
87+
- missing API key
88+
- invalid JSON
89+
- missing text block
90+
- retryable HTTP
91+
- non-retryable HTTP
6492
- 리스크:
6593
- OpenAI 계열과 다른 payload contract로 테스트 표면이 크게 늘어난다.
6694
- 보류 이유:

docs/context-memory.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
- `blocked`
7575
- sign-off/report summary에 포함:
7676
- blocker/review issue summary
77+
- unclassified issue summary
7778
- field issue summary
7879
- top rejected rows
7980
- sample review evidence

docs/roadmap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- second OpenAI adapter baseline
4343
- `draft-spec` xlsx fail-closed baseline
4444
- OpenAI env validation baseline
45+
- unclassified issue reconcile baseline
4546

4647
## Optional Backlog
4748
- fixture 시나리오를 더 늘려 adapter edge case 회귀를 강화

docs/test-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ MVP가 `재현 가능성`, `설명 가능성`, `fail-closed` 원칙을 만족하
5353
- `unknown_code` 같은 blocker issue는 `blocked`로 분류된다
5454
- blocker가 없어도 loadable row가 0이면 `blocked`로 분류된다
5555
- `required_missing`, `rule_violation`, `type_mismatch``review-needed` 근거로 남는다
56+
- unclassified issue만 있어도 `review-needed``unclassified_issue_summary`가 남아야 한다
5657
- reconciliation summary는 `field_issue_summary`, `top_row_issue_summary`, `sample_issues`를 남긴다
5758

5859
### 7. Workflow Harness Tests

tests/test_cli_init.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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\n1001,Kim,ACTIVE\n1002,,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+
833911
def 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

Comments
 (0)