Should It Launch
Production-Readiness Dossier
Report No. SIL-2026-0613 · Filed 06·19·2026

Subject of Audit

harborandvine.com

Harbor & Vine · wine bar · built on Lovable
captured 06·19·2026 11:42 utc · engine v1.0 · ruleset r1

ownership attested ✓
owner private view
active probe enabled

Verdict: Not ready for client handoff

Why it can't ship yet

One blocking issue: a private database key is exposed in the site's code, which hands anyone who opens the page full read and write access to your data. Security also sits below our release floor.

criticals: 1 floor breached: security gate → not ready

Readiness Index

57/100

the index is diagnostic. the gate verdict above is what governs release.

Accessibility

WCAG 2.2 AA 72/100

Above the release floor, but three deterministic failures would each be visible to a client using a keyboard or screen reader. axe-core ran against the rendered DOM; judgment checks are marked for human review rather than auto-failed.

▲ high A11Y-CONTRAST-01 FAIL

Reserve-a-table button fails contrast

The gold button text on cream measures 2.1:1. AA requires 4.5:1 for this size. A client with low vision can't read the primary call to action.

▸ evidence
selector .hero .btn-reserve
foreground #C9A24B
background #F5F0E6
ratio 2.1:1 (needs 4.5:1)
wcag 1.4.3 Contrast (Minimum) · AA
▸ fix prompt · suggestion-grade
paste into the tool that built this site
The "Reserve a table" button uses #C9A24B text on a #F5F0E6 background, a 2.1:1 contrast ratio. Raise it to at least 4.5:1. Set the button background to the dark ink #1B1710 with cream (#F4EEDF) text, or keep the cream background and darken the gold to #7A5310. Re-check the computed ratio after the change.
▲ high A11Y-LABEL-03 FAIL

Reservation form inputs have no labels

Three inputs rely on placeholder text alone. A screen reader announces them as unlabeled, and the placeholder vanishes once typing starts.

▸ evidence
#reservation input[name=name] no <label>, no aria-label
#reservation input[name=email] no <label>, no aria-label
#reservation input[name=party] no <label>, no aria-label
wcag 1.3.1 Info & Relationships · 4.1.2 Name, Role, Value
• medium A11Y-ALT-02 FAIL

Gallery images missing alt text

Three of five gallery images carry no alt attribute, so a screen reader reads the file path or nothing at all.

▸ evidence
img /gallery/interior-2.jpg alt=""
img /gallery/pour-3.jpg (no alt attribute)
img /gallery/cellar-5.jpg (no alt attribute)
wcag 1.1.1 Non-text Content · A
? review A11Y-FOCUS-07 INCOMPLETE

Mobile menu focus order needs a human check

The menu opens in a portal. Tab order looks plausible but logical focus order is a judgment call, so we flag it for review rather than asserting a pass or a fail. We never claim a WCAG pass we did not verify.

Security

floor 60 · breached 24/100

This dimension holds the verdict. One verified critical and a missing header stack put it well below the release floor. The two-stage secret detector cleared the public keys before flagging the dangerous one, which is the line that separates a real finding from a false alarm.

× critical SEC-SECRET-01 stage 2: verified active FAIL

Supabase service_role key exposed in the page bundle

The service_role key bypasses every row-level-security rule. Anyone who opens the site can read, edit, and delete your entire database from their browser. This is the single issue that blocks handoff. Treat the key as compromised and rotate it now, before fixing the code.

▸ evidence · redacted
file /assets/index-4a1b9c.js
location line 1 · col 88204
assigned const SUPABASE_KEY = "…"
decoded jwt payload → "role":"service_role"
value eyJhbGciOiJI•••••••••••• redacted ••••••••••••9.Qx7vK
reveal value · owner private view
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3MTgyMH0.Qx7vK2sR
in the live product this renders only in your authenticated owner view and is never written into any hosted or shared copy. shown here as a sample of that owner view.
▸ fix prompt · suggestion-grade
rotate the key first, then paste this
Remove the Supabase service_role key from all client-side code. It must never appear in the browser bundle. First rotate it: in the Supabase dashboard go to Settings, API, and reset the service_role key, since the current one is exposed and must be treated as compromised. The browser may only ever hold the anon public key. Move any logic that needs elevated access into a Supabase Edge Function or a server route, and confirm row-level security is enabled on every table.
✓ checked & cleared SEC-SECRET-ALLOW-04 PASS

Stripe publishable key, public by design, not a leak

A pk_live_ key sits in the checkout script. Publishable keys are meant to be public, so we cleared it against the allowlist instead of flagging it. Telling this apart from a real leak is the difference between a report a client trusts and one that cries wolf.

▸ why this is safe
candidate pk_live_51M•••••• (checkout.js)
matched allowlist: stripe publishable
action suppressed before stage 2. never escalated.
note also cleared: supabase anon jwt (role:anon)
▲ high SEC-HEADERS-02 FAIL · grade F

No security headers set

The response ships with none of the baseline protections. Graded against the Observatory algorithm it lands at F.

▸ evidence
absent content-security-policy
absent strict-transport-security
absent x-content-type-options
absent referrer-policy
absent permissions-policy
grade F (start 100, penalties applied)
• medium SEC-SRCMAP-05 FAIL

Source maps are publicly reachable

A reachable .js.map exposes your original source, which is how the key above was so easy to read. Disable source-map output for production builds.

