Provider-agnostic mail sync worker loop over MailReader/RefreshPolicy, plus quota/audit send-path composition over MailSender
| Tier | operations |
| Role | unclassified (baselined) |
| Path | crates/operations/mail-sync |
| Edition | 2021 |
| Targets | operations_mail_sync, composite_account |
| Public items | 12 across 4 modules |
| Tests | 14 |
What it is for
# operations-mail-sync
Two responsibilities (sprint 3.45 crate 10):
1. The sync worker loop (SyncWorker): turns a foundation_refresh_policy::RefreshPolicy decision into MailReader::list/fetch calls, persisting through a caller-supplied MailStore seam. Advances the cursor only after a full page has been persisted with zero errors (Gate 1.5.6 batch-failure rule). 2. Send-path quota/audit composition (SendGuard): wires operations-rate-limit and foundation-audit-log around a &dyn MailSender, per sprint 3.45's "No new crate" decision (Gate 0.5 dedup — composition, not a new primitives-mail-send-guard crate).
Source: rust-gmail-manager/src/services/sync_service.rs's worker-loop portion — the override -> full -> incremental -> bootstrap decision logic itself was already lifted into foundation-refresh-policy (crate 2); this crate builds the loop AROUND that decision, not a second extraction of it. See CHANGELOG.md for the full Gate 4.5 provenance note.
Capabilities
MailSyncError
Errors for the operations-mail-sync worker loop and send-path composition.
| Item |
|---|
pub enum MailSyncError |
SendGuardError
Errors for the operations-mail-sync worker loop and send-path composition.
| Item |
|---|
pub enum SendGuardError |
AuditSink
Send-path composition: quota (operations-rate-limit) + audit
| Item |
|---|
pub trait AuditSink |
SendGuard
Send-path composition: quota (operations-rate-limit) + audit
| Item |
|---|
pub struct SendGuard |
SendGuard :: fn per_day(max_per_day : u64) -> Self |
SendGuard :: async fn send(& self, sender : & dyn MailSender, message : & Email, audit : & dyn AuditSink, override_quota : bool,) -> Result <SendReceipt, SendGuardError> |
MailStore
MailStore — the persistence seam the sync worker writes through.
| Item |
|---|
pub trait MailStore |
SyncOutcome
The sync worker loop: RefreshPolicy::decide chooses a SyncStrategy,
| Item |
|---|
pub struct SyncOutcome |
SyncRequest
The sync worker loop: RefreshPolicy::decide chooses a SyncStrategy,
| Item |
|---|
pub struct SyncRequest<'a> |
SyncWorker
The sync worker loop: RefreshPolicy::decide chooses a SyncStrategy,
| Item |
|---|
pub struct SyncWorker<'a> |
SyncWorker<'a>
The sync worker loop: RefreshPolicy::decide chooses a SyncStrategy,
| Item |
|---|
SyncWorker<'a> :: fn new(reader : & 'a dyn MailReader, store : & 'a dyn MailStore, policy : RefreshPolicy,) -> Self |
SyncWorker<'a> :: async fn run(& self, request : & SyncRequest <'_>) -> Result <SyncOutcome, MailSyncError> |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
operations_mail_sync
errorsend_guardstoreworker
flowchart TD n_operations_mail_sync["operations_mail_sync"] n_operations_mail_sync --> n_error["error"] n_operations_mail_sync --> n_send_guard["send_guard"] n_operations_mail_sync --> n_store["store"] n_operations_mail_sync --> n_worker["worker"]
Public surface
`error`
| Item | What it is |
|---|---|
pub enum MailSyncError | Errors from the sync worker loop (crate::worker::SyncWorker) |
pub enum SendGuardError | Errors from the send-path quota/audit composition (crate::send_guard::SendGuard). |
`send_guard`
| Item | What it is |
|---|---|
pub trait AuditSink | Where SendGuard writes every send/throttle/deny audit event |
pub struct SendGuard | Composes a per-day, per-recipient quota (operations-rate-limit) with mandatory audit logging (foundation-audit-log) around a &dyn MailSender. |
SendGuard :: fn per_day(max_per_day : u64) -> Self | A guard enforcing max_per_day sends per recipient address (RateLimitKey::new("recipient", addr)). |
SendGuard :: async fn send(& self, sender : & dyn MailSender, message : & Email, audit : & dyn AuditSink, override_quota : bool,) -> Result <SendReceipt, SendGuardError> | Send message through sender, enforcing the per-recipient daily quota (checked against every address in message.to) unless override_quota is set, and recording an audit event for every outcome: mail.send (sent), mail.send.throttled (quota denied), or mail.send.denied (the backend itself rejected/failed the send) |
`store`
| Item | What it is |
|---|---|
pub trait MailStore | Where the sync worker persists fetched messages and the sync cursor |
`worker`
| Item | What it is |
|---|---|
pub struct SyncRequest<'a> | One sync run's request parameters. |
pub struct SyncOutcome | Per-run counters, mirroring rust-gmail-manager's SyncReport (new_emails/updated_emails/errors), plus the strategy this run resolved to (useful for caller-side logging/telemetry). |
pub struct SyncWorker<'a> | The sync worker: composes a &dyn MailReader, a RefreshPolicy, and a &dyn MailStore into one account's sync run |
SyncWorker<'a> :: fn new(reader : & 'a dyn MailReader, store : & 'a dyn MailStore, policy : RefreshPolicy,) -> Self | Compose a worker from a reader backend, a storage seam, and a decision policy. |
SyncWorker<'a> :: async fn run(& self, request : & SyncRequest <'_>) -> Result <SyncOutcome, MailSyncError> | Run one sync: decide the strategy, page through list()/fetch(), persist through store, and advance the stored cursor only after a full page has been persisted with zero errors |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
MailStore | store::MailStore |
{AuditSink,SendGuard} | send_guard::{AuditSink,SendGuard} |
{MailSyncError,SendGuardError} | error::{MailSyncError,SendGuardError} |
{SyncOutcome,SyncRequest,SyncWorker} | worker::{SyncOutcome,SyncRequest,SyncWorker} |
Boundary
Reaches into foundation.
Shares tier operations with 40 other crates: operations-approval-workflow, operations-assessments, operations-block-imaging, operations-boot-media, operations-browser-agent-worker, operations-camera-discovery, operations-camera-liveview, operations-camera-registry, … (40 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | operations |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/operations/mail-sync |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_operations["operations"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-audit-log` | foundation | no | always |
| `foundation-mail-message` | foundation | no | always |
| `foundation-refresh-policy` | foundation | no | always |
| `operations-rate-limit` | operations | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | full | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full, macros, rt-multi-thread | no | always |
Build. None.
Depended on by. 1 workspace crate.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_tools_mail_manager["tools-mail-manager"] -->|uses| SELF SELF["operations-mail-sync"] SELF -->|runtime| n_foundation_audit_log["foundation-audit-log"] SELF -->|runtime| n_foundation_mail_message["foundation-mail-message"] SELF -->|runtime| n_foundation_refresh_policy["foundation-refresh-policy"] SELF -->|runtime| n_operations_rate_limit["operations-rate-limit"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | operations_mail_sync | `src/lib.rs` |
| test | composite_account | `tests/composite_account.rs` |
Error model
| Error type | Named by |
|---|---|
MailSyncError | declared, no public signature returns it |
SendGuardError | declared, no public signature returns it |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
1 workspace crate depends on this one: tools-mail-manager.
Verification
| Kind | Count |
|---|---|
| Unit tests | 13 |
| Integration tests | 1 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
error | 2 | 0 | 2 |
send_guard | 2 | 0 | 2 |
store | 1 | 0 | 1 |
worker | 3 | 0 | 3 |
What the tests establish, by name:
composite_account_syncs_via_reader_and_sends_via_sender_independently—tests/composite_account.rsoverride_quota_skips_limiter_but_still_sends_and_audits—src/send_guard.rsquota_exhaustion_throttles_without_calling_sender—src/send_guard.rsquota_is_scoped_per_recipient_not_global—src/send_guard.rssend_within_quota_succeeds_and_audits_mail_send—src/send_guard.rssender_failure_is_audited_as_denied_and_propagated—src/send_guard.rsbootstrap_run_lists_and_persists_all_messages_and_advances_cursor—src/worker.rsbudget_truncated_page_does_not_advance_cursor_or_skip_mail—src/worker.rsempty_first_page_is_a_no_op—src/worker.rsfetch_failure_mid_page_aborts_without_advancing_cursor—src/worker.rsfull_flag_ignores_stored_cursor—src/worker.rsincremental_resumes_from_stored_cursor—src/worker.rsoverride_filter_wins_and_is_forwarded_as_opaque_cursor—src/worker.rsstore_failure_mid_page_aborts_without_advancing_cursor—src/worker.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 12 | 12 |
Public modules with a //! block | 4 | 4 |
pie showData
title Public items with rustdoc
"Documented" : 12
"No rustdoc detected" : 0
Metrics
| Metric | Value |
|---|---|
| Rust source files | 5 |
| Source lines | 1151 |
| Code lines | 797 |
| Public API items | 12 |
| Public modules | 4 |
| Tests | 14 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 11 |
| Workspace reverse dependencies | 1 |
pie showData
title Public API by kind
"enum" : 2
"method" : 4
"struct" : 4
"trait" : 2
pie showData
title Rust source composition
"Code" : 797
"Blank or comment" : 354
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.