feat: add message and list ids to cveprocess links (#11)

Towards a richer #5
diff --git a/app/reports.py b/app/reports.py
index e6c9d30..36b62fa 100644
--- a/app/reports.py
+++ b/app/reports.py
@@ -68,11 +68,10 @@
     jira: str
     """If this project tracks security issues in private jira issues, the Jira ID"""
     title: str
-    asf_member_link: str
-    """link to the first email in the thread. this might only be
-       available to ASF members. make-report.py does something
-       smarter to give a link that's likely to also be accessible
-       by PMC members that are not ASF members."""
+    messageid: str
+    """message_id of the first email in the thread."""
+    listid: str
+    """list id of the first email in the thread."""
     link: str
     """link to the email archive for project members who may not
        necessarily be ASF members."""
@@ -95,6 +94,10 @@
         cleaned = re.sub(r"\s+", " ", cleaned).strip()
         return cleaned[:200]
 
+    @property
+    def asf_member_link(self) -> str:
+        return _ponymail_link(self.messageid, self.listid)
+
 def _known_bad_address(time, address):
     mailtime = datetime.datetime.fromtimestamp(time, tz=datetime.timezone.utc).date()
     spark_retirement = datetime.date.fromisoformat("2026-02-16")
@@ -113,20 +116,16 @@
             return address
     return None
 
-def _asf_member_link(email):
-    apache_list_address = _apache_list_address(email)
-    if apache_list_address:
-        listid = apache_list_address.replace('@', '.')
-    else:
-        listid = 'security.apache.org'
-    messageid = email['message_id'].replace(' ', '+').replace('+', '%2B').replace('=', '%3D').replace('@', '%40')
-    return f"https://lists.apache.org/thread/{messageid}?<{listid}>"
+def _ponymail_link(messageid, listid):
+    partly_encoded_messageid = messageid.replace(' ', '+').replace('+', '%2B').replace('=', '%3D').replace('@', '%40')
+    return f"https://lists.apache.org/thread/{partly_encoded_messageid}?<{listid}>"
 
 def _project_link(emails):
     for email in emails[:5]:
-        if _apache_list_address(email):
-            return _asf_member_link(email)
-    return None
+        list_addr = _apache_list_address(email)
+        if list_addr:
+            return _ponymail_link(email['message_id'], list_addr.replace('@', '.'))
+    return _ponymail_link(emails[0]['message_id'], "security.apache.org")
 
 def _reporter(email) -> Reporter | None:
     addresses = [a for a in getaddresses([email.get('from', '')]) if a[1]]
@@ -196,14 +195,21 @@
     elif title.startswith("[Security] "):
         title = title.removeprefix("[Security] ")
 
+    apache_list_address = _apache_list_address(first_email)
+    if apache_list_address:
+        listid = apache_list_address.replace('@', '.')
+    else:
+        listid = 'security.apache.org'
+
     return Report(
         path.name,
         cves,
         github,
         jira,
         title,
-        _asf_member_link(first_email),
-        _project_link(emails) or _asf_member_link(first_email),
+        first_email['message_id'],
+        listid,
+        _project_link(emails),
         _reporter(first_email),
         state,
         subproject,
diff --git a/app/templates/project.html b/app/templates/project.html
index 8b4007f..b4524d3 100644
--- a/app/templates/project.html
+++ b/app/templates/project.html
@@ -62,7 +62,7 @@
               <div><a href="https://cveprocess.apache.org/cve5/{{cve}}">{{ cve }}</a></div>
             {%- endfor -%}
             {%- if report.state == 'confirmed' and not report.cves -%}
-              <div><a href="https://cveprocess.apache.org/allocatecve?pmc={{ project_name|urlencode }}&amp;title={{ report.sanitized_title|urlencode }}">allocate CVE</a></div>
+              <div><a href="https://cveprocess.apache.org/allocatecve?pmc={{ project_name|urlencode }}&amp;title={{ report.sanitized_title|urlencode }}&amp;messageid={{ report.messageid|urlencode }}&amp;listid={{ report.listid|urlencode }}">allocate CVE</a></div>
             {%- endif -%}
           </td>
           <td class="reports-reporter">