<!-- task=m-iop-agent-cli-runtime/01_common_runtime_node_bridge plan=2 tag=REVIEW_TEST -->

# Deterministic terminal flush regression evidence follow-up

## 이 파일을 읽는 구현 에이전트에게

구현과 검증을 마친 뒤 반드시 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 기록하고 active 파일을 그대로 둔 채 리뷰 준비 완료를 보고한다. 최종 판정, 사용자 판단 요청, user-input 도구 호출, control-plane stop 파일, log archive, `complete.log` 작성은 코드리뷰 에이전트 전용이다. 진행이 막히면 구현 소유 evidence 필드에 정확한 blocker, 시도한 명령/output, 재개 조건만 기록한다.

## 배경

terminal ordering production fix와 정상 경로 검증은 통과했지만 Node의 필수 concurrency regression이 terminal `Emit`과 `Flush` 사이의 happens-before 없이 goroutine 생성 순서를 기대해 반복 실행에서 실패한다. 같은 제출의 `gofmt -l` 원문도 실제 파일 상태와 불일치했으므로, test oracle을 결정적으로 고치고 fresh S04 evidence를 다시 생성해야 한다.

## Archive Evidence Snapshot

- 이전 task: `agent-task/m-iop-agent-cli-runtime/01_common_runtime_node_bridge/`
- 이전 plan/review: `plan_local_G08_1.log`, `code_review_cloud_G08_1.log`
- 판정: `FAIL`; Required 2, Suggested 0, Nit 0
- Required 1: `apps/node/internal/node/sink_test.go`의 terminal/Flush concurrency test가 terminal lock 대기 진입을 동기화하지 않아 `expected 2 events, got 1`로 간헐 실패한다.
- Required 2: 이전 review에 기록된 빈 `gofmt -l` 출력과 달리 reviewer 실행은 `apps/node/internal/node/sink_test.go`를 출력했다. reviewer가 포맷은 직접 정리했지만 fresh evidence를 다시 생성해야 한다.
- 영향 파일: `apps/node/internal/node/sink_test.go`, 새 `CODE_REVIEW-cloud-G07.md`
- reviewer 검증 evidence: `/config/.local/bin/go test -count=500 ./apps/node/internal/node -run '^TestTerminalDeferringSinkPreservesConcurrentDeliveryOrder$'`는 2회 실패했고, 같은 test를 포함한 `-race -count=100` 회귀 묶음은 Node package에서 다수 실패했다. common emitter test는 PASS했다.
- reviewer 정리: `apps/node/internal/node/sink_test.go`를 `gofmt`로 정리했고 이후 대상 파일 `gofmt -l`과 `git diff --check`는 빈 출력이다.
- Roadmap carryover: `node-consumer`는 미완료이며 SDD S04 evidence를 이 후속 PASS로 닫는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
- Task ids:
  - `node-consumer`: Node의 공통 runtime bridge 전환
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/agentruntime/emitter.go`
- `packages/go/agentruntime/emitter_test.go`
- `packages/go/agentruntime/types.go`
- `packages/go/agentruntime/conformance_test.go`
- `packages/go/agentprovider/cli/cli.go`
- `apps/node/internal/node/runtime_sink.go`
- `apps/node/internal/node/sink_test.go`
- `apps/node/internal/node/run_handler.go`
- `agent-contract/inner/agent-runtime.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/testing-smoke.md`
- `agent-ops/skills/project/e2e-smoke/SKILL.md`

### SDD 기준

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`, 상태 `[승인됨]`, SDD 잠금 `해제`
- 대상: S04 → Milestone Task `node-consumer`
- Evidence Map: S04의 Node wire/config compatibility suite와 기존 contract conformance evidence
- 반영: scheduler 우연에 의존하지 않는 terminal acceptance/flush regression을 구현 항목으로 두고, 반복 race·Node package·aggregate suite와 실제 Edge-Node process 진단을 최종 evidence로 다시 실행한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`와 매칭 profile `node-smoke.md`, `platform-common-smoke.md`, `testing-smoke.md`를 읽었다. 구조적 공백, 누락 profile, `<확인 필요>` 값은 없다.
- 적용 명령: host Go identity, fresh/repeated race 대상 test, Node/common aggregate test, mock smoke, 별도 Edge/Node reconnect diagnostic, deterministic search, `gofmt -l`, `git diff --check`.
- profile의 repo root `/config/workspace/iop`와 실제 checkout `/config/workspace/iop-s0`가 다르므로 root assertion은 실제 checkout을 사용한다. 이는 기존 local profile 사실이며 이번 test fix에서 test-rule 문서를 변경하지 않는다.
- 테스트 환경 프리플라이트: local runner, repo `/config/workspace/iop-s0`, branch `dev`, HEAD `432284820e36a7a3c6b35caaa8e4b9f903145b86`, task 구현을 포함한 dirty worktree다. Go는 `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go1.26.2 linux/arm64`, GOROOT `/config/opt/go`다. diagnostic은 현재 checkout에서 Node binary를 재빌드하고 Edge를 `go run`으로 실행하며 임시 loopback port/config와 `test-node` identity를 정리한다. 외부 host, provider endpoint, credential은 요구하지 않는다.
- Go test cache는 허용하지 않으며 모든 검증에 `-count`를 명시한다.

### 테스트 커버리지 공백

- common `TerminalEmitter`의 concurrent delta→terminal ordering test는 반복 race 실행에서 PASS한다.
- Node의 post-terminal suppression test는 결정적이며 현재 동작을 검증한다.
- `TestTerminalDeferringSinkPreservesConcurrentDeliveryOrder`는 terminal goroutine 생성과 mutex acceptance 사이의 동기화가 없어 `Flush`가 빈 queue를 먼저 비울 수 있다. 먼저 blocking non-terminal을 해제하고 terminal acceptance 완료를 기다린 뒤 Flush하도록 oracle을 고쳐야 한다.
- production `TerminalEmitter`와 `terminalDeferringSink` 변경 필요성은 fresh reproducer에서 확인되지 않았다.

### 심볼 참조

- rename/remove 없음.

### 분할 판단

- 단일 계획이다. 한 test의 happens-before 복구와 그 test가 지지하는 S04 evidence 재생성은 compact한 검증 신뢰성 경계이며 별도 PASS 단위로 나누지 않는다.

### 범위 결정 근거

- `packages/go/agentruntime/emitter.go`와 `apps/node/internal/node/runtime_sink.go` production logic은 이번 reproducer에서 새 위반이 확인되지 않아 변경하지 않는다.
- provider 실행/stream parser, config/protobuf schema, registry, Edge 구현, agent-contract/SDD/spec/test-rule 문서는 변경하지 않는다.
- 실제 외부 CLI profile은 이 Node bridge test oracle 복구의 대상이 아니며 deterministic loopback diagnostic으로 S04 relay evidence를 재생성한다.

### 최종 라우팅

- `evaluation_mode=isolated-reassessment`, `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`; capability gap 없음
- build scores: scope 1, state/concurrency 2, blast 0, evidence/diagnosis 2, verification 2 → G07
- build: `base_route_basis=local-fit`, `route_basis=recovery-boundary`, `lane=cloud`, `filename=PLAN-cloud-G07.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`; capability gap 없음
- review scores: scope 1, state/concurrency 2, blast 0, evidence/diagnosis 2, verification 2 → G07
- review: `route_basis=official-review`, `lane=cloud`, adapter `codex`, model `gpt-5.6-sol`, reasoning `xhigh`, `filename=CODE_REVIEW-cloud-G07.md`
- `large_indivisible_context=false`
- positive loop risks: `concurrent_consistency`; count 1, risk boundary false
- recovery: `review_rework_count=2`, `evidence_integrity_failure=true`, recovery boundary true

## 구현 체크리스트

- [ ] REVIEW_TEST-1 Node terminal ordering regression의 scheduler 가정을 제거하고 explicit terminal acceptance 뒤 Flush하도록 결정적으로 수정한다.
- [ ] REVIEW_TEST-2 focused repeat/race, fresh Go suites, duplicate search, mock smoke와 실제 Edge-Node diagnostic을 재실행해 신뢰 가능한 S04 evidence를 생성한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_TEST-1] Deterministic terminal acceptance and flush

**문제**

`apps/node/internal/node/sink_test.go:240-255`는 complete goroutine을 만든 뒤 곧바로 Flush goroutine을 만들고 blocking send를 해제한다. goroutine 생성 순서는 `complete`의 `emitMu` 대기 진입을 보장하지 않으므로 Flush가 먼저 lock을 얻으면 terminal은 flush 뒤 deferred queue에 남는다.

```go
completeDone := make(chan error, 1)
go func() {
	completeDone <- sink.Emit(context.Background(), complete)
}()

