Commit Graph

399 Commits

Author SHA1 Message Date
GPT on behalf of DHH 9f1b73a75f Merge #342: update the RuboCop toolchain 2026-10-08 13:15:30 +02:00
GPT on behalf of DHH da44351c5f Merge #341: exercise actual libvips loader blocks 2026-10-08 13:15:30 +02:00
GPT on behalf of DHH 9b8a1683a7 Reuse native plain text conversion for attachment-free content 2026-10-08 12:41:14 +02:00
GPT on behalf of DHH a0d72938f3 Keep transaction guards temporary and simplify quick boost forms 2026-10-08 12:40:31 +02:00
GPT on behalf of DHH a9007010c1 Check cache transactions without leasing a database connection 2026-10-08 12:34:18 +02:00
GPT on behalf of DHH bbee420097 Reuse epoch-verified record snapshots and avoid empty push payloads 2026-10-08 12:26:50 +02:00
GPT on behalf of DHH 74381e8b58 Retain content-validated message fragments across unrelated commits 2026-10-08 12:00:45 +02:00
Thomas Klemm 9ad45892ed Update RuboCop toolchain
Amp-Thread-ID: https://ampcode.com/threads/T-01a11acc-6153-7316-a06c-fcf2d713cd64
Co-authored-by: Amp <amp@ampcode.com>
2026-10-08 09:48:51 +00:00
GPT on behalf of DHH 59e1c6d3d7 Merge #335: reject invalid QR code requests before rendering 2026-10-08 11:43:27 +02:00
GPT on behalf of DHH 2c53c46767 Reuse token-free message controls and render posting deliveries once 2026-10-08 11:43:27 +02:00
Thomas Klemm 5940ea068f Test blocked libvips loaders directly
Amp-Thread-ID: https://ampcode.com/threads/T-01a11acc-6153-7316-a06c-fcf2d713cd64
Co-authored-by: Amp <amp@ampcode.com>
2026-10-08 09:35:53 +00:00
GPT on behalf of DHH e55b6dc3e5 Merge #339: checkpoint SQLite WAL outside request threads 2026-10-08 11:33:18 +02:00
GPT on behalf of DHH f36b5a2fae Merge #336: avoid rewriting already-unread room memberships 2026-10-08 11:33:18 +02:00
GPT on behalf of DHH ec31092919 Publish verified Fetch Metadata cache benchmarks 2026-10-08 10:17:30 +02:00
GPT on behalf of DHH 0f5d0b2b6e Use header-only forgery protection and cache complete responses 2026-10-08 09:05:10 +02:00
GPT on behalf of DHH 008ea1ab2a Update verified benchmarks for final upstream Rails revision 2026-10-08 00:09:25 +02:00
GPT on behalf of DHH 8de3e22ab4 Clarify database task repair hook scope 2026-10-07 23:51:12 +02:00
GPT on behalf of DHH f792e16e64 Repair message counters within database task pools 2026-10-07 23:50:37 +02:00
GPT on behalf of DHH fd49099729 Merge SQLite-maintained bot pagination counts from PR #337 2026-10-07 23:28:53 +02:00
GPT on behalf of DHH d9c3207886 Merge upstream banned-user push notification fix 2026-10-07 23:19:33 +02:00
GPT on behalf of DHH cd0414ef8f Publish verified architecture-transfer benchmarks 2026-10-07 23:15:30 +02:00
Cursor Agent b4ab2df20f Repair messages_count triggers in one SQLite write transaction
ensure! now rechecks trigger presence under BEGIN IMMEDIATE, backfills
drifted rooms.messages_count, and reinstalls all three triggers before
commit so concurrent writers and concurrent boot repairs cannot observe
a partial install or keep a wrong total. install! uses the same
immediate write lock for drop/recreate. Lifecycle regressions cover
missing/partial trigger drift, concurrent ensure!, and writes racing
repair.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 20:58:32 +00:00
Cursor Agent 32748e7fd6 Keep fixtures :all; isolate trigger lifecycle tests
Drop the explicit fixture list and prepend module. Override load_fixtures
only long enough to ensure! + backfill after alphabetical fixture load.
Move destructive trigger DDL into its own test file so parallel CI workers
do not strip triggers from the counter examples.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 20:14:13 +00:00
GPT on behalf of DHH 7271eb59f4 Wait for persisted messages before leaving system job helpers 2026-10-07 21:56:09 +02:00
Cursor Agent 9fd1563c9b Simplify messages_count trigger install and harden ensure!
Require all three SQLite triggers before treating the counter as installed,
drop the schema.rb / dump-rewrite install paths in favor of rake ensure after
schema load, isolate destructive trigger tests, and cover foreign room moves
plus fixture baseline counts.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 19:46:57 +00:00
GPT on behalf of DHH 105f4b8fd1 Digest session credentials in instrumented fragment keys 2026-10-07 21:45:31 +02:00
GPT on behalf of DHH a95361dd15 Recheck captured snapshot before native fragment lookup 2026-10-07 21:43:26 +02:00
GPT on behalf of DHH 8d02540e31 Bound native fragment caching and collapse concurrent page renders 2026-10-07 21:38:36 +02:00
Cursor Agent 1f5858098b Keep bot page totals on SQLite-maintained rooms.messages_count
X-Total-Count on GET /rooms/:id/:bot_key/messages was COUNT(*) of the
room on every page. Serve it from rooms.messages_count updated by SQLite
triggers so Rails, bulk SQL, and foreign writers stay in step — without
ActiveRecord counter_cache callbacks those paths skip.

