{
"issueNumber": 541,
"branchName": "feat/541-atlassian-oauth-auth-provider",
"generatedAt": "2026-04-23T00:00:00Z",
"stories": [
{
"id": "S1",
"title": "Create AtlassianOAuthStateRepo interface + injection token",
"priority": 1,
"dependsOn": [],
"acceptanceCriteria": [
"src/atlassian/atlassian-oauth-state.repo.ts defines AtlassianOAuthStateRepo with loadRefreshToken/saveRefreshToken",
"Exports ATLASSIAN_OAUTH_STATE_REPO injection token"
],
"passes": false,
"completedAt": null
},
{
"id": "S2",
"title": "Implement abstract AtlassianOAuthAuthProvider class",
"priority": 2,
"dependsOn": [
"S1"
],
"acceptanceCriteria": [
"Implements PlatformAuthProvider (tsc passes under strict)",
"Returns cached access token when > 5 min from expiry",
"Preemptively refreshes within 5-min buffer via POST tokenEndpoint with grant_type=refresh_token",
"Concurrent getToken() calls share a single refreshPromise (pattern from github-app-auth.provider.ts)",
"Rotated refresh_token persisted via stateRepo.saveRefreshToken before returning access token",
"Endpoint failure rethrows without mutating cachedToken/tokenExpiresAt",
"HTTP client and state repo are injectable (fetchFn parameter defaults to global fetch)",
"Docstring describes refresh-token rotation invariant",
"getBotUsername is abstract"
],
"passes": false,
"completedAt": null
},
{
"id": "S3",
"title": "Add src/atlassian/index.ts barrel export",
"priority": 3,
"dependsOn": [
"S1",
"S2"
],
"acceptanceCriteria": [
"Re-exports AtlassianOAuthAuthProvider, AtlassianOAuthStateRepo, ATLASSIAN_OAUTH_STATE_REPO"
],
"passes": false,
"completedAt": null
},
{
"id": "S4",
"title": "Add unit tests via a concrete test subclass",
"priority": 4,
"dependsOn": [
"S2"
],
"acceptanceCriteria": [
"Covers cached-token reuse, preemptive refresh within 5-min buffer, concurrent getToken coalescing, rotated-token persistence, endpoint-failure error propagation",
"Uses injected fetchFn + mocked AtlassianOAuthStateRepo (no real network)",
"npm run test -- atlassian-oauth-auth passes",
"npm run lint zero warnings"
],
"passes": false,
"completedAt": null
}
]
}