Rails' ActiveStorage proxy controllers hardcode `http_cache_forever public: true`,
which sets `Cache-Control: public, immutable`. For non-public blobs behind auth,
this allows CDN caching that serves responses without authorization checks.
Override `http_cache_forever` in the Authorize concern to downgrade `public` to
`false` for non-public blobs.
See https://github.com/basecamp/fizzy/pull/2251 for context
- Fix indentation
- Split awkward initializer into separate methods
- Rename credentials to passkeys
- Simplify authenticator registry loading
- Flatten nested errors under ActionPack::WebAuthn
- Set passkey current params only requests that use it
- Inline anemic methods
- Replace custom validation with ActiveModel::Validation
- Rename identity to holder in Passkey
- Rename credentials to passkeys in JS
- Extract framework library out of controllers
- Pass params hashes down to ActionPack
- Attempt to simplify public interface
- Push data decoding down to the classes representing the data
- Introduce has_passkeys
- Add CBOR bigint support
- Rename ActionPack::WebAuthn::Passkey to ActionPack::Passkey
- Add create_passkey_button helper
- Rename public-key to creation-options
- Add sign_in_with_passkey_button helper
- Dispatch events for the whole Passkey lifecycle
- Add ED25519 support
- Prevent crash on missing meta tag
- Validate resident key options
- Don't clobber existing ActionPack config options
- Validate cryptographic params
- Move CurrentWebAuthnRequest into ActionPack::Passkey
- Use ActiveModel::Attributes for Options objects
- Implement expiring challanges
- Add lifecycle events
- Extract param helpers into Request
- Add passkey_creation_options and passkey_request_options helpers
- Add create_passkey_challenge to make it easier to override the create method if needed
- Prefix all view helpers with passkey_
- Auto-include ActionPack::Passkey::Holder
- Make the passkey challange url configurable
- Add a reminder about Passkeys to the magic link email
Separate the attachment existence check from the broadcast suppression
override so each file has a single responsibility. The guard now lives
in ActiveStorageAnalyzeJobSkipDetached with its own documentation
explaining the upload-then-delete race condition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upload-then-delete races cause AnalyzeJob to hit S3 after PurgeJob has
already removed the object, producing Aws::S3::Errors::NoSuchKey noise
in solid_queue_failed_executions. Since PurgeOnLastAttachment destroys
the attachment row before enqueueing PurgeJob, checking
blob.attachments.exists? catches this — a fast DB query that avoids the
S3 round-trip entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
replacing normalize_base36. This should make it easier to convert
UUIDs between hex and base36 formats, useful when debugging production
logs that contain hex UUIDs.
For example, if in a log you see:
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE
`users`.`account_id` = x'019afab815897a4f920f3a24fab75400' AND
`users`.`id` = x'019afab815a2790688c58c7f8326e700' LIMIT 1
and you want to find that account or that user in the console, you can
now do:
id = ActiveRecord::Type::Uuid.hex_to_base36("019afab815897a4f920f3a24fab75400")
=> "03f6bilrvt3oghzhurll4pp8g"
An append-only storage ledger replaces deadlock-prone synchronous
counter updates and drift-prone async updates.
All content storage (card images, card/comment/board description embeds)
is tracked. Account exports (which expire) and avatars are not tracked.
Storage is accounted for by Account and Board. Both consume the same
event stream independently: no bubble-up storage bumps triggering
deadlocks due to lock sequencing. Each calculates its own total from
the underlying ledger with independent cursors and materialization.
* Storage::Entry: Append-only ledger recording attach/detach/transfer
events with delta bytes. Single event stream indexed for both
Account and Board cursor queries.
* Storage::Total: Polymorphic snapshot cache with cursor (last_entry_id)
tracking which entries have been materialized.
* Storage::Totaled concern: Provides bytes_used (fast snapshot) and
bytes_used_exact (snapshot + pending) query modes, plus
materialize_storage! to roll up pending entries.
* Storage::Tracked concern: For models owning attachments (Card,
Comment, Board). Provides board_for_storage_tracking for models
where board is determined differently (Board returns self).
Handles board transfers by recording transfer_out/transfer_in entries.
* Storage::AttachmentTracking: Hooks ActiveStorage::Attachment lifecycle
to record attach/detach entries. Handles ActionText::RichText embeds
by traversing to the actual model. Snapshots context in before_destroy
to handle cascading deletes where parent record may be gone by
after_destroy_commit.
* MaterializeJob: Rolls up pending entries into snapshot. Concurrency
limited per owner to prevent duplicate work.
* ReconcileJob: On-demand reconciliation against actual attachment
storage for support/debugging. Compares ledger total to real bytes
from card images, card embeds, comment embeds, and board embeds.
Usage:
* account.bytes_used / board.bytes_used: fast, slightly stale bytesize
* account.bytes_used_exact / board.bytes_used_exact: real-time bytesize
* Storage::Entry: audit trail for debugging and point-in-time queries
* main: (116 commits)
Ensure avatar thumbnails are square
Update useragent to recognize twitterbot/facebot
Add defensive styles for non-square avatar images
Update test for copy changes
Missed commit
AI: standardize on https://agents.md
Make it clear this is just notifications, not comprehensive activity
AI: configure MCP servers for Chrome, Grafana, and Sentry (#1727)
Allow requests from Google Image Proxy
Update to basecamp's useragent fork
Clean up a little bit the CSRF reporting code
Claude: production observability guidance (#1725)
Prevent autoscroll to the root columns container to prevent jump on page load
Include full name string so you can type your name to filter
Prioritize current user and assigned users in assignment dropdown
Check and report on Sec-Fetch-Site header for forgery protection
bundle update
Bump bootsnap from 1.18.6 to 1.19.0
Bump rails from `077c3ad` to `17f6e00`
Fix cards getting stuck in edit mode
...
This should fix the recurring ActiveStorage::FileNotFoundError errors
from ActiveStorage::AnalyzeJob.
The issue is that, when images are not direct uploaded (e.g. a card
background image or avatar image), the AnalyzeJob has been getting
enqueued before the file itself was uploaded to blob storage.
Setting ActiveJob::Base.enqueue_after_transaction_commit = true makes
sure that jobs are always enqueued only once *all* transactions have
been committed.
This will be the Rails default at some point, but for now we still
need to explicitly set it.
Big thanks to @jeremy for the assist. 👏
We are not trying to handle user input so `cast` can be a no-op. Also
extract the base36 normalization logic, let the binary class handle the
serialize encoding and add some tests.
- Switch to binary 16 for UUID keys
- Remove AccountScopedRecord base class, all model use binary uuids now
- Fix the search sql to serialize uuids properly
- Patch the MySQL schema dumper to output binary lengths