Skip to content

Commit 6b19fe1

Browse files
authored
Chore/dependency supply chain discipline (#225)
* chore(deps): lock dependency supply chain * chore(ci): add installer verification workflow * fix(ci): pin osv scanner action * fix(ci): baseline existing osv findings * fix(ci): skip pystray backend check on headless linux
1 parent 852bf3e commit 6b19fe1

42 files changed

Lines changed: 6621 additions & 342 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ jobs:
2929
- uses: actions/setup-python@v6
3030
with:
3131
python-version: "3.12"
32-
cache: pip
32+
33+
- name: Install uv
34+
run: python -m pip install "uv>=0.7,<1.0"
3335

3436
- name: Install Linux runtime libraries
3537
if: runner.os == 'Linux'
@@ -42,26 +44,26 @@ jobs:
4244
run: brew install portaudio
4345

4446
- name: Install Python packages
45-
run: python -m pip install -r requirements.txt pytest
47+
run: uv sync --locked --all-extras --group test
4648

4749
- name: Verify required runtime dependencies
48-
run: python scripts/verify_runtime_dependencies.py
50+
run: uv run python scripts/verify_runtime_dependencies.py all
4951

5052
- name: Compile Python files
51-
run: python -m compileall -q -x "(\\.git|\\.venv|dist|build|__pycache__)" .
53+
run: uv run python -m compileall -q -x "(\\.git|\\.venv|dist|build|__pycache__)" .
5254

5355
- name: Focused startup hardening tests
54-
run: python -m pytest tests/test_startup_hardening.py tests/test_app_port.py
56+
run: uv run python -m pytest tests/test_dependency_metadata.py tests/test_optional_dependency_imports.py tests/test_startup_hardening.py tests/test_app_port.py
5557

5658
- name: Focused agent orchestration tests
57-
run: python -m pytest tests/test_agent_profiles.py tests/test_agent_context.py tests/test_agent_tool_catalog.py tests/test_agent_runs.py tests/test_agent_runner.py tests/test_agent_tool.py tests/test_agent_tool_filtering.py tests/test_agent_write_locks.py tests/test_goal_mode.py tests/test_channel_goal_runtime.py tests/test_row_bot_status_agents.py
59+
run: uv run python -m pytest tests/test_agent_profiles.py tests/test_agent_context.py tests/test_agent_tool_catalog.py tests/test_agent_runs.py tests/test_agent_runner.py tests/test_agent_tool.py tests/test_agent_tool_filtering.py tests/test_agent_write_locks.py tests/test_goal_mode.py tests/test_channel_goal_runtime.py tests/test_row_bot_status_agents.py
5860

5961
- name: Focused provider model selection tests
60-
run: python -m pytest tests/test_provider_catalog.py tests/test_provider_selection.py tests/test_provider_runtime.py tests/test_provider_media.py tests/test_row_bot_status_media.py tests/test_provider_subscription_auth.py tests/test_atlascloud_first_class_provider.py tests/test_openai_compatible_transport.py tests/test_claude_subscription_auth.py tests/test_claude_subscription_transport.py tests/test_xai_oauth_provider.py tests/test_xai_oauth_transport.py tests/test_xai_media.py tests/test_model_picker_regressions.py -k "not xai_oauth_loopback"
62+
run: uv run python -m pytest tests/test_provider_catalog.py tests/test_provider_selection.py tests/test_provider_runtime.py tests/test_provider_media.py tests/test_row_bot_status_media.py tests/test_provider_subscription_auth.py tests/test_atlascloud_first_class_provider.py tests/test_openai_compatible_transport.py tests/test_claude_subscription_auth.py tests/test_claude_subscription_transport.py tests/test_xai_oauth_provider.py tests/test_xai_oauth_transport.py tests/test_xai_media.py tests/test_model_picker_regressions.py -k "not xai_oauth_loopback"
6163

6264
- name: Runtime smoke on Linux
6365
if: runner.os == 'Linux'
64-
run: python scripts/smoke_app.py --port 8090 --timeout 120
66+
run: uv run python scripts/smoke_app.py --port 8090 --timeout 120
6567

6668
- name: Validate Linux installer scripts
6769
if: runner.os == 'Linux'
@@ -79,7 +81,9 @@ jobs:
7981
- uses: actions/setup-python@v6
8082
with:
8183
python-version: "3.12"
82-
cache: pip
84+
85+
- name: Install uv
86+
run: python -m pip install "uv>=0.7,<1.0"
8387

8488
- name: Install Ollama
8589
run: |
@@ -90,10 +94,10 @@ jobs:
9094
ollama pull qwen3:1.7b
9195
9296
- name: Install Python packages
93-
run: python -m pip install -r requirements.txt pytest
97+
run: uv sync --locked --all-extras --group test
9498

9599
- name: Verify required runtime dependencies
96-
run: python scripts/verify_runtime_dependencies.py
100+
run: uv run python scripts/verify_runtime_dependencies.py all
97101

98102
- name: Run test suite
99-
run: python tests/test_suite.py
103+
run: uv run python tests/test_suite.py
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
name: Installer Verify
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
windows:
7+
description: Build and smoke the Windows Inno Setup installer
8+
type: boolean
9+
default: true
10+
linux:
11+
description: Build and smoke the Linux self-contained tarball
12+
type: boolean
13+
default: true
14+
macos:
15+
description: Build and smoke the unsigned macOS app/pkg
16+
type: boolean
17+
default: true
18+
19+
permissions:
20+
contents: read
21+
22+
concurrency:
23+
group: installer-verify-${{ github.ref }}
24+
cancel-in-progress: false
25+
26+
env:
27+
WINDOWS_PYTHON_VERSION: "3.13.2"
28+
ROW_BOT_STARTUP_TIMEOUT: "180"
29+
30+
jobs:
31+
preflight:
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 15
34+
steps:
35+
- uses: actions/checkout@v6
36+
37+
- uses: actions/setup-python@v6
38+
with:
39+
python-version: "3.12"
40+
41+
- name: Install uv
42+
run: python -m pip install "uv>=0.7,<1.0"
43+
44+
- name: Verify locked dependency files
45+
run: |
46+
uv lock --check
47+
python scripts/export_locked_requirements.py --check
48+
49+
verify-windows:
50+
if: ${{ inputs.windows }}
51+
needs: preflight
52+
runs-on: windows-latest
53+
timeout-minutes: 120
54+
steps:
55+
- uses: actions/checkout@v6
56+
57+
- uses: actions/setup-python@v6
58+
with:
59+
python-version: ${{ env.WINDOWS_PYTHON_VERSION }}
60+
61+
- name: Install Inno Setup
62+
run: choco install innosetup --yes --no-progress
63+
64+
- name: Build Windows installer
65+
shell: pwsh
66+
run: .\installer\build_installer.ps1 -PythonVersion "${{ env.WINDOWS_PYTHON_VERSION }}"
67+
68+
- name: Smoke installed Windows package
69+
shell: pwsh
70+
run: |
71+
$ErrorActionPreference = "Stop"
72+
$installer = Get-ChildItem -Path dist -Filter "Row-Bot-*-Windows-*.exe" | Select-Object -First 1
73+
if (-not $installer) {
74+
throw "Windows installer artifact not found under dist"
75+
}
76+
77+
$installDir = Join-Path $env:RUNNER_TEMP "Row-Bot"
78+
$installLog = Join-Path $env:RUNNER_TEMP "row-bot-install.log"
79+
if (Test-Path $installDir) {
80+
Remove-Item -LiteralPath $installDir -Recurse -Force
81+
}
82+
83+
$installerArgs = @(
84+
"/VERYSILENT",
85+
"/SUPPRESSMSGBOXES",
86+
"/NORESTART",
87+
"/DIR=$installDir",
88+
"/LOG=$installLog"
89+
)
90+
& $installer.FullName @installerArgs
91+
if ($LASTEXITCODE -ne 0) {
92+
Get-Content $installLog -ErrorAction SilentlyContinue
93+
throw "Installer exited with code $LASTEXITCODE"
94+
}
95+
96+
$pythonExe = Join-Path $installDir "python\python.exe"
97+
$appDir = Join-Path $installDir "app"
98+
foreach ($required in @(
99+
$pythonExe,
100+
(Join-Path $appDir "launcher.py"),
101+
(Join-Path $appDir "scripts\verify_runtime_dependencies.py")
102+
)) {
103+
if (!(Test-Path $required)) {
104+
throw "Expected installed file missing: $required"
105+
}
106+
}
107+
108+
$env:PYTHONNOUSERSITE = "1"
109+
$env:PYTHONIOENCODING = "utf-8"
110+
$env:TCL_LIBRARY = Join-Path $installDir "python\tcl\tcl8.6"
111+
$env:TK_LIBRARY = Join-Path $installDir "python\tcl\tk8.6"
112+
$env:PLAYWRIGHT_BROWSERS_PATH = Join-Path $installDir "python\playwright-browsers"
113+
$env:PATH = (Join-Path $installDir "python\Scripts") + ";" + (Join-Path $installDir "python") + ";" + $env:PATH
114+
115+
& $pythonExe (Join-Path $appDir "scripts\verify_runtime_dependencies.py") all
116+
if ($LASTEXITCODE -ne 0) {
117+
throw "Installed runtime verifier failed with code $LASTEXITCODE"
118+
}
119+
120+
python scripts/smoke_app.py `
121+
--port 8094 `
122+
--timeout 180 `
123+
--cwd "$appDir" `
124+
-- "$pythonExe" launcher.py --server --no-open --no-splash --no-ollama --port 8094
125+
126+
- name: Upload Windows installer
127+
if: always()
128+
uses: actions/upload-artifact@v7
129+
with:
130+
name: Row-Bot-Windows-verified
131+
path: dist/Row-Bot-*-Windows-*.exe
132+
retention-days: 14
133+
134+
- name: Upload Windows install log
135+
if: always()
136+
uses: actions/upload-artifact@v7
137+
with:
138+
name: Row-Bot-Windows-install-log
139+
path: ${{ runner.temp }}\row-bot-install.log
140+
if-no-files-found: ignore
141+
retention-days: 14
142+
143+
verify-linux:
144+
if: ${{ inputs.linux }}
145+
needs: preflight
146+
runs-on: ubuntu-latest
147+
timeout-minutes: 120
148+
steps:
149+
- uses: actions/checkout@v6
150+
151+
- uses: actions/setup-python@v6
152+
with:
153+
python-version: "3.12"
154+
155+
- name: Install Linux build/runtime libraries
156+
run: |
157+
sudo apt-get update
158+
sudo apt-get install -y --no-install-recommends rsync binutils libgl1 libegl1 libglib2.0-0 libxcb-cursor0 libportaudio2
159+
160+
- name: Validate Linux installer scripts
161+
run: bash -n build_linux_app.sh installer/build_linux_app.sh installer/install-linux.sh
162+
163+
- name: Build Linux package
164+
run: |
165+
chmod +x installer/build_linux_app.sh
166+
./installer/build_linux_app.sh
167+
168+
- name: Smoke installed Linux package
169+
run: |
170+
set -euo pipefail
171+
mkdir -p "$RUNNER_TEMP/row-bot-linux-smoke"
172+
tar -xzf dist/Row-Bot-*-Linux-*.tar.gz -C "$RUNNER_TEMP/row-bot-linux-smoke"
173+
PACKAGE_ROOT="$(find "$RUNNER_TEMP/row-bot-linux-smoke" -maxdepth 1 -type d -name 'Row-Bot-*-Linux-*' | head -n 1)"
174+
if [ -z "$PACKAGE_ROOT" ]; then
175+
echo "::error::Linux package root not found after extraction"
176+
exit 1
177+
fi
178+
chmod +x "$PACKAGE_ROOT/bin/row-bot"
179+
export HOME="$RUNNER_TEMP/row-bot-linux-home"
180+
export XDG_DATA_HOME="$RUNNER_TEMP/row-bot-linux-xdg"
181+
mkdir -p "$HOME" "$XDG_DATA_HOME"
182+
bash "$PACKAGE_ROOT/install.sh"
183+
python scripts/smoke_app.py \
184+
--port 8095 \
185+
--timeout 180 \
186+
--cwd "$XDG_DATA_HOME/row-bot/current/app" \
187+
-- "$HOME/.local/bin/row-bot"
188+
python scripts/smoke_app.py \
189+
--port 8096 \
190+
--timeout 120 \
191+
--cwd "$XDG_DATA_HOME/row-bot/current/app" \
192+
-- "$HOME/.local/bin/row-bot" --server --no-open --port 8096 --no-ollama
193+
194+
- name: Upload Linux package
195+
if: always()
196+
uses: actions/upload-artifact@v7
197+
with:
198+
name: Row-Bot-Linux-verified
199+
path: dist/Row-Bot-*-Linux-*.tar.gz
200+
retention-days: 14
201+
202+
verify-macos:
203+
if: ${{ inputs.macos }}
204+
needs: preflight
205+
runs-on: macos-latest
206+
timeout-minutes: 180
207+
steps:
208+
- uses: actions/checkout@v6
209+
210+
- uses: actions/setup-python@v6
211+
with:
212+
python-version: "3.12"
213+
214+
- name: Build unsigned macOS package
215+
env:
216+
BUNDLE_PLAYWRIGHT: "0"
217+
run: |
218+
chmod +x installer/build_mac_app.sh
219+
./installer/build_mac_app.sh
220+
221+
- name: Smoke macOS app bundle
222+
run: |
223+
set -euo pipefail
224+
APP_PATH="installer/build/mac/Row-Bot.app"
225+
RESOURCES="$APP_PATH/Contents/Resources"
226+
APP_DIR="$RESOURCES/app"
227+
PYTHON="$RESOURCES/python/bin/python3"
228+
if [ ! -x "$PYTHON" ]; then
229+
echo "::error::Bundled Python not found at $PYTHON"
230+
exit 1
231+
fi
232+
ROW_BOT_INSTALL_ROOT="$RESOURCES" \
233+
PYTHONNOUSERSITE=1 \
234+
PYTHONIOENCODING=utf-8 \
235+
PLAYWRIGHT_BROWSERS_PATH="$RESOURCES/python/playwright-browsers" \
236+
python scripts/smoke_app.py \
237+
--port 8097 \
238+
--timeout 180 \
239+
--cwd "$APP_DIR" \
240+
-- "$PYTHON" launcher.py --server --no-open --no-splash --no-ollama --port 8097
241+
242+
- name: Install and smoke macOS package
243+
run: |
244+
set -euo pipefail
245+
PKG="$(find dist -maxdepth 1 -name 'Row-Bot-*-macOS-*.pkg' | head -n 1)"
246+
if [ -z "$PKG" ]; then
247+
echo "::error::macOS pkg artifact not found under dist"
248+
exit 1
249+
fi
250+
sudo rm -rf /Applications/Row-Bot.app
251+
sudo installer -pkg "$PKG" -target /
252+
253+
APP_PATH="/Applications/Row-Bot.app"
254+
RESOURCES="$APP_PATH/Contents/Resources"
255+
APP_DIR="$RESOURCES/app"
256+
PYTHON="$RESOURCES/python/bin/python3"
257+
if [ ! -x "$PYTHON" ]; then
258+
echo "::error::Installed bundled Python not found at $PYTHON"
259+
exit 1
260+
fi
261+
ROW_BOT_INSTALL_ROOT="$RESOURCES" \
262+
PYTHONNOUSERSITE=1 \
263+
PYTHONIOENCODING=utf-8 \
264+
PLAYWRIGHT_BROWSERS_PATH="$RESOURCES/python/playwright-browsers" \
265+
python scripts/smoke_app.py \
266+
--port 8098 \
267+
--timeout 180 \
268+
--cwd "$APP_DIR" \
269+
-- "$PYTHON" launcher.py --server --no-open --no-splash --no-ollama --port 8098
270+
271+
- name: Upload macOS package
272+
if: always()
273+
uses: actions/upload-artifact@v7
274+
with:
275+
name: Row-Bot-macOS-verified
276+
path: dist/Row-Bot-*-macOS-*.pkg
277+
retention-days: 14

.github/workflows/lint.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Lint (advisory)
1+
name: Lint (safety + advisory)
22

33
on:
44
pull_request:
@@ -17,11 +17,15 @@ jobs:
1717
- uses: actions/setup-python@v6
1818
with:
1919
python-version: "3.12"
20-
- name: Install Ruff
21-
run: python -m pip install ruff
22-
- name: Ruff check
20+
- name: Install uv
21+
run: python -m pip install "uv>=0.7,<1.0"
22+
- name: Install lint dependencies
23+
run: uv sync --locked --group lint
24+
- name: Ruff blocking safety checks
25+
run: uv run ruff check . --select E9,F63,F7,F82 --output-format=github
26+
- name: Ruff full check (advisory)
2327
continue-on-error: true
24-
run: ruff check . --output-format=github
28+
run: uv run ruff check . --output-format=github
2529
- name: Ruff format check
2630
continue-on-error: true
27-
run: ruff format --check .
31+
run: uv run ruff format --check .

0 commit comments

Comments
 (0)