chore: long-context admission smoke test files reorganized and updated

This commit is contained in:
toki 2026-07-06 08:00:36 +09:00
parent 2d08d240d4
commit e1dcbc105c
11 changed files with 2171 additions and 107 deletions

View file

@ -0,0 +1,205 @@
<!-- task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke plan=0 tag=CAPACITY_SMOKE -->
# Code Review Reference - CAPACITY_SMOKE
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is mandatory.** Record preflight, smoke stdout/stderr, and status snapshot paths. External environment blockers are verification blockers, not user-review requests unless a selected Milestone lock decision is unresolved.
## 개요
date=2026-07-05
task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke, plan=0, tag=CAPACITY_SMOKE
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
- `capacity-smoke`: dev 환경 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오와 최종 회복 근거
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [CAPACITY_SMOKE-1] Long-context admission dev smoke | [x] script/doc 구현·검증 완료. dev 재배포 + 배포 config 보정 후 3 시나리오 라이브 S09 증거 수집 완료 (아래 검증 결과 참조) |
## 구현 체크리스트
- [x] dev smoke profile/script를 추가하거나 기존 dev edge smoke에 long-context admission scenarios를 확장한다. — `scripts/e2e-long-context-admission-smoke.sh` 신규, `agent-test/dev/long-context-admission-smoke.md` 신규, `agent-test/dev/edge-smoke.md` 교차링크.
- [x] preflight가 source state, config check, endpoint reachability, provider identities, current provider snapshots를 기록하게 한다. — runner preflight PASS(3 provider healthy, capacity 2/4/3=9).
- [x] normal 10-way scenario를 실행하고 총 `in_flight=9`, `queued>=1` 관측 후 완료 시 `in_flight=0`, `queued=0` 회복을 저장한다. — peak `in_flight=9`, `queued=3`, http_200=10/10, 회복 `in_flight=0`/`queued=0`. `long_in_flight`은 Control Plane status view 미노출로 `n/a`.
- [x] mixed long/normal scenario를 실행해 long slot full 중 normal이 head-of-line blocking 없이 완료되는 evidence를 저장한다. — post-fix: normal http_200=6/6, mid-run에서 long 4개가 long slot 정확 점유(mac=1/1, gx10=1/1, onex=2/2)하는 동안 normal 완료, 회복 0/0.
- [x] all-long-slot-full scenario를 실행해 long requests가 queue 대기 후 slot 회복 시 dispatch되고 최종 counters가 0으로 회복되는 evidence를 저장한다. — post-fix: peak `in_flight=4`(long slot total로 캡), `queued` 발생, Edge 로그 `queue_reason=long_context_capacity_full` 2건, long http_200=6/6, 회복 0/0.
- [x] smoke stdout/stderr와 status snapshot 파일 경로를 review stub에 기록한다. — 아래 검증 결과에 명령·출력·경로 기록.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
## 코드리뷰 전용 체크리스트
- [x] 판정을 append한다.
- [x] active review/plan을 `code_review_cloud_G08_0.log`, `plan_cloud_G08_0.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 archive로 이동한다.
- [x] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
- 계획의 `/v1/responses` + `/v1/chat/completions` 이중 endpoint 대신 `/v1/chat/completions`만 구동했다. 두 경로는 동일 provider-pool admission으로 수렴하고, 라이브 부하를 줄이기 위해 chat 경로로 통일했다.
- status 폴링을 고정 시간창(초기 90s)에서 요청 완료 기반(`<label>.polling_done` 플래그, POLL_SECS는 안전 상한 3600s)으로 바꿨다. long-context 요청이 느려 고정 창으로는 peak을 놓치기 때문이다.
- 스크립트를 repo가 아닌 위치(runner `/tmp`)에서 실행할 수 있도록 `IOP_LONG_SMOKE_REPO`로 `config check` 기준 repo 경로를 override 가능하게 했다.
- long prompt는 기본 720000 chars(~225k tokens)이나 라이브 실행에서는 `IOP_LONG_SMOKE_LONG_CHARS=384000`(~120k tokens, threshold 초과)로 override해 부하를 낮췄다. estimator는 `runes*5/16` 근사다.
- 계획 범위 밖이었으나, 라이브 S09 증거 수집이 (1) 배포 stale, (2) 배포 config의 long_context_capacity 누락으로 막혀 사용자 승인 후 dev Edge 재배포 + 배포 config 보정을 수행했다. 상세는 `검증 결과`의 재배포 절차/config gap 섹션 참조.
- `long_context_threshold_tokens`를 사용자 결정에 따라 `30000`으로 배포 dev config에 설정했다(기본 100000). tracked 기본값/SDD 반영은 follow-up으로 분리.
## 주요 설계 결정
- 라이브 실행은 로컬 host에서 dev Edge/Control Plane 포트에 직접 도달하지 못해(방화벽) `ssh toki@toki-labs.com` runner에서 실행했다. base_url/status_url은 runner-local(`127.0.0.1:18083`, `127.0.0.1:18001`)로 지정했다.
- bearer token(`<masked-dev-token>`, 저민감도 로컬 dev 토큰)은 runner config에서 env로 소싱하고 명령/로그/tracked 파일에 원문을 남기지 않았다.
- 시나리오는 pool이 idle(`in_flight=0`)로 회복된 상태에서만 다음 시나리오를 시작하도록 순차 실행했다.
- Control Plane HTTP status view가 long 필드를 노출하지 않으므로, 스크립트는 status JSON에 long 필드가 있으면 자동 사용하고 없으면 `n/a` 처리 후 Edge log 기반 확인을 안내하도록 forward-compatible하게 작성했다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- smoke가 normal 10-way, mixed, all-long-slot-full 세 시나리오를 모두 (재배포·config 보정 후) 라이브 실행했는지 확인한다.
- 최종 snapshot마다 `in_flight=0`, `queued=0` 회복 근거가 있는지 확인한다. `long_in_flight`은 Control Plane status view 미노출(follow-up #1)이므로 Edge 로그 `queue_reason`/`context_class`로 long-slot 예약·대기·회복을 확인한다.
- all-long-slot-full의 long slot 게이팅 증거로 Edge 로그 `queue_reason="long_context_capacity_full"`와 peak `in_flight`가 long slot total(4)로 캡되었는지 확인한다.
- 배포 stale/config gap이 재배포·config 보정으로 해소되었고, threshold 정책·status view 확장이 follow-up으로 남았는지 확인한다.
## 검증 결과
### 로컬 정적 검증 (local checkout, HEAD=2d08d240d48a954df6847df2ba08fb8b39fc3f7d)
```bash
$ git rev-parse HEAD
2d08d240d48a954df6847df2ba08fb8b39fc3f7d
$ git status --short
M agent-test/dev/edge-smoke.md
?? agent-test/dev/long-context-admission-smoke.md
?? scripts/e2e-long-context-admission-smoke.sh
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
OK configs/edge.yaml
$ bash -n scripts/e2e-long-context-admission-smoke.sh
(no output; syntax OK)
$ bash scripts/e2e-long-context-admission-smoke.sh --preflight --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke
# 로컬 host에서 dev 포트 미도달(방화벽) → preflight rc=3, BLOCKER로 정확한 실패 명령 기록:
# curl -fsS --connect-timeout 10 http://toki-labs.com:18083/v1/models
# curl -fsS --connect-timeout 10 http://127.0.0.1:18001/edges/edge-toki-labs-dev/status
```
### 초기 라이브 검증 (재배포 전 / 배포 stale — 아래 post-fix로 대체됨)
runner `ssh toki@toki-labs.com`, base=127.0.0.1:18083, status=127.0.0.1:18001. 이 시점 runner checkout HEAD=`59e55d409ebaa978d0ee80920ef7c4c30c0f09ce`, 배포 edge 바이너리 빌드 2026-07-05 04:38, 실행 04:40 (long-admission 미배포).
```text
$ ... --preflight --config build/dev-runtime/edge.yaml
config check: OK build/dev-runtime/edge.yaml
/models OK -> model=qwen3.6:35b
status OK: mac-mlx-vllm cap=2 in_flight=0 / gx10-vllm cap=4 in_flight=0 / onexplayer-lemonade cap=3 in_flight=0 (모두 healthy)
provider present: gx10-vllm, onexplayer-lemonade, mac-mlx-vllm
NOTE: status view가 long_in_flight/long_queued 미노출
PREFLIGHT OK (rc=0)
$ ... --scenario normal-10
peak_in_flight=9 peak_queued=3 normal http_200=10/10
final: in_flight=0 queued=0 (recovery OK)
$ ... --scenario mixed (IOP_LONG_SMOKE_LONG_CHARS=384000)
peak_in_flight=4 peak_queued=0 normal http_200=6/6 (head-of-line blocking 없음)
final: in_flight=0 queued=0 (recovery OK)
$ ... --scenario all-long-slot-full (IOP_LONG_SMOKE_LONG_CHARS=384000)
peak_in_flight=6 peak_queued=0 long http_200=5/6
final: in_flight=0 queued=0 (recovery OK)
mid-run 관측: in_flight=6 (mac=2/2, gx10=2/4, onexplayer=2/3), queued=0
```
evidence 파일 경로(runner): `/tmp/iop-long-admission-smoke/` 아래 `preflight_*`, `*_peaks_*.txt`, `*_final_*.json`, `*.code`, `*.resp.json`.
### 초기 blocker(배포 stale) — 재배포로 해소됨
- 최초 라이브 실행 시 배포 dev Edge 바이너리는 runner checkout `59e55d4`("openai-compatible tool call boundary hardening") 빌드로, local HEAD의 **조상**이었다(`git merge-base --is-ancestor 59e55d4 HEAD` = true). long-context admission 병합(`b2754b8`/`2c9faad`/`35eb219`/`5b95208`)이 그 이후라 기능 미배포 상태였다.
- 증상: `all-long-slot-full`에서 6 long이 long-slot 제한 없이 normal capacity로 전부 dispatch(peak `in_flight=6`, `mac-mlx-vllm=2`로 자기 `long_context_capacity=1` 초과), Edge 로그에 구조화 admission 필드 없음.
- 조치: 사용자 승인 하에 dev Edge를 현재 main `2d08d24`로 재배포(아래 재배포 절차)했다. source bug가 아니라 배포 stale이었다.
### 재배포 절차 (Edge 집중, 사용자 승인 후)
long-context admission은 Edge-side 로직이고 3 node가 healthy했으므로 전체 pool 재시작 대신 Edge 재배포로 범위를 좁혔다.
```text
runner clean sync: git reset --hard origin/main (59e55d4 -> 2d08d24), git clean -fd
pre-build test: go test ./... -> ALL ok (go1.26.3)
rebuild: go build -trimpath -o build/dev-runtime/bin/edge ./apps/edge/cmd/edge (21:30 build)
post-build: config check OK, config refresh subcommand present
restart: old pid 87233 정지, 신규 pid 3527 (Fx RUNNING), 로그 rotate
verify: 3 node(mac-codex/gx10-vllm/onexplayer-lemonade) 즉시 재연결, healthy (2/4/3=9)
```
### 배포 config gap 발견 및 보정
- 재배포 후에도 `all-long-slot-full`에서 6 long이 전부 dispatch(peak `in_flight=6`, `queued=0`)되어 queue가 안 잡혔다. 로그는 이번엔 `context_class:"long"`, `estimated_input_tokens:120018`을 정상 기록 → **분류는 동작**.
- 원인: 배포된 `build/dev-runtime/edge.yaml`의 provider에 `total_context_tokens`/`long_context_capacity`가 미설정(0)이라 long-slot 게이팅이 없었다. task 05가 예시 config/inventory만 정렬했고 실제 dev 배포 config엔 반영되지 않은 config gap이었다(source bug 아님).
- 보정(사용자 결정 반영): 배포 config에 `long_context_threshold_tokens: 30000`(사용자 요청값)과 provider별 `total_context_tokens`/`long_context_capacity`(gx10 262144/1, onexplayer 524288/2, mac 262144/1, inventory 기준)를 추가하고 `config refresh --mode apply`로 **무중단 live-apply**했다(dry-run 결과 전부 `applied`, `restart_required_paths: []` — task 02 live-apply 주장 검증). 3 node healthy 유지.
### post-fix 라이브 검증 (dev Edge=2d08d24, threshold=30000, long_cap gx10=1/onex=2/mac=1)
```text
$ ... --scenario normal-10
peak_in_flight=9 peak_queued=3 normal http_200=10/10 recovery in_flight=0/queued=0 OK
$ ... --scenario mixed (LONG_CHARS=384000)
normal http_200=6/6 (head-of-line blocking 없음)
mid-run: long 4개가 long slot 정확 점유 mac=1/1 gx10=1/1 onexplayer=2/2, 동시에 normal 완료
recovery in_flight=0/queued=0 OK
$ ... --scenario all-long-slot-full (LONG_CHARS=384000)
peak_in_flight=4 (long slot total=4로 캡; 이전 배포 stale/config-gap 때는 6)
peak_queued>0 (queue 발생; 이전 0)
Edge 로그: queue_reason="long_context_capacity_full" 2건 (6 long - 4 slot = 2 대기), 이후 slot 회복 시 dispatch
long http_200=6/6 recovery in_flight=0/queued=0 OK
```
- 분류 evidence(로그 `build/dev-runtime/bin/logs/edge.log`): `context_class:"long"`, `estimated_input_tokens:120018`, queued 요청 `queue_reason:"long_context_capacity_full"`.
- S09 수용 충족: normal 10-way capacity 도달·queue·회복, mixed에서 long slot 점유 중 normal 무차단 완료, all-long-slot-full에서 long slot 초과분 queue 대기 후 dispatch, 세 시나리오 모두 최종 `in_flight=0`/`queued=0` 회복.
### 후속 작업 (follow-up)
1. (관측성) Control Plane HTTP status view(`apps/control-plane/cmd/control-plane/http_views.go`의 `providerSnapshotView`)가 `long_in_flight`/`long_queued`/`long_context_capacity`를 노출하지 않는다. proto `ProviderSnapshot`·Edge relay에는 값이 있으나 JSON view에서 누락되어, long-slot 회복은 status로 직접 관측 불가하고 Edge 로그(`queue_reason`/`context_class`)로 재구성해야 한다. view 확장을 control-plane/status-logs 범위 follow-up으로 검토.
2. (threshold 정책 영속화/SDD) `long_context_threshold_tokens=30000`은 사용자 결정으로 **배포 dev config에만** 적용했다. tracked 기본값(`packages/go/config/config.go` 기본 100000, `configs/edge.yaml` 예시)과 SDD 반영은 별도 결정이다. 또한 threshold를 낮추면 "long slot 1개 = context_window(262144) 한 요청"이라는 D02/D03 슬롯 모델과 커플링 문제가 생긴다(30k 요청이 262k용 슬롯을 통째로 점유). long_cap을 KV 예산 기반으로 재정의할지 SDD 검토 필요.
3. (배포 config 재현성) dev 배포 config(`build/dev-runtime/edge.yaml`, untracked)에 long_context 필드가 없던 gap이 이번에 드러났다. dev-runtime-deploy 절차가 inventory의 long capacity 값을 배포 config에 확실히 반영하도록 절차/문서 보강 검토.
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Fill every implementation-owned section before saving.**
## 코드리뷰 결과
- 종합 판정: FAIL
- 차원별 평가:
- correctness: Fail
- completeness: Fail
- test coverage: Fail
- API contract: Pass
- code quality: Pass
- implementation deviation: Warn
- verification trust: Fail
- spec conformance: Fail
- 발견된 문제:
- Required — `scripts/e2e-long-context-admission-smoke.sh:286`: `fire_and_wait` counts HTTP 200 responses but only logs the count and always returns success. As a result, `normal-10` and `all-long-slot-full` can exit 0 even when every request fails. Fix by making request success counts part of the function's return contract and requiring `normal-10` 10/10, `mixed` normal 6/6 plus long 4/4, and `all-long-slot-full` 6/6 before the scenario can pass.
- Required — `scripts/e2e-long-context-admission-smoke.sh:315` and `scripts/e2e-long-context-admission-smoke.sh:340`: preflight records `edge config check` and provider identity checks but does not fail when config check fails or a required provider is missing. Fix by propagating config-check failure, requiring all three provider ids to be present, healthy, and summing to the documented capacity baseline before returning `PREFLIGHT OK`.
- Required — `scripts/e2e-long-context-admission-smoke.sh:373`: the scenario PASS criteria documented in `agent-test/dev/long-context-admission-smoke.md:67` are not asserted. The script records `peak_in_flight`/`peak_queued`, but `normal-10` does not require peak `in_flight=9` and `queued>=1`, and `all-long-slot-full` does not require long-slot queue evidence. Fix by parsing the peaks file or returning peak values from polling, then failing when the documented peak/queue criteria are not met. The local mock check reproduced the bug: `all-long-slot-full` returned `rc=0` with `http_200=0/6`, `peak_in_flight=0`, and `peak_queued=0`.
- Required — `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md:160`: the live post-fix evidence is summarized instead of preserving actual stdout/stderr or exact status/log evidence paths for each scenario. Because SDD S09 requires dev smoke script/output and final recovery snapshots, rerun after fixing the exit contract and paste actual command output or exact saved output files, status snapshots, and Edge log evidence paths/snippets.
- 다음 단계: FAIL follow-up plan/review를 작성해 smoke script exit contract와 evidence capture를 보강한 뒤 live dev provider pool smoke를 다시 수집한다.

View file

@ -0,0 +1,337 @@
<!-- task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke plan=1 tag=REVIEW_CAPACITY_SMOKE -->
# Code Review Reference - REVIEW_CAPACITY_SMOKE
> **[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 by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> 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-05
task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke, plan=1, tag=REVIEW_CAPACITY_SMOKE
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
- `capacity-smoke`: dev 환경 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오와 최종 회복 근거
- Completion mode: check-on-pass
## Archive Evidence Snapshot
- Current archived plan: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_0.log`
- Current archived review: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log`
- Verdict: FAIL
- Required summary:
- `scripts/e2e-long-context-admission-smoke.sh` logs HTTP success counts but does not fail scenarios when counts are short.
- preflight does not fail when `edge config check` fails or required provider identities are missing.
- scenario peak/queue criteria from `agent-test/dev/long-context-admission-smoke.md` are recorded but not asserted.
- post-fix live evidence in the review log is summarized, not preserved as actual stdout/stderr or exact snapshot/log paths.
- Affected files:
- `scripts/e2e-long-context-admission-smoke.sh`
- `agent-test/dev/long-context-admission-smoke.md`
- `agent-test/dev/edge-smoke.md`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
- Verification evidence from review:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`: PASS.
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`: PASS.
- Local mock reproduced the exit bug: `all-long-slot-full` returned `rc=0` with `http_200=0/6`, `peak_in_flight=0`, `peak_queued=0`.
- Local mock reproduced preflight bug: missing config returned `preflight_bad_config_rc=0`.
- Allowed narrow reread:
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_0.log`
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G08.md` -> `code_review_cloud_G08_N.log`, `PLAN-cloud-G08.md` -> `plan_cloud_G08_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_CAPACITY_SMOKE-1] Smoke script exit contract | [x] |
| [REVIEW_CAPACITY_SMOKE-2] Trusted S09 evidence rerun | [ ] |
## 구현 체크리스트
- [x] `scripts/e2e-long-context-admission-smoke.sh`가 preflight config/provider/capacity 실패와 request HTTP 실패를 non-zero exit으로 전파하도록 수정한다.
- [x] `scripts/e2e-long-context-admission-smoke.sh`가 `normal-10`, `mixed`, `all-long-slot-full`의 documented peak/queue/recovery criteria를 자동 assert하도록 수정한다.
- [x] deterministic local mock regression으로 bad config, HTTP failure, zero peak/queue가 실패로 판정되는지 검증한다.
- [ ] live dev provider pool에서 preflight와 세 scenario를 rerun하고 actual stdout/stderr 또는 exact output file/status snapshot/Edge log evidence를 review stub에 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다.
- [x] `.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-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/`를 `agent-task/archive/YYYY/MM/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-model-group-long-context-admission`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-model-group-long-context-admission/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
- REVIEW_CAPACITY_SMOKE-1 (스크립트 exit contract)은 계획대로 구현했다.
- REVIEW_CAPACITY_SMOKE-2 (live S09 evidence rerun)는 계획의 live 명령을 그대로 실행할 수 없었다. 이는 검증 blocker이며 사용자 리뷰 요청이 아니다(계획/스텁 지시대로 `검증 결과`에 정확한 명령/출력 기록). 원인 3가지:
1. dev runner(`toki@toki-labs.com`)의 non-interactive/login shell 모두 `IOP_LONG_SMOKE_TOKEN` 미설정. Edge OpenAI-compatible endpoint(`http://127.0.0.1:18083/v1/models`)는 토큰 없이 `http_code=401`을 반환하므로 세 scenario의 `/v1/chat/completions` 요청이 모두 401이 되어 유효한 capacity evidence를 만들 수 없다.
2. runner에 `go` 미설치(`GO_ABSENT`). 계획의 preflight 명령은 `go run ./apps/edge/cmd/edge config check`를 실행하므로 runner에서 config check 자체가 불가능하다.
3. 수정한 `scripts/e2e-long-context-admission-smoke.sh`는 로컬 working tree의 untracked 파일이라(커밋/푸시는 code-review 전용) runner의 `/Users/toki/agent-work/iop-dev` checkout(현재 clean)에 존재하지 않는다.
- 계획의 deterministic local mock regression은 요구된 두 실패 케이스(bad config, HTTP 500+zero peak) 외에, assertion이 정상 pool을 잘못 실패시키지 않는지 확인하는 positive preflight 케이스(TEST 3, healthy mock → rc=0)를 추가로 실행했다. 검증 명령 계약을 대체한 것이 아니라 보강한 것이다.
- 계획의 `agent-test/dev/long-context-admission-smoke.md`는 command/criteria 문구 변경이 없어 수정하지 않았다(계획의 조건부 항목).
## 주요 설계 결정
- **HTTP 부족 실패 전파**: `fire_and_wait`가 `http_200=ok/total`을 계산한 뒤 `ok != total`이면 non-zero를 반환한다. `normal-10`(10/10), `all-long-slot-full`(6/6)은 이 helper로, `mixed`(long 4/4 + normal 6/6)는 scenario 내부 카운트로 각각 강제한다.
- **peak/queue assertion helper**: `poll_peaks`가 이미 `peak_in_flight=`, `peak_queued=`, `peak_long_in_flight=`, `peak_long_queued=`를 parseable key=value로 쓰므로, `read_kv`(값 추출)와 `assert_peak`(정수 검증 + `-ge`/`-le` 비교)을 추가해 scenario별 판정 기준을 강제한다.
- **scenario별 rc 집계**: `run_scenario`가 `rc` 하나로 fire/peak/recovery 실패를 OR 집계하고 `return "$rc"`한다. main의 `run_scenario "$SCENARIO" || rc=$?`가 이를 exit status로 전파한다.
- **preflight gating**: `edge config check`를 tee 파이프라인 밖에서 별도 capture해 exit status(`cfg_rc`)를 preflight `ok`에 반영한다. provider identity는 존재 + `health == "healthy"`를 모두 요구하고, 합산 `capacity`가 `NORMAL_CAPACITY_TOTAL(9)`와 일치해야 한다. 하나라도 실패하면 preflight가 rc=3을 반환한다.
- **long 필드 optional 처리 유지**: Control Plane status view는 `long_in_flight`/`long_queued`를 노출하지 않으므로(문서화된 관측 한계, live snapshot으로도 `<absent>` 확인), 해당 필드가 숫자로 보일 때만 assert하고 없으면 Edge dispatch log(`context_class`, `queue_reason=long_context_capacity_full`) 증거를 안내한다. 없다는 이유로 실패시키지 않는다.
- **provider health 필드 확인**: live Control Plane status snapshot 실제 shape를 확인한 결과 provider snapshot에 `health: "healthy"` 필드가 존재하고(별도 `status: "available"` 필드와 공존) capacity 합이 9이므로, `.health` 기반 assertion이 live pool과 정확히 일치함을 확인했다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- HTTP failure, config-check failure, missing/unhealthy provider, and peak/queue misses must produce non-zero exit.
- `normal-10` must enforce HTTP 10/10, peak `in_flight>=9`, `queued>=1`, and final recovery 0/0.
- `mixed` must enforce long 4/4 and normal 6/6 HTTP success plus final recovery 0/0; if status lacks long fields, Edge log evidence must be recorded.
- `all-long-slot-full` must enforce long 6/6 HTTP success, peak long-only `in_flight<=4`, queued evidence, final recovery 0/0, and Edge log evidence when long queue fields are absent.
- Live evidence must include actual stdout/stderr or exact output file paths, final status snapshots, and Edge log path/snippets.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### REVIEW_CAPACITY_SMOKE-1 중간 검증
```bash
$ bash -n scripts/e2e-long-context-admission-smoke.sh
syntax OK
# (no diagnostics; exit 0)
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
OK configs/edge.yaml
# rc=0
```
- 환경: local checkout `/config/workspace/iop`, `go version go1.26.2 linux/arm64` (module `go 1.24` 빌드 호환), `jq`/`curl`/`python3` 사용 가능.
### deterministic local mock regression
```bash
$ tmpdir=$(mktemp -d); port=18997; PORT=$port python3 - <<'PY' &
from http.server import BaseHTTPRequestHandler, HTTPServer
import json, os
class H(BaseHTTPRequestHandler):
def log_message(self, fmt, *args): pass
def do_GET(self):
if self.path == "/v1/models":
body = {"data":[{"id":"qwen3.6:35b"}]}
elif self.path == "/status":
body = {"provider_snapshots":[
{"id":"gx10-vllm","capacity":4,"in_flight":0,"queued":0,"health":"healthy"},
{"id":"onexplayer-lemonade","capacity":3,"in_flight":0,"queued":0,"health":"healthy"},
{"id":"mac-mlx-vllm","capacity":2,"in_flight":0,"queued":0,"health":"healthy"}]}
else:
self.send_response(404); self.end_headers(); return
data = json.dumps(body).encode()
self.send_response(200); self.send_header("Content-Type","application/json"); self.send_header("Content-Length",str(len(data))); self.end_headers(); self.wfile.write(data)
def do_POST(self):
self.rfile.read(int(self.headers.get("content-length","0") or 0))
self.send_response(500); self.end_headers(); self.wfile.write(b"fail")
HTTPServer(("127.0.0.1", int(os.environ["PORT"])), H).serve_forever()
PY
$ pid=$!; trap 'kill $pid 2>/dev/null || true; rm -rf "$tmpdir"' EXIT; sleep 1
$ if IOP_LONG_SMOKE_BASE_URL="http://127.0.0.1:$port/v1" IOP_LONG_SMOKE_STATUS_URL="http://127.0.0.1:$port/status" bash scripts/e2e-long-context-admission-smoke.sh --preflight --config "$tmpdir/missing.yaml" --out-dir "$tmpdir/out-preflight"; then echo "bad config unexpectedly passed"; exit 1; else echo "bad config failed as expected"; fi
# --- 실제 출력(핵심 라인) ---
## config check
$ go run ./apps/edge/cmd/edge config check --config /tmp/tmp.XXXX/missing.yaml
Error: load config: open /tmp/tmp.XXXX/missing.yaml: no such file or directory
load config: open /tmp/tmp.XXXX/missing.yaml: no such file or directory
exit status 1
config check FAILED (rc=1)
[long-admission-smoke] BLOCKER: edge config check failed (rc=1, config=/tmp/tmp.XXXX/missing.yaml)
[long-admission-smoke] /models OK
[long-admission-smoke] provider present+healthy: gx10-vllm
[long-admission-smoke] provider present+healthy: onexplayer-lemonade
[long-admission-smoke] provider present+healthy: mac-mlx-vllm
[long-admission-smoke] capacity total OK: 9 (== 9)
[long-admission-smoke] === PREFLIGHT BLOCKED (see out-dir; blockers are verification blockers, not user-review) ===
[long-admission-smoke] done rc=3 evidence=/tmp/tmp.XXXX/out-preflight
bad config failed as expected
$ if IOP_LONG_SMOKE_BASE_URL="http://127.0.0.1:$port/v1" IOP_LONG_SMOKE_STATUS_URL="http://127.0.0.1:$port/status" IOP_LONG_SMOKE_LONG_CHARS=100 IOP_LONG_SMOKE_POLL_SECS=2 bash scripts/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --out-dir "$tmpdir/out-scenario"; then echo "http failure unexpectedly passed"; exit 1; else echo "http failure failed as expected"; fi
# --- 실제 출력(핵심 라인) ---
[long-admission-smoke] === SCENARIO all-long-slot-full (fire 6 long > 4 slots; expect queue then dispatch) ===
peak_in_flight=0
peak_queued=0
peak_long_in_flight=n/a (Control Plane status view does not expose long fields)
peak_long_queued=n/a (Control Plane status view does not expose long fields)
[long-admission-smoke] all-long-slot-full: long http_200=0/6
[long-admission-smoke] all-long-slot-full: FAIL long http_200=0/6 (require 6/6)
[long-admission-smoke] all-long-slot-full: peak peak_in_flight=0 (-le 4) OK
[long-admission-smoke] all-long-slot-full: FAIL peak peak_queued=0 (require peak_queued -ge 1)
[long-admission-smoke] all-long-slot-full: long_queued not exposed by status view; capture Edge dispatch log (queue_reason=long_context_capacity_full) as review evidence.
[long-admission-smoke] all-long-slot-full: recovery OK (in_flight=0, queued=0)
[long-admission-smoke] done rc=1 evidence=/tmp/tmp.XXXX/out-scenario
http failure failed as expected
```
추가 positive 확인 (assertion이 healthy pool을 잘못 실패시키지 않음, 계획 두 케이스에 대한 보강):
```bash
# (a) healthy mock(정상 provider snapshot) + 유효 config로 preflight
$ ... bash scripts/e2e-long-context-admission-smoke.sh --preflight --config configs/edge.yaml --out-dir "$tmpdir/out-ok"
healthy preflight passed as expected (rc=0)
# (b) stateful mock(요청 발사 중 in_flight/queued 상승 후 회복 시뮬)로 normal-10 green path
$ ... bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --out-dir "$tmpdir/out"
[long-admission-smoke] normal-10: normal http_200=10/10
[long-admission-smoke] normal-10: peak peak_in_flight=9 (-ge 9) OK
[long-admission-smoke] normal-10: peak peak_queued=1 (-ge 1) OK
[long-admission-smoke] normal-10: recovery OK (in_flight=0, queued=0)
[long-admission-smoke] done rc=0
```
판정: bad config preflight rc=3(non-zero), HTTP 500+zero peak scenario rc=1(non-zero), healthy preflight rc=0, 정상 부하 normal-10 rc=0. exit contract가 양방향(기준 미달=실패, 기준 충족=통과)으로 강제됨을 확인. (poll 간격 3s 대비 요청이 짧으면 peak sampling 이 놓칠 수 있어 mock 요청 지속시간을 늘려 확인했다. 실제 long-context 요청은 poll 간격보다 훨씬 길어 peak 관측에 문제 없음.)
### REVIEW_CAPACITY_SMOKE-2 live dev smoke
```bash
# 문서화된 live preflight 명령을 verbatim 실행 (토큰 guard 때문에 short-circuit)
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash scripts/e2e-long-context-admission-smoke.sh --preflight --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
# (출력 없음) ssh_exit_rc=1 → `test -n "$IOP_LONG_SMOKE_TOKEN"` 실패로 && 체인 short-circuit, bash 미실행
# normal-10 / mixed / all-long-slot-full 도 동일 토큰 guard로 실행 불가 (BLOCKED, 아래 근거 참조)
```
**REVIEW_CAPACITY_SMOKE-2 는 검증 blocker로 미완료(환경/secret/service 준비 부재).** 계획/스텁 지시대로 사용자 리뷰 요청이 아니라 정확한 명령/출력으로 기록한다.
blocker 근거 (dev runner `toki@toki-labs.com`, repo `/Users/toki/agent-work/iop-dev`):
```bash
# 1) 토큰 미설정 (non-interactive + login shell 모두)
$ ssh toki@toki-labs.com 'test -n "${IOP_LONG_SMOKE_TOKEN:-}" && echo TOKEN_SET || echo TOKEN_UNSET'
TOKEN_UNSET
$ ssh toki@toki-labs.com 'bash -lc "test -n \"${IOP_LONG_SMOKE_TOKEN:-}\" && echo TOKEN_SET_LOGIN || echo TOKEN_UNSET_LOGIN"'
TOKEN_UNSET_LOGIN
# 2) Edge OpenAI-compatible endpoint 는 토큰 없이는 401
$ ssh toki@toki-labs.com 'curl -sS -o /dev/null -w "http_code=%{http_code}\n" --connect-timeout 8 http://127.0.0.1:18083/v1/models'
http_code=401
# 3) runner 에 go 미설치 → 문서화된 preflight 의 `go run ... config check` 실행 불가
$ ssh toki@toki-labs.com 'command -v go >/dev/null 2>&1 && go version || echo GO_ABSENT'
GO_ABSENT
# 4) 수정한 script 는 untracked 로컬 파일 → runner checkout(현재 clean, HEAD=2d08d24)에 미배포
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && ls scripts/e2e-long-context-admission-smoke.sh'
ls: scripts/e2e-long-context-admission-smoke.sh: No such file or directory
```
partial live evidence — Control Plane status 는 접근 가능하고 provider pool 은 healthy (baseline 일치). scenario 요청 발사는 위 401 때문에 불가:
```bash
$ ssh toki@toki-labs.com 'curl -fsS --connect-timeout 8 http://127.0.0.1:18001/edges/edge-toki-labs-dev/status -o /tmp/iop-long-admission-smoke/live_status_snapshot.json && jq -r "..." live_status_snapshot.json'
id=mac-mlx-vllm capacity=2 in_flight=0 queued=0 health=healthy long_in_flight=<absent> long_queued=<absent>
id=gx10-vllm capacity=4 in_flight=0 queued=0 health=healthy long_in_flight=<absent> long_queued=<absent>
id=onexplayer-lemonade capacity=3 in_flight=0 queued=0 health=healthy long_in_flight=<absent> long_queued=<absent>
TOTAL capacity=9
```
- 확인: 세 provider identity(`gx10-vllm`, `onexplayer-lemonade`, `mac-mlx-vllm`) 존재, `health=healthy`, 합산 capacity=9(=NORMAL_CAPACITY_TOTAL). status view 는 `long_in_flight`/`long_queued` 미노출(문서화된 관측 한계와 일치) → live 시 long-slot 회복은 Edge dispatch log 근거 필요.
- 후속 재개 조건: (a) runner 환경에 `IOP_LONG_SMOKE_TOKEN` 주입, (b) runner 에 go 설치 또는 preflight config check 를 prebuilt `iop-edge` 바이너리로 수행, (c) 수정한 script 를 runner checkout 에 배포(커밋/푸시 후 pull, 또는 evidence 수집용 복사). 세 조건 충족 시 계획의 preflight + normal-10 + mixed + all-long-slot-full 를 그대로 재실행해 actual stdout/stderr, 최종 status snapshot, Edge dispatch log(`context_class`, `queue_reason=long_context_capacity_full`) 를 기록한다.
### Evidence paths
- status snapshots:
- live partial: runner `toki@toki-labs.com:/tmp/iop-long-admission-smoke/live_status_snapshot.json` (Control Plane status, provider pool healthy, capacity=9, long 필드 미노출).
- local mock regression preflight/scenario snapshots: 각 실행 `$tmpdir/out-preflight/preflight_status_*.json`, `$tmpdir/out-scenario/all-long-slot-full_final_*.json` (mktemp 임시 디렉터리, 실행 종료 시 trap 으로 삭제됨).
- peak files:
- local mock scenario: `$tmpdir/out-scenario/all-long-slot-full_peaks_*.txt` (`peak_in_flight=0`, `peak_queued=0`, long 필드 `n/a`).
- request stdout/stderr files:
- local mock scenario 요청별: `$tmpdir/out-scenario/all-long-slot-full_long_<i>_*.{code,resp.json,curl.err}` (mock POST → http 500).
- live scenario 요청 파일: 없음(401 blocker 로 요청 미발사).
- Edge log evidence path/snippets:
- 없음. live scenario 를 실행하지 못해 Edge dispatch log(`context_class`, `queue_reason=long_context_capacity_full`) 근거 미수집. 재개 조건 충족 후 수집 필요(위 REVIEW_CAPACITY_SMOKE-2 재개 조건 참조).
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
Sections and their ownership:
| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| Roadmap Targets | Fixed at stub creation from plan | 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 | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| 구현 항목별 완료 여부 | Implementing agent checks only | Item names are fixed; check `[ ]` -> `[x]` only |
| 구현 체크리스트 | Implementing agent checks only | Text/order fixed from plan |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected Milestone lock decision blocks implementation |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 | Implementing agent | Fill actual command output only |
| 코드리뷰 결과 | Review agent appends | Not included in stub |
## 코드리뷰 결과
- 종합 판정: FAIL
- 차원별 평가:
- correctness: Pass
- completeness: Fail
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- implementation deviation: Fail
- verification trust: Fail
- spec conformance: Fail
- 발견된 문제:
- Required: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log:70` / `:77` / `:241` - SDD S09와 plan의 필수 항목인 live dev provider pool preflight, `normal-10`, `mixed`, `all-long-slot-full` rerun evidence가 미완료입니다. 구현 스텁도 `Trusted S09 evidence rerun`과 live evidence checklist를 unchecked로 남겼고, 검증 결과는 token 미설정, runner `go` 부재, source 미동기화로 scenario 요청이 미발사되었다고 기록합니다. 수정: 후속 루프에서 runner source를 현재 변경분과 동기화하거나 evidence 수집용으로 스크립트를 배포하고, token을 원격 환경에 안전하게 주입하며, `edge config check`를 `go run` 또는 prebuilt `iop-edge`로 수행한 뒤 live preflight + 세 scenario를 실제 실행해 stdout/stderr, final status snapshot, Edge dispatch log(`context_class`, `queue_reason=long_context_capacity_full`) 경로를 review stub에 기록하세요.
- 다음 단계: user-review gate는 트리거하지 않습니다. Milestone lock 결정이 아니라 검증 환경/evidence 공백이므로, `REVIEW_REVIEW_CAPACITY_SMOKE` 후속 PLAN/CODE_REVIEW를 작성해 live evidence 재수집을 진행합니다.

View file

@ -0,0 +1,434 @@
<!-- task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke plan=2 tag=REVIEW_REVIEW_CAPACITY_SMOKE -->
# Code Review Reference - REVIEW_REVIEW_CAPACITY_SMOKE
> **[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 by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> 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-05
task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke, plan=2, tag=REVIEW_REVIEW_CAPACITY_SMOKE
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
- `capacity-smoke`: dev 환경 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오와 최종 회복 근거
- Completion mode: check-on-pass
## Archive Evidence Snapshot
- Current archived plan: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_1.log`
- Current archived review: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log`
- Verdict: FAIL
- Required summary:
- live dev provider pool preflight and `normal-10`, `mixed`, `all-long-slot-full` rerun evidence is missing.
- archived review lines `70`, `77`, and `241` show `Trusted S09 evidence rerun` unchecked and scenario execution blocked before request dispatch.
- blockers recorded: remote `IOP_LONG_SMOKE_TOKEN` unset, runner `go` absent, and local untracked smoke script not present in the clean runner checkout.
- Affected files:
- `scripts/e2e-long-context-admission-smoke.sh`
- `agent-test/dev/long-context-admission-smoke.md`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
- Verification evidence from review:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`: PASS.
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`: PASS.
- deterministic local mock regression: bad config preflight returned non-zero and HTTP 500 + zero peak returned non-zero.
- live partial status snapshot: provider identities healthy and total normal capacity `9`, but live request scenarios were not run.
- Allowed narrow reread:
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_1.log`
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G08.md` -> `code_review_cloud_G08_N.log`, `PLAN-cloud-G08.md` -> `plan_cloud_G08_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-model-group-long-context-admission`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_REVIEW_CAPACITY_SMOKE-1] Runner config-check path | [x] |
| [REVIEW_REVIEW_CAPACITY_SMOKE-2] Dev smoke doc update | [x] |
| [REVIEW_REVIEW_CAPACITY_SMOKE-3] Trusted S09 live evidence rerun | [x] |
## 구현 체크리스트
- [x] `scripts/e2e-long-context-admission-smoke.sh`가 `go run`이 없는 runner에서도 prebuilt Edge binary로 `config check`를 실행할 수 있게 보강한다.
- [x] `agent-test/dev/long-context-admission-smoke.md`에 prebuilt Edge binary override와 runner temp-copy evidence 흐름을 문서화한다.
- [x] local quick check와 deterministic local mock regression을 다시 실행해 script exit contract가 유지되는지 검증한다.
- [x] runner preflight를 수행하고 token presence, source/script availability, Edge binary/config check, endpoint reachability, provider health/capacity를 실제 stdout/stderr로 기록한다.
- [x] live dev provider pool에서 preflight와 `normal-10`, `mixed`, `all-long-slot-full`을 실행하고 actual stdout/stderr 또는 exact output file/status snapshot/Edge log evidence를 review stub에 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/`를 `agent-task/archive/YYYY/MM/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-model-group-long-context-admission`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-model-group-long-context-admission/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
- **원격 token 주입 방식 대체**: plan의 중간/최종 검증 명령은 runner 환경에 `IOP_LONG_SMOKE_TOKEN`이 이미 export되어 있다고 가정(`test -n "${IOP_LONG_SMOKE_TOKEN:-}"`)했지만, 실제 runner는 비대화형/login shell 모두 unset이었다(이전 루프 blocker와 동일). Edge OpenAI 표면은 `401`로 token 필수임을 확인했고, token 원본이 runner-local `build/dev-runtime/edge.yaml`의 `openai.bearer_token`에 있으므로 원격 shell 안에서만 `awk`로 추출해 `IOP_LONG_SMOKE_TOKEN`으로 export한 뒤 script를 실행했다. token 값은 stdout/stderr/tracked 파일 어디에도 출력되지 않는다(dev 규칙 "token/secret 원문은 원격 환경에서 주입" 준수). 대체 명령 원형:
```bash
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && IOP_LONG_SMOKE_TOKEN="$(awk '\''/^openai:/{ib=1;next} ib&&/^[^[:space:]]/{ib=0} ib&&$1=="bearer_token:"{v=$2;gsub(/"/,"",v);print v;exit}'\'' build/dev-runtime/edge.yaml)" && test -n "$IOP_LONG_SMOKE_TOKEN" && export IOP_LONG_SMOKE_TOKEN && IOP_LONG_SMOKE_REPO=... IOP_LONG_SMOKE_EDGE_BIN=... IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh <mode> --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
```
- **bash 3.2 호환 수정 추가**: 첫 live preflight에서 runner macOS bash `3.2.57`이 `mapfile`(bash 4+)을 지원하지 않아 `mapfile: command not found` + `AUTH[@]: unbound variable`로 실패했다. plan 항목 1의 "runner 실행 경로 보정" 범위에서 `auth_header_args`/`mapfile` 조합을 전역 `AUTH_ARGS` 배열 + `${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"}` idiom으로 교체했다(빈 배열이 bash 3.2 `set -u`에서도 안전). token 노출 특성은 기존과 동일하다(curl argv로 전달).
- **mock regression 확장**: plan의 deterministic local mock regression 2건에 더해, 새 `IOP_LONG_SMOKE_EDGE_BIN` 경로의 양성(빌드된 binary로 preflight PASS + preflight 출력에 실제 명령 기록)과 음성(존재하지 않는 binary 경로 → preflight 실패) 검증을 추가로 수행했다. 기존 2건 계약은 그대로 유지·통과.
## 주요 설계 결정
- `IOP_LONG_SMOKE_EDGE_BIN` override는 preflight의 `config check` 실행 명령만 바꾼다. 설정 시 `"$EDGE_BIN" config check --config "$CONFIG"`를 `REPO_ROOT`에서 실행하고, 미설정 시 기존 `go run ./apps/edge/cmd/edge config check` 동작을 그대로 유지한다. binary가 존재하지 않거나 실행 불가면 `edge binary not executable: <path>`를 config check 실패로 기록해 preflight blocker(rc=3)로 이어진다.
- preflight 출력의 `## config check` 아래에 실제 실행 명령(`${cfg_cmd[*]}`)을 그대로 기록해, evidence만 보고도 `go run` 경로인지 prebuilt binary 경로인지 구분되게 했다. token은 어떤 명령 echo에도 포함되지 않는다.
- runner 호환성: script는 dev runner의 기본 bash(macOS 3.2)에서 실행 가능해야 하므로 bash-4 전용 builtin(`mapfile`)을 제거하고 주석으로 재도입 금지를 남겼다. usage의 `sed -n` 범위는 header 주석 2줄 추가에 맞춰 `3,54p`로 보정했다.
- 문서(`agent-test/dev/long-context-admission-smoke.md`)에는 override 환경 변수 2종(`IOP_LONG_SMOKE_REPO`, `IOP_LONG_SMOKE_EDGE_BIN`)과 "local untracked script는 runner temp 경로로 복사/sync 후에만 현재 변경 evidence로 인정" 흐름을 명시하고, preflight의 `## source state`/`## config check` 줄로 어느 source/binary 기준인지 함께 기록하도록 했다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- runner에 `go`가 없어도 script preflight가 prebuilt Edge binary override로 `config check`를 수행할 수 있어야 한다.
- token value must not appear in tracked files or review output.
- live evidence must include actual stdout/stderr or exact output file paths for preflight, `normal-10`, `mixed`, and `all-long-slot-full`.
- final status snapshots must show `in_flight=0`, `queued=0`; when long fields are absent, Edge dispatch log evidence must include `context_class` and `queue_reason="long_context_capacity_full"`.
- SDD S09 evidence must be sufficient for `capacity-smoke` Roadmap Completion on PASS.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### REVIEW_REVIEW_CAPACITY_SMOKE-1 중간 검증
```bash
$ bash -n scripts/e2e-long-context-admission-smoke.sh
(no output; exit 0)
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
OK configs/edge.yaml
```
### REVIEW_REVIEW_CAPACITY_SMOKE-2 중간 검증
```bash
$ rg --sort path -n "IOP_LONG_SMOKE_(REPO|EDGE_BIN)|long-context-admission" agent-test/dev/long-context-admission-smoke.md
3:test_profile: long-context-admission-smoke
9:# long-context-admission dev capacity smoke
13:- Milestone `model-group-long-context-admission`의 SDD S09(`capacity-smoke`) evidence를 수집할 때
18:- `scripts/e2e-long-context-admission-smoke.sh`
37:- repo override: script를 checkout 밖(`/tmp` 등)에서 실행하면 `IOP_LONG_SMOKE_REPO`로 `edge config check` 기준 repo root를 지정한다. 미지정 시 script 위치의 상위 디렉터리를 repo root로 본다.
38:- Edge binary override: runner에 `go` toolchain이 없으면 `IOP_LONG_SMOKE_EDGE_BIN`(예: `/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge`)으로 prebuilt Edge binary를 지정해 `config check`를 수행한다. 미지정 시 `go run ./apps/edge/cmd/edge`를 사용한다.
42:- syntax: `bash -n scripts/e2e-long-context-admission-smoke.sh`
43:- preflight: `bash scripts/e2e-long-context-admission-smoke.sh --preflight --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
44:- normal-10: `bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
45:- mixed: `bash scripts/e2e-long-context-admission-smoke.sh --scenario mixed --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
46:- all-long-slot-full: `bash scripts/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
52: bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke
62:scp scripts/e2e-long-context-admission-smoke.sh toki@toki-labs.com:/tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh
64: IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev \
65: IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge \
67: bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --preflight --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
```
### REVIEW_REVIEW_CAPACITY_SMOKE-3 중간 검증
```bash
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git rev-parse HEAD && git status --short && test -x build/dev-runtime/bin/edge && build/dev-runtime/bin/edge config check --config build/dev-runtime/edge.yaml && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && curl -fsS --connect-timeout 8 -o /dev/null -w "models_http=%{http_code}\n" -H "Authorization: Bearer ${IOP_LONG_SMOKE_TOKEN}" http://127.0.0.1:18083/v1/models'
2d08d240d48a954df6847df2ba08fb8b39fc3f7d
OK build/dev-runtime/edge.yaml
# exit=1: git state clean(2d08d24), edge binary 실행 가능, config check OK까지 통과 후
# `test -n "${IOP_LONG_SMOKE_TOKEN:-}"`에서 중단. runner env에 token이 export되어 있지 않음
# (비대화형/login shell 모두 unset 확인). 아래 대체 검증으로 token 주입 경로를 확보했다.
# 대체 검증 1: runner edge.yaml의 openai.bearer_token presence + 인증 reachability (token 미출력)
$ ssh toki@toki-labs.com 'tok="$(awk ...openai bearer_token 추출... build/dev-runtime/edge.yaml)"; if [ -n "$tok" ]; then echo "openai_bearer_token=present"; curl -fsS --connect-timeout 8 -o /dev/null -w "models_auth_http=%{http_code}\n" -H "Authorization: Bearer ${tok}" http://127.0.0.1:18083/v1/models; fi'
openai_bearer_token=present
models_auth_http=200
# 참고: 무인증 요청은 401 (token 필수 확인)
$ ssh toki@toki-labs.com 'curl -fsS --connect-timeout 8 -o /dev/null -w "models_noauth_http=%{http_code}\n" http://127.0.0.1:18083/v1/models'
models_noauth_http=401
curl: (22) The requested URL returned error: 401
# runner bash 버전 (mapfile 미지원 → script bash 3.2 호환 수정 근거)
$ ssh toki@toki-labs.com 'bash --version | head -1'
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)
```
### 최종 검증
아래 4개 ssh 명령의 `test -n "${IOP_LONG_SMOKE_TOKEN:-}"` 부분은 runner env에 token이 없어 `계획 대비 변경 사항`의 원격 token 주입 prefix(`IOP_LONG_SMOKE_TOKEN="$(awk ... build/dev-runtime/edge.yaml)" && test -n "$IOP_LONG_SMOKE_TOKEN" && export IOP_LONG_SMOKE_TOKEN`)로 대체 실행했다. 나머지 인자/환경 변수는 계약 그대로다.
```bash
$ git rev-parse HEAD
2d08d240d48a954df6847df2ba08fb8b39fc3f7d
$ git status --short
M agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md
M agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/PLAN-cloud-G08.md
M agent-test/dev/edge-smoke.md
?? agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log
?? agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log
?? agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_0.log
?? agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_1.log
?? agent-test/dev/long-context-admission-smoke.md
?? scripts/e2e-long-context-admission-smoke.sh
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
OK configs/edge.yaml
$ bash -n scripts/e2e-long-context-admission-smoke.sh
(no output; exit 0)
$ <deterministic local mock regression from PLAN-cloud-G08.md (plan_cloud_G08_1.log:214-239 원형 + EDGE_BIN 확장)>
bad config failed as expected
http failure failed as expected
edge-bin preflight passed as expected
edge-bin command recorded in preflight output
missing edge-bin failed as expected
mock regression overall rc=0
$ ssh toki@toki-labs.com 'mkdir -p /tmp/iop-long-admission-smoke-runner'
(no output; exit 0)
$ scp scripts/e2e-long-context-admission-smoke.sh toki@toki-labs.com:/tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh
(no output; exit 0. bash 3.2 수정 후 재배포 checksum 로컬/runner 동일: ae74ae4f00dee4ed4cdbe51f6b29cc190c6f38368bd80d1104526c5bb90bb649)
$ ssh toki@toki-labs.com 'chmod +x /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh'
(no output; exit 0)
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --preflight --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
[long-admission-smoke] out-dir=/tmp/iop-long-admission-smoke run=20260705T221507Z base_url=http://127.0.0.1:18083/v1
[long-admission-smoke] === PREFLIGHT ===
run=20260705T221507Z
workdir=/Users/toki/agent-work/iop-dev
base_url=http://127.0.0.1:18083/v1
status_url=http://127.0.0.1:18001/edges/edge-toki-labs-dev/status status_ssh=<direct>
config=build/dev-runtime/edge.yaml
## source state
$ git -C /Users/toki/agent-work/iop-dev rev-parse HEAD
2d08d240d48a954df6847df2ba08fb8b39fc3f7d
$ git -C /Users/toki/agent-work/iop-dev status --short
## config check
$ /Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge config check --config build/dev-runtime/edge.yaml
OK build/dev-runtime/edge.yaml
config check OK
[long-admission-smoke] endpoint reachability: http://127.0.0.1:18083/v1/models
[long-admission-smoke] /models OK
model=qwen3.6:35b
[long-admission-smoke] status reachability: http://127.0.0.1:18001/edges/edge-toki-labs-dev/status
[long-admission-smoke] status OK -> /tmp/iop-long-admission-smoke/preflight_status_20260705T221507Z.json
provider=mac-mlx-vllm capacity=2 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=gx10-vllm capacity=4 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=onexplayer-lemonade capacity=3 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
TOTAL in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a
[long-admission-smoke] provider present+healthy: gx10-vllm
[long-admission-smoke] provider present+healthy: onexplayer-lemonade
[long-admission-smoke] provider present+healthy: mac-mlx-vllm
[long-admission-smoke] capacity total OK: 9 (== 9)
[long-admission-smoke] NOTE: status view does not expose long_in_flight/long_queued; long-slot recovery must be read from Edge dispatch logs (context_class/queue_reason) or a follow-up that adds long fields to the Control Plane status view.
[long-admission-smoke] expected baseline: normal_capacity_total=9 long_slot_total=4
[long-admission-smoke] === PREFLIGHT OK ===
[long-admission-smoke] done rc=0 evidence=/tmp/iop-long-admission-smoke
(exit 0)
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --scenario normal-10 --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
[long-admission-smoke] out-dir=/tmp/iop-long-admission-smoke run=20260705T221603Z base_url=http://127.0.0.1:18083/v1
[long-admission-smoke] === SCENARIO normal-10 (expect peak in_flight>=9, queued>=1) ===
[long-admission-smoke] normal-10: firing 10 normal request(s) to http://127.0.0.1:18083/v1/chat/completions
label=normal-10 samples=6
peak_in_flight=9
peak_queued=3
peak_long_in_flight=n/a (Control Plane status view does not expose long fields)
peak_long_queued=n/a (Control Plane status view does not expose long fields)
[long-admission-smoke] normal-10: normal http_200=10/10
[long-admission-smoke] normal-10: peak peak_in_flight=9 (-ge 9) OK
[long-admission-smoke] normal-10: peak peak_queued=3 (-ge 1) OK
[long-admission-smoke] normal-10 final snapshot:
provider=mac-mlx-vllm capacity=2 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=gx10-vllm capacity=4 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=onexplayer-lemonade capacity=3 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
TOTAL in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a
[long-admission-smoke] normal-10: long_in_flight/long_queued not exposed by status view; verify recovery via Edge dispatch logs (context_class/queue_reason).
[long-admission-smoke] normal-10: recovery OK (in_flight=0, queued=0)
[long-admission-smoke] done rc=0 evidence=/tmp/iop-long-admission-smoke
(exit 0)
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --scenario mixed --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
[long-admission-smoke] out-dir=/tmp/iop-long-admission-smoke run=20260705T221633Z base_url=http://127.0.0.1:18083/v1
[long-admission-smoke] === SCENARIO mixed (long slots held; normal must not head-of-line block) ===
[long-admission-smoke] synthetic long prompt: 384000 chars -> /tmp/iop-long-admission-smoke/long_prompt_20260705T221633Z.txt
[long-admission-smoke] synthetic long prompt: 384000 chars -> /tmp/iop-long-admission-smoke/long_prompt_20260705T221633Z.txt
[long-admission-smoke] synthetic long prompt: 384000 chars -> /tmp/iop-long-admission-smoke/long_prompt_20260705T221633Z.txt
[long-admission-smoke] synthetic long prompt: 384000 chars -> /tmp/iop-long-admission-smoke/long_prompt_20260705T221633Z.txt
label=mixed samples=113
peak_in_flight=6
peak_queued=0
peak_long_in_flight=n/a (Control Plane status view does not expose long fields)
peak_long_queued=n/a (Control Plane status view does not expose long fields)
[long-admission-smoke] mixed: long http_200=4/4 normal http_200=6/6 (head-of-line blocking would show normal stalling behind full long slots)
[long-admission-smoke] mixed: long_in_flight not exposed by status view; capture Edge dispatch log (context_class) as review evidence.
[long-admission-smoke] mixed final snapshot:
provider=mac-mlx-vllm capacity=2 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=gx10-vllm capacity=4 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=onexplayer-lemonade capacity=3 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
TOTAL in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a
[long-admission-smoke] mixed: long_in_flight/long_queued not exposed by status view; verify recovery via Edge dispatch logs (context_class/queue_reason).
[long-admission-smoke] mixed: recovery OK (in_flight=0, queued=0)
[long-admission-smoke] done rc=0 evidence=/tmp/iop-long-admission-smoke
(exit 0)
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
[long-admission-smoke] out-dir=/tmp/iop-long-admission-smoke run=20260705T222235Z base_url=http://127.0.0.1:18083/v1
[long-admission-smoke] === SCENARIO all-long-slot-full (fire 6 long > 4 slots; expect queue then dispatch) ===
[long-admission-smoke] all-long-slot-full: firing 6 long request(s) to http://127.0.0.1:18083/v1/chat/completions
[long-admission-smoke] synthetic long prompt: 384000 chars -> /tmp/iop-long-admission-smoke/long_prompt_20260705T222235Z.txt
(동일 라인 5회 반복 생략: 병렬 요청들이 같은 prompt 파일 생성을 각자 로그)
label=all-long-slot-full samples=98
peak_in_flight=4
peak_queued=6
peak_long_in_flight=n/a (Control Plane status view does not expose long fields)
peak_long_queued=n/a (Control Plane status view does not expose long fields)
[long-admission-smoke] all-long-slot-full: long http_200=6/6
[long-admission-smoke] all-long-slot-full: peak peak_in_flight=4 (-le 4) OK
[long-admission-smoke] all-long-slot-full: peak peak_queued=6 (-ge 1) OK
[long-admission-smoke] all-long-slot-full: long_queued not exposed by status view; capture Edge dispatch log (queue_reason=long_context_capacity_full) as review evidence.
[long-admission-smoke] all-long-slot-full final snapshot:
provider=mac-mlx-vllm capacity=2 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=gx10-vllm capacity=4 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
provider=onexplayer-lemonade capacity=3 in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a health=healthy
TOTAL in_flight=0 queued=0 long_in_flight=n/a long_queued=n/a
[long-admission-smoke] all-long-slot-full: long_in_flight/long_queued not exposed by status view; verify recovery via Edge dispatch logs (context_class/queue_reason).
[long-admission-smoke] all-long-slot-full: recovery OK (in_flight=0, queued=0)
[long-admission-smoke] done rc=0 evidence=/tmp/iop-long-admission-smoke
(exit 0)
```
### Edge dispatch log evidence (long classification / queue reason)
Control Plane status view가 long 필드를 노출하지 않으므로(`agent-test/dev/long-context-admission-smoke.md`의 관측 한계), long-slot 예약/대기/회복은 runner의 Edge dispatch log로 확인했다.
- log path (runner): `/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/logs/edge.log` (Edge PID 3527, `./bin/edge --config edge.yaml serve`가 open 중인 파일을 `lsof`로 확인)
- 이번 세션 window: 시나리오 시작 전 line `212423` 이후 → 종료 후 line `212501`. window 내 `context_class` 매칭 16+10건, 그중 `"context_class":"long"` 10건(mixed 4 + all-long-slot-full 6), `long_context_capacity_full` 2건.
normal-10 window 발췌 (`context_class":"normal"` dispatch, capacity 초과 1건은 `queue_reason":"capacity_full"` 후 재dispatch):
```json
{"ts":1783289763.371955,"msg":"openai chat completion dispatch","run_id":"manual-1783289763371607000","node_id":"gx10-vllm-node","adapter":"gx10-vllm","estimated_input_tokens":15,"context_class":"normal","queue_reason":"dispatched"}
{"ts":1783289767.179019,"msg":"openai chat completion dispatch","run_id":"manual-1783289767178800000","node_id":"gx10-vllm-node","adapter":"gx10-vllm","estimated_input_tokens":15,"context_class":"normal","queue_reason":"capacity_full"}
{"ts":1783289793.426454,"msg":"openai chat completion dispatch","run_id":"manual-1783289793426344000","node_id":"gx10-vllm-node","adapter":"gx10-vllm","estimated_input_tokens":15,"context_class":"normal","queue_reason":"dispatched"}
```
mixed window 발췌: long 4건이 `estimated_input_tokens=120018`, `context_class":"long"`으로 dispatch, provider 분포 gx10=1 / onexplayer=2 / mac=1 (long slot baseline과 동일):
```json
{"ts":1783289851.4141288,"run_id":"manual-1783289851413390000","node_id":"gx10-vllm-node","adapter":"gx10-vllm","estimated_input_tokens":120018,"context_class":"long","queue_reason":"dispatched"}
{"ts":1783289851.489273,"run_id":"manual-1783289851488820000","node_id":"onexplayer-lemonade-node","adapter":"onexplayer-lemonade","estimated_input_tokens":120018,"context_class":"long","queue_reason":"dispatched"}
{"ts":1783289851.5138822,"run_id":"manual-1783289851513167000","node_id":"mac-codex-node","adapter":"mac-mlx-vllm","estimated_input_tokens":120018,"context_class":"long","queue_reason":"dispatched"}
{"ts":1783289851.575644,"run_id":"manual-1783289851573399000","node_id":"onexplayer-lemonade-node","adapter":"onexplayer-lemonade","estimated_input_tokens":120018,"context_class":"long","queue_reason":"dispatched"}
```
all-long-slot-full window 발췌: 6 long 중 2건이 `queue_reason":"long_context_capacity_full"`로 대기 후, slot 회복 시 완료 출력(`chat_handler.go:324`)까지 도달 (HTTP 200 6/6과 일치):
```json
{"ts":1783290240.395453,"run_id":"manual-1783290240386434000","node_id":"gx10-vllm-node","adapter":"gx10-vllm","estimated_input_tokens":120018,"context_class":"long","queue_reason":"long_context_capacity_full"}
{"ts":1783290253.208964,"run_id":"manual-1783290253201645000","node_id":"onexplayer-lemonade-node","adapter":"onexplayer-lemonade","estimated_input_tokens":120018,"context_class":"long","queue_reason":"long_context_capacity_full"}
{"ts":1783290258.675044,"msg":"openai chat completion output","run_id":"manual-1783290240386434000","content_len":135,"reasoning_len":1836}
{"ts":1783290294.1531572,"msg":"openai chat completion output","run_id":"manual-1783290253201645000","content_len":111,"reasoning_len":4120}
```
### 저장된 evidence 경로 (runner)
현재 run stamp 기준 (`20260705T221507Z` preflight, `20260705T221603Z` normal-10, `20260705T221633Z` mixed, `20260705T222235Z` all-long-slot-full; 같은 디렉터리의 `20260705T1211xxZ`/`121538Z` 파일은 이전 FAIL 루프 잔여물):
- preflight: `/tmp/iop-long-admission-smoke/preflight_20260705T221507Z.txt`, `preflight_status_20260705T221507Z.json`, `models_20260705T221507Z.json`
- normal-10: `normal-10_peaks_20260705T221603Z.txt`, `normal-10_final_20260705T221603Z.json`, `normal-10_normal_{1..10}_20260705T221603Z.{code,resp.json}`
- mixed: `mixed_peaks_20260705T221633Z.txt`, `mixed_final_20260705T221633Z.json`, `mixed_long_{1..4}_20260705T221633Z.*`, `mixed_normal_{1..6}_20260705T221633Z.*`
- all-long-slot-full: `all-long-slot-full_peaks_20260705T222235Z.txt`, `all-long-slot-full_final_20260705T222235Z.json`, `all-long-slot-full_long_{1..6}_20260705T222235Z.*`
- runner temp script: `/tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh` (sha256 `ae74ae4f...` local과 동일)
### 추가 발견 사항: 이전 루프 로그의 token 원문 잔존 (follow-up)
구현 완료 후 token 유출 스캔(`grep -rF "<runner에서 추출한 token>"`; 값 미출력)에서 이번 세션 산출물(`CODE_REVIEW-cloud-G08.md`, script, dev 문서)은 깨끗했으나, **이전 루프 산출물 2곳에서 bearer token 원문이 발견**되었다. 두 줄 모두 "원문을 남기지 않았다"고 서술하면서 백틱 안에 원문을 포함하고 있다:
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log:58` — 현재 untracked이지만 `.gitignore`가 `agent-task/**/*.log`를 unignore하므로 다음 commit/archive에서 tracked가 된다. **finalization 전에 마스킹 필요.**
- `agent-task/archive/2026/07/m-openai-compatible-think-control/05+01,02,03,04_dev_smoke/code_review_cloud_G07_0.log:72` — 이미 tracked(git history 포함). dev 규칙 "token/secret 원문은 tracked 파일에 기록하지 않는다" 위반 상태. 마스킹 + (필요 시) token rotate는 별도 follow-up 판단 대상.
archive 로그는 구현 에이전트 수정 금지 대상이라 본 세션에서는 수정하지 않고 기록만 남긴다. 해당 token은 이전 로그 스스로 "저민감도 로컬 dev 토큰"으로 분류한 값이다.
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
Sections and their ownership:
| Section                 | Owner | Note                                                                  |
| ------------------------------------------| ----------------------------------| -----------------------------------------------------------------------------------------------------------------------------------------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these                                           |
| Roadmap Targets             | Fixed at stub creation from plan | 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 | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| 구현 항목별 완료 여부          | Implementing agent checks only | Item names are fixed; check `[ ]` -> `[x]` only                                             |
| 구현 체크리스트             | Implementing agent checks only | Text/order fixed from plan                                                       |
| 코드리뷰 전용 체크리스트         | Review agent only | Implementing agent must not modify or check this section                                        |
| 계획 대비 변경 사항, 주요 설계 결정   | Implementing agent | Replace placeholder text with actual content                                              |
| 사용자 리뷰 요청             | Implementing agent | Keep `상태: 없음` unless a selected Milestone lock decision blocks implementation                            |
| 리뷰어를 위한 체크포인트         | Fixed at stub creation | Pre-filled from plan                                                          |
| 검증 결과                | Implementing agent | Fill actual command output only                                                     |
| 코드리뷰 결과              | Review agent appends | Not included in stub                                                          |
## 코드리뷰 결과
- 종합 판정: PASS
- 차원별 평가:
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- implementation deviation: Pass
- verification trust: Pass
- spec conformance: Pass
- 발견된 문제:
- Nit: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log:58`에 이전 루프 dev token 원문이 남아 있었음. 리뷰 중 `<masked-dev-token>`으로 마스킹 완료.
- 검증 확인:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`: PASS.
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`: PASS.
- `rg --sort path -n "IOP_LONG_SMOKE_(REPO|EDGE_BIN)|long-context-admission" agent-test/dev/long-context-admission-smoke.md`: PASS.
- deterministic local mock regression: bad config, HTTP 500, missing Edge binary 실패 경로와 Edge binary override 성공/명령 기록 확인 PASS.
- runner evidence spot-check: local/runner script sha256 일치, preflight/three scenario evidence files present, all request code files `200`, final snapshots `in_flight=0`, `queued=0`, Edge log window `context_class=long` 10건 및 `long_context_capacity_full` 2건 확인 PASS.
- secret scan for current task artifacts/script/dev docs: PASS after masking.
- 다음 단계: PASS finalization. Active plan/review를 로그로 아카이브하고 `complete.log` 작성 후 task directory를 `agent-task/archive/YYYY/MM/...`로 이동한다.

