<!-- canonical: https://docs.axelabs.ai/services/index/skill-integration -->
<!-- source: content/services/index/skill-integration.mdx -->

---
title: Skill 통합 아키텍처
description: ingest / ic / pmc 3 skill 의 index 통합 설계 — 5-axis (본질·결과·퀄리티·안전·혁신) 정합. atomic propose + idempotency + 3-layer error model + cross-deal aggregate.
---

# Skill 통합 아키텍처

> **⚠ 부분 SUPERSEDED (2026-06, [D-index-45](/ops/decisions)/[46](/ops/decisions)/[47](/ops/decisions)).** design-era (D-index-11~15) 의 통합 설계다. **유효한 것**: atomic `propose_deal_closure` 1-transaction 원칙 (§2, 실구현됨) · 3-layer error model (§4) · schema authority immutable (§5, 실제는 `frozen_enums_hash` + `SCHEMA_CONTRACT_DRIFT` gate 로 강화 — [schema-catalog § frozen-enum](/services/index/schema-catalog#frozen-enum-계약--frozen_enums_hash-d-index-47-p1)). **달라진 것**:
>
> - **ingest → artifact 적재**는 design-era `ingest_financial_model_xlsx` 가 아니라 **`propose_artifacts_from_ingest`** (dataroom sidecar glob → typed artifact + citation + `_provenance` stamp). artifact-first judgment layer ([D-index-45](/ops/decisions)) 가 당시엔 없던 본질.
> - **pmc 8-agent / cross-deal SQL (§3, §6)** 은 `portfolio_kpi` · `risk_alert` · `valuation` 테이블을 가정하나 이들은 아직 **phase-1 stub** (미적재) — [schema-catalog § stubs](/services/index/schema-catalog#6-phase-1-stubs-status-phase1_backlog).
> - **skill ownership** 의 실 분배 경로 = [D-index-47](/ops/decisions) universal-base mirror + vertical-gate ([skill-ownership](/services/index/skill-ownership)). MCP byte-delivery skill discovery 는 infeasible 판명.
>
> 아래는 server-side 설계 의도 기록으로 보존. 현행 도구 카탈로그 = [/services/index § MCP 도구 카탈로그](/services/index#mcp-도구-카탈로그-27).

ingest · ic · pmc 3 skill 이 index MCP 서비스와 어떻게 통합되는가의 본격 설계. 본 페이지는 [/ops/decisions D-index-11~15](/ops/decisions) 의 implementation 가이드 + [/services/index/skill-evolution](/services/index/skill-evolution) 의 진화 path 의 server-side counterpart.

## 5-axis 정합

| Axis | 핵심 명시 |
|---|---|
| **본질** | skill = workflow (LLM-heavy authoring), index = persistence (typed fact SoT). 둘의 boundary 가 명시적이고 protected |
| **결과** | 3 deal (Iippo / Sentry / Canopy) 의 Phase 0 acceptance 8 test 모두 PASS — IRR ±1pp 일치, 펀드별 IRR 독립 산출, citation roundtrip |
| **퀄리티** | atomic `propose_deal_closure` 1 transaction, idempotency_key UUIDv5, 3-layer 에러 모델, schema authority immutable |
| **안전** | graceful degradation (index 죽어도 markdown 산출 유지), partial failure 차단 (all-or-rollback), Korean OCR worst-case 차단 (xlsx vision 만 label, PDF 거부) |
| **혁신** | cross-deal aggregate (fund NAV / portfolio risk heatmap / sector KPI benchmark) — ic/dd/vc 가 못 하는 영역. time-travel query 자연 |

## 3 skill 통합 매트릭스

| skill | 변경 정도 | 핵심 결정 | 산출물 |
|---|---|---|---|
| **ingest** | **변경 0** | 이중 path. dataroom markdown clone 만 (discovery phase). index 는 자체 `ingest_financial_model_xlsx` MCP tool 보유 — post-convergence, typed schema 강제. `vision_ingest.py` orthogonal | (그대로) |
| **ic** | **5th mode 추가, ~280-350 lines** | `--push-to-index` = FINALIZE 평행 track (sequential 아님). Step 5.5 (gate_memo.sh PASS 후, postmortem_stub 이전). atomic `propose_deal_closure` 단일 batch | SKILL.md Rule 25 + Step 5.5 + gate_memo.sh dispatch + 보조 scripts |
| **pmc** | **신규 greenfield + 2 skill 흡수** | portfolio-management + investor-relations 3-phase deprecate. 8 sub-agent (ic 19 < pmc 8). Trigger 3-tier (scheduled + event + manual). postmortem 2-track | SKILL.md + 8 sub-agent + references/ + scripts/ + pmc/ folder convention |

---

## 1. ingest skill 통합 — 이중 path

### 원칙

ingest skill 은 **수정하지 않음**. 이유:

- **본질 분리**: ingest = "dataroom 의 모든 확장자를 agent-readable markdown 으로" (discovery, all-files, vision-optional). index ingest = "재무 모델 xlsx 를 typed driver tree 로" (post-convergence, single-type, schema-strict).
- **재발 risk 차단**: ingest 가 typed schema 책임 가지면 deal 별로 다른 xlsx 모양에 대응하느라 generic ingest 기능이 distort.
- **vision_ingest.py orthogonal**: ingest skill 의 vision 은 dataroom 의 정성 정보 (IR PDF / 회사 자료 PPTX) 의 vision. index 의 ingest 는 xlsx sheet 의 label inference 만 vision (수치는 mechanical).

### Boundary

```
회사 IR pack 받음
  ↓
dataroom/{IR_v1.pdf, 회사소개.pptx, 3FS_model.xlsx, ...}
  ↓ /ingest (변경 없음)
  ↓
dataroom/{IR_v1_pdf.md, 회사소개_pptx.md, 3FS_model_xlsx.md, ...}  ← markdown clone (모두)
  ↓
/ic 실행 (Research squad 가 markdown clone 만 읽음)
  ↓
ic/finance/3fs_base/v{N}.xlsx 산출 (회사 모델 위 AXE 가정 overlay)
  ↓ /ic --push-to-index (선택, 5th mode)
  ↓
index.ingest_financial_model_xlsx(deal_id, ic/finance/3fs_base/v{N}.xlsx blob)
  ↓ Anthropic Sonnet (label inference) + calamine (cell value)
  ↓
financial_model + financial_driver + financial_driver_value DB 적재
```

### 정합 룰 ([D-index-15](/ops/decisions) [B-index-vision-boundary-rule](/ops/backlog))

| Input | Vision 허용 | Mechanical 강제 |
|---|---|---|
| dataroom 의 PDF (IR pack 등) | ✅ ingest skill (markdown clone 만) | (markdown 만 산출) |
| dataroom 의 PPTX | ✅ ingest skill (markdown clone 만) | (markdown 만 산출) |
| ic/finance/*.xlsx | sheet/row **label inference 만** | **numeric cell value 는 calamine** 강제 |
| ic/finance/*.xlsx 의 PDF embed | ❌ vision X | (지원 안 함) |
| dataroom PDF 직접 index 적재 | ❌ 차단 — `ingest_financial_model_xlsx` 가 PDF blob 거부 | — |

→ "5,941원 → 5,941엔" Korean OCR 오인식이 DB authoritative cell 이 되는 worst-case 영구 차단.

---

## 2. ic skill 통합 — 5th mode (`--push-to-index`)

### 4 mode → 5 mode

```
기존:                              진화 후 (D-index-11):
  /ic                                /ic
  ├─ INITIAL    (v1 full run)        ├─ INITIAL    (v1 full run)
  ├─ REVISION   (v{N}→v{N+1})        ├─ REVISION   (v{N}→v{N+1})
  ├─ APPEND     (§17 entry)          ├─ APPEND     (§17 entry)
  └─ FINALIZE   (ic/final/ lock)     ├─ FINALIZE   (ic/final/ lock)
                                     └─ --push-to-index  ← 신규, FINALIZE 평행 track
```

**왜 FINALIZE 와 평행 (sequential 아님)?**

- FINALIZE = OneDrive deliverable lifecycle (`ic/final/v{N}.{md,pdf}` lock)
- `--push-to-index` = DB lifecycle (typed fact persistence)
- 두 lifecycle 이 같은 commit 에 묶이면 partial failure 시 rollback 모호 (PDF 만 lock 되고 DB 미반영 / DB 만 적재되고 PDF 미생성)
- 평행 track = 한 쪽 실패가 다른 쪽 blocking 안 함. 양쪽 모두 retry-safe

### Step 5.5 위치

```
Step 0   : dataroom md clone + ctx sync (PR-O 4-mode dispatch 의 Mode 0)
Step 1-4 : 19-agent orchestration (proponent / premortem_critic / devils / qa / ...)
Step 5   : gate_memo.sh chain (citation_trace / xlsx_integrity / check_arithmetic / check_exit_matrix)
           ↓ PASS
           humanize Stage 4 (humanize-corrector sub-agent)
           ↓
           pdf_quality gate (check_pdf_quality.py)
           ↓
Step 5.5 : ← 신규. --push-to-index 플래그 detect 시 발동
           ├─ idempotency_key = UUIDv5(deal_id, f"{version}::{content_sha256}::{actor_email}")
           ├─ payload 수집 (financial_model xlsx blob + ic_decision + fund_investments[] +
           │   dd_findings[] + exit_matrix_leaves[] + risk_alerts[])
           ├─ POST /index/mcp propose_deal_closure (단일 atomic call, [D-index-13](/ops/decisions))
           ├─ 응답: { model_id, decision_id, ... } 또는 { error.code: "IDEMPOTENCY_CONFLICT", context: ... }
           └─ ic/index_push_state/v{N}.json local checkpoint
Step 6   : postmortem_stub.py 자동 호출 (ic skill 잔존 책임)
Step 7   : FINALIZE 가능 (별도 invocation, ic/final/ lock + render)
```

### Atomic `propose_deal_closure` ([D-index-13](/ops/decisions))

**개별 propose tool 6+ round-trip 금지** — partial failure 시 inconsistent state:

```jsonc
// ❌ 잘못된 패턴
POST /propose_financial_model    → 200 (commit)
POST /propose_ic_decision         → 200 (commit)
POST /propose_fund_investment[0]  → 200 (commit)
POST /propose_fund_investment[1]  → 500 ← FAIL — rollback 불가 (3 commit 됐음)
POST /propose_dd_finding[]        → never reached
```

```jsonc
// ✅ atomic 패턴
POST /propose_deal_closure {
  "deal_id": "iippo",
  "version": 8,
  "idempotency_key": "uuid-v5-...",
  "financial_model": { xlsx_blob, meta },
  "ic_decision": { memo_pdf_url, irr_7metric, voting },
  "fund_investments": [
    { fund: "axe_ia_001", round: "Pre-A RCPS 1호", committed: 99998912, ... },
    { fund: "axe_ia_002", round: "Pre-A RCPS 2호", committed: 100000000, ... }
  ],
  "dd_findings": [...],
  "exit_matrix_leaves": [...],
  "risk_alerts": [...]
}
  ↓ index 측 1 PostgreSQL transaction (SERIALIZABLE isolation)
  ↓ 모두 INSERT or ROLLBACK
  → 200 { model_id, decision_id, fund_investment_ids: [...], ... }
    또는 409 { error.code: "IDEMPOTENCY_CONFLICT", context.prior_actor, prior_ts }
```

### Idempotency ([D-index-13](/ops/decisions))

```python
# ic skill gate_memo.sh 가 매 호출 동일 input 으로 동일 key 생성
idempotency_key = uuid5(
    namespace=deal_id,
    name=f"{version}::{sha256(memo_v{N}.md + finance_xlsx_hashes)}::{actor_email}"
)
```

- `idempotency_record` 테이블 (hive 패턴 1:1 미러) — PK = idempotency_key, TTL = 24h
- 2번째 동일 key call → 409 + 1차 응답 cached body 반환 (silent re-insert 안 됨)
- 다른 input → 다른 key → 정상 처리

### gate_memo.sh 4 Python check 의 phased migration

[D-index-11](/ops/decisions) 의 결정. **즉시 deprecate 아닌 phased**:

| Script | 현재 (ic skill) | M7 Phase 1 | M7 Phase 2 |
|---|---|---|---|
| `citation_trace.py` | local WARN | local (memo prose audit, 유지) | local (영구 유지 — index 영역 외) |
| `check_arithmetic.py` | local WARN | local (memo prose audit, 유지) | local (영구 유지) |
| `xlsx_integrity.py` | local WARN | **이중 gate** — local 가벼운 pre-flight + index `validate_financial_model` DB trigger | DB trigger 만 (local script archive) |
| `check_exit_matrix.py` | local WARN | **이중 gate** — local + index trigger 가 결과 캐시 (dead_leaf / concentration flag) | DB trigger 만 |

**backward compat 100%** — `--push-to-index` 미사용 시 기존 4 script 그대로 동작.

### REVISION mode 와의 상호작용

```
v7 작성 + --push-to-index → financial_model v7 row DB
  ↓
v7 → v8 REVISION (사용자 피드백 반영)
  ↓
v8 --push-to-index → financial_model v8 row DB (UNIQUE (deal_id, version) 새 row)
  ↓
audit_trail 에 v7 supersede 이벤트 + v8 propose 이벤트 모두 보존
  ↓
last-write-wins (v8 이 "current"), 단 v7 도 time-travel query 가능
```

---

## 3. pmc skill 통합 — greenfield + 2 skill 흡수

상세 = [skill-evolution § pmc](/services/index/skill-evolution#pmc-skill) — 본 페이지는 server-side touchpoint.

### Trigger 3-tier

```
1. SCHEDULED (launchd daily 09:00 KST, 매월 첫 영업일 fire)
   /pmc --quarterly --deal-code <CODE> --push-to-index
   ↓
   - 지난 분기 KPI 스냅샷 (board pack ingest 또는 index prior 조회)
   - board_pack draft (markdown + PDF render)
   - LP quarterly letter draft
   - index.portfolio_kpi + index.valuation propose

2. EVENT (red alert 자동 fire)
   pmc/scripts/risk_monitor.py (launchd 5분 polling)
   ↓ runway < 6M / 핵심 인력 이탈 / burn_spike > 30% 등 감지
   ↓
   /pmc --alert --deal-code <CODE> --kind <runway_under_6m|key_personnel|...>
   ↓ index.risk_alert propose (severity=red, raised_at=now)
   ↓ Teams bot notification (operator + deal partner)

3. MANUAL
   /pmc --postmortem-fill --deal-code <CODE> --timepoint <t_3m|t_6m|t_12m|t_24m|exit>
   /pmc --board-pack --deal-code <CODE> --quarter <YYYY-Q[1-4]>
   /pmc --lp-letter --fund <axe_ia_001> --quarter <YYYY-Q[1-4]>
```

### Postmortem 2-track ([D-index-12](/ops/decisions))

```
ic skill (investment phase — 책임 유지):
  postmortem_stub.py (gate_memo.sh PASS 시 stub 생성, idempotent)
  postmortem_reminder.py (launchd daily 09:00 KST, target_date ±7일 미완 macOS notification)
  references/postmortem-cadence.md (spec SoT)

pmc skill (portfolio care phase — 신규):
  scripts/postmortem_fill_interactive.py (yaml interactive fill, 5 시점)
  scripts/render_postmortem.py (ic 에서 이관)
  --push-to-index → index.postmortem propose
  references/postmortem-cadence.md (ic 의 file 을 symlink — single SoT)
```

같은 yaml file (`ic/_recap/{DEAL}_postmortem.yaml`) 을 양쪽이 다른 동작으로 다룸:
- ic = stub 생성 + reminder
- pmc = fill (yaml 채움) + render (1-pager) + push (index DB)

### 8 sub-agent (ic 19 < pmc 8 — lighter)

| Sub-agent | 역할 | LLM model |
|---|---|---|
| 1. data-fetcher | dataroom board pack PDF / 회사 KPI 자료 / index prior 조회 | Sonnet |
| 2. kpi-extractor | raw data → normalized snapshot (MRR/ARR/churn/runway/...) | Sonnet |
| 3. risk-alerter | portfolio-management 5 신호 vs 실측 비교 → red/yellow flag | Sonnet |
| 4. valuation-updater | last-round / mark-to-market / DCF 중 선택 + NAV 계산 | Opus |
| 5. exit-signal-analyzer | IPO / M&A / Secondary 체크리스트 진행도 평가 | Sonnet |
| 6. board-pack-drafter | KPI + risk + valuation → markdown board deck | Opus |
| 7. lp-comm-drafter | board pack → LP quarterly letter | Opus |
| 8. index-payload-composer | 위 산출물 → propose_*_snapshot JSON payloads | Sonnet |

### 3-phase deprecation ([D-index-12](/ops/decisions))

```
Phase 0 (즉시, M7 시작 시):
  portfolio-management SKILL.md 에 deprecation notice
  investor-relations SKILL.md 에 deprecation notice
  pmc SKILL.md skeleton 등록 (frontmatter + references/ placeholder)

Phase 1 (M7 Phase 1 launch + 첫 quarterly cycle 실행 후):
  portfolio-management KPI 표 → pmc/references/kpi-catalog.md copy + cross-link
  investor-relations LP 보고 구조 → pmc/references/ir-lifecycle.md copy

Phase 2 (3개월 후):
  portfolio-management / investor-relations SKILL.md disable
  Blueprint search/suggest 에서 pmc 로 redirect
  legacy 사용처 grep → pmc 로 갱신

Phase 3 (6개월 후):
  완전 archive (.deprecated/ 이동)
  pmc 가 context 에 fully absorbed
```

---

## 4. 3-layer error model ([D-index-14](/ops/decisions))

skill → index 모든 통신 에러는 일관 3-layer 표현 — 양파껍질 ([D-bp-mcp-1](/ops/decisions)) 의 "진단 불가, 모든 401 같은 메시지" 영구 차단.

### L1 — HTTP (RFC 9728)

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://axe.axelabs.ai/index/mcp",
                  error="invalid_token",
                  error_description="Token aud claim missing index app_id_uri"
Content-Type: application/json
```

### L2 — MCP body (structured)

```jsonc
{
  "error": {
    "code": "FINANCIAL_MODEL_CONFLICT",
    "message": "financial_model (deal=iippo, version=7) already proposed",
    "context": {
      "prior_actor": "ai@axellc.com",
      "prior_ts": "2026-05-06T10:00:00Z",
      "idempotency_key": "uuid-v5-...",
      "existing_model_id": "uuid-mdl-..."
    }
  }
}
```

### L3 — skill handler

```python
# ic skill 의 lib/index_client.py (또는 pmc 의 동형)
def propose_deal_closure(deal_id, version, payload):
    resp = mcp_call("propose_deal_closure", {...})
    match resp.error.code:
        case "IDEMPOTENCY_CONFLICT":
            # 기존 propose 표시 + retry 차단
            print(f"⚠ Already proposed by {resp.context.prior_actor}")
            print(f"  Open ctx review queue: ...")
            return existing_response_from_cache
        case "VALIDATION_WARNING":
            # ctx review 사용자 confirm 요청
            return prompt_user_confirm(resp.context.detail)
        case "SCHEMA_NOT_FOUND":
            print(f"⚠ index schema {resp.context.schema_id} not found")
            print(f"  Run: axe deploy index --register-schemas")
            return None
        case "SERVICE_UNAVAILABLE":
            # 로컬 drafts 보존 + retry 안내
            save_to_local_drafts(payload)
            return None
        case "CITATION_RESOLVE_FAILED":
            # cached value + warning chip
            return resp.context.cached_value_with_warning
```

### `IndexError` Rust enum SoT

```rust
// src/index/error_model.rs
pub enum IndexError {
    IdempotencyConflict { prior_actor: String, prior_ts: String, idempotency_key: Uuid },
    ValidationWarning { check: String, detail: String },
    SchemaNotFound { schema_id: String },
    CitationResolveFailed { citation_kind: String, reason: String },
    FinancialModelLocked { model_id: Uuid, locked_at: DateTime<Utc> },
    DslSyntaxError { formula: String, position: usize, suggestion: String },
    UnknownEnum { field: String, value: String, allowed: Vec<String> },
    DealNotFound { deal_id: String },
    FundEntityNotFound { fund_entity_id: String },
    ServiceUnavailable { reason: String },
    RateLimitExceeded { limit: u32, window: String },
    InsufficientScope { required: String, granted: Vec<String> },
}

impl From<IndexError> for McpErrorResponse {
    fn from(e: IndexError) -> Self {
        // deterministic (code, message, context) mapping
    }
}
```

**unit test 가 모든 case 의 wire-level response 검증** — silent fallback 절대 금지.

---

## 5. Schema authority immutable ([D-index-15](/ops/decisions))

DSL grammar + enum 은 service code 에만, skill 측 override 불가:

| 항목 | 위치 | 변경 절차 |
|---|---|---|
| DSL grammar (10 operator) | `src/index/dsl/grammar.pest` | service code SoT |
| `risk_alert.kind` enum 5종 | `src/index/schemas.rs` | service code SoT |
| `instrument` enum 5종 | `src/index/schemas.rs` | service code SoT |
| `fund_investment.status` enum 6종 | `src/index/schemas.rs` | service code SoT |
| `exit_matrix_leaf.path` enum 6종 | `src/index/schemas.rs` | service code SoT |

**ic skill 의 `scenario_deltas.yaml` 이 DSL syntax error 또는 unknown enum 값 emit 시**:

```jsonc
// ic skill 호출
POST /ingest_financial_model_xlsx { deal_id: "iippo", xlsx_blob: "..." }
  ↓ index 측 validation
  ↓
{
  "error": {
    "code": "DSL_SYNTAX_ERROR",
    "message": "Formula parse failed at position 23",
    "context": {
      "formula": "revenue[y0] * (1 + 0.85)",  // wrong — should reference driver
      "position": 23,
      "suggestion": "revenue[y0] * (1 + revenue_growth_y1)",
      "doc": "https://docs.axelabs.ai/services/index/financial-model#dsl-formula"
    }
  }
}
  ↓
ic skill → ctx review queue 에 highlighted error + 정정 예시 표시 → 사용자 수정 후 confirm
```

**enum value 추가는 coordination PR 강제**:

1. `src/index/schemas.rs` SoT 수정 + 새 case 추가
2. `/index/schemas` envelope `@1.0` → `@2.0` version bump
3. Blueprint `artifact_schema` 자동 mirror (다음 fetch cycle)
4. ic / pmc skill 의 yaml validator 갱신
5. 모두 같은 PR 또는 ordered PR chain

silent 추가 금지 — frame KSME `accounting_standard` enum 영구 freeze 패턴 ([D-frame-fund-ksme-policy-check](/ops/decisions)) 의 index domain 확장.

---

## 6. Cross-deal aggregate — pmc 만의 본질 (혁신 axis)

ic / dd / vc 가 deal-by-deal 시점. pmc 는 **fund × portfolio × sector × time** 4축 aggregate. **LP 보고의 backbone**.

### Fund-level quarterly NAV

```sql
SELECT
  fi.fund_entity_id,
  DATE_TRUNC('quarter', v.asof_date) AS quarter,
  SUM(v.nav_krw) AS total_nav,
  SUM(v.unrealized_krw) AS total_unrealized,
  SUM(v.realized_krw) AS total_realized,
  COUNT(DISTINCT v.deal_id) AS portfolio_size
FROM index.valuation v
JOIN index.fund_investment fi ON v.fund_investment_id = fi.id
WHERE fi.fund_entity_id = 'axe_ia_001'
  AND v.asof_date >= '2025-01-01'
GROUP BY fi.fund_entity_id, DATE_TRUNC('quarter', v.asof_date)
ORDER BY quarter DESC;
```

### Portfolio risk heatmap

```sql
SELECT
  d.deal_code,
  tc.brand_name,
  COUNT(*) FILTER (WHERE ra.severity = 'red') AS red_count,
  COUNT(*) FILTER (WHERE ra.severity = 'yellow') AS yellow_count,
  STRING_AGG(DISTINCT ra.kind, ', ') AS active_alerts
FROM index.deal d
JOIN index.target_company tc ON d.target_company_id = tc.id
LEFT JOIN index.fund_investment fi ON d.id = fi.deal_id
LEFT JOIN index.risk_alert ra ON d.id = ra.deal_id AND ra.resolved_at IS NULL
WHERE fi.fund_entity_id = 'axe_ia_001'
GROUP BY d.id, tc.brand_name
ORDER BY red_count DESC, yellow_count DESC;
```

### Sector KPI benchmark

```sql
SELECT
  tc.sector,
  DATE_TRUNC('quarter', k.asof_date) AS quarter,
  AVG(k.value) FILTER (WHERE k.kind = 'burn_multiple') AS avg_burn_multiple,
  AVG(k.value) FILTER (WHERE k.kind = 'runway_months') AS avg_runway,
  AVG(k.value) FILTER (WHERE k.kind = 'churn_monthly') AS avg_churn,
  COUNT(DISTINCT k.deal_id) AS sample_size
FROM index.portfolio_kpi k
JOIN index.deal d ON k.deal_id = d.id
JOIN index.target_company tc ON d.target_company_id = tc.id
WHERE k.asof_date >= '2025-01-01'
GROUP BY tc.sector, DATE_TRUNC('quarter', k.asof_date)
ORDER BY tc.sector, quarter DESC;
```

→ `pmc/cross_deal/{fund_quarterly_nav_report.md, portfolio_risk_heatmap.xlsx, sector_kpi_benchmark.md, fund_irr_projection.md}` 자동 생성.

→ LP 보고서의 정량 backbone — 매 분기 산출 시간 days → minutes.

---

## 7. 3 worst-case integration risks + 차단

### Risk 1 — Schema drift (퀄리티 axis)

**시나리오**: ic 의 `scenario_deltas/v7.yaml` 의 `revenue_growth_y1: 1.85` (multiplier) 를 index 가 `0.85` (percentage) 로 해석 → IRR 산출 wrong → LP audit fail.

**차단**:
- DSL grammar 가 **service code hardcoded** ([D-index-15](/ops/decisions))
- ingest tool 이 syntax error 시 `DSL_SYNTAX_ERROR` + explicit suggestion + doc link
- Phase 0 acceptance A2 (`compute_outputs ±1pp 일치`) 가 매 PR 검증
- `B-index-integration-fixtures` 가 Iippo/Sentry/Canopy 3 deal regression

### Risk 2 — Korean OCR worst-case (안전 axis)

**시나리오**: dataroom IR PDF 의 "5,941원" → "5,941엔" (yen, 1000x off) 오인식 → 영구 DB 적재 → IRR -2pp.

**차단**:
- `ingest_financial_model_xlsx` 의 input validation: PDF blob 거부 (`B-index-vision-boundary-rule`)
- xlsx 만 vision 보조 — sheet/row label inference 만, numeric value 는 calamine 강제
- Phase 0 acceptance A2 ±1pp tolerance 가 catch
- ic skill 잔존 `citation_trace.py` 가 memo prose 숫자 trace 강제 (PDF 출처 명시)

### Risk 3 — pmc ↔ ic 동시 propose race (안전 axis)

**시나리오**: PCC #6 trigger fire + 동시에 pmc 가 Q4 KPI propose → 같은 `(deal_id, kind, asof_date)` 두 row → audit_trail 충돌.

**차단**:
- `record_kpi_snapshot` MCP tool 의 UPSERT (UNIQUE (deal_id, kind, asof_date))
- `updated_at > now - 6h` 시 warn modal (사용자 의도 확인)
- pmc skill rule: `portfolio_kpi_snapshot.updated_at > (now - 6h)` 시 propose 보류
- audit_trail 에 양쪽 모두 기록 + `is_override=true` flag

---

## 8. 양파껍질 재발 차단 — 21 항목 운영 체크포인트

[mcp-server-checklist § 8](/architecture/mcp-server-checklist) 의 운영 14 → **21** 항목으로 확장 ([B-index-mcp-checklist-extension](/ops/backlog)):

| # | 검증 | 명령 | 정답 |
|---|---|---|---|
| 17 | skill idempotency | 2× call same `propose_deal_closure(idempotency_key=X)` | 2nd call 409 + `context.prior_actor`. silent re-insert 0 |
| 18 | citation resolver roundtrip | ic memo citation `{kind:"index.financial_output", ...}` → resolve → DB query value | floating-point epsilon (0.001 percent) 이내 일치 |
| 19 | cross-skill conflict detect | pmc + ic 동시 `record_kpi_snapshot(같은 deal, kind, asof_date, 다른 value)` | audit_trail 양쪽 기록 + last-write-wins + warning modal |
| 20 | graceful degradation | `docker stop index-mcp` 후 `ic --push-to-index` | MCP timeout (3s) 후 markdown-only fallback + warning banner. memo 정상 render |
| 21 | schema evolution | index 에 `financial_output.tax_adjusted_irr` 새 field 추가. 기존 ic skill 호출 | Blueprint `/schemas` mirror 자동 갱신. 기존 artifact citation resolve 정상. ic skill 다음 run 시 새 field 옵션 사용 |

`axe test index --accept-gate` 통합 명령 + 24h production monitor (error rate under 0.1 percent, citation cache hit over 95 percent).

---

## 9. 변경 분량 추정

| 영역 | Phase 0 | Phase 1 | Phase 2 |
|---|---|---|---|
| **ic skill 변경** | — | ~280-350 lines (SKILL.md Rule 25 + Step 5.5 + gate_memo.sh dispatch + scripts) | — |
| **pmc skill 신규** | SKILL.md skeleton (~150 lines) | 8 sub-agent + references/ (~500 lines) + scripts/ (~300 lines) | render templates + cross-deal SQL |
| **portfolio-management deprecation** | notice 추가 (~10 lines) | KPI 표 → kpi-catalog.md copy | SKILL.md disable |
| **investor-relations deprecation** | notice 추가 (~10 lines) | LP 구조 → ir-lifecycle.md copy | SKILL.md disable |
| **index Rust 코드** | error_model.rs + integration fixtures (~1500 lines) | propose_deal_closure + idempotency_record (~800 lines) | cross-deal SQL views + pmc tools (~600 lines) |
| **Blueprint client** | `lib/index_client.py` skill 측 wrapper (~200 lines) | citation resolver (`citations/index.ts`) (~300 lines) | UI integration |
| **docs** | 본 페이지 + skill-evolution.mdx update | financial-model.mdx + schema-catalog.mdx update | pmc-specific section 추가 |

총 추정: Phase 0 ~ 2,000 lines, Phase 1 ~ 2,500 lines, Phase 2 ~ 1,500 lines. 6 weeks Phase 0+1, 12 weeks total (M7 완료).

---

## 관련 페이지

- [/services/index](/services/index) — 서비스 main
- [/services/index/financial-model](/services/index/financial-model) — 6-table SoT + 3 deal worked example
- [/services/index/schema-catalog](/services/index/schema-catalog) — 14 schemas spec
- [/services/index/skill-evolution](/services/index/skill-evolution) — 5 skill → service 진화 (본 페이지의 자매)
- [/ops/decisions D-index-11~15](/ops/decisions) — 5 신규 결정
- [/ops/backlog](/ops/backlog) — 본 페이지의 M7 implementation 항목 (`B-ic-push-mode-impl` 외 9 항목)
- [/architecture/mcp-server-checklist](/architecture/mcp-server-checklist) — 21 운영 체크포인트 (B-index-mcp-checklist-extension 후)
- [/architecture/artifacts](/architecture/artifacts) — Blueprint artifact + citation kind `index.*` (D-index-6 mirror)
