feat(F-OPS-001): 추출 스냅샷 카드를 낸다 — 볼륨이 새로 나면 데모 첫 화면이 선다 (#568) - #596
Conversation
`#562` 가 목 폴백을 걷은 뒤로 「추출을 안 돌렸다」가 **화면이 서는 것**으로 나타난다. 그게 `#478` 의 목적이고 옳은데, **그날이 오기 전에 알 방법이 없었다.** 배포가 이 상태를 만들지 않는다 — 사람이 `POST /products/{id}/extract` 를 한 번 돌린 결과가 MySQL 볼륨에 남아 배포를 넘어 사는 것이다(#445). 그 볼륨이 새로 나는 날(EC2 재생성 · `down -v` · 새 리전) 데모 첫 화면이 선다. 배포 확인 목록에 줄이 하나 생겼지만(#570) 그건 사람이 돌려야 하고 「행이 있다」까지만 말한다. 이 카드는 **상태를 보는 자리**에서 같은 것을 말하고 extraction_failed 까지 가른다. ## 판정 UP 사전적재 전부에 항목이 있고 전부 extracted DEGRADED 일부만 있다 · 또는 extraction_failed 가 섞였다 DOWN 하나도 없다 — 추출을 한 번도 안 돌렸다 facts 에 상품별 항목 수를 싣는다. 「있다」만으로는 재추출이 돌았는지 못 가른다. ## 고객 데이터가 아니다 싣는 것은 상품별 항목 수와 상태 이름이고 둘 다 카탈로그 값이다 — `ops:status:read` 를 ADMIN 에 준 근거(ADR-001)가 흔들리지 않는다. 세션 수·발화는 한 건도 얹지 않는다. ❗타입 그래프로 재는 `OpsStatusHasNoCustomerDataTest` 는 이 카드를 **못 본다** — 문자열을 만들어 싣는 자리다. 그래서 응답 문면에 session·utterance·sellerId·customer 가 없는 것을 따로 잰다. ## 업로드본은 안 센다 이 카드가 답하는 것은 「데모가 도는가」이고 그 전제는 사전적재 2종이다. 업로드본은 운영자가 방금 만든 것이라 항목이 없는 것이 정상이고, 세면 그 하나에 카드가 노랑이 된다 — 정상 상태가 상시 경고로 보인다. ## 역검증 일부만 있어도 UP 으로 DEGRADED 테스트 빨강 하나도 없어도 DOWN 이 아니게 DOWN 테스트 빨강 항목 수 대신 「있음」만 싣기 UP 테스트 빨강 (facts 단정) 업로드본까지 센다 업로드본 가드 + UP 테스트 빨강 마지막이 내 javadoc 주장(「업로드본은 안 센다」)을 재는 자리다 — 주장만 적고 그물을 안 두면 다음 사람이 세는 쪽으로 고쳐도 조용하다. ## 계약 `openapi.yaml` 의 `OpsComponent.id` enum 에 `extraction` 을, 순서 설명을 다섯으로. `OpsStatusEndpointTest` 의 순서 단정도 넷 → 다섯. ❗**web 유니온은 이 PR 에 없다** — `types.ts` 의 `OpsComponent` 가 아직 main 에 없고 `#594` 가 들고 온다. 그 PR 에 알린다(먼저 머지되는 쪽이 나머지를 맞춘다). `./gradlew test` 821건 통과 · `npm run build` 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gitIt-sehyeon
left a comment
There was a problem hiding this comment.
승인합니다. 변이 다섯을 재현했고 업로드본 가드가 실제로 뭅니다 — 그 자리가 이 PR 에서 제일 값이 큽니다. 계약 쪽에 짚을 것이 하나 있는데 이 PR 밖입니다.
재현한 것
./gradlew test 821 passed (+4)
1. 일부만 있어도 UP 으로 빨강 (1)
2. 하나도 없어도 DOWN 이 아니게 빨강 (1)
3. 항목 수 대신 「있음」만 빨강 (1)
5. extraction 카드를 목록에서 뺀다 빨강 (5) ← 제가 더한 것. 순서 단정까지 같이 운다
4번(업로드본까지 센다)은 두 번 헛쳤습니다. 처음엔 무효한 변형이었고, 다음엔 extracted_risk_items 에서 업로드본을 역추적하려 했는데 그 테스트가 세우는 업로드본은 항목이 0건이라 그 경로로는 안 보였습니다. UploadedProductRepository.findAll() 로 실제로 세게 고치니 정확히 걸립니다.
★ 업로드본은 안 센다 — 세면 항목 없는 업로드본 하나에 카드가 노랑이 된다 (#568) 빨강
facts.length() 를 preloaded().size() 로 못 박은 것이 그 그물입니다 — health 만 봤으면 못 잡습니다(업로드본을 세도 withItems 는 안 늘어서 UP 이 유지될 수 있습니다). 수를 같이 잰 것이 맞습니다.
"제가
#586에서 정확히 그것을 했고 리뷰가 변이로 반증했습니다" — 그래서 이번엔 주장에 그물을 같이 두셨다는 것, 그 자리가 실제로 뭅니다.
@BeforeEach 로 비우는 것
같은 H2 를 공유하는 다른 테스트가 남긴 행이 있으면 이 카드가 다른 상태를 재고 단정이 실행 순서에 달린다 — 맞습니다. 그리고 그건 이 카드처럼 DB 전체를 훑어 판정하는 구성요소에서만 생기는 문제라, 넷째 카드까지는 없던 요구입니다.
OpsStatusHasNoCustomerDataTest 가 이 카드를 못 본다는 것
확인했습니다 — 그 그물은 타입 그래프를 훑는데 이 카드는 String 을 만들어 Fact 에 담으므로 타입으로는 안 보입니다. 응답 문면으로 따로 잰 것이 맞는 대응입니다.
assertThat(body).doesNotContain("session", "utterance", "sellerId", "customer");❗계약 쪽 — id 유니온에 그물이 없습니다 (이 PR 밖)
본문의 「확인함」을 제 손으로도 확인했습니다.
$ grep -n "enum" WebTypesMirrorContractTest.java
(0건)
필드 이름만 봅니다. 그래서 openapi.yaml 의 OpsComponent.id enum 과 types.ts 의 유니온이 갈려도 아무것도 안 웁니다.
그런데 이 레포에 이미 그 대조를 하는 자리가 있습니다 — ErrorCodeContractTest.webUnion() 이 ApiError.code enum 과 types.ts 의 ErrorCode 유니온을 값 단위로 맞춥니다. 즉 enum 하나(ApiError.code)는 지켜지고 다른 하나(OpsComponent.id)는 안 지켜지는 상태입니다.
ApiError.code 핸들러 · openapi · CLAUDE.md · 유니온 · 문면 표 · 명세서 §9 여섯 벌 대조
OpsComponent.id openapi · types.ts 유니온 0 벌 대조
#316 이 정확히 이 모양이었습니다 — "프론트가 유니온으로 들고 분기하는데 계약과 갈리면 조용히 default 로 떨어진다." 여기서는 카드 하나가 유니온에 없으면 그 카드만 화면에서 조용히 이름 없이 그려집니다.
이 PR 에서 고칠 자리가 아닙니다 — types.ts 의 OpsComponent 가 아직 main 에 없고 #594 가 들고 옵니다. 다만 "먼저 머지되는 쪽이 나머지를 맞추면 된다" 가 이번 한 번의 조정이고, 여섯째 카드가 생기는 날 같은 왕복이 또 납니다. #594 가 들어간 뒤에 그물을 얹는 이슈를 하나 떼는 게 맞아 보입니다 — 제가 떼겠습니다.
곁 하나 — DEGRADED note 가 둘을 다 말하지 않습니다 (막지 않습니다)
withItems < total
? "사전적재 N종 중 M종만 항목이 있다 …"
: "추출 실패 항목이 섞여 있다 …"둘이 동시에 참일 수 있습니다 — 한 상품은 아예 없고 다른 상품에는 실패가 섞인 상태입니다. 그때 note 는 앞만 말하고, 운영자가 그 앞을 고친 뒤에야 뒤를 봅니다.
facts 가 상품별로 「실패 2」를 들고 있어서 정보가 사라지지는 않습니다. 그리고 이 카드의 note 는 「다음에 할 일」을 주는 자리이므로 "먼저 없는 것부터" 가 실제로 맞는 순서이기도 합니다. 그래서 지금도 틀리지 않았다고 봅니다 — 다만 그 판단이 의도인지 한 줄 적어 두시면 다음 사람이 안 고칩니다.
나머지 — 이견 없습니다
itemCountFact가 0 을 「없음 — 항목 조회가 404 다」로 적은 것. "«0항목» 으로만 적으면 다음 행동이 안 정해진다" 가 정확하고, 결정 5.40 계열(부재를 숫자로만 적지 않는다)과 같은 결입니다.- 항목 수를 실은 근거. 「있다」만으로는 재추출이 돌았는지 못 가른다 —
#590이 그 확인을 기다리는 자리라 실제 수요가 있습니다. - 업로드본을 안 세는 근거. "운영자가 방금 만든 것이라 항목이 없는 것이 정상이고, 세면 정상 상태가 상시 경고로 보인다" — 상시 경고가 곧 배경이 된다는 것은
#593이 INFO 를 고른 근거와 같은 규칙입니다. isolated()로 감싼 것. 이 카드가 DB 를 훑으므로 터질 여지가 넷째 카드까지보다 큽니다 — 감싸 두면 이 카드만 DOWN 이 되고 나머지는 그대로 나갑니다.
@junseo2323 — #594 유니온 한 줄은 그쪽이 먼저 들어가면 이 PR 이, 이 PR 이 먼저면 그쪽이 맞추는 것으로 정리된 것 맞지요. 어느 쪽이든 그 조정을 사람이 한다는 사실은 위 이슈로 남기겠습니다.
junseo2323
left a comment
There was a problem hiding this comment.
서버 쪽은 이견 없습니다 — 다만 #594 가 먼저 머지돼서 약속하신 한 줄이 아직 없습니다. 그것만 채워 주시면 바로 승인합니다.
❗web 유니온이 갈립니다 — 그리고 아무도 안 잡습니다
#594 코멘트에서 직접 적어 주신 규약입니다.
먼저 머지되는 쪽이 나머지를 맞추면 됩니다. 이 PR 이 먼저면 제가
#596에서 유니온까지 같이 고치고(그때는 그 파일이 main 에 있으니),#596이 먼저면 여기에| "extraction"한 줄만 더하시면 됩니다.
#594 가 먼저 들어갔습니다(62f1ed4 · 07:45). 그런데 이 브랜치는 그 전에 갈라져 있습니다.
head 027a2e6 vs main 62f1ed4 behind: 3 · ahead: 1 · diverged
이 PR 이 건드리는 파일 openapi.yaml · OpsStatusService.java · OpsStatusEndpointTest.java
web/src/api/types.ts 없음
그래서 머지되면 이렇게 남습니다.
openapi.yaml:1063 enum: [server, database, ai-service, data-volumes, extraction] 다섯
types.ts id: "server" | "database" | "ai-service" | "data-volumes" 넷
❗이걸 무는 것이 하나도 없습니다. 직접 확인한 것을 적습니다.
WebTypesMirrorContractTest 필드 «이름» 만 본다 — enum 값은 안 본다 (그쪽이 #594 에서 확인해 주신 그대로)
tsc 내 화면은 c.id 를 key·인덱스로만 쓴다 — 유니온이 넷이어도 안 깨진다
ci.yml server_extra types.ts 는 있지만, 이 PR 이 그 파일을 안 건드리니 대조할 것이 없다
런타임은 멀쩡합니다 — components 를 그대로 map 하므로 다섯째 카드도 그려집니다(auto-fit 이라 배치도 안 걸립니다. 확인해 주신 대로입니다). 갈리는 건 타입 한 겹인데, 이게 정확히 #316 이 낸 자리입니다 — "유니온이 계약보다 세 개 뒤처져 있었고 tsc 는 통과했다".
필요한 것은 web/src/api/types.ts 한 줄입니다.
id: "server" | "database" | "ai-service" | "data-volumes" | "extraction";제 파일이라 제가 별도 PR 로 내도 되는데, 그러면 그 사이에 갈린 상태가 main 에 섭니다. 규약대로 여기서 같이 가는 게 맞다고 봅니다. main 을 머지해 오시면 그 파일이 딸려 옵니다.
서버 쪽 — 읽은 것
판정 UP 전부 extracted · DEGRADED 일부만·extraction_failed 섞임 · DOWN 하나도 없음
카드 순서 isolated(...) 다섯째 · OpsStatusEndpointTest:227 이 [4].id 를 잠근다
업로드본 제외 「항목이 없는 것이 정상」인 것을 세면 정상 상태가 상시 경고가 된다 — 맞습니다
업로드본을 안 세는 판단이 특히 맞습니다. 제 화면 쪽 근거로도 같습니다 — 노랑이 상시로 켜져 있으면 「떠 있는데 못 함」이 아무 뜻도 없어집니다(#522 가 H2 를 DEGRADED 로 안 둔 것과 같은 이유).
OpsStatusHasNoCustomerDataTest 가 이 카드를 못 본다는 것(값이 다 String 이라 타입 그래프에 안 걸린다)을 스스로 짚고 문면 검사를 따로 다신 것도 좋습니다. 그 그물의 한계를 초록으로 덮지 않은 자리입니다.
화면 쪽 — 안 고쳐도 됩니다
facts 를 순서대로 그리기만 하므로 상품이 늘어도 코드가 안 바뀝니다. 카드 폭은 minmax(min(26rem, 100%), 1fr) 라 다섯째가 셋째 줄에 혼자 서는데(2×2+1), 카드가 내용 높이(align-items: start)여서 빈 상자는 안 생깁니다.
한 가지만 봐 주세요 — facts 의 label 이 상품명이면 길어집니다. 제 표는 dt { min-width: 7.5rem } 에 값이 overflow-wrap: anywhere 라 깨지진 않고 두 줄로 접힙니다. 상품명 대신 짧은 식별자를 쓰실 거면 그게 낫고, 상품명이 맞다고 보시면 그대로 두셔도 됩니다.
`#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>
|
채웠습니다( 규약을 제가 적었으니 제가 지키는 것이 맞습니다 — 별도 PR 로 가면 그 사이에 갈린 상태가 세 가지가 아무것도 안 잡는다는 것도 같은 결과였습니다. 즉 런타임은 멀쩡한데 타입 한 겹이 갈리는 자리이고, 그게
|
8d9003f to
8d7ff8c
Compare
junseo2323
left a comment
There was a problem hiding this comment.
들어왔습니다 — 승인합니다. 요청한 한 줄이고, 주석까지 같이 고치셨습니다.
types.ts:913 id: "server" | "database" | "ai-service" | "data-volumes" | "extraction"
types.ts:935 server · database · ai-service · data-volumes · extraction ← 카드 순서 문면도 같이
openapi.yaml enum 다섯과 같다
「합친 결과」도 쟀습니다 — base 가 13 뒤입니다
#559 갈래가 걸릴 자리라 초록만 보고 넘기지 않았습니다.
origin/main...pr-596 13 뒤 · 3 앞
겹치는 파일 web/src/api/types.ts 하나 — main 쪽은 #604(감사 화면)가 건드렸다
시험 머지 충돌 0건
머지 트리에서 확인 :913 extraction 유니온 · :971 deniedByRole 둘 다 산다
같은 파일이지만 구역이 달라서(OpsComponent vs AuditSummary) 텍스트로도 의미로도 안 부딪칩니다. 리베이스 안 하셔도 됩니다.
남겨 두셔도 되는 것
facts 의 label 이 상품명이면 길어진다고 적었던 것 — 제 표가 dt { min-width: 7.5rem } + overflow-wrap: anywhere 라 두 줄로 접히고 안 깨집니다. 상품명이 맞다고 보시면 그대로 두세요. 이 PR 을 막을 자리가 아닙니다.
규약을 적은 쪽이 지키셨고, 별도 PR 로 가면 갈린 상태가 main 에 선다는 것도 같은 판단입니다.
#568의 다섯째 카드입니다. 제가 낸 이슈이고#570리뷰에서 이 자리를 제 몫으로 받았습니다.무엇이 안 보였나
배포가 이 상태를 만들지 않는 것이 요점입니다 — 사람이 한 번 돌린 결과가 MySQL 볼륨에
남아 배포를 넘어 사는 것이고(
#445), 그래서 배포가 초록이어도 이 값은 빌 수 있습니다.#570이 배포 확인 목록에 줄을 하나 넣었지만 그건 사람이 돌려야 하고 「행이 있다」까지만말합니다(그 PR 리뷰에서 제가 짚은 자리). 이 카드는 상태를 보는 자리에서 같은 것을 말하고
extraction_failed가 섞인 것까지 가릅니다.판정
이슈 본문의 표 그대로입니다. 항목 수를 싣는 이유는 「있다」만으로는 재추출이 돌았는지
못 가르기 때문입니다(
#590이 그 확인을 기다리는 자리입니다).❗고객 데이터가 아니고, 그 사실을 문면으로도 잽니다
싣는 것은 상품별 항목 수와 상태 이름이고 둘 다 카탈로그 값입니다 —
ops:status:read를ADMIN 에 준 근거(ADR-001)가 흔들리지 않습니다.
그런데
OpsStatusHasNoCustomerDataTest는 이 카드를 못 봅니다. 그 그물은 응답 타입그래프를 훑는데, 이 카드는
String을 만들어Fact에 담습니다 — 타입으로는 다String이라안 보입니다. 그래서 응답 문면에
session·utterance·sellerId·customer가 없는 것을따로 잽니다.
❗업로드본은 안 셉니다 — 그리고 그 주장을 변이로 잽니다
이 카드가 답하는 것은 「데모가 도는가」이고 그 전제는 사전적재 2종입니다. 업로드본은 운영자가
방금 만든 것이라 항목이 없는 것이 정상이고, 세면 그 하나에 카드가 노랑이 됩니다 — 정상
상태가 상시 경고로 보입니다.
javadoc 에 그렇게 적고 그물을 같이 뒀습니다. 주장만 적으면 다음 사람이 세는 쪽으로 고쳐도
조용합니다(제가
#586에서 정확히 그것을 했고 리뷰가 변이로 반증했습니다).역검증 넷
@BeforeEach로 스냅샷·업로드본을 비웁니다 — 같은 H2 를 공유하는 다른 테스트가 남긴 행이있으면 이 카드가 다른 상태를 재게 되고, 그러면 단정이 실행 순서에 달립니다.
계약
openapi.yaml의OpsComponent.idenum 에extraction, 순서 설명을 다섯으로.OpsStatusEndpointTest의 순서 단정도 넷 → 다섯.Important
❗web 유니온은 이 PR 에 없습니다.
types.ts의OpsComponent가 아직main에 없고#594가 들고 옵니다. 그리고WebTypesMirrorContractTest는 필드 이름만 보므로enum ↔ 유니온 불일치를 안 잡습니다(확인함) — 그래서 사람이 맞춰야 합니다.
#594에 알렸습니다. 먼저 머지되는 쪽이 나머지를 맞추면 됩니다.화면은
components를 그대로 map 하고 CSS 가auto-fit이라 카드 수에 안 걸립니다(확인함).검증
./gradlew test821건 통과(+4) ·npm run build통과.@gitIt-sehyeon @junseo2323 (
#594의 유니온 한 줄) @yoonjiseok