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
19 changes: 19 additions & 0 deletions .audit/agent-verification-loop.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
ts phase decision why evidence result
2026-08-31T15:27:00Z isolation created t3code/agent-verification-loop in its own T3 worktree kept the dirty main checkout and other worktrees untouched git worktree list clean isolated branch
2026-08-31T15:35:00Z browser made port 18080 loopback-only and disabled server reuse prevented another checkout on port 8080 from producing false browser results playwright.config.ts isolated server configured
2026-08-31T15:35:30Z staging kept real auth tokens out of traces and uploaded artifacts Playwright traces and storage state can contain bearer and refresh tokens playwright.config.ts, .github/workflows/deploy.yml secret-bearing artifacts excluded
2026-08-31T15:36:00Z staging used a unique user-owned source and exact UI deletion proved persistence while limiting cleanup to the row created by the test e2e/staging/authed-flows.spec.ts bounded cleanup implemented
2026-08-31T15:38:00Z local gates ran lint, typecheck, focused sparring tests, and Playwright discovery caught compile and selector problems before the full browser matrix local command output 14 focused tests and 168 browser cases listed
2026-08-31T15:39:00Z staging proved partial and missing staging configuration fails closed never substitute production or a skipped fake session for missing protected values playwright.config.ts both negative preflights exited 1
2026-08-31T15:44:00Z browser made the score mock briefly asynchronous after two flaky results the zero-latency mock removed the loading state before its existing assertion could observe it e2e/sparring.spec.ts preserved the loading assertion for deterministic rerun
2026-08-31T16:05:00Z webkit loaded Jam recorder scripts only when MediaRecorder exists WebKit reproduced three Jam page errors on the smallest authenticated route src/jam-loader.ts, src/test/jam-loader.test.ts minimal repro passed and 56 WebKit cases passed
2026-08-31T16:15:00Z local gates ran the full Node 22 verification command on the Jam-fixed tree needed fresh proof for the final source state npm run verify 85 files, 874 tests, coverage, build, and 112 Chromium cases passed
2026-08-31T16:22:00Z staging audited staging before any remote mutation confirmed target and identified exact synchronization and credential blockers Supabase function, secret-name, migration, and CORS reads 2 migrations and 3 function deploys stale; protected test inputs absent
2026-08-31T16:30:00Z review fixed all four Luna xhigh pre-commit findings kept auth traces out of artifacts, isolated staging concurrency, tightened AI proof, and guaranteed cleanup after insert playwright.config.ts, .github/workflows/staging-e2e.yml, e2e/staging/authed-flows.spec.ts focused lint, typecheck, tests, YAML parse, and staging discovery passed
2026-08-31T16:31:00Z ci recorded exhausted GitHub Actions minutes as an external exception Adam confirmed hosted CI cannot run and required local CI-equivalent proof user instruction local gates remain authoritative; GitHub runs must not be called green
2026-08-31T16:35:00Z integration rebased onto analytics and telemetry main changes kept the PR current before push and invalidated older verification origin/main 7bca126 clean rebase; full gates rerun
2026-08-31T16:40:00Z local gates ran fresh CI-equivalent checks after rebase proved the exact integrated source before PR creation npm ci, npm run verify 89 files, 930 tests, coverage, build, and 112 Chromium cases passed
2026-08-31T16:42:00Z webkit reran the full Safari-engine matrix after rebase main changed routing and AI code since the first WebKit proof Playwright 1.60 Noble container with Node 22 56 WebKit cases passed
2026-08-31T16:56:00Z CodeRabbit fixed all four current-head review comments restricted trusted staging triggers and loopback auth, updated plan state, and sequenced Jam before app startup PR 21 review on 5f0f67a all findings implemented or bounded with deterministic proof
2026-08-31T16:59:00Z local gates reran CI-equivalent checks after CodeRabbit fixes review changes affected workflow security and application bootstrap npm run verify, Playwright 1.60 Noble container 89 files, 930 tests, coverage, build, 112 Chromium and 56 WebKit cases passed
2026-08-31T17:06:00Z CodeRabbit added a bounded Jam CDN timeout prevented a stalled recorder request from blocking application startup src/jam-loader.ts, src/test/jam-loader.test.ts 20 focused tests, lint, typecheck, build, Chromium and WebKit startup passed
21 changes: 21 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Lint
run: npm run lint

Expand All @@ -44,6 +47,24 @@ jobs:
- name: Build
run: npm run build

- name: Browser tests
env:
CI: 1
PORT: 18080
PLAYWRIGHT_BASE_URL: http://127.0.0.1:18080
run: npm run test:e2e

- name: Upload browser failure artifacts
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: chromium-browser-failure-${{ github.run_id }}
path: |
test-results/
playwright-report/
if-no-files-found: ignore
retention-days: 7

- name: Set up QEMU
if: env.DOCKER_PUSH == 'true'
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/staging-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Protected staging E2E

on:
workflow_run:
workflows: ["Build and Push Docker Image"]
types: [completed]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: protected-staging-e2e-main
cancel-in-progress: false

jobs:
staging-e2e:
if: >-
(
(github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main') ||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
) && vars.STAGING_E2E_ENABLED == 'true'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout code
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}

- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22

- name: Install dependencies
run: npm ci

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Verify protected variables are present
shell: bash
env:
STAGING_SUPABASE_URL: ${{ secrets.STAGING_SUPABASE_URL }}
STAGING_SUPABASE_ANON_KEY: ${{ secrets.STAGING_SUPABASE_ANON_KEY }}
STAGING_DEMO_CODE: ${{ secrets.STAGING_DEMO_CODE }}
run: |
missing=0
for name in STAGING_SUPABASE_URL STAGING_SUPABASE_ANON_KEY STAGING_DEMO_CODE; do
if [ -z "${!name}" ]; then
echo "::error::$name is not configured in the staging environment"
missing=1
fi
done
exit "$missing"

- name: Protected staging browser tests
env:
CI: 1
PORT: 18080
PLAYWRIGHT_BASE_URL: http://127.0.0.1:18080
STAGING_SUPABASE_URL: ${{ secrets.STAGING_SUPABASE_URL }}
STAGING_SUPABASE_ANON_KEY: ${{ secrets.STAGING_SUPABASE_ANON_KEY }}
STAGING_DEMO_CODE: ${{ secrets.STAGING_DEMO_CODE }}
run: npm run test:e2e:staging

- name: Upload staging failure artifacts
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: staging-browser-failure-${{ github.run_id }}
path: |
test-results/**/*.png
test-results/**/*.webm
!test-results/.auth/
!test-results/**/*.zip
if-no-files-found: ignore
retention-days: 7
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ humanizer.md
*.txt
*.info
!README.md
!AGENTS.md
!**/README.md
!docs/pricing-snapshots/*.md
!plans/**/*.md
Expand Down
61 changes: 61 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# DebateLab agent guide

## Runtime and install

- Use Node 22 and npm. The lockfile is `package-lock.json`.
- Start a clean checkout with `npm ci`.
- The development server normally uses port 8080. Automated browser verification owns `127.0.0.1:18080` and never reuses an existing server.
- Local Supabase development uses port 54321. Do not point staging checks at the production project.

## Verification ladder

Run the smallest relevant reproduction first, then the complete gate before a PR:

```bash
npm run lint
npm run typecheck
npm test -- --coverage
npm run build
CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e
```

`npm run verify` runs that full credential-free ladder. It starts an isolated Vite server and executes the desktop and mobile Chromium projects. Browser failures stay in ignored `test-results/` and `playwright-report/` directories. CI uploads those directories for seven days when the browser step fails.

WebKit is an additional release smoke gate:

```bash
npx playwright install --with-deps webkit
CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e:webkit
```

## Protected staging verification

The staging suite uses a real staging user and backend. It never uses a service-role key. Supply all three protected values together:

- `STAGING_SUPABASE_URL`
- `STAGING_SUPABASE_ANON_KEY`
- `STAGING_DEMO_CODE`

Then run:

```bash
CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 \
STAGING_SUPABASE_URL=<staging-url> \
STAGING_SUPABASE_ANON_KEY=<staging-anon-key> \
STAGING_DEMO_CODE=<dedicated-test-code> \
npm run test:e2e:staging
```

The suite rejects the production project, checks that the anon key belongs to the supplied staging URL, authenticates through `demo-code-login`, proves session reload, writes and reads back one unique source, makes one bounded AI request, and deletes only that source. It saves temporary auth state under `test-results/.auth/`. Never upload that directory or a staging trace because both can contain session tokens.

