Skip to content

fix(pm-discovery): promote JIRA baseUrl from config into discovery credentials#1184

Merged
zbigniewsobiecki merged 1 commit intodevfrom
fix/pm-discovery-jira-base-url
Apr 24, 2026
Merged

fix(pm-discovery): promote JIRA baseUrl from config into discovery credentials#1184
zbigniewsobiecki merged 1 commit intodevfrom
fix/pm-discovery-jira-base-url

Conversation

@zbigniewsobiecki
Copy link
Copy Markdown
Member

Summary

  • Fixes production "Internal server error" on the JIRA wizard's Select Project step in edit mode (reproed today on ua-store).
  • Adds a generic configToCredentials(config) manifest hook so providers can promote non-secret connection fields from project_integrations.config into the discovery credentials bag — JIRA wires it to promote baseUrlbase_url.
  • Preserves the spec-009 "new provider touches zero shared files" invariant (new-provider-surface.test.ts still green).

Root cause

Regression from spec 010/2 (generic pm.discovery.discover). JIRA's baseUrl lives on project_integrations.config, not project_credentials. The resolver only iterated credentialRoles → returned {email, api_token} with no base_url. createDiscoveryProvider then built new Version3Client({ host: '' }) → jira.js threw "Couldn't parse the host URL."

Legacy jiraProjectsByProject (deleted in 010/2) read baseUrl directly from integration config. The generic replacement lost that step.

First-time wizard setup was unaffected (wizard passes credentials: { email, api_token, base_url } explicitly). Edit-mode re-verification broke.

Fix shape

New optional manifest field:

readonly configToCredentials?: (config: unknown) => Record<string, string>;
  • Invoked on projectId path of resolvePMCredentials, not on the explicit-credentials path.
  • project_credentials values win on key collision — DB secret always beats config-derived default.
  • Malformed hook returns / throws are caught + logged; one bad provider can't take down discovery for all.

JIRA implementation:

configToCredentials: (config) => {
  if (!config || typeof config !== 'object') return {};
  const baseUrl = (config as { baseUrl?: unknown }).baseUrl;
  return typeof baseUrl === 'string' && baseUrl.length > 0
    ? { base_url: baseUrl }
    : {};
}

Extracted helpers (promoteConfigCredentials, loadIntegrationAndManifest) keep resolvePMCredentials under biome's cognitive-complexity threshold.

Tests (11 new cases, all green)

  • tests/unit/api/pm-discovery.test.ts — 4 router cases: hook is merged, DB creds win on collision, no-hook providers still work, hook not invoked on explicit-credentials path.
  • tests/unit/pm/jira/manifest-config-to-credentials.test.ts — 7 unit cases pinning the JIRA hook contract (missing / empty / non-string / null / nested / unrelated fields).

Hygiene

  • Fixed all 12 pre-existing biome warnings surfaced by my changes (complexity + test non-null-assertions).
  • Documented configToCredentials in src/integrations/README.md.
  • npm run lint, npm run typecheck, and npx vitest run --project unit-api --project unit-core all clean (6398 tests pass).

Test plan

  • Unit: 6398 tests pass locally
  • Lint: 0 errors, 0 warnings
  • Typecheck: clean
  • PM conformance harness: 72/72 passing for all providers
  • CI green
  • Manual verification on prod: reopen ua-store JIRA wizard → Select Project loads project list instead of "Internal server error"

🤖 Generated with Claude Code

…edentials

Edit-mode re-verification in the JIRA wizard's Select Project step was
returning "Internal server error" because `pm.discovery.discover({ projectId })`
resolved credentials only from `project_credentials` — where JIRA's `baseUrl`
does not live. The JIRA `createDiscoveryProvider` factory built
`new Version3Client({ host: '' })` and the underlying jira.js client threw
"Couldn't parse the host URL."

Regression from spec 010/2, which replaced per-provider discovery procedures
with the generic `pm.discovery.discover`. The legacy `jiraProjectsByProject`
read `baseUrl` off integration config directly.

Fix: add an optional `configToCredentials(config): Record<string, string>`
hook to `PMProviderManifest`. The resolver invokes it on the `projectId`
path to seed the credentials bag with non-secret connection fields promoted
from `project_integrations.config`. `project_credentials` values override
on key collisions — DB-scoped secrets always win over config-derived defaults.
JIRA declares the hook to promote `baseUrl` → `base_url`.

The hook is generic (no JIRA `if` in shared infra), defensive (bad hook
returns or throws are caught and logged, discovery stays up), and preserves
the spec-009 "new provider touches zero shared files" invariant.

Also: refactored `resolvePMCredentials` to stay under the biome cognitive-
complexity threshold by extracting `promoteConfigCredentials` +
`loadIntegrationAndManifest` helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zbigniewsobiecki zbigniewsobiecki merged commit b1d3487 into dev Apr 24, 2026
8 checks passed
@zbigniewsobiecki zbigniewsobiecki deleted the fix/pm-discovery-jira-base-url branch April 24, 2026 12:35
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Codecov Report

❌ Patch coverage is 62.12121% with 25 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/api/routers/pm-discovery.ts 58.33% 24 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

zbigniewsobiecki added a commit that referenced this pull request Apr 24, 2026
…ntials refactor (#1187)

#1184 extracted `promoteConfigCredentials` + `loadIntegrationAndManifest`
helpers but added five guard branches none of the new tests exercised
(codecov patch coverage on that PR was 58.33%, below the 80% target).
This commit pins each branch directly so the patch is in the green:

- UNAUTHORIZED when projectId is set but effectiveOrgId is null
- NOT_FOUND when no PM integration is configured for the project
- NOT_FOUND when the saved integration belongs to a different provider
- configToCredentials returning a non-object (string/null/array) is
  silently coerced to empty — resolved bag ends up project_credentials-only
- configToCredentials throwing is swallowed with a console.warn; discovery
  still returns project_credentials — a broken hook cannot brick the wizard

All five are behavioural contracts the original `resolvePMCredentials`
upheld through inline conditionals; the refactor preserved them but moved
them into the helpers. These tests make the contract explicit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant