Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/actions/setup-minimum-xcode-16/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Set up minimum Xcode 16 environment
description: Select Xcode 16.0 and install the pinned RootFS, XcodeGen, and iOS 18 Simulator runtime.

inputs:
install-simulator-runtime:
description: Install and verify the iOS 18.0 Simulator runtime during setup.
required: false
default: "true"

runs:
using: composite
steps:
- name: Configure public package submodule checkout
shell: bash
run: |
git config --global \
url."https://github.com/".insteadOf \
"ssh://git@ssh.github.com:443/"

- name: Verify minimum Apple toolchain
shell: bash
run: |
test "$(uname -m)" = "arm64"
test -d /Applications/Xcode_16.0.app/Contents/Developer
sudo xcode-select \
--switch /Applications/Xcode_16.0.app/Contents/Developer
test "$(xcodebuild -version | sed -n '1p')" = "Xcode 16.0"
test "$(xcrun --sdk iphoneos --show-sdk-version)" = "18.0"
test "$(xcrun --sdk iphonesimulator --show-sdk-version)" = "18.0"
xcodebuild -version
swift --version

- name: Download and verify pinned RootFS release archive
shell: bash
env:
ROOTFS_SHA256: "be0f3c133f78f28b023288459b33dc28fa253a6ef29f7123bc5f3892edf90ad4"
ROOTFS_BYTE_COUNT: "6581376"
ROOTFS_URL: "https://github.com/Lolendor/ish-arm64-pkg/releases/download/v0.3.3/fs.tar.gz"
run: |
archive="$RUNNER_TEMP/pocketroot-fs-v0.3.3.tar.gz"
curl --fail --location --retry 3 \
--output "$archive" \
"$ROOTFS_URL"
test "$(stat -f '%z' "$archive")" = "$ROOTFS_BYTE_COUNT"
echo "$ROOTFS_SHA256 $archive" | shasum -a 256 --check
echo "POCKETROOT_ROOTFS_ARCHIVE=$archive" >> "$GITHUB_ENV"

- name: Install XcodeGen
shell: bash
env:
XCODEGEN_VERSION: "2.46.0"
XCODEGEN_SHA256: "4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806"
run: |
archive="$RUNNER_TEMP/xcodegen.zip"
install_root="$RUNNER_TEMP/xcodegen-install"
curl --fail --location --retry 3 \
--output "$archive" \
"https://github.com/yonaskolb/XcodeGen/releases/download/$XCODEGEN_VERSION/xcodegen.zip"
echo "$XCODEGEN_SHA256 $archive" | shasum -a 256 --check
unzip -q "$archive" -d "$install_root"
echo "$install_root/xcodegen/bin" >> "$GITHUB_PATH"

- name: Install iOS 18.0 Simulator runtime
if: ${{ inputs.install-simulator-runtime == 'true' }}
shell: bash
run: |
xcrun simctl list >/dev/null
xcodebuild -downloadPlatform iOS
xcrun simctl list runtimes available
xcrun simctl list runtimes available \
| grep -F "iOS 18.0"
153 changes: 70 additions & 83 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,66 +291,25 @@ jobs:
"$POCKETROOT_SPDX_SCHEMA" \
"$evidence/SBOM.spdx.json"

minimum-xcode-16:
name: Minimum Xcode 16.0
minimum-xcode-16-runtime:
name: Minimum Xcode 16.0 / Native runtime
runs-on: macos-15
timeout-minutes: 90
timeout-minutes: 35

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Configure public package submodule checkout
run: |
git config --global \
url."https://github.com/".insteadOf \
"ssh://git@ssh.github.com:443/"

- name: Verify minimum Apple toolchain
run: |
test "$(uname -m)" = "arm64"
test -d /Applications/Xcode_16.0.app/Contents/Developer
sudo xcode-select \
--switch /Applications/Xcode_16.0.app/Contents/Developer
test "$(xcodebuild -version | sed -n '1p')" = "Xcode 16.0"
test "$(xcrun --sdk iphoneos --show-sdk-version)" = "18.0"
test "$(xcrun --sdk iphonesimulator --show-sdk-version)" = "18.0"
xcodebuild -version
swift --version

