commits on main

2026-10-20

c807ee60 bunny.fae.mom sync: drop upstream's subscribeRepos rate limit

sync: drop upstream's subscribeRepos rate limit

Reverts upstream 8fd032f ("Rate limit subscribeRepos", in v0.7.0), which limited com.atproto.sync.subscribeRepos to 5 connections per 60s per IP. Ana asked for the fork to run without it. Every other rate limit is kept. No test covered the limit (the test env sets DISABLE_RATE_LIMITING), so no tests change. Upstream's CHANGELOG entry for it is left as history.

open commit browse files
c74f6c8f bunny.fae.mom sync: upstream tranquil-pds v0.7.1

sync: upstream tranquil-pds v0.7.1

Brings in upstream v0.7.1, including the webpki-roots / webpki-root-certs 1.0.10 and rustls-webpki 0.103.16 bump for the eMudhra intermediate compromise. Version stays at upstream's 0.7.1. Conflicts: - authorize/login.rs: upstream wraps authorize_get responses in respond!() (iOS cookie test); the fork's ?sso= forwarding (register and login redirects) now goes through respond!() too. authorize_post takes upstream's begin_second_factor flow; the device cookie it uses is read with the fork's current_device() (bumps last_seen, drops swept devices). - authorize/mod.rs: AuthorizeQuery keeps both the fork's `sso` and upstream's `redirect_test` fields. - authorize/passkey.rs, two_factor.rs: upstream's begin/complete_second_factor and consent_or_code structure, with every extract_device_cookie() call replaced by current_device() as the fork's device-retention commit did. - tests/it/store_parity.rs: union of imports (fork DeviceId, upstream OutboundMessage). - tranquil-store metastore/oauth_ops.rs: fork device-link helpers and stage_account_devices_removal kept; one test module with both the fork's stale-device sweep test and upstream's completed-identity guard test (chrono::Duration imported in the test module since upstream dropped it from the top-level import). - frontend api.ts: createPasskeyAccount takes upstream's requestUri and the fork's captchaToken. - frontend registration/flow.svelte.ts: fork's single sign-up form kept; upstream's registration binding applied by sending requestUri from the URL in createAccount (upstream's did:web/BYOD code stays removed). - frontend Register.svelte: upstream's login_required -> goToLogin handling added (goToLogin ported) alongside the fork's signup.errors.generic text. - frontend tests/oauth-registration.test.ts (auto-merged, adapted): the new request_uri test now drives the fork's createAccount(); the passkey variant is dropped as the fork's flow has no passkey-account creation. Squashed from the merge 7aaa931 (parents 7a4c109, v0.7.1 4e5ded7) so the PR applies as a patch on tangled; the tree is identical to that merge.

open commit browse files

2026-10-12

7a4c1096 nekomimi.pet oauth: forget sign-in devices of deleted accounts and idle ones

oauth: forget sign-in devices of deleted accounts and idle ones

oauth_device keeps the IP address and user agent of every browser that signed in, but nothing ever deleted a row: account deletion cascaded only the oauth_account_device link, so the device row outlived the account, and the tranquil-store backend kept the link too. Every account deletion path (delete_account_complete, the admin one and delete_account_with_firehose; tranquil-store's shared delete path) now deletes, in the same transaction or write batch, the devices only that account is linked to. Devices another account also signs in on keep their row and just lose the link. A sweep on the scheduled delete-check tick removes devices unused for scheduled.oauth_device_retention_secs (OAUTH_DEVICE_RETENTION_SECS, default 90 days, 0 disables), and unlinked devices after a day: they are created when a sign-in starts, before any account is linked. Use is the newest of the device's last_seen_at, its account links' updated_at (bumped on every sign-in on it) and, on postgres, the updated_at of tokens issued through it (bumped on refresh); tokens in tranquil-store don't record their device. Devices still trusted for 2FA are kept until the trust ends. last_seen_at was never refreshed before; the authorize endpoints now bump it whenever they read the device cookie, and a cookie naming a swept device is treated as no cookie, so a sign-in can't hit the oauth_device foreign keys with a dead id. oauth_token.device_id and oauth_authorization_request.device_id are ON DELETE SET NULL, so deleting a device never ends a session. Tests: store_parity covers all three deletion paths and the sweep on both backends, plus postgres-only trust and token-refresh cases; a tranquil-store unit test covers the sweep with backdated links and a trusted device.

open commit browse files

2026-10-05

b050cc55 nekomimi.pet oauth: let clients go straight to an SSO provider

oauth: let clients go straight to an SSO provider

/oauth/authorize?request_uri=…&sso=github now forwards `sso` to the login page (or, for prompt=create, the sign-up page) when that provider is enabled; unknown or disabled values are ignored. The page drops the param from the address bar and starts the provider right away, so a client like wisp.place can offer "Sign in with GitHub" without a second click on the PDS. A failed attempt lands on the normal page and never retries on its own.

open commit browse files
5fb1d3a9 nekomimi.pet signup: GitHub sign-ups set a password too

signup: GitHub sign-ups set a password too

SSO registration accepted no password, so GitHub-made accounts got a one-time app password and nothing to log into Bluesky with afterwards. complete-registration now takes an optional `password`, checks it with validate_password before any account work, and stores it with set_new_user_password once the account exists. With a password set, no initial app password is generated; if storing it fails the old app password path still runs so the user is never locked out. The SSO completion page asks for the password (with a show toggle), and both forms now check the server's rules client-side (8+ characters, upper and lowercase, a digit) with a hint that says so. Tests: sso.rs covers sign-up with a password (no appPassword, then createSession works) and a weak password (400, pending registration kept); vitest covers the client-side rule and the SSO page sending the password.

open commit browse files
5894a9b6 nekomimi.pet frontend: link to this build's source on every app page

frontend: link to this build's source on every app page

AGPL-3.0 section 13: pds.wisp.place runs a modified build, so the app (sign-up, login, consent, dashboard) gets a small "source code" footer pointing at https://tangled.org/okami.mom/tranquil-pds, next to the homepage footer link. It renders once i18n has loaded.

open commit browse files
69813465 nekomimi.pet frontend: link the homepage footer to this fork's source

frontend: link the homepage footer to this fork's source

pds.wisp.place runs a modified build, so "Open source & open hearts" now links to https://tangled.org/okami.mom/tranquil-pds.

open commit browse files
78c9a001 nekomimi.pet frontend: log in to the new PDS by DID, not email, during migration

frontend: log in to the new PDS by DID, not email, during migration

The inbound migration wizards authenticated against the destination PDS with the account's email as the createSession identifier. createSession only resolves DIDs and handles: 34f050a ("discoverable passkey authentication") dropped `OR email = $1` from get_login_full_by_identifier and renamed the lookups from *_by_handle_or_email to *_by_identifier, and 6ca6c45 added the AtIdentifier::new() check, which rejects anything that is not a DID or handle before the lookup. Email logins therefore fail with "Invalid identifier or password". The frontend was never updated. For passkey accounts this strands the user at the passkey step: completePasskeySetup succeeds server-side (passkey registered, app password minted, one-time setup token consumed), then the follow-up login with the email throws, so the wizard never advances to the app-password step and a retry reports "Setup has already been completed or expired". The password path fails after email verification the same way. Log in with the migrating DID instead (state.sourceDid for the inbound wizard, state.userDid for the offline one). The DID is preserved by migration and is always accepted by createSession, including for deactivated accounts via allowDeactivated. Adds login-identifier.test.ts, which models createSession rejecting emails and covers the inbound password and passkey paths and the offline password and passkey paths.

open commit browse files
c72bdd2e nekomimi.pet frontend: one simple sign-up form

frontend: one simple sign-up form

Replace the Passkey / Password / SSO tabs, identity-type picker and verification-method picker with a single form: handle (with the .fae.mom suffix and live availability), email, password with a show toggle, an invite code only when describeServer requires one, and the Turnstile widget only when it advertises turnstileSiteKey. Accounts are always did:plc and verified by email. SSO providers render as "Sign up with <provider>" buttons above the form, and migrating is a small link. After the email code (or link) is confirmed the flow has a full-access session from confirmSignup / createSession, so it offers an optional passkey through com.atproto.server.startPasskeyRegistration and finishPasskeyRegistration before completing the OAuth request with the password via /oauth/register/complete, as before. Skipping, or a browser without WebAuthn, goes straight on. The handle check now hits this PDS (/oauth/sso/check-handle-available) instead of https://<first user domain>, which was the wrong host when user domains differ from the PDS hostname. /oauth/register-password and /oauth/register-sso render the same page. The SSO completion page loses its identity and channel pickers too. Saved registration state moves to version 2 (no did:web data); older entries are discarded. Copy lives under the new en.json "signup" keys; other locales fall back to English.

open commit browse files
575b604c nekomimi.pet frontend: wisp.place theme

frontend: wisp.place theme

Re-point the design tokens at wisp.place's landing-page palette (paper, ink, rose, pink, lilac, butter, mint, sky; dusky purple in dark mode) and type (Fraunces display headings, JetBrains Mono body, Caveat kickers, loaded from Google Fonts like the landing page). Buttons become bordered pills with hard offset shadows that lift on hover, fields get 2px ink borders, an inset shadow and a pink focus ring. Motion is dropped under prefers-reduced-motion. signup.css carries the sign-up card, field and button styles shared by the registration, verification, passkey, SSO-completion and consent screens.

open commit browse files
f4ab89e9 nekomimi.pet frontend: Turnstile widget on registration pages

frontend: Turnstile widget on registration pages

Renders the widget (explicit render of challenges.cloudflare.com's api.js) on the password/passkey and SSO registration forms only when describeServer advertises turnstileSiteKey, keeps submit disabled until a token arrives, sends it as x-turnstile-token, and resets the widget after a failed attempt since tokens are single-use.

open commit browse files
b4d38212 nekomimi.pet signup: optional Cloudflare Turnstile check on account creation

signup: optional Cloudflare Turnstile check on account creation

Off unless both TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY are set. When on, createAccount, _account.createPasskeyAccount and SSO complete-registration need a token in the x-turnstile-token header, verified against siteverify (secret, response, remoteip from the rate limiter's client IP, PDS hostname check) before any other work or invite-code use. Siteverify failures fail closed (503 CaptchaUnavailable); missing and rejected tokens are 400 CaptchaRequired and InvalidCaptcha. Requests carrying a createAccount service token from the DID they bring (inbound migration, BYOD did:web) are exempt so goat and PDSMoover keep working; delegated accounts are already authenticated. describeServer gains turnstileSiteKey only when enabled.

open commit browse files

2026-10-02

e3b987b4 Johanna Larsson ci: tag main on every image push

ci: tag main on every image push

Instead of moving :latest, this adds :main for the intrepid adventurers who wish to live dangerously. And then we can have :latest track the latest proper release instead.

open commit browse files
072e993c oyster.cafe docker-compose: did-method-plc apparently needs forcing to main branch

docker-compose: did-method-plc apparently needs forcing to main branch

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>

open commit browse files
2817d9f2 june docs: add ebil pds to the instance list :3

docs: add ebil pds to the instance list :3

meow meow

open commit browse files
9538a1dd oyster.cafe docs: impromptu LLM-talk and other small touches

docs: impromptu LLM-talk and other small touches

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>

open commit browse files

2026-10-01

13830021 oyster.cafe types: locale newtype, single comms type, channel identifiers

types: locale newtype, single comms type, channel identifiers

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>

open commit browse files
d44c114a oyster.cafe password: take/verify scrypt passwords too from mysterious sources

password: take/verify scrypt passwords too from mysterious sources

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>

open commit browse files

2026-09-30

78f751d9 Johanna Larsson replace

replace

open commit browse files

2026-09-27

66ea8287 Johanna Larsson refactor: drop aws crates for s3

refactor: drop aws crates for s3

We were just using them for S3 support. This PR switches us over to the s3 crate which works the same way, with some minor differences. The main implication of this change is that we're no longer doing all the AWS stuff, like checking aws profile in home directory, or automatic discovery of metadata. This change is covered by our brand new s3 test suite, but I also did the old school `just test-full` with minio.

open commit browse files
3054f043 Johanna Larsson ci: restore image publishing

ci: restore image publishing

open commit browse files

2026-09-26

78c8e083 Johanna Larsson fix: abort multipart upload on errors

fix: abort multipart upload on errors

adds a test that proves it works too!

open commit browse files
c56801bc Johanna Larsson fix: ensure s3 blob uploads are streaming

fix: ensure s3 blob uploads are streaming

The existing code buffered the whole thing into vec which defeated the purpose of `put_stream`.

open commit browse files
1475bdfe Johanna Larsson test(storage): use s3s to test S3BlobStorage

test(storage): use s3s to test S3BlobStorage

Introduces s3s and s3s-fs to run an in-memory s3 server in tests. With this we can promote s3 tests to run in `just test`, they won't need containers, and they're super fast. Adds `just test-s3` to run them more easily. ``` Nextest run ID db9a9530-738e-4c92-ad3a-23289252cac2 with nextest profile: default Starting 6 tests across 1 binary PASS [ 0.356s] tranquil-storage::s3 put_stream_error_aborts_upload PASS [ 0.356s] tranquil-storage::s3 put_stream_empty_aborts_upload PASS [ 0.358s] tranquil-storage::s3 copy PASS [ 0.360s] tranquil-storage::s3 put_get_head_delete PASS [ 0.360s] tranquil-storage::s3 put_stream PASS [ 0.447s] tranquil-storage::s3 put_stream_multipart ──────────── Summary [ 0.448s] 6 tests run: 6 passed, 0 skipped ``` Also optimizes more hashing functions because it matters in tests.

open commit browse files

2026-09-25

5fd900ee Johanna Larsson build: switch governor to system time

build: switch governor to system time

We can make this test only by adding a feature for it and checking, but it didn't seem immediately worth it to me. Ping me if you want me to change it. Shaves about 15-20% off of a j2 test run, because we spent about 200ms on every one of the 500 tests against the server just initializing the governor clock.

open commit browse files
b7583724 Johanna Larsson ci: speed up test by running against /dev/shm

ci: speed up test by running against /dev/shm

open commit browse files
845eee12 Johanna Larsson test: introduce test jail to hide lewis' sins

test: introduce test jail to hide lewis' sins

We've got a bunch of tests that never pass because they time out on their own, or that take 10s because they test a 10s timeout, that we can rethink. Together they introduce a lot of slowness. This puts anything over 10s in CI into test jail.

open commit browse files
779733f6 Johanna Larsson test: lower bcrypt cost in tests

test: lower bcrypt cost in tests

The default is 12 but we can go down to 4 to waste less time on this.

open commit browse files
32307400 Johanna Larsson ci: split workflow into two and apply flags

ci: split workflow into two and apply flags

Taking some flags from the knot2 build and splitting it in the hopes that that's enough time to finish

open commit browse files
b5ad3a92 Johanna Larsson build: set tikv-jemalloc-sys opt-level to 1

build: set tikv-jemalloc-sys opt-level to 1

`gcc` uses fortify to do bounds checking or some other important thing and in CI it's failing because it's defaulting to -o0

open commit browse files
e86d1174 Johanna Larsson style: format everything

style: format everything

Guess we haven't run cargo fmt over the whole thing ina while!

open commit browse files
5714eb12 Johanna Larsson ci: Introduce a basic test+lint workflow

ci: Introduce a basic test+lint workflow

This removes the old workflows that had not been working for a while and introduces a new microvm based one that does some basic checks and runs the default test suite.

open commit browse files
4302611e Johanna Larsson build: use ring over aws-lc

build: use ring over aws-lc

All of the codebase uses ring explicitly or implicitly, but the HTTP client used for S3 requests was using aws-lc instead. This commit removes that extra dependency. Anecdotally it shaves 10s off a dev build on my machine, but slower machines would benefit more from it.

open commit browse files
1210ef9c Johanna Larsson fix: verify comms enqueue by ID

fix: verify comms enqueue by ID

Grabbing latest comms means you sometimes get the signup email instead of the one sent by the test.

open commit browse files
9768b477 Johanna Larsson chore: skip fsync in more test suites

chore: skip fsync in more test suites

Many of these don't finish anyway on my computer, I gave up around 40 minutes. But for the ones that did, it's a big speed up! For the others I guess they need more love. gauntlet-pr: 67.9s with it, 120.9s without test-store: 508.8s with it, still running at 1313s (I gave up) test-store-sim-nightly: ran for like 40 minutes each attempt without finishing. I just gave up. test-store-asan: only linux? but no reason why we can't apply the fix here too

open commit browse files

2026-09-24

4eb59e4f Johanna Larsson Creating a mega test binary

Creating a mega test binary

Overall `just test` runs about 10-20s faster on this branch than current main, for me, with around 170s instead of around 180-190s. But the cool thing is the compile time. Making a simple edit to any file involved and running `just test` on main takes about 60s to recompile the test binaries. On this branch it takes less than 15s. This is done by replacing a ton of separate test binaries with a single megabinary. Only test files that looked safe have been included, avoiding ones that do stuff like setting env.

open commit browse files

2026-09-23

0c5eda61 Johanna Larsson Drop rustls 0.21 copy

Drop rustls 0.21 copy

It's not being used anyway, so just wasted CPU cycles.

open commit browse files
7978246c Johanna Larsson Remove redundant cargo test build and set SQLX_OFFLINE=true

Remove redundant cargo test build and set SQLX_OFFLINE=true

The pg tests were missing SQLX_OFFLINE

open commit browse files
ffc8ef4e Johanna Larsson Disable fsync for postgres too

Disable fsync for postgres too

Summary [ 166.110s] 1453 tests run: 1453 passed (1 slow), 15 skipped Above is before this commit, below is after. Summary [ 140.410s] 1453 tests run: 1453 passed, 15 skipped The mechanism here is the same as for the metastore tests. By skipping fsync we can run the tests much faster, and fsync doesn't matter in tests. Unless you're testing postgres itself I guess

open commit browse files
7c0ca21d Johanna Larsson Remove sleeps from tests

Remove sleeps from tests

Replace sleeps with the existing polling function instead. Exposes that the stress test isn't much of a stress test, it probably needs more data or something, but if it's heavy it should maybe not be part of `just test`

open commit browse files
5214dd1c Johanna Larsson Remove forced serialization where not needed

Remove forced serialization where not needed

Tests already run in isolation, so this is only relevant when messing with env vars and stuff, I think!

open commit browse files
8a72d2c0 Johanna Larsson Stop talking to docker in tests that don't need docker

Stop talking to docker in tests that don't need docker

`just test` doesn't use docker, but the cleanup was still pruning containers over and over. This switches to only cleaning up containers created by the tests.

open commit browse files
939b5a8c Johanna Larsson Stop pg tests from duplicating tranquil-store tests

Stop pg tests from duplicating tranquil-store tests

open commit browse files
e483fa13 Johanna Larsson Remove tests that require postgres from `just test`

Remove tests that require postgres from `just test`

These suites got added but actually need the postgres setup, and so can't succeed and are just annoying everyone.

open commit browse files
e8795261 Johanna Larsson Avoid compiling and running tests twice

Avoid compiling and running tests twice

test-unit was building a second copy of the code with different features, and then the main test command ran the same tests anyway, so we just built and ran the tests twice Locally shaves a minute off of a full build+test, on this branch (so both runs include the fsync change etc) We're at [ 173.444s] overall for `just test` now

open commit browse files
43dfd2f2 Johanna Larsson Stop compiling tranqil-store for just-test

Stop compiling tranqil-store for just-test

Takes off ~140s CPU time for me, but no wall time improvement. Still, less work!

open commit browse files
781be29d Johanna Larsson Speed up bcrypt and blowfish in tests

Speed up bcrypt and blowfish in tests

Build optimized versions of bcrypt and blowfish to speed them up, referencing things like https://github.com/oxidecomputer/omicron/blob/main/Cargo.toml#L981-L982 Password hashing is not a dominating cost, so this doesn't have a significant impact on the overall time, but a lot of tests like `**tranquil-pds::legacy_2fa** **test_legacy_2fa_code_consumed_after_use**` speed up. It goes from 5.6s to 2.7s on my machine.

open commit browse files
06b12557 Johanna Larsson Skip fsync when running tests

Skip fsync when running tests

Flushing to disk all the time ensures no partial states when writing to disk during awkwardnesses like crashes in prod risking real user data. That's good! We're also doing it while running tests. Less good! Well, at least not important! Comparing a clean test run (`just test --no-fail-fast`) and a run on this branch, on my MacOS M4 Pro, I get: `**Summary** [ 517.723s] **1424** tests run: **1405** **passed** (**7** **slow**), **19** **failed**, **15** **skipped**` before and `**Summary** [ 326.369s] **1424** tests run: **1405** **passed**, **19** **failed**, **15** **skipped**` after. The reason I run `--no-fail-fast` is that a bunch of tests always fail and time out and stuff

open commit browse files
eebce289 Johanna Larsson fix: handle wildcard + aud in OAuth scopes

fix: handle wildcard + aud in OAuth scopes

`rpc:*?aud=did:web:api.bsky.app#bsky_appview` fails in the permissions check even though it's a valid scope. This makes it resolve correctly. Also this commit fixes a bug where `rpc` as a bare scope is interpreted as all methods for all services, but it's really an invalid scope. Can drop it if it's a problem, but it just looks like a bug in the implementation rather than something intended? Note that the parser treats `rpc:*` as "allow everything for no services", but aud is actually required, so it's not a valid scope? Didn't mess with it though because delegation uses it and that part of the code treats it as allow everything for every service. We should maybe split the scope parsers so we have one for delegated scopes and one for regular? However, to avoid the same bug as bare `rpc`, I changed the enforcement step in "regular" OAuth to disallow `rpc:*` (this matches the existing behavior on main, no actual change, just a little acknowledgement).

open commit browse files
b260ee10 oyster.cafe tranquil-store: make it seem a little less 'experimental'

tranquil-store: make it seem a little less 'experimental'

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>

open commit browse files
older commits →