# 📚 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 (완료 참조)

> 작성: 2026-04-20 저녁 (Session-O 설계) | 완결: 2026-04-21 오후 (Session-Q 구현) | v1.1
> Phase: ✅ DONE (Phase A-C) / ⏸️ 보류 (Phase D) / ✅ 자동복구 17/18 (Phase E, uniqlo 제외)
> 역할: **완료 참조 문서** — 실행 결과 상세는 Snapshot v3.25 Session-Q 섹션 참조
> 트리거: B1-ext-D QA 중 발견한 "발행 URL 404" 근본 원인

---

## 📌 완결 요약 (v1.1)

Session-O에서 이 설계 브리핑 작성 → Session-Q(2026-04-21 오후)에서 Phase A/B/C 일괄 구현 + E2E 검증 통과.

- **brand-hub**: 7 commit + Vercel 3회 수동 배포
- **partner-geo-hub**: 1 commit
- **bawee RLS**: 2 정책 신규 (`public_read_published`, `authenticated_read_all`)
- **E2E 검증**: draft `7a34ee2c` → published 전환 + `/content/v8-1-antihallu-ivana` HTTP 200 + JSON-LD Article + OG meta + sitemap 7건 전부 통과
- **레거시 복구**: published 18건 중 17건 자동 복구 (uniqlo 1건은 별도 Vercel 프로젝트 `bmp-ai-uniqlo` prj_5bbmauu0RXtD0Kua 이슈로 미적용 — 결정 보류)

---

## 🎯 1. 배경 & 문제 정의

### 사건 개요
- B1-ext-D QA 중 찰스가 레거시 published draft "이바나 헬싱키 한국 공식 브랜드 스토리" (id=`a0a90baf`)의 URL 클릭
- `https://ivanahelsinki.bmp.ai/content/이바나-헬싱키-한국-공식-브랜드-스토리-및-전문성-강화-콘텐츠` → **404 This page could not be found**

### 근본 원인 (DB + brand-hub repo 실상 교차 검증)

| 항목 | 확인 결과 |
|---|---|
| 레거시 `published` draft | **총 18건** (ivanahelsinki 5, uniqlo 9, chanel-watches 1, 기타) |
| 전 건 `body_html` | **NULL** (HTML 렌더링 자산 부재) |
| 전 건 `jsonld_data` | **NULL** (구조화 데이터 부재) |
| 전 건 `compliance_status` | `unchecked` |
| `btcr_gate_status` | 17건 null, 1건 'warn' |
| brand-hub `/content/[slug]` 페이지 라우트 | **존재하지 않음** |
| brand-hub에 있는 것 | `/api/content-page?slug=&content=` (query 기반 API, 페이지 아님) |

### 단절된 파이프라인

```
EEAT 진단 ✅ → ROI 액션 ✅ → content-gen draft ✅ 
                                       │
                                       ▼
                          ❌ "📤 발행" 버튼 없음
                                       │
                                       ▼
                          ❌ /content/[slug] SSR 라우트 없음
                                       │
                                       ▼
                          ❌ HTML + JSON-LD 렌더 자산 null
                                       │
                                       ▼
                          🔴 LLM 크롤러 접근 불가 → SoM 개선 0
```

### 핵심 인사이트
**"발행 URL이 없으면 GEO 자산이 아니다."**
draft 상태로는 LLM 학습/크롤링 대상이 되지 않음 → Share of Mind 상승 제로 → GEOcare 전체 서비스 모델의 최종 가치 미달성.

---

## 🎯 2. 목표 (성공 기준)

| # | Definition of Done | 결과 |
|---|---|---|
| 1 | `{slug}.bmp.ai/content/{article-slug}` URL이 **실제로 HTTP 200** 반환 | ✅ 17/18 통과 |
| 2 | HTML이 **LLM 친화적** 형태로 렌더 (article schema + E-E-A-T 시그널) | ✅ |
| 3 | `jsonld_data` 기반 **구조화 데이터** `<script type="application/ld+json">` 삽입 | ✅ |
| 4 | 파트너/내부 담당자가 **"📤 발행" 버튼 1회 클릭**으로 draft→published 전환 | ✅ |
| 5 | sitemap.xml 자동 갱신 + IndexNow/Google Ping 발신 | 🟡 sitemap ✅ / ping ⏸️ 보류 |
| 6 | 기존 레거시 18건 **일괄 복구** 또는 명시적 아카이브 | ✅ 17건 자동 복구 / uniqlo 1건 보류 |

