appsok/agent-task/m-jenkins-thin-artifact-ci/PLAN-cloud-G07.md
toki e6feb735aa feat: jenkins thin artifact CI 마일스톤 및 검증 스크립트 업데이트
- jenkins-thin-artifact-ci 마일스톤 계획 수립
- macos certified build 스크립트 및 테스트 업데이트
- docs/macos-certified-build.md 문서 갱신
- ci secrets setup 스크립트 추가
2026-06-16 22:36:29 +09:00

15 KiB

Jenkins Thin Job Plan

이 파일을 읽는 구현 에이전트에게

이 계획은 CODE_REVIEW-cloud-G07.md의 구현 에이전트 소유 섹션을 채우는 것까지가 필수 작업이다. 구현, 검증, 실제 출력 기록을 끝낸 뒤 active 파일을 유지하고 리뷰 준비 상태로 보고한다. 최종 판정, log rename, complete.log 작성, archive 이동은 code-review 스킬 전용이다.

구현 중 사용자만 결정할 수 있는 외부 환경 전제, secret 준비, scope 충돌이 발견되면 사용자에게 직접 묻지 말고 CODE_REVIEW-cloud-G07.md사용자 리뷰 요청 섹션에 정확한 결정, 근거, 실행한 명령과 출력, 재개 조건을 기록하고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 evidence 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 계획 대비 변경 사항에 기록한다.

배경

현재 마일스톤은 AppSok 인증 ZIP을 Jenkins thin job으로 빌드하고 archived artifact로 배포하는 것이다. artifact-copy는 즉시 처리되어 ZIP 옆 .sha256 파일을 남기지만, Jenkins job 생성/갱신은 외부 서비스 상태를 바꾸므로 계획 루프로 처리한다. 이 계획은 appsok-macos-certified job을 생성 또는 갱신하고, macbook-ultra node, main SCM, 빌드 스크립트 호출, archive, 최근 10개 보관 정책을 한 번에 적용한다.

사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 사용자 리뷰 요청 섹션에 기록한다. 구현 에이전트는 채팅으로 직접 질문하거나 선택지를 제시하거나 request_user_input을 호출하지 않는다. code-review가 요청의 타당성을 검증하고 필요할 때만 USER_REVIEW.md를 작성한다.

Roadmap Targets

  • Milestone: agent-roadmap/phase/security-distribution/milestones/jenkins-thin-artifact-ci.md
  • Task ids:
    • job-config: macbook-ultra node, AppSok SCM, main branch, shell build step을 포함한 Jenkins job config를 만든다.
    • script-call: job build step이 ./scripts/build-certified-macos.sh를 호출하고 secret 원문을 job config나 repository에 남기지 않는다.
    • archive: Jenkins archived artifact 설정으로 ZIP과 checksum을 보관한다.
    • retention: build log와 artifact 보관 정책을 최근 10개 build 기준에 맞춘다.
  • Completion mode: check-on-pass

분석 결과

읽은 파일

  • AGENTS.md
  • agent-ops/rules/project/rules.md
  • agent-ops/rules/common/rules-roadmap.md
  • agent-ops/skills/common/router.md
  • agent-ops/skills/common/update-roadmap/SKILL.md
  • agent-ops/skills/common/plan/SKILL.md
  • agent-ops/skills/common/_templates/implementation-user-review-request-section.md
  • agent-roadmap/current.md
  • agent-roadmap/ROADMAP.md
  • agent-roadmap/phase/security-distribution/PHASE.md
  • agent-roadmap/phase/security-distribution/milestones/jenkins-thin-artifact-ci.md
  • agent-ops/rules/project/domain/artifact-flow/rules.md
  • agent-ops/rules/project/domain/macos-platform/rules.md
  • agent-test/local/rules.md
  • agent-test/local/artifact-flow-smoke.md
  • agent-test/local/macos-platform-smoke.md
  • scripts/build-certified-macos.sh
  • scripts/setup-appsok-ci-secrets.sh
  • docs/macos-certified-build.md
  • test/certified_macos_scripts_test.dart
  • .gitignore
  • pubspec.yaml

테스트 환경 규칙

  • 선택 env: local.
  • agent-test/local/rules.md를 읽었다. 기본 완료 evidence는 remote Mac runner toki@toki-labs.com$HOME/docker/services/code-server/data/volume/workspace/appsok checkout 기준이다.
  • 매칭 profile: agent-test/local/artifact-flow-smoke.md, agent-test/local/macos-platform-smoke.md.
  • 적용 명령: flutter analyze, flutter test, macOS platform 변경 시 remote runner의 flutter build macos.
  • Jenkins 실제 조회/다운로드와 job API 검증은 secret 원문 없이 SOPS payload와 Jenkins API 상태 코드로 확인한다.
  • 현재 checkout에서 이미 실행한 작은 작업 검증: flutter test test/certified_macos_scripts_test.dart 통과, git diff --check 통과.

