**Module ID:** `net-exceptions`  
**Module name:** Network Failure Exceptions  
**Lifecycle phase:** formation  
**Status:** INTENT ONLY. No code yet.  
**Date:** 2026-07-21  

---

## 1. Intent (the "why")
To provide structured, user-context-aware exception handling for network-layer failures in FreshCloud's mail operations. This module will decouple network error propagation from business logic, enabling graceful fallbacks (e.g., offline caching), consistent error reporting via `email_audit`, and user-friendly messages. Unlike MailSo's 9-file implementation, we streamline to 4 focused exceptions to reduce complexity while preserving critical failure semantics.

---

## 2. User-facing behaviour
- **Throw** specialized exceptions for connection, read, and write failures  
- **Access** user-friendly messages via `getUserMessage()` for UI display  
- **Carry** user/account context in all exceptions for audit tagging  
- **Support** offline fallback via `NetConnectionException` handling  

---

## 3. Public contract (data shapes + signatures)
```php
// Base exception for network failures
interface NetException {
  public function getUserId(): string;           // FreshCloud user_id
  public function getAccountId(): string;        // account_id from email_audit
  public function getUserMessage(): string;       // Localized error text
}

// Connection-level failures
class NetConnectionException extends NetException {
  public static function fromSocketError(int $code, string $host): self;
}

// Socket read failures (timeout/buffer issues)
class SocketReadException extends NetException {
  public const TIMEOUT = 1001;                  // Read timeout
  public const BUFFER_ISSUE = 1002;            // Buffer unread
  
  public function getReadErrorType(): int;      // TIMEOUT or BUFFER_ISSUE
}

// Socket write failures
class SocketWriteException extends NetException {
  public static function fromError(int $code): self;
}
```

---

## 4. Persistence (what gets stored where)
- **`email_audit` table**: All net exceptions logged as events with:
  - `user_id`, `account_id` (from exception context)
  - `event_type = "network_failure"`
  - `error_code` (derived from exception type)
- **No persistent storage for exceptions**: Transient only, stateless.  

---

## 5. Dependencies (what this module uses, what uses this)
- **Uses**:  
  - `FreshCloud\Mail\Net` (network protocol implementations)  
  - `FreshCloud\Mail\UserContext` (user/account ID injector)  
  - `email_audit` table (via bridge layer)  
- **Used by**:  
  - `net-imap`, `net-smtp` modules  
  - `offline-cache` module for fallback handling  
  - React UI bridge for error display  

---

## 6. Behaviour inventory (the N public methods)
| Method                 | Signature                          | Purpose                                  | Side Effects              | Errors Thrown |
|------------------------|------------------------------------|------------------------------------------|---------------------------|---------------|
| `NetConnectionException::fromSocketError` | `(int $code, string $host): self` | Factory for socket connection failures   | None                      | None          |
| `SocketReadException::getReadErrorType` | `(): int` | Returns TIMEOUT/BUFFER_ISSUE code        | None                      | None          |
| `SocketWriteException::fromError` | `(int $code): self` | Factory for socket write failures       | None                      | None          |
| `NetException::getUserId` | `(): string` | Returns user_id from context             | None                      | None          |
| `NetException::getAccountId` | `(): string` | Returns account_id from context         | None                      | None          |
| `NetException::getUserMessage` | `(): string` | Returns localized user-facing message   | None                      | None          |

---

## 7. State machine
```
[Idle] → Connection Attempt → [Connected] → Read/Write Operation → [Success]  
               ↑                      ↓                      ↓  
           [NetConnectionException] [SocketReadException] [SocketWriteException]  
               ↓                      ↓                      ↓  
         [Fallback to Offline Cache] → [User Message Display]
```

**Transitions**:  
- Connection failure → `NetConnectionException` (triggers offline cache)  
- Read timeout/buffer → `SocketReadException` (displays cached message UI)  
- Write failure → `SocketWriteException` (shows "failed to send" message)  

---

## 8. Side effects
- **Writes**:  
  - `email_audit` row on exception (logged via bridge layer)  
- **Reads**:  
  - `user_context` from dependency injection  
- **Emits**:  
  - Metric: `freshcloud_network_errors_total{type, user_id, account_id}`  
  - Event: `network_failure` to React bridge for UI update  

---

## 9. Input validation
- All exceptions **require** `user_id` and `account_id` (non-empty)  
- `fromSocketError()`/`fromError()` **require** valid HTTP socket error codes  
- `getUserMessage()` **returns** localized string from `emails/network-errors.yaml`  

---

## 10. Failure modes
| Failure Case               | Exception Thrown           | Caller Action               | User Display                |
|----------------------------|----------------------------|-----------------------------|-----------------------------|
| Connection timeout         | `NetConnectionException`   | Fall back to offline cache  | "Showing cached messages"   |
| Host unreachable            | `NetConnectionException`   | Show retry dialog           | "Can't reach server"        |
| Read timeout                | `SocketReadException(TIMEOUT)` | Use stale cache          | "Loading from 2h ago"      |
| Buffer overflow            | `SocketReadException(BUFFER_ISSUE)` | Retry operation      | "Connection error"          |
| Write failure              | `SocketWriteException`      | Queue retry or fail         | "Message not sent"          |

