[object Object]

← back to Ticket System

Retain reviewed Prestige and Gemma approval preparation

61e33ebd2ff69e8d22ba69785b1c7800d31692e0 · 2026-09-16 15:02:52 -0700 · Steve Abrams

Files touched

Diff

commit 61e33ebd2ff69e8d22ba69785b1c7800d31692e0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 16 15:02:52 2026 -0700

    Retain reviewed Prestige and Gemma approval preparation
---
 .../TK-11850-photo-route-decommission-yf2139.md    |  41 +
 .../TK-11852-gemma-roster-capacity-yf2139.md       |  26 +
 .../yoloforever-yf2139.YF57g8/dispositions.json    |  73 ++
 .../yoloforever-yf2139.YF57g8/increment1-dtd.json  |  18 +
 .../yoloforever-yf2139.YF57g8/increment2-dtd.json  |  18 +
 .../model-parent-proof.json                        |   1 +
 .../model-prep/approval-memo.md                    |  26 +
 .../model-prep/arena-models.body                   |   1 +
 .../model-prep/arena-models.json                   |   6 +
 .../model-prep/checkout-status.txt                 |  57 ++
 .../model-prep/e2e-proof.json                      |  61 ++
 .../model-prep/handoff.json                        |  14 +
 .../model-prep/ollama-tags.body                    |   1 +
 .../model-prep/ollama-tags.json                    |   6 +
 .../model-prep/roster-source.txt                   |   1 +
 .../prestige-parent-proof.json                     |   7 +
 .../prestige/review.dtOpeo/api-places.json         |   1 +
 .../prestige/review.dtOpeo/e2e-proof.json          | 142 ++++
 .../prestige/review.dtOpeo/handoff.json            |  62 ++
 .../prestige/review.dtOpeo/photo-route.patch       |  25 +
 .../prestige/review.dtOpeo/rollback/server.js      | 910 +++++++++++++++++++++
 .../prestige/review.dtOpeo/server.before.js        | 910 +++++++++++++++++++++
 .../prestige/review.dtOpeo/server.candidate.js     | 894 ++++++++++++++++++++
 .../yoloforever-yf2139.YF57g8/supplied-queue.json  | 530 ++++++++++++
 24 files changed, 3831 insertions(+)

