A name used before it exists only fails when that line runs, so a
rarely-taken branch ships broken. Two routes 500'd on every call and
Stop crashed on compose-slot apps, both found by pyflakes in seconds.
The check now runs in the ratchet job and in preflight.
CONTRIBUTING asks contributors to target dev, but every workflow only
triggered on PRs into main, so contributor PRs merged with no checks
and failures surfaced only after the push to dev.
Centralize backup schedule calculations and shared request/form flows, encode query values, and move repository finalization into its service. Add browser CI coverage, repair stale test fixtures, and provide opt-in request and build measurement tools.
test_buildpacks.py asserts on Dockerfile TEXT, which cannot see a bug that
only fails at build or boot time -- the `--omit=dev` dead fallback shipped
`vite: not found` to a user's deploy. New @docker_builds leg (gated on
SERVERKIT_DOCKER_BUILDS=1, sibling of @real_binaries) runs the full path
per stack: detect() -> generate_dockerfile() -> `docker build` ->
`docker run` -> HTTP 200. Fixtures: vite (the regression), express with no
build step (prod-only install must still boot), flask, static. Wired as a
backend-ci job beside real-binaries.
Writing the flask fixture surfaced the same dead-at-boot class one layer
up: detection invents `gunicorn app:app` / `uvicorn main:app` as the start
command but never installed the server, so a repo that doesn't declare
gunicorn built green and died at boot. The build command now installs the
invented server (a no-op when the repo already pins it); Procfile-authored
start commands remain the author's contract.
All four stacks verified locally: build + boot + HTTP 200 in 53s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel's crontab/nginx code only ever met the fake_subprocess stub —
the 8-distro matrix runs stubs and the container legs run 12 detection
tests, so a syntax the real parser rejects failed on a user's box, not
in CI (the #117 class).
- new `real_binaries` marker (registered in conftest); the tests also
gate on SERVERKIT_REAL_BINARIES=1 so ordinary suite runs never mutate
a machine's crontab by surprise. Local loop (WSL works):
SERVERKIT_REAL_BINARIES=1 pytest tests -m real_binaries
- test_real_crontab_roundtrip: add -> list -> toggle -> remove through
the real `crontab -`/`crontab -l`, operator lines preserved, name
injection blocked; user crontab saved/restored around each test
(verified green on WSL against the real binary)
- test_real_nginx_vhost_syntax: every render_site_config flavor through
a real `nginx -t` in a tmp wrapper conf, plus a garbage probe so the
harness can't go vacuous
- backend-ci gains a real-binaries job (ubuntu runner, apt nginx+cron)
- scripts-ci gains the probe-matrix job: scripts/test/probe-matrix.sh
(real ufw/nginx/firewalld in 4 container shapes) existed since plan
75 but was wired into NO workflow; the ubuntu-root-sbinless leg was
re-verified green locally before wiring
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend CI was the entire pipeline wait. Measured on the last dev push:
Extensions CI 12s
Version Bump 18s
Test System Utilities 35s
Security Scan 41s
Scripts CI 66s
Release Build Smoke 68s
Backend CI 21m 50s <- everything else finished in a minute
Step timings put all of it in the test run itself (install deps 22s, ratchet
9s), so there is no caching win to take — it is 3173 tests against a
function-scoped `app` fixture that rebuilds the Flask app and all 80+ tables
per test. Fixing that fixture is the real repair and is planned separately;
this change buys the wall-clock back now without touching a single test.
Split over 4 runners via pytest-split (794/794/794/791). Sharding rather than
pytest-xdist is deliberate: each shard is its own VM, so the process-shared
state that makes in-process parallelism unsafe here (templates.json, APPS_DIR
— see the per-PID DB dance in tests/conftest.py) simply isn't shared, and no
test code has to change.
The ratchet moves to its own job because it must see the whole suite; a shard
only collects its quarter. It runs beside the shards and costs no wall-clock.
Two details:
- The shard command is scoped to `tests` rather than a bare `pytest`.
Identical in CI (3173 either way, backend/dev-data/ being gitignored), but a
bare pytest on a dev box tries to collect the locally deployed apps under
backend/dev-data/ and dies during collection. The line is now copy-pasteable
for debugging a red shard locally.
- With no .test_durations file, pytest-split balances by test count, not time,
and these tests range from 0.1s to 10s+. If one shard becomes the new
critical path, run once with --store-durations and commit the file.
main also drops out of `push` here: release.yml gates itself on this workflow
via ci-gate, so listing main ran the whole suite a second time on every merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Install the guard the data loss taught us to want: code and its proving tests
died together, so the suite stayed green (2572->2209 passing) while whole
features silently vanished — nobody diffs a green number.
- backend/tests/BASELINE_COUNT (2475) + check_test_count.py: compares pytest's
collected count to the floor; CI (backend-ci.yml new step) fails if it drops.
Lowering the floor requires editing BASELINE_COUNT in the same commit, so a
silent test loss surfaces as a red X instead of a still-green suite. Verified:
passes at baseline, exits 1 on a simulated drop.
- CHANGELOG: document the recovery-parity restore (Cloudflare R2 engine,
email-bounce, DNS-cutover staging, survey routes, cron CSS, 18 test suites)
and the ratchet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the Mapanare release pattern for the ServerKit panel:
- Trigger release on push to main (PR merge), v* tags, and workflow_dispatch
- Add workflow_call to backend-ci.yml so release can use it as a CI gate
- Read VERSION file, create/push v{VERSION} tag when releasing from main
- Build and push multi-arch Docker image jhd3197/serverkit:{VERSION} + latest
- Create GitHub Release with Docker install instructions
The Backend CI installed only requirements.txt (which doesn't carry the
test-only pytest dependency) and then ran `python -m pytest`, failing with
"No module named pytest". Add `pip install pytest` to the install step.
pytest is the only test-only import across tests/ (everything else they
import — cryptography, flask_jwt_extended, prompture, yaml — is already a
runtime dependency). Full suite: 232 passed, 12 skipped locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nothing ran the agent's tests or the backend suite automatically, so breakage
was only found by hand. Add two workflows:
- agent-ci.yml: go vet + go test on linux, plus a cross-compile build matrix
(linux amd64/arm64, windows amd64) to catch platform-specific breakage at
PR time. darwin is excluded — fyne.io/systray needs the macOS Cocoa toolchain
and can't cross-compile from Linux (it's built on a mac at release time).
- backend-ci.yml: install requirements and run the full pytest suite, including
the new end-to-end agent command-loop test.
Verified locally: go vet clean, all three cross-builds succeed, agent unit
tests and the full backend suite (136 passed) are green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>