diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs
index 1ca734e75bd1ef1512b699b90d4b3494f3882c18..dc6db9444bebf0ed9514876df50aeb0486b18864 100644
--- a/perl/lib/Nix/Store.xs
+++ b/perl/lib/Nix/Store.xs
@@ -106,7 +106,7 @@ SV * queryPathInfo(char * path, int base32)
                 XPUSHs(&PL_sv_undef);
             else
                 XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(*info->deriver).c_str(), 0)));
-            auto s = info->narHash.to_string(base32 ? Base32 : Base16);
+            auto s = info->narHash.to_string(base32 ? Base::Base32 : Base::Base16);
             XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
             mXPUSHi(info->registrationTime);
             mXPUSHi(info->narSize);
@@ -192,7 +192,7 @@ SV * hashPath(char * algo, int base32, char * path)
     PPCODE:
         try {
             Hash h = hashPath(parseHashType(algo), path).first;
-            auto s = h.to_string(base32 ? Base32 : Base16, false);
+            auto s = h.to_string(base32 ? Base::Base32 : Base::Base16, false);
             XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
         } catch (Error & e) {
             croak("%s", e.what());
@@ -203,7 +203,7 @@ SV * hashFile(char * algo, int base32, char * path)
     PPCODE:
         try {
             Hash h = hashFile(parseHashType(algo), path);
-            auto s = h.to_string(base32 ? Base32 : Base16, false);
+            auto s = h.to_string(base32 ? Base::Base32 : Base::Base16, false);
             XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
         } catch (Error & e) {
             croak("%s", e.what());
@@ -214,7 +214,7 @@ SV * hashString(char * algo, int base32, char * s)
     PPCODE:
         try {
             Hash h = hashString(parseHashType(algo), s);
-            auto s = h.to_string(base32 ? Base32 : Base16, false);
+            auto s = h.to_string(base32 ? Base::Base32 : Base::Base16, false);
             XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
         } catch (Error & e) {
             croak("%s", e.what());
@@ -225,7 +225,7 @@ SV * convertHash(char * algo, char * s, int toBase32)
     PPCODE:
         try {
             Hash h(s, parseHashType(algo));
-            string s = h.to_string(toBase32 ? Base32 : Base16, false);
+            string s = h.to_string(toBase32 ? Base::Base32 : Base::Base16, false);
             XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
         } catch (Error & e) {
             croak("%s", e.what());
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc
index 69d1c6f7e46730e68884cbea4d6b282812a2a1ca..6843a8f13883c9196ac504c5a2a513725c74dc85 100644
--- a/src/build-remote/build-remote.cc
+++ b/src/build-remote/build-remote.cc
@@ -184,7 +184,7 @@ static int _main(int argc, char * * argv)
 
                 try {
 
-                    Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri));
+                    Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("connecting to '%s'", bestMachine->storeUri));
 
                     Store::Params storeParams;
                     if (hasPrefix(bestMachine->storeUri, "ssh://")) {
@@ -222,7 +222,7 @@ connected:
         AutoCloseFD uploadLock = openLockFile(currentLoad + "/" + escapeUri(storeUri) + ".upload-lock", true);
 
         {
-            Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri));
+            Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("waiting for the upload lock to '%s'", storeUri));
 
             auto old = signal(SIGALRM, handleAlarm);
             alarm(15 * 60);
@@ -235,7 +235,7 @@ connected:
         auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute;
 
         {
-            Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri));
+            Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("copying dependencies to '%s'", storeUri));
             copyPaths(store, ref<Store>(sshStore), store->parseStorePathSet(inputs), NoRepair, NoCheckSigs, substitute);
         }
 
@@ -254,7 +254,7 @@ connected:
             if (!store->isValidPath(store->parseStorePath(path))) missing.insert(store->parseStorePath(path));
 
         if (!missing.empty()) {
-            Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri));
+            Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("copying outputs from '%s'", storeUri));
             for (auto & i : missing)
                 store->locksHeld.insert(store->printStorePath(i)); /* FIXME: ugly */
             copyPaths(ref<Store>(sshStore), store, missing, NoRepair, NoCheckSigs, NoSubstitute);
diff --git a/src/cpptoml/cpptoml.h b/src/cpptoml/cpptoml.h
index 5a00da3b4cdac83d2ba75fad7ccbe4bb4cf8dc2c..fae1f0bc9fba6a449dcc74e17c506eacb9f705f6 100644
--- a/src/cpptoml/cpptoml.h
+++ b/src/cpptoml/cpptoml.h
@@ -51,7 +51,7 @@ using string_to_base_map
     = std::unordered_map<std::string, std::shared_ptr<base>>;
 #endif
 
-// if defined, `base` will retain type information in form of an enum class
+// if defined, `base` will retain type information in form of an enum struct
 // such that static_cast can be used instead of dynamic_cast
 // #define CPPTOML_NO_RTTI
 
@@ -405,7 +405,7 @@ inline std::shared_ptr<table_array> make_table_array(bool is_inline = false);
 
 #if defined(CPPTOML_NO_RTTI)
 /// Base type used to store underlying data type explicitly if RTTI is disabled
-enum class base_type
+enum struct base_type
 {
     NONE,
     STRING,
@@ -2268,7 +2268,7 @@ class parser
         return key;
     }
 
-    enum class parse_type
+    enum struct parse_type
     {
         STRING = 1,
         LOCAL_TIME,
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index dac32b6f56323be7bfc5d78a8b08bc3c607c1b13..b94035a60724b22dccd3abc261e54d042caf4317 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -1661,10 +1661,10 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
     else {
         auto p = settings.readOnlyMode
             ? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first
-            : store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), true, htSHA256, defaultPathFilter, repair);
+            : store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), true, HashType::SHA256, defaultPathFilter, repair);
         dstPath = store->printStorePath(p);
         srcToStore.insert_or_assign(path, std::move(p));
-        printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, dstPath);
+        printMsg(Verbosity::Chatty, "copied source '%1%' -> '%2%'", path, dstPath);
     }
 
     context.insert(dstPath);
diff --git a/src/libexpr/function-trace.cc b/src/libexpr/function-trace.cc
index c6057b3842f1ad75fe351d5d824e9024b1cbd5e5..882da99370ff32860615224fb47b69e93f8b924d 100644
--- a/src/libexpr/function-trace.cc
+++ b/src/libexpr/function-trace.cc
@@ -6,13 +6,13 @@ namespace nix {
 FunctionCallTrace::FunctionCallTrace(const Pos & pos) : pos(pos) {
     auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
     auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
-    printMsg(lvlInfo, "function-trace entered %1% at %2%", pos, ns.count());
+    printMsg(Verbosity::Info, "function-trace entered %1% at %2%", pos, ns.count());
 }
 
 FunctionCallTrace::~FunctionCallTrace() {
     auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
     auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
-    printMsg(lvlInfo, "function-trace exited %1% at %2%", pos, ns.count());
+    printMsg(Verbosity::Info, "function-trace exited %1% at %2%", pos, ns.count());
 }
 
 }
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 9c769e803768a7e09e72609c56529d64715c8e6a..acffc23e3a0f58f808876c2f1a7788ebd33f7a2a 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -625,7 +625,7 @@ Expr * EvalState::parseExprFromString(std::string_view s, const Path & basePath)
 
 Expr * EvalState::parseStdin()
 {
-    //Activity act(*logger, lvlTalkative, format("parsing standard input"));
+    //Activity act(*logger, Verbosity::Talkative, format("parsing standard input"));
     return parseExprFromString(drainFD(0), absPath("."));
 }
 
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 8de23495187d61adcfe7bbd5df936c6c93f96580..1ddbe33c44569eb19201f7b802e2b205c6cf142f 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -719,14 +719,14 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
         if (outputs.size() != 1 || *(outputs.begin()) != "out")
             throw Error(format("multiple outputs are not supported in fixed-output derivations, at %1%") % posDrvName);
 
-        HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo);
+        HashType ht = outputHashAlgo.empty() ? HashType::Unknown : parseHashType(outputHashAlgo);
         Hash h(*outputHash, ht);
 
         auto outPath = state.store->makeFixedOutputPath(outputHashRecursive, h, drvName);
         if (!jsonObject) drv.env["out"] = state.store->printStorePath(outPath);
         drv.outputs.insert_or_assign("out", DerivationOutput(std::move(outPath),
                 (outputHashRecursive ? "r:" : "") + printHashType(h.type),
-                h.to_string(Base16, false)));
+                h.to_string(Base::Base16, false)));
     }
 
     else {
@@ -756,7 +756,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     auto drvPath = writeDerivation(state.store, drv, drvName, state.repair);
     auto drvPathS = state.store->printStorePath(drvPath);
 
-    printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
+    printMsg(Verbosity::Chatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
 
     /* Optimisation, but required in read-only mode! because in that
        case we don't actually write store derivations, so we can't
@@ -933,13 +933,13 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
 {
     string type = state.forceStringNoCtx(*args[0], pos);
     HashType ht = parseHashType(type);
-    if (ht == htUnknown)
+    if (ht == HashType::Unknown)
       throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
 
     PathSet context; // discarded
     Path p = state.coerceToPath(pos, *args[1], context);
 
-    mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), context);
+    mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base::Base16, false), context);
 }
 
 /* Read a directory (without . or ..) */
@@ -1073,8 +1073,8 @@ static void addPath(EvalState & state, const Pos & pos, const string & name, con
     Path dstPath;
     if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
         dstPath = state.store->printStorePath(settings.readOnlyMode
-            ? state.store->computeStorePathForPath(name, path, recursive, htSHA256, filter).first
-            : state.store->addToStore(name, path, recursive, htSHA256, filter, state.repair));
+            ? state.store->computeStorePathForPath(name, path, recursive, HashType::SHA256, filter).first
+            : state.store->addToStore(name, path, recursive, HashType::SHA256, filter, state.repair));
         if (expectedHash && expectedStorePath != state.store->parseStorePath(dstPath))
             throw Error("store path mismatch in (possibly filtered) path added from '%s'", path);
     } else
@@ -1122,7 +1122,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
         } else if (n == "recursive")
             recursive = state.forceBool(*attr.value, *attr.pos);
         else if (n == "sha256")
-            expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+            expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), HashType::SHA256);
         else
             throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos);
     }
@@ -1806,13 +1806,13 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
 {
     string type = state.forceStringNoCtx(*args[0], pos);
     HashType ht = parseHashType(type);
-    if (ht == htUnknown)
+    if (ht == HashType::Unknown)
       throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
 
     PathSet context; // discarded
     string s = state.forceString(*args[1], context, pos);
 
-    mkString(v, hashString(ht, s).to_string(Base16, false), context);
+    mkString(v, hashString(ht, s).to_string(Base::Base16, false), context);
 }
 
 
@@ -2068,7 +2068,7 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
             if (n == "url")
                 request.uri = state.forceStringNoCtx(*attr.value, *attr.pos);
             else if (n == "sha256")
-                request.expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+                request.expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), HashType::SHA256);
             else if (n == "name")
                 request.name = state.forceStringNoCtx(*attr.value, *attr.pos);
             else
diff --git a/src/libexpr/primops/fetchGit.cc b/src/libexpr/primops/fetchGit.cc
index 4aee1073eeb105d0c41a64f0b2f2e94fd149f64e..7f70dfab0bdb5db2ccc9ba0db102a55b7b62db43 100644
--- a/src/libexpr/primops/fetchGit.cc
+++ b/src/libexpr/primops/fetchGit.cc
@@ -69,7 +69,7 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
                 return files.count(file);
             };
 
-            gitInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, htSHA256, filter));
+            gitInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, HashType::SHA256, filter));
 
             return gitInfo;
         }
@@ -86,7 +86,7 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
 
     deletePath(getCacheDir() + "/nix/git");
 
-    Path cacheDir = getCacheDir() + "/nix/gitv2/" + hashString(htSHA256, uri).to_string(Base32, false);
+    Path cacheDir = getCacheDir() + "/nix/gitv2/" + hashString(HashType::SHA256, uri).to_string(Base::Base32, false);
 
     if (!pathExists(cacheDir)) {
         createDirs(dirOf(cacheDir));
@@ -123,7 +123,7 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
     }
     if (doFetch)
     {
-        Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Git repository '%s'", uri));
+        Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("fetching Git repository '%s'", uri));
 
         // FIXME: git stderr messes up our progress indicator, so
         // we're using --quiet for now. Should process its stderr.
@@ -145,7 +145,7 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
 
     printTalkative("using revision %s of repo '%s'", gitInfo.rev, uri);
 
-    std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + gitInfo.rev).to_string(Base32, false);
+    std::string storeLinkName = hashString(HashType::SHA512, name + std::string("\0"s) + gitInfo.rev).to_string(Base::Base32, false);
     Path storeLink = cacheDir + "/" + storeLinkName + ".link";
     PathLocks storeLinkLock({storeLink}, fmt("waiting for lock on '%1%'...", storeLink)); // FIXME: broken
 
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index db274fa4f70e9125edd186a9454cab5b22faea4d..65f52f6828a203a7f1478e65bcb21908345ef48b 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -63,7 +63,7 @@ HgInfo exportMercurial(ref<Store> store, const std::string & uri,
                 return files.count(file);
             };
 
-            hgInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, htSHA256, filter));
+            hgInfo.storePath = store->printStorePath(store->addToStore("source", uri, true, HashType::SHA256, filter));
 
             return hgInfo;
         }
@@ -71,9 +71,9 @@ HgInfo exportMercurial(ref<Store> store, const std::string & uri,
 
     if (rev == "") rev = "default";
 
-    Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(htSHA256, uri).to_string(Base32, false));
+    Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(HashType::SHA256, uri).to_string(Base::Base32, false));
 
-    Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, hashString(htSHA512, rev).to_string(Base32, false));
+    Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, hashString(HashType::SHA512, rev).to_string(Base::Base32, false));
 
     /* If we haven't pulled this repo less than ‘tarball-ttl’ seconds,
        do so now. */