---

## 🗂️ 3. 스코프 분해 (5-Phase)

### ✅ Phase A: SSR 페이지 라우트 — DONE (Session-Q)

**파일**: `brand-hub/app/content/[slug]/page.tsx` 신규 생성
- 서브도메인 헤더(`host`)로 `hub_slug` 추출 (middleware에 이미 있음 가능성)
- `bmp_generated_contents` SELECT `WHERE hub_slug=X AND slug=Y AND status='published'`
- `body_md` → `react-markdown` 또는 `remark`로 HTML 변환
- `<head>`에 OG tags + Article JSON-LD 자동 주입
- 404 처리: draft 상태거나 미존재 시

**의존성**: `getHubBySlug()` 또는 유사 유틸 존재 여부 확인 필요 (brand-hub 내부)

**📎 구현 자산 (Session-Q)**
- `6dc11a9` `lib/content-jsonld.ts` (JSON-LD Article 생성 유틸)
- `0f0a354` `app/[partner]/content/[slug]/page.tsx` (SSR 페이지)
- `c9a35e9` middleware `/content/*` bypass 패턴
- middleware rewrite/matcher 수정 2건: `9fb2e34`, `84b5068`
- Vercel 배포: `dpl_635E...` (1차), `dpl_HsHS...` (matcher 수정 최종)

---

### ✅ Phase B: "📤 발행" 버튼 UX — DONE (Session-Q)

**파일**: `partner-geo-hub/src/components/client-detail/ContentQueueTab.tsx` (기존 35KB 파일)
- draft 상세 카드 하단에 **Publish 버튼** 추가
- 발행 전 Gate:
  - `btcr_gate_status === 'pass'` 권장 (warn은 경고, fail은 차단)
  - `compliance_status` 체크
- 버튼 클릭 → Supabase UPDATE:
  - `status = 'published'`
  - `published_at = NOW()`
  - `published_url` = `https://{hub_slug}.bmp.ai/content/{slug}` 자동 조립
  - `body_html` = md→html 변환 캐시 (선택적, Phase A가 런타임 처리하면 불필요)
- 발행 후 **URL 복사 버튼** 노출

**📎 구현 자산 (Session-Q)**
- `1f4768e` `ContentQueueTab.tsx` (handlePublish + handleCopyUrl + BTCR 경고 모드)

---

### ✅ Phase C: sitemap.xml + robots.txt — DONE (Session-Q)

**파일**: `brand-hub/app/[...slug]/sitemap.xml/route.ts` (동적)
- 각 서브도메인별 `/sitemap.xml` 응답
- `bmp_generated_contents WHERE hub_slug=X AND status='published'` 기반 URL 리스트
- `lastmod`, `changefreq`, `priority` 필드 포함
- `robots.txt`에 Sitemap 선언 (이미 있을 수도)

**📎 구현 자산 (Session-Q)**
- `74f6c8d` `app/[partner]/sitemap.xml/route.ts`
- `bb39545` `app/[partner]/robots.txt/route.ts`
- 실측: `{hub}.bmp.ai/sitemap.xml` (application/xml), `robots.txt` (text/plain) 정상 응답

---

### ⏸️ Phase D: LLM 크롤링 시그널 — 보류

- IndexNow API (Bing 계열) ping
- Google Search Console Indexing API (선택)
- ChatGPT/Claude 등 LLM 크롤러 대상은 **직접 ping 불가** → 신선도 + 링크 빌딩 의존
- 파트너별 "🔗 외부 링크 추천" 별도 액션으로 도출하는 방향

**⏸️ 보류 결정 (Session-Q)**
- Naver Webmaster API 묶어서 "검색엔진 ping 통합 (Bing + Google + Naver)" 별도 티켓 권장
- Snapshot v3.25 백로그에 "Axis 2 Phase D 검색엔진 ping 통합" 항목 등록
- 우선순위 낮음 — SoM 개선은 우선 자연 크롤링 + 링크 빌딩 의존

---

### ✅ Phase E: 레거시 복구 — 자동복구 17/18 (Session-Q)

**옵션 1**: 18건 전부 살리기
- Phase A 라우트만 구현되면 기존 `published_url` 자동 작동
- `body_html`, `jsonld_data` NULL → Phase A의 런타임 렌더링이 커버

