refactor: debloat the Spaces server, EF Core stores and streaming consumers #208

Merged
Grandiras merged 1 commit from issue-207-spaces-streaming-debloat into main 2026-10-05 12:46:02 +00:00
Owner

Closes #207.

A debloat of src/ATProtoNet.Server/Spaces, src/ATProtoNet.Server.EntityFrameworkCore, src/ATProtoNet/Streaming and Tap, with no behaviour or public API change (PublicAPI.*.txt untouched). EventStreamFrame.cs, which is in #205, is untouched.

  • EF Core stores. A new internal DbContextFactoryExtensions.UseAsync replaces the create-context / await using / query boilerplate at 15 sites. The optional cursor filter in ListReposAsync/ListMembersAsync stays a conditional Where, so the first page's SQL carries no @cursor IS NULL OR … that could defeat the index.
  • Duplicated checks and messages.
    • The service-DID-required message is now SpaceServerOptions.ServiceDidRequired.
    • One RequireHttps helper serves the attestation verifier's two https checks.
    • In RepoSyncVerifier, TryReadCommit/IsStale (bool + out error) become ReadCommit/StaleReason returning string?. The Sync 1.1 checks and their order are unchanged.
  • Braces. Single-statement guard bodies lose their braces, about 25 sites. Primary constructors are used on internal types only (CursorTracker, StreamSocket).

Security-adjacent spots are brace-only: DPoPProof, SpaceCredentialVerifier, SpaceDelegationTokenVerifier, SpaceRequestAuthenticator, SpaceAuthorityEndpoints and SimpleSpaceAccessPolicy.

Net src: about −190 lines. Build has 0 warnings; 4964 tests pass, including the EF stores on SQLite in memory.

🤖 Generated with Claude Code

Closes #207. A debloat of `src/ATProtoNet.Server/Spaces`, `src/ATProtoNet.Server.EntityFrameworkCore`, `src/ATProtoNet/Streaming` and `Tap`, with no behaviour or public API change (`PublicAPI.*.txt` untouched). `EventStreamFrame.cs`, which is in #205, is untouched. - **EF Core stores.** A new internal `DbContextFactoryExtensions.UseAsync` replaces the create-context / `await using` / query boilerplate at 15 sites. The optional cursor filter in `ListReposAsync`/`ListMembersAsync` stays a conditional `Where`, so the first page's SQL carries no `@cursor IS NULL OR …` that could defeat the index. - **Duplicated checks and messages.** - The service-DID-required message is now `SpaceServerOptions.ServiceDidRequired`. - One `RequireHttps` helper serves the attestation verifier's two https checks. - In `RepoSyncVerifier`, `TryReadCommit`/`IsStale` (`bool` + `out error`) become `ReadCommit`/`StaleReason` returning `string?`. The Sync 1.1 checks and their order are unchanged. - **Braces.** Single-statement guard bodies lose their braces, about 25 sites. Primary constructors are used on internal types only (`CursorTracker`, `StreamSocket`). Security-adjacent spots are brace-only: `DPoPProof`, `SpaceCredentialVerifier`, `SpaceDelegationTokenVerifier`, `SpaceRequestAuthenticator`, `SpaceAuthorityEndpoints` and `SimpleSpaceAccessPolicy`. Net `src`: about −190 lines. Build has 0 warnings; 4964 tests pass, including the EF stores on SQLite in memory. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor: debloat the Spaces server, EF Core stores and streaming consumers (closes #207)
All checks were successful
CI / pds-integration (pull_request) Successful in 35s
CI / build-and-test (pull_request) Successful in 5m52s
91f8e20a9a
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

@claude /review