---

## 11. User simulation
**Scenario 1: Offline Fallback**  
Given `NetConnectionException` is thrown  
When user views inbox  
Then display cached messages and "Offline mode" banner  

**Scenario 2: Timeout Handling**  
Given `SocketReadException` with TIMEOUT error  
When fetching new emails  
Then show "Loading from cache" and retry silently  

**Scenario 3: Write Failure**  
Given `SocketWriteException` when sending draft  
Then disable send button and show "Retry sending?" toast  

**Scenario 4: Audit Logging**  
Given `SocketReadException` with BUFFER_ISSUE  
When bridge catches exception  
Then write `email_audit` event with user_id, account_id, error_code=1002  

**Scenario 5: UX Localization**  
Given `NetConnectionException`  
When `getUserMessage()` called  
Then return localized text from emails/network-errors.yaml  

**Scenario 6: Metric Emission**  
Given any net exception  
When exception thrown  
Then emit `freshcloud_network_errors_total{type="read", ...}`  

---

## 12. Reproducibility test
1. Create `FreshCloud\Mail\NetExceptions\NetConnectionException`  
2. Inject `user_id`/`account_id` via constructor  
3. Add `getUserMessage()` returning "Connection failed"  
4. Add `getErrorCode()` returning HTTP socket code  
5. Create `SocketReadException` with `TIMEOUT`/`BUFFER_ISSUE` flags  
6. Create `SocketWriteException` with `getErrorCode()`  
7. Wire exceptions to `email_audit` logging in bridge layer  
8. Add React UI handler for `network_failure` events  
9. Verify metrics emitted on exception creation  

---

## 13. Substitutability (FvW v8 §37.4)
**Must provide**:  
- 4 concrete exception classes (NetConnectionException, SocketReadException, SocketWriteException, base NetException)  
- `user_id`/`account_id` in all exception constructors  
- `getUserMessage()` returning localized strings  
- Audit logging hook to `email_audit`  
- Metric emission for `freshcloud_network_errors_total`  

**May differ in**:  
- Localization string source (e.g., database vs YAML)  
- Metrics aggregation approach  

---

## 14. Invariants (the 5 things that must always hold)
1. **All exceptions carry user_id + account_id**  
2. **`getUserMessage()` never returns null/empty**  
3. **NetConnectionException always triggers offline cache fallback**  
4. **No plaintext passwords in exception context**  
5. **No AGPLv3 code in implementation**  

---

## 15. Sweep fields applied (the 14 design intent fields)
### Sweep 1: Multi-user + PocketBase  
- **user_scoping**: `user_id`/`account_id` in all exception constructors  
- **pb_integration**: Exceptions logged to `email_audit` via bridge  
- **auth_boundary**: No auth checks (inject context via dependency)  
- **state_isolation**: Exceptions are stateless, transient only  
- **logout_behaviour**: No action needed (exceptions not persistent)  
- **multi_account**: All exceptions carry `account_id` for multi-account support  

### Sweep 2: World-class Abilities  
- **connection_pool**: ✗ Not applicable (exceptions are transient)  
- **offline_cache**: ✓ NetConnectionException triggers cache fallback  
- **sieve_integration**: ✗ Not applicable (no Sieve layer)  
- **audit_logging**: ✓ All exceptions logged to `email_audit`  
- **ux_primitives**: ✓ `getUserMessage()` returns localized UI text  

### Sweep 3: Migration + Observability + Tests  
- **migration**: ✓ No schema changes (uses existing `email_audit`)  
- **observability**: ✓ Metrics emitted with user_id/account_id tags  
- **tests**: ✓ PHPUnit tests for exception types, context preservation, user messages  

---

## 16. Cross-references
- **§11**: Module constitution (rules.md)  
- **§37**: Fragment publication (public contract)  
- **§38**: Formation intent (this document)  
- **§19**: Clean-room reconstruction from MailSo  
- **Dependent modules**: `net-imap`, `net-smtp`, `offline-cache`  
- **Sister pilot**: `recipes/engine/log/formation.md` (logging pattern)  

---

## 17. What we are intentionally NOT doing (deferred to future)
- MailSo's 9 separate exception files (simplified to 4 focused classes)  
- Legacy socket error code → exception type mapping (use HTTP codes instead)  
- Retry mechanisms (defer to `retry-engine` module)  
- TLS certificate validation (handled by `net-tls` module)  
- Email-specific error codes (use standard HTTP/Socket codes)  
- Connection pooling logic (defer to `net-connection` module)  
- Complex backoff algorithms (simpler exponential backoff elsewhere)  
- AGPLv3 code (clean-room MIT only)  
- Sieve protocol support (defer to `sieve-client` module)  
- Buffer size configuration (fixed 8KB in base class)  
- IPv4/IPv6 address routing abstraction (defer to OS/network layer)