feat: milestone work item creation sync 구현 및 agent-task 구조 추가
This commit is contained in:
parent
c71ae81882
commit
26f90bae08
10 changed files with 975 additions and 0 deletions
|
|
@ -0,0 +1,122 @@
|
||||||
|
<!-- task=m-milestone-work-item-creation-sync/12+11_authoring_develop_gate plan=0 tag=API -->
|
||||||
|
|
||||||
|
# Code Review Reference - API
|
||||||
|
|
||||||
|
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||||
|
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||||
|
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||||
|
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||||
|
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
|
||||||
|
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
|
||||||
|
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
date=2026-06-15
|
||||||
|
task=m-milestone-work-item-creation-sync/12+11_authoring_develop_gate, plan=0, tag=API
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- Task ids:
|
||||||
|
- `authoring-develop-gate`: IOP authoring 응답만으로 task 완료를 단정하지 않고, `develop` Milestone push와 provider identity match가 확인될 때 authoring 성공 또는 sync-ready로 판정된다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 이 파일을 읽는 리뷰 에이전트에게
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||||
|
|
||||||
|
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||||
|
|
||||||
|
1. 판정을 append한다.
|
||||||
|
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다.
|
||||||
|
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-milestone-work-item-creation-sync/12+11_authoring_develop_gate/`로 이동한다.
|
||||||
|
4. PASS이고 task group이 `m-milestone-work-item-creation-sync`이면 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 항목별 완료 여부
|
||||||
|
|
||||||
|
| 항목 | 완료 여부 |
|
||||||
|
|------|---------|
|
||||||
|
| [API-1] Authoring model response must not complete the task | [ ] |
|
||||||
|
| [API-2] Creation sync projection finalizes the originating task | [ ] |
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] Plane-origin authoring task가 model response-only 상태에서 `completed`로 전이되지 않고 `running` + `authoring_run_state=in_progress` + wait reason을 유지하도록 scheduler/workflow 경계를 수정한다.
|
||||||
|
- [ ] `RoadmapCreationSyncWorker`가 `SyncActionProjected` 또는 already-complete outcome에서 원래 Plane external ref task를 완료할 수 있는 finalizer seam을 추가한다.
|
||||||
|
- [ ] model bridge error, push failure, dirty workspace, conflict 같은 failure category는 task failure metadata로 남기고 Plane `Todo` projection으로 넘어가지 않도록 테스트한다.
|
||||||
|
- [ ] `cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow ./internal/authoring ./internal/roadmapsyncpipeline ./internal/gitosync`를 통과시킨다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
## 코드리뷰 전용 체크리스트
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||||
|
|
||||||
|
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||||
|
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||||
|
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
|
||||||
|
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
|
||||||
|
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||||
|
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||||
|
- [ ] PASS이면 active task 디렉터리를 archive로 이동한다.
|
||||||
|
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||||
|
|
||||||
|
## 계획 대비 변경 사항
|
||||||
|
|
||||||
|
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||||
|
|
||||||
|
## 주요 설계 결정
|
||||||
|
|
||||||
|
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청
|
||||||
|
|
||||||
|
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 사유 유형: 없음
|
||||||
|
- 결정 필요: 없음
|
||||||
|
- 차단 근거: 없음
|
||||||
|
- 실행한 검증/명령: 없음
|
||||||
|
- 자동 후속 불가 이유: 없음
|
||||||
|
- 재개 조건: 없음
|
||||||
|
|
||||||
|
## 리뷰어를 위한 체크포인트
|
||||||
|
|
||||||
|
- model response-only authoring task가 `completed`로 전이되지 않는지 확인한다.
|
||||||
|
- projection terminal outcome만 원래 external-ref task를 완료하는지 확인한다.
|
||||||
|
- not_ready/conflict/self-mutation outcome에서 provider side effect와 task completion이 섞이지 않는지 확인한다.
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||||
|
|
||||||
|
### API-1 중간 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API-2 중간 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow ./internal/roadmapsyncpipeline
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 최종 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow ./internal/authoring ./internal/roadmapsyncpipeline ./internal/gitosync
|
||||||
|
(output)
|
||||||
|
$ cd services/core && go test ./...
|
||||||
|
(output)
|
||||||
|
$ cd services/core && go vet ./...
|
||||||
|
(output)
|
||||||
|
$ git diff --check
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
<!-- task=m-milestone-work-item-creation-sync/12+11_authoring_develop_gate plan=0 tag=API -->
|
||||||
|
|
||||||
|
# Plan - API
|
||||||
|
|
||||||
|
## 이 파일을 읽는 구현 에이전트에게
|
||||||
|
|
||||||
|
구현의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채우는 것이다. 검증을 실행하고, active 파일을 유지한 채 리뷰 준비 상태로 보고한다. 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 진행할 수 없을 때는 review stub의 `사용자 리뷰 요청`에 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
|
||||||
|
`live-cycle-gate`의 `authoring-develop-gate`는 IOP authoring 응답만으로 task를 완료하지 않고, `develop` Milestone push와 provider identity match가 확인될 때만 성공 또는 sync-ready로 판정해야 한다. 현재 scheduler는 Plane-origin authoring model 응답을 받은 뒤 `CompleteTask`를 호출할 수 있어 gate가 runtime status에 반영되지 않는다. 이 작업은 authoring task 완료를 develop/projection 결과로 미루는 최소 runtime 경계를 만든다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청 흐름
|
||||||
|
|
||||||
|
구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- Task ids:
|
||||||
|
- `authoring-develop-gate`: IOP authoring 응답만으로 task 완료를 단정하지 않고, `develop` Milestone push와 provider identity match가 확인될 때 authoring 성공 또는 sync-ready로 판정된다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 분석 결과
|
||||||
|
|
||||||
|
### 읽은 파일
|
||||||
|
|
||||||
|
- `agent-ops/rules/project/rules.md`
|
||||||
|
- `agent-ops/rules/private/rules.md`
|
||||||
|
- `agent-ops/rules/common/rules-roadmap.md`
|
||||||
|
- `agent-ops/rules/project/domain/core/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/workspace-ops/rules.md`
|
||||||
|
- `agent-ops/skills/common/router.md`
|
||||||
|
- `agent-ops/skills/common/plan/SKILL.md`
|
||||||
|
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
|
||||||
|
- `agent-test/local/rules.md`
|
||||||
|
- `agent-test/local/core-smoke.md`
|
||||||
|
- `agent-test/local/workspace-ops-smoke.md`
|
||||||
|
- `agent-test/plane-dev.md`
|
||||||
|
- `agent-roadmap/current.md`
|
||||||
|
- `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
|
||||||
|
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- `agent-task/archive/2026/06/m-milestone-work-item-creation-sync/11_creation_sync_orchestrator/complete.log`
|
||||||
|
- `services/core/internal/scheduler/jobs.go`
|
||||||
|
- `services/core/internal/scheduler/jobs_test.go`
|
||||||
|
- `services/core/internal/scheduler/river.go`
|
||||||
|
- `services/core/internal/scheduler/roadmap_sync_jobs.go`
|
||||||
|
- `services/core/internal/workflow/service.go`
|
||||||
|
- `services/core/internal/workflow/lifecycle.go`
|
||||||
|
- `services/core/internal/workflow/model.go`
|
||||||
|
- `services/core/internal/authoring/request.go`
|
||||||
|
- `services/core/internal/authoring/result.go`
|
||||||
|
- `services/core/internal/authoring/result_test.go`
|
||||||
|
- `services/core/internal/roadmapsyncpipeline/service.go`
|
||||||
|
- `services/core/internal/roadmapsyncpipeline/service_test.go`
|
||||||
|
- `services/core/internal/gitosync/bridge.go`
|
||||||
|
- `services/core/internal/gitosync/bridge_test.go`
|
||||||
|
- `services/core/internal/gitosync/scanner.go`
|
||||||
|
- `services/core/internal/gitosync/scanner_test.go`
|
||||||
|
- `services/core/cmd/server/main.go`
|
||||||
|
|
||||||
|
### 테스트 환경 규칙
|
||||||
|
|
||||||
|
- `test_env=local`.
|
||||||
|
- `agent-test/local/rules.md` present/read.
|
||||||
|
- Matched profiles: `agent-test/local/core-smoke.md`, `agent-test/local/workspace-ops-smoke.md`.
|
||||||
|
- Core 변경 필수 검증: `cd services/core && go test ./...`; 보조 검증: `cd services/core && go vet ./...`.
|
||||||
|
- roadmap/agent-task 문서 변경 검증: `git diff --check`.
|
||||||
|
- `<확인 필요>` 값은 core-smoke 기준 출력 예시에만 있으며, 이번 plan 명령에는 사용하지 않는다.
|
||||||
|
|
||||||
|
### 테스트 커버리지 공백
|
||||||
|
|
||||||
|
- Existing tests cover authoring request routing and `DecideAuthoringResult`, but not scheduler task completion being delayed until develop/projection confirmation.
|
||||||
|
- Existing `RoadmapCreationSyncWorker` tests cover SyncCreation delegation, but not completing the original external-ref task after projection.
|
||||||
|
|
||||||
|
### 심볼 참조
|
||||||
|
|
||||||
|
- Removed/renamed symbols: none.
|
||||||
|
- New call sites must be found with `rg --sort path "CompleteTaskByExternalRef|AuthoringCompletion|MetadataKeyAuthoringRunState|RoadmapCreationSyncWorker" services/core/internal services/core/cmd`.
|
||||||
|
|
||||||
|
### 분할 판단
|
||||||
|
|
||||||
|
- Split policy evaluated before writing. Shared task group: `m-milestone-work-item-creation-sync`.
|
||||||
|
- `12+11_authoring_develop_gate` depends on archived predecessor `agent-task/archive/2026/06/m-milestone-work-item-creation-sync/11_creation_sync_orchestrator/complete.log`.
|
||||||
|
- `13+12_slot_terminal_state` depends on this task because slot release policy must consume the final authoring/projection outcome.
|
||||||
|
- `14+12,13_live_cycle_smoke` depends on this task and slot terminal state because live evidence must observe the final runtime behavior.
|
||||||
|
|
||||||
|
### 범위 결정 근거
|
||||||
|
|
||||||
|
- Do not implement Plane child ticket creation, `Todo -> In Progress`, Gito proto-socket producer changes, or UI.
|
||||||
|
- Do not change generic manual/A2A task completion semantics.
|
||||||
|
- Do not introduce public HTTP routes for `RoadmapCreationSync`; use existing internal scheduler/worker seams.
|
||||||
|
|
||||||
|
### 빌드 등급
|
||||||
|
|
||||||
|
- `cloud-G07`: scheduler, workflow lifecycle, roadmap sync worker, async completion, and external-ref task finalization cross multiple runtime boundaries.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] Plane-origin authoring task가 model response-only 상태에서 `completed`로 전이되지 않고 `running` + `authoring_run_state=in_progress` + wait reason을 유지하도록 scheduler/workflow 경계를 수정한다.
|
||||||
|
- [ ] `RoadmapCreationSyncWorker`가 `SyncActionProjected` 또는 already-complete outcome에서 원래 Plane external ref task를 완료할 수 있는 finalizer seam을 추가한다.
|
||||||
|
- [ ] model bridge error, push failure, dirty workspace, conflict 같은 failure category는 task failure metadata로 남기고 Plane `Todo` projection으로 넘어가지 않도록 테스트한다.
|
||||||
|
- [ ] `cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow ./internal/authoring ./internal/roadmapsyncpipeline ./internal/gitosync`를 통과시킨다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
### [API-1] Authoring model response must not complete the task
|
||||||
|
|
||||||
|
문제: [services/core/internal/scheduler/jobs.go](/config/workspace/nomadcode/services/core/internal/scheduler/jobs.go:127)에서 Plane-origin authoring branch는 model 응답을 result로 반환하고, caller가 이후 `CompleteTask`를 호출한다.
|
||||||
|
|
||||||
|
해결 방법:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/core/internal/scheduler/jobs.go:140
|
||||||
|
result := map[string]any{
|
||||||
|
"message": generated.Text,
|
||||||
|
"mode": "authoring_run",
|
||||||
|
"model": generated.Model,
|
||||||
|
"response_id": generated.ID,
|
||||||
|
"authoring_run_state": "in_progress",
|
||||||
|
"authoring_run_updated_at": time.Now().UTC().Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
return raw, "authoring run completed", nil
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Shape only; choose local names that fit the final code.
|
||||||
|
decision := authoring.DecideAuthoringResult(authoring.DecisionInput{BridgeSuccess: true})
|
||||||
|
if decision.State == "in_progress" {
|
||||||
|
_, err := w.Lifecycle.MergeTaskMetadata(ctx, task.ID, map[string]any{
|
||||||
|
workflow.MetadataKeyAuthoringRunState: "in_progress",
|
||||||
|
workflow.MetadataKeyStatusReason: decision.Reason,
|
||||||
|
workflow.MetadataKeyWaitType: "develop_match",
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
수정 파일 및 체크리스트:
|
||||||
|
|
||||||
|
- [ ] `services/core/internal/scheduler/jobs.go`: authoring branch를 terminal/non-terminal outcome으로 분리한다.
|
||||||
|
- [ ] `services/core/internal/scheduler/jobs_test.go`: model response-only path가 `CompleteTask`를 호출하지 않는 regression test를 추가한다.
|
||||||
|
- [ ] `services/core/internal/workflow/lifecycle.go`: 필요한 경우 running task metadata merge가 status를 유지하는지 test와 맞춘다.
|
||||||
|
|
||||||
|
테스트 작성: `TestWorkKeepsAuthoringTaskRunningUntilDevelopMatch` 추가. model response-only 입력에서 `completed` call count가 0이고 metadata에 `authoring_run_state=in_progress`, `wait_type=develop_match`가 남는지 확인한다.
|
||||||
|
|
||||||
|
중간 검증: `cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow`가 PASS.
|
||||||
|
|
||||||
|
### [API-2] Creation sync projection finalizes the originating task
|
||||||
|
|
||||||
|
문제: [services/core/internal/scheduler/roadmap_sync_jobs.go](/config/workspace/nomadcode/services/core/internal/scheduler/roadmap_sync_jobs.go:61)는 projection 결과를 로그로만 남기며, 원래 Plane external ref task의 완료 상태를 갱신하지 않는다.
|
||||||
|
|
||||||
|
해결 방법:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/core/internal/scheduler/roadmap_sync_jobs.go:61
|
||||||
|
res, err := w.Sync.SyncCreation(ctx, roadmapsyncpipeline.SyncCreationInput{...})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Shape only; keep the seam narrow and testable.
|
||||||
|
if isProjectionTerminal(res.Action) && w.TaskFinalizer != nil {
|
||||||
|
if err := w.TaskFinalizer.CompleteByExternalRef(ctx, string(args.Ref.Provider), args.Ref.ID, result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
수정 파일 및 체크리스트:
|
||||||
|
|
||||||
|
- [ ] `services/core/internal/scheduler/roadmap_sync_jobs.go`: optional finalizer interface를 추가하고 projected/complete action에서 호출한다.
|
||||||
|
- [ ] `services/core/internal/scheduler/river.go`: worker construction에 finalizer를 주입한다.
|
||||||
|
- [ ] `services/core/cmd/server/main.go`: workflow/lifecycle 기반 finalizer wiring을 추가한다.
|
||||||
|
- [ ] `services/core/internal/workflow/service.go`: `CompleteTaskByExternalRef` 또는 동등한 좁은 API를 추가한다.
|
||||||
|
- [ ] `services/core/internal/scheduler/river_test.go`: finalizer wiring regression을 추가한다.
|
||||||
|
|
||||||
|
테스트 작성: `TestRoadmapCreationSyncWorkerFinalizesExternalTaskOnProjected`와 `TestRoadmapCreationSyncWorkerDoesNotFinalizeNotReady` 추가.
|
||||||
|
|
||||||
|
중간 검증: `cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow ./internal/roadmapsyncpipeline`.
|
||||||
|
|
||||||
|
## 의존 관계 및 구현 순서
|
||||||
|
|
||||||
|
1. `11_creation_sync_orchestrator`는 archived `complete.log`로 충족됨.
|
||||||
|
2. 먼저 scheduler의 non-terminal authoring outcome을 만든다.
|
||||||
|
3. 그 다음 creation sync worker finalizer를 붙인다.
|
||||||
|
4. 마지막으로 server/river wiring과 regression test를 닫는다.
|
||||||
|
|
||||||
|
## 수정 파일 요약
|
||||||
|
|
||||||
|
| 파일 | 항목 |
|
||||||
|
|------|------|
|
||||||
|
| `services/core/internal/scheduler/jobs.go` | API-1 |
|
||||||
|
| `services/core/internal/scheduler/jobs_test.go` | API-1 |
|
||||||
|
| `services/core/internal/workflow/service.go` | API-2 |
|
||||||
|
| `services/core/internal/workflow/lifecycle.go` | API-1, API-2 |
|
||||||
|
| `services/core/internal/scheduler/roadmap_sync_jobs.go` | API-2 |
|
||||||
|
| `services/core/internal/scheduler/river.go` | API-2 |
|
||||||
|
| `services/core/internal/scheduler/river_test.go` | API-2 |
|
||||||
|
| `services/core/cmd/server/main.go` | API-2 |
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
|
||||||
|
- `cd services/core && go test -count=1 ./internal/scheduler ./internal/workflow ./internal/authoring ./internal/roadmapsyncpipeline ./internal/gitosync`
|
||||||
|
- `cd services/core && go test ./...`
|
||||||
|
- `cd services/core && go vet ./...`
|
||||||
|
- `git diff --check`
|
||||||
|
|
||||||
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
<!-- task=m-milestone-work-item-creation-sync/13+12_slot_terminal_state plan=0 tag=API -->
|
||||||
|
|
||||||
|
# Code Review Reference - API
|
||||||
|
|
||||||
|
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||||
|
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||||
|
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||||
|
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||||
|
> Do not ask the user directly; use `사용자 리뷰 요청` for blockers.
|
||||||
|
> Finalization is review-agent-only.
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
date=2026-06-15
|
||||||
|
task=m-milestone-work-item-creation-sync/13+12_slot_terminal_state, plan=0, tag=API
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- Task ids:
|
||||||
|
- `slot-terminal-state`: authoring 또는 projection이 terminal outcome에 도달하면 reserved workspace slot이 정책에 따라 `available`, `dirty`, `error` 중 하나로 전환된다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 이 파일을 읽는 리뷰 에이전트에게
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||||
|
|
||||||
|
각 항목의 구현을 실제 소스 파일과 대조하고 검증 출력을 확인하세요.
|
||||||
|
|
||||||
|
## 구현 항목별 완료 여부
|
||||||
|
|
||||||
|
| 항목 | 완료 여부 |
|
||||||
|
|------|---------|
|
||||||
|
| [API-1] Add slot terminal policy helper | [ ] |
|
||||||
|
| [API-2] Apply slot release on terminal paths | [ ] |
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] checkout metadata에서 `slot_id`를 읽는 helper를 추가하고 missing slot id는 slot update를 skip하되 task completion/failure를 막지 않도록 기록한다.
|
||||||
|
- [ ] success projection은 reserved slot을 `available`로, dirty/conflict/push failure는 `dirty` 또는 `error`로 전환하는 정책 helper를 추가한다.
|
||||||
|
- [ ] TaskWorker failure path와 RoadmapCreationSync finalizer path가 같은 slot release policy를 사용하도록 연결한다.
|
||||||
|
- [ ] `cd services/core && go test -count=1 ./internal/projectsync ./internal/storage ./internal/scheduler ./internal/workflow`를 통과시킨다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
## 코드리뷰 전용 체크리스트
|
||||||
|
|
||||||
|
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||||
|
- [ ] active review/plan 파일을 `.log`로 아카이브한다.
|
||||||
|
- [ ] PASS이면 `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다.
|
||||||
|
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
|
||||||
|
|
||||||
|
## 계획 대비 변경 사항
|
||||||
|
|
||||||
|
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||||
|
|
||||||
|
## 주요 설계 결정
|
||||||
|
|
||||||
|
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청
|
||||||
|
|
||||||
|
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 사유 유형: 없음
|
||||||
|
- 결정 필요: 없음
|
||||||
|
- 차단 근거: 없음
|
||||||
|
- 실행한 검증/명령: 없음
|
||||||
|
- 자동 후속 불가 이유: 없음
|
||||||
|
- 재개 조건: 없음
|
||||||
|
|
||||||
|
## 리뷰어를 위한 체크포인트
|
||||||
|
|
||||||
|
- success projection이 slot을 `available`로 되돌리는지 확인한다.
|
||||||
|
- dirty/conflict/push failure가 원인에 맞는 slot terminal state를 남기는지 확인한다.
|
||||||
|
- missing slot metadata가 task finalization 자체를 깨지 않는지 확인한다.
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
### API-1 중간 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./internal/authoring ./internal/projectsync
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API-2 중간 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./internal/scheduler ./internal/storage
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 최종 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./internal/projectsync ./internal/storage ./internal/authoring ./internal/scheduler ./internal/workflow
|
||||||
|
(output)
|
||||||
|
$ cd services/core && go test ./...
|
||||||
|
(output)
|
||||||
|
$ cd services/core && go vet ./...
|
||||||
|
(output)
|
||||||
|
$ git diff --check
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
<!-- task=m-milestone-work-item-creation-sync/13+12_slot_terminal_state plan=0 tag=API -->
|
||||||
|
|
||||||
|
# Plan - API
|
||||||
|
|
||||||
|
## 이 파일을 읽는 구현 에이전트에게
|
||||||
|
|
||||||
|
구현의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채우는 것이다. 사용자 결정이나 외부 secret이 필요하면 직접 묻지 말고 review stub의 `사용자 리뷰 요청`에 기록한다. finalization은 code-review 전용이다.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
|
||||||
|
`live-cycle-gate`의 `slot-terminal-state`는 authoring/projection terminal outcome 이후 reserved slot이 다음 smoke를 막지 않도록 정책 상태로 회수되는지 확인한다. 현재 저장소에는 `UpdateWorkspaceSlotState`가 있지만 scheduler/projection terminal outcome과 연결되어 있지 않다. 이 작업은 12번 task의 finalizer 결과를 받아 slot을 `available`, `dirty`, `error` 중 하나로 전환한다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청 흐름
|
||||||
|
|
||||||
|
구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며 code-review가 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- Task ids:
|
||||||
|
- `slot-terminal-state`: authoring 또는 projection이 terminal outcome에 도달하면 reserved workspace slot이 정책에 따라 `available`, `dirty`, `error` 중 하나로 전환된다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 분석 결과
|
||||||
|
|
||||||
|
### 읽은 파일
|
||||||
|
|
||||||
|
- `agent-test/local/rules.md`
|
||||||
|
- `agent-test/local/core-smoke.md`
|
||||||
|
- `agent-test/local/workspace-ops-smoke.md`
|
||||||
|
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- `services/core/internal/projectsync/config.go`
|
||||||
|
- `services/core/internal/projectsync/checkout.go`
|
||||||
|
- `services/core/internal/storage/workspace_slots.go`
|
||||||
|
- `services/core/internal/storage/workspace_slots_test.go`
|
||||||
|
- `services/core/queries/workspace_slots.sql`
|
||||||
|
- `services/core/migrations/00005_create_workspace_slots.sql`
|
||||||
|
- `services/core/internal/workitempipeline/service.go`
|
||||||
|
- `services/core/internal/scheduler/jobs.go`
|
||||||
|
- `services/core/internal/scheduler/roadmap_sync_jobs.go`
|
||||||
|
- `services/core/internal/workflow/lifecycle.go`
|
||||||
|
|
||||||
|
### 테스트 환경 규칙
|
||||||
|
|
||||||
|
- `test_env=local`, `agent-test/local/rules.md` present/read.
|
||||||
|
- Matched profiles: `core-smoke`, `workspace-ops-smoke`.
|
||||||
|
- Required commands: `cd services/core && go test ./...`, `git diff --check`; focused package tests are allowed before final verification.
|
||||||
|
|
||||||
|
### 테스트 커버리지 공백
|
||||||
|
|
||||||
|
- Storage tests verify state update query shape.
|
||||||
|
- No existing test verifies terminal authoring/projection outcome chooses and persists a slot state.
|
||||||
|
|
||||||
|
### 심볼 참조
|
||||||
|
|
||||||
|
- Removed/renamed symbols: none.
|
||||||
|
- New references must be checked with `rg --sort path "UpdateWorkspaceSlotState|SlotStateAvailable|SlotStateDirty|SlotStateError|checkout.slot_id" services/core/internal`.
|
||||||
|
|
||||||
|
### 분할 판단
|
||||||
|
|
||||||
|
- Split policy evaluated. This task depends on active sibling `12+11_authoring_develop_gate`; predecessor 12 is currently missing `complete.log`, so implementation must wait until task 12 PASS or be run by runtime after predecessor completion.
|
||||||
|
- Slot terminal state is separate from authoring gate because it touches storage/projectsync state policy and failure categorization.
|
||||||
|
|
||||||
|
### 범위 결정 근거
|
||||||
|
|
||||||
|
- Do not add new slot states beyond existing DB check constraint.
|
||||||
|
- Do not reset slots on generic task completion.
|
||||||
|
- Do not perform filesystem cleanup; only persist policy state.
|
||||||
|
|
||||||
|
### 빌드 등급
|
||||||
|
|
||||||
|
- `cloud-G06`: cross-boundary but bounded to slot state policy, storage API, scheduler/projection finalization hooks.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] checkout metadata에서 `slot_id`를 읽는 helper를 추가하고 missing slot id는 slot update를 skip하되 task completion/failure를 막지 않도록 기록한다.
|
||||||
|
- [ ] success projection은 reserved slot을 `available`로, dirty/conflict/push failure는 `dirty` 또는 `error`로 전환하는 정책 helper를 추가한다.
|
||||||
|
- [ ] TaskWorker failure path와 RoadmapCreationSync finalizer path가 같은 slot release policy를 사용하도록 연결한다.
|
||||||
|
- [ ] `cd services/core && go test -count=1 ./internal/projectsync ./internal/storage ./internal/scheduler ./internal/workflow`를 통과시킨다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
### [API-1] Add slot terminal policy helper
|
||||||
|
|
||||||
|
문제: [services/core/internal/storage/workspace_slots.go](/config/workspace/nomadcode/services/core/internal/storage/workspace_slots.go:45)에 state update API는 있으나 terminal outcome 정책이 없다.
|
||||||
|
|
||||||
|
해결 방법:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/core/internal/storage/workspace_slots.go:45
|
||||||
|
func (s *Store) UpdateWorkspaceSlotState(ctx context.Context, args db.UpdateWorkspaceSlotStateParams) (db.WorkspaceSlot, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Shape only.
|
||||||
|
func SlotStateForAuthoringOutcome(decision authoring.Decision) projectsync.SlotState {
|
||||||
|
switch decision.FailureCategory {
|
||||||
|
case "dirty_workspace", "conflict", "push_failed":
|
||||||
|
return projectsync.SlotStateDirty
|
||||||
|
case "":
|
||||||
|
return projectsync.SlotStateAvailable
|
||||||
|
default:
|
||||||
|
return projectsync.SlotStateError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
수정 파일 및 체크리스트:
|
||||||
|
|
||||||
|
- [ ] `services/core/internal/authoring/result.go` 또는 새 narrow helper 위치에 slot outcome mapping을 둔다.
|
||||||
|
- [ ] `services/core/internal/authoring/result_test.go`: succeeded/dirty/conflict/push_failed/bridge_failed mapping을 검증한다.
|
||||||
|
|
||||||
|
테스트 작성: table test 추가.
|
||||||
|
|
||||||
|
중간 검증: `cd services/core && go test -count=1 ./internal/authoring ./internal/projectsync`.
|
||||||
|
|
||||||
|
### [API-2] Apply slot release on terminal paths
|
||||||
|
|
||||||
|
문제: [services/core/internal/scheduler/jobs.go](/config/workspace/nomadcode/services/core/internal/scheduler/jobs.go:385) failure metadata는 authoring state만 기록하고, slot state는 바꾸지 않는다.
|
||||||
|
|
||||||
|
해결 방법:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/core/internal/scheduler/jobs.go:403
|
||||||
|
failInput.ExtraMetadata = map[string]any{
|
||||||
|
workflow.MetadataKeyAuthoringRunState: "failed",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Shape only.
|
||||||
|
if updater := w.SlotFinalizer; updater != nil {
|
||||||
|
_ = updater.UpdateWorkspaceSlotState(ctx, slotID, targetState)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
수정 파일 및 체크리스트:
|
||||||
|
|
||||||
|
- [ ] `services/core/internal/scheduler/jobs.go`: authoring failure path에 optional slot finalizer를 연결한다.
|
||||||
|
- [ ] `services/core/internal/scheduler/roadmap_sync_jobs.go`: projection success finalizer에 available transition을 연결한다.
|
||||||
|
- [ ] `services/core/cmd/server/main.go`: storage store를 finalizer로 wiring한다.
|
||||||
|
- [ ] `services/core/internal/scheduler/jobs_test.go`: failure category별 slot update 호출을 검증한다.
|
||||||
|
- [ ] `services/core/internal/scheduler/river_test.go`: worker wiring을 확인한다.
|
||||||
|
|
||||||
|
테스트 작성: failure path와 projected success path 모두 regression test 작성.
|
||||||
|
|
||||||
|
중간 검증: `cd services/core && go test -count=1 ./internal/scheduler ./internal/storage`.
|
||||||
|
|
||||||
|
## 의존 관계 및 구현 순서
|
||||||
|
|
||||||
|
이 subtask는 `12+11_authoring_develop_gate`의 `complete.log`가 필요하다. runtime dependency는 디렉터리명 `13+12_slot_terminal_state`가 source of truth다.
|
||||||
|
|
||||||
|
## 수정 파일 요약
|
||||||
|
|
||||||
|
| 파일 | 항목 |
|
||||||
|
|------|------|
|
||||||
|
| `services/core/internal/authoring/result.go` | API-1 |
|
||||||
|
| `services/core/internal/authoring/result_test.go` | API-1 |
|
||||||
|
| `services/core/internal/scheduler/jobs.go` | API-2 |
|
||||||
|
| `services/core/internal/scheduler/jobs_test.go` | API-2 |
|
||||||
|
| `services/core/internal/scheduler/roadmap_sync_jobs.go` | API-2 |
|
||||||
|
| `services/core/internal/scheduler/river.go` | API-2 |
|
||||||
|
| `services/core/internal/scheduler/river_test.go` | API-2 |
|
||||||
|
| `services/core/cmd/server/main.go` | API-2 |
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
|
||||||
|
- `cd services/core && go test -count=1 ./internal/projectsync ./internal/storage ./internal/authoring ./internal/scheduler ./internal/workflow`
|
||||||
|
- `cd services/core && go test ./...`
|
||||||
|
- `cd services/core && go vet ./...`
|
||||||
|
- `git diff --check`
|
||||||
|
|
||||||
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<!-- task=m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke plan=0 tag=TEST -->
|
||||||
|
|
||||||
|
# Code Review Reference - TEST
|
||||||
|
|
||||||
|
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||||
|
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||||
|
> Use `사용자 리뷰 요청` for external secret/runtime blockers; do not ask the user directly.
|
||||||
|
> Finalization is review-agent-only.
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
date=2026-06-15
|
||||||
|
task=m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke, plan=0, tag=TEST
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- Task ids:
|
||||||
|
- `live-webhook-task`: 원격 dev Plane에서 `Backlog + AGENT assignee` 상위 티켓을 만들거나 갱신하면 webhook만으로 Core task가 생성되고 checkout metadata가 남는다.
|
||||||
|
- `webhook-auto-enqueue`: webhook-dispatched creation task가 별도 수동 API 호출 없이 authoring queue로 이어진다.
|
||||||
|
- `live-creation-projection`: matched develop scan/job 입력이 `roadmapsyncpipeline.Service`와 `RoadmapCreationSyncWorker`를 통해 실제 Plane 원문 댓글 보존, 본문/제목 갱신, `Todo` 이동으로 수렴한다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 이 파일을 읽는 리뷰 에이전트에게
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||||
|
|
||||||
|
검증 출력이 secret-free redacted evidence인지 확인하세요.
|
||||||
|
|
||||||
|
## 구현 항목별 완료 여부
|
||||||
|
|
||||||
|
| 항목 | 완료 여부 |
|
||||||
|
|------|---------|
|
||||||
|
| [TEST-1] Build or document the smoke runner | [ ] |
|
||||||
|
| [TEST-2] Collect live or runtime-equivalent evidence | [ ] |
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] Decide whether live smoke can run against the reviewed commit; if not, write a runtime-equivalent smoke that states exactly which live evidence is unavailable and why.
|
||||||
|
- [ ] Capture redacted evidence for webhook 202, Core task external ref, checkout metadata, slot reservation, and queued/running authoring state without printing secrets.
|
||||||
|
- [ ] Capture redacted evidence for roadmap sync ledger steps `develop_matched`, `original_comment_preserved`, `plane_body_updated`, `plane_todo_moved` and Plane Todo/title state.
|
||||||
|
- [ ] Capture evidence that a second smoke can reserve a slot without manual DB reset after terminal outcome.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
## 코드리뷰 전용 체크리스트
|
||||||
|
|
||||||
|
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||||
|
- [ ] active review/plan 파일을 `.log`로 아카이브한다.
|
||||||
|
- [ ] PASS이면 `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다.
|
||||||
|
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
|
||||||
|
|
||||||
|
## 계획 대비 변경 사항
|
||||||
|
|
||||||
|
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||||
|
|
||||||
|
## 주요 설계 결정
|
||||||
|
|
||||||
|
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청
|
||||||
|
|
||||||
|
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 사유 유형: 없음
|
||||||
|
- 결정 필요: 없음
|
||||||
|
- 차단 근거: 없음
|
||||||
|
- 실행한 검증/명령: 없음
|
||||||
|
- 자동 후속 불가 이유: 없음
|
||||||
|
- 재개 조건: 없음
|
||||||
|
|
||||||
|
## 리뷰어를 위한 체크포인트
|
||||||
|
|
||||||
|
- smoke evidence가 reviewed commit 또는 명시된 runtime-equivalent 범위에 묶여 있는지 확인한다.
|
||||||
|
- secret/token/webhook signature 원문이 없는지 확인한다.
|
||||||
|
- `live-webhook-task`, `webhook-auto-enqueue`, `live-creation-projection`의 증거가 각각 분리되어 있는지 확인한다.
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
### TEST-1 중간 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./cmd/plane-smoke ./internal/http ./internal/scheduler
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### TEST-2 중간 검증
|
||||||
|
```text
|
||||||
|
$ <exact live/runtime-equivalent smoke command>
|
||||||
|
(redacted output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 최종 검증
|
||||||
|
```text
|
||||||
|
$ cd services/core && go test -count=1 ./cmd/plane-smoke ./internal/http ./internal/scheduler ./internal/roadmapsyncpipeline
|
||||||
|
(output)
|
||||||
|
$ cd services/core && go test ./...
|
||||||
|
(output)
|
||||||
|
$ git diff --check
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
<!-- task=m-milestone-work-item-creation-sync/14+12,13_live_cycle_smoke plan=0 tag=TEST -->
|
||||||
|
|
||||||
|
# Plan - TEST
|
||||||
|
|
||||||
|
## 이 파일을 읽는 구현 에이전트에게
|
||||||
|
|
||||||
|
구현의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채우는 것이다. 외부 secret, dev Plane 상태, 원격 runner 준비가 막히면 직접 사용자에게 묻지 말고 `사용자 리뷰 요청`에 시도한 명령과 redacted 근거를 남긴다.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
|
||||||
|
마일스톤 완료 리뷰는 unit/fake 기반 evidence만으로 Plane `Todo`까지 실제 수렴을 증명하기 어렵다고 판정했다. 12/13번 구현 이후에도 archive 전에는 webhook, auto enqueue, develop gate, Plane projection, slot 회수의 live 또는 runtime-equivalent smoke evidence가 필요하다. 이 작업은 secret redaction을 지키며 재현 가능한 smoke 절차와 증거 산출물을 만든다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청 흐름
|
||||||
|
|
||||||
|
구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며 code-review가 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
- Task ids:
|
||||||
|
- `live-webhook-task`: 원격 dev Plane에서 `Backlog + AGENT assignee` 상위 티켓을 만들거나 갱신하면 webhook만으로 Core task가 생성되고 checkout metadata가 남는다.
|
||||||
|
- `webhook-auto-enqueue`: webhook-dispatched creation task가 별도 수동 API 호출 없이 authoring queue로 이어진다.
|
||||||
|
- `live-creation-projection`: matched develop scan/job 입력이 `roadmapsyncpipeline.Service`와 `RoadmapCreationSyncWorker`를 통해 실제 Plane 원문 댓글 보존, 본문/제목 갱신, `Todo` 이동으로 수렴한다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 분석 결과
|
||||||
|
|
||||||
|
### 읽은 파일
|
||||||
|
|
||||||
|
- `agent-test/local/rules.md`
|
||||||
|
- `agent-test/local/core-smoke.md`
|
||||||
|
- `agent-test/local/workspace-ops-smoke.md`
|
||||||
|
- `agent-test/plane-dev.md`
|
||||||
|
- `services/core/README.md`
|
||||||
|
- `services/core/cmd/plane-smoke/main.go`
|
||||||
|
- `services/core/cmd/plane-smoke/main_test.go`
|
||||||
|
- `services/core/internal/http/plane_webhook.go`
|
||||||
|
- `services/core/internal/http/plane_webhook_test.go`
|
||||||
|
- `services/core/internal/workitempipeline/service.go`
|
||||||
|
- `services/core/internal/scheduler/jobs.go`
|
||||||
|
- `services/core/internal/scheduler/roadmap_sync_jobs.go`
|
||||||
|
- `services/core/internal/roadmapsyncpipeline/service.go`
|
||||||
|
- `services/core/internal/gitosync/bridge.go`
|
||||||
|
- `services/core/internal/gitosync/scanner.go`
|
||||||
|
- `services/core/cmd/server/main.go`
|
||||||
|
- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md`
|
||||||
|
|
||||||
|
### 테스트 환경 규칙
|
||||||
|
|
||||||
|
- `test_env=local`, `agent-test/local/rules.md` present/read.
|
||||||
|
- Matched profiles: `core-smoke`, `workspace-ops-smoke`.
|
||||||
|
- Plane dev fixture: `agent-test/plane-dev.md`; secrets must come from ignored `.env.plane.local` or SOPS/runtime env and must not be printed.
|
||||||
|
- Current remote runner is reachable, but observed remote checkout had unrelated dirty changes; smoke must either deploy/point at the reviewed commit or clearly record runtime-equivalent limits.
|
||||||
|
|
||||||
|
### 테스트 커버리지 공백
|
||||||
|
|
||||||
|
- Unit tests can prove dispatch and projection seams, but not dev Plane webhook delivery, actual Core logs, or Plane UI state.
|
||||||
|
- No existing smoke command verifies the whole Backlog+AGENT -> Core task -> queued/running -> develop scan -> Plane Todo -> reusable slot sequence.
|
||||||
|
|
||||||
|
### 심볼 참조
|
||||||
|
|
||||||
|
- Removed/renamed symbols: none.
|
||||||
|
- Smoke implementation should search `rg --sort path "plane-smoke|PLANE_WEBHOOK|RoadmapCreationSync|workspace_slots|roadmap_sync_steps" services/core agent-test docs`.
|
||||||
|
|
||||||
|
### 분할 판단
|
||||||
|
|
||||||
|
- Split policy evaluated. This task depends on active siblings `12+11_authoring_develop_gate` and `13+12_slot_terminal_state`; both predecessors are currently missing `complete.log`.
|
||||||
|
- Live smoke is isolated because it depends on external Plane/Core runner state and may require evidence recovery without changing product logic.
|
||||||
|
|
||||||
|
### 범위 결정 근거
|
||||||
|
|
||||||
|
- Do not store raw Plane token, webhook secret, API key, or private endpoint output in tracked files.
|
||||||
|
- Do not move the Milestone to archive; user approval is still required after evidence.
|
||||||
|
- Do not cover `Todo -> In Progress` or child task execution lifecycle.
|
||||||
|
|
||||||
|
### 빌드 등급
|
||||||
|
|
||||||
|
- `cloud-G07`: external live/runtime evidence, redaction, remote runner state, and multiple async systems are central to success.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] Decide whether live smoke can run against the reviewed commit; if not, write a runtime-equivalent smoke that states exactly which live evidence is unavailable and why.
|
||||||
|
- [ ] Capture redacted evidence for webhook 202, Core task external ref, checkout metadata, slot reservation, and queued/running authoring state without printing secrets.
|
||||||
|
- [ ] Capture redacted evidence for roadmap sync ledger steps `develop_matched`, `original_comment_preserved`, `plane_body_updated`, `plane_todo_moved` and Plane Todo/title state.
|
||||||
|
- [ ] Capture evidence that a second smoke can reserve a slot without manual DB reset after terminal outcome.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
### [TEST-1] Build or document the smoke runner
|
||||||
|
|
||||||
|
문제: [services/core/README.md](/config/workspace/nomadcode/services/core/README.md:135)의 `plane-smoke` covers lookup/comment/status projection, but not webhook-dispatched authoring and creation sync ledger.
|
||||||
|
|
||||||
|
해결 방법:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```text
|
||||||
|
services/core/bin/plane-smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Either extend existing smoke docs/runner or add a focused command.
|
||||||
|
PLANE_SMOKE_APPLY=1 <redacted env> ./bin/plane-cycle-smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
수정 파일 및 체크리스트:
|
||||||
|
|
||||||
|
- [ ] Prefer extending `services/core/cmd/plane-smoke` only if it can stay non-destructive by default.
|
||||||
|
- [ ] Otherwise add a focused smoke doc under `agent-test/` plus a shell-safe command sequence; keep secrets ignored.
|
||||||
|
- [ ] Ensure all output redacts token/secret and uses work item ids only when non-secret fixture ids are allowed.
|
||||||
|
|
||||||
|
테스트 작성: If adding Go smoke command parsing, add config/redaction unit tests under `services/core/cmd/...`.
|
||||||
|
|
||||||
|
중간 검증: `cd services/core && go test -count=1 ./cmd/plane-smoke ./internal/http ./internal/scheduler`.
|
||||||
|
|
||||||
|
### [TEST-2] Collect live or runtime-equivalent evidence
|
||||||
|
|
||||||
|
문제: [agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md](/config/workspace/nomadcode/agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-work-item-creation-sync.md:126)는 현재 live 확인 범위가 webhook 수신과 task 생성까지만이라고 기록한다.
|
||||||
|
|
||||||
|
해결 방법:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```text
|
||||||
|
webhook receiver answers 202 accepted; task creation only
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```text
|
||||||
|
webhook 202 -> task external ref + checkout metadata -> queued/running -> develop match -> roadmap_sync_steps all marked -> Plane Todo -> slot reusable
|
||||||
|
```
|
||||||
|
|
||||||
|
수정 파일 및 체크리스트:
|
||||||
|
|
||||||
|
- [ ] Use `agent-test/plane-dev.md` fixture ids and ignored secret files only.
|
||||||
|
- [ ] Record remote commit/branch, Core health/ready, and whether runtime matches reviewed commit.
|
||||||
|
- [ ] Save redacted evidence in an untracked temp path or active review output; do not write raw secret logs.
|
||||||
|
- [ ] If live Plane mutation cannot be safely run, record runtime-equivalent commands and exact missing live gate.
|
||||||
|
|
||||||
|
테스트 작성: No product unit test if this is evidence-only; if a smoke runner is added, cover redaction and config validation.
|
||||||
|
|
||||||
|
중간 검증: `git diff --check` plus the exact smoke command chosen by implementation.
|
||||||
|
|
||||||
|
## 의존 관계 및 구현 순서
|
||||||
|
|
||||||
|
This task waits for `12+11_authoring_develop_gate/complete.log` and `13+12_slot_terminal_state/complete.log` under the same task group. Do not run final live smoke before both predecessors PASS.
|
||||||
|
|
||||||
|
## 수정 파일 요약
|
||||||
|
|
||||||
|
| 파일 | 항목 |
|
||||||
|
|------|------|
|
||||||
|
| `services/core/cmd/plane-smoke/main.go` or new focused smoke command | TEST-1 |
|
||||||
|
| `services/core/cmd/plane-smoke/main_test.go` or new command tests | TEST-1 |
|
||||||
|
| `agent-test/plane-dev.md` or new smoke doc | TEST-1, TEST-2 |
|
||||||
|
| active `CODE_REVIEW-cloud-G07.md` evidence section | TEST-2 |
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
|
||||||
|
- `cd services/core && go test -count=1 ./cmd/plane-smoke ./internal/http ./internal/scheduler ./internal/roadmapsyncpipeline`
|
||||||
|
- `cd services/core && go test ./...`
|
||||||
|
- `git diff --check`
|
||||||
|
- Exact live/runtime-equivalent smoke command chosen by implementation, with redacted output pasted into `CODE_REVIEW-cloud-G07.md`.
|
||||||
|
|
||||||
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||||
|
|
@ -29,12 +29,17 @@ type WorkItemTaskCreator interface {
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
db *pgxpool.Pool
|
db *pgxpool.Pool
|
||||||
workflow *workflow.Service
|
workflow *workflow.Service
|
||||||
|
webhookEnqueuer webhookTaskEnqueuer
|
||||||
workItemProviders map[workitem.ProviderID]WorkItemTaskCreator
|
workItemProviders map[workitem.ProviderID]WorkItemTaskCreator
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
planeWebhookSecret string
|
planeWebhookSecret string
|
||||||
planeDispatch planeWebhookDispatch
|
planeDispatch planeWebhookDispatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type webhookTaskEnqueuer interface {
|
||||||
|
EnqueueTask(ctx context.Context, id string) (storage.Task, error)
|
||||||
|
}
|
||||||
|
|
||||||
// PlaneWebhookDispatchConfig carries the non-secret trigger gate identifiers used
|
// PlaneWebhookDispatchConfig carries the non-secret trigger gate identifiers used
|
||||||
// to turn a normalized Plane webhook event into a work item task creation. The
|
// to turn a normalized Plane webhook event into a work item task creation. The
|
||||||
// webhook secret stays separate (SetPlaneWebhookSecret); all values here are
|
// webhook secret stays separate (SetPlaneWebhookSecret); all values here are
|
||||||
|
|
@ -73,6 +78,7 @@ func NewHandler(pool *pgxpool.Pool, workflowService *workflow.Service, binder wo
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
db: pool,
|
db: pool,
|
||||||
workflow: workflowService,
|
workflow: workflowService,
|
||||||
|
webhookEnqueuer: workflowService,
|
||||||
workItemProviders: make(map[workitem.ProviderID]WorkItemTaskCreator),
|
workItemProviders: make(map[workitem.ProviderID]WorkItemTaskCreator),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,23 @@ func withPlaneCreator(creator WorkItemTaskCreator) handlerOpt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeWebhookEnqueuer struct {
|
||||||
|
task storage.Task
|
||||||
|
err error
|
||||||
|
enqueued []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeWebhookEnqueuer) EnqueueTask(_ context.Context, id string) (storage.Task, error) {
|
||||||
|
f.enqueued = append(f.enqueued, id)
|
||||||
|
return f.task, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func withWebhookEnqueuer(enqueuer webhookTaskEnqueuer) handlerOpt {
|
||||||
|
return func(h *Handler) {
|
||||||
|
h.webhookEnqueuer = enqueuer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newHandlerForTest(opts ...handlerOpt) *Handler {
|
func newHandlerForTest(opts ...handlerOpt) *Handler {
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
workItemProviders: make(map[workitem.ProviderID]WorkItemTaskCreator),
|
workItemProviders: make(map[workitem.ProviderID]WorkItemTaskCreator),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nomadcode/nomadcode-core/internal/storage"
|
"github.com/nomadcode/nomadcode-core/internal/storage"
|
||||||
|
"github.com/nomadcode/nomadcode-core/internal/workflow"
|
||||||
"github.com/nomadcode/nomadcode-core/internal/workitem"
|
"github.com/nomadcode/nomadcode-core/internal/workitem"
|
||||||
"github.com/nomadcode/nomadcode-core/internal/workitempipeline"
|
"github.com/nomadcode/nomadcode-core/internal/workitempipeline"
|
||||||
)
|
)
|
||||||
|
|
@ -199,9 +200,28 @@ func (h *Handler) dispatchPlaneWebhookWorkItem(ctx context.Context, event planeW
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return storage.Task{}, false, err
|
return storage.Task{}, false, err
|
||||||
}
|
}
|
||||||
|
if h.webhookEnqueuer != nil && planeWebhookShouldEnqueue(task.Status) {
|
||||||
|
queued, err := h.webhookEnqueuer.EnqueueTask(ctx, task.ID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, workflow.ErrTaskCannotBeEnqueued) {
|
||||||
|
return task, true, nil
|
||||||
|
}
|
||||||
|
return storage.Task{}, false, err
|
||||||
|
}
|
||||||
|
task = queued
|
||||||
|
}
|
||||||
return task, true, nil
|
return task, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func planeWebhookShouldEnqueue(status string) bool {
|
||||||
|
switch workflow.TaskStatus(strings.TrimSpace(status)) {
|
||||||
|
case workflow.StatusPending, workflow.StatusFailed:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) ReceivePlaneWebhook(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
func (h *Handler) ReceivePlaneWebhook(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
||||||
secret := strings.TrimSpace(h.planeWebhookSecret)
|
secret := strings.TrimSpace(h.planeWebhookSecret)
|
||||||
if secret == "" {
|
if secret == "" {
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,50 @@ func TestReceivePlaneWebhookDispatchesMatchingIssueEvent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReceivePlaneWebhookAutoEnqueuesPendingTask(t *testing.T) {
|
||||||
|
creator := &fakeWorkItemTaskCreator{task: storage.Task{ID: "task-1", Status: "pending"}}
|
||||||
|
enqueuer := &fakeWebhookEnqueuer{task: storage.Task{ID: "task-1", Status: "queued"}}
|
||||||
|
h := newHandlerForTest(withPlaneCreator(creator), withWebhookEnqueuer(enqueuer))
|
||||||
|
h.SetPlaneWebhookSecret("secret")
|
||||||
|
h.SetPlaneWebhookDispatchConfig(planeDispatchTestConfig())
|
||||||
|
|
||||||
|
body := `{"event":"issue","action":"updated","webhook_id":"wh-1","workspace_id":"ws-uuid","data":{"id":"item-1","project":"proj-1","workspace":"ws-uuid"},"activity":{"field":"assignees","actor":{"id":"actor-1"}}}`
|
||||||
|
req, rec := signedPlaneWebhookRequest(body)
|
||||||
|
|
||||||
|
h.ReceivePlaneWebhook(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusAccepted {
|
||||||
|
t.Fatalf("expected %d, got %d: %s", http.StatusAccepted, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
if len(enqueuer.enqueued) != 1 || enqueuer.enqueued[0] != "task-1" {
|
||||||
|
t.Fatalf("expected task-1 to be enqueued once, got %v", enqueuer.enqueued)
|
||||||
|
}
|
||||||
|
resp := decodeWebhookResponse(t, rec)
|
||||||
|
if resp["status"] != "accepted" || resp["task_id"] != "task-1" {
|
||||||
|
t.Fatalf("unexpected response body: %v", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceivePlaneWebhookDoesNotAutoEnqueueAlreadyQueuedTask(t *testing.T) {
|
||||||
|
creator := &fakeWorkItemTaskCreator{task: storage.Task{ID: "task-1", Status: "queued"}}
|
||||||
|
enqueuer := &fakeWebhookEnqueuer{task: storage.Task{ID: "task-1", Status: "queued"}}
|
||||||
|
h := newHandlerForTest(withPlaneCreator(creator), withWebhookEnqueuer(enqueuer))
|
||||||
|
h.SetPlaneWebhookSecret("secret")
|
||||||
|
h.SetPlaneWebhookDispatchConfig(planeDispatchTestConfig())
|
||||||
|
|
||||||
|
body := `{"event":"issue","action":"updated","webhook_id":"wh-1","workspace_id":"ws-uuid","data":{"id":"item-1","project":"proj-1","workspace":"ws-uuid"},"activity":{"field":"assignees","actor":{"id":"actor-1"}}}`
|
||||||
|
req, rec := signedPlaneWebhookRequest(body)
|
||||||
|
|
||||||
|
h.ReceivePlaneWebhook(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusAccepted {
|
||||||
|
t.Fatalf("expected %d, got %d: %s", http.StatusAccepted, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
if len(enqueuer.enqueued) != 0 {
|
||||||
|
t.Fatalf("expected no enqueue for queued duplicate, got %v", enqueuer.enqueued)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReceivePlaneWebhookIgnoresIrrelevantEventWithoutCreatorCall(t *testing.T) {
|
func TestReceivePlaneWebhookIgnoresIrrelevantEventWithoutCreatorCall(t *testing.T) {
|
||||||
creator := &fakeWorkItemTaskCreator{task: storage.Task{ID: "task-x"}}
|
creator := &fakeWorkItemTaskCreator{task: storage.Task{ID: "task-x"}}
|
||||||
h := newHandlerForTest(withPlaneCreator(creator))
|
h := newHandlerForTest(withPlaneCreator(creator))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue