From 74a7e8f7929b5986fb60c5c79b1a5bc9e7043e87 Mon Sep 17 00:00:00 2001 From: Jason Walker <913443@dadeschools.net> Date: Thu, 2 Jul 2026 14:34:18 -0400 Subject: [PATCH] docs: Jenkins read-only build status tools design (#72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add docs/architecture/jenkins-readonly-build-status-design.md: implementation-ready design notes for the jenkins-mcp read-only tool set — minimum tools (whoami, list_jobs, latest_build, build_status, get_build, gated console_tail), safe return-field allowlist (url, number, timestamp, duration, branch, result, commit), fail-closed failure behavior (unknown job, unreachable, 5xx, auth, malformed JSON), bounded+redacted console tail behind a distinct jenkins.console.read operation, per-service credential/ profile requirements (token by reference, fail closed), explicit exclusions (build/deploy triggers, parameterized launches), job addressing with mapping deferred to #77, and a mocked-Jenkins testing strategy. Design only; no implementation, no code behavior changed, no Jenkins code in mcp_server.py. Co-Authored-By: Claude Fable 5 --- .../jenkins-readonly-build-status-design.md | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/architecture/jenkins-readonly-build-status-design.md diff --git a/docs/architecture/jenkins-readonly-build-status-design.md b/docs/architecture/jenkins-readonly-build-status-design.md new file mode 100644 index 0000000..7e231e5 --- /dev/null +++ b/docs/architecture/jenkins-readonly-build-status-design.md @@ -0,0 +1,151 @@ +# Jenkins Read-Only Build Status Tools — Design Notes + +- **Status:** Design (implementation-ready notes; **no implementation in this repo**) +- **Issue:** #72 (parent umbrella: #75; boundary decision: ADR-0001, #71) +- **Related:** #77 (repo/branch/PR → job mapping, designed separately) +- **Date:** 2026-07-02 + +## 1. Purpose and scope + +Define the minimum **read-only** Jenkins MCP tool set that lets an LLM answer: +*"Did the latest build for this project/branch succeed or fail?"* — plus enough +detail (build URL, number, timing, result) to report or investigate. + +Phase 1 is **strictly read-only**, per ADR-0001 +([`adr-0001-mcp-control-plane-boundaries.md`](adr-0001-mcp-control-plane-boundaries.md)): + +- **Excluded: build triggers.** +- **Excluded: deploy triggers.** +- **Excluded: parameterized job launches.** +- Excluded: job creation/deletion/config changes, queue manipulation, node + management — any Jenkins mutation whatsoever. + +## 2. Boundary placement + +These tools belong to the **`jenkins-mcp`** package/server of the MCP Control +Plane — **never** inside `gitea-mcp` (`mcp_server.py` in this repo). +Consequences (from `tool-boundaries.md`, `credential-isolation.md`, ADR-0001): + +- `jenkins-mcp` runs as its own server process with its own `.env`. +- **Jenkins credentials never enter the Gitea MCP runtime**, and Gitea + credentials never enter `jenkins-mcp`. +- This document lands in this repo only because the repo currently hosts the + Control Plane's architecture docs; the code ships elsewhere (owner decision + #1 of ADR-0001). + +## 3. Minimum read-only tool set + +| Tool | Purpose | +|---|---| +| `jenkins_whoami` | Verify authenticated Jenkins identity + active profile (mirror of `gitea_whoami`; fail-closed identity proof before anything else) | +| `jenkins_list_jobs` | List visible jobs (supports folder paths), with pagination bounds | +| `jenkins_latest_build` | The primary question: latest build of a job (or job+branch for multibranch) → status summary | +| `jenkins_build_status` | Status of a specific build number (job, number) | +| `jenkins_get_build` | Full safe detail of a build (fields in §4) | +| `jenkins_console_tail` | Bounded, redacted tail of a build's console log (§6) — optional, approval-gated addition | + +All tools are `GET`-only against the Jenkins JSON API (`/api/json`, +`.../lastBuild/api/json`, `.../consoleText`). No tool issues POST/PUT/DELETE. + +## 4. Return payloads (safe fields) + +`jenkins_latest_build` / `jenkins_build_status` / `jenkins_get_build` return: + +| Field | Source | Notes | +|---|---|---| +| `job` | request | Fully-qualified job path (folders joined with `/`) | +| `build_number` | `number` | int | +| `result` | `result` | `SUCCESS` / `FAILURE` / `UNSTABLE` / `ABORTED` / `NOT_BUILT`; `null` → `IN_PROGRESS` when `building=true` | +| `building` | `building` | bool | +| `url` | `url` | Build URL | +| `branch` | multibranch job name / SCM action | Best-effort; omitted when unknown | +| `timestamp` | `timestamp` | ISO-8601 UTC (converted from epoch ms) | +| `duration_seconds` | `duration` | 0/omitted while building | +| `commit_sha` | SCM build action | Best-effort; omitted when unknown | + +Rules: no raw Jenkins payload passthrough (allowlist projection only); no +`Authorization` header, token, or crumb material in any output or error +(reuse the shared redaction approach of `safety-model.md` §3 / `gitea_audit`). + +## 5. Failure behavior (fail closed, clear, safe) + +| Condition | Behavior | +|---|---| +| Unknown job | Explicit `{"found": false, "job": "", "error": "job not found"}` — never guess or fuzzy-match a job name (hard rule; see also #77) | +| Jenkins unreachable (DNS/timeout/conn refused) | Clear `"network error contacting Jenkins: "`; no retry storm — mirror `gitea_auth.api_request` timeout + failure conversion | +| 502/503/504 | Explicit "Jenkins upstream unavailable" | +| 401/403 | "Jenkins auth failed / insufficient permissions" — **without** echoing credentials or the request's auth material | +| Malformed JSON | "malformed JSON response from Jenkins" (no raw-body dump) | +| Missing profile/creds | Fail closed before any network call (§7) | + +## 6. Console tail safety (`jenkins_console_tail`) + +Console logs are the highest-risk surface (secrets, tokens, internal hosts +routinely leak into build logs). If included at all (owner may defer it): + +- **Bounded:** hard server-side cap (default: last 200 lines AND ≤ 64 KiB, + whichever is smaller; caller may request less, never more). +- **Redacted:** pass through the shared secret redactor (token/`Basic`/`Bearer`/ + password/key-value patterns) before returning; redaction failure ⇒ return an + error, never the raw text. +- **Default off:** summary fields (`result`, failing stage if cheaply available) + are preferred; the tail requires an explicit `allowed_operations` entry + (`jenkins.console.read`) distinct from plain `jenkins.build.read`. + +## 7. Credentials and profile requirements + +Follows the per-service profile model (`gitea-execution-profiles.md`, extended +by #76): + +- Env/config: `JENKINS_URL`, `JENKINS_USER`, `JENKINS_TOKEN_SOURCE_NAME` + (name-of-secret only — value resolved at runtime, never logged/committed). +- Profile: e.g. `jenkins-readonly` with namespaced + `allowed_operations: ["jenkins.read", "jenkins.build.read"]` + (+ `jenkins.console.read` only if the tail tool is approved); + `forbidden_operations: ["jenkins.build.trigger", "jenkins.deploy", "jenkins.job.configure"]` + as belt-and-braces even though no mutating tool exists. +- Missing URL/user/token/profile ⇒ **fail closed** with a clear message. +- Since every tool is read-only, no confirmation gates are needed — but + identity (`jenkins_whoami`) must still work so workflows can prove which + Jenkins account they act as. + +## 8. Job addressing and mapping + +Tools accept an explicit fully-qualified job path (folder-aware: +`folder/subfolder/job`). How a *repo/branch/PR* resolves to that job path is +**out of scope here** and designed in **#77**, with these fixed constraints: + +- No silent guessing of job names — unmapped input returns an explicit + "no mapping" result. +- Multibranch pipelines address a branch job as `/` with proper + URL-encoding of branch names (e.g. `feature%2Fx`). + +## 9. Testing strategy (for the implementing package) + +Mocked-Jenkins unit tests only (no live Jenkins in unit CI), mirroring this +repo's conventions (`docs/developer-testing-guidelines.md`): + +- Patch the HTTP client; assert method is always `GET` and URL shape is correct + (folders, multibranch encoding). +- Success projections: field allowlist exactly as §4; unknown fields dropped. +- `result=null + building=true` ⇒ `IN_PROGRESS`. +- Unknown job ⇒ found:false, no fuzzy match, no API retry. +- Timeout/DNS/5xx/malformed-JSON ⇒ safe errors, no secret/credential leakage + (explicit no-token-in-error assertions). +- Console tail: cap enforcement (lines and bytes), redaction applied, redaction + failure ⇒ error not raw text, gated behind `jenkins.console.read`. +- Profile gate: missing/insufficient profile ⇒ no network call + (`mock_api.assert_not_called()` pattern). + +## 10. Implementation-readiness checklist + +Ready to implement in `jenkins-mcp` once: + +1. ADR-0001 owner decision #1 (where `jenkins-mcp` lives) is made. +2. #76 profile schema exists (or a minimal `jenkins-readonly` profile is + hand-rolled to the same rules). +3. #77 mapping design is accepted (or tools ship path-addressed only, mapping + deferred). + +Explicitly **not** unlocked by this document: build triggers, deploys, +parameterized launches, any Jenkins code in `mcp_server.py`.