Merge upstream v0.16.23

Five conflicts, resolved:

- crates/common/src/auth/authentication.rs: upstream's get_directory_for_token
  and JwtClaims replace extract_jwt_domain; the per-domain directory code
  (DIR-1, DIR-5 to DIR-7) is kept, and the token lookup routes through it.
  The release's one new Enterprise snippet was the body of
  get_directory_for_issuer, which stays returning None: a token naming no
  address gets the server default, as DIR-2 specifies and as v0.16.22 did.
- crates/common/src/manager/application.rs: upstream's rewrite of the tests,
  with the temp directory names renamed again, and the 5(a) notice the
  name-purge change should have added.
- crates/common/src/network/mta.rs: both sides' imports.
- crates/main/Cargo.toml: the AGPL-only license kept, version 0.16.23.
- Cargo.lock: upstream's, with the fork's crates added by Cargo.
This commit is contained in:
2026-09-22 16:57:06 -07:00
82 changed files with 1527 additions and 611 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "services"
version = "0.16.22"
version = "0.16.23"
edition = "2024"
[dependencies]
+9 -6
View File
@@ -96,6 +96,13 @@ pub fn spawn_broadcast_subscriber(inner: Arc<Inner>, mut shutdown_rx: watch::Rec
}
};
inner
.shared_core
.load()
.storage
.data
.invalidate_read_snapshot();
loop {
match batch.next_event() {
Ok(Some(event)) => {
@@ -174,9 +181,7 @@ pub fn spawn_broadcast_subscriber(inner: Arc<Inner>, mut shutdown_rx: watch::Rec
.await;
}
BroadcastEvent::QueueRefresh => {
let core = inner.shared_core.load_full();
if core.network.roles.outbound_mta {
core.storage.data.invalidate_read_snapshot();
if inner.shared_core.load().network.roles.outbound_mta {
let _ = inner
.ipc
.queue_tx
@@ -185,9 +190,7 @@ pub fn spawn_broadcast_subscriber(inner: Arc<Inner>, mut shutdown_rx: watch::Rec
}
}
BroadcastEvent::RegistryChange(change) => {
let server = inner.build_server();
server.store().invalidate_read_snapshot();
match Box::pin(server.reload_registry(change)).await {
match Box::pin(inner.build_server().reload_registry(change)).await {
Ok(result) => {
result.log();
}
@@ -6,7 +6,7 @@
* Modified by Coffey Labs in 2026 for INBUXA.
*/
use crate::task_manager::TaskResult;
use crate::task_manager::{TaskResult, deferred_retry_time};
use common::Server;
use email::{message::metadata::MessageMetadata, sieve::SieveScript};
use groupware::file::FileNode;
@@ -41,7 +41,7 @@ impl DestroyAccountTask for Server {
match destroy_account(self, task).await {
Ok(result) => result,
Err(err) => {
let result = TaskResult::temporary(err.to_string());
let result = TaskResult::deferred(deferred_retry_time(&err), err.to_string());
trc::error!(
err.account_id(task.account_id.document_id())
.details("Failed to destroy account")
+3 -19
View File
@@ -6,7 +6,7 @@
* Modified by Coffey Labs in 2026 for INBUXA.
*/
use crate::task_manager::{Task, TaskDetails, TaskFailureType, TaskResult};
use crate::task_manager::{Task, TaskDetails, TaskFailureType, TaskResult, deferred_retry_time};
use common::Server;
use email::{
cache::MessageCacheFetch,
@@ -274,7 +274,7 @@ impl SearchIndexTask for Server {
);
for r in results.iter_mut() {
if r.task_type == TaskType::Insert && r.result.is_success() {
r.result = search_store_failure(retry_at, "Failed to index documents");
r.result = TaskResult::deferred(retry_at, "Failed to index documents");
}
}
return results;
@@ -331,7 +331,7 @@ impl SearchIndexTask for Server {
for r in results.iter_mut() {
if r.task_type == TaskType::Delete && r.result.is_success() {
r.result =
search_store_failure(retry_at, "Failed to delete documents from index");
TaskResult::deferred(retry_at, "Failed to delete documents from index");
}
}
return results;
@@ -445,22 +445,6 @@ pub(crate) async fn reindex_account(server: &Server, account_id: u32) -> trc::Re
Ok(())
}
fn deferred_retry_time(err: &trc::Error) -> Option<u64> {
err.value(trc::Key::NextRetry)
.and_then(|value| value.to_uint())
}
fn search_store_failure(retry_at: Option<u64>, message: &'static str) -> TaskResult {
match retry_at {
Some(retry_at) => TaskResult::Failure {
typ: TaskFailureType::Retry(retry_at),
message: message.into(),
max_attempts: None,
},
None => TaskResult::temporary(message),
}
}
fn attempt_number(status: &TaskStatus) -> u64 {
match status {
TaskStatus::Pending(_) => 0,
@@ -621,6 +621,7 @@ pub fn perpetual_retry_time(typ: TaskType, attempt: u64) -> Option<u64> {
| TaskType::DkimManagement
| TaskType::IndexDocument
| TaskType::UnindexDocument
| TaskType::DestroyAccount
)
.then(|| {
now().saturating_add(
+16
View File
@@ -137,4 +137,20 @@ impl TaskResult {
max_attempts: None,
}
}
pub fn deferred(retry_at: Option<u64>, message: impl Into<String>) -> Self {
match retry_at {
Some(retry_at) => TaskResult::Failure {
typ: TaskFailureType::Retry(retry_at),
message: message.into(),
max_attempts: None,
},
None => TaskResult::temporary(message),
}
}
}
pub(crate) fn deferred_retry_time(err: &trc::Error) -> Option<u64> {
err.value(trc::Key::NextRetry)
.and_then(|value| value.to_uint())
}