flix

0.78.0

FileSystem.flix

/*
 * Copyright 2026 Flix Authors
 *
 * Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE.md file.
 */

///
/// Effect for performing all filesystem operations, and its functions.
///
pub mod Fs.FileSystem {

    use Fs.FileSystem
    use Fs.FsLayer.{logPre, logPre2, logPost, logPost2}
    use Fs.Size
    use Fs.Size.bytes
    use IoError.IoError
    use IoError.ErrorKind
    use Time.Clock
    use Fs.CopyOption
    use Fs.MoveOption

    ///
    /// Effect for performing all filesystem operations.
    ///
    pub eff FileSystem {

        ///
        /// Returns `true` if the given file `f` exists.
        ///
        def exists(f: String): Result[IoError, Bool]

        ///
        /// Returns `true` if the given file `f` is a directory.
        ///
        def isDirectory(f: String): Result[IoError, Bool]

        ///
        /// Returns `true` if the given file `f` is a regular file.
        ///
        def isRegularFile(f: String): Result[IoError, Bool]

        ///
        /// Returns `true` if the given file `f` is a symbolic link.
        ///
        def isSymbolicLink(f: String): Result[IoError, Bool]

        ///
        /// Returns `true` if the given file `f` is readable.
        ///
        def isReadable(f: String): Result[IoError, Bool]

        ///
        /// Returns `true` if the given file `f` is writable.
        ///
        def isWritable(f: String): Result[IoError, Bool]

        ///
        /// Returns `true` if the given file `f` is executable.
        ///
        def isExecutable(f: String): Result[IoError, Bool]

        ///
        /// Returns the last access time of the given file `f` in milliseconds since the epoch.
        ///
        def accessTime(f: String): Result[IoError, Int64]

        ///
        /// Returns the creation time of the given file `f` in milliseconds since the epoch.
        ///
        def creationTime(f: String): Result[IoError, Int64]

        ///
        /// Returns the last-modified timestamp of the given file `f` in milliseconds since the epoch.
        ///
        def modificationTime(f: String): Result[IoError, Int64]

        ///
        /// Returns the size of the given file `f`.
        ///
        def size(f: String): Result[IoError, Size]

        ///
        /// Returns a string of all the contents of the given file `f`.
        ///
        def read(f: String): Result[IoError, String]

        ///
        /// Returns a list of all lines in the given file `f`.
        ///
        def readLines(f: String): Result[IoError, List[String]]

        ///
        /// Returns a vector of all the bytes in the given file `f`.
        ///
        def readBytes(f: String): Result[IoError, Vector[Int8]]

        ///
        /// Returns a list with the names of all files and directories in the given directory `f`.
        ///
        def list(f: String): Result[IoError, List[String]]

        ///
        /// Returns a list of paths under `base` that match the given glob `pattern`.
        ///
        def glob(base: String, pattern: String): Result[IoError, List[String]]

        ///
        /// Writes `str` to the given file `f`.
        ///
        /// Creates file `f` if it does not exist. Overwrites it if it exists.
        ///
        def write(data: {str = String}, f: String): Result[IoError, Unit]

        ///
        /// Writes `lines` to the given file `f`.
        ///
        /// Creates `f` if it does not exist. Overwrites it if it exists.
        ///
        def writeLines(data: {lines = List[String]}, f: String): Result[IoError, Unit]

        ///
        /// Writes `data` to the given file `f`.
        ///
        /// Creates file `f` if it does not exist. Overwrites it if it exists.
        ///
        def writeBytes(data: Vector[Int8], f: String): Result[IoError, Unit]

        ///
        /// Appends `str` to the given file `f`.
        ///
        /// Creates the file `f` if it does not exist.
        ///
        def append(data: {str = String}, f: String): Result[IoError, Unit]

        ///
        /// Appends `lines` to the given file `f`.
        ///
        /// Creates the file `f` if it does not exist.
        ///
        def appendLines(data: {lines = List[String]}, f: String): Result[IoError, Unit]

        ///
        /// Appends `data` to the given file `f`.
        ///
        /// Creates the file `f` if it does not exist.
        ///
        def appendBytes(data: Vector[Int8], f: String): Result[IoError, Unit]

        ///
        /// Truncates the given file `f`.
        ///
        def truncate(f: String): Result[IoError, Unit]

        ///
        /// Copies the file `src` to `dst` with the given `options`.
        ///
        def copyWith(src: {src = String}, dst: String, opts: Set[CopyOption]): Result[IoError, Unit]

        ///
        /// Moves (renames) the file or directory `src` to `dst` with the given `options`.
        ///
        def moveWith(src: {src = String}, dst: String, opts: Set[MoveOption]): Result[IoError, Unit]

        ///
        /// Deletes the given file `f`.
        ///
        /// Fails if the file does not exist.
        ///
        def delete(f: String): Result[IoError, Unit]

        ///
        /// Creates the directory `d`.
        ///
        def mkDir(d: String): Result[IoError, Unit]

        ///
        /// Creates the directory `d` and all its parent directories.
        ///
        def mkDirs(d: String): Result[IoError, Unit]

        ///
        /// Creates a new temporary directory with the given prefix.
        ///
        /// Returns the path to the directory.
        ///
        def mkTempDir(prefix: String): Result[IoError, String]

    }

    ///
    /// Convenience function: copies `src` to `dst` with no options.
    ///
    pub def copy(src: {src = String}, dst: String): Result[IoError, Unit] \ FileSystem =
        Fs.FileSystem.copyWith(src, dst, Set.empty())

    ///
    /// Convenience function: moves `src` to `dst` with no options.
    ///
    pub def move(src: {src = String}, dst: String): Result[IoError, Unit] \ FileSystem =
        Fs.FileSystem.moveWith(src, dst, Set.empty())

