title: content-orchestration 파이프라인 설계서 (L1)
date: 2026-02-25T17:20:00+09:00
type: design
layer: L1
status: in-review
task_id: "dc1d0d04-5bca-4d69-abc7-e0b4f3e40c79"
tags: [content-orchestration, pipeline-design, L1]
author: pipeline-design-pl
project: content-orchestration
reviewed_by: "jarvis"
reviewed_at: "2026-02-25T17:35:00+09:00"
approved_by: ""
approved_at: ""
content-orchestration 파이프라인 설계서 (L1)
Task ID: dc1d0d04-5bca-4d69-abc7-e0b4f3e40c79
작성일: 2026-02-25
작성자: pipeline-design-pl
근거 문서:
- L0:
content-orchestration-biz-v4.md (비즈 기획서)
- L1:
content-orchestration-design-db.md (DB 설계서, 확정)
1. 현황 분석
1-1. 현재 파이프라인 구조
content-pipeline 프로젝트(projects/content-pipeline/src/pipeline/)에 구현된 파이프라인은 다음 8개 파일로 구성된다:
| 파일 | 역할 | 실행 방식 |
|---|
collect.ts | RSS 17개 소스 수집, 중복 제거, 1단계 필라 키워드 필터링 | CLI / import |
generate.ts | 뉴스레터 생성 (Gemini Flash), 관련성 점수 기반 뉴스 선별 | CLI / import |
generate-blog.ts | 블로그 포스트 생성, 5대 필라별 프롬프트, 8항목 QA 검증 | CLI / import |
publish.ts | 뉴스레터 발행 (Brevo + 블로그 + SNS getlate.dev) | CLI / import |
publish-blog.ts | 블로그 단독 발행 (Turso blog_posts 테이블에 INSERT) | CLI / import |
publish-sns.ts | SNS 멀티플랫폼 발행 (getlate.dev API, 플랫폼별 콘텐츠 변환) | CLI / import |
run.ts | 뉴스레터 파이프라인 오케스트레이터 (수집 -> 생성 -> 이메일 -> 블로그 -> SNS) | CLI |
run-blog-pipeline.ts | 블로그 파이프라인 오케스트레이터 (수집 -> 주제선정 -> 리서치 -> 생성 -> QA -> 게시) | CLI |
현재 데이터 흐름:
RSS 17개 피드 ─→ collect.ts ─→ collected_news (178건)
│
┌───────────────┤
↓ ↓
generate.ts generate-blog.ts
(뉴스레터) (블로그 포스트)
│ │
↓ ↓
newsletters blog_posts
(content-os DB) (apppro-kr DB)
│ │
┌────────┼────────┐ ↓
↓ ↓ ↓ publish-blog.ts
Brevo API blog_posts getlate API
(publish) (publish) (publish-sns)
1-2. 현재 파이프라인의 강점
- 1단계 경량 필터링 구현:
filterByPillar() — 필라 키워드 매칭 + 등급(S/A/B) 기반 통과 판단. AI 토큰 소비 0
- 5대 필라 체계: 요일별 콘텐츠 필라 자동 배정 (월~금)
- 8항목 QA 검증: 본문 길이, H2 구조, 메타 설명, 제목 길이, 마크다운 유효성, 요약, 카테고리 등
- 관련성 점수 기반 뉴스 선별: 비즈니스 키워드 가중치 + 최신성 보너스 + 소스 다양성
- SNS 플랫폼별 콘텐츠 변환: 트위터(280자), 링크드인(3000자) 등 플랫폼 특성 반영
- 중복 제거: URL 정규화 + 제목 유사도(70% 단어 겹침) 기반 이중 중복 검사
- QA 실패 시 재시도:
run-blog-pipeline.ts에서 최대 2회 재생성
1-3. 현재 한계점
| # | 한계 | 상세 | DB 설계서 연계 |
|---|
| 1 | 파이프라인 실행 기록 없음 | pipeline_logs 0건. 언제 무엇이 실행됐는지 추적 불가 | pipeline_logs 확장 (trigger_type, error_log_id) |
| 2 | 콘텐츠 큐 미사용 | content_queue 0건. 생성→발행이 단일 프로세스로 직렬 실행 | content_queue 확장 (승인 플로우 5컬럼) |
| 3 | 승인 플로우 부재 | AI 생성 후 즉시 발행. CEO 검수 단계 없음 | content_queue.status: draft→reviewing→approved |
| 4 | 채널 하드코딩 | Brevo, getlate, blog DB 연결 정보가 코드에 내장 | channels 테이블로 동적 관리 |
| 5 | 에러 추적 산재 | 각 함수의 console.error/warn만 존재. 통합 에러 로그 없음 | error_logs 테이블 |
| 6 | 재시도 제한적 | 블로그 QA 실패 시 2회만 재시도. Brevo/SNS는 재시도 없음 | error_logs.auto_fix_attempted + content_distributions.retry_count |
| 7 | 2-Layer 상태 추적 불가 | 내부 DB 상태와 외부 플랫폼 등록 상태가 분리되지 않음 | content_distributions 테이블 |
| 8 | 멀티 프로젝트 미지원 | apppro 단일 프로젝트 전용. 프로젝트별 설정 분리 없음 | channels.project, content_queue.project |
| 9 | content-pipeline과 orchestration 분리 | 파이프라인 코드는 ai-blog 레포, 대시보드는 content-orchestration 레포. 연동 포인트 없음 | pipeline_logs로 실행 결과 기록 |
2. 파이프라인 설계 (5단계)
2-0. 전체 아키텍처 개요
┌──────────────────────────────────────────────────────────────────┐
│ content-orchestration │
│ (대시보드 + API + 스케줄러) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Stage 1 Stage 2 Stage 3 Stage 4 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 수집 │──→──│ 생성 │──→──│ 승인 │──→──│ 배포 │ │
│ │Collect│ │Generate│ │Approve│ │Publish│ │
│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │ │
│ ↓ ↓ ↓ ↓ │
│ collected content_queue content_queue content_ │
│ _news (draft) (approved) distributions │
│ │
│ ────────────── Stage 5: 결과 추적 ────────────────────────── │
│ pipeline_logs │ error_logs │ optimization_history │
│ │
└──────────────────────────────────────────────────────────────────┘
핵심 설계 원칙:
- 큐 기반 비동기: 각 단계는 DB 상태를 통해 연결. 단계 간 직접 호출 최소화
- 멱등성: 동일 입력에 대해 중복 처리 방지 (URL 정규화, slug 중복 체크)
- 관찰가능성: 모든 실행은 pipeline_logs에 기록, 에러는 error_logs에 수집
- 점진적 확장: Phase 1은 기존 코드 재활용, Phase 2~3에서 고도화
2-1. Stage 1: RSS 수집 (Collect)
목적: 외부 소스에서 뉴스를 수집하여 collected_news 테이블에 저장한다.
입력
| 항목 | 상세 |
|---|
| 소스 | RSS 17개 피드 (collect.ts의 RSS_FEEDS 배열) |
| 트리거 | Vercel Cron 스케줄 (매일 06:00 KST) 또는 수동 API 호출 |
| 설정 참조 | 현재: 코드 하드코딩 / Phase 2: channels 테이블의 RSS 소스 설정 |
처리 로직
1. RSS 피드 순회 파싱 (rss-parser, timeout 10초)
│
2. 피드별 최신 10건 추출
│
3. 중복 제거 (2단계)
├─ URL 정규화 (utm 파라미터 제거, trailing slash 제거)
└─ 제목 유사도 검사 (70% 단어 겹침 = 중복)
│
4. 1단계 경량 필터링 (토큰 소비 0)
├─ 광고성/무관 키워드 즉시 제거
├─ S등급: 무조건 통과
├─ A등급: 필라 키워드 1개 이상 매칭 시 통과
└─ B등급: 필라 키워드 2개 이상 매칭 시만 통과
│
5. collected_news INSERT (ON CONFLICT url DO NOTHING)
출력
| 항목 | 테이블 | 상세 |
|---|
| 수집 기사 | collected_news | 중복 제거 + 필터링 통과한 기사. lang/grade/category 포함 |
| 실행 로그 | pipeline_logs | pipeline_name='collect', status, duration_ms, items_processed, metadata(feeds_ok/feeds_fail) |
| 에러 로그 | error_logs | 피드 수집 실패 시: component='rss_collector', error_type='timeout'/'api_error' |
에러 처리
| 에러 유형 | 현재 처리 | 설계 후 처리 |
|---|
| 피드 파싱 실패 (HTTP 4xx/5xx) | console.warn 후 skip | error_logs 기록 + 나머지 피드 계속 처리 |
| 피드 timeout (10초) | rss-parser 자체 timeout | error_logs 기록 + skip |
| DB 저장 실패 | try-catch 무시 | error_logs 기록 + pipeline_logs에 부분 실패 기록 |
| 모든 피드 실패 | 빈 배열 반환 | pipeline_logs status='failed' + error_logs escalated=1 |
트리거 조건
| 트리거 | 조건 | Phase |
|---|
| 스케줄 (Cron) | 매일 06:00 KST | Phase 1 |
| 수동 (API) | POST /api/pipeline/collect | Phase 1 |
| 수동 (대시보드) | 대시보드 "수집 실행" 버튼 | Phase 2 |
2-2. Stage 2: AI 콘텐츠 생성 (Generate)
목적: 수집된 뉴스를 기반으로 AI가 콘텐츠를 생성하여 content_queue에 draft 상태로 저장한다.
입력
| 항목 | 상세 |
|---|
| 소스 데이터 | collected_news에서 used_in_newsletter=0인 미사용 기사 |
| AI 모델 | Gemini 2.0 Flash (현재) / Phase 2: content_generation_config 테이블 참조 |
| 프롬프트 | 필라별 프롬프트 템플릿 (prompts/pillars/*.md) |
| 트리거 | Stage 1 완료 후 자동 / 수동 API 호출 |
처리 로직
1. 콘텐츠 유형 결정
├─ 블로그: 요일 기반 필라 자동 배정 (getTodayPillar)
└─ 뉴스레터: 주 1회 (월요일) 주간 브리핑
│
2. 뉴스 선별 (관련성 점수 기반)
├─ 비즈니스 키워드 고가중치 (+3)
├─ AI 일반 토픽 중가중치 (+1)
├─ 연구/기술 저가중치 (-1)
├─ 최신성 보너스 (2일 이내 +3, 5일 이내 +1)
└─ 소스 다양성 (동일 소스 최대 3건)
│
3. AI 콘텐츠 생성 (Gemini Flash API)
├─ 시스템 프롬프트: BASE_SYSTEM_PROMPT + 필라별 추가 지침
├─ 뉴스 컨텍스트: 최근 5건 뉴스 요약
└─ 출력: JSON (title, slug, content, excerpt, meta_description, category, tags)
│
4. QA 검증 (8항목)
├─ 본문 길이 (1,500~4,000자)
├─ H2 헤딩 (최소 3개)
├─ AI 디렉토리 링크 (최소 2개, 프로덕션 도메인 설정 시)
├─ 메타 설명 (최대 160자)
├─ 제목 길이 (최대 45자)
├─ 마크다운 유효성 (빈 링크 없음)
├─ 요약 길이 (최대 200자)
└─ 카테고리 유효성 (5대 필라 중 하나)
│
├─ 6/8 이상 통과: PASS → content_queue에 draft INSERT
└─ 미달: 재생성 (최대 2회)
│
5. content_queue INSERT (status='draft')
├─ title, content_body, type, pillar, topic, project
└─ collected_news.used_in_newsletter = 1 (사용된 뉴스 마킹)
출력
| 항목 | 테이블 | 상세 |
|---|
| 콘텐츠 초안 | content_queue | status='draft', type='blog'/'newsletter', content_body에 마크다운/HTML 본문 |
| 뉴스레터 | newsletters | content_queue_id 연계, HTML+텍스트 본문 |
| 실행 로그 | pipeline_logs | pipeline_name='generate', items_processed, metadata(pillar, qa_score) |
| 에러 로그 | error_logs | AI 생성 실패: component='ai_generator', error_type='api_error'/'quality_fail' |
에러 처리
| 에러 유형 | 처리 |
|---|
| Gemini API 타임아웃/에러 | 1회 재시도 → 실패 시 error_logs 기록 + mock 생성(개발 모드만) |
| JSON 파싱 실패 | 이스케이프 보정 → 필드별 추출 시도 → 실패 시 error_logs |
| QA 미달 (6/8 미만) | 최대 2회 재생성 → 3회 실패 시 error_logs (quality_fail) + 에스컬레이션 |
| API 키 미설정 | mock 생성 + pipeline_logs metadata에 mock=true 기록 |
2-3. Stage 3: 승인 플로우 (Approve)
목적: CEO가 콘텐츠를 검수하고 승인/거부한다. 승인된 콘텐츠만 배포 단계로 진행한다.
상태 전이 (content_queue.status)
draft ──→ reviewing ──→ approved ──→ scheduled ──→ published
│ │
│ └──→ draft (거부: rejected_reason에 사유 기록)
│
└──→ failed (생성 단계 실패)
처리 로직
1. draft → reviewing 전환
├─ 트리거: 자동 (AI 생성 완료 즉시) 또는 수동 (대시보드에서 검수 요청)
└─ 액션: 텔레그램 알림 발송 (CEO에게 검수 요청)
│
2. CEO 검수
├─ 대시보드: /[project]/content/[id] 페이지에서 미리보기 + 승인/거부 버튼
├─ 텔레그램: 인라인 버튼으로 간편 승인 (Phase 2)
└─ 자동 승인 타이머: N시간 무응답 시 자동 approved (Phase 3, 설정 가능)
│
3. reviewing → approved
├─ approved_by: 'ceo' (수동) / 'auto' (자동 승인 타이머)
├─ approved_at: 현재 시각 (ms epoch)
└─ 후속 액션: Stage 4 (배포) 자동 트리거
│
4. reviewing → draft (거부)
├─ rejected_reason: CEO 피드백 텍스트
└─ 후속: AI 재생성 또는 수동 수정 후 다시 reviewing
입력/출력
| 항목 | 테이블 | 상세 |
|---|
| 입력 | content_queue | status='draft' 또는 'reviewing'인 콘텐츠 |
| 출력 | content_queue | status='approved', approved_by, approved_at 업데이트 |
| 알림 | 텔레그램 | reviewing 전환 시 CEO에게 검수 요청 메시지 |
| 로그 | pipeline_logs | pipeline_name='approve', metadata(approved_by, content_id) |
트리거 조건
| 트리거 | 조건 | Phase |
|---|
| 자동 (생성 완료) | Stage 2에서 QA 통과 후 자동 reviewing 전환 | Phase 1 |
| 수동 (대시보드) | CEO가 승인/거부 버튼 클릭 | Phase 1 |
| 수동 (API) | POST /api/content/[id]/approve 또는 /reject | Phase 1 |
| 텔레그램 인라인 | 텔레그램 메시지 내 승인 버튼 | Phase 2 |
| 자동 승인 타이머 | reviewing 후 N시간 무응답 | Phase 3 |
2-4. Stage 4: 채널 배포 (Publish)
목적: 승인된 콘텐츠를 각 채널에 예약 등록하고, 발행 상태를 추적한다.
2-Layer 상태 관리
내부 상태 (content_queue.status) 외부 상태 (content_distributions.platform_status)
───────────────────────────── ───────────────────────────────────────────────
approved → pending (배포 대상 등록)
→ registered (외부 플랫폼에 예약 등록 완료)
scheduled (모든 채널 등록 완료) → published (실제 발행 완료)
published (모든 채널 발행 완료) → failed (등록/발행 실패)
처리 로직
1. 배포 대상 채널 결정
├─ channels 테이블에서 is_active=1인 채널 조회
├─ 콘텐츠 유형(blog/newsletter/sns)에 맞는 채널 필터링
└─ content_distributions에 채널별 레코드 INSERT (platform_status='pending')
│
2. 채널별 예약 등록
│
├─ 블로그 (apppro.kr)
│ ├─ apppro-kr Turso DB의 blog_posts에 INSERT (published=0, scheduled_at 설정)
│ ├─ 기존 Vercel Cron (/api/cron/publish, 6h 주기)이 scheduled_at 도래 시 published=1 전환
│ └─ content_distributions UPDATE: platform_status='registered', platform_id=post_id
│
├─ Brevo 뉴스레터
│ ├─ Brevo API: 캠페인 생성 + scheduledAt 파라미터로 예약 발송
│ └─ content_distributions UPDATE: platform_status='registered', platform_id=campaign_id
│
└─ SNS (getlate.dev)
├─ Late API: 플랫폼별 콘텐츠 변환 → 예약 포스트 등록
│ ├─ Twitter/X: 280자 제한 (제목 + URL + 해시태그)
│ ├─ LinkedIn: 3000자 (제목 + 요약 + URL + 해시태그)
│ └─ Threads: 500자 (제목 + 요약 + URL)
└─ content_distributions UPDATE: platform_status='registered', platform_id=post_id
│
3. 상태 갱신
├─ 모든 채널 registered → content_queue.status = 'scheduled'
└─ 모든 채널 published → content_queue.status = 'published'
채널별 발행 메커니즘
| 채널 | API/방법 | 예약 발행 | Phase |
|---|
| 블로그 (apppro.kr) | Turso DB INSERT + Vercel Cron | scheduled_at 필드 기반, 기존 Cron이 자동 전환 | Phase 1 |
| Brevo 뉴스레터 | @getbrevo/brevo SDK, sendCampaign() | scheduledAt 파라미터 | Phase 1 |
| Twitter/X | getlate.dev API /posts | scheduledFor 파라미터 | Phase 1 |
| LinkedIn | getlate.dev API /posts | scheduledFor 파라미터 | Phase 1~2 |
| Threads | getlate.dev API /posts | scheduledFor 파라미터 | Phase 2 |
| 티스토리 | Tistory Open API (OAuth) | 직접 예약 발행 | Phase 3 |
| 미디엄 | Medium API (POST /users/{id}/posts) | publishStatus='draft'로 등록 | Phase 3 |
입력/출력
| 항목 | 테이블 | 상세 |
|---|
| 입력 | content_queue | status='approved'인 콘텐츠 |
| 입력 | channels | is_active=1인 활성 채널 목록 |
| 출력 | content_distributions | 채널별 배포 레코드 (platform_status, platform_id, platform_url) |
| 출력 | content_queue | status → 'scheduled' → 'published' 순차 전환 |
| 출력 | content_logs | 발행 완료 시 불변 이벤트 기록 (append-only) |
| 로그 | pipeline_logs | pipeline_name='publish', metadata(channels_ok/channels_fail) |
| 에러 | error_logs | 등록/발행 실패: component='publisher'/'brevo'/'sns_publisher' |
에러 처리
| 에러 유형 | 처리 |
|---|
| 블로그 DB 저장 실패 | 1회 재시도 → content_distributions.platform_status='failed' + error_logs |
| Brevo API 인증 실패 | 토큰 갱신 시도 → 재발송 → 실패 시 error_logs (escalated=1, CEO 갱신 필요) |
| Brevo rate limit | 발송 시간 변경(피크 회피) → 재시도 |
| getlate API 에러 | 1회 재시도 → 실패 시 error_logs + 해당 플랫폼만 skip |
| getlate 계정 미연결 | error_logs (error_type='auth_fail') + 나머지 플랫폼 계속 |
| 일부 채널만 실패 | 성공 채널은 registered, 실패 채널은 failed. content_queue는 부분 상태 유지 |
2-5. Stage 5: 결과 추적 (Track)
목적: 파이프라인 실행 이력, 에러 로그, 최적화 파라미터 변경을 기록하고 대시보드에 표시한다.
기록 대상
| 테이블 | 기록 시점 | 기록 내용 |
|---|
pipeline_logs | 각 단계(collect/generate/approve/publish) 시작/완료 시 | pipeline_name, status, duration_ms, items_processed, trigger_type, metadata |
error_logs | 에러 발생 시 (모든 단계) | component, error_type, error_message, auto_fix 시도/결과, escalated |
content_logs | 콘텐츠 발행 완료 시 | content_type, content_id, platform, status, published_at (불변 감사 로그) |
content_distributions | 배포 상태 변경 시 | platform_status UPDATE (pending→registered→published/failed) |
optimization_history | AI 파라미터 변경 시 (Phase 2+) | parameter, old_value, new_value, reason, impact |
pipeline_logs 기록 상세
각 파이프라인 단계 실행 시 아래 형식으로 기록한다:
시작 시: INSERT pipeline_logs (pipeline_name, status='started', trigger_type, created_at)
완료 시: UPDATE pipeline_logs SET status='completed', duration_ms, items_processed, metadata
실패 시: UPDATE pipeline_logs SET status='failed', error_message, error_log_id
metadata JSON 구조 (단계별):
| 단계 | metadata 예시 |
|---|
| collect | {"feeds_ok":15, "feeds_fail":2, "raw_count":120, "dedup_count":95, "filter_pass":78} |
| generate | {"pillar":"AI도구리뷰", "qa_score":7, "content_type":"blog", "model":"gemini-2.0-flash"} |
| approve | {"approved_by":"ceo", "content_id":"cq-001", "wait_hours":2.5} |
| publish | {"channels_ok":3, "channels_fail":0, "channels":["apppro-blog","brevo","twitter"]} |
error_logs 기록 상세
에러 발생 시: INSERT error_logs (
component, -- rss_collector / ai_generator / publisher / qa_checker / brevo / sns_publisher
error_type, -- timeout / auth_fail / quality_fail / api_error / rate_limit / validation_fail
error_message, -- 원본 에러 메시지
content_id, -- 관련 콘텐츠 ID (nullable)
channel_id, -- 관련 채널 ID (nullable)
auto_fix_attempted -- 0/1
)
자동 교정 시도 후: UPDATE error_logs SET
auto_fix_attempted = 1,
auto_fix_result = 'success'/'failed'/'skipped',
auto_fix_action = '교정 액션 설명'
해결 시: UPDATE error_logs SET
resolved_at = 현재시각,
resolution_type = 'auto_fixed'/'manual_fixed'/'ignored'
에스컬레이션 시: UPDATE error_logs SET escalated = 1
3. 자체교정(Self-Healing) 연계
3-1. 교정 등급별 설계
기획서 7-4의 자체교정 등급(Autonomy Level)을 파이프라인 단계별로 매핑한다.
| 등급 | 교정 행동 | 파이프라인 적용 | Phase |
|---|
| L1: 자동 재시도 | 동일 작업 재실행 | RSS 수집 재시도, API 호출 재시도, DB 저장 재시도 | Phase 1 |
| L2: 파라미터 조정 | 설정값 변경 후 재실행 | QA 미달 시 프롬프트 온도 조정, 발행 시간 변경 | Phase 2 |
| L3: 대체 경로 | fallback 우회 | Gemini 실패 시 Claude 전환, RSS URL 자동 교체 | Phase 2 |
| L4: 구조 변경 | 코드/설정 수정 | RSS 파서 로직 수정, 새 API 연동 | Phase 3 |
| L5: 전략 변경 | 콘텐츠 전략 수정 | 토픽 필라 변경, 채널 추가/삭제 | CEO 승인 필수 |
3-2. 단계별 자체교정 시나리오
Stage 1 (수집) 교정
| 에러 | 감지 | L1 재시도 | L2 조정 | L3 대체 | 에스컬레이션 |
|---|
| RSS 피드 HTTP 4xx/5xx | fetch 에러 코드 | 30초 후 1회 재시도 | — | 대체 URL 탐색 | 3회 연속 실패 시 |
| RSS 피드 timeout | 10초 초과 | timeout 15초로 확장 후 재시도 | — | — | 연속 5회 실패 시 |
| 파싱 에러 (XML 깨짐) | rss-parser 예외 | — | — | content_snippet만 추출 시도 | 즉시 error_logs |
Stage 2 (생성) 교정
| 에러 | 감지 | L1 재시도 | L2 조정 | L3 대체 | 에스컬레이션 |
|---|
| AI API 타임아웃 | fetch timeout | 1회 재시도 | — | — | 2회 연속 실패 시 |
| AI API 인증 실패 | HTTP 401/403 | — | — | — | 즉시 (CEO 키 갱신 필요) |
| QA 미달 (score < 6/8) | validateQuality | 최대 2회 재생성 | 프롬프트 온도 0.7→0.5 | — | 3회 재생성 후에도 미달 시 |
| JSON 파싱 실패 | JSON.parse 예외 | 이스케이프 보정 → 필드별 추출 | — | — | 모든 파싱 방법 실패 시 |
Stage 4 (배포) 교정
| 에러 | 감지 | L1 재시도 | L2 조정 | L3 대체 | 에스컬레이션 |
|---|
| 블로그 DB INSERT 실패 | SQL 예외 | 1회 재시도 | — | — | 2회 실패 시 |
| Brevo API 인증 만료 | HTTP 401 | — | — | — | 즉시 (CEO 토큰 갱신) |
| Brevo rate limit | HTTP 429 | 60초 대기 후 재시도 | 발송 시간 변경 | — | 3회 재시도 실패 |
| getlate 계정 미연결 | 'NO_ACCOUNTS' | — | — | 해당 플랫폼 skip | error_logs 기록 |
| slug 중복 (블로그) | ON CONFLICT | slug에 날짜/순번 자동 추가 | — | — | — |
3-3. error_logs → 자동 진단 → 교정 플로우
에러 발생
│
↓
error_logs INSERT (component, error_type, error_message)
│
↓
교정 등급 판단 (L1~L5)
│
├─ L1~L3 (자동 교정 가능)
│ ├─ auto_fix_attempted = 1
│ ├─ 교정 실행
│ ├─ auto_fix_result = 'success'/'failed'
│ └─ 성공 시: resolved_at 기록, resolution_type = 'auto_fixed'
│
└─ L4~L5 또는 자동 교정 실패
├─ escalated = 1
├─ 텔레그램 알림 (자비스/CEO)
└─ resolution_type = 'manual_fixed' (수동 해결 후)
4. Phase별 구현 범위
4-1. Phase 1 MVP (1주): 스케줄 수집 → AI 생성 → 수동 승인 → 블로그 발행
목표: "대시보드에서 CEO가 콘텐츠를 승인하면 실제 블로그에 발행되는 흐름 1건 이상 동작"
구현 범위:
| 단계 | 구현 내용 | 기존 코드 활용 |
|---|
| Stage 1 | Vercel Cron 트리거 → collect.ts 실행 → collected_news 저장 + pipeline_logs 기록 | collect.ts 그대로, pipeline_logs INSERT 추가 |
| Stage 2 | collect 완료 후 → generate-blog.ts 실행 → content_queue(draft) 저장 | generate-blog.ts 리팩토링: 결과를 content_queue에 저장 |
| Stage 3 | draft → reviewing 자동 전환 + 대시보드 승인/거부 API | 신규: /api/content/[id]/approve, /api/content/[id]/reject |
| Stage 4 | approved → 블로그 DB INSERT → content_distributions 기록 | publish-blog.ts 리팩토링: content_distributions 연계 |
| Stage 5 | pipeline_logs 기록 (모든 단계), error_logs 기록 (에러 시) | 신규: 로깅 유틸리티 |
| 자체교정 | L1 자동 재시도만 (RSS 재시도, API 재시도, QA 재생성) | 기존 재시도 로직 유지 + error_logs 기록 추가 |
Phase 1에서 하지 않는 것:
- Brevo 뉴스레터 자동 발송 (수동으로 Brevo 대시보드에서 발행)
- SNS 자동 배포 (수동으로 getlate 대시보드에서 발행)
- 텔레그램 알림 연동
- 자동 승인 타이머
- 멀티 프로젝트 (apppro만)
4-2. Phase 2 (2주): 다중 채널 배포 + 알림 + 자동 교정
구현 범위:
| 기능 | 상세 |
|---|
| Brevo 자동 발송 | approved → Brevo API 캠페인 생성 + 예약 → content_distributions |
| SNS 자동 배포 | approved → getlate API 예약 포스트 → content_distributions |
| 텔레그램 알림 | reviewing 전환 시 CEO에게 검수 요청 자동 알림 |
| AI 파라미터 관리 | content_generation_config 테이블 활용, 대시보드에서 설정 변경 |
| L2 파라미터 조정 | QA 미달 시 프롬프트 온도/길이 자동 조정 |
| L3 대체 경로 | AI 모델 fallback, RSS URL 자동 교체 |
| 성과 수집 (D+3) | 발행 후 3일 뒤 조회수/오픈율 수집 → content_distributions.metrics |
| pipeline_logs 확장 | error_log_id, trigger_type, parent_log_id 컬럼 활용 |
| optimization_history | 파라미터 변경 이력 기록 |
| 프로젝트별 RSS 분리 | channels 테이블 기반 프로젝트별 소스 관리 |
4-3. Phase 3 (3주+): 자체교정 고도화 + 성과 기반 최적화
구현 범위:
| 기능 | 상세 |
|---|
| 자동 승인 타이머 | reviewing 후 N시간 무응답 시 자동 approved |
| L4 구조 변경 | RSS 파서 로직 자동 수정, 새 API 자동 연동 (자비스 검수 후) |
| 성과 기반 자동 최적화 | 발행 시간, 제목 패턴, 채널 배분 자동 조정 → optimization_history 기록 |
| 외부 플랫폼 연동 | 티스토리(Open API), 미디엄(API), EO(API 확인 후) |
| DAG 시각화 | 파이프라인 전 과정을 대시보드에서 시각적으로 표시 |
| AI 콘텐츠 자동 트리거 | 큐 기반 자동 실행 (content_queue.status = 'pending' 감지 → 생성 시작) |
| 크로스포스팅 | 1개 콘텐츠 → 블로그 + 뉴스레터 + SNS 동시 배포 |
| 자체교정 정확도 추적 | 자동 교정 성공률 대시보드 표시 (목표: 80%+) |
5. 파이프라인 API 엔드포인트 (Phase 1)
content-orchestration Next.js App Router에 구현할 API 라우트:
| 메서드 | 경로 | 용도 | Phase |
|---|
POST | /api/pipeline/collect | 수동 RSS 수집 트리거 | 1 |
POST | /api/pipeline/generate | 수동 콘텐츠 생성 트리거 | 1 |
POST | /api/content/[id]/approve | 콘텐츠 승인 | 1 |
POST | /api/content/[id]/reject | 콘텐츠 거부 (body: { reason }) | 1 |
POST | /api/pipeline/publish/[id] | 수동 배포 트리거 (승인된 콘텐츠) | 1 |
GET | /api/pipeline/logs | 파이프라인 실행 로그 조회 | 1 |
GET | /api/pipeline/errors | 에러 로그 조회 (미해결 우선) | 1 |
GET | /api/cron/pipeline | Vercel Cron: 전체 파이프라인 자동 실행 | 1 |
Cron 스케줄 설계:
// vercel.json
{
"crons": [
{
"path": "/api/cron/pipeline",
"schedule": "0 21 * * 1-5"
}
]
}
0 21 * * 1-5 = UTC 21:00 = KST 06:00, 월~금
- Cron 핸들러가 Stage 1(수집) → Stage 2(생성) 순차 실행
- Stage 3(승인)은 CEO 액션 대기, Stage 4(배포)는 승인 후 자동
6. 데이터 흐름 종합 (DB 테이블 연계)
┌──────────────────┐
│ RSS 17개 피드 │
└────────┬─────────┘
│ Stage 1: collect
↓
┌──────────────────┐ ┌──────────────────┐
│ collected_news │ │ pipeline_logs │
│ (178+ 건) │ │ name='collect' │
└────────┬─────────┘ └──────────────────┘
│ Stage 2: generate
↓
┌──────────────────┐ ┌──────────────────────────────┐
│ content_queue │ │ content_generation_config │
│ status='draft' │────→│ (AI 모델/프롬프트/QA 설정) │
│ + title │ └──────────────────────────────┘
│ + content_body │
│ + type/pillar │
└────────┬─────────┘
│ Stage 3: approve
↓
┌──────────────────┐ ┌──────────────────┐
│ content_queue │ │ 텔레그램 알림 │
│ status='approved'│ │ (Phase 2) │
│ + approved_by/at │ └──────────────────┘
└────────┬─────────┘
│ Stage 4: publish
↓
┌──────────────────┐ ┌──────────────────┐
│ content_ │────→│ channels │
│ distributions │ │ (활성 채널 목록) │
│ platform_status │ └──────────────────┘
│ platform_id/url │
│ scheduled_at │ ┌──────────────────┐
│ published_at │ │ content_logs │
└────────┬─────────┘ │ (발행 감사 로그) │
│ └──────────────────┘
│ Stage 5: track
↓
┌──────────────────┐ ┌──────────────────────────┐
│ pipeline_logs │ │ optimization_history │
│ (전 단계 실행 이력)│ │ (파라미터 변경 이력, P2+) │
└──────────────────┘ └──────────────────────────┘
│
┌──────────────────┐
│ error_logs │
│ (에러 + 자동교정) │
└──────────────────┘
7. 기존 코드 → 신규 파이프라인 전환 전략
7-1. Phase 1 코드 변경 최소화 원칙
기존 content-pipeline 코드를 최대한 재활용하되, content-orchestration 레포에서 호출하는 구조로 전환한다.
| 기존 코드 | 변경 사항 | 변경 이유 |
|---|
collect.ts | collectNews() 결과를 pipeline_logs에 기록하는 래퍼 추가 | 관찰가능성 |
generate-blog.ts | 결과를 blog_posts 대신 content_queue에 저장하도록 분기 | 승인 플로우 도입 |
publish-blog.ts | content_queue에서 읽어 blog_posts에 저장 + content_distributions 기록 | 2-Layer 상태 |
publish.ts | sendViaBrevo, publishToSnsViaGetlate에 content_distributions 연계 추가 | 2-Layer 상태 |
run.ts / run-blog-pipeline.ts | content-orchestration API에서 호출하는 구조로 전환 | 대시보드 통합 |
7-2. 모듈 구조 (content-orchestration 레포 내)
content-orchestration/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ ├── pipeline/
│ │ │ │ ├── collect/route.ts ← Stage 1 트리거
│ │ │ │ ├── generate/route.ts ← Stage 2 트리거
│ │ │ │ ├── publish/[id]/route.ts ← Stage 4 트리거
│ │ │ │ ├── logs/route.ts ← 실행 로그 조회
│ │ │ │ └── errors/route.ts ← 에러 로그 조회
│ │ │ ├── content/
│ │ │ │ └── [id]/
│ │ │ │ ├── approve/route.ts ← 승인
│ │ │ │ └── reject/route.ts ← 거부
│ │ │ └── cron/
│ │ │ └── pipeline/route.ts ← Vercel Cron 핸들러
│ │ └── [project]/
│ │ └── content/
│ │ └── [id]/
│ │ └── page.tsx ← 콘텐츠 미리보기 + 승인 UI
│ └── lib/
│ ├── pipeline/
│ │ ├── collect.ts ← collect.ts 래퍼 (pipeline_logs 연계)
│ │ ├── generate.ts ← generate-blog.ts 래퍼 (content_queue 연계)
│ │ ├── publish.ts ← publish 래퍼 (content_distributions 연계)
│ │ └── logger.ts ← pipeline_logs / error_logs 유틸리티
│ └── db/
│ └── content-os.ts ← Turso 클라이언트 (기존)
8. 전체 타임라인 요약
Phase 1 (1주)
├─ Day 1-2: DB 마이그레이션 (channels, content_queue 확장, content_distributions, error_logs)
├─ Day 2-3: pipeline logger 유틸리티 + collect/generate 래퍼
├─ Day 3-4: 승인 API + 대시보드 UI (approve/reject)
├─ Day 4-5: publish 래퍼 (블로그만) + Cron 설정 + E2E 테스트
└─ Day 5: QA + 1건 실제 플로우 동작 확인
Phase 2 (2주)
├─ Brevo 자동 발송 연동
├─ getlate SNS 자동 배포 연동
├─ 텔레그램 검수 알림
├─ content_generation_config 대시보드 관리
├─ L2/L3 자동 교정
└─ 성과 수집 (D+3)
Phase 3 (3주+)
├─ 자동 승인 타이머
├─ 외부 플랫폼 연동 (티스토리, 미디엄)
├─ 성과 기반 자동 최적화
├─ DAG 시각화
└─ 크로스포스팅
리뷰 로그
[pipeline-design-pl 초안 작성] 2026-02-25 17:20
- 기존 8개 파이프라인 파일 분석 완료 (collect, generate, generate-blog, publish, publish-blog, publish-sns, run, run-blog-pipeline)
- 현재 파이프라인 강점 7건 + 한계점 9건 정리
- 5단계 파이프라인 설계: 수집(Collect) → 생성(Generate) → 승인(Approve) → 배포(Publish) → 추적(Track)
- 각 단계별 입력/출력/트리거/에러처리 상세 명세
- 자체교정(Self-Healing) 등급별 설계: L1~L5, 단계별 시나리오 매핑
- DB 설계서(확정) 기준 테이블 연계: collected_news, content_queue, channels, content_distributions, error_logs, pipeline_logs, content_logs, optimization_history, content_generation_config
- Phase별 구현 범위: Phase 1 MVP(1주) / Phase 2(2주) / Phase 3(3주+)
- 기존 코드 전환 전략: 래퍼 패턴으로 최소 변경
[자비스 1차 검수] 2026-02-25 17:35
- ✅ frontmatter 완비 (ISO 8601 시간 포함, 전 필드 존재)
- ✅ 비즈 기획서 v4 요구사항 전체 커버 (5단계 파이프라인, 승인 플로우, 2-Layer 상태, 자체교정 L1~L5)
- ✅ DB 설계서(확정) 기준 9개 테이블 전부 활용 시점 명세
- ✅ Phase 1 MVP 1주 범위 명확 + 하지 않는 것 명시
- ✅ 기존 코드 래퍼 패턴으로 변경 최소화 전략 우수
- ✅ 자체교정 시나리오별 L1~L3 단계 구체적 명세
- ✅ planning-rules.md 준수 (한글, 리뷰 로그, L1 설계서 규격)
- 결과: 승인 (approved) — 수정 없이 VP 2차 검수 요청