- name: Download and verify pinned RootFS release archive
env:
ROOTFS_SHA256: "be0f3c133f78f28b023288459b33dc28fa253a6ef29f7123bc5f3892edf90ad4"
ROOTFS_BYTE_COUNT: "6581376"
ROOTFS_URL: "https://github.com/Lolendor/ish-arm64-pkg/releases/download/v0.3.3/fs.tar.gz"
run: |
archive="$RUNNER_TEMP/pocketroot-fs-v0.3.3.tar.gz"
curl --fail --location --retry 3 \
--output "$archive" \
"$ROOTFS_URL"
test "$(stat -f '%z' "$archive")" = "$ROOTFS_BYTE_COUNT"
echo "$ROOTFS_SHA256 $archive" | shasum -a 256 --check
echo "POCKETROOT_ROOTFS_ARCHIVE=$archive" >> "$GITHUB_ENV"
- name: Set up minimum Xcode 16 environment
uses: ./.github/actions/setup-minimum-xcode-16
with:
install-simulator-runtime: "false"

- name: Validate real RootFS install with Xcode 16.0
run: |
swift test \
--filter testPinnedReleaseArchiveWhenProvidedByEnvironment

- name: Install XcodeGen
env:
XCODEGEN_VERSION: "2.46.0"
XCODEGEN_SHA256: "4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806"
run: |
archive="$RUNNER_TEMP/xcodegen.zip"
install_root="$RUNNER_TEMP/xcodegen-install"
curl --fail --location --retry 3 \
--output "$archive" \
"https://github.com/yonaskolb/XcodeGen/releases/download/$XCODEGEN_VERSION/xcodegen.zip"
echo "$XCODEGEN_SHA256 $archive" | shasum -a 256 --check
unzip -q "$archive" -d "$install_root"
echo "$install_root/xcodegen/bin" >> "$GITHUB_PATH"

- name: Install iOS 18.0 Simulator runtime
run: |
xcrun simctl list >/dev/null
Expand All @@ -370,52 +329,80 @@ jobs:
POCKETROOT_SMOKE_STABILITY_INTERVAL_MILLISECONDS: "250"
run: ./Scripts/run-runtime-smoke.sh

- name: Run public-SHA External Consumer UI acceptance
minimum-xcode-16-ui:
name: Minimum Xcode 16.0 / UI (${{ matrix.label }})
runs-on: macos-15
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- suite: external-consumer
label: External consumer
- suite: quick-start-iphone
label: Quick Start iPhone
- suite: quick-start-ipad
label: Quick Start iPad
- suite: host-iphone
label: Host App iPhone
- suite: host-ipad
label: Host App iPad

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up minimum Xcode 16 environment
uses: ./.github/actions/setup-minimum-xcode-16

- name: Run ${{ matrix.label }} UI smoke with Xcode 16.0
env:
POCKETROOT_UI_SUITE: ${{ matrix.suite }}
POCKETROOT_UI_FAILURE_ROOT: >-
${{ runner.temp }}/pocketroot-ui-artifacts/${{ matrix.suite }}
POCKETROOT_EXTERNAL_CONSUMER_REPOSITORY_URL: >-
${{ github.event.pull_request.head.repo.clone_url ||
format('{0}/{1}.git', github.server_url, github.repository) }}
POCKETROOT_EXTERNAL_CONSUMER_REVISION: >-
${{ github.event.pull_request.head.sha || github.sha }}
POCKETROOT_EXTERNAL_CONSUMER_REQUIRE_REMOTE: "1"
run: ./Scripts/run-external-consumer-ui-smoke.sh

- name: Run Quick Start two-entry UI smoke with Xcode 16.0
env:
POCKETROOT_UI_FAILURE_ARTIFACTS_DIR: >-
${{ runner.temp }}/pocketroot-ui-artifacts/quick-start-iphone
run: ./Scripts/run-quick-start-ui-smoke.sh

- name: Run Quick Start iPad UI smoke with Xcode 16.0
env:
POCKETROOT_QUICK_START_UI_DEVICE_TYPE: >-
com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation
POCKETROOT_QUICK_START_UI_DEVICE_NAME: >-
PocketRoot-Quick-Start-iPad-UI-Smoke
POCKETROOT_UI_FAILURE_ARTIFACTS_DIR: >-
${{ runner.temp }}/pocketroot-ui-artifacts/quick-start-ipad
run: ./Scripts/run-quick-start-ui-smoke.sh

