# Master Public Release Gate 9 - Performance And Large-Data Smoke

Date: 2026-06-16

## Verdict

Gate 9 status: **Approved for the next gate.**

This gate adds local large-data performance smoke coverage for high-risk CSV import paths and list-page reads. It is not a substitute for hosted load testing against production infrastructure, but it proves the current monolith can validate, write, list, and undo realistic 500-row import batches without local warnings or failures.

## Implemented

- Added `tools/performance-smoke.php`.
- Added `docs/PERFORMANCE_SMOKE_REPORT.md`.
- Added Composer script `performance-smoke`.
- Added bounded list-read methods for production quote, invoice, and team repositories.
- Updated quote, invoice, and team index controllers to use bounded reads when available.
- Replaced expensive throwaway team-member password hashing with an explicit login-disabled marker until invite acceptance.
- Changed team CSV imports to default imported members to `invited` status.
- Added PHP regression assertions for bounded reads, team import invitation default, login-disabled team records, and the performance smoke tool.

## Initial Issue Found

The first Gate 9 smoke failed because importing 500 team members spent about 31 seconds generating bcrypt hashes for random throwaway passwords. Imported or manually added team members do not have an invite/password-acceptance flow yet, so the hash work did not grant useful access. The repository now stores a login-disabled marker that `password_verify()` rejects until a future invite/reset flow replaces it with a real password hash.

The first smoke also warned that quote, invoice, and team index-style reads returned every tenant row. Production repositories now expose bounded `listForTenant()` methods, and index controllers call those methods when present.

## Smoke Coverage

`tools/performance-smoke.php` checks:

- 500-row CSV preview for customers, jobs, quotes, invoices, and team.
- 500-row repository-backed commit for each import type.
- Bounded index-style list reads after each import.
- Undo cleanup for each import type.
- The hard 1,000-row CSV limit by confirming a 1,001-row import is rejected.
- Peak memory against a 128 MB local smoke budget.

## Verification

| Check | Result |
| --- | --- |
| `php tools/performance-smoke.php` | PASS, 500 rows per import type, 0 failures, 0 warnings, 6 MB peak memory |
| Workspace `composer performance-smoke` | PASS, 500 rows per import type, 0 failures, 0 warnings, 6 MB peak memory |
| XAMPP `composer check` | PASS, 117 routes, 211 links, 65 form/action targets, 92 buttons, 0 findings |
| XAMPP `composer performance-smoke` | PASS, 500 rows per import type, 0 failures, 0 warnings, 4 MB peak memory |
| XAMPP `tools/browser-smoke.cjs` | PASS when run serially, 174 checks, 0 failures |
| XAMPP `tools/role-walkthrough.cjs` | PASS when run serially, 141 checks, 0 failures |
| XAMPP `tools/accessibility-smoke.cjs` | PASS, 66 pages checked, 0 failures, 0 warnings |
| `docs/PERFORMANCE_SMOKE_REPORT.md` | PASS, generated with per-type preview/commit/list/undo timings |

## Current Smoke Results

| Type | Preview ms | Commit ms | List ms | Undo ms | Rows committed | Rows after undo | List rows returned |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| customers | 4.58 | 12.62 | 0.70 | 1.21 | 500 | 1 | 200 |
| jobs | 3.82 | 8.89 | 1.49 | 0.98 | 500 | 0 | 200 |
| quotes | 3.56 | 46.96 | 6.68 | 23.76 | 500 | 0 | 200 |
| invoices | 3.88 | 57.94 | 6.65 | 33.44 | 500 | 0 | 200 |
| team | 3.71 | 39.59 | 0.57 | 1.91 | 500 | 1 | 200 |

## Routes And Files Extended

- `composer.json`
- `src/Importing/Service/ImportPipelineService.php`
- `src/Invoice/Controller/InvoiceController.php`
- `src/Invoice/Repository/InvoiceRepository.php`
- `src/Quote/Controller/QuoteController.php`
- `src/Quote/Repository/QuoteRepository.php`
- `src/Team/Controller/TeamController.php`
- `src/Team/Repository/TeamMemberRepository.php`
- `tests/run.php`
- `tools/performance-smoke.php`
- `docs/PERFORMANCE_SMOKE_REPORT.md`

## Remaining After Gate 9

- Hosted load testing is still required with owner-provided infrastructure values and production-like MySQL data volume.
- Live provider activation, live payment capture, native mobile/app-store readiness, and hosted deployment evidence remain open.
