feat(edge): add console foreground dedupe task and update console events
- Add edge_console_foreground_dedupe agent-task with plan and code review logs - Update console_events.go with foreground dedupe support - Update console_test.go with related test cases - Update e2e-smoke SKILL.md
This commit is contained in:
parent
ebd2832ab8
commit
ffc30cb927
6 changed files with 418 additions and 3 deletions
|
|
@ -53,6 +53,7 @@ description: bin/edge.sh와 bin/node.sh 기반 사용자 실행 파이프라인
|
|||
- background off 기본값에서 foreground 응답 대기가 complete event까지 이어지는지 확인한다.
|
||||
- `/session <id>` 변경 후 메시지를 보내고 출력의 session 값과 node event/message 흐름이 유지되는지 확인한다.
|
||||
- `/terminate-session` 명령이 성공 출력 또는 mock adapter의 명확한 unsupported/error 출력을 내는지 확인한다.
|
||||
- foreground mock smoke에서는 `<empty>` message line이 없어야 하고, foreground complete 출력 수가 전송한 run 수를 초과하지 않는지 확인한다.
|
||||
|
||||
4. **edge console / transport 항목 확인**
|
||||
- node 연결/해제 lifecycle event가 run event와 분리되어 출력되는지 확인한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
<!-- task=edge_console_foreground_dedupe plan=0 tag=ECF -->
|
||||
|
||||
# Code Review Reference - ECF
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every section below is filled in.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-16
|
||||
task=edge_console_foreground_dedupe, plan=0, tag=ECF
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
||||
|
||||
1. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log` (N = 기존 code_review_*.log 수)
|
||||
2. `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log` (M = 기존 plan_*.log 수)
|
||||
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 routed plan + review 스텁 작성.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [ECF-1] Foreground Run Async 출력 차단 | [x] |
|
||||
| [ECF-2] e2e-smoke 기준에 중복 출력 실패 조건 반영 | [x] |
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- `apps/edge/cmd/edge/console_test.go`의 기존 `TestConsoleEventRouterPrintsUnregisteredRun`에서 delta/complete event 인스턴스에 `Background: true`를 추가했다. 계획에서는 "유지"로 명시했으나, ECF-1의 변경(`!event.GetBackground()` early return)으로 인해 background 플래그가 누락된 event는 더 이상 출력되지 않으므로 테스트 데이터 보정이 필요했다. 실제 node는 `RunEvent.Background`를 모든 event에 채워 보내므로(계획 분석 결과 항목 참조) 보정된 데이터가 실 운영과 일치한다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `consoleEventRouter.Handle`의 책임을 background/async event 출력으로 명확히 한다. `RunEvent.Background == false`인 event는 foreground 경로(`sendConsoleRun`)가 동기 출력하므로 async 경로에서는 tracking 상태와 무관하게 무시한다.
|
||||
- 기존 `foreground` map 기반 suppress 로직과 lifecycle은 그대로 유지하여 방어선을 이중으로 둔다. background event는 변경된 경로에 영향을 받지 않으므로 회귀 테스트(`TestConsoleEventRouterPrintsUnregisteredRun` 등)는 계속 동일한 출력을 검증한다.
|
||||
- nil event도 early return 처리해 라우터 호출부에서 추가 nil 검사를 강요하지 않는다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- foreground run event가 tracking 해제 후 async `Handle`로 들어와도 출력되지 않는지 확인한다.
|
||||
- background run async 출력은 기존처럼 유지되는지 확인한다.
|
||||
- `<empty>` line이 foreground 중복 complete의 부작용으로 다시 생기지 않는지 확인한다.
|
||||
- `e2e-smoke` skill에 이번 회귀를 잡는 체크 조건이 추가되었는지 확인한다.
|
||||
- 검증 결과에 실제 stdout/stderr가 기록되었는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### ECF-1 중간 검증
|
||||
```
|
||||
$ go test -count=1 ./apps/edge/cmd/edge
|
||||
ok iop/apps/edge/cmd/edge 0.004s
|
||||
```
|
||||
|
||||
### ECF-2 중간 검증
|
||||
```
|
||||
$ rg --sort path -n "<empty>|complete 출력 수|foreground mock smoke" agent-ops/skills/project/e2e-smoke/SKILL.md
|
||||
56: - foreground mock smoke에서는 `<empty>` message line이 없어야 하고, foreground complete 출력 수가 전송한 run 수를 초과하지 않는지 확인한다.
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
$ go test -count=1 ./apps/edge/cmd/edge
|
||||
ok iop/apps/edge/cmd/edge 0.004s
|
||||
```
|
||||
|
||||
```
|
||||
$ go test -count=1 ./...
|
||||
? iop/apps/control-plane/cmd/control-plane [no test files]
|
||||
ok iop/apps/edge/cmd/edge 0.009s
|
||||
? iop/apps/edge/internal/bootstrap [no test files]
|
||||
ok iop/apps/edge/internal/events 0.007s
|
||||
ok iop/apps/edge/internal/node 0.006s
|
||||
ok iop/apps/edge/internal/service 0.009s
|
||||
ok iop/apps/edge/internal/transport 0.014s
|
||||
? iop/apps/node/cmd/node [no test files]
|
||||
ok iop/apps/node/internal/adapters 0.011s
|
||||
ok iop/apps/node/internal/adapters/cli 87.812s
|
||||
? iop/apps/node/internal/adapters/cli/internal/testutil [no test files]
|
||||
ok iop/apps/node/internal/adapters/cli/status 31.118s
|
||||
? iop/apps/node/internal/adapters/mock [no test files]
|
||||
? iop/apps/node/internal/adapters/ollama [no test files]
|
||||
? iop/apps/node/internal/adapters/vllm [no test files]
|
||||
ok iop/apps/node/internal/bootstrap 0.163s
|
||||
ok iop/apps/node/internal/node 0.008s
|
||||
ok iop/apps/node/internal/router 0.006s
|
||||
? iop/apps/node/internal/runtime [no test files]
|
||||
ok iop/apps/node/internal/store 0.031s
|
||||
ok iop/apps/node/internal/transport 0.006s
|
||||
? iop/apps/worker/cmd/worker [no test files]
|
||||
? iop/packages/auth [no test files]
|
||||
ok iop/packages/config 0.008s
|
||||
? iop/packages/events [no test files]
|
||||
? iop/packages/jobs [no test files]
|
||||
? iop/packages/metadata [no test files]
|
||||
? iop/packages/observability [no test files]
|
||||
? iop/packages/policy [no test files]
|
||||
? iop/packages/version [no test files]
|
||||
? iop/proto/gen/iop [no test files]
|
||||
```
|
||||
|
||||
```
|
||||
$ rg --sort path -n "<empty>|foreground mock smoke|complete 출력 수" agent-ops/skills/project/e2e-smoke/SKILL.md
|
||||
56: - foreground mock smoke에서는 `<empty>` message line이 없어야 하고, foreground complete 출력 수가 전송한 run 수를 초과하지 않는지 확인한다.
|
||||
```
|
||||
|
||||
```
|
||||
$ agent-ops/skills/project/e2e-smoke/SKILL.md mode=mock 절차를 임시 config/포트로 실행
|
||||
# 임시 config: /tmp/iop-smoke/edge.yaml (listen 127.0.0.1:19090, metrics 19092, console adapter=mock target=mock-echo),
|
||||
# /tmp/iop-smoke/node.yaml (transport.edge_addr=127.0.0.1:19090, metrics 19091).
|
||||
# 실행: ./bin/edge.sh (IOP_EDGE_CONFIG=...) 와 ./bin/node.sh (IOP_NODE_CONFIG=...) 동시 구동, stdin 으로 /nodes, "hello first", "hello second", /exit 입력.
|
||||
|
||||
# edge stdout (요약):
|
||||
[edge] config=/tmp/iop-smoke/edge.yaml
|
||||
{"level":"info","ts":...,"caller":"transport/server.go:81","msg":"edge listening for nodes","addr":"127.0.0.1:19090"}
|
||||
IOP Edge console listening on 127.0.0.1:19090
|
||||
Console target node= adapter=mock target=mock-echo session=default background=false
|
||||
...
|
||||
{"level":"info","msg":"node registered","node_id":"node-smoke-01","alias":"smoke-node"}
|
||||
[node-smoke-node-event] connected reason="registered"
|
||||
node-smoke-01 (smoke-node)
|
||||
edge> [edge] sent run_id=manual-1778938958173388388 node=smoke-node adapter=mock target=mock-echo session=default background=false
|
||||
[node-smoke-node-event] start run_id=manual-1778938958173388388
|
||||
[node-smoke-node-message] echo: hello first
|
||||
[node-smoke-node-event] complete run_id=manual-1778938958173388388 detail="mock execution complete"
|
||||
edge> [edge] sent run_id=manual-1778938963180483918 node=smoke-node adapter=mock target=mock-echo session=default background=false
|
||||
[node-smoke-node-event] start run_id=manual-1778938963180483918
|
||||
[node-smoke-node-message] echo: hello second
|
||||
[node-smoke-node-event] complete run_id=manual-1778938963180483918 detail="mock execution complete"
|
||||
edge> bye
|
||||
|
||||
# 자동 카운트:
|
||||
$ grep -c "<empty>" /tmp/iop-smoke/edge.log
|
||||
0
|
||||
$ grep -c "complete run_id=" /tmp/iop-smoke/edge.log
|
||||
2
|
||||
$ grep -c "start run_id=" /tmp/iop-smoke/edge.log
|
||||
2
|
||||
|
||||
# 결과: node register, /nodes 표시, mock message 2회, event/message/complete 출력 확인.
|
||||
# edge console 출력에 `[node-*-message] <empty>` 없음, foreground complete 출력 수(2) = 전송 run 수(2).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every section: completion table, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 종합 판정
|
||||
|
||||
PASS
|
||||
|
||||
### 차원별 평가
|
||||
|
||||
| 차원 | 평가 |
|
||||
|------|------|
|
||||
| correctness | Pass |
|
||||
| completeness | Pass |
|
||||
| test coverage | Pass |
|
||||
| API contract | Pass |
|
||||
| code quality | Pass |
|
||||
| plan deviation | Pass |
|
||||
| verification trust | Pass |
|
||||
|
||||
### 발견된 문제
|
||||
|
||||
- Nit: `agent-task/edge_console_foreground_dedupe/CODE_REVIEW-cloud-G07.md:123`의 smoke 검증 출력은 `요약`/`...`가 섞인 형태라 원칙상 실제 stdout/stderr 전문 기록이 더 좋다. 리뷰 중 `go test -count=1 ./apps/edge/cmd/edge`, `go test -count=1 ./...`, `rg --sort path ...`, 그리고 임시 config/포트 기반 `bin/edge.sh` + `bin/node.sh` mock smoke를 재실행해 결과가 코드와 일치함을 확인했다.
|
||||
|
||||
### 다음 단계
|
||||
|
||||
PASS: active plan/review 파일을 archive하고 `complete.log`를 작성한다.
|
||||
20
agent-task/edge_console_foreground_dedupe/complete.log
Normal file
20
agent-task/edge_console_foreground_dedupe/complete.log
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
완료 일시: 2026-05-16
|
||||
|
||||
요약: edge console foreground run의 async 중복 출력 문제 수정 및 e2e-smoke 체크리스트 보강, 1회 루프 완료.
|
||||
|
||||
루프 이력:
|
||||
|
||||
| plan | code_review | verdict |
|
||||
|------|-------------|---------|
|
||||
| plan_cloud_G07_0.log | code_review_cloud_G07_0.log | PASS |
|
||||
|
||||
최종 리뷰 요약:
|
||||
|
||||
- `consoleEventRouter.Handle`가 `Background=false` foreground run event를 async 출력하지 않도록 변경되어 늦게 도착한 all-runs event가 `<empty>`/중복 complete를 만들지 않는다.
|
||||
- late foreground event regression test가 추가되었고, background async 출력 테스트는 실제 `Background=true` event로 보강되었다.
|
||||
- `e2e-smoke` skill에 foreground mock smoke의 `<empty>` 금지와 complete count 조건이 추가되었다.
|
||||
- 리뷰어가 `go test -count=1 ./apps/edge/cmd/edge`, `go test -count=1 ./...`, `rg --sort path ...`, 임시 config/포트 기반 `bin/edge.sh` + `bin/node.sh` mock smoke를 재실행해 PASS를 확인했다.
|
||||
|
||||
잔여 Nit:
|
||||
|
||||
- 구현자가 리뷰 파일에 기록한 smoke 출력은 일부 `요약`/`...`가 포함되어 있었다. 리뷰 중 동일 검증을 재실행해 신뢰를 회복했지만, 이후에는 bin smoke 출력도 실제 stdout/stderr 중심으로 기록하는 것이 좋다.
|
||||
192
agent-task/edge_console_foreground_dedupe/plan_cloud_G07_0.log
Normal file
192
agent-task/edge_console_foreground_dedupe/plan_cloud_G07_0.log
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
<!-- task=edge_console_foreground_dedupe plan=0 tag=ECF -->
|
||||
|
||||
# Edge Console Foreground Event Deduplication
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
**구현 마지막 단계에서 `CODE_REVIEW-cloud-G07.md`의 모든 섹션을 실제 내용으로 채워야 한다. 이 파일 작성 전까지 작업은 완료가 아니다.**
|
||||
중간/최종 검증을 실행하고, 실행한 명령의 실제 stdout/stderr를 `CODE_REVIEW-cloud-G07.md`에 붙여 넣는다.
|
||||
리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 archive/complete.log 절차는 구현 에이전트가 실행하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
`e2e-smoke` 검증에서 foreground mock run이 정상 완료된 뒤, 같은 run의 `complete` event가 async all-runs 경로에서 뒤늦게 한 번 더 출력되었다.
|
||||
그 결과 edge console에 `[node-*-message] <empty>`와 중복 `complete`가 섞여 사용자 출력이 지저분해진다.
|
||||
foreground run은 `sendConsoleRun`이 동기적으로 출력하고, async router는 background/asynchronous run 출력만 담당해야 한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/skills/project/e2e-smoke/SKILL.md`
|
||||
- `apps/edge/cmd/edge/console.go`
|
||||
- `apps/edge/cmd/edge/console_events.go`
|
||||
- `apps/edge/cmd/edge/console_test.go`
|
||||
- `apps/edge/internal/events/bus.go`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 `TestConsoleEventRouterSuppressesTrackedForegroundRun`은 foreground run이 tracking 중일 때 async 출력이 억제되는지만 검증한다.
|
||||
- tracking 해제 후 all-runs 채널에 남아 있던 foreground event가 뒤늦게 `Handle`로 들어오는 경우는 테스트하지 않는다.
|
||||
- background async run 출력은 `TestConsoleEventRouterPrintsUnregisteredRun`이 검증하고 있으므로 유지되어야 한다.
|
||||
- bin 기반 smoke에서 중복 `<empty>`/중복 `complete`가 없어야 한다는 조건은 아직 자동화된 고정 명령이 없다. 이번 구현 후 `e2e-smoke` mock 절차로 수동 검증한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- `TrackForeground`: `apps/edge/cmd/edge/console.go:175`, `apps/edge/cmd/edge/console_events.go:36`, `apps/edge/cmd/edge/console_test.go:90`.
|
||||
- `consoleEventRouter.Handle`: `apps/edge/cmd/edge/console_events.go:48`, `apps/edge/cmd/edge/console_events.go:75`, `apps/edge/cmd/edge/console_test.go:93`, `apps/edge/cmd/edge/console_test.go:104`, `apps/edge/cmd/edge/console_test.go:109`, `apps/edge/cmd/edge/console_test.go:110`, `apps/edge/cmd/edge/console_test.go:370`, `apps/edge/cmd/edge/console_test.go:387`.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- `apps/edge/internal/events/bus.go`는 event fanout 자체가 정상 동작한다. foreground 전용 구독과 all-runs 구독 양쪽에 보내는 현재 구조는 여러 소비자 지원을 위한 것이므로 변경하지 않는다.
|
||||
- `apps/node/**`는 `RunEvent.Background`를 이미 채워 보내고 있으므로 수정하지 않는다.
|
||||
- real CLI profile 동작은 이번 버그의 원인이 아니므로 opt-in smoke만 남기고 코드 변경 범위에서 제외한다.
|
||||
- 이번 수정은 edge console async 출력 라우팅과 그 테스트에 한정한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- build=`cloud-G07`, review=`cloud-G07`.
|
||||
- 이유: 코드 변경은 작지만 성공 조건이 bin-level smoke output, stdout 중복 억제, foreground/background event routing에 걸려 있는 terminal-agent 검증 작업이다.
|
||||
|
||||
## [ECF-1] Foreground Run Async 출력 차단
|
||||
|
||||
### 문제
|
||||
|
||||
`eventBus.PublishRun`은 `apps/edge/internal/events/bus.go:118`과 `apps/edge/internal/events/bus.go:121`에서 all-runs 구독자와 run-specific 구독자 모두에게 같은 event를 보낸다.
|
||||
foreground run은 `apps/edge/cmd/edge/console.go:175`에서 `TrackForeground`로 suppress 등록되고, `apps/edge/cmd/edge/console.go:176`의 defer로 `sendConsoleRun` 종료 시 해제된다.
|
||||
all-runs 채널에 남아 있던 foreground `complete`가 해제 후 `apps/edge/cmd/edge/console_events.go:48`의 async `Handle`로 들어오면 `apps/edge/cmd/edge/console_events.go:148`의 `finishStream`이 빈 stream을 마감하며 `<empty>`를 출력한다.
|
||||
|
||||
Before:
|
||||
|
||||
```go
|
||||
// apps/edge/cmd/edge/console_events.go:48
|
||||
func (r *consoleEventRouter) Handle(event *iop.RunEvent) {
|
||||
runID := event.GetRunId()
|
||||
|
||||
r.mu.Lock()
|
||||
if _, ok := r.foreground[runID]; ok {
|
||||
r.mu.Unlock()
|
||||
return
|
||||
}
|
||||
r.printAsyncLocked(event)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
```
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`consoleEventRouter.Handle`의 책임을 async/background 출력으로 명확히 한다.
|
||||
`RunEvent.Background == false`인 event는 foreground 경로가 이미 처리하는 event이므로 tracking 상태와 무관하게 async 출력하지 않는다.
|
||||
기존 `foreground` map은 방어적 suppress와 현재 테스트 호환을 위해 유지한다.
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
func (r *consoleEventRouter) Handle(event *iop.RunEvent) {
|
||||
if event == nil || !event.GetBackground() {
|
||||
return
|
||||
}
|
||||
|
||||
runID := event.GetRunId()
|
||||
|
||||
r.mu.Lock()
|
||||
if _, ok := r.foreground[runID]; ok {
|
||||
r.mu.Unlock()
|
||||
return
|
||||
}
|
||||
r.printAsyncLocked(event)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
```
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/cmd/edge/console_events.go`: `Handle`에서 nil event와 non-background event를 early return한다.
|
||||
- [ ] `apps/edge/cmd/edge/console_events.go`: background event 출력 경로는 변경하지 않는다.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
- 작성한다: `apps/edge/cmd/edge/console_test.go`.
|
||||
- 테스트명: `TestConsoleEventRouterIgnoresLateForegroundAfterUntrack`.
|
||||
- 목표: `TrackForeground` 해제 후 `Background=false`인 start/delta/complete event가 `Handle`로 들어와도 출력이 비어 있고 `<empty>`/중복 complete가 생기지 않음을 검증한다.
|
||||
- 기존 `TestConsoleEventRouterPrintsUnregisteredRun`은 background event가 계속 출력되는 회귀 테스트로 유지한다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/cmd/edge
|
||||
```
|
||||
|
||||
기대 결과: `ok iop/apps/edge/cmd/edge`.
|
||||
|
||||
## [ECF-2] e2e-smoke 기준에 중복 출력 실패 조건 반영
|
||||
|
||||
### 문제
|
||||
|
||||
현재 `e2e-smoke` 스킬은 메시지 2회, event/message/complete 출력 확인을 요구하지만, foreground run의 중복 `<empty>` message와 중복 `complete`를 실패 조건으로 명시하지 않는다.
|
||||
이번 이슈는 스킬 기반 smoke에서 발견되었으므로, 같은 검증 절차가 재발을 더 잘 잡아야 한다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`agent-ops/skills/project/e2e-smoke/SKILL.md`의 기본 E2E smoke 확인 항목에 다음 조건을 추가한다.
|
||||
|
||||
```md
|
||||
- foreground mock smoke에서는 `<empty>` message line이 없어야 하고, foreground complete 출력 수가 전송한 run 수를 초과하지 않는지 확인한다.
|
||||
```
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `agent-ops/skills/project/e2e-smoke/SKILL.md`: 기본 E2E smoke 실행 항목에 중복 `<empty>`/중복 complete 금지 조건을 추가한다.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
- 별도 코드 테스트는 작성하지 않는다. 이 항목은 스킬 문서의 검증 체크리스트 보강이다.
|
||||
- 실제 회귀 검증은 ECF-1의 unit test와 최종 `e2e-smoke` mock 절차에서 수행한다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```bash
|
||||
rg --sort path -n "<empty>|complete 출력 수|foreground mock smoke" agent-ops/skills/project/e2e-smoke/SKILL.md
|
||||
```
|
||||
|
||||
기대 결과: foreground mock smoke의 `<empty>` 금지와 complete count 조건이 검색된다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `apps/edge/cmd/edge/console_events.go` | ECF-1 |
|
||||
| `apps/edge/cmd/edge/console_test.go` | ECF-1 |
|
||||
| `agent-ops/skills/project/e2e-smoke/SKILL.md` | ECF-2 |
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
1. ECF-1을 먼저 구현하고 unit regression test를 추가한다.
|
||||
2. ECF-2로 smoke skill의 실패 조건을 보강한다.
|
||||
3. Go 테스트 후 `e2e-smoke` mock 절차를 실행해 bin-level 출력에서 `<empty>`와 중복 complete가 사라졌는지 확인한다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/cmd/edge
|
||||
```
|
||||
|
||||
기대 결과: edge console unit tests 통과.
|
||||
|
||||
```bash
|
||||
go test -count=1 ./...
|
||||
```
|
||||
|
||||
기대 결과: 전체 Go test 통과. 캐시 출력은 허용하지 않는다.
|
||||
|
||||
```bash
|
||||
rg --sort path -n "<empty>|foreground mock smoke|complete 출력 수" agent-ops/skills/project/e2e-smoke/SKILL.md
|
||||
```
|
||||
|
||||
기대 결과: e2e-smoke skill에 foreground 중복 출력 실패 조건이 명시되어 있다.
|
||||
|
||||
`agent-ops/skills/project/e2e-smoke/SKILL.md`의 `mode=mock` 절차를 임시 config/포트로 실행한다.
|
||||
기대 결과: node register, `/nodes`, mock message 2회 이상, event/message/complete 출력이 확인되고, edge console output에 `[node-*-message] <empty>`가 없으며 foreground run의 complete 출력 수가 전송 run 수를 초과하지 않는다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 전체 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -46,6 +46,10 @@ func (r *consoleEventRouter) TrackForeground(runID string) func() {
|
|||
}
|
||||
|
||||
func (r *consoleEventRouter) Handle(event *iop.RunEvent) {
|
||||
if event == nil || !event.GetBackground() {
|
||||
return
|
||||
}
|
||||
|
||||
runID := event.GetRunId()
|
||||
|
||||
r.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -97,17 +97,32 @@ func TestConsoleEventRouterSuppressesTrackedForegroundRun(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestConsoleEventRouterIgnoresLateForegroundAfterUntrack(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
router := newConsoleEventRouter(&out, nil, nil)
|
||||
unregister := router.TrackForeground("run-fg")
|
||||
unregister()
|
||||
|
||||
router.Handle(&iop.RunEvent{RunId: "run-fg", Type: "start", SessionId: "s1"})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-fg", Type: "delta", Delta: "hi"})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-fg", Type: "complete", Message: "done"})
|
||||
|
||||
if got := out.String(); got != "" {
|
||||
t.Fatalf("expected no async output for late foreground events, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsoleEventRouterPrintsUnregisteredRun(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
router := newConsoleEventRouter(&out, nil, nil)
|
||||
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "start", SessionId: "s1", Background: true})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "delta", Delta: "hello"})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "delta", Delta: "hello", Background: true})
|
||||
if got := out.String(); !strings.Contains(got, "[node-unknown-message] hello") {
|
||||
t.Fatalf("expected delta to be printed before completion, got:\n%s", got)
|
||||
}
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "delta", Delta: " world\n"})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "complete", Message: "done"})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "delta", Delta: " world\n", Background: true})
|
||||
router.Handle(&iop.RunEvent{RunId: "run-bg", Type: "complete", Message: "done", Background: true})
|
||||
|
||||
got := out.String()
|
||||
for _, want := range []string{
|
||||
|
|
|
|||
Loading…
Reference in a new issue