flushDone := make(chan error, 1)
go func() {
	flushDone <- sink.Flush(context.Background())
}()

close(ms.releaseFirst)
```

**해결 방법**

blocking non-terminal이 inner sink에 들어간 상태에서 terminal `Emit`을 시작해 두 call의 concurrency는 유지한다. 첫 send를 해제한 뒤 `startDone`, `completeDone` 순으로 기다려 terminal이 accepted/deferred 되었음을 확정하고, terminal이 아직 inner sink에 노출되지 않았음을 확인한 다음 `Flush`를 호출해 start→complete를 검증한다.

```go
close(ms.releaseFirst)
requireNoError(<-startDone)
requireNoError(<-completeDone)
assertSentTypes(start)

requireNoError(sink.Flush(context.Background()))
assertSentTypes(start, complete)
```

**수정 파일 및 체크리스트**

- [ ] `apps/node/internal/node/sink_test.go`: `flushDone` scheduling race를 제거하고 pre-flush start-only, post-flush start→complete를 명시적으로 assertion한다.
- [ ] production `runtime_sink.go`는 변경하지 않는다.

**테스트 작성**

- 기존 `TestTerminalDeferringSinkPreservesConcurrentDeliveryOrder`를 수정한다. 별도 test 파일은 만들지 않는다.
- 첫 non-terminal과 terminal `Emit`은 channel-controlled blocking sink로 겹치게 유지하고, terminal acceptance 뒤 Flush라는 실제 Node call contract를 명시한다.
- sleep을 사용하지 않으며 timeout/polling 없이 channel completion으로 happens-before를 만든다.

**중간 검증**

```bash
/config/.local/bin/go test -race -count=100 ./packages/go/agentruntime ./apps/node/internal/node -run 'TestTerminalEmitterPreservesConcurrentAcceptedOrder|TestTerminalDeferringSink(DropsPostTerminalEvents|PreservesConcurrentDeliveryOrder)'
```

예상: 두 package의 지정 regression을 100회 fresh race 실행해 모두 PASS하고 race report가 없다.

### [REVIEW_TEST-2] Fresh contract and S04 evidence regeneration

**문제**

이전 loop의 단발 PASS와 빈 `gofmt -l` 기록은 fresh reviewer evidence와 충돌했다. test 수정 뒤 전체 Node/common 회귀와 process-level terminal relay를 새로운 원문 출력으로 다시 고정해야 한다.

**해결 방법**

현재 checkout과 host Go identity를 먼저 기록한다. focused repeat/race를 통과한 뒤 common/Node fresh suites, mock smoke, 실제 분리 Edge/Node reconnect diagnostic, duplicate search, formatting과 diff 검증을 순서대로 실행하고 출력 원문을 새 review stub에 기록한다.

**수정 파일 및 체크리스트**

- [ ] `CODE_REVIEW-cloud-G07.md`: 모든 명령의 실제 stdout/stderr를 기록한다.
- [ ] fresh 검증 전에 `gofmt`를 적용하고, 최종 `gofmt -l` 및 `git diff --check` 빈 출력을 실제로 확인한다.

**테스트 작성**

- 추가 test는 만들지 않는다. REVIEW_TEST-1의 결정적 regression과 기존 package/integration/full-cycle suites가 이 항목의 oracle이다.

**중간 검증**

```bash
test "$(git rev-parse --show-toplevel)" = "/config/workspace/iop-s0"
command -v go
readlink -f "$(command -v go)"
go version
go env GOROOT
/config/.local/bin/go test -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/...
/config/.local/bin/go test -count=1 ./apps/node/internal/...
/config/.local/bin/go test -race -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/... ./apps/node/internal/adapters/... ./apps/node/internal/router/...
```

예상: checkout/Go identity가 preflight와 일치하고 모든 fresh/race package test가 PASS한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/node/sink_test.go` | REVIEW_TEST-1 |
| `CODE_REVIEW-cloud-G07.md` | REVIEW_TEST-2 |

