feat(F-OPS-001): 운영 콘솔 /console — 「떠 있는데 못 하는 상태」를 화면이 가른다 (#522) - #594
Conversation
서버 몫(`GET /ops/status`)은 #531 로 끝났고 web 만 남아 있었다. 이슈가 남긴 결정 넷 중 내 몫 셋을 확정하고 화면을 낸다. 무엇을 그리나 - 카드 넷(server · database · ai-service · data-volumes)을 **서버가 보낸 순서 그대로**. 순서가 곧 중요도라 화면이 다시 정하지 않는다. - 상태는 UP · DEGRADED · DOWN 셋. **`DEGRADED` 가 요점이다** — UP/DOWN 둘로만 그리면 「떠 있는데 못 하는 상태」가 전부 정상으로 보이고, 그게 #522 의 출발점이다. - 5초 폴링 · 일시정지 · 멈춘 동안 「지금 한 번」. - 왕복 시간 추이는 **이 화면을 연 뒤로만**(브라우저 메모리). 서버에 시계열을 안 쌓는다. - 403 은 오류가 아니라 「차단됨」으로 그린다(S-08 과 같은 판단). 내가 정한 것 셋 - **판정 3색을 쓴다**(질문 1). 새 상태색 체계를 만드는 쪽이 더 나쁘다 — 화면마다 다른 상태색이 생기면 색 체계를 두 벌 배워야 한다. 조건 셋(제품 흐름 밖 · 라벨 병기 · 고객 비노출)을 `tokens.css` 규칙 1 옆에 못 박았다. 팀 셋 다 찬성한 방향이다. - **화면 번호를 안 준다**(질문 3, 정세현·윤지석 합의를 따른다). 파일도 `Guide.tsx` 와 같은 모양으로 `Console.tsx` 다. `SCREENS` 에 안 넣는다. - **진입점은 `/guide` 의 「여기서 자주 막혀요」 하나**. 제품 흐름에서 링크하면 판매자에게는 전부 403 이고, 아무 데서도 안 열리면 URL 을 외워야 한다(#406 에서 실제로 그 상태였다). ❗못 잰 값을 0 으로 접지 않는다 계약이 `latencyMs` 를 nullable 로 준 이유가 「즉시 응답」과 「안 쟀다」를 가르는 것이라, 화면이 다시 뭉치면 그 뜻이 사라진다. 추이 막대도 그 자리를 빈 칸으로 둔다. ❗요청이 실패하면 직전 카드를 지운다 「지금」을 답하는 화면에서 남은 카드는 전부 낡은 값이고, 그중 초록이 하나라도 있으면 화면이 서버가 하지 않은 말을 한다. 실측 - `npm run build`(tsc) 통과 · server `./gradlew test` 812건 통과. - `WebTypesMirrorContractTest` 가 새 타입을 실제로 짝지었다 — `checkedAt` 를 한 글자 바꿔 재니 `OpsStatus="계약에만 [checkedAt] · 화면에만 [checkedAtX]"` 로 빨개졌다. - 로컬 실서버로 UP·DOWN 을, 스텁으로 DEGRADED·측정 실패·403·연결 실패를 그려 봤다. 런타임 오류 0건. Refs #522 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vJboj38TYcxUpG5ZZcQKs
hd0rable
left a comment
There was a problem hiding this comment.
승인합니다. DEGRADED 를 화면의 요점으로 잡은 것이 이 이슈의 답이고, 서버가 그 구별을
내려도 화면이 UP/DOWN 둘로 접으면 #531 이 한 일이 사라집니다.
계약 쪽을 제가 대조했습니다 (서버가 제 영역입니다)
OpsStatus.java:29·54 checkedAt · deployment · components / id·name·health·latencyMs·note·facts
types.ts:911-936 같은 이름·같은 nullable — 못 잰 자리는 null
카드 순서 OpsStatusService.java:94 의 List.of(...) 가 고정하고
OpsStatusEndpointTest:144 「구성요소가 넷이고 순서가 고정이다」가 잠근다
→ 화면이 그 순서에 기대는 것이 계약에 서 있다
facts 가 배열 객체면 순서가 구현에 달린다 — 그 요청대로 배열이다
rbac_policy.yaml:220 ops:status:read → roles: [ADMIN] · 15-demo-mode.sh:112 가 주입한다
latencyMs 를 0 으로 접지 않은 것이 특히 맞습니다. 서버가 nullable 로 낸 이유를 그
javadoc 이 "0 으로 채우면 «즉시 응답» 과 «재지 않았다» 가 화면에서 같아진다" 로 적어 뒀고,
실측에서 database: 0 과 server: null 이 실제로 같이 나옵니다 — 화면이 다시 뭉치면 그
필드가 존재할 이유가 없어집니다.
요청 실패에 직전 카드를 지우는 것도 맞습니다. "그중 초록이 하나라도 있으면 화면이 서버가
하지 않은 말을 한다" 가 이 화면의 성격을 정확히 짚습니다.
❗한 자리 — 실패한 폴링만 추이에서 흔적 없이 사라집니다
화면의 규칙이 "못 잰 자리는 높이 0 이 아니라 바닥 점" 인데, 폴링 자체가 실패하면 그
자리에 아무것도 안 남습니다.
Console.tsx:128-155
성공 setTrend((prev) => … [...prev[c.id], c.latencyMs] …) null 도 그대로 쌓인다 ✓
실패 setStatus(null) · setError(...) setTrend 를 안 부른다 ✗
그래서 이렇게 됩니다.
1·2·3 성공(3 표본) → 4 실패(표본 없음) → 5 성공
추이 막대: ▮▮▮▮ ← 넷이 연속으로 보인다. 4 회차에 못 쟀다는 흔적이 없다
가장 강한 「안 쟀다」가 유일하게 안 보입니다. 카드가 없는 동안엔 추이도 안 보이니 그 순간
문제는 아니고, 복구된 뒤에 끊긴 구간이 이어진 것처럼 남습니다.
실패 때도 아는 id 마다 null 을 한 칸 쌓으면 그 자리가 바닥 점으로 남습니다 —
Trend 가 null 을 이미 그렇게 그립니다(:341-343). 지우는 쪽(추이 초기화)도 되지만,
그러면 복구 직후에 «처음부터 잰 것» 처럼 보여서 이쪽이 나아 보입니다.
코드를 읽고 낸 것이고 화면으로는 못 재봤습니다(web 에 러너가 없어서) — 판단은 그쪽 몫입니다.
막지는 않습니다.
정한 것 셋 — 이견 없습니다
- ① 판정 3색 — "화면마다 다른 상태색이 생기면 색 체계를 두 벌 배워야 하고, 그러면 규칙 1
이 지키려던 것이 오히려 옅어진다" 가 설득력 있습니다. 그리고 예외를tokens.css규칙 1
옆에 조건 셋으로 못 박은 것이 이 결정의 값입니다(:26-37) — 안 적으면 다음 사람이
"3색은 아무 데나" 의 근거로 씁니다. S-08 과의 구분(«수치에 3색을 얹으면 지점 성과 지표로
읽힌다»)도 맞습니다. - ② 번호 없음 ·
Console.tsx—Guide.tsx와 같은 모양이고SCREENS에 안 넣는 것이
「제품 흐름 밖」이라는 사실과 맞습니다. - ③ 진입점을
/guide하나로 — 제품 흐름에서 링크하면 판매자에게 전부 403 이라는 지적이
정확합니다(ops:status:read는 ADMIN 뿐입니다). 그리고 아무 데도 안 걸면 URL 을 외워야
한다는 것도 —/upload가 지금 그 상태입니다.
서버 쪽 전제 하나만 적어 둡니다
이 화면이 그리는 값에 고객 데이터가 0건인 것이 ops:status:read 를 ADMIN 에 준 근거입니다
(ADR-001). types.ts:925-928 이 "이 인터페이스에 그런 필드가 생기면 고칠 자리는 화면이
아니라 서버 응답" 이라고 적어 둔 것이 정확합니다 — 서버 쪽은 OpsStatusHasNoCustomerDataTest
가 허용 목록으로 막고 있고, 그건 제 영역이라 계속 그렇게 둡니다.
WebTypesMirrorContractTest 를 변이로 확인한 것(checkedAt → checkedAtX 에서 이름이 실제로
걸리는 것)도 좋습니다 — 그 대조는 «짝을 못 찾아 0개를 대조했다» 와 초록이 구분되지 않는
자리입니다.
|
승인은 그대로 두고 유니온 한 줄만 알립니다.
❗** 먼저 머지되는 쪽이 나머지를 맞추면 됩니다. 이 PR 이 먼저면 제가 화면 쪽은 확인했습니다 — |
gitIt-sehyeon
left a comment
There was a problem hiding this comment.
승인합니다. 배선 셋과 미러 대조를 제 손으로 재현했고, 물어보신 「측정 실패」건은 응답으로 못 가르는 게 맞습니다 — 서버 코드로 확인했습니다. 아래에 제 의견과, 제 몫으로 남은 명세 8절 건을 적습니다.
재현한 것
npm run build (tsc) 통과
server ./gradlew test 812건 · 실패 0
배선 3건 (다시 안 만지셨다는 것)
15-demo-mode.sh:112 ~^/api/ops/status$ → $admin 있다
rbac_policy.yaml:219 ops:status:read 있다
docker-compose.yml SPHINX_STACK 있다
미러 대조가 새 타입을 정말 짝지었는지는 변이 셋으로 확인했습니다 — 초록이 「짝을 못 찾아 0개를 대조했다」와 구분이 안 되는 자리라 하신 것이 맞습니다.
checkedAt → checkedAtX 빨강
latencyMs 를 지운다 빨강
health 를 지운다 빨강
셋 다 걸립니다. OpsStatus 가 실제로 대조 대상에 들어가 있습니다.
❗물어보신 것 — 「측정 실패」는 응답으로 못 가릅니다. 맞습니다
OpsStatusService.isolated() 를 읽었습니다.
return new Component(id, name, Health.DOWN, null,
"상태를 재지 못했다(" + oneLine(e.toString()) + ") — 서버 로그를 본다",
List.of());Health.DOWN · latencyMs=null · facts=[] — 진짜 DOWN 과 필드가 하나도 안 다릅니다. 가르려면 note 문면을 파싱해야 하고, 그건 types.ts 주석이 "문면 파싱으로 가르면 서버 문구가 바뀔 때 조용히 깨진다" 로 막아 둔 그 방식입니다. 안 하신 판단이 맞습니다.
그리고 이건 화면 문제가 아니라 서버가 모르는 것을 안다고 적는 문제로 보입니다
결정 5.40 못 잰 값은 `0` 이 아니라 「모른다」로 적는다
latencyMs 는 그 규칙을 지킵니다(null). 그런데 health 는 안 지킵니다 — 측정이 터졌을 때 우리가 아는 것은 "못 쟀다" 뿐인데 DOWN 은 "그 구성요소가 죽었다" 를 단정합니다. 화면이 그걸 그대로 그리면 운영자는 멀쩡한 ai-service 를 재시작하러 갑니다 — #556 이 없앤 그 고리와 같은 모양입니다.
그래서 저는 note 옆에 신호를 붙이는 것보다 health 를 정직하게 만드는 쪽이 맞다고 봅니다.
ⓐ Health 에 UNKNOWN 을 더한다 「못 쟀다」가 상태값이 된다. 화면이 색·문면을 따로 줄 수 있다
비용: enum 이 계약이라 수요자 전원 · 3색 매핑을 다시 정해야 한다
ⓑ Component 에 measured: boolean enum 을 안 건드린다. 다만 health=DOWN 이 여전히 거짓말이다
ⓐ 를 봅니다. ⓑ 는 「DOWN 인데 안 쟀다」라는 모순된 조합을 계약이 허용하게 만들고, 읽는 쪽이 두 필드를 같이 봐야 뜻이 섭니다. latencyMs 를 nullable 로 둔 것과 같은 결로 가려면 상태값 자체가 「모른다」를 가져야 합니다.
core/ops/ 와 contracts/ 가 @hd0rable 님 것이라 제가 정할 자리는 아닙니다. 이슈로 떼시는 데 동의하고, 이 PR 은 지금 이대로가 맞습니다 — 계약에 신호가 없는 상태에서 화면이 추측하면 그게 더 나쁩니다.
제가 정한 것 셋에 대한 답
① 판정 3색 재사용 — 동의합니다. 특히 예외 조건을 tokens.css 규칙 1 옆에 못 박은 것이 이 결정의 값입니다. 안 적었으면 다음 사람이 "콘솔도 3색 쓰던데" 로 아무 데나 가져다 씁니다. 네 조건 중 «값이 판정과 같은 종류(정상·주의·불가)» 가 특히 좋습니다 — S-08 이 3색을 안 쓰는 이유(수치라 성과 지표로 읽힌다)와 대칭이라 두 판단이 한 규칙에서 나옵니다.
② 화면 번호 없음 · Console.tsx — 합의 그대로입니다. SCREENS 에 안 넣은 것도 맞습니다.
③ 진입점을 /guide 하나로 — 이게 셋 중 제일 좋습니다. "링크가 있다 ≠ 그 API 를 부를 권한이 있다" 가 정확하고, 반대로 아무 데도 안 걸면 /upload 처럼 URL 을 외워야 하는 상태(#406)가 됩니다. 막힌 사람이 찾아오는 자리에서만 보인다가 그 둘 사이의 정확한 지점입니다.
못 잰 값을 0 으로 안 접은 것
database latencyMs: 0 "왕복 0ms"
API 서버 latencyMs: null "왕복 — (안 쟀어요)"
추이 막대를 바닥 점으로 둔 것까지 간 것이 좋습니다. 높이 0 으로 두면 그래프에서는 「빠르다」로 읽히는데, 그 자리가 결정 5.40 이 말하는 «부재를 0 으로 적으면 다음 사람이 없었다고 읽는다» 와 같은 함정입니다.
요청 실패 시 직전 카드를 지우는 것
동의합니다. "「지금」을 답하는 화면에서 남은 카드는 전부 낡은 값이고, 그중 초록이 하나라도 있으면 화면이 서버가 하지 않은 말을 한다" — 이게 pr-review.yml 가드에서 밟은 것과 같은 종류입니다(갱신 실패가 화면에 흔적을 안 남기면 낡은 초록이 남는다).
제 몫 — 명세 8절
명세 8절 「제품 흐름 밖 화면」 절 — @gitIt-sehyeon (
/guide와/console같이)
가져갑니다. /guide 와 /console 을 같이 적고, SCREENS 에 없는 이유(제품 흐름 밖 · 화면 번호 없음 · ADMIN 전용)를 그 절이 말하게 하겠습니다. 이 PR 이 머지된 뒤에 내겠습니다 — 지금 쓰면 아직 없는 화면을 명세가 먼저 말합니다.
/guide 캡처를 alpha 실화면 규약대로 배포 뒤로 미루신 것도 맞습니다.
이 PR 이 만드는 것은 `/console` 화면 하나다. 앞 커밋이 같이 실었던 둘을 뺀다. - `web/src/pages/Guide.tsx` — 「여기서 자주 막혀요」 진입점 한 줄 - `web/src/styles/tokens.css` — 판정 3색 예외를 규칙 1 옆에 적은 문단 ❗**두 분이 승인하신 뒤의 변경이다**(@hd0rable @gitIt-sehyeon). 두 리뷰 다 그 둘을 좋게 보셨으므로, 승인이 이 범위에도 그대로 서는지는 다시 봐 주셔야 한다. 남는 것과 그 귀결 - 화면이 **어디서도 안 걸린다** — 닿는 길이 주소 직접 입력뿐이다(`/upload` 와 같은 상태 · #406). `App.tsx` 주석에 그 사실을 적었다. 진입점은 이 PR 밖에서 정한다. - `tokens.css` 규칙 1 은 **예외를 모르는 채로 남는다.** 그래서 이 화면이 3색을 쓰는 근거와 조건 넷이 `Console.tsx` 머리말 한 곳에만 있다 — 팔레트 쪽에 옮겨 적을지는 따로 정한다. `Console.css` 머리말도 그 자리를 가리키게 고쳤다. ❗같이: 실패한 폴링도 추이에 한 칸 남긴다 (리뷰 · 강희진) 성공한 회차만 이어 붙어서 **끊긴 구간이 이어진 것처럼** 보였다 — 카드가 없는 동안이 아니라 **복구된 뒤**에 거짓이 된다. 이 화면의 규칙이 「못 잰 자리는 흔적을 남긴다」인데 정작 가장 강한 「안 쟀다」가 유일하게 안 보이는 자리였다. 화면 안의 결함이라 같이 고쳤다. 실측(스텁 · 4·5 회차만 연결 실패): 막대 ▮▮▮··▮▮ ← · 가 못 잰 자리. 고치기 전에는 ▮▮▮▮▮ 다섯이었다 aria 왕복 시간 추이 — 최근 7회, 최대 17ms `npm run build`(tsc) 통과 · 계약 미러 대조 통과. Refs #522 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vJboj38TYcxUpG5ZZcQKs
|
두 분 승인 뒤에 범위를 좁혔습니다 — 다시 봐 주셔야 합니다( 뺀 것 둘두 분이 정확히 그 둘을 이 PR 의 값으로 꼽으셨습니다. @gitIt-sehyeon 은 진입점을 "셋 중 제일 좋습니다 … 막힌 사람이 찾아오는 자리에서만 보인다가 그 둘 사이의 정확한 지점" 으로, @hd0rable 은 예외 문면을 "안 적으면 다음 사람이 「3색은 아무 데나」의 근거로 씁니다" 로 보셨습니다. 그래서 승인이 이 범위에도 그대로 서는지는 두 분 판단입니다. 남는 상태를 숨기지 않고 적어 둡니다. 둘 다 이 PR 밖에서 정합니다. 같이 고친 것 — 실패한 폴링도 추이에 남깁니다@hd0rable 이 코드로 짚어 주신 자리입니다. 화면 안의 결함이라 같이 고쳤고, 스텁으로 4·5 회차만 연결 실패시켜 재봤습니다. 추이를 비우는 쪽도 봤는데, 그러면 복구 직후가 «처음부터 잰 것» 처럼 보여서 말씀하신 쪽으로 갔습니다. 근거는
|
gitIt-sehyeon
left a comment
There was a problem hiding this comment.
좁힌 범위에도 승인 유지합니다. 다만 "이 PR 밖에서 정한다" 두 건에 받는 사람이 없어서 이슈로 떼겠습니다 — 이 레포가 반복해서 밟은 자리가 정확히 그것입니다.
확인한 것
tokens.css 되돌림 주석 문단만 빠졌다 — 색 토큰은 안 건드렸다 (확인함)
npm run build (tsc) 통과
계약 미러 대조 통과
좁힌 판단 — 맞습니다
두 조각이 서로 다른 질문을 엽니다. Guide.tsx 는 제품 문안이고 tokens.css 는 팔레트 규칙이라, 이 PR(화면 하나)과 되돌릴 단위가 다릅니다. 그리고 뺀 사실과 그 귀결을 숨기지 않고 적으신 것이 이 커밋의 값입니다 — 특히 App.tsx 주석에 "지금은 어디서도 안 걸린다" 를 남긴 것.
❗다만 「밖에서 정한다」에 주소가 없습니다
① /console 이 어디서도 안 걸린다 #406 이 만든 /upload 상태와 같다
② tokens.css 규칙 1 이 예외를 모른다 규칙은 절대문이고 화면 하나가 그것을 깨는데 포인터가 없다
②가 특히 그렇습니다. Console.css 머리말이 Console.tsx 를 가리키게 하셨지만, 문제는 반대 방향입니다 — 다음에 3색을 쓰려는 사람은 Console.tsx 를 안 읽고 tokens.css 규칙 1 을 읽습니다. 거기엔 예외가 없으니 "규칙이 절대문인데 콘솔은 쓰네" 를 보고 근거 없이 따라 쓰거나, 반대로 콘솔을 규칙 위반으로 고칩니다. 앞 판의 그 문단이 막던 것이 그것이었습니다.
이슈 둘로 떼겠습니다 — 제가 냅니다. 그래야 데모 전에 ①이 남아 있는지 목록에서 보입니다.
같이 고친 것 — 실패한 폴링을 추이에 남긴 것
이게 좁히기보다 큰 변경인데 방향이 맞습니다.
카드가 없는 동안이 아니라 복구된 뒤에 거짓이 된다.
정확합니다. 실패 중에는 화면 전체가 「지금 못 잰다」를 말하므로 사람이 안 속는데, 복구되고 나면 추이만 남고 그 막대가 끊긴 적 없는 것처럼 보입니다. 그리고 이 화면의 규칙이 "못 잰 자리는 0 이 아니라 흔적" 인데 가장 강한 「안 쟀다」가 유일하게 안 보이던 자리였다는 정리가 그 규칙을 스스로 적용한 것입니다.
추이를 비우는 쪽을 안 고른 근거("복구 직후가 «처음부터 잰 것» 처럼 보인다")도 맞습니다 — 그건 같은 거짓말을 다른 모양으로 하는 것입니다.
남는 하나 — 「측정 실패」
앞 리뷰에서 적은 Health 에 UNKNOWN 건은 그대로 열려 있습니다. 계약이라 @hd0rable 님 자리이고 이 PR 밖입니다.
yoonjiseok
left a comment
There was a problem hiding this comment.
승인합니다 — 범위 축소 뒤의 판에 대해서도 섭니다(아래). web/ 은 제 영역이 아니라 화면 자체는 두 분 리뷰에 얹고, ai-service 쪽 사실이 이 화면까지 제대로 오는가를 봤습니다.
내 쪽 → 화면 사슬이 P1 모양입니다
ai-service /healthz status:"ok" 를 **항상** 낸다 + llm_configured 를 사실로 낸다 ← 측정
server OpsStatusService llmConfigured != true → problems → Health.DEGRADED ← 판정
web /console 노랑 칩 + note "LLM 키가 없다 — 채점(F-SCR-001)이 502 로 떨어진다"
제 서비스가 스스로 «나 degraded 야» 라고 말하지 않는 것이 맞습니다. 제 쪽은 사실만 내고 그것을 건강으로 바꾸는 것은 룰입니다 — P1(«AI 는 측정, 룰은 결정»)이 운영 상태 축에서도 같은 모양으로 서 있습니다. 화면이 그 판정을 색으로만 말하지 않고 note 로 다음 행동까지 적는 것도 이 이슈의 출발점(«셋이 겉으로 같은 502 하나였다»)에 맞습니다.
새 타입이 그물 안입니다 — 재봤습니다
이 레포가 「N 벌」로 여러 번 데인 자리라, types.ts 에 사본이 하나 느는 것을 봤습니다.
기준선 BUILD SUCCESSFUL
OpsComponent 에서 latencyMs 를 뺀다 2 tests, 1 failed
health → status 로 이름을 바꾼다 2 tests, 1 failed
WebTypesMirrorContractTest 가 openapi.yaml ↔ types.ts 를 대조하고 OpsComponent(:1055)가 그 안에 있습니다. 그리고 그 테스트가 이 PR 에서 실제로 뜹니다 — web/src/api/types.ts 가 ci.yml 의 server_extra 에 있어서 web/ 만 건드리는 PR 인데도 server 잡이 뜹니다. 실행이 최종 커밋 위인 것도 확인했습니다.
PR head 63d91f7
테스트 (4모듈) 실행 success · 63d91f7 · 07:10:38 ← 같은 SHA
범위 축소에 대해 — 승인 섭니다
물어보신 자리입니다. 뺀 둘이 이 PR 의 값을 안 줄입니다.
/guide 진입점 화면이 어디서도 안 걸린다 → /upload 와 같은 상태(#406). 진입점은 밖에서 정한다
tokens.css 예외 규칙 1 이 예외를 모르는 채 남는다
첫째는 이 PR 이 만드는 것과 성격이 다릅니다 — 「어디서 들어가나」는 화면 목록 전체의 문제이고(S-00 을 지운 뒤 #406 이 다루는 축), 여기 한 줄로 붙이면 그 결정이 이 PR 에 묻힙니다. 둘째는 tokens.css 가 오준서 님 파일이라도 규칙 문서 성격이라, 예외를 규칙 옆에 적는 것은 그 규칙을 쓰는 화면 전부에 걸리는 결정입니다. 둘 다 나중에 하는 게 맞아 보입니다.
다만 App.tsx 주석에 «닿는 길이 주소 직접 입력뿐»을 적어 두신 것이 이 축소를 안전하게 만듭니다 — 안 적었으면 다음 사람이 «진입점이 있겠지» 로 읽습니다.
❗내 쪽 사실 셋이 카드까지 안 옵니다 (이 PR 밖 · 서버 몫)
/healthz 가 내는 것 중 카드에 안 실리는 것이 셋입니다.
llm_model 카드에 없음 ❗아래
llm_base_url 카드에 없음 ❗아래
env_files 카드에 없음 (어느 .env 를 읽었나 — 데이터 디렉토리로 대체됨. 이견 없습니다)
앞 둘이 걸립니다. 「떠 있는데 못 하는 상태」의 한 갈래가 «키는 있는데 다른 모델·다른 프로바이더를 보고 있다» 입니다. 그때 서비스는 UP 이고 채점도 돌지만, 나오는 수가 우리가 잰 수가 아닙니다. 제 config.py 가 그 귀결을 이미 적어 뒀습니다.
# config.py:59
# .env 의 LLM_MODEL 로 덮어쓸 수 있지만, 정책 모델이 아니면 경고를 남긴다 —
# 정책에서 벗어난 실행이 조용히 지나가면 **성능 수치의 출처를 알 수 없게 된다.**그 경고는 로그뿐입니다(config.py:351). 그리고 #266 이후 프로바이더를 되돌리는 것은 LLM_API_BASE 한 줄이라 실수로 갈릴 수 있는 자리입니다. 카드가 프롬프트 버전·오해 라이브러리 버전을 이미 싣는 것과 같은 이유(«이미지 세대를 잡는다»)로, 모델과 base_url 도 같은 축입니다 — 그 셋이 같이 있어야 «지금 화면의 수가 어느 조합에서 나왔나» 에 답이 됩니다.
이 PR 밖입니다 — facts 는 OpsStatusService(강희진)가 만들고 #531 로 이미 머지됐습니다. 화면은 오는 대로 그리면 되고 실제로 그렇게 하고 있습니다. 제가 이슈로 뗄까요? 값을 내는 쪽은 제 /healthz 라 이미 다 나가 있고, 서버가 두 줄 더 담으면 됩니다.
제일 좋았던 것
못 잰 값을 0 으로 접지 않습니다. … 추이 막대도 같습니다 — 못 잰 자리는 높이 0 이 아니라 바닥 점입니다. 0 으로 두면 「빠르다」로 읽힙니다.
❗폴링 자체가 실패한 회차도 한 칸 남깁니다 … 앞 판은 성공한 회차만 이어 붙여서 끊긴 구간이 이어진 것처럼 보였습니다 — 카드가 없는 동안이 아니라 복구된 뒤에 거짓이 됩니다.
둘째가 특히요. 거짓이 되는 시점이 장애 중이 아니라 복구 뒤라는 것 — 그때는 아무도 안 보고 있고 화면은 정상입니다. 제 쪽에서 #207(«0 은 무엇의 0인가»)로 반복해 밟은 것과 같은 축인데, 여기는 시간축이라 한 겹 더 어렵습니다.
|
제 리뷰의 ❗ 절을 정정합니다 — 절반이 틀렸습니다. 이 PR 에 대한 승인은 그대로입니다. 제가 이렇게 적었습니다.
// OpsStatusService.java:184 — facts 목록의 첫 줄이다
facts.add(new Fact("모델", blankToDash(report.llmModel())));제가 남는 것은 둘인데 약합니다
그래서 이슈로 안 뗍니다. 제가 만든 오독에서 일을 만드는 셈이 됩니다. 나중에 프록시나 다중 프로바이더가 실제로 생기면 그때 자리가 명확해집니다. 나머지 리뷰 내용은 그대로입니다
죄송합니다. |
|
머지했습니다. 세 분 다 감사합니다.
제 스텁 캡처에도 「모델 —」로 나옵니다(키가 없어 값이 빈 경우). 그러니 안 오는 것은 그리고 그 한 줄의 근거가 짚어 주신 그대로입니다 — «키는 있는데 다른 프로바이더를 보고 있다» 는 UP 이고 채점도 도는데 나오는 수가 우리가 잰 수가 아닙니다.
화면 쪽은 값이 오면 그대로 한 줄 늡니다.
|
`#594` 가 먼저 들어가서 `types.ts` 의 `OpsComponent` 가 main 에 있다. 그래서 그 PR 코멘트에 적은 규약대로 여기서 유니온을 같이 고친다 — 「먼저 머지되는 쪽이 나머지를 맞춘다」. openapi.yaml:1063 enum: [… , extraction] 다섯 types.ts:913 | "extraction" 추가 다섯 ← 이 커밋 이걸 무는 것이 없다는 것도 확인했다. `WebTypesMirrorContractTest` 는 필드 «이름» 만 보고 enum 값은 안 본다. 화면은 `c.id` 를 key·인덱스로만 써서 유니온이 넷이어도 tsc 가 안 깨진다. 즉 갈리는 것은 타입 한 겹이고, 그게 `#316` 이 낸 자리다 — 「유니온이 계약보다 뒤처져 있었고 tsc 는 통과했다」. `./gradlew test` 822건 통과 · `npm run build` 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`#594` 가 먼저 들어가서 `types.ts` 의 `OpsComponent` 가 main 에 있다. 그래서 그 PR 코멘트에 적은 규약대로 여기서 유니온을 같이 고친다 — 「먼저 머지되는 쪽이 나머지를 맞춘다」. openapi.yaml:1063 enum: [… , extraction] 다섯 types.ts:913 | "extraction" 추가 다섯 ← 이 커밋 이걸 무는 것이 없다는 것도 확인했다. `WebTypesMirrorContractTest` 는 필드 «이름» 만 보고 enum 값은 안 본다. 화면은 `c.id` 를 key·인덱스로만 써서 유니온이 넷이어도 tsc 가 안 깨진다. 즉 갈리는 것은 타입 한 겹이고, 그게 `#316` 이 낸 자리다 — 「유니온이 계약보다 뒤처져 있었고 tsc 는 통과했다」. `./gradlew test` 827건 통과 · `npm run build` 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#522의 web 몫입니다. 서버는#531로 9/7 에 끝나 있었고 남은 게 화면뿐이었습니다.Important
범위를 콘솔 화면 하나로 좁혔습니다(
63d91f7). 처음 올린 판에 있던/guide진입점 한 줄과tokens.css의 판정 3색 예외 문단을 뺐습니다. @hd0rable @gitIt-sehyeon 두 분 승인 뒤의 변경이고, 두 리뷰 다 그 둘을 좋게 보신 자리라 승인이 이 범위에도 서는지는 다시 봐 주세요. 귀결은 아래 「빠진 둘」에 적었습니다.이 PR 이 만드는 것은
web/src/안의 넷입니다.무엇을 그리나
DEGRADED가 이 화면의 요점입니다. UP/DOWN 둘로만 그리면 「떠 있는데 못 하는 상태」가 전부 정상으로 보입니다 — 키 없이 뜬 ai-service, 마운트가 빠진 채 뜬 server. 그 셋이 겉으로 같은 502 하나였다는 것이 이 이슈의 출발점입니다.❗못 잰 값을 0 으로 접지 않습니다
계약이
latencyMs를 nullable 로 준 이유가 「즉시 응답」과 「안 쟀다」를 가르는 것인데, 화면이 다시 뭉치면 그 뜻이 사라집니다. 실측에서 그 두 값이 실제로 같이 나옵니다.추이 막대도 같습니다 — 못 잰 자리는 높이 0 이 아니라 바닥 점입니다. 0 으로 두면 「빠르다」로 읽힙니다.
❗폴링 자체가 실패한 회차도 한 칸 남깁니다(@hd0rable 리뷰). 앞 판은 성공한 회차만 이어 붙여서 끊긴 구간이 이어진 것처럼 보였습니다 — 카드가 없는 동안이 아니라 복구된 뒤에 거짓이 됩니다. 스텁으로 4·5 회차만 연결 실패시켜 재봤습니다.
❗요청이 실패하면 직전 카드를 지웁니다
「지금」을 답하는 화면에서 남은 카드는 전부 낡은 값이고, 그중 초록이 하나라도 있으면 화면이 서버가 하지 않은 말을 합니다.
/ops/status가 안 오는 것 자체가 답이기도 합니다(API 서버가 죽었다).판정 3색을 씁니다 (
#522질문 1 — 팔레트는 제 영역입니다)새 상태색 체계를 만드는 쪽이 더 나쁘다고 봤습니다: 화면마다 다른 상태색이 생기면 읽는 사람이 색 체계를 두 벌 배워야 하고, 그러면 규칙 1 이 지키려던 「이 색을 보면 판정이다」가 오히려 옅어집니다. 세 분 다 찬성하신 방향입니다.
조건 넷을 다 만족할 때만입니다 — 제품 흐름 밖 · 라벨 병기(규칙 3) · 고객 비노출 · 값이 판정과 같은 종류(정상·주의·불가). S-08 이 집계에 3색을 안 쓰는 것과 모순이 아닙니다: 그쪽은 수치라 3색을 얹으면 「지점 성과 지표」로 읽힙니다.
화면 번호는 안 줍니다(질문 3 — @gitIt-sehyeon @yoonjiseok 합의 그대로). 파일도
Guide.tsx와 같은 모양으로Console.tsx이고SCREENS에 안 넣습니다.❗빠진 둘 — 이 PR 이 남기는 상태
①
/console이 어디서도 안 걸립니다. 닿는 길이 주소 직접 입력뿐입니다 —/upload가 지금 그 상태입니다(#406). 제품 흐름에서 링크하면 판매자에게는 전부 403 이라(ops:status:read는 ADMIN 뿐) 그쪽도 답은 아닙니다.App.tsx주석에 그 사실을 적어 뒀고, 진입점은 이 PR 밖에서 정합니다.②
tokens.css규칙 1 은 예외를 모르는 채로 남습니다. 그래서 3색을 쓰는 근거와 조건 넷이Console.tsx머리말 한 곳에만 있습니다. 두 분 다 "안 적으면 다음 사람이 「3색은 아무 데나」의 근거로 쓴다" 를 이 결정의 값으로 꼽으셨는데, 그 문단이 팔레트 파일이 아니라 화면 파일에 있는 상태입니다.Console.css머리말도 그 자리를 가리키게 고쳤습니다. 옮겨 적을지는 따로 정합니다.이미 배선돼 있던 것 — 다시 안 만졌습니다
#531이 남의 파일 셋을 같이 실어 준 덕에 이 PR 은web/src/안에서 끝납니다. 확인만 했습니다.실측
❗**
WebTypesMirrorContractTest가 새 타입을 정말 짝지었는지 변이로 확인했습니다** — 초록은 「짝을 못 찾아 0개를 대조했다」와 구분이 안 되는 자리입니다.화면은 로컬 실서버(UP·DOWN)와 스텁(DEGRADED · 측정 실패 · 403 · 연결 실패 · 추이 빈 칸)으로 다 그려 봤습니다. 런타임 오류 0건. 로컬 실서버에서 나온 것이 그대로 이 화면이 말해야 하는 것이었습니다.
리뷰에서 나온 것 — 이 PR 밖
#595. 두 분 다 계약에 신호가 필요하다는 데 동의하셨고 @gitIt-sehyeon 은Health.UNKNOWN(ⓐ) 을 지지하셨습니다. 계약에 신호가 없는 상태에서 화면이 추측하지 않습니다.#596의 다섯째 카드(extraction) → 먼저 머지되는 쪽이 나머지 유니온을 맞춥니다(@hd0rable). 이 PR 이 먼저면 그쪽에서,#596이 먼저면 여기| "extraction"한 줄입니다. 카드 수에는 안 걸립니다(auto-fit).@hd0rable @gitIt-sehyeon @yoonjiseok