Skip to content

feat(F-OPS-001): 측정 실패를 UNKNOWN 으로 가른다 — DOWN 으로 내면 서버가 「죽었다」를 단정한다 (#595) - #629

Open
hd0rable wants to merge 1 commit into
mainfrom
feat/ops-health-unknown-595
Open

hd0rable wants to merge 1 commit into
mainfrom
feat/ops-health-unknown-595

Conversation

@hd0rable

Copy link
Copy Markdown
Member

#595 입니다. ⓐ(UNKNOWN)로 갑니다 — 든 근거 그대로입니다. 이 값이 답하는 질문이 「이 구성요소가 어떤가」인데 측정 실패는 그 질문에 답을 못 한 것이라 세 값 중 하나가 아닙니다.

❗다만 바뀌는 자리는 하나입니다

이슈가 셋을 나란히 들었는데, 재 보니 둘은 지금이 맞습니다.

OpsStatusService:120  isolated() 측정 실패     → UNKNOWN   ◀━━ 여기만 바뀐다
OpsStatusService:166  database SQLException   → DOWN      측정이 성공했다. 그 답이 「안 된다」다
OpsStatusService:177  aiService probe==null   → DOWN      같다 — 안 닿는 것을 확인한 것이다

UNKNOWN우리 측정 코드가 터진 경우 하나뿐입니다. 넓히면 반대 방향 오진이 됩니다 — 운영자가 죽은 상류를 두고 서버 로그를 뒤집니다. 그 변이도 걸어 뒀습니다(아래 ⓑ).

문면 파싱을 배제한 근거에 동의합니다

types.tsErrorCode 주석(결정 1.4)을 인용하신 것이 정확합니다. 그리고 여기 문면은 oneLine(e.toString()) 을 품고 있어 예외 종류에 따라 앞부분이 달라집니다 — 화면이 그걸 물면 서버 로그 문구가 화면의 계약이 됩니다.

같이 고친 것

contracts/openapi.yaml    enum: [UP, DEGRADED, DOWN, UNKNOWN] + description
core/ops/OpsStatus.java   Health enum + javadoc
core/ops/OpsStatusService isolated() 만 UNKNOWN
web/src/api/types.ts      유니온 + 주석
web/src/pages/Console.tsx HEALTH_LABEL 에 "상태 못 잼"

유니온과 라벨을 제가 같이 고친 이유HEALTH_LABELRecord<OpsHealth, string> 전체 맵이라 유니온만 늘리면 tsc 가 막기 때문입니다 — 문면 표와 같은 구조라 계약과 같은 커밋에 있어야 빌드가 섭니다. 라벨 문구는 제 것이 아니니 바꾸실 게 있으면 그대로 고쳐 주세요.

Console.css 는 안 건드렸습니다 — 그쪽 판단이 필요합니다

지금 상태로는 UNKNOWN 카드가 왼쪽 띠가 기본색이고 칩도 기본 스타일입니다.

.cn__card[data-health="UP"]       { border-left-color: var(--green); }
.cn__card[data-health="DEGRADED"] { border-left-color: var(--yellow); }
.cn__card[data-health="DOWN"]     { border-left-color: var(--red); }
/* UNKNOWN 없음 → var(--border-strong) 기본값 */

측정 실패가 DOWN 보다 조용해 보이면 안 됩니다 — 그러면 아무도 로그를 안 봅니다. 그런데 중립색 토큰(--gray/--gray-bg 류)이 없고, 이 파일이 스스로 「색각 이상에서 적/녹이 같아진다(토큰 규칙 3)」를 적어 둔 자리라 색을 제가 고르지 않았습니다. 계약 description 에는 "화면은 이 값을 DOWN 보다 약하게 그리지 않는다" 로 의도만 적어 뒀습니다.

역검증

변이 결과
isolated()DOWN 으로 되돌린다 1 failed
안 닿는 것도 UNKNOWN 으로 넓힌다(과잉) 2 failed ← 방향만 바뀐 오진을 막는다
계약에서 UNKNOWN 을 뺀다(유니온은 그대로) 이 브랜치에서는 0 failed

세 번째가 그대로 #626(계약 enum ≡ web 유니온) 이야기입니다. 그 브랜치를 합쳐서 다시 재 봤습니다.

#626 을 합친 상태에서 같은 변이   → WebUnionsMirrorContractEnumsTest 빨강
둘을 합친 전체                   → BUILD SUCCESSFUL (충돌 없음)

OpsComponent.id 로 시작한 그 이슈가 같은 스키마의 옆 칸에서 바로 값을 냈습니다.

검증

server 832건 · 실패 0 · skip 0   ·   web 빌드 통과

Refs #595 · #522

…595)

