feat!: Spaces repoRev/spaceRev and listRepos checkpoints #216

Merged
Grandiras merged 2 commits from issue-213-spaces-space-rev into main 2026-10-05 13:40:27 +00:00
Owner

Closes #213.

Brings the Spaces sync model to the 2026-10-01 alpha (upstream bluesky-social/atproto#5574, read from permissioned-data-alpha at 79d6307).

Wire changes

  • listRepos entries are {did, repoRev, hash, spaceRev}, in ascending spaceRev order. cursor is an exclusive spaceRev checkpoint: every non-empty page returns its last spaceRev, and an empty page omits it.
  • notifyWrite: rev becomes repoRev. The optional spaceRev/prevSpaceRev are set only on forwards to syncers. New errors: FutureRev (more than 5 minutes ahead) and SpaceNotFound.

Authority sequencing

  • Each accepted write gets a strictly increasing spaceRev: a fresh TID, or one tick past the previous one (the reference's TID.nextStr(prev)).
  • Allocation and the writer update are atomic per space, so a checkpoint can never skip a write that commits later.
    • In memory: a per-space lock, which also covers the listRepos read.
    • EF Core: a transaction claims the next revision with an update conditioned on the last value, retrying on a lost race or a serialization failure.
  • A repoRev at or below the recorded one is ignored and consumes no revision.

Repo hosts and syncers

  • SpaceWriteNotifier.NotifyWriteInBackground coalesces per (space, repo) and retries transient failures (network, timeout, 408/429/5xx) with jittered exponential backoff for RetryWindow (default 1 h).
    • Known gap vs the reference: the queue is in-process. The reference persists it and retries for 24 h. This is documented, including how a host can re-enqueue from its own durable record on startup.
  • SpaceSyncer.NeedsCatchUp checks for a gap via prevSpaceRev, and ListChangedReposAsync walks listRepos from a checkpoint.

Breaking

Covered in the docs/migrating-to-0.8.md Spaces section: the renames, the store interface's SpaceWriteSequence, old DID cursors being invalid, and the EF Core schema change with a sample migration. SpaceRev needs an ordinal collation.

Build has 0 warnings; 5001 unit tests pass, including store-contract tests on both stores (EF on SQLite) and 200 concurrent writers forming one revision chain. Not run against a live alpha PDS.

🤖 Generated with Claude Code

Closes #213. Brings the Spaces sync model to the 2026-10-01 alpha (upstream bluesky-social/atproto#5574, read from `permissioned-data-alpha` at `79d6307`). ## Wire changes - **`listRepos`** entries are `{did, repoRev, hash, spaceRev}`, in ascending `spaceRev` order. `cursor` is an exclusive `spaceRev` checkpoint: every non-empty page returns its last `spaceRev`, and an empty page omits it. - **`notifyWrite`**: `rev` becomes `repoRev`. The optional `spaceRev`/`prevSpaceRev` are set only on forwards to syncers. New errors: `FutureRev` (more than 5 minutes ahead) and `SpaceNotFound`. ## Authority sequencing - Each accepted write gets a strictly increasing `spaceRev`: a fresh TID, or one tick past the previous one (the reference's `TID.nextStr(prev)`). - Allocation and the writer update are atomic per space, so a checkpoint can never skip a write that commits later. - In memory: a per-space lock, which also covers the `listRepos` read. - EF Core: a transaction claims the next revision with an update conditioned on the last value, retrying on a lost race or a serialization failure. - A `repoRev` at or below the recorded one is ignored and consumes no revision. ## Repo hosts and syncers - `SpaceWriteNotifier.NotifyWriteInBackground` coalesces per `(space, repo)` and retries transient failures (network, timeout, 408/429/5xx) with jittered exponential backoff for `RetryWindow` (default 1 h). - **Known gap vs the reference:** the queue is in-process. The reference persists it and retries for 24 h. This is documented, including how a host can re-enqueue from its own durable record on startup. - `SpaceSyncer.NeedsCatchUp` checks for a gap via `prevSpaceRev`, and `ListChangedReposAsync` walks `listRepos` from a checkpoint. ## Breaking Covered in the `docs/migrating-to-0.8.md` Spaces section: the renames, the store interface's `SpaceWriteSequence`, old DID cursors being invalid, and the EF Core schema change with a sample migration. `SpaceRev` needs an ordinal collation. Build has 0 warnings; 5001 unit tests pass, including store-contract tests on both stores (EF on SQLite) and 200 concurrent writers forming one revision chain. Not run against a live alpha PDS. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat!: Spaces repoRev/spaceRev and listRepos checkpoints (closes #213)
All checks were successful
CI / pds-integration (pull_request) Successful in 27s
CI / build-and-test (pull_request) Successful in 53s
f00cecf571
The authority sequences every accepted notifyWrite with a strictly increasing spaceRev and listRepos is ordered by it, with an exclusive spaceRev cursor. notifyWrite carries repoRev (and spaceRev/prevSpaceRev on forwards), answers FutureRev and SpaceNotFound, and ignores a stale repoRev. NotifyWriteInBackground retries with backoff; SpaceSyncer gains NeedsCatchUp and ListChangedReposAsync.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-2026-10-05T13-24-02-896Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/743); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-743.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

💬 Verdict: comment — notes only, nothing blocking.

The sequencing design is sound. Allocation and the writer update are atomic per space, a stale repoRev consumes no revision, the checkpoint paging semantics are consistent across both stores, and docs, changelog and migration notes are thorough. I have two concerns, both about robustness rather than the happy path.

  • Retry catch is too narrow. The EF RecordWriteAsync retry (see the inline comment) may not cover the serialization failures and lock conflicts the PR description says it retries.
  • Old DID cursors fail silently. The listRepos path (inline comment) quietly returns an empty page for a pre-0.8 DID cursor instead of an error.

Minor notes:

  • NotifyWriteInBackground: when a newer revision replaces a pending one during backoff, the worker still sleeps out the remaining delay (up to 5 minutes) before sending it. Waking early on replacement would reduce latency.
  • Not run against a live alpha PDS, as the description says.

🧠 claude-sonnet-5-5 · ⏱ 0m 29s · 3 turns · tokens: 71.8k in / 3.3k out (+93.7k cache-read)
📜 Full session transcript

💬 **Verdict: comment** — notes only, nothing blocking. The sequencing design is sound. Allocation and the writer update are atomic per space, a stale `repoRev` consumes no revision, the checkpoint paging semantics are consistent across both stores, and docs, changelog and migration notes are thorough. I have two concerns, both about robustness rather than the happy path. - **Retry catch is too narrow.** The EF `RecordWriteAsync` retry (see the inline comment) may not cover the serialization failures and lock conflicts the PR description says it retries. - **Old DID cursors fail silently.** The `listRepos` path (inline comment) quietly returns an empty page for a pre-0.8 DID cursor instead of an error. Minor notes: - `NotifyWriteInBackground`: when a newer revision replaces a pending one during backoff, the worker still sleeps out the remaining delay (up to 5 minutes) before sending it. Waking early on replacement would reduce latency. - Not run against a live alpha PDS, as the description says. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 29s · 3 turns · tokens: 71.8k in / 3.3k out (+93.7k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-743.md) <!-- claude-verdict:comment --> <!-- claude-reviewed-head:f00cecf57159f9070afa1ec348a9a9cd4830cd50 -->
@ -201,3 +173,1 @@
if (updated > 0)
return;
catch (DbUpdateException) when (attempt < MaxWriteAttempts)
Collaborator

Only DbUpdateException is retried, but the conditional claims go through ExecuteUpdateAsync, and ExecuteUpdate/ExecuteDelete surface provider exceptions unwrapped (e.g. SqliteException SQLITE_BUSY on a read-then-write upgrade, or a Postgres/SQL Server serialization failure or deadlock). Those would bubble out of RecordWriteAsync rather than retry, despite the description saying it retries on "a serialization failure". Only the SaveChanges unique-key violation is covered. Consider also catching provider transient failures (or DbException with a retryable check), and add a short backoff between attempts, since the current retry is immediate.

Only `DbUpdateException` is retried, but the conditional claims go through `ExecuteUpdateAsync`, and `ExecuteUpdate`/`ExecuteDelete` surface provider exceptions unwrapped (e.g. `SqliteException` SQLITE_BUSY on a read-then-write upgrade, or a Postgres/SQL Server serialization failure or deadlock). Those would bubble out of `RecordWriteAsync` rather than retry, despite the description saying it retries on "a serialization failure". Only the `SaveChanges` unique-key violation is covered. Consider also catching provider transient failures (or `DbException` with a retryable check), and add a short backoff between attempts, since the current retry is immediate.
@ -69,0 +68,4 @@
// later revision without an earlier one, and the cursor would then skip the earlier.
List<KeyValuePair<Did, WriterState>> rows;
lock (state.Sequence)
rows = SpacePaging.After(state.Writers, entry => entry.Value.SpaceRev.Value, cursor, limit);
Collaborator

A pre-0.8 DID cursor (or any non-TID string) is compared as an ordinal string against SpaceRev. "did:…" sorts above every TID, so the result is an empty page with no cursor. A syncer holding an old checkpoint would conclude it is fully caught up and silently miss data. The EF store behaves the same way. Have ListSpaceReposEndpoint validate cursor as a TID and answer InvalidRequest, so stale checkpoints fail loudly instead of looking like "nothing changed".

A pre-0.8 DID cursor (or any non-TID string) is compared as an ordinal string against `SpaceRev`. `"did:…"` sorts above every TID, so the result is an empty page with no cursor. A syncer holding an old checkpoint would conclude it is fully caught up and silently miss data. The EF store behaves the same way. Have `ListSpaceReposEndpoint` validate `cursor` as a TID and answer `InvalidRequest`, so stale checkpoints fail loudly instead of looking like "nothing changed".
Author
Owner

Independent adversarial review (read-only Sonnet reviewer): approve, no defects found. Unit suite: 5001 passed. A throwaway concurrency test ran 40 concurrent RecordWriteAsync calls on file-backed SQLite with a connection per context: each run gave 40 distinct spaceRevs with no lost writes, including the first-write insert race. The per-space lock (in memory) and the conditioned update (EF Core) both stop a reader from seeing N+1 before N. TID monotonicity, cursor exclusivity, the notifyWrite check order and the notifier's bounds all match the reference. Two non-blocking notes: a newer revision doesn't cut short a pending backoff delay (up to ~5 min late), and NeedsCatchUp can trigger a harmless extra catch-up.

Independent adversarial review (read-only Sonnet reviewer): **approve, no defects found**. Unit suite: 5001 passed. A throwaway concurrency test ran 40 concurrent `RecordWriteAsync` calls on file-backed SQLite with a connection per context: each run gave 40 distinct `spaceRev`s with no lost writes, including the first-write insert race. The per-space lock (in memory) and the conditioned update (EF Core) both stop a reader from seeing N+1 before N. TID monotonicity, cursor exclusivity, the `notifyWrite` check order and the notifier's bounds all match the reference. Two non-blocking notes: a newer revision doesn't cut short a pending backoff delay (up to ~5 min late), and `NeedsCatchUp` can trigger a harmless extra catch-up.
opencode-reviewer approved these changes 2026-10-05 13:29:42 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

APPROVE — the 2026-10-01 alpha spaceRev model is implemented carefully and the sequencing guarantees that make listRepos checkpoints safe hold in both stores.

What I verified:

  • Checkpoint paging (SpacePaging.CheckpointPage): every non-empty page carries its last spaceRev, an empty one none; the EF store fetches limit + 1 rows and the in-memory one enumerates the writer dictionary under the sequencing lock, so a page cannot silently include a revision without its predecessors.
  • Sequencing: the in-memory store allocates and updates under a per-space lock; the EF store takes one transaction, claims LastSpaceRev with a conditional ExecuteUpdate, and every loss path (concurrent first insert, lost claim, writer row changed under it, rollback of an uncommitted claim) retries cleanly. A repoRev at or below the recorded one consumes no revision in both.
  • NextSpaceRev: strictly increasing by construction (one tick past prev with the fresh clock id), which is the property the protocol needs.
  • FutureRev check: microsecond skew math and ordering relative to auth/live-space checks are correct.
  • Retry worker: coalescing, deadline restart on newer revisions, backoff capping/jitter, CancellationToken.None for fire-and-forget deliveries, and the lock pattern around _pending are all race-safe. The in-process queue limitation is documented with a workaround.
  • NeedsCatchUp/ListChangedReposAsync: value-equality != via the record operator, checkpoint-resume cursor math, and walk-termination are consistent with the doc text and covered by tests.

Minor notes (non-blocking):

  • The sample migration in docs/spaces.md writes DELETE FROM "AtProtoSpaceWriters" — double-quoted identifiers work on Postgres and SQLite but MySQL's default mode treats "..." as a string literal. Worth a one-line caveat if the EF Core module is meant to be provider-agnostic alongside the collation advice.
  • The PR states the reference is mirrored at a specific commit; I could not independently resolve the upstream TID.nextStr clock-id choice from here, but correctness here depends only on strict monotonicity, which holds.
  • As the PR itself acknowledges, this has not been run against a live alpha PDS — worth a follow-up interop check once feasible.

Test coverage is strong: store-contract tests on both stores, the 200-concurrent-writer chain test, retry/coalescing tests, and the new FutureRev/stale-repoRev endpoint tests.


⏱ 5m 39s · 11 steps · $0.0411 · tokens: 59.8k in / 1.8k out (+427.1k cache-read) · Go: 5h 2% · wk 1% · mo 91%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. APPROVE — the 2026-10-01 alpha `spaceRev` model is implemented carefully and the sequencing guarantees that make `listRepos` checkpoints safe hold in both stores. What I verified: - **Checkpoint paging** (`SpacePaging.CheckpointPage`): every non-empty page carries its last `spaceRev`, an empty one none; the EF store fetches `limit + 1` rows and the in-memory one enumerates the writer dictionary under the sequencing lock, so a page cannot silently include a revision without its predecessors. - **Sequencing**: the in-memory store allocates and updates under a per-space lock; the EF store takes one transaction, claims `LastSpaceRev` with a conditional `ExecuteUpdate`, and every loss path (concurrent first insert, lost claim, writer row changed under it, rollback of an uncommitted claim) retries cleanly. A repoRev at or below the recorded one consumes no revision in both. - **`NextSpaceRev`**: strictly increasing by construction (one tick past `prev` with the fresh clock id), which is the property the protocol needs. - **`FutureRev` check**: microsecond skew math and ordering relative to auth/live-space checks are correct. - **Retry worker**: coalescing, deadline restart on newer revisions, backoff capping/jitter, `CancellationToken.None` for fire-and-forget deliveries, and the lock pattern around `_pending` are all race-safe. The in-process queue limitation is documented with a workaround. - **`NeedsCatchUp`/`ListChangedReposAsync`**: value-equality `!=` via the record operator, checkpoint-resume cursor math, and walk-termination are consistent with the doc text and covered by tests. Minor notes (non-blocking): - The sample migration in `docs/spaces.md` writes `DELETE FROM "AtProtoSpaceWriters"` — double-quoted identifiers work on Postgres and SQLite but MySQL's default mode treats `"..."` as a string literal. Worth a one-line caveat if the EF Core module is meant to be provider-agnostic alongside the collation advice. - The PR states the reference is mirrored at a specific commit; I could not independently resolve the upstream `TID.nextStr` clock-id choice from here, but correctness here depends only on strict monotonicity, which holds. - As the PR itself acknowledges, this has not been run against a live alpha PDS — worth a follow-up interop check once feasible. Test coverage is strong: store-contract tests on both stores, the 200-concurrent-writer chain test, retry/coalescing tests, and the new `FutureRev`/stale-repoRev endpoint tests. --- ⏱ 5m 39s · 11 steps · $0.0411 · tokens: 59.8k in / 1.8k out (+427.1k cache-read) · Go: 5h 2% · wk 1% · mo 91% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-2026-10-05T13-24-02-896Z.md) <!-- opencode-reviewed-head:f00cecf57159f9070afa1ec348a9a9cd4830cd50 --> <!-- opencode-verdict:approve -->
fix: retry provider failures when sequencing a space write, and refuse a non-revision listRepos cursor
Some checks failed
CI / pds-integration (pull_request) Has been cancelled
CI / build-and-test (pull_request) Has been cancelled
d003cf1788
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grandiras force-pushed issue-213-spaces-space-rev from d003cf1788
Some checks failed
CI / pds-integration (pull_request) Has been cancelled
CI / build-and-test (pull_request) Has been cancelled
to 9bf1f03500
All checks were successful
CI / pds-integration (pull_request) Successful in 26s
CI / build-and-test (pull_request) Successful in 52s
Sync Closures to GitHub / sync-closure (pull_request) Successful in 6s
2026-10-05 13:31:59 +00:00
Compare
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-2026-10-05T13-33-00-731Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/744); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-744.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Both points from my last pass are resolved.

  • Retry catch: the EF RecordWriteAsync now catches DbUpdateException and DbException, and waits a short jittered delay before the next attempt. The attempt limit is 8. Provider-level serialization failures and busy-database errors from ExecuteUpdate are now retried.
  • Old DID cursors: ListSpaceReposEndpoint rejects a non-TID cursor with InvalidRequest, and a test covers it. A pre-0.8 checkpoint now fails loudly instead of reading as "nothing changed".

