Frozen domain/data contracts for issues, pull requests, reviews, and comments so TASK-008's UI can be built without touching shared layers. - Separate immutable IssueSummary/Detail and PullRequestSummary/Detail models; WorkItemAuthor.Unavailable instead of inventing an identity for deleted/null authors. - PullRequestState derived from Gitea's merged flag + open/closed state (no single field already encodes "merged"). - listIssues drops entries with a non-null pull_request field so Gitea's issue-list endpoint (which also returns PRs) never duplicates listPullRequests, done client-side rather than trusting an unverified query parameter. - Reuses existing Page<T> and ApiFailure rather than new parallel types. Read-only: no put/delete/postForm call anywhere in the file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.7 KiB
TASK-010: Implement pull-request and issue API contracts
Status: implemented; all required checks pass; pending Codex review and contract freeze 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/IssueDetailvsPullRequestSummary/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, explicitnextPage) 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 —
WorkItemAuthorisKnown(login, displayName)orUnavailable; a null/blankuserfield maps toUnavailable, never a fabricated name. - 401, 403, 404, 422, rate-limit, network, parse, and server failures map to typed results — reuses the existing
ApiFailuresealed class (AuthenticationExpired,PermissionDenied,NotFound,Unprocessable,RateLimited,Network,InvalidResponse,Server) rather than a parallel failure hierarchy. - Endpoint DTOs remain outside domain and UI packages — all
*Dtotypes areprivatenested classes ofdata.workitems.GiteaWorkItemsApi; nothing indomain.workitemsreferences 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 —
GiteaWorkItemsApionly callsHttpTransport.get; confirmed by grep (noput/delete/postFormcall 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/IssueDetailandPullRequestSummary/PullRequestDetailare separate data classes.PullRequestStateis derived from Gitea'smergedboolean plus the issue-levelopen/closedstate (merged=true→MERGED; elsestatemaps directly), matching Gitea's actual struct shape (there is no singlestateenum value that already means "merged").ReviewStatemaps five known Gitea values case-insensitively and falls back toOTHERfor 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}/issuescan also return pull requests (a PR is internally an issue with a non-nullpull_requestfield).listIssuesdrops 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/unverifiedtype=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 thanpageSizegenuine 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 fromlistPullRequestComments. - Reused rather than duplicated:
domain.model.Page<T>for pagination,core.network.ApiFailure/requireSuccess()for typed failures,data.repositories.BearerTokenProviderfor 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.