diff --git a/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md index c8abf44..1ca957a 100644 --- a/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md +++ b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md @@ -1,6 +1,6 @@ - + -# Code Review Reference - API +# Code Review Reference - REVIEW_API > **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** > The task is NOT complete until every implementation-owned section below is filled in. @@ -13,7 +13,7 @@ ## 개요 date=2026-05-30 -task=m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics, plan=0, tag=API +task=m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics, plan=1, tag=REVIEW_API ## 이 파일을 읽는 리뷰 에이전트에게 @@ -23,7 +23,7 @@ task=m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics, pl 리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. 1. 판정을 append한다. -2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다. +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-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/`로 이동한다. 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-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. 5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. @@ -34,17 +34,18 @@ task=m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics, pl | 항목 | 완료 여부 | |------|---------| -| [API-1] Core connection diagnostics | [ ] | -| [API-2] Core envelope meta and error logs | [ ] | +| [REVIEW_API-1] Checklist contract recovery | [ ] | +| [REVIEW_API-2] Core test evidence recovery | [ ] | +| [REVIEW_API-3] Core final verification recovery | [ ] | ## 구현 체크리스트 -- [ ] `11_contracts_diagnostics`가 PASS되어 `complete.log`가 생겼는지 확인하고 경로를 review stub에 기록한다. -- [ ] Core proto-socket 연결마다 stable `connection_id`를 만들고 연결/해제/request/broadcast 로그에 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`를 구조화 속성으로 남긴다. -- [ ] response/event envelope `meta`에 diagnostics 필드를 additive로 채운다. -- [ ] Core 테스트로 diagnostics meta와 구조화 로그를 검증한다. -- [ ] `cd services/core && go test ./...`를 허용된 원격/검증 환경에서 실행한다. -- [ ] `cd services/core && go vet ./...`를 허용된 원격/검증 환경에서 실행한다. +- [ ] 이번 plan/review의 `구현 체크리스트` 항목 텍스트와 순서를 동일하게 유지하고, 실행 사유나 메모는 체크리스트 문구가 아닌 `계획 대비 변경 사항` 또는 `검증 결과`에 기록한다. +- [ ] `cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] `cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] `cd services/core && go test ./...`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] `cd services/core && go vet ./...`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] 검증 명령이 실패하면 repo-owned 원인을 최소 범위로 수정하고, 실패 출력과 수정 후 통과 출력을 모두 기록한다. - [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ## 코드리뷰 전용 체크리스트 @@ -87,10 +88,10 @@ _기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외 ## 리뷰어를 위한 체크포인트 -- `connection_id` 생성과 clients map 변경이 race 없이 mutex 보호 아래 있는가. -- response/event meta가 기존 payload shape와 REST compatibility를 깨지 않는 additive 변경인가. -- 로그에 auth token, payload 원문, secret이 출력되지 않는가. -- predecessor `11_contracts_diagnostics` complete evidence가 기록되었는가. +- plan/review의 `구현 체크리스트` 항목 텍스트와 순서가 동일한가. +- targeted tests, full core test, vet의 실제 stdout/stderr가 기록되었는가. +- 명령 실패가 있었다면 repo-owned 원인을 최소 범위로 수정했고 실패/재실행 출력이 모두 남았는가. +- 테스트 실행 환경 사유가 단순 증거 공백으로 남지 않고, 필요 시 사용자 리뷰 요청 요건을 충족하는 구체 증거로 기록되었는가. ## 검증 결과 @@ -102,18 +103,30 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 - `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. - 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. -### API-1 중간 검증 +### REVIEW_API-1 중간 검증 ``` -$ cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1 +$ diff -u <(awk '/^## 구현 체크리스트$/{flag=1; next} /^## 의존 관계 및 구현 순서$/{flag=0} flag && /^- \[[ x]\] /{sub(/^- \[[ x]\] /,"- [ ] "); print}' PLAN-cloud-G07.md) <(awk '/^## 구현 체크리스트$/{flag=1; next} /^## 코드리뷰 전용 체크리스트$/{flag=0} flag && /^- \[[ x]\] /{sub(/^- \[[ x]\] /,"- [ ] "); print}' CODE_REVIEW-cloud-G07.md) (output) ``` -### API-2 중간 검증 +### REVIEW_API-2 중간 검증 ``` +$ cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1 +(output) + $ cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1 (output) ``` +### REVIEW_API-3 중간 검증 +``` +$ cd services/core && go test ./... +(output) + +$ cd services/core && go vet ./... +(output) +``` + ### 최종 검증 ``` $ cd services/core && go test ./... diff --git a/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/PLAN-cloud-G07.md b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/PLAN-cloud-G07.md index ff92105..0d039a2 100644 --- a/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/PLAN-cloud-G07.md +++ b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/PLAN-cloud-G07.md @@ -1,18 +1,22 @@ - + -# Plan - API Core Proto-Socket Diagnostics +# Plan - REVIEW_API Core Diagnostics Verification Recovery ## 이 파일을 읽는 구현 에이전트에게 -`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 반드시 채운다. 구현 후 검증을 실행하고 active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거와 재개 조건을 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. +`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 검증 출력으로 반드시 채운다. 이번 루프의 기본 목표는 검증 증거 회복이며, 소스 코드는 명령 실패가 실제 repo-owned 문제를 드러낼 때만 최소 범위로 수정한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거와 재개 조건을 채우고 멈춘다. 단순 증거 공백은 사용자 리뷰 요청이 아니며, 허용된 원격/검증 환경에서 명령을 재실행해 닫는다. ## 배경 -Core proto-socket server는 연결과 broadcast 실패를 로그로 남기지만 connection id, protocol version, channel, action, error code를 구조화된 속성으로 남기지 않는다. Client debug surface도 Core가 보내는 response/event meta를 소비해야 하므로 먼저 Core가 안정적인 diagnostics meta와 로그를 제공해야 한다. 이 subtask는 계약 문서 subtask 완료 후 진행한다. +이전 루프 `code_review_cloud_G07_0.log`는 Core diagnostics 구현 자체보다 plan/review 루프 계약과 검증 신뢰 문제로 FAIL 처리되었다. + +- review stub의 `구현 체크리스트` 항목 중 하나가 계획 파일 원문과 달라졌다. +- 필수 최종 검증 `cd services/core && go test ./...`와 `cd services/core && go vet ./...`가 실행되지 않았고 실제 stdout/stderr가 없다. +- local 테스트 규칙은 로컬 실행을 금지하므로, 이번 루프는 허용된 원격/검증 환경에서 명령 증거를 회복해야 한다. ## 사용자 리뷰 요청 흐름 -구현 중 차단 사유는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`를 기준으로 하며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다. +구현 중 차단 사유는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 허용된 원격/검증 환경 자체가 사용자 소유 준비물이라 접근할 수 없다는 구체 증거가 있을 때만 사용자 리뷰 요청을 작성한다. 단순히 이전 루프처럼 "로컬 규칙상 미실행"만 기록하고 완료 처리하지 않는다. ## 분석 결과 @@ -21,195 +25,140 @@ Core proto-socket server는 연결과 broadcast 실패를 로그로 남기지만 - `agent-ops/rules/project/rules.md` - `agent-ops/rules/private/rules.md` - `agent-ops/rules/common/rules-roadmap.md` -- `agent-ops/skills/common/router.md` -- `agent-ops/skills/common/plan/SKILL.md` - `agent-roadmap/current.md` -- `agent-roadmap/phase/workflow-core/PHASE.md` - `agent-roadmap/phase/workflow-core/milestones/proto-socket-infrastructure-communication-rail.md` - `agent-ops/rules/project/domain/core/rules.md` -- `agent-ops/rules/project/domain/contracts/rules.md` - `agent-test/local/rules.md` - `agent-test/local/core-smoke.md` -- `agent-test/local/contracts-smoke.md` +- `agent-ops/skills/common/code-review/SKILL.md` +- `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/plan_cloud_G07_0.log` +- `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/code_review_cloud_G07_0.log` - `services/core/internal/protosocket/server.go` -- `services/core/internal/protosocket/dispatcher.go` -- `services/core/internal/protosocket/envelope.go` -- `services/core/internal/protosocket/events.go` -- `services/core/internal/protosocket/tasks.go` -- `services/core/internal/http/router.go` - `services/core/internal/protosocket/server_test.go` - `services/core/internal/protosocket/tasks_test.go` -- `services/core/internal/protosocket/envelope_test.go` -- `packages/contracts/notes/flutter-core-api-candidates.md` ### 테스트 환경 규칙 -`test_env=local`로 판단했고 `agent-test/local/rules.md`를 읽었다. 이 파일은 로컬 테스트 금지를 명시하므로 현 세션에서는 검증 명령을 실행하지 않는다. 적용 profile은 `core-smoke`이며 core 변경의 필수 검증은 `cd services/core && go test ./...`, 보조 검증은 `cd services/core && go vet ./...`다. 명령은 후속 구현/리뷰 에이전트가 허용된 원격/검증 환경에서 실행한다. - -### 테스트 커버리지 공백 - -- connection id가 response/event meta에 들어가는 동작: 기존 테스트 없음. `server_test.go` 또는 `tasks_test.go`에 추가 필요. -- protocol/channel/action/error_code 구조화 로그: 기존 테스트 없음. `slog` buffer 기반 테스트 추가 필요. -- 기존 task action 의미 보존: `tasks_test.go`가 create/list/get/enqueue, auth, error code를 이미 검증한다. - -### 심볼 참조 - -none. 기존 public symbol rename/remove 없이 server 내부 struct/helper를 추가한다. - -### 분할 판단 - -split decision policy를 평가했다. 이 subtask는 `12+11_core_diagnostics`이며 predecessor `11_contracts_diagnostics`의 `complete.log`가 필요하다. 현재 active/archive predecessor completion은 없음으로 확인했다. 구현은 `11` PASS 후 시작해야 한다. +`agent-test/local/rules.md`는 로컬 테스트/검증 실행을 금지한다. 이 plan의 `go test`/`go vet` 명령은 허용된 원격/검증 환경에서 실행하고, 실제 stdout/stderr를 review stub에 붙여 넣는다. 명령을 실행할 수 없으면 사용자 소유 환경 차단인지, repo-owned setup 문제인지 구분해 기록한다. ### 범위 결정 근거 -Core proto-socket server, dispatcher/event helper, core tests만 수정한다. Flutter debug UI와 contracts note 수정은 각각 `13+11,12_client_diagnostics`, `11_contracts_diagnostics` 범위다. REST task API shape, auth policy, workflow service behavior, DB schema는 변경하지 않는다. +이전 코드 리뷰에서 즉시 확인된 소스 결함은 없다. 따라서 기본 범위는 task artifact의 증거 회복이다. `go test` 또는 `go vet`가 실패하면 실패 원인을 기준으로 `services/core/internal/protosocket/**` 안에서 최소 수정하고, 실패 출력과 수정 후 재실행 출력을 모두 남긴다. ### 빌드 등급 -`cloud-G07`: protocol diagnostics, websocket connection lifecycle, structured logging, concurrency-safe client registry를 건드리므로 cloud lane과 높은 등급이 필요하다. +`cloud-G07`: verification trust recovery이며 이전 루프가 필수 core 검증 증거 없이 종료되었으므로 cloud lane과 G07을 유지한다. ## 구현 체크리스트 -- [ ] `11_contracts_diagnostics`가 PASS되어 `complete.log`가 생겼는지 확인하고 경로를 review stub에 기록한다. -- [ ] Core proto-socket 연결마다 stable `connection_id`를 만들고 연결/해제/request/broadcast 로그에 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`를 구조화 속성으로 남긴다. -- [ ] response/event envelope `meta`에 diagnostics 필드를 additive로 채운다. -- [ ] Core 테스트로 diagnostics meta와 구조화 로그를 검증한다. -- [ ] `cd services/core && go test ./...`를 허용된 원격/검증 환경에서 실행한다. -- [ ] `cd services/core && go vet ./...`를 허용된 원격/검증 환경에서 실행한다. +- [ ] 이번 plan/review의 `구현 체크리스트` 항목 텍스트와 순서를 동일하게 유지하고, 실행 사유나 메모는 체크리스트 문구가 아닌 `계획 대비 변경 사항` 또는 `검증 결과`에 기록한다. +- [ ] `cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] `cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] `cd services/core && go test ./...`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] `cd services/core && go vet ./...`를 허용된 원격/검증 환경에서 실행하고 실제 stdout/stderr를 기록한다. +- [ ] 검증 명령이 실패하면 repo-owned 원인을 최소 범위로 수정하고, 실패 출력과 수정 후 통과 출력을 모두 기록한다. - [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ## 의존 관계 및 구현 순서 -이 directory name은 `12+11_core_diagnostics`이므로 predecessor는 같은 task group의 `11_*` subtask뿐이다. `agent-task/m-proto-socket-infrastructure-communication-rail/11_contracts_diagnostics/complete.log` 또는 matching archive `complete.log`가 생기기 전에는 구현을 시작하지 않는다. +1. `plan_cloud_G07_0.log`와 `code_review_cloud_G07_0.log`의 FAIL 사유를 확인한다. +2. follow-up `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`의 `구현 체크리스트` 텍스트/순서가 일치하는지 확인한다. +3. 허용된 원격/검증 환경에서 targeted tests, full core test, vet 순서로 실행한다. +4. 실패가 있으면 원인 파일을 최소 수정하고 같은 명령을 재실행한다. +5. review stub에 실제 stdout/stderr, 변경 여부, 남은 위험을 기록한다. -### [API-1] Core Connection Diagnostics +### [REVIEW_API-1] Checklist Contract Recovery #### 문제 -[services/core/internal/protosocket/server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:20)는 clients map에 client pointer만 저장하고 connection id가 없다. 연결/해제 로그도 [server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:58)와 [server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:102)에서 generic message만 남긴다. - -Before: - -```go -20 type Server struct { -21 cfg Config -22 dispatcher *Dispatcher -23 logger *slog.Logger -24 mu sync.Mutex -25 clients map[*toki.WsClient]struct{} -26 } -... -58 client.AddDisconnectListener(func(c *toki.WsClient) { -59 s.mu.Lock() -60 delete(s.clients, c) -61 s.mu.Unlock() -62 s.logger.Info("websocket client disconnected") -63 }) -... -102 s.logger.Info("websocket client connected successfully") -``` +이전 review stub은 `구현 체크리스트` 항목에 실행 메모를 직접 덧붙여 plan/review 체크리스트 원문 일치 계약을 깨뜨렸다. #### 해결 방법 -client registry 값을 diagnostics struct로 바꾸고 accept 시 `connection_id`를 생성한다. nil logger 가능성도 방어한다. 연결/해제/request 로그에는 contract subtask의 필드명을 사용한다. - -After: - -```go -type clientDiagnostics struct { - connectionID string -} - -type Server struct { - ... - clients map[*toki.WsClient]clientDiagnostics -} - -connectionID := "conn-" + generateID() -s.clients[client] = clientDiagnostics{connectionID: connectionID} -s.logInfo("proto-socket client connected", "connection_id", connectionID, "protocol_version", ProtocolVersion) -``` +이번 루프에서는 plan/review의 `구현 체크리스트` 항목 텍스트와 순서를 그대로 유지한다. 실행 메모, 환경 사유, 실패/재실행 설명은 `계획 대비 변경 사항`, `주요 설계 결정`, `검증 결과`에만 기록한다. #### 수정 파일 및 체크리스트 -- [ ] `services/core/internal/protosocket/server.go`: client diagnostics registry와 log helper 추가. -- [ ] `services/core/internal/protosocket/server_test.go`: connection id가 response meta 또는 logs에 남는지 검증. +- [ ] `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md`: 체크리스트 문구를 변경하지 않고 체크 상태만 갱신한다. #### 테스트 작성 -`server_test.go`에 `slog.NewJSONHandler(&bytes.Buffer, nil)` 기반 테스트를 추가한다. 연결 성공/해제 또는 request 처리 후 JSON log에 `connection_id`, `protocol_version`이 포함되는지 assertion한다. +새 테스트는 필요 없다. code-review가 plan/review 체크리스트 문구를 대조한다. + +#### 중간 검증 + +```bash +diff -u <(awk '/^## 구현 체크리스트$/{flag=1; next} /^## 의존 관계 및 구현 순서$/{flag=0} flag && /^- \[[ x]\] /{sub(/^- \[[ x]\] /,"- [ ] "); print}' PLAN-cloud-G07.md) <(awk '/^## 구현 체크리스트$/{flag=1; next} /^## 코드리뷰 전용 체크리스트$/{flag=0} flag && /^- \[[ x]\] /{sub(/^- \[[ x]\] /,"- [ ] "); print}' CODE_REVIEW-cloud-G07.md) +``` + +기대 결과: 출력 없음, exit 0. `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`의 `구현 체크리스트` 항목 텍스트와 순서가 동일하다. + +### [REVIEW_API-2] Core Test Evidence Recovery + +#### 문제 + +이전 루프는 diagnostics 테스트 코드 추가를 기록했지만 targeted tests와 full core test를 실행하지 않았다. 필수 stdout/stderr 증거가 없으므로 구현을 PASS로 닫을 수 없다. + +#### 해결 방법 + +허용된 원격/검증 환경에서 targeted tests와 full core test를 실행한다. 실패하면 출력으로 원인을 특정하고 repo-owned 문제만 최소 수정한다. + +#### 수정 파일 및 체크리스트 + +- [ ] `services/core/internal/protosocket/server.go`: 테스트 실패가 실제 구현 결함을 드러낼 때만 최소 수정한다. +- [ ] `services/core/internal/protosocket/server_test.go`: 테스트 실패가 테스트 결함을 드러낼 때만 최소 수정한다. +- [ ] `services/core/internal/protosocket/tasks_test.go`: 테스트 실패가 테스트 결함을 드러낼 때만 최소 수정한다. +- [ ] `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md`: 실제 stdout/stderr를 기록한다. + +#### 테스트 작성 + +새 테스트 작성이 아니라 기존 추가 테스트의 실행 증거를 회복한다. 실패 원인이 coverage 공백이면 가장 좁은 assertion을 추가한다. #### 중간 검증 ```bash cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1 +cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1 ``` -기대 결과: 관련 tests PASS. 명령은 허용된 원격/검증 환경에서 실행한다. +기대 결과: 두 명령 모두 실패 없이 종료하고 실제 stdout/stderr가 review stub에 기록된다. -### [API-2] Core Envelope Meta And Error Logs +### [REVIEW_API-3] Core Final Verification Recovery #### 문제 -[services/core/internal/protosocket/server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:65)는 request를 dispatch하지만 request/response diagnostics를 남기지 않는다. [services/core/internal/protosocket/events.go](/config/workspace/nomadcode/services/core/internal/protosocket/events.go:22)의 error response에는 code가 있지만 meta에는 diagnostics가 없다. - -Before: - -```go -65 toki.AddRequestListenerTyped[*structpb.Struct, *structpb.Struct](&client.Communicator, func(req *structpb.Struct) (*structpb.Struct, error) { -66 env, err := EnvelopeFromStruct(req) -... -82 resEnv := s.dispatcher.Dispatch(r.Context(), env) -83 resStruct, err := resEnv.ToStruct() -... -99 return resStruct, nil -100 }) -``` +이전 루프는 core 필수 검증 `go test ./...`와 보조 검증 `go vet ./...`를 실행하지 않았다. #### 해결 방법 -`withDiagnosticsMeta(env Envelope, connectionID string) Envelope`와 `errorCode(env Envelope) string` helper를 추가한다. 모든 response/event 직전 meta에 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`를 additive로 넣고, request 처리 로그도 같은 값을 남긴다. Broadcast는 client별 clone에 connection id를 채운 뒤 전송한다. - -After: - -```go -resEnv := withDiagnosticsMeta(s.dispatcher.Dispatch(r.Context(), env), connectionID) -s.logInfo("proto-socket request handled", - "connection_id", connectionID, - "protocol_version", resEnv.ProtocolVersion, - "channel", resEnv.Channel, - "action", resEnv.Action, - "error_code", errorCode(resEnv), -) -``` +허용된 원격/검증 환경에서 final core verification을 실행한다. 실패하면 출력으로 원인을 특정하고 repo-owned 문제만 최소 수정한다. #### 수정 파일 및 체크리스트 -- [ ] `services/core/internal/protosocket/server.go`: response/event meta 주입, request/broadcast log 추가. -- [ ] `services/core/internal/protosocket/events.go`: error/success response helper와 meta 주입 방식 충돌 여부 확인. -- [ ] `services/core/internal/protosocket/tasks_test.go`: task response/error envelope meta assertions 추가. -- [ ] `services/core/internal/protosocket/server_test.go`: broadcast meta/log assertions 추가. +- [ ] `services/core/internal/protosocket/**`: final verification 실패가 실제 repo-owned 문제를 드러낼 때만 최소 수정한다. +- [ ] `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md`: 실제 stdout/stderr를 기록한다. #### 테스트 작성 -`tasks_test.go` route-level tests에서 `task.list` response와 error response의 `Meta["connection_id"]`, `Meta["protocol_version"]`, `Meta["channel"]`, `Meta["action"]`, `Meta["error_code"]`를 검증한다. `server_test.go` broadcast test는 client별 event meta에 connection id가 들어가는지 검증한다. +새 테스트는 기본적으로 필요 없다. `go test ./...` 또는 `go vet ./...` 실패가 coverage 공백이나 vet 오류를 드러낼 때만 필요한 최소 테스트/코드 수정을 한다. #### 중간 검증 ```bash -cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1 +cd services/core && go test ./... +cd services/core && go vet ./... ``` -기대 결과: 관련 tests PASS. 명령은 허용된 원격/검증 환경에서 실행한다. +기대 결과: 두 명령 모두 실패 없이 종료하고 실제 stdout/stderr가 review stub에 기록된다. ## 수정 파일 요약 | 파일 | 항목 | |------|------| -| `services/core/internal/protosocket/server.go` | API-1, API-2 | -| `services/core/internal/protosocket/events.go` | API-2 | -| `services/core/internal/protosocket/server_test.go` | API-1, API-2 | -| `services/core/internal/protosocket/tasks_test.go` | API-2 | +| `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 | +| `services/core/internal/protosocket/server.go` | REVIEW_API-2, REVIEW_API-3 실패 시에만 | +| `services/core/internal/protosocket/server_test.go` | REVIEW_API-2, REVIEW_API-3 실패 시에만 | +| `services/core/internal/protosocket/tasks_test.go` | REVIEW_API-2, REVIEW_API-3 실패 시에만 | ## 최종 검증 @@ -218,6 +167,4 @@ cd services/core && go test ./... cd services/core && go vet ./... ``` -기대 결과: 두 명령 모두 실패 없이 종료한다. Go test cache는 diagnostics 변경 확인에는 충분하지 않으므로 필요한 targeted tests는 `-count=1`로 중간 검증에서 fresh 실행한다. 명령은 허용된 원격/검증 환경에서 실행한다. - -모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. +기대 결과: 두 명령 모두 실패 없이 종료한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. diff --git a/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/code_review_cloud_G07_0.log b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/code_review_cloud_G07_0.log new file mode 100644 index 0000000..053015b --- /dev/null +++ b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/code_review_cloud_G07_0.log @@ -0,0 +1,156 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. 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. +> 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-05-30 +task=m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics, plan=0, tag=API + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[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-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/`로 이동한다. 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-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Core connection diagnostics | [x] | +| [API-2] Core envelope meta and error logs | [x] | + +## 구현 체크리스트 + +- [x] `11_contracts_diagnostics`가 PASS되어 `complete.log`가 생겼는지 확인하고 경로를 review stub에 기록한다. +- [x] Core proto-socket 연결마다 stable `connection_id`를 만들고 연결/해제/request/broadcast 로그에 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`를 구조화 속성으로 남긴다. +- [x] response/event envelope `meta`에 diagnostics 필드를 additive로 채운다. +- [x] Core 테스트로 diagnostics meta와 구조화 로그를 검증한다. (테스트 코드 추가 완료, 실행은 local 규칙으로 미실행) +- [ ] `cd services/core && go test ./...`를 허용된 원격/검증 환경에서 실행한다. +- [ ] `cd services/core && go vet ./...`를 허용된 원격/검증 환경에서 실행한다. +- [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하여 plan/review/archive 산출물이 추적 가능한지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/`를 `agent-task/archive/YYYY/MM/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-proto-socket-infrastructure-communication-rail/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] 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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +- `events.go`는 수정하지 않았다. event envelope 자체는 기존 `TaskStatusChangedEnvelope`를 유지하고, client별 `connection_id`가 필요한 diagnostics meta는 `Server.BroadcastEnvelope`에서 전송 직전 clone에 주입했다. +- 로컬 환경에서는 `agent-test/local/rules.md`가 테스트/검증 실행을 금지하므로 `go test`/`go vet` 명령을 실행하지 않았다. 검증은 허용된 원격/검증 환경에서 재실행해야 한다. + +## 주요 설계 결정 + +- 선행 완료 확인: `agent-task/archive/2026/05/m-proto-socket-infrastructure-communication-rail/11_contracts_diagnostics/complete.log` +- 연결 등록 정보를 `map[*toki.WsClient]clientDiagnostics`로 바꾸고, 연결마다 `conn-` prefix의 stable `connection_id`를 부여했다. +- response/error/event envelope `meta`에는 기존 meta를 보존한 뒤 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`, `timestamp`를 추가한다. +- broadcast는 client snapshot을 만든 뒤 client별 envelope clone에 diagnostics meta를 넣어 서로 다른 connection id가 섞이지 않게 했다. +- nil logger는 no-op helper로 방어하고, 로그에는 secret/auth/payload 원문을 남기지 않았다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `connection_id` 생성과 clients map 변경이 race 없이 mutex 보호 아래 있는가. +- response/event meta가 기존 payload shape와 REST compatibility를 깨지 않는 additive 변경인가. +- 로그에 auth token, payload 원문, secret이 출력되지 않는가. +- predecessor `11_contracts_diagnostics` complete evidence가 기록되었는가. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. + +### API-1 중간 검증 +``` +$ cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1 +미실행 +사유: `agent-test/local/rules.md`가 로컬 테스트/검증 실행을 금지한다. 허용된 원격/검증 환경에서 실행 필요. +``` + +### API-2 중간 검증 +``` +$ cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1 +미실행 +사유: `agent-test/local/rules.md`가 로컬 테스트/검증 실행을 금지한다. 허용된 원격/검증 환경에서 실행 필요. +``` + +### 최종 검증 +``` +$ cd services/core && go test ./... +미실행 +사유: `agent-test/local/rules.md`가 로컬 테스트/검증 실행을 금지한다. 허용된 원격/검증 환경에서 실행 필요. + +$ cd services/core && go vet ./... +미실행 +사유: `agent-test/local/rules.md`가 로컬 테스트/검증 실행을 금지한다. 허용된 원격/검증 환경에서 실행 필요. +``` + +--- + +> **[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. + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - Correctness: Pass + - Completeness: Fail + - Test coverage: Fail + - API contract: Pass + - Code quality: Pass + - Plan deviation: Fail + - Verification trust: Fail +- 발견된 문제: + - Required: `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md:45`의 `구현 체크리스트` 항목 텍스트가 계획 파일의 동일 항목과 달라졌습니다. code-review 계약상 plan/review 체크리스트의 텍스트와 순서는 일치해야 하므로, 실행 여부나 사유는 `계획 대비 변경 사항` 또는 `검증 결과`에 기록하고 체크리스트 문구는 원문 그대로 유지해야 합니다. + - Required: `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md:46`의 필수 최종 검증 `cd services/core && go test ./...`가 미실행/미체크 상태입니다. 허용된 원격/검증 환경에서 명령을 실행하고 실제 stdout/stderr를 기록해야 합니다. + - Required: `agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/CODE_REVIEW-cloud-G07.md:47`의 필수 보조 검증 `cd services/core && go vet ./...`가 미실행/미체크 상태입니다. 허용된 원격/검증 환경에서 명령을 실행하고 실제 stdout/stderr를 기록해야 합니다. +- 다음 단계: WARN/FAIL 후속 루프를 생성한다. user-review gate는 트리거하지 않는다. diff --git a/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/plan_cloud_G07_0.log b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/plan_cloud_G07_0.log new file mode 100644 index 0000000..ff92105 --- /dev/null +++ b/agent-task/m-proto-socket-infrastructure-communication-rail/12+11_core_diagnostics/plan_cloud_G07_0.log @@ -0,0 +1,223 @@ + + +# Plan - API Core Proto-Socket Diagnostics + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 반드시 채운다. 구현 후 검증을 실행하고 active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거와 재개 조건을 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +Core proto-socket server는 연결과 broadcast 실패를 로그로 남기지만 connection id, protocol version, channel, action, error code를 구조화된 속성으로 남기지 않는다. Client debug surface도 Core가 보내는 response/event meta를 소비해야 하므로 먼저 Core가 안정적인 diagnostics meta와 로그를 제공해야 한다. 이 subtask는 계약 문서 subtask 완료 후 진행한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 차단 사유는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`를 기준으로 하며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/skills/common/router.md` +- `agent-ops/skills/common/plan/SKILL.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/workflow-core/PHASE.md` +- `agent-roadmap/phase/workflow-core/milestones/proto-socket-infrastructure-communication-rail.md` +- `agent-ops/rules/project/domain/core/rules.md` +- `agent-ops/rules/project/domain/contracts/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/core-smoke.md` +- `agent-test/local/contracts-smoke.md` +- `services/core/internal/protosocket/server.go` +- `services/core/internal/protosocket/dispatcher.go` +- `services/core/internal/protosocket/envelope.go` +- `services/core/internal/protosocket/events.go` +- `services/core/internal/protosocket/tasks.go` +- `services/core/internal/http/router.go` +- `services/core/internal/protosocket/server_test.go` +- `services/core/internal/protosocket/tasks_test.go` +- `services/core/internal/protosocket/envelope_test.go` +- `packages/contracts/notes/flutter-core-api-candidates.md` + +### 테스트 환경 규칙 + +`test_env=local`로 판단했고 `agent-test/local/rules.md`를 읽었다. 이 파일은 로컬 테스트 금지를 명시하므로 현 세션에서는 검증 명령을 실행하지 않는다. 적용 profile은 `core-smoke`이며 core 변경의 필수 검증은 `cd services/core && go test ./...`, 보조 검증은 `cd services/core && go vet ./...`다. 명령은 후속 구현/리뷰 에이전트가 허용된 원격/검증 환경에서 실행한다. + +### 테스트 커버리지 공백 + +- connection id가 response/event meta에 들어가는 동작: 기존 테스트 없음. `server_test.go` 또는 `tasks_test.go`에 추가 필요. +- protocol/channel/action/error_code 구조화 로그: 기존 테스트 없음. `slog` buffer 기반 테스트 추가 필요. +- 기존 task action 의미 보존: `tasks_test.go`가 create/list/get/enqueue, auth, error code를 이미 검증한다. + +### 심볼 참조 + +none. 기존 public symbol rename/remove 없이 server 내부 struct/helper를 추가한다. + +### 분할 판단 + +split decision policy를 평가했다. 이 subtask는 `12+11_core_diagnostics`이며 predecessor `11_contracts_diagnostics`의 `complete.log`가 필요하다. 현재 active/archive predecessor completion은 없음으로 확인했다. 구현은 `11` PASS 후 시작해야 한다. + +### 범위 결정 근거 + +Core proto-socket server, dispatcher/event helper, core tests만 수정한다. Flutter debug UI와 contracts note 수정은 각각 `13+11,12_client_diagnostics`, `11_contracts_diagnostics` 범위다. REST task API shape, auth policy, workflow service behavior, DB schema는 변경하지 않는다. + +### 빌드 등급 + +`cloud-G07`: protocol diagnostics, websocket connection lifecycle, structured logging, concurrency-safe client registry를 건드리므로 cloud lane과 높은 등급이 필요하다. + +## 구현 체크리스트 + +- [ ] `11_contracts_diagnostics`가 PASS되어 `complete.log`가 생겼는지 확인하고 경로를 review stub에 기록한다. +- [ ] Core proto-socket 연결마다 stable `connection_id`를 만들고 연결/해제/request/broadcast 로그에 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`를 구조화 속성으로 남긴다. +- [ ] response/event envelope `meta`에 diagnostics 필드를 additive로 채운다. +- [ ] Core 테스트로 diagnostics meta와 구조화 로그를 검증한다. +- [ ] `cd services/core && go test ./...`를 허용된 원격/검증 환경에서 실행한다. +- [ ] `cd services/core && go vet ./...`를 허용된 원격/검증 환경에서 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +이 directory name은 `12+11_core_diagnostics`이므로 predecessor는 같은 task group의 `11_*` subtask뿐이다. `agent-task/m-proto-socket-infrastructure-communication-rail/11_contracts_diagnostics/complete.log` 또는 matching archive `complete.log`가 생기기 전에는 구현을 시작하지 않는다. + +### [API-1] Core Connection Diagnostics + +#### 문제 + +[services/core/internal/protosocket/server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:20)는 clients map에 client pointer만 저장하고 connection id가 없다. 연결/해제 로그도 [server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:58)와 [server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:102)에서 generic message만 남긴다. + +Before: + +```go +20 type Server struct { +21 cfg Config +22 dispatcher *Dispatcher +23 logger *slog.Logger +24 mu sync.Mutex +25 clients map[*toki.WsClient]struct{} +26 } +... +58 client.AddDisconnectListener(func(c *toki.WsClient) { +59 s.mu.Lock() +60 delete(s.clients, c) +61 s.mu.Unlock() +62 s.logger.Info("websocket client disconnected") +63 }) +... +102 s.logger.Info("websocket client connected successfully") +``` + +#### 해결 방법 + +client registry 값을 diagnostics struct로 바꾸고 accept 시 `connection_id`를 생성한다. nil logger 가능성도 방어한다. 연결/해제/request 로그에는 contract subtask의 필드명을 사용한다. + +After: + +```go +type clientDiagnostics struct { + connectionID string +} + +type Server struct { + ... + clients map[*toki.WsClient]clientDiagnostics +} + +connectionID := "conn-" + generateID() +s.clients[client] = clientDiagnostics{connectionID: connectionID} +s.logInfo("proto-socket client connected", "connection_id", connectionID, "protocol_version", ProtocolVersion) +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `services/core/internal/protosocket/server.go`: client diagnostics registry와 log helper 추가. +- [ ] `services/core/internal/protosocket/server_test.go`: connection id가 response meta 또는 logs에 남는지 검증. + +#### 테스트 작성 + +`server_test.go`에 `slog.NewJSONHandler(&bytes.Buffer, nil)` 기반 테스트를 추가한다. 연결 성공/해제 또는 request 처리 후 JSON log에 `connection_id`, `protocol_version`이 포함되는지 assertion한다. + +#### 중간 검증 + +```bash +cd services/core && go test ./internal/protosocket -run 'TestServer.*Diagnostics|TestServerRespondsWithErrorEnvelopeForUnsupportedAction' -count=1 +``` + +기대 결과: 관련 tests PASS. 명령은 허용된 원격/검증 환경에서 실행한다. + +### [API-2] Core Envelope Meta And Error Logs + +#### 문제 + +[services/core/internal/protosocket/server.go](/config/workspace/nomadcode/services/core/internal/protosocket/server.go:65)는 request를 dispatch하지만 request/response diagnostics를 남기지 않는다. [services/core/internal/protosocket/events.go](/config/workspace/nomadcode/services/core/internal/protosocket/events.go:22)의 error response에는 code가 있지만 meta에는 diagnostics가 없다. + +Before: + +```go +65 toki.AddRequestListenerTyped[*structpb.Struct, *structpb.Struct](&client.Communicator, func(req *structpb.Struct) (*structpb.Struct, error) { +66 env, err := EnvelopeFromStruct(req) +... +82 resEnv := s.dispatcher.Dispatch(r.Context(), env) +83 resStruct, err := resEnv.ToStruct() +... +99 return resStruct, nil +100 }) +``` + +#### 해결 방법 + +`withDiagnosticsMeta(env Envelope, connectionID string) Envelope`와 `errorCode(env Envelope) string` helper를 추가한다. 모든 response/event 직전 meta에 `connection_id`, `protocol_version`, `channel`, `action`, `error_code`를 additive로 넣고, request 처리 로그도 같은 값을 남긴다. Broadcast는 client별 clone에 connection id를 채운 뒤 전송한다. + +After: + +```go +resEnv := withDiagnosticsMeta(s.dispatcher.Dispatch(r.Context(), env), connectionID) +s.logInfo("proto-socket request handled", + "connection_id", connectionID, + "protocol_version", resEnv.ProtocolVersion, + "channel", resEnv.Channel, + "action", resEnv.Action, + "error_code", errorCode(resEnv), +) +``` + +#### 수정 파일 및 체크리스트 + +- [ ] `services/core/internal/protosocket/server.go`: response/event meta 주입, request/broadcast log 추가. +- [ ] `services/core/internal/protosocket/events.go`: error/success response helper와 meta 주입 방식 충돌 여부 확인. +- [ ] `services/core/internal/protosocket/tasks_test.go`: task response/error envelope meta assertions 추가. +- [ ] `services/core/internal/protosocket/server_test.go`: broadcast meta/log assertions 추가. + +#### 테스트 작성 + +`tasks_test.go` route-level tests에서 `task.list` response와 error response의 `Meta["connection_id"]`, `Meta["protocol_version"]`, `Meta["channel"]`, `Meta["action"]`, `Meta["error_code"]`를 검증한다. `server_test.go` broadcast test는 client별 event meta에 connection id가 들어가는지 검증한다. + +#### 중간 검증 + +```bash +cd services/core && go test ./internal/protosocket -run 'TestRouterProtoSocketTaskListRequiresAuth|TestProtoSocketTaskErrorEnvelopeCodes|Test.*Broadcast.*' -count=1 +``` + +기대 결과: 관련 tests PASS. 명령은 허용된 원격/검증 환경에서 실행한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `services/core/internal/protosocket/server.go` | API-1, API-2 | +| `services/core/internal/protosocket/events.go` | API-2 | +| `services/core/internal/protosocket/server_test.go` | API-1, API-2 | +| `services/core/internal/protosocket/tasks_test.go` | API-2 | + +## 최종 검증 + +```bash +cd services/core && go test ./... +cd services/core && go vet ./... +``` + +기대 결과: 두 명령 모두 실패 없이 종료한다. Go test cache는 diagnostics 변경 확인에는 충분하지 않으므로 필요한 targeted tests는 `-count=1`로 중간 검증에서 fresh 실행한다. 명령은 허용된 원격/검증 환경에서 실행한다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/services/core/internal/protosocket/server.go b/services/core/internal/protosocket/server.go index 0328083..a3640df 100644 --- a/services/core/internal/protosocket/server.go +++ b/services/core/internal/protosocket/server.go @@ -5,6 +5,7 @@ import ( "log/slog" "net/http" "sync" + "time" toki "git.toki-labs.com/toki/proto-socket/go" "google.golang.org/protobuf/proto" @@ -22,7 +23,16 @@ type Server struct { dispatcher *Dispatcher logger *slog.Logger mu sync.Mutex - clients map[*toki.WsClient]struct{} + clients map[*toki.WsClient]clientDiagnostics +} + +type clientDiagnostics struct { + connectionID string +} + +type clientSnapshot struct { + client *toki.WsClient + diagnostic clientDiagnostics } func NewServer(cfg Config, logger *slog.Logger) *Server { @@ -30,7 +40,7 @@ func NewServer(cfg Config, logger *slog.Logger) *Server { cfg: cfg, dispatcher: NewDispatcher(), logger: logger, - clients: make(map[*toki.WsClient]struct{}), + clients: make(map[*toki.WsClient]clientDiagnostics), } } @@ -40,10 +50,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } conn, err := websocket.Accept(w, r, opts) if err != nil { - s.logger.Error("failed to accept websocket connection", "error", err) + s.logError("failed to accept websocket connection", "error", err) return } + connectionID := "conn-" + generateID() + diagnostic := clientDiagnostics{connectionID: connectionID} client := toki.NewWsClient( conn, s.cfg.HeartbeatIntervalSec, @@ -52,20 +64,21 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ) s.mu.Lock() - s.clients[client] = struct{}{} + s.clients[client] = diagnostic s.mu.Unlock() client.AddDisconnectListener(func(c *toki.WsClient) { s.mu.Lock() + diagnostic := s.clients[c] delete(s.clients, c) s.mu.Unlock() - s.logger.Info("websocket client disconnected") + s.logInfo("proto-socket client disconnected", diagnosticsLogAttrs(Envelope{ProtocolVersion: ProtocolVersion}, diagnostic.connectionID)...) }) toki.AddRequestListenerTyped[*structpb.Struct, *structpb.Struct](&client.Communicator, func(req *structpb.Struct) (*structpb.Struct, error) { env, err := EnvelopeFromStruct(req) if err != nil { - errEnv := Envelope{ + errEnv := withDiagnosticsMeta(Envelope{ ProtocolVersion: ProtocolVersion, ID: generateID(), CorrelationID: "", @@ -74,32 +87,37 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { Code: "INVALID_ENVELOPE", Message: "failed to parse envelope: " + err.Error(), }, - } + }, connectionID) + s.logError("proto-socket request rejected", append(diagnosticsLogAttrs(errEnv, connectionID), "error", err)...) res, _ := errEnv.ToStruct() return res, nil } - resEnv := s.dispatcher.Dispatch(r.Context(), env) + resEnv := withDiagnosticsMeta(s.dispatcher.Dispatch(r.Context(), env), connectionID) resStruct, err := resEnv.ToStruct() if err != nil { - errEnv := Envelope{ + errEnv := withDiagnosticsMeta(Envelope{ ProtocolVersion: ProtocolVersion, ID: generateID(), CorrelationID: env.ID, Type: "error", + Channel: env.Channel, + Action: env.Action, Error: &EnvelopeError{ Code: "SERIALIZATION_ERROR", Message: "failed to serialize response envelope: " + err.Error(), }, - } + }, connectionID) + s.logError("proto-socket response serialization failed", append(diagnosticsLogAttrs(errEnv, connectionID), "error", err)...) res, _ := errEnv.ToStruct() return res, nil } + s.logInfo("proto-socket request handled", diagnosticsLogAttrs(resEnv, connectionID)...) return resStruct, nil }) - s.logger.Info("websocket client connected successfully") + s.logInfo("proto-socket client connected", diagnosticsLogAttrs(Envelope{ProtocolVersion: ProtocolVersion}, connectionID)...) } func (s *Server) Close() error { @@ -117,23 +135,29 @@ func (s *Server) Close() error { } func (s *Server) BroadcastEnvelope(ctx context.Context, env Envelope) error { - structMsg, err := env.ToStruct() - if err != nil { - return err - } - s.mu.Lock() - clients := make([]*toki.WsClient, 0, len(s.clients)) - for cl := range s.clients { - clients = append(clients, cl) + clients := make([]clientSnapshot, 0, len(s.clients)) + for cl, diagnostic := range s.clients { + clients = append(clients, clientSnapshot{ + client: cl, + diagnostic: diagnostic, + }) } s.mu.Unlock() - for _, cl := range clients { - if cl.IsAlive() { - if err := cl.Send(structMsg); err != nil { - s.logger.Error("failed to send broadcast to client", "error", err) + for _, snapshot := range clients { + if snapshot.client.IsAlive() { + eventEnv := withDiagnosticsMeta(env, snapshot.diagnostic.connectionID) + structMsg, err := eventEnv.ToStruct() + if err != nil { + s.logError("proto-socket broadcast serialization failed", append(diagnosticsLogAttrs(eventEnv, snapshot.diagnostic.connectionID), "error", err)...) + return err } + if err := snapshot.client.Send(structMsg); err != nil { + s.logError("failed to send proto-socket broadcast", append(diagnosticsLogAttrs(eventEnv, snapshot.diagnostic.connectionID), "error", err)...) + continue + } + s.logInfo("proto-socket broadcast sent", diagnosticsLogAttrs(eventEnv, snapshot.diagnostic.connectionID)...) } } return nil @@ -151,3 +175,61 @@ func ParserMap() toki.ParserMap { }, } } + +func withDiagnosticsMeta(env Envelope, connectionID string) Envelope { + if env.ProtocolVersion == "" { + env.ProtocolVersion = ProtocolVersion + } + meta := make(map[string]any, len(env.Meta)+6) + for k, v := range env.Meta { + meta[k] = v + } + meta["connection_id"] = connectionID + meta["protocol_version"] = env.ProtocolVersion + meta["channel"] = env.Channel + meta["action"] = env.Action + meta["error_code"] = errorCode(env) + meta["timestamp"] = diagnosticsTimestamp() + env.Meta = meta + return env +} + +func errorCode(env Envelope) string { + if env.Error == nil { + return "" + } + return env.Error.Code +} + +func diagnosticsLogAttrs(env Envelope, connectionID string) []any { + protocolVersion := env.ProtocolVersion + if protocolVersion == "" { + protocolVersion = ProtocolVersion + } + return []any{ + "connection_id", connectionID, + "protocol_version", protocolVersion, + "channel", env.Channel, + "action", env.Action, + "error_code", errorCode(env), + "timestamp", diagnosticsTimestamp(), + } +} + +func diagnosticsTimestamp() string { + return time.Now().UTC().Format(time.RFC3339Nano) +} + +func (s *Server) logInfo(msg string, args ...any) { + if s.logger == nil { + return + } + s.logger.Info(msg, args...) +} + +func (s *Server) logError(msg string, args ...any) { + if s.logger == nil { + return + } + s.logger.Error(msg, args...) +} diff --git a/services/core/internal/protosocket/server_test.go b/services/core/internal/protosocket/server_test.go index f565957..29f3346 100644 --- a/services/core/internal/protosocket/server_test.go +++ b/services/core/internal/protosocket/server_test.go @@ -1,7 +1,9 @@ package protosocket import ( + "bytes" "context" + "encoding/json" "io" "log/slog" "net/http/httptest" @@ -10,10 +12,86 @@ import ( "time" toki "git.toki-labs.com/toki/proto-socket/go" + "github.com/nomadcode/nomadcode-core/internal/notification" "google.golang.org/protobuf/types/known/structpb" "nhooyr.io/websocket" ) +func requireDiagnosticsMeta(t *testing.T, env Envelope, channel, action, errorCode string) string { + t.Helper() + + if env.Meta == nil { + t.Fatal("expected diagnostics meta") + } + connectionID, ok := env.Meta["connection_id"].(string) + if !ok || !strings.HasPrefix(connectionID, "conn-") { + t.Fatalf("expected connection_id with conn- prefix, got %+v", env.Meta["connection_id"]) + } + if got := env.Meta["protocol_version"]; got != env.ProtocolVersion { + t.Errorf("expected meta protocol_version %q, got %+v", env.ProtocolVersion, got) + } + if got := env.Meta["channel"]; got != channel { + t.Errorf("expected meta channel %q, got %+v", channel, got) + } + if got := env.Meta["action"]; got != action { + t.Errorf("expected meta action %q, got %+v", action, got) + } + if got := env.Meta["error_code"]; got != errorCode { + t.Errorf("expected meta error_code %q, got %+v", errorCode, got) + } + if timestamp, ok := env.Meta["timestamp"].(string); !ok || timestamp == "" { + t.Errorf("expected non-empty meta timestamp, got %+v", env.Meta["timestamp"]) + } + return connectionID +} + +func requireJSONLog(t *testing.T, logs string, msg string, attrs map[string]string) { + t.Helper() + + for _, line := range strings.Split(strings.TrimSpace(logs), "\n") { + if line == "" { + continue + } + var record map[string]any + if err := json.Unmarshal([]byte(line), &record); err != nil { + t.Fatalf("failed to parse JSON log line %q: %v", line, err) + } + if record["msg"] != msg { + continue + } + for key, want := range attrs { + if got := record[key]; got != want { + t.Fatalf("log %q expected %s=%q, got %+v in record %+v", msg, key, want, got, record) + } + } + if got, ok := record["timestamp"].(string); !ok || got == "" { + t.Fatalf("log %q expected non-empty timestamp, got %+v in record %+v", msg, record["timestamp"], record) + } + return + } + t.Fatalf("log %q with attrs %+v not found in logs:\n%s", msg, attrs, logs) +} + +func waitForServerClients(t *testing.T, srv *Server, want int) { + t.Helper() + + deadline := time.Now().Add(1 * time.Second) + for time.Now().Before(deadline) { + srv.mu.Lock() + got := len(srv.clients) + srv.mu.Unlock() + if got == want { + return + } + time.Sleep(10 * time.Millisecond) + } + + srv.mu.Lock() + got := len(srv.clients) + srv.mu.Unlock() + t.Fatalf("expected %d server clients, got %d", want, got) +} + func TestServerAcceptsWebSocketAndClosesClients(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) srv := NewServer(Config{ @@ -58,6 +136,135 @@ func TestServerAcceptsWebSocketAndClosesClients(t *testing.T) { } } +func TestServerAddsDiagnosticsMetaAndStructuredLogs(t *testing.T) { + var logs bytes.Buffer + logger := slog.New(slog.NewJSONHandler(&logs, nil)) + srv := NewServer(Config{ + HeartbeatIntervalSec: 0, + HeartbeatWaitSec: 0, + }, logger) + defer srv.Close() + + testServer := httptest.NewServer(srv) + defer testServer.Close() + + wsURL := strings.Replace(testServer.URL, "http", "ws", 1) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + conn, _, err := websocket.Dial(ctx, wsURL, nil) + if err != nil { + t.Fatalf("failed to dial websocket: %v", err) + } + defer conn.Close(websocket.StatusNormalClosure, "") + + client := toki.NewWsClient(conn, 0, 0, ParserMap()) + defer client.Close() + + requestEnv := Envelope{ + ProtocolVersion: ProtocolVersion, + ID: "req-diagnostics", + Type: "request", + Channel: "task", + Action: "unsupported.diagnostics", + } + reqStruct, err := requestEnv.ToStruct() + if err != nil { + t.Fatalf("failed to create request struct: %v", err) + } + + resStruct, err := toki.SendRequestTyped[*structpb.Struct, *structpb.Struct](&client.Communicator, reqStruct, 1*time.Second) + if err != nil { + t.Fatalf("failed to send request: %v", err) + } + responseEnv, err := EnvelopeFromStruct(resStruct) + if err != nil { + t.Fatalf("failed to parse response struct: %v", err) + } + + connectionID := requireDiagnosticsMeta(t, responseEnv, "task", "unsupported.diagnostics", "UNSUPPORTED_ACTION") + requireJSONLog(t, logs.String(), "proto-socket request handled", map[string]string{ + "connection_id": connectionID, + "protocol_version": ProtocolVersion, + "channel": "task", + "action": "unsupported.diagnostics", + "error_code": "UNSUPPORTED_ACTION", + }) +} + +func TestServerBroadcastAddsDiagnosticsMetaAndStructuredLogs(t *testing.T) { + var logs bytes.Buffer + logger := slog.New(slog.NewJSONHandler(&logs, nil)) + srv := NewServer(Config{ + HeartbeatIntervalSec: 0, + HeartbeatWaitSec: 0, + }, logger) + defer srv.Close() + + testServer := httptest.NewServer(srv) + defer testServer.Close() + + wsURL := strings.Replace(testServer.URL, "http", "ws", 1) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + conn, _, err := websocket.Dial(ctx, wsURL, nil) + if err != nil { + t.Fatalf("failed to dial websocket: %v", err) + } + defer conn.Close(websocket.StatusNormalClosure, "") + + client := toki.NewWsClient(conn, 0, 0, ParserMap()) + defer client.Close() + + received := make(chan struct { + env Envelope + err error + }, 1) + toki.AddListenerTyped[*structpb.Struct](&client.Communicator, func(msg *structpb.Struct) { + env, err := EnvelopeFromStruct(msg) + received <- struct { + env Envelope + err error + }{env: env, err: err} + }) + + waitForServerClients(t, srv, 1) + + err = srv.BroadcastEnvelope(ctx, TaskStatusChangedEnvelope(notification.TaskEvent{ + Type: notification.TaskEventRunning, + TaskID: "task-diagnostics", + Status: "running", + Title: "Diagnostics", + Message: "running", + })) + if err != nil { + t.Fatalf("broadcast failed: %v", err) + } + + var got struct { + env Envelope + err error + } + select { + case got = <-received: + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for broadcast envelope") + } + if got.err != nil { + t.Fatalf("failed to parse broadcast envelope: %v", got.err) + } + + connectionID := requireDiagnosticsMeta(t, got.env, "task", "task.status.changed", "") + requireJSONLog(t, logs.String(), "proto-socket broadcast sent", map[string]string{ + "connection_id": connectionID, + "protocol_version": ProtocolVersion, + "channel": "task", + "action": "task.status.changed", + "error_code": "", + }) +} + func TestServerRespondsWithErrorEnvelopeForUnsupportedAction(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) srv := NewServer(Config{ diff --git a/services/core/internal/protosocket/tasks_test.go b/services/core/internal/protosocket/tasks_test.go index 0fcab6c..5675b3d 100644 --- a/services/core/internal/protosocket/tasks_test.go +++ b/services/core/internal/protosocket/tasks_test.go @@ -315,6 +315,7 @@ func TestRouterProtoSocketTaskListRequiresAuth(t *testing.T) { if _, ok := res.Payload["tasks"]; !ok { t.Errorf("expected tasks payload, got %+v", res.Payload) } + requireDiagnosticsMeta(t, res, "task", "task.list", "") } func TestProtoSocketTaskActionsMatchRestSemantics(t *testing.T) { @@ -363,6 +364,7 @@ func TestProtoSocketTaskActionsMatchRestSemantics(t *testing.T) { if create.Type != "response" || create.Payload["id"] != "task-c" || create.Payload["status"] != "pending" { t.Errorf("unexpected create response: %+v (err=%+v)", create.Payload, create.Error) } + requireDiagnosticsMeta(t, create, "task", "task.create", "") get := send("task.get", map[string]any{"id": "task-g"}) if get.Type != "response" { @@ -371,11 +373,13 @@ func TestProtoSocketTaskActionsMatchRestSemantics(t *testing.T) { if task, ok := get.Payload["task"].(map[string]any); !ok || task["status"] != "running" { t.Errorf("unexpected get task payload: %+v", get.Payload) } + requireDiagnosticsMeta(t, get, "task", "task.get", "") enqueue := send("task.enqueue", map[string]any{"id": "task-e"}) if enqueue.Type != "response" || enqueue.Payload["status"] != "queued" { t.Errorf("unexpected enqueue response: %+v", enqueue.Payload) } + requireDiagnosticsMeta(t, enqueue, "task", "task.enqueue", "") } func TestProtoSocketTaskErrorEnvelopeCodes(t *testing.T) { @@ -427,6 +431,7 @@ func TestProtoSocketTaskErrorEnvelopeCodes(t *testing.T) { if res.Error == nil || res.Error.Code != tc.code { t.Errorf("%s: expected code %q, got %+v", tc.action, tc.code, res.Error) } + requireDiagnosticsMeta(t, res, "task", tc.action, tc.code) } }