Conversation
`#609` 가 ❗지금으로 표시한 칸이다. 9/6 에 요청이 왔고 **5일째 답이 없던 것이 내 쪽**이다. 공개 의무(기획서 5절)가 요구하는 것은 「기준이 문서로 존재하고 감사·심사가 볼 수 있다」이고, 그 «볼 수 있다» 를 성립시키는 것이 화면이다. 그런데 화면은 ai-service 를 직접 못 부른다 (내부망 전용) — 그래서 서버가 읽기 전용으로 중계한다. GET /products/rubrics[?productType] → /internal/rubrics GET /products/rubrics/{itemId} → /internal/rubrics/{item_id} ## 아무것도 안 바꾼다 승인 산출물은 `app/rubrics/*.yaml` 파일이고 승인은 git 커밋이다(결정 #475 ⓐ · 서버 stateless). 이 경로는 그 파일을 보여줄 뿐이다. approve 는 별건(#609 의 데모 뒤 칸). ## 판매 직원에게 열지 않는다 — 정책 + HTTP 둘로 `rubric:read` 는 COMPL·MGR·ADMIN 뿐이다(#610). 정책 한 줄만으로는 다음 사람이 역할을 더해도 조용하니 `seller-01` 로 403 을 재는 단정을 넣었다 — #610 리뷰에서 약속한 그물이다. ## 404 를 404 로 넘긴다 루브릭이 없는 항목은 **정상 상태**다(recommended 는 루브릭이 없고 채점 대상도 아니다 — 결정 10.1 · #435). 502 로 뭉치면 화면이 「AI 서비스 장애」로 읽고 「기준이 없다」와 「기준이 비어 있다」가 같아진다 — #556 이 고친 것과 같은 부류다. ## total 을 접지 않는다 ai-service 가 「필터 전 전체 개수」를 같이 내는 이유가 *"걸러진 목록만 보이면 이게 전부로 읽힌다"* 이고, 경계에서 버리면 그 뜻이 사라진다. R-00 이 분모를 지키는 것과 같은 결이다. ## 역검증 — 둘이 처음엔 초록이었다 정책에 SELLER 를 되살린다 403 단정 빨강 @PreAuthorize 를 뗀다 403 단정 빨강 404 를 502 로 뭉친다 ❗처음엔 초록 → 클라이언트 테스트를 넣어서 빨강 total 을 목록 길이로 접는다 ❗처음엔 초록 → 목록 길이와 total 을 다르게 둬서 빨강 셋째는 배선 테스트가 `aiServiceClient` 를 스텁해서 404 번역을 안 지나던 것이고, 넷째는 목록 길이와 total 을 같게(0·0) 둬서 접어도 통과하던 것이다. ## 남의 파일 한 줄 `web/docker-entrypoint.d/15-demo-mode.sh` 에 지도 한 줄을 더했다 — `DemoModeAccountMapTest` 가 요구한다(없으면 개방 모드에서 SELLER 계정이 주입돼 전부 403). `#609` ③ 이 그 칸을 오준서 몫으로 적어 뒀고 내용도 그 코멘트대로 ADMIN 이다. `~^/api/products/` 로 넓히지 않은 이유도 같이 적었다. `./gradlew test` 831건 통과 · `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.
승인합니다. 제 #610 위에 정확히 올라왔고, 약속하신 seller-01 403 그물도 들어 있습니다. 역검증 둘을 제 손으로 재현했습니다.
정책에 SELLER 를 되살린다 ProductAccessWiringTest FAILED + AccessPolicyTest FAILED ❗둘 다
@PreAuthorize 를 뗀다 ProductAccessWiringTest FAILED
server 전체 BUILD SUCCESSFUL
첫 변이에서 두 층이 같이 빨개지는 것이 이 PR 의 값입니다. #610 만 있을 때는 정책 층 하나였고, 그 사이가 비어 있다고 제가 적었는데 지금 메워졌습니다.
❗한 칸이 비어 있습니다 — 제 몫이라 바로 채웠습니다 (#614)
이 PR 이 머지되는 순간부터 명세가 「감사 대상」이라 정한 경로가 기록 없이 뜹니다.
docs/functional-spec-v1.2.md:462 「COMPL·MGR·ADMIN 읽기 + 감사 대상」
AuditInterceptor.java:63 audited 에 없으면 조용히 넘긴다 (return)
rbac_policy.yaml audited rubric:read 없음 — #610 에서 일부러 비웠다
그 구간에 알파 배포가 나가면 채점 정답표를 열어 본 사실이 아무 데도 안 남습니다. 로그 0건이 「아무도 안 봤다」로 읽히는 것은 이 레포가 unreachable 을 따로 세는 이유와 같은 부류입니다.
#614 를 이 브랜치 위에 쌓았습니다. 한 PR 로 합칠 수 없어서(그쪽은 api/, 제 쪽은 rbac_policy.yaml) 두 머지 사이의 틈을 커밋 하나로 줄이는 쪽을 택했습니다. 정책 한 줄 + 테스트 하나이고, 프록시가 있으니 everyAuditedActionIsReachable 이 통과합니다 — #610 에서 «넣으면 빨갛다»를 보였고 거기서 «있으니 통과한다»를 보입니다.
Important
스택이 셋이 됐습니다: #610(정책) ← #612(프록시) ← #614(감사).
이 PR 을 머지하실 때 --delete-branch 를 쓰지 말아 주세요 — #614 가 닫힙니다.
제 영역에서 확인한 것
rbac_policy.yaml 안 건드렸다 ✅ (@PreAuthorize 가 action 이름만 참조 — 규칙대로)
canAggregate 사용 맞다. 루브릭은 세션·상품에 안 매이므로 소유자 없는 대상이다
P3 (고객 텍스트 경로) 무관하다 — 나가는 것이 productType 뿐이고 고객 발화가 없다
15-demo-mode.sh 한 줄 #494 9/7 확정본대로 ADMIN. DemoModeAccountMapTest 가 요구한다
~^/api/products/ 로 넓히지 않은 이유를 적어 두신 것이 좋습니다 — 그 아래 product:read 는 SELLER 도 있어서 default 여야 하는데, 넓히면 판매자가 상품 목록을 못 봅니다. 지도 판정 둘(#263)이 그래서 둘인 것이고요.
404 와 total — 「처음엔 초록이었다」를 적으신 것
이 PR 에서 제일 값이 큰 문단입니다. 역검증이 통과한 것이 결함을 못 찾은 것이 아니라 테스트가 그 갈래를 안 지난 것이었다는 기록이라서요.
404 를 502 로 뭉친다 배선 테스트가 aiServiceClient 를 스텁해서 번역을 안 지났다
total 을 길이로 접는다 목록 길이와 total 을 같게(0·0) 둬서 접어도 통과했다
둘째가 특히 그렇습니다 — 단정의 두 값이 우연히 같으면 그 단정은 아무것도 안 잽니다. 1건 · total 17 로 바꾼 것이 필터를 건 실제 모양이기도 해서 두 번 맞습니다.
계약 — 이견 없습니다
status: draft 에 "채점은 지금 이 값을 안 본다(#609 ① 미결) — 화면이 「확정됨」으로 그리면 안 된다" 를 적으신 것이 맞습니다. #609 가 그 칸을 ①의 빈칸으로 열어 뒀는데, 계약이 먼저 나가므로 화면이 그 전에 잘못 그릴 수 있는 자리입니다.
u1Requires(개수가 아니라 문턱) · unlinkedUntil(null 과 빈 배열이 다르다) 둘도 같은 성격이고, 셋 다 읽는 쪽이 틀리기 쉬운 것을 필드 설명에 둔 것이라 자리가 맞습니다.
경로 변수를 계약에서 {id} 로 통일한 것도 OpenApiPermissionSyncTest 가 자리로 맞추므로 문제없습니다.
#609 를 안 닫는 것
맞습니다 — 추적 이슈이고 ② 세 칸 중 하나입니다. #614 가 들어가면 ② 의 「지금」 칸 셋(정책·프록시·감사)이 다 닫히고, 남는 것은 propose 프록시와 데모 뒤 approve 입니다.
yoonjiseok
left a comment
There was a problem hiding this comment.
승인합니다. /internal/rubrics 소유자로서 계약·역직렬화·404 규약을 실물과 대조했고 전부 맞습니다. 아래는 전부 막지 않는 것이고, 셋은 제 몫입니다.
실물과 대조했습니다 — 양쪽이 손으로 쓴 것이라
픽스처와 Java 레코드가 둘 다 사람이 쓴 것이라 같이 틀릴 수 있는 자리입니다. 그래서 ai-service 를 실제로 띄워 응답을 받았습니다.
GET /internal/rubrics
키 9개 (snake) item_id · product_type · name · status · required_elements
u1_requires · misconception_conditions · related_misconceptions · unlinked_until
total 17 · 목록 17
GET /internal/rubrics?product_type=ELS 목록 10 · total 17 ← 필터 전 분모, 접히지 않음
GET /internal/rubrics/NOPE 404 {"detail": "루브릭이 없다: NOPE"}
AiServiceClientTest 픽스처와 키·모양·404 본문이 전부 같습니다. Rubric 레코드 9필드가 제 RubricView 9필드와 1:1 이고, unlinkedUntil 만 null 을 보존하신 것도 맞습니다 — #493 리뷰에서 정세현이 짚어 제가 is not None 으로 둔 그 규약입니다.
변이를 제 쪽에서도 걸었습니다
"둘이 처음엔 초록이었다" 고 적으신 자리 둘을 다시 재봤습니다.
total 을 list.rubrics().size() 로 접는다 1 failed (supervisorsAndAdminReadRubrics)
404 갈래를 죽인다 (if (false)) 1 failed (aMissingRubricStaysNotFound)
컨트롤러에서만 404 를 502 로 뭉친다 2 failed ← 클라이언트 층과 배선 층이 각각 문다
셋째는 제가 «이음매가 안 덮였나» 싶어 건 것인데 이미 aMissingRubricIsNotFoundNotAnOutage 가 그 자리를 잡고 있었습니다. 클라이언트(404→NoSuchElementException)와 배선(→HTTP 404 + NOT_FOUND)이 다른 층에서 각각 물립니다. #548·#551 계열에서 계속 비어 있던 «타입은 늘었는데 HTTP 는 안 바뀐다» 가 여기서는 안 생깁니다.
./gradlew test 831 · 실패 0 · skip 0 — 본문 숫자와 같습니다(JDK 17).
① ❗지금 이 경로가 내주는 17종 중 7종이 draft 입니다
계약 설명에 "화면이 draft 를 「확정됨」으로 그리면 안 된다" 를 적어 주신 것이 맞는데, 숫자를 같이 두면 ③(화면)이 이걸 곁가지로 안 읽습니다.
confirmed 10 ELS 전부
draft 7 VAR-EARLY-SURRENDER-RATIO · VAR-FEE-DEDUCTION · VAR-NOT-BANK-SAVINGS
VAR-PARTIAL-DEPOSIT-INSURANCE · VAR-PERFORMANCE-LINKED
VAR-PRINCIPAL-LOSS · VAR-SURRENDER-BELOW-PREMIUM
변액을 열면 10종 전부가 draft 입니다. 공개 의무 화면이 "이것이 우리 채점 기준입니다" 로 보여주는 첫 화면에서 변액 쪽은 전부 미확정인 셈인데, 지금 그걸 가르는 것은 이 status 문자열 하나뿐입니다.
❗그리고 채점은 그 값을 안 봅니다 — 그게 #609 ① 이고 제 칸입니다. 여기서 막을 일이 아니라 제가 결론을 내야 하는 자리입니다. @junseo2323 님 화면이 이 위에 서니 draft 를 시각적으로 가르는 것만 먼저 부탁드립니다(제 ⓑ 결론이 뭐가 되든 그 표시는 필요합니다).
② relatedMisconceptions·misconceptionConditions 를 required 로 두는 게 맞습니다
이 PR 이 unlinkedUntil 에서 지킨 구별이 옆 필드에서 풀립니다.
required: [itemId, productType, name, status, requiredElements, u1Requires]
# ↑ misconceptionConditions · relatedMisconceptions 가 빠져 있다제 스키마는 그 둘을 항상 냅니다(default_factory=list — 없으면 빈 배열). 계약이 optional 이면 웹은 undefined 도 처리해야 하고, 그러면 "관련 오해가 없다(빈 배열)" 와 "필드가 안 왔다" 가 화면에서 같아집니다. unlinkedUntil 의 존재 이유가 정확히 "빈 것과 없는 것을 가른다"(#284·#396)라, 그 옆 필드가 그 구별을 못 하면 짝이 안 맞습니다.
빈 배열로 항상 오는 것이 실물이니 required 에 넣어 주시면 계약이 실물과 같아집니다. (unlinkedUntil 은 지금처럼 nullable optional 이 맞습니다 — 그건 정말 «안 적음» 이 있는 필드입니다.)
③ unlinkedUntil 은 지금 17종 전부 null 입니다 — 계약은 유지가 맞습니다
#284 (a) 로 VAR-PARTIAL-DEPOSIT-INSURANCE 의 선언이 조건 셋이 다 차서 걷혔고(9/5), 그 뒤로 이 필드를 쓰는 루브릭이 하나도 없습니다. 즉 화면이 non-null 을 만나는 경로가 지금은 없습니다.
지우자는 말이 아닙니다 — 그 상태가 «해당 없음» 이 아니라 «지금은 다 걸렸다» 이고, 새 오해 유형이 늦게 오면 다시 생깁니다. 다만 ③ 이 화면을 만들 때 그 갈래가 실데이터로는 안 나온다는 것을 알고 계셔야 눈으로 확인 못 한 코드가 남는 것을 압니다.
④ 루브릭 하나가 깨지면 이 경로가 통째로 502 입니다 (#475 ④ · 제 몫)
@lru_cache(maxsize=1)
def _all() -> dict[str, Rubric]: # 파일 하나가 파싱 실패하면 여기서 던진다#475 ④ 로 제가 낸 것인데 이 PR 이 그 결함이 처음 보이는 자리를 만듭니다. 그때 화면에 뜨는 것은 「AI 서비스 장애」이고, 실제로는 "루브릭 파일 하나에 오타가 있다" 입니다 — 이 PR 이 404 에서 정확히 갈라 낸 그 혼동의 다른 판입니다.
여기서 고칠 일은 아니고 제 쪽 문제입니다(승인이 PR 이어야 하는 근거이기도 합니다). 다만 ②(propose 프록시) 를 내실 때쯤이면 제가 답을 갖고 있는 게 맞겠습니다.
⑤ 이 경로가 보여주는 것은 git HEAD 가 아니라 도는 프로세스의 캐시입니다
_all() 이 @lru_cache(maxsize=1) 이라, 파일이 커밋돼도 프로세스가 새로 뜨기 전에는 안 바뀝니다. 배포가 매번 컨테이너 교체라 실무상 문제가 없다는 것은 #475 ⑤ 로 제가 쟀습니다.
그런데 공개 의무 문면에서는 이게 오히려 정확한 쪽입니다 — 화면이 보여야 하는 것은 "레포에 있는 기준" 이 아니라 "지금 채점에 쓰이고 있는 기준" 이니까요. 승인이 git 커밋(#475 ⓐ)인 구조에서 그 둘이 갈리는 구간(머지됨 · 아직 배포 전)이 실제로 있습니다. 화면 문면을 「현재 적용 중인 채점 기준」으로 잡아 주시면 그 구간에서도 거짓말이 안 됩니다.
좋았던 것
total을 경계에서 안 접은 것. 제가 저쪽에 그 값을 넣은 이유를 그대로 옮겨 적어 주셨고, 무엇보다 목록 길이와 total 을 일부러 다르게 둔 것(1 vs 17)이 핵심입니다 — 같게 두면 접어도 초록입니다. 실측으로 그걸 찾으신 게 이 PR 에서 제일 좋습니다.- 404 를 404 로 넘긴 것. 「기준이 없다」와 「기준이 비어 있다」를 가르는 것이
recommended항목(결정 10.1)에서 실제로 필요합니다. 제가get_rubric에 "빈 루브릭을 지어내지 않는다" 로 적어 둔 것을 정확히 같은 뜻으로 받으셨습니다. - 정책 + HTTP 두 겹.
#610리뷰에서 약속하신seller-01403 단정이 들어왔습니다. 정책만 두면 다음 사람이 역할을 더해도 조용한데, 이제 두 줄이 같이 빨개집니다. u1Requires를 계약에 박고 응답 단정까지 건 것.#450이 정확히 그 결함이었습니다 — 요소 목록만 보이면 「전부 말해야 한다」로 읽힙니다.
#610 은 제 변경요청(SELLER)이 74847d3 으로 해소된 것을 확인했고, 그쪽에 재리뷰를 따로 올리겠습니다. 그게 머지되면 base 를 main 으로 돌리시면 됩니다.
정세현이 잡았다. ELS 10 · 변액 7 인데 둘을 뒤섞어 적었고, 실제는 **변액 7종 전부**가
draft 다.
ELS 10종 · draft 0종 데모가 도는 상품이다
변액 7종 · draft 7종 전부다
## 사실이 더 세다
「일부가 검토 전」과 「전부가 검토 전」은 화면에서 다른 이야기다. 항목마다 갈리면 배지가
맞고, **상품 전체가 그렇다면 상품 단위 문면**이 맞을 수 있다 — `#609` ① 의 ⓑ
(`Judgment.rubric_status`)를 정할 때 이 차이가 걸린다. `#612` 가 머지되면 화면에서 변액을
열었을 때 **예외 없이 전부** draft 다.
## 숫자를 주석에만 적어서 낡았다 — 그 문장을 검사로 만든다
`test_the_split_is_by_product_not_by_item` 이 상품별 구성을 못박는다. `_DRAFT_IN_SCORING`
집합만으로는 이 사실이 안 잡힌다 — **변액 루브릭이 confirmed 로 하나 늘어도 그 집합은
그대로**이고 「전부」라는 주석만 조용히 거짓이 된다.
역검증: `VAR-FEE-DEDUCTION` 을 confirmed 로 바꾸면 4 failed.
ai-service 1138 passed · skip 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
junseo2323
left a comment
There was a problem hiding this comment.
승인합니다. 저쪽 실물과 자 그대로 맞는지 대조했고, 그물 다섯을 변이로 하나씩 끊어 봤습니다 — 다섯 다 빨개집니다. 제 파일(15-demo-mode.sh)을 건드린 것도 강제였음을 확인했습니다(④). 막지 않는 관찰 두 개는 맨 아래 남깁니다.
① 계약 ≡ ai-service 실물 — 자 그대로입니다
routes.py:492 @router.get("/rubrics") router prefix=/internal → /internal/rubrics
routes.py:507 @router.get("/rubrics/{item_id}") RubricNotFound → HTTPException(404)
schemas.py:688 RubricView 9개 필드 · unlinked_until: list[str] | None = None
schemas.py:715 RubricListResponse rubrics · total
routes.py:503 total=len(every) ❗picked 가 아니라 every — 「필터 전」이 저쪽 실물에서도 참이다
Rubric 레코드 9개 필드·u1_requires·unlinked_until 의 null 보존까지 저쪽과 1:1 입니다. total 이 필터 전이라는 주장은 저쪽 코드에서도 참입니다(len(every)).
② 변이 다섯 — 하나씩 끊어 봤습니다
기준선 server 전체 BUILD SUCCESSFUL · 831건
① total 을 목록 길이로 접는다 ProductAccessWiringTest FAILED
② 404 갈래를 죽여 502 로 뭉친다 AiServiceClientTest FAILED
③ 목록에서 @PreAuthorize 를 뗀다 ProductAccessWiringTest FAILED
AccessControlWiringTest FAILED ← 두 층이 같이 문다
④ nginx 지도 한 줄을 지운다 DemoModeAccountMapTest FAILED
⑤ openapi 에서 두 경로를 지운다 OpenApiPermissionSyncTest FAILED
①②는 본문이 "처음엔 초록이었다" 고 적은 자리인데, 지금은 둘 다 뭅니다. ③은 한 층이 아니라 두 층이 물어서, 어노테이션을 떼는 변이가 배선 대조에서도 걸립니다.
③ ④ — 제 파일을 건드린 것은 강제였습니다
15-demo-mode.sh 는 #609 ③ 이 제 칸으로 적어 둔 파일이라 그 한 줄만 따로 봤습니다.
그 줄을 지운다 → DemoModeAccountMapTest "❗개방 모드에서 주입되는 계정이 그 엔드포인트의 action 을 만족한다" FAILED
없으면 빨간 줄이라 이 PR 안에 있는 것이 맞습니다. 내용도 맞게 봤습니다.
- 순서 —
map의 정규식은 나온 순서로 재는데, 앞선~^/api/products/(documents|[^/]+/extract)$가$로 닫혀 있어/api/products/rubrics를 안 먹습니다. 새 줄까지 내려옵니다. - 계정 —
admin은rubric:read의[COMPL, MGR, ADMIN]안입니다.compl도 되지만#4949/7 이 ADMIN 으로 정했고 제가 그 코멘트를 쓴 쪽입니다. ~^/api/products/로 안 넓힌 것 — 넓히면product:read(SELLER 있음)가 admin 으로 올라가 감사에 판매자 조회가 admin 으로 남습니다. 바로 위ops/status주석이 짚어 둔 함정과 같은 자리고, 그 이유를 줄 옆에 적어 두신 것이 좋습니다.
④ 배포에서 실제로 서는가
403 주장이 목 개발 스위치 뒤에서만 참인 것 아닌가를 봤습니다.
AccessGuard @Value("${sphinx.security.enforce:false}") 기본값 false
application-prod.yml:83 enforce: true ❗배포는 켜져 있다
배포에서 정책이 실제로 돕니다. 나머지 배선도 기존 형태 그대로입니다 — canAggregate 는 바로 위 product:read 와 같은 모양이고, NoSuchElementException → 404 NOT_FOUND 는 GlobalExceptionHandler:41 의 기존 매핑입니다(새 매핑을 만들지 않았습니다).
막지 않는 관찰 둘
ⓐ /internal/rubrics/{id} 의 404 는 두 가지입니다. 같은 파일 parseFailure() 주석이 이미 그 규칙을 적어 뒀습니다 — "다른 내부 경로의 404 는 «라우트가 없다» 이고, 그걸 문서 문제로 읽으면 반대로 오진한다". 여기도 대칭으로 참입니다: ai-service 가 그 라우트를 모르는 판(서버만 새 이미지)이면 FastAPI 기본 404 가 나오고, 화면은 **모든 항목에 「기준이 없다」**를 그립니다 — 이 PR 이 막으려던 혼동의 정확한 반대쪽입니다.
루브릭 없음 {"detail":"루브릭이 없다: ELS-..."} routes.py:515
라우트 없음 {"detail":"Not Found"} FastAPI 기본 — 커스텀 핸들러 없음(main.py 확인)
가르는 값이 본문에 있고 errorBody(resp) 도 이미 읽고 있어서 비용이 크진 않습니다. 다만 parse 가 이미 같은 잔여 위험을 안고 머지된 자리라 이 PR 에서만 요구하는 것은 결이 안 맞습니다 — 막지 않겠습니다. 원하시면 제가 이슈로 떼어 두 자리를 같이 다루겠습니다(그 라우트는 9/6 1df3139 에 들어갔으니 지금 알파는 어긋나 있지 않습니다).
ⓑ productType 이 enum 대조 없이 지납니다. 계약은 [ELS, VARIABLE_INSURANCE] 인데 컨트롤러는 문자열을 그대로 넘기고, 저쪽은 product_type in (None, r.product_type) 이라 오타가 오면 rubrics: [] · total: 17 이 나갑니다. 「그 유형에 기준이 없다」로 읽히는 값이지만 total 이 분모를 들고 있어서 화면이 「0 / 17」로 그릴 수 있고, 중계가 얇은 편이 나은 자리이기도 해서 이것도 막지 않습니다.
스택
#610 은 방금 승인했습니다. 이게 머지되면 base 를 main 으로 돌리고, 그 뒤 #614(감사 한 줄) 순서입니다.
리뷰에서 지적된 ①②③ 을 고쳤고, 재현하는 과정에서 같은 절의 네 번째 결함을 찾았다. ① 조건을 「리뷰 라벨 없음」 으로 좁혔다. pr-review.yml:297 이 관리하는 것은 리뷰 라벨 셋뿐이고 분류 라벨은 손대지 않는다. 「라벨 없음」 으로 두면 #612 처럼 분류 라벨만 달린 PR 이 전원 승인돼도 영원히 안 들어간다. ② 판정의 정본을 라벨에서 「소유자 승인 여부」 커밋 상태로 바꿨다. CLAUDE.md 와 반대로 적혀 있었고, 라벨은 승인이 마지막 사건인 PR 에서 낡는다 — 그 상태가 머지 직전이다. pass·fail·error 세 값과 문면을 그대로 적었다. ③ 「자식 0건」 을 「있으면 먼저 재타겟한다」 로 바꿨다. 0건을 금지 조건으로 두면 부모는 자식 때문에 막히고 자식은 부모가 안 들어가서 못 간다. 결정 9.1 의 사유 (닫힌 PR 은 base 를 못 바꾼다 · 재타겟은 머지하는 쪽이 한다)를 되살렸다. ④ scan.sh 가 「소유자 승인 여부」 를 통째로 버리고 있었다. rollup 에 워크플로 잡 (name·conclusion)과 커밋 상태(context·state)가 섞여 오는데 select(.name != null) 로 걸러서, 승인 부족인 PR 이 「초록」 으로 보였다. error 는 「판정불가」 로 따로 센다.
|
부모 지금 이 PR 은 남의 PR 이라 제가 누르지 않습니다. 이게 들어가면 그 위의 덧붙여, 이 스택이 |
#609의 ② 첫 칸입니다 — ❗지금으로 표시된 것이고, 9/6 요청에 5일째 답이 없던 것이 제쪽입니다.
Refs #475 · #609 · #474 ②
Important
#610위에 쌓았습니다.@PreAuthorize('rubric:read')가 정책에 그 action 이 있어야하고(
everyDeclaredActionExistsInPolicy— 제가#610리뷰에서 실측했습니다), 그 파일은소유자가 다릅니다.
#610이 머지되면 base 를main으로 돌립니다.흐름
❗아무것도 안 바꿉니다
승인 산출물은
app/rubrics/*.yaml파일이고 승인은 git 커밋입니다(결정#475ⓐ · 서버stateless). 이 경로는 그 파일을 보여줄 뿐이고 approve 는 별건입니다(
#609의 데모 뒤 칸).판매 직원에게 열지 않습니다 — 정책 한 줄로 안 끝냅니다
rubric:read는 COMPL·MGR·ADMIN 뿐입니다(#610). 그런데 정책만으로는 다음 사람이 역할을더해도 조용합니다. 그래서
seller-01로 403 을 재는 단정을 넣었습니다 —#610리뷰에서약속한 그물입니다.
404 를 404 로 넘깁니다
루브릭이 없는 항목은 정상 상태입니다 —
recommended항목은 루브릭이 없고 채점 대상도아닙니다(결정 10.1 ·
#435). 502 로 뭉치면 화면이 「AI 서비스 장애」로 읽고 「기준이 없다」와「기준이 비어 있다」가 같아집니다.
#556이 고친 것과 같은 부류라 같은 방식으로 갈랐습니다.total을 접지 않습니다ai-service 가 「필터 전 전체 개수」를 같이 내는 이유가 "걸러진 목록만 보이면 이게 전부로
읽힌다" 이고, 경계에서 버리면 그 뜻이 사라집니다.
R-00이 분모를 지키는 것과 같은 결입니다.❗역검증 — 둘이 처음엔 초록이었습니다
셋째: 배선 테스트가
aiServiceClient를 스텁해서 404 번역을 안 지났습니다.AiServiceClientTest에 실제 404 응답을 세웠습니다.넷째: 목록 길이와
total을 같게(0·0) 둬서 접어도 통과했습니다. 이제 1건 · total 17 로둡니다 — 필터를 건 실제 모양이기도 합니다.
계약
openapi.yaml에 두 경로와 스키마 셋(Rubric·RubricsResponse· 봉투 둘). 필드 설명에세 가지를 적었습니다.
경로 변수는 계약이
{id}로 통일합니다(컨트롤러는{itemId}) —OpenApiPermissionSyncTest가 이름이 아니라 자리로 맞춥니다.
❗남의 파일 한 줄 —
15-demo-mode.shDemoModeAccountMapTest가 요구합니다. 없으면 개방 모드에서 SELLER 계정이 주입돼 전부403 이라 화면이 안 뜹니다.
#609③ 이 이 칸을 @junseo2323 님 몫으로 적어 뒀고 내용도 그#4949/7 코멘트대로 ADMIN 입니다.~^/api/products/로 넓히지 않은 이유(그 아래product:read는 SELLER 도 있어서 default 여야 한다)도 같이 적었습니다.남은 칸 (제 몫)
#609는 닫지 않습니다 — 추적 이슈이고 제 칸 셋 중 하나입니다.검증
./gradlew test831건 통과(+6) ·npm run build통과.@gitIt-sehyeon (
#610부모 · 계약) @junseo2323 (③ 화면이 이 위에 섭니다 · 지도 한 줄) @yoonjiseok (/internal/rubrics소유)