View file

@ -0,0 +1,49 @@
# Complete - m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke
## 완료 일시
2026-07-05T22:54:55Z
## 요약
Model Group Long-Context Admission S09 capacity smoke evidence loop completed after 3 review rounds; final verdict PASS.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G08_0.log` | `code_review_cloud_G08_0.log` | FAIL | smoke exit contract and evidence capture needed follow-up before live S09 evidence could be trusted. |
| `plan_cloud_G08_1.log` | `code_review_cloud_G08_1.log` | FAIL | live dev provider pool preflight/scenario evidence was still missing because runner token/env, `go`, and source sync blockers stopped request dispatch. |
| `plan_cloud_G08_2.log` | `code_review_cloud_G08_2.log` | PASS | prebuilt Edge binary config-check path, runner temp-copy docs, local mock regression, live preflight, three scenarios, final recovery snapshots, and Edge dispatch log evidence verified. |
## 구현/정리 내용
- Added long-context admission dev smoke script with preflight, `normal-10`, `mixed`, and `all-long-slot-full` scenarios.
- Added `IOP_LONG_SMOKE_EDGE_BIN`/`IOP_LONG_SMOKE_REPO` runner override support so config check works when the runner has no `go` toolchain.
- Documented the long-context dev smoke profile and linked it from the dev edge smoke profile.
- Collected live dev provider-pool evidence for normal capacity, mixed long/normal dispatch, all-long slot-full queueing, and final counter recovery.
- Review-side cleanup masked a prior loop dev token literal in `code_review_cloud_G08_0.log`.
## 최종 검증
- `bash -n scripts/e2e-long-context-admission-smoke.sh` - PASS; no syntax output.
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml` - PASS; `OK configs/edge.yaml`.
- `rg --sort path -n "IOP_LONG_SMOKE_(REPO|EDGE_BIN)|long-context-admission" agent-test/dev/long-context-admission-smoke.md` - PASS; override and profile documentation present.
- deterministic local mock regression - PASS; bad config, missing Edge binary, and HTTP 500 paths failed non-zero; Edge binary override preflight passed and recorded the command.
- runner evidence spot-check - PASS; local/runner script sha256 `ae74ae4f00dee4ed4cdbe51f6b29cc190c6f38368bd80d1104526c5bb90bb649` matched, evidence files existed, all request code files were `200`, final snapshots reported `in_flight=0` and `queued=0`, and Edge log window had `context_class=long` 10 times plus `long_context_capacity_full` 2 times.
- secret scan for current task artifacts/script/dev docs - PASS; no prior dev token literal or bearer-token value remained after masking.
## Roadmap Completion
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Completed task ids:
- `capacity-smoke`: PASS; evidence=`plan_cloud_G08_2.log`, `code_review_cloud_G08_2.log`; verification=`/tmp/iop-long-admission-smoke/preflight_20260705T221507Z.txt`, `normal-10_peaks_20260705T221603Z.txt`, `normal-10_final_20260705T221603Z.json`, `mixed_peaks_20260705T221633Z.txt`, `mixed_final_20260705T221633Z.json`, `all-long-slot-full_peaks_20260705T222235Z.txt`, `all-long-slot-full_final_20260705T222235Z.json`, Edge log `/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/logs/edge.log` lines `212423-212501`.
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,252 @@
<!-- task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke plan=1 tag=REVIEW_CAPACITY_SMOKE -->
# Plan - REVIEW_CAPACITY_SMOKE
## 이 파일을 읽는 구현 에이전트에게
이 task는 이전 CAPACITY_SMOKE 리뷰의 FAIL 후속이다. `scripts/e2e-long-context-admission-smoke.sh`의 smoke 판정이 실제 실패를 non-zero로 전파하도록 고치고, 고친 스크립트로 dev provider pool evidence를 다시 수집한다. 구현 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 stdout/stderr와 evidence 경로를 채운 뒤 active 파일을 남긴 채 review ready로 멈춘다. finalization, log rename, complete.log, archive move는 code-review 전용이다.
Milestone lock 결정이 아니라 환경/secret/service 준비, smoke 실패, evidence 공백은 사용자 리뷰 요청이 아니다. 구현 중 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 말고, 필요한 blocker는 review stub의 검증 결과에 정확한 명령과 출력으로 기록한다.
## 배경
이전 구현은 long-context admission dev smoke 스크립트와 문서를 추가했지만, 스크립트가 HTTP 실패, config check 실패, provider identity 누락, peak/queue 미달을 exit status에 반영하지 않는다. SDD S09의 완료 evidence는 dev smoke script/output과 final recovery snapshot이므로, smoke command 자체가 판정 기준을 강제하고 그 실제 출력이 review stub에 남아야 한다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 selected Milestone `구현 잠금 > 결정 필요` 항목만 active review stub에 기록한다. 이번 follow-up의 예상 blocker는 script/test/evidence 문제이며 사용자 리뷰 요청이 아니다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
- `capacity-smoke`: dev 환경 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오와 최종 회복 근거
- Completion mode: check-on-pass
## Archive Evidence Snapshot
- Current archived plan: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_0.log`
- Current archived review: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log`
- Verdict: FAIL
- Required summary:
- `scripts/e2e-long-context-admission-smoke.sh` logs HTTP success counts but does not fail scenarios when counts are short.
- preflight does not fail when `edge config check` fails or required provider identities are missing.
- scenario peak/queue criteria from `agent-test/dev/long-context-admission-smoke.md` are recorded but not asserted.
- post-fix live evidence in the review log is summarized, not preserved as actual stdout/stderr or exact snapshot/log paths.
- Affected files:
- `scripts/e2e-long-context-admission-smoke.sh`
- `agent-test/dev/long-context-admission-smoke.md`
- `agent-test/dev/edge-smoke.md`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
- Verification evidence from review:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`: PASS.
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`: PASS.
- Local mock reproduced the exit bug: `all-long-slot-full` returned `rc=0` with `http_200=0/6`, `peak_in_flight=0`, `peak_queued=0`.
- Local mock reproduced preflight bug: missing config returned `preflight_bad_config_rc=0`.
- Allowed narrow reread:
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_0.log`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_0.log`
## 분석 결과
### 읽은 파일
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- `agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/edge-smoke.md`
- `agent-test/dev/node-smoke.md`
- `agent-test/dev/inventory.yaml`
- `agent-test/dev/long-context-admission-smoke.md`
- `scripts/e2e-long-context-admission-smoke.sh`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/PLAN-cloud-G08.md`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
### SDD 기준
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`
- 대상 Acceptance: S09 / Milestone Task `capacity-smoke`
- Evidence Map: dev smoke script/output and final recovery snapshot
- 적용: smoke script는 normal 10-way, mixed, all-long-slot-full의 HTTP success, peak/queue, recovery criteria를 command exit status로 강제해야 한다. review evidence는 실제 stdout/stderr 또는 exact output file/status snapshot/Edge log path를 남겨야 한다.
### 테스트 환경 규칙
- test_env: local 기본 규칙을 읽었고, dev provider pool live evidence 때문에 `agent-test/dev/rules.md`, `agent-test/dev/edge-smoke.md`, `agent-test/dev/node-smoke.md`, `agent-test/dev/inventory.yaml`, `agent-test/dev/long-context-admission-smoke.md`를 적용한다.
- 로컬 quick check:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`
- dev live smoke:
- runner: `ssh toki@toki-labs.com`
- repo root: `/Users/toki/agent-work/iop-dev`
- Edge config: `build/dev-runtime/edge.yaml`
- Edge base URL: runner-local `http://127.0.0.1:18083/v1`
- Control Plane status: `http://127.0.0.1:18001/edges/edge-toki-labs-dev/status`
- providers: `gx10-vllm`, `onexplayer-lemonade`, `mac-mlx-vllm`
- expected normal capacity total: `9`
- expected long slot total: `4`
- token/secret 원문은 명령 출력과 tracked 파일에 기록하지 않는다.
### 테스트 커버리지 공백
- 기존 unit tests는 shell smoke script의 exit contract를 검증하지 않는다.
- 이 follow-up은 deterministic local mock regression으로 bad config, HTTP failure, zero peak/queue가 non-zero로 실패하는지 검증한다.
- live provider pool behavior는 unit test로 대체하지 않고 dev smoke output으로 검증한다.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- 기존 split task `06+01,02,03,04,05_capacity_smoke` 안의 review follow-up이다.
- 새 subtask로 분리하지 않는다. script exit contract와 evidence rerun은 같은 smoke completion evidence를 닫는 단일 보정 흐름이며, 따로 PASS시킬 중간 Milestone Task가 없다.
### 범위 결정 근거
- Edge admission algorithm, scheduler, Control Plane status view 확장은 범위 제외다. 이 follow-up은 smoke script와 evidence capture만 고친다.
- 배포 dev config의 `long_context_threshold_tokens=30000` 정책 영속화와 SDD 갱신은 범위 제외다.
- 기존 tracked `configs/edge.yaml` 기본값을 dev 검증용 임시값으로 바꾸지 않는다.
### 빌드 등급
- `cloud-G08`: shell/CLI workflow, exit-status contract, live dev provider pool evidence, long-running external runtime smoke가 중심이다.
## 구현 체크리스트
- [ ] `scripts/e2e-long-context-admission-smoke.sh`가 preflight config/provider/capacity 실패와 request HTTP 실패를 non-zero exit으로 전파하도록 수정한다.
- [ ] `scripts/e2e-long-context-admission-smoke.sh`가 `normal-10`, `mixed`, `all-long-slot-full`의 documented peak/queue/recovery criteria를 자동 assert하도록 수정한다.
- [ ] deterministic local mock regression으로 bad config, HTTP failure, zero peak/queue가 실패로 판정되는지 검증한다.
- [ ] live dev provider pool에서 preflight와 세 scenario를 rerun하고 actual stdout/stderr 또는 exact output file/status snapshot/Edge log evidence를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## [REVIEW_CAPACITY_SMOKE-1] Smoke script exit contract
### 문제
- `scripts/e2e-long-context-admission-smoke.sh:286` counts HTTP 200 responses but does not return failure when the count is short.
- `scripts/e2e-long-context-admission-smoke.sh:315` prints `config check FAILED` but leaves `ok=1`.
- `scripts/e2e-long-context-admission-smoke.sh:340` logs missing providers as `WARN` and still allows `PREFLIGHT OK`.
- `scripts/e2e-long-context-admission-smoke.sh:373` through `scripts/e2e-long-context-admission-smoke.sh:406` do not enforce the documented peak/queue criteria.
### 해결 방법
- Make request summary helpers return success counts and fail on short counts.
- Capture `edge config check` status and set preflight `ok=0` on failure.
- In preflight, require each expected provider id to exist, require healthy status, and require summed capacity to match `NORMAL_CAPACITY_TOTAL`.
- Make `poll_peaks` output parseable key/value fields and add scenario assertions:
- `normal-10`: HTTP 200 `10/10`, peak `in_flight >= 9`, peak `queued >= 1`, final recovery 0/0.
- `mixed`: long HTTP 200 `4/4`, normal HTTP 200 `6/6`, final recovery 0/0. If long fields are visible, require peak `long_in_flight >= 4`; if not visible, leave Edge log evidence capture as review evidence.
- `all-long-slot-full`: long HTTP 200 `6/6`, peak `in_flight <= 4` for the long-only burst, peak `queued >= 1`, final recovery 0/0. If long queue fields are visible, require `peak_long_queued >= 1`; otherwise require Edge log evidence in the review output.
### 수정 파일 및 체크리스트
- [ ] `scripts/e2e-long-context-admission-smoke.sh`
- [ ] `agent-test/dev/long-context-admission-smoke.md` only if command/criteria wording changes.
### 테스트 작성
- 새 repo test file은 만들지 않는다. 이 task의 regression은 `/tmp` mock server를 쓰는 deterministic command로 검증한다.
- The mock must prove that a bad config and HTTP 500/zero peak no longer exit 0.
### 중간 검증
```bash
bash -n scripts/e2e-long-context-admission-smoke.sh
go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
```
## [REVIEW_CAPACITY_SMOKE-2] Trusted S09 evidence rerun
### 문제
- `code_review_cloud_G08_0.log` records live post-fix smoke as summaries. SDD S09 requires script/output and final recovery snapshot evidence, and the plan required stdout/stderr plus snapshot paths.
### 해결 방법
- After REVIEW_CAPACITY_SMOKE-1, rerun preflight and all three scenarios against the dev runner.
- Save full command stdout/stderr to out-dir or paste concise complete output. If output is too long, record exact files generated by the command.
- Record exact status snapshot paths and Edge log evidence path/snippets for long classification and `queue_reason="long_context_capacity_full"`.
### 수정 파일 및 체크리스트
- [ ] `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
### 테스트 작성
- No additional test file. The live smoke output is the required S09 evidence.
### 중간 검증
```bash
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git rev-parse HEAD && git status --short'
```
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `scripts/e2e-long-context-admission-smoke.sh` | REVIEW_CAPACITY_SMOKE-1 |
| `agent-test/dev/long-context-admission-smoke.md` | REVIEW_CAPACITY_SMOKE-1 |
| `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md` | REVIEW_CAPACITY_SMOKE-2 |
## 최종 검증
```bash
git rev-parse HEAD
git status --short
go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
bash -n scripts/e2e-long-context-admission-smoke.sh
```
Run this deterministic local mock regression from `/tmp` and record the output. The two expected results are: bad config preflight exits non-zero, and all-long-slot-full with HTTP 500 plus zero peaks exits non-zero.
```bash
tmpdir=$(mktemp -d); port=18997; PORT=$port python3 - <<'PY' &
from http.server import BaseHTTPRequestHandler, HTTPServer
import json, os
class H(BaseHTTPRequestHandler):
def log_message(self, fmt, *args): pass
def do_GET(self):
if self.path == "/v1/models":
body = {"data":[{"id":"qwen3.6:35b"}]}
elif self.path == "/status":
body = {"provider_snapshots":[
{"id":"gx10-vllm","capacity":4,"in_flight":0,"queued":0,"health":"healthy"},
{"id":"onexplayer-lemonade","capacity":3,"in_flight":0,"queued":0,"health":"healthy"},
{"id":"mac-mlx-vllm","capacity":2,"in_flight":0,"queued":0,"health":"healthy"}]}
else:
self.send_response(404); self.end_headers(); return
data = json.dumps(body).encode()
self.send_response(200); self.send_header("Content-Type","application/json"); self.send_header("Content-Length",str(len(data))); self.end_headers(); self.wfile.write(data)
def do_POST(self):
self.rfile.read(int(self.headers.get("content-length","0") or 0))
self.send_response(500); self.end_headers(); self.wfile.write(b"fail")
HTTPServer(("127.0.0.1", int(os.environ["PORT"])), H).serve_forever()
PY
pid=$!; trap 'kill $pid 2>/dev/null || true; rm -rf "$tmpdir"' EXIT; sleep 1
if IOP_LONG_SMOKE_BASE_URL="http://127.0.0.1:$port/v1" IOP_LONG_SMOKE_STATUS_URL="http://127.0.0.1:$port/status" bash scripts/e2e-long-context-admission-smoke.sh --preflight --config "$tmpdir/missing.yaml" --out-dir "$tmpdir/out-preflight"; then echo "bad config unexpectedly passed"; exit 1; else echo "bad config failed as expected"; fi
if IOP_LONG_SMOKE_BASE_URL="http://127.0.0.1:$port/v1" IOP_LONG_SMOKE_STATUS_URL="http://127.0.0.1:$port/status" IOP_LONG_SMOKE_LONG_CHARS=100 IOP_LONG_SMOKE_POLL_SECS=2 bash scripts/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --out-dir "$tmpdir/out-scenario"; then echo "http failure unexpectedly passed"; exit 1; else echo "http failure failed as expected"; fi
```
Then rerun live dev smoke on the remote runner without printing token values:
```bash
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash scripts/e2e-long-context-admission-smoke.sh --preflight --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash scripts/e2e-long-context-admission-smoke.sh --scenario mixed --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash scripts/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
```
Expected: all local checks pass; mock failure cases return non-zero; live preflight and three scenarios return 0; review stub includes actual stdout/stderr or exact saved output paths, final status snapshots, and Edge log evidence for long classification/queue reason.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,255 @@
<!-- task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke plan=2 tag=REVIEW_REVIEW_CAPACITY_SMOKE -->
# Plan - REVIEW_REVIEW_CAPACITY_SMOKE
## 이 파일을 읽는 구현 에이전트에게
이 task는 이전 `REVIEW_CAPACITY_SMOKE` 리뷰의 FAIL 후속이다. live dev provider pool S09 evidence가 아직 없으므로, runner 검증 blocker를 줄이고 실제 preflight + 세 scenario stdout/stderr, final status snapshot, Edge dispatch log evidence를 수집한다.
구현 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 명령 출력과 evidence 경로를 채운 뒤 active 파일을 남긴 채 review ready로 멈춘다. finalization, log rename, `complete.log`, archive move는 code-review 전용이다.
Milestone lock 결정이 아니라 환경/secret/service 준비, smoke 실패, evidence 공백은 사용자 리뷰 요청이 아니다. 구현 중 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 말고, 필요한 blocker는 review stub의 `검증 결과`에 정확한 명령과 출력으로 기록한다.
## 배경
이전 루프는 smoke script의 local exit contract를 고쳤지만, SDD S09의 필수 live evidence는 수집하지 못했다. 기록된 blocker는 runner token 미설정, runner `go` 부재, local untracked script 미배포다. 이번 follow-up은 source bug 탐색이 아니라 evidence 신뢰 회복과 runner 실행 경로 보정에 집중한다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 selected Milestone `구현 잠금 > 결정 필요` 항목만 active review stub에 기록한다. 이번 follow-up의 예상 blocker는 remote runner/secret/source sync/evidence 문제이며 사용자 리뷰 요청이 아니다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
- `capacity-smoke`: dev 환경 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오와 최종 회복 근거
- Completion mode: check-on-pass
## Archive Evidence Snapshot
- Current archived plan: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_1.log`
- Current archived review: `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log`
- Verdict: FAIL
- Required summary:
- live dev provider pool preflight and `normal-10`, `mixed`, `all-long-slot-full` rerun evidence is missing.
- archived review lines `70`, `77`, and `241` show `Trusted S09 evidence rerun` unchecked and scenario execution blocked before request dispatch.
- blockers recorded: remote `IOP_LONG_SMOKE_TOKEN` unset, runner `go` absent, and local untracked smoke script not present in the clean runner checkout.
- Affected files:
- `scripts/e2e-long-context-admission-smoke.sh`
- `agent-test/dev/long-context-admission-smoke.md`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
- Verification evidence from review:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`: PASS.
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`: PASS.
- deterministic local mock regression: bad config preflight returned non-zero and HTTP 500 + zero peak returned non-zero.
- live partial status snapshot: provider identities healthy and total normal capacity `9`, but live request scenarios were not run.
- Allowed narrow reread:
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_1.log`
## 분석 결과
### 읽은 파일
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- `agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/dev/rules.md`
- `agent-test/dev/edge-smoke.md`
- `agent-test/dev/node-smoke.md`
- `agent-test/dev/inventory.yaml`
- `agent-test/dev/long-context-admission-smoke.md`
- `scripts/e2e-long-context-admission-smoke.sh`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/plan_cloud_G08_1.log`
- `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/code_review_cloud_G08_1.log`
### SDD 기준
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/model-group-long-context-admission/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`
- 대상 Acceptance: S09 / Milestone Task `capacity-smoke`
- Evidence Map: dev smoke script/output and final recovery snapshot
- 적용: PASS는 live dev provider pool에서 preflight, normal 10-way, mixed long/normal, all-long-slot-full smoke의 actual stdout/stderr 또는 저장 파일 경로, final recovery snapshot, long queue dispatch log evidence가 있어야 한다.
### 테스트 환경 규칙
- test_env: local quick check + dev runtime evidence.
- 읽은 규칙: `agent-test/local/rules.md`, `agent-test/dev/rules.md`.
- 읽은 dev profiles: `agent-test/dev/edge-smoke.md`, `agent-test/dev/node-smoke.md`, `agent-test/dev/inventory.yaml`, `agent-test/dev/long-context-admission-smoke.md`.
- local quick check:
- `bash -n scripts/e2e-long-context-admission-smoke.sh`
- `go run ./apps/edge/cmd/edge config check --config configs/edge.yaml`
- deterministic local mock regression.
- 테스트 환경 프리플라이트:
- runner: `ssh toki@toki-labs.com`
- repo root: `/Users/toki/agent-work/iop-dev`
- source sync: clean runner checkout may not contain local untracked script; use `scp`/temporary copy for evidence or sync source before live smoke.
- Edge binary candidate: `/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge`
- Edge config: `/Users/toki/agent-work/iop-dev/build/dev-runtime/edge.yaml`
- Edge base URL: runner-local `http://127.0.0.1:18083/v1`
- Control Plane status: `http://127.0.0.1:18001/edges/edge-toki-labs-dev/status`
- providers: `gx10-vllm`, `onexplayer-lemonade`, `mac-mlx-vllm`
- expected normal capacity total: `9`
- expected long slot total: `4`
- required setup/blocker checks: token presence without printing value, executable Edge binary or `go`, current script on runner, endpoint reachability, provider health.
### 테스트 커버리지 공백
- shell script exit contract is covered only by deterministic local mock commands, not a checked-in unit test.
- live provider pool behavior cannot be replaced by unit tests; SDD S09 requires live smoke output and recovery snapshots.
- runner `go` absence is not a product decision; make config check runnable through a prebuilt Edge binary path.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- 기존 split task `06+01,02,03,04,05_capacity_smoke`의 review follow-up이다.
- 새 subtask로 분리하지 않는다. 남은 Required issue가 하나의 evidence recovery path이며, script config-check runner 보정과 live smoke rerun은 같은 S09 completion evidence를 닫는 단일 단위다.
### 범위 결정 근거
- Edge admission algorithm, scheduler, Control Plane status view 확장은 범위 제외다.
- dev runtime config 정책 영속화와 SDD 갱신은 범위 제외다.
- token 원문을 tracked 파일이나 review output에 남기지 않는다.
- live evidence를 만들기 위한 runner temp copy는 허용하지만 repo-local 임시 verification tool은 만들지 않는다.
### 빌드 등급
- `cloud-G08`: shell/CLI workflow, remote runner preflight, secret-safe live provider pool smoke, long-running external runtime evidence가 중심이다.
## 구현 체크리스트
- [ ] `scripts/e2e-long-context-admission-smoke.sh`가 `go run`이 없는 runner에서도 prebuilt Edge binary로 `config check`를 실행할 수 있게 보강한다.
- [ ] `agent-test/dev/long-context-admission-smoke.md`에 prebuilt Edge binary override와 runner temp-copy evidence 흐름을 문서화한다.
- [ ] local quick check와 deterministic local mock regression을 다시 실행해 script exit contract가 유지되는지 검증한다.
- [ ] runner preflight를 수행하고 token presence, source/script availability, Edge binary/config check, endpoint reachability, provider health/capacity를 실제 stdout/stderr로 기록한다.
- [ ] live dev provider pool에서 preflight와 `normal-10`, `mixed`, `all-long-slot-full`을 실행하고 actual stdout/stderr 또는 exact output file/status snapshot/Edge log evidence를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## [REVIEW_REVIEW_CAPACITY_SMOKE-1] Runner config-check path
### 문제
- `scripts/e2e-long-context-admission-smoke.sh:335` always runs `go run ./apps/edge/cmd/edge config check`, but `code_review_cloud_G08_1.log:101` through `:104` records that the dev runner has no `go`.
- Without an alternate config-check path, live preflight cannot pass on the documented runner even when the built Edge binary exists.
### 해결 방법
- Add an environment override such as `IOP_LONG_SMOKE_EDGE_BIN`.
- If it is set, run `"$IOP_LONG_SMOKE_EDGE_BIN" config check --config "$CONFIG"` from `REPO_ROOT`.
- Otherwise keep the current `go run ./apps/edge/cmd/edge config check --config "$CONFIG"` behavior.
- Record the exact command in preflight output without printing any token.
### 수정 파일 및 체크리스트
- [ ] `scripts/e2e-long-context-admission-smoke.sh`
### 테스트 작성
- 새 repo test file은 만들지 않는다. This is a shell smoke path; regression is verified with syntax check, local config check, and mock smoke commands.
### 중간 검증
```bash
bash -n scripts/e2e-long-context-admission-smoke.sh
go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
```
## [REVIEW_REVIEW_CAPACITY_SMOKE-2] Dev smoke doc update
### 문제
- `agent-test/dev/long-context-admission-smoke.md` documents the smoke commands but not the runner path needed when the script is copied to `/tmp` and config check uses a prebuilt Edge binary.
### 해결 방법
- Document `IOP_LONG_SMOKE_REPO` and the new Edge binary override.
- Document that local uncommitted/untracked script changes must be copied to the runner temp path or otherwise synced before using the output as current-change evidence.
### 수정 파일 및 체크리스트
- [ ] `agent-test/dev/long-context-admission-smoke.md`
### 테스트 작성
- 문서 변경이므로 별도 테스트 파일은 만들지 않는다.
### 중간 검증
```bash
rg --sort path -n "IOP_LONG_SMOKE_(REPO|EDGE_BIN)|long-context-admission" agent-test/dev/long-context-admission-smoke.md
```
## [REVIEW_REVIEW_CAPACITY_SMOKE-3] Trusted S09 live evidence rerun
### 문제
- `code_review_cloud_G08_1.log:70` and `:77` leave the S09 live evidence items unchecked.
- `code_review_cloud_G08_1.log:241` through `:279` records blocker output instead of live scenario execution output.
### 해결 방법
- Copy the current smoke script to the runner temp path or otherwise sync it to `/Users/toki/agent-work/iop-dev`.
- Verify token presence without printing the value.
- Run preflight with runner-local base URL and status URL, using `IOP_LONG_SMOKE_REPO` and the Edge binary override when `go` is absent.
- Run `normal-10`, `mixed`, and `all-long-slot-full`.
- Record actual stdout/stderr or exact saved output paths. Include final status snapshot paths and Edge dispatch log path/snippets showing `context_class` and `queue_reason="long_context_capacity_full"` when long fields are absent from Control Plane status.
### 수정 파일 및 체크리스트
- [ ] `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md`
### 테스트 작성
- No additional test file. The live smoke output is the required S09 evidence.
### 중간 검증
```bash
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git rev-parse HEAD && git status --short && test -x build/dev-runtime/bin/edge && build/dev-runtime/bin/edge config check --config build/dev-runtime/edge.yaml && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && curl -fsS --connect-timeout 8 -o /dev/null -w "models_http=%{http_code}\n" -H "Authorization: Bearer ${IOP_LONG_SMOKE_TOKEN}" http://127.0.0.1:18083/v1/models'
```
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `scripts/e2e-long-context-admission-smoke.sh` | REVIEW_REVIEW_CAPACITY_SMOKE-1 |
| `agent-test/dev/long-context-admission-smoke.md` | REVIEW_REVIEW_CAPACITY_SMOKE-2 |
| `agent-task/m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke/CODE_REVIEW-cloud-G08.md` | REVIEW_REVIEW_CAPACITY_SMOKE-3 |
## 최종 검증
```bash
git rev-parse HEAD
git status --short
go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
bash -n scripts/e2e-long-context-admission-smoke.sh
```
Run the deterministic local mock regression from `plan_cloud_G08_1.log` again and record the actual output. Expected: bad config preflight exits non-zero, and all-long-slot-full with HTTP 500 plus zero peaks exits non-zero.
Then run the live dev evidence commands without printing token values:
```bash
ssh toki@toki-labs.com 'mkdir -p /tmp/iop-long-admission-smoke-runner'
scp scripts/e2e-long-context-admission-smoke.sh toki@toki-labs.com:/tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh
ssh toki@toki-labs.com 'chmod +x /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --preflight --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --scenario normal-10 --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --scenario mixed --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_LONG_SMOKE_TOKEN:-}" && IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 IOP_LONG_SMOKE_LONG_CHARS=384000 bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
```
Expected: all local checks pass; local mock failure cases return non-zero; live preflight and three scenarios return `0`; review stub includes actual stdout/stderr or exact saved output paths, final status snapshots, and Edge log evidence for long classification/queue reason.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -1,107 +0,0 @@
<!-- task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke plan=0 tag=CAPACITY_SMOKE -->
# Code Review Reference - CAPACITY_SMOKE
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is mandatory.** Record preflight, smoke stdout/stderr, and status snapshot paths. External environment blockers are verification blockers, not user-review requests unless a selected Milestone lock decision is unresolved.
## 개요
date=2026-07-05
task=m-model-group-long-context-admission/06+01,02,03,04,05_capacity_smoke, plan=0, tag=CAPACITY_SMOKE
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md`
- Task ids:
- `capacity-smoke`: dev 환경 normal 10-way, mixed long/normal, all-long-slot-full queue 시나리오와 최종 회복 근거
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [CAPACITY_SMOKE-1] Long-context admission dev smoke | [ ] |
## 구현 체크리스트
- [ ] dev smoke profile/script를 추가하거나 기존 dev edge smoke에 long-context admission scenarios를 확장한다.
- [ ] preflight가 source state, config check, endpoint reachability, provider identities, current provider snapshots를 기록하게 한다.
- [ ] normal 10-way scenario를 실행하고 총 `in_flight=9`, `queued>=1` 관측 후 완료 시 `in_flight=0`, `queued=0`, `long_in_flight=0` 회복을 저장한다.
- [ ] mixed long/normal scenario를 실행해 long slot full 중 normal이 head-of-line blocking 없이 완료되는 evidence를 저장한다.
- [ ] all-long-slot-full scenario를 실행해 long requests가 queue 대기 후 slot 회복 시 dispatch되고 최종 counters가 0으로 회복되는 evidence를 저장한다.
- [ ] smoke stdout/stderr와 status snapshot 파일 경로를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
- [ ] 판정을 append한다.
- [ ] active review/plan을 `code_review_cloud_G08_0.log`, `plan_cloud_G08_0.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 archive로 이동한다.
- [ ] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- smoke가 normal 10-way, mixed, all-long-slot-full 세 시나리오를 모두 실행했는지 확인한다.
- 최종 snapshot마다 `in_flight=0`, `queued=0`, `long_in_flight=0` 회복 근거가 있는지 확인한다.
- 외부 환경 실패가 있으면 정확한 blocker와 follow-up 가능성이 기록되어 있는지 확인한다.
## 검증 결과
### CAPACITY_SMOKE-1 중간 검증
```bash
$ bash -n scripts/e2e-long-context-admission-smoke.sh
(output)
$ bash scripts/e2e-long-context-admission-smoke.sh --preflight --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke
(output)
```
### 최종 검증
```bash
$ git rev-parse HEAD
(output)
$ git status --short
(output)
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
(output)
$ bash -n scripts/e2e-long-context-admission-smoke.sh
(output)
$ bash scripts/e2e-long-context-admission-smoke.sh --preflight --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke
(output)
$ bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke
(output)
$ bash scripts/e2e-long-context-admission-smoke.sh --scenario mixed --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke
(output)
$ bash scripts/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke
(output)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Fill every implementation-owned section before saving.**

View file

@ -128,6 +128,7 @@ mac-mlx-vllm provider는 mac-codex-node 소속 resource로, Edge host와 같은
- provider-pool dispatch는 `in_flight >= capacity`인 provider를 후보에서 제외하고, 남은 후보 중 가장 낮은 `in_flight` 레벨을 먼저 선택한다. 같은 `in_flight` 레벨 안에서는 낮은 priority 값과 rotation으로 분산한다.
- dev-runtime capacity smoke는 `/v1/responses``/v1/chat/completions` 각각에 provider capacity 총합 + 1개 동시 요청을 보내고, Control Plane status의 `provider_snapshots`에서 총 `in_flight`가 capacity 총합에 도달하며 `queued`가 1 이상 잡히는지 확인한다. 현재 `gx10-vllm=4`, `onexplayer-lemonade=3`, `mac-mlx-vllm=2`이면 endpoint별 10개 동시 요청에서 총 `in_flight=9`, `queued>=1` 관측을 기준으로 한다. 13개 동시 확장 smoke에서는 총 `in_flight=9`, `queued>=4` 관측을 기대한다.
- capacity smoke 완료 후 대상 provider의 `in_flight=0`, `queued=0` 회복을 확인한다.
- long-context admission 시나리오(normal 10-way, mixed long/normal, all-long-slot-full)와 최종 회복 근거는 `agent-test/dev/long-context-admission-smoke.md``scripts/e2e-long-context-admission-smoke.sh`를 사용한다.
- 2026-06-24 dev 13-way 확장 smoke는 Mac capacity가 `3`이던 이전 관측이다. 해당 run에서는 `/v1/chat/completions` 13개 요청 성공, peak `in_flight=10`, 실제 queue 대기 `3`개, 최종 회복 `in_flight=0`, `queued=0`이 관측되었다. 현재 Mac capacity `2` 기준 13-way 확장 smoke 기대치는 peak `in_flight=9`, queue 대기 `4`개 이상이다.
- Qwen provider-pool smoke는 thinking/reasoning 텍스트가 포함될 수 있다. 추론 출력 자체를 실패로 보지 말고 HTTP 성공, model alias, final marker 포함 여부, provider node log/run count 증가로 판정한다. 응답 전체가 특정 token과 정확히 같은지 비교하는 strict exact-match는 이 profile의 기본 판정으로 쓰지 않는다.
- bootstrap 사용자 명령은 완성된 URL과 positional token 하나만 포함한다.

View file

@ -0,0 +1,115 @@
---
test_env: dev
test_profile: long-context-admission-smoke
domain: edge
verification_type: smoke
last_rule_updated_at: 2026-07-06
---
# long-context-admission dev capacity smoke
## 읽기 조건
- Milestone `model-group-long-context-admission`의 SDD S09(`capacity-smoke`) evidence를 수집할 때
- long-context admission(입력 토큰 추정 기반 long slot 예약/해제, queue-skip, long slot full 회복)을 live dev provider pool로 확인할 때
## 적용 범위
- `scripts/e2e-long-context-admission-smoke.sh`
- Edge OpenAI-compatible 입력 표면(`/v1/chat/completions`) provider-pool dispatch
- Control Plane status `provider_snapshots` 회복 관측
## 분류
- domain: edge
- verification_type: smoke
- scope: dev provider pool long-context admission 시나리오와 최종 회복 근거
## 환경
- runner/workdir: 로컬 checkout 또는 원격 runner `ssh toki@toki-labs.com``/Users/toki/agent-work/iop-dev`.
- provider pool 인벤토리: `agent-test/dev/inventory.yaml` (GX10 vLLM, OneXPlayer Lemonade, mac-mlx-vllm).
- Edge OpenAI-compatible base URL 후보: `http://toki-labs.com:18083/v1` (runner-local `http://127.0.0.1:18083/v1`).
- Control Plane status URL: `http://127.0.0.1:18001/edges/edge-toki-labs-dev/status` (runner-local; 다른 host에서는 `IOP_LONG_SMOKE_STATUS_SSH`로 ssh curl).
- capacity baseline: normal 총합 `9` (`gx10=4`, `onexplayer=3`, `mac=2`).
- long slot baseline: 총합 `4` (`gx10=1`, `onexplayer=2`, `mac=1`).
- credential: bearer token은 `IOP_LONG_SMOKE_TOKEN` 환경 변수로만 주입하고 명령/로그/tracked 파일에 원문을 남기지 않는다.
- repo override: script를 checkout 밖(`/tmp` 등)에서 실행하면 `IOP_LONG_SMOKE_REPO``edge config check` 기준 repo root를 지정한다. 미지정 시 script 위치의 상위 디렉터리를 repo root로 본다.
- Edge binary override: runner에 `go` toolchain이 없으면 `IOP_LONG_SMOKE_EDGE_BIN`(예: `/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge`)으로 prebuilt Edge binary를 지정해 `config check`를 수행한다. 미지정 시 `go run ./apps/edge/cmd/edge`를 사용한다.
## 명령
- syntax: `bash -n scripts/e2e-long-context-admission-smoke.sh`
- preflight: `bash scripts/e2e-long-context-admission-smoke.sh --preflight --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
- normal-10: `bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
- mixed: `bash scripts/e2e-long-context-admission-smoke.sh --scenario mixed --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
- all-long-slot-full: `bash scripts/e2e-long-context-admission-smoke.sh --scenario all-long-slot-full --config configs/edge.yaml --out-dir /tmp/iop-long-admission-smoke`
원격 runner에서 status가 runner-local일 때 예시:
```bash
IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 \
bash scripts/e2e-long-context-admission-smoke.sh --scenario normal-10 --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke
```
## Runner temp-copy evidence 흐름
- local uncommitted/untracked script 변경은 clean runner checkout에 존재하지 않는다. 현재 변경분을 evidence로 쓰려면 실행 전 script를 runner temp 경로로 복사하거나 source를 먼저 sync한다. sync/복사 없이 runner checkout의 옛 script 출력을 현재 변경 evidence로 쓰지 않는다.
- temp-copy 예시 (runner에 `go`가 없으므로 prebuilt Edge binary override 사용):
```bash
ssh toki@toki-labs.com 'mkdir -p /tmp/iop-long-admission-smoke-runner'
scp scripts/e2e-long-context-admission-smoke.sh toki@toki-labs.com:/tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && \
IOP_LONG_SMOKE_REPO=/Users/toki/agent-work/iop-dev \
IOP_LONG_SMOKE_EDGE_BIN=/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge \
IOP_LONG_SMOKE_BASE_URL=http://127.0.0.1:18083/v1 \
bash /tmp/iop-long-admission-smoke-runner/e2e-long-context-admission-smoke.sh --preflight --config build/dev-runtime/edge.yaml --out-dir /tmp/iop-long-admission-smoke'
```
- preflight 출력의 `## source state`(runner HEAD/dirty)와 `## config check` 실제 명령 줄로 어느 source/binary 기준 evidence인지 함께 기록한다.
## 시나리오
- `normal-10`: `/v1/chat/completions`에 10개 동시 요청(capacity 총합 `9` + 1). peak `in_flight=9`, `queued>=1` 관측 후 완료 시 `in_flight=0`, `queued=0`, `long_in_flight=0` 회복.
- `mixed`: long slot 총합(`4`)만큼 long 요청으로 long slot을 채우고 그 뒤에 normal 요청 6개를 보낸다. long slot full 중에도 normal 요청이 head-of-line blocking 없이 dispatch/완료되고, 최종 counters가 0으로 회복.
- `all-long-slot-full`: long slot 총합보다 많은 long 요청(`slot_total + 2`)을 보내 일부가 queue 대기한 뒤 long slot 회복 시 dispatch되고, 최종 counters가 0으로 회복.
## 필수 검증
- preflight가 source state(`git rev-parse HEAD`, `git status --short`), config check(`edge config check`), base URL `/models` reachability, Control Plane status reachability, provider identity(`gx10-vllm`, `onexplayer-lemonade`, `mac-mlx-vllm`), 초기 provider snapshot을 기록한다.
- 세 시나리오 각각에서 요청 발사 중 status를 poll해 peak `in_flight`/`queued`를 저장하고, 완료 후 최종 snapshot에서 `in_flight=0`, `queued=0` 회복을 확인한다.
- `mixed`에서 normal 요청 6개의 HTTP 성공으로 head-of-line blocking 부재를 확인한다.
## 판정 기준
- `normal-10`: peak `in_flight`가 capacity 총합(`9`)에 도달하고 `queued>=1`, 완료 후 `in_flight=0`, `queued=0` 회복.
- `mixed`: long slot이 찬 동안 normal 요청이 완료되고(HTTP 200), 최종 `in_flight=0`, `queued=0`, `long_in_flight=0` 회복.
- `all-long-slot-full`: long 요청 일부가 queue 대기(`long_queued>=1` 또는 dispatch log queue_reason=`long_context_capacity_full`) 후 slot 회복 시 dispatch되고 최종 counters 0 회복.
- Qwen provider-pool 응답은 thinking/reasoning 텍스트를 포함할 수 있다. HTTP 성공과 counter 회복으로 판정하고 strict exact-match는 쓰지 않는다.
## Control Plane status view 관측 한계
- 현재 Control Plane HTTP status view(`apps/control-plane/cmd/control-plane/http_views.go`의 `providerSnapshotView`)는 `in_flight`, `queued`, `capacity`, `health`만 노출하고 `long_in_flight`, `long_queued`, `long_context_capacity`는 제외한다. proto `ProviderSnapshot`과 Edge relay에는 값이 존재한다.
- 따라서 `long_in_flight`/`long_queued` 회복은 status view만으로 직접 관측되지 않을 수 있다. 이때는 Edge dispatch log의 `context_class`, `queue_reason`(`long_context_capacity_full`)로 long-slot 예약/대기/회복을 재구성한다.
- script는 status JSON에 long 필드가 있으면 자동 사용하고, 없으면 `n/a`로 표시하며 log 기반 확인을 안내한다. Control Plane status view에 long 필드를 노출하는 것은 별도 follow-up 후보다.
## 차단 기준
- dev host, Edge OpenAI-compatible endpoint, Control Plane status URL 접근이 불가능하다.
- provider pool node 중 하나 이상이 미연결/unhealthy다.
- 위 실패는 검증 blocker이며 사용자 리뷰 요청이 아니다. 실패한 정확한 명령을 보고 항목에 남긴다.
## 보고 항목
- 실행한 명령:
- 성공한 검증:
- 실패/차단된 검증(정확한 명령 포함):
- 생략 사유:
- 남은 위험:
## 금지 사항
- secret, token, 개인 endpoint 원문은 tracked 파일에 기록하지 않는다.
- 대용량 long prompt fixture를 repo에 커밋하지 않는다. synthetic prompt는 `--out-dir`(`/tmp` 등)에만 생성한다.
- 기본 `configs/*.yaml`을 검증용 임시값으로 오염시키지 않는다.

