<!-- task=m-plane-origin-authoring-roundtrip-sync/07+05,06_slug_reconcile plan=1 tag=REVIEW_REFACTOR -->

# Plan - REVIEW_REFACTOR

## 이 파일을 읽는 구현 에이전트에게

이 후속 계획은 이전 review의 Required 2건만 닫는다. 직접 사용자에게 질문하지 말고, 새 결정이 필요한 경우에도 선택된 SDD 결정 또는 Milestone lock 결정만 `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청`에 기록한다. 이번 범위는 repo-fixable code/test follow-up이므로 기본적으로 `USER_REVIEW.md` 대상이 아니다.

## 배경

이전 구현은 같은 filename slug 재처리에서 canonical id 중심 lookup과 conflict mapping을 추가했지만, 기존 identity row가 있을 때 provider projection target과 completed-cycle safe attribute update가 계획/SDD S12를 완전히 만족하지 못한다. 같은 slug 재처리는 중복 Plane item을 만들지 않으면서 path, provider work item id, revision, completed step ledger를 갱신해야 한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
- Task ids:
  - `slug-reconcile`: 같은 Milestone slug 재처리는 중복 Plane item을 만들지 않고 path, provider work item id, revision, 완료 step만 갱신한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`
- Acceptance scenarios:
  - `S12`: task=`slug-reconcile`; evidence=`retry/idempotency tests for same slug reprocess, partial failure resume, and slug conflict/reuse failure`
- Completion mode: spec-check-on-pass

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-plane-origin-authoring-roundtrip-sync/07+05,06_slug_reconcile/plan_cloud_G05_0.log`
- Current archived review: `agent-task/m-plane-origin-authoring-roundtrip-sync/07+05,06_slug_reconcile/code_review_cloud_G05_0.log`
- Verdict: FAIL
- Required summary:
  - `services/core/internal/roadmapsyncpipeline/service.go:128`: existing row lookup overwrites incoming trigger identity/ref, so provider projection can target the stale persisted Plane item while the ledger upsert records the new work item id.
  - `services/core/internal/roadmapsyncpipeline/service.go:265`: `ActionComplete` returns before safe attribute upsert, so all-steps-complete same-slug reprocesses do not update path/work item/revision attributes.
- Suggested/Nit summary: 없음
- Affected files:
  - `services/core/internal/roadmapsyncpipeline/service.go`
  - `services/core/internal/roadmapsyncpipeline/service_test.go`
  - 필요 시 `services/core/internal/roadmapsync/retry.go` 또는 `services/core/internal/roadmapsync/retry_test.go`
- Verification evidence from failed loop:
  - `go test -count=1 ./internal/gitosync ./internal/roadmapsync ./internal/storage ./internal/roadmapsyncpipeline` in `services/core`: PASS
  - `go test -count=1 ./...` in `services/core`: PASS
  - `git diff --check`: PASS after review-only EOF blank-line cleanup
- Roadmap carryover: `slug-reconcile`
- Spec carryover: `S12`
- Narrow archive reread allowed: if exact prior context is needed, read only the two archived files listed above.

## 분석 결과

### 범위 결정 근거

이 후속은 `SyncCreation` orchestration과 해당 unit tests에 한정한다. Scanner duplicate active slug detection, storage SQL unique index, live Plane smoke, Gito delivery, Plane Todo projection 전체 smoke는 이번 Required의 직접 수정 범위가 아니다.

### 빌드 등급

`local-G05`: bounded Go service/test 수정이며 provider side effect target과 complete-row update를 fake tests로 결정적으로 검증할 수 있다.

## 구현 체크리스트

