Invariants

Prev Next

Invariants

These invariants are contracts, not guidelines.
Any change that violates them must be treated as a breaking change to the system.

Each invariant below includes the source location where it is enforced.

Invariant Map

Image

1. Engine Is the Single Orchestrator

UploadEngineImpl is the only layer allowed to create sessions, schedule workers, and control concurrency.

Enforced by:

  • UploadEngine.ts:47start<T>() is the only entry point for creating uploads
  • UploadEngine.ts:31private queue: UploadQueue — queue is private to engine
  • index.ts — barrel export exposes only UploadEngine, not UploadSession or UploadWorker

2. UI Independence Is Mandatory

The Upload Engine has zero imports from React, Next.js, or any UI framework.

Enforced by:

  • Import graph — no file imports react, next/router, or any hook
  • All 12 source files depend only on internal libraries and config

:::tip Verification
grep -r "from 'react'" src/lib/internal/UploadEngine/ must return zero results.
:::

3. Completion Is State, Not a Callback

Upload completion is durable state (completed) observable via snapshots and events. The onComplete callback is best-effort.

Enforced by:

  • UploadSession.ts:112complete(fileUrl) transitions state to completed before calling callback
  • UploadWorker.ts:360runCompletionHandler() wraps callback in try/catch; callback failure → completion_failed state, not lost completion
  • UploadStateMachine.ts:58isTerminalState('completed') returns true

4. Explicit State Machine

Every upload follows the STATE_TRANSITIONS map. Illegal transitions throw in development.

Enforced by:

  • UploadStateMachine.ts:6-28STATE_TRANSITIONS defines all 9 × N legal transitions
  • UploadStateMachine.ts:42throw new Error('Illegal state transition: "${from}" → "${to}"')
  • UploadSession.ts:198 — all state changes go through transition(from, to)

5. Single Source of Truth

Each upload is represented by exactly one UploadSession instance. The engine holds the canonical Map<UploadSessionId, UploadSession>.

Enforced by:

  • UploadEngine.ts:29private sessions = new Map<UploadSessionId, UploadSession>()
  • UploadSession.ts:21private snapshot: InternalUploadSnapshot — state is encapsulated
  • UploadSession.ts:64getSnapshot() returns the snapshot, not a copy of external state

6. No Shared Mutable State

Upload sessions do not share mutable data. Each session and worker have their own state.

Enforced by:

  • UploadEngine.ts:29-30 — separate Map for sessions and workers, keyed by UploadSessionId
  • UploadWorker.ts:24-33 — all worker state is private instance fields (planner, completedPartNumbers, pauseRequested, etc.)
  • UploadQueue.ts:23-24pending and active track IDs only, not shared session state

7. Pause and Resume Are First-Class

Every upload supports pause and resume as semantic operations with proper state transitions.

Enforced by:

  • UploadStateMachine.tsuploading → paused and paused → resuming are legal transitions
  • UploadWorker.ts:59requestPause() sets pauseRequested = true and aborts controller
  • UploadWorker.ts:174-175 — window loop checks pauseRequested and throws PauseError
  • UploadWorker.ts:271 — chunk loop checks pauseRequested before each chunk
  • UploadEngine.ts:96resume() creates a new worker and re-enqueues with prioritize()

8. Persistence Guarantees Correctness

Upload state is persisted to sessionStorage after every meaningful state change.

Enforced by:

  • UploadEngine.ts:201handleEvent() calls toPersistedState() and UploadPersistence.save() on every event
  • UploadEngine.ts:137rehydrateFromPersistence() runs in constructor, restoring sessions on page load
  • UploadPersistence.ts:59loadForTenant(site, siteId) ensures tenant-scoped isolation

9. Upload and Domain Are Separate Concerns

The engine transfers files. Domain actions are handled by external listeners.

Enforced by:

  • index.ts — public surface is UploadEngine + types; no domain types exported
  • UploadWorker.tscompleteMultipart() produces fileUrl; does not call domain services
  • UploadTypes.tsUploadContext<T> is generic; engine never inspects T

10. Completion Handling Is Event-Driven

Completion is delivered as a UPLOAD_COMPLETED event via the Signal bus.

Enforced by:

  • UploadEvents.ts:6emitUploadEvent(event) calls Signal.emit('upload', event)
  • UploadEngine.ts:201handleEvent() receives event from session, publishes to bus
  • Event delivery is fire-and-forget; engine does not wait for consumer acknowledgment

11. Intent Metadata Is Immutable

UploadContext<T> is set at start() and never mutated.

