Cortex
한 줄 소개: 투자 네트워크를 다루는 1인용 CRM. 별도 앱을 열어 폼을 채우는 대신, 평소 쓰는 AI 에이전트에게 한국어로 시키면 그 에이전트가 Cortex 에 기록하고 찾아 준다. 데이터를 눈으로 훑을 때는 읽기 전용 웹 포털이 따로 있다.
- 웹:
https://cortex.axelabs.ai - MCP:
https://cortex.axelabs.ai/mcp(Bearer 토큰)
기록은 전부 본인 것만 보인다. 다른 사람은 물론 관리자도 남의 기록을 조회할 경로가 없다. 모든 변경은 지울 수 없는 이력으로 남고, 각 기록에는 어디서 온 정보인지(구글 연락처 · 메일 · 메시지 · 외부 링크) 근거가 붙는다.
사용하기
여기부터 읽으세요. Cortex 에는 “로그인해서 클릭하는” 별도 업무 앱 화면이 없습니다.
접근 방법 3가지 — 뭘 고를까
| 방법 | ”front end” 가 뭐냐 | 관리자 권한 |
|---|---|---|
| ① axe CLI (권장) | 당신의 AI 에이전트 (Claude Code · Codex · Cursor · claude.ai) — 또는 그냥 터미널 | 불필요 ✅ |
| ② 읽기 전용 웹 포털 | 브라우저 — cortex.axelabs.ai | 불필요 ✅ |
| ③ claude.ai MCP 커넥터 | claude.ai 커넥터 설정 화면 | Microsoft 테넌트 관리자 동의 필요 ⚠️ |
관리자 계정이 아니면 ①을 쓰세요. claude.ai 에서 Custom Connector 추가가 인증 오류로 막히는 것은 정상입니다 — ③은 테넌트 관리자의 사전 동의가 있어야 동작합니다. 커넥터는 안 해도 됩니다.
axeCLI 는 본인 Microsoft 로그인만으로 Cortex 를 쓰게 해줍니다.
① axe CLI — 4단계
1. 설치 (로그인 불필요) — 에이전트 에디션 다운로드는 공개입니다. 계정이 없어도 내려받아 axe --version 까지 확인할 수 있습니다.
mkdir -p ~/axe-cli
curl -fsSL "https://axe.axelabs.ai/api/cli/download?edition=agent" -o ~/axe-cli/axe
chmod +x ~/axe-cli/axe
alias axe="~/axe-cli/axe" # 셸 rc 에도 추가
axe --version # 여기까지는 계정 없이 됩니다2. 로그인 (계정 필요) — 여기서부터는 AXE 워크스페이스 계정이 있어야 합니다.
axe login # 브라우저로 Microsoft 로그인 1회
axe whoami # 출력 scope 에 cortex 가 보이면 준비 끝어딘가에 적어 두신 옛 명령이 있다면 버리세요. 예전 안내가 쓰던
curl -fsSL <워크스페이스>/cli -o ~/axe-cli/axe는 더 이상 동작하지 않습니다 — 공개/cli바이너리 경로는 폐기돼 404 입니다.curl -f가curl: (56) The requested URL returned error: 404한 줄을 내고 종료하므로 파일은 아예 만들어지지 않고, 뒤에 이어 붙인chmod +x도 실행되지 않습니다. 로그인한 사용자는 워크스페이스 대시보드의 ”⤓ CLI 설치” 카드에서 토큰이 박힌 설치 명령(/cli.sh설치기)을 복사해 쓰셔도 됩니다 — 그 설치기는 운영자 에디션까지 받을 수 있어AXE_TOKEN을 요구합니다.
3. “front end” = 당신의 AI 에이전트. Claude Code · Codex · Cursor · claude.ai 채팅 중 아무거나 열고, 아래 블록을 그대로 붙여넣으면 그 에이전트가 Cortex 를 쓸 수 있습니다:
너는 AXE 의 `axe` CLI 로 Cortex(투자 인맥 CRM)를 쓸 수 있다 — MCP 커넥터 불요.
항상 (1) 도구를 먼저 조회하고 axe cortex tools
(2) 그 스키마대로 호출한다 axe cortex call <tool> --args '<json>'
주요 도구:
search_person 이름·회사·메모 등 아무 필드나 substring 으로 인물 검색
get_person 한 인물의 상세 + 근거 + 최근 이력
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 에 연동해줘”.
4. (선택) 터미널에서 직접
axe cortex tools # 도구 목록 (스키마 포함)
axe cortex call search_person --args '{"query":"홍길동"}'
axe cortex call register_person --args '{"display_name":"김상무","org":"ABC캐피탈","memo":"2026 Q2 소개"}'
axe cortex call connect_google --args '{}' # → 반환된 authorize_url 을 브라우저로 열기② 읽기 전용 웹 포털 — 눈으로 둘러보기
추가·수정 없이 보기만 할 거면 브라우저로 https://cortex.axelabs.ai → Microsoft 로그인.
| 화면 | 내용 |
|---|---|
| 대시보드 | 네트워크 깔때기 통계 |
| 인물 · 조직 · 딜 | 목록과 상세 |
| 상호작용 | 미팅 · 통화 · 이벤트 이력 |
| 관계 | 사람·조직·딜 사이의 연결 |
| AXE Contacts | 엑셀형 인맥 마스터 뷰 — 이름 · 이메일 · 전화 · 별점 · Needs/Offers · 레이블 · 대화 노트, 필터와 복사, 레이블 드래그 지정 |
본인 데이터만 보이고 읽기 전용이다. 등록·수정은 ①(CLI/에이전트)로 한다. 예외적으로 별점 · 대화 노트 · 레이블 세 가지 가벼운 보강은 웹에서 바로 쓴다.
우측 패널의 협업 세션
우측 컨텍스트 패널의 “세션” 섹션에 Blueprint 협업 세션 중 service=cortex 로 분류된 세션이 컴팩트 한 줄 목록(상태 도트: 진행 = 채운 초록, 완료 = 빈 원)으로 뜬다 (D-bp-collab-3, @axe/ui 0.38.0 레일 v2). 머리행 [+] 는 그 자리에서 새 세션을 만들고(원장은 Blueprint, 귀속은 로그인한 본인), 프리셋 아이콘은 /collab/sessions 목록 페이지로 간다. 행을 누르면 Cortex 안의 세션 상세(/collab/sessions/:id)에서 프롬프트와 게재물을 읽는다 — Blueprint 로 이탈하지 않는다. Cortex 에는 법인 컨텍스트가 없어 생성 시 법인을 붙이지 않는다(서비스 귀속만으로 목록을 얻는다).
이 섹션은 연결되어 있을 때만 나타난다. 연동 설정이 없거나 조회가 실패하면 빈 목록 대신 섹션이 통째로 사라진다 — 비어 보이는 목록은 “세션이 없다”로 읽히기 때문이다. 연동에 필요한 설정은 배포 환경 변수 두 개(CORTEX_BLUEPRINT_INTERNAL_URL · CORTEX_BLUEPRINT_INTERNAL_API_KEY)뿐이고, 인증 키는 Blueprint 와 같은 것을 쓴다(새 비밀 없음).
Google 연락처 연동 (1회)
Cortex 는 본인 Google 연락처를 원본으로 삼아 단방향으로 가져온다.
- 에이전트에 “내 구글 연락처 연동해줘” (또는
axe cortex call connect_google --args '{}') - 반환된
authorize_url을 브라우저로 열어 Google 동의 - ”✓ 연결 완료” 후 다음 동기화 주기(또는
sync_google_now)부터 자동 유입
민감 라벨(기본값 Personal · HPE)이 붙은 연락처는 애초에 들어오지 않는다. 허용 라벨을 지정하면 그 라벨이 붙은 연락처만 통과시킬 수도 있다. 라벨 목록을 읽지 못하면 그 주기를 통째로 건너뛴다 — 필터가 불확실한 채로 유입시키지 않는다.
데이터가 쌓이는 방식
Cortex 의 모든 기록은 artifact 하나의 형태를 공유한다. 사람 · 조직 · 딜 · 상호작용 · 관계 · 메모 · 분류가 전부 같은 구조의 typed fact 이고, 내용은 자유 형식이라 미리 정의하지 않은 필드도 그대로 저장된다.
artifact 사람 · 조직 · 딜 · 상호작용 · 관계 · 메모 · 속성
└─ 내용 약속된 필드 + 임의의 추가 필드
└─ visibility shared | private
citation 근거 — 구글 연락처 · 메일 · 메시지 · 외부 링크
event 누가 · 언제 · 무엇을 했는지 (추가만 가능, 수정·삭제 불가)관계도 artifact 의 한 종류다. introduced_by(누가 소개했나) · works_at · attended(미팅 참석) · lp_invested_fund 같은 연결 종류를 붙여 두 기록을 잇는다. 미팅을 기록하면 참석자마다 관계가 자동으로 생긴다.
기록을 지우는 대신 보관(archive) 한다. 목록에서는 사라지지만 이력은 남고 언제든 되돌릴 수 있다. 사람은 network(내 인맥)와 pool(원본 연락처) 두 층으로 나뉘며, 기본 목록은 인맥만 보여준다.
공개 범위 — shared 와 private
모든 기록은 이미 본인에게만 보이므로, visibility 는 나와 남을 가르는 장치가 아니라 데이터가 내 영역 밖으로 나가는 것을 막는 게이트다.
| 등급 | 본인 | 밖으로 (미러 · 내보내기 · 대시보드) | Google 로 밀어내기 |
|---|---|---|---|
shared (기본) | 보임 | 보임 | 신규 인물 등록 시 연락처 생성 |
private | 보임 | 절대 안 나감 | 하지 않음 |
기본값은 shared 다. 딜 코멘트 · 협상 의견 · 사적 메모 · 비공개 미팅처럼 민감할 수 있는 내용은, 쓰기 도구의 설명에 심어 둔 규칙에 따라 에이전트가 저장 전에 “private 으로 할까요?”를 먼저 묻는다. 이름 · 소속 · 전화 같은 일반 정보는 묻지 않고 shared 로 간다. 딜 등록은 반대로 기본값이 private 이다.
“인물은 공유하되 메모만 비공개”로 하려면, 인물은 shared 로 등록하고 민감한 내용은 별도의 분류 항목으로 private 지정한다.
기록 내용을 이력에 남기고 싶지 않을 때는 skip_audit_payload 를 준다. 그러면 그 기록에 표식이 붙어, 이후의 동기화·보관·수정도 내용 스냅샷 없이 “누가·언제·무엇을 했다”만 남긴다. 대신 그 기록은 이력에서 되살릴 수 없다 — 비밀을 택하면 복구 경로를 포기하는 교환이다. 표식은 언제든 해제할 수 있다.
MCP 도구 (30)
https://cortex.axelabs.ai/mcp — JSON-RPC 2.0, Bearer 인증 필수.
| 도구 | 동작 | 비고 |
|---|---|---|
whoami | 인증된 사용자 확인 | 헬스 체크 |
list_persons | 사람 목록 (본인 것만) | tier(network|pool|all, 기본 network) · visibility · q 자유검색 · labels AND 교집합 · order · include(계정 케이스 | 최근 접점) · 분류 제외 필터 |
search_person | 내용 전체를 substring 으로 검색 | 미리 정의되지 않은 필드도 매칭 |
get_person | 한 인물의 상세 + 근거 + 최근 이력 20건 | 인물이 아닌 id 는 명시적 에러. 보관된 것도 id 로 직접 조회 가능 |
register_person | 신규 인물 등록 | Google 연동 사용자는 연락처도 함께 생성. visibility=private 이면 Google 로 밀지 않음 |
update_person | 인물 내용 부분 수정 + 변경 전후 이력 | Google 로 밀지 않음(단방향). 공개 범위 재지정도 여기서 |
demote_to_pool | 인맥 → 원본 연락처로 강등 | 등급을 되돌리는 유일한 경로. 삭제가 아니라 재분류 |
classify_person | 인물에 분류 태그 부착 | 키 자유 형식. (대상, 키) 멱등. 민감 분류는 private |
log_interaction | 미팅 · 통화 · 이벤트 기록 | 참석자마다 관계 자동 생성, 상호작용의 공개 범위를 상속 |
list_interactions | 상호작용 목록 + 인물 필터 | 참석자는 내용과 관계 양쪽에서 해소 |
register_organization | 조직 등록 | 멱등 — 같은 이름이면 기존 것을 돌려준다. 별칭 배열 지원 |
update_organization | 조직 내용 부분 수정 | 등록이 기존 내용을 덮지 않으므로 정정은 이 도구로. 공개 범위는 생략 시 불변 |
list_organizations | 조직 목록 | 이름·별칭 검색, 정렬, 소속 인원 수 · 딜 수 · 계정 케이스 포함 옵션 |
get_organization | 조직 상세 + 최근 이력 20건 | 보관된 것도 반환 |
register_deal | 딜 등록 | 기본 private. 단계(발굴|스크리닝|실사|심의|진행|종결|중단), 대상 조직, 담당자 연결 |
list_deals | 딜 목록 | 단계 · 대상 조직 · 공개 범위 필터 |
get_deal | 딜 상세 + 최근 이력 20건 | 보관된 것도 반환 |
register_relationship | 두 기록을 잇는 관계 생성 | 멱등 — 같은 (출발, 도착, 종류). 소개 · 재직 · 참석 · 자문 등 |
list_relationships | 관계 목록 | 인물 · 관계 종류 · 공개 범위 필터 |
tag_account | 사람·조직을 특정 건의 거래 계정으로 태깅 | (대상, 건) 멱등. 유형 권장 어휘: investor | client | advisor | partner | lp | vendor | portfolio | acquirer | other. 사람은 인맥으로 자동 승격 |
untag_account | 계정 태그 제거 | 멱등. 등급 강등은 하지 않음 |
list_account_cases | 특정 건에 태깅된 사람·조직 조회 | 건 라벨 substring(접두 조회 가능), 대상 종류 필터 |
list_org_directory | 조직 전체 인맥 목록 | 노출 항목은 이름 · 조직 · 직함 · 라벨 · 공개 범위 · 담당자까지 — 연락처와 내용은 나오지 않는다 |
list_org_accounts | 조직 전체 계정 케이스 보유자 | 동일한 축소 투영 |
network_stats | 네트워크 깔때기 통계 | scope personal(기본) | orgwide(익명 합계) |
connect_google | Google 연동 시작 — authorize_url 반환 | 브라우저에서 동의 |
disconnect_google | 연동 해제 | 멱등 |
sync_google_now | 다음 주기를 기다리지 않고 즉시 1회 동기화 | 추가·수정·라벨로 제외된 건수 반환 |
update_contact | Cortex 의 식별 필드를 Google 연락처로 되밀기 | 연결된 연락처 근거가 있어야 함. private 은 거부. 메모·분류는 의도적으로 밀지 않음 |
archive_artifact | 기록 보관 / 복원 | 삭제 대신. 멱등, 이력 보존 |
단일 조회 3종(get_person · get_organization · get_deal)의 recent_events 는 같은 모양이다 — {id, op, actor, payload_after, visibility_before, visibility_after, occurred_at} 를 최신 20건. 이력 내용을 억제한 기록은 payload_after 가 null 로 오는데, 이는 누락이 아니라 억제다. 공개 범위 필드는 그 변경이 없었으면 null 이며, 게이트 상태라 억제 대상이 아니다.
도구 스키마의 인자 이름은 전부 ASCII 다. 한글 키가 하나라도 들어가면 일부 LLM API 가 도구 목록 전체를 거부한다. 값은 한글 자유.
인증
Cortex 는 세 갈래 인증을 받는다. 어느 쪽이든 본인 확인 후 본인 데이터에만 접근한다는 점은 같다.
| 경로 | 언제 |
|---|---|
| AXE 플랫폼 SSO (기본) | axe login · axe cortex — Microsoft 로그인 1회로 받은 플랫폼 토큰으로 전 서비스를 호출한다. 별도 앱 등록이나 커넥터 동의가 필요 없다 |
| Microsoft Entra ID 직접 | claude.ai 커넥터, 개발용 CLI 토큰 |
| 웹 세션 쿠키 | 읽기 전용 포털 — 8시간 슬라이딩 |
토큰 검증은 서명(RS256) · 발급자 · 대상 · 만료를 모두 확인한다. 인증에 실패하면 RFC 9728 형식으로 어디서 인증을 받아야 하는지 응답에 담아 돌려준다.
테넌트 축 (2026-09-05, 2~5단계 라이브 · D-ops-115): 한 cortex 가 여러 테넌트를 담는다. 요청마다 어느 테넌트인지를 먼저 정하고, 그 뒤의 모든 읽기·쓰기는 그 테넌트 안에서만 성립한다.
DB 가 보증하는 것과 코드가 지는 몫은 다르다. 세션 하나가 세운 테넌트 표식은 그 세션의 모든 질의에 행 수준 보안 정책으로 걸린다 — 즉 한 세션 = 한 테넌트이고, 질의문에 조건을 빠뜨려도 그 세션이 두 테넌트를 함께 읽는 일은 없다. 다만 어느 테넌트인지를 고르는 것은 코드다(표식을 세우는 자리가 코드다). 그래서 남는 위험은 ‘조건 누락’ 이 아니라 ‘엉뚱한 테넌트로 세션을 여는 것’ 이고, 그쪽은 표식을 세우는 자리를 한 곳(db.rs)으로 모으고 대리 쓰기·대리 읽기가 매번 DB 에 두 사람의 소속을 되묻게 해서 막는다.
| 들어온 경로 | 테넌트를 무엇으로 정하나 |
|---|---|
AXE 플랫폼 SSO (axe login 토큰 · 웹의 Blueprint 로그인) | 토큰의 tenant_id 클레임이 필수다. 이미 등록된 사람은 그 클레임과 등록된 테넌트가 같아야 통과하고, 다르면 “다른 테넌트에 등록된 계정” 으로 거절한다. 등록이 없으면 그 클레임이 가리키는 테넌트로 첫 요청에서 편입된다 |
| Microsoft Entra ID 직접 · 웹의 Microsoft 로그인 | 이 토큰들은 AXE 테넌트에 핀돼 있어 테넌트를 말해 주지 않는다. 등록이 있으면 그 등록이 정답이고, 없으면 AXE 가 선언한 도메인의 주소일 때만 편입한다 — 그 밖(게스트·외부 초대 계정)은 거절하고 플랫폼 로그인으로 안내한다 |
자동 편입은 초대가 아니다. 편입은 토큰이 이미 증명한 테넌트에 그 사람을 매다는 것뿐이고, 테넌트 자체를 만드는 것은 요청 경로에 없다 — 운영자 명령뿐이다.
도메인 선언은 옵트인 하드닝이다. 테넌트가 --domain 으로 도메인을 선언했으면 그 목록 밖의 주소는 두 경로 모두 자동 편입되지 않는다. 선언이 하나도 없으면 플랫폼 SSO 경로는 종전대로 토큰의 클레임을 믿는다 — 그 경로는 클레임이라는 근거가 따로 있기 때문이고, 그래서 기존 테넌트의 동작이 이번 판에서 바뀌지 않는다. Microsoft 직접 경로는 도메인 말고 근거가 없으므로 선언이 비면 아무도 자동 편입되지 않는다. 고객사 계정에는 그 회사의 게스트·외주 주소가 섞여 있고 편입 즉시 조직 목록이 열리므로, 새 테넌트를 만들 때 --domain 을 채우는 것을 기본으로 삼는다.
잘못 편입됐으면 되돌린다: cortex tenant remove-owner --tenant <슬러그> --email <주소> 가 소속을 닫는다(행은 이력으로 남는다). 닫아야 그 계정을 다른 테넌트로 편입할 수 있다 — 한 사람은 활성 테넌트 하나뿐이라는 제약을 DB 가 강제하기 때문이다. 같은 테넌트로 되돌리는 것은 add-owner 가 한다.
테넌트 등록은 명령 두 줄이다. cortex tenant add --slug <슬러그> --display-name <표시명> --domain <도메인> 으로 테넌트를 만들고, 필요하면 cortex tenant add-owner --tenant <슬러그> --email <주소> 로 사람을 미리 매단다(둘 다 멱등). 앞 명령은 스키마 검사를 선행 가드로 걸어 마이그레이션이 아직 안 돈 DB 에는 두 번째 테넌트를 만들지 않는다 — 그 상태로 만들면 새 테넌트의 첫 행이 옛 기본값(AXE)으로 찍히고, 그 순간 그 행의 귀속은 되돌릴 수 없다.
조직 전역 화면도 이제 테넌트 안이다. 전사 통계 · 조직 인맥 목록 · 조직 계정 목록 · LP 보드 넷은 종전에 보안 정책을 우회해서 읽었는데, 이제 “이 테넌트 전체” 를 뜻하는 표식을 세운 뒤 정책을 통과해서 읽는다. 우회 경로 자체가 사라졌다는 뜻이다. 그 표식은 읽기 정책에만 달려 있어 그 세션은 테넌트 전체를 읽기만 한다 — 읽기와 쓰기를 한 정책으로 묶으면 넓힌 읽기 범위가 삭제 범위까지 같이 넓힌다.
아직 인스턴스 전역인 것: LP 모듈 담당자 · 교차 소유자 쓰기 허용목록 · Google 동기화의 라벨 필터 셋은 프로세스 전역 설정으로 남아 있다. 앞의 둘은 테넌트 경계가 한 겹 더 막는다 — LP 보드는 담당자가 요청자와 같은 테넌트일 때만 켜지고, 교차 소유자 쓰기는 두 사람이 같은 테넌트일 때만 성립한다. 라벨 필터만 전 테넌트 공통이라, 두 테넌트가 같은 라벨명을 서로 다른 뜻으로 쓰면 동기화 범위가 어긋난다(경계가 새는 것이 아니라 범위가 틀리는 것이다).
배포와 롤백: 스키마는 앞으로만 간다 — 되돌리는 서브커맨드가 없다. 그래서 코드 롤백은 직전 한 판까지만 안전하다(이 스키마 위에 이번 판 이전 코드를 올리지 않는다). 그리고 배포 순서상 새 코드가 잠깐 옛 스키마 위에서 뜨므로, 부팅의 스키마 검사는 실패해도 멈추지 않고 경고만 남기고, cortex migrate 안의 같은 검사는 실패로 배포를 세운다 — 세울 자리에서만 세운다.
그 창(새 코드 + 옛 스키마)에서 조직 전역 화면 넷은 오류가 아니라 빈 목록이 된다. 새 코드는 “이 테넌트 전체” 표식으로 읽는데 옛 정책은 그 표식을 모르기 때문이고, 마이그레이션이 실패하면 그 상태가 고착된다. 화면의 “데이터 없음” 이 데이터 없음이 아닐 수 있다는 뜻이라, 부팅 검사가 옛 스키마를 보면 그 뒤 조직 전역 조회마다 경고를 로그에 남긴다. 같은 창에서 도메인 선언 표도 아직 없으므로, Microsoft 직접 경로의 미등록 계정은 “일시적 오류” 가 아니라 “미등록 계정” 으로 거절된다 — 구조적 거절을 일시적 실패로 위장하지 않는다.
claude.ai 커넥터로 붙일 때
- Connector URL 에 MCP 주소를 넣는다.
- claude.ai 가 401 응답의 안내를 따라 인증 서버 메타데이터를 조회한다.
- 인증 서버는 Microsoft 이고, claude.ai 는 confidential client 로 동작하므로 client id 와 client secret 입력을 요구한다. 두 값은 조직 관리자가 발급해 전달한다.
- Microsoft 동의 후 발급된 토큰으로 MCP 를 호출한다.
이 경로는 테넌트 관리자의 사전 동의가 선행되어야 한다. 동의가 없으면 Microsoft 로그인 자체는 성공하는데 콜백에서 실패하고, Cortex 에는 요청이 한 건도 도달하지 않는다. 동의를 받은 뒤에는 커넥터를 한 번 끊었다 다시 연결해야 새 인증이 발급된다.
Google 연동
Cortex 가 요청하는 범위는 셋이다 — 연락처(contacts, 읽기는 동기화에·쓰기는 신규 인물 등록 시 연락처 생성에 쓴다), 그리고 연동된 계정이 어느 Google 계정인지 하나를 확인하기 위한 openid + userinfo.email.
계정 이메일은 Google 의 OIDC userinfo 엔드포인트에서 한 번 읽는다. 연락처 API 를 쓰지 않으므로 연락처 호출 한도와 무관하다. 읽는 시점은 연결 직후의 동기화와 그 뒤의 정기 주기이고, 알아낸 값은 그 답을 받아온 그 연결(grant)이 아직 살아 있고 아직 비어 있을 때만 적힌다. 연결은 승인 시각과 연결마다 새로 찍는 고유 식별자 두 조각으로 구분하므로, 같은 초에 두 번 다시 연결해도 서로 다른 연결로 남는다 — 어느 쪽이 현재인지는 시계가 아니라 저장된 기록이 정한다. 캡처가 도는 사이에 재연결이 끼면 그 쓰기는 아무 행도 건드리지 않고 끝나고(옛 계정 이메일이 새 연결에 적히는 일이 없다) 다음 주기가 새 연결로 다시 읽는다.
읽기에 실패했을 때의 처리는 두 갈래뿐이고, 그 구분이 재시도 정책 전부다.
| 갈래 | 어떤 답이 여기로 오나 | 그다음 |
|---|---|---|
| 영구 — 다시 연결하기 전까지 끝 | ① 권한 범위(동의 범위)가 모자란다고 스스로 지목한 응답. ② 진짜 userinfo 응답인데 계정 이메일 항목이 통째로 빠진 200 — OIDC 는 내주는 항목이 동의 범위를 따르므로, 이 범위가 없는 연결은 거절이 아니라 “빈자리” 로 답한다(계정에 이메일이 없는 경우도 같다) | 경고 한 줄을 한 번만 남기고 그 연결로는 더 시도하지 않는다. 재동의 전까지 답이 안 바뀌기 때문이다 — connect_google 로 다시 연결해야 채워진다 |
| 일시 — 그 밖 전부 | 네트워크·타임아웃, 5xx, 범위를 지목하지 않은 401/403(만료된 토큰 · 시계 어긋남 · 호출 한도), 그리고 userinfo 응답으로 읽을 수조차 없는 2xx 본문(중간 경로가 끼워 넣은 오류 안내 화면 · 잘린 응답) | 연결마다 따로 잡은 예산 안에서 다시 시도하고, 예산을 다 쓰면 30분 쉬었다가(실패가 이어지면 2배씩, 최대 하루) 같은 연결로 다시 읽는다. 포기가 아니다 |
일시 갈래의 재시도는 정기 동기화가 돌 때마다 한 번씩이라(기본 10분 간격) 몇 번의 시도가 한꺼번에 몰리지 않는다. 동기화를 수동으로 부르거나 다시 연결해 곧바로 한 번 돌리는 경로도 같은 예산을 함께 쓰므로, 그렇게 연달아 치면 예산이 그만큼 빨리 소진된다. 한 연결에 대해 캡처는 한 번에 하나만 돈다 — 정기 주기 · 수동 동기화 · 재연결 직후의 즉시 동기화가 겹쳐도 Google 을 겹쳐 부르지 않는다. 값을 알아냈는데 기록이 실패했다면 그것은 성공이 아니다: 예산이 되돌아가지 않고 일시 실패로 처리돼 다음 기회에 다시 시도한다.
이미 연결해 둔 사용자는 이 값이 비어 있다. 옛 동의에 이 범위가 없어서 그런 것이고 오류가 아니다 — 다만 저절로 채워지지도 않는다. 범위가 바뀌면 재동의가 필요하므로 connect_google 로 한 번 다시 연결해야 한다. 그리고 그 동의 화면이 이 범위를 내주려면 관리자가 Google Cloud Console 의 동의 화면(Data Access) 설정에 openid 와 userinfo.email 을 미리 등록해 두어야 한다.
연동 계정이 계속 비어 있다면 셋 중 하나다.
- 옛 연결이라 이 범위가 없다 — 위 표의 영구 갈래다. 기다려도 낫지 않고 재시도도 하지 않는다.
connect_google로 다시 연결(재동의)하는 것이 유일한 답이다. - 관리자가 동의 화면에 범위를 등록하지 않았다 — 이 상태로 다시 연결하면 새 연결에도 범위가 없어 1번으로 되돌아온다. 등록이 먼저고, 등록한 뒤 한 번 더 재연결해야 한다.
- 일시적인 통신·서버 사정 — 이건 시간이 답이다. 쉬었다 다시 시도하는 과정을 필요한 만큼 되풀이하므로, 일시적인 오류 때문에 이 값이 영영 비어 있게 되지는 않는다.
다른 계정으로 다시 연결하면 이 값은 비워지고 새 계정 기준으로 다시 읽는다 — 옛 계정 이메일이 새 연결에 남지 않는다. 연동을 해제했거나 연결이 만료된 뒤에 뒤늦게 도착한 답도 마찬가지로 기록하지 않는다 — 이미 끊긴 연동에 계정 이메일이 적혀 남는 일이 없다.
재발급 토큰은 암호화해 사용자별로 저장하며, 연동 해제 시 Google 쪽에서도 취소한다.
스키마와 배포
이번 판에 스키마가 두 자리 늘었다. 둘 다 기존 데이터를 옮기거나 지우지 않는다.
| 마이그레이션 | 무엇 | 왜 |
|---|---|---|
| 0010 | 기록 표의 계정 케이스 조회에 붙는 표현식 확장통계 (인덱스가 아니다) | 느렸던 원인이 스캔 경로가 아니라 행수 추정 붕괴였다. 플래너가 바깥쪽을 1행으로 보고 안쪽 표를 실제 행수만큼 되훑었고, 인덱스를 붙여 봐야 이 경로(행 수준 보안 아래)에서는 조건으로 승격되지 못해 무효였다. 통계를 붙여 추정이 실제와 맞자 같은 조회의 실행 시간이 자릿수 단위로 줄었다 |
| 0011 | 연동 토큰 행의 연결 고유 식별자 한 칸 | 연결의 정체를 승인 시각만으로 가르면 같은 시각을 받은 두 재연결이 구분되지 않는다. 그러면 앞 연결의 포기 표시가 새 연결을 잠그거나, 옛 계정 이메일이 새 연결의 기록 조건을 통과한다 |
마이그레이션은 컨테이너가 뜰 때 자동으로 돌지 않는다. 배포가 새 이미지를 올린 직후, 헬스 판정 이전에 그 이미지의 cortex migrate 를 한 번 실행한다 — 새 코드가 옛 스키마 위에서 뜨는 창을 없애기 위해서다. 저장소 README 와 운영자 설치 문서에 나오는 cortex migrate 는 로컬·개발 경로이고, 배포에서 사람이 손으로 다시 칠 일이 아니다.
돌리는 중에 잃는 것은 없다. 0011 은 이미 있던 행마다 새 식별자를 하나씩 채우므로, 마이그레이션 순간에 돌고 있던 계정 이메일 캡처는 조건이 안 맞아 아무 행도 안 건드리고 끝나고 다음 주기가 새 기준으로 다시 읽는다 — 값이 한 주기 늦게 채워질 뿐 틀린 값이 남지 않는다. 새 환경 변수도, 새 비밀도 없다.
관련 서비스
- Blueprint — Cortex 의 기록을 본인 범위 그대로 미러해 업무 대시보드와 메모 인용에 활용한다.
- Frame — 딜 기록이 회계·법인 도메인의 식별자를 참조 필드로 들고 있다.