Repository: 7mind/izumi
Pull request #1283 (2020-10-07 19:14:33)

Documentation yak shave
* use zio in first distage example
* document standard axis
* document default modules
* rename GC chapter into "Dependency pruning"
* add ResourceRewriter scaladoc
* document Injector inheritance
* expose individual axises in distage package object
* remove references to `distage` package object in `distage.Injector` (could cause over-eager classloading)
* add `Planner.plan(ModuleBase, Roots)` method
* make `GraphDumpBootstrapModule` an object
* Use `@@dependency` directive for all librarydeps
* Provide `LogIO[F]` by default in distage-framework/distage-testkit

codecov-io: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/1283?src=pr&el=h1) Report
> Merging [#1283](https://codecov.io/gh/7mind/izumi/pull/1283?src=pr&el=desc) into [develop](https://codecov.io/gh/7mind/izumi/commit/7cb402b0e56754082dc0e15da3fded4a9c0dbc2d?el=desc) will **increase** coverage by `0.06%`.
> The diff coverage is `88.23%`.


```diff
@@             Coverage Diff             @@
##           develop    #1283      +/-   ##
===========================================
+ Coverage    55.11%   55.17%   +0.06%     
===========================================
  Files          432      432              
  Lines         8183     8190       +7     
  Branches       364      371       +7     
===========================================
+ Hits          4510     4519       +9     
+ Misses        3673     3671       -2     
```


Patch:
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala
index 87f9fd79f..b8c3d095f 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/Planner.scala
@@ -13,4 +13,7 @@ trait Planner extends PlanSplittingOps {
 
   final def plan(bindings: ModuleBase, activation: Activation, roots: Roots): OrderedPlan =
     plan(PlannerInput(bindings, activation, roots))
+
+  final def plan(bindings: ModuleBase, roots: Roots): OrderedPlan =
+    plan(bindings, Activation.empty, roots)
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala
index cbfe245cb..fb254d592 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleDef.scala
@@ -24,7 +24,9 @@ import izumi.distage.model.definition.dsl.ModuleDefDSL
   *   - `make[X]` = create X using its constructor
   *   - `make[X].from[XImpl]` = bind X to its subtype XImpl using XImpl's constructor
   *   - `make[X].from(myX)` = bind X to an already existing instance `myX`
-  *   - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]]
+  *   - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]] requesting an Y parameter
+  *   - `make[X].from { y: Y @Id("special") => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter
+  *   - `make[X].from { y: Y => new X(y) }`.annotateParameter[Y]("special") = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter
   *   - `make[X].named("special")` = bind a named instance of X. It can then be summoned using [[Id]] annotation.
   *   - `make[X].using[X]("special")` = bind X to refer to another already bound named instance at key `[X].named("special")`
   *   - `make[X].fromEffect(X.create[F]: F[X])` = create X using a purely-functional effect `X.create` in `F` monad
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleMake.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleMake.scala
index a34a58082..af74c75d9 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleMake.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/ModuleMake.scala
@@ -7,7 +7,6 @@ object ModuleMake {
 
   sealed trait Aux[-S, +T <: ModuleBase] {
     def make(bindings: Set[Binding]): T
-
     final def empty: T = make(Set.empty)
   }
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/StandardAxis.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/StandardAxis.scala
index ab13d509d..901f59ecf 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/StandardAxis.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/StandardAxis.scala
@@ -2,6 +2,7 @@ package izumi.distage.model.definition
 
 object StandardAxis {
 
+  /** Describes a generic choice between production and test implementations of a component */
   object Mode extends Axis {
     override def name: String = "mode"
 
@@ -9,12 +10,12 @@ object StandardAxis {
     case object Test extends AxisValueDef
   }
 
-  @deprecated("Use `distage.StandardAxis.Mode` instead", "0.11.0")
-  final val Env = Mode
-
   /**
     * Any entities which may store and persist state or "repositories".
     * e.g. databases, message queues, KV storages, file systems, etc.
+    *
+    * Those may typically have both in-memory `Dummy` implementations
+    * and heavyweight `Prod` implementations using external databases.
     */
   object Repo extends Axis {
     override def name: String = "repo"
@@ -26,6 +27,8 @@ object StandardAxis {
   /**
     * Third-party integrations which are not controlled by the application and provided "as is".
     * e.g. Facebook API, Google API, etc.
+    *
+    * Those may contact a `Real` external integration or a `Mock` one with predefined responses.
     */
   object World extends Axis {
     override def name: String = "world"
@@ -34,17 +37,16 @@ object StandardAxis {
     case object Mock extends AxisValueDef
   }
 
-  @deprecated("Use StandardAxis.World instead", "0.11.0")
-  final val ExternalApi = World
-
   /**
-    * Choice axis controlling whether third-party services that the application requires
-    * should be provided by `distage-framework-docker` or another orchestrator when the application starts (`Scene.Managed`),
-    * or whether it should try to connect to these services as if they already exist in the environment (`Scene.Provided`)
+    * Describes whether external services required by the application
+    * should be set-up on the fly by an orchestrator library such as `distage-framework-docker` (`Scene.Managed`),
+    * or whether the application should try to connect to external services
+    * as if they already exist in the environment (`Scene.Provided`).
     *
-    * The set of third-party services required by the application is called a `Scene`, etymology being that the running
-    * third-party services that the application depends on are like a scene that is prepared for for the actor (the application)
-    * to enter.
+    * We call a set of external services required by the application a `Scene`,
+    * etymology being that the running external services required by the application
+    * are like a "scene" that the "theatre staff" (the orchestrator) must prepare
+    * for the "actor" (the application) to enter.
     */
   object Scene extends Axis {
     override def name: String = "scene"
@@ -80,4 +82,10 @@ object StandardAxis {
     )
   }
 
+  @deprecated("Use `distage.StandardAxis.Mode` instead", "0.11")
+  lazy val Env: Mode.type = Mode
+
+  @deprecated("Use `distage.StandardAxis.World` instead", "0.11")
+  lazy val ExternalApi: World.type = World
+
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
index 134e7f398..8c6caa6dd 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/dsl/ModuleDefDSL.scala
@@ -40,7 +40,9 @@ import scala.collection.immutable.HashSet
   *   - `make[X]` = create X using its constructor
   *   - `make[X].from[XImpl]` = bind X to its subtype XImpl using XImpl's constructor
   *   - `make[X].from(myX)` = bind X to an already existing instance `myX`
-  *   - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]]
+  *   - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]] requesting an Y parameter
+  *   - `make[X].from { y: Y @Id("special") => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter
+  *   - `make[X].from { y: Y => new X(y) }`.annotateParameter[Y]("special") = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter
   *   - `make[X].named("special")` = bind a named instance of X. It can then be summoned using [[Id]] annotation.
   *   - `make[X].using[X]("special")` = bind X to refer to another already bound named instance at key `[X].named("special")`
   *   - `make[X].fromEffect(X.create[F]: F[X])` = create X using a purely-functional effect `X.create` in `F` monad
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala
index 93ee1f8ef..9a42e6172 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/Roots.scala
@@ -23,7 +23,7 @@ import izumi.reflect.Tag
   * @see distage-testkit    https://izumi.7mind.io/distage/distage-testkit.html
   */
 sealed trait Roots {
-  def ++(that: Roots): Roots = {
+  final def ++(that: Roots): Roots = {
     (this, that) match {
       case (Roots.Of(a), Roots.Of(b)) => Roots.Of(a ++ b)
       case (Roots.Everything, _) => Roots.Everything
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala
index f4441cf7a..f89585c87 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/impl/OrderedPlanExtensions.scala
@@ -24,6 +24,11 @@ private[plan] trait OrderedPlanExtensions extends Any { this: OrderedPlan.type =
 private[plan] object OrderedPlanExtensions {
 
   final class OrderedPlanRenderOps(private val plan: OrderedPlan) extends AnyVal {
+    /**
+      * Render with compact type names
+      *
+      * @see [[OrderedPlanExtensions#defaultFormatter]]
+      */
     def render()(implicit ev: Renderable[OrderedPlan]): String = ev.render(plan)
     def renderDeps(node: DepNode): String = new DepTreeRenderer(node, plan).render()
 
diff --git a/distage/distage-core/src/main/scala/distage/Distage.scala b/distage/distage-core/src/main/scala/distage/Distage.scala
index eba2949a5..ee9fa3402 100644
--- a/distage/distage-core/src/main/scala/distage/Distage.scala
+++ b/distage/distage-core/src/main/scala/distage/Distage.scala
@@ -49,6 +49,10 @@ trait Distage {
   val Axis: model.definition.Axis.type = model.definition.Axis
 
   val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
+  val Mode: model.definition.StandardAxis.Mode.type = model.definition.StandardAxis.Mode
+  val Repo: model.definition.StandardAxis.Repo.type = model.definition.StandardAxis.Repo
+  val World: model.definition.StandardAxis.World.type = model.definition.StandardAxis.World
+  val Scene: model.definition.StandardAxis.Scene.type = model.definition.StandardAxis.Scene
 
   type DIKey = model.reflection.DIKey
   val DIKey: model.reflection.DIKey.type = model.reflection.DIKey
diff --git a/distage/distage-core/src/main/scala/distage/Injector.scala b/distage/distage-core/src/main/scala/distage/Injector.scala
index 0e6f08398..2ad4efafd 100644
--- a/distage/distage-core/src/main/scala/distage/Injector.scala
+++ b/distage/distage-core/src/main/scala/distage/Injector.scala
@@ -1,11 +1,12 @@
 package distage
 
 import izumi.distage.bootstrap.{BootstrapLocator, Cycles}
+import izumi.distage.model.definition
 import izumi.distage.model.definition.BootstrapContextModule
 import izumi.distage.model.effect.DIEffect
 import izumi.distage.model.recursive.Bootloader
 import izumi.distage.modules.support.IdentitySupportModule
-import izumi.distage.{InjectorDefaultImpl, InjectorFactory}
+import izumi.distage.{InjectorDefaultImpl, InjectorFactory, modules}
 import izumi.fundamentals.platform.functional.Identity
 
 object Injector extends InjectorFactory {
@@ -80,7 +81,7 @@ object Injector extends InjectorFactory {
     * @param parent Instances from parent [[izumi.distage.model.Locator]] will be available as imports in new Injector's [[izumi.distage.model.Producer#produce produce]]
     */
   override def inherit[F[_]: DIEffect: TagK](parent: Locator): Injector[F] = {
-    new InjectorDefaultImpl(parent, this, defaultModule = Module.empty)
+    new InjectorDefaultImpl(parent, this, defaultModule = definition.Module.empty)
   }
 
   override def inheritWithDefaultModule[F[_]: DIEffect: TagK](parent: Locator, defaultModule: Module): Injector[F] = {
@@ -129,14 +130,14 @@ object Injector extends InjectorFactory {
     }
 
     override final def inherit[F[_]: DIEffect: TagK](parent: Locator): Injector[F] = {
-      new InjectorDefaultImpl(parent, this, Module.empty)
+      new InjectorDefaultImpl(parent, this, definition.Module.empty)
     }
 
     override final def inheritWithDefaultModule[F[_]: DIEffect: TagK](parent: Locator, defaultModule: Module): Injector[F] = {
       new InjectorDefaultImpl(parent, this, defaultModule)
     }
 
-    private[this] def cycleActivation: Activation = Activation(Cycles -> cycleChoice)
+    private[this] def cycleActivation: Activation = definition.Activation(Cycles -> cycleChoice)
   }
 
   private[this] def bootstrap[F[_]: DIEffect: TagK: DefaultModule](
@@ -145,7 +146,7 @@ object Injector extends InjectorFactory {
     overrides: BootstrapModule,
   ): Injector[F] = {
     val bootstrapLocator = new BootstrapLocator(bootstrapBase overriddenBy overrides, activation)
-    val defaultModules = DefaultModule[F] ++ IdentitySupportModule // Identity support always on
+    val defaultModules = modules.DefaultModule[F] ++ IdentitySupportModule // Identity support always on
     new InjectorDefaultImpl(bootstrapLocator, this, defaultModules)
   }
 
diff --git a/distage/distage-core/src/main/scala/distage/package.scala b/distage/distage-core/src/main/scala/distage/package.scala
index 0fbc18820..796934582 100644
--- a/distage/distage-core/src/main/scala/distage/package.scala
+++ b/distage/distage-core/src/main/scala/distage/package.scala
@@ -47,6 +47,10 @@ package object distage extends Distage {
   override val Axis: model.definition.Axis.type = model.definition.Axis
 
   override val StandardAxis: model.definition.StandardAxis.type = model.definition.StandardAxis
+  override val Mode: model.definition.StandardAxis.Mode.type = model.definition.StandardAxis.Mode
+  override val Repo: model.definition.StandardAxis.Repo.type = model.definition.StandardAxis.Repo
+  override val World: model.definition.StandardAxis.World.type = model.definition.StandardAxis.World
+  override val Scene: model.definition.StandardAxis.Scene.type = model.definition.StandardAxis.Scene
 
   override type DIKey = model.reflection.DIKey
   override val DIKey: model.reflection.DIKey.type = model.reflection.DIKey
diff --git a/distage/distage-core/src/main/scala/izumi/distage/planning/extensions/GraphDumpBootstrapModule.scala b/distage/distage-core/src/main/scala/izumi/distage/planning/extensions/GraphDumpBootstrapModule.scala
index 88d5e7ca1..9efc0e855 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/planning/extensions/GraphDumpBootstrapModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/planning/extensions/GraphDumpBootstrapModule.scala
@@ -4,12 +4,11 @@ import izumi.distage.model.definition.BootstrapModuleDef
 import izumi.distage.model.planning.PlanningObserver
 
 class GraphDumpBootstrapModule extends BootstrapModuleDef {
-  // GraphDumpObserver doesn't work on sjs
-
+  // note: GraphDumpObserver doesn't work on Scala.js [due to file IO?]
   many[PlanningObserver]
     .add[GraphDumpObserver]
 }
 
-object GraphDumpBootstrapModule {
-  def apply(): GraphDumpBootstrapModule = new GraphDumpBootstrapModule()
+object GraphDumpBootstrapModule extends GraphDumpBootstrapModule {
+  def apply(): GraphDumpBootstrapModule.type = this
 }
diff --git a/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala b/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala
index 27941f9a1..f3a498538 100644
--- a/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala
+++ b/distage/distage-extension-logstage/src/main/scala/izumi/logstage/distage/LogIOModule.scala
@@ -1,33 +1,56 @@
 package izumi.logstage.distage
 
-import izumi.distage.model.definition.BootstrapModuleDef
-import izumi.functional.bio.{SyncSafe2, SyncSafe3}
+import izumi.distage.model.definition.ModuleDef
 import izumi.functional.mono.SyncSafe
+import izumi.logstage.api.IzLogger
 import izumi.reflect.{TagK, TagK3, TagKK}
 import logstage.{LogCreateIO, LogIO, LogRouter, UnsafeLogIO}
 
-class LogIOModule[F[_]: SyncSafe: TagK](
-  router: LogRouter,
+/**
+  * @param maybeRouter if `Some`, include a new [[LogstageModule]] based on the passed [[izumi.logstage.api.logger.LogRouter]]
+  *                    if `None`, just add a [[logstage.LogIO]] binding
+  *
+  * @param setupStaticLogRouter if `true`, register the passed [[izumi.logstage.api.logger.LogRouter]] in [[izumi.logstage.api.routing.StaticLogRouter]]
+  *                             (required for slf4j support)
+  */
+class LogIOModule[F[_]: TagK](
+  maybeRouter: Option[LogRouter],
   setupStaticLogRouter: Boolean,
-) extends BootstrapModuleDef {
-  include(LogstageModule(router, setupStaticLogRouter))
+) extends ModuleDef {
+  maybeRouter.foreach {
+    router =>
+      include(LogstageModule(router, setupStaticLogRouter))
+  }
 
   make[LogIO[F]]
-    .from(LogIO.fromLogger[F](_))
+    .from(LogIO.fromLogger[F](_: IzLogger)(_: SyncSafe[F]))
     .aliased[UnsafeLogIO[F]]
     .aliased[LogCreateIO[F]]
 }
 
-class LogBIOModule[F[_, _]: SyncSafe2: TagKK](router: LogRouter, setupStaticLogRouter: Boolean) extends LogIOModule[F[Nothing, ?]](router, setupStaticLogRouter)
-
-class LogBIO3Module[F[_, _, _]: SyncSafe3: TagK3](router: LogRouter, setupStaticLogRouter: Boolean) extends LogIOModule[F[Any, Nothing, ?]](router, setupStaticLogRouter)
-
 object LogIOModule {
-  @inline def apply[F[_]: SyncSafe: TagK](router: LogRouter, setupStaticLogRouter: Boolean): LogIOModule[F] = new LogIOModule(router, setupStaticLogRouter)
+  @inline def apply[F[_]: TagK](router: LogRouter, setupStaticLogRouter: Boolean): LogIOModule[F] = new LogIOModule(Some(router), setupStaticLogRouter)
+  @inline def apply[F[_]: TagK](): LogIOModule[F] = new LogIOModule(None, setupStaticLogRouter = false)
 }
+
+/** [[LogIOModule]] for bifunctors */
+class LogBIOModule[F[_, _]: TagKK](
+  maybeRouter: Option[LogRouter],
+  setupStaticLogRouter: Boolean,
+) extends LogIOModule[F[Nothing, ?]](maybeRouter, setupStaticLogRouter)
+
 object LogBIOModule {
-  @inline def apply[F[_, _]: SyncSafe2: TagKK](router: LogRouter, setupStaticLogRouter: Boolean): LogBIOModule[F] = new LogBIOModule(router, setupStaticLogRouter)
+  @inline def apply[F[_, _]: TagKK](router: LogRouter, setupStaticLogRouter: Boolean): LogBIOModule[F] = new LogBIOModule(Some(router), setupStaticLogRouter)
+  @inline def apply[F[_, _]: TagKK](): LogBIOModule[F] = new LogBIOModule(None, setupStaticLogRouter = false)
 }
+
+/** [[LogIOModule]] for trifunctors */
+class LogBIO3Module[F[_, _, _]: TagK3](
+  maybeRouter: Option[LogRouter],
+  setupStaticLogRouter: Boolean,
+) extends LogIOModule[F[Any, Nothing, ?]](maybeRouter, setupStaticLogRouter)
+
 object LogBIO3Module {
-  @inline def apply[F[_, _, _]: SyncSafe3: TagK3](router: LogRouter, setupStaticLogRouter: Boolean): LogBIO3Module[F] = new LogBIO3Module(router, setupStaticLogRouter)
+  @inline def apply[F[_, _, _]: TagK3](router: LogRouter, setupStaticLogRouter: Boolean): LogBIO3Module[F] = new LogBIO3Module(Some(router), setupStaticLogRouter)
+  @inline def apply[F[_, _, _]: TagK3](): LogBIO3Module[F] = new LogBIO3Module(None, setupStaticLogRouter = false)
 }
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
index 9ac1a0d88..e3e855b88 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/PluginConfig.scala
@@ -34,19 +34,19 @@ final case class PluginConfig(
 }
 
 object PluginConfig {
-  /** Scan the specified packages for modules that inherit [[PluginBase]] */
+  /** Scan the specified packages at runtime for classes and objects that inherit [[PluginBase]] */
   def cached(packagesEnabled: Seq[String]): PluginConfig = PluginConfig(packagesEnabled, Nil, cachePackages = cacheEnabled, debug = false, Nil, Nil)
   def cached(pluginsPackage: String): PluginConfig = PluginConfig(pluginsPackage :: Nil, Nil, cachePackages = cacheEnabled, debug = false, Nil, Nil)
   def packages(packagesEnabled: Seq[String]): PluginConfig = PluginConfig(packagesEnabled, Nil, cachePackages = false, debug = false, Nil, Nil)
   def packages(pluginsPackage: String): PluginConfig = PluginConfig(pluginsPackage :: Nil, Nil, cachePackages = false, debug = false, Nil, Nil)
 
-  /** Create a [[PluginConfig]] that returns a list of plugins scanned at compile-time.
+  /** Scan the specified package *at compile-time* for classes and objects that inherit [[PluginBase]]
     *
     * WARN: may interact badly with incremental compilation
-    * WARN: will _not_ find plugins defined in current module, only those defined in dependency modules (similarly to
-    *       how you cannot call Scala macros defined in the current module)
+    * WARN: will _not_ find plugins defined in the current module,only those defined in dependency modules
+    *       (similarly to how you cannot call Scala macros defined in the current module)
     */
-  def staticallyAvailablePlugins(pluginsPackage: String): PluginConfig = macro StaticPluginLoaderMacro.staticallyAvailablePluginConfig
+  def static(pluginsPackage: String): PluginConfig = macro StaticPluginLoaderMacro.staticallyAvailablePluginConfig
 
   /** Create a [[PluginConfig]] that simply returns the specified plugins */
   def const(plugins: Seq[PluginBase]): PluginConfig = PluginConfig(Nil, Nil, cachePackages = false, debug = false, plugins, Nil)
@@ -56,4 +56,13 @@ object PluginConfig {
   lazy val empty: PluginConfig = const(Nil)
 
   private[this] lazy val cacheEnabled: Boolean = DebugProperties.`izumi.distage.plugins.cache`.boolValue(true)
+
+  /** Scan the specified package *at compile-time* for classes and objects that inherit [[PluginBase]] and put them into a list.
+    *
+    * WARN: may interact badly with incremental compilation
+    * WARN: will _not_ find plugins defined in the current module,only those defined in dependency modules
+    *       (similarly to how you cannot call Scala macros defined in the current module)
+    */
+  @deprecated("renamed to `.static`", "1.0.0")
+  def staticallyAvailablePlugins(pluginsPackage: String): PluginConfig = macro StaticPluginLoaderMacro.staticallyAvailablePluginConfig
 }
diff --git a/distage/distage-framework-api/src/main/scala/izumi/distage/roles/model/definition/RoleModuleDef.scala b/distage/distage-framework-api/src/main/scala/izumi/distage/roles/model/definition/RoleModuleDef.scala
index d59e1916f..02bcb4879 100644
--- a/distage/distage-framework-api/src/main/scala/izumi/distage/roles/model/definition/RoleModuleDef.scala
+++ b/distage/distage-framework-api/src/main/scala/izumi/distage/roles/model/definition/RoleModuleDef.scala
@@ -4,15 +4,13 @@ import izumi.distage.constructors.macros.AnyConstructorMacro
 import izumi.distage.model.definition.ModuleDef
 import izumi.distage.model.definition.dsl.ModuleDefDSL.MakeDSL
 import izumi.distage.roles.model.RoleDescriptor.GetRoleDescriptor
+import izumi.distage.roles.model.definition.RoleModuleDef.RoleModuleDefMacros
 
 import scala.language.experimental.macros
 import scala.reflect.macros.blackbox
 
 trait RoleModuleDef extends ModuleDef {
-
-  final protected[this] def makeRole[T](implicit getRoleDescriptor: GetRoleDescriptor[T]): MakeDSL[T] =
-    macro RoleModuleDef.RoleModuleDefMacros.makeRole[T]
-
+  final protected[this] def makeRole[T](implicit getRoleDescriptor: GetRoleDescriptor[T]): MakeDSL[T] = macro RoleModuleDefMacros.makeRole[T]
 }
 
 object RoleModuleDef {
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala
index c756d07ad..1b4e6c6bb 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ConfigLoader.scala
@@ -20,9 +20,9 @@ import scala.util.{Failure, Success, Try}
 
 /**
   * Default config resources:
-  *   - `\$roleName.conf``
-  *   - `\$roleName-reference.conf`
-  *   - `\$roleName-reference-dev.conf`
+  *   - `\${roleName}.conf`
+  *   - `\${roleName}-reference.conf`
+  *   - `\${roleName}-reference-dev.conf`
   *   - `application.conf`
   *   - `application-reference.conf`
   *   - `application-reference-dev.conf`
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala
index 3f1d98b33..fe7e286ed 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala
@@ -14,7 +14,8 @@ import izumi.functional.bio.BIORunner.FailureHandler
 import izumi.fundamentals.platform.cli.model.raw.RawAppArgs
 import izumi.logstage.api.IzLogger
 import izumi.logstage.api.logger.LogRouter
-import izumi.logstage.distage.LogstageModule
+import izumi.logstage.distage.{LogIOModule, LogstageModule}
+import izumi.reflect.TagK
 
 trait ModuleProvider {
   def bootstrapModules(): Seq[BootstrapModule]
@@ -23,7 +24,7 @@ trait ModuleProvider {
 
 object ModuleProvider {
 
-  class Impl(
+  class Impl[F[_]: TagK](
     logRouter: LogRouter,
     config: AppConfig,
     roles: RolesInfo,
@@ -62,21 +63,26 @@ object ModuleProvider {
     }
 
     def appModules(): Seq[Module] = {
-      new ModuleDef {
-        make[FailureHandler].from {
-          logger: IzLogger =>
-            FailureHandler.Custom {
-              case BIOExit.Error(error, trace) =>
-                logger.warn(s"Fiber errored out due to unhandled $error $trace")
-              case BIOExit.Termination(interrupt, (_: InterruptedException) :: _, trace) =>
-                logger.trace(s"Fiber interrupted with $interrupt $trace")
-              case BIOExit.Termination(defect, _, trace) =>
-                logger.warn(s"Fiber terminated erroneously with unhandled $defect $trace")
-            }
-        }
-      } :: Nil
+      Seq(
+        LogIOModule[F](),
+        LogstageFailureHandlerModule,
+      )
     }
 
   }
 
+  object LogstageFailureHandlerModule extends ModuleDef {
+    make[FailureHandler].from {
+      logger: IzLogger =>
+        FailureHandler.Custom {
+          case BIOExit.Error(error, trace) =>
+            logger.warn(s"Fiber errored out due to unhandled $error $trace")
+          case BIOExit.Termination(interrupt, (_: InterruptedException) :: _, trace) =>
+            logger.trace(s"Fiber interrupted with $interrupt $trace")
+          case BIOExit.Termination(defect, _, trace) =>
+            logger.warn(s"Fiber terminated erroneously with unhandled $defect $trace")
+        }
+    }
+  }
+
 }
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ResourceRewriter.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ResourceRewriter.scala
index c55a1b03f..ce264ea40 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ResourceRewriter.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ResourceRewriter.scala
@@ -14,6 +14,24 @@ import izumi.fundamentals.platform.language.SourceFilePosition
 import izumi.reflect.Tag
 import izumi.logstage.api.IzLogger
 
+/**
+  * Rewrites bindings implemented with `_ <: AutoCloseable` into resource bindings that automatically close the implementation closeable.
+  *
+  * {{{
+  *   class XImpl extends AutoCloseable
+  *   make[X].from[XImpl]
+  * }}}
+  *
+  * becomes:
+  *
+  * {{{
+  *   make[X].fromResource {
+  *    ClassConstructor[XImpl].map(distage.Lifecycle.fromAutoCloseable(_))
+  *   }
+  * }}}
+  *
+  * Will produce warnings for all rewritten bindings, so better explicitly use `.fromResource`!
+  */
 class ResourceRewriter(
   logger: IzLogger,
   rules: RewriteRules,
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/MainAppModule.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/MainAppModule.scala
index 9dae23ab0..e3f568384 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/roles/MainAppModule.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/MainAppModule.scala
@@ -107,11 +107,11 @@ class MainAppModule[F[_]: TagK: DefaultModule](
     }
 
   make[Seq[PluginBase]]
-    .named("main")
-    .from {
-      (loader: PluginLoader @Id("main"), config: PluginConfig @Id("main")) =>
-        loader.load(config)
+    .named("main").from {
+      (_: PluginLoader @Id("main")).load(_: PluginConfig @Id("main"))
     }
+    .annotateParameter[PluginLoader]("main")
+    .annotateParameter[PluginConfig]("main")
 
   make[Activation].named("main").fromValue(StandardAxis.prodActivation)
   make[Activation].named("additional").fromValue(Activation.empty)
@@ -123,9 +123,9 @@ class MainAppModule[F[_]: TagK: DefaultModule](
   make[IzLogger].from {
     (
       parameters: RawAppArgs,
-      defaultLogLevel: Log.Level @Id("early"),
       earlyLogger: IzLogger @Id("early"),
       config: AppConfig,
+      defaultLogLevel: Log.Level @Id("early"),
       defaultLogFormatJson: Boolean @Id("distage.roles.logs.json"),
     ) =>
       EarlyLoggers.makeLateLogger(parameters, earlyLogger, config, defaultLogLevel, defaultLogFormatJson)
@@ -191,7 +191,7 @@ class MainAppModule[F[_]: TagK: DefaultModule](
     logger: IzLogger =>
       logger.router
   }
-  make[ModuleProvider].from[ModuleProvider.Impl]
+  make[ModuleProvider].from[ModuleProvider.Impl[F]]
 
   make[BootstrapModule].named("roleapp").from {
     (provider: ModuleProvider, bsModule: ModuleBase @Id("bootstrap")) =>
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/RoleProvider.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/RoleProvider.scala
index 207753c35..52f2db494 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/RoleProvider.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/launcher/RoleProvider.scala
@@ -89,7 +89,8 @@ object RoleProvider {
         case _ =>
           Seq.empty
       }.flatMap {
-          case Right((roleBinding, descriptor)) => mkRoleBinding(roleBinding, descriptor)
+          case Right((roleBinding, descriptor)) =>
+            mkRoleBinding(roleBinding, descriptor)
           case Left(roleBinding) =>
             if (reflectionEnabled()) {
               reflectCompanionDescriptor(roleBinding.key.tpe) match {
diff --git a/doc/microsite/src/main/tut/distage/00_distage.md b/doc/microsite/src/main/tut/distage/00_distage.md
index 0a0be0ed6..27e6b7a4a 100644
--- a/doc/microsite/src/main/tut/distage/00_distage.md
+++ b/doc/microsite/src/main/tut/distage/00_distage.md
@@ -5,17 +5,37 @@ out: index.html
 distage: Staged Dependency Injection
 ====================================
 
-`distage` is a pragmatic module system for FP Scala & Scala.js. It combines simplicity and expressiveness of pure FP
+`distage` is a pragmatic module system for pure-FP Scala & Scala.js. It combines simplicity and expressiveness of pure FP
 with the flexibility and extreme late-binding, traditionally associated with object-oriented dependency injection frameworks, such as Guice.
 
 `distage` is suitable for wiring @ref[Tagless Final Style](basics.md#tagless-final-style),
-@ref[ZIO Environment style and ZLayer-based applications](basics.md#zio-has-bindings), and imperative Scala style applications.
+@ref[ZIO ZLayer-based applications](basics.md#zio-has-bindings), and ordinary FP, actor-based or imperative Scala applications.
+
+#### Real-world example
+
+Check out [`distage-example` project](https://github.com/7mind/distage-example) for a complete example built using `distage`, tagless final, `http4s`, `doobie` and `zio` libraries.
+
+It shows how to write an idiomatic `distage`-style from scratch and how to:
+
+- write tests using @ref[`distage-testkit`](distage-testkit.md)
+- setup portable, zero-setup test environments using @ref[`distage-framework-docker`](distage-framework-docker.md)
+- create @ref[role-based applications](distage-framework.md#roles)
+- enable @ref[compile-time checks](distage-framework.md) for fast-feedback on wiring errors
+
+```scala mdoc:invisible
+/**
+add to distage-example
+
+- how to setup graalvm native image with distage
+- how to debug dump graphs and render to graphviz [Actually, we have a GUI component now, can we show em there???]
+*/
+```
 
 Why distage?
 ------------
 
 1. **Faster applications and tests**:
-    `distage` guarantees that no unnecessary instantiations will happen during your tests or application startup.
+    `distage` guarantees that no unnecessary instantiations will happen during your tests or application startup. `distage` itself is very fast, in part due to not relying on any sort of runtime reflection.
 2. **Quick failure detection**:
     `distage` performs all the integration checks for your application and tests even before any instantiations happened.    
 3. **Simple tests**:
@@ -35,8 +55,7 @@ Why distage?
 8. **High Correctness**:
     `distage` supports resources and lifecycle natively and guarantees proper cleanups even when something went wrong.
 9. **No reflection**:
-    `distage` generates constructors and [type information](https://blog.7mind.io/lightweight-reflection.html) at compile-time and does not use Scala reflection.
-    As such, it's compatible with Graal Native Image and Scala.js.
+    `distage` generates constructors and [type information](https://blog.7mind.io/lightweight-reflection.html) at compile-time and does not use Scala reflection. As such, it's compatible with GraalVM Native Image and Scala.js.
 10. **Non-invasive**:
     `distage` is designed to not impact the way your Scala code is written, it just removes all the initialization boilerplate.
     You don't need to learn magic tricks to write components in a distage application.
@@ -68,8 +87,8 @@ a few experimental @ref[macros](distage-framework.md#compile-time-checks) for ab
 Documentation
 -------------
 
-- @ref[Basics](basics.md)
-- @ref[Advanced features](advanced-features.md)
+- @ref[Introduction](basics.md)
+- @ref[Advanced Features](advanced-features.md)
 - @ref[Debugging](debugging.md)
 - @ref[distage-framework](distage-framework.md)
 - @ref[distage-framework-docker](distage-framework-docker.md)
@@ -104,8 +123,8 @@ Videos:
 
 @@@ index
 
-* [Basics](basics.md)
-* [Advanced features](advanced-features.md)
+* [Introduction](basics.md)
+* [Advanced Features](advanced-features.md)
 * [Debugging](debugging.md)
 * [distage-framework](distage-framework.md)
 * [distage-framework-docker](distage-framework-docker.md)
diff --git a/doc/microsite/src/main/tut/distage/advanced-features.md b/doc/microsite/src/main/tut/distage/advanced-features.md
index 0e6ee2966..aa73c98be 100644
--- a/doc/microsite/src/main/tut/distage/advanced-features.md
+++ b/doc/microsite/src/main/tut/distage/advanced-features.md
@@ -3,20 +3,22 @@ Advanced Features
 
 @@toc { depth=2 }
 
-### Garbage Collection
+### Dependency Pruning
 
-A garbage collector is included in `distage` by default. Given a set of `GC root` keys, GC will remove all bindings that
-are neither direct nor transitive dependencies of the supplied roots – these bindings will be thrown out and never instantiated.
+`distage` performs pruning of all unused bindings by default.
+When you configure a set of "root" keys - 
+either explicitly by passing @scaladoc[Roots](izumi.distage.model.plan.Roots)
+or implicitly by using @scaladoc[Injector#produceRun](izumi.distage.model.Injector#produceRun) or @scaladoc[Injector#produceGet](izumi.distage.model.Injector#produceGet) methods, `distage` will remove all bindings that aren't required to create the supplied roots – these bindings will be thrown out and not even considered, much less executed.
 
-GC serves two important purposes:
+Pruning serves two important purposes:
 
-* It enables faster @ref[tests](distage-testkit.md) by omitting unrequired instantiations and initialization of potentially heavy resources,
-* It enables multiple independent applications, aka "@ref[Roles](distage-framework.md#roles)" to be hosted within a single `.jar` file.
+* It enables faster @ref[tests](distage-testkit.md) by omitting unused instantiations and allocations of potentially heavy resources,
+* It enables multiple independent applications, aka "@ref[Roles](distage-framework.md#roles)" to be hosted within a single binary.
 
-To use garbage collector, pass GC roots as an argument to `Injector.produce*` methods:
+Example:
 
 ```scala mdoc:reset:to-string
-import distage._
+import distage.{Roots, ModuleDef, Injector}
 
 class A(b: B) {
   println("A!")
@@ -28,20 +30,16 @@ class C() {
   println("C!")
 }
 
-val module = new ModuleDef {
+def module = new ModuleDef {
   make[A]
   make[B]
   make[C]
 }
 
-// declare `A` as a GC root
-
-val gc = Roots(root = DIKey[A])
-
 // create an object graph from description in `module`
-// with `A` as a GC root
+// with `A` as the GC root
 
-val objects = Injector().produce(module, gc).unsafeGet()
+val objects = Injector().produce(module, Roots.target[A]).unsafeGet()
 
 // A and B are in the object graph
 
@@ -54,8 +52,10 @@ objects.find[B]
 objects.find[C]
 ```
 
-Class `C` was removed because neither `B` nor `A` depended on it. It's not present in the `Locator` and the `"C!"` message has never been printed.
-If class `B` had a `C` parameter, like `class B(c: C)`; `C` would have been retained, because `A` - the GC root, would depend on `B`, and `B` would depend on `C`.
+Class `C` was removed because neither `B` nor `A` depended on it. It's neither present in the `Locator` nor the `"C!"` message from it's constructor was ever printed.
+
+If you add `c: C` parameter to `class B` , like `class B(c: C)` - then `C` _will_ be instantiated,
+because `A` - the "root", will now depend on `B`, and `B` will depend on `C`.
 
 ```scala mdoc:reset-object:invisible:to-string
 import distage._
@@ -67,7 +67,7 @@ class C() {
   println("C!")
 }
 
-val module = new ModuleDef {
+def module = new ModuleDef {
   make[A]
   make[B]
   make[C]
@@ -79,7 +79,7 @@ class B(c: C) {
   println("B!")
 }
 
-val objects = Injector().produce(module, Roots(DIKey[A])).unsafeGet()
+val objects = Injector().produce(module, Roots.target[A]).unsafeGet()
 
 objects.find[C]
 ```
@@ -89,26 +89,30 @@ objects.find[C]
 `distage` automatically resolves arbitrary circular dependencies, including self-references:
 
 ```scala mdoc:reset-object:to-string
-import distage.{Roots, ModuleDef, Injector}
+import distage.{DIKey, Roots, ModuleDef, Injector}
 
 class A(val b: B)
 class B(val a: A) 
 class C(val c: C)
 
-val objects = Injector().produce(new ModuleDef {
+def module = new ModuleDef {
   make[A]
   make[B]
   make[C]
-}, Roots.Everything).unsafeGet()
+}
+
+val objects = Injector().produce(module, Roots(DIKey[A], DIKey[C])).unsafeGet()
 
 objects.get[A] eq objects.get[B].a
+
 objects.get[B] eq objects.get[A].b
+
 objects.get[C] eq objects.get[C].c
 ```
 
 #### Automatic Resolution with generated proxies
 
-The above strategy depends on `distage-core-proxy-cglib` module which is a default dependency of `distage-core`.
+The above strategy depends on `distage-core-proxy-cglib` module and is enabled by default.
 
 If you want to disable it, use `NoProxies` bootstrap configuration:
 
@@ -123,7 +127,7 @@ Proxies are not supported on Scala.js.
 Most cycles can be resolved without proxies, using By-Name parameters:
 
 ```scala mdoc:reset:to-string
-import distage.{Roots, ModuleDef, Injector}
+import distage.{DIKey, Roots, ModuleDef, Injector}
 
 class A(b0: => B) {
   def b: B = b0
@@ -137,7 +141,7 @@ class C(self: => C) {
   def c: C = self
 }
 
-val module = new ModuleDef {
+def module = new ModuleDef {
   make[A]
   make[B]
   make[C]
@@ -146,11 +150,13 @@ val module = new ModuleDef {
 // disable proxies and execute the module
 
 val locator = Injector.NoProxies()
-  .produce(module, Roots.Everything)
+  .produce(module, Roots(DIKey[A], DIKey[C]))
   .unsafeGet()
 
 locator.get[A].b eq locator.get[B]
+
 locator.get[B].a eq locator.get[A]
+
 locator.get[C].c eq locator.get[C]
 ```
 
@@ -160,56 +166,6 @@ out of control of the origin module.
 Note: Currently a limitation applies to by-names - ALL dependencies of a class engaged in a by-name circular dependency must
 be by-name, otherwise `distage` will revert to generating proxies.
 
-### Auto-Sets
-
-AutoSet @scaladoc[Planner](izumi.distage.model.Planner) Hooks can traverse the plan and collect all future objects that match a predicate.
-
-Using Auto-Sets you can e.g. collect all `AutoCloseable` classes and `.close()` them after the application has finished work.
-NOTE: please use @ref[Resource bindings](basics.md#resource-bindings-lifecycle) for real lifecycle, this is just an example.
-
-```scala mdoc:reset:to-string
-import distage.{BootstrapModuleDef, ModuleDef, Injector, Roots}
-import izumi.distage.model.planning.PlanningHook
-import izumi.distage.planning.AutoSetHook
-
-class PrintResource(name: String) {
-  def start(): Unit = println(s"$name started")
-  def stop(): Unit = println(s"$name stopped")
-}
-
-class A extends PrintResource("A")
-class B(val a: A) extends PrintResource("B")
-class C(val b: B) extends PrintResource("C")
-
-val bootstrapModule = new BootstrapModuleDef {
-  many[PlanningHook]
-    .add(new AutoSetHook[PrintResource, PrintResource])
-}
-
-val appModule = new ModuleDef {
-  make[A]
-  make[B]
-  make[C]
-}
-
-val resources = Injector(bootstrapModule)
-  .produce(appModule, Roots.Everything)
-  .use(_.get[Set[PrintResource]])
-
-resources.foreach(_.start())
-resources.toSeq.reverse.foreach(_.stop())
-```
-
-Calling `.foreach` on an auto-set is safe; the actions will be executed in order of dependencies.
-Auto-Sets preserve ordering, unlike user-defined @ref[Sets](basics.md#set-bindings).
-e.g. If `C` depends on `B` depends on `A`, autoset order is: `A, B, C`, to start call: `A, B, C`, to close call: `C, B, A`.
-When you use auto-sets for finalization, you **must** `.reverse` the autoset.
-
-Note: Auto-Sets are NOT subject to @ref[Garbage Collection](advanced-features.md#garbage-collection), they are assembled *after* garbage collection is done,
-as such they can't contain garbage by construction. Because of that they also cannot be used as GC Roots.
-
-See also: same concept in [MacWire](https://github.com/softwaremill/macwire#multi-wiring-wireset)
-
 ### Weak Sets
 
 @ref[Set bindings](basics.md#set-bindings) can contain *weak* references. References designated as weak will
@@ -218,7 +174,7 @@ be retained *only* if there are *other* dependencies on the referred bindings, *
 Example:
 
 ```scala mdoc:reset-object:to-string
-import distage._
+import distage.{Roots, ModuleDef, Injector}
 
 sealed trait Elem
 
@@ -230,7 +186,7 @@ final case class Weak() extends Elem {
   println("Weak constructed")
 }
 
-val module = new ModuleDef {
+def module = new ModuleDef {
   make[Strong]
   make[Weak]
   
@@ -243,9 +199,9 @@ val module = new ModuleDef {
 // everything that Set[Elem] does not strongly depend on will be garbage collected
 // and will not be constructed. 
 
-val roots = Set[DIKey](DIKey[Set[Elem]])
+val roots = Roots.target[Set[Elem]]
 
-val objects = Injector().produce(PlannerInput(module, Activation.empty, roots)).unsafeGet()
+val objects = Injector().produce(module, roots).unsafeGet()
 
 // Strong is around
 
@@ -266,7 +222,7 @@ The `Strong` class remained, because the reference to it was **strong**, so it w
 If we change `Strong` to depend on the `Weak`, then `Weak` will be retained:
 
 ```scala mdoc:reset-object:invisible:to-string
-import distage._
+import distage.{Roots, ModuleDef, Injector}
 
 sealed trait Elem
 
@@ -283,7 +239,7 @@ def module = new ModuleDef {
     .weak[Weak]
 }
 
-val roots = Set[DIKey](DIKey[Set[Elem]])
+val roots = Roots.target[Set[Elem]]
 ```
 
 ```scala mdoc:to-string
@@ -291,7 +247,7 @@ final class Strong(weak: Weak) extends Elem {
   println("Strong constructed")
 }
 
-val objects = Injector().produce(PlannerInput(module, Activation.empty, roots)).unsafeGet()
+val objects = Injector().produce(module, roots).unsafeGet()
 
 // Weak is around
 
@@ -302,92 +258,64 @@ objects.find[Weak]
 objects.get[Set[Elem]]
 ```
 
-### Inner Classes and Path-Dependent Types
+### Auto-Sets
 
-Path-dependent types with a value prefix will be instantiated normally:
+AutoSet @scaladoc[Planner](izumi.distage.model.Planner) Hooks can traverse the plan and collect all future objects that match a predicate.
 
-```scala mdoc:reset:to-string
-import distage.{Roots, ModuleDef, Injector}
+Using Auto-Sets you can e.g. collect all `AutoCloseable` classes and `.close()` them after the application has finished work.
+NOTE: please use @ref[Resource bindings](basics.md#resource-bindings-lifecycle) for real lifecycle, this is just an example.
 
-class Path {
-  class A
-}
-val path = new Path
+```scala mdoc:reset:to-string
+import distage.{BootstrapModuleDef, ModuleDef, Injector, Roots}
+import izumi.distage.model.planning.PlanningHook
+import izumi.distage.planning.AutoSetHook
 
-def module = new ModuleDef {
-  make[path.A]
+class PrintResource(name: String) {
+  def start(): Unit = println(s"$name started")
+  def stop(): Unit = println(s"$name stopped")
 }
 
-Injector()
-  .produce(module, Roots.Everything)
-  .use(_.get[path.A])
-```
-
-Since version `0.10`, support for path-dependent types with a non-value (type) prefix hasn't reimplemented after a rewrite of the internals, see issue: https://github.com/7mind/izumi/issues/764
-
-However, there's a gotcha with value prefixes, when seen by distage they're based on the **literal variable name** of the prefix,
-not the full type information available to the compiler, therefore the following usage will fail:
+class A extends PrintResource("A")
+class B(val a: A) extends PrintResource("B")
+class C(val b: B) extends PrintResource("C")
 
-```scala mdoc:to-string
-def pathModule(p: Path) = new ModuleDef {
-  make[p.A]
+def bootstrapModule = new BootstrapModuleDef {
+  many[PlanningHook]
+    .add(new AutoSetHook[PrintResource, PrintResource])
 }
 
-val path1 = new Path
-val path2 = new Path
-```
-
-```scala mdoc:to-string:crash
-Injector()
-  .produceRun(pathModule(path1) ++ pathModule(path2)) {
-    (p1a: path1.A, p2a: path2.A) =>
-      println((p1a, p2a))
-  }
-```
-
-This will fail because while `path1.A` and `p.A` inside `new ModuleDef` are the same type, the varialbes
-`path1` & `p` are spelled differently and this causes a mismatch.
+def appModule = new ModuleDef {
+  make[A]
+  make[B]
+  make[C]
+}
 
-There's one way to workaround this - turn the type member `A` into a type parameter using the [Aux Pattern](http://gigiigig.github.io/posts/2015/09/13/aux-pattern.html),
-and then for that type parameter in turn, summon the type information using `Tag` implicit (as described in @ref[Tagless Final Style chapter](basics.md#tagless-final-style))
-and summon the constructor using the `ClassConstructor` implicit, example:
+val resources = Injector(bootstrapModule)
+  .produceGet[Set[PrintResource]](appModule)
+  .use(set => set)
 
-```scala mdoc:to-string:reset:invisible
-class Path {
-  class A
-}
+resources.foreach(_.start())
+resources.toSeq.reverse.foreach(_.stop())
 ```
 
-```scala mdoc:to-string
-import distage.{ClassConstructor, ModuleDef, Injector, Tag}
+Calling `.foreach` on an auto-set is safe; the actions will be executed in order of dependencies - Auto-Sets preserve ordering, unlike user-defined @ref[Sets](basics.md#set-bindings)
 
-object Path {
-  type Aux[A0] = Path { type A = A0 }
-}
+e.g. If `C` depends on `B` depends on `A`, autoset order is: `A, B, C`, to start call: `A, B, C`, to close call: `C, B, A`.  When using an auto-set for finalization, you must `.reverse` the autoset.
 
-def pathModule[A: Tag: ClassConstructor](p: Path.Aux[A]) = new ModuleDef {
-  make[A]
-}
+Note: Auto-Sets are assembled *after* @ref[Garbage Collection](advanced-features.md#dependency-pruning), they are assembled *after* garbage collection is done,
+as such they can't contain garbage by construction. Because of that they also cannot be used as GC Roots.
 
-val path1 = new Path
-val path2 = new Path
+Further reading:
 
-Injector()
-  .produceRun(pathModule(path1) ++ pathModule(path2)) {
-    (p1a: path1.A, p2a: path2.A) =>
-      println((p1a, p2a))
-  }
-```
+- MacWire calls the same concept ["Multi Wiring"](https://github.com/softwaremill/macwire#multi-wiring-wireset)
 
-Now the example works, because the `A` inside `pathModule` is `path1.A` & `path2.A` respectively, the same as it is later
-in `produceRun`
 
 ### Depending on Locator
 
 Objects can depend on the outer object graph that contains them (@scaladoc[Locator](izumi.distage.model.Locator)), by including a @scaladoc[LocatorRef](izumi.distage.model.recursive.LocatorRef) parameter:
 
 ```scala mdoc:reset:to-string
-import distage.{ModuleDef, LocatorRef, Injector, Roots}
+import distage.{DIKey, ModuleDef, LocatorRef, Injector, Roots}
 
 class A(
   objects: LocatorRef
@@ -403,7 +331,7 @@ def module = new ModuleDef {
   make[C]
 }
 
-val objects = Injector().produce(module, Roots.Everything).unsafeGet()
+val objects = Injector().produce(module, Roots(DIKey[A], DIKey[B], DIKey[C])).unsafeGet()
 
 // A took C from the object graph
 
@@ -412,6 +340,7 @@ objects.get[A].c
 // this C is the same C as in this `objects` value
 
 val thisC = objects.get[C]
+
 val thatC = objects.get[A].c
 
 assert(thisC == thatC)
@@ -431,19 +360,38 @@ val plan: OrderedPlan = objects.plan
 val bindings: ModuleBase = plan.definition
 ```
 
-The plan and bindings in Locator are saved in the state they were AFTER @ref[Garbage Collection](#garbage-collection) has been performed.
+#### Injector inheritance
+
+You may run a new planning cycle, inheriting the instances from an existing `Locator` into your new object subgraph:
+
+```scala mdoc:to-string
+val childInjector = Injector.inherit(objects)
+
+class Printer(a: A, b: B, c: C) {
+  def printEm() = 
+    println(s"I've got A=$a, B=$b, C=$c, all here!")
+}
+
+childInjector.produceRun(new ModuleDef { make[Printer] }) {
+  (_: Printer).printEm()
+}
+```
+
+#### Bootloader
+
+The plan and bindings in Locator are saved in the state they were AFTER @ref[Garbage Collection](#dependency-pruning) has been performed.
 Objects can request the original input via a `PlannerInput` parameter:
 
 ```scala mdoc:reset:to-string
-import distage.{DIKey, Roots, ModuleDef, PlannerInput, Injector, Activation}
+import distage.{Roots, ModuleDef, PlannerInput, Injector, Activation}
 
 class InjectionInfo(val plannerInput: PlannerInput)
 
-val module = new ModuleDef {
+def module = new ModuleDef {
   make[InjectionInfo]
 }
 
-val input = PlannerInput(module, Activation.empty, Roots(root = DIKey[InjectionInfo]))
+val input = PlannerInput(module, Activation.empty, Roots.target[InjectionInfo])
 
 val injectionInfo = Injector().produce(input).unsafeGet().get[InjectionInfo]
 
@@ -452,5 +400,86 @@ val injectionInfo = Injector().produce(input).unsafeGet().get[InjectionInfo]
 assert(injectionInfo.plannerInput == input)
 ```
 
-@scaladoc[Bootloader](izumi.distage.model.recursive.Bootloader) is another summonable parameter that contains the above information in aggregate
-and lets you create another object graph from the same inputs as the current or with alterations.
+@scaladoc[Bootloader](izumi.distage.model.recursive.Bootloader) is another summonable parameter that contains the above information in aggregate and lets you create another object graph from the same inputs as the current or with alterations.
+
+
+### Inner Classes and Path-Dependent Types
+
+Path-dependent types with a value prefix will be instantiated normally:
+
+```scala mdoc:reset:to-string
+import distage.{Roots, ModuleDef, Injector}
+
+class Path {
+  class A
+}
+val path = new Path
+
+def module = new ModuleDef {
+  make[path.A]
+}
+
+Injector()
+  .produce(module, Roots.Everything)
+  .use(_.get[path.A])
+```
+
+Since version `0.10`, support for types with a non-value prefix (type projections) [has been dropped](https://github.com/7mind/izumi/issues/764).
+
+However, there's a gotcha with value prefixes, when seen by distage they're based on the **literal variable name** of the prefix,
+not the full type information available to the compiler, therefore the following usage, a simple rename, will fail:
+
+```scala mdoc:to-string
+def pathModule(p: Path) = new ModuleDef {
+  make[p.A]
+}
+
+val path1 = new Path
+val path2 = new Path
+```
+
+```scala mdoc:invisible:to-string
+import scala.util.Try
+```
+
+```scala mdoc:to-string
+Try {
+  Injector().produceRun(pathModule(path1) ++ pathModule(path2)) {
+    (p1a: path1.A, p2a: path2.A) =>
+      println((p1a, p2a))
+  }
+}.isFailure
+```
+
+This will fail because while `path1.A` and `p.A` inside `new ModuleDef` are the same type as far as *Scala* is concerned, the variables
+`path1` & `p` are spelled differently and this causes a mismatch in `distage`.
+
+There's one way to workaround this - turn the type member `A` into a type parameter using the [Aux Pattern](http://gigiigig.github.io/posts/2015/09/13/aux-pattern.html), and then for that type parameter in turn, summon the type information using `Tag` implicit (as described in @ref[Tagless Final Style chapter](basics.md#tagless-final-style)) and summon the constructor using the `ClassConstructor` implicit, example:
+
+```scala mdoc:to-string:reset:invisible
+class Path {
+  class A
+}
+```
+
+```scala mdoc:to-string
+import distage.{ClassConstructor, ModuleDef, Injector, Tag}
+
+object Path {
+  type Aux[A0] = Path { type A = A0 }
+}
+
+def pathModule[A: Tag: ClassConstructor](p: Path.Aux[A]) = new ModuleDef {
+  make[A]
+}
+
+val path1 = new Path
+val path2 = new Path
+
+Injector().produceRun(pathModule(path1) ++ pathModule(path2)) {
+  (p1a: path1.A, p2a: path2.A) =>
+    println((p1a, p2a))
+}
+```
+
+Now the example works, because the `A` type inside `pathModule(path1)` is `path1.A` and for `pathModule(path2)` it's `path2.A`, which matches their subsequent spelling in `(p1a: path1.A, p2a: path2.A) =>` in `produceRun`
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index ebc923afb..fa3830ab3 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -1,16 +1,18 @@
-Basics
-======
+Introduction
+============
 
 @@toc { depth=2 }
 
 ### Quick Start
 
+#### Hello World example
+
 Suppose we have an abstract `Greeter` component, and some other components that depend on it:
 
 ```scala mdoc:reset:invisible:to-string
 var counter = 0
 val names = Array("izumi", "kai", "Pavel")
-def readLine() = {
+def HACK_OVERRIDE_getStrLn = zio.ZIO {
   val n = names(counter % names.length)
   counter += 1
   println(s"> $n")
@@ -18,32 +20,39 @@ def readLine() = {
 }
 ```
 
-```scala mdoc:to-string
-import distage.{ModuleDef, Injector, Roots, Activation}
+```scala mdoc:override:to-string
+import zio.RIO
+import zio.console.{Console, getStrLn, putStrLn}
 
 trait Greeter {
-  def hello(name: String): Unit
+  def hello(name: String): RIO[Console, Unit]
 }
 
 final class PrintGreeter extends Greeter {
-  override def hello(name: String) = println(s"Hello $name!") 
+  override def hello(name: String) = 
+    putStrLn(s"Hello $name!") 
 }
 
 trait Byer {
-  def bye(name: String): Unit
+  def bye(name: String): RIO[Console, Unit]
 }
 
 final class PrintByer extends Byer {  
-  override def bye(name: String) = println(s"Bye $name!")
+  override def bye(name: String) = 
+    putStrLn(s"Bye $name!")
 }
 
-final class HelloByeApp(greeter: Greeter, byer: Byer) {
-  def run(): Unit = {
-    println("What's your name?")
-    val name = readLine()
-    
-    greeter.hello(name)
-    byer.bye(name)
+final class HelloByeApp(
+  greeter: Greeter, 
+  byer: Byer,
+) {
+  def run: RIO[Console, Unit] = {
+    for {
+      _    <- putStrLn("What's your name?") 
+      name <- HACK_OVERRIDE_getStrLn
+      _    <- greeter.hello(name)
+      _    <- byer.bye(name) 
+    } yield ()
   }
 }
 ```
@@ -52,6 +61,8 @@ To actually run the `HelloByeApp`, we have to wire implementations of `Greeter`
 We will not do it directly. First we'll only declare the component interfaces we have, and the implementations we want for them:
 
 ```scala mdoc:to-string
+import distage.ModuleDef
+
 val HelloByeModule = new ModuleDef {
   make[Greeter].from[PrintGreeter]
   make[Byer].from[PrintByer]
@@ -61,47 +72,79 @@ val HelloByeModule = new ModuleDef {
 
 `ModuleDef` merely contains a description of the desired object graph, let's transform that high-level description into an
 actionable series of steps - an @scaladoc[OrderedPlan](izumi.distage.model.plan.OrderedPlan), a datatype we can
-@ref[inspect](debugging.md#pretty-printing-plans), @ref[test](debugging.md#testing-plans) or @ref[verify at compile-time](distage-framework.md#compile-time-checks) – without actually creating any objects or executing any effects.
+@ref[inspect](debugging.md#pretty-printing-plans), @ref[test](debugging.md#testing-plans) or @ref[verify at compile-time](distage-framework.md#compile-time-checks) – without having to actually create objects or execute effects.
 
 ```scala mdoc:to-string
-val plan = Injector().plan(HelloByeModule, Activation.empty, Roots.target[HelloByeApp])
+import distage.{Activation, Injector, Roots}
+
+val injector = Injector[RIO[Console, *]]()
+
+val plan = injector.plan(HelloByeModule, Activation.empty, Roots.target[HelloByeApp])
 ```
 
-The series of steps must be executed to produce the object graph. `Injector.produce` will interpret the steps into a @ref[Resource](basics.md#resource-bindings-lifecycle) value, that holds the lifecycle of the object graph:
+The series of steps must be executed to produce the object graph.
+
+`Injector.produce` will interpret the steps into a @ref[`Lifecycle`](basics.md#resource-bindings-lifecycle) value holding the lifecycle of the object graph:
 
 ```scala mdoc:to-string
-// Interpret into Lifecycle
+import zio.Runtime.default.unsafeRun
+
+// Interpret into a Lifecycle value
 
-val resource = Injector().produce(plan)
+val resource = injector.produce(plan)
 
 // Use the object graph:
 // After `.use` exits, all objects will be deallocated,
 // and all allocated resources will be freed.
 
-resource.use {
+val effect = resource.use {
   objects =>
-    objects.get[HelloByeApp].run()
+    objects.get[HelloByeApp].run
 }
+
+// Run the resulting program
+
+unsafeRun(effect)
 ```
 
-`distage` always creates components exactly once, even if multiple other objects depend on them. Coming from other DI frameworks, you may think it's as if there is only a "Singleton" scope. It's impossible to create non-singletons in `distage`.
-If you need multiple singleton instances of the same type, you can create `named` instances and disambiguate between them using `@Id` annotation. (`javax.inject.Named` is also supported)
+`distage` always creates components exactly once, even if multiple other objects depend on them. Coming from other DI frameworks, you may think of it as if there's only a "Singleton" scope. It's impossible to create non-singletons in `distage`.
 
-```scala mdoc:to-string
+#### Named instances
+
+If you need multiple singleton instances of the same type, you may create "named" instances and disambiguate between them using @scaladoc[`@distage.Id`](izumi.distage.model.definition.Id) annotation. (`javax.inject.Named` is also supported)
+
+```scala mdoc:silent
 import distage.Id
 
+def negateByer(otherByer: Byer): Byer = {
+  new Byer {
+    def bye(name: String) =   
+     otherByer.bye(s"NOT-$name")
+  }
+}
+
 new ModuleDef {
   make[Byer].named("byer-1").from[PrintByer]
   make[Byer].named("byer-2").from {
     otherByer: Byer @Id("byer-1") =>
-      new Byer {
-        def bye(name: String) = otherByer.bye(s"NOT-$name")
-      }
+      negateByer(otherByer)
   }
 }
 ```
 
-You can abstract over annotations using type aliases or string constants:
+
+You can use `make[_].annotateParameter` method instead of an annotation, to attach a name component to an existing constructor:
+
+```scala mdoc:silent
+new ModuleDef {
+  // same binding as above
+  make[Byer].named("byer-2")
+    .from(negateByer(_))
+    .annotateParameter[Byer]("byer-1")
+}
+```
+
+You can also abstract over annotations using type aliases or string constants:
 
 ```scala mdoc:to-string
 object Ids {
@@ -110,20 +153,48 @@ object Ids {
 }
 ```
 
-To create fully non-singleton components you must use explicit factory classes. You can use @ref[Auto-Factories](#auto-factories) to auto-generate implementations for these factories.
+#### Non-singleton components
+
+To create new non-singleton instances you must use explicit factories. `distage`'s @ref[Auto-Factories](#auto-factories) can generate implementations for your factories, removing the associated boilerplate.
+
+While Auto-Factories may remove the boilerplate of passing the singleton parts of the graph to your new non-singleton components along with dynamic arguments,
+if you absolutely must wire a new non-trivial subgraph in a dynamic scope you'll need to run `Injector` again in your scope. 
+
+You may use `Injector.inherit` to obtain access to your outer object graph in your new sub-graph. It's safe to run `Injector` multiple times in nested scopes, as it's extremely fast, not least due to total absence of runtime reflection. See @ref[Injector Inheritance](advanced-features.md#injector-inheritance)
+
+#### Real-world example
+
+Check out [`distage-example` project](https://github.com/7mind/distage-example) for a complete example built using `distage`, tagless final, `http4s`, `doobie` and `zio` libraries.
+
+It shows how to write an idiomatic `distage`-style from scratch and how to:
+
+- write tests using @ref[`distage-testkit`](distage-testkit.md)
+- setup portable, zero-setup test environments using @ref[`distage-framework-docker`](distage-framework-docker.md)
+- create @ref[role-based applications](distage-framework.md#roles)
+- enable @ref[compile-time checks](distage-framework.md) for fast-feedback on wiring errors
+
+```scala mdoc:invisible
+/**
+add to distage-example
+
+- how to setup graalvm native image with distage
+- how to debug dump graphs and render to graphviz [Actually, we have a GUI component now, can we show em there???]
+*/
+```
 
 ### Activation Axis
 
-You can choose between different implementations of a component using `Axis` tags:
+You can choose between different implementations of a component using "Activation axis":
 
 ```scala mdoc:to-string
-import distage.{Axis, Activation, ModuleDef, Injector, Roots}
+import distage.{Axis, Activation, ModuleDef, Injector}
 
 class AllCapsGreeter extends Greeter {
-  def hello(name: String) = println(s"HELLO ${name.toUpperCase}")
+  def hello(name: String) = 
+    putStrLn(s"HELLO ${name.toUpperCase}")
 }
 
-// declare the configuration axis for our components
+// declare a configuration axis for our components
 
 object Style extends Axis {
   case object AllCaps extends AxisValueDef
@@ -133,7 +204,7 @@ object Style extends Axis {
 // Declare a module with several implementations of Greeter
 // but in different environments
 
-val TwoImplsModule = new ModuleDef {
+def TwoImplsModule = new ModuleDef {
   make[Greeter].tagged(Style.Normal)
     .from[PrintGreeter]
   
@@ -144,28 +215,37 @@ val TwoImplsModule = new ModuleDef {
 // Combine previous `HelloByeModule` with our new module
 // While overriding `make[Greeter]` bindings from the first module 
 
-val CombinedModule = HelloByeModule overriddenBy TwoImplsModule
+def CombinedModule = HelloByeModule overriddenBy TwoImplsModule
 
 // Choose component configuration when making an Injector:
-Injector()
-  .produceGet[HelloByeApp](CombinedModule, Activation(Style -> Style.AllCaps))
-  .use(_.run())
+
+unsafeRun {
+  Injector()
+    .produceGet[HelloByeApp](CombinedModule, Activation(Style -> Style.AllCaps))
+    .use(_.run)
+}
 
 // Check that result changes with a different configuration:
 
-Injector()
-  .produceGet[HelloByeApp](CombinedModule, Activation(Style -> Style.Normal))
-  .use(_.run())
+unsafeRun {
+  Injector()
+    .produceGet[HelloByeApp](CombinedModule, Activation(Style -> Style.Normal))
+    .use(_.run)
+}
 ```
 
-@scaladoc[distage.StandardAxis](izumi.distage.model.definition.StandardAxis) contains bundled Axes for back-end development: 
+@scaladoc[distage.StandardAxis](izumi.distage.model.definition.StandardAxis$) contains bundled Axes for back-end development: 
 
-- @scaladoc[Repo](izumi.distage.model.definition.StandardAxis$$Repo) axis with `Prod`/`Dummy` choices
-- @scaladoc[Mode](izumi.distage.model.definition.StandardAxis$$Mode) axis with `Prod`/`Test` choices
-- @scaladoc[World](izumi.distage.model.definition.StandardAxis$$World) axis with `Real`/`Mock` choices
-- @scaladoc[Scene](izumi.distage.model.definition.StandardAxis$$Scene) axis with `Managed`/`Provided` choices.
+- @scaladoc[Repo](izumi.distage.model.definition.StandardAxis$$Repo$) axis, with `Prod`/`Dummy` choices, describes any entities which may store and persist state or "repositories". e.g. databases, message queues, KV storages, file systems, etc. Those may typically have both in-memory `Dummy` implementations and heavyweight `Prod` implementations using external databases.
+  
+- @scaladoc[Mode](izumi.distage.model.definition.StandardAxis$$Mode$) axis, with `Prod`/`Test` choices, describes a generic choice between production and test implementations of a component.
+  
+- @scaladoc[World](izumi.distage.model.definition.StandardAxis$$World$) axis, with `Real`/`Mock` choices, describes third-party integrations which are not controlled by the application and provided "as is". e.g. Facebook API, Google API, etc. those may contact a `Real` external integration or a `Mock` one with predefined responses.
+  
+- @scaladoc[Scene](izumi.distage.model.definition.StandardAxis$$Scene$) axis with `Managed`/`Provided` choices, describes whether external services required by the application should be set-up on the fly by an orchestrator library such as @ref[`distage-framework-docker`](distage-framework-docker.md) (`Scene.Managed`), or whether the application should try to connect to external services as if they already exist in the environment (`Scene.Provided`). 
+  We call a set of external services required by the application a `Scene`, etymology being that the running external services required by the application are like a "scene" that the "theatre staff" (the orchestrator) must prepare for the "actor" (the application) to enter.
 
-In `distage-framework`'s @scaladoc[RoleAppLauncher](izumi.distage.roles.RoleAppLauncher), you can choose axes using the `-u` command-line parameter:
+In `distage-framework`'s @scaladoc[RoleAppMain](izumi.distage.roles.RoleAppMain), you can choose axes using the `-u` command-line parameter:
 
 ```
 ./launcher -u repo:dummy -u env:prod app1
@@ -179,10 +259,12 @@ import izumi.distage.testkit.TestConfig
 import izumi.distage.testkit.scalatest.DistageBIOSpecScalatest
 
 class AxisTest extends DistageBIOSpecScalatest[zio.IO] {
-  override protected def config: TestConfig = super.config.copy(
-    // choose implementations `.tagged` as `Repo.Dummy` over those tagged `Repo.Prod`
+
+  // choose implementations `.tagged` as `Repo.Dummy` over those tagged `Repo.Prod`
+  override def config: TestConfig = super.config.copy(
     activation = Activation(Repo -> Repo.Dummy)
   )
+  
 }
 ```
 
@@ -191,39 +273,40 @@ class AxisTest extends DistageBIOSpecScalatest[zio.IO] {
 There may be many configuration axes in an application and components can specify multiple axis choices at once:
 
 ```scala mdoc:to-string
-import distage.StandardAxis.Env
+import distage.StandardAxis.Mode
 
 class TestPrintGreeter extends Greeter {
-  def hello(name: String) = println(s"Test 1 2, hello $name")
+  def hello(name: String) = 
+    putStrLn(s"Test 1 2, hello $name")
 }
 
 // declare 3 possible implementations
 
 val TestModule = new ModuleDef {
-  make[Greeter].tagged(Style.Normal, Env.Prod).from[PrintGreeter]
-  make[Greeter].tagged(Style.Normal, Env.Test).from[TestPrintGreeter]
+  make[Greeter].tagged(Style.Normal, Mode.Prod).from[PrintGreeter]
+  make[Greeter].tagged(Style.Normal, Mode.Test).from[TestPrintGreeter]
   make[Greeter].tagged(Style.AllCaps).from[AllCapsGreeter]
 }
 
-def runWith(activation: Activation) =
+def runWith(activation: Activation) = unsafeRun {
   Injector().produceRun(TestModule, activation) {
     greeter: Greeter => greeter.hello("$USERNAME")
   }
+}
 
 // Production Normal Greeter
 
-runWith(Activation(Style -> Style.Normal, Env -> Env.Prod))
+runWith(Activation(Style -> Style.Normal, Mode -> Mode.Prod))
 
 // Test Normal Greeter
 
-runWith(Activation(Style -> Style.Normal, Env -> Env.Test))
+runWith(Activation(Style -> Style.Normal, Mode -> Mode.Test))
 
 // Both Production and Test Caps Greeters are the same:
 
-runWith(Activation(Style -> Style.AllCaps, Env -> Env.Prod))
-
-runWith(Activation(Style -> Style.AllCaps, Env -> Env.Test))
+runWith(Activation(Style -> Style.AllCaps, Mode -> Mode.Prod))
 
+runWith(Activation(Style -> Style.AllCaps, Mode -> Mode.Test))
 ```
 
 ### Resource Bindings, Lifecycle
@@ -264,7 +347,6 @@ class MyApp(db: DBConnection, mq: MessageQueueConnection) {
 val module = new ModuleDef {
   make[DBConnection].fromResource(dbResource)
   make[MessageQueueConnection].fromResource(mqResource)
-  addImplicit[Bracket[IO, Throwable]]
   make[MyApp]
 }
 ```
@@ -284,7 +366,7 @@ objectGraphResource
 Lifecycle management with `Lifecycle` is also available without an effect type, via `Lifecycle.Simple` and `Lifecycle.Mutable`:
 
 ```scala mdoc:reset:to-string
-import distage.{Lifecycle, Roots, ModuleDef, Injector}
+import distage.{Lifecycle, ModuleDef, Injector}
 
 class Init {
   var initialized = false
@@ -348,8 +430,12 @@ Therefore, you can convert an expression based resource-constructor such as:
 import distage.Lifecycle, cats.Monad
 
 class A
+
 object A {
-  def resource[F[_]](implicit F: Monad[F]): Lifecycle[F, A] = Lifecycle.pure[F, A](new A)
+
+  def resource[F[_]: Monad]: Lifecycle[F, A] =
+    Lifecycle.pure[F, A](new A)
+    
 }
 ```
 
@@ -359,11 +445,14 @@ Into class-based form:
 import distage.Lifecycle, cats.Monad
 
 class A
+
 object A {
-final class Resource[F[_]](implicit F: Monad[F])
-  extends Lifecycle.Of(
-    Lifecycle.pure[F, A](new A)
-  )
+
+  final class Resource[F[_]: Monad]
+    extends Lifecycle.Of(
+      Lifecycle.pure[F, A](new A)
+    )
+    
 }
 ```
 
@@ -384,7 +473,7 @@ Everywhere where a collection of components is required, a Set Binding is approp
 
 To define a Set binding use `.many` and `.add` methods of the @scaladoc[ModuleDef](izumi.distage.model.definition.ModuleDef) DSL.
 
-As an example, we may declare multiple command handlers and use them to interpret user input in a REPLL
+As an example, we may declare multiple command handlers and use them to interpret user input in a REPL
 
 
 ```scala mdoc:reset:to-string
@@ -405,7 +494,9 @@ object AdditionModule extends ModuleDef {
 ```
 
 We've used `many` method to declare an open `Set` of command handlers and then added one handler to it.
+
 When module definitions are combined, elements for the same type of `Set` will be merged together into a larger set.
+
 You can summon a Set binding by summoning a scala `Set`, as in `Set[CommandHandler]`.
 
 Let's define a new module with another handler:
@@ -421,8 +512,7 @@ object SubtractionModule extends ModuleDef {
 }
 ```
 
-Now it's the time to define our main flow that exposes all the different command handlers as a command-line application:
-
+Let's create a command-line application using our command handlers:
 
 ```scala mdoc:invisible:to-string
 implicit final class InjRun_NO_IDENTITY_TYPE_IN_OUTPUT(inj: distage.Injector[izumi.fundamentals.platform.functional.Identity]) {
@@ -481,7 +571,7 @@ app.interpret("1 / 3")
 app.interpret("help")
 ```
 
-If we wire the app while excluding `SubtractionModule` our app will expectedly lose the ability to subtract:
+If we rewire the app without `SubtractionModule`, it will expectedly lose the ability to subtract:
 
 ```scala mdoc:override:to-string
 Injector().HACK_OVERRIDE_produceRun(AppModule -- SubtractionModule.keys) {
@@ -490,7 +580,9 @@ Injector().HACK_OVERRIDE_produceRun(AppModule -- SubtractionModule.keys) {
 }
 ```
 
-Further reading: the same concept is called [Multibindings](https://github.com/google/guice/wiki/Multibindings) in Guice.
+Further reading:
+
+- Guice calls the same concept ["Multibindings"](https://github.com/google/guice/wiki/Multibindings).
 
 ### Effect Bindings
 
@@ -503,8 +595,8 @@ In these cases we can use `.fromEffect` to create a value using an effectful con
 Example with a `Ref`-based Tagless Final `KVStore`:
 
 ```scala mdoc:reset:to-string
-import distage.{Roots, ModuleDef, Injector}
-import izumi.functional.bio.{BIOMonadError, BIOPrimitives, F}
+import distage.{ModuleDef, Injector}
+import izumi.functional.bio.{BIOError, BIOPrimitives, F}
 import zio.{Task, IO}
 
 trait KVStore[F[_, _]] {
@@ -512,7 +604,7 @@ trait KVStore[F[_, _]] {
   def put(key: String, value: String): F[Nothing, Unit]
 }
 
-def dummyKVStore[F[+_, +_]: BIOMonadError: BIOPrimitives]: F[Nothing, KVStore[F]] = {
+def dummyKVStore[F[+_, +_]: BIOError: BIOPrimitives]: F[Nothing, KVStore[F]] = {
   for {
     ref <- F.mkRef(Map.empty[String, String])
   } yield new KVStore[F] {
@@ -600,10 +692,9 @@ def module2 = new ModuleDef {
 Example:
 
 ```scala mdoc:reset:to-string
-import distage.{DIKey, ModuleDef, Injector, Functoid, Tag}
-import izumi.distage.constructors.TraitConstructor
+import distage.{ModuleDef, Injector}
 import zio.console.{putStrLn, Console}
-import zio.{UIO, URIO, ZIO, Ref, Task, Has}
+import zio.{UIO, URIO, Ref, Task, Has}
 
 trait Hello {
   def hello: UIO[String]
@@ -615,9 +706,9 @@ trait World {
 // Environment forwarders that allow
 // using service functions from everywhere
 
-val hello: URIO[Has[Hello], String] = ZIO.accessM(_.get.hello)
+val hello: URIO[Has[Hello], String] = URIO.accessM(_.get.hello)
 
-val world: URIO[Has[World], String] = ZIO.accessM(_.get.world)
+val world: URIO[Has[World], String] = URIO.accessM(_.get.world)
 
 // service implementations
 
@@ -661,20 +752,24 @@ val main = Injector[Task]()
 zio.Runtime.default.unsafeRun(main)
 ```
 
-#### Converting between ZIO environment dependencies and parameters
+#### Converting ZIO environment dependencies to parameters
 
 Any ZIO Service that requires an environment can be turned into a service without an environment dependency by providing
-the dependency in each method. This pattern can be generalized by implementing an instance of `cats.Contravariant` (or `cats.tagless.FunctorK`) for your services
-and using it to turn environment dependencies into constructor parameters – that way ZIO Environment can be used uniformly
+the dependency in each method using `.provide`.
+
+This pattern can be generalized by implementing an instance of `cats.Contravariant` (or `cats.tagless.FunctorK`) for your services 
+and using it to turn environment dependencies into constructor parameters.
+
+In that way ZIO Environment can be used uniformly
 for declaration of dependencies, but the dependencies used inside the service do not leak to other services calling it.
-Details: https://gitter.im/ZIO/Core?at=5dbb06a86570b076740f6db2
+See: https://gitter.im/ZIO/Core?at=5dbb06a86570b076740f6db2
 
 Example:
 
 ```scala mdoc:reset:to-string
 import cats.Contravariant
-import distage.{Roots, Injector, ModuleDef, Functoid, Tag, TagK, HasConstructor}
-import zio.{Task, UIO, URIO, ZIO, Has}
+import distage.{Injector, ModuleDef, Functoid, Tag, TagK, HasConstructor}
+import zio.{Task, UIO, URIO, Has}
 
 trait Dependee[-R] {
   def x(y: String): URIO[R, Int]
@@ -691,8 +786,12 @@ implicit val contra2: Contravariant[Depender] = new Contravariant[Depender] {
 
 type DependeeR = Has[Dependee[Any]]
 type DependerR = Has[Depender[Any]]
-object dependee extends Dependee[DependeeR] { def x(y: String) = ZIO.accessM(_.get.x(y)) }
-object depender extends Depender[DependerR] { def y            = ZIO.accessM(_.get.y) }
+object dependee extends Dependee[DependeeR] { 
+  def x(y: String) = URIO.accessM(_.get.x(y))
+}
+object depender extends Depender[DependerR] { 
+  def y = URIO.accessM(_.get.y) 
+}
 
 // cycle
 object dependerImpl extends Depender[DependeeR] {
@@ -808,9 +907,10 @@ Injector()
   }
 ```
 
+#### @impl annotation
+
 Abstract classes or traits without obvious concrete subclasses
-may hinder the readability of a codebase, if you still want to use them
-to avoid writing the full constructor, you may use an optional @scaladoc[@impl](izumi.distage.model.definition.impl) 
+may hinder the readability of a codebase, to mitigate that you may use an optional @scaladoc[@impl](izumi.distage.model.definition.impl) 
 documenting annotation to aid the reader in understanding your intention.
 
 ```scala mdoc:to-string
@@ -818,7 +918,11 @@ import distage.impl
 
 @impl abstract class Impl(
   pluser: Pluser
-) extends PlusedInt
+) extends PlusedInt with Trait1 with Trait2 {
+  override def result(): Int = {
+    pluser.plus(a, b)
+  }
+}
 ```
 
 ### Auto-Factories
@@ -828,7 +932,7 @@ All unimplemented methods _with parameters_ in a trait will be filled by factory
 
 Given a class `ActorFactory`:
 
-```scala mdoc:to-string
+```scala mdoc:reset:to-string
 import distage.ModuleDef
 import java.util.UUID
 
@@ -862,9 +966,11 @@ class ActorFactoryImpl(sessionStorage: SessionStorage) extends ActorFactory {
 }
 ```
 
+#### @With annotation
+
 `@With` annotation can be used to specify the implementation class, to avoid leaking the implementation type in factory method result:
 
-```scala mdoc:to-string:reset
+```scala mdoc:reset:to-string
 import distage.{ModuleDef, Injector, With}
 
 trait Actor { 
@@ -933,13 +1039,17 @@ trait Validation[F[_]] {
   def minSize(s: String, n: Int): F[Boolean]
   def hasNumber(s: String): F[Boolean]
 }
-def Validation[F[_]: Validation]: Validation[F] = implicitly
+object Validation {
+  def apply[F[_]: Validation]: Validation[F] = implicitly
+}
 
 trait Interaction[F[_]] {
   def tell(msg: String): F[Unit]
   def ask(prompt: String): F[String]
 }
-def Interaction[F[_]: Interaction]: Interaction[F] = implicitly
+object Interaction {
+  def apply[F[_]: Interaction]: Interaction[F] = implicitly
+}
 
 class TaglessProgram[F[_]: Monad: Validation: Interaction] {
   def program: F[Unit] = for {
@@ -952,7 +1062,6 @@ class TaglessProgram[F[_]: Monad: Validation: Interaction] {
 
 def ProgramModule[F[_]: TagK: Monad] = new ModuleDef {
   make[TaglessProgram[F]]
-  addImplicit[Monad[F]]
 }
 ```
 
@@ -978,7 +1087,6 @@ def SyncInterpreters[F[_]: TagK: Sync] = {
   new ModuleDef {
     make[Validation[F]].from[SyncValidation[F]]
     make[Interaction[F]].from[SyncInteraction[F]]
-    addImplicit[Sync[F]]
   }
 }
 
@@ -995,6 +1103,18 @@ val objectsResource = Injector[IO]().produce(SyncProgram[IO], Roots.Everything)
 objectsResource.use(_.get[TaglessProgram[IO]].program).unsafeRunSync()
 ```
 
+#### Out-of-the-box typeclass instances
+
+Note: we did not have to include either `Monad[IO]` or `Sync[IO]` instances for our program to run,
+despite the fact that `SyncInteraction` depends on `Sync[F]`.
+
+This is because `distage` includes all the relevant typeclass instances for the provided effect type by default, using implicits.
+In general whenever `cats-effect` is on the classpath and your effect type has `cats-effect` instances, they will be included, this is valid for `ZIO`, `cats.effect.IO`, `monix`, `monix-bio` and any other type with cats-effect instances.
+For ZIO, `monix-bio` and any other implementors of @ref[BIO](../bio/00_bio.md), instances of bifunctor @ref[BIO](../bio/00_bio.md) effect hierarchy will also be included.
+See @scaladoc[`DefaultModule`](izumi.distage.modules.DefaultModule) for more details.
+
+#### Effect-type polymorphism
+
 The program module is polymorphic over effect type. It can be instantiated by a different effect:
 
 ```scala mdoc:to-string
@@ -1036,6 +1156,8 @@ def chooseInterpreters(isDummy: Boolean) = {
 chooseInterpreters(true)
 ```
 
+#### Kind polymorphism
+
 Modules can be polymorphic over arbitrary kinds - use `TagKK` to abstract over bifunctors:
 
 ```scala mdoc:to-string
@@ -1056,7 +1178,7 @@ class TrifunctorModule[F[_, _, _]: Tag.auto.T] extends ModuleDef
 class EldritchModule[F[+_, -_[_, _], _[_[_, _], _], _]: Tag.auto.T] extends ModuleDef
 ```
 
-consult @scaladoc[HKTag](izumi.fundamentals.reflection.WithTags#HKTag) docs for more details.
+consult [izumi.reflect.HKTag](https://javadoc.io/doc/dev.zio/izumi-reflect_2.13/latest/izumi/reflect/HKTag.html) docs for more details.
 
 ### Cats & ZIO Integration
 
@@ -1066,6 +1188,8 @@ However, distage *won't* bring in `cats` or `zio` as dependencies if you don't a
 
 @ref[Cats Resource & ZIO ZManaged Bindings](basics.md#resource-bindings-lifecycle) also work out of the box without any magic imports.
 
+All relevant typeclass instances for chosen effect type, such as `ConcurrentEffect[F]`, are @ref[included by default](basics.md#out-of-the-box-typeclass-instances) (overridable by user bindings)
+
 Example:
 
 ```scala mdoc:reset:invisible:to-string
@@ -1081,20 +1205,20 @@ def SyncInterpreters[F[_]]: Module = Module.empty
 ```scala mdoc:to-string
 import cats.syntax.semigroup._
 import cats.effect.{ExitCode, IO, IOApp}
-import distage.{DIKey, Roots, Injector, Activation}
+import distage.{DIKey, Roots, Injector}
 
 trait AppEntrypoint {
   def run: IO[Unit]
 }
 
-object Main extends App {
-  def run(args: List[String]): IO[ExitCode] = {
+object Main extends IOApp {
+  override def run(args: List[String]): IO[ExitCode] = {
     
     // `distage.Module` has a Monoid instance
 
     val myModules = ProgramModule[IO] |+| SyncInterpreters[IO]
 
-    val plan = Injector().plan(myModules, Activation.empty, Roots(DIKey[AppEntrypoint]))
+    val plan = Injector().plan(myModules, Roots.target[AppEntrypoint])
 
     for {
       // resolveImportsF can effectfully add missing instances to an existing plan
diff --git a/doc/microsite/src/main/tut/distage/debugging.md b/doc/microsite/src/main/tut/distage/debugging.md
index 26574d7e1..5835a3db4 100644
--- a/doc/microsite/src/main/tut/distage/debugging.md
+++ b/doc/microsite/src/main/tut/distage/debugging.md
@@ -8,30 +8,36 @@ Debugging
 Use `OrderedPlan#assertValid` method to test whether the plan will execute correctly when passed to `Injector#produce`.
 
 ```scala mdoc:reset:to-string
-import distage.{DIKey, Roots, ModuleDef, Injector, Activation}
+import distage.{DIKey, Roots, ModuleDef, Injector}
 
 class A(b: B)
 class B
 
-val badModule = new ModuleDef {
+def badModule = new ModuleDef {
   make[A]
-  make[Int].fromEffect(zio.Task { ??? })
+  make[B].fromEffect(zio.Task { ??? })
 }
 
-val badPlan = Injector[cats.effect.IO]().plan(badModule, Activation.empty, Roots.Everything)
+val badPlan = Injector[cats.effect.IO]().plan(badModule, Roots.target[A])
 ```
 
 ```scala mdoc:crash:to-string
+// the effect types are mismatched - `badModule` uses `zio.Task`, but we expect `cats.effect.IO`
+
 badPlan.assertValid[cats.effect.IO]().unsafeRunSync()
 ```
 
 ```scala mdoc:to-string
-val goodModule = new ModuleDef {
+def goodModule = new ModuleDef {
   make[A]
   make[B].fromEffect(cats.effect.IO(new B))
 }
 
-val plan = Injector[cats.effect.IO]().plan(goodModule, Activation.empty, Roots.Everything)
+val plan = Injector[cats.effect.IO]().plan(goodModule, Roots.target[A])
+```
+
+```scala mdoc:to-string
+// the effect types in `goodModule` and here match now
 
 plan.assertValid[cats.effect.IO]().unsafeRunSync()
 ```
@@ -64,23 +70,26 @@ The printer highlights circular dependencies:
 To debug macros used by `distage` you may use the following Java Properties:
 
 ```bash
-sbt -Dizumi.debug.macro.rtti=true compile # fundamentals-reflection & LightTypeTag macros
-sbt -Dizumi.debug.macro.distage.constructors=true compile # izumi.distage.constructors.* macros
-sbt -Dizumi.debug.macro.distage.functoid=true compile # Functoid macro
+# izumi-reflect macros
+-Dizumi.debug.macro.rtti=true
+
+# izumi.distage.constructors.* macros
+-Dizumi.debug.macro.distage.constructors=true
+
+# Functoid macro
+-Dizumi.debug.macro.distage.functoid=true
 ```
 
 ### Graphviz rendering
 
-Add `GraphDumpBootstrapModule` to your `Injector`'s configuration to enable dumping of graphviz files with a graphical representation of the `Plan`.
+Add `GraphDumpBootstrapModule` to your `Injector`'s configuration to enable writing GraphViz files with a graphical representation of the `OrderedPlan`. Data will be saved to `./target/plan-last-full.gv` and `./target/plan-last-nogc.gv` in the current working directory.
 
-```scala mdoc:to-string
-import distage.GraphDumpBootstrapModule
+```scala mdoc:reset:to-string
+import distage.{GraphDumpBootstrapModule, Injector}
 
-val injector = Injector(GraphDumpBootstrapModule())
+Injector(GraphDumpBootstrapModule)
 ```
 
-Data will be saved dumped to `./target/plan-last-full.gv` and `./target/plan-last-nogc.gv` in current working directory. 
-
 You'll need a `GraphViz` installation to render these files into a viewable PNG images:
 
 ```bash
diff --git a/doc/microsite/src/main/tut/distage/distage-framework-docker.md b/doc/microsite/src/main/tut/distage/distage-framework-docker.md
index 8a0bc79f9..b2aeb3831 100644
--- a/doc/microsite/src/main/tut/distage/distage-framework-docker.md
+++ b/doc/microsite/src/main/tut/distage/distage-framework-docker.md
@@ -13,14 +13,11 @@ distage-framework-docker
 
 Add the `distage-framework-docker` library:
 
-@@@vars
-
-```scala
-libraryDependencies += "io.7mind.izumi" %% "distage-framework-docker" % "$izumi.version$"
-```
-
-@@@
-
+@@dependency[sbt,Maven,Gradle] {
+  group="io.7mind.izumi"
+  artifact="distage-framework-docker_2.13"
+  version="$izumi.version$"
+}
 
 ### Overview
 
@@ -32,17 +29,6 @@ Usage of `distage-framework-docker` generally follows these steps:
 3. Include the @scaladoc[`DockerSupportModule`](izumi.distage.docker.modules.DockerSupportModule) in
    the application's modules
 
-#### Setup
-
-Required imports:
-
-```scala mdoc:silent
-import izumi.distage.docker.ContainerDef
-import izumi.distage.docker.Docker
-import izumi.distage.model.definition.ModuleDef
-import izumi.reflect.TagK
-```
-
 #### 1. Create a `ContainerDef`
 
 The @scaladoc[`ContainerDef`](izumi.distage.docker.ContainerDef) is a trait used to define a Docker
@@ -58,7 +44,9 @@ additional parameters.
 
 Example [postgres](https://hub.docker.com/_/postgres/) container definition:
 
-```scala mdoc:silent
+```scala mdoc:to-string
+import izumi.distage.docker.{ContainerDef, Docker}
+
 object PostgresDocker extends ContainerDef {
   val primaryPort: Docker.DockerPort = Docker.DockerPort.TCP(5432)
 
@@ -78,7 +66,10 @@ To use this container, a module that declares this component is required:
 
 Use @scaladoc[`make`](izumi.distage.docker.ContainerDef#make) for binding in a `ModuleDef`:
 
-```scala mdoc:silent
+```scala mdoc:to-string
+import distage.ModuleDef
+import izumi.reflect.TagK
+
 class PostgresDockerModule[F[_]: TagK] extends ModuleDef {
   make[PostgresDocker.Container].fromResource {
     PostgresDocker.make[F]
@@ -100,18 +91,18 @@ modules. This module contains required component declarations and initializes th
 import cats.effect.IO
 import com.typesafe.config.ConfigFactory
 import distage.Injector
-import izumi.distage.config.AppConfigModule
+import distage.config.AppConfigModule
 import izumi.distage.docker.modules.DockerSupportModule
-import izumi.logstage.api.routing.StaticLogRouter
 import izumi.logstage.distage.LogIOModule
+import logstage.LogRouter
 
-val distageFrameworkModules = new ModuleDef {
+object DistageFrameworkModules extends ModuleDef {
   // required for docker
   include(DockerSupportModule[IO])
 
   // standard distage framework modules
-  include(AppConfigModule(ConfigFactory.defaultApplication))
-  include(LogIOModule[IO](StaticLogRouter.instance, false))
+  include(AppConfigModule(ConfigFactory.defaultApplication()))
+  include(LogIOModule[IO](LogRouter(), true))
 }
 ```
 
@@ -122,16 +113,14 @@ containers:
 def minimalExample = {
   val applicationModules = new ModuleDef {
     include(PostgresDockerModule[IO])
-    include(distageFrameworkModules)
+    include(DistageFrameworkModules)
   }
 
-  Injector[IO]()
-    .produceGet[PostgresDocker.Container](applicationModules)
-    .use {
-      container =>
-        val port = container.availablePorts.first(PostgresDocker.primaryPort)
-        IO(println(s"postgres is available on port ${port}"))
-    }
+  Injector[IO]().produceRun(applicationModules) {
+    container: PostgresDocker.Container =>
+      val port = container.availablePorts.first(PostgresDocker.primaryPort)
+      IO(println(s"postgres is available on port ${port}"))
+  }
 }
 
 minimalExample.unsafeRunSync()
@@ -145,21 +134,23 @@ dependent resource will fail with a `izumi.distage.model.exceptions.Provisioning
 The @scaladoc[`DockerProviderExtensions`](izumi.distage.docker.DockerContainer$$DockerProviderExtensions)
 provides additional APIs for modiying the container definition.
 
-#### `modifyConfig`
+#### modifyConfig
 
-Use
-@scaladoc[`modifyConfig`](izumi.distage.docker.DockerContainer$$DockerProviderExtensions#modifyConfig)
+Use @scaladoc[`modifyConfig`](izumi.distage.docker.DockerContainer$$DockerProviderExtensions#modifyConfig)
 to modify the configuration of a container. The modifier is instantiated to a `Functoid`, which
 will summon any additional dependencies.
 
 For example, to change the user of the PostgreSQL container:
 
-```scala mdoc:silent
+```scala mdoc:to-string
 class PostgresRunAsAdminModule[F[_]: TagK] extends ModuleDef {
   make[PostgresDocker.Container].fromResource {
-    PostgresDocker.make[F].modifyConfig { () => (old: PostgresDocker.Config) =>
-      old.copy(user = Some("admin"))
-    }
+    PostgresDocker
+      .make[F]
+      .modifyConfig { 
+        () => (old: PostgresDocker.Config) =>
+          old.copy(user = Some("admin"))
+      }
   }
 }
 ```
@@ -168,7 +159,7 @@ Suppose `HostPostgresData` is a component provided by the application modules. T
 added to the PostgreSQL container's mounts by adding to the additional dependencies of the provider
 magnet:
 
-```scala mdoc:silent
+```scala mdoc:to-string
 final case class HostPostgresData(path: String)
 
 class PostgresWithMountsDockerModule[F[_]: TagK] extends ModuleDef {
@@ -183,7 +174,7 @@ class PostgresWithMountsDockerModule[F[_]: TagK] extends ModuleDef {
 }
 ```
 
-#### `dependOnDocker`
+#### dependOnDocker
 
 @scaladoc[`dependOnDocker`](izumi.distage.docker.DockerContainer$$DockerProviderExtensions#dependOnDocker)
 adds a dependency on a given Docker container. `distage` ensures the requested container is available
@@ -192,7 +183,7 @@ before the dependent is provided.
 For example, suppose a system under test requires both PostgreSQL and Elasticsearch. One option is to
 use `dependOnDocker` to declare the Elasticsearch container depends on the PostgreSQL container:
 
-```scala mdoc:silent
+```scala mdoc:to-string
 object ElasticSearchDocker extends ContainerDef {
   val ports = Seq(9200, 9300)
 
@@ -235,6 +226,10 @@ The @scaladoc[availablePorts](izumi.distage.docker.DockerContainer#availablePort
 the container resource are the mapped ports that passed the health check. This is a map
 from a `Docker.DockerPort` provided in the config to a host and port. For example:
 
+```scala mdoc:invisible
+def _ref = (_: ElasticSearchDocker.Container).availablePorts.first(izumi.distage.docker.Docker.DockerPort.TCP(9020))
+```
+
 ```scala
 val port = container.availablePorts.first(PostgresDocker.primaryPort)
 ```
@@ -257,7 +252,9 @@ import doobie.Transactor
 import doobie.syntax.connectionio._
 import doobie.syntax.string._
 
-class PostgresExampleApp(xa: Transactor[IO]) {
+final class PostgresExampleApp(
+  xa: Transactor[IO]
+) {
   def plusOne(a: Int): IO[Int] = {
     sql"select ${a} + 1".query[Int].unique.transact(xa)
   }
@@ -279,7 +276,7 @@ final case class PostgresServerConfig(
   password: String,
 )
 
-class TransactorFromConfigModule extends ModuleDef {
+object TransactorFromConfigModule extends ModuleDef {
   make[Transactor[IO]].from {
     (config: PostgresServerConfig, contextShift: ContextShift[IO]) =>
       implicit val CS = contextShift
@@ -300,9 +297,8 @@ Note that the above code is agnostic of environment. Provided a `PostgresServerC
 An integration test would use a module that provides the `PostgresServerConfig` from a
 `PostgresDocker.Container`:
 
-```scala mdoc:silent
-
-class IntegUsingDockerModule extends ModuleDef {
+```scala mdoc:to-string
+object PostgresUsingDockerModule extends ModuleDef {
   make[PostgresServerConfig].from {
     container: PostgresDocker.Container => {
       val knownAddress = container.availablePorts.first(PostgresDocker.primaryPort)
@@ -328,20 +324,21 @@ Using `distage-testkit` the test would be written like this:
 import izumi.distage.testkit.scalatest.{AssertCIO, DistageSpecScalatest}
 import distage.DIKey
 
-class PostgresExampleAppIntegTest extends DistageSpecScalatest[IO] with AssertCIO {
+class PostgresExampleAppIntegrationTest extends DistageSpecScalatest[IO] with AssertCIO {
   override def config = super.config.copy(
     moduleOverrides = new ModuleDef {
-      include(new TransactorFromConfigModule)
-      include(new IntegUsingDockerModule)
-      include(distageFrameworkModules)
+      include(TransactorFromConfigModule)
+      include(PostgresUsingDockerModule)
+      include(DistageFrameworkModules)
       make[PostgresExampleApp]
     },
     memoizationRoots = Set(
       DIKey[PostgresServerConfig]
-    )
+    ),
   )
 
   "distage docker" should {
+  
     "support integration tests using containers" in {
       app: PostgresExampleApp =>
         for {
@@ -349,6 +346,7 @@ class PostgresExampleAppIntegTest extends DistageSpecScalatest[IO] with AssertCI
           _ <- assertIO(v == 2)
         } yield ()
     }
+    
   }
 }
 ```
@@ -359,15 +357,17 @@ using:
 ```scala mdoc:to-string
 def postgresDockerIntegrationExample = {
   val applicationModules = new ModuleDef {
-    include(new TransactorFromConfigModule)
-    include(new IntegUsingDockerModule)
-    include(distageFrameworkModules)
+    include(TransactorFromConfigModule)
+    include(PostgresUsingDockerModule)
+    include(DistageFrameworkModules)
+   
     make[PostgresExampleApp]
   }
 
-  Injector[IO]()
-    .produceGet[PostgresExampleApp](applicationModules)
-    .use(app => app.run)
+  Injector[IO]().produceRun(applicationModules) {
+    app: PostgresExampleApp =>
+      app.run
+  }
 }
 
 postgresDockerIntegrationExample.unsafeRunSync()
@@ -416,7 +416,7 @@ create the required network and add each container to that network.
 A minimal @scaladoc[`ContainerNetworkDef`](izumi.distage.docker.ContainerNetworkDef) uses the default
 configuration.
 
-```mdoc:silent
+```mdoc:to-string
 object TestClusterNetwork extends ContainerNetworkDef {
   override def config: Config = Config()
 }
@@ -432,7 +432,7 @@ A container will be connected to all networks in the `networks` of the `config`.
 @scaladoc[`connectToNetwork`](izumi.distage.docker.DockerContainer$$DockerProviderExtensions#connectToNetwork)
 adds a dependency on a network defined by a `ContainerNetworkDef`, as in this example:
 
-```mdoc:silent
+```mdoc:to-string
 class TestClusterNetworkModule[F[_]: TagK] extends ModuleDef {
   make[TestClusterNetwork.Network].fromResource {
     TestClusterNetwork.make[F]
@@ -455,7 +455,7 @@ Container networks, like containers, are reused by default. If there is an exist
 matches a definition then that network will be used. This can be disabled by setting the `reuse`
 configuration to `DockerReusePolicy.KillOnExitNoReuse`:
 
-```mdoc:silent
+```mdoc:to-string
 object AlwaysFreshNetwork extends ContainerNetworkDef {
   override def config: Config = Config(reuse = Docker.DockerReusePolicy.KillOnExitNoReuse)
 }
@@ -558,11 +558,11 @@ When utilizing reuse, the performance cost of inspecting the Docker system can b
 example, in this integration test the container resource is not reconstructed for each test. Because the
 resource is not reconstructed there is no repeated inspection of the Docker system.
 
-```scala mdoc:silent
+```scala mdoc:to-string
 class NoReuseByMemoizationExampleTest extends DistageSpecScalatest[IO] {
   override def config = super.config.copy(
     moduleOverrides = new ModuleDef {
-      include(distageFrameworkModules)
+      include(DistageFrameworkModules)
       include(PostgresDockerModule[IO])
     },
     memoizationRoots = Set(
@@ -610,8 +610,9 @@ docker rm -f $(docker ps -q -a -f 'label=distage.type')
 //  - {type.izumi.distage.docker.DockerClientWrapper[=λ %0 → IO[+0]]} (distage-framework-docker.md:40), MissingInstanceException: Instance is not available in the object graph: {type.izumi.distage.docker.DockerClientWrapper[=λ %0 → IO[+0]]}.
 ```
 
-The `DockerSupportModule` was not included in the application modules. The component
-`DockerClientWrapper` is provided by `izumi.distage.docker.modules.DockerSupportModule`.
+This error means that @scaladoc[`DockerSupportModule`](izumi.distage.docker.modules.DockerSupportModule) hasn't been `include`d with the application modules.
+
+`DockerClientWrapper` component is provided by @scaladoc[`izumi.distage.docker.modules.DockerSupportModule`](izumi.distage.docker.modules.DockerSupportModule).
 
 ### References
 
diff --git a/doc/microsite/src/main/tut/distage/distage-framework.md b/doc/microsite/src/main/tut/distage/distage-framework.md
index 68a71ab7d..0ed424a62 100644
--- a/doc/microsite/src/main/tut/distage/distage-framework.md
+++ b/doc/microsite/src/main/tut/distage/distage-framework.md
@@ -8,50 +8,62 @@ distage-framework
 A "Role" is an entrypoint for a specific application hosted in a larger software suite.
 Bundling multiple roles in a single `.jar` file can simplify deployment and operations.
 
-`distage-framework` module contains the distage Role API:
+Add `distage-framework` library for Roles API:
 
-@@@vars
+@@dependency[sbt,Maven,Gradle] {
+  group="io.7mind.izumi"
+  artifact="distage-framework_2.13"
+  version="$izumi.version$"
+}
 
-```scala
-libraryDependencies += "io.7mind.izumi" %% "distage-framework" % "$izumi.version$"
+With default @scaladoc[RoleAppMain](izumi.distage.roles.RoleAppMain), roles to launch are specified on the command-line:
+
+```
+./launcher :rolename1 :rolename2`
 ```
 
-@@@
+Only the components required by the specified roles will be created, everything else will be pruned. (see: @ref[Dependency Pruning](advanced-features.md#dependency-pruning))
 
-With default @scaladoc[RoleAppLauncherImpl](izumi.distage.roles.RoleAppLauncherImpl), roles to launch are specified on the command-line: `./launcher role1 role2 role3`.
-Only the components required by the specified roles will be created, everything else will be pruned. (see: @ref[GC](advanced-features.md#garbage-collection))
+@scaladoc[BundledRolesModule](izumi.distage.roles.bundled.BundledRolesModule) contains two example roles: 
 
-Two roles are bundled by default: @scaladoc[Help](izumi.distage.roles.examples.Help) and @scaladoc[ConfigWriter](izumi.distage.roles.examples.ConfigWriter).
+- @scaladoc[Help](izumi.distage.roles.bundled.Help) - prints help message when launched `./launcher :help` 
+- @scaladoc[ConfigWriter](izumi.distage.roles.bundled.ConfigWriter) - writes reference config into files, split by roles (includes only parts of the config used by the application)
 
-Further reading: [Roles: a viable alternative to Microservices](https://github.com/7mind/slides/blob/master/02-roles/roles.pdf)
+Further reading: 
+- [Roles: a viable alternative to Microservices and Monoliths](https://github.com/7mind/slides/blob/master/02-roles/roles.pdf)
 
 ### Typesafe Config
 
-`distage-extension-config` library allows summoning case classes and sealed traits from `typesafe-config` configuration
-
-To use it, add `distage-extension-config` library:
-
-@@@vars
-
-```scala
-libraryDependencies += "io.7mind.izumi" %% "distage-extension-config" % "$izumi.version$"
-```
+`distage-extension-config` library allows parsing case classes and sealed traits from `typesafe-config` configuration files.
 
-@@@
+To use it, add the `distage-extension-config` library:
 
+@@dependency[sbt,Maven,Gradle] {
+  group="io.7mind.izumi"
+  artifact="distage-extension-config_2.13"
+  version="$izumi.version$"
+}
 
-Use helper functions in `ConfigModuleDef` to parse the Typesafe Config instance bound to `AppConfig` into case classes:
+Use helper functions in `ConfigModuleDef`, `makeConfig` and `make[_].fromConfig` to parse the bound `AppConfig` instance into case classes, sealed traits or literals:
 
 ```scala mdoc:reset-object:to-string
-import distage.{DIKey, Roots, ModuleDef, Id, Injector}
+import distage.{Id, Injector}
 import distage.config.{AppConfig, ConfigModuleDef}
 import com.typesafe.config.ConfigFactory
 
-final case class Conf(name: String, age: Int)
+final case class Conf(
+  name: String,
+  age: Int,
+)
 
-final case class OtherConf(other: Boolean)
+final case class OtherConf(
+  other: Boolean
+)
 
-final class ConfigPrinter(conf: Conf, otherConf: OtherConf @Id("other")) {
+final class ConfigPrinter(
+  conf: Conf,
+  otherConf: OtherConf @Id("other"),
+) {
   def print() = {
     println(s"name: ${conf.name}, age: ${conf.age}, other: ${otherConf.other}")
   }
@@ -65,13 +77,15 @@ val module = new ConfigModuleDef {
   makeConfig[OtherConf]("conf").named("other")
 
   // add config instance
-  make[AppConfig].from(AppConfig(ConfigFactory.parseString(
-    """conf {
-      |  name = "John"
-      |  age = 33
-      |  other = true
-      |}""".stripMargin
-  )))
+  make[AppConfig].from(AppConfig(
+    ConfigFactory.parseString(
+      """conf {
+        |  name = "John"
+        |  age = 33
+        |  other = true
+        |}""".stripMargin
+    )
+  ))
 }
 
 Injector().produceRun(module) {
@@ -81,30 +95,33 @@ Injector().produceRun(module) {
 ```
 
 Automatic derivation of config codecs is based on [pureconfig-magnolia](https://github.com/pureconfig/pureconfig).
-Pureconfig codecs for a type will be used if they exist.
 
-You don't have to explicitly `make[AppConfig]` in @ref[distage-testkit](distage-testkit.md)'s tests and in @ref[distage-framework](distage-framework.md)'s Roles, unless you want to override default behavior. By default, tests and roles will try to read the configurations from resources with the following names, in order:
+When a given type already has custom `pureconfig.ConfigReader` instances in implicit scope, they will be used, otherwise they will be derived automatically.
 
-```
-- $roleName.conf
-- $roleName-reference.conf
-- $roleName-reference-dev.conf
-- application.conf
-- application-reference.conf
-- application-reference-dev.conf
-- common.conf
-- common-reference.conf
-- common-reference-dev.conf
-```
+You don't have to explicitly `make[AppConfig]` in @ref[distage-testkit](distage-testkit.md)'s tests and in @ref[distage-framework](distage-framework.md)'s Roles, unless you want to override default behavior.
+
+By default, tests and roles will try to read the configurations from resources with the following names, in order:
+
+- `${roleName}.conf`
+- `${roleName}-reference.conf`
+- `${roleName}-reference-dev.conf`
+- `application.conf`
+- `application-reference.conf`
+- `application-reference-dev.conf`
+- `common.conf`
+- `common-reference.conf`
+- `common-reference-dev.conf`
 
 ```scala mdoc:reset:invisible
 type _ref = izumi.distage.testkit.TestConfig
+def _ref = (_: izumi.distage.testkit.TestConfig).configBaseName
 ```
 
-Where `distage-testkit` uses @scaladoc[`TestConfig#testBaseName`](izumi.distage.testkit.TestConfig#testBaseName) instead of `roleName`.
+Where `distage-testkit` uses @scaladoc[`TestConfig#configBaseName`](izumi.distage.testkit.TestConfig#configBaseName) instead of `roleName`.
 
-When explicit configs are passed to the role launcher on the command-line using the `-c` option, they have higher priority than all the reference configs.
-Role-specific configs on the command-line (`-c` option after `:role` argument) override global command-line configs (`-c` option given before the first `:role` argument).
+Explicit config files passed to the role launcher `-c file.conf` the command-line flag have a higher priority than resource configs.
+
+Role-specific configs on the command-line, passed via `-c file.conf` option *after* a `:roleName` argument, in turn have a higher priority than the explicit config files passed *before* the first `:role` argument.
 
 Example:
 
@@ -112,7 +129,7 @@ Example:
   ./launcher -c global.conf :role1 -c role1.conf :role2 -c role2.conf
 ```
 
-Here configs will be loaded in the following order, with higher priority earlier:
+Here configs will be loaded in the following order, with higher priority earlier and earlier configs overriding the values in later configs:
 
   - explicits: `role1.conf`, `role2.conf`, `global.conf`,
   - resources: `role1[-reference,-dev].conf`, `role2[-reference,-dev].conf`, ,`application[-reference,-dev].conf`, `common[-reference,-dev].conf`
@@ -126,13 +143,11 @@ as they're defined in a separate module.
 
 To use plugins, first add the `distage-extension-plugins` library:
 
-@@@vars
-
-```scala
-libraryDependencies += "io.7mind.izumi" %% "distage-extension-plugins" % "$izumi.version$"
-```
-
-@@@
+@@dependency[sbt,Maven,Gradle] {
+  group="io.7mind.izumi"
+  artifact="distage-extension-plugins_2.13"
+  version="$izumi.version$"
+}
 
 Create a module extending the `PluginDef` trait instead of `ModuleDef`:
 
@@ -140,11 +155,11 @@ Create a module extending the `PluginDef` trait instead of `ModuleDef`:
 import com.example.petstore._
 ```
 
-```scala mdoc:to-string
-// package com.example.petstore
+```scala mdoc:fakepackage:to-string
+"fakepackage com.example.petstore": Unit
 
-import distage._
-import distage.plugins._
+import distage.Injector
+import distage.plugins.{PluginConfig, PluginDef, PluginLoader}
 
 object PetStorePlugin extends PluginDef {
   make[PetRepository]
@@ -153,17 +168,18 @@ object PetStorePlugin extends PluginDef {
 }
 ```
 
-Collect all `PluginDefs` in a package:
+Collect all the `PluginDef` classes and objects in a package:
 
 ```scala mdoc:to-string
 val pluginConfig = PluginConfig.cached(
-  packagesEnabled = Seq("com.example.petstore") // packages to scan
+  // packages to scan
+  packagesEnabled = Seq("com.example.petstore")
 )
 
 val appModules = PluginLoader().load(pluginConfig)
 ```
 
-Execute collected modules as usual:
+Wire the collected modules as usual:
 
 ```scala mdoc:to-string
 // combine all modules into one
@@ -183,22 +199,25 @@ An experimental compile-time verification API is available in the `distage-frame
 
 To use it add `distage-framework` library:
 
-@@@vars
-
-```scala
-libraryDependencies += "io.7mind.izumi" %% "distage-framework" % "$izumi.version$"
-```
+@@dependency[sbt,Maven,Gradle] {
+  group="io.7mind.izumi"
+  artifact="distage-framework_2.13"
+  version="$izumi.version$"
+}
 
-@@@
+Only plugins defined in a different module can be checked at compile-time.
 
-Only plugins defined in a different module can be checked at compile-time, `test` scope counts as a different module.
+`test` scope counts as a separate module.
 
 Example:
 
 In main scope:
 
-```scala mdoc:reset:to-string
-// package com.example
+```scala mdoc:reset:invisible:to-string
+```
+
+```scala mdoc:fakepackage:to-string
+"fakepackage com.example": Unit
 
 import distage.DIKey
 import distage.StandardAxis.Env
@@ -240,8 +259,11 @@ config {
 
 In test scope:
 
-```scala mdoc:reset-object:to-string
-// package com.example.test
+```scala mdoc:reset-object:invisible:to-string
+```
+
+```scala mdoc:fakepackage:to-string
+"fakepackage com.example.test": Unit
 
 import com.example._
 import org.scalatest.wordspec.AnyWordSpec
diff --git a/doc/microsite/src/main/tut/distage/reference.md b/doc/microsite/src/main/tut/distage/reference.md
index 02fc90b65..3a216fe10 100644
--- a/doc/microsite/src/main/tut/distage/reference.md
+++ b/doc/microsite/src/main/tut/distage/reference.md
@@ -8,7 +8,9 @@ Singleton bindings:
   - `make[X]` = create X using its constructor
   - `make[X].from[XImpl]` = bind X to its subtype XImpl using XImpl's constructor
   - `make[X].from(myX)` = bind X to an already existing instance `myX`
-  - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]]
+  - `make[X].from { y: Y => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]] requesting an Y parameter
+  - `make[X].from { y: Y @Id("special") => new X(y) }` = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter
+  - `make[X].from { y: Y => new X(y) }`.annotateParameter[Y]("special") = bind X to an instance of X constructed by a given [[izumi.distage.model.providers.Functoid Functoid]], requesting a named "special" Y parameter
   - `make[X].named("special")` = bind a named instance of X. It can then be summoned using [[Id]] annotation.
   - `make[X].using[X]("special")` = bind X to refer to another already bound named instance at key `[X].named("special")`
   - `make[X].fromEffect(X.create[F]: F[X])` = create X using a purely-functional effect `X.create` in `F` monad
diff --git a/logstage/logstage-core/.jvm/src/main/scala/izumi/logstage/sink/QueueingSink.scala b/logstage/logstage-core/.jvm/src/main/scala/izumi/logstage/sink/QueueingSink.scala
index 0206c3f05..f134089a8 100644
--- a/logstage/logstage-core/.jvm/src/main/scala/izumi/logstage/sink/QueueingSink.scala
+++ b/logstage/logstage-core/.jvm/src/main/scala/izumi/logstage/sink/QueueingSink.scala
@@ -6,8 +6,9 @@ import java.util.concurrent.atomic.AtomicBoolean
 import izumi.fundamentals.platform.console.TrivialLogger
 import izumi.fundamentals.platform.console.TrivialLogger.Config
 import izumi.fundamentals.platform.language.Quirks._
+import izumi.logstage.DebugProperties
 import izumi.logstage.api.Log
-import izumi.logstage.api.logger.{LogRouter, LogSink}
+import izumi.logstage.api.logger.LogSink
 
 import scala.concurrent.duration._
 
@@ -19,7 +20,7 @@ class QueueingSink(target: LogSink, sleepTime: FiniteDuration = 50.millis) exten
   private val maxBatchSize = 100
   private val stop = new AtomicBoolean(false)
 
-  private val fallback = TrivialLogger.make[FallbackConsoleSink](LogRouter.fallbackPropertyName, Config(forceLog = true))
+  private val fallback = TrivialLogger.make[FallbackConsoleSink](DebugProperties.`izumi.logstage.routing.log-failures`.name, Config(forceLog = true))
 
   private val pollingThread = {
     val result = new Thread(new ThreadGroup("logstage"), poller(), "logstage-poll")
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala
index 38a9c06e5..90f2f4a91 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/DebugProperties.scala
@@ -6,4 +6,5 @@ object DebugProperties extends properties.DebugProperties {
   final val `izumi.debug.macro.logstage` = BoolProperty("izumi.debug.macro.logstage")
   final val `izumi.logstage.rendering.colored` = BoolProperty("izumi.logstage.rendering.colored")
   final val `izumi.logstage.rendering.colored.forced` = BoolProperty("izumi.logstage.rendering.colored.forced")
+  final val `izumi.logstage.routing.log-failures` = BoolProperty("izumi.logstage.routing.log-failures")
 }
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/EncodingAwareAbstractLogger.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/EncodingAwareAbstractLogger.scala
index 5b0886561..172440e93 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/EncodingAwareAbstractLogger.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/EncodingAwareAbstractLogger.scala
@@ -4,7 +4,6 @@ import izumi.logstage.api.Log.CustomContext
 import izumi.logstage.api.rendering.AnyEncoded
 
 trait EncodingAwareAbstractLogger[E <: AnyEncoded] extends AbstractLogger {
-
   override type Self <: EncodingAwareAbstractLogger[E]
 
   final def withCustomContext(context: (String, E)*): Self = withCustomContextMap(context.toMap)
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala
index 882fdfdf8..cbc5ca069 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/logger/LogRouter.scala
@@ -2,7 +2,10 @@ package izumi.logstage.api.logger
 
 import izumi.fundamentals.platform.console.TrivialLogger
 import izumi.fundamentals.platform.console.TrivialLogger.Config
+import izumi.logstage.DebugProperties
 import izumi.logstage.api.Log
+import izumi.logstage.api.routing.ConfigurableLogRouter
+import izumi.logstage.sink.ConsoleSink
 
 trait LogRouter extends AutoCloseable {
   def log(entry: Log.Entry): Unit
@@ -12,16 +15,12 @@ trait LogRouter extends AutoCloseable {
 }
 
 object LogRouter {
-  final val fallbackPropertyName = "izumi.logstage.routing.log-failures"
-
-  final val nullRouter: LogRouter = new LogRouter {
-    override def acceptable(id: Log.LoggerId, messageLevel: Log.Level): Boolean = false
-
-    override def log(entry: Log.Entry): Unit = {}
+  def apply(threshold: Log.Level = Log.Level.Trace, sink: LogSink = ConsoleSink.ColoredConsoleSink, levels: Map[String, Log.Level] = Map.empty): ConfigurableLogRouter = {
+    ConfigurableLogRouter(threshold, Seq(sink), levels)
   }
 
-  final val debugRouter: LogRouter = new LogRouter {
-    private val fallback: TrivialLogger = TrivialLogger.make[LogRouter](LogRouter.fallbackPropertyName, Config(forceLog = true))
+  lazy val debugRouter: LogRouter = new LogRouter {
+    private val fallback: TrivialLogger = TrivialLogger.make[LogRouter](DebugProperties.`izumi.logstage.routing.log-failures`.name, Config(forceLog = true))
 
     override def acceptable(id: Log.LoggerId, messageLevel: Log.Level): Boolean = true
 
@@ -29,4 +28,9 @@ object LogRouter {
       fallback.log(entry.message.template.raw(entry.message.args.map(_.value): _*) + s"\n{{ ${entry.toString} }}\n")
     }
   }
+
+  lazy val nullRouter: LogRouter = new LogRouter {
+    override def acceptable(id: Log.LoggerId, messageLevel: Log.Level): Boolean = false
+    override def log(entry: Log.Entry): Unit = {}
+  }
 }
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala
index 28888bddb..39d80b039 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/routing/ConfigurableLogRouter.scala
@@ -2,6 +2,7 @@ package izumi.logstage.api.routing
 
 import izumi.fundamentals.platform.console.TrivialLogger
 import izumi.fundamentals.platform.console.TrivialLogger.Config
+import izumi.logstage.DebugProperties
 import izumi.logstage.api.Log
 import izumi.logstage.api.config.{LogConfigService, LoggerConfig, LoggerPathConfig}
 import izumi.logstage.api.logger.{LogRouter, LogSink}
@@ -13,7 +14,7 @@ import scala.util.control.NonFatal
 class ConfigurableLogRouter(
   logConfigService: LogConfigService
 ) extends LogRouter {
-  private final val fallback = TrivialLogger.make[FallbackConsoleSink](LogRouter.fallbackPropertyName, Config(forceLog = true))
+  private final val fallback = TrivialLogger.make[FallbackConsoleSink](DebugProperties.`izumi.logstage.routing.log-failures`.name, Config(forceLog = true))
 
   override def log(entry: Log.Entry): Unit = {
     val sinks = logConfigService
@@ -41,24 +42,25 @@ class ConfigurableLogRouter(
 }
 
 object ConfigurableLogRouter {
-
-  final def apply(threshold: Log.Level, sink: LogSink = ConsoleSink.ColoredConsoleSink, levels: Map[String, Log.Level] = Map.empty): ConfigurableLogRouter = {
+  def apply(threshold: Log.Level = Log.Level.Trace, sink: LogSink = ConsoleSink.ColoredConsoleSink, levels: Map[String, Log.Level] = Map.empty): ConfigurableLogRouter = {
     ConfigurableLogRouter(threshold, Seq(sink), levels)
   }
 
-  final def apply(threshold: Log.Level, sinks: Seq[LogSink]): ConfigurableLogRouter = {
+  def apply(threshold: Log.Level, sinks: Seq[LogSink]): ConfigurableLogRouter = {
     ConfigurableLogRouter(threshold, sinks, Map.empty[String, Log.Level])
   }
 
   @nowarn("msg=Unused import")
-  final def apply(threshold: Log.Level, sinks: Seq[LogSink], levels: Map[String, Log.Level]): ConfigurableLogRouter = {
+  def apply(threshold: Log.Level, sinks: Seq[LogSink], levels: Map[String, Log.Level]): ConfigurableLogRouter = {
     import scala.collection.compat._
-    val levelConfigs = levels.view.mapValues(lvl => LoggerPathConfig(lvl, sinks)).toMap
+
     val rootConfig = LoggerPathConfig(threshold, sinks)
+    val levelConfigs = levels.view.mapValues(lvl => LoggerPathConfig(lvl, sinks)).toMap
+
     val configService = new LogConfigServiceImpl(LoggerConfig(rootConfig, levelConfigs))
-    val router = ConfigurableLogRouter(configService)
-    router
+
+    ConfigurableLogRouter(configService)
   }
 
-  final def apply(logConfigService: LogConfigService): ConfigurableLogRouter = new ConfigurableLogRouter(logConfigService)
+  def apply(logConfigService: LogConfigService): ConfigurableLogRouter = new ConfigurableLogRouter(logConfigService)
 }
diff --git a/logstage/logstage-core/src/main/scala/logstage/LogCreateIO.scala b/logstage/logstage-core/src/main/scala/logstage/LogCreateIO.scala
index adb2acab5..14a09de5c 100644
--- a/logstage/logstage-core/src/main/scala/logstage/LogCreateIO.scala
+++ b/logstage/logstage-core/src/main/scala/logstage/LogCreateIO.scala
@@ -38,5 +38,5 @@ object LogCreateIO {
     * @see https://github.com/scala/bug/issues/11427
     */
   implicit def limitedCovariance[F[+_, _], E](implicit log: LogCreateBIO[F]): LogCreateIO[F[E, ?]] = log.widen
-  implicit def covarianceConversion[G[_], F[_]](log: LogCreateIO[F])(implicit @unused ev: F[_] <:< G[_]): LogCreateIO[G] = log.widen
+  implicit def covarianceConversion[G[_], F[_]](log: LogCreateIO[F])(implicit ev: F[_] <:< G[_]): LogCreateIO[G] = log.widen
 }
diff --git a/logstage/logstage-core/src/main/scala/logstage/LogIO.scala b/logstage/logstage-core/src/main/scala/logstage/LogIO.scala
index b3203fa33..a309768ac 100644
--- a/logstage/logstage-core/src/main/scala/logstage/LogIO.scala
+++ b/logstage/logstage-core/src/main/scala/logstage/LogIO.scala
@@ -59,5 +59,5 @@ object LogIO {
     * @see https://github.com/scala/bug/issues/11427
     */
   implicit def limitedCovariance[F[+_, _], E](implicit log: LogBIO[F]): LogIO[F[E, ?]] = log.widen
-  implicit def covarianceConversion[G[_], F[_]](log: LogIO[F])(implicit @unused ev: F[_] <:< G[_]): LogIO[G] = log.widen
+  implicit def covarianceConversion[G[_], F[_]](log: LogIO[F])(implicit ev: F[_] <:< G[_]): LogIO[G] = log.widen
 }
diff --git a/logstage/logstage-core/src/main/scala/logstage/UnsafeLogIO.scala b/logstage/logstage-core/src/main/scala/logstage/UnsafeLogIO.scala
index 0f70da12e..9340171f0 100644
--- a/logstage/logstage-core/src/main/scala/logstage/UnsafeLogIO.scala
+++ b/logstage/logstage-core/src/main/scala/logstage/UnsafeLogIO.scala
@@ -50,5 +50,5 @@ object UnsafeLogIO {
     * @see https://github.com/scala/bug/issues/11427
     */
   implicit def limitedCovariance[F[+_, _], E](implicit log: UnsafeLogBIO[F]): UnsafeLogIO[F[E, ?]] = log.widen
-  implicit def covarianceConversion[G[_], F[_]](log: UnsafeLogIO[F])(implicit @unused ev: F[_] <:< G[_]): UnsafeLogIO[G] = log.widen
+  implicit def covarianceConversion[G[_], F[_]](log: UnsafeLogIO[F])(implicit ev: F[_] <:< G[_]): UnsafeLogIO[G] = log.widen
 }
diff --git a/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala b/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala
index c469ac784..121823b61 100644
--- a/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala
+++ b/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala
@@ -1,7 +1,7 @@
 package logstage.strict
 
 import izumi.functional.mono.SyncSafe
-import izumi.fundamentals.platform.language.{CodePositionMaterializer, unused}
+import izumi.fundamentals.platform.language.CodePositionMaterializer
 import izumi.logstage.api.Log._
 import izumi.logstage.api.logger.{AbstractLogger, AbstractMacroStrictLoggerF}
 import izumi.logstage.api.rendering.StrictEncoded
@@ -14,6 +14,8 @@ trait LogIOStrict[F[_]] extends EncodingAwareAbstractLogIO[F, StrictEncoded] wit
   override type Self[f[_]] = LogIOStrict[f]
 
   final def raw: LogIORaw[F, StrictEncoded] = new LogIORaw(this)
+
+  override def widen[G[_]](implicit ev: F[_] <:< G[_]): LogIOStrict[G] = this.asInstanceOf[LogIOStrict[G]]
 }
 
 object LogIOStrict {
@@ -58,5 +60,5 @@ object LogIOStrict {
     * @see https://github.com/scala/bug/issues/11427
     */
   implicit def limitedCovariance[F[+_, _], E](implicit log: LogBIOStrict[F]): LogIOStrict[F[E, ?]] = log.asInstanceOf[LogIOStrict[F[E, ?]]]
-  implicit def covarianceConversion[G[_], F[_]](log: LogIOStrict[F])(implicit @unused ev: F[_] <:< G[_]): LogIOStrict[G] = log.asInstanceOf[LogIOStrict[G]]
+  implicit def covarianceConversion[G[_], F[_]](log: LogIOStrict[F])(implicit ev: F[_] <:< G[_]): LogIOStrict[G] = log.widen
 }


Test patch:
diff --git a/distage/distage-framework/src/test/scala/izumi/distage/roles/test/StaticPluginLoaderTest.scala b/distage/distage-framework/src/test/scala/izumi/distage/roles/test/StaticPluginLoaderTest.scala
index 83f9cbef8..82978adcd 100644
--- a/distage/distage-framework/src/test/scala/izumi/distage/roles/test/StaticPluginLoaderTest.scala
+++ b/distage/distage-framework/src/test/scala/izumi/distage/roles/test/StaticPluginLoaderTest.scala
@@ -25,7 +25,7 @@ class StaticPluginLoaderTest extends AnyWordSpec {
     }
 
     "Prepopulate plugins list in compile time (PluginConfig)" in {
-      val plugins = PluginLoader().load(PluginConfig.staticallyAvailablePlugins("com.github.pshirshov.test.plugins"))
+      val plugins = PluginLoader().load(PluginConfig.static("com.github.pshirshov.test.plugins"))
       assert(plugins.size == 6)
       assert(
         plugins.map(_.getClass).toSet == Set(
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala
index 1dfcfac26..f7c04180d 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala
@@ -1,6 +1,5 @@
 package izumi.distage.testkit.services.dstest
 
-import distage.TagK
 import distage.config.AppConfig
 import izumi.distage.framework.config.PlanningOptions
 import izumi.distage.framework.model.ActivationInfo
@@ -10,6 +9,7 @@ import izumi.distage.roles.model.meta.RolesInfo
 import izumi.fundamentals.platform.cli.model.raw.RawAppArgs
 import izumi.logstage.api.IzLogger
 import izumi.logstage.api.logger.LogRouter
+import izumi.reflect.TagK
 
 trait BootstrapFactory {
   def makeConfigLoader(configResourceName: String, logger: IzLogger): ConfigLoader
@@ -38,7 +38,7 @@ object BootstrapFactory {
       activation: Activation,
     ): ModuleProvider = {
       // roles descriptor is not actually required there, we bind it just in case someone wish to inject a class depending on it
-      new ModuleProvider.Impl(
+      new ModuleProvider.Impl[F](
         logRouter = logRouter,
         config = config,
         roles = roles,
diff --git a/doc/microsite/src/main/tut/distage/distage-testkit.md b/doc/microsite/src/main/tut/distage/distage-testkit.md
index ce3f84ac2..ea7e0454e 100644
--- a/doc/microsite/src/main/tut/distage/distage-testkit.md
+++ b/doc/microsite/src/main/tut/distage/distage-testkit.md
@@ -801,7 +801,7 @@ dependencies. Integration checks are executed only in `distage-testkit` tests an
 - Slides for [Scala, Functional Programming and Team Productivity
   ](https://www.slideshare.net/7mind/scala-functional-programming-and-team-productivity)
 - [distage Example Project](https://github.com/7mind/distage-example) project shows how to use
-  `distage-testkit`
+  `distage`, `distage-testkit` & `distage-framework-docker`
 - The [Hyper-pragmatic Pure FP Testing with
   distage-testkit](https://www.youtube.com/watch?v=CzpvjkUukAs) talk is an overview of the concepts,
   design and usage.