# Parity checks between the React frontend and standalone_interface.html, # plus the demo-scenario replay suite (config_small_grid coverage). # # See scripts/PARITY_README.md for the four-layer parity design and # scripts/parity_e2e/DEMO_REPLAY_README.md for the demo-replay layers. # Summary: # Layer 1 — static inventory (events, API paths, settings, schema drift) # Layer 2 — session-reload fidelity (save-vs-restore symmetry) # Layer 3a — gesture-sequence static proxy (ordered event emission) # Layer 3b — behavioural E2E + demo-replay (real DOM, mocked backend): # * `e2e_parity.spec.ts` — React ↔ standalone parity # * `demo_replay.spec.ts` — fiche-as-data scenario walker # * `demo_visual_snapshots.spec.ts` — normalised SVG/HTML diffs # * `demo_meta_invariants.spec.ts` — console errors, empty # text, pin-count sanity # Layer 4 — user-observable invariants (static source patterns) # # Layers 1 + 2 + 3a + 4 run on every PR (fast, deterministic, no browser). # The cheap `demo_meta_invariants` slice runs on every PR too (~1 min # Chromium install + ~20 s test) — it catches console errors and # undefined-id leaks that no static pattern can see. # The full Layer 3b suite (parity + replay + snapshots) runs nightly + # on PRs labelled `e2e`. ~3 min per run. name: Parity on: push: branches: [main] # Run on every PR regardless of its base branch (e.g. PRs stacked onto a # `claude/*` working branch, not just those targeting main). pull_request: branches: ['**'] schedule: # Nightly at 02:30 UTC — catches drift introduced between PRs # (e.g. docs/features/interaction-logging.md updates without a code change). - cron: '30 2 * * *' # Always allow a re-run to supersede an in-flight one for the same PR. concurrency: group: parity-${{ github.ref }} cancel-in-progress: true jobs: # ------------------------------------------------------------------- # Layer 1 — static inventory parity (events, API paths, settings, # three-way spec diff). No backend, no browser. # ------------------------------------------------------------------- layer1-static-inventory: name: 'Layer 1 · static inventory parity' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' # The parity check needs an up-to-date standalone bundle to # diff against. `frontend/dist-standalone/` is gitignored, so # in CI we have to (re)build it from source — otherwise the # script falls back to the frozen `standalone_interface_legacy.html` # which lags behind the React source by design. - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Build standalone bundle working-directory: frontend run: | npm ci npm run build:standalone - name: Run parity check run: python scripts/check_standalone_parity.py - name: Emit Markdown summary if: always() run: | python scripts/check_standalone_parity.py --emit-markdown \ >> "$GITHUB_STEP_SUMMARY" || true # ------------------------------------------------------------------- # Layer 2 — session-reload fidelity (save/restore symmetry). # ------------------------------------------------------------------- layer2-session-fidelity: name: 'Layer 2 · session-reload fidelity' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Build standalone bundle working-directory: frontend run: | npm ci npm run build:standalone - name: Run session-fidelity check run: python scripts/check_session_fidelity.py # ------------------------------------------------------------------- # Layer 3a — gesture-sequence static proxy. Lightweight sequence- # aware check; complements Layer 1's set-based diff. # ------------------------------------------------------------------- layer3a-gesture-sequence: name: 'Layer 3a · gesture-sequence static proxy' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Build standalone bundle working-directory: frontend run: | npm ci npm run build:standalone - name: Run gesture-sequence check run: python scripts/check_gesture_sequence.py # ------------------------------------------------------------------- # Layer 4 — user-observable invariants. Static source-pattern # assertions for the six bug classes Layers 1-3a cannot catch by # construction (visual thresholds, conditional rendering, # field semantics, auto-effects, loading-state, performance). # ------------------------------------------------------------------- layer4-invariants: name: 'Layer 4 · user-observable invariants' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Build standalone bundle working-directory: frontend run: | npm ci npm run build:standalone - name: Run invariants check run: python scripts/check_invariants.py # ------------------------------------------------------------------- # Demo meta-invariants — cheap Playwright slice that catches console # errors, empty visible text, undefined-id leaks and pin-count # inconsistencies on the small_grid demo flow. Runs on every PR # because the signal-to-cost ratio is high (~1 min total: Chromium # install + ~20 s test). The full Layer 3b suite still gates on # the `e2e` label / nightly schedule. # ------------------------------------------------------------------- demo-meta-invariants: name: 'Demo · meta-invariants (Playwright, fast)' runs-on: ubuntu-latest timeout-minutes: 6 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: | frontend/package-lock.json scripts/parity_e2e/package-lock.json - name: Install frontend deps + build working-directory: frontend run: | npm ci npm run build - name: Install E2E deps working-directory: scripts/parity_e2e run: npm ci - name: Install Playwright Chromium working-directory: scripts/parity_e2e run: npx playwright install --with-deps chromium - name: Run demo meta-invariants only working-directory: scripts/parity_e2e run: npx playwright test demo_meta_invariants.spec.ts - name: Upload Playwright report on failure if: failure() uses: actions/upload-artifact@v4 with: name: playwright-demo-meta-report path: scripts/parity_e2e/playwright-report/ if-no-files-found: ignore retention-days: 7 # ------------------------------------------------------------------- # Layer 3b — behavioural E2E + demo-replay full suite with # Playwright. Runs every spec matched by `playwright.config.ts`'s # testMatch (`*parity.spec.ts` + `demo_*.spec.ts`): the React ↔ # standalone parity check, the fiche-as-data scenario walker, the # normalised SVG/HTML snapshot diffs, and the meta-invariants # battery. Browser install + 4 specs ≈ 3 min per run, so this job # only fires nightly or on PRs carrying the `e2e` label. # ------------------------------------------------------------------- layer3b-behavioural-e2e: name: 'Layer 3b · behavioural E2E + demo replay (Playwright)' runs-on: ubuntu-latest if: | github.event_name == 'schedule' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'e2e')) timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' cache-dependency-path: | frontend/package-lock.json scripts/parity_e2e/package-lock.json - name: Install frontend deps + build working-directory: frontend run: | npm ci npm run build - name: Install E2E deps working-directory: scripts/parity_e2e run: npm ci - name: Install Playwright browsers working-directory: scripts/parity_e2e run: npx playwright install --with-deps chromium - name: Run E2E parity spec working-directory: scripts/parity_e2e run: npx playwright test - name: Upload Playwright report + artefacts if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: | scripts/parity_e2e/playwright-report/ scripts/parity_e2e/artefacts.json if-no-files-found: ignore retention-days: 14