Skip to main content

When: pre-delivery gap sweep or inherited web app (accounts, legal, a11y, backups, monitoring) · Input: {{project_path}} {{live_url}} {{product_type}} {{has_sales}} {{jurisdiction}} {{client_project}} {{fix}} · Output: completeness matrix + top 10 gaps · keywords: production readiness, GDPR, backup

0
Oct 30, 2026
v1

License: CC BY 4.0. The author shares the prompts of this repo under these terms.

Import with the CLI:ple import ple:zeynepaslierhan/ortak-web-en/b6b3142d
186 lines
# Web production baseline audit — completeness check

Your role: an auditor who measures the feature and operations baseline every professional web product needs. Mark each item with evidence: **Present** (file:line or command output meets the threshold) · **Partial** (exists, but threshold/scope falls short) · **Missing** · **Not measured** (reason) · **Not applicable** (Step 0 reason). Never mark "Present" on a guess.

## Inputs
| Variable | Meaning | This run |
|---|---|---|
| project_path | Repo root (`<root>` below) | {{#if project_path}}`{{project_path}}`{{else}}`.`{{/if}} |
| live_url | Live address (`<url>` below) | {{#if live_url}}`{{live_url}}`{{else}}none → live measurements go to "Not measured"{{/if}} |
| product_type | saas · content · ecommerce · dashboard · community | {{#if product_type}}{{product_type}}{{else}}infer from code (Step 0){{/if}} |
| has_sales | Payments/sales: sales items (F10-F17) are mandatory | {{#if has_sales}}yes{{else}}not given → if Step 0 finds a payment provider, apply F10-F17; otherwise "Not applicable"{{/if}} |
| jurisdiction | global · eu · uk · us · tr (legal rows in F and M2) | {{#if jurisdiction}}{{jurisdiction}}{{else}}global{{/if}} |
| client_project | Client project | {{#if client_project}}yes: NO commit/push; report + patch proposal + client summary{{else}}internal project{{/if}} |
| fix | Close small P0/P1 gaps | {{#if fix}}yes: minimal diff + test (Step 3){{else}}report only{{/if}} |

{{#if standards_dir}}"Read index.md in {{standards_dir}} and apply the relevant rule tables too."{{/if}}

## Fixed rules
- Every finding: ID · Where (file:line or URL) · Measured · Expected/threshold · Severity · Fix (minimal) · Verification command.
- Severity (shared across the pack): P0 critical (live data/account/payment/secret risk, now) · P1 high (before delivery) · P2 medium · P3 low.
- Write "not measured (reason)" for anything you did not measure. Never invent thresholds, versions, prices or quotas; for a threshold marked "(not verified)", open the current official source and record its date.
- Never read or print the VALUES of `.env` or key files; record only the key name + location.
- `\|` in tables is a markdown escape; type `|` in the shell.
- Legal rows are a checklist, not legal advice; state this once in the report.
- Ownership: headers/RLS/secrets/authorization/input validation/error leakage/webhook signatures/incident runbook → "Web security audit (Next.js + Supabase)"; indexing, meta/OG/icons, structured data, 404 status code, CWV, broken links → "Web SEO audit". Do not measure "ref" rows here; in the matrix write "Not applicable (owner: …)". Call format: `ple get "<full title>" --var project_path=<root> --var live_url=<url>`.

## Step 0 — Stack and context detection
- [ ] `package.json`: `next` version; `app/` or `pages/`; `@supabase/*` packages; auth method (`@supabase/ssr`, NextAuth, Clerk…).
- [ ] Hosting: `vercel.json`, `netlify.toml`, `Dockerfile`; i18n (`next-intl`, `i18next`, `[locale]` segment); payment provider (`grep -rniE "stripe|lemonsqueezy|iyzico|paddle|paytr" package.json src app lib 2>/dev/null`).
- [ ] Product type: {{#if product_type}}`{{product_type}}` given; note it if code evidence contradicts it.{{else}}infer from routes (`/checkout`→ecommerce, `/billing`→saas, `/blog`→content, mostly `/admin`→dashboard, posts/comments→community) and state your reasoning.{{/if}}
- [ ] Done ≠ live: `git -C <root> fetch -q; git -C <root> rev-list --count @{u}..HEAD` (if there is no upstream, write "no upstream"); if >0, write "repo ahead of remote (N commits)". Compare the live deployment's commit SHA (Vercel dashboard > Deployments) with `git rev-parse --short HEAD`; if they differ, write "live is NOT the code this report describes" and label live measurements separately. {{#if live_url}}Measure live: `curl -sI {{live_url}}`.{{/if}}
- [ ] Backend ownership: if "Web full audit and launch gate" passed backend=shared, or `ls <root>/supabase/migrations 2>/dev/null` is empty and the code connects to a shared Supabase project in another repo via a tenant key (`grep -rn "site_key" src app lib 2>/dev/null`), then backend=shared: do not measure the project-settings part of A1/A3, A9, I1-I4, I6, I7 or the F7 purge job here; in the matrix write "Not applicable (owner: <backend repo>)" and route the finding to that repo.
- [ ] If there is only an admin login (no visitor accounts): A5/A6 "Not applicable"; visitor access/erasure requests for form data are handled via the rights-request route in the F1 notice.
- [ ] Move areas that do not apply to "Not applicable" with the reason.

Extra focus by product type (add to the matrix):
| Type | Extra items |
|---|---|
| saas | Subscription state machine, access after cancellation, plan change, trial-ending e-mail |
| content | Content/image license register, publishing flow (draft → approval → publish), form submissions reach the owner (L7); OG, RUM, broken links → SEO audit |
| ecommerce | Consumer sales set (F10-F13 per jurisdiction), stock/order transaction, returns flow |
| dashboard | Role/permission screen, audit log, list pagination, bulk-action confirmation |
| community | Report/flag, blocking, moderation queue, user content deletion |

## Step 1 — Checklist (each row becomes one matrix row)

**A. Account lifecycle**
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| A1 | Signup + e-mail verification | Local: `supabase/config.toml` `[auth.email] enable_confirmations = true`; prod: Dashboard > Authentication > Email "Confirm email" or Management API `GET /v1/projects/{ref}/config/auth` → `mailer_autoconfirm=false` (do NOT print the token VALUE); try logging in with an unverified test account | Unverified logins 0 | P1 |
| A2 | Login, logout, session refresh | Walk the flow; after logout, `curl -s -o /dev/null -w "%{http_code}"` a protected page/API | 401 or redirect to login | P1 |
| A3 | Password reset | `grep -rn "resetPasswordForEmail"`; link lifetime: prod `mailer_otp_exp`, local `otp_expiry` | ≤3600 s | P1 |
| A4 | Change e-mail and password | `grep -rn "updateUser"`; re-verification on e-mail change | — | P2 |
| A5 | Account deletion (end to end) | Delete a test account; are PII columns + Storage `<bucket>/<userid>/` objects cleared; are financial records separated | Request→done ≤30 days | P2 |
| A6 | Data export | Endpoint where users download their own data (JSON/CSV) | — | P2 |
| A7 | Accessible authentication (WCAG 3.3.8) | `grep -rn "onPaste"` (preventDefault); `autocomplete` values `current-password`, `new-password`, `one-time-code`; if CAPTCHA, an alternative without a cognitive test | Blocks 0 | P1 |
| A8 | Auth events logged server-side | Is auth client-only (AuthContext); grep logger call sites; trigger a failed login, see it in logs/Sentry | Uncalled auth logger 0 | P2 |
| A9 | Auth prod settings | Management API/Dashboard: `security_captcha_enabled=true` (signup/signin/reset), `smtp_host` set (built-in SMTP is limited to 2 e-mails per hour — Supabase going-into-prod, 2026-10-30), e-mail link tracking off, no test phone numbers | — | P1 |

**B. States and error surface**
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| B1 | Loading/empty/error are distinct | Throttle + empty data + simulated error; empty state has explanation + CTA, error has "retry" | Indicator for waits >300 ms (not verified) | P3 |
| B2 | Offline | Cut the network; meaningful message or silent freeze | — | P3 |
| B3 | 404/500 pages + error boundaries | App Router: `ls app/{not-found,error,global-error}.* src/app/{not-found,error,global-error}.* 2>/dev/null`, count of segment `error.tsx`; Pages Router: `pages/404.*`, `pages/500.*` (404 status code: SEO D) | not-found + global-error exist | P2 |
| B4 | Error leakage | ref: security 9.1 | — | — |
| B5 | Single error schema + correct codes | Routes return `{ error: { code, message } }`; no session → 401, unauthorized → 403, invalid input → 400, missing resource → 404 (one curl each); deliberate 404 on guessable ids | 401/403 mix-ups 0 | P2 |
| B6 | Critical errors persist | Payment/deletion errors are not auto-dismissing toasts; `useOptimistic` 0 in these flows | 0 | P1 |
| B7 | Success semantics | Call a writing endpoint, then query the DB for the expected row; 201 + id for new resources, 204 when no body | Endpoints returning 200 with no side effect 0 | P1 |

**C. Forms** — C1 server-side validation: ref security 5.1 · C2 clear field-level errors wired with `aria-describedby` (P2) · C3 every input has a visible `<label>` (P2) · C4 no re-entry in multi-step forms, WCAG 3.3.7 (P2) · C5 double-submit lock on submit (P2).

**D. Accessibility — WCAG 2.2 AA quick checks**
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| D1 | Automated scan | `npx @axe-core/cli <url> --tags wcag2a,wcag2aa,wcag21a,wcag21aa,wcag22aa --load-delay 2000 --exit` or `npx pa11y <url>` on the top 5 pages. WCAG 2.2 rules (target-size) are off by default in axe: a run without the `wcag22aa` tag does not measure D5; a tag list excludes best-practice rules. color-contrast measured before animations/transitions finish can be a false positive → wait ≥2 s or use Playwright `emulateMedia({ reducedMotion: 'reduce' })` (if the site honors `prefers-reduced-motion`) | Critical/serious 0 | P1 |
| D2 | Keyboard + visible focus | Tab through every flow; for each `grep -rnE "outline-none"` and `"outline: ?none"` match, check for a `focus-visible` style next to it | Traps 0 | P1 |
| D3 | Contrast | axe result + manual sample | Text 4.5:1, large text/UI 3:1 | P2 |
| D4 | Alt text, `html lang` | grep for `<img`/`<Image` without `alt`; `<html lang=` | Missing 0 | P2 |
| D5 | Target size (2.5.8) | Measure small icon buttons | ≥24×24 CSS px | P2 |
| D6 | Dragging alternative (2.5.7), consistent help (3.2.6) | grep `draggable`, `dnd-kit`, `onDrag` → button alternative; help link order identical across pages. 4.1.1 Parsing was removed in 2.2; do not report it | 0 | P2 |

**E. Performance and mobile**
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| E1 | Core Web Vitals + RUM | ref: SEO J (field/lab) and N (RUM) | — | — |
| E2 | JS budget per route | Use the First Load JS column of `next build` output if present; otherwise (may be absent from Next 16 output — not verified) `@next/bundle-analyzer` or the analyze command listed in `npx next --help`; closed/flagged routes use dynamic import | Budget written down (no number → "undefined") | P3 |
| E3 | ISR consistency | No `new Date()`/`Math.random()` in render on ISR/static pages (image sizing: SEO J) | Missing 0 | P3 |
| E4 | Responsive | At a 320 px viewport, console: `document.documentElement.scrollWidth > innerWidth` is false (WCAG 1.4.10); viewport meta | Horizontal scroll 0 | P2 |
| E5 | Large lists | 500+ DOM items via .map (first `content-visibility:auto`, then virtualize); call the list API without a limit and with `limit=100000`, record the returned count (not verified: default ≤50, max ≤200) | Unbounded queries 0 | P2 |
| E6 | Video | `curl -sI <video-url>` Content-Type `video/mp4` or `video/webm`; `<video>` has real .mp4 (H.264) + .webm `<source>`; no lone .mov; plays in Safari + Chromium; large video on a stream/embed provider rather than Storage (not verified) | Browsers that fail to play 0 | P2 |

**F. Legal and privacy (jurisdiction: {{#if jurisdiction}}{{jurisdiction}}{{else}}global{{/if}})** — checklist, not legal advice. Apply the column that matches the jurisdiction; eu/uk use the global column. A row whose cell is "—" is "Not applicable" for that jurisdiction. If the product also serves users in another listed jurisdiction, apply that column too and say so.
| ID | Item | global / eu / uk | us | tr (Türkiye) | Sev |
|---|---|---|---|---|---|
| F1 | Privacy notice + rights | Privacy notice linked from every collection point (signup, forms); lawful basis per purpose; route for access/erasure/portability requests | Notice at collection linked from every collection point (CCPA/CPRA); route for know/delete/correct requests (not verified: applicability thresholds) | KVKK aydınlatma metni: her veri toplama noktasından (kayıt, form) bağlantı; KVKK m.11 başvuru yolu | P1 |
| F2 | Separate consent | Purposes outside the notice (marketing etc.) on a separate, unticked box | "Do Not Sell or Share" link where applicable; marketing e-mail per CAN-SPAM (working opt-out + sender postal address — not verified) | Açık rıza ayrı: aydınlatma dışı amaçlar (ticari ileti vb.) ayrı, önceden işaretsiz kutu | P1 |
| F3 | Cookie banner + consent-gated analytics | "Reject" on the same layer as "Accept", same number of clicks | Banner not assumed mandatory (not verified); opt-out honored where F2 applies | "Reddet" "Kabul et" ile aynı katmanda, eşit tık sayısında | P1 |
| F4 | Privacy policy + terms of use | Footer links, last-updated date | same | same | P2 |
| F5 | Contact/imprint | Selling site shows legal entity name, address, e-mail (EU/UK imprint rules — not verified) | Contact e-mail + postal address | Satış yapan sitede unvan, MERSİS/vergi no, adres, e-posta, telefon (6563 s. Kanun m.3) | P2 |
| F6 | Processors and transfers | Processor inventory (hosting, DB, e-mail, monitoring); DPA signed with each; transfer mechanism recorded | Service-provider contracts with processors (not verified) | Yurt dışı aktarım: yurt dışı servis envanteri (barındırma, DB, e-posta, izleme); standart sözleşme imza tarihi → Kurum bildirimi ≤5 iş günü | P1 |
| F7 | Retention periods | Retention defined for form/auth/log data + purge job (cron); periodic purge exists | same | same | P2 |
| F8 | Cookie guidance currency | Banner text matches current regulator guidance; record its date | — | KVKK Çerez Rehberi 28.07.2025 sürümüne göre band metni (not verified — güncel PDF'i oku) | P2 |
| F9 | Registration/records | Records of processing kept; UK: ICO data protection fee (not verified) | — | VERBİS: muafiyet otomatik varsayılmaz; çalışan sayısı + bilanço eşiği kvkk.gov.tr'den tarihle teyit (not verified: 2023 eşiği 100 milyon TL); teyit tarihi yazılı | P3 |

F3 measurement (all jurisdictions): pre-scan `curl -s <url> \| grep -cE 'googletagmanager.com/gtag/js\|connect.facebook.net'` (> 0 → candidate); definitive: in a private window, check Network for analytics/marketing requests before consent and after "Reject". Threshold: tracking requests without consent 0 (us: after opt-out 0); no browser → not measured. A single DM channel is never enough for F5.

Sales/payment items ({{#if has_sales}}sales present: all mandatory{{else}}only if payments/sales were detected; otherwise F10-F17 "Not applicable"{{/if}}):
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| F10 | Pre-contract information + terms of sale | Walk checkout: links + consent checkbox; cannot proceed to payment unchecked. global/eu/uk: pre-contract information + withdrawal-right notice; us: terms of sale + refund policy (subscription auto-renewal disclosure — not verified); tr: ön bilgilendirme formu + mesafeli satış sözleşmesi | Bypasses 0 | P1 |
| F11 | Regulation change (tr only) | tr: RG 24.05.2025/32909 (yürürlük 2026-01-01) m.5 bentleri ve cayma istisnaları metne işlendi mi (not verified — Resmî Gazete'den teyit et); others: — | — | P1 |
| F12 | Refund/cancellation + invoice | Refund/cancellation page; invoicing flow and delivery to the user defined | — | P1 |
| F13 | ETBİS (tr only) | tr: kendi sitesinden satışta kayıt belgesi; bilgi değişince bildirim (not verified: 30 gün); others: — | — | P2 |
| F14 | POST idempotency | Payment/money/stock POST accepts `Idempotency-Key`; two POSTs with the same key → one DB row (webhook signature + event id: security 8.1-8.3) | Double processing 0 | P0 |
| F15 | Subscription/order state | Status column limited to the provider's state set via CHECK/enum (INSERT of an invalid value errors); on cancellation access ends via `now() > ends_at`, not `status='cancelled'` alone | Early cutoff 0 | P1 |
| F16 | Money/stock writes | ≥2 writes in one transaction/lock; probe with two concurrent requests; no hard delete of financial records (`deleted_at` or append-only) | Negative balance 0 | P1 |
| F17 | European Accessibility Act (if selling to EU consumers) | Since 28 June 2025; WCAG AA (D) + accessibility statement `curl -sI <url>/accessibility` (tr sites: `/erisilebilirlik`) returns 200; microenterprise exemption for services (not verified — check the current source) | Statement page 200 | P2 |

**G. Transactional e-mail delivery** — find the sending domain (From), then: `v=spf1` in `dig +short TXT <domain>` (SPF) · `dig +short TXT <selector>._domainkey.<domain>` (DKIM; selector is on the e-mail provider's DNS settings screen) · `dig +short TXT _dmarc.<domain>` (DMARC, at least `p=none`) · sender uses its own domain, From aligned · Supabase Auth custom SMTP (A9) · marketing/bulk e-mail has `List-Unsubscribe` + `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058) and a visible unsubscribe link (grep the raw headers of a sent message). Threshold (Google sender guidelines, 2026-10-30): SPF or DKIM for all senders; senders of >5000/day to Gmail need SPF + DKIM + DMARC + alignment + one-click unsubscribe; spam rate <0.1%, never ≥0.3% (Postmaster Tools). Missing SPF/DKIM = P1, no DMARC = P1, no unsubscribe header (bulk) = P1.

**H. Monitoring and logs**
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| H1 | Error tracking client + server | Sentry init: `instrumentation.ts`, `sentry.*.config`, error boundary; test error shows in the dashboard | 2/2 | P1 |
| H2 | Source map upload | `withSentryConfig` uploads sourcemaps; is the auth key NAME present in CI (value not read); PII scrubbing: security 9.5 | — | P2 |
| H3 | Uptime + alert channel | External monitor entry, interval; does a test alert land in a channel someone reads | ≤5 min | P1 |
| H4 | Structured logs | JSON logs (level, time, request id); PII/secrets in logs: security 9.4 | — | P2 |

**I. Data, backups, environments**
| ID | Item | How to measure | Threshold | Sev |
|---|---|---|---|---|
| I1 | Written backup plan | Supabase plan + last backup date in the Backups tab; Free has no automatic backups → off-site `supabase db dump` via cron/Action; Pro daily backups for 7 days (2026-08 note; confirm on the current page) | Written plan + last backup ≤1 day | P0 |
| I2 | Restore drill | Restore the dump into an isolated project, dated record | ≥once a year | P0 |
| I3 | PITR decision | If payment/critical data or DB >4 GB, PITR on or decision recorded | — | P1 |
| I4 | Prod not on Free plan | On Free the project pauses when inactive and backups cannot be downloaded | Client prod on Free 0 | P1 |
| I5 | Environment separation | `vercel env ls` (names only): is `NEXT_PUBLIC_SUPABASE_URL` a separate row for Production and Preview; does the Supabase host (public ref) differ in Network on preview vs live | Shared prod data 0 | P1 |
| I6 | Supabase org | Account MFA, owners ≥2, SSL Enforcement, Network Restrictions | owners ≥2 | P1 |
| I7 | Indexes/N+1 | Columns used in RLS and filters are indexed: Dashboard > Advisors > Performance or the seq-scans/index commands in `npx supabase inspect db --help` (`--linked`); `EXPLAIN ANALYZE` in SQL Editor shows Seq Scan + >1,000 rows; extra queries per list request O(1) (≤3) | — | P2 |
| I8 | Identity migration | On repairs/migrations that change a userid: `select name from storage.objects where name like '<old-userid>/%'` | Objects under old prefix 0 | P1 |

**J. Delivery pipeline** — J1 CI gates: lint + typecheck + test + build on PRs (`ls .github/workflows`; none → P1) · J2 post-deploy smoke test: main routes 200, protected API 401 without a session, critical API actually writes (query the DB row) (none → P1) · J3 rollback path written down (command to return to the previous deploy) (P2) · J4 undeclared (transitive) dependencies: every import in code is in `package.json`; `npx knip --include unlisted` (unlisted 0); for suspects, `npm ls <package>` (pnpm: `pnpm why <package>`) — undeclared if it appears only under another package (P2).

**K. Infrastructure continuity** — K1 TLS: `echo | openssl s_client -connect <domain>:443 -servername <domain> 2>/dev/null | openssl x509 -noout -dates`; independent alert 21-30 days before expiry; CA/B maximum certificate lifetime is 200 days from 2026-03-15, 100 from 2027-03-15, 47 from 2029-03-15 — renewal automation is required (P1) · K2 `dig CAA <domain>` does not block the issuer (e.g. Let's Encrypt) (P1) · K3 `whois <domain> | grep -i status` (or rdap): `clientTransferProhibited` + auto-renew; if fields differ for .tr domains, write "not measured"; domain expiry and payment card expiry on a separate calendar ahead of registrar reminders (P1) · K4 spend limits: Supabase Spend Cap (on by default on Pro, restricts on overage) and Vercel Spend Management (only warns by default) chosen deliberately, alert thresholds defined (P2) · K5 quota items (bandwidth, invocations, image optimization, egress, MAU) tracked in the Usage panel; confirm numbers on the current pricing page (P3).

**L. Product polish and operations**
| ID | Item | How to measure | Sev |
|---|---|---|---|
| L1 | Favicon, manifest, OG image | ref: SEO I | — |
| L2 | i18n readiness | Fixed strings in a dictionary; dates/money via `Intl`; `lang` dynamic | P3 |
| L3 | Support channel + status notice | Does a test message from the contact form arrive (owner seeing it: L7); status page/banner for outages | P2 |
| L4 | Admin/operations tools | User search, disable, role change, audit log of critical actions | P2 |
| L5 | Documentation | README (setup, commands, env NAMES), architecture/file map, handover note | P2 |
| L6 | Broken links | ref: SEO N | — |
| L7 | Owner sees form submissions | For each form (contact/booking/quote), a write path + a read path: a `.select(` on the same table in an admin list, or a notification e-mail to the owner; see the test submission on the owner's screen/inbox (if backend=shared, the admin list may live in the backend repo; look there). Write path only → P1 | P1 |

L7 pre-scan: writes `grep -rnE "\.(insert|upsert|rpc)\(" src app lib 2>/dev/null` · notifications `grep -rniE "resend|nodemailer|sendmail|postmark|sendgrid" src app lib 2>/dev/null`.

**M. Incident response** — M1 runbook, containment commands, key rotation, incident log timestamps: ref security Step 12 · M2 personal data breach notification flow written down, by jurisdiction: global/eu/uk: supervisory authority ≤72 hours after detection (GDPR Art. 33 / UK GDPR), data subjects without undue delay when high risk; us: state breach-notification laws, deadlines vary (not verified); tr: Kurul bildirimi tespitten ≤72 saat (KVKK m.12/5, ihlalbildirim.kvkk.gov.tr; AB kullanıcısı varsa GDPR m.33) ve ilgili kişilere "en kısa sürede" (flow not written → P1; deadline missed on an ongoing breach → P0) · M3 post-mortems are blameless and tie every finding to a new test; age of open findings is reported (P2).

## Step 2 — Ranking
- For each Missing/Partial, give impact (3 high · 2 medium · 1 low) and effort (S ≤1 hour · M ≤1 day · L >1 day).
- Order: severity first (P0→P3), ties broken by the higher impact/effort ratio. Move the first 10 to "Top 10 gaps".

## Step 3 — Fix
{{#if fix}}{{#if client_project}}Do not change code; for each P0/P1 propose a separate branch name + patch (diff), NO commit/push.{{else}}Close only S-effort P0/P1 gaps with a minimal diff (e.g. not-found page, `lang`, label, sourcemap setting, CAA record proposal); add a test or verification command for each change, run the tests, stage explicit file paths only. Report M/L-effort items only.{{/if}}{{else}}Do not change code; report only.{{/if}}

## Output (in this order)
## Verdict
One sentence + PASS/FAIL. FAIL if there is an open P0 or an open P1 without owner and date (shared pack rule). Add the P0/P1 counts and the completeness percentage (Present ÷ items excluding Not applicable).
## Findings
**Completeness matrix** — | Area (A–M) | Present | Partial | Missing | Not measured | Not applicable |
**Gaps (severity-ordered)** — one row per Missing/Partial: | ID | Area | Where | Measured | Expected/threshold | Severity | Fix | Verification | Impact/effort |. The first 10 rows are the "Top 10 gaps"; mark them in bold.
## Checked and clean
Items marked "Present" with their evidence (mandatory; shows the coverage).
## Not measured
Item · reason (no access, no live URL, dashboard required) · who should check it.
## Not applicable
Item · reason (from Step 0).
## Next 3 steps
{{#if client_project}}
## Client summary
Non-technical, max 6 lines: current state, the top 3 risks, recommended order.
{{/if}}

Feedback: If this prompt missed a check, gave a wrong threshold or you hit a PLE problem, send one line: ple feedback --type prompt --prompt "Web production baseline audit" "<what is missing → what it should be>" (use --type bug or --type missing for PLE issues; if the command is unavailable, add a "PLE feedback:" line at the end of your report).

Related prompts