Ubisam-MobileGit/tasks/010-work-items-api-contract.md
choiminyeong f6ac20c8ed TASK-063: task records say what is actually open
No task waits on a Codex review; the redesign tasks are confirmed on the
product owner's Z Fold7; TASK-016's leftovers are on hold; no biometric
reauthentication.

Agent: main (opus)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:25:49 +09:00

6.7 KiB

TASK-010: Implement pull-request and issue API contracts

Status: implemented; all required checks pass; no review pending (Codex retired, 2026-09-29) Owner: Claude (ownership transferred by human product owner) Reviewer: Codex

Outcome

Provide frozen read-only domain and data contracts for pull requests, issues, reviews, and comments so Claude can implement TASK-008 without editing shared layers.

Writable paths

  • app/src/main/java/com/ubisam/ubgitea/domain/workitems/
  • app/src/main/java/com/ubisam/ubgitea/data/workitems/
  • app/src/test/java/com/ubisam/ubgitea/domain/workitems/
  • app/src/test/java/com/ubisam/ubgitea/data/workitems/
  • tasks/010-work-items-api-contract.md

Acceptance criteria

  • Pull requests and issues use separate immutable domain models (IssueSummary/IssueDetail vs PullRequestSummary/PullRequestDetail; no shared base type, no shared mutable state).
  • Lists, reviews, and comments have explicit immutable pagination contracts — reuses the existing domain.model.Page<T> (unmodifiable list, explicit nextPage) already used by every other paged endpoint in the app, rather than a new pagination type.
  • Nullable or deleted authors map without crashes or invented identity — WorkItemAuthor is Known(login, displayName) or Unavailable; a null/blank user field maps to Unavailable, never a fabricated name.
  • 401, 403, 404, 422, rate-limit, network, parse, and server failures map to typed results — reuses the existing ApiFailure sealed class (AuthenticationExpired, PermissionDenied, NotFound, Unprocessable, RateLimited, Network, InvalidResponse, Server) rather than a parallel failure hierarchy.
  • Endpoint DTOs remain outside domain and UI packages — all *Dto types are private nested classes of data.workitems.GiteaWorkItemsApi; nothing in domain.workitems references Gson or JSON.
  • Representative JSON and fake-server tests cover lists, reviews, comments, deleted users, pagination, and failures (see Evidence).
  • No create, edit, merge, close, comment, or notification-write endpoint is exposed — GiteaWorkItemsApi only calls HttpTransport.get; confirmed by grep (no put/delete/postForm call anywhere in the file).
  • Evidence records the reviewed contract-freeze commit for TASK-008 — cannot be closed by this task alone; requires Codex's read-only review of this commit first, per the same freeze convention used for TASK-005→006.

Required checks

./gradlew assembleDebug
./gradlew testDebugUnitTest
./gradlew lintDebug

Evidence

Implemented on branch claude/task-010-work-items-contract from commit 525a0aa (latest reviewed Claude work at the time; base contracts unchanged from 0929282).

  • New files (writable scope only): domain/workitems/WorkItemModels.kt, domain/workitems/WorkItemsRepository.kt, data/workitems/GiteaWorkItemsApi.kt, data/workitems/GiteaWorkItemsApiTest.kt.
  • Design: IssueSummary/IssueDetail and PullRequestSummary/PullRequestDetail are separate data classes. PullRequestState is derived from Gitea's merged boolean plus the issue-level open/closed state (merged=true → MERGED; else state maps directly), matching Gitea's actual struct shape (there is no single state enum value that already means "merged"). ReviewState maps five known Gitea values case-insensitively and falls back to OTHER for anything unrecognized instead of failing the whole review list closed — reviews are decorative status, not identity- or safety-critical, so this is intentionally looser than the fail-closed convention used elsewhere (documented in a code comment).
  • Gitea quirk handled: GET /repos/{owner}/{repo}/issues can also return pull requests (a PR is internally an issue with a non-null pull_request field). listIssues drops any entry with that field set, confirmed by a test with a mixed issue+PR response. This is done client-side rather than via a guessed/unverified type= query parameter, so correctness does not depend on an assumption about Gitea's exact filter semantics; the accepted tradeoff (documented in a class-level comment) is that a filtered page can return fewer than pageSize genuine issues.
  • PR/issue discussion comments both use /issues/{number}/comments (Gitea treats PRs as issues internally for this endpoint), confirmed by a test asserting the exact URL path segments from listPullRequestComments.
  • Reused rather than duplicated: domain.model.Page<T> for pagination, core.network.ApiFailure/requireSuccess() for typed failures, data.repositories.BearerTokenProvider for the token-supply contract — no parallel types were introduced for things the app already has.

Test coverage (12 cases in GiteaWorkItemsApiTest): issue list excludes PRs and maps state/author/comments (1), issue detail maps labels/assignees/body (1), deleted-author comment maps to Unavailable without crashing (1), PR state derivation across open/merged/closed (1), PR detail maps base/head/mergeable independently (1), review state known-values-plus-OTHER-fallback (1), PR comments reuse the issue-comments URL (1), malformed required field fails closed (1), missing token fails closed as AuthenticationExpired (1), HTTP status mapping (1), no-next-page-when-under-page-size (1), plus URL/component-encoding assertions folded into the list/comments tests using an owner/repo containing a space and +/#.

Checks run from the new worktree (local.properties created locally, not committed):

./gradlew testDebugUnitTest --tests "com.ubisam.ubgitea.data.workitems.GiteaWorkItemsApiTest"   → BUILD SUCCESSFUL, 12/12 passed
./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks                                → BUILD SUCCESSFUL
  - 128 unit tests across 24 suites, 0 failures/errors
  - 0 lint errors
git diff --check                                                                                  → clean
sensitive-pattern scan of changed files                                                           → no matches

Known limitations:

  • Gitea's exact query-parameter behavior for filtering pull requests out of the issue-list endpoint was not confirmed against live documentation (uncertain across versions); the client-side pull_request-field filter used instead is correct regardless of that behavior, at the cost of potentially-short pages after filtering.
  • No physical device or live Gitea instance was used; all coverage is fake-transport/unit-level, consistent with how TASK-004/009 were verified before their own device/contract-freeze review.

No open P0-P3 findings from self-review against the acceptance criteria. Requesting Codex read-only review and contract-freeze recording so TASK-008 can begin.