<!-- task=dart_web_e2e/01_browser_harness plan=1 tag=REVIEW_TEST -->

# Dart Web Browser Harness Follow-up Plan

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 필수다. 구현 후 검증을 실행하고 실제 출력, 계획 대비 변경 사항, 주요 설계 결정을 기록한 뒤 active 파일을 그대로 둔 상태로 리뷰 준비를 보고한다. 최종 archive, `complete.log`, 판정 기록은 code-review-skill 전용이다.

## 배경

첫 구현은 브라우저 런타임 테스트 파일과 Go 오케스트레이터를 추가했지만 실제 Chrome 환경에서 WebSocket handshake가 실패했다. `nhooyr.io/websocket`의 Origin 검증은 Origin host 전체를 매칭하므로 `dart test -p chrome`의 `localhost:<random-port>` Origin을 현재 패턴이 허용하지 못한다. 또한 Go 오케스트레이터가 부분 기능 서버마다 전체 Dart browser suite를 실행하는 구조라, Origin을 고쳐도 테스트/서버 기능 단위가 맞지 않는다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/roadmap/current.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/rules/project/domain/dart/rules.md`
- `agent-ops/rules/project/domain/go/rules.md`
- `agent-task/dart_web_e2e/01_browser_harness/plan_cloud_G07_0.log`
- `agent-task/dart_web_e2e/01_browser_harness/code_review_cloud_G07_0.log`
- `dart/test/browser_ws_runtime_test.dart`
- `dart/lib/src/ws_protobuf_client_web.dart`
- `go/crosstest/go_dart_web.go`
- `go/crosstest/go_dart.go`
- `go/ws_server.go`
- `/config/go/pkg/mod/nhooyr.io/websocket@v1.8.17/accept.go`

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

- Dart browser WS runtime regression: 새 테스트가 있으나 실제 Chrome 실행에서 실패해 아직 커버리지로 인정할 수 없다.
- Go orchestrator topology: 기존 테스트 없음. `go run ./crosstest/go_dart_web.go`와 원격 Chrome 실행 성공으로 검증한다.
- repo-local artifact cleanup: 테스트 불필요. `test ! -e go/go_dart_web`로 검증한다.

### 심볼 참조

- rename/remove 없음.
- `rg --sort path "runWebWSSendPush|runWebWSRequests|runDartBrowserTest|OriginPatterns|go_dart_web"`로 관련 참조를 확인한다.

### 분할 판단

split policy를 재평가했다. 현재 follow-up은 기존 foundation task의 Required 이슈 수정이며, 변경 파일은 `go/crosstest/go_dart_web.go`와 `go/go_dart_web` artifact cleanup, 검증 기록에 한정된다. 브라우저 원격 검증과 오케스트레이터 수정이 같은 실패를 닫는 하나의 검증 단위라 단일 follow-up plan이 적합하다.

### 범위 결정 근거

이번 follow-up은 Go 서버 기준 Dart Web E2E foundation만 고친다. `agent-task/dart_web_e2e/02+01_multilang_matrix`의 Kotlin/Python/TypeScript 확장, 매트릭스 표 출력, WSS 브라우저 런타임은 건드리지 않는다. Dart public API와 protocol schema도 변경하지 않는다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. 실제 브라우저, SSH 원격 검증, cross-build binary, subprocess exit-status를 다루는 terminal/browser automation 작업이다.

## 구현 체크리스트

- [ ] [REVIEW_TEST-1] 브라우저 Origin host/port 패턴을 허용한다.
- [ ] [REVIEW_TEST-2] Go 오케스트레이터가 전체 browser suite를 한 번만 실행하도록 서버 기능을 합친다.
- [ ] [REVIEW_TEST-3] repo-local Go 빌드 산출물을 제거하고 재생성을 막는다.
- [ ] [REVIEW_TEST-4] 로컬 정적 검증과 `ssh toki@toki-labs.com` 원격 Chrome E2E를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_TEST-1] Origin Host/Port Pattern

#### 문제

`go/crosstest/go_dart_web.go:63`와 `go/crosstest/go_dart_web.go:104`는 `OriginPatterns: []string{"localhost", "127.0.0.1"}`를 사용한다. `nhooyr.io/websocket@v1.8.17/accept.go:210`의 `authenticateOrigin`은 Origin URL의 `u.Host` 전체를 `filepath.Match`로 비교하므로 `dart test -p chrome`의 `http://localhost:<random-port>` Origin은 현재 패턴과 매칭되지 않는다.

