명령 하나, 포트는 원장에서
엔진은 cwk-avatars Conda 환경에서 python -m backend.serve로 떠. Uvicorn은 설정된 포트로 모든 인터페이스에 붙고 proxy_headers=False를 줘. 그래도 괜찮은 이유는 호출 바로 위 주석에 있어. 앱의 네트워크 관문은 루프백과 tailnet 피어만 통과시킨다고. 넓게 붙고 앱 안에서 막는 건 Solo Auth Quest가 0.0.0.0 결정이라고 부르는 패턴이야. 프록시 헤더를 끄면 전달 헤더 하나로 요청이 다른 데서 온 척할 수 없어. 관문 자체는 트랙 8에서 하나씩 짚어.
settings.py엔 포트 숫자가 하나도 없어. 엔진 포트와 Tailscale Serve HTTPS 트윈은 가족 레지스트리에서 와. 그 레지스트리는 Firelink 포트 원장을 벤더링한 사본이야. 거기서 Avatars는 엔진 4100, 트윈 4101이고, Vite 개발 서버 4102는 예정으로 올라가 있어. 출처도 같은 레지스트리에서 와. 피파와 Firelink는 스튜디오와 같은 Mac에서 도니까 루프백으로 불러. Ember는 HTTPS 트윈으로 불러. tailnet IP를 그대로 박아 부르는 일은 절대 없어. 이 스냅샷의 레지스트리는 Ember도 같은 Mac에 둬. 그러니까 트윈은 코드가 읽는 주소일 뿐이고, 멀리 있다는 표시가 아니야. 공개 출처는 스튜디오 자기 트윈이야. 데이터 루트도 레지스트리에서 오고, 테스트할 땐 AVATARS_DATA로 덮어써. 밑에 깔린 원칙은 Firelink Quest의 트랙 '사실 하나에 소유자 하나'야. 포트는 주인이 하나고, 나머지는 읽기만 해.
이사 뒤에도 남은 숫자
그 숫자의 사본 두 개가 살아남았어. iOS 앱의 Engine.swift엔 static let twinPort = 4101이 있어. 레지스트리에 Avatars 트윈이 없을 때만 쓰고, 그땐 앱이 피파 호스트의 그 포트로 물러나. vite.config.ts의 개발 프록시는 /api를 http://127.0.0.1:4100로 보내. 하우스 사인엔 Vite가 strictPort를 써야 한다고 적혀 있어. 그런데 개발 스크립트에도 설정에도 그게 없어. 스크립트는 vite --host 127.0.0.1 --port 4102뿐이야. 4102가 이미 쓰이고 있으면 개발 서버가 조용히 다른 포트로 옮겨 가. 지금은 값이 전부 원장과 맞아. 그래도 사본은 사본이고, 원장이 포트를 옮기는 날 버그가 돼. 코드나 문서가 원장과 다르면 원장이 이겨.
자기 자리를 묻는 LaunchAgent
scripts/install-service.py는 LaunchAgent com.cwk.avatars-serve를 써. 켜질 때 실행, 계속 살려 두기, 재시작 사이 10초 간격, 로그는 데이터 루트 밑. 그런데 제일 먼저 하는 일은 거절이야. Avatars에 쓰는 쪽은 하나뿐이고, 스크립트는 runs_here("cwkAvatars")로 이 Mac이 그 자리인지 레지스트리에 물어. 10월 19일까진 플릿 라벨로 확인했어. 가족이 서비스를 새 호스트로 옮겼을 때, 레지스트리에 묻는 덕분에 설치 스크립트도 같이 따라갔지. 마지막으로 찍는 줄은 증거부터 보라는 규칙이야. 서비스가 살아 있다고 말하기 전에 /api/health부터 보라고. 헬스는 제품 이름, ok, cwk-product.json의 버전을 답해. 지금은 0.1.4야.
엔진은 frontend/dist에서 빌드된 웹 클라이언트도 내줘. 셸은 no-cache로 보내니까, 설치된 페이지는 켤 때마다 새 버전인지 확인해. 클라이언트를 다시 빌드하는 것과 서비스를 재시작하는 건 별개 단계고, git pull은 둘 다 안 해.
테스트가 세는 것
| 스위트 | f6cd664 시점 개수 | 상태 |
|---|---|---|
| 백엔드, pytest | 파일 7개에 41개 | 실행: 41개 통과 |
| 프런트엔드, Vitest | 파일 8개에 32개 | 실행: 32개 통과 |
| iOS 단위, XCTest | 파일 5개에 40개 | 센 것. 마지막 기록된 실행은 실패 0 |
| iOS UI, XCUITest | 2개 | 센 것. 느린 업로드 벤치는 자기 엔진이 없으면 건너뛰어 |
README와 옛 커밋 메시지엔 더 작은 숫자가 남아 있어. 그날그날의 숫자야. 테스트 개수는 날짜가 붙은 관측이야. 그래서 이 표도 스냅샷을 밝혀 둬. cwkAvatars f6cd664, 2026년 10월 23일 커밋이야. iOS 스위트는 거기서 센 숫자고, 마지막으로 돌린 기록은 10월 19일 fc0a21d야. 백엔드 테스트 하나는 가족 키트의 드리프트 검사를 이 체크아웃에 돌려. 그래서 벤더링한 파일을 손으로 고치면 스위트가 실패해. 그 검사가 왜 쓰는 쪽 테스트에 사는지는 Sibling Kit Quest의 트랙 '이빨 달린 검사'가 설명해.
TestFlight: 사실 넷, 따로따로
iOS 앱은 TestFlight로 나가. 케이블 설치는 절대 안 해. scripts/mobile-testflight.sh는 키트에서 벤더링한 거야. 가족의 iOS 앱 전부를 위한 프로그램 하나고, Avatars는 그 옆에 자기 사실만 둬. 그 사실은 이래. 번들 id com.cwk.avatars.mobile, 전용 시뮬레이터, 커밋돼 있어야 하는 입력, project.yml의 빌드 번호, cwk-product.json의 마케팅 버전. 가드는 정해진 순서로 돌고, 테스트와 첫 화면마다 찍어 보는 시각 검사까지 빌드 번호가 움직이기 전에 다 끝나. 손으로 키운 사본 여덟 개가 어떻게 이 프로그램 하나가 됐는지는 Sibling Kit Quest의 레슨 '프로그램 하나, 설정 여럿'에 있어.
파이프라인의 성공 줄이 보증하는 사실은 하나뿐이야. 그런데 전달 사실은 넷이야.
- 업로드: App Store Connect에 빌드가 있어.
- 처리: 애플이 처리를 끝냈어.
- 배정: 내부 테스트 그룹이 받았어.
- 설치: 테스터 기기에 깔렸어. 애플의 설치 수는 늦게 따라오니까, 0은 아직 안 셌다는 뜻이지 안 깔렸다는 뜻이 아니야.
빌드 1부터 21까지 파이프라인 커밋이 있어. 하나하나가 관문을 통과한 업로드(Destination: upload)를 기록한 거고, 네 가지 사실 중 첫째일 뿐 전달이 아니야. README의 기록도 사실을 똑같이 갈라 둬. 빌드 8은 업로드됐고, 처리됐고, 내부 테스트에 들어갔고, 기기 설치는 아직 안 셌다고. 제품은 v0.1.4야. 빌드 21이나 웹 패키지의 0.1.0은 번호 체계가 달라.