From the relay architecture review 2026-10-14. Concurrent sends to one device now commit in id order (row lock), so a poll cursor can no longer skip and delete an unseen envelope. One Delivery.write for send, sealed send and federated deposits, with a bounded push worker; federated deposits now wake and push. Prekey pops use FOR UPDATE SKIP LOCKED plus a used recheck. Key log appends take an advisory lock. Account delete retires each device (revoke leaves). Deleted accounts and DB errors are refused. retryingTransaction retries PG 40P01 and 40001. Monthly PITR restore drill (installed, PASS). 915 tests pass with Postgres. Generated with Claude Code.
From the relay architecture review 2026-10-14. Concurrent sends to one device now commit in id order (row lock), so a poll cursor can no longer skip and delete an unseen envelope. One Delivery.write for send, sealed send and federated deposits, with a bounded push worker; federated deposits now wake and push. Prekey pops use FOR UPDATE SKIP LOCKED plus a used recheck. Key log appends take an advisory lock. Account delete retires each device (revoke leaves). Deleted accounts and DB errors are refused. retryingTransaction retries PG 40P01 and 40001. Monthly PITR restore drill (installed, PASS). 915 tests pass with Postgres. Generated with Claude Code.
Restores the newest base backup plus every WAL segment since into a
throwaway postgres container, compares table count, devices, users and
key_log (md5 over seq and leaf_hash up to the restored seq) with live,
and writes PASS or FAIL to LAST_PITR_DRILL. Installed on the box
2026-10-14: first run PASS, and pointing it at a missing archive
gives FAIL with exit 1. Timer: the 2nd of each month, 06:30 UTC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Postgres test holds one send inside its transaction after its insert
and sends a second to the same device. On main the second commits first,
a poll sees id N+1, the cursor passes N, and delete-on-ack removes N
unread. RelayHarness gains a postgres option for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
popPreKey and popPQPreKey: the candidate subquery takes FOR UPDATE SKIP
LOCKED on Postgres and the outer UPDATE rechecks used = false. Before,
under READ COMMITTED a second concurrent pop waited on the first one's
row lock, rechecked only id = X, and returned the same one-time key.
SQLite keeps the old statement plus the recheck: one writer at a time.
KeyLog.append takes pg_advisory_xact_lock in the same transaction as the
insert, joining the caller's transaction when there is one. Before, two
appends could commit out of seq order, so a head read between the two
commits was not a prefix of the next head.
The pooler guard now allows pg_advisory_xact_, which is released at
COMMIT. docs/POOLING.md says so.
Postgres tests (FN_TEST_POSTGRES): two connections pop one device's pool
and get two different keys, classical and PQ; a head read while one
append is open is a prefix of the final log. All three fail on
gitea/main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deleteAccount now retires each device through DeviceRetirement inside
one retryingTransaction, before the user row goes, so every device gets
a revoke leaf under the account's subject. Before, devices were deleted
directly and the key log never heard about it. Earlier leaves are
untouched; key_log only grows.
noteAliveOrRefuse no longer swallows errors. A database error refuses
the request, so the suspension check cannot be skipped. A missing user
row is refused when this relay issues tokens (FN_ISSUER_PRIVATE_KEY set);
a verify-only relay still accepts accounts it has no row for.
Tests: account delete appends one revoke leaf per device and keeps the
earlier leaves; a token for the deleted account is refused (410 for the
retired device, 401 for a fresh device id); a database error refuses;
a verify-only relay still accepts an unknown account. The first three
fail on gitea/main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sends and federated deposits now write envelopes through one function,
Delivery.write. Inside the transaction it locks each target device row
(sorted, FOR NO KEY UPDATE) before inserting, so on Postgres one
device's envelopes take ids and commit in the same order. A poll cursor
can no longer pass an uncommitted lower id, and delete-on-ack can no
longer remove an envelope the device never saw. The cursor stays the
global envelope id, so installed apps are unaffected. SQLite already
has one writer and skips the lock.
After the commit, Delivery.write wakes the devices and queues the push
on a PushWorker: 8 at a time, 1024 queued, a full queue drops the push
and counts it, and shutdown flushes the queue. This replaces one
Task.detached per send. Federated deposits now wake and push like a
local send. An introduction's first message is a sealed send and
already took this path.
placeDeposits becomes resolveDeposits and returns the rows to write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
retryingTransaction now retries PSQLError with SQLSTATE 40P01
(deadlock_detected) or 40001 (serialization_failure), with the same
5 attempts and backoff as SQLite BUSY. No other code is retried.
Postgres has already rolled the attempt back, so a rerun is clean.
Only the outermost call retries a Postgres error. A nested call shares
an already aborted transaction, so it rethrows and the outer call
starts over. This covers KeyLog.append taking the advisory lock inside
a caller's transaction.
Test (FN_TEST_POSTGRES): two connections lock rows 1 and 2 in opposite
order inside retryingTransaction. One gets 40P01, runs again, and both
commit once: 3 attempts, rows end at [2, 2]. Fails with 40P01 before
this change; passed 5 of 5 runs after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's e2e conformance suite (test/relay.mjs at 310c315) mints tokens
for bare subjects on an issuing relay, and the account check refused
them all as deleted. Account delete now lists the id in a new
deleted_accounts table (id and time only), and a missing row is
refused only when the id is listed there. Production had 0 devices
without a user row. Conformance: 266 passed, 0 failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The four repositories moved from GitLab to Gitea. The hand-written
source page (en, es) and the layout's sameAs list now point at
https://git.extant2000.com/extant2000/<repo>; all four answer 200
and clone without credentials. Generated doc pages still carry
GitLab links until their source documents change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
From the relay architecture review 2026-10-14. Concurrent sends to one device now commit in id order (row lock), so a poll cursor can no longer skip and delete an unseen envelope. One Delivery.write for send, sealed send and federated deposits, with a bounded push worker; federated deposits now wake and push. Prekey pops use FOR UPDATE SKIP LOCKED plus a used recheck. Key log appends take an advisory lock. Account delete retires each device (revoke leaves). Deleted accounts and DB errors are refused. retryingTransaction retries PG 40P01 and 40001. Monthly PITR restore drill (installed, PASS). 915 tests pass with Postgres. Generated with Claude Code.
Already in main: this PR head and main are both
5a0331e37a. Wave 1 is live. Closing rather than merging a no-op.Pull request closed