## 최종 검증

```bash
test "$(git rev-parse --show-toplevel)" = "/config/workspace/iop-s0"
command -v go
readlink -f "$(command -v go)"
go version
go env GOROOT
/config/.local/bin/go test -race -count=100 ./packages/go/agentruntime ./apps/node/internal/node -run 'TestTerminalEmitterPreservesConcurrentAcceptedOrder|TestTerminalDeferringSink(DropsPostTerminalEvents|PreservesConcurrentDeliveryOrder)'
/config/.local/bin/go test -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/...
/config/.local/bin/go test -count=1 ./apps/node/internal/...
/config/.local/bin/go test -race -count=1 ./packages/go/agentruntime/... ./packages/go/agentprovider/cli/... ./apps/node/internal/adapters/... ./apps/node/internal/router/...
/config/.local/bin/go test -count=1 ./packages/go/... ./apps/node/...
IOP_E2E_BIND_TIMEOUT=60 IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh
IOP_DEV_RECONNECT_BIND_TIMEOUT=60 ./scripts/dev/edge-node-reconnect-diagnostic.sh
rg --sort path -n 'type (Adapter|RuntimeEvent|ExecutionSpec|RunRequest|Capabilities|EventSink|Registry) ' packages/go apps/node/internal
rg --sort path -n 'apps/node/internal/(runtime|adapters/cli|terminal)' packages/go || true
gofmt -l packages/go/agentruntime apps/node/internal/node/runtime_sink.go apps/node/internal/node/sink_test.go
git diff --check
```

예상: focused repeat/race, fresh/race/aggregate Go tests, mock smoke와 two-process diagnostic이 PASS한다. duplicate search에는 삭제된 Node-owned runtime/CLI/terminal import가 없고 `gofmt -l` 및 `git diff --check` 출력이 없다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
