- SupplierOffer::delete_for_isbn() was the one Catalog method still
missing a $wpdb failure check. There's no unique constraint on isbn13
alone (schema only has a composite key on isbn13+status+base_price), so
SyntheticOfferGenerator's delete-then-insert pattern relied entirely on
the delete actually succeeding — a silent failure there left the stale
row in place alongside the new insert, two rows for one ISBN, with
best_offer_for_isbns() picking whichever was cheaper by chance and
generate-offers reporting full success throughout. Verified with the
same forced-failure reproduction as the review that found it (a BEFORE
DELETE trigger): before this fix, that scenario left 2 rows for the
ISBN; after, it throws immediately (caught by generate_all()'s existing
per-ISBN handler) and the row count stays at 1.
- deploy/lib/env.sh's env_get() didn't strip a trailing \r or leading/
trailing whitespace from extracted values. Consequential specifically
because of restore.sh's DB drop/recreate (added earlier this session):
DB_NAME_VALUE feeds directly into DROP DATABASE IF EXISTS/CREATE DATABASE
for that name — a CRLF-saved or hand-edited-with-trailing-space .env file
would silently target a different database name than the real one, with
no error, defeating the "clean restore" guarantee that fix exists for.
Also mattered for quote-stripping: a trailing \r landing after a closing
quote made the end-with-quote check silently fail to match, leaking
literal quote characters into the value. Verified against CRLF-terminated,
trailing-whitespace, and quoted+CRLF combinations, including a byte-level
check that no \r survives; confirmed no regression against normal
LF-terminated unquoted/quoted values via a full deploy.sh dev run.
- Replace the bare-repo post-receive hook with deploy/poll-deploy.sh: Gitea
and the Docker hosts are separate machines, so each box polls its branch
via host crontab instead of needing an exposed webhook receiver.
- Add Blocksy theme + Blocksy Companion auto-install to deploy.sh (free
tier; the paid Book Store starter site still needs a manual license step).
- Fix deploy.sh/backup.sh/restore.sh sourcing .env files as bash: a bcrypt
hash's `$2a$14$...` shape breaks under `set -u`. Replaced with
deploy/lib/env.sh, a literal (non-executing) KEY=VALUE reader.
- Fix docker compose itself mangling the same kind of value: both
`environment: ${VAR}` and `env_file:` run values through Compose's
interpolation, which silently blanks `$identifier`-shaped substrings.
The staging basic-auth hash is now rendered directly into the Caddyfile
by deploy.sh, bypassing Compose's variable system entirely.
- Fix dev/staging/production silently sharing one Compose project (and
therefore one db_data volume) by pinning an explicit -p per environment.
- cron and wordpress now share one environment anchor so they can't drift
apart again (cron was silently missing WORDPRESS_CONFIG_EXTRA before).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>