@claude /review
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/733); 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-208/run-733.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-05 11:50:29 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I read the diff but did not build it or run the tests. It looks like a behaviour-preserving cleanup.

  • UseAsync: the helper has the same lifetime and cancellation semantics as the code it replaces. The one new call site that passes CancellationToken.None matches the old token-less CreateDbContextAsync() in the JTI purge.
  • Cursor paging: both pagers still add the cursor filter as a conditional Where, so the first page's SQL is unchanged.
  • RepoSyncVerifier: the checks and their order are the same after TryReadCommit/IsStale became ReadCommit/StaleReason. The out car and out block values are assigned or nulled as before.
  • Merged guards: the combined || guards in the Jetstream parsers still satisfy definite assignment of the pattern variables.
  • RequireHttps: it keeps the same scheme comparison and error messages.
  • Brace removals: the removals in the security-adjacent files are brace-only, and I saw no dangling-else or nested-statement hazard.
  • Primary constructors: CursorTracker and StreamSocket are internal, so the public API is unaffected.

One minor point: SpaceServerOptions.ServiceDidRequired hardcodes SpaceServerOptions.ServiceDid in the string where the old code used nameof, so it will no longer follow a rename of the property. Consider a $"…{nameof(…)}" interpolated const, which C# allows for constant strings.


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

✅ **Verdict: approve** — no blocking issues found. I read the diff but did not build it or run the tests. It looks like a behaviour-preserving cleanup. - **`UseAsync`:** the helper has the same lifetime and cancellation semantics as the code it replaces. The one new call site that passes `CancellationToken.None` matches the old token-less `CreateDbContextAsync()` in the JTI purge. - **Cursor paging:** both pagers still add the cursor filter as a conditional `Where`, so the first page's SQL is unchanged. - **`RepoSyncVerifier`:** the checks and their order are the same after `TryReadCommit`/`IsStale` became `ReadCommit`/`StaleReason`. The `out car` and `out block` values are assigned or nulled as before. - **Merged guards:** the combined `||` guards in the Jetstream parsers still satisfy definite assignment of the pattern variables. - **`RequireHttps`:** it keeps the same scheme comparison and error messages. - **Brace removals:** the removals in the security-adjacent files are brace-only, and I saw no dangling-`else` or nested-statement hazard. - **Primary constructors:** `CursorTracker` and `StreamSocket` are internal, so the public API is unaffected. One minor point: `SpaceServerOptions.ServiceDidRequired` hardcodes `SpaceServerOptions.ServiceDid` in the string where the old code used `nameof`, so it will no longer follow a rename of the property. Consider a `$"…{nameof(…)}"` interpolated `const`, which C# allows for constant strings. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 12s · 2 turns · tokens: 40.4k in / 1.5k out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-733.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:91f8e20a9a1dc79abe88ba764d03687a559b7e58 -->
Collaborator

🤖 Resolving the merge conflicts — the reply will link the full session transcript.

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

🤖 Resolving the merge conflicts — the reply will link the full session transcript. <!-- opencode-run:conflict --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-2026-10-05T12-08-15-671Z.md) — updating every ~5 minutes while the fix runs.
Collaborator

Conflicted file: CHANGELOG.md only.

Cause: both commits appended a bullet right after the same anchor — the Leaner Lexicon clients line under [Unreleased] → ### Changed.

Resolution: kept both bullets, one per line:

  • HEAD's Leaner Spaces server, EF Core stores and streaming consumers … (#207) — the PR's own entry
  • origin/main's Identity fetches use HTTP/2 … (#197)

