# Milestone: macOS 빌드 CI와 인증 빌드

## 위치

- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/workflow-integration/PHASE.md`

## 목표

Teams deep link와 설치 이력 작업으로 들어가기 전에, 원격 Mac runner에서 AppSok macOS 빌드가 재현 가능하게 생성되고 최종 배포 후보가 Developer ID signing, notarization, stapling까지 통과하는지 확인한다. 생성된 인증 빌드는 다음 업무 흐름 Milestone의 검증 출발점으로 재사용할 수 있어야 한다.

## 상태

[완료]

## 승격 조건

- 없음

## 구현 잠금

- 상태: 해제
- 결정 필요: 없음

## 범위

- remote Mac runner의 Flutter/Xcode/codesigning readiness 확인
- `flutter analyze`, `flutter test`, `flutter build macos`를 CI 후보 명령으로 정리
- macOS release `.app` bundle 생성 여부 확인
- Developer ID signing, notarization, stapling, Gatekeeper assessment 확인
- MDM 배포 정책과 조직 배포 자동화는 후속 배포 Milestone과 분리

## 기능

### Epic: [baseline] 원격 Mac 빌드 baseline

원격 Mac runner에서 현재 checkout을 실제로 빌드할 수 있는지 확인한다.

- [x] [remote-env] remote checkout, Flutter, Xcode, codesigning identity 상태를 확인한다.
- [x] [analyze-test] remote runner에서 `flutter analyze`와 `flutter test`를 통과시킨다.
- [x] [macos-build] remote runner에서 `flutter build macos`로 `Release/AppSok.app`을 생성한다.
- [x] [bundle-check] 빌드 산출물의 앱 이름, bundle id, codesign 상태를 확인한다.

### Epic: [ci] CI 게이트 정의

반복 가능한 CI 검증으로 만들기 위해 명령, runner, 산출물, 실패 기준을 문서화한다.

- [x] [ci-commands] CI에서 실행할 명령 순서와 기대 출력을 정리한다.
- [x] [runner-contract] remote Mac runner 또는 self-hosted runner 필수 조건을 정리한다.
- [x] [artifact-output] `AppSok.app` 산출물 경로, 보존 방식, build evidence 위치를 정한다.
- [x] [signing-boundary] adhoc build, Developer ID signing, notarization의 책임 경계를 기록한다.

### Epic: [certified] 최종 인증 빌드

최종 배포 후보를 macOS Gatekeeper 기준으로 검증 가능한 상태로 만든다.

- [x] [keychain-access] 비대화식 CI 세션에서 Developer ID private key 접근이 가능한지 확인한다.
- [x] [developer-id-sign] `AppSok.app`과 nested framework/runtime 파일을 Developer ID로 서명한다.
- [x] [notary-submit] `notarytool submit --wait`로 Apple notarization을 통과시킨다.
- [x] [staple] notarization ticket을 산출물에 staple한다.
- [x] [gatekeeper] `spctl --assess --verbose`로 배포 후보 실행 가능성을 확인한다.
- [x] [certified-artifact] 인증된 산출물 경로와 checksum을 기록한다.

## 완료 리뷰

- 상태: 승인됨
- 요청일: 2026-06-14
- 완료 근거:
  - 원격 Mac runner에서 Flutter/Xcode/codesigning readiness, `flutter analyze`, `flutter test`, `flutter build macos`를 통과했다.
  - Developer ID signing, Apple notarization, stapling, Gatekeeper assessment, 최종 ZIP checksum 기록을 완료했다.
  - SOPS secret setup과 인증 빌드 반복 실행 절차를 스크립트, 문서, 테스트로 남겼다.
- 리뷰 필요:
  - [x] 사용자가 완료 결과를 확인했다
  - [x] archive 이동을 승인했다
- 리뷰 코멘트: 전체화면 전용 검증은 제품 목표가 아니므로 완료 조건에서 제외한다. 일반 창 기반 업무 흐름 확인은 각 후속 기능 Milestone의 smoke에서 필요할 때 다룬다.

## 범위 제외

- Teams deep link 구현
- 전체화면 전용 UX 또는 전체화면 모드 검증
- 실제 Jenkins credential 또는 private endpoint 원문 기록
- 조직 배포 채널과 MDM 자동화 정책 확정

## 작업 컨텍스트

- 관련 경로: `macos/`, `pubspec.yaml`, CI 설정 후보 파일
- 관련 문서: `docs/macos-certified-build.md`, `agent-test/local/macos-remote-build-smoke.md`
- 관련 테스트: `test/certified_macos_scripts_test.dart`
- 표준선(선택): CI baseline은 `flutter pub get`, `flutter analyze`, `flutter test`, `flutter build macos` 순서로 둔다.
- 표준선(선택): 인증서 원문, private key, password, Apple account secret은 채팅/roadmap/tracked 파일에 기록하지 않고 runner Keychain 또는 CI secret으로만 다룬다.
- 표준선(선택): adhoc build, Developer ID signing, notarization은 서로 다른 검증 단계로 기록하고, 인증 빌드는 `scripts/build-certified-macos.sh`로 반복한다.
- 검증 메모:
  - 2026-06-14 remote checkout: `main...origin/main`, clean.
  - `flutter --version --machine`: Flutter `3.44.0`, Dart `3.12.0`.
  - `xcodebuild -version`: Xcode `26.0.1`, Build `17A400`.
  - `security find-identity -v -p codesigning`: valid identity 3개 확인.
  - `flutter pub get` PASS.
  - `flutter analyze` PASS: `No issues found!`.
  - `flutter test` PASS: `215: All tests passed!`.
  - `flutter build macos` PASS: `Built build/macos/Build/Products/Release/AppSok.app (60.2MB)`.
  - Bundle check: 앱 이름 `AppSok`, bundle id `com.tokilabs.appsok`, codesign `Signature=adhoc`, universal `x86_64 arm64`.
  - 2026-06-14 signing readiness check: `notarytool` version `1.1.0 (39)` 확인.
  - 2026-06-14 signing blocker: SSH 비대화식 세션에서 `security show-keychain-info login.keychain-db`가 `User interaction is not allowed`로 실패했고, notary profile 조회는 `keychainLocked`에서 중단됐다.
  - 2026-06-14 signing blocker: Developer ID로 임시 executable 서명 probe와 `AppSok.app` 서명 시도가 `errSecInternalComponent`로 실패했다. 인증서 원문 문제가 아니라 runner Keychain/private key 접근 준비가 필요한 상태다.
  - 2026-06-14 secret manager check: remote runner에는 `sops 3.13.1`과 `age`가 설치되어 있다. AppSok repo 안에는 아직 `.sops.yaml` 또는 AppSok용 encrypted secret 파일이 없고, SSH 세션에 `SOPS_AGE_KEY_FILE`도 설정되어 있지 않아 현재 CI secret retrieval 경로로 바로 사용할 수는 없다.
  - 2026-06-14 secret manager setup: `scripts/setup-appsok-ci-secrets.sh`로 SOPS encrypted secret file `secrets/appsok.ci.sops.json`, age key `$HOME/.config/sops/age/appsok-ci-key.txt`, notary profile `appsok-notary`를 준비했다.
  - 2026-06-14 signing readiness resolved: SOPS 복호화, keychain unlock, `appsok-notary` profile 접근, Developer ID signing probe가 통과했다.
  - 2026-06-14 certified build first attempt: `AppSok.app` 본체는 Developer ID 서명됐으나 `Contents/Resources/adb-runtime/adb`가 resource executable로 남아 notarization `Invalid` 처리됐다. submission id: `57457c8f-9b0b-4e6f-a387-760ce98279c0`.
  - 2026-06-14 certified build success: bundled `adb`를 먼저 Developer ID + hardened runtime + timestamp로 서명한 뒤 `AppSok.app`을 서명했고 notarization `Accepted`, stapler `worked`, `spctl` `source=Notarized Developer ID`를 확인했다. submission id: `eda0f701-78f4-45b5-91ce-58e49b0b7000`.
  - 2026-06-14 certified artifact: `build/macos/Build/Products/Release/AppSok-certified.zip`, size `23M`, sha256 `1c8e89816d4cbf92bcf7c70aa40a4e36ae29efeaeb3cc64ce391c22bc6deef2a`.
  - 반복 실행 명령: `scripts/build-certified-macos.sh`.
  - 2026-06-14 docs/test: `docs/macos-certified-build.md`와 `test/certified_macos_scripts_test.dart`를 추가해 secret setup, certified build command, bundled `adb` signing 순서, notarization/stapling/Gatekeeper 기준을 남겼다.
  - 2026-06-14 verification: `flutter test test/certified_macos_scripts_test.dart` PASS, `flutter analyze` PASS, `git diff --check` PASS.
  - 2026-06-14 code-level closure review: `APPSOK_CI_SECRET_FILE` override 시 `.sops.yaml` creation rule이 기본 경로에 고정되는 작은 이슈를 수정했고, `bash -n scripts/setup-appsok-ci-secrets.sh scripts/build-certified-macos.sh`, `flutter test`, `flutter analyze`, `git diff --check` PASS.
- 선행 작업: ADB 콘솔과 logcat
- 후속 작업: Teams deep link 설치 진입
- 확인 필요: 사용자 완료 확인과 archive 이동 승인.