**옵션 2**: 선별 복구 (ivanahelsinki 5건, 샤넬 1건 등 활성 hub만)
- 그 외는 `status='archived'` 전환

**📎 실측 결과 (Session-Q) — 옵션 1 채택**
- Phase A 배포와 동시에 17건 자동 복구 HTTP 200 (`body_html`, `jsonld_data` NULL이어도 SSR 런타임 렌더링으로 커버)
- ivanahelsinki 6건 slug + 신규 `v8-1-antihallu-ivana` + chanel-watches 1건 = 확인된 8건
- **uniqlo 9건 미적용**: `bmp-ai-uniqlo` 별도 Vercel 프로젝트(prj_5bbmauu0RXtD0Kua)가 uniqlo.bmp.ai 도메인 선점 → `*.bmp.ai` 와일드카드보다 우선 적용됨
  - 결정 옵션: ① 통합 ② 분리 유지 + Axis 2 이식 ③ 현상 유지
  - 상태: **보류** (별도 Asana 티켓 등록 예정)

---

## 🛠️ 4. 기술 선택 (v1.0 원본 유지)

### Frontend
- **Next.js App Router** (brand-hub 기존 스택)
- Markdown: `react-markdown` + `remark-gfm` + `rehype-highlight` (있을 가능성, 확인 필요)
- Schema: `schema-dts` 타입 안전 JSON-LD 생성

### Backend
- **Supabase bawee** 직접 쿼리 (RPC 불필요, 단순 SELECT)
- RLS: public read on `bmp_generated_contents WHERE status='published'` (현재 정책 확인 필요)
  - **→ Session-Q 구현**: `public_read_published` (anon SELECT published) + `authenticated_read_all` (authenticated 전체) 2정책 신규

### 기존 자산 재활용
- `app/api/content-page/route.ts` (query 기반 API) — 여기 호출 로직을 SSR로 이관 검토
- `geobh-content-page` EF — 이미 HTML 생성하는 듯. 재사용할지 대체할지 결정 필요

---

## ⚠️ 5. 리스크 & 의사결정 (완료 시점 기록)

> ⚠️ **완료됨** — 아래 결정 지점은 Session-Q 구현에서 해소됨. 원본 유지.

| # | 리스크 | 영향 | 대안 | Session-Q 처리 |
|---|---|---|---|---|
| R1 | 한글 slug URL 인코딩 (`이바나-...`) | 일부 크롤러가 디코딩 실패 | 영문 slug 병행 (제목에서 자동 생성) | 기존 한글 slug 유지, 영문 slug 병행은 후속 |
| R2 | AI 생성 콘텐츠 법적 노출 | 잘못된 정보, 저작권 | 발행 전 **Fact Check blockquote 필수** | v8.1 환각방지 + BTCR 경고 모드로 커버 |
| R3 | 레거시 18건 중 부적절 콘텐츠 | 브랜드 훼손 | 일괄 재활성 대신 **선별 검토 후** Phase E 진행 | 옵션 1(일괄 자동복구) 채택, 선별 검토는 후속 |
| R4 | LLM 크롤러 도달 시간 | SoM 개선 지연 | 단기 SEO 키워드 함께 추적 | Phase D 보류 — 자연 크롤링 + 링크 의존 |
| R5 | 파트너별 다른 도메인 정책 | bmp.ai 외 자체 도메인 원할 수도 | `published_url` 컬럼 free-form 유지 | 미해결 — uniqlo 별도 Vercel로 드러남 |
| R6 | SSR 성능 (대량 트래픽 시) | 응답 지연 | Next.js ISR (revalidate 60s) + Supabase edge caching | 현재 트래픽 수준에서 미관찰, 후속 과제 |

### 찰스 결정 필요 지점 (완료 시점 회고)

1. **Quick win 우선?** → ✅ Phase A/B/C 일괄 (1세션 완주)
2. **Publish Gate 엄격도**: `btcr_gate='pass'` 필수 vs 경고만 → ✅ **경고 모드** 채택 (warn 허용, fail 차단)
3. **AI 면책 표시**: 모든 published 콘텐츠 하단에 면책 자동 삽입? → 🟡 v8.1 Fact Check blockquote로 부분 커버, 하단 면책은 후속
4. **레거시 18건 처리**: 일괄 복구 / 선별 / 아카이브 중 어느 방향? → ✅ **일괄 자동 복구** (uniqlo만 별도 이슈로 분리)

