Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b32ee1a
test(nova): add Playwright E2E suite for governed chat flow (QA.4)
Shaivpidadi Apr 18, 2026
a788efb
ci: add GitHub Actions e2e job with Playwright and pnpm test:e2e
Shaivpidadi Apr 18, 2026
f4fb801
ci: replace local-only e2e workflow with staging-aware CI — warmup + …
Shaivpidadi Apr 18, 2026
409cc3e
fix: exclude playwright config from Next.js TypeScript compilation
Shaivpidadi Apr 19, 2026
8fec977
fix: pnpm lock files
Shaivpidadi Apr 19, 2026
c821bcb
fix: guard GOVERNSAI_ISSUER against invalid placeholder URLs at build…
Shaivpidadi Apr 19, 2026
dd26f54
fix: replace NextAuth middleware wrapper with lightweight getToken JW…
Shaivpidadi Apr 19, 2026
1efb8b5
Merge pull request #4 from Governs-AI/feat/nova-qa4-e2e-governed-chat
Shaivpidadi Apr 19, 2026
b1353a7
test(dl-6): E2E — decision row appears in dashboard after chat messag…
Shaivpidadi Apr 20, 2026
5a41b82
fix(ci): prevent E2E warm-up from hanging on Render cold start (#7)
Shaivpidadi Apr 20, 2026
d1d6b84
feat(licensing): Add MIT LICENSE file and license field (#6)
Shaivpidadi Apr 20, 2026
2be9538
fix: LICENSE copyright year 2024 -> 2026
Shaivpidadi Apr 20, 2026
542f2e2
test(e2e): QA.4 — governed chat flow (login, PII, deny, audit log) (#9)
Shaivpidadi Apr 23, 2026
ae876d5
chore(deps): bump @governs-ai/sdk alpha.12 -> alpha.14 (#8)
Shaivpidadi Apr 23, 2026
5c12ff1
chore(sdk): bump @governs-ai/sdk alpha.12 -> alpha.14 (#10)
Shaivpidadi Apr 23, 2026
3803f8a
test(e2e): QA.4 tool-policy violation UI indicator (#14)
Shaivpidadi Apr 23, 2026
7bd073b
test(nova): stabilize staging E2E against Render cold-starts (GOV-592…
Shaivpidadi Apr 23, 2026
ab9f79e
chore(changelog): scaffold CHANGELOG.md for Arbiter releases (#11)
Shaivpidadi Apr 23, 2026
1bbf398
test(nova): address Nexus review of QA.4 PII → decision-log spec (GOV…
Shaivpidadi Apr 24, 2026
416e459
Merge remote-tracking branch 'origin/main' into dev
Shaivpidadi May 12, 2026
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
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on:
push:
branches: [dev, main, 'feat/**']
pull_request:
branches: [dev, main]

jobs:
lint-typecheck:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Type check
run: pnpm tsc --noEmit

- name: Lint
run: pnpm lint
84 changes: 84 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: E2E Tests

on:
push:
branches: [dev]
workflow_dispatch:

jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30

env:
E2E_USERNAME: ${{ secrets.E2E_USERNAME }}
E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}
E2E_CHAT_URL: ${{ secrets.STAGING_CHAT_URL }}
GOVERNSAI_ISSUER: ${{ secrets.STAGING_KEYCLOAK_URL }}/realms/governs-ai
GOVERNSAI_CLIENT_ID: governs-chat
GOVERNSAI_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CHAT_CLIENT_SECRET }}
PRECHECK_URL: ${{ secrets.STAGING_PRECHECK_URL }}
PLATFORM_URL: ${{ secrets.STAGING_PLATFORM_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
NEXTAUTH_URL: ${{ secrets.STAGING_CHAT_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Install Playwright browsers
run: pnpm dlx playwright install chromium --with-deps

- name: Warm up staging services
run: |
echo "Waking up Render free-tier services..."

# Warmup treats ANY HTTP response as "service is awake" — we care that the
# Render container has exited cold-start, not that a specific route returns
# 2xx. That's what the Playwright specs are for. Using -f (fail-on-http-error)
# made this step flap whenever /login returned a 3xx/4xx on a warm container.
warmup() {
local name="$1"
local url="$2"
local max_attempts=20
echo "Pinging $name at $url"
for i in $(seq 1 $max_attempts); do
local code
code=$(curl -sSL -o /dev/null -w "%{http_code}" \
--max-time 60 --connect-timeout 10 "$url" || echo "000")
if [ "$code" != "000" ] && [ "$code" != "502" ] && [ "$code" != "503" ] && [ "$code" != "504" ]; then
echo " ✓ $name is up (attempt $i, HTTP $code)"
return 0
fi
echo " $name waiting... ($i/$max_attempts, HTTP $code)"
sleep 15
done
echo " ✗ $name failed to wake up after $max_attempts attempts"
return 1
}

warmup precheck "${{ secrets.STAGING_PRECHECK_URL }}/api/v1/health"
warmup keycloak "${{ secrets.STAGING_KEYCLOAK_URL }}/realms/governs-ai/.well-known/openid-configuration"
warmup chat "${{ secrets.STAGING_CHAT_URL }}/"

- name: Run E2E tests
run: pnpm test:e2e

- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: |
playwright-report/
test-results/
retention-days: 7
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,9 @@ temp/

# Turbo
.turbo

# Playwright
/test-results/
/playwright-report/
/playwright/.cache/

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

All notable changes to this project are documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 GovernsAI

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
{
"name": "governsai-demo-chat",
"version": "0.1.0",
"license": "MIT",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
"@governs-ai/sdk": "1.0.0-alpha.12",
"@governs-ai/sdk": "1.0.0-alpha.14",
"@mendable/firecrawl-js": "^4.3.6",
"@simplewebauthn/browser": "^13.2.0",
"autoprefixer": "^10.4.21",
Expand All @@ -22,6 +25,7 @@
"uuid": "^10.0.0"
},
"devDependencies": {
"@playwright/test": "^1.46.0",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand All @@ -33,4 +37,4 @@
"typescript": "^5.5.3"
},
"packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"
}
}
33 changes: 33 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineConfig, devices } from '@playwright/test';

// BASE_URL takes precedence for CI/CD environments; E2E_CHAT_URL is the legacy override.
// Falls back to the known staging deployment so tests can run without local services.
const CHAT_URL =
process.env.BASE_URL ||
process.env.E2E_CHAT_URL ||
'http://localhost:3004';

export default defineConfig({
testDir: './tests/e2e',
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: 1,
reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
timeout: 120_000,
expect: { timeout: 15_000 },
use: {
baseURL: CHAT_URL,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 15_000,
navigationTimeout: 60_000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
48 changes: 43 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/app/advanced-demo/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export default function AdvancedDemoPage() {
{scenario.expected}
</span>
</div>
<p className="mt-2 text-sm text-slate-300">"{scenario.prompt}"</p>
<p className="mt-2 text-sm text-slate-300">&quot;{scenario.prompt}&quot;</p>
<p className="mt-2 text-xs text-slate-400">{scenario.why}</p>
</div>
))}
Expand Down Expand Up @@ -151,7 +151,7 @@ async function runGovernedCall(userId: string, rawPrompt: string) {
corr_id: corrId,
});

if (pre.decision === "block" || pre.decision === "deny") {
if (pre.decision === "deny") {
return { status: "blocked", reasons: pre.reasons, corrId };
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async function executeToolCall(
writer.writeDecision(precheckResponse.decision, precheckResponse.reasons);

// Handle precheck decision
if (precheckResponse.decision === 'block' || precheckResponse.decision === 'deny') {
if (precheckResponse.decision === 'deny') {
console.log(`❌ TOOL CALL BLOCKED: ${toolCall.function.name}`);

// Clean up the error message to be more user-friendly
Expand Down Expand Up @@ -332,7 +332,7 @@ export async function POST(request: NextRequest) {
writer.writeDecision(precheckResponse.decision, precheckResponse.reasons);

// Step 2: Handle precheck decision
if (precheckResponse.decision === 'block' || precheckResponse.decision === 'deny') {
if (precheckResponse.decision === 'deny') {
console.log('❌ REQUEST BLOCKED BY PRECHECK');
writer.writeError(
`Request blocked: ${precheckResponse.reasons?.join(', ') || 'Policy violation'}`
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/mcp/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function POST(request: NextRequest) {
const precheckResponse = await precheck(precheckRequest, userId);

// Step 2: Handle precheck decision
if (precheckResponse.decision === 'block') {
if (precheckResponse.decision === 'deny') {
return Response.json({
success: false,
error: 'MCP call blocked by policy',
Expand Down
Loading
Loading