diff --git a/agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md b/agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md index bc84817..cd9f18e 100644 --- a/agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md +++ b/agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md @@ -41,14 +41,14 @@ Gito의 초기 작업자가 경계와 명령을 바로 이해할 수 있는 repo - [x] [domain-packages] core domain, gitengine, provider, events, storage, worker, agentshell package boundary를 만든다. 검증: `cd services/core && go test ./...` PASS. - [x] [contracts] proto-socket 우선 transport, REST 예외, gRPC 제외, event/DTO 후보를 contracts note에 기록한다. - [x] [readme] README를 사람과 AI가 함께 읽는 작업 진입점으로 보강한다. -- [ ] [protosocket-surface] proto-socket placeholder를 channel/action registry scaffold로 전환한다. 검증: proto-socket route test가 operation/event channel placeholder를 확인한다. +- [x] [protosocket-surface] proto-socket placeholder를 channel/action registry scaffold로 전환한다. 검증: proto-socket route test가 operation/event channel placeholder를 확인한다. - [ ] [storage-boundary] Postgres migration과 storage boundary를 실제 store interface 기준으로 연결한다. 검증: storage package test가 repo/operation/event 기본 CRUD 또는 interface contract를 확인한다. ## 완료 리뷰 - 상태: 없음 - 요청일: 없음 -- 완료 근거: 일부 scaffold와 문서 기준선은 생성되었지만 proto-socket surface와 storage boundary 연결이 남아 있다. +- 완료 근거: proto-socket surface는 channel/action registry scaffold로 전환되었고, storage boundary 연결이 남아 있다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 @@ -69,4 +69,3 @@ Gito의 초기 작업자가 경계와 명령을 바로 이해할 수 있는 repo - 선행 작업: 없음 - 후속 작업: Repo Registry and Workspace Lease - 확인 필요: 없음 - diff --git a/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G05_0.log b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G05_0.log new file mode 100644 index 0000000..365e06c --- /dev/null +++ b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G05_0.log @@ -0,0 +1,178 @@ + + +# Code Review Reference - STORAGE + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts 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 the needed 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-13 +task=m-runtime-scaffold-and-contract-baseline, plan=0, tag=STORAGE + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md` +- Task ids: + - `storage-boundary`: Postgres migration과 storage boundary를 실제 store interface 기준으로 연결한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G05.md` → `code_review_local_G05_N.log`, `PLAN-local-G05.md` → `plan_local_G05_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-runtime-scaffold-and-contract-baseline/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-runtime-scaffold-and-contract-baseline`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [STORAGE-1] Store boundary interface 작성 | [x] | +| [STORAGE-2] Migration/interface contract test 추가 | [x] | + +## 구현 체크리스트 + +- [x] `services/core/internal/storage/storage.go`를 repo/workspace/operation/event store interface와 boundary constructor 기준으로 교체한다. +- [x] `services/core/internal/storage/storage_test.go`를 추가해 interface contract와 migration support를 검증한다. 검증: `cd services/core && go test -count=1 ./internal/storage` PASS. +- [x] 전체 core baseline을 확인한다. 검증: `cd services/core && go test -count=1 ./...`와 `cd services/core && go vet ./...` PASS. +- [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_local_G05_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_local_G05_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-runtime-scaffold-and-contract-baseline/`를 `agent-task/archive/YYYY/MM/m-runtime-scaffold-and-contract-baseline/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +- `TestInitialMigrationSupportsStoreBoundary`에서 plan의 `required` 슬라이스 항목 중 6번째(`"operation_events.operation_id" + ""`)는 문법적으로 의미 없는 빈 연결이었다. 테이블 스니펫 5개와 컬럼 스니펫 `"operation_id"` 체크를 별도 슬라이스로 분리하여 명확하게 정리했다. 검증 결과는 동일하다. + +## 주요 설계 결정 + +- `Boundary`는 `Store` interface를 구현하는 concrete type이며, 각 sub-store는 생성 시점에 주입된다. DB driver나 SQL 구현은 포함하지 않았다. +- `Ping`은 nil context와 nil sub-store 주입 여부만 검사한다. 실제 DB 연결 확인은 후속 Milestone 범위다. +- `OperationEvent`는 `OperationID string`과 `Event events.Event` 두 필드만 포함해 plan 지침을 따랐다. +- 컴파일 타임 interface 적합성 검사(`var _ Store = (*Boundary)(nil)` 및 각 fake에 대한 `var _ XxxStore = (*fakeXxx)(nil)`)를 test 파일에 명시해 future drift를 방지했다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `storage.go`가 migration에 존재하는 repo/workspace/operation/event 경계를 반영하는지 확인한다. +- 새 테스트가 외부 PostgreSQL이나 secret 없이 deterministic하게 실행되는지 확인한다. +- `Roadmap Targets`의 `storage-boundary` 외 task를 임의로 완료 처리하지 않았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### STORAGE-1 중간 검증 +```sh +$ cd services/core && go test -count=1 ./internal/storage +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.002s +``` + +### STORAGE-2 중간 검증 +```sh +$ cd services/core && go test -count=1 ./internal/storage +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.002s +``` + +### 최종 검증 +```sh +$ cd services/core && go test -count=1 ./internal/storage +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.002s + +$ cd services/core && go test -count=1 ./... +? git.toki-labs.com/toki/gito/services/core/cmd/server [no test files] +? git.toki-labs.com/toki/gito/services/core/cmd/shell [no test files] +? git.toki-labs.com/toki/gito/services/core/cmd/worker [no test files] +? git.toki-labs.com/toki/gito/services/core/internal/agentshell [no test files] +ok git.toki-labs.com/toki/gito/services/core/internal/config 0.002s +ok git.toki-labs.com/toki/gito/services/core/internal/controlplane 0.003s +? git.toki-labs.com/toki/gito/services/core/internal/core [no test files] +? git.toki-labs.com/toki/gito/services/core/internal/events [no test files] +ok git.toki-labs.com/toki/gito/services/core/internal/gitengine 0.002s +? git.toki-labs.com/toki/gito/services/core/internal/provider [no test files] +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.003s +? git.toki-labs.com/toki/gito/services/core/internal/worker [no test files] + +$ cd services/core && go vet ./... +(no output — exit 0) +``` + +--- + +## Ownership + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) | +| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS | +| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only | +| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only; final checkbox is mandatory before saving | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | +| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required to proceed; do not ask the user directly during implementation; when filled, include exact decision, evidence, commands/output, why automatic follow-up cannot resolve it, and resume condition | +| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan | +| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry | +| 코드리뷰 결과 | Review agent appends | Not included in stub | + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - correctness: Pass + - completeness: Fail + - test coverage: Fail + - API contract: Pass + - code quality: Pass + - plan deviation: Fail + - verification trust: Pass +- 발견된 문제: + - Required: `services/core/internal/storage/storage_test.go:67`의 `TestBoundaryContract`는 accessor들이 nil이 아닌지만 확인하고, 계획에 명시된 "`New(...)`가 주입받은 store를 그대로 반환하는지"를 검증하지 않는다. 이 상태에서는 `New`가 다른 non-nil sub-store를 반환해도 테스트가 통과해 boundary injection contract가 고정되지 않는다. `repos := &fakeRepos{}`처럼 각 fake 값을 지역 변수로 만든 뒤 `storage.New(repos, leases, ops, opEvents)`를 호출하고, `b.Repos() == repos`, `b.WorkspaceLeases() == leases`, `b.Operations() == ops`, `b.OperationEvents() == opEvents`를 각각 assertion한다. +- 다음 단계: user-review gate는 트리거하지 않는다. `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.md` 후속 루프로 누락된 contract assertion만 보완한다. diff --git a/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G06_1.log b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G06_1.log new file mode 100644 index 0000000..c9b83e3 --- /dev/null +++ b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G06_1.log @@ -0,0 +1,170 @@ + + +# Code Review Reference - REVIEW_STORAGE + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts 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 the needed 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-13 +task=m-runtime-scaffold-and-contract-baseline, plan=1, tag=REVIEW_STORAGE + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md` +- Task ids: + - `storage-boundary`: Postgres migration과 storage boundary를 실제 store interface 기준으로 연결한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G06.md` -> `code_review_local_G06_N.log`, `PLAN-local-G06.md` -> `plan_local_G06_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-runtime-scaffold-and-contract-baseline/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-runtime-scaffold-and-contract-baseline`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_STORAGE-1] Boundary injection identity assertion 추가 | [x] | + +## 구현 체크리스트 + +- [x] `TestBoundaryContract`에서 `storage.New(...)`에 주입한 각 fake sub-store가 accessor로 동일하게 반환되는지 identity assertion을 추가한다. 검증: `cd services/core && go test -count=1 ./internal/storage` PASS. +- [x] 전체 core baseline을 재확인한다. 검증: `cd services/core && go test -count=1 ./...`와 `cd services/core && go vet ./...` PASS. +- [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_local_G06_1.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_local_G06_1.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-runtime-scaffold-and-contract-baseline/`를 `agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-runtime-scaffold-and-contract-baseline`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +- 계획과 동일하게 구현했다. 변경 없음. + +## 주요 설계 결정 + +- `TestBoundaryContract`에서 `newBoundary()` 헬퍼를 그대로 두되, 테스트 함수 내부에서 각 fake를 지역 변수로 선언한 뒤 `storage.New(repos, leases, ops, opEvents)`를 직접 호출해 identity 비교가 가능하게 했다. 기존 nil 여부 확인은 identity 비교로 대체됐고(주입된 값이 동일하면 nil이 아님도 암묵적으로 보장됨), `Ping` 정상 동작 확인은 유지했다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `TestBoundaryContract`가 nil 여부뿐 아니라 `storage.New(...)`에 주입한 정확한 fake store identity를 검증하는지 확인한다. +- 후속 작업이 `services/core/internal/storage/storage_test.go` 범위를 넘지 않았는지 확인한다. +- 검증 결과의 `go test -count=1 ./internal/storage`, `go test -count=1 ./...`, `go vet ./...` 출력이 현재 코드와 일치하는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_STORAGE-1 중간 검증 +```sh +$ cd services/core && go test -count=1 ./internal/storage +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.002s +``` + +### 최종 검증 +```sh +$ cd services/core && go test -count=1 ./internal/storage +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.002s + +$ cd services/core && go test -count=1 ./... +? git.toki-labs.com/toki/gito/services/core/cmd/server [no test files] +? git.toki-labs.com/toki/gito/services/core/cmd/shell [no test files] +? git.toki-labs.com/toki/gito/services/core/cmd/worker [no test files] +? git.toki-labs.com/toki/gito/services/core/internal/agentshell [no test files] +ok git.toki-labs.com/toki/gito/services/core/internal/config 0.003s +ok git.toki-labs.com/toki/gito/services/core/internal/controlplane 0.003s +? git.toki-labs.com/toki/gito/services/core/internal/core [no test files] +? git.toki-labs.com/toki/gito/services/core/internal/events [no test files] +ok git.toki-labs.com/toki/gito/services/core/internal/gitengine 0.002s +? git.toki-labs.com/toki/gito/services/core/internal/provider [no test files] +ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.003s +? git.toki-labs.com/toki/gito/services/core/internal/worker [no test files] + +$ cd services/core && go vet ./... +(no output — exit 0) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +## Ownership + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) | +| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS | +| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지; 구현 중 직접 질문은 금지; 필요하면 결정 항목, 근거, 명령 출력, 자동 후속 불가 이유, 재개 조건을 기록 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - correctness: Pass + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Pass +- 발견된 문제: 없음 +- 다음 단계: PASS로 `complete.log`를 작성하고 `agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/`로 task 디렉터리를 이동한다. task group이 `m-runtime-scaffold-and-contract-baseline`이므로 runtime completion metadata를 보고하며, roadmap 수정이나 `update-roadmap` 호출은 수행하지 않는다. diff --git a/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/complete.log b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/complete.log new file mode 100644 index 0000000..bc96666 --- /dev/null +++ b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/complete.log @@ -0,0 +1,43 @@ +# Complete - m-runtime-scaffold-and-contract-baseline + +## 완료 일시 + +2026-06-13 + +## 요약 + +storage boundary interface와 migration-backed contract test 기준선을 2회 리뷰 루프로 완료했다. 최종 판정은 PASS다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_local_G05_0.log` | `code_review_local_G05_0.log` | FAIL | `TestBoundaryContract`가 `New(...)` injection identity를 검증하지 않아 후속 테스트 보강이 필요했다. | +| `plan_local_G06_1.log` | `code_review_local_G06_1.log` | PASS | 각 sub-store accessor가 주입받은 fake store를 그대로 반환하는지 검증했고 필수 Go 검증이 통과했다. | + +## 구현/정리 내용 + +- `services/core/internal/storage/storage.go`를 repo/workspace/operation/event store interface와 `Boundary` constructor 기준으로 정리했다. +- `services/core/internal/storage/storage_test.go`를 추가해 compile-time interface contract, constructor injection identity, `Ping` guard, migration table/column support를 검증했다. +- 코드리뷰 중 storage 파일에 gofmt를 적용했다. + +## 최종 검증 + +- `cd services/core && go test -count=1 ./internal/storage` - PASS; `ok git.toki-labs.com/toki/gito/services/core/internal/storage 0.002s` +- `cd services/core && go test -count=1 ./...` - PASS; core 전체 패키지 테스트 통과, storage 포함. +- `cd services/core && go vet ./...` - PASS; 출력 없음, exit 0. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md` +- Completed task ids: + - `storage-boundary`: PASS; evidence=`agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G06_1.log`, `agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/code_review_local_G06_1.log`; verification=`cd services/core && go test -count=1 ./internal/storage`, `cd services/core && go test -count=1 ./...`, `cd services/core && go vet ./...` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G05_0.log b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G05_0.log new file mode 100644 index 0000000..4adefd7 --- /dev/null +++ b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G05_0.log @@ -0,0 +1,217 @@ + + +# Plan - STORAGE + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현자는 아래 검증을 실행하고, 실제 stdout/stderr를 리뷰 스텁에 기록하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 구현이 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret, 또는 scope conflict에 막히면 사용자에게 직접 묻지 말고 리뷰 스텁의 `사용자 리뷰 요청` 섹션에 결정 필요 사항과 근거를 채운 뒤 중단한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 evidence gap은 사용자 리뷰 요청이 아니다. + +## 배경 + +현재 `storage` 패키지는 `Ping`만 있는 placeholder라 migration의 repo/workspace/operation/event 원장과 코드 경계가 연결되어 있지 않다. 이 마일스톤의 남은 `storage-boundary` Task는 실제 DB 구현 전체가 아니라, 다음 repo registry/lease Milestone이 의존할 수 있는 store interface와 contract test 기준선을 닫는 작업이다. 외부 PostgreSQL 서비스나 driver 추가 없이 `database/sql` 이전의 경계와 migration 일치성부터 고정한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 중 직접 사용자에게 질문하거나 chat 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. code-review가 blocker 정당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md` +- Task ids: + - `storage-boundary`: Postgres migration과 storage boundary를 실제 store interface 기준으로 연결한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-roadmap/current.md` +- `agent-roadmap/phase/control-plane-foundation/PHASE.md` +- `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.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` +- `.gitignore` +- `services/core/internal/storage/storage.go` +- `services/core/internal/core/model.go` +- `services/core/internal/events/events.go` +- `services/core/migrations/00001_initial.sql` + +### 테스트 환경 규칙 + +- 선택한 `test_env`: `local`. +- `agent-test/local/rules.md`는 존재하고 읽었다. 적용 명령은 core baseline인 `cd services/core && go test ./...`, `cd services/core && go vet ./...`이다. +- `agent-test/local/core-smoke.md`는 읽었지만 `적용 범위`, `필수 검증`, `판정 기준`이 `<확인 필요>` skeleton이다. 따라서 구체 명령은 `agent-test/local/rules.md` baseline과 `services/core/go.mod` 기준 Go command로 fallback한다. +- `agent-test/local/workspace-ops-smoke.md`는 plan artifact 생성 때문에 읽었지만 skeleton이다. 별도 workspace-ops 검증 명령은 적용하지 않는다. +- Fresh storage contract 확인이 필요하므로 Go test cache는 핵심 명령에서 허용하지 않는다. `go test -count=1`을 사용한다. + +### 테스트 커버리지 공백 + +- `storage` 패키지는 현재 test file이 없고 `Store` interface/contract를 검증하지 않는다. +- migration은 repo/workspace/operation/event 테이블을 만들지만, storage boundary가 해당 테이블을 반영하는지 확인하는 테스트가 없다. +- 이번 plan은 `storage_test.go`를 추가해 interface contract와 migration support를 동시에 고정한다. + +### 심볼 참조 + +- 변경 후보: `storage.Store`, `storage.New`, `(*Store).Ping`. +- `rg --sort path "storage\\.New|storage\\.Store|\\bStore\\b|\\bPing\\(" services/core` 결과 외부 call site는 없고 `services/core/internal/storage/storage.go` 내부 정의만 있었다. +- 삭제/rename으로 인한 call-site update는 예상되지 않는다. + +### 분할 판단 + +- split decision policy를 평가했다. +- 단일 plan으로 둔다. 변경 범위가 `core` 도메인의 `storage` 패키지와 같은 패키지 테스트에 머물고, API foundation과 call-site rollout이 분리되어 있지 않다. +- storage interface와 contract test는 서로를 검증하는 한 단위라 별도 subtask로 나누면 실제 완료 판정이 인위적으로 쪼개진다. +- DB driver, 실제 PostgreSQL CRUD, repo registry workflow는 제외하므로 storage/security/concurrency 위험은 bounded local review로 감당 가능하다. + +### 범위 결정 근거 + +- 포함: `services/core/internal/storage/storage.go`, `services/core/internal/storage/storage_test.go`, 필요 시 `services/core/migrations/00001_initial.sql`의 최소 정합성 보정. +- 제외: 실제 PostgreSQL connection pool, driver dependency, SQL CRUD 구현, repo registry API, workspace lease allocation algorithm, operation worker execution, proto-socket handler wiring. +- 제외 이유: 이 마일스톤은 scaffold/contract baseline이고, 실제 repo/workspace 동작은 후속 `Repo Registry and Workspace Lease` Milestone 범위다. + +### 빌드 등급 + +- `local-G05`: storage 경계와 테스트 추가는 public-ish internal API 설계지만 call site가 없고 검증이 Go unit/vet으로 deterministic하다. + +## 구현 체크리스트 + +- [ ] `services/core/internal/storage/storage.go`를 repo/workspace/operation/event store interface와 boundary constructor 기준으로 교체한다. +- [ ] `services/core/internal/storage/storage_test.go`를 추가해 interface contract와 migration support를 검증한다. 검증: `cd services/core && go test -count=1 ./internal/storage` PASS. +- [ ] 전체 core baseline을 확인한다. 검증: `cd services/core && go test -count=1 ./...`와 `cd services/core && go vet ./...` PASS. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## [STORAGE-1] Store boundary interface 작성 + +### 문제 + +`services/core/internal/storage/storage.go:5-14`는 `Ping`만 가진 placeholder라 migration과 core model이 요구하는 repo/workspace/operation/event store 경계를 표현하지 못한다. + +```go +5 type Store struct{} +6 +7 func New() *Store { +8 return &Store{} +9 } +10 +11 func (s *Store) Ping(ctx context.Context) error { +12 _ = ctx +13 return nil +14 } +``` + +`services/core/migrations/00001_initial.sql:3-56`에는 `repos`, `workspace_leases`, `operations`, `operation_events`, `revision_cursors`가 이미 있다. + +### 해결 방법 + +`storage.go`를 실제 boundary contract로 바꾼다. 외부 DB driver는 추가하지 않는다. + +```go +import ( + "context" + + "git.toki-labs.com/toki/gito/services/core/internal/core" + "git.toki-labs.com/toki/gito/services/core/internal/events" +) + +type Store interface { + Ping(ctx context.Context) error + Repos() RepoStore + WorkspaceLeases() WorkspaceLeaseStore + Operations() OperationStore + OperationEvents() OperationEventStore +} + +type RepoStore interface { + SaveRepo(ctx context.Context, repo core.Repo) error + GetRepo(ctx context.Context, id string) (core.Repo, error) + ListRepos(ctx context.Context) ([]core.Repo, error) +} +``` + +`WorkspaceLeaseStore`, `OperationStore`, `OperationEventStore`도 같은 방식으로 `core.WorkspaceLease`, `core.Operation`, `events.Event`를 사용해 최소 CRUD 또는 append/list contract를 정의한다. `Boundary` concrete type과 `New(...) *Boundary` constructor는 주입받은 sub-store를 반환하며 `Ping`은 nil sub-store가 없는지 확인한다. + +### 수정 파일 및 체크리스트 + +- [ ] `services/core/internal/storage/storage.go`: `core`와 `events` import 추가. +- [ ] `services/core/internal/storage/storage.go`: `Store`, `RepoStore`, `WorkspaceLeaseStore`, `OperationStore`, `OperationEventStore` interface 추가. +- [ ] `services/core/internal/storage/storage.go`: `OperationEvent` value type이 필요하면 `OperationID string`과 `Event events.Event`만 포함한다. +- [ ] `services/core/internal/storage/storage.go`: `Boundary`와 `New(...) *Boundary` constructor 추가. +- [ ] `services/core/internal/storage/storage.go`: `Ping`은 `context.Context` nil 여부와 필수 sub-store 주입 여부만 검증한다. + +### 테스트 작성 + +`services/core/internal/storage/storage_test.go`에서 fake sub-store를 두고 `var _ Store = (*Boundary)(nil)`와 각 sub-store interface implementation을 compile-time로 검증한다. `New(...)`가 주입받은 store를 그대로 반환하는지도 확인한다. + +### 중간 검증 + +```sh +cd services/core && go test -count=1 ./internal/storage +``` + +기대 결과: `ok git.toki-labs.com/toki/gito/services/core/internal/storage`. + +## [STORAGE-2] Migration/interface contract test 추가 + +### 문제 + +`services/core/migrations/00001_initial.sql:3-56`의 테이블들은 storage interface의 backing schema지만 현재 테스트가 없다. 새 boundary가 migration에서 제공하지 않는 entity를 요구하거나, migration이 interface 핵심 테이블을 빠뜨려도 감지하지 못한다. + +### 해결 방법 + +`storage_test.go`에 migration support test를 추가한다. SQL parser를 새로 들이지 말고, 현재 repo의 goose migration 파일을 읽어 interface가 요구하는 table/key column snippet이 있는지만 deterministic하게 확인한다. + +```go +required := []string{ + "CREATE TABLE IF NOT EXISTS repos", + "CREATE TABLE IF NOT EXISTS workspace_leases", + "CREATE TABLE IF NOT EXISTS operations", + "CREATE TABLE IF NOT EXISTS operation_events", + "CREATE TABLE IF NOT EXISTS revision_cursors", +} +``` + +필요하면 `operation_events.operation_id`처럼 interface와 직접 맞물리는 column snippet도 추가한다. + +### 수정 파일 및 체크리스트 + +- [ ] `services/core/internal/storage/storage_test.go`: migration 파일 `../../migrations/00001_initial.sql`을 읽는 test 추가. +- [ ] `services/core/internal/storage/storage_test.go`: repo/workspace/operation/event/revision cursor table snippet 확인. +- [ ] `services/core/internal/storage/storage_test.go`: interface contract test와 migration support test가 같은 package에서 통과하도록 정리. + +### 테스트 작성 + +새 test 이름은 `TestBoundaryContract`와 `TestInitialMigrationSupportsStoreBoundary`로 둔다. + +### 중간 검증 + +```sh +cd services/core && go test -count=1 ./internal/storage +``` + +기대 결과: `ok git.toki-labs.com/toki/gito/services/core/internal/storage`. + +## 의존 관계 및 구현 순서 + +1. `STORAGE-1`에서 interface와 `Boundary`를 먼저 만든다. +2. `STORAGE-2`에서 같은 package test로 interface와 migration support를 검증한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +| --- | --- | +| `services/core/internal/storage/storage.go` | `STORAGE-1` | +| `services/core/internal/storage/storage_test.go` | `STORAGE-1`, `STORAGE-2` | +| `services/core/migrations/00001_initial.sql` | `STORAGE-2` only if a minimal schema mismatch is found | + +## 최종 검증 + +```sh +cd services/core && go test -count=1 ./internal/storage +cd services/core && go test -count=1 ./... +cd services/core && go vet ./... +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G06_1.log b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G06_1.log new file mode 100644 index 0000000..16b76e2 --- /dev/null +++ b/agent-task/archive/2026/06/m-runtime-scaffold-and-contract-baseline/plan_local_G06_1.log @@ -0,0 +1,97 @@ + + +# Plan - REVIEW_STORAGE + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현자는 아래 검증을 실행하고, 실제 stdout/stderr를 리뷰 스텁에 기록하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 구현이 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret, 또는 scope conflict에 막히면 사용자에게 직접 묻지 말고 리뷰 스텁의 `사용자 리뷰 요청` 섹션에 결정 필요 사항과 근거를 채운 뒤 중단한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 evidence gap은 사용자 리뷰 요청이 아니다. + +## 배경 + +이전 리뷰(`code_review_local_G05_0.log`)에서 storage boundary 구현 자체와 Go 검증은 통과했지만, 계획에 명시된 `New(...)` injection contract 테스트가 충분하지 않았다. 현재 `TestBoundaryContract`는 accessor 반환값이 nil이 아닌지만 확인하므로, constructor가 주입받은 sub-store가 아닌 다른 non-nil store를 반환해도 테스트가 통과할 수 있다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 중 직접 사용자에게 질문하거나 chat 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. code-review가 blocker 정당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-foundation/milestones/runtime-scaffold-and-contract-baseline.md` +- Task ids: + - `storage-boundary`: Postgres migration과 storage boundary를 실제 store interface 기준으로 연결한다. +- Completion mode: check-on-pass + +## 범위 결정 근거 + +- 포함: `services/core/internal/storage/storage_test.go`의 `TestBoundaryContract` assertion 보강. +- 제외: `storage.go` API 변경, migration 변경, 실제 PostgreSQL CRUD, driver dependency, repo registry API. +- 제외 이유: 실패 원인은 구현 기능이 아니라 계획된 contract assertion 누락이며, deterministic local test로 닫을 수 있다. + +## 구현 체크리스트 + +- [ ] `TestBoundaryContract`에서 `storage.New(...)`에 주입한 각 fake sub-store가 accessor로 동일하게 반환되는지 identity assertion을 추가한다. 검증: `cd services/core && go test -count=1 ./internal/storage` PASS. +- [ ] 전체 core baseline을 재확인한다. 검증: `cd services/core && go test -count=1 ./...`와 `cd services/core && go vet ./...` PASS. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## [REVIEW_STORAGE-1] Boundary injection identity assertion 추가 + +### 문제 + +`services/core/internal/storage/storage_test.go`의 `TestBoundaryContract`는 `b.Repos() == nil` 같은 nil 여부만 확인한다. 이전 계획은 `New(...)`가 주입받은 store를 그대로 반환하는지도 확인하라고 요구했으므로, 현재 테스트는 storage boundary injection contract를 완전히 고정하지 못한다. + +### 해결 방법 + +`TestBoundaryContract` 안에서 각 fake를 지역 변수로 만든 뒤 `storage.New(repos, leases, ops, opEvents)`를 호출한다. 이후 accessor 반환값이 같은 interface 값인지 직접 비교한다. + +예시: + +```go +repos := &fakeRepos{} +leases := &fakeLeases{} +ops := &fakeOps{} +opEvents := &fakeOpEvents{} + +b := storage.New(repos, leases, ops, opEvents) + +if b.Repos() != repos { + t.Error("Repos() must return the injected repo store") +} +``` + +같은 방식으로 `WorkspaceLeases`, `Operations`, `OperationEvents`도 확인한다. 기존 nil context와 nil sub-store 테스트는 유지한다. + +### 수정 파일 및 체크리스트 + +- [ ] `services/core/internal/storage/storage_test.go`: `TestBoundaryContract`가 직접 fake 인스턴스를 만들도록 조정한다. +- [ ] `services/core/internal/storage/storage_test.go`: 각 accessor가 주입받은 정확한 fake 값을 반환하는지 assertion한다. +- [ ] `services/core/internal/storage/storage_test.go`: gofmt를 적용한다. + +### 중간 검증 + +```sh +cd services/core && go test -count=1 ./internal/storage +``` + +기대 결과: `ok git.toki-labs.com/toki/gito/services/core/internal/storage`. + +## 의존 관계 및 구현 순서 + +1. `TestBoundaryContract`의 setup을 identity assertion이 가능한 지역 변수 형태로 바꾼다. +2. storage package test를 실행한다. +3. 전체 core baseline과 vet을 실행한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +| --- | --- | +| `services/core/internal/storage/storage_test.go` | `REVIEW_STORAGE-1` | + +## 최종 검증 + +```sh +cd services/core && go test -count=1 ./internal/storage +cd services/core && go test -count=1 ./... +cd services/core && go vet ./... +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/packages/contracts/notes/gito-control-plane.md b/packages/contracts/notes/gito-control-plane.md index ddc1868..42fe385 100644 --- a/packages/contracts/notes/gito-control-plane.md +++ b/packages/contracts/notes/gito-control-plane.md @@ -19,6 +19,22 @@ | `agent_shell` | Shell heartbeat, command dispatch, and log streaming. | | `event` | Subscribe to normalized events. | +## proto-socket Action Registry Scaffold + +The first proto-socket surface exposes channel/action registry metadata before +real command execution. Every listed action starts as `placeholder` until its +own Milestone wires storage, worker, or provider behavior. + +| Channel | Placeholder actions | +| --- | --- | +| `repo` | `register`, `get`, `list` | +| `workspace` | `lease`, `release`, `get` | +| `operation` | `create`, `cancel`, `get`, `stream` | +| `git` | `clone`, `fetch`, `status`, `diff`, `commit`, `push` | +| `change_request` | `open`, `update`, `list` | +| `agent_shell` | `heartbeat`, `dispatch`, `stream_logs` | +| `event` | `subscribe`, `ack` | + ## Core DTO Candidates ### Repo @@ -97,4 +113,3 @@ platform features. | `change_request.updated` | PR/MR-like object changed. | | `change_request.merged` | PR/MR-like object merged. | | `provider.webhook.received` | Provider webhook received. | - diff --git a/services/core/internal/controlplane/router.go b/services/core/internal/controlplane/router.go index ceda675..eeaeaf2 100644 --- a/services/core/internal/controlplane/router.go +++ b/services/core/internal/controlplane/router.go @@ -20,14 +20,119 @@ func NewRouter(cfg config.Config, logger *slog.Logger) http.Handler { }) }) mux.HandleFunc(cfg.ProtoSocketPath, func(w http.ResponseWriter, r *http.Request) { - logger.Info("proto-socket endpoint placeholder", "path", r.URL.Path) - writeJSON(w, http.StatusNotImplemented, map[string]string{ - "error": "proto-socket endpoint is not implemented yet", - }) + if r.Method != http.MethodGet { + w.Header().Set("Allow", http.MethodGet) + writeJSON(w, http.StatusMethodNotAllowed, map[string]string{ + "error": "method not allowed", + }) + return + } + logger.Info("proto-socket registry scaffold", "path", r.URL.Path) + writeJSON(w, http.StatusOK, DefaultProtoSocketRegistry()) }) return mux } +type ProtoSocketRegistry struct { + Transport string `json:"transport"` + Status string `json:"status"` + Channels []ProtoSocketChannel `json:"channels"` +} + +type ProtoSocketChannel struct { + Name string `json:"name"` + Purpose string `json:"purpose"` + Status string `json:"status"` + Actions []ProtoSocketAction `json:"actions"` +} + +type ProtoSocketAction struct { + Name string `json:"name"` + Status string `json:"status"` +} + +func DefaultProtoSocketRegistry() ProtoSocketRegistry { + return ProtoSocketRegistry{ + Transport: "proto-socket", + Status: "registry-placeholder", + Channels: []ProtoSocketChannel{ + { + Name: "repo", + Purpose: "register, inspect, and list managed repositories", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "register", Status: "placeholder"}, + {Name: "get", Status: "placeholder"}, + {Name: "list", Status: "placeholder"}, + }, + }, + { + Name: "workspace", + Purpose: "lease, release, and inspect workspace slots", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "lease", Status: "placeholder"}, + {Name: "release", Status: "placeholder"}, + {Name: "get", Status: "placeholder"}, + }, + }, + { + Name: "operation", + Purpose: "create, cancel, inspect, and stream operations", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "create", Status: "placeholder"}, + {Name: "cancel", Status: "placeholder"}, + {Name: "get", Status: "placeholder"}, + {Name: "stream", Status: "placeholder"}, + }, + }, + { + Name: "git", + Purpose: "request platformless Git operations", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "clone", Status: "placeholder"}, + {Name: "fetch", Status: "placeholder"}, + {Name: "status", Status: "placeholder"}, + {Name: "diff", Status: "placeholder"}, + {Name: "commit", Status: "placeholder"}, + {Name: "push", Status: "placeholder"}, + }, + }, + { + Name: "change_request", + Purpose: "manage provider-neutral change requests", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "open", Status: "placeholder"}, + {Name: "update", Status: "placeholder"}, + {Name: "list", Status: "placeholder"}, + }, + }, + { + Name: "agent_shell", + Purpose: "shell heartbeat, command dispatch, and log streaming", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "heartbeat", Status: "placeholder"}, + {Name: "dispatch", Status: "placeholder"}, + {Name: "stream_logs", Status: "placeholder"}, + }, + }, + { + Name: "event", + Purpose: "subscribe to normalized events", + Status: "placeholder", + Actions: []ProtoSocketAction{ + {Name: "subscribe", Status: "placeholder"}, + {Name: "ack", Status: "placeholder"}, + }, + }, + }, + } +} + func writeJSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) diff --git a/services/core/internal/controlplane/router_test.go b/services/core/internal/controlplane/router_test.go index 81af024..69d5a9d 100644 --- a/services/core/internal/controlplane/router_test.go +++ b/services/core/internal/controlplane/router_test.go @@ -29,14 +29,47 @@ func TestHealthz(t *testing.T) { } } -func TestProtoSocketPlaceholder(t *testing.T) { +func TestProtoSocketRegistry(t *testing.T) { router := NewRouter(config.Config{AppEnv: "test", ProtoSocketPath: "/proto-socket"}, slog.Default()) req := httptest.NewRequest(http.MethodGet, "/proto-socket", nil) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) - if rec.Code != http.StatusNotImplemented { + if rec.Code != http.StatusOK { t.Fatalf("status: got %d", rec.Code) } + var body ProtoSocketRegistry + if err := json.NewDecoder(rec.Body).Decode(&body); err != nil { + t.Fatalf("decode: %v", err) + } + if body.Transport != "proto-socket" { + t.Fatalf("transport: got %q", body.Transport) + } + channels := make(map[string]ProtoSocketChannel, len(body.Channels)) + for _, channel := range body.Channels { + channels[channel.Name] = channel + } + assertPlaceholderChannel(t, channels, "operation", []string{"create", "cancel", "get", "stream"}) + assertPlaceholderChannel(t, channels, "event", []string{"subscribe", "ack"}) +} + +func assertPlaceholderChannel(t *testing.T, channels map[string]ProtoSocketChannel, name string, actions []string) { + t.Helper() + channel, ok := channels[name] + if !ok { + t.Fatalf("missing channel %q", name) + } + if channel.Status != "placeholder" { + t.Fatalf("channel %q status: got %q", name, channel.Status) + } + actionStatuses := make(map[string]string, len(channel.Actions)) + for _, action := range channel.Actions { + actionStatuses[action.Name] = action.Status + } + for _, action := range actions { + if actionStatuses[action] != "placeholder" { + t.Fatalf("channel %q action %q status: got %q", name, action, actionStatuses[action]) + } + } } diff --git a/services/core/internal/storage/storage.go b/services/core/internal/storage/storage.go index 624fffd..b7df5ad 100644 --- a/services/core/internal/storage/storage.go +++ b/services/core/internal/storage/storage.go @@ -1,14 +1,77 @@ package storage -import "context" +import ( + "context" + "fmt" -type Store struct{} + "git.toki-labs.com/toki/gito/services/core/internal/core" + "git.toki-labs.com/toki/gito/services/core/internal/events" +) -func New() *Store { - return &Store{} +type Store interface { + Ping(ctx context.Context) error + Repos() RepoStore + WorkspaceLeases() WorkspaceLeaseStore + Operations() OperationStore + OperationEvents() OperationEventStore } -func (s *Store) Ping(ctx context.Context) error { - _ = ctx +type RepoStore interface { + SaveRepo(ctx context.Context, repo core.Repo) error + GetRepo(ctx context.Context, id string) (core.Repo, error) + ListRepos(ctx context.Context) ([]core.Repo, error) +} + +type WorkspaceLeaseStore interface { + SaveLease(ctx context.Context, lease core.WorkspaceLease) error + GetLease(ctx context.Context, id string) (core.WorkspaceLease, error) + ListLeases(ctx context.Context, repoID string) ([]core.WorkspaceLease, error) +} + +type OperationStore interface { + SaveOperation(ctx context.Context, op core.Operation) error + GetOperation(ctx context.Context, id string) (core.Operation, error) + ListOperations(ctx context.Context, repoID string) ([]core.Operation, error) +} + +type OperationEvent struct { + OperationID string + Event events.Event +} + +type OperationEventStore interface { + AppendEvent(ctx context.Context, ev OperationEvent) error + ListEvents(ctx context.Context, operationID string) ([]OperationEvent, error) +} + +// Boundary is the concrete implementation of Store backed by injected sub-stores. +type Boundary struct { + repos RepoStore + leases WorkspaceLeaseStore + ops OperationStore + opEvents OperationEventStore +} + +func New(repos RepoStore, leases WorkspaceLeaseStore, ops OperationStore, opEvents OperationEventStore) *Boundary { + return &Boundary{ + repos: repos, + leases: leases, + ops: ops, + opEvents: opEvents, + } +} + +func (b *Boundary) Ping(ctx context.Context) error { + if ctx == nil { + return fmt.Errorf("storage: nil context") + } + if b.repos == nil || b.leases == nil || b.ops == nil || b.opEvents == nil { + return fmt.Errorf("storage: one or more sub-stores not injected") + } return nil } + +func (b *Boundary) Repos() RepoStore { return b.repos } +func (b *Boundary) WorkspaceLeases() WorkspaceLeaseStore { return b.leases } +func (b *Boundary) Operations() OperationStore { return b.ops } +func (b *Boundary) OperationEvents() OperationEventStore { return b.opEvents } diff --git a/services/core/internal/storage/storage_test.go b/services/core/internal/storage/storage_test.go new file mode 100644 index 0000000..cfb7c6d --- /dev/null +++ b/services/core/internal/storage/storage_test.go @@ -0,0 +1,138 @@ +package storage_test + +import ( + "context" + "os" + "strings" + "testing" + + "git.toki-labs.com/toki/gito/services/core/internal/core" + "git.toki-labs.com/toki/gito/services/core/internal/events" + "git.toki-labs.com/toki/gito/services/core/internal/storage" +) + +// compile-time interface checks +var _ storage.Store = (*storage.Boundary)(nil) + +// fakes + +type fakeRepos struct{} + +func (f *fakeRepos) SaveRepo(_ context.Context, _ core.Repo) error { return nil } +func (f *fakeRepos) GetRepo(_ context.Context, _ string) (core.Repo, error) { return core.Repo{}, nil } +func (f *fakeRepos) ListRepos(_ context.Context) ([]core.Repo, error) { return nil, nil } + +var _ storage.RepoStore = (*fakeRepos)(nil) + +type fakeLeases struct{} + +func (f *fakeLeases) SaveLease(_ context.Context, _ core.WorkspaceLease) error { return nil } +func (f *fakeLeases) GetLease(_ context.Context, _ string) (core.WorkspaceLease, error) { + return core.WorkspaceLease{}, nil +} +func (f *fakeLeases) ListLeases(_ context.Context, _ string) ([]core.WorkspaceLease, error) { + return nil, nil +} + +var _ storage.WorkspaceLeaseStore = (*fakeLeases)(nil) + +type fakeOps struct{} + +func (f *fakeOps) SaveOperation(_ context.Context, _ core.Operation) error { return nil } +func (f *fakeOps) GetOperation(_ context.Context, _ string) (core.Operation, error) { + return core.Operation{}, nil +} +func (f *fakeOps) ListOperations(_ context.Context, _ string) ([]core.Operation, error) { + return nil, nil +} + +var _ storage.OperationStore = (*fakeOps)(nil) + +type fakeOpEvents struct{} + +func (f *fakeOpEvents) AppendEvent(_ context.Context, _ storage.OperationEvent) error { return nil } +func (f *fakeOpEvents) ListEvents(_ context.Context, _ string) ([]storage.OperationEvent, error) { + return nil, nil +} + +var _ storage.OperationEventStore = (*fakeOpEvents)(nil) + +// OperationEvent uses events.Event +var _ = storage.OperationEvent{Event: events.Event{}} + +func newBoundary() *storage.Boundary { + return storage.New(&fakeRepos{}, &fakeLeases{}, &fakeOps{}, &fakeOpEvents{}) +} + +func TestBoundaryContract(t *testing.T) { + repos := &fakeRepos{} + leases := &fakeLeases{} + ops := &fakeOps{} + opEvents := &fakeOpEvents{} + + b := storage.New(repos, leases, ops, opEvents) + + if b.Repos() != repos { + t.Error("Repos() must return the injected repo store") + } + if b.WorkspaceLeases() != leases { + t.Error("WorkspaceLeases() must return the injected lease store") + } + if b.Operations() != ops { + t.Error("Operations() must return the injected operation store") + } + if b.OperationEvents() != opEvents { + t.Error("OperationEvents() must return the injected operation event store") + } + + if err := b.Ping(context.Background()); err != nil { + t.Errorf("Ping with fully injected boundary: %v", err) + } +} + +func TestBoundaryPingNilContext(t *testing.T) { + b := newBoundary() + //nolint:staticcheck + if err := b.Ping(nil); err == nil { + t.Error("Ping with nil context must return error") + } +} + +func TestBoundaryPingMissingSubStore(t *testing.T) { + b := storage.New(nil, nil, nil, nil) + if err := b.Ping(context.Background()); err == nil { + t.Error("Ping with nil sub-stores must return error") + } +} + +func TestInitialMigrationSupportsStoreBoundary(t *testing.T) { + data, err := os.ReadFile("../../migrations/00001_initial.sql") + if err != nil { + t.Fatalf("cannot read migration file: %v", err) + } + content := string(data) + + tableSnippets := []string{ + "CREATE TABLE IF NOT EXISTS repos", + "CREATE TABLE IF NOT EXISTS workspace_leases", + "CREATE TABLE IF NOT EXISTS operations", + "CREATE TABLE IF NOT EXISTS operation_events", + "CREATE TABLE IF NOT EXISTS revision_cursors", + } + + // operation_events must declare operation_id column (direct FK to operations) + columnSnippets := []string{ + "operation_id", + } + + for _, snippet := range tableSnippets { + if !strings.Contains(content, snippet) { + t.Errorf("migration missing required table definition: %q", snippet) + } + } + for _, col := range columnSnippets { + if !strings.Contains(content, col) { + t.Errorf("migration missing required column snippet: %q", col) + } + } +}