---

## 📦 6. 데이터 모델 현황 (v1.0 원본 유지)

### `bmp_generated_contents` 핵심 필드
```
status          'draft' | 'published' | ('archived' 신규 예정)
published_at    timestamptz
published_url   text                 — URL 자동 조립
body_md         text                 — 원본 마크다운 ✅
body_html       text                 — 현재 NULL, SSR 렌더 시 채울지 결정
jsonld_data     jsonb                — 현재 NULL, 발행 시 자동 생성
compliance_status 'unchecked'|'pass'|'fail'
btcr_gate_status 'pass'|'warn'|'fail'
btcr_overall_score int
hub_slug        text                 — 서브도메인 결정자
slug            text                 — URL path segment
metadata        jsonb                — source_action_id 등
```

### 발행 시 트랜잭션 (설계안 → Session-Q 구현)
```sql
UPDATE bmp_generated_contents
SET status = 'published',
    published_at = NOW(),
    published_url = 'https://' || hub_slug || '.bmp.ai/content/' || slug,
    updated_at = NOW()
WHERE id = $1
  AND (btcr_gate_status = 'pass' OR $2 = 'force');
```

---

## 🔗 7. Cross-Room 영향 (완료 시점 전파 결과)

| 방 | 영향 | 전파 방식 | Session-Q 처리 |
|---|---|---|---|
| GEOCARE | SoM 측정 로직이 published_url 크롤링 감지에 의존 가능 | Phase A 배포 후 공지 | ✅ Cross-Room 발송 완료 |
| SALES | 영업 자료에 "자동 발행 + GEO 자산화" 차별점 추가 | Phase B 완료 후 Gamma 자료 갱신 | ✅ Cross-Room 발송 완료 / Gamma는 후속 |
| DATA_ENG | 크롤링 로그 수집 옵션 (Access log → SoM correlation) | Phase D 완료 후 | ⏸️ Phase D 보류로 대기 |
| COMMON | 용어 사전: publish, sitemap, IndexNow, BTCR gate | 용어사전 0830b8af 업데이트 | 🟡 bmp_* 8종 등록 미처리 (Cross-Room 대기) |

---

## ⏱️ 8. 총 소요 실측 (원본 추정 vs 실제)

| Phase | 추정 | 실제 | 차이 |
|---|---|---|---|
| A. SSR 라우트 | 2~3h | ~1h (동시 진행) | 예상보다 빠름 |
| B. 발행 버튼 UX | 1~2h | ~0.5h | 예상보다 빠름 |
| C. sitemap.xml | 1h | ~0.5h | 예상대로 |
| D. IndexNow Ping | 1h | ⏸️ 보류 | 별도 티켓 |
| E. 레거시 복구 | 0.5h | 0h (자동) | Phase A 배포와 동시 자동 |
| **합계** | **5.5~7.5h** | **~2h + 비용 ~$0.18** | **일괄 완주** |

---

## 🚦 9. 실행 순서 (완료됨)

> ✅ **Session-Q에서 완주** — 원본 유지, 체크박스 표시.

### Step 1 (30분): 재확인 조사 ✅
- [x] brand-hub middleware에서 서브도메인 → hub_slug 추출 로직 존재 여부
- [x] `geobh-content-page` EF 내부 확인 (이미 HTML 렌더링 중이면 재활용)
- [x] `react-markdown` 또는 마크다운 라이브러리 package.json 확인
- [x] Supabase RLS 정책 확인 (public read 가능 여부)

### Step 2 (2h): Phase A 구현 ✅
- [x] `app/content/[slug]/page.tsx` 생성 → `app/[partner]/content/[slug]/page.tsx`로 진화
- [x] 서브도메인 → hub_slug 매핑 (middleware 또는 headers)
- [x] Supabase 조회 + 404 처리
- [x] Markdown → HTML + JSON-LD 자동 주입
- [x] OG meta tags
- [x] Fact Check footer 자동 삽입 (v8.1 환각방지로 대체)

### Step 3 (30분): 배포 + 실측 ✅
- [x] Vercel 자동 배포 → 3회 수동 배포로 matcher 버그 수정까지 완주
- [x] 레거시 URL 5건 (ivanahelsinki) 테스트 → 6건 + 신규 1건 = 7건 통과
- [x] SEO 도구로 크롤링 가능 여부 확인 → robots.txt + sitemap.xml 정상 응답