    ///
    /// Handles the `FileSystem` effect of the given function `f`.
    ///
    /// In other words, re-interprets the `FileSystem` effect using the `IO` effect.
    ///
    pub def handle(f: a -> b \ ef): a -> b \ (ef - FileSystem) + IO = x ->
        run {
            f(x)
        } with handler FileSystem {
            def exists(filename, k)           = k(Fs.FsLayer.exists(filename))
            def isDirectory(filename, k)      = k(Fs.FsLayer.isDirectory(filename))
            def isRegularFile(filename, k)    = k(Fs.FsLayer.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(Fs.FsLayer.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(Fs.FsLayer.isReadable(filename))
            def isWritable(filename, k)       = k(Fs.FsLayer.isWritable(filename))
            def isExecutable(filename, k)     = k(Fs.FsLayer.isExecutable(filename))
            def accessTime(filename, k)       = k(Fs.FsLayer.accessTime(filename))
            def creationTime(filename, k)     = k(Fs.FsLayer.creationTime(filename))
            def modificationTime(filename, k) = k(Fs.FsLayer.modificationTime(filename))
            def size(filename, k)             = k(Fs.FsLayer.size(filename))
            def read(filename, k)             = k(Fs.FsLayer.read(filename))
            def readLines(filename, k)        = k(Fs.FsLayer.readLines(filename))
            def readBytes(filename, k)        = k(Fs.FsLayer.readBytes(filename))
            def list(filename, k)             = k(Fs.FsLayer.list(filename))
            def glob(base, pattern, k)        = k(Fs.FsLayer.glob(base, pattern))
            def write(data, file, k)          = k(Fs.FsLayer.write(data, file))
            def writeLines(data, file, k)     = k(Fs.FsLayer.writeLines(data, file))
            def writeBytes(data, file, k)     = k(Fs.FsLayer.writeBytes(data, file))
            def append(data, file, k)         = k(Fs.FsLayer.append(data, file))
            def appendLines(data, file, k)    = k(Fs.FsLayer.appendLines(data, file))
            def appendBytes(data, file, k)    = k(Fs.FsLayer.appendBytes(data, file))
            def truncate(file, k)             = k(Fs.FsLayer.truncate(file))
            def copyWith(src, dst, opts, k)   = k(Fs.FsLayer.copyWith(src, dst, opts))
            def moveWith(src, dst, opts, k)   = k(Fs.FsLayer.moveWith(src, dst, opts))
            def delete(file, k)               = k(Fs.FsLayer.delete(file))
            def mkDir(d, k)                   = k(Fs.FsLayer.mkDir(d))
            def mkDirs(d, k)                  = k(Fs.FsLayer.mkDirs(d))
            def mkTempDir(prefix, k)          = k(Fs.FsLayer.mkTempDir(prefix))
        }

    ///
    /// Runs the `FileSystem` effect of the given function `f`.
    ///
    /// In other words, re-interprets the `FileSystem` effect using the `IO` effect.
    ///
    @DefaultHandler
    pub def runWithIO(f: Unit -> a \ ef): a \ (ef - FileSystem) + IO = handle(f)()

    ///
    /// Middleware that intercepts the `FileSystem` effect, logging each operation
    /// and its result via the `Logger` effect.
    ///
    /// Successful operations are logged at `Debug` level; errors at `Warn`.
    ///
    pub def withLogging(f: Unit -> a \ ef): a \ (ef - FileSystem) + { FileSystem, Logger } =
        run { f() } with handler FileSystem {
            def exists(filename, k) = {
                logPre("exists", filename);
                let result = FileSystem.exists(filename);
                logPost("exists", filename, b -> "${b}", result);
                k(result)
            }
            def isDirectory(filename, k) = {
                logPre("isDirectory", filename);
                let result = FileSystem.isDirectory(filename);
                logPost("isDirectory", filename, b -> "${b}", result);
                k(result)
            }
            def isRegularFile(filename, k) = {
                logPre("isRegularFile", filename);
                let result = FileSystem.isRegularFile(filename);
                logPost("isRegularFile", filename, b -> "${b}", result);
                k(result)
            }
            def isSymbolicLink(filename, k) = {
                logPre("isSymbolicLink", filename);
                let result = FileSystem.isSymbolicLink(filename);
                logPost("isSymbolicLink", filename, b -> "${b}", result);
                k(result)
            }
            def isReadable(filename, k) = {
                logPre("isReadable", filename);
                let result = FileSystem.isReadable(filename);
                logPost("isReadable", filename, b -> "${b}", result);
                k(result)
            }
            def isWritable(filename, k) = {
                logPre("isWritable", filename);
                let result = FileSystem.isWritable(filename);
                logPost("isWritable", filename, b -> "${b}", result);
                k(result)
            }
            def isExecutable(filename, k) = {
                logPre("isExecutable", filename);
                let result = FileSystem.isExecutable(filename);
                logPost("isExecutable", filename, b -> "${b}", result);
                k(result)
            }
            def accessTime(filename, k) = {
                logPre("accessTime", filename);
                let result = FileSystem.accessTime(filename);
                logPost("accessTime", filename, t -> "${t}ms", result);
                k(result)
            }
            def creationTime(filename, k) = {
                logPre("creationTime", filename);
                let result = FileSystem.creationTime(filename);
                logPost("creationTime", filename, t -> "${t}ms", result);
                k(result)
            }
            def modificationTime(filename, k) = {
                logPre("modificationTime", filename);
                let result = FileSystem.modificationTime(filename);
                logPost("modificationTime", filename, t -> "${t}ms", result);
                k(result)
            }
            def size(filename, k) = {
                logPre("size", filename);
                let result = FileSystem.size(filename);
                logPost("size", filename, s -> "${s}", result);
                k(result)
            }
            def read(filename, k) = {
                logPre("read", filename);
                let result = FileSystem.read(filename);
                logPost("read", filename, s -> "${String.length(s)} chars", result);
                k(result)
            }
            def readLines(filename, k) = {
                logPre("readLines", filename);
                let result = FileSystem.readLines(filename);
                logPost("readLines", filename, ls -> "${List.length(ls)} lines", result);
                k(result)
            }
            def readBytes(filename, k) = {
                logPre("readBytes", filename);
                let result = FileSystem.readBytes(filename);
                logPost("readBytes", filename, bs -> "${Vector.length(bs)} bytes", result);
                k(result)
            }
            def list(filename, k) = {
                logPre("list", filename);
                let result = FileSystem.list(filename);
                logPost("list", filename, ls -> "${List.length(ls)} entries", result);
                k(result)
            }
            def glob(base, pattern, k) = {
                logPre("glob", base);
                let result = FileSystem.glob(base, pattern);
                logPost("glob", base, ls -> "${List.length(ls)} matches", result);
                k(result)
            }
            def write(data, file, k) = {
                logPre("write", file);
                let result = FileSystem.write(data, file);
                logPost("write", file, _ -> "${String.length(data#str)} chars", result);
                k(result)
            }
            def writeLines(data, file, k) = {
                logPre("writeLines", file);
                let result = FileSystem.writeLines(data, file);
                logPost("writeLines", file, _ -> "${List.length(data#lines)} lines", result);
                k(result)
            }
            def writeBytes(data, file, k) = {
                logPre("writeBytes", file);
                let result = FileSystem.writeBytes(data, file);
                logPost("writeBytes", file, _ -> "${Vector.length(data)} bytes", result);
                k(result)
            }
            def append(data, file, k) = {
                logPre("append", file);
                let result = FileSystem.append(data, file);
                logPost("append", file, _ -> "${String.length(data#str)} chars", result);
                k(result)
            }
            def appendLines(data, file, k) = {
                logPre("appendLines", file);
                let result = FileSystem.appendLines(data, file);
                logPost("appendLines", file, _ -> "${List.length(data#lines)} lines", result);
                k(result)
            }
            def appendBytes(data, file, k) = {
                logPre("appendBytes", file);
                let result = FileSystem.appendBytes(data, file);
                logPost("appendBytes", file, _ -> "${Vector.length(data)} bytes", result);
                k(result)
            }
            def truncate(file, k) = {
                logPre("truncate", file);
                let result = FileSystem.truncate(file);
                logPost("truncate", file, _ -> "done", result);
                k(result)
            }
            def copyWith(src, dst, opts, k) = {
                logPre2("copy", src#src, dst);
                let result = FileSystem.copyWith(src, dst, opts);
                logPost2("copy", src#src, dst, result);
                k(result)
            }
            def moveWith(src, dst, opts, k) = {
                logPre2("move", src#src, dst);
                let result = FileSystem.moveWith(src, dst, opts);
                logPost2("move", src#src, dst, result);
                k(result)
            }
            def delete(file, k) = {
                logPre("delete", file);
                let result = FileSystem.delete(file);
                logPost("delete", file, _ -> "done", result);
                k(result)
            }
            def mkDir(d, k) = {
                logPre("mkDir", d);
                let result = FileSystem.mkDir(d);
                logPost("mkDir", d, _ -> "done", result);
                k(result)
            }
            def mkDirs(d, k) = {
                logPre("mkDirs", d);
                let result = FileSystem.mkDirs(d);
                logPost("mkDirs", d, _ -> "done", result);
                k(result)
            }
            def mkTempDir(prefix, k) = {
                logPre("mkTempDir", prefix);
                let result = FileSystem.mkTempDir(prefix);
                logPost("mkTempDir", prefix, path -> path, result);
                k(result)
            }
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, resolving all file paths
    /// relative to the given `baseDir` using Java's `Path.resolve` semantics.
    ///
    /// Absolute paths are passed through unchanged. The `mkTempDir` prefix is not resolved.
    ///
    pub def withBaseDir(baseDir: String, f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let r = p -> Fs.FsLayer.resolve(baseDir, p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(FileSystem.exists(r(filename)))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(r(filename)))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(r(filename)))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(r(filename)))
            def isReadable(filename, k)       = k(FileSystem.isReadable(r(filename)))
            def isWritable(filename, k)       = k(FileSystem.isWritable(r(filename)))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(r(filename)))
            def accessTime(filename, k)       = k(FileSystem.accessTime(r(filename)))
            def creationTime(filename, k)     = k(FileSystem.creationTime(r(filename)))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(r(filename)))
            def size(filename, k)             = k(FileSystem.size(r(filename)))
            def read(filename, k)             = k(FileSystem.read(r(filename)))
            def readLines(filename, k)        = k(FileSystem.readLines(r(filename)))
            def readBytes(filename, k)        = k(FileSystem.readBytes(r(filename)))
            def list(filename, k)             = k(FileSystem.list(r(filename)))
            def glob(base, pattern, k)        = k(FileSystem.glob(r(base), pattern))
            def write(data, file, k)          = k(FileSystem.write(data, r(file)))
            def writeLines(data, file, k)     = k(FileSystem.writeLines(data, r(file)))
            def writeBytes(data, file, k)     = k(FileSystem.writeBytes(data, r(file)))
            def append(data, file, k)         = k(FileSystem.append(data, r(file)))
            def appendLines(data, file, k)    = k(FileSystem.appendLines(data, r(file)))
            def appendBytes(data, file, k)    = k(FileSystem.appendBytes(data, r(file)))
            def truncate(file, k)             = k(FileSystem.truncate(r(file)))
            def copyWith(src, dst, opts, k)   = k(FileSystem.copyWith(src = r(src#src), r(dst), opts))
            def moveWith(src, dst, opts, k)   = k(FileSystem.moveWith(src = r(src#src), r(dst), opts))
            def delete(file, k)               = k(FileSystem.delete(r(file)))
            def mkDir(d, k)                   = k(FileSystem.mkDir(r(d)))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(r(d)))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, validating that all file paths
    /// resolve within the given `chrootDir`. Rejects paths that escape the chroot with
    /// a `PermissionDenied` error.
    ///
    /// The `mkTempDir` operation is blocked since it creates directories outside the chroot.
    ///
    pub def withChroot(chrootDir: String, f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        use IoError.IoError;
        use IoError.ErrorKind;
        let c = p -> Fs.FsLayer.chroot(chrootDir, p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(Result.flatMap(p -> FileSystem.exists(p), c(filename)))
            def isDirectory(filename, k)      = k(Result.flatMap(p -> FileSystem.isDirectory(p), c(filename)))
            def isRegularFile(filename, k)    = k(Result.flatMap(p -> FileSystem.isRegularFile(p), c(filename)))
            def isSymbolicLink(filename, k)   = k(Result.flatMap(p -> FileSystem.isSymbolicLink(p), c(filename)))
            def isReadable(filename, k)       = k(Result.flatMap(p -> FileSystem.isReadable(p), c(filename)))
            def isWritable(filename, k)       = k(Result.flatMap(p -> FileSystem.isWritable(p), c(filename)))
            def isExecutable(filename, k)     = k(Result.flatMap(p -> FileSystem.isExecutable(p), c(filename)))
            def accessTime(filename, k)       = k(Result.flatMap(p -> FileSystem.accessTime(p), c(filename)))
            def creationTime(filename, k)     = k(Result.flatMap(p -> FileSystem.creationTime(p), c(filename)))
            def modificationTime(filename, k) = k(Result.flatMap(p -> FileSystem.modificationTime(p), c(filename)))
            def size(filename, k)             = k(Result.flatMap(p -> FileSystem.size(p), c(filename)))
            def read(filename, k)             = k(Result.flatMap(p -> FileSystem.read(p), c(filename)))
            def readLines(filename, k)        = k(Result.flatMap(p -> FileSystem.readLines(p), c(filename)))
            def readBytes(filename, k)        = k(Result.flatMap(p -> FileSystem.readBytes(p), c(filename)))
            def list(filename, k)             = k(Result.flatMap(p -> FileSystem.list(p), c(filename)))
            def glob(base, pattern, k)        = k(Result.flatMap(p -> FileSystem.glob(p, pattern), c(base)))
            def write(data, file, k)          = k(Result.flatMap(p -> FileSystem.write(data, p), c(file)))
            def writeLines(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeLines(data, p), c(file)))
            def writeBytes(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeBytes(data, p), c(file)))
            def append(data, file, k)         = k(Result.flatMap(p -> FileSystem.append(data, p), c(file)))
            def appendLines(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendLines(data, p), c(file)))
            def appendBytes(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendBytes(data, p), c(file)))
            def truncate(file, k)             = k(Result.flatMap(p -> FileSystem.truncate(p), c(file)))
            def copyWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.copyWith(src = s, d, opts), c(dst)), c(src#src)))
            def moveWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.moveWith(src = s, d, opts), c(dst)), c(src#src)))
            def delete(file, k)               = k(Result.flatMap(p -> FileSystem.delete(p), c(file)))
            def mkDir(d, k)                   = k(Result.flatMap(p -> FileSystem.mkDir(p), c(d)))
            def mkDirs(d, k)                  = k(Result.flatMap(p -> FileSystem.mkDirs(p), c(d)))
            def mkTempDir(_prefix, k)         = k(Err(IoError(ErrorKind.PermissionDenied, "mkTempDir is not permitted inside a chroot")))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, validating that all file paths
    /// resolve within one of the given `allowedDirs`. Rejects paths outside the
    /// allow list with a `PermissionDenied` error.
    ///
    /// The `mkTempDir` operation is blocked since the temp directory location cannot be verified.
    ///
    pub def withAllowList(allowedDirs: Nel[String], f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        use IoError.IoError;
        use IoError.ErrorKind;
        let c = p -> Fs.FsLayer.allowList(allowedDirs, p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(Result.flatMap(p -> FileSystem.exists(p), c(filename)))
            def isDirectory(filename, k)      = k(Result.flatMap(p -> FileSystem.isDirectory(p), c(filename)))
            def isRegularFile(filename, k)    = k(Result.flatMap(p -> FileSystem.isRegularFile(p), c(filename)))
            def isSymbolicLink(filename, k)   = k(Result.flatMap(p -> FileSystem.isSymbolicLink(p), c(filename)))
            def isReadable(filename, k)       = k(Result.flatMap(p -> FileSystem.isReadable(p), c(filename)))
            def isWritable(filename, k)       = k(Result.flatMap(p -> FileSystem.isWritable(p), c(filename)))
            def isExecutable(filename, k)     = k(Result.flatMap(p -> FileSystem.isExecutable(p), c(filename)))
            def accessTime(filename, k)       = k(Result.flatMap(p -> FileSystem.accessTime(p), c(filename)))
            def creationTime(filename, k)     = k(Result.flatMap(p -> FileSystem.creationTime(p), c(filename)))
            def modificationTime(filename, k) = k(Result.flatMap(p -> FileSystem.modificationTime(p), c(filename)))
            def size(filename, k)             = k(Result.flatMap(p -> FileSystem.size(p), c(filename)))
            def read(filename, k)             = k(Result.flatMap(p -> FileSystem.read(p), c(filename)))
            def readLines(filename, k)        = k(Result.flatMap(p -> FileSystem.readLines(p), c(filename)))
            def readBytes(filename, k)        = k(Result.flatMap(p -> FileSystem.readBytes(p), c(filename)))
            def list(filename, k)             = k(Result.flatMap(p -> FileSystem.list(p), c(filename)))
            def glob(base, pattern, k)        = k(Result.flatMap(p -> FileSystem.glob(p, pattern), c(base)))
            def write(data, file, k)          = k(Result.flatMap(p -> FileSystem.write(data, p), c(file)))
            def writeLines(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeLines(data, p), c(file)))
            def writeBytes(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeBytes(data, p), c(file)))
            def append(data, file, k)         = k(Result.flatMap(p -> FileSystem.append(data, p), c(file)))
            def appendLines(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendLines(data, p), c(file)))
            def appendBytes(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendBytes(data, p), c(file)))
            def truncate(file, k)             = k(Result.flatMap(p -> FileSystem.truncate(p), c(file)))
            def copyWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.copyWith(src = s, d, opts), c(dst)), c(src#src)))
            def moveWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.moveWith(src = s, d, opts), c(dst)), c(src#src)))
            def delete(file, k)               = k(Result.flatMap(p -> FileSystem.delete(p), c(file)))
            def mkDir(d, k)                   = k(Result.flatMap(p -> FileSystem.mkDir(p), c(d)))
            def mkDirs(d, k)                  = k(Result.flatMap(p -> FileSystem.mkDirs(p), c(d)))
            def mkTempDir(_prefix, k)         = k(Err(IoError(ErrorKind.PermissionDenied, "mkTempDir is not permitted with an allow list")))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, validating that all file paths
    /// do not resolve within any of the given `deniedDirs`. Rejects paths inside a
    /// denied directory with a `PermissionDenied` error.
    ///
    /// The `mkTempDir` operation is allowed since deny lists are default-allow.
    ///
    pub def withDenyList(deniedDirs: Nel[String], f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let c = p -> Fs.FsLayer.denyList(deniedDirs, p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(Result.flatMap(p -> FileSystem.exists(p), c(filename)))
            def isDirectory(filename, k)      = k(Result.flatMap(p -> FileSystem.isDirectory(p), c(filename)))
            def isRegularFile(filename, k)    = k(Result.flatMap(p -> FileSystem.isRegularFile(p), c(filename)))
            def isSymbolicLink(filename, k)   = k(Result.flatMap(p -> FileSystem.isSymbolicLink(p), c(filename)))
            def isReadable(filename, k)       = k(Result.flatMap(p -> FileSystem.isReadable(p), c(filename)))
            def isWritable(filename, k)       = k(Result.flatMap(p -> FileSystem.isWritable(p), c(filename)))
            def isExecutable(filename, k)     = k(Result.flatMap(p -> FileSystem.isExecutable(p), c(filename)))
            def accessTime(filename, k)       = k(Result.flatMap(p -> FileSystem.accessTime(p), c(filename)))
            def creationTime(filename, k)     = k(Result.flatMap(p -> FileSystem.creationTime(p), c(filename)))
            def modificationTime(filename, k) = k(Result.flatMap(p -> FileSystem.modificationTime(p), c(filename)))
            def size(filename, k)             = k(Result.flatMap(p -> FileSystem.size(p), c(filename)))
            def read(filename, k)             = k(Result.flatMap(p -> FileSystem.read(p), c(filename)))
            def readLines(filename, k)        = k(Result.flatMap(p -> FileSystem.readLines(p), c(filename)))
            def readBytes(filename, k)        = k(Result.flatMap(p -> FileSystem.readBytes(p), c(filename)))
            def list(filename, k)             = k(Result.flatMap(p -> FileSystem.list(p), c(filename)))
            def glob(base, pattern, k)        = k(Result.flatMap(p -> FileSystem.glob(p, pattern), c(base)))
            def write(data, file, k)          = k(Result.flatMap(p -> FileSystem.write(data, p), c(file)))
            def writeLines(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeLines(data, p), c(file)))
            def writeBytes(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeBytes(data, p), c(file)))
            def append(data, file, k)         = k(Result.flatMap(p -> FileSystem.append(data, p), c(file)))
            def appendLines(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendLines(data, p), c(file)))
            def appendBytes(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendBytes(data, p), c(file)))
            def truncate(file, k)             = k(Result.flatMap(p -> FileSystem.truncate(p), c(file)))
            def copyWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.copyWith(src = s, d, opts), c(dst)), c(src#src)))
            def moveWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.moveWith(src = s, d, opts), c(dst)), c(src#src)))
            def delete(file, k)               = k(Result.flatMap(p -> FileSystem.delete(p), c(file)))
            def mkDir(d, k)                   = k(Result.flatMap(p -> FileSystem.mkDir(p), c(d)))
            def mkDirs(d, k)                  = k(Result.flatMap(p -> FileSystem.mkDirs(p), c(d)))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, validating that all file paths
    /// match at least one of the given glob `patterns`. Rejects paths that do not match
    /// with a `PermissionDenied` error.
    ///
    /// The `mkTempDir` operation is blocked since the temp directory location cannot be verified.
    ///
    pub def withAllowGlob(patterns: Nel[String], f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        use IoError.IoError;
        use IoError.ErrorKind;
        let c = p -> Fs.FsLayer.allowGlob(patterns, p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(Result.flatMap(p -> FileSystem.exists(p), c(filename)))
            def isDirectory(filename, k)      = k(Result.flatMap(p -> FileSystem.isDirectory(p), c(filename)))
            def isRegularFile(filename, k)    = k(Result.flatMap(p -> FileSystem.isRegularFile(p), c(filename)))
            def isSymbolicLink(filename, k)   = k(Result.flatMap(p -> FileSystem.isSymbolicLink(p), c(filename)))
            def isReadable(filename, k)       = k(Result.flatMap(p -> FileSystem.isReadable(p), c(filename)))
            def isWritable(filename, k)       = k(Result.flatMap(p -> FileSystem.isWritable(p), c(filename)))
            def isExecutable(filename, k)     = k(Result.flatMap(p -> FileSystem.isExecutable(p), c(filename)))
            def accessTime(filename, k)       = k(Result.flatMap(p -> FileSystem.accessTime(p), c(filename)))
            def creationTime(filename, k)     = k(Result.flatMap(p -> FileSystem.creationTime(p), c(filename)))
            def modificationTime(filename, k) = k(Result.flatMap(p -> FileSystem.modificationTime(p), c(filename)))
            def size(filename, k)             = k(Result.flatMap(p -> FileSystem.size(p), c(filename)))
            def read(filename, k)             = k(Result.flatMap(p -> FileSystem.read(p), c(filename)))
            def readLines(filename, k)        = k(Result.flatMap(p -> FileSystem.readLines(p), c(filename)))
            def readBytes(filename, k)        = k(Result.flatMap(p -> FileSystem.readBytes(p), c(filename)))
            def list(filename, k)             = k(Result.flatMap(p -> FileSystem.list(p), c(filename)))
            def glob(base, pattern, k)        = k(Result.flatMap(p -> FileSystem.glob(p, pattern), c(base)))
            def write(data, file, k)          = k(Result.flatMap(p -> FileSystem.write(data, p), c(file)))
            def writeLines(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeLines(data, p), c(file)))
            def writeBytes(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeBytes(data, p), c(file)))
            def append(data, file, k)         = k(Result.flatMap(p -> FileSystem.append(data, p), c(file)))
            def appendLines(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendLines(data, p), c(file)))
            def appendBytes(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendBytes(data, p), c(file)))
            def truncate(file, k)             = k(Result.flatMap(p -> FileSystem.truncate(p), c(file)))
            def copyWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.copyWith(src = s, d, opts), c(dst)), c(src#src)))
            def moveWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.moveWith(src = s, d, opts), c(dst)), c(src#src)))
            def delete(file, k)               = k(Result.flatMap(p -> FileSystem.delete(p), c(file)))
            def mkDir(d, k)                   = k(Result.flatMap(p -> FileSystem.mkDir(p), c(d)))
            def mkDirs(d, k)                  = k(Result.flatMap(p -> FileSystem.mkDirs(p), c(d)))
            def mkTempDir(_prefix, k)         = k(Err(IoError(ErrorKind.PermissionDenied, "mkTempDir is not permitted with an allow glob")))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, validating that all file paths
    /// do not match any of the given glob `patterns`. Rejects paths that match a denied
    /// pattern with a `PermissionDenied` error.
    ///
    /// The `mkTempDir` operation is allowed since deny globs are default-allow.
    ///
    pub def withDenyGlob(patterns: Nel[String], f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let c = p -> Fs.FsLayer.denyGlob(patterns, p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(Result.flatMap(p -> FileSystem.exists(p), c(filename)))
            def isDirectory(filename, k)      = k(Result.flatMap(p -> FileSystem.isDirectory(p), c(filename)))
            def isRegularFile(filename, k)    = k(Result.flatMap(p -> FileSystem.isRegularFile(p), c(filename)))
            def isSymbolicLink(filename, k)   = k(Result.flatMap(p -> FileSystem.isSymbolicLink(p), c(filename)))
            def isReadable(filename, k)       = k(Result.flatMap(p -> FileSystem.isReadable(p), c(filename)))
            def isWritable(filename, k)       = k(Result.flatMap(p -> FileSystem.isWritable(p), c(filename)))
            def isExecutable(filename, k)     = k(Result.flatMap(p -> FileSystem.isExecutable(p), c(filename)))
            def accessTime(filename, k)       = k(Result.flatMap(p -> FileSystem.accessTime(p), c(filename)))
            def creationTime(filename, k)     = k(Result.flatMap(p -> FileSystem.creationTime(p), c(filename)))
            def modificationTime(filename, k) = k(Result.flatMap(p -> FileSystem.modificationTime(p), c(filename)))
            def size(filename, k)             = k(Result.flatMap(p -> FileSystem.size(p), c(filename)))
            def read(filename, k)             = k(Result.flatMap(p -> FileSystem.read(p), c(filename)))
            def readLines(filename, k)        = k(Result.flatMap(p -> FileSystem.readLines(p), c(filename)))
            def readBytes(filename, k)        = k(Result.flatMap(p -> FileSystem.readBytes(p), c(filename)))
            def list(filename, k)             = k(Result.flatMap(p -> FileSystem.list(p), c(filename)))
            def glob(base, pattern, k)        = k(Result.flatMap(p -> FileSystem.glob(p, pattern), c(base)))
            def write(data, file, k)          = k(Result.flatMap(p -> FileSystem.write(data, p), c(file)))
            def writeLines(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeLines(data, p), c(file)))
            def writeBytes(data, file, k)     = k(Result.flatMap(p -> FileSystem.writeBytes(data, p), c(file)))
            def append(data, file, k)         = k(Result.flatMap(p -> FileSystem.append(data, p), c(file)))
            def appendLines(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendLines(data, p), c(file)))
            def appendBytes(data, file, k)    = k(Result.flatMap(p -> FileSystem.appendBytes(data, p), c(file)))
            def truncate(file, k)             = k(Result.flatMap(p -> FileSystem.truncate(p), c(file)))
            def copyWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.copyWith(src = s, d, opts), c(dst)), c(src#src)))
            def moveWith(src, dst, opts, k)   = k(Result.flatMap(s -> Result.flatMap(d -> FileSystem.moveWith(src = s, d, opts), c(dst)), c(src#src)))
            def delete(file, k)               = k(Result.flatMap(p -> FileSystem.delete(p), c(file)))
            def mkDir(d, k)                   = k(Result.flatMap(p -> FileSystem.mkDir(p), c(d)))
            def mkDirs(d, k)                  = k(Result.flatMap(p -> FileSystem.mkDirs(p), c(d)))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, resolving all file paths
    /// to their real filesystem targets by following symlinks. This gives opt-in
    /// "follow symlinks" behavior (traditional Unix `stat` vs `lstat` semantics).
    ///
    /// - Bool ops (`exists`, `isDirectory`, etc.): resolve path; return `Ok(false)` for dangling symlinks.
    /// - Value ops (`accessTime`, `size`, etc.): resolve path; propagate all errors.
    /// - Action ops (`read`, `write`, etc.): resolve path with fallback to original (for file creation).
    /// - `isSymbolicLink`: passed through unchanged (always checks the link itself).
    /// - `mkTempDir`: passed through unchanged (no path to resolve).
    ///
    pub def withFollowLinks(f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let resolveBool = (path, op) -> match Fs.FsLayer.resolveRealPath(path) {
            case Ok(p)                               => op(p)
            case Err(IoError(ErrorKind.NotFound, _)) => Ok(false)
            case Err(e)                              => Err(e)
        };
        let resolve = p -> Fs.FsLayer.resolveRealPathOrSelf(p);
        run { f() } with handler FileSystem {
            def exists(filename, k)           = k(resolveBool(filename, p -> FileSystem.exists(p)))
            def isDirectory(filename, k)      = k(resolveBool(filename, p -> FileSystem.isDirectory(p)))
            def isRegularFile(filename, k)    = k(resolveBool(filename, p -> FileSystem.isRegularFile(p)))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(resolveBool(filename, p -> FileSystem.isReadable(p)))
            def isWritable(filename, k)       = k(resolveBool(filename, p -> FileSystem.isWritable(p)))
            def isExecutable(filename, k)     = k(resolveBool(filename, p -> FileSystem.isExecutable(p)))
            def accessTime(filename, k)       = k(Result.flatMap(p -> FileSystem.accessTime(p), Fs.FsLayer.resolveRealPath(filename)))
            def creationTime(filename, k)     = k(Result.flatMap(p -> FileSystem.creationTime(p), Fs.FsLayer.resolveRealPath(filename)))
            def modificationTime(filename, k) = k(Result.flatMap(p -> FileSystem.modificationTime(p), Fs.FsLayer.resolveRealPath(filename)))
            def size(filename, k)             = k(Result.flatMap(p -> FileSystem.size(p), Fs.FsLayer.resolveRealPath(filename)))
            def read(filename, k)             = k(FileSystem.read(resolve(filename)))
            def readLines(filename, k)        = k(FileSystem.readLines(resolve(filename)))
            def readBytes(filename, k)        = k(FileSystem.readBytes(resolve(filename)))
            def list(filename, k)             = k(FileSystem.list(resolve(filename)))
            def glob(base, pattern, k)        = k(FileSystem.glob(resolve(base), pattern))
            def write(data, file, k)          = k(FileSystem.write(data, resolve(file)))
            def writeLines(data, file, k)     = k(FileSystem.writeLines(data, resolve(file)))
            def writeBytes(data, file, k)     = k(FileSystem.writeBytes(data, resolve(file)))
            def append(data, file, k)         = k(FileSystem.append(data, resolve(file)))
            def appendLines(data, file, k)    = k(FileSystem.appendLines(data, resolve(file)))
            def appendBytes(data, file, k)    = k(FileSystem.appendBytes(data, resolve(file)))
            def truncate(file, k)             = k(FileSystem.truncate(resolve(file)))
            def copyWith(src, dst, opts, k)   = k(FileSystem.copyWith(src = resolve(src#src), resolve(dst), opts))
            def moveWith(src, dst, opts, k)   = k(FileSystem.moveWith(src = resolve(src#src), resolve(dst), opts))
            def delete(file, k)               = k(FileSystem.delete(resolve(file)))
            def mkDir(d, k)                   = k(FileSystem.mkDir(resolve(d)))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(resolve(d)))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, logging each write
    /// operation via the `Logger` effect at `Debug` level without performing
    /// the actual filesystem modification. All write operations return `Ok(())`;
    /// `mkTempDir` returns `Ok("<dry-run>")`. Read, stat, and test operations
    /// are passed through to the underlying `FileSystem` effect unchanged.
    ///
    pub def withDryRun(f: Unit -> a \ ef): a \ (ef - FileSystem) + { FileSystem, Logger } =
        use Logger.Severity;
        use RichString.{text, bold, cyan, yellow, gray};
        let tag = yellow("[dry-run]");
        run { f() } with handler FileSystem {
            // Read/stat/test ops — pass through
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def read(filename, k)             = k(FileSystem.read(filename))
            def readLines(filename, k)        = k(FileSystem.readLines(filename))
            def readBytes(filename, k)        = k(FileSystem.readBytes(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))
            // Write ops — dry-run (log + return Ok)
            def write(data, file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("write") + text(" ") + cyan(file) + gray(" (${String.length(data#str)} chars)"));
                k(Ok(()))
            }
            def writeLines(data, file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("writeLines") + text(" ") + cyan(file) + gray(" (${List.length(data#lines)} lines)"));
                k(Ok(()))
            }
            def writeBytes(data, file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("writeBytes") + text(" ") + cyan(file) + gray(" (${Vector.length(data)} bytes)"));
                k(Ok(()))
            }
            def append(data, file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("append") + text(" ") + cyan(file) + gray(" (${String.length(data#str)} chars)"));
                k(Ok(()))
            }
            def appendLines(data, file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("appendLines") + text(" ") + cyan(file) + gray(" (${List.length(data#lines)} lines)"));
                k(Ok(()))
            }
            def appendBytes(data, file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("appendBytes") + text(" ") + cyan(file) + gray(" (${Vector.length(data)} bytes)"));
                k(Ok(()))
            }
            def truncate(file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("truncate") + text(" ") + cyan(file));
                k(Ok(()))
            }
            def copyWith(src, dst, _opts, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("copy") + text(" ") + cyan(src#src) + gray(" -> ") + cyan(dst));
                k(Ok(()))
            }
            def moveWith(src, dst, _opts, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("move") + text(" ") + cyan(src#src) + gray(" -> ") + cyan(dst));
                k(Ok(()))
            }
            def delete(file, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("delete") + text(" ") + cyan(file));
                k(Ok(()))
            }
            def mkDir(d, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("mkDir") + text(" ") + cyan(d));
                k(Ok(()))
            }
            def mkDirs(d, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("mkDirs") + text(" ") + cyan(d));
                k(Ok(()))
            }
            def mkTempDir(prefix, k) = {
                Logger.log(Severity.Debug, tag + text(" ") + bold("mkTempDir") + text(" ") + cyan(prefix));
                k(Ok("<dry-run>"))
            }
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, layering an in-memory
    /// writable store on top of the real filesystem. Writes are captured in memory
    /// (never touching disk), and subsequent reads see the written data. The real
    /// filesystem is never modified. This is useful for safe experimentation,
    /// testing, and sandboxing.
    ///
    pub def withMemoryOverlay(f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        region rc {
            let o = Fs.MemoryOverlay.fresh(rc);
            run { f() } with handler FileSystem {
                // ── Stat/test ops — delegate on None ───────────────
                def exists(f1, k) = k(
                    match Fs.MemoryOverlay.tryExists(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.exists(f1)
                    }
                )
                def isDirectory(f1, k) = k(
                    match Fs.MemoryOverlay.tryIsDirectory(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.isDirectory(f1)
                    }
                )
                def isRegularFile(f1, k) = k(
                    match Fs.MemoryOverlay.tryIsRegularFile(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.isRegularFile(f1)
                    }
                )
                def isSymbolicLink(f1, k) = k(
                    match Fs.MemoryOverlay.tryIsSymbolicLink(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.isSymbolicLink(f1)
                    }
                )
                def isReadable(f1, k) = k(
                    match Fs.MemoryOverlay.tryIsReadable(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.isReadable(f1)
                    }
                )
                def isWritable(f1, k) = k(
                    match Fs.MemoryOverlay.tryIsWritable(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.isWritable(f1)
                    }
                )
                def isExecutable(f1, k) = k(
                    match Fs.MemoryOverlay.tryIsExecutable(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.isExecutable(f1)
                    }
                )
                def accessTime(f1, k) = k(
                    match Fs.MemoryOverlay.tryTime(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.accessTime(f1)
                    }
                )
                def creationTime(f1, k) = k(
                    match Fs.MemoryOverlay.tryTime(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.creationTime(f1)
                    }
                )
                def modificationTime(f1, k) = k(
                    match Fs.MemoryOverlay.tryTime(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.modificationTime(f1)
                    }
                )
                def size(f1, k) = k(
                    match Fs.MemoryOverlay.trySize(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.size(f1)
                    }
                )
                def read(f1, k) = k(
                    match Fs.MemoryOverlay.tryRead(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.read(f1)
                    }
                )
                def readLines(f1, k) = k(
                    match Fs.MemoryOverlay.tryReadLines(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.readLines(f1)
                    }
                )
                def readBytes(f1, k) = k(
                    match Fs.MemoryOverlay.tryReadBytes(f1, o) {
                        case Some(r) => r
                        case None    => FileSystem.readBytes(f1)
                    }
                )

                // ── List/glob — pass lower result to Overlay ────────
                def list(f1, k)               = k(Fs.MemoryOverlay.list(f1, FileSystem.list(f1), o))
                def glob(base, pattern, k)    = k(Fs.MemoryOverlay.glob(base, pattern, FileSystem.glob(base, pattern), o))

                // ── Write ops — direct forwarding ──────────────────
                def write(data, file, k)      = k(Fs.MemoryOverlay.write(file, data#str, o))
                def writeLines(data, file, k) = k(Fs.MemoryOverlay.writeLines(file, data#lines, o))
                def writeBytes(data, file, k) = k(Fs.MemoryOverlay.writeBytes(file, data, o))
                def truncate(file, k)         = k(Fs.MemoryOverlay.truncate(file, o))
                def delete(file, k)           = k(Fs.MemoryOverlay.delete(file, o))
                def mkDir(d, k)               = k(Fs.MemoryOverlay.mkDir(d, o))
                def mkDirs(d, k)              = k(Fs.MemoryOverlay.mkDirs(d, o))
                def mkTempDir(prefix, k)      = k(Fs.MemoryOverlay.mkTempDir(prefix, o))

                // ── Append ops — fetch lower content if needed ─────
                def append(data, file, k) = {
                    let lower = if (Fs.MemoryOverlay.needsLower(file, o)) Result.toOption(FileSystem.read(file)) else None;
                    k(Fs.MemoryOverlay.append(file, data#str, lower, o))
                }
                def appendLines(data, file, k) = {
                    let lower = if (Fs.MemoryOverlay.needsLower(file, o)) Result.toOption(FileSystem.read(file)) else None;
                    k(Fs.MemoryOverlay.appendLines(file, data#lines, lower, o))
                }
                def appendBytes(data, file, k) = {
                    let lower = if (Fs.MemoryOverlay.needsLower(file, o)) Result.toOption(FileSystem.readBytes(file)) else None;
                    k(Fs.MemoryOverlay.appendBytes(file, data, lower, o))
                }

                // ── Copy — fetch lower content if needed ────────────
                def copyWith(src, dst, _opts, k) = {
                    let lower = if (Fs.MemoryOverlay.needsLower(src#src, o))
                        Result.toOption(FileSystem.readBytes(src#src))
                    else
                        None;
                    k(Fs.MemoryOverlay.copy(src#src, dst, lower, o))
                }

                // ── Move — fetch lower content if needed ────────────
                def moveWith(src, dst, _opts, k) = {
                    let lower = if (Fs.MemoryOverlay.needsLower(src#src, o))
                        Result.toOption(FileSystem.readBytes(src#src))
                    else
                        None;
                    k(Fs.MemoryOverlay.move(src#src, dst, lower, o))
                }
            }
        }

    ///
    /// Terminal handler that intercepts the `FileSystem` effect with a fully
    /// in-memory filesystem. The filesystem starts empty; reads of non-written
    /// files return `NotFound`. No real filesystem access occurs. This is
    /// useful for testing, sandboxing, and deterministic simulations.
    ///
    pub def withInMemoryFS(f: Unit -> a \ ef): a \ (ef - FileSystem) + Clock =
        region rc {
            let o = Fs.InMemoryFs.fresh(rc);
            run { f() } with handler FileSystem {
                def exists(f1, k)               = k(Fs.InMemoryFs.exists(f1, o))
                def isDirectory(f1, k)          = k(Fs.InMemoryFs.isDirectory(f1, o))
                def isRegularFile(f1, k)        = k(Fs.InMemoryFs.isRegularFile(f1, o))
                def isSymbolicLink(f1, k)       = k(Fs.InMemoryFs.isSymbolicLink(f1, o))
                def isReadable(f1, k)           = k(Fs.InMemoryFs.isReadable(f1, o))
                def isWritable(f1, k)           = k(Fs.InMemoryFs.isWritable(f1, o))
                def isExecutable(f1, k)         = k(Fs.InMemoryFs.isExecutable(f1, o))
                def accessTime(f1, k)           = k(Fs.InMemoryFs.accessTime(f1, o))
                def creationTime(f1, k)         = k(Fs.InMemoryFs.creationTime(f1, o))
                def modificationTime(f1, k)     = k(Fs.InMemoryFs.modificationTime(f1, o))
                def size(f1, k)                 = k(Fs.InMemoryFs.size(f1, o))
                def read(f1, k)                 = k(Fs.InMemoryFs.read(f1, o))
                def readLines(f1, k)            = k(Fs.InMemoryFs.readLines(f1, o))
                def readBytes(f1, k)            = k(Fs.InMemoryFs.readBytes(f1, o))
                def list(f1, k)                 = k(Fs.InMemoryFs.list(f1, o))
                def glob(base, pattern, k)      = k(Fs.InMemoryFs.glob(base, pattern, o))
                def write(data, file, k)        = k(Fs.InMemoryFs.write(file, data#str, o))
                def writeLines(data, file, k)   = k(Fs.InMemoryFs.writeLines(file, data#lines, o))
                def writeBytes(data, file, k)   = k(Fs.InMemoryFs.writeBytes(file, data, o))
                def append(data, file, k)       = k(Fs.InMemoryFs.append(file, data#str, o))
                def appendLines(data, file, k)  = k(Fs.InMemoryFs.appendLines(file, data#lines, o))
                def appendBytes(data, file, k)  = k(Fs.InMemoryFs.appendBytes(file, data, o))
                def truncate(file, k)           = k(Fs.InMemoryFs.truncate(file, o))
                def copyWith(src, dst, opts, k) = k(Fs.InMemoryFs.copy(src#src, dst, opts, o))
                def moveWith(src, dst, opts, k) = k(Fs.InMemoryFs.move(src#src, dst, opts, o))
                def delete(file, k)             = k(Fs.InMemoryFs.delete(file, o))
                def mkDir(d, k)                 = k(Fs.InMemoryFs.mkDir(d, o))
                def mkDirs(d, k)                = k(Fs.InMemoryFs.mkDirs(d, o))
                def mkTempDir(prefix, k)        = k(Fs.InMemoryFs.mkTempDir(prefix, o))
            }
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, blocking all write
    /// operations with a `PermissionDenied` error while passing read, stat,
    /// and test operations through to the underlying `FileSystem` effect
    /// unchanged. This is useful for sandboxing code that should only observe
    /// the filesystem.
    ///
    pub def withReadOnly(f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        use IoError.IoError;
        use IoError.ErrorKind;
        let denied = op -> Err(IoError(ErrorKind.PermissionDenied, "read-only: ${op} is not permitted"));
        run { f() } with handler FileSystem {
            // Read/stat/test ops — pass through
            def exists(filename, k)            = k(FileSystem.exists(filename))
            def isDirectory(filename, k)       = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)     = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)    = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)        = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)        = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)      = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)        = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)      = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k)  = k(FileSystem.modificationTime(filename))
            def size(filename, k)              = k(FileSystem.size(filename))
            def read(filename, k)              = k(FileSystem.read(filename))
            def readLines(filename, k)         = k(FileSystem.readLines(filename))
            def readBytes(filename, k)         = k(FileSystem.readBytes(filename))
            def list(filename, k)              = k(FileSystem.list(filename))
            def glob(base, pattern, k)         = k(FileSystem.glob(base, pattern))
            // Write ops — blocked
            def write(_data, _file, k)         = k(denied("write"))
            def writeLines(_data, _file, k)    = k(denied("writeLines"))
            def writeBytes(_data, _file, k)    = k(denied("writeBytes"))
            def append(_data, _file, k)        = k(denied("append"))
            def appendLines(_data, _file, k)   = k(denied("appendLines"))
            def appendBytes(_data, _file, k)   = k(denied("appendBytes"))
            def truncate(_file, k)             = k(denied("truncate"))
            def copyWith(_src, _dst, _opts, k) = k(denied("copy"))
            def moveWith(_src, _dst, _opts, k) = k(denied("move"))
            def delete(_file, k)               = k(denied("delete"))
            def mkDir(_d, k)                   = k(denied("mkDir"))
            def mkDirs(_d, k)                  = k(denied("mkDirs"))
            def mkTempDir(_prefix, k)          = k(Err(IoError(ErrorKind.PermissionDenied, "read-only: mkTempDir is not permitted")))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, making `write`,
    /// `writeLines`, and `writeBytes` atomic. Data is first written to a
    /// temporary file in the same directory, then atomically moved into place.
    /// On failure the temporary file is cleaned up.
    ///
    /// All other operations (read, stat, test, list, glob, append, truncate,
    /// copy, move, delete, mkdir) are passed through unchanged.
    ///
    pub def withAtomicWrite(f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let mv = (tmp, dst) -> FileSystem.moveWith(src = tmp, dst, Set#{Fs.MoveOption.AtomicMove, Fs.MoveOption.ReplaceExisting});
        let del = tmp -> FileSystem.delete(tmp);
        run { f() } with handler FileSystem {
            // Read/stat/test ops — pass through
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def read(filename, k)             = k(FileSystem.read(filename))
            def readLines(filename, k)        = k(FileSystem.readLines(filename))
            def readBytes(filename, k)        = k(FileSystem.readBytes(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

            // Overwrite ops — atomic write-to-temp then rename
            def write(data, file, k)          = k(Fs.FsLayer.atomicWrite(file, tmp -> FileSystem.write(data, tmp), mv, del))
            def writeLines(data, file, k)     = k(Fs.FsLayer.atomicWrite(file, tmp -> FileSystem.writeLines(data, tmp), mv, del))
            def writeBytes(data, file, k)     = k(Fs.FsLayer.atomicWrite(file, tmp -> FileSystem.writeBytes(data, tmp), mv, del))

            // Other write ops — pass through
            def append(data, file, k)         = k(FileSystem.append(data, file))
            def appendLines(data, file, k)    = k(FileSystem.appendLines(data, file))
            def appendBytes(data, file, k)    = k(FileSystem.appendBytes(data, file))
            def truncate(file, k)             = k(FileSystem.truncate(file))
            def copyWith(src, dst, opts, k)   = k(FileSystem.copyWith(src, dst, opts))
            def moveWith(src, dst, opts, k)   = k(FileSystem.moveWith(src, dst, opts))
            def delete(file, k)               = k(FileSystem.delete(file))
            def mkDir(d, k)                   = k(FileSystem.mkDir(d))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, creating a backup
    /// of existing files before destructive operations. Before `write`,
    /// `writeLines`, `writeBytes`, `truncate`, and `delete`, the existing
    /// file is copied to `file + suffix` (e.g., `"data.txt.bak"` when
    /// suffix is `".bak"`).
    ///
    /// For `copyWith` and `moveWith`, the destination file is backed up.
    ///
    /// If the file does not exist, no backup is created and the operation
    /// proceeds normally. If the backup fails for any other reason, the
    /// original operation is aborted and the backup error is returned.
    ///
    /// If a backup file already exists, it is overwritten. Only the most
    /// recent previous version of each file is preserved.
    ///
    /// Read, stat, test, list, glob, append, mkdir, and mkTempDir operations
    /// are passed through unchanged.
    ///
    pub def withBackup(suffix: String, f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let doCopy = (src, dst) -> FileSystem.copyWith(src = src, dst, Set#{Fs.CopyOption.ReplaceExisting});
        run { f() } with handler FileSystem {
            // Read/stat/test ops — pass through
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def read(filename, k)             = k(FileSystem.read(filename))
            def readLines(filename, k)        = k(FileSystem.readLines(filename))
            def readBytes(filename, k)        = k(FileSystem.readBytes(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

            // Destructive ops — backup then proceed
            def write(data, file, k)          = k(Fs.FsLayer.backup(file, suffix, doCopy, () -> FileSystem.write(data, file)))
            def writeLines(data, file, k)     = k(Fs.FsLayer.backup(file, suffix, doCopy, () -> FileSystem.writeLines(data, file)))
            def writeBytes(data, file, k)     = k(Fs.FsLayer.backup(file, suffix, doCopy, () -> FileSystem.writeBytes(data, file)))
            def truncate(file, k)             = k(Fs.FsLayer.backup(file, suffix, doCopy, () -> FileSystem.truncate(file)))
            def delete(file, k)               = k(Fs.FsLayer.backup(file, suffix, doCopy, () -> FileSystem.delete(file)))
            def copyWith(src, dst, opts, k)   = k(Fs.FsLayer.backup(dst, suffix, doCopy, () -> FileSystem.copyWith(src, dst, opts)))
            def moveWith(src, dst, opts, k)   = k(Fs.FsLayer.backup(dst, suffix, doCopy, () -> FileSystem.moveWith(src, dst, opts)))

            // Non-destructive write ops — pass through
            def append(data, file, k)         = k(FileSystem.append(data, file))
            def appendLines(data, file, k)    = k(FileSystem.appendLines(data, file))
            def appendBytes(data, file, k)    = k(FileSystem.appendBytes(data, file))
            def mkDir(d, k)                   = k(FileSystem.mkDir(d))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, automatically creating
    /// parent directories before write, append, copy, and move operations.
    ///
    /// Before `write`, `writeLines`, `writeBytes`, `append`, `appendLines`,
    /// `appendBytes`, `copyWith`, and `moveWith`, the parent directory of the
    /// target file is created via `mkDirs`. If the parent directory already
    /// exists, this is a no-op.
    ///
    /// If parent directory creation fails, the original operation is aborted
    /// and the error is returned.
    ///
    /// Read, stat, test, list, glob, `truncate`, `delete`, `mkDir`, `mkDirs`,
    /// and `mkTempDir` are passed through unchanged.
    ///
    pub def withMkParentDirs(f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let mk = d -> FileSystem.mkDirs(d);
        run { f() } with handler FileSystem {
            // Read/stat/test ops — pass through
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def read(filename, k)             = k(FileSystem.read(filename))
            def readLines(filename, k)        = k(FileSystem.readLines(filename))
            def readBytes(filename, k)        = k(FileSystem.readBytes(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

            // Write/append/copy/move — ensure parent dirs exist
            def write(data, file, k)          = k(Fs.FsLayer.ensureParentDirs(file, mk, () -> FileSystem.write(data, file)))
            def writeLines(data, file, k)     = k(Fs.FsLayer.ensureParentDirs(file, mk, () -> FileSystem.writeLines(data, file)))
            def writeBytes(data, file, k)     = k(Fs.FsLayer.ensureParentDirs(file, mk, () -> FileSystem.writeBytes(data, file)))
            def append(data, file, k)         = k(Fs.FsLayer.ensureParentDirs(file, mk, () -> FileSystem.append(data, file)))
            def appendLines(data, file, k)    = k(Fs.FsLayer.ensureParentDirs(file, mk, () -> FileSystem.appendLines(data, file)))
            def appendBytes(data, file, k)    = k(Fs.FsLayer.ensureParentDirs(file, mk, () -> FileSystem.appendBytes(data, file)))
            def copyWith(src, dst, opts, k)   = k(Fs.FsLayer.ensureParentDirs(dst, mk, () -> FileSystem.copyWith(src, dst, opts)))
            def moveWith(src, dst, opts, k)   = k(Fs.FsLayer.ensureParentDirs(dst, mk, () -> FileSystem.moveWith(src, dst, opts)))

            // Other write ops — pass through
            def truncate(file, k)             = k(FileSystem.truncate(file))
            def delete(file, k)               = k(FileSystem.delete(file))
            def mkDir(d, k)                   = k(FileSystem.mkDir(d))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, rejecting read, write,
    /// and append operations where the data exceeds `maxBytes`. For reads and
    /// `copyWith`, the file size is checked via `FileSystem.size`.
    ///
    /// String-based operations estimate byte count as `charCount * 4` (the
    /// maximum bytes per character in UTF-8). Use byte-based operations for
    /// exact enforcement.
    ///
    /// Operations that don't transfer data (`truncate`, `moveWith`, `delete`,
    /// `mkDir`, `mkDirs`, `mkTempDir`) and metadata operations are passed
    /// through unchanged.
    ///
    pub def withTransferLimit(maxSize: Size, f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let check = sz -> Fs.FsLayer.checkTransferLimit(maxSize, sz);
        let estStr = s -> Fs.FsLayer.estimateStringBytes(s);
        run { f() } with handler FileSystem {
            // Stat/test/metadata ops — pass through
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

            // Read ops — check file size before reading
            def read(filename, k)             = k(Result.flatMap(_ -> FileSystem.read(filename), Result.flatMap(check, FileSystem.size(filename))))
            def readLines(filename, k)        = k(Result.flatMap(_ -> FileSystem.readLines(filename), Result.flatMap(check, FileSystem.size(filename))))
            def readBytes(filename, k)        = k(Result.flatMap(_ -> FileSystem.readBytes(filename), Result.flatMap(check, FileSystem.size(filename))))

            // Write ops — check payload size
            def write(data, file, k)          = k(Result.flatMap(_ -> FileSystem.write(data, file), check(estStr(data#str))))
            def writeLines(data, file, k)     = k(Result.flatMap(_ -> FileSystem.writeLines(data, file), check(Fs.FsLayer.estimateStringListBytes(data#lines))))
            def writeBytes(data, file, k)     = k(Result.flatMap(_ -> FileSystem.writeBytes(data, file), check(bytes(Int32.toInt64(Vector.length(data))))))
            def append(data, file, k)         = k(Result.flatMap(_ -> FileSystem.append(data, file), check(estStr(data#str))))
            def appendLines(data, file, k)    = k(Result.flatMap(_ -> FileSystem.appendLines(data, file), check(Fs.FsLayer.estimateStringListBytes(data#lines))))
            def appendBytes(data, file, k)    = k(Result.flatMap(_ -> FileSystem.appendBytes(data, file), check(bytes(Int32.toInt64(Vector.length(data))))))
            def copyWith(src, dst, opts, k)   = k(Result.flatMap(sz -> Result.flatMap(_ -> FileSystem.copyWith(src, dst, opts), check(sz)), FileSystem.size(src#src)))

            // Non-data-transfer ops — pass through
            def truncate(file, k)             = k(FileSystem.truncate(file))
            def moveWith(src, dst, opts, k)   = k(FileSystem.moveWith(src, dst, opts))
            def delete(file, k)               = k(FileSystem.delete(file))
            def mkDir(d, k)                   = k(FileSystem.mkDir(d))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, providing optimistic
    /// concurrency control. When a file is read (`read`, `readLines`,
    /// `readBytes`), its modification time is snapshotted. When a file is
    /// subsequently written, the current modification time is compared against
    /// the snapshot. If the file was modified externally, the operation returns
    /// `Err(Conflict(...))`.
    ///
    /// After a successful write, the modification time is re-snapshotted to
    /// prevent false positives on subsequent writes to the same file.
    ///
    /// Files that were never read have no snapshot and are written without
    /// conflict checks.
    ///
    /// The following operations are passed through unchanged:
    /// `exists`, `isDirectory`, `isRegularFile`, `isSymbolicLink`,
    /// `isReadable`, `isWritable`, `isExecutable`, `accessTime`,
    /// `creationTime`, `modificationTime`, `size`, `list`, `glob`,
    /// `mkDir`, `mkDirs`, and `mkTempDir`.
    ///
    pub def withConflictCheck(f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        region rc {
            let snapshots: MutMap[String, Int64, rc] = MutMap.empty(rc);
            let getModTime = file -> FileSystem.modificationTime(file);
            let snap = file -> Fs.FsLayer.snapshotModTime(file, snapshots, getModTime);
            let check = file -> Fs.FsLayer.conflictCheck(file, snapshots, getModTime);
            let checkAndResnap = (file, doWrite) ->
                forM (
                    _ <- check(file);
                    v <- doWrite()
                ) yield { snap(file); v };
            run { f() } with handler FileSystem {
                // ── Stat/test/perm/time/size — pass through ──
                def exists(filename, k)           = k(FileSystem.exists(filename))
                def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
                def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
                def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
                def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
                def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
                def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
                def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
                def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
                def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
                def size(filename, k)             = k(FileSystem.size(filename))

                // ── List/glob — pass through ──
                def list(filename, k)             = k(FileSystem.list(filename))
                def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

                // ── Read ops — snapshot mod time after success ──
                def read(filename, k) = {
                    let result = FileSystem.read(filename);
                    match result {
                        case Ok(_) => snap(filename)
                        case _     => ()
                    };
                    k(result)
                }
                def readLines(filename, k) = {
                    let result = FileSystem.readLines(filename);
                    match result {
                        case Ok(_) => snap(filename)
                        case _     => ()
                    };
                    k(result)
                }
                def readBytes(filename, k) = {
                    let result = FileSystem.readBytes(filename);
                    match result {
                        case Ok(_) => snap(filename)
                        case _     => ()
                    };
                    k(result)
                }

                // ── Write ops — conflict check before, re-snapshot after ──
                def write(data, file, k)          = k(checkAndResnap(file, () -> FileSystem.write(data, file)))
                def writeLines(data, file, k)     = k(checkAndResnap(file, () -> FileSystem.writeLines(data, file)))
                def writeBytes(data, file, k)     = k(checkAndResnap(file, () -> FileSystem.writeBytes(data, file)))
                def append(data, file, k)         = k(checkAndResnap(file, () -> FileSystem.append(data, file)))
                def appendLines(data, file, k)    = k(checkAndResnap(file, () -> FileSystem.appendLines(data, file)))
                def appendBytes(data, file, k)    = k(checkAndResnap(file, () -> FileSystem.appendBytes(data, file)))
                def truncate(file, k)             = k(checkAndResnap(file, () -> FileSystem.truncate(file)))
                def delete(file, k) = {
                    match check(file) {
                        case Err(e) => k(Err(e))
                        case Ok(_) =>
                            let result = FileSystem.delete(file);
                            match result {
                                case Ok(_) => MutMap.remove(file, snapshots)
                                case _     => ()
                            };
                            k(result)
                    }
                }

                // ── Copy/move — conflict check on destination ──
                def copyWith(src, dst, opts, k)   = k(checkAndResnap(dst, () -> FileSystem.copyWith(src, dst, opts)))
                def moveWith(src, dst, opts, k)   = k(checkAndResnap(dst, () -> FileSystem.moveWith(src, dst, opts)))

                // ── Directory ops — pass through ──
                def mkDir(d, k)                   = k(FileSystem.mkDir(d))
                def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
                def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
            }
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, automatically rotating
    /// files when they reach a size threshold before append operations.
    ///
    /// Before `append`, `appendLines`, and `appendBytes`, the target file's size
    /// is checked. If it meets or exceeds `maxSize`, existing rotated files are
    /// shifted (`file.1` → `file.2`, etc.), the oldest (`file.{maxFiles}`) is
    /// deleted, and the current file is moved to `file.1`. The append then
    /// writes to the now-absent original path.
    ///
    /// All other operations are passed through unchanged.
    ///
    pub def withSizeRotation(maxSize: Size, maxFiles: Int32, f: Unit -> a \ ef): a \ (ef - FileSystem) + FileSystem =
        let doSize = file -> FileSystem.size(file);
        let doMove = (src, dst) -> FileSystem.moveWith(src = src, dst, Set#{Fs.MoveOption.ReplaceExisting});
        let doDelete = file -> FileSystem.delete(file);
        run { f() } with handler FileSystem {
            // Read/stat/test ops — pass through
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def read(filename, k)             = k(FileSystem.read(filename))
            def readLines(filename, k)        = k(FileSystem.readLines(filename))
            def readBytes(filename, k)        = k(FileSystem.readBytes(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

            // Append ops — rotate if needed before appending
            def append(data, file, k)         = k(Fs.FsLayer.rotateIfNeeded(file, maxSize, maxFiles, doSize, doMove, doDelete, () -> FileSystem.append(data, file)))
            def appendLines(data, file, k)    = k(Fs.FsLayer.rotateIfNeeded(file, maxSize, maxFiles, doSize, doMove, doDelete, () -> FileSystem.appendLines(data, file)))
            def appendBytes(data, file, k)    = k(Fs.FsLayer.rotateIfNeeded(file, maxSize, maxFiles, doSize, doMove, doDelete, () -> FileSystem.appendBytes(data, file)))

            // Other write ops — pass through
            def write(data, file, k)          = k(FileSystem.write(data, file))
            def writeLines(data, file, k)     = k(FileSystem.writeLines(data, file))
            def writeBytes(data, file, k)     = k(FileSystem.writeBytes(data, file))
            def truncate(file, k)             = k(FileSystem.truncate(file))
            def copyWith(src, dst, opts, k)   = k(FileSystem.copyWith(src, dst, opts))
            def moveWith(src, dst, opts, k)   = k(FileSystem.moveWith(src, dst, opts))
            def delete(file, k)               = k(FileSystem.delete(file))
            def mkDir(d, k)                   = k(FileSystem.mkDir(d))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

    ///
    /// Middleware that intercepts the `FileSystem` effect, providing data integrity
    /// via checksum sidecar files. Write operations produce checksum sidecars;
    /// read operations verify against them.
    ///
    /// `hash` is a pure function from file bytes to digest bytes.
    /// `sidecar` is the file suffix (e.g., `".sha256"`).
    ///
    /// After write, append, truncate, and copy operations, the file is re-read
    /// and a sidecar containing the hex digest is written. After move, the
    /// sidecar is moved. After delete, the sidecar is removed.
    ///
    /// On read, if a sidecar exists, the file's contents are hashed and compared
    /// against the stored checksum. If no sidecar exists, the read proceeds
    /// normally.
    ///
    /// Stat, test, permission, time, size, list, and glob operations are
    /// passed through unchanged.
    ///
    pub def withChecksum(
        hash: Vector[Int8] -> Vector[Int8],
        sidecar: String,
        f: Unit -> a \ ef
    ): a \ (ef - FileSystem) + FileSystem =
        let computeHex = bytes -> Util.Codec.encodeHex(hash(bytes));
        let doReadBytes = file -> FileSystem.readBytes(file);
        let doReadSidecar = file -> FileSystem.read(file);
        let updateSidecar = file -> Fs.FsLayer.updateSidecar(file, sidecar, computeHex, doReadBytes, (str, f1) -> FileSystem.write(str = str, f1));
        let afterWrite = (file, doWrite) -> Fs.FsLayer.checksumAfterWrite(file, doWrite, updateSidecar);
        let doMoveSidecar = (s, d) -> FileSystem.moveWith(src = s, d, Set#{Fs.MoveOption.ReplaceExisting});
        let doDeleteSidecar = file -> FileSystem.delete(file);
        run { f() } with handler FileSystem {
            // ── Stat/test/perm/time/size — pass through ──
            def exists(filename, k)           = k(FileSystem.exists(filename))
            def isDirectory(filename, k)      = k(FileSystem.isDirectory(filename))
            def isRegularFile(filename, k)    = k(FileSystem.isRegularFile(filename))
            def isSymbolicLink(filename, k)   = k(FileSystem.isSymbolicLink(filename))
            def isReadable(filename, k)       = k(FileSystem.isReadable(filename))
            def isWritable(filename, k)       = k(FileSystem.isWritable(filename))
            def isExecutable(filename, k)     = k(FileSystem.isExecutable(filename))
            def accessTime(filename, k)       = k(FileSystem.accessTime(filename))
            def creationTime(filename, k)     = k(FileSystem.creationTime(filename))
            def modificationTime(filename, k) = k(FileSystem.modificationTime(filename))
            def size(filename, k)             = k(FileSystem.size(filename))
            def list(filename, k)             = k(FileSystem.list(filename))
            def glob(base, pattern, k)        = k(FileSystem.glob(base, pattern))

            // ── Read ops — verify checksum after read ──
            def read(filename, k)             = k(Fs.FsLayer.checksumOnRead(filename, sidecar, computeHex, FileSystem.read(filename), doReadBytes, doReadSidecar))
            def readLines(filename, k)        = k(Fs.FsLayer.checksumOnRead(filename, sidecar, computeHex, FileSystem.readLines(filename), doReadBytes, doReadSidecar))
            def readBytes(filename, k)        = k(Fs.FsLayer.checksumOnReadBytes(filename, sidecar, computeHex, FileSystem.readBytes(filename), doReadSidecar))

            // ── Write ops — update checksum sidecar after write ──
            def write(data, file, k)          = k(afterWrite(file, () -> FileSystem.write(data, file)))
            def writeLines(data, file, k)     = k(afterWrite(file, () -> FileSystem.writeLines(data, file)))
            def writeBytes(data, file, k)     = k(afterWrite(file, () -> FileSystem.writeBytes(data, file)))
            def append(data, file, k)         = k(afterWrite(file, () -> FileSystem.append(data, file)))
            def appendLines(data, file, k)    = k(afterWrite(file, () -> FileSystem.appendLines(data, file)))
            def appendBytes(data, file, k)    = k(afterWrite(file, () -> FileSystem.appendBytes(data, file)))
            def truncate(file, k)             = k(afterWrite(file, () -> FileSystem.truncate(file)))
            def copyWith(src, dst, opts, k)   = k(afterWrite(dst, () -> FileSystem.copyWith(src, dst, opts)))
            def moveWith(src, dst, opts, k)   = k(Fs.FsLayer.checksumAfterMove(src#src, dst, sidecar, FileSystem.moveWith(src, dst, opts), doMoveSidecar))
            def delete(file, k)               = k(Fs.FsLayer.checksumAfterDelete(file, sidecar, FileSystem.delete(file), doDeleteSidecar))

            // ── Directory ops — pass through ──
            def mkDir(d, k)                   = k(FileSystem.mkDir(d))
            def mkDirs(d, k)                  = k(FileSystem.mkDirs(d))
            def mkTempDir(prefix, k)          = k(FileSystem.mkTempDir(prefix))
        }

}