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

---
title: Cortex
description: AI-native relationship CRM — artifact-first, per-user private, Google Contacts canonical. Rust + axum + sqlx.
---

# Cortex

**한 줄 소개**: 강수훈 개인이 Windows OneDrive 의 `Network_CRM.xlsx` 로 운영해온 1인용 투자 네트워크 CRM 을 platform 안으로 끌어들임. artifact-first ([D-cortex-1](/ops/decisions)), per-user private RLS ([D-cortex-2](/ops/decisions)), Google Contacts 단방향 pull canonical ([D-cortex-3](/ops/decisions)), Day 1 multi-user ([D-cortex-5](/ops/decisions)), MCP + read-only 웹 UI ([D-cortex-6](/ops/decisions) → [D-cortex-6.1](/ops/decisions)), Rust ([D-cortex-7](/ops/decisions)). 2026-05-26 출발 · 2026-05-29 웹 UI 추가.

기존 `/Users/axe/cortex` (file-based "VC 심사역 AI 기억 시스템", 2026-03 시작) 은 도메인이 유사했지만 모델이 새 설계와 완전히 달라 `.legacy.20260526/` 으로 폐기 보존.

## 사용하기 — 설치 후 무엇을·어디서·어떻게 (직원용)

> **여기부터 읽으세요.** Cortex 에는 "로그인해서 클릭하는" 별도 앱 화면이 없습니다. 평소 쓰는 **AI 에이전트(Claude Code · Codex · Cursor · claude.ai 채팅)에게 한국어로 시키면**, 그 에이전트가 `axe` CLI 로 Cortex 를 대신 호출합니다. 데이터를 눈으로 둘러보기만 할 때는 **읽기 전용 웹 포털**이 따로 있습니다. 아래 ②~③(스택·포트·인증 등)은 운영자/개발용이므로 직원은 건너뛰어도 됩니다.

### 접근 방법 3가지 — 뭘 고를까