- name: Run Host App PTY and Files UI smoke with Xcode 16.0
env:
POCKETROOT_HOST_UI_FAILURE_ARTIFACTS_DIR: >-
${{ runner.temp }}/pocketroot-ui-artifacts/host-iphone
run: ./Scripts/run-host-app-ui-smoke.sh

- name: Run Host App iPad UI smoke with Xcode 16.0
env:
POCKETROOT_HOST_UI_DEVICE_TYPE: >-
com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation
POCKETROOT_HOST_UI_DEVICE_NAME: PocketRoot-Host-iPad-UI-Smoke
POCKETROOT_HOST_UI_FAILURE_ARTIFACTS_DIR: >-
${{ runner.temp }}/pocketroot-ui-artifacts/host-ipad
run: ./Scripts/run-host-app-ui-smoke.sh
run: |
case "$POCKETROOT_UI_SUITE" in
external-consumer)
export POCKETROOT_UI_FAILURE_ARTIFACTS_DIR="$POCKETROOT_UI_FAILURE_ROOT"
./Scripts/run-external-consumer-ui-smoke.sh
;;
quick-start-iphone)
export POCKETROOT_UI_FAILURE_ARTIFACTS_DIR="$POCKETROOT_UI_FAILURE_ROOT"
./Scripts/run-quick-start-ui-smoke.sh
;;
quick-start-ipad)
export POCKETROOT_QUICK_START_UI_DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation"
export POCKETROOT_QUICK_START_UI_DEVICE_NAME="PocketRoot-Quick-Start-iPad-UI-Smoke"
export POCKETROOT_UI_FAILURE_ARTIFACTS_DIR="$POCKETROOT_UI_FAILURE_ROOT"
./Scripts/run-quick-start-ui-smoke.sh
;;
host-iphone)
export POCKETROOT_HOST_UI_FAILURE_ARTIFACTS_DIR="$POCKETROOT_UI_FAILURE_ROOT"
./Scripts/run-host-app-ui-smoke.sh
;;
host-ipad)
export POCKETROOT_HOST_UI_DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation"
export POCKETROOT_HOST_UI_DEVICE_NAME="PocketRoot-Host-iPad-UI-Smoke"
export POCKETROOT_HOST_UI_FAILURE_ARTIFACTS_DIR="$POCKETROOT_UI_FAILURE_ROOT"
./Scripts/run-host-app-ui-smoke.sh
;;
*)
echo "Unknown minimum-Xcode UI suite: $POCKETROOT_UI_SUITE" >&2
exit 2
;;
esac

