---
phase: 09-testing-qa
plan: 03
type: execute
wave: 2
depends_on: ["09-01"]
files_modified:
  - .gitea/workflows/ci.yml
autonomous: false
requirements:
  - QA-07

must_haves:
  truths:
    - "CI pipeline YAML exists and is syntactically valid for Gitea Actions"
    - "Pipeline stages enforce fail-fast: lint/type-check block unit tests, unit tests block E2E"
    - "Pipeline includes backend tests (lint, type-check, pytest) and portal tests (build, E2E, Lighthouse)"
    - "Test reports (JUnit XML, HTML) are uploaded as artifacts"
  artifacts:
    - path: ".gitea/workflows/ci.yml"
      provides: "Complete CI pipeline for Gitea Actions"
      contains: "playwright test"
  key_links:
    - from: ".gitea/workflows/ci.yml"
      to: "packages/portal/playwright.config.ts"
      via: "npx playwright test command"
      pattern: "playwright test"
    - from: ".gitea/workflows/ci.yml"
      to: "packages/portal/e2e/lighthouse/lighthouserc.json"
      via: "npx lhci autorun --config"
      pattern: "lhci autorun"
---

<objective>
Create the Gitea Actions CI pipeline that runs the full test suite (backend lint + type-check + pytest, portal build + E2E + Lighthouse) on every push and PR to main.

Purpose: Makes the test suite CI-ready so quality gates are enforced automatically, not just locally. Completes the beta-readiness quality infrastructure.

Output: .gitea/workflows/ci.yml with fail-fast stages and artifact uploads.
</objective>

<execution_context>
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/09-testing-qa/09-CONTEXT.md
@.planning/phases/09-testing-qa/09-RESEARCH.md
@.planning/phases/09-testing-qa/09-01-SUMMARY.md

Depends on Plan 01 for: Playwright config and test files that CI will execute
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create Gitea Actions CI workflow</name>
  <files>
    .gitea/workflows/ci.yml
  </files>
  <action>
Create `.gitea/workflows/ci.yml` based on RESEARCH Pattern 7 with these specifics:

1. Triggers: push to main, pull_request to main

2. Job 1: `backend` (Backend Tests)
   - runs-on: ubuntu-latest
   - Service containers:
     - postgres: pgvector/pgvector:pg16, env POSTGRES_DB/USER/PASSWORD, health-cmd pg_isready
     - redis: redis:7-alpine, health-cmd "redis-cli ping"
   - Env vars: DATABASE_URL (asyncpg to konstruct_app), DATABASE_ADMIN_URL (asyncpg to postgres), REDIS_URL
   - Steps:
     - actions/checkout@v4
     - actions/setup-python@v5 python-version 3.12
     - pip install uv
     - uv sync
     - uv run ruff check packages/ tests/
     - uv run ruff format --check packages/ tests/
     - uv run pytest tests/ -x --tb=short --junitxml=test-results.xml
   - Upload test-results.xml as artifact (if: always())

3. Job 2: `portal` (Portal E2E) -- needs: backend
   - runs-on: ubuntu-latest
   - Service containers: same postgres + redis
   - Steps:
     - actions/checkout@v4
     - actions/setup-node@v4 node-version 22
     - actions/setup-python@v5 python-version 3.12 (for gateway)
     - Install portal deps: `cd packages/portal && npm ci`
     - Build portal: `cd packages/portal && npm run build` with NEXT_PUBLIC_API_URL env
     - Copy standalone assets: `cd packages/portal && cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public`
     - Install Playwright browsers: `cd packages/portal && npx playwright install --with-deps chromium firefox webkit`
     - Start gateway (background):
       ```
       pip install uv && uv sync
       uv run alembic upgrade head
       uv run python -c "from shared.db import seed_admin; import asyncio; asyncio.run(seed_admin())" || true
       uv run uvicorn gateway.main:app --host 0.0.0.0 --port 8001 &
       ```
       env: DATABASE_URL, DATABASE_ADMIN_URL, REDIS_URL, LLM_POOL_URL (http://localhost:8004)
     - Wait for gateway: `timeout 30 bash -c 'until curl -sf http://localhost:8001/health; do sleep 1; done'`
     - Run E2E tests: `cd packages/portal && npx playwright test e2e/flows/ e2e/accessibility/`
       env: CI=true, PLAYWRIGHT_BASE_URL, API_URL, AUTH_SECRET, E2E_ADMIN_EMAIL, E2E_ADMIN_PASSWORD, E2E_CADMIN_EMAIL, E2E_CADMIN_PASSWORD, E2E_OPERATOR_EMAIL, E2E_OPERATOR_PASSWORD
       (Use secrets for credentials: ${{ secrets.E2E_ADMIN_EMAIL }} etc.)
     - Run Lighthouse CI: `cd packages/portal && npx lhci autorun --config=e2e/lighthouse/lighthouserc.json`
       env: LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }}
     - Upload Playwright report (if: always()): actions/upload-artifact@v4, path packages/portal/playwright-report/
     - Upload Playwright JUnit (if: always()): actions/upload-artifact@v4, path packages/portal/playwright-results.xml
     - Upload Lighthouse report (if: always()): actions/upload-artifact@v4, path packages/portal/.lighthouseci/