diff --git a/verification/yoloforever-yf2139.YF57g8/TK-11850-photo-route-decommission-yf2139.md b/verification/yoloforever-yf2139.YF57g8/TK-11850-photo-route-decommission-yf2139.md
new file mode 100644
index 00000000..37040b24
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/TK-11850-photo-route-decommission-yf2139.md
@@ -0,0 +1,41 @@
+# TK-11850 — Review narrowed Places photo proxy removal
+
+Decision requested: **APPROVE / REVISE / BLOCK**. Recommendation: **APPROVE the narrower removal after fresh preflight**, subject to the separate activation gate below. Preparation is complete; source outcome is UNVERIFIED. Cost: **$0 (local)**.
+
+Correlation: `yf2139-i1-execute`; accountable finalizer: root / codex-yoloforever. Scope authorized for this increment: R0 patch artifact and this memo only. DTD A won 5/5 according to parent handoff; that is not activation approval.
+
+## Exact proposal and correction to earlier ticket
+
+Remove only `server.js`'s `/api/places/photo/:i` handler and its two adjacent explanatory comment lines (plus separating whitespace). Preserve `/api/places`, `photo_urls`, local-photo mapping, media serving, and all data. The original ticket proposed deleting photos plumbing as well: that is too broad. The public frontend fetches `/api/places` at `public/index.html:177`, reads `p.photo_urls` at line 183, and maps those URLs into image sources at lines 184–185. Literal absence of `/api/places/photo` in frontend source does **not** establish no indirect consumer.
+
+All seven current `data/places.json` photo values are local `media/real/*.jpg` paths; all seven files exist and were hashed. Read-only `http://127.0.0.1:9808/api/places` returned 200 and the seven expected local photo URLs. A GET of its first local image returned 200 image/jpeg and matched the local file bytes. No photo proxy GET, Google call, key read/change, deployment, restart, public-site write, or external send occurred.
+
+## Remaining limitation
+
+The preserved `/api/places` mapping still turns future `places/` resource references into `/api/places/photo/:i` URLs. Removing the handler leaves that latent branch without the intended photo service; its comment also continues to describe a proxy branch. Approval must explicitly accept this dormant limitation for the current local-photo dataset. Future Google references require a separate decision before introducing them, including whether to change that mapping or reintroduce a deliberately configured photo service. This memo makes no claim that all external callers are absent or that production matches this local checkout.
+
+## Review artifacts
+
+- Patch: `/Users/macstudio3/Projects/ticket-system/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/photo-route.patch`
+- Evidence: `/Users/macstudio3/Projects/ticket-system/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/e2e-proof.json`
+- Exact baseline: `/Users/macstudio3/Projects/ticket-system/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.before.js`
+- Syntax-checked candidate: `/Users/macstudio3/Projects/ticket-system/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.candidate.js`
+- Baseline Git HEAD: `59d77fcb08e950ea77e66f3dc287339a1fef7d0b`
+- Baseline server SHA-256: `03776959d3f31aaec8235d5a1a23efcf6d375194bfcd0920ba507c5442330eaf`
+- Patch SHA-256: `8f25354cc18546a1ed217fb7bb0e6f61e0397b21b9fefa7f3d44c151ecf49836`
+- Candidate SHA-256: `1cfa7a06beed1971eb71a04450085e637dddb56eea9d999c47ea076b8c8b7b93`
+- Data SHA-256: `7047d9297a71d4223d7a5eba0e2b379384444488074225d7cb95ae5e3a5dd40e`
+
+PASS: `git apply --check` against active checkout; `node --check` on isolated candidate; exact bounded removal inspection; all local files; existing local API/media GETs; reverse-patch rehearsal in isolated directory restored exact baseline bytes; active source/data unchanged and Git status clean. Retain all artifacts.
+
+SKIP: candidate runtime/UI journey, browser rendering, route-negative HTTP test, production comparison/deploy/restart/post-activation checks. These require separately authorized activation or would risk a billed proxy request. R0 artifact proof is complete; the operational source ticket must remain open pending the gated action and its E2E proof.
+
+## Activation gate and fresh preflight
+
+Steve must explicitly approve applying this exact patch to the named active checkout. Restart/deploy or any customer-facing activation requires approval naming the target service/environment; approval of this memo alone must not silently widen scope. No credentials, data edits, provider calls, DNS, or remote push are included.
+
+Immediately before any approved application, reread the canonical ticket/ownership, confirm the target environment, compare source HEAD and server/data hashes above, check clean status and patch applicability, verify the current data still contains only local photos and files exist, and repeat the existing local API/photo baseline. Stop and request a revised review if source/data drift or Google resource references appear. After approved activation, require actual frontend photo rendering, `/api/places` and local image checks, safe negative/error checks that cannot reach Google, source diff review, and independent parent verification before closure.
+
+## Rollback
+
+An isolated reverse-patch rehearsal already restored the exact baseline SHA-256. For an approved activation, retain the target's fresh pre-change snapshot and exact diff. If the rollout fails, use an approved targeted reversal of only this handler change after checking for intervening edits; do not reset the whole checkout. Reactivating the reverted service uses the same explicit restart/deploy gate. Verify local photo rendering and API/media baselines after rollback. Rollback can restore the credential-backed handler: do not invoke it or add a key as part of rollback.
diff --git a/verification/yoloforever-yf2139.YF57g8/TK-11852-gemma-roster-capacity-yf2139.md b/verification/yoloforever-yf2139.YF57g8/TK-11852-gemma-roster-capacity-yf2139.md
new file mode 100644
index 00000000..5550da05
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/TK-11852-gemma-roster-capacity-yf2139.md
@@ -0,0 +1,26 @@
+# TK-11852 — Gemma roster and capacity decision
+
+Prepared 2026-09-16T21:59:07.353966+00:00; correlation yf2139-i2-execute; owner/finalizer codex-yoloforever.
+Recommendation: REVISE pending confirmation of intended roster and capacity; conditional APPROVE for the scoped restoration below only after those conditions pass. Source outcome: UNVERIFIED. This is an R0 local review artifact, not a model restoration.
+
+## Decision for Steve
+
+- APPROVE: confirm gemma3:12b remains an intended contender and authorize only its roughly 8GB download on Mac2 after a fresh capacity preflight demonstrates sufficient persistent and temporary download/extraction headroom. Record the model storage volume, expected bytes, transient overhead, concurrent growth allowance, and post-download free reserve; an unquantified reserve or continued disk warning requires a separate explicit capacity decision before proceeding. The existing 8%-free warning is not clearance.
+- REVISE: clarify whether Gemma was deliberately retired and specify the intended roster or a capacity plan; prepare a separately reviewable roster change if retirement is intended.
+- BLOCK: retain the current state and document reduced-field rating risk; do not close TK-11852.
+
+## Concrete evidence
+
+The local source server.js:78 includes gemma3:12b at localhost:11434; commit 80f1b9c942509c55ef6026709344e969805a4a03, SHA-256 9ce80306a635fc81ffccc914f2d5886c875a5d8fb0dcf555ed2c917330860a07. The GET /api/tags returned HTTP 200 and these tags: qwen2.5vl:7b, hermes3:8b, qwen3:14b; gemma3:12b was absent. GET /api/models returned HTTP 401 without authentication: available:false is a historical source-ticket report, not a fresh authenticated measurement. No credentials were loaded to bypass that boundary.
+
+Fresh pure disk probe: {"unit": "Mac2", "reachable": true, "free_pct": 8, "free_gb": 78, "size_gb": 887, "verdict": "WARN"}. Earlier parent evidence was WARN, 8% free, 78 GiB; the helper labels its binary-unit field free_gb. The missing-input probe returned UNKNOWN. The full disk canary was not run because it includes alert/state side effects; only the previously extracted df helper ran.
+
+TK-11852 describes successful-looking rows with partial:false despite a reduced contestant field, which can bias ELO/win-rate interpretation. That is a documented downstream risk, not a newly reproduced battle. No inference, battle, ledger write, model pull, restart, roster edit, model deletion, or source checkout edit occurred. Existing untracked runtime artifacts remain untouched.
+
+## Conditional implementation and acceptance
+
+After explicit approval and roster confirmation: recapture local tags and storage-volume capacity immediately before the intended pull, quantify transient headroom and reserve, and stop if the approved capacity assumptions fail. The scoped action proposed is ollama pull gemma3:12b; no general roster change or daemon restart is included. After any later authorized pull, verify the exact tag/digest, remaining capacity, and authenticated availability using already-authorized credentials without exposing them. Any inference or battle and its ledger writes require an explicitly authorized verification step; do not imply a restored rating pipeline from tag presence alone. Parent/finalizer retains the source ticket until the intended operational outcome has real proof.
+
+## Evidence and rollback
+
+Evidence directory: /Users/macstudio3/Projects/ticket-system/verification/yoloforever-yf2139.YF57g8/model-prep. Retained endpoint response bodies/status, roster excerpt, disk and negative probe, checkout status, e2e-proof.json, and handoff.json support this preparation only. Artifacts are additive; revision/superseding is the rollback for this memo. A later pull consumes shared model storage; no autonomous deletion is authorized as rollback. Provider spend: $0. DTD guard read directly: ZERO_COST_REQUIRED; environment DTD_ZERO_COST=1.
diff --git a/verification/yoloforever-yf2139.YF57g8/dispositions.json b/verification/yoloforever-yf2139.YF57g8/dispositions.json
new file mode 100644
index 00000000..8cdda24c
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/dispositions.json
@@ -0,0 +1,73 @@
+[
+  {
+    "position": 1,
+    "id": "TK-11859-cost-backlog-join-repair-class-18-vendor",
+    "disposition": "external-blocked",
+    "reason": "PROVISIONAL reassessment: creator process exists but parentTK11427 is done21:24Z; active-source ownership not proven. Domain owner assessing existing artifacts and first safe preparation before final disposition.",
+    "evidence": [
+      "TK-11859.json",
+      "owner-processes.json"
+    ]
+  },
+  {
+    "position": 2,
+    "id": "TK-11850-prestige-car-wash-places-photo-route-has",
+    "disposition": "prepared-gated",
+    "reason": "Narrow handler-only removal patch verified; preserve7 local photos; exact active application and target activation await Steve.",
+    "evidence": [
+      "prestige-parent-proof.json",
+      "prestige/review.dtOpeo/handoff.json"
+    ]
+  },
+  {
+    "position": 3,
+    "id": "TK-11784-yoloforever-autonomous-loop-dtd-cody-gat",
+    "disposition": "no-safe-increment",
+    "reason": "Existing corrected disk/reclaim memos unchanged; fresh pure canary WARN8percent78GiB, negative UNKNOWN; no missing safe prep reproduced. Cleanup remains gated.",
+    "evidence": [
+      "existing-memos.json",
+      "disk-probe.log",
+      "reread-11784.json"
+    ]
+  },
+  {
+    "position": 4,
+    "id": "TK-11852-model-arena-gemma3-12b-missing-from-mac2",
+    "disposition": "prepared-gated",
+    "reason": "Concrete roster/capacity memo verified; missing Gemma, WARN8percent78GiB; intended contender and scoped download approval required.",
+    "evidence": [
+      "model-parent-proof.json",
+      "model-prep/handoff.json"
+    ]
+  },
+  {
+    "position": 5,
+    "id": "TK-11861-phillipe-romano-cost-model-13-066-active",
+    "disposition": "external-blocked",
+    "reason": "PROVISIONAL reassessment: creator process exists but parentTK11427 is done21:24Z; active-source ownership not proven. Domain owner assessing existing artifacts and first safe preparation before final disposition.",
+    "evidence": [
+      "owner-refresh.json",
+      "TK-11861.json"
+    ]
+  },
+  {
+    "position": 6,
+    "id": "TK-11860-cost-backlog-verify-the-column-class-26",
+    "disposition": "external-blocked",
+    "reason": "PROVISIONAL reassessment: creator process exists but parentTK11427 is done21:24Z; active-source ownership not proven. Domain owner assessing existing artifacts and first safe preparation before final disposition.",
+    "evidence": [
+      "owner-refresh.json",
+      "TK-11860.json"
+    ]
+  },
+  {
+    "position": 7,
+    "id": "TK-11851-env-leak-recurrence-18-fanned-out-claude",
+    "disposition": "owner-active",
+    "reason": "Related masterdot worker TK11841 PID94755 and terminal ownerTK11831 PID42322 active; identity diagnosis overlaps their surface; no dispatcher edits/repaint.",
+    "evidence": [
+      "owner-refresh.json",
+      "TK-11851.json"
+    ]
+  }
+]
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/increment1-dtd.json b/verification/yoloforever-yf2139.YF57g8/increment1-dtd.json
new file mode 100644
index 00000000..32ce16cc
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/increment1-dtd.json
@@ -0,0 +1,18 @@
+{
+  "verdict": "A",
+  "valid_tally": "5/5",
+  "dissent": "none; limitations and gates retained",
+  "votes": {
+    "exo.txt": "[exo unavailable: no good+safe model loaded on cluster]\n",
+    "kimi.txt": "[kimi disabled: DTD_ZERO_COST=1 \u2014 paid lens intentionally skipped, not a failure]\n",
+    "heretic.txt": "[ERR heretic]\n",
+    "grok.txt": "[grok disabled: DTD_ZERO_COST=1 \u2014 paid lens intentionally skipped, not a failure]\n",
+    "qwen.txt": "VERDICT: A  \nPreparation is required to ensure compliance with hard gates and ZERO_COST constraints. A LOCAL patch artifact must isolate removal of the unused /api/places/photo/:i route while preserving local photo rendering logic and /api/places mapping, as confirmed by server.js analysis. An approval memo is necessary to document assumptions (e.g., no active dependencies on the removed route, local photo plumbing integrity) and flag any proposal changes. Source/data assumptions must be explicitly reproduced to avoid unintended deletions, and the absence of an existing ticket-specific approval memo necessitates this step. No credential/production changes are permitted, aligning with DTD_ZERO_COST=1 and local-only execution requirements.\n",
+    "codex.txt": "VERDICT: A\n\nCanonical reread confirms TK-11850 remains open and assigned to claude-run-11615; the checkout is clean at `59d77fc`. All seven photo entries resolve to existing local files, with zero Google references. Prepare removal only of the proxy handler and its accompanying comment, preserving `/api/places`, `photo_urls`, and `/media` rendering. This explicitly corrects the ticket\u2019s broader \u201cdelete photos plumbing\u201d proposal; future Google references would still map to the removed endpoint and require separate review. Artifact persistence is blocked by this session\u2019s read-only filesystem, so no patch or approval memo was saved, no ownership claimed, and no closure or execution performed.",
+    "muse.txt": "[disabled \u2014 muse-glimmer removed 2026-08-31 per TK-10980; voter slot retired]\n",
+    "root": "VERDICT: A\nPrepare exact patch with local image boundary preserved; execution remains gated.\n",
+    "ops": "VERDICT: A\n\nPrepare the bounded local patch artifact and approval memo because canonical TK-11850 describes dormant cleanup with no current customer degradation, so creating a key adds credential scope and operational obligations without an established need. The ticket recommendation to delete photos plumbing is broader than the supplied source finding that /api/places maps local images; preserve that mapping and local rendering, and explicitly record the narrower proposal as a correction requiring review. Before proposing route removal, reproduce the local-versus-Google-reference mapping and data assumptions so latent proxy dependencies are surfaced rather than silently severed. Keep the active checkout, service, credentials, and data unchanged, attach evidence and a reversible patch for Steve to review, and retain the source ticket until any approved execution is separately verified.\n\nGuard evidence: directly read /Users/macstudio3/Projects/ticket-system/config/dtd-cost-mode = ZERO_COST_REQUIRED; printenv DTD_ZERO_COST = 1. Read complete a2a-contract and dtd skills and canonical TK-11850 with tk show. Codex voter only; no Claude runtime, external model APIs, or provider spend. Correlation: yf2139-i1-ops.\n",
+    "risk": "VERDICT: A\nPrepare the local patch and approval memo because this permits review without introducing credential exposure, spending authority, or a customer-facing change. The canonical TK-11850 record confirms a dormant key-dependent route and manual scripts, but its earlier suggestion to delete photos plumbing is broader than the present bounded increment: preserve local-photo rendering and /api/places mapping, verify current source/data and references, and explicitly report any Google-reference mapping left pointing at the proposed removed route. The patch must remain an artifact, with no active checkout edits, credential creation, deployment, ticket closure, or assertion that customer behavior is unchanged without evidence. B crosses the credential gate unnecessarily; C leaves authorized low-risk preparation undone.\n\nGuard evidence: independently read /Users/macstudio3/Projects/ticket-system/config/dtd-cost-mode as ZERO_COST_REQUIRED and printenv DTD_ZERO_COST as 1. Read complete a2a-contract/SKILL.md and dtd/SKILL.md and reread canonical ticket with tk show TK-11850. No other voter outputs read. Provider spend: $0; no Claude runtime or paid APIs.\nCorrelation: yf2139-i1-risk\nStatus: complete (independent risk vote only). Changed path: this artifact only; no source edit or commit. Remaining gate: Steve approval for any active/customer-facing/credential execution. Safest next action: parent independently review artifact and prepare the bounded proposal.\n"
+  },
+  "cost_usd": 0
+}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/increment2-dtd.json b/verification/yoloforever-yf2139.YF57g8/increment2-dtd.json
new file mode 100644
index 00000000..b51f0e06
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/increment2-dtd.json
@@ -0,0 +1,18 @@
+{
+  "verdict": "A",
+  "valid_tally": "5/5",
+  "dissent": "none; limitations and gates retained",
+  "votes": {
+    "exo.txt": "[exo unavailable: no good+safe model loaded on cluster]\n",
+    "kimi.txt": "[kimi disabled: DTD_ZERO_COST=1 \u2014 paid lens intentionally skipped, not a failure]\n",
+    "heretic.txt": "[ERR heretic]\n",
+    "grok.txt": "[grok disabled: DTD_ZERO_COST=1 \u2014 paid lens intentionally skipped, not a failure]\n",
+    "qwen.txt": "VERDICT: A  \nPreparation only: A local approval memo is required to restore gemma3:12b, contingent on Steve confirming its intended use and approving the ~8GB download against a fresh disk preflight. The memo must document source roster/tag evidence (e.g., server.js inclusion of gemma3:12b, /api/tags listing qwen2.5vl:7b, hermes3:8b, qwen3:14b) and downstream partial-rating risks (e.g., Mac2 canary WARN8percent78GiB, missing-input UNKNOWN returns). No pull/roster changes or battle actions are permitted without explicit Steve-gated authorization. Existing ticket-specific memo absence is noted, and runtime output files remain isolated but dirty. Model production restart/roster mutation remains under Steve\u2019s control. Codex CLI/local endpoints enforce ZERO_COST_REQUIRED/env1 guardrails. No closure actions are taken.\n",
+    "codex.txt": "VERDICT: A\n\nTK-11852 \u00b7 cycle yf2139.YF57g8 \u2014 Approval memo draft: Restore `gemma3:12b` only after Steve confirms it remains intended and approves the ~8GB download against a fresh Mac2 disk preflight. [Source roster](/Users/macstudio3/Projects/model-arena/server.js:78) includes it; supplied `/api/tags` observations list only `qwen2.5vl:7b`, `hermes3:8b`, and `qwen3:14b`. Disk helper reported `WARN8percent78GiB`; missing-input test returned `UNKNOWN`. Arena `/api/models` returned unauthenticated 401, so availability remains unverified. Missing contenders risk incomplete battle coverage and partial ratings. Creator11723 was absent from the supplied exact owner scan; canonical source remains open. Preserve `ZERO_COST_REQUIRED=1`; no pull, restart, roster change, battle, or closure. Runtime outputs remain untouched. Local memo persistence is blocked by this session\u2019s read-only filesystem; this draft was not saved.",
+    "muse.txt": "[disabled \u2014 muse-glimmer removed 2026-08-31 per TK-10980; voter slot retired]\n",
+    "root": "VERDICT: A\nEvidence-based gated restoration proposal preserves roster intent and disk safety.\n",
+    "ops": "VERDICT: A\n\nPrepare the local approval memo because the current source roster and reported live tags show a configuration-versus-capacity discrepancy, but neither proves whether gemma3:12b was deliberately retired. A blind roughly 8GB pull would consume scarce disk on a host with a disk-critical history, while deleting the contender would change the meaning of future ratings without a confirmed product decision. The memo should present the roster/tag evidence, distinguish the historical ticket claim about /api/models from the present unauthenticated 401, explain the risk of apparently complete ratings from a reduced field, and ask Steve to confirm roster intent before any restoration. If restoration is approved, require a fresh disk preflight with capacity headroom for download and extraction; neither this memo nor the existing WARN reading authorizes a pull, battle, restart, live roster edit, or ticket closure.\n\nGuard evidence: directly read /Users/macstudio3/Projects/ticket-system/config/dtd-cost-mode = ZERO_COST_REQUIRED and printenv DTD_ZERO_COST = 1 again for this increment; canonical TK-11852 read using tk show. Codex only, no external provider calls, spend $0. Correlation: yf2139-i2-ops.\n",
+    "risk": "VERDICT: A\nPrepare the approval memo because the missing intended contender can bias subsequent ratings, but neither a model download nor a live roster removal is authorized. Preserve the distinction between directly observed Ollama tags and source roster evidence, historical ticket observations, and the presently unverified authenticated API availability field. The reported 78 GiB free does not nullify the 8-percent disk warning or prove that an approximately 8 GB download plus transient storage is safe under concurrent workloads; require a fresh capacity check and Steve confirmation that gemma3:12b remains intended before any pull. Record the partial-rating risk and require post-approval verification of model availability and participant completeness before claiming recovery. B spends storage and changes runtime state without approval; C changes contest semantics without confirming retirement intent.\n\nGuard evidence: independently reread /Users/macstudio3/Projects/ticket-system/config/dtd-cost-mode as ZERO_COST_REQUIRED and printenv DTD_ZERO_COST as 1. Read increment2-question.txt and reread canonical TK-11852 with tk show. Required a2a-contract and dtd skills were read in full earlier in this same delegated session. No other voter outputs read. Provider spend $0; no paid API or Claude runtime.\nCorrelation: yf2139-i2-risk\nStatus: complete (independent risk vote only). Changed path: this artifact only; no source edit or commit. Remaining gates: confirmation of intended contender and approval for download or live roster mutation. Safest next action: parent independently inspect this vote and prepare a factual local approval memo, without closing the source ticket.\n"
+  },
+  "cost_usd": 0
+}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/model-parent-proof.json b/verification/yoloforever-yf2139.YF57g8/model-parent-proof.json
new file mode 100644
index 00000000..99568a1b
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-parent-proof.json
@@ -0,0 +1 @@
+{"tags_absent": "PASS", "source_hash_unchanged": "PASS", "disk_WARN8": "PASS", "unauth401_limitation": "RETAINED", "runtime_outcome": "UNVERIFIED"}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/approval-memo.md b/verification/yoloforever-yf2139.YF57g8/model-prep/approval-memo.md
new file mode 100644
index 00000000..7612b3c6
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/approval-memo.md
@@ -0,0 +1,26 @@
+# TK-11852 — Gemma roster and capacity decision
+
+Prepared 2026-09-16T21:59:07.353966+00:00; correlation yf2139-i2-execute; owner/finalizer codex-yoloforever.
+Recommendation: REVISE pending confirmation of intended roster and capacity; conditional APPROVE for the scoped restoration below only after those conditions pass. Source outcome: UNVERIFIED. This is an R0 local review artifact, not a model restoration.
+
+## Decision for Steve
+
+- APPROVE: confirm gemma3:12b remains an intended contender and authorize only its roughly 8GB download on Mac2 after a fresh capacity preflight demonstrates sufficient persistent and temporary download/extraction headroom. Record the model storage volume, expected bytes, transient overhead, concurrent growth allowance, and post-download free reserve; an unquantified reserve or continued disk warning requires a separate explicit capacity decision before proceeding. The existing 8%-free warning is not clearance.
+- REVISE: clarify whether Gemma was deliberately retired and specify the intended roster or a capacity plan; prepare a separately reviewable roster change if retirement is intended.
+- BLOCK: retain the current state and document reduced-field rating risk; do not close TK-11852.
+
+## Concrete evidence
+
+The local source server.js:78 includes gemma3:12b at localhost:11434; commit 80f1b9c942509c55ef6026709344e969805a4a03, SHA-256 9ce80306a635fc81ffccc914f2d5886c875a5d8fb0dcf555ed2c917330860a07. The GET /api/tags returned HTTP 200 and these tags: qwen2.5vl:7b, hermes3:8b, qwen3:14b; gemma3:12b was absent. GET /api/models returned HTTP 401 without authentication: available:false is a historical source-ticket report, not a fresh authenticated measurement. No credentials were loaded to bypass that boundary.
+
+Fresh pure disk probe: {"unit": "Mac2", "reachable": true, "free_pct": 8, "free_gb": 78, "size_gb": 887, "verdict": "WARN"}. Earlier parent evidence was WARN, 8% free, 78 GiB; the helper labels its binary-unit field free_gb. The missing-input probe returned UNKNOWN. The full disk canary was not run because it includes alert/state side effects; only the previously extracted df helper ran.
+
+TK-11852 describes successful-looking rows with partial:false despite a reduced contestant field, which can bias ELO/win-rate interpretation. That is a documented downstream risk, not a newly reproduced battle. No inference, battle, ledger write, model pull, restart, roster edit, model deletion, or source checkout edit occurred. Existing untracked runtime artifacts remain untouched.
+
+## Conditional implementation and acceptance
+
+After explicit approval and roster confirmation: recapture local tags and storage-volume capacity immediately before the intended pull, quantify transient headroom and reserve, and stop if the approved capacity assumptions fail. The scoped action proposed is ollama pull gemma3:12b; no general roster change or daemon restart is included. After any later authorized pull, verify the exact tag/digest, remaining capacity, and authenticated availability using already-authorized credentials without exposing them. Any inference or battle and its ledger writes require an explicitly authorized verification step; do not imply a restored rating pipeline from tag presence alone. Parent/finalizer retains the source ticket until the intended operational outcome has real proof.
+
+## Evidence and rollback
+
+Evidence directory: /private/tmp/yf2139.YF57g8/model-prep. Retained endpoint response bodies/status, roster excerpt, disk and negative probe, checkout status, e2e-proof.json, and handoff.json support this preparation only. Artifacts are additive; revision/superseding is the rollback for this memo. A later pull consumes shared model storage; no autonomous deletion is authorized as rollback. Provider spend: $0. DTD guard read directly: ZERO_COST_REQUIRED; environment DTD_ZERO_COST=1.
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/arena-models.body b/verification/yoloforever-yf2139.YF57g8/model-prep/arena-models.body
new file mode 100644
index 00000000..094b1c99
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/arena-models.body
@@ -0,0 +1 @@
+auth required
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/arena-models.json b/verification/yoloforever-yf2139.YF57g8/model-prep/arena-models.json
new file mode 100644
index 00000000..7b2d6b33
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/arena-models.json
@@ -0,0 +1,6 @@
+{
+  "url": "http://localhost:9758/api/models",
+  "status": 401,
+  "body_path": "/private/tmp/yf2139.YF57g8/model-prep/arena-models.body",
+  "timestamp": "2026-09-16T21:59:07.353966+00:00"
+}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/checkout-status.txt b/verification/yoloforever-yf2139.YF57g8/model-prep/checkout-status.txt
new file mode 100644
index 00000000..a009b4ef
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/checkout-status.txt
@@ -0,0 +1,57 @@
+?? data/artifacts/047c3b8392fa/
+?? data/artifacts/0a0a9e11af9d/
+?? data/artifacts/1a25203f4053/
+?? data/artifacts/2763d4c24f73/
+?? data/artifacts/370279e038b0/claude-code.html
+?? data/artifacts/370279e038b0/qwen25-7b.html
+?? data/artifacts/370279e038b0/qwen3-14b.html
+?? data/artifacts/37c005ebdc66/
+?? data/artifacts/38ff3a42e334/
+?? data/artifacts/4312ba790371/claude-code.html
+?? data/artifacts/4312ba790371/qwen25-7b.html
+?? data/artifacts/4312ba790371/qwen3-14b.html
+?? data/artifacts/46d29b76eccd/
+?? data/artifacts/6a21079e1cf0/qwen3-14b.html
+?? data/artifacts/826c70c1203f/
+?? data/artifacts/8a56b59e83f2/
+?? data/artifacts/93852a2b65ff/claude-code.html
+?? data/artifacts/93852a2b65ff/qwen25-7b.html
+?? data/artifacts/93852a2b65ff/qwen3-14b.html
+?? data/artifacts/98e154faf854/
+?? data/artifacts/9a879cc61947/claude-code.html
+?? data/artifacts/9a879cc61947/qwen25-7b.html
+?? data/artifacts/9a879cc61947/qwen3-14b.html
+?? data/artifacts/9b9ed3ebe477/
+?? data/artifacts/9e978d77c375/
+?? data/artifacts/a3f76984cd9c/claude-code.html
+?? data/artifacts/a3f76984cd9c/qwen25-7b.html
+?? data/artifacts/a3f76984cd9c/qwen3-14b.html
+?? data/artifacts/b2babfd5dbc5/claude-code.html
+?? data/artifacts/b2babfd5dbc5/qwen25-7b.html
+?? data/artifacts/b2babfd5dbc5/qwen3-14b.html
+?? data/artifacts/c60235b46976/qwen25-7b.html
+?? data/artifacts/c60235b46976/qwen3-14b.html
+?? data/artifacts/c8e81379ec15/claude-code.html
+?? data/artifacts/c8e81379ec15/qwen25-7b.html
+?? data/artifacts/c8e81379ec15/qwen3-14b.html
+?? data/artifacts/e05d1f859c94/
+?? data/artifacts/e2f4ae36f757/claude-code.html
+?? data/artifacts/e2f4ae36f757/qwen25-7b.html
+?? data/artifacts/e2f4ae36f757/qwen3-14b.html
+?? data/artifacts/e3e7e829b1f6/
+?? data/artifacts/e5ca87e59211/
+?? data/artifacts/e5e301be79a9/claude-code.html
+?? data/artifacts/e5e301be79a9/qwen25-7b.html
+?? data/artifacts/e5e301be79a9/qwen3-14b.html
+?? data/artifacts/e67884672ccf/qwen25-7b.html
+?? data/artifacts/e67884672ccf/qwen3-14b.html
+?? data/artifacts/f3bef7cf51a6/claude-code.html
+?? data/artifacts/f3bef7cf51a6/qwen25-7b.html
+?? data/artifacts/f3bef7cf51a6/qwen3-14b.html
+?? data/landings/127317180daf-claude-code.html
+?? data/landings/96c1603e5f4a-qwen3-14b.html
+?? data/landings/cd83de847d24-claude-code.html
+?? data/landings/e5ca87e59211-claude-code.html
+?? data/landings/e5ca87e59211-qwen3-14b.html
+?? data/landings/wallcovering-catalog.html
+?? wall-of-sound.html
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/e2e-proof.json b/verification/yoloforever-yf2139.YF57g8/model-prep/e2e-proof.json
new file mode 100644
index 00000000..0051b8ef
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/e2e-proof.json
@@ -0,0 +1,61 @@
+{
+  "intent": "Prepare reviewable roster/capacity approval memo only",
+  "risk_tier": "R0",
+  "environment": "local Mac2 endpoints",
+  "build_commit": "80f1b9c942509c55ef6026709344e969805a4a03",
+  "timestamp": "2026-09-16T21:59:07.353966+00:00",
+  "baseline": "Gemma in source roster, absent tags; runtime checkout has unrelated untracked output",
+  "commands": [
+    "GET http://localhost:11434/api/tags (unauthenticated; timeout 8s)",
+    "GET http://localhost:9758/api/models (unauthenticated; timeout 8s)",
+    "bash /private/tmp/yf2139.YF57g8/disk-probe.sh (pure extracted probe only)"
+  ],
+  "assertions": [
+    {
+      "check": "roster entry captured",
+      "verdict": "PASS"
+    },
+    {
+      "check": "fresh tags exclude gemma3:12b",
+      "verdict": "PASS"
+    },
+    {
+      "check": "unauthenticated availability boundary",
+      "verdict": "PASS"
+    },
+    {
+      "check": "disk missing-input fails unknown",
+      "verdict": "PASS"
+    },
+    {
+      "check": "source server hash unchanged",
+      "verdict": "PASS"
+    },
+    {
+      "check": "restoration and battle critical path",
+      "verdict": "SKIP",
+      "reason": "Not authorized in preparation scope; source outcome UNVERIFIED"
+    },
+    {
+      "check": "memo boundaries and required approval labels",
+      "verdict": "PASS"
+    }
+  ],
+  "artifacts": [
+    "/private/tmp/yf2139.YF57g8/model-prep/approval-memo.md",
+    "/private/tmp/yf2139.YF57g8/model-prep/disk-probe.log",
+    "/private/tmp/yf2139.YF57g8/model-prep/arena-models.json",
+    "/private/tmp/yf2139.YF57g8/model-prep/ollama-tags.body",
+    "/private/tmp/yf2139.YF57g8/model-prep/ollama-tags.json",
+    "/private/tmp/yf2139.YF57g8/model-prep/checkout-status.txt",
+    "/private/tmp/yf2139.YF57g8/model-prep/roster-source.txt",
+    "/private/tmp/yf2139.YF57g8/model-prep/arena-models.body",
+    "/Users/macstudio3/.claude/yolo-queue/pending-approval/TK-11852-gemma-roster-capacity-yf2139.md"
+  ],
+  "negative_auth_checks": "401 API boundary and UNKNOWN disk missing input",
+  "cleanup_rollback": "Evidence retained; no runtime changes; supersede memo to revise",
+  "preparation_status": "complete",
+  "source_outcome": "UNVERIFIED",
+  "correlation": "yf2139-i2-execute",
+  "provider_spend_usd": 0
+}
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/handoff.json b/verification/yoloforever-yf2139.YF57g8/model-prep/handoff.json
new file mode 100644
index 00000000..41d92b6b
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/handoff.json
@@ -0,0 +1,14 @@
+{
+  "schema_version": 1,
+  "task_id": "yf2139-i2-execute",
+  "source_ticket": "TK-11852",
+  "status": "complete",
+  "scope": "R0 preparation only",
+  "source_outcome": "UNVERIFIED",
+  "memo": "/Users/macstudio3/.claude/yolo-queue/pending-approval/TK-11852-gemma-roster-capacity-yf2139.md",
+  "evidence": "/private/tmp/yf2139.YF57g8/model-prep/e2e-proof.json",
+  "commit": null,
+  "parent_acceptance": "PENDING",
+  "remaining_gate": "Steve intended-roster confirmation plus scoped approval and fresh capacity/temporary-headroom preflight",
+  "no_runtime_actions": true
+}
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/ollama-tags.body b/verification/yoloforever-yf2139.YF57g8/model-prep/ollama-tags.body
new file mode 100644
index 00000000..19d9eb7f
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/ollama-tags.body
@@ -0,0 +1 @@
+{"models":[{"name":"qwen2.5vl:7b","model":"qwen2.5vl:7b","modified_at":"2026-09-08T15:49:43.544986944-07:00","size":5969245856,"digest":"5ced39dfa4bac325dc183dd1e4febaa1c46b3ea28bce48896c8e69c1e79611cc","details":{"parent_model":"","format":"gguf","family":"qwen25vl","families":["qwen25vl"],"parameter_size":"8.3B","quantization_level":"Q4_K_M","context_length":128000,"embedding_length":3584},"capabilities":["vision","completion"]},{"name":"hermes3:8b","model":"hermes3:8b","modified_at":"2026-09-08T15:39:28.340461404-07:00","size":4661227243,"digest":"4f6b83f30b62bc3d0cf9be09266db222805ee815c8fd7d8b38f863f655be78b7","details":{"parent_model":"","format":"gguf","family":"llama","families":["llama"],"parameter_size":"8.0B","quantization_level":"Q4_0","context_length":131072,"embedding_length":4096},"capabilities":["completion","tools"]},{"name":"qwen3:14b","model":"qwen3:14b","modified_at":"2026-03-16T11:27:16-07:00","size":9276198565,"digest":"bdbd181c33f2ed1b31c972991882db3cf4d192569092138a7d29e973cd9debe8","details":{"parent_model":"","format":"gguf","family":"qwen3","families":["qwen3"],"parameter_size":"14.8B","quantization_level":"Q4_K_M","context_length":40960,"embedding_length":5120},"capabilities":["completion","tools","thinking"]}]}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/ollama-tags.json b/verification/yoloforever-yf2139.YF57g8/model-prep/ollama-tags.json
new file mode 100644
index 00000000..544d0558
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/ollama-tags.json
@@ -0,0 +1,6 @@
+{
+  "url": "http://localhost:11434/api/tags",
+  "status": 200,
+  "body_path": "/private/tmp/yf2139.YF57g8/model-prep/ollama-tags.body",
+  "timestamp": "2026-09-16T21:59:07.353966+00:00"
+}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/model-prep/roster-source.txt b/verification/yoloforever-yf2139.YF57g8/model-prep/roster-source.txt
new file mode 100644
index 00000000..146427c7
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/model-prep/roster-source.txt
@@ -0,0 +1 @@
+78:   { id: 'gemma3-12b', label: 'Gemma3 12B',   kind: 'local', host: 'http://localhost:11434',      model: 'gemma3:12b', estCost: 0 },
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige-parent-proof.json b/verification/yoloforever-yf2139.YF57g8/prestige-parent-proof.json
new file mode 100644
index 00000000..eea0db30
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige-parent-proof.json
@@ -0,0 +1,7 @@
+{
+  "patch_apply": "PASS",
+  "candidate_syntax": "PASS",
+  "exact_removal": "PASS",
+  "source_unchanged": "PASS",
+  "note": "Initial parent syntax invocation used wrong filename; corrected to server.candidate.js and passed."
+}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/api-places.json b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/api-places.json
new file mode 100644
index 00000000..f60ff1ad
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/api-places.json
@@ -0,0 +1 @@
+{"_mock":false,"_note":"Populated from public listings 2026-07-04: Yelp biz page (prestige-hand-car-wash-los-angeles), owner Instagram @prestigecwash, and a receipt photo confirming address+phone. Rating/review-count intentionally null until the Google Places API key is added (gated) — never display numbers we can't verify.","name":"Prestige Car Wash","legal_name":"Prestige Hand Car Wash","address":"7601 Reseda Blvd, Reseda, CA 91335","cross_street":"Reseda Blvd & Saticoy St","phone":"(747) 265-6293","rating":4.4,"reviews_count":34,"hours":["Mon–Sat 8:00 AM – 6:00 PM","Sun 8:00 AM – 5:00 PM"],"website":"https://prestige.agentabrams.com","yelp_url":"https://www.yelp.com/biz/prestige-hand-car-wash-los-angeles","instagram":"https://www.instagram.com/prestigecwash/","maps_query":"https://www.google.com/maps/search/Prestige+Hand+Car+Wash+7601+Reseda+Blvd+Reseda+CA+91335","place_id":"ChIJ5Wj7RtKbwoAR-3obIxcKE1g","photos":["media/real/real-mustang-foam.jpg","media/real/real-tunnel-benz.jpg","media/real/real-type-r.jpg","media/real/real-brabus-g900.jpg","media/real/real-corvette.jpg","media/real/real-price-board.jpg","media/real/real-storefront.jpg"],"videos":[{"src":"/media/real/real-lot-tour.mp4","poster":"/media/real/real-lot-tour-poster.jpg","label":"Lot tour — fresh off the line"},{"src":"/media/real/real-wheel-detail.mp4","poster":"/media/real/real-wheel-detail-poster.jpg","label":"Hand wheel detail at Reseda & Saticoy"},{"src":"/media/real/real-unlimited-member.mp4","poster":"/media/real/real-unlimited-member-poster.jpg","label":"An unlimited-wash member's visit"}],"reviews":[{"source":"Yelp","author":"Janey E.","rating":5,"date":"2026-04","text":"I almost don’t even want to give this place five stars or even write a review because it genuinely feels like something I want to gatekeep — but they deserve all the business they can get."},{"source":"Yelp","author":"June Q.","rating":5,"date":"2026-06","text":"Great service… My car is so clean. Thank you! I highly recommend. The space is also very spacious, and there’s plenty of seating!"},{"source":"Yelp","author":"Tony B.","rating":5,"date":"2025-09","text":"Best car wash around! Super professional, fast, and my car came out spotless. The hand wash makes a huge difference. The attention to detail is next level. Highly recommend."},{"source":"Yelp","author":"Alan G.","rating":5,"date":"2025-12","text":"Very great pricing for very great washes. Nelson was very friendly. He took great care of my Corvette inside and out and gave attention to all the little details. My car came out spotless."},{"source":"Yelp","author":"CustomDip W.","rating":5,"date":"2026-01","text":"I’ll start off by saying wow!!! This car wash staff is amazing, very professional, kind, and caring in all aspects. This is my 8th time bringing my car here."},{"source":"Yelp","author":"Paige M.","rating":5,"date":"2026-06","text":"If you’re in Reseda, definitely come here for a car wash. I was so happy to see that they were able to reopen! The staff are incredibly kind, attentive, and friendly."},{"source":"Yelp","author":"Arias A.","rating":5,"date":"2026-01","text":"The convenience of drive-up full-service car washes seems to be going the way of the dinosaurs. Most classic spots have been reworked by the self-serve monthly membership brands. Prestige is different."},{"source":"Yelp","author":"Jordana K.","rating":5,"date":"2026-06","text":"I had just come off a long road trip and my car was a mess with the dirt and the bugs. I didn’t have a lot of time. They suggested an express wash and took care making sure it was all handled."},{"source":"Yelp","author":"Magin B.","rating":5,"date":"2026-05","text":"I’m updating my review because our second experience completely changed my opinion."}],"updated_at":"2026-07-05T09:45:00-07:00","rating_source":"Yelp","rating_note":"Yelp page shows 4.4/34 (2026-03); Apple Maps Yelp widget 4.1/45; MapQuest Yelp feed 4.5/24. Google rating unverified (no Places key yet).","photo_urls":["/media/real/real-mustang-foam.jpg","/media/real/real-tunnel-benz.jpg","/media/real/real-type-r.jpg","/media/real/real-brabus-g900.jpg","/media/real/real-corvette.jpg","/media/real/real-price-board.jpg","/media/real/real-storefront.jpg"]}
\ No newline at end of file
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/e2e-proof.json b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/e2e-proof.json
new file mode 100644
index 00000000..a695b9dc
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/e2e-proof.json
@@ -0,0 +1,142 @@
+{
+  "schema_version": 1,
+  "task_id": "yf2139-i1-execute",
+  "ticket": "TK-11850",
+  "timestamp": "2026-09-16T21:49:05.838097+00:00",
+  "intent": "R0 reviewable patch and approval memo preparation only",
+  "risk_tier": "R0",
+  "environment": "local retained artifacts; existing service read-only",
+  "cost": "$0 (local)",
+  "cost_guards": {
+    "config": "ZERO_COST_REQUIRED",
+    "DTD_ZERO_COST": "1"
+  },
+  "source_commit": "59d77fcb08e950ea77e66f3dc287339a1fef7d0b",
+  "hashes": {
+    "source_sha256": "03776959d3f31aaec8235d5a1a23efcf6d375194bfcd0920ba507c5442330eaf",
+    "candidate_sha256": "1cfa7a06beed1971eb71a04450085e637dddb56eea9d999c47ea076b8c8b7b93",
+    "patch_sha256": "8f25354cc18546a1ed217fb7bb0e6f61e0397b21b9fefa7f3d44c151ecf49836",
+    "places_data_sha256": "7047d9297a71d4223d7a5eba0e2b379384444488074225d7cb95ae5e3a5dd40e"
+  },
+  "checks": [
+    {
+      "name": "patch_applies",
+      "command": [
+        "git",
+        "-C",
+        "/Users/macstudio3/Projects/prestige-car-wash",
+        "apply",
+        "--check",
+        "/private/tmp/yf2139.YF57g8/prestige/review.dtOpeo/photo-route.patch"
+      ],
+      "exit_code": 0,
+      "stdout": "",
+      "stderr": "",
+      "verdict": "PASS"
+    },
+    {
+      "name": "candidate_syntax",
+      "command": [
+        "node",
+        "--check",
+        "/private/tmp/yf2139.YF57g8/prestige/review.dtOpeo/server.candidate.js"
+      ],
+      "exit_code": 0,
+      "stdout": "",
+      "stderr": "",
+      "verdict": "PASS"
+    },
+    {
+      "name": "source_status",
+      "command": [
+        "git",
+        "-C",
+        "/Users/macstudio3/Projects/prestige-car-wash",
+        "status",
+        "--porcelain"
+      ],
+      "exit_code": 0,
+      "stdout": "",
+      "stderr": "",
+      "verdict": "PASS"
+    }
+  ],
+  "local_photos": [
+    {
+      "path": "media/real/real-mustang-foam.jpg",
+      "bytes": 246937,
+      "sha256": "730975c056bb5b4676779342fe2f94da6904ad0d4958d8f16f1d6666646facdf"
+    },
+    {
+      "path": "media/real/real-tunnel-benz.jpg",
+      "bytes": 262780,
+      "sha256": "5884264aa4726460a7ca6e4a25987b25cab3b155e9c1e811387172e0325e3485"
+    },
+    {
+      "path": "media/real/real-type-r.jpg",
+      "bytes": 266357,
+      "sha256": "c5b0687f7e954ed4dc8f00a781c8527195005be2c325e04b76f5b368b2964970"
+    },
+    {
+      "path": "media/real/real-brabus-g900.jpg",
+      "bytes": 168686,
+      "sha256": "69abd387ffc90162f4721ef640163a9066e7aff25d47e1cb1dda804ec95febee"
+    },
+    {
+      "path": "media/real/real-corvette.jpg",
+      "bytes": 130548,
+      "sha256": "f1cd56877942bc77e8089133ae4591c7b8b81b25a0b2b088fc4924cdaafbeeee"
+    },
+    {
+      "path": "media/real/real-price-board.jpg",
+      "bytes": 120361,
+      "sha256": "2f90a01cfcb3e17247dd5e055f0365aacffe027285e147787604f05ade7b18e1"
+    },
+    {
+      "path": "media/real/real-storefront.jpg",
+      "bytes": 268309,
+      "sha256": "33373f87b7e0e919238f28303128111909cf53dfe2e996022cb7182720804e60"
+    }
+  ],
+  "api": {
+    "status": 200,
+    "count": 7,
+    "matches_local_data": true
+  },
+  "local_photo": {
+    "status": 200,
+    "content_type": "image/jpeg",
+    "url": "http://127.0.0.1:9808/media/real/real-mustang-foam.jpg",
+    "sha256": "730975c056bb5b4676779342fe2f94da6904ad0d4958d8f16f1d6666646facdf",
+    "matches_file": true
+  },
+  "frontend_indirect_consumer": "public/index.html:177 fetch /api/places; :183 photo_urls; :184 maps URLs into img src. A missing literal proxy URL is not proof of no consumer.",
+  "exact_scope": "Only handler, two adjacent comment lines, and separating blank line removed. Local /api/places, mapping, media static mount and data unchanged.",
+  "rollback_rehearsal": "PASS reverse patch on isolated candidate restores exact source bytes",
+  "source_unchanged": "PASS bytes and clean git status",
+  "limitations": [
+    "Existing /api/places mapping still emits /api/places/photo/:i for future places/ refs; future Google refs require separate decision before enabling.",
+    "No assertion of external caller absence or live production equivalence."
+  ],
+  "skips": [
+    {
+      "check": "proxy GET",
+      "verdict": "SKIP",
+      "reason": "Could call billed Google endpoint; prohibited."
+    },
+    {
+      "check": "candidate runtime/UI and deployment",
+      "verdict": "SKIP",
+      "reason": "Not authorized; preparation only; source outcome unverified."
+    },
+    {
+      "check": "production post-activation and credential checks",
+      "verdict": "SKIP",
+      "reason": "Steve gate; no credentials read/changed or external Google calls."
+    }
+  ],
+  "cleanup": "All temporary evidence retained; no active source/data changes; no service restart.",
+  "verdict": "PASS R0 preparation evidence; source outcome UNVERIFIED",
+  "approval_memo": "/Users/macstudio3/.claude/yolo-queue/pending-approval/TK-11850-photo-route-decommission-yf2139.md",
+  "memo_sha256": "b5bc625c524a38314247aa7fe7e852735207dedf112c8aa235165cdca97c74a1"
+}
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/handoff.json b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/handoff.json
new file mode 100644
index 00000000..c16d3a5d
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/handoff.json
@@ -0,0 +1,62 @@
+{
+  "schema_version": 1,
+  "task_id": "yf2139-i1-execute",
+  "ticket": "TK-11850",
+  "delegation_chain": [
+    "root",
+    "prestige_prep"
+  ],
+  "status": "complete",
+  "scope": "R0 preparation only",
+  "source_outcome": "UNVERIFIED; active source unchanged",
+  "changed_paths": [
+    "/private/tmp/yf2139.YF57g8/prestige/review.dtOpeo",
+    "/Users/macstudio3/.claude/yolo-queue/pending-approval/TK-11850-photo-route-decommission-yf2139.md"
+  ],
+  "commit_ids": [],
+  "source_commit": "59d77fcb08e950ea77e66f3dc287339a1fef7d0b",
+  "hashes": {
+    "source_sha256": "03776959d3f31aaec8235d5a1a23efcf6d375194bfcd0920ba507c5442330eaf",
+    "candidate_sha256": "1cfa7a06beed1971eb71a04450085e637dddb56eea9d999c47ea076b8c8b7b93",
+    "patch_sha256": "8f25354cc18546a1ed217fb7bb0e6f61e0397b21b9fefa7f3d44c151ecf49836",
+    "places_data_sha256": "7047d9297a71d4223d7a5eba0e2b379384444488074225d7cb95ae5e3a5dd40e"
+  },
+  "memo_sha256": "b5bc625c524a38314247aa7fe7e852735207dedf112c8aa235165cdca97c74a1",
+  "checks": {
+    "patch_applies": "PASS",
+    "candidate_syntax": "PASS",
+    "exact_removal_only": "PASS",
+    "seven_local_paths_and_files": "PASS",
+    "existing_local_api": "PASS 200; seven local URLs",
+    "existing_local_photo": "PASS 200 image/jpeg; byte match",
+    "frontend_indirect_consumer": "PASS traced photo_urls to img src",
+    "rollback_rehearsal": "PASS byte-exact",
+    "source_untouched": "PASS hashes and clean status"
+  },
+  "evidence_path": "/private/tmp/yf2139.YF57g8/prestige/review.dtOpeo/e2e-proof.json",
+  "skips": [
+    {
+      "check": "proxy GET",
+      "verdict": "SKIP",
+      "reason": "Could call billed Google endpoint; prohibited."
+    },
+    {
+      "check": "candidate runtime/UI and deployment",
+      "verdict": "SKIP",
+      "reason": "Not authorized; preparation only; source outcome unverified."
+    },
+    {
+      "check": "production post-activation and credential checks",
+      "verdict": "SKIP",
+      "reason": "Steve gate; no credentials read/changed or external Google calls."
+    }
+  ],
+  "residual_risks": [
+    "Existing /api/places mapping still emits /api/places/photo/:i for future places/ refs; future Google refs require separate decision before enabling.",
+    "No assertion of external caller absence or live production equivalence."
+  ],
+  "approvals_required": "Steve approval for exact active-source patch; target-specific approval before restart/deploy/customer activation",
+  "safest_next_action": "Parent independently inspect patch and evidence; keep source outcome unverified, present memo for Steve decision",
+  "cost": "$0 (local)",
+  "terminal_state": "Not painted; inherited parent ticket; parent owns terminal verification"
+}
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/photo-route.patch b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/photo-route.patch
new file mode 100644
index 00000000..b3f2ed19
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/photo-route.patch
@@ -0,0 +1,25 @@
+--- a/server.js
++++ b/server.js
+@@ -450,22 +450,6 @@
+ });
+ 
+ // ---- static + clean URLs ---------------------------------------------------
+-// Google Place photo proxy — fetches photo media with the server-side key and streams
+-// it, so the key never reaches the browser. Cached to avoid re-billing on every view.
+-app.get('/api/places/photo/:i', async (req, res) => {
+-  const key = process.env.GOOGLE_PLACES_API_KEY;
+-  const name = (places().photos || [])[parseInt(req.params.i, 10)];
+-  if (!key || !name) return res.status(404).end();
+-  const w = Math.min(1600, Math.max(100, parseInt(req.query.w, 10) || 800));
+-  try {
+-    const r = await fetch(`https://places.googleapis.com/v1/${name}/media?maxWidthPx=${w}&key=${key}`);
+-    if (!r.ok) return res.status(502).end();
+-    res.set('Content-Type', r.headers.get('content-type') || 'image/jpeg');
+-    res.set('Cache-Control', 'public, max-age=86400');
+-    res.end(Buffer.from(await r.arrayBuffer()));
+-  } catch { res.status(502).end(); }
+-});
+-
+ // Inline SVG favicon (avoids a 404 on the browser's automatic request).
+ app.get('/favicon.svg', (req, res) => res.type('image/svg+xml').send(
+   '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#0a84ff"/><text x="16" y="22" font-size="18" text-anchor="middle">🚗</text></svg>'));
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/rollback/server.js b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/rollback/server.js
new file mode 100644
index 00000000..2e8a2863
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/rollback/server.js
@@ -0,0 +1,910 @@
+'use strict';
+/**
+ * Prestige Car Wash (PCW) — one Express app serving:
+ *   - the public marketing site (/, /services, /contact)
+ *   - the Basic-Auth growth admin (/admin) + its bucket APIs (/api/admin/*)
+ *
+ * Data source: data/*.json snapshots (Postgres is an optional future upgrade; the
+ * schema in scripts/db-init.sql mirrors these files). Everything the front-end needs
+ * comes through /api/* so the same catalog powers both the public page and the admin.
+ */
+require('dotenv').config();
+const fs = require('fs');
+const path = require('path');
+const express = require('express');
+const helmet = require('helmet');
+
+const app = express();
+const PORT = process.env.PORT || 9808;
+const ADMIN_USER = process.env.ADMIN_USER || 'admin';
+const ADMIN_PASS = process.env.ADMIN_PASS || 'DW2024!';
+const DATA = path.join(__dirname, 'data');
+
+// ---- helpers ---------------------------------------------------------------
+const readJSON = (f, fallback) => {
+  try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); }
+  catch { return fallback; }
+};
+// Re-read on each request so admin edits / script runs show up without a restart.
+const services     = () => readJSON('services.json', []);
+const competitors  = () => readJSON('competitors.json', []);
+const suggestions  = () => readJSON('suggestions.json', []);
+const holidays     = () => readJSON('holidays.json', []);
+const directories  = () => readJSON('directories.json', []);
+const ads          = () => readJSON('ads.json', []);
+const bestTimes    = () => readJSON('best-times.json', {});
+const places       = () => readJSON('places.json', {});
+const socialTpl    = () => readJSON('social-templates.json', { platforms: [], hashtag_sets: {}, templates: [] });
+const promos       = () => readJSON('promos.json', { promos: [] });
+
+// Mask a secret to a "present (…last4)" descriptor — never returns the value.
+const maskEnv = (key) => {
+  const v = process.env[key];
+  if (!v) return { key, present: false, hint: '' };
+  return { key, present: true, hint: '…' + String(v).slice(-4) };
+};
+
+// ---- middleware ------------------------------------------------------------
+app.use(helmet({
+  contentSecurityPolicy: {
+    directives: {
+      defaultSrc: ["'self'"],
+      scriptSrc: ["'self'", "'unsafe-inline'"],
+      // inline event-handler attributes (onclick=, onerror=) are a separate directive;
+      // Helmet defaults it to 'none'. The site/admin use inline handlers (house style,
+      // Basic-Auth admin, no user-generated content) so allow them explicitly.
+      scriptSrcAttr: ["'unsafe-inline'"],
+      styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
+      fontSrc: ["'self'", 'https://fonts.gstatic.com', 'data:'],
+      imgSrc: ["'self'", 'data:', 'https:'],
+      mediaSrc: ["'self'", 'data:', 'blob:'],
+      connectSrc: ["'self'"]
+    }
+  }
+}));
+app.use(express.json());
+
+// Basic Auth gate for /admin and /api/admin/*
+function requireAdmin(req, res, next) {
+  const hdr = req.headers.authorization || '';
+  const [scheme, encoded] = hdr.split(' ');
+  if (scheme === 'Basic' && encoded) {
+    const [u, p] = Buffer.from(encoded, 'base64').toString().split(':');
+    if (u === ADMIN_USER && p === ADMIN_PASS) return next();
+  }
+  res.set('WWW-Authenticate', 'Basic realm="Prestige Admin"').status(401).send('Auth required');
+}
+app.use('/admin', requireAdmin);
+app.use('/api/admin', requireAdmin);
+
+// ---- sort (shared by public services grid) --------------------------------
+function sortServices(list, mode) {
+  const arr = [...list];
+  const byTitle = (a, b) => String(a.name).localeCompare(String(b.name));
+  switch (mode) {
+    case 'title':      return arr.sort(byTitle);
+    case 'price-asc':  return arr.sort((a, b) => (a.price || 0) - (b.price || 0) || byTitle(a, b));
+    case 'price-desc': return arr.sort((a, b) => (b.price || 0) - (a.price || 0) || byTitle(a, b));
+    case 'duration':   return arr.sort((a, b) => (a.duration_min || 0) - (b.duration_min || 0) || byTitle(a, b));
+    case 'featured':
+    default:           return arr.sort((a, b) => ((b.featured ? 1 : 0) - (a.featured ? 1 : 0)) || (a.sort_order || 99) - (b.sort_order || 99));
+  }
+}
+
+// Price bands for the left filter panel.
+const priceBand = (p) => p <= 25 ? 'Under $25' : p <= 60 ? '$25–$60' : p <= 150 ? '$60–$150' : '$150+';
+
+// ---- public API ------------------------------------------------------------
+app.get('/api/health', (req, res) => {
+  res.json({
+    ok: true, service: 'prestige-car-wash', port: PORT,
+    counts: {
+      services: services().length, competitors: competitors().length,
+      suggestions: suggestions().length, holidays: holidays().length,
+      directories: directories().length, ads: ads().length,
+      promos: (promos().promos || []).length
+    },
+    ts: new Date().toISOString()
+  });
+});
+
+app.get('/api/services', (req, res) => {
+  let list = services().map(s => ({ ...s, price_band: priceBand(s.price || 0) }));
+  const { category, band, q } = req.query;
+  if (category) list = list.filter(s => s.category === category);
+  if (band) list = list.filter(s => s.price_band === band);
+  if (q) {
+    const needle = String(q).toLowerCase();
+    list = list.filter(s => [s.name, s.blurb, s.category].join(' ').toLowerCase().includes(needle));
+  }
+  res.json(sortServices(list, req.query.sort));
+});
+
+// Facet counts for the left panel — each dimension counted over the OTHER active filters.
+app.get('/api/facets', (req, res) => {
+  const base = services().map(s => ({ ...s, price_band: priceBand(s.price || 0) }));
+  const tally = (rows, key) => rows.reduce((m, r) => (m[r[key]] = (m[r[key]] || 0) + 1, m), {});
+  const applyExcept = (except) => base.filter(s =>
+    (except === 'category' || !req.query.category || s.category === req.query.category) &&
+    (except === 'band' || !req.query.band || s.price_band === req.query.band)
+  );
+  res.json({
+    category: tally(applyExcept('category'), 'category'),
+    band: tally(applyExcept('band'), 'price_band')
+  });
+});
+
+app.get('/api/best-times', (req, res) => res.json(bestTimes()));
+// Emit ready-to-use photo/video URLs so the client is format-agnostic:
+// Google photo refs → the proxy route; local paths (real business photos) → direct.
+app.get('/api/places', (req, res) => {
+  const p = places();
+  const photo_urls = (p.photos || []).map((ph, i) =>
+    /^places\//.test(ph) ? `/api/places/photo/${i}` : '/' + String(ph).replace(/^\//, ''));
+  const videos = (p.videos || []).map(v => ({
+    ...v,
+    src: '/' + String(v.src || '').replace(/^\//, ''),
+    poster: v.poster ? '/' + String(v.poster).replace(/^\//, '') : ''
+  }));
+  res.json({ ...p, photo_urls, videos });
+});
+
+// ---- admin bucket APIs -----------------------------------------------------
+app.get('/api/admin/competitors', (req, res) => res.json(competitors()));
+app.get('/api/admin/suggestions', (req, res) => res.json(suggestions()));
+app.get('/api/admin/holidays', (req, res) => res.json(holidays()));
+app.get('/api/admin/directories', (req, res) => res.json(directories()));
+app.get('/api/admin/ads', (req, res) => res.json(ads()));
+app.get('/api/admin/services', (req, res) => res.json(services()));
+app.get('/api/admin/best-times', (req, res) => res.json(bestTimes()));
+app.get('/api/admin/places', (req, res) => res.json(places()));
+
+// ---- Socials: create + post from ONE place (draft-only; never auto-posts) --
+// Media library: every image/video under /media (incl. media/real/), each video
+// paired with its poster image (svc-<stem>.png or <stem>-poster.jpg) so the
+// composer can preview + attach any asset. Read-only scan of the media dir.
+const MEDIA_DIR = path.join(__dirname, 'media');
+function mediaLibrary() {
+  const walk = (dir, prefix) => {
+    let out = [];
+    let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return out; }
+    for (const e of entries) {
+      if (e.isDirectory()) { out = out.concat(walk(path.join(dir, e.name), prefix + e.name + '/')); continue; }
+      const ext = path.extname(e.name).toLowerCase();
+      const kind = ['.mp4', '.mov', '.webm'].includes(ext) ? 'video'
+        : ['.png', '.jpg', '.jpeg', '.webp', '.gif'].includes(ext) ? 'image' : null;
+      if (!kind) continue;
+      out.push({ name: e.name, stem: e.name.replace(/\.[^.]+$/, ''), kind, url: '/media/' + prefix + e.name });
+    }
+    return out;
+  };
+  const all = walk(MEDIA_DIR, '');
+  const imgByStem = {};
+  all.filter(m => m.kind === 'image').forEach(m => { imgByStem[m.stem] = m.url; });
+  all.forEach(m => { if (m.kind === 'video') m.poster = imgByStem[m.stem] || imgByStem[m.stem + '-poster'] || ''; });
+  // Hide poster stills that only exist to back a video (keep genuine standalone photos).
+  const videoStems = new Set(all.filter(m => m.kind === 'video').map(m => m.stem));
+  return all
+    .filter(m => !(m.kind === 'image' && (videoStems.has(m.stem) || videoStems.has(m.stem.replace(/-poster$/, '')))))
+    .sort((a, b) => a.name.localeCompare(b.name));
+}
+app.get('/api/admin/media', (req, res) => res.json(mediaLibrary()));
+app.get('/api/admin/social/templates', (req, res) => res.json(socialTpl()));
+
+// ---- Promos: HyperFrames-generated brand films -----------------------------
+// Curated, first-class list of the finished brand videos (distinct from the raw
+// svc-* service clips in the media library). Repo-relative src/poster are
+// normalized to absolute /media URLs so the client stays format-agnostic; any
+// promo whose file is missing on disk is flagged (ready=false) rather than
+// served as a broken <video>. mm:ss is precomputed for the card badge.
+const abs = (p) => '/' + String(p || '').replace(/^\//, '');
+const mmss = (s) => { const sec=Math.max(0,parseInt(s,10)||0); return `${Math.floor(sec/60)}:${String(sec%60).padStart(2,'0')}`; };
+app.get('/api/admin/promos', (req, res) => {
+  const list = (promos().promos || []).map(p => {
+    const rel = String(p.src || '').replace(/^\//, '');
+    const ready = rel ? fs.existsSync(path.join(__dirname, rel)) : false;
+    return {
+      ...p,
+      src: abs(p.src),
+      poster: p.poster ? abs(p.poster) : '',
+      duration_label: p.duration_sec ? mmss(p.duration_sec) : '',
+      ready
+    };
+  });
+  res.json(list);
+});
+
+// Save a composed post as a DRAFT (append-only). This NEVER posts to any network —
+// the front-end deep-links into each platform's native composer for the human to post.
+app.post('/api/admin/social/draft', (req, res) => {
+  const b = req.body || {};
+  const draft = {
+    id: 'sd-' + Date.now().toString(36),
+    caption: cap(b.caption, 2200),
+    platforms: Array.isArray(b.platforms) ? b.platforms.slice(0, 12).map(p => cap(p, 40)) : [],
+    asset: b.asset ? { url: cap(b.asset.url, 300), kind: cap(b.asset.kind, 12), name: cap(b.asset.name, 160) } : null,
+    scheduled_for: cap(b.scheduled_for, 40),
+    created_at: new Date().toISOString(), status: 'draft'
+  };
+  if (!draft.caption && !draft.asset) return res.status(400).json({ ok: false, error: 'need a caption or an asset' });
+  try {
+    const dir = path.join(__dirname, 'reports');
+    fs.mkdirSync(dir, { recursive: true });
+    fs.appendFileSync(path.join(dir, 'social-drafts.jsonl'), JSON.stringify(draft) + '\n');
+  } catch { return res.status(500).json({ ok: false, error: 'could not save draft' }); }
+  res.json({ ok: true, draft });
+});
+app.get('/api/admin/social/drafts', (req, res) => {
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'social-drafts.jsonl'), 'utf8').trim();
+    res.json(raw ? raw.split('\n').map(l => JSON.parse(l)).reverse() : []);
+  } catch { res.json([]); }
+});
+
+// Credentials tab — presence + last-4 only. NEVER returns secret values.
+app.get('/api/admin/credentials', (req, res) => {
+  const keys = [
+    { key: 'GEMINI_API_KEY', label: 'Nano Banana (Gemini image)', purpose: 'Generate service stills' },
+    { key: 'REPLICATE_API_TOKEN', label: 'SeeDance (Replicate)', purpose: 'Generate wash/wax video clips' },
+    { key: 'GOOGLE_PLACES_API_KEY', label: 'Google Places (read)', purpose: 'Live hours/reviews/photos — gated setup' },
+    { key: 'PCW_PLACE_ID', label: 'Google Place ID', purpose: 'Which listing to read' },
+    { key: 'DATABASE_URL', label: 'Postgres', purpose: 'Optional data backend' }
+  ];
+  res.json(keys.map(k => ({ ...maskEnv(k.key), label: k.label, purpose: k.purpose })));
+});
+
+// "Update Google Place" — draft-write: returns a prefilled Google Business Profile URL.
+// No direct API write (owner OAuth + API approval deferred). Front-end opens this in a new tab.
+app.post('/api/admin/place/draft-update', (req, res) => {
+  const { field } = req.body || {};
+  res.json({
+    ok: true, mode: 'draft',
+    message: `Draft update for "${field || 'listing'}" — opens Google Business Profile prefilled.`,
+    url: 'https://business.google.com/edit/l/' + (process.env.PCW_PLACE_ID || '')
+  });
+});
+
+// Simple per-IP rate limiter for the public form (prevents spam / unbounded lead file).
+const _hits = new Map();
+function rateLimit(max, windowMs) {
+  return (req, res, next) => {
+    const ip = req.ip || req.connection.remoteAddress || 'unknown';
+    const now = Date.now();
+    const arr = (_hits.get(ip) || []).filter(t => now - t < windowMs);
+    if (arr.length >= max) return res.status(429).json({ ok: false, error: 'Too many requests — try again shortly.' });
+    arr.push(now); _hits.set(ip, arr);
+    next();
+  };
+}
+const cap = (s, n) => String(s || '').slice(0, n);
+
+// Contact / booking lead — saved locally + optional webhook notify (env-gated).
+// NOTIFY_WEBHOOK (Slack/Discord/Zapier URL) gives leads a real notification path;
+// without it the lead is captured in the admin Leads tab. No mass email (that stays gated).
+app.post('/api/contact', rateLimit(5, 10 * 60 * 1000), (req, res) => {
+  const b = req.body || {};
+  const OK_MSG = "Thanks! We'll follow up to confirm your booking — usually within a few hours.";
+  // Honeypot: a hidden field real users never see. Field name is a NON-demographic token
+  // (b_confirm) so password managers / mobile autofill won't populate it for a real user and
+  // silently drop their lead. If it's filled it's a bot — fake success (no retry), drop it,
+  // but LOG the drop so a false-positive is auditable instead of an invisible lost booking.
+  if (b.b_confirm) { console.warn('[honeypot] dropped submission ip=%s name=%s', req.ip, cap(b.name, 60)); return res.json({ ok: true, message: OK_MSG }); }
+  const name = cap(b.name, 120).trim();
+  const phone = cap(b.phone, 40).trim();
+  const email = cap(b.email, 160).trim();
+  const emailOk = !email || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+  const phoneOk = !phone || phone.replace(/\D/g, '').length >= 7;
+  // Validate with human-readable errors so a real lead is never silently lost to a bad submit.
+  if (name.length < 2) return res.status(400).json({ ok: false, error: 'Please enter your name.' });
+  if (!phone && !email) return res.status(400).json({ ok: false, error: 'Please leave a phone number or email so we can reach you.' });
+  if (!emailOk) return res.status(400).json({ ok: false, error: 'That email address doesn’t look right — please double-check it.' });
+  if (!phoneOk) return res.status(400).json({ ok: false, error: 'That phone number looks too short — please double-check it.' });
+  const lead = {
+    id: 'ld_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
+    name, phone, email, vehicle: cap(b.vehicle, 120), service: cap(b.service, 120),
+    preferred: cap(b.preferred, 120), message: cap(b.message, 1000),
+    created_at: new Date().toISOString(), source: 'web-form'
+  };
+  // Duplicate-submit guard: if the same person (name + phone + email + message) already
+  // landed within the last 2 minutes, treat it as a double-click — succeed without saving twice.
+  try {
+    const lp = path.join(__dirname, 'reports', 'leads.jsonl');
+    if (fs.existsSync(lp)) {
+      const recent = fs.readFileSync(lp, 'utf8').trim().split('\n').slice(-25);
+      const now = Date.now();
+      const dup = recent.some(l => { try { const o = JSON.parse(l); return (now - new Date(o.created_at).getTime() < 120000) && o.name === name && (o.phone || '') === phone && (o.email || '') === email && (o.message || '') === lead.message; } catch { return false; } });
+      if (dup) return res.json({ ok: true, message: "Thanks — we’ve already got your request and we’ll follow up shortly." });
+    }
+  } catch { /* if the dedupe read fails, fall through and save (never block a real lead) */ }
+  try {
+    const dir = path.join(__dirname, 'reports');
+    fs.mkdirSync(dir, { recursive: true });
+    fs.appendFileSync(path.join(dir, 'leads.jsonl'), JSON.stringify(lead) + '\n');
+  } catch (e) { return res.status(500).json({ ok: false, error: 'could not save lead' }); }
+  // Fire-and-forget notification if a webhook is configured.
+  if (process.env.NOTIFY_WEBHOOK) {
+    fetch(process.env.NOTIFY_WEBHOOK, {
+      method: 'POST', headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ text: `🚗 New Prestige lead: ${lead.name} (${lead.phone || lead.email}) — ${lead.service || 'general'}${lead.message ? ' · ' + lead.message : ''}` })
+    }).catch(() => {});
+  }
+  res.json({ ok: true, message: "Thanks! We'll follow up to confirm your booking — usually within a few hours." });
+});
+
+// Lead pipeline status — kept in a SEPARATE json map (keyed by lead id, or created_at for
+// legacy leads) so the append-only leads.jsonl log is never rewritten. dtd:C 2026-07-26.
+const LEAD_STATUSES = ['new', 'contacted', 'booked', 'won', 'lost'];
+const leadStatusPath = () => path.join(__dirname, 'reports', 'lead-status.json');
+function readLeadStatus() { try { return JSON.parse(fs.readFileSync(leadStatusPath(), 'utf8')); } catch { return {}; } }
+
+// Admin: view captured leads, each merged with its pipeline status (default 'new').
+app.get('/api/admin/leads', (req, res) => {
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'leads.jsonl'), 'utf8').trim();
+    const st = readLeadStatus();
+    const leads = raw ? raw.split('\n').map(l => JSON.parse(l)) : [];
+    leads.forEach(l => { l.status = st[l.id || l.created_at] || 'new'; });
+    res.json(leads.reverse());
+  } catch { res.json([]); }
+});
+
+// Admin: export all leads (+ pipeline status) as CSV for offline follow-up / records.
+app.get('/api/admin/leads.csv', (req, res) => {
+  let leads = [];
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'leads.jsonl'), 'utf8').trim();
+    const st = readLeadStatus();
+    leads = raw ? raw.split('\n').map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean) : [];
+    leads.forEach(l => { l.status = st[l.id || l.created_at] || 'new'; });
+    leads.reverse();
+  } catch { /* empty export is still a valid CSV */ }
+  const cols = ['created_at', 'name', 'phone', 'email', 'vehicle', 'service', 'preferred', 'message', 'status'];
+  const esc = v => {
+    let s = String(v == null ? '' : v);
+    if (/^[=+\-@]/.test(s)) s = "'" + s;                          // neutralize CSV/formula injection
+    return /[",\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
+  };
+  const out = [cols.join(',')].concat(leads.map(l => cols.map(c => esc(l[c])).join(','))).join('\r\n') + '\r\n';
+  const day = new Date().toISOString().slice(0, 10);
+  res.setHeader('Content-Type', 'text/csv; charset=utf-8');
+  res.setHeader('Content-Disposition', `attachment; filename="prestige-leads-${day}.csv"`);
+  res.send(out);
+});
+
+// Admin: advance a lead through the pipeline (new → contacted → booked/won/lost).
+app.post('/api/admin/lead-status', (req, res) => {
+  const { id, status } = req.body || {};
+  if (!id || !LEAD_STATUSES.includes(status)) return res.status(400).json({ ok: false, error: 'id + valid status required' });
+  try {
+    const st = readLeadStatus(); st[id] = status;
+    fs.mkdirSync(path.join(__dirname, 'reports'), { recursive: true });
+    fs.writeFileSync(leadStatusPath(), JSON.stringify(st, null, 2));
+    res.json({ ok: true, id, status });
+  } catch { res.status(500).json({ ok: false, error: 'could not save status' }); }
+});
+
+// ── Self-hosted, privacy-friendly analytics-lite (DTD 2026-07-27) ──────────────────────
+// First-party AGGREGATE counts ONLY — no cookies, no IP, no per-user data — so it needs no
+// consent banner and stores nothing we can't stand behind. Gives the owner the denominator
+// (views) behind their lead counts => a real, honestly-measured conversion rate. NOT GA.
+const ANALYTICS_EVENTS = ['pageview', 'form_start', 'form_submit'];
+const analyticsPath = () => path.join(__dirname, 'reports', 'analytics.json');
+function readAnalytics() { try { return JSON.parse(fs.readFileSync(analyticsPath(), 'utf8')); } catch { return {}; } }
+const dayKeyLA = () => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }); // YYYY-MM-DD, shop tz
+
+// Public beacon. Whitelisted events only; unknown events are silently ignored (204, no error
+// surface). Aggregate increment only — never stores anything about who the visitor is.
+app.post('/api/track', rateLimit(60, 60 * 1000), (req, res) => {
+  const ev = String((req.body || {}).event || '');
+  if (!ANALYTICS_EVENTS.includes(ev)) return res.status(204).end();
+  try {
+    const a = readAnalytics(); const day = dayKeyLA();
+    a[day] = a[day] || {}; a[day][ev] = (a[day][ev] || 0) + 1;
+    const days = Object.keys(a).sort();                       // keep the file bounded (last 120 days)
+    if (days.length > 120) for (const d of days.slice(0, days.length - 120)) delete a[d];
+    fs.mkdirSync(path.join(__dirname, 'reports'), { recursive: true });
+    // Atomic write (temp + rename) so a partial/concurrent write can never leave a corrupt
+    // analytics.json that a reader would choke on.
+    const tmp = analyticsPath() + '.tmp';
+    fs.writeFileSync(tmp, JSON.stringify(a));
+    fs.renameSync(tmp, analyticsPath());
+  } catch { /* analytics must NEVER break a page — swallow */ }
+  res.status(204).end();
+});
+
+// Admin: aggregate analytics (today / last 7 days / all-time + honest conversion rate).
+app.get('/api/admin/analytics', (req, res) => {
+  const a = readAnalytics();
+  const sum = keys => keys.reduce((o, d) => { const b = a[d] || {}; ANALYTICS_EVENTS.forEach(e => o[e] = (o[e] || 0) + (b[e] || 0)); return o; }, {});
+  const allDays = Object.keys(a).sort();
+  const last7 = []; for (let i = 0; i < 7; i++) { const d = new Date(); d.setDate(d.getDate() - i); last7.push(d.toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' })); }
+  const conv = t => t.pageview ? Math.round((t.form_submit || 0) / t.pageview * 1000) / 10 : 0; // submits per 100 views
+  const t7 = sum(last7), tot = sum(allDays);
+  res.json({
+    today: sum([dayKeyLA()]), last7: t7, total: tot,
+    conversion7: conv(t7), conversionTotal: conv(tot),
+    series: allDays.slice(-14).map(d => ({ date: d, ...ANALYTICS_EVENTS.reduce((o, e) => (o[e] = (a[d] || {})[e] || 0, o), {}) }))
+  });
+});
+
+// Public: HONEST "typical wait" estimate derived from the demand model.
+// It is NOT a live queue count — it's a transparent function of best-times.json
+// demand for the current weekday, always labeled as an estimate, so we never
+// display a number we can't verify (the places.json data-honesty rule). dtd:A 2026-07-26.
+app.get('/api/wait', (req, res) => {
+  const days = ((bestTimes() || {}).traffic || {}).by_day || [];
+  // Weekday in the shop's LOCAL tz (LA) — the server clock is UTC, so a Friday-evening
+  // CA visitor must not be shown Saturday's level. (contrarian gate, 2026-07-26)
+  const dayName = new Date().toLocaleDateString('en-US', { timeZone: 'America/Los_Angeles', weekday: 'short' });
+  const today = days.find(d => d.day === dayName);
+  const demand = today ? Math.max(1, Math.min(5, Number(today.demand) || 3)) : 3;
+  // Report the demand LEVEL the data actually measures — a qualitative busy-ness, NOT a
+  // fabricated minute count we can't verify (Steve's data-honesty rule).
+  const LEVELS = { 1: ['Quiet', 'little to no wait'], 2: ['Quiet', 'little to no wait'], 3: ['Moderate', 'a short wait is typical'], 4: ['Busy', 'expect a wait'], 5: ['Very busy', 'expect a longer wait'] };
+  const [level, phrase] = LEVELS[demand];
+  res.json({
+    level, phrase, demand, day: dayName,
+    estimate: true,
+    basis: 'Based on typical demand for this day — not a live queue count'
+  });
+});
+
+// ---- static + clean URLs ---------------------------------------------------
+// Google Place photo proxy — fetches photo media with the server-side key and streams
+// it, so the key never reaches the browser. Cached to avoid re-billing on every view.
+app.get('/api/places/photo/:i', async (req, res) => {
+  const key = process.env.GOOGLE_PLACES_API_KEY;
+  const name = (places().photos || [])[parseInt(req.params.i, 10)];
+  if (!key || !name) return res.status(404).end();
+  const w = Math.min(1600, Math.max(100, parseInt(req.query.w, 10) || 800));
+  try {
+    const r = await fetch(`https://places.googleapis.com/v1/${name}/media?maxWidthPx=${w}&key=${key}`);
+    if (!r.ok) return res.status(502).end();
+    res.set('Content-Type', r.headers.get('content-type') || 'image/jpeg');
+    res.set('Cache-Control', 'public, max-age=86400');
+    res.end(Buffer.from(await r.arrayBuffer()));
+  } catch { res.status(502).end(); }
+});
+
+// Inline SVG favicon (avoids a 404 on the browser's automatic request).
+app.get('/favicon.svg', (req, res) => res.type('image/svg+xml').send(
+  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#0a84ff"/><text x="16" y="22" font-size="18" text-anchor="middle">🚗</text></svg>'));
+app.get('/favicon.ico', (req, res) => res.redirect(302, '/favicon.svg'));
+app.use('/media', express.static(path.join(__dirname, 'media')));
+app.get(/^\/(.+)\.html$/, (req, res) => res.redirect(301, '/' + req.params[0]));
+
+// LocalBusiness (AutoWash) JSON-LD for local SEO — built LIVE from data/places.json so it
+// can never drift from the visible name/address/phone/hours (Google cross-checks NAP).
+// Server-rendered into <head> so crawlers see it without executing JS. dtd:B 2026-07-26.
+// We only emit fields we can verify — NO aggregateRating (unverified) and NO geo (no lat/lng).
+const DOW = { Mon: 'Monday', Tue: 'Tuesday', Wed: 'Wednesday', Thu: 'Thursday', Fri: 'Friday', Sat: 'Saturday', Sun: 'Sunday' };
+const DOW_ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
+function to24(t) {
+  const m = String(t).match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
+  if (!m) return null;
+  let h = +m[1]; const ap = m[3].toUpperCase();
+  if (ap === 'PM' && h !== 12) h += 12;
+  if (ap === 'AM' && h === 12) h = 0;
+  return String(h).padStart(2, '0') + ':' + m[2];
+}
+function parseHoursLine(line) {
+  const times = String(line).match(/\d{1,2}:\d{2}\s*[AP]M/gi);
+  if (!times || times.length < 2) return null;
+  const opens = to24(times[0]), closes = to24(times[1]);
+  const dayPart = String(line).split(/\s+\d/)[0].trim();
+  const dr = dayPart.split(/[–—-]/).map(s => s.trim());
+  let days = [];
+  if (dr.length === 2) { const a = DOW_ORDER.indexOf(dr[0]), b = DOW_ORDER.indexOf(dr[1]); if (a >= 0 && b >= 0) days = DOW_ORDER.slice(a, b + 1); }
+  else if (DOW[dr[0]]) days = [dr[0]];
+  if (!days.length || !opens || !closes) return null;
+  return { '@type': 'OpeningHoursSpecification', dayOfWeek: days.map(d => DOW[d]), opens, closes };
+}
+// Canonical serving origin. prestige.agentabrams.com is the PERMANENT public home for this
+// build (Steve 2026-07-27: the prestigehandcarwash.com apex does not exist — agentabrams only).
+// All self-referential SEO (canonical, og:url, sitemap, article URLs, entity url) points here.
+const SITE_ORIGIN = 'https://prestige.agentabrams.com';
+function siteOrigin() { return (SITE_ORIGIN || '').replace(/\/$/, ''); }
+function buildJsonLd() {
+  const p = places();
+  if (!p || !p.name) return null;
+  const ld = { '@context': 'https://schema.org', '@type': 'AutoWash', name: p.name };
+  if (p.legal_name && p.legal_name !== p.name) ld.alternateName = p.legal_name;
+  if (siteOrigin()) ld.url = siteOrigin();
+  if (p.phone) ld.telephone = p.phone;
+  if (p.address) {
+    const parts = p.address.split(',').map(s => s.trim());
+    const addr = { '@type': 'PostalAddress', addressCountry: 'US' };
+    if (parts[0]) addr.streetAddress = parts[0];
+    if (parts[1]) addr.addressLocality = parts[1];
+    if (parts[2]) { const sz = parts[2].match(/([A-Z]{2})\s*(\d{5})/); if (sz) { addr.addressRegion = sz[1]; addr.postalCode = sz[2]; } }
+    ld.address = addr;
+  }
+  const hrs = (p.hours || []).map(parseHoursLine).filter(Boolean);
+  if (hrs.length) ld.openingHoursSpecification = hrs;
+  const ig = p.instagram ? (/^https?:\/\//.test(p.instagram) ? p.instagram : `https://instagram.com/${String(p.instagram).replace(/^@/, '')}`) : null;
+  const same = [ig, p.yelp_url].filter(Boolean);
+  if (same.length) ld.sameAs = same;
+  // Service catalog with REAL prices (only where a numeric price exists — quote-only
+  // services list the Service without a fabricated price). Strengthens rich results.
+  const svc = (services() || []).filter(s => s && s.name);
+  if (svc.length) {
+    ld.hasOfferCatalog = {
+      '@type': 'OfferCatalog', name: 'Car Wash & Detailing Services',
+      itemListElement: svc.map(s => {
+        const offer = { '@type': 'Offer', itemOffered: { '@type': 'Service', name: s.name } };
+        if (s.blurb) offer.itemOffered.description = s.blurb;
+        if (s.price != null && !isNaN(Number(s.price))) { offer.price = Number(s.price); offer.priceCurrency = 'USD'; }
+        return offer;
+      })
+    };
+  }
+  return ld;
+}
+// Booking-objection FAQ — the SINGLE SOURCE for both the on-page accordion (/api/faqs)
+// and the FAQPage JSON-LD, so visible text and structured data can never drift. Every
+// answer is a verifiable operational commitment the business already makes on-site
+// (data-honesty rule: no invented durations, prices, or ratings). Framed as the exact
+// objections that stall a booking — folds the /dtd "D" dissent (friction reduction) into A.
+function faqs() {
+  return [
+    { q: 'Do you actually hand wash, or is it an automatic tunnel?',
+      a: 'Every car is hand washed using the two-bucket method with a foam cannon — no automatic tunnel and no brushes, so no swirl marks. It is safe for ceramic-coated, matte, and PPF-wrapped finishes.' },
+    { q: 'Is the pricing really flat, or will I get upsold?',
+      a: 'The price on the sign is the price you pay. There is no commission-driven wax-and-polish pressure — pick a service and that is the cost. The full price list is on the Services page.' },
+    { q: 'What happens if it rains right after my wash?',
+      a: 'If it rains within 48 hours of your wash, your next basic wash is on us — just come back.' },
+    { q: 'Do you clean the back seats and door jambs?',
+      a: 'Yes. Every seat is touched and door jambs are included on every wash, always. If we miss a spot, we re-do it free.' },
+    { q: 'I am sensitive to fragrances — is there an option?',
+      a: 'Yes, a fragrance-free option is available on request. Just note it when you book.' },
+    { q: 'Do you offer full detailing and ceramic coating?',
+      a: 'Yes — alongside the hand wash we offer full-service detailing and 9H ceramic coating. See the Services page for current options and flat pricing.' },
+    { q: 'Which neighborhoods do you serve?',
+      a: 'We serve the San Fernando Valley — including Sherman Oaks, Encino, Van Nuys, Studio City, Northridge, Reseda, Tarzana, Woodland Hills, and North Hollywood.' },
+    { q: 'How do I book, and how soon will I hear back?',
+      a: 'Send a request from the Book / Contact page with your vehicle and preferred time. We follow up to confirm — usually within a few hours during business hours. No pressure, no upsell.' }
+  ];
+}
+// Persistent mobile action bar — injected into customer pages so the primary CTA (Book)
+// and a click-to-call are always one thumb-tap away on phones (where most Valley car-wash
+// traffic is). CSS-only visibility (<=640px), no JS. The tel: number comes from the REAL
+// places().phone (single source, no fabrication); the Call button is omitted if no phone.
+function mobileBar(pagePath) {
+  const p = places() || {};
+  let d = String(p.phone || '').replace(/\D/g, '');
+  if (d.length === 11 && d[0] === '1') d = d.slice(1);
+  const tel = d.length === 10 ? '+1' + d : (d ? '+' + d : '');
+  const call = tel ? `<a class="mbar-btn call" href="tel:${tel}" aria-label="Call ${(p.name || 'Prestige Car Wash').replace(/"/g, '')}">📞 Call</a>` : '';
+  // On the contact page the form IS the booking action, so scroll to it (#f) rather than
+  // self-linking to the top of the page and losing any half-filled form state.
+  const bookHref = pagePath === '/contact' ? '#f' : '/contact';
+  return `<style>
+.mbar{display:none}
+@media(max-width:640px){
+  body{padding-bottom:74px}
+  .mbar{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:80;gap:10px;padding:10px 12px calc(10px + env(safe-area-inset-bottom,0px));background:rgba(11,15,22,.93);backdrop-filter:blur(10px);border-top:1px solid var(--line)}
+  .mbar-btn{flex:1;text-align:center;padding:13px 10px;border-radius:12px;font-weight:800;font-size:15px;text-decoration:none;display:flex;align-items:center;justify-content:center;gap:6px}
+  .mbar .call{background:transparent;color:var(--ink);border:1px solid var(--line)}
+  .mbar .book{background:linear-gradient(135deg,var(--brand),var(--brand2));color:#001018}
+}</style>
+<div class="mbar" aria-label="Quick actions">${call}<a class="mbar-btn book" href="${bookHref}">Book a Wash →</a></div>`;
+}
+// Honest "find & review us" trust block — links to the REAL, verified off-site profiles
+// from places() (Google Maps, Yelp, Instagram). Deliberately shows NO star number: ratings
+// drift and would go stale on-page, so we route to the authoritative live profile instead
+// (data-honesty rule — never display a number we can't keep verifiably current). Only a
+// profile with a real non-empty value is rendered (no dead links). Server-rendered so the
+// off-site authority links ship in the crawler HTML.
+function trustBlockHtml() {
+  const p = places() || {};
+  const esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  const btn = (href, label, cls) => href
+    ? `<a class="${cls}" href="${esc(href)}" target="_blank" rel="noopener" style="text-decoration:none">${esc(label)}</a>` : '';
+  const igHandle = p.instagram ? '@' + String(p.instagram).replace(/^https?:\/\/(www\.)?instagram\.com\//i, '').replace(/\/+$/, '').replace(/^@/, '') : '';
+  const igUrl = p.instagram ? (/^https?:\/\//.test(p.instagram) ? p.instagram : 'https://instagram.com/' + String(p.instagram).replace(/^@/, '')) : '';
+  const links = [
+    btn(p.maps_query, '📍 Find us on Google Maps', 'btn'),
+    btn(p.yelp_url, '⭐ Read reviews on Yelp', 'btn ghost'),
+    btn(igUrl, '📸 ' + igHandle, 'btn ghost')
+  ].filter(Boolean);
+  if (!links.length) return '';
+  // Review-SOLICITATION CTA routes to Google's write-review dialog — and ONLY when a real
+  // Google place_id is present (currently empty). We deliberately do NOT solicit on Yelp
+  // (Yelp's "Don't Ask for Reviews" policy filters solicited reviews into "not recommended",
+  // burying exactly what we'd be trying to grow), and we never point "leave a review" at a
+  // bare search URL or a guessed id. So the CTA stays gated on a verified place_id and simply
+  // appears once it's set — the "Read reviews on Yelp" link above stays (finding ≠ soliciting).
+  const pid = /^[A-Za-z0-9_-]{20,}$/.test(String(p.place_id || '')) ? p.place_id : '';
+  const reviewHref = pid ? `https://search.google.com/local/writereview?placeid=${encodeURIComponent(pid)}` : '';
+  const cta = reviewHref ? `<div style="margin-top:16px">${btn(reviewHref, 'Leave us a review on Google →', 'btn')}</div>` : '';
+  return `<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center">${links.join('')}</div>${cta}`;
+}
+function buildFaqLd() {
+  const list = faqs();
+  if (!list.length) return null;
+  return {
+    '@context': 'https://schema.org', '@type': 'FAQPage',
+    mainEntity: list.map(f => ({
+      '@type': 'Question', name: f.q,
+      acceptedAnswer: { '@type': 'Answer', text: f.a }
+    }))
+  };
+}
+// Server-rendered accordion HTML from the SAME faqs() source. Injected into the #faqList
+// placeholder (see pageHtml) so the visible FAQ ships in the crawler-received HTML and
+// MATCHES the FAQPage JSON-LD — Google requires structured data to reflect visible content
+// (client-only rendering risks a manual action). Native <details> means zero JS needed.
+function faqHtml() {
+  const esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  return faqs().map((f, i) => `
+    <details class="faq"${i === 0 ? ' open' : ''} style="border:1px solid var(--line);border-radius:12px;margin-bottom:10px;background:var(--panel)">
+      <summary style="cursor:pointer;padding:15px 18px;font-weight:700;font-size:16px;display:flex;justify-content:space-between;gap:12px;align-items:center">
+        <span>${esc(f.q)}</span><span class="faq-mk" aria-hidden="true" style="color:var(--mut);font-weight:800;font-size:20px">+</span>
+      </summary>
+      <div style="padding:0 18px 16px;color:var(--mut);font-size:15px;line-height:1.55">${esc(f.a)}</div>
+    </details>`).join('');
+}
+// ── Evergreen car-care guides (DTD iteration 5) ────────────────────────────────────────
+// Genuinely useful, HONEST top-of-funnel content: established detailing best-practice
+// guidance phrased as advice (no fabricated statistics, no invented studies). Rendered
+// server-side so it's fully crawlable, with BlogPosting + BreadcrumbList JSON-LD and
+// internal links into services/booking. GUIDE_DATE is the real publish date.
+const GUIDE_DATE = '2026-07-26';
+function guides() {
+  return [
+    {
+      slug: 'how-often-to-wash-a-ceramic-coated-car',
+      title: 'How Often Should You Wash a Ceramic-Coated Car?',
+      description: 'A ceramic coating changes the maintenance math. Here is a sensible wash rhythm that protects the coating without over-washing.',
+      dek: 'A coating is slick and sacrificial — but it still needs the right care to keep beading and looking its best.',
+      sections: [
+        { h: 'The short answer', p: 'For a daily-driven coated car, every two weeks is a sensible default. Stretch it if the car lives in a garage; tighten it after rain, road grime, or bird droppings — droppings are acidic and can etch even a coated surface if left to bake in the sun.' },
+        { h: 'Why gentle washing matters more, not less', p: 'A coating makes dirt easier to remove, but automatic tunnel brushes still drag grit across the surface and dull the gloss over time. A gentle two-bucket hand wash with a pH-neutral soap preserves the coating’s hydrophobic behavior far longer than a tunnel ever will.' },
+        { h: 'What to avoid', p: 'Skip strong degreasers and high-alkaline wheel chemicals on coated paint — they strip the coating’s top layer. And never let water spot-dry in direct sun; the dissolved minerals bond to the coating and are a pain to remove.' },
+        { h: 'When to top it up', p: 'If water stops beading and starts sheeting flat, the coating is asking for help. A spray-on SiO2 booster applied after a wash restores hydrophobicity between professional maintenance visits.' }
+      ]
+    },
+    {
+      slug: 'hand-wash-vs-automatic-tunnel',
+      title: 'Hand Wash vs. Automatic Tunnel: What Is Actually Safe for Your Paint',
+      description: 'Automatic tunnels are fast and cheap — but here is what they do to your clear coat, and why hand washing is gentler.',
+      dek: 'The difference comes down to one thing: grit, and whether it gets dragged across your paint.',
+      sections: [
+        { h: 'Where tunnel washes go wrong', p: 'Tunnels recirculate water and run stiff brushes or cloth strips that have already touched every car before yours. The grit they carry is exactly what puts fine swirl marks and hairline scratches into a clear coat.' },
+        { h: 'Why two-bucket hand washing is gentler', p: 'Two buckets — one of clean soapy water, one to rinse the mitt — let grit drop out of circulation instead of going back onto the paint. A foam cannon lifts and floats dirt off the surface before the mitt ever touches it.' },
+        { h: 'The finishes that cannot take a tunnel', p: 'Ceramic coatings, matte and satin paint, and paint protection film (PPF) can all be dulled, hazed, or lifted at the edges by automated equipment. For any of these, hand washing is the safe default.' },
+        { h: 'The honest trade-off', p: 'Tunnels win on speed and price per wash. If your car is a daily beater, that may be all you need. If you care about the finish — or you have invested in a coating, a wrap, or a fresh repaint — hand washing pays for itself in preserved gloss and resale value.' }
+      ]
+    },
+    {
+      slug: 'protecting-matte-and-ppf-finishes',
+      title: 'Protecting Matte and PPF Finishes: A Care Guide',
+      description: 'Matte paint and paint protection film need different care than glossy clear coat. Here is how to keep them looking right.',
+      dek: 'The wrong product on a matte finish is a mistake you cannot polish out — so it pays to know the rules.',
+      sections: [
+        { h: 'Matte is a different animal', p: 'Matte and satin clear coats get their look from a micro-textured surface that scatters light. Anything that fills or polishes that texture — wax, sealant, polish, or a buffer — creates permanent shiny spots that can only be corrected with a refinish.' },
+        { h: 'How to wash matte safely', p: 'Hand wash only, with a matte-specific (wax-free) pH-neutral shampoo, a soft mitt, and straight-line motions rather than circles. Dry with a clean microfiber or a filtered-air blower instead of dragging a towel across the texture.' },
+        { h: 'Caring for PPF', p: 'Paint protection film is tough, but its edges can trap dirt and lift if scrubbed hard. Keep the edges clean, avoid blasting high-pressure water straight at the seams, and skip cutting compounds. Many modern films are self-healing — light swirls vanish in the sun or after a warm rinse.' },
+        { h: 'What we do', p: 'Our hand wash is matte- and PPF-safe by default: no automatic brushes, never any wax on matte, and a fragrance-free option on request. Tell us what your car is wearing and we will treat it accordingly.' }
+      ]
+    }
+  ];
+}
+function _e(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
+function guideLayout(title, description, bodyHtml) {
+  return `<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>${_e(title)}</title>
+<meta name="description" content="${_e(description)}">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/assets/pcw.css">
+</head><body>
+<nav class="nav"><div class="wrap row">
+  <div class="brand"><span class="mk">🚗</span><div>Prestige<br><small>Car Wash · SFV</small></div></div>
+  <button class="hamb" aria-label="Menu" aria-expanded="false" onclick="var o=document.getElementById('lnk').classList.toggle('open');this.setAttribute('aria-expanded',o)">☰</button>
+  <div class="links" id="lnk"><a href="/">Home</a><a href="/services">Services</a><a href="/guides" class="on">Guides</a><a class="btn" href="/contact">Book a Wash</a></div>
+</div></nav>
+${bodyHtml}
+<footer class="foot"><div class="wrap">© 2026 Prestige Car Wash · San Fernando Valley, CA · <a href="/services">Services</a> · <a href="/guides">Guides</a> · <a href="/contact">Book</a></div></footer>
+</body></html>`;
+}
+function guideCtaHtml() {
+  return `<div class="card" style="max-width:760px;margin:26px auto 0"><div class="body" style="text-align:center">
+    <div class="ttl" style="font-size:20px">Want it done right, by hand?</div>
+    <div class="blurb">We hand wash every car — coating-, matte-, and PPF-safe — with no upsell pressure.</div>
+    <div style="margin-top:12px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
+      <a class="btn" href="/contact">Book a Wash</a><a class="btn ghost" href="/services">See Services &amp; Pricing</a>
+    </div>
+  </div></div>`;
+}
+function guideArticleHtml(g) {
+  const secs = g.sections.map(s => `<h2>${_e(s.h)}</h2>\n<p>${_e(s.p)}</p>`).join('\n');
+  return `<article class="section"><div class="wrap" style="max-width:760px">
+    <div style="margin-bottom:10px"><a href="/guides" style="color:var(--mut);font-size:13px">← Car-care guides</a></div>
+    <span class="pill">Car-care guide</span>
+    <h1>${_e(g.title)}</h1>
+    <p class="lead">${_e(g.dek)}</p>
+    ${secs}
+    ${guideCtaHtml()}
+  </div></article>`;
+}
+function guideIndexHtml() {
+  const cards = guides().map(g => `<a class="card" href="/guides/${_e(g.slug)}" style="text-decoration:none;color:inherit"><div class="body">
+      <div class="cat">Car-care guide</div>
+      <div class="ttl">${_e(g.title)}</div>
+      <div class="blurb">${_e(g.description)}</div>
+      <div style="margin-top:8px;color:var(--brand);font-weight:700;font-size:14px">Read →</div>
+    </div></a>`).join('');
+  return `<section class="section"><div class="wrap">
+    <span class="pill">Guides</span>
+    <h1>Car-care guides</h1>
+    <p class="sub">Straight, no-nonsense advice on keeping your car’s finish looking its best — from the crew that hand washes them.</p>
+    <div class="grid" style="--cols:3">${cards}</div>
+  </div></section>`;
+}
+function buildArticleLd(g, canonicalUrl) {
+  const name = (places() || {}).name || 'Prestige Car Wash';
+  const ld = {
+    '@context': 'https://schema.org', '@type': 'BlogPosting',
+    headline: g.title, description: g.description,
+    datePublished: GUIDE_DATE, dateModified: GUIDE_DATE,
+    author: { '@type': 'Organization', name }, publisher: { '@type': 'Organization', name },
+    articleSection: 'Car care'
+  };
+  if (canonicalUrl) ld.mainEntityOfPage = canonicalUrl;
+  return ld;
+}
+function buildBreadcrumbLd(items) {
+  return {
+    '@context': 'https://schema.org', '@type': 'BreadcrumbList',
+    itemListElement: items.map((it, i) => ({ '@type': 'ListItem', position: i + 1, name: it.name, item: it.url }))
+  };
+}
+// Open Graph / Twitter tags for the homepage — reuses the page's own <title> and meta
+// description so social previews stay in sync with on-page SEO. og:image is a real asset.
+function buildSocialTags(html, pagePath) {
+  // Bail if the HTML already carries og: tags OR a canonical (e.g. added directly to the
+  // file) so we never inject a duplicate that makes Facebook/Google flag the markup.
+  if (/property=["']og:title["']/i.test(html) || /<link\b[^>]*\brel=["']canonical["']/i.test(html)) return '';
+  const p = places() || {};
+  const base = siteOrigin();
+  const url = base ? base + (pagePath || '') : '';   // page-specific canonical + og:url
+  const title = (html.match(/<title>([^<]*)<\/title>/i) || [])[1] || p.name || 'Prestige Car Wash';
+  // Attribute-order-independent description extraction, with a real fallback (never a
+  // blank social card). Handles both name-first and content-first, single or double quotes.
+  const descMatch = html.match(/<meta\b[^>]*\bname=["']description["'][^>]*\bcontent=["']([^"']*)["']/i)
+                 || html.match(/<meta\b[^>]*\bcontent=["']([^"']*)["'][^>]*\bname=["']description["']/i);
+  const desc = (descMatch || [])[1] || `${p.name || 'Prestige Car Wash'} — no-pressure flat pricing, two-bucket hand wash, detailing & ceramic coating in the San Fernando Valley.`;
+  const img = base ? base + '/media/svc-full-service.png' : '';
+  const esc = s => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
+  // Individual guide articles are og:type "article"; everything else is a "website".
+  const ogType = /^\/guides\/.+/.test(pagePath || '') ? 'article' : 'website';
+  const tags = [
+    ['og:type', ogType], ['og:site_name', p.name || 'Prestige Car Wash'],
+    ['og:title', title], ['og:description', desc], url && ['og:url', url], img && ['og:image', img],
+    ['twitter:card', img ? 'summary_large_image' : 'summary'],
+    ['twitter:title', title], ['twitter:description', desc], img && ['twitter:image', img]
+  ].filter(Boolean);
+  const metas = tags.map(([k, v]) => k.startsWith('twitter')
+    ? `<meta name="${k}" content="${esc(v)}">`
+    : `<meta property="${k}" content="${esc(v)}">`).join('\n');
+  return (url ? `<link rel="canonical" href="${esc(url)}">\n` : '') + metas;
+}
+// Built fresh per request (sync file read + one string replace = microseconds) so the
+// injected JSON-LD/OG NEVER drift from live places.json — a stale NAP is the exact thing
+// Google penalizes, which would defeat the point of injecting it. (contrarian gate, 2026-07-26)
+// GA4 gtag snippet — injected on the customer pages ONLY when GA_MEASUREMENT_ID is set
+// (a valid G-XXXX id). No-op until then, so this is safe to ship before the id exists.
+// Admin (internal) is intentionally NOT tracked as customer traffic.
+function gaSnippet() {
+  const id = process.env.GA_MEASUREMENT_ID;
+  if (!id || !/^G-[A-Z0-9]{6,}$/.test(id)) return '';
+  return `\n<script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>` +
+         `\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${id}');</script>`;
+}
+// Shared page decorator: takes a full HTML string and injects the FAQ/trust placeholders,
+// the mobile bar, OG/canonical tags, JSON-LD, and the GA snippet. Used by BOTH file-backed
+// pages (pageHtml) and server-generated pages (the /guides articles), so head/injection logic
+// lives in exactly one place.
+function decorate(html, pagePath, ld) {
+  // Fill the FAQ accordion server-side so the visible content is in the crawler-received
+  // HTML and matches the FAQPage JSON-LD. Only touches a page that carries the placeholder.
+  if (html.includes('id="faqList"')) {
+    html = html.replace(/(<div id="faqList"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + faqHtml() + close);
+  }
+  // Fill the honest find-&-review trust block server-side (real off-site profile links).
+  // If there are no real profiles to show, strip the WHOLE section so a heading never floats
+  // over an empty body (graceful degradation, per contrarian).
+  if (html.includes('id="findReview"')) {
+    const tb = trustBlockHtml();
+    if (tb) html = html.replace(/(<div id="findReview"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + tb + close);
+    else html = html.replace(/<section class="section" id="findreview">[\s\S]*?<\/section>/, '');
+  }
+  // Persistent mobile Book/Call bar on every customer page (guard against double-inject).
+  if (!/class="mbar"/.test(html)) html = html.replace('</body>', mobileBar(pagePath) + '\n</body>');
+  // Admin ↔ live-view toggle: on customer pages show a "🔧 Admin" pill ONLY if this browser
+  // carries the pcw.admin flag (set when the admin visits /admin). Invisible to real customers;
+  // gives the logged-in admin a one-tap way back to the admin from any live page.
+  html = html.replace('</body>', `<script>(function(){try{if(localStorage.getItem('pcw.admin')!=='1')return;var a=document.createElement('a');a.href='/admin';a.textContent='🔧 Admin view';a.title='Switch to admin view';a.setAttribute('aria-label','Switch to admin view');a.style.cssText='position:fixed;left:12px;bottom:12px;z-index:95;background:#12100e;color:#c6a765;font:700 11px/1 system-ui,-apple-system,sans-serif;letter-spacing:.1em;text-transform:uppercase;padding:9px 13px;border-radius:6px;text-decoration:none;box-shadow:0 6px 18px rgba(0,0,0,.45);border:1px solid rgba(198,167,101,.45)';document.body.appendChild(a);}catch(e){}})();</script>\n</body>`);
+  let inject = buildSocialTags(html, pagePath);
+  // Perf: preconnect to the font-file origin (pages already preconnect googleapis; the actual
+  // WOFF2 files come from gstatic, so this shaves a round-trip on first paint). Dedup-guarded.
+  if (!/fonts\.gstatic\.com/.test(html)) inject += '\n<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
+  // ld may be a single JSON-LD object OR an array of them (e.g. AutoWash + FAQPage on the
+  // homepage). Emit one <script> per object; escape "<" so the JSON can't break out of the tag.
+  for (const obj of [].concat(ld || []).filter(Boolean)) {
+    inject += `\n<script type="application/ld+json">${JSON.stringify(obj).replace(/</g, '\\u003c')}</script>`;
+  }
+  inject += gaSnippet();
+  // First-party pageview beacon (analytics-lite) — sendBeacon so it never blocks paint; the
+  // /api/track endpoint only increments an aggregate counter (no cookie/IP/PII). Customer
+  // pages only (admin isn't served through decorate, so the owner's own visits aren't counted).
+  inject += `\n<script>(function(){try{var b=JSON.stringify({event:'pageview'});if(navigator.sendBeacon){navigator.sendBeacon('/api/track',new Blob([b],{type:'application/json'}))}else{fetch('/api/track',{method:'POST',headers:{'Content-Type':'application/json'},body:b,keepalive:true})}}catch(e){}})();</script>`;
+  return html.replace('</head>', inject + '\n</head>');
+}
+function pageHtml(file, pagePath, ld) {
+  return decorate(fs.readFileSync(path.join(__dirname, 'public', file), 'utf8'), pagePath, ld);
+}
+const sendPage = (res, file, pagePath, ld) => {
+  // If tag INJECTION throws, still serve the raw page (graceful degradation). If the file
+  // itself is unreadable, sendFile's callback guarantees a clean 500 — never a hung response.
+  try { res.type('html').send(pageHtml(file, pagePath, ld)); }
+  catch { res.sendFile(path.join(__dirname, 'public', file), err => { if (err && !res.headersSent) res.status(500).end(); }); }
+};
+// FAQ list for the on-page accordion — same source the homepage FAQPage schema is built from.
+app.get('/api/faqs', (req, res) => res.json(faqs()));
+// Homepage carries BOTH the AutoWash business schema and the FAQPage schema (the FAQ is
+// visibly rendered on the page below, which is what makes the FAQPage markup legitimate).
+app.get('/', (req, res) => sendPage(res, 'index.html', '', [buildJsonLd(), buildFaqLd()]));
+app.get('/services', (req, res) => sendPage(res, 'services.html', '/services', null));
+app.get('/contact', (req, res) => sendPage(res, 'contact.html', '/contact', null));
+// Car-care guides — server-generated (shares the decorate() pipeline: OG/canonical/GA/mobile bar).
+app.get('/guides', (req, res) => {
+  try {
+    const html = guideLayout('Car-Care Guides — Prestige Car Wash',
+      'Honest, practical car-care guides from a San Fernando Valley hand-wash & detail shop: ceramic-coating care, hand wash vs. tunnel, and matte & PPF protection.',
+      guideIndexHtml());
+    res.type('html').send(decorate(html, '/guides', null));
+  } catch { res.status(500).end(); }
+});
+app.get('/guides/:slug', (req, res) => {
+  const g = guides().find(x => x.slug === req.params.slug);
+  const base = siteOrigin();
+  if (!g) {
+    const nf = guideLayout('Guide Not Found — Prestige Car Wash', 'That guide could not be found.',
+      `<section class="section"><div class="wrap"><h1>Guide not found</h1><p class="sub">That guide may have moved. <a href="/guides">Browse all car-care guides →</a></p></div></section>`);
+    return res.status(404).type('html').send(decorate(nf, '/guides', null));
+  }
+  const canonical = base ? base + '/guides/' + g.slug : '';
+  const ld = [
+    buildArticleLd(g, canonical),
+    buildBreadcrumbLd([
+      { name: 'Home', url: base || '/' },
+      { name: 'Guides', url: (base || '') + '/guides' },
+      { name: g.title, url: canonical || ('/guides/' + g.slug) }
+    ])
+  ];
+  try {
+    res.type('html').send(decorate(guideLayout(g.title + ' — Prestige Car Wash', g.description, guideArticleHtml(g)), '/guides/' + g.slug, ld));
+  } catch { res.status(500).end(); }
+});
+
+// XML sitemap generated from the public URL — kept in one place, no static file to drift.
+app.get('/sitemap.xml', (req, res) => {
+  const base = siteOrigin();
+  if (!base) return res.status(404).end();
+  const urls = ['/', '/services', '/contact', '/guides', ...guides().map(g => '/guides/' + g.slug)];
+  const xml = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
+    urls.map(u => `  <url><loc>${base}${u}</loc></url>`).join('\n') + '\n</urlset>';
+  res.type('application/xml').send(xml);
+});
+
+app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
+
+app.listen(PORT, () => {
+  console.log(`[prestige-car-wash] http://localhost:${PORT}  (admin: /admin)`);
+  if (process.env.NODE_ENV === 'production' && !process.env.ADMIN_PASS) {
+    console.warn('⚠ SECURITY: running in production with the DEFAULT admin password. Set ADMIN_PASS in .env before exposing this host.');
+  }
+});
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.before.js b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.before.js
new file mode 100644
index 00000000..2e8a2863
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.before.js
@@ -0,0 +1,910 @@
+'use strict';
+/**
+ * Prestige Car Wash (PCW) — one Express app serving:
+ *   - the public marketing site (/, /services, /contact)
+ *   - the Basic-Auth growth admin (/admin) + its bucket APIs (/api/admin/*)
+ *
+ * Data source: data/*.json snapshots (Postgres is an optional future upgrade; the
+ * schema in scripts/db-init.sql mirrors these files). Everything the front-end needs
+ * comes through /api/* so the same catalog powers both the public page and the admin.
+ */
+require('dotenv').config();
+const fs = require('fs');
+const path = require('path');
+const express = require('express');
+const helmet = require('helmet');
+
+const app = express();
+const PORT = process.env.PORT || 9808;
+const ADMIN_USER = process.env.ADMIN_USER || 'admin';
+const ADMIN_PASS = process.env.ADMIN_PASS || 'DW2024!';
+const DATA = path.join(__dirname, 'data');
+
+// ---- helpers ---------------------------------------------------------------
+const readJSON = (f, fallback) => {
+  try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); }
+  catch { return fallback; }
+};
+// Re-read on each request so admin edits / script runs show up without a restart.
+const services     = () => readJSON('services.json', []);
+const competitors  = () => readJSON('competitors.json', []);
+const suggestions  = () => readJSON('suggestions.json', []);
+const holidays     = () => readJSON('holidays.json', []);
+const directories  = () => readJSON('directories.json', []);
+const ads          = () => readJSON('ads.json', []);
+const bestTimes    = () => readJSON('best-times.json', {});
+const places       = () => readJSON('places.json', {});
+const socialTpl    = () => readJSON('social-templates.json', { platforms: [], hashtag_sets: {}, templates: [] });
+const promos       = () => readJSON('promos.json', { promos: [] });
+
+// Mask a secret to a "present (…last4)" descriptor — never returns the value.
+const maskEnv = (key) => {
+  const v = process.env[key];
+  if (!v) return { key, present: false, hint: '' };
+  return { key, present: true, hint: '…' + String(v).slice(-4) };
+};
+
+// ---- middleware ------------------------------------------------------------
+app.use(helmet({
+  contentSecurityPolicy: {
+    directives: {
+      defaultSrc: ["'self'"],
+      scriptSrc: ["'self'", "'unsafe-inline'"],
+      // inline event-handler attributes (onclick=, onerror=) are a separate directive;
+      // Helmet defaults it to 'none'. The site/admin use inline handlers (house style,
+      // Basic-Auth admin, no user-generated content) so allow them explicitly.
+      scriptSrcAttr: ["'unsafe-inline'"],
+      styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
+      fontSrc: ["'self'", 'https://fonts.gstatic.com', 'data:'],
+      imgSrc: ["'self'", 'data:', 'https:'],
+      mediaSrc: ["'self'", 'data:', 'blob:'],
+      connectSrc: ["'self'"]
+    }
+  }
+}));
+app.use(express.json());
+
+// Basic Auth gate for /admin and /api/admin/*
+function requireAdmin(req, res, next) {
+  const hdr = req.headers.authorization || '';
+  const [scheme, encoded] = hdr.split(' ');
+  if (scheme === 'Basic' && encoded) {
+    const [u, p] = Buffer.from(encoded, 'base64').toString().split(':');
+    if (u === ADMIN_USER && p === ADMIN_PASS) return next();
+  }
+  res.set('WWW-Authenticate', 'Basic realm="Prestige Admin"').status(401).send('Auth required');
+}
+app.use('/admin', requireAdmin);
+app.use('/api/admin', requireAdmin);
+
+// ---- sort (shared by public services grid) --------------------------------
+function sortServices(list, mode) {
+  const arr = [...list];
+  const byTitle = (a, b) => String(a.name).localeCompare(String(b.name));
+  switch (mode) {
+    case 'title':      return arr.sort(byTitle);
+    case 'price-asc':  return arr.sort((a, b) => (a.price || 0) - (b.price || 0) || byTitle(a, b));
+    case 'price-desc': return arr.sort((a, b) => (b.price || 0) - (a.price || 0) || byTitle(a, b));
+    case 'duration':   return arr.sort((a, b) => (a.duration_min || 0) - (b.duration_min || 0) || byTitle(a, b));
+    case 'featured':
+    default:           return arr.sort((a, b) => ((b.featured ? 1 : 0) - (a.featured ? 1 : 0)) || (a.sort_order || 99) - (b.sort_order || 99));
+  }
+}
+
+// Price bands for the left filter panel.
+const priceBand = (p) => p <= 25 ? 'Under $25' : p <= 60 ? '$25–$60' : p <= 150 ? '$60–$150' : '$150+';
+
+// ---- public API ------------------------------------------------------------
+app.get('/api/health', (req, res) => {
+  res.json({
+    ok: true, service: 'prestige-car-wash', port: PORT,
+    counts: {
+      services: services().length, competitors: competitors().length,
+      suggestions: suggestions().length, holidays: holidays().length,
+      directories: directories().length, ads: ads().length,
+      promos: (promos().promos || []).length
+    },
+    ts: new Date().toISOString()
+  });
+});
+
+app.get('/api/services', (req, res) => {
+  let list = services().map(s => ({ ...s, price_band: priceBand(s.price || 0) }));
+  const { category, band, q } = req.query;
+  if (category) list = list.filter(s => s.category === category);
+  if (band) list = list.filter(s => s.price_band === band);
+  if (q) {
+    const needle = String(q).toLowerCase();
+    list = list.filter(s => [s.name, s.blurb, s.category].join(' ').toLowerCase().includes(needle));
+  }
+  res.json(sortServices(list, req.query.sort));
+});
+
+// Facet counts for the left panel — each dimension counted over the OTHER active filters.
+app.get('/api/facets', (req, res) => {
+  const base = services().map(s => ({ ...s, price_band: priceBand(s.price || 0) }));
+  const tally = (rows, key) => rows.reduce((m, r) => (m[r[key]] = (m[r[key]] || 0) + 1, m), {});
+  const applyExcept = (except) => base.filter(s =>
+    (except === 'category' || !req.query.category || s.category === req.query.category) &&
+    (except === 'band' || !req.query.band || s.price_band === req.query.band)
+  );
+  res.json({
+    category: tally(applyExcept('category'), 'category'),
+    band: tally(applyExcept('band'), 'price_band')
+  });
+});
+
+app.get('/api/best-times', (req, res) => res.json(bestTimes()));
+// Emit ready-to-use photo/video URLs so the client is format-agnostic:
+// Google photo refs → the proxy route; local paths (real business photos) → direct.
+app.get('/api/places', (req, res) => {
+  const p = places();
+  const photo_urls = (p.photos || []).map((ph, i) =>
+    /^places\//.test(ph) ? `/api/places/photo/${i}` : '/' + String(ph).replace(/^\//, ''));
+  const videos = (p.videos || []).map(v => ({
+    ...v,
+    src: '/' + String(v.src || '').replace(/^\//, ''),
+    poster: v.poster ? '/' + String(v.poster).replace(/^\//, '') : ''
+  }));
+  res.json({ ...p, photo_urls, videos });
+});
+
+// ---- admin bucket APIs -----------------------------------------------------
+app.get('/api/admin/competitors', (req, res) => res.json(competitors()));
+app.get('/api/admin/suggestions', (req, res) => res.json(suggestions()));
+app.get('/api/admin/holidays', (req, res) => res.json(holidays()));
+app.get('/api/admin/directories', (req, res) => res.json(directories()));
+app.get('/api/admin/ads', (req, res) => res.json(ads()));
+app.get('/api/admin/services', (req, res) => res.json(services()));
+app.get('/api/admin/best-times', (req, res) => res.json(bestTimes()));
+app.get('/api/admin/places', (req, res) => res.json(places()));
+
+// ---- Socials: create + post from ONE place (draft-only; never auto-posts) --
+// Media library: every image/video under /media (incl. media/real/), each video
+// paired with its poster image (svc-<stem>.png or <stem>-poster.jpg) so the
+// composer can preview + attach any asset. Read-only scan of the media dir.
+const MEDIA_DIR = path.join(__dirname, 'media');
+function mediaLibrary() {
+  const walk = (dir, prefix) => {
+    let out = [];
+    let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return out; }
+    for (const e of entries) {
+      if (e.isDirectory()) { out = out.concat(walk(path.join(dir, e.name), prefix + e.name + '/')); continue; }
+      const ext = path.extname(e.name).toLowerCase();
+      const kind = ['.mp4', '.mov', '.webm'].includes(ext) ? 'video'
+        : ['.png', '.jpg', '.jpeg', '.webp', '.gif'].includes(ext) ? 'image' : null;
+      if (!kind) continue;
+      out.push({ name: e.name, stem: e.name.replace(/\.[^.]+$/, ''), kind, url: '/media/' + prefix + e.name });
+    }
+    return out;
+  };
+  const all = walk(MEDIA_DIR, '');
+  const imgByStem = {};
+  all.filter(m => m.kind === 'image').forEach(m => { imgByStem[m.stem] = m.url; });
+  all.forEach(m => { if (m.kind === 'video') m.poster = imgByStem[m.stem] || imgByStem[m.stem + '-poster'] || ''; });
+  // Hide poster stills that only exist to back a video (keep genuine standalone photos).
+  const videoStems = new Set(all.filter(m => m.kind === 'video').map(m => m.stem));
+  return all
+    .filter(m => !(m.kind === 'image' && (videoStems.has(m.stem) || videoStems.has(m.stem.replace(/-poster$/, '')))))
+    .sort((a, b) => a.name.localeCompare(b.name));
+}
+app.get('/api/admin/media', (req, res) => res.json(mediaLibrary()));
+app.get('/api/admin/social/templates', (req, res) => res.json(socialTpl()));
+
+// ---- Promos: HyperFrames-generated brand films -----------------------------
+// Curated, first-class list of the finished brand videos (distinct from the raw
+// svc-* service clips in the media library). Repo-relative src/poster are
+// normalized to absolute /media URLs so the client stays format-agnostic; any
+// promo whose file is missing on disk is flagged (ready=false) rather than
+// served as a broken <video>. mm:ss is precomputed for the card badge.
+const abs = (p) => '/' + String(p || '').replace(/^\//, '');
+const mmss = (s) => { const sec=Math.max(0,parseInt(s,10)||0); return `${Math.floor(sec/60)}:${String(sec%60).padStart(2,'0')}`; };
+app.get('/api/admin/promos', (req, res) => {
+  const list = (promos().promos || []).map(p => {
+    const rel = String(p.src || '').replace(/^\//, '');
+    const ready = rel ? fs.existsSync(path.join(__dirname, rel)) : false;
+    return {
+      ...p,
+      src: abs(p.src),
+      poster: p.poster ? abs(p.poster) : '',
+      duration_label: p.duration_sec ? mmss(p.duration_sec) : '',
+      ready
+    };
+  });
+  res.json(list);
+});
+
+// Save a composed post as a DRAFT (append-only). This NEVER posts to any network —
+// the front-end deep-links into each platform's native composer for the human to post.
+app.post('/api/admin/social/draft', (req, res) => {
+  const b = req.body || {};
+  const draft = {
+    id: 'sd-' + Date.now().toString(36),
+    caption: cap(b.caption, 2200),
+    platforms: Array.isArray(b.platforms) ? b.platforms.slice(0, 12).map(p => cap(p, 40)) : [],
+    asset: b.asset ? { url: cap(b.asset.url, 300), kind: cap(b.asset.kind, 12), name: cap(b.asset.name, 160) } : null,
+    scheduled_for: cap(b.scheduled_for, 40),
+    created_at: new Date().toISOString(), status: 'draft'
+  };
+  if (!draft.caption && !draft.asset) return res.status(400).json({ ok: false, error: 'need a caption or an asset' });
+  try {
+    const dir = path.join(__dirname, 'reports');
+    fs.mkdirSync(dir, { recursive: true });
+    fs.appendFileSync(path.join(dir, 'social-drafts.jsonl'), JSON.stringify(draft) + '\n');
+  } catch { return res.status(500).json({ ok: false, error: 'could not save draft' }); }
+  res.json({ ok: true, draft });
+});
+app.get('/api/admin/social/drafts', (req, res) => {
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'social-drafts.jsonl'), 'utf8').trim();
+    res.json(raw ? raw.split('\n').map(l => JSON.parse(l)).reverse() : []);
+  } catch { res.json([]); }
+});
+
+// Credentials tab — presence + last-4 only. NEVER returns secret values.
+app.get('/api/admin/credentials', (req, res) => {
+  const keys = [
+    { key: 'GEMINI_API_KEY', label: 'Nano Banana (Gemini image)', purpose: 'Generate service stills' },
+    { key: 'REPLICATE_API_TOKEN', label: 'SeeDance (Replicate)', purpose: 'Generate wash/wax video clips' },
+    { key: 'GOOGLE_PLACES_API_KEY', label: 'Google Places (read)', purpose: 'Live hours/reviews/photos — gated setup' },
+    { key: 'PCW_PLACE_ID', label: 'Google Place ID', purpose: 'Which listing to read' },
+    { key: 'DATABASE_URL', label: 'Postgres', purpose: 'Optional data backend' }
+  ];
+  res.json(keys.map(k => ({ ...maskEnv(k.key), label: k.label, purpose: k.purpose })));
+});
+
+// "Update Google Place" — draft-write: returns a prefilled Google Business Profile URL.
+// No direct API write (owner OAuth + API approval deferred). Front-end opens this in a new tab.
+app.post('/api/admin/place/draft-update', (req, res) => {
+  const { field } = req.body || {};
+  res.json({
+    ok: true, mode: 'draft',
+    message: `Draft update for "${field || 'listing'}" — opens Google Business Profile prefilled.`,
+    url: 'https://business.google.com/edit/l/' + (process.env.PCW_PLACE_ID || '')
+  });
+});
+
+// Simple per-IP rate limiter for the public form (prevents spam / unbounded lead file).
+const _hits = new Map();
+function rateLimit(max, windowMs) {
+  return (req, res, next) => {
+    const ip = req.ip || req.connection.remoteAddress || 'unknown';
+    const now = Date.now();
+    const arr = (_hits.get(ip) || []).filter(t => now - t < windowMs);
+    if (arr.length >= max) return res.status(429).json({ ok: false, error: 'Too many requests — try again shortly.' });
+    arr.push(now); _hits.set(ip, arr);
+    next();
+  };
+}
+const cap = (s, n) => String(s || '').slice(0, n);
+
+// Contact / booking lead — saved locally + optional webhook notify (env-gated).
+// NOTIFY_WEBHOOK (Slack/Discord/Zapier URL) gives leads a real notification path;
+// without it the lead is captured in the admin Leads tab. No mass email (that stays gated).
+app.post('/api/contact', rateLimit(5, 10 * 60 * 1000), (req, res) => {
+  const b = req.body || {};
+  const OK_MSG = "Thanks! We'll follow up to confirm your booking — usually within a few hours.";
+  // Honeypot: a hidden field real users never see. Field name is a NON-demographic token
+  // (b_confirm) so password managers / mobile autofill won't populate it for a real user and
+  // silently drop their lead. If it's filled it's a bot — fake success (no retry), drop it,
+  // but LOG the drop so a false-positive is auditable instead of an invisible lost booking.
+  if (b.b_confirm) { console.warn('[honeypot] dropped submission ip=%s name=%s', req.ip, cap(b.name, 60)); return res.json({ ok: true, message: OK_MSG }); }
+  const name = cap(b.name, 120).trim();
+  const phone = cap(b.phone, 40).trim();
+  const email = cap(b.email, 160).trim();
+  const emailOk = !email || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+  const phoneOk = !phone || phone.replace(/\D/g, '').length >= 7;
+  // Validate with human-readable errors so a real lead is never silently lost to a bad submit.
+  if (name.length < 2) return res.status(400).json({ ok: false, error: 'Please enter your name.' });
+  if (!phone && !email) return res.status(400).json({ ok: false, error: 'Please leave a phone number or email so we can reach you.' });
+  if (!emailOk) return res.status(400).json({ ok: false, error: 'That email address doesn’t look right — please double-check it.' });
+  if (!phoneOk) return res.status(400).json({ ok: false, error: 'That phone number looks too short — please double-check it.' });
+  const lead = {
+    id: 'ld_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
+    name, phone, email, vehicle: cap(b.vehicle, 120), service: cap(b.service, 120),
+    preferred: cap(b.preferred, 120), message: cap(b.message, 1000),
+    created_at: new Date().toISOString(), source: 'web-form'
+  };
+  // Duplicate-submit guard: if the same person (name + phone + email + message) already
+  // landed within the last 2 minutes, treat it as a double-click — succeed without saving twice.
+  try {
+    const lp = path.join(__dirname, 'reports', 'leads.jsonl');
+    if (fs.existsSync(lp)) {
+      const recent = fs.readFileSync(lp, 'utf8').trim().split('\n').slice(-25);
+      const now = Date.now();
+      const dup = recent.some(l => { try { const o = JSON.parse(l); return (now - new Date(o.created_at).getTime() < 120000) && o.name === name && (o.phone || '') === phone && (o.email || '') === email && (o.message || '') === lead.message; } catch { return false; } });
+      if (dup) return res.json({ ok: true, message: "Thanks — we’ve already got your request and we’ll follow up shortly." });
+    }
+  } catch { /* if the dedupe read fails, fall through and save (never block a real lead) */ }
+  try {
+    const dir = path.join(__dirname, 'reports');
+    fs.mkdirSync(dir, { recursive: true });
+    fs.appendFileSync(path.join(dir, 'leads.jsonl'), JSON.stringify(lead) + '\n');
+  } catch (e) { return res.status(500).json({ ok: false, error: 'could not save lead' }); }
+  // Fire-and-forget notification if a webhook is configured.
+  if (process.env.NOTIFY_WEBHOOK) {
+    fetch(process.env.NOTIFY_WEBHOOK, {
+      method: 'POST', headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ text: `🚗 New Prestige lead: ${lead.name} (${lead.phone || lead.email}) — ${lead.service || 'general'}${lead.message ? ' · ' + lead.message : ''}` })
+    }).catch(() => {});
+  }
+  res.json({ ok: true, message: "Thanks! We'll follow up to confirm your booking — usually within a few hours." });
+});
+
+// Lead pipeline status — kept in a SEPARATE json map (keyed by lead id, or created_at for
+// legacy leads) so the append-only leads.jsonl log is never rewritten. dtd:C 2026-07-26.
+const LEAD_STATUSES = ['new', 'contacted', 'booked', 'won', 'lost'];
+const leadStatusPath = () => path.join(__dirname, 'reports', 'lead-status.json');
+function readLeadStatus() { try { return JSON.parse(fs.readFileSync(leadStatusPath(), 'utf8')); } catch { return {}; } }
+
+// Admin: view captured leads, each merged with its pipeline status (default 'new').
+app.get('/api/admin/leads', (req, res) => {
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'leads.jsonl'), 'utf8').trim();
+    const st = readLeadStatus();
+    const leads = raw ? raw.split('\n').map(l => JSON.parse(l)) : [];
+    leads.forEach(l => { l.status = st[l.id || l.created_at] || 'new'; });
+    res.json(leads.reverse());
+  } catch { res.json([]); }
+});
+
+// Admin: export all leads (+ pipeline status) as CSV for offline follow-up / records.
+app.get('/api/admin/leads.csv', (req, res) => {
+  let leads = [];
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'leads.jsonl'), 'utf8').trim();
+    const st = readLeadStatus();
+    leads = raw ? raw.split('\n').map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean) : [];
+    leads.forEach(l => { l.status = st[l.id || l.created_at] || 'new'; });
+    leads.reverse();
+  } catch { /* empty export is still a valid CSV */ }
+  const cols = ['created_at', 'name', 'phone', 'email', 'vehicle', 'service', 'preferred', 'message', 'status'];
+  const esc = v => {
+    let s = String(v == null ? '' : v);
+    if (/^[=+\-@]/.test(s)) s = "'" + s;                          // neutralize CSV/formula injection
+    return /[",\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
+  };
+  const out = [cols.join(',')].concat(leads.map(l => cols.map(c => esc(l[c])).join(','))).join('\r\n') + '\r\n';
+  const day = new Date().toISOString().slice(0, 10);
+  res.setHeader('Content-Type', 'text/csv; charset=utf-8');
+  res.setHeader('Content-Disposition', `attachment; filename="prestige-leads-${day}.csv"`);
+  res.send(out);
+});
+
+// Admin: advance a lead through the pipeline (new → contacted → booked/won/lost).
+app.post('/api/admin/lead-status', (req, res) => {
+  const { id, status } = req.body || {};
+  if (!id || !LEAD_STATUSES.includes(status)) return res.status(400).json({ ok: false, error: 'id + valid status required' });
+  try {
+    const st = readLeadStatus(); st[id] = status;
+    fs.mkdirSync(path.join(__dirname, 'reports'), { recursive: true });
+    fs.writeFileSync(leadStatusPath(), JSON.stringify(st, null, 2));
+    res.json({ ok: true, id, status });
+  } catch { res.status(500).json({ ok: false, error: 'could not save status' }); }
+});
+
+// ── Self-hosted, privacy-friendly analytics-lite (DTD 2026-07-27) ──────────────────────
+// First-party AGGREGATE counts ONLY — no cookies, no IP, no per-user data — so it needs no
+// consent banner and stores nothing we can't stand behind. Gives the owner the denominator
+// (views) behind their lead counts => a real, honestly-measured conversion rate. NOT GA.
+const ANALYTICS_EVENTS = ['pageview', 'form_start', 'form_submit'];
+const analyticsPath = () => path.join(__dirname, 'reports', 'analytics.json');
+function readAnalytics() { try { return JSON.parse(fs.readFileSync(analyticsPath(), 'utf8')); } catch { return {}; } }
+const dayKeyLA = () => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }); // YYYY-MM-DD, shop tz
+
+// Public beacon. Whitelisted events only; unknown events are silently ignored (204, no error
+// surface). Aggregate increment only — never stores anything about who the visitor is.
+app.post('/api/track', rateLimit(60, 60 * 1000), (req, res) => {
+  const ev = String((req.body || {}).event || '');
+  if (!ANALYTICS_EVENTS.includes(ev)) return res.status(204).end();
+  try {
+    const a = readAnalytics(); const day = dayKeyLA();
+    a[day] = a[day] || {}; a[day][ev] = (a[day][ev] || 0) + 1;
+    const days = Object.keys(a).sort();                       // keep the file bounded (last 120 days)
+    if (days.length > 120) for (const d of days.slice(0, days.length - 120)) delete a[d];
+    fs.mkdirSync(path.join(__dirname, 'reports'), { recursive: true });
+    // Atomic write (temp + rename) so a partial/concurrent write can never leave a corrupt
+    // analytics.json that a reader would choke on.
+    const tmp = analyticsPath() + '.tmp';
+    fs.writeFileSync(tmp, JSON.stringify(a));
+    fs.renameSync(tmp, analyticsPath());
+  } catch { /* analytics must NEVER break a page — swallow */ }
+  res.status(204).end();
+});
+
+// Admin: aggregate analytics (today / last 7 days / all-time + honest conversion rate).
+app.get('/api/admin/analytics', (req, res) => {
+  const a = readAnalytics();
+  const sum = keys => keys.reduce((o, d) => { const b = a[d] || {}; ANALYTICS_EVENTS.forEach(e => o[e] = (o[e] || 0) + (b[e] || 0)); return o; }, {});
+  const allDays = Object.keys(a).sort();
+  const last7 = []; for (let i = 0; i < 7; i++) { const d = new Date(); d.setDate(d.getDate() - i); last7.push(d.toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' })); }
+  const conv = t => t.pageview ? Math.round((t.form_submit || 0) / t.pageview * 1000) / 10 : 0; // submits per 100 views
+  const t7 = sum(last7), tot = sum(allDays);
+  res.json({
+    today: sum([dayKeyLA()]), last7: t7, total: tot,
+    conversion7: conv(t7), conversionTotal: conv(tot),
+    series: allDays.slice(-14).map(d => ({ date: d, ...ANALYTICS_EVENTS.reduce((o, e) => (o[e] = (a[d] || {})[e] || 0, o), {}) }))
+  });
+});
+
+// Public: HONEST "typical wait" estimate derived from the demand model.
+// It is NOT a live queue count — it's a transparent function of best-times.json
+// demand for the current weekday, always labeled as an estimate, so we never
+// display a number we can't verify (the places.json data-honesty rule). dtd:A 2026-07-26.
+app.get('/api/wait', (req, res) => {
+  const days = ((bestTimes() || {}).traffic || {}).by_day || [];
+  // Weekday in the shop's LOCAL tz (LA) — the server clock is UTC, so a Friday-evening
+  // CA visitor must not be shown Saturday's level. (contrarian gate, 2026-07-26)
+  const dayName = new Date().toLocaleDateString('en-US', { timeZone: 'America/Los_Angeles', weekday: 'short' });
+  const today = days.find(d => d.day === dayName);
+  const demand = today ? Math.max(1, Math.min(5, Number(today.demand) || 3)) : 3;
+  // Report the demand LEVEL the data actually measures — a qualitative busy-ness, NOT a
+  // fabricated minute count we can't verify (Steve's data-honesty rule).
+  const LEVELS = { 1: ['Quiet', 'little to no wait'], 2: ['Quiet', 'little to no wait'], 3: ['Moderate', 'a short wait is typical'], 4: ['Busy', 'expect a wait'], 5: ['Very busy', 'expect a longer wait'] };
+  const [level, phrase] = LEVELS[demand];
+  res.json({
+    level, phrase, demand, day: dayName,
+    estimate: true,
+    basis: 'Based on typical demand for this day — not a live queue count'
+  });
+});
+
+// ---- static + clean URLs ---------------------------------------------------
+// Google Place photo proxy — fetches photo media with the server-side key and streams
+// it, so the key never reaches the browser. Cached to avoid re-billing on every view.
+app.get('/api/places/photo/:i', async (req, res) => {
+  const key = process.env.GOOGLE_PLACES_API_KEY;
+  const name = (places().photos || [])[parseInt(req.params.i, 10)];
+  if (!key || !name) return res.status(404).end();
+  const w = Math.min(1600, Math.max(100, parseInt(req.query.w, 10) || 800));
+  try {
+    const r = await fetch(`https://places.googleapis.com/v1/${name}/media?maxWidthPx=${w}&key=${key}`);
+    if (!r.ok) return res.status(502).end();
+    res.set('Content-Type', r.headers.get('content-type') || 'image/jpeg');
+    res.set('Cache-Control', 'public, max-age=86400');
+    res.end(Buffer.from(await r.arrayBuffer()));
+  } catch { res.status(502).end(); }
+});
+
+// Inline SVG favicon (avoids a 404 on the browser's automatic request).
+app.get('/favicon.svg', (req, res) => res.type('image/svg+xml').send(
+  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#0a84ff"/><text x="16" y="22" font-size="18" text-anchor="middle">🚗</text></svg>'));
+app.get('/favicon.ico', (req, res) => res.redirect(302, '/favicon.svg'));
+app.use('/media', express.static(path.join(__dirname, 'media')));
+app.get(/^\/(.+)\.html$/, (req, res) => res.redirect(301, '/' + req.params[0]));
+
+// LocalBusiness (AutoWash) JSON-LD for local SEO — built LIVE from data/places.json so it
+// can never drift from the visible name/address/phone/hours (Google cross-checks NAP).
+// Server-rendered into <head> so crawlers see it without executing JS. dtd:B 2026-07-26.
+// We only emit fields we can verify — NO aggregateRating (unverified) and NO geo (no lat/lng).
+const DOW = { Mon: 'Monday', Tue: 'Tuesday', Wed: 'Wednesday', Thu: 'Thursday', Fri: 'Friday', Sat: 'Saturday', Sun: 'Sunday' };
+const DOW_ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
+function to24(t) {
+  const m = String(t).match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
+  if (!m) return null;
+  let h = +m[1]; const ap = m[3].toUpperCase();
+  if (ap === 'PM' && h !== 12) h += 12;
+  if (ap === 'AM' && h === 12) h = 0;
+  return String(h).padStart(2, '0') + ':' + m[2];
+}
+function parseHoursLine(line) {
+  const times = String(line).match(/\d{1,2}:\d{2}\s*[AP]M/gi);
+  if (!times || times.length < 2) return null;
+  const opens = to24(times[0]), closes = to24(times[1]);
+  const dayPart = String(line).split(/\s+\d/)[0].trim();
+  const dr = dayPart.split(/[–—-]/).map(s => s.trim());
+  let days = [];
+  if (dr.length === 2) { const a = DOW_ORDER.indexOf(dr[0]), b = DOW_ORDER.indexOf(dr[1]); if (a >= 0 && b >= 0) days = DOW_ORDER.slice(a, b + 1); }
+  else if (DOW[dr[0]]) days = [dr[0]];
+  if (!days.length || !opens || !closes) return null;
+  return { '@type': 'OpeningHoursSpecification', dayOfWeek: days.map(d => DOW[d]), opens, closes };
+}
+// Canonical serving origin. prestige.agentabrams.com is the PERMANENT public home for this
+// build (Steve 2026-07-27: the prestigehandcarwash.com apex does not exist — agentabrams only).
+// All self-referential SEO (canonical, og:url, sitemap, article URLs, entity url) points here.
+const SITE_ORIGIN = 'https://prestige.agentabrams.com';
+function siteOrigin() { return (SITE_ORIGIN || '').replace(/\/$/, ''); }
+function buildJsonLd() {
+  const p = places();
+  if (!p || !p.name) return null;
+  const ld = { '@context': 'https://schema.org', '@type': 'AutoWash', name: p.name };
+  if (p.legal_name && p.legal_name !== p.name) ld.alternateName = p.legal_name;
+  if (siteOrigin()) ld.url = siteOrigin();
+  if (p.phone) ld.telephone = p.phone;
+  if (p.address) {
+    const parts = p.address.split(',').map(s => s.trim());
+    const addr = { '@type': 'PostalAddress', addressCountry: 'US' };
+    if (parts[0]) addr.streetAddress = parts[0];
+    if (parts[1]) addr.addressLocality = parts[1];
+    if (parts[2]) { const sz = parts[2].match(/([A-Z]{2})\s*(\d{5})/); if (sz) { addr.addressRegion = sz[1]; addr.postalCode = sz[2]; } }
+    ld.address = addr;
+  }
+  const hrs = (p.hours || []).map(parseHoursLine).filter(Boolean);
+  if (hrs.length) ld.openingHoursSpecification = hrs;
+  const ig = p.instagram ? (/^https?:\/\//.test(p.instagram) ? p.instagram : `https://instagram.com/${String(p.instagram).replace(/^@/, '')}`) : null;
+  const same = [ig, p.yelp_url].filter(Boolean);
+  if (same.length) ld.sameAs = same;
+  // Service catalog with REAL prices (only where a numeric price exists — quote-only
+  // services list the Service without a fabricated price). Strengthens rich results.
+  const svc = (services() || []).filter(s => s && s.name);
+  if (svc.length) {
+    ld.hasOfferCatalog = {
+      '@type': 'OfferCatalog', name: 'Car Wash & Detailing Services',
+      itemListElement: svc.map(s => {
+        const offer = { '@type': 'Offer', itemOffered: { '@type': 'Service', name: s.name } };
+        if (s.blurb) offer.itemOffered.description = s.blurb;
+        if (s.price != null && !isNaN(Number(s.price))) { offer.price = Number(s.price); offer.priceCurrency = 'USD'; }
+        return offer;
+      })
+    };
+  }
+  return ld;
+}
+// Booking-objection FAQ — the SINGLE SOURCE for both the on-page accordion (/api/faqs)
+// and the FAQPage JSON-LD, so visible text and structured data can never drift. Every
+// answer is a verifiable operational commitment the business already makes on-site
+// (data-honesty rule: no invented durations, prices, or ratings). Framed as the exact
+// objections that stall a booking — folds the /dtd "D" dissent (friction reduction) into A.
+function faqs() {
+  return [
+    { q: 'Do you actually hand wash, or is it an automatic tunnel?',
+      a: 'Every car is hand washed using the two-bucket method with a foam cannon — no automatic tunnel and no brushes, so no swirl marks. It is safe for ceramic-coated, matte, and PPF-wrapped finishes.' },
+    { q: 'Is the pricing really flat, or will I get upsold?',
+      a: 'The price on the sign is the price you pay. There is no commission-driven wax-and-polish pressure — pick a service and that is the cost. The full price list is on the Services page.' },
+    { q: 'What happens if it rains right after my wash?',
+      a: 'If it rains within 48 hours of your wash, your next basic wash is on us — just come back.' },
+    { q: 'Do you clean the back seats and door jambs?',
+      a: 'Yes. Every seat is touched and door jambs are included on every wash, always. If we miss a spot, we re-do it free.' },
+    { q: 'I am sensitive to fragrances — is there an option?',
+      a: 'Yes, a fragrance-free option is available on request. Just note it when you book.' },
+    { q: 'Do you offer full detailing and ceramic coating?',
+      a: 'Yes — alongside the hand wash we offer full-service detailing and 9H ceramic coating. See the Services page for current options and flat pricing.' },
+    { q: 'Which neighborhoods do you serve?',
+      a: 'We serve the San Fernando Valley — including Sherman Oaks, Encino, Van Nuys, Studio City, Northridge, Reseda, Tarzana, Woodland Hills, and North Hollywood.' },
+    { q: 'How do I book, and how soon will I hear back?',
+      a: 'Send a request from the Book / Contact page with your vehicle and preferred time. We follow up to confirm — usually within a few hours during business hours. No pressure, no upsell.' }
+  ];
+}
+// Persistent mobile action bar — injected into customer pages so the primary CTA (Book)
+// and a click-to-call are always one thumb-tap away on phones (where most Valley car-wash
+// traffic is). CSS-only visibility (<=640px), no JS. The tel: number comes from the REAL
+// places().phone (single source, no fabrication); the Call button is omitted if no phone.
+function mobileBar(pagePath) {
+  const p = places() || {};
+  let d = String(p.phone || '').replace(/\D/g, '');
+  if (d.length === 11 && d[0] === '1') d = d.slice(1);
+  const tel = d.length === 10 ? '+1' + d : (d ? '+' + d : '');
+  const call = tel ? `<a class="mbar-btn call" href="tel:${tel}" aria-label="Call ${(p.name || 'Prestige Car Wash').replace(/"/g, '')}">📞 Call</a>` : '';
+  // On the contact page the form IS the booking action, so scroll to it (#f) rather than
+  // self-linking to the top of the page and losing any half-filled form state.
+  const bookHref = pagePath === '/contact' ? '#f' : '/contact';
+  return `<style>
+.mbar{display:none}
+@media(max-width:640px){
+  body{padding-bottom:74px}
+  .mbar{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:80;gap:10px;padding:10px 12px calc(10px + env(safe-area-inset-bottom,0px));background:rgba(11,15,22,.93);backdrop-filter:blur(10px);border-top:1px solid var(--line)}
+  .mbar-btn{flex:1;text-align:center;padding:13px 10px;border-radius:12px;font-weight:800;font-size:15px;text-decoration:none;display:flex;align-items:center;justify-content:center;gap:6px}
+  .mbar .call{background:transparent;color:var(--ink);border:1px solid var(--line)}
+  .mbar .book{background:linear-gradient(135deg,var(--brand),var(--brand2));color:#001018}
+}</style>
+<div class="mbar" aria-label="Quick actions">${call}<a class="mbar-btn book" href="${bookHref}">Book a Wash →</a></div>`;
+}
+// Honest "find & review us" trust block — links to the REAL, verified off-site profiles
+// from places() (Google Maps, Yelp, Instagram). Deliberately shows NO star number: ratings
+// drift and would go stale on-page, so we route to the authoritative live profile instead
+// (data-honesty rule — never display a number we can't keep verifiably current). Only a
+// profile with a real non-empty value is rendered (no dead links). Server-rendered so the
+// off-site authority links ship in the crawler HTML.
+function trustBlockHtml() {
+  const p = places() || {};
+  const esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  const btn = (href, label, cls) => href
+    ? `<a class="${cls}" href="${esc(href)}" target="_blank" rel="noopener" style="text-decoration:none">${esc(label)}</a>` : '';
+  const igHandle = p.instagram ? '@' + String(p.instagram).replace(/^https?:\/\/(www\.)?instagram\.com\//i, '').replace(/\/+$/, '').replace(/^@/, '') : '';
+  const igUrl = p.instagram ? (/^https?:\/\//.test(p.instagram) ? p.instagram : 'https://instagram.com/' + String(p.instagram).replace(/^@/, '')) : '';
+  const links = [
+    btn(p.maps_query, '📍 Find us on Google Maps', 'btn'),
+    btn(p.yelp_url, '⭐ Read reviews on Yelp', 'btn ghost'),
+    btn(igUrl, '📸 ' + igHandle, 'btn ghost')
+  ].filter(Boolean);
+  if (!links.length) return '';
+  // Review-SOLICITATION CTA routes to Google's write-review dialog — and ONLY when a real
+  // Google place_id is present (currently empty). We deliberately do NOT solicit on Yelp
+  // (Yelp's "Don't Ask for Reviews" policy filters solicited reviews into "not recommended",
+  // burying exactly what we'd be trying to grow), and we never point "leave a review" at a
+  // bare search URL or a guessed id. So the CTA stays gated on a verified place_id and simply
+  // appears once it's set — the "Read reviews on Yelp" link above stays (finding ≠ soliciting).
+  const pid = /^[A-Za-z0-9_-]{20,}$/.test(String(p.place_id || '')) ? p.place_id : '';
+  const reviewHref = pid ? `https://search.google.com/local/writereview?placeid=${encodeURIComponent(pid)}` : '';
+  const cta = reviewHref ? `<div style="margin-top:16px">${btn(reviewHref, 'Leave us a review on Google →', 'btn')}</div>` : '';
+  return `<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center">${links.join('')}</div>${cta}`;
+}
+function buildFaqLd() {
+  const list = faqs();
+  if (!list.length) return null;
+  return {
+    '@context': 'https://schema.org', '@type': 'FAQPage',
+    mainEntity: list.map(f => ({
+      '@type': 'Question', name: f.q,
+      acceptedAnswer: { '@type': 'Answer', text: f.a }
+    }))
+  };
+}
+// Server-rendered accordion HTML from the SAME faqs() source. Injected into the #faqList
+// placeholder (see pageHtml) so the visible FAQ ships in the crawler-received HTML and
+// MATCHES the FAQPage JSON-LD — Google requires structured data to reflect visible content
+// (client-only rendering risks a manual action). Native <details> means zero JS needed.
+function faqHtml() {
+  const esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  return faqs().map((f, i) => `
+    <details class="faq"${i === 0 ? ' open' : ''} style="border:1px solid var(--line);border-radius:12px;margin-bottom:10px;background:var(--panel)">
+      <summary style="cursor:pointer;padding:15px 18px;font-weight:700;font-size:16px;display:flex;justify-content:space-between;gap:12px;align-items:center">
+        <span>${esc(f.q)}</span><span class="faq-mk" aria-hidden="true" style="color:var(--mut);font-weight:800;font-size:20px">+</span>
+      </summary>
+      <div style="padding:0 18px 16px;color:var(--mut);font-size:15px;line-height:1.55">${esc(f.a)}</div>
+    </details>`).join('');
+}
+// ── Evergreen car-care guides (DTD iteration 5) ────────────────────────────────────────
+// Genuinely useful, HONEST top-of-funnel content: established detailing best-practice
+// guidance phrased as advice (no fabricated statistics, no invented studies). Rendered
+// server-side so it's fully crawlable, with BlogPosting + BreadcrumbList JSON-LD and
+// internal links into services/booking. GUIDE_DATE is the real publish date.
+const GUIDE_DATE = '2026-07-26';
+function guides() {
+  return [
+    {
+      slug: 'how-often-to-wash-a-ceramic-coated-car',
+      title: 'How Often Should You Wash a Ceramic-Coated Car?',
+      description: 'A ceramic coating changes the maintenance math. Here is a sensible wash rhythm that protects the coating without over-washing.',
+      dek: 'A coating is slick and sacrificial — but it still needs the right care to keep beading and looking its best.',
+      sections: [
+        { h: 'The short answer', p: 'For a daily-driven coated car, every two weeks is a sensible default. Stretch it if the car lives in a garage; tighten it after rain, road grime, or bird droppings — droppings are acidic and can etch even a coated surface if left to bake in the sun.' },
+        { h: 'Why gentle washing matters more, not less', p: 'A coating makes dirt easier to remove, but automatic tunnel brushes still drag grit across the surface and dull the gloss over time. A gentle two-bucket hand wash with a pH-neutral soap preserves the coating’s hydrophobic behavior far longer than a tunnel ever will.' },
+        { h: 'What to avoid', p: 'Skip strong degreasers and high-alkaline wheel chemicals on coated paint — they strip the coating’s top layer. And never let water spot-dry in direct sun; the dissolved minerals bond to the coating and are a pain to remove.' },
+        { h: 'When to top it up', p: 'If water stops beading and starts sheeting flat, the coating is asking for help. A spray-on SiO2 booster applied after a wash restores hydrophobicity between professional maintenance visits.' }
+      ]
+    },
+    {
+      slug: 'hand-wash-vs-automatic-tunnel',
+      title: 'Hand Wash vs. Automatic Tunnel: What Is Actually Safe for Your Paint',
+      description: 'Automatic tunnels are fast and cheap — but here is what they do to your clear coat, and why hand washing is gentler.',
+      dek: 'The difference comes down to one thing: grit, and whether it gets dragged across your paint.',
+      sections: [
+        { h: 'Where tunnel washes go wrong', p: 'Tunnels recirculate water and run stiff brushes or cloth strips that have already touched every car before yours. The grit they carry is exactly what puts fine swirl marks and hairline scratches into a clear coat.' },
+        { h: 'Why two-bucket hand washing is gentler', p: 'Two buckets — one of clean soapy water, one to rinse the mitt — let grit drop out of circulation instead of going back onto the paint. A foam cannon lifts and floats dirt off the surface before the mitt ever touches it.' },
+        { h: 'The finishes that cannot take a tunnel', p: 'Ceramic coatings, matte and satin paint, and paint protection film (PPF) can all be dulled, hazed, or lifted at the edges by automated equipment. For any of these, hand washing is the safe default.' },
+        { h: 'The honest trade-off', p: 'Tunnels win on speed and price per wash. If your car is a daily beater, that may be all you need. If you care about the finish — or you have invested in a coating, a wrap, or a fresh repaint — hand washing pays for itself in preserved gloss and resale value.' }
+      ]
+    },
+    {
+      slug: 'protecting-matte-and-ppf-finishes',
+      title: 'Protecting Matte and PPF Finishes: A Care Guide',
+      description: 'Matte paint and paint protection film need different care than glossy clear coat. Here is how to keep them looking right.',
+      dek: 'The wrong product on a matte finish is a mistake you cannot polish out — so it pays to know the rules.',
+      sections: [
+        { h: 'Matte is a different animal', p: 'Matte and satin clear coats get their look from a micro-textured surface that scatters light. Anything that fills or polishes that texture — wax, sealant, polish, or a buffer — creates permanent shiny spots that can only be corrected with a refinish.' },
+        { h: 'How to wash matte safely', p: 'Hand wash only, with a matte-specific (wax-free) pH-neutral shampoo, a soft mitt, and straight-line motions rather than circles. Dry with a clean microfiber or a filtered-air blower instead of dragging a towel across the texture.' },
+        { h: 'Caring for PPF', p: 'Paint protection film is tough, but its edges can trap dirt and lift if scrubbed hard. Keep the edges clean, avoid blasting high-pressure water straight at the seams, and skip cutting compounds. Many modern films are self-healing — light swirls vanish in the sun or after a warm rinse.' },
+        { h: 'What we do', p: 'Our hand wash is matte- and PPF-safe by default: no automatic brushes, never any wax on matte, and a fragrance-free option on request. Tell us what your car is wearing and we will treat it accordingly.' }
+      ]
+    }
+  ];
+}
+function _e(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
+function guideLayout(title, description, bodyHtml) {
+  return `<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>${_e(title)}</title>
+<meta name="description" content="${_e(description)}">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/assets/pcw.css">
+</head><body>
+<nav class="nav"><div class="wrap row">
+  <div class="brand"><span class="mk">🚗</span><div>Prestige<br><small>Car Wash · SFV</small></div></div>
+  <button class="hamb" aria-label="Menu" aria-expanded="false" onclick="var o=document.getElementById('lnk').classList.toggle('open');this.setAttribute('aria-expanded',o)">☰</button>
+  <div class="links" id="lnk"><a href="/">Home</a><a href="/services">Services</a><a href="/guides" class="on">Guides</a><a class="btn" href="/contact">Book a Wash</a></div>
+</div></nav>
+${bodyHtml}
+<footer class="foot"><div class="wrap">© 2026 Prestige Car Wash · San Fernando Valley, CA · <a href="/services">Services</a> · <a href="/guides">Guides</a> · <a href="/contact">Book</a></div></footer>
+</body></html>`;
+}
+function guideCtaHtml() {
+  return `<div class="card" style="max-width:760px;margin:26px auto 0"><div class="body" style="text-align:center">
+    <div class="ttl" style="font-size:20px">Want it done right, by hand?</div>
+    <div class="blurb">We hand wash every car — coating-, matte-, and PPF-safe — with no upsell pressure.</div>
+    <div style="margin-top:12px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
+      <a class="btn" href="/contact">Book a Wash</a><a class="btn ghost" href="/services">See Services &amp; Pricing</a>
+    </div>
+  </div></div>`;
+}
+function guideArticleHtml(g) {
+  const secs = g.sections.map(s => `<h2>${_e(s.h)}</h2>\n<p>${_e(s.p)}</p>`).join('\n');
+  return `<article class="section"><div class="wrap" style="max-width:760px">
+    <div style="margin-bottom:10px"><a href="/guides" style="color:var(--mut);font-size:13px">← Car-care guides</a></div>
+    <span class="pill">Car-care guide</span>
+    <h1>${_e(g.title)}</h1>
+    <p class="lead">${_e(g.dek)}</p>
+    ${secs}
+    ${guideCtaHtml()}
+  </div></article>`;
+}
+function guideIndexHtml() {
+  const cards = guides().map(g => `<a class="card" href="/guides/${_e(g.slug)}" style="text-decoration:none;color:inherit"><div class="body">
+      <div class="cat">Car-care guide</div>
+      <div class="ttl">${_e(g.title)}</div>
+      <div class="blurb">${_e(g.description)}</div>
+      <div style="margin-top:8px;color:var(--brand);font-weight:700;font-size:14px">Read →</div>
+    </div></a>`).join('');
+  return `<section class="section"><div class="wrap">
+    <span class="pill">Guides</span>
+    <h1>Car-care guides</h1>
+    <p class="sub">Straight, no-nonsense advice on keeping your car’s finish looking its best — from the crew that hand washes them.</p>
+    <div class="grid" style="--cols:3">${cards}</div>
+  </div></section>`;
+}
+function buildArticleLd(g, canonicalUrl) {
+  const name = (places() || {}).name || 'Prestige Car Wash';
+  const ld = {
+    '@context': 'https://schema.org', '@type': 'BlogPosting',
+    headline: g.title, description: g.description,
+    datePublished: GUIDE_DATE, dateModified: GUIDE_DATE,
+    author: { '@type': 'Organization', name }, publisher: { '@type': 'Organization', name },
+    articleSection: 'Car care'
+  };
+  if (canonicalUrl) ld.mainEntityOfPage = canonicalUrl;
+  return ld;
+}
+function buildBreadcrumbLd(items) {
+  return {
+    '@context': 'https://schema.org', '@type': 'BreadcrumbList',
+    itemListElement: items.map((it, i) => ({ '@type': 'ListItem', position: i + 1, name: it.name, item: it.url }))
+  };
+}
+// Open Graph / Twitter tags for the homepage — reuses the page's own <title> and meta
+// description so social previews stay in sync with on-page SEO. og:image is a real asset.
+function buildSocialTags(html, pagePath) {
+  // Bail if the HTML already carries og: tags OR a canonical (e.g. added directly to the
+  // file) so we never inject a duplicate that makes Facebook/Google flag the markup.
+  if (/property=["']og:title["']/i.test(html) || /<link\b[^>]*\brel=["']canonical["']/i.test(html)) return '';
+  const p = places() || {};
+  const base = siteOrigin();
+  const url = base ? base + (pagePath || '') : '';   // page-specific canonical + og:url
+  const title = (html.match(/<title>([^<]*)<\/title>/i) || [])[1] || p.name || 'Prestige Car Wash';
+  // Attribute-order-independent description extraction, with a real fallback (never a
+  // blank social card). Handles both name-first and content-first, single or double quotes.
+  const descMatch = html.match(/<meta\b[^>]*\bname=["']description["'][^>]*\bcontent=["']([^"']*)["']/i)
+                 || html.match(/<meta\b[^>]*\bcontent=["']([^"']*)["'][^>]*\bname=["']description["']/i);
+  const desc = (descMatch || [])[1] || `${p.name || 'Prestige Car Wash'} — no-pressure flat pricing, two-bucket hand wash, detailing & ceramic coating in the San Fernando Valley.`;
+  const img = base ? base + '/media/svc-full-service.png' : '';
+  const esc = s => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
+  // Individual guide articles are og:type "article"; everything else is a "website".
+  const ogType = /^\/guides\/.+/.test(pagePath || '') ? 'article' : 'website';
+  const tags = [
+    ['og:type', ogType], ['og:site_name', p.name || 'Prestige Car Wash'],
+    ['og:title', title], ['og:description', desc], url && ['og:url', url], img && ['og:image', img],
+    ['twitter:card', img ? 'summary_large_image' : 'summary'],
+    ['twitter:title', title], ['twitter:description', desc], img && ['twitter:image', img]
+  ].filter(Boolean);
+  const metas = tags.map(([k, v]) => k.startsWith('twitter')
+    ? `<meta name="${k}" content="${esc(v)}">`
+    : `<meta property="${k}" content="${esc(v)}">`).join('\n');
+  return (url ? `<link rel="canonical" href="${esc(url)}">\n` : '') + metas;
+}
+// Built fresh per request (sync file read + one string replace = microseconds) so the
+// injected JSON-LD/OG NEVER drift from live places.json — a stale NAP is the exact thing
+// Google penalizes, which would defeat the point of injecting it. (contrarian gate, 2026-07-26)
+// GA4 gtag snippet — injected on the customer pages ONLY when GA_MEASUREMENT_ID is set
+// (a valid G-XXXX id). No-op until then, so this is safe to ship before the id exists.
+// Admin (internal) is intentionally NOT tracked as customer traffic.
+function gaSnippet() {
+  const id = process.env.GA_MEASUREMENT_ID;
+  if (!id || !/^G-[A-Z0-9]{6,}$/.test(id)) return '';
+  return `\n<script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>` +
+         `\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${id}');</script>`;
+}
+// Shared page decorator: takes a full HTML string and injects the FAQ/trust placeholders,
+// the mobile bar, OG/canonical tags, JSON-LD, and the GA snippet. Used by BOTH file-backed
+// pages (pageHtml) and server-generated pages (the /guides articles), so head/injection logic
+// lives in exactly one place.
+function decorate(html, pagePath, ld) {
+  // Fill the FAQ accordion server-side so the visible content is in the crawler-received
+  // HTML and matches the FAQPage JSON-LD. Only touches a page that carries the placeholder.
+  if (html.includes('id="faqList"')) {
+    html = html.replace(/(<div id="faqList"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + faqHtml() + close);
+  }
+  // Fill the honest find-&-review trust block server-side (real off-site profile links).
+  // If there are no real profiles to show, strip the WHOLE section so a heading never floats
+  // over an empty body (graceful degradation, per contrarian).
+  if (html.includes('id="findReview"')) {
+    const tb = trustBlockHtml();
+    if (tb) html = html.replace(/(<div id="findReview"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + tb + close);
+    else html = html.replace(/<section class="section" id="findreview">[\s\S]*?<\/section>/, '');
+  }
+  // Persistent mobile Book/Call bar on every customer page (guard against double-inject).
+  if (!/class="mbar"/.test(html)) html = html.replace('</body>', mobileBar(pagePath) + '\n</body>');
+  // Admin ↔ live-view toggle: on customer pages show a "🔧 Admin" pill ONLY if this browser
+  // carries the pcw.admin flag (set when the admin visits /admin). Invisible to real customers;
+  // gives the logged-in admin a one-tap way back to the admin from any live page.
+  html = html.replace('</body>', `<script>(function(){try{if(localStorage.getItem('pcw.admin')!=='1')return;var a=document.createElement('a');a.href='/admin';a.textContent='🔧 Admin view';a.title='Switch to admin view';a.setAttribute('aria-label','Switch to admin view');a.style.cssText='position:fixed;left:12px;bottom:12px;z-index:95;background:#12100e;color:#c6a765;font:700 11px/1 system-ui,-apple-system,sans-serif;letter-spacing:.1em;text-transform:uppercase;padding:9px 13px;border-radius:6px;text-decoration:none;box-shadow:0 6px 18px rgba(0,0,0,.45);border:1px solid rgba(198,167,101,.45)';document.body.appendChild(a);}catch(e){}})();</script>\n</body>`);
+  let inject = buildSocialTags(html, pagePath);
+  // Perf: preconnect to the font-file origin (pages already preconnect googleapis; the actual
+  // WOFF2 files come from gstatic, so this shaves a round-trip on first paint). Dedup-guarded.
+  if (!/fonts\.gstatic\.com/.test(html)) inject += '\n<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
+  // ld may be a single JSON-LD object OR an array of them (e.g. AutoWash + FAQPage on the
+  // homepage). Emit one <script> per object; escape "<" so the JSON can't break out of the tag.
+  for (const obj of [].concat(ld || []).filter(Boolean)) {
+    inject += `\n<script type="application/ld+json">${JSON.stringify(obj).replace(/</g, '\\u003c')}</script>`;
+  }
+  inject += gaSnippet();
+  // First-party pageview beacon (analytics-lite) — sendBeacon so it never blocks paint; the
+  // /api/track endpoint only increments an aggregate counter (no cookie/IP/PII). Customer
+  // pages only (admin isn't served through decorate, so the owner's own visits aren't counted).
+  inject += `\n<script>(function(){try{var b=JSON.stringify({event:'pageview'});if(navigator.sendBeacon){navigator.sendBeacon('/api/track',new Blob([b],{type:'application/json'}))}else{fetch('/api/track',{method:'POST',headers:{'Content-Type':'application/json'},body:b,keepalive:true})}}catch(e){}})();</script>`;
+  return html.replace('</head>', inject + '\n</head>');
+}
+function pageHtml(file, pagePath, ld) {
+  return decorate(fs.readFileSync(path.join(__dirname, 'public', file), 'utf8'), pagePath, ld);
+}
+const sendPage = (res, file, pagePath, ld) => {
+  // If tag INJECTION throws, still serve the raw page (graceful degradation). If the file
+  // itself is unreadable, sendFile's callback guarantees a clean 500 — never a hung response.
+  try { res.type('html').send(pageHtml(file, pagePath, ld)); }
+  catch { res.sendFile(path.join(__dirname, 'public', file), err => { if (err && !res.headersSent) res.status(500).end(); }); }
+};
+// FAQ list for the on-page accordion — same source the homepage FAQPage schema is built from.
+app.get('/api/faqs', (req, res) => res.json(faqs()));
+// Homepage carries BOTH the AutoWash business schema and the FAQPage schema (the FAQ is
+// visibly rendered on the page below, which is what makes the FAQPage markup legitimate).
+app.get('/', (req, res) => sendPage(res, 'index.html', '', [buildJsonLd(), buildFaqLd()]));
+app.get('/services', (req, res) => sendPage(res, 'services.html', '/services', null));
+app.get('/contact', (req, res) => sendPage(res, 'contact.html', '/contact', null));
+// Car-care guides — server-generated (shares the decorate() pipeline: OG/canonical/GA/mobile bar).
+app.get('/guides', (req, res) => {
+  try {
+    const html = guideLayout('Car-Care Guides — Prestige Car Wash',
+      'Honest, practical car-care guides from a San Fernando Valley hand-wash & detail shop: ceramic-coating care, hand wash vs. tunnel, and matte & PPF protection.',
+      guideIndexHtml());
+    res.type('html').send(decorate(html, '/guides', null));
+  } catch { res.status(500).end(); }
+});
+app.get('/guides/:slug', (req, res) => {
+  const g = guides().find(x => x.slug === req.params.slug);
+  const base = siteOrigin();
+  if (!g) {
+    const nf = guideLayout('Guide Not Found — Prestige Car Wash', 'That guide could not be found.',
+      `<section class="section"><div class="wrap"><h1>Guide not found</h1><p class="sub">That guide may have moved. <a href="/guides">Browse all car-care guides →</a></p></div></section>`);
+    return res.status(404).type('html').send(decorate(nf, '/guides', null));
+  }
+  const canonical = base ? base + '/guides/' + g.slug : '';
+  const ld = [
+    buildArticleLd(g, canonical),
+    buildBreadcrumbLd([
+      { name: 'Home', url: base || '/' },
+      { name: 'Guides', url: (base || '') + '/guides' },
+      { name: g.title, url: canonical || ('/guides/' + g.slug) }
+    ])
+  ];
+  try {
+    res.type('html').send(decorate(guideLayout(g.title + ' — Prestige Car Wash', g.description, guideArticleHtml(g)), '/guides/' + g.slug, ld));
+  } catch { res.status(500).end(); }
+});
+
+// XML sitemap generated from the public URL — kept in one place, no static file to drift.
+app.get('/sitemap.xml', (req, res) => {
+  const base = siteOrigin();
+  if (!base) return res.status(404).end();
+  const urls = ['/', '/services', '/contact', '/guides', ...guides().map(g => '/guides/' + g.slug)];
+  const xml = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
+    urls.map(u => `  <url><loc>${base}${u}</loc></url>`).join('\n') + '\n</urlset>';
+  res.type('application/xml').send(xml);
+});
+
+app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
+
+app.listen(PORT, () => {
+  console.log(`[prestige-car-wash] http://localhost:${PORT}  (admin: /admin)`);
+  if (process.env.NODE_ENV === 'production' && !process.env.ADMIN_PASS) {
+    console.warn('⚠ SECURITY: running in production with the DEFAULT admin password. Set ADMIN_PASS in .env before exposing this host.');
+  }
+});
diff --git a/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.candidate.js b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.candidate.js
new file mode 100644
index 00000000..99c98c92
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/prestige/review.dtOpeo/server.candidate.js
@@ -0,0 +1,894 @@
+'use strict';
+/**
+ * Prestige Car Wash (PCW) — one Express app serving:
+ *   - the public marketing site (/, /services, /contact)
+ *   - the Basic-Auth growth admin (/admin) + its bucket APIs (/api/admin/*)
+ *
+ * Data source: data/*.json snapshots (Postgres is an optional future upgrade; the
+ * schema in scripts/db-init.sql mirrors these files). Everything the front-end needs
+ * comes through /api/* so the same catalog powers both the public page and the admin.
+ */
+require('dotenv').config();
+const fs = require('fs');
+const path = require('path');
+const express = require('express');
+const helmet = require('helmet');
+
+const app = express();
+const PORT = process.env.PORT || 9808;
+const ADMIN_USER = process.env.ADMIN_USER || 'admin';
+const ADMIN_PASS = process.env.ADMIN_PASS || 'DW2024!';
+const DATA = path.join(__dirname, 'data');
+
+// ---- helpers ---------------------------------------------------------------
+const readJSON = (f, fallback) => {
+  try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); }
+  catch { return fallback; }
+};
+// Re-read on each request so admin edits / script runs show up without a restart.
+const services     = () => readJSON('services.json', []);
+const competitors  = () => readJSON('competitors.json', []);
+const suggestions  = () => readJSON('suggestions.json', []);
+const holidays     = () => readJSON('holidays.json', []);
+const directories  = () => readJSON('directories.json', []);
+const ads          = () => readJSON('ads.json', []);
+const bestTimes    = () => readJSON('best-times.json', {});
+const places       = () => readJSON('places.json', {});
+const socialTpl    = () => readJSON('social-templates.json', { platforms: [], hashtag_sets: {}, templates: [] });
+const promos       = () => readJSON('promos.json', { promos: [] });
+
+// Mask a secret to a "present (…last4)" descriptor — never returns the value.
+const maskEnv = (key) => {
+  const v = process.env[key];
+  if (!v) return { key, present: false, hint: '' };
+  return { key, present: true, hint: '…' + String(v).slice(-4) };
+};
+
+// ---- middleware ------------------------------------------------------------
+app.use(helmet({
+  contentSecurityPolicy: {
+    directives: {
+      defaultSrc: ["'self'"],
+      scriptSrc: ["'self'", "'unsafe-inline'"],
+      // inline event-handler attributes (onclick=, onerror=) are a separate directive;
+      // Helmet defaults it to 'none'. The site/admin use inline handlers (house style,
+      // Basic-Auth admin, no user-generated content) so allow them explicitly.
+      scriptSrcAttr: ["'unsafe-inline'"],
+      styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
+      fontSrc: ["'self'", 'https://fonts.gstatic.com', 'data:'],
+      imgSrc: ["'self'", 'data:', 'https:'],
+      mediaSrc: ["'self'", 'data:', 'blob:'],
+      connectSrc: ["'self'"]
+    }
+  }
+}));
+app.use(express.json());
+
+// Basic Auth gate for /admin and /api/admin/*
+function requireAdmin(req, res, next) {
+  const hdr = req.headers.authorization || '';
+  const [scheme, encoded] = hdr.split(' ');
+  if (scheme === 'Basic' && encoded) {
+    const [u, p] = Buffer.from(encoded, 'base64').toString().split(':');
+    if (u === ADMIN_USER && p === ADMIN_PASS) return next();
+  }
+  res.set('WWW-Authenticate', 'Basic realm="Prestige Admin"').status(401).send('Auth required');
+}
+app.use('/admin', requireAdmin);
+app.use('/api/admin', requireAdmin);
+
+// ---- sort (shared by public services grid) --------------------------------
+function sortServices(list, mode) {
+  const arr = [...list];
+  const byTitle = (a, b) => String(a.name).localeCompare(String(b.name));
+  switch (mode) {
+    case 'title':      return arr.sort(byTitle);
+    case 'price-asc':  return arr.sort((a, b) => (a.price || 0) - (b.price || 0) || byTitle(a, b));
+    case 'price-desc': return arr.sort((a, b) => (b.price || 0) - (a.price || 0) || byTitle(a, b));
+    case 'duration':   return arr.sort((a, b) => (a.duration_min || 0) - (b.duration_min || 0) || byTitle(a, b));
+    case 'featured':
+    default:           return arr.sort((a, b) => ((b.featured ? 1 : 0) - (a.featured ? 1 : 0)) || (a.sort_order || 99) - (b.sort_order || 99));
+  }
+}
+
+// Price bands for the left filter panel.
+const priceBand = (p) => p <= 25 ? 'Under $25' : p <= 60 ? '$25–$60' : p <= 150 ? '$60–$150' : '$150+';
+
+// ---- public API ------------------------------------------------------------
+app.get('/api/health', (req, res) => {
+  res.json({
+    ok: true, service: 'prestige-car-wash', port: PORT,
+    counts: {
+      services: services().length, competitors: competitors().length,
+      suggestions: suggestions().length, holidays: holidays().length,
+      directories: directories().length, ads: ads().length,
+      promos: (promos().promos || []).length
+    },
+    ts: new Date().toISOString()
+  });
+});
+
+app.get('/api/services', (req, res) => {
+  let list = services().map(s => ({ ...s, price_band: priceBand(s.price || 0) }));
+  const { category, band, q } = req.query;
+  if (category) list = list.filter(s => s.category === category);
+  if (band) list = list.filter(s => s.price_band === band);
+  if (q) {
+    const needle = String(q).toLowerCase();
+    list = list.filter(s => [s.name, s.blurb, s.category].join(' ').toLowerCase().includes(needle));
+  }
+  res.json(sortServices(list, req.query.sort));
+});
+
+// Facet counts for the left panel — each dimension counted over the OTHER active filters.
+app.get('/api/facets', (req, res) => {
+  const base = services().map(s => ({ ...s, price_band: priceBand(s.price || 0) }));
+  const tally = (rows, key) => rows.reduce((m, r) => (m[r[key]] = (m[r[key]] || 0) + 1, m), {});
+  const applyExcept = (except) => base.filter(s =>
+    (except === 'category' || !req.query.category || s.category === req.query.category) &&
+    (except === 'band' || !req.query.band || s.price_band === req.query.band)
+  );
+  res.json({
+    category: tally(applyExcept('category'), 'category'),
+    band: tally(applyExcept('band'), 'price_band')
+  });
+});
+
+app.get('/api/best-times', (req, res) => res.json(bestTimes()));
+// Emit ready-to-use photo/video URLs so the client is format-agnostic:
+// Google photo refs → the proxy route; local paths (real business photos) → direct.
+app.get('/api/places', (req, res) => {
+  const p = places();
+  const photo_urls = (p.photos || []).map((ph, i) =>
+    /^places\//.test(ph) ? `/api/places/photo/${i}` : '/' + String(ph).replace(/^\//, ''));
+  const videos = (p.videos || []).map(v => ({
+    ...v,
+    src: '/' + String(v.src || '').replace(/^\//, ''),
+    poster: v.poster ? '/' + String(v.poster).replace(/^\//, '') : ''
+  }));
+  res.json({ ...p, photo_urls, videos });
+});
+
+// ---- admin bucket APIs -----------------------------------------------------
+app.get('/api/admin/competitors', (req, res) => res.json(competitors()));
+app.get('/api/admin/suggestions', (req, res) => res.json(suggestions()));
+app.get('/api/admin/holidays', (req, res) => res.json(holidays()));
+app.get('/api/admin/directories', (req, res) => res.json(directories()));
+app.get('/api/admin/ads', (req, res) => res.json(ads()));
+app.get('/api/admin/services', (req, res) => res.json(services()));
+app.get('/api/admin/best-times', (req, res) => res.json(bestTimes()));
+app.get('/api/admin/places', (req, res) => res.json(places()));
+
+// ---- Socials: create + post from ONE place (draft-only; never auto-posts) --
+// Media library: every image/video under /media (incl. media/real/), each video
+// paired with its poster image (svc-<stem>.png or <stem>-poster.jpg) so the
+// composer can preview + attach any asset. Read-only scan of the media dir.
+const MEDIA_DIR = path.join(__dirname, 'media');
+function mediaLibrary() {
+  const walk = (dir, prefix) => {
+    let out = [];
+    let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return out; }
+    for (const e of entries) {
+      if (e.isDirectory()) { out = out.concat(walk(path.join(dir, e.name), prefix + e.name + '/')); continue; }
+      const ext = path.extname(e.name).toLowerCase();
+      const kind = ['.mp4', '.mov', '.webm'].includes(ext) ? 'video'
+        : ['.png', '.jpg', '.jpeg', '.webp', '.gif'].includes(ext) ? 'image' : null;
+      if (!kind) continue;
+      out.push({ name: e.name, stem: e.name.replace(/\.[^.]+$/, ''), kind, url: '/media/' + prefix + e.name });
+    }
+    return out;
+  };
+  const all = walk(MEDIA_DIR, '');
+  const imgByStem = {};
+  all.filter(m => m.kind === 'image').forEach(m => { imgByStem[m.stem] = m.url; });
+  all.forEach(m => { if (m.kind === 'video') m.poster = imgByStem[m.stem] || imgByStem[m.stem + '-poster'] || ''; });
+  // Hide poster stills that only exist to back a video (keep genuine standalone photos).
+  const videoStems = new Set(all.filter(m => m.kind === 'video').map(m => m.stem));
+  return all
+    .filter(m => !(m.kind === 'image' && (videoStems.has(m.stem) || videoStems.has(m.stem.replace(/-poster$/, '')))))
+    .sort((a, b) => a.name.localeCompare(b.name));
+}
+app.get('/api/admin/media', (req, res) => res.json(mediaLibrary()));
+app.get('/api/admin/social/templates', (req, res) => res.json(socialTpl()));
+
+// ---- Promos: HyperFrames-generated brand films -----------------------------
+// Curated, first-class list of the finished brand videos (distinct from the raw
+// svc-* service clips in the media library). Repo-relative src/poster are
+// normalized to absolute /media URLs so the client stays format-agnostic; any
+// promo whose file is missing on disk is flagged (ready=false) rather than
+// served as a broken <video>. mm:ss is precomputed for the card badge.
+const abs = (p) => '/' + String(p || '').replace(/^\//, '');
+const mmss = (s) => { const sec=Math.max(0,parseInt(s,10)||0); return `${Math.floor(sec/60)}:${String(sec%60).padStart(2,'0')}`; };
+app.get('/api/admin/promos', (req, res) => {
+  const list = (promos().promos || []).map(p => {
+    const rel = String(p.src || '').replace(/^\//, '');
+    const ready = rel ? fs.existsSync(path.join(__dirname, rel)) : false;
+    return {
+      ...p,
+      src: abs(p.src),
+      poster: p.poster ? abs(p.poster) : '',
+      duration_label: p.duration_sec ? mmss(p.duration_sec) : '',
+      ready
+    };
+  });
+  res.json(list);
+});
+
+// Save a composed post as a DRAFT (append-only). This NEVER posts to any network —
+// the front-end deep-links into each platform's native composer for the human to post.
+app.post('/api/admin/social/draft', (req, res) => {
+  const b = req.body || {};
+  const draft = {
+    id: 'sd-' + Date.now().toString(36),
+    caption: cap(b.caption, 2200),
+    platforms: Array.isArray(b.platforms) ? b.platforms.slice(0, 12).map(p => cap(p, 40)) : [],
+    asset: b.asset ? { url: cap(b.asset.url, 300), kind: cap(b.asset.kind, 12), name: cap(b.asset.name, 160) } : null,
+    scheduled_for: cap(b.scheduled_for, 40),
+    created_at: new Date().toISOString(), status: 'draft'
+  };
+  if (!draft.caption && !draft.asset) return res.status(400).json({ ok: false, error: 'need a caption or an asset' });
+  try {
+    const dir = path.join(__dirname, 'reports');
+    fs.mkdirSync(dir, { recursive: true });
+    fs.appendFileSync(path.join(dir, 'social-drafts.jsonl'), JSON.stringify(draft) + '\n');
+  } catch { return res.status(500).json({ ok: false, error: 'could not save draft' }); }
+  res.json({ ok: true, draft });
+});
+app.get('/api/admin/social/drafts', (req, res) => {
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'social-drafts.jsonl'), 'utf8').trim();
+    res.json(raw ? raw.split('\n').map(l => JSON.parse(l)).reverse() : []);
+  } catch { res.json([]); }
+});
+
+// Credentials tab — presence + last-4 only. NEVER returns secret values.
+app.get('/api/admin/credentials', (req, res) => {
+  const keys = [
+    { key: 'GEMINI_API_KEY', label: 'Nano Banana (Gemini image)', purpose: 'Generate service stills' },
+    { key: 'REPLICATE_API_TOKEN', label: 'SeeDance (Replicate)', purpose: 'Generate wash/wax video clips' },
+    { key: 'GOOGLE_PLACES_API_KEY', label: 'Google Places (read)', purpose: 'Live hours/reviews/photos — gated setup' },
+    { key: 'PCW_PLACE_ID', label: 'Google Place ID', purpose: 'Which listing to read' },
+    { key: 'DATABASE_URL', label: 'Postgres', purpose: 'Optional data backend' }
+  ];
+  res.json(keys.map(k => ({ ...maskEnv(k.key), label: k.label, purpose: k.purpose })));
+});
+
+// "Update Google Place" — draft-write: returns a prefilled Google Business Profile URL.
+// No direct API write (owner OAuth + API approval deferred). Front-end opens this in a new tab.
+app.post('/api/admin/place/draft-update', (req, res) => {
+  const { field } = req.body || {};
+  res.json({
+    ok: true, mode: 'draft',
+    message: `Draft update for "${field || 'listing'}" — opens Google Business Profile prefilled.`,
+    url: 'https://business.google.com/edit/l/' + (process.env.PCW_PLACE_ID || '')
+  });
+});
+
+// Simple per-IP rate limiter for the public form (prevents spam / unbounded lead file).
+const _hits = new Map();
+function rateLimit(max, windowMs) {
+  return (req, res, next) => {
+    const ip = req.ip || req.connection.remoteAddress || 'unknown';
+    const now = Date.now();
+    const arr = (_hits.get(ip) || []).filter(t => now - t < windowMs);
+    if (arr.length >= max) return res.status(429).json({ ok: false, error: 'Too many requests — try again shortly.' });
+    arr.push(now); _hits.set(ip, arr);
+    next();
+  };
+}
+const cap = (s, n) => String(s || '').slice(0, n);
+
+// Contact / booking lead — saved locally + optional webhook notify (env-gated).
+// NOTIFY_WEBHOOK (Slack/Discord/Zapier URL) gives leads a real notification path;
+// without it the lead is captured in the admin Leads tab. No mass email (that stays gated).
+app.post('/api/contact', rateLimit(5, 10 * 60 * 1000), (req, res) => {
+  const b = req.body || {};
+  const OK_MSG = "Thanks! We'll follow up to confirm your booking — usually within a few hours.";
+  // Honeypot: a hidden field real users never see. Field name is a NON-demographic token
+  // (b_confirm) so password managers / mobile autofill won't populate it for a real user and
+  // silently drop their lead. If it's filled it's a bot — fake success (no retry), drop it,
+  // but LOG the drop so a false-positive is auditable instead of an invisible lost booking.
+  if (b.b_confirm) { console.warn('[honeypot] dropped submission ip=%s name=%s', req.ip, cap(b.name, 60)); return res.json({ ok: true, message: OK_MSG }); }
+  const name = cap(b.name, 120).trim();
+  const phone = cap(b.phone, 40).trim();
+  const email = cap(b.email, 160).trim();
+  const emailOk = !email || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+  const phoneOk = !phone || phone.replace(/\D/g, '').length >= 7;
+  // Validate with human-readable errors so a real lead is never silently lost to a bad submit.
+  if (name.length < 2) return res.status(400).json({ ok: false, error: 'Please enter your name.' });
+  if (!phone && !email) return res.status(400).json({ ok: false, error: 'Please leave a phone number or email so we can reach you.' });
+  if (!emailOk) return res.status(400).json({ ok: false, error: 'That email address doesn’t look right — please double-check it.' });
+  if (!phoneOk) return res.status(400).json({ ok: false, error: 'That phone number looks too short — please double-check it.' });
+  const lead = {
+    id: 'ld_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
+    name, phone, email, vehicle: cap(b.vehicle, 120), service: cap(b.service, 120),
+    preferred: cap(b.preferred, 120), message: cap(b.message, 1000),
+    created_at: new Date().toISOString(), source: 'web-form'
+  };
+  // Duplicate-submit guard: if the same person (name + phone + email + message) already
+  // landed within the last 2 minutes, treat it as a double-click — succeed without saving twice.
+  try {
+    const lp = path.join(__dirname, 'reports', 'leads.jsonl');
+    if (fs.existsSync(lp)) {
+      const recent = fs.readFileSync(lp, 'utf8').trim().split('\n').slice(-25);
+      const now = Date.now();
+      const dup = recent.some(l => { try { const o = JSON.parse(l); return (now - new Date(o.created_at).getTime() < 120000) && o.name === name && (o.phone || '') === phone && (o.email || '') === email && (o.message || '') === lead.message; } catch { return false; } });
+      if (dup) return res.json({ ok: true, message: "Thanks — we’ve already got your request and we’ll follow up shortly." });
+    }
+  } catch { /* if the dedupe read fails, fall through and save (never block a real lead) */ }
+  try {
+    const dir = path.join(__dirname, 'reports');
+    fs.mkdirSync(dir, { recursive: true });
+    fs.appendFileSync(path.join(dir, 'leads.jsonl'), JSON.stringify(lead) + '\n');
+  } catch (e) { return res.status(500).json({ ok: false, error: 'could not save lead' }); }
+  // Fire-and-forget notification if a webhook is configured.
+  if (process.env.NOTIFY_WEBHOOK) {
+    fetch(process.env.NOTIFY_WEBHOOK, {
+      method: 'POST', headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ text: `🚗 New Prestige lead: ${lead.name} (${lead.phone || lead.email}) — ${lead.service || 'general'}${lead.message ? ' · ' + lead.message : ''}` })
+    }).catch(() => {});
+  }
+  res.json({ ok: true, message: "Thanks! We'll follow up to confirm your booking — usually within a few hours." });
+});
+
+// Lead pipeline status — kept in a SEPARATE json map (keyed by lead id, or created_at for
+// legacy leads) so the append-only leads.jsonl log is never rewritten. dtd:C 2026-07-26.
+const LEAD_STATUSES = ['new', 'contacted', 'booked', 'won', 'lost'];
+const leadStatusPath = () => path.join(__dirname, 'reports', 'lead-status.json');
+function readLeadStatus() { try { return JSON.parse(fs.readFileSync(leadStatusPath(), 'utf8')); } catch { return {}; } }
+
+// Admin: view captured leads, each merged with its pipeline status (default 'new').
+app.get('/api/admin/leads', (req, res) => {
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'leads.jsonl'), 'utf8').trim();
+    const st = readLeadStatus();
+    const leads = raw ? raw.split('\n').map(l => JSON.parse(l)) : [];
+    leads.forEach(l => { l.status = st[l.id || l.created_at] || 'new'; });
+    res.json(leads.reverse());
+  } catch { res.json([]); }
+});
+
+// Admin: export all leads (+ pipeline status) as CSV for offline follow-up / records.
+app.get('/api/admin/leads.csv', (req, res) => {
+  let leads = [];
+  try {
+    const raw = fs.readFileSync(path.join(__dirname, 'reports', 'leads.jsonl'), 'utf8').trim();
+    const st = readLeadStatus();
+    leads = raw ? raw.split('\n').map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean) : [];
+    leads.forEach(l => { l.status = st[l.id || l.created_at] || 'new'; });
+    leads.reverse();
+  } catch { /* empty export is still a valid CSV */ }
+  const cols = ['created_at', 'name', 'phone', 'email', 'vehicle', 'service', 'preferred', 'message', 'status'];
+  const esc = v => {
+    let s = String(v == null ? '' : v);
+    if (/^[=+\-@]/.test(s)) s = "'" + s;                          // neutralize CSV/formula injection
+    return /[",\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
+  };
+  const out = [cols.join(',')].concat(leads.map(l => cols.map(c => esc(l[c])).join(','))).join('\r\n') + '\r\n';
+  const day = new Date().toISOString().slice(0, 10);
+  res.setHeader('Content-Type', 'text/csv; charset=utf-8');
+  res.setHeader('Content-Disposition', `attachment; filename="prestige-leads-${day}.csv"`);
+  res.send(out);
+});
+
+// Admin: advance a lead through the pipeline (new → contacted → booked/won/lost).
+app.post('/api/admin/lead-status', (req, res) => {
+  const { id, status } = req.body || {};
+  if (!id || !LEAD_STATUSES.includes(status)) return res.status(400).json({ ok: false, error: 'id + valid status required' });
+  try {
+    const st = readLeadStatus(); st[id] = status;
+    fs.mkdirSync(path.join(__dirname, 'reports'), { recursive: true });
+    fs.writeFileSync(leadStatusPath(), JSON.stringify(st, null, 2));
+    res.json({ ok: true, id, status });
+  } catch { res.status(500).json({ ok: false, error: 'could not save status' }); }
+});
+
+// ── Self-hosted, privacy-friendly analytics-lite (DTD 2026-07-27) ──────────────────────
+// First-party AGGREGATE counts ONLY — no cookies, no IP, no per-user data — so it needs no
+// consent banner and stores nothing we can't stand behind. Gives the owner the denominator
+// (views) behind their lead counts => a real, honestly-measured conversion rate. NOT GA.
+const ANALYTICS_EVENTS = ['pageview', 'form_start', 'form_submit'];
+const analyticsPath = () => path.join(__dirname, 'reports', 'analytics.json');
+function readAnalytics() { try { return JSON.parse(fs.readFileSync(analyticsPath(), 'utf8')); } catch { return {}; } }
+const dayKeyLA = () => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }); // YYYY-MM-DD, shop tz
+
+// Public beacon. Whitelisted events only; unknown events are silently ignored (204, no error
+// surface). Aggregate increment only — never stores anything about who the visitor is.
+app.post('/api/track', rateLimit(60, 60 * 1000), (req, res) => {
+  const ev = String((req.body || {}).event || '');
+  if (!ANALYTICS_EVENTS.includes(ev)) return res.status(204).end();
+  try {
+    const a = readAnalytics(); const day = dayKeyLA();
+    a[day] = a[day] || {}; a[day][ev] = (a[day][ev] || 0) + 1;
+    const days = Object.keys(a).sort();                       // keep the file bounded (last 120 days)
+    if (days.length > 120) for (const d of days.slice(0, days.length - 120)) delete a[d];
+    fs.mkdirSync(path.join(__dirname, 'reports'), { recursive: true });
+    // Atomic write (temp + rename) so a partial/concurrent write can never leave a corrupt
+    // analytics.json that a reader would choke on.
+    const tmp = analyticsPath() + '.tmp';
+    fs.writeFileSync(tmp, JSON.stringify(a));
+    fs.renameSync(tmp, analyticsPath());
+  } catch { /* analytics must NEVER break a page — swallow */ }
+  res.status(204).end();
+});
+
+// Admin: aggregate analytics (today / last 7 days / all-time + honest conversion rate).
+app.get('/api/admin/analytics', (req, res) => {
+  const a = readAnalytics();
+  const sum = keys => keys.reduce((o, d) => { const b = a[d] || {}; ANALYTICS_EVENTS.forEach(e => o[e] = (o[e] || 0) + (b[e] || 0)); return o; }, {});
+  const allDays = Object.keys(a).sort();
+  const last7 = []; for (let i = 0; i < 7; i++) { const d = new Date(); d.setDate(d.getDate() - i); last7.push(d.toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' })); }
+  const conv = t => t.pageview ? Math.round((t.form_submit || 0) / t.pageview * 1000) / 10 : 0; // submits per 100 views
+  const t7 = sum(last7), tot = sum(allDays);
+  res.json({
+    today: sum([dayKeyLA()]), last7: t7, total: tot,
+    conversion7: conv(t7), conversionTotal: conv(tot),
+    series: allDays.slice(-14).map(d => ({ date: d, ...ANALYTICS_EVENTS.reduce((o, e) => (o[e] = (a[d] || {})[e] || 0, o), {}) }))
+  });
+});
+
+// Public: HONEST "typical wait" estimate derived from the demand model.
+// It is NOT a live queue count — it's a transparent function of best-times.json
+// demand for the current weekday, always labeled as an estimate, so we never
+// display a number we can't verify (the places.json data-honesty rule). dtd:A 2026-07-26.
+app.get('/api/wait', (req, res) => {
+  const days = ((bestTimes() || {}).traffic || {}).by_day || [];
+  // Weekday in the shop's LOCAL tz (LA) — the server clock is UTC, so a Friday-evening
+  // CA visitor must not be shown Saturday's level. (contrarian gate, 2026-07-26)
+  const dayName = new Date().toLocaleDateString('en-US', { timeZone: 'America/Los_Angeles', weekday: 'short' });
+  const today = days.find(d => d.day === dayName);
+  const demand = today ? Math.max(1, Math.min(5, Number(today.demand) || 3)) : 3;
+  // Report the demand LEVEL the data actually measures — a qualitative busy-ness, NOT a
+  // fabricated minute count we can't verify (Steve's data-honesty rule).
+  const LEVELS = { 1: ['Quiet', 'little to no wait'], 2: ['Quiet', 'little to no wait'], 3: ['Moderate', 'a short wait is typical'], 4: ['Busy', 'expect a wait'], 5: ['Very busy', 'expect a longer wait'] };
+  const [level, phrase] = LEVELS[demand];
+  res.json({
+    level, phrase, demand, day: dayName,
+    estimate: true,
+    basis: 'Based on typical demand for this day — not a live queue count'
+  });
+});
+
+// ---- static + clean URLs ---------------------------------------------------
+// Inline SVG favicon (avoids a 404 on the browser's automatic request).
+app.get('/favicon.svg', (req, res) => res.type('image/svg+xml').send(
+  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#0a84ff"/><text x="16" y="22" font-size="18" text-anchor="middle">🚗</text></svg>'));
+app.get('/favicon.ico', (req, res) => res.redirect(302, '/favicon.svg'));
+app.use('/media', express.static(path.join(__dirname, 'media')));
+app.get(/^\/(.+)\.html$/, (req, res) => res.redirect(301, '/' + req.params[0]));
+
+// LocalBusiness (AutoWash) JSON-LD for local SEO — built LIVE from data/places.json so it
+// can never drift from the visible name/address/phone/hours (Google cross-checks NAP).
+// Server-rendered into <head> so crawlers see it without executing JS. dtd:B 2026-07-26.
+// We only emit fields we can verify — NO aggregateRating (unverified) and NO geo (no lat/lng).
+const DOW = { Mon: 'Monday', Tue: 'Tuesday', Wed: 'Wednesday', Thu: 'Thursday', Fri: 'Friday', Sat: 'Saturday', Sun: 'Sunday' };
+const DOW_ORDER = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
+function to24(t) {
+  const m = String(t).match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
+  if (!m) return null;
+  let h = +m[1]; const ap = m[3].toUpperCase();
+  if (ap === 'PM' && h !== 12) h += 12;
+  if (ap === 'AM' && h === 12) h = 0;
+  return String(h).padStart(2, '0') + ':' + m[2];
+}
+function parseHoursLine(line) {
+  const times = String(line).match(/\d{1,2}:\d{2}\s*[AP]M/gi);
+  if (!times || times.length < 2) return null;
+  const opens = to24(times[0]), closes = to24(times[1]);
+  const dayPart = String(line).split(/\s+\d/)[0].trim();
+  const dr = dayPart.split(/[–—-]/).map(s => s.trim());
+  let days = [];
+  if (dr.length === 2) { const a = DOW_ORDER.indexOf(dr[0]), b = DOW_ORDER.indexOf(dr[1]); if (a >= 0 && b >= 0) days = DOW_ORDER.slice(a, b + 1); }
+  else if (DOW[dr[0]]) days = [dr[0]];
+  if (!days.length || !opens || !closes) return null;
+  return { '@type': 'OpeningHoursSpecification', dayOfWeek: days.map(d => DOW[d]), opens, closes };
+}
+// Canonical serving origin. prestige.agentabrams.com is the PERMANENT public home for this
+// build (Steve 2026-07-27: the prestigehandcarwash.com apex does not exist — agentabrams only).
+// All self-referential SEO (canonical, og:url, sitemap, article URLs, entity url) points here.
+const SITE_ORIGIN = 'https://prestige.agentabrams.com';
+function siteOrigin() { return (SITE_ORIGIN || '').replace(/\/$/, ''); }
+function buildJsonLd() {
+  const p = places();
+  if (!p || !p.name) return null;
+  const ld = { '@context': 'https://schema.org', '@type': 'AutoWash', name: p.name };
+  if (p.legal_name && p.legal_name !== p.name) ld.alternateName = p.legal_name;
+  if (siteOrigin()) ld.url = siteOrigin();
+  if (p.phone) ld.telephone = p.phone;
+  if (p.address) {
+    const parts = p.address.split(',').map(s => s.trim());
+    const addr = { '@type': 'PostalAddress', addressCountry: 'US' };
+    if (parts[0]) addr.streetAddress = parts[0];
+    if (parts[1]) addr.addressLocality = parts[1];
+    if (parts[2]) { const sz = parts[2].match(/([A-Z]{2})\s*(\d{5})/); if (sz) { addr.addressRegion = sz[1]; addr.postalCode = sz[2]; } }
+    ld.address = addr;
+  }
+  const hrs = (p.hours || []).map(parseHoursLine).filter(Boolean);
+  if (hrs.length) ld.openingHoursSpecification = hrs;
+  const ig = p.instagram ? (/^https?:\/\//.test(p.instagram) ? p.instagram : `https://instagram.com/${String(p.instagram).replace(/^@/, '')}`) : null;
+  const same = [ig, p.yelp_url].filter(Boolean);
+  if (same.length) ld.sameAs = same;
+  // Service catalog with REAL prices (only where a numeric price exists — quote-only
+  // services list the Service without a fabricated price). Strengthens rich results.
+  const svc = (services() || []).filter(s => s && s.name);
+  if (svc.length) {
+    ld.hasOfferCatalog = {
+      '@type': 'OfferCatalog', name: 'Car Wash & Detailing Services',
+      itemListElement: svc.map(s => {
+        const offer = { '@type': 'Offer', itemOffered: { '@type': 'Service', name: s.name } };
+        if (s.blurb) offer.itemOffered.description = s.blurb;
+        if (s.price != null && !isNaN(Number(s.price))) { offer.price = Number(s.price); offer.priceCurrency = 'USD'; }
+        return offer;
+      })
+    };
+  }
+  return ld;
+}
+// Booking-objection FAQ — the SINGLE SOURCE for both the on-page accordion (/api/faqs)
+// and the FAQPage JSON-LD, so visible text and structured data can never drift. Every
+// answer is a verifiable operational commitment the business already makes on-site
+// (data-honesty rule: no invented durations, prices, or ratings). Framed as the exact
+// objections that stall a booking — folds the /dtd "D" dissent (friction reduction) into A.
+function faqs() {
+  return [
+    { q: 'Do you actually hand wash, or is it an automatic tunnel?',
+      a: 'Every car is hand washed using the two-bucket method with a foam cannon — no automatic tunnel and no brushes, so no swirl marks. It is safe for ceramic-coated, matte, and PPF-wrapped finishes.' },
+    { q: 'Is the pricing really flat, or will I get upsold?',
+      a: 'The price on the sign is the price you pay. There is no commission-driven wax-and-polish pressure — pick a service and that is the cost. The full price list is on the Services page.' },
+    { q: 'What happens if it rains right after my wash?',
+      a: 'If it rains within 48 hours of your wash, your next basic wash is on us — just come back.' },
+    { q: 'Do you clean the back seats and door jambs?',
+      a: 'Yes. Every seat is touched and door jambs are included on every wash, always. If we miss a spot, we re-do it free.' },
+    { q: 'I am sensitive to fragrances — is there an option?',
+      a: 'Yes, a fragrance-free option is available on request. Just note it when you book.' },
+    { q: 'Do you offer full detailing and ceramic coating?',
+      a: 'Yes — alongside the hand wash we offer full-service detailing and 9H ceramic coating. See the Services page for current options and flat pricing.' },
+    { q: 'Which neighborhoods do you serve?',
+      a: 'We serve the San Fernando Valley — including Sherman Oaks, Encino, Van Nuys, Studio City, Northridge, Reseda, Tarzana, Woodland Hills, and North Hollywood.' },
+    { q: 'How do I book, and how soon will I hear back?',
+      a: 'Send a request from the Book / Contact page with your vehicle and preferred time. We follow up to confirm — usually within a few hours during business hours. No pressure, no upsell.' }
+  ];
+}
+// Persistent mobile action bar — injected into customer pages so the primary CTA (Book)
+// and a click-to-call are always one thumb-tap away on phones (where most Valley car-wash
+// traffic is). CSS-only visibility (<=640px), no JS. The tel: number comes from the REAL
+// places().phone (single source, no fabrication); the Call button is omitted if no phone.
+function mobileBar(pagePath) {
+  const p = places() || {};
+  let d = String(p.phone || '').replace(/\D/g, '');
+  if (d.length === 11 && d[0] === '1') d = d.slice(1);
+  const tel = d.length === 10 ? '+1' + d : (d ? '+' + d : '');
+  const call = tel ? `<a class="mbar-btn call" href="tel:${tel}" aria-label="Call ${(p.name || 'Prestige Car Wash').replace(/"/g, '')}">📞 Call</a>` : '';
+  // On the contact page the form IS the booking action, so scroll to it (#f) rather than
+  // self-linking to the top of the page and losing any half-filled form state.
+  const bookHref = pagePath === '/contact' ? '#f' : '/contact';
+  return `<style>
+.mbar{display:none}
+@media(max-width:640px){
+  body{padding-bottom:74px}
+  .mbar{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:80;gap:10px;padding:10px 12px calc(10px + env(safe-area-inset-bottom,0px));background:rgba(11,15,22,.93);backdrop-filter:blur(10px);border-top:1px solid var(--line)}
+  .mbar-btn{flex:1;text-align:center;padding:13px 10px;border-radius:12px;font-weight:800;font-size:15px;text-decoration:none;display:flex;align-items:center;justify-content:center;gap:6px}
+  .mbar .call{background:transparent;color:var(--ink);border:1px solid var(--line)}
+  .mbar .book{background:linear-gradient(135deg,var(--brand),var(--brand2));color:#001018}
+}</style>
+<div class="mbar" aria-label="Quick actions">${call}<a class="mbar-btn book" href="${bookHref}">Book a Wash →</a></div>`;
+}
+// Honest "find & review us" trust block — links to the REAL, verified off-site profiles
+// from places() (Google Maps, Yelp, Instagram). Deliberately shows NO star number: ratings
+// drift and would go stale on-page, so we route to the authoritative live profile instead
+// (data-honesty rule — never display a number we can't keep verifiably current). Only a
+// profile with a real non-empty value is rendered (no dead links). Server-rendered so the
+// off-site authority links ship in the crawler HTML.
+function trustBlockHtml() {
+  const p = places() || {};
+  const esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  const btn = (href, label, cls) => href
+    ? `<a class="${cls}" href="${esc(href)}" target="_blank" rel="noopener" style="text-decoration:none">${esc(label)}</a>` : '';
+  const igHandle = p.instagram ? '@' + String(p.instagram).replace(/^https?:\/\/(www\.)?instagram\.com\//i, '').replace(/\/+$/, '').replace(/^@/, '') : '';
+  const igUrl = p.instagram ? (/^https?:\/\//.test(p.instagram) ? p.instagram : 'https://instagram.com/' + String(p.instagram).replace(/^@/, '')) : '';
+  const links = [
+    btn(p.maps_query, '📍 Find us on Google Maps', 'btn'),
+    btn(p.yelp_url, '⭐ Read reviews on Yelp', 'btn ghost'),
+    btn(igUrl, '📸 ' + igHandle, 'btn ghost')
+  ].filter(Boolean);
+  if (!links.length) return '';
+  // Review-SOLICITATION CTA routes to Google's write-review dialog — and ONLY when a real
+  // Google place_id is present (currently empty). We deliberately do NOT solicit on Yelp
+  // (Yelp's "Don't Ask for Reviews" policy filters solicited reviews into "not recommended",
+  // burying exactly what we'd be trying to grow), and we never point "leave a review" at a
+  // bare search URL or a guessed id. So the CTA stays gated on a verified place_id and simply
+  // appears once it's set — the "Read reviews on Yelp" link above stays (finding ≠ soliciting).
+  const pid = /^[A-Za-z0-9_-]{20,}$/.test(String(p.place_id || '')) ? p.place_id : '';
+  const reviewHref = pid ? `https://search.google.com/local/writereview?placeid=${encodeURIComponent(pid)}` : '';
+  const cta = reviewHref ? `<div style="margin-top:16px">${btn(reviewHref, 'Leave us a review on Google →', 'btn')}</div>` : '';
+  return `<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center">${links.join('')}</div>${cta}`;
+}
+function buildFaqLd() {
+  const list = faqs();
+  if (!list.length) return null;
+  return {
+    '@context': 'https://schema.org', '@type': 'FAQPage',
+    mainEntity: list.map(f => ({
+      '@type': 'Question', name: f.q,
+      acceptedAnswer: { '@type': 'Answer', text: f.a }
+    }))
+  };
+}
+// Server-rendered accordion HTML from the SAME faqs() source. Injected into the #faqList
+// placeholder (see pageHtml) so the visible FAQ ships in the crawler-received HTML and
+// MATCHES the FAQPage JSON-LD — Google requires structured data to reflect visible content
+// (client-only rendering risks a manual action). Native <details> means zero JS needed.
+function faqHtml() {
+  const esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+  return faqs().map((f, i) => `
+    <details class="faq"${i === 0 ? ' open' : ''} style="border:1px solid var(--line);border-radius:12px;margin-bottom:10px;background:var(--panel)">
+      <summary style="cursor:pointer;padding:15px 18px;font-weight:700;font-size:16px;display:flex;justify-content:space-between;gap:12px;align-items:center">
+        <span>${esc(f.q)}</span><span class="faq-mk" aria-hidden="true" style="color:var(--mut);font-weight:800;font-size:20px">+</span>
+      </summary>
+      <div style="padding:0 18px 16px;color:var(--mut);font-size:15px;line-height:1.55">${esc(f.a)}</div>
+    </details>`).join('');
+}
+// ── Evergreen car-care guides (DTD iteration 5) ────────────────────────────────────────
+// Genuinely useful, HONEST top-of-funnel content: established detailing best-practice
+// guidance phrased as advice (no fabricated statistics, no invented studies). Rendered
+// server-side so it's fully crawlable, with BlogPosting + BreadcrumbList JSON-LD and
+// internal links into services/booking. GUIDE_DATE is the real publish date.
+const GUIDE_DATE = '2026-07-26';
+function guides() {
+  return [
+    {
+      slug: 'how-often-to-wash-a-ceramic-coated-car',
+      title: 'How Often Should You Wash a Ceramic-Coated Car?',
+      description: 'A ceramic coating changes the maintenance math. Here is a sensible wash rhythm that protects the coating without over-washing.',
+      dek: 'A coating is slick and sacrificial — but it still needs the right care to keep beading and looking its best.',
+      sections: [
+        { h: 'The short answer', p: 'For a daily-driven coated car, every two weeks is a sensible default. Stretch it if the car lives in a garage; tighten it after rain, road grime, or bird droppings — droppings are acidic and can etch even a coated surface if left to bake in the sun.' },
+        { h: 'Why gentle washing matters more, not less', p: 'A coating makes dirt easier to remove, but automatic tunnel brushes still drag grit across the surface and dull the gloss over time. A gentle two-bucket hand wash with a pH-neutral soap preserves the coating’s hydrophobic behavior far longer than a tunnel ever will.' },
+        { h: 'What to avoid', p: 'Skip strong degreasers and high-alkaline wheel chemicals on coated paint — they strip the coating’s top layer. And never let water spot-dry in direct sun; the dissolved minerals bond to the coating and are a pain to remove.' },
+        { h: 'When to top it up', p: 'If water stops beading and starts sheeting flat, the coating is asking for help. A spray-on SiO2 booster applied after a wash restores hydrophobicity between professional maintenance visits.' }
+      ]
+    },
+    {
+      slug: 'hand-wash-vs-automatic-tunnel',
+      title: 'Hand Wash vs. Automatic Tunnel: What Is Actually Safe for Your Paint',
+      description: 'Automatic tunnels are fast and cheap — but here is what they do to your clear coat, and why hand washing is gentler.',
+      dek: 'The difference comes down to one thing: grit, and whether it gets dragged across your paint.',
+      sections: [
+        { h: 'Where tunnel washes go wrong', p: 'Tunnels recirculate water and run stiff brushes or cloth strips that have already touched every car before yours. The grit they carry is exactly what puts fine swirl marks and hairline scratches into a clear coat.' },
+        { h: 'Why two-bucket hand washing is gentler', p: 'Two buckets — one of clean soapy water, one to rinse the mitt — let grit drop out of circulation instead of going back onto the paint. A foam cannon lifts and floats dirt off the surface before the mitt ever touches it.' },
+        { h: 'The finishes that cannot take a tunnel', p: 'Ceramic coatings, matte and satin paint, and paint protection film (PPF) can all be dulled, hazed, or lifted at the edges by automated equipment. For any of these, hand washing is the safe default.' },
+        { h: 'The honest trade-off', p: 'Tunnels win on speed and price per wash. If your car is a daily beater, that may be all you need. If you care about the finish — or you have invested in a coating, a wrap, or a fresh repaint — hand washing pays for itself in preserved gloss and resale value.' }
+      ]
+    },
+    {
+      slug: 'protecting-matte-and-ppf-finishes',
+      title: 'Protecting Matte and PPF Finishes: A Care Guide',
+      description: 'Matte paint and paint protection film need different care than glossy clear coat. Here is how to keep them looking right.',
+      dek: 'The wrong product on a matte finish is a mistake you cannot polish out — so it pays to know the rules.',
+      sections: [
+        { h: 'Matte is a different animal', p: 'Matte and satin clear coats get their look from a micro-textured surface that scatters light. Anything that fills or polishes that texture — wax, sealant, polish, or a buffer — creates permanent shiny spots that can only be corrected with a refinish.' },
+        { h: 'How to wash matte safely', p: 'Hand wash only, with a matte-specific (wax-free) pH-neutral shampoo, a soft mitt, and straight-line motions rather than circles. Dry with a clean microfiber or a filtered-air blower instead of dragging a towel across the texture.' },
+        { h: 'Caring for PPF', p: 'Paint protection film is tough, but its edges can trap dirt and lift if scrubbed hard. Keep the edges clean, avoid blasting high-pressure water straight at the seams, and skip cutting compounds. Many modern films are self-healing — light swirls vanish in the sun or after a warm rinse.' },
+        { h: 'What we do', p: 'Our hand wash is matte- and PPF-safe by default: no automatic brushes, never any wax on matte, and a fragrance-free option on request. Tell us what your car is wearing and we will treat it accordingly.' }
+      ]
+    }
+  ];
+}
+function _e(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
+function guideLayout(title, description, bodyHtml) {
+  return `<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>${_e(title)}</title>
+<meta name="description" content="${_e(description)}">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/assets/pcw.css">
+</head><body>
+<nav class="nav"><div class="wrap row">
+  <div class="brand"><span class="mk">🚗</span><div>Prestige<br><small>Car Wash · SFV</small></div></div>
+  <button class="hamb" aria-label="Menu" aria-expanded="false" onclick="var o=document.getElementById('lnk').classList.toggle('open');this.setAttribute('aria-expanded',o)">☰</button>
+  <div class="links" id="lnk"><a href="/">Home</a><a href="/services">Services</a><a href="/guides" class="on">Guides</a><a class="btn" href="/contact">Book a Wash</a></div>
+</div></nav>
+${bodyHtml}
+<footer class="foot"><div class="wrap">© 2026 Prestige Car Wash · San Fernando Valley, CA · <a href="/services">Services</a> · <a href="/guides">Guides</a> · <a href="/contact">Book</a></div></footer>
+</body></html>`;
+}
+function guideCtaHtml() {
+  return `<div class="card" style="max-width:760px;margin:26px auto 0"><div class="body" style="text-align:center">
+    <div class="ttl" style="font-size:20px">Want it done right, by hand?</div>
+    <div class="blurb">We hand wash every car — coating-, matte-, and PPF-safe — with no upsell pressure.</div>
+    <div style="margin-top:12px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap">
+      <a class="btn" href="/contact">Book a Wash</a><a class="btn ghost" href="/services">See Services &amp; Pricing</a>
+    </div>
+  </div></div>`;
+}
+function guideArticleHtml(g) {
+  const secs = g.sections.map(s => `<h2>${_e(s.h)}</h2>\n<p>${_e(s.p)}</p>`).join('\n');
+  return `<article class="section"><div class="wrap" style="max-width:760px">
+    <div style="margin-bottom:10px"><a href="/guides" style="color:var(--mut);font-size:13px">← Car-care guides</a></div>
+    <span class="pill">Car-care guide</span>
+    <h1>${_e(g.title)}</h1>
+    <p class="lead">${_e(g.dek)}</p>
+    ${secs}
+    ${guideCtaHtml()}
+  </div></article>`;
+}
+function guideIndexHtml() {
+  const cards = guides().map(g => `<a class="card" href="/guides/${_e(g.slug)}" style="text-decoration:none;color:inherit"><div class="body">
+      <div class="cat">Car-care guide</div>
+      <div class="ttl">${_e(g.title)}</div>
+      <div class="blurb">${_e(g.description)}</div>
+      <div style="margin-top:8px;color:var(--brand);font-weight:700;font-size:14px">Read →</div>
+    </div></a>`).join('');
+  return `<section class="section"><div class="wrap">
+    <span class="pill">Guides</span>
+    <h1>Car-care guides</h1>
+    <p class="sub">Straight, no-nonsense advice on keeping your car’s finish looking its best — from the crew that hand washes them.</p>
+    <div class="grid" style="--cols:3">${cards}</div>
+  </div></section>`;
+}
+function buildArticleLd(g, canonicalUrl) {
+  const name = (places() || {}).name || 'Prestige Car Wash';
+  const ld = {
+    '@context': 'https://schema.org', '@type': 'BlogPosting',
+    headline: g.title, description: g.description,
+    datePublished: GUIDE_DATE, dateModified: GUIDE_DATE,
+    author: { '@type': 'Organization', name }, publisher: { '@type': 'Organization', name },
+    articleSection: 'Car care'
+  };
+  if (canonicalUrl) ld.mainEntityOfPage = canonicalUrl;
+  return ld;
+}
+function buildBreadcrumbLd(items) {
+  return {
+    '@context': 'https://schema.org', '@type': 'BreadcrumbList',
+    itemListElement: items.map((it, i) => ({ '@type': 'ListItem', position: i + 1, name: it.name, item: it.url }))
+  };
+}
+// Open Graph / Twitter tags for the homepage — reuses the page's own <title> and meta
+// description so social previews stay in sync with on-page SEO. og:image is a real asset.
+function buildSocialTags(html, pagePath) {
+  // Bail if the HTML already carries og: tags OR a canonical (e.g. added directly to the
+  // file) so we never inject a duplicate that makes Facebook/Google flag the markup.
+  if (/property=["']og:title["']/i.test(html) || /<link\b[^>]*\brel=["']canonical["']/i.test(html)) return '';
+  const p = places() || {};
+  const base = siteOrigin();
+  const url = base ? base + (pagePath || '') : '';   // page-specific canonical + og:url
+  const title = (html.match(/<title>([^<]*)<\/title>/i) || [])[1] || p.name || 'Prestige Car Wash';
+  // Attribute-order-independent description extraction, with a real fallback (never a
+  // blank social card). Handles both name-first and content-first, single or double quotes.
+  const descMatch = html.match(/<meta\b[^>]*\bname=["']description["'][^>]*\bcontent=["']([^"']*)["']/i)
+                 || html.match(/<meta\b[^>]*\bcontent=["']([^"']*)["'][^>]*\bname=["']description["']/i);
+  const desc = (descMatch || [])[1] || `${p.name || 'Prestige Car Wash'} — no-pressure flat pricing, two-bucket hand wash, detailing & ceramic coating in the San Fernando Valley.`;
+  const img = base ? base + '/media/svc-full-service.png' : '';
+  const esc = s => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
+  // Individual guide articles are og:type "article"; everything else is a "website".
+  const ogType = /^\/guides\/.+/.test(pagePath || '') ? 'article' : 'website';
+  const tags = [
+    ['og:type', ogType], ['og:site_name', p.name || 'Prestige Car Wash'],
+    ['og:title', title], ['og:description', desc], url && ['og:url', url], img && ['og:image', img],
+    ['twitter:card', img ? 'summary_large_image' : 'summary'],
+    ['twitter:title', title], ['twitter:description', desc], img && ['twitter:image', img]
+  ].filter(Boolean);
+  const metas = tags.map(([k, v]) => k.startsWith('twitter')
+    ? `<meta name="${k}" content="${esc(v)}">`
+    : `<meta property="${k}" content="${esc(v)}">`).join('\n');
+  return (url ? `<link rel="canonical" href="${esc(url)}">\n` : '') + metas;
+}
+// Built fresh per request (sync file read + one string replace = microseconds) so the
+// injected JSON-LD/OG NEVER drift from live places.json — a stale NAP is the exact thing
+// Google penalizes, which would defeat the point of injecting it. (contrarian gate, 2026-07-26)
+// GA4 gtag snippet — injected on the customer pages ONLY when GA_MEASUREMENT_ID is set
+// (a valid G-XXXX id). No-op until then, so this is safe to ship before the id exists.
+// Admin (internal) is intentionally NOT tracked as customer traffic.
+function gaSnippet() {
+  const id = process.env.GA_MEASUREMENT_ID;
+  if (!id || !/^G-[A-Z0-9]{6,}$/.test(id)) return '';
+  return `\n<script async src="https://www.googletagmanager.com/gtag/js?id=${id}"></script>` +
+         `\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${id}');</script>`;
+}
+// Shared page decorator: takes a full HTML string and injects the FAQ/trust placeholders,
+// the mobile bar, OG/canonical tags, JSON-LD, and the GA snippet. Used by BOTH file-backed
+// pages (pageHtml) and server-generated pages (the /guides articles), so head/injection logic
+// lives in exactly one place.
+function decorate(html, pagePath, ld) {
+  // Fill the FAQ accordion server-side so the visible content is in the crawler-received
+  // HTML and matches the FAQPage JSON-LD. Only touches a page that carries the placeholder.
+  if (html.includes('id="faqList"')) {
+    html = html.replace(/(<div id="faqList"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + faqHtml() + close);
+  }
+  // Fill the honest find-&-review trust block server-side (real off-site profile links).
+  // If there are no real profiles to show, strip the WHOLE section so a heading never floats
+  // over an empty body (graceful degradation, per contrarian).
+  if (html.includes('id="findReview"')) {
+    const tb = trustBlockHtml();
+    if (tb) html = html.replace(/(<div id="findReview"[^>]*>)\s*(<\/div>)/, (m, open, close) => open + tb + close);
+    else html = html.replace(/<section class="section" id="findreview">[\s\S]*?<\/section>/, '');
+  }
+  // Persistent mobile Book/Call bar on every customer page (guard against double-inject).
+  if (!/class="mbar"/.test(html)) html = html.replace('</body>', mobileBar(pagePath) + '\n</body>');
+  // Admin ↔ live-view toggle: on customer pages show a "🔧 Admin" pill ONLY if this browser
+  // carries the pcw.admin flag (set when the admin visits /admin). Invisible to real customers;
+  // gives the logged-in admin a one-tap way back to the admin from any live page.
+  html = html.replace('</body>', `<script>(function(){try{if(localStorage.getItem('pcw.admin')!=='1')return;var a=document.createElement('a');a.href='/admin';a.textContent='🔧 Admin view';a.title='Switch to admin view';a.setAttribute('aria-label','Switch to admin view');a.style.cssText='position:fixed;left:12px;bottom:12px;z-index:95;background:#12100e;color:#c6a765;font:700 11px/1 system-ui,-apple-system,sans-serif;letter-spacing:.1em;text-transform:uppercase;padding:9px 13px;border-radius:6px;text-decoration:none;box-shadow:0 6px 18px rgba(0,0,0,.45);border:1px solid rgba(198,167,101,.45)';document.body.appendChild(a);}catch(e){}})();</script>\n</body>`);
+  let inject = buildSocialTags(html, pagePath);
+  // Perf: preconnect to the font-file origin (pages already preconnect googleapis; the actual
+  // WOFF2 files come from gstatic, so this shaves a round-trip on first paint). Dedup-guarded.
+  if (!/fonts\.gstatic\.com/.test(html)) inject += '\n<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
+  // ld may be a single JSON-LD object OR an array of them (e.g. AutoWash + FAQPage on the
+  // homepage). Emit one <script> per object; escape "<" so the JSON can't break out of the tag.
+  for (const obj of [].concat(ld || []).filter(Boolean)) {
+    inject += `\n<script type="application/ld+json">${JSON.stringify(obj).replace(/</g, '\\u003c')}</script>`;
+  }
+  inject += gaSnippet();
+  // First-party pageview beacon (analytics-lite) — sendBeacon so it never blocks paint; the
+  // /api/track endpoint only increments an aggregate counter (no cookie/IP/PII). Customer
+  // pages only (admin isn't served through decorate, so the owner's own visits aren't counted).
+  inject += `\n<script>(function(){try{var b=JSON.stringify({event:'pageview'});if(navigator.sendBeacon){navigator.sendBeacon('/api/track',new Blob([b],{type:'application/json'}))}else{fetch('/api/track',{method:'POST',headers:{'Content-Type':'application/json'},body:b,keepalive:true})}}catch(e){}})();</script>`;
+  return html.replace('</head>', inject + '\n</head>');
+}
+function pageHtml(file, pagePath, ld) {
+  return decorate(fs.readFileSync(path.join(__dirname, 'public', file), 'utf8'), pagePath, ld);
+}
+const sendPage = (res, file, pagePath, ld) => {
+  // If tag INJECTION throws, still serve the raw page (graceful degradation). If the file
+  // itself is unreadable, sendFile's callback guarantees a clean 500 — never a hung response.
+  try { res.type('html').send(pageHtml(file, pagePath, ld)); }
+  catch { res.sendFile(path.join(__dirname, 'public', file), err => { if (err && !res.headersSent) res.status(500).end(); }); }
+};
+// FAQ list for the on-page accordion — same source the homepage FAQPage schema is built from.
+app.get('/api/faqs', (req, res) => res.json(faqs()));
+// Homepage carries BOTH the AutoWash business schema and the FAQPage schema (the FAQ is
+// visibly rendered on the page below, which is what makes the FAQPage markup legitimate).
+app.get('/', (req, res) => sendPage(res, 'index.html', '', [buildJsonLd(), buildFaqLd()]));
+app.get('/services', (req, res) => sendPage(res, 'services.html', '/services', null));
+app.get('/contact', (req, res) => sendPage(res, 'contact.html', '/contact', null));
+// Car-care guides — server-generated (shares the decorate() pipeline: OG/canonical/GA/mobile bar).
+app.get('/guides', (req, res) => {
+  try {
+    const html = guideLayout('Car-Care Guides — Prestige Car Wash',
+      'Honest, practical car-care guides from a San Fernando Valley hand-wash & detail shop: ceramic-coating care, hand wash vs. tunnel, and matte & PPF protection.',
+      guideIndexHtml());
+    res.type('html').send(decorate(html, '/guides', null));
+  } catch { res.status(500).end(); }
+});
+app.get('/guides/:slug', (req, res) => {
+  const g = guides().find(x => x.slug === req.params.slug);
+  const base = siteOrigin();
+  if (!g) {
+    const nf = guideLayout('Guide Not Found — Prestige Car Wash', 'That guide could not be found.',
+      `<section class="section"><div class="wrap"><h1>Guide not found</h1><p class="sub">That guide may have moved. <a href="/guides">Browse all car-care guides →</a></p></div></section>`);
+    return res.status(404).type('html').send(decorate(nf, '/guides', null));
+  }
+  const canonical = base ? base + '/guides/' + g.slug : '';
+  const ld = [
+    buildArticleLd(g, canonical),
+    buildBreadcrumbLd([
+      { name: 'Home', url: base || '/' },
+      { name: 'Guides', url: (base || '') + '/guides' },
+      { name: g.title, url: canonical || ('/guides/' + g.slug) }
+    ])
+  ];
+  try {
+    res.type('html').send(decorate(guideLayout(g.title + ' — Prestige Car Wash', g.description, guideArticleHtml(g)), '/guides/' + g.slug, ld));
+  } catch { res.status(500).end(); }
+});
+
+// XML sitemap generated from the public URL — kept in one place, no static file to drift.
+app.get('/sitemap.xml', (req, res) => {
+  const base = siteOrigin();
+  if (!base) return res.status(404).end();
+  const urls = ['/', '/services', '/contact', '/guides', ...guides().map(g => '/guides/' + g.slug)];
+  const xml = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
+    urls.map(u => `  <url><loc>${base}${u}</loc></url>`).join('\n') + '\n</urlset>';
+  res.type('application/xml').send(xml);
+});
+
+app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
+
+app.listen(PORT, () => {
+  console.log(`[prestige-car-wash] http://localhost:${PORT}  (admin: /admin)`);
+  if (process.env.NODE_ENV === 'production' && !process.env.ADMIN_PASS) {
+    console.warn('⚠ SECURITY: running in production with the DEFAULT admin password. Set ADMIN_PASS in .env before exposing this host.');
+  }
+});
diff --git a/verification/yoloforever-yf2139.YF57g8/supplied-queue.json b/verification/yoloforever-yf2139.YF57g8/supplied-queue.json
new file mode 100644
index 00000000..a42a3036
--- /dev/null
+++ b/verification/yoloforever-yf2139.YF57g8/supplied-queue.json
@@ -0,0 +1,530 @@
+{
+  "schema_version": 1,
+  "captured_at": "2026-09-16T21:39:14.094Z",
+  "source": "http://127.0.0.1:9794/api/tickets",
+  "order": "board rank ascending; numeric ticket id breaks ties",
+  "open_count": 15,
+  "task_count": 15,
+  "queue": [
+    {
+      "position": 1,
+      "id": "TK-11859-cost-backlog-join-repair-class-18-vendor",
+      "rank": 9,
+      "title": "Cost backlog / JOIN-REPAIR class: 18 vendors, 6,243 ACTIVE products hold a cost-ish column that joins to NOTHING live (0 reachable)",
+      "project": "dw",
+      "assignee": "claude-run-11427",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:23:12.537Z",
+      "blocker": null
+    },
+    {
+      "position": 2,
+      "id": "TK-11850-prestige-car-wash-places-photo-route-has",
+      "rank": 12,
+      "title": "prestige-car-wash: Places photo route has no key since TK-11615 rotation \u2014 mint restricted key or delete the dead route",
+      "project": "prestige-car-wash",
+      "assignee": "claude-run-11615",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:12:20.567Z",
+      "blocker": null
+    },
+    {
+      "position": 3,
+      "id": "TK-11784-yoloforever-autonomous-loop-dtd-cody-gat",
+      "rank": 16,
+      "title": "yoloforever autonomous loop \u2014 DTD+Cody gated, reversible-only, draft gated",
+      "project": "ops",
+      "assignee": "yoloforever-loop",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T19:57:49.691Z",
+      "blocker": null
+    },
+    {
+      "position": 4,
+      "id": "TK-11852-model-arena-gemma3-12b-missing-from-mac2",
+      "rank": 24,
+      "title": "model-arena: gemma3:12b missing from Mac2 ollama \u2014 daily battles running short-handed",
+      "project": "model-arena",
+      "assignee": "claude-run-11723",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:15:00.811Z",
+      "blocker": null
+    },
+    {
+      "position": 5,
+      "id": "TK-11861-phillipe-romano-cost-model-13-066-active",
+      "rank": 25,
+      "title": "Phillipe Romano cost model: 13,066 ACTIVE products (23% of the whole cost-unknown population) under one house umbrella spanning 232 SKU prefixes \u2014 needs per-sub-brand cost sourcing, not a SQL join",
+      "project": "dw",
+      "assignee": "claude-run-11427",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:23:46.505Z",
+      "blocker": null
+    },
+    {
+      "position": 6,
+      "id": "TK-11860-cost-backlog-verify-the-column-class-26",
+      "rank": 26,
+      "title": "Cost backlog / VERIFY-THE-COLUMN class: 26 vendors, 12,852 ACTIVE (10,704 already joinable) need external-plane proof their cost column is cost, not retail",
+      "project": "dw",
+      "assignee": "claude-run-11427",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:23:13.882Z",
+      "blocker": null
+    },
+    {
+      "position": 7,
+      "id": "TK-11851-env-leak-recurrence-18-fanned-out-claude",
+      "rank": 51,
+      "title": "ENV-LEAK RECURRENCE: 18 fanned-out claude windows share dead parent's CLAUDE_CODE_SESSION_ID (197653ee.../PID 42588), explaining the masterdot 'none' bucket",
+      "project": "infra",
+      "assignee": "masterdot",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:24:05.131Z",
+      "blocker": null
+    },
+    {
+      "position": 8,
+      "id": "TK-11848-yoloforever-watchtower-watch-sh-pm2-god",
+      "rank": 52,
+      "title": "yoloforever-watchtower watch.sh: pm2 God count greps ps argv text (PM2.*God) \u2192 false pm2-god:2 from peer tk-log text; anchor to '^PM2 v[0-9.]+: God Daemon' on ps -axo command=",
+      "project": "yoloforever",
+      "assignee": "yoloforever-watchtower",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:11:55.101Z",
+      "blocker": null
+    },
+    {
+      "position": 9,
+      "id": "TK-11858-cost-authority-conflict-1-621-active-pro",
+      "rank": 54,
+      "title": "Cost authority conflict: ~1,621 ACTIVE products store OUR RETAIL as cost_price (stamped, so counted COVERED); 2,408 map-vs-stamp disagreements >10% with no precedence rule",
+      "project": "dw",
+      "assignee": "claude-run-11427",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:20:54.895Z",
+      "blocker": null
+    },
+    {
+      "position": 10,
+      "id": "TK-11853-gcp-replacement-places-key-ffis-has-no-a",
+      "rank": 55,
+      "title": "gcp: replacement Places key fFIs has NO application restriction + no measured billing cap \u2014 same exposure class the TK-11615 leak had",
+      "project": "govarbitrage",
+      "assignee": "claude-run-11615",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:17:22.961Z",
+      "blocker": null
+    },
+    {
+      "position": 11,
+      "id": "TK-11847-gmc-alcohol-legal-restrictions-false-pos",
+      "rank": 56,
+      "title": "GMC alcohol/legal_restrictions false positives: 29 offers disapproved because 'Champagne' is a COLORWAY name \u2014 same classifier bug as TK-11449's Hemp/tobacco, appeal-clearable",
+      "project": "dw-commerce",
+      "assignee": "claude-run-11449",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:11:45.082Z",
+      "blocker": null
+    },
+    {
+      "position": 12,
+      "id": "TK-11838-dot-screen-router-durable-anti-thrash-da",
+      "rank": 76,
+      "title": "dot-screen-router durable anti-thrash: data-drive layout.json + ENFORCED flock + git-track + fan-out discipline (DTD verdict C)",
+      "project": "infra",
+      "assignee": "night-TK-11831",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T19:39:15.334Z",
+      "blocker": null
+    },
+    {
+      "position": 13,
+      "id": "TK-11857-tk-11357-residuals-shared-price-integrit",
+      "rank": 79,
+      "title": "TK-11357 residuals: shared price-integrity gate fail-opens + 2 guards uncommitted in the nested scripts repo",
+      "project": "designerwallcoverings",
+      "assignee": "claude-run-11357",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:18:52.278Z",
+      "blocker": null
+    },
+    {
+      "position": 14,
+      "id": "TK-11856-57-of-71-counter-based-launchd-jobs-shar",
+      "rank": 107,
+      "title": "57 of 71 counter-based launchd jobs share the TK-11298 0-byte-stdout shape (liveness on an untrustworthy counter)",
+      "project": "cron-fire-canary",
+      "assignee": "claude-run-11298",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:18:24.640Z",
+      "blocker": null
+    },
+    {
+      "position": 15,
+      "id": "TK-11846-gmc-titlefix-mc-read-v1-js-getproduct-40",
+      "rank": 108,
+      "title": "gmc-titlefix _mc-read-v1.js getProduct() 400s on legacy bare-variant-id offers \u2014 a NOT_MEASURED that reads like 'no data'",
+      "project": "gmc-titlefix",
+      "assignee": "claude-run-11307",
+      "status": "open",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:10:43.250Z",
+      "blocker": null
+    }
+  ],
+  "open_non_tasks": [],
+  "in_flight": [
+    {
+      "id": "TK-11",
+      "rank": 35,
+      "title": "astek pw rotation + sales-to-Slack scope",
+      "project": "dw-infra",
+      "assignee": "claude-run-11",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:46:42.253Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-12",
+      "rank": 80,
+      "title": "nineoh Unofficial 90210 \u2192 App Store/TestFlight",
+      "project": "nineoh-guide",
+      "assignee": "claude-run-12",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:46:44.237Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-16",
+      "rank": 5,
+      "title": "Add more free west-coast property feeds (LA County + others) to deed/assessment layer",
+      "project": "usrealestate",
+      "assignee": "claude-run-16",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:46:48.444Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-00034-fentucci-tokiwa-504-go-live-gated-quote",
+      "rank": 34,
+      "title": "Fentucci/Tokiwa 504 go-live: gated quote-only trickle activation (Steve override: $0 per-yard, $4.25 sample, no width)",
+      "project": "fentucci-naturals",
+      "assignee": "claude-run-00034",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:47:09.216Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-00046-govarb-agents-results-grid-missing-sort",
+      "rank": 78,
+      "title": "GovArb /agents: results grid missing sort + density (standing-rule regression)",
+      "project": "govarbitrage",
+      "assignee": "claude-run-00046",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:25:55.168Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-00048-wpb-de-badge-residual-tail-retry-79-reje",
+      "rank": 94,
+      "title": "WPB de-badge residual tail \u2014 retry 79 rejects (plain/marble fill) + reproduce salvage-alternate pass",
+      "project": "wallpapersback",
+      "assignee": "claude-run-00048",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:47:10.549Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-00051-enrich-skill-template-deliverable-data-d",
+      "rank": 87,
+      "title": "Enrich skill template deliverable \u2014 data-driven SWOT bucket (future portals match rentv depth)",
+      "project": "consulting",
+      "assignee": "claude-run-00051",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:47:13.986Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-00062-koroseal-mfr-sku-custom-dwc-global-width",
+      "rank": 57,
+      "title": "Koroseal mfr_sku(custom+dwc)+global.width additive backfill \u2014 128 live products",
+      "project": "designerwallcoverings",
+      "assignee": "claude-run-00062",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:47:15.121Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-10162-register-all-8-affiliate-programs-via-op",
+      "rank": 6,
+      "title": "Register all 8 affiliate programs via OpenClaw real Chrome",
+      "project": "abramsego",
+      "assignee": "codex-run-10162",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T03:20:32.378Z",
+      "blocker": {
+        "type": "steve_action",
+        "condition": "Both forms staged and verified 2026-09-15. Awin at Verification: address plus agreement/final Join Our Network outstanding; deposit not yet shown. Cloudways business fields filled; tax/W9, payout, agreement/final Submit outstanding; Submit disabled. 6/8 registry active.",
+        "next_action": "Complete Awin business address and review final agreement/submission in foreground tab; complete Cloudways tax/W9 and payout details then review Submit. Any displayed charge requires separate approval. See pending-approval/2026-09-15-TK-10162-forms-staged.md.",
+        "owner": "steve",
+        "evidence_at": "2026-09-15T18:31:22Z",
+        "steve_one_action": false
+      }
+    },
+    {
+      "id": "TK-11046-sanderson-onboarding-go-live-blocked-sho",
+      "rank": 40,
+      "title": "Sanderson onboarding go-live blocked: SHOPIFY_ADMIN_TOKEN lacks write_inventory scope",
+      "project": "dw-commerce",
+      "assignee": "run-now-sanderson4",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:28:18.186Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11068-sanderson-na-per-colorway-featured-image",
+      "rank": 68,
+      "title": "Sanderson NA per-colorway featured-image fix (Cycle 2)",
+      "project": "dw-newarrivals-fix",
+      "assignee": "vp-dw-commerce",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-14T20:25:30.540Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11287-drive-open-tickets-in-board-order-using",
+      "rank": null,
+      "title": "Drive open tickets in board order using agents and skills on persistent loop",
+      "project": "ticket-system",
+      "assignee": "codex-ticket-loop",
+      "status": "doing",
+      "kind": "continuous_loop",
+      "updated_at": "2026-09-16T21:16:59.739Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11438-migrate-218-tcp-style-postgres-consumers",
+      "rank": 41,
+      "title": "Migrate 218 TCP-style Postgres consumers to unix socket (prereq for closing loopback TCP)",
+      "project": "designerwallcoverings",
+      "assignee": "codex-run-11438",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T16:41:08.923Z",
+      "blocker": {
+        "type": "steve_action",
+        "condition": "Prepared Crezana one-line socket change plus one service restart invokes existing canonical dw_unified schema commands; this newly described batch is outside earlier scoped approvals.",
+        "next_action": "Steve approve or revise pending-approval/TK-11438-postgres-socket-crezana-batch.md; on approval revalidate baseline, back up one file, apply and independently verify only Crezana. Emails excluded.",
+        "owner": "codex-run-11438",
+        "evidence_at": "2026-09-12T14:14:50.342Z",
+        "steve_one_action": true
+      }
+    },
+    {
+      "id": "TK-11450-gmc-scope-gap-the-canada-feed-1-077-offe",
+      "rank": 44,
+      "title": "GMC scope gap: the Canada feed (1,077 offers) and a third datasource have NEVER been audited \u2014 every remediation to date was US-only",
+      "project": "designerwallcoverings",
+      "assignee": "google-merchant-agent",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T08:12:35.263Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11624-malibu-wallpaper-vendor-tag-contains-55",
+      "rank": 8,
+      "title": "Malibu Wallpaper vendor tag contains 55 products from a DIFFERENT line (supplier 'Malibu Walls' / CDA- / 'Architectural Wallcoverings')",
+      "project": "dw-catalog",
+      "assignee": "claude-run-11624",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-15T23:54:33.655Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11658-kravet-family-hi-res-1-636-active-produc",
+      "rank": 39,
+      "title": "Kravet family hi-res: 1,636 active products still at 400px featured image; Brunschwig & Fils 43.5% below the 1200px threshold",
+      "project": "designerwallcoverings",
+      "assignee": "vp-dw-commerce",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:28:20.056Z",
+      "blocker": {
+        "type": "technical_dependency",
+        "condition": "Phase-1 gate DONE. Residuals: 112 oversized-source (>5000px) need resize-on-ingest; 1,593 need vendor rescrape (Phase-2).",
+        "next_action": "vp-dw-commerce: build resize-to-<=5000px step for the 112 self-healed, then re-run apply for those; separately run Phase-2 vendor rescrape for the 1,593.",
+        "owner": "vp-dw-commerce",
+        "evidence_at": "2026-09-14T21:37:04Z",
+        "steve_one_action": false
+      }
+    },
+    {
+      "id": "TK-11683-claude-session-transcripts-retain-live-s",
+      "rank": 32,
+      "title": "Claude session transcripts retain live secrets \u2014 nobody sweeps ~/.claude/projects/**/*.jsonl",
+      "project": "secrets-manager",
+      "assignee": "pinkroll-TK-11683-claude-session-transcripts-retain-live-s",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T19:34:22.263Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11709-ai-cluster-benchmark-exo-ring-heavy-mode",
+      "rank": 105,
+      "title": "ai-cluster: benchmark exo-ring heavy models via --with-exo-heavy (Llama-3.3-70B / Qwen3-Next-80B / Qwen3.5-122B) so the large_llm capability routes on measured cluster numbers",
+      "project": "ai-cluster",
+      "assignee": "blk-TK-11709",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:28:00.224Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11713-ai-cluster-context-conditional-model-fit",
+      "rank": 49,
+      "title": "ai-cluster: context-conditional model_fit (bench at production ctx 16-32k, not just 4096; KV-cache spill on 32GB workers can invert the measured ranking) \u2014 Kimi #9",
+      "project": "ai-cluster",
+      "assignee": "blk-TK-11713",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-14T17:40:51.034Z",
+      "blocker": {
+        "type": "technical_dependency",
+        "condition": "Analytical projector shipped (ea1f0d9): predicts NO inversion on 32GB workers with current GQA models, but worker RAM null in snapshot (30/45 NOT_MEASURED) and real 16k/32k measurement outstanding+gated. Scheduler/scoring integration surfaces owned by live runner (TK-11711).",
+        "next_action": "Steve/owner: (1) cheap-populate worker memory_total_gb via make discover then re-run projector for a real verdict; (2) off-peak gated 16k/32k benchmark to confirm; (3) live runner wires the spill signal into context-conditional model_fit in scoring.py.",
+        "owner": "vp-engineering",
+        "evidence_at": "2026-09-14T08:36:00Z",
+        "steve_one_action": false
+      }
+    },
+    {
+      "id": "TK-11743-dw-price-integrity-go-live-review-4-bloc",
+      "rank": 74,
+      "title": "DW price-integrity go-live review + 4 blocker fixes (tk11357-source-fix-d)",
+      "project": "designerwallcoverings",
+      "assignee": "code-reviewer",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-14T22:11:04.183Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11783-master-credentials-and-passwords-needed",
+      "rank": 31,
+      "title": "MASTER: Credentials and passwords needed (clearinghouse)",
+      "project": "master",
+      "assignee": "night-TK-11783",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:10:02.140Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11793-answer-cockpit-one-card-needs-steve-stre",
+      "rank": 42,
+      "title": "Answer Cockpit \u2014 one-card needs-Steve stream (:9805)",
+      "project": "answer-cockpit",
+      "assignee": "answer-cockpit",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T16:29:06.053Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11798-clear-approvals-queue-95-memos-reversibi",
+      "rank": 82,
+      "title": "Clear approvals queue \u2014 95 memos, reversibility-tier triage",
+      "project": "ops",
+      "assignee": "approval-agent",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T17:27:21.981Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11799-reproduce-draft-remediation-for-zero-pri",
+      "rank": 106,
+      "title": "Reproduce+draft remediation for zero-price orderable canary FAILs",
+      "project": "designerwallcoverings",
+      "assignee": "vp-dw-commerce",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T01:51:44.213Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11802-gmc-price-parity-fail-85-live-under-adve",
+      "rank": 4,
+      "title": "gmc-price-parity FAIL: 85 live under-advertised offers (stale-low Google prices vs live landing)",
+      "project": "dw-commerce",
+      "assignee": "claude-run-11802",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:35:22.222Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11841-masterdot-full-pass-2026-09-16-13-58-sna",
+      "rank": 18,
+      "title": "masterdot full pass 2026-09-16 13:58 snapshot - 19 live sessions, 10 none-bucket audit",
+      "project": "masterdot",
+      "assignee": "night-TK-11841",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:35:26.576Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11842-egress-sentinel-derive-proxy-allowlist-f",
+      "rank": 101,
+      "title": "egress-sentinel: derive proxy allowlist from secrets (ip:PORT scoped) \u2014 stop burning a CRITICAL on every Webshare exit-IP rotation",
+      "project": "egress-sentinel",
+      "assignee": "night-TK-11842",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:38:57.166Z",
+      "blocker": null
+    },
+    {
+      "id": "TK-11844-phillip-jeffries-showroom-only-still-has",
+      "rank": 1,
+      "title": "Phillip Jeffries (showroom-only) still has live GMC offers \u2014 >=8 confirmed, >=4 SERVING; routed from TK-11449 whose target TK-11193 was reaper-auto-closed",
+      "project": "dw-commerce",
+      "assignee": "night-TK-11844",
+      "status": "doing",
+      "kind": "task",
+      "updated_at": "2026-09-16T21:38:33.811Z",
+      "blocker": null
+    }
+  ],
+  "instruction": "Assess every queue entry in order. Gates, collisions and reads do not consume execution slots or count as implementation progress. Re-read each canonical ticket before claiming or mutating; this snapshot grants no authority."
+}
\ No newline at end of file

← c5f29c6d Save yoloforever cycle ledger receipt  ·  back to Ticket System  ·  Audit unproven cost join candidates with retained offline ev 5a926415 →