Skip to content

ci(S15P11A705-154): profile 리터럴 대조를 CI 로 옮기고 WORKLOG 를 메운다 - #41

Closed
colosair wants to merge 2 commits into
mainfrom
ci/S15P11A705-154-profile-parity
Closed

ci(S15P11A705-154): profile 리터럴 대조를 CI 로 옮기고 WORKLOG 를 메운다#41
colosair wants to merge 2 commits into
mainfrom
ci/S15P11A705-154-profile-parity

Conversation

@colosair

Copy link
Copy Markdown
Member

요약

ai#40 병합 후속 두 건이다. 파일이 겹치지 않아 한 PR로 묶었다. ① docs/WORKLOG.md
빠져 있던 ai#40 줄을 채운다. ② BD-39가 *"두 값이 같다"*고 명시한 명제를 사람의 눈
대신 CI가 지키게 한다. 앱 동작·DB·계약 변경 없음 — CI 잡 하나와 검증 코드, 문서 한 줄.

Jira (필수)

변경 사항

파일 무엇을·왜
docs/WORKLOG.md ai#40(action pin 갱신·보존 기록 복원·dev 준비) 한 줄 추가. ai#39가 남긴 S15P11A705-154 줄은 그 PR 것이고 ai#40은 별개 작업이라 규약상 한 줄이 더 필요하다. 이 PR 자신의 줄도 함께 추가한다 — 같은 누락을 다시 만들지 않기 위함
tools/check_embedding_profile_parity.py 두 레포의 Profile 리터럴 대조기(신규). backapplication.yml을 무인증 raw endpoint로 읽고 aiSettings 필드 선언과 비교, 불일치·조회실패 모두 exit 1
tests/test_embedding_profile_parity.py 대조기 단위 검증 14케이스(신규). 네트워크 미사용
.github/workflows/ai-ci.yml embedding-profile-parity 잡 추가. check독립 실행

배경 — 왜 CI 잡인가

back#98 리뷰의 지적을 그대로 옮기면, BD-39가 (c)안(기동 시 FastAPI 조회)을 기각한
근거는 *"상대 값을 받아 상대에게 되돌려 주면 대조가 항상 통과한다"*인데, 이는 상대
값을 채택하는 것
에 대한 반박으로는 맞지만 상대 값과 대조하는 것까지 배제한다는
것이다. 그리고 현재 상태에서 *"두 값이 같다"*를 지키는 장치는 사람의 눈뿐이다.

이 PR은 리뷰가 제안한 두 안 중 값싼 쪽만 취한다.

  • (a)안 유지 — 정본은 여전히 backapplication.yml 리터럴이다. 이 PR은 ai
    값을 정본으로 승격하지 않고, 어느 쪽도 상대 값을 채택하지 않는다.
  • "기동 시 대조" 안은 채택하지 않았다. 중앙 판단이며 이 PR의 범위가 아니다.
    ai에 Profile을 노출하는 엔드포인트가 필요한 별도 협의 사안이다.
  • BD-39 문서 자체는 개정하지 않았다. (a)를 유지한 채 검증만 붙인다.

리뷰 포인트

  1. back 값을 읽는 경로 — 무인증 raw endpoint. 두 레포가 모두 public이라
    (gh api repos/Team-PinLog/back --jq .visibilitypublic) 토큰 없이 읽힌다.
    토큰을 쓰지 않은 것은 편의가 아니라 경계다 — 이 잡에 credential을 주면 ai CI가
    타 레포 접근 권한을 상시로 들고 다닌다. 반대로 back이 private으로 바뀌면 이 잡은
    조회 실패로 exit 1 하며 조용히 통과하지 않는다.
  2. 읽는 ref를 backdev로 잡았다. back의 기본 브랜치이자 통합 브랜치다.
    배포 대상인 릴리스 브랜치가 아니라 dev를 읽는 것은 드리프트를 가장 이르게
    잡으려는 선택인데, 대가가 있다backdev 커밋 하나가 우리 PR CI를
    붉게 만들 수 있다. 드리프트 감지기의 본질이라고 판단했지만 이 결합이 과하다고
    보면 릴리스 브랜치로 낮추는 것이 대안이다.
  3. 런타임 값이 아니라 선언된 리터럴을 비교한다. 양쪽 다 환경변수 덮어쓰기를
    허용하므로(${VAR:default} / Field(alias=...)), 프로세스 환경이 결과를 바꾸면
    CI가 무엇을 검증하는지 알 수 없게 된다. ai 쪽은 Settings()인스턴스화하지
    않고
    Settings.model_fields[...].default만 읽는다 — 인스턴스화는 환경변수를 읽고
    profile 정합 검증까지 돌린다. test_ai_side_reads_the_declared_literal_not_the_environment
    PINLOG_EMBEDDING_PROFILE을 세팅해도 선언값이 이긴다는 것을 고정한다.
  4. 조회 실패를 통과로 처리하지 않는다. 네트워크 실패·키 경로 소실·기본값 없는
    ${VAR} 형태 모두 exit 1이다. "확인하지 못했다"를 초록으로 두면 사람의 눈을 CI로
    옮긴 의미가 사라진다. 다만 이 때문에 GitHub raw endpoint 장애가 PR을 막을 수
    있다
    — 재시도를 넣지 않은 것은 드리프트 알람의 신뢰도를 낮추지 않으려는 판단이다.
  5. check와 독립 실행(needs 없음). 순차로 묶으면 테스트가 깨진 PR에서 계약
    드리프트가 보이지 않고, 반대로 이 잡의 네트워크 실패가 테스트 신호를 가리지도
    않는다. 대신 아래 「범위 밖」 1번을 봐 주세요 — 지금은 필수 체크가 아니다.
  6. 잡이 늘 초록이라는 문제. 실제 두 값이 같은 동안 이 잡은 항상 통과하므로,
    "대조기가 불일치를 잡는가"는 검증되지 않은 채 남는다. tests/test_embedding_profile_parity.py
    14케이스가 고정 입력으로 그 능력을 못박고, test_mismatch_is_detected_between_the_two_sides
    한쪽만 어긋난 경우를 직접 단언한다.