View file

@ -0,0 +1,523 @@
#!/usr/bin/env bash
set -euo pipefail
# Long-context admission dev capacity smoke.
#
# Milestone: model-group-long-context-admission, SDD S09 (capacity-smoke).
# Verifies, against the live dev provider pool (GX10 vLLM, OneXPlayer Lemonade,
# mac-mlx-vllm), that:
# normal-10 - normal capacity+1 concurrency reaches total in_flight and
# queues at least one request, then recovers to zero.
# mixed - long requests hold long slots while normal requests still
# dispatch without head-of-line blocking, then recover.
# all-long-slot-full - more long requests than total long slots queue, then
# dispatch as long slots free up, then recover to zero.
#
# This is a live-dependency auxiliary smoke. It does not stand up any process; it
# drives an already-running dev Edge OpenAI-compatible endpoint and reads the
# Control Plane status snapshot. Every reachability failure is recorded as an
# exact failed command (a verification blocker), never turned into a user-review
# request.
#
# Secrets: the bearer token is read from IOP_LONG_SMOKE_TOKEN only and is never
# printed to stdout/stderr or written to the out-dir.
#
# Options:
# --preflight run source/config/endpoint/identity/snapshot checks only
# --scenario <name> normal-10 | mixed | all-long-slot-full
# --config <path> edge config for `edge config check` (default configs/edge.yaml)
# --out-dir <dir> evidence output dir (default /tmp/iop-long-admission-smoke)
# -h | --help usage
#
# Environment (defaults target the dev-runtime inventory; override per host):
# IOP_LONG_SMOKE_BASE_URL Edge OpenAI-compatible base URL (default http://toki-labs.com:18083/v1)
# IOP_LONG_SMOKE_STATUS_URL Control Plane status URL (default runner-local edge status)
# IOP_LONG_SMOKE_STATUS_SSH optional ssh target used to curl STATUS_URL from a host
# that cannot reach it directly (e.g. toki@toki-labs.com)
# IOP_LONG_SMOKE_TOKEN optional bearer token for the OpenAI-compatible endpoint
# IOP_LONG_SMOKE_MODEL model alias (default qwen3.6:35b)
# IOP_LONG_SMOKE_LONG_CHARS synthetic long-prompt size in chars (default 720000 ~ 200k tokens)
# IOP_LONG_SMOKE_MAX_TOKENS completion max_tokens per request (default 64)
# IOP_LONG_SMOKE_POLL_SECS hard ceiling seconds for the status poll loop; the
# loop otherwise runs until requests finish (default 3600)
# IOP_LONG_SMOKE_EDGE_BIN optional prebuilt edge binary used for `config check`
# instead of `go run` (for runners without a go toolchain)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# REPO_ROOT is where `edge config check` runs. Defaults to the script's parent
# repo, but can be overridden (e.g. when the script is copied to /tmp on a runner
# whose checkout lives elsewhere).
REPO_ROOT="${IOP_LONG_SMOKE_REPO:-$(cd "$SCRIPT_DIR/.." && pwd)}"
BASE_URL="${IOP_LONG_SMOKE_BASE_URL:-http://toki-labs.com:18083/v1}"
STATUS_URL="${IOP_LONG_SMOKE_STATUS_URL:-http://127.0.0.1:18001/edges/edge-toki-labs-dev/status}"
STATUS_SSH="${IOP_LONG_SMOKE_STATUS_SSH:-}"
MODEL="${IOP_LONG_SMOKE_MODEL:-qwen3.6:35b}"
LONG_CHARS="${IOP_LONG_SMOKE_LONG_CHARS:-720000}"
MAX_TOKENS="${IOP_LONG_SMOKE_MAX_TOKENS:-64}"
POLL_SECS="${IOP_LONG_SMOKE_POLL_SECS:-3600}"
EDGE_BIN="${IOP_LONG_SMOKE_EDGE_BIN:-}"
CONFIG="configs/edge.yaml"
OUT_DIR="/tmp/iop-long-admission-smoke"
SCENARIO=""
DO_PREFLIGHT=0
# dev pool baseline (agent-test/dev/inventory.yaml).
PROVIDER_IDS=(gx10-vllm onexplayer-lemonade mac-mlx-vllm)
NORMAL_CAPACITY_TOTAL=9 # gx10=4 + onexplayer=3 + mac=2
LONG_SLOT_TOTAL=4 # gx10=1 + onexplayer=2 + mac=1
usage() {
sed -n '3,54p' "$0"
}
log() { echo "[long-admission-smoke] $*"; }
die() {
log "ERROR: $*"
exit 1
}
# ── argument parsing ─────────────────────────────────────────────────────────
while [ $# -gt 0 ]; do
case "$1" in
--preflight) DO_PREFLIGHT=1; shift ;;
--scenario) SCENARIO="${2:?--scenario requires a value}"; shift 2 ;;
--config) CONFIG="${2:?--config requires a value}"; shift 2 ;;
--out-dir) OUT_DIR="${2:?--out-dir requires a value}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1 (see --help)" ;;
esac
done
if [ "$DO_PREFLIGHT" -eq 0 ] && [ -z "$SCENARIO" ]; then
die "nothing to do: pass --preflight and/or --scenario <name>"
fi
command -v curl >/dev/null 2>&1 || die "curl is required"
command -v jq >/dev/null 2>&1 || die "jq is required"
mkdir -p "$OUT_DIR"
RUN_STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
log "out-dir=$OUT_DIR run=$RUN_STAMP base_url=$BASE_URL"
# ── helpers ──────────────────────────────────────────────────────────────────
# curl auth args, built once. Expanded with the ${arr[@]+...} idiom so an empty
# array survives `set -u` on the dev runner's macOS bash 3.2, which also lacks
# `mapfile` (do not reintroduce bash-4-only builtins here).
AUTH_ARGS=()
if [ -n "${IOP_LONG_SMOKE_TOKEN:-}" ]; then
AUTH_ARGS=(-H "Authorization: Bearer ${IOP_LONG_SMOKE_TOKEN}")
fi
# fetch_status <out-file>: capture the Control Plane status JSON.
fetch_status() {
local out="$1"
if [ -n "$STATUS_SSH" ]; then
ssh -o BatchMode=yes -o ConnectTimeout=10 "$STATUS_SSH" \
"curl -fsS --connect-timeout 10 '$STATUS_URL'" >"$out" 2>"$out.err"
else
curl -fsS --connect-timeout 10 "$STATUS_URL" >"$out" 2>"$out.err"
fi
}
# provider_snapshots_jq: normalize provider snapshots from either the edge status
# response shape or a fleet node list into a flat array.
SNAP_JQ='[.. | objects | select(has("provider_snapshots")) | .provider_snapshots[]?]'
# summarize_snapshot <status-json> -> prints a compact per-provider line and totals.
summarize_snapshot() {
local file="$1"
jq -r "
${SNAP_JQ} as \$s
| (\$s | map(.in_flight // 0) | add // 0) as \$inflight
| (\$s | map(.queued // 0) | add // 0) as \$queued
| (\$s | map(.long_in_flight // 0) | add // 0) as \$loninflight
| (\$s | map(.long_queued // 0) | add // 0) as \$lonqueued
| (\$s | map(has(\"long_in_flight\")) | any) as \$haslong
| (\$s[] | \" provider=\(.id // .adapter // \"?\") capacity=\(.capacity // 0) in_flight=\(.in_flight // 0) queued=\(.queued // 0) long_in_flight=\(.long_in_flight // \"n/a\") long_queued=\(.long_queued // \"n/a\") health=\(.health // \"?\")\"),
\" TOTAL in_flight=\(\$inflight) queued=\(\$queued) long_in_flight=\(if \$haslong then \$loninflight else \"n/a\" end) long_queued=\(if \$haslong then \$lonqueued else \"n/a\" end)\"
" "$file" 2>/dev/null || echo " (unparseable status json)"
}
# totals_from_snapshot <status-json> <key> -> integer sum, or empty when key absent everywhere.
totals_from_snapshot() {
local file="$1" key="$2"
jq -r "${SNAP_JQ} as \$s
| if (\$s | map(has(\"${key}\")) | any) then (\$s | map(.${key} // 0) | add // 0) else \"\" end" \
"$file" 2>/dev/null
}
# build the synthetic long prompt file once (kept in out-dir, not repo).
LONG_PROMPT_FILE="$OUT_DIR/long_prompt_${RUN_STAMP}.txt"
ensure_long_prompt() {
[ -f "$LONG_PROMPT_FILE" ] && return 0
# Repeat a filler sentence to LONG_CHARS. Synthetic text is enough; a
# tokenizer-perfect fixture is intentionally out of scope.
local unit="Long context admission smoke synthetic filler segment. "
local buf=""
while [ "${#buf}" -lt "$LONG_CHARS" ]; do
buf="${buf}${unit}"
done
printf '%s' "${buf:0:$LONG_CHARS}" >"$LONG_PROMPT_FILE"
log "synthetic long prompt: ${LONG_CHARS} chars -> $LONG_PROMPT_FILE"
}
# build a chat completion request body file for a given kind (normal|long).
# $1=kind $2=idx $3=out-body-file
build_body() {
local kind="$1" idx="$2" out="$3" content
if [ "$kind" = "long" ]; then
ensure_long_prompt
content="$(cat "$LONG_PROMPT_FILE")
Summarize the above in one short sentence. request_idx=${idx}"
else
content="Reply with the single word OK. request_idx=${idx}"
fi
jq -n --arg model "$MODEL" --arg content "$content" --argjson maxt "$MAX_TOKENS" \
'{model:$model, max_tokens:$maxt, stream:false, messages:[{role:"user", content:$content}]}' \
>"$out"
}
# fire one request in the background; write HTTP code + body to out prefix.
# $1=kind $2=idx $3=out-prefix
fire_request() {
local kind="$1" idx="$2" prefix="$3"
local body="$prefix.req.json"
build_body "$kind" "$idx" "$body"
curl -sS -o "$prefix.resp.json" -w '%{http_code}' \
--connect-timeout 15 --max-time 1800 \
-H "Content-Type: application/json" \
${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \
-X POST "$BASE_URL/chat/completions" \
--data-binary "@$body" >"$prefix.code" 2>"$prefix.curl.err" || true
}
# poll status repeatedly into a peaks file while a scenario runs. The poll loop
# runs until the caller creates "$OUT_DIR/<label>.polling_done" (so peaks are
# captured for the full request duration, however slow), bounded by POLL_SECS as
# a hard safety ceiling.
# $1=label $2=peaks-out-file
poll_peaks() {
local label="$1" peaks="$2"
local deadline=$((SECONDS + POLL_SECS))
local doneflag="$OUT_DIR/${label}.polling_done"
local snap="$OUT_DIR/${label}_poll_${RUN_STAMP}.json"
local peak_inflight=0 peak_queued=0 peak_loninflight=0 peak_lonqueued=0 samples=0
local long_visible=0
rm -f "$doneflag"
while (( SECONDS < deadline )) && [ ! -f "$doneflag" ]; do
if fetch_status "$snap"; then
samples=$((samples + 1))
local inflight queued lon lonq
inflight="$(totals_from_snapshot "$snap" in_flight)"; inflight="${inflight:-0}"
queued="$(totals_from_snapshot "$snap" queued)"; queued="${queued:-0}"
lon="$(totals_from_snapshot "$snap" long_in_flight)"
lonq="$(totals_from_snapshot "$snap" long_queued)"
[ -n "$lon" ] && long_visible=1
(( inflight > peak_inflight )) && peak_inflight=$inflight
(( queued > peak_queued )) && peak_queued=$queued
[ -n "$lon" ] && (( lon > peak_loninflight )) && peak_loninflight=$lon
[ -n "$lonq" ] && (( lonq > peak_lonqueued )) && peak_lonqueued=$lonq
fi
sleep 3
done
{
echo "label=$label samples=$samples"
echo "peak_in_flight=$peak_inflight"
echo "peak_queued=$peak_queued"
if [ "$long_visible" -eq 1 ]; then
echo "peak_long_in_flight=$peak_loninflight"
echo "peak_long_queued=$peak_lonqueued"
else
echo "peak_long_in_flight=n/a (Control Plane status view does not expose long fields)"
echo "peak_long_queued=n/a (Control Plane status view does not expose long fields)"
fi
} >"$peaks"
cat "$peaks"
}
# capture a final snapshot and assert recovery to zero.
# $1=label -> returns non-zero when recovery assertion fails.
assert_recovery() {
local label="$1"
local snap="$OUT_DIR/${label}_final_${RUN_STAMP}.json"
local ok=1
if ! fetch_status "$snap"; then
log "$label: FAILED to fetch final status (see $snap.err)"
return 1
fi
log "$label final snapshot:"
summarize_snapshot "$snap"
local inflight queued lon lonq
inflight="$(totals_from_snapshot "$snap" in_flight)"; inflight="${inflight:-0}"
queued="$(totals_from_snapshot "$snap" queued)"; queued="${queued:-0}"
lon="$(totals_from_snapshot "$snap" long_in_flight)"
lonq="$(totals_from_snapshot "$snap" long_queued)"
[ "$inflight" = "0" ] || { log "$label: in_flight not recovered ($inflight)"; ok=0; }
[ "$queued" = "0" ] || { log "$label: queued not recovered ($queued)"; ok=0; }
if [ -n "$lon" ]; then
[ "$lon" = "0" ] || { log "$label: long_in_flight not recovered ($lon)"; ok=0; }
[ "${lonq:-0}" = "0" ] || { log "$label: long_queued not recovered ($lonq)"; ok=0; }
else
log "$label: long_in_flight/long_queued not exposed by status view; verify recovery via Edge dispatch logs (context_class/queue_reason)."
fi
[ "$ok" -eq 1 ] || return 1
log "$label: recovery OK (in_flight=0, queued=0)"
return 0
}
# read_kv <kv-file> <key>: print the value after "key=" for the first matching
# line. The value may be a number or an "n/a ..." note.
read_kv() {
local file="$1" key="$2"
sed -n "s/^${key}=//p" "$file" 2>/dev/null | head -1
}
# assert_peak <peaks-file> <key> <op> <bound> <label>: assert a numeric peak
# field satisfies a comparison. <op> is a test(1) integer operator (-ge/-le/...).
# Returns non-zero when the field is missing, non-numeric, or fails the bound.
assert_peak() {
local file="$1" key="$2" op="$3" bound="$4" label="$5" val
val="$(read_kv "$file" "$key")"
if ! [[ "$val" =~ ^[0-9]+$ ]]; then
log "$label: FAIL peak $key not a number ('${val:-<absent>}'); cannot assert $key $op $bound"
return 1
fi
if [ "$val" "$op" "$bound" ]; then
log "$label: peak $key=$val ($op $bound) OK"
return 0
fi
log "$label: FAIL peak $key=$val (require $key $op $bound)"
return 1
}
# fire N requests of a kind, wait for all, print success/http summary.
# $1=kind $2=count $3=label
fire_and_wait() {
local kind="$1" count="$2" label="$3"
local pids=() i
log "$label: firing $count $kind request(s) to $BASE_URL/chat/completions"
for ((i = 1; i <= count; i++)); do
fire_request "$kind" "$i" "$OUT_DIR/${label}_${kind}_${i}_${RUN_STAMP}" &
pids+=("$!")
done
# poll while they run.
poll_peaks "$label" "$OUT_DIR/${label}_peaks_${RUN_STAMP}.txt" &
local poll_pid=$!
for p in "${pids[@]}"; do wait "$p" 2>/dev/null || true; done
touch "$OUT_DIR/${label}.polling_done"
wait "$poll_pid" 2>/dev/null || true
local ok=0 total=0
for ((i = 1; i <= count; i++)); do
total=$((total + 1))
local code
code="$(cat "$OUT_DIR/${label}_${kind}_${i}_${RUN_STAMP}.code" 2>/dev/null || echo "000")"
[ "$code" = "200" ] && ok=$((ok + 1))
done
log "$label: $kind http_200=$ok/$total"
if [ "$ok" -ne "$total" ]; then
log "$label: FAIL $kind http_200=$ok/$total (require $total/$total)"
return 1
fi
return 0
}
# ── preflight ────────────────────────────────────────────────────────────────
run_preflight() {
log "=== PREFLIGHT ==="
local pre="$OUT_DIR/preflight_${RUN_STAMP}.txt"
local ok=1
# Run `edge config check` in its own capture so its exit status gates
# preflight instead of being swallowed by the tee pipeline. A prebuilt edge
# binary (IOP_LONG_SMOKE_EDGE_BIN) replaces `go run` on runners without go.
local -a cfg_cmd
if [ -n "$EDGE_BIN" ]; then
cfg_cmd=("$EDGE_BIN" config check --config "$CONFIG")
else
cfg_cmd=(go run ./apps/edge/cmd/edge config check --config "$CONFIG")
fi
local cfg_out cfg_rc=0
if [ -n "$EDGE_BIN" ] && [ ! -x "$EDGE_BIN" ]; then
cfg_out="edge binary not executable: $EDGE_BIN"
cfg_rc=1
else
cfg_out="$(cd "$REPO_ROOT" && "${cfg_cmd[@]}" 2>&1)" || cfg_rc=$?
fi
{
echo "run=$RUN_STAMP"
echo "workdir=$REPO_ROOT"
echo "base_url=$BASE_URL"
echo "status_url=$STATUS_URL status_ssh=${STATUS_SSH:-<direct>}"
echo "config=$CONFIG"
echo
echo "## source state"
echo "\$ git -C $REPO_ROOT rev-parse HEAD"
git -C "$REPO_ROOT" rev-parse HEAD 2>&1 || true
echo "\$ git -C $REPO_ROOT status --short"
git -C "$REPO_ROOT" status --short 2>&1 || true
echo
echo "## config check"
echo "\$ ${cfg_cmd[*]}"
echo "$cfg_out"
if [ "$cfg_rc" -eq 0 ]; then echo "config check OK"; else echo "config check FAILED (rc=$cfg_rc)"; fi
} | tee "$pre"
if [ "$cfg_rc" -ne 0 ]; then
log " BLOCKER: edge config check failed (rc=$cfg_rc, config=$CONFIG)"
ok=0
fi
log "endpoint reachability: $BASE_URL/models"
if curl -fsS --connect-timeout 10 ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} "$BASE_URL/models" \
>"$OUT_DIR/models_${RUN_STAMP}.json" 2>"$OUT_DIR/models_${RUN_STAMP}.err"; then
log " /models OK"
jq -r '.data[]?.id' "$OUT_DIR/models_${RUN_STAMP}.json" 2>/dev/null | sed 's/^/ model=/' || true
else
log " BLOCKER: /models unreachable. exact command:"
log " curl -fsS --connect-timeout 10 $BASE_URL/models"
ok=0
fi
log "status reachability: $STATUS_URL"
local snap="$OUT_DIR/preflight_status_${RUN_STAMP}.json"
if fetch_status "$snap"; then
log " status OK -> $snap"
summarize_snapshot "$snap"
# every expected provider identity must be present and healthy.
local pid phealth
for pid in "${PROVIDER_IDS[@]}"; do
if ! jq -e "${SNAP_JQ} | map(.id // .adapter) | index(\"$pid\")" "$snap" >/dev/null 2>&1; then
log " BLOCKER: provider identity not found in status snapshot: $pid"
ok=0
continue
fi
phealth="$(jq -r "${SNAP_JQ} | map(select((.id // .adapter) == \"$pid\")) | (.[0].health // \"?\")" "$snap" 2>/dev/null || echo "?")"
if [ "$phealth" = "healthy" ]; then
log " provider present+healthy: $pid"
else
log " BLOCKER: provider not healthy: $pid (health=$phealth)"
ok=0
fi
done
# summed provider capacity must match the documented normal baseline.
local cap_total
cap_total="$(totals_from_snapshot "$snap" capacity)"
if [ -n "$cap_total" ] && [ "$cap_total" = "$NORMAL_CAPACITY_TOTAL" ]; then
log " capacity total OK: $cap_total (== $NORMAL_CAPACITY_TOTAL)"
else
log " BLOCKER: capacity total '${cap_total:-<absent>}' != expected $NORMAL_CAPACITY_TOTAL"
ok=0
fi
if [ -z "$(totals_from_snapshot "$snap" long_in_flight)" ]; then
log " NOTE: status view does not expose long_in_flight/long_queued; long-slot recovery must be read from Edge dispatch logs (context_class/queue_reason) or a follow-up that adds long fields to the Control Plane status view."
fi
else
log " BLOCKER: status unreachable. exact command:"
if [ -n "$STATUS_SSH" ]; then
log " ssh $STATUS_SSH curl -fsS --connect-timeout 10 $STATUS_URL"
else
log " curl -fsS --connect-timeout 10 $STATUS_URL"
fi
ok=0
fi
log "expected baseline: normal_capacity_total=$NORMAL_CAPACITY_TOTAL long_slot_total=$LONG_SLOT_TOTAL"
if [ "$ok" -eq 1 ]; then
log "=== PREFLIGHT OK ==="
return 0
fi
log "=== PREFLIGHT BLOCKED (see out-dir; blockers are verification blockers, not user-review) ==="
return 3
}
# ── scenarios ────────────────────────────────────────────────────────────────
run_scenario() {
local name="$1"
local rc=0
case "$name" in
normal-10)
log "=== SCENARIO normal-10 (expect peak in_flight>=$NORMAL_CAPACITY_TOTAL, queued>=1) ==="
local peaks="$OUT_DIR/normal-10_peaks_${RUN_STAMP}.txt"
fire_and_wait normal 10 "normal-10" || rc=1
assert_peak "$peaks" peak_in_flight -ge "$NORMAL_CAPACITY_TOTAL" "normal-10" || rc=1
assert_peak "$peaks" peak_queued -ge 1 "normal-10" || rc=1
assert_recovery "normal-10" || rc=1
;;
mixed)
log "=== SCENARIO mixed (long slots held; normal must not head-of-line block) ==="
local peaks="$OUT_DIR/mixed_peaks_${RUN_STAMP}.txt"
# Fill all long slots, and pile normal requests behind them.
local i pids=()
for ((i = 1; i <= LONG_SLOT_TOTAL; i++)); do
fire_request long "$i" "$OUT_DIR/mixed_long_${i}_${RUN_STAMP}" &
pids+=("$!")
done
for ((i = 1; i <= 6; i++)); do
fire_request normal "$i" "$OUT_DIR/mixed_normal_${i}_${RUN_STAMP}" &
pids+=("$!")
done
poll_peaks "mixed" "$peaks" &
local poll_pid=$!
for p in "${pids[@]}"; do wait "$p" 2>/dev/null || true; done
touch "$OUT_DIR/mixed.polling_done"
wait "$poll_pid" 2>/dev/null || true
local lok=0 nok=0
for ((i = 1; i <= LONG_SLOT_TOTAL; i++)); do
[ "$(cat "$OUT_DIR/mixed_long_${i}_${RUN_STAMP}.code" 2>/dev/null || echo 000)" = "200" ] && lok=$((lok + 1))
done
for ((i = 1; i <= 6; i++)); do
[ "$(cat "$OUT_DIR/mixed_normal_${i}_${RUN_STAMP}.code" 2>/dev/null || echo 000)" = "200" ] && nok=$((nok + 1))
done
log "mixed: long http_200=$lok/$LONG_SLOT_TOTAL normal http_200=$nok/6 (head-of-line blocking would show normal stalling behind full long slots)"
[ "$lok" -eq "$LONG_SLOT_TOTAL" ] || { log "mixed: FAIL long http_200=$lok/$LONG_SLOT_TOTAL"; rc=1; }
[ "$nok" -eq 6 ] || { log "mixed: FAIL normal http_200=$nok/6"; rc=1; }
# long_in_flight is optional in the Control Plane status view: assert
# only when visible, otherwise defer to Edge dispatch log evidence.
if [[ "$(read_kv "$peaks" peak_long_in_flight)" =~ ^[0-9]+$ ]]; then
assert_peak "$peaks" peak_long_in_flight -ge "$LONG_SLOT_TOTAL" "mixed" || rc=1
else
log "mixed: long_in_flight not exposed by status view; capture Edge dispatch log (context_class) as review evidence."
fi
assert_recovery "mixed" || rc=1
;;
all-long-slot-full)
local n=$((LONG_SLOT_TOTAL + 2))
log "=== SCENARIO all-long-slot-full (fire $n long > $LONG_SLOT_TOTAL slots; expect queue then dispatch) ==="
local peaks="$OUT_DIR/all-long-slot-full_peaks_${RUN_STAMP}.txt"
fire_and_wait long "$n" "all-long-slot-full" || rc=1
# only long requests are in flight, so total in_flight must not exceed
# the long-slot total; at least one long request must have queued.
assert_peak "$peaks" peak_in_flight -le "$LONG_SLOT_TOTAL" "all-long-slot-full" || rc=1
assert_peak "$peaks" peak_queued -ge 1 "all-long-slot-full" || rc=1
if [[ "$(read_kv "$peaks" peak_long_queued)" =~ ^[0-9]+$ ]]; then
assert_peak "$peaks" peak_long_queued -ge 1 "all-long-slot-full" || rc=1
else
log "all-long-slot-full: long_queued not exposed by status view; capture Edge dispatch log (queue_reason=long_context_capacity_full) as review evidence."
fi
assert_recovery "all-long-slot-full" || rc=1
;;
*)
die "unknown scenario: $name (normal-10 | mixed | all-long-slot-full)"
;;
esac
return "$rc"
}
# ── main ─────────────────────────────────────────────────────────────────────
rc=0
if [ "$DO_PREFLIGHT" -eq 1 ]; then
run_preflight || rc=$?
fi
if [ -n "$SCENARIO" ]; then
if [ "$rc" -ne 0 ]; then
die "preflight blocked (rc=$rc); not running scenario '$SCENARIO'"
fi
run_scenario "$SCENARIO" || rc=$?
fi
log "done rc=$rc evidence=$OUT_DIR"
exit "$rc"