IMPORTANT: Do NOT include mypy --strict step (existing codebase may not be fully strict-typed). Only include ruff check and ruff format --check for linting.

NOTE: The seed_admin call may not exist -- include `|| true` so it doesn't block. The E2E auth setup creates test users via the login form, so the admin user must already exist in the database. If there's a migration seed, it will handle this.

Pipeline target: < 5 minutes total.
  </action>
  <verify>
    <automated>test -f /home/adelorenzo/repos/konstruct/.gitea/workflows/ci.yml && python3 -c "import yaml; yaml.safe_load(open('/home/adelorenzo/repos/konstruct/.gitea/workflows/ci.yml'))" && echo "VALID YAML"</automated>
  </verify>
  <done>CI pipeline YAML exists at .gitea/workflows/ci.yml, is valid YAML, has 2 jobs (backend + portal), portal depends on backend (fail-fast), includes lint/format/pytest/E2E/Lighthouse/artifact-upload steps</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 2: Verify test suite and CI pipeline</name>
  <what-built>
Complete E2E test suite (7 flow specs + accessibility + visual regression + Lighthouse CI) and Gitea Actions CI pipeline. Tests cover login, tenant CRUD, agent deployment, chat with mocked WebSocket, RBAC enforcement, i18n language switching, mobile viewport behavior, accessibility (axe-core), and visual regression at 3 viewports.
  </what-built>
  <how-to-verify>
1. Run the full E2E test suite locally:
   ```
   cd packages/portal
   npx playwright test --project=chromium --reporter=list
   ```
   Expected: All flow tests + accessibility tests pass

2. Run cross-browser:
   ```
   npx playwright test e2e/flows/ --reporter=list
   ```
   Expected: All tests pass on chromium, firefox, webkit

3. Check the Playwright HTML report:
   ```
   npx playwright show-report
   ```
   Expected: Opens browser with detailed test results

4. Review the CI pipeline:
   ```
   cat .gitea/workflows/ci.yml
   ```
   Expected: Valid YAML with backend job (lint + pytest) and portal job (build + E2E + Lighthouse), portal depends on backend

5. (Optional) Push a branch to trigger CI on git.oe74.net and verify pipeline runs
  </how-to-verify>
  <resume-signal>Type "approved" if tests pass and CI pipeline looks correct, or describe issues</resume-signal>
</task>

</tasks>

<verification>
1. `.gitea/workflows/ci.yml` exists and is valid YAML
2. Pipeline has 2 jobs: backend (lint + pytest) and portal (build + E2E + Lighthouse)
3. Portal job depends on backend job (fail-fast enforced)
4. Secrets referenced for credentials (not hardcoded)
5. Artifacts uploaded for test reports
</verification>

<success_criteria>
- CI pipeline YAML is syntactically valid
- Pipeline stages enforce fail-fast ordering
- Backend job: ruff check + ruff format --check + pytest
- Portal job: npm build + Playwright E2E + Lighthouse CI
- Test reports uploaded as artifacts (JUnit XML, HTML, Lighthouse)
- Human approves test suite and pipeline structure
</success_criteria>

<output>
After completion, create `.planning/phases/09-testing-qa/09-03-SUMMARY.md`
</output>