테스트 커버리지 공백

  • 새 Jenkins upsert script의 XML 구조, secret 비노출, endpoint 호출 순서는 test/certified_macos_scripts_test.dart에 문자열/순서 테스트를 추가한다.
  • 실제 Jenkins job 생성/갱신은 unit test로 대체할 수 없다. --dry-run XML 생성 검증과 remote runner의 Jenkins API whoAmI, crumb, job config 조회로 보완한다.
  • 실제 인증 ZIP 빌드는 시간이 길고 notarization 외부 상태에 의존한다. 이 계획의 최종 검증은 job config 적용과 Jenkins config 조회까지로 두고, 실제 build 실행은 access Epic의 smoke Task에서 다룬다.

심볼 참조

  • renamed/removed symbol: none.
  • 새 파일 후보: scripts/upsert-jenkins-certified-job.sh.

분할 판단

  • split policy를 먼저 평가했다.
  • 단일 계획을 선택한다. job-config, script-call, archive, retention은 모두 하나의 Jenkins freestyle job XML에 들어가며, 일부만 적용하면 job이 의도와 다르게 동작한다.
  • ownership boundary는 scripts/**, docs/**, Jenkins API apply 한 곳으로 묶인다.
  • 검증도 동일한 dry-run XML, unit test, Jenkins config 조회로 판단 가능하다.
  • artifact-copy는 이미 작은 작업으로 즉시 처리되어 이 계획의 Roadmap Targets에서 제외했다.

범위 결정 근거

  • access Epic의 trigger, download-url, auth-policy, smoke는 이 계획에 포함하지 않는다. 사용자가 요청한 범위가 job Epic이기 때문이다.
  • Jenkins plugin, Auth Broker, OTO runner, 중앙 감사 서버는 마일스톤 범위 제외다.
  • Jenkins API token, Apple credential, keychain password, private endpoint 원문은 tracked 파일, task log, 최종 응답에 기록하지 않는다.
  • SCM credential id는 새로 만들지 않는다. job config는 repository remote.origin.url을 사용하고 credential이 필요해 실패하면 구현 에이전트가 사용자 리뷰 요청으로 멈춘다.

빌드 등급

  • cloud-G07: 외부 Jenkins 상태 변경, shell/API orchestration, secret redaction, terminal verification이 핵심이다.

구현 체크리스트

  • [JENKINS-1] Jenkins job upsert script를 추가하고 macbook-ultra, main, ./scripts/build-certified-macos.sh, ZIP/sha256 archive, 최근 10개 보관 정책을 dry-run XML로 검증한다.
  • [JENKINS-2] 문서와 테스트를 갱신하고 remote runner에서 Jenkins API apply 및 config 조회 evidence를 남긴다.
  • CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

[JENKINS-1] Jenkins Job Upsert Script

문제

현재 scripts/build-certified-macos.sh:42는 SOPS secret을 읽고 인증 ZIP을 만들지만, Jenkins job을 생성하거나 갱신하는 entrypoint가 없다. 마일스톤의 job-config, script-call, archive, retention Task를 닫으려면 job XML을 repeatable하게 생성하고 Jenkins API로 upsert할 수 있어야 한다.

Before:

# scripts/build-certified-macos.sh:42
read_secret() {
  local key="$1"
  sops -d "$SECRET_FILE" | python3 -c "import json, sys; print(json.load(sys.stdin)['$key'])"
}

해결 방법

새 파일 scripts/upsert-jenkins-certified-job.sh를 만든다. 기존 secret 패턴을 재사용하되 jenkins_url, jenkins_username, jenkins_api_token만 읽고 원문을 출력하지 않는다. 기본은 --dry-run으로 XML만 stdout에 쓰고, --apply를 명시해야 Jenkins createItem 또는 config.xml 업데이트를 수행한다.

핵심 XML은 Jenkins freestyle project로 만든다.

<project>
  <actions/>
  <description>AppSok certified macOS artifact thin launcher.</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.plugins.git.GitSCM" plugin="git">
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/main</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>${SCM_URL}</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
  </scm>
  <assignedNode>macbook-ultra</assignedNode>
  <canRoam>false</canRoam>
  <logRotator class="hudson.tasks.LogRotator">
    <daysToKeep>-1</daysToKeep>
    <numToKeep>10</numToKeep>
    <artifactDaysToKeep>-1</artifactDaysToKeep>
    <artifactNumToKeep>10</artifactNumToKeep>
  </logRotator>
  <builders>
    <hudson.tasks.Shell>
      <command>set -euo pipefail
./scripts/build-certified-macos.sh</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers>
    <hudson.tasks.ArtifactArchiver>
      <artifacts>build/macos/Build/Products/Release/AppSok-certified.zip,build/macos/Build/Products/Release/AppSok-certified.zip.sha256</artifacts>
      <allowEmptyArchive>false</allowEmptyArchive>
      <onlyIfSuccessful>true</onlyIfSuccessful>
    </hudson.tasks.ArtifactArchiver>
  </publishers>
  <buildWrappers/>
</project>

Jenkins 호출은 crumb issuer를 먼저 확인한다. 대상 job appsok-macos-certified가 없으면 POST /createItem?name=appsok-macos-certified, 있으면 POST /job/appsok-macos-certified/config.xml을 사용한다. HTTP 200/201/302를 성공으로 보고, 실패 시 status만 출력한다.

수정 파일 및 체크리스트

  • scripts/upsert-jenkins-certified-job.sh 추가.
  • chmod +x scripts/upsert-jenkins-certified-job.sh.
  • test/certified_macos_scripts_test.dart에 job XML/secret/API 호출 구조 테스트 추가.
  • docs/macos-certified-build.md에 Jenkins job upsert 사용법, dry-run/apply, archive 대상, secret 비노출 원칙 추가.

테스트 작성

  • test/certified_macos_scripts_test.dart
    • test name: jenkins job upsert configures thin certified job
    • assertion: script contains appsok-macos-certified, macbook-ultra, */main, ./scripts/build-certified-macos.sh, archive artifact paths, numToKeep>10, artifactNumToKeep>10.
    • test name: jenkins job upsert keeps credentials out of generated config
    • assertion: XML generation references SOPS only for API auth, no API token value is interpolated into config XML, script has --dry-run default and explicit --apply.