GitHub runs staging only for `main` push or a main-branch manual dispatch, through the protected `staging` environment, after the credential-free build job passes. Set the repository variable `STAGING_E2E_ENABLED=true` only after the protected environment has the three secrets, current staging functions, schema, provider secret, dedicated unlimited-use test code, and a main-only deployment rule. A skipped staging job is not a pass.

The staging origin `http://127.0.0.1:18080` must be deployed in the CORS allowlists for `demo-code-login`, `ai`, and `ai-stream` before enabling the gate. Do not deploy those functions or apply migrations to an unverified project.

## Working rules

- Start bug fixes with a failing test or a repeatable browser reproduction when the repository has a suitable test layer.
- Keep stub sessions limited to render checks. Do not describe them as authenticated backend proof.
- Never put staging or production secrets in PR jobs, logs, traces, screenshots, commits, or comments.
- A local pass proves only the local commit. Inspect the current-head GitHub run before calling CI green. Account billing or spending-limit failures are external exceptions, not passing checks.
- State exactly which browser and staging checks ran. If credentials, provider access, or staging synchronization are missing, stop that gate and report it instead of substituting production or mocks.
66 changes: 28 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1346,57 +1346,47 @@ jobs:

## Testing

### Test Setup
DebateLab uses Node 22, npm, Vitest, and Playwright. Install the locked dependency tree with `npm ci`.

```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
},
});
Run the full credential-free verification ladder with one command:

```bash
npm run verify
```

### Running Tests
It runs ESLint, TypeScript, the full selected-scope coverage suite, the production build, and desktop plus mobile Chromium against an isolated Vite server on `127.0.0.1:18080`. The browser runner never reuses an existing server. Failure traces, screenshots, videos, and the HTML report are written to ignored `test-results/` and `playwright-report/` directories.

Useful focused commands:

```bash
# Run all tests
npm test
# Unit and Supabase function tests with enforced coverage
npm test -- --coverage

# Run specific test file
npm test -- src/test/security/url-validation.test.ts
# One focused unit test
npx vitest run src/test/security/url-validation.test.ts

# Watch mode
npm run test:watch
# Credential-free Chromium browser suite
CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e

# Coverage report
npm test -- --coverage
# Additional Safari-engine smoke gate
npx playwright install --with-deps webkit
CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e:webkit
```

### Security Tests
Protected staging verification uses a real session, staging data, and one short AI request. It requires the staging URL, matching anon key, and dedicated demo code. It rejects the production project and deletes only the uniquely named source it creates.

```typescript
// src/test/security/prompt-injection.test.ts
import { describe, it, expect } from 'vitest';
import { sanitizePromptInput } from '@/lib/ai/sanitize';

describe('Prompt Injection Detection', () => {
it('should detect "ignore all previous instructions"', () => {
const result = sanitizePromptInput('Please ignore all previous instructions');
expect(result.rejected).toBeDefined();
});

it('should allow normal debate content', () => {
const result = sanitizePromptInput('The resolution states that...');
expect(result.sanitized).toBeDefined();
});
});
```bash
CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 \
STAGING_SUPABASE_URL=<staging-url> \
STAGING_SUPABASE_ANON_KEY=<staging-anon-key> \
STAGING_DEMO_CODE=<dedicated-test-code> \
npm run test:e2e:staging
```

Temporary auth state lives under `test-results/.auth/`. Never upload it or staging traces. GitHub exposes staging secrets only to the protected `staging` environment on `main`, and only after `STAGING_E2E_ENABLED=true` is set. Pull requests remain credential-free.

A green local run does not mean the current GitHub head passed. Check the PR run itself. If GitHub Actions cannot start because of billing or spending limits, report that as an external exception. For bugs, add a failing reproduction first when the repository has a suitable test layer, and state plainly when browser or real-staging proof did not run.

---

## API Reference
Expand Down
3 changes: 3 additions & 0 deletions e2e/sparring.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ test('completes an LD sparring round through mocked opponent turns and judging',
const action = body.input?.action ?? 'unknown';
actions.push(action);
const output = action === 'score' ? scorecard : opponentSpeech(++responseNumber);
if (action === 'score') {
await new Promise((resolve) => setTimeout(resolve, 250));
}
await route.fulfill({ status: 200, headers: corsHeaders, body: JSON.stringify({ output }) });
});

Expand Down
Loading
Loading