테스트 / 검증

RED — 잡이 실제로 불일치를 잡는 것을 관측

양방향으로 실측했다. 한쪽만 보면 대조기가 상대 값을 그대로 되읽고 있는 경우를
놓친다.

back 쪽을 어긋냄 (3-small-15363-large-3072 픽스처):

$ python tools/check_embedding_profile_parity.py --back-yaml back-app-skewed.yml
  pinlog.ai.embedding-profile = openai-text-embedding-3-large-3072-cosine-v1
  Settings.embedding_profile  = openai-text-embedding-3-small-1536-cosine-v1
::error::Embedding Profile 리터럴 불일치 — ...                      # exit 1

ai 쪽을 어긋냄 (실제 back dev 대상, config.py 기본값 임시 변경):

$ python tools/check_embedding_profile_parity.py
  pinlog.ai.embedding-profile = openai-text-embedding-3-small-1536-cosine-v1
  Settings.embedding_profile  = deliberately-skewed-profile-v0
::error::Embedding Profile 리터럴 불일치 — ...                      # exit 1

둘 다 되돌린 뒤 git diff --stat app/core/config.py 무출력으로 복원을 확인했다.
CI에서의 RED 관측 결과는 아래 「CI」 항목에 런 ID로 기록한다.

GREEN

$ python tools/check_embedding_profile_parity.py                     # exit 0
  pinlog.ai.embedding-profile = openai-text-embedding-3-small-1536-cosine-v1
  Settings.embedding_profile  = openai-text-embedding-3-small-1536-cosine-v1
  일치
$ python -m pytest tests/test_embedding_profile_parity.py -q         # exit 0
14 passed

Regression

  • ruff check .All checks passed! (exit 0)
  • python -m compileall app tools — exit 0
  • pytest --cov=app --cov-branch --cov-report=term-missing88 passed, exit 0
    (74 → 88, 신규 14). TOTAL 77% (693 stmt/150 miss/102 branch)
  • DB 계약 변경 시 pgvector(PostgreSQL) Testcontainers 검증 — 스키마·DB 계약 변경은
    없으나 Docker 29.6.1로 전체 suite를 돌려 DB 테스트를 건너뛰지 않았다

app/ 변경이 없어 커버리지는 main과 동일하다. 신규 코드는 tools/--cov=app
집계 대상이 아니고, 그 대신 14케이스 단위 테스트로 덮었다.

검증 커밋: 79dfebc · 분기 기준 main 518b2bf

리스크

  • 계약: 계약 자체는 바뀌지 않는다. 이 잡은 기존 명제를 감시할 뿐이고 어느 쪽 값도
    바꾸지 않는다. BD-39 문서 미개정.
  • 데이터·개인정보: 없음. 읽는 값은 공용 계약 05 §7.1에 공개된 Profile 문자열이고
    credential을 쓰지 않는다.
  • 운영·배포: image publish 경로 무변경. 새 잡은 외부 네트워크(GitHub raw)에
    의존하므로 그 장애가 PR을 막을 수 있고, back dev의 변경이 우리 CI를 붉게
    만들 수 있다. 둘 다 의도된 트레이드오프이며 리뷰 포인트 2·4에 적었다.

범위 밖 / 후속

