Technical architecture baseline 2026.08

Nooklet, traced
end to end.

A source-backed engineering dossier for the supervised application container and its internal YouTube provider sidecar: authenticated request boundaries, explicit domain workflows, native NNTP and YouTube transfer, normalized persistence, failure recovery, filesystem policy, and verified production delivery.

  • Next.js 16
  • React 19
  • TypeScript 6
  • SQLite + Drizzle
  • Node 24

Audited repository scale

18domain modules
50normalized tables
48SQL migrations
265executable test files

Nooklet documentation

Choose the level you need.

Move from product outcomes to daily operation to implementation evidence without losing context.

Scope and constraints

What the system is designed to optimize.

Nooklet favors operability for a self-hosted instance over distributed scale. The architecture keeps one deployment unit while maintaining explicit domain boundaries, persisted workflow state, and recoverable failure semantics inside that unit.

QA-01

Explainability

Every blocked action should identify the checked resource, observed state, and corrective action.

QA-02

Recoverability

Request, queue, import, job, and audit state survive process restarts in SQLite.

QA-03

Constrained trust

Secrets, outbound hosts, persisted result ownership, and filesystem roots are policy-bound.

QA-04

Low operational load

One supervised application container owns the web runtime, worker, download engines, migrations, and health surface; one internal sidecar supplies YouTube proof-of-origin tokens.

System boundary and explicit non-goals
Concern In scope Not claimed Reason
Deployment One hardened application container Horizontal multi-replica execution Process-local locks and runtime singletons intentionally match the self-hosted model.
Downloads Newznab + Nooklet's native Usenet downloader Torrent protocol support Automatic acquisition searches enabled Newznab sources only; a protocol guard remains as defense in depth.
Persistence SQLite, validated migrations, operational-record retention, verified online backup Automatic backup-file retention or off-host restore drills Operational rows are bounded in-app; backup scheduling and off-host recovery remain operator policies.
Assurance Automated, container, and signed-in browser verification Formal security audit or WCAG certification The dossier distinguishes observed evidence from broader assurance claims.

Runtime and dependency architecture

One application boundary. Explicit internal services.

Next.js handles delivery and authenticated interaction. Named workflows own use cases. Repositories own transactions. Typed adapters own protocols. The worker hosts the Usenet and YouTube runners, while their durable state remains outside process memory. Compose adds one internal YouTube proof-of-origin provider sidecar.

FIG 01 · SYSTEM CONTEXT

Runtime, durable state, and external edges

  • Local or persisted
  • Explicit outbound
  • Policy boundary
Actor Authenticated person Browser · responsive workspace
HTTPS

Nooklet container · supervised Node 24 processes

Presentation Next.js App Router Server components, actions, route handlers
Application Domain workflows Validation, orchestration, outcome contracts
Execution Worker + download engines Scheduled lanes, NNTP and YouTube transfer, import
Durable state SQLite WAL · migrations · workflow records
Approved mounts Download + media filesystems Canonical roots · staging · library
Metadata and intelligence TMDB · AI provider Bounded HTTP adapters
Acquisition Indexers · TLS NNTP · YouTube Owned results, yt-dlp, internal token sidecar
Context and delivery Plex · Tautulli · Trakt · webhooks Explicit, user-configured edges
Figure 1. The application container supervises separate web and worker processes, while Compose keeps the YouTube token provider in an unexposed sidecar. Only SQLite and approved filesystems are durable. External dependencies are typed edges rather than generic proxy targets. Source: ADR-0001.

FIG 02 · DEPENDENCY DIRECTION