Fixes #309.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 19:36:01 +00:00
Cursor Agent 85ad590632 Wait for checkpoint thread exit; avoid lock spin without a DB
stop joins until the contender finishes so before_fork never inherits an
open flock or SQLite connection. Skip acquire when the database file is
missing, and sleep if with_database no-ops after a race.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 19:23:20 +00:00
Cursor Agent 8704baa860 Load WAL checkpointer outside Zeitwerk reload; harden fork stop
Keep the module under lib/rails_ext, use before_worker_boot, shorten the
SQLite busy timeout under stop's join, and PASSIVE-checkpoint on exit for
short-lived writers.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 19:05:44 +00:00
Cursor Agent 61631ea5f9 Stop WAL checkpointer before fork; one thread owns the flock
Unify startup: initializer starts every non-test process, Puma and Resque
stop before fork and start again in the child. Only the contender thread
releases the lock. Drop Puma::CLI / single_puma_process? special cases.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 19:00:40 +00:00
GPT on behalf of DHH b220486c16 Admit paginated HTML and hydrate CSRF tokens outside presentation keys 2026-10-07 20:58:54 +02:00
Cursor Agent 88acc4e026 Checkpoint SQLite WAL off the request thread safely
Disable wal_autocheckpoint and run PASSIVE checkpoints from one elected
writer process. Contenders start in console/rake (initializer), Puma
workers (including WEB_CONCURRENCY=auto), and Resque children, with
failover, capped error backoff, and tests for lock takeover.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
2026-10-07 18:53:19 +00:00
Jeremy Daer 6e312c6028 Stop sending push notifications to banned users (#338)
Banning a user deletes their sessions and closes their connections but
keeps their push subscriptions, and Room::MessagePusher chose recipients
by membership alone. A banned user's browser or phone therefore went on
receiving the room name, sender and text of new direct messages,
mentions, and messages in rooms they had set to everything.

Choose subscriptions from active users only. The subscriptions are kept,
so unbanning brings notifications back without the user having to
subscribe again (the client doesn't resubscribe while the browser still
holds a subscription).

Co-authored-by: Marcello Costagliola <176920116+namespaceMarcello@users.noreply.github.com>
v1.5.2
2026-10-07 11:25:16 -07:00
GPT on behalf of DHH ac73267b07 Reuse authorized read pages without stale presentation or CSRF masks
Transfer the C completed-response cache lesson into Rails, keeping authentication, room checks and cookies per request. A persistent read-only SQLite observer detects local and foreign commits and rejects racing admission. Whole-page misses render fresh to avoid stale nested fragments; message ETags reflect token-neutral presentation.
2026-10-07 20:02:20 +02:00
GPT on behalf of DHH ee5fe3717a Update benchmarks from the shared response-verified comparison 2026-10-07 17:53:35 +02:00
GPT on behalf of DHH 27f5461067 Render a complete auto-submit transfer form 2026-10-07 17:08:48 +02:00
GPT on behalf of DHH 8bbe129030 Preserve active ping editors during sidebar refreshes 2026-10-07 16:55:08 +02:00
Marcello Costagliola af4f94c4bd Leave members who are unread already out of a new message's update
Every message rewrote the row of every disconnected member of the room,
including the ones who were unread already and stay unread. Open rooms
take in the whole account, so in steady state that is every member on
every message: 325 WAL pages per post at 10,000 members, against 16 when
only the members who had read the room are written.

Directs keep touching all their members: a direct's sidebar row is
cached by its membership and carries the room's recency, so it has to
be refreshed on every message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dFSDHrgrLoBELwjV3Qunq
2026-10-07 16:41:20 +02:00
GPT on behalf of DHH 765711f27d Preserve request ports in message copy links 2026-10-07 16:37:53 +02:00
GPT on behalf of DHH 3c022a1641 Preserve timestamp ordering of Rails unread notices 2026-10-07 16:19:10 +02:00
GPT on behalf of DHH 2b9685c35c Wait for sidebar frames before reloading on Cable connections 2026-10-07 16:18:39 +02:00
GPT on behalf of DHH dbc7620a76 Bound accessible search probes and reject invalid benchmark responses 2026-10-07 14:22:07 +02:00
GPT on behalf of DHH c49f53d8f8 Refresh Go measurements after final sidebar correction 2026-10-07 12:22:17 +02:00
GPT on behalf of DHH 6288430f37 Update shared benchmarks after independent performance review 2026-10-07 12:17:01 +02:00
GPT on behalf of DHH 7df98ac883 Merge current Rails main during performance review
# Conflicts:
#	app/views/messages/_message.html.erb
2026-10-07 11:34:17 +02:00
GPT on behalf of DHH 27065ef489 Keep same-second unread events and bound idle push connections 2026-10-07 11:00:47 +02:00
Donal McBreen 8a6e4290d8 Merge commit from fork
* Derive message DOM ids from the server id, not client_message_id

A message's DOM id was derived from the browser-chosen client_message_id
via a Message#to_key override, so dom_id(message) was
"message_<client_message_id>". Turbo's append de-dups by DOM id, so a room
member who posted a message reusing a victim's client_message_id displaced
the victim's message element in every connected member's live view; editing
the attacker's own message then broadcast onto the victim's presentation id.

Drop the to_key override so every message DOM id and broadcast target derives
from the record's primary key. Two distinct records can no longer share a DOM
id regardless of stored client_message_id, so the collision is impossible with
no data migration and no uniqueness constraint. to_param and the fragment
cache key already used the primary key, so message URLs and per-message cache
entries are unchanged.

The composer's optimistic pending message still uses client_message_id as its
placeholder DOM id, which no longer matches the server broadcast's PK-based id.
Reconcile instead by rendering data-client-message-id on the real message and
having the messages controller drop the matching pending placeholder on
connect. Only client-side placeholders (data-pending-message) are removed, so a
message another member posts reusing the same client_message_id can never
displace a real one through the reconciliation path either.

Also point the boost broadcast target at the PK-based dom_id(message, :boosts)
to match the rebuilt container id.

GHSA-3v99-4vxh-xg84

* Bust cached message fragments rendered with client_message_id DOM ids

The message fragment cache keys on the record and the template digest, and
removing the to_key override changes neither. Fragments cached by an earlier
release would keep their message_<client_message_id> ids, so edit, delete and
boost broadcasts, which now target primary-key ids, would miss those messages
in other members' live views until the cache entry expired.

* Locate messages by record id in the client_message_id collision tests

Assert on data-message-id rather than the new primary-key DOM ids, so the tests
describe the behavior instead of the fix and fail on the vulnerable code for the
real reason. Edit the attacker's message to new text and wait for it to arrive,
so the edit path is exercised rather than passing vacuously. Add a request test
that two messages sharing a client_message_id render as distinct elements.

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2026-10-07 01:41:12 -07:00
GPT on behalf of DHH 14a2f8f550 Merge pull request #329: Delete a room's messages in a job, one transaction each
Reviewed and merged by GPT on behalf of DHH.
2026-10-07 10:33:57 +02:00