@gitkraken/core-gitlens 0.22.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/plus/integrations/index.d.ts +3 -3
  3. package/dist/plus/integrations/index.js.map +1 -1
  4. package/dist/plus/integrations/integrationService.d.ts +10 -2
  5. package/dist/plus/integrations/integrationService.js +9 -1
  6. package/dist/plus/integrations/integrationService.js.map +1 -1
  7. package/dist/plus/integrations/manager.d.ts +25 -1
  8. package/dist/plus/integrations/models/issueReads.d.ts +5 -0
  9. package/dist/plus/integrations/providers/linear.d.ts +39 -6
  10. package/dist/plus/integrations/providers/linear.js +129 -15
  11. package/dist/plus/integrations/providers/linear.js.map +1 -1
  12. package/dist/plus/integrations/providers/models.d.ts +4 -1
  13. package/dist/plus/integrations/providers/models.js.map +1 -1
  14. package/dist/plus/integrations/providers/providersApi.d.ts +9 -1
  15. package/dist/plus/integrations/providers/providersApi.js +8 -0
  16. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  17. package/dist/plus/integrations/reads/cursors.d.ts +8 -0
  18. package/dist/plus/integrations/reads/cursors.js +14 -4
  19. package/dist/plus/integrations/reads/cursors.js.map +1 -1
  20. package/dist/plus/integrations/reads/hierarchy.d.ts +8 -2
  21. package/dist/plus/integrations/reads/hierarchy.js +42 -2
  22. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  23. package/dist/plus/integrations/reads/issueTracker.d.ts +14 -1
  24. package/dist/plus/integrations/reads/issueTracker.js +46 -5
  25. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  26. package/dist/plus/integrations/reads/selectionConfirmation.d.ts +5 -3
  27. package/dist/plus/integrations/reads/selectionConfirmation.js.map +1 -1
  28. package/dist/plus/integrations/reads/warnings.js +1 -1
  29. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  30. package/dist/plus/integrations/results.d.ts +18 -2
  31. package/dist/plus/integrations/results.js.map +1 -1
  32. package/dist/plus/integrations/utils/integration.utils.d.ts +3 -0
  33. package/dist/plus/integrations/utils/integration.utils.js +5 -1
  34. package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
  35. package/docs/integrations.md +41 -22
  36. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata, CollectionScope } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { toError } from '../errors.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { ProjectIssuesDrain, ProjectIssuesRecovery } from '../models/issueReads.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning, ProviderWarningSelector } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { discoverResources, runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, selectorMatches } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport type { SelectionTarget } from './selectionConfirmation.js';\nimport { confirmCredential, confirmSelection, toSelectionAction } from './selectionConfirmation.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tprojectCutWarning,\n\tunmatchedSelectorWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\treturn readIssueTrackerIssuesPage(ctx, options, false);\n}\n\n/**\n * {@link listIssueTrackerIssuesPage}. `refreshed` marks the one read made again after a selection's confirmation\n * found the cached discovery stale and dropped it, so this read's discovery is fresh and no further one is made.\n */\nasync function readIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: Parameters<typeof listIssueTrackerIssuesPage>[1],\n\trefreshed: boolean,\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\t// A known state this tracker can't express is an unsupported capability; a value outside the vocabulary is\n\t\t// caller input the caller fixes in the request.\n\t\twarnings.push(\n\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t)\n\t\t\t\t: otherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tawait integration.getCurrentAccount({ connectionId: options.connectionId }).catch(() => undefined);\n\n\tconst {\n\t\tvalue: resources,\n\t\twarning: resourcesWarning,\n\t\tmetadata: resourcesMetadata,\n\t} = await discoverResources(integration, options.providerId, domain, options.connectionId);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tconst scopedResources =\n\t\toptions.org != null\n\t\t\t? (resources ?? []).filter(r => selectorMatches(options.providerId, r, options.org!))\n\t\t\t: (resources ?? []);\n\t// Only a complete discovery proves a selection gone: a failed one may just not have reached it, and no resource\n\t// list without a warning is no session.\n\tconst resourceDiscoveryComplete = resources != null && resourcesWarning == null;\n\tconst selectionTarget: SelectionTarget = {\n\t\tintegration: integration,\n\t\tproviderId: options.providerId,\n\t\tdomain: domain,\n\t\tconnectionId: options.connectionId,\n\t\torg: options.org,\n\t\tproject: options.project,\n\t};\n\tconst unconfirmed = (reasons: readonly ProviderWarning[]): ProviderPagedResult<IssueShape> => {\n\t\tif (reasons.length === 0) {\n\t\t\twarnings.push(\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`Could not confirm '${options.project ?? options.org}' is gone, so it is not reported as removed.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tfor (const warning of reasons) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\treturn emptyPage(true, false, discoveryFailureRetry());\n\t};\n\t/**\n\t * Settles a selection the cached discovery matches nothing for from the provider's lists as they are now (see\n\t * {@link confirmSelection} and {@link toSelectionAction}). The cached lists can predate what was created since, so\n\t * a stale one is dropped and the read made once more over fresh lists; that read confirms the same way, since its\n\t * own discovery can still be joined to one started before the drop.\n\t */\n\tconst missSelection = async (selector: ProviderWarningSelector): Promise<ProviderPagedResult<IssueShape>> => {\n\t\tconst next = toSelectionAction(await confirmSelection(selectionTarget, selector.kind), refreshed);\n\t\tswitch (next.action) {\n\t\t\tcase 'unconfirmed':\n\t\t\t\treturn unconfirmed(next.warnings);\n\t\t\tcase 'refresh':\n\t\t\t\tintegration.invalidateDiscoveryCaches();\n\t\t\t\t// The caches are dropped already: the refreshed read needs no second session sync.\n\t\t\t\treturn readIssueTrackerIssuesPage(ctx, { ...options, forceSync: false }, true);\n\t\t\tcase 'report-missing': {\n\t\t\t\tconst credential = await confirmCredential(selectionTarget, next.resource);\n\t\t\t\tif (!credential.confirmed) {\n\t\t\t\t\treturn unconfirmed(credential.warning != null ? [credential.warning] : []);\n\t\t\t\t}\n\n\t\t\t\t// What the lists miss now: a selected org that is gone misses the org, whichever the cache missed.\n\t\t\t\tconst missed: ProviderWarningSelector =\n\t\t\t\t\tnext.missed === 'org' && options.org != null ? { kind: 'org', value: options.org } : selector;\n\t\t\t\twarnings.push(\n\t\t\t\t\tunmatchedSelectorWarning(options.providerId, domain, options.connectionId, missed, options.org),\n\t\t\t\t);\n\t\t\t\treturn emptyPage();\n\t\t\t}\n\t\t}\n\t};\n\t// Checked before the empty return, since an account with no resources left misses the selection too.\n\tif (options.org != null && resourceDiscoveryComplete && scopedResources.length === 0) {\n\t\treturn missSelection({ kind: 'org', value: options.org });\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tif (options.project != null && resourceDiscoveryComplete) {\n\t\t\treturn missSelection({ kind: 'project', value: options.project });\n\t\t}\n\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\tconst { truncated } = mergeAssessmentInto(\n\t\t\twarnings,\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\tresourcesMetadata,\n\t\t);\n\t\treturn emptyPage(fetchFailed, truncated, fetchFailed || truncated ? discoveryFailureRetry() : undefined);\n\t}\n\t// Only a partial discovery, reported by its warning, reaches here without the selected resource.\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tconst matchedProjects =\n\t\toptions.project != null\n\t\t\t? (projects ?? []).filter(p => selectorMatches(options.providerId, p, options.project!))\n\t\t\t: (projects ?? []);\n\tconst projectDiscoveryComplete =\n\t\tresourceDiscoveryComplete && projectsWarning == null && !projectDiscoveryFailed && !projectDiscoveryTruncated;\n\tif (options.project != null && projects != null && projectDiscoveryComplete && matchedProjects.length === 0) {\n\t\treturn missSelection({ kind: 'project', value: options.project });\n\t}\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// One call for the whole window, so a tracker that can search several projects at once (Jira) does, instead\n\t// of paying one drain per project. Results still come back per project, which the accounting below needs.\n\tlet projectReads: IntegrationResult<ProjectIssuesDrain | undefined>[];\n\ttry {\n\t\tprojectReads = await integration.getIssuesForProjectsWithTruncationResult(\n\t\t\tscopedProjects.map(project => ({\n\t\t\t\tproject: project,\n\t\t\t\toptions: {\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t},\n\t\t\t})),\n\t\t\toptions.connectionId,\n\t\t);\n\t} catch (ex) {\n\t\tconst error = toError(ex);\n\t\tprojectReads = scopedProjects.map(() => ({ error: error }));\n\t}\n\tconst perProject = await Promise.all(\n\t\tscopedProjects.map(async (project, index) => ({\n\t\t\tproject: project,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tPromise.resolve(projectReads[index]),\n\t\t\t)),\n\t\t})),\n\t);\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\t// Whether a request failed, which `fetchFailed` alone doesn't say: see `assessCollectionMetadata`'s `requestFailed`.\n\tlet requestFailed = fetchFailed;\n\tlet truncated = projectDiscoveryTruncated;\n\t// The projects whose read stopped short with nothing in its metadata to say why. One whose provider names its scope\n\t// is reported on its own, even when the read also failed: a project's cut must not hide behind another's failure,\n\t// which may be no more than an issue it skipped (#6011), nor its recovery behind another project's.\n\tconst cuts: { project: ResourceDescriptor; recovery: ProjectIssuesRecovery; scope: CollectionScope }[] = [];\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\trequestFailed = true;\n\t\t\ttruncated = true;\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t\trequestFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\ttruncated = true;\n\t\t\t\tif (result.scope != null && !result.metadata?.failures?.length && !result.metadata?.omissions?.length) {\n\t\t\t\t\tcuts.push({ project: project, recovery: result.recovery, scope: result.scope });\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// The truncation the projects reported already gets a structured warning below. Replace metadata's unstructured\n\t\t// fallback, the only warning it raises when nothing failed and nothing was omitted, while retaining failures and\n\t\t// omissions.\n\t\tif (truncated && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\trequestFailed = requestFailed || drainAssessment.requestFailed;\n\ttruncated = truncated || drainAssessment.truncated;\n\n\tfor (const { project, recovery, scope } of cuts) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tprojectCutWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tresourceLabel(project),\n\t\t\t\trecovery,\n\t\t\t\tscope,\n\t\t\t),\n\t\t);\n\t}\n\t// A truncation no project's own warning accounts for (a project list read short, or a cut from a provider that names\n\t// no scope for its projects) is reported for the read as a whole, unless a failure or an omission explains it. On\n\t// `fetchFailed`, not `requestFailed`: an issue left unread truncates the read too, and its own warning explains that.\n\t// A project list read short publishes its own warning, so it is never left to an unreadable issue's alone.\n\tif (!fetchFailed && truncated && !warnings.some(warning => warning.omission != null)) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\t'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, requestFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: truncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\trequestFailed: requestFailed,\n\t\tunexpectedGap: nextPage != null && cursor == null,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata, CollectionScope } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { IssuesCloudHostIntegrationId, providerFanOutConcurrency } from '../constants.js';\nimport { toError } from '../errors.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { ProjectIssuesDrain, ProjectIssuesRecovery } from '../models/issueReads.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport type { LinearIntegration } from '../providers/linear.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning, ProviderWarningSelector } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId, isLinearIntegration } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { linearProjectIds, parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { discoverResources, runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, selectorMatches } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport type { SelectionTarget, TrackerSelectorKind } from './selectionConfirmation.js';\nimport { confirmCredential, confirmSelection, toSelectionAction } from './selectionConfirmation.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tprojectCutWarning,\n\tunmatchedSelectorWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/**\n\t\t * Broadens the read to every assignee. Scopes to the user's issues when omitted: those assigned to them, and on\n\t\t * Linear also the GitHub-synced issues they created (unless `filters` asks for `Assignee` by name).\n\t\t */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\t/**\n\t\t * Linear only: narrows the read to issues in any of these Linear Projects (Linear's `Project` entity, by id).\n\t\t *\n\t\t * A narrowing of each team's read, not a scope of its own: the read is still windowed and selected by team\n\t\t * (`org`/`project`), and each team reports the issues in these Projects, server-side, so they don't count\n\t\t * against its page backstop. A Linear Project spans teams, so a read of every team is what reads a Project\n\t\t * whole. Refused by every other tracker rather than ignored, since ignoring it would publish the whole project.\n\t\t * Recorded in a composite cursor and checked on resume, as `state` is.\n\t\t */\n\t\tlinearProjects?: string[];\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\treturn readIssueTrackerIssuesPage(ctx, options, false);\n}\n\n/**\n * {@link listIssueTrackerIssuesPage}. `refreshed` marks the one read made again after a selection's confirmation\n * found the cached discovery stale and dropped it, so this read's discovery is fresh and no further one is made.\n */\nasync function readIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: Parameters<typeof listIssueTrackerIssuesPage>[1],\n\trefreshed: boolean,\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\tconst linearProjects = linearProjectIds(options.linearProjects);\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t\tlinearProjects: linearProjects,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\t// A known state this tracker can't express is an unsupported capability; a value outside the vocabulary is\n\t\t// caller input the caller fixes in the request.\n\t\twarnings.push(\n\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t)\n\t\t\t\t: otherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Refused before any request like the state. A value that is not a list of non-empty ids is caller input, refused\n\t// rather than dropped, since dropping it (or its last entry) widens the read to every issue; another tracker would\n\t// ignore it and publish its whole project.\n\tif (options.linearProjects != null) {\n\t\tif (\n\t\t\t!Array.isArray(options.linearProjects) ||\n\t\t\toptions.linearProjects.some(id => typeof id !== 'string' || id.length === 0)\n\t\t) {\n\t\t\twarnings.push(\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t'Linear Projects must be a list of non-empty ids.',\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t\tif (options.linearProjects.length > 0 && options.providerId !== IssuesCloudHostIntegrationId.Linear) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`Narrowing by Linear Project is not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// As with the state: a cursor resumed under other Projects skips teams drained under the ones it was minted for.\n\t// Only a composite cursor records them, so any other cursor came from a read narrowed to none.\n\tif (\n\t\toptions.cursor != null &&\n\t\t(compositeCursor?.linearProjects ?? []).join('\\0') !== (linearProjects ?? []).join('\\0')\n\t) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of other Linear Projects; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\t// A Project id the workspace no longer lists narrows every team to nothing, which would read as a Project with no\n\t// issues. Reported as a selector miss instead, as an `org` or `project` that matches nothing is.\n\tif (linearProjects != null && isLinearIntegration(integration)) {\n\t\tconst missing = await findUnlistedLinearProject(integration, linearProjects, options.connectionId);\n\t\tif (missing != null) {\n\t\t\twarnings.push(\n\t\t\t\tunmatchedSelectorWarning(options.providerId, domain, options.connectionId, {\n\t\t\t\t\tkind: 'linearProject',\n\t\t\t\t\tvalue: missing,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tawait integration.getCurrentAccount({ connectionId: options.connectionId }).catch(() => undefined);\n\n\tconst {\n\t\tvalue: resources,\n\t\twarning: resourcesWarning,\n\t\tmetadata: resourcesMetadata,\n\t} = await discoverResources(integration, options.providerId, domain, options.connectionId);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tconst scopedResources =\n\t\toptions.org != null\n\t\t\t? (resources ?? []).filter(r => selectorMatches(options.providerId, r, options.org!))\n\t\t\t: (resources ?? []);\n\t// Only a complete discovery proves a selection gone: a failed one may just not have reached it, and no resource\n\t// list without a warning is no session.\n\tconst resourceDiscoveryComplete = resources != null && resourcesWarning == null;\n\tconst selectionTarget: SelectionTarget = {\n\t\tintegration: integration,\n\t\tproviderId: options.providerId,\n\t\tdomain: domain,\n\t\tconnectionId: options.connectionId,\n\t\torg: options.org,\n\t\tproject: options.project,\n\t};\n\tconst unconfirmed = (reasons: readonly ProviderWarning[]): ProviderPagedResult<IssueShape> => {\n\t\tif (reasons.length === 0) {\n\t\t\twarnings.push(\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`Could not confirm '${options.project ?? options.org}' is gone, so it is not reported as removed.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tfor (const warning of reasons) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\treturn emptyPage(true, false, discoveryFailureRetry());\n\t};\n\t/**\n\t * Settles a selection the cached discovery matches nothing for from the provider's lists as they are now (see\n\t * {@link confirmSelection} and {@link toSelectionAction}). The cached lists can predate what was created since, so\n\t * a stale one is dropped and the read made once more over fresh lists; that read confirms the same way, since its\n\t * own discovery can still be joined to one started before the drop.\n\t */\n\tconst missSelection = async (\n\t\tselector: ProviderWarningSelector & { kind: TrackerSelectorKind },\n\t): Promise<ProviderPagedResult<IssueShape>> => {\n\t\tconst next = toSelectionAction(await confirmSelection(selectionTarget, selector.kind), refreshed);\n\t\tswitch (next.action) {\n\t\t\tcase 'unconfirmed':\n\t\t\t\treturn unconfirmed(next.warnings);\n\t\t\tcase 'refresh':\n\t\t\t\tintegration.invalidateDiscoveryCaches();\n\t\t\t\t// The caches are dropped already: the refreshed read needs no second session sync.\n\t\t\t\treturn readIssueTrackerIssuesPage(ctx, { ...options, forceSync: false }, true);\n\t\t\tcase 'report-missing': {\n\t\t\t\tconst credential = await confirmCredential(selectionTarget, next.resource);\n\t\t\t\tif (!credential.confirmed) {\n\t\t\t\t\treturn unconfirmed(credential.warning != null ? [credential.warning] : []);\n\t\t\t\t}\n\n\t\t\t\t// What the lists miss now: a selected org that is gone misses the org, whichever the cache missed.\n\t\t\t\tconst missed: ProviderWarningSelector =\n\t\t\t\t\tnext.missed === 'org' && options.org != null ? { kind: 'org', value: options.org } : selector;\n\t\t\t\twarnings.push(\n\t\t\t\t\tunmatchedSelectorWarning(options.providerId, domain, options.connectionId, missed, options.org),\n\t\t\t\t);\n\t\t\t\treturn emptyPage();\n\t\t\t}\n\t\t}\n\t};\n\t// Checked before the empty return, since an account with no resources left misses the selection too.\n\tif (options.org != null && resourceDiscoveryComplete && scopedResources.length === 0) {\n\t\treturn missSelection({ kind: 'org', value: options.org });\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tif (options.project != null && resourceDiscoveryComplete) {\n\t\t\treturn missSelection({ kind: 'project', value: options.project });\n\t\t}\n\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\tconst { truncated } = mergeAssessmentInto(\n\t\t\twarnings,\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\tresourcesMetadata,\n\t\t);\n\t\treturn emptyPage(fetchFailed, truncated, fetchFailed || truncated ? discoveryFailureRetry() : undefined);\n\t}\n\t// Only a partial discovery, reported by its warning, reaches here without the selected resource.\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tconst matchedProjects =\n\t\toptions.project != null\n\t\t\t? (projects ?? []).filter(p => selectorMatches(options.providerId, p, options.project!))\n\t\t\t: (projects ?? []);\n\tconst projectDiscoveryComplete =\n\t\tresourceDiscoveryComplete && projectsWarning == null && !projectDiscoveryFailed && !projectDiscoveryTruncated;\n\tif (options.project != null && projects != null && projectDiscoveryComplete && matchedProjects.length === 0) {\n\t\treturn missSelection({ kind: 'project', value: options.project });\n\t}\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\tunsupportedWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// One call for the whole window, so a tracker that can search several projects at once (Jira) does, instead\n\t// of paying one drain per project. Results still come back per project, which the accounting below needs.\n\tlet projectReads: IntegrationResult<ProjectIssuesDrain | undefined>[];\n\ttry {\n\t\tprojectReads = await integration.getIssuesForProjectsWithTruncationResult(\n\t\t\tscopedProjects.map(project => ({\n\t\t\t\tproject: project,\n\t\t\t\toptions: {\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\t...(linearProjects != null ? { linearProjects: linearProjects } : {}),\n\t\t\t\t},\n\t\t\t})),\n\t\t\toptions.connectionId,\n\t\t);\n\t} catch (ex) {\n\t\tconst error = toError(ex);\n\t\tprojectReads = scopedProjects.map(() => ({ error: error }));\n\t}\n\tconst perProject = await Promise.all(\n\t\tscopedProjects.map(async (project, index) => ({\n\t\t\tproject: project,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tPromise.resolve(projectReads[index]),\n\t\t\t)),\n\t\t})),\n\t);\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\t// Whether a request failed, which `fetchFailed` alone doesn't say: see `assessCollectionMetadata`'s `requestFailed`.\n\tlet requestFailed = fetchFailed;\n\tlet truncated = projectDiscoveryTruncated;\n\t// The projects whose read stopped short with nothing in its metadata to say why. One whose provider names its scope\n\t// is reported on its own, even when the read also failed: a project's cut must not hide behind another's failure,\n\t// which may be no more than an issue it skipped (#6011), nor its recovery behind another project's.\n\tconst cuts: { project: ResourceDescriptor; recovery: ProjectIssuesRecovery; scope: CollectionScope }[] = [];\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\trequestFailed = true;\n\t\t\ttruncated = true;\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t\trequestFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\ttruncated = true;\n\t\t\t\tif (result.scope != null && !result.metadata?.failures?.length && !result.metadata?.omissions?.length) {\n\t\t\t\t\tcuts.push({ project: project, recovery: result.recovery, scope: result.scope });\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// The truncation the projects reported already gets a structured warning below. Replace metadata's unstructured\n\t\t// fallback, the only warning it raises when nothing failed and nothing was omitted, while retaining failures and\n\t\t// omissions.\n\t\tif (truncated && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\trequestFailed = requestFailed || drainAssessment.requestFailed;\n\ttruncated = truncated || drainAssessment.truncated;\n\n\tfor (const { project, recovery, scope } of cuts) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tprojectCutWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tresourceLabel(project),\n\t\t\t\trecovery,\n\t\t\t\tscope,\n\t\t\t),\n\t\t);\n\t}\n\t// A truncation no project's own warning accounts for (a project list read short, or a cut from a provider that names\n\t// no scope for its projects) is reported for the read as a whole, unless a failure or an omission explains it. On\n\t// `fetchFailed`, not `requestFailed`: an issue left unread truncates the read too, and its own warning explains that.\n\t// A project list read short publishes its own warning, so it is never left to an unreadable issue's alone.\n\tif (!fetchFailed && truncated && !warnings.some(warning => warning.omission != null)) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\t'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, requestFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t\tlinearProjects: linearProjects,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: truncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\trequestFailed: requestFailed,\n\t\tunexpectedGap: nextPage != null && cursor == null,\n\t};\n}\n\n/**\n * The first of `ids` the workspace's Projects don't list, confirmed against a list read past the cache, since a cached\n * one can predate a Project created since. Undefined when every id is listed, or when no complete list can prove one\n * missing: the read then goes ahead, and an id that names nothing reads as empty.\n */\nasync function findUnlistedLinearProject(\n\tintegration: LinearIntegration,\n\tids: readonly string[],\n\tconnectionId: string | undefined,\n): Promise<string | undefined> {\n\tlet unlisted: string | undefined;\n\tfor (const fresh of [false, true]) {\n\t\tconst projects = (await integration.getWorkspaceProjectsResult(connectionId, { fresh: fresh }))?.value;\n\t\tif (\n\t\t\tprojects == null ||\n\t\t\tprojects.truncated === true ||\n\t\t\t(projects.metadata != null && projects.metadata.completeness !== 'complete')\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst listed = new Set(projects.values.map(project => project.id));\n\t\tunlisted = ids.find(id => !listed.has(id));\n\t\tif (unlisted == null) return undefined;\n\t}\n\treturn unlisted;\n}\n"],"mappings":"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"}
