mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-14 06:16:20 -06:00
main has been red since 4f7b4e3 for a reason visible nowhere in its diff.
The player-parity baselines describe what an UN-UPDATED display can do, so
they are judged against the SHIPPED source — `git show <latest tag>:…` —
rather than the working tree. actions/checkout defaults to a shallow clone
with no tags, so that lookup found nothing and the suite fell back to the
working tree. In a tree where today's QA had just fixed the players'
set_volume payload bug, the biconditional then demanded that
BASELINE.web/tizen gain audio.volume — for displays that cannot possibly
have the fix yet. Green locally, red in CI, and the failing assertion names
a baseline rather than the checkout that caused it.
So fetch the tags in the test job, and make the biconditionals SKIP when
there are none instead of asserting against the wrong source. That fallback
was never a slightly-early assertion; it was an inverted one. A skipped
assertion announces itself, a wrong one does not.
Verified both ways: with tags 15/15 pass, in a tagless shallow clone 13
pass + 2 skip + 0 fail (previously 1 fail).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS
142 lines
5.6 KiB
YAML
142 lines
5.6 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# main gets frequent pushes - cancel an in-flight run when a newer commit
|
|
# (or rerun) supersedes it, per ref.
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
test:
|
|
name: Unit tests (node --test)
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: server
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
# The player-parity baselines judge a claim against the SHIPPED source
|
|
# (`git show <latest tag>:…`), because a baseline describes what an
|
|
# UN-UPDATED display can do. The default shallow checkout has no tags, so
|
|
# the suite silently fell back to the working tree and the biconditionals
|
|
# inverted: fixing a player's payload bug made CI demand a baseline change
|
|
# for displays that cannot possibly have the fix yet. Green locally, red
|
|
# here, for a reason found nowhere in the diff.
|
|
fetch-depth: 0
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '20'
|
|
cache: npm
|
|
cache-dependency-path: server/package-lock.json
|
|
- run: npm ci
|
|
- run: npm test
|
|
|
|
openapi:
|
|
name: OpenAPI spec lint
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '20'
|
|
- name: Lint the public API spec
|
|
run: npx --yes @redocly/cli@latest lint docs/openapi.yaml
|
|
# Contract integrity: the spec documents ONLY the token-reachable public surface.
|
|
# A JWT-only router (admin/auth/provision/...) appearing here is a security flag,
|
|
# not a convenience - fail loudly. (The runtime partition test is a separate suite
|
|
# that will cross-check the spec against the live mount list.)
|
|
- name: Assert spec is public-only
|
|
run: |
|
|
BAD=$(grep -oE '^ /(admin|auth|workspaces|ai|provision|white-label|status|subscription|stripe|teams|player-debug|contact|tokens)\b' docs/openapi.yaml || true)
|
|
if [ -n "$BAD" ]; then echo "::error::JWT-only path(s) leaked into the public spec:"; echo "$BAD"; exit 1; fi
|
|
if grep -qE 'unassigned|/prune' docs/openapi.yaml; then echo "::error::token-denied endpoint present in public spec"; exit 1; fi
|
|
echo "OK: spec is public-only"
|
|
|
|
android-test:
|
|
name: Android unit tests (Kotlin schedule evaluator vectors)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: '17'
|
|
# Cache ~/.gradle/caches AND ~/.gradle/wrapper. The wrapper cache holds the
|
|
# Gradle distribution (gradle-8.5-bin.zip); without it every run re-downloaded
|
|
# ~130MB from services.gradle.org, and a transient reset there failed the job
|
|
# before any test ran (org.gradle.wrapper.Install.forceFetch). On a cache hit the
|
|
# distribution is already present, so the download — and its flake — is skipped.
|
|
cache: gradle
|
|
- uses: android-actions/setup-android@v3
|
|
# ScheduleEvalTest reads the SHARED shared/schedule-vectors.json (wired via
|
|
# the test task in app/build.gradle.kts), so a ScheduleEval.kt change that
|
|
# breaks the contract fails here.
|
|
- name: Kotlin evaluator vector conformance
|
|
working-directory: android
|
|
run: ./gradlew :app:testDebugUnitTest --no-daemon
|
|
|
|
smoke:
|
|
name: Boot smoke + version check
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '20'
|
|
cache: npm
|
|
cache-dependency-path: server/package-lock.json
|
|
|
|
- name: Install deps
|
|
working-directory: server
|
|
run: npm ci
|
|
|
|
# Boot against a fresh SQLite db (clean checkout = no db yet). SELF_HOSTED
|
|
# makes the first user an admin with no billing. No certs present, so the
|
|
# server listens on plain HTTP at :3001. Background it and wait until it
|
|
# answers.
|
|
- name: Boot server
|
|
working-directory: server
|
|
env:
|
|
SELF_HOSTED: 'true'
|
|
run: |
|
|
node server.js > "$RUNNER_TEMP/server.log" 2>&1 &
|
|
echo $! > "$RUNNER_TEMP/server.pid"
|
|
for i in $(seq 1 30); do
|
|
curl -sf http://localhost:3001/api/status >/dev/null && exit 0
|
|
sleep 1
|
|
done
|
|
echo "server did not come up within 30s:"; cat "$RUNNER_TEMP/server.log"; exit 1
|
|
|
|
# Assert the public status endpoint is healthy and reports exactly the
|
|
# VERSION file - this is what proves the single-source-of-truth wiring.
|
|
- name: Assert /api/status ok and version matches VERSION
|
|
run: |
|
|
STATUS="$(curl -sf http://localhost:3001/api/status)"
|
|
echo "status: $STATUS"
|
|
EXPECTED="$(cat VERSION)"
|
|
REPORTED="$(echo "$STATUS" | jq -r .version)"
|
|
echo "VERSION file: $EXPECTED reported: $REPORTED"
|
|
test "$(echo "$STATUS" | jq -r .status)" = "ok"
|
|
test "$REPORTED" = "$EXPECTED"
|
|
echo "OK: status ok, version $REPORTED matches VERSION"
|
|
|
|
- name: Stop server
|
|
if: always()
|
|
run: kill "$(cat "$RUNNER_TEMP/server.pid")" 2>/dev/null || true
|
|
|
|
# TODO (deferred - needs a tag earlier than HEAD, so meaningful from v1.8.0 on):
|
|
# upgrade-path job. Restore a db created by the previous tagged release, boot
|
|
# the current code against it, and assert migrations complete and /api/status
|
|
# is healthy. Add once a prior release tag exists.
|