LazyPad 개발기 4: TestFlight와 Mac Companion 배포 후보 검증
LazyPad 3편에서는 손을 뗐다가 다시 올리는 첫 입력 문제를 build 0.1.0 (10) physical smoke 기준선까지 좁힌 과정을 정리했습니다.
입력감 기준선이 생기면 다음 문제는 코드가 아니었습니다. 외부 사용자가 실제로 설치하고 테스트할 수 있는 경로를 만들어야 했습니다.
LazyPad는 iPhone 앱 하나로 끝나는 제품이 아닙니다. Mac Companion이 필요하고, Mac Companion은 Accessibility permission이 필요합니다. iOS 쪽은 TestFlight 심사와 tester invite 흐름을 지나야 합니다. 지원 주소와 privacy/support page도 필요합니다.
이번 글은 이 코드 밖의 gate를 정리합니다. 핵심은 "배포 완료"가 아니라 "배포 후보를 검증할 수 있는 상태"를 정확하게 말하는 것입니다.
배포 용어를 알아야 코드 밖 gate가 보입니다
이번 글은 코드보다 배포 절차가 많습니다. 그래서 Apple 생태계와 macOS 보안 절차에서 쓰는 단어를 먼저 정리합니다.
| 용어 | 쉬운 설명 | LazyPad에서의 의미 |
|---|---|---|
| TestFlight | App Store 정식 출시 전 iOS 앱을 테스터에게 전달하는 Apple 테스트 배포 경로입니다 | 외부 사용자가 iPhone 앱을 설치할 수 있는 공식 beta 경로입니다 |
| App Store Connect | iOS 앱 등록, build 업로드, TestFlight 심사 정보를 관리하는 Apple 웹 서비스입니다 | build 0.1.0 (11)을 외부 TestFlight 심사에 올린 장소입니다 |
| Apple Beta App Review | 외부 TestFlight 배포 전에 Apple이 beta 앱을 검토하는 절차입니다 | 외부 초대 전에 남아 있는 gate입니다 |
| Tester invite | 심사 뒤 외부 tester에게 설치 초대를 보내는 단계입니다 | 아직 완료됐다고 말하지 않는 단계입니다 |
| Mac Companion | Mac에서 실제 입력을 실행하는 별도 앱입니다 | iPhone 앱과 함께 있어야 LazyPad가 동작합니다 |
.app bundle | macOS 앱을 하나의 앱처럼 보이게 묶은 폴더 구조입니다 | 외부 사용자가 설치하고 실행하는 Mac 배포물 형태입니다 |
| Developer ID | App Store 밖에서 배포하는 Mac 앱에 개발자 신원을 붙이는 Apple 서명 체계입니다 | Mac Companion을 외부 배포물로 설명하려면 필요했습니다 |
| Notarization | Apple이 Mac 앱을 검사하고 배포 가능 상태로 확인해주는 절차입니다 | 사용자가 앱을 열 때 macOS 보안 경고를 줄이기 위해 필요했습니다 |
| Stapling | notarization ticket을 앱에 붙여두는 절차입니다 | 사용자의 Mac이 오프라인이거나 확인이 늦어도 앱 상태를 증명하기 쉽게 합니다 |
| Gatekeeper | macOS가 앱 실행 전에 안전성을 확인하는 보안 기능입니다 | 외부 zip을 풀어도 앱이 열릴 수 있는지 확인하는 gate였습니다 |
| Artifact | 검증 대상이 되는 실제 배포 파일입니다 | 코드가 아니라 사용자가 받는 .app 또는 .zip을 뜻합니다 |
iOS 앱 하나로는 외부 테스트가 시작되지 않았습니다
LazyPad는 paired Mac에 입력을 보내는 앱입니다. 그래서 iPhone 앱만 준비되면 끝나는 구조가 아니었습니다.
외부 사용자가 테스트하려면 다음 네 가지가 함께 필요했습니다.
| 구성 | 필요한 이유 |
|---|---|
| iOS TestFlight build | iPhone 앱을 외부 tester에게 전달하는 경로입니다 |
Mac Companion .app | Mac에서 pointer, click, scroll, drag event를 실행하는 실행면입니다 |
| Accessibility permission 안내 | macOS 입력 실행에 필요한 권한을 사용자가 이해해야 합니다 |
| support/privacy 표면 | 설치 전후 질문과 local-first 경계를 설명해야 합니다 |
이 네 가지 중 하나라도 비어 있으면 "외부 사용자가 테스트할 수 있다"고 말하기 어렵습니다. 그래서 기능 구현 이후에는 release gate를 별도 작업으로 분리했습니다.
build 10과 build 11의 역할을 분리했습니다
LazyPad 문서에는 build 0.1.0 (10)과 build 0.1.0 (11)이 함께 등장합니다.
두 build는 같은 의미가 아닙니다. 이 구분을 먼저 두어야 현재 상태를 과장하지 않을 수 있습니다.
| build | 역할 | 말할 수 있는 범위 |
|---|---|---|
0.1.0 (10) | 입력감과 Mac Companion 배포물 검증 기준선 | lift/re-touch, fast movement feel, drag regression, Mac signing/notarization/zip 검증 기준으로 설명할 수 있습니다 |
0.1.0 (11) | App Store Connect 외부 TestFlight 심사 제출 build | 외부 그룹에 올리고 Apple Beta App Review에 제출한 상태로 설명할 수 있습니다 |
build 10은 제품 동작과 Mac artifact를 설명하는 기준선입니다. build 11은 외부 TestFlight 흐름에 올린 build입니다.
따라서 "외부 테스트 완료"라고 쓰지 않습니다. 현재 안전한 표현은 외부 TestFlight 심사 제출과 심사 대기입니다.
Mac Companion은 별도의 배포물로 검증했습니다
Mac Companion은 iPhone 앱의 보조 파일이 아닙니다. Mac에서 실제 입력을 실행하는 별도 앱입니다.
그래서 외부 사용자가 설치할 수 있는 배포물로 만들려면 macOS 보안 gate를 통과해야 했습니다.
초보자 기준으로 보면 흐름은 다음과 같습니다.
- Mac 실행 파일을 release build로 만듭니다.
LazyPad Companion.appbundle로 감쌉니다.- Developer ID로 app을 signing합니다.
- signed app을 zip으로 묶어 Apple notarization에 제출합니다.
- Apple이 accepted를 반환하면 ticket을 app에 stapling합니다.
- stapled app을
codesign,stapler validate, Gatekeeper로 다시 확인합니다. - 최종 zip을 다시 만들고, unzip한 app도 같은 검증을 통과하는지 확인합니다.
이 과정이 필요한 이유는 단순합니다. 배포 직전에 통과한 app이, zip으로 전달된 뒤에도 같은 상태로 열려야 하기 때문입니다.
각 단계를 조금 더 풀면 다음과 같습니다.
| 단계 | 설명 |
|---|---|
| Release build | 개발 중 확인용 build가 아니라 외부 전달을 전제로 만든 build입니다 |
| App bundle | 실행 파일, 아이콘, 권한 정보 등을 macOS 앱 형태로 묶은 구조입니다 |
| Signing | "이 앱은 이 개발자가 만들었습니다"라는 암호화된 서명을 붙이는 절차입니다 |
| Notarization | Apple에 제출해 악성 코드 검사와 배포 가능 확인을 받는 절차입니다 |
| Stapling | notarization 결과 ticket을 앱에 붙이는 절차입니다 |
codesign verification | 앱 서명이 깨지지 않았는지 명령으로 확인하는 절차입니다 |
stapler validate | notarization ticket이 앱에 제대로 붙었는지 확인하는 절차입니다 |
| Gatekeeper assessment | macOS가 이 앱을 실행 가능한 배포물로 받아들이는지 확인하는 절차입니다 |
| Zip/unzip verification | 압축해서 전달한 뒤 다시 풀어도 서명과 ticket이 유지되는지 확인하는 절차입니다 |
build 10 계열 Mac Companion은 다음 검증을 통과했습니다.
| gate | 상태 |
|---|---|
| release build | 통과 |
| Developer ID signing | 통과 |
| Apple notarization | accepted |
| ticket stapling | 통과 |
| strict codesign verification | 통과 |
| stapler validation | 통과 |
| Gatekeeper assessment | Notarized Developer ID로 accepted |
| final zip recreation | 통과 |
| unzip verification | strict codesign, stapler validation, Gatekeeper assessment 통과 |
이 결과로 Mac Companion artifact gate는 닫혔습니다. 다만 iOS 외부 TestFlight 심사와 tester invitation은 별도 gate로 남아 있었습니다.
TestFlight는 upload가 아니라 심사 흐름이었습니다
처음에는 TestFlight를 build upload 중심으로 생각하기 쉽습니다. 실제 외부 테스트에서는 그보다 많은 표면이 필요했습니다.
App Store Connect에는 test information이 필요했습니다. Beta App Description, Feedback Email, Review Contact, Review Notes, What to Test를 채워야 했습니다.
이 항목들은 단순 양식이 아니었습니다. Apple reviewer와 외부 tester가 앱의 목적, 테스트 범위, 연락 경로를 이해하기 위한 설명이었습니다.
| 항목 | 쉬운 설명 |
|---|---|
| Beta App Description | beta 앱이 무엇을 하는지 설명하는 문장입니다 |
| Feedback Email | tester가 문제를 보낼 수 있는 공개 연락 주소입니다 |
| Review Contact | Apple review 과정에서 연락할 수 있는 정보입니다 |
| Review Notes | reviewer가 앱을 어떻게 확인해야 하는지 적는 안내입니다 |
| What to Test | 이번 beta에서 tester가 무엇을 확인하면 되는지 적는 문장입니다 |
| External group | 외부 tester를 묶는 TestFlight 그룹입니다 |
여기서 중요한 판단은 공개 지원 주소였습니다. 개인 이메일 대신 support@lazypad.app을 만들고, 테스트 메일 송수신까지 확인했습니다. 이 주소를 feedback/review contact용 공개 지원 경로로 사용했습니다.
외부 TestFlight 흐름은 다음 순서로 정리했습니다.
| 단계 | 상태 |
|---|---|
| build number bump | build 0.1.0 (11)로 올렸습니다 |
| Xcode Organizer upload | 업로드 완료 화면을 확인했습니다 |
| support email | support@lazypad.app 송수신을 확인했습니다 |
| test information | Beta App Description, Feedback Email, Review Notes, What to Test를 정리했습니다 |
| external group | External Beta 0.1 그룹을 만들었습니다 |
| review submission | build 0.1.0 (11)을 Apple Beta App Review에 제출했습니다 |
| tester invite | Apple Beta App Review 이후로 남아 있습니다 |
이 상태는 배포 완료가 아닙니다. 외부 사용자가 설치하기 전 단계입니다. 그래서 글에서도 "심사 제출"과 "초대 완료"를 분리해서 말합니다.
support와 privacy page도 제품의 일부였습니다
외부 사용자는 code diff를 보지 않습니다. 사용자는 설치 전에 무엇을 믿어야 하는지 봅니다.
LazyPad에서는 이 설명이 특히 중요했습니다. Mac Companion은 Accessibility permission을 요구하고, iPhone과 Mac은 같은 local network에서 연결됩니다. 이 설명이 부족하면 기능이 맞아도 설치 단계에서 신뢰를 잃을 수 있습니다.
그래서 lazypad.app 정적 초안을 만들었습니다.
| 페이지 | 역할 |
|---|---|
/ | LazyPad가 무엇을 하는지와 현재 beta candidate 상태를 설명합니다 |
/support/ | Mac Companion 설치, Accessibility permission, pairing/test flow를 설명합니다 |
/privacy/ | local-first, no cloud relay, raw evidence boundary를 설명합니다 |
Local-first는 입력이 기본적으로 가까운 iPhone과 Mac 사이에서 처리된다는 뜻입니다. No cloud relay는 runtime input control을 cloud server를 거쳐 보내지 않는다는 뜻입니다.
Raw evidence boundary는 개발 중 확인한 원본 로그, 계정 값, device identifier 같은 민감 정보를 공개 글이나 저장소에 그대로 남기지 않는 원칙입니다. LazyPad는 입력 앱이기 때문에 기능 설명만큼 이 경계도 중요했습니다.
문구는 보수적으로 잡았습니다. External TestFlight pending, Mac Companion artifact verified, private beta candidate처럼 현재 상태를 넘지 않는 표현만 사용했습니다.
말하지 않는 표현을 먼저 정했습니다
배포 직전 단계에서는 무엇을 했는지보다 무엇을 아직 말하지 않는지가 중요했습니다.
| 사용하지 않는 표현 | 이유 |
|---|---|
| 정식 출시 | App Store release 상태가 아닙니다 |
| 외부 테스트 완료 | Apple Beta App Review와 tester invitation이 남아 있습니다 |
| public beta opened | 첫 external group invite가 완료되지 않았습니다 |
| Magic Trackpad replacement | v0.1 scope가 아닙니다 |
| Bluetooth trackpad | 현재 구조가 Wi-Fi/Bonjour + Mac Companion입니다 |
| remote desktop | 화면 제어나 원격 데스크톱 제품이 아닙니다 |
이 경계는 글쓰기만의 문제가 아니었습니다. 제품 문장, TestFlight review note, support page, privacy page가 모두 같은 경계를 지켜야 했습니다.
AI는 checklist를 만들었고, gate는 직접 통과시켰습니다
AI는 release gate를 정리하는 데 도움이 됐습니다.
Mac Companion packaging 순서를 설명하고, TestFlight packet 초안을 만들고, forbidden claim을 찾아내고, support/privacy 문구를 보수적으로 다듬는 데 사용할 수 있었습니다.
하지만 실제 gate는 직접 통과해야 했습니다.
| AI로 빠르게 한 일 | 직접 확인한 일 |
|---|---|
| Mac packaging checklist 정리 | signing, notarization, stapling, Gatekeeper, zip/unzip verification 결과 |
| TestFlight review note 초안 | App Store Connect에 필요한 정보 입력과 제출 상태 |
| support/privacy 문구 정리 | support@lazypad.app 송수신 확인 |
| claim boundary 점검 | 완료, 대기, 금지 표현 분리 |
이 과정에서 AI 활용 능력은 속도만의 문제가 아니었습니다. AI가 만든 checklist가 실제 gate와 맞는지 확인하는 능력이 더 중요했습니다.
이번 시즌의 결론은 배포 완료가 아니라 후보 검증입니다
LazyPad 1차 개발기 시즌은 여기서 닫을 수 있습니다.
1편에서는 처음 다루는 Swift/Xcode와 AI-assisted learning loop로 iPhone-Mac 앱을 만든 전체 흐름을 정리했습니다. 2편에서는 입력감 알고리즘과 핵심 용어를 풀었습니다. 3편에서는 cursor reacquire 문제를 build 10 입력감 기준선으로 좁힌 과정을 정리했습니다. 4편에서는 외부 사용자가 설치할 수 있는 후보가 되기 위해 필요한 TestFlight, Mac Companion, support/privacy gate를 정리했습니다.
현재 결론은 명확합니다.
| 영역 | 현재 상태 |
|---|---|
| 입력감 기준선 | build 0.1.0 (10) physical smoke 기준으로 설명 가능 |
| Mac Companion | signed/notarized/stapled/Gatekeeper/zip-unzip verified artifact로 설명 가능 |
| iOS TestFlight | build 0.1.0 (11) external group + Apple Beta App Review 제출 상태 |
| support email | support@lazypad.app 송수신 확인 |
| 웹 표면 | lazypad.app landing/support/privacy 초안 준비 |
| 외부 tester 운영 | Apple review와 invitation 이후 별도 기록 필요 |
다음 글은 실제 외부 tester가 설치한 뒤에 쓰겠습니다. 승인, 반려, 첫 feedback, 설치 실패, permission confusion 같은 운영 증거가 생기면 그때 다시 돌아오겠습니다.
이어 읽기
시리즈는 순서대로, 편집 추천은 맥락대로, 비슷한 주제는 태그 기준으로 정리합니다.
시리즈 전체
LazyPad 개발기: 아이폰을 맥 입력면으로 만들기4/4편- 1.LazyPad 개발기 1: 처음 쓰는 Swift와 Xcode로 iPhone-Mac 앱 배포 후보 만들기
- 2.LazyPad 개발기 2: 터치 입력감은 왜 배율 하나로 해결되지 않았나
- 3.LazyPad 개발기 3: 첫 재터치에서 드러난 커서 입력감 디버깅
- 4.LazyPad 개발기 4: TestFlight와 Mac Companion 배포 후보 검증
함께 읽으면 좋은 글
편집 추천비슷한 주제의 글
태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.
AI라는 다른 종 앞에서, 나는 나를 더 보여주기로 했다
AI를 다른 종처럼 느낀 두려움과 안도를 출발점으로, 흩어진 취향과 반복 자동화 작업을 AI에게 나를 더 잘 알 수 있는 지도로 건네며 나와 AI가 함께 특징화되는 과정을 쓴 개인 에세이.
AI와 파트너가 아니라 위임자로 일하기 위해 중장기계획을 만들었다
AI와의 관계를 파트너가 아니라 위임자로 재정의하고, 6개 repository 분석, 자동화와 harness, sprint sheet, second brain wiki, 매일 갱신되는 중장기계획을 통해 스스로 진화하는 AI Native 작업 시스템을 만들려는 의도를 정리한 글.
위임자가 되려면 Codex 완료 신호부터 받아야 했다
AI에게 일을 위임하려면 먼저 완료 신호와 보고 체계가 필요하다는 관점에서 Codex 작업 완료 Slack 알림을 P0로 올린 이유를 정리한 글.