or HTML became stored XSS on a public route.\n\nSwitched to:\n- + JSON.parse\n (with a <-escape on the JSON output for an extra layer in case the\n pattern is ever copy-pasted into a non-JSON context)\n- Popup built via document.createElement('strong') + .textContent so\n titles can never become HTML.\n\nP2 (data integrity) — dog_shows_ca.js:158 + migrations/007_*.sql\nON CONFLICT (source, source_url, start_at) collided for AKC/UKC where\nsource_url is the shared search/results page. Two distinct shows on the\nsame date silently overwrote each other in the events table.\n\nAdded title to the dedupe key + new migration that drops the old unique\nindex and creates idx_events_dedupe (source, source_url, start_at, title).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"bf214ed","d":"2026-05-01","s":"fix: codex review findings — creds + URL parse + license regex","b":"Three issues codex flagged on 2026-05-01:\n\nP1 — src/scripts/2week-followup-audit.sh:12: Hardcoded George Basic-auth\ncredential. Same pattern as run-meeting.sh had — read GEORGE_AUTH from env\nor ~/Projects/secrets-manager/.env, abort send if neither has it.\n\nP1 — src/lib/auth.js:157-158: `new URL(req.headers.origin)` throws on a\nmalformed Origin or Referer header, surfacing as an unhandled promise\nrejection in this Express 4 async handler — could crash the process.\nWrap with safeHost() so a bad header is treated as cross-origin (rejected)\ninstead of a 500.\n\nP2 — src/enrich/wikimedia_breed_images.js:37: ALLOWED_LICENSES regex only\nmatched hyphen-joined or no-separator forms (CC-BY-4.0, CCBY4.0). Real\nCommons LicenseShortName values use spaces too (CC BY-SA 4.0, CC BY 2.0),\nso the enricher was rejecting most CC-BY images. Updated regex to accept\nhyphen, space, or no separator between CC/BY/SA and the version.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"95e913a","d":"2026-05-01","s":"fix(community): debate-driven fixes for visibility, comments, signup","b":"5 defects converged on by claude+codex debate (animals-pawcircles-2026-05-01):\n\nP1 functional — marketplace SELECT omitted visibility/app_user_id/visible_circles,\n so the in-memory userCanSeeListingSync filter read undefined and hid every\n non-public listing from logged-in users. Now selects the fields and\n over-fetches 200 → filters → slices to 100.\n\nP1 privacy — /circles/:slug returned matching-zip/state listings with no\n visibility check at all, leaking scoped listings to anyone browsing a\n circle. Now post-filtered through the same userCanSeeListingSync helper\n used elsewhere (single source of truth for the gate).\n\nP2 — POST /marketplace/:id/comment had no listing-access check; users could\n comment on listings they couldn't see. Now loads the listing and runs the\n visibility gate before insert.\n\nP2 — auth.js signup: species_pets from a checkbox group is a string when one\n box is checked, an array when several. Insert into TEXT[] failed for the\n single-checkbox case. Normalize via [].concat() before insert.\n\nP3 — admin_pitch.js: \"All states\"