- name: Upload UI failure diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pocketroot-ui-failure-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/pocketroot-ui-artifacts
name: pocketroot-ui-failure-${{ matrix.suite }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/pocketroot-ui-artifacts/${{ matrix.suite }}
if-no-files-found: warn
retention-days: 7
32 changes: 29 additions & 3 deletions CHANGELOG.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ All notable PocketRoot changes are recorded here. Semantic Versioning begins wit

## Unreleased

There are currently no changes outside the release candidate.
### Changed

- Split the minimum-Xcode 16 gate into an independent native-runtime job and a
five-way `fail-fast: false` UI matrix for the public-SHA external consumer,
Quick Start iPhone/iPad, and Host App iPhone/iPad. Every isolated runner
re-verifies the RootFS, XcodeGen, Xcode 16, and iOS 18 runtime through one
repository-owned composite action without transferring an unreviewed App or
DerivedData; each lane has a collision-free failure artifact. A runner-owned
Simulator that explicitly disappears from Xcode destinations is recreated
with the same runtime/device type for one bounded retry; caller-owned devices
and assertion failures are never retried. Coverage is unchanged while PR
wall-clock time no longer accumulates every UI smoke serially.

## 0.2.0 - Unreleased

Expand Down Expand Up @@ -73,13 +84,28 @@ There are currently no changes outside the release candidate.
independent Simulators and `xcodebuild` invocations, retaining phase
checkpoints, `.xcresult`, test output, and PocketRoot Simulator logs on
failure while keeping a ten-minute hard limit per test. The system-file
round trip now waits within one 30-second window for either the local device
location or a restored Host folder. The shared UI runner restarts and retries
round trip now uses one 60-second state loop for the Host fixture, a local
location, or an interactive `Browse` transition instead of mistaking an early
navigation bar for readiness. If the picker is still on Recents, it taps the
`Browse` tab, which can enter the Xcode 16 accessibility tree late; a picker
already in the Host folder does not return until the fixture file is actually
visible. Fixture selection uses a verified current frame and retries once if
iOS 18.0 leaves the picker open after reporting a synthesized tap. The helper
returns an explicit success state so a failed navigation or selection ends
the test case without cascading taps and secondary failures. The PTY size
probe likewise refocuses and resends only once when the first synthesized
command's unique marker remains absent for 15 seconds; the final 30-second
assertion still exposes a real terminal-input or resize synchronization
failure. The shared
UI runner restarts and retries
exactly once only when its own temporary Simulator test runner was not
registered with FrontBoard; caller-supplied shared Simulators are not
restarted, and assertion, timeout, and other build failures still fail
immediately. If retry or restart fails, both attempt logs and available
`.xcresult` bundles are retained.
- The native Xcode 16 lane validates the real RootFS install before downloading
the multi-gigabyte Simulator runtime, preserving the disk headroom enforced
by the installer while UI lanes retain the shared one-step setup.

## 0.1.0 - 2026-07-31

Expand Down
23 changes: 20 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ PocketRoot 的重要变化记录在这里,并从首个公开版本开始遵循

## Unreleased

当前暂无未归入候选版本的变更。
### Changed

- 将最低 Xcode 16 门禁拆为独立 native runtime job 和五路 `fail-fast: false` UI
matrix,分别并行验证公开 SHA 外部消费者、Quick Start iPhone/iPad 与 Host App
iPhone/iPad。全部 job 复用仓库内 composite action,但各自在隔离 runner 上重新
校验 RootFS、XcodeGen、Xcode 16 与 iOS 18 runtime,不传递未审计 App 或 DerivedData;
每路失败诊断使用独立 artifact 名称。runner 自建 Simulator 明确从 Xcode destination
消失时可按相同 runtime/device type 重建并有界重试一次;调用方设备和断言失败不重试。
测试覆盖不减少,PR 墙钟时间不再由全部 UI smoke 串行累加。

## 0.2.0 - Unreleased

Expand Down Expand Up @@ -53,12 +61,21 @@ PocketRoot 的重要变化记录在这里,并从首个公开版本开始遵循
- 将 Host App 的 Files/Workspace 与 PTY lifecycle UI smoke 拆分到独立模拟器和
`xcodebuild` 调用;失败时记录阶段检查点,并保留 `.xcresult`、测试输出和
PocketRoot 模拟器日志供 CI 下载分析,同时维持每项测试 10 分钟硬上限;
系统文件 round-trip 测试也会在同一个 30 秒窗口内等待本机位置或已恢复的
Host 目录,兼容文件选择器保存上次目录及慢速模拟器的合法状态。通用 UI runner
系统文件 round-trip 测试也会在一个 60 秒状态循环中等待 Host fixture、本机位置
或可交互的 `Browse/浏览`,而不会把提前出现的 picker 导航栏误判为就绪;
当 picker 停在 Recents 时,点击可能延迟进入 accessibility tree 的 Browse,
而已经位于 Host 目录时会等到 fixture 文件真正可见才返回。fixture 选择使用当前
已验证 frame;若 iOS 18.0 在报告合成 tap 后仍保留 picker,则有界重试一次。辅助函数
返回明确的成功状态,导航或选择失败后立即停止当前用例,避免后续点击产生连锁误报。
PTY 尺寸探针同样只在首条合成输入的唯一标记 15 秒内未出现时重新聚焦并补发一次,
最终 30 秒断言仍会暴露真实的终端输入或尺寸同步故障。
通用 UI runner
只对 test runner 未注册到 FrontBoard 的模拟器基础设施错误重启自己创建的临时设备
并重试一次,调用方指定的共享 Simulator 不会被重启;测试
断言、超时和其他构建错误仍立即失败,重试或重启失败时同时保留首次与最终日志及
可用的两次 `.xcresult`。
- Xcode 16 native lane 会先验证真实 RootFS 安装,再下载数 GB 的 Simulator runtime,
为安装器的磁盘余量门禁保留空间;UI lanes 继续使用统一的一步 setup。

## 0.1.0 - 2026-07-31

Expand Down
4 changes: 2 additions & 2 deletions Compliance/Release/experimental-v0.2.0/COMPOSITION.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"Scripts/inject-demo-rootfs.sh": "3982b5382b0d1e13e0c8e8a5bb5404c5bad1dfc4d6e9cd23a39e3395a83087bb",
"Scripts/scan-release-artifact.rb": "48f27ab280491864228393e1675fe3a2889cbd616f79e3cb16bae7efedf647c0",
"Scripts/run-host-app-ui-smoke.sh": "c8b11b1bf3a467fd10ed2c4124a72396b0a803e98f155d1d709fab46b59eede1",
"Scripts/run-ios-example-ui-smoke.sh": "071fa3336479f1849eb3be3f1f6b6516025197a0cf30640e64cd902282d9ed4d",
"Scripts/run-ios-example-ui-smoke.sh": "147fa58dc255e28fd63b0a7c62ed747b2215df652df3a0bdf5585eee4b49d395",
"Scripts/run-quick-start-ui-smoke.sh": "acf26d68fc37250e24911c2d23791a0d12ff06a624dedcefee0ed7905bc31af2",
"Scripts/run-external-consumer-ui-smoke.sh": "d9544ede3ecbad8d42a114015fa77071d31aa84aafc1cbe0f0f001453b1a4d0d",
"Scripts/run-host-app-device-ui-smoke.sh": "2f934265cb46145b27bb26e0d7e84acebc14290602e23cd1ab8f222aef68ed1f",
Expand Down Expand Up @@ -120,7 +120,7 @@
"Examples/PocketRootDemo/Sources/PocketRootDemo/Shared/UI/PlaceholderViewController.swift": "48e0f6462228a66face2aef51f9bd39ec02923c4d1c01b6b34f920948998f2b8",
"Examples/PocketRootHostApp/Sources/HostApp.swift": "0c36bd3104ddc5bebb9e23675a44a92e3ce4aec2c1461b3e182d86d1a75159c9",
"Examples/PocketRootHostApp/Sources/Info.plist": "711b38923f11fdb4ceb7d28cb90a51674afec0a64786a20f9bd68e3f406f8d26",
"Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "bc99a66278cd3476f7b4e854d85ec975d839362a386e8b5a455496f63e14c491",
"Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "cfa59f0489f94b14b242ffa1aa9f75e0eeda99c74ca487318e0ea096bbef8d61",
"Examples/PocketRootQuickStartApp/Sources/Info.plist": "b3c0c96e89db99c72c9f020590b1c5cdc0c927fd3af4b6ae7865abfdc81049b8",
"Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift": "1c77c9229281d0d1f9fbe3678bcc9995fff8c7d2189f4c6152daaa2999683fb6",
"Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift": "828471c84386dc8ddba23ecadc3618ccd559d92084ab762f4ef0832c3e3d5f59",
Expand Down
2 changes: 1 addition & 1 deletion Compliance/Release/experimental-v0.2.0/SBOM.spdx.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "PocketRoot experimental full-graph composition 0.2.0",
"documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.2.0/ac8d5bb757a49cc8c7c154c43a223a39caf7d05ec81c3a315f1afe08513c26be",
"documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.2.0/44f6d771dd3c6d83d7f5ee81ad4ffe83a50ef3efdafa24a26b1e8a70fc5f53d3",
"creationInfo": {
"created": "2026-08-03T00:00:00Z",
"creators": [
Expand Down
4 changes: 2 additions & 2 deletions Compliance/Release/experimental-v0.2.0/SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ac8d5bb757a49cc8c7c154c43a223a39caf7d05ec81c3a315f1afe08513c26be COMPOSITION.json
44f6d771dd3c6d83d7f5ee81ad4ffe83a50ef3efdafa24a26b1e8a70fc5f53d3 COMPOSITION.json
9fe270ba11160051d13f99cfe4ce6ee03c52882b88339dd6e4c1b4923ae30755 READINESS.json
6d41126608b019b46332a7957889f41e3c3ec6c6e9e2b884dca5ffc6d405e943 README.md
fab07621b5188930bfd2a38117e206815d94f755e667d65fc182ee55d14d2d40 RELEASE-CHECKLIST.md
0c88032bfa74974a774165e49a2ebf5f4fbfed6632436f1660c16209d7a7f773 SBOM.spdx.json
c56b8eec15f2112bcd38b8f1d8f0c56da52967538284e12cd749422565076b8f SBOM.spdx.json
Loading