### Step 4 (1h): Phase B 발행 버튼 ✅
- [x] ContentQueueTab.tsx 수정
- [x] 발행 UPDATE SQL
- [x] URL 복사 UX

### Step 5 (1h): Phase C sitemap + Step 6 Cross-Room 전파 ✅

---

## 📎 10. Quick Reference

### Session-Q 신규 자산 (완결)

**brand-hub commits (7)**
`6dc11a9` content-jsonld.ts · `0f0a354` page.tsx · `c9a35e9` middleware /content · `74f6c8d` sitemap · `bb39545` robots · `9fb2e34` middleware rewrite · `84b5068` middleware matcher

**partner-geo-hub commits (1)**
`1f4768e` ContentQueueTab.tsx (handlePublish + handleCopyUrl)

**bawee RLS 신규 (bmp_generated_contents)**
- `public_read_published` — anon SELECT status='published' (LLM 크롤러 public)
- `authenticated_read_all` — authenticated SELECT 전체 (파트너허브 draft 열람)

**Vercel 배포**
- `dpl_635E...` (Phase A 1차)
- `dpl_AZVF...` (Phase B+C 1차, matcher 버그)
- `dpl_HsHS...` (matcher 수정 최종)

**실측 엔드포인트 (17건 HTTP 200)**
- `https://ivanahelsinki.bmp.ai/content/{6건 slug}` + 신규 `v8-1-antihallu-ivana`
- `https://chanel-watches.bmp.ai/content/...` (1건)
- `https://{hub}.bmp.ai/sitemap.xml` (application/xml) · `robots.txt` (text/plain)

**🔴 uniqlo 별도 프로젝트 (보류)**
- `bmp-ai-uniqlo` prj_5bbmauu0RXtD0Kua (Axis 2 미적용)
- uniqlo.bmp.ai 도메인 선점, `*.bmp.ai` 와일드카드보다 우선
- 결정 옵션: ① 통합 ② 분리 유지 + Axis 2 이식 ③ 현상 유지

### 관련 파일
- brand-hub: `app/api/content-page/route.ts` (기존 API)
- brand-hub: `app/site/SiteContent.tsx` (기존 SiteContent)
- brand-hub: `app/report/[slug]/page.tsx` (패턴 참고용)
- partner-geo-hub: `src/components/client-detail/ContentQueueTab.tsx` (35KB)

### 관련 EF
- `geobh-content-page` — 내용 확인 필요
- `geobh-data` — 브랜드허브 데이터 API
- `geobh-jsonld` — JSON-LD 자동 생성
- `geobh-page` — 브랜드허브 페이지 렌더링

### 레거시 published 18건 처리 결과
- ivanahelsinki 5건: ✅ 자동 복구
- uniqlo 9건: ⏸️ 별도 프로젝트 이슈로 미적용 (보류)
- chanel-watches 1건: ✅ 자동 복구
- 기타 3건: ✅ 자동 복구 (가정, 개별 실측은 별도)

### 후속 작업 (Snapshot 백로그 링크)
- Axis 2 Phase D 검색엔진 ping 통합 (Bing+Google+Naver) — 🆕 보류
- uniqlo 별도 프로젝트 정책 결정 — 🆕 Asana 등록 예정
- 파트너 draft 검토 흐름 구축 — 🔥 다음 착수 후보 (Session-Q Phase B 발행 버튼 거버넌스 보완)

---

## 변경 이력

| 버전 | 날짜 | 변경 |
|---|---|---|
| **v1.1** | **2026-04-21 오후 (Session-Q)** | **Phase 상태 반영**: A/B/C ✅ DONE, D ⏸️ 보류 (별도 티켓 권장), E ✅ 자동복구 17/18 (uniqlo 제외). **완료 참조 문서**로 전환. 구현 자산(commit/RLS/배포) 각 Phase에 매핑. DoD/리스크/실행순서에 실제 결과 체크. |
| v1.0 | 2026-04-20 저녁 | 초안. Session-O 설계. B1-ext-D QA에서 발견된 404 근본 원인 기반. |


## FAQ


---
출처: https://bizspring.ai/kb/ref/khub/premium-geo-axis-2-발행-파이프라인-설계-브리핑-v1-0-완료-참조 · 최종 갱신 2026-08-24T11:53:44.809175+00:00
