Boundaries
This document defines the hard boundaries of the Upload Engine — what it depends on, what it must never depend on, and how information flows.
Boundaries are stricter than responsibilities.
Responsibilities describe what the module owns.
Boundaries describe what the module is not allowed to see, depend on, or influence.
Boundary Principle
The Upload Engine is an infrastructure subsystem.
It must be possible to:
- remove every UI component without breaking uploads
- change backend implementations without touching UI code
- add new upload consumers without modifying the engine
If a change makes any of the above false, it has crossed a boundary.
Dependency Boundaries (from Source)
Actual Import Graph
Every external dependency of the Upload Engine, traced from source:

External Dependencies by File
| File | External Imports | Why |
|---|---|---|
UploadEngine.ts |
CookieManager, Config, persistence.adapter |
Reads auth token via cookie; reads site config; maps state for persistence |
UploadService.ts |
AsyncHandler, Fetch, Endpoints, AppError, getAuthHeadersOnClient |
HTTP layer for multipart API calls |
UploadEvents.ts |
Signal |
Publishes events to the global signal bus |
UploadPersistence.ts |
Keys (constants) |
Storage key prefix for sessionStorage |
UploadPlanner.ts |
AppError |
Error class for chunk-math validation failures |
UploadWorker.ts |
persistence.adapter |
Converts internal state to persisted format |
UploadTypes.ts |
ContentTypeEnum |
Union member in UploadContext |
RetryPolicy.ts |
(none) | Zero external dependencies — pure logic |
UploadStateMachine.ts |
(none) | Zero external dependencies — pure state table |
UploadQueue.ts |
(none) | Zero external dependencies — pure FIFO scheduler |
UploadSession.ts |
(none) | Zero external dependencies — pure state container |
What the Engine Must Not Depend On
The following are hard import boundaries — no file in the Upload Engine directory may ever import:
| Forbidden Dependency | Reason |
|---|---|
| React / any UI framework | Engine is UI-agnostic; lifecycle-safe |
| Hooks or component lifecycle | No useEffect, useState, etc. |
| Next.js routing / navigation | Uploads must survive route changes |
Feature modules (src/modules/*) |
Infrastructure cannot depend on features |
| Domain services | Engine emits facts, never calls domain logic |
| Analytics business logic | Telemetry is external — observe events, don't instrument internals |
:::tip Verification
Rungrep -r "from 'react'" src/lib/internal/UploadEngine/— must return zero results at all times.
:::
Information Flow Boundaries
Allowed Direction
Information flows in one direction only:

flowchart LR
UI["UI / Feature Modules"] -->|"start(file, context, onComplete)"| Engine[UploadEngine]
Engine -->|"emitUploadEvent(event)"| Context["UploadContext
(React context)"]
Context -->|"snapshot observation"| Listener["UploadListener
(React effect)"]
Listener -->|"domain side-effects"| Domain["Domain Services"]
Domain -.->|"❌ mutate upload state"| Engine
Domain -.->|"❌ control orchestration"| Engine
Context -.->|"❌ call domain APIs"| Domain
Listener -.->|"❌ emit upload events"| Engine
| Direction | Allowed? | Mechanism |
|---|---|---|
| UI → Engine | ✅ | UploadEngine.start(), .pause(), .resume(), .abort() |
| Engine → Signal Bus | ✅ | emitUploadEvent() via Signal.emit() |
| Signal Bus → UploadContext | ✅ | Signal.subscribe() in React context |
| UploadContext → UploadListener | ✅ | React state observation |
| UploadListener → Domain Services | ✅ | Calls domain APIs based on upload intent |
| Domain Services → Engine | ❌ | Domain must never mutate upload state |
| UI → Engine internals | ❌ | Only public API methods; no internal state mutation |
Forbidden Flows
These flows are design errors — if observed, they must be refactored:
- Domain services mutating upload state — domain reacts to facts, never controls the engine
UploadContextcalling domain APIs — context is a state container onlyUploadListeneremitting upload events — listeners observe, never produce- UI bypassing
UploadEngineto accessUploadSessionorUploadWorker— these are private modules - Backend responses altering engine behavior beyond what
UploadServicetranslates
Boundary Between Upload and Domain
The uploader boundary ends at:
- a successfully uploaded file (
state: completed) - a durable
UPLOAD_COMPLETEDevent withfileUrl - an immutable
UploadSessionSnapshot
Everything after that point is outside the Upload Engine.
The engine must never:
| Action | Why it's forbidden |
|---|---|
| Create or mutate CMS entities | Domain concern — handled by UploadListener |
Interpret UploadContext<T> metadata |
Generic metadata; engine passes it through opaquely |
| Retry domain actions | Engine retries HTTP uploads only (RetryPolicy.ts) |
| Infer business success from upload success | Upload success ≠ domain success |
Boundary Between Engine and Adapter
The persistence.adapter.ts file sits at the boundary between the engine's internal state model and the persistence format:

The engine never exposes InternalUploadState directly.
All consumers receive UploadSessionSnapshot via the adapter layer.
Boundary Enforcement
| Enforcement | Mechanism |
|---|---|
| Import isolation | No React, no hooks, no feature modules in engine imports |
| Barrel export | index.ts exports only public types and UploadEngine singleton |
| Adapter layer | persistence.adapter.ts maps between internal and public types |
| Event contracts | emitUploadEvent() is the only outbound channel |
| Immutable metadata | UploadContext<T> is set at start() and never mutated |
| Code review | Boundary violations must be caught in review |