운영 콘솔이 「상류가 죽었다」와 「측정이 터졌다」를 응답만 보고 구별할 수 없었다.

  health          DOWN     DOWN
  latencyMs       null     null
  facts           []       []
  note            문면만 다르다   ← 이것으로 가르려면 화면이 문면을 파싱해야 한다

이 레포는 그 방식을 이미 한 번 버렸다(types.ts ErrorCode 주석 · 결정 1.4):
"문면 파싱으로 가르면 서버 문구가 바뀔 때 조용히 깨진다". 게다가 이 문면은 e.toString()
을 품고 있어 예외 종류에 따라 앞부분이 달라진다 — 화면이 그걸 물면 서버 로그 문구가
화면의 계약이 된다.

  상류가 죽었다   → 그 구성요소를 본다      DOWN     (측정은 성공했고 그 답이 「안 된다」다)
  측정이 터졌다   → **서버 로그**를 본다     UNKNOWN  (그 구성요소는 멀쩡할 수 있다)

❗바뀌는 자리는 isolated() 하나다. 이슈는 셋이 같은 모양이라고 적었지만, DB 의
SQLException 과 ai-service probe==null 은 **측정이 성공한 것**이고 그 답이 DOWN 이라
맞다. UNKNOWN 은 우리 측정 코드가 터진 경우 하나뿐이다.

- latencyMs 를 못 잰 자리에 0 이 아니라 null 을 주는 판단과 같은 모양이다 — "즉시 응답"
  과 "안 쟀다" 를 같게 만들지 않는다
- 계약·유니온·라벨을 같이 고쳤다. HEALTH_LABEL 이 Record<OpsHealth, string> 전체 맵이라
  유니온만 늘리면 tsc 가 막는다(문면 표와 같은 구조)
- 화면 스타일(Console.css)은 안 건드렸다 — 아래 참조

역검증:
  isolated() 를 DOWN 으로 되돌린다              1 failed
  안 닿는 것도 UNKNOWN 으로 넓힌다(과잉)          2 failed  ← 방향만 바뀐 오진을 막는다
  계약에서 UNKNOWN 을 뺀다(유니온은 그대로)        이 브랜치에서는 0 failed
    → #626(계약 enum ≡ web 유니온)을 합치면 빨강. 실측으로 확인했고 둘을 합쳐도 전체 초록

server 832건 · 실패 0 · skip 0 · web 빌드 통과

Refs #595 #522

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hd0rable hd0rable added 계약 모듈 간 계약 — contracts/, openapi.yaml, 스키마 server Spring (:8000) web Vite (:5173) labels Sep 14, 2026
@github-actions github-actions Bot added 리뷰대기: 오준서 오준서 이 배정됐고 아직 아무것도 제출하지 않았다 리뷰대기: 정세현 정세현 이 배정됐고 아직 아무것도 제출하지 않았다 labels Sep 14, 2026

@junseo2323 junseo2323 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

승인합니다. ⓐ 로 간 판단과 바뀌는 자리를 하나로 좁힌 것이 이 PR 의 핵심이고, 그 좁힘이 실제로 지켜지는지를 반대 방향 변이로 확인했습니다. 그리고 맡기신 Console.css 는 제 자리이므로 아래 ④ 에서 값까지 정합니다.

① 변이 넷

기준선                                  server 832건 BUILD SUCCESSFUL · web 빌드 통과

ⓐ isolated() 를 DOWN 으로 되돌린다        1 failed   "❗측정이 터지면 UNKNOWN 이다"
ⓑ 안 닿는 것도 UNKNOWN 으로 넓힌다         2 failed   "★ 안 닿는 것은 여전히 DOWN 이다"
                                                  + "★ 안 닿으면 DOWN 이고 latencyMs 가 null 이다"
ⓒ 계약에서만 UNKNOWN 제거(유니온 유지)      0 failed   ❗이 브랜치에서는 안 잡힌다
ⓓ HEALTH_LABEL 에서 UNKNOWN 만 뺀다        tsc TS2741  Property 'UNKNOWN' is missing …

ⓑ 가 인 것이 좋습니다 — 넓히는 변이는 「못 잼」쪽으로만 흐려지는 게 아니라 latencyMs 규약까지 같이 흐립니다. 본문이 "방향만 바뀐 오진" 이라 부른 것이 그 자리입니다.

