아키텍처 결정 누적 기록
다음 두 SSOT 파일에 결정이 누적되어 있고, 본 페이지는 그 요약입니다. 변경 발생 시 그쪽 파일이 우선이고 본 페이지는 후행 정리:
- /Users/axe/multi-tenant-platform-plan.md — 마스터 플랫폼 plan + D 결정 + 함정
- /Users/axe/frame/DECISIONS.md — frame 측 D-ops-1 ~ 15
D — 플랫폼 결정
| ID | 결정 | 채택일 |
|---|---|---|
| D1 | 격리 = customer 1개 / macmini 1대 (OS-level, SPOF 수용) | 2026-05 |
| D2 | 테넌트 ID = 이메일 도메인 (customers.yaml SSOT) | 2026-05 |
| D3 | path-based 1-level subdomain ({customer}.axelabs.ai/{service}) | 2026-05 |
| D4 | Tailscale 메시 + SSH key (자체 mTLS 안 만듦) | 2026-05 |
| D5 | HTTP MCP + 부트스트랩 JWT (OAuth 1차 deferral) | 2026-05 |
| D6 | push-based CI/CD (pull polling 안 함) | 2026-05 |
| D7 | P2P ring restic backup (mesh 안 만듦) | 2026-05 |
| D8 | 3-tier secret (vault + keychain + env) | 2026-05 |
| D9 | schema-per-entity (고객 내 법인 격리, RLS 대신) | 2026-04 |
| D10 | index / vault 동일 패턴 (Phase 6 deferral) | 2026-05 |
| D-config-13 | 다운타임 0 frame deploy (blue/green + host proxy, cloudflared 미관) | 2026-05-15 |
| D-config-14 | DR cold storage (external SSD rotation, 종이 password) | 2026-05-15 |
| D-config-15 | blueprint multi-customer (env override per macmini) | 2026-05 |
| D-config-16 | blueprint = Postgres (SQLite deprecate 예정) | 2026-05 |
| D-config-17 | Postgres cutover 무중단 (운영자 명시 승인) | 2026-05 |
D-ops — frame 운영 결정
| ID | 결정 | 채택일 |
|---|---|---|
| D-gate-5 | e-sign 직접구현 = 암호등급 자체 서명엔진 (D-gate-3 모두싸인 integrator 입장 supersede) (2026-06-07 — 빌드+검증 완료, 운영자 pivot) — D-gate-3 은 e-sign 코어를 모두싸인이 흡수(integrator)하고 직접-build 서명엔진을 Phase-2 로 defer 했으나, 운영자가 “모두싸인의 기능을 직접, 암호등급으로 구현” 지시 → gate Phase-1 = gate-native 직접-build 암호 서명엔진(작동 self-hosted, 이 세션 빌드+검증). 무엇이 빌트됐나: ① CMS/PKCS#7 CAdES-BES detached 서명(RSA-2048+SHA-256), DocumentSigner trait(HSM-ready) + SoftwareSigner(self-signed X.509) — 적대적 리뷰(위조 도달경로 없음) + openssl 양방향 호환, 7 tests. ② RFC3161 신뢰 타임스탬프 → CAdES-T(hand-rolled ASN.1, 단일 clean dep; default DigiCert RSA TSA, 한국 prod = Koscom/CrossCert config-swap), 9 tests + live round-trip. ⑤ 워크플로 배선: 시행(execute) 시 esign.gate actuator 가 승인된 결정을 server-seal(canonical decision bytes → CAdES-T), append-only esign_seal evidence 테이블(전자문서법 §5 custodian) 저장. 신규 verify_seal MCP tool(서명+무결성+타임스탬프 검증). e2e: 결재→seal→verify valid, 저장 CMS openssl cross-verify 통과, DB tamper→integrity fail. 법적 근거(전자서명법 2020, 공인폐지): 사서명(private/internal e-sign)은 실지명의 + 서명의사면 유효 — gate 의 경우 실지명의 = 인증된 gate identity(내부 서명자), 서명의사 = 결재 승인 행위 → 운영 라이선스 불요·합법. 유지된 경계(여전히 Phase-2/하드닝): 외부 서명자 본인확인 = 방통위 지정 본인확인기관 통합(정보통신망법 §23-3, 자가구축 불가) = 진짜 Phase-2 · HSM(cryptoki/PKCS#11, abstraction ready) · PAdES 서명-PDF 컨테이너(③④ typst deterministic render + pyHanko) · production 키관리(dev = ephemeral key) = 하드닝. red-team P0 해소-또는-명시: 직접-build 를 막았던 P0(HSM·HTML→PDF 결정성·custody)가 — 결정성은 canonical bytes 로 now(typst PDF 는 later), HSM 은 abstraction ready(trait), custody 는 esign_seal + RFC3161 TSA 로 해소-또는-명시. MUST NOT(불변): 자체 CA·per-user cert·자체 TSA·“공인/인증” 주장 — gate 는 단일 서버 서명 identity(ModuSign-recipe 모델: per-user cert 없이 서버가 봉인). 스택: RustCrypto cms/der/rsa + RFC3161 + DigiCert TSA. 갱신: /architecture/governance §6 재작성 + para-os §84 footnote + D-gate-1 Phase-1 e-sign 행. 정합: D-gate-1(scope) · supersede D-gate-3(e-sign 경계). | 2026-06-07 |
| D-gate-1 | gate = 한국형 결재+e-sign+법무 거버넌스 제품 — narrowed Phase-1 확정(경로 A), 야심 5종 Phase-2 defer (2026-06-06 결정 → 코드 착수 직전; 마스터플랜 B-bp-decision-pipeline-esign, 상세 /architecture/governance) — gate = 신규 Rust+Postgres 서비스(포트 41xx: pg4100/blue4110/green4111/proxy4112, origin axe.axelabs.ai/gate), 한국형 내부 전자결재 + e-sign + HTML 문서 + 법무검색을 AI-driven 결정→실행 파이프라인으로. 동기: 7라운드 설계 후 7-agent red-team 이 풀설계(결재+직접 e-sign+법무 RAG flywheel+공개 SaaS+결제+멀티테넌트 = 1인이 동시에 짓는 6gate.decision citation mirror. AXE-internal only, pgvector-KR(Pinecone 아님), 법무=내부검색만(결재 자동주입 X, D-gate-4). 결제·멀티테넌트 없음. Phase-2+(각자 규제/substrate 게이트, defer): 외부 서명자 본인확인기관 통합(정보통신망법 §23-3, B-gate-bonin-id-contract) — 직접 e-sign 엔진 자체는 D-gate-5 로 Phase-1 빌트; 잔여 하드닝 = HSM·PAdES-PDF·prod-key · Pinecone 법무 flywheel(+PIPA) · 공개 SaaS(+tenant_id RLS substrate·self-IdP) · 결제(포트원/토스/Paddle, +전자금융/전자상거래) · cross-tenant 축적(+PIPA 가명정보). 빌드게이트 = B-gate-legal-counsel + B-gate-phase2-gates. 착수 순서: docs-first(D-gate-1cp -R cortex gate scaffold(auth 3-branch ladder·RLS·blue/green 상속) → 7테이블 migration(+esign_seal) → 상태기계 → esign.gate actuator(gate-native CAdES-T server-seal + verify_seal, D-gate-5 빌트) + 모두싸인 alt-provider stub(optional). 기준 = para-os §7 refine, D-bp-para-1 governance clause 확장. | 2026-06-06 |
| D-gate-2 | 결정 record SoR = gate decision 테이블, Blueprint = gate.decision citation mirror — D-bp-para-1 governance clause refine (2026-06-06) — D-bp-para-1/para-os §7 은 결정로그를 “Blueprint core primitive(가로)” 로 뒀으나 = AXE-internal 전제. standalone 외부제품 요구 + 3축 survey(SoR경계·auth·멀티테넌트) 가 refine: record SoR = gate decision 테이블(완료 workflow + 서명 HTML문서 + cert·authority·actor·decided_at·basis citations·supersedes, append-only). Blueprint = mirror: citation kind gate.decision(artifact·Area 가 cite) + 후일 visibility-gated mirror 테이블 — optional consumer 1종, 외부고객 = OFF(gate 단독 완결). 근거(grounded): /architecture/artifacts “MCP backend = sole owner, data 중복 0” · /architecture/data cross-service = 직접 DB read 금지·명시적 신호채널(pg_notify/outbox)만 · D-cortex-9 = service-owns-SoR + Blueprint visibility-gated mirror 이미 빌트(template 기성) · para-os §7 e-sign cert 를 citation 으로 = pre-authorize. → round-1 의 “record→Blueprint core” 가 오히려 platform 패턴(service-owns-SoR) 이탈, standalone 요구가 아키텍처가 가리키던 곳을 드러냄. 상세 = /architecture/governance. | 2026-06-06 |
| D-gate-3 | actuation ledger(결정→실행 멱등 일관성) + e-sign = 모두싸인 integrator(gate = 전자문서법 §5 evidence custodian); 직접-build 서명엔진 = Phase-2 gate (2026-06-06) — ⚠️ e-sign 부분은 D-gate-5(2026-06-07) 가 supersede — 직접-build 암호 서명엔진이 Phase-1 으로 당겨져 빌트됨(아래 e-sign 경계는 역사 기록). actuation ledger 부분은 유효. 승인된 결정 → actuator 호출(frame.post_journal/index.register_deal 등, para-os §7 “Area 서비스 = actuator”). actuation 테이블 = 실행 ledger(actuator_kind·payload·idempotency_key=uuidv5(workflow+seq+actuator+payload_hash)·state pending/inflight/done/failed·result_ref·reconciled_at) + reconcile 워커(frame-worker 패턴, pg_notify). e-sign 경계(Phase-1 = INTEGRATE, para-os §84 와 정합): signature.provider=modusign — 본인확인·타임스탬프·증거능력 코어를 모두싸인이 흡수, gate 는 전자문서법 §5 evidence custodian(서명문서+메타[작성자·수신자·송수신/서명 일시] 무결성 보관 + consent 기록, immutable audit store). cert ID 만 citation 참조(복제0). ⚠️ 직접-build 서명엔진(자체 PKI 서버서명·PAdES/XAdES·HTML→PDF 결정성[sealed-bytes=원본]·해시봉인·공개검증포털·감사추적인증서·HSM/KMS·서명키 DR/escrow·대법원 2017도13263 custody·본인확인기관 직통합) = Phase-2 gate(red-team P0 #3/#7/#10) — 정당화였던 KISA §8 인정조차 “MVP 불요”. MUST NOT(Phase 무관): 자체 CA·per-user cert·자체 TSA·“공인/인증” 주장. 멱등 보강: 모두싸인 actuator 도 provider-side idempotency 토큰 필요(gate idempotency_key 가 외부 이중실행 못막음, red-team P0 #5); saga 보상(e-sign+결제+분개) = 결제 도입 Phase-2. 상세 = /architecture/governance. | 2026-06-06 |
| D-gate-4 | 법무 모듈 가드레일 = 변호사법 §109/§34 검색·정보 한정(자문 금지); Phase-1 = pgvector-KR 내부검색만, flywheel = Phase-2 (2026-06-06) — gate 법무모듈 = 검색·요약·번역·템플릿(로폼: user-blank, 기계적)만. 하드룰 변호사법 §109(형사)·§34(5): 법률상담/사안별 자문/대리/결과예측-as-advice 금지, 공개 “AI 법률상담” 금지(AI대륙아주 7개월 폐쇄·징계 선례), 변호사 노출 시 정액광고만(로톡 모델, 알선료/성공보수/광고수익배분 금지). 모든 출력 “법률 정보, 자문 아님” disclaimer + cite = retrieval-verified(존재 + 시행일/선고일 temporal 강제). 결재 자동주입 차단(AI 기안이 법령/판례를 auto-approve 경로에 주입 X — red-team P0 #2). Phase-1 data: 국가법령정보 OPEN API(open.law.go.kr, 이용제한없음·무료) + data.go.kr 판례(상용 OK) + HF korean_law_open_data_precedents(openrail), 대법원/헌재+법령 grade(하급심=상용 walled 제외). SoR = gate Postgres, index = pgvector-KR(Pinecone 아님). Phase-2 gate: Pinecone+Cohere rerank+Gemini embed flywheel(LLM비용 사용자부과·DB 복리) + cross-tenant 축적 = PIPA 처리근거·가명정보·국외이전 자문(B-gate-legal-counsel) 선결 — lbox_open/kcl=CC BY-NC=유료제품 금지. B2B/internal-use 유지 = 최저위험. 상세 = /architecture/governance. | 2026-06-06 |
| D-ops-44 | Vault 비밀 주입 = SSH 환경 raw-bw keychain-free (axe vault unlock 금지) (2026-06-09, /Users/axe/CLAUDE.md 강제 규칙으로 박제) — 이 머신 = AXE Mac mini, Claude Code 세션은 SSH(loopback) 위에서 돌고 운영자는 Windows 에서 ssh [email protected] (Tailscale) 로 접속. macOS 는 SSH 세션이 GUI login keychain 에 쓰는 것을 차단(“User interaction is not allowed”) → vault 비밀을 넣는 경로의 keychain 의존이 전부 막힘. 금지(SSH 에서 전부 실패): (1) 운영자에게 axe vault unlock 시키기 — keychain-cache 단계가 SSH 에서 crash. (2) axe secret push/pull/check 로 비밀 PUT — _vault_env 가 keychain 세션만 읽음(security find-generic-password -s axe.vault.session -a [email protected]) → SSH 에서 “vault session not found”. (3) osascript display dialog — GUI 다이얼로그라 원격/Windows 운영자에게 안 보임(운영자-타이핑 비밀은 read -rs). 정답 = keychain-free raw-bw, 운영자가 자기 인터랙티브 셸(Windows ssh axe@… 또는 Mac 터미널)에서 직접 실행. 에이전트는 ASCII-only·주석 없음·bw unlock 을 단독 한 줄로 공급(한 블록 붙여넣기는 unlock 프롬프트가 다음 줄을 비밀번호로 삼켜버림). 단계: ① export NODE_EXTRA_CA_CERTS=/Users/axe/.axe/vault/certs/rootCA.pem(Vaultwarden private CA — 필수) ② 단독 줄 export BW_SESSION="$(bw unlock --raw)"(master-pw 프롬프트; TTY-flaky 폴백 read -rs PW; export BW_SESSION="$(BW_PASSWORD="$PW" bw unlock --passwordenv BW_PASSWORD --raw)"; unset PW) ③ raw bw create/bw edit 로 Login item — name = customers.yaml 의 services.<svc>.secrets[].vault 경로(예 gate/axe/jwt-secret), username = env var 이름, password = 스크립트 안에서 생성/읽은 값($(openssl rand -hex 32)/$(cat key.pem)/상수 — echo·history·argv·agent-context 어디에도 안 남김) → bw sync → unset BW_SESSION. 검증: 에이전트는 검증 불가(세션이 운영자 셸에만 존재) → 운영자의 created/updated + synced 출력이 곧 확인, axe secret check 호출 금지(keychain 필요). keychain 세션이 필요한 곳 = deploy/ship 시점 한정(axe ship/secret pull 이 그때 keychain 읽음): SSH 에서 read -rs KCPW; security unlock-keychain -p "$KCPW" ~/Library/Keychains/login.keychain-db; unset KCPW(headless unlock, GUI 없음) → axe vault unlock → axe ship. 비밀을 넣는 작업엔 불요. 정합: D-ops-17(vault SoT) · D-ops-40(osascript hidden-dialog — GUI 세션 전제라 SSH 에선 본 raw-bw 가 대체). | 2026-06-09 |
| D-ops-43 | 운영자 원격 데스크톱 = RustDesk(Direct IP) over Cloudflare Access — Google Remote Desktop 대체, Tailscale 비의존 (2026-06-05 결정 → Phase 1 구축 중) — GRD(Google relay 경유 + Google 의존) 를 자체 트러스트 경계로 대체. 전송: RustDesk 를 Direct IP 모드(자체 relay/ID 서버 미사용)로 axe-macmini 에서 구동 + axelabs-tunnel(d8efecdd) 에 TCP ingress (rdp-axe.axelabs.ai → tcp://host.docker.internal:<port>) 추가 → ssh-axe (D-ops-39) 와 동일 Cloudflare Access 패턴. 데스크톱 = cloudflared access tcp --hostname rdp-axe.axelabs.ai --url localhost:<port> 후 RustDesk 클라이언트 127.0.0.1 직결; 모바일 = Cloudflare WARP 등록 (cloudflared access CLI 가 폰에 부재). 인증 (2-phase): Phase 1 = Microsoft Entra 직결 IdP (즉시 가동, Blueprint 코드변경 0; 복구 채널이라 플랫폼 비의존이 오히려 견고 — 순환 의존 회피). Phase 2 fast-follow = Blueprint OIDC 主 + Entra break-glass 副 (B-blueprint-oidc-third-party-rp) — 현 Blueprint OIDC (src/lib/platform-oidc.ts) 가 public·PKCE-only·exact-match 콜백 화이트리스트라 제3자 confidential RP (Cloudflare Access) 를 못 받음 → 콜백 허용 + client_secret 처리 + id_token/userinfo 완결성 확장 필요 (D-axe-idp-1 Phase 2 영역). Tailscale 배제 근거: 인터랙티브 운영자 접속의 트러스트·전송은 Cloudflare Access 단일화 (ssh-axe 동형). Tailscale (D4) 은 backend (배포 push + 백업 ring) 전용 — RustDesk+Tailscale 안 함. 비용 $0 (Cloudflare Zero Trust Free ≤50 user; Spectrum 유료 경로 미사용). macOS 26 Tahoe 함정: RustDesk 에 화면 녹화 + 손쉬운 사용 권한 1회 수동 부여 필수 (SIP 보호, CLI 불가) + 주기적 재확인 nag. 거부된 대안: (a) RustDesk+Tailscale — Tailscale 비의존 방향 역행. (b) RustDesk public relay / 자체 hbbs·hbbr — privacy + 추가 인프라. (c) Cloudflare Spectrum — 유료. (d) Blueprint 主 from start — 플랫폼 OIDC 코어(5 서비스 의존) 선확장 부담 → working RD 지연. (e) macOS 화면 공유(VNC) — Apple 코덱 성능 열위 (RustDesk 코덱 우선, 권한 마찰 수용). 본문: /architecture/topology + /ops/inventory. 정정 (2026-06-05 동일 결정 진화): {customer}.axelabs.ai/vnc 경로 요구 → RustDesk(raw TCP) 폐기, 브라우저 VNC(Apache Guacamole) 전환. path 라우팅 = HTTP 전용이라 TCP 불가 (ssh-axe flat 과 동일 D-ops-39 제약); Cloudflare 네이티브 browser-render VNC 도 hostname-bound (path 미지원, 2026-06 docs 확인) → 진짜 HTTP 앱 Guacamole 를 /frame 처럼 path 로. origin = macOS Screen Sharing(:5900) ← guacd ← guacamole webapp ← tunnel http://host.docker.internal:<port> ← Access(path-scoped, Entra). 이득 = 브라우저 클라이언트리스(모바일 ✓ — WARP 갭 해소) + 커스터머별 표준 path 템플릿. 트레이드오프 = RustDesk 코덱 → Apple VNC 코덱(관리/제어 충분). 초기 RustDesk-flat(rdp-axe, Access app 0653d6ef) 자원 철거(2026-06-05). Phase 2 Blueprint SSO 는 Guacamole(HTTP)가 Cf-Access-Authenticated-User-Email 헤더 passthrough 로 RustDesk 보다 용이. ★ 최종 결론 (2026-06-05, 실측 후): GRD(Google Remote Desktop) 존치, 자가호스팅 대체 보류. noVNC /vnc 를 e2e 구축·검증했으나 Apple 레거시 VNC 코덱이라 체감 사용 불가 (Retina 프레임버퍼 + RFB + CF 다단 홉). RustDesk = 코덱 우수하나 네이티브 앱(브라우저/path 불가). GRD 우위 = WebRTC/VP8 비디오 코덱을 브라우저 안에서 + 네트워킹 셋업 0 + 모바일 — 이 조합을 자가호스팅으로 매칭하는 솔루션이 현재 없음 (Guacamole/noVNC/Cloudflare browser-render 전부 :5900 에 RFB 라 코덱 천장 동일; Mac 은 RDP 미지원이라 Guacamole RDP 경로도 불가). /vnc 실험 자원(noVNC 스택 /Users/axe/axelabs/vnc/ + Access app eae049e7 + ingress path rule + vault VNC pw) 철거 대상. de-Google 미래 경로 = (a) RustDesk web client 성숙 시 self-host (코덱+브라우저 양립), (b) WebRTC 기반 자체 클라이언트(tether). 그 전까지 운영자 원격 데스크톱 = GRD (project_devenv “GRD 유지” 와 정합 — 본 결정이 실측으로 재확인). 수용한 trade-off: GRD = 폐쇄소스(Google 독점 Chrome Remote Desktop — 호스트 코드 일부만 Chromium src/remoting/ 공개, 릴레이·시그널링·디렉터리는 Google 비공개·self-host 불가) + 트래픽 Google relay 경유 = sovereignty 비용을 de-Google 솔루션 부재 시점까지 의식적으로 수용. | 2026-06-05 |
| D-ops-42 | 배포 SSOT 아키텍처 — origin/main SHA = 배포 진실원천 (worktree 작업격리 + build-from-SHA + deploy lock + 스테이지 상태기계 + migration-validation 게이트) (2026-06-04, DESIGN LOCKED · additive rollout) — 원칙: origin/main 의 commit SHA = 배포의 SSOT. working tree·로컬 main·실행 중 컨테이너는 전부 그 SHA 의 파생·일회용 투영. docs·backlog 가 이미 matrix-postgres SSOT 규율(D-matrix-3)을 따르듯, 같은 규율을 배포로 확장. 동기 (실측 incident): N개 동시 Claude Code 세션이 repo 당 1 working tree + 1 main 공유 → (a) 다른 세션이 먼저 push 하면 main diverge → non-fast-forward push 거부, (b) 서로 다른 feature 의 uncommitted WIP 가 한 tree 에 누적 → 누구도 자기 slice 만 clean commit 불가, (c) axe deploy 가 dirty working tree 에서 이미지 빌드 → uncommitted 코드 prod 누출 + push 전 배포 가능. 2026-06-04: 이미 배포된 blueprint fix (commit b4067504) 가 main diverge + 타 세션 WIP 로 수 시간 push 불가 → 운영자 수동 reconcile (stash→rebase→push). 추가 발견: 운영자 axe CLI (364KB) 자체가 버전 미관리·in-place 편집 = 같은 병의 가장 깊은 사례. 컴포넌트 A–F: (A) 작업 격리 axe work <svc> <slug> = 세션별 git worktree (~/.worktrees/<svc>/<slug>) off origin/main, 정규 repo 는 fast-forward 전용 “main mirror” 강등(손편집 금지) → 공유 tree 경합·WIP 혼재·stash 더미 발생 불가능. (B) SHA 에서만 빌드 = deploy 가 working tree 아닌 pushed SHA 의 clean checkout(git worktree --detach/archive)에서 빌드, 이미지 태그 <svc>:<sha>, push 안 된 SHA 배포 거부 → deploy-before-push + dirty-tree 누출 구조적 불가능. (C) Deploy lock = 서비스당 배포 직렬화 (matrix-postgres pg_advisory_lock 우선, 파일락 fallback) → 두 세션 동시 blue/green flip 불가. (D) Provenance + drift = 컨테이너 org.axe.git_sha 라벨, axe health/axe host 가 color 별 실행 SHA + origin/main 대비 drift 표시 → “라이브가 git 과 일치하나” 항상 답. (E) 잘못된 경로 제거 = axe deploy 가 tree 입력 폐기, git push origin main 직접 = pre-push 훅 + GitHub branch protection 으로 기계적 차단(현 honor-system 대체), axe ship 만 main 전진. (F) 통합 ship = axe ship(worktree 에서): fetch → origin/main rebase → SHA build+test → push(main ff) → 그 SHA deploy(lock 하) → mirror ff → shiplog. 오늘 손 reconcile 전부가 한 명령. 스테이지 상태기계: built → canary(passive color, 트래픽 0) → [migration-validation 게이트] → live(active flip) → previous(직전 active = 즉시 롤백 타깃) — blue/green 이 이미 canary/live/previous 3스테이지 제공. migration-validation (안전 척추): 스키마 마이그레이션 포함 릴리스는 flip 전 prod DB 의 ephemeral clone 에 마이그레이션 적용·검증 (axe drill/backup 스냅샷 재사용). blue/green 이 DB 공유 → 깨지는 스키마 변경은 카나리 불가능 (green 용 마이그레이션이 blue 즉시 오염). 이 게이트가 blue/green 이 못 막는 유일한 위험 차단. 대상: blueprint 대기 migration 2개 (add_user_entra_oid, add_entity_legal_name), hive alembic. rollout (additive): 전부 기존 동작과 공존 → --dry-run + passive-color 카나리(flip X) 검증 → cutover(기본값 flip + 가드 활성, escape hatch 포함) 게이트 → 되돌림 가능, 진행 중 WIP 보존. 채택 안 함: 별도 staging 환경 (staging.axelabs.ai + 독립 DB/터널) — 단일 Mac mini 에서 새 실패유형 0 + 비용·parity 부담만, canary + migration-validation 이 실위험 커버. 작업추적 새 “스테이지” 축 — roadmap/backlog/updates 가 이미 작업 파이프라인. 본문: /ops/runbook/deploy-ssot. 정합: D-ops-16 (release-gate axe ship) · D-matrix-3 (matrix SSOT) · Blue/green deploy. | 2026-06-04 |
| D-axe-idp-1 | Blueprint = 플랫폼 인가서버 (OIDC Provider) — 로그인 1회로 전 서비스 (2026-06-03 결정 → Phase 1+2 LIVE 2026-06-04; 현행 구현 SSOT /architecture/platform-identity) — D-axe-cli-1 의 외부 인증을 “신규 Entra 앱” 대신 Blueprint 중앙 관장으로 (본질적 선택). Blueprint 가 Entra 를 상위 federate(인간 SSO 1회) + 자체 플랫폼 토큰 발행(서명 JWT + JWKS + authorize/token/register + OIDC .well-known) → 모든 서비스가 Blueprint 신뢰(iss=blueprint + JWKS 검증). 한 토큰이 전 서비스 동작 (서비스별 audience juggling 불필요). frame/cortex 의 per-service OAuth-RP 프록시(D-ops-14/15)를 Blueprint 하나로 통합(net-new 아닌 consolidation). 거버넌스(per-user/tenant scope·revoke·audit) Blueprint 중앙. D-axe-cli-1 토큰모델 정정: 인터랙티브=Blueprint 발행 토큰(Entra-direct/HS256 아님), HS256 부트스트랩은 헤드리스/cron 잔존. 보안 핵심 → 설계문서 + 신·구 병행 비파괴 cutover. Phase: ①Blueprint OIDC-OP + axe login(loopback PKCE→Blueprint) + frame 가 Blueprint 신뢰(모델 증명) ②index/hive/cortex/matrix trust 이전 + proxy 폐기 ③거버넌스/감사 + headless device-code. 상태: Phase 1+2 LIVE (2026-06-04) — Blueprint OIDC-OP(discovery·jwks·authorize·token·register·revoke) + RS256 키(vault) + axe login loopback-PKCE + frame·hive·cortex·index·matrix + Blueprint 자체 MCP 6개 서비스 전부 Blueprint 토큰 신뢰(영속·e2e 검증). claude.ai→Blueprint OP 이전 + per-service 프록시 폐기, Phase 3(인가 중앙화·감사 UI·device-code·키 회전 자동화)만 잔여. | 2026-06-03 |
| D-axe-cli-1 | 외부/멀티에이전트 접근 = 고객 CLI (gh 모델) + 토큰 로그인, MCP 커넥터 대체 (2026-06-03 결정 → LIVE: 고객 CLI 배포·axe login·전 서비스 호출 가동; 사용자 매뉴얼 /services/cli) — 외부 인원이 서비스별 MCP 커넥터를 개별 등록하던 방식의 “느리고 불편” 본질 = ① 1인당 N개 커넥터 등록·인증 마찰 ② 커넥터당 도구 스키마 100개+ (frame ~54 + HR ~40 + blueprint ~20 실측) 가 매 턴 컨텍스트 적재 → 모델 턴 지연·오선택. 해소 = 단일 CLI (~/axe-cli). 근거: 셸 조합성(pipe/loop/batch 1회 실행, 모델 왕복 0 — MCP 도구는 고립 호출) + 에이전트 무관 (Claude Code·Codex·Cursor·Gemini CLI·cron·CI; CLI = syscall/ABI 층, vendor lock-in 0). CLI-only (웹 Cowork 용 Skill 폐기 — 외부 인원 Claude Code 표준화). 토큰 = axe login --token → OS Keychain → Authorization: Bearer (헤드리스/Codex 가능, 브라우저 OAuth 회피). 토큰 실체 = HS256 부트스트랩 JWT — frame frame.<customer>.jwt_secret 의 이미-LIVE 패턴 표준화; index 는 INDEX_JWT_SECRET 설정만 있고 미배선(Entra RS256 만 검증) → 배선 필요, hive/blueprint/cortex/matrix 도 PAT 경로 추가(P2). 배포 = Blueprint 설치 시 in-place 번들 (Blueprint=구동 시스템; axe onboard 가 테넌트 PAT 발급→Keychain = zero-touch 인증 + control-plane 자기설정) + standalone(curl/pip) 보조. 스코프 분리: 운영자 axe(vault/deploy/secret/customers) ≠ 고객 CLI(업무 명령·토큰 스코프). 기존 axe mcp connector-catalog 는 외부용 대체. 현행 정정: 토큰 모델은 D-axe-idp-1 로 갱신됨 — 인터랙티브 = Blueprint 발행 RS256 플랫폼 토큰(axe login SSO), HS256 부트스트랩은 헤드리스/cron 잔존. 배포 = https://<tenant>.axelabs.ai/cli(+/cli.ps1) served + axe self-update(매 실행 conditional GET). 잔여 backlog: headless device-code(B-axe-cli-device-code). | 2026-06-03 |
| D-ops-1 | folder-as-context — .mcp.json per folder | 2026-05-08 |
| D-ops-2 | JWT 평문 디스크 금지 — Keychain + launchctl env | 2026-05-08 |
| D-ops-3 | sub = identity, permissions = entitlement (분리) | 2026-05-08 |
| D-ops-4 | tool surface 분리 — operator/employee tools | 2026-05-09 |
| D-ops-5 | v1 permission matrix — read/write/close/admin | 2026-05-09 |
| D-ops-6 | end-to-end MCP test 자동화 | 2026-05-10 |
| D-ops-7 | .mcp.json is the spec location, NOT .claude/settings.json ⚠️ | 2026-05-11 |
| D-ops-8 | vault-per-entity canonical distribution | 2026-05-13 |
| D-ops-9 | Vaultwarden 가 canonical secret store | 2026-05-14 |
| D-ops-10 | Phase 1 SSO via Vaultwarden OIDC (Timshel) | 2026-05-14 |
| D-ops-11 | Timshel digest pin (mainline 무한 미지원 회피) | 2026-05-15 |
| D-ops-12 | sso_nonce manual SQL fix | 2026-05-15 |
| D-ops-13 | external_id mapping (Microsoft oid ↔ Vaultwarden user) | 2026-05-15 |
| D-ops-14 | frame OAuth-RP (Microsoft Entra ID direct) + Blueprint /onboard | 2026-05-17 |
| D-ops-15 | OAuth proxy 시도 (dormant — Anthropic Connector 한계로 미사용) | 2026-05-21 |
| D-ops-15.5 | axe.axelabs.ai 도메인 검증 + Application ID URI 형식 URL | 2026-05-21 |
| D-ops-16 | docs drift 강제는 release-gate (axe ship), pre-commit hook 채택 X | 2026-05-21 |
| D-ops-17 | secret = vault SoT + deploy-time pull (customers.yaml 매니페스트), runtime vault 호출 X | 2026-05-21 |
| D-ops-18 | compose env_file 단일 출처 (environment block 의 secret 중복 금지 + 하위 dir compose 의 .env symlink) + axe secret pull merge-mode (config 보존) | 2026-05-21 |
| D-ops-19 | Azure secret rotation = az cli + --append (overlap window) + app owner 운영자 명시 prereq | 2026-05-21 |
| D-ops-20 | 7-stack 응집 — service 별 1 stack 으로 통합 (proxy/console/docs/tunnel 분산 → frame·hive·blueprint·artemis·mysrt·vault·axelabs). cross-cutting infra (caddy/tunnels/docs/console) = axelabs stack. Portainer 12 stack → 7 stack. 본질·퀄리티·안전 우선. | 2026-05-22 |
| D-ops-21 | frame register-entity 자동 chart seed — entity 등록 시 표준 chart (KSME/KGAAP/KIFRS) 자동 apply. 이전엔 별도 apply-chart 누락 시 모든 분개가 account_code not found 로 실패하던 운영 함정 해소. | 2026-05-22 |
| D-ops-22 | frame 펀드 도메인 지원 — shared.entity 에 entity_kind (corporate/kip/kvf) + fund_meta JSONB + closed_at 컬럼 ADD. shared.entity_relationship 확장 (ownership_numerator/denominator/unit — % 폐기, kind ENUM 에 gp_managed_fund/lp_invested_fund 추가). shared.cross_journal_link 신설 (AXEV↔fund mirror 분개 pair 무결성). register-entity CLI 에 --kind/--fund-meta flag. AXEV 창업기획자 산하 KIP/KVF 다수 결성 수용. 회계 원칙: AXEV↔fund = K-IFRS 1110 통제 미달 → 운용 관계 (parent-child 아님). frame_meta schema 신설 폐기 (기존 shared 확장이 자연 + 명명 충돌 회피). alembic 0008_shared migration + frame-mcp blue/green rebuild. | 2026-05-22 |
| D-ops-23 | frame RFC 9728 resource-level metadata path fix — /frame/mcp/.well-known/oauth-protected-resource (application_id_uri 의 sub-path, RFC 9728 정확 path) 가 frame middleware 의 unauthenticated allowlist 누락으로 401 반환. claude.ai Connector / Anthropic MCP client 가 본 path fetch 시도 → OAuth challenge metadata 못 받음 → Reconnect silently 실패. 9b43845 feat(mcp): OAuth-RP dispatch + RFC 9728 endpoint 시점부터 latent, D-ops-22 deploy (frame-mcp blue/green rebuild) 가 stored token invalidate 하면서 발현. fix: http_server.py 의 _PUBLIC_PATHS + inner.router 에 resource-level path 추가 (handler reuse _oauth_protected_resource). 양쪽 path (server-level /frame/.well-known/... + resource-level /frame/mcp/.well-known/...) 모두 200. commit 4f5dfcc. | 2026-05-22 |
| D-ops-24 | Vaultwarden SSO + Microsoft Entra ID — email_verified 면제 — Entra ID id_token 에 email_verified claim 부재 → Timshel fork 가 “Your provider does not send email verification status. … SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION” 오류로 거부. 첫 employee SSO 시도 ([email protected], 2026-05-22) 에서 발현. fix: /Users/axe/.axe/vault/docker-compose.yml 의 axe-vaultwarden.environment 에 SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION: "true" 추가. SSO_AUTHORITY 가 single-tenant URL (122fb574-...) 이므로 외부 tenant 사용자 진입 불가 → 추가 검증 면제 안전. + Vaultwarden UI 함정 (이메일-first 기본 화면이 SSO Identifier 입력 칸 가림) 은 /vault/#/sso 직링크 안내 (troubleshooting.mdx). | 2026-05-22 |
| D-ops-25 | MCP service 의 startup probe 강제 (D-bp-mcp-3 cross-cutting 일반화) — 모든 DB 종속 MCP service 의 Starlette lifespan 안에서 첫 traffic 받기 전 SELECT 1 probe 실행. broken DB / malformed URL 시 lifespan fail → uvicorn 시작 실패 → healthcheck never green → axe deploy SERVICE-mcp blue/green swap 자동 거부 (broken container 가 active promote 못 됨). /health/ready endpoint 가 있어도 그건 request-time 검증이라 부팅 직후 broken DB 가 active 가능. 현재 상태: blueprint-mcp ✅ 적용 (PR #353 ). frame-mcp ✅ 적용 (2026-05-22, commit 3c7ca6a) — src/frame/mcp/http_server.py 의 outer Starlette lifespan 을 @asynccontextmanager wrapper 로 변경 (sync Engine.begin() + SELECT 1 + log + async with inner.router.lifespan_context(...) bridge). startup log "startup probe: DB reachable, schema accessible (D-ops-25)" verified. hive-mcp 미적용 — frame pattern 1:1 미러 (5-10 분, hive/src/hive/mcp/http_server.py). mcp-server-checklist § 8 #16 으로 항구화. | 2026-05-22 |
| D-ops-26 | Vaultwarden JIT user provisioning 활성 — D-ops-24 후속 incident. SSO 인증 통과 후 신규 user ([email protected], 2026-05-22 10:01) 가 /#/set-password-jit?identifier=AXE 페이지에서 “An error has occurred. Failed to retrieve the invitation” 차단. vaultwarden log Failed to retrieve the invitation from vaultwarden::api::core::accounts. 원인: SIGNUPS_ALLOWED=false 가 JIT user 생성 차단 → invitation fallback 도 (INVITATIONS_ALLOWED=false) 없음 → 양쪽 모두 닫혀서 신규 employee 영구 차단. fix: SIGNUPS_ALLOWED: "true" 단독. INVITATIONS_ALLOWED=false 유지 (manual invitation flow 사용 안 함, JIT 로 통일). D-ops-27 (operator 결정) 와 함께 읽을 것 — SSO_ONLY 는 false 유지 (emergency MP fallback 보존). | 2026-05-22 |
| D-ops-28 | bw CLI 다운그레이드 2025.7.0 + 업스트림 vault migration 백로그 — 한진우/강태훈 AXE org 멤버 초대 자동화 시 brew 의 bw CLI 2026.4.1 이 TypeError: Cannot read properties of null (reading 'toWrappedAccountCryptographicState') 로 login 단계 crash. bitwarden/clients #19413 와 동일 — CLI 2026.x 가 server ≥ 2026.x API 응답 필드 요구하는데 Timshel fork 의 최신 stable 은 1.34.1-6 (upstream Vaultwarden 1.34.1, 2025-07-15 빌드). Timshel ghcr 에 1.34 이상 image 없음 (1.36.0 git tag 만 존재). fix: brew uninstall bitwarden-cli && npm install -g @bitwarden/[email protected] (Timshel 1.34.1-6 동시기). /Users/axe/.axe/vault/invite-members.sh 가 멤버 invite + auto-accept + confirm 한 번에 처리. 상위 결정 변경: D-ops-11 의 “Timshel digest pin” 사유 (mainline Vaultwarden SSO 미지원) 가 2026-05 upstream 1.36.0 의 native SSO 도입으로 해소 → backlog B-vault-upstream-migration 등재 (dani-garcia/vaultwarden:1.36.0 으로 이전, schema migration + SSO 환경변수 매핑 + 데이터 보존 검증). 이전 완료 후 bw CLI 최신 line 복귀 가능. | 2026-05-22 |
| D-ops-27 | Vaultwarden SSO_ONLY=false 유지 + ORGANIZATION_INVITE_AUTO_ACCEPT=true (operator setup 마무리, 2026-05-22). (a) SSO_ONLY=false: D-ops-26 초안의 SSO_ONLY=true 폐기. Microsoft Entra 장애 / SSO 설정 오류 시 operator 가 master password 로 emergency 진입 가능해야 함 (axe ship docs 와 동일 원칙 — single-vendor lock-out 회피). trade-off: 외부에서 axe.axelabs.ai/vault 직접 password 가입 가능 (org collection 접근 X). mitigations: SSO_AUTHORITY single-tenant 가 외부 tenant SSO 진입 차단 + org member invite operator-only. TODO (backlog): 비정상 user row INSERT 감지 cron (B-vault-anomaly-cron). (b) ORGANIZATION_INVITE_AUTO_ACCEPT=true: org member 초대 시 사용자 측 “Accept invitation” 클릭 단계 자동 통과. operator 의 “Confirm” (org key 를 user public key 로 암호화 전달) 만 남음. 안전: invite 자체는 operator-only 이고 user pool 은 SSO 통과한 tenant user 만 가능. | 2026-05-22 |
| D-ops-29 | Dual-identity for 강수훈 — [email protected] (automation, AAD 7f110c52-...) + [email protected] (human Owner, AAD e35e6778-...) — founding operator 가 두 identity 보유. 분리 원칙: (a) ai = 자동화 / bot / cron / agent identity. Graph token 발행, 직원 proactive DM, frame/hive agent run. 사람 손작업 X. (b) soohun.kang = 본인의 사람 작업 identity. Teams 응답, vault item 등재, blueprint admin UI. Vault AXE org 에서 둘 다 Owner + access_all=1 (DB UPDATE users_organizations.atype=0, access_all=1 적용 2026-05-22). Blueprint User.role 둘 다 admin. 이유: bot 메시지가 human sender 로 표시되거나 vice versa 시 incident response 추적 혼란 (D-ops-23 의 mcp_client_invalid 디버깅 사례 참조). audit trail 명확화 + 신원 misuse 회피 + sender identity propagation 본질 원칙. | 2026-05-22 |
| D-ops-30 | Bot proactive DM endpoint — /api/admin/broadcast-dm (blueprint) — operator ([email protected] identity) 가 직원 N명에게 동일 메시지 일괄 1:1 Teams DM. Auth: Bearer <CRON_SECRET>. Body: emails 배열 + text + contentType (text | html). 각 email 마다 (1) User.aadObjectId lookup → (2) Graph POST /chats oneOnOne (bot + user, idempotent — same pair 반환) → (3) POST /chats/CHAT_ID/messages. Pattern: cron-failure-alert.ts:postAlertToAdmin 재사용 (single → fan-out). 사용 사례: vault onboarding 안내, 플랫폼 공지, 운영자 broadcast. agent-context reply 는 graph_chat_message_post (Blueprint MCP) 그대로 사용. |
| D-frame-lottecard-scraper | 롯데카드 거래내역 자체 스크래퍼 + 멀티카드 적재 (2026-05-29) — CODEF 등 외부 집계 API 미사용, 자격증명으로 롯데 법인 포털 직접 로그인하는 헤드풀 Playwright 스크래퍼 (frame scrape <source> 제네릭 + 롯데카드 첫 커넥터, host-only). 법인관리자 ID/PW (웹 보안키패드) → 승인내역 개인정보 미포함(=이메일/인증 생략) 신청 → 비동기 생성 → CP2 .xls 다운로드 → ingest. 법인전체 export 가 법인의 모든 카드를 담으므로 ingest_lotte_card 를 행별 계정 resolve 로 수정 (마스킹 5105-****-****-*042 ↔ 기존 실번호 계정 5105-5400-0199-9042 와 와일드카드 매칭하여 링크, 실번호 보존; 신규 카드는 새 계정). 비밀 = frame/axe/lotte-portal-{username,password} (vault, host-only — MCP 컨테이너 제외). 스케줄은 DB-driven (D-frame-scrape-schedule). 함정: axec id=3 의 기존 701행이 혼합 카드 (과거 첫-행-버그) — 별도 정합성 정리 대상. | 2026-05-29 |
| D-frame-scrape-schedule | 스크래퍼 주기 = DB(MCP 조정) + launchd 단순 tick (2026-05-29) — 주기를 plist 하드코딩하지 않고 shared.scrape_schedule(KST cron) 에 두고 MCP set_scrape_schedule(raw cron 또는 daily/weekly/monthly 프리셋) 로 조정. MCP 컨테이너는 호스트 launchd 를 못 건드리므로 launchd 는 com.frame.scrape-tick(08–21시 매시 :15) 단순 tick → frame scrape-due 가 croniter 로 due 판정해 해당 source 만 실행 + last_run/next_due 갱신. cron 은 KST (anchor=last_run else created_at → 갓 만든 weekly 는 첫 진짜 fire 에 실행, 첫 틱 즉발 X). croniter+Playwright=host-only(schedule.py), cron 컴파일=pure(cron.py, 컨테이너 공용). 마이그레이션 0009_shared. 신규 MCP 도구 3개 → frame-mcp rebuild 필요. SLA(다운타임 catch-up): 놓친 실행은 복구 후 1회 따라잡기 + look-back 을 now−last_run(상한 365일)까지 확장해 그 기간 backfill (row_hash dedup, 손실 0), RunAtLoad 부팅/wake catch-up, MCP overdue 가시화. seed: lottecard/axec weekly Mon 08:10. | 2026-05-29 |
| D-ops-33 | axe vault bootstrap {customer} CLI 신설 — D-ops-24/26/27 4-key 적용을 코드로 항구화. customer-onboarding.mdx 의 D+1 수동 단계를 1 명령으로 축소. 동작: SSH (or local for axe) 로 /Users/{ssh_user}/.axe/vault/docker-compose.yml 읽음 → 4 key 진단 (idempotent) → 누락만 patch (anchor: SSO_SIGNUPS_MATCH_EMAIL) → force-recreate axe-vaultwarden. Default = dry-run, --apply 로 실 패치. 검증: axe vault bootstrap axe (이미 적용 — “bootstrap 불필요” 정확 detect) + axe vault bootstrap realchoice (file 부재 시 깔끔한 에러). 후속 (B-onboard-cli-vault-bootstrap): Vault Organization 자동 생성 + 첫 admin invite (현재 web UI 수동). | 2026-05-22 |
| D-ops-31 | axep entity 신설 (액스파트너스 유한책임회사) — axe customer 의 3번째 entity 추가 완료 (2026-05-22). frame register-entity --id axep --kind corporate --accounting-standard ksme (chart auto-seed 54/59 accounts, D-ops-21) + hive register-entity --id axep (frame mirror). customers.yaml: entities 배열 ["axec","axev","axep"] + frame/hive PII passphrase secret 매니페스트 2개 (frame/axe/pii-passphrase-axep, hive/axe/pii-passphrase-axep) 추가. user_entity_map: ai/cfo/soohun 만 ["axev","axec","axep"] (axep 참여), taehun/jinwoo 은 axep 제외 유지. Deploy 완료: PII passphrase 2개 vault push (axe secret push) + frame blue/green swap (active=green:3711, blue:3710 next passive) + hive sequential force-recreate (green → 6s → blue). axec/axev 영향 0 (PII passphrase entity별 분리 + lazy-load). 자동화: /Users/axe/.axe/vault/deploy-axep.sh. 부수 발견 함정: axe secret push 가 BW_SESSION env var 무시 + macOS Keychain (axe.vault.session / [email protected]) 만 읽음 → 자동화 시 security add-generic-password -U 로 keychain 갱신 단계 필수. | 2026-05-22 |
| D-ops-34 | Onboard 1-input 모델 — Phase A (vault unlock = 세션 prerequisite) + Phase B (umbrella 명령 1 줄) — 신규 customer (realchoice 등) 배포 시 운영자 D-day 입력 = axe 명령 1 줄. 가정: vault 가 미리 unlock 돼 있어야 함 (bw unlock --raw → Keychain axe.vault.session cache, 8시간). 이후 모든 axe secret/axe onboard/axe deploy 가 keychain BW_SESSION 자동 사용 — D-day 추가 prompt 0. Phase B umbrella = axe deploy customer {customer} --from-pack pack.json --apply (B-onboard-umbrella) — 내부에서 ingest + onboard (cloudflared+frame) + deploy blueprint + deploy hive 순차. 거부: cron 기반 bw unlock keep-alive (vault 상시 unlocked = 머신 침해 시 전체 노출 = blast radius 큼). 권장 모델 = “출근 시 비번 1 회 입력 → 하루 1 입력” 보안/편의 최적점. customer IT 측 mirror = az login 1 회 + axelabs-bootstrap.sh 1 줄 (docs.axelabs.ai 만으로 자력 — D-onboard-bootstrap-publish, /partner/registration §Option A). | 2026-05-23 |
| D-ops-32 | Vault collection 구조 v1 — 6 collection (D-ops-9/27/29 종합 설계) — 기존 4 collection (Default empty, frame-jwt-axec/axev D-ops-14 잔재 each 1 item, frame-jwt-operators empty) → 6 collection: (1) Platform — Service Secrets (37 customers.yaml secrets, ai+soohun only) (2) Platform — Infrastructure (Cloudflare API, vault ADMIN_TOKEN raw, GitHub org admin, DNS, cert; ai+soohun only) (3) AXEC (4명 RW — KB/홈택스/Azure axec/세무사 access) (4) AXEV (4명 RW — KB/KSD/금감원/GP-LP portal/investee data room) (5) AXEP (ai+soohun only — GP 단독 출자/운영) (6) Shared Tools (4명 RW — Notion/Slack/공통 SaaS/도메인 admin/메일 alias). 기존 frame-jwt-axec + frame-jwt-axev 의 2 item → Platform — Service Secrets 로 이동. 기존 Default + frame-jwt-operators 삭제. 구체 마이그레이션은 운영자 web UI 권장 (SQL/bw 직접 manipulation 은 Vaultwarden cache 충돌 위험). 외부 인력 (회계사/LP) 합류 시점에 entity-level read-only 분리 검토. Progress (2026-05-26): 6 collection 전부 신설 ✅. AXEC (5b13e7e4...) + AXEV (6eb223cb...) + AXEP (89de0ab9...) + Platform — Service Secrets (1d794d29...) + Platform — Infrastructure (50f2705f...) + Shared Tools (68197247...). 권한 부여: AXEC/AXEV/Shared Tools 4명 RW (ai+soohun manage=true / taehun+jinwoo manage=false), AXEP + Platform 둘은 ai+soohun only. bw create org-collection 자동화 검증 — D-ops-32 가 명시한 “운영자 web UI 권장” 은 완전 우회 가능. 영향 평가: axe CLI _vault_get 가 name 기반 (bw get password <name>), service deploy 가 .env 에 inject — collection 추가/이동/삭제로 MCP connector 깨지지 않음 (running connector 영향 0, 다음 deploy 시 lookup 도 무관). AXE org role 확인: ai@ + soohun.kang 모두 type=0 (Owner), taehun + jinwoo type=2 (User). 남은 작업 (별건): (a) 운영자 ai@ 의 personal vault 의 service secrets (axe secret list 의 ~37 path) 를 Platform — Service Secrets 로 이전 — 현재 ai@ only, bus factor 위험. (b) Default 의 3 items (axe-macmini local: soohun.kang, axe-macmini local: taehun.kang, github PAT: [email protected]) 검토 — github PAT 은 Platform — Infrastructure, 나머지 2 은 personal vault 가 적절. (c) frame-jwt-axec/axev/operators 의 2 item (taehun-kang-axec-2026-05-17, ai-operator-2026-05-16) 검토 후 적절한 신설 collection 으로 이동. (d) 옛 4 collection (Default + frame-jwt-*) 삭제. → 신규 backlog B-vault-collection-migration-v1. 2026-05-26 권한 정정 fix 적용 완료: bw create org-collection 의 users 배열 처리 server-side 함정 (Owner skip + manage=0 강제 — 자세한 root cause = B-vault-org-perm-3-quirks) 으로 6 collection 모두 ai+soohun 권한 누락 또는 partial 이었음. SQLite 직접 users_collections 일괄 INSERT/UPDATE + ai+soohun-only collection 의 taehun/jinwoo entry DELETE 로 본 결정의 권한 모델 (4명 RW vs ai+soohun only) 그대로 정합 복원. cipher delete/move 는 */admin HTTP endpoint 직접 호출이 정공법 (bw CLI 의 cipher.permissions.delete client-check 우회). | 2026-05-22 |
| D-nemotron-1 | /services/nemotron-personas 카탈로그 등재 (placeholder) — 트루비아 측 기존 자산 (Docker SSE :8771, NVIDIA Nemotron-Personas 데이터셋 1M+ 페르소나 풀) 이 docs 미등재였던 갭 해소. 보고서 Q5 confirm 후속 (2026-05-23 RE^2). 본격 본문은 D+14 (2026-06-15) 트루비아 측 라이선스/origin 회신 후. 라이선스 cross-customer OK 면 공식 카탈로그 + Blueprint MCP registry, X 면 realchoice 전용 (customers.realchoice.private_services[] 신설 검토). 현 시점 docs 노출은 가시성 + 운영자 SPOF 회피 목적. | 2026-05-23 |
| D-magnet-tenant-map-1 | customers.yaml > customers.{customer}.service_tenant_map 신설 — magnet 의 service-internal RLS tenant_id (integer) 와 AXE customer ID (string) 의 매핑 SSOT. MAGNET_TENANT_ID=1 (realchoice) 유지, 향후 axe 가 magnet 사용 시 tenant_id=2 부여로 충돌 회피. 트루비아 측 보고서 Q6 confirm 후속 (2026-05-23 RE^2). /services/magnet#tenant-id-mapping 본문. 향후 stream / 기타 service-internal id 도 동일 패턴 확장. axe ship 이 manifest 에서 자동 주입은 B-magnet-tenant-env-injection 후속. | 2026-05-23 |
| D-onboard-bootstrap-publish | axelabs-bootstrap.sh 를 docs.axelabs.ai 의 raw 로 노출 — 신규 customer IT 가 docs link 만 받고 자력 완료 가능한 상태 (별도 안전채널 메시지 + realchoice_entra_id_setup_v4.md 의존 폐기). /Users/axe/.axe/bootstrap/axelabs-bootstrap.sh → /Users/axe/axelabs-docs/public/axelabs-bootstrap.sh 복사 (17749 bytes, SHA-256 7d2d607136e5387c6c9368bf8529d4ec0e219f8eb7bab45b57590a00838210cc). partner/registration §Option A 가 docs 안에 다운로드+검증+실행+회신을 모두 포함하도록 재작성. 함께 신설: partner/macmini-prep.mdx (Tailscale 가입 + SSH key 회신 + 절전 OFF + Docker 6 단계) + partner/domain-prep §A (axelabs.ai zone = 운영자 책임, 귀사 IT 1 분 한 줄 전달) §B (귀사 corporate domain TXT 가이드) + partner/handoff JSON pack 양식 (axelabs-bootstrap/v1 schema) + partner/index 의 4-step 흐름. customers.yaml realchoice.sso.apps 에 blueprint 슬롯 사전 추가 (bootstrap.sh 의 apps.blueprint 출력 흡수 대비). 운영자 측 1-shot umbrella (D-ops-34) 는 여전히 backlog — 운영자 D-day 명령 수 5→1 줄이는 작업은 별도 추진. customer-facing 의 docs-only 자력 완료는 본 결정으로 달성. | 2026-05-23 |
| D-ops-41 | SSH client-side automation: NSSM/서비스 폐기, user-context Scheduled Task + VBS wrapper 채택 — /onboard/ssh-access 실전 셋업 (Soohun Kang Windows 11, 2026-05-27) 도중 6 함정 발견 후속. (1) cloudflared TCP forward 와 token renewal 모두 user-context Scheduled Task 로 통일. TCP forward 는 VBScript wrapper 호스팅 으로 0-flash hidden + 무한 재시작 보장 (wscript.exe + Do ... sh.Run ..., 0, True; WScript.Sleep 5000; Loop). Token renewal 은 self-rescheduling PS1 — JWT exp claim 파싱 후 exp - 5min 에 한 번만 fire (24h 주기 task fire 2~3회), Set-ScheduledTask 가 자기 trigger 재예약. (2) 거부된 대안: (a) NSSM/SYSTEM 서비스화 — JWT 토큰 user-scoped (%USERPROFILE%\.cloudflared\) vs SYSTEM 의 %SystemRoot%\System32\config\systemprofile\.cloudflared\ 격리 → 무한 failed to acquire app token lock 루프 + SYSTEM 의 desktop session 부재로 자체 login 도 무한 “Waiting for login…” (함정 #10). (b) NSSM 사용자 계정 실행 — 패스워드 평문 저장 + Microsoft 계정/Windows Hello 사용자 UX 저하 + 계정 패스워드 변경 시 서비스 정지. (c) Cloudflare Service Token — D-ops-29 dual identity audit 와 충돌 (SSO email log 없어짐). (d) Access app session_duration 30d 연장 — 갱신 빈도만 줄어들 뿐 자동화는 별개. (3) PowerShell -WindowStyle Hidden 폐기 (daemon 류) — Windows 가 hidden flag 적용 전 수십 ms 콘솔 노출 → 매 task fire 마다 깜빡임 잔상 (함정 #13). wscript.exe 호스팅이 0-flash. (4) Task -RestartCount 의존성 0 — FAILURE (non-zero exit) 에만 발동이라 cloudflared 정상 종료 (exit 0) 시 silent 중단 → 며칠 후 SSH 끊김 (함정 #14). VBS wrapper 안의 Do ... Loop 가 어떤 종료 코드든 5초 뒤 재기동. (5) Renewal task -RunLevel Highest 필수 (함정 #11) — Set-ScheduledTask 는 elevated 권한 필요, Interactive 만으로 Access is denied. (6) SSH use case 의 active logon session 전제 — “logon 전에도 살아있음” 이 필요 없음 → user-context Scheduled Task 가 NSSM 보다 깔끔 (패스워드 저장 0 / 토큰 캐시 자동 공유 / 본인 끌 UI 0 / 가시적 창 0). 결과 = 사람 클릭 0회 조건부 — Microsoft “로그인 상태를 유지” sticky session 켜져 있으면 renewal 시 SSO 자동 통과. Entra conditional access 가 sticky 차단하면 갱신 시점 1회 클릭 필요 (정책 영역, client 우회 불가). 검증 환경: Windows 11 + cloudflared 2026.5.1 + OpenSSH for Windows 9.5p2 / LibreSSL 3.8.2. 참조: /onboard/ssh-access § TCP forward 자동화 / § Token 자동 갱신 / § 함정 #9~#14. | 2026-05-27 |
| D-ops-40 | axe.3 vault release plan — 2 patch (Owner skip + cipher.permissions) + KDF rotation procedure + axelabs-ai/vault GHA pipeline 구축 — D-ops-37 axe.2 deploy 후 발견된 B-vault-org-perm-3-quirks (3 함정) + B-vault-axe.2-sso-mp-incomplete 의 모든 근본 진단 (2026-05-26 timshel/vaultwarden source clone read-only 진단). Patch A — 함정 1 (Owner skip on collection user save): src/api/core/organizations.rs 의 2곳 동일 fix — post_organization_collections (line 522-524) + post_organization_collection_update (line 685-687) 에서 if member.access_all { continue; } 3 라인 제거. 결과 = Owner role 도 CollectionUser::save 명시 entry 생성 → 권한 체크 정합 + cipher.permissions.delete 계산 정상. Patch B — 함정 3 (cipher.permissions field missing): src/db/models/cipher.rs::to_json 함수 line ~239 부근 "edit" / "viewPassword" 옆에 "permissions": {"response": null, "delete": <bool>, "restore": <bool>} 추가. <bool> 계산 = Membership::find_by_user_and_org(user_uuid, org_uuid).await 결과의 atype <= Admin OR cipher.user_uuid == Some(user_uuid) OR (get_access_restrictions().manage == true). Bitwarden mainline schema backport. Patch C — 함정 2 (cipher edit collectionIds silent partial-revert): patch 안 함 — vaultwarden bug 아닌 mainline design (collectionIds 변경은 POST /ciphers/{id}/collections 별도 endpoint). 운영 룰: bw CLI 의 bw edit item 으로 collectionIds 변경 불가, 대신 bw move 또는 /admin endpoint 직접 호출 (B-vault-org-perm-3-quirks 의 운영 임시 룰 영구). Patch D — SSO→MP wrapped MP: patch 안 함 — axe.2 의 response shape backport 가 이미 충분. 기존 user (axe 측 4명) 의 user.akey 가 옛 KDF/wrapping schema 로 생성된 게 원인 (realchoice 측 신규 가입 user 는 axe.2 적용 후라 새 schema, 정상). Fix = KDF rotation runbook (운영자 셀프, patch 영역 외): 각 user 가 MP 단독 로그인 → Settings → Account → Security → Master password change (same value OK) + KDF type PBKDF2 → Argon2id → save → vault items re-encrypt → logout → SSO→MP 정상. axe 측 4명 (ai/soohun/taehun/jinwoo) 차례로. realchoice 측 영향 없음. Build pipeline (axelabs-ai/vault repo, 현재 빈 상태): build/build.sh (clone timshel 1.34.1-6 tag + patches/*.patch 일괄 git apply + docker buildx --platform linux/amd64,linux/arm64 --push ghcr.io/axelabs-ai/vault:<tag>) + .github/workflows/build.yml (push.branches=main 자동 빌드 main-<sha7> rolling tag + workflow_dispatch input tag 로 release 빌드 1.34.1-6-axe.3) + build/patches/0001..0004.patch (axe.2 의 prelogin/password + AccountKeys + axe.3 의 organizations Owner-skip + cipher permissions). Deploy 흐름: (1) axelabs-ai/vault repo 의 build/ + workflows/ + patches/ commit. (2) workflow_dispatch → GHCR push. (3) axe-macmini: docker compose pull vaultwarden && docker compose up -d (compose.yml image 라인 = ghcr.io/axelabs-ai/vault@sha256:<new-digest> 로 핀, D-ops-37 의 axe.2 ImageID 주석 → axe.3 digest 로 갱신, 이전 digest rollback 주석 보존). (4) realchoice-macmini: 동일 pull + up (customer self-deploy, D-ops-customer-sovereignty 원칙). (5) axe + realchoice 양쪽 검증 = collection 생성 (Owner entry 명시 확인) + cipher delete (bw CLI 정상 동작 확인) + SSO→MP (KDF rotation 후 검증). 현금 비용 0 (GHCR + GHA runner 무료 한도 안, image 460MB, build axelabs-ai/vault GHCR push public 가능 + source 공개 같이. private 유지 원하면 patch 만 사적 운영 (배포 안 함, GHCR private + 본인 macmini 만 pull). 본 결정 = public 모드 (source 공개 = patch 공개, GHCR public, 라이선스 정합 + 무료 GitHub plan 한도 무관). Progress (2026-05-26 KST, ~3.5h 실 진행): (i) axelabs-ai/vault repo PUBLIC 전환 ✅ (조직 정책상 GHCR package 는 PRIVATE 유지). (ii) realchoice-macmini 의 기존 build pipeline (build/build.sh + Dockerfile + README + workflow + 2 axe.2 patches) 그대로 vendor + axe.3 patch 2개 추가 ✅: patch 0003 src/api/core/organizations.rs line 522 + 600 + 685 의 if member.access_all { continue; } 3 라인 제거 (plan 의 “2곳” 외 line 600 post_bulk_access_collections 추가 발견 — 동일 root cause, 3곳 모두 patch). patch 0004 src/db/models/cipher.rs::to_json line 374 (실 line, plan 의 ~239 와 다름) 의 edit/viewPassword 옆에 permissions: {response,delete,restore} 추가 + line 161 의 _ → manage 로 바꿔 get_access_restrictions 결과 reuse (DB round-trip 절약). can_delete = collection manage flag OR org Owner/Admin (atype <= 1) — Membership::find_by_user_and_org 한 번 호출. (iii) 로컬 docker buildx (arm64, —load) 검증 ✅ — 5분 (M-series + cache), Rust 컴파일 정합 확인 (image axelabs-ai/vault:axe.3-localtest ImageID sha256:e1ac1617...). (iv) commit 799015b push to axelabs-ai/vault main → GHA build-vault-image 자동 트리거 → 13분 multi-arch build → ghcr.io/axelabs-ai/vault:main-799015b push ✅. workflow_dispatch 는 axe-labs-ai PAT 의 actions scope 부족으로 403 — 대신 docker buildx imagetools create 로 1.34.1-6-axe.3 alias 발행 (rebuild 없이, 3초). manifest list digest sha256:a26208a0794acbc9a2807379ffba33c7478dbe8d41daed24893f7916a55aeada (arm64 sha256:d97a6ed..., amd64 sha256:402eeac...). (v) GHCR auth: PAT (read+write:packages+workflow) 발급 → AXE vault Platform — Service Secrets / ghcr-axelabs-ai-pull-pat 저장 (D-ops-ops-17 SoT 원칙 — keychain 안 씀). docker login 패턴: bw get password ghcr-axelabs-ai-pull-pat | docker login ghcr.io -u axe-labs-ai --password-stdin. (vi) axe-macmini deploy: data tgz + sqlite 백업 (data-pre-axe.3-20260526-131744.tgz, db-pre-axe.3-20260526-131744.sqlite3) → docker compose pull && up -d → 60s healthy. compose image 라인 = ghcr.io/axelabs-ai/vault@sha256:a26208a.... (vii) 검증 3건 in-production (13:19 KST): VERIFY 1 = bw create org-collection (test 용) → SQLite users_collections 에 [email protected]|0|0|1 row 명시 insert ✅ (axe.2 = zero rows). VERIFY 2 = raw GET /api/sync → org cipher 의 permissions: {delete:true,restore:true,response:null} 정확 emit ✅ (bw CLI 2025.7.0 출력은 null — 옛 client 가 unknown field 파싱 못해 표시 안 됨, 새 client ≥2026.4 는 정상 읽음). VERIFY 3 = bw delete item (test cipher) rc=0 ✅ (cleanup 후). (viii) external https://axe.axelabs.ai/vault/alive HTTP 200 (0.28s). (ix) B-vault-fork-build-pipeline ✅, B-vault-org-perm-3-quirks ✅. (x) 잔여 = KDF rotation 4명 (ai/soohun/taehun/jinwoo) — 운영자 본인 MP 입력 필수라 자동화 불가, B-vault-axe.2-sso-mp-incomplete runbook 참조. realchoice 안내 별도 (customer-sovereignty — 직접 pull, vault 에서 PAT 받아감). (xi) ai@ KDF rotation 검증 완료 (Master password change + rotate encryption key 만으로 SSO→MP unlock 정상 — KDF 변경 없이 akey 재wrap 만으로 충분 확인). (xii) post-deploy 정책 layer 박제 (/architecture/vault-policies 신설): (a) server env SSO_AUTH_ONLY_NOT_SESSION=true 적용 — SSO 가 인증만, session lifecycle = Vaultwarden 자체 30일 idle refresh token. trade-off = Entra 비활성/제명 ≤30일 lag (AXE 4명 규모 OK), benefit = SSO+MP 재입력 월 ~1회. (b) Org Policies 4종 API 적용 (Owner ai@ token 으로 PUT) — MasterPassword(min 12, complexity 1), PasswordGenerator(len 20), SingleOrg, PersonalOwnership. (c) MaximumVaultTimeout 시도 HTTP 400 — Timshel source 의 // MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed) 확인. Bitwarden commercial license 라 Vaultwarden 영구 미지원, SSO_AUTH_ONLY_NOT_SESSION 이 가용 영역 안 best 대체. 3 layer 모델 (server env + Org Policies + per-client preferences) 통합 박제. (xiii) 4명 KDF rotation 전부 완료 → D-ops-40 전체 종결 (2026-06-04): ai (5/27) → soohun → taehun (5/29) → jinwoo (6/4) 순서. 본질 발견 = KDF type dropdown 변경 불필요 — Master password change + ☑ “Also rotate my account’s encryption key” 체크박스 만으로 user.akey 재wrap 충분 (plan 의 “KDF PBKDF2 → Argon2id” 보다 간소). DB: ai 는 Argon2id (Phase B 까지 진행), soohun/taehun/jinwoo 는 PBKDF2 유지 — kdf_type 안 바뀌어도 akey 재wrap 만으로 SSO→MP fix 동작 확인. 4명 모두 SSO→MP unlock 정상. jinwoo last_login 5/23→6/4 점프 = 완료 신호 + 운영자 구두 확인. 운영 도구 부산물: /onboard/vault-setup playbook (taehun 피드백 6건 반영) + /ops/runbook/operator-broadcast (Teams DM 1:N) + /architecture/vault-policies. B-vault-axe.2-sso-mp-incomplete ✅. D-ops-40 전 작업 (build pipeline + axe.3 patches + GHCR + deploy + 검증 3건 + 정책 layer + 4명 rotation) 종결. | 2026-05-26 |
| D-ops-39 | 무료 Universal SSL 의 1-level wildcard 한계 = flat-hostname 컨벤션 강제 (ACM 유료 회피) — Cloudflare 무료 plan 의 *.axelabs.ai Universal SSL cert 는 1단 서브도메인만 cover. 2단 (ssh.axe.axelabs.ai) 은 SAN 미일치 → edge default cert fallback → 클라이언트 tls: handshake failure (Windows: SEC_E_ILLEGAL_MESSAGE 0x80090326, macOS: sslv3 alert handshake failure). 강태훈 Windows 의 cloudflared access login https://ssh.axe.axelabs.ai (2026-05-25) 에서 발현, MAX agent 진단 (axelabs.ai chat) → 운영자 forward → 본 결정. Fix: 신규 1단 hostname ssh-axe.axelabs.ai (DNS CNAME → 동일 tunnel d8efecdd, ingress 규칙 clone, Access app b903d8cd self_hosted_domains 추가). 검증: curl -v https://ssh-axe.axelabs.ai → *.axelabs.ai cert SAN 매치 + Access 302 OK. 옛 ssh.axe.axelabs.ai 와 vestigial *.axe.axelabs.ai 와일드카드 (이번 한계 violation) 는 B-ssh-axe-flat-hostname-sunset 로 grace period 후 일괄 삭제. 거부된 대안: (a) Cloudflare Advanced Certificate Manager $10/cert/월 — 1개 hostname 위해 유료 전환 부당. (b) axe.axelabs.ai 별도 zone 등록 — NS delegation/zone 이원화 overhead 과잉. (c) 강태훈 Tailscale 우회 — 일반 직원 권한 범위 외 + 권한 확장 정책 필요. (d) 클라이언트 cert 검증 우회 — 보안 위반 + 다음 직원도 같은 함정 재현. 컨벤션: HTTPS 노출되는 모든 hostname 은 {name}.axelabs.ai 1단 (ssh-axe, docs-axe 등 flat). sub-context 필요하면 path (axe.axelabs.ai/frame). 본 룰 /architecture/domains#함정—universal-ssl-wildcard-의-1-level-한계-d-ops-39. | 2026-05-26 |
| D-ops-38 | 외부 service 2FA 는 vault item TOTP 필드, AXE 내부 vault 계정 자체 2FA skip (Entra SSO 가 MFA 역할) — AXE 가 사용하는 외부 services (KB/홈택스/AWS/GitHub/카드사/세무사 SaaS/etc.) 의 2FA 코드 는 Vaultwarden item 의 login.totp 필드 (Vaultwarden 무료 지원, Bitwarden premium 의 self-host 등가) 에 저장 → vault unlock 시점에 client (web/ext/native) 가 자동 6-digit 생성. AXE 내부 vault 자체 user 2FA 는 활성 안 함 — Microsoft Entra SSO 가 이미 MFA 역할 (Entra ID 의 conditional access + Authenticator app 정책). Vault 의 SSO→MP unlock 흐름이 동작하면 (현 B-vault-axe.2-sso-mp-incomplete 해소 후) 그 자체가 multi-layer. AI agent 자동화 활용: Claude Code / axe CLI / 기타 운영 자동화가 외부 service 로그인 시 TOTP 필요한 경우 bw get totp <item-name> 또는 향후 axe secret totp <name> (B-axe-secret-totp-cli) 으로 vault 에서 자동 조달 — 운영자 phone 의존 제거 + bus factor 0. 운영 룰: 외부 service 2FA 등록 시점에 (a) phone Authenticator 앱은 사용 안 함, (b) QR/secret/otpauth:// URI 를 vault item 의 TOTP 필드에 직접 저장, (c) phone 분실/교체 시 손실 없음. trade-off: vault 가 단일 trust boundary — 뚫리면 외부 service 2FA 도 같이 뚫림. 따라서 vault 의 SSO/MP/(향후) 2FA layer 가 더 중요해짐. AXE 내부 vault 계정 자체 2FA skip 결정은 Entra SSO 가 살아있을 때 만 유효 — SSO 가 깨진 현재 상태 (B-vault-axe.2-sso-mp-incomplete) 에서는 MP single-factor 단계라 임시 위험 가시. SSO 흐름 복원 시점에 자동 mitigation. | 2026-05-26 |
| D-ops-37 | AXE-local Vaultwarden fork build — ghcr.io/axelabs-ai/vault@sha256:a26208a0794acbc9a2807379ffba33c7478dbe8d41daed24893f7916a55aeada (axe.3, 2026-05-26) — Bitwarden client >= 2026.4 (Chrome extension + bw CLI) 와 Timshel 1.34.1-6 fork 사이 두 protocol 불일치 영구 fix. (1) Bitwarden client 가 /accounts/prelogin/password 로 split — Timshel 은 legacy /accounts/prelogin 만 노출 → 404 → 로그인 시작 불가. (2) /identity/connect/token 응답에 새 wrapped-variant 필드 (AccountKeys / MasterPasswordUnlock) 부재 → “toWrappedAccountCryptographicState” throw → unlock 실패. 패치 2개 모두 dani-garcia/vaultwarden mainline 에서 backport. realchoice tenant 가 2026-05-25 자체 build & deploy → axe-macmini 는 본 일 (2026-05-26) realchoice-macmini local image 를 docker save/load 로 import → axe-vaultwarden recreate (ImageID sha256:11536845..., arm64). prelogin/password endpoint 200 OK 회귀 검증 완료 (이전 404). 2026-05-26 추가 (axe.3 ramp, D-ops-40): axelabs-ai/vault GHA pipeline 정식 구축 + axe.3 patch 2개 추가 (organizations Owner-skip + cipher.permissions) → image 라인 = ghcr.io/axelabs-ai/vault@sha256:a26208a... (manifest list, multi-arch). D-ops-11 immutable digest 핀 정합 복원. rollback (axe.2): 로컬 tag axelabs-ai/vault:1.34.1-6-axe.2, ImageID sha256:11536845.... rollback (pre-axe.2): ghcr.io/timshel/vaultwarden@sha256:a5d5ed0366abd84cc7443a79e52ffe4774671648dca5e1a5412fd91b77d4f692. follow-up: (a) axelabs-ai/vault 에 build/patches/ + GHA workflow 실 셋업 | 2026-05-26 |
| D-ops-35 | axe deploy hive {customer} subcommand 신설 — frame blue/green 패턴 1:1 mirror. cmd_deploy_hive + _hive_active_color + _hive_port 헬퍼 + HIVE_* 상수 (compose, proxy=axe-hive-proxy, networks=["hive_default","artemis_default"], alias=hive-mcp, drain=5s). 8-step: build passive → up -d → /health/ready 200 wait → add alias (양쪽 network) → caddy reload → edge verify (200/401 둘 다 정상 — JWTAuthMiddleware) → drain 5s → 옛 active alias 제거. argparse choices 에 hive 추가, dispatcher 분기. 검증: axe deploy hive axe --apply 실 swap (blue:3810 → green:3811 active, edge axe.axelabs.ai/hive/health: 200, 무중단). hive 가 axe ship hive 의 manual_hint 만 남아있던 격차 해소. realchoice 6/1 deploy 가 진정 1-shot 가능 — axe deploy customer realchoice --from-pack ... umbrella (D-ops-34) 의 hive 단계도 자동. customer-onboarding.mdx 의 step 4 manual ssh 폐기. | 2026-05-23 |
| D-ksme-1 | KSME seed 판관비 10개 보강 — 5210 급여 / 5211 임원급여 / 5212 잡급 / 5213 퇴직급여 / 5214 복리후생비_4대보험 / 5215 통신비 / 5216 광고선전비 / 5217 도서인쇄비 / 5218 교육훈련비 / 5219 감가상각비. 한국 법인 필수인 급여 계정 자체가 없던 본질적 결손. | 2026-05-22 |
| D-frame-3 | cash_flow_statement 신규 계정 fallback 분류 (type + code prefix) — 명시적 dict 우선, 미매핑 계정만 규칙 적용 — CF 분류가 hardcoded dict (INVESTING_ACCOUNTS/FINANCING_ACCOUNTS/OPERATING_*) 라 신규 계정(1302 투자조합출자금·1303 매도가능증권·2104 주임종단기차입금)이 unclassified_accounts 로 남아 reconciliation_diff≠0 → verified=false. Fix (ops/statements.py): 미분류 계정에 한해 fallback rule — 1300번대 자산(prefix 13, type=asset) → 투자활동 (1301/1302/1303 + 향후 1300번대 신계정 자동 포함), 차입금성 부채(BORROWING_LIABILITIES=2105, type=liability) → 재무활동. ⚠️ 2101 미지급금/2102 미지급세금/2103 부가세예수금/2106 가수금 은 OPERATING_LIABILITIES 라 영업활동 유지 — 21xx prefix 로 싸잡지 않고 차입금 코드만 explicit set (미지급금 재무 누출 차단). 명시적 dict 멤버는 기존 로직대로 처리 (기존 분류·테스트 불변, 회귀 0). reconciliation 계산을 fallback 분류 後로 이동. 라이브 axec 2026-01~05 CF: diff −195M → 0, verified=true (1302 −225M·1303 −200M 투자, 2104 +230M 재무). | 2026-06-04 |
| D-frame-2 | evidence/source_file blob 은 항상 .local/files content-addressed 복사 + storage_url 은 portable (relative <hash[:2]>/<hash>/<filename>) — local://<host 경로> 참조 금지 (컨테이너가 host 경로 read 불가 + source 파일 rename/move 시 깨짐), CWD 의존 absolute 경로도 금지 (host /Users/.../.local/files/... vs 컨테이너 /app/.local/files/... 불일치). 근인: ingest/entity_meta.py·ingest/resolution.py 가 blob 을 복사 안 하고 local://{pdf_path.resolve()} 만 저장 → 외부 OneDrive/Downloads 포인터라 컨테이너에서 blob 부재; _store_file_locally·ops/evidence.py·hometax.py 는 복사는 했지만 str(dest.resolve()) 라 CWD 의존. Fix: _store_file_locally 가 portable url 반환 + 신규 resolve_storage_path() 가 read 시 settings.file_storage_path 기준 absolute 로 매핑 (portable//app//Users/local:// 4형식 tolerant — legacy row 무중단). 모든 write-site (entity_meta/resolution/hometax/ops.evidence) 가 _store_file_locally 경유. read-site = http_server.py storage-integrity probe 가 resolve_storage_path 사용. Alembic entity migration 0020_normalize_evidence_storage_url 가 blob 이 .local/files 에 실존하는 기존 row (axec 158 + axev 19 = 177) 를 portable 로 정규화 (blob 부재 row 는 절대 미변경·notice; blob 절대 삭제 안 함). | 2026-06-04 |
| D-frame-1 | pending_payroll: 분개 trigger = 실 거래 (matching principle) — D-hive-26 의 즉시 분개 디자인 폐기. hive event → pending_payroll INSERT (예약). KB raw_tx → match_pending_payroll (date±3 + amount exact + name fuzzy) → 분개 자동 생성 + link. status: pending→matched/stale/cancelled. 회계 표준 (matching principle: 거래 발생 시점에 분개) 부합. 이중 분개 위험 0. | 2026-05-22 |
| D-matrix-1 | Matrix: Rust + native MCP (no Python SDK) — 첫 Rust 서비스. MCP Streamable HTTP 직접 구현 (JSON-RPC 2.0 + axum). 모니터링 서비스 저메모리·단일 바이너리 적합. D-bp-mcp-1 (Python FastMCP) 과 별개 precedent | 2026-05-23 |
| D-matrix-2 | Matrix: WAN/인터넷 가용성 모니터링 + ISP 귀책 판별 — collector 에 check_wan (wan-gateway / wan-internet / wan-dns 프로브) 추가. gateway↑ + internet↓ ⇒ ISP/WAN fault 자동 태깅. 스키마 변경 0 (기존 check_results JSONB 재사용). 컨테이너 NET_RAW + iputils-ping, default interval 60→30 | 2026-06-03 |
| D-matrix-4 | netheal — 호스트 인터넷 자가치유 데몬 (matrix 보완) — 인터넷 지속 끊김 시 DNS플러시→DHCP갱신→WiFi바운스 사다리 자동 실행. ISP 장애(gateway↑·외부↓)면 백오프(WiFi 안 건드림). root LaunchDaemon (컨테이너 밖, 호스트 en1 제어). 자동 재부팅 기본 OFF. ~/axe-netheal/ | 2026-06-04 |
| D-matrix-3 | Matrix: backlog/roadmap/ship-log 구조화 SSOT — backlog·roadmap·updates(ship log) 의 SSOT 를 hand-edited markdown → matrix-postgres(slug PK B-foo/M1 보존) 로 이전. matrix MCP 8 도구(backlog_list/create/update/transition·roadmap_list/upsert·shiplog_append/list)로 전 서비스 에이전트가 멀티라이터-안전 read/write. docs 의 3 페이지는 host-side 생성(axe ship docs 가 matrix fetch→.mdx, matrix-git-push 모델 기각). known-gaps·decisions 는 narrative 라 markdown 유지. 동기 = git-markdown 멀티라이터 충돌(2026-06-03 세션 실측: known-gaps swept-commit·backlog 동시 +3). ADR =matrix/docs/adr/backlog-roadmap-system.md. Phase 1(스키마 3테이블+MCP 8도구+import 파서) 구현·cargo check·import dry-run(206 backlog+16 ship+7 roadmap) 검증 완료, 미배포 | 2026-06-03 |
| D-hive-1 | Hive HR backend 신설 — frame 패턴 mirror (schema-per-entity, OAuth-RP) | 2026-05-21 |
| D-hive-21 | Payroll v2 — 한국 도메인 본질 7가지 (간이세액표 lookup · 비과세 분리 · 보수월액별 · 부양가족 · 정산 · 일할 · 보험별 라운딩). v1 단순 누진세 폐기. 첨부 급여대장 셀별 정확 일치. | 2026-05-21 |
| D-hive-22 | 급여명세서 메일 발송 인프라 (PDF + 평문 본문) + 법정 8항목 (근로기준법 §48 ②) | 2026-05-21 |
| D-hive-23 | M365 SMTP AUTH 의 deprecation 회피 → Graph API SendMail + client_credentials + DKIM/DMARC DNS (Cloudflare API token 자동화) | 2026-05-21 |
| D-hive-24 | 메시지 템플릿 관리 — shared default + entity override + Jinja2 SandboxedEnvironment + StrictUndefined + partial unique index | 2026-05-21 |
| D-hive-25 | Customize 강제 (require_entity_override=True — shared fallback 차단) + 발송·송금 audit log (payslip_dispatch_log + payslip_send_log with body_sha256) | 2026-05-21 |
| D-hive-26 | Cross-service event consumer = CONSUMER-worker 별도 컨테이너 (consumer-grouped, multi-channel LISTEN). frame-worker 1개가 hive_events 받음 + 미래 magnet/stream 채널은 dispatch table 추가만 (컨테이너 추가 X). N×N 폭증 회피 (N+M, M≤N). in-process async task (P2) 검토 후 책임분리·격리·blue/green swap 무관·crash 격리 우선으로 sidecar (P1) 선택. | 2026-05-22 |
| D-hive-27 | employees.payroll_start_date 단일 컬럼 분리 — hire_date 가 (i) 계약 발효일 (ii) 보수 개시일 (iii) 4대보험 가입일 3 의미 동시에 짊어지던 모순 해소. payroll_start_date Date nullable, NULL → hire_date fallback (정직원 90% 는 NULL → 자동). 임원계약 §2 (계약 시작) ≠ §6/§12 (보수·4대보험 시작) 케이스 대응. frame.executive_officer.appointed_date (= hive.hire_date) 정합 보존. 4대보험 가입일은 당분간 보수 개시일과 동일로 가정 — 다른 경우 발생 시 social_insurance_enroll_date 추가 검토. cycle.py 일할 base + employee_create/update MCP 옵셔널 입력 지원. alembic entity 0009. | 2026-05-29 |
| D-hive-28 | compute_period 시그니처에서 employee_ids 제거 — D-hive-20 원작자가 정정 경로를 분리 (full re-run = compute_period, 사후 정정 = compensation_events.adjustment, D-hive-17) 했음에도 후행 adjustment leg 미구현 + employee_ids 파라미터의 동작 (“period 전체 wipe + 일부만 INSERT”) 이 운영자에게 “부분 정정” 직관을 줘 다른 직원 payslip 손실 위험. 단순 결함 아닌 의도된 분기 + 미완성 + 의미 모호의 누적. 시그니처 모호 파라미터 제거가 원작자 의도 정합. 골든 테스트 + MCP wrapper 사용처 0건이라 호환성 영향 없음. 후속: compensation_events.adjustment 정식 MCP 도구 구현은 별도 backlog. | 2026-05-30 |
| D-hive-30 | compensation_plan_list + compensation_award_create MCP 도구 신설 (M 트랙, D-hive-17 후행 leg) — alembic 0004 의 plan/award/event 3-table schema 만 정의되고 등록 도구 미구현이라 운영자가 직접 SQL 박아야 했음. 본 결정에서: compensation_plan_list(entity, plan_kind?) (read) — plan_code 조회용. compensation_award_create(entity, employee_no, plan_code, annual_amount_krw, effective_from, effective_until?, award_code?) (admin) — employee/plan 존재 + active overlap 검증 후 INSERT (overlap 시 COMPENSATION_AWARD_CONFLICT raise, 옛 award effective_until 먼저 set 안내). payroll_compute_period 가 active salary award 없으면 skip — 본 도구가 페이슬립 산정의 선행. 사용 절차: plan_list → award_create → compute_period 재호출 → case A INSERT 자동 산정. 후속 (별도 backlog): award_update / award_close / plan_create / event_* 등. | 2026-06-05 |
| D-hive-29 | compute_period UPSERT-with-gates (ABCDE) + compensation_events.adjustment 정공법 도구 — DELETE+INSERT 가 (i) payslip_send_log FK NO ACTION 과 충돌해 발송된 페이슬립 재산정 차단, (ii) ID 변경으로 외부 참조 dangling, (iii) audit history 가 두 ID 로 쪼개짐. UPDATE 는 PG WAL/MVCC 동등 또는 우위. 직원별 case A(없음→INSERT) / B(pending+send_log=0→UPDATE) / C(pending+발송됨→skip) / D(paid→skip) / E(reversed→skip) MECE 룰. UPDATE 는 산정 fields 덮어쓰기 / 운영 fields + 운영 메타 보존. 동반 구현 = compensation_event_adjustment_create MCP 도구 (admin scope) — payslips uq 제약으로 갈래 1 line append + 합계 보정 채택, compensation_events.adjustment row 가 audit 본질. delta_krw 부호 = line_items.amount_krw 부호. 발송된 페이슬립 정정 시 직원 메일 ≠ DB → 운영자 재발송 책임. D-hive-28 의 “DELETE+INSERT” 표현을 본 결정이 갱신, employee_ids 제거 자체는 유지. 보강 (룰 H, 동일 날짜): adjustment 도구는 case C 만 허용 — A→PAYSLIP_NOT_FOUND / B→PAYSLIP_NOT_DISPATCHED (compute_period 안내) / D→PAYSLIP_ALREADY_PAID (운영자 수동·다음달 소급 자동화 의도적 미구현) / E→PAYSLIP_REVERSED raise. 자동화 leg (frame 정정 분개 consumer / 정정명세서 자동 재발송 / 운영자 Teams 알림) 별도 backlog. 보강 (룰 I, 2026-06-05): NPS 4.5% → 4.75% (2026 한국 국민연금 보험료율 9%→9.5% 인상, 1998년 이후 첫 조정). 사용자 원본 산식 MIN(base, IF(base<370K,16650,IF(base>5.9M,265500,ROUNDDOWN(ROUNDDOWN(base,-3)*4.75%,-1)))) 그대로 적용 — base 천원 절사 + 양끝 정액 cap (16,650 / 265,500) + 외부 MIN wrapper. 룰 K (2026-06-05 최종) — 근로소득세/주민세 base = R(과세표준) / 4대보험(NPS·NHIS·LTC·EI) base = 신고보수월액(reported_income_*_krw, NULL fallback R). 보수월액 컬럼 값은 R 과 같게 set 정책 (운영자 책임, 회계·공단 신고 정합). 정액 케이스는 R 기준과 결과 동일, 일할 케이스 (신규 입사·중도 퇴사) 에서 R(일할 변동) vs 보수월액(고정) 차이 가능 — 별도 backlog B-hive-payroll-base-policy-decision 정책 결정. axec.AXEC-002/003 reported_income 4,186,222/4,022,567 → 3,966,667 정합 UPDATE (axev 는 이미 정합). 골든 axec 4월 net 강태훈=3,607,277 / 한진우=3,571,577 — 사용자 expected 정확 일치. MCP 노출 leg 정정: D-hive-21 payroll v2 컬럼 8개 (reported_income_pension/health·dependents·children·vehicle·is_executive·meal/vehicle_allowance) 를 employee_get select / employee_update allowed 에 일괄 추가 — 운영자가 신고보수월액 조회·갱신 가능. 골든 강태훈/한진우 axec 4월 pension 188,370→198,830, net 3,605,007→3,594,547 (강태훈) / 3,569,307→3,558,847 (한진우). 5월 ps#7 강태훈 (case C) = adjustment 정공법 정정 필요, ps#8 한진우 (case B) = 다음 compute_period 호출 시 UPDATE 자동. | 2026-06-01 / 2026-06-05 |
| D-bp-mcp-calendar-2 | Admin send-as 캘린더 쓰기 — create_event 등 6개 write tool 에 as_user_email optional 파라미터 추가 (find_free_time 제외 — 이미 attendeeEmails 로 다중 사용자 조회). 호출자 role=admin 이면 target user 의 캘린더에 직접 write 가능, event organizer = target user 표시. 구현: 신규 getAppOnlyClient() (graph.ts) = msalApp.acquireTokenByClientCredential({ scopes: [".default"] }) 로 app-only token 발급 → /users/{targetUpn}/events Graph endpoint (UPN/email key — Blueprint User.id = Prisma cuid 이지 Entra oid 아님). Azure App Application permission Calendars.ReadWrite 필요 + tenant admin consent 1회. 권한은 Blueprint Next.js Azure App (2b222356-1c36-48e0-96a3-2c5e0ecbf937) 에 부여 — MCP custom connector 의 별도 App (482598f7-...) 가 아님 (함정 /ops/known-gaps#blueprint-azure-app-id-혼동). 명령: az ad app permission admin-consent --id 2b222356-1c36-48e0-96a3-2c5e0ecbf937 (Global Admin 계정 필요 — AXE 테넌트는 soohun.kang 만). consent 후 blueprint-app 컨테이너 1회 재시작 (MSAL acquireTokenByClientCredential 내부 토큰 캐시 비우기). 비-admin caller 의 as_user_email 사용 시 403 send_as_forbidden. D-bp-mcp-calendar-1 의 delegated 경로는 그대로 (caller 본인 캘린더 = 기본 동작). 동기 = 관리자 ([email protected]) 가 직원 계정 ([email protected]) 의 캘린더에 직접 일정 등록하는 사용자 시나리오. | 2026-05-26 |
| D-bp-mcp-mail-1 | 범용 이메일 발송 tool send_mail — Blueprint MCP 의 첫 일반 outbound mail surface (기존 outbound 은 create_event .ics 초대뿐). caller 본인 mailbox 에서 임의 수신자(외부 도메인 포함)로 메일 발송. 구현: 신규 dispatcher route POST /api/internal/mail (calendar route 형제) → caller delegated token (getClientForUser; Mail.Send delegated 는 이미 src/lib/graph.ts SCOPES 에 존재 → self 발송은 re-consent 불필요) → Graph POST /me/sendMail. params: to/subject/body(html|text)/cc/bcc/attachments(Graph fileAttachment base64)/save_to_sent_items/dry_run. Admin send-as (as_user_email, calendar-2 와 동일 게이트): caller role=admin 이면 app-only token (getAppOnlyClient) + /users/{targetUpn}/sendMail 로 타 사용자 mailbox 발송. Azure App Application permission Mail.Send + admin consent 필요 — Blueprint Next.js app (2b222356-1c36-48e0-96a3-2c5e0ecbf937) 측, MCP connector app (482598f7-...) 아님 (함정 /ops/known-gaps#blueprint-azure-app-id-혼동). 비-admin 의 as_user_email → 403 send_as_forbidden. Audit (injection-safety 정책 필수): 모든 발송 (sent/dry_run/failed) 이 append-only MailSendLog 1 row (caller · sentAs · to/cc/bcc count · subject · status · httpStatus · messageId) — Hive payslip_send_log + magnet decisions-ledger 패턴 정합. Graph sendMail 은 202 no-body → message_id null (Hive D-hive-23 동일; Sent 폴더 사본이 durable record). dry_run=true = 메시지 조립·검증·preview 만 반환, Graph 미호출. 동기 = 운영자 봇 ([email protected], role=admin) 이 외부 고객/임원에게 요약 메일 발송 필요. | 2026-05-29 |
| D-bp-mcp-1 | Blueprint MCP standalone (Python+FastMCP, frame/hive 1:1 미러) + 5층 양파껍질 회고 → 체크리스트 항구화 | 2026-05-21 |
| D-bp-mcp-2 | Blueprint MCP blue/green pair (frame frame-mcp-blue/green 패턴 미러, alias swap + Caddy reload, cloudflared 무중단) — single replica 가 build 시 5-10s 다운 유발하던 drift 해소 | 2026-05-21 |
| D-bp-mcp-teams-1 | get_teams_message MCP tool — Blueprint MCP 가 teams.microsoft.com/l/message/... deep link 를 본문·발신자·타임스탬프로 resolve. 구현 = Python MCP → docker default 망 → Blueprint Next.js POST /api/internal/teams/fetch-message (Bearer BLUEPRINT_INTERNAL_API_KEY 공유 비밀, /api/internal/entity-roles 패턴 미러) → 기존 getChatMessage() (bot identity [email protected], app perm ChatMessage.Read.All) 재사용. 동기 = Claude (web/desktop) connector 사용자가 Teams 링크 던졌을 때 “fetch 불가” 응답이 나오던 capability gap — connector 자체는 enable 됐으나 tool 이 없어서 호출 불가. Scope: contextType=chat (1:1·그룹) 만. channel-context URL 은 501 (/teams/{teamId}/channels/{channelId}/messages/{messageId} Graph 경로 + groupId 파싱 follow-up). bot 미멤버 chat 은 403 그대로 surface. | 2026-05-26 |
| D-bp-mcp-calendar-1 | Blueprint MCP 의 첫 write surface = M365 caller 캘린더 CRUD (7 tools: create/update/delete/get/list event, add_attendees, find_free_time). 사용자 요청 = Outlook calendar 자동 일정 등록 + 외부 도메인 (gmail/naver 등) attendee 초대. 구현: 단일 dispatcher route POST /api/internal/calendar (op selector — 7 ops in one file, calendar 가 1 logical CRUD domain). caller email → User.id → getClientForUser(userId) delegated Graph (Calendars.ReadWrite). 외부 attendee 도메인 = Graph 가 SMTP invite 자동 발송, 별도 provider 통합 불필요 (Google Calendar API 와 무관). NextAuth + graph.ts SCOPES 양쪽에 Calendars.ReadWrite 추가 — 기존 사용자 1회 re-consent (또는 admin tenant-wide consent). D-bp-mcp-1 의 read-only 원칙과 무관: D-bp-mcp-1 은 Blueprint 자체 도메인 (issue/session/workspace) 에 한정 — calendar 는 별개 도메인 (Microsoft Graph). 후속 D-bp-mcp-calendar-2 에서 admin send-as 추가. | 2026-05-26 |
| D-bp-mcp-teams-3 | reference 첨부 파일 본문 fetch — D-bp-mcp-teams-2 의 v2 가 attachments[i].contentUrl 을 surface 했지만 OneDrive/SharePoint 파일의 실제 본문은 별도 Graph 호출이 필요해서 호출자가 링크만 받고 끝남. 사용자 첫 메시지에 .md 첨부가 있어 즉시 노출된 gap. v3 = 신규 tool get_teams_attachment_file(content_url) + 신규 endpoint POST /api/internal/teams/fetch-attachment-file. downloadReferenceFile() (graph-client.ts:1507) 의 in-memory 버전 = encodeShareUrl (private helper 미러: u! + base64url, no padding) + /shares/{enc}/driveItem/content ARRAYBUFFER fetch. 응답 = 확장자 기반 3 분기: text-like (md/txt/csv/json/yaml/yml/log/xml/html/ts/py/sh/sql/conf/toml/diff 등) → UTF-8 decode 후 kind: "text"; image (png/jpg/gif/webp/svg) → base64 + kind: "image"; 나머지 → base64 + kind: "binary". 50 MB 기본 / 100 MB hard cap (호출 시 maxBytes override 가능). 동기 = file vision 으로 가는 게 아니라 텍스트 파일이 압도적이므로 text → string 직답이 가장 큰 가치. 이미지 자료는 get_teams_hosted_content (inline message images) 가 별도 경로. | 2026-05-26 |
| D-bp-mcp-teams-2 | 첨부 + 인용 + 인라인이미지 지원 확장 — D-bp-mcp-teams-1 의 v1 응답이 attachments[] 를 drop 해 사용자 첫 smoke test 메시지에 첨부가 있어도 호출자가 알 수 없던 gap. v2 응답에 (a) attachments[] (reference OneDrive 파일 + contentUrl / messageReference Teams 인용 / Adaptive Card content JSON), (b) quotes[] (messageReference 자동 1-level resolve — attachments.ts:resolveMessageReferences() 패턴 미러, quote-of-quote 는 opaque), (c) inlineImageIds[] (extractInlineImageRefs(bodyHtml)) 추가. 신규 tool get_teams_hosted_content(chat_id, message_id, hosted_content_id) = Graph /chats/{c}/messages/{m}/hostedContents/{h}/$value ARRAYBUFFER fetch → 매직바이트 sniff (PNG/JPEG/GIF/WebP) → FastMCP Image() 반환 → Claude vision 직접 활용. 10 MB 상한. 신규 internal endpoint POST /api/internal/teams/fetch-hosted-content. | 2026-05-26 |
| D-bp-entity-1 | Blueprint 에 entity 개념 도입 (axec/axev) — Workspace 에 entityId FK + paraLayer enum (PROJECT/AREA/RESOURCE/ARCHIVE). PARA 4 layer 모두 entity scope. hive 의 schema-per-entity 와 달리 row-level FK (Blueprint 의 cross-entity workflow 와 부합). PR #339 + hotfix PR #341 적용 완료. | 2026-05-21 |
| D-bp-entity-2 | PARA dispatch flow sub-consensus — Project 종결 → Area/Resource 이관은 copy-with-provenance (sourceWorkspaceId / sourceArtifactPath / copiedAt). Archive = 시점형 archaeology, Area/Resource = 현재형 living knowledge (검색 분리). 자동 분배 = LLM 제안 + 사용자 확인. schema 부분은 D-bp-entity-1 PR 에 통합, UI 는 PR 5 예정 (Path B Spike 선행 권고). | 2026-05-21 |
| D-bp-para-1 | PARA 범용 재구조화 — 조직론 모델 + dispatch “집(link)” + governance 분리 + 죽은 딜 본질 (D-bp-entity-2 copy→link revise) — PARA=조직론(Area=영속기능/MCP=물질화 · Project=TFT · R/A는 Area 안). dispatch=“집” 모델: artifact 몸통1·집N(link 기본), move=소모성→Archive→폐기, copy-curate=재사용본 저작→Resource (copy-with-provenance default 폐기). Blueprint=모든 Area 기본 substrate + 가로 결정로그, 서비스=졸업한 live 엔진. governance=결정로그(record, append-only) + 결재워크플로(process, layer), e-sign 통합=B-bp-decision-pipeline-esign. 본질=죽은 딜 저장·harvest. 신규코드=Rust. 상세=/architecture/para-os. | 2026-06-06 |
| D-bp-rust-1 | Blueprint 점진 Rust 전환 (strangler-fig) — substrate = 첫 organ (D-bp-para-1 열린질문 a/c/d 확정) — 백엔드를 도메인별 Rust(axum) organ 으로 점진 추출, Next.js=얇아지는 frontend-of-record (프론트는 당분간 TS, UI 전면 재작성은 별개·먼 결정). 첫 organ = PARA substrate(artifact/dispatch/knowledge-query). 경계=Artifact-scoped: substrate 가 artifact/artifact_link(+annotation)/mcp_schema 소유, workspace_id·entity_id=opaque 외부 ref(FK·중복 0), Workspace+paraLayer SoR=Prisma 잔류. 공존=Postgres schema-split(Prisma public / sqlx substrate, 한 blueprint-postgres, 교차쓰기 0) → D-bp-artifact-4 “monolith first·신규 인프라 0” refine(모순 아님): 인프라 동일·언어만 Rust·프로세스 분리. 배포=blueprint compose sidecar blueprint-substrate (cloudflared/공개 MCP/blue-green/ops 온보딩 없음, axe ship blueprint 한 경로). Next→Rust 인증=기존 BLUEPRINT_INTERNAL_API_KEY Bearer. 모델=D-bp-para-1 home/link(“집”) 1급 + 2-level scope(personal/shared) 확정(a) + citation 8종(D-index-6 index.* · D-gate-2 gate.decision 포함). Project staffing=후속 defer(c). 상세=ADR docs/adr/blueprint-rust-migration.md + /architecture/para-os. | 2026-06-06 |
| D-bp-entity-3 | General bucket 본질 제거 + sys entity 도입 — Workspace.entityId nullable → NOT NULL FK (모든 workspace 가 정확히 1 entity 보유). ad-hoc / 시스템 row 는 seeded sys entity (kind=system, end-user dropdown 미노출). 사용자 결정 2026-05-22 (“General entity 는 필요하지 않습니다”). PR #344 + hotfix PR #345 (workspace.create 3곳 entityId 필수 — _resolveEntityIdForCreate helper: opts.entitySlug / drivePath guess / user.entityScopes[0]). 적용 후: axec 1 / axev 15 / sys 2 / null 0. | 2026-05-22 |
| D-bp-entity-4 | TopNav 전역 EntitySelector + hive name sync — 페이지별 entity dropdown 폐기 → TopNav 우측 단일 selector (URL ?entity= primary + localStorage axe:lastEntity fallback). session.user.entityScopes 노출. ProjectsClient + ARAClient 가 URL query 읽기로 fetch 구성. Blueprint Entity.name 을 hive shared.entity.legal_name 한국어 정식 명칭 (액스코퍼레이션 주식회사 / 액스벤처스 주식회사) 으로 sync — 두 시스템 entity universe SOT 정합. PR #346 + PR #347 (Next.js 16 useSearchParams Suspense — /axe layout dynamic) + PR #348 + PR #349 . | 2026-05-22 |
| D-bp-entity-5 | Entity metadata 확장 + /api/entities + TopNav fetch — Entity 에 bizNo / fiscalYearStartMonth / countryCode 추가 (hive shared.entity mirror, axec=366-86-03798 / axev=643-86-01377). /api/entities GET 엔드포인트 (서버 측 entityScopes 권한 검증). TopNav ENTITY_LABELS hardcode 제거 → /api/entities fetch — 새 entity 등록 시 client 재배포 0. seed drift detect 가 모든 mirror field 비교 (PR #350 의 early-out fix). PR #350 + PR #351 . | 2026-05-22 |
| D-bp-entity-6 | Team entity scope + Member.entityId rename — Member.entityId (polymorphic User/Agent FK disambiguator) → underlyingId. D-bp-entity-1 의 Workspace.entityId (accounting entity) 와 naming 충돌 영구 해소. team-service 응답에 entityScopes: string[] | null 추가 (human = User.entityScopes / agent = null). TeamClient + OrgChartView 가 chip 표시 (axec/axev = lime / sys = muted) + URL ?entity= filter (human 만 적용, agent 는 system actor pass-through). PR #352 . | 2026-05-22 |
| D-bp-entity-7 | User.defaultEntity — 사용자별 명시적 default entity — 이전: TopNav 첫 로드 “all” / createWorkspace fallback “scopes[0]” 가 customers.yaml 순서 의존 (axec). 사용자별 default 명시 불가. 본 PR: User.defaultEntity String? 컬럼 + hydrateEntityScopesForUser 자동 set (null + non-empty scopes → scopes[0]; scope 외 default → scopes[0] fallback). session.user.defaultEntity 노출. TopNav fallback chain: URL → localStorage → defaultEntity → “all”. createWorkspace fallback: opts → drivePath → defaultEntity → scopes[0] → throw. /api/user/default-entity PATCH 엔드포인트 (server-side gate). PR #354 . 운영 후속: Settings UI dropdown (endpoint 의 사용자 surface, 별도 PR). | 2026-05-22 |
| D-bp-entity-8 | entityScopes YAML 순서 보존 + scopes[0] fallback — D-bp-entity-7 의 후속 함정: hydrate 가 entityScopes 를 alphabetic sort 후 저장 → customers.yaml ["axev","axec"] operator 의도 SOT 가 DB ["axec","axev"] 로 reset → scopes[0] = “axec” → axev default 불가능. fix: hydrate 가 raw resolved 순서로 저장 (sort 는 set-equality 비교만). TopNav fallback chain 에 userScopes[0] 추가 — URL → localStorage → defaultEntity → scopes[0] → “all”. session.defaultEntity null (build cache miss 등) 이어도 yaml 순서 가 운영 default. customers.yaml 의 user_entity_map list 순서가 진정한 SOT. 사용자 결정 “모두 default 는 axev” 만족. PR #355 . | 2026-05-22 |
| D-bp-entity-9 | owned account switching (TopNav AccountSwitcher) — Soohun 이 [email protected] + [email protected] + [email protected] 모두 운영. owner 가 owned AI agent identity 로 view-only switch. 본 PR: User.ownedBy String? self-FK (NULL 시 본인 계정, set 시 owner.id 참조). POST /api/auth/switch-account { targetUserId } 가 target.ownedBy === caller.userId 검증 후 httpOnly cookie axe:active-user-id set. DELETE 는 cookie clear (revert). session callback 이 cookie 발견 시 매 read 마다 ownership 재검증 → session.user.* 모두 target 로 override (email/name/role/entityScopes/defaultEntity). JWT 자체는 untouched — authenticated identity 의 audit trail 보존, view 만 변경. TopNav AccountSwitcher dropdown (owned ≥1 시만 노출, isImpersonating=true 시 lime border + tooltip). mid-session ownership revoke 시 자동 revert (defense-in-depth). admin role 도 bypass 불가 — owned-only. PR #356 + hotfix PR #357 (Suspense import). 적용 후 DB: ai@/cfo@ ownedBy = soohun.kang.id. | 2026-05-22 |
| D-bp-entity-10 | AccountSwitcher 항상 노출 + Add account — 이전: owned ≥1 (accounts.length > 1) 조건으로 single-identity 사용자에게 dropdown 가려짐 → 다른 계정 추가 진입로 부재. 본 PR: gate 를 !activeUserId 로만 (signed-in 모두 노출). dropdown 안에 + 다른 계정으로 로그인 옵션 추가 → 선택 시 signIn("azure-ad", ..., { prompt: "select_account" }) 호출, Microsoft 계정 picker 강제. PR #358 첫 시도는 return null on accounts.length === 0 함정 (initial state 가 빈 배열 → null mount → useEffect setAccounts 무효) → hotfix PR #359 가 condition 을 !activeUserId 로 좁힘. PR #358 + #359. | 2026-05-22 |
| D-bp-entity-11 | ”전체 entity” 폐기 — 항상 specific scope — 이전: TopNav 와 ProjectsClient/ARAClient/TeamClient 가 entity === "all" 분기로 전체 entity 합산 뷰 제공. 사용자 보고: “전체 entity 선택하는 것 잘 동작하지 않습니다” + 회계/거버넌스 측면에서도 cross-entity 합산은 의미 부재 (axec 와 axev 는 별 법인). 본 PR: ALL 상수 제거 + URL entity 가 userScopes 안 이 아니면 자동 specific 으로 rewrite (fallback: localStorage → defaultEntity → scopes[0]). 클라이언트 fetch 도 entity 항상 set. PR #360 + follow-up PR #361 (3 client 의 “all” 분기 제거 — ProjectsClient/ARAClient/TeamClient). | 2026-05-22 |
| D-bp-entity-12 | user name = AccountSwitcher trigger (M365/Gmail 패턴) — 이전: 사용자 이름은 정적 <span>, AccountSwitcher 는 별도 <select> — 사용자 보고 “nav 에서 id 클릭하면 다른 어카운트로 바뀌는거 구현 왜 안되어있습니까?”. 본 PR: 두 element 통합 — 사용자 이름이 자체 dropdown trigger (button) 이고, 클릭 시 popover 메뉴 펼침 (owned accounts list + + 다른 계정으로 로그인). click-outside / Escape close + active item lime / aria-haspopup=“menu” + aria-checked. data-testid nav-user-name 유지 (E2E backward compat). isImpersonating=true 시 button border + label lime. <select> native 동작 (browser-provided dismiss / option 색) 잃지만 M365·Gmail UX 일관성 + click target = label 본질 우선. | 2026-05-22 |
| D-bp-entity-13 | EntitySelector → button + popover (UI 일관성) — 사용자 요청: “entity dropdown 도 id dropdown 처럼 보여지면 좋겠습니다”. TopNav 의 두 dropdown (EntitySelector + AccountSwitcher) 중 EntitySelector 만 native <select> 로 남아있어 시각 불일치 + dark-mode option 색이 OS 마다 차이. 본 PR: 동일 button + popover 패턴 미러 (chevron rotate 120ms, click-outside / Escape close, aria-haspopup=“menu”, aria-checked). 각 행에 slug chip (axec/axev/sys) 표시 — 사용자가 legal name 만 보다가 slug 도 확인 가능. data-testid nav-entity-selector 유지 + nav-entity-menu/nav-entity-item-{slug} 추가. 부수: userScopes useMemo 로 wrap → react-hooks/exhaustive-deps 경고 0. | 2026-05-22 |
| D-bp-entity-14 | /axe/usage entity scope (회계 분리) — UsageLog → metadata.sessionId → Session.workspaceId → Workspace.entityId 경로로 axec/axev 비용 분리. K-IFRS/K-GAAP 회계 원칙 (거래는 발생 entity 기준 — economic locus = workspace). UsageLog 자체에 entityId column 추가 없이 derived join 으로 Phase 1 구현 — 228 row 규모에서는 query 부담 무시 가능, 5k+ scale 시 Phase 2 로 column + backfill. workspace 미존재 usage (ad-hoc Teams 1:1 / system) 는 sys entity 분류 → TopNav 에서 sys 선택 가능한 user (root) 에게만 노출. response 에 entity + byEntity aggregation 추가 (UI 가 각 entity 별 합계 표시 가능). byUser/byChat/byWorkspace/byModel/byAuthMode/byDay + recent 모두 entity filter 적용. UsageClient 가 URL ?entity= 읽기 — TopNav EntitySelector 와 자동 연동. server-side entityScopes.includes 검증 (defense-in-depth). | 2026-05-22 |
| D-bp-entity-15 | switch-account ownership = authenticated id (impersonated sibling switch fix) — 사용자 보고: “id dropdown 에서 다른 id 들이 클릭 안되는 이슈”. 근본 원인: switch-account/route.ts 의 callerId = session.user.id 가 effective (impersonated) id 였음. 시나리오: soohun 로 로그인 → ai@ 로 impersonate → cookie effective=[email protected], JWT authenticated=soohun.id → ai@ 화면에서 cfo@ 클릭 시 [email protected], target.ownedBy=soohun.id ≠ [email protected] → 403. UI 의 try/catch 가 silent fail 하여 사용자에게 “클릭 안 됨” 으로만 보임. 본 PR: ownership 검증 + self-switch 모두 authenticatedUserId 기준. JWT 가 실제 switching authority 의 SOT (cookie 는 view override 만). 부수: UI selectAccount 의 silent fail 폐기 — non-ok response + network error 모두 console.error + window.alert 로 노출. 정책 명시: impersonated session 이라도 같은 authenticated owner 의 owned set 안에서는 자유롭게 sibling switch 가능 (ai@↔cfo@). 권한 상승은 여전히 차단 — owned identity 가 authenticated 가 아닌 target 으로 switch 불가. | 2026-05-22 |
| D-bp-entity-16 | admin role 자동 sys entity 포함 — D-bp-entity-14 follow-up. /axe/usage?entity=sys 가 403 반환되던 함정. 근본: hydrateEntityScopesForUser 의 admin fallback 이 customers.yaml 미설정 시에만 작동 (resolved.length === 0). customers.yaml 에 명시된 user (axec/axev) 가 admin 이어도 sys 가 entityScopes 에 없음. 본 PR: admin role 이면 explicit list 뒤에 sys 자동 append. 정책: ad-hoc Teams 1:1 / system 사용량 회계 attribution 은 운영자만 확인 (회계 보고서 작성용). 일반 user 는 그대로 403 (자기 ad-hoc 보고 싶으면 admin 승격 필요). customers.yaml 손대지 않고 role 기반 자동화 — operator-editing-yaml-every-time 함정 회피. | 2026-05-22 |
| D-bp-entity-17 | EntityRole 모델 — entity-scoped 3-tier 권한 (admin/owner/member) — 트리거: axev 대표 강태훈 ([email protected]) 이 axev HR/payroll 운영을 위해 admin scope 필요한데, 기존 User.role=admin 격상은 tenant-wide → axec 까지 영향하는 over-scope. 본 PR: EntityRole { userId, entitySlug, role } 모델 도입. role hierarchy admin > owner > member. tenant admin (Soohun, ai@) 은 모든 entity 에 implicit admin (admin fallback 정신 계승). hydration = sign-in 시 entityScopes 기반 member row 자동 생성 + slug 제거 시 revoke; UI grant 가 owner/admin overlay. Admin UI (/axe/admin/users) 에 entity 별 dropdown 추가 (tenant admin gate). 내부 API GET /api/internal/entity-roles?email=... (HMAC bearer) — hive/frame OIDC RP 가 PR 2/3 에서 호출해 자동 MCP scope grant 의 SOT. entityScopes JSON 은 dual-read (가시성 SOT) — 추후 별도 D 에서 폐기 평가. | 2026-05-22 |
| D-frame-fund-ksme-policy-check | frame entity_versions/0003 의 ck_policy_standard 가 fund_ksme 미허용으로 fund entity 등록 좀비 — 트리거: 강태훈 대표 (2026-05-22 11:42) “frame 에서 axe_ia_001 (액스 투자조합 1호, kind=kip) entity 오류없이 등록 및 활용할 수 있도록 조치” 요청. 근본: D-ops-30 (Migration #2) 가 fund kind 시 shared.entity.accounting_standard='fund_ksme' set 하는데, 0003 의 per-entity accounting_policy.standard CHECK 가 ('ksme','kgaap','kifrs') 만 허용 → bootstrap INSERT 가 COALESCE(accounting_standard,'ksme') 로 fund_ksme 그대로 넣다가 CheckViolation → upgrade_entity 중단 → axe_ia_001 schema 빈 채 (alembic_version 행도 없음, 0 tables) zombie. Fix 2-prong: (a) 0003 코드 패치 — CheckConstraint 정의 자체에 fund_ksme 추가 (신규 entity 가 처음부터 통과), (b) 0019 신규 migration (0018 fund_commitment_ledger 다음에 선형화) — DROP CONSTRAINT IF EXISTS + IF EXISTS 가드 + ALTER ADD CONSTRAINT 로 기존 entity (axec/axev/axep/axtest/axe_ia_001_diag) CHECK in-place 확장 + zombie schema 도 fresh 0001→head 로 진행 통과. 배포 후 강태훈 측 register_entity 재호출 → schema head + chart auto-apply 성공 경로 회복. | 2026-05-22 |
| D-frame-register-entity-atomic | register_entity atomicity + mismatch detect — 강태훈 bug report 의 root cause #2/#3 잔여분 (D-frame-fund-ksme-policy-check 후속). bug report 의 (1) CHECK 제약은 PR #38 로 fix 됐지만 (2) explicit accounting_standard='ksme' 명시해도 stored 가 fund_ksme 이면 silent override + (3) shared.entity INSERT 와 upgrade_entity 가 별개 begin() 블록 → migrate 실패 시 좀비 row 잔존 — 두 잠재 함정이 미수정. 본 결정: src/frame/db/migrations.py:register_entity 를 2-prong 강화 — (a) mismatch detect: existing row 의 accounting_standard/entity_kind/schema_name 가 요청 인자와 다르면 ValueError (silent skip 금지, “use different entity_id or update via admin path”), (b) compensating action: upgrade_entity 실패 시 inserted_now 인 경우 DROP SCHEMA CASCADE + DELETE shared.entity 로 회수 (기존 row 는 보존). 6 신규 pytest (mismatch standard/kind/all-diffs · idempotent identical · zombie cleanup · pre-existing 보호) + 16 기존 PASS 무회귀. 본 audit 결과: bug fix 적용 후 axe_ia_001 은 valid empty fund 로 회복 (48 accounts seeded + accounting_policy fund_ksme bootstrapped + alembic 0019 head). 좀비 cleanup = axe_ia_001_diag (강태훈 진단용) + bug_check_kip_20260524 (강태훈 fix 검증용) DROP. axep (액스파트너스 유한책임회사, 의도 불명, 데이터 0) 는 보존. | 2026-05-26 |
| D-hive-blueprint-entity-roles | hive OIDC 가 Blueprint EntityRole 조회해 admin scope 자동 부여 (D-bp-entity-17 PR 2) — 트리거: D-bp-entity-17 후속 + 강태훈 axev owner 격상 후 hive connector 의 admin 자동 활성. 이전: auth_oidc.py 가 OIDC 인증 시 무조건 [read, write, approve] 만 부여 (admin 은 운영자 HS256 manual). 본 PR: sign-in 마다 Blueprint 내부 API GET /api/internal/entity-roles?email=... (HMAC bearer BLUEPRINT_INTERNAL_API_KEY) 호출 → role admin/owner 면 그 entity 의 scope 에 admin 추가. fail-open (Blueprint 장애 시 default scope 유지 — lockout 회피), 5분 cache (admin grant 가 Cowork 재인증 cadence 내 반영, Blueprint API 부담 최소화), _log.info("admin scope granted on ...") 로 audit. customers.yaml services.hive.secrets[] 에 BLUEPRINT_INTERNAL_API_KEY 추가 (Vaultwarden collection 동일). | 2026-05-22 |
| D-frame-blueprint-entity-roles | frame OIDC 가 Blueprint EntityRole 조회해 admin scope 자동 부여 (D-bp-entity-17 PR 3) — D-hive-blueprint-entity-roles 의 frame 측 1:1 미러. 동일 함수 시그니처/캐시 정책/fail-open. frame OIDC default [read, write, close] + role admin/owner 시 admin 추가 (reopen 은 여전히 HS256 only). customers.yaml services.frame.secrets[] 에 BLUEPRINT_INTERNAL_API_KEY 추가. | 2026-05-22 |
| D-dev-platform-1 | Phase 1 contributor platform — axe-macmini multi-user dev access (B 안) — 비전: macmini 10대 × customer 직원 1-2명/customer 가 시스템 개선 활동. 본 PR Phase 1 = 강태훈 + Soohun 양쪽 dev 진입 인프라. (1) macOS multi-user 정책 = first.last shortname (soohun.kang/taehun.kang), dev group + setgid + git core.sharedRepository=group 으로 /Users/axe/{blueprint,frame,hive,axelabs-docs} 공유. (2) sudoers /etc/sudoers.d/ai-axe 좁은 NOPASSWD (dseditgroup/sysadminctl/createhomedir/launchctl/sshd/install/tee /Users/*+/etc/ssh/sshd_config*/mv /etc/ssh/sshd_config*/chown). (3) SSH 강화 — /etc/ssh/sshd_config.d/200-axe-hardening.conf 에 PasswordAuth disable / PubkeyOnly / Root disable / ChallengeResponse disable (AllowUsers 는 자동화 user 명단 확정 후 별 round). (4) Cloudflare tunnel ingress + DNS ssh.axe.axelabs.ai (proxied, host SSH 22 로 forward) — public 외부 노출 없음, tunnel only. (5) 4 repo (blueprint/frame/hive — axelabs-ai org 으로 transfer 완료, axelabs-docs local-only) 에 CODEOWNERS skeleton (* @soohunkang) + PR template. macOS local password 는 Vaultwarden AXE org Default collection 의 axe-macmini local: <user> item 으로 등재 — Teams 채팅에 평문 송부 X. 본 PR 미완: Cloudflare Zero Trust App (Account-scope CF token 발급 후 별 round), branch protection (GitHub Team plan 또는 public 전환 결정 필요). | 2026-05-23 |
| D-dev-platform-2 | axe customers add <id> CLI — Phase 2 customer onboarding 자동화 entry point — Phase 1 의 강태훈 reference implementation 다음 step. AXE central side metadata 등재 (customers.yaml block 추가). Idempotent (이미 존재 시 거절), dry-run, validation (id ^[a-z][a-z0-9-]{1,30}$, entity slug ^[a-z][a-z0-9_]{1,20}$ — underscore OK / hyphen X, frame/hive schema name compatibility), --legal-name/--primary-domain/--tailscale-host/--public-domain/--entities/--yes. 후속 (별 commands, 별 PRs): axe customers vault-bootstrap <id> (Vaultwarden collection), axe customers github-team <id> (GitHub team + collaborator slot), axe customers dns-placeholder <id> (Cloudflare CNAME stub), axe customer deploy <id> (customer macmini Docker stack). 첫 use case = realchoice (이미 customers.yaml 등재 — 다음 deploy 단계). | 2026-05-23 |
| D-dev-platform-3 | axe customers vault-bootstrap + github-team CLI 추가 — D-dev-platform-2 follow-up — vault-bootstrap <id>: AXE Vaultwarden org 에 3 collection 생성 (<id>-macmini-local-accounts / frame-jwt-<id>-operators / <id>-deploy-secrets). BW_SESSION 요구, idempotent. github-team <id>: axelabs-ai/<id>-developers team + 3 core repo (blueprint/frame/hive) push permission. member 추가는 axe user add (별 PR). 둘 다 dry-run + confirm prompt. realchoice 가 첫 use case (CLI dry-run 검증 완료). | 2026-05-23 |
| D-dev-platform-4 | [email protected] 통합 GitHub identity (axe-labs-ai) — git/gh 단일화 — 본 머신 git config (global) = AXE Labs AI <[email protected]>, gh CLI active account = axe-labs-ai (PAT vault item github PAT: [email protected], Default collection). 모든 자동화 commit + push 가 단일 identity. 사용자 직원 (soohun.kang/taehun.kang macOS user) 은 자기 ~/.gitconfig 별도 (각자 본인 email/name). 기존 soohunkang gh account 도 keyring 잔존 — break-glass gh auth switch -u soohunkang 가능. org 권한: axe-labs-ai 가 axelabs-ai org Owner (team 생성 / collaborator add 자동화 가능). PAT 발급 정책 (fine-grained): Resource owner = axelabs-ai org (본인 X — 본인 발급은 org repo 접근 X); All repositories; Permissions = Repository (Contents R+W / PR R+W / Workflows R+W / Administration R+W / Metadata R) + Organization (Members R+W / Administration R+W). PAT TTL 90일 (만료 2026-08-21), rotation backlog (B-axe-pat-rotation-cron). 2FA 권장 (B-axe-labs-ai-2fa). | 2026-05-23 |
| D-dev-platform-5 | Cloudflare Zero Trust App + Free GitHub plan 결정 — D-dev-platform-1 미완 부분 마감 — Zero Trust: ssh.axe.axelabs.ai self-hosted App (b903d8cd-...) + Email domain policy (allow *@axellc.com, e10f3198-...). vault token Cloudflare API - axelabs (fae8ff16-...) 가 Account-scope Access Apps Edit 이미 보유 — 새 token 발급 불요 (notes 만 부정확했음). GitHub branch protection: Free plan 유지. 비용 0, invite-only collaborator + CODEOWNERS + PR template 으로 비전 충족 (강제 X — social norm + axe ship release-gate 가 main 직접 push blast radius 0). 향후 contributor 5+ 또는 외부 진입 시 Team plan / public 재평가. | 2026-05-23 |
| D-dev-platform-6 | axe vault unlock/lock/status — Keychain-backed bw session, ai@ stdin tty 영구 회피 — 이전: BW_SESSION 자주 만료 + 운영자가 매번 bw unlock --raw 출력 채팅 회신 + transcript 노출 + ai@ subprocess 의 stdin tty 문제로 bw CLI 호출 fail. 본 PR: axe vault unlock 가 운영자 1회 interactive unlock 후 session token 을 macOS Keychain (security add-generic-password -s axe.vault.session -a [email protected] -w ... -U) 에 push. _vault_env() 가 모든 ai@ subprocess 의 bw 호출 시 자동 fetch + --session flag 우회 패턴으로 stdin tty 문제 영구 회피. Keychain item 은 user 별 isolation (axe user 의 BW_SESSION 은 soohun.kang/taehun.kang user 접근 불가, macOS native ACL). bw serve 폐기 사유: UNIX socket / token auth 미지원 → TCP 8087 port 가 same-machine 다른 user 노출 risk. cmd_customers_vault_bootstrap 의 BW_SESSION env require 도 _vault_env() 자동 fetch 로 교체. | 2026-05-23 |
| D-host-setup-1 | 신설 /ops/host-setup page — macmini 13 layer SSOT — 비전: 10 customer macmini 운영 + 신규 macmini 1 page reference 로 reproducible. 본 머신 (axe-macmini) 의 모든 layer (Hardware / macOS tunables / users + dev group / sudoers / SSH / Tailscale + Cloudflare / Entra / Vault / Docker stack / launchd / CLI / Git / OneDrive / Backup) 와 그 현재 값을 1 page 에 정리. 본 세션의 D-dev-platform-1~6 + 이전 세션의 D-ops/D-config/D-hive/D-frame 결정 다 cross-ref. 본 page 는 read-only reference + drift detect SOT — 변경 시 = 운영 실패 / 함정 발견 trigger. 자동화 CLI (axe host bootstrap / axe host inventory) backlog. | 2026-05-23 |
| D-hive-backup | hive-postgres Tier A 백업 합산 — axe-backup 의 blueprint-postgres 블록 직후 hive-postgres pg_dumpall 블록 추가 (running check + size sanity + restic tag). Phase 1 (조직/휴가) + Phase 3 (payroll v2 axec/axev 실데이터) 운영 중인데 Tier A 0 이던 비대칭 해소. backup.mdx 시나리오 1c (hive DB 손상 복원) 신설. mysrt-postgres 는 SRT 외부 SOT 가능성 잔존 → 별도 결정 보류. | 2026-05-21 |
| D-docs-search-1 | Nextra 4 UI 검색 = Pagefind 정적 인덱스 — 빌드 후 pagefind --site .next/server/app --output-path public/_pagefind 별도 실행. Nextra 4 가 v3 와 달리 검색 UI (Search 컴포넌트) 와 인덱스 생성을 분리해 두어 next build 만으로는 검색박스가 “Failed to load search index.” 가 됨 (초기 scaffold 부터 줄곧 누락). /api/search JSON 엔드포인트는 LLM 용 자체 구현이라 무관 — UI 용 = Pagefind, LLM 용 = /api/search. | 2026-05-22 |
| D-bp-alert-1 | L2 silent-drop alert = AXE infra only (DB row + macOS launchd osascript). 외부 SaaS (Slack/Teams webhook) 모두 배제 — AXE 는 Teams 가 main comm 이고 Slack 미사용, 또한 Teams webhook 도 같은 Graph 종속이라 본 incident class 차단 불가. Channel 1 = OperatorAlert Prisma row + /axe/admin/system red banner (운영자가 dashboard 자주 보는 곳). Channel 2 = [OPERATOR-ALERT] console marker → host launchd com.axe.operator-alert-notify daemon 이 docker logs tail + osascript notification (Graph/Postgres 모두 무관, 운영자 mac 앞에 있을 때 즉시 push). 두 채널 독립 — DB 죽어도 marker 살아있고 vice versa. 본질·퀄리티·결과·안전 4-axis 동시 만족. PR #340 의 Slack webhook design 폐기 후 본 결정. | 2026-05-22 |
| D-docs-roadmap-1 | /ops/roadmap + /ops/backlog 분리, /ops/known-gaps 책임 좁힘 — 다른 세션이 들어왔을 때 무엇부터 할지가 5초 안에 보이도록 실행 큐 (backlog) 와 큰 그림 (roadmap) 을 별도 페이지로 분리. known-gaps 는 분석적 사실 기록만 유지 (entry point 아님). backlog lifecycle = 신규 / ready / in-progress / done. CLAUDE.md ritual 의 “known-gaps 단일 SoT” 를 “backlog = entry point + known-gaps = 함정/사실” 로 재정의. | 2026-05-22 |
| D-docs-updates-1 | /ops/updates 신설로 4-페이지 시간축 분리 — backlog (현재) + roadmap (미래) + updates (과거) + known-gaps (사실). updates 는 3 섹션: (1) Ship Log = 매 ship 한 줄 (Phase 1 수동, Phase 2 자동 hook), (2) Highlights = 사람 큐레이션 narrative (직원·외부 IT), (3) API feed = /api/changes JSON (LLM agent fetch). build 시점 scripts/generate-changes-json.mjs 가 git log → public/changes.json export — runtime container 에 git 바이너리 없는 (.dockerignore 가 .git 제외) 함정 회피. CLAUDE.md ritual 도 4-페이지로 확장. | 2026-05-22 |
| D-docs-playbook | playbook 표준 형식 박제 (## AI 요청 프롬프트 + step block + 표준 꼬리 2줄) — 행위 단위 페이지 (셋업/배포/공지/복구) 가 사람과 AI 양쪽의 단일 entry point. commit 18e99d8 (ssh-access 의 “맨 위 복붙 prompt block” 도입) 이 5/26 4 페이지 (ssh-access · vault-setup · claude-connectors · operator-broadcast) 로 정착 → 본 결정으로 /architecture/playbooks 박제 + 4 페이지 minor drift fix. 후속 라운드에 11 페이지 추가 playbook 화 (총 15) + frontmatter playbook: true + sidebar ⚡ prefix + scripts/generate-playbook-catalog.mjs (SSOT 표 자동 + public/playbooks.json deterministic export, prebuild 자동 hook + npm run catalog ad-hoc) 모두 본 결정의 확장. 2026-05-27 갱신: 헤딩 🤖 AI 에 던질 prompt (복사해서 본인 AI session 에 paste) → AI 요청 프롬프트 (이모지/괄호 제거, 한국어 명사구 통일). 15 페이지 + 본 SSOT 일괄 정합. 형식 drift = 사용자 mental model 재학습 비용 = 패턴 가치 훼손. | 2026-05-26 |
| D-docs-mdx-traps | mdx 함정 3종 박제 + scripts/verify-mdx-compile.mjs prebuild 자동 audit — playbook catalog 작업 라운드들에서 노출된 3 함정: (1) ## Heading {#explicit-anchor} MDX 3 acorn parse fail (기존 함정, 운영자 commit 941196f), (2) GFM 표 cell 의 backtick code 안 | 가 column separator 로 잘못 인식 → { mid-cell unclosed → acorn fail (known-gaps line 386 의 {reason: "a"|"b"|"c"} 패턴 노출 — webpack cache 가 보통 가려두지만 catalog script 의 SSOT 갱신이 page-map invalidate → fresh re-parse 시 노출), (3) plain text 안 <PascalCase> 가 close tag 없는 JSX 컴포넌트로 인식 → build fail (decisions.mdx line 315 의 Extension<AuthContext> 노출). 모두 /ops/known-gaps mdx 함정 섹션에 등재 + project_axelabs_docs_mdx_traps memory 갱신. 미래 재발 예방 = scripts/verify-mdx-compile.mjs (mdx-js + remark-gfm + remark-frontmatter compile 시도, fail file 별 정확한 line/col 출력) 가 prebuild hook 자동 + npm run verify:mdx ad-hoc alias. next build 의 webpack 모듈 단위 알림보다 정확한 anchor 제공. | 2026-05-27 |
| D-bp-mcp-3 | Blueprint MCP Postgres cutover 잔재 + startup probe — D-config-17 (Postgres cutover, 5/15) 가 blueprint-app 만 다루고 .env 의 DATABASE_URL="file:./data/blueprint.db" SQLite legacy line 그대로 둠. blueprint-mcp 는 그 .env 받아 SQLAlchemy ArgumentError: Could not parse SQLAlchemy URL deep in ASGI handler → 모든 OAuth-bearing 요청 500 → Claude.ai UI 가 “Authorization with the MCP server failed” 로 표시 → 운영자가 secret/Azure config 만 의심 (실은 무관). MCP launch (5/21) 부터 종일 broken, container 8h+ healthy 였지만 200 응답 0건. 4-axis fix: (1) 본질 — .env 의 stale DATABASE_URL line 제거 → compose default ${DATABASE_URL:-postgresql://blueprint:${BLUEPRINT_DB_PASSWORD}@postgres:5432/blueprint} 자동 적용. (2) 퀄리티 — config.get_database_url() 가 postgresql+asyncpg:///postgresql:///postgres:// 외 URL fail-fast (silent parse 대신 명확한 RuntimeError + remediation 안내). (3) 결과 — Claude.ai bearer → MCP 200, tools 실제 동작. (4) 안전 — Starlette lifespan 에 SELECT 1 startup probe — DB 미접속/URL malformed 시 lifespan fail → healthcheck fail → axe blueprint upgrade blue/green swap 거부 (broken container active 못 됨). 양파껍질 6번째 layer = “Postgres cutover env_file 잔재 + SQLAlchemy silent parse fail” → mcp-server-checklist 에 항구화. | 2026-05-22 |
| D-bp-ui-5 | Blueprint globals.css alias bridge 제거 — D-bp-ui-4 (PR #368) 가 모든 사용처를 새 토큰으로 마이그한 뒤 alias LHS 가 dead code. 본 PR 제거: @layer base :root 의 legacy var 9개 정의 (--navy/--navy-light/--navy-mid/--white/--gray/--gray-light/--dark-border/--green/--red) + @layer utilities 의 legacy class 5개 (.bg-navy/.bg-navy-mid/.text-gray-ax/.text-gray-light-ax/.border-dark). 보존: --accent-glow (41 tsx 사용, mode-invariant fixed RGB) + .font-display/.text-accent (새 토큰 직접 참조). 1 file, +7/-37. production deployed: --accent-glow:#e3ff6626 검증, legacy var 정의 0 hits. PR #370 — @axe/ui adoption 5-PR 시리즈 마무리. | 2026-05-22 |
| D-bp-ui-4 | Blueprint legacy var() → @axe/ui 토큰명 일괄 마이그레이션 — 50+ tsx 의 var(--navy)/--white/--gray/--gray-light/--dark-border/--green/--red/--navy-light/--navy-mid 를 sed in-place 로 새 토큰 (--bg-base/--text-primary/--text-muted/--text-tertiary/--border-subtle/--success/--danger/--gray-2/--gray-3) 로 1:1 rename. 57 files, +1645/-1645 (mechanical, semantic change 0). globals.css 의 alias bridge LHS 는 보존 — deprecation 단계, 다음 PR (D-bp-ui-5) 에서 제거. Tailwind utility class 이름 (.bg-navy/.text-gray-ax/등) 그대로 (사용처 무수정), globals.css 의 정의가 새 토큰 직접 가리키게 자동 갱신. sed 순서 유의 — -light/-mid 가 base 보다 먼저. PR #368. | 2026-05-22 |
| D-bp-ui-3 | Blueprint light/dark mode 토글 (cookie-backed SSR) — D-bp-ui-1 토큰 인프라 위 첫 user-visible 기능. next/headers 의 cookies() (async) 를 RootLayout 에서 read → <html data-theme={theme}> 결정. cookie 없으면 dark 기본. ThemeToggle client 컴포넌트가 토글 시 (1) document.cookie = 'theme=...; max-age=31536000; SameSite=Lax' 1년 persistence, (2) documentElement.setAttribute 즉시 교체, (3) state 갱신. 다음 SSR 도 같은 cookie 읽으므로 FOUC/hydration mismatch 없음. next-themes 의존성 미추가 — 65-line custom. /axe/settings General 섹션의 placeholder GeneralRow Theme=“Dark” 를 active 토글로 교체. TopNav 의 hard-coded rgba(26,6,16,0.92) → color-mix(in srgb, var(--bg-base) 92%, transparent) (modern color-mix Chrome 111+/Safari 16.4+/Firefox 113+). PR #367. | 2026-05-22 |
| D-bp-ui-2 | Blueprint Space Grotesk inline → @axe/ui font 토큰 — D-bp-ui-1 후속 cleanup. 8 tsx 의 14 inline fontFamily refs 를 토큰화. SVG <text fontFamily="'Space Grotesk', sans-serif"> × 8 (agents/org × 4, team/OrgChartView × 4) → var(--font-sans). React inline style fontFamily: "'Space Grotesk', monospace" × 6 (system prompt textarea — fallback monospace 가 의도 신호) → var(--font-mono) (Sarasa Fixed K). layout.tsx 의 Space Grotesk <link> 제거 (의존성 0). 시각 변화: SVG = Pretendard 로 전환, textarea = Sarasa Fixed K mono 로 전환 (한글 박스 정렬 회복). PR #366. | 2026-05-22 |
| D-bp-ui-1 | Blueprint @axe/ui 채택 + Tailwind 4 패치 2종 — Blueprint 가 /Users/axe/axelabs/src/lib/ SSOT 를 src/app/_axe-ui/ 로 sync. Blueprint 전용 패치: (a) dest path src/app/_axe-ui/ (canonical app/_axe-ui/ 와 다른 src/ source root), (b) fonts.css 의 @import url(pretendard/d2coding) strip — Tailwind 4 + Turbopack 이 토큰 파일을 globals.css 로 inline 한 뒤 위 @import 들이 다른 rule 뒤로 밀려 CSS spec 위반으로 브라우저 silent drop (Pretendard 미로드). 회피: sync 단계 strip + layout.tsx <link rel="stylesheet"> (Clash Display 와 같은 패턴 확장). 35+ tsx 의 기존 var(--navy)/var(--white)/var(--gray-ax)/--dark-border 는 globals.css 에서 var(--bg-base)/var(--text-primary)/var(--text-muted)/var(--border-subtle) 로 alias — 비파괴 자동 전환. <html data-theme="dark"> 기본, CSP style-src+font-src 에 cdn.jsdelivr.net 추가. predev/prebuild/check-axe-ui npm hook 으로 SSOT drift 감지. | 2026-05-22 |
| D-axe-ui-1 | @axe/ui 단일 배포 채널 = git+ssh + axelabs.ai dogfood — 디자인 시스템 (/Users/axe/axelabs/) 이 npm registry 없이 git+ssh 임포트 (pnpm add 'git+ssh://...axelabs#tag') 만으로 모든 React 소비자 (Blueprint · frame admin · hive admin · docs.axelabs.ai MDX) 에 전파. axelabs.ai 회사 홈이 자기 자신을 dogfood — axelabs.ai/ui 라이브 쇼케이스가 깨지면 즉시 인지. Verdaccio / GitHub Packages / npm publish 모두 미사용 — 소비자 ≤3개 시점에서 부담만. v1.0 시점 재검토. Clash Display <link> 패턴은 Blueprint 와 일치 (Fontshare CSS @import 의 f[]= drop 함정 회피). | 2026-05-22 |
| D-bp-artifact-1 | Blueprint artifact + PARA 지식 레이어 도입 — Blueprint 내부 meta-layer, MCPs 와 별 트랙 — Blueprint 의 typed fact unit (Artifact) 를 1st-class 도입. source 문서가 아니라 agent 가 직접 read 하는, 사전 추출된 사실 한 조각: per-field schema (versioned, schema_id FK) + per-field citation + confidence + audit_trail + paraLayer scope. MCPs (frame · hive · magnet · stream …) 는 domain-specific data system 으로 그대로 — Artifact 는 그 위에서 사실을 organize 하는 Blueprint 내부 meta-layer (별 트랙). Schema authority = MCP service — frame/hive/magnet/stream 등 각자 자기 도메인 schema 정의 + /schemas endpoint 로 노출, Blueprint 는 schema registry 아니라 discovery / mirror (fetch + cache + version). free-form 영역 (IC memo / Board / LP comm / Sector framework 등 MCP 권위 없는 영역) 은 LLM 자율 (extract 시점 ad-hoc typed structure 또는 freeform.llm@auto content + citation wrap). 별도 system naming 거절 — “Blueprint 의 artifact + PARA structure” 라고만 부름. 5종 artifact 유형 예시 (ICMemoArtifact PROJECT · BoardMeetingNotesArtifact PROJECT→ARCHIVE · LPCapitalCallArtifact PROJECT · SectorFrameworkArtifact RESOURCE · PortcoBoardKPIArtifact AREA) — 이는 schema 미리 정의 아니라 LLM 자율 또는 IC/ic skill 의 own structure (frame.balance / hive.employee 같은 MCP authority 예시는 /architecture/artifacts § Schema authority 분산). D-bp-entity-1 의 PARA 4 layer schema + D-bp-entity-2 의 workspace-level provenance 위에 field-level 로 진화. 상세 = /architecture/artifacts. | 2026-05-23 |
| D-bp-artifact-2 | Artifact 저장 = blueprint-postgres + JSONB content + JSONB citations — Primary storage 는 Blueprint Postgres 16 (D-config-16 cutover 완료) 의 신규 Artifact 테이블. 신규 인프라 0. 컬럼: id / schema_id (versioned schema FK) / workspace_id / entity_id (NOT NULL, D-bp-entity-3 정합) / para_layer (PROJECT/AREA/RESOURCE/ARCHIVE) / content (JSONB, schema 별 typed field) / citations (JSONB array) / confidence (numeric) / audit_trail (JSONB, propose/review/confirm/edit event 누적) / parent_artifact_id (PARA dispatch fork chain) / created_at / updated_at / archived_at (soft archive). Index: workspace / (entity, paraLayer) / schema / parent. content + audit_trail 무결성은 pg_jsonschema extension (확장 add-on) 으로 가드. 상세 schema = /architecture/artifacts § 데이터 모델. | 2026-05-23 |
| D-bp-artifact-3 | Citation = 외부 source 의 stable ID — Artifact 의 citation 은 stable ID 만 보유, data 중복 0. 6 kind: (1) onedrive (drive_item_id + version + sheet/page/cell), (2) frame.* (entity + account/period + queried_at — 회계 도메인 typed query snapshot), (3) hive.* (entity + employee_id + queried_at — HR 도메인), (4) teams.message (chat_id + message_id), (5) mail.thread (thread_id + message_id), (6) external.web (url + fetched_at snapshot 시점). large content / 원본 파일은 모두 원위치 유지 — OneDrive driveItem, frame DB resolution/journal IDs, hive DB employee IDs, Teams chat+msg, mail thread+msg, external API. artifact 가 frame query_balance 결과를 복사하지 않고 citation 으로만 가리킴 → resolver (src/lib/artifact/citations/) 가 fetch + cache. Trinity dashboard (Workspace × OneDrive × TeamsChat GUID 3축) 의 field-level 진화. | 2026-05-23 |
| D-bp-artifact-4 | monolith first — 별도 KnowledgeStore service 분리 안 함 — Blueprint 가 이미 PARA OS home 이고 per-customer macmini isolation (D1) 안에서 Blueprint Postgres 동거가 자연. 추출 필요 시 나중. 대안 거절: Graph DB (Neo4j) — citation chain 이 graph 형태이긴 하나 query workload 가 hash-lookup + filter 위주, Postgres JSONB GIN 으로 충분 / Vector DB (Qdrant) primary — primary 로 두면 transactional consistency · FK · audit trail 다 잃음, semantic search 필요 시 pgvector extension / ClickHouse — analytical OLAP 강점은 있으나 artifact row count < 100k 수준의 transactional workload, column-store 이점 X / MongoDB — JSONB 동등 + sharding 미필요 + frame/hive Postgres 일관성, 별 DB 추가 부담 / SQLite per-workspace — multi-agent concurrent read-write 미적합. 확장 add-on (필요 시): pgvector (semantic search) · pg_jsonschema (JSONB validation) · temporal_tables 또는 timestamptz column (time-travel). 상세 비교 = /architecture/artifacts § 저장 결정 + 대안 비교. | 2026-05-23 |
| D-bp-artifact-5 | ctx skill 진화 — markdown PKM → artifact curation interface + markdown 점진 migration — 기존 ctx (agent session delta → markdown append + parent KB propagation) 를 artifact store 의 curation interface 로 재정의. 대체 아님 — 진화. 흐름 4 단계: source → ingest → typed extraction (schema-aware LLM extract) → proposed fact (audit_trail: { event: "propose", actor: "agent", ... }) → ctx review (user confirm / edit / reject, UX 가 markdown diff + citation hover) → confirmed fact + audit (audit_trail: { event: "confirm", actor: "user", ... }) → agent reads via Query API (grep 안 함 — typed query + citation chain). markdown 은 보조 — review UX 의 diff 표면 + Archive 보존 + emergency cat (DB 장애 시). primary read path 는 Query API. 기존 ctx sync / ctx review / ctx status 3 mode 호환 유지 + artifact 모드 추가. 기존 markdown KB 는 점진 migration (항목별 AI-assisted ctx review) — mass migration 안 함, 1 entry → LLM extract → proposed artifact → ctx review (사용자 confirm/edit/reject) → confirmed artifact + 원본 markdown entry <!-- migrated: <artifact_id> --> archive 마킹. 신규 fact 는 처음부터 artifact (markdown append 안 함). 5 단계 migration 흐름 상세 = /architecture/artifacts § Markdown → artifact 점진 migration. | 2026-05-23 |
| D-bp-artifact-6 | Workspace lifecycle + Dispatch ≠ Close 분리 — Workspace.paraLayer 가 PROJECT / AREA / RESOURCE / ARCHIVE 4 layer transition 관리. (a) Dispatch (anytime activity) — Project 진행 중에도 artifact/file 을 다른 Workspace 로 흘려보냄. 2 mode: copy (새 artifact row, parent_artifact_id chain, source frozen) vs link (artifact_link 테이블 — multi-parent reference, 원본 1 row 만 유지). (b) Close (한 번) — workspace.paraLayer 한 번 변경 (PROJECT → ARCHIVE 보통, 또는 reclassify 다른 layer 로). (c) Reclassify bidirectional 모두 허용 (ARCHIVE → AREA resurrect 포함, audit_trail 보존). (d) Area cardinality = function 당 1개 — 중복 function Area 만들기 시 UI/LLM 가 “기존 Area 와 merge 권고”. (e) AXEV 현재 Area 4 (Finance / BOD / Legal / License (AC 관련)), Resource = 0 (naturally emerge). (f) OneDrive 폴더 mirror — trinity-sync 가 2 시점 처리: dispatch 별 file 부분 이동 + close 시 workspace 전체 이동. (g) artifact_link 테이블 신설 (PK (artifact_id, workspace_id), link_type ∈ {'reference','sub_project','cross_link'}, linked_at + linked_by). 상세 = /architecture/artifacts § Workspace lifecycle. | 2026-05-23 |
| D-bp-artifact-7 | LLM 호출 = Max plan OAuth 우선, Access = MCP tool 위주 (REST 최소화) — 2 layer 정책. Layer 1 (LLM 호출): artifact extraction / propose / review 의 LLM 호출은 Claude Code OAuth token (Anthropic Max plan) 우선. 비용 측면 per-token incremental 0 (Max plan 무제한 활용). Anthropic API direct (per-token billing) 은 fallback / 대량 batch 만 — 한 번에 1000+ extraction, vision-heavy ingest (pdf/pptx 대량 — 기존 vision_ingest.py 패턴 확장). Surface = Teams bot · 로컬 Claude Code CLI · 웹 (Max session) 모두 same OAuth. Layer 2 (Access): artifact access 의 primary layer = Blueprint MCP tool (query_knowledge / get_artifact / dispatch_artifact 등). 별도 REST endpoint proliferation 회피 — 웹 UI 만 최소 REST (curation UX 용). 외부 agent 도 Blueprint MCP via OAuth-RP (D-bp-mcp-1). 권한 모델 single source (D-bp-entity-17 EntityRole). 상세 = /architecture/artifacts § LLM 호출 + Access 정책. | 2026-05-23 |
| D-index-1 | index 서비스 inception — AXE Labs 7번째 vertical, 투자 도메인 MCP backend — 펀드 운용 lifecycle (소싱 → 실사 → IC → 포트폴리오 → 엑싯 → LP IR) 의 typed fact SoT. frame (회계) / hive (HR) 와 같은 layer. Blueprint 의 ic / due-diligence / vc-deal-sourcing / portfolio-management / investor-relations 5 skill 의 dataroom-bound run-time 산출물 (memo markdown / xlsx / yaml) 을 persistent typed artifact + cross-deal / cross-fund / time-travel query 가능한 형태로 승격. 상세 = /services/index. 5 skill 자체는 작성/조사 workflow 로 유지 — index 는 사실의 보존소. | 2026-05-27 |
| D-index-2 | Rust + axum + sqlx + Day 1 artifact-first (D-cortex-7 precedent) — cortex 의 5-table reference 1:1 미러: artifact / citation / artifact_event / mcp_schema + owner_id RLS + index_app NOSUPERUSER role + SET LOCAL index.actor GUC. 사유 (D-cortex-7 4 사유 그대로): (a) artifact 의 discriminated union 과 Rust algebraic type 자연, (b) sqlx compile-time SQL check 가 schema/RLS query 안전성 보강, (c) MCP HTTP 서버 long-running 단일 process 의 panic-free 적합, (d) 단일 binary. Anthropic SDK 부재 → reqwest direct (/messages endpoint). MCP checklist 의 “Python+FastMCP 통일” 은 frame/hive/blueprint mcp 의 양파껍질 회고 기반인데 cortex/matrix 가 이미 break 한 precedent. 시작 = cp -R /Users/axe/cortex /Users/axe/index byte-by-byte. | 2026-05-27 |
| D-index-3 | schema-per-entity Postgres 16 + 40xx 포트 범위 — frame/hive 패턴 1:1 미러. Shared schema (deal · target_company · fund_investment · sector_framework · lp_master · cross_journal_link · mcp_schema_version) + per-entity schema (financial_model · financial_driver · financial_scenario · financial_driver_value · financial_output · exit_matrix · dd_finding · ic_decision · portfolio_kpi_snapshot · risk_alert · valuation_snapshot · exit_signal · lp_communication · postmortem · audit_log). 펀드 entity (axe_ia_001 / axe_ia_002 / …) 가 frame 의 shared.entity 와 정합 — frame 이 register 한 펀드를 index 가 그대로 사용. 포트: 4000 postgres / 4010 mcp blue / 4011 green / 4012 axe-index-proxy Caddy. cloudflared ingress ^/index(/.*)?$ → host.docker.internal:4012. 39xx (matrix) 다음 40xx 자연 할당. | 2026-05-27 |
| D-index-4 | financial_model 6-table SoT + DSL formula + topo evaluator + xlsx round-trip — 본 결정의 핵심. ic skill 의 ic/finance/{3fs_*,scenario_deltas,exit_matrix,irr_analysis,irr_cell_ref,prob_tree,assumptions} 산출물을 DB 1st-class entity 로 승격. 6 테이블: (1) financial_model (deal 별 1+, time_axis + horizon + locked_at + source_xlsx_sha256 + OneDrive citation), (2) financial_driver (code · kind=input/derived · formula DSL · dependencies[] · units · category), (3) financial_scenario (Upside/Base/Mgmt/Downside · probability · driver_changes narrative · parent_scenario_id fork chain — ic skill 의 PR-J 4-scenario 정합), (4) financial_driver_value (model × driver_code × scenario × period_index → value · is_override · PK 4-tuple), (5) financial_output (irr/moic/npv/exit_val/runway × scenario × period → value + inputs_hash sha256 reproducibility), (6) exit_matrix_leaf (scenario × timing × path × probability × proceeds_krw × axe_proceeds_krw + dead_leaf/concentration flag — ic skill check_exit_matrix.py 결과 캐시). DSL = Rust pest parser + 10 operators (+ - * / min max if sum avg lag(driver,n) growth) — xlsx formula 100% 가 아닌 IC memo cite 80~90% 만. Topo evaluator (petgraph topological sort) — driver DAG cycle detect + period 별 순차 평가. compute_outputs(model_id, scenario_id) 호출 시 driver_value snapshot sha256 → output row 의 inputs_hash 저장 → driver_value 변경 시 재산출 trigger. Round-trip: ingest_financial_model_xlsx(deal_id, blob) LLM-assisted (Anthropic Sonnet 으로 sheet/cell pattern → driver tree extract) + export_xlsx(model_id, scenarios[]) 재생성. ic skill 의 4 sanity check (xlsx_integrity / check_arithmetic / check_exit_matrix / citation_trace) 가 validate_financial_model MCP tool 의 DB CHECK + trigger 로 흡수. 상세 = /services/index/financial-model. | 2026-05-27 |
| D-index-5 | fund_investment(deal × fund × round) — 3차원 N:M — 본 결정의 두 번째 핵심. deal 은 회사 단위 thesis, fund_investment 는 cash flow + IRR carrier. 한 deal 이 (a) 여러 펀드에서 동시 투자 (Iippo: 액스 투자조합 1호 99,998,912 + 2호 100,000,000 = 199,998,912원, RCPS), (b) 다른 시점 follow-on round (Canopy: 1호 BW 5억 + v5 신주 follow 1억), (c) 다른 instrument (RCPS / BW / SAFE / 신주 / 보통주) 로 들어갈 수 있음. fund_investment row = (deal_id, fund_entity_id, round_label, instrument, committed_krw, invested_krw, share_count, price_per_share, entry_date, paid_date, pre_money_krw, post_money_krw, ownership_pct_post, ownership_pct_fd, status [committed/paid/locked_in/written_off/exited], conditions[] JSONB [Iippo 2호 의 6 PCC 같은 trigger]). financial_model 은 deal 레벨 (회사 projection 은 fund 무관 동일) — financial_output.axe_proceeds 산출 → fund_investment 의 ratio 로 split → 펀드별 IRR = irr(committed_at_paid_date, axe_proceeds × fund.ratio_in_round) 독립 산출. 사용자가 강조한 “Sentry 1호/2호 IRR 각각 관리” 의 본질이 fund_investment row 분리로 자연 해결. 실 데이터 검증: Iippo 가 1:N (1 deal × 2 fund) 의 cleanest 케이스. 상세 = /services/index/financial-model § fund_investment. | 2026-05-27 |
| D-index-6 | Blueprint citation kind 7번째 index.* 추가 + Workspace ↔ deal N:1 — Blueprint artifact 의 citation kind (D-bp-artifact-3 의 6 kind: onedrive · frame.* · hive.* · teams.message · mail.thread · external.web) 에 7번째: index.* (deal · fund_investment · dd_finding · ic_decision · portfolio_kpi · financial_model · financial_output · valuation · lp_comm · risk_alert · exit_signal · postmortem · sector_framework · target_company). 예: IC memo 한 줄 “base case IRR 23.4%” 의 citation = { kind: "index.financial_output", model_id, scenario_id: "base", output_code: "irr", computed_at }. xlsx cell ref 가 아닌 DB cell ref 가 primary citation unit. Workspace ↔ deal = N:1 — Blueprint workspace 는 sector deep-dive / pipeline / fund container, deal 은 그 안의 artifact subject. ic skill 의 현재 “workspace.name = deal name” 가정 폐기 — deal-name-as-tag 로 진화. ic skill 폴더 규약 ic/memo/v{N}.md → ic/deal/<deal_id>/memo/v{N}.md 로 재구성 (workspace 단위 → deal 단위 artifact 그룹). 한 deal 의 IC convergence 후 별도 workspace spin-off 가 자연스러우면 link mode dispatch (D-bp-artifact-6) 로 cover. | 2026-05-27 |
| D-index-7 | /index/schemas 권위 + 14 schema set 노출 — frame /frame/schemas 13 schemas + hive /hive/schemas 15 schemas 패턴 1:1 미러. SoT = src/index/mcp/schemas.rs (Rust 측, cortex 의 schemas.rs 미러). 첫 노출 14 schemas: [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected] · [email protected]. Blueprint 의 B-bp-artifact-schema-discovery fetcher 에 index entry 1 줄 추가만으로 자동 mirror. 인증 required (JWTAuthMiddleware, frame 동일), versioning @{version} suffix, envelope { version, service, schemas }. 상세 schema spec = /services/index/schema-catalog. | 2026-05-27 |
| D-index-8 | 5 skill → service 진화 + --push-to-index mode — ic / due-diligence / vc-deal-sourcing / portfolio-management / investor-relations 5 skill 의 진화 경로. skill = workflow (LLM-heavy authoring), index = persistence (typed fact). (1) vc-deal-sourcing pipeline 표 → index.deal + deal_stage_history (영구 + cross-fund pipeline view), (2) due-diligence 체크리스트 → index.dd_finding + citation chain (finding 한 줄마다 출처 trace), (3) ic skill 의 18-section memo + 19-agent orchestration → run-time orchestration 유지 (LLM heavy 본질), 산출물은 index.ic_decision typed fact 로 push. memo PDF 본문 = OneDrive citation, scenario / IRR / postmortem cadence = typed field, (4) portfolio-management KPI 표 → index.portfolio_kpi time series (QoQ chart, cross-portfolio benchmark), (5) investor-relations LP 보고 → index.lp_comm audit + LP 별 last-contact view + AXE 펀드 성과 지표 자동 산출 (frame lp_master 결합). 새 mode = /ic --push-to-index — IC convergence (/ic --finalize 와 평행) 후 산출물의 typed fact 부분 (deal / ic_decision / financial_model / dd_finding / risk_alert / exit_matrix_leaf) 만 index 로 propose. ctx review queue 경유 후 confirmed → audit trail 시작. 기존 markdown / xlsx / yaml 산출물은 OneDrive 그대로 보존 (citation source). 상세 = /services/index/skill-evolution. | 2026-05-27 |
| D-index-9 | frame cross_journal_link mirror (펀드 회계 자동) — index 의 fund_investment event (commit / paid / valuation change / exit) 가 frame 의 commitment_ledger (D-ops-22 펀드 회계 확장 — [B-frame-fund-commitment-ledger](/ops/backlog)) 와 자동 mirror 분개. 채널 = pg_notify (index_events 채널 from index-postgres → frame-postgres LISTEN, hive payroll → frame 패턴 1:1 미러). 이벤트: (a) fund_investment.paid → frame 의 capital call 분개 + commitment_ledger 차감, (b) valuation_snapshot.recorded → frame 의 평가손익 분개 (분기), (c) fund_investment.exited → frame 의 distribution 분개 + LP 비례 carry 산출. cross_journal_link 테이블 (D-ops-22) 가 양쪽 journal pair 무결성 보장. 이는 frame M2 (펀드 회계 확장) 의 자연스러운 client — index 가 frame 의 펀드 회계 trigger SoT. 단, M2 미완 시 index 는 stand-alone 가동 (event_outbox 보류 + 운영자 수동 review). | 2026-05-27 |
| D-index-10 | 3 deal validation fixture (Iippo / Sentry / Canopy) — Phase 0 acceptance bar — Phase 0 의 schema correctness 검증 fixture. 3 deal 모두 ic skill 의 완전한 산출물 보유 (memo v4~v8 / scenario_deltas yaml / exit_matrix yaml / 3fs xlsx / irr_analysis xlsx / assumptions md). Iippo (킵코퍼레이션, AXE 199,998,912원 = 1호 99,998,912 + 2호 100,000,000, 1:N pure case) + Sentry (센티넬딥액티브, 1호 단독 5억 + 후속 5억 pro-rata option, 1:1 + future option case) + Canopy (사이클로이드, 1호 BW 5억 + v5 신주 follow 1억, multi-round 1:1×2 case) 가 fund_investment 의 3차원 N:M 을 모두 cover. Phase 0 acceptance bar: (a) 3 deal 모두 seed yaml → index DB ingest 성공, (b) ic skill 의 기존 irr_analysis xlsx 산출 IRR 과 index compute_outputs(model_id, scenario_id) 결과가 ±1pp 일치, (c) Iippo 1호 / 2호 IRR 각각 산출 가능 (별도 row), (d) Blueprint artifact 의 ICMemo citation = { kind: "index.financial_output", ... } 로 정확 resolve. seed yaml SoT = /Users/axe/index/seeds/{iippo,sentry,canopy}.yaml (본 결정 PR 의 산출물). | 2026-05-27 |
| D-index-11 | ic skill --push-to-index = 5th mode (FINALIZE 평행 track), atomic propose — ic skill 의 기존 4 mode (INITIAL / REVISION / APPEND / FINALIZE) 에 5번째 mode 가 sequential 추가 아닌 parallel track. FINALIZE 는 ic/final/ lock + PDF deliverable (OneDrive deliverable lifecycle), --push-to-index 는 typed fact persistence (DB lifecycle) — 둘이 같은 commit 에 묶이면 partial failure 시 rollback 모호. Step 5.5 신규 삽입 (gate_memo.sh PASS → humanize Stage 4 → pdf_quality 다음, postmortem_stub.py 자동 호출 이전): --push-to-index 플래그 detect → atomic propose_deal_closure(deal_id, version, idempotency_key, payload) 단일 batch MCP call → index 측 1 transaction 으로 모두 INSERT or all-rollback. 개별 propose tool 금지 (financial_model 따로, ic_decision 따로 → 중간 실패 시 inconsistent state). 산출물: ic/index_push_state/v(N).json (model_id, status, timestamp, errors 배열 포함) local checkpoint — retry-safe. 변경 범위 = SKILL.md (40-60 lines, Rule 25 + Step 5.5) + gate_memo.sh (80-120 lines post-V_COUNT) + orchestration-initial.md (50-80 lines) + build_3fs.py + calc_irr.py + postmortem_stub.py (~30-50 lines). 총 ~280-350 lines, backward compat 100% (flag 미사용 시 기존 동작). REVISION mode 와의 상호작용 = v(N+1) push 시 v(N) model 자동 supersede (last-write-wins, audit_trail 가 prior 보존). | 2026-05-27 |
| D-index-12 | pmc (Post-Money Care) skill 신규 + portfolio-management / investor-relations 통합 3-phase deprecation — IC 결의 ~ Exit/WD (5년+) 수명의 포트폴리오 케어 단계 통합 도구. 기존 2 prompt-only skill (portfolio-management 60 lines + investor-relations) 가 개념은 정확하지만 실행 불가 (KPI 정의표만, 추출/저장/푸시 워크플로 없음) — pmc 가 둘을 흡수해 실행 가능 도구화. ic skill 의 19-agent 보다 lighter (8 sub-agent: data-fetcher / kpi-extractor / risk-alerter / valuation-updater / exit-signal-analyzer / board-pack-drafter / lp-comm-drafter / index-payload-composer). Trigger 3-tier: SCHEDULED (분기 첫 영업일 09:00 KST launchd) + EVENT (red alert 자동 fire — runway 6M 미만 / 핵심 인력 이탈 / burn_spike 30 percent 초과 / growth_slowdown / customer_concentration) + MANUAL (/pmc --postmortem-fill 또는 --board-pack 또는 --push-to-index). 폴더 = deal 폴더 안 pmc/ 하위에 board_packs · kpi (snapshots + time_series) · risk/alerts · valuation/quarterly_nav · exit_signals · lp_comms · postmortem · index_push · cross_deal · manifests 10 sub-dir. Postmortem 2-track 분리: ic skill 의 postmortem_stub.py + postmortem_reminder.py 는 유지 (gate_memo.sh PASS 시점 stub 생성 + launchd daily reminder = investment phase 책임), pmc 는 fill + render + push (scripts/render_postmortem.py 이관 + index.postmortem propose = portfolio care phase 책임). references/postmortem-cadence.md 는 양쪽 symlink (single SoT). 3-phase 마이그레이션 (D-bp-artifact-5 점진 패턴): Phase 0 (즉시) — pmc SKILL.md skeleton 등록 + portfolio-management/investor-relations 에 deprecation notice / Phase 1 (M7 Phase 1 launch 후) — 첫 quarterly cycle 실행 + KPI catalog → pmc/references/kpi-catalog.md copy + IR lifecycle → pmc/references/ir-lifecycle.md copy / Phase 2 (3개월 후) — 기존 skill SKILL.md soft-deprecate (Blueprint suggest 에서 pmc 로 redirect) / Phase 3 — 완전 archive. 5 risk_alert.kind enum 영구 freeze (DB CHECK constraint): runway_under_6m / key_personnel / growth_slowdown / burn_spike / customer_concentration — portfolio-management SKILL.md 표 1:1 mirror, 새 kind 추가는 schema version bump 필요. | 2026-05-27 |
| D-index-13 | Atomic propose_deal_closure MCP tool + idempotency key UUIDv5 — ic skill 의 --push-to-index 가 financial_model / ic_decision / fund_investments / dd_findings / exit_matrix_leaves / risk_alerts 를 개별 propose call (6+ round-trip) 시 partial failure risk: 첫 4개 commit 됐는데 5번째에서 schema validation fail → DB inconsistent state. 대신 단일 batch MCP tool propose_deal_closure(deal_id, version, idempotency_key, payload) 가 index 측에서 1 DB transaction 으로 묶음. 모두 INSERT or ROLLBACK (PostgreSQL serializable isolation). Idempotency key = UUIDv5(namespace=deal_id, name="<version>::<content_sha256>::<actor_email>") — ic skill gate_memo.sh 가 매 호출 동일 input 으로 동일 key 생성, DB UNIQUE constraint (idempotency_record 테이블, 24h TTL — hive idempotency_record 패턴 미러) 가 2nd call 을 409 + context.prior_actor 반환. silent re-insert 절대 금지 — 두 ic session 이 동시 fire 시 race detect 됨. retry-safe: same input → same key → cached response. 본 tool 이 D-index-11 의 atomic Step 5.5 의 server-side counterpart. Phase 0 의 핵심 MCP tool (Phase 1 후속이 아닌). 비유: hive 의 payslip_send_log 의 body_sha256 immutable 패턴 + frame 의 idempotency_record cross-entity dedup + post_journal 5계층 검증 의 atomic 정신 결합. | 2026-05-27 |
| D-index-14 | 3-layer error model + IndexError Rust enum SoT — skill → index 모든 통신 에러는 일관 3-layer 표현. L1 HTTP: 401 Unauthorized + WWW-Authenticate: Bearer resource_metadata="..." (RFC 9728 정합, frame/hive 패턴). L2 MCP body: JSON envelope with error.code (영구 freeze enum, 예: FINANCIAL_MODEL_CONFLICT) + error.message + error.context (case 별 typed payload: prior_actor / prior_ts / idempotency_key 등) — silent fail 금지. L3 skill handler: ic skill 의 lib/index_client.py (또는 pmc 의 동형) 가 code 별 deterministic 분기 — IDEMPOTENCY_CONFLICT → 기존 표시 + retry 차단 / VALIDATION_WARNING → ctx review 사용자 confirm 요청 / SCHEMA_NOT_FOUND → upgrade hint / SERVICE_UNAVAILABLE → 로컬 drafts 보존 + retry 안내 / CITATION_RESOLVE_FAILED → cached value + warning chip. SoT = src/index/error_model.rs Rust enum (12+ case, deterministic mapping to McpErrorResponse) — silent fallback 금지. skill code 측 매칭 spec 은 본 enum 의 변경 시 deterministic 갱신 (test fixture 가 매 PR 검증). 양파껍질 (D-bp-mcp-1) layer 2 (“진단 불가, 모든 401 같은 메시지”) 의 영구 차단. | 2026-05-27 |
| D-index-51 | index-postgres 백업 (evidence_blob = 죽은 딜 SOLE 사본 보호) + teardown 역연산·감사 1급화 — frame reciprocal audit 마감 — D-index-50 teardown 으로 10 딜(Sendy·apposter·dayton·DeveloperGroup·Eduon·JsEnl·Medistaff·Novachips·OpenResearch·Catalyst)의 OneDrive 원본 + 2 Blueprint workspace 삭제 → evidence_blob(328 blob/312MB)가 그 ~350 파일의 유일 사본. frame 이 index 의 Archive 설계를 reciprocal audit (A RLS gate · B dedup race · C size cap · D backup · E cross-deal refcount) → 2 FAIL 발견·마감. D (P0 — backup gap): index-postgres 가 axe-backup scope 에 부재 = SOLE 사본인데 백업 0 → 단일 실패점에서 영구 소실 가능. 수정 = axe-backup 에 index-postgres pg_dumpall 블록 추가 (매일 03:00 launchd · 312MB bytea → ~633MB hex dump → restic content-dedup 으로 ~274MB stored, snapshot 21735e04 즉시 검증). C (size cap): register_evidence_blob 에 512MiB 상한 가드 — bytea 1GiB hard-limit 아래에서 LOUD fail (초과 미디어는 inline 금지·citation 참조). E (refcount + CAS 불변): cross-deal dedup blob 은 deal_evidence→evidence_blob FK(NO ACTION)가 참조 중 blob 삭제를 물리 차단 + teardown 이 index 행 미삭제로 안전. ⚠ 단 evidence_blob 의 D-index-50 마이그는 SELECT/INSERT only 를 명시 의도(주석 “UPDATE/DELETE grant 없음”)했으나, 스키마 전역 ALTER DEFAULT PRIVILEGES … GRANT … ON TABLES TO index_app(20260526210000 artifact 스키마)가 이후 생성된 모든 테이블에 UPDATE/DELETE 를 자동 과다부여 → evidence_blob·teardown_event 도 열려 있었음(코드 INSERT-only 라 live 위반 0, by-construction 보장 아님; frame-audit E 의 “DELETE grant 부재” 기술은 당시 부정확) → migration 20260606000004(evidence_blob)·20260606000005(teardown_event) 로 UPDATE/DELETE 회수, evidence_blob = append-only(INSERT/SELECT only) — content 불변 + index_app blob 삭제 불가가 by-construction. ★향후 orphan GC 는 별도 권한 role + refcount-guarded (DELETE blob WHERE NOT EXISTS (SELECT 1 FROM deal_evidence …)) 필수, per-deal 즉시삭제 금지. reversibility (역연산 1급화): ① export-evidence CLI (evidence_blob → 파일 복원 + 디스크 재-hash 로 sha256 round-trip 검증, Sendy 28/28 0-mismatch) = teardown 이 되돌릴 수 있음을 증명. ② teardown_event audit 테이블 (deal·file_count·total_bytes·onedrive_path·blueprint_workspace_id·sha_manifest recovery map [{path,sha256,kind,size}], RLS owner_only) = “무엇을 언제 어디서 떼어냈고 무엇으로 되돌리나” 의 단일 기록. axe CLI 1급화: axe index teardown/restore/list-archive — cross-service orchestrator (폴더해소 = Blueprint Workspace.drivePath 권위 → host index ingest-evidence archive → sha byte-for-byte verify → capture-first OneDrive rmtree + Blueprint workspace 삭제 → teardown_event insert). 경계 = axe CLI(orchestration·삭제·감사) · index binary(archive·restore·무결성). D-index-50 bin/deal_teardown.py 스톱갭을 흡수. 108 test. | 2026-06-06 |
| D-index-50 | Archive — 죽은 딜 evidence-blob 저장 + post-mortem schedule (Sendy 첫 완전 teardown) — 2026-06-06 사용자 “Sendy 딜 저장·Resource화 후 종결 — OneDrive 폴더+Blueprint workspace 제거, 모든 파일 index 저장(frame evidence 참고), post-mortem 시점+할일”. D-index-49 dead-deal-harvest 의 §8 dispatch 실현: raw 파일=폐기가능·추출지식=영구, 폐기 전 흡수. gap: index 가 citation(OneDrive 경로 포인터)만 보유 — 폴더 삭제 시 orphan. 구현 (2 subsystem, frame evidence 패턴 mirror·bytea 백엔드): ① evidence_blob(sha256 PK · content bytea=파일 bytes 자체 · content-addressed dedup, CAS RLS 없음) + deal_evidence(owner RLS 링크 · kind 7종 · original_filename · source_path provenance). bytea 선택 이유 = blue/green ephemeral 컨테이너 + 단일 DB 백업이 파일까지 커버(filesystem volume 불요·frame 과 차이). get_evidence_blob 은 deal_evidence(RLS) JOIN 접근통제. ② postmortem_schedule(deal · kind postmortem/revisit/follow_up · due_at · trigger_condition · action_items · status 상태머신) — PASS 결정의 disconfirming evidence 를 후일 actual 대비 검증. tool 6(register/list/get_evidence + schedule/list/update_postmortem, 29→35) + CLI ingest-evidence. Sendy teardown: 28 파일(6.6MB) 전부 흡수(dataroom 2 PDF+4 extract · finance 7 · ic_memo v2 · kb 6 · decision_log 8) + financial_model citation durable anchor(memo sha256) + post-mortem revisit(Q4 2026: sendyX 20+ customer·₩100M ARR / Series C ≤250억 / BEP). 검증(subagent, 삭제 전 게이트): 28/28 sha256 byte-for-byte(file↔store set-diff·in-DB content==hash·4.8MB PDF round-trip) · 의사결정 blob+typed fact 이중 보존(PASS사유·v1→v2 재calib·95.7%→35% IRR override 버그+버그파일 verbatim·post-mortem) · OneDrive 의존 0(self-contained) → SAFE TO DELETE. 108 test. ⭐ 부수: frame 에 bytea-vs-filesystem durability 통찰 전달(storage_url 가 백업·컨테이너 재빌드 survive 하나 점검). | 2026-06-06 |
| D-index-49 | 죽은 딜 harvest — why-passed/lost JUDGMENT 1급화 (deal_judgment artifact kind, dead-deal-harvest Pillar A) — 2026-06-06 사용자 “죽은 딜 저장이 본질” (Blueprint PARA 철학 §11). live deal 관리는 moat 아님 — VC 지식의 압도적 대부분은 죽은 딜(시장맵·경쟁분석·섹터 thesis·valuation + 무엇보다 judgment “왜 패스했나”)에 있고 보통 Archive 에 묻혀 증발 → 복리 corpus 로 harvest. gap (recon): index 는 이미 passed/dead 를 상태로 1급 저장(deal.stage Passed/Writedown · 357 artifact)하나 judgment 층이 전부 finance-centric(왜 숫자가 그런가 — assumption/calibration). thesis-level 처분 JUDGMENT(“왜 passed/lost/died” + durable lesson, 예 Apposter “good company ≠ good deal”·“field DD kills thesis”, Interstellar “high IRR ≠ Go”)은 seed 주석·fund_investment.conditions 문자열에만 = typed·queryable 아님. 인식론 분리 (§0): claim(source-cited·live·갱신 = extracted_metric) vs judgment(author·불변·supersede) — deal_judgment 가 후자의 1급화. 구현 (D-index-45/46 기계장치 순수 확장, SQL migration 0 — store generic·kind/relation CHECK 없음): ① 새 artifact kind deal_judgment(payload deal_code/verdict/thesis/rationale[]/lesson/sector/tags[]/decided_by/at), FROZEN verdict passed/lost/dead/invested/missed, content-identity deal_code·verdict, para_layer=area. ② 새 FROZEN artifact_ref relation supersedes(judgment 불변 — 재판단=새 artifact 가 prior supersede). ③ seed judgment: block 에 verdict: 확장 (seed=SoT) → seed-ingest --emit-judgment 가 emit (index_field(deal,"deal:disposition") citation + 선택 corpus backing anchored_to, confirm-on-create, content-identity idempotent). ④ schema index.deal_judgment (/index/schemas active 14→15) + verdict frozen-enums 발행 → frozen_enums_hash 재-pin (D-index-47 drift tripwire 정상 발화). ⑤ read tool query_dispositions(verdict/sector/tag 횡단)·get_deal_judgment(단일 deal) — 27→29 tool. ⑥ 23 canonical seed 전체 verdict backfill (헤더/conditions 에서 TRANSCRIBE, 4 병렬 agent + orchestrator QC, 날조 X) → 16 passed + 6 invested + 1 lost. 검증 (additive-only 3중 증명): store 357→380(+23 deal_judgment), 재ingest 시 assumption/calibration/comp/base_rate 전부 +0(319 idempotent-skip), artifact_event op = propose 23 + confirm 23 only(edit/dispatch/archive 0), relational deal.updated_at 미전진(2026-05-30 고정), validate-seeds Δ=+0.00. 103 test, sqlx —check clean, blue/green 배포. 결과: “왜 우리는 스마트링/교육/supplement 딜을 패스했나, durable lesson 은” 이 typed cross-deal 질의 + backing comp evidence 해소 가능 (Apposter→“Hardware wearable EV/Rev band” anchored_to). B·C 설계 ratify (후속 backlog): B = sector harvest = artifact-first sector Resource (SoT seeds/_sectors.yaml, para_layer=resource, comp/base_rate/deal_judgment citation) + 휴면 빈 sector_framework 테이블 폐기 → B-index-sector-harvest; C = cross-deal pattern (query_sector·find_similar_deals + 벡터 semantic = hot sector Resource 색인, §5 “벡터=Resource 위 index, 가치 클 때만”) → B-index-cross-deal-pattern-query·B-index-sector-vector-index. 5축: 본질(죽은 딜 judgment = 진짜 moat 의 1급화)·안전(additive·frozen-drift gate·fail-closed verdict·append-only)·결과(380 fact queryable judgment corpus)·혁신(claim/judgment 인식론 분리 + supersede 불변)·퀄리티(103 test·frozen verdict·transcribe-not-invent). | 2026-06-06 |
| D-index-48 | 브라우저 SSO 현황 페이지 — Blueprint OIDC 로그인 시 세션-게이트 firm-read 상세 열람 — 2026-06-04 사용자 “웹페이지에서 blueprint 기반 sso login 되면 상세 투자정보 조회 가능하도록”. 현 /index 는 public aggregate(민감정보 0, leak-tripwire). 추가: Blueprint OIDC Authorization Code + PKCE(S256) 웹 플로우 — /index/login(PKCE+state 서명쿠키→/oauth/authorize 리다이렉트, Entra 세션 재사용 = 진짜 SSO) · /index/callback(/oauth/token 교환→auth::verify_blueprint_rs256 재검증→email→세션쿠키) · /index/logout. 세션 있으면 /index 가 firm-read 상세(deal별 IRR(E[CF])·E[MoM]·P(loss) + 펀드 1·2·3호 성과 + calibration), 없으면 기존 aggregate + 로그인 버튼. 보안: 세션·tx 쿠키 = HMAC(INDEX_JWT_SECRET 재사용, httpOnly+Secure+SameSite=Lax, 단명 exp) · state CSRF 가드 · 콜백 토큰은 RS256+iss+aud 재검증 · 무세션 경로 leak-tripwire 보존(공개 페이지 불변). 권한: 세분화(IC allowlist)는 추후 — 우선 AXE 인증 전체 firm-read(superuser pool 전역 SELECT + 세션 게이트가 보호, RLS actor 불요). Blueprint 의존(위임): isAllowedRedirectUri(platform-oidc.ts)가 현재 loopback+claude.ai 만 허용 → https://axe.axelabs.ai/index/callback 거부 → web redirect policy + axe-index-web static client 추가는 병렬 IdP 세션(D-axe-idp-1)에 위임(머지 전 web 로그인 비활성, index 코드 ready, env BLUEPRINT_INDEX_CLIENT_ID). index-only 구축. ✅ 2026-06-04 LIVE: #383 merged(19f30042) + blueprint blue/green 배포(green active, edge 200) + index env flip(INDEX_PUBLIC_BASE_URL=https://axe.axelabs.ai + INDEX_WEB_LOGIN_READY=true) + recreate. authorize→Entra 수락 e2e 통과(운영자 첫 SSO 1회 잔여). 배포 시 3rd 세션 미커밋 WIP 는 stash 격리·복원(무손상, prod 미배포). | 2026-06-04 |
| D-index-47 | skill ownership/distribution 아키텍처 ratify + P0 reconcile (index=SoT 거짓 해소) — 2026-06-03 사용자 “위생 본질 전략 순” 의 전략 leg (opus 서브에이전트). 직전 멀티에이전트 플랜 ratify: index=truth(SoT bytes·semver·schema-contract·audit) · Blueprint=road(universal git→webhook→rsync 분배, 신규 transport 0) · surface=run(Agent SDK 로컬 디스크 읽기). P0 “STOP THE LIE” 완료(index commit 3b39a03): index=SoT 가 ingest 에 거짓이었음(Blueprint base mirror 가 v2 구조추출로 AHEAD) → blueprint→index port 로 index 현행 superset 화(SKILL.md 285→348 · ingest.py 1443→2035 · test 신규, diff -qr 동일). ic byte-identical · pmc index-only 확인 · INDEX_OWNED.txt manifest + 죽은 launchd 폐기. 검증된 정정: (a) 폐기 launchd 의 target script 는 실재(오늘 수정)·방향 안전(index MCP→blueprint skills-cache drift 검증, 파괴적 아님) — P2 binary(index/bin/index-skill-sync.py) 미존재로 비기능 → 폐기. (b) ⚠ 런타임은 아직 blueprint origin/main 서빙: LIVE com.axe.blueprint-skills-sync launchd 가 blueprint→~/.claude/skills rsync — index=SoT 는 disk/git 에선 참이나 런타임 반영은 P2(index→blueprint mirror+push) 필요. (c) owner:index frontmatter 미기재(manifest 가 de-facto SoT) → follow-up. 잔여: P1 stamp(provenance+schema-drift gate, index-side 安) · P2 CI mirror(런타임 fix — blueprint write+push, production 영향) · P3 contamination purge(ic/dd/vc-sourcing/ir/pmgmt/legal 을 universal base 에서 제거 — 전 customer 컨테이너 영향, 高위험). P2/P3 = explicit go + blueprint PR 필요. | 2026-06-03 |
| D-index-46 | seed = SoT for judgment facts — hardcoded backfill 폐기 (B-index-judgment-in-seeds ✅) — 2026-06-03 사용자 “위생 본질 전략 순” 의 본질 leg. index commit 8ec2c10. D-index-45 가 318 judgment artifact 를 1회성 hardcode migration(backfill.rs 2221 LoC + corpus.rs 603 LoC)으로 넣은 것 = seed prose 의 중복 = transcription 부채. 제거: seed.yaml 이 judgment 의 단일 SoT. ① seed schema: per-seed judgment:(assumptions[]+calibrations[]) + 공유 seeds/_corpus.yaml(comps+base_rates), serde + fail-closed validate(identity-unique·FROZEN scrub_kind/relation·anchor XOR·band/prob). ② seed-ingest emit leg(--emit-judgment): relational ingest 후 judgment artifact 를 propose_judgment_fact 로 emit, comp/base-rate anchor 를 natural name → corpus artifact_id 해소, idempotent·confirm-on-create. ③ 23 seed + _corpus 마이그레이션(backfill.rs/corpus.rs 에서 verbatim) 후 두 모듈 삭제. HARD GATE PASS: (1) 전 23 seed 재ingest = +0 new(content-identity 일치). (2) fresh-reproduction — committed wipe(318→0, relational 무손상) + seed-ingest 단독 +318 재현, byte-identical, 유일 delta = 42 stale dup citation pruned(corpus.rs 중복 = 제거 대상 부채; 587→545). (3) backfill 모듈 삭제, seed-ingest 유일 경로. (4) additive-safe(event propose/confirm only·append-only trigger intact·validate-seeds 23 Δ=+0.00). 정확 구성: 357 = 218 assumption + 58 calibration + 35 comp + 7 base_rate + 39 기존. 51 test(6 invariant 를 backfill.rs→seed.rs 이전, 23 seed YAML=SoT 위에서 assert). 신규 deal 은 seed-ingest 로 자동 artifact화 — hardcode transcription 0. 5축: 본질(seed=진짜 SoT)·안전(2중 증명+append-only intact)·결과(2824 LoC 삭제+자동화)·혁신(natural-name anchor 해소)·퀄리티(51 test+fail-closed validate). | 2026-06-03 |
| D-index-45 | artifact-first judgment layer — 증거층↔판단층 결합 (Epic 1, 2-wave supervised) — 2026-06-03 사용자 “Epic 1 같이 검토하고 들어갑시다. 실제 구현은 서브에이전트로” + “본질에 집중, quick fix 금지”. index commit 1c707e1. 휴면이던 artifact store(39 fact — Apposter 추출치뿐)를 relational compute substrate(23 deal)와 결합 — 모든 투자판단 fact 를 store 의 일급 typed+cited+audited artifact 로 승격. 설계 (검증으로 확정): ① side-bridge stringly-typed 테이블(quick-fix) 거부 ② relational 전면 재작성(안전 위반) 거부 → propose API 가 kind 에 generic 이고 artifact.kind·citation.kind 에 CHECK 없음 확인 → migration 0. 4 신규 kind: assumption(보정 가정)·calibration(surface↔calibrated delta)·comp(외부 비교)·base_rate(경험적 기저율), schema SoT mcp::judgment_schema_defs → /index/schemas(active 10→14)+mcp_schema upsert. FROZEN enum: scrub_kind 7(fat_tail_comp_scrub·genuine_wipeout·par_entry·dilution·over_pessimism_floor·ramp_haircut·multiple_anchor) + artifact_ref relation 4(calibrated_from·scrubbed_from·anchored_to·contradicts). 링크 = 일급 citation(side-table 아님): index_field ref {deal,field} → relational field_ref(예 scenario:base:exit_multiple·output:irr_expected_cashflow) + artifact_ref ref {artifact_id,relation}. propose_judgment_fact 가 propose_extracted_fact INSERT shape 재사용(multi-citation·content-idempotency·confirm-on-create·fail-closed guard). 2 read tool: get_judgment_provenance(deal,field_ref)·list_calibrations(deal). 결과: 23/23 deal → 357 fact(assumption 218 + calibration 58 + comp 35 + base_rate 7 + 기존 39), 0 orphan judgment value. 모든 scenario 확률·exit multiple·recovery·ramp 가 backing assumption 보유 + AUDIT-TRAIL surface→calibrated delta 마다 calibration artifact 가 scrubbed_from comp 인용(예 Apposter MoM 8.60x→1.48x scrubbed_from Oura 10x PSR via fat_tail_comp_scrub). 안전(additive-only 3중 증명): artifact_event op = propose/confirm 만(edit/dispatch/archive/restore 0) · relational updated_at 가 seed-ingest(05-30) 이후 미전진 · validate-seeds Δ=+0.00. 48/48 test(backfill self-contract 포함) · sqlx —check clean. 과정: wave1 = machinery + 14/23 — adversarial gate 가 61% 미완 적발(per_deal 일부 aspirational, 병렬 agent crate 충돌) → 운영자 직접 미실행 subcommand 실행 +4 → wave2 = 미작성 5딜(Medistaff·Nanora·Novachips·OpenResearch·Superman) author + 6 rival backfill module → 1 backfill.rs + corpus.rs 통합. 5축: 본질(artifact-first 실재화 = 판단 기억)·결과(357 fact queryable)·안전(additive 3중+gate)·혁신(scrub→comp 역추적 provenance·우리 epistemic 차별점)·퀄리티(48 test·frozen enum·0 dangling ref). 잔여(방향): judgment data 를 structured seed field 로 이전 → ingest 가 artifact 파생(현 1회성 transcription) [B-index-judgment-in-seeds] · Epic 2 confidence load-bearing · Epic 3 self-calibrating prediction→outcome loop. | 2026-06-03 |
| D-index-44 | ingest 데이터품질 + lifecycle 안전 — metric_kind 세분 + 회귀 스위트 (5축 평가 후 병렬 2 unit) — 2026-05-30 사용자 “지금 가능한 작업 서브에이전트 진행. 본질·퀄리티·결과·안전·혁신”. D-index-43 잔여를 5축 평가 → 최고가치 2개 병렬(non-conflicting repo). (X) metric_kind 세분 (blueprint PR #380 merged 931284b9 → ~/.claude/skills/ingest webhook sync): reconcile 가 스스로 노출한 근본결함 — coarse valuation 이 절대 EV(270억) + EV/EBITDA 배수(9x) + 음수 helper(−74) 혼재 — 를 source 에서 fix. order-priority 분류기(moic→multiple→valuation) + per-kind curation: valuation(절대 EV/EqV/proceeds, currency fmt)·multiple(EV/EBITDA·EV/Rev·P/E·PSR·PBR·배수)·moic 독립. curation: multiple 은 양수 0~100x band·valuation 은 양수 절대값만 → 음수 EV/EBITDA helper drop. test 52/52, irr·moic·cagr 0 regression(XIRR function-path 무영향). (Y) lifecycle 회귀 스위트 (index c8b3746): 8 test — idempotency·1:1:1·status 파생·confirm/reject·append-only(grant+trigger 이중)·RLS 격리·reconcile·seed-draft non-ingestability(serde reject 로 증명). tx-rollback isolation + graceful skip, 35 pass, 0 residue, offline-safe. ⭐ 발견(버그 아님): append-only trigger 가 ON DELETE CASCADE 도 차단 → event 보유 artifact 는 normal session 삭제 불가 = audit 영속성 보장(operational invariant). 통합 결과: 정제 classifier 재ingest → store refresh → artifact store split 반영: valuation 12 + multiple 4(이전 conflated 10) + irr 8·cagr 7·moic 4 + figure 4 = 39 facts. extractor 결함이 정량 데이터로 노출(reconcile)되고 source 에서 닫힘 — self-correcting 루프. 5축: 본질(근본 데이터품질)·결과(split 가시)·안전(회귀 lock+audit 영속)·혁신(unit-aware 분류기+self-correcting reconcile)·퀄리티(52+35 test). | 2026-05-30 |
| D-index-43 | artifact-first lifecycle 완성 — Query API + ctx review + reconcile + seed-draft scaffold (D-index-42 잔여 4 완결) — 2026-05-30 사용자 “서브에이전트로 종결 후 보고”. D-index-42(propose)가 남긴 4개를 2 unit 순차 구현(supervised subagents) + blue/green 배포. index commit 3eece93(lifecycle)+118568a(seed-draft). (1) Query API read path — query_artifacts(para_anchor/kind/metric_kind/status 필터)·get_artifact(payload+citations+event history). status = 최신 artifact_event.op 파생(propose→proposed/confirm→confirmed/reject→rejected; edit·dispatch 는 직전 유지) — agent 가 grep 대신 typed query. (2) ctx review — confirm_artifact(op=confirm; edit_payload 시 payload UPDATE+before/after 감사)·reject_artifact(op=reject+reason, row 보존). proposed→confirmed/rejected. (3) L3-lite reconcile — reconcile_artifacts: metric_kind 별 value spread flag, 불일치 {value, source, location, status} 전량 surface (silently 택1 금지). Apposter irr spread 86%(Exit IRR 0.572 vs Min IRR 0.08) + 5 group 전부 flag. (4) L2 본령 seed-draft — draft_seed_from_artifacts: deal artifacts → 보수적 seed.yaml SCAFFOLD(index draft-seed CLI + MCP). ⭐ 날조 금지를 구조로 강제: surface metrics(57.2% 등)→AUDIT TRAIL 주석+citation only / scenario probability·calibrated scenario·wipeout·baseline·intake = 전부 ~ TODO / valuation 후보 다수(270억 vs 349억)→자동선택 거부 / draft 는 의도적 non-ingestable(validate-seeds 가 ~ placeholder 에서 fail) — judgment gate 가 문서 아닌 타입시스템으로 강제. coverage(Apposter): auto_filled 0 / needs_human 18 / surface_metrics 29 — 사람은 skeleton·number-hunt 절약, calibration(D-index-24/26/21) 판단은 보존. 전 tx RLS 정식경로(set_current_user), build clean online+offline(sqlx guard), 27 test pass, 6 신규 MCP tool live(blue/green, ext healthz 200). → artifact-first 전 lifecycle 가동: propose→query→review→reconcile→draft. 잔여(축소): ctx review UI(Blueprint markdown-diff 표면, 현재 CLI/MCP only) · Query API 를 /ic·seed-building 의 primary read path 통합 · extractor metric_kind 세분(valuation 이 EV+EV/EBITDA 혼재 — reconcile 가 노출한 finding) · 전 deal artifact화(현재 Apposter 1건) · L4 versioned diff. | 2026-05-30 |
| D-index-42 | ingest 고도화 L2(부분) — extraction → proposed artifacts (artifact화, artifact store 첫 가동) — 2026-05-30 사용자 “ingest 때 artifact화 한다던 것 구현됐나?”. D-index-41 L1(extraction→JSON sidecar) 후속 — sidecar 의 fact 를 index artifact store 에 proposed artifact 로 적재 (D-bp-artifact-5 흐름 “source→ingest→typed extraction→proposed fact(audit_trail propose)+citation” 의 전반부 실현). 발견된 gap: index 의 artifact/citation/artifact_event store (D-index-2 cortex mirror, Day-1 schema) 가 0행 — 한 번도 안 쓰임. 23-deal 은 relational deal-domain 테이블에만 존재, artifact-first 층은 dormant 였음 (정직히 미구현). 구현 (index commit 4ca5868, blue/green 재배포 live): propose-from-ingest <dataroom> CLI + propose_artifacts_from_ingest MCP tool — *_xlsx.cells.json key_outputs + *_pdf.figures.json figures → 각 fact = artifact(kind=extracted_metric | extracted_figure, payload value+formula+metric_kind+location, confidence, para_layer=resource) + citation(xlsx_cell |
| D-index-41 | ingest 고도화 L1 — extraction-not-transcription + source-type-aware structured extraction (재무모델·IR 정문 손실 해소) — 2026-05-30 사용자 “ingest 역량 끝까지 고도화, 서브에이전트로, before/after 만”. B-index-ingest-structured-extraction 의 L1 ship (설계 ratify 동반). 문제: ingest 가 dataroom xlsx/pdf 를 md 로 photocopy 하던 게 lossy — xlsx 는 값만 표로, 수식은 40개 truncate 후 <!-- formulas: +182 more --> 주석에 매몰 → return model 의 IRR/MoM 기계추출 불가 (23-deal 적재 시 수작업 발굴해야 했던 근본 원인). 정문 손실은 ic·index 에서 복구 불가 (GIGO). L1 구현 (blueprint PR #379 merged → ~/.claude/skills/ingest v2, launchd webhook sync): convert_xlsx v2 — {stem}_xlsx.cells.json sidecar (시트별 full grid: value+formula+number_format+label, named_ranges, key_outputs[]) + key_outputs 자동탐지 (XIRR/IRR/MIRR/NPV/XNPV 함수 + IRR/MoM/NPV/CAGR/valuation/EV/배수/수익률/밸류 라벨 정규식; numeric-value + rate-타당성 게이트로 false-positive 차단) + md ## Model Summary / ## Key Outputs (detected) / per-sheet formula↔value 표(truncated 주석 대체) / ## Named Ranges. convert_pdf v2 — {stem}_pdf.figures.json (라벨 인접 재무 figure sweep, precision-first) + ## Detected Figures. 불변식 준수: 숫자는 mechanical(openpyxl/pdfplumber) only (D-index-15 vision-boundary), 추출 fact 마다 citation(sheet!cell / page) 자동. ⭐ before/after (Apposter 20-sheet return model): BEFORE = Exit IRR =XIRR(L85:Q85,L79:Q79) 가 +182 more truncated 주석에 매몰, 기계추출 불가. AFTER = 31 key outputs 자동 surfacing — Exit IRR 57.2%/50.4%(Case B 49.6%/43.3%) · Exit MoM 8.60x/7.12x · Entry EV 270억 · Exit 9x EV/EBITDA, 전부 값+수식+라벨로 파일 상단. (직전 23-deal 의 Apposter agent 가 수작업 발굴한 수치가 이제 mechanical 추출.) test 40/40 PASS · 기존 포맷(docx/pptx/csv/image/zip) 무변경 · 신규 의존성 0 · datetime→ISO 직렬화 버그 fix 포함. orchestrator QC: agent draft 의 broad-recall false-positive(date cell·mislabel CAGR 25M) 를 numeric+rate 게이트로 34→31 정정(true output 전량 보존). 잔여 (L2-L4, 후속): L2 cells.json→index typed intake/financial_model seed-draft 자동생성, L3 cross-source reconciliation(deck vs xlsx vs cap table 불일치 flag), L4 versioned re-ingest diff. | 2026-05-30 |
| D-index-40 | Render/리얼초이스(23rd) 적재 + par-entry cheap-option = E[CF] 극단 사례 (D-index-25 poster child) — 데이터룸 소진 — 2026-05-30 사용자 “더 추가할 프로젝트?”. 전체 데이터룸 스캔 → 적재가능 신규 = Render 1건. Render = 리얼초이스/트루비아(데일리퓨어크레아틴, 크레아틴 건기식 D2C). dataroom 有·IC 미완 → best-effort SCREEN, status=‘passed’(미투자 — ⚠ 리얼초이스/트루비아는 AXE 플랫폼 고객(realchoice vault·Truvia frame 회계)이나 고객 ≠ 투자집행; 주금납입·조합결성 증거 없음). ⭐ 핵심 — par-entry cheap-option 이 E[CF] 를 오해소지로 끌어올림: AXE 진입 = 5,000만 @ 100원 = 액면=순자산(93원) = par/book value(트루비아 벤처기업 확인 enabler 목적 개인투자조합 신주, 정상 priced VC 라운드 아님). par 진입이라 어떤 성공도 거대 multiple → upside 28.0x(P 0.12). 결과 E[CF] 35.13%(랭킹 4위 수준)이나 E[MoM] 4.53x·P(loss) 68%·median=손실(0.40x). ⭐ fantasy(D-index-26)와 구분: exit multiple 은 0.8-1.4x commodity 정합 scrub 완료(Oura/EGA식 인플레 아님) — 높은 E[CF] 는 par 진입의 구조적 산물이지 multiple 환상이 아님. 고로 Render 는 D-index-25(“E[CF]=fat-tail winner 가 끄는 mean → E[MoM]+P(loss) 동반 필수”)의 poster child: E[CF] 단독 랭킹은 par-entry 딜에서 오해 유발 — Render 35% 는 top deal 이 아니라 68% 손실확률 par 진입 cheap call option. SCREEN=PASS(commodity supplement·moat 약함·median 손실·보통주 무보호). 단 5,000만 = 트루비아 벤처확인 enabler + 고객관계 cheap option 으로 재무 IC 와 분리 의사결정 가능. EGA(RCPS·매출16억·올리브영)·Nanora($9M cap)보다 early·고위험. deal_id eb83b883. 데이터룸 소진 (DB 23): 잔여 = Whale=위시켓(executed 포폴이나 원 IC/모델 부재, 자료 확보 시) · Purple AI/TR Corp/엘리시움(DHP 빈 폴더) · Prj_Artemis(AXE 자체 제품) · 델리후레쉬(RFP) · 이노씨앤에스/Curi AI/가람봇/딥트리/블루밍(thin lead) = 전부 비-딜/자료대기. 23-deal canonical E[CF] 랭킹 (⚠ par-entry 주의): Sentry 43.5 > Iippo 43.3 > Starnex 38.1 > Render 35.13(⚠par cheap-option, P loss 68%·median 손실) > Canopy 31.8 > Interstellar 30.5 > Hancom 23.9 > EGA 23.3 > (Superman 20.8 별도) > Catalyst 16.99 > 데이톤 16.57 > Infinity 16.5 > Eduon 15.0 > 디벨로퍼그룹 14.76 > 수성별 14.0 > Sendy 11.7 > Nanora 10.1 > Novachips 9.2 > Apposter 8.34 > 유비랩 4.06 > Medistaff −0.59 > JS E&L −1.97 > Open Research −25.85. index commit de47a2c. | 2026-05-30 |
| D-index-39 | Archive batch 6-deal 적재 (17~22th) — 전부 correctly-avoided(PASSED) — DB 22 — 2026-05-30 사용자 “1_Project (Archive) 6건 모두 best effort 검토”. 6 병행 agent + orchestrator 양방향 QC. 전부 archived=미투자 → D-index-24 calibrated canonical(surface 헤더 audit-trail). 딜별: Medistaff(의료진 플랫폼, screen) E[CF] −0.59% — founder 형사 jeopardy(블랙리스트 방조·증거인멸 검찰송치) + 두나무 lead 선점. Open Research/oo.ai E[CF] −25.85%(P loss 90%) — ⭐ “deal structure ≠ company quality”: 회사는 Series A 2,000억 라운드 중이었으나 본 거래는 창업자 김일두(전 카카오브레인) 개인 distressed 담보 secondary bridge(도박·사채 변제용) → 직후 도박/무단 지분매각/서비스중단/팀 9-of-9 이탈 표면화. 계약서 당사자 공란=미체결. JS E&L(2차전지 부품, transcribe) E[CF] −1.97% — EV 캐즘, 자본잠식 임박·누적결손 56.85억, surface exit PER 16x(2차전지 고배수)→NPC 2x·AJ 8x scrub. Catalyst/클래스카드(교육 PE buyout, transcribe) E[CF] 16.99% — ⭐ index 2nd buyout + 첫 PEF/GP-structured(AXE=GP, 삼일 PwC 옥션 입찰, committed=consortium equity 170억 ≠ AXE direct; GP commit ~5억+carry). surface Gross 30.86% → 10x flat exit를 교육 comp 5-9x로 scrub + 60% LTV wipeout. “높은 IRR≠Go”: first-time GP 가 600억 consortium 0 commit + 학령인구 + leverage. 미체결(SPA 없음, Mgmt/Exit 폴더 공란, archived before SPA). 데이톤/DATON(DC AI Ops, screen) E[CF] 16.57% — 흑자전환 100억 매출·DC tailwind·레퍼런스(에코프로BM/현대제철) 진성이나 terms-void(valuation/instrument 전부 assumed) + AINATION 합병 미확인. Apposter/b.ring(스마트링, transcribe) E[CF] 8.34% — ⭐ “field DD kills thesis”: 현장실사(Bic Camera 방문 + McKinsey JP)가 moat 사망 확인(commodity 광동 OEM, sleep accuracy 열위, disposable test-buy) → Case Drop. surface Oura 10x PSR(2029 4,000억 exit) fantasy → Movano/Fitbit 2-3x scrub + 15yr 累損 wipeout. Nanora(10%) 대비 강한 traction·강한 kill. ⭐ batch 교훈: (1) negative-E[CF] 기록 = PASS 결정의 정량 검증 — index 가 correctly-avoided 딜을 음수 what-if 로 보존(Open Research −25.85% / JS E&L −1.97% / Medistaff −0.59%), 회피 판단 근거화. (2) 양방향 QC 가 agent 에 전파 — 6 agent 가 instrument floor·fat-tail scrub·genuine wipeout 를 briefing 만으로 자체 적용(D-index-37/38). (3) deal-class 다양화: buyout/PEF·founder-distressed-secondary·field-DD. 22-deal canonical E[CF] 랭킹: Sentry 43.5 > Iippo 43.3 > Starnex 38.1 > Canopy 31.8 > Interstellar 30.5 > Hancom 23.9 > EGA 23.3 > (Superman 20.8 별도) > Catalyst 16.99 > 데이톤 16.57 > Infinity 16.5 > Eduon 15.0(s) > 디벨로퍼그룹 14.76(buyout) > 수성별 14.0 > Sendy 11.7 > Nanora 10.1 > Novachips 9.2 > Apposter 8.34 > 유비랩 4.06(s) > Medistaff −0.59 > JS E&L −1.97 > Open Research −25.85. index commit 7d6ccca. | 2026-05-30 |
| D-index-38 | 디벨로퍼그룹(15th, index 최초 buyout-class)·유비랩(16th, deep-tech screen) 2-deal batch — buyout FCF-floor + 양방향 QC — 2026-05-30 사용자 “디벨로퍼그룹·유비랩 검토”. 입력 richness 양극: 디벨로퍼그룹 = 완성 full IC(v5, 5개정) / 유비랩 = IR+Q&A only(screen). (1) 디벨로퍼그룹 스터디카페 사업부 100% 영업양도 = index 최초 buyout-class(PE형 캐시카우 M&A, AXE 12억/EBITDA 3.87x, 무차입, 100% 보유, 보통주/secondary). full IC surface 공식 확률가중 E[CF] 34.09% 이나 메모 스스로 Devils-reweighted 16.5% 명시(17.6pp 갭) — Exit 5.0x(근거 ‘L’, 비교사례 없음) + EBITDA 310M(이승호 대표 보수 미계상→실 230-260M + 수도권유지보수 83→23M -61.7% 급감 미규명) + Upside 10.53x “카카오/네이버 9,251M” fat-tail 의존. D-index-24 미집행→calibrated canonical + D-index-26 fat-tail scrub(Exit 4.0x·조정EBITDA·kakao 폐기) + D-index-21 genuine wipeout → E[CF] 14.76% / E[MoM] 2.02x / P(loss) 15% (메모 Devils 근방). ⭐ buyout 신 insight: cashflow buyout 의 FCF 배당이 실질 하방 floor(downside 도 ~1.1x, 원금손실 거의 없음) → P(loss)가 VC 딜보다 구조적으로 낮음 (손실은 운영붕괴 wipeout 경로뿐). VC equity 와 다른 첫 자산군 — 단 SME 성숙기라 upside cap(IPO 5년내 불가). deal_id b88e2dcb. (2) 유비랩(그래핀/그라파이트 4단 적층 방열 시트, OLED foldable + AI 반도체 TIM) SCREEN: 병행 agent + orchestrator QC. elite founder(유봉현 — 삼성D R&D 33년·세계최초 82인치 UHD·美특허 120+·NeoGraf 美 VP) + 진성 traction(삼성D 업체코드+1/2/3차 샘플매출+애플 샘플전달)이나 component multiple cap(회사가 Q&A 에서 지목한 직접경쟁사 신화인터텍 EV/Rev 0.2x·PBR 0.51x) + pre-rev hockey-stick(92억@3y=2,500x, NeoGraf 글로벌1위도 $16M) + Samsung 단일고객 concentration → “좋은 회사 ≠ 좋은 딜” (Interstellar “높은 IRR≠Go”·Novachips “polish≠quality” 가족). ⭐ 양방향 QC (D-index-37 보완): D-index-37 교훈은 “낙관 메모 blind transcribe 금지”(아래로 scrub). 유비랩은 반대 방향 — agent 가 base 를 as-converted(0.76x)로 계산해 E[CF] 2.9%/P(loss) 80% 산출했으나, 본인이 가정한 RCPS 1x 우선권을 base(EV 143억 ≫ 20억 우선권)엔 미적용한 내부 불일치 artifact(downside 엔 적용). 우선권 floor 일관 적용(base 1.0x) → E[CF] 4.06% / P(loss) 43% (peer 정합: EGA 40%·에듀온 45%·Nanora 50%). QC = 올바른 수치에 착지(단지 낮은 수치가 아님) — fat-tail 도 instrument-floor artifact 도 모두 교정. deal_id c98ba89e, stage=‘Screening’. 16-deal canonical E[CF] 랭킹: Sentry 43.5 > Iippo 43.3 > Starnex 38.1 > Canopy 31.8 > Interstellar 30.5 > Hancom 23.9 > EGA 23.3 > Infinity 16.5 > Eduon 15.0(screen) > 디벨로퍼그룹 14.76(buyout) > 수성별 14.0 > Sendy 11.7 > Nanora 10.1 > Novachips 9.2 > 유비랩 4.06(screen) (+ Superman 20.8 별도). index commit c0bf6d5. | 2026-05-30 |
| D-index-37 | EGA·수성별·Nanora 3-deal batch 적재 (12~14th) — transcribed 메모 rigor 가변 → QC+calibration 필수 — 2026-05-30 사용자 “EGA·Nanora·수성별 모두 진행” + “Nanora full ic”. 3 병행 background agent(deal당 1) + orchestrator QC. 핵심 교훈 — full-IC 메모를 blind transcribe 하면 안 됨: 메모 rigor 가 deal 마다 극과 극. EGA(디파이넘버, NMN 뉴트리코스메틱) 메모는 surface-optimistic → agent 가 그대로 옮긴 E[CF] 127%(매출 16억→550억 3년 34x + EV/Rev 7× + 18x MoM@2y)는 D-index-26 fat-tail 환상(Iippo 74% 재현). EGA 는 status=‘passed’(미투자·조건부)라 D-index-24상 surface 아닌 calibrated 가 canonical → scrub(revenue ramp 보수화 16→300/120억 + EV/Rev 5×/3.5×/2.5× K-beauty M&A anchor + genuine wipeout 추가: IR에 창업자 이름·이력 부재 + 매출 16억 미검증 red flag) → calibrated E[CF] 23.3% (E[MoM] 2.33x, P(loss) 40%). surface 는 헤더 audit trail 보존. 반면 수성별(국군복지단 DOOH 광고, 우선협상대상자) 메모는 이미 보수적 — Σp −23.8% → IRR(E[CF]) +14.0%(sign-flip, Interstellar 패턴). 단 deal 조건 전부 dataroom 부재(10억/30억/25% 가정) + 팀공백 + CAPEX gap 5:1 = info-void Conditional Pass (instrument TBD, series 기타). Nanora(US precision-wellness, TCM→구독 supplement+smart ring+AI, SAFE @ $9M post cap, pre-launch) 는 사용자 지시로 SCREEN→FULL IC 승격(rich dataroom): 18-section + multi-scenario + adversarial(proponent/premortem-critic 분리). research-calibrated E[CF] 10.05%(E[MoM] 1.76x, P(loss) 50%) — 회사 plan을 Care/of($225M→Bayer→2024 폐업) category-graveyard + Oura 11x→supplement 1.5-2.5x rev + Pre-Seed→A 85% fail base-rate 로 하향. 판정 Conditional(미투자, 결정변수 = post-launch 90-day cohort retention, T+9M revisit). QC 결론: agent batch 는 효율적이나 transcribe 산출은 반드시 orchestrator 가 fat-tail/wipeout/base-rate QC (EGA 127%를 적재 전 차단). 14-deal canonical E[CF] 랭킹: Sentry 43.5 > Iippo 43.3 > Starnex 38.1 > Canopy 31.8 > Interstellar 30.5 > Hancom 23.9 > EGA 23.3 > Infinity 16.5 > Eduon 15.0(screen) > 수성별 14.0 > Sendy 11.7 > Nanora 10.1 > Novachips 9.2 (+ Superman 3-step 별도). index commit fc56004+7b4a50d. | 2026-05-30 |
| D-index-36 | SCREENING 단계 정규화 — best-effort pre-DD 게이트 (research+모델 필수, full /ic 자원 gate) — 2026-05-30 사용자 “screen 단계 추가 + 적합 IC memo + 최소 리서치 전부 + 재무모델 + best effort + 정규 프로세스화 + 적재”. 1-pager(seed teaser)만 있는 inbound 에 19-agent /ic(수 시간·dataroom 전제)를 바로 돌리는 건 GIGO·낭비 → Screening 게이트 신설. deal lifecycle: sourcing → ⟨Screening⟩ → DD(full /ic) → IC 결의(Go/Conditional/Pass) → Portfolio → Exit. (deal.stage enum 에 ‘Screening’ 이미 존재 — migration 불요.) Screening 이 답하는 질문 = “이 건에 DD 자원 투입 가치가 있나?” (Go/No-Go 투자결정 아님). 프로토콜 (타협 금지): (a) 최소 외부 리서치 필수 — 시장·수요 / sector franchise-or-stage base-rate / exit comp(공개 multiple) / 경쟁·moat / founder 검증, (b) 재무모델은 회사 plan 이 아닌 base-rate calibrated, (c) IRR 은 index engine IRR(E[CF]) (D-index-25/32) 로 검증, (d) premortem + 가정 라벨 A1..An (H/M/L). 산출물: 딜폴더 ic/{research,finance,memo}/ + index 적재 (stage=‘Screening’, screen-pass 면 status=‘passed’, what-if IRR 보존 D-index-30). 첫 사례 = 에듀온(EDUON) — 초등 영어 Writing 프랜차이즈 Seed 보통주 5억@Pre 20억. ⭐ 교훈 — best-effort screen 이 ad-hoc 낙관을 base-rate 로 보정: 회사 IRR 58% / Claude ad-hoc E[CF] 26.5%(upside 10x) → research-calibrated E[CF] 15.0% (E[MoM] 2.0x, P(loss) 45%). 두 회사 낙관(가맹 “3년 1,000” vs 3030영어 21년→1,400 / exit “매출100억→300억=3x rev” vs 크레버스 0.59x·웅진 0.20x rev)이 base-rate·comp 에 동시에 깨지며 upside 10x→6x cap. Screen 판정 = PASS(DD 미진행) — E[CF] 15%≪30% hurdle + SME 프랜차이즈(upside cap) + 보통주 무보호 + AI(Wizon) commodity. founder·product 우량하나 현 조건 DD 정당화 불가 → 재screen 조건(RCPS+Pre 10-12억+가맹 traction+vesting) + T+6M(론칭 후) 재screen. 정직 원칙: 입력이 1-pager 면 “full cycle 의 정직한 형태 = Screen 에서 멈춤”. 향후 /ic 에 lightweight screen-mode 통합 = B-index-ic-screen-mode. index 11th deal (첫 Screening), deal_id 2bb5d585. | 2026-05-30 |
| D-index-35 | Interstellar (인티그레이션) 적재 — 10th deal, PASS (governance-driven, not IRR) — 2026-05-30 사용자 “추가 적재 프로젝트?” → 데이터룸(OneDrive 1_Project/) 스캔 결과 IC 분석 완료·미적재 = Prj_Interstellar 1건 (Render=IC 미완 / Whale[위시켓]=보유 admin docs / 델리후레쉬=RFP 제휴건 / Pipeline=스크리닝 단계 — 모두 미대상). 인티그레이션: 한의·치과 5-카테고리 플랫폼 (한의사 가입률 83% 사실상 단독 1위, FY24A 매출 454억 +74%). Series D RCPS, AXE follow 검토 20억 (신주 16.4+구주 3.6, pre 1,085억, AXE 1.6% O/S) — PASS (알토스+네이버 lead 160억 종결). D-index-24: PASS→calibrated canonical (Sendy 동급, status='passed', what-if IRR 보존). canonical E[CF] IRR 30.5% / E[MoM] 3.23x / P(loss) 36.0% (IC v1 surface Σp 계열 25.3%/2.71x). ⭐ 핵심 — PASS 사유가 IRR 아님: E[CF] 30.5% 는 매력적(아래 9-deal 중 5위)이나 PASS 는 거버넌스(R6 자기거래 — 정희범 대표 명의 메디스트림 한의원+원외탕전 매출 인식 → IPO 심사 reject ≥30%) + AXE 1.6% minority(이사회 미진입) + 알토스/네이버 lead 후 marginal value 부재 = 순수 비-IRR. “높은 IRR ≠ Go” 명시 사례 (Novachips “polish ≠ quality” 와 짝). 현 canonical 9-deal 랭킹 (D-index-34 8-deal supersede): Sentry 43.5 > Iippo 43.3 > Starnex 38.1 > Canopy 31.8 > Interstellar 30.5 > Hancom 23.9 > Infinity 16.5 > Sendy 11.7 > Novachips 9.2. 부수: seed.rs SERIES_OK 에 Series D/E 추가 (누락 — 향후 Series D+ intake hard-reject 방지). validate-seeds 12 Pass / 23 test / DB 10 deal. index commit 7269286. | 2026-05-30 |
| D-index-34 | canonical 8-deal E[CF] 랭킹 확정 — Σp/surface 시절 랭킹 supersede — 2026-05-29 D-index-32/33 후속, B-index-docs-irr-ecf-restate 수행. D-index-19/21/23/24 의 인라인 랭킹은 Σp·IRR(폐기) 또는 surface 기준 → engine validate-seeds 로 9 deal IRR(E[CF]) 재산출하여 canonical 확정. canonical E[CF] 랭킹: Sentry 43.5 ≈ Iippo 43.3 > Starnex 38.1 > Canopy 31.8 > Hancom 23.9 > Infinity 16.5 > Sendy 11.7 > Novachips 9.2. 구 Σp 랭킹 대비 변화: (a) tail flip — Sendy(구 6.5 최하위) 가 Novachips(구 7.8) 위로 (E[CF] 11.7 vs 9.2); (b) Sentry–Iippo 격차 4pp→0.2pp (거의 동률); (c) 전반적으로 E[CF] 가 Σp 보다 높음 (fat-tail upside 가 mean 견인). per-deal 구(Σp/surface)→신(E[CF]): Sentry 40.3→43.5 / Iippo 36.5→43.3 / Starnex 36.4→38.1 / Canopy 26.4→31.8 / Hancom 22.4→23.9 / Infinity 13.8→16.5 / Novachips 7.8→9.2 / Sendy 6.5→11.7. overlay: Infinity recalibrated -8.3→-4.3 / Superman fresh 10.5→20.8 (D-index-33 에서 seed 전환 완료). Infinity 특례 (executed deal — D-index-24↔32 조정): v8 모델(leaves) 불변 유지(집행건 decision-time 보존) + 집계만 E[CF] 통일 → canonical metric = E[CF] 16.5% (baseline 0.1646), IC headline 13.8% 는 provenance (집계법 차이, 모델 자체 불변). 즉 D-index-24 의 “사후 재작성 금지” = 모델/가정 보존이지 집계법 고정이 아님. seed 주석도 정합 (index commit 537c04e: starnex/hancom/sendy/infinity 주석 E[CF] 재기재, value 불변). **역사적 결정문(D-index-19/21/23/24)**은 revision 최소화 — 인라인 숫자는 당시 기록 보존, 본 결정이 현 canonical 랭킹 SoT. | 2026-05-29 |
| D-index-33 | irr_instrument_adjusted 폐기 — instrument 효과는 leaf waterfall 로만 표현 (flat discount 이중계상) — 2026-05-29 사용자 “수익성 정확도 심화” 방향 선택 후 조사 결과. 발견: exit_matrix leaf recovery 가 instrument 구조를 포트폴리오 전역에서 이미 반영 — RCPS deal 은 preference floor (Sentry 0.05~0.14 / Starnex 회생 1.10x “RCPS floor”), 보통주 deal 은 후순위 wipeout (Hancom 0.30 / Sendy 0.15 / Novachips 0.20 / Infinity 0.12, leaf 설명에 “보통주 후순위” 명시). 따라서 IRR(E[CF]) 가 이미 instrument-aware. Novachips 1건에만 있던 irr_instrument_adjusted (E[CF] +9.2% 에서 -12pp 추가 → -4.55%) 는 leaf 의 보통주 wipeout(0.2x)을 이중계상 (검산: E[MoM] 1.34x → E[CF] +9.2%, wipeout 6M@5y 이미 포함). D-index-19 의 instrument-adjusted-IRR 제안(B-index-instrument-adjusted-irr) supersede — discount table 미구현. 변경: (a) novachips irr_instrument_adjusted baseline 제거 + 헤더 재프레이밍; (b) seed.rs::validate DEPRECATED_OUTPUT_CODES load-time hard reject — irr_instrument_adjusted (D-index-33) + irr_loss_included / irr_success_only (Σp, D-index-32 완결, 음성 테스트 OK); (c) overlay seed 잔존 Σp output_code 정합 — infinity_recalibrated / superman_fresh 의 irr_loss_included → irr_expected_cashflow (engine E[CF]: Infinity -4.3% [구 Σp -8.3%] / Superman fresh 20.8% [구 Σp 10.5%] — fat-tail 일수록 Σp↔E[CF] 격차 큼). 잔여 (정직 기록): docs 8-deal 랭킹 등 IRR 다수가 pre-E[CF] (Σp/surface) → B-index-docs-irr-ecf-restate. Σp·IRR 과 동형 (잘못된 별도 집계 폐기). 11 seed validate + 23 test + 음성 테스트 Pass. index commit 74e1eb4. | 2026-05-29 |
| D-index-32 | Σp·IRR 폐기 — IRR(E[CF]) 단일 canonical 집계 — 2026-05-29 사용자 승인 “p IRR 폐기하고 남은 것 모두 진행” (D-index-25 의 “폐기 보류” 해제). 배경: Σp·IRR (per-scenario IRR 의 확률가중 평균) 은 “수익률을 평균” 하는 잘못된 집계 — fat-tail deal 에서 rate-averaging 이 mean 을 왜곡. IRR(E[CF]) (확률을 cash flow 에 먼저 적용 후 IRR) 만 canonical 로 유지. 변경: (a) query_irr summary 에서 weighted_irr_loss_included / weighted_irr_success_only 제거 → irr_expected_cashflow + expected_moic + prob_loss + success_probability + methodology 만 노출; (b) src/irr.rs::weighted_irr / weighted_irr_success_only 에 #[deprecated] + #[allow(dead_code)] (호출부 0, 산식은 audit 위해 보존); (c) E[MoM] (weighted_moic) 는 폐기 아님 — MoIC 는 linear 집계라 확률가중 평균이 정합 (rate 가 아닌 multiple); (d) validate.rs ValidateReport + 9 seed financial_outputs_baseline 의 output_code 를 irr_expected_cashflow 로 전환 (E[CF] 값); (e) html/xlsx cover 의 IrrSummary 표시 = E[CF] / E[MoM] / P(loss). 23 unit test + validate 3 PASS. index commit 3b2f543. | 2026-05-29 |
| D-index-31b | share_type 신주(primary)/구주(secondary) 구분 + post-money invariant — 2026-05-29 사용자 “round size 와 entry 가 신주 구주 나눠진 경우도 수용 가능한가 → 신·구주 혼합 등 다양한 상황 많다”. 모델: fund_investment.share_type ∈ {primary|secondary} — primary(신주) 는 회사로 자금 유입 → post-money 증가에 반영, secondary(구주) 는 기존 주주 지분 매입 → post 불변 (회사 미유입). 혼합 라운드는 round_primary_krw (라운드 중 신주분) 별도 기재. invariant (SeedFile::validate Check 1b + validate.rs): post_money ≈ pre_money + 신주(round_primary) ±2% — 위반 시 hard reject. 검증 효과: Infinity pre 499.5억 / post 500억 이 DSC 라운드 100억 신주와 불일치 (500 ≠ 499.5 + 100) → invariant 가 포착 → pre 400억 으로 정정 (post 500 = pre 400 + 신주 100). Hancom / Novachips 는 구주 매입분 포함 → share_type=secondary. migration 0005 = share_type + round_primary_krw. index commit 98b88c2. | 2026-05-29 |
| D-index-31 | 투자성 분석 최소 intake 게이트 (5 필수 필드) + EV/EqV 혼동 차단 + _TEMPLATE — 2026-05-29 사용자 “투자성 분석 시 최소 필요한 정보는 받아서 시작 — entry date / valuation (EV 인지 EqV 인지 혼동 없게) / 전체 라운드 / 투자 규모 / Series”. 5 필수 필드 (SeedFile::validate Check 1b): series + round_size_krw (라운드 전체) + committed_krw (우리 투자) + entry_date + pre_money_krw. enforcement 2-tier (사용자 “Warn 기존 호환 + 신규만 hard”): intake_enforced=true (신규/template) → 누락 시 hard reject; =false (기존 9건) → warn 만. EV/EqV 규칙 (혼동 영구 차단): entry valuation (pre/post) 은 항상 EqV (지분율 분모) — EV 입력 금지; exit 만 proceeds_basis 로 EV→EqV bridge (D-index-22). 신주/구주: round 을 primary/secondary 로 분해 (D-index-31b). 산출물 = seeds/_TEMPLATE.yaml (intake form + EV/EqV 가이드 주석 + 신주/구주 혼합 예시). migration 0004 = series + round_size_krw. 기존 9건 backfill (series + round_size_krw + share_type). index commit c4c12f4 + 9e86a07. | 2026-05-29 |
| D-index-30 | status=‘passed’ (검토 미투자) — what-if IRR 보존 + fund/project baseline 제외 + 5/29 배치 정정 — 2026-05-29 사용자 “pass 도 당연히 IRR 로 데이터 관리. fund·project 수익성에서는 제외”. status=‘passed’ = 검토했으나 미집행 — per-position what-if IRR 은 계산·보존 (의사결정 학습용) 하되 committed_total / fund pooling / project baseline 에서 제외. all-passed fallback: 한 deal 의 position 이 전부 passed 면 (실투자 0) baseline 계산 시 passed 를 포함 (빈 집계 방지). 구현 = incl() closure (status != "option" && (!has_real || status != "passed")) 를 mcp.rs query_irr + validate.rs 에 공통 적용. 5/29 사용자 배치 정정 동반: (a) Sentry Pre-A = 투자조합 1호; (b) Iippo 1호=1호·2호=2호; (c) Starnex 검토 20억 / entry 8월말; (d) Canopy BW 는 별도 deal 아닌 Canopy 안의 passed position (BW entry 2026-02-28, 4.2% warrant — per-position 모델로 BW IRR 28.0% < RCPS 31.8% 정합, 이전 별도 deal CanopyBW 삭제); (e) Hancom 검토 5억 / 납입 7월말 / passed; (f) Sendy 검토 10억 / Pre-C / passed; (g) Novachips entry 2026-06-10 / passed. proceeds 비례 재척도 (사용자 “IRR 보존”) — 규모 변경 시 leaf proceeds × (new/old) 로 IRR 불변 유지 (Starnex 38.1% / Hancom 23.9% / Sendy 11.7%). migration 0003 = passed status. index commit f650bec. | 2026-05-29 |
| D-index-29 | status=‘option’ (pro-rata 권리 미행사) — baseline·fund·per-position IRR 전면 제외 — 2026-05-29 사용자 “Series A Option 은 무슨 표현?” 후속 재분류. status=‘option’ = 향후 행사 가능한 pro-rata 권리 (아직 집행도 검토결정도 아님) — committed_total / baseline / recovery / fund pooling 에서 전면 제외, per-position IRR = null (행사 전이라 cashflow 미존재). passed (D-index-30) 와의 차이: passed 는 검토 완료·what-if IRR 보존, option 은 권리만 보유·IRR 미산출. 대표 케이스 = Sentry 후속 5억 pro-rata option (1호 단독 5억 집행 + 후속은 권리만) → status=‘option’ 으로 재분류 (이전 committed 로 잘못 집계되던 것 제거). fund_investment.status enum 최종 = option / passed / committed / paid / locked_in / written_off / partial_exited / exited. migration 0002 = option status. index commit 9523638. | 2026-05-29 |
| D-index-28 | 미집행 position entry timing — planned date + never-silent flag + fund pooling 클램프 버그 수정 — 2026-05-29 사용자 지적 “2호 납입 시점이 한 달 다른데 IRR 이 완전 동일한 게 말이 되나”. 진단: 계산식은 정확 (격리 테스트 IippoTT — 2호 +1개월 44.24% / +4개월 47.40%, MoIC 5.685x 불변; exit=회사 단일 청산 calendar 고정이라 늦은 entry=짧은 hold=높은 IRR). 동일값 원인 = 2호 paid_date=null → 코드가 offset 0 으로 silent fallback → 1호와 hold 동일. 수정 3종: (a) seeds/iippo.yaml 2호 entry_date=2026-06-15(가안/planned, 사용자 결정), paid_date=null 유지(미집행) → 코드 paid_date.or(entry_date) fallback → 2호 IRR 44.24% (1호 43.27%, +0.97pp). (b) never-silent flag — entry_basis ∈ {paid|planned|assumed} + note (paid_date 없으면 ”⚠ 미집행 … 보수적 하한” 명시; query_irr per_fund + fund_performance per_position). (c) fund_ecf_irr 풀링 버그 — deal_ref_offset 의 .max(0.0) 제거: fund position 이 deal lead 보다 늦게 진입(deal_ref < fund_epoch)하면 음수 offset 이 정상인데 클램프가 hold 항등식(exit_t − paid_offset = leaf.timing − deal_paid_offset)을 깨 단일 position 펀드 IRR(43.27%)이 standalone(44.24%)과 불일치 → 수정 후 일치. 부수: flag 가 Hancom/Novachips/Sendy/Starnex/Superman entry date 누락(assumed) 노출 (이전 silent) → B-index-entry-date-backfill. index commit 85c8481. | 2026-05-29 |
| D-index-27 | per-position / per-fund IRR (투자조합 1·2·3호 수익성) + 추가투자 = 별도 position + Iippo 2호 = AXE Private Fund II — 2026-05-29 사용자 4 지시 “추가 투자가 별도 건으로 관리 / 1·2·3호 펀드 수익성 / Iippo 2호 별도 적재·분석” 의 답. per-position IRR: fund_investment row 별 독립 IRR — proceeds split = non-writedown leaf.proceeds × (own_fd/ref_own), writedown recovery × own_committed; timing = leaf.timing − deal_paid_offset (position 납입이 deal lead 보다 늦으면 hold 단축). src/irr.rs::position_ecf_irr / position_moic. per-fund IRR: fund_performance(fund_entity_id) MCP tool — 펀드의 모든 deal 횡단 position pool → 단일 calendar E[CF] IRR (fund_ecf_irr). fund_entity_id = axe_ia_001(1호 블라인드) / axe_ia_002(2호) / axe_ia_003(3호). Iippo 2호 정정: 가공했던 “Series A follow-on” 제거 → 실제 = AXE Private Fund II (개인투자조합) 통한 Pre-A 동일 라운드 1억 RCPS 공동투자 (GP 액스벤처스/강태훈, LP=Iippo측 유치 개인 + AXE 100M, MOSS 미등록·6 PCC·Conditional GO 미집행). 별도 IC Memo = OneDrive Prj_Iippo/ic/memo/Iippo_2호_FundII_IC_memo_v1.md (vehicle 심의, deal thesis = IC v8). index commit 7e313b5. | 2026-05-29 |
| D-index-26 | fat-tail optimistic leg comp-scrub → canonical (overlay 아님) — Iippo/Sentry/Canopy 74% 환상 제거 — 2026-05-29 사용자 “투자성 재검토 필요한 건 없나 → 2번 수행 (upside-leg 재검토)” + “기대현금흐름 IRR 아까 수정 안 됐나” 의 답. D-index-25 IRR(E[CF]) 도입 후 fat-tail multi-leg deal 의 mean 이 mgmt/upside fantasy leg 에 과민 → 외부 comp 로 cap. scrub (comp anchor): (a) Iippo mgmt EV 1financial_outputs_baseline 주석에 IC 원본(pre-scrub) 보존 (Iippo IC v8 37.0%/14.9x, Sentry IC pass 41%/13.2x, Canopy 27%) — audit trail. D-index-24(executed=IC canonical) 와 양립: leaf scrub 은 live E[CF] 계산 입력, 원본은 추적 가능. index commit 8c5e60e. | 2026-05-29 |
| D-index-25 | 기대현금흐름 IRR (IRR of E[CF]) = canonical 수익성 지표; Σp·IRR 은 병기 (폐기 보류) — 2026-05-29 사용자 질의 “확률 고려한 cashflow IRR 의 정확한 표현은?” + “cashflow IRR 이 확률 내포면 그게 더 정합” 의 답. 두 집계 구분: (a) Σp·IRR (per-scenario IRR 의 확률가중 평균) = “수익률의 평균” — 잘못된 집계 (rate 를 평균), legacy; (b) 기대현금흐름 IRR = IRR(E[CF]) = 확률을 cash flow 에 먼저 적용 → 그 기대 CF stream 의 IRR — canonical. toy: 50% 10x + 50% 0.1x → Σp·IRR 10.8% vs IRR(E[CF]) 38.5% vs E[MoM] 5.05x. IRR(E[CF]) 는 fat tail 이 carry 하는 평균값 → 반드시 E[MoM] + P(loss MoM<1) / median 과 묶어 제시 (단독이면 우상향 왜곡). 구현 src/irr.rs::expected_cashflow_irr(committed, leaves) = leaf timing 별 group → E[CF] → compute_irr; prob_loss. query_irr summary = irr_expected_cashflow(canonical) + expected_moic + prob_loss + legacy weighted_irr_* 병기. Σp·IRR 폐기는 사용자 승인 후 (현재 보류 — “우선 추후 폐기”) → B-index-deprecate-sump-irr. index commit 2d2066f. | 2026-05-29 |
| D-index-24 | executed deal = IC-approved 기준 canonical (decision-time 기록 보존); 현 practice 재calibration = retrospective overlay — 2026-05-29 사용자 지시 “우선 13.8% 로 IC 승인 후 집행한 건이므로 13.8% 를 적재하라”. D-index-23 Infinity 3-step 비교(surface 13.8% → reviewed −8.3%) 후속 — 어느 값이 canonical 인가의 결정. 원칙: deal.stage 가 Closed/집행됨이면 canonical DB 기록 = IC 가 실제 승인·의사결정한 기준 (Infinity 13.8%, v8 FINAL irr_locked) — 집행된 deal 의 IRR 은 사후 재작성하지 않는다 (회수 시 actual 대조 baseline + 의사결정 history 무결성 보존). 현 practice 재calibration(−8.3%) 은 seeds/infinity_recalibrated.yaml + v9 memo 에 retrospective overlay 로 보존 (post-mortem·learning lens, DB 미적재 — 동일 deal_code 충돌). executed-vs-passed 규칙: Sendy(Passed) = 집행 결정 부재 → calibrated(6.5%)가 canonical (보존할 의사결정 없음, D-index-21); Infinity(Closed) = IC-approved(13.8%)가 canonical, recalibration 은 overlay. 즉 index canonical 선택 = (passed → 현 practice 재작성 canonical) vs (executed → decision-time 기록 canonical + 별도 overlay). 8-deal canonical 랭킹 (⚠ pre-E[CF] Σp 기준 — 현 canonical 랭킹은 D-index-34 E[CF]): Sentry 40.3 > Iippo 36.5 > Starnex 36.4 > Canopy 26.4 > Hancom 23.2 > Infinity 13.8 > Novachips 7.8 > Sendy 6.5 (overlay 적용 시 Infinity −8.3 최하위). seed: infinity.yaml(canonical 13.8% v8) + infinity_recalibrated.yaml(overlay −8.3% v9). index commit (canonical swap) 후속. | 2026-05-29 |
| D-index-23 | 3-step 검증 프로토콜 (surface 적재 → 현 practice 재수행 → 비교) — Prj_Infinity(INEX) 13.8% → −8.3% sign-flip — 2026-05-29 사용자 지시 “기존 데이터 적재 테스트 → 신규 IC 재수행 → 두개 비교로 전 건들과 같게”. D-index-19(surface↔calibrated gap)·D-index-21(현 practice 재작성) 를 명시적 3-step 으로 정식화 — surface 를 별도 seed 로 적재(audit snapshot)하여 gap 을 DB 에서 정량화. 대상 INEX (가상자산 거래소/VASP, 보통주 bridge 0.4997억 / POST 500억 / Closed). Step 1 (surface): v8 FINAL(irr_locked) 그대로 (= 현 canonical infinity.yaml) → index 가 13.8% / MoM 1.70x / success 76.5% 정확 재현. Step 2 (현 practice 재수행 + 외부 research): 4 anchor — (a) 실명계좌 base-rate near-zero (2021 특금법 후 원화마켓 신규 진입 0건, 5사 closed club, FIU 옥죔, P<10%) → surface success 76.5% 는 mgmt optimism anchor, (b) 코빗 won-market ceiling 1,000-1,400억(Mirae MOU 25.12) → INEX coin-only 는 그 below, (c) Gopax/Binance license-shell M&A 선례 (실패 coin-only entity 도 SI 매수, 2.7yr 규제 slog + discount) → 모달 exit = won-market rerating 이 아닌 SI shell M&A breakeven, (d) 보통주 distress recovery 0~20% (RCPS 우선권 후순위) → genuine wipeout leg. Step 3 (비교): calibrated infinity_recalibrated.yaml(overlay) = −8.3% / MoM 0.82x (upside 0.10 / base 0.42 breakeven / downside 0.30 / wipeout 0.18 @ 0.12x). ~22pp gap + sign-flip. 핵심 시사점: 성숙·정직한 IC memo 도 핵심 base-rate 를 mgmt optimism 에 anchor 하면 결론 역전 — v8 은 Event-Driven all-or-nothing 명시 + 종합 IRR 13.8% < hurdle 20% 공개 + 3 reviewer + irr_locked 로 매우 성숙했으나, success 확률을 실명계좌 optimism(실제 base-rate <10%)에 anchor + 보통주 wipeout leg 누락 → reviewed E[IRR] sign-flip. “return 이 hurdle 미달임을 공개”하는 것과 “probability 가 base-rate anchored”인 것은 별개 — index value = base-rate-anchored 확률 + genuine wipeout 강제 (surface honesty 와 독립). proceeds 메커니즘은 v8 이 이미 정확 (ev_bridge·net debt 0 보통주·exit-diluted 0.0724% — D-index-22 모범) → 재계산 0, gap 은 순수 analytical. 8-deal 랭킹 (⚠ pre-E[CF] Σp 기준 — 현 canonical D-index-34): canonical = IC-approved 13.8% (executed deal — D-index-24): Sentry 40.3 > Iippo 36.5 > Starnex 36.4 > Canopy 26.4 > Hancom 23.2 > Infinity 13.8 > Novachips 7.8 > Sendy 6.5; recalibration overlay(−8.3%) 적용 시 Infinity 최하위. 단 INEX 는 조합 6.6%(0.5억) Event-Driven 소액 optionality bet 라 invested 자체는 방어 가능 (license scarcity + SI LOI floor). 산출물: OneDrive Prj_Infinity/ic/memo/v9_axe_current_practice_260529.md + seed infinity.yaml(canonical 13.8% v8) + infinity_recalibrated.yaml(overlay −8.3% v9). index commit c0dcd9a + canonical swap. | 2026-05-29 |
| D-index-22 | proceeds 산정 강화 — EV→EqV(net debt) bridge + exit-dilution 명시·강제 (manual scalar 불가) — 2026-05-29 사용자 audit “모두 EV = EqV + Net Debt 개념과 투자 후 dilution 을 적절히 고려해 계산됐는지 체크” 의 답. 발견 — 7-deal proceeds 도출이 비일관: (a) net-debt bridge = Canopy 만 명시 (EV 797억 − net debt 300억 = EqV 498억; KB캐피탈 fleet 차입이라 가장 필요한 deal 을 옳게 처리), Sendy(v2)·Iippo 는 stake × EV 직접 (EqV bridge 누락), pre-IPO 3 + Sentry 는 시총/PER equity multiple (bridge 구조적 N/A — 정상); (b) dilution = pre-IPO 3 + Sendy 는 exit-date 희석 stake 명시, Iippo·Sentry 는 entry F/D flat (중간 라운드 희석 미반영 → proceeds 수 pp 과대 낙관). 결정 (D-index-20 의 “IRR deterministic·override 불가” 를 proceeds 로 확장): proceeds 도 manual scalar 가 아닌 명시 도출. financial_model.proceeds_basis enum 4종 — ev_bridge = exit_stake × (exit_ev − exit_net_debt) / equity_value = 시총·PER (equity multiple, net-debt N/A) / mom = committed × MoM (pre-IPO check 기반) / legacy_ev = EV-based pre-hardening (per-leaf bridge 미itemize, retrofit 대기). 강제 2중: ev_bridge non-writedown leaf 는 stake×(EV−net debt) 가 axe_proceeds 와 ±2% 일치해야 — (1) seed load-time hard reject (SeedFile::validate) + (2) validate_financial_model Check 7 exit_proceeds.bridge Error. negative test 로 거부 확인 (stake 10% × EV 2,225억 = 222.5억 ≠ 날조 300억 → “proceeds 불일치 >2%” reject). migration 0005 = proceeds_basis + exit_ev_krw/exit_net_debt_krw/exit_stake_pct 컬럼. Sendy 정정 (v3): proceeds_basis=ev_bridge, net debt = 0 (asset-light 플랫폼 트럭 미보유 + 자금니즈를 debt 아닌 equity[Series C]로 충족 + dataroom 차입 증거 0 → 보수적; 없는 부채 날조 안 함), exit_stake 10% = entry 12.5% 의 20% 희석. → proceeds·IRR 불변 (E[IRR] 6.47%) 이나 EV≠EqV 누락 class 를 구조적으로 영구 차단. “정정 = 구조적 (bridge 명시 + 엔진 강제), 숫자 불변” 이 evidence 상 정직한 결과. 7-deal 결과: Sendy ev_bridge PASS(3 leaf reconciled) / Sentry equity_value PASS / Starnex·Hancom·Novachips mom PASS / Iippo·Canopy legacy_ev WARN — 0 errors. 잔여 gap (정직 기록 → known-gaps): (1) Iippo·Canopy per-leaf EV→EqV bridge 미itemize → B-index-proceeds-bridge-retrofit, (2) Iippo·Sentry exit-dilution = entry F/D flat (중간 라운드 희석 미반영; RCPS anti-dilution/pro-rata 부분 상쇄 — Closed deal 이라 사후 실익 낮으나 IRR-정직성 차원). index commit ca714b7. | 2026-05-29 |
| D-index-21 | historical deal 축적 = 현 practice 재작성 (raw 축적 아님) — 3-tier IRR + v1 5-flaw 교정 — 2026-05-29 사용자 redirect “그냥 적재하지 말고 ic memo 를 거의 현재 우리 practice 수준으로 새로 써본다 생각하고 임하라 (당시 작성 방법론은 현재보다 열위일 가능성 높음, 추가 조사 가능)” 의 답. D-index-19 가 “xlsx surface cell → calibrated” 였다면 본 결정은 그 논리적 종착 — 전체 memo 방법론 자체를 현 5-tenet practice 로 재작성. 3-tier IRR story (index 의 reviewed-truth 가치 실증): (a) v1 md 기재 = Expected 31% / Devils 22.8% (per-scenario 62/35/-8, Excel 과 모순된 fabricated override — D-index-20 가 architectural 차단), (b) v1 Excel 실제 = Base 95.7% / weighted 92.4% (math-correct 이나 낙관 input — D-index-20 이 자동 산출), (c) v2 현 practice 재calibration = E[IRR] +6.5% ⭐ canonical (외부 comps anchor + genuine wipeout leg). v1 의 5 결함 교정: (1) IRR override 95.7→35 fabrication (D-index-20), (2) wipeout leg 부재 — v1 의 Downside 가 +31.6% gain 이라 손실 시나리오 자체 없음 → genuine 18% wipeout leg 추가 (보통주 floor 0 / 부릉 1/6 precedent recovery 0.15x), (3) sendyX 66x 미검증 (beta 25.12 고객 5곳 월 375만원 → 2026E 25억 = 1년 66배 bottom-up 무근거) → base-rate haircut (mgmt 919억 대비 -93% deep-discount, B2B SaaS PMF base rate 정합), (4) Exit 5x flat → segment-weighted (freight matching 1.01_Project/Pipeline/Sendy/ic/memo/v2_axe_current_practice_260529.md (5-tenet · 4-scenario · deterministic IRR · premortem · v1→v2 교정표) + seed v2 = /Users/axe/index/seeds/sendy.yaml (financial_model version:2). | 2026-05-29 |
| D-index-20 | manual IRR override 불가 (deterministic compute SoT) + declined-deal (Passed stage) tracking — 2026-05-29 센디(Sendy) 축적 시 발견된 flagship 사례, index 존재 이유의 결정적 증거. 센디 ic/finance/exit_matrix/v1.yaml line 76: irr_approx_pct: 95 # (375/50)^(1/3)-1 ≈ corrected: 35% — 계산값 95.7% 를 근거 없이 “35%” 로 손수 override. 이 오류가 md 전반 전파 (Base 35% / Downside -8% / Expected 31% / Devils 22.8%), 그 중 Downside -8% 는 구조적 불가 (수취 114억 > 투자 50억 → IRR 반드시 양수). 문서 자체가 “수정 필요 상태 (현재 미수정)” 명시했으나 미교정 잔존. index 정책: IRR 은 exit_matrix_leaf 의 proceeds + timing 에서 single_flow_irr deterministic 계산 — financial_output 에 IRR 직접 쓰는 path 없음 (manual override 물리적 불가). 센디 seed (v1) 를 EV-derived proceeds (Exit EV × stake 10%) 로 작성 → index 가 Base 95.7% / Downside 31.6% / Upside 146.6% / weighted 92.43% 자동 산출 = Excel 실제값 정확히 일치 (md override 자동 교정). md 의 erroneous 값은 financial_outputs_baseline citation 으로만 보존 (audit). 단 이 92.43% 는 math-correct 이나 v1 의 낙관 input (sendyX 66x + Exit 5x flat + wipeout leg 부재) 산물 — 초기 축적값일 뿐 canonical 아님; 사용자 redirect 로 현 practice 재작성 → seed v2 = E[IRR] 6.5% 로 supersede (D-index-21). 즉 본 결정은 “computed override 차단” (architecture), D-index-21 은 “낙관 input 자체를 현 practice 로 재calibration” (analysis) — 2-layer. 부수 결정 — deal.stage 에 ‘Passed’ 추가 (migration 0004): index 가 GO/Closed deal 만이 아니라 PASS 결정 (IC 검토 후 미투자) 도 first-class institutional memory 로 보존. 센디 PASS (2026-02-15) 사유 = 구조 (10억서 AXE CB 외 equity 참여 제한) + 타이밍 — quality/IRR 아님 (물류 round-trip 최적화 긍정 평가, “할 수 있었으면 좋았을 deal”). 7-deal portfolio = Closed 3 (Iippo/Sentry/Canopy) / IC 3 (Starnex/Hancom/Novachips) / Passed 1 (Sendy). 시사점: (1) D-index-19 가 “surface vs reviewed gap” 였다면 본 결정은 “computed vs manually-overridden gap” — index 가 후자를 물리적 차단, (2) screening-grade model honesty — 92% 는 math-correct 이나 Exit EV 낙관 (sendyX 2026E 25억 = beta 5곳 월375만원 대비 66x 미검증); inputs_hash 가 가정 재현성 보존하되 가정 타당성 판단은 별도 (validate sanity 확장 여지), (3) declined deal 이 “왜 안 했는가 + 그 시점 IRR” 보존 → 센디 Q4 post-mortem (sendyX 상용화/BEP) 시 actual 대비 검증 가능. seed = /Users/axe/index/seeds/sendy.yaml. | 2026-05-29 |
| D-index-19 | reviewed truth 축적 — index 는 calibrated 검토 본 저장, xlsx surface cell 아님 — 2026-05-28 사용자 요청 “3건 (스타넥스/노바칩스/한컴인스페이스) 찾아서 축적하되 그대로가 아닌 검토 + 시사점” 의 답. 3 pre-IPO deal 을 _comparative_20260528_preIPO/ (13-agent 비교 IC 메모) 의 common-protocol calibrated 본으로 index 축적. 핵심 발견 — surface IRR ≠ reviewed IRR (최대 36pp gap): 스타넥스 surface(IC v3) 19.6% → calibrated 36.4% (+17pp), 한컴 surface 58.5%(P 거래소 85%) → calibrated 22.4% (-36pp, KOSDAQ 재청구 base rate 40~55% anchored 65% 로 down-cal), 노바칩스 surface -1.5% → calibrated 7.45% (+9pp). 만약 xlsx surface cell 을 그대로 축적했으면 한컴이 1위로 오기록 (실제 calibrated 랭킹 스타넥스 > 한컴 > 노바칩스) → index 의 본질 = typed reviewed truth 보존 (D-index-1 실증, raw cell dump 아님). 5 시사점: (1) surface vs calibrated gap → index 가 검토 본 저장하는 가치 정량 실증, (2) instrument structure 가 IRR-material (8~15pp) — 노바칩스 보통주 구주 instrument-adjusted -4.55% vs surface 7.45% = 12pp; 현 schema instrument enum 은 있으나 irr_instrument_adjusted 자동계산 없음 → B-index-instrument-adjusted-irr gap, (3) probability calibration provenance (base rate anchor) 가 scenario.description 에 박제 → “왜 65%?” 추적 가능, (4) 동일 exit_matrix_leaf schema 가 Pre-A multi-leaf (Iippo/Sentry/Canopy) + pre-IPO single-leaf (각 scenario=단일 outcome) 양쪽 수용 — schema 유연성 검증; timing 이 bucket(3y/5y/7y) → 소수(2.5y/4.5y) 확장 필요 (parse_timing_years + CHECK 완화 migration 20260528000003), (5) 변곡점: IC 산출물 완성도 ≠ deal quality — 노바칩스 (5 estimator × 8 scenario, 가장 정교) = 최약체 (IRR 7.8% 최하위 + Sharpe -0.02), index cross-deal benchmark 가 “polish bias” 객관 노출. 검증: 6-deal portfolio IRR 랭킹 Sentry 40.3 > Iippo 36.5 > Starnex 36.4 > Canopy 26.4 > Hancom 23.2 > Novachips 7.8, pre-IPO 내부 랭킹 AXE 비교메모 결론과 일치, validate-model 6/6 PASS. seed = /Users/axe/index/seeds/{starnex,hancom_inspace,novachips}.yaml. | 2026-05-28 |
| D-index-18 | DSL formula = “single driver + period-indexed values” 패턴 강제 (Y-suffix multi-driver 금지) — 2026-05-28 사용자 질문 “Excel model → DB화 best practice” 의 답. 본 service 의 모든 financial_model 은 driver 를 시간축 분할 (revenue_growth_y1, revenue_growth_y2, … revenue_growth_y7 처럼 7개 개별 driver) 하지 않고, 단일 driver (revenue_growth) + period 0..N 별 값 으로 표현. 이유: (a) DSL 의 lag 연산자 (revenue[y-1]) + current_period 자동 lookup 이 자연스럽게 작동 — formula revenue[y-1] * (1 + revenue_growth) 이 period 0 에는 growth[0], period 1 에는 growth[1] 을 자동 읽음. (b) Y-suffix anti-pattern 의 함정 박제 — compute_full_model (commit 9df7f71) 의 Iippo base output: 모든 period 에 revenue_growth_y1=6.5 만 reuse → 7년차에 revenue 1.2 quadrillion 원 비현실적 compounding, seed comment “Y2+ override 별 row” 의도였으나 DSL 이 단일 formula 만 지원 → 모델 거짓 양성. (c) Excel 의 “input row” 패턴과 동형 — Excel 에서도 growth rate 는 1 row × N column (period 별), 별도 7 row 가 아님. 현재 seed 정정 필요: Iippo 의 revenue_growth_y1~y7 7 개 driver → 단일 revenue_growth driver + period 0..6 별 driver_value rows. Sentry/Canopy 는 exit-matrix-only 라 영향 없음. revenue formula 도 단순화 (revenue[y-1] * (1 + revenue_growth) 로 충분). 본 결정으로 도입되는 backlog: B-index-driver-period-indexed-refactor (Iippo seed 정정 + re-ingest + compute_full_model 재검증 + IRR ±1pp 회귀). best practice 결정 4종 (D-index-15 frozen enum 의 modeling 차원 확장): (1) driver code = snake_case 명사, 시간축 suffix 금지, (2) period-varying value 는 driver_value rows (period_index 컬럼), (3) formula 는 단일 표현 — period-별 분기 금지 (해당 시 lag/multi-period 패턴 사용), (4) base_revenue_period0 같은 sentinel 은 financial_model row 의 명시적 컬럼. LP-deliverable 관점: 동일 model 을 1년 후 재계산해도 deterministic — driver_value 의 period 별 값이 audit trail 의 단일 SoT. Excel 의 =A2*(1+$B$3) 같은 cell-reference anti-pattern 영구 차단. | 2026-05-28 |
| D-index-17 | ic + ingest skill ownership Blueprint → index 이전 (overlap 기간 양쪽 보유) — 2026-05-28 사용자 결정. 도메인 alignment: ic skill (19-agent IC memo orchestration) 은 투자 도메인 → index 가 자연 owner. ingest skill (dataroom pdf/pptx/xlsx → md clone) 은 ic 의 직접 의존이라 함께 이전. 이전 방식 = overlap (copy), symlink 아님 — 안정화 기간 동안 양쪽 모두 byte-identical 보유, index/skills/ = canonical SoT, /Users/axe/blueprint/.claude/skills/ = mirror (deploy 호환성). Phase 1 후속 대상: due-diligence, vc-deal-sourcing 도 도메인 정합 (별도 결정). portfolio-management + investor-relations 는 D-index-12 에서 이미 pmc skill 흡수 결정. ctx / humanizer / report-writing / vault-secret-capture / office-admin / code-review 등 일반 skill 은 Blueprint 잔존. 안정화 sunset 기준 3가지 (모두 충족 시 Blueprint mirror 제거 — 별도 결정): (a) 다음 3 deal 의 IC 실행이 index/skills/ic/ 기반 정상 작동, (b) Blueprint Claude Agent SDK 가 index/skills/ mount/sync 자동화 확립, (c) ic --push-to-index mode 일반화 (B-ic-push-mode-impl). 본 결정 + sunset 작업 = B-index-skill-overlap-sunset. Phase 2 진화 path (별 backlog): B-index-skill-mcp-discovery (index MCP 에 list_skills + get_skill_resource tool) + B-blueprint-skill-mcp-client (Claude Agent SDK 가 MCP 로 skill 발견, filesystem 의존 제거) + B-index-skill-versioning-schema ([email protected] schema + audit_event — per-deal “이 IC 의 메모는 ic skill v8.3 으로 작성” trace). Overlap 기간 sync 책임 = index 편집 후 Blueprint mirror 로 sha256 검증 cp. Blueprint hook (skill-sot-guard.sh) 가 main 직접 편집 차단 → drift 방지 자동화. 상세 = /services/index/skill-ownership. | 2026-05-28 |
| D-index-16 | IRR methodology 단일화 — per-leaf weighted 만 사용, anchor methodology deprecate — 2026-05-28 사용자 결정. 본 service 의 모든 deal IRR 산출은 per-leaf weighted methodology (each leaf: (proceeds/committed)^(1/timing) - 1 산출 후 joint_probability 가중 합산) 로 통일. 별 anchor methodology (V5 final 의 F/D Pre anchor + Series A 희석 chain + success_probability 단일 flow) 폐기. 이유: (a) 데이터 정합으로 충분 — V1 yaml outdated → V5 final 의 3 보강 layer (광고 element 본문화 + 서울시 4자 합의 + KB 캐피탈 capex debt backbone) 반영 시 per-leaf 가 자연스럽게 27% 매칭. Canopy V1 → V5 정합 3-step (writedown recovery_rate 도입 + scenario probability rebalance + never writedown 비합리성 정정) 으로 -17.41% → 26.39% 달성, IC pass 27% baseline Δ=0.01 PASS. (b) 단일 methodology 운영 단순성 — deal 마다 다른 methodology 선택 logic 불필요. financial_model schema 에 irr_methodology enum 추가 불필요. (c) 3 deal per-leaf ALL ±1pp: Iippo 37% (computed 36.52%) / Sentry 41% (40.26%) / Canopy 27% (26.39%) — 일관성 검증 완료. 본 결정으로 폐기되는 backlog: B-index-canopy-anchor-irr-algo (anchor IRR 별 algorithm) + B-index-canopy-v5-anchor-methodology (anchor 재구현). 본 결정으로 발견된 신규 발견 = V1 yaml 의 outdated assumption (upside scenario 10% never writedown, base 20% never writedown 같은 구조적 결함) 을 ic skill --push-to-index 시 자동 surface 가능 — within-scenario writedown probability 합리성 check 가 financial_model validate trigger 로 추가 (B-index-leaf-probability-sanity 신규). src/irr.rs 의 anchor_irr + canopy_anchor_grid 는 #[deprecated] 표시 후 Phase 1 에서 삭제. | 2026-05-28 |
| D-index-15 | Schema authority immutable — DSL grammar + enum 은 service code 에만, skill 쪽 override 불가 — index 의 financial_driver.formula DSL grammar (10 operator: + - * / min max if sum avg lag growth) + risk_alert.kind enum 5종 + instrument enum 5종 (RCPS/BW/SAFE/신주/보통주) + fund_investment.status enum 6종 + exit_matrix_leaf.path enum 6종 (ma/ma_strategic/ipo/secondary/writedown/partial) 모두 service code 에 hardcoded, customers.yaml / 환경 변수 / runtime config 으로 override 불가. ic skill 의 scenario_deltas.yaml 이 DSL syntax error 또는 unknown enum 값 emit 시 ingest_financial_model_xlsx 가 error.code = "formula_syntax_error" 또는 "unknown_enum" + suggestion 본문 반환 → ctx review queue 에 highlighted error + 정정 예시 표시 → 사용자 수정 후 confirm. skill schema drift → service crash 시나리오 영구 차단 (worst-case risk #1). 새 enum value 추가는 (a) src/index/schemas.rs SoT 수정 + (b) /index/schemas envelope @1.0 → @2.0 version bump + (c) Blueprint artifact_schema 자동 mirror + (d) ic/pmc skill 의 yaml validator 갱신 — coordination PR 강제 (silent 추가 금지). frame 의 KSME accounting_standard enum 영구 freeze 패턴 (D-frame-fund-ksme-policy-check) 의 index domain 확장. | 2026-05-27 |
| D-bp-entity-canonical | Entity canonical SoT 의 계층화 — Blueprint 가 entity 의 canonical name + customer 소속 + role SoT, 도메인 메타는 도메인 service SoT, customers.yaml 은 deployment 식별 SoT — 운영자 질문 (“id, entity 정보는 blueprint 가 SoT 가 되어야 할 것 같습니다”) 을 architectural 결정으로 정착. 현재 entity 정보 4 곳 분산 (customers.yaml.entities + customers.yaml.entity_meta + frame.shared.entity + Blueprint EntityRole + Blueprint User.entityScopes) — drift 위험 + bus factor. 계층화된 SoT: (a) customers.yaml.entities = customer→entity 식별 (deployment prerequisite, 부팅 self-contained, 변경 X), (b) Blueprint Entity 테이블 신설 = entity 의 canonical name (한글) + customerId + role (corporate/GP/etc) + isActive — 사용자-facing + 인증 게이트 + UI selector 의 자연스러운 위치, (c) frame.shared.entity = 회계 도메인 메타 (accounting_standard / fund_meta / biz_no / entity_kind) 유지, (d) Blueprint EntityRole + User.entityScopes = 권한 (기존 SoT 유지). 본 2026-05-27 세션의 customers.yaml.entity_meta 신설은 transitional — Blueprint Entity 테이블 신설 후 derived view 또는 cross-check drift 검증으로 전환 (B-customers-yaml-entity-meta-derive). 단계화 3-phase: Phase 0 (본 결정 등재 + backlog 2 항목 등재) / Phase 1 (B-bp-entity-table-create, Prisma Entity model + customers.yaml + frame.shared.entity union seed) / Phase 2 (customers.yaml.entity_meta 를 axe CLI 의 derived view + matrix collector 에 drift 검증 추가). frame/hive 의 legal_name 영문 → 한글 정정 (B-frame-entity-legal-name-i18n) 도 Blueprint Entity SoT sync 의 일환. | 2026-05-27 |
| D-ui-1 | 통합 layout chrome .axe-app-shell 도입 + ui.axelabs.ai 디자인 시스템 사이트 신설 (Phase 15) — cortex 운영자의 “docs.axelabs.ai 와 묘하게 다르다” 지적 (2026-05-29) 을 출발점으로, 토큰만 SSOT 화하던 기존 상태를 chrome 자체 SSOT 로 확장. 3 결정: (a) variant 옵션 — 단일 .axe-app-shell 이 `data-shell=“docs | dashboard |
| D-ui-2 | layout primitives (Stack/Cluster/Grid/Container) + ThemeToggle 를 @axe/ui SSOT 로 흡수 (Phase 16) — D-ui-1 chrome SSOT 의 micro 레이아웃 짝. 출발점: matrix 페이지가 col(gap)/grid() inline 헬퍼로 flex/grid 를 재발명 + design 페이지가 ThemeProvider 우회해 document.documentElement 를 직접 토글하던 버그. 흡수 5종: Stack (세로 flex+gap) · Cluster (가로 flex+wrap — toolbar/액션 줄) · Grid (auto+min auto-fill 으로 cortex .cortex-metric-grid 대체) · Container (max-width 중앙) · ThemeToggle (light/system/dark, useTheme 위 thin UI — DOM/storage 반영을 Provider 에 위임, 외부 아이콘 dep 0). chrome 과 동일하게 styling 100% CSS 클래스 (.axe-stack/.axe-cluster/.axe-grid/.axe-container/.axe-theme-toggle + --axe-grid-min) → cortex maud/jinja 등 비-React 소비자 동일 markup. gap = spacing scale 1:1 (매직 px 금지). 채택·브라우저 검증 2026-05-29: app/page.tsx (axelabs.ai 홈 — Hero/Section/Footer 컴포지트 + Stack/Cluster/Grid, D-ui-1 step 4 동시 이행) + app/matrix (MatrixNav ThemeToggle + page 전체) + app/design (ui.axelabs.ai). cortex/docs/blueprint 순차 적용 대기. 본문: /services/ui § Layout primitives. | 2026-05-29 |
| D-ui-3 | 디자인 시스템 도메인 ui.axelabs.ai → design.axelabs.ai rename (완전 교체·구 도메인 폐기) + 문서 HTML 템플릿 (IC Memo · LP letter, 가로/세로) 도입 — 운영자 Phase 2 결정 (2026-05-29). (1) 도메인: design.axelabs.ai 가 의미를 더 명확히 전달 (디자인 시스템 + 문서 템플릿 showcase). ui.axelabs.ai 는 ingress 제거로 catch-all 404 (완전 교체, 병행 X). 패키지 이름은 @axe/ui 유지 (도메인 ≠ 패키지) → docs 경로도 /services/ui 유지. (2) 분기 메커니즘 정정: host 분기는 next.config.mjs rewrites() 가 아니라 proxy.ts (Next.js 16 convention) — 정적 prerender 된 / 에 next.config rewrites 가 안 걸리는 함정 우회 (proxy 는 항상 runtime). Host: design.axelabs.ai → root 를 /design rewrite, sub-path 는 root 로 308, axelabs.ai/design/* 는 404. (3) 인프라 (vault CF 토큰으로 처리): design.axelabs.ai proxied CNAME → d8efecdd….cfargotunnel.com 을 axelabs.ai zone 에 CF API (Cloudflare API - axelabs 토큰) 로 생성. tunnel ingress 는 remote-managed (known-gaps Cortex 7함정 #7) 라 config.yml 편집 무효 → PUT /accounts/<acct>/cfd_tunnel/<uuid>/configurations 로 ui→design 1-rule swap (v21→v22, 나머지 15 rule 보존). 함정 2개: ① cloudflared tunnel route dns 는 cert-scoped (axellc.com) 라 design.axelabs.ai.axellc.com 오생성 → CF API 로 삭제, axelabs.ai zone DNS 는 반드시 API 토큰 사용. ② 신규 1-level host 직후 로컬 resolver AAAA-only → IPv6 무라우팅 머신 curl HTTP 000 (production 무관, curl -4/--resolve 로 검증). 검증: design 200 (양 edge·@axe/ui 페이지) · sub-path 308 · ui 404 · axelabs.ai/www/docs 무영향. (4) 문서 템플릿: AXE 발신 문서 (IC Memo · LP letter 등) 를 .axe-doc CSS-class HTML 로 — 가로(landscape)·세로(portrait) 모두 modifier 로 지원 (문서 성격별 고정 X), A4 @page + @media print (브라우저 인쇄→PDF), 기존 md-to-pdf 렌더 파이프라인과 통합 (병렬 렌더러 신설 X). design.axelabs.ai showcase 에 orientation 토글 + 인쇄 버튼. 구현 완료 (2026-05-29): src/lib/styles/document.css (.axe-doc* + named @page 스코프) + src/lib/templates/documents/ic-memo.html.jinja·lp-letter.html.jinja (jinja2 가로·세로 4 combo 렌더 검증) + app/design 쇼케이스 섹션 (문서·orientation·zoom 토글 + 인쇄). 렌더는 기존 md-to-pdf skill (pandoc + chromium --print-to-pdf) + ic skill check_pdf_quality.py 게이트 재사용 (병렬 렌더러 X — body 슬롯에 markdown→HTML 주입). 본문: /services/ui. | 2026-05-29 |
D-bp-mcp-1 — Blueprint MCP standalone (양파껍질 lesson)
Blueprint 자신의 read-only MCP 서버 launch (axe.axelabs.ai/blueprint/mcp, 9 tools). 의도: frame 의 auth_oidc.py + mcp/http_server.py 를 byte-by-byte 미러. 실제: “비슷하게” 작성 → claude.ai 등록 시 5 단계 순차 차단 (PR #331~#335, 약 1.5 h 소비):
| # | 차단 | 메시지 (claude.ai) | 원인 |
|---|---|---|---|
| 1 | server 500 | ”integration may not be available right now” | httpx.Timeout(connect, read) 2-arg → httpx 0.28 ValueError |
| 2 | 진단 불가 | (모든 401 이 같은 메시지) | _unauthorized body 가 reason 무시 |
| 3 | 토큰 검증 401 | ”rejected the credentials, so the connection was reverted” | audience=app_id_uri 단일 (v2 토큰 aud = client_id GUID 임) |
| 4 | 인증 후 첫 호출 RuntimeError | ”Task group not initialized” | Starlette Mount 가 FastMCP child lifespan 미전파 |
| 5 | 421 Misdirected | ”Invalid Host header: axe.axelabs.ai” | FastMCP DNS-rebinding guard, allowed_hosts env 미설정 |
결정: 새 MCP 서버는 /architecture/mcp-server-checklist 의 9·5·5·14 체크포인트 (azure manifest 9 · python 코드 5 · compose 5 · 운영 14) 를 1:1 통과 후 production. 시작은 cp -R frame/src/frame /tmp/NEW_SERVICE 부터, 그 다음 service-specific edits.
D-bp-mcp-2 — Blueprint MCP blue/green pair
D-bp-mcp-1 launch 직후 진단으로 발견: Blueprint MCP 는 frame/hive 의 docker compose 패턴 (&frame-mcp-blue anchor + green sibling) 을 미러하지 않은 단일 replica 였음. docker compose up -d --build mcp 시 build + recreate 5-10s 다운 발생 — D-config-13 의 “다운타임 0 frame deploy” 원칙에 어긋남.
결정: docker/docker-compose.yml 의 mcp: 블록을 mcp-blue: &blueprint-mcp-blue + mcp-green 으로 분리. blue 가 docker network alias blueprint-mcp 보유 (active), green 은 alias 없이 가동 (passive). swap = docker network connect/disconnect --alias + caddy reload, sub-second. cloudflared 비건드림 (D-config-13 §3 frame 패턴과 동일).
host port: blueprint-mcp-blue:3152, blueprint-mcp-green:3153 (디버그용, 외부 path 는 여전히 :3151 Caddy proxy).
D-bp-entity-1 — Blueprint entity 개념 도입 (PARA × entity)
진단 (2026-05-21 5분야 멀티 에이전트): Blueprint 의 Organization 모델이 single-row stub (prisma/schema.prisma:689), Workspace/UsageLog/Agent 에 organization FK 부재 (line 688 “별도 PR” 주석). 동시 customers.yaml 의 axe.entities: ["axec","axev"] (frame/hive 와 정합) 은 이미 회계/HR 차원에서 격리 운영 중. Blueprint 의 PARA 폴더 컨벤션 (/Ventures · /Corporation · /General × 1. Project · 2. Area · 3. Resource · 4. Archive) 도 entity 차원의 격리를 함의.
결정:
| 항목 | 방향 |
|---|---|
| entity 정의 | axec (AXE Corporation), axev (AXE Ventures) — frame/hive 와 정합. General 은 cross-entity bucket 으로 추후 (axec/axev 외) entityId 부재 row 가 대응 |
| 모델 | Workspace.entityId FK (nullable for migration) + Workspace.paraLayer ParaLayer? enum {PROJECT, AREA, RESOURCE, ARCHIVE} — 4 PARA layer 모두 동일 row-level entity scope |
| 격리 강도 | hive 의 schema-per-entity 와 달리 row-level FK + auth gate (D-hive-1 의 SQL boundary 격리 미적용). Blueprint 의 cross-entity workflow (Teams bot · IC pipeline · Trinity) 와 부합 |
| Area/Resource/Archive | 별도 first-class 모델 안 만들고 Workspace 의 paraLayer variant. 고유 속성 발견 시 후속 PR 분리 |
| 적용 시점 | Stage 1 외부 customer rollout 전. customers.yaml 의 axe.entities 가 이미 production 이므로 single-customer 내 multi-entity 만으로도 시급 |
Implementation (PR #339 , 2026-05-22, 단일 PR 4 commit):
- Prisma migration:
Entity모델 +ParaLayerenum +Workspace.entityId/paraLayer+ D-bp-entity-2 provenance 3필드 +User.entityScopes. Idempotent SQL + axec/axev seed. Postgres baseline 부재 →scripts/apply-entity-para-migration.ts운영자 1회 적용 후 매 bootseed-entities.ts가 idempotent upsert. - drivePath 기반 backfill (
scripts/backfill-workspace-entity-para.ts):/Ventures/...→ entityId=axev,/Corporation/...→ entityId=axec,paraLayer=1_Project/2_Area/3_Resource/4_Archivesegment match. dry-run + apply 분리. src/lib/entity-scopes.ts(resolve/hydrate/get/require + entityScopeWhereFilter) +src/lib/auth.tssignIn hydration +src/lib/workspace.tslistWorkspaces opt 확장 +/api/workspaces/*게이트.- UI:
/axe/projectsentity dropdown + 항상paraLayer=PROJECTfetch //axe/arastub → ARAClient 3-tab + fork badge.
사용자 결정 (2026-05-22): Workspace ↔ Entity = scalar 1:N (OneDrive /Ventures/ /Corporation/ 폴더 분리가 SOT, cross-entity 는 User.entityScopes 로 처리). frame fund 회계 N:M 권고는 reject.
Production hotfix (PR #341 , 2026-05-22): verification 중 발견한 3 drift — (a) docker-compose.yml app-green block 에 customers.yaml mount + CUSTOMERS_YAML_PATH env 누락 (blue 만 있던 mount 가 cutover 시 옮겨지지 않아 entityScopes hydration 항상 admin-fallback), (b) scripts/apply-entity-para-migration.ts 의 header comment 안 $$ 가 splitter inDollar flag 오염 → P2010, (c) backfill regex [.\s] 에 underscore 추가 (1_Project 매치).
Production 결과: Entity 2 row (axec/axev seed) · Workspace 18 row 중 16 backfill 적용 (PROJECT × axec: 1 / PROJECT × axev: 14 / PROJECT × null: 2 / AREA × axev: 1 [HR]) · 5 user entityScopes hydrate · Chrome MCP /axe/projects + /axe/ara 검증 PASS.
D-bp-entity-2 — PARA dispatch flow sub-consensus
D-bp-entity-1 머지 후 Project 종결 → Area/Resource 이관 (dispatch) 흐름. 본 결정은 dispatch UI (PR #339 의 schema 까지만 포함, UI 는 PR 5 예정) 의 4 합의:
| # | 합의 |
|---|---|
| 1 | Copy-with-provenance (Fork) — Project row → Area/Resource row 는 dispatch 시점의 snapshot copy. 원본 archived workspace 에 freeze. sourceWorkspaceId / sourceArtifactPath / copiedAt 3 필드 (D-bp-entity-1 schema 에 통합) |
| 2 | 검색 분리 — Archive = “그때 어떻게 생각했는지” (시점형, 의사결정 archaeology). Area/Resource = “지금 이렇게 생각한다” (현재형, living knowledge). Search surface 가 PARA-aware |
| 3 | 끊임없는 정비 workflow — Dispatch 는 일회성 migration tool 이 아님. 복사본은 각 Area/Resource 의 목적에 맞게 지속 편집되는 living document. PARA 철학 (Tiago Forte) 핵심 |
| 4 | 자동 분배 ≡ LLM 제안 + 사용자 확인 — NEVER fire-and-forget. Suggestion-based, non-destructive |
미해결 (dispatch UI PR 직전 결정): (a) Dispatch unit (file vs semantic chunk), (b) Area instance 정의 권한 (org-admin vs free), (c) 정비 트리거 우선순위 (manual / periodic / LLM-detected staleness). 권고 경로: Path B Spike (DB 변경 없이 단일 workspace 로 흐름 검증 3-5일) → Path A 본구현 (Area/Resource UI + dispatch modal + archive search separation).
관련 진단: /Users/axe/.claude/projects/-Users-axe-blueprint/memory/feedback_multi_agent_diagnosis_2026_05_21.md
D-bp-para-1 — PARA 범용 재구조화 (조직론 + 집(link) 모델 + governance + 죽은 딜 본질)
D-bp-entity-1/D-bp-entity-2 의 AXE-shaped 초기 설계(copy-with-provenance · Area=workspace)를 범용 AI OS 관점으로 재구조화 (2026-06-06 설계 세션). 상세·철학 = /architecture/para-os. 미구현 dispatch/UX zone 한정 — 저장층(Artifact/ArtifactLink/McpSchema, PR #339 ) schema 는 유효.
확정 결정:
- PARA = 조직론 — Area=영속 기능(정적 조직, MCP=물질화), Project=cross-functional TFT(동적 조직). Resource·Archive 는 각 Area 안, Project 만 가로지름.
- dispatch = “집(home)” 모델 — move/copy/link 3지선다는 폴더-thinking 증상. artifact 몸통 1 + 집 N(link). link=기본(다중 Area 참조), move=소모성 working→Archive→폐기, copy-curate=재사용본 저작→Resource. → D-bp-entity-2 의 copy-with-provenance default 를 link-default 로 revise (copy 는 method→Resource 큐레이션 예외만).
- Blueprint = 도메인 서비스가 안 덮는 것의 substrate (미졸업·무서비스 Area live + 그 R/A + cross-service 종합물 + 가로 결정로그 mirror). 졸업 서비스(frame/hive/index/gate)는 자기 도메인 지식(죽은 딜·섹터 프레임 등 R/A 포함)을 가져감 — moat(죽은 딜 복리) = index. Area 졸업 트리거 = 도메인 로직/AI 상호작용(저장 아님). [정정 2026-06-06: 원안 “모든 R/A → Blueprint” 를 졸업 기준으로 좁힘 (사용자 확정), 상세 para-os §4]
- substrate 3분 — typed Area=쿼리 / Resource=SoR+벡터 index(hot) / Archive=cold·폐기가능(추출 지식만 영구·queryable). 벡터=저장소 아닌 index.
- Governance 분리 — 결정로그(record: Blueprint core, append-only, supersede, 모든 Area cite) vs 결재워크플로(process: 전자결재, policy-driven·agent-assisted layer). 서명 한 종류(내부=외부 e-sign). Area 서비스=결정 actuator. 통합 구현=B-bp-decision-pipeline-esign.
- 본질 = 죽은 딜 저장·harvest — VC 지식의 대부분이 dead deal(judgment “왜 패스” + 섹터지식)에. Archive raw=폐기 가능하되 추출 지식=영구. index 가 passed/dead 딜을 1급 저장해야 하는 근거.
열린 질문 (확정 시 후속 D 등재): (a) 2-level scope(personal+shared), (b) governance servicization(Blueprint core vs 독립 서비스 — 부족함 진단 저장/로직/AI 로 가름), (c) Project staffing, (d) Rust 적용 범위(기존 Python 서비스 확장 언어).
제약: 신규 코드 = Rust (사용자 지침). SoT 메모 = project_para_ai_os_philosophy.md (Blueprint global memory).
D-bp-rust-1 — Blueprint 점진 Rust 전환 (strangler-fig) · substrate = 첫 organ
D-bp-para-1 의 열린 질문 (a)/(c)/(d) 를 확정하고, “신규 코드 = Rust” 지침을 Blueprint 토폴로지에 정착 (2026-06-06 세션). 사용자 발화: “점차 blueprint 도 rust 로 전환할 계획입니다 … 점진적 대체로 하려고 합니다.”
전략 — strangler-fig (점진 대체):
- 프론트엔드(React/Next.js UI) = 당분간 TS 유지. “Blueprint→Rust” = 백엔드 전환이지 UI 재작성이 아님. WASM 프론트(Leptos/Dioxus) 전면 재작성은 별개·먼 결정 (현재 scope 아님). → 열린질문 (d) 확정: Rust 범위 = 신규 백엔드 organ, 프론트 제외.
- 백엔드(API routes +
src/lib/*로직) = 도메인별 Rust(axum) organ 으로 점진 추출. Next.js 는 점점 얇은 frontend-of-record 로 후퇴, Rust 를 내부 HTTP 호출. - 데이터 공존 = Postgres schema-split 소유권 (“두 ORM 한 DB” 를 안전하게): Prisma=
public(Workspace/Entity/User/Session/Issue…), Rust/sqlx=substrate(artifact/artifact_link/mcp_schema). 한 blueprint-postgres 인스턴스, 교차쓰기 금지 (substrate 는 Workspace 미접근, Prisma 는 artifact 미접근 → 마이그레이션 도구 충돌 0).public이 시간에 따라 줄어드는 게 strangler.
→ D-bp-artifact-4 refine (모순 아님): “monolith first / 별도 KnowledgeStore service 분리 안 함 / 신규 인프라 0” 의 인프라 정신은 보존 (동일 blueprint-postgres · 동일 compose · 동일 axe ship blueprint). 바뀐 것은 언어 뿐 — knowledge substrate 를 Rust 프로세스로 구현 (언어 마이그레이션 목적의 프로세스 분리). frame/hive/gate 식 독립 product-service 가 아님 (cloudflared·공개 MCP·blue/green pair·customers.yaml service·pre-push guard 등재 전부 없음).
첫 organ = PARA substrate:
- 경계 = Artifact-scoped (사용자 확정): substrate 가
artifact/artifact_link(+annotation) /mcp_schema소유.workspace_id·entity_id는 OneDrive citation 처럼 opaque 외부 ref(FK 없음, 데이터 중복 0). Workspace + paraLayer SoR = Blueprint/Prisma 잔류. Workspace lifecycle 변경(reclassify/close) → 내부 신호(pg_notify 또는 webhook)로 substrate 의 cached paraLayer 동기화 (D-bp-artifact-6 “Re-sync on paraLayer change” 의 cross-process 판). - 모델은 처음부터 옳게: D-bp-para-1 home/link(“집”) 1급 —
artifact_link (artifact_id, workspace_id)= home 멤버십 본체 +annotation(Area 별 해석 레이어), dispatch modelink(기본)/copy_curate(method→Resource)/move(working→Archive),parent_artifact_id→derived_from_id(진짜 파생 lineage 만). 2-level scope(personal/shared) = artifactscope컬럼 → 열린질문 (a) 확정. citation = D-bp-artifact-3 6종 + D-index-6index.*+ D-gate-2gate.decision= 8종 처음부터. - 배포 = blueprint compose 내 sidecar
blueprint-substrate(Rust+axum, 내부 포트). 위치 =blueprint/substrate/(레포 내부 Cargo — polyglot 레포 = 점진 전환의 정상태).axe ship blueprint한 경로 안에서 빌드. - Next→Rust 인증 = 기존
BLUEPRINT_INTERNAL_API_KEYBearer (/api/internal/*패턴) + caller identity·entity scope 헤더. 동일 compose 망 내부 호출 신뢰. - 첫 organ 이 재사용 패턴 확립 (schema-split · 내부 auth 브릿지 · typed TS client · compose-service) → 이후 organ 들이 복제.
열린 질문 처리 (D-bp-para-1):
- (a) 2-level scope → 확정: personal + shared (artifact
scope컬럼, 기본 personal → 승급 shared). - (b) governance servicization → 별 트랙
gate서비스가 흡수 (D-gate-1/D-gate-2). substrate 와 무관. - (c) Project staffing(“던지면 emerge”) → 후속 defer (이번 substrate scope 아님; Project=컨테이너 + 기존 WorkspaceMember).
- (d) Rust 범위 → 확정: 신규 백엔드 organ = Rust, 프론트 = TS 유지, 점진 대체.
시퀀싱 (중요): 첫 scaffold 는 기존 M6 Stage-1 TS Artifact 테이블(Prisma public)을 건드리지 않고 병행 신설. cutover(Prisma Artifact 제거 + /api/artifact/propose → substrate client repoint)는 Rust 경로 동작 검증 후 별 stage — 기존 propose route 안 깨짐.
정직한 비용: polyglot 레포 + 두 마이그레이션 도구(schema-split 로 관리), Workspace↔artifact 교차 트랜잭션 상실(Artifact-scoped 라 수용 — workspace 생성 후 artifact propose 분리). 상세 ADR = docs/adr/blueprint-rust-migration.md. SoT 메모 = project_para_ai_os_philosophy.md.
D-bp-ui-1 — Blueprint @axe/ui 채택 + Tailwind 4 패치
D-axe-ui-1 (SSOT 단일 배포 채널 결정) 의 Blueprint 측 implementation. 첫 SSOT 소비자 중 Tailwind 4 + Turbopack 스택을 가진 곳 — Nextra (axelabs-docs) 와 다른 CSS 파이프라인 거동 때문에 sync 단계 패치 필요.
채택 범위 (2026-05-22):
| 항목 | 결정 |
|---|---|
| SSOT 경로 | /Users/axe/axelabs/src/lib/{tokens,styles}/ (D-axe-ui-1 과 동일) |
| Blueprint dest | src/app/_axe-ui/ (canonical app/_axe-ui/ 와 다름 — src/ source root) |
| sync 스크립트 | scripts/sync-axe-ui.mjs — canonical /Users/axe/axelabs-docs/scripts/sync-axe-ui.mjs 패턴 + Blueprint 패치 |
| 자동화 | predev / prebuild npm hook + check-axe-ui dry-run |
| VERSION 필드 | source-sha / source-lib-sha / source-lib-date (execSync git rev-parse + log -1) |
| DOM 토글 마커 | <html data-theme="dark"> 기본. `class=“light |
| CSP 추가 | style-src + font-src 에 cdn.jsdelivr.net (Pretendard + D2Coding CDN) |
Blueprint 전용 패치 1 — fonts.css @import url() strip:
axelabs SSOT 의 src/lib/tokens/fonts.css 는 Pretendard + D2Coding 을 @import url("https://cdn.jsdelivr.net/...") 로 로드. 이는 Nextra (axelabs-docs) 에서 정상 동작. 그러나 Blueprint 의 Tailwind 4 + Turbopack 파이프라인 은 globals.css 가 import 한 4 토큰 파일 (colors.css · spacing.css · typography.css · fonts.css) 을 globals.css 본문 안으로 inline 한 뒤 컴파일. 결과 CSS 의 구조:
[colors.css :root 블록] ...
[spacing.css :root 블록] ...
[typography.css body { ... } html { ... }] ...
[fonts.css 의 @import url(pretendard) ← 다른 rule 뒤로 밀림!]
[fonts.css @font-face { Sarasa } :root { --font-* }]CSS spec: @import 은 @charset / @layer 만 선행 가능. 다른 rule 뒤의 @import 는 브라우저가 silent drop. 결과: Pretendard 미로드, body 폰트가 system sans-serif 로 fallback (검증 안 했으면 모르고 지나갈 수 있음). 빌드 단계에서 @import rules must precede all rules aside from @charset and @layer statements 경고 발생.
회피: SSOT 미변경. sync 스크립트가 fonts.css 복사 시 content.replace(/^@import url\([^)]*\);\s*\n/gm, "") 로 @import url(...) 두 줄만 strip. Pretendard + D2Coding 은 src/app/layout.tsx 의 <link rel="stylesheet"> 로 로드 — 이는 SSOT 자체가 Clash Display 에 대해 fonts.css 주석에서 명시한 회피 패턴 (“Fontshare URL 의 f[]= 대괄호가 Next.js CSS @import 파이프라인에서 떨궈지는 이슈 회피”) 의 확장. 후속 axelabs SSOT 의 모든 @import url() 도 동일 strip 적용 (sync 스크립트의 정적 규칙).
Blueprint 전용 패치 2 — Legacy var alias bridge:
35+ tsx 가 기존 토큰 (var(--navy) var(--white) var(--gray-ax) var(--gray-light-ax) var(--dark-border) var(--accent-glow) var(--green) var(--red) var(--navy-light) var(--navy-mid)) 직접 사용. 비파괴 채택을 위해 src/app/globals.css 의 @layer base :root { } 에서 alias:
--navy: var(--bg-base); /* dark: #1a0610 (legacy 동일) */
--navy-light: var(--gray-2);
--navy-mid: var(--gray-3);
--white: var(--text-primary); /* dark: #f2e8ee (≈ legacy #f0f2f5) */
--gray: var(--text-muted);
--gray-light: var(--text-tertiary);
--dark-border: var(--border-subtle);
--accent-glow: rgb(227 255 102 / 0.15);
--green: var(--success);
--red: var(--danger);--accent 는 legacy 값 (#E3FF66) 을 제거 — colors.css 가 모드별로 자동 주입 (var(--brand-claret) light / var(--brand-neon) dark). dark 모드에서 --accent = #e3ff66 는 legacy hex (#E3FF66) 와 byte-equivalent. 즉 data-theme="dark" 기본인 한 모든 컴포넌트 무수정 작동, light 토글 시 일관성 있게 claret 톤으로 전환.
검증 (deployed @ 2026-05-22 PR 머지 시):
curl https://blueprint.axellc.com/_next/static/chunks/0bd4439~8glcs.css | grep -oE '\[data-theme="dark"\],\.dark'— lightningcss optimizer 가 selector list 보존--gray-1: #1a0610(dark block),--brand-neon: #e3ff66,--navy: var(--bg-base)확인- Pretendard CDN 200,
/api/healthlocalhost + edge 200 next build컴파일 ~8s,@import rules must precede경고 0
후속 (별도 PR):
- 8+ tsx 의 inline
fontFamily: "'Space Grotesk', ..."→var(--font-sans)토큰화 (SVG 텍스트). 완료되면 Space Grotesk<link>제거 가능. - 35+ tsx 의
var(--navy)/var(--white)/var(--gray-ax)직접 사용 → 새 토큰명 (var(--bg-base)/var(--text-primary)/var(--text-muted)) 점진 마이그레이션. alias bridge 는 그 동안 보존. - Light mode 토글 UI (인프라는 준비 —
data-themeattribute, persistence 만 별도).
관련 진단: /Users/axe/.claude/projects/-Users-axe-blueprint/memory/architecture_blueprint_axe_ui_adoption.md
D-matrix-1 — Matrix: Rust + native MCP (no Python SDK)
AXE Labs 첫 Rust 서비스. MCP Streamable HTTP 를 JSON-RPC 2.0 + axum 으로 직접 구현 (Python FastMCP 미사용). 모니터링 서비스 특성상 저메모리 (~15 MB), 단일 바이너리, tokio 기반 concurrent health check 가 핵심 이점.
| 항목 | 결정 |
|---|---|
| 언어 | Rust 1.95 — 기존 Python 3.12+ (frame/hive/blueprint) 과 별 트랙 |
| MCP 구현 | JSON-RPC 2.0 + SSE over axum (native). MCP 프로토콜 레이어가 충분히 단순 (JSON-RPC + SSE) 하여 직접 구현 |
| DB | PostgreSQL 16 (sqlx) — 기존 서비스와 동일 DB 엔진 |
| Docker API | bollard crate — 컨테이너 상태 실시간 수집 |
| 배포 패턴 | blue/green (frame/hive 와 동일 — port 3910/3911 + proxy 3912) |
기존 결정과의 관계: D-bp-mcp-1 (Python + FastMCP 기반 MCP 서버 표준) 은 기존 Python 서비스에 그대로 유효. 본 결정은 infrastructure tooling 의 새 precedent — Rust 의 장점 (메모리, 바이너리 크기, 동시성) 이 Python SDK lock-in 보다 우선하는 경우에 한정.
Rust 마이그레이션 적합성 평가 (2026-05-21): 기존 8개 프로젝트 (frame, blueprint, magnet, stream, artemis, cortex, mysrt, tether) 의 Rust 전환은 모두 No 판정 (mcp Python SDK + anthropic SDK + 한글 PDF 파싱 lock-in). 새 컴포넌트에서 시작하는 것이 미래 후보 — matrix 가 그 첫 사례. 2026-05-26: cortex 가 두번째 Rust 서비스 — 기존 file-based recall 폐기 후 artifact-first 재출발 (D-cortex 참조).
D-matrix-2 — Matrix: WAN/인터넷 가용성 모니터링 + ISP 귀책 판별 (2026-06-03)
운영자의 댁내 Wi-Fi/인터넷 장애를 통신사에 민원 제출하려 macmini 시스템 로그 (/var/log/wifi.log 11일치 + 통합로그) 를 분석한 데서 출발. 단말 로그는 “이 기기가 해당 시각 인터넷 도달 실패” 는 증명하나 ISP vs 댁내 (공유기/Wi-Fi) 귀책을 단독 확정 못 함 (6/3 장애는 DNS 실패 + IPv4 경로 99회 상실 = upstream 정합이나 동시에 Wi-Fi 펌웨어 트랩 1회 → 100% 단정 불가). matrix 가 상시 가동 인프라 모니터링 MCP 이므로 “인터넷 회선 가용성” 을 종합 모니터링 항목으로 흡수 — 향후 장애를 분 단위로 자동 기록 + 귀책 자동 판별.
| 항목 | 결정 |
|---|---|
| 구현 위치 | collector.rs::check_wan() — run_check 의 5 번째 점검. collector 가 제네릭 (ServiceCheck{name,type,status,detail}) 이라 프로브 1 개 추가 = 저장·alert·history·uptime·status board 자동 적용, 스키마 변경 0 |
| 3 프로브 | wan-gateway (공유기 ICMP — LAN 링크) · wan-internet (공인 anycast ICMP, RTT + 손실%) · wan-dns (호스트네임 해석 시간, tokio::net::lookup_host). tokio::join! 동시 실행 |
| 귀책 판별 | gateway↑ + internet↓ ⇒ ISP/WAN fault (warning 발생) · gateway↓ + internet↓ ⇒ 댁내 링크/Wi-Fi. wifi.log 분석이 못 한 구분을 상시 자동화 |
| 컨테이너 ICMP | cap_add: NET_RAW + 런타임 이미지 iputils-ping (권한상승 없는 unprivileged ping). Docker Desktop NAT 통과 + 공유기 (192.168.55.1) / 외부 (1.1.1.1) 도달 사전검증 (2026-06-03, 손실 0%) |
| 신규 env | MATRIX_LAN_GATEWAY (default 192.168.55.1 — 노드별·DHCP 주의) · MATRIX_WAN_TARGET (1.1.1.1) · MATRIX_DNS_HOST (google.com). 비밀 아님 → vault 등재 불요. MATRIX_CHECK_INTERVAL default 60→30 (끊김 해상도) |
| 범위 (v1) | 프로브 + 귀책 alert. 기존 get_service_history / get_uptime_report 가 wan-* 이름에 즉시 작동. v2 후보 = get_wan_report MCP tool (장애 구간 start/end/지속 + 업타임 + 귀책 = ISP 제출용 타임라인). B-matrix-wan-report-tool |
| 검증 | host cargo check 통과 (cargo 1.95). 런타임 검증은 axe ship 후 /matrix/api/status 에서 wan-* 등장 확인 |
기존 결정 정합: D-matrix-1 (collector 제네릭 설계) 위에 프로브 추가만으로 성립 — 새 테이블/도구 불요가 핵심. multi-tenant — wan-* 는 노드별 자기 회선 모니터라 customer sovereignty 와 자연 정합.
D-matrix-4 — netheal: 호스트 인터넷 자가치유 데몬 (2026-06-04)
D-matrix-2 가 WAN 끊김을 감지·귀책 하면, netheal 은 치유 — 6/3 운영자가 손으로 한 WiFi off/on 6회 → 재부팅을 자동화. matrix 는 Docker (Linux VM) 안이라 macOS en1 을 제어할 수 없어 치유는 호스트로 분리.
| 항목 | 결정 |
|---|---|
| 위치 제약 | 치유는 호스트 root LaunchDaemon (com.axe.netheal) — matrix=감지/가시화, netheal=치유. Docker 비의존 → 네트워크/컨테이너 동반 장애에도 작동 |
| 치유 사다리 | DNS 플러시 (dscacheutil + mDNSResponder HUP) → DHCP 갱신 (ipconfig set DHCP) → WiFi 바운스 (networksetup setairportpower off/on). 약→강 |
| ISP 인지 백오프 | gateway↑ + 외부↓ = ISP/회선 장애 → WiFi 바운스 futile → DNS/DHCP 1회 후 HOLD. gateway↓ = 로컬 → 풀 사다리 |
| 안전장치 | 디바운스 (~60s 지속돼야 작동) · 쿨다운 90s · 시간당 12회 서킷브레이커 · 자동 재부팅 기본 OFF (NETHEAL_ALLOW_REBOOT, 박스가 frame/hive/matrix 구동) · 전 동작 타임스탬프 로깅 (/var/log/axe-netheal.log, 증거 겸용) |
| 구현 | 순수 bash (의존성 0, 감사 용이, /bin/bash 3.2 호환). ~/axe-netheal/{axe-netheal.sh, com.axe.netheal.plist, README.md}. bash -n + 스모크 (started + 오발동 0) 검증 |
| 잔여 | 운영자 sudo 설치 (/usr/local/sbin + /Library/LaunchDaemons) + launchctl bootstrap → inventory.mdx launchd 표 등재. B-netheal-install |
기존 결정 정합: D-matrix-2 의 gateway-vs-internet 귀책 로직을 그대로 재사용 (감지→치유). multi-tenant — 노드별 자기 회선 치유라 customer sovereignty 와 직교.
D-cortex — Cortex (AI-native relationship CRM, 2026-05-26)
7 개 결정의 묶음. 강수훈 개인이 Windows + OneDrive 의 단일 Network_CRM.xlsx 로 운영해온 1인용 투자 네트워크 CRM 을 platform 안으로 끌어들임 — frame 이 회계 자산을, hive 가 HR 자산을 platform 안으로 끌어들인 것과 동일 패턴. 도구는 개인용이었지만 데이터 (HPE 딜 / AXEV 파이프라인 / LP·SI·FI·Portfolio·Talent 관계) 는 회사가 의존하는 자산이라 한 사람의 OneDrive 에 잠겨있는 게 본질적 위험.
기존 /Users/axe/cortex (2026-03 시작한 file-based “VC 심사역 AI 기억 시스템” — 186 markdown topic tree + 16 entity profile + 3,394 keyword DB + 45,903 connection edge) 은 도메인이 유사했지만 모델 (파일 기반 / 단일 사용자 / 자체 router LLM) 이 새 설계와 완전히 다름 → .legacy.20260526/ 으로 rename 보존 (623 MB, 추후 enrichment migration 참조용).
| ID | 결정 | 핵심 |
|---|---|---|
| D-cortex-1 | M6 artifact 모델 Day 1 채택 | Blueprint 의 typed-fact artifact + citation + append-only event log + PARA dispatch 가 Q4 2026 target 에서 Day 1 표준으로 escalation. Cortex 가 platform 의 첫 artifact-first reference impl. 다른 vertical service 도 향후 동일 패턴. frame 의 domain-strict 테이블 (entity/journal/raw_transaction) 은 회계 KSME 제약 때문에 그대로 유지 — /schemas endpoint 가 artifact mirror. |
| D-cortex-2 | per-user private (owner_id + Postgres RLS) | 모든 도메인 테이블 (artifact, citation, artifact_event, google_oauth_token) 에 owner_id 필드 + FORCE ROW LEVEL SECURITY + cortex_app NOSUPERUSER 역할 + SET LOCAL ROLE cortex_app; SET LOCAL cortex.actor = <email> 패턴. POSTGRES_USER cortex 가 docker postgres init 단계에서 SUPERUSER 로 만들어져 RLS 우회 가능하므로 application connection 은 항상 cortex_app 으로 role downgrade. GUC 이름 = cortex.actor (current_user 가 Postgres 예약어). |
| D-cortex-3 | Google Contacts canonical, 단방향 pull default + enrichment 보존 계약 | 흐름: 명함 → Remember → Google Contacts → Cortex (10분 cron). Google 이 base field (이름·전화·이메일·소속·직급) owning. Cortex 는 enrichment (분류 HPE/AXEV/구분1/구분2 / 메모 / interaction / 관계 / deal link / cohort / former_org/title 등) 만 owning. 신규 인물 진입은 항상 Remember/Google 경로. 양방향 sync 안 함. Sync 보존 계약 (2026-05-29 PM4 hardening — 동일 일자 PM 의 데이터 손실 버그 발견 + 수정): Google → Cortex overlay 는 GOOGLE_CANONICAL_KEYS 화이트리스트 (display_name · given_name · family_name · org · title · department · emails · phones · memberships · biographies · userDefined) 만, 그것도 meaningful (non-null + non-empty 문자열/배열/객체) 값일 때만. Cortex enrichment 키는 sync 가 절대 안 건드림. 버그 원인: upsert_from_person 의 UPDATE artifact SET payload=$1 가 전체 payload 덮어쓰기 → register_person 으로 등록된 person 의 enrichment (display_name 포함) 가 10분 후 sync 실행 시 빈 Google payload 로 destroy 됐음. 증거: 김승우 (코오롱인더스트리 전무) + 정성화 (한섬비즈온 대표) 신규 등록 직후 audit 가 propose (operator) → 4분 후 edit (system:google-sync, display_name=null + 모든 enrichment 사라짐) 기록. 복구: append-only audit log 보존 덕분에 propose payload + 후속 operator edit 으로 restore (op=‘restore’ event, actor=‘system:enrichment-recovery-D-cortex-3’). 동반 fix: register_person 의 createContact 가 display_name 만 보내는데 People API 의 Name.displayName 은 server-computed 라 무시됨 → unstructured_name 필드 같이 보내도록 NameCreate 확장 (mcp.rs + web/views.rs push_one). 향후 신규 등록은 Google 측에 정상 displayName 저장. 검증: 복구 + 강제 sync → enrichment 보존, sync 는 memberships=["myContacts"] (Google meaningful) 만 overlay. 단방향 원칙의 예외 (식별 키 정정 한정): PM4c 에 발견 — unstructured_name fix 가 ship 되더라도 fix 이전에 생성된 134 Google contact 들의 displayName 은 빈 상태 그대로. 이걸 Google 측에 재push 하는 도구 = update_contact MCP tool + cortex backfill-google-names CLI (D-cortex-google-names-backfill). 둘 다 식별 키 (display_name/given/family, 그리고 phone/email/org/title 등 GOOGLE_CANONICAL_KEYS 의 일부) 만 push, enrichment 키 (memo/cohort/HPE/AXEV/former_org/former_title) 는 절대 push 안 함. updateContact 는 createContact 의 보완재 (역방향 sync 가 아닌 식별 정정), 단방향 pull default 와 양립. 이번에 함정 추가 발견 — Docker layer cache: 코드 fix push 후 docker compose build 만 했을 때 src content 변경에도 캐시된 빌더 레이어가 재사용되어 old binary 가 컨테이너로 들어가는 케이스 발생. --no-cache 강제 빌드 + 새 binary 동작 (enrichment 보존) 직접 검증 후 134 backfill 실행. PM7 재발 — blue/green 동일 binary invariant + 회귀 테스트 추가 (2026-05-29 PM7): 같은 날 저녁 동일 증상 재현. 김승우/정성화 (한섬·스위티) 3명의 memo/cohort/former_org/former_title 이 다시 system:google-sync 의 edit event 로 wipe. 진짜 root cause: 소스는 이미 PM4 의 overlay-preserving 코드였으나, docker compose build --no-cache 가 cortex-mcp-blue 한 서비스에만 적용됐고 cortex-mcp-green 은 stale 캐시된 옛 이미지 그대로. blue/green 둘 다 run_loop 를 동시에 돌기 때문 (leader election 부재) sync 한 번 돌 때마다 race — 새 코드의 보존 INSERT 직후 옛 코드가 같은 초에 destructive UPDATE 로 덮음. 검증법: docker exec cortex-mcp-blue sha256sum /usr/local/bin/cortex 와 docker exec cortex-mcp-green sha256sum /usr/local/bin/cortex 가 정확히 같아야 함 (image-level hash 는 빌드 timestamp 비결정성으로 달라도 OK — 안의 binary 만 같으면 안전). 복구: 운영자 status=‘paused’ → 두 서비스 모두 --no-cache 재빌드 + up -d --force-recreate → 183 person restore-from-audit --apply → sync 재개. 회귀 차단: src/google/sync.rs 에 #[cfg(test)] mod tests 5개 unit test 추가 — (1) overlay 가 enrichment 보존 + (2) Google canonical 이 모두 null 일 때 enrichment 보존 (edge case) + (3) wholesale replace 차단 + (4) 멱등 + (5) is_meaningful 가드. cargo test --bin cortex google::sync::tests 5/5 통과. 향후 ship hook 에 묶을 것. 운영 invariant 명문화 (CLAUDE.md 의 운영 함정 섹션): sync.rs 같은 critical path 수정 시 blue/green 둘 다 명시적 --no-cache 후 binary sha256 일치 검증 필수. |
| D-cortex-4 | opportunistic push to Google (신규 등록 시에만) | register_person MCP tool 호출 시, 사용자가 Google 에 없는 사람을 입력하면 people:createContact 으로 Google 에도 등록 (resource_name 받아 artifact citation 으로 사용). Update push X — Cortex enrichment 필드 (분류·메모·관계·interaction) 는 Google 으로 안 흘러감. Cortex-only person 은 존재하지 않음 (모든 artifact 가 google_contact citation 보유 가능). |
| D-cortex-5 | Day 1 multi-user (per-user Google OAuth Web client) | 각 사용자가 자기 개인 Google 계정 (Remember 흐름의 upstream) 을 Cortex 에 authorize. refresh_token 은 Postgres google_oauth_token 에 pgp_sym_encrypt(token, CORTEX_PII_PASSPHRASE_AXEC) 로 저장. per-user sync 가 격리 — 한 사용자 sync 실패가 다른 사용자 안 막음. Initial 사용자는 강수훈 (axellc.com) 이지만 architecture 가 multi-user 지원. State JWT (HS256 with CORTEX_JWT_SECRET) 가 OAuth callback 인증 (DB hit 없이 stateless owner_id 추출). |
| D-cortex-6 | MCP-only 1차 (Next.js 프론트 없음) | claude.ai connector + Claude Code 가 primary client surface. 12 tools (whoami / list_persons / search_person / get_person / register_person / log_interaction / list_interactions / connect_google / disconnect_google / sync_google_now / classify_person / update_person) + RFC 9728 OAuth metadata (server-level + resource-level /cortex/mcp/.well-known/oauth-protected-resource 둘 다 — D-ops-23 frame 학습) + Google OAuth callback HTML. UI 가 필요해지면 Blueprint 안에 Cortex 페이지 추가 (M6 통합 이후). 별도 Next.js 안 짓기. → D-cortex-6.1 로 부분 번복 (2026-05-29). |
| D-cortex-6.1 | read-only 웹 UI 는 cortex binary 안에서 (axum + maud) | 운영자 “axe.axelabs.ai/cortex 웹에서 볼 수 있게” 요청 (2026-05-29). D-cortex-6 의 “UI 는 Blueprint” 부분만 번복 — Blueprint 분리 X 이유: (a) D-cortex-7 의 단일 binary 운영 ergonomics 유지, (b) 같은 PgPool / RLS / set_current_user 헬퍼 재사용, (c) Microsoft federation 코드 (oauth_as.rs 의 server-to-server token exchange + JWKS verify) 가 그대로 cookie 세션용으로 재사용 가능, (d) Blueprint 가 M6 mirror 까지 cortex 데이터를 모름 → cross-service HTTP 호출 회피. 구성: src/web/{session,auth,views,mod}.rs. 세션 = HS256 JWT cookie cortex_session (tok="web" discriminator, 8h sliding refresh, HttpOnly+Secure+SameSite=Lax, Path=/cortex). 로그인 = Entra ID federation (/cortex/login/start → Microsoft → /cortex/login/callback, 별도 redirect URI 등록 — Entra app 의 web.redirectUris 에 https://axe.axelabs.ai/cortex/login/callback 추가). 페이지: /cortex/home 대시보드 (kind 별 count + Google 연결 상태 + 최근 audit event) · /cortex/{people,orgs,deals,interactions,relationships} 리스트 (visibility 필터 + 페이지네이션) · /cortex/a/:id detail (payload pretty + citations + 최근 20 events) · /cortex/google/reconnect (기존 build_authorize_url 재호출 wrapper). read-only: write 는 여전히 MCP. 향후 write 추가 시 CSRF 토큰 필요 (현재 GET 만이라 미적용). 세션 격리: MCP auth::middleware (Bearer) 와 web require_session (Cookie) 은 별도 axum layer 로 적용 — /cortex/mcp 가 cookie 우연히 받아도 Bearer 헤더 없으면 401, web 라우터가 Bearer 받아도 cookie 검증 분리. tok="web" 미스매치 시 둘 다 거절. |
| D-cortex-google-push-batch | xlsx 적재 후 Google push 큐 운영 — batch + skip + visibility 격리 | xlsx --create-missing 이후 Cortex-only person 215~ 명을 Google 에 동기화하려면 행 단위 클릭 비현실적. D-cortex-4 (opportunistic) 안에서 batch 정의 = 사용자 명시 클릭 (체크박스 multi-select + “Push selected (N)” 버튼) 으로 N 건 enqueue, worker 가 1 req/sec 페이스로 Google quota (90/min/user) 안에서 순차 처리. 데이터모델 — JobRegistry (src/web/google_push.rs): Arc<Mutex<{jobs: HashMap<JobId,JobStatus>, owner_running: HashMap<owner,JobId>}>> (in-memory, 프로세스 재시작 시 손실 허용 — push_one 의 google_contact citation 중복 거절로 재시도 idempotent). owner 동시 1 job 락 — 2nd start 가 같은 owner 면 진행 중 job 의 status page 로 ?already_running=1 와 함께 redirect (에러 페이지 X, 운영자가 진행 중인 batch 가 어디 있는지 즉시 봄). 진행률: status page = progress bar + 결과 table + 클라이언트 setInterval(1500ms) 로 JSON polling — 워커가 끝나면 stop. owner 격리: status snapshot 핸들러는 owner == auth.email 강제 (다른 사용자 batch 못 봄). 데이터모델 — skip 마킹: 연락처미상 placeholder 영구 제외 = kind='attribute', payload={subject_artifact_id, key='do_not_sync_google', value='true'} 별도 artifact (classify_person 패턴 일관, person payload mutation 회피, append-only audit 자동). list_google_pending SQL 이 NOT EXISTS 두 종 (citation google_contact + skip attribute) 강제. D-cortex-9 안전 fix 동반: 같은 SQL 에 AND a.visibility='shared' 추가 — private person 절대 push 후보 안 됨 (배포 전 누락이었음 → 같이 패치). MAX_BATCH_SIZE = 500 sanity cap. 라우트: POST /cortex/google/push/start (form ids[]) · GET /cortex/google/push/status/<job_id> HTML · GET /cortex/google/push/json/<job_id> JSON · POST /cortex/a/<id>/skip-google-push 마킹 신규/idempotent. 검증 (fake UUIDs 2 개 + concurrency + completion + 락 재획득 모두 OK, Google API 호출 0 회 — NotFound 가 token refresh 이전 단락). skip 후 pending 215→214, DB 에 attribute 1 row 확인. |
| D-cortex-design-axe-ui | cortex 웹 UI 도 axelabs design tokens 그대로 채택 — docs.axelabs.ai 와 같은 시각언어 | D-cortex-6.1 의 inline CSS (STYLE const ~200 line) 폐기 → axelabs SSOT (/Users/axe/axelabs/src/lib/{tokens,styles}/) 를 가져다 씀. Sync 메커니즘 — scripts/sync-axe-ui.sh (docs.axelabs.ai 의 scripts/sync-axe-ui.mjs 패턴 미러). 호스트 빌드 시 axelabs 소스를 읽어 static/axe-ui/bundle.css (83 KB) 와 static/axe-ui/fonts/SarasaFixedK-Regular.woff2 (533 KB) 갱신. Bundle 구성 = tokens 4 종 (colors / spacing / typography / fonts) + styles 6 종 (reset / components base / form / data-display / dashboard / feedback). components.css 의 @import 중 cortex 가 안 쓰는 그룹 (overlays, agent, menus, mobile, layout, indicators, chat-ext, data-views, composites) 은 sync 시 strip. fonts.css 의 /fonts/SarasaFixedK-Regular.woff2 절대경로는 cortex 가 자기 static 으로 서빙하도록 /cortex/static/fonts/... 로 rewrite. Binary stamp — include_str!/include_bytes! 로 cortex binary 안에 박아 Docker stage 가 axelabs 디렉토리를 못 봐도 빌드 가능 (Dockerfile 도 COPY static ./static 한 줄 추가). 라우트 = GET /cortex/static/axe-ui.css + GET /cortex/static/fonts/SarasaFixedK-Regular.woff2 (public, 세션 불요, Cache-Control 1일/30일). HTML 적용 — <html data-theme="light"> 로 라이트 모드 anchor + <link rel="stylesheet" href="/cortex/static/axe-ui.css">. 컴포넌트 클래스 매핑: .topbar → .axe-topnav, .brand → .axe-logo, .card → .axe-card, .btn → .axe-btn .axe-btn--{primary,secondary,ghost}, .metric → .axe-metric-card, <table> → .axe-data-table > __scroll > __table > __th/__td, .empty → .axe-empty-state, callout box → .axe-callout--{info,danger}, checkbox → .axe-checkbox. Cortex-only 개념 (visibility tag, payload pre, kv list, filter bar, simple pager, flash, login wrap) 은 .cortex- prefix 로 분리 inline. 검증: axe-ui.css 200 (85100 bytes, text/css) · font 200 (533932 bytes, font/woff2) · /cortex/home 200 + data-theme=“light” + link stylesheet + .axe-topnav/.axe-metric-card/.cortex-metric-grid 모두 렌더 · /cortex/google/pending 200 + .axe-data-table/.axe-checkbox/.axe-btn—primary 렌더 · 엣지 https://axe.axelabs.ai/cortex/static/axe-ui.css 200. |
| D-cortex-backfill-orgs | person.payload.org inline string → organization artifact + employed_by relationship 일괄 승급 (CLI 2단계) | 운영자 관찰 (2026-05-29): /cortex/orgs 페이지가 비어있음. person 3,597 개 모두에 회사가 payload.org 안에 inline string 으로만 있고 distinct 약 1,536 개. 자동 normalization 의 함정 — “노루” vs “노루홀딩스” vs “(주)노루홀딩스” 같은 변형을 LLM/trigram 만으로 합치면 잘못 합치는 사고. 따라서 2단계 워크플로: Phase 1 = cortex backfill-orgs --owner <email> (src/backfill.rs) → pg_trgm 활성화 + distinct org 추출 + similarity ≥ 0.6 클러스터 제안 YAML stdout (canonical_name + aliases + 빈도). 운영자 파일 저장 → 편집 (잘못 클러스터된 항목 분리, 합칠 항목 합치고, placeholder/미상은 skip: true 추가). Phase 2 = --mapping <yaml> 로 dry-run 통계, Phase 3 = --apply 실제 INSERT. Idempotent — (owner_id, payload->>‘name’) 으로 org 중복 안 만듦, (edge_kind=‘employed_by’, from_artifact_id, to_artifact_id) 로 relationship 중복 안 만듦, 재실행 무해. artifact 형식 — kind='organization', payload={name, aliases:[...], backfill_source:'backfill_orgs_v1'}, visibility='shared'; kind='relationship', payload={from_artifact_id, to_artifact_id, edge_kind:'employed_by', source_org_string:<원본>, backfill_source:'backfill_orgs_v1'}, visibility='shared'. D-cortex-6.1 read-only 원칙 유지 — 등록 mutation 은 MCP-only 이고 backfill 은 CLI 일회성 도구 (운영자 명시적 의도 분명, 웹 form 추가 안 함). 회사명·employed_by 사실은 민감 아님 → shared 고정. 검증: 1,536 distinct strings · 1,446 suggested clusters · YAML 5,000+ 줄 stdout 정상 · 소규모 2-org mapping 으로 Phase 2 dry-run + Phase 3 apply + 재apply (existing=2 idempotent) 모두 통과 · /cortex/orgs 페이지 가 빈 axe-empty-state 에서 데이터 있는 axe-data-table 로 전환됨 확인. |
| D-cortex-google-names-backfill | cortex backfill-google-names 일회성 CLI — Google contact 측 빈 displayName 정정 (134건) | unstructured_name fix ship (commit 1326123, cutoff 2026-05-29T06:48:07Z) 이전에 register_person → createContact 로 Google 에 push 된 contact 들의 Google-side displayName 이 NULL 상태. People API 의 Name.displayName 은 server-computed — 옛 코드가 보낸 클라이언트 displayName 무시되고 given/family 도 null 이라 빈 displayName 으로 저장됨. Candidate filter: kind=google_contact, ref.source=register_person, artifact.visibility=shared, last_pushed_at 이 cutoff 이전 (또는 NULL + citation.created_at 이 cutoff 이전), citation.ref 의 unstructured_name_backfill 이 v1 아님 (idempotency gate). per-person flow: Cortex payload 의 display_name/given_name/family_name 추출 → People API updateContact PATCH (unstructuredName 동봉) → 응답 etag 추출 → RLS tx 안에서 citation.ref 갱신 (last_pushed_at + unstructured_name_backfill v1 마킹 + etag + backfill_source) + INSERT artifact_event ‘dispatch’ (actor=system:backfill-google-names). Rate limit: 1 req/sec (Google quota 90/min/user 안전 여유). Error 분기: 4xx etag mismatch 면 fresh getContact 후 1회 retry · 5xx 면 exponential backoff 1s/3s/9s 3회 · 429 면 60s 대기 후 1회 · 그 외 4xx 면 log + skip. D-cortex-3 정합: 식별 키만 push, enrichment 키는 절대 push 안 함. D-cortex-4: 운영자 명시 —apply 실행만. D-cortex-9: visibility=shared 만 candidate, private 자동 제외. 검증: dry-run candidate=134 (Bucket A=2 김승우/정성화, Bucket B=132). canary 5/5 → full 129/129 success (총 134, 약 2.5분, 1 req/sec). 사후 candidate=0, dispatch event 134개, 김승우/정성화 citation 마킹 v1. 전제 — restore-from-audit 가 Cortex 측 display_name 을 먼저 채워둬야 함. |
| D-cortex-restore-from-audit | append-only audit 기반 enrichment 복원 CLI — cortex restore-from-audit --owner <email> [--artifact-id <uuid> | --scan] [--apply] | D-cortex-3 sync overwrite 같은 destructive event 후 audit log 기반 enrichment 복원. 알고리즘 — per-key 최신 meaningful 값 picking: 단일 operator payload_after 만 보면 (예: 김승우 14:41 operator edit 가 이미 깨진 state 위에 former_org 만 추가 + display_name=null) 복원 실패. 키별로 ASC 훑어 마지막 meaningful (non-null + non-empty) 값을 채택. 소스 필터: op IN ('propose','edit') (dispatch/archive/restore op 는 enrichment intent 아님) + actor NOT IN system actors. xlsx-migration 은 enrichment source 로 인정 (operator 가 다른 채널로 못 만진 경우 backup). 대상 키: Cortex enrichment 키 (비-GOOGLE_CANONICAL_KEYS) 만 복원 + display_name 은 canonical 이지만 sync bug 손실 패턴 흔해서 예외 처리. idempotent: 이미 복원된 (current payload 가 meaningful) 케이스는 skip 보고. 검증: D-cortex-3 hotfix 직후 일회성 Python script (177 복구) + 동일 알고리즘 CLI v2 가 14건 추가 발견·복구 (single-payload 정책 미스). 미래 비슷한 사고에 재사용. |
| D-cortex-mcp-write-tools | MCP write tool 표면 완성 — register_organization/deal/relationship + update_contact + archive_artifact + list_organizations/get_organization/list_deals/get_deal/list_relationships (10종 추가, 총 22 tool, register_deal 재설계 — name + contact_person_ids 자동 deal_contact relationship + visibility default private + source_attribute_id, archive_artifact 에 reason/restore alias 추가) | 운영자 관찰 (2026-05-29 PM3): claude.ai 가 organization/deal/relationship 을 일상 등록할 수 있는 채널이 부재. 그리고 PM4 에 발견된 D-cortex-3 destructive overwrite 버그 fix 과정에서 Cortex → Google 으로 식별 필드 push 도구 (update_contact) 필요성 드러남. 현재 organization 채움 경로는 cortex backfill-orgs CLI 하나뿐. 일상 운영 (예: claude.ai 안에서 “이번 미팅에서 A 회사 알게 됐다 → register_organization”) 흐름이 시작되도록 register_person 패턴을 4 kind 으로 확장. register_organization = (owner, payload->>‘name’) idempotent (같은 이름이면 existing id 반환, payload 갱신 X), visibility default ‘shared’, payload 가 additionalProperties: true. register_deal = NOT idempotent (같은 title 의 deal 가 round 별로 나옴), target_org_id 가 있으면 owner-scope + kind=‘organization’ 검증, payload 자유. register_relationship = (owner, edge_kind, from_artifact_id, to_artifact_id) idempotent, from/to 양쪽 owner-scope 존재 검증, self-loop 차단 (from==to → InvalidArg). archive_artifact = payload.archived=true + payload.archived_at=ts UPDATE + append-only event op='archive'. 혁신 — 별도 archive_person/archive_organization/… 안 만들고 한 일반 도구로 통일. archive=false 로 restore (역방향, op=‘restore’). 멱등 — 같은 상태로 호출하면 no_op. visibility tier 와 직교 (안 건드림). DELETE 차단 우회의 핵심: artifact_event append-only trigger 가 DELETE/UPDATE 차단 → artifact 자체는 DELETE 가능하지만 CASCADE 로 events 같이 손실. archive flag 로 lifecycle 표현하면 audit 보존 + list/count/push 자동 제외. List 쿼리 일괄 갱신: web (fetch_kind_counts/recent_events/artifacts_by_kind/pending_push), MCP (list_persons/search_person via push_person_filters), backfill (distinct_orgs/person_rows) 모두 NOT COALESCE((payload->>'archived')::boolean, false) default 필터. get_person(id) 등 ID 직접 접근은 archived 도 반환 (복원 흐름 + audit 보존). 검증 (11/11 통과): (1) register_organization 신규 (2) 재호출 idempotent 같은 id+existing (3) register_relationship 신규 (4) 재호출 idempotent (5) register_deal 신규 (6) 동일 title 재호출 새 id (7) target_org_id 가 person uuid → kind mismatch error (8) archive 4개 (org/2 deal/rel) (9) 재 archive no_op (10) archive=false restore (11) 이전 세션 SMOKE_TEST_ORG_DELETE_ME_BACKFILL 정리. UI 검증: /cortex/orgs 가 archived 항목 안 보임, home dashboard 카운트 archived 제외 정합. |
| D-cortex-7 | Rust + axum + sqlx (D-matrix-1 후속 precedent) | 기존 8개 프로젝트는 Python/TypeScript 인데 Cortex 만 Rust 인 이유: (a) artifact 의 typed discriminated union 과 Rust 의 algebraic type 자연스러움, (b) sqlx 의 compile-time SQL check 가 schema/RLS query 안전성 보강, (c) MCP HTTP 서버 = long-running 단일 process 이므로 Rust 의 panic-free + 낮은 메모리 ergonomics 적합, (d) 단일 binary 배포. Day 1 부터 Rust. matrix (D-matrix-1) 가 첫 Rust precedent, cortex 가 두번째. |
| D-cortex-8 | claude.ai connector OAuth = Microsoft 직접 federation (self-hosted AS proxy 아님) | RFC 9728 의 authorization_servers 를 Microsoft (login.microsoftonline.com/<tenant>/v2.0) 로 둠 → claude.ai 가 Microsoft 의 OIDC metadata fetch → Microsoft /authorize·/token 사용. cortex 는 RS256 access_token (aud = Application ID URI https://axe.axelabs.ai/cortex/mcp) 만 검증 (auth.rs RS256 path). frame 의 “직접-Microsoft 현재 live” path 와 동일. claude.ai connector 의 Advanced field 에 client_id (60d04ea8-...) + client_secret 입력 필수 (confidential client). 탐색 후 폐기: cortex 자체 AS proxy (/cortex/oauth/{authorize,callback,token,register} + RFC 8414 metadata + oauth_authorization_codes 테이블 + auth.rs HS256 path, src/oauth_as.rs ~620 line) 를 구현했으나 불필요로 판명 → frame oauth.py 와 동일하게 dormant 보존 (claude.ai client 개선 시 재활성 가능). 사유: authorization_servers 를 cortex (path 있는 issuer https://axe.axelabs.ai/cortex) 로 두면 RFC 8414 §3.1 의 path-insertion (metadata 위치 = https://axe.axelabs.ai/.well-known/oauth-authorization-server/cortex) 때문에 claude.ai 가 cortex sub-path 의 RFC 8414 metadata 를 못 찾음 → origin 의 /authorize fallback → blueprint 404. |
| D-cortex-person-tier | person 을 tier (pool|network) 로 2분 — 자동 sync 연락처(pool) vs 운영자 인맥(network), auto-promote 자동화 | 운영자 관찰 (2026-05-29 PM9): Google Contacts 3,601 person 중 대부분이 일회성/업무 raw 연락처라 “내 인맥” 뷰를 오염. 개념: pool = Google sync 가 자동으로 가져온 raw 연락처 (의미 부여 X, 손실 없이 보존), network = 운영자가 의도적으로 추가/관리하는 인맥. backfill (tier_backfill_v1): network 625 / pool 2,976. 기본값: (a) Google sync 신규 person = pool (sync.rs INSERT 시 자동; 기존 person 의 tier 는 enrichment 키 → overlay 가 GOOGLE_CANONICAL_KEYS 화이트리스트 밖이라 절대 안 건드림). (b) register_person (운영자 명시) = network default (claude.ai 에서 의도적 등록 = 이미 인맥); tier=‘pool’ 명시 허용 (행사 명함 일괄 후 추후 분류). (c) import-xlsx (Network_CRM.xlsx = 운영자 큐레이션 인맥) = network — create-missing 신규는 생성 시 tier=‘network’, matched (Google-synced pool) person 도 xlsx 등장 = 인맥이라 system:xlsx-migration 이 network 로 승급 (멱등). auto-promote (pool→network, 멱등): classify_person(attribute 부착) / register_relationship(person endpoint, edge_kind 무관) / log_interaction(participant) 중 어느 액션이든 발생하면 대상 person 자동 승급 — payload.tier=‘network’ + tier_source=‘auto_promoted_from_<action>’ + tier_promoted_at, audit op=‘edit’ actor=‘system:auto-promote’. 한 helper (auto_promote_to_network) 가 3 handler 의 tx 안에서 호출. network→pool 강등 없음 — 한 번 인맥은 영원히 인맥. 유일한 역방향 = 운영자 명시 demote_to_pool(person_id, reason) MCP tool (archive_artifact 패턴 미러, payload.tier=‘pool’ + tier_demoted_at + tier_demote_reason, audit actor=operator, 멱등). 노출: list_persons MCP tool 기본 tier=‘network’ (claude.ai 1차 뷰 = 인맥), web /cortex/people 기본 tier=‘network’ + ?tier=pool|all 확장 필터 바, home 대시보드 People 카드 = 내 인맥(network) 값 + 풀/전체 sublabel. D-cortex-3 정합: tier 는 enrichment 키 → sync 가 절대 안 건드림. 회귀 차단: sync.rs 의 overlay_never_touches_tier_enrichment_key unit test (6 tests). MCP tool 수: 22 → 23 (demote_to_pool). |
| D-cortex-org-stats | home 대시보드 전사(cross-user) 집계 — D-cortex-2 per-user 격리를 처음 의도적으로 여는 표면 (익명 합계 한정) | 운영자 요청 (2026-05-29 PM10): “대시보드에 조직 전체 통계도”. 범위 확정 — 전사 집계(cross-user) + 전 직원 visible + 전사 합계만 (직원 익명, per-employee breakdown 없음). 구현: fetch_org_wide_counts(state) 가 set_current_user 를 호출하지 않아 cortex superuser 연결(rolbypassrls)로 RLS(owner_only) 를 우회 → 모든 owner 의 artifact 를 kind 별 COUNT + distinct owner(사용자) 수만 집계 (sync.rs 의 cross-row 패턴과 동일; per-user 경로는 여전히 SET LOCAL ROLE cortex_app + cortex.actor 로 격리). 격리 보존 핵심: 개별 레코드도 owner 별 분해도 반환 안 함 — 오직 익명 총합 숫자만 RLS 섬 밖으로 나감 (D-cortex-2 의 “타인 레코드 비가시” 유지, 집계 숫자만 공유). archived 제외. home 에 “조직 전체 (전사 집계 · 사용자 N명)” 섹션 + 익명 안내 callout + org_metric (non-link — 전사 합계는 per-user 리스트로 안 매핑되므로 클릭 misleading 방지). 검증: 집계 person 3600 / relationship 3071 / org 1500 / deal 17 / interaction 1 / users 1. (smoke 잔여 archive 후 정확.) 확장 시 주의: 직원별 breakdown(attribution, created_by/owner_id 기반)을 추가하려면 개인 인맥 규모가 직원 간 노출 → 별도 운영자 결정 필요. created_by(artifact) + actor(artifact_event append-only) 는 이미 누가 등록·수정했는지 전부 기록 중이라 attribution 데이터는 준비됨. |
| D-cortex-customer | person/organization 고객 태깅 — 다대다 (1 subject ↔ 여러 case) 수동 태깅 | 운영자 결정 (2026-05-29 PM11): “고객인지 아닌지·어떤 건 고객인지 별도 DB화 — 불편해도 우선 수동, 단 1명이 여러 건(프로젝트 a/b/c)으로 고객 가능”. 모델: 고객 = subject 에 붙은 ≥1개의 customer_case attribute (kind=‘attribute’, key=‘customer_case’, value=case 라벨). 한 subject 가 여러 case 로 고객이면 case 마다 별도 attribute → 다대다. 별도 kind/table 안 만들고 attribute 전용 key 재사용 (“별도 DB화” 를 dedicated key 로 충족, 기존 archived/visibility/audit 인프라 그대로). MCP tool 2종: tag_customer(subject_id, case, note?, visibility=shared) — (subject, case) 멱등, person 이면 tier network 자동 승급 (고객=인맥); untag_customer(subject_id, case) — 해당 case attribute archive (멱등, tier 강등 X, 다른 case 유지). visibility default shared (전사 고객 카운트 근거). 대시보드: “고객 N명 / N개 회사” = customer_case 있는 distinct person / organization. MCP tool 수 23 → 25. 확장 여지: case 를 자유 텍스트 대신 deal/project artifact 로 승급하면 자동 파생 가능 (현재는 수동 우선). 2026-05-29 후속 — customer→account 리네임 + type 차원: 운영자 “customer 는 일차원적” → 개념을 account (거래 계정) 로 리네임 (tag_customer→tag_account, untag_customer→untag_account, key customer_case→account_case, 대시보드 라벨 ‘Account’). 동시에 type 차원 추가 (account_case payload 의 optional type, free-form 권장 vocab investor/client/advisor/partner/lp/vendor/portfolio/acquirer/other) — 1 account 가 case 별로 여러 type 가능 (예: investor + client). tag_account 에 type 파라미터 (같은 case 재태깅 시 type 만 UPDATE, 중복 X). 마이그레이션: 기존 58행 account_case 로 리네임 (audit actor=‘system:account-rename’), 활성 57 AXEC 행 type=‘investor’. 대시보드 sublabel 에 type별 distinct subject (상위 3). |
| D-cortex-dashboard-redesign | home 을 사람 중심 2-row (전사 / 나의) 로 재편 — relationship·kind 카드 제거 | 운영자 피드백 (2026-05-29 PM11): “relationship 은 사람에게 크게 중요한 정보 아닌 것 같다”. 기존 home = kind 카드 그리드(people/orgs/deals/interactions/relationships) → 그래프 내부 엣지(relationship) 카운트는 사용자에게 noise. 새 레이아웃 순서: 구글 연동 → 구글 Push 대기 → 전사 row → 안심 주석 → 나의 row. 각 row 4 지표: 전체 인원 N명 · 네트워크 N명/N개 회사 · 고객 N명/N개 회사 · 1개월 내 활동 N건/총 N건. 회사 = network person 들이 employed_by 로 연결된 distinct organization. 안심 주석 (전사 row 아래): “네트워크가 아닌 인원은 정보가 조직 내 공유되지 않습니다. 안심하세요.” (전사 ‘총’ 은 pool 포함하지만 익명 합계라 개별 pool 인원 정보는 비공유 — 운영자 결정 ‘pool 포함 총’). 구현: NetworkStats 한 구조 + NETWORK_STATS_SQL 한 쿼리를 per-user(set_current_user RLS) 와 전사(superuser bypass) 양쪽 재사용. stat_row/stat_cell (non-link — 집계는 per-user 리스트로 안 매핑). 30일 윈도우 created_at 기준 (occurred_at cast 실패 회피). 검증 (나의 row): 총 3600 / 네트워크 624 / 네트워크회사 413 / 고객 0(태그전) / 활동 1·총 1. |
| D-cortex-leader-election | blue/green run_loop 중 한 쪽만 sync 실행 — pg_try_advisory_xact_lock 으로 race 본질 차단 (D-cortex-3 PM7 follow-up) | D-cortex-3 PM7 가 blue/green binary 불일치 race 였는데 fix 가 “둘 다 —no-cache 재빌드 + binary sha 일치 검증” 운영 invariant 였음. 본질적 root cause = blue/green 둘 다 run_loop 를 돈다는 것 자체. binary 동일성은 race 가 일어나도 같은 결정을 내리는 우회. 본질 = race 자체 차단 → leader election. 구현: src/google/sync.rs::run_cycle 시작 시 state.pool.begin().await 로 transaction 열고 pg_try_advisory_xact_lock(SYNC_LEADER_LOCK_KEY) (i64 const = 2026052907 = PM7 incident 날짜) 시도. 성공 시 leader → run_cycle_inner 실행 → tx.commit() 시 advisory_xact_lock auto-release. 실패 시 INFO 로그 (sync cycle: leader lock held by other instance, skipping) 후 즉시 return. Transaction-scoped lock 선택 이유: session lock 은 sqlx pool 의 connection 재사용으로 release 대상 backend 가 모호. xact lock 은 COMMIT/ROLLBACK/disconnect 모두에서 auto-release → leader 인스턴스가 panic·crash·OOM 으로 죽어도 PG backend session 종료 시 lock 즉시 해제, 다음 cycle 에 다른 인스턴스가 leader. 무인 페일오버, lock 영구 stuck 위험 없음. 부수효과: Google API quota 절반 (이전 2× 호출 → 1×), DB 부하 절반. Race 차단 일반화: PM7 같은 binary 불일치 뿐 아니라 코드 수정 race / 동시 sync 가 같은 person 을 두 번 처리하는 모든 race 가 leader 한 명 보장으로 자동 차단. Ship hook 자동화 (Dockerfile 통합): cargo build --release && cargo test --release --bin cortex google::sync::tests 가 Dockerfile RUN 한 단계 — test 실패 시 image 생성 자체 불가 → 옛 destructive 코드가 production 으로 흘러갈 수 없음. 매 docker compose build 자동 검증. 운영 함정 갱신 (CLAUDE.md): blue/green 동일 binary 가 강제→권장 으로 격하 (leader election 도입 후엔 binary 가 달라도 한 쪽만 실행). 새 함정 = “skip 로그가 한 쪽에서 항상 안 보이면 그 쪽이 leader 가 못 됨 (DB pool 문제)”. 검증: (1) psql 두 세션 동시 시도 → 한 쪽만 t, 다른 쪽 f 받음 (mutual exclusion). 첫 세션 commit 후 두번째 시도 → t (auto-release). (2) Docker rebuild —no-cache both + recreate → 같은 binary sha 확인 + 첫 sync cycle 후 blue/green 중 한 쪽만 “owner sync ok”, 다른 쪽 “leader lock held, skipping”. (3) cargo test 5/5 통과 (회귀 차단). |
| D-cortex-9 | artifact visibility tier (shared|private) — owner 의 RLS 섬 밖으로 무엇이 나가는가를 통제하는 propagation gate | soohun.kang 2026-05-28 “Private Partition” 요청. 핵심 재구성: 현재 모든 artifact 는 이미 RLS (owner_only: owner_id=current_setting('cortex.actor'), 앱 role NOSUPERUSER NOBYPASSRLS) 로 owner 본인에게만 보임 — 타인·관리자·감사자가 볼 수 있는 경로 자체가 없음. 따라서 private 플래그는 본인 vs 타인 격리가 아니라 데이터가 owner RLS 섬을 벗어나는 미래 표면을 막는 게이트. 그 표면 = Blueprint M6 artifact mirror (D-bp-artifact-1, /architecture/artifacts): Blueprint Artifact 테이블은 workspace_id+entity_id scope 이고 owner_id·visibility 컬럼이 없어, mirror 시 owner 격리가 풀림. M6 이 아직 📐 설계 (2026 Q4) 라 mirror 가 생기기 전 지금이 visibility 를 박을 적기. 계약: 모든 outward propagation (Blueprint mirror / org export / dashboard) 은 WHERE visibility='shared' 강제, private 은 절대 RLS 섬을 안 떠남. owner 본인 세션은 shared+private 모두 봄. 기본값 정책: shared (Google 동기화 base 연락처는 이미 Google 에 존재하는 저민감 canonical) — 단 민감 분류(HPE 등)·xlsx import 의 sensitive 컬럼·명시 flag 는 private. 신규 sensitive 데이터(HPE 138건)가 아직 미유입(B-cortex-xlsx-import-run)이라 backfill 불필요. 단계: Phase 0 (지금) = artifact.visibility 컬럼 + list_persons(visibility=, exclude_classification_key/value) 필터 + write tool visibility/skip_audit_payload 파라미터 (§6 스톱갭). Phase 1 = Google contactGroup sync 화이트/블랙리스트 (유입 차단). Phase 2 (M6 착수 시) = Blueprint mirror 계약 + field-level privacy (payload private 하위객체 + 기존 CORTEX_PII_PASSPHRASE pgp 재사용) + private artifact 의 audit redact. Phase 3 (opt-in) = client-key sealed E2E (search·Google push 포기 trade-off). visibility 결정 UX: 기본 shared, write tool 4종 description 의 SENSITIVITY RULE 로 커넥터 LLM 이 민감 가능 내용에 한해 저장 전 ‘private/shared?’ 확인 (MCP 는 요청-응답이라 직접 못 물음 → LLM-guidance). 공유 인물 + 비공개 메모 = person shared + 별도 classify_person(visibility=private) attribute (별개 artifact). log_interaction 도 visibility 보유 (private 미팅 → participant relationship 도 상속). |
| D-cortex-google-drift | Google sync 가 인맥(network)의 소속/직급을 다른 meaningful 값으로 덮을 때 drift 알림 기록 — overlay 와 독립한 additive notification 레이어 | Google sync 는 D-cortex-3 에 따라 canonical 필드를 자동 overlay (Google = canonical). 하지만 org/title 이 “한 의미있는 값 → 다른 의미있는 값” 으로 바뀌면 = 이직/승진 신호 → 운영자가 대시보드에서 검토하도록 drift 레코드 생성. overlay 자체는 안 건드림 — drift 는 순수 additive. 판정 (overlay 와 별개): is_meaningful_change(old,new) = 공백 정규화 후 둘 다 non-empty 이고 서로 다름. null→value(채움)·value→null(지움)·whitespace-only 차이는 drift 아님. tier=‘network’ 만 (pool = raw Google dump 의 잡음 변화 무시). org·title 두 필드 검사. 레코드: kind='attribute', key=‘google_drift’, payload {subject_artifact_id, field:'org'|'title', old, new, detected_at, source_etag, acknowledged:false}, visibility=‘shared’, created_by=‘system:google-sync’ + artifact_event op=‘propose’. 멱등: 같은 (subject, field, new) 의 활성(archived=false, acknowledged=false) drift 가 있으면 skip. 모두 sync tx 안 (이미 RLS-scoped). 웹: home 에 ”🔔 변화 감지 (Google)” 카드 (미확인 N>0 시) → /cortex/google/drift 목록 (이름 link · 변화 “소속/직급: old → new” · 감지일 · [확인]) → POST /cortex/a/:id/ack-drift (payload.acknowledged=true + acknowledged_at + op=‘confirm’ event, RLS-scoped). D-cortex-6.1 read-only 예외 (push/skip 처럼 운영자 lifecycle 액션). 백필 안 함: 기존 audit 의 org/title 변경은 운영자 cleanup 이라 진짜 커리어 변화 아님 — going-forward 만. 회귀 차단: sync.rs #[cfg(test)] mod tests 에 9 drift test 추가 (총 15) — Dockerfile cargo test ... google::sync::tests gate 가 강제. |
부수 효과:
/Users/axe/cortex폴더 폐기 (cortex.legacy.20260526/),cortex.axellc.com도메인 라우트 삭제 (cloudflared~/.cloudflared/config.yml정리, mysrt.axellc.com 만 보존)- 32xx 포트 재할당: 3200 postgres / 3210 mcp-blue / 3211 mcp-green / 3212 proxy (frame 3700/3710/3711/3712 패턴)
customers.yaml:sso.apps.cortex_mcp(appId=60d04ea8-5d7b-453e-a4c0-af421b4689f5, az cli 등록 2026-05-26) +services.cortex(5 secrets: DB_PASSWORD, PII_PASSPHRASE_AXEC, JWT_SECRET, AZURE_CORTEX_MCP_CLIENT_SECRET, GOOGLE_OAUTH_CLIENT_SECRET)- Entra app: frame_mcp / hive_mcp / blueprint_mcp 와 동형 (signInAudience=AzureADMyOrg, requestedAccessTokenVersion=2, identifierUris=
https://axe.axelabs.ai/cortex/mcp, scopemcp.access, webRedirectUris=claude.ai+claude.com/api/mcp/auth_callback) - Postgres 5 테이블 (artifact / citation / artifact_event / mcp_schema / google_oauth_token), GIN+btree+partial index, append-only trigger, 4 RLS policy (owner_only USING+WITH CHECK)
- 10 MCP tools — auth middleware (Entra ID JWKS 1h TTL cache + RS256 + audience-issuer-exp 검증) 통과 후
Extension<AuthContext>로 owner_id 사용 - Google OAuth Web flow + People API client + per-user sync loop (600s 간격,
system:google-syncactor 로 artifact_event 기록)
운영자 작업 — 2026-05-28 production live 완료:
- ✅ 5 secret vault push (
cortex/axe/{db-password, pii-passphrase-axec, jwt-secret, oauth-client-secret, google-oauth-client-secret}) — 모두 stdin pipe (jq -Rsc) 로 shell history·context 미경유 - ✅ Google OAuth Web client — 신규 GCP 프로젝트
Cortex(idcortex-497605, 옛Gemini/Network Manager 폐기 예정). People API enable + consent screen External/Testing + client (135512942819-...apps.googleusercontent.com) - ✅ Cloudflare ingress —
axelabs터널은 Cloudflare Dashboard remote-managed (로컬 config.yml 무시 —Updated to new configuration version=N이 증거).axe의 기존 CF API token (vaultCloudflare API - axelabs) 으로PUT /accounts/.../cfd_tunnel/.../configurations하여axe.axelabs.aipath=cortex(/.*)?→host.docker.internal:3212추가 - ✅
docker compose up -d --build(postgres 3200 + mcp-blue 3210 + mcp-green 3211 + proxy 3212).cortex migrate(2 migration: artifact schema + oauth_authorization_codes) - ✅ claude.ai connector 등록 (URL + client_id + client_secret) → Microsoft OAuth → 12 tools 노출 + whoami 검증
- ✅ connect_google → 브라우저 consent ([email protected], Test users 등록 필요) → ”✓ Google 연결 완료”
- 🔧 (잔여)
cortex import-xlsx660 행 enrichment 1회 마이그 +axe mcp publishcatalog 등재 — B-cortex-xlsx-import-run, B-cortex-mcp-catalog-publish
시행착오 (전부 known-gaps 등재): DB password 의 base64 / 가 URL port 파싱 깨뜨림 (PgConnectOptions builder 로 회피) · docker compose env_file 가 따옴표 literal (axe secret pull quote 가 postgres auth 깨뜨림) · resource-level RFC 9728 path 누락 · RFC 8414 path-insertion · MCP tool property key 한글 (메모) 이 tools/list 전체 거부 · Cloudflare remote-managed tunnel · Docker image 가 옛 코드라 cargo build 만으론 부족 (--build 필수).
상세: services/cortex, /Users/axe/cortex/docs/operator-setup.md , /Users/axe/cortex/CLAUDE.md .
D-vault-mcp-catalog — MCP Connectors catalog view (Vaultwarden org collection)
axelabs 자체 운영 MCP (frame, hive, blueprint, …) 의 OAuth 등록 정보 4 조각 (이름, MCP URL, client_id, client_secret) 을 사용자가 claude.ai/customize/connectors 등 MCP host 에 등록할 때마다 4 곳에서 찾는 비효율 해소. catalog view = Vaultwarden 의 MCP Connectors organization collection (org-wide read access). 사용자는 Bitwarden 브라우저 확장만 열면 URI 매칭으로 자동 suggest.
| 항목 | 결정 |
|---|---|
| SoT 불변 | /Users/axe/.axe/customers.yaml customers.<cust>.sso.apps.*_mcp (client_id, application_id_uri, scopes) + Vaultwarden item <svc>/<cust>/oauth-client-secret (또는 blueprint/<cust>/mcp-client-secret) for secret. 데이터 중복 0 |
| catalog 위치 | AXE Vaultwarden org 0c5d8bbd-ad85-42b4-8b8a-2849031981b1 의 MCP Connectors collection ID 1a62e754-6e47-43e0-a99a-cf71c37b8638. 4명 org 멤버 모두 access (ai@/soohun@ = manage, taehun@/jinwoo@ = read-only) |
| item shape | Login item per *_mcp app. title = "{Svc} MCP ({cust})", username = client_id, password = client_secret, custom fields = MCP URL / Tenant ID / Scopes / Vault path, URIs = claude.ai/customize/connectors + claude.com/customize/connectors + MCP endpoint (모두 “Starts with” match → 브라우저 확장 자동 suggest) |
| publisher | axe mcp publish [--customer axe] — /Users/axe/.axe/bin/axe 의 cmd_mcp_publish. 매니페스트 순회 + vault fetch + bw create/edit upsert. idempotent. axe mcp list 는 read-only 미리보기 (vault 미접근) |
| hook | axe secret rotate <ENV> --service <svc> 가 MCP client_secret 회전 시 axe mcp publish 자동 호출 → catalog 의 password 도 즉시 갱신. 사용자가 옛 secret 으로 claude.ai 에 등록하다 OAuth 거부 받는 함정 사전 차단 |
| 신규 MCP 추가 시 | (1) Entra app 등록 + customers.yaml 의 sso.apps.<svc>_mcp 4-key (client_id, application_id_uri, scopes, client_secret_env) 추가 + services manifest 의 secret entry 등재 → (2) axe secret push → (3) axe mcp publish. 3 단계 표준 |
| realchoice (sovereignty 격리) | customer 측 vault 에 customer 자체 MCP Connectors collection. operator 의 catalog 에 customer secret 비포함. B-customer-sovereignty-architecture Q3 milestone |
함정 (도입 직후 발견): customers.yaml customers.axe.sso.apps.frame_mcp.client_secret_env 가 5/21 hive_mcp 등재 후에도 D-ops-15.5 의 “proxy path only” 주석 형태로 stale 처리되어 있었음 (실제 vault item 존재 + claude.ai 가 confidential client 요구). 본 D 도입 시 정정 — frame_mcp 도 secret 포함하여 catalog publish. 같은 함정 = 미래 새 MCP 추가 시 client_secret_env 누락 시 catalog item 이 (public) 으로 잘못 publish 됨. axe mcp list 출력의 “vault item” 칸이 (public) 이면 점검 신호.
D-ops-42 — 배포 SSOT 아키텍처 (origin/main SHA = 배포 진실원천)
다중 동시 Claude Code 세션이 repo 당 1 working tree + 1 main 을 공유하던 구조가 만든 배포 엉킴을 근본 해소. 원칙 한 줄: origin/main 의 commit SHA = 배포의 SSOT. working tree·로컬 main·실행 중 컨테이너는 전부 그 SHA 의 파생·일회용 투영. AXE 가 docs·backlog(matrix-postgres, D-matrix-3)에 이미 적용 중인 SSOT 규율을 배포로 확장한 것. 전체 본문 = /ops/runbook/deploy-ssot.
동기 (실측 incident, 2026-06-04): 공유 tree + 공유 main 이 세 가지 고장을 동시에 발생:
| 고장 | 메커니즘 |
|---|---|
| push 거부 | 타 세션이 먼저 push → 로컬 main 이 origin 과 diverge → non-fast-forward → push blocked |
| WIP 혼재 | 서로 다른 feature 의 uncommitted 변경이 한 tree 에 누적 → 누구도 자기 slice 만 clean commit 불가 |
| dirty 누출 | axe deploy 가 dirty working tree 에서 이미지 빌드 → uncommitted 코드 prod 누출 + push 전 배포 가능 |
구체 사례: 이미 배포까지 끝난 blueprint fix (commit b4067504) 가 main diverge + 타 세션 WIP 로 수 시간 push 불가 → 운영자가 수동 reconcile (stash→rebase→push). 추가 발견 = 운영자 axe CLI (364KB) 자체가 버전 미관리 + in-place 편집 = 같은 병의 가장 깊은 사례 (CLI 가 배포 도구인데 자기 자신은 SHA 추적 밖).
컴포넌트 — 각자 제거하는 고장:
| 컴포넌트 | 동작 | 제거하는 고장 |
|---|---|---|
| A. 작업 격리 | axe work <svc> <slug> = 세션별 git worktree (~/.worktrees/<svc>/<slug>) off origin/main. 정규 repo 는 fast-forward 전용 “main mirror” 강등 (손편집 금지) | 공유 tree 경합 · WIP 혼재 · stash 더미 = 발생 불가능 |
| B. SHA 에서만 빌드 | deploy 가 working tree 가 아니라 pushed SHA 의 clean checkout (git worktree --detach/archive) 에서 빌드. 이미지 태그 <svc>:<sha>. push 안 된 SHA 배포 거부 | deploy-before-push + dirty-tree 누출 = 구조적 불가능 |
| C. Deploy lock | 서비스당 배포 직렬화 (matrix-postgres pg_advisory_lock 우선, 파일락 fallback) | 두 세션이 같은 서비스 blue/green 동시 flip 불가 |
| D. Provenance + drift | 컨테이너 org.axe.git_sha 라벨. axe health/axe host 가 color 별 실행 SHA + origin/main 대비 drift 표시 | ”지금 라이브가 git 과 일치하나” 에 항상 답 (silent drift 소멸) |
| E. 잘못된 경로 제거 | axe deploy 가 tree 입력 폐기. git push origin main 직접 = pre-push 훅 + GitHub branch protection 으로 기계적 차단. axe ship 만 main 전진 | honor-system 의존 (현재 = 사회적 규약뿐) 제거 |
| F. 통합 ship | axe ship (worktree 에서): fetch → origin/main rebase → SHA build+test → push (main ff) → 그 SHA deploy (lock 하) → mirror ff → shiplog | 오늘 운영자가 손으로 한 reconcile 전 단계가 한 명령 |
스테이지 상태기계 (blue/green 이 이미 3스테이지 제공):
built → canary(passive color, 트래픽 0) → [migration-validation 게이트] → live(active flip) → previous(직전 active = 즉시 롤백)migration-validation (안전 척추): 스키마 마이그레이션 포함 릴리스는 flip 전에 prod DB 의 ephemeral clone 에 마이그레이션을 적용·검증 (axe drill/backup 스냅샷 기계 재사용). 본질적 이유 = blue 와 green 이 DB 를 공유하므로 깨지는 스키마 변경은 blue/green 으로 카나리가 불가능 (green 용 마이그레이션이 blue 도 즉시 오염). 이 게이트가 blue/green 이 못 막는 유일한 위험을 덮음. 대상 = blueprint 대기 migration 2개 (add_user_entra_oid, add_entity_legal_name), hive alembic.
rollout (additive → canary → cutover): 전부 기존 동작과 공존 (additive) → --dry-run + passive-color 카나리 (flip 안 함) 검증 → cutover (기본값 flip + 가드 활성화, escape hatch 포함) 는 게이트됨 → 되돌림 가능. 진행 중 WIP 보존.
채택 안 함:
- 별도 staging 환경 (staging.axelabs.ai + 독립 DB/터널) — 단일 Mac mini 호스트에서 새 실패유형은 안 잡으면서 비용·parity 만 증가. canary + migration-validation 이 실위험을 이미 커버.
- 작업추적에 새 “스테이지” 축 — roadmap/backlog/updates 가 이미 작업 스테이지 파이프라인 (D-matrix-3). 배포 스테이지와 작업 스테이지를 혼동하지 않음.
정합: D-ops-16 (docs drift = release-gate axe ship) 의 release-gate 를 배포 진실원천까지 확장 · D-matrix-3 (matrix-postgres SSOT) 의 SSOT 규율을 배포로 확장 · Blue/green deploy 의 alias swap 메커니즘 재사용.
D-ops-44 — Vault 비밀 주입 = SSH 환경 raw-bw keychain-free (axe vault unlock 금지)
이 머신은 AXE 의 Mac mini 이고 Claude Code 세션은 SSH(loopback) 위에서 돈다. 운영자는 Windows 에서 ssh [email protected] (Tailscale) 로 들어온다. macOS 는 SSH 세션이 GUI login keychain 에 쓰는 것을 거부한다 (“User interaction is not allowed”). 그 결과 vault 비밀을 넣는 통상 경로 (keychain 세션 의존) 가 SSH 컨텍스트에서 전부 막힌다. 본 결정 = SSH 환경에서 비밀을 vault 에 넣는 정답 경로를 keychain-free raw-bw 로 고정하고, 이를 /Users/axe/CLAUDE.md 에 강제 규칙으로 박제한 것. 운영자-facing how-to = /architecture/secrets#ssh-환경에서-vault-비밀-주입-keychain-free-raw-bw.
금지 (SSH 에서 전부 실패):
| 금지 | 이유 (SSH 에서 깨지는 메커니즘) |
|---|---|
운영자에게 axe vault unlock 시키기 | unlock 의 keychain-cache 단계가 SSH 세션에서 crash |
axe secret push/pull/check 로 비밀 PUT | 내부 _vault_env 가 keychain 세션만 읽음 (security find-generic-password -s axe.vault.session -a [email protected]) → SSH 에선 “vault session not found” |
osascript display dialog | GUI 다이얼로그 = 원격/Windows 운영자에게 안 보임. 운영자-타이핑 비밀은 read -rs 로 받음 |
정답 = keychain-free raw-bw (운영자가 자기 인터랙티브 셸 — Windows ssh [email protected] 또는 Mac 터미널 — 에서 직접 실행). 에이전트는 스크립트를 ASCII-only · 주석 없음 · bw unlock 을 단독 한 줄로 공급한다 (한 블록으로 붙여넣으면 unlock 프롬프트가 바로 다음 줄을 비밀번호로 삼켜버림). 단계:
export NODE_EXTRA_CA_CERTS=/Users/axe/.axe/vault/certs/rootCA.pem— Vaultwarden private CA, 필수.- 자기 단독 줄에서
export BW_SESSION="$(bw unlock --raw)"— master-pw 프롬프트. TTY-flaky 폴백:read -rs PW; export BW_SESSION="$(BW_PASSWORD="$PW" bw unlock --passwordenv BW_PASSWORD --raw)"; unset PW. - raw
bw create/bw edit로 Login item 생성·갱신 — name =customers.yaml의services.<svc>.secrets[].vault경로 (예gate/axe/jwt-secret), username = env var 이름, password = 스크립트 안에서 생성/읽은 값 ($(openssl rand -hex 32)/$(cat key.pem)/ 상수 — echo·history·argv·agent-context 어디에도 안 남김) →bw sync→unset BW_SESSION.
검증 (에이전트는 검증 불가): BW_SESSION 이 운영자 셸 안에만 살아 있으므로 에이전트는 결과를 직접 확인할 수 없다. 운영자의 created/updated + synced 출력이 곧 확인이다. axe secret check 호출 금지 (keychain 필요).
keychain 세션이 필요한 곳 = deploy/ship 시점 한정: axe ship/axe secret pull 은 그때 keychain 세션을 읽는다. SSH 에서 GUI 없이 그 세션을 채우는 법 = read -rs KCPW; security unlock-keychain -p "$KCPW" ~/Library/Keychains/login.keychain-db; unset KCPW (headless unlock) → axe vault unlock → axe ship. 비밀을 넣는 작업은 이 단계가 불요 — raw-bw 가 keychain 없이 vault 에 직접 쓴다.
정합: D-ops-17 (vault = canonical SoT, manifest 매핑) · D-ops-40 (osascript hidden-dialog 패턴 — GUI 세션 전제라 SSH 컨텍스트에선 본 raw-bw 가 대체). 더 깊은 잔여 격차 (=_vault_env 가 keychain 만 읽고 env-session 폴백이 없는 것) 는 /ops/known-gaps 에 분석 기록.
⚠️ 함정 — 회피 필수
| # | 함정 | 결과 | 회피 |
|---|---|---|---|
| 1 | pull-polling 배포 | 1시간+ lag | push (D6) |
| 2 | 자체 mTLS | 운영 부담 | Tailscale + restic 암호화 |
| 3 | schema-per-customer (한 macmini 다중 customer) | 격리 약 | macmini 1대/customer |
| 4 | OAuth 1차 시도 | 도메인 검증 등 함정 layer | Phase 3 deferral 까지 |
| 5 | 2-level subdomain SSL | 유료 ($10/cert/월) | 1-level + path |
| 6 | service-only domain (frame.axelabs.ai) | customer 격리 불가 | {customer}.axelabs.ai/{service} |
| 7 | .local 도메인 | mDNS 충돌 | platform/corporate 분리 |
| 8 | axellc.com 으로 platform 트래픽 | corporate ↔ platform 혼탁 | axelabs.ai 만 |
| 9 | cloudflared path strip 가정 | 라우터에서 prefix 못 찾음 | 서비스에 prefix mount |
| 10 | cloudflared SIGHUP 직접 호출 | process 죽음 | docker restart |
| 11 | frame container 직접 cloudflared 바인딩 | swap 불가 | host-side proxy 경유 |
| 12 | 다운타임 deploy | 사용자 5xx | blue/green |
| 13 | .claude/settings.json 의 mcpServers | silent 무시 | .mcp.json (D-ops-7) |
| 14 | Microsoft Application ID URI 와 scope prefix 불일치 | AADSTS9010010 | 둘 다 같은 URL prefix |
| 15 | Allow public client flows: No + secret 없음 | AADSTS7000218 | Yes 또는 secret |
| 16 | accessTokenAcceptedVersion=2 + Anthropic 의 aud check | mcp_client_invalid | null/1 (v1 token, aud=URI) |
| 17 | App ID URI 삭제 cleanup | AADSTS500011 | 복원 후 propagation 대기 |
| 18 | compose environment: 의 $VAR default-empty 치환 (docker-compose dollar-brace VAR colon dash brace 형식) 가 env_file 값을 shadow | 컨테이너 secret 빈 값 | environment: 에서 비밀 var 제거 (D-ops-18) |
| 19 | compose subdir (REPO/docker/ 형태) 의 Docker auto-load .env 부재 | $VAR substitution (docker-compose 변수) 실패, 컨테이너 미부팅 | ln -sf ../.env docker/.env symlink (D-ops-18) |
| 20 | axe secret pull 옛 버전이 .env 전체 덮어씀 | 비밀 외 config 라인 wipe (2026-05-21 blueprint 사고) | 코드 fix 완료 (merge-mode). pull 출력에 “non-managed lines preserved” 확인 |
| 21 | portal-등록 Azure app 에 owner 미지정 | az ad app credential reset 권한 거부 | 운영자를 portal 의 Owners 탭에 명시 추가 (D-ops-19) |
| 22 | az ad app credential reset (no --append) | 기존 secrets 전부 삭제 → overlap window 없음 | 항상 --append (D-ops-19) |
| 23 | 새 fact 도입 시 schema 를 AXE 가 직접 정의 시도 | AXE 가 schema authority 부담 → 작업 정체 + drift | MCP 가 schema 권위 (frame/hive 등 — 자기 도메인 /schemas endpoint). 자기 도메인 MCP 없으면 LLM 자율 free-form. Blueprint 는 discovery / mirror only (D-bp-artifact-1) |
| 24 | artifact extraction 을 Anthropic API direct call 로 구현 | per-token 비용 + per-user attribution 손실 + Max plan 무제한 활용 못 함 | Claude Code OAuth (Max plan) 통해 호출 — Teams bot / 로컬 CLI / 웹 모두 same surface. API direct 는 대량 batch / vision-heavy 만 (D-bp-artifact-7) |
결정 변경 절차
새 결정 또는 기존 결정 변경 시:
- multi-tenant-platform-plan.md 또는 frame/DECISIONS.md 에 추가 (해당하는 곳)
- 본 페이지의 표 갱신
- 영향 받는 다른 docs 페이지 수정
- customer admin / 직원에게 통지 (영향 받는 경우)
회고 주기
분기마다 (Jan/Apr/Jul/Oct 15) DECISIONS 회고:
- 채택한 결정 중 후회 있는 것
- 함정에 빠진 사례
- 새로 발견된 함정
- D-ops 회수번호 (deprecate 결정)
참고
/Users/axe/multi-tenant-platform-plan.md— 마스터 plan + D-config-N/Users/axe/frame/DECISIONS.md— frame D-ops-1~15/Users/axe/hive/DECISIONS.md— hive D-hive-1~25/Users/axe/blueprint/docs/project-blueprint.md— blueprint 마스터 (5 분과)/Users/axe/CLAUDE.md— 포트 · launchd 인벤토리