@@ -3,6 +3,8 @@ import { IntegrationIds } from "../constants.js";
3
3
  import { ProviderWarning, ProviderWarningSelector } from "../results.js";
4
4
  import { IssuesIntegration } from "../models/issuesIntegration.js";
5
5
  //#region ../plus/integrations/src/reads/selectionConfirmation.d.ts
6
+ /** The selectors a tracker's own discovery lists, which the lists confirm; not a Linear Project, read on its own. */
7
+ export type TrackerSelectorKind = Exclude<ProviderWarningSelector['kind'], 'linearProject'>;
6
8
  /**
7
9
  * What the provider lists now for a selection the cached discovery matched nothing for:
8
10
  * - `missing`: the lists, complete, match nothing. `missed` is the selector they miss, the org when it is gone
@@ -13,7 +15,7 @@ import { IssuesIntegration } from "../models/issuesIntegration.js";
13
15
  */
14
16
  export type SelectionConfirmation = {
15
17
  verdict: 'missing';
16
- missed: ProviderWarningSelector['kind'];
18
+ missed: TrackerSelectorKind;
17
19
  resource: ResourceDescriptor | undefined;
18
20
  } | {
19
21
  verdict: 'stale';
@@ -27,7 +29,7 @@ export type SelectionConfirmation = {
27
29
  */
28
30
  export type SelectionAction = {
29
31
  action: 'report-missing';
30
- missed: ProviderWarningSelector['kind'];
32
+ missed: TrackerSelectorKind;
31
33
  resource: ResourceDescriptor | undefined;
32
34
  } | {
33
35
  action: 'unconfirmed';
@@ -52,7 +54,7 @@ export interface SelectionTarget {
52
54
  * selection is reported missing only from this, never from the cached lists, which can predate what was created or
53
55
  * deleted since, and which a read made right after dropping them can still be served from a read started before.
54
56
  */
55
- export declare function confirmSelection(target: SelectionTarget, missed: ProviderWarningSelector['kind']): Promise<SelectionConfirmation>;
57
+ export declare function confirmSelection(target: SelectionTarget, missed: TrackerSelectorKind): Promise<SelectionConfirmation>;
56
58
  /**
57
59
  * Whether the tracker accepted the connection's credential, which alone proves a selection gone: Jira Data Center
58
60
  * serves a rejected one as the anonymous user, whose project list is empty, and nothing tells them apart once a proxy
@@ -1 +1 @@
1
- {"version":3,"file":"selectionConfirmation.js","names":[],"sources":["../../../../../plus/integrations/src/reads/selectionConfirmation.ts"],"sourcesContent":["import type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { assessCollectionMetadata } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderWarning, ProviderWarningSelector } from '../results.js';\nimport { runCaptured } from './drains.js';\nimport { selectorMatches } from './hierarchy.utils.js';\n\n/**\n * What the provider lists now for a selection the cached discovery matched nothing for:\n * - `missing`: the lists, complete, match nothing. `missed` is the selector they miss, the org when it is gone\n * whichever the cache missed, and `resource` a resource they name, for {@link confirmCredential};\n * - `stale`: they match it, so the cache predates it;\n * - `unknown`: they could not be listed completely, and `warnings` say why, as the failed read classified it (an\n * `auth` or `rate-limit` refusal stays one).\n */\nexport type SelectionConfirmation =\n\t| { verdict: 'missing'; missed: ProviderWarningSelector['kind']; resource: ResourceDescriptor | undefined }\n\t| { verdict: 'stale' }\n\t| { verdict: 'unknown'; warnings: ProviderWarning[] };\n\n/**\n * What a read does with a {@link SelectionConfirmation}: report the selection missing, read once more over fresh\n * lists, or report that the selection could not be confirmed, with a retry cursor.\n */\nexport type SelectionAction =\n\t| { action: 'report-missing'; missed: ProviderWarningSelector['kind']; resource: ResourceDescriptor | undefined }\n\t| { action: 'unconfirmed'; warnings: ProviderWarning[] }\n\t| { action: 'refresh' };\n\n/** The action for `confirmation`. A read already made over refreshed lists (`refreshed`) is not made again. */\nexport function toSelectionAction(confirmation: SelectionConfirmation, refreshed: boolean): SelectionAction {\n\tswitch (confirmation.verdict) {\n\t\tcase 'missing':\n\t\t\treturn { action: 'report-missing', missed: confirmation.missed, resource: confirmation.resource };\n\t\tcase 'unknown':\n\t\t\treturn { action: 'unconfirmed', warnings: confirmation.warnings };\n\t\tcase 'stale':\n\t\t\treturn refreshed ? { action: 'unconfirmed', warnings: [] } : { action: 'refresh' };\n\t}\n}\n\n/** Where a read's selection lives: the tracker, its connection, and what the caller selected. */\nexport interface SelectionTarget {\n\tintegration: IssuesIntegration;\n\tproviderId: IntegrationIds;\n\tdomain: string | undefined;\n\tconnectionId: string | undefined;\n\torg: string | undefined;\n\tproject: string | undefined;\n}\n\n/**\n * Lists the selection as the provider holds it now, through discovery that shares nothing with the cache: it joins no\n * read in flight, so a read started before what the caller needs to see can't answer it, and stores nothing. A\n * selection is reported missing only from this, never from the cached lists, which can predate what was created or\n * deleted since, and which a read made right after dropping them can still be served from a read started before.\n */\nexport async function confirmSelection(\n\ttarget: SelectionTarget,\n\tmissed: ProviderWarningSelector['kind'],\n): Promise<SelectionConfirmation> {\n\tconst { integration, providerId, domain, connectionId, org, project } = target;\n\tconst { value: discovery, warning } = await runCaptured(providerId, domain, connectionId, () =>\n\t\tintegration.getUncachedDiscoveryResult(r => org == null || selectorMatches(providerId, r, org), connectionId),\n\t);\n\tconst resources = discovery?.resources;\n\tif (resources == null) return { verdict: 'unknown', warnings: warning != null ? [warning] : [] };\n\n\tconst selected = org != null ? resources.filter(r => selectorMatches(providerId, r, org)) : resources;\n\t// The account is confirmed on a resource the lists name now, which a revoked one in the cache may not be.\n\tconst resource = selected[0] ?? resources[0];\n\tif (selected.length === 0) return { verdict: 'missing', missed: 'org', resource: resource };\n\t// The org the cache missed is listed now: what it holds is for a read over fresh lists to find.\n\tif (missed === 'org' || project == null) return { verdict: 'stale' };\n\n\tconst projects = discovery?.projects;\n\tif (projects == null) return { verdict: 'unknown', warnings: [] };\n\n\tconst assessed = assessCollectionMetadata(providerId, domain, connectionId, projects.metadata);\n\tif (assessed.fetchFailed || assessed.truncated) return { verdict: 'unknown', warnings: assessed.warnings };\n\n\treturn projects.values.some(p => selectorMatches(providerId, p, project))\n\t\t? { verdict: 'stale' }\n\t\t: { verdict: 'missing', missed: 'project', resource: resource };\n}\n\n/**\n * Whether the tracker accepted the connection's credential, which alone proves a selection gone: Jira Data Center\n * serves a rejected one as the anonymous user, whose project list is empty, and nothing tells them apart once a proxy\n * strips `X-AUSERNAME`; its account lookup fails. Looked up past any lookup in flight, which may predate a revocation.\n * An account whose lists name no resource has none to look up on, and is judged on its lists alone.\n */\nexport async function confirmCredential(\n\ttarget: Pick<SelectionTarget, 'integration' | 'providerId' | 'domain' | 'connectionId'>,\n\tresource: ResourceDescriptor | undefined,\n): Promise<{ confirmed: true } | { confirmed: false; warning: ProviderWarning | undefined }> {\n\tif (resource == null) return { confirmed: true };\n\n\tconst { integration, providerId, domain, connectionId } = target;\n\tconst { value: account, warning } = await runCaptured(providerId, domain, connectionId, () =>\n\t\tintegration.getUncachedAccountForResourceResult(resource, connectionId),\n\t);\n\treturn (account?.username ?? account?.name) != null ? { confirmed: true } : { confirmed: false, warning: warning };\n}\n"],"mappings":";;;;;AA+BA,SAAgB,kBAAkB,cAAqC,WAAqC;CAC3G,QAAQ,aAAa,SAArB;EACC,KAAK,WACJ,OAAO;GAAE,QAAQ;GAAkB,QAAQ,aAAa;GAAQ,UAAU,aAAa;EAAS;EACjG,KAAK,WACJ,OAAO;GAAE,QAAQ;GAAe,UAAU,aAAa;EAAS;EACjE,KAAK,SACJ,OAAO,YAAY;GAAE,QAAQ;GAAe,UAAU,CAAC;EAAE,IAAI,EAAE,QAAQ,UAAU;CACnF;AACD;;;;;;;AAkBA,eAAsB,iBACrB,QACA,QACiC;CACjC,MAAM,EAAE,aAAa,YAAY,QAAQ,cAAc,KAAK,YAAY;CACxE,MAAM,EAAE,OAAO,WAAW,YAAY,MAAM,YAAY,YAAY,QAAQ,oBAC3E,YAAY,4BAA2B,MAAK,OAAO,QAAQ,gBAAgB,YAAY,GAAG,GAAG,GAAG,YAAY,CAC7G;CACA,MAAM,YAAY,WAAW;CAC7B,IAAI,aAAa,MAAM,OAAO;EAAE,SAAS;EAAW,UAAU,WAAW,OAAO,CAAC,OAAO,IAAI,CAAC;CAAE;CAE/F,MAAM,WAAW,OAAO,OAAO,UAAU,QAAO,MAAK,gBAAgB,YAAY,GAAG,GAAG,CAAC,IAAI;CAE5F,MAAM,WAAW,SAAS,MAAM,UAAU;CAC1C,IAAI,SAAS,WAAW,GAAG,OAAO;EAAE,SAAS;EAAW,QAAQ;EAAiB;CAAS;CAE1F,IAAI,WAAW,SAAS,WAAW,MAAM,OAAO,EAAE,SAAS,QAAQ;CAEnE,MAAM,WAAW,WAAW;CAC5B,IAAI,YAAY,MAAM,OAAO;EAAE,SAAS;EAAW,UAAU,CAAC;CAAE;CAEhE,MAAM,WAAW,yBAAyB,YAAY,QAAQ,cAAc,SAAS,QAAQ;CAC7F,IAAI,SAAS,eAAe,SAAS,WAAW,OAAO;EAAE,SAAS;EAAW,UAAU,SAAS;CAAS;CAEzG,OAAO,SAAS,OAAO,MAAK,MAAK,gBAAgB,YAAY,GAAG,OAAO,CAAC,IACrE,EAAE,SAAS,QAAQ,IACnB;EAAE,SAAS;EAAW,QAAQ;EAAqB;CAAS;AAChE;;;;;;;AAQA,eAAsB,kBACrB,QACA,UAC4F;CAC5F,IAAI,YAAY,MAAM,OAAO,EAAE,WAAW,KAAK;CAE/C,MAAM,EAAE,aAAa,YAAY,QAAQ,iBAAiB;CAC1D,MAAM,EAAE,OAAO,SAAS,YAAY,MAAM,YAAY,YAAY,QAAQ,oBACzE,YAAY,oCAAoC,UAAU,YAAY,CACvE;CACA,QAAQ,SAAS,YAAY,SAAS,SAAS,OAAO,EAAE,WAAW,KAAK,IAAI;EAAE,WAAW;EAAgB;CAAQ;AAClH"}
1
+ {"version":3,"file":"selectionConfirmation.js","names":[],"sources":["../../../../../plus/integrations/src/reads/selectionConfirmation.ts"],"sourcesContent":["import type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { assessCollectionMetadata } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderWarning, ProviderWarningSelector } from '../results.js';\nimport { runCaptured } from './drains.js';\nimport { selectorMatches } from './hierarchy.utils.js';\n\n/** The selectors a tracker's own discovery lists, which the lists confirm; not a Linear Project, read on its own. */\nexport type TrackerSelectorKind = Exclude<ProviderWarningSelector['kind'], 'linearProject'>;\n\n/**\n * What the provider lists now for a selection the cached discovery matched nothing for:\n * - `missing`: the lists, complete, match nothing. `missed` is the selector they miss, the org when it is gone\n * whichever the cache missed, and `resource` a resource they name, for {@link confirmCredential};\n * - `stale`: they match it, so the cache predates it;\n * - `unknown`: they could not be listed completely, and `warnings` say why, as the failed read classified it (an\n * `auth` or `rate-limit` refusal stays one).\n */\nexport type SelectionConfirmation =\n\t| { verdict: 'missing'; missed: TrackerSelectorKind; resource: ResourceDescriptor | undefined }\n\t| { verdict: 'stale' }\n\t| { verdict: 'unknown'; warnings: ProviderWarning[] };\n\n/**\n * What a read does with a {@link SelectionConfirmation}: report the selection missing, read once more over fresh\n * lists, or report that the selection could not be confirmed, with a retry cursor.\n */\nexport type SelectionAction =\n\t| { action: 'report-missing'; missed: TrackerSelectorKind; resource: ResourceDescriptor | undefined }\n\t| { action: 'unconfirmed'; warnings: ProviderWarning[] }\n\t| { action: 'refresh' };\n\n/** The action for `confirmation`. A read already made over refreshed lists (`refreshed`) is not made again. */\nexport function toSelectionAction(confirmation: SelectionConfirmation, refreshed: boolean): SelectionAction {\n\tswitch (confirmation.verdict) {\n\t\tcase 'missing':\n\t\t\treturn { action: 'report-missing', missed: confirmation.missed, resource: confirmation.resource };\n\t\tcase 'unknown':\n\t\t\treturn { action: 'unconfirmed', warnings: confirmation.warnings };\n\t\tcase 'stale':\n\t\t\treturn refreshed ? { action: 'unconfirmed', warnings: [] } : { action: 'refresh' };\n\t}\n}\n\n/** Where a read's selection lives: the tracker, its connection, and what the caller selected. */\nexport interface SelectionTarget {\n\tintegration: IssuesIntegration;\n\tproviderId: IntegrationIds;\n\tdomain: string | undefined;\n\tconnectionId: string | undefined;\n\torg: string | undefined;\n\tproject: string | undefined;\n}\n\n/**\n * Lists the selection as the provider holds it now, through discovery that shares nothing with the cache: it joins no\n * read in flight, so a read started before what the caller needs to see can't answer it, and stores nothing. A\n * selection is reported missing only from this, never from the cached lists, which can predate what was created or\n * deleted since, and which a read made right after dropping them can still be served from a read started before.\n */\nexport async function confirmSelection(\n\ttarget: SelectionTarget,\n\tmissed: TrackerSelectorKind,\n): Promise<SelectionConfirmation> {\n\tconst { integration, providerId, domain, connectionId, org, project } = target;\n\tconst { value: discovery, warning } = await runCaptured(providerId, domain, connectionId, () =>\n\t\tintegration.getUncachedDiscoveryResult(r => org == null || selectorMatches(providerId, r, org), connectionId),\n\t);\n\tconst resources = discovery?.resources;\n\tif (resources == null) return { verdict: 'unknown', warnings: warning != null ? [warning] : [] };\n\n\tconst selected = org != null ? resources.filter(r => selectorMatches(providerId, r, org)) : resources;\n\t// The account is confirmed on a resource the lists name now, which a revoked one in the cache may not be.\n\tconst resource = selected[0] ?? resources[0];\n\tif (selected.length === 0) return { verdict: 'missing', missed: 'org', resource: resource };\n\t// The org the cache missed is listed now: what it holds is for a read over fresh lists to find.\n\tif (missed === 'org' || project == null) return { verdict: 'stale' };\n\n\tconst projects = discovery?.projects;\n\tif (projects == null) return { verdict: 'unknown', warnings: [] };\n\n\tconst assessed = assessCollectionMetadata(providerId, domain, connectionId, projects.metadata);\n\tif (assessed.fetchFailed || assessed.truncated) return { verdict: 'unknown', warnings: assessed.warnings };\n\n\treturn projects.values.some(p => selectorMatches(providerId, p, project))\n\t\t? { verdict: 'stale' }\n\t\t: { verdict: 'missing', missed: 'project', resource: resource };\n}\n\n/**\n * Whether the tracker accepted the connection's credential, which alone proves a selection gone: Jira Data Center\n * serves a rejected one as the anonymous user, whose project list is empty, and nothing tells them apart once a proxy\n * strips `X-AUSERNAME`; its account lookup fails. Looked up past any lookup in flight, which may predate a revocation.\n * An account whose lists name no resource has none to look up on, and is judged on its lists alone.\n */\nexport async function confirmCredential(\n\ttarget: Pick<SelectionTarget, 'integration' | 'providerId' | 'domain' | 'connectionId'>,\n\tresource: ResourceDescriptor | undefined,\n): Promise<{ confirmed: true } | { confirmed: false; warning: ProviderWarning | undefined }> {\n\tif (resource == null) return { confirmed: true };\n\n\tconst { integration, providerId, domain, connectionId } = target;\n\tconst { value: account, warning } = await runCaptured(providerId, domain, connectionId, () =>\n\t\tintegration.getUncachedAccountForResourceResult(resource, connectionId),\n\t);\n\treturn (account?.username ?? account?.name) != null ? { confirmed: true } : { confirmed: false, warning: warning };\n}\n"],"mappings":";;;;;AAkCA,SAAgB,kBAAkB,cAAqC,WAAqC;CAC3G,QAAQ,aAAa,SAArB;EACC,KAAK,WACJ,OAAO;GAAE,QAAQ;GAAkB,QAAQ,aAAa;GAAQ,UAAU,aAAa;EAAS;EACjG,KAAK,WACJ,OAAO;GAAE,QAAQ;GAAe,UAAU,aAAa;EAAS;EACjE,KAAK,SACJ,OAAO,YAAY;GAAE,QAAQ;GAAe,UAAU,CAAC;EAAE,IAAI,EAAE,QAAQ,UAAU;CACnF;AACD;;;;;;;AAkBA,eAAsB,iBACrB,QACA,QACiC;CACjC,MAAM,EAAE,aAAa,YAAY,QAAQ,cAAc,KAAK,YAAY;CACxE,MAAM,EAAE,OAAO,WAAW,YAAY,MAAM,YAAY,YAAY,QAAQ,oBAC3E,YAAY,4BAA2B,MAAK,OAAO,QAAQ,gBAAgB,YAAY,GAAG,GAAG,GAAG,YAAY,CAC7G;CACA,MAAM,YAAY,WAAW;CAC7B,IAAI,aAAa,MAAM,OAAO;EAAE,SAAS;EAAW,UAAU,WAAW,OAAO,CAAC,OAAO,IAAI,CAAC;CAAE;CAE/F,MAAM,WAAW,OAAO,OAAO,UAAU,QAAO,MAAK,gBAAgB,YAAY,GAAG,GAAG,CAAC,IAAI;CAE5F,MAAM,WAAW,SAAS,MAAM,UAAU;CAC1C,IAAI,SAAS,WAAW,GAAG,OAAO;EAAE,SAAS;EAAW,QAAQ;EAAiB;CAAS;CAE1F,IAAI,WAAW,SAAS,WAAW,MAAM,OAAO,EAAE,SAAS,QAAQ;CAEnE,MAAM,WAAW,WAAW;CAC5B,IAAI,YAAY,MAAM,OAAO;EAAE,SAAS;EAAW,UAAU,CAAC;CAAE;CAEhE,MAAM,WAAW,yBAAyB,YAAY,QAAQ,cAAc,SAAS,QAAQ;CAC7F,IAAI,SAAS,eAAe,SAAS,WAAW,OAAO;EAAE,SAAS;EAAW,UAAU,SAAS;CAAS;CAEzG,OAAO,SAAS,OAAO,MAAK,MAAK,gBAAgB,YAAY,GAAG,OAAO,CAAC,IACrE,EAAE,SAAS,QAAQ,IACnB;EAAE,SAAS;EAAW,QAAQ;EAAqB;CAAS;AAChE;;;;;;;AAQA,eAAsB,kBACrB,QACA,UAC4F;CAC5F,IAAI,YAAY,MAAM,OAAO,EAAE,WAAW,KAAK;CAE/C,MAAM,EAAE,aAAa,YAAY,QAAQ,iBAAiB;CAC1D,MAAM,EAAE,OAAO,SAAS,YAAY,MAAM,YAAY,YAAY,QAAQ,oBACzE,YAAY,oCAAoC,UAAU,YAAY,CACvE;CACA,QAAQ,SAAS,YAAY,SAAS,SAAS,OAAO,EAAE,WAAW,KAAK,IAAI;EAAE,WAAW;EAAgB;CAAQ;AAClH"}
@@ -77,7 +77,7 @@ function unmatchedSelectorWarning(id, domain, connectionId, selector, org) {
77
77
  providerId: id,
78
78
  domain,
79
79
  connectionId,
80
- message: selector.kind === "org" ? `No organization matches '${selector.value}' for '${id}'; it was removed or this connection can no longer see it.` : `No project matches '${selector.value}'${org != null ? ` in '${org}'` : ""} for '${id}'; it was removed, renamed or this connection can no longer see it.`,
80
+ message: selector.kind === "org" ? `No organization matches '${selector.value}' for '${id}'; it was removed or this connection can no longer see it.` : selector.kind === "linearProject" ? `No Linear Project matches '${selector.value}' for '${id}'; it was removed or this connection can no longer see it.` : `No project matches '${selector.value}'${org != null ? ` in '${org}'` : ""} for '${id}'; it was removed, renamed or this connection can no longer see it.`,
81
81
  kind: "not-found",
82
82
  isAuth: false,
83
83
  selector
@@ -1 +1 @@
1
- {"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type { ProjectIssuesRecovery } from '../models/issueReads.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type {\n\tProviderWarning,\n\tProviderWarningOmissionRecovery,\n\tProviderWarningOmissionScope,\n\tProviderWarningSelector,\n} from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed\n * request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the\n * provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without\n * reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through\n * one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is\n * assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —\n * `collectionMetadata.ts` builds those from the structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type or built by their own builders ({@link noConnectionWarning},\n * {@link unmatchedSelectorWarning}), never by this one; see `ProviderWarningKind`.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the\n * request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo\n * discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,\n * search criterion or option combination it cannot express. Retrying the same request never succeeds; a\n * different provider, scope or request may.\n *\n * NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,\n * invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are\n * {@link otherWarning}.\n */\nexport function unsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'unsupported',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds the `not-found` warning for an issue-tracker project read whose `org` or `project` selector matched\n * nothing a complete discovery returned; see {@link ProviderWarning.selector}. A `project` miss under an `org` names\n * the project, since the org matched.\n */\nexport function unmatchedSelectorWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tselector: ProviderWarningSelector,\n\torg?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tselector.kind === 'org'\n\t\t\t\t? `No organization matches '${selector.value}' for '${id}'; it was removed or this connection can no longer see it.`\n\t\t\t\t: `No project matches '${selector.value}'${org != null ? ` in '${org}'` : ''} for '${id}'; it was removed, renamed or this connection can no longer see it.`,\n\t\tkind: 'not-found',\n\t\tisAuth: false,\n\t\tselector: selector,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it\n * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n\tscope?: ProviderWarningOmissionScope,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t\t...(scope != null ? { scope: { ...scope } } : {}),\n\t\t},\n\t};\n}\n\n/**\n * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say\n * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,\n * which names the project the way the provider's own failures do.\n */\nexport function projectCutWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tproject: string,\n\trecovery: ProjectIssuesRecovery,\n\tscope: ProviderWarningOmissionScope,\n): ProviderWarning {\n\tconst [message, cause]: [string, IncompleteReadCause] =\n\t\trecovery === 'narrow-scope'\n\t\t\t? [\n\t\t\t\t\t`Project '${project}' holds more issues than one read can return; narrow the scope to read the rest.`,\n\t\t\t\t\t'scope-too-large',\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t`Some issues of project '${project}' were omitted; the provider returned an incomplete result.`,\n\t\t\t\t\t'exhausted',\n\t\t\t\t];\n\treturn incompleteReadWarning(id, domain, connectionId, message, cause, scope);\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\n/**\n * The part of a drain that paging could not reach, when that drain ALSO stopped at its page budget with a usable\n * cursor — one facet of a composite read reaching the provider's ceiling while a sibling can still page. Reported\n * beside the `page-budget` {@link truncationWarning}, never in place of it: raising the budget returns more of the\n * rest, and nothing returns this part, so each warning speaks only for its own.\n *\n * The message names no mechanism. The fact behind it is `paging.truncated`, which a provider cap sets but so does\n * `hasMore` with no usable cursor (GitHub's `structuralIncompleteness`) and any per-repository truncation on the\n * repo-scoped path, so \"capped by the provider\" would name a cause we cannot confirm.\n */\nexport function cappedBesideBudgetWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n): ProviderWarning {\n\treturn incompleteReadWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${readKind} read for '${id}' could not reach part of its results by paging; raising the page budget will not return those.`,\n\t\t'exhausted',\n\t);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind\n * follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and\n * stays `'other'`.\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\t// Caller input the caller fixes in the request itself, so `'other'` rather than `'unsupported'`.\n\t\t\treturn otherWarning(\n\t\t\t\tid,\n\t\t\t\tdomain,\n\t\t\t\tconnectionId,\n\t\t\t\t`The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`,\n\t\t\t);\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type { ProjectIssuesRecovery } from '../models/issueReads.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type {\n\tProviderWarning,\n\tProviderWarningOmissionRecovery,\n\tProviderWarningOmissionScope,\n\tProviderWarningSelector,\n} from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed\n * request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the\n * provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without\n * reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through\n * one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is\n * assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —\n * `collectionMetadata.ts` builds those from the structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type or built by their own builders ({@link noConnectionWarning},\n * {@link unmatchedSelectorWarning}), never by this one; see `ProviderWarningKind`.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the\n * request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo\n * discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,\n * search criterion or option combination it cannot express. Retrying the same request never succeeds; a\n * different provider, scope or request may.\n *\n * NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,\n * invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are\n * {@link otherWarning}.\n */\nexport function unsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'unsupported',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds the `not-found` warning for an issue-tracker project read whose `org` or `project` selector matched\n * nothing a complete discovery returned; see {@link ProviderWarning.selector}. A `project` miss under an `org` names\n * the project, since the org matched.\n */\nexport function unmatchedSelectorWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tselector: ProviderWarningSelector,\n\torg?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tselector.kind === 'org'\n\t\t\t\t? `No organization matches '${selector.value}' for '${id}'; it was removed or this connection can no longer see it.`\n\t\t\t\t: selector.kind === 'linearProject'\n\t\t\t\t\t? `No Linear Project matches '${selector.value}' for '${id}'; it was removed or this connection can no longer see it.`\n\t\t\t\t\t: `No project matches '${selector.value}'${org != null ? ` in '${org}'` : ''} for '${id}'; it was removed, renamed or this connection can no longer see it.`,\n\t\tkind: 'not-found',\n\t\tisAuth: false,\n\t\tselector: selector,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it\n * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n\tscope?: ProviderWarningOmissionScope,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t\t...(scope != null ? { scope: { ...scope } } : {}),\n\t\t},\n\t};\n}\n\n/**\n * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say\n * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,\n * which names the project the way the provider's own failures do.\n */\nexport function projectCutWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tproject: string,\n\trecovery: ProjectIssuesRecovery,\n\tscope: ProviderWarningOmissionScope,\n): ProviderWarning {\n\tconst [message, cause]: [string, IncompleteReadCause] =\n\t\trecovery === 'narrow-scope'\n\t\t\t? [\n\t\t\t\t\t`Project '${project}' holds more issues than one read can return; narrow the scope to read the rest.`,\n\t\t\t\t\t'scope-too-large',\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t`Some issues of project '${project}' were omitted; the provider returned an incomplete result.`,\n\t\t\t\t\t'exhausted',\n\t\t\t\t];\n\treturn incompleteReadWarning(id, domain, connectionId, message, cause, scope);\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\n/**\n * The part of a drain that paging could not reach, when that drain ALSO stopped at its page budget with a usable\n * cursor — one facet of a composite read reaching the provider's ceiling while a sibling can still page. Reported\n * beside the `page-budget` {@link truncationWarning}, never in place of it: raising the budget returns more of the\n * rest, and nothing returns this part, so each warning speaks only for its own.\n *\n * The message names no mechanism. The fact behind it is `paging.truncated`, which a provider cap sets but so does\n * `hasMore` with no usable cursor (GitHub's `structuralIncompleteness`) and any per-repository truncation on the\n * repo-scoped path, so \"capped by the provider\" would name a cause we cannot confirm.\n */\nexport function cappedBesideBudgetWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n): ProviderWarning {\n\treturn incompleteReadWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${readKind} read for '${id}' could not reach part of its results by paging; raising the page budget will not return those.`,\n\t\t'exhausted',\n\t);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind\n * follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and\n * stays `'other'`.\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\t// Caller input the caller fixes in the request itself, so `'other'` rather than `'unsupported'`.\n\t\t\treturn otherWarning(\n\t\t\t\tid,\n\t\t\t\tdomain,\n\t\t\t\tconnectionId,\n\t\t\t\t`The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`,\n\t\t\t);\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
@@ -91,10 +91,11 @@ export interface ProviderWarningScope {
91
91
  }
92
92
  /**
93
93
  * The selector of an issue-tracker project read that matched nothing: see {@link ProviderWarning.selector}. `kind`
94
- * names which one missed, and `value` is that selector exactly as the caller passed it.
94
+ * names which one missed, and `value` is that selector exactly as the caller passed it. `linearProject` is one of a
95
+ * Linear read's `linearProjects`, an id the workspace's Projects no longer list.
95
96
  */
96
97
  export interface ProviderWarningSelector {
97
- kind: 'org' | 'project';
98
+ kind: 'org' | 'project' | 'linearProject';
98
99
  value: string;
99
100
  }
100
101
  /**
@@ -249,6 +250,8 @@ export interface ProviderWarning {
249
250
  * Present when an issue-tracker project read's `org` or `project` selector matched nothing the connection
250
251
  * discovered: the selection was removed, renamed, or is no longer visible to this connection. `kind` is
251
252
  * `'not-found'`, the page is empty and not `fetchFailed`, and it carries no cursor, since a retry finds the same.
253
+ * A `linearProject` miss is diagnostic: the requested Project ids still filter the read, preserving issues in
254
+ * other selected Projects. It therefore does not force an empty page or remove its continuation.
252
255
  *
253
256
  * Set only when discovery was complete. A read whose discovery failed or was truncated cannot tell a missing
254
257
  * selection from one it did not reach, so it keeps reporting that failure instead, without this field. A miss is
@@ -433,6 +436,19 @@ export interface ProviderOrganization {
433
436
  orgId?: string;
434
437
  url: string;
435
438
  }
439
+ /**
440
+ * A Linear Project (Linear's own `Project` entity, which can span several teams), as `listLinearProjects` lists it.
441
+ * Distinct from the teams `listProjects` publishes for Linear: pass its `id` to `listIssueTrackerIssuesPage`'s
442
+ * `linearProjects` to narrow an issue read to it.
443
+ */
444
+ export interface ProviderLinearProjectShape {
445
+ id: string;
446
+ providerId: IntegrationIds;
447
+ name: string;
448
+ /** Linear's project state, as Linear names it (e.g. `planned`, `started`, `paused`, `completed`, `canceled`). */
449
+ state: string;
450
+ url: string;
451
+ }
436
452
  /** Normalized repository shape returned by the provider facade. */
437
453
  export interface ProviderRepositoryShape {
438
454
  id: string;