-
Notifications
You must be signed in to change notification settings - Fork 0
250 lines (218 loc) · 11.1 KB
/
Copy pathci-packaging.yml
File metadata and controls
250 lines (218 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# Phase 6 — Portable-package CI and native runners
# (docs/plans/20260829-no_more_docker_architecture.md §9; execution plan Phase 6).
#
# Responsibilities kept distinct from the existing fake CI (ci.yml, Task 1) and the image build
# (image.yml, Task 5): this workflow proves the *installable* product — a self-contained wheel +
# reproducible sdist with the Studio baked in — across a real Linux runner (arc-general), a native
# macOS runner, and a native Windows runner. A Linux cross runner cannot satisfy the native jobs
# (real Torch/MPS/Waitress/filesystem/audio behavior requires the actual OS). Target-resolution
# jobs resolve for foreign platforms with --python-platform without installing foreign wheels
# (Task 3).
#
# Runner-label ruling (ADR, not a placeholder): every other workflow in this repo (ci.yml,
# image.yml, labeler.yml, release-please.yml, ci-ui.yml) runs on self-hosted arc-* labels
# provisioned via ../llama-monitor-runner — but that sibling repo's arc-* scale sets are all
# Linux pods (they cross-compile toward macOS/Windows targets with osxcross/mingw, they don't
# execute on those OSes), so they can't satisfy Gate 6's "native jobs must identify their real
# OS; a cross runner cannot satisfy them" requirement. Native execution needs a real kernel.
# Both the macos and windows legs now run on dedicated self-hosted runners (plain
# actions-runner agent, not ARC — ARC is Kubernetes/Linux-only) registered directly against
# this repo: `self-hosted-macos` and `self-hosted-windows`. Zero GitHub-hosted billable
# minutes for either leg. The "Identify real OS" step plus the doctor receipt's
# platform.sys_platform field make the real OS auditable in every run.
name: Packaging
on:
pull_request:
paths:
- .github/workflows/ci-packaging.yml
- pyproject.toml
- uv.lock
- hatch_build.py
- "src/persona_forge/**"
- "frontend/**"
- "tests/fixtures/**"
- "tests/tier1_unit/test_cli.py"
- "scripts/inspect_release_artifacts.py"
- "scripts/ci_isolated_install.py"
push:
branches: [main]
paths:
- .github/workflows/ci-packaging.yml
- pyproject.toml
- uv.lock
- hatch_build.py
- "src/persona_forge/**"
- "frontend/**"
- "tests/fixtures/**"
- "tests/tier1_unit/test_cli.py"
- "scripts/inspect_release_artifacts.py"
- "scripts/ci_isolated_install.py"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: packaging-${{ github.ref }}
cancel-in-progress: true
env:
UV_VERSION: "0.12.9"
PYTHON_VERSION: "3.13"
NODE_VERSION: "24"
jobs:
package:
name: package-${{ matrix.name }}
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
include:
- name: linux
runs-on: arc-general
pythonpath: "src:src/export"
os_probe: "uname -a"
pytest_workers: "auto"
- name: macos
runs-on: self-hosted-macos
pythonpath: "src:src/export"
os_probe: "uname -a"
pytest_workers: "auto"
- name: windows
runs-on: self-hosted-windows
pythonpath: "src;src/export"
os_probe: "[System.Environment]::OSVersion.VersionString"
# Each xdist worker starts an in-process fake HTTP server. The native Windows
# runner's loopback stack becomes unreliable under eight simultaneous startups.
pytest_workers: "1"
env:
PYTHONPATH: ${{ matrix.pythonpath }}
steps:
# Gate 6 requires native jobs to identify their real OS (a cross runner cannot satisfy
# them). The probe lands in the run log AND the doctor receipt captures platform.sys_platform.
- name: Identify real OS
run: ${{ matrix.os_probe }}
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# actions/setup-python's hermetic Python download assumes the GitHub-hosted directory
# layout (its install script hard-`mkdir`s /Users/runner on macOS) and fails with
# "Permission denied" as a non-`runner` user on a self-hosted box. `python -m pip install`
# is equally unsafe to assume: this Mac's system Python is Homebrew-managed and PEP
# 668-externally-managed, so pip refuses outright. astral-sh/setup-uv installs the pinned
# uv binary directly (no Python/pip dependency) and works identically hosted or
# self-hosted; `uv python install` then gets Python from uv's own toolchain.
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: ${{ env.UV_VERSION }}
- name: uv python install (pinned)
run: uv python install ${{ env.PYTHON_VERSION }}
- name: uv lock --check
run: uv lock --check
- name: uv sync --locked
run: uv sync --locked
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ env.NODE_VERSION }}
- name: Frontend check + build (mandatory)
working-directory: frontend
run: |
npm ci
npm run check
npm run build
# On this self-hosted Windows box, System32 precedes Git\bin on PATH, so plain `shell:
# bash` resolves to the WSL launcher stub at System32\bash.exe (WSL isn't installed here,
# and it's staying that way) instead of Git Bash. GITHUB_PATH only affects this job's own
# remaining steps in this run — no machine-wide PATH edit, no WSL. `shell:` itself can't
# take an expression (GitHub rejects `matrix` there at parse time — "Unrecognized
# named-value"), so the fix has to live in PATH ordering, not in a dynamic shell field.
- name: Prefer Git Bash over the WSL stub (Windows only)
if: matrix.name == 'windows'
shell: powershell
run: echo "C:\Program Files\Git\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Make logs directory
shell: bash
run: mkdir -p logs
# Task 6: "upload full logs and machine-readable receipts on failure and success" - every
# step below tees its full output into logs/, independent of the receipts uploaded further
# down, then the whole directory is uploaded unconditionally.
- name: Semantic tests (fast, fake lane)
shell: bash
run: uv run --frozen python -m pytest -m "not slow and not requires_torch and not requires_model_weights and not requires_openvino_ir" -n ${{ matrix.pytest_workers }} --tb=short tests/tier1_unit tests/tier2_backend tests/tier3_api_integration 2>&1 | tee logs/semantic-tests.log
- name: Spawned-process acceptance (fake-runtime readiness + clean shutdown)
shell: bash
run: uv run --frozen python -m pytest -m "slow" --tb=long tests/tier1_unit/test_cli.py 2>&1 | tee logs/spawned-process-acceptance.log
- name: Build wheel + sdist
shell: bash
run: uv build 2>&1 | tee logs/build.log
- name: Inspect release artifacts (fail-closed)
shell: bash
run: uv run --frozen python scripts/inspect_release_artifacts.py --dist-dir dist --receipt-out artifact-receipt.json 2>&1 | tee logs/inspect-release-artifacts.log
- name: Isolated non-editable install + read-only doctor (outside checkout)
shell: bash
run: uv run --frozen python scripts/ci_isolated_install.py 2>&1 | tee logs/ci-isolated-install.log
- name: Upload full logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: logs-${{ matrix.name }}
path: logs/
retention-days: 7
- name: Upload artifact receipt
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: artifact-receipt-${{ matrix.name }}
path: artifact-receipt.json
retention-days: 7
- name: Upload wheel + sdist
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: artifacts-${{ matrix.name }}
path: dist/
retention-days: 7
resolve:
name: target-resolution
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: arc-general
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: ${{ env.UV_VERSION }}
- name: uv python install (pinned)
run: uv python install ${{ env.PYTHON_VERSION }}
- name: uv lock --check
run: uv lock --check
# Resolution-only proof for foreign targets: --dry-run resolves and reports the install set
# without downloading or installing a single foreign wheel (Task 3). macOS is proven by the
# real macos-latest job instead, because torch's macOS wheel needs a specific platform tag
# the generic --python-platform value does not express. Every target is attempted (all
# failures are collected), a truthful machine-readable receipt is written, and the step is
# fail-closed: it exits non-zero if ANY target fails to resolve.
- name: Resolve targets (no foreign wheels installed)
shell: bash
run: |
set -o pipefail
status="pass"
run() { echo "+ $*"; if ! "$@"; then status="fail"; echo " !! resolution failed: $*"; fi; }
run uv sync --locked --dry-run --python-platform x86_64-unknown-linux-gnu
run uv sync --locked --dry-run --python-platform x86_64-pc-windows-msvc
run uv sync --locked --dry-run --python-platform x86_64-unknown-linux-gnu --extra cuda12
run uv sync --locked --dry-run --python-platform x86_64-unknown-linux-gnu --extra cuda13
run uv sync --locked --dry-run --python-platform x86_64-unknown-linux-gnu --extra xpu
run uv sync --locked --dry-run --python-platform x86_64-unknown-linux-gnu --extra rocm
run uv sync --locked --dry-run --python-platform x86_64-pc-windows-msvc --extra cuda12
run uv sync --locked --dry-run --python-platform x86_64-pc-windows-msvc --extra cuda13
run uv sync --locked --dry-run --python-platform x86_64-pc-windows-msvc --extra xpu
cat > resolve-receipt.json <<JSON
{"job":"target-resolution","status":"${status}","platforms":["x86_64-unknown-linux-gnu","x86_64-pc-windows-msvc"],"extras":["cuda12","cuda13","xpu","rocm"]}
JSON
echo "=== receipt ==="; cat resolve-receipt.json
if [ "${status}" != "pass" ]; then echo "target resolution FAILED"; exit 1; fi
- name: Upload resolution receipt
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: resolve-receipt
path: resolve-receipt.json
retention-days: 7