@@ -90,7 +90,7 @@ HgInfo exportMercurial(ref<Store> store, const std::string & uri,
                     RunOptions("hg", { "log", "-R", cacheDir, "-r", rev, "--template", "1" })
                     .killStderr(true)).second == "1"))
         {
-            Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", uri));
+            Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("fetching Mercurial repository '%s'", uri));
 
             if (pathExists(cacheDir)) {
                 try {
@@ -124,7 +124,7 @@ HgInfo exportMercurial(ref<Store> store, const std::string & uri,
     hgInfo.revCount = std::stoull(tokens[1]);
     hgInfo.branch = tokens[2];
 
-    std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + hgInfo.rev).to_string(Base32, false);
+    std::string storeLinkName = hashString(HashType::SHA512, name + std::string("\0"s) + hgInfo.rev).to_string(Base::Base32, false);
     Path storeLink = fmt("%s/.hg/%s.link", cacheDir, storeLinkName);
 
     try {
diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc
index 9e1d7cee60e654111ca63ae2fe8da90b18a36bc3..9c873e22a632d99c5c212090ff82a5c873818c79 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -10,17 +10,19 @@ MixCommonArgs::MixCommonArgs(const string & programName)
         .longName("verbose")
         .shortName('v')
         .description("increase verbosity level")
-        .handler([]() { verbosity = (Verbosity) (verbosity + 1); });
+        .handler([]() { verbosity = (Verbosity) ((uint64_t) verbosity + 1); });
 
     mkFlag()
         .longName("quiet")
         .description("decrease verbosity level")
-        .handler([]() { verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError; });
+        .handler([]() { verbosity = verbosity > Verbosity::Error
+            ? (Verbosity) ((uint64_t) verbosity - 1)
+            : Verbosity::Error; });
 
     mkFlag()
         .longName("debug")
         .description("enable debug output")
-        .handler([]() { verbosity = lvlDebug; });
+        .handler([]() { verbosity = Verbosity::Debug; });
 
     mkFlag()
         .longName("option")
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index d41e772e9a72dfcc02c38d3dc9a8634a12e975a0..79e35eedc08e97e1512af1c5ea1d39f6ee796f05 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -251,7 +251,7 @@ void parseCmdLine(const string & programName, const Strings & args,
 void printVersion(const string & programName)
 {
     std::cout << format("%1% (Nix) %2%") % programName % nixVersion << std::endl;
-    if (verbosity > lvlInfo) {
+    if (verbosity > Verbosity::Info) {
         Strings cfg;
 #if HAVE_BOEHMGC
         cfg.push_back("gc");
diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh
index b49574652271451de42ecd46cbab111f3bde6a8f..96d001ec3896acd4503c55f549a804efc0e14c55 100644
--- a/src/libmain/shared.hh
+++ b/src/libmain/shared.hh
@@ -43,11 +43,11 @@ struct StorePathWithOutputs;
 void printMissing(
     ref<Store> store,
     const std::vector<StorePathWithOutputs> & paths,
-    Verbosity lvl = lvlInfo);
+    Verbosity lvl = Verbosity::Info);
 
 void printMissing(ref<Store> store, const StorePathSet & willBuild,
     const StorePathSet & willSubstitute, const StorePathSet & unknown,
-    unsigned long long downloadSize, unsigned long long narSize, Verbosity lvl = lvlInfo);
+    unsigned long long downloadSize, unsigned long long narSize, Verbosity lvl = Verbosity::Info);
 
 string getArg(const string & opt,
     Strings::iterator & i, const Strings::iterator & end);
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index 3a2d848615b590c51f39223850e6dc5e988459d0..a650bbfa674c698b2e236616d5cff1090fcb36b8 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -134,7 +134,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::str
     auto narInfo = make_ref<NarInfo>(info);
 
     narInfo->narSize = nar->size();
-    narInfo->narHash = hashString(htSHA256, *nar);
+    narInfo->narHash = hashString(HashType::SHA256, *nar);
 
     if (info.narHash && info.narHash != narInfo->narHash)
         throw Error("refusing to copy corrupted path '%1%' to binary cache", printStorePath(info.path));
@@ -169,16 +169,16 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::str
     auto now1 = std::chrono::steady_clock::now();
     auto narCompressed = compress(compression, *nar, parallelCompression);
     auto now2 = std::chrono::steady_clock::now();
-    narInfo->fileHash = hashString(htSHA256, *narCompressed);
+    narInfo->fileHash = hashString(HashType::SHA256, *narCompressed);
     narInfo->fileSize = narCompressed->size();
 
     auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now2 - now1).count();
-    printMsg(lvlTalkative, "copying path '%1%' (%2% bytes, compressed %3$.1f%% in %4% ms) to binary cache",
+    printMsg(Verbosity::Talkative, "copying path '%1%' (%2% bytes, compressed %3$.1f%% in %4% ms) to binary cache",
         printStorePath(narInfo->path), narInfo->narSize,
         ((1.0 - (double) narCompressed->size() / nar->size()) * 100.0),
         duration);
 
-    narInfo->url = "nar/" + narInfo->fileHash.to_string(Base32, false) + ".nar"
+    narInfo->url = "nar/" + narInfo->fileHash.to_string(Base::Base32, false) + ".nar"
         + (compression == "xz" ? ".xz" :
            compression == "bzip2" ? ".bz2" :
            compression == "br" ? ".br" :
@@ -206,7 +206,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::str
                 // to a GC'ed file, so overwriting might be useful...
                 if (fileExists(key)) return;
 
-                printMsg(lvlTalkative, "creating debuginfo link from '%s' to '%s'", key, target);
+                printMsg(Verbosity::Talkative, "creating debuginfo link from '%s' to '%s'", key, target);
 
                 upsertFile(key, json.dump(), "application/json");
             };
@@ -299,7 +299,7 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
 {
     auto uri = getUri();
     auto storePathS = printStorePath(storePath);
-    auto act = std::make_shared<Activity>(*logger, lvlTalkative, actQueryPathInfo,
+    auto act = std::make_shared<Activity>(*logger, Verbosity::Talkative, ActivityType::QueryPathInfo,
         fmt("querying info about '%s' on '%s'", storePathS, uri), Logger::Fields{storePathS, uri});
     PushActivity pact(act->id);
 
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 0e3a23a4dc8bac7ba056b6cda36169e65b1969f4..0259cfd0bffe493c82be285393b9e208f7a80099 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -107,7 +107,13 @@ typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
 class Goal : public std::enable_shared_from_this<Goal>
 {
 public:
-    typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
+    enum struct ExitCode {
+        Busy,
+        Success,
+        Failed,
+        NoSubstituters,
+        IncompleteClosure,
+    };
 
 protected:
 
@@ -141,7 +147,7 @@ protected:
     Goal(Worker & worker) : worker(worker)
     {
         nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
-        exitCode = ecBusy;
+        exitCode = ExitCode::Busy;
     }
 
     virtual ~Goal()
@@ -361,8 +367,8 @@ public:
     {
         actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
         actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
-        act.setExpected(actDownload, expectedDownloadSize + doneDownloadSize);
-        act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
+        act.setExpected(ActivityType::Download, expectedDownloadSize + doneDownloadSize);
+        act.setExpected(ActivityType::CopyPath, expectedNarSize + doneNarSize);
     }
 };
 
@@ -395,13 +401,13 @@ void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
     trace(format("waitee '%1%' done; %2% left") %
         waitee->name % waitees.size());
 
-    if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure) ++nrFailed;
+    if (result == ExitCode::Failed || result == ExitCode::NoSubstituters || result == ExitCode::IncompleteClosure) ++nrFailed;
 
-    if (result == ecNoSubstituters) ++nrNoSubstituters;
+    if (result == ExitCode::NoSubstituters) ++nrNoSubstituters;
 
-    if (result == ecIncompleteClosure) ++nrIncompleteClosure;
+    if (result == ExitCode::IncompleteClosure) ++nrIncompleteClosure;
 
-    if (waitees.empty() || (result == ecFailed && !settings.keepGoing)) {
+    if (waitees.empty() || (result == ExitCode::Failed && !settings.keepGoing)) {
 
         /* If we failed and keepGoing is not set, we remove all
            remaining waitees. */
@@ -421,8 +427,8 @@ void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
 void Goal::amDone(ExitCode result)
 {
     trace("done");
-    assert(exitCode == ecBusy);
-    assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
+    assert(exitCode == ExitCode::Busy);
+    assert(result == ExitCode::Success || result == ExitCode::Failed || result == ExitCode::NoSubstituters || result == ExitCode::IncompleteClosure);
     exitCode = result;
     for (auto & i : waiters) {
         GoalPtr goal = i.lock();
@@ -668,7 +674,7 @@ HookInstance::HookInstance()
 
         Strings args = {
             std::string(baseNameOf(settings.buildHook.get())),
-            std::to_string(verbosity),
+            std::to_string((uint64_t)verbosity),
         };
 
         execv(settings.buildHook.get().c_str(), stringsToCharPtrs(args).data());
@@ -1445,7 +1451,7 @@ void DerivationGoal::tryToBuild()
             "building '%s'", worker.store.printStorePath(drvPath), curRound, nrRounds);
         fmt("building '%s'", worker.store.printStorePath(drvPath));
         if (hook) msg += fmt(" on '%s'", machineName);
-        act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
+        act = std::make_unique<Activity>(*logger, Verbosity::Info, ActivityType::Build, msg,
             Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", curRound, nrRounds});
         mcRunningBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.runningBuilds);
         worker.updateProgress();
@@ -1625,7 +1631,7 @@ void DerivationGoal::buildDone()
         registerOutputs();
 
         if (settings.postBuildHook != "") {
-            Activity act(*logger, lvlInfo, actPostBuildHook,
+            Activity act(*logger, Verbosity::Info, ActivityType::PostBuildHook,
                 fmt("running post-build-hook '%s'", settings.postBuildHook),
                 Logger::Fields{worker.store.printStorePath(drvPath)});
             PushActivity pact(act.id);
@@ -1660,7 +1666,7 @@ void DerivationGoal::buildDone()
                     if (settings.verboseBuild) {
                         printError("post-build-hook: " + currentLine);
                     } else {
-                        act.result(resPostBuildLogLine, currentLine);
+                        act.result(ResultType::PostBuildLogLine, currentLine);
                     }
                     currentLine.clear();
                 }
@@ -2078,7 +2084,7 @@ void DerivationGoal::startBuilder()
         /* Clean up the chroot directory automatically. */
         autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
 
-        printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
+        printMsg(Verbosity::Chatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
 
         if (mkdir(chrootRootDir.c_str(), 0750) == -1)
             throw SysError(format("cannot create '%1%'") % chrootRootDir);
@@ -2187,7 +2193,7 @@ void DerivationGoal::startBuilder()
     }
 
     if (useChroot && settings.preBuildHook != "" && dynamic_cast<Derivation *>(drv.get())) {
-        printMsg(lvlChatty, format("executing pre-build hook '%1%'")
+        printMsg(Verbosity::Chatty, format("executing pre-build hook '%1%'")
             % settings.preBuildHook);
         auto args = useChroot ? Strings({worker.store.printStorePath(drvPath), chrootRootDir}) :
             Strings({ worker.store.printStorePath(drvPath) });
@@ -2229,7 +2235,7 @@ void DerivationGoal::startBuilder()
         startDaemon();
 
     /* Run the builder. */
-    printMsg(lvlChatty, format("executing builder '%1%'") % drv->builder);
+    printMsg(Verbosity::Chatty, format("executing builder '%1%'") % drv->builder);
 
     /* Create the log file. */
     Path logFile = openLogFile();
@@ -2462,8 +2468,8 @@ void DerivationGoal::initTmpDir() {
             if (passAsFile.find(i.first) == passAsFile.end()) {
                 env[i.first] = i.second;
             } else {
-                auto hash = hashString(htSHA256, i.first);
-                string fn = ".attr-" + hash.to_string(Base32, false);
+                auto hash = hashString(HashType::SHA256, i.first);
+                string fn = ".attr-" + hash.to_string(Base::Base32, false);
                 Path p = tmpDir + "/" + fn;
                 writeFile(p, i.second);
                 chownToBuilder(p);
@@ -2712,7 +2718,7 @@ struct RestrictedStore : public LocalFSStore
     { throw Error("queryPathFromHashPart"); }
 
     StorePath addToStore(const string & name, const Path & srcPath,
-        bool recursive = true, HashType hashAlgo = htSHA256,
+        bool recursive = true, HashType hashAlgo = HashType::SHA256,
         PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) override
     { throw Error("addToStore"); }
 
@@ -2725,7 +2731,7 @@ struct RestrictedStore : public LocalFSStore
     }
 
     StorePath addToStoreFromDump(const string & dump, const string & name,
-        bool recursive = true, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair) override
+        bool recursive = true, HashType hashAlgo = HashType::SHA256, RepairFlag repair = NoRepair) override
     {
         auto path = next->addToStoreFromDump(dump, name, recursive, hashAlgo, repair);
         goal.addDependency(path);
@@ -3670,7 +3676,7 @@ void DerivationGoal::registerOutputs()
                 worker.hashMismatch = true;
                 delayedException = std::make_exception_ptr(
                     BuildError("hash mismatch in fixed-output derivation '%s':\n  wanted: %s\n  got:    %s",
-                        worker.store.printStorePath(dest), h.to_string(SRI), h2.to_string(SRI)));
+                        worker.store.printStorePath(dest), h.to_string(Base::SRI), h2.to_string(Base::SRI)));
 
                 Path actualDest = worker.store.Store::toRealPath(dest);
 
@@ -4114,7 +4120,7 @@ void DerivationGoal::flushLine()
             if (logTail.size() > settings.logLines) logTail.pop_front();
         }
 
-        act->result(resBuildLogLine, currentLogLine);
+        act->result(ResultType::BuildLogLine, currentLogLine);
     }
 
     currentLogLine = "";
@@ -4141,7 +4147,7 @@ void DerivationGoal::addHashRewrite(const StorePath & path)
     auto h1 = std::string(((std::string_view) path.to_string()).substr(0, 32));
     auto p = worker.store.makeStorePath(
         "rewrite:" + std::string(drvPath.to_string()) + ":" + std::string(path.to_string()),
-        Hash(htSHA256), path.name());
+        Hash(HashType::SHA256), path.name());
     auto h2 = std::string(((std::string_view) p.to_string()).substr(0, 32));
     deletePath(worker.store.printStorePath(p));
     inputRewrites[h1] = h2;
@@ -4154,7 +4160,7 @@ void DerivationGoal::done(BuildResult::Status status, const string & msg)
 {
     result.status = status;
     result.errorMsg = msg;
-    amDone(result.success() ? ecSuccess : ecFailed);
+    amDone(result.success() ? ExitCode::Success : ExitCode::Failed);
     if (result.status == BuildResult::TimedOut)
         worker.timedOut = true;
     if (result.status == BuildResult::PermanentFailure)
@@ -4295,7 +4301,7 @@ void SubstitutionGoal::init()
 
     /* If the path already exists we're done. */
     if (!repair && worker.store.isValidPath(storePath)) {
-        amDone(ecSuccess);
+        amDone(ExitCode::Success);
         return;
     }
 
@@ -4320,7 +4326,7 @@ void SubstitutionGoal::tryNext()
         /* Hack: don't indicate failure if there were no substituters.
            In that case the calling derivation should just do a
            build. */
-        amDone(substituterFailed ? ecFailed : ecNoSubstituters);
+        amDone(substituterFailed ? ExitCode::Failed : ExitCode::NoSubstituters);
 
         if (substituterFailed) {
             worker.failedSubstitutions++;
@@ -4403,7 +4409,7 @@ void SubstitutionGoal::referencesValid()
 
     if (nrFailed > 0) {
         debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath));
-        amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
+        amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ExitCode::IncompleteClosure : ExitCode::Failed);
         return;
     }
 
@@ -4441,7 +4447,7 @@ void SubstitutionGoal::tryToRun()
             /* Wake up the worker loop when we're done. */
             Finally updateStats([this]() { outPipe.writeSide = -1; });
 
-            Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
+            Activity act(*logger, ActivityType::Substitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
             PushActivity pact(act.id);
 
             copyStorePath(ref<Store>(sub), ref<Store>(worker.store.shared_from_this()),
@@ -4490,7 +4496,7 @@ void SubstitutionGoal::finished()
 
     worker.markContentsGood(storePath.clone());
 
-    printMsg(lvlChatty, "substitution of path '%s' succeeded", worker.store.printStorePath(storePath));
+    printMsg(Verbosity::Chatty, "substitution of path '%s' succeeded", worker.store.printStorePath(storePath));
 
     maintainRunningSubstitutions.reset();
 
@@ -4508,7 +4514,7 @@ void SubstitutionGoal::finished()
 
     worker.updateProgress();
 
-    amDone(ecSuccess);
+    amDone(ExitCode::Success);
 }
 
 
@@ -4527,9 +4533,9 @@ void SubstitutionGoal::handleEOF(int fd)
 
 
 Worker::Worker(LocalStore & store)
-    : act(*logger, actRealise)
-    , actDerivations(*logger, actBuilds)
-    , actSubstitutions(*logger, actCopyPaths)
+    : act(*logger, ActivityType::Realise)
+    , actDerivations(*logger, ActivityType::Builds)
+    , actSubstitutions(*logger, ActivityType::CopyPaths)
     , store(store)
 {
     /* Debugging: prevent recursive workers. */
@@ -4613,7 +4619,7 @@ void Worker::removeGoal(GoalPtr goal)
         topGoals.erase(goal);
         /* If a top-level goal failed, then kill all other goals
            (unless keepGoing was set). */
-        if (goal->getExitCode() == Goal::ecFailed && !settings.keepGoing)
+        if (goal->getExitCode() == Goal::ExitCode::Failed && !settings.keepGoing)
             topGoals.clear();
     }
 
@@ -4757,7 +4763,7 @@ void Worker::run(const Goals & _topGoals)
 
 void Worker::waitForInput()
 {
-    printMsg(lvlVomit, "waiting for children");
+    printMsg(Verbosity::Vomit, "waiting for children");
 
     /* Process output from the file descriptors attached to the
        children, namely log output and output path creation commands.
@@ -4852,7 +4858,7 @@ void Worker::waitForInput()
                     if (errno != EINTR)
                         throw SysError("%s: read failed", goal->getName());
                 } else {
-                    printMsg(lvlVomit, format("%1%: read %2% bytes")
+                    printMsg(Verbosity::Vomit, format("%1%: read %2% bytes")
                         % goal->getName() % rd);
                     string data((char *) buffer.data(), rd);
                     j->lastOutput = after;
@@ -4861,7 +4867,7 @@ void Worker::waitForInput()
             }
         }
 
-        if (goal->getExitCode() == Goal::ecBusy &&
+        if (goal->getExitCode() == Goal::ExitCode::Busy &&
             0 != settings.maxSilentTime &&
             j->respectTimeouts &&
             after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
@@ -4872,7 +4878,7 @@ void Worker::waitForInput()
             goal->timedOut();
         }
 
-        else if (goal->getExitCode() == Goal::ecBusy &&
+        else if (goal->getExitCode() == Goal::ExitCode::Busy &&
             0 != settings.buildTimeout &&
             j->respectTimeouts &&
             after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
@@ -4934,7 +4940,7 @@ bool Worker::pathContentsGood(const StorePath & path)
         res = false;
     else {
         HashResult current = hashPath(info->narHash.type, store.printStorePath(path));
-        Hash nullHash(htSHA256);
+        Hash nullHash(HashType::SHA256);
         res = info->narHash == nullHash || info->narHash == current.first;
     }
     pathContentsGoodCache.insert_or_assign(path.clone(), res);
@@ -4983,7 +4989,7 @@ void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths,
 
     StorePathSet failed;
     for (auto & i : goals) {
-        if (i->getExitCode() != Goal::ecSuccess) {
+        if (i->getExitCode() != Goal::ExitCode::Success) {
             DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i.get());
             if (i2) failed.insert(i2->getDrvPath());
             else failed.insert(dynamic_cast<SubstitutionGoal *>(i.get())->getStorePath());
@@ -5028,7 +5034,7 @@ void LocalStore::ensurePath(const StorePath & path)
 
     worker.run(goals);
 
-    if (goal->getExitCode() != Goal::ecSuccess)
+    if (goal->getExitCode() != Goal::ExitCode::Success)
         throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
 }
 
@@ -5041,7 +5047,7 @@ void LocalStore::repairPath(const StorePath & path)
 
     worker.run(goals);
 
-    if (goal->getExitCode() != Goal::ecSuccess) {
+    if (goal->getExitCode() != Goal::ExitCode::Success) {
         /* Since substituting the path didn't work, if we have a valid
            deriver, then rebuild the deriver. */
         auto info = queryPathInfo(path);
diff --git a/src/libstore/builtins/fetchurl.cc b/src/libstore/builtins/fetchurl.cc
index f6ae5d2e622ea6557367d721203ab73b62b5e50f..809689d4460998e873ffdafda1413760f90fa13a 100644
--- a/src/libstore/builtins/fetchurl.cc
+++ b/src/libstore/builtins/fetchurl.cc
@@ -65,7 +65,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
                 if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/';
                 auto ht = parseHashType(getAttr("outputHashAlgo"));
                 auto h = Hash(getAttr("outputHash"), ht);
-                fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(Base16, false));
+                fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(Base::Base16, false));
                 return;
             } catch (Error & e) {
                 debug(e.what());
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 8e9f9d71bf077db5295b1062d053069b66aa8b8f..f854d1ebd4978189760aab1e1fa9023a1a081820 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -114,7 +114,13 @@ struct TunnelLogger : public Logger
         }
 
         StringSink buf;
-        buf << STDERR_START_ACTIVITY << act << lvl << type << s << fields << parent;
+        buf << STDERR_START_ACTIVITY 
+            << act
+            << (uint64_t) lvl
+            << (uint64_t) type
+            << s
+            << fields
+            << parent;
         enqueueMsg(*buf.s);
     }
 
@@ -130,7 +136,10 @@ struct TunnelLogger : public Logger
     {
         if (GET_PROTOCOL_MINOR(clientVersion) < 20) return;
         StringSink buf;
-        buf << STDERR_RESULT << act << type << fields;
+        buf << STDERR_RESULT
+            << act
+            << (uint64_t) type
+            << fields;
         enqueueMsg(*buf.s);
     }
 };
@@ -302,7 +311,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
         logger->startWork();
         auto hash = store->queryPathInfo(path)->narHash;
         logger->stopWork();
-        to << hash.to_string(Base16, false);
+        to << hash.to_string(Base::Base16, false);
         break;
     }
 
@@ -542,7 +551,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
         clientSettings.maxBuildJobs = readInt(from);
         clientSettings.maxSilentTime = readInt(from);
         readInt(from); // obsolete useBuildHook
-        clientSettings.verboseBuild = lvlError == (Verbosity) readInt(from);
+        clientSettings.verboseBuild = Verbosity::Error == (Verbosity) readInt(from);
         readInt(from); // obsolete logType
         readInt(from); // obsolete printBuildTrace
         clientSettings.buildCores = readInt(from);
@@ -627,7 +636,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
             if (GET_PROTOCOL_MINOR(clientVersion) >= 17)
                 to << 1;
             to << (info->deriver ? store->printStorePath(*info->deriver) : "")
-               << info->narHash.to_string(Base16, false);
+               << info->narHash.to_string(Base::Base16, false);
             writeStorePaths(*store, to, info->references);
             to << info->registrationTime << info->narSize;
             if (GET_PROTOCOL_MINOR(clientVersion) >= 16) {
@@ -687,7 +696,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
         auto deriver = readString(from);
         if (deriver != "")
             info.deriver = store->parseStorePath(deriver);
-        info.narHash = Hash(readString(from), htSHA256);
+        info.narHash = Hash(readString(from), HashType::SHA256);
         info.references = readStorePaths<StorePathSet>(*store, from);
         from >> info.registrationTime >> info.narSize >> info.ultimate;
         info.sigs = readStrings<StringSet>(from);
@@ -770,7 +779,7 @@ void processConnection(
 
     Finally finally([&]() {
         _isInterrupted = false;
-        prevLogger->log(lvlDebug, fmt("%d operations", opCount));
+        prevLogger->log(Verbosity::Debug, fmt("%d operations", opCount));
     });
 
     if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from)) {
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 973ddc86abf190f2703e6be5c92b1c3d15523ba9..38b4122dd366d69eac3e38329f9043659194b533 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -20,7 +20,7 @@ void DerivationOutput::parseHashInfo(bool & recursive, Hash & hash) const
     }
 
     HashType hashType = parseHashType(algo);
-    if (hashType == htUnknown)
+    if (hashType == HashType::Unknown)
         throw Error("unknown hash algorithm '%s'", algo);
 
     hash = Hash(this->hash, hashType);
@@ -364,7 +364,7 @@ Hash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutput
     /* Return a fixed hash for fixed-output derivations. */
     if (drv.isFixedOutput()) {
         DerivationOutputs::const_iterator i = drv.outputs.begin();
-        return hashString(htSHA256, "fixed:out:"
+        return hashString(HashType::SHA256, "fixed:out:"
             + i->second.hashAlgo + ":"
             + i->second.hash + ":"
             + store.printStorePath(i->second.path));
@@ -380,10 +380,10 @@ Hash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutput
             h = drvHashes.insert_or_assign(i.first.clone(), hashDerivationModulo(store,
                 readDerivation(store, store.toRealPath(store.printStorePath(i.first))), false)).first;
         }
-        inputs2.insert_or_assign(h->second.to_string(Base16, false), i.second);
+        inputs2.insert_or_assign(h->second.to_string(Base::Base16, false), i.second);
     }
 
-    return hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
+    return hashString(HashType::SHA256, drv.unparse(store, maskOutputs, &inputs2));
 }
 
 
@@ -453,7 +453,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
 std::string hashPlaceholder(const std::string & outputName)
 {
     // FIXME: memoize?
-    return "/" + hashString(htSHA256, "nix-output:" + outputName).to_string(Base32, false);
+    return "/" + hashString(HashType::SHA256, "nix-output:" + outputName).to_string(Base::Base32, false);
 }
 
 
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 149c84765297b252c1f1d57b0de91d6e75fe9199..99ce291f4a39f29c8fb4b5e2db347f1c305295d7 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -85,7 +85,7 @@ struct CurlDownloader : public Downloader
             Callback<DownloadResult> && callback)
             : downloader(downloader)
             , request(request)
-            , act(*logger, lvlTalkative, actDownload,
+            , act(*logger, Verbosity::Talkative, ActivityType::Download,
                 fmt(request.data ? "uploading '%s'" : "downloading '%s'", request.uri),
                 {request.uri}, request.parentAct)
             , callback(std::move(callback))
@@ -164,7 +164,7 @@ struct CurlDownloader : public Downloader
         {
             size_t realSize = size * nmemb;
             std::string line((char *) contents, realSize);
-            printMsg(lvlVomit, format("got header for '%s': %s") % request.uri % trim(line));
+            printMsg(Verbosity::Vomit, format("got header for '%s': %s") % request.uri % trim(line));
             if (line.compare(0, 5, "HTTP/") == 0) { // new response starts
                 result.etag = "";
                 auto ss = tokenizeString<vector<string>>(line, " ");
@@ -247,7 +247,7 @@ struct CurlDownloader : public Downloader
 
             curl_easy_reset(req);
 
-            if (verbosity >= lvlVomit) {
+            if (verbosity >= Verbosity::Vomit) {
                 curl_easy_setopt(req, CURLOPT_VERBOSE, 1);
                 curl_easy_setopt(req, CURLOPT_DEBUGFUNCTION, DownloadItem::debugCallback);
             }
@@ -826,7 +826,7 @@ CachedDownloadResult Downloader::downloadCached(
     Path cacheDir = getCacheDir() + "/nix/tarballs";
     createDirs(cacheDir);
 
-    string urlHash = hashString(htSHA256, name + std::string("\0"s) + url).to_string(Base32, false);
+    string urlHash = hashString(HashType::SHA256, name + std::string("\0"s) + url).to_string(Base::Base32, false);
 
     Path dataFile = cacheDir + "/" + urlHash + ".info";
     Path fileLink = cacheDir + "/" + urlHash + "-file";
@@ -874,9 +874,9 @@ CachedDownloadResult Downloader::downloadCached(
             if (!res.cached) {
                 StringSink sink;
                 dumpString(*res.data, sink);
-                Hash hash = hashString(request.expectedHash ? request.expectedHash.type : htSHA256, *res.data);
+                Hash hash = hashString(request.expectedHash ? request.expectedHash.type : HashType::SHA256, *res.data);
                 ValidPathInfo info(store->makeFixedOutputPath(false, hash, name));
-                info.narHash = hashString(htSHA256, *sink.s);
+                info.narHash = hashString(HashType::SHA256, *sink.s);
                 info.narSize = sink.s->size();
                 info.ca = makeFixedOutputCA(false, hash);
                 store->addToStore(info, sink.s, NoRepair, NoCheckSigs);
@@ -914,7 +914,7 @@ CachedDownloadResult Downloader::downloadCached(
             if (members.size() != 1)
                 throw nix::Error("tarball '%s' contains an unexpected number of top-level files", url);
             auto topDir = tmpDir + "/" + members.begin()->name;
-            unpackedStorePath = store->addToStore(name, topDir, true, htSHA256, defaultPathFilter, NoRepair);
+            unpackedStorePath = store->addToStore(name, topDir, true, HashType::SHA256, defaultPathFilter, NoRepair);
         }
         replaceSymlink(store->printStorePath(*unpackedStorePath), unpackedLink);
         storePath = std::move(*unpackedStorePath);
diff --git a/src/libstore/export-import.cc b/src/libstore/export-import.cc
index 4692d1a7be304a8fce447fc370c32a0c47cdf6bc..8a5e9d08edc31fdc0cc654844e9acbe0cc2d9c70 100644
--- a/src/libstore/export-import.cc
+++ b/src/libstore/export-import.cc
@@ -10,7 +10,7 @@ struct HashAndWriteSink : Sink
 {
     Sink & writeSink;
     HashSink hashSink;
-    HashAndWriteSink(Sink & writeSink) : writeSink(writeSink), hashSink(htSHA256)
+    HashAndWriteSink(Sink & writeSink) : writeSink(writeSink), hashSink(HashType::SHA256)
     {
     }
     virtual void operator () (const unsigned char * data, size_t len)
@@ -33,7 +33,7 @@ void Store::exportPaths(const StorePathSet & paths, Sink & sink)
     //logger->incExpected(doneLabel, sorted.size());
 
     for (auto & path : sorted) {
-        //Activity act(*logger, lvlInfo, format("exporting path '%s'") % path);
+        //Activity act(*logger, Verbosity::Info, format("exporting path '%s'") % path);
         sink << 1;
         exportPath(path, sink);
         //logger->incProgress(doneLabel);
@@ -85,7 +85,7 @@ StorePaths Store::importPaths(Source & source, std::shared_ptr<FSAccessor> acces
 
         ValidPathInfo info(parseStorePath(readString(source)));
 
-        //Activity act(*logger, lvlInfo, format("importing path '%s'") % info.path);
+        //Activity act(*logger, Verbosity::Info, format("importing path '%s'") % info.path);
 
         info.references = readStorePaths<StorePathSet>(*this, source);
 
@@ -93,7 +93,7 @@ StorePaths Store::importPaths(Source & source, std::shared_ptr<FSAccessor> acces
         if (deriver != "")
             info.deriver = parseStorePath(deriver);
 
-        info.narHash = hashString(htSHA256, *tee.source.data);
+        info.narHash = hashString(HashType::SHA256, *tee.source.data);
         info.narSize = tee.source.data->size();
 
         // Ignore optional legacy signature.
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 0c3d896112c42cae468c2758408bcd8bd9586589..d201958f5a67e6fce5be77fd7b53e2eea0a1c5a8 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -78,7 +78,7 @@ void LocalStore::syncWithGC()
 
 void LocalStore::addIndirectRoot(const Path & path)
 {
-    string hash = hashString(htSHA1, path).to_string(Base32, false);
+    string hash = hashString(HashType::SHA1, path).to_string(Base::Base32, false);
     Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
         % stateDir % gcRootsDir % hash).str());
     makeSymlink(realRoot, path);
@@ -632,7 +632,7 @@ void LocalStore::tryToDelete(GCState & state, const Path & path)
     auto realPath = realStoreDir + "/" + std::string(baseNameOf(path));
     if (realPath == linksDir || realPath == trashDir) return;
 
-    //Activity act(*logger, lvlDebug, format("considering whether to delete '%1%'") % path);
+    //Activity act(*logger, Verbosity::Debug, format("considering whether to delete '%1%'") % path);
 
     auto storePath = maybeParseStorePath(path);
 
@@ -697,7 +697,7 @@ void LocalStore::removeUnusedLinks(const GCState & state)
             continue;
         }
 
-        printMsg(lvlTalkative, format("deleting unused link '%1%'") % path);
+        printMsg(Verbosity::Talkative, format("deleting unused link '%1%'") % path);
 
         if (unlink(path.c_str()) == -1)
             throw SysError(format("deleting '%1%'") % path);
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index 458266be00f1f8ab21e58fb51a424d4cd9e3e1b6..f123efe0138cc92ceb8e567ae72e1151e31a027e 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -139,7 +139,7 @@ struct LegacySSHStore : public Store
                 << cmdAddToStoreNar
                 << printStorePath(info.path)
                 << (info.deriver ? printStorePath(*info.deriver) : "")
-                << info.narHash.to_string(Base16, false);
+                << info.narHash.to_string(Base::Base16, false);
             writeStorePaths(*this, conn->to, info.references);
             conn->to
                 << info.registrationTime
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index ae7513ad8154bf86eaa3fd35cc9221a7a05a9045..f293ecb4a8417fdaee379fa8979b052795574fcd 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -580,7 +580,7 @@ uint64_t LocalStore::addValidPath(State & state,
 
     state.stmtRegisterValidPath.use()
         (printStorePath(info.path))
-        (info.narHash.to_string(Base16))
+        (info.narHash.to_string(Base::Base16))
         (info.registrationTime == 0 ? time(0) : info.registrationTime)
         (info.deriver ? printStorePath(*info.deriver) : "", (bool) info.deriver)
         (info.narSize, info.narSize != 0)
@@ -680,7 +680,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
 {
     state.stmtUpdatePathInfo.use()
         (info.narSize, info.narSize != 0)
-        (info.narHash.to_string(Base16))
+        (info.narHash.to_string(Base::Base16))
         (info.ultimate ? 1 : 0, info.ultimate)
         (concatStringsSep(" ", info.sigs), !info.sigs.empty())
         (info.ca, !info.ca.empty())
@@ -908,7 +908,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
         StorePathSet paths;
 
         for (auto & i : infos) {
-            assert(i.narHash.type == htSHA256);
+            assert(i.narHash.type == HashType::SHA256);
             if (isValidPath_(*state, i.path))
                 updatePathInfo(*state, i);
             else
@@ -1006,9 +1006,9 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
                of the NAR. */
             std::unique_ptr<AbstractHashSink> hashSink;
             if (info.ca == "" || !info.references.count(info.path))
-                hashSink = std::make_unique<HashSink>(htSHA256);
+                hashSink = std::make_unique<HashSink>(HashType::SHA256);
             else
-                hashSink = std::make_unique<HashModuloSink>(htSHA256, storePathToHash(printStorePath(info.path)));
+                hashSink = std::make_unique<HashModuloSink>(HashType::SHA256, storePathToHash(printStorePath(info.path)));
 
             LambdaSource wrapperSource([&](unsigned char * data, size_t len) -> size_t {
                 size_t n = source.read(data, len);
@@ -1081,10 +1081,10 @@ StorePath LocalStore::addToStoreFromDump(const string & dump, const string & nam
                sha256); otherwise, compute it here. */
             HashResult hash;
             if (recursive) {
-                hash.first = hashAlgo == htSHA256 ? h : hashString(htSHA256, dump);
+                hash.first = hashAlgo == HashType::SHA256 ? h : hashString(HashType::SHA256, dump);
                 hash.second = dump.size();
             } else
-                hash = hashPath(htSHA256, realPath);
+                hash = hashPath(HashType::SHA256, realPath);
 
             optimisePath(realPath); // FIXME: combine with hashPath()
 
@@ -1123,7 +1123,7 @@ StorePath LocalStore::addToStore(const string & name, const Path & _srcPath,
 StorePath LocalStore::addTextToStore(const string & name, const string & s,
     const StorePathSet & references, RepairFlag repair)
 {
-    auto hash = hashString(htSHA256, s);
+    auto hash = hashString(HashType::SHA256, s);
     auto dstPath = makeTextPath(name, hash, references);
 
     addTempRoot(dstPath);
@@ -1147,7 +1147,7 @@ StorePath LocalStore::addTextToStore(const string & name, const string & s,
 
             StringSink sink;
             dumpString(s, sink);
-            auto narHash = hashString(htSHA256, *sink.s);
+            auto narHash = hashString(HashType::SHA256, *sink.s);
 
             optimisePath(realPath);
 
@@ -1233,9 +1233,9 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
         printInfo("checking link hashes...");
 
         for (auto & link : readDirectory(linksDir)) {
-            printMsg(lvlTalkative, "checking contents of '%s'", link.name);
+            printMsg(Verbosity::Talkative, "checking contents of '%s'", link.name);
             Path linkPath = linksDir + "/" + link.name;
-            string hash = hashPath(htSHA256, linkPath).first.to_string(Base32, false);
+            string hash = hashPath(HashType::SHA256, linkPath).first.to_string(Base::Base32, false);
             if (hash != link.name) {
                 printError(
                     "link '%s' was modified! expected hash '%s', got '%s'",
@@ -1253,14 +1253,14 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
 
         printInfo("checking store hashes...");
 
-        Hash nullHash(htSHA256);
+        Hash nullHash(HashType::SHA256);
 
         for (auto & i : validPaths) {
             try {
                 auto info = std::const_pointer_cast<ValidPathInfo>(std::shared_ptr<const ValidPathInfo>(queryPathInfo(i)));
 
                 /* Check the content hash (optionally - slow). */
-                printMsg(lvlTalkative, "checking contents of '%s'", printStorePath(i));
+                printMsg(Verbosity::Talkative, "checking contents of '%s'", printStorePath(i));
 
                 std::unique_ptr<AbstractHashSink> hashSink;
                 if (info->ca == "" || !info->references.count(info->path))
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 16aeab0ad9e8e50e07082814d1efd0cee36f51db..76bacd32661408618d5697140ddf34a3536ef060 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -157,7 +157,7 @@ public:
        true) or simply the contents of a regular file (if recursive ==
        false). */
     StorePath addToStoreFromDump(const string & dump, const string & name,
-        bool recursive = true, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair) override;
+        bool recursive = true, HashType hashAlgo = HashType::SHA256, RepairFlag repair = NoRepair) override;
 
     StorePath addTextToStore(const string & name, const string & s,
         const StorePathSet & references, RepairFlag repair) override;
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index 9c47fe524542a148ebebcabf2cb49d9eea76e88d..fb538a1c52db8faec1b642355204ffff9a0940ec 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -112,7 +112,7 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
     StorePathSet & willBuild_, StorePathSet & willSubstitute_, StorePathSet & unknown_,
     unsigned long long & downloadSize_, unsigned long long & narSize_)
 {
-    Activity act(*logger, lvlDebug, actUnknown, "querying info about missing paths");
+    Activity act(*logger, Verbosity::Debug, ActivityType::Unknown, "querying info about missing paths");
 
     downloadSize_ = narSize_ = 0;
 
diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc
index 1375094b5bf14daee336d23e10779eaa269cf157..a35886302f38b20c532b13bd7c47d2ae4cf4336d 100644
--- a/src/libstore/nar-info.cc
+++ b/src/libstore/nar-info.cc
@@ -86,11 +86,11 @@ std::string NarInfo::to_string(const Store & store) const
     res += "URL: " + url + "\n";
     assert(compression != "");
     res += "Compression: " + compression + "\n";
-    assert(fileHash.type == htSHA256);
-    res += "FileHash: " + fileHash.to_string(Base32) + "\n";
+    assert(fileHash.type == HashType::SHA256);
+    res += "FileHash: " + fileHash.to_string(Base::Base32) + "\n";
     res += "FileSize: " + std::to_string(fileSize) + "\n";
-    assert(narHash.type == htSHA256);
-    res += "NarHash: " + narHash.to_string(Base32) + "\n";
+    assert(narHash.type == HashType::SHA256);
+    res += "NarHash: " + narHash.to_string(Base::Base32) + "\n";
     res += "NarSize: " + std::to_string(narSize) + "\n";
 
     res += "References: " + concatStringsSep(" ", shortRefs()) + "\n";
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index 8ac382e9dbf3c2b644b77f1acfd5fa3f8a71375b..5c01e1b3b28e6d9b814b54669b166b09093ac2a6 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -57,7 +57,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash()
     }
     if (errno) throw SysError(format("reading directory '%1%'") % linksDir);
 
-    printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());
+    printMsg(Verbosity::Talkative, format("loaded %1% hash inodes") % inodeHash.size());
 
     return inodeHash;
 }
@@ -149,11 +149,11 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
        Also note that if `path' is a symlink, then we're hashing the
        contents of the symlink (i.e. the result of readlink()), not
        the contents of the target (which may not even exist). */
-    Hash hash = hashPath(htSHA256, path).first;
+    Hash hash = hashPath(HashType::SHA256, path).first;
     debug(format("'%1%' has hash '%2%'") % path % hash.to_string());
 
     /* Check if this is a known hash. */
-    Path linkPath = linksDir + "/" + hash.to_string(Base32, false);
+    Path linkPath = linksDir + "/" + hash.to_string(Base::Base32, false);
 
  retry:
     if (!pathExists(linkPath)) {
@@ -199,7 +199,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
         goto retry;
     }
 
-    printMsg(lvlTalkative, format("linking '%1%' to '%2%'") % path % linkPath);
+    printMsg(Verbosity::Talkative, format("linking '%1%' to '%2%'") % path % linkPath);
 
     /* Make the containing directory writable, but only if it's not
        the store itself (we don't want or need to mess with its
@@ -246,13 +246,13 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
     stats.blocksFreed += st.st_blocks;
 
     if (act)
-        act->result(resFileLinked, st.st_size, st.st_blocks);
+        act->result(ResultType::FileLinked, st.st_size, st.st_blocks);
 }
 
 
 void LocalStore::optimiseStore(OptimiseStats & stats)
 {
-    Activity act(*logger, actOptimiseStore);
+    Activity act(*logger, ActivityType::OptimiseStore);
 
     auto paths = queryAllValidPaths();
     InodeHash inodeHash = loadInodeHash();
@@ -265,7 +265,7 @@ void LocalStore::optimiseStore(OptimiseStats & stats)
         addTempRoot(i);
         if (!isValidPath(i)) continue; /* path was GC'ed, probably */
         {
-            Activity act(*logger, lvlTalkative, actUnknown, fmt("optimising path '%s'", printStorePath(i)));
+            Activity act(*logger, Verbosity::Talkative, ActivityType::Unknown, fmt("optimising path '%s'", printStorePath(i)));
             optimisePath_(&act, stats, realStoreDir + "/" + std::string(i.to_string()), inodeHash);
         }
         done++;
diff --git a/src/libstore/references.cc b/src/libstore/references.cc
index 102e1592180a8e51db975bdfbb13f1652399aeab..6652e1e26ca3b1bdf01221d772ba52c18ced3c7e 100644
--- a/src/libstore/references.cc
+++ b/src/libstore/references.cc
@@ -54,7 +54,7 @@ struct RefScanSink : Sink
 
     string tail;
 
-    RefScanSink() : hashSink(htSHA256) { }
+    RefScanSink() : hashSink(HashType::SHA256) { }
 
     void operator () (const unsigned char * data, size_t len);
 };
@@ -96,7 +96,7 @@ PathSet scanForReferences(const string & path,
         string s = string(baseName, 0, pos);
         assert(s.size() == refLength);
         assert(backMap.find(s) == backMap.end());
-        // parseHash(htSHA256, s);
+        // parseHash(HashType::SHA256, s);
         sink.hashes.insert(s);
         backMap[s] = i;
     }
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 8c55da2686e595ebf06e93452a06311572deabbc..3fc7ddfc052615d0c9f9ca03359c229cc3062ecc 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -177,11 +177,11 @@ void RemoteStore::setOptions(Connection & conn)
        << settings.keepFailed
        << settings.keepGoing
        << settings.tryFallback
-       << verbosity
+       << (uint64_t) verbosity
        << settings.maxBuildJobs
        << settings.maxSilentTime
        << true
-       << (settings.verboseBuild ? lvlError : lvlVomit)
+       << (uint64_t) (settings.verboseBuild ? Verbosity::Error : Verbosity::Vomit)
        << 0 // obsolete log type
        << 0 /* obsolete print build trace */
        << settings.buildCores
@@ -375,7 +375,7 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path,
             info = std::make_shared<ValidPathInfo>(path.clone());
             auto deriver = readString(conn->from);
             if (deriver != "") info->deriver = parseStorePath(deriver);
-            info->narHash = Hash(readString(conn->from), htSHA256);
+            info->narHash = Hash(readString(conn->from), HashType::SHA256);
             info->references = readStorePaths<StorePathSet>(*this, conn->from);
             conn->from >> info->registrationTime >> info->narSize;
             if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) {
@@ -471,7 +471,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
         conn->to << wopAddToStoreNar
                  << printStorePath(info.path)
                  << (info.deriver ? printStorePath(*info.deriver) : "")
-                 << info.narHash.to_string(Base16, false);
+                 << info.narHash.to_string(Base::Base16, false);
         writeStorePaths(*this, conn->to, info.references);
         conn->to << info.registrationTime << info.narSize
                  << info.ultimate << info.sigs << info.ca
@@ -493,7 +493,7 @@ StorePath RemoteStore::addToStore(const string & name, const Path & _srcPath,
     Path srcPath(absPath(_srcPath));
 
     conn->to << wopAddToStore << name
-       << ((hashAlgo == htSHA256 && recursive) ? 0 : 1) /* backwards compatibility hack */
+       << ((hashAlgo == HashType::SHA256 && recursive) ? 0 : 1) /* backwards compatibility hack */
        << (recursive ? 1 : 0)
        << printHashType(hashAlgo);
 
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index f301a97d8c426528511e7a22eee1103e993cf904..62cff8e3f9fe24d0a6d9eb0000c9b9009638dcc6 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -65,7 +65,7 @@ public:
         std::shared_ptr<FSAccessor> accessor) override;
 
     StorePath addToStore(const string & name, const Path & srcPath,
-        bool recursive = true, HashType hashAlgo = htSHA256,
+        bool recursive = true, HashType hashAlgo = HashType::SHA256,
         PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) override;
 
     StorePath addTextToStore(const string & name, const string & s,
diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc
index f2e4b63e0593a1e7fef6aa9f946b042e1366ab7f..fb56ee62e3099572ca5d6736999ede86114f535c 100644
--- a/src/libstore/s3-binary-cache-store.cc
+++ b/src/libstore/s3-binary-cache-store.cc
@@ -68,9 +68,9 @@ static void initAWS()
            shared.cc), so don't let aws-sdk-cpp override it. */
         options.cryptoOptions.initAndCleanupOpenSSL = false;
 
-        if (verbosity >= lvlDebug) {
+        if (verbosity >= Verbosity::Debug) {
             options.loggingOptions.logLevel =
-                verbosity == lvlDebug
+                verbosity == Verbosity::Debug
                 ? Aws::Utils::Logging::LogLevel::Debug
                 : Aws::Utils::Logging::LogLevel::Trace;
             options.loggingOptions.logger_create_fn = [options]() {
diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc
index 84548a6e4ebfdbdda8de8af1882b76fb62479e92..f61c094a2201aeb4fe96cdb673ce83970ed0c2e5 100644
--- a/src/libstore/ssh.cc
+++ b/src/libstore/ssh.cc
@@ -58,7 +58,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
             addCommonSSHOpts(args);
             if (socketPath != "")
                 args.insert(args.end(), {"-S", socketPath});
-            if (verbosity >= lvlChatty)
+            if (verbosity >= Verbosity::Chatty)
                 args.push_back("-v");
         }
 
@@ -110,7 +110,7 @@ Path SSHMaster::startMaster()
             , "-o", "LocalCommand=echo started"
             , "-o", "PermitLocalCommand=yes"
             };
-        if (verbosity >= lvlChatty)
+        if (verbosity >= Verbosity::Chatty)
             args.push_back("-v");
         addCommonSSHOpts(args);
         execvp(args.begin()->c_str(), stringsToCharPtrs(args).data());
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index b9e894a9a3f1b82cf42793b0a026af53ae2b96e4..98475fd4ce1f6700d10ad32852fa0d6a6ca0e329 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -141,8 +141,8 @@ StorePath Store::makeStorePath(const string & type,
     const Hash & hash, std::string_view name) const
 {
     /* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */
-    string s = type + ":" + hash.to_string(Base16) + ":" + storeDir + ":" + std::string(name);
-    auto h = compressHash(hashString(htSHA256, s), 20);
+    string s = type + ":" + hash.to_string(Base::Base16) + ":" + storeDir + ":" + std::string(name);
+    auto h = compressHash(hashString(HashType::SHA256, s), 20);
     return StorePath::make(h.hash, name);
 }
 
@@ -177,13 +177,13 @@ StorePath Store::makeFixedOutputPath(
     const StorePathSet & references,
     bool hasSelfReference) const
 {
-    if (hash.type == htSHA256 && recursive) {
+    if (hash.type == HashType::SHA256 && recursive) {
         return makeStorePath(makeType(*this, "source", references, hasSelfReference), hash, name);
     } else {
         assert(references.empty());
-        return makeStorePath("output:out", hashString(htSHA256,
+        return makeStorePath("output:out", hashString(HashType::SHA256,
                 "fixed:out:" + (recursive ? (string) "r:" : "") +
-                hash.to_string(Base16) + ":"), name);
+                hash.to_string(Base::Base16) + ":"), name);
     }
 }
 
@@ -191,7 +191,7 @@ StorePath Store::makeFixedOutputPath(
 StorePath Store::makeTextPath(std::string_view name, const Hash & hash,
     const StorePathSet & references) const
 {
-    assert(hash.type == htSHA256);
+    assert(hash.type == HashType::SHA256);
     /* Stuff the references (if any) into the type.  This is a bit
        hacky, but we can't put them in `s' since that would be
        ambiguous. */
@@ -210,7 +210,7 @@ std::pair<StorePath, Hash> Store::computeStorePathForPath(std::string_view name,
 StorePath Store::computeStorePathForText(const string & name, const string & s,
     const StorePathSet & references) const
 {
-    return makeTextPath(name, hashString(htSHA256, s), references);
+    return makeTextPath(name, hashString(HashType::SHA256, s), references);
 }
 
 
@@ -423,7 +423,7 @@ string Store::makeValidityRegistration(const StorePathSet & paths,
         auto info = queryPathInfo(i);
 
         if (showHash) {
-            s += info->narHash.to_string(Base16, false) + "\n";
+            s += info->narHash.to_string(Base::Base16, false) + "\n";
             s += (format("%1%\n") % info->narSize).str();
         }
 
@@ -561,7 +561,7 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
     auto srcUri = srcStore->getUri();
     auto dstUri = dstStore->getUri();
 
-    Activity act(*logger, lvlInfo, actCopyPath,
+    Activity act(*logger, Verbosity::Info, ActivityType::CopyPath,
         srcUri == "local" || srcUri == "daemon"
         ? fmt("copying path '%s' to '%s'", srcStore->printStorePath(storePath), dstUri)
           : dstUri == "local" || dstUri == "daemon"
@@ -578,7 +578,7 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
         StringSink sink;
         srcStore->narFromPath({storePath}, sink);
         auto info2 = make_ref<ValidPathInfo>(*info);
-        info2->narHash = hashString(htSHA256, *sink.s);
+        info2->narHash = hashString(HashType::SHA256, *sink.s);
         if (!info->narSize) info2->narSize = sink.s->size();
         if (info->ultimate) info2->ultimate = false;
         info = info2;
@@ -620,7 +620,7 @@ void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & st
 
     if (missing.empty()) return;
 
-    Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
+    Activity act(*logger, Verbosity::Info, ActivityType::CopyPaths, fmt("copying %d paths", missing.size()));
 
     std::atomic<size_t> nrDone{0};
     std::atomic<size_t> nrFailed{0};
@@ -646,7 +646,7 @@ void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & st
             auto info = srcStore->queryPathInfo(srcStore->parseStorePath(storePath));
 
             bytesExpected += info->narSize;
-            act.setExpected(actCopyPath, bytesExpected);
+            act.setExpected(ActivityType::CopyPath, bytesExpected);
 
             return srcStore->printStorePathSet(info->references);
         },
@@ -665,7 +665,7 @@ void copyPaths(ref<Store> srcStore, ref<Store> dstStore, const StorePathSet & st
                     nrFailed++;
                     if (!settings.keepGoing)
                         throw e;
-                    logger->log(lvlError, fmt("could not copy %s: %s", storePathS, e.what()));
+                    logger->log(Verbosity::Error, fmt("could not copy %s: %s", storePathS, e.what()));
                     showProgress();
                     return;
                 }
@@ -711,7 +711,7 @@ std::optional<ValidPathInfo> decodeValidPathInfo(const Store & store, std::istre
     if (hashGiven) {
         string s;
         getline(str, s);
-        info.narHash = Hash(s, htSHA256);
+        info.narHash = Hash(s, HashType::SHA256);
         getline(str, s);
         if (!string2Int(s, info.narSize)) throw Error("number expected");
     }
@@ -754,7 +754,7 @@ std::string ValidPathInfo::fingerprint(const Store & store) const
             store.printStorePath(path));
     return
         "1;" + store.printStorePath(path) + ";"
-        + narHash.to_string(Base32) + ";"
+        + narHash.to_string(Base::Base32) + ";"
         + std::to_string(narSize) + ";"
         + concatStringsSep(",", store.printStorePathSet(references));
 }
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 0fa59be6a61e362caa73cb204dd103b0424655f8..4247367b5468697d010561a9c4762a2a8ffaf11e 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -359,7 +359,7 @@ public:
        path and the cryptographic hash of the contents of srcPath. */
     std::pair<StorePath, Hash> computeStorePathForPath(std::string_view name,
         const Path & srcPath, bool recursive = true,
-        HashType hashAlgo = htSHA256, PathFilter & filter = defaultPathFilter) const;
+        HashType hashAlgo = HashType::SHA256, PathFilter & filter = defaultPathFilter) const;
 
     /* Preparatory part of addTextToStore().
 
@@ -462,12 +462,12 @@ public:
        The function object `filter' can be used to exclude files (see
        libutil/archive.hh). */
     virtual StorePath addToStore(const string & name, const Path & srcPath,
-        bool recursive = true, HashType hashAlgo = htSHA256,
+        bool recursive = true, HashType hashAlgo = HashType::SHA256,
         PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair) = 0;
 
     // FIXME: remove?
     virtual StorePath addToStoreFromDump(const string & dump, const string & name,
-        bool recursive = true, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair)
+        bool recursive = true, HashType hashAlgo = HashType::SHA256, RepairFlag repair = NoRepair)
     {
         throw Error("addToStoreFromDump() is not supported by this store");
     }
@@ -561,7 +561,7 @@ public:
        each path is included. */
     void pathInfoToJSON(JSONPlaceholder & jsonOut, const StorePathSet & storePaths,
         bool includeImpureInfo, bool showClosureSize,
-        Base hashBase = Base32,
+        Base hashBase = Base::Base32,
         AllowInvalidFlag allowInvalid = DisallowInvalid);
 
     /* Return the size of the closure of the specified path, that is,
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index ba15ea5710994762f2884b09ec65f048ca488837..8fd437f26bc9684675ad7eeea517ccd8a8dd5ff0 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -164,7 +164,7 @@ Args::FlagMaker & Args::FlagMaker::mkHashTypeFlag(HashType * ht)
     description("hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')");
     handler([ht](std::string s) {
         *ht = parseHashType(s);
-        if (*ht == htUnknown)
+        if (*ht == HashType::Unknown)
             throw UsageError("unknown hash type '%1%'", s);
     });
     return *this;
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index 967efbe1c7e297540ece8fada9cf8957169d4fec..afa4936630d08e1ee5d979938eb9ac7f10fd9a8c 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -10,7 +10,7 @@ namespace nix {
 
 MakeError(UsageError, Error);
 
-enum HashType : char;
+enum struct HashType : char;
 
 class Args
 {
diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc
index 860b04adb79496cd2bebc10875c02e975322146d..75e889f41d3ca102235444a6b5a7967669384614 100644
--- a/src/libutil/compression.cc
+++ b/src/libutil/compression.cc
@@ -314,7 +314,7 @@ struct XzCompressionSink : CompressionSink
             ret = lzma_stream_encoder_mt(&strm, &mt_options);
             done = true;
 #else
-            printMsg(lvlError, "warning: parallel XZ compression requested but not supported, falling back to single-threaded compression");
+            printMsg(Verbosity::Error, "warning: parallel XZ compression requested but not supported, falling back to single-threaded compression");
 #endif
         }
 
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index 7caee1da7f8b5a82f2bda6d4572457897fded643..5e6edeec30445ca63566f37b87a6d6e978afb2d6 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -18,10 +18,10 @@ namespace nix {
 
 void Hash::init()
 {
-    if (type == htMD5) hashSize = md5HashSize;
-    else if (type == htSHA1) hashSize = sha1HashSize;
-    else if (type == htSHA256) hashSize = sha256HashSize;
-    else if (type == htSHA512) hashSize = sha512HashSize;
+    if (type == HashType::MD5) hashSize = md5HashSize;
+    else if (type == HashType::SHA1) hashSize = sha1HashSize;
+    else if (type == HashType::SHA256) hashSize = sha256HashSize;
+    else if (type == HashType::SHA512) hashSize = sha512HashSize;
     else abort();
     assert(hashSize <= maxHashSize);
     memset(hash, 0, maxHashSize);
@@ -98,26 +98,26 @@ static string printHash32(const Hash & hash)
 
 string printHash16or32(const Hash & hash)
 {
-    return hash.to_string(hash.type == htMD5 ? Base16 : Base32, false);
+    return hash.to_string(hash.type == HashType::MD5 ? Base::Base16 : Base::Base32, false);
 }
 
 
 std::string Hash::to_string(Base base, bool includeType) const
 {
     std::string s;
-    if (base == SRI || includeType) {
+    if (base == Base::SRI || includeType) {
         s += printHashType(type);
-        s += base == SRI ? '-' : ':';
+        s += base == Base::SRI ? '-' : ':';
     }
     switch (base) {
-    case Base16:
+    case Base::Base16:
         s += printHash16(*this);
         break;
-    case Base32:
+    case Base::Base32:
         s += printHash32(*this);
         break;
-    case Base64:
-    case SRI:
+    case Base::Base64:
+    case Base::SRI:
         s += base64Encode(std::string((const char *) hash, hashSize));
         break;
     }
@@ -136,16 +136,16 @@ Hash::Hash(const std::string & s, HashType type)
         sep = s.find('-');
         if (sep != string::npos) {
             isSRI = true;
-        } else if (type == htUnknown)
+        } else if (type == HashType::Unknown)
             throw BadHash("hash '%s' does not include a type", s);
     }
 
     if (sep != string::npos) {
         string hts = string(s, 0, sep);
         this->type = parseHashType(hts);
-        if (this->type == htUnknown)
+        if (this->type == HashType::Unknown)
             throw BadHash("unknown hash type '%s'", hts);
-        if (type != htUnknown && type != this->type)
+        if (type != HashType::Unknown && type != this->type)
             throw BadHash("hash '%s' should have type '%s'", s, printHashType(type));
         pos = sep + 1;
     }
@@ -217,29 +217,29 @@ union Ctx
 
 static void start(HashType ht, Ctx & ctx)
 {
-    if (ht == htMD5) MD5_Init(&ctx.md5);
-    else if (ht == htSHA1) SHA1_Init(&ctx.sha1);
-    else if (ht == htSHA256) SHA256_Init(&ctx.sha256);
-    else if (ht == htSHA512) SHA512_Init(&ctx.sha512);
+    if (ht == HashType::MD5) MD5_Init(&ctx.md5);
+    else if (ht == HashType::SHA1) SHA1_Init(&ctx.sha1);
+    else if (ht == HashType::SHA256) SHA256_Init(&ctx.sha256);
+    else if (ht == HashType::SHA512) SHA512_Init(&ctx.sha512);
 }
 
 
 static void update(HashType ht, Ctx & ctx,
     const unsigned char * bytes, size_t len)
 {
-    if (ht == htMD5) MD5_Update(&ctx.md5, bytes, len);
-    else if (ht == htSHA1) SHA1_Update(&ctx.sha1, bytes, len);
-    else if (ht == htSHA256) SHA256_Update(&ctx.sha256, bytes, len);
-    else if (ht == htSHA512) SHA512_Update(&ctx.sha512, bytes, len);
+    if (ht == HashType::MD5) MD5_Update(&ctx.md5, bytes, len);
+    else if (ht == HashType::SHA1) SHA1_Update(&ctx.sha1, bytes, len);
+    else if (ht == HashType::SHA256) SHA256_Update(&ctx.sha256, bytes, len);
+    else if (ht == HashType::SHA512) SHA512_Update(&ctx.sha512, bytes, len);
 }
 
 
 static void finish(HashType ht, Ctx & ctx, unsigned char * hash)
 {
-    if (ht == htMD5) MD5_Final(hash, &ctx.md5);
-    else if (ht == htSHA1) SHA1_Final(hash, &ctx.sha1);
-    else if (ht == htSHA256) SHA256_Final(hash, &ctx.sha256);
-    else if (ht == htSHA512) SHA512_Final(hash, &ctx.sha512);
+    if (ht == HashType::MD5) MD5_Final(hash, &ctx.md5);
+    else if (ht == HashType::SHA1) SHA1_Final(hash, &ctx.sha1);
+    else if (ht == HashType::SHA256) SHA256_Final(hash, &ctx.sha256);
+    else if (ht == HashType::SHA512) SHA512_Final(hash, &ctx.sha512);
 }
 
 
@@ -320,20 +320,20 @@ Hash compressHash(const Hash & hash, unsigned int newSize)
 
 HashType parseHashType(const string & s)
 {
-    if (s == "md5") return htMD5;
-    else if (s == "sha1") return htSHA1;
-    else if (s == "sha256") return htSHA256;
-    else if (s == "sha512") return htSHA512;
-    else return htUnknown;
+    if (s == "md5") return HashType::MD5;
+    else if (s == "sha1") return HashType::SHA1;
+    else if (s == "sha256") return HashType::SHA256;
+    else if (s == "sha512") return HashType::SHA512;
+    else return HashType::Unknown;
 }
 
 
 string printHashType(HashType ht)
 {
-    if (ht == htMD5) return "md5";
-    else if (ht == htSHA1) return "sha1";
-    else if (ht == htSHA256) return "sha256";
-    else if (ht == htSHA512) return "sha512";
+    if (ht == HashType::MD5) return "md5";
+    else if (ht == HashType::SHA1) return "sha1";
+    else if (ht == HashType::SHA256) return "sha256";
+    else if (ht == HashType::SHA512) return "sha512";
     else abort();
 }
 
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index ea9fca3e74f2e0565d7a580928bf210be2609a21..0fe6e767713fead03e09cd15fe3b46f0cdaedc55 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -10,7 +10,13 @@ namespace nix {
 MakeError(BadHash, Error);
 
 
-enum HashType : char { htUnknown, htMD5, htSHA1, htSHA256, htSHA512 };
+enum struct HashType : char {
+    Unknown,
+    MD5,
+    SHA1,
+    SHA256,
+    SHA512,
+};
 
 
 const int md5HashSize = 16;
@@ -20,7 +26,12 @@ const int sha512HashSize = 64;
 
 extern const string base32Chars;
 
-enum Base : int { Base64, Base32, Base16, SRI };
+enum struct Base : int {
+    Base64,
+    Base32,
+    Base16,
+    SRI,
+};
 
 
 struct Hash
@@ -29,7 +40,7 @@ struct Hash
     unsigned int hashSize = 0;
     unsigned char hash[maxHashSize] = {};
 
-    HashType type = htUnknown;
+    HashType type = HashType::Unknown;
 
     /* Create an unset hash object. */
     Hash() { };
@@ -40,14 +51,14 @@ struct Hash
     /* Initialize the hash from a string representation, in the format
        "[<type>:]<base16|base32|base64>" or "<type>-<base64>" (a
        Subresource Integrity hash expression). If the 'type' argument
-       is htUnknown, then the hash type must be specified in the
+       is HashType::Unknown, then the hash type must be specified in the
        string. */
-    Hash(const std::string & s, HashType type = htUnknown);
+    Hash(const std::string & s, HashType type = HashType::Unknown);
 
     void init();
 
     /* Check whether a hash is set. */
-    operator bool () const { return type != htUnknown; }
+    operator bool () const { return type != HashType::Unknown; }
 
     /* Check whether two hash are equal. */
     bool operator == (const Hash & h2) const;
@@ -79,18 +90,18 @@ struct Hash
     /* Return a string representation of the hash, in base-16, base-32
        or base-64. By default, this is prefixed by the hash type
        (e.g. "sha256:"). */
-    std::string to_string(Base base = Base32, bool includeType = true) const;
+    std::string to_string(Base base = Base::Base32, bool includeType = true) const;
 
     std::string gitRev() const
     {
-        assert(type == htSHA1);
-        return to_string(Base16, false);
+        assert(type == HashType::SHA1);
+        return to_string(Base::Base16, false);
     }
 
     std::string gitShortRev() const
     {
-        assert(type == htSHA1);
-        return std::string(to_string(Base16, false), 0, 7);
+        assert(type == HashType::SHA1);
+        return std::string(to_string(Base::Base16, false), 0, 7);
     }
 };
 
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index fa5c84a27333847659b0a56874b3cf0788a9aae0..54c73a9136ccd816f9c9ab7b732928559a6e4516 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -21,7 +21,7 @@ Logger * logger = makeDefaultLogger();
 
 void Logger::warn(const std::string & msg)
 {
-    log(lvlWarn, ANSI_YELLOW "warning:" ANSI_NORMAL " " + msg);
+    log(Verbosity::Warn, ANSI_YELLOW "warning:" ANSI_NORMAL " " + msg);
 }
 
 class SimpleLogger : public Logger
@@ -45,10 +45,10 @@ public:
         if (systemd) {
             char c;
             switch (lvl) {
-            case lvlError: c = '3'; break;
-            case lvlWarn: c = '4'; break;
-            case lvlInfo: c = '5'; break;
-            case lvlTalkative: case lvlChatty: c = '6'; break;
+            case Verbosity::Error: c = '3'; break;
+            case Verbosity::Warn: c = '4'; break;
+            case Verbosity::Info: c = '5'; break;
+            case Verbosity::Talkative: case Verbosity::Chatty: c = '6'; break;
             default: c = '7';
             }
             prefix = std::string("<") + c + ">";
@@ -66,7 +66,7 @@ public:
     }
 };
 
-Verbosity verbosity = lvlInfo;
+Verbosity verbosity = Verbosity::Info;
 
 void warnOnce(bool & haveWarned, const FormatOrString & fs)
 {
@@ -123,7 +123,7 @@ struct JSONLogger : Logger
 
     void write(const nlohmann::json & json)
     {
-        prevLogger.log(lvlError, "@nix " + json.dump());
+        prevLogger.log(Verbosity::Error, "@nix " + json.dump());
     }
 
     void log(Verbosity lvl, const FormatOrString & fs) override
@@ -198,7 +198,7 @@ bool handleJSONLogMessage(const std::string & msg,
 
         if (action == "start") {
             auto type = (ActivityType) json["type"];
-            if (trusted || type == actDownload)
+            if (trusted || type == ActivityType::Download)
                 activities.emplace(std::piecewise_construct,
                     std::forward_as_tuple(json["id"]),
                     std::forward_as_tuple(*logger, (Verbosity) json["level"], type,
@@ -216,7 +216,7 @@ bool handleJSONLogMessage(const std::string & msg,
 
         else if (action == "setPhase") {
             std::string phase = json["phase"];
-            act.result(resSetPhase, phase);
+            act.result(ResultType::SetPhase, phase);
         }
 
         else if (action == "msg") {
diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh
index beb5e6b64e6df48ac1639b305978fdbe62782c4f..cde5253581cab847b91e8f78e4fc75c5647979ed 100644
--- a/src/libutil/logging.hh
+++ b/src/libutil/logging.hh
@@ -4,41 +4,41 @@
 
 namespace nix {
 
-typedef enum {
-    lvlError = 0,
-    lvlWarn,
-    lvlInfo,
-    lvlTalkative,
-    lvlChatty,
-    lvlDebug,
-    lvlVomit
-} Verbosity;
-
-typedef enum {
-    actUnknown = 0,
-    actCopyPath = 100,
-    actDownload = 101,
-    actRealise = 102,
-    actCopyPaths = 103,
-    actBuilds = 104,
-    actBuild = 105,
-    actOptimiseStore = 106,
-    actVerifyPaths = 107,
-    actSubstitute = 108,
-    actQueryPathInfo = 109,
-    actPostBuildHook = 110,
-} ActivityType;
-
-typedef enum {
-    resFileLinked = 100,
-    resBuildLogLine = 101,
-    resUntrustedPath = 102,
-    resCorruptedPath = 103,
-    resSetPhase = 104,
-    resProgress = 105,
-    resSetExpected = 106,
-    resPostBuildLogLine = 107,
-} ResultType;
+enum struct Verbosity : uint64_t {
+    Error = 0,
+    Warn,
+    Info,
+    Talkative,
+    Chatty,
+    Debug,
+    Vomit,
+};
+
+enum struct ActivityType : uint64_t {
+    Unknown = 0,
+    CopyPath = 100,
+    Download = 101,
+    Realise = 102,
+    CopyPaths = 103,
+    Builds = 104,
+    Build = 105,
+    OptimiseStore = 106,
+    VerifyPaths = 107,
+    Substitute = 108,
+    QueryPathInfo = 109,
+    PostBuildHook = 110,
+};
+
+enum struct ResultType : uint64_t {
+    FileLinked = 100,
+    BuildLogLine = 101,
+    UntrustedPath = 102,
+    CorruptedPath = 103,
+    SetPhase = 104,
+    Progress = 105,
+    SetExpected = 106,
+    PostBuildLogLine = 107,
+};
 
 typedef uint64_t ActivityId;
 
@@ -67,7 +67,7 @@ public:
 
     void log(const FormatOrString & fs)
     {
-        log(lvlInfo, fs);
+        log(Verbosity::Info, fs);
     }
 
     virtual void warn(const std::string & msg);
@@ -94,17 +94,17 @@ struct Activity
 
     Activity(Logger & logger, ActivityType type,
         const Logger::Fields & fields = {}, ActivityId parent = getCurActivity())
-        : Activity(logger, lvlError, type, "", fields, parent) { };
+        : Activity(logger, Verbosity::Error, type, "", fields, parent) { };
 
     Activity(const Activity & act) = delete;
 
     ~Activity();
 
     void progress(uint64_t done = 0, uint64_t expected = 0, uint64_t running = 0, uint64_t failed = 0) const
-    { result(resProgress, done, expected, running, failed); }
+    { result(ResultType::Progress, done, expected, running, failed); }
 
     void setExpected(ActivityType type2, uint64_t expected) const
-    { result(resSetExpected, type2, expected); }
+    { result(ResultType::SetExpected, (uint64_t)type2, expected); }
 
     template<typename... Args>
     void result(ResultType type, const Args & ... args) const
@@ -151,11 +151,11 @@ extern Verbosity verbosity; /* suppress msgs > this */
         } \
     } while (0)
 
-#define printError(args...) printMsg(lvlError, args)
-#define printInfo(args...) printMsg(lvlInfo, args)
-#define printTalkative(args...) printMsg(lvlTalkative, args)
-#define debug(args...) printMsg(lvlDebug, args)
-#define vomit(args...) printMsg(lvlVomit, args)
+#define printError(args...) printMsg(Verbosity::Error, args)
+#define printInfo(args...) printMsg(Verbosity::Info, args)
+#define printTalkative(args...) printMsg(Verbosity::Talkative, args)
+#define debug(args...) printMsg(Verbosity::Debug, args)
+#define vomit(args...) printMsg(Verbosity::Vomit, args)
 
 template<typename... Args>
 inline void warn(const std::string & fs, const Args & ... args)
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 097ff210aff2482a11d114b186752e4eda7df7ff..8cabbb503404a20564677c32d9e61da8c3bebddd 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -430,7 +430,7 @@ void deletePath(const Path & path)
 
 void deletePath(const Path & path, unsigned long long & bytesFreed)
 {
-    //Activity act(*logger, lvlDebug, format("recursively deleting path '%1%'") % path);
+    //Activity act(*logger, Verbosity::Debug, format("recursively deleting path '%1%'") % path);
     bytesFreed = 0;
     _deletePath(path, bytesFreed);
 }
@@ -1410,7 +1410,7 @@ string base64Decode(const string & s)
 
         char digit = decode[(unsigned char) c];
         if (digit == -1)
-            throw Error("invalid character in Base64 string");
+            throw Error("invalid character in Base::Base64 string");
 
         bits += 6;
         d = d << 6 | digit;
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 7c3a302425379a9b754aad86a58a490e2789d3e0..e04ce3701db6a7e72571a31d8dfbbf827759b3b9 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -468,7 +468,7 @@ std::string filterANSIEscapes(const std::string & s,
     unsigned int width = std::numeric_limits<unsigned int>::max());
 
 
-/* Base64 encoding/decoding. */
+/* Base::Base64 encoding/decoding. */
 string base64Encode(const string & s);
 string base64Decode(const string & s);
 
diff --git a/src/nix-copy-closure/nix-copy-closure.cc b/src/nix-copy-closure/nix-copy-closure.cc
index f8703576042730cb86f0228c6c04f569c9fef1e3..74ab03b6fc72d902cf7aadb236584c5f976a3085 100755
--- a/src/nix-copy-closure/nix-copy-closure.cc
+++ b/src/nix-copy-closure/nix-copy-closure.cc
@@ -22,7 +22,7 @@ static int _main(int argc, char ** argv)
                 printVersion("nix-copy-closure");
             else if (*arg == "--gzip" || *arg == "--bzip2" || *arg == "--xz") {
                 if (*arg != "--gzip")
-                    printMsg(lvlError, format("Warning: '%1%' is not implemented, falling back to gzip") % *arg);
+                    printMsg(Verbosity::Error, format("Warning: '%1%' is not implemented, falling back to gzip") % *arg);
                 gzip = true;
             } else if (*arg == "--from")
                 toMode = false;
@@ -31,7 +31,7 @@ static int _main(int argc, char ** argv)
             else if (*arg == "--include-outputs")
                 includeOutputs = true;
             else if (*arg == "--show-progress")
-                printMsg(lvlError, "Warning: '--show-progress' is not implemented");
+                printMsg(Verbosity::Error, "Warning: '--show-progress' is not implemented");
             else if (*arg == "--dry-run")
                 dryRun = true;
             else if (*arg == "--use-substitutes" || *arg == "-s")
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index e47b00acf3b1a6fd5b3a3c13186753daa48b8f8a..4d8537705d745ff75f91e5a4f3abba461102a1d9 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -961,7 +961,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
             try {
                 paths.insert(globals.state->store->parseStorePath(i.queryOutPath()));
             } catch (AssertionError & e) {
-                printMsg(lvlTalkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName());
+                printMsg(Verbosity::Talkative, "skipping derivation named '%s' which gives an assertion failure", i.queryName());
                 i.setFailed();
             }
         validPaths = globals.state->store->queryValidPaths(paths);
@@ -987,7 +987,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
         try {
             if (i.hasFailed()) continue;
 
-            //Activity act(*logger, lvlDebug, format("outputting query result '%1%'") % i.attrPath);
+            //Activity act(*logger, Verbosity::Debug, format("outputting query result '%1%'") % i.attrPath);
 
             if (globals.prebuiltOnly &&
                 !validPaths.count(globals.state->store->parseStorePath(i.queryOutPath())) &&
@@ -1163,7 +1163,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
             cout.flush();
 
         } catch (AssertionError & e) {
-            printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
+            printMsg(Verbosity::Talkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName());
         } catch (Error & e) {
             e.addPrefix(fmt("while querying the derivation named '%1%':\n", i.queryName()));
             throw;
diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc
index 18ced94b1a792864b69791ec755c28d274b81153..750ac2327c6907be4f764628af4ed0a6ee1a4fd6 100644
--- a/src/nix-prefetch-url/nix-prefetch-url.cc
+++ b/src/nix-prefetch-url/nix-prefetch-url.cc
@@ -51,7 +51,7 @@ string resolveMirrorUri(EvalState & state, string uri)
 static int _main(int argc, char * * argv)
 {
     {
-        HashType ht = htSHA256;
+        HashType ht = HashType::SHA256;
         std::vector<string> args;
         bool printPath = getEnv("PRINT_PATH") == "1";
         bool fromExpr = false;
@@ -72,7 +72,7 @@ static int _main(int argc, char * * argv)
             else if (*arg == "--type") {
                 string s = getArg(*arg, arg, end);
                 ht = parseHashType(s);
-                if (ht == htUnknown)
+                if (ht == HashType::Unknown)
                     throw UsageError(format("unknown hash type '%1%'") % s);
             }
             else if (*arg == "--print-path")
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 806ab756381e5930844845d6c69d9dd06ace3bce..454f3e775c425fe33a40bd4fbe5a79a16fa7e31f 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -372,8 +372,8 @@ static void opQuery(Strings opFlags, Strings opArgs)
                 for (auto & j : maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)) {
                     auto info = store->queryPathInfo(j);
                     if (query == qHash) {
-                        assert(info->narHash.type == htSHA256);
-                        cout << fmt("%s\n", info->narHash.to_string(Base32));
+                        assert(info->narHash.type == HashType::SHA256);
+                        cout << fmt("%s\n", info->narHash.to_string(Base::Base32));
                     } else if (query == qSize)
                         cout << fmt("%d\n", info->narSize);
                 }
@@ -502,7 +502,7 @@ static void registerValidity(bool reregister, bool hashGiven, bool canonicalise)
             if (canonicalise)
                 canonicalisePathMetaData(store->printStorePath(info->path), -1);
             if (!hashGiven) {
-                HashResult hash = hashPath(htSHA256, store->printStorePath(info->path));
+                HashResult hash = hashPath(HashType::SHA256, store->printStorePath(info->path));
                 info->narHash = hash.first;
                 info->narSize = hash.second;
             }
@@ -720,7 +720,7 @@ static void opVerifyPath(Strings opFlags, Strings opArgs)
 
     for (auto & i : opArgs) {
         auto path = store->followLinksToStorePath(i);
-        printMsg(lvlTalkative, "checking path '%s'...", store->printStorePath(path));
+        printMsg(Verbosity::Talkative, "checking path '%s'...", store->printStorePath(path));
         auto info = store->queryPathInfo(path);
         HashSink sink(info->narHash.type);
         store->narFromPath(path, sink);
@@ -781,7 +781,7 @@ static void opServe(Strings opFlags, Strings opArgs)
     auto getBuildSettings = [&]() {
         // FIXME: changing options here doesn't work if we're
         // building through the daemon.
-        verbosity = lvlError;
+        verbosity = Verbosity::Error;
         settings.keepLog = false;
         settings.useSubstitutes = false;
         settings.maxSilentTime = readInt(in);
@@ -940,7 +940,7 @@ static void opServe(Strings opFlags, Strings opArgs)
                 auto deriver = readString(in);
                 if (deriver != "")
                     info.deriver = store->parseStorePath(deriver);
-                info.narHash = Hash(readString(in), htSHA256);
+                info.narHash = Hash(readString(in), HashType::SHA256);
                 info.references = readStorePaths<StorePathSet>(*store, in);
                 in >> info.registrationTime >> info.narSize >> info.ultimate;
                 info.sigs = readStrings<StringSet>(in);
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index 139db3657b504cf17e9a186de99b347c0d2836ea..ed35616e6f443c5bb9d3233d216dd4528da3b25a 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -40,7 +40,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand
         StringSink sink;
         dumpPath(path, sink);
 
-        auto narHash = hashString(htSHA256, *sink.s);
+        auto narHash = hashString(HashType::SHA256, *sink.s);
 
         ValidPathInfo info(store->makeFixedOutputPath(true, narHash, *namePart));
         info.narHash = narHash;
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 0cc523f507e72f5ea308bfbad249e8e42ac81c01..deced3d11241ec96ccadc3af42f1d9aa2070a723 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -11,18 +11,18 @@ struct CmdHash : Command
 {
     enum Mode { mFile, mPath };
     Mode mode;
-    Base base = SRI;
+    Base base = Base::SRI;
     bool truncate = false;
-    HashType ht = htSHA256;
+    HashType ht = HashType::SHA256;
     std::vector<std::string> paths;
     std::optional<std::string> modulus;
 
     CmdHash(Mode mode) : mode(mode)
     {
-        mkFlag(0, "sri", "print hash in SRI format", &base, SRI);
-        mkFlag(0, "base64", "print hash in base-64", &base, Base64);
-        mkFlag(0, "base32", "print hash in base-32 (Nix-specific)", &base, Base32);
-        mkFlag(0, "base16", "print hash in base-16", &base, Base16);
+        mkFlag(0, "sri", "print hash in Base::SRI format", &base, Base::SRI);
+        mkFlag(0, "base64", "print hash in base-64", &base, Base::Base64);
+        mkFlag(0, "base32", "print hash in base-32 (Nix-specific)", &base, Base::Base32);
+        mkFlag(0, "base16", "print hash in base-16", &base, Base::Base16);
         mkFlag()
             .longName("type")
             .mkHashTypeFlag(&ht);
@@ -61,7 +61,7 @@ struct CmdHash : Command
             Hash h = hashSink->finish().first;
             if (truncate && h.hashSize > 20) h = compressHash(h, 20);
             std::cout << format("%1%\n") %
-                h.to_string(base, base == SRI);
+                h.to_string(base, base == Base::SRI);
         }
     }
 };
@@ -72,7 +72,7 @@ static RegisterCommand r2("hash-path", [](){ return make_ref<CmdHash>(CmdHash::m
 struct CmdToBase : Command
 {
     Base base;
-    HashType ht = htUnknown;
+    HashType ht = HashType::Unknown;
     std::vector<std::string> args;
 
     CmdToBase(Base base) : base(base)
@@ -86,28 +86,28 @@ struct CmdToBase : Command
     std::string description() override
     {
         return fmt("convert a hash to %s representation",
-            base == Base16 ? "base-16" :
-            base == Base32 ? "base-32" :
-            base == Base64 ? "base-64" :
-            "SRI");
+            base == Base::Base16 ? "base-16" :
+            base == Base::Base32 ? "base-32" :
+            base == Base::Base64 ? "base-64" :
+            "Base::SRI");
     }
 
     void run() override
     {
         for (auto s : args)
-            std::cout << fmt("%s\n", Hash(s, ht).to_string(base, base == SRI));
+            std::cout << fmt("%s\n", Hash(s, ht).to_string(base, base == Base::SRI));
     }
 };
 
-static RegisterCommand r3("to-base16", [](){ return make_ref<CmdToBase>(Base16); });
-static RegisterCommand r4("to-base32", [](){ return make_ref<CmdToBase>(Base32); });
-static RegisterCommand r5("to-base64", [](){ return make_ref<CmdToBase>(Base64); });
-static RegisterCommand r6("to-sri", [](){ return make_ref<CmdToBase>(SRI); });
+static RegisterCommand r3("to-base16", [](){ return make_ref<CmdToBase>(Base::Base16); });
+static RegisterCommand r4("to-base32", [](){ return make_ref<CmdToBase>(Base::Base32); });
+static RegisterCommand r5("to-base64", [](){ return make_ref<CmdToBase>(Base::Base64); });
+static RegisterCommand r6("to-sri", [](){ return make_ref<CmdToBase>(Base::SRI); });
 
 /* Legacy nix-hash command. */
 static int compatNixHash(int argc, char * * argv)
 {
-    HashType ht = htMD5;
+    HashType ht = HashType::MD5;
     bool flat = false;
     bool base32 = false;
     bool truncate = false;
@@ -125,7 +125,7 @@ static int compatNixHash(int argc, char * * argv)
         else if (*arg == "--type") {
             string s = getArg(*arg, arg, end);
             ht = parseHashType(s);
-            if (ht == htUnknown)
+            if (ht == HashType::Unknown)
                 throw UsageError(format("unknown hash type '%1%'") % s);
         }
         else if (*arg == "--to-base16") op = opTo16;
@@ -140,14 +140,14 @@ static int compatNixHash(int argc, char * * argv)
     if (op == opHash) {
         CmdHash cmd(flat ? CmdHash::mFile : CmdHash::mPath);
         cmd.ht = ht;
-        cmd.base = base32 ? Base32 : Base16;
+        cmd.base = base32 ? Base::Base32 : Base::Base16;
         cmd.truncate = truncate;
         cmd.paths = ss;
         cmd.run();
     }
 
     else {
-        CmdToBase cmd(op == opTo32 ? Base32 : Base16);
+        CmdToBase cmd(op == opTo32 ? Base::Base32 : Base::Base16);
         cmd.args = ss;
         cmd.ht = ht;
         cmd.run();
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 013218cd924605eb1357c620308cc22b8c70ebac..03d03e90a29e5a1f641f18c5b887e2153479f7fe 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -272,7 +272,7 @@ Buildables build(ref<Store> store, RealiseMode mode,
     }
 
     if (mode == DryRun)
-        printMissing(store, pathsToBuild, lvlError);
+        printMissing(store, pathsToBuild, Verbosity::Error);
     else if (mode == Build)
         store->buildPaths(pathsToBuild);
 
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 3b5f5516fdff9c89592245b89c0568c296f63c9f..d0a43ab233ce259bf0f763ca0b25e47571a28e11 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -148,7 +148,7 @@ void mainWrapped(int argc, char * * argv)
         if (legacy) return legacy(argc, argv);
     }
 
-    verbosity = lvlWarn;
+    verbosity = Verbosity::Warn;
     settings.verboseBuild = false;
 
     NixArgs args;
diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc
index f9c7fef3f019680b846e85203bcefec9ed8f762f..5c964ec2713ac0114d9c0d5b210e2915ff6068a9 100644
--- a/src/nix/make-content-addressable.cc
+++ b/src/nix/make-content-addressable.cc
@@ -69,7 +69,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
 
             *sink.s = rewriteStrings(*sink.s, rewrites);
 
-            HashModuloSink hashModuloSink(htSHA256, oldHashPart);
+            HashModuloSink hashModuloSink(HashType::SHA256, oldHashPart);
             hashModuloSink((unsigned char *) sink.s->data(), sink.s->size());
 
             auto narHash = hashModuloSink.finish().first;
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index 45ec297d2fbdb79ad190603d66ace2a8e68851c2..a15912ccc074b632c5c8a6a1ed0c415b93388be3 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -89,7 +89,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
             store->pathInfoToJSON(jsonRoot,
                 // FIXME: preserve order?
                 storePathsToSet(storePaths),
-                true, showClosureSize, SRI, AllowInvalid);
+                true, showClosureSize, Base::SRI, AllowInvalid);
         }
 
         else {
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
index 26631416cc25e6733fd8428dea022b645e41dd1b..d53e671edf13c3204da02677544d8f5ba8230a01 100644
--- a/src/nix/progress-bar.cc
+++ b/src/nix/progress-bar.cc
@@ -38,7 +38,7 @@ private:
     struct ActInfo
     {
         std::string s, lastLine, phase;
-        ActivityType type = actUnknown;
+        ActivityType type = ActivityType::Unknown;
         uint64_t done = 0;
         uint64_t expected = 0;
         uint64_t running = 0;
@@ -152,7 +152,7 @@ public:
         state->its.emplace(act, i);
         state->activitiesByType[type].its.emplace(act, i);
 
-        if (type == actBuild) {
+        if (type == ActivityType::Build) {
             auto name = storePathToName(getS(fields, 0));
             if (hasSuffix(name, ".drv"))
                 name = name.substr(0, name.size() - 4);
@@ -167,7 +167,7 @@ public:
             i->name = DrvName(name).name;
         }
 
-        if (type == actSubstitute) {
+        if (type == ActivityType::Substitute) {
             auto name = storePathToName(getS(fields, 0));
             auto sub = getS(fields, 1);
             i->s = fmt(
@@ -177,7 +177,7 @@ public:
                 name, sub);
         }
 
-        if (type == actPostBuildHook) {
+        if (type == ActivityType::PostBuildHook) {
             auto name = storePathToName(getS(fields, 0));
             if (hasSuffix(name, ".drv"))
                 name = name.substr(0, name.size() - 4);
@@ -185,14 +185,14 @@ public:
             i->name = DrvName(name).name;
         }
 
-        if (type == actQueryPathInfo) {
+        if (type == ActivityType::QueryPathInfo) {
             auto name = storePathToName(getS(fields, 0));
             i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
         }
 
-        if ((type == actDownload && hasAncestor(*state, actCopyPath, parent))
-            || (type == actDownload && hasAncestor(*state, actQueryPathInfo, parent))
-            || (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
+        if ((type == ActivityType::Download && hasAncestor(*state, ActivityType::CopyPath, parent))
+            || (type == ActivityType::Download && hasAncestor(*state, ActivityType::QueryPathInfo, parent))
+            || (type == ActivityType::CopyPath && hasAncestor(*state, ActivityType::Substitute, parent)))
             i->visible = false;
 
         update(*state);
@@ -237,13 +237,13 @@ public:
     {
         auto state(state_.lock());
 
-        if (type == resFileLinked) {
+        if (type == ResultType::FileLinked) {
             state->filesLinked++;
             state->bytesLinked += getI(fields, 0);
             update(*state);
         }
 
-        else if (type == resBuildLogLine || type == resPostBuildLogLine) {
+        else if (type == ResultType::BuildLogLine || type == ResultType::PostBuildLogLine) {
             auto lastLine = trim(getS(fields, 0));
             if (!lastLine.empty()) {
                 auto i = state->its.find(act);
@@ -251,10 +251,10 @@ public:
                 ActInfo info = *i->second;
                 if (printBuildLogs) {
                     auto suffix = "> ";
-                    if (type == resPostBuildLogLine) {
+                    if (type == ResultType::PostBuildLogLine) {
                         suffix = " (post)> ";
                     }
-                    log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
+                    log(*state, Verbosity::Info, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine);
                 } else {
                     state->activities.erase(i->second);
                     info.lastLine = lastLine;
@@ -265,24 +265,24 @@ public:
             }
         }
 
-        else if (type == resUntrustedPath) {
+        else if (type == ResultType::UntrustedPath) {
             state->untrustedPaths++;
             update(*state);
         }
 
-        else if (type == resCorruptedPath) {
+        else if (type == ResultType::CorruptedPath) {
             state->corruptedPaths++;
             update(*state);
         }
 
-        else if (type == resSetPhase) {
+        else if (type == ResultType::SetPhase) {
             auto i = state->its.find(act);
             assert(i != state->its.end());
             i->second->phase = getS(fields, 0);
             update(*state);
         }
 
-        else if (type == resProgress) {
+        else if (type == ResultType::Progress) {
             auto i = state->its.find(act);
             assert(i != state->its.end());
             ActInfo & actInfo = *i->second;
@@ -293,7 +293,7 @@ public:
             update(*state);
         }
 
-        else if (type == resSetExpected) {
+        else if (type == ResultType::SetExpected) {
             auto i = state->its.find(act);
             assert(i != state->its.end());
             ActInfo & actInfo = *i->second;
@@ -405,10 +405,10 @@ public:
             res += s;
         };
 
-        showActivity(actBuilds, "%s built");
+        showActivity(ActivityType::Builds, "%s built");
 
-        auto s1 = renderActivity(actCopyPaths, "%s copied");
-        auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
+        auto s1 = renderActivity(ActivityType::CopyPaths, "%s copied");
+        auto s2 = renderActivity(ActivityType::CopyPath, "%s MiB", "%.1f", MiB);
 
         if (!s1.empty() || !s2.empty()) {
             if (!res.empty()) res += ", ";
@@ -416,10 +416,10 @@ public:
             if (!s2.empty()) { res += " ("; res += s2; res += ')'; }
         }
 
-        showActivity(actDownload, "%s MiB DL", "%.1f", MiB);
+        showActivity(ActivityType::Download, "%s MiB DL", "%.1f", MiB);
 
         {
-            auto s = renderActivity(actOptimiseStore, "%s paths optimised");
+            auto s = renderActivity(ActivityType::OptimiseStore, "%s paths optimised");
             if (s != "") {
                 s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
                 if (!res.empty()) res += ", ";
@@ -428,7 +428,7 @@ public:
         }
 
         // FIXME: don't show "done" paths in green.
-        showActivity(actVerifyPaths, "%s paths verified");
+        showActivity(ActivityType::VerifyPaths, "%s paths verified");
 
         if (state.corruptedPaths) {
             if (!res.empty()) res += ", ";
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 27727bd25506881769891764fb7181a3826f7b48..795aa6682f8462d5d6248697d2b799696e1a1988 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -252,12 +252,12 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
                 // input without clearing the input so far.
                 continue;
             } else {
-              printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
+              printMsg(Verbosity::Error, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
             }
         } catch (Error & e) {
-            printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
+            printMsg(Verbosity::Error, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
         } catch (Interrupted & e) {
-            printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
+            printMsg(Verbosity::Error, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
         }
 
         // We handled the current input fully, so we should clear it
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 5f07448e09af587b386b437e2514f7fced235121..2c3d2a107bd66578120c59017af661572ffd53d6 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -45,7 +45,7 @@ struct CmdCopySigs : StorePathsCommand
         //logger->setExpected(doneLabel, storePaths.size());
 
         auto doPath = [&](const Path & storePathS) {
-            //Activity act(*logger, lvlInfo, format("getting signatures for '%s'") % storePath);
+            //Activity act(*logger, Verbosity::Info, format("getting signatures for '%s'") % storePath);
 
             checkInterrupt();
 
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index c05c29517b25e53801715e59bf1e60de2b46224f..26de92d320b9f33679179373f61ba3852adfa989 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -69,12 +69,12 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
         }
 
         {
-            Activity act(*logger, lvlInfo, actUnknown, fmt("downloading '%s'...", store->printStorePath(storePath)));
+            Activity act(*logger, Verbosity::Info, ActivityType::Unknown, fmt("downloading '%s'...", store->printStorePath(storePath)));
             store->ensurePath(storePath);
         }
 
         {
-            Activity act(*logger, lvlInfo, actUnknown, fmt("verifying that '%s' works...", store->printStorePath(storePath)));
+            Activity act(*logger, Verbosity::Info, ActivityType::Unknown, fmt("verifying that '%s' works...", store->printStorePath(storePath)));
             auto program = store->printStorePath(storePath) + "/bin/nix-env";
             auto s = runProgram(program, false, {"--version"});
             if (s.find("Nix") == std::string::npos)
@@ -84,7 +84,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
         stopProgressBar();
 
         {
-            Activity act(*logger, lvlInfo, actUnknown,
+            Activity act(*logger, Verbosity::Info, ActivityType::Unknown,
                 fmt("installing '%s' into profile '%s'...", store->printStorePath(storePath), profileDir));
             runProgram(settings.nixBinDir + "/nix-env", false,
                 {"--profile", profileDir, "-i", store->printStorePath(storePath), "--no-sandbox"});
@@ -135,7 +135,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
     /* Return the store path of the latest stable Nix. */
     StorePath getLatestNix(ref<Store> store)
     {
-        Activity act(*logger, lvlInfo, actUnknown, "querying latest Nix version");
+        Activity act(*logger, Verbosity::Info, ActivityType::Unknown, "querying latest Nix version");
 
         // FIXME: use nixos.org?
         auto req = DownloadRequest(storePathsUrl);
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index 9b0658803661c2e257a3ead185a21b3cd7db253b..17d4410cfb78f9463868f080c705c96d92bc35cc 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -57,7 +57,7 @@ struct CmdVerify : StorePathsCommand
 
         auto publicKeys = getDefaultPublicKeys();
 
-        Activity act(*logger, actVerifyPaths);
+        Activity act(*logger, ActivityType::VerifyPaths);
 
         std::atomic<size_t> done{0};
         std::atomic<size_t> untrusted{0};
@@ -75,7 +75,7 @@ struct CmdVerify : StorePathsCommand
             try {
                 checkInterrupt();
 
-                Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", storePath));
+                Activity act2(*logger, Verbosity::Info, ActivityType::Unknown, fmt("checking '%s'", storePath));
 
                 MaintainCount<std::atomic<size_t>> mcActive(active);
                 update();
@@ -96,7 +96,7 @@ struct CmdVerify : StorePathsCommand
 
                     if (hash.first != info->narHash) {
                         corrupted++;
-                        act2.result(resCorruptedPath, store->printStorePath(info->path));
+                        act2.result(ResultType::CorruptedPath, store->printStorePath(info->path));
                         printError(
                             "path '%s' was modified! expected hash '%s', got '%s'",
                             store->printStorePath(info->path), info->narHash.to_string(), hash.first.to_string());
@@ -147,7 +147,7 @@ struct CmdVerify : StorePathsCommand
 
                     if (!good) {
                         untrusted++;
-                        act2.result(resUntrustedPath, store->printStorePath(info->path));
+                        act2.result(ResultType::UntrustedPath, store->printStorePath(info->path));
                         printError("path '%s' is untrusted", store->printStorePath(info->path));
                     }