The two commits touch disjoint file sets (verified with git show --name-only + comm), so no code-side logic could clash and nothing else needed resolution. All 7 other files main had touched (src/ATProtoNet/Identity/*, docs/did-resolution.md, PublicAPI.Unshipped.txt, HandleResolverTests.cs) merged in cleanly from main.

Verified:

  • no <<<<<<< / ======= / >>>>>>> markers left anywhere (remaining grep hits were ==== separator strings inside sample programs, not markers)
  • dotnet build over the whole solution: succeeded, 0 warnings, 0 errors
  • committed as an ordinary merge commit (git commit --no-edit); branch is 2 commits ahead, ready for the daemon to push

→ Merge resolved and pushed; a re-review follows.


⏱ 0m 47s · 9 steps · $0.0112 · tokens: 36.6k in / 1.4k out (+134.7k cache-read) · Go: 5h 0% · wk 0% · mo 91%

📜 Full session transcript

**Conflicted file:** `CHANGELOG.md` only. **Cause:** both commits appended a bullet right after the same anchor — the `Leaner Lexicon clients` line under `[Unreleased] → ### Changed`. **Resolution:** kept both bullets, one per line: - HEAD's `Leaner Spaces server, EF Core stores and streaming consumers … (#207)` — the PR's own entry - `origin/main`'s `Identity fetches use HTTP/2 … (#197)` The two commits touch disjoint file sets (verified with `git show --name-only` + `comm`), so no code-side logic could clash and nothing else needed resolution. All 7 other files main had touched (`src/ATProtoNet/Identity/*`, `docs/did-resolution.md`, `PublicAPI.Unshipped.txt`, `HandleResolverTests.cs`) merged in cleanly from `main`. **Verified:** - no `<<<<<<<` / `=======` / `>>>>>>>` markers left anywhere (remaining grep hits were `====` separator strings inside sample programs, not markers) - `dotnet build` over the whole solution: succeeded, 0 warnings, 0 errors - committed as an ordinary merge commit (`git commit --no-edit`); branch is 2 commits ahead, ready for the daemon to push → Merge resolved and pushed; a re-review follows. --- ⏱ 0m 47s · 9 steps · $0.0112 · tokens: 36.6k in / 1.4k out (+134.7k cache-read) · Go: 5h 0% · wk 0% · mo 91% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-2026-10-05T12-08-15-671Z.md) <!-- opencode-session:ses_ef4090860ffefR1c9h6GjT6nXN -->
Merge remote-tracking branch 'origin/main' into issue-207-spaces-streaming-debloat
All checks were successful
CI / pds-integration (pull_request) Successful in 18m33s
CI / build-and-test (pull_request) Successful in 19m17s
dc7f07c6cb
# Conflicts:
#	CHANGELOG.md
Grandiras force-pushed issue-207-spaces-streaming-debloat from dc7f07c6cb
All checks were successful
CI / pds-integration (pull_request) Successful in 18m33s
CI / build-and-test (pull_request) Successful in 19m17s
to c630fc7a70
All checks were successful
CI / pds-integration (pull_request) Successful in 37s
CI / build-and-test (pull_request) Successful in 1m12s
2026-10-05 12:40:27 +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-208/run-2026-10-05T12-41-29-111Z.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/737); 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-208/run-737.md) — updating every ~5 minutes while the task runs.
opencode-reviewer approved these changes 2026-10-05 12:42:46 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

A faithful debloat with no correctness or behaviour regressions found. Specific checks:

  • DbContextFactoryExtensions.UseAsync preserves the old semantics: context factored with the caller's token, disposed via await using on exception and success alike, and the work lambda gets the same token the old inline code used. All 15 call sites replicate the original queries verbatim, including the conditional cursor Where in ListMembersAsync/ListReposAsync (so the first-page SQL still avoids @cursor IS NULL OR …, as claimed).
  • RepoSyncVerifier: TryReadCommit/IsStale → ReadCommit/StaleReason keep the exact check order (no blocks, size, CAR parse, commit DID, commit rev, future-TID) and messages; all call sites handle the string? result identically.
  • The Jetstream/V2 parser condition merges preserve short-circuit evaluation order; the brace removals around foreach+yield (JetstreamSegmentReader, JetstreamReplayConsumer) are semantically identical.
  • The ServiceDidRequired const matches the previously interpolated message string exactly, and being internal it leaves the public API (and PublicAPI.*.txt) untouched.
  • Primary constructors on CursorTracker/StreamSocket keep the Math.Max(1, persistInterval) clamp and all captured-field logic intact; both types are internal.
  • Security-adjacent files (DPoP, attestation/delegation/credential verifiers, request authenticator, access policy) are brace-only edits; the RequireHttps helper short-circuits exactly like the two checks it replaces.

Minor note, non-blocking: SweepAsync in EfCoreJtiReplayStore switched from an implicit token to explicit CancellationToken.None for the factory and delete — that matches the old behaviour (which passed no token), just spelled out now.


⏱ 1m 18s · 5 steps · $0.0068 · tokens: 27.2k in / 892 out (+68.0k cache-read) · Go: 5h 0% · wk 0% · mo 91%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. A faithful debloat with no correctness or behaviour regressions found. Specific checks: - `DbContextFactoryExtensions.UseAsync` preserves the old semantics: context factored with the caller's token, disposed via `await using` on exception and success alike, and the work lambda gets the same token the old inline code used. All 15 call sites replicate the original queries verbatim, including the conditional cursor `Where` in `ListMembersAsync`/`ListReposAsync` (so the first-page SQL still avoids `@cursor IS NULL OR …`, as claimed). - `RepoSyncVerifier`: `TryReadCommit`/`IsStale` → `ReadCommit`/`StaleReason` keep the exact check order (no blocks, size, CAR parse, commit DID, commit rev, future-TID) and messages; all call sites handle the `string?` result identically. - The Jetstream/V2 parser condition merges preserve short-circuit evaluation order; the brace removals around `foreach`+`yield` (JetstreamSegmentReader, JetstreamReplayConsumer) are semantically identical. - The `ServiceDidRequired` const matches the previously interpolated message string exactly, and being `internal` it leaves the public API (and `PublicAPI.*.txt`) untouched. - Primary constructors on `CursorTracker`/`StreamSocket` keep the `Math.Max(1, persistInterval)` clamp and all captured-field logic intact; both types are internal. - Security-adjacent files (DPoP, attestation/delegation/credential verifiers, request authenticator, access policy) are brace-only edits; the `RequireHttps` helper short-circuits exactly like the two checks it replaces. Minor note, non-blocking: `SweepAsync` in `EfCoreJtiReplayStore` switched from an implicit token to explicit `CancellationToken.None` for the factory and delete — that matches the old behaviour (which passed no token), just spelled out now. --- ⏱ 1m 18s · 5 steps · $0.0068 · tokens: 27.2k in / 892 out (+68.0k cache-read) · Go: 5h 0% · wk 0% · mo 91% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-2026-10-05T12-41-29-111Z.md) <!-- opencode-reviewed-head:c630fc7a70094349a6681c95cb0432bd662ce9fe --> <!-- opencode-verdict:approve -->
claude-reviewer approved these changes 2026-10-05 12:42:48 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I read the diff again but did not build it or run the tests. Nothing blocking turned up, and the change still looks like a behaviour-preserving cleanup.

My earlier note is still open and non-blocking. SpaceServerOptions.ServiceDidRequired still hardcodes SpaceServerOptions.ServiceDid, so it won't follow a rename of the property. A const built as $"…{nameof(…)}" would fix that.

Everything else I checked last time is unchanged:

  • UseAsync keeps the same context lifetime and cancellation behaviour as the code it replaces.
  • Both pagers still add the cursor filter conditionally.
  • The RepoSyncVerifier checks run in the same order.
  • The combined || guards still satisfy definite assignment of the pattern variables.
  • The brace removals in the security-adjacent files are brace-only.
  • The primary constructors are on internal types only.

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

✅ **Verdict: approve** — no blocking issues found. I read the diff again but did not build it or run the tests. Nothing blocking turned up, and the change still looks like a behaviour-preserving cleanup. My earlier note is still open and non-blocking. `SpaceServerOptions.ServiceDidRequired` still hardcodes `SpaceServerOptions.ServiceDid`, so it won't follow a rename of the property. A `const` built as `$"…{nameof(…)}"` would fix that. Everything else I checked last time is unchanged: - `UseAsync` keeps the same context lifetime and cancellation behaviour as the code it replaces. - Both pagers still add the cursor filter conditionally. - The `RepoSyncVerifier` checks run in the same order. - The combined `||` guards still satisfy definite assignment of the pattern variables. - The brace removals in the security-adjacent files are brace-only. - The primary constructors are on internal types only. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 6s · 2 turns · tokens: 41.4k in / 560 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-737.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:c630fc7a70094349a6681c95cb0432bd662ce9fe -->
Grandiras force-pushed issue-207-spaces-streaming-debloat from c630fc7a70
All checks were successful
CI / pds-integration (pull_request) Successful in 37s
CI / build-and-test (pull_request) Successful in 1m12s
to 5660eea196
All checks were successful
CI / pds-integration (pull_request) Successful in 38s
CI / build-and-test (pull_request) Successful in 1m19s
Sync Closures to GitHub / sync-closure (pull_request) Successful in 7s
2026-10-05 12:44:16 +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-208/run-2026-10-05T12-45-17-265Z.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/738); 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-208/run-738.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I re-read the diff but did not build it or run the tests. It still looks like a behaviour-preserving cleanup, and nothing blocks merging.

My earlier note is still open and non-blocking. SpaceServerOptions.ServiceDidRequired hardcodes SpaceServerOptions.ServiceDid where the old messages used nameof, so it won't follow a rename of the property. A const built as $"…{nameof(SpaceServerOptions)}.{nameof(ServiceDid)}…" would fix that, since C# allows interpolated constant strings.

The rest is unchanged from my last pass:

  • UseAsync keeps the same context lifetime and cancellation behaviour as the code it replaces.
  • Both pagers still add the cursor filter conditionally, so the first page's SQL is unchanged.
  • The RepoSyncVerifier checks run in the same order.
  • The merged || guards still satisfy definite assignment of the pattern variables.
  • The brace removals in the security-adjacent files are brace-only.
  • The primary constructors are on internal types only.

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

✅ **Verdict: approve** — no blocking issues found. I re-read the diff but did not build it or run the tests. It still looks like a behaviour-preserving cleanup, and nothing blocks merging. My earlier note is still open and non-blocking. `SpaceServerOptions.ServiceDidRequired` hardcodes `SpaceServerOptions.ServiceDid` where the old messages used `nameof`, so it won't follow a rename of the property. A `const` built as `$"…{nameof(SpaceServerOptions)}.{nameof(ServiceDid)}…"` would fix that, since C# allows interpolated constant strings. The rest is unchanged from my last pass: - `UseAsync` keeps the same context lifetime and cancellation behaviour as the code it replaces. - Both pagers still add the cursor filter conditionally, so the first page's SQL is unchanged. - The `RepoSyncVerifier` checks run in the same order. - The merged `||` guards still satisfy definite assignment of the pattern variables. - The brace removals in the security-adjacent files are brace-only. - The primary constructors are on internal types only. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 4s · 2 turns · tokens: 41.7k in / 566 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-738.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:5660eea1963430a0074c4cbf6f38cefef0869510 -->
Author
Owner

Approval carry-over: this head (5660eea) is a CHANGELOG-only rebase of c630fc7, which opencode-reviewer approved. The PR's patch against its merge base, excluding CHANGELOG.md, has the same git patch-id (6e320e80), CI is green on the new head, and claude-reviewer approved it.

Approval carry-over: this head (5660eea) is a CHANGELOG-only rebase of c630fc7, which opencode-reviewer approved. The PR's patch against its merge base, excluding `CHANGELOG.md`, has the same `git patch-id` (6e320e80), CI is green on the new head, and claude-reviewer approved it.
Grandiras deleted branch issue-207-spaces-streaming-debloat 2026-10-05 12:46:02 +00:00
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Re-review of the current head: no new issues found. Everything I checked last time still holds:

  • DbContextFactoryExtensions.UseAsync keeps the exact lifetime and cancellation semantics of the inline boilerplate (factory token, await using disposal on success and exception, same token handed to the work lambda), and the multi-statement upsert/fallback sites (EfCoreSimpleSpaceStore.PutMemberAsync, EfCoreSpaceAuthorityStore.DeleteSpaceAsync/RegisterNotifyAsync) still dispose the write context before falling through to the second context, matching the old await using (…) { } extents.
  • The cursor pagers still apply the cursor filter conditionally, so the first page's SQL has no @cursor IS NULL OR clause; SpacePaging.Page still receives one past-the-end row.
  • RepoSyncVerifier: the TryReadCommit/IsStale → ReadCommit/StaleReason conversion preserves the check order (no blocks, size limit, CAR parse, commit DID, commit rev, future-TID) and every message; car and block are still force-initialized as null! before the early returns, so all call sites compile and behave the same.
  • The merged guard conditions in the Jetstream parsers satisfy definite assignment of did/time/collection/rkey (each operand is evaluated when the condition is false), preserving short-circuit order.
  • Brace removals across the security-adjacent files (DPoP, attestation/delegation/credential verifiers, authenticator, access policy, authority endpoints) are semantically identical, including the foreach + yield sites in the segment reader and replay consumer.
  • Primary constructors on internal CursorTracker/StreamSocket keep the Math.Max(1, persistInterval) clamp and all captured-field logic; the public API surface (PublicAPI.*.txt) is untouched.
  • SweepAsync's explicit CancellationToken.None matches the previous token-less CreateDbContextAsync()/ExecuteDeleteAsync() behaviour.

The one minor non-blocking note from my earlier passes is still open: SpaceServerOptions.ServiceDidRequired hardcodes SpaceServerOptions.ServiceDid in the literal and won't follow a rename of the property. Since it's an internal const, this is purely rename-robustness polish — a $"…{nameof(SpaceServerOptions.ServiceDid)}." interpolated constant string (valid for const in C# 10+) would make it rename-safe. Not a blocker; fine to land as is.


⏱ 5m 14s · 23 steps · $0.0319 · tokens: 76.6k in / 2.4k out (+580.1k cache-read) · Go: 5h 0% · wk 1% · mo 91%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Re-review of the current head: no new issues found. Everything I checked last time still holds: - `DbContextFactoryExtensions.UseAsync` keeps the exact lifetime and cancellation semantics of the inline boilerplate (factory token, `await using` disposal on success and exception, same token handed to the work lambda), and the multi-statement upsert/fallback sites (`EfCoreSimpleSpaceStore.PutMemberAsync`, `EfCoreSpaceAuthorityStore.DeleteSpaceAsync`/`RegisterNotifyAsync`) still dispose the write context before falling through to the second context, matching the old `await using (…) { }` extents. - The cursor pagers still apply the cursor filter conditionally, so the first page's SQL has no `@cursor IS NULL OR` clause; `SpacePaging.Page` still receives one past-the-end row. - `RepoSyncVerifier`: the `TryReadCommit`/`IsStale` → `ReadCommit`/`StaleReason` conversion preserves the check order (no blocks, size limit, CAR parse, commit DID, commit rev, future-TID) and every message; `car` and `block` are still force-initialized as `null!` before the early returns, so all call sites compile and behave the same. - The merged guard conditions in the Jetstream parsers satisfy definite assignment of `did`/`time`/`collection`/`rkey` (each operand is evaluated when the condition is false), preserving short-circuit order. - Brace removals across the security-adjacent files (DPoP, attestation/delegation/credential verifiers, authenticator, access policy, authority endpoints) are semantically identical, including the `foreach` + `yield` sites in the segment reader and replay consumer. - Primary constructors on internal `CursorTracker`/`StreamSocket` keep the `Math.Max(1, persistInterval)` clamp and all captured-field logic; the public API surface (`PublicAPI.*.txt`) is untouched. - `SweepAsync`'s explicit `CancellationToken.None` matches the previous token-less `CreateDbContextAsync()`/`ExecuteDeleteAsync()` behaviour. The one minor non-blocking note from my earlier passes is still open: `SpaceServerOptions.ServiceDidRequired` hardcodes `SpaceServerOptions.ServiceDid` in the literal and won't follow a rename of the property. Since it's an internal `const`, this is purely rename-robustness polish — a `$"…{nameof(SpaceServerOptions.ServiceDid)}."` interpolated constant string (valid for `const` in C# 10+) would make it rename-safe. Not a blocker; fine to land as is. --- ⏱ 5m 14s · 23 steps · $0.0319 · tokens: 76.6k in / 2.4k out (+580.1k cache-read) · Go: 5h 0% · wk 1% · mo 91% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-208/run-2026-10-05T12-45-17-265Z.md) <!-- opencode-reviewed-head:5660eea1963430a0074c4cbf6f38cefef0869510 --> <!-- opencode-verdict:approve -->
Sign in to join this conversation.
No description provided.