- [ ] 기존 identity row가 있어도 `SyncCreation`이 incoming trigger identity/ref를 stale persisted row로 덮어쓰지 않고, provider projection target이 matched candidate의 최신 provider work item id/path를 사용하도록 수정한다.
- [ ] all-steps-complete same-slug 재처리에서도 provider call 없이 safe attribute upsert가 수행되어 path, provider work item id, revision이 갱신되도록 수정한다.
- [ ] `roadmapsyncpipeline` tests에 changed provider work item id 재처리 provider `Ref` 검증과 completed-cycle attribute update/no-provider-call 검증을 추가한다.
- [ ] `cd services/core && go test -count=1 ./internal/roadmapsyncpipeline`와 `cd services/core && go test -count=1 ./internal/gitosync ./internal/roadmapsync ./internal/storage ./internal/roadmapsyncpipeline`와 `cd services/core && go test -count=1 ./...`와 `git diff --check`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 plan은 기존 `07+05,06_slug_reconcile` follow-up이다. 선행 complete evidence는 이전 loop에서 확인되었으므로 새 archive 검색을 반복하지 않는다.

### [REVIEW_REFACTOR-1] Preserve Trigger Ref For Provider Projection

문제: `SyncCreation`이 existing row를 milestone id로 찾은 뒤 `in.Expected`와 `in.Ref`를 persisted row 값으로 덮어쓴다. 이후 `MatchDevelopMilestone`은 scanned identity의 새 work item id를 candidate로 만들 수 있지만, provider projection input은 stale `in.Ref`를 사용한다.

해결 방법: persisted row는 `ExistingIdentity`/revision/step ledger 판단에만 사용하고, incoming trigger identity/ref는 별도로 보존한다. Projection ref는 normalized `candidate.Identity`에서 만들거나, incoming ref가 candidate와 일치하면 incoming ref를 사용한다. tenant/project fallback이 필요하면 existing row를 "빈 필드 보충"에만 사용하고 새 work item id를 덮지 않는다.

수정 파일 및 체크리스트:
- [ ] `services/core/internal/roadmapsyncpipeline/service.go`
- [ ] `services/core/internal/roadmapsyncpipeline/service_test.go`

테스트 작성: 작성한다. fake provider가 comment/body/status input ref를 기록하도록 확장하고, existing row의 `WorkItemID=NOMAD-OLD`, scanned/incoming identity의 `WorkItemID=NOMAD-1`인 partial resume에서 provider calls가 `NOMAD-1`을 대상으로 실행되고 row도 `NOMAD-1`로 갱신되는지 확인한다.

중간 검증: `cd services/core && go test -count=1 ./internal/roadmapsyncpipeline`.

### [REVIEW_REFACTOR-2] Update Attributes Before Complete No-Op

문제: `ReconcileCreationCycle`이 `ActionComplete`를 반환하면 `SyncCreation`이 즉시 `SyncActionComplete`로 빠져 safe attribute upsert를 실행하지 않는다. 모든 step이 완료된 same-slug reprocess에서 path/work item/revision 변경을 ledger row에 반영하지 못한다.

해결 방법: reconcile이 conflict/self-mutation이 아니고 existing row가 있는 경우, complete 반환 전에도 candidate identity와 trigger revisions가 persisted row와 다르면 provider side effect 없이 upsert한다. 다만 revision mismatch conflict는 기존처럼 upsert 전에 차단해야 한다.

수정 파일 및 체크리스트:
- [ ] `services/core/internal/roadmapsyncpipeline/service.go`
- [ ] `services/core/internal/roadmapsyncpipeline/service_test.go`

테스트 작성: 작성한다. completed set에 `develop_matched`, `original_comment_preserved`, `plane_body_updated`, `plane_todo_moved`가 모두 있는 existing row에서 scanned path/work item id/revision이 바뀐 입력을 넣고, 결과는 `SyncActionComplete`, provider calls는 없음, store upsert는 최신 path/work item id/revision으로 1회 발생해야 한다.

중간 검증: `cd services/core && go test -count=1 ./internal/roadmapsyncpipeline`.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/core/internal/roadmapsyncpipeline/service.go` | REVIEW_REFACTOR-1, REVIEW_REFACTOR-2 |
| `services/core/internal/roadmapsyncpipeline/service_test.go` | REVIEW_REFACTOR-1, REVIEW_REFACTOR-2 |

## 최종 검증

`cd services/core && go test -count=1 ./internal/roadmapsyncpipeline`

`cd services/core && go test -count=1 ./internal/gitosync ./internal/roadmapsync ./internal/storage ./internal/roadmapsyncpipeline`

`cd services/core && go test -count=1 ./...`

`git diff --check`

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