중간 검증

git diff --check -- scripts/upsert-jenkins-certified-job.sh test/certified_macos_scripts_test.dart docs/macos-certified-build.md
flutter test test/certified_macos_scripts_test.dart

Expected: no whitespace errors and all tests pass.

[JENKINS-2] Jenkins Apply And Evidence

문제

Jenkins 접근은 비파괴 preflight에서 확인됐다: whoAmI=200 authenticated=true, crumb=200, root API 200, appsok-macos-certified job 404, newJob page 200, macbook-ultra node 200 offline=false, git plugin present. 그러나 실제 job config는 아직 Jenkins에 없다.

Before:

agent-roadmap/phase/security-distribution/milestones/jenkins-thin-artifact-ci.md:43
- [ ] [job-config] `macbook-ultra` node, AppSok SCM, `main` branch, shell build step을 포함한 Jenkins job config를 만든다.

해결 방법

remote Mac runner에서 --dry-run으로 XML을 생성해 non-secret structure를 확인한 뒤 --apply를 실행한다. 적용 후 Jenkins API로 job/appsok-macos-certified/config.xmlapi/json을 조회해 node, branch, archive path, retention 값이 반영되었는지 확인한다. endpoint, username, token 원문은 기록하지 않는다.

수정 파일 및 체크리스트

  • remote runner에서 ./scripts/upsert-jenkins-certified-job.sh --dry-run 실행.
  • dry-run XML에서 secret/token-like value가 없는지 확인.
  • remote runner에서 ./scripts/upsert-jenkins-certified-job.sh --apply 실행.
  • Jenkins job API 조회로 appsok-macos-certified가 존재하는지 확인.
  • Jenkins config XML 조회로 macbook-ultra, */main, ./scripts/build-certified-macos.sh, artifact archive, retention 10 확인.

테스트 작성

  • 실제 Jenkins apply는 unit test가 아니라 smoke evidence다. unit test는 JENKINS-1에서 script 구조를 검증한다.

중간 검증

ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && export PATH="$HOME/SDK/flutter/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" && ./scripts/upsert-jenkins-certified-job.sh --dry-run >/tmp/appsok-macos-certified-config.xml && for marker in "appsok-macos-certified" "macbook-ultra" "*/main" "AppSok-certified.zip.sha256" "<numToKeep>10</numToKeep>" "<artifactNumToKeep>10</artifactNumToKeep>"; do grep -F "$marker" /tmp/appsok-macos-certified-config.xml >/dev/null && printf "%s=present\n" "$marker"; done'\'''

Expected: each required XML marker is present and no secret is printed.

수정 파일 요약

파일 항목
scripts/upsert-jenkins-certified-job.sh JENKINS-1, JENKINS-2
test/certified_macos_scripts_test.dart JENKINS-1
docs/macos-certified-build.md JENKINS-1
Jenkins job appsok-macos-certified JENKINS-2

최종 검증

git diff --check -- scripts/upsert-jenkins-certified-job.sh test/certified_macos_scripts_test.dart docs/macos-certified-build.md
flutter test test/certified_macos_scripts_test.dart
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && export PATH="$HOME/SDK/flutter/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" && ./scripts/upsert-jenkins-certified-job.sh --dry-run >/tmp/appsok-macos-certified-config.xml && for marker in "appsok-macos-certified" "macbook-ultra" "*/main" "AppSok-certified.zip.sha256" "<numToKeep>10</numToKeep>" "<artifactNumToKeep>10</artifactNumToKeep>"; do grep -F "$marker" /tmp/appsok-macos-certified-config.xml >/dev/null && printf "%s=present\n" "$marker"; done'\'''
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && export PATH="$HOME/SDK/flutter/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" && ./scripts/upsert-jenkins-certified-job.sh --apply'\'''

Expected: whitespace check passes, targeted Flutter test passes, dry-run XML markers are present, Jenkins apply succeeds without printing secret values.

모든 코드 변경 완료 후 반드시 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.