Layer
한 줄 소개: 보낸 문서가 어떻게 읽히는지 보이게 하는 서비스. PDF 를 올리면 공유 링크가 생기고, 받은 사람이 로그인 없이 웹 뷰어로 읽는 동안 페이지별 체류·완독률·리드 정보를 수집한다 (featpaper/DocSend 류, D-layer-1).
기술 스택
| 항목 | 값 |
|---|---|
| 언어 | Python 3.12 |
| 프레임워크 | FastAPI (async) + Jinja2 + vanilla JS (빌드 도구 없음) |
| DB | PostgreSQL 16 + SQLAlchemy 2 async + Alembic |
| 문서 처리 (v2, D-layer-4) | HTML 네이티브 — nh3 sanitize + 헤딩 자동 섹션추출(htmlproc). PDF 래스터(PyMuPDF) 폐기, PDF 는 @media print 출력 기능만 |
| 디자인 | @axe/ui vendor v0.5.0 (build-time 번들) + 폰트 self-host (CDN 0) |
| 배포 | docker compose blue/green + Caddy proxy (hive 패턴) |
| repo | github.com/axelabs-ai/layer (canonical /Users/axe/layer) |
포트 (42xx)
| 포트 | 용도 |
|---|---|
| 4200 | PostgreSQL 16 (layer-postgres) |
| 4210 | app blue (active) |
| 4211 | app green (passive) |
| 4212 | axe-layer-proxy (Caddy, blue/green selector, 127.0.0.1 바인딩) |
URL 컨벤션 — 듀얼 마운트 (라이브, D-layer-2)
B-platform-domain-scoping 의 2축 규칙을 그대로 적용. 같은 앱이 두 곳에 동시 노출 (gate 선례 미러링):
| URL | 의미 | root_path |
|---|---|---|
https://layer.axelabs.ai | 전역 서비스 웹사이트 (익명 랜딩 + 로그인) | "" |
https://axe.axelabs.ai/layer | axe 내부 문서 회람 | /layer |
- cloudflared 가 path prefix 를 보존 → 앱이
scope["path"]로 prefix 자체 감지 (MountPrefixMiddleware, 외부 헤더 비신뢰). Caddy 는 strip 안 함 (gate 패턴). - 공유 뷰어(무로그인):
/v/{slug}· 페이지 이미지/v/{slug}/p/{n}.jpg· 트래킹/v/{slug}/events(각 마운트의 base 로public_base(request)가 절대 URL 생성). - 헬스:
/health/ready(DB 포함) ·/health/live - 로컬:
http://127.0.0.1:4212/(proxy) · blue:4210/ green:4211.
도메인 모델
v2 (HTML 네이티브, D-layer-4): users → documents → document_versions(링크 유지 버전 교체) → sections(헤딩 anchor + level + title). 공유 = links(slug, access_mode open/gated/allowlist × identify_policy email/phone/either/both, revoked_at) + link_allowlist(email/phone/domain). 추적 = visitors(email/phone 식별) → visits(visit_token, max_scroll_pct) → section_dwell(섹션별 ms 가산) + click_events. (구 pages/page_dwell 폐기 — alembic 0002)
엔게이지먼트 등급: 방문이 0.5초 이상 본 페이지 비율 기준 HOT ≥70% / WARM ≥50% / COLD 미만 (total 0.5s 미만 방문 제외 — featpaper 벤치마크 호환).
환경 변수 / 비밀
customers.yaml services.layer.secrets[] 등재 (vault 캡처 = 운영자 raw-bw 1회 필요):
| env | vault | 용도 |
|---|---|---|
LAYER_DB_PASSWORD | layer/axe/db-password | postgres |
LAYER_SECRET_KEY | layer/axe/secret-key | 세션 서명·ip 해시 |
LAYER_ADMIN_PASSWORD | layer/axe/admin-password | 초기 admin 시드 (LAYER_ADMIN_EMAIL 과 페어) |
비밀 아닌 설정: LAYER_DB_HOST/PORT/USER/NAME, LAYER_DATA_DIR(기본 /data 볼륨), LAYER_PUBLIC_BASE_URL(no-Host 폴백), LAYER_MOUNTED_PREFIX(기본 /layer — axe 마운트 prefix).
SSO (Microsoft Entra ID, D-layer-3)
| env | vault / 값 | 용도 |
|---|---|---|
LAYER_OIDC_CLIENT_ID | 0e034eee-81a8-4e5f-a61b-1ff24ac7a5b0 (비밀 아님) | Entra Layer Web 앱 |
LAYER_OIDC_CLIENT_SECRET | layer/axe/oidc-client-secret | confidential client secret |
LAYER_OIDC_TENANT_ID | 122fb574-… (비밀 아님) | AXE 테넌트 |
LAYER_OIDC_ALLOWED_DOMAIN | axellc.com | email 도메인 화이트리스트(authZ 경계) |
세 OIDC 값이 모두 있을 때만 SSO 활성(oidc_enabled); 없으면 password 로그인만(로컬/테스트). 로그인 = /auth/sso/login → Entra → /auth/sso/callback. 자세한 검증/인가/흐름 보안은 auth.mdx “Layer Web app 설정”. 부트스트랩 운영자 계정은 sso: 타입, break-glass [email protected](password).
상태 (2026-06-13)
- ✅ MVP — 업로드→링크→뷰어→추적→분석 핵심 루프 (B-layer-mvp), tests 41 green.
- ✅ 라이브 노출 —
layer.axelabs.ai(전역) +axe.axelabs.ai/layer(axe 회람) 듀얼 마운트, HTTPS e2e(로그인→대시보드, 양쪽 마운트 prefix·static·Set-Cookie 스코프·스푸핑 방어) 실측 (D-layer-2). - ✅ SSO 로그인 — Microsoft Entra ID OIDC(authorization code flow, FastAPI self-impl), 적대 리뷰 7건 반영, admin-consent 완료. 라이브 authorize redirect 양쪽 마운트 정확일치 실측, tests 63 green (D-layer-3). 브라우저 왕복은 사용자 실로그인 시 완결.
- ✅ v2 HTML 네이티브 전환 (D-layer-4) — PDF 래스터 폐기 → HTML 반응형 뷰어(@axe/ui, 목차 스크롤스파이+읽음 dot, 햄버거) + sandbox iframe(allow-scripts, allow-same-origin 제외)+엄격 CSP(script-src nonce 1개)+nonce 추적→postMessage→부모 인증 POST. 이메일/전화 식별+allowlist, 섹션/스크롤 인게이지먼트(문서목록 행 노출). 3-wave 빌드(기초→라우터/UI→마무리), 적대 보안검토(data: URL XSS 차단)·실 Chrome e2e(추적 실행 검증), tests 114 green. 라이브 DB 0002 마이그레이션 + 컨테이너 재배포 + 클린 슬레이트 커토버 완료. 설계 =
~/layer/ctx/layer-v2-redesign-proposal.md, 계약 =~/layer/CONTRACT_V2.md. - ⏳ 잔여 (B-layer-platform-integration): axe-cli
SHIP_SERVICES등록(현재axe ship layer불가 — 수동docker compose --env-file .env.local up -d --build), vault 캡처 4건(DB/secret/admin + OIDC client secret — 현재.env.local에만, 운영자 raw-bw 1회). - Phase-2 defer: 멀티테넌트/과금, 영상·임베드 오버레이(Motion), 메일머지 링크, Zapier/플러그인, AI 요약·인용 최적화
함정
| # | 함정 | 증상 | 회피 |
|---|---|---|---|
| 1 | 40xx/41xx 가 비어 보임 (CLAUDE.md 포트표 미갱신) | 신규 서비스 포트 충돌 | 40xx=index, 41xx=gate 점유. 배정 전 lsof 실측 (layer=42xx) |
| 2 | axelabs-docs repo 에 origin 원격 없음 | axe work docs <slug> 가 fetch 실패 | docs 는 CLAUDE.md 빠른 갱신 절차대로 공유 트리 직접 편집 + 본인 파일만 선택 커밋 |