▸ evidence
GET /assets/index-4a1b9c.js.map → 200 OK
exposes original src tree, component names, comments
✓ pass TLS certificate valid, no mixed content, cookies carry Secure and HttpOnly SEC-TLS-06

Performance & Core Web Vitals

source: lighthouse / psi / crux 58/100

Presented as diagnostic. Lighthouse is lab data and does not measure field CWV directly, so no single number drives the verdict. Field data from CrUX is not yet available for this domain, which we flag rather than guess.

LCP · lab
4.1s
poor · target ≤ 2.5s
TBT · lab
480ms
proxy for INP
CLS · lab
0.18
target ≤ 0.10
CrUX · field
no data
volatile · excluded from verdict
▲ high PERF-IMG-01 WARN

Hero image is a 2.3 MB unoptimized PNG

The largest contentful paint is the hero photo. It ships uncompressed with no dimensions set, which drives both the slow LCP and most of the layout shift.

▸ evidence
resource /hero-bar.png 2,341 KB png
served no width/height attrs, no srcset, no preload
impact lcp element · ~2.1s of the 4.1s

SEO & Structured Data

66/100

The basics are present but the page is invisible to link previews and to local search. For a wine bar, the missing LocalBusiness schema is a real miss.

• med

No meta description · no canonical

Search engines write their own snippet, and duplicate URLs can't be consolidated.

FAIL
• med

No Open Graph or Twitter cards

Shared to social or messaging, the link shows a blank box with no title or image.

FAIL
• low

No LocalBusiness schema

Hours, address, and price range aren't machine-readable, so the map and rich results stay empty.

WARN
✓ pass robots.txt and sitemap.xml present and valid, heading hierarchy sound SEO-CRAWL-04

AI-tell / Slop

E1 convicts · E2 advisory 38/100

The leftovers of an AI build that shipped before anyone read it top to bottom. E1 tells are facts and score at full weight. E2 tells are judgment, kept advisory, and can never decide a verdict.

E1 · deterministic tells

× fail lorem ipsum in rendered body · .about > p "Lorem ipsum dolor sit amet…"
× fail placeholder image · .menu img src=via.placeholder.com/640x400
× fail dummy email in footer · hello@example.com
× fail dead nav link · a[href="/events"] → 404
× fail console error on load · TypeError: Cannot read properties of null (reading 'map')
· ctx generator fingerprint · lovable (cdn.gpteng.co/gptengineer.js)
▸ fix prompt · lorem ipsum · suggestion-grade
paste into the tool that built this site
Replace the placeholder text in the About section. The first paragraph still reads "Lorem ipsum dolor sit amet". Write two or three real sentences about Harbor & Vine: what the room feels like, the focus of the wine list, and who it's for. Then search the whole project for any remaining "lorem ipsum", "example.com", and "placeholder" strings and replace each with real content.

E2 · inferred · advisory only, cannot convict

~ polish

Hero copy "Welcome to our amazing restaurant" reads templated. This feeds the Polish signal only. It does not affect the verdict.

~ polish

"Our Story" repeats the same three-card boilerplate seen in many generated builds. Advisory, not a fault.

Functional Integrity

44/100

The page renders, but its two main jobs, taking a reservation and an online order, both do nothing when clicked. Detected from the captured console and network logs.

▲ high FUNC-FORM-01 FAIL

Reservation form submits nowhere

The form's onSubmit handler is missing. The button shows a success message but no request leaves the browser, so every reservation is silently lost. This is the kind of finding a client discovers by losing a booking.

▸ evidence
form #reservation · onSubmit = undefined
network 0 requests fired on submit
ui shows "Thanks, see you soon" with no post
▸ fix prompt · suggestion-grade
paste into the tool that built this site
In the ReservationForm component the onSubmit handler is missing, so the form shows a success message without sending anything. Wire onSubmit to validate the required fields (name, email, party size, date), insert the booking into the reservations table via the Supabase client using the anon key, show a real success state only after the insert resolves, and show an error state if it fails. Do not display "Thanks" until the request succeeds.
• med

"Order online" button has no handler

Clicking does nothing. No navigation, no modal, no console activity.

FAIL
• med

Two gallery images return 404

/gallery/pour-3.jpg and /gallery/cellar-5.jpg fail to load, leaving broken-image icons.

FAIL
✓ pass Page renders fully, no white screen, bundle loads FUNC-RENDER-00

Privacy & Legal Hygiene

informational 70/100

Flagged for awareness, not scored against the gate. This is information, not legal advice.

i info

Google Analytics loads on entry, but no consent banner was detected. Depending on visitor location this may need addressing.

i info

No privacy-policy link in the footer or nav.

✓ pass No PII found in URLs or query strings

Responsive / Mobile

63/100

Most clients open the link on a phone first. Two issues show up at 375px wide.

▲ high

Horizontal overflow at 375px

The menu table is 40px wider than the viewport, so the whole page scrolls sideways.

FAIL
• med

Footer social icons are 28px tap targets

Below the 44px minimum, hard to tap accurately on a phone.

FAIL
✓ pass Viewport meta correct, text scales without clipping RESP-VP-01

Prove your fixes

Fix, then re-audit to clear the verdict

Paste the fix prompts into the tool that built the site, then re-run this audit. A re-scan within 7 days of a paid audit is covered. The verdict updates when the evidence does.

index 57 first audit · no trend yet