Skip to content

fix: 승인 메일이 종류마다 실제로 일어나는 일을 말하도록 수정 - #129

Merged
yessjun merged 4 commits into
mainfrom
fix/approval-mail-copy
Sep 16, 2026
Merged

yessjun merged 4 commits into
mainfrom
fix/approval-mail-copy

Conversation

@yessjun

@yessjun yessjun commented Sep 16, 2026

Copy link
Copy Markdown
Member

📝 작업내용

  • 종전 승인 메일은 LLM API 키 신청자에게 생성 시작과 완료 후 재통지를 약속
  • 키 행은 승인 트랜잭션 안에서 생성되어 PENDING 상태로 대기, 승인 이후 동작하는 작업 없음
  • kr/ac/pusan/pickle/llm/ 전체에 NotificationService 호출 0건, 약속한 후속 알림을 발행하는 코드 부재
  • 신청자가 수행할 콘솔에서의 키 발급이 메일에 미기재
  • 승인 분기를 ResourceType 식 switch 로 교체, default 부재로 다섯째 종류는 해당 위치에서 컴파일 실패
  • 종류별 문장 분리, VM 만 후속 통지 약속
  • VM 은 프로비저닝이 vm.create.done 을 발행하므로 약속이 성립, LLM 키와 도메인은 신청자의 다음 행동 안내
  • 평문 키 1회 노출 경고를 메일에서 제거, 발급 화면이 세 자리에서 이미 안내
  • GPU 분기가 누락하던 검토 의견 복구, ApprovalService 는 모든 종류에 전달하나 GPU 문자열만 미참조
  • LLM 키 승인 메일의 버튼 목적지를 /console/llm-keys/{id} 로 변경
  • 단일 개행을 <br /> 로 렌더, HTML 파트와 평문 파트의 줄바꿈 위치 일치
  • 기존 템플릿 26곳 재줄바꿈, 소스 폭 때문에 접혀 있던 위치 정리
  • 이스케이프 순서 반전, 줄마다 이스케이프 후 결합
  • 문단 경계 2곳 수정, 만료 예고와 주소 없이 메모만 있는 교내 IP 알림
  • 알림 메일 평문 파트에 URL 줄 추가, 계정 메일과 동일한 형태
  • 버튼 라벨을 이벤트와 목적지로 결정, /admin/ 접두는 관리자용 문구
  • 받은편지함 미리보기용 숨김 preheader 추가
  • 신청 목적, 반려 사유, 검토 의견을 목록 항목에서 자기 문단으로 이동, 작성자의 줄바꿈 보존
  • resourceNameworkspaceName 에 개행 접기 적용, 가짜 목록 항목 주입 차단
  • NotificationEventNotificationStatus 의 주석 정정, 종전 주석은 존재하지 않는 이벤트별 메일 채널 스위치를 설명

⭐️ 검증

  • 테스트 1521개 통과, 정적 분석과 빌드 포함
  • ResourceType 에 임시 값 추가 시 NotificationComposer 의 해당 switch 에서 빌드 실패, 완전성 검사 실측
  • setText 인자를 뒤바꾸면 신규 테스트 실패, 종전 단언은 통과
  • 메모리상 MimeMessagegetContent() 가 두 본문을 자기 헤더와 반대 순서로 반환, 원문 덤프로 확인
  • 와이어 바이트는 정확하므로 파트 내용 검사는 직렬화 후 재파싱 필요
  • 렌더 결과 파일 검사, 앵커 1개와 <br /> 위치, preheader 존재, 목록 유지
  • 독립 서브에이전트 적대 검토 1회, 지적 사항 전부 반영

💬 리뷰 포인트

  • 종류별 승인 문장 넷의 표현, 특히 LLM 키의 「콘솔에서 키를 발급하면 사용할 수 있습니다」
  • 자유 산문을 목록 항목이 아닌 자기 문단으로 옮긴 판단
  • 신청 목적은 2000자 자유 입력이고 컴포저가 발행 시점에 동작하므로 저장된 본문이 콘솔 알림함에도 잔존
  • 도메인 승인 메일의 버튼이 신청 상세로 이동하는 점, DomainRequestSupport 부재로 domainId 전달 불가
  • 배포 직전 PENDING 상태로 적재된 행과 재발송되는 실패 행은 종전 방식으로 줄바꿈된 본문 보유

