diff --git a/agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md b/agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md index c50a1dc..8bb48b1 100644 --- a/agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md +++ b/agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md @@ -82,7 +82,7 @@ Gito HTTP webhook wakeup 이후 Core sync pipeline이 Plane Todo projection까 - 요청일: 없음 - 완료 근거: 없음 - 검토 항목: - - [ ] SDD gate가 해제되었다. + - [x] SDD gate가 해제되었다. - [ ] 모든 기능 Task와 검증이 충족되었다. - [ ] 실제 dev Plane-origin full-cycle smoke에서 Plane `Todo` projection까지 확인했다. - [ ] 사용자 완료 확인과 archive 승인을 받았다. @@ -109,11 +109,16 @@ Gito HTTP webhook wakeup 이후 Core sync pipeline이 Plane Todo projection까 - 얇은 authoring prompt로 생성된 Milestone에는 provider identity block이 빠질 수 있고, 현재 `gitosync` scanner는 identity 없는 Milestone을 projection 대상으로 삼지 않는다. - 첫 authoring 호출만으로는 `develop` push가 보장되지 않으며, push-only workspace agent 요청과 remote `develop` 재검증이 필요하다. - Gito webhook 202만으로는 Plane 동기화 완료가 아니며, `roadmap_sync_identities`, `roadmap_sync_steps`, Plane `Todo` 상태까지 확인해야 한다. +- 작업 현황 동기화(2026-06-20): + - SDD는 `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`에 작성되었고 `[승인됨]`, `SDD 잠금: 해제`, 사용자 리뷰 없음 상태다. + - 마일스톤 구현 잠금은 `해제` 상태이며, 활성 상태는 `[진행중]`이다. + - `thin-prompt` Task는 `services/core/internal/authoring` 테스트 근거로 완료 처리되었다. + - 현재 활성 `agent-task` plan/review/user-review/complete 파일은 없다. + - 다음 실구현은 남은 Task를 `agent-task/m-plane-origin-authoring-roundtrip-sync/` 아래 milestone 기반 plan으로 분해해 진행한다. - 핸즈오프(2026-06-20): - 현재 타겟은 이 마일스톤이다. 로컬 `agent-roadmap/current.md`의 활성 Milestone은 `Plane-Origin Authoring Roundtrip Sync` 하나로 정리되어 있다. - - 이 세션에서는 마일스톤 문서 작성과 Phase/ROADMAP 연결만 수행했다. 구현 plan, SDD 본문, 코드 변경 착수는 하지 않았다. - - 다음 세션은 AGENTS 규칙을 다시 읽은 뒤 이 마일스톤과 `agent-test/live-cycle-smoke.md`, `services/core/internal/authoring/`, `services/core/internal/adapters/openai/`, `services/core/internal/gitosync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`를 우선 확인한다. - - 구현 착수 전 SDD를 먼저 작성/해제해야 한다. 이유는 IOP 호출, git push, Gito webhook, Plane provider write, identity map, retry/timeout, live smoke가 한 경계에 묶여 있기 때문이다. + - SDD gate는 작성과 해제까지 완료되어 구현 plan 생성이 가능하다. + - 다음 세션은 AGENTS 규칙을 다시 읽은 뒤 이 마일스톤, SDD, `agent-test/live-cycle-smoke.md`, `services/core/internal/authoring/`, `services/core/internal/adapters/openai/`, `services/core/internal/gitosync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`를 우선 확인한다. - 중요한 방향: Plane 티켓 본문과 authoring prompt는 사용자 요청 원문 중심으로 얇게 유지한다. provider identity, project sync 설정, push-only 단계, Gito callback, Plane Todo projection은 사용자 프롬프트가 아니라 Core/runtime의 결정적 책임으로 설계한다. - 현재 알려진 회귀: IOP authoring은 milestone 파일을 만들 수 있지만 remote `develop` push가 별도 조치 없이는 보장되지 않고, 생성된 Milestone에 `## Provider identity` block이 없으면 `gitosync` scanner가 projection 대상으로 삼지 않는다. - 검증 기준: Gito webhook 202나 remote `develop` commit만으로 완료로 보지 않는다. `roadmap_sync_identities`, `roadmap_sync_steps`의 단계 기록과 Plane `Todo` 상태까지 확인해야 full-cycle 완료다. diff --git a/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log new file mode 100644 index 0000000..3f870a2 --- /dev/null +++ b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log @@ -0,0 +1,150 @@ + + +# Code Review Reference - AUTHORING_RUNTIME + +> **[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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked 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-20 +task=m-plane-origin-authoring-roundtrip-sync/01_iop_long_run, plan=0, tag=AUTHORING_RUNTIME + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `iop-long-run`: IOP Responses 호출이 10분 이상 걸릴 수 있는 작업을 실패로 조기 확정하지 않도록 timeout, retry, queue timeout, stale 판단 기준을 재정의한다. +- 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: + - `S02`: task=`iop-long-run`; evidence=`core config/scheduler test or smoke evidence for long-running timeout/retry/stale metadata behavior` +- Completion mode: spec-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-plane-origin-authoring-roundtrip-sync/01_iop_long_run/`로 이동한다. +4. PASS이고 task group이 `m-plane-origin-authoring-roundtrip-sync`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 하지 않는다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [AUTHORING_RUNTIME-1] Authoring long-run timeout foundation | [x] | + +## 구현 체크리스트 + +- [x] `iop-long-run` 기준으로 authoring 작업은 generic `WORKFLOW_TASK_TIMEOUT_SEC`로 조기 `failed` 처리되지 않고, `MODEL_TIMEOUT_SEC`/helper/docs는 10분 이상 실행 가능한 기본선으로 정리한다. 검증: focused Go tests에서 generic timeout은 유지되고 authoring은 관찰 가능한 in-progress/stale metadata를 남긴다. +- [x] `services/core/README.md`, `services/core/Makefile`, `services/core/bin/run`, `services/core/docker-compose.yml`의 timeout 설명/기본값이 code default와 충돌하지 않게 갱신한다. +- [x] `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...`와 `git diff --check`를 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/`를 `agent-task/archive/YYYY/MM/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/`로 이동한다. +- [ ] PASS이고 task group이 `m-plane-origin-authoring-roundtrip-sync`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-plane-origin-authoring-roundtrip-sync/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] WARN/FAIL이면 다음 active plan/review 파일 또는 `USER_REVIEW.md` 필요 여부를 판단한다. + +## 계획 대비 변경 사항 + +없음. 계획에 명시된 모든 파일과 접근 방식을 그대로 따랐다. + +## 주요 설계 결정 + +- **RunTimeout 면제 판별**: `authoring.TaskCheckoutMetadata(task)`로 checkout slot이 있는 Plane-origin 태스크를 식별한다. 이미 `Work` 내부에서 `in_progress` 메타데이터 기록 시 동일 함수를 쓰고 있어 경계가 일관된다. +- **MODEL_TIMEOUT_SEC 기본값 900**: plan에서 "최소 900초" 기준을 명시했고, IOP Responses long-run 호출이 10분 이상 걸릴 수 있어 15분(900초)을 기본선으로 설정했다. WORKFLOW_TASK_TIMEOUT_SEC(300)는 generic worker 전용으로 유지한다. +- **openai.defaultTimeoutSec 동기화**: config default와 client fallback이 따로 분기되는 것을 막기 위해 동일하게 900으로 맞췄다. `NewClient`는 `cfg.TimeoutSec <= 0`일 때만 fallback을 사용하므로 env override 동작에는 영향 없다. +- **회귀 테스트 방식**: `done` 채널을 사용해 모델 응답을 `RunTimeout`(1ms) 이후에 반환한다. authoring 태스크가 timeout 면제를 받지 못하면 `context.DeadlineExceeded`로 실패하므로 회귀를 정확히 감지한다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Generic task timeout behavior remains covered and unchanged. +- Plane-origin authoring does not get marked failed by generic `RunTimeout`. +- Timeout defaults/docs/helpers are internally consistent and secret-free. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### AUTHORING_RUNTIME-1 중간 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +ok github.com/nomadcode/nomadcode-core/internal/config 0.002s +ok github.com/nomadcode/nomadcode-core/internal/scheduler 2.039s +ok github.com/nomadcode/nomadcode-core/internal/adapters/openai 0.013s +``` + +### 최종 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +ok github.com/nomadcode/nomadcode-core/internal/config 0.002s +ok github.com/nomadcode/nomadcode-core/internal/scheduler 2.039s +ok github.com/nomadcode/nomadcode-core/internal/adapters/openai 0.013s +``` + +```sh +$ git diff --check +(no output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - Correctness: Pass + - Completeness: Fail + - Test coverage: Fail + - API contract: Pass + - Code quality: Pass + - Plan deviation: Fail + - Verification trust: Pass + - Spec conformance: Fail +- 발견된 문제: + - Required: `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md:83`와 `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md:103`의 `S02/iop-long-run`은 timeout, retry, queue 대기/stale 판단 기준과 그 evidence를 요구하지만, 현재 구현은 `services/core/internal/scheduler/jobs.go:69`에서 `authoring_run_state`/`authoring_run_updated_at`을 기록하고 `services/core/internal/scheduler/jobs.go:93`에서 generic `RunTimeout`만 우회한다. `workflow.MetadataKeyAuthoringRunUpdatedAt`의 주석은 configured staleness threshold를 전제로 하지만 실제 config/default/docs/test에는 stale threshold, queue timeout 판단, retry 판단 기준이 닫혀 있지 않다. 이 상태로 PASS하면 `Roadmap Targets`/`Spec Targets`에 의해 `iop-long-run`과 `S02` 완료가 과대 기록된다. 수정: Plane-origin authoring의 queue/in-progress stale threshold와 retry/timeout 판단 기준을 코드 또는 명시적 설정/문서로 닫고, focused tests에서 generic timeout 유지, authoring non-premature failure, stale/queue/retry metadata 판단 evidence를 함께 검증한다. +- 다음 단계: FAIL 후속 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다. diff --git a/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_1.log b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_1.log new file mode 100644 index 0000000..653c700 --- /dev/null +++ b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_1.log @@ -0,0 +1,204 @@ + + +# Code Review Reference - REVIEW_AUTHORING_RUNTIME + +> **[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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service setup, generic scope conflicts, loop exhaustion, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only the linked SDD/Milestone lock decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-20 +task=m-plane-origin-authoring-roundtrip-sync/01_iop_long_run, plan=1, tag=REVIEW_AUTHORING_RUNTIME + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `iop-long-run`: IOP Responses 호출이 10분 이상 걸릴 수 있는 작업을 실패로 조기 확정하지 않도록 timeout, retry, queue timeout, stale 판단 기준을 재정의한다. +- 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: + - `S02`: task=`iop-long-run`; evidence=`core config/scheduler test or smoke evidence for long-running timeout/retry/stale metadata behavior` +- Completion mode: spec-check-on-pass + +## Archive Evidence Snapshot + +- Current archived plan: `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log` +- Current archived review: `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log` +- Verdict: FAIL +- Required summary: + - `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md:83` and `:103` require `S02/iop-long-run` evidence for timeout, retry, queue waiting, and stale behavior. The implemented code only records `authoring_run_state`/`authoring_run_updated_at` and skips generic `RunTimeout`; it does not close configured stale threshold, queue timeout, or retry decision evidence. +- Affected files from prior loop: + - `services/core/internal/scheduler/jobs.go` + - `services/core/internal/scheduler/jobs_test.go` + - `services/core/internal/config/config.go` + - `services/core/internal/config/config_test.go` + - `services/core/internal/adapters/openai/client.go` + - `services/core/internal/adapters/openai/client_test.go` + - `services/core/README.md` + - `services/core/Makefile` + - `services/core/bin/run` + - `services/core/docker-compose.yml` +- Verification evidence: + - `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...` passed in the review checkout. + - `git diff --check` passed with no output in the review checkout. +- Roadmap/spec carryover: + - Keep the same `Roadmap Targets` and `Spec Targets` only if this follow-up closes the full `iop-long-run`/`S02` completion evidence. If implementation intentionally remains a foundation-only slice, remove completion claims and explain why in `범위 결정 근거`; do not PASS with over-broad completion targets. +- Narrow reread allowed: + - The next implementer may read only the archived files named above if exact prior-loop wording is needed. Do not search `agent-task/archive/**`. + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[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-plane-origin-authoring-roundtrip-sync/01_iop_long_run/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-plane-origin-authoring-roundtrip-sync`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 하지 않는다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_AUTHORING_RUNTIME-1] Complete S02 timeout/retry/stale policy evidence | [x] | + +## 구현 체크리스트 + +- [x] `iop-long-run`/`S02` 완료 주장에 맞게 Plane-origin authoring의 timeout, retry, queue 대기 timeout, stale 판단 기준을 코드 또는 명시적 config/docs로 닫는다. 검증: focused tests에서 generic timeout 유지, authoring non-premature failure, stale/queue/retry metadata 또는 판단 evidence가 함께 확인된다. +- [x] `services/core/README.md`와 관련 config/helper/docs가 `MODEL_TIMEOUT_SEC`, `WORKFLOW_TASK_TIMEOUT_SEC`, River retry, authoring stale/queue 기준을 서로 충돌 없이 설명한다. +- [x] `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...`와 `git diff --check`를 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/`를 `agent-task/archive/YYYY/MM/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/`로 이동한다. +- [x] PASS이고 task group이 `m-plane-origin-authoring-roundtrip-sync`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-plane-origin-authoring-roundtrip-sync/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +없음. 계획의 "config/default 상수 또는 helper로 stale threshold를 드러낸다" 방향을 그대로 따랐다. + +## 주요 설계 결정 + +- **`AuthoringStaleAfterSec` config field** (`AUTHORING_STALE_AFTER_SEC`, default 1200초): `MODEL_TIMEOUT_SEC=900`보다 넉넉한 20분으로 설정해 정상 완료된 IOP 호출이 stale로 오탐되지 않게 한다. Makefile/bin/run/docker-compose.yml에 동기화. +- **`IsAuthoringStale(observedAt, thresholdSec)` helper** (`scheduler/stale.go`): running 태스크는 `authoring_run_updated_at` parsed 값, queue 대기 태스크는 `task.UpdatedAt`을 넘겨 동일 함수로 판단한다. 실제 stale 조치(알림·재-Enqueue)는 후속 모니터링 컴포넌트 책임이고 이 구현은 관찰 가능성만 제공한다. +- **`ParseAuthoringRunUpdatedAt(meta)` helper**: RFC3339 파싱 실패를 zero time으로 처리해 호출자가 nil 체크 없이 `IsAuthoringStale`에 바로 전달할 수 있다. +- **retry**: `FailTaskWithMetadata`는 lifecycle에서 `retryable` 메타데이터를 이미 기록하며 authoring 태스크도 동일 경로를 통과한다. 신규 코드 불필요. +- **queue wait**: queue 대기 중 `WORKFLOW_TASK_TIMEOUT_SEC`가 적용되지 않음은 이미 `TestWorkAuthoringSkipsGenericRunTimeout`으로 증명됨. `IsAuthoringStale(task.UpdatedAt, ...)` 패턴으로 queue stale도 관찰 가능함을 `TestIsAuthoringStaleAppliesToQueueWait`으로 커버. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `S02/iop-long-run` completion evidence가 timeout, retry, queue waiting, stale 판단을 모두 포함하는지 확인한다. +- Generic task timeout behavior remains covered and unchanged. +- Plane-origin authoring does not get marked failed by generic `RunTimeout`. +- Docs/helper defaults are internally consistent and secret-free. +- If the implementation intentionally narrows scope, `Roadmap Targets`/`Spec Targets` are not left overclaiming completion. + +## 검증 결과 + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_AUTHORING_RUNTIME-1 중간 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +ok github.com/nomadcode/nomadcode-core/internal/config 0.005s +ok github.com/nomadcode/nomadcode-core/internal/scheduler 2.036s +ok github.com/nomadcode/nomadcode-core/internal/adapters/openai 0.007s +``` + +### 최종 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +ok github.com/nomadcode/nomadcode-core/internal/config 0.005s +ok github.com/nomadcode/nomadcode-core/internal/scheduler 2.036s +ok github.com/nomadcode/nomadcode-core/internal/adapters/openai 0.007s +``` + +```sh +$ git diff --check +(no output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** + +Sections and their ownership: + +| 섹션 | 소유자 | 설명 | +|------|--------|------| +| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 | +| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 | +| Spec Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Spec Completion`으로 복사 | +| Archive Evidence Snapshot | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트의 기본 이전 루프 컨텍스트; 추가 확인이 필요할 때 여기에 명시된 archive 파일만 좁게 읽음 | +| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채움; 환경/secret/서비스/검증 공백/일반 범위 조정은 사용자 리뷰 요청으로 쓰지 않음 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - Correctness: Pass + - Completeness: Pass + - Test coverage: Pass + - API contract: Pass + - Code quality: Pass + - Plan deviation: Pass + - Verification trust: Pass + - Spec conformance: Pass +- 발견된 문제: 없음 +- 다음 단계: PASS 종결. `complete.log` 작성 후 task directory를 archive로 이동한다. diff --git a/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log new file mode 100644 index 0000000..cf8e00b --- /dev/null +++ b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log @@ -0,0 +1,51 @@ +# Complete - m-plane-origin-authoring-roundtrip-sync/01_iop_long_run + +## 완료 일시 + +2026-06-20 + +## 요약 + +Plane-origin authoring long-run timeout/retry/stale 기준 정리를 2개 review loop 끝에 PASS로 완료했다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | generic `RunTimeout` 우회와 model timeout 기본값은 통과했지만 S02의 retry/queue/stale 판단 evidence가 부족했다. | +| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | PASS | `AUTHORING_STALE_AFTER_SEC` config/default/docs와 scheduler stale helper/tests로 S02 evidence를 보강했다. | + +## 구현/정리 내용 + +- Plane-origin authoring task가 generic `WORKFLOW_TASK_TIMEOUT_SEC`로 조기 `failed` 처리되지 않도록 scheduler `RunTimeout` 적용 경계를 좁혔다. +- `MODEL_TIMEOUT_SEC` 기본값을 900초로 맞추고 README, Makefile, `bin/run`, docker compose, OpenAI client fallback과 config tests를 동기화했다. +- `AUTHORING_STALE_AFTER_SEC` 기본값과 env override를 추가하고, running/queued authoring stale 판단 helper와 tests를 추가했다. +- retry evidence는 기존 `FailTaskWithMetadata`의 `retryable` metadata 경로와 authoring failure metadata tests를 기준으로 문서화했다. + +## 최종 검증 + +- `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...` - PASS; `internal/config`, `internal/scheduler`, `internal/adapters/openai` 모두 ok. +- `git diff --check` - PASS; 출력 없음. +- `bash -n services/core/bin/run` - PASS; 출력 없음. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Completed task ids: + - `iop-long-run`: PASS; evidence=`agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log`, `agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log`, `agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_1.log`, `agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_1.log`; verification=`cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...`, `git diff --check` +- Not completed task ids: 없음 + +## Spec Completion + +- SDD: `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md` +- Completed scenario ids: + - `S02`: PASS; task=`iop-long-run`; evidence=`agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log`, `agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log`, `agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_1.log`, `agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_1.log`; verification=`cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...` +- Not completed scenario ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log new file mode 100644 index 0000000..6ddd0b8 --- /dev/null +++ b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log @@ -0,0 +1,200 @@ + + +# Implementation Plan - AUTHORING_RUNTIME + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현 완료는 active `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 구현 중 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 막히면 채팅으로 묻지 말고 review stub의 `사용자 리뷰 요청` 섹션을 채운 뒤 멈춘다. 환경, secret, 외부 서비스, 검증 증거 부족은 사용자 리뷰 요청이 아니며 `검증 결과` 또는 후속 plan으로 남긴다. archive, `complete.log`, roadmap 갱신은 code-review/runtime 소유다. + +## 배경 + +`authoring-runtime` 에픽의 `iop-long-run` Task는 Plane-origin authoring이 10분 이상 걸리는 IOP Responses 작업을 조기 실패로 확정하지 않도록 요구한다. 현재 core는 모델 HTTP timeout과 scheduler worker timeout이 모두 300초 기본값이고, scheduler가 모든 작업 실행에 같은 `RunTimeout`을 씌운다. 이 plan은 authoring 작업이 generic task timeout에 끌려 실패하지 않게 하고, 10분 이상 작업의 관찰 가능한 metadata를 남기는 foundation 작업만 다룬다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. code-review가 해당 요청을 검증하고 필요 시 `USER_REVIEW.md`를 작성한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `iop-long-run`: IOP Responses 호출이 10분 이상 걸릴 수 있는 작업을 실패로 조기 확정하지 않도록 timeout, retry, queue timeout, stale 판단 기준을 재정의한다. +- 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: + - `S02`: task=`iop-long-run`; evidence=`core config/scheduler test or smoke evidence for long-running timeout/retry/stale metadata behavior` +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-ops/skills/common/roadmap-sdd/SKILL.md` +- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md` +- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md` +- `agent-ops/rules/project/domain/core/rules.md` +- `agent-ops/rules/project/domain/workspace-ops/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/core-smoke.md` +- `agent-test/local/workspace-ops-smoke.md` +- `agent-test/live-cycle-smoke.md` +- `services/core/internal/config/config.go` +- `services/core/internal/config/config_test.go` +- `services/core/internal/model/model.go` +- `services/core/internal/workflow/model.go` +- `services/core/internal/adapters/openai/client.go` +- `services/core/internal/adapters/openai/client_test.go` +- `services/core/internal/authoring/request.go` +- `services/core/internal/authoring/request_test.go` +- `services/core/internal/authoring/result.go` +- `services/core/internal/authoring/result_test.go` +- `services/core/internal/scheduler/jobs.go` +- `services/core/internal/scheduler/jobs_test.go` +- `services/core/internal/scheduler/river.go` +- `services/core/internal/scheduler/river_test.go` +- `services/core/internal/agent/agent.go` +- `services/core/internal/adapters/a2a/client.go` +- `services/core/internal/adapters/a2a/client_test.go` +- `services/core/cmd/server/main.go` +- `services/core/README.md` +- `services/core/docker-compose.yml` +- `services/core/Makefile` +- `services/core/bin/run` +- `services/core/go.mod` + +### 테스트 환경 규칙 + +- test_env: `local`. +- `agent-test/local/rules.md` present/read. Matching profiles read: `agent-test/local/core-smoke.md`, `agent-test/local/workspace-ops-smoke.md`. +- core 필수 검증: `cd services/core && go test ./...`; focused 검증은 `go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...`. +- workspace-ops 문서/task artifact 변경 검증: `git diff --check`. +- `agent-test/live-cycle-smoke.md`는 live gate 참고로 읽었지만 이 plan의 pass 조건에는 포함하지 않는다. + +### 테스트 커버리지 공백 + +- Generic worker timeout은 `services/core/internal/scheduler/jobs_test.go:305`의 `TestWorkMarksTimeoutFailure`가 덮는다. +- Plane-origin authoring이 `RunTimeout` 때문에 조기 `failed`가 되지 않는 회귀 테스트는 없다. +- `MODEL_TIMEOUT_SEC` 기본값과 dev helper/compose/README의 장시간 기준 일관성 테스트는 없다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- 새 config 필드나 model result 필드를 도입하면 `cmd/server/main.go`, `internal/config`, `internal/adapters/openai`, `internal/scheduler` call site를 모두 갱신한다. + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- 공유 task group: `agent-task/m-plane-origin-authoring-roundtrip-sync`. +- `01_iop_long_run`: independent foundation. scheduler timeout/config/metadata 기준을 먼저 세운다. +- `02+01_iop_progress`: depends on `01`; progress metadata와 streaming/fallback은 timeout foundation 위에서 구현한다. predecessor `01`: missing active/archive `complete.log`. +- `03+01,02_push_command`: depends on `01` and `02`; push-only 단계는 장시간/progress metadata와 completion gate를 재사용한다. predecessors `01`, `02`: missing active/archive `complete.log`. + +### 범위 결정 근거 + +- 포함: authoring long-run timeout policy, default model timeout 문서/helper 일관성, scheduler authoring timeout regression tests. +- 제외: `/v1/responses` streaming/SSE parsing은 `02+01_iop_progress`. +- 제외: push-only workspace agent request는 `03+01,02_push_command`. +- 제외: identity-sync, gito-plane, live-gate 에픽 Task. + +### 빌드 등급 + +- `cloud-G07`: 외부 IOP long-running runtime semantics가 핵심이고 generic task timeout과 authoring terminal state를 분리해야 하므로 cloud lane이 맞다. + +## 구현 체크리스트 + +- [ ] `iop-long-run` 기준으로 authoring 작업은 generic `WORKFLOW_TASK_TIMEOUT_SEC`로 조기 `failed` 처리되지 않고, `MODEL_TIMEOUT_SEC`/helper/docs는 10분 이상 실행 가능한 기본선으로 정리한다. 검증: focused Go tests에서 generic timeout은 유지되고 authoring은 관찰 가능한 in-progress/stale metadata를 남긴다. +- [ ] `services/core/README.md`, `services/core/Makefile`, `services/core/bin/run`, `services/core/docker-compose.yml`의 timeout 설명/기본값이 code default와 충돌하지 않게 갱신한다. +- [ ] `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [AUTHORING_RUNTIME-1] Authoring long-run timeout foundation + +문제: + +`services/core/internal/config/config.go:86` and `services/core/internal/config/config.go:105` default both model and workflow task timeout to 300 seconds. `services/core/internal/adapters/openai/client.go:20` and `services/core/internal/adapters/openai/client.go:39` repeat the 300 second model timeout default. `services/core/internal/scheduler/jobs.go:92` applies `RunTimeout` to every task before `w.runTask`, so a Plane-origin authoring call can hit `context.DeadlineExceeded` and `markFailedWithTask` before 10 minutes. + +Before: + +```go +// services/core/internal/scheduler/jobs.go:92 +runCtx := ctx +if w.RunTimeout > 0 { + var cancel context.CancelFunc + runCtx, cancel = context.WithTimeout(ctx, w.RunTimeout) + defer cancel() +} + +result, message, err := w.runTask(runCtx, task) +``` + +해결 방법: + +- Add a small scheduler helper that chooses execution context by task type. Generic tasks keep `RunTimeout`; Plane-origin authoring tasks do not inherit the generic worker deadline and instead rely on model timeout/progress/stale metadata. +- Raise model timeout defaults used for IOP Responses authoring to at least 900 seconds in `config`, `openai`, `Makefile`, `bin/run`, `docker-compose.yml`, and README examples. Keep env override behavior. +- Keep `WORKFLOW_TASK_TIMEOUT_SEC` documented as generic worker-attempt timeout; document that Plane-origin authoring is long-running and observed through `authoring_run_state`, `authoring_run_updated_at`, and later stale/progress metadata. +- Add scheduler tests proving a short `RunTimeout` still fails generic model work but does not cancel a Plane-origin authoring model call that completes after that generic timeout. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/scheduler/jobs.go`: extract context selection and exempt authoring tasks from generic `RunTimeout`. +- [ ] `services/core/internal/scheduler/jobs_test.go`: add authoring long-run regression test; keep `TestWorkMarksTimeoutFailure` generic behavior. +- [ ] `services/core/internal/config/config.go`: update model timeout default or introduce named constants if local style stays simple. +- [ ] `services/core/internal/config/config_test.go`: update default assertions and add override assertion if new field/constant is introduced. +- [ ] `services/core/internal/adapters/openai/client.go`: keep client default aligned with config default. +- [ ] `services/core/internal/adapters/openai/client_test.go`: add/adjust timeout-default test if needed. +- [ ] `services/core/README.md`, `services/core/Makefile`, `services/core/bin/run`, `services/core/docker-compose.yml`: update default examples and long-running authoring note. + +테스트 작성: + +- Write regression tests in `services/core/internal/scheduler/jobs_test.go`. +- Update config/adapter tests for timeout defaults. +- No DB migration test is required; no schema is changed. + +중간 검증: + +```sh +cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +``` + +Expected: tests pass, including new authoring long-run regression and existing generic timeout failure. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/scheduler/jobs.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/scheduler/jobs_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/config/config.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/config/config_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/adapters/openai/client.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/adapters/openai/client_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/README.md` | AUTHORING_RUNTIME-1 | +| `services/core/Makefile` | AUTHORING_RUNTIME-1 | +| `services/core/bin/run` | AUTHORING_RUNTIME-1 | +| `services/core/docker-compose.yml` | AUTHORING_RUNTIME-1 | + +## 최종 검증 + +```sh +cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +``` + +Expected: pass; Go test cache is not acceptable because timeout behavior changed. + +```sh +git diff --check +``` + +Expected: no output. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_1.log b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_1.log new file mode 100644 index 0000000..ba6cc2d --- /dev/null +++ b/agent-task/archive/2026/06/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_1.log @@ -0,0 +1,158 @@ + + +# Implementation Plan - REVIEW_AUTHORING_RUNTIME + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현 완료는 active `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 구현 중 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. 환경, secret, 외부 서비스, 검증 증거 부족은 사용자 리뷰 요청이 아니며 `검증 결과` 또는 후속 plan으로 남긴다. archive, `complete.log`, roadmap 갱신은 code-review/runtime 소유다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. + +## 배경 + +직전 루프는 Plane-origin authoring 작업이 generic `WORKFLOW_TASK_TIMEOUT_SEC`에 의해 10분 전에 조기 실패하지 않도록 `RunTimeout` 우회와 `MODEL_TIMEOUT_SEC=900` 기본값 정리를 구현했다. focused tests와 `git diff --check`는 통과했지만, active `Roadmap Targets`/`Spec Targets`는 `iop-long-run`과 SDD `S02` 완료를 주장한다. SDD `S02`는 timeout뿐 아니라 retry, queue 대기 timeout, stale 판단 기준 및 evidence를 요구하므로, 현재 상태로 PASS하면 milestone/spec completion이 과대 기록된다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. code-review가 해당 요청을 검증하고 필요 시 `USER_REVIEW.md`를 작성한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `iop-long-run`: IOP Responses 호출이 10분 이상 걸릴 수 있는 작업을 실패로 조기 확정하지 않도록 timeout, retry, queue timeout, stale 판단 기준을 재정의한다. +- 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: + - `S02`: task=`iop-long-run`; evidence=`core config/scheduler test or smoke evidence for long-running timeout/retry/stale metadata behavior` +- Completion mode: spec-check-on-pass + +## Archive Evidence Snapshot + +- Current archived plan: `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/plan_cloud_G07_0.log` +- Current archived review: `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/code_review_cloud_G07_0.log` +- Verdict: FAIL +- Required summary: + - `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md:83` and `:103` require `S02/iop-long-run` evidence for timeout, retry, queue waiting, and stale behavior. The implemented code only records `authoring_run_state`/`authoring_run_updated_at` and skips generic `RunTimeout`; it does not close configured stale threshold, queue timeout, or retry decision evidence. +- Affected files from prior loop: + - `services/core/internal/scheduler/jobs.go` + - `services/core/internal/scheduler/jobs_test.go` + - `services/core/internal/config/config.go` + - `services/core/internal/config/config_test.go` + - `services/core/internal/adapters/openai/client.go` + - `services/core/internal/adapters/openai/client_test.go` + - `services/core/README.md` + - `services/core/Makefile` + - `services/core/bin/run` + - `services/core/docker-compose.yml` +- Verification evidence: + - `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...` passed in the review checkout. + - `git diff --check` passed with no output in the review checkout. +- Roadmap/spec carryover: + - Keep the same `Roadmap Targets` and `Spec Targets` only if this follow-up closes the full `iop-long-run`/`S02` completion evidence. If implementation intentionally remains a foundation-only slice, remove completion claims and explain why in `범위 결정 근거`; do not PASS with over-broad completion targets. +- Narrow reread allowed: + - The next implementer may read only the archived files named above if exact prior-loop wording is needed. Do not search `agent-task/archive/**`. + +## 범위 결정 근거 + +- 포함: Plane-origin authoring의 timeout/retry/queue/stale 판단 기준을 코드, docs, tests에서 명시하고 `S02` completion evidence가 과대 기록되지 않게 한다. +- 포함: 직전 루프의 `RunTimeout` 우회와 `MODEL_TIMEOUT_SEC=900` 기본값은 유지하되, 그 위에 부족한 SDD evidence를 닫는다. +- 제외: `/v1/responses` streaming/fallback progress 구현은 `02+01_iop_progress` 범위다. +- 제외: push-only workspace agent request와 remote `develop` 검증은 `03+01,02_push_command` 범위다. +- 제외: live full-cycle smoke는 이 follow-up의 pass 조건이 아니다. + +## 빌드 등급 + +- `cloud-G07`: 외부 IOP long-running runtime semantics와 SDD completion evidence 신뢰가 핵심이며, timeout/retry/stale 판단이 scheduler/config/docs/tests에 걸쳐 있어 cloud lane이 맞다. + +## 구현 체크리스트 + +- [ ] `iop-long-run`/`S02` 완료 주장에 맞게 Plane-origin authoring의 timeout, retry, queue 대기 timeout, stale 판단 기준을 코드 또는 명시적 config/docs로 닫는다. 검증: focused tests에서 generic timeout 유지, authoring non-premature failure, stale/queue/retry metadata 또는 판단 evidence가 함께 확인된다. +- [ ] `services/core/README.md`와 관련 config/helper/docs가 `MODEL_TIMEOUT_SEC`, `WORKFLOW_TASK_TIMEOUT_SEC`, River retry, authoring stale/queue 기준을 서로 충돌 없이 설명한다. +- [ ] `cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/...`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [REVIEW_AUTHORING_RUNTIME-1] Complete S02 timeout/retry/stale policy evidence + +문제: + +직전 구현은 authoring task에 generic `RunTimeout`을 적용하지 않으므로 10분 전 조기 `failed` 문제는 줄였다. 그러나 active target은 `iop-long-run` 전체 완료이고, SDD `S02` evidence는 timeout, retry, queue 대기, stale 판단 기준을 요구한다. 현재 코드와 문서는 `authoring_run_updated_at`을 남기지만 stale threshold가 어디서 설정되는지, queue 대기를 언제 stale/timeout으로 판단하는지, retryable 판단이 long-running authoring에 어떤 metadata로 확인되는지 닫지 않는다. + +Before: + +```go +// services/core/internal/scheduler/jobs.go +if _, isAuthoring := authoring.TaskCheckoutMetadata(task); isAuthoring { + _ = w.Lifecycle.MergeTaskMetadata(ctx, taskID, map[string]any{ + workflow.MetadataKeyAuthoringRunState: "in_progress", + workflow.MetadataKeyAuthoringRunUpdatedAt: time.Now().UTC().Format(time.RFC3339), + }) +} +``` + +해결 방법: + +- 기존 `RunTimeout` 우회는 유지한다. +- 기존 구조에 맞는 최소 단위로 authoring long-run observation policy를 명시한다. 가능한 방향은 다음 중 코드베이스에 가장 작은 변경으로 맞는 것을 택한다: + - config/default 상수 또는 helper로 authoring stale threshold/queue wait threshold를 드러내고 README에 기록한다. + - queue 상태 task와 running authoring task에서 `authoring_run_updated_at`, task `UpdatedAt`, attempt/retryable metadata를 어떻게 판단하는지 scheduler/workflow 테스트가 검증할 수 있게 한다. + - 실제 monitor 구현이 아직 후속 범위라면 active `Roadmap Targets`/`Spec Targets` 완료 주장을 유지하지 말고, 이번 follow-up에서 완료 evidence를 foundation-only로 축소한다. 단, `check-on-pass`를 유지하려면 `S02` evidence를 닫아야 한다. +- tests는 단순 존재 확인이 아니라 다음을 구분해야 한다: + - generic task는 짧은 `RunTimeout`에서 timeout failure와 `retryable` metadata를 유지한다. + - Plane-origin authoring은 generic `RunTimeout`으로 failed가 되지 않는다. + - Plane-origin authoring의 stale/queue/retry 판단 기준이 metadata 또는 helper output으로 관찰 가능하다. +- docs/helper default는 code default와 같은 숫자와 의미를 사용한다. `WORKFLOW_TASK_TIMEOUT_SEC`는 generic worker attempt timeout, `MODEL_TIMEOUT_SEC`는 IOP Responses HTTP 실행 bound, authoring stale/queue 기준은 별도 의미로 설명한다. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/scheduler/jobs.go` 또는 인접 helper: authoring long-run observation policy가 코드에서 확인 가능하게 한다. +- [ ] `services/core/internal/scheduler/jobs_test.go`: S02 evidence를 timeout/retry/stale/queue 관점으로 보강한다. +- [ ] `services/core/internal/config/config.go`와 `services/core/internal/config/config_test.go`: 새 threshold/config를 도입하는 경우 default와 env override를 검증한다. +- [ ] `services/core/README.md`, `services/core/Makefile`, `services/core/bin/run`, `services/core/docker-compose.yml`: default와 설명이 code와 충돌하지 않게 갱신한다. +- [ ] 필요 시 `services/core/internal/adapters/openai/client.go`와 `client_test.go`: model timeout default alignment가 깨지지 않는지 유지한다. + +테스트 작성: + +- focused scheduler/config tests를 우선한다. +- 새 config key를 도입하면 override test를 추가한다. +- DB migration test는 schema 변경이 없으면 필요 없다. + +중간 검증: + +```sh +cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +``` + +Expected: tests pass without cache, and new/updated tests explicitly cover `S02` completion evidence. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/scheduler/jobs.go` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/internal/scheduler/jobs_test.go` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/internal/config/config.go` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/internal/config/config_test.go` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/internal/adapters/openai/client.go` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/internal/adapters/openai/client_test.go` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/README.md` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/Makefile` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/bin/run` | REVIEW_AUTHORING_RUNTIME-1 | +| `services/core/docker-compose.yml` | REVIEW_AUTHORING_RUNTIME-1 | + +## 최종 검증 + +```sh +cd services/core && go test -count=1 ./internal/config/... ./internal/scheduler/... ./internal/adapters/openai/... +``` + +Expected: pass; Go test cache is not acceptable because timeout/stale/retry behavior changed. + +```sh +git diff --check +``` + +Expected: no output. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/CODE_REVIEW-cloud-G07.md b/agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..1bb243f --- /dev/null +++ b/agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,114 @@ + + +# Code Review Reference - AUTHORING_RUNTIME + +> **[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. +> Confirm predecessor `01_iop_long_run` has `complete.log` before implementation starts. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization is review-agent-only. + +## 개요 + +date=2026-06-20 +task=m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress, plan=0, tag=AUTHORING_RUNTIME + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `iop-progress`: `/v1/responses` streaming 지원 여부를 dev IOP 계약으로 확인하고, stream 미지원 환경에서는 `authoring_run_updated_at`과 task metadata 기반 stale monitor로 대체한다. +- 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: + - `S03`: task=`iop-progress`; evidence=`IOP streaming capability check and fallback stale-monitor evidence` +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [AUTHORING_RUNTIME-1] Responses progress contract and fallback metadata | [ ] | + +## 구현 체크리스트 + +- [ ] `iop-progress` 기준으로 `/v1/responses` stream 지원/미지원이 task metadata에 명시되고, 진행 갱신이 있을 때 `authoring_run_updated_at`이 갱신된다. 검증: stream 지원 path와 stream 미지원 fallback path unit tests가 모두 통과한다. +- [ ] stream 미지원은 silent failure가 아니라 `authoring_progress_mode`/`authoring_progress_reason` 같은 명시 metadata로 남긴다. +- [ ] `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] active review/plan files를 `.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block을 확인한다. +- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이면 runtime completion metadata를 보고하고 roadmap 직접 수정은 하지 않는다. +- [ ] WARN/FAIL이면 후속 plan 또는 USER_REVIEW 필요 여부를 판단한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `stream:false` fallback still works. +- Stream unsupported state is explicit metadata, not a swallowed error. +- Scheduler progress callback cannot overwrite secret or raw payload data into task metadata. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### AUTHORING_RUNTIME-1 중간 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/... +(output) +``` + +### 최종 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/... +(output) +``` + +```sh +$ git diff --check +(output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/PLAN-cloud-G07.md b/agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/PLAN-cloud-G07.md new file mode 100644 index 0000000..29d1f62 --- /dev/null +++ b/agent-task/m-plane-origin-authoring-roundtrip-sync/02+01_iop_progress/PLAN-cloud-G07.md @@ -0,0 +1,180 @@ + + +# Implementation Plan - AUTHORING_RUNTIME + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan은 split predecessor `01_iop_long_run`의 `complete.log`가 필요하다. 구현 시작 전 `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log` 또는 matching archive `complete.log`가 있는지 확인한다. 구현 완료는 active `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 사용자에게 직접 질문하지 말고, 선택된 SDD/Milestone lock 결정만 review stub의 `사용자 리뷰 요청`에 기록한다. + +## 배경 + +`iop-progress` Task는 dev IOP의 `/v1/responses` streaming 지원 여부를 확인하고, stream 미지원이면 silent failure가 아니라 명시 metadata와 stale monitor 기준으로 대체하라고 요구한다. 현재 OpenAI-compatible adapter는 `stream:false`를 hard-code하고 응답 body를 한 번에 읽는다. Scheduler는 `authoring_run_updated_at`을 시작과 최종 대기 상태에서만 갱신하므로 진행 갱신 중단을 감지할 수 없다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. code-review가 해당 요청을 검증하고 필요 시 `USER_REVIEW.md`를 작성한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `iop-progress`: `/v1/responses` streaming 지원 여부를 dev IOP 계약으로 확인하고, stream 미지원 환경에서는 `authoring_run_updated_at`과 task metadata 기반 stale monitor로 대체한다. +- 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: + - `S03`: task=`iop-progress`; evidence=`IOP streaming capability check and fallback stale-monitor evidence` +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-test/local/rules.md` +- `agent-test/local/core-smoke.md` +- `agent-test/local/workspace-ops-smoke.md` +- `agent-test/live-cycle-smoke.md` +- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md` +- `services/core/internal/model/model.go` +- `services/core/internal/workflow/model.go` +- `services/core/internal/adapters/openai/client.go` +- `services/core/internal/adapters/openai/client_test.go` +- `services/core/internal/scheduler/jobs.go` +- `services/core/internal/scheduler/jobs_test.go` +- `services/core/internal/config/config.go` +- `services/core/internal/config/config_test.go` +- `services/core/README.md` +- `services/core/go.mod` + +### 테스트 환경 규칙 + +- test_env: `local`. +- `agent-test/local/rules.md`, `core-smoke`, `workspace-ops-smoke` read. +- focused verification: `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...`. +- `git diff --check` for docs/task artifact whitespace. + +### 테스트 커버리지 공백 + +- `services/core/internal/adapters/openai/client_test.go` covers non-streaming response shape and workspace metadata. +- No test covers `stream:true`, SSE parsing, streaming unsupported fallback, progress callback, or scheduler metadata updates from progress events. + +### 심볼 참조 + +- If `model.Client.Generate` signature remains unchanged, no call-site rename is needed. +- If adding progress callback fields to `model.GenerateInput` or `model.GenerateResult`, update fake clients in `services/core/internal/scheduler/jobs_test.go` and adapter tests. + +### 분할 판단 + +- split decision policy evaluated. +- Directory dependency `02+01_iop_progress` means predecessor `01_iop_long_run` must finish first. +- Predecessor `01`: missing `complete.log` at active `agent-task/m-plane-origin-authoring-roundtrip-sync/01_iop_long_run/complete.log`; no archive candidate was read because no candidate path exists for this new split group. +- This plan is separate because streaming/progress changes touch the model adapter contract and can be reviewed independently from timeout policy and push-only flow. + +### 범위 결정 근거 + +- 포함: model progress metadata, OpenAI-compatible Responses stream/fallback behavior, scheduler metadata updates for progress/stale. +- 제외: initial long-run timeout defaults (`01_iop_long_run`). +- 제외: push-only request and remote develop gate (`03+01,02_push_command`). +- Exclude live Plane full-cycle evidence; this plan may document dev IOP stream capability but does not claim `full-cycle`. + +### 빌드 등급 + +- `cloud-G07`: streaming support/fallback can pass unit tests while still breaking live IOP semantics, so cloud lane is required. + +## 구현 체크리스트 + +- [ ] `iop-progress` 기준으로 `/v1/responses` stream 지원/미지원이 task metadata에 명시되고, 진행 갱신이 있을 때 `authoring_run_updated_at`이 갱신된다. 검증: stream 지원 path와 stream 미지원 fallback path unit tests가 모두 통과한다. +- [ ] stream 미지원은 silent failure가 아니라 `authoring_progress_mode`/`authoring_progress_reason` 같은 명시 metadata로 남긴다. +- [ ] `cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/...`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- Runtime dependency from directory name: `01_iop_long_run` must produce `complete.log` before this subtask starts. +- Do not add hidden dependencies beyond the directory name. + +### [AUTHORING_RUNTIME-1] Responses progress contract and fallback metadata + +문제: + +`services/core/internal/adapters/openai/client.go:66` builds a Responses request with `Stream: false` at `services/core/internal/adapters/openai/client.go:71`, then reads the whole body at `services/core/internal/adapters/openai/client.go:102` through `services/core/internal/adapters/openai/client.go:118`. `services/core/internal/model/model.go:27` has no progress callback/result field. `services/core/internal/scheduler/jobs.go:69` through `services/core/internal/scheduler/jobs.go:79` only record initial `authoring_run_updated_at`, so progress interruption and stream unsupported states are invisible. + +Before: + +```go +// services/core/internal/adapters/openai/client.go:66 +reqBody := responsesRequest{ + Model: modelName, + Input: input.Input, + Instructions: input.Instructions, + Metadata: buildRequestMetadata(input), + Stream: false, + Temperature: input.Temperature, + TopP: input.TopP, + MaxOutputTokens: input.MaxOutputTokens, +} +``` + +해결 방법: + +- Add an internal model progress representation without exposing raw stream payloads or secrets. Suggested fields: progress mode (`streaming`, `stream_unsupported`, `non_streaming`), reason, last event time, and optional callback on `GenerateInput`. +- Add config/env for enabling Responses streaming only when dev IOP supports it, defaulting to non-streaming fallback. If the endpoint returns an unsupported stream error, retry once non-streaming and return explicit fallback reason. +- Implement SSE parsing for OpenAI-compatible Responses stream events that update progress and final text. Keep non-streaming behavior compatible. +- In scheduler authoring path, pass a progress callback that merges `authoring_run_updated_at` and progress metadata through `Lifecycle.MergeTaskMetadata`. +- Add tests for stream supported, stream unsupported fallback, and scheduler metadata merge on progress. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/model/model.go`: add progress fields/callback types. +- [ ] `services/core/internal/adapters/openai/client.go`: support stream-enabled path, unsupported fallback, and progress result metadata. +- [ ] `services/core/internal/adapters/openai/client_test.go`: add SSE supported and unsupported fallback tests. +- [ ] `services/core/internal/config/config.go`: add stream enable/config field only if needed. +- [ ] `services/core/internal/config/config_test.go`: assert stream config defaults/overrides if added. +- [ ] `services/core/internal/scheduler/jobs.go`: persist authoring progress metadata safely. +- [ ] `services/core/internal/scheduler/jobs_test.go`: add progress callback metadata regression test. +- [ ] `services/core/README.md`: document stream default/fallback and stale-monitor metadata without secrets. + +테스트 작성: + +- Required. Add adapter tests for stream support and fallback. +- Required. Add scheduler test for `authoring_run_updated_at` update on progress. + +중간 검증: + +```sh +cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/... +``` + +Expected: pass with stream/fallback/progress metadata tests. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/model/model.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/adapters/openai/client.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/adapters/openai/client_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/config/config.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/config/config_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/scheduler/jobs.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/scheduler/jobs_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/README.md` | AUTHORING_RUNTIME-1 | + +## 최종 검증 + +```sh +cd services/core && go test -count=1 ./internal/model/... ./internal/adapters/openai/... ./internal/scheduler/... ./internal/config/... +``` + +Expected: pass; Go test cache is not acceptable because stream/progress behavior changed. + +```sh +git diff --check +``` + +Expected: no output. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command/CODE_REVIEW-cloud-G08.md b/agent-task/m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command/CODE_REVIEW-cloud-G08.md new file mode 100644 index 0000000..0b4014d --- /dev/null +++ b/agent-task/m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command/CODE_REVIEW-cloud-G08.md @@ -0,0 +1,114 @@ + + +# Code Review Reference - AUTHORING_RUNTIME + +> **[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. +> Confirm predecessors `01_iop_long_run` and `02+01_iop_progress` have `complete.log` before implementation starts. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization is review-agent-only. + +## 개요 + +date=2026-06-20 +task=m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command, plan=0, tag=AUTHORING_RUNTIME + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `push-command`: authoring 결과 작성 이후 Core가 workspace agent에 `변경된 내용에 대해 develop 브런치에 푸시해` push-only 요청을 별도 단계로 보낼 수 있다. +- 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: + - `S04`: task=`push-command`; evidence=`scheduler/runtime test or smoke showing push-only request and remote develop verification gate` +- Completion mode: spec-check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [AUTHORING_RUNTIME-1] Push-only authoring stage and develop-match gate | [ ] | + +## 구현 체크리스트 + +- [ ] `push-command` 기준으로 first authoring response success alone does not complete or advance projection; a push-only request is sent with the exact user-facing command and workspace metadata. 검증: scheduler test captures two authoring model/agent calls and the second command text. +- [ ] push-only success records `wait_type=develop_match` and waits for remote `develop` confirmation via the existing Gito/develop match path; push-only failure records `push_failed` and maps slot to `dirty`. +- [ ] `cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] active review/plan files를 `.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block을 확인한다. +- [ ] PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이면 runtime completion metadata를 보고하고 roadmap 직접 수정은 하지 않는다. +- [ ] WARN/FAIL이면 후속 plan 또는 USER_REVIEW 필요 여부를 판단한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Push-only command text is exact and user-facing. +- Push-only failure is `push_failed` and slot `dirty`, not generic bridge failure. +- Scheduler still waits for Gito/develop match before task completion/projection. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### AUTHORING_RUNTIME-1 중간 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/... +(output) +``` + +### 최종 검증 + +```sh +$ cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/... +(output) +``` + +```sh +$ git diff --check +(output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-task/m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command/PLAN-cloud-G08.md b/agent-task/m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command/PLAN-cloud-G08.md new file mode 100644 index 0000000..452e727 --- /dev/null +++ b/agent-task/m-plane-origin-authoring-roundtrip-sync/03+01,02_push_command/PLAN-cloud-G08.md @@ -0,0 +1,185 @@ + + +# Implementation Plan - AUTHORING_RUNTIME + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan은 split predecessors `01_iop_long_run`과 `02+01_iop_progress`의 `complete.log`가 필요하다. 구현 시작 전 두 predecessor가 완료되었는지 확인한다. 구현 완료는 active `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 사용자에게 직접 질문하지 말고, 선택된 SDD/Milestone lock 결정만 review stub의 `사용자 리뷰 요청`에 기록한다. + +## 배경 + +`push-command` Task는 첫 authoring 응답 성공만으로 task completion이나 projection으로 넘어가지 않고, 별도 push-only workspace agent 요청과 remote `develop` 확인을 요구한다. 현재 scheduler는 authoring model call을 한 번만 실행하고 결과를 `in_progress`로 두어 develop match를 기다린다. push-only 요청이 없으므로 생성된 milestone이 remote `develop`에 반영되지 않을 수 있다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자에게 질문하지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정이 실제 구현을 차단할 때만 active `CODE_REVIEW-cloud-G08.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한다. code-review가 해당 요청을 검증하고 필요 시 `USER_REVIEW.md`를 작성한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- Task ids: + - `push-command`: authoring 결과 작성 이후 Core가 workspace agent에 `변경된 내용에 대해 develop 브런치에 푸시해` push-only 요청을 별도 단계로 보낼 수 있다. +- 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: + - `S04`: task=`push-command`; evidence=`scheduler/runtime test or smoke showing push-only request and remote develop verification gate` +- Completion mode: spec-check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-test/local/rules.md` +- `agent-test/local/core-smoke.md` +- `agent-test/local/workspace-ops-smoke.md` +- `agent-test/live-cycle-smoke.md` +- `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md` +- `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md` +- `services/core/internal/model/model.go` +- `services/core/internal/agent/agent.go` +- `services/core/internal/adapters/a2a/client.go` +- `services/core/internal/adapters/a2a/client_test.go` +- `services/core/internal/adapters/openai/client.go` +- `services/core/internal/adapters/openai/client_test.go` +- `services/core/internal/authoring/request.go` +- `services/core/internal/authoring/request_test.go` +- `services/core/internal/authoring/result.go` +- `services/core/internal/authoring/result_test.go` +- `services/core/internal/scheduler/jobs.go` +- `services/core/internal/scheduler/jobs_test.go` +- `services/core/internal/scheduler/river.go` +- `services/core/internal/scheduler/river_test.go` +- `services/core/cmd/server/main.go` +- `services/core/README.md` +- `services/core/go.mod` + +### 테스트 환경 규칙 + +- test_env: `local`. +- `agent-test/local/rules.md`, `core-smoke`, `workspace-ops-smoke` read. +- focused verification: `cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...`. +- `agent-test/live-cycle-smoke.md` read as runtime-equivalent context; live Plane/secret evidence remains outside this Task's required pass condition unless implementation chooses to run it and records redacted output. + +### 테스트 커버리지 공백 + +- `services/core/internal/scheduler/jobs_test.go:978` proves authoring waits for develop match after model success. +- No test proves a second push-only request is sent. +- No test proves push-only failure becomes `push_failed`/dirty instead of generic `bridge_failed`/error. +- No test proves `wait_type=develop_match` is set only after push-only request succeeds. + +### 심볼 참조 + +- If adding `BuildAuthoringPushInput` or similar helper, update only scheduler/authoring tests. +- If changing `TaskWorker` fields or model/agent interface, update `services/core/internal/scheduler/river.go`, `services/core/cmd/server/main.go`, and test fakes. + +### 분할 판단 + +- split decision policy evaluated. +- Directory dependency `03+01,02_push_command` means predecessors `01_iop_long_run` and `02+01_iop_progress` must finish first. +- Predecessor `01`: missing active/archive `complete.log`. +- Predecessor `02`: missing active/archive `complete.log`. +- This plan is separated because push-only behavior changes authoring completion semantics and failure categories after the timeout/progress foundation is available. + +### 범위 결정 근거 + +- 포함: push-only workspace agent/model request, failure category mapping, scheduler wait gate, unit/runtime-equivalent tests. +- 제외: actual Gito webhook delivery and Plane Todo projection; those are `gito-plane`/`live-gate` Task scopes. +- Exclude provider identity injection/matching; that belongs to `identity-sync`. + +### 빌드 등급 + +- `cloud-G08`: terminal-agent behavior and remote branch verification are central, and a plausible local-only implementation could still fail the runtime roundtrip. + +## 구현 체크리스트 + +- [ ] `push-command` 기준으로 first authoring response success alone does not complete or advance projection; a push-only request is sent with the exact user-facing command and workspace metadata. 검증: scheduler test captures two authoring model/agent calls and the second command text. +- [ ] push-only success records `wait_type=develop_match` and waits for remote `develop` confirmation via the existing Gito/develop match path; push-only failure records `push_failed` and maps slot to `dirty`. +- [ ] `cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/...`와 `git diff --check`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- Runtime dependency from directory name: `01_iop_long_run` and `02+01_iop_progress` must produce `complete.log` before this subtask starts. +- Do not add hidden dependencies beyond the directory name. + +### [AUTHORING_RUNTIME-1] Push-only authoring stage and develop-match gate + +문제: + +`services/core/internal/scheduler/jobs.go:167` calls `w.Model.Generate` once for Plane-origin authoring. `services/core/internal/scheduler/jobs.go:171` through `services/core/internal/scheduler/jobs.go:191` returns `authoring_run_state=in_progress` immediately after that first response. `services/core/internal/scheduler/jobs.go:105` through `services/core/internal/scheduler/jobs.go:129` keeps the task running for develop match, but no push-only workspace request is ever sent. `services/core/internal/authoring/result.go:47` already has `PushFailed`, but scheduler cannot currently reach it for push-only failure. + +Before: + +```go +// services/core/internal/scheduler/jobs.go:167 +generated, err := w.Model.Generate(ctx, generateInput) +if err != nil { + return nil, "", err +} +decision := authoring.DecideAuthoringResult(authoring.DecisionInput{ + BridgeSuccess: true, +}) +``` + +해결 방법: + +- Add an authoring helper that builds a push-only `model.GenerateInput` from the original task/workspace metadata. The command text must be exactly `변경된 내용에 대해 develop 브런치에 푸시해` unless a local constant is introduced with that exact value. +- Refactor scheduler authoring path into a small `runAuthoringTask` helper: first call authoring generate, then call push-only generate. Do not mark/wait for develop match until push-only returns success. +- Persist distinct metadata keys for first authoring response and push-only response, for example `authoring_response_id`, `push_response_id`, `push_request_state=succeeded`. +- On push-only error, surface an authoring failure that `markFailedWithTask` maps to `authoring_failure_category=push_failed`, `failure_type=execution`, and slot `dirty`. +- Keep remote `develop` confirmation delegated to existing Gito scan/develop match finalization. The scheduler must remain running with `wait_type=develop_match` after push-only succeeds. + +수정 파일 및 체크리스트: + +- [ ] `services/core/internal/authoring/request.go`: add push-only input helper and constant. +- [ ] `services/core/internal/authoring/request_test.go`: assert command text, workspace metadata, and provider identity are preserved. +- [ ] `services/core/internal/authoring/result.go`: add helper/error mapping if needed so push failures do not become bridge failures. +- [ ] `services/core/internal/authoring/result_test.go`: assert push failure category remains `push_failed`. +- [ ] `services/core/internal/scheduler/jobs.go`: refactor authoring run, perform push-only call, gate develop wait after push success. +- [ ] `services/core/internal/scheduler/jobs_test.go`: add two-call success test, no-complete-before-develop-match assertion, push failure metadata/slot dirty test. +- [ ] `services/core/README.md`: document first authoring response, push-only request, and develop match as separate stages. + +테스트 작성: + +- Required scheduler unit tests for success and push failure. +- Required authoring helper tests for exact command and metadata preservation. +- No DB migration test required. + +중간 검증: + +```sh +cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/... +``` + +Expected: pass with push-only success/failure coverage. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/authoring/request.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/authoring/request_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/authoring/result.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/authoring/result_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/scheduler/jobs.go` | AUTHORING_RUNTIME-1 | +| `services/core/internal/scheduler/jobs_test.go` | AUTHORING_RUNTIME-1 | +| `services/core/README.md` | AUTHORING_RUNTIME-1 | + +## 최종 검증 + +```sh +cd services/core && go test -count=1 ./internal/authoring/... ./internal/scheduler/... ./internal/adapters/openai/... ./internal/agent/... ./internal/adapters/a2a/... +``` + +Expected: pass; Go test cache is not acceptable because scheduler behavior changed. + +```sh +git diff --check +``` + +Expected: no output. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/services/core/Makefile b/services/core/Makefile index 63d3b64..0736780 100644 --- a/services/core/Makefile +++ b/services/core/Makefile @@ -10,11 +10,12 @@ export MODEL_BASE_URL ?= http://192.168.0.91:11434 export MODEL_API_KEY ?= ollama export MODEL_NAME ?= qwen3.6:35b-a3b-bf16 export MODEL_CONTEXT_SIZE ?= 262144 -export MODEL_TIMEOUT_SEC ?= 300 +export MODEL_TIMEOUT_SEC ?= 900 export A2A_EDGE_URL ?= export A2A_AGENT_URL ?= export A2A_TIMEOUT_SEC ?= 300 export WORKFLOW_TASK_TIMEOUT_SEC ?= 300 +export AUTHORING_STALE_AFTER_SEC ?= 1200 export GOOSE_DRIVER ?= postgres export GOOSE_DBSTRING ?= $(DATABASE_URL) export OUTPUT ?= .build/nomadcode-core diff --git a/services/core/README.md b/services/core/README.md index 8a3f9d6..58caf95 100644 --- a/services/core/README.md +++ b/services/core/README.md @@ -86,7 +86,7 @@ MODEL_BASE_URL="http://toki-labs.com:18083/v1" \ MODEL_API_KEY="" \ MODEL_NAME="codex" \ MODEL_CONTEXT_SIZE="0" \ -MODEL_TIMEOUT_SEC="300" \ +MODEL_TIMEOUT_SEC="900" \ ./bin/run ``` @@ -242,9 +242,18 @@ NomadCode Core의 비동기 작업 재시도 및 타임아웃 처리는 다음 - **단일 시도 타임아웃 (Timeout wraps a single worker execution attempt)**: - 개별 작업 실행 시도는 `WORKFLOW_TASK_TIMEOUT_SEC` (기본값: 300초) 설정 범위 내에서 래핑되어 실행됩니다. - 이 제한을 초과하면 단일 실행이 실패한 것으로 간주하여 에러를 반환하고, 작업 상태를 `failed`로 변경합니다. 이때 실패 분류 메타데이터로 `failure_type=timeout`, `status_reason=timeout`이 기록됩니다. + - **예외 — Plane-origin authoring 작업**: checkout slot이 있는 Plane-origin authoring 태스크는 `WORKFLOW_TASK_TIMEOUT_SEC` 제한을 받지 않습니다. IOP Responses 호출은 10분 이상 실행될 수 있으며, 실행 범위는 `MODEL_TIMEOUT_SEC` (기본값: 900초)로만 제어됩니다. 진행 상태는 `authoring_run_state`와 `authoring_run_updated_at` 메타데이터로 관찰합니다. - **최대 시도 횟수 (Max Attempts)**: - River 비동기 작업 큐는 실패한 작업 시도를 최대 `workflow.DefaultTaskMaxAttempts` (기본값: 3회)까지 자동으로 다시 시도합니다. - 각 시도가 시작될 때마다 작업 메타데이터의 `attempt` 카운트가 1씩 증가하여 기록되며, 실패 시 메타데이터의 `retryable` 필드에 남은 재시도 가능 여부(`attempt < DefaultTaskMaxAttempts`)가 기록됩니다. - **재-Enqueue 가능 여부 (Re-Enqueue Behavior)**: - 최종적으로 실패 상태(`failed`)인 작업은 다시 `queued`로 Enqueue하여 다시 처음부터 실행할 수 있습니다. - 반면 완료(`completed`) 또는 취소(`canceled`)된 터미널 상태의 작업은 다시 시작(Restart/Re-enqueue)할 수 없습니다. +- **Plane-origin authoring stale 판단 (Authoring Stale Detection)**: + - authoring 작업의 stale 여부는 `authoring_run_updated_at` 메타데이터 타임스탬프와 `AUTHORING_STALE_AFTER_SEC` (기본값: 1200초) 기준으로 판단합니다. + - 실행 중인 authoring 작업은 `authoring_run_state=in_progress`와 `authoring_run_updated_at`이 함께 기록됩니다. 이 타임스탬프가 `AUTHORING_STALE_AFTER_SEC`를 초과하면 stale로 관찰됩니다. + - queue 대기 중인 authoring 작업은 `task.UpdatedAt`을 기준 타임스탬프로 사용하여 동일한 `AUTHORING_STALE_AFTER_SEC` 임계값에 비교할 수 있습니다. queue 대기 중에는 `WORKFLOW_TASK_TIMEOUT_SEC`가 적용되지 않으므로 조기 실패가 발생하지 않습니다. + - stale로 판단된 작업에 대한 조치(알림, 재-Enqueue 등)는 후속 모니터링 컴포넌트의 책임이며, 이 설정은 판단 기준만 제공합니다. +- **retry 메타데이터 (Retry Metadata)**: + - 모든 실패 작업(generic 및 authoring)은 `FailTaskWithMetadata` 경로를 통해 `retryable` 메타데이터를 기록합니다. `retryable=true`이면 `attempt < DefaultTaskMaxAttempts`이므로 River가 다음 시도를 스케줄링합니다. + - authoring 태스크 실패 시 `authoring_run_state=failed`, `authoring_failure_type`, `authoring_failure_category`, `authoring_run_updated_at`, `retryable`이 함께 기록됩니다. diff --git a/services/core/bin/run b/services/core/bin/run index d5638de..b1da5c8 100755 --- a/services/core/bin/run +++ b/services/core/bin/run @@ -14,8 +14,9 @@ default_redis_key_prefix="nomadcode-core:local" default_model_base_url="http://192.168.0.91:11434" default_model_name="qwen3.6:35b-a3b-bf16" default_model_context_size="262144" -default_model_timeout_sec="300" +default_model_timeout_sec="900" default_a2a_timeout_sec="300" +default_authoring_stale_after_sec="1200" # code-server exports a shared DATABASE_URL; remap that default to this project's local DB. if [[ "${DATABASE_URL:-}" == "$legacy_code_server_database_url" ]]; then export DATABASE_URL="$default_database_url" @@ -36,5 +37,6 @@ export MODEL_NAME="${MODEL_NAME:-$default_model_name}" export MODEL_CONTEXT_SIZE="${MODEL_CONTEXT_SIZE:-$default_model_context_size}" export MODEL_TIMEOUT_SEC="${MODEL_TIMEOUT_SEC:-$default_model_timeout_sec}" export A2A_TIMEOUT_SEC="${A2A_TIMEOUT_SEC:-$default_a2a_timeout_sec}" +export AUTHORING_STALE_AFTER_SEC="${AUTHORING_STALE_AFTER_SEC:-$default_authoring_stale_after_sec}" exec go run ./cmd/server "$@" diff --git a/services/core/docker-compose.yml b/services/core/docker-compose.yml index 23bdac2..1d84c24 100644 --- a/services/core/docker-compose.yml +++ b/services/core/docker-compose.yml @@ -15,11 +15,12 @@ services: MODEL_API_KEY: ${MODEL_API_KEY:-} MODEL_NAME: ${MODEL_NAME:-codex} MODEL_CONTEXT_SIZE: ${MODEL_CONTEXT_SIZE:-0} - MODEL_TIMEOUT_SEC: ${MODEL_TIMEOUT_SEC:-300} + MODEL_TIMEOUT_SEC: ${MODEL_TIMEOUT_SEC:-900} A2A_EDGE_URL: ${A2A_EDGE_URL:-} A2A_AGENT_URL: ${A2A_AGENT_URL:-} A2A_TOKEN: ${A2A_TOKEN:-} A2A_TIMEOUT_SEC: ${A2A_TIMEOUT_SEC:-300} + AUTHORING_STALE_AFTER_SEC: ${AUTHORING_STALE_AFTER_SEC:-1200} MATTERMOST_BASE_URL: "" MATTERMOST_TOKEN: "" PLANE_BASE_URL: ${PLANE_BASE_URL:-} diff --git a/services/core/internal/adapters/openai/client.go b/services/core/internal/adapters/openai/client.go index 13d6d34..48e45bc 100644 --- a/services/core/internal/adapters/openai/client.go +++ b/services/core/internal/adapters/openai/client.go @@ -17,7 +17,7 @@ import ( ) const ( - defaultTimeoutSec = 300 + defaultTimeoutSec = 900 responsesPath = "/v1/responses" ) diff --git a/services/core/internal/adapters/openai/client_test.go b/services/core/internal/adapters/openai/client_test.go index 298cda3..02eabe2 100644 --- a/services/core/internal/adapters/openai/client_test.go +++ b/services/core/internal/adapters/openai/client_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/nomadcode/nomadcode-core/internal/model" ) @@ -195,6 +196,14 @@ func TestGenerateExtractsOutputContent(t *testing.T) { } } +func TestNewClientDefaultTimeout(t *testing.T) { + client := NewClient(Config{BaseURL: "http://localhost", Model: "m"}, nil) + want := time.Duration(defaultTimeoutSec) * time.Second + if client.httpClient.Timeout != want { + t.Fatalf("default timeout: got %v, want %v", client.httpClient.Timeout, want) + } +} + func TestResponsesURL(t *testing.T) { tests := map[string]string{ "http://localhost:11434": "http://localhost:11434/v1/responses", diff --git a/services/core/internal/config/config.go b/services/core/internal/config/config.go index b6c03f5..1c0b526 100644 --- a/services/core/internal/config/config.go +++ b/services/core/internal/config/config.go @@ -37,6 +37,7 @@ type Config struct { JiraEmail string JiraAPIToken string WorkflowTaskTimeoutSec int + AuthoringStaleAfterSec int ProtoSocketPath string ProtoSocketHeartbeatIntervalSec int ProtoSocketHeartbeatWaitSec int @@ -83,7 +84,7 @@ func Load() Config { ModelAPIKey: os.Getenv("MODEL_API_KEY"), ModelName: getEnv("MODEL_NAME", "codex"), ModelContextSize: getEnvInt("MODEL_CONTEXT_SIZE", 0), - ModelTimeoutSec: getEnvInt("MODEL_TIMEOUT_SEC", 300), + ModelTimeoutSec: getEnvInt("MODEL_TIMEOUT_SEC", 900), A2AEdgeURL: firstEnv("A2A_EDGE_URL", "A2A_AGENT_URL"), A2AAgentURL: firstEnv("A2A_AGENT_URL", "A2A_EDGE_URL"), A2AToken: os.Getenv("A2A_TOKEN"), @@ -103,6 +104,7 @@ func Load() Config { JiraEmail: os.Getenv("JIRA_EMAIL"), JiraAPIToken: os.Getenv("JIRA_API_TOKEN"), WorkflowTaskTimeoutSec: getEnvInt("WORKFLOW_TASK_TIMEOUT_SEC", 300), + AuthoringStaleAfterSec: getEnvInt("AUTHORING_STALE_AFTER_SEC", 1200), ProtoSocketPath: getEnv("PROTO_SOCKET_PATH", "/proto-socket"), ProtoSocketHeartbeatIntervalSec: getEnvInt("PROTO_SOCKET_HEARTBEAT_INTERVAL_SEC", 30), ProtoSocketHeartbeatWaitSec: getEnvInt("PROTO_SOCKET_HEARTBEAT_WAIT_SEC", 10), diff --git a/services/core/internal/config/config_test.go b/services/core/internal/config/config_test.go index 283a7d5..8141c72 100644 --- a/services/core/internal/config/config_test.go +++ b/services/core/internal/config/config_test.go @@ -44,7 +44,7 @@ func TestLoadModelAndA2ADefaults(t *testing.T) { if cfg.ModelContextSize != 0 { t.Fatalf("ModelContextSize: got %d", cfg.ModelContextSize) } - if cfg.ModelTimeoutSec != 300 { + if cfg.ModelTimeoutSec != 900 { t.Fatalf("ModelTimeoutSec: got %d", cfg.ModelTimeoutSec) } if cfg.A2ATimeoutSec != 300 { @@ -52,6 +52,29 @@ func TestLoadModelAndA2ADefaults(t *testing.T) { } } +func TestConfigLoadsAuthoringStaleAfterSecDefault(t *testing.T) { + cfg := Load() + if cfg.AuthoringStaleAfterSec != 1200 { + t.Fatalf("AuthoringStaleAfterSec default: got %d, want 1200", cfg.AuthoringStaleAfterSec) + } +} + +func TestConfigLoadsAuthoringStaleAfterSecOverride(t *testing.T) { + t.Setenv("AUTHORING_STALE_AFTER_SEC", "3600") + cfg := Load() + if cfg.AuthoringStaleAfterSec != 3600 { + t.Fatalf("AuthoringStaleAfterSec override: got %d, want 3600", cfg.AuthoringStaleAfterSec) + } +} + +func TestConfigLoadsModelTimeoutOverride(t *testing.T) { + t.Setenv("MODEL_TIMEOUT_SEC", "1800") + cfg := Load() + if cfg.ModelTimeoutSec != 1800 { + t.Fatalf("ModelTimeoutSec override: got %d, want 1800", cfg.ModelTimeoutSec) + } +} + func TestConfigLoadsWorkflowTaskTimeout(t *testing.T) { cfg := Load() if cfg.WorkflowTaskTimeoutSec != 300 { diff --git a/services/core/internal/scheduler/jobs.go b/services/core/internal/scheduler/jobs.go index e7d5605..4d0a656 100644 --- a/services/core/internal/scheduler/jobs.go +++ b/services/core/internal/scheduler/jobs.go @@ -91,9 +91,14 @@ func (w *TaskWorker) Work(ctx context.Context, job *river.Job[TaskJobArgs]) erro runCtx := ctx if w.RunTimeout > 0 { - var cancel context.CancelFunc - runCtx, cancel = context.WithTimeout(ctx, w.RunTimeout) - defer cancel() + // Plane-origin authoring tasks rely on MODEL_TIMEOUT_SEC for their + // execution bound; applying a generic worker deadline would prematurely + // cancel IOP Responses calls that run for 10+ minutes. + if _, isAuthoring := authoring.TaskCheckoutMetadata(task); !isAuthoring { + var cancel context.CancelFunc + runCtx, cancel = context.WithTimeout(ctx, w.RunTimeout) + defer cancel() + } } result, message, err := w.runTask(runCtx, task) diff --git a/services/core/internal/scheduler/jobs_test.go b/services/core/internal/scheduler/jobs_test.go index 0276268..a531488 100644 --- a/services/core/internal/scheduler/jobs_test.go +++ b/services/core/internal/scheduler/jobs_test.go @@ -975,6 +975,51 @@ func TestWorkEmitsFailedEvent(t *testing.T) { } } +func TestWorkAuthoringSkipsGenericRunTimeout(t *testing.T) { + // done is closed after RunTimeout would have fired. If the scheduler applied + // RunTimeout to authoring tasks the context would expire and this test would + // fail with DeadlineExceeded. + done := make(chan struct{}) + fakeModel := fakeModelClient{ + generate: func(ctx context.Context, _ model.GenerateInput) (model.GenerateResult, error) { + select { + case <-done: + return model.GenerateResult{Text: "authoring done", Model: "m"}, nil + case <-ctx.Done(): + return model.GenerateResult{}, ctx.Err() + } + }, + } + + fakeLifecycle := &fakeTaskLifecycle{ + task: storage.Task{ + ID: "task-auth-timeout", + Source: "plane", + Status: "pending", + Metadata: checkoutTaskMeta("/home/user/workspace/nomadcode/slots/000", "develop"), + }, + } + + worker := &TaskWorker{ + Lifecycle: fakeLifecycle, + Model: fakeModel, + RunTimeout: 1 * time.Millisecond, + } + + go func() { + time.Sleep(20 * time.Millisecond) + close(done) + }() + + job := &river.Job[TaskJobArgs]{Args: TaskJobArgs{TaskID: "task-auth-timeout"}} + if err := worker.Work(context.Background(), job); err != nil { + t.Fatalf("authoring task with short RunTimeout returned error: %v", err) + } + if len(fakeLifecycle.failed) != 0 { + t.Fatalf("expected authoring task not to be failed by generic RunTimeout, got failed: %v", fakeLifecycle.failed) + } +} + func TestWorkKeepsAuthoringTaskRunningUntilDevelopMatch(t *testing.T) { fakeModel := fakeModelClient{ generate: func(_ context.Context, _ model.GenerateInput) (model.GenerateResult, error) { diff --git a/services/core/internal/scheduler/stale.go b/services/core/internal/scheduler/stale.go new file mode 100644 index 0000000..58600be --- /dev/null +++ b/services/core/internal/scheduler/stale.go @@ -0,0 +1,37 @@ +package scheduler + +import ( + "time" + + "github.com/nomadcode/nomadcode-core/internal/workflow" +) + +// IsAuthoringStale reports whether an authoring observation timestamp +// indicates a stale state. For running authoring tasks, pass the parsed +// authoring_run_updated_at value; for queued tasks, pass task.UpdatedAt. +// thresholdSec is the AUTHORING_STALE_AFTER_SEC config value (default 1200). +// Returns false when observedAt is zero or thresholdSec is non-positive. +func IsAuthoringStale(observedAt time.Time, thresholdSec int) bool { + if observedAt.IsZero() || thresholdSec <= 0 { + return false + } + return time.Since(observedAt) > time.Duration(thresholdSec)*time.Second +} + +// ParseAuthoringRunUpdatedAt parses the authoring_run_updated_at RFC3339 +// string from task metadata. Returns zero time on missing or parse error. +func ParseAuthoringRunUpdatedAt(meta map[string]any) time.Time { + v, ok := meta[workflow.MetadataKeyAuthoringRunUpdatedAt] + if !ok { + return time.Time{} + } + s, ok := v.(string) + if !ok || s == "" { + return time.Time{} + } + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return time.Time{} + } + return t +} diff --git a/services/core/internal/scheduler/stale_test.go b/services/core/internal/scheduler/stale_test.go new file mode 100644 index 0000000..d3dfa5b --- /dev/null +++ b/services/core/internal/scheduler/stale_test.go @@ -0,0 +1,81 @@ +package scheduler + +import ( + "testing" + "time" + + "github.com/nomadcode/nomadcode-core/internal/workflow" +) + +func TestIsAuthoringStaleReturnsTrueWhenExpired(t *testing.T) { + old := time.Now().Add(-2000 * time.Second) + if !IsAuthoringStale(old, 1200) { + t.Fatal("expected stale for timestamp older than threshold") + } +} + +func TestIsAuthoringStaleReturnsFalseWhenRecent(t *testing.T) { + recent := time.Now().Add(-60 * time.Second) + if IsAuthoringStale(recent, 1200) { + t.Fatal("expected not stale for recent timestamp") + } +} + +func TestIsAuthoringStaleReturnsFalseForZeroTime(t *testing.T) { + if IsAuthoringStale(time.Time{}, 1200) { + t.Fatal("expected false for zero time") + } +} + +func TestIsAuthoringStaleReturnsFalseForNonPositiveThreshold(t *testing.T) { + old := time.Now().Add(-9999 * time.Second) + if IsAuthoringStale(old, 0) { + t.Fatal("expected false when threshold is zero (feature disabled)") + } + if IsAuthoringStale(old, -1) { + t.Fatal("expected false when threshold is negative") + } +} + +func TestParseAuthoringRunUpdatedAtReturnsTimeFromMeta(t *testing.T) { + now := time.Now().UTC().Truncate(time.Second) + meta := map[string]any{ + workflow.MetadataKeyAuthoringRunUpdatedAt: now.Format(time.RFC3339), + } + got := ParseAuthoringRunUpdatedAt(meta) + if !got.Equal(now) { + t.Fatalf("got %v, want %v", got, now) + } +} + +func TestParseAuthoringRunUpdatedAtReturnsZeroOnMissing(t *testing.T) { + if !ParseAuthoringRunUpdatedAt(map[string]any{}).IsZero() { + t.Fatal("expected zero time when key is absent") + } +} + +func TestParseAuthoringRunUpdatedAtReturnsZeroOnBadFormat(t *testing.T) { + meta := map[string]any{ + workflow.MetadataKeyAuthoringRunUpdatedAt: "not-a-date", + } + if !ParseAuthoringRunUpdatedAt(meta).IsZero() { + t.Fatal("expected zero time for unparseable date") + } +} + +// TestIsAuthoringStaleAppliesToQueueWait verifies that the stale helper can be +// applied to task.UpdatedAt for queue-waiting authoring tasks: if a task has +// been queued for longer than AUTHORING_STALE_AFTER_SEC it is considered stale. +func TestIsAuthoringStaleAppliesToQueueWait(t *testing.T) { + // Simulate a task queued 25 minutes ago (threshold default 1200s = 20min). + queuedAt := time.Now().Add(-25 * time.Minute) + if !IsAuthoringStale(queuedAt, 1200) { + t.Fatal("expected stale for task queued longer than threshold") + } + + // A task queued 10 minutes ago is not yet stale. + recent := time.Now().Add(-10 * time.Minute) + if IsAuthoringStale(recent, 1200) { + t.Fatal("expected not stale for task queued less than threshold") + } +}