| 방법 | "front end" 가 뭐냐 | MS 관리자 권한 |
|---|---|---|
| **① axe CLI (권장)** | **너의 AI 에이전트** (Claude Code·Codex·Cursor·claude.ai) — 또는 그냥 터미널 | 불필요 ✅ |
| ② 읽기 전용 웹 포털 | 브라우저 — [`axe.axelabs.ai/cortex`](https://axe.axelabs.ai/cortex) | 불필요 ✅ |
| ③ claude.ai MCP 커넥터 | claude.ai 커넥터 설정 화면 | **MS 관리자 consent 필요** ⚠️ |

> **관리자 계정이 아니면 ①을 쓰세요.** claude.ai 에서 Custom Connector 추가가 `Authorization with the MCP server failed … Entra Trace ID …` 로 막히는 건 정상입니다 — ③의 커넥터 흐름은 MS 테넌트 관리자의 사전 consent 가 있어야 동작하기 때문입니다([인증](#인증)). **커넥터는 안 해도 됩니다.** `axe` CLI 는 본인 Microsoft 로그인만으로 Cortex 를 쓰게 해주므로 ①로 우회하면 끝입니다.

### ① axe CLI — 3단계

**1. 설치 + 로그인 (한 번만)** — 상세/문제해결은 [AXE CLI](/services/cli):

```bash
mkdir -p ~/axe-cli && curl -fsSL https://axe.axelabs.ai/cli -o ~/axe-cli/axe && chmod +x ~/axe-cli/axe
alias axe="~/axe-cli/axe"            # 셸 rc 에도 추가
axe login                            # 브라우저로 Microsoft 로그인 1회
axe whoami                           # 출력 scope 에 cortex 가 보이면 준비 끝
```

**2. "front end" = 너의 AI 에이전트.** Claude Code · Codex · Cursor · claude.ai 채팅 중 아무거나 열고, 아래 블록을 그대로 붙여넣으면 그 에이전트가 Cortex 를 쓸 수 있게 됩니다:

```text
너는 AXE 의 `axe` CLI 로 Cortex(투자 인맥 CRM)를 쓸 수 있다 — MCP 커넥터 불요.
항상 (1) 도구를 먼저 조회하고  axe cortex tools
     (2) 그 스키마대로 호출한다  axe cortex call <tool> --args '<json>'
주요 도구:
  search_person   이름·회사·메모 등 아무 필드나 substring 으로 인물 검색
  get_person      한 인물의 상세 + citation + 최근 이력
  register_person 신규 인물 등록 (Google 연동 시 내 연락처에도 생성)
  log_interaction 미팅·통화·이벤트 기록 (참석자별 관계도 자동 생성)
  classify_person 인물에 분류 태그 부착 (예: 담당=나, 구분=LP)
  connect_google  내 Google 연락처를 Cortex 로 동기화 시작
규칙:
- 도구 이름·인자는 외우지 말고 매번 `axe cortex tools` 결과에서 가져와라.
- 딜 코멘트·협상 의견·사적 메모처럼 민감하면 visibility=private 로 저장할지 나에게 먼저 물어봐라(기본 shared).
- 호출이 401/403 이면 먼저 `axe refresh`, 그래도 안 되면 `axe login` 재실행을 안내해라.
```

붙여넣은 뒤에는 그냥 한국어로 시키면 됩니다 — 예: *"홍길동 연락처 찾아줘"*, *"방금 ABC캐피탈 김상무랑 한 미팅 기록해줘"*, *"내 구글 연락처 Cortex 에 연동해줘"*.

**3. (선택) 터미널에서 직접** — 에이전트 없이 손으로 칠 때:

```bash
axe cortex tools                                          # 12개 도구 목록
axe cortex call search_person   --args '{"q":"홍길동"}'
axe cortex call register_person --args '{"name":"김상무","org":"ABC캐피탈","memo":"2026 Q2 소개"}'
axe cortex call connect_google  --args '{}'               # → 반환된 authorize_url 을 브라우저로 열기
```

### ② 읽기 전용 웹 포털 — 눈으로 둘러보기

추가·수정 없이 **보기만** 할 거면 브라우저로:

- [`https://axe.axelabs.ai/cortex`](https://axe.axelabs.ai/cortex) → Microsoft 로그인 → 대시보드 / 인물 / 조직 / 딜 / 상호작용 / 관계 그래프
- 본인 데이터만 보입니다(owner-scoped). **읽기 전용**이라 등록·수정은 ①(CLI/에이전트)로 합니다.

### Google 연락처 연동 (1회)

Cortex 는 본인 Google 연락처를 canonical source 로 **단방향 pull** 합니다:

1. 에이전트에 *"내 구글 연락처 연동해줘"* (또는 `axe cortex call connect_google --args '{}'`)
2. 반환된 `authorize_url` 을 브라우저로 열어 Google consent
3. "✓ 연결 완료" 후 다음 sync cycle(또는 `sync_google_now`)부터 자동 유입. 민감 라벨(`Personal`/`HPE`)이 붙은 연락처는 자동 제외됩니다(아래 visibility / private partition 정책 참조).

## 기술 스택

| 항목 | 값 |
|---|---|
| 언어 | Rust 1.95+ ([D-cortex-7](/ops/decisions)) |
| HTTP | axum 0.7 + tower-http + tokio |
| DB | PostgreSQL 16 (sqlx 0.8, rustls, runtime-tokio) |
| Migration | `sqlx migrate run` (alembic 대신, raw .sql) |
| 인증 | jsonwebtoken (Entra ID RS256 JWKS, HS256 state) + reqwest (rustls-tls) |
| Google API | raw reqwest (oauth2 crate 미사용 — rustls 일관성) |
| Logging | tracing + tracing-subscriber (env-filter, json) |
| CLI | clap 4 (subcommand: migrate / mcp-http / show-settings) |
| 단일 binary | 6.7 MB stripped (multi-stage Dockerfile) |

## 포트 (32xx)

| 포트 | 컨테이너 | 용도 |
|---|---|---|
| 3200 | cortex-postgres | PostgreSQL 16 |
| 3210 | cortex-mcp-blue | MCP HTTP/SSE (active) |
| 3211 | cortex-mcp-green | MCP HTTP/SSE (passive, blue/green) |
| 3212 | axe-cortex-proxy | Caddy selector (blue/green swap) — task 15 |

Cloudflared origin: `axe.axelabs.ai/cortex/* → host.docker.internal:3212` (axe-tunnel Docker, task 16).

## 접속

| Endpoint | 인증 | 용도 |
|---|---|---|
| `https://axe.axelabs.ai/cortex/healthz` | 없음 | health probe |
| `…/cortex/mcp/.well-known/oauth-protected-resource` | 없음 | RFC 9728 metadata — **claude.ai 가 실제 조회하는 resource-level 경로** (apex `/.well-known/…` + `/cortex/.well-known/…` 도 동일 응답 제공) |
| `https://axe.axelabs.ai/cortex/mcp` | Entra ID Bearer | MCP JSON-RPC 2.0 endpoint |
| `https://axe.axelabs.ai/cortex/schemas` | Entra ID Bearer | typed fact schema registry (mcp_schema 테이블) |
| `https://axe.axelabs.ai/cortex/oauth/google/callback` | state JWT 자체검증 | Google OAuth Web flow callback |
| `https://axe.axelabs.ai/cortex` | 없음 | 웹 UI 랜딩 ([D-cortex-6.1](/ops/decisions)) — 미인증 시 `/cortex/login` 으로 303 |
| `https://axe.axelabs.ai/cortex/login` · `/login/start` · `/login/callback` | 없음 (Entra federation) | 웹 SSO 흐름. `/login/callback` 은 Entra Redirect URI 에 등록 필수 |
| `https://axe.axelabs.ai/cortex/home` · `/people` · `/orgs` · `/deals` · `/interactions` · `/relationships` · `/a/:id` | `cortex_session` 쿠키 (HS256, `tok="web"`, 8h sliding) | read-only 운영자 포털 (dashboard / 리스트 / detail) |
| `POST /cortex/logout` | (idempotent) | 세션 쿠키 즉시 만료 |

## 데이터 모델 (artifact-first)

5 테이블, RLS FORCE + cortex_app NOSUPERUSER role + append-only trigger.

```
artifact         -- typed fact (kind = person | organization | deal | interaction | relationship | note | attribute)
  └─ payload jsonb   ← 약속: known fields + 임의 미정의 필드 (additionalProperties: true)
  └─ owner_id        ← RLS 격리 키
  └─ visibility      ← shared | private (D-cortex-9) — outward propagation gate. default shared
  └─ schema_version  ← mcp_schema 와 매칭

citation         -- evidence (kind = google_contact | remember_card | teams_msg | mail_thread | frame_query | external_url)
  └─ artifact_id FK + ref jsonb (kind 마다 다른 shape)

artifact_event   -- append-only audit
  └─ op (propose | confirm | reject | edit | dispatch | archive | restore)
  └─ actor (user email 또는 system:google-sync)
  └─ payload_before / payload_after (diff 기록)
  └─ UPDATE/DELETE 는 trigger 가 raise

mcp_schema       -- (kind, version) → JSON schema. payload 검증 advisory 가능 (현재 미강제)

google_oauth_token  -- per-user, owner_id primary key
  └─ refresh_token bytea ← pgp_sym_encrypt(CORTEX_PII_PASSPHRASE_AXEC)
  └─ status (active | revoked | expired)
```

**Relationship 은 artifact 의 한 kind** (kind='relationship', payload = `{from_artifact_id, to_artifact_id, edge_kind, context}`). edge_kind 예: `introduced_by` / `works_at` / `attended_meeting` / `lp_invested_fund` / `referenced_in_deal` / `co_attended`. 그래프 쿼리는 1차에 JSONB GIN index, 부족 시 materialized artifact_link 추가.

**임의 정보 적재 유연성**: artifact.kind, citation.kind 가 free-form string (DB 측 enum 강제 X). payload 가 JSONB 라 어떤 구조도 round-trip. 사전 정의 안 된 `kind='unicorn'`, citation `kind='voice_memo'`, 미정의 payload 필드 `aircraft_collection_hobby` 모두 무손실 저장. 검증 layer 는 (1) MCP tool inputSchema (additionalProperties: true), (2) mcp_schema 등록 advisory, (3) 운영 hygiene 쿼리 (`SELECT DISTINCT kind FROM artifact GROUP BY 1`). 안전은 RLS + append-only audit + citation provenance 로.

## visibility / private partition ([D-cortex-9](/ops/decisions))

**핵심**: 현재 모든 artifact 는 이미 RLS (`owner_only`) 로 owner 본인에게만 보임 — 타인·관리자·감사자가 볼 경로 자체가 없음. 따라서 `visibility` 는 본인 vs 타인 격리가 아니라, 데이터가 **owner 의 RLS 섬을 벗어나는 미래 표면을 막는 propagation gate**. 그 표면 = **Blueprint M6 artifact mirror** ([D-bp-artifact-1](/ops/decisions), [/architecture/artifacts](/architecture/artifacts)) — Blueprint `Artifact` 테이블은 `workspace_id`+`entity_id` scope 이라 `owner_id`·`visibility` 가 없어 mirror 시 owner 격리가 풀림. M6 이 아직 📐 설계 (2026 Q4) 라 mirror 가 생기기 전 지금 박아둠.

| tier | owner 본인 세션 | 바깥 (mirror/export/dashboard) | Google push |
|---|---|---|---|
| `shared` (기본) | 보임 | **보임** (`WHERE visibility='shared'`) | register_person 시 createContact |
| `private` | 보임 | **절대 안 나감** | **skip** |

- **기본값 `shared`**: Google 동기화 base 연락처는 이미 Google 에 존재하는 저민감 canonical. 민감 분류(HPE 등)·xlsx import sensitive 컬럼·명시 flag 만 `private`. 신규 민감 데이터(HPE 138건)가 아직 미유입(`B-cortex-xlsx-import-run`)이라 backfill 불필요.
- **계약 (Phase 2, M6 착수 시)**: 모든 outward propagation 은 `visibility='shared'` 만. `private` artifact 의 audit event 는 redact. payload 내 field-level privacy (`payload.private.*` 하위객체 + `CORTEX_PII_PASSPHRASE` pgp) 는 같은 단계.
- **§6 스톱갭 (Phase 0)**: `list_persons(exclude_classification_key=, exclude_classification_value=)` 로 `구분=개인` 같은 분류 태그 항목을 export 에서 제외. `skip_audit_payload=true` 로 민감 등록·수정의 before/after snapshot 생략 (메타만).
- **sync 유입 필터 (Phase 1)**: visibility 가 *이미 유입된* artifact 의 전파 게이트라면, 이쪽은 **유입 자체를 막는 더 앞단 게이트** — 민감 라벨 연락처를 Cortex 에 안 들이는 가장 강한 보장. Google People sync 가 contactGroup(라벨) 이름 기준으로 거른다: `CORTEX_SYNC_GROUP_BLACKLIST`(기본 `Personal,HPE`) 라벨이 붙은 연락처는 애초에 들어오지 않음. `CORTEX_SYNC_GROUP_WHITELIST` 설정 시 그 라벨 보유 연락처만 통과(gate)하고 blacklist 가 더 강한 veto. 동작: sync 시작 시 contactGroups id→name 맵 1회 로드(필터 비활성이면 호출 생략) → 연락처별 `contactGroupMembership` 를 case-insensitive 매칭. 맵 로드 실패 시 해당 owner cycle 을 fail-safe 로 통째 skip(민감 라벨 누유 방지, 다음 cycle 재시도, 한 owner 실패는 다른 owner 무영향). 제외 수는 `sync_google_now` 의 `skipped_by_label` 로 보고. **이미 유입된 항목은 소급 삭제 안 함** — 그건 Phase 0 export 제외 / 별도 정리 영역. ([B-cortex-visibility-phase1-sync-filter](/ops/backlog))
- **visibility 결정 UX (누가 private 을 정하나)**: 기본값 `shared`. 행위자 3분 — 사용자(자연어) → 커넥터 LLM(인자 결정, 질문 가능) → Cortex MCP(실행, 질문 불가). write tool 4종(register/classify/update_person + log_interaction) description 에 **SENSITIVITY RULE** 을 심어, `visibility` 미지정 + 민감 가능 내용(딜 코멘트·협상 의견·사적 메모·비공개 미팅)이면 커넥터 LLM 이 저장 전 *"private/shared?"* 를 **먼저 확인**. 일반 공개 정보(이름·소속·전화)는 묻지 않고 shared. (MCP 는 요청-응답이라 직접 못 물음 → LLM-guidance 패턴.) `register_person`/`log_interaction` 은 `visibility=private` 시 Google push·외부 전파 생략.
- **필드 단위 — 공유 인물 + 비공개 메모**: person 은 `register_person(visibility=shared)`, 민감 메모는 **별도** `classify_person(..., visibility=private)` attribute 로 (둘이 별개 artifact 라 각자 visibility). 한 payload 안의 특정 필드만 private 은 Phase 2.
- **Phase 3 (opt-in)**: client-key sealed E2E — 서버 복호화 불가, search·Google push 포기 trade-off.

## MCP Tools (12)

`POST /cortex/mcp` JSON-RPC 2.0. Bearer auth 필수. **모든 tool inputSchema property key 는 ASCII** (`^[a-zA-Z0-9_.-]{1,64}$`) — Korean key 한 개라도 들어가면 Anthropic API 가 tools/list 전체를 400 으로 거부 ([known-gaps](/ops/known-gaps) Cortex 함정 #6). 값(value) 은 Korean 자유.

| Tool | 동작 | 비고 |
|---|---|---|
| `whoami` | 인증된 사용자의 email + sub claim 반환 | 헬스 체크 |
| `list_persons` | kind='person' artifact 페이지네이션 (owner-scoped) | limit 1-200, offset, `visibility` (shared\|private\|all, 기본 all), `exclude_classification_key`/`_value` (share-safe export §6) |
| `search_person` | payload::text ILIKE substring (임의 필드 매칭) | 미정의 필드도 검색 가능 |
| `get_person` | artifact + citations + 최근 20 event 묶음 | UUID |
| `register_person` | 신규 person artifact 생성 — payload 가 args 전체 (additionalProperties 그대로) + 'propose' audit event. Google 연동 사용자는 People API `createContact` 까지 수행 후 `google_contact` citation 추가 ([D-cortex-4](/ops/decisions) opportunistic push) | payload key `memo` (ASCII) — Korean key 금지. `visibility`(기본 shared) — **private 면 Google push skip**. `skip_audit_payload` |
| `log_interaction` | interaction artifact + participants 마다 relationship artifact (edge_kind='attended', interaction 의 visibility 상속) | 미팅·통화·이벤트. `visibility`(비공개 미팅) + `skip_audit_payload` |
| `list_interactions` | interaction 페이지 + person_id 필터 (payload->'participants' @> jsonb_array) | |
| `classify_person` | person 에 typed 분류 attribute 부착 — kind='attribute' artifact (subject_artifact_id + key + value). (subject, key) idempotent | key free-form (`HPE`/`AXEV`/`구분1`/`담당`/`Top100` 등). `visibility`(민감 분류는 private) + `skip_audit_payload` |
| `update_person` | person payload JSONB shallow-merge patch + before/after audit event | **Google push 안 함** ([D-cortex-3](/ops/decisions) 단방향). 신규는 register_person, 분류는 classify_person. `visibility` re-tag(shared↔private) + `skip_audit_payload` (snapshot 생략) |
| `connect_google` | Google OAuth Web flow 시작 — authorize_url 반환 (10분 TTL state JWT) | 사용자가 브라우저 → consent → callback |
| `disconnect_google` | refresh_token revoke (DB status='revoked' + Google revoke best-effort) | idempotent |
| `sync_google_now` | 다음 cycle 안 기다리고 즉시 1회 Google → Cortex sync | inserted/updated/pages/`skipped_by_label` 카운트 반환 (`skipped_by_label` = 라벨 필터로 유입 제외된 연락처 수, [D-cortex-9](/ops/decisions) Phase 1) |

### Schema discovery — `GET /cortex/schemas`

Blueprint artifact + PARA layer ([D-bp-artifact-1](/ops/decisions)) 미러를 위해 frame 패턴 차용. 현재 빈 응답 — mcp_schema 테이블에 typed fact schema 등록 시 노출 (`person`, `organization`, `deal`, `interaction`, `relationship`, `note` schema 향후 등록).

## 인증

### Entra ID (RS256, OAuth-RP)

- App: "Cortex MCP", appId=`60d04ea8-5d7b-453e-a4c0-af421b4689f5` (등록 2026-05-26 via az CLI)
- 형식: frame_mcp / hive_mcp / blueprint_mcp 와 동형 (signInAudience=AzureADMyOrg, requestedAccessTokenVersion=2, identifierUris=`https://axe.axelabs.ai/cortex/mcp`, scope `mcp.access`, webRedirectUris=claude.ai+claude.com `/api/mcp/auth_callback`)
- 검증: JWKS 1h TTL cache (RwLock fast→slow path), RS256, audience (MCP URI + client_id 둘 다 허용), issuer (`login.microsoftonline.com/{tenant}/v2.0`), exp/iat 자동
- 401 응답: RFC 9728 § 5.1 WWW-Authenticate 4-tuple (realm + error + error_description + resource_metadata URI)
- email 추출 fallback: `email` → `preferred_username` → `upn`

### claude.ai connector 등록 흐름 (Microsoft-direct federation)

frame 의 "직접-Microsoft" 패턴과 동형 ([D-cortex-8](/ops/decisions)). claude.ai 또는 Claude Code 에서 Cortex 를 custom connector 로 추가할 때:

1. **Connector URL**: `https://axe.axelabs.ai/cortex/mcp`
2. claude.ai 가 `https://axe.axelabs.ai/cortex/mcp` → 401 → `WWW-Authenticate` 의 `resource_metadata` 따라 **resource-level** RFC 9728 metadata 조회. Cortex 는 3 경로 모두 제공: `/.well-known/oauth-protected-resource`, `/cortex/.well-known/oauth-protected-resource`, **`/cortex/mcp/.well-known/oauth-protected-resource`** (claude.ai 가 실제로 찌르는 경로 — 빠지면 discovery 실패 → origin `/authorize` fallback → blueprint apex 404, [known-gaps](/ops/known-gaps) Cortex 함정 #3).
3. metadata 의 `authorization_servers[0]` = **Microsoft** (`https://login.microsoftonline.com/{tenant}/v2.0`), `scopes_supported` 에 `https://axe.axelabs.ai/cortex/mcp/mcp.access` 포함. → claude.ai 는 Microsoft 의 RFC 8414 metadata 를 직접 읽고 Microsoft 로 authorize.
4. claude.ai 가 **client_id + client_secret 직접 입력**을 요구 (confidential client). 입력값:
   - client_id = `60d04ea8-5d7b-453e-a4c0-af421b4689f5`
   - client_secret = vault `cortex/axe/oauth-client-secret`
5. Microsoft consent → claude.ai 가 RS256 access token 수령 (aud = MCP App ID URI 또는 client_id) → 이후 `/cortex/mcp` 호출에 Bearer 첨부.

> **AS proxy 는 dormant** ([D-cortex-8](/ops/decisions)): `src/oauth_as.rs` 에 full RFC 8414 + /authorize→Microsoft + /token PKCE + /register DCR 프록시를 구현했으나, frame 의 Microsoft-direct 가 live 라 **현재 미사용**. `authorization_servers` 를 cortex 자신으로 돌리고 싶을 때(예: claude.ai 가 client_secret 입력을 못 받는 클라이언트 지원) 활성화. `verify_cortex_jwt()` 만 auth.rs 가 HS256 운영자 path 용으로 사용 중.

### Google OAuth 2 (Web flow, per-user)

- Cortex → Google People API on behalf of user
- App: "Cortex" Web client (Cloud Console, GCP project Gemini) — 등록 task 8
- Authorized redirect URI: `https://axe.axelabs.ai/cortex/oauth/google/callback`
- Consent screen: External + testing mode (100 사용자 한도, AXE Labs 인원 충분)
- Scope: `https://www.googleapis.com/auth/contacts` (read + write — read 는 sync, write 는 register_person → createContact)
- refresh_token 저장: `google_oauth_token.refresh_token bytea` = `pgp_sym_encrypt(token, CORTEX_PII_PASSPHRASE_AXEC)`. 분실 = 사용자별 재인증 필요 (DB 행만 영향, 다른 데이터 무영향).
- State JWT (HS256 with CORTEX_JWT_SECRET, 10분 TTL): callback 인증 (DB 없이 stateless owner_id 추출)

## 환경 변수 (.env.local)

| 변수 | Source | 용도 |
|---|---|---|
| `CORTEX_DB_HOST/PORT/NAME/USER/PASSWORD` | vault `cortex/axe/db-password` | Postgres 연결 |
| `AZURE_AD_TENANT_ID` | hardcoded `122fb574-7efa-476a-95b6-bee81bce2cce` | Entra ID tenant |
| `AZURE_CORTEX_MCP_CLIENT_ID` | 등록 후 `60d04ea8-...` | MCP audience claim 매칭 |
| `AZURE_CORTEX_MCP_CLIENT_SECRET` | vault `cortex/axe/oauth-client-secret` | OAuth confidential client (claude.ai 요구) |
| `AZURE_CORTEX_MCP_AUDIENCE` | `https://axe.axelabs.ai/cortex/mcp` | JWT aud 검증 |
| `GOOGLE_OAUTH_CLIENT_ID` | Cloud Console 등록 후 | Google OAuth Web flow |
| `GOOGLE_OAUTH_CLIENT_SECRET` | vault `cortex/axe/google-oauth-client-secret` | 동상 |
| `GOOGLE_OAUTH_REDIRECT_URI` | `https://axe.axelabs.ai/cortex/oauth/google/callback` | callback 등록 매칭 |
| `CORTEX_PII_PASSPHRASE_AXEC` | vault `cortex/axe/pii-passphrase-axec` | pgp_sym_encrypt key (refresh_token 암호화) |
| `CORTEX_JWT_SECRET` | vault `cortex/axe/jwt-secret` | HS256 — OAuth state 서명 + 운영자 path B |
| `CORTEX_MCP_HOST/PORT` | `0.0.0.0:3210` | MCP server bind |
| `CORTEX_DEPLOY_COLOR` | `blue` 또는 `green` | blue/green swap |
| `CORTEX_PUBLIC_BASE_URL` | `https://axe.axelabs.ai` | WWW-Authenticate resource_metadata URI base |
| `CORTEX_SYNC_INTERVAL_SECONDS` | `600` (10분) | per-user sync loop interval |
| `CORTEX_GOOGLE_RATE_LIMIT_PER_MIN` | `90` | Google People API 한도 |
| `CORTEX_SYNC_GROUP_BLACKLIST` | `Personal,HPE` (기본) | **sync 유입 라벨 차단** — 이 contactGroup(라벨) 이름이 붙은 Google 연락처는 sync 가 Cortex 에 들이지 않음 ([D-cortex-9](/ops/decisions) Phase 1). 라벨 이름 case-insensitive 매칭. 명시적 빈 값 (`CORTEX_SYNC_GROUP_BLACKLIST=`) 으로 필터 비활성화 |
| `CORTEX_SYNC_GROUP_WHITELIST` | (빈 값) | 비어있지 않으면 이 라벨 중 하나라도 붙은 연락처만 유입 (gate). blacklist 가 whitelist 보다 강한 veto. 둘 다 비면 필터 off (contactGroups API 호출 자체 생략) |

[D-ops-17](/ops/decisions): secret 은 vault SoT, `axe ship cortex` 가 deploy-time 에 vault → `.env.local` pull. 코드는 runtime 에 vault 호출 X.

## 운영

```bash
# 로컬 개발 (postgres 만 docker, MCP server 는 cargo run 으로 hot iterate)
cd /Users/axe/cortex
docker compose up -d postgres
sqlx migrate run
cargo run --release -- mcp-http

# 또는 배포 형태 (full docker)
docker compose up -d --build

# 배포 (axe ship — release-gate + blue/green swap)
axe ship cortex --customer axe

# DB 직접 점검
docker exec -it cortex-postgres psql -U cortex -d cortex
SELECT count(*), kind FROM artifact GROUP BY kind;

# Schema 디스커버리
curl -H "Authorization: Bearer $TOKEN" https://axe.axelabs.ai/cortex/schemas

# 사용자 Google 연동 (Claude.ai 안에서 MCP tool 호출)
# 1. claude.ai/customize/connectors 에 Cortex 등록 (MCP Connectors catalog 참조)
# 2. MCP tool: connect_google() → authorize_url 받음 → 브라우저로 열기
# 3. Google consent → callback → "✓ Google 연결 완료" 페이지
# 4. 다음 sync cycle (또는 sync_google_now) 부터 Google Contacts pull
```

## 폐기된 것

- `/Users/axe/cortex.legacy.20260526/` (623 MB) — file-based recall 시스템. data/ 의 markdown topic tree (186) + entity profile (16) + keyword DB + connection graph 는 추후 enrichment migration 시 참고용.
- `cortex.axellc.com` 도메인 (cloudflared `~/.cloudflared/config.yml` 에서 ingress 제거됨). `com.cortex.cloudflared` launchd 는 `mysrt.axellc.com` 만 서비스 중 — 추후 mysrt 를 axe-tunnel Docker 로 이전 후 launchd 자체 폐기 ([B-mysrt-tunnel-consolidate](/ops/backlog)).
- `~/Network/network_crm.py` 양방향 sync 스크립트 + `Network_CRM.xlsx` (660 rows) + Google OAuth Desktop client "Network Manager" — task 17 (xlsx enrichment migration) 완료 후 `~/Network.legacy/` 로 archive.

## 관련 서비스

- **Frame** — deal artifact 가 frame entity ID 를 referenced field 로 보유 (`payload.frame_deal_ref`). 향후 frame 의 deal 도메인 ↔ cortex 의 deal artifact 간 양방향 link.
- **Blueprint** — M6 (Q4 2026 target → Day 1 Cortex 로 escalation) artifact + PARA UI mirror. Blueprint 가 Cortex artifact 를 owner-scoped 로 mirror 후 PARA dispatch + IC 메모 citation 에 활용.
- **Hive** — 직원도 사람 ([D-cortex-3](/ops/decisions)). 외부 contact → 직원 transition path 는 추후 결정 ([B-cortex-hive-link](/ops/backlog)).

## SoT

- **본 페이지** = operator SoT (signed AXE LLC)
- [`/Users/axe/cortex/CLAUDE.md`](https://github.com/soohunkang/cortex/blob/main/CLAUDE.md) — local 운영 노트
- [`/Users/axe/cortex/docs/operator-setup.md`](https://github.com/soohunkang/cortex/blob/main/docs/operator-setup.md) — operator 직접 작업 절차 (Entra credential reset, vault push, Google Cloud Console GUI)
- [`/Users/axe/.axe/customers.yaml`](https://github.com/soohunkang/blueprint/blob/main/multi-tenant-platform-plan.md) `services.cortex` 매니페스트 + `sso.apps.cortex_mcp` 등록
- alembic 0001 의 SQL 본문 = `migrations/20260526210000_initial_artifact_schema.{up,down}.sql` (sqlx-cli 포맷)
