# The upstream commit this fork has been reviewed against.
#
# The fork's git history is squashed and disjoint from upstream's, so comparing
# by commit reports all ~730 upstream commits as "missing" - including "First
# commit" - which is useless. A triage therefore reads only the commits AFTER
# the SHA below, and this SHA is advanced as each batch is triaged. (The GitHub
# workflow upstream-watch.yml read it for that until GitHub suspended the
# account on 18 September 2026; nothing on GitLab reads it, and a triage now
# starts by hand from `git log <SHA>..master` in a clone of upstream
# hMailServer, https://github.com/hmailserver/hmailserver.)
#
# Triaged 2026-08-12, content-first (reading our functions, not matching commit
# hashes), covering the 22 commits from 4fb63e806758 to 5ce853685dec:
#
#   - 19 ALREADY PRESENT, several as supersets of upstream's fix. Our partial-FETCH
#     clamp additionally handles negative start and negative count, which upstream
#     still does not.
#   - bc9e37750abe (TLSOptions #520) SUPERSEDED: the two UI files it changes do not
#     exist here (the classic Administrator and the PHP WebAdmin were replaced),
#     and we enforce the ChaCha constraint server-side in SslContextInitializer,
#     which upstream does not do at all.
#   - 5ce853685dec (libpq 15.18) NOT APPLICABLE: a downgrade. We are deliberately
#     ahead on the dependency set (PostgreSQL 18.3, OpenSSL 4.0.x, Boost 1.91).
#   - Nothing required porting.
#
# Two upstream hunks are deliberately NOT taken and should stay that way:
#   - From 61c6e3cb3b7f (#528): "if (error == boost::asio::error::eof) return;"
#     inside the read-error branch. It returns before popping the read operation
#     and before EnqueueDisconnect, leaking the operation and leaving the session
#     open, and it suppresses OnReadError - which is what marks recipients when a
#     remote server hangs up mid-delivery. We handle the same case by disconnecting
#     after an end-of-stream instead.
#   - The libpq downgrade above.
#
# Known cosmetic-only divergence from 02b7f1e00278 (#530), tracked but not taken:
# the Delimeter->Delimiter and has_on_too_many_invalid_comands_ renames, a
# #define -> constexpr, and some comment typos. Semantically identical; taking
# them would only reduce future textual merge friction.
#
# Triaged 2026-08-15: one commit since the previous baseline.
#
#   - a998b528f9ba (link libpq to OpenSSL, #531/#538) ALREADY PRESENT: a
#     README-only build-recipe change. Upstream adds a config.pl openssl entry to
#     their libpq 15 build so the DLL can make encrypted PostgreSQL connections;
#     our README has built libpq 18.x through meson with -Dssl=openssl (plus the
#     same Dependencies-tool verification tip) since the recipe was written.
#
# The same pass re-verified, content-first, a sample of the pre-baseline batch
# against our tree: route+catch-all duplication (#200) early return, IMAP STATUS
# escaping (#85), the Mime FindString/trailing-newline rewrite, BODY[X.MIME]
# (#459) show_body_mime_/loadEncapsulated, the auto-reply cache clear (#268),
# the full-scan line-ending check, DKIM-sign-aliases and the spam-flag
# reply/forward suppression. All present, as the 2026-08-12 triage recorded.
#
# Triaged 2026-09-04: 71 commits since the previous baseline. Upstream is active
# again (Martin Knafve, RvdH, from mid-August 2026). Content-first, three
# read-only passes partitioned by area, every "already present" verdict tied to
# a named function in our tree. The previous month's scheduled run had computed
# this list and then lost it on a token permission error creating the tracking
# issue; upstream-watch.yml then created the issue through the REST endpoint.
#
#   PORTED in 6.2.23 Alpha 2 (each with a test where the suite can reach it):
#   - b558cc7b8 (#546/#168) Received-header IP parsing: the first bracketed
#     value after "from" is the HELO literal the CLIENT chose; the observed
#     address is the last one before "by". A header whose host name was not a
#     valid domain name was skipped entirely, letting parsing fall through into
#     headers the sender wrote, or leaving no originating address at all - so
#     DNSBL/SPF/HELO tests ran against a sender-chosen IP or not at all.
#     Utilities::GetIPAddressFromReceivedHeader rewritten; the two assert(0)
#     on sender-reachable input removed; RetrieveReceivedIPList_ keeps a header
#     without a host name. Upstream's in-tree tests taken with it.
#   - 2f399fdb9 (#605) + b57256ee2 (#604): ResetCurrentMessage_ no longer
#     promotes INITIAL to HEADER, and OnHandshakeCompleted returns to INITIAL,
#     so neither RSET-before-EHLO nor STARTTLS-without-re-EHLO can open a
#     transaction with helo_host_ empty (which skipped SpamTestHeloHost and the
#     OnHELO/OnEHLO events). #604's local save/restore is superseded by #605's
#     guard and is not taken separately. Two regression tests in
#     SSL/StartTls/SmtpServerTests.cs.
#   - d5e087066 (#609) PTR trailing dot only: a fully qualified PTR owner name
#     no longer fails the equality filter in DNSResolverWinApi::RunQuery_. The
#     rest of #609 (malloc/free, |=, custom-server fallback) is NOT APPLICABLE:
#     our DnsQueryEx rewrite has none of that code and already ORs the flag.
#   - db478a671 (#583) engine_ null guard in ScriptSite::ProcedureExists.
#   - 3d0cf55fe (#580), the null-folder guards only: IMAPNotificationClient
#     dereferenced GetCurrentFolder() on the notifying thread while the owning
#     session could be closing it. The rest of #580 is a roadmap row.
#   - 9e33a6f3e (#534), the COM half only: InterfaceIMAPFolder::Delete now
#     reports a refused deletion instead of S_OK. The orphaned-row defect itself
#     is NOT APPLICABLE - our PersistentIMAPFolder::DeleteObject keeps only the
#     inbox, never a special-use folder.
#   - a643b33c7 (#579), one line of it: the installer raises when the database
#     step fails, so setup exits non-zero instead of 0 with a suppressible box.
#     The password forwarding and the silent-mode behaviour were already here,
#     and deeper (the upgrade path forwards it too; Authenticator fails rather
#     than prompts under /silent).
#
#   ALREADY PRESENT, as supersets (nothing to port):
#   - 4fe93f9f2 (#565) file left open after DATA: TransparentTransmissionBuffer
#     ::Flush closes on transmission_ended_ in both modes, fsyncs when asked
#     and checks the result; the empty-buffer early return is already gone.
#   - a41b9d175 (#553) kept-open log flush: our #33 fix of 2026-08-16, with a
#     checked File::Flush and a separate FlushToDisk.
#   - 2f37c6c50 (#556) atomic Move: two-argument Move with the return value
#     checked at both call sites. Residuals (backoff, GetLastError in
#     LoadHeader) are cosmetic.
#   - 174a67db7 (#595) OnClientLogon from AUTHENTICATE: fired from four paths
#     here (PLAIN, OAUTHBEARER/XOAUTH2, SCRAM success and failure).
#   - 7e250b408 (#594) POP3 -ERR with [SYS/TEMP].
#   - e4485b55a (#603) duplicate-UID guard in MessageCleanup_ (the RAII lock
#     half is a roadmap row).
#   - 2a1e18ce2 (#537) accountid subquery, independently derived.
#   - e5352cb9a (#597) DMARC: ours adds ARC, aggregate reporting and a
#     compiled-in public suffix list.
#   - 239f7364b (#550) SPECIAL-USE: complete, plus backup of the designation
#     (auto-create at account creation is a roadmap row).
#   - 620cf52d9/4f5e35c25 (#559/#582) MariaDB Connector/C: vendored with its
#     auth plugins (the TLS climb-down is a roadmap row).
#   - 059dc3691 wait for the service to stop: ours polls to STOPPED on a budget
#     derived from ShutdownDrainSeconds and blocks the install on failure.
#   - f05a14493 (#601) GetPartialFilename: fixed here case-insensitively (the
#     tool-side public-folder walk is a roadmap row).
#   - 13aeee3bf (#573) endless read loop: SUPERSEDED - TCPConnection
#     disconnects after any end-of-stream (the counterpart of the eof hunk
#     from #528 that this file already records as deliberately not taken).
#   - Cache setter locking from #580, Collection::GetCount and
#     GetRecentMessageIDs from #566, DeleteItemByDBID result from #534.
#
#   NOT APPLICABLE:
#   - e09490a0a/29fef7c2e OpenSSL 3.5.x, b5bd0b500 PostgreSQL 15.19: behind us
#     (4.0.1, 18.3). 1b7c248c4 Boost 1.92: newer than our 1.91 by one routine
#     release; a maintenance bump, not a fix. Side finding: libraries/
#     asio-overrides is referenced by nothing in hMailServer.vcxproj.
#   - c4df877e2 MimeCode comment typo: we keep nDelimeter, recorded above.
#   - 0f307c57f codespell pass, d8528aaf1 bundled translations: our text and
#     string catalogues have diverged; run codespell on our own tree instead,
#     and do not import 40 catalogues that lack every fork-specific string.
#     The dead Language::Download / InterfaceLanguage::Download pair is a COM
#     method and stays, since removing an IDL method shifts the vtable.
#   - 7ce4538e8 (#541) MySQL subquery tidy-up: no defect either way.
#   - 99c037b04 (#592) hosted-runner builds, fd1abf26f (#554) tunable hash
#     cost, 806304507 (#566) / 3d0cf55fe (#580) remainder, fce66080a (#602),
#     0f7c26762 (#552), f047b8c1b, a843b0ec4 (#558): roadmap rows under
#     "Planned work", each with the size and the reason it did not ride along.
#
# Ported 2026-10-13 (branch large-message-headers): 5841e577e, upstream issue
# #683, merged as PR #686 - a message over 80 MB. Here the write of such a
# message was already refused (MessageData::CheckWritable_, HM6010), so the blank
# message #683 reports never went out; what was left was the defect's other half,
# fixed in our code for the paths it names, with tests that fail on 74aed9ab8:
# the rule-loop count that an account's forward, a Sieve redirect, a rule's
# forward and copy and a bounce read and raise, the rules' header conditions and
# header actions, the spam headers of a message marked as spam and their removal
# from an account's copy, and the signature and the disclaimers, which are not
# appended to such a message and say so in the log
# (hmailserver/docs/MailFlowRules.md, "A message over 80 MB"). Upstream's
# SignatureAdder refusal is taken, with the log line beside it.
#
#   DELIBERATELY NOT TAKEN:
#   - The header-only load for EVERY caller once the file is over 80 MB
#     (MessageData::LoadFromMessage). Ours reads the header alone only for a
#     caller that asks (MessageData::LargeMessage::HeaderOnly): the other callers -
#     the virus scanner, the sandbox, the indexer, IMAP SEARCH, the REST routes,
#     COM's Message and more - would otherwise see a message with no body and no
#     attachments where they now see a load refused, and a scan of its
#     attachments would find nothing in a message it never read.
#   - Its Events.cs case, a script's header change saved over COM on such a
#     message (OnAcceptMessage, Message.HeaderValue, Message.Save). COM's Message
#     still refuses a message over 80 MB, so the change is not saved (HM6010) and
#     the message is kept as it was. Reading it header only there would change what
#     every script sees of such a message - its header, where it now sees none -
#     which is the owner's to decide. Until then a rule's Run function is not run
#     on such a message - its Message would show the script an empty one - and
#     the log says so (RuleApplier::ApplyAction_).
#   - Upstream's error numbers 5736-5738 (ours are HM7840-HM7842) and its own
#     temporary-file copy of the body in MimeBody::SaveAllToFile: ours copies it
#     in pieces through FileUtilities::WriteToFileAtomically, the writer every
#     rewrite of a message here goes through.
#
# Ported 2026-10-12 (branch dns-failed-lookup): 8141d66af, upstream PR #640,
# adapted - a DNS lookup that failed was read as "no records". An address lookup
# asks A and AAAA and was true when either query was answered, so a SERVFAIL or
# a timeout on A beside AAAA answered NODATA - every IPv4-only host's answer -
# was a name with no addresses: the HELO test scored what it could not look up,
# and a DNS blacklist or SURBL read the failure as "not listed". Upstream's rule
# is written into DNSResolver::GetIpAddressesRecursive_, with a status for each
# query: false unless addresses were found or every A and AAAA query was
# answered. The DNS blacklists and SURBL ask through
# DNSResolver::GetDnsListAnswers - the A record alone (RFC 5782), asked once
# more past the DNS cache when it fails, which is upstream's second attempt -
# and DNSResolverWinApi::Query takes upstream's bypassCache on our DnsQueryEx
# path (the POSIX resolver keeps no cache). SURBL passes over a name it could
# not look up in our concurrent SURBL::Run, as upstream's loop skips it; BLCheck
# writes upstream's "DNS lookup failed" line in place of "0 addresses found",
# where upstream writes both, and reports to AntiSpamDiagnostics. Beyond
# upstream: a CNAME query that fails after an answer whose records were not kept
# (on Windows the answer for an alias, which DNSResolverWinApi::RunQuery_ does
# not keep under the name asked) fails the address lookup,
# GetRecordsOfTypeRecursive_ and GetMXRecordsRecursive_, where upstream counts
# the CNAME query in none of them and leaves the record lookups as they were;
# after NODATA it changes nothing, as before, and a name answered NXDOMAIN is
# asked none. SpamTestPTR scores no reverse name whose forward lookup failed, as
# it scores no PTR lookup that failed (upstream leaves SpamTestPTR as it was;
# the owner kept it on 13 October 2026). GetEmailServersRecursive_ takes an
# address two exchanges share out on its failure return too, which the rule
# sends more exchanges to. AntiSpam/DnsLookupFailures.cs holds the spam tests,
# the delivery notice and the MX query to it, its cases of the defect failing on
# 74aed9ab8, and DNSResolverTester the lookups, where each part of the fix has a
# case that fails with it taken out.
#
#   DELIBERATELY NOT TAKEN:
#   - 8141d66af (#640), DNSResolver::GetIpAddressesWithRetry, which asks A and
#     AAAA twice: GetDnsListAnswers serves the only callers that retry, BLCheck
#     and SURBL, and asks the A record alone. Under the new rule an AAAA query
#     that failed beside a list's answer would fail a lookup the list answered,
#     and a list that does not answer would be waited for four timeouts where
#     two were waited for before.
#   - 8141d66af (#640), GetIpAddresses' bypassCache parameter: no other caller
#     asks an address lookup past the cache, and GetDnsListAnswers passes
#     bypassCache to DNSResolverWinApi::Query itself.
#
#   NOT APPLICABLE:
#   - 8141d66af (#640), VMTestRunner.Console/TestEnvironments.json: it enables
#     the machines of upstream's own test lab. Ours lists those of the 6.0
#     baseline, and is neither its pre-image nor its post-image.
#
# Ported 2026-10-12 (branch script-function-name-at-save): 4c03c0bb8, upstream
# PR #671, its save-time half, adapted. A rule's Run function action is refused
# when it is saved naming no function or a name that is not a plain function
# name - by COM's RuleAction.Save and Rule.Save and by the REST API's rule
# routes, with the reason - where it was stored as given and refused only each
# time the rule fired. RuleGuard::WhyScriptFunctionMayNotBeStored asks
# ScriptServer::IsPlainProcedureName, the test the firing path holds the name to
# (FireEvent on Windows, and ScriptBinding's IsIdentifier_ on Linux, which asks
# it too); RuleOwnerTester, Rules/Rules.cs, API/RestApiRules.cs and
# API/RestApiAccountRules.cs test it, and each fails without the check.
#
#   DELIBERATELY NOT TAKEN:
#   - 4c03c0bb8 (#671), RuleAction::IsValidScriptFunctionName and
#     HasValidScriptFunction: the name held to a test of its own, letters, digits
#     and _ only. It refuses $, which a JScript function on Windows and a
#     JavaScript one on Linux may be named with and which the firing path runs, so
#     a rule would be refused for a name that works. Ours holds the name, when it
#     is saved, to the firing path's own test.
#   - 4c03c0bb8 (#671), its refusal in RuleApplier::ApplyAction_ScriptFunction
#     (HM5735, Medium): a second check when the rule fires. ScriptServer::FireEvent
#     has refused such a name there since 6.3.4 (HM5018, High, before a Windows
#     engine sees it), and the Linux lookup never evaluates one.
#   - 4c03c0bb8 (#671), its check in InterfaceRule::Save of every action of the
#     rule, the stored ones with the one changed: a rule holding a name stored
#     before the check, or restored from a backup, could then not be saved for
#     any change, and the Control Panel, which stores a criterion or an action
#     before it saves the rule, left the part stored and told of it as not saved.
#     Ours (13 October 2026) holds an action at Rule.Save where the save changes
#     it - a new one, one changed or made another type, every action of a rule
#     moved to another account - and leaves one stored as it is to the firing
#     path, which refuses it.
#
# Triaged 2026-10-12: the 85 commits from the previous baseline to a843921bb (11
# October 2026), from a clone of upstream - 44 to 607dcc648, upstream's head when
# the SPF port read them on 15 September (Roadmap.md's row on them; this SHA was
# never advanced past them), and 41 after it. Content-first, each verdict against
# the function in our tree that does the job, at 74aed9ab8 (the line numbers
# below are that commit's). 14 of the 85 are merges; two hold a resolution of
# their own - a30c94ed7 (include paths and ClassTester lines) and 9f4f8aca5
# (their VERSION file, 5.8.0) - and none carries code of its own.
#
#   PORTED in this triage, each with a test that fails with the fix taken out
#   (save the two said below that no test can provoke):
#   - 1ffcc7c77 (#649 SRS), the findings of upstream's review that our SRS
#     (Util/SRS.cpp, SMTP/RecipientParser.cpp, SMTP/SMTPForwarding.cpp) shared:
#     an SRS0 address this server handed out was followed back to the original
#     sender as local delivery whatever the MAIL FROM, so the relay rules were
#     not asked of mail that was not a bounce - only a bounce is taken so now
#     (RecipientParser::CheckDeliveryPossibility), and the relay rules refuse
#     anything else at its DATA or BDAT, its RCPT TO answered as before for a
#     server verifying the address (SMTPConnection::ProtocolRCPT_, below); the
#     hash was compared with a string comparison (HashCreator::HexDigestsMatch,
#     constant time - no test can time it); an address 1023 days old read as
#     one day ahead and was accepted as clock skew (SRS::TimeSlotInRange_ takes
#     tomorrow's stamp in the last hour of today only, and keeps the 21st day to
#     its midnight); and a quoted or over-long sender was rewritten into an
#     address no server takes (SRS::ForwardAt). BATV (Util/BATV.cpp, not
#     upstream's) had the same comparison and the same 999-day wrap, and is
#     fixed with it. #649's BlowFishEncryptor buffer fix is ALREADY PRESENT:
#     Util/LegacyBlowfish.cpp sizes both halves from the input and bounds the
#     text (HM7277).
#   - 013bf4b41 (#656) the RFC 4616 authzid: decoded and ignored by SMTP, POP3 and
#     ManageSieve, so a client asking to act as another account was signed in as
#     itself and told it had succeeded; refused now (AccountLogon::
#     AuthorizationIdentityIsOwn), the authzid prepared with SASLprep as our
#     protocols prepare the authcid - upstream, which has no SASLprep, compares
#     both as they came. IMAP, which uses it for the master user, took an
#     authzid naming the client itself as a masquerade and answered BAD "No master
#     user defined."; it is taken as none. #656's parse (tabs in the password, the
#     split on NUL) was already here: StringParser::DecodeSaslPlain.
#   - b64edb5f1 (#654/#661) and #656's masking: SMTPConnection::LogClientCommand_
#     hid AUTH PLAIN only after EHLO and in one strict form, and never called
#     PasswordRemover's SMTP arm, so an XOAUTH2 or OAUTHBEARER token, a SCRAM
#     client proof or a PLAIN credential sent at another moment reached the SMTP
#     log. Every SASL line is masked now.
#   - 1db53268e (#668) a backup's outcome - the backup log's line, the error, the
#     alert, the OnBackupCompleted or OnBackupFailed script - was reported before
#     BackupTask released the run slot, so a backup started on the strength of it
#     was refused as already started: BackupManager::OutcomeHold.
#   - a7b5b7c47 (#665), the parser half: Utilities::GetRecipientFromReceivedHeader
#     ran a comment after the address - Gmail's "for <x@y> (Google Transport
#     Security)" - into it, and the external fetcher found no recipient.
#   - 5cf83a9fe/6ac3c58fd (#650/#666), the findings our IMAP shared: APPENDUID and
#     COPYUID given to a user who may not read the folder (RFC 4315 section 6;
#     IMAPCommandRangeAction::RecordCopyUidIfReadable, IMAPCommandAppend); a copy
#     or an append into a folder carrying \Deleted, \Draft, \Answered, \Flagged
#     and keywords the user may not set there (RFC 4314 section 4;
#     IMAPCommandRangeAction::KeepOnlySettableFlags); ACL refusals NO without
#     [NOPERM] (RFC 5530); and a COPY that failed part way leaving the copies it
#     had made (RFC 3501 6.4.7; IMAPCopy::RollBack).
#   - 6ac3c58fd (#666), the session's view: an expunge from another session let
#     out at the start of a COPY or a MOVE by number, which renumbered the set the
#     client had sent - "COPY 2" copied message 3 (IMAPConnection::
#     NotifyFolderChange now holds it back for both while they read their set,
#     and ReportExpungesHeldForCommand sends it in their own response before the
#     tagged one, as RFC 2180 4.4 asks - IMAPConnection::AnswerCommand before
#     the NO or BAD of one refused before it read its set; upstream held COPY
#     alone, to the next command, below); a COPY or UID COPY into the
#     selected folder never reported to the session that made it (IMAPCopy::
#     ReportCopiesInTheSelectedFolder, as APPEND and MOVE report theirs, and
#     claiming no \Recent in a session that has the folder open with EXAMINE,
#     as IMAPNotificationClient::ClaimRecentMessages_ claims none there); the
#     flag changes of other sessions sent before the EXISTS of the messages they
#     were for (IMAPNotificationClient::SendCachedNotifications); and EXPUNGE and
#     UID EXPUNGE taking new messages into the view with no EXISTS before an
#     EXPUNGE could number one (IMAPCommandExpunge.cpp:59, IMAPCommandUID.cpp:
#     309) - the one change of the four no test can provoke once COPY reports its
#     copies (its trigger is a delivery between its save and its notification).
#   - 4c03c0bb8 (#671), the literals: our JScript calls were escaped whole since 24
#     September (ScriptServer::EscapeForJScriptLiteral, every caller), but three
#     VBScript calls were quoted by hand - OnClientValidatePassword's password,
#     OnExternalAccountDownload's remote unique-id and OnDeliveryFailed's values -
#     so a lone CR or LF in a password or in a unique-id a POP3 server lists (the
#     listing is split at CR LF pairs only), or a lone LF in a remote server's
#     answer (a CR ends the line it is read from), left the literal open and the
#     handler was not called, and OnDeliveryFailed lost the answer's quotes
#     (Events.cpp, EscapeForVBScriptLiteral now, as upstream's ToStringLiteral
#     took all three; OnDeliveryFailed's error trimmed as before, below).
#   - 099f3f3dd/85a89fb1a (#658/#659): hMailServer.Minidump.exe had no version
#     resource, and nor had hMailServer.Updater.exe beside it; both take one from
#     Version.h now (hMailServer.Minidump.rc, hMailServer.Updater.rc), and
#     build/check-version-resources.py holds every C++ program to it. Not taken:
#     Interop.hMailServer.dll's version, TlbImp's from the type library's
#     version(1.0) (build/generate-com-wrapper.ps1:192) - the version .NET programs
#     bind to, not to be moved for a label; the C# tools carry <Version> already.
#     ControlPanel.Core.csproj (hMailCP.Core.dll) has none, and needs none: it is
#     the Control Panel tests' library, which ControlPanel.Tests.csproj alone
#     references; the installer ships ControlPanel\publish, the dotnet publish
#     of ControlPanel.csproj (build/ci/windows-installer.ps1), which does not.
#   - 1cf0bcd63's test fix: SessionLimitingTests.AssertMaxSessionCount asserted
#     "at least" (RetryableAssert.GreaterOrEqual), so a session count that had not
#     come down passed; and the OnClientConnect test asked the wrong protocol's
#     count, as did the IMAP test beside it, which upstream did not touch. Its
#     Events.cs line waits in a test of theirs this tree does not have (the
#     OnExternalAccountDownload UID through JScript; ours escapes it,
#     ScriptServer::EscapeForJScriptLiteral).
#
#   PORTED by the other units of the same wave (PLAN.md batch48 and batch50):
#   - 8141d66af (#640) a failed DNS lookup read as "no records": dns-failed-lookup.
#   - 86a5ce3b8 (#653/#664) more 5xx replies counted as invalid commands:
#     smtp-invalid-command-count.
#   - 5841e577e (#683/#686) messages over 80 MB: large-message-headers.
#   - 4c03c0bb8 (#671), a bad script function name refused when the rule is saved
#     (ScriptServer.cpp refuses it when the rule fires): script-function-name-at-save.
#   - 9f6d0a8a1 (#621) the backup's 7-Zip thread count: backup-7zip-threads.
#   - acb4d4ea7 (#647) DDS import into public folders: dds-public-folder-import.
#   - 3506329b2/d34fd6fa2 (#535/#622) In-Reply-To and References on DSNs,
#     auto-replies and virus notices: auto-reply-threading.
#   - 5d1d343fa (#620) ValidLanguages overwritten by an upgrade: batch55 (the
#     installer).
#
#   PORTED 15 September 2026: e304ce904, 9f539a48c, d173b0768, 36297f054 and
#   1beaeab9c (#642-#645) the SPF evaluator, SMTP/SPF/.
#
#   ALREADY PRESENT:
#   - 607dcc648 (#646) a backup that cannot be restarted: the run slot is a weak
#     reference to the task, so it is free when the task dies however it ends
#     (BackupManager.cpp:49-80); the second backup to a directory without
#     compression copies over what the first left (#234), and a file deleted
#     during the copy is skipped, by the backup's own copy (BackupExecuter.cpp:70-
#     110). Not taken: #646's catch in BackupTask::DoWork that turns an exception
#     into OnBackupFailed - our work queue contains it (WorkQueue::ExecuteTask) and
#     ExceptionHandler reports it, but no BACKUP ERROR line and no event follow.
#     What threw in upstream's (their FileUtilities copy) does not throw here, and
#     nothing a test can do makes our backup throw, so the catch waits for a seam
#     to provoke it: then a catch of std::exception there, as upstream's, reported
#     through OnBackupFailed, which BackupManager::OutcomeHold now delivers after
#     the run slot.
#   - 15d4d55d7 (#618) the service with fewer privileges: ServiceAccount
#     (docs/ServiceAccount.md, COM/InterfaceSettings, IniFileSettings), with
#     /ProtectFolders and /GrantServiceAccess.
#   - 922ddfff8 (#626) Interop.hMailServer.dll for the server component:
#     section_files_64.iss:53 ("server admintools"), and the synchronizer is
#     published with its own (:56).
#   - cfb763f54 (#613) each connection's socket operations in a strand
#     (TCPConnection.h:365-375, since 5 September). Not taken: its IMAP log line
#     written when the write completes rather than when it is queued (cosmetic).
#   - ff24290db (#611) a PTR answer's trailing dot (DNSResolverWinApi.cpp:543-552,
#     ported with #609); the pSrvList half is NOT APPLICABLE (DnsQueryEx). Not
#     taken: the dot stripped from every record type's name, which nothing has
#     shown Windows to return.
#   - 44b4c0b3a (#617) a newer 7-Zip: ours is 26.03 (third-party-binaries.json).
#   - 5abbec695: our TestOnExternalAccountDownload waits for the delivery
#     already.
#   - a8b888a43 (#670) HashCreator through EVP_Digest (HashCreator.cpp); DKIM.cpp's
#     EVP_MD_CTX_create/destroy are the macros of _new/_free. Its build switch is a
#     row below.
#   - The rest of #666 that ours had: TRYCREATE (IMAPCommandCopy.cpp:59-68), a set
#     naming a message twice acting once (IMAPCommandRangeAction::
#     ResolveTargets_), a UIDVALIDITY for a folder made again within the second
#     (PersistentIMAPFolder::NextFolderCreationTime_, within one process), EXPUNGE
#     refused NO without the right, [OVERQUOTA] on APPEND, COPY and MOVE
#     (IMAPCommandAppend.cpp:670, IMAPCopy.cpp:75, IMAPMove.cpp:79), a COPY naming
#     a message expunged elsewhere refused before anything is copied
#     (MissingMessagePolicy::FailBeforeActing, IMAPCopy.h:19) and taken back when
#     a file will not copy (IMAPCopy::RollBack, above), and SEARCH's sets read
#     against the session's own numbers with "*" its largest (IMAPCommandSEARCH::
#     MatchesUIDCriteria_ and MatchesSequenceSetCriteria_, IMAPListLookup::
#     ToRuns) - upstream's had read them through an older IMAPListLookup
#     parse.
#
#   NOT YET PORTED - larger than this triage took on; each is a row of its own
#   under Planned work in Roadmap.md, which points here for the plan the triage
#   wrote for it:
#   - 1ffcc7c77 (#649): the SRS reversal below the account, alias and list lookups
#     (an account named srs0-x@ is unreachable, the catch-all is passed over); the
#     forward rule action and global rules rewriting the sender (RuleApplier::
#     ApplyAction_Forward); no rewrite for a forward that stays here; SRS1
#     chaining; an undeliverable SRS bounce reported.
#     The plan, about two days with tests in SMTP/Srs.cs: in
#     RecipientParser::CheckDeliveryPossibility (RecipientParser.cpp:112-134,
#     ahead of every lookup, "unknown user" when it does not reverse) reverse an
#     SRS0 address only once the account, alias and list lookups of the domain
#     have found nothing, as upstream does (SenderRewriteScheme::TryReverse), so
#     that an account of that name and the catch-all still answer; make RuleApplier::ApplyAction_Forward
#     (RuleApplier.cpp:1042-1046, the naive RewriteEnvelopeFromWhenForwarding only)
#     and a global rule's forward call SRS::Forward as SMTPForwarding does, for an
#     external sender and an external target; skip the rewrite when every target
#     is local; give SRS1 to an SRS0 sender forwarded again; and answer an SRS
#     address that is ours but expired with that reason, not "unknown user".
#   - a7b5b7c47 (#665): envelope-from and "for <recipient>" in our own Received
#     header (upstream #663, a feature). The plan, half a day: in
#     SMTPMessageHeaderCreator (Received: at SMTPMessageHeaderCreator.cpp:186) add
#     " (envelope-from <x>)" for an unauthenticated client and " for <rcpt>" when
#     the message has exactly one recipient - several would show a Bcc - as
#     upstream's does; SMTP/ReceivedHeaders tests for one, two and an
#     authenticated sender; the Received-header parsers already read both forms.
#   - a8b888a43 (#670): OPENSSL_API_COMPAT and OPENSSL_NO_DEPRECATED on our own
#     code, as upstream builds now (theirs: 30500 in hMailServer.vcxproj, and
#     no-deprecated in their OpenSSL build). Measured on 12 October 2026: the
#     core and the server built with OPENSSL_NO_DEPRECATED and
#     OPENSSL_API_COMPAT=30000 (GCC 15.2 against OpenSSL 3.5.5, the CI image)
#     fail at one call alone - TCPConnection.cpp:875's SSL_get_peer_certificate,
#     whose successor is SSL_get1_peer_certificate, the same reference count -
#     while DKIM.cpp's EVP_MD_CTX_create and _destroy (four each) still compile,
#     macros of _new and _free that are worth renaming with it. The plan, half a
#     day: those calls changed, the two defines in CMakeLists.txt's core target
#     and in hMailServer.vcxproj's PreprocessorDefinitions (the integrator's
#     file), and a clang, GCC and MSVC build (OpenSSL 4.0 on Windows) - the build
#     is the test. Not a defect: the server works as it is.
#   - 6ac3c58fd (#666), the UIDVALIDITY a whole restore gives back: IMAPFolder::
#     XMLLoad (IMAPFolder.cpp:205-206) puts back the folder's CreateTime, which is
#     its UIDVALIDITY here, and its CurrentUID, so the UIDs given after the backup
#     are given again under the same UIDVALIDITY and a client's cache shows the
#     wrong messages. Not without a schema change, which the integrator owns:
#     upstream's design - hm_imapfolders.folderuidvalidity and a per-account
#     counter table (hm_uidvalidity, UIDValidityGenerator::GetNext, seeded from
#     the newest folder creation time so values only grow) - with IMAPFolder::
#     GetUIDValidity reading the stored value (the creation time while it is 0),
#     XMLLoad setting it to 0 so that the restored folder is given a new one, and
#     the across-restart half of NextFolderCreationTime_ coming with it;
#     BackupRestore.ConfirmAccountObject then asserts the creation time kept and
#     SELECT's [UIDVALIDITY] changed. A schema bump of one column and one table,
#     on the five engines; a day with the upgrade scripts and the test.
#
#   NOT APPLICABLE:
#   - The classic Administrator, the PHP WebAdmin and their strings, which the
#     Control Panel and the webmail replaced: 68033314f (#628), fe624592a (#630),
#     f3cf217c2, aa3d84e30, a86bc693e, d7d7b106c, 81bd13914 (#631-#635), 3b2971041
#     (#636), 002481f9d (#637), 6a415fe36 (#638), f1d679d5e (#627), 56b25b5fc
#     (#639), e39d31a8a, a699306a5.
#   - Their build, CI and release machinery, which GitLab CI replaced:
#     a7b309221 (#619), fa47a13d3, 2185fbd1a (#655 SignPath; we sign with Azure
#     Artifact Signing), c4deee51c, 0f47e728c, 5ad442724, 190289626 (#614; our
#     stamping is Version.h and RELEASE.md), 08d09003c (#615), 3b4538871,
#     083dd5b7f, 93758a217 (#641, the portable build; the Linux port replaced it),
#     e3fa7d7b1, 36de66daf, 5bf82f937 (their COM pages), 29e0e16aa and 95404afd9
#     (#657/#662, a signing policy their signing service asks for).
#   - Versions and dependencies behind ours: b04c6552c, 1959c5fd4, ef2ee2c89,
#     cc4441f5e, 1cf0bcd63's version lines (5.7.x and 5.8); eb8e5e24d/12bb534c6
#     (#679 OpenSSL 3.5.9; ours is 4.0.x).
#   - bd9a3ab42 (#625) and faabc8282 (#624, Inno Setup 6.2.2) for Windows Vista:
#     our floor is Windows 10 1607 (section_setup.iss MinVersion=10.0.14393).
#   - 36aabce67 their obsolete tools; ours are their own question.
#
#   DELIBERATELY NOT TAKEN:
#   - 1ffcc7c77 (#649), the RCPT TO refused: upstream answers a RCPT TO naming one
#     of its SRS0 addresses with a sender of its own as the relay rules answer
#     mail for an outside address, which a server verifying the Return-Path of
#     the forwarded mail - Postfix's reject_unverified_sender, asking as
#     double-bounce@ - reads as no address, and so refuses the forward itself.
#     Ours answers that RCPT TO as it always did and refuses the transaction at
#     DATA or BDAT with the reply the relay rules give it; the address keeps its
#     transaction to itself - a recipient after it is answered 452 4.5.3, to come
#     in a transaction of its own, and named after another recipient the address
#     is refused at once - so nothing else is lost with it
#     (SMTPConnection::ProtocolRCPT_, KeepSrsRelayRefusal_; SMTP/Srs.cs).
#   - 1ffcc7c77 (#649), its clock skew as written: SRS::ValidateTimestamp_ moves
#     the clock an hour forward for the whole test, which ends every address's
#     last day an hour early. Ours keeps the 21 days (BATV's 7) to midnight by our
#     clock, as before, and takes tomorrow's stamp in the last hour of today only
#     (SRS::TimeSlotInRange_, BATV::DayNumberInRange_).
#   - 2ffa859af (#667): one domain pattern for the three address checks, a
#     refactor with no change of behaviour; PreSaveLimitationsCheck::
#     IsValidAccountAddress_ and StringParser carry the same pattern here.
#   - 4c03c0bb8 (#671), OnDeliveryFailed's error trimmed whole before it is
#     quoted: upstream trims it first for both languages, which on VBScript
#     takes off the CR LF that ends the error's last line - given to a VBScript
#     handler until now, since the call joined each CR LF pair in before it
#     trimmed. Ours trims VBScript's as before, the white space outside the
#     first pair and the last (ScriptServer::TrimKeepingCrLfPairs), then quotes
#     it whole (Events.cpp; API/Events.cs); JScript's is trimmed whole, as it
#     has been since 24 September.
#   - 6ac3c58fd (#666), COPY's held expunge left for the next command: ours sends
#     it in the COPY's, and the MOVE's, own response once the set is read - RFC
#     2180 4.4: "Pending EXPUNGE response(s) MUST be returned to the COPY
#     command" - as this server's COPY response carried it before, and before
#     the NO or BAD of one refused before it reads its set (a mailbox that is
#     not there, a read-only selection, a missing right); only its moment
#     moves, from before the set is read to after. A set naming the message
#     expunged is refused NO [EXPUNGEISSUED] with it (4.4.1).
#   - 6ac3c58fd (#666), BAD for a set holding 0 or a parenthesised set: ours reads
#     a number it cannot use as one no message has (IMAPCommandRangeAction.cpp:38-
#     49, ParseSetUid_) and answers OK with nothing done. A lenient parse that has
#     never acted on a message it should not; refusing what clients send today is
#     not worth a client broken by it.
#   - ff24290db (#611), and our own line beside it: DNSResolverWinApi.cpp:536's
#     debug line compares a record's name with its dot, so the PTR record the
#     filter keeps is logged "[FILTERED OUT]" (server1's bench, 12 October 2026). A
#     debug line; computing the compared name once for the line and the filter
#     would mend it.
#
# Update this SHA (and only this SHA) when a batch has been triaged.
a843921bbc8d974dc9e456e9d3c21778a1857979
