아카이브 전에 서는 관문
scripts/test-mobile.sh는 iOS 스위트의 정문이야. 파이프라인은 빌드 번호를 건드리기 전에 이걸 먼저 돌려. SIZZLE_MOBILE_SIMULATOR_ID가 없으면 시작을 거부하고, 쓸 수 있는 기기 목록을 보여줘. 한 Mac의 시뮬레이터를 여러 앱 세션이 같이 쓰거든. 마지막에 열려 있던 아무 시뮬레이터에 올라탄 실행은 다른 앱의 화면을 뺏어. 그래서 스위트는 전용 Sizzle QA iPhone에서 돌아. 먼저 xcodegen으로 Xcode 프로젝트를 다시 만들어. 원본은 project.yml이고, 재생성 없이 추가한 테스트 파일은 스위트가 조용히 안 돌리거든. 이어서 xcodebuild test를 돌리고 출력을 로그로 남겨. 판정은 그 로그의 ** TEST SUCCEEDED ** 배너로 해. 필터를 건 파이프의 종료 코드는 필터의 결과일 뿐이니까. 단위 테스트는 앱 안에서 돌고, 거기 나오는 클라이언트는 전부 미리 짠 전송 계층 위에 있어. 그래도 UI 타깃은 엔진에 닿아. 거기 테스트는 진짜 앱을 띄우고, 진짜 앱은 평소 쓰는 전송 경로로 설정된 엔진에 연결해. 사진 지우기 테스트도 앱을 띄우는 것만으로 접시 목록을 읽어. 그 통신은 아무것도 확인 안 하니까 답이 오든 말든 통과하긴 해. 일부러 엔진을 겨누는 건 업로드 벤치마크야. SIZZLE_BENCH_ENGINE을 주면 킷의 속도 제한 프록시 뒤에 있는 진짜 엔진에 진짜 앱을 띄우고, Save 타임라인을 찍어. 그 변수가 없으면 건너뛰고, 평소엔 그렇게 돌아. 스크립트 머리 주석은 아직도 여기선 어떤 엔진에도 안 닿는다고 말해.
프로그램 하나, 앱의 사실은 설정 파일에
scripts/mobile-testflight.sh는 킷에서 벤더링한 파일이고, 머리말에 가족의 모든 iOS 앱을 위한 프로그램 하나라고 적혀 있어. 2026-09-14에 아빠가 그렇게 정했어. 손으로 키운 사본 여덟 개를 조사해 보니, 저마다 다른 안전장치가 하나씩 빠져 있었거든. Sizzle이 보태는 건 scripts/mobile-testflight.config.zsh가 전부야. 대입문은 13개야. 앱 이름, 결과 줄의 표식, 경로들, 번들 id com.cwk.sizzle.mobile, 아이콘, 시뮬레이터 변수가 들어가. 커밋돼 있어야 할 입력, 빌드 번호가 사는 곳, 워치 앱이 없다는 사실, 번호 올림 커밋의 제목, 마케팅 버전의 출처도 있어. 사본들이 어떻게 프로그램 하나가 됐는지는 sibling-kit-quest의 how-a-duplicate-dies 트랙에 있어.
가족의 첫 아이폰 앱은 Firebrand였고, 아빠가 그 모양을 뒤따르는 모든 앱의 기준으로 정했어. 그 폰 이야기는 firebrand-quest의 remote-vessel 트랙이 다뤄. Sizzle은 나중에 와서 사본 대신 합쳐진 파이프라인을 받았어.
단계 하나보다 순서가 더 중요해. 인자와 설정, App Store Connect 키, 전용 시뮬레이터, 커밋된 입력, 깨끗한 킷, 아이콘, 스위트, 폰 폭의 시각 관문. 이걸 다 지나야 빌드 번호, 아카이브, 빌드 중에 HEAD와 입력이 안 움직였는지 확인, 아카이브 관문, 내보내기나 업로드가 오고, 맨 끝에 번호 올림 커밋과 결과 줄이 와.
2번부터 19번까지
빌드 번호는 project.yml의 CURRENT_PROJECT_VERSION에 살아. 돌 때마다 하나씩 올리고, 프로젝트를 다시 만들고, 끝에서 그 변경을 커밋해. 커밋 본문엔 원본 커밋과 목적지가 적혀. 그래서 git이 장부가 돼. 2번 빌드는 내보내기만 했고, 그걸로 번들 id가 등록됐어. 그 뒤에 아빠가 App Store Connect 레코드를 만들었고. 3번부터 19번까지 17개는 업로드였어. 마지막이 2026-10-22고, 지금 project.yml엔 19가 적혀 있어. 폰 쪽에서 19번 뒤의 커밋들은 글만 바꿨어. 주석이랑 Settings 화면 문구. 엔진은 달라졌어. 13c12f8은 스테이징 문이 파트를 읽는 동안 크기 상한을 지키게 바꿨고, 킷 동기화 커밋 여럿이 벤더링한 레지스트리를 고치고 파이프라인 스크립트도 건드렸어. 그중 폰 코드는 하나도 없고, 그 뒤로 나간 빌드도 없어.
번호 올림 커밋이 스크립트 어디에 있는지 봐. 업로드 관문이 내보내기의 종료 코드와 로그 배너를 둘 다 받아들인 뒤에야 써져. 업로드가 실패하면 그 전에 빠져나가면서 번호 올림을 되돌리라고 알려줘. 그러니까 본문에 Destination: upload가 적힌 커밋은 사실 1의 증거야. 그 뒤의 사실에 대해선 아무 말도 안 해.
사실 넷, 증인 넷
| 사실 | 무엇이 증명해 | 어디서 읽어 |
|---|---|---|
| 1. 업로드됨 | SIZZLE_MOBILE_UPLOAD_OK 줄, 그리고 번호 올림 커밋 | 파이프라인 자기 출력 |
| 2. 처리됨 | processingState가 VALID | App Store Connect에서 읽는 scripts/testflight-facts.sh |
| 3. 배정됨 | internalBuildState가 IN_BETA_TESTING | 같은 스크립트 |
| 4. 설치됨 | 0보다 큰 설치 수, 아니면 기기 자체 | 설치 수는 실제 설치보다 늦어. 폰의 TestFlight 앱이 증인이야 |
testflight-facts.sh는 사실 2부터 4까지를 GET 요청으로만 읽어. 팀 키는 업로드 관문과 같은, 하나뿐인 키 읽기 경로로 받아. --wait를 주면 사실 1부터 3까지가 설 때까지, 아니면 시간이 다 될 때까지 30초마다 다시 물어. 마지막 기계용 줄은 설치를 yes, not_counted, unknown 중 하나로 적고, 설치 안 됨이라는 값은 절대 안 써. 실제 설치보다 늦게 따라오는 사용량 지표의 0은 아무것도 증명 못 하니까.
Sizzle 자기 기록이 이 조심이 왜 필요한지 보여줘. 3번 빌드 때 이 스크립트는 업로드, 처리, 내부 그룹까지 확인했는데, 설치 수는 아직 안 셌다고 했어. 사실 4의 증인은 아빠였어. TestFlight로 3번 빌드를 폰에 깔았거든. 4번과 6번은 사실 1부터 3까지 기록돼 있고, 설치는 확인되지 않았어. 나머지 빌드는 git이 업로드까지만 증명해. 딱 이런 보고를 쓰는 법은 coop-quest의 reporting-back 트랙에 있어.
보이는 버전 하나
cwk-product.json엔 사실이 세 개 들어 있어. 스키마 버전 1, 제품 id cwkSizzle, 버전 1.0.0. 쓰는 쪽은 Firelink 하나뿐이야. 2026-09-25에 1.0.0을 적은 릴리스 커밋에 v1.0.0 태그가 붙어 있어. 모든 화면이 이 파일을 읽어.
- 엔진의
/api/health는 킷의product_version을 거쳐서 이 값을 내줘. 스키마, 제품 id, SemVer 모양을 확인하고, 파일이 틀렸으면 경고 한 번과 함께0.0.0으로 물러나. - 웹 Settings 페이지는 그 헬스 응답을 받아
Sizzle v1.0.0을 보여줘. - 설정 파일에
mtf_marketing_version=cwk-product가 있어서, 파이프라인은plutil로version을 읽어 아카이브에MARKETING_VERSION으로 넘겨.project.yml에도 1.0.0이 있는데 로컬 빌드용일 뿐이고, Firelink의 릴리스 카드가 둘을 같게 맞춰. - 폰의 Settings는
CFBundleShortVersionString에서 Version을,CFBundleVersion에서 Build를 보여줘. 엔진을 확인할 때는 엔진이 알려준 버전도 같이 띄워.
다른 숫자들은 자기 영역에 머물러. frontend/package.json의 0.0.0은 패키지 버전이고, 빌드 번호는 파이프라인 빌드를 세고, RUBRIC_VERSION 2는 읽기 스키마의 버전이야. 어느 것도 제품 버전과 다투지 않아.