이번 PR에서 다루지 않는 항목:

  1. 이 잡은 아직 필수 체크가 아니다. main(과 dev)의 branch protection이 요구하는
    것은 strict ai-ci / check 하나이므로, ai-ci / embedding profile parity는 지금은
    PR에 붉게 보이지만 병합을 막지는 못한다. 실제로 게이트가 되려면 중앙이 required
    status에 추가해야 한다 — branch protection은 중앙 소관이라 손대지 않았다. 후속 필요.
  2. "기동 시 대조" 안 — 리뷰 포인트 1의 (a)+대조 조합. 채택하지 않았다(중앙 판단).
    ai에 Profile 노출 엔드포인트가 필요하며 AI·백엔드 협의 사안이다.
  3. docs/development/workflow.md 4·8·33·53행과 P44 52행의 main 참조
    ai#40CONTRIBUTING.mddev 기준으로 바꾼 뒤로 불일치 상태다. ai#40
    패킷에서 이미 올린 항목이며 이 PR 범위 밖이다. 후속 티켓 필요.
  4. back 소관 — 리뷰가 지적한 embeddingProfile 빈 문자열 기동 검사,
    distinctByRecordmatch == null 가드, 테스트 구멍 3건. back 레포 변경은
    금지 범위다.
  5. docs 소관08 §1.5 상태 코드 표에 503과 새 error.code 2종 미등록.

후속 Jira: 1번(required status 추가)은 중앙, 3번은 AI 파트 신규 티켓, 4·5번은
back·docs 소관.

영구 문서

  • docs/WORKLOG.mdai#40 줄과 이 PR 줄

tools/·tests/ 신규 파일은 구현물이라 별도 implements 리포트를 만들지 않았다. 잡의
판단 근거(무인증 경로·dev ref·선언값 비교·실패를 통과로 두지 않음)는 스크립트
docstring과 workflow 주석에 두었고, 이 PR 본문이 리뷰 기록으로 남는다.

관련 GitHub Issue (선택)

colosair and others added 2 commits July 30, 2026 11:05
두 건이다. 파일이 겹치지 않는다.

WORKLOG 에 ai#40 줄이 없었다. ai#39 가 남긴 S15P11A705-154 줄은 그 PR 것이고
ai#40(action pin 갱신·보존 기록 복원·dev 준비)은 별개 작업이라 규약상 한 줄이
더 필요하다.

BD-39 는 Embedding Profile 정본을 back 의 application.yml 리터럴로 두는 (a)안을
택하면서 "두 값이 같다"를 지켜야 할 명제로 명시했다. back#98 리뷰가 그 명제를
지키는 장치가 사람의 눈뿐이라고 지적했다. (a)안은 그대로 두고 대조만 기계로
옮긴다 — 리뷰가 함께 제안한 기동 시 대조는 채택 대상이 아니다.

back 도 ai 도 public 저장소라 raw endpoint 를 무인증으로 읽는다. 토큰을 주지
않는 것이 경계다 — 주면 이 잡이 타 레포 접근 권한을 상시로 들고 다닌다.

비교 대상은 런타임 값이 아니라 선언된 리터럴이다. 양쪽 다 환경변수 덮어쓰기를
허용하므로, 프로세스 환경이 결과를 바꾸면 CI 가 무엇을 검증하는지 알 수 없게
된다. ai 쪽은 Settings 를 인스턴스화하지 않고 필드 선언만 읽는다.

조회 실패도 exit 1 이다. "확인하지 못했다"를 통과로 처리하면 사람의 눈을 CI 로
옮긴 의미가 없다.

실제 두 값이 같은 동안 이 잡은 늘 초록이라, 대조기가 불일치를 잡는 능력 자체는
검증되지 않은 채 남는다. tests/test_embedding_profile_parity.py 14 케이스가
고정 입력으로 그것을 못박는다 — 네트워크를 타지 않는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
이 커밋은 다음 커밋에서 되돌린다. embedding-profile-parity 잡이 실제로 불일치를
잡는지 CI 에서 확인하기 위한 것이다. v1 -> v2 만 바꿨으므로 config.py 자체의
profile 정합 검증(model/dimension/distance 토큰 포함)은 그대로 통과한다 — 새 잡을
검증하는 것이고 기존 검증기를 건드리는 것이 아니다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@colosair

Copy link
Copy Markdown
Member Author

티켓 S15P11A705-156 이 뒤늦게 발급돼 브랜치·커밋·PR 을 규약 형식(ci(S15P11A705-156): …)으로 다시 만들었습니다. 대체 PR: #42

이 PR 에서 관측한 CI RED(run 30507610160 · job 90760691342, ai-ci / embedding profile parity fail 19s)는 대체 PR 본문에 인용했습니다 — 검증한 코드는 동일하고 런 기록은 브랜치 삭제 후에도 Actions 에 남습니다. 코드 변경은 없고 티켓 키만 바뀌었습니다.

@colosair colosair closed this Jul 30, 2026
@colosair
colosair deleted the ci/S15P11A705-154-profile-parity branch July 30, 2026 02:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant