From 1bc2f206236d02c5b44ab737cbadb135b5102e52 Mon Sep 17 00:00:00 2001 From: Jason Walker <913443@dadeschools.net> Date: Thu, 2 Jul 2026 15:09:48 -0400 Subject: [PATCH] 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- 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 --- .../jenkins-job-mapping-design.md | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/architecture/jenkins-job-mapping-design.md diff --git a/docs/architecture/jenkins-job-mapping-design.md b/docs/architecture/jenkins-job-mapping-design.md new file mode 100644 index 0000000..e0b04ff --- /dev/null +++ b/docs/architecture/jenkins-job-mapping-design.md @@ -0,0 +1,165 @@ +# 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`](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): + +```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 `/` + (e.g. `feature/x` → `feature%2Fx`). PRs addressed as `/PR-` + (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 + +```json +{ + "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` → `/feature%2Fx`; PR → `/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.