diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b66e8a1..c3df72d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: - uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: app/api/packages.lock.json - run: npm ci - run: dotnet restore app/api --locked-mode - run: dotnet tool restore @@ -29,6 +31,16 @@ jobs: - run: npm run typecheck - run: dotnet format app/api --verify-no-changes --no-restore - run: npm test + - name: Upload Playwright artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-failure-${{ github.run_id }} + path: | + playwright-report/ + test-results/ + if-no-files-found: ignore + retention-days: 7 # Render deploys the API from this Dockerfile only after a merge (render.yaml). Build and boot # the same image here, so a broken image or Production startup fails the PR instead. @@ -89,6 +101,8 @@ jobs: - uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: app/api/packages.lock.json - run: dotnet restore app/api --locked-mode - run: dotnet tool restore - name: Build the migration bundle @@ -116,3 +130,15 @@ jobs: test -n "$RENDER_DEPLOY_HOOK_URL" || { echo "::error::Set the RENDER_DEPLOY_HOOK_URL secret in the api-deploy environment."; exit 1; } curl --silent --show-error --fail --request POST --output /dev/null "${RENDER_DEPLOY_HOOK_URL}&ref=${GITHUB_SHA}" + - name: Wait for Render API health + run: | + for attempt in $(seq 1 30); do + body=$(curl --silent --fail --max-time 2 https://specthread-api.onrender.com/health) || body="" + if [ "$body" = '{"status":"ok"}' ]; then + echo "Render API health check passed." + exit 0 + fi + if [ "$attempt" -lt 30 ]; then sleep 8; fi + done + echo "::error::Render API did not become healthy within five minutes." + exit 1 diff --git a/docs/CI-PIPELINE-FEEDBACK.md b/docs/CI-PIPELINE-FEEDBACK.md new file mode 100644 index 0000000..0e90f19 --- /dev/null +++ b/docs/CI-PIPELINE-FEEDBACK.md @@ -0,0 +1,72 @@ +# CI Pipeline Feedback and Deployment Availability + +## Status + +Implemented on `feature/pipeline-feedback`; pending PR review and GitHub Actions validation. + +## Problem + +The current GitHub Actions workflow validates pull requests and deploys the API after +merges to `main`. It does not retain Playwright reports as workflow artifacts, NuGet +restore caching is not configured, and the deployment job stops after calling the +Render deploy hook without checking that the public API is responding. + +## Goals + +- Make failed Playwright runs easier to diagnose from GitHub Actions. +- Reuse NuGet packages across runs, keyed by the API lock file while retaining locked + restores. +- After the API deploy hook is accepted, check that the Render API health endpoint + returns the expected liveness response within a bounded time. + +## Non-goals + +- Change application behavior, database schema, or migration order. +- Deploy the web app; Vercel continues to manage that separately. +- Add Render API credentials, query Render's deployment API, or prove that the + responding process is running the exact requested commit. +- Retry a failed migration, roll back a deployment automatically, or run deploy checks + on pull requests. + +## Design + +1. Configure `actions/setup-dotnet` NuGet caching in the `verify` and `deploy-api` + jobs, using `app/api/packages.lock.json` as the cache dependency. Keep + `dotnet restore --locked-mode` unchanged. +2. If `verify` fails, upload `playwright-report/` and `test-results/` as a GitHub + Actions artifact for seven days. Ignore missing artifact directories because a + failure can occur before Playwright starts. The test workflow uses isolated, + disposable data and dummy provider credentials. +3. After the Render deploy-hook request succeeds, poll + `https://specthread-api.onrender.com/health` for up to five minutes. Succeed only + when the response is HTTP 200 with `{"status":"ok"}`; otherwise fail the deploy + job with a concise diagnostic. Each request is capped at two seconds, with an + eight-second pause between at most 30 attempts, keeping the total below five + minutes. Never print the deploy-hook URL or credentials. + +The health endpoint is a liveness check. Without querying Render's deployment API or +exposing a build identifier, this verifies service availability but does not prove that +the exact requested commit is serving traffic. + +## Acceptance criteria + +- Pull-request checks and their ordering are unchanged; deployment still runs only on + pushes to `main` after `verify` and `api-image` pass. +- Failed `verify` runs retain any available Playwright report and test results for + seven days, without failing solely because a report directory is absent. +- NuGet cache keys follow `app/api/packages.lock.json`; locked restore remains enabled + in both jobs. +- The deployment job performs bounded health polling after the hook succeeds and fails + clearly on timeout or an unexpected response. +- No secrets, database data, migrations, new services, or third-party test frameworks + are added. + +## Validation + +- Workflow YAML parsing and structural assertions passed; the health polling worst case + is 292 seconds. `git diff --check` passed. +- Passed locally: `npm run lint`, `npm run typecheck`, + `dotnet format app/api --verify-no-changes --no-restore`, `npm run build`, + `npm run build:api`, and `npm test` (409 passed). +- GitHub Actions has not run on this branch yet. The live Render health check runs only + after a merge to `main`; no production deployment was triggered during local validation. \ No newline at end of file diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 42b224a..bb43b41 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1,6 +1,37 @@ # Shared handoff -## Current task: Link suggestions from closing references, and moving a link (2026-11-05) +## Current task: CI pipeline feedback and deployment availability (2026-10-30) + +- Branch: `feature/pipeline-feedback`, from `main` at `09848a9`. +- Scope: improve CI feedback, NuGet restore performance, and API deployment availability + without changing application behavior, migrations, or production secrets. +- Completed: + - `.github/workflows/ci.yml` enables setup-dotnet NuGet caching keyed by + `app/api/packages.lock.json` in `verify` and `deploy-api`; restores remain locked. + - Failed `verify` runs upload any available Playwright report and test results for + seven days. + - After a successful Render deploy-hook request, `deploy-api` polls the API's public + `/health` endpoint for up to five minutes (30 attempts, two-second request timeout, + eight-second pauses) and fails clearly if it never returns the expected liveness + response. + - `docs/CI-PIPELINE-FEEDBACK.md` records the design and acceptance criteria; this + guide records the CI behavior. +- Changed files: `.github/workflows/ci.yml`, `docs/CI-PIPELINE-FEEDBACK.md`, + `docs/TESTING.md`, and this handoff. +- Validation: workflow YAML parsed and structural assertions passed; the health poll's + calculated worst case is 292 seconds. Passed `git diff --check`, `npm run lint`, + `npm run typecheck`, `dotnet format app/api --verify-no-changes --no-restore`, + `npm run build`, `npm run build:api`, and `npm test` (409 passed). GitHub Actions has + not run on this branch yet. +- Risks and limitations: `/health` is liveness-only and may be answered by an already + healthy instance; this does not prove the requested SHA is serving. Verifying the + deployed revision would require Render deployment-status access or an API version + endpoint. The health poll runs only after a merge to `main`, not in a PR. +- Next step: open a PR and confirm the workflow passes. After merge, check that the + Render deployment reaches healthy status; compare CI restore duration after the + NuGet cache has been populated. + +## Previous task: Link suggestions from closing references, and moving a link (2026-11-05) - Branch: `feature/evidence-suggestions-and-moves`, from `main` at `031dd88` (PR #25 merged). - Scope: two stories for Danielle (Tech Lead), ADR-056: suggest links from explicit @@ -179,7 +210,6 @@ - Next step: review, then open a PR to `main` carrying both stories and the migration. ## Previous task: Criteria versions, accepted-criteria warning, follow-ups, and milestones (2026-10-29, merged as PR #23) - - Branch: `feature/requirement-change-tracking`, from `main` at `8555d75` (PR #22 merged). - Scope: four stories from the scenarios "Changing a requirement after its code was merged", "Signing off a contract milestone", and "Checking student team progress": diff --git a/docs/TESTING.md b/docs/TESTING.md index 82cbd36..9f07522 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -22,6 +22,11 @@ On Linux CI install browsers with `npx playwright install --with-deps chromium`. The GitHub Actions workflow runs these same checks. It does not need Supabase credentials. Reinstall Chromium after updating Playwright. +CI caches npm packages from `package-lock.json` and NuGet packages from +`app/api/packages.lock.json`; the API restore remains locked. If the `verify` job +fails, its Playwright HTML report and `test-results/` directory are uploaded as a +GitHub Actions artifact for seven days when present. + A second CI job, `api-image`, builds the root Dockerfile that Render deploys and runs it as Render does: Production, port 8080, no database or issuer. It requires `/health` to answer `{"status":"ok"}`, anonymous `/me` to answer 401, the OpenAPI document not to @@ -31,6 +36,9 @@ trust store. Reproduce it with `docker build --tag specthread-api:ci .` and A third job, `deploy-api`, runs only on pushes to `main` after both pass. It is not a check: it applies migrations to Supabase and deploys the API (ADR-044, docs/DEPLOYMENT.md). +After the Render deploy hook succeeds, it polls the public `/health` endpoint for up +to five minutes and requires `{"status":"ok"}`. This checks service liveness, not +that the exact requested commit is serving traffic. The test web server and schema project require a running Docker engine and postgres:17. The first webServer entry calls scripts/build-api.mjs before starting