ⓓ 는 유니온과 라벨을 같은 커밋에 둔 이유를 그대로 증명합니다. Record<OpsHealth, string> 전체 맵이라 tsc 가 막는 것이 맞고, 그래서 제 파일이어도 이 PR 에 있어야 빌드가 섭니다. 라벨 문구("상태 못 잼")도 그대로 쓰겠습니다 — 「안 됨」과 글자 수가 같아 칩 폭이 안 튀고, 가리키는 곳이 구성요소가 아니라 로그라는 것이 문면에 있습니다.

② ⓒ 는 #626 이야기가 맞습니다 — 합쳐서 쟀습니다

이 브랜치 + origin/feat/web-union-enum-mirror-597   merge 충돌 0
같은 변이(계약에서만 UNKNOWN 제거)                    WebUnionsMirrorContractEnumsTest  FAILED

본문 주장 그대로입니다. 덧붙이면 이 값집합이 계약에서 유일해서 잡히는 것입니다 — #626 리뷰에 적었지만 값집합이 두 자리 이상인 유니온(등급·세션 상태·상품유형 등 11개)은 아직 안 걸립니다. OpsComponent.health 는 유일한 쪽이라 저쪽이 들어오면 이 자리는 실제로 지켜집니다.

③ 네 번째 값이 생길 때의 함정 — 소비처를 전수로 봤습니다

enum 에 값을 더하면 보통 어딘가의 롤업이 조용히 기본 갈래로 떨어집니다(「DOWN 이 하나라도 있으면 전체 DOWN」 같은 자리). 그래서 Health 를 읽는 곳을 전부 열어 봤습니다.

server   OpsStatusService 안에서 만들기만 한다 — 컴포넌트 건강을 합치는 코드가 없다
web      Console.tsx:310/314  data-health 속성과 HEALTH_LABEL 조회뿐 — 분기·집계 없음

롤업이 없어서 이 PR 이 늘린 값이 조용히 흡수될 자리가 없습니다. 이건 지금 구조가 그런 것이라, 나중에 「전체 상태」 배지를 만드는 사람이 UNKNOWN 을 UP 쪽으로 접지 않게 그 자리에서 다시 봐야 합니다.

Console.css — 제 자리이므로 여기서 정합니다

넘기신 판단 맞습니다. 판정 3색을 쓰지 않는 것이 맞고(저 셋은 "그 구성요소가 어떤가" 의 답인데 UNKNOWN 은 답을 못 낸 것입니다), DOWN 보다 조용하면 안 된다는 것도 맞습니다. 그래서 «새 색을 만들지 않고 무게만 맞추는» 쪽으로 갑니다 — 네 번째 안료를 들이는 것은 #601(토큰 규칙 1 이 예외를 모른다) 자리이지 이 PR 자리가 아닙니다.

/* UNKNOWN — 「못 잼」. 판정 3색을 안 쓴다(저 셋은 «어떤가» 의 답이고 이건 답이 없는 것이다).
   대신 **무게를 DOWN 과 같게** 둔다 — 잉크색 4px 은 빨강과 같은 굵기·비슷한 어두움이라
   카드 줄에서 먼저 눈에 든다. 색각 이상에서도 갈리는 것은 칩 글자(「상태 못 잼」)가 맡는다. */
.cn__card[data-health="UNKNOWN"] { border-left-color: var(--text-muted); }
.cn__chip[data-health="UNKNOWN"] { background: var(--surface-sunken); color: var(--text); }

.cn__note 는 손댈 것이 없습니다. 기본값이 이미 --surface-sunkenfont-weight: 600 이라 DOWN 의 note 와 무게가 같습니다 — 색조만 다릅니다. 두 줄이면 끝입니다.

넣어 주셔도 되고(문면·값은 소유자로서 여기서 확정했습니다), 이 PR 을 계약·서버로 깨끗하게 두고 싶으시면 머지 뒤 제가 #601 과 같이 내겠습니다. 어느 쪽이든 승인은 이대로입니다.

@github-actions github-actions Bot removed the 리뷰대기: 오준서 오준서 이 배정됐고 아직 아무것도 제출하지 않았다 label Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

server Spring (:8000) web Vite (:5173) 계약 모듈 간 계약 — contracts/, openapi.yaml, 스키마 리뷰대기: 정세현 정세현 이 배정됐고 아직 아무것도 제출하지 않았다

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants