tools/mail-patch/Capability: contract:change-request
Kind: implementation
Vendor: email
Organization: ASF
[PATCH]-mail change-request adapter — the backend that lets a project driving code review over its developer mailing list use the pr-management-* skills. It implements the tools/change-request/ contract for the oldest review mechanism in open source: a patch posted to dev@ as a [PATCH] thread, discussed in replies, and committed to trunk by a committer.
There is no forge behind this backend at all. The “pull request” is a mail thread; the “diff” is the [PATCH] body or attachment; the “review” is the reply chain. This adapter composes two contracts the framework already ships:
contract:mail-archive (tools/ponymail/ at the ASF) — thread search, thread fetch, participant extraction.contract:mail-create — the adapter never sends mail autonomously; post_review and reject produce a drafted reply the maintainer sends by hand.Its land verb, like jira-patch, delegates the actual commit to the project's contract:source-control adapter (svn patch + svn commit) and then drafts an “applied in rNNNNN” reply on the thread.
mail-archive, mail-create, and source-control adapters, no local package.svn for the delegated land.dev@ archives (PonyMail); an authenticated mail-create session to compose replies (drafts only — never sends); and ASF committer credentials for the SVN commit that land delegates to tools/asf-svn/. As with jira-patch, the commit runs under the landing committer's identity and credits the patch author in the commit message — see Attribution.lists.apache.org) and svn.apache.org for the delegated land.The mail-patch backend resolves the change-request as: a [PATCH] thread on the developer list is one change proposal. The thread's opaque archive id is the proposal id; the [PATCH] body/attachment is the diff; the reply chain is the review discussion. A follow-up [PATCH v2] thread is a superseded-then-new proposal.
Each change-request verb resolves onto the mail surfaces as follows.
| Verb | mail-patch resolution |
|---|---|
list_open(filter) | mail-archive.list_recent_threads(dev-list, since) filtered to [PATCH]-subject threads that have no terminal “applied”/“rejected” reply. One proposal_summary per open patch thread. |
get(id) | mail-archive.fetch_thread_by_url(id); the diff is extracted from the first message's [PATCH] body or .patch attachment. base is the trunk from config; commits is []; mergeable is unknown until an svn patch --dry-run. |
get_discussion(id) | The thread's reply chain, normalised to {author, date, body, kind}. A reply containing the configured approval token (LGTM / +1) maps to kind: approval. |
post_review(id, verdict, body) | Drafts a threaded reply via contract:mail-create (in-reply-to the thread), never sends. The verdict shapes the reply's opening line (+1, needs work); the maintainer reviews and sends. |
land(id, strategy) | Delegates to contract:source-control. Extracts the patch via get, calls the source-control adapter's apply + commit (svn patch <file> then svn commit — see tools/asf-svn/source-control.md), then drafts an “applied in rNNNNN, thanks!” reply on the thread. strategy is advisory — SVN lands a patch as one commit (squash). |
reject(id, reason) | Drafts a threaded reply carrying reason. No commit — the absence of a land is the rejection. Nothing is transitioned; the thread simply closes socially. |
status(id) | checks: none (a mail thread has no CI), mergeable from an svn patch --dry-run. Skills degrade the checks gate to advisory and fall back to a human-judgement prompt (the contract's status graceful-degradation path — this is the maximally-degraded backend). |
The delegated land credits the patch author in the SVN commit message (Patch by <From: header>.) while committing under the landing committer‘s identity — the same convention ASF committers follow when applying a mailed patch by hand. The adapter reads the author from the [PATCH] message’s From: header; it never impersonates the author‘s identity. This answers #669’s “patch-author vs. committer attribution” open question for the mail-patch backend.
A [PATCH] body has no forge-assigned id, so the adapter derives a stable proposal id from the thread‘s archive permalink (the mail-archive thread hash). Re-posted [PATCH v2] threads get a new id and mark the prior thread superseded — this is the mail-patch answer to #669’s “diff identity normalization across backends” open question: identity is the thread, not the diff bytes.
Declared under the change-request block in projects/<project>/project.md:
change_request: backend: mail-patch land_via: source-control # land delegates to the VCS adapter review_channel: mailing-list default_strategy: squash # SVN applies a patch as one commit mail_patch: dev_list: dev@<project>.apache.org patch_subject_prefix: "[PATCH]" approval_token: "LGTM" # reply token that counts as an approval trunk_url: https://svn.apache.org/repos/asf/<project>/trunk
dev_list — the list list_open scans and replies draft against.patch_subject_prefix — the subject marker that identifies a patch thread (some projects use [PATCH], some [PROPOSAL]).approval_token — the reply string get_discussion reads as a kind: approval.trunk_url — the SVN trunk the delegated land applies to.Backend-specific keys live under change_request.mail_patch.*; the generic keys are the contract's.
tools/change-request/tools/mail-archive/ (ASF: tools/ponymail/)contract:mail-createtools/asf-svn/source-control.md