Changelog
A reverse-chronological record of every shipped change that touches
business-logic code or user-facing behaviour. Each entry is its own page —
authored by the developer (with the docs-update skill’s
help) and reviewed in the same PR as the code change.
Entries live under docs/app/changelog/entries/
as one file each, so two branches adding entries never touch the same file.
See the docs-automation spec
for how entries are maintained, and the covers: / owns: convention
for how /business-logic/ pages declare which code they document.
About 4% of questions got a blank reply after the assistant spent its tool calls hunting for skill ids. It now gets the ids up front, always writes a final answer, and logs any empty answer as a failure.
TUN-880PR #950An over-long member number made the contact form's insert fail, and the email to IBA was never sent. The form now validates, cleans up the member number, and sends the email even if saving fails.
TNBUGS-1862PR #951getRedisValue opened with a bare return null, so every cache read across the app returned null since 29 August. Writes kept working, which is why nothing alerted.
TUN-876PR #942GitHub ran the daily jobs 4-6 hours late every day and dropped three quarters of the hourly slots. A Cloudflare Worker takes over the timer, and op_cron_logs finally records what each run actually changed.
TUN-874PR #941Accordion's daily batch of completed flights by IBA members at US iFLY tunnels is now received, validated and stored.
TUN-856PR #929npm run lint in api/ had never worked — eslint was undeclared, so npx fetched a version the pinned Node cannot run. Behind it sat 57 errors, two of them real.
TUN-875PR #940Answers are stored hashed, so the editor shows every box blank. The writer read a blank as 'remove this', so a member who changed one answer lost the two they had left alone.
TUN-873PR #938A failed 2FA response only said what to do next when the email bounced, and 'has security questions' meant 'has a row' — so members were shown a door that would not open. Both are closed, and the fallback is now instrumented.
TUN-873PR #937A member with no verified phone whose passcode email bounces was stuck with 'contact support'. They can now answer their security questions to sign in directly — no password reset — but only in a short window after a real bounce.
TUN-870The login screen used to say a code had been sent to a named address while it bounced. It now reports the failure, explains the likely cause, and offers SMS where the member has a verified phone.
TUN-867PR #932Twelve logger.info and logger.warn calls recorded fields that never arrived — the event turned up with its label and nothing else. A winston detail split each one across two lines.
TUN-868PR #933The presence probe proved the signature header survives the proxy — 6,291 calls, no misses. What it could not prove is that our copy of the URL reproduces Mandrill's digest, so the webhook now computes and compares one, and acts on nothing.
TUN-859PR #931app.locals held its boot-time copy of the faqs, skills, news and tunnels keys for the life of the process, so CMS saves rebuilt Redis and the site did not change. It now re-reads once a copy ages out.
TUN-855The last v1 caller migrated to POST /get-member, so the legacy endpoint and its parallel controller/service/repository/model stack are gone. The old per-vendor tables are dropped in a follow-up deploy.
TUN-793PR #905A wall-clock gate that the droplet's crontab satisfied but GitHub's scheduler never does left one check skipped for two weeks. Alerting moves to Slack, skips now expire, and op_health_checks gets a retention sweep.
TUN-845PR #903The OpenAI Assistants API shuts down on 26 August. Ask Rusty now runs on Claude Sonnet 5, answers from live database content instead of a hardcoded PDF list, and gates every source through a single access resolver.
TUN-788PR #901Tunn3l joins FuseMetrix and Convergence as a booking-system integration, and the per-vendor connector tables are consolidated into one set discriminated by a vendor column.
TUN-661PR #744executeQuery now retries deadlocked statements up to 3 times with backoff, so concurrent approve-manually requests no longer surface a 500.
TUN-817PR #868Removed the transitional dual-tag so the website tags only the unified GA4 property, retiring the legacy property and GTM container.
TUN-801Server-side GA4 purchase events now capture the real GA client_id/session_id client-side so purchases join the member's web session.
PR #765The 2FA passcode SMS appends an Apple domain-bound code line so iOS autofill pins to the exact current passcode instead of a stale one.
TNBUGS-1732The /book page now reports the member's Convergence discount tier to GA4, so app-driven bookings can be measured by tier.
TUN-798PR #753role_id 11 and 12 are now always required to enable 2FA, independent of instructor currency — previously they were never prompted.
PR #686Removed a dead dialing-code lookup so the 2FA SMS recipient number is built directly from the stored members.country value.
TNBUGS-1729PR #664Fixes a concurrency race that left members without an earned parent skill, via serialised locking, sequential UIs, and a reconciliation cron.
TNBUGS-1728PR #648The API now applies schema/data changes through a tracked, forward-only migration runner with CI and deploy-time guardrails.
PR #650Tooling that keeps business-logic docs in sync with code — the docs-update skill, a frontmatter convention, and deterministic drift safety nets.