feat(ozone): moderation, report and queue tooling surface #162

Merged
Grandiras merged 1 commit from issue-131-ozone into main 2026-09-25 21:01:35 +00:00
Owner

Closes #131.

The SDK called 24 of the 68 tools.ozone methods. It now calls all 68 (checked against the vendored snapshot: comm of the upstream query/procedure NSIDs and the SDK's call sites is empty). Every new model uses typed identifiers, AtDatetime, IReadOnlyList<T>, LexObject extension data and open unions, and passes the drift test. Its allow-lists gain no exemptions: only three DefOf entries for defs whose names would be poor C# class names (event, option) and nine knownValues locations for the new constants classes, and the ReportReasons entry #123 left for this issue is removed.

tools.ozone.moderation

  • New methods on ModerationClient:
    • GetAccountPreferencesAsync(did): returns the typed app.bsky.actor Preference union.
    • GetReposAsync(dids), GetRecordsAsync(uris), GetSubjectsAsync(subjects), GetAccountTimelineAsync(did) and GetReporterStatsAsync(dids).
    • ScheduleActionAsync(subjects, action, scheduling, createdBy, modTool?), ListScheduledActionsAsync / EnumerateScheduledActionsAsync (a procedure: the cursor goes in the body) and CancelScheduledActionsAsync.
  • New union ModerationSubjectView for getRepos, getRecords and the verification views' subjectRepo / issuerRepo:
    • Variants: RepoViewDetail, RepoViewNotFound, RecordViewDetail, RecordViewNotFound, plus UnknownModerationSubjectView.
    • RepoViewDetail and RecordViewDetail now derive from it instead of directly from LexObject. This adds a class to the hierarchy and is not binary-breaking. getRepo / getRecord still read them without a $type (tested). Serializing one on its own now writes its $type.
    • One base covers every repo/record view variant because modEventViewDetail.subject is the same family (repoView, repoViewNotFound, recordView, recordViewNotFound, convoView). A single class can only belong to one union, so separate bases per endpoint would block typing that field later.
  • The 11 missing event variants on ModEventType: ModEventResolveAppeal, ModEventPriorityScore, AccountEvent, IdentityEvent, RecordEvent, AgeAssuranceEvent, AgeAssuranceOverrideEvent, AgeAssurancePurgeEvent, RevokeAccountCredentialsEvent, ScheduleTakedownEvent and CancelScheduledTakedownEvent. UnknownModEvent stays the fallback.
  • New union ScheduledAction (ScheduledTakedown), and new models SchedulingConfig, ScheduledActionView, ScheduledActionResults / CancellationResults, SubjectView, ReporterStats and TimelineItem(Summary).
  • New constants class ScheduledActionStatus.

tools.ozone.report and tools.ozone.queue

  • client.Ozone.Report covers all 14 methods:
    • Reading reports: GetReportAsync, GetLatestReportAsync, and QueryReportsAsync(status, ReportFilter?) / EnumerateReportsAsync. The required status is a parameter. The 12 optional filters go in a ReportFilter, following the SubjectStatusFilter pattern.
    • CloseReportsAsync and ReassignQueueAsync.
    • Assignment: AssignModeratorAsync, UnassignModeratorAsync, GetAssignmentsAsync / EnumerateAssignmentsAsync.
    • Activities: CreateActivityAsync(reportId, activity, …) and CreateActivityForEventAsync(eventId, activity, …). The Lexicon requires exactly one of reportId or eventId, so two methods replace a runtime check.
    • Listing activities: ListActivitiesAsync / EnumerateReportActivitiesAsync for one report, and QueryActivitiesAsync / EnumerateActivitiesAsync across all reports.
    • Statistics: GetLiveStatsAsync, GetHistoricalStatsAsync / EnumerateHistoricalStatsAsync, and RefreshStatsAsync(DateOnly, DateOnly, …), which takes DateOnly because the wire format is YYYY-MM-DD.
    • Activities form the open ReportActivity union: queue, assignment, escalation, close, reopen and note.
  • client.Ozone.Queue covers all 8 methods: create, update, delete, list/enumerate, RouteReportsAsync, and assign / unassign / get assignments.
    • queue.assignModerator#did is a plain string in the Lexicon. It is typed Did here because it is one.
  • New constants classes: ReportStatus and ReportSubjectType.
  • client.Ozone.Safelink: add, update and remove rules; QueryRulesAsync / EnumerateRulesAsync and QueryEventsAsync / EnumerateEventsAsync. Both queries are procedures, so the cursor goes in the body. Constants: SafelinkPatternType, SafelinkActionType, SafelinkReasonType and SafelinkEventType.
  • client.Ozone.Setting: ListOptionsAsync / EnumerateOptionsAsync, UpsertOptionAsync (the value is a JsonElement) and RemoveOptionsAsync. Constants: SettingScope.
  • client.Ozone.Verification: GrantVerificationsAsync, RevokeVerificationsAsync and ListVerificationsAsync / EnumerateVerificationsAsync.
  • client.Ozone.Hosting: GetAccountHistoryAsync / EnumerateAccountHistoryAsync, with the open AccountHistoryDetails union (AccountCreated, EmailUpdated, EmailConfirmed, PasswordUpdated, HandleUpdated). Constants: AccountHistoryEventType.

ReasonType

  • ReportReasons gains the 40 tools.ozone.report.defs#reason* values, grouped by category.
  • #reasonAppeal and #reasonOther clash with the coarse Appeal and Other, so they are OzoneAppeal and OzoneOther.
  • Each coarse constant's doc names the granular reason upstream prefers.
  • The allow-list entry MissingKnownValues["ReportReasons:tools.ozone.report.defs#*"] is removed.

Fixes to existing event models

  • ModEventMuteReporter.DurationInHours is int?, no longer required int. Upstream makes it optional, and Ozone omits it for a permanent mute. Before this, one such event made QueryEventsAsync / GetEventAsync throw for the whole page. A regression test reads a mute-reporter event without durationInHours. The CHANGELOG has a Fixed bullet for it.
  • ModEventReport.ReportType is required string, no longer string?. This matches the Lexicon, where the field is required. A report event without it is non-conforming and now fails to deserialize, and a test pins that.

Breaking changes

  • ModEventMuteReporter.DurationInHours changes from int to int?. This is a binary and source break for readers. Migration: read the value as nullable, where null means a permanent mute.
  • ModEventReport.ReportType becomes required. This is a source break for object initializers. Migration: set ReportType when constructing a ModEventReport.

Both changes share one CHANGELOG Breaking changes bullet, as the repo's CHANGELOG rule requires for API signature changes.

The two *ViewDetail classes gain an intermediate base class and now serialize their $type. That change is additive and not binary-breaking, so the CHANGELOG notes it under Added.

Verification

  • Unit tests: 3365 passed, 0 failed, 5 skipped, on the branch rebased onto ca43404 (#158).
  • New tests: 93.
    • 76 in four new files: OzoneModerationToolingTests, OzoneReportClientTests, OzoneQueueClientTests and OzoneToolsClientTests.
      • Each of the 43 endpoints has at least one test. It asserts the HTTP method and NSID, the exact query string or JSON body, and the typed response.
      • Each new enumerator has a paging test. For procedures, the test checks that the cursor goes in the body.
      • A theory round-trips all 11 new event variants.
      • Two regression tests cover the event-model fixes: a mute reporter without a duration, and a report event without a type.
    • 8 new rows in OpenUnionTests: 4 new union bases, each checked for an unknown variant and a byte-for-byte round trip.
    • 9 new KnownValueConstants_MatchUpstream rows.
  • Changed tests: OpenUnionTests used accountEvent and identityEvent as its examples of event types the SDK did not model. It now uses a hypothetical #futureEvent.
  • Build: the full solution builds with --no-incremental with 0 warnings and 0 errors.
  • Shared allow-lists:
    • TypedIdentifierGuardTests.Exceptions gains six entries. All are DID-or-AT-URI subjects, an email subject line, or hosting#event.createdBy, which has no format.
    • LexiconDriftTests.Allowlists gains three DefOf entries and nine knownValues locations.
  • Docs:
    • docs/ozone.md gets new sections: Reports, Queues, batch lookups, Scheduled Actions, Settings, URL Safety Rules, Verifications and Account History. Its sub-client and event tables are updated.
    • The docs/api-reference.md Ozone table is updated.

Deferred or flagged

  • ModEventViewDetail.Subject is still a JsonElement. Typing it as ModerationSubjectView also needs the repoView, recordView and convoView variants, and it changes a public property's type. The union is built for that follow-up.
  • The profile views are JsonElement. SubjectView.Profile and VerificationView.SubjectProfile / IssuerProfile stay as JsonElement, because upstream declares these unions with no variants.
  • A cross-area dependency. GetAccountPreferencesResponse uses App.Bsky.Actor.Preference, which is #127's area. It is referenced there, not edited.

🤖 Generated with Claude Code

Closes #131. The SDK called 24 of the 68 `tools.ozone` methods. It now calls all 68 (checked against the vendored snapshot: `comm` of the upstream query/procedure NSIDs and the SDK's call sites is empty). Every new model uses typed identifiers, `AtDatetime`, `IReadOnlyList<T>`, `LexObject` extension data and open unions, and passes the drift test. Its allow-lists gain no exemptions: only three `DefOf` entries for defs whose names would be poor C# class names (`event`, `option`) and nine knownValues locations for the new constants classes, and the `ReportReasons` entry #123 left for this issue is removed. ## `tools.ozone.moderation` - **New methods on `ModerationClient`:** - `GetAccountPreferencesAsync(did)`: returns the typed `app.bsky.actor` `Preference` union. - `GetReposAsync(dids)`, `GetRecordsAsync(uris)`, `GetSubjectsAsync(subjects)`, `GetAccountTimelineAsync(did)` and `GetReporterStatsAsync(dids)`. - `ScheduleActionAsync(subjects, action, scheduling, createdBy, modTool?)`, `ListScheduledActionsAsync` / `EnumerateScheduledActionsAsync` (a procedure: the cursor goes in the body) and `CancelScheduledActionsAsync`. - **New union `ModerationSubjectView`** for `getRepos`, `getRecords` and the verification views' `subjectRepo` / `issuerRepo`: - Variants: `RepoViewDetail`, `RepoViewNotFound`, `RecordViewDetail`, `RecordViewNotFound`, plus `UnknownModerationSubjectView`. - `RepoViewDetail` and `RecordViewDetail` now derive from it instead of directly from `LexObject`. This adds a class to the hierarchy and is not binary-breaking. `getRepo` / `getRecord` still read them without a `$type` (tested). Serializing one on its own now writes its `$type`. - One base covers every repo/record view variant because `modEventViewDetail.subject` is the same family (`repoView`, `repoViewNotFound`, `recordView`, `recordViewNotFound`, `convoView`). A single class can only belong to one union, so separate bases per endpoint would block typing that field later. - **The 11 missing event variants** on `ModEventType`: `ModEventResolveAppeal`, `ModEventPriorityScore`, `AccountEvent`, `IdentityEvent`, `RecordEvent`, `AgeAssuranceEvent`, `AgeAssuranceOverrideEvent`, `AgeAssurancePurgeEvent`, `RevokeAccountCredentialsEvent`, `ScheduleTakedownEvent` and `CancelScheduledTakedownEvent`. `UnknownModEvent` stays the fallback. - **New union** `ScheduledAction` (`ScheduledTakedown`), and **new models** `SchedulingConfig`, `ScheduledActionView`, `ScheduledActionResults` / `CancellationResults`, `SubjectView`, `ReporterStats` and `TimelineItem(Summary)`. - **New constants class** `ScheduledActionStatus`. ## `tools.ozone.report` and `tools.ozone.queue` - **`client.Ozone.Report`** covers all 14 methods: - Reading reports: `GetReportAsync`, `GetLatestReportAsync`, and `QueryReportsAsync(status, ReportFilter?)` / `EnumerateReportsAsync`. The required `status` is a parameter. The 12 optional filters go in a `ReportFilter`, following the `SubjectStatusFilter` pattern. - `CloseReportsAsync` and `ReassignQueueAsync`. - Assignment: `AssignModeratorAsync`, `UnassignModeratorAsync`, `GetAssignmentsAsync` / `EnumerateAssignmentsAsync`. - Activities: `CreateActivityAsync(reportId, activity, …)` and `CreateActivityForEventAsync(eventId, activity, …)`. The Lexicon requires exactly one of `reportId` or `eventId`, so two methods replace a runtime check. - Listing activities: `ListActivitiesAsync` / `EnumerateReportActivitiesAsync` for one report, and `QueryActivitiesAsync` / `EnumerateActivitiesAsync` across all reports. - Statistics: `GetLiveStatsAsync`, `GetHistoricalStatsAsync` / `EnumerateHistoricalStatsAsync`, and `RefreshStatsAsync(DateOnly, DateOnly, …)`, which takes `DateOnly` because the wire format is `YYYY-MM-DD`. - Activities form the open `ReportActivity` union: queue, assignment, escalation, close, reopen and note. - **`client.Ozone.Queue`** covers all 8 methods: create, update, delete, list/enumerate, `RouteReportsAsync`, and assign / unassign / get assignments. - `queue.assignModerator#did` is a plain `string` in the Lexicon. It is typed `Did` here because it is one. - **New constants classes:** `ReportStatus` and `ReportSubjectType`. ## Safelink, setting, verification, hosting - **`client.Ozone.Safelink`:** add, update and remove rules; `QueryRulesAsync` / `EnumerateRulesAsync` and `QueryEventsAsync` / `EnumerateEventsAsync`. Both queries are procedures, so the cursor goes in the body. Constants: `SafelinkPatternType`, `SafelinkActionType`, `SafelinkReasonType` and `SafelinkEventType`. - **`client.Ozone.Setting`:** `ListOptionsAsync` / `EnumerateOptionsAsync`, `UpsertOptionAsync` (the value is a `JsonElement`) and `RemoveOptionsAsync`. Constants: `SettingScope`. - **`client.Ozone.Verification`:** `GrantVerificationsAsync`, `RevokeVerificationsAsync` and `ListVerificationsAsync` / `EnumerateVerificationsAsync`. - **`client.Ozone.Hosting`:** `GetAccountHistoryAsync` / `EnumerateAccountHistoryAsync`, with the open `AccountHistoryDetails` union (`AccountCreated`, `EmailUpdated`, `EmailConfirmed`, `PasswordUpdated`, `HandleUpdated`). Constants: `AccountHistoryEventType`. ## `ReasonType` - `ReportReasons` gains the 40 `tools.ozone.report.defs#reason*` values, grouped by category. - `#reasonAppeal` and `#reasonOther` clash with the coarse `Appeal` and `Other`, so they are `OzoneAppeal` and `OzoneOther`. - Each coarse constant's doc names the granular reason upstream prefers. - The allow-list entry `MissingKnownValues["ReportReasons:tools.ozone.report.defs#*"]` is removed. ## Fixes to existing event models - **`ModEventMuteReporter.DurationInHours` is `int?`, no longer `required int`.** Upstream makes it optional, and Ozone omits it for a permanent mute. Before this, one such event made `QueryEventsAsync` / `GetEventAsync` throw for the whole page. A regression test reads a mute-reporter event without `durationInHours`. The CHANGELOG has a Fixed bullet for it. - **`ModEventReport.ReportType` is `required string`, no longer `string?`.** This matches the Lexicon, where the field is required. A report event without it is non-conforming and now fails to deserialize, and a test pins that. ## Breaking changes - **`ModEventMuteReporter.DurationInHours` changes from `int` to `int?`.** This is a binary and source break for readers. Migration: read the value as nullable, where `null` means a permanent mute. - **`ModEventReport.ReportType` becomes `required`.** This is a source break for object initializers. Migration: set `ReportType` when constructing a `ModEventReport`. Both changes share one CHANGELOG Breaking changes bullet, as the repo's CHANGELOG rule requires for API signature changes. The two `*ViewDetail` classes gain an intermediate base class and now serialize their `$type`. That change is additive and not binary-breaking, so the CHANGELOG notes it under Added. ## Verification - **Unit tests:** 3365 passed, 0 failed, 5 skipped, on the branch rebased onto `ca43404` (#158). - **New tests:** 93. - 76 in four new files: `OzoneModerationToolingTests`, `OzoneReportClientTests`, `OzoneQueueClientTests` and `OzoneToolsClientTests`. - Each of the 43 endpoints has at least one test. It asserts the HTTP method and NSID, the exact query string or JSON body, and the typed response. - Each new enumerator has a paging test. For procedures, the test checks that the cursor goes in the body. - A theory round-trips all 11 new event variants. - Two regression tests cover the event-model fixes: a mute reporter without a duration, and a report event without a type. - 8 new rows in `OpenUnionTests`: 4 new union bases, each checked for an unknown variant and a byte-for-byte round trip. - 9 new `KnownValueConstants_MatchUpstream` rows. - **Changed tests:** `OpenUnionTests` used `accountEvent` and `identityEvent` as its examples of event types the SDK did not model. It now uses a hypothetical `#futureEvent`. - **Build:** the full solution builds with `--no-incremental` with 0 warnings and 0 errors. - **Shared allow-lists:** - `TypedIdentifierGuardTests.Exceptions` gains six entries. All are DID-or-AT-URI subjects, an email subject line, or `hosting#event.createdBy`, which has no format. - `LexiconDriftTests.Allowlists` gains three `DefOf` entries and nine knownValues locations. - **Docs:** - `docs/ozone.md` gets new sections: Reports, Queues, batch lookups, Scheduled Actions, Settings, URL Safety Rules, Verifications and Account History. Its sub-client and event tables are updated. - The `docs/api-reference.md` Ozone table is updated. ## Deferred or flagged - **`ModEventViewDetail.Subject` is still a `JsonElement`.** Typing it as `ModerationSubjectView` also needs the `repoView`, `recordView` and `convoView` variants, and it changes a public property's type. The union is built for that follow-up. - **The profile views are `JsonElement`.** `SubjectView.Profile` and `VerificationView.SubjectProfile` / `IssuerProfile` stay as `JsonElement`, because upstream declares these unions with no variants. - **A cross-area dependency.** `GetAccountPreferencesResponse` uses `App.Bsky.Actor.Preference`, which is #127's area. It is referenced there, not edited. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ozone): report, queue and moderator tooling surface (closes #131)
All checks were successful
CI / pds-integration (pull_request) Successful in 28s
CI / build-and-test (pull_request) Successful in 1m5s
Sync Closures to GitHub / sync-closure (pull_request) Successful in 7s
09a8e5b10e
The SDK covered 24 of the 68 tools.ozone methods. Ozone's report-centric
workflow (reports, custom queues, closeReports), its settings, URL safety
rules, verifications and account history had no client, and eleven event
types Ozone emits read only as UnknownModEvent.

- client.Ozone.Report and client.Ozone.Queue cover all of tools.ozone.report
  (14) and tools.ozone.queue (8); queryReports takes its filters as a
  ReportFilter, like queryStatuses.
- client.Ozone.Setting, Safelink, Verification and Hosting cover the
  remaining tools.ozone namespaces.
- ModerationClient gains the batch lookups, account timeline, reporter
  stats, account preferences and scheduled actions. getRepos, getRecords and
  the verification views return a new open ModerationSubjectView union, of
  which RepoViewDetail and RecordViewDetail are now variants.
- ModEventType gains the eleven missing event variants.
- ReportReasons gains the granular tools.ozone.report.defs#reason* values;
  the drift test's allow-list entry for them is gone.
- ModEventMuteReporter.DurationInHours is optional and ModEventReport.
  ReportType required, as in the Lexicon: a permanent reporter mute has no
  duration, and failed the whole event page it was on.

Every model is typed, open-union and extension-data aware and passes the
upstream drift test; every endpoint has a unit test.

Co-Authored-By: Claude Opus 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-162/run-2026-09-25T20-58-53-562Z.md) — updating every ~5 minutes while the review runs.
Collaborator

⏳ Held on the OpenCode Go window — the 5-hour window is at 0% (the bot holds at 95%).
This continues automatically after the window resets (2026-09-26T01:54:42.152Z) — nothing to re-trigger.

⏳ **Held on the OpenCode Go window** — the 5-hour window is at 0% (the bot holds at 95%). This continues automatically after the window resets (2026-09-26T01:54:42.152Z) — nothing to re-trigger. <!-- opencode-run:paused --> <!-- opencode-session:ses_f25a2d18fffeKVPccjoFhQoIKV -->
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/603); 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-162/run-603.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Large, well-organized surface addition (report/queue/safelink/setting/verification/hosting clients plus moderation lookups, scheduled actions, and new event/reason types). From what's visible in the diff:

  • The two breaking changes (ModEventMuteReporter.DurationInHours → int?, ModEventReport.ReportType → required string) are correctly documented in CHANGELOG under Breaking changes with migration notes, matching CLAUDE.md's rule.
  • RepoViewDetail/RecordViewDetail now derive from the new ModerationSubjectView union base; this is additive (adds a class to the hierarchy, doesn't remove members) and is correctly filed under Added rather than Breaking changes.
  • The query-parameter formatting for AtDatetime? values in the new ReportClient methods (e.g. createdAfter?.ToString()) matches the exact pattern already used in the pre-existing ModerationClient.EnumerateEventsAsync/QueryEventsAsync, so it's consistent with established convention rather than a new inconsistency.
  • The two same-named AssignmentView classes (Tools.Ozone.Report.AssignmentView vs Tools.Ozone.Queue.AssignmentView) live in different namespaces with genuinely different shapes (one keyed by ReportId, the other by Queue), and C# namespace-member resolution means the using ATProtoNet.Lexicon.Tools.Ozone.Queue; import in ReportModels.cs doesn't create ambiguity with the locally-declared AssignmentView — so no actual compile hazard there.
  • CreateActivityAsync/CreateActivityForEventAsync cleanly encode the Lexicon's "exactly one of reportId/eventId" constraint as two separate methods rather than a runtime check, which is a nice simplification.

The diff was truncated before I could see ReportFilter, RefreshStatsRequest (including how DateOnly is (de)serialized to YYYY-MM-DD), and the tail of ReportModels.cs/SafelinkModels.cs/SettingModels.cs/VerificationModels.cs, so I can't fully verify those pieces, and I wasn't able to fetch the branch to check them directly (network access wasn't available in this session). Nothing in the portion I could review looks incorrect, and the PR description's stated verification (93 new tests covering every endpoint, drift tests, allow-list updates, full-solution warning-free build) is consistent with the care shown in the visible code. No blocking issues found.


⏱ 1m 52s · 8 turns · tokens: 85.5k in / 9.4k out (+767.0k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Large, well-organized surface addition (report/queue/safelink/setting/verification/hosting clients plus moderation lookups, scheduled actions, and new event/reason types). From what's visible in the diff: - The two breaking changes (`ModEventMuteReporter.DurationInHours` → `int?`, `ModEventReport.ReportType` → `required string`) are correctly documented in CHANGELOG under Breaking changes with migration notes, matching CLAUDE.md's rule. - `RepoViewDetail`/`RecordViewDetail` now derive from the new `ModerationSubjectView` union base; this is additive (adds a class to the hierarchy, doesn't remove members) and is correctly filed under Added rather than Breaking changes. - The query-parameter formatting for `AtDatetime?` values in the new `ReportClient` methods (e.g. `createdAfter?.ToString()`) matches the exact pattern already used in the pre-existing `ModerationClient.EnumerateEventsAsync`/`QueryEventsAsync`, so it's consistent with established convention rather than a new inconsistency. - The two same-named `AssignmentView` classes (`Tools.Ozone.Report.AssignmentView` vs `Tools.Ozone.Queue.AssignmentView`) live in different namespaces with genuinely different shapes (one keyed by `ReportId`, the other by `Queue`), and C# namespace-member resolution means the `using ATProtoNet.Lexicon.Tools.Ozone.Queue;` import in `ReportModels.cs` doesn't create ambiguity with the locally-declared `AssignmentView` — so no actual compile hazard there. - `CreateActivityAsync`/`CreateActivityForEventAsync` cleanly encode the Lexicon's "exactly one of reportId/eventId" constraint as two separate methods rather than a runtime check, which is a nice simplification. The diff was truncated before I could see `ReportFilter`, `RefreshStatsRequest` (including how `DateOnly` is (de)serialized to `YYYY-MM-DD`), and the tail of `ReportModels.cs`/`SafelinkModels.cs`/`SettingModels.cs`/`VerificationModels.cs`, so I can't fully verify those pieces, and I wasn't able to fetch the branch to check them directly (network access wasn't available in this session). Nothing in the portion I could review looks incorrect, and the PR description's stated verification (93 new tests covering every endpoint, drift tests, allow-list updates, full-solution warning-free build) is consistent with the care shown in the visible code. No blocking issues found. --- ⏱ 1m 52s · 8 turns · tokens: 85.5k in / 9.4k out (+767.0k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-162/run-603.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:09a8e5b10eb301a00308374e4a1b85f0c8f5faeb -->
Grandiras deleted branch issue-131-ozone 2026-09-25 21:01:35 +00:00
Sign in to join this conversation.
No description provided.