Workflow-shaped application layers

  1. 01
    PresentationApp Router pages and accessible controls
    src/app · src/components
  2. 02
    BoundaryServer actions and narrow route handlers
    authenticate → validate → delegate
  3. 03
    ApplicationCommands, queries, and explicit workflow phases
    src/modules/*/workflows
  4. 04
    Domain dataRepositories, transactions, and ownership records
    Drizzle contracts · normalized IDs
  5. 05
    InfrastructureSQLite, filesystem, HTTP, NNTP, and notification adapters
    typed capability boundaries
Figure 2. Dependency direction moves inward through a named use case and outward only through repositories or typed adapters. UI code does not call raw vendor clients. Project structure.

Runtime startup

Database first, then worker.

The container supervisor applies the migration journal before starting distinct web and worker children. src/instrumentation.ts initializes the web database client; the worker child owns the 15-second background loop and native download engine.

HTTP surface

Small by design.

Most mutations use authenticated server actions. Three API route files cover narrow HTTP concerns, including health, rather than exposing a generic CRUD or upstream proxy API.

Domain ownership

Workflows are the unit of change.

The repository contains 147 production workflow files and 98 workflow test files across request, search, scan, import, recommendation, connection, and history domains.

Request, transfer, and import

A persisted sequence, not a button-to-vendor shortcut.

The request path validates intent and ownership before acquisition, reserves local state before enqueue, compensates partial writes, and carries built-in transfer evidence into one conservative import pipeline.

FIG 03 · SEQUENCE TRACE

Title request to library availability

  • Interface
  • Domain
  • Persistence
  1. 01 Person / UI Server action
    Authenticate and parse intent

    Media type, monitored scope, destination, quality, and download intent become a typed request.

  2. 02 Request workflow SQLite
    Acquire an expiring logical-request lease

    Normal requests use five minutes; TV expansion uses two hours, while season recovery has a separate renewable 15-minute per-plan lease.

  3. 03 Indexer workflow Verified sources
    Search, normalize, score, and persist results

    Only enabled Newznab sources enter automatic acquisition ranking; URLs are encrypted, user-owned, and expire after 24 hours.

  4. 04 Queue workflow SQLite
    Validate associations and reserve the request

    A partial unique index rejects another active request for the same user and media target.

  5. 05 Submission adapter Native engine
    Verify origin, fetch NZB, and run capacity preflight

    The URL origin must match the supplying indexer; fetch is bounded to 60 seconds and 50 MiB, then capacity is atomically reserved.

  6. 06 Engine runner NNTP + staging
    Claim, transfer, decode, verify, repair, and extract

    Segments are parallelized within one claimed release and progress is serialized into SQLite.

  7. 07 Import maintenance Media root
    Resolve destination and organize accepted files

    Samples are rejected, collisions are checked, and claimed hard-link or streamed copies retain their engine-owned source until persistence succeeds.

  8. 08 Persistence Library / Activity
    Commit outcome, targeted scan, audit, and notify

    Import records preserve the outcome and queue a scan only for affected active destination paths.

Figure 3. The built-in path keeps result resolution, queue ownership, engine state, and import evidence independently inspectable. The built-in engine owns transfer and completion evidence end to end. Source: queue workflow.

FIG 04 · SEASON FULFILLMENT

One durable intent coordinates every pack and episode attempt

  • Coordinator state
  • Persisted evidence
  • Operator boundary
5 min → 6 hrtransient search and active-capacity retry backoff
15 minactive-download coverage recheck
6 hrunavailable episode release recheck
blockedworkspace free space/mapping, path, downloader, or credential repair required
Figure 4. A fulfillment is the Activity-level user outcome; a download_request is one physical attempt. Failed or intrinsically oversized releases are excluded within the plan; active capacity contention waits without burning a release; current storage pressure blocks for repair. Due work survives restart in SQLite, and infrastructure faults stop fan-out. Source: season fulfillment workflow.

FIG 05 · ENGINE STATE MODEL

Observed native-engine transitions

Persistqueued
Atomic claimfetching
Intactextracting
Damagedrepairing
Terminal engine statecompleted
Pausequeued | fetching → paused → queued
RestartStranded active states return to queued; incomplete bytes restart from the stored NZB.
FailureAny processing phase may end in failed with a persisted reason.
ImportImport is represented by request/import tables, not an engine importing state.
Figure 5. This reflects current code and the amended ADR: assembly occurs during fetching, without a separate persisted intermediate state. Engine runner source.

Work + completed staging

Two engine locations

/app/data/engine-work → /downloads/nooklet-engine/complete

High-concurrency incomplete, assembly, repair, and extraction I/O stays in the work root; finalized output crosses to completed staging with a sequential copy.

  • Both filesystems measured with statfs
  • The tighter capacity limits admission
  • 512 MiB fixed floor
  • Two copies of active remainder
  • Two copies of new declared bytes

Permanent storage

Final library destination

/media/movies

Only accepted primary media and confidently matched companion files are published into this root. Cross-filesystem copies report progress and recover only Nooklet-owned partials after interruption.

  • Independent capacity reading
  • Canonical approved root
  • Collision-safe organization
  • File-grounded library status

FIG 06 · CAPACITY POLICY

Request-time staging estimator

Maximum new release under this policy 179.8 GiB Request can be admitted at or below this declared size.
  • Safety floor0.5 GiB
  • Active reservation40 GiB
  • New archive179.8 GiB
  • Unpack headroom179.8 GiB
Processing reservation0.5 GiB + Σ(active total + remaining)
Available for new workfree - processing reservation
Maximum new releasefloor(available / 2)
Figure 6. The estimator mirrors the atomic queue reservation. Current free space already excludes bytes downloaded so far; each active item therefore reserves its full output plus remaining transfer, while a new item reserves an archive and unpacked copy. Active-work contention retries without consuming a release, an item larger than the entire filesystem advances to another candidate, and non-active storage pressure blocks with a drive/mount repair action. Capacity implementation.
Failure semantics and compensating behavior
Failure point Persisted outcome Compensation or recovery Source preservation
Capacity preflight Request submission fails before engine creation Exact path, available bytes, required bytes, and reserve are reported No engine workspace created
Downloader accepted; local persistence failed Reserved request becomes failed Best-effort queue removal; manual cleanup warning if that also fails Downloader cleanup attempted
Transfer or finalization Engine row becomes failed with a phase-specific reason Import maintenance evaluates retry and notification policy Terminal input is consumed after outcome persistence
Retryable import Import/request remains visible for attention or retry Alternate-release search stops after three failed releases Completed output remains until successful import or terminal handling
Missing engine queue row Queue item and request become failed A visible reason closes the formerly active request Unknown; no queue-owned source remains

Persistence and data integrity

Workflow state is normalized and independently auditable.

Fifty Drizzle tables separate instance configuration, identity and session validity, intent, fulfillment state, Usenet and YouTube transfer state, import evidence, recommendations, history, jobs, notifications, credentials, and audit events. SQLite-specific behavior is embraced deliberately rather than hidden behind portability claims.

CHART 01 · SCHEMA COMPOSITION

Fifty persisted record types by responsibility

Chart 1. Counts are derived from the 50 sqliteTable definitions in schema.ts. The categories explain responsibility; they are not storage-size measurements.

FIG 07 · DOMAIN RELATIONSHIPS

Core records across the request lifecycle

Identity users personal rows + stable configuration anchor
Library media_titles seasons · episodes · paths · files
Intent download_fulfillments strategy · due time · episode coverage
Transfer download_requests queue items · engine downloads
Import download_import_runs imported files · terminal outcome
audit_eventsrecords consequential changes
jobsschedules recurring and immediate work
notification_dispatch_auditrecords lifecycle delivery
Figure 7. Season intent, physical release attempts, engine execution, and import evidence remain separate records. That preserves both an aggregate user outcome and the technical history needed to explain how it was reached. Shared service, indexer, and library configuration resolves through one persisted instance owner rather than changing with administrator ordering. YouTube uses a parallel durable source, membership, selection, download, and imported-file model.

Connection policy

SQLite runtime pragmas

Busy timeout
5 seconds
Journal
WAL
Durability
synchronous=NORMAL
Integrity
Foreign keys enabled

Write invariants

Database-enforced uniqueness

  • One active request per user, title, and episode/season key.
  • One open season fulfillment per user, title, and season.
  • One job per user, type, target type, and target key.
  • One queue item per built-in engine download identifier.
  • One normalized watch-history item per source and media key.

Migration contract

Journal plus compatibility checks

ensureDatabaseReady applies the Drizzle journal when its signature changes, then performs narrowly scoped compatibility repairs and asserts the expected schema.

FIG 08 · MIGRATION TIMELINE

Schema evolution from foundation to operational safety

  1. 0000-0009FoundationIdentity, preferences, secrets, recommendations, history, jobs
  2. 0010-0020Trust + deliveryRate limits, login security, metrics, notifications, delivery audit
  3. 0021-0027Native mediaLibrary, indexers, downloads, imports, retries, deduplication
  4. 0028-0032Standalone engineExternal manager removal, request locks, destination, engine persistence
  5. 0033RepairIdempotent correction for a historical journal-order issue
  6. 0034-0035Execution safetyJob leases, heartbeats, and forced temporary-password replacement
  7. 0036Season recoveryDurable fulfillment plans, child episode state, and attempt linkage
  8. 0037Failure semanticsStructured engine failure classification for safe recovery decisions
  9. 0038-0039Cancellation durabilitySeason and standalone request checkpoints for verified asynchronous cleanup
  10. 0040-0041Filesystem + engine controlDurable storage snapshots, pause/cancel intent, and transfer speed
  11. 0042Stable shared configurationPersisted instance owner, safe row consolidation, and query indexes
  12. 0043Canonical automationOne preserved instance-wide schedule per automation type, with legacy duplicates disabled safely
  13. 0044Monotonic session validityPer-login records and a user authentication generation close stale-response, password-change, and disable/re-enable races
  14. 0045-0047Engine cleanup + YouTubeRemoves a dead engine state and adds YouTube sources, selections, downloads, imported files, and atomic source membership
Figure 8. Forty-eight migrations span 0000 through 0047. Migration 0033 deliberately repairs a known historical state using idempotent DDL. The repository validator enforces contiguous tags, SQL artifacts, and monotonic new timestamps. Migration source.

Concurrency and recovery

Persist ownership; assume the process can disappear.

The worker runs as a separately supervised process, but job ownership is not inferred from memory. Compare-and-set claims, random run tokens, expiring leases, heartbeats, and token-checked completion make stale work reclaimable.

CHART 02 · WORKER TIMING

Scheduling and ownership intervals

Chart 2. Heartbeats refresh a five-minute lease every 30 seconds. A stopped process cannot finalize without its run token, and expired ownership becomes claimable on a later 15-second tick. The supervisor independently recycles a worker whose persisted progress is stale for 120 seconds by default.

FIG 09 · LEASE PROTOCOL

Persisted job ownership and deferred reruns

  1. 01Find due workEnabled, due, and not protected by a live lease
  2. 02Compare-and-set claimWrite random runToken, status, timestamps, and lockedUntil
  3. 03Execute one per classDifferent workload classes can progress concurrently
  4. 04Heartbeat ownershipExtend the lease only while the same token remains active
  5. 05Token-checked finishPersist success/failure and compute the next schedule
Figure 9. Ownership is a persisted protocol, while concurrency remains intentionally bounded to one job per workload class. Job repository source.

FIG 09B · CANCELLATION PROTOCOL

Cancellation is a verified, restart-safe workflow

  1. 01Persist intentWrite an immutable cancellation-request timestamp before external cleanup
  2. 02Claim ownershipAcquire the request or season work lease and re-read the checkpoint
  3. 03Enumerate all evidenceInclude current and historical Nooklet request, queue, and engine identifiers
  4. 04Remove and verifyDelete built-in queue state and files, then observe confirmed absence
  5. 05CAS finalizationFinalize only if the exact checkpoint still matches; otherwise persist a five-minute retry
Figure 9B. A UI click records intent; it does not claim success before downloader absence and file cleanup are verified. Both checkpoints survive restarts and retain request-aware ownership. Standalone reconciler and season reconciler.

Maintenance lane

Maintenance order is intentional.

  1. Prune due operational history once per day
  2. Wake the built-in engine runner
  3. Season cancellation reconciliation
  4. Standalone cancellation reconciliation
  5. Built-in completion import
  6. Due season-fulfillment recovery

Five scheduled lanes

Independent workload classes.

  • watch-history-sync
  • media-library-scan
  • recommendation-run
  • missing-content-search
  • metadata-refresh

Failure isolation

Promise.allSettled, not fail-fast.

An optional integration failure does not prevent unrelated job types or built-in import work from making progress. Failure context is persisted and reflected in health.

Security architecture

Trust is narrowed at every edge.

Nooklet treats browser input, configured service hosts, persisted result URLs, downloaded archives, and filesystem paths as separate trust boundaries. Controls are layered around the actual data flow.

FIG 10 · TRUST BOUNDARIES

Inbound, stored, outbound, and filesystem policy

Untrusted input Browser forms · service URLs · NZB/XML · archives
Boundary A Session + rate limit + Zod + ownership
Application trust zone Named workflows and typed adapters
Secrets decrypted only server-side Audit payloads scrubbed Failure messages bounded
Boundary B · network Scheme, DNS class, pinned address, no redirects, timeout, size cap
Boundary C · filesystem Realpath, approved root, regular-file and archive-entry policy
ExternalExplicit service endpoints
StoredEncrypted secrets + normalized records
FilesystemStaging + approved media roots
Figure 10. Private network hosts require exact allowlisting or an explicit trusted-LAN override. Link-local, metadata, multicast, and blocked address ranges remain denied.

Scroll horizontally to compare control coverage on a narrow screen.

Control coverage by trust surface
Control Identity Stored data Network Filesystem Operations
Local identity + lockout Primary - Supporting - Supporting
Temporary-password recovery Primary Supporting - - Primary
AES-256-GCM secret envelopes Supporting Primary Supporting - Supporting
SSRF-aware safe fetch - Supporting Primary - Supporting
Approved-root policy - Supporting - Primary Supporting
Backup, health, audit Supporting Supporting - Supporting Primary

Primary = directly enforced at that surface. Supporting = contributes defense in depth.

Identity

Revocable session checks

Passwords use scrypt and timing-safe comparison. Login and bootstrap are throttled. Disabled users and password changes invalidate otherwise live sessions; temporary passwords require replacement. Encrypted JWT cookies also require an active per-login SQLite record and the user's current monotonic authentication generation, so sign-out remains final when an older response arrives late and a pending login cannot survive password or disable/re-enable invalidation. Legacy tokens without every claim fail closed.

Secrets

Versioned authenticated encryption

AES-256-GCM envelopes use HKDF-SHA256-derived keys. A dedicated SECRET_BOX_KEY is preferred, and previous keys support lazy read-and-rewrite rotation.

Outbound network

Validate, resolve, then pin.

safeFetch permits HTTP(S), rejects URL credentials, classifies all resolved addresses, pins validated DNS results, refuses redirects, and enforces duration and body limits.

Filesystem + archives

Canonical containment before mutation.

Media roots fail closed when unconfigured. Filesystem roots, device/network paths, symlinks, escaped files, unsafe archive entries, and unbounded extraction trees are rejected.

Deployment and operations

A compact runtime with explicit persistence.

The production image is multi-stage and runs as a non-root user behind tini. Compose drops Linux capabilities, enables no-new-privileges, binds to loopback by default, and makes persistent/mounted state visible.

FIG 11 · DEPLOYMENT TOPOLOGY

Container process, volumes, mounts, and integrations

  • Mounted / persisted
  • Outbound connection
Ingress Browser / reverse proxy Loopback bind by default · operator-managed TLS

Nooklet application container

Next.js standalone server Persisted job worker Native NNTP engine par2 · unrar · 7zz
USER node · tini -g · port 42021
Named volume/app/dataSQLite plus default engine work and output data
Bind mount/downloadsOptional completed-output staging
Bind mounts/media/*Approved final library roots
Outbound integrations Metadata · indexers · NNTP · history · AI · notifications Only explicitly configured endpoints
Figure 11. The image contains the repair and extraction binaries needed by the native downloader. The named volume and bind mounts are the durable boundary; the container image is replaceable.

Observe

/api/health

The public probe returns only database, worker, and engine status. Database failure or a stale/stopped worker returns 503; authenticated /health owns timestamps, counts, stages, and private errors.

Protect

Verified online backup

The source and completed copy both pass SQLite quick_check; the command refuses overwrite and restricts permissions where supported.

Package

Sanitized standalone output

Source, tests, environment files, local databases, private keys, and local tooling state are removed; every journaled migration must remain.

Recover

Scoped admin reset

The recovery command resets one selected local administrator, clears lockout state, audits the event, and requires password replacement.

Production verification path
npm ci
npm run check
docker build --tag nooklet:verified .
Backup and administrator recovery
npm run db:backup
npm run account:recover -- admin@example.com
01

Single process

Simple deployment and backup semantics; no multi-replica safety claim.

02

SQLite + WAL

Excellent local operability; one local persistent volume and one write coordinator.

03

Engine concurrency

One release globally at a time; NNTP segments parallelize inside that release.

04

Restart behavior

Queued intent survives; interrupted transfer bytes restart rather than resume.

05

Single queue authority

Nooklet's built-in engine owns transfer, progress, cancellation, and completion state.

06

Operator boundary

TLS, mounts, backup retention, and off-host recovery stay explicit operator decisions.

Engineering evidence

Measured structure and layered verification.

Counts describe the audited source baseline; they are not code-coverage percentages. Verification evidence is separated by the question each gate answers.

CHART 03 · DOMAIN ENGINEERING DENSITY

Production and test files in src/modules

  • Production
  • Tests
media-library
93 + 51
downloads
49 + 39
service-connections
31 + 15
download-engine
26 + 20
indexers
27 + 13
recommendations
21 + 10
watch-history
22 + 6
youtube
14 + 12
notifications
14 + 3
nine smaller modules
48 + 22
Chart 3. The 18 domain directories contain 536 TypeScript/TSX files: 345 production and 191 test files. There is no separate root registry; the complete src tree contains 831 TypeScript/TSX files, including 260 test files.

FIG 12 · RELEASE PROOF LADDER

Each gate answers a different engineering question

  1. 01Lint

    Is source quality structurally clean?

    Passed
  2. 02Typecheck

    Do TypeScript contracts compose?

    Passed
  3. 03Automated tests

    Do workflow, policy, component accessibility, and first-run browser behaviors hold?

    238 Vitest/Node files + 1 Playwright spec
  4. 04Production build

    Can the standalone artifact be assembled and sanitized?

    Passed
  5. 05Docker smoke

    Does the hardened runtime start and report healthy?

    Passed
  6. 06Playwright + axe

    Can a user bootstrap, sign in, reach Home, and avoid serious/critical accessibility violations?

    Chromium smoke
  7. 07Signed-in browser QA

    Can key workflows be understood and operated responsively?

    Desktop + mobile
Figure 12. Docker smoke validates startup and health, not a complete live media request. Playwright covers one Chromium bootstrap/login path; hands-on browser QA remains release evidence, not an automated cross-browser or accessibility certification suite.
Repository834 files under src831 TypeScript/TSX · 571 production · 260 test
Application surface28 pages · 19 server-action files · 3 API route files18 actions live under src/app; sign-out lives under src/components/layout.
Workflow layer147 production workflow files98 colocated workflow test files.
Historical overhaul362 files in commit 49432fb20,879 additions · 7,178 deletions.

Scroll horizontally to inspect implementation traceability on a narrow screen.

Requirement-to-source traceability index
Concern User surface Owning implementation Primary evidence
Explainable capacity Storage + request errors get-storage-overview
enqueue-nzb-download
Capacity workflow tests + signed-in QA
Capability readiness Home + Setup Center readiness evaluator Evaluator tests + browser QA
Owned download request Search + Discover queue-indexer-result Phase tests + database uniqueness
Resilient season intent Library + Activity season-fulfillment
fulfillment repository
State-machine, persistence, fallback, and migration tests
Verified cancellation Activity request cancellation
season cancellation
Checkpoint, lease, built-in cleanup, and retry-backoff tests
Native transfer Activity engine-runner
download-nzb
Fake NNTP integration tests + queue tests
Conservative import Activity + Library file-organization
file-transfer
import-file-policy
Policy, collision, durable claim recovery, progress, rollback, and multipart tests
Job recovery Automation + Health job-repository
worker
Lease, heartbeat, stale-claim, and deferred-run tests
Network trust Connections + Indexers safe-fetch Address-classification, timeout, redirect, and size tests
Production delivery Container runtime Dockerfile
ci.yml
Build + hardened health smoke
Current CI contract The workflow requires dependency audit/SBOM, documentation, migration and boundary checks, script/application tests, production build, image build, and hardened health smoke.
Inspect workflow source

Engineering conclusion

The system is small by deployment count, not by hidden responsibility.

Nooklet keeps operational topology compact while making boundaries, persistence, recovery, trust, and failure semantics explicit. The result is a self-hosted system that can explain what it is doing and leave enough evidence to diagnose what happened.