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
This commit is contained in:
toki 2026-07-26 20:53:32 +09:00
parent d182eafeff
commit ae5845cd68
10 changed files with 554 additions and 60 deletions

View file

@ -1,6 +1,6 @@
<!-- 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 -->
# 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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. 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 경로에서 이 체크리스트를 갱신한다.

View file

@ -1,4 +1,4 @@
<!-- 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 -->
# 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 |
## 최종 검증

View file

@ -0,0 +1,191 @@
<!-- task=m-stream-evidence-gate-core/19+18_core_runtime_loop plan=5 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP -->
# 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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. 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-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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 쌍을 작성한다.

View file

@ -0,0 +1,195 @@
<!-- task=m-stream-evidence-gate-core/19+18_core_runtime_loop plan=5 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_CORE_RUNTIME_LOOP -->
# 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`의 구현 에이전트 소유 섹션을 채운다.

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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())

View file

@ -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{})

View file

@ -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)
}
}