Enforced by:

  • UploadSession.ts:40context is set in constructor from args.context
  • UploadTypes.tsUploadContext has no setter methods; it's a plain data type
  • No code path in UploadSession or UploadWorker mutates the context after construction

12. Engine Does Not Interpret Intent

The engine stores UploadContext<T> but never branches on its contents.

Enforced by:

  • UploadEngine.tsstart<T>() passes options.context to session constructor without reading it
  • UploadWorker.ts — never accesses session.context for control flow
  • The generic <T> parameter means the engine literally cannot know the shape at compile time

13. Event-Driven Communication Only

The engine communicates outward only via emitUploadEvent(). No direct method calls into consumers.

Enforced by:

  • UploadEvents.ts — single emitUploadEvent() function is the only outbound channel
  • UploadEngine.ts:201handleEvent() publishes events; does not call consumer APIs
  • index.ts — no consumer interfaces or callback registries are exported

14. Listener Boundaries Are Strict

UploadContext (React) subscribes and stores. UploadListener (React) observes and reacts.

Enforced by:

  • Architectural convention — these layers live outside src/lib/internal/UploadEngine/
  • The engine has no knowledge of these consumers; it only emits to Signal
  • Boundary violations are caught in code review (no compile-time enforcement for external layers)

15. Abort Is Terminal

Aborting an upload is irrecoverable. Aborted uploads never resume.

Enforced by:

  • UploadStateMachine.ts:58isTerminalState('aborted') returns true
  • UploadStateMachine.ts — no transition exists from aborted to any other state
  • UploadWorker.ts:65requestAbort() aborts the controller; AbortError propagates
  • UploadSession.ts:133abort() checks isTerminalState and no-ops if already terminal

16. Failure Resolves Explicitly

All failures resolve into failed, completion_failed, needs_attention, or aborted. No indeterminate states.

Enforced by:

  • UploadWorker.ts:72-107run() catch block routes errors to specific terminal states
  • UploadSession.ts:122fail(stage, message) transitions to failed or completion_failed based on stage
  • UploadStateMachine.tsisTerminalState() covers all end states; requiresAttention() covers attention state

17. Storage Is Not the Source of Truth

Object storage is a transport layer. Orphaned or duplicate objects are acceptable intermediate states.

Enforced by:

  • The engine never queries S3 to verify upload state
  • UploadPersistence.ts uses sessionStorage, not object storage, as the persistence layer
  • No S3 cleanup or deduplication code exists in the engine

18. Analytics Are Observational

Analytics observe events; they never influence upload behavior.

Enforced by:

  • No analytics imports exist in any Upload Engine file
  • Events are emitted to Signal bus; analytics subscribes externally
  • No conditional logic based on analytics state

19. Predictability Over Throughput

Deterministic behavior is preferred over maximum throughput.

Enforced by:

  • UploadEngine.ts:25MAX_PARALLEL_UPLOADS = 5 is a hard constant, not configurable per-call
  • UploadWorker.ts:221PARALLEL_CHUNK_LIMIT = 5 is a static readonly, not dynamic
  • UploadQueue.ts — FIFO ordering with no priority scheduling (except prioritize() for resume)

20. Infrastructure First

The Upload Engine is infrastructure. Clarity over cleverness, stability over flexibility.

Enforced by:

  • 12 focused, single-responsibility source files
  • Zero UI framework dependencies
  • Pure functions for state machine, retry policy, and chunk math
  • Singleton pattern prevents multiple engine instances

Quick Reference

# Invariant Key Enforcement File
1 Single orchestrator UploadEngine.ts, index.ts
2 UI independence All files (zero React imports)
3 Completion is state UploadSession.ts, UploadWorker.ts
4 Explicit state machine UploadStateMachine.ts
5 Single source of truth UploadEngine.ts, UploadSession.ts
6 No shared mutable state Per-session instance isolation
7 Pause/resume first-class UploadWorker.ts, UploadStateMachine.ts
8 Persistence guarantees UploadPersistence.ts, UploadEngine.ts
9 Upload ≠ domain index.ts, UploadTypes.ts
10 Event-driven completion UploadEvents.ts
11 Immutable intent metadata UploadSession.ts, UploadTypes.ts
12 Engine doesn't interpret intent Generic <T> parameter
13 Event-driven only UploadEvents.ts
14 Listener boundaries Architectural convention
15 Abort is terminal UploadStateMachine.ts
16 Failure resolves explicitly UploadWorker.ts, UploadSession.ts
17 Storage ≠ truth No S3 queries in engine
18 Analytics are observational No analytics imports
19 Predictability over throughput Hard constants
20 Infrastructure first Module design