Files
Gitea-Tools/docs/architecture/jenkins-job-mapping-design.md
T
sysadmin 1bc2f20623 docs: Jenkins repo/branch/PR to job mapping design (#77)
Add docs/architecture/jenkins-job-mapping-design.md: declarative versioned
mapping config (exact-match repo/branch entries, no globs, fail-closed load
on malformed/duplicate entries), resolution semantics for multibranch/
single/parameterized-view job types with URL-encoded branch and PR-<n>
addressing, branch-pinned-over-repo-wide precedence, fork PRs resolving via
base repo only, explicit machine-checkable no-match payload (never guess or
probe job names), config location in the jenkins-mcp package (no secrets,
env-overridable path), a read-only jenkins_resolve_job tool surface, and a
mocked-config/mocked-Jenkins testing strategy.

Design only; no implementation, no code behavior changed, no Jenkins write
actions introduced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:09:48 -04:00

6.7 KiB

Jenkins Repo/Branch/PR → Job Mapping — Design Notes

  • Status: Design (implementation-ready notes; no implementation in this repo)
  • Issue: #77 (parent: #72 read-only tools design; umbrella: #75; boundary: ADR-0001, #71)
  • Related docs: jenkins-readonly-build-status-design.md
  • Date: 2026-07-02

1. Purpose

The #72 tool set addresses Jenkins jobs by explicit fully-qualified job path. This document designs the layer above it: how a (repository, branch, PR) tuple — the vocabulary of Gitea workflows — resolves deterministically to a Jenkins job path, so an LLM can ask "did the build for Gitea-Tools master pass?" without knowing Jenkins internals.

Hard constraints inherited from #72 / ADR-0001:

  • No silent guessing of job names. Unmapped input returns an explicit "no mapping" result — never a fuzzy match, never a constructed-and-probed name.
  • Read-only. Mapping introduces no Jenkins write actions.
  • Lives in the jenkins-mcp boundary; no Gitea credentials involved.

2. Mapping format

Declarative, versioned config (TOML or JSON — match whatever config format jenkins-mcp adopts; illustrated here as TOML):

version = 1

[[mapping]]
# Source side (what the caller supplies)
repo   = "Scaled-Tech-Consulting/Gitea-Tools"   # org/repo, exact
# Target side (where it lives in Jenkins)
job    = "scaled-tech/gitea-tools"              # foldered job path
type   = "multibranch"                           # "multibranch" | "single" | "parameterized-view"

[[mapping]]
repo   = "Scaled-Tech-Consulting/Timesheet"
branch = "master"                                # optional: branch-specific override
job    = "scaled-tech/timesheet-master"
type   = "single"

Field semantics:

Field Required Meaning
repo yes Exact org/repo (case-insensitive compare, stored canonical)
branch no Exact branch name this entry pins; absent = all branches
job yes Fully-qualified Jenkins job path, folders /-joined
type yes How branch/PR resolves under the job (§3)

Rules:

  • Exact matching only on repo and branch. No globs in v1 (globs invite accidental over-matching; add later behind an explicit pattern = true flag if ever needed).
  • Unknown type or malformed entry ⇒ config load fails closed with a clear error naming the entry — a broken mapping file must not half-load.
  • Duplicate (repo, branch) keys ⇒ load error (ambiguity is refused, not resolved).

3. Resolution semantics by job type

Given caller input (repo, branch?, pr?):

  • multibranch — branch job addressed as <job>/<url-encoded-branch> (e.g. feature/xfeature%2Fx). PRs addressed as <job>/PR-<number> (Jenkins multibranch PR-discovery naming). Both per #72 §8.
  • single — the job path is used as-is; branch/pr input beyond the entry's pinned branch is a no-mapping result (a single job cannot answer for arbitrary branches).
  • parameterized-view — read-only variant for jobs that encode branch as a build parameter: resolution returns the base job path plus a branch_param filter hint the status tools may apply client-side when scanning recent builds. It never triggers anything (read-only rule).

4. Precedence

Most-specific entry wins, evaluated in this order:

  1. (repo, branch) exact entry — branch-pinned override.
  2. (repo) entry — repo-wide (multibranch typical).
  3. Nothing → no mapping (§5).

PR input resolves through the same chain: a PR belongs to its base repo's mapping; forks never introduce their own mapping (a fork's head repo is not consulted — CI runs live under the base repo's job). If the base repo is unmapped, the PR is unmapped.

Ties are impossible by construction (duplicate keys refused at load).

5. No-match behavior

{
  "mapped": false,
  "repo": "org/unknown-repo",
  "branch": "master",
  "error": "no Jenkins job mapping for this repo/branch",
  "hint": "add an entry to the jenkins-mcp mapping config"
}
  • Deterministic, explicit, machine-checkable (mapped: false).
  • Never falls back to name construction ("repo name probably equals job name"), never probes Jenkins for candidates, never string-similarity ranks.
  • The hint names the config, not a guessed job.

6. Where the mapping config lives

  • In the jenkins-mcp package/deployment (e.g. jenkins-mcp/mapping.toml), version-controlled next to the server that consumes it — not in Gitea-Tools and not in per-user env vars (mappings are shared team facts, not credentials).
  • Path overridable via env (JENKINS_MCP_MAPPING_FILE) for tests/containers.
  • Contains no secrets — job paths and repo names only — so it is safe to commit and review like any other config.
  • Reloaded at server start; a hot-reload tool is out of scope (restart is the documented path).

7. Exposed tool surface (read-only)

One addition to the #72 tool set:

Tool Purpose
jenkins_resolve_job (repo, branch?, pr?){mapped, job, addressed_path, type} or the §5 no-match result. Pure config lookup — no Jenkins API call at all.

Status tools (jenkins_latest_build etc.) accept either an explicit job path (as designed in #72) or (repo, branch) which they resolve via the same mapping layer first. Resolution failure surfaces the §5 payload rather than querying Jenkins.

8. Testing strategy (mocked; for the implementing package)

Config-layer tests (no network at all):

  • Exact-match hit: repo-wide and branch-pinned entries.
  • Precedence: branch-pinned beats repo-wide.
  • Multibranch encoding: feature/x<job>/feature%2Fx; PR → <job>/PR-7.
  • single type with non-pinned branch ⇒ no-mapping.
  • Fork PR resolves through base repo; unmapped base ⇒ no-mapping.
  • Unknown repo/branch ⇒ §5 payload, and no Jenkins client call (mock_api.assert_not_called()).
  • Malformed config / duplicate keys / unknown type ⇒ load fails closed with entry-naming error.
  • No-secret check: mapping load/error paths never touch or print credentials.

Integration with mocked Jenkins API (per #72 §9): resolved path is used verbatim in the GET URL; no write verbs anywhere.

9. Standalone-worthiness and readiness

#77 was split from #72 on the condition it stays "standalone only if mapping is nontrivial." The precedence rules, fork/PR semantics, three job types, and fail-closed config loading above are the nontrivial part; this document is the justification.

Ready to implement in jenkins-mcp when #72's readiness checklist clears (ADR-0001 owner decision #1; profile schema per #76 or hand-rolled jenkins-readonly). Nothing here unlocks build triggers, deploys, or parameterized launches.