#### 해결 방법

OriginPatterns를 포트까지 포함한 host 패턴으로 바꾼다. 중복을 피하려면 파일 내부 helper를 추가해 두 서버 생성 지점에서 같은 옵션을 사용한다.

Before:

```go
// go/crosstest/go_dart_web.go:63
AcceptOptions: &websocket.AcceptOptions{OriginPatterns: []string{"localhost", "127.0.0.1"}},
```

After:

```go
func browserAcceptOptions() *websocket.AcceptOptions {
    return &websocket.AcceptOptions{
        OriginPatterns: []string{"localhost:*", "127.0.0.1:*"},
    }
}
```

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

- [ ] `go/crosstest/go_dart_web.go`에 Origin helper 추가
- [ ] 모든 Web server 생성 지점에서 helper 사용
- [ ] `InsecureSkipVerify`는 사용하지 않음

#### 테스트 작성

별도 unit test는 작성하지 않는다. 이 문제는 실제 browser Origin이 있어야 재현되므로 원격 Chrome E2E를 회귀 테스트로 사용한다.

#### 중간 검증

```bash
cd go && go build -o /tmp/proto-socket-go-dart-web-review ./crosstest/go_dart_web.go
```

기대 결과: exit code 0, repo 내부에 새 바이너리 없음.

### [REVIEW_TEST-2] Single Browser Suite Server

#### 문제

`go/crosstest/go_dart_web.go:86`의 `runDartBrowserTest()`는 `dart/test/browser_ws_runtime_test.dart` 전체를 실행한다. 하지만 `runWebWSSendPush` 서버는 send-push listener만 등록하므로 `dart/test/browser_ws_runtime_test.dart:45`와 `dart/test/browser_ws_runtime_test.dart:61`의 request 테스트를 처리할 수 없다.

#### 해결 방법

send-push listener와 request listener를 같은 `OnClientConnected`에서 모두 등록하고, `runDartBrowserTest()`를 한 번만 실행한다. 실행 후 `received` channel로 send-push 서버 수신도 확인한다. 별도 request 서버 재시작 함수는 제거하거나 사용하지 않게 정리한다.

Before:

```go
// go/crosstest/go_dart_web.go:50
if err := runWebWSSendPush(); err != nil {
    return err
}
time.Sleep(150 * time.Millisecond)
return runWebWSRequests()
```

After:

```go
// go/crosstest/go_dart_web.go:50
return runWebWSSuite()
```

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

- [ ] `runWebWSSuite` 또는 동등한 단일 서버 함수 작성
- [ ] send-push listener 유지
- [ ] request listener를 같은 client communicator에 등록
- [ ] `runDartBrowserTest()`는 전체 browser suite를 한 번만 실행
- [ ] 실행 후 send-push 수신 검증 유지
- [ ] 불필요한 sleep/중복 서버 함수 제거

#### 테스트 작성

기존 `dart/test/browser_ws_runtime_test.dart`를 회귀 테스트로 사용한다. 필요하면 테스트명은 유지하고 서버 기대값만 현재 Go 서버와 일치시킨다.

#### 중간 검증

```bash
cd go && go build -o /tmp/proto-socket-go-dart-web-review ./crosstest/go_dart_web.go
```

기대 결과: exit code 0.

### [REVIEW_TEST-3] Repo-local Artifact Cleanup

#### 문제

`go/go_dart_web:1`에 `go build ./crosstest/go_dart_web.go` 산출물 바이너리가 untracked 파일로 남아 있다. 작업 트리에 repo-local verification artifact가 남으면 후속 diff와 리뷰 신뢰도를 해친다.

#### 해결 방법

`go/go_dart_web`를 삭제한다. 이후 검증 명령은 항상 `-o /tmp/...`를 사용하고, 원격 검증용 binary도 `/tmp`에서 만든 뒤 `ssh` 임시 디렉터리로 전송한다.

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

- [ ] `go/go_dart_web` 삭제
- [ ] 검증 과정에서 `go/go_dart_web`가 재생성되지 않게 `go build -o /tmp/...` 사용

#### 테스트 작성

테스트 파일은 작성하지 않는다. 파일 존재 여부를 명령으로 검증한다.

#### 중간 검증

```bash
test ! -e go/go_dart_web
```

기대 결과: exit code 0.

### [REVIEW_TEST-4] Remote Chrome Verification

#### 문제

이전 검증은 로컬 Chrome 미설치로 E2E를 끝까지 실행하지 못했고, 코드리뷰 중 `ssh toki@toki-labs.com` 원격 Chrome 환경에서 실제 실패가 확인됐다. 후속 구현은 같은 원격 조건에서 성공을 증명해야 한다.

#### 해결 방법

로컬에서는 Dart analyzer와 Go cross-build를 실행한다. 원격에는 Go가 확인되지 않았으므로, 로컬에서 `GOOS=darwin GOARCH=arm64`로 만든 서버 바이너리와 현재 `dart/` 패키지를 임시 디렉터리에 올린다. 원격은 `/Users/toki/SDK/flutter/bin/dart`와 `/Applications/Google Chrome.app`를 사용한다. 검증 후 원격 임시 디렉터리는 삭제한다.

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

- [ ] 로컬 `dart analyze` 출력 기록
- [ ] 로컬 `go build -o /tmp/...` 출력 기록
- [ ] 원격 준비 명령 출력 기록
- [ ] 원격 `dart pub get` 및 `./go_dart_web` 출력 기록
- [ ] 원격 임시 디렉터리 cleanup 기록

#### 테스트 작성

새 테스트는 작성하지 않는다. 이 항목은 검증 신뢰도 회복 항목이다.

#### 중간 검증

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'printf "remote-ok\n"; test -d "/Applications/Google Chrome.app"; /Users/toki/SDK/flutter/bin/dart --version'
```

기대 결과: `remote-ok`, Chrome app 존재, Dart SDK version 출력.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `go/crosstest/go_dart_web.go` | REVIEW_TEST-1, REVIEW_TEST-2 |
| `go/go_dart_web` | REVIEW_TEST-3 |
| `agent-task/dart_web_e2e/01_browser_harness/CODE_REVIEW-cloud-G07.md` | REVIEW_TEST-4 |

## 최종 검증

```bash
cd dart && dart analyze test/browser_ws_runtime_test.dart
cd go && go build -o /tmp/proto-socket-go-dart-web-review ./crosstest/go_dart_web.go
test ! -e go/go_dart_web
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'printf "remote-ok\n"; test -d "/Applications/Google Chrome.app"; /Users/toki/SDK/flutter/bin/dart --version'
GOOS=darwin GOARCH=arm64 go build -o /tmp/proto-socket-go-dart-web-darwin-arm64 ./go/crosstest/go_dart_web.go
remote_dir="/Users/toki/tmp/proto-socket-review-$(date +%Y%m%d%H%M%S)" && \
  ssh -o BatchMode=yes toki@toki-labs.com "mkdir -p '$remote_dir'/go '$remote_dir'/dart" && \
  tar --exclude='dart/.dart_tool' --exclude='dart/build' --exclude='dart/.packages' --exclude='dart/.git' -cf - dart | ssh -o BatchMode=yes toki@toki-labs.com "cd '$remote_dir' && tar -xf -" && \
  scp -q /tmp/proto-socket-go-dart-web-darwin-arm64 "toki@toki-labs.com:$remote_dir/go/go_dart_web" && \
  ssh -o BatchMode=yes toki@toki-labs.com "chmod +x '$remote_dir/go/go_dart_web'; zsh -lc 'export PATH=/Users/toki/SDK/flutter/bin:\$PATH; cd '$remote_dir'/dart && dart pub get && cd '$remote_dir'/go && ./go_dart_web'" ; \
  status=$?; ssh -o BatchMode=yes toki@toki-labs.com "rm -rf '$remote_dir'"; exit $status
```

기대 결과: analyzer issue 없음, Go build exit code 0, `go/go_dart_web` 없음, 원격 E2E 출력 마지막에 `PASS all go-server/dart-web-client crosstests passed`.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
