[{"data":1,"prerenderedAt":2607},["ShallowReactive",2],{"doc:\u002Fhistory\u002Fhistory.20260527":3},{"id":4,"title":5,"body":6,"description":1468,"extension":2600,"meta":2601,"navigation":2602,"path":2603,"seo":2604,"stem":2605,"__hash__":2606},"docs\u002Fhistory\u002Fhistory.20260527.md","2026-05-27 — malgn-noti-admin Nuxt 3 부트스트랩 + 셸 레이아웃 + 첫 프로덕션 배포 + malgn-noti-api 멱등 수정·NHN 어댑터·Queues·webhook·Email\u002FKakao\u002FPush 채널·배포 #6·#7 + 사용자단 추가(랜딩페이지·문의 이동·나의 페이지·충전 — 배포 #44~#46) + malgn-noti-api 루트→\u002Fdoc 리다이렉트 + RCS 채널·Export 잡·Flow 정의 정식 커밋·배포 #8",{"type":7,"value":8,"toc":2548},"minimark",[9,13,18,133,137,203,207,329,333,425,429,476,480,532,535,577,580,584,591,602,613,667,677,680,841,850,861,866,950,954,992,1015,1019,1026,1029,1062,1069,1072,1135,1142,1149,1153,1160,1235,1239,1367,1371,1431,1446,1450,1459,1469,1476,1480,1486,1564,1579,1583,1612,1616,1619,1670,1672,1676,1686,1690,1811,1815,1873,1877,1884,1888,1968,1972,2016,2020,2061,2065,2098,2102,2112,2116,2152,2156,2168,2171,2175,2216,2220,2285,2289,2329,2333,2407,2411,2414,2478,2481,2544],[10,11,5],"h1",{"id":12},"_2026-05-27-malgn-noti-admin-nuxt-3-부트스트랩-셸-레이아웃-첫-프로덕션-배포-malgn-noti-api-멱등-수정nhn-어댑터queueswebhookemailkakaopush-채널배포-67-사용자단-추가랜딩페이지문의-이동나의-페이지충전-배포-4446-malgn-noti-api-루트doc-리다이렉트-rcs-채널export-잡flow-정의-정식-커밋배포-8",[14,15,17],"h2",{"id":16},"한-줄-요약","한 줄 요약",[19,20,21,22,26,27,30,31,35,36,39,40,43,44,47,48,51,52,55,56,59,60,63,64,67,68,71,72,75,76,79,80,83,84,87,88,91,92,95,96,75,99,102,103,106,107,30,110,113,114,30,117,120,121,124,125,128,129,132],"p",{},"세 트랙 병행. ",[23,24,25],"strong",{},"(A) malgn-noti-admin"," — 비어 있던 BackOffice 레포를 ",[23,28,29],{},"Nuxt 3 + Nuxt UI v3로 부트스트랩"," + ",[32,33,34],"code",{},"design_handoff_customer_detail"," 정본 참조 ",[23,37,38],{},"LNB(256px sticky · 8 그룹 메뉴) + TopBar(64px sticky)"," 셸 레이아웃 + ",[32,41,42],{},"https:\u002F\u002Fmalgn-noti-admin.pages.dev"," 첫 Nuxt 앱 프로덕션 배포(정적 placeholder 대체). ",[23,45,46],{},"(B) malgn-noti-api"," — §19에서 추적한 멱등 버그를 ",[32,49,50],{},"TB_IDEMPOTENCY"," + INSERT-then-conflict race-free 패턴으로 정식 해결 + NHN SMS 어댑터(mock\u002Freal) + Cloudflare Queues(",[32,53,54],{},"malgn-noti-dispatch",") + consumer worker(",[32,57,58],{},"dispatch_state"," 천이) + ",[32,61,62],{},"POST \u002Fwebhooks\u002Fnhn\u002Fsms"," HMAC 콜백 수신 + Producer·Consumer 동시 바인딩 프로덕션 배포 #6(Version ",[32,65,66],{},"b30dc2a3...",") + ",[23,69,70],{},"Email · Kakao(알림톡\u002F친구톡) · Push 3채널 동시 추가"," + 배포 #7(Version ",[32,73,74],{},"12dae362...","). ",[23,77,78],{},"(C) malgn-noti 사용자단"," — 메시지 관리 랜딩페이지 만들기 신규(목록·기본형\u002F확장형 등록 폼·미리보기 모달) + 문의하기 경로를 ",[32,81,82],{},"\u002Faccount\u002Finquiry","로 이동(GNB·푸터·사이트맵 링크 정리) + 나의 페이지 섹션(공통 셸 + 9개 라우트·회원 정보 변경·결제 카드 관리·이메일\u002F휴대폰 인증 모달) + 크레딧 충전 플로우(충전 페이지 재구성·결제 컨펌·결과 화면)로 Pages 배포 #44·#45·#46. ",[23,85,86],{},"(D) malgn-noti-api 추가"," — 루트(",[32,89,90],{},"\u002F",") placeholder JSON을 ",[32,93,94],{},"c.redirect('\u002Fdoc')","로 교체해 워커 도메인 접속이 곧장 Scalar API 문서로 이동하도록 변경, Workers 재배포(Version ",[32,97,98],{},"f3fd3eb4...",[23,100,101],{},"(E) malgn-noti-api §13 WIP 정식 커밋"," — §13 시점에 라이브로 올라가 있던 RCS 채널(5채널째 — adapter·producer·consumer·webhook·",[32,104,105],{},"RCS_PRICING",") + Export 잡(",[32,108,109],{},"TB_EXPORT_JOB",[32,111,112],{},"\u002Fexport-jobs"," CRUD) + Flow 정의(",[32,115,116],{},"TB_FLOW_DEFINITION\u002FRUN\u002FSTEP_RUN",[32,118,119],{},"\u002Fflow-definitions"," CRUD) + OpenAPI 4지점(+230) + ",[32,122,123],{},"0002_export_flow.sql"," 마이그레이션을 단일 배치 커밋(",[32,126,127],{},"1e7bd61",", 12 files +1228 −16) + 배포 #8(Version ",[32,130,131],{},"95f9f894...",")로 working tree ↔ main 동기화. DDL 적용은 Cloudflare 1105 회복 후로 보류.",[14,134,136],{"id":135},"_1-사전-조사","1. 사전 조사",[138,139,140,173,194],"ul",{},[141,142,143,146,147,150,151,154,155],"li",{},[23,144,145],{},"참조 정본",": ",[32,148,149],{},"\u002FUsers\u002Fdotype\u002FProjects\u002Fdesign_handoff_customer_detail\u002F"," — README + ",[32,152,153],{},"prototype\u002F"," HTML\u002FJSX 프로토타입. 고객사 상세 1화면 hi-fi 시안.\n",[138,156,157,164,167],{},[141,158,159,160,163],{},"권장 스택: Nuxt 3 + ",[23,161,162],{},"Nuxt UI v3"," + Tailwind v4 + DM Sans(라틴) + Pretendard(한글) + Lucide.",[141,165,166],{},"레이아웃: LNB 256px · TopBar 64px · Content + MemoPanel 360px.",[141,168,169,172],{},[32,170,171],{},"prototype\u002Flnb.jsx","에서 8개 그룹 메뉴 트리 + 액티브\u002F배지\u002FNEW 스타일 추출.",[141,174,175,146,178,181,182,185,186,189,190,193],{},[23,176,177],{},"대상 상태",[32,179,180],{},"malgn-noti-admin\u002F","은 ",[32,183,184],{},"CLAUDE.md","(상세 운영 도메인 문서)·",[32,187,188],{},"doc\u002FDESIGN-ADMIN.md","(Part A 상속 + Part B 관리자 밀도)·정적 ",[32,191,192],{},"public\u002Findex.html","(placeholder)만 존재 — Nuxt 미부트스트랩.",[141,195,196,146,199,202],{},[23,197,198],{},"사용자 확인",[32,200,201],{},"AskUserQuestion"," — 산출물 형태 → \"Nuxt 3 부트스트랩 + 셸 레이아웃\", 범위 → \"셸만(LNB + TopBar + 빈 콘텐츠)\".",[14,204,206],{"id":205},"_2-프로젝트-부트스트랩","2. 프로젝트 부트스트랩",[138,208,209,239,260,270,287,296],{},[141,210,211,214,215,218,219,222,223,222,226,222,229,222,232,222,235,238],{},[23,212,213],{},"package.json","(신규): name=",[32,216,217],{},"malgn-noti-admin",", 사용자단과 동일 버전 라인 — ",[32,220,221],{},"nuxt ^3.16"," · ",[32,224,225],{},"@nuxt\u002Fui ^3.0",[32,227,228],{},"@iconify-json\u002Flucide",[32,230,231],{},"vue ^3.5",[32,233,234],{},"vue-router ^4.4",[32,236,237],{},"typescript ^5.6"," · pnpm 10.25. Pinia\u002FChart\u002FZod 제외 — 셸 범위 밖.",[141,240,241,244,245,248,249,222,252,255,256,259],{},[23,242,243],{},"nuxt.config.ts","(신규): ",[32,246,247],{},"compatibilityVersion: 4","(app\u002F 디렉터리 구조) · ",[32,250,251],{},"modules: ['@nuxt\u002Fui']",[32,253,254],{},"nitro.preset: 'cloudflare-pages'"," · DM Sans + Pretendard 폰트 head 주입 · ",[32,257,258],{},"noindex,nofollow"," 메타.",[141,261,262,265,266,269],{},[23,263,264],{},"tsconfig.json","(신규): Nuxt 자동생성 ",[32,267,268],{},".nuxt\u002Ftsconfig.json"," 상속.",[141,271,272,275,276,282,283,286],{},[23,273,274],{},"app\u002Fapp.config.ts","(신규): Nuxt UI 컬러 매핑 — ",[23,277,278,279],{},"primary ",[32,280,281],{},"blue"," + neutral ",[32,284,285],{},"slate"," (핸드오프 정본).",[141,288,289,244,292,295],{},[23,290,291],{},"app\u002Fapp.vue",[32,293,294],{},"\u003CUApp :locale=\"ko\">\u003CNuxtLayout>\u003CNuxtPage\u002F>\u003C\u002FNuxtLayout>\u003C\u002FUApp>"," 루트.",[141,297,298,244,301,30,304,307,308,311,312,315,316,222,319,222,322,222,325,328],{},[23,299,300],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[32,302,303],{},"@import \"tailwindcss\"",[32,305,306],{},"@import \"@nuxt\u002Fui\"",". ",[32,309,310],{},"@theme","에 폰트 정의(DM Sans + Pretendard), ",[32,313,314],{},":root","에 ",[32,317,318],{},"--lnb-width: 256px",[32,320,321],{},"--topbar-height: 64px",[32,323,324],{},"--ring-default",[32,326,327],{},"--shadow-ring",". 본문 13px · slate-50 paper · slate-900 텍스트.",[14,330,332],{"id":331},"_3-셸-레이아웃","3. 셸 레이아웃",[138,334,335,351,401,419],{},[141,336,337,244,340,343,344,30,347,350],{},[23,338,339],{},"app\u002Flayouts\u002Fdefault.vue",[32,341,342],{},"flex min-h-screen bg-slate-50"," 컨테이너 — ",[32,345,346],{},"\u003CAppLnb\u002F>",[32,348,349],{},"\u003Cdiv class=\"flex-1 min-w-0 flex flex-col\">\u003CAppTopbar\u002F>\u003Cmain class=\"flex-1 px-6 py-5\">\u003Cslot\u002F>\u003C\u002Fmain>\u003C\u002Fdiv>",".",[141,352,353,356,357],{},[23,354,355],{},"app\u002Fcomponents\u002FAppLnb.vue","(신규): 256px sticky 사이드바.\n",[138,358,359,366,373,387,398],{},[141,360,361,362,365],{},"브랜드 헤더 — 그라데이션 로고 + \"맑은 message ",[23,363,364],{},"Admin","\".",[141,367,368,369,372],{},"⌘K 검색 버튼(",[32,370,371],{},"ring-1 ring-inset"," 입력형).",[141,374,375,378,379,382,383,386],{},[23,376,377],{},"8개 그룹 메뉴","(핸드오프 ",[32,380,381],{},"lnb.jsx"," 1:1 매핑) — 대시보드 · 회원\u002F고객사(고객사·계정·감사로그+",[32,384,385],{},"3"," 배지·차단\u002F제재) · 발송 관리(PUSH·RCS·SMS\u002FLMS·알림톡·이메일·예약 발송+NEW) · 템플릿(채널 4종+치환 변수) · 리포트(발송량·실패율·채널·크레딧·정산) · 채널\u002F연동(FCM·APNs·RCS Biz·카카오 비즈·SMPP) · 결제\u002F크레딧(발급·사용 내역·세금계산서) · 운영(공지·FAQ·1:1 문의).",[141,388,389,390,393,394,397],{},"1depth 액티브 — ",[32,391,392],{},"bg-blue-50 text-blue-700"," + 아이콘 박스 ",[32,395,396],{},"bg-blue-100",". 2depth 액티브 — 좌측 점 표식. 그룹 펼침\u002F접힘 토글.",[141,399,400],{},"하단 사용자 chip(아바타 + 이름\u002F이메일 + chevron).",[141,402,403,406,407],{},[23,404,405],{},"app\u002Fcomponents\u002FAppTopbar.vue","(신규): 64px sticky 상단 바.\n",[138,408,409,412],{},[141,410,411],{},"좌측 — 사이드바 토글 + 홈 아이콘 · 브레드크럼 \"대시보드\".",[141,413,414,415,418],{},"우측 — ",[32,416,417],{},"bg-emerald-50 ring-emerald-200"," 시스템 상태 pill(\"시스템 정상 · 14ms\"), 검색·벨(+빨간 점), 회사 스위처(\"맑은소프트\"), 사용자 chip.",[141,420,421,424],{},[23,422,423],{},"app\u002Fpages\u002Findex.vue","(신규): 빈 콘텐츠 데모 — 페이지 제목 \"맑은 메시징 BackOffice\" + 가운데 placeholder 카드(\"페이지 콘텐츠가 들어갈 영역입니다\").",[14,426,428],{"id":427},"_4-부팅-트러블슈팅","4. 부팅 트러블슈팅",[138,430,431,437,449,465],{},[141,432,433,436],{},[32,434,435],{},"pnpm install"," 완료(Nuxt 3.21.6, Nuxt UI 3.3.7).",[141,438,439,442,443,445,446,448],{},[32,440,441],{},"pnpm dev --port 3001"," 백그라운드 실행 → ",[32,444,90],{}," 응답이 부트스트랩 전 정적 ",[32,447,192],{},"(어두운 placeholder)을 반환.",[141,450,451,454,455,458,459,461,462,464],{},[23,452,453],{},"원인",": Nitro가 ",[32,456,457],{},"public\u002F"," 정적 파일을 Nuxt 라우트보다 우선 처리 → ",[32,460,192],{},"이 ",[32,463,90],{},"를 가로챔.",[141,466,467,146,470,472,473,475],{},[23,468,469],{},"해결",[32,471,192],{}," 삭제 → Nuxt SSR이 ",[32,474,423],{},"를 정상 렌더, LNB 메뉴 마커(대시보드·발송 관리·템플릿·리포트) 확인.",[14,477,479],{"id":478},"_5-첫-프로덕션-배포","5. 첫 프로덕션 배포",[138,481,482,492,497,507,518],{},[141,483,484,487,488,491],{},[32,485,486],{},"pnpm build"," → ",[32,489,490],{},"dist\u002F"," 272 kB gzip (셸 단계라 작음).",[141,493,494,350],{},[32,495,496],{},"npx wrangler@4 pages deploy dist --project-name=malgn-noti-admin --branch=main --commit-dirty=true --commit-message \"Initial admin shell: Nuxt 3 + Nuxt UI v3 + LNB + TopBar\"",[141,498,499,500,503,504,350],{},"프로덕션 검증 — ",[32,501,502],{},"https:\u002F\u002Fmalgn-noti-admin.pages.dev\u002F"," HTTP 200, \"대시보드·발송 관리·템플릿·맑은 메시징\" 마커 확인. alias ",[32,505,506],{},"https:\u002F\u002F471451c7.malgn-noti-admin.pages.dev",[141,508,509,510,513,514,517],{},"커밋: ",[32,511,512],{},"6cbf238 Nuxt 3 + Nuxt UI v3 부트스트랩 + LNB·TopBar 셸 레이아웃"," (12 files, +9064 −24 — 대부분 ",[32,515,516],{},"pnpm-lock.yaml",").",[141,519,520,521,524,525,528,529,517],{},"푸시: ",[32,522,523],{},"origin\u002Fmain"," (",[32,526,527],{},"d2a6bb6..6cbf238",", ",[32,530,531],{},"malgnsoft\u002Fmalgn-noti-admin",[14,533,534],{"id":534},"산출물",[138,536,537,565,571],{},[141,538,539,540,542,543,222,545,222,547,222,549,222,551,222,553,222,555,222,557,222,559,222,562,564],{},"신규 파일(",[32,541,180],{},"): ",[32,544,213],{},[32,546,243],{},[32,548,264],{},[32,550,516],{},[32,552,274],{},[32,554,291],{},[32,556,300],{},[32,558,339],{},[32,560,561],{},"app\u002Fcomponents\u002F{AppLnb,AppTopbar}.vue",[32,563,423],{}," (10종 + lockfile).",[141,566,567,568,570],{},"삭제: ",[32,569,192],{}," (부트스트랩 전 placeholder, Nuxt 라우트 가로채는 충돌 해소).",[141,572,573,574,576],{},"프로덕션 URL: ",[32,575,42],{}," (첫 Nuxt 앱 배포 — 이전 #2 placeholder 정적 페이지 대체).",[578,579],"hr",{},[10,581,583],{"id":582},"트랙-b-malgn-noti-api-백엔드-작업","트랙 B — malgn-noti-api 백엔드 작업",[19,585,586,587,590],{},"§19에서 추적한 멱등 버그 정식 해결 + 실제 NHN 발송 경로(어댑터 + 큐 + worker) 구축 + 프로덕션 배포 #6. 본 트랙의 상세는 ",[32,588,589],{},"history.20260526.md §22·§23"," 에 기록 — 26일 파일에 §N으로 누적 중이던 흐름의 연속이라 자연 위치를 따랐고, 본 27일 파일에는 요약 + 참조만 둠.",[14,592,594,595,528,598,601],{"id":593},"_6-멱등-버그-해결-malgn-noti-api-020307f-history20260526md-22","6. 멱등 버그 해결 (",[32,596,597],{},"malgn-noti-api 020307f",[32,599,600],{},"history.20260526.md §22",")",[19,603,604,605,608,609,612],{},"§19에서 보고했던 ",[32,606,607],{},"\u002Fsend\u002Fsms"," 멱등 버그(같은 ",[32,610,611],{},"Idempotency-Key"," 재호출 시 중복 적재·크레딧 중복 차감)를 정식 수정.",[138,614,615,631,637,657],{},[141,616,617,620,621,623,624,528,627,630],{},[23,618,619],{},"0001_idempotency.sql"," — 비파티션 추적 테이블 ",[32,622,50],{}," (PK ",[32,625,626],{},"(company_id, scope, idempotency_key)",[32,628,629],{},"result_id NULL→채움","). Aurora 적용 후 테이블 50개 라이브.",[141,632,633,636],{},[23,634,635],{},"race-free 패턴"," — MySQL이 PK 인덱스로 atomic dedup. 진행 중 트랜잭션과는 row-lock 대기 후 duplicate key error.",[141,638,639,641,642,645,646,649,650,653,654,350],{},[32,640,607],{}," 신규 흐름 — ",[32,643,644],{},"INSERT TB_IDEMPOTENCY (resultType='pending')"," 점유 → 검증·트랜잭션 → 마지막에 ",[32,647,648],{},"UPDATE result_id",". 점유 후 실패 시 ",[32,651,652],{},"rollbackIdempotency()"," 로 키 해제(재시도 가능). 처리 중이지만 commit 전이면 ",[32,655,656],{},"202 idempotent_in_flight",[141,658,659,662,663,666],{},[23,660,661],{},"검증 3건",": call 1 (key=K) → ID=8, call 2 (key=K, 같음) → ",[23,664,665],{},"ID=8 + idempotent:true",", call 3 (key=K2) → ID=9. 통과 확인.",[14,668,670,671,528,674,601],{"id":669},"_7-nhn-sms-어댑터-cloudflare-queues-consumer-worker-malgn-noti-api-5e1ac72-history20260526md-231232","7. NHN SMS 어댑터 + Cloudflare Queues + consumer worker (",[32,672,673],{},"malgn-noti-api 5e1ac72",[32,675,676],{},"history.20260526.md §23.1~23.2",[19,678,679],{},"발송 흐름의 비동기 처리 인프라 구축.",[138,681,682,697,742,786,800,809,822,831],{},[141,683,684,146,687,689,690,75,693,696],{},[23,685,686],{},"Cloudflare Queue 생성",[32,688,54],{}," (id ",[32,691,692],{},"6c67d698...",[32,694,695],{},"wrangler.toml","에 producer + consumer 동시 바인딩, batch 10·timeout 5s·max_retries 3.",[141,698,699,524,702,705,706],{},[23,700,701],{},"NHN SMS 어댑터",[32,703,704],{},"src\u002Fadapters\u002Fnhn\u002F",")\n",[138,707,708,726],{},[141,709,710,713,714,222,717,222,720,222,723,350],{},[32,711,712],{},"types.ts"," — ",[32,715,716],{},"NhnCredentials",[32,718,719],{},"NhnSmsSendRequest",[32,721,722],{},"NhnSmsSendResponse",[32,724,725],{},"NormalizedSendResult",[141,727,728,713,731,734,735,738,739,350],{},[32,729,730],{},"sms.ts",[32,732,733],{},"sendSms(creds, input, mockMode)",". mock 모드는 외부 호출 없이 시뮬레이션, real 모드는 ",[32,736,737],{},"\u002Fsms\u002Fv3.0\u002FappKeys\u002F{appKey}\u002Fsender\u002F(sms|mms)"," POST + ",[32,740,741],{},"X-Secret-Key",[141,743,744,524,747,750,751,487,754,757,758,487,761,764,765,768,769,768,772,775,776,30,779,782,783,350],{},[23,745,746],{},"Consumer worker",[32,748,749],{},"src\u002Fworkers\u002Fdispatch.ts",") — ",[32,752,753],{},"processDispatchBatch(batch, env)",[32,755,756],{},"processOne()"," 으로 메시지마다: dispatch_request 조회 → items + sender + nhn_credential 조회 → ",[32,759,760],{},"dispatch_state→'sending'",[32,762,763],{},"sendSms()"," → item별 ",[32,766,767],{},"send_state","·",[32,770,771],{},"fail_code",[32,773,774],{},"nhn_request_id"," 갱신 → ",[32,777,778],{},"dispatch_state→'delivered'\u002F'failed'",[32,780,781],{},"completed_at",". 일시 오류는 ",[32,784,785],{},"msg.retry({ delaySeconds: 30 })",[141,787,788,713,791,793,794,30,797,350],{},[23,789,790],{},"Producer 연동",[32,792,607],{}," 트랜잭션 commit 후 ",[32,795,796],{},"c.executionCtx.waitUntil(env.DISPATCH_QUEUE.send({ dispatchRequestId }))",[32,798,799],{},"dispatch_state→'queued'",[141,801,802,713,805,808],{},[23,803,804],{},"스키마 확장",[32,806,807],{},"nhnCredential"," 테이블 Drizzle 추가 (DATA-MODEL §10.5 반영).",[141,810,811,713,814,817,818,821],{},[23,812,813],{},"Workers entry 변경",[32,815,816],{},"src\u002Findex.ts"," 의 default export를 ",[32,819,820],{},"{ fetch: app.fetch, queue: processDispatchBatch }"," 객체로 (fetch + queue handler 분리).",[141,823,824,713,827,830],{},[23,825,826],{},"secret",[32,828,829],{},"NHN_MOCK=1"," wrangler secret 등록(프로덕션도 모의 모드로 시작).",[141,832,833,836,837,840],{},[23,834,835],{},"로컬 검증 한계"," — wrangler 경고 ",[32,838,839],{},"\"Queues are not yet supported in wrangler dev remote mode\""," → 로컬 dev에서 큐 처리 검증 불가.",[14,842,844,845,524,847,601],{"id":843},"_8-nhn-webhook-핸들러-post-webhooksnhnsms-malgn-noti-api-0d28173","8. NHN webhook 핸들러 — ",[32,846,62],{},[32,848,849],{},"malgn-noti-api 0d28173",[19,851,852,853,856,857,860],{},"NHN의 발송 결과 콜백을 받아 ",[32,854,855],{},"TB_DISPATCH_EVENT"," 적재 + ",[32,858,859],{},"TB_DISPATCH_ITEM.recv_state"," 갱신. 발송 흐름의 피드백 루프를 완성.",[862,863,865],"h3",{"id":864},"_81-흐름","8.1 흐름",[867,868,869,894,910,921,932,941],"ol",{},[141,870,871,713,874,877,878,881,882],{},[23,872,873],{},"HMAC-SHA256 서명 검증",[32,875,876],{},"X-NHN-Signature: sha256=\u003Chex>"," 헤더 + ",[32,879,880],{},"NHN_WEBHOOK_SIGNING_SECRET"," env.\n",[138,883,884,887],{},[141,885,886],{},"프로덕션: secret 미설정 → 403, 서명 불일치 → 401.",[141,888,889,890,893],{},"로컬 dev (",[32,891,892],{},"APP_ENV=local","): 서명 헤더 없으면 우회(개발 편의).",[141,895,896,713,899,487,902,905,906,909],{},[23,897,898],{},"payload 정규화",[32,900,901],{},"parseSmsCallback()",[32,903,904],{},"msgStatus"," (0\u002F1\u002F2\u002F3\u002F4) → ",[32,907,908],{},"eventType"," (accepted\u002Fsent\u002Fdelivered\u002Ffailed\u002Fbounced).",[141,911,912,713,915,30,917,920],{},[23,913,914],{},"dispatch_item lookup",[32,916,774],{},[32,918,919],{},"recipient_address"," 로 매핑.",[141,922,923,713,928,931],{},[23,924,925,927],{},[32,926,855],{}," INSERT",[32,929,930],{},"dedup_key = \"sms:\u003CrequestId>:\u003CrecipientNo>\"",". UNIQUE 위반 → 200 dedup (멱등).",[141,933,934,940],{},[23,935,936,939],{},[32,937,938],{},"recv_state"," 천이"," — delivered→success \u002F failed,bounced→failed \u002F 그 외 pending 유지.",[141,942,943,946,947,949],{},[23,944,945],{},"dispatch_request 마무리"," — 모든 item이 final 이면 ",[32,948,58],{}," 갱신(best-effort).",[862,951,953],{"id":952},"_82-신규-파일","8.2 신규 파일",[138,955,956,968,977,985],{},[141,957,958,713,961,964,965,517],{},[32,959,960],{},"src\u002Fdb\u002Fschema.ts",[32,962,963],{},"dispatchEvent"," (파티션 PK ",[32,966,967],{},"(id, received_at)",[141,969,970,713,973,976],{},[32,971,972],{},"src\u002Flib\u002Fwebhook-signature.ts",[32,974,975],{},"verifyHmacSignature()",", Web Crypto + timing-safe.",[141,978,979,713,982,984],{},[32,980,981],{},"src\u002Fadapters\u002Fnhn\u002Fwebhook.ts",[32,983,901],{}," (NHN payload → NormalizedEvent).",[141,986,987,713,990,350],{},[32,988,989],{},"src\u002Froutes\u002Fwebhooks.ts",[32,991,62],{},[19,993,994,713,997,1000,1001,1003,1004,713,1007,1010,1011,1014],{},[32,995,996],{},"index.ts",[32,998,999],{},"app.route('\u002Fwebhooks', webhooks)",", Bindings에 ",[32,1002,880],{}," 추가.\n",[32,1005,1006],{},"openapi.ts",[32,1008,1009],{},"webhooks"," 태그 + ",[32,1012,1013],{},"\u002Fwebhooks\u002Fnhn\u002Fsms"," 경로(요청·응답·서명 헤더 명세).",[862,1016,1018],{"id":1017},"_83-검증-보류","8.3 검증 보류",[19,1020,1021,1022,1025],{},"Cloudflare 원격 미리보기 인프라 장애(1105) 지속 — 로컬 ",[32,1023,1024],{},"pnpm dev"," 자체가 503 → 웹훅 라우트 검증 차단. 타입 검사·코드 패턴은 동일.",[19,1027,1028],{},"Cloudflare 회복 후 검증 절차:",[867,1030,1031,1037,1040,1053],{},[141,1032,1033,1034,601],{},"NHN_WEBHOOK_SIGNING_SECRET 등록(",[32,1035,1036],{},"wrangler secret put",[141,1038,1039],{},"외부에서 NHN 형식 + 서명 헤더로 POST",[141,1041,1042,1045,1046,30,1049,1052],{},[32,1043,1044],{},"\u002Fdispatch\u002Frequests\u002F:id\u002Fitems"," 에서 ",[32,1047,1048],{},"recv_state=success",[32,1050,1051],{},"received_at"," 확인",[141,1054,1055,1058,1059,1061],{},[32,1056,1057],{},"\u002Fadmin\u002F..."," 로 ",[32,1060,855],{}," 적재 행 확인",[14,1063,1065,1066,601],{"id":1064},"_9-malgn-noti-api-프로덕션-배포-6-history20260526md-231233","9. malgn-noti-api 프로덕션 배포 #6 (",[32,1067,1068],{},"history.20260526.md §23.1~23.3",[19,1070,1071],{},"§6·§7 변경을 라이브 반영.",[138,1073,1074,1083,1097,1118],{},[141,1075,1076,146,1079,1082],{},[23,1077,1078],{},"Version",[32,1080,1081],{},"b30dc2a3-dc5a-4050-a435-c3d03a5e69a7",". 번들 2460 KiB \u002F gzip 572. Worker Startup 74 ms.",[141,1084,1085,1086,30,1091,1096],{},"배포 명세에 ",[23,1087,1088],{},[32,1089,1090],{},"Producer for malgn-noti-dispatch",[23,1092,1093],{},[32,1094,1095],{},"Consumer for malgn-noti-dispatch"," 동시 등록 확인.",[141,1098,1099,1100,528,1103,1106,1107,1110,1111,1113,1114,1117],{},"검증 5건 — ",[32,1101,1102],{},"\u002Fhealth",[32,1104,1105],{},"\u002Fhealth\u002Fdb","(mysql 8.0.42), ",[32,1108,1109],{},"\u002Fdoc","(paths 44·ops 78·schemas 53), ",[32,1112,607],{}," no-auth 401, ",[32,1115,1116],{},"\u002Fauth\u002Flogin"," JWT 발급 정상.",[141,1119,1120,1123,1124,768,1127,1130,1131,1134],{},[23,1121,1122],{},"큐 e2e 검증 보류"," — Cloudflare 원격 미리보기 인프라 장애(1105 Temporarily unavailable, Ray ID ",[32,1125,1126],{},"a02212096ea185af",[32,1128,1129],{},"a02213318ecb85af"," 등 30분 지속) → 로컬 ",[32,1132,1133],{},"pnpm dev --remote"," 가 edge-preview 토큰을 받지 못해 prod Aurora 시드 SQL 송신 자체 실패. 코드·바인딩은 정상이며 Cloudflare 회복 후 외부에서 e2e 절차로 검증 가능.",[14,1136,1138,1139,601],{"id":1137},"_10-email-kakao-push-채널-추가-malgn-noti-api-06cf052","10. Email · Kakao · Push 채널 추가 (",[32,1140,1141],{},"malgn-noti-api 06cf052",[19,1143,1144,1145,1148],{},"§7 SMS 토대 위에 3개 추가 채널을 동일 패턴으로 적층 — ",[23,1146,1147],{},"producer · adapter · worker · OpenAPI"," 4지점 갱신.",[862,1150,1152],{"id":1151},"_101-공통-패턴","10.1 공통 패턴",[19,1154,1155,1156,1159],{},"모든 ",[32,1157,1158],{},"POST \u002Fsend\u002F{channel}"," 라우트는 §6 멱등 + §7 큐 패턴을 재사용:",[867,1161,1162,1170,1176,1189,1203,1214,1223],{},[141,1163,1164,1169],{},[23,1165,1166,1168],{},[32,1167,611],{}," 헤더 점유"," — TB_IDEMPOTENCY INSERT-then-conflict (race-free).",[141,1171,1172,1175],{},[23,1173,1174],{},"발신자 자격 검증"," — own + 채널별 승인 상태(아래 표).",[141,1177,1178,1181,1182,90,1185,1188],{},[23,1179,1180],{},"수신자 옵트아웃 필터"," — channelKind 키(",[32,1183,1184],{},"phone",[32,1186,1187],{},"email",")로 TB_OPTOUT_ENTRY 매칭. Push만 예외(앱 내 설정 책임).",[141,1190,1191,1194,1195,1198,1199,1202],{},[23,1192,1193],{},"단가·총액 계산"," + 크레딧 hold (",[32,1196,1197],{},"TB_COMPANY.credit_balance"," UPDATE…WHERE balance ≥ amount + ",[32,1200,1201],{},"TB_CREDIT_LEDGER"," append).",[141,1204,1205,1213],{},[23,1206,1207,30,1210],{},[32,1208,1209],{},"TB_DISPATCH_REQUEST",[32,1211,1212],{},"TB_DISPATCH_ITEM"," bulk insert (단일 트랜잭션).",[141,1215,1216,1222],{},[23,1217,1218,1221],{},[32,1219,1220],{},"TB_IDEMPOTENCY.result_id"," = 발송 ID"," (트랜잭션 안에서).",[141,1224,1225,30,1228,1231,1232,517],{},[23,1226,1227],{},"큐 enqueue",[32,1229,1230],{},"dispatch_state=queued"," 천이 (",[32,1233,1234],{},"executionCtx.waitUntil",[862,1236,1238],{"id":1237},"_102-채널별-발신자옵트아웃단가","10.2 채널별 발신자·옵트아웃·단가",[1240,1241,1242,1267],"table",{},[1243,1244,1245],"thead",{},[1246,1247,1248,1252,1255,1258,1261,1264],"tr",{},[1249,1250,1251],"th",{},"채널",[1249,1253,1254],{},"senderRef",[1249,1256,1257],{},"검증",[1249,1259,1260],{},"옵트아웃 키",[1249,1262,1263],{},"단가(1건)",[1249,1265,1266],{},"추가 검증",[1268,1269,1270,1300,1334],"tbody",{},[1246,1271,1272,1277,1282,1287,1291,1294],{},[1273,1274,1275],"td",{},[32,1276,1187],{},[1273,1278,1279],{},[32,1280,1281],{},"emailDomainId",[1273,1283,1284],{},[32,1285,1286],{},"verified_yn=Y",[1273,1288,1289],{},[32,1290,1187],{},[1273,1292,1293],{},"0.65",[1273,1295,1296,1299],{},[32,1297,1298],{},"from","의 도메인이 emailDomain.domain과 일치",[1246,1301,1302,1307,1312,1320,1324,1327],{},[1273,1303,1304],{},[32,1305,1306],{},"kakao",[1273,1308,1309],{},[32,1310,1311],{},"kakaoSenderProfileId",[1273,1313,1314,30,1317],{},[32,1315,1316],{},"profile_state=승인",[32,1318,1319],{},"token_state=완료",[1273,1321,1322],{},[32,1323,1184],{},[1273,1325,1326],{},"alimtalk 8 \u002F friendtalk 12",[1273,1328,1329,1330,1333],{},"알림톡은 ",[32,1331,1332],{},"templateCode"," 필수(Zod refine)",[1246,1335,1336,1341,1346,1355,1358,1361],{},[1273,1337,1338],{},[32,1339,1340],{},"push",[1273,1342,1343],{},[32,1344,1345],{},"pushCertId",[1273,1347,1348,30,1351,1354],{},[32,1349,1350],{},"status=1",[32,1352,1353],{},"expires_at"," 미경과",[1273,1356,1357],{},"(없음)",[1273,1359,1360],{},"0.5",[1273,1362,1363,1366],{},[32,1364,1365],{},"recipients[].token"," 8~255자 device token",[862,1368,1370],{"id":1369},"_103-어댑터-신규-파일","10.3 어댑터 (신규 파일)",[138,1372,1373,1393,1417],{},[141,1374,1375,1380,1381,1384,1385,1388,1389,1392],{},[23,1376,1377],{},[32,1378,1379],{},"src\u002Fadapters\u002Fnhn\u002Femail.ts"," — NHN ",[32,1382,1383],{},"\u002Femail\u002Fv2.1\u002FappKeys\u002F{appKey}\u002Fsender\u002Fmail"," POST. ",[32,1386,1387],{},"receiverList[].receiveType='MRT0'","(TO). mock 시 ",[32,1390,1391],{},"mock-email-\u003Cuuid>"," requestId.",[141,1394,1395,1400,1401,1404,1405,1408,1409,1412,1413,1416],{},[23,1396,1397],{},[32,1398,1399],{},"src\u002Fadapters\u002Fnhn\u002Fkakao.ts"," — 알림톡 ",[32,1402,1403],{},"\u002Falimtalk\u002Fv2.3\u002F..."," + 친구톡 ",[32,1406,1407],{},"\u002Ffriendtalk\u002Fv2.0\u002F..."," 분기. ",[32,1410,1411],{},"senderKey"," 는 ",[32,1414,1415],{},"TB_KAKAO_SENDER_PROFILE.send_key_enc"," (평문 가정 — envelope 복호화는 TODO).",[141,1418,1419,1380,1424,1384,1427,1430],{},[23,1420,1421],{},[32,1422,1423],{},"src\u002Fadapters\u002Fnhn\u002Fpush.ts",[32,1425,1426],{},"\u002Fpush\u002Fv2.5\u002Fappkeys\u002F{appKey}\u002Fmessages",[32,1428,1429],{},"target.type='TOKEN'",". NHN Push는 messageId 1개만 반환 → 개별 토큰 결과는 webhook(후속)으로 갱신.",[19,1432,1433,1434,1437,1438,1441,1442,1445],{},"기존 ",[32,1435,1436],{},"src\u002Fadapters\u002Fnhn\u002Ftypes.ts"," 의 ",[32,1439,1440],{},"NormalizedSendResult.perRecipient"," 필드 ",[32,1443,1444],{},"phone → address"," 로 리네임 → 채널 무관(phone\u002Femail\u002Ftoken). SMS 어댑터도 동반 수정.",[862,1447,1449],{"id":1448},"_104-worker-분기","10.4 worker 분기",[19,1451,1452,1454,1455,1458],{},[32,1453,749],{}," — 조건 ",[32,1456,1457],{},"channel !== 'sms' && !== 'email' && !== 'kakao' && !== 'push'"," 외 skip. channel별 spec 파싱 + 어댑터 호출:",[1460,1461,1466],"pre",{"className":1462,"code":1464,"language":1465},[1463],"language-text","sms   → senderRefId → TB_SENDER_PHONE.number\nemail → senderRefId → TB_EMAIL_DOMAIN (존재만 확인, verified는 producer 단계에서 검증 완료)\nkakao → senderRefId → TB_KAKAO_SENDER_PROFILE.send_key_enc → senderKey\npush  → senderRefId → TB_PUSH_CERT (존재만 확인)\n","text",[32,1467,1464],{"__ignoreMap":1468},"",[19,1470,1471,1472,1475],{},"credential 조회는 ",[32,1473,1474],{},"nhn_credential.channel = dr.channel"," 로 generic 변경(SMS 하드코딩 제거).",[862,1477,1479],{"id":1478},"_105-단가-정의","10.5 단가 정의",[19,1481,1482,1485],{},[32,1483,1484],{},"src\u002Flib\u002Fpricing.ts"," 확장:",[1460,1487,1491],{"className":1488,"code":1489,"language":1490,"meta":1468,"style":1468},"language-ts shiki shiki-themes github-light github-dark","export const EMAIL_PRICING = 0.65\nexport const KAKAO_PRICING = { alimtalk: 8, friendtalk: 12 } as const\nexport const PUSH_PRICING = 0.5\n","ts",[32,1492,1493,1515,1549],{"__ignoreMap":1468},[1494,1495,1498,1502,1505,1509,1512],"span",{"class":1496,"line":1497},"line",1,[1494,1499,1501],{"class":1500},"szBVR","export",[1494,1503,1504],{"class":1500}," const",[1494,1506,1508],{"class":1507},"sj4cs"," EMAIL_PRICING",[1494,1510,1511],{"class":1500}," =",[1494,1513,1514],{"class":1507}," 0.65\n",[1494,1516,1518,1520,1522,1525,1527,1531,1534,1537,1540,1543,1546],{"class":1496,"line":1517},2,[1494,1519,1501],{"class":1500},[1494,1521,1504],{"class":1500},[1494,1523,1524],{"class":1507}," KAKAO_PRICING",[1494,1526,1511],{"class":1500},[1494,1528,1530],{"class":1529},"sVt8B"," { alimtalk: ",[1494,1532,1533],{"class":1507},"8",[1494,1535,1536],{"class":1529},", friendtalk: ",[1494,1538,1539],{"class":1507},"12",[1494,1541,1542],{"class":1529}," } ",[1494,1544,1545],{"class":1500},"as",[1494,1547,1548],{"class":1500}," const\n",[1494,1550,1552,1554,1556,1559,1561],{"class":1496,"line":1551},3,[1494,1553,1501],{"class":1500},[1494,1555,1504],{"class":1500},[1494,1557,1558],{"class":1507}," PUSH_PRICING",[1494,1560,1511],{"class":1500},[1494,1562,1563],{"class":1507}," 0.5\n",[19,1565,1566,1567,1570,1571,1574,1575,1578],{},"프론트엔드 ",[32,1568,1569],{},"app\u002Ftypes\u002Fchannel.ts","의 ",[32,1572,1573],{},"*_META.pricePerUnit"," 와 동기. 실제 운영 시 ",[32,1576,1577],{},"TB_PRICING"," 테이블로 이전 예정.",[862,1580,1582],{"id":1581},"_106-openapi","10.6 OpenAPI",[19,1584,1585,713,1588,222,1591,222,1594,1597,1598,222,1601,222,1604,1607,1608,1611],{},[32,1586,1587],{},"src\u002Fopenapi.ts",[32,1589,1590],{},"SendEmailRequest",[32,1592,1593],{},"SendKakaoRequest",[32,1595,1596],{},"SendPushRequest"," 스키마 + ",[32,1599,1600],{},"\u002Fsend\u002Femail",[32,1602,1603],{},"\u002Fsend\u002Fkakao",[32,1605,1606],{},"\u002Fsend\u002Fpush"," 경로 추가. 응답은 SMS와 동일한 ",[32,1609,1610],{},"SendResponse"," 재사용.",[14,1613,1615],{"id":1614},"_11-malgn-noti-api-프로덕션-배포-7","11. malgn-noti-api 프로덕션 배포 #7",[19,1617,1618],{},"§10 변경을 라이브 반영.",[138,1620,1621,1629,1639,1642,1667],{},[141,1622,1623,146,1625,1628],{},[23,1624,1078],{},[32,1626,1627],{},"12dae362-2900-42ca-9a2e-e6dfb9c60091",". 번들 2508 KiB \u002F gzip 579. Worker Startup 80 ms.",[141,1630,1631,1632,1635,1636,517],{},"명령: ",[32,1633,1634],{},"pnpm run deploy"," (자동 ",[32,1637,1638],{},"wrangler deploy",[141,1640,1641],{},"바인딩: DISPATCH_QUEUE · HYPERDRIVE(a2ba4efe...) · APP_ENV=production · NHN_MOCK=1(default).",[141,1643,1644,1645],{},"검증 3건:\n",[138,1646,1647,1655,1662],{},[141,1648,1649,1651,1652],{},[32,1650,1102],{}," → 200, ",[32,1653,1654],{},"{\"ok\":true,\"env\":\"production\",\"time\":\"2026-05-27T04:42:50.327Z\"}",[141,1656,1657,1651,1659],{},[32,1658,1105],{},[32,1660,1661],{},"{\"ok\":true,\"mysql_version\":\"8.0.42\",...}",[141,1663,1664,1666],{},[32,1665,1109],{}," → 200 (Scalar UI 페이지 응답)",[141,1668,1669],{},"큐 e2e + 외부 NHN 자격증명 연결은 별도 작업(미수행).",[578,1671],{},[10,1673,1675],{"id":1674},"트랙-c-malgn-noti-사용자단-추가-작업","트랙 C — malgn-noti 사용자단 추가 작업",[1677,1678,1679],"blockquote",{},[19,1680,1681,1682,1685],{},"비고: 본 트랙의 §12·§13은 같은 날(2026-05-27) 작업이지만 세션 도중 시스템 날짜 알림 혼선으로 일부 세부 섹션이 ",[32,1683,1684],{},"history.20260521.md §22~§25","로 먼저 기록됨. 거기에는 화면별 상세 + 동시 작업 격리 절차가 남아 있고, 본 파일에는 요약·배포·커밋만 둠.",[14,1687,1689],{"id":1688},"_12-사용자단-추가-랜딩페이지-만들기문의-경로-이동나의-페이지충전-배포-444546","12. 사용자단 추가 — 랜딩페이지 만들기·문의 경로 이동·나의 페이지·충전 (배포 #44·#45·#46)",[138,1691,1692,1725,1765],{},[141,1693,1694,524,1697,1702,1703,1706,1707,768,1710,1713,1714,1717,1718,1721,1722,350],{},[23,1695,1696],{},"#44 — 메시지 관리 \u002F 랜딩페이지 만들기",[1698,1699,1701],"a",{"href":1700},"..\u002F..\u002Fapp\u002Fpages\u002Fmanage\u002Flanding.vue","\u002Fmanage\u002Flanding","): GNB 메시지 관리 메뉴 ",[32,1704,1705],{},"상세 설정"," 위에 신규. C 테이블 스타일 목록(공개여부 필터·이름 검색·선택 복사\u002F삭제·",[32,1708,1709],{},"미리보기",[32,1711,1712],{},"URL 복사",") + 기본형\u002F확장형 등록·수정 폼 뷰 전환(공개 여부 토글·랜딩페이지명·URL·메인 타이틀·확장형 비주얼 이미지·콘텐츠 영역·확장형 CTA 버튼) + ",[32,1715,1716],{},"AppLandingPreviewDialog","(LIVELY SHOP 빅세일 샘플 렌더, width 960·min-height 74vh) + ",[32,1719,1720],{},"AppLandingUrlDialog","(URL 복사 완료). alias ",[32,1723,1724],{},"https:\u002F\u002F184b0fe1.malgn-noti.pages.dev",[141,1726,1727,1733,1734,1737,1738,1741,1742,768,1746,768,1750,1570,1754,1757,1758,1761,1762,350],{},[23,1728,1729,1730,1732],{},"#45 — 문의하기 페이지 ",[32,1731,82],{}," 경로로 이동",": 문의 폼·완료 페이지를 ",[32,1735,1736],{},"app\u002Fpages\u002Faccount\u002Finquiry\u002F"," 하위로 이동, 중복되던 별도 문의 목록 페이지 삭제(나의 문의 목록은 ",[32,1739,1740],{},"\u002Faccount\u002Finquiries","에 별도 작업). ",[1698,1743,1745],{"href":1744},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppGnb.vue","AppGnb",[1698,1747,1749],{"href":1748},"..\u002F..\u002Fapp\u002Fcomponents\u002FAppFooter.vue","AppFooter",[1698,1751,1753],{"href":1752},"..\u002F..\u002Fapp\u002Fpages\u002Fsitemap.vue","sitemap.vue",[32,1755,1756],{},"\u002Finquiry"," 링크를 새 경로로 갱신. 동시 진행 중인 '나의 페이지'·충전 작업분이 working tree에 섞여 있어 임시 git worktree(",[32,1759,1760],{},"a021e2b"," 체크아웃)에서 문의 변경만 격리 빌드 후 배포. alias ",[32,1763,1764],{},"https:\u002F\u002Faa4503b7.malgn-noti.pages.dev",[141,1766,1767,1770,1771,1774,1775,1778,1779,67,1782,768,1785,768,1788,768,1791,768,1794,1797,1798,1802,1803,1807,1808,350],{},[23,1768,1769],{},"#46 — 나의 페이지 섹션 + 크레딧 충전 플로우",": 계정 관리를 ",[32,1772,1773],{},"나의 페이지","로 개편. ",[32,1776,1777],{},"AppMyPageShell","(공통 셸 + 좌측 메뉴 9종) + 라우트 9개(",[32,1780,1781],{},"\u002Faccount\u002F{settings,cards,password,security,multi,contract,credit,billing,inquiries}",[32,1783,1784],{},"AppMemberInfoPanel",[32,1786,1787],{},"AppCardListPanel",[32,1789,1790],{},"AppEmailChangeDialog",[32,1792,1793],{},"AppPhoneVerifyDialog",[32,1795,1796],{},"AppCardAddDialog",". 크레딧 충전은 ",[1698,1799,1801],{"href":1800},"..\u002F..\u002Fapp\u002Fpages\u002Fcharge\u002Findex.vue","\u002Fcharge"," 시안 기반 재구성(충전 금액 선택·결제 카드 등록·결제 및 환불안내·동의) + 진행 컨펌 모달 + ",[1698,1804,1806],{"href":1805},"..\u002F..\u002Fapp\u002Fpages\u002Fcharge\u002Fresult.vue","\u002Fcharge\u002Fresult"," 완료 화면. alias ",[32,1809,1810],{},"https:\u002F\u002Ffcb87146.malgn-noti.pages.dev",[14,1812,1814],{"id":1813},"_13-malgn-noti-api-루트-doc-리다이렉트","13. malgn-noti-api 루트(\u002F) → \u002Fdoc 리다이렉트",[138,1816,1817,1831,1852],{},[141,1818,1819,1823,1824,1826,1827,1830],{},[1698,1820,1822],{"href":1821},"..\u002F..\u002F..\u002Fmalgn-noti-api\u002Fsrc\u002Findex.ts","src\u002Findex.ts:55"," 의 placeholder JSON 핸들러를 ",[32,1825,94],{}," 한 줄로 교체 — ",[32,1828,1829],{},"https:\u002F\u002Fmalgn-noti-api.malgnsoft.workers.dev\u002F"," 접속이 곧장 Scalar API 문서로 302 이동.",[141,1832,1833,1834,90,1837,1840,1841,1843,1844,1847,1848,1851],{},"동시 진행 중인 API 작업분(NHN webhook·send·schema·dispatch worker·",[32,1835,1836],{},"flow-definitions",[32,1838,1839],{},"export-jobs"," 신규 라우트)이 working tree에 섞여 있어 — 배포는 working tree 기준이라 함께 라이브에 올라갔으나(typecheck 통과·",[32,1842,1102],{}," 정상), 커밋은 임시 ",[32,1845,1846],{},"git checkout HEAD -- src\u002Findex.ts","로 베이스라인 복원 → 리다이렉트만 재적용 → stage·commit 후 WIP 복원 방식으로 ",[23,1849,1850],{},"리다이렉트 한 줄만"," 격리 기록.",[141,1853,1854,1855,307,1858,1861,1862,528,1865,1868,1869,1872],{},"Version ",[32,1856,1857],{},"f3fd3eb4-c594-471c-949a-f61ba1b30db1",[32,1859,1860],{},"GET \u002F"," → 302 ",[32,1863,1864],{},"Location: \u002Fdoc",[32,1866,1867],{},"GET \u002Fdoc"," → 200 (Scalar UI), ",[32,1870,1871],{},"GET \u002Fhealth"," → 200 production 확인.",[14,1874,1876],{"id":1875},"_14-malgn-noti-api-13-wip-정식-커밋-rcs-채널-export-잡-flow-정의-배포-8","14. malgn-noti-api §13 WIP 정식 커밋 — RCS 채널 + Export 잡 + Flow 정의 + 배포 #8",[19,1878,1879,1880,1883],{},"§13 시점에 working tree에 라이브로 올라가 있었으나 커밋 격리로 인해 ",[32,1881,1882],{},"main","과 어긋난 채 남아 있던 3 도메인 슬라이스를 단일 배치로 정리 — 코드 + 신규 마이그레이션 SQL + Workers 재배포까지 일관화.",[862,1885,1887],{"id":1886},"_141-rcs-채널-5채널째-smsemailkakaopush-잇는","14.1 RCS 채널 (5채널째 — sms·email·kakao·push 잇는)",[138,1889,1890,1913,1930,1940,1952,1957],{},[141,1891,1892,1895,1896,1899,1900,1902,1903,1906,1907,877,1909,1912],{},[32,1893,1894],{},"src\u002Fadapters\u002Fnhn\u002Frcs.ts"," 신규 — NHN ",[32,1897,1898],{},"\u002Frcs-biz\u002Fv2.0"," 4타입(sms\u002Flms\u002Fmms\u002Ftemplate) 어댑터. ",[32,1901,829],{}," 또는 ",[32,1904,1905],{},"brandId"," 미설정 시 mock 응답. 실 모드는 ",[32,1908,741],{},[32,1910,1911],{},"chatbotId"," 발신.",[141,1914,1915,1917,1918,768,1921,768,1924,768,1927,1929],{},[32,1916,981],{}," — RCS 콜백 파서 추가(",[32,1919,1920],{},"nhnRequestId",[32,1922,1923],{},"recipientNo",[32,1925,1926],{},"resultCode",[32,1928,938],{}," 천이).",[141,1931,1932,1935,1936,1939],{},[32,1933,1934],{},"src\u002Froutes\u002Fsend.ts"," (+227) — RCS producer. 발신 RCS 브랜드(",[32,1937,1938],{},"TB_RCS_BRAND",") 검증 + 옵트아웃 필터 + 크레딧 hold + 트랜잭션 적재(기존 SMS\u002FEmail\u002FKakao\u002FPush 패턴 generic 재사용).",[141,1941,1942,713,1944,1947,1948,1951],{},[32,1943,989],{},[32,1945,1946],{},"POST \u002Fwebhooks\u002Fnhn\u002Frcs"," (기존 SMS 핸들러와 동일 HMAC-SHA256 검증 + ",[32,1949,1950],{},"dedup_key"," 멱등).",[141,1953,1954,1956],{},[32,1955,749],{}," — Consumer에 RCS 채널 분기 추가.",[141,1958,1959,713,1961,1964,1965,1967],{},[32,1960,1484],{},[32,1962,1963],{},"RCS_PRICING = { sms: 12, lms: 40, mms: 120, template: 50 }"," (frontend ",[32,1966,1569],{}," RCS_META 참조).",[862,1969,1971],{"id":1970},"_142-export-잡-비동기-다운로드","14.2 Export 잡 (비동기 다운로드)",[138,1973,1974,1981,2003,2013],{},[141,1975,1976,1977,1980],{},"시안의 \"",[23,1978,1979],{},"다운로드 요청 PU + 목록 PU","\" 패턴 + 90일 윈도우 우회 경로.",[141,1982,1983,713,1985,1987,1988,1991,1992,1995,1996,1999,2000,2002],{},[32,1984,960],{},[32,1986,109],{}," 비파티션. ",[32,1989,1990],{},"resource_type"," 7종(history_sms\u002Femail\u002Fkakao\u002Fpush\u002Frcs + contacts + credit_ledger) · ",[32,1993,1994],{},"job_state"," 5단계(pending → running → ready \u002F failed \u002F expired) · ",[32,1997,1998],{},"r2_key","(R2 결과 위치) · ",[32,2001,1353],{},"(등록 +30일).",[141,2004,2005,2008,2009,2012],{},[32,2006,2007],{},"src\u002Froutes\u002Fexport-jobs.ts"," 신규 — POST 등록 \u002F GET 목록(커서·필터) \u002F GET 단건(ready면 ",[32,2010,2011],{},"downloadUrl"," placeholder 노출) \u002F DELETE soft.",[141,2014,2015],{},"처리 worker + R2 presigned URL 발급은 후속.",[862,2017,2019],{"id":2018},"_143-flow-정의-복합-발송-그래프","14.3 Flow 정의 (복합 발송 그래프)",[138,2021,2022,2038,2051],{},[141,2023,2024,713,2026,2029,2030,2033,2034,2037],{},[32,2025,960],{},[32,2027,2028],{},"TB_FLOW_DEFINITION","(nodes JSON) + ",[32,2031,2032],{},"TB_FLOW_RUN","(1회 실행 인스턴스) + ",[32,2035,2036],{},"TB_FLOW_STEP_RUN","(노드 단위). 3 테이블 모두 비파티션 + 인덱스.",[141,2039,2040,2043,2044,2047,2048,517],{},[32,2041,2042],{},"src\u002Froutes\u002Fflow-definitions.ts"," 신규 — Zod superRefine로 노드 검증(order 0부터 연속 + 첫 노드 ",[32,2045,2046],{},"condition='always'"," 강제 + 채널 5종 enum + ",[32,2049,2050],{},"delayMinutes ≤ 7일",[141,2052,2053,2054,2057,2058,2060],{},"실행 엔진(",[32,2055,2056],{},"POST \u002Fsend\u002Fflow",")·",[32,2059,2036],{}," 천이는 후속.",[862,2062,2064],{"id":2063},"_144-신규-마이그레이션","14.4 신규 마이그레이션",[138,2066,2067,2073],{},[141,2068,2069,2072],{},[32,2070,2071],{},"src\u002Fdb\u002Fmigrations\u002F0002_export_flow.sql"," 신규 — TB_EXPORT_JOB + TB_FLOW_DEFINITION + TB_FLOW_RUN + TB_FLOW_STEP_RUN (인덱스 4종 포함).",[141,2074,2075,713,2078,2081,2082,2084,2085,2088,2089,768,2091,2093,2094,2097],{},[23,2076,2077],{},"DDL 적용 보류",[32,2079,2080],{},"wrangler dev --remote","가 Cloudflare API edge-preview 호출에서 실패(1105 잔류). 1105 회복 후 ",[32,2083,1133],{}," 띄워 ",[32,2086,2087],{},"\u002Fadmin\u002Fmigrate","에 POST 예정. 그동안 ",[32,2090,112],{},[32,2092,119],{},"는 라이브 5xx(table not found) 가능 — ",[23,2095,2096],{},"프런트 호출처 0개","로 무영향.",[862,2099,2101],{"id":2100},"_145-openapi-4지점-갱신","14.5 OpenAPI 4지점 갱신",[138,2103,2104],{},[141,2105,2106,2108,2109,2111],{},[32,2107,1587],{}," (+230) — RCS 발송·Export 잡 CRUD·Flow 정의 CRUD 문서화. Scalar UI(",[32,2110,1109],{},")에서 즉시 확인 가능.",[862,2113,2115],{"id":2114},"_146-배포-8-검증","14.6 배포 #8 + 검증",[138,2117,2118,2131,2138],{},[141,2119,2120,2123,2124,2128,2129,350],{},[32,2121,2122],{},"wrangler whoami"," OAuth 토큰 재인증(",[1698,2125,2127],{"href":2126},"mailto:info@malgnsoft.com","info@malgnsoft.com",") 선행 후 ",[32,2130,1634],{},[141,2132,2133,2134,2137],{},"Workers Version ",[32,2135,2136],{},"95f9f894-4d6c-419d-9cec-8bc7f6c37999",". Total Upload 2549.53 KiB \u002F gzip 583.70 KiB, Startup 78 ms.",[141,2139,2140,2141,2143,2144,2146,2147,768,2149,2151],{},"검증: ",[32,2142,1102],{}," 200(env=production), ",[32,2145,1105],{}," 200(mysql 8.0.42), ",[32,2148,112],{},[32,2150,119],{}," 모두 401(auth 가드 작동 — DDL 부재가 트리거되지 않음).",[862,2153,2155],{"id":2154},"_147-정리-working-tree-main-동기","14.7 정리 — working tree ↔ main 동기",[138,2157,2158],{},[141,2159,2160,2161,2164,2165,517],{},"단일 배치 커밋 ",[32,2162,2163],{},"malgn-noti-api: 1e7bd61 feat: RCS 채널 + Export 잡 + Flow 정의 — 3 도메인 슬라이스"," (12 files, +1228 −16). origin\u002Fmain 푸시 완료(",[32,2166,2167],{},"677dffa..1e7bd61",[14,2169,534],{"id":2170},"산출물-1",[862,2172,2174],{"id":2173},"트랙-a-admin","트랙 A (admin)",[138,2176,2177,2201,2205,2209],{},[141,2178,539,2179,542,2181,222,2183,222,2185,222,2187,222,2189,222,2191,222,2193,222,2195,222,2197,222,2199,564],{},[32,2180,180],{},[32,2182,213],{},[32,2184,243],{},[32,2186,264],{},[32,2188,516],{},[32,2190,274],{},[32,2192,291],{},[32,2194,300],{},[32,2196,339],{},[32,2198,561],{},[32,2200,423],{},[141,2202,567,2203,570],{},[32,2204,192],{},[141,2206,573,2207,576],{},[32,2208,42],{},[141,2210,509,2211,513,2214,517],{},[32,2212,2213],{},"malgn-noti-admin: 6cbf238 Nuxt 3 + Nuxt UI v3 부트스트랩 + LNB·TopBar 셸 레이아웃",[32,2215,516],{},[862,2217,2219],{"id":2218},"트랙-b-api","트랙 B (api)",[138,2221,2222,2231,2242,2257,2266,2280],{},[141,2223,2224,2227,2228,2230],{},[32,2225,2226],{},"malgn-noti-api: 020307f fix(idempotency): TB_IDEMPOTENCY + INSERT-then-conflict 패턴 — 멱등 버그 해결"," (4 files, +151 −74). ",[32,2229,619],{}," 신규.",[141,2232,2233,2236,2237,768,2240,2230],{},[32,2234,2235],{},"malgn-noti-api: 5e1ac72 feat(send): NHN SMS 어댑터 + Cloudflare Queues + consumer worker (mock 모드)"," (8 files, +439 −5). ",[32,2238,2239],{},"src\u002Fadapters\u002Fnhn\u002F{types,sms}.ts",[32,2241,749],{},[141,2243,2244,2247,2248,2250,2251,222,2253,222,2255,2230],{},[32,2245,2246],{},"malgn-noti-api: 0d28173 feat(webhooks): POST \u002Fwebhooks\u002Fnhn\u002Fsms — NHN 발송 결과 콜백 수신"," (6 files). ",[32,2249,960],{}," (dispatchEvent) · ",[32,2252,972],{},[32,2254,981],{},[32,2256,989],{},[141,2258,2259,2262,2263,2230],{},[32,2260,2261],{},"malgn-noti-api: 06cf052 feat(send): Email · Kakao · Push 채널 추가 — 3채널 producer + adapter + worker"," (9 files, +1211 −50). ",[32,2264,2265],{},"src\u002Fadapters\u002Fnhn\u002F{email,kakao,push}.ts",[141,2267,2268,2269,689,2271,2273,2274,2276,2277,2279],{},"Cloudflare Queue ",[32,2270,54],{},[32,2272,692],{},") + Workers Version ",[32,2275,1081],{}," (배포 #6) → ",[32,2278,1627],{}," (배포 #7).",[141,2281,2282,2284],{},[32,2283,589],{}," 에 트랙 B 상세 기록.",[862,2286,2288],{"id":2287},"트랙-c-사용자단-추가","트랙 C (사용자단 추가)",[138,2290,2291,2299,2307,2315,2323],{},[141,2292,2293,2296,2297,601],{},[32,2294,2295],{},"malgn-noti: 265395a"," 랜딩페이지 만들기 — 목록·등록\u002F수정 폼·미리보기 신규 구성 (배포 #44, alias ",[32,2298,1724],{},[141,2300,2301,2304,2305,601],{},[32,2302,2303],{},"malgn-noti: a021e2b"," 문의하기 페이지를 \u002Faccount\u002Finquiry 경로로 이동 (배포 #45, alias ",[32,2306,1764],{},[141,2308,2309,2312,2313,601],{},[32,2310,2311],{},"malgn-noti: 83c4c37"," 나의 페이지 섹션 + 크레딧 충전 플로우 신규 구성 (배포 #46, alias ",[32,2314,1810],{},[141,2316,2317,2320,2321,601],{},[32,2318,2319],{},"malgn-noti-api: 677dffa"," 루트(\u002F) 요청을 API 문서(\u002Fdoc)로 302 리다이렉트 (Workers Version ",[32,2322,1857],{},[141,2324,2325,2326,2328],{},"화면·격리 절차 상세는 ",[32,2327,1684],{}," 에 기록(세션 도중 시스템 날짜 알림 혼선으로 1차 작성된 위치).",[862,2330,2332],{"id":2331},"트랙-e-api-13-wip-정식-커밋-rcsexportflow","트랙 E (api §13 WIP 정식 커밋 — RCS·Export·Flow)",[138,2334,2335,2372,2393],{},[141,2336,2337,2339,2340,222,2342,222,2345,2347,2348,2350,2351,2353,2354,2356,2357,2359,2360,2362,2363,222,2365,222,2367,222,2369,2371],{},[32,2338,2163],{}," (12 files, +1228 −16). 신규: ",[32,2341,1894],{},[32,2343,2344],{},"src\u002Froutes\u002F{export-jobs,flow-definitions}.ts",[32,2346,2071],{},". 수정: ",[32,2349,960],{},"(4 테이블 추가) · ",[32,2352,1587],{},"(+230) · ",[32,2355,1934],{},"(+227 RCS producer) · ",[32,2358,1484],{},"(",[32,2361,105],{},") · ",[32,2364,981],{},[32,2366,989],{},[32,2368,749],{},[32,2370,816],{},"(라우트 마운트).",[141,2373,2133,2374,2376,2377,2379,2380,307,2382,768,2384,768,2386,768,2389,2392],{},[32,2375,2136],{}," (배포 #8). ",[32,2378,2122],{}," 재인증 후 ",[32,2381,1634],{},[32,2383,1102],{},[32,2385,1105],{},[32,2387,2388],{},"\u002Fexport-jobs(401)",[32,2390,2391],{},"\u002Fflow-definitions(401)"," 검증.",[141,2394,2395,146,2398,2400,2401,2403,2404,2406],{},[23,2396,2397],{},"DDL 보류",[32,2399,123],{}," 적용은 ",[32,2402,2080],{},"가 Cloudflare 1105로 막혀 있어 1105 회복 후 ",[32,2405,2087],{},"로 적용 예정.",[14,2408,2410],{"id":2409},"다음-단계-알려진-한계","다음 단계 \u002F 알려진 한계",[862,2412,2174],{"id":2413},"트랙-a-admin-1",[138,2415,2416,2438,2444,2461,2471],{},[141,2417,2418,2419,528,2422,528,2425,528,2428,528,2431,528,2434,2437],{},"셸만 구성 — 각 라우트(",[32,2420,2421],{},"\u002Fcustomer",[32,2423,2424],{},"\u002Fsend",[32,2426,2427],{},"\u002Ftemplate",[32,2429,2430],{},"\u002Freport",[32,2432,2433],{},"\u002Fbilling",[32,2435,2436],{},"\u002Fops"," …)의 페이지\u002F컴포넌트는 미구현. LNB 메뉴 항목은 시각 동작만(클릭 시 라우트 이동 없음, 액티브 키만 변경).",[141,2439,2440,2443],{},[32,2441,2442],{},"MemoPanel","(우측 360px sticky)·고객사 상세의 InfoCard·계정 테이블·BarChart·ActivityList·권한 변경 모달 — 핸드오프 정본에 있으나 셸 범위 밖.",[141,2445,2446,2448,2449,2452,2453,2456,2457,2460],{},[32,2447,188],{},"의 primary ",[32,2450,2451],{},"#6366f1","(indigo)과 부트스트랩 정본 ",[32,2454,2455],{},"blue(#3b82f6)"," ",[23,2458,2459],{},"불일치"," — 핸드오프를 우선해 구현, DESIGN-ADMIN.md 정합화는 후속 작업.",[141,2462,2463,2466,2467,2470],{},[32,2464,2465],{},"malgn-noti-admin\u002FCLAUDE.md","에 사용자단 §7.1 유사한 ",[23,2468,2469],{},"배포·Git·작업 이력 운영 컨벤션"," 미수록 — 후속 작성 필요(현재는 사용자단 컨벤션을 준용해 진행).",[141,2472,2473,2474,2477],{},"인증 미들웨어·RBAC·",[32,2475,2476],{},"Cmd+K"," 명령 팔레트·다크 모드 — 모두 셸 이후 별도 작업.",[862,2479,2219],{"id":2480},"트랙-b-api-1",[138,2482,2483,2492,2505,2518,2528,2531,2538,2541],{},[141,2484,2485,2486,2488,2489,2491],{},"큐 e2e 검증 — Cloudflare 1105 회복 후 ",[32,2487,1024],{}," + 시드 + PROD URL 발송 → ",[32,2490,58],{}," 천이 추적.",[141,2493,2494,2496,2497,2499,2500,768,2502,2504],{},[32,2495,123],{}," DDL 적용 — 1105 회복 후 ",[32,2498,2087],{},"로 4 테이블 생성. 그 전까지 ",[32,2501,112],{},[32,2503,119],{}," 호출 시 5xx(table not found).",[141,2506,2507,2508,222,2511,222,2514,2517],{},"채널별 webhook — ",[32,2509,2510],{},"\u002Fwebhooks\u002Fnhn\u002Femail",[32,2512,2513],{},"\u002Fwebhooks\u002Fnhn\u002Fkakao",[32,2515,2516],{},"\u002Fwebhooks\u002Fnhn\u002Fpush"," (현재는 SMS·RCS만). Push는 별도 결과 조회 API 필요.",[141,2519,2520,2521,30,2523,90,2525,2527],{},"Flow 실행 엔진 — ",[32,2522,2056],{},[32,2524,2032],{},[32,2526,2036],{}," 천이 + 폴백 정책(예: 알림톡→친구톡→LMS) 실행기.",[141,2529,2530],{},"Export 잡 처리 worker — Cloudflare Queues 컨슈머 + 채널별 이력 조회 → R2 업로드 → presigned URL 발급.",[141,2532,2533,2534,2537],{},"실 NHN 자격증명 등록 + ",[32,2535,2536],{},"NHN_MOCK"," secret 삭제 → real 모드 전환 (채널별 appKey).",[141,2539,2540],{},"Kakao senderKey · pushCert credential의 envelope 복호화 (현재 평문 가정).",[141,2542,2543],{},"트랜잭션 rollback 시 idempotency cleanup race 추가 보강.",[2545,2546,2547],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":1468,"searchDepth":1551,"depth":1551,"links":2549},[2550,2551,2552,2553,2554,2555,2556,2557,2559,2561,2567,2569,2578,2579,2580,2581,2590,2596],{"id":16,"depth":1517,"text":17},{"id":135,"depth":1517,"text":136},{"id":205,"depth":1517,"text":206},{"id":331,"depth":1517,"text":332},{"id":427,"depth":1517,"text":428},{"id":478,"depth":1517,"text":479},{"id":534,"depth":1517,"text":534},{"id":593,"depth":1517,"text":2558},"6. 멱등 버그 해결 (malgn-noti-api 020307f, history.20260526.md §22)",{"id":669,"depth":1517,"text":2560},"7. NHN SMS 어댑터 + Cloudflare Queues + consumer worker (malgn-noti-api 5e1ac72, history.20260526.md §23.1~23.2)",{"id":843,"depth":1517,"text":2562,"children":2563},"8. NHN webhook 핸들러 — POST \u002Fwebhooks\u002Fnhn\u002Fsms (malgn-noti-api 0d28173)",[2564,2565,2566],{"id":864,"depth":1551,"text":865},{"id":952,"depth":1551,"text":953},{"id":1017,"depth":1551,"text":1018},{"id":1064,"depth":1517,"text":2568},"9. malgn-noti-api 프로덕션 배포 #6 (history.20260526.md §23.1~23.3)",{"id":1137,"depth":1517,"text":2570,"children":2571},"10. Email · Kakao · Push 채널 추가 (malgn-noti-api 06cf052)",[2572,2573,2574,2575,2576,2577],{"id":1151,"depth":1551,"text":1152},{"id":1237,"depth":1551,"text":1238},{"id":1369,"depth":1551,"text":1370},{"id":1448,"depth":1551,"text":1449},{"id":1478,"depth":1551,"text":1479},{"id":1581,"depth":1551,"text":1582},{"id":1614,"depth":1517,"text":1615},{"id":1688,"depth":1517,"text":1689},{"id":1813,"depth":1517,"text":1814},{"id":1875,"depth":1517,"text":1876,"children":2582},[2583,2584,2585,2586,2587,2588,2589],{"id":1886,"depth":1551,"text":1887},{"id":1970,"depth":1551,"text":1971},{"id":2018,"depth":1551,"text":2019},{"id":2063,"depth":1551,"text":2064},{"id":2100,"depth":1551,"text":2101},{"id":2114,"depth":1551,"text":2115},{"id":2154,"depth":1551,"text":2155},{"id":2170,"depth":1517,"text":534,"children":2591},[2592,2593,2594,2595],{"id":2173,"depth":1551,"text":2174},{"id":2218,"depth":1551,"text":2219},{"id":2287,"depth":1551,"text":2288},{"id":2331,"depth":1551,"text":2332},{"id":2409,"depth":1517,"text":2410,"children":2597},[2598,2599],{"id":2413,"depth":1551,"text":2174},{"id":2480,"depth":1551,"text":2219},"md",{},true,"\u002Fhistory\u002Fhistory.20260527",{"title":5,"description":1468},"history\u002Fhistory.20260527","B3vQNsMbcFOaNb4elNnAXxxARVFcH644NHQVIf0krpc",1780639567004]