{
  "id": "e778a396-bfc8-4244-abb5-e078c4a6ce4b",
  "slug": "ref/khub/premium-geo-axis-2-발행-파이프라인-설계-브리핑-v1-0-완료-참조",
  "doc_type": "ref",
  "title": "📚 Axis 2 발행 파이프라인 설계 브리핑 v1.0 (완료 참조)",
  "title_en": null,
  "one_liner": "# 📚 Axis 2: 발행 파이프라인 설계 브리핑 v1.0 (완료 참조) > 작성: 2026-04-20 저녁 (Session-O 설계) | 완결: 2026-04-21 오후 (Session-Q 구현) | v1.1 > Phase: ✅ DONE (Phase A-C) / ⏸️ 보류 (Pha",
  "summary_bullets": [],
  "body_md": "# 📚 Axis 2: 발행 파이프라인 설계 브리핑 v1.0 (완료 참조)\n\n> 작성: 2026-04-20 저녁 (Session-O 설계) | 완결: 2026-04-21 오후 (Session-Q 구현) | v1.1\n> Phase: ✅ DONE (Phase A-C) / ⏸️ 보류 (Phase D) / ✅ 자동복구 17/18 (Phase E, uniqlo 제외)\n> 역할: **완료 참조 문서** — 실행 결과 상세는 Snapshot v3.25 Session-Q 섹션 참조\n> 트리거: B1-ext-D QA 중 발견한 \"발행 URL 404\" 근본 원인\n\n---\n\n## 📌 완결 요약 (v1.1)\n\nSession-O에서 이 설계 브리핑 작성 → Session-Q(2026-04-21 오후)에서 Phase A/B/C 일괄 구현 + E2E 검증 통과.\n\n- **brand-hub**: 7 commit + Vercel 3회 수동 배포\n- **partner-geo-hub**: 1 commit\n- **bawee RLS**: 2 정책 신규 (`public_read_published`, `authenticated_read_all`)\n- **E2E 검증**: draft `7a34ee2c` → published 전환 + `/content/v8-1-antihallu-ivana` HTTP 200 + JSON-LD Article + OG meta + sitemap 7건 전부 통과\n- **레거시 복구**: published 18건 중 17건 자동 복구 (uniqlo 1건은 별도 Vercel 프로젝트 `bmp-ai-uniqlo` prj_5bbmauu0RXtD0Kua 이슈로 미적용 — 결정 보류)\n\n---\n\n## 🎯 1. 배경 & 문제 정의\n\n### 사건 개요\n- B1-ext-D QA 중 찰스가 레거시 published draft \"이바나 헬싱키 한국 공식 브랜드 스토리\" (id=`a0a90baf`)의 URL 클릭\n- `https://ivanahelsinki.bmp.ai/content/이바나-헬싱키-한국-공식-브랜드-스토리-및-전문성-강화-콘텐츠` → **404 This page could not be found**\n\n### 근본 원인 (DB + brand-hub repo 실상 교차 검증)\n\n| 항목 | 확인 결과 |\n|---|---|\n| 레거시 `published` draft | **총 18건** (ivanahelsinki 5, uniqlo 9, chanel-watches 1, 기타) |\n| 전 건 `body_html` | **NULL** (HTML 렌더링 자산 부재) |\n| 전 건 `jsonld_data` | **NULL** (구조화 데이터 부재) |\n| 전 건 `compliance_status` | `unchecked` |\n| `btcr_gate_status` | 17건 null, 1건 'warn' |\n| brand-hub `/content/[slug]` 페이지 라우트 | **존재하지 않음** |\n| brand-hub에 있는 것 | `/api/content-page?slug=&content=` (query 기반 API, 페이지 아님) |\n\n### 단절된 파이프라인\n\n```\nEEAT 진단 ✅ → ROI 액션 ✅ → content-gen draft ✅ \n                                       │\n                                       ▼\n                          ❌ \"📤 발행\" 버튼 없음\n                                       │\n                                       ▼\n                          ❌ /content/[slug] SSR 라우트 없음\n                                       │\n                                       ▼\n                          ❌ HTML + JSON-LD 렌더 자산 null\n                                       │\n                                       ▼\n                          🔴 LLM 크롤러 접근 불가 → SoM 개선 0\n```\n\n### 핵심 인사이트\n**\"발행 URL이 없으면 GEO 자산이 아니다.\"**\ndraft 상태로는 LLM 학습/크롤링 대상이 되지 않음 → Share of Mind 상승 제로 → GEOcare 전체 서비스 모델의 최종 가치 미달성.\n\n---\n\n## 🎯 2. 목표 (성공 기준)\n\n| # | Definition of Done | 결과 |\n|---|---|---|\n| 1 | `{slug}.bmp.ai/content/{article-slug}` URL이 **실제로 HTTP 200** 반환 | ✅ 17/18 통과 |\n| 2 | HTML이 **LLM 친화적** 형태로 렌더 (article schema + E-E-A-T 시그널) | ✅ |\n| 3 | `jsonld_data` 기반 **구조화 데이터** `<script type=\"application/ld+json\">` 삽입 | ✅ |\n| 4 | 파트너/내부 담당자가 **\"📤 발행\" 버튼 1회 클릭**으로 draft→published 전환 | ✅ |\n| 5 | sitemap.xml 자동 갱신 + IndexNow/Google Ping 발신 | 🟡 sitemap ✅ / ping ⏸️ 보류 |\n| 6 | 기존 레거시 18건 **일괄 복구** 또는 명시적 아카이브 | ✅ 17건 자동 복구 / uniqlo 1건 보류 |\n\n---\n\n## 🗂️ 3. 스코프 분해 (5-Phase)\n\n### ✅ Phase A: SSR 페이지 라우트 — DONE (Session-Q)\n\n**파일**: `brand-hub/app/content/[slug]/page.tsx` 신규 생성\n- 서브도메인 헤더(`host`)로 `hub_slug` 추출 (middleware에 이미 있음 가능성)\n- `bmp_generated_contents` SELECT `WHERE hub_slug=X AND slug=Y AND status='published'`\n- `body_md` → `react-markdown` 또는 `remark`로 HTML 변환\n- `<head>`에 OG tags + Article JSON-LD 자동 주입\n- 404 처리: draft 상태거나 미존재 시\n\n**의존성**: `getHubBySlug()` 또는 유사 유틸 존재 여부 확인 필요 (brand-hub 내부)\n\n**📎 구현 자산 (Session-Q)**\n- `6dc11a9` `lib/content-jsonld.ts` (JSON-LD Article 생성 유틸)\n- `0f0a354` `app/[partner]/content/[slug]/page.tsx` (SSR 페이지)\n- `c9a35e9` middleware `/content/*` bypass 패턴\n- middleware rewrite/matcher 수정 2건: `9fb2e34`, `84b5068`\n- Vercel 배포: `dpl_635E...` (1차), `dpl_HsHS...` (matcher 수정 최종)\n\n---\n\n### ✅ Phase B: \"📤 발행\" 버튼 UX — DONE (Session-Q)\n\n**파일**: `partner-geo-hub/src/components/client-detail/ContentQueueTab.tsx` (기존 35KB 파일)\n- draft 상세 카드 하단에 **Publish 버튼** 추가\n- 발행 전 Gate:\n  - `btcr_gate_status === 'pass'` 권장 (warn은 경고, fail은 차단)\n  - `compliance_status` 체크\n- 버튼 클릭 → Supabase UPDATE:\n  - `status = 'published'`\n  - `published_at = NOW()`\n  - `published_url` = `https://{hub_slug}.bmp.ai/content/{slug}` 자동 조립\n  - `body_html` = md→html 변환 캐시 (선택적, Phase A가 런타임 처리하면 불필요)\n- 발행 후 **URL 복사 버튼** 노출\n\n**📎 구현 자산 (Session-Q)**\n- `1f4768e` `ContentQueueTab.tsx` (handlePublish + handleCopyUrl + BTCR 경고 모드)\n\n---\n\n### ✅ Phase C: sitemap.xml + robots.txt — DONE (Session-Q)\n\n**파일**: `brand-hub/app/[...slug]/sitemap.xml/route.ts` (동적)\n- 각 서브도메인별 `/sitemap.xml` 응답\n- `bmp_generated_contents WHERE hub_slug=X AND status='published'` 기반 URL 리스트\n- `lastmod`, `changefreq`, `priority` 필드 포함\n- `robots.txt`에 Sitemap 선언 (이미 있을 수도)\n\n**📎 구현 자산 (Session-Q)**\n- `74f6c8d` `app/[partner]/sitemap.xml/route.ts`\n- `bb39545` `app/[partner]/robots.txt/route.ts`\n- 실측: `{hub}.bmp.ai/sitemap.xml` (application/xml), `robots.txt` (text/plain) 정상 응답\n\n---\n\n### ⏸️ Phase D: LLM 크롤링 시그널 — 보류\n\n- IndexNow API (Bing 계열) ping\n- Google Search Console Indexing API (선택)\n- ChatGPT/Claude 등 LLM 크롤러 대상은 **직접 ping 불가** → 신선도 + 링크 빌딩 의존\n- 파트너별 \"🔗 외부 링크 추천\" 별도 액션으로 도출하는 방향\n\n**⏸️ 보류 결정 (Session-Q)**\n- Naver Webmaster API 묶어서 \"검색엔진 ping 통합 (Bing + Google + Naver)\" 별도 티켓 권장\n- Snapshot v3.25 백로그에 \"Axis 2 Phase D 검색엔진 ping 통합\" 항목 등록\n- 우선순위 낮음 — SoM 개선은 우선 자연 크롤링 + 링크 빌딩 의존\n\n---\n\n### ✅ Phase E: 레거시 복구 — 자동복구 17/18 (Session-Q)\n\n**옵션 1**: 18건 전부 살리기\n- Phase A 라우트만 구현되면 기존 `published_url` 자동 작동\n- `body_html`, `jsonld_data` NULL → Phase A의 런타임 렌더링이 커버\n\n**옵션 2**: 선별 복구 (ivanahelsinki 5건, 샤넬 1건 등 활성 hub만)\n- 그 외는 `status='archived'` 전환\n\n**📎 실측 결과 (Session-Q) — 옵션 1 채택**\n- Phase A 배포와 동시에 17건 자동 복구 HTTP 200 (`body_html`, `jsonld_data` NULL이어도 SSR 런타임 렌더링으로 커버)\n- ivanahelsinki 6건 slug + 신규 `v8-1-antihallu-ivana` + chanel-watches 1건 = 확인된 8건\n- **uniqlo 9건 미적용**: `bmp-ai-uniqlo` 별도 Vercel 프로젝트(prj_5bbmauu0RXtD0Kua)가 uniqlo.bmp.ai 도메인 선점 → `*.bmp.ai` 와일드카드보다 우선 적용됨\n  - 결정 옵션: ① 통합 ② 분리 유지 + Axis 2 이식 ③ 현상 유지\n  - 상태: **보류** (별도 Asana 티켓 등록 예정)\n\n---\n\n## 🛠️ 4. 기술 선택 (v1.0 원본 유지)\n\n### Frontend\n- **Next.js App Router** (brand-hub 기존 스택)\n- Markdown: `react-markdown` + `remark-gfm` + `rehype-highlight` (있을 가능성, 확인 필요)\n- Schema: `schema-dts` 타입 안전 JSON-LD 생성\n\n### Backend\n- **Supabase bawee** 직접 쿼리 (RPC 불필요, 단순 SELECT)\n- RLS: public read on `bmp_generated_contents WHERE status='published'` (현재 정책 확인 필요)\n  - **→ Session-Q 구현**: `public_read_published` (anon SELECT published) + `authenticated_read_all` (authenticated 전체) 2정책 신규\n\n### 기존 자산 재활용\n- `app/api/content-page/route.ts` (query 기반 API) — 여기 호출 로직을 SSR로 이관 검토\n- `geobh-content-page` EF — 이미 HTML 생성하는 듯. 재사용할지 대체할지 결정 필요\n\n---\n\n## ⚠️ 5. 리스크 & 의사결정 (완료 시점 기록)\n\n> ⚠️ **완료됨** — 아래 결정 지점은 Session-Q 구현에서 해소됨. 원본 유지.\n\n| # | 리스크 | 영향 | 대안 | Session-Q 처리 |\n|---|---|---|---|---|\n| R1 | 한글 slug URL 인코딩 (`이바나-...`) | 일부 크롤러가 디코딩 실패 | 영문 slug 병행 (제목에서 자동 생성) | 기존 한글 slug 유지, 영문 slug 병행은 후속 |\n| R2 | AI 생성 콘텐츠 법적 노출 | 잘못된 정보, 저작권 | 발행 전 **Fact Check blockquote 필수** | v8.1 환각방지 + BTCR 경고 모드로 커버 |\n| R3 | 레거시 18건 중 부적절 콘텐츠 | 브랜드 훼손 | 일괄 재활성 대신 **선별 검토 후** Phase E 진행 | 옵션 1(일괄 자동복구) 채택, 선별 검토는 후속 |\n| R4 | LLM 크롤러 도달 시간 | SoM 개선 지연 | 단기 SEO 키워드 함께 추적 | Phase D 보류 — 자연 크롤링 + 링크 의존 |\n| R5 | 파트너별 다른 도메인 정책 | bmp.ai 외 자체 도메인 원할 수도 | `published_url` 컬럼 free-form 유지 | 미해결 — uniqlo 별도 Vercel로 드러남 |\n| R6 | SSR 성능 (대량 트래픽 시) | 응답 지연 | Next.js ISR (revalidate 60s) + Supabase edge caching | 현재 트래픽 수준에서 미관찰, 후속 과제 |\n\n### 찰스 결정 필요 지점 (완료 시점 회고)\n\n1. **Quick win 우선?** → ✅ Phase A/B/C 일괄 (1세션 완주)\n2. **Publish Gate 엄격도**: `btcr_gate='pass'` 필수 vs 경고만 → ✅ **경고 모드** 채택 (warn 허용, fail 차단)\n3. **AI 면책 표시**: 모든 published 콘텐츠 하단에 면책 자동 삽입? → 🟡 v8.1 Fact Check blockquote로 부분 커버, 하단 면책은 후속\n4. **레거시 18건 처리**: 일괄 복구 / 선별 / 아카이브 중 어느 방향? → ✅ **일괄 자동 복구** (uniqlo만 별도 이슈로 분리)\n\n---\n\n## 📦 6. 데이터 모델 현황 (v1.0 원본 유지)\n\n### `bmp_generated_contents` 핵심 필드\n```\nstatus          'draft' | 'published' | ('archived' 신규 예정)\npublished_at    timestamptz\npublished_url   text                 — URL 자동 조립\nbody_md         text                 — 원본 마크다운 ✅\nbody_html       text                 — 현재 NULL, SSR 렌더 시 채울지 결정\njsonld_data     jsonb                — 현재 NULL, 발행 시 자동 생성\ncompliance_status 'unchecked'|'pass'|'fail'\nbtcr_gate_status 'pass'|'warn'|'fail'\nbtcr_overall_score int\nhub_slug        text                 — 서브도메인 결정자\nslug            text                 — URL path segment\nmetadata        jsonb                — source_action_id 등\n```\n\n### 발행 시 트랜잭션 (설계안 → Session-Q 구현)\n```sql\nUPDATE bmp_generated_contents\nSET status = 'published',\n    published_at = NOW(),\n    published_url = 'https://' || hub_slug || '.bmp.ai/content/' || slug,\n    updated_at = NOW()\nWHERE id = $1\n  AND (btcr_gate_status = 'pass' OR $2 = 'force');\n```\n\n---\n\n## 🔗 7. Cross-Room 영향 (완료 시점 전파 결과)\n\n| 방 | 영향 | 전파 방식 | Session-Q 처리 |\n|---|---|---|---|\n| GEOCARE | SoM 측정 로직이 published_url 크롤링 감지에 의존 가능 | Phase A 배포 후 공지 | ✅ Cross-Room 발송 완료 |\n| SALES | 영업 자료에 \"자동 발행 + GEO 자산화\" 차별점 추가 | Phase B 완료 후 Gamma 자료 갱신 | ✅ Cross-Room 발송 완료 / Gamma는 후속 |\n| DATA_ENG | 크롤링 로그 수집 옵션 (Access log → SoM correlation) | Phase D 완료 후 | ⏸️ Phase D 보류로 대기 |\n| COMMON | 용어 사전: publish, sitemap, IndexNow, BTCR gate | 용어사전 0830b8af 업데이트 | 🟡 bmp_* 8종 등록 미처리 (Cross-Room 대기) |\n\n---\n\n## ⏱️ 8. 총 소요 실측 (원본 추정 vs 실제)\n\n| Phase | 추정 | 실제 | 차이 |\n|---|---|---|---|\n| A. SSR 라우트 | 2~3h | ~1h (동시 진행) | 예상보다 빠름 |\n| B. 발행 버튼 UX | 1~2h | ~0.5h | 예상보다 빠름 |\n| C. sitemap.xml | 1h | ~0.5h | 예상대로 |\n| D. IndexNow Ping | 1h | ⏸️ 보류 | 별도 티켓 |\n| E. 레거시 복구 | 0.5h | 0h (자동) | Phase A 배포와 동시 자동 |\n| **합계** | **5.5~7.5h** | **~2h + 비용 ~$0.18** | **일괄 완주** |\n\n---\n\n## 🚦 9. 실행 순서 (완료됨)\n\n> ✅ **Session-Q에서 완주** — 원본 유지, 체크박스 표시.\n\n### Step 1 (30분): 재확인 조사 ✅\n- [x] brand-hub middleware에서 서브도메인 → hub_slug 추출 로직 존재 여부\n- [x] `geobh-content-page` EF 내부 확인 (이미 HTML 렌더링 중이면 재활용)\n- [x] `react-markdown` 또는 마크다운 라이브러리 package.json 확인\n- [x] Supabase RLS 정책 확인 (public read 가능 여부)\n\n### Step 2 (2h): Phase A 구현 ✅\n- [x] `app/content/[slug]/page.tsx` 생성 → `app/[partner]/content/[slug]/page.tsx`로 진화\n- [x] 서브도메인 → hub_slug 매핑 (middleware 또는 headers)\n- [x] Supabase 조회 + 404 처리\n- [x] Markdown → HTML + JSON-LD 자동 주입\n- [x] OG meta tags\n- [x] Fact Check footer 자동 삽입 (v8.1 환각방지로 대체)\n\n### Step 3 (30분): 배포 + 실측 ✅\n- [x] Vercel 자동 배포 → 3회 수동 배포로 matcher 버그 수정까지 완주\n- [x] 레거시 URL 5건 (ivanahelsinki) 테스트 → 6건 + 신규 1건 = 7건 통과\n- [x] SEO 도구로 크롤링 가능 여부 확인 → robots.txt + sitemap.xml 정상 응답\n\n### Step 4 (1h): Phase B 발행 버튼 ✅\n- [x] ContentQueueTab.tsx 수정\n- [x] 발행 UPDATE SQL\n- [x] URL 복사 UX\n\n### Step 5 (1h): Phase C sitemap + Step 6 Cross-Room 전파 ✅\n\n---\n\n## 📎 10. Quick Reference\n\n### Session-Q 신규 자산 (완결)\n\n**brand-hub commits (7)**\n`6dc11a9` content-jsonld.ts · `0f0a354` page.tsx · `c9a35e9` middleware /content · `74f6c8d` sitemap · `bb39545` robots · `9fb2e34` middleware rewrite · `84b5068` middleware matcher\n\n**partner-geo-hub commits (1)**\n`1f4768e` ContentQueueTab.tsx (handlePublish + handleCopyUrl)\n\n**bawee RLS 신규 (bmp_generated_contents)**\n- `public_read_published` — anon SELECT status='published' (LLM 크롤러 public)\n- `authenticated_read_all` — authenticated SELECT 전체 (파트너허브 draft 열람)\n\n**Vercel 배포**\n- `dpl_635E...` (Phase A 1차)\n- `dpl_AZVF...` (Phase B+C 1차, matcher 버그)\n- `dpl_HsHS...` (matcher 수정 최종)\n\n**실측 엔드포인트 (17건 HTTP 200)**\n- `https://ivanahelsinki.bmp.ai/content/{6건 slug}` + 신규 `v8-1-antihallu-ivana`\n- `https://chanel-watches.bmp.ai/content/...` (1건)\n- `https://{hub}.bmp.ai/sitemap.xml` (application/xml) · `robots.txt` (text/plain)\n\n**🔴 uniqlo 별도 프로젝트 (보류)**\n- `bmp-ai-uniqlo` prj_5bbmauu0RXtD0Kua (Axis 2 미적용)\n- uniqlo.bmp.ai 도메인 선점, `*.bmp.ai` 와일드카드보다 우선\n- 결정 옵션: ① 통합 ② 분리 유지 + Axis 2 이식 ③ 현상 유지\n\n### 관련 파일\n- brand-hub: `app/api/content-page/route.ts` (기존 API)\n- brand-hub: `app/site/SiteContent.tsx` (기존 SiteContent)\n- brand-hub: `app/report/[slug]/page.tsx` (패턴 참고용)\n- partner-geo-hub: `src/components/client-detail/ContentQueueTab.tsx` (35KB)\n\n### 관련 EF\n- `geobh-content-page` — 내용 확인 필요\n- `geobh-data` — 브랜드허브 데이터 API\n- `geobh-jsonld` — JSON-LD 자동 생성\n- `geobh-page` — 브랜드허브 페이지 렌더링\n\n### 레거시 published 18건 처리 결과\n- ivanahelsinki 5건: ✅ 자동 복구\n- uniqlo 9건: ⏸️ 별도 프로젝트 이슈로 미적용 (보류)\n- chanel-watches 1건: ✅ 자동 복구\n- 기타 3건: ✅ 자동 복구 (가정, 개별 실측은 별도)\n\n### 후속 작업 (Snapshot 백로그 링크)\n- Axis 2 Phase D 검색엔진 ping 통합 (Bing+Google+Naver) — 🆕 보류\n- uniqlo 별도 프로젝트 정책 결정 — 🆕 Asana 등록 예정\n- 파트너 draft 검토 흐름 구축 — 🔥 다음 착수 후보 (Session-Q Phase B 발행 버튼 거버넌스 보완)\n\n---\n\n## 변경 이력\n\n| 버전 | 날짜 | 변경 |\n|---|---|---|\n| **v1.1** | **2026-04-21 오후 (Session-Q)** | **Phase 상태 반영**: A/B/C ✅ DONE, D ⏸️ 보류 (별도 티켓 권장), E ✅ 자동복구 17/18 (uniqlo 제외). **완료 참조 문서**로 전환. 구현 자산(commit/RLS/배포) 각 Phase에 매핑. DoD/리스크/실행순서에 실제 결과 체크. |\n| v1.0 | 2026-04-20 저녁 | 초안. Session-O 설계. B1-ext-D QA에서 발견된 404 근본 원인 기반. |\n",
  "related_slugs": [],
  "faq": [],
  "jsonld": null,
  "keywords": [],
  "source_id": "90ef9bfc-ae92-44fe-b3f8-b141f2c66021",
  "video_url": null,
  "duration_sec": null,
  "thumbnail_url": null,
  "image_url": null,
  "caption": null,
  "solution_slug": null,
  "captured_at": null,
  "width": null,
  "height": null,
  "created_at": "2026-08-24T11:53:44.809175+00:00",
  "updated_at": "2026-08-24T11:53:44.809175+00:00",
  "visibility": "public",
  "source_stage": "1",
  "anonymized": true,
  "source_key": "legacy-khub",
  "origin": "ingest",
  "locked": false,
  "locked_by": null,
  "locked_at": null,
  "gate_state": {
    "g1": {
      "hits": [],
      "pass": true
    },
    "g2": {
      "pass": true,
      "total": 51,
      "issues": [
        "숫자·규격 같은 검증 가능한 구체가 부족합니다",
        "핵심 요약 불릿이 3개 미만입니다",
        "Q&A 쌍이 3개 미만입니다"
      ],
      "applies": true
    },
    "g3": {
      "pass": true,
      "required": false
    },
    "stage": "1",
    "decided_at": "2026-08-24T11:53:44.334Z"
  },
  "raw_id": "44010a86-9df8-4457-97d4-25cafe469ecd",
  "search_text": "📚 Axis 2 발행 파이프라인 설계 브리핑 v1.0 (완료 참조)  # 📚 Axis 2: 발행 파이프라인 설계 브리핑 v1.0 (완료 참조) > 작성: 2026-04-20 저녁 (Session-O 설계) | 완결: 2026-04-21 오후 (Session-Q 구현) | v1.1 > Phase: ✅ DONE (Phase A-C) / ⏸️ 보류 (Pha  # 📚 Axis 2: 발행 파이프라인 설계 브리핑 v1.0 (완료 참조)\n\n> 작성: 2026-04-20 저녁 (Session-O 설계) | 완결: 2026-04-21 오후 (Session-Q 구현) | v1.1\n> Phase: ✅ DONE (Phase A-C) / ⏸️ 보류 (Phase D) / ✅ 자동복구 17/18 (Phase E, uniqlo 제외)\n> 역할: **완료 참조 문서** — 실행 결과 상세는 Snapshot v3.25 Session-Q 섹션 참조\n> 트리거: B1-ext-D QA 중 발견한 \"발행 URL 404\" 근본 원인\n\n---\n\n## 📌 완결 요약 (v1.1)\n\nSession-O에서 이 설계 브리핑 작성 → Session-Q(2026-04-21 오후)에서 Phase A/B/C 일괄 구현 + E2E 검증 통과.\n\n- **brand-hub**: 7 commit + Vercel 3회 수동 배포\n- **partner-geo-hub**: 1 commit\n- **bawee RLS**: 2 정책 신규 (`public_read_published`, `authenticated_read_all`)\n- **E2E 검증**: draft `7a34ee2c` → published 전환 + `/content/v8-1-antihallu-ivana` HTTP 200 + JSON-LD Article + OG meta + sitemap 7건 전부 통과\n- **레거시 복구**: published 18건 중 17건 자동 복구 (uniqlo 1건은 별도 Vercel 프로젝트 `bmp-ai-uniqlo` prj_5bbmauu0RXtD0Kua 이슈로 미적용 — 결정 보류)\n\n---\n\n## 🎯 1. 배경 & 문제 정의\n\n### 사건 개요\n- B1-ext-D QA 중 찰스가 레거시 published draft \"이바나 헬싱키 한국 공식 브랜드 스토리\" (id=`a0a90baf`)의 URL 클릭\n- `https://ivanahelsinki.bmp.ai/content/이바나-헬싱키-한국-공식-브랜드-스토리-및-전문성-강화-콘텐츠` → **404 This page could not be found**\n\n### 근본 원인 (DB + brand-hub repo 실상 교차 검증)\n\n| 항목 | 확인 결과 |\n|---|---|\n| 레거시 `published` draft | **총 18건** (ivanahelsinki 5, uniqlo 9, chanel-watches 1, 기타) |\n| 전 건 `body_html` | **NULL** (HTML 렌더링 자산 부재) |\n| 전 건 `jsonld_data` | **NULL** (구조화 데이터 부재) |\n| 전 건 `compliance_status` | `unchecked` |\n| `btcr_gate_status` | 17건 null, 1건 'warn' |\n| brand-hub `/content/[slug]` 페이지 라우트 | **존재하지 않음** |\n| brand-hub에 있는 것 | `/api/content-page?slug=&content=` (query 기반 API, 페이지 아님) |\n\n### 단절된 파이프라인\n\n```\nEEAT 진단 ✅ → ROI 액션 ✅ → content-gen draft ✅ \n                                       │\n                                       ▼\n                          ❌ \"📤 발행\" 버튼 없음\n                                       │\n                                       ▼\n                          ❌ /content/[slug] SSR 라우트 없음\n                                       │\n                                       ▼\n                          ❌ HTML + JSON-LD 렌더 자산 null\n                                       │\n                                       ▼\n                          🔴 LLM 크롤러 접근 불가 → SoM 개선 0\n```\n\n### 핵심 인사이트\n**\"발행 URL이 없으면 GEO 자산이 아니다.\"**\ndraft 상태로는 LLM 학습/크롤링 대상이 되지 않음 → Share of Mind 상승 제로 → GEOcare 전체 서비스 모델의 최종 가치 미달성.\n\n---\n\n## 🎯 2. 목표 (성공 기준)\n\n| # | Definition of Done | 결과 |\n|---|---|---|\n| 1 | `{slug}.bmp.ai/content/{article-slug}` URL이 **실제로 HTTP 200** 반환 | ✅ 17/18 통과 |\n| 2 | HTML이 **LLM 친화적** 형태로 렌더 (article schema + E-E-A-T 시그널) | ✅ |\n| 3 | `jsonld_data` 기반 **구조화 데이터** `<script type=\"application/ld+json\">` 삽입 | ✅ |\n| 4 | 파트너/내부 담당자가 **\"📤 발행\" 버튼 1회 클릭**으로 draft→published 전환 | ✅ |\n| 5 | sitemap.xml 자동 갱신 + IndexNow/Google Ping 발신 | 🟡 sitemap ✅ / ping ⏸️ 보류 |\n| 6 | 기존 레거시 18건 **일괄 복구** 또는 명시적 아카이브 | ✅ 17건 자동 복구 / uniqlo 1건 보류 |\n\n---\n\n## 🗂️ 3. 스코프 분해 (5-Phase)\n\n### ✅ Phase A: SSR 페이지 라우트 — DONE (Session-Q)\n\n**파일**: `brand-hub/app/content/[slug]/page.tsx` 신규 생성\n- 서브도메인 헤더(`host`)로 `hub_slug` 추출 (middleware에 이미 있음 가능성)\n- `bmp_generated_contents` SELECT `WHERE hub_slug=X AND slug=Y AND status='published'`\n- `body_md` → `react-markdown` 또는 `remark`로 HTML 변환\n- `<head>`에 OG tags + Article JSON-LD 자동 주입\n- 404 처리: draft 상태거나 미존재 시\n\n**의존성**: `getHubBySlug()` 또는 유사 유틸 존재 여부 확인 필요 (brand-hub 내부)\n\n**📎 구현 자산 (Session-Q)**\n- `6dc11a9` `lib/content-jsonld.ts` (JSON-LD Article 생성 유틸)\n- `0f0a354` `app/[partner]/content/[slug]/page.tsx` (SSR 페이지)\n- `c9a35e9` middleware `/content/*` bypass 패턴\n- middleware rewrite/matcher 수정 2건: `9fb2e34`, `84b5068`\n- Vercel 배포: `dpl_635E...` (1차), `dpl_HsHS...` (matcher 수정 최종)\n\n---\n\n### ✅ Phase B: \"📤 발행\" 버튼 UX — DONE (Session-Q)\n\n**파일**: `partner-geo-hub/src/components/client-detail/ContentQueueTab.tsx` (기존 35KB 파일)\n- draft 상세 카드 하단에 **Publish 버튼** 추가\n- 발행 전 Gate:\n  - `btcr_gate_status === 'pass'` 권장 (warn은 경고, fail은 차단)\n  - `compliance_status` 체크\n- 버튼 클릭 → Supabase UPDATE:\n  - `status = 'published'`\n  - `published_at = NOW()`\n  - `published_url` = `https://{hub_slug}.bmp.ai/content/{slug}` 자동 조립\n  - `body_html` = md→html 변환 캐시 (선택적, Phase A가 런타임 처리하면 불필요)\n- 발행 후 **URL 복사 버튼** 노출\n\n**📎 구현 자산 (Session-Q)**\n- `1f4768e` `ContentQueueTab.tsx` (handlePublish + handleCopyUrl + BTCR 경고 모드)\n\n---\n\n### ✅ Phase C: sitemap.xml + robots.txt — DONE (Session-Q)\n\n**파일**: `brand-hub/app/[...slug]/sitemap.xml/route.ts` (동적)\n- 각 서브도메인별 `/sitemap.xml` 응답\n- `bmp_generated_contents WHERE hub_slug=X AND status='published'` 기반 URL 리스트\n- `lastmod`, `changefreq`, `priority` 필드 포함\n- `robots.txt`에 Sitemap 선언 (이미 있을 수도)\n\n**📎 구현 자산 (Session-Q)**\n- `74f6c8d` `app/[partner]/sitemap.xml/route.ts`\n- `bb39545` `app/[partner]/robots.txt/route.ts`\n- 실측: `{hub}.bmp.ai/sitemap.xml` (application/xml), `robots.txt` (text/plain) 정상 응답\n\n---\n\n### ⏸️ Phase D: LLM 크롤링 시그널 — 보류\n\n- IndexNow API (Bing 계열) ping\n- Google Search Console Indexing API (선택)\n- ChatGPT/Claude 등 LLM 크롤러 대상은 **직접 ping 불가** → 신선도 + 링크 빌딩 의존\n- 파트너별 \"🔗 외부 링크 추천\" 별도 액션으로 도출하는 방향\n\n**⏸️ 보류 결정 (Session-Q)**\n- Naver Webmaster API 묶어서 \"검색엔진 ping 통합 (Bing + Google + Naver)\" 별도 티켓 권장\n- Snapshot v3.25 백로그에 \"Axis 2 Phase D 검색엔진 ping 통합\" 항목 등록\n- 우선순위 낮음 — SoM 개선은 우선 자연 크롤링 + 링크 빌딩 의존\n\n---\n\n### ✅ Phase E: 레거시 복구 — 자동복구 17/18 (Session-Q)\n\n**옵션 1**: 18건 전부 살리기\n- Phase A 라우트만 구현되면 기존 `published_url` 자동 작동\n- `body_html`, `jsonld_data` NULL → Phase A의 런타임 렌더링이 커버\n\n**옵션 2**: 선별 복구 (ivanahelsinki 5건, 샤넬 1건 등 활성 hub만)\n- 그 외는 `status='archived'` 전환\n\n**📎 실측 결과 (Session-Q) — 옵션 1 채택**\n- Phase A 배포와 동시에 17건 자동 복구 HTTP 200 (`body_html`, `jsonld_data` NULL이어도 SSR 런타임 렌더링으로 커버)\n- ivanahelsinki 6건 slug + 신규 `v8-1-antihallu-ivana` + chanel-watches 1건 = 확인된 8건\n- **uniqlo 9건 미적용**: `bmp-ai-uniqlo` 별도 Vercel 프로젝트(prj_5bbmauu0RXtD0Kua)가 uniqlo.bmp.ai 도메인 선점 → `*.bmp.ai` 와일드카드보다 우선 적용됨\n  - 결정 옵션: ① 통합 ② 분리 유지 + Axis 2 이식 ③ 현상 유지\n  - 상태: **보류** (별도 Asana 티켓 등록 예정)\n\n---\n\n## 🛠️ 4. 기술 선택 (v1.0 원본 유지)\n\n### Frontend\n- **Next.js App Router** (brand-hub 기존 스택)\n- Markdown: `react-markdown` + `remark-gfm` + `rehype-highlight` (있을 가능성, 확인 필요)\n- Schema: `schema-dts` 타입 안전 JSON-LD 생성\n\n### Backend\n- **Supabase bawee** 직접 쿼리 (RPC 불필요, 단순 SELECT)\n- RLS: public read on `bmp_generated_contents WHERE status='published'` (현재 정책 확인 필요)\n  - **→ Session-Q 구현**: `public_read_published` (anon SELECT published) + `authenticated_read_all` (authenticated 전체) 2정책 신규\n\n### 기존 자산 재활용\n- `app/api/content-page/route.ts` (query 기반 API) — 여기 호출 로직을 SSR로 이관 검토\n- `geobh-content-page` EF — 이미 HTML 생성하는 듯. 재사용할지 대체할지 결정 필요\n\n---\n\n## ⚠️ 5. 리스크 & 의사결정 (완료 시점 기록)\n\n> ⚠️ **완료됨** — 아래 결정 지점은 Session-Q 구현에서 해소됨. 원본 유지.\n\n| # | 리스크 | 영향 | 대안 | Session-Q 처리 |\n|---|---|---|---|---|\n| R1 | 한글 slug URL 인코딩 (`이바나-...`) | 일부 크롤러가 디코딩 실패 | 영문 slug 병행 (제목에서 자동 생성) | 기존 한글 slug 유지, 영문 slug 병행은 후속 |\n| R2 | AI 생성 콘텐츠 법적 노출 | 잘못된 정보, 저작권 | 발행 전 **Fact Check blockquote 필수** | v8.1 환각방지 + BTCR 경고 모드로 커버 |\n| R3 | 레거시 18건 중 부적절 콘텐츠 | 브랜드 훼손 | 일괄 재활성 대신 **선별 검토 후** Phase E 진행 | 옵션 1(일괄 자동복구) 채택, 선별 검토는 후속 |\n| R4 | LLM 크롤러 도달 시간 | SoM 개선 지연 | 단기 SEO 키워드 함께 추적 | Phase D 보류 — 자연 크롤링 + 링크 의존 |\n| R5 | 파트너별 다른 도메인 정책 | bmp.ai 외 자체 도메인 원할 수도 | `published_url` 컬럼 free-form 유지 | 미해결 — uniqlo 별도 Vercel로 드러남 |\n| R6 | SSR 성능 (대량 트래픽 시) | 응답 지연 | Next.js ISR (revalidate 60s) + Supabase edge caching | 현재 트래픽 수준에서 미관찰, 후속 과제 |\n\n### 찰스 결정 필요 지점 (완료 시점 회고)\n\n1. **Quick win 우선?** → ✅ Phase A/B/C 일괄 (1세션 완주)\n2. **Publish Gate 엄격도**: `btcr_gate='pass'` 필수 vs 경고만 → ✅ **경고 모드** 채택 (warn 허용, fail 차단)\n3. **AI 면책 표시**: 모든 published 콘텐츠 하단에 면책 자동 삽입? → 🟡 v8.1 Fact Check blockquote로 부분 커버, 하단 면책은 후속\n4. **레거시 18건 처리**: 일괄 복구 / 선별 / 아카이브 중 어느 방향? → ✅ **일괄 자동 복구** (uniqlo만 별도 이슈로 분리)\n\n---\n\n## 📦 6. 데이터 모델 현황 (v1.0 원본 유지)\n\n### `bmp_generated_contents` 핵심 필드\n```\nstatus          'draft' | 'published' | ('archived' 신규 예정)\npublished_at    timestamptz\npublished_url   text                 — URL 자동 조립\nbody_md         text                 — 원본 마크다운 ✅\nbody_html       text                 — 현재 NULL, SSR 렌더 시 채울지 결정\njsonld_data     jsonb                — 현재 NULL, 발행 시 자동 생성\ncompliance_status 'unchecked'|'pass'|'fail'\nbtcr_gate_status 'pass'|'warn'|'fail'\nbtcr_overall_score int\nhub_slug        text                 — 서브도메인 결정자\nslug            text                 — URL path segment\nmetadata        jsonb                — source_action_id 등\n```\n\n### 발행 시 트랜잭션 (설계안 → Session-Q 구현)\n```sql\nUPDATE bmp_generated_contents\nSET status = 'published',\n    published_at = NOW(),\n    published_url = 'https://' || hub_slug || '.bmp.ai/content/' || slug,\n    updated_at = NOW()\nWHERE id = $1\n  AND (btcr_gate_status = 'pass' OR $2 = 'force');\n```\n\n---\n\n## 🔗 7. Cross-Room 영향 (완료 시점 전파 결과)\n\n| 방 | 영향 | 전파 방식 | Session-Q 처리 |\n|---|---|---|---|\n| GEOCARE | SoM 측정 로직이 published_url 크롤링 감지에 의존 가능 | Phase A 배포 후 공지 | ✅ Cross-Room 발송 완료 |\n| SALES | 영업 자료에 \"자동 발행 + GEO 자산화\" 차별점 추가 | Phase B 완료 후 Gamma 자료 갱신 | ✅ Cross-Room 발송 완료 / Gamma는 후속 |\n| DATA_ENG | 크롤링 로그 수집 옵션 (Access log → SoM correlation) | Phase D 완료 후 | ⏸️ Phase D 보류로 대기 |\n| COMMON | 용어 사전: publish, sitemap, IndexNow, BTCR gate | 용어사전 0830b8af 업데이트 | 🟡 bmp_* 8종 등록 미처리 (Cross-Room 대기) |\n\n---\n\n## ⏱️ 8. 총 소요 실측 (원본 추정 vs 실제)\n\n| Phase | 추정 | 실제 | 차이 |\n|---|---|---|---|\n| A. SSR 라우트 | 2~3h | ~1h (동시 진행) | 예상보다 빠름 |\n| B. 발행 버튼 UX | 1~2h | ~0.5h | 예상보다 빠름 |\n| C. sitemap.xml | 1h | ~0.5h | 예상대로 |\n| D. IndexNow Ping | 1h | ⏸️ 보류 | 별도 티켓 |\n| E. 레거시 복구 | 0.5h | 0h (자동) | Phase A 배포와 동시 자동 |\n| **합계** | **5.5~7.5h** | **~2h + 비용 ~$0.18** | **일괄 완주** |\n\n---\n\n## 🚦 9. 실행 순서 (완료됨)\n\n> ✅ **Session-Q에서 완주** — 원본 유지, 체크박스 표시.\n\n### Step 1 (30분): 재확인 조사 ✅\n- [x] brand-hub middleware에서 서브도메인 → hub_slug 추출 로직 존재 여부\n- [x] `geobh-content-page` EF 내부 확인 (이미 HTML 렌더링 중이면 재활용)\n- [x] `react-markdown` 또는 마크다운 라이브러리 package.json 확인\n- [x] Supabase RLS 정책 확인 (public read 가능 여부)\n\n### Step 2 (2h): Phase A 구현 ✅\n- [x] `app/content/[slug]/page.tsx` 생성 → `app/[partner]/content/[slug]/page.tsx`로 진화\n- [x] 서브도메인 → hub_slug 매핑 (middleware 또는 headers)\n- [x] Supabase 조회 + 404 처리\n- [x] Markdown → HTML + JSON-LD 자동 주입\n- [x] OG meta tags\n- [x] Fact Check footer 자동 삽입 (v8.1 환각방지로 대체)\n\n### Step 3 (30분): 배포 + 실측 ✅\n- [x] Vercel 자동 배포 → 3회 수동 배포로 matcher 버그 수정까지 완주\n- [x] 레거시 URL 5건 (ivanahelsinki) 테스트 → 6건 + 신규 1건 = 7건 통과\n- [x] SEO 도구로 크롤링 가능 여부 확인 → robots.txt + sitemap.xml 정상 응답\n\n### Step 4 (1h): Phase B 발행 버튼 ✅\n- [x] ContentQueueTab.tsx 수정\n- [x] 발행 UPDATE SQL\n- [x] URL 복사 UX\n\n### Step 5 (1h): Phase C sitemap + Step 6 Cross-Room 전파 ✅\n\n---\n\n## 📎 10. Quick Reference\n\n### Session-Q 신규 자산 (완결)\n\n**brand-hub commits (7)**\n`6dc11a9` content-jsonld.ts · `0f0a354` page.tsx · `c9a35e9` middleware /content · `74f6c8d` sitemap · `bb39545` robots · `9fb2e34` middleware rewrite · `84b5068` middleware matcher\n\n**partner-geo-hub commits (1)**\n`1f4768e` ContentQueueTab.tsx (handlePublish + handleCopyUrl)\n\n**bawee RLS 신규 (bmp_generated_contents)**\n- `public_read_published` — anon SELECT status='published' (LLM 크롤러 public)\n- `authenticated_read_all` — authenticated SELECT 전체 (파트너허브 draft 열람)\n\n**Vercel 배포**\n- `dpl_635E...` (Phase A 1차)\n- `dpl_AZVF...` (Phase B+C 1차, matcher 버그)\n- `dpl_HsHS...` (matcher 수정 최종)\n\n**실측 엔드포인트 (17건 HTTP 200)**\n- `https://ivanahelsinki.bmp.ai/content/{6건 slug}` + 신규 `v8-1-antihallu-ivana`\n- `https://chanel-watches.bmp.ai/content/...` (1건)\n- `https://{hub}.bmp.ai/sitemap.xml` (application/xml) · `robots.txt` (text/plain)\n\n**🔴 uniqlo 별도 프로젝트 (보류)**\n- `bmp-ai-uniqlo` prj_5bbmauu0RXtD0Kua (Axis 2 미적용)\n- uniqlo.bmp.ai 도메인 선점, `*.bmp.ai` 와일드카드보다 우선\n- 결정 옵션: ① 통합 ② 분리 유지 + Axis 2 이식 ③ 현상 유지\n\n### 관련 파일\n- brand-hub: `app/api/content-page/route.ts` (기존 API)\n- brand-hub: `app/site/SiteContent.tsx` (기존 SiteContent)\n- brand-hub: `app/report/[slug]/page.tsx` (패턴 참고용)\n- partner-geo-hub: `src/components/client-detail/ContentQueueTab.tsx` (35KB)\n\n### 관련 EF\n- `geobh-content-page` — 내용 확인 필요\n- `geobh-data` — 브랜드허브 데이터 API\n- `geobh-jsonld` — JSON-LD 자동 생성\n- `geobh-page` — 브랜드허브 페이지 렌더링\n\n### 레거시 published 18건 처리 결과\n- ivanahelsinki 5건: ✅ 자동 복구\n- uniqlo 9건: ⏸️ 별도 프로젝트 이슈로 미적용 (보류)\n- chanel-watches 1건: ✅ 자동 복구\n- 기타 3건: ✅ 자동 복구 (가정, 개별 실측은 별도)\n\n### 후속 작업 (Snapshot 백로그 링크)\n- Axis 2 Phase D 검색엔진 ping 통합 (Bing+Google+Naver) — 🆕 보류\n- uniqlo 별도 프로젝트 정책 결정 — 🆕 Asana 등록 예정\n- 파트너 draft 검토 흐름 구축 — 🔥 다음 착수 후보 (Session-Q Phase B 발행 버튼 거버넌스 보완)\n\n---\n\n## 변경 이력\n\n| 버전 | 날짜 | 변경 |\n|---|---|---|\n| **v1.1** | **2026-04-21 오후 (Session-Q)** | **Phase 상태 반영**: A/B/C ✅ DONE, D ⏸️ 보류 (별도 티켓 권장), E ✅ 자동복구 17/18 (uniqlo 제외). **완료 참조 문서**로 전환. 구현 자산(commit/RLS/배포) 각 Phase에 매핑. DoD/리스크/실행순서에 실제 결과 체크. |\n| v1.0 | 2026-04-20 저녁 | 초안. Session-O 설계. B1-ext-D QA에서 발견된 404 근본 원인 기반. |\n",
  "embedding": null,
  "embedded_at": null,
  "embedding_hash": null,
  "backlinks": [],
  "html_url": "https://bizspring.ai/kb/ref/khub/premium-geo-axis-2-발행-파이프라인-설계-브리핑-v1-0-완료-참조",
  "markdown_url": "https://bizspring.ai/kb/ref/khub/premium-geo-axis-2-발행-파이프라인-설계-브리핑-v1-0-완료-참조.md"
}