The approval notice was shared by every resource type through a string
comparison that only branched out GPU, so an LLM key inherited the VM
sentence: creation had started, and the requester would be told when it
finished. Neither was true. The key row is created inside the approval
transaction and lands PENDING, nothing runs afterwards, and no code in the
llm package publishes a notification at all, so the promised follow-up
could never arrive. What the requester actually has to do next, issue the
key themselves, went unsaid.

Each kind now says what follows it, and the branch is an expression switch
over ResourceType with no default, so a fifth kind fails to compile here
rather than inheriting a sentence written for something else. The key's
notice links to the screen that issues rather than to the request, since
this is the only mail an LLM key sends. The one-time-reveal warning stays
on that screen, where it applies, instead of arriving days early.

Two adjacent repairs: the GPU branch never read the reviewer's comment
that ApprovalService puts in the payload for every kind, and free text
interpolated into a list line is folded onto one line so a multi-line
value cannot split the list.
The HTML part joined the lines of a paragraph with a space while the plain
part kept them, so the two alternatives of one mail showed different text.
An administrator writing a three-line announcement reached HTML readers as
one run-on sentence, against the console's promise that the body is
included as written.

A single newline now renders as a line break, which is what the plan for
this layout said before the implementation went the other way. The test
that pinned the join asserted the behaviour rather than the intent, so it
never signalled; it is inverted here, and a new one covers the escape
order, which nothing checked and which silently turns the separator into
literal text if the lines are joined before they are escaped.

Every template's wraps become visible with this, so the ones folded for
source width are rejoined and only deliberate breaks remain. Two seams
that only looked like paragraph breaks in source, the expiry notice and a
campus-IP notice carrying a note without an address, are now real ones.

Also adds the hidden preheader that gives the inbox preview line the
body's own first sentence instead of the lockup text every mail shares.
A notification's link existed only in the HTML alternative, so a reader
whose client shows the plain part had the mail and no way to reach what it
was about. The account mails have always put the URL on a line of its own;
notifications now do the same. Safe against link-prefetching gateways for
the reason the single-anchor rule guards elsewhere: a notification link is
a plain console path, never a one-time token.

The button also stops saying the same thing everywhere. It named the
console regardless of where it went, including admin pages and the screen
that issues an LLM key. The label comes from the stored event, with the
destination deciding the one case the event cannot: every kind shares
request.approved, and only the key's notice goes to the issue screen. The
stored value is the rendered id, so an expiry notice is filed under its
stage and has to fall through to the default rather than resolve to
nothing.

Also pins which body lands in which multipart part. The existing assertion
read the two content types and would have passed with the arguments
swapped, which would send every reader markup as their plain text. It has
to parse the serialized message to see this: on the in-memory one the
accessor returns the two bodies in the opposite order to their own
headers, while the bytes on the wire are correct.
An adversarial review of this branch found the newline folding applied
where it hurts and missing where it was the point.

The purpose a requester writes is a 2000-character textarea and it is what
a reviewer reads to decide, so flattening its paragraphs into one line
damaged the mail the decision is made from, permanently: the composer runs
at publish time, so the flattened text is what the notification row stores
and the console inbox keeps. The reason it was flattened at all was that
it sat inside a list item, where a second line ends the run. Free prose
now gets a paragraph of its own under a label, which is where it belonged
and where its own breaks survive. The rejection reason and the reviewer's
comment move with it.

Folding is instead applied to the two names a user chooses, which is where
it was needed and absent: a resource or workspace name carrying a newline
whose next line begins with a dash was promoted into a list item, letting
a requester forge a review-comment bullet in a mail the platform sends.

Same review on the rest: the button label is decided by the destination
rather than by a row per event, so the admin half of request.submitted no
longer reads like a requester's and gpu.review is no longer missed; the
key-issuing label is keyed on the event too, so a later key notice linking
to the same screen cannot tell its reader to issue a key they have; the
preheader spans the opening line, which had left the signup and
password-reset mails previewing identically; and the enum doc that
contradicted the one this branch corrected now agrees with it.
@yessjun
yessjun merged commit 999db51 into main Sep 16, 2026
1 check passed
@yessjun
yessjun deleted the fix/approval-mail-copy branch September 16, 2026 09:57
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