The rest still looks right: allocating the revision and updating the writer are atomic per space, a stale repoRev consumes no revision, and the FutureRev check compares the same units on both sides. The docs, changelog and migration notes match the code.

Non-blocking notes:

  • When a newer revision replaces a pending one during backoff, the worker still sleeps out the remaining delay (up to 5 minutes) before sending it. Waking early would cut latency.
  • The change has not been run against a live alpha PDS, as the description says.

🧠 claude-sonnet-5-5 · ⏱ 0m 7s · 2 turns · tokens: 71.3k in / 763 out (+12.2k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Both points from my last pass are resolved. - **Retry catch:** the EF `RecordWriteAsync` now catches `DbUpdateException` and `DbException`, and waits a short jittered delay before the next attempt. The attempt limit is 8. Provider-level serialization failures and busy-database errors from `ExecuteUpdate` are now retried. - **Old DID cursors:** `ListSpaceReposEndpoint` rejects a non-TID cursor with `InvalidRequest`, and a test covers it. A pre-0.8 checkpoint now fails loudly instead of reading as "nothing changed". The rest still looks right: allocating the revision and updating the writer are atomic per space, a stale `repoRev` consumes no revision, and the `FutureRev` check compares the same units on both sides. The docs, changelog and migration notes match the code. Non-blocking notes: - When a newer revision replaces a pending one during backoff, the worker still sleeps out the remaining delay (up to 5 minutes) before sending it. Waking early would cut latency. - The change has not been run against a live alpha PDS, as the description says. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 7s · 2 turns · tokens: 71.3k in / 763 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-744.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:9bf1f035004d4181d977ed41d78cc1942cf9fdfc -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Both issues from the previous pass are fixed in this head; nothing new blocks merging.

  • EF retry broadened (EfCoreSpaceAuthorityStore.RecordWriteAsync): the catch now covers the provider's DbException as well as DbUpdateException, so ExecuteUpdate-surfaced serialization failures, deadlocks and SQLITE_BUSY retry instead of bubbling out, and retries pause with jitter between attempts instead of hammering. The error paths from the transaction body all roll back through the await using scope before the loop continues.
  • Old DID cursors now fail loudly: ListSpaceReposEndpoint validates the cursor with Tid.TryParse and answers InvalidRequest with a comment explaining why a silent empty page would be worse, and a test pins the endpoint behavior.

Sequencing, CheckpointPage, NextSpaceRev, the FutureRev check, and the notifier retry/coalescing are unchanged since my prior approve and still hold; the contract tests, the 200-writer chain test, and the new cursor/future-rev endpoint tests all cover the changed paths.

Remaining minor notes, non-blocking:

  • The worker in NotifyWriteInBackground still sleeps out the whole backoff delay before noticing a newer revision pending; waking early would shave latency.
  • The sample migration's DELETE FROM "AtProtoSpaceWriters" uses double-quoted identifiers, which are not MySQL's default; a one-line caveat would help if the module stays provider-agnostic.
  • Still not exercised against a live alpha PDS — worth an interop follow-up when possible.

⏱ 7m 12s · 5 steps · $0.0083 · tokens: 10.0k in / 826 out (+188.0k cache-read) · Go: 5h 2% · wk 1% · mo 91%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Both issues from the previous pass are fixed in this head; nothing new blocks merging. - **EF retry broadened** (`EfCoreSpaceAuthorityStore.RecordWriteAsync`): the catch now covers the provider's `DbException` as well as `DbUpdateException`, so `ExecuteUpdate`-surfaced serialization failures, deadlocks and `SQLITE_BUSY` retry instead of bubbling out, and retries pause with jitter between attempts instead of hammering. The error paths from the transaction body all roll back through the `await using` scope before the loop continues. - **Old DID cursors** now fail loudly: `ListSpaceReposEndpoint` validates the cursor with `Tid.TryParse` and answers `InvalidRequest` with a comment explaining why a silent empty page would be worse, and a test pins the endpoint behavior. Sequencing, `CheckpointPage`, `NextSpaceRev`, the `FutureRev` check, and the notifier retry/coalescing are unchanged since my prior approve and still hold; the contract tests, the 200-writer chain test, and the new cursor/future-rev endpoint tests all cover the changed paths. Remaining minor notes, non-blocking: - The worker in `NotifyWriteInBackground` still sleeps out the whole backoff delay before noticing a newer revision pending; waking early would shave latency. - The sample migration's `DELETE FROM "AtProtoSpaceWriters"` uses double-quoted identifiers, which are not MySQL's default; a one-line caveat would help if the module stays provider-agnostic. - Still not exercised against a live alpha PDS — worth an interop follow-up when possible. --- ⏱ 7m 12s · 5 steps · $0.0083 · tokens: 10.0k in / 826 out (+188.0k cache-read) · Go: 5h 2% · wk 1% · mo 91% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-216/run-2026-10-05T13-33-00-731Z.md) <!-- opencode-reviewed-head:9bf1f035004d4181d977ed41d78cc1942cf9fdfc --> <!-- opencode-verdict:approve -->
Grandiras deleted branch issue-213-spaces-space-rev 2026-10-05 13:40:27 +00:00
Sign in to join this conversation.
No description provided.