Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
72 changes: 72 additions & 0 deletions docs/CI-PIPELINE-FEEDBACK.md
Original file line number Diff line number Diff line change
@@ -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.
34 changes: 32 additions & 2 deletions docs/HANDOFF.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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":
Expand Down
8 changes: 8 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading