@@ -2,13 +2,44 @@ name: CI
22
33on :
44 push :
5- branches : [main, "feat/**", "fix/**"]
5+ branches :
6+ - main
67 pull_request :
8+ branches :
9+ - main
710
8- env :
9- CARGO_TERM_COLOR : always
11+ permissions :
12+ contents : read
1013
1114jobs :
15+ # ── Frontend quality gate ─────────────────────────────────────────────────
16+ frontend :
17+ name : Frontend (lint → typecheck → build)
18+ runs-on : ubuntu-latest
19+ defaults :
20+ run :
21+ working-directory : frontend
22+ steps :
23+ - uses : actions/checkout@v4
24+
25+ - uses : actions/setup-node@v4
26+ with :
27+ node-version-file : .nvmrc
28+ cache : npm
29+ cache-dependency-path : frontend/package-lock.json
30+
31+ - name : Install dependencies
32+ run : npm ci
33+
34+ - name : Lint (fail on warnings)
35+ run : npm run lint -- --max-warnings=0
36+
37+ - name : Typecheck
38+ run : npm run typecheck
39+
40+ - name : Build
41+ run : npm run build
42+
1243 # ── Soroban ABI golden-vector drift guard ────────────────────────────────
1344 golden-vectors :
1445 name : Soroban ABI golden vectors
@@ -46,58 +77,73 @@ jobs:
4677 contract :
4778 name : Contract (Rust / Soroban)
4879 runs-on : ubuntu-latest
49- env :
50- RUSTFLAGS : " -D warnings"
5180 steps :
5281 - uses : actions/checkout@v4
53-
54- - uses : dtolnay/rust-toolchain@stable
55- with :
56- targets : wasm32-unknown-unknown
57- components : rustfmt, clippy
58-
59- - uses : actions/cache@v4
82+ - uses : actions/setup-node@v4
6083 with :
61- path : |
62- ~/.cargo/registry
63- ~/.cargo/git
64- target
65- key : ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
66-
67- - run : cargo fmt --all -- --check
68- - run : cargo clippy --target wasm32-unknown-unknown --release -- -D warnings
69- - run : cargo test
70- - run : cargo build --target wasm32-unknown-unknown --release
71-
72- - name : Record wasm SHA-256
73- run : sha256sum target/wasm32-unknown-unknown/release/niffyinsure.wasm | tee niffyinsure.wasm.sha256
74-
75- - name : Simulate wasm drift (staging acceptance test)
84+ node-version : ' 20'
85+ - name : Determine package manager
86+ id : pkgmgr
7687 run : |
77- ACTUAL=$(cat niffyinsure.wasm.sha256 | awk '{print $1}')
78- EXPECTED=$(jq -r '.contracts[0].expectedWasmHash' contracts/deployment-registry.json)
79- # In CI the registry holds a placeholder; drift is detected when they differ.
80- # In staging, set NIFFYINSURE_EXPECTED_WASM_HASH to a known-wrong value to
81- # verify the alert path fires. Exit 0 here — alerting is runtime, not build-time.
82- if [ "$EXPECTED" = "\${NIFFYINSURE_EXPECTED_WASM_HASH}" ]; then
83- echo "Registry uses env placeholder — skipping drift comparison in CI"
84- elif [ "$ACTUAL" != "$EXPECTED" ]; then
85- echo "::warning::Wasm drift detected: expected=$EXPECTED actual=$ACTUAL"
88+ if [ -f pnpm-lock.yaml ]; then
89+ echo "manager=pnpm" >> "$GITHUB_OUTPUT"
90+ elif [ -f package-lock.json ]; then
91+ echo "manager=npm" >> "$GITHUB_OUTPUT"
92+ elif [ -f yarn.lock ]; then
93+ echo "manager=yarn" >> "$GITHUB_OUTPUT"
8694 else
87- echo "Wasm hash matches registry: $ACTUAL "
95+ echo "manager=npm" >> "$GITHUB_OUTPUT "
8896 fi
89-
90- - uses : actions/upload-artifact@v4
97+ - name : Setup pnpm when needed
98+ if : steps.pkgmgr.outputs.manager == 'pnpm'
99+ uses : pnpm/action-setup@v2
100+ with :
101+ version : 8
102+ - name : Cache node modules
103+ uses : actions/cache@v4
91104 with :
92- name : niffyinsure-wasm-${{ github.sha }}
93105 path : |
94- target/wasm32-unknown-unknown/release/niffyinsure.wasm
95- niffyinsure.wasm.sha256
96- retention-days : 30
106+ node_modules
107+ ~/.pnpm-store
108+ key : ${{ runner.os }}-node-${{ steps.pkgmgr.outputs.manager }}-${{ hashFiles('**/package-lock.json', '**/pnpm-lock.yaml', '**/yarn.lock') }}
109+ restore-keys : ${{ runner.os }}-node-${{ steps.pkgmgr.outputs.manager }}-
110+ - name : Cache .next cache
111+ uses : actions/cache@v4
112+ with :
113+ path : .next/cache
114+ key : ${{ runner.os }}-next-cache-${{ hashFiles('**/package-lock.json', '**/pnpm-lock.yaml', '**/yarn.lock') }}
115+ restore-keys : ${{ runner.os }}-next-cache-
116+ - name : Install dependencies
117+ run : |
118+ if [ "${{ steps.pkgmgr.outputs.manager }}" = "pnpm" ]; then
119+ pnpm install --frozen-lockfile
120+ else
121+ npm ci
122+ fi
123+ - name : Lint (fail on warnings)
124+ run : |
125+ if [ "${{ steps.pkgmgr.outputs.manager }}" = "pnpm" ]; then
126+ pnpm eslint --max-warnings=0 .
127+ else
128+ npm run lint -- --max-warnings=0
129+ fi
130+ - name : TypeScript compile
131+ run : |
132+ if [ "${{ steps.pkgmgr.outputs.manager }}" = "pnpm" ]; then
133+ pnpm tsc --noEmit
134+ else
135+ npm run build --if-present -- --noEmit
136+ fi
137+ - name : Build
138+ run : |
139+ if [ "${{ steps.pkgmgr.outputs.manager }}" = "pnpm" ]; then
140+ pnpm build
141+ else
142+ npm run build
143+ fi
97144
98- # ── Backend ───────────────────────────────────────────────────────────────
99- backend :
100- name : Backend (Node / TypeScript)
145+ unit-tests :
146+ name : Unit tests
101147 runs-on : ubuntu-latest
102148 defaults :
103149 run :
@@ -121,60 +167,35 @@ jobs:
121167
122168 - uses : actions/setup-node@v4
123169 with :
124- node-version : 22
125-
126- - run : npm install
127- - run : npm run lint
128- - run : npm run build
129- - run : npm test
130-
131- # ── Dependency audit / supply-chain ─────────────────────────────────────
132- # Policy: CRITICAL CVEs fail the build. HIGH CVEs produce a warning and
133- # must be triaged within 7 days. Accepted risks require a signed-off entry
134- # in docs/ops/audit-exceptions.md before the override label is applied.
135- # Override process:
136- # 1. Engineer opens a PR adding the CVE to audit-exceptions.md with
137- # justification, mitigations, and a review-by date.
138- # 2. A second engineer approves the PR.
139- # 3. Add the GitHub label `audit-exception-approved` to the failing PR.
140- # 4. Re-run this job — it will pass once the exception is documented.
141- dependency-audit :
142- name : Dependency Audit (npm / SBOM)
170+ node-version : ' 20'
171+ - name : Install dependencies
172+ run : npm ci
173+ - name : Run unit tests
174+ run : npm test
175+
176+ e2e-tests :
177+ name : Playwright E2E tests
143178 runs-on : ubuntu-latest
179+ needs : frontend
144180 steps :
145181 - uses : actions/checkout@v4
146-
147182 - uses : actions/setup-node@v4
148183 with :
149- node-version : 22
150-
151- - name : Audit backend dependencies
152- working-directory : backend
153- run : |
154- npm install --ignore-scripts
155- # Fail on critical; warn on high (exit 0 so we can capture output)
156- npm audit --audit-level=critical
157- npm audit --audit-level=high || echo "::warning::High-severity advisories found — triage within 7 days per audit policy"
158-
159- - name : Audit frontend dependencies
160- working-directory : frontend
161- run : |
162- npm ci --ignore-scripts
163- npm audit --audit-level=critical
164- npm audit --audit-level=high || echo "::warning::High-severity advisories found — triage within 7 days per audit policy"
165-
166- - name : Generate SBOM (backend)
167- working-directory : backend
168- run : npx --yes @cyclonedx/cyclonedx-npm --output-format JSON --output-file ../sbom-backend.json
169-
170- - name : Generate SBOM (frontend)
171- working-directory : frontend
172- run : npx --yes @cyclonedx/cyclonedx-npm --output-format JSON --output-file ../sbom-frontend.json
173-
174- - uses : actions/upload-artifact@v4
184+ node-version : ' 20'
185+ - name : Install dependencies
186+ run : npm ci
187+ - name : Install Playwright browsers
188+ run : npx playwright install --with-deps
189+ - name : Run Playwright tests
190+ run : npx playwright test --reporter=html
191+ continue-on-error : true
192+ - name : Upload Playwright artifacts on failure
193+ if : failure()
194+ uses : actions/upload-artifact@v3
175195 with :
176- name : sbom- ${{ github.sha }}
196+ name : playwright-failure- ${{ github.run_id }}
177197 path : |
198+ feat/accessibility-audit
178199 sbom-backend.json
179200 sbom-frontend.json
180201 retention-days : 90
@@ -201,37 +222,13 @@ jobs:
201222 - run : npm run build
202223 - run : npm test
203224
204- # ── Playwright e2e ────────────────────────────────────────────────────────
205- # Runs headless Chromium tests against a production build of the frontend.
206- # Traces and screenshots are uploaded as artifacts on failure so engineers
207- # can debug within minutes without reproducing locally.
208- #
209- # Secrets: no testnet accounts are used — all API and wallet calls are
210- # mocked at the network layer via Playwright route interception.
211- #
212- # Flake policy: tests retry up to 2 times (configured in playwright.config.ts).
213- # Persistent flakes must be quarantined in e2e/quarantine/ and tracked in a
214- # GitHub issue within 48 hours of detection.
215- playwright :
216- name : Playwright e2e
225+ # ── Accessibility (axe) ───────────────────────────────────────────────────
226+ accessibility :
227+ name : Accessibility (axe / Playwright)
217228 runs-on : ubuntu-latest
218- needs : frontend
219229 defaults :
220230 run :
221231 working-directory : frontend
222- env :
223- CI : true
224- # Analytics disabled in CI — no real Plausible domain configured
225- NEXT_PUBLIC_ANALYTICS_ENABLED : " false"
226- NEXT_PUBLIC_API_URL : " http://localhost:3001"
227- NEXT_PUBLIC_SOROBAN_RPC_URL : " https://soroban-testnet.stellar.org"
228- NEXT_PUBLIC_HORIZON_URL : " https://horizon-testnet.stellar.org"
229- NEXT_PUBLIC_NETWORK : " testnet"
230- NEXT_PUBLIC_CONTRACT_ID : " mock-contract-id"
231- NEXT_PUBLIC_IPFS_GATEWAY : " https://ipfs.io/ipfs"
232- NEXT_PUBLIC_CAPTCHA_SITE_KEY : " "
233- NEXT_PUBLIC_CAPTCHA_PROVIDER : " turnstile"
234- PLAYWRIGHT_BASE_URL : " http://localhost:3000"
235232 steps :
236233 - uses : actions/checkout@v4
237234
@@ -242,29 +239,25 @@ jobs:
242239 cache-dependency-path : frontend/package-lock.json
243240
244241 - run : npm ci
242+ - run : npm run build
245243
246244 - name : Install Playwright browsers
247245 run : npx playwright install --with-deps chromium
248246
249- - name : Build Next.js app
250- run : npm run build
251-
252- - name : Start Next.js preview server
253- run : npx next start --port 3000 &
254- # Give the server a moment to start; Playwright will wait for baseURL
255-
256- - name : Wait for server
257- run : npx wait-on http://localhost:3000 --timeout 30000
258-
259- - name : Run Playwright tests
260- run : npx playwright test
247+ - name : Run axe accessibility checks
248+ run : npx playwright test tests/accessibility.spec.ts --reporter=list
249+ env :
250+ BASE_URL : http://localhost:3000
261251
262- - name : Upload Playwright traces and screenshots
252+ - uses : actions/upload-artifact@v4
263253 if : failure()
264- uses : actions/upload-artifact@v4
265254 with :
266- name : playwright-artifacts-${{ github.sha }}
267- path : |
268- frontend/playwright-report/
269- frontend/test-results/
270- retention-days : 7
255+ name : axe-report-${{ github.sha }}
256+ path : frontend/playwright-report/
257+ retention-days : 14
258+
259+ test-results
260+ playwright-report
261+ traces
262+ .playwright/traces
263+
0 commit comments