[4.0.0] - 2026-05-27
Shaped Graph Transfer (DAG) and Multi-User Account Partition
v4.0.0 lands two architectural shifts in the same release.
The first is the promotion of the ready-frontier DAG transfer engine introduced in v3.8.4 to the single production path for every transfer surface. The three rollout flags that gated the engine during the v3.8.x cycle are gone, the hand-rolled JoinSet batch orchestrator has been deleted, and the shaped builders are now the single source of truth for every graph the executor schedules: single-file leaves, multi-file batches, sync sessions, intra-file segmented downloads, and cross-bucket copies. The release introduces a principled trait expansion on StorageProvider so the engine can dispatch the right shape per call from a provider's TransferCapabilities, ships the wiring + integration tests that prove the shapes work against live S3, B2, WebDAV, Azure, ImageKit, Google Drive, Dropbox, OneDrive, Box, MEGA, pCloud, kDrive, OpenDrive, Yandex Disk, Jottacloud, Drime, Uploadcare, Cloudinary, and Filen, and pairs the architectural shift with a power-user CLI surface that exposes 25+ runtime knobs over the same engine.
The second is the Multi-User Account Partition: the vault splits into per-user partitions while remaining fully backward-compatible with single-user installs. A boot-time Account Lock Screen presents the configured users, the vault is partition-aware end-to-end (server profiles, AeroSync settings, CLI --user flag), and an admin role with a self-or-admin gate plus an admin reset-passphrase backend rounds out the surface. Migration from a v3.8.x single-user keystore is automatic and idempotent, the admin role is opt-in with a last-admin guard, and the unlock prompts use honest crypto-stack labels (Argon2id key derivation, AES partition encryption). Sixteen commits cover the partition foundation, the boot picker, the CLI --user flag across profile and transfer commands, the cross-user dedup probe with HMAC keying, the migration of every existing keystore consumer (ConnectionScreen, SettingsPanel, IntroHub, ExportImport, KeystoreWizard, Cloud, SavedServers, App.tsx), the admin role, and the UX polish (logout, perceptible unlock spinner, refined L2 picker).
What's new
Capability-aware shape per transfer
The shaped graph builder picks the transfer-core shape per call from the provider's capability snapshot:
- Native multipart upload fan-out on S3, Backblaze B2, Google Drive, Dropbox, OneDrive, and Box: an upload above one preferred chunk now produces N
UploadPartgraph nodes (one per chunk). The runner orchestrates the lifecycle end-to-end. Per-provider live validation: Drive 500 MiB in 34.6 s (63 parts x 8 MiB), Dropbox 500 MiB in 54.2 s (63 parts x 8 MiB concurrent), OneDrive 500 MiB in 63.8 s (50 parts x 10 MiB sequential), Box 100 MiB in 24.6 s (13 parts x 8 MiB, SHA-1 per chunk + whole-file SHA-1 commit). - Server-side copy on every backend that advertises the capability: S3
x-amz-copy-source, B2b2_copy_file, WebDAV RFC 4918COPY, ImageKitcopyFile, plus 14 other native providers. The shaped-copy graph collapses to a singleServerSideCopynode that reserves only anapi_slot: no file slot, no disk I/O, no local host bytes. The server moves the data. S3 sources above the 5 GiBCopyObjectlimit fan out into parallelUploadPartCopyrequests (T-DEBT-08). - Intra-file segmented downloads through the shared
shaped_rangesbuilder: when a provider proves it honours HTTPRange, the segmented download fans out into NDownloadRangenodes with no inter-segment dependencies, governed by the shared chunk / HTTP / disk-write budget.
For users on backends that advertise none of these capabilities the engine degrades honestly to the same single-transfer-core path that shipped pre-v4.0.0, byte-identical with the legacy provider.upload / provider.download.
Provider trait expansion
Five new methods extend StorageProvider:
begin_multipart_upload(remote_path, total_size, content_type, local_source_path)upload_part(handle, part_number, data)complete_multipart_upload(handle, parts)abort_multipart_upload(handle)server_side_copy(from, to)(alongside the existingsupports_server_side_copy()capability gate).
Default implementations return NotSupported, so a provider that never advertises a capability never reaches the new methods. Nineteen native backends already implement them: S3 (multipart + server-side copy), B2 (multipart + 5 GB-capped server-side copy), Azure Blob (Put Block / Put Block List), WebDAV (Nextcloud chunked v2 + RFC 4918 COPY), ImageKit (copyFile / copyFolder), Google Drive (resumable session), Dropbox (concurrent upload_session + explicit close), OneDrive (Microsoft Graph createUploadSession), Box (chunked v2 with per-chunk SHA-1 + whole-file SHA-1 commit), MEGA (gfs* canonical chunk ramp), pCloud (chunked upload session), kDrive (upload-session API), OpenDrive (chunked upload session), Yandex Disk (upload-target API), Jottacloud (allocate API), Drime (S3 multipart), Uploadcare (multipart API), Cloudinary (chunked upload), Filen v3 (chunked AES-GCM encrypted upload). ImageKit / Internxt / MEGA / 4shared / FileLu document the trait as NotSupported-by-design for cases where the protocol does not offer a real multipart surface.
Power-user CLI knob expansion (PR [#261])
Twenty-five new flags expose the same DAG engine to scripted workflows and CI pipelines without touching code:
- Generic (Sprint K1 Pacchetto A):
--sftp-concurrency,--checkers,--tpslimit/--tpslimit-burst,--no-traverse,--order-by. - S3 surface (KE-B1):
--s3-upload-concurrency,--s3-no-check-bucket,--s3-disable-checksum,--s3-acl,--s3-storage-class. - Retry-After parsing extensions (KE-E): S3, Azure and Filen now parse and honour server-issued
Retry-Afteron 429 / 503 throttle responses (joining Google Drive, Dropbox, OneDrive and Box from T-DEBT-05). - Drive non-AIMD knobs + AIMD config (KE-B2): pacer / abuse acknowledgement / copy-related runtime settings for Google Drive.
- OneDrive (KE-B3):
--onedrive-no-versions,--onedrive-list-chunk,--onedrive-link-scope. - Azure (KE-B4):
--azure-upload-concurrency,--azure-disable-checksum,--azure-access-tier,--azure-archive-tier-delete. - AIMD adaptive concurrency (KE-D):
--aimd-disablekill switch,--aimd-min/--aimd-max/--aimd-step-windowoverrides + per-class TOML configuration, hint table propagation through the executor. - FUSE mount surface (Sprint K3 / T-DEBT-13):
--cache-mode={off|minimal|writes|full}policy preset, polling / timeout knobs,--write-back-cacheviaFilesystem::initcapability negotiation,--fuse-threadsmulti-threaded session loop.fuserbumped 0.16 to 0.17.
AeroCloud catch-up (PR [#262])
- Koofr background sync dispatch fix. Koofr was already a stable provider in the AeroCloud wizard but
cloud_provider_factory.rsdid not route it to the background sync path, so a Koofr profile selected from the wizard would surface "provider not implemented" at first scheduled sync. One-line factory dispatch fix. - Four providers exposed in the AeroCloud wizard: ImageKit, Uploadcare, Cloudinary, Backblaze B2. They were already wired through the factory since v3.x but never appeared as selectable presets in the wizard UI, leaving them reachable only by hand-rolled config.
Convergence cleanup
- The three
AEROFTP_TRANSFER_ENGINE_DAG_*environment-variable flags are removed. - The matching
dag_single_file_enabled()/dag_batch_enabled()/dag_sync_enabled()functions are removed. should_route_sync_to_dag(dry_run)collapses to!dry_run.- The four engine-routing shims in
provider_commands, the CLI, the batch orchestrator and the sync path drop their flag check. - The hand-rolled
JoinSetsliding-window orchestrator intransfer_orchestrator::execute_batch(130+ lines includingspawn_transfer_task) is removed:execute_batch_dagis the entire body.
Multi-User Account Partition (PR [#279])
A second architectural pillar that splits the vault into per-user partitions while keeping single-user installs fully backward-compatible:
- Encrypted partition foundation with per-user data isolation, Argon2id key derivation, and AES partition encryption.
- Boot-time Account Lock Screen that presents the configured users and accepts a per-user passphrase, with honest crypto-stack labels in the unlock prompts.
- Partition-aware vault wiring end-to-end: SavedServers, App.tsx, ConnectionScreen, SettingsPanel, IntroHub, ExportImport, KeystoreWizard, Cloud, favicon.
- Per-user AeroSync settings CRUD on top of the partition layer.
- Admin role with a self-or-admin gate and an admin reset-passphrase backend, last-admin guard so an installation cannot lock itself out.
- CLI
--userflag across profile and transfer commands; optional everywhere, defaults to the active profile. - Cross-user dedup probe with HMAC keying.
- UX polish: logout flow, perceptible unlock spinner, refined L2 picker, account avatars with emoji/color customization.
- Migration from a v3.8.x single-user keystore is automatic, idempotent, and preserves all existing data; the admin role is opt-in.
Fixed
DAG engine
- Per-provider chunk size honoured verbatim: the runner consumes the provider's advertised
multipart_part_sizeas the per-part length verbatim (last part takes the remainder); the legacydiv_ceildistribution stays as the fallback. Fixes Google Drive 256-KiB alignment and OneDrive 320-KiB alignment that previously hit HTTP 503 mid-stream. - Serial chain for
max_chunk_slots = 1providers: the builder chainsUploadPartnodes serially whenever the cap is one, preserving Drive's monotonicContent-Rangecontract; providers with parallel chunk fan-out (S3, B2, Dropbox concurrent, Box chunked v2) keep the unconstrained shape. - Dropbox concurrent session explicit close:
complete_multipart_uploadnow emits the no-opupload_session/append_v2withclose: truebefore the finish call, so Dropbox no longer returns HTTP 409concurrent_session_not_closed. - DAG single-file lock race (closes [#233]):
provider_disconnectandprovider_connectdrain a per-transfer in-flight counter viaTransferOperationGuardbefore mutating the provider slot. - DAG batch progress accounting (closes [#234]): a transient
session_pool.acquire()failure during a batch transfer now increments thefailedcounter on theBatchProgressSnapshotand emits abatch_progressevent before the node completes. - AIMD honours server-provided Retry-After (T-DEBT-05): Google Drive, Dropbox, OneDrive and Box now parse their flavour of the hint and propagate it across the provider/executor boundary through a marker convention. The executor extracts it and forwards it to
AimdController::on_congestion_with_hint, clamped to a[1 s, 10 x default cooldown]safety band.
Community-reported fixes
- Image preview clipped edges after zoom-in then zoom-out (#239, reported by @EhudKirsh). Scrolling the wheel on a previewed image silently switched the viewer out of Fit-to-screen mode. Wheel and toolbar zoom now act purely as a multiplier on top of the active Fit / Actual-size mode; the pan offset is reset when zoom returns to fit.
- Windows installer overwrote user PATH (#240, reported by @miguelsotobaez). Critical regression affecting every Windows installer from v3.6.4 through v3.8.5. The post-install NSIS
ReadRegStr+WriteRegExpandStrpattern silently truncated user PATH values larger thanNSIS_MAX_STRLEN(8192 chars in the Tauri large-strings build), wiping every previously registered toolchain entry. Replaced with the EnVar NSIS plugin (zlib licence) which talks to the Win32 registry directly, preserves the original value type, and is idempotent. - AeroFTP did not boot on macOS Intel (#241, reported by @reset131). A lookbehind regex from
mdast-util-gfm-autolink-literal@2.0.1shipped untranspiled in the production bundle threwSyntaxError: Invalid regular expression: invalid group specifier nameon JavaScriptCore <= 16.4 (Big Sur Safari 15), leaving React unmounted. Patched viapatch-packageto substitute\bfor the lookbehind; theprevious()helper in the same file already gates on the previous character so the substitution preserves all match offsets. Also clamps the initial window size to the primary monitor, pins chrome rows and main / panel flex children for small-screen layouts, and rebuilds the macOSicon.icnsfrom the rocket source so Finder stops showing the stale orbit-only mark.
Changed
- macOS Intel build restored (
x86_64-apple-darwin). The Intel DMG, dropped earlier in the v3.6.x cycle as a recurring source of flakybundle_dmg.shfailures, is back now that the Big Sur boot regression (#241) is fixed and confirmed working on a Big Sur Intel MacBook. The release ships a native Intel DMG (built on themacos-13Ventura runner) alongside the Apple Silicon DMG (macos-14Sonoma,aarch64);minimumSystemVersionstays at 10.13 so Big Sur is covered. A single universal binary is planned for a later release. - My Servers screen lagged on Windows (#221, reported by @raelb). Five optimisations let
React.memoskip re-rendering server cards whose own data did not change: stable drag callbacks (signature change to(idx, e) => void),useMemo'd id maps forfindIndexandcrossProfileSelection, search-text cache, pre-allocated context menu icons. - OpenDrive folder privacy returned HTTP 400 (#252, reported by @EhudKirsh).
folder/setaccess.jsonrequireswith_child_filesalongsidesession_id/folder_id/folder_is_public; the matchingfile/access.jsonendpoint does not, which is whysetAsPrivate/setAsPublicworked on files but failed on folders. Passwith_child_files=trueso the requested privacy propagates to inner files. - OpenDrive privacy state not surfaced in the GUI (#252 pts 3 and 4, reported by @EhudKirsh, PR [#280]). OpenDrive was stashing the raw
Accessvalue undermetadatabut never populatingRemoteEntry.permissions, so the context menu rendered bothSet as PrivateandSet as Publicwith one disabled and the Properties Permissions tab showed theNot availableempty state for files that actually carried visibility metadata. A newaccess_to_permissions(raw)helper maps the documented numeric levels (0 = private,1 = public,2 = hidden) to AeroFTP-canonical tokens, the raw integer stays undermetadata["opendrive_access"]for diagnostics, and bothfolder_to_entryandfile_to_entrynow populateentry.permissions. The Properties Permissions tab gained a privacy-aware branch that renders a labeled Visibility row with a human description, and OpenDrive + 4shared context menus now render the applicable item conditionally instead of greying out the inapplicable one (matching the FileLu precedent from [#161]). - OpenDrive
hiddenvisibility level unreachable from the GUI (#252 pt 2, reported by @EhudKirsh, PR [#282]). OpenDrive documents three visibility levels for both files and folders (0 = private,1 = public,2 = hidden) via the official API samples; the existing binarySet as Private/Set as Publictoggle only flipped between two of them, leavinghiddenunreachable. A newPrivacy...entry in the OpenDrive context menu opens a three-option chooser dialog (radio list with per-level description,currentbadge on the active level, Save disabled until the user changes the selection). The newOpenDriveAccessLevelenum andset_file_access/set_folder_accessmethods drive the samefile/access.jsonandfolder/setaccess.jsonendpoints with the typed level; the existing binaryset_file_privacy/set_folder_privacybecome thin wrappers, so the wire shape of the toggle does not change. OpenDrive accepts the richer per-axis flags (folder_public_upl,folder_public_display,folder_public_dnl) only onfolder/create.json, never onfolder/setaccess.json, so modifying them after creation is intentionally not exposed. - MEGAcmd non-WebDAV: recursive delete timed out and Windows uploads failed (#263, reported by @EhudKirsh). Folder deletes hit a daemon timeout before completion, and uploads dispatched from a Windows local path (Ctrl+C / Paste workflow) never resolved the source. PR [#265] unblocks both paths on the non-WebDAV MegaCmd flavor.
- MEGAcmd WebDAV bridge: image preview failed on single-file resources (#264, reported by @EhudKirsh). The WebDAV client only knew how to PROPFIND a collection root and walk children, so the MEGAcmd WebDAV bridge, which serves a single file per URL, broke on every preview attempt. PR [#269] adds a single-file-mode probe at
connect(): a PROPFIND on the configured URL returns 207 with a non-collectionresourcetype, the parsed entry is cached, andlist/stat/build_urlshort-circuit to operate on the URL verbatim. Traditional WebDAV servers fall through to the existing PROPFIND/flow unchanged.
CLI hardening
aeroftp-cliWindows stack overflow at launch (PR [#267]). The ~80-variant clap-derived enum overflowed the 1 MB Windows default thread stack beforemain()could run. Adds/STACK:8388608link arg viabuild.rs, gated ontarget_os = "windows"and scoped to theaeroftp-clibinary only. Pre-existing latent bug, surfaced and patched during PR [#265] validation by the Windows debug runner.
Added
- MEGAcmd storage quota via
mega-df(#253, reported by @EhudKirsh). A helper that surfaces storage quota for MEGA accounts through the official MEGAcmd daemon, with automatic daemon warm-up on first request. Replaces the WebDAV walk for MEGAcmd profiles (which missed Device Centre) and adds the same surface to the non-WebDAV MegaCmd flavor. Translated across all 47 locales.
Documentation
- New canonical technical reference at
docs/DAG-TRANSFER-ENGINE.md. - New long-form architecture walk-through at
docs.aeroftp.app/architecture/dag-transfer-engine. AGENTS.mdgains a Transfer Engine section spelling out the three shapes the engine picks per call.- New Privacy and Visibility Controls section in
docs/PROTOCOL-FEATURES.md(#252 pts 6 and 7, reported by @EhudKirsh, PR [#281]). Documents the independent axes that contribute to privacy in a cloud-sync app (encryption at rest, default visibility on create, granular toggles, share-link semantics, IP controls, AeroVault overlay), per-provider behavior for OpenDrive / 4shared / FileLu / Filen / Internxt / MEGA / OAuth catch-all, an OpenDrive REST-API-vs-WebDAV reliability note covering the per-IP rate-limit and blacklist behavior onsession/login.json, and the explicit disclosure that the extended OpenDrive flags (folder_public_upl,folder_public_display,folder_public_dnl) are accepted only onfolder/create.json. The Advanced Operations matrixPrivacy Togglerow, previously listing only FileLu, now also lists 4shared and OpenDrive.
Compatibility
The MCP tool surface is unchanged: same names, same arguments, same notifications. Progress events are now sourced from the engine's per-node lifecycle, but downstream consumers see the same JSON shape and the same event cadence. The CLI exit codes and the GUI transfer_event channel are likewise unchanged. The Multi-User migration is forward-only and idempotent: existing single-user installs keep their data on the first boot under a synthesised default user; invocations of aeroftp-cli without --user continue to target that user, so existing scripts keep working.
Why v4.0.0
The version bump reflects the architectural shift: the production transfer path is now a single, provider-agnostic, capability-aware DAG scheduler with five new trait methods on the public StorageProvider API, paired with a power-user CLI knob surface that exposes the same engine over 25+ runtime flags. Three flags, four shims, and the legacy batch orchestrator are gone. The Multi-User Account Partition is a schema-level change in the vault and an authorization-level change on the management API; bumping to v4.0.0 makes both the migration and the surface change explicit. The destructive admin reset, the last-admin guard and the OS-style lock screen are the load-bearing pieces of the new account surface, every one of them audited (MU-SEC P1) before landing. The convergence is complete; the cleanup pass for provider_transfer_executor.rs is filed as accepted technical debt for the post-v4.0.0 window (see docs/dev/roadmap/APPENDIX-DAG-ENGINE/STATUS_TODO.md).
Dependency updates landed in this release
serde_json1.0.149 to 1.0.150similar3.1.0 to 3.1.1rpassword7.5.2 to 7.5.3tar0.4.45 to 0.4.46postcss8.5.14 to 8.5.15vitest4.1.6 to 4.1.7dompurify3.4.2 to 3.4.5fuser0.16 to 0.17 (T-DEBT-13)
Held back intentionally: russh 0.60.3 to 0.61.1 (pre-tag risk on the SFTP path, post-v4.0.0 candidate), codecov/codecov-action 6.0.0 to 6.0.1 (CI workflow check is failing on every PR, needs a separate investigation).
Downloads:
- Windows:
.msiinstaller,.exe, or.zipportable (no installation required) - macOS:
.dmgdisk image - Linux:
.deb,.rpm,.snap, or.AppImage