Skip to content

refactor(api): dedup AI tools/validators/schedulers/auth to ~0% (#243)#404

Merged
thomasluizon merged 2 commits into
mainfrom
chore/api-dedup-to-zero
Jul 14, 2026
Merged

refactor(api): dedup AI tools/validators/schedulers/auth to ~0% (#243)#404
thomasluizon merged 2 commits into
mainfrom
chore/api-dedup-to-zero

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

What

Behavior-preserving duplication burn-down for orbit-api, driving SonarCloud duplicated_lines_density from 1.0% toward ~0% (part of thomasluizon/orbit-ui-mobile#243, "Duplicated code → 0%"). Same outputs, error strings, and execution order throughout.

Each culprit was verified against SonarCloud's authoritative /api/duplications/show before deciding extract vs. exclude.

Extracted (real shared logic)

Duplicate pair Before Shared unit introduced
BulkLog/BulkSkip command validators 66.7% each BulkHabitCommandValidatorBase<TCommand,TItem> + IBulkHabitCommand/IBulkHabitItem
VerifyCode / GoogleAuth auth commands ~12% each AuthUserProvisioning.FindOrCreateUserAsync (race-safe find-or-create)
BulkLog/BulkSkip command handlers ~12% each BulkHabitLoader.LoadHabitsWithRecentLogsAsync
DeleteGoal / UpdateGoal AI tools ~38% / 17% GoalToolHelpers (parse + owner lookup + not-found guard)
GetGoalById / GetGoalDetail queries ~17% / 20% GoalDetailLoader.BuildGoalDetailAsync
Calendar / Notification chat tools ~9% / 7% ChatToolMediator.RunAsync (removed 3 copy-pasted private helpers)
BulkLog/BulkSkip + Log/Skip AI tools up to 50% HabitToolHelpers.BulkHabitActionSchema / SingleHabitDateSchema; Log/Skip reuse existing parse/not-found guards
ChatController ProcessChat / ProcessChatStream 42 lines ParseChatInputsAsync
MCP HabitTools BulkLog/BulkSkip 38 lines ExecuteBulkHabitOperationAsync

GoogleAuth keeps its inside-flow welcome email (now fired on IsNew); VerifyCode keeps its caller-side welcome. Orphaned usings removed as they fell out.

Justifiably excluded (sonar.cpd.exclusions, rationale inline in the workflow)

  • CreateHabitTool / UpdateHabitTool — declarative LLM-facing JSON parameter schemas: overlapping habit fields but per-tool field sets and per-tool descriptions in a single anonymous object that cannot be composed from shared fragments without leaving CPD-flagged mirror call sites or degrading readability of the tool contracts.
  • AgentCatalogService.UserDataCatalog — declarative user-data catalog of unique rows that share a UserDataFieldDescriptor constructor shape (parity with the file's existing S107/S1192/CA1861 suppressions).
  • Slip / ProactiveCheckin schedulers — already share ScheduledServiceBase; residual dup is per-class [LoggerMessage] source-generator partial-method boilerplate the compiler requires in each class.

Verification (all foreground)

  • dotnet build Orbit.slnx — 0 errors (warnings 10 → 7, from removed orphaned usings)
  • dotnet test Orbit.slnx5454 passed, 0 failed
  • dotnet ef migrations has-pending-model-changesfalse (no entity/model touched)
  • src/Orbit.Api/openapi.jsonunchanged (checksum identical)
  • No bare // narration added (ORBIT0001 clean)

Before / after duplication

Before: duplicated_lines_density = 1.0% (project). This PR removes the extracted pairs from the count and excludes the three irreducible mirrors; the PR's SonarCloud run should show the drop.

Refs thomasluizon/orbit-ui-mobile#243 (duplication → 0)

Behavior-preserving duplication burn-down for orbit-api (part of #243,
"Duplicated code → 0%"). Same outputs, error strings, and ordering
throughout; verified by the full unit suite (5454 green).

Extracted shared logic:
- Bulk validators: BulkLog/BulkSkip command validators (66.7% dup each)
  now derive from BulkHabitCommandValidatorBase<TCommand,TItem> over new
  IBulkHabitCommand/IBulkHabitItem marker interfaces.
- Auth commands: VerifyCode/GoogleAuth share AuthUserProvisioning
  .FindOrCreateUserAsync (race-safe find-or-create); GoogleAuth keeps its
  welcome-email side effect, VerifyCode its caller-side welcome.
- Bulk commands: BulkLog/BulkSkip handlers share BulkHabitLoader
  .LoadHabitsWithRecentLogsAsync (habit map + windowed logs).
- Goal tools: DeleteGoal/UpdateGoal share GoalToolHelpers (goal-id parse,
  owner-scoped lookup, not-found guard), mirroring HabitToolHelpers.
- Goal queries: GetGoalById/GetGoalDetail share GoalDetailLoader
  .BuildGoalDetailAsync (load + project GoalDetailDto).
- Chat tools: Calendar/Notification tools share ChatToolMediator.RunAsync
  (the duplicated mediator-run-to-ToolResult helper).
- AI habit tools: Bulk/Log/Skip tools share HabitToolHelpers schema
  builders; Log/Skip reuse the existing parse/not-found guards.
- ChatController: ProcessChat/ProcessChatStream share ParseChatInputsAsync.
- MCP HabitTools: BulkLog/BulkSkip share ExecuteBulkHabitOperationAsync.

Justifiably excluded (sonar.cpd.exclusions, rationale in workflow):
- CreateHabitTool/UpdateHabitTool: declarative LLM-facing JSON parameter
  schemas — overlapping fields but per-tool field sets and descriptions in
  a single anonymous object that cannot be composed without CPD-flagged
  mirror call sites or worse readability.
- AgentCatalogService.UserDataCatalog: declarative user-data catalog of
  unique rows sharing a constructor shape (parity with its existing
  S107/S1192/CA1861 suppressions).
- Slip/ProactiveCheckin schedulers: residual dup is per-class
  [LoggerMessage] source-generator partial-method boilerplate.

Refs thomasluizon/orbit-ui-mobile#243 (duplication → 0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #404 — thomasluizon/orbit-api

Recommendation: APPROVE

Summary

This is a behavior-preserving duplication burn-down: 27 files touched, extracting shared logic into 7 new internal helper types (AuthUserProvisioning, ChatToolMediator, GoalToolHelpers, GoalDetailLoader, BulkHabitLoader, IBulkHabitCommand/IBulkHabitItem, BulkHabitCommandValidatorBase) and updating call sites in auth commands, AI/MCP tools, goal queries, and bulk habit commands/validators. Every changed file was read in full (not just diff hunks) and each extraction diffed against the pre-refactor code it replaces. Owner-scoping predicates (UserId/goalId/habitId filters), the race-safe unique-violation retry in the new user-provisioning path, validation rules (empty/size-cap/per-item checks), and JSON tool schemas are all reproduced exactly, just parameterized. No endpoint routes, DTOs, MCP tool names, or [Authorize] annotations changed.

Findings

Critical

None.

High

None.

Medium

None.

Low / Info

[Info] New internal helper classes have no direct unit tests, but are already exercised end-to-end through existing handler/tool tests
· dimension: Backend hard rules (#13, Tests)
· location: src/Orbit.Application/Auth/Commands/AuthUserProvisioning.cs, src/Orbit.Application/Goals/Queries/GoalDetailLoader.cs, src/Orbit.Application/Habits/Commands/BulkHabitLoader.cs, src/Orbit.Application/Chat/Tools/Implementations/ChatToolMediator.cs, GoalToolHelpers.cs
· issue: These are pure extractions of previously-tested code into shared internal statics; no new behavior was introduced.
· risk: None currently — GoogleAuthCommandHandlerTests.cs:162 and VerifyCodeCommandHandlerTests.cs:143 already cover the DbUpdateException unique-violation race path (both construct a FakeUniqueViolationException and assert the retry), and the goal/bulk-habit/tool tests cover the rest indirectly. PR body confirms 5454/5454 tests still pass.
· fix: No action required now; if these helpers grow additional branches later, add direct unit tests at that point rather than relying only on caller coverage.
· reference: tests/CLAUDE.md "Every new feature needs... unit tests"

Subagents

Agent Verdict
security-reviewer PASS — reviewed extraction of AuthUserProvisioning.FindOrCreateUserAsync, GoalToolHelpers.FindGoalAsync, BulkHabitLoader.LoadHabitsWithRecentLogsAsync, BulkHabitCommandValidatorBase, ChatController.ParseChatInputsAsync, and HabitTools.ExecuteBulkHabitOperationAsync. Owner-scoping predicates, the DbUpdateException unique-violation retry, [Authorize]/rate-limit/size-limit attributes, and validator rules all preserved exactly. No new logging of secrets/PII, no new raw SQL. No findings.
contract-aligner N/A — no DTO, Controller route, or packages/shared type changed (internal refactor only; MCP tool names bulk_log_habits/bulk_skip_habits unchanged, ChatController route unchanged). Not verifiable in CI (orbit-ui-mobile not checked out), but diff scope confirms no consumer-facing surface changed.

Validation

Check Result
Build (dotnet) N/A — CI runs Build as a separate required check; PR description reports dotnet build Orbit.slnx 0 errors
Tests (dotnet) N/A — CI runs Tests as a separate required check; PR description reports 5454 passed, 0 failed

Deferred — N/A dimensions & files not verdicted

  • Dimension 8 (DESIGN.md/AI-slop): N/A — no apps/* UI files in this diff (backend-only repo).
  • Dimension 9 (Parity) / Dimension 10 (i18n): N/A — frontend-only dimensions owned by the orbit-ui-mobile side; this diff has no mobile/web surface.
  • Dimension 14 (FEATURES.md parity): N/A — pure refactor, no user-facing feature added/changed/removed; all AI-tool and MCP-tool names, descriptions, and schemas are byte-identical to before.
  • Cross-model /second-opinion step: not triggered — no Critical findings survived to invoke it.
  • All 27 changed files were read in full and given a verdict; nothing was skipped for size.

What's good

  • Each extraction was verified against SonarCloud's /api/duplications/show per the PR body, and the sonar.cpd.exclusions additions are all real, justified files (verified: CreateHabitTool.cs, UpdateHabitTool.cs, AgentCatalogService.UserDataCatalog.cs, SlipAlertSchedulerService.cs, ProactiveCheckinSchedulerService.cs all exist).
  • AuthUserProvisioning cleanly merges two previously near-identical, separately race-safe implementations while preserving each caller's behavioral difference via parameters (GoogleAuth's inside-flow welcome email fired on IsNew vs VerifyCode's caller-side one and email.Split('@')[0] name derivation).
  • IBulkHabitCommand<TItem>/IBulkHabitItem is a minimal, well-scoped generic contract — exactly the "third real use" bar the DRY rule asks for.
  • No narration comments introduced; new types carry proper /////** */ XML-doc.

Recommendation

Approve as-is. Low-risk, thoroughly self-verified refactor with no behavior, contract, or security changes detected.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #404 — thomasluizon/orbit-api

Recommendation: APPROVE

Summary

This PR's own content diff (27 files, the behavior-preserving SonarCloud duplication burn-down) is byte-for-byte identical to what was already reviewed in a prior pass, which reported no Critical/High findings. The only new commit since then, 965620c (merge from main), merges in unrelated main-branch commits (PR #403's StripeRetryPolicy/HabitScheduleService bounded-query fix) and does not touch this PR's own diff surface.

Findings

Critical

None.

High

None.

Medium

None.

Low / Info

The new internal helper types (AuthUserProvisioning, GoalDetailLoader, BulkHabitLoader, ChatToolMediator, GoalToolHelpers) have no direct unit tests but are pure extractions already exercised end-to-end via existing handler/tool tests (5454/5454 passing per PR body). No action required.

Subagents

Agent Verdict
security-reviewer PASS (from prior full pass; diff unchanged)
contract-aligner N/A — no DTO/route/contract surface touched

Validation

Check Result
Build (dotnet) N/A — CI runs as separate required check
Tests (dotnet) N/A — CI runs as separate required check

Deferred — N/A dimensions

  • Cross-repo dimensions (contract drift, mobile-side contract-aligner) — not verifiable in this CI job; sibling orbit-ui-mobile repo is not checked out. No contract surface touched anyway.
  • Parity / i18n / FEATURES.md — N/A, backend-only internal refactor.

What's good

Owner-scoping predicates, the race-safe unique-violation retry, validator rules, and JSON tool schemas were reproduced exactly across all seven extractions, with zero route/DTO/[Authorize] changes.

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit b7b5142 into main Jul 14, 2026
19 checks passed
@thomasluizon
thomasluizon deleted the chore/api-dedup-to-zero branch July 14, 2026 17:19
thomasluizon added a commit that referenced this pull request Jul 14, 2026
Drives orbit-api SonarCloud duplicated_lines_density 0.1% -> 0.0% (#404
deferred these files to avoid a concurrent-edit conflict).

Real extractions (genuinely-shared logic):
- HabitScheduleService: the identical active-days filter + frequency-unit
  switch tail of IsHabitDueOnDate and IsHabitHistoricallyDueOnDate is
  extracted into one private MatchesFrequency helper (self-dup ~24 lines).
- LevelDefinitions.SyncLevel(User): the GetLevelForXp + SetLevel level-sync
  idiom, previously copy-pasted in 6 places (both accept commands, SendCheer,
  ChallengeProgressService, JoinChallenge, and GamificationService.UpdateLevel),
  is unified into one canonical helper; UpdateLevel is deleted.

Justified exclusion (irreducible parallel mirror):
- AcceptAccountabilityPairCommand / AcceptFriendRequestCommand: after the
  shared level-sync is extracted, the only residual is the
  BuildRequesterNotification parallel mirror -- a null-guard + isPortuguese
  ternary skeleton emitting DISTINCT localized copy for two independent accept
  events in separate bounded contexts. CPD normalizes the differing string
  literals so the skeletons match; a shared builder would couple the two
  contexts without improving readability. Added to sonar.cpd.exclusions with
  rationale, consistent with the existing #404 exclusions.

Behavior-preserving: build 0 errors, all tests green, no EF model changes,
openapi.json unchanged.

Refs thomasluizon/orbit-ui-mobile#243 (duplication -> 0)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant