From ae5845cd6837e67340f4e981be9502151673e1b4 Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 26 Jul 2026 20:53:32 +0900 Subject: [PATCH] fix: post-commit refinements - Update runtime loop plan/code-review - Fix chat_handler, input_estimator_test, openai_request_rebuilder, provider_tunnel, responses_handler - Fix ingress_snapshot_test - Add code review and plan logs --- .../CODE_REVIEW-cloud-G07.md | 28 +-- .../19+18_core_runtime_loop/PLAN-local-G07.md | 34 +-- .../code_review_cloud_G07_5.log | 191 +++++++++++++++++ .../plan_local_G07_5.log | 195 ++++++++++++++++++ apps/edge/internal/openai/chat_handler.go | 6 +- .../internal/openai/input_estimator_test.go | 22 ++ .../openai/openai_request_rebuilder.go | 6 +- apps/edge/internal/openai/provider_tunnel.go | 48 +++-- .../edge/internal/openai/responses_handler.go | 8 +- .../go/streamgate/ingress_snapshot_test.go | 76 +++++++ 10 files changed, 554 insertions(+), 60 deletions(-) create mode 100644 agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log create mode 100644 agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log diff --git a/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md index 7dc7dcc..47080c9 100644 --- a/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md +++ b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md @@ -1,6 +1,6 @@ - + -# Code Review Reference - REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP +# Code Review Reference - REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP > **[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. @@ -14,12 +14,12 @@ ## 개요 date=2026-07-26 -task=m-stream-evidence-gate-core/19+18_core_runtime_loop, plan=5, tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP +task=m-stream-evidence-gate-core/19+18_core_runtime_loop, plan=6, tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP ## Archive Evidence Snapshot -- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_4.log` -- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_4.log` +- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log` +- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log` - 판정: `FAIL` - 문제 수: Required 3, Suggested 0, Nit 0 - 문제 요약: @@ -39,7 +39,7 @@ task=m-stream-evidence-gate-core/19+18_core_runtime_loop, plan=5, tag=REVIEW_REV 리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. 1. 판정을 append한다. -2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_5.log`, `PLAN-local-G07.md` → `plan_local_G07_5.log`로 아카이브한다. +2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_6.log`, `PLAN-local-G07.md` → `plan_local_G07_6.log`로 아카이브한다. 3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-stream-evidence-gate-core/19+18_core_runtime_loop/`로 이동한다. WARN/FAIL이면 code-review skill이 요구하는 다음 filesystem state를 완전히 작성한다. 4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. 5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. @@ -50,15 +50,15 @@ task=m-stream-evidence-gate-core/19+18_core_runtime_loop, plan=5, tag=REVIEW_REV | 항목 | 완료 여부 | |------|---------| -| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 request-local owner lifecycle | [ ] | -| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake-host lifecycle fixtures | [ ] | -| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 검증과 evidence 복구 | [ ] | +| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 request-local owner lifecycle | [ ] | +| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake-host lifecycle fixtures | [ ] | +| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 검증과 evidence 복구 | [ ] | ## 구현 체크리스트 -- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다. -- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, 0/1/3 exhausted single-terminal fixture를 추가한다. -- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate focused/unit/race, formatting/import boundary, diff 검증을 fresh 실행하고 review evidence를 복구한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, 0/1/3 exhausted single-terminal fixture를 추가한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate focused/unit/race, formatting/import boundary, diff 검증을 fresh 실행하고 review evidence를 복구한다. - [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. ## 코드리뷰 전용 체크리스트 @@ -68,8 +68,8 @@ task=m-stream-evidence-gate-core/19+18_core_runtime_loop, plan=5, tag=REVIEW_REV - [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. - [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. -- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_5.log`로 아카이브한다. -- [ ] active `PLAN-*-G??.md`를 `plan_local_G07_5.log`로 아카이브한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_6.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G07_6.log`로 아카이브한다. - [ ] `.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-stream-evidence-gate-core/19+18_core_runtime_loop/`를 `agent-task/archive/YYYY/MM/m-stream-evidence-gate-core/19+18_core_runtime_loop/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. diff --git a/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/PLAN-local-G07.md b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/PLAN-local-G07.md index e232d31..4bd0fd2 100644 --- a/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/PLAN-local-G07.md +++ b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/PLAN-local-G07.md @@ -1,4 +1,4 @@ - + # Plan - Stream Gate owner loop 구현 재복구 @@ -8,12 +8,12 @@ ## 배경 -다섯 번째 구현 루프도 active review의 완료표와 evidence를 비운 채 `RequestRuntimeSnapshot` 기준선만 남겼다. 필수 owner API와 lifecycle fixture가 없어 package PASS가 실제 request-local lifecycle을 실행하지 않는다. 이 후속은 transport-agnostic 경계를 유지하며 owner loop, fake-host 검증, 신뢰 가능한 review evidence를 한 번에 닫는다. +여섯 번째 구현 루프도 active review의 완료표와 evidence를 비운 채 `RequestRuntimeSnapshot` 기준선만 남겼다. 필수 owner API와 lifecycle fixture가 없어 package PASS가 실제 request-local lifecycle을 실행하지 않는다. 이 후속은 transport-agnostic 경계를 유지하며 owner loop, fake-host 검증, 신뢰 가능한 review evidence를 한 번에 닫는다. ## Archive Evidence Snapshot -- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_4.log` -- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_4.log` +- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log` +- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log` - 판정: `FAIL` - 문제 수: Required 3, Suggested 0, Nit 0 - 문제 요약: @@ -31,8 +31,8 @@ - `packages/go/streamgate/runtime.go` - `packages/go/streamgate/runtime_contract_test.go` -- `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_4.log` -- `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_4.log` +- `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log` +- `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log` - `agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log` - `agent-roadmap/current.md` - `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md` @@ -102,12 +102,12 @@ ## 구현 체크리스트 -- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다. -- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, 0/1/3 exhausted single-terminal fixture를 추가한다. -- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate focused/unit/race, formatting/import boundary, diff 검증을 fresh 실행하고 review evidence를 복구한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, 0/1/3 exhausted single-terminal fixture를 추가한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate focused/unit/race, formatting/import boundary, diff 검증을 fresh 실행하고 review evidence를 복구한다. - [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. -### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1] request-local owner lifecycle +### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1] request-local owner lifecycle - 문제: `packages/go/streamgate/runtime.go:113-296`은 immutable snapshot과 `BuildAttemptFilterContext`에서 끝난다. initial binding 설치, event owner, staged response, attempt-local filter resolve, evidence epoch, arbitration, recovery rebinding, terminal convergence가 없다. - 해결 방법: @@ -142,7 +142,7 @@ func (r *RequestRuntime) Run(ctx context.Context) error - 테스트 작성: 작성. `runtime_test.go`의 세 public-entry test가 normal/boundary/concurrency path를 실행한다. - 중간 검증: `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime(DisabledAndPass|RecoveryLifecycle|FailureMatrix)$'`가 `[no tests to run]` 없이 PASS해야 한다. -### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2] fake-host lifecycle fixtures +### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2] fake-host lifecycle fixtures - 문제: `packages/go/streamgate/runtime_test.go:1`이 없고 기존 `runtime_contract_test.go`는 owner lifecycle을 실행하지 않는다. - 해결 방법: @@ -158,9 +158,9 @@ func (r *RequestRuntime) Run(ctx context.Context) error - 테스트 작성: 작성. wall-clock sleep 대신 channel/barrier와 side-effect order/count를 사용한다. - 중간 검증: `go test -race -count=1 ./packages/go/streamgate -run '^TestRequestRuntime'`가 race와 `[no tests to run]` 없이 PASS해야 한다. -### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3] 검증과 evidence 복구 +### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3] 검증과 evidence 복구 -- 문제: `code_review_cloud_G07_4.log:49-191`의 구현 소유 완료/evidence가 비어 있고, 현재 재실행도 누락 테스트 때문에 lifecycle을 증명하지 못한다. +- 문제: `code_review_cloud_G07_5.log:49-175`의 구현 소유 완료/evidence가 비어 있고, 현재 재실행도 누락 테스트 때문에 lifecycle을 증명하지 못한다. - 해결 방법: 고정 명령을 fresh 실행하고 active review의 완료표·체크리스트·계획 대비 변경·주요 설계 결정·각 `_실제 출력:_`을 채운다. 명령 변경 시 이유와 대체 명령을 `계획 대비 변경 사항`에 기록한다. - 수정 파일 및 체크리스트: - [ ] production/test 파일을 gofmt하고 Core import boundary를 확인한다. @@ -171,15 +171,15 @@ func (r *RequestRuntime) Run(ctx context.Context) error ## 의존 관계 및 구현 순서 -`18` (`agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`, PASS) → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3. 후속 `20`은 이 subtask PASS 이후 Core owner를 Edge host에 연결한다. +`18` (`agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`, PASS) → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3. 후속 `20`은 이 subtask PASS 이후 Core owner를 Edge host에 연결한다. ## 수정 파일 요약 | 파일 | 항목 | |---|---| -| `packages/go/streamgate/runtime.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 | -| 새 `packages/go/streamgate/runtime_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1/2 | -| `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 evidence | +| `packages/go/streamgate/runtime.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 | +| 새 `packages/go/streamgate/runtime_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1/2 | +| `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 evidence | ## 최종 검증 diff --git a/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log new file mode 100644 index 0000000..f6bd998 --- /dev/null +++ b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_5.log @@ -0,0 +1,191 @@ + + +# Code Review Reference - REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP + +> **[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, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields. +> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state. +> 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-07-26 +task=m-stream-evidence-gate-core/19+18_core_runtime_loop, plan=5, tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP + +## Archive Evidence Snapshot + +- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_4.log` +- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_4.log` +- 판정: `FAIL` +- 문제 수: Required 3, Suggested 0, Nit 0 +- 문제 요약: + - `packages/go/streamgate/runtime.go`에 request-local owner와 initial/recovery 공통 binding 설치 경로가 없다. + - `packages/go/streamgate/runtime_test.go`가 없어 focused lifecycle 명령이 `[no tests to run]`으로 끝난다. + - active review 구현 체크·설계 결정·실제 검증 출력이 비어 있고 formatting/import 검증은 누락 파일 때문에 rc=2다. +- 영향 파일: `packages/go/streamgate/runtime.go`, `packages/go/streamgate/runtime_test.go`, active review evidence +- 실제 검증 evidence: Go `1.26.2`, GOROOT `/config/opt/go`; `make proto`, package unit/race, `git diff --check`는 PASS했다. focused lifecycle은 `[no tests to run]`, `gofmt`와 import boundary는 `runtime_test.go` 부재로 rc=2였다. +- 기본 재열람은 불필요하다. 세부 판정이 필요할 때만 위 두 archive 파일을 좁게 읽는다. +- Roadmap carryover: 이 subtask 단독 PASS는 Milestone Task id 완료를 주장하지 않으므로 `Roadmap Targets`를 두지 않는다. + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_5.log`, `PLAN-local-G07.md` → `plan_local_G07_5.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-stream-evidence-gate-core/19+18_core_runtime_loop/`로 이동한다. WARN/FAIL이면 code-review skill이 요구하는 다음 filesystem state를 완전히 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 request-local owner lifecycle | [ ] | +| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake-host lifecycle fixtures | [ ] | +| REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 검증과 evidence 복구 | [ ] | + +## 구현 체크리스트 + +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, 0/1/3 exhausted single-terminal fixture를 추가한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate focused/unit/race, formatting/import boundary, diff 검증을 fresh 실행하고 review evidence를 복구한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_5.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G07_5.log`로 아카이브한다. +- [ ] `.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-stream-evidence-gate-core/19+18_core_runtime_loop/`를 `agent-task/archive/YYYY/MM/m-stream-evidence-gate-core/19+18_core_runtime_loop/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-stream-evidence-gate-core/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이면 code-review skill의 판정에 맞는 다음 filesystem state를 작성하고 `complete.log`를 작성하지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 리뷰어를 위한 체크포인트 + +- initial binding과 recovery binding이 같은 `installAttempt` 경로에서 actual target의 active filter를 다시 resolve하고 Core가 raw codec/proto/HTTP/concrete admission을 import하지 않는가. +- response-start는 first safe release 전 staged 상태이며 replace는 reset, continuation은 committed look-behind/cursor와 downstream stream을 보존하는가. +- 한 epoch/cycle에서 all-complete arbitration 뒤 plan/dispatch/action이 하나이고 terminal sink 호출이 정확히 한 번인가. +- fake host가 prepare ordering, binding switch, failure policy, backpressure, cancel, 0/1/3 cap exhaustion을 side-effect count/order로 실제 증명하는가. +- focused command가 세 lifecycle test를 실제 실행하고 package unit/race PASS와 구분되는가. + +## 검증 결과 + +> 아래 명령을 그대로 실행하고 각 `_실제 출력:_` 아래에 stdout/stderr를 붙인다. 명령을 바꾸면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. + +### Environment + +- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT` + +_실제 출력:_ + +### Proto setup + +- `make proto` + +_실제 출력:_ + +### Formatting + +- `gofmt -d packages/go/streamgate/runtime.go packages/go/streamgate/runtime_test.go` + +_실제 출력:_ + +### Focused lifecycle + +- `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime(DisabledAndPass|RecoveryLifecycle|FailureMatrix)$'` + +_실제 출력:_ + +### Focused lifecycle race + +- `go test -race -count=1 ./packages/go/streamgate -run '^TestRequestRuntime'` + +_실제 출력:_ + +### Package unit + +- `go test -count=1 ./packages/go/streamgate` + +_실제 출력:_ + +### Package race + +- `go test -race -count=1 ./packages/go/streamgate` + +_실제 출력:_ + +### Import boundary + +- `rg -n --sort path 'iop/apps/' packages/go/streamgate/runtime.go packages/go/streamgate/runtime_test.go` + +_실제 출력:_ + +### Diff + +- `git diff --check` + +_실제 출력:_ + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +## 섹션 소유권 + +| 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 | +| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required | +| Agent UI Completion | Mixed | Present only for plan-required agent-ui code work; implementing agent fills actual evidence, review agent applies `구현됨` status/evidence update on PASS and copies the section into `complete.log` | +| 구현 항목별 완료 여부 (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 | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content | +| 리뷰어를 위한 체크포인트 | 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: Fail — 계획한 request-local owner와 실행 루프가 구현되지 않아 normalized event를 release/recovery/terminal로 수렴시키는 경로가 없다. + - completeness: Fail — 세 구현 항목, 구현 체크리스트, 설계 결정과 검증 evidence가 모두 미완료다. + - test coverage: Fail — 필수 lifecycle fixture 파일과 세 public-entry test가 없으며 focused 명령이 `[no tests to run]`으로 끝난다. + - API contract: Fail — 계획과 승인 SDD가 요구한 `RequestRuntime` constructor/`Run`/공통 attempt 설치 계약이 없다. + - code quality: Pass — 이번 루프가 추가한 구현이 없어 별도의 debug/dead-code/formatting 문제는 관찰되지 않았다. + - implementation deviation: Fail — 구현 범위가 `RequestRuntimeSnapshot` 기준선에 머물러 계획의 owner lifecycle 전체를 누락했다. + - verification trust: Fail — active review의 실제 출력이 비어 있고 formatting/import-boundary 명령은 누락 파일로 rc=2다. +- 발견된 문제: + - Required — `packages/go/streamgate/runtime.go:113`: 파일이 immutable snapshot과 `BuildAttemptFilterContext`에서 끝난다. 계획한 `RequestRuntime`, `NewRequestRuntime`, `Run`, shared `installAttempt`를 구현하고 initial/recovery binding 재해석, staged response, all-complete arbitration, recovery/terminal single-owner loop를 연결해야 한다. + - Required — `packages/go/streamgate/runtime_test.go:1`: 파일과 `TestRequestRuntimeDisabledAndPass`, `TestRequestRuntimeRecoveryLifecycle`, `TestRequestRuntimeFailureMatrix`가 없다. fake host의 side-effect 순서/횟수로 disabled/pass, recovery, binding switch, prepare failure, backpressure/cancel, 0/1/3 cap과 single terminal을 검증해야 한다. + - Required — `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md:49`: 구현 완료표·체크리스트·설계 결정·모든 실제 검증 출력이 비어 있다. 구현 후 고정 명령을 fresh 실행해 evidence를 채우고, focused lifecycle에서 실제 세 테스트가 실행됐음을 증명해야 한다. +- 다음 단계: plan 스킬을 `prepare-follow-up`으로 호출해 fresh routing된 후속 PLAN/CODE_REVIEW 쌍을 작성한다. diff --git a/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log new file mode 100644 index 0000000..e232d31 --- /dev/null +++ b/agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_5.log @@ -0,0 +1,195 @@ + + +# Plan - Stream Gate owner loop 구현 재복구 + +## 이 파일을 읽는 구현 에이전트에게 + +구현과 테스트, 실제 검증 evidence만 소유한다. 아래 체크리스트를 끝낸 뒤 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 stdout/stderr로 채우고 active pair를 그대로 둔 채 리뷰 준비 완료를 보고한다. 차단 시에는 정확한 blocker, 시도한 명령/출력, 재개 조건만 구현 소유 evidence에 기록한다. 사용자에게 결정을 묻거나 user-input 도구·control-plane stop 파일을 만들거나 다음 상태를 분류하지 않으며, log archive·`complete.log`·roadmap·`WORK_LOG.md`는 수정하지 않는다. + +## 배경 + +다섯 번째 구현 루프도 active review의 완료표와 evidence를 비운 채 `RequestRuntimeSnapshot` 기준선만 남겼다. 필수 owner API와 lifecycle fixture가 없어 package PASS가 실제 request-local lifecycle을 실행하지 않는다. 이 후속은 transport-agnostic 경계를 유지하며 owner loop, fake-host 검증, 신뢰 가능한 review evidence를 한 번에 닫는다. + +## Archive Evidence Snapshot + +- 이전 Plan: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_4.log` +- 이전 Review: `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_4.log` +- 판정: `FAIL` +- 문제 수: Required 3, Suggested 0, Nit 0 +- 문제 요약: + - `packages/go/streamgate/runtime.go`에 request-local owner와 initial/recovery 공통 binding 설치 경로가 없다. + - `packages/go/streamgate/runtime_test.go`가 없어 focused lifecycle 명령이 `[no tests to run]`으로 끝난다. + - active review 구현 체크·설계 결정·실제 검증 출력이 비어 있고 formatting/import 검증은 누락 파일 때문에 rc=2다. +- 영향 파일: `packages/go/streamgate/runtime.go`, `packages/go/streamgate/runtime_test.go`, active review evidence +- 실제 검증 evidence: Go `1.26.2`, GOROOT `/config/opt/go`; `make proto`, package unit/race, `git diff --check`는 PASS했다. focused lifecycle은 `[no tests to run]`, `gofmt`와 import boundary는 `runtime_test.go` 부재로 rc=2였다. +- 기본 재열람은 불필요하다. 세부 판정이 필요할 때만 위 두 archive 파일을 좁게 읽는다. +- Roadmap carryover: 이 subtask 단독 PASS는 Milestone Task id 완료를 주장하지 않으므로 `Roadmap Targets`를 두지 않는다. + +## 분석 결과 + +### 읽은 파일 + +- `packages/go/streamgate/runtime.go` +- `packages/go/streamgate/runtime_contract_test.go` +- `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/plan_local_G07_4.log` +- `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/code_review_cloud_G07_4.log` +- `agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md` +- `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md` +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/project/domain/platform-common/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/common/rules-agent-spec.md` +- `agent-spec/index.md` +- `agent-contract/index.md` +- `agent-test/local/rules.md` +- `agent-test/local/platform-common-smoke.md` + +### SDD 기준 + +- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`, 사용자 리뷰 `없음`. +- 대상 Acceptance Scenario는 S03/S05 staged response·commit eligibility, S12/S13 all-complete/single action, S14 cycle당 단일 plan/dispatch와 cap, S16 vertical lifecycle, S18 continuation 보존, S20 abort failure no-dispatch, S23 one-shot prepare다. +- Evidence Map의 eager commit 금지, complete outcome set, single plan/dispatch/terminal, replace reset/continuation preserve, prepare success/failure/no-budget, binding switch/backpressure/cancel/exhausted fixture를 구현 항목 1/2와 fresh unit/race 검증에 직접 연결한다. +- 이 subtask는 `recovery-coordinator`나 `vertical-slice` Roadmap Task 전체를 단독 완료하지 않으므로 Roadmap Task id를 체크하지 않는다. + +### 테스트 환경 규칙 + +- `test_env=local`; `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다. +- PATH의 host Go는 `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go1.26.2`, GOROOT `/config/opt/go`로 일치한다. +- profile의 `make proto`를 setup으로 실행한다. transport-agnostic package의 실제 동작은 fresh package focused/unit/race로 검증하며 cache를 허용하지 않는다. +- 외부 runner, Docker, provider, port, credential은 필요 없다. repo-local 검증 산출물은 만들지 않는다. +- agent-spec index에는 `streamgate` 매칭 living spec이 없고, agent-contract index에도 이 transport-agnostic owner 전용 계약은 없다. 승인 SDD, 코드, 테스트를 기준으로 판정한다. + +### 테스트 커버리지 공백 + +- `runtime_contract_test.go`는 options, snapshot validation, filter context builder만 검증한다. +- owner construction/Run, response staging, initial/recovery binding re-resolution, arbitration action, prepare/dispatch ordering, terminal convergence를 실행하는 테스트가 없다. +- 새 `runtime_test.go`의 세 public-entry fixture가 disabled/pass, recovery, failure/concurrency matrix를 모두 실행해야 한다. + +### 심볼 참조 + +- rename/remove 대상은 없다. +- `rg` 기준 `RequestRuntime`, `NewRequestRuntime`, `installAttempt` 참조는 없고 `NormalizedEventSource`/`AttemptBinding`은 Core 계약과 Edge 후속 dispatcher에서만 사용된다. + +### 분할 판단 + +- split decision policy를 파일명 확정 전에 평가했다. +- 현재 고정 subtask의 owner state와 fake-host assertions는 같은 public API와 side-effect count를 함께 검증해야 하므로 production/test 분리는 독립 PASS 단위를 만들지 못한다. +- primary ownership은 `packages/go/streamgate` 하나이며 Edge codec/admission/HTTP 연결과 observation은 기존 sibling에 분리돼 있다. 현재 두 파일 범위를 더 쪼개면 artificial coordination만 늘어난다. +- 선행 index `18`은 `agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`의 PASS로 충족됐고 현재 subtask 경로의 dependency metadata는 유지한다. + +### 범위 결정 근거 + +- 수정 범위는 `packages/go/streamgate/runtime.go`, 새 `packages/go/streamgate/runtime_test.go`, active review evidence다. +- raw codec/proto, concrete admission/auth/model selection, OpenAI request rebuild, HTTP/SSE serialization, config, observation sink, Edge handler와 roadmap 문서는 제외한다. +- 기존 `FilterRegistrySnapshot`, `EvidenceTail`, `GateCoordinator`, `DecisionArbiter`, `RecoveryCoordinator`, `StreamReleaser` 계약을 조립하며 해당 component를 재구현하거나 `apps/*/internal`을 import하지 않는다. + +### 최종 라우팅 + +- evaluation_mode: `isolated-reassessment`; finalizer: `finalize-task-policy.sh`; finalizer_mode: `pair`. +- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`. + - scope: owner lifecycle, fake-host fixture, review evidence가 `runtime.go`, 새 `runtime_test.go`, active review로 닫힌다. + - context: 승인 SDD state machine, 기존 snapshot·host seam, package component와 현재 실패 출력을 함께 유지할 수 있다. + - verification/evidence: owner symbol과 test 파일 부재, `[no tests to run]`, rc=2 formatting/import 실패가 재현됐고 fresh focused/unit/race로 성공을 결정할 수 있다. + - ownership/decision: `packages/go/streamgate` 단일 소유이며 Milestone 잠금은 해제됐고 결정 필요가 없다. +- build route_basis: `local-fit`; capability_gap: `none`; grade scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1, 합계 G07. +- build loop risk: state 16개와 retry/cancel/recovery/duplicate 경로, owner·Gate·filter evaluator 3 actor와 single-flight/exactly-once 제약, 7개 Core component immutable boundary, commit×resume×enforcement×cap variant 축이 있다. structured interpretation은 없다. matched signatures는 `temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product`; triggered=true. +- build output: lane=`local`, grade=`G07`, filename=`PLAN-local-G07.md`. +- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`; 공식 reviewer가 SDD, owner state, concurrency fixture와 실제 명령 출력을 독립 대조할 수 있다. +- review route_basis: `official-review`; capability_gap: `none`; grade scores: scope_coupling=2, state_concurrency=2, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1, 합계 G07. +- review output: lane=`cloud`, grade=`G07`, filename=`CODE_REVIEW-cloud-G07.md`, target=`Codex gpt-5.6-sol xhigh`. + +## 구현 체크리스트 + +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 initial/recovery binding을 같은 attempt 설치 경로로 연결하고 normalized event → evidence/evaluation/arbitration → release/recovery/terminal request-local owner loop를 구현한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 fake host로 disabled/pass, one recovery, simultaneous violation, prepare success/failure, binding switch, failure policy, backpressure, cancel, 0/1/3 exhausted single-terminal fixture를 추가한다. +- [ ] REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 environment/proto setup, streamgate focused/unit/race, formatting/import boundary, diff 검증을 fresh 실행하고 review evidence를 복구한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. + +### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1] request-local owner lifecycle + +- 문제: `packages/go/streamgate/runtime.go:113-296`은 immutable snapshot과 `BuildAttemptFilterContext`에서 끝난다. initial binding 설치, event owner, staged response, attempt-local filter resolve, evidence epoch, arbitration, recovery rebinding, terminal convergence가 없다. +- 해결 방법: + - `import ("context"; "errors")`를 사용해 host initial `AttemptBinding`과 model group을 받는 `RequestRuntime`/constructor/`Run(context.Context)`를 추가한다. + - private `installAttempt(binding, resumeMode)` 하나가 initial/recovery binding을 검증하고 actual model/provider/path로 fixed registry snapshot의 active set을 다시 resolve한다. replace는 pending/look-behind/cursor를 reset하고 continuation은 committed look-behind/cursor와 downstream commit state를 보존한다. + - response-start는 stage만 하고 나머지 normalized event는 tail → immutable batch → Gate all-complete → Arbiter single action으로 직렬화한다. release/terminal은 `StreamReleaser`, recovery는 request-local `RecoveryCoordinator`만 호출한다. + - cycle/terminal/stale guard로 plan/dispatch/terminal 중복과 terminal 이후 sink 호출을 거부하고 모든 종료 경로에서 Gate를 닫는다. +- Before (`packages/go/streamgate/runtime.go:115`): + +```go +type RequestRuntimeSnapshot struct { + // immutable request state and host seams only +} +``` + +- After: + +```go +type RequestRuntime struct { + snapshot RequestRuntimeSnapshot + // request-local attempt, evidence, gate, recovery, release and terminal state +} + +func NewRequestRuntime(snapshot RequestRuntimeSnapshot, modelGroup string, initial AttemptBinding) (*RequestRuntime, error) +func (r *RequestRuntime) Run(ctx context.Context) error +``` + +- 수정 파일 및 체크리스트: + - [ ] `packages/go/streamgate/runtime.go`: owner constructor/state와 shared attempt install을 추가한다. + - [ ] `packages/go/streamgate/runtime.go`: staged response/event/evidence/all-complete arbitration action loop를 추가한다. + - [ ] `packages/go/streamgate/runtime.go`: replace/continuation rebinding과 cancel/error/cap single-terminal convergence를 추가한다. +- 테스트 작성: 작성. `runtime_test.go`의 세 public-entry test가 normal/boundary/concurrency path를 실행한다. +- 중간 검증: `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime(DisabledAndPass|RecoveryLifecycle|FailureMatrix)$'`가 `[no tests to run]` 없이 PASS해야 한다. + +### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2] fake-host lifecycle fixtures + +- 문제: `packages/go/streamgate/runtime_test.go:1`이 없고 기존 `runtime_contract_test.go`는 owner lifecycle을 실행하지 않는다. +- 해결 방법: + - 새 테스트는 `import ("context"; "errors"; "sync"; "testing"; "time")`를 사용하고 deterministic fake event source/controller/dispatcher/rebuilder/preparer/sink와 injected filter를 둔다. + - disabled/Noop pass에서 staged response-start가 first safe body 직전에 한 번 commit되는지 검증한다. + - violation/conflict에서 cycle당 plan/abort/rebuild/dispatch 하나, recovery binding target re-resolution, prepare success ordering과 failure no-dispatch/no-budget를 검증한다. + - replace reset, continuation safe-prefix/look-behind/cursor 보존, blocking/observe-only failure, channel barrier backpressure, caller cancel, 0/1/3 cap exhaustion과 terminal sink 정확히 1회를 table-driven assertion으로 고정한다. +- 수정 파일 및 체크리스트: + - [ ] 새 `packages/go/streamgate/runtime_test.go`: thread-safe fake host/filter/recorder를 추가한다. + - [ ] `TestRequestRuntimeDisabledAndPass`: disabled와 enabled Noop staging/release를 검증한다. + - [ ] `TestRequestRuntimeRecoveryLifecycle`: single conflict recovery, prepare, binding switch, replace/continue를 검증한다. + - [ ] `TestRequestRuntimeFailureMatrix`: enforcement, backpressure, cancel, exhaustion과 single terminal을 검증한다. +- 테스트 작성: 작성. wall-clock sleep 대신 channel/barrier와 side-effect order/count를 사용한다. +- 중간 검증: `go test -race -count=1 ./packages/go/streamgate -run '^TestRequestRuntime'`가 race와 `[no tests to run]` 없이 PASS해야 한다. + +### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3] 검증과 evidence 복구 + +- 문제: `code_review_cloud_G07_4.log:49-191`의 구현 소유 완료/evidence가 비어 있고, 현재 재실행도 누락 테스트 때문에 lifecycle을 증명하지 못한다. +- 해결 방법: 고정 명령을 fresh 실행하고 active review의 완료표·체크리스트·계획 대비 변경·주요 설계 결정·각 `_실제 출력:_`을 채운다. 명령 변경 시 이유와 대체 명령을 `계획 대비 변경 사항`에 기록한다. +- 수정 파일 및 체크리스트: + - [ ] production/test 파일을 gofmt하고 Core import boundary를 확인한다. + - [ ] environment/setup/focused/unit/race/diff 명령을 fresh 실행한다. + - [ ] active review 구현 소유 섹션에 실제 stdout/stderr와 완료 evidence를 기록한다. +- 테스트 작성: 별도 테스트 없음. 항목 1/2의 실제 실행 trust를 닫는 evidence 작업이다. +- 중간 검증: `git diff --check` 출력이 없고 active review에 placeholder `_실제 출력:_`만 남지 않아야 한다. + +## 의존 관계 및 구현 순서 + +`18` (`agent-task/archive/2026/07/m-stream-evidence-gate-core/18+14,16_runtime_contract/complete.log`, PASS) → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-2 → REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3. 후속 `20`은 이 subtask PASS 이후 Core owner를 Edge host에 연결한다. + +## 수정 파일 요약 + +| 파일 | 항목 | +|---|---| +| `packages/go/streamgate/runtime.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1 | +| 새 `packages/go/streamgate/runtime_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-1/2 | +| `agent-task/m-stream-evidence-gate-core/19+18_core_runtime_loop/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP-3 evidence | + +## 최종 검증 + +- `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT` — host Go/GOROOT 일치. +- `make proto` — setup 성공. +- `gofmt -d packages/go/streamgate/runtime.go packages/go/streamgate/runtime_test.go` — 출력 없음. +- `go test -count=1 ./packages/go/streamgate -run '^TestRequestRuntime(DisabledAndPass|RecoveryLifecycle|FailureMatrix)$'` — fresh focused PASS, `[no tests to run]` 없음. +- `go test -count=1 ./packages/go/streamgate` — fresh package PASS. +- `go test -race -count=1 ./packages/go/streamgate` — fresh race PASS. +- `rg -n --sort path 'iop/apps/' packages/go/streamgate/runtime.go packages/go/streamgate/runtime_test.go` — 출력 없음. +- `git diff --check` — 출력 없음. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. diff --git a/apps/edge/internal/openai/chat_handler.go b/apps/edge/internal/openai/chat_handler.go index aa92133..073b3a0 100644 --- a/apps/edge/internal/openai/chat_handler.go +++ b/apps/edge/internal/openai/chat_handler.go @@ -265,9 +265,10 @@ func (s *Server) handleChatCompletionsProviderPool(w http.ResponseWriter, dc *ch // strict-output output policy only applies to normalized dispatch, // not to raw tunnel passthrough (SDD D02). The caller's raw body is the // passthrough source; only the model field is rewritten to the served target. - poolReq.Tunnel.BuildBody = func(target string) ([]byte, error) { + bodyBuilder := newOpenAIProviderBodyBuilder(func(target string) (*openAIRebuiltLease, error) { return rewriteChatCompletionModelFromIngress(dc.ingress, target, req) - } + }) + poolReq.Tunnel.BuildBody = bodyBuilder.BuildBody // Normalized branch: chat completions use lenient decode so unknown // fields survive; PrepareRun passes through the already-built Run @@ -278,6 +279,7 @@ func (s *Server) handleChatCompletionsProviderPool(w http.ResponseWriter, dc *ch } result, err := s.service.SubmitProviderPool(r.Context(), poolReq) + bodyBuilder.Close() if err != nil { emitUsageMetrics(dc.usageLabels(s, responseModePassthrough), usageStatusForError(err), usageObservation{}) // Provider auth failure is a client request error (400), not a diff --git a/apps/edge/internal/openai/input_estimator_test.go b/apps/edge/internal/openai/input_estimator_test.go index 5067e09..56c10c9 100644 --- a/apps/edge/internal/openai/input_estimator_test.go +++ b/apps/edge/internal/openai/input_estimator_test.go @@ -125,3 +125,25 @@ func TestClassifyContext_AtThreshold(t *testing.T) { t.Fatalf("expected normal at threshold-1 %d", threshold-1) } } + +func TestEstimateInputTokensBytesMatchesString(t *testing.T) { + inputs := [][]byte{ + nil, + []byte("ASCII request body"), + []byte("다국어 요청 🚀"), + {'a', 0xff, 'b', 0xc0, 0xaf}, + } + metadata := map[string]string{"workspace": "테스트", "request_id": "req-1"} + tools := []any{map[string]any{ + "name": "lookup", + "parameters": map[string]any{"type": "object", "description": "도구"}, + }} + toolChoice := map[string]any{"function": map[string]any{"name": "lookup"}} + for _, input := range inputs { + got := estimateInputTokensBytes(input, metadata, tools, toolChoice) + want := estimateInputTokens(string(input), metadata, tools, toolChoice) + if got != want { + t.Fatalf("estimateInputTokensBytes(%q) = %d, want %d", input, got, want) + } + } +} diff --git a/apps/edge/internal/openai/openai_request_rebuilder.go b/apps/edge/internal/openai/openai_request_rebuilder.go index 9f6ff4b..657c0ea 100644 --- a/apps/edge/internal/openai/openai_request_rebuilder.go +++ b/apps/edge/internal/openai/openai_request_rebuilder.go @@ -94,8 +94,9 @@ func (s *openAIRecoveryPatchStore) PutContinuation(snapshotRef string, cursor in s.mu.Unlock() return errOpenAIRecoveryPatchDuplicate } + ingress := s.ingress s.mu.Unlock() - guard, err := s.ingress.reserveRebuild(int64(len(encoded))) + guard, err := ingress.reserveRebuild(int64(len(encoded))) if err != nil { return err } @@ -139,8 +140,9 @@ func (s *openAIRecoveryPatchStore) PutSchema(schemaRef, patchCode string, value s.mu.Unlock() return errOpenAIRecoveryPatchDuplicate } + ingress := s.ingress s.mu.Unlock() - guard, err := s.ingress.reserveRebuild(int64(len(encoded))) + guard, err := ingress.reserveRebuild(int64(len(encoded))) if err != nil { return err } diff --git a/apps/edge/internal/openai/provider_tunnel.go b/apps/edge/internal/openai/provider_tunnel.go index fc9d493..9bc32bc 100644 --- a/apps/edge/internal/openai/provider_tunnel.go +++ b/apps/edge/internal/openai/provider_tunnel.go @@ -74,18 +74,19 @@ func (s *Server) submitChatCompletionTunnel(w http.ResponseWriter, dc *chatDispa metadata["estimated_input_tokens"] = strconv.Itoa(dc.estimate) metadata["context_class"] = dc.contextClass + bodyBuilder := newOpenAIProviderBodyBuilder(func(target string) (*openAIRebuiltLease, error) { + return rewriteChatCompletionModelFromIngress(dc.ingress, target, dc.req) + }) tunnelReq := edgeservice.SubmitProviderTunnelRequest{ - NodeRef: dc.route.NodeRef, - ModelGroupKey: strings.TrimSpace(dc.req.Model), - Adapter: dc.route.Adapter, - Target: dc.route.Target, - SessionID: dc.route.SessionID, - Method: http.MethodPost, - Path: "/v1/chat/completions", - Headers: providerAuthHeaders, - BuildBody: func(target string) ([]byte, error) { - return rewriteChatCompletionModelFromIngress(dc.ingress, target, dc.req) - }, + NodeRef: dc.route.NodeRef, + ModelGroupKey: strings.TrimSpace(dc.req.Model), + Adapter: dc.route.Adapter, + Target: dc.route.Target, + SessionID: dc.route.SessionID, + Method: http.MethodPost, + Path: "/v1/chat/completions", + Headers: providerAuthHeaders, + BuildBody: bodyBuilder.BuildBody, Stream: dc.req.Stream, TimeoutSec: dc.route.TimeoutSec, MaxQueue: dc.route.MaxQueue, @@ -98,6 +99,7 @@ func (s *Server) submitChatCompletionTunnel(w http.ResponseWriter, dc *chatDispa metricLabels := dc.usageLabels(s, responseModePassthrough) handle, err := s.service.SubmitProviderTunnel(dc.r.Context(), tunnelReq) + bodyBuilder.Close() if err != nil { emitUsageMetrics(metricLabels, usageStatusForError(err), usageObservation{}) writeError(w, http.StatusBadGateway, "node_dispatch_error", err.Error()) @@ -345,18 +347,19 @@ func (s *Server) tunnelResponsesPassthrough(w http.ResponseWriter, requestCtx *r metadata["estimated_input_tokens"] = strconv.Itoa(requestCtx.estimate) metadata["context_class"] = requestCtx.contextClass + bodyBuilder := newOpenAIProviderBodyBuilder(func(target string) (*openAIRebuiltLease, error) { + return rewriteResponsesModelFromIngress(requestCtx.ingress, target) + }) tunnelReq := edgeservice.SubmitProviderTunnelRequest{ - NodeRef: requestCtx.route.NodeRef, - ModelGroupKey: strings.TrimSpace(requestCtx.envelope.Model), - Adapter: requestCtx.route.Adapter, - Target: requestCtx.route.Target, - SessionID: requestCtx.route.SessionID, - Method: http.MethodPost, - Path: "/v1/responses", - Headers: providerAuthHeaders, - BuildBody: func(target string) ([]byte, error) { - return rewriteResponsesModelFromIngress(requestCtx.ingress, target) - }, + NodeRef: requestCtx.route.NodeRef, + ModelGroupKey: strings.TrimSpace(requestCtx.envelope.Model), + Adapter: requestCtx.route.Adapter, + Target: requestCtx.route.Target, + SessionID: requestCtx.route.SessionID, + Method: http.MethodPost, + Path: "/v1/responses", + Headers: providerAuthHeaders, + BuildBody: bodyBuilder.BuildBody, Stream: requestCtx.envelope.Stream, TimeoutSec: requestCtx.route.TimeoutSec, MaxQueue: requestCtx.route.MaxQueue, @@ -369,6 +372,7 @@ func (s *Server) tunnelResponsesPassthrough(w http.ResponseWriter, requestCtx *r metricLabels := s.usageLabelsFor(requestCtx.r.Context(), strings.TrimSpace(requestCtx.envelope.Model), requestCtx.endpoint, responseModePassthrough) handle, err := s.service.SubmitProviderTunnel(requestCtx.r.Context(), tunnelReq) + bodyBuilder.Close() if err != nil { emitUsageMetrics(metricLabels, usageStatusForError(err), usageObservation{}) writeError(w, http.StatusBadGateway, "node_dispatch_error", err.Error()) diff --git a/apps/edge/internal/openai/responses_handler.go b/apps/edge/internal/openai/responses_handler.go index 13c2a14..f8cfe7e 100644 --- a/apps/edge/internal/openai/responses_handler.go +++ b/apps/edge/internal/openai/responses_handler.go @@ -169,7 +169,7 @@ func (s *Server) newResponsesRequestContext(w http.ResponseWriter, r *http.Reque // The Responses estimate is taken from the raw body so both the tunnel and // the normalized branch carry the same observability and long-context // information (SDD S02/S03). - estimate := estimateInputTokens(string(canonicalBody), runMeta, nil, nil) + estimate := estimateInputTokensBytes(canonicalBody, runMeta, nil, nil) canonicalBody = nil return &responsesRequestContext{ openAIRequestContext: openAIRequestContext{ @@ -351,9 +351,10 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx * // Tunnel branch: rewrite only the model field, preserve all other fields // (tools, max_output_tokens, custom fields). Stream/background gating is // skipped: the provider itself enforces those constraints (SDD S04). - poolReq.Tunnel.BuildBody = func(target string) ([]byte, error) { + bodyBuilder := newOpenAIProviderBodyBuilder(func(target string) (*openAIRebuiltLease, error) { return rewriteResponsesModelFromIngress(requestCtx.ingress, target) - } + }) + poolReq.Tunnel.BuildBody = bodyBuilder.BuildBody // The normalized context is created only after candidate selection. That // preserves raw passthrough for tunnel candidates while giving the @@ -390,6 +391,7 @@ func (s *Server) handleResponsesProviderPool(w http.ResponseWriter, requestCtx * } result, err := s.service.SubmitProviderPool(r.Context(), poolReq) + bodyBuilder.Close() if err != nil { metricLabels := s.usageLabelsFor(r.Context(), strings.TrimSpace(env.Model), usageEndpointResponses, responseModePassthrough) emitUsageMetrics(metricLabels, usageStatusForError(err), usageObservation{}) diff --git a/packages/go/streamgate/ingress_snapshot_test.go b/packages/go/streamgate/ingress_snapshot_test.go index 2f34820..46b14a5 100644 --- a/packages/go/streamgate/ingress_snapshot_test.go +++ b/packages/go/streamgate/ingress_snapshot_test.go @@ -2560,3 +2560,79 @@ func TestIngressSnapshot_RebuildConcurrentOverflowCloseCancelRace(t *testing.T) t.Errorf("guardC Cancel: want nil, got %v", cancelErr) } } + +func TestIngressSnapshot_OwnedBackingTransferAndCommit(t *testing.T) { + canonical := []byte(`{"model":"owned"}`) + builder, err := NewIngressSnapshotBuilder(1024) + if err != nil { + t.Fatalf("NewIngressSnapshotBuilder: %v", err) + } + defer builder.Close() + + handle, err := builder.IssueOwnedBackingHandle(canonical) + if err != nil { + t.Fatalf("IssueOwnedBackingHandle: %v", err) + } + if err := builder.SetCanonicalWithHandle(handle); err != nil { + t.Fatalf("SetCanonicalWithHandle: %v", err) + } + if err := builder.AddTypedView("semantic", handle); err != nil { + t.Fatalf("AddTypedView: %v", err) + } + snapshot, err := builder.Build() + if err != nil { + t.Fatalf("Build: %v", err) + } + defer snapshot.Close() + + alias, err := snapshot.Accessor().CanonicalAlias() + if err != nil { + t.Fatalf("CanonicalAlias: %v", err) + } + if len(alias) == 0 || &alias[0] != &canonical[0] { + t.Fatal("owned canonical backing was copied") + } + copied, err := snapshot.Accessor().Canonical() + if err != nil { + t.Fatalf("Canonical: %v", err) + } + if &copied[0] == &canonical[0] { + t.Fatal("defensive Canonical API unexpectedly returned an alias") + } + copied[0] = '!' + if alias[0] == '!' { + t.Fatal("mutating the defensive copy changed the owned backing") + } + + output := []byte(`{"model":"rebuilt","input":"fixed"}`) + guard, err := snapshot.ReserveRebuild(int64(len(output))) + if err != nil { + t.Fatalf("ReserveRebuild: %v", err) + } + rebuilt, err := guard.CommitOwnedTyped("rebuilt", output) + if err != nil { + guard.Close() + t.Fatalf("CommitOwnedTyped: %v", err) + } + rebuiltAlias, err := rebuilt.Accessor().TypedViewAlias("rebuilt") + if err != nil { + t.Fatalf("TypedViewAlias: %v", err) + } + if len(rebuiltAlias) == 0 || &rebuiltAlias[0] != &output[0] { + t.Fatal("owned rebuild output was copied") + } + wantRetained := int64(len(canonical) + len(output)) + if got := rebuilt.Accessor().RetainedBytes(); got != wantRetained { + t.Fatalf("rebuilt retained bytes = %d, want %d", got, wantRetained) + } + + rebuiltAccessor := rebuilt.Accessor() + rebuilt.Close() + guard.Close() + if !guard.IsReleased() { + t.Fatal("owned rebuild guard was not released") + } + if _, err := rebuiltAccessor.TypedViewAlias("rebuilt"); !errors.Is(err, ErrIngressSnapshotClosed) { + t.Fatalf("